diff --git a/.github/workflows/docs-amplify.yaml b/.github/workflows/docs-amplify.yaml new file mode 100644 index 0000000000000..3ca12933b50c1 --- /dev/null +++ b/.github/workflows/docs-amplify.yaml @@ -0,0 +1,42 @@ +name: Docs Preview +on: + pull_request: + paths: + - 'docs/**' + - .github/workflows/docs-amplify.yaml + workflow_dispatch: + +permissions: + pull-requests: write + id-token: write + +jobs: + amplify-preview: + name: Prepare Amplify preview URL + runs-on: ubuntu-22.04-2core-arm64 + environment: docs-amplify + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4 + with: + aws-region: us-west-2 + role-to-assume: ${{ vars.IAM_ROLE }} + + - name: Create Amplify preview environment + uses: gravitational/shared-workflows/tools/amplify-preview@tools/amplify-preview/v0.0.1 + continue-on-error: true + with: + app_ids: ${{ vars.AMPLIFY_APP_IDS }} + create_branches: "true" + github_token: ${{ secrets.GITHUB_TOKEN }} + wait: "true" + + - name: Print failure message + if: failure() + env: + ERR_TITLE: Teleport Docs preview build failed + ERR_MESSAGE: >- + Please refer to the following documentation for help: https://www.notion.so/goteleport/How-to-Amplify-deployments-162fdd3830be8096ba72efa1a49ee7bc?pvs=4 + run: | + echo ::error title=$ERR_TITLE::$ERR_MESSAGE + exit 1 diff --git a/.golangci.yml b/.golangci.yml index 2300891f36ec0..01e8fe99ef061 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -9,54 +9,55 @@ issues: exclude-dirs-use-default: false exclude-rules: - linters: - - gosimple - text: "S1002: should omit comparison to bool constant" + - gosimple + text: 'S1002: should omit comparison to bool constant' - linters: - - revive - text: "exported: exported const" + - revive + text: 'exported: exported const' # TODO(hugoShaka): Remove once https://github.com/dominikh/go-tools/issues/1294 is fixed - linters: - - unused + - unused path: 'integrations/operator/controllers/resources/(.+)_controller_test\.go' # TODO(codingllama): Remove once we move to grpc.NewClient. - linters: [staticcheck] - text: "grpc.Dial is deprecated" + text: 'grpc.Dial is deprecated' - linters: [staticcheck] - text: "grpc.DialContext is deprecated" + text: 'grpc.DialContext is deprecated' # Deprecated gRPC dial options. Related to grpc.NewClient. - path: (client/client.go|client/proxy/client_test.go) # api/ linters: [staticcheck] # grpc.FailOnNonTempDialError # grpc.WithReturnConnectionError - text: "this DialOption is not supported by NewClient" + text: 'this DialOption is not supported by NewClient' - path: lib/kube/grpc/grpc_test.go linters: [staticcheck] - text: "grpc.WithBlock is deprecated" + text: 'grpc.WithBlock is deprecated' - path: lib/observability/tracing/client.go linters: [staticcheck] - text: "grpc.WithBlock is deprecated" + text: 'grpc.WithBlock is deprecated' - path: integrations/lib/config.go linters: [staticcheck] - text: "grpc.WithReturnConnectionError is deprecated" + text: 'grpc.WithReturnConnectionError is deprecated' - path: lib/service/service_test.go linters: [staticcheck] # grpc.WithReturnConnectionError # grpc.FailOnNonTempDialError - text: "this DialOption is not supported by NewClient" + text: 'this DialOption is not supported by NewClient' - path: integration/client_test.go linters: [staticcheck] - text: "grpc.WithReturnConnectionError is deprecated" + text: 'grpc.WithReturnConnectionError is deprecated' - path: integration/integration_test.go linters: [staticcheck] - text: "grpc.WithBlock is deprecated" + text: 'grpc.WithBlock is deprecated' - path: lib/multiplexer/multiplexer_test.go linters: [staticcheck] - text: "grpc.WithBlock is deprecated" + text: 'grpc.WithBlock is deprecated' - path: provider/provider.go # integrations/terraform linters: [staticcheck] - text: "grpc.WithReturnConnectionError is deprecated" + text: 'grpc.WithReturnConnectionError is deprecated' - linters: [govet] - text: "non-constant format string in call to github.com/gravitational/trace." + path-except: ^e/ + text: 'non-constant format string in call to github.com/gravitational/trace.' exclude-use-default: true max-same-issues: 0 max-issues-per-linter: 0 @@ -121,6 +122,7 @@ linters-settings: files: - '**/api/**' - '**/e/**' + - '**/lib/srv/**' deny: - pkg: github.com/sirupsen/logrus desc: 'use "log/slog" instead' @@ -130,7 +132,7 @@ linters-settings: client-tools: files: # Tests can do anything - - "!$test" + - '!$test' - '**/tool/tbot/**' - '**/lib/tbot/**' - '**/tool/tctl/**' @@ -158,7 +160,7 @@ linters-settings: cgo: files: # Tests can do anything - - "!$test" + - '!$test' - '**/tool/tbot/**' - '**/lib/client/**' - '!**/lib/integrations/**' @@ -240,8 +242,8 @@ linters-settings: require-specific: true revive: rules: - - name: unused-parameter - disabled: true + - name: unused-parameter + disabled: true sloglint: context: all key-naming-case: snake diff --git a/api/client/client.go b/api/client/client.go index e3e1184250572..edffe12d00ff2 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -2286,12 +2286,56 @@ func (c *Client) GetTrustedClusters(ctx context.Context) ([]types.TrustedCluster } // UpsertTrustedCluster creates or updates a Trusted Cluster. -func (c *Client) UpsertTrustedCluster(ctx context.Context, trusedCluster types.TrustedCluster) (types.TrustedCluster, error) { - trustedCluster, ok := trusedCluster.(*types.TrustedClusterV2) +// +// Deprecated: Use [Client.UpsertTrustedClusterV2] instead. +func (c *Client) UpsertTrustedCluster(ctx context.Context, trustedCluster types.TrustedCluster) (types.TrustedCluster, error) { + trustedClusterV2, ok := trustedCluster.(*types.TrustedClusterV2) if !ok { - return nil, trace.BadParameter("invalid type %T", trusedCluster) + return nil, trace.BadParameter("invalid type %T", trustedCluster) } - resp, err := c.grpc.UpsertTrustedCluster(ctx, trustedCluster) + resp, err := c.grpc.UpsertTrustedCluster(ctx, trustedClusterV2) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + +// UpsertTrustedClusterV2 creates or updates a Trusted Cluster. +func (c *Client) UpsertTrustedClusterV2(ctx context.Context, trustedCluster types.TrustedCluster) (types.TrustedCluster, error) { + trustedClusterV2, ok := trustedCluster.(*types.TrustedClusterV2) + if !ok { + return nil, trace.BadParameter("invalid type %T", trustedCluster) + } + req := &trustpb.UpsertTrustedClusterRequest{TrustedCluster: trustedClusterV2} + resp, err := c.TrustClient().UpsertTrustedCluster(ctx, req) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + +// CreateTrustedCluster creates a Trusted Cluster. +func (c *Client) CreateTrustedCluster(ctx context.Context, trustedCluster types.TrustedCluster) (types.TrustedCluster, error) { + trustedClusterV2, ok := trustedCluster.(*types.TrustedClusterV2) + if !ok { + return nil, trace.BadParameter("invalid type %T", trustedCluster) + } + req := &trustpb.CreateTrustedClusterRequest{TrustedCluster: trustedClusterV2} + resp, err := c.TrustClient().CreateTrustedCluster(ctx, req) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + +// UpdateTrustedCluster updates a Trusted Cluster. +func (c *Client) UpdateTrustedCluster(ctx context.Context, trustedCluster types.TrustedCluster) (types.TrustedCluster, error) { + trustedClusterV2, ok := trustedCluster.(*types.TrustedClusterV2) + if !ok { + return nil, trace.BadParameter("invalid type %T", trustedCluster) + } + req := &trustpb.UpdateTrustedClusterRequest{TrustedCluster: trustedClusterV2} + resp, err := c.TrustClient().UpdateTrustedCluster(ctx, req) if err != nil { return nil, trace.Wrap(err) } @@ -4262,6 +4306,12 @@ func (c *Client) GetSSHTargets(ctx context.Context, req *proto.GetSSHTargetsRequ return rsp, trace.Wrap(err) } +// ResolveSSHTarget gets a server that would match an equivalent ssh dial request. +func (c *Client) ResolveSSHTarget(ctx context.Context, req *proto.ResolveSSHTargetRequest) (*proto.ResolveSSHTargetResponse, error) { + rsp, err := c.grpc.ResolveSSHTarget(ctx, req) + return rsp, trace.Wrap(err) +} + // CreateSessionTracker creates a tracker resource for an active session. func (c *Client) CreateSessionTracker(ctx context.Context, st types.SessionTracker) (types.SessionTracker, error) { v1, ok := st.(*types.SessionTrackerV1) @@ -5091,6 +5141,52 @@ func (c *Client) UpsertUserLastSeenNotification(ctx context.Context, req *notifi return rsp, trace.Wrap(err) } +// GetWorkloadIdentity returns a workload identity by name. +func (c *Client) GetWorkloadIdentity(ctx context.Context, name string) (*workloadidentityv1pb.WorkloadIdentity, error) { + resp, err := c.WorkloadIdentityResourceServiceClient().GetWorkloadIdentity(ctx, &workloadidentityv1pb.GetWorkloadIdentityRequest{ + Name: name, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + +// DeleteWorkloadIdentity deletes a workload identity by name. It will throw an +// error if the workload identity does not exist. +func (c *Client) DeleteWorkloadIdentity(ctx context.Context, name string) error { + _, err := c.WorkloadIdentityResourceServiceClient().DeleteWorkloadIdentity(ctx, &workloadidentityv1pb.DeleteWorkloadIdentityRequest{ + Name: name, + }) + if err != nil { + return trace.Wrap(err) + } + return nil +} + +// CreateWorkloadIdentity creates a new workload identity, it will not overwrite +// an existing workload identity with the same name. +func (c *Client) CreateWorkloadIdentity(ctx context.Context, r *workloadidentityv1pb.WorkloadIdentity) (*workloadidentityv1pb.WorkloadIdentity, error) { + resp, err := c.WorkloadIdentityResourceServiceClient().CreateWorkloadIdentity(ctx, &workloadidentityv1pb.CreateWorkloadIdentityRequest{ + WorkloadIdentity: r, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + +// UpsertWorkloadIdentity creates or updates a workload identity. +func (c *Client) UpsertWorkloadIdentity(ctx context.Context, r *workloadidentityv1pb.WorkloadIdentity) (*workloadidentityv1pb.WorkloadIdentity, error) { + resp, err := c.WorkloadIdentityResourceServiceClient().UpsertWorkloadIdentity(ctx, &workloadidentityv1pb.UpsertWorkloadIdentityRequest{ + WorkloadIdentity: r, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + // ResourceUsageClient returns an unadorned Resource Usage service client, // using the underlying Auth gRPC connection. // Clients connecting to non-Enterprise clusters, or older Teleport versions, diff --git a/api/client/proto/authservice.pb.go b/api/client/proto/authservice.pb.go index dd1161bbfcac2..f9099550a91f1 100644 --- a/api/client/proto/authservice.pb.go +++ b/api/client/proto/authservice.pb.go @@ -11522,6 +11522,150 @@ func (m *ListResourcesRequest) GetIncludeLogins() bool { return false } +// ResolveSSHTargetRequest provides details about a server to be resolved in +// an equivalent manner to a ssh dial request. +// +// Resolution can happen in two modes: +// 1. searching for hosts based on labels, a predicate expression, or keywords +// 2. searching based on hostname +// +// If a Host is provided, resolution will only operate in the second mode and +// will not perform any resolution based on labels. In order to resolve via +// labels the Host must not be populated. +type ResolveSSHTargetRequest struct { + // The target host as would be sent to the proxy during a dial request. + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + // The ssh port. This value is optional, and both empty string and "0" are typically + // treated as meaning that any port should match. + Port string `protobuf:"bytes,2,opt,name=port,proto3" json:"port,omitempty"` + // If not empty, a label-based matcher. + Labels map[string]string `protobuf:"bytes,3,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Boolean conditions that will be matched against the resource. + PredicateExpression string `protobuf:"bytes,4,opt,name=predicate_expression,json=predicateExpression,proto3" json:"predicate_expression,omitempty"` + // A list of search keywords to match against resource field values. + SearchKeywords []string `protobuf:"bytes,5,rep,name=search_keywords,json=searchKeywords,proto3" json:"search_keywords,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ResolveSSHTargetRequest) Reset() { *m = ResolveSSHTargetRequest{} } +func (m *ResolveSSHTargetRequest) String() string { return proto.CompactTextString(m) } +func (*ResolveSSHTargetRequest) ProtoMessage() {} +func (*ResolveSSHTargetRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_0ffcffcda38ae159, []int{163} +} +func (m *ResolveSSHTargetRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ResolveSSHTargetRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ResolveSSHTargetRequest.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 *ResolveSSHTargetRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ResolveSSHTargetRequest.Merge(m, src) +} +func (m *ResolveSSHTargetRequest) XXX_Size() int { + return m.Size() +} +func (m *ResolveSSHTargetRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ResolveSSHTargetRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_ResolveSSHTargetRequest proto.InternalMessageInfo + +func (m *ResolveSSHTargetRequest) GetHost() string { + if m != nil { + return m.Host + } + return "" +} + +func (m *ResolveSSHTargetRequest) GetPort() string { + if m != nil { + return m.Port + } + return "" +} + +func (m *ResolveSSHTargetRequest) GetLabels() map[string]string { + if m != nil { + return m.Labels + } + return nil +} + +func (m *ResolveSSHTargetRequest) GetPredicateExpression() string { + if m != nil { + return m.PredicateExpression + } + return "" +} + +func (m *ResolveSSHTargetRequest) GetSearchKeywords() []string { + if m != nil { + return m.SearchKeywords + } + return nil +} + +// GetSSHTargetsResponse holds ssh servers that match an ssh targets request. +type ResolveSSHTargetResponse struct { + // The target matching the supplied request. + Server *types.ServerV2 `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ResolveSSHTargetResponse) Reset() { *m = ResolveSSHTargetResponse{} } +func (m *ResolveSSHTargetResponse) String() string { return proto.CompactTextString(m) } +func (*ResolveSSHTargetResponse) ProtoMessage() {} +func (*ResolveSSHTargetResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_0ffcffcda38ae159, []int{164} +} +func (m *ResolveSSHTargetResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ResolveSSHTargetResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ResolveSSHTargetResponse.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 *ResolveSSHTargetResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ResolveSSHTargetResponse.Merge(m, src) +} +func (m *ResolveSSHTargetResponse) XXX_Size() int { + return m.Size() +} +func (m *ResolveSSHTargetResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ResolveSSHTargetResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ResolveSSHTargetResponse proto.InternalMessageInfo + +func (m *ResolveSSHTargetResponse) GetServer() *types.ServerV2 { + if m != nil { + return m.Server + } + return nil +} + // GetSSHTargetsRequest gets all servers that might match an equivalent ssh dial request. type GetSSHTargetsRequest struct { // Host is the target host as would be sent to the proxy during a dial request. @@ -11538,7 +11682,7 @@ func (m *GetSSHTargetsRequest) Reset() { *m = GetSSHTargetsRequest{} } func (m *GetSSHTargetsRequest) String() string { return proto.CompactTextString(m) } func (*GetSSHTargetsRequest) ProtoMessage() {} func (*GetSSHTargetsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{163} + return fileDescriptor_0ffcffcda38ae159, []int{165} } func (m *GetSSHTargetsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11594,7 +11738,7 @@ func (m *GetSSHTargetsResponse) Reset() { *m = GetSSHTargetsResponse{} } func (m *GetSSHTargetsResponse) String() string { return proto.CompactTextString(m) } func (*GetSSHTargetsResponse) ProtoMessage() {} func (*GetSSHTargetsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{164} + return fileDescriptor_0ffcffcda38ae159, []int{166} } func (m *GetSSHTargetsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11649,7 +11793,7 @@ func (m *ListResourcesResponse) Reset() { *m = ListResourcesResponse{} } func (m *ListResourcesResponse) String() string { return proto.CompactTextString(m) } func (*ListResourcesResponse) ProtoMessage() {} func (*ListResourcesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{165} + return fileDescriptor_0ffcffcda38ae159, []int{167} } func (m *ListResourcesResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11714,7 +11858,7 @@ func (m *CreateSessionTrackerRequest) Reset() { *m = CreateSessionTracke func (m *CreateSessionTrackerRequest) String() string { return proto.CompactTextString(m) } func (*CreateSessionTrackerRequest) ProtoMessage() {} func (*CreateSessionTrackerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{166} + return fileDescriptor_0ffcffcda38ae159, []int{168} } func (m *CreateSessionTrackerRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11763,7 +11907,7 @@ func (m *GetSessionTrackerRequest) Reset() { *m = GetSessionTrackerReque func (m *GetSessionTrackerRequest) String() string { return proto.CompactTextString(m) } func (*GetSessionTrackerRequest) ProtoMessage() {} func (*GetSessionTrackerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{167} + return fileDescriptor_0ffcffcda38ae159, []int{169} } func (m *GetSessionTrackerRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11812,7 +11956,7 @@ func (m *RemoveSessionTrackerRequest) Reset() { *m = RemoveSessionTracke func (m *RemoveSessionTrackerRequest) String() string { return proto.CompactTextString(m) } func (*RemoveSessionTrackerRequest) ProtoMessage() {} func (*RemoveSessionTrackerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{168} + return fileDescriptor_0ffcffcda38ae159, []int{170} } func (m *RemoveSessionTrackerRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11860,7 +12004,7 @@ func (m *SessionTrackerUpdateState) Reset() { *m = SessionTrackerUpdateS func (m *SessionTrackerUpdateState) String() string { return proto.CompactTextString(m) } func (*SessionTrackerUpdateState) ProtoMessage() {} func (*SessionTrackerUpdateState) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{169} + return fileDescriptor_0ffcffcda38ae159, []int{171} } func (m *SessionTrackerUpdateState) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11908,7 +12052,7 @@ func (m *SessionTrackerAddParticipant) Reset() { *m = SessionTrackerAddP func (m *SessionTrackerAddParticipant) String() string { return proto.CompactTextString(m) } func (*SessionTrackerAddParticipant) ProtoMessage() {} func (*SessionTrackerAddParticipant) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{170} + return fileDescriptor_0ffcffcda38ae159, []int{172} } func (m *SessionTrackerAddParticipant) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -11956,7 +12100,7 @@ func (m *SessionTrackerRemoveParticipant) Reset() { *m = SessionTrackerR func (m *SessionTrackerRemoveParticipant) String() string { return proto.CompactTextString(m) } func (*SessionTrackerRemoveParticipant) ProtoMessage() {} func (*SessionTrackerRemoveParticipant) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{171} + return fileDescriptor_0ffcffcda38ae159, []int{173} } func (m *SessionTrackerRemoveParticipant) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12005,7 +12149,7 @@ func (m *SessionTrackerUpdateExpiry) Reset() { *m = SessionTrackerUpdate func (m *SessionTrackerUpdateExpiry) String() string { return proto.CompactTextString(m) } func (*SessionTrackerUpdateExpiry) ProtoMessage() {} func (*SessionTrackerUpdateExpiry) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{172} + return fileDescriptor_0ffcffcda38ae159, []int{174} } func (m *SessionTrackerUpdateExpiry) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12060,7 +12204,7 @@ func (m *UpdateSessionTrackerRequest) Reset() { *m = UpdateSessionTracke func (m *UpdateSessionTrackerRequest) String() string { return proto.CompactTextString(m) } func (*UpdateSessionTrackerRequest) ProtoMessage() {} func (*UpdateSessionTrackerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{173} + return fileDescriptor_0ffcffcda38ae159, []int{175} } func (m *UpdateSessionTrackerRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12181,7 +12325,7 @@ func (m *PresenceMFAChallengeRequest) Reset() { *m = PresenceMFAChalleng func (m *PresenceMFAChallengeRequest) String() string { return proto.CompactTextString(m) } func (*PresenceMFAChallengeRequest) ProtoMessage() {} func (*PresenceMFAChallengeRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{174} + return fileDescriptor_0ffcffcda38ae159, []int{176} } func (m *PresenceMFAChallengeRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12239,7 +12383,7 @@ func (m *PresenceMFAChallengeSend) Reset() { *m = PresenceMFAChallengeSe func (m *PresenceMFAChallengeSend) String() string { return proto.CompactTextString(m) } func (*PresenceMFAChallengeSend) ProtoMessage() {} func (*PresenceMFAChallengeSend) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{175} + return fileDescriptor_0ffcffcda38ae159, []int{177} } func (m *PresenceMFAChallengeSend) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12326,7 +12470,7 @@ func (m *GetDomainNameResponse) Reset() { *m = GetDomainNameResponse{} } func (m *GetDomainNameResponse) String() string { return proto.CompactTextString(m) } func (*GetDomainNameResponse) ProtoMessage() {} func (*GetDomainNameResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{176} + return fileDescriptor_0ffcffcda38ae159, []int{178} } func (m *GetDomainNameResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12375,7 +12519,7 @@ func (m *GetClusterCACertResponse) Reset() { *m = GetClusterCACertRespon func (m *GetClusterCACertResponse) String() string { return proto.CompactTextString(m) } func (*GetClusterCACertResponse) ProtoMessage() {} func (*GetClusterCACertResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{177} + return fileDescriptor_0ffcffcda38ae159, []int{179} } func (m *GetClusterCACertResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12423,7 +12567,7 @@ func (m *GetLicenseResponse) Reset() { *m = GetLicenseResponse{} } func (m *GetLicenseResponse) String() string { return proto.CompactTextString(m) } func (*GetLicenseResponse) ProtoMessage() {} func (*GetLicenseResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{178} + return fileDescriptor_0ffcffcda38ae159, []int{180} } func (m *GetLicenseResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12471,7 +12615,7 @@ func (m *ListReleasesResponse) Reset() { *m = ListReleasesResponse{} } func (m *ListReleasesResponse) String() string { return proto.CompactTextString(m) } func (*ListReleasesResponse) ProtoMessage() {} func (*ListReleasesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{179} + return fileDescriptor_0ffcffcda38ae159, []int{181} } func (m *ListReleasesResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12520,7 +12664,7 @@ func (m *GetOIDCAuthRequestRequest) Reset() { *m = GetOIDCAuthRequestReq func (m *GetOIDCAuthRequestRequest) String() string { return proto.CompactTextString(m) } func (*GetOIDCAuthRequestRequest) ProtoMessage() {} func (*GetOIDCAuthRequestRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{180} + return fileDescriptor_0ffcffcda38ae159, []int{182} } func (m *GetOIDCAuthRequestRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12569,7 +12713,7 @@ func (m *GetSAMLAuthRequestRequest) Reset() { *m = GetSAMLAuthRequestReq func (m *GetSAMLAuthRequestRequest) String() string { return proto.CompactTextString(m) } func (*GetSAMLAuthRequestRequest) ProtoMessage() {} func (*GetSAMLAuthRequestRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{181} + return fileDescriptor_0ffcffcda38ae159, []int{183} } func (m *GetSAMLAuthRequestRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12618,7 +12762,7 @@ func (m *GetGithubAuthRequestRequest) Reset() { *m = GetGithubAuthReques func (m *GetGithubAuthRequestRequest) String() string { return proto.CompactTextString(m) } func (*GetGithubAuthRequestRequest) ProtoMessage() {} func (*GetGithubAuthRequestRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{182} + return fileDescriptor_0ffcffcda38ae159, []int{184} } func (m *GetGithubAuthRequestRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12667,7 +12811,7 @@ func (m *CreateOIDCConnectorRequest) Reset() { *m = CreateOIDCConnectorR func (m *CreateOIDCConnectorRequest) String() string { return proto.CompactTextString(m) } func (*CreateOIDCConnectorRequest) ProtoMessage() {} func (*CreateOIDCConnectorRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{183} + return fileDescriptor_0ffcffcda38ae159, []int{185} } func (m *CreateOIDCConnectorRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12716,7 +12860,7 @@ func (m *UpdateOIDCConnectorRequest) Reset() { *m = UpdateOIDCConnectorR func (m *UpdateOIDCConnectorRequest) String() string { return proto.CompactTextString(m) } func (*UpdateOIDCConnectorRequest) ProtoMessage() {} func (*UpdateOIDCConnectorRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{184} + return fileDescriptor_0ffcffcda38ae159, []int{186} } func (m *UpdateOIDCConnectorRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12765,7 +12909,7 @@ func (m *UpsertOIDCConnectorRequest) Reset() { *m = UpsertOIDCConnectorR func (m *UpsertOIDCConnectorRequest) String() string { return proto.CompactTextString(m) } func (*UpsertOIDCConnectorRequest) ProtoMessage() {} func (*UpsertOIDCConnectorRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{185} + return fileDescriptor_0ffcffcda38ae159, []int{187} } func (m *UpsertOIDCConnectorRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12814,7 +12958,7 @@ func (m *CreateSAMLConnectorRequest) Reset() { *m = CreateSAMLConnectorR func (m *CreateSAMLConnectorRequest) String() string { return proto.CompactTextString(m) } func (*CreateSAMLConnectorRequest) ProtoMessage() {} func (*CreateSAMLConnectorRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{186} + return fileDescriptor_0ffcffcda38ae159, []int{188} } func (m *CreateSAMLConnectorRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12863,7 +13007,7 @@ func (m *UpdateSAMLConnectorRequest) Reset() { *m = UpdateSAMLConnectorR func (m *UpdateSAMLConnectorRequest) String() string { return proto.CompactTextString(m) } func (*UpdateSAMLConnectorRequest) ProtoMessage() {} func (*UpdateSAMLConnectorRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{187} + return fileDescriptor_0ffcffcda38ae159, []int{189} } func (m *UpdateSAMLConnectorRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12912,7 +13056,7 @@ func (m *UpsertSAMLConnectorRequest) Reset() { *m = UpsertSAMLConnectorR func (m *UpsertSAMLConnectorRequest) String() string { return proto.CompactTextString(m) } func (*UpsertSAMLConnectorRequest) ProtoMessage() {} func (*UpsertSAMLConnectorRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{188} + return fileDescriptor_0ffcffcda38ae159, []int{190} } func (m *UpsertSAMLConnectorRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -12961,7 +13105,7 @@ func (m *CreateGithubConnectorRequest) Reset() { *m = CreateGithubConnec func (m *CreateGithubConnectorRequest) String() string { return proto.CompactTextString(m) } func (*CreateGithubConnectorRequest) ProtoMessage() {} func (*CreateGithubConnectorRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{189} + return fileDescriptor_0ffcffcda38ae159, []int{191} } func (m *CreateGithubConnectorRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13010,7 +13154,7 @@ func (m *UpdateGithubConnectorRequest) Reset() { *m = UpdateGithubConnec func (m *UpdateGithubConnectorRequest) String() string { return proto.CompactTextString(m) } func (*UpdateGithubConnectorRequest) ProtoMessage() {} func (*UpdateGithubConnectorRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{190} + return fileDescriptor_0ffcffcda38ae159, []int{192} } func (m *UpdateGithubConnectorRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13059,7 +13203,7 @@ func (m *UpsertGithubConnectorRequest) Reset() { *m = UpsertGithubConnec func (m *UpsertGithubConnectorRequest) String() string { return proto.CompactTextString(m) } func (*UpsertGithubConnectorRequest) ProtoMessage() {} func (*UpsertGithubConnectorRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{191} + return fileDescriptor_0ffcffcda38ae159, []int{193} } func (m *UpsertGithubConnectorRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13110,7 +13254,7 @@ func (m *GetSSODiagnosticInfoRequest) Reset() { *m = GetSSODiagnosticInf func (m *GetSSODiagnosticInfoRequest) String() string { return proto.CompactTextString(m) } func (*GetSSODiagnosticInfoRequest) ProtoMessage() {} func (*GetSSODiagnosticInfoRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{192} + return fileDescriptor_0ffcffcda38ae159, []int{194} } func (m *GetSSODiagnosticInfoRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13176,7 +13320,7 @@ func (m *SystemRoleAssertion) Reset() { *m = SystemRoleAssertion{} } func (m *SystemRoleAssertion) String() string { return proto.CompactTextString(m) } func (*SystemRoleAssertion) ProtoMessage() {} func (*SystemRoleAssertion) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{193} + return fileDescriptor_0ffcffcda38ae159, []int{195} } func (m *SystemRoleAssertion) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13246,7 +13390,7 @@ func (m *SystemRoleAssertionSet) Reset() { *m = SystemRoleAssertionSet{} func (m *SystemRoleAssertionSet) String() string { return proto.CompactTextString(m) } func (*SystemRoleAssertionSet) ProtoMessage() {} func (*SystemRoleAssertionSet) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{194} + return fileDescriptor_0ffcffcda38ae159, []int{196} } func (m *SystemRoleAssertionSet) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13315,7 +13459,7 @@ func (m *UpstreamInventoryOneOf) Reset() { *m = UpstreamInventoryOneOf{} func (m *UpstreamInventoryOneOf) String() string { return proto.CompactTextString(m) } func (*UpstreamInventoryOneOf) ProtoMessage() {} func (*UpstreamInventoryOneOf) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{195} + return fileDescriptor_0ffcffcda38ae159, []int{197} } func (m *UpstreamInventoryOneOf) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13442,7 +13586,7 @@ func (m *DownstreamInventoryOneOf) Reset() { *m = DownstreamInventoryOne func (m *DownstreamInventoryOneOf) String() string { return proto.CompactTextString(m) } func (*DownstreamInventoryOneOf) ProtoMessage() {} func (*DownstreamInventoryOneOf) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{196} + return fileDescriptor_0ffcffcda38ae159, []int{198} } func (m *DownstreamInventoryOneOf) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13540,7 +13684,7 @@ func (m *DownstreamInventoryPing) Reset() { *m = DownstreamInventoryPing func (m *DownstreamInventoryPing) String() string { return proto.CompactTextString(m) } func (*DownstreamInventoryPing) ProtoMessage() {} func (*DownstreamInventoryPing) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{197} + return fileDescriptor_0ffcffcda38ae159, []int{199} } func (m *DownstreamInventoryPing) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13591,7 +13735,7 @@ func (m *UpstreamInventoryPong) Reset() { *m = UpstreamInventoryPong{} } func (m *UpstreamInventoryPong) String() string { return proto.CompactTextString(m) } func (*UpstreamInventoryPong) ProtoMessage() {} func (*UpstreamInventoryPong) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{198} + return fileDescriptor_0ffcffcda38ae159, []int{200} } func (m *UpstreamInventoryPong) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13665,7 +13809,7 @@ func (m *UpstreamInventoryHello) Reset() { *m = UpstreamInventoryHello{} func (m *UpstreamInventoryHello) String() string { return proto.CompactTextString(m) } func (*UpstreamInventoryHello) ProtoMessage() {} func (*UpstreamInventoryHello) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{199} + return fileDescriptor_0ffcffcda38ae159, []int{201} } func (m *UpstreamInventoryHello) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13765,7 +13909,7 @@ func (m *UpstreamInventoryAgentMetadata) Reset() { *m = UpstreamInventor func (m *UpstreamInventoryAgentMetadata) String() string { return proto.CompactTextString(m) } func (*UpstreamInventoryAgentMetadata) ProtoMessage() {} func (*UpstreamInventoryAgentMetadata) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{200} + return fileDescriptor_0ffcffcda38ae159, []int{202} } func (m *UpstreamInventoryAgentMetadata) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13867,7 +14011,7 @@ func (m *DownstreamInventoryHello) Reset() { *m = DownstreamInventoryHel func (m *DownstreamInventoryHello) String() string { return proto.CompactTextString(m) } func (*DownstreamInventoryHello) ProtoMessage() {} func (*DownstreamInventoryHello) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{201} + return fileDescriptor_0ffcffcda38ae159, []int{203} } func (m *DownstreamInventoryHello) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -13970,7 +14114,7 @@ func (m *DownstreamInventoryHello_SupportedCapabilities) String() string { } func (*DownstreamInventoryHello_SupportedCapabilities) ProtoMessage() {} func (*DownstreamInventoryHello_SupportedCapabilities) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{201, 0} + return fileDescriptor_0ffcffcda38ae159, []int{203, 0} } func (m *DownstreamInventoryHello_SupportedCapabilities) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14143,7 +14287,7 @@ func (m *InventoryUpdateLabelsRequest) Reset() { *m = InventoryUpdateLab func (m *InventoryUpdateLabelsRequest) String() string { return proto.CompactTextString(m) } func (*InventoryUpdateLabelsRequest) ProtoMessage() {} func (*InventoryUpdateLabelsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{202} + return fileDescriptor_0ffcffcda38ae159, []int{204} } func (m *InventoryUpdateLabelsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14209,7 +14353,7 @@ func (m *DownstreamInventoryUpdateLabels) Reset() { *m = DownstreamInven func (m *DownstreamInventoryUpdateLabels) String() string { return proto.CompactTextString(m) } func (*DownstreamInventoryUpdateLabels) ProtoMessage() {} func (*DownstreamInventoryUpdateLabels) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{203} + return fileDescriptor_0ffcffcda38ae159, []int{205} } func (m *DownstreamInventoryUpdateLabels) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14274,7 +14418,7 @@ func (m *InventoryHeartbeat) Reset() { *m = InventoryHeartbeat{} } func (m *InventoryHeartbeat) String() string { return proto.CompactTextString(m) } func (*InventoryHeartbeat) ProtoMessage() {} func (*InventoryHeartbeat) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{204} + return fileDescriptor_0ffcffcda38ae159, []int{206} } func (m *InventoryHeartbeat) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14346,7 +14490,7 @@ func (m *UpstreamInventoryGoodbye) Reset() { *m = UpstreamInventoryGoodb func (m *UpstreamInventoryGoodbye) String() string { return proto.CompactTextString(m) } func (*UpstreamInventoryGoodbye) ProtoMessage() {} func (*UpstreamInventoryGoodbye) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{205} + return fileDescriptor_0ffcffcda38ae159, []int{207} } func (m *UpstreamInventoryGoodbye) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14396,7 +14540,7 @@ func (m *InventoryStatusRequest) Reset() { *m = InventoryStatusRequest{} func (m *InventoryStatusRequest) String() string { return proto.CompactTextString(m) } func (*InventoryStatusRequest) ProtoMessage() {} func (*InventoryStatusRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{206} + return fileDescriptor_0ffcffcda38ae159, []int{208} } func (m *InventoryStatusRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14454,7 +14598,7 @@ func (m *InventoryStatusSummary) Reset() { *m = InventoryStatusSummary{} func (m *InventoryStatusSummary) String() string { return proto.CompactTextString(m) } func (*InventoryStatusSummary) ProtoMessage() {} func (*InventoryStatusSummary) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{207} + return fileDescriptor_0ffcffcda38ae159, []int{209} } func (m *InventoryStatusSummary) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14531,7 +14675,7 @@ func (m *InventoryConnectedServiceCountsRequest) Reset() { func (m *InventoryConnectedServiceCountsRequest) String() string { return proto.CompactTextString(m) } func (*InventoryConnectedServiceCountsRequest) ProtoMessage() {} func (*InventoryConnectedServiceCountsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{208} + return fileDescriptor_0ffcffcda38ae159, []int{210} } func (m *InventoryConnectedServiceCountsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14573,7 +14717,7 @@ func (m *InventoryConnectedServiceCounts) Reset() { *m = InventoryConnec func (m *InventoryConnectedServiceCounts) String() string { return proto.CompactTextString(m) } func (*InventoryConnectedServiceCounts) ProtoMessage() {} func (*InventoryConnectedServiceCounts) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{209} + return fileDescriptor_0ffcffcda38ae159, []int{211} } func (m *InventoryConnectedServiceCounts) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14627,7 +14771,7 @@ func (m *InventoryPingRequest) Reset() { *m = InventoryPingRequest{} } func (m *InventoryPingRequest) String() string { return proto.CompactTextString(m) } func (*InventoryPingRequest) ProtoMessage() {} func (*InventoryPingRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{210} + return fileDescriptor_0ffcffcda38ae159, []int{212} } func (m *InventoryPingRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14684,7 +14828,7 @@ func (m *InventoryPingResponse) Reset() { *m = InventoryPingResponse{} } func (m *InventoryPingResponse) String() string { return proto.CompactTextString(m) } func (*InventoryPingResponse) ProtoMessage() {} func (*InventoryPingResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{211} + return fileDescriptor_0ffcffcda38ae159, []int{213} } func (m *InventoryPingResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14733,7 +14877,7 @@ func (m *GetClusterAlertsResponse) Reset() { *m = GetClusterAlertsRespon func (m *GetClusterAlertsResponse) String() string { return proto.CompactTextString(m) } func (*GetClusterAlertsResponse) ProtoMessage() {} func (*GetClusterAlertsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{212} + return fileDescriptor_0ffcffcda38ae159, []int{214} } func (m *GetClusterAlertsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14780,7 +14924,7 @@ func (m *GetAlertAcksRequest) Reset() { *m = GetAlertAcksRequest{} } func (m *GetAlertAcksRequest) String() string { return proto.CompactTextString(m) } func (*GetAlertAcksRequest) ProtoMessage() {} func (*GetAlertAcksRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{213} + return fileDescriptor_0ffcffcda38ae159, []int{215} } func (m *GetAlertAcksRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14822,7 +14966,7 @@ func (m *GetAlertAcksResponse) Reset() { *m = GetAlertAcksResponse{} } func (m *GetAlertAcksResponse) String() string { return proto.CompactTextString(m) } func (*GetAlertAcksResponse) ProtoMessage() {} func (*GetAlertAcksResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{214} + return fileDescriptor_0ffcffcda38ae159, []int{216} } func (m *GetAlertAcksResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14872,7 +15016,7 @@ func (m *ClearAlertAcksRequest) Reset() { *m = ClearAlertAcksRequest{} } func (m *ClearAlertAcksRequest) String() string { return proto.CompactTextString(m) } func (*ClearAlertAcksRequest) ProtoMessage() {} func (*ClearAlertAcksRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{215} + return fileDescriptor_0ffcffcda38ae159, []int{217} } func (m *ClearAlertAcksRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14921,7 +15065,7 @@ func (m *UpsertClusterAlertRequest) Reset() { *m = UpsertClusterAlertReq func (m *UpsertClusterAlertRequest) String() string { return proto.CompactTextString(m) } func (*UpsertClusterAlertRequest) ProtoMessage() {} func (*UpsertClusterAlertRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{216} + return fileDescriptor_0ffcffcda38ae159, []int{218} } func (m *UpsertClusterAlertRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14970,7 +15114,7 @@ func (m *GetConnectionDiagnosticRequest) Reset() { *m = GetConnectionDia func (m *GetConnectionDiagnosticRequest) String() string { return proto.CompactTextString(m) } func (*GetConnectionDiagnosticRequest) ProtoMessage() {} func (*GetConnectionDiagnosticRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{217} + return fileDescriptor_0ffcffcda38ae159, []int{219} } func (m *GetConnectionDiagnosticRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15021,7 +15165,7 @@ func (m *AppendDiagnosticTraceRequest) Reset() { *m = AppendDiagnosticTr func (m *AppendDiagnosticTraceRequest) String() string { return proto.CompactTextString(m) } func (*AppendDiagnosticTraceRequest) ProtoMessage() {} func (*AppendDiagnosticTraceRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{218} + return fileDescriptor_0ffcffcda38ae159, []int{220} } func (m *AppendDiagnosticTraceRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15076,7 +15220,7 @@ func (m *SubmitUsageEventRequest) Reset() { *m = SubmitUsageEventRequest func (m *SubmitUsageEventRequest) String() string { return proto.CompactTextString(m) } func (*SubmitUsageEventRequest) ProtoMessage() {} func (*SubmitUsageEventRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{219} + return fileDescriptor_0ffcffcda38ae159, []int{221} } func (m *SubmitUsageEventRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15123,7 +15267,7 @@ func (m *GetLicenseRequest) Reset() { *m = GetLicenseRequest{} } func (m *GetLicenseRequest) String() string { return proto.CompactTextString(m) } func (*GetLicenseRequest) ProtoMessage() {} func (*GetLicenseRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{220} + return fileDescriptor_0ffcffcda38ae159, []int{222} } func (m *GetLicenseRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15163,7 +15307,7 @@ func (m *ListReleasesRequest) Reset() { *m = ListReleasesRequest{} } func (m *ListReleasesRequest) String() string { return proto.CompactTextString(m) } func (*ListReleasesRequest) ProtoMessage() {} func (*ListReleasesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{221} + return fileDescriptor_0ffcffcda38ae159, []int{223} } func (m *ListReleasesRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15207,7 +15351,7 @@ func (m *CreateTokenV2Request) Reset() { *m = CreateTokenV2Request{} } func (m *CreateTokenV2Request) String() string { return proto.CompactTextString(m) } func (*CreateTokenV2Request) ProtoMessage() {} func (*CreateTokenV2Request) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{222} + return fileDescriptor_0ffcffcda38ae159, []int{224} } func (m *CreateTokenV2Request) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15284,7 +15428,7 @@ func (m *UpsertTokenV2Request) Reset() { *m = UpsertTokenV2Request{} } func (m *UpsertTokenV2Request) String() string { return proto.CompactTextString(m) } func (*UpsertTokenV2Request) ProtoMessage() {} func (*UpsertTokenV2Request) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{223} + return fileDescriptor_0ffcffcda38ae159, []int{225} } func (m *UpsertTokenV2Request) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15359,7 +15503,7 @@ func (m *GetHeadlessAuthenticationRequest) Reset() { *m = GetHeadlessAut func (m *GetHeadlessAuthenticationRequest) String() string { return proto.CompactTextString(m) } func (*GetHeadlessAuthenticationRequest) ProtoMessage() {} func (*GetHeadlessAuthenticationRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{224} + return fileDescriptor_0ffcffcda38ae159, []int{226} } func (m *GetHeadlessAuthenticationRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15417,7 +15561,7 @@ func (m *UpdateHeadlessAuthenticationStateRequest) Reset() { func (m *UpdateHeadlessAuthenticationStateRequest) String() string { return proto.CompactTextString(m) } func (*UpdateHeadlessAuthenticationStateRequest) ProtoMessage() {} func (*UpdateHeadlessAuthenticationStateRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{225} + return fileDescriptor_0ffcffcda38ae159, []int{227} } func (m *UpdateHeadlessAuthenticationStateRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15483,7 +15627,7 @@ func (m *ExportUpgradeWindowsRequest) Reset() { *m = ExportUpgradeWindow func (m *ExportUpgradeWindowsRequest) String() string { return proto.CompactTextString(m) } func (*ExportUpgradeWindowsRequest) ProtoMessage() {} func (*ExportUpgradeWindowsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{226} + return fileDescriptor_0ffcffcda38ae159, []int{228} } func (m *ExportUpgradeWindowsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15549,7 +15693,7 @@ func (m *ExportUpgradeWindowsResponse) Reset() { *m = ExportUpgradeWindo func (m *ExportUpgradeWindowsResponse) String() string { return proto.CompactTextString(m) } func (*ExportUpgradeWindowsResponse) ProtoMessage() {} func (*ExportUpgradeWindowsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{227} + return fileDescriptor_0ffcffcda38ae159, []int{229} } func (m *ExportUpgradeWindowsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15624,7 +15768,7 @@ func (m *ListAccessRequestsRequest) Reset() { *m = ListAccessRequestsReq func (m *ListAccessRequestsRequest) String() string { return proto.CompactTextString(m) } func (*ListAccessRequestsRequest) ProtoMessage() {} func (*ListAccessRequestsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{228} + return fileDescriptor_0ffcffcda38ae159, []int{230} } func (m *ListAccessRequestsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15703,7 +15847,7 @@ func (m *ListAccessRequestsResponse) Reset() { *m = ListAccessRequestsRe func (m *ListAccessRequestsResponse) String() string { return proto.CompactTextString(m) } func (*ListAccessRequestsResponse) ProtoMessage() {} func (*ListAccessRequestsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{229} + return fileDescriptor_0ffcffcda38ae159, []int{231} } func (m *ListAccessRequestsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15759,7 +15903,7 @@ func (m *AccessRequestAllowedPromotionRequest) Reset() { *m = AccessRequ func (m *AccessRequestAllowedPromotionRequest) String() string { return proto.CompactTextString(m) } func (*AccessRequestAllowedPromotionRequest) ProtoMessage() {} func (*AccessRequestAllowedPromotionRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{230} + return fileDescriptor_0ffcffcda38ae159, []int{232} } func (m *AccessRequestAllowedPromotionRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -15808,7 +15952,7 @@ func (m *AccessRequestAllowedPromotionResponse) Reset() { *m = AccessReq func (m *AccessRequestAllowedPromotionResponse) String() string { return proto.CompactTextString(m) } func (*AccessRequestAllowedPromotionResponse) ProtoMessage() {} func (*AccessRequestAllowedPromotionResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0ffcffcda38ae159, []int{231} + return fileDescriptor_0ffcffcda38ae159, []int{233} } func (m *AccessRequestAllowedPromotionResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -16025,6 +16169,9 @@ func init() { proto.RegisterType((*ListUnifiedResourcesResponse)(nil), "proto.ListUnifiedResourcesResponse") proto.RegisterType((*ListResourcesRequest)(nil), "proto.ListResourcesRequest") proto.RegisterMapType((map[string]string)(nil), "proto.ListResourcesRequest.LabelsEntry") + proto.RegisterType((*ResolveSSHTargetRequest)(nil), "proto.ResolveSSHTargetRequest") + proto.RegisterMapType((map[string]string)(nil), "proto.ResolveSSHTargetRequest.LabelsEntry") + proto.RegisterType((*ResolveSSHTargetResponse)(nil), "proto.ResolveSSHTargetResponse") proto.RegisterType((*GetSSHTargetsRequest)(nil), "proto.GetSSHTargetsRequest") proto.RegisterType((*GetSSHTargetsResponse)(nil), "proto.GetSSHTargetsResponse") proto.RegisterType((*ListResourcesResponse)(nil), "proto.ListResourcesResponse") @@ -16108,971 +16255,979 @@ func init() { } var fileDescriptor_0ffcffcda38ae159 = []byte{ - // 15418 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0xbd, 0x59, 0x6c, 0x5c, 0xc9, - 0x7a, 0x18, 0xac, 0xe6, 0xce, 0x8f, 0x8b, 0x5a, 0x45, 0x52, 0x6c, 0x51, 0x4b, 0x4b, 0x47, 0xa3, - 0x19, 0x8d, 0xee, 0x5c, 0x2d, 0x9c, 0xe5, 0xce, 0x3e, 0xd3, 0x4d, 0x52, 0x22, 0x25, 0x6e, 0x73, - 0x9a, 0xa4, 0x66, 0xf3, 0xed, 0x7b, 0xd8, 0x5d, 0x22, 0x8f, 0xd5, 0x3c, 0xa7, 0xef, 0x39, 0xa7, - 0xa5, 0xd1, 0xf5, 0x7f, 0xfd, 0xc3, 0x76, 0x16, 0x07, 0x41, 0x12, 0x1b, 0x88, 0x03, 0x27, 0x79, - 0x70, 0x02, 0x38, 0x40, 0x10, 0x20, 0x80, 0x5f, 0x02, 0x3f, 0x19, 0x41, 0x9e, 0x72, 0x63, 0x20, - 0x48, 0x0c, 0xdb, 0x2f, 0x01, 0x42, 0x27, 0x17, 0xf0, 0x0b, 0x91, 0x3c, 0x18, 0x41, 0x02, 0xe4, - 0x06, 0x06, 0x82, 0xfa, 0x6a, 0x39, 0x55, 0x67, 0xe9, 0x26, 0x25, 0xcd, 0x75, 0x5e, 0x24, 0xf6, - 0xb7, 0x55, 0xd5, 0x57, 0x75, 0xaa, 0xea, 0xfb, 0xea, 0xab, 0xaf, 0xe0, 0x66, 0x44, 0x5b, 0xb4, - 0xed, 0x07, 0xd1, 0xad, 0x16, 0xdd, 0x73, 0x1a, 0xcf, 0x6e, 0x35, 0x5a, 0x2e, 0xf5, 0xa2, 0x5b, - 0xed, 0xc0, 0x8f, 0xfc, 0x5b, 0x4e, 0x27, 0xda, 0x0f, 0x69, 0xf0, 0xc4, 0x6d, 0xd0, 0x9b, 0x08, - 0x21, 0x83, 0xf8, 0xdf, 0xdc, 0xf4, 0x9e, 0xbf, 0xe7, 0x73, 0x1a, 0xf6, 0x17, 0x47, 0xce, 0x9d, - 0xdf, 0xf3, 0xfd, 0xbd, 0x16, 0xe5, 0xcc, 0xbb, 0x9d, 0x47, 0xb7, 0xe8, 0x41, 0x3b, 0x7a, 0x26, - 0x90, 0xe5, 0x24, 0x32, 0x72, 0x0f, 0x68, 0x18, 0x39, 0x07, 0x6d, 0x41, 0xf0, 0xba, 0xaa, 0x8a, - 0x13, 0x45, 0x0c, 0x13, 0xb9, 0xbe, 0x77, 0xeb, 0xc9, 0x1d, 0xfd, 0xa7, 0x20, 0xbd, 0xde, 0xb5, - 0xd6, 0x0d, 0x1a, 0x44, 0xe1, 0xb1, 0x28, 0xe9, 0x13, 0xea, 0x45, 0xa9, 0xe2, 0x05, 0x65, 0xf4, - 0xac, 0x4d, 0x43, 0x4e, 0x22, 0xff, 0x13, 0xa4, 0x57, 0xb2, 0x49, 0xf1, 0x5f, 0x41, 0xf2, 0xdd, - 0x6c, 0x92, 0xa7, 0x74, 0x97, 0xe9, 0xd4, 0x53, 0x7f, 0xf4, 0x20, 0x0f, 0x9c, 0x76, 0x9b, 0x06, - 0xf1, 0x1f, 0x82, 0xfc, 0x9c, 0x22, 0x3f, 0x78, 0xe4, 0x30, 0x15, 0x1d, 0x3c, 0x72, 0x52, 0xcd, - 0xe8, 0x84, 0xce, 0x1e, 0x15, 0xd5, 0x7f, 0x72, 0x47, 0xff, 0xc9, 0x49, 0xad, 0xdf, 0x29, 0xc0, - 0xe0, 0x43, 0x27, 0x6a, 0xec, 0x93, 0x4f, 0x60, 0xf0, 0x81, 0xeb, 0x35, 0xc3, 0x52, 0xe1, 0x72, - 0xff, 0xf5, 0xb1, 0xf9, 0xe2, 0x4d, 0xde, 0x14, 0x44, 0x32, 0x44, 0x75, 0xf6, 0x27, 0x87, 0xe5, - 0x53, 0x47, 0x87, 0xe5, 0xd3, 0x8f, 0x19, 0xd9, 0x1b, 0xfe, 0x81, 0x1b, 0x61, 0xdf, 0xda, 0x9c, - 0x8f, 0x6c, 0xc3, 0x54, 0xa5, 0xd5, 0xf2, 0x9f, 0x6e, 0x3a, 0x41, 0xe4, 0x3a, 0xad, 0x5a, 0xa7, - 0xd1, 0xa0, 0x61, 0x58, 0xea, 0xbb, 0x5c, 0xb8, 0x3e, 0x52, 0xbd, 0x7a, 0x74, 0x58, 0x2e, 0x3b, - 0x0c, 0x5d, 0x6f, 0x73, 0x7c, 0x3d, 0xe4, 0x04, 0x9a, 0xa0, 0x2c, 0x7e, 0xeb, 0x0f, 0x87, 0xa0, - 0xb8, 0xec, 0x87, 0xd1, 0x02, 0xeb, 0x51, 0x9b, 0xfe, 0xb0, 0x43, 0xc3, 0x88, 0x5c, 0x85, 0x21, - 0x06, 0x5b, 0x59, 0x2c, 0x15, 0x2e, 0x17, 0xae, 0x8f, 0x56, 0xc7, 0x8e, 0x0e, 0xcb, 0xc3, 0xfb, - 0x7e, 0x18, 0xd5, 0xdd, 0xa6, 0x2d, 0x50, 0xe4, 0x75, 0x18, 0x59, 0xf7, 0x9b, 0x74, 0xdd, 0x39, - 0xa0, 0x58, 0x8b, 0xd1, 0xea, 0xc4, 0xd1, 0x61, 0x79, 0xd4, 0xf3, 0x9b, 0xb4, 0xee, 0x39, 0x07, - 0xd4, 0x56, 0x68, 0xb2, 0x03, 0x03, 0xb6, 0xdf, 0xa2, 0xa5, 0x7e, 0x24, 0xab, 0x1e, 0x1d, 0x96, - 0x07, 0x02, 0xbf, 0x45, 0x7f, 0x76, 0x58, 0x7e, 0x67, 0xcf, 0x8d, 0xf6, 0x3b, 0xbb, 0x37, 0x1b, - 0xfe, 0xc1, 0xad, 0xbd, 0xc0, 0x79, 0xe2, 0xf2, 0x41, 0xe8, 0xb4, 0x6e, 0xc5, 0x43, 0xb5, 0xed, - 0x8a, 0x7e, 0xaf, 0x3d, 0x0b, 0x23, 0x7a, 0xc0, 0x24, 0xd9, 0x28, 0x8f, 0x3c, 0x84, 0xe9, 0x4a, - 0xb3, 0xe9, 0x72, 0x8e, 0xcd, 0xc0, 0xf5, 0x1a, 0x6e, 0xdb, 0x69, 0x85, 0xa5, 0x81, 0xcb, 0xfd, - 0xd7, 0x47, 0x85, 0x52, 0x14, 0xbe, 0xde, 0x56, 0x04, 0x9a, 0x52, 0x32, 0x05, 0x90, 0x37, 0x61, - 0x64, 0x71, 0xbd, 0xc6, 0xea, 0x1e, 0x96, 0x06, 0x51, 0xd8, 0xec, 0xd1, 0x61, 0x79, 0xaa, 0xe9, - 0x85, 0xd8, 0x34, 0x5d, 0x80, 0x22, 0x24, 0xef, 0xc0, 0xf8, 0x66, 0x67, 0xb7, 0xe5, 0x36, 0xb6, - 0x56, 0x6b, 0x0f, 0xe8, 0xb3, 0xd2, 0xd0, 0xe5, 0xc2, 0xf5, 0xf1, 0x2a, 0x39, 0x3a, 0x2c, 0x4f, - 0xb6, 0x11, 0x5e, 0x8f, 0x5a, 0x61, 0xfd, 0x31, 0x7d, 0x66, 0x1b, 0x74, 0x31, 0x5f, 0xad, 0xb6, - 0xcc, 0xf8, 0x86, 0x53, 0x7c, 0x61, 0xb8, 0xaf, 0xf3, 0x71, 0x3a, 0x72, 0x0b, 0xc0, 0xa6, 0x07, - 0x7e, 0x44, 0x2b, 0xcd, 0x66, 0x50, 0x1a, 0x41, 0xdd, 0x9e, 0x3e, 0x3a, 0x2c, 0x8f, 0x05, 0x08, - 0xad, 0x3b, 0xcd, 0x66, 0x60, 0x6b, 0x24, 0x64, 0x01, 0x46, 0x6c, 0x9f, 0x2b, 0xb8, 0x34, 0x7a, - 0xb9, 0x70, 0x7d, 0x6c, 0xfe, 0xb4, 0x18, 0x86, 0x12, 0x5c, 0x3d, 0x7b, 0x74, 0x58, 0x26, 0x81, - 0xf8, 0xa5, 0xb7, 0x52, 0x52, 0x90, 0x32, 0x0c, 0xaf, 0xfb, 0x0b, 0x4e, 0x63, 0x9f, 0x96, 0x00, - 0xc7, 0xde, 0xe0, 0xd1, 0x61, 0xb9, 0xf0, 0x5d, 0x5b, 0x42, 0xc9, 0x13, 0x18, 0x8b, 0x3b, 0x2a, - 0x2c, 0x8d, 0xa1, 0xfa, 0xb6, 0x8e, 0x0e, 0xcb, 0x67, 0x43, 0x04, 0xd7, 0x59, 0xd7, 0x6b, 0x1a, - 0x7c, 0x81, 0x51, 0xa0, 0x17, 0x44, 0xbe, 0x86, 0x99, 0xf8, 0x67, 0x25, 0x0c, 0x69, 0xc0, 0x64, - 0xac, 0x2c, 0x96, 0x26, 0x50, 0x33, 0xaf, 0x1e, 0x1d, 0x96, 0x2d, 0xad, 0x06, 0x75, 0x47, 0x92, - 0xd4, 0xdd, 0xa6, 0xd6, 0xd2, 0x6c, 0x21, 0xf7, 0x07, 0x46, 0xc6, 0x8b, 0x13, 0xf6, 0xc5, 0x6d, - 0x2f, 0x8c, 0x9c, 0xdd, 0x16, 0xcd, 0x24, 0xb2, 0xfe, 0xb2, 0x00, 0x64, 0xa3, 0x4d, 0xbd, 0x5a, - 0x6d, 0x99, 0x7d, 0x4f, 0xf2, 0x73, 0x7a, 0x03, 0x46, 0x79, 0xc7, 0xb1, 0xde, 0xed, 0xc3, 0xde, - 0x9d, 0x3c, 0x3a, 0x2c, 0x83, 0xe8, 0x5d, 0xd6, 0xb3, 0x31, 0x01, 0xb9, 0x06, 0xfd, 0x5b, 0x5b, - 0xab, 0xf8, 0xad, 0xf4, 0x57, 0xa7, 0x8e, 0x0e, 0xcb, 0xfd, 0x51, 0xd4, 0xfa, 0xd9, 0x61, 0x79, - 0x64, 0xb1, 0x13, 0xa0, 0x5a, 0x6c, 0x86, 0x27, 0xd7, 0x60, 0x78, 0xa1, 0xd5, 0x09, 0x23, 0x1a, - 0x94, 0x06, 0xe2, 0x8f, 0xb4, 0xc1, 0x41, 0xb6, 0xc4, 0x91, 0xef, 0xc0, 0xc0, 0x76, 0x48, 0x83, - 0xd2, 0x20, 0xf6, 0xf7, 0x84, 0xe8, 0x6f, 0x06, 0xda, 0x99, 0xaf, 0x8e, 0xb0, 0x2f, 0xb1, 0x13, - 0xd2, 0xc0, 0x46, 0x22, 0x72, 0x13, 0x06, 0x79, 0xa7, 0x0d, 0xe1, 0x24, 0x35, 0xa1, 0x46, 0x47, - 0x8b, 0xee, 0xbc, 0x53, 0x1d, 0x3d, 0x3a, 0x2c, 0x0f, 0x62, 0xe7, 0xd9, 0x9c, 0xec, 0xfe, 0xc0, - 0x48, 0xa1, 0xd8, 0x67, 0x8f, 0x30, 0x5e, 0xf6, 0x59, 0x58, 0xdf, 0x81, 0x31, 0xad, 0xf9, 0xe4, - 0x02, 0x0c, 0xb0, 0xff, 0x71, 0x12, 0x19, 0xe7, 0x85, 0xb1, 0x85, 0xc3, 0x46, 0xa8, 0xf5, 0x4f, - 0xa6, 0xa0, 0xc8, 0x38, 0x8d, 0x99, 0xe7, 0xa6, 0xae, 0x2a, 0xce, 0x57, 0x34, 0x55, 0x55, 0x2a, - 0xe8, 0xca, 0xba, 0x0e, 0xaa, 0x74, 0x31, 0x09, 0x8d, 0x1f, 0x1d, 0x96, 0x47, 0x3a, 0x02, 0x16, - 0xd7, 0x8d, 0xd4, 0x60, 0x78, 0xe9, 0x9b, 0xb6, 0x1b, 0xd0, 0x10, 0x55, 0x3b, 0x36, 0x3f, 0x77, - 0x93, 0x2f, 0x97, 0x37, 0xe5, 0x72, 0x79, 0x73, 0x4b, 0x2e, 0x97, 0xd5, 0x8b, 0x62, 0x32, 0x3e, - 0x43, 0x39, 0x4b, 0x3c, 0x3e, 0x7e, 0xe3, 0xcf, 0xca, 0x05, 0x5b, 0x4a, 0x22, 0x6f, 0xc0, 0xd0, - 0x5d, 0x3f, 0x38, 0x70, 0x22, 0xd1, 0x07, 0xd3, 0x47, 0x87, 0xe5, 0xe2, 0x23, 0x84, 0x68, 0x43, - 0x4a, 0xd0, 0x90, 0xbb, 0x30, 0x69, 0xfb, 0x9d, 0x88, 0x6e, 0xf9, 0xb2, 0xe7, 0x06, 0x91, 0xeb, - 0xd2, 0xd1, 0x61, 0x79, 0x2e, 0x60, 0x98, 0x7a, 0xe4, 0xd7, 0x45, 0x17, 0x6a, 0xfc, 0x09, 0x2e, - 0xb2, 0x04, 0x93, 0x15, 0x9c, 0xbd, 0x85, 0xd6, 0x78, 0x7f, 0x8d, 0x56, 0x2f, 0x1e, 0x1d, 0x96, - 0xcf, 0x39, 0x88, 0xa9, 0x07, 0x02, 0xa5, 0x8b, 0x31, 0x99, 0xc8, 0x3a, 0x9c, 0x79, 0xd0, 0xd9, - 0xa5, 0x81, 0x47, 0x23, 0x1a, 0xca, 0x1a, 0x0d, 0x63, 0x8d, 0x2e, 0x1f, 0x1d, 0x96, 0x2f, 0x3c, - 0x56, 0xc8, 0x8c, 0x3a, 0xa5, 0x59, 0x09, 0x85, 0xd3, 0xa2, 0xa2, 0x8b, 0x4e, 0xe4, 0xec, 0x3a, - 0x21, 0xc5, 0x49, 0x69, 0x6c, 0xfe, 0x2c, 0x57, 0xf1, 0xcd, 0x04, 0xb6, 0x7a, 0x55, 0x68, 0xf9, - 0xbc, 0x6a, 0x7b, 0x53, 0xa0, 0xb4, 0x82, 0x92, 0x32, 0xd9, 0xdc, 0xac, 0xd6, 0x9d, 0x51, 0xac, - 0x2d, 0xce, 0xcd, 0x6a, 0xdd, 0xd1, 0x67, 0x2d, 0xb5, 0x02, 0xad, 0xc2, 0xe0, 0x36, 0x5b, 0x9d, - 0x71, 0xce, 0x9a, 0x9c, 0xbf, 0x22, 0x6a, 0x94, 0x1c, 0x7f, 0x37, 0xd9, 0x0f, 0x24, 0xc4, 0x2f, - 0xef, 0x34, 0xae, 0xe8, 0xfa, 0x5a, 0x8c, 0x38, 0xf2, 0x19, 0x80, 0xa8, 0x55, 0xa5, 0xdd, 0x2e, - 0x8d, 0x61, 0x23, 0xcf, 0x98, 0x8d, 0xac, 0xb4, 0xdb, 0xd5, 0x4b, 0xa2, 0x7d, 0x67, 0x55, 0xfb, - 0x9c, 0x76, 0x5b, 0x93, 0xa6, 0x09, 0x21, 0x9f, 0xc0, 0x38, 0x4e, 0x69, 0xb2, 0x47, 0xc7, 0xb1, - 0x47, 0xcf, 0x1f, 0x1d, 0x96, 0x67, 0x71, 0xb6, 0xca, 0xe8, 0x4f, 0x83, 0x81, 0xfc, 0x32, 0xcc, - 0x08, 0x71, 0x0f, 0x5d, 0xaf, 0xe9, 0x3f, 0x0d, 0x17, 0x69, 0xf8, 0x38, 0xf2, 0xdb, 0x38, 0xfd, - 0x8d, 0xcd, 0x5f, 0x30, 0xab, 0x67, 0xd2, 0x54, 0x6f, 0x88, 0x9a, 0x5a, 0xaa, 0xa6, 0x4f, 0x39, - 0x41, 0xbd, 0xc9, 0x29, 0xf4, 0x09, 0x32, 0x53, 0x04, 0x59, 0x81, 0xd3, 0xdb, 0x21, 0x35, 0xda, - 0x30, 0x89, 0xeb, 0x43, 0x99, 0xf5, 0x70, 0x27, 0xa4, 0xf5, 0xbc, 0x76, 0x24, 0xf9, 0x88, 0x0d, - 0x64, 0x31, 0xf0, 0xdb, 0x89, 0x31, 0x7e, 0x1a, 0x35, 0x62, 0x1d, 0x1d, 0x96, 0x2f, 0x35, 0x03, - 0xbf, 0x5d, 0xcf, 0x1f, 0xe8, 0x19, 0xdc, 0xe4, 0xfb, 0x70, 0x76, 0xc1, 0xf7, 0x3c, 0xda, 0x60, - 0x33, 0xe8, 0xa2, 0xeb, 0xec, 0x79, 0x7e, 0x18, 0xb9, 0x8d, 0x95, 0xc5, 0x52, 0x31, 0x5e, 0x1e, - 0x1a, 0x8a, 0xa2, 0xde, 0x54, 0x24, 0xe6, 0xf2, 0x90, 0x23, 0x85, 0x7c, 0x05, 0x13, 0xa2, 0x2c, - 0x1a, 0xe0, 0xd0, 0x3c, 0xd3, 0x7d, 0xa0, 0x29, 0x62, 0xbe, 0xd0, 0x07, 0xf2, 0x27, 0xdf, 0x3a, - 0x99, 0xb2, 0xc8, 0xd7, 0x30, 0xb6, 0x76, 0xb7, 0x62, 0xd3, 0xb0, 0xed, 0x7b, 0x21, 0x2d, 0x11, - 0xec, 0xd1, 0x4b, 0x42, 0xf4, 0xda, 0xdd, 0x4a, 0xa5, 0x13, 0xed, 0x53, 0x2f, 0x72, 0x1b, 0x4e, - 0x44, 0x25, 0x55, 0x75, 0x8e, 0x8d, 0xbc, 0x83, 0x47, 0x4e, 0x3d, 0x10, 0x10, 0xad, 0x15, 0xba, - 0x38, 0x32, 0x07, 0x23, 0xb5, 0xda, 0xf2, 0xaa, 0xbf, 0xe7, 0x7a, 0xa5, 0x29, 0xa6, 0x0c, 0x5b, - 0xfd, 0x26, 0x8f, 0x60, 0x46, 0xb3, 0x0d, 0xea, 0xec, 0x7f, 0x7a, 0x40, 0xbd, 0xa8, 0x34, 0x8d, - 0x75, 0xf8, 0xae, 0x32, 0x6e, 0x6e, 0xea, 0x26, 0xc4, 0x93, 0x3b, 0x37, 0x2b, 0xf1, 0xcf, 0x9a, - 0x64, 0xaa, 0xf6, 0x95, 0x0a, 0xf6, 0xb4, 0x93, 0x81, 0x21, 0x5b, 0x30, 0xbc, 0xd9, 0x09, 0xda, - 0x7e, 0x48, 0x4b, 0x33, 0xa8, 0xb8, 0xab, 0xdd, 0xbe, 0x50, 0x41, 0x5a, 0x9d, 0x61, 0x53, 0x74, - 0x9b, 0xff, 0xd0, 0x5a, 0x27, 0x45, 0x91, 0x4f, 0x61, 0xbc, 0x56, 0x5b, 0x8e, 0x17, 0x94, 0xb3, - 0xb8, 0xa0, 0x5c, 0x38, 0x3a, 0x2c, 0x97, 0xd8, 0x96, 0x2a, 0x5e, 0x54, 0xf4, 0xaf, 0x4a, 0xe7, - 0x60, 0x12, 0xb6, 0x56, 0x6b, 0xb1, 0x84, 0xd9, 0x58, 0x02, 0xdb, 0xcc, 0x65, 0x4b, 0xd0, 0x39, - 0xc8, 0xbf, 0x2c, 0xc0, 0x65, 0x5d, 0x64, 0x96, 0x62, 0x4a, 0xe7, 0x9e, 0x47, 0x9b, 0xf3, 0x47, - 0x87, 0xe5, 0x9b, 0x66, 0x3b, 0xea, 0x99, 0x9d, 0xa5, 0xd5, 0xad, 0x67, 0x55, 0xb0, 0xbe, 0x7a, - 0x03, 0x32, 0xeb, 0x3b, 0xf7, 0xdc, 0xf5, 0x35, 0xb5, 0xd6, 0xbb, 0xbe, 0xbd, 0xaa, 0x62, 0x7d, - 0x0e, 0xa3, 0x6a, 0xd2, 0x26, 0xc3, 0xd0, 0x5f, 0x69, 0xb5, 0x8a, 0xa7, 0xd8, 0x1f, 0xb5, 0xda, - 0x72, 0xb1, 0x40, 0x26, 0x01, 0xe2, 0x95, 0xaa, 0xd8, 0x47, 0xc6, 0x61, 0x44, 0xae, 0x24, 0xc5, - 0x7e, 0xa4, 0x6f, 0xb7, 0x8b, 0x03, 0x84, 0xc0, 0xa4, 0x39, 0x9f, 0x15, 0x07, 0xad, 0xdf, 0x2c, - 0xc0, 0xa8, 0xfa, 0x0e, 0xc9, 0x69, 0x18, 0xdb, 0x5e, 0xaf, 0x6d, 0x2e, 0x2d, 0xac, 0xdc, 0x5d, - 0x59, 0x5a, 0x2c, 0x9e, 0x22, 0x17, 0xe1, 0xdc, 0x56, 0x6d, 0xb9, 0xbe, 0x58, 0xad, 0xaf, 0x6e, - 0x2c, 0x54, 0x56, 0xeb, 0x9b, 0xf6, 0xc6, 0xe7, 0x5f, 0xd4, 0xb7, 0xb6, 0xd7, 0xd7, 0x97, 0x56, - 0x8b, 0x05, 0x52, 0x82, 0x69, 0x86, 0x7e, 0xb0, 0x5d, 0x5d, 0xd2, 0x09, 0x8a, 0x7d, 0xe4, 0x0a, - 0x5c, 0xcc, 0xc2, 0xd4, 0x97, 0x97, 0x2a, 0x8b, 0xab, 0x4b, 0xb5, 0x5a, 0xb1, 0x9f, 0xcc, 0xc2, - 0x14, 0x23, 0xa9, 0x6c, 0x6e, 0x1a, 0xbc, 0x03, 0x56, 0x0b, 0xc6, 0xb4, 0x0f, 0x80, 0x5c, 0x80, - 0xd2, 0xc2, 0x92, 0xbd, 0x55, 0xdf, 0xdc, 0xb6, 0x37, 0x37, 0x6a, 0x4b, 0x75, 0xb3, 0x86, 0x49, - 0xec, 0xea, 0xc6, 0xbd, 0x95, 0xf5, 0x3a, 0x03, 0xd5, 0x8a, 0x05, 0x56, 0x0d, 0x03, 0x5b, 0x5b, - 0x59, 0xbf, 0xb7, 0xba, 0x54, 0xdf, 0xae, 0x2d, 0x09, 0x92, 0x3e, 0xeb, 0x57, 0xfb, 0x52, 0x4b, - 0x3a, 0x99, 0x87, 0xb1, 0x1a, 0xf7, 0x57, 0xe0, 0x34, 0xc7, 0x0d, 0x44, 0xb6, 0x47, 0x1b, 0x17, - 0x6e, 0x0c, 0x3e, 0x83, 0xe9, 0x44, 0x6c, 0x97, 0xb6, 0xc9, 0xbe, 0xe6, 0x86, 0xdf, 0xd2, 0x77, - 0x69, 0x6d, 0x01, 0xb3, 0x15, 0x96, 0xcc, 0x6b, 0xfb, 0x39, 0x6e, 0x2d, 0xa2, 0x45, 0x22, 0xf7, - 0x73, 0xfa, 0xda, 0xae, 0x76, 0x76, 0xf3, 0x71, 0x97, 0x8a, 0x6d, 0x18, 0xf2, 0x64, 0xec, 0x25, - 0x14, 0x1d, 0x79, 0x5d, 0xee, 0x74, 0xb9, 0x75, 0x87, 0x8b, 0x7d, 0xc2, 0x2e, 0x11, 0x9b, 0x5c, - 0xab, 0x93, 0xb3, 0xb0, 0x92, 0x0f, 0x92, 0x63, 0x46, 0x28, 0x03, 0x85, 0x25, 0xd6, 0x4f, 0x3b, - 0x41, 0x4a, 0xca, 0x30, 0xc8, 0x67, 0x5c, 0xae, 0x0f, 0xdc, 0x5b, 0xb7, 0x18, 0xc0, 0xe6, 0x70, - 0xeb, 0x8f, 0xfa, 0xf5, 0x4d, 0x06, 0xdb, 0x4b, 0x6b, 0xfa, 0xc6, 0xbd, 0x34, 0xea, 0x19, 0xa1, - 0xcc, 0x14, 0xe4, 0x5f, 0x09, 0x9a, 0x82, 0xfd, 0xb1, 0x29, 0x28, 0x3e, 0x35, 0x6e, 0x0a, 0xc6, - 0x24, 0xac, 0x17, 0xc5, 0xb6, 0x0d, 0xa5, 0x0e, 0xc4, 0xbd, 0x28, 0xb6, 0x7a, 0xa2, 0x17, 0x35, - 0x22, 0xf2, 0x3e, 0x40, 0xe5, 0x61, 0x0d, 0x6d, 0x1e, 0x7b, 0x5d, 0x6c, 0x5d, 0x71, 0x91, 0x71, - 0x9e, 0x86, 0xc2, 0xa4, 0x0a, 0x74, 0x9b, 0x51, 0xa3, 0x26, 0x55, 0x98, 0xa8, 0xfc, 0xa8, 0x13, - 0xd0, 0x95, 0x26, 0x5b, 0xa7, 0x22, 0x6e, 0x1c, 0x8f, 0xf2, 0x89, 0xd4, 0x61, 0x88, 0xba, 0x2b, - 0x30, 0x9a, 0x00, 0x93, 0x85, 0x6c, 0xc0, 0x99, 0x7b, 0x0b, 0x9b, 0x62, 0x5c, 0x55, 0x1a, 0x0d, - 0xbf, 0xe3, 0x45, 0x62, 0xbf, 0x7a, 0xe5, 0xe8, 0xb0, 0x7c, 0x71, 0xaf, 0xd1, 0xae, 0xcb, 0x31, - 0xe8, 0x70, 0xb4, 0xbe, 0x61, 0x4d, 0xf1, 0x92, 0xab, 0xd0, 0xbf, 0x6d, 0xaf, 0x08, 0xcb, 0xf9, - 0xcc, 0xd1, 0x61, 0x79, 0xa2, 0x13, 0xb8, 0x1a, 0x0b, 0xc3, 0x92, 0xf7, 0x00, 0xb6, 0x9c, 0x60, - 0x8f, 0x46, 0x9b, 0x7e, 0x10, 0xe1, 0x86, 0x73, 0xa2, 0x7a, 0xee, 0xe8, 0xb0, 0x3c, 0x13, 0x21, - 0xb4, 0xce, 0xa6, 0x3f, 0xbd, 0xd1, 0x31, 0xf1, 0xfd, 0x81, 0x91, 0xbe, 0x62, 0xbf, 0x3d, 0x5a, - 0xa3, 0x61, 0xc8, 0xed, 0xc3, 0x16, 0x4c, 0xde, 0xa3, 0x11, 0x1b, 0xb8, 0xd2, 0xde, 0xe9, 0xde, - 0xad, 0x1f, 0xc2, 0xd8, 0x43, 0x37, 0xda, 0xaf, 0xd1, 0x46, 0x40, 0x23, 0xe9, 0xeb, 0x41, 0x95, - 0x3f, 0x75, 0xa3, 0xfd, 0x7a, 0xc8, 0xe1, 0xfa, 0xba, 0xae, 0x91, 0x5b, 0x4b, 0x70, 0x5a, 0x94, - 0xa6, 0xcc, 0xab, 0x79, 0x53, 0x60, 0x01, 0x05, 0x62, 0xb7, 0xeb, 0x02, 0x4d, 0x31, 0xff, 0xaa, - 0x0f, 0x66, 0x16, 0xf6, 0x1d, 0x6f, 0x8f, 0x6e, 0x3a, 0x61, 0xf8, 0xd4, 0x0f, 0x9a, 0x5a, 0xe5, - 0xd1, 0xb6, 0x4c, 0x55, 0x1e, 0x8d, 0xc9, 0x79, 0x18, 0xdb, 0x68, 0x35, 0x25, 0x8f, 0xb0, 0x7b, - 0xb1, 0x2c, 0xbf, 0xd5, 0xac, 0xb7, 0xa5, 0x2c, 0x9d, 0x88, 0xf1, 0xac, 0xd3, 0xa7, 0x8a, 0xa7, - 0x3f, 0xe6, 0xf1, 0xe8, 0x53, 0x8d, 0x47, 0x23, 0x22, 0x4b, 0x70, 0xa6, 0x46, 0x1b, 0xbe, 0xd7, - 0xbc, 0xeb, 0x34, 0x22, 0x3f, 0xd8, 0xf2, 0x1f, 0x53, 0x4f, 0x0c, 0x68, 0x34, 0x0c, 0x42, 0x44, - 0xd6, 0x1f, 0x21, 0xb6, 0x1e, 0x31, 0xb4, 0x9d, 0xe6, 0x20, 0x1b, 0x30, 0xf2, 0x50, 0x78, 0x0c, - 0x85, 0xb1, 0x7c, 0xed, 0xa6, 0x72, 0x21, 0x2e, 0x04, 0x14, 0x47, 0xa1, 0xd3, 0x52, 0xe6, 0xbe, - 0xda, 0x67, 0xe1, 0x54, 0x26, 0x29, 0x6d, 0x25, 0xc4, 0xda, 0x86, 0x89, 0xcd, 0x56, 0x67, 0xcf, - 0xf5, 0xd8, 0xa4, 0x53, 0xa3, 0x3f, 0x24, 0x8b, 0x00, 0x31, 0x40, 0xf8, 0x01, 0xa7, 0x84, 0x89, - 0x1d, 0x23, 0x76, 0xde, 0x14, 0x5f, 0x2e, 0x42, 0xd0, 0x22, 0xb2, 0x35, 0x3e, 0xeb, 0x7f, 0xf7, - 0x03, 0x11, 0x1d, 0x80, 0x8b, 0x60, 0x8d, 0x46, 0x6c, 0x79, 0x3a, 0x0b, 0x7d, 0xca, 0x5d, 0x37, - 0x74, 0x74, 0x58, 0xee, 0x73, 0x9b, 0x76, 0xdf, 0xca, 0x22, 0x79, 0x0b, 0x06, 0x91, 0x0c, 0xf5, - 0x3f, 0xa9, 0xca, 0xd3, 0x25, 0xf0, 0xc9, 0x07, 0x57, 0x5f, 0x9b, 0x13, 0x93, 0xb7, 0x61, 0x74, - 0x91, 0xb6, 0xe8, 0x9e, 0x13, 0xf9, 0x72, 0x3a, 0xe1, 0x0e, 0x30, 0x09, 0xd4, 0xc6, 0x5c, 0x4c, - 0xc9, 0xcc, 0x61, 0x9b, 0x3a, 0xa1, 0xef, 0xe9, 0xe6, 0x70, 0x80, 0x10, 0xdd, 0x1c, 0xe6, 0x34, - 0xe4, 0xb7, 0x0a, 0x30, 0x56, 0xf1, 0x3c, 0xe1, 0x58, 0x0a, 0x85, 0xd6, 0x67, 0x6e, 0x2a, 0x4f, - 0xec, 0xaa, 0xb3, 0x4b, 0x5b, 0x3b, 0x4e, 0xab, 0x43, 0xc3, 0xea, 0xd7, 0xcc, 0x42, 0xf9, 0x4f, - 0x87, 0xe5, 0x0f, 0x4e, 0xe0, 0x2a, 0x8a, 0x7d, 0xba, 0x5b, 0x81, 0xe3, 0x46, 0x21, 0xfb, 0x6a, - 0x9d, 0xb8, 0x40, 0xfd, 0xbb, 0xd1, 0xea, 0x11, 0xaf, 0x0d, 0x43, 0xbd, 0xd6, 0x06, 0x72, 0x00, - 0xa7, 0x2b, 0x61, 0xd8, 0x39, 0xa0, 0xb5, 0xc8, 0x09, 0xa2, 0x2d, 0xf7, 0x80, 0xe2, 0x84, 0xd4, - 0xdd, 0xb9, 0xf0, 0xda, 0x4f, 0x0e, 0xcb, 0x05, 0x66, 0x14, 0x39, 0xc8, 0xca, 0xf6, 0x3d, 0x41, - 0x54, 0x8f, 0x5c, 0x7d, 0x79, 0x43, 0x37, 0x43, 0x52, 0xb6, 0x75, 0x55, 0x6d, 0x48, 0x56, 0x16, - 0xf3, 0x7a, 0xdc, 0x5a, 0x80, 0x0b, 0xf7, 0x68, 0x64, 0xd3, 0x90, 0x46, 0xf2, 0x1b, 0xc1, 0x11, - 0x1e, 0x3b, 0x77, 0x87, 0xf1, 0xb7, 0x62, 0xc6, 0xee, 0xe7, 0xdf, 0x85, 0xc4, 0x58, 0x7f, 0xad, - 0x00, 0xe5, 0x85, 0x80, 0x72, 0x7b, 0x22, 0x47, 0x50, 0xf7, 0xb9, 0xeb, 0x02, 0x0c, 0x6c, 0x3d, - 0x6b, 0x4b, 0xaf, 0x0c, 0x62, 0x59, 0xa7, 0xd8, 0x08, 0x3d, 0xa6, 0x93, 0xcb, 0x7a, 0x04, 0x33, - 0x36, 0xf5, 0xe8, 0x53, 0x67, 0xb7, 0x45, 0x0d, 0x3f, 0x51, 0x19, 0x06, 0xf9, 0x87, 0x9e, 0x6a, - 0x02, 0x87, 0x9f, 0xcc, 0xe7, 0x66, 0x4d, 0xc0, 0xd8, 0xa6, 0xeb, 0xed, 0x09, 0xe9, 0xd6, 0x9f, - 0x0f, 0xc0, 0x38, 0xff, 0x2d, 0x4c, 0xa4, 0xc4, 0x72, 0x59, 0x38, 0xce, 0x72, 0xf9, 0x2e, 0x4c, - 0xb0, 0xf5, 0x86, 0x06, 0x3b, 0x34, 0x60, 0xf3, 0xbf, 0xd0, 0x04, 0x9a, 0x7b, 0x21, 0x22, 0xea, - 0x4f, 0x38, 0xc6, 0x36, 0x09, 0xc9, 0x2a, 0x4c, 0x72, 0xc0, 0x5d, 0xea, 0x44, 0x9d, 0xd8, 0x63, - 0x75, 0x5a, 0xd8, 0x44, 0x12, 0xcc, 0x87, 0xa6, 0x90, 0xf5, 0x48, 0x00, 0xed, 0x04, 0x2f, 0xf9, - 0x04, 0x4e, 0x6f, 0x06, 0xfe, 0x37, 0xcf, 0xb4, 0x0d, 0x02, 0xff, 0x3a, 0xb9, 0xf5, 0xc4, 0x50, - 0x75, 0x7d, 0x9b, 0x90, 0xa4, 0x26, 0xaf, 0xc3, 0xc8, 0x4a, 0x58, 0xf5, 0x03, 0xd7, 0xdb, 0xc3, - 0x6f, 0x74, 0x84, 0x3b, 0xfa, 0xdd, 0xb0, 0xbe, 0x8b, 0x40, 0x5b, 0xa1, 0x13, 0x2e, 0xe9, 0xe1, - 0xde, 0x2e, 0xe9, 0xdb, 0x00, 0xab, 0xbe, 0xd3, 0xac, 0xb4, 0x5a, 0x0b, 0x95, 0x10, 0x57, 0x62, - 0xb1, 0x1e, 0xb5, 0x7c, 0xa7, 0x59, 0x77, 0x5a, 0xad, 0x7a, 0xc3, 0x09, 0x6d, 0x8d, 0x86, 0x7c, - 0x09, 0xe7, 0x42, 0x77, 0xcf, 0xc3, 0xc6, 0xd5, 0x9d, 0xd6, 0x9e, 0x1f, 0xb8, 0xd1, 0xfe, 0x41, - 0x3d, 0xec, 0xb8, 0x11, 0xf7, 0x07, 0x4d, 0xce, 0x5f, 0x12, 0x93, 0x5c, 0x4d, 0xd2, 0x55, 0x24, - 0x59, 0x8d, 0x51, 0xd9, 0xb3, 0x61, 0x36, 0x82, 0x3c, 0x84, 0x89, 0x55, 0xb7, 0x41, 0xbd, 0x90, - 0xa2, 0x83, 0xef, 0x19, 0x7a, 0x8b, 0xba, 0x7f, 0xcc, 0x4c, 0x89, 0x13, 0x2d, 0x9d, 0x09, 0x3f, - 0x5d, 0x53, 0xce, 0xfd, 0x81, 0x91, 0xa1, 0xe2, 0xb0, 0x7d, 0x5a, 0x00, 0x1f, 0x3a, 0x81, 0xe7, - 0x7a, 0x7b, 0xa1, 0xf5, 0xb7, 0x09, 0x8c, 0xa8, 0x7e, 0xba, 0xa9, 0x5b, 0x2a, 0x62, 0x69, 0xc6, - 0x21, 0x1b, 0xfb, 0xe1, 0x6c, 0x8d, 0x82, 0x9c, 0x43, 0xdb, 0x45, 0x6c, 0x0a, 0x86, 0xd9, 0x27, - 0xe4, 0xb4, 0xdb, 0x36, 0x83, 0xb1, 0xa9, 0x61, 0xb1, 0x8a, 0x83, 0x66, 0x84, 0x4f, 0x0d, 0xcd, - 0x5d, 0xbb, 0x6f, 0xb1, 0xca, 0xbe, 0xc9, 0x8d, 0x95, 0xc5, 0x05, 0xec, 0xff, 0x11, 0xfe, 0x4d, - 0xfa, 0x6e, 0xb3, 0x61, 0x23, 0x94, 0x61, 0x6b, 0x95, 0xb5, 0x55, 0xd1, 0xc7, 0x88, 0x0d, 0x9d, - 0x83, 0x96, 0x8d, 0x50, 0xb6, 0xdb, 0xe5, 0x2e, 0x95, 0x05, 0xdf, 0x8b, 0x02, 0xbf, 0x15, 0xe2, - 0x16, 0x6e, 0x84, 0x8f, 0x41, 0xe1, 0x8b, 0x69, 0x08, 0x94, 0x9d, 0x20, 0x25, 0x0f, 0x61, 0xb6, - 0xd2, 0x7c, 0xe2, 0x78, 0x0d, 0xda, 0xe4, 0x98, 0x87, 0x7e, 0xf0, 0xf8, 0x51, 0xcb, 0x7f, 0x1a, - 0xe2, 0x20, 0x19, 0x11, 0xae, 0x4b, 0x41, 0x22, 0x5d, 0x3b, 0x4f, 0x25, 0x91, 0x9d, 0xc7, 0xcd, - 0xe6, 0x81, 0x85, 0x96, 0xdf, 0x69, 0x8a, 0xa1, 0x83, 0xf3, 0x40, 0x83, 0x01, 0x6c, 0x0e, 0x67, - 0x5a, 0x5a, 0xae, 0xad, 0xe1, 0xc0, 0x10, 0x5a, 0xda, 0x0f, 0x0f, 0x6c, 0x06, 0x23, 0xd7, 0x60, - 0x58, 0x6e, 0xdc, 0xf9, 0x49, 0x06, 0x7a, 0xd0, 0xe5, 0x86, 0x5d, 0xe2, 0xd8, 0x77, 0x6c, 0xd3, - 0x86, 0xff, 0x84, 0x06, 0xcf, 0x16, 0xfc, 0x26, 0x95, 0x6e, 0x2d, 0xe1, 0xb6, 0xe1, 0x88, 0x7a, - 0x83, 0x61, 0x6c, 0x93, 0x90, 0x15, 0xc0, 0x17, 0xee, 0xb0, 0x74, 0x3a, 0x2e, 0x80, 0x2f, 0xec, - 0xa1, 0x2d, 0x71, 0x64, 0x11, 0xce, 0x54, 0x3a, 0x91, 0x7f, 0xe0, 0x44, 0x6e, 0x63, 0xbb, 0xbd, - 0x17, 0x38, 0xac, 0x90, 0x22, 0x32, 0xa0, 0x21, 0xe3, 0x48, 0x64, 0xbd, 0x23, 0xb0, 0x76, 0x9a, - 0x81, 0xbc, 0x03, 0xe3, 0x2b, 0x21, 0x77, 0x5d, 0x3a, 0x21, 0x6d, 0xa2, 0xff, 0x49, 0xd4, 0xd2, - 0x0d, 0xeb, 0xe8, 0xc8, 0xac, 0x33, 0xd3, 0xa7, 0x69, 0x1b, 0x74, 0xc4, 0x82, 0xa1, 0x4a, 0x18, - 0xba, 0x61, 0x84, 0x6e, 0xa5, 0x91, 0x2a, 0x1c, 0x1d, 0x96, 0x87, 0x1c, 0x84, 0xd8, 0x02, 0x43, - 0x1e, 0xc2, 0xd8, 0x22, 0x65, 0x3b, 0xe7, 0xad, 0xa0, 0x13, 0x46, 0xe8, 0x24, 0x1a, 0x9b, 0x3f, - 0x27, 0x66, 0x23, 0x0d, 0x23, 0xc6, 0x32, 0xdf, 0xa2, 0x36, 0x11, 0x5e, 0x8f, 0x18, 0x42, 0x5f, - 0x6a, 0x35, 0x7a, 0x66, 0x16, 0x08, 0x9e, 0x65, 0xb7, 0xc9, 0xe6, 0x97, 0x69, 0xac, 0x03, 0x9a, - 0x05, 0x62, 0x42, 0xab, 0xef, 0x23, 0x46, 0x37, 0x0b, 0x0c, 0x16, 0xd2, 0x48, 0x79, 0xc3, 0x67, - 0x0c, 0x8f, 0xa7, 0x89, 0x94, 0x55, 0x3c, 0xa1, 0xaf, 0xfc, 0x43, 0x18, 0x5b, 0xe8, 0x84, 0x91, - 0x7f, 0xb0, 0xb5, 0x4f, 0x0f, 0x28, 0x3a, 0x92, 0x84, 0xf1, 0xd3, 0x40, 0x70, 0x3d, 0x62, 0x70, - 0xbd, 0x99, 0x1a, 0x39, 0xf9, 0x0c, 0x88, 0xb4, 0x62, 0xee, 0xb1, 0xf1, 0xe1, 0xb1, 0xb1, 0x8c, - 0xbe, 0xa4, 0x11, 0x6e, 0xba, 0x48, 0xe3, 0xa7, 0xbe, 0xa7, 0xd0, 0xba, 0x3f, 0x33, 0xcd, 0xcc, - 0x2a, 0xc4, 0xab, 0x78, 0x2f, 0x70, 0xda, 0xfb, 0xa5, 0x52, 0x6c, 0x1a, 0x88, 0x46, 0xed, 0x31, - 0xb8, 0xb1, 0xc5, 0x89, 0xc9, 0x49, 0x0d, 0x80, 0xff, 0x5c, 0x65, 0x1d, 0xcf, 0xbd, 0x4f, 0x25, - 0x43, 0x5f, 0x0c, 0x21, 0x75, 0x85, 0xe6, 0x8e, 0x10, 0xdb, 0x72, 0x8d, 0xde, 0xd4, 0xc4, 0x90, - 0xc7, 0x50, 0xe4, 0xbf, 0xd6, 0x7c, 0xcf, 0x8d, 0xf8, 0x7a, 0x31, 0x67, 0xb8, 0x2a, 0x93, 0x68, - 0x59, 0x00, 0xba, 0x88, 0x45, 0x01, 0x07, 0x0a, 0xab, 0x15, 0x93, 0x12, 0x4c, 0x36, 0x61, 0x6c, - 0x33, 0xf0, 0x9b, 0x9d, 0x46, 0x84, 0xbb, 0x8c, 0xf3, 0x38, 0xf1, 0x13, 0x51, 0x8e, 0x86, 0xe1, - 0x3a, 0x69, 0x73, 0x40, 0x9d, 0xad, 0x0b, 0xba, 0x4e, 0x34, 0x42, 0x52, 0x85, 0xa1, 0x4d, 0xbf, - 0xe5, 0x36, 0x9e, 0x95, 0x2e, 0x60, 0xa5, 0xa7, 0xa5, 0x30, 0x04, 0xca, 0xaa, 0xe2, 0x96, 0xb6, - 0x8d, 0x20, 0x7d, 0x4b, 0xcb, 0x89, 0x48, 0x05, 0x26, 0x3e, 0x63, 0x03, 0xc6, 0xf5, 0x3d, 0xcf, - 0x71, 0x03, 0x5a, 0xba, 0x88, 0xfd, 0x82, 0x6e, 0xfc, 0x1f, 0xea, 0x08, 0x7d, 0x38, 0x1b, 0x1c, - 0x64, 0x05, 0x4e, 0xaf, 0x84, 0xb5, 0x28, 0x70, 0xdb, 0x74, 0xcd, 0xf1, 0x9c, 0x3d, 0xda, 0x2c, - 0x5d, 0x8a, 0xfd, 0xe8, 0x6e, 0x58, 0x0f, 0x11, 0x57, 0x3f, 0xe0, 0x48, 0xdd, 0x8f, 0x9e, 0xe0, - 0x23, 0x9f, 0xc3, 0xf4, 0xd2, 0x37, 0x11, 0x1b, 0x31, 0xad, 0x4a, 0xa7, 0xe9, 0x46, 0xb5, 0xc8, - 0x0f, 0x9c, 0x3d, 0x5a, 0x2a, 0xa3, 0xbc, 0x57, 0x8e, 0x0e, 0xcb, 0x97, 0xa9, 0xc0, 0xd7, 0x1d, - 0x46, 0x50, 0x0f, 0x39, 0x85, 0x7e, 0x3e, 0x9e, 0x25, 0x81, 0x69, 0xbf, 0xd6, 0x69, 0xb3, 0xdd, - 0x36, 0x6a, 0xff, 0xb2, 0xa1, 0x7d, 0x0d, 0xc3, 0xb5, 0x1f, 0x72, 0x40, 0x4a, 0xfb, 0x1a, 0x21, - 0xb1, 0x81, 0xdc, 0xf7, 0x5d, 0xaf, 0xd2, 0x88, 0xdc, 0x27, 0x54, 0x58, 0xcc, 0x61, 0xe9, 0x0a, - 0xd6, 0x14, 0x7d, 0xfe, 0xbf, 0xe8, 0xbb, 0x5e, 0xdd, 0x41, 0x74, 0x3d, 0x14, 0x78, 0xfd, 0x1b, - 0x49, 0x73, 0x93, 0xef, 0xc3, 0xd9, 0x35, 0x7f, 0xd7, 0x6d, 0x51, 0x3e, 0xe5, 0x70, 0xb5, 0xa0, - 0xff, 0xd2, 0x42, 0xb9, 0xe8, 0xf3, 0x3f, 0x40, 0x8a, 0xba, 0x98, 0xad, 0x0e, 0x14, 0x8d, 0xee, - 0xf3, 0xcf, 0x96, 0x42, 0x96, 0x60, 0x1c, 0xbf, 0xcb, 0x16, 0xfe, 0x0c, 0x4b, 0x57, 0xd1, 0xa4, - 0xbb, 0x92, 0xd8, 0xa5, 0xdd, 0x5c, 0xd2, 0x68, 0x96, 0xbc, 0x28, 0x78, 0x66, 0x1b, 0x6c, 0xe4, - 0x63, 0x98, 0x4b, 0x0e, 0xef, 0x05, 0xdf, 0x7b, 0xe4, 0xee, 0x75, 0x02, 0xda, 0x2c, 0xbd, 0xc2, - 0xaa, 0x6a, 0x77, 0xa1, 0x20, 0x5f, 0xc1, 0x0c, 0xae, 0x75, 0x15, 0xcf, 0xf7, 0x9e, 0x1d, 0xb8, - 0x3f, 0xc2, 0xfd, 0x33, 0xdb, 0xf6, 0x5e, 0xc3, 0x6d, 0xef, 0xb5, 0xa3, 0xc3, 0xf2, 0x15, 0x5c, - 0x13, 0xeb, 0x8e, 0x4e, 0x91, 0xf0, 0x5a, 0x67, 0xcb, 0x98, 0x7b, 0x08, 0x67, 0x52, 0xf5, 0x27, - 0x45, 0xe8, 0x7f, 0x2c, 0xce, 0x67, 0x47, 0x6d, 0xf6, 0x27, 0x79, 0x03, 0x06, 0x9f, 0x30, 0x43, - 0x0d, 0xb7, 0x23, 0xf1, 0x89, 0x9f, 0xc6, 0xba, 0xe2, 0x3d, 0xf2, 0x6d, 0x4e, 0xf4, 0x7e, 0xdf, - 0xbb, 0x85, 0xfb, 0x03, 0x23, 0x63, 0xc5, 0x71, 0x7e, 0xac, 0x7e, 0x7f, 0x60, 0x64, 0xa2, 0x38, - 0x69, 0x55, 0xe0, 0x74, 0x82, 0x9e, 0x94, 0x60, 0x98, 0x7a, 0x6c, 0xf3, 0xdf, 0xe4, 0x1b, 0x22, - 0x5b, 0xfe, 0x24, 0xd3, 0x30, 0xd8, 0x72, 0x0f, 0xdc, 0x08, 0x0b, 0x1c, 0xb4, 0xf9, 0x0f, 0xeb, - 0xb7, 0x0b, 0x40, 0xd2, 0xeb, 0x11, 0xb9, 0x95, 0x10, 0xc3, 0xb7, 0xbe, 0x02, 0xa4, 0x1f, 0x1c, - 0x48, 0xe9, 0x9f, 0xc1, 0x14, 0x1f, 0x10, 0x72, 0xe5, 0xd4, 0xca, 0xe2, 0x33, 0x76, 0x06, 0x5a, - 0x77, 0x36, 0x09, 0x34, 0xae, 0xb3, 0xab, 0x58, 0xb5, 0x0e, 0xcc, 0x64, 0xae, 0x44, 0x64, 0x0d, - 0x66, 0x0e, 0x7c, 0x2f, 0xda, 0x6f, 0x3d, 0x93, 0x0b, 0x91, 0x28, 0xad, 0x80, 0xa5, 0xe1, 0xe4, - 0x9b, 0x49, 0x60, 0x4f, 0x09, 0xb0, 0x90, 0x88, 0xe5, 0x08, 0xa7, 0x93, 0x6c, 0x89, 0x65, 0xc3, - 0x99, 0xd4, 0x84, 0x4e, 0x3e, 0x82, 0xf1, 0x06, 0x1a, 0x77, 0x46, 0x49, 0x7c, 0x39, 0xd3, 0xe0, - 0xfa, 0xb7, 0xca, 0xe1, 0xbc, 0x29, 0xff, 0xac, 0x00, 0xb3, 0x39, 0x53, 0xf9, 0xc9, 0x55, 0xfd, - 0x05, 0x9c, 0x3d, 0x70, 0xbe, 0xa9, 0x07, 0x68, 0xbb, 0xd7, 0x03, 0xc7, 0x4b, 0x68, 0x1b, 0xa7, - 0xa9, 0x6c, 0x0a, 0x3d, 0xb6, 0xe9, 0xc0, 0xf9, 0xc6, 0x46, 0x02, 0x9b, 0xe1, 0x79, 0x3d, 0x3f, - 0x85, 0x09, 0x63, 0xf2, 0x3e, 0x71, 0xe5, 0xac, 0x3b, 0x70, 0x66, 0x91, 0xb6, 0x68, 0x44, 0x8f, - 0xed, 0xb3, 0xb3, 0x36, 0x01, 0x6a, 0xf4, 0xc0, 0x69, 0xef, 0xfb, 0x6c, 0x53, 0x5f, 0xd5, 0x7f, - 0x09, 0x9f, 0x0f, 0x91, 0xe6, 0x89, 0x44, 0xec, 0xbc, 0xc9, 0x37, 0xfa, 0xa1, 0xa2, 0xb4, 0x35, - 0x2e, 0xeb, 0xdf, 0xf7, 0x01, 0x11, 0xb3, 0x6f, 0x40, 0x9d, 0x03, 0x59, 0x8d, 0xf7, 0x60, 0x9c, - 0x5b, 0xe8, 0x1c, 0x8c, 0xd5, 0x19, 0x9b, 0x9f, 0x12, 0x5f, 0x9e, 0x8e, 0x5a, 0x3e, 0x65, 0x1b, - 0xa4, 0x8c, 0xd5, 0xa6, 0xdc, 0xb5, 0x80, 0xac, 0x7d, 0x06, 0xab, 0x8e, 0x62, 0xac, 0xfa, 0x6f, - 0xf2, 0x09, 0x4c, 0x2e, 0xf8, 0x07, 0x6d, 0xa6, 0x13, 0xc1, 0xdc, 0x2f, 0xdc, 0x36, 0xa2, 0x5c, - 0x03, 0xb9, 0x7c, 0xca, 0x4e, 0x90, 0x93, 0x75, 0x98, 0xba, 0xdb, 0xea, 0x84, 0xfb, 0x15, 0xaf, - 0xb9, 0xd0, 0xf2, 0x43, 0x29, 0x65, 0x40, 0x58, 0x5a, 0x62, 0xee, 0x4c, 0x53, 0x2c, 0x9f, 0xb2, - 0xb3, 0x18, 0xc9, 0x35, 0x18, 0x5c, 0x7a, 0xc2, 0xe6, 0x74, 0x19, 0xe1, 0x22, 0x02, 0xf0, 0x36, - 0x3c, 0xba, 0xf1, 0x68, 0xf9, 0x94, 0xcd, 0xb1, 0xd5, 0x51, 0x18, 0x96, 0xd6, 0xfd, 0x2d, 0xb6, - 0xdf, 0x56, 0xea, 0xac, 0x45, 0x4e, 0xd4, 0x09, 0xc9, 0x1c, 0x8c, 0x6c, 0xb7, 0x99, 0xd1, 0x29, - 0xdd, 0x22, 0xb6, 0xfa, 0x6d, 0xbd, 0x61, 0x6a, 0x9a, 0x5c, 0x80, 0xd8, 0xa7, 0x2b, 0x88, 0x35, - 0x27, 0xef, 0xb2, 0xa9, 0xdc, 0xee, 0xd4, 0x46, 0xb9, 0x7d, 0x89, 0x72, 0x8b, 0x49, 0x5d, 0x5b, - 0x33, 0x99, 0xca, 0xb3, 0x3e, 0x87, 0x4b, 0xdb, 0xed, 0x90, 0x06, 0x51, 0xa5, 0xdd, 0x6e, 0xb9, - 0x0d, 0x7e, 0x42, 0x86, 0x5e, 0x00, 0x39, 0x58, 0xde, 0x81, 0x21, 0x0e, 0x10, 0xc3, 0x44, 0x8e, - 0xc1, 0x4a, 0xbb, 0x2d, 0x7c, 0x0f, 0x6f, 0xf2, 0x9d, 0x3f, 0xf7, 0x26, 0xd8, 0x82, 0xda, 0xfa, - 0x8d, 0x02, 0x5c, 0xe2, 0x5f, 0x40, 0xae, 0xe8, 0xef, 0xc0, 0x28, 0xc6, 0xbf, 0xb5, 0x9d, 0x86, - 0xfc, 0x26, 0x78, 0x20, 0xa0, 0x04, 0xda, 0x31, 0x5e, 0x8b, 0x2c, 0xec, 0xcb, 0x8f, 0x2c, 0x94, - 0x1f, 0x58, 0x7f, 0xe6, 0x07, 0xf6, 0x19, 0x58, 0xa2, 0x46, 0xad, 0x56, 0xaa, 0x52, 0xe1, 0xf3, - 0xd4, 0xca, 0xfa, 0xef, 0x7d, 0x30, 0x7b, 0x8f, 0x7a, 0x34, 0x70, 0xb0, 0x9d, 0x86, 0x97, 0x4b, - 0x8f, 0x30, 0x2a, 0x74, 0x8d, 0x30, 0x2a, 0x4b, 0xbf, 0x61, 0x1f, 0xfa, 0x0d, 0x53, 0xe1, 0x52, - 0xcc, 0x16, 0xdd, 0xb6, 0x57, 0x44, 0xb3, 0xd0, 0x16, 0xed, 0x04, 0x2e, 0x3f, 0x65, 0x58, 0x89, - 0xa3, 0x93, 0x06, 0x7a, 0xfa, 0x1c, 0xa6, 0x44, 0xb4, 0xc6, 0xb0, 0x88, 0x4e, 0x32, 0x63, 0x92, - 0xd6, 0x61, 0x88, 0xbb, 0x3b, 0xf1, 0x6c, 0x6b, 0x6c, 0xfe, 0x86, 0xf8, 0xa6, 0x72, 0x1a, 0x28, - 0x7c, 0xa3, 0xb8, 0xb0, 0xf3, 0x21, 0x10, 0x21, 0xc0, 0x16, 0x52, 0xe6, 0x3e, 0x83, 0x31, 0x8d, - 0xe4, 0x38, 0x6b, 0xbf, 0x72, 0xbb, 0xb2, 0xed, 0xa8, 0xb7, 0xc7, 0x3d, 0xb8, 0xda, 0xda, 0x6f, - 0x7d, 0x00, 0xa5, 0x74, 0x6d, 0x84, 0xab, 0xad, 0x97, 0x67, 0xcf, 0x5a, 0x84, 0xe9, 0x7b, 0x34, - 0xc2, 0x81, 0x8b, 0x1f, 0x91, 0x16, 0x65, 0x97, 0xf8, 0xce, 0xe4, 0xac, 0x8a, 0x40, 0x36, 0xc0, - 0xb4, 0xaf, 0xb4, 0x06, 0x33, 0x09, 0x29, 0xa2, 0xfc, 0xf7, 0x61, 0x58, 0x80, 0xd4, 0x8c, 0x2a, - 0x42, 0x75, 0xe9, 0xae, 0x40, 0xec, 0xcc, 0xf3, 0x71, 0x2b, 0x24, 0xdb, 0x92, 0xc1, 0xda, 0x87, - 0xb3, 0x6c, 0x99, 0x8d, 0xa5, 0xaa, 0xe1, 0x78, 0x1e, 0x46, 0xdb, 0x6c, 0xa3, 0x10, 0xba, 0x3f, - 0xe2, 0xc3, 0x68, 0xd0, 0x1e, 0x61, 0x80, 0x9a, 0xfb, 0x23, 0x4a, 0x2e, 0x02, 0x20, 0x12, 0x9b, - 0x29, 0x66, 0x01, 0x24, 0xe7, 0xae, 0x4c, 0x02, 0x18, 0xa3, 0xc7, 0xc7, 0x8d, 0x8d, 0x7f, 0x5b, - 0x01, 0xcc, 0xa6, 0x4a, 0x12, 0x0d, 0xb8, 0x05, 0x23, 0x72, 0x7f, 0x9c, 0x38, 0x64, 0xd0, 0x5b, - 0x60, 0x2b, 0x22, 0xf2, 0x2a, 0x9c, 0xf6, 0xe8, 0x37, 0x51, 0x3d, 0x55, 0x87, 0x09, 0x06, 0xde, - 0x94, 0xf5, 0xb0, 0x7e, 0x01, 0x1d, 0xcb, 0x35, 0xcf, 0x7f, 0xfa, 0xa8, 0xe5, 0x3c, 0xa6, 0xa9, - 0x82, 0x3f, 0x82, 0x91, 0x5a, 0xef, 0x82, 0xf9, 0xe7, 0x23, 0x0b, 0xb7, 0x15, 0x8b, 0xd5, 0x82, - 0x39, 0xd6, 0xa4, 0x5a, 0x65, 0x6d, 0x75, 0xa5, 0xb9, 0xf9, 0x6d, 0x2b, 0xf0, 0x09, 0x9c, 0xcf, - 0x2c, 0xed, 0xdb, 0x56, 0xe2, 0x1f, 0x0c, 0xc0, 0x2c, 0x5f, 0x4c, 0xd2, 0x23, 0xf8, 0xf8, 0x53, - 0xcd, 0xcf, 0xe5, 0xbc, 0xf7, 0x76, 0xc6, 0x79, 0x2f, 0xb2, 0xe8, 0xe7, 0xbd, 0xc6, 0x29, 0xef, - 0xbb, 0xd9, 0xa7, 0xbc, 0xe8, 0x84, 0x32, 0x4f, 0x79, 0x93, 0x67, 0xbb, 0x4b, 0xf9, 0x67, 0xbb, - 0x78, 0xf0, 0x94, 0x71, 0xb6, 0x9b, 0x75, 0xa2, 0x9b, 0x08, 0x94, 0x1a, 0x79, 0xb9, 0x81, 0x52, - 0xaf, 0xc2, 0x70, 0xa5, 0xdd, 0xd6, 0x02, 0x0f, 0xb1, 0x7b, 0x9c, 0x76, 0x9b, 0x2b, 0x4f, 0x22, - 0xe5, 0x3c, 0x0f, 0x19, 0xf3, 0xfc, 0x7b, 0x00, 0x0b, 0x78, 0x3d, 0x02, 0x3b, 0x6e, 0x0c, 0x29, - 0x70, 0x87, 0xcf, 0x2f, 0x4d, 0x60, 0xc7, 0xe9, 0xee, 0x95, 0x98, 0x98, 0x6f, 0xec, 0xad, 0x1d, - 0x28, 0xa5, 0x87, 0xcf, 0x4b, 0x98, 0xba, 0x7e, 0xbf, 0x00, 0x17, 0xc5, 0x26, 0x27, 0xf1, 0x81, - 0x9f, 0x7c, 0x74, 0xbe, 0x0d, 0xe3, 0x82, 0x77, 0x2b, 0xfe, 0x10, 0xf8, 0x01, 0xbb, 0x9c, 0x8c, - 0xf9, 0x8c, 0x6e, 0x90, 0x91, 0xb7, 0x61, 0x04, 0xff, 0x88, 0x0f, 0x86, 0x98, 0x66, 0x46, 0x91, - 0xb4, 0x9e, 0x3c, 0x1e, 0x52, 0xa4, 0xd6, 0xd7, 0x70, 0x29, 0xaf, 0xe2, 0x2f, 0x41, 0x2f, 0xff, - 0xa6, 0x00, 0xe7, 0x85, 0x78, 0x63, 0xaa, 0x78, 0xae, 0x55, 0xe7, 0x04, 0xe1, 0xca, 0xf7, 0x61, - 0x8c, 0x15, 0x28, 0xeb, 0xdd, 0x2f, 0x96, 0x56, 0x61, 0x39, 0xc4, 0x98, 0x45, 0x27, 0x72, 0x44, - 0xf8, 0x8d, 0x73, 0xd0, 0x92, 0x9e, 0x11, 0x5b, 0x67, 0xb6, 0xbe, 0x84, 0x0b, 0xd9, 0x4d, 0x78, - 0x09, 0xfa, 0xb9, 0x0f, 0x73, 0x19, 0x8b, 0xc2, 0xf3, 0xad, 0xc9, 0x5f, 0xc0, 0xf9, 0x4c, 0x59, - 0x2f, 0xa1, 0x9a, 0xcb, 0x6c, 0xc7, 0x11, 0xbd, 0x84, 0x2e, 0xb4, 0x1e, 0xc2, 0xb9, 0x0c, 0x49, - 0x2f, 0xa1, 0x8a, 0xf7, 0x60, 0x56, 0xed, 0xb4, 0x5f, 0xa8, 0x86, 0x6b, 0x70, 0x91, 0x0b, 0x7a, - 0x39, 0xbd, 0xf2, 0x00, 0xce, 0x0b, 0x71, 0x2f, 0x41, 0x7b, 0xcb, 0x70, 0x21, 0x36, 0xa8, 0x33, - 0xf6, 0x49, 0xc7, 0x9e, 0x64, 0xac, 0x55, 0xb8, 0x1c, 0x4b, 0xca, 0xd9, 0x34, 0x1c, 0x5f, 0x1a, - 0xdf, 0x0e, 0xc6, 0xbd, 0xf4, 0x52, 0x7a, 0xf4, 0x21, 0x9c, 0x35, 0x84, 0xbe, 0xb4, 0xad, 0xd2, - 0x0a, 0x4c, 0x71, 0xc1, 0xe6, 0xd6, 0x79, 0x5e, 0xdf, 0x3a, 0x8f, 0xcd, 0x9f, 0x89, 0x45, 0x22, - 0x78, 0xe7, 0xcd, 0x8c, 0xdd, 0xf4, 0x1a, 0xee, 0xa6, 0x25, 0x49, 0x5c, 0xc3, 0xb7, 0x61, 0x88, - 0x43, 0x44, 0xfd, 0x32, 0x84, 0x71, 0x63, 0x81, 0xb3, 0x09, 0x62, 0xeb, 0xfb, 0x70, 0x91, 0x5b, - 0xa2, 0xf1, 0x41, 0xa5, 0x69, 0x2d, 0x7e, 0x94, 0x30, 0x44, 0xcf, 0x09, 0xb9, 0x49, 0xfa, 0x1c, - 0x7b, 0x74, 0x57, 0x8e, 0xed, 0x3c, 0xf9, 0xc7, 0xba, 0xba, 0x26, 0x0d, 0xcc, 0xbe, 0x4c, 0x03, - 0xf3, 0x2a, 0x5c, 0x51, 0x06, 0x66, 0xb2, 0x18, 0x39, 0xb4, 0xac, 0x2f, 0xe1, 0x3c, 0x6f, 0xa8, - 0x0c, 0x29, 0x34, 0xab, 0xf1, 0x41, 0xa2, 0x99, 0xb3, 0xa2, 0x99, 0x26, 0x75, 0x4e, 0x23, 0xff, - 0x4e, 0x41, 0x7e, 0x72, 0xd9, 0xc2, 0x7f, 0xde, 0x16, 0xf7, 0x3a, 0x94, 0x95, 0x42, 0xcc, 0x1a, - 0x3d, 0x9f, 0xb9, 0xbd, 0x06, 0x33, 0xba, 0x18, 0xb7, 0x41, 0x77, 0xee, 0xe0, 0x09, 0xd2, 0x5b, - 0xec, 0xb3, 0x40, 0x80, 0x1c, 0x76, 0xa5, 0x0c, 0xbd, 0x21, 0xbd, 0xad, 0x28, 0xad, 0x3a, 0x5c, - 0x48, 0x77, 0x85, 0xdb, 0x90, 0xf7, 0x09, 0xc8, 0x27, 0xec, 0x13, 0x46, 0x88, 0xe8, 0x8c, 0x5c, - 0xa1, 0xf2, 0x3b, 0xe6, 0xec, 0x92, 0xcb, 0xb2, 0xe4, 0x54, 0x93, 0x68, 0x3f, 0x2b, 0x5d, 0x8e, - 0x87, 0x1f, 0x03, 0x91, 0xa8, 0x85, 0x9a, 0x2d, 0x8b, 0x3e, 0x07, 0xfd, 0x0b, 0x35, 0x5b, 0x5c, - 0x64, 0xc2, 0x9d, 0x60, 0x23, 0x0c, 0x6c, 0x06, 0x4b, 0xee, 0xc8, 0xfb, 0x8e, 0xb1, 0x23, 0xbf, - 0x3f, 0x30, 0xd2, 0x5f, 0x1c, 0xb0, 0x49, 0xcd, 0xdd, 0xf3, 0x1e, 0xba, 0xd1, 0xbe, 0x2a, 0xb0, - 0x62, 0x7d, 0x05, 0x53, 0x46, 0xf1, 0xe2, 0x2b, 0xee, 0x7a, 0x03, 0x8b, 0xed, 0x67, 0x17, 0x2a, - 0x18, 0x56, 0x83, 0x2e, 0x8b, 0x71, 0x3e, 0xdf, 0x34, 0x9c, 0x3a, 0x5e, 0xef, 0xb5, 0x25, 0xd2, - 0xfa, 0xdd, 0x01, 0x4d, 0xba, 0x76, 0xaf, 0xad, 0x4b, 0xeb, 0xee, 0x00, 0xf0, 0x11, 0xa2, 0x35, - 0x8e, 0x6d, 0x00, 0xc7, 0x44, 0xb4, 0x0a, 0x9f, 0x92, 0x6d, 0x8d, 0xe8, 0xb8, 0xf7, 0xde, 0x44, - 0xfc, 0x31, 0x67, 0x92, 0x57, 0x3d, 0x55, 0xfc, 0xb1, 0x10, 0x1d, 0xda, 0x3a, 0x11, 0xf9, 0x7e, - 0xf2, 0x72, 0xc6, 0x20, 0x1e, 0x58, 0xbd, 0x22, 0x4f, 0xb0, 0xd3, 0x6d, 0x3b, 0xd9, 0xfd, 0x8c, - 0xa7, 0x30, 0xc3, 0x78, 0xdd, 0x47, 0x68, 0x58, 0x2c, 0x7d, 0x13, 0x51, 0x8f, 0xcf, 0xed, 0x43, - 0x58, 0xce, 0xb5, 0x2e, 0xe5, 0xc4, 0xc4, 0xc2, 0xff, 0x1e, 0xcb, 0xa9, 0x53, 0x85, 0xb3, 0xb3, - 0xe5, 0xe3, 0x20, 0xb2, 0x57, 0x97, 0xbc, 0x66, 0xdb, 0x77, 0x95, 0xc1, 0xc4, 0x07, 0x51, 0xd0, - 0xaa, 0x53, 0x01, 0xb7, 0x75, 0x22, 0xeb, 0xd5, 0xae, 0x51, 0xed, 0x23, 0x30, 0xb0, 0xb5, 0xb0, - 0xb5, 0x5a, 0x2c, 0x58, 0xb7, 0x00, 0xb4, 0x92, 0x00, 0x86, 0xd6, 0x37, 0xec, 0xb5, 0xca, 0x6a, - 0xf1, 0x14, 0x99, 0x81, 0x33, 0x0f, 0x57, 0xd6, 0x17, 0x37, 0x1e, 0xd6, 0xea, 0xb5, 0xb5, 0x8a, - 0xbd, 0xb5, 0x50, 0xb1, 0x17, 0x8b, 0x05, 0xeb, 0x6b, 0x98, 0x36, 0x5b, 0xf8, 0x52, 0x07, 0x61, - 0x04, 0x53, 0x6a, 0x3f, 0x73, 0xff, 0xe1, 0x96, 0x16, 0xd1, 0x2a, 0x8c, 0xbf, 0x64, 0x64, 0x96, - 0x30, 0x13, 0xc5, 0x67, 0xa4, 0x11, 0x91, 0xd7, 0xf9, 0xb6, 0x20, 0x79, 0x73, 0x99, 0x6d, 0x0b, - 0xea, 0xf1, 0xbe, 0x00, 0xa7, 0xbe, 0xef, 0xc1, 0xb4, 0x59, 0xea, 0x71, 0xbd, 0x54, 0xaf, 0x60, - 0xa8, 0xaf, 0x76, 0xad, 0x89, 0x10, 0xfd, 0xd8, 0x40, 0xcc, 0xac, 0xdf, 0x83, 0xa2, 0xa0, 0x8a, - 0x57, 0xde, 0xab, 0xd2, 0x8d, 0x58, 0xc8, 0xb8, 0x84, 0x29, 0x83, 0xd2, 0x7d, 0x28, 0xb2, 0x19, - 0x53, 0x70, 0xf2, 0x02, 0xa6, 0x61, 0x70, 0x35, 0x3e, 0xce, 0xb1, 0xf9, 0x0f, 0xbc, 0xdd, 0x13, - 0x39, 0x41, 0x24, 0xe3, 0xe0, 0x46, 0x6d, 0xf5, 0x9b, 0xbc, 0x0e, 0x43, 0x77, 0xdd, 0x56, 0x24, - 0x5c, 0x23, 0xf1, 0x22, 0xcf, 0xc4, 0x72, 0x84, 0x2d, 0x08, 0x2c, 0x1b, 0xce, 0x68, 0x05, 0x9e, - 0xa0, 0xaa, 0xa4, 0x04, 0xc3, 0xeb, 0xf4, 0x1b, 0xad, 0x7c, 0xf9, 0xd3, 0x7a, 0x07, 0xce, 0x88, - 0x18, 0x43, 0x4d, 0x4d, 0x57, 0xc4, 0x5d, 0xf1, 0x82, 0x71, 0x61, 0x55, 0x88, 0x44, 0x14, 0xe3, - 0xdb, 0x6e, 0x37, 0x9f, 0x93, 0x8f, 0x2d, 0x14, 0x27, 0xe4, 0x7b, 0x4d, 0x9e, 0x02, 0xf5, 0xea, - 0xce, 0xbf, 0xd1, 0x07, 0xa5, 0x84, 0x97, 0x61, 0x61, 0xdf, 0x69, 0xb5, 0xa8, 0xb7, 0x47, 0xc9, - 0x75, 0x18, 0xd8, 0xda, 0xd8, 0xda, 0x14, 0x5e, 0x52, 0x19, 0x5d, 0xc0, 0x40, 0x8a, 0xc6, 0x46, - 0x0a, 0xf2, 0x00, 0xce, 0xc8, 0x28, 0x62, 0x85, 0x12, 0x3d, 0x74, 0xb1, 0x7b, 0x4c, 0x72, 0x9a, - 0x8f, 0xbc, 0x25, 0x5c, 0x22, 0x3f, 0xec, 0xb8, 0x01, 0x6d, 0xa2, 0xe7, 0x27, 0x3e, 0xaa, 0xd7, - 0x30, 0xb6, 0x4e, 0x46, 0xbe, 0x07, 0xe3, 0xb5, 0xda, 0x46, 0x5c, 0xfa, 0xa0, 0x71, 0x42, 0xa4, - 0xa3, 0x6c, 0x83, 0x90, 0x5f, 0x09, 0xb6, 0xfe, 0xa0, 0x00, 0xb3, 0x39, 0xee, 0x16, 0xf2, 0xba, - 0xa1, 0x87, 0x29, 0x4d, 0x0f, 0x92, 0x64, 0xf9, 0x94, 0x50, 0xc4, 0x82, 0x16, 0x93, 0xdd, 0x7f, - 0x82, 0x98, 0xec, 0xe5, 0x53, 0x71, 0x1c, 0x36, 0x79, 0x15, 0xfa, 0x6b, 0xb5, 0x0d, 0xe1, 0x56, - 0x27, 0x71, 0x0b, 0x34, 0x62, 0x46, 0x50, 0x05, 0x18, 0x91, 0x20, 0xeb, 0x34, 0x4c, 0x18, 0x1d, - 0x63, 0x59, 0x30, 0xae, 0xd7, 0x90, 0xf5, 0xfe, 0x82, 0xdf, 0x54, 0xbd, 0xcf, 0xfe, 0xb6, 0x7e, - 0x6c, 0xea, 0x8c, 0x5c, 0x04, 0x90, 0xe7, 0xb5, 0x6e, 0x53, 0x9e, 0xfc, 0x08, 0xc8, 0x4a, 0x93, - 0x5c, 0x81, 0xf1, 0x80, 0x36, 0xdd, 0x80, 0x36, 0xa2, 0x7a, 0x27, 0x10, 0x17, 0x63, 0xec, 0x31, - 0x09, 0xdb, 0x0e, 0x5a, 0xe4, 0x3b, 0x30, 0xc4, 0x0f, 0x92, 0x45, 0xeb, 0xa5, 0x91, 0x50, 0xab, - 0x6d, 0xac, 0xdd, 0xad, 0xf0, 0x83, 0x6e, 0x5b, 0x90, 0x58, 0x55, 0x18, 0xd3, 0x5a, 0xd5, 0xab, - 0xf4, 0x69, 0x18, 0xd4, 0xbd, 0x94, 0xfc, 0x87, 0xf5, 0x9b, 0x05, 0x98, 0xc6, 0x61, 0xb0, 0xe7, - 0xb2, 0xe5, 0x21, 0x6e, 0xcb, 0xbc, 0xd1, 0x69, 0x17, 0x8c, 0x4e, 0x4b, 0xd0, 0xaa, 0xde, 0x7b, - 0x3f, 0xd5, 0x7b, 0x17, 0xb2, 0x7a, 0x0f, 0xa7, 0x00, 0xd7, 0xf7, 0xf4, 0x4e, 0xd3, 0x8f, 0xeb, - 0x7e, 0xbb, 0x00, 0x53, 0x5a, 0x9d, 0x54, 0x03, 0xef, 0x18, 0x55, 0x3a, 0x9f, 0x51, 0xa5, 0xd4, - 0x78, 0xaa, 0xa6, 0x6a, 0xf4, 0x4a, 0xb7, 0x1a, 0x65, 0x0d, 0x27, 0x63, 0x98, 0xfc, 0x79, 0x01, - 0x66, 0x32, 0x75, 0x40, 0xce, 0xb2, 0xfd, 0x7f, 0x23, 0xa0, 0x91, 0xd0, 0xbc, 0xf8, 0xc5, 0xe0, - 0x2b, 0x61, 0xd8, 0xa1, 0x81, 0xd0, 0xbb, 0xf8, 0x45, 0x5e, 0x81, 0x89, 0x4d, 0x1a, 0xb8, 0x7e, - 0x93, 0x5f, 0x4c, 0xe0, 0x11, 0xbf, 0x13, 0xb6, 0x09, 0x24, 0x17, 0x60, 0x54, 0x45, 0xac, 0x72, - 0x1f, 0xae, 0x1d, 0x03, 0x98, 0xec, 0x45, 0x77, 0x8f, 0x1f, 0xfc, 0x30, 0x66, 0xf1, 0x8b, 0x4d, - 0xc0, 0xd2, 0xa3, 0x3a, 0xc4, 0x27, 0x60, 0xe9, 0x2e, 0x3d, 0x0b, 0x43, 0x9f, 0xd9, 0x38, 0x8e, - 0x31, 0xe7, 0x84, 0x2d, 0x7e, 0x91, 0x49, 0x0c, 0x2d, 0xc7, 0x7b, 0x31, 0x18, 0x52, 0xfe, 0x3e, - 0x4c, 0x67, 0xe9, 0x35, 0xeb, 0x2b, 0x10, 0xbc, 0x7d, 0x8a, 0xf7, 0x4b, 0x98, 0xaa, 0x34, 0x9b, - 0xf1, 0x70, 0xe5, 0xbd, 0xca, 0xe7, 0x09, 0xee, 0xd3, 0x14, 0xdb, 0xda, 0x81, 0x15, 0xcf, 0x8d, - 0xec, 0xa9, 0xa5, 0x6f, 0xdc, 0x30, 0x72, 0xbd, 0x3d, 0xcd, 0xf1, 0x6a, 0x9f, 0x5d, 0xa7, 0x4f, - 0x33, 0x86, 0x00, 0xdb, 0x71, 0x98, 0xb2, 0x39, 0x3c, 0x43, 0xf8, 0xb4, 0x26, 0x36, 0x9e, 0xba, - 0x66, 0x4d, 0xb9, 0x31, 0xa2, 0xbf, 0xd2, 0x78, 0x6c, 0x7d, 0x0f, 0xce, 0xf2, 0x69, 0xbf, 0x5b, - 0xe5, 0x45, 0xb5, 0x75, 0x3f, 0xb1, 0xf5, 0xae, 0xf4, 0xe4, 0x74, 0xad, 0x99, 0x3d, 0x6e, 0xd4, - 0x05, 0x8b, 0xfc, 0x6f, 0x05, 0x98, 0x4b, 0xb0, 0xd6, 0x9e, 0x79, 0x0d, 0xb9, 0xe6, 0xbc, 0x9a, - 0x0c, 0xdd, 0xc7, 0xbd, 0x12, 0x77, 0x90, 0xba, 0x4d, 0x15, 0xbd, 0x4f, 0x6e, 0x01, 0x70, 0x66, - 0x6d, 0x8b, 0x83, 0xc7, 0x03, 0x22, 0xca, 0x09, 0x37, 0x39, 0x1a, 0x09, 0xe9, 0x40, 0x96, 0xde, - 0xc5, 0x37, 0xd2, 0xcb, 0x7f, 0x8e, 0x79, 0x56, 0xa8, 0x60, 0xaf, 0xe7, 0x38, 0xd2, 0xb3, 0xe4, - 0x5b, 0x7f, 0xb7, 0x1f, 0x66, 0xf5, 0x0e, 0x7c, 0x9e, 0xb6, 0x6e, 0xc2, 0xd8, 0x82, 0xef, 0x45, - 0xf4, 0x9b, 0x48, 0xcb, 0x73, 0x41, 0x54, 0x34, 0x82, 0xc2, 0x88, 0xed, 0x35, 0x07, 0xd4, 0xd9, - 0x5e, 0xcf, 0x88, 0xd6, 0x8c, 0x09, 0xc9, 0x02, 0x4c, 0xac, 0xd3, 0xa7, 0x29, 0x05, 0x62, 0xc4, - 0xa8, 0x47, 0x9f, 0xd6, 0x35, 0x25, 0xea, 0x61, 0x7c, 0x06, 0x0f, 0xd9, 0x85, 0x49, 0x39, 0xb8, - 0x0c, 0x65, 0xce, 0xe9, 0x2b, 0xaf, 0x39, 0x9c, 0x79, 0x1e, 0x08, 0x56, 0x42, 0x8e, 0x0e, 0x13, - 0x12, 0x59, 0xd3, 0x79, 0x89, 0x3c, 0xb5, 0x81, 0xb9, 0xb4, 0x6b, 0x18, 0x23, 0x1e, 0x37, 0x99, - 0xd2, 0x40, 0x17, 0x61, 0x6d, 0x42, 0x29, 0xdd, 0x1f, 0xa2, 0xb4, 0xb7, 0x60, 0x88, 0x43, 0xc5, - 0x56, 0x49, 0xa6, 0x30, 0x52, 0xd4, 0xdc, 0x97, 0xd1, 0x14, 0xab, 0x12, 0x87, 0x59, 0xcb, 0xe8, - 0x5f, 0x52, 0x34, 0x6a, 0xb3, 0x7a, 0x3b, 0xd9, 0xbd, 0x18, 0xea, 0x2c, 0xbb, 0x57, 0x8f, 0xc5, - 0x91, 0x57, 0x52, 0x16, 0xd0, 0x45, 0xa7, 0x4b, 0x12, 0x15, 0xbb, 0x01, 0xc3, 0x02, 0x94, 0x48, - 0xae, 0x14, 0x7f, 0x7e, 0x92, 0xc0, 0x7a, 0x1f, 0xce, 0xa1, 0xbf, 0xd0, 0xf5, 0xf6, 0x5a, 0x74, - 0x3b, 0x34, 0x2e, 0x95, 0xf4, 0xfa, 0xac, 0x3f, 0x84, 0xb9, 0x2c, 0xde, 0x9e, 0x5f, 0x36, 0x4f, - 0x77, 0xf2, 0xa7, 0x7d, 0x30, 0xbd, 0x12, 0xea, 0x1b, 0x2e, 0x95, 0xf2, 0x24, 0x23, 0x0d, 0x07, - 0xea, 0x64, 0xf9, 0x54, 0x56, 0x9a, 0x8d, 0xb7, 0xb4, 0xeb, 0xae, 0x7d, 0xdd, 0xf2, 0x6b, 0xb0, - 0x65, 0x4b, 0x5d, 0x78, 0x7d, 0x15, 0x06, 0xd6, 0xd9, 0x54, 0xdd, 0x2f, 0xfa, 0x8e, 0x73, 0x30, - 0x10, 0x5e, 0x37, 0x65, 0x4b, 0x24, 0xfb, 0x41, 0xee, 0xa6, 0x2e, 0xb5, 0x0e, 0xf4, 0xce, 0x1f, - 0xb1, 0x7c, 0x2a, 0x75, 0xbf, 0xf5, 0x1d, 0x18, 0xab, 0x34, 0x0f, 0x78, 0x48, 0xa6, 0xef, 0x25, - 0x3e, 0x4b, 0x0d, 0xb3, 0x7c, 0xca, 0xd6, 0x09, 0xc9, 0x35, 0x7e, 0xab, 0x61, 0x28, 0x27, 0xa7, - 0x06, 0xdb, 0xac, 0x55, 0xda, 0xed, 0xea, 0x08, 0x0c, 0xf1, 0x8b, 0x96, 0xd6, 0x97, 0x30, 0x27, - 0x02, 0x79, 0xb8, 0x77, 0x14, 0xc3, 0x7d, 0xc2, 0x38, 0x56, 0xab, 0x5b, 0xf0, 0xcd, 0x25, 0x00, - 0xb4, 0x85, 0x56, 0xbc, 0x26, 0xfd, 0x46, 0x44, 0x12, 0x6a, 0x10, 0xeb, 0x6d, 0x18, 0x55, 0x1a, - 0xc2, 0x0d, 0xbf, 0xb6, 0xd8, 0xa1, 0xb6, 0xa6, 0x8d, 0x5b, 0xbc, 0xf2, 0xea, 0xee, 0x39, 0xa3, - 0xed, 0x22, 0x4b, 0x0e, 0xb7, 0x10, 0x5c, 0x98, 0x49, 0x0c, 0x82, 0x38, 0x09, 0x83, 0xda, 0xa3, - 0xf3, 0x50, 0x47, 0xf5, 0x3b, 0xb9, 0x85, 0xef, 0x3b, 0xd6, 0x16, 0xde, 0xfa, 0x17, 0x7d, 0x68, - 0x5c, 0xa6, 0xf4, 0x91, 0xf0, 0xd3, 0xe9, 0xbe, 0xc2, 0x2a, 0x8c, 0x62, 0xeb, 0x17, 0xe5, 0x85, - 0xc1, 0xee, 0x71, 0x28, 0x23, 0x3f, 0x39, 0x2c, 0x9f, 0xc2, 0xe0, 0x93, 0x98, 0x8d, 0x7c, 0x0c, - 0xc3, 0x4b, 0x5e, 0x13, 0x25, 0xf4, 0x9f, 0x40, 0x82, 0x64, 0x62, 0x7d, 0x82, 0x55, 0xde, 0x62, - 0x9f, 0x30, 0x77, 0xef, 0xd8, 0x1a, 0x24, 0xb6, 0x72, 0x07, 0xf3, 0xac, 0xdc, 0xa1, 0x84, 0x95, - 0x6b, 0xc1, 0xe0, 0x46, 0xd0, 0x14, 0xb9, 0x6d, 0x26, 0xe7, 0xc7, 0x85, 0xe2, 0x10, 0x66, 0x73, - 0x94, 0xf5, 0x3f, 0x0a, 0x30, 0x7b, 0x8f, 0x46, 0x99, 0x63, 0xc8, 0xd0, 0x4a, 0xe1, 0x85, 0xb5, - 0xd2, 0xf7, 0x3c, 0x5a, 0x51, 0xad, 0xee, 0xcf, 0x6b, 0xf5, 0x40, 0x5e, 0xab, 0x07, 0xf3, 0x5b, - 0x7d, 0x0f, 0x86, 0x78, 0x53, 0x99, 0x25, 0xbf, 0x12, 0xd1, 0x83, 0xd8, 0x92, 0xd7, 0xa3, 0xe8, - 0x6c, 0x8e, 0x63, 0x1b, 0xc9, 0x55, 0x27, 0xd4, 0x2d, 0x79, 0xf1, 0xd3, 0xfa, 0x01, 0x5e, 0x35, - 0x5e, 0xf5, 0x1b, 0x8f, 0x35, 0x8f, 0xf0, 0x30, 0xff, 0x42, 0x93, 0x27, 0x08, 0x8c, 0x8a, 0x63, - 0x6c, 0x49, 0x41, 0x2e, 0xc3, 0xd8, 0x8a, 0x77, 0xd7, 0x0f, 0x1a, 0x74, 0xc3, 0x6b, 0x71, 0xe9, - 0x23, 0xb6, 0x0e, 0x12, 0x9e, 0x12, 0x51, 0x42, 0xec, 0x7e, 0x40, 0x40, 0xc2, 0xfd, 0xc0, 0x60, - 0x3b, 0xf3, 0x36, 0xc7, 0x09, 0x47, 0x0c, 0xfb, 0xbb, 0x9b, 0xe5, 0xae, 0x4c, 0xfc, 0x5e, 0x84, - 0xbb, 0x70, 0xce, 0xa6, 0xed, 0x96, 0xc3, 0xf6, 0x74, 0x07, 0x3e, 0xa7, 0x57, 0x6d, 0xbe, 0x9c, - 0x71, 0x4d, 0xd0, 0x8c, 0xa9, 0x50, 0x55, 0xee, 0xeb, 0x52, 0xe5, 0x03, 0xb8, 0x72, 0x8f, 0x46, - 0xe6, 0x84, 0x1a, 0xfb, 0x9b, 0x45, 0xe3, 0x97, 0x61, 0x24, 0x34, 0x7d, 0xe5, 0xf2, 0xda, 0x5b, - 0x26, 0xe3, 0xce, 0x9b, 0xf2, 0x34, 0x49, 0xc8, 0x51, 0x7f, 0x59, 0x9f, 0x40, 0x39, 0xaf, 0xb8, - 0xe3, 0x85, 0xbc, 0xba, 0x70, 0x39, 0x5f, 0x80, 0xa8, 0xee, 0x12, 0x48, 0xbf, 0xba, 0xf8, 0x84, - 0x7a, 0xd5, 0xd6, 0x74, 0xc5, 0x8b, 0x3f, 0xac, 0xaa, 0x0c, 0xfe, 0x7b, 0x81, 0xea, 0xd6, 0xf1, - 0xc8, 0xda, 0x14, 0x10, 0xeb, 0xb5, 0x02, 0x23, 0x12, 0x26, 0xf4, 0x3a, 0x9b, 0x59, 0x53, 0xa9, - 0xd0, 0xa6, 0x14, 0xa0, 0xd8, 0xac, 0x1f, 0xc8, 0xe3, 0x1b, 0x93, 0xe3, 0x78, 0xf7, 0x66, 0x8f, - 0x73, 0x5e, 0x63, 0xf9, 0x70, 0xce, 0x94, 0xad, 0xbb, 0xe5, 0x8b, 0x9a, 0x5b, 0x9e, 0x7b, 0xe3, - 0x2f, 0x9b, 0x6e, 0x62, 0xe1, 0x69, 0xd0, 0x40, 0xe4, 0x92, 0xee, 0x7c, 0x1f, 0x4f, 0x5f, 0xc4, - 0xbd, 0x0d, 0x73, 0x59, 0x05, 0x6a, 0x76, 0xa0, 0xf2, 0xf0, 0x8a, 0xfd, 0xce, 0x22, 0x5c, 0x92, - 0xd9, 0xa5, 0x7c, 0x3f, 0x0a, 0xa3, 0xc0, 0x69, 0xd7, 0x1a, 0x81, 0xdb, 0x8e, 0xb9, 0x2c, 0x18, - 0xe2, 0x10, 0xa1, 0x09, 0x7e, 0x14, 0xc6, 0x69, 0x04, 0xc6, 0xfa, 0x95, 0x02, 0x58, 0x46, 0x9c, - 0x16, 0xf6, 0xf3, 0x66, 0xe0, 0x3f, 0x71, 0x9b, 0xda, 0xf1, 0xd3, 0xeb, 0x86, 0xeb, 0x93, 0xdf, - 0x49, 0x4c, 0x86, 0x88, 0x8b, 0x39, 0xf3, 0x76, 0xc2, 0x1d, 0xc9, 0x37, 0x9e, 0x18, 0xbb, 0x65, - 0x5e, 0x88, 0x50, 0x6e, 0xca, 0xff, 0x55, 0x80, 0xab, 0x5d, 0xeb, 0x20, 0xda, 0xb3, 0x0b, 0xc5, - 0x24, 0x4e, 0x8c, 0xa0, 0xb2, 0x16, 0xb7, 0x91, 0x96, 0xb0, 0x73, 0x87, 0xc7, 0xa1, 0xcb, 0xf8, - 0xa6, 0xb6, 0x92, 0x9c, 0x92, 0x77, 0xf2, 0xda, 0x63, 0xfe, 0x0a, 0x3f, 0x72, 0x5a, 0x0b, 0xe8, - 0x00, 0xe8, 0x8f, 0xef, 0x14, 0x44, 0x0c, 0x5a, 0x4f, 0xa6, 0xc9, 0xd0, 0x88, 0xad, 0x4f, 0xf1, - 0xbb, 0xce, 0xae, 0xf4, 0xf1, 0x3e, 0xb5, 0x05, 0xb8, 0x9a, 0x88, 0x1d, 0x78, 0x0e, 0x21, 0x11, - 0xcc, 0x30, 0xf5, 0xb3, 0xbd, 0xf7, 0xbd, 0xc0, 0xef, 0xb4, 0x7f, 0x3e, 0xbd, 0xfe, 0x87, 0x05, - 0x1e, 0xcc, 0xa9, 0x17, 0x2b, 0x3a, 0x7a, 0x01, 0x20, 0x86, 0x26, 0x82, 0xfa, 0x15, 0x62, 0xe7, - 0x0e, 0x37, 0xb9, 0xf1, 0x54, 0x61, 0x8f, 0x0b, 0xd0, 0xd8, 0x7e, 0xbe, 0x3d, 0xf9, 0x26, 0x06, - 0x0c, 0xa8, 0xd2, 0x8f, 0xa7, 0xf7, 0x77, 0xa4, 0xff, 0xe3, 0x84, 0x7c, 0xfb, 0x30, 0xcd, 0x66, - 0x80, 0x4a, 0x27, 0xda, 0xf7, 0x03, 0x37, 0x92, 0xd7, 0x53, 0xc8, 0xa6, 0xc8, 0x08, 0xc0, 0xb9, - 0x3e, 0xfc, 0xd9, 0x61, 0xf9, 0xdd, 0x93, 0xe4, 0xfd, 0x94, 0x32, 0xb7, 0x54, 0x16, 0x01, 0x6b, - 0x16, 0xfa, 0x17, 0xec, 0x55, 0x9c, 0xf0, 0xec, 0x55, 0x35, 0xe1, 0xd9, 0xab, 0xd6, 0x5f, 0xf4, - 0x41, 0x99, 0xe7, 0x2c, 0xc1, 0x38, 0x93, 0xd8, 0x6b, 0xa1, 0x05, 0xae, 0x1c, 0xd7, 0xc1, 0x90, - 0xc8, 0x49, 0xd2, 0x77, 0x9c, 0x9c, 0x24, 0xbf, 0x04, 0x39, 0x2e, 0xab, 0x63, 0x78, 0x01, 0x5e, - 0x3b, 0x3a, 0x2c, 0x5f, 0x8d, 0xbd, 0x00, 0x1c, 0x9b, 0xe5, 0x0e, 0xc8, 0x29, 0x22, 0xed, 0xbf, - 0x18, 0x78, 0x0e, 0xff, 0xc5, 0x6d, 0x18, 0x46, 0x63, 0x66, 0x65, 0x53, 0x44, 0x7e, 0xe2, 0xf0, - 0xc4, 0x0c, 0x45, 0x75, 0x57, 0x4f, 0x07, 0x28, 0xc9, 0xac, 0x7f, 0xd0, 0x07, 0x97, 0xf3, 0x75, - 0x2e, 0xea, 0xb6, 0x08, 0x10, 0x47, 0xb8, 0x74, 0x8b, 0xa8, 0xc1, 0x6f, 0xe7, 0x29, 0xdd, 0x55, - 0x11, 0x6d, 0x1a, 0x1f, 0xdb, 0xfb, 0xc8, 0x9b, 0xd6, 0x89, 0xe3, 0x14, 0xe3, 0x02, 0xb6, 0xc8, - 0x66, 0x2b, 0x40, 0x46, 0x36, 0x5b, 0x01, 0x23, 0xbb, 0x30, 0xbb, 0x19, 0xb8, 0x4f, 0x9c, 0x88, - 0x3e, 0xa0, 0xcf, 0xf8, 0x65, 0xa1, 0x25, 0x71, 0x43, 0x88, 0x5f, 0x9f, 0xbf, 0x7e, 0x74, 0x58, - 0x7e, 0xa5, 0xcd, 0x49, 0x30, 0x63, 0x19, 0xbf, 0xfb, 0x59, 0x4f, 0x5f, 0x1a, 0xca, 0x13, 0x64, - 0xfd, 0xbb, 0x02, 0x9c, 0xc7, 0x6d, 0xb9, 0x70, 0xbb, 0xca, 0xc2, 0x9f, 0x2b, 0xb0, 0x52, 0x6f, - 0xa0, 0x18, 0x8b, 0x18, 0x58, 0x69, 0xdc, 0x44, 0xb7, 0x0d, 0x32, 0xb2, 0x02, 0x63, 0xe2, 0x37, - 0x7e, 0x7f, 0xfd, 0x68, 0x10, 0xcc, 0x68, 0x13, 0x16, 0x0e, 0x75, 0xee, 0x2a, 0xc2, 0x81, 0x2d, - 0x84, 0xe1, 0x85, 0x4d, 0x5b, 0xe7, 0xb5, 0x7e, 0xda, 0x07, 0x17, 0x76, 0x68, 0xe0, 0x3e, 0x7a, - 0x96, 0xd3, 0x98, 0x0d, 0x98, 0x96, 0x20, 0x9e, 0xb7, 0xc4, 0xf8, 0xc4, 0x78, 0x3e, 0x4b, 0x59, - 0x55, 0x91, 0xf8, 0x44, 0x7e, 0x71, 0x99, 0x8c, 0x27, 0x08, 0x99, 0x7c, 0x0b, 0x46, 0x12, 0x99, - 0x83, 0xb0, 0xff, 0xe5, 0x17, 0x1a, 0x77, 0xd5, 0xf2, 0x29, 0x5b, 0x51, 0x92, 0x5f, 0xcb, 0x3f, - 0xaa, 0x12, 0xae, 0x8f, 0x5e, 0xfe, 0x4f, 0xfc, 0x60, 0xd9, 0xc7, 0xea, 0x68, 0xd8, 0x8c, 0x0f, - 0x76, 0xf9, 0x94, 0x9d, 0x57, 0x52, 0x75, 0x0c, 0x46, 0x2b, 0x78, 0x6e, 0xc7, 0x2c, 0xf7, 0xff, - 0xd9, 0x07, 0x97, 0xe4, 0xc5, 0x9f, 0x1c, 0x35, 0x7f, 0x0e, 0xb3, 0x12, 0x54, 0x69, 0xb3, 0x0d, - 0x03, 0x6d, 0x9a, 0x9a, 0xe6, 0x39, 0x65, 0xa5, 0xa6, 0x1d, 0x41, 0x13, 0x2b, 0x3b, 0x8f, 0xfd, - 0xe5, 0x78, 0x3f, 0x3f, 0xce, 0xca, 0xe3, 0x84, 0x5e, 0x48, 0x7d, 0xce, 0x34, 0x54, 0x63, 0xcc, - 0x9f, 0xcd, 0x94, 0xf7, 0x74, 0xe0, 0x45, 0xbd, 0xa7, 0xcb, 0xa7, 0x92, 0xfe, 0xd3, 0xea, 0x24, - 0x8c, 0xaf, 0xd3, 0xa7, 0xb1, 0xde, 0xff, 0x7a, 0x21, 0x91, 0xea, 0x81, 0xed, 0x30, 0x78, 0xce, - 0x87, 0x42, 0x9c, 0x0a, 0x08, 0x53, 0x3d, 0xe8, 0x3b, 0x0c, 0x4e, 0xba, 0x02, 0xc3, 0xfc, 0x30, - 0xbb, 0x79, 0x0c, 0x0b, 0x5f, 0xdd, 0xe0, 0xe1, 0xd7, 0x2a, 0x9b, 0xdc, 0xd8, 0x17, 0xfc, 0xd6, - 0x03, 0xb8, 0x22, 0x62, 0xbc, 0xcd, 0xce, 0xc7, 0x82, 0x4e, 0xb8, 0x7c, 0x59, 0x0e, 0x5c, 0xba, - 0x47, 0x93, 0x53, 0x8f, 0x71, 0xc3, 0xe9, 0x13, 0x38, 0x6d, 0xc0, 0x95, 0x44, 0xdc, 0x95, 0xaa, - 0x31, 0xa4, 0x44, 0x27, 0xa9, 0xad, 0xcb, 0x59, 0x45, 0xe8, 0x95, 0xb5, 0x28, 0x26, 0x87, 0x0d, - 0xe2, 0x23, 0xb6, 0xf0, 0x04, 0xb3, 0xde, 0x75, 0xed, 0xbb, 0xe6, 0x33, 0x1e, 0xcf, 0x1e, 0x28, - 0x57, 0x5e, 0x85, 0xb5, 0x26, 0x8c, 0xb3, 0x00, 0x6b, 0x12, 0xc6, 0x25, 0xaa, 0x45, 0xc3, 0xd0, - 0xfa, 0xcf, 0x83, 0x60, 0x09, 0xc5, 0x66, 0x9d, 0xd0, 0x4b, 0x7d, 0xec, 0xa6, 0x2a, 0x2b, 0x16, - 0xaa, 0xb3, 0x7a, 0x4e, 0xd2, 0x18, 0xcb, 0x47, 0x1e, 0xee, 0xf3, 0x1a, 0x31, 0xd4, 0x18, 0x79, - 0xa9, 0xd6, 0x7f, 0x95, 0x33, 0x4d, 0xf2, 0x8f, 0x0d, 0xaf, 0x6c, 0xe7, 0x4c, 0x93, 0x86, 0xdc, - 0xec, 0x29, 0xd3, 0x36, 0x8f, 0x44, 0xfa, 0x9f, 0xe7, 0x48, 0x84, 0x7d, 0x91, 0xfa, 0xa1, 0xc8, - 0xb6, 0xa9, 0x4b, 0xf1, 0x3d, 0xca, 0xd3, 0x7b, 0x1d, 0x25, 0x32, 0x2e, 0x68, 0x10, 0x43, 0xaa, - 0x21, 0x86, 0xb8, 0x50, 0xd4, 0x7c, 0x96, 0x0b, 0xfb, 0xb4, 0xf1, 0x58, 0xf8, 0x8a, 0xe5, 0x81, - 0x6e, 0x96, 0xcf, 0x9c, 0xe7, 0xa7, 0xe6, 0xdf, 0x39, 0x47, 0xd4, 0x1b, 0x8c, 0x55, 0xcf, 0x18, - 0x91, 0x14, 0x4b, 0x7e, 0x04, 0x53, 0xaa, 0xab, 0x13, 0x21, 0x5a, 0x63, 0xf3, 0xaf, 0xc4, 0xa9, - 0x4c, 0x0f, 0x1e, 0x39, 0x37, 0x9f, 0xdc, 0xb9, 0x99, 0x41, 0xcb, 0x13, 0x11, 0x34, 0x24, 0x42, - 0x8b, 0xcf, 0xd2, 0x0f, 0xba, 0x32, 0x18, 0xc9, 0x17, 0x30, 0x5d, 0xab, 0x6d, 0xf0, 0xcb, 0x1c, - 0xb6, 0x3c, 0xe0, 0xb7, 0x57, 0x45, 0xc0, 0x16, 0x76, 0x77, 0x18, 0xfa, 0x75, 0x71, 0x09, 0x44, - 0x0f, 0x0b, 0xd0, 0x53, 0x31, 0x64, 0x89, 0xd0, 0x4f, 0xca, 0xff, 0xbe, 0xba, 0xab, 0xc0, 0xb6, - 0x22, 0x6e, 0x8b, 0x8a, 0x4b, 0x47, 0x72, 0x60, 0xe7, 0x9c, 0xf2, 0x15, 0xbe, 0xe5, 0x53, 0xbe, - 0xdf, 0xeb, 0x93, 0x37, 0x34, 0xd2, 0x07, 0xad, 0x27, 0x3e, 0xec, 0xcb, 0x6c, 0xc1, 0xb1, 0xd6, - 0xe9, 0xcc, 0xca, 0x91, 0xaa, 0x3c, 0x2a, 0x55, 0xc9, 0xca, 0x26, 0xd5, 0xb1, 0x43, 0x8c, 0x30, - 0x4e, 0x4f, 0x71, 0x57, 0xa4, 0x71, 0x25, 0xcf, 0xe1, 0xfa, 0x5f, 0xfc, 0x1c, 0xee, 0xc7, 0x30, - 0x23, 0xaf, 0x46, 0x2d, 0x50, 0x2f, 0xa2, 0x81, 0x3c, 0xb1, 0x9f, 0x8c, 0x93, 0xbe, 0x61, 0x7a, - 0xbf, 0x22, 0xf4, 0x57, 0xec, 0x75, 0xe1, 0xd1, 0x61, 0x7f, 0x92, 0xcb, 0x66, 0x40, 0x1c, 0xbf, - 0xf3, 0x66, 0x84, 0xbf, 0x5d, 0x66, 0xd5, 0xe5, 0x7e, 0x16, 0x57, 0xa6, 0xea, 0xb3, 0x75, 0x90, - 0xb5, 0x00, 0xe7, 0xcd, 0xe2, 0x37, 0x69, 0x70, 0xe0, 0xe2, 0xde, 0xbb, 0x46, 0x23, 0x59, 0x68, - 0x21, 0x2e, 0x94, 0xe8, 0x01, 0xd5, 0xc2, 0x0c, 0xfc, 0x3f, 0x7d, 0x50, 0xce, 0x6c, 0x44, 0x25, - 0x0c, 0xdd, 0x3d, 0x0f, 0x33, 0x68, 0x5c, 0x80, 0x81, 0x07, 0xae, 0xd7, 0xd4, 0x0d, 0xc9, 0xc7, - 0xae, 0xd7, 0xb4, 0x11, 0xca, 0x6c, 0x90, 0x5a, 0x67, 0x17, 0x09, 0x34, 0x13, 0x39, 0xec, 0xec, - 0xd6, 0x19, 0x91, 0x6e, 0x83, 0x08, 0x32, 0x72, 0x0d, 0x86, 0x65, 0xb6, 0xb5, 0xfe, 0xd8, 0x7b, - 0x26, 0xd3, 0xac, 0x49, 0x1c, 0xf9, 0x08, 0x46, 0xd6, 0x68, 0xe4, 0x34, 0x9d, 0xc8, 0x11, 0x63, - 0x47, 0x3e, 0x84, 0x21, 0xc1, 0xd5, 0xa2, 0x58, 0xa1, 0x47, 0x0e, 0x04, 0xc4, 0x56, 0x2c, 0xa8, - 0x40, 0x37, 0x6c, 0xb7, 0x9c, 0x67, 0x2a, 0x98, 0x94, 0x29, 0x30, 0x06, 0x91, 0x77, 0xcc, 0x90, - 0x8b, 0xf8, 0xf8, 0x2c, 0x53, 0x21, 0x71, 0x40, 0xc6, 0x32, 0x86, 0x81, 0xc4, 0xaa, 0x16, 0xd9, - 0x04, 0xad, 0x4c, 0x6e, 0x83, 0xd2, 0x36, 0x19, 0xad, 0xdf, 0x1a, 0x83, 0x33, 0x9b, 0xce, 0x9e, - 0xeb, 0xb1, 0x1d, 0x85, 0x4d, 0x43, 0xbf, 0x13, 0x34, 0x28, 0xa9, 0xc0, 0xa4, 0x19, 0xc0, 0xdd, - 0x23, 0x3c, 0x9d, 0x6d, 0x9a, 0x4c, 0x18, 0x99, 0x87, 0x51, 0x75, 0x69, 0x5c, 0xec, 0x74, 0x32, - 0x2e, 0x93, 0x2f, 0x9f, 0xb2, 0x63, 0x32, 0xf2, 0x9e, 0x71, 0xf8, 0x78, 0x5a, 0xe5, 0x3f, 0x40, - 0xda, 0x79, 0x1e, 0x61, 0xeb, 0xf9, 0x4d, 0x73, 0xb7, 0xc6, 0x4f, 0xd8, 0x7e, 0x90, 0x3a, 0x8f, - 0x1c, 0x34, 0x6a, 0x9c, 0x72, 0xca, 0xe2, 0x46, 0x35, 0x37, 0x7b, 0x7d, 0xc6, 0x49, 0xe5, 0x97, - 0x30, 0xf6, 0xa0, 0xb3, 0x4b, 0xe5, 0xc9, 0xeb, 0x90, 0xd8, 0xbc, 0x25, 0xaf, 0x25, 0x08, 0xfc, - 0xce, 0x9b, 0xfc, 0x2b, 0x7e, 0xdc, 0xd9, 0xa5, 0xe9, 0x67, 0x11, 0xd8, 0xaa, 0xa9, 0x09, 0x23, - 0xfb, 0x50, 0x4c, 0xde, 0x20, 0x10, 0x5d, 0xda, 0xe5, 0xde, 0x03, 0x26, 0xfa, 0xd1, 0x1e, 0x5f, - 0xe0, 0x71, 0xcd, 0x46, 0x21, 0x29, 0xa9, 0xe4, 0xc7, 0x30, 0x93, 0xe9, 0x12, 0x57, 0x77, 0x20, - 0xbb, 0x7b, 0xdb, 0x71, 0x09, 0x4a, 0x68, 0x4d, 0x5e, 0xb8, 0x34, 0x4a, 0xce, 0x2e, 0x85, 0x34, - 0xe1, 0x74, 0x22, 0x32, 0x5e, 0xbc, 0x30, 0x93, 0x1f, 0x6b, 0x8f, 0xbb, 0x26, 0x99, 0xa4, 0x39, - 0xb3, 0xac, 0xa4, 0x48, 0xb2, 0x0a, 0xa3, 0xca, 0x17, 0x25, 0x72, 0xf3, 0x65, 0xf9, 0xdd, 0x4a, - 0x47, 0x87, 0xe5, 0xe9, 0xd8, 0xef, 0x66, 0xc8, 0x8c, 0x05, 0x90, 0x5f, 0x86, 0x2b, 0x6a, 0x88, - 0x6e, 0x04, 0xd9, 0x1e, 0x4a, 0xf1, 0xb8, 0xc3, 0x8d, 0xe4, 0x08, 0xcf, 0xa3, 0xdf, 0xb9, 0x53, - 0xed, 0x2b, 0x15, 0x96, 0x4f, 0xd9, 0xbd, 0x45, 0x93, 0x5f, 0x2d, 0xc0, 0xd9, 0x9c, 0x52, 0xc7, - 0xb1, 0xd4, 0x9e, 0x6e, 0x63, 0xb4, 0x3c, 0xf1, 0xde, 0x9f, 0xdb, 0x8c, 0xef, 0xc7, 0x4a, 0xff, - 0xb1, 0xd1, 0xee, 0x9c, 0x92, 0xc8, 0x6d, 0x80, 0x3d, 0x37, 0x12, 0x63, 0x0c, 0xd3, 0xd4, 0xa5, - 0x3f, 0x50, 0xa6, 0xb6, 0x3d, 0x37, 0x12, 0x23, 0xed, 0x77, 0x0b, 0x3d, 0xe7, 0x75, 0xcc, 0x5e, - 0x37, 0x36, 0xff, 0x6a, 0xb7, 0x49, 0x2f, 0xa6, 0xae, 0xde, 0x3e, 0x3a, 0x2c, 0xbf, 0xa1, 0x52, - 0xa0, 0x35, 0x90, 0x4a, 0xde, 0xf2, 0xad, 0x3b, 0x8a, 0xce, 0x68, 0x4f, 0xcf, 0xa5, 0xe5, 0x0d, - 0x18, 0x42, 0xcf, 0x54, 0x58, 0x9a, 0x40, 0xdb, 0x0d, 0x13, 0x77, 0xa1, 0xff, 0x4a, 0xdf, 0xad, - 0x09, 0x1a, 0xb2, 0xcc, 0x6c, 0x20, 0xdc, 0x2d, 0x4a, 0x9b, 0x45, 0xa4, 0xf9, 0x13, 0x76, 0x34, - 0x47, 0xc9, 0xfc, 0x3b, 0xc6, 0xf3, 0x24, 0x26, 0x5b, 0x15, 0x60, 0x24, 0x10, 0xd3, 0xed, 0xfd, - 0x81, 0x91, 0x81, 0xe2, 0x20, 0x9f, 0x11, 0xe4, 0x5d, 0x92, 0x5f, 0x1f, 0xe1, 0x17, 0xcf, 0xb7, - 0x3d, 0xf7, 0x91, 0x1b, 0xcf, 0xcc, 0xba, 0x4f, 0x3b, 0x7e, 0x27, 0x4c, 0x58, 0x9c, 0x39, 0x2f, - 0x82, 0x29, 0xf7, 0x77, 0x5f, 0x4f, 0xf7, 0xf7, 0x9b, 0xda, 0x41, 0xb1, 0x96, 0xce, 0x97, 0x5b, - 0x16, 0xa6, 0xbb, 0x39, 0x3e, 0x41, 0xfe, 0x1a, 0x86, 0x30, 0x03, 0x2f, 0x3f, 0x85, 0x1f, 0x9b, - 0xbf, 0x29, 0xba, 0xb3, 0x4b, 0xf5, 0x79, 0xca, 0x5e, 0x91, 0x4c, 0x82, 0x6b, 0x1c, 0x01, 0x86, - 0xc6, 0x11, 0x42, 0xb6, 0x60, 0x6a, 0x93, 0x6d, 0x74, 0xf9, 0x8d, 0x86, 0x76, 0x20, 0x5c, 0x82, - 0xdc, 0xd9, 0x88, 0x1b, 0xed, 0xb6, 0x44, 0xd7, 0xa9, 0xc2, 0xeb, 0x7b, 0xcd, 0x0c, 0x76, 0xb2, - 0x04, 0x93, 0x35, 0xea, 0x04, 0x8d, 0xfd, 0x07, 0xf4, 0x19, 0x33, 0x32, 0x8c, 0xa7, 0x71, 0x42, - 0xc4, 0xb0, 0xf6, 0x22, 0x4a, 0x8f, 0xac, 0x32, 0x99, 0xc8, 0xa7, 0x30, 0x54, 0xf3, 0x83, 0xa8, - 0xfa, 0x4c, 0xcc, 0xd6, 0xf2, 0x9c, 0x96, 0x03, 0xab, 0xe7, 0xe4, 0xf3, 0x40, 0xa1, 0x1f, 0x44, - 0xf5, 0x5d, 0x23, 0x13, 0x1c, 0x27, 0x21, 0xcf, 0x60, 0xda, 0x9c, 0x29, 0x45, 0xa0, 0xfd, 0x88, - 0x30, 0x6e, 0xb2, 0xa6, 0x63, 0x4e, 0x52, 0xbd, 0x2e, 0xa4, 0x5f, 0x4e, 0xce, 0xc7, 0x8f, 0x10, - 0xaf, 0x5b, 0x04, 0x59, 0xfc, 0x64, 0x0d, 0xdf, 0x55, 0xe2, 0x2d, 0xaa, 0x84, 0x3c, 0x40, 0x7f, - 0x34, 0xce, 0x35, 0xd8, 0xc1, 0xd9, 0x16, 0x35, 0xe1, 0x84, 0xc9, 0xc7, 0xb8, 0xec, 0x14, 0x2b, - 0xd9, 0x84, 0x33, 0xdb, 0x21, 0xdd, 0x0c, 0xe8, 0x13, 0x97, 0x3e, 0x95, 0xf2, 0x20, 0x4e, 0xcc, - 0xc6, 0xe4, 0xb5, 0x39, 0x36, 0x4b, 0x60, 0x9a, 0x99, 0xbc, 0x07, 0xb0, 0xe9, 0x7a, 0x1e, 0x6d, - 0xe2, 0x61, 0xff, 0x18, 0x8a, 0xc2, 0x83, 0x8c, 0x36, 0x42, 0xeb, 0xbe, 0xd7, 0xd2, 0x55, 0xaa, - 0x11, 0x93, 0x2a, 0x4c, 0xac, 0x78, 0x8d, 0x56, 0x47, 0x04, 0xe5, 0x84, 0x38, 0x53, 0x8a, 0x84, - 0x91, 0x2e, 0x47, 0xd4, 0x53, 0x1f, 0xb9, 0xc9, 0x42, 0x1e, 0x00, 0x11, 0x00, 0x31, 0x6a, 0x9d, - 0xdd, 0x16, 0x15, 0x9f, 0x3b, 0x3a, 0x28, 0xa5, 0x20, 0x1c, 0xee, 0x46, 0x1e, 0xc6, 0x14, 0xdb, - 0xdc, 0x7b, 0x30, 0xa6, 0x8d, 0xf9, 0x8c, 0xec, 0x28, 0xd3, 0x7a, 0x76, 0x94, 0x51, 0x3d, 0x0b, - 0xca, 0x3f, 0x2d, 0xc0, 0x85, 0xec, 0x6f, 0x49, 0xd8, 0x26, 0x1b, 0x30, 0xaa, 0x80, 0xea, 0x3e, - 0x9c, 0x34, 0xb8, 0x13, 0x5b, 0x3b, 0xfe, 0x41, 0xcb, 0x99, 0x47, 0x6f, 0x7d, 0x2c, 0xe3, 0x39, - 0x4e, 0xc1, 0xfe, 0xe6, 0x08, 0x4c, 0xe3, 0xbd, 0x8f, 0xe4, 0x3c, 0xf5, 0x09, 0x66, 0x39, 0x42, - 0x98, 0x76, 0xa8, 0x23, 0xfc, 0xbb, 0x1c, 0x9e, 0xcc, 0xf7, 0x67, 0x30, 0x90, 0xb7, 0xf5, 0x48, - 0xa4, 0x3e, 0xed, 0x1d, 0x27, 0x09, 0xd4, 0x9b, 0x10, 0x87, 0x28, 0xbd, 0x6e, 0x04, 0xc2, 0x1c, - 0x7b, 0xd2, 0x1b, 0x38, 0xee, 0xa4, 0xb7, 0xad, 0x26, 0x3d, 0x9e, 0x3d, 0xe7, 0x35, 0x6d, 0xd2, - 0x7b, 0xf9, 0xb3, 0xdd, 0xd0, 0xcb, 0x9e, 0xed, 0x86, 0x5f, 0x6c, 0xb6, 0x1b, 0x79, 0xce, 0xd9, - 0xee, 0x2e, 0x4c, 0xae, 0x53, 0xda, 0xd4, 0x8e, 0x27, 0x47, 0xe3, 0xd5, 0xd3, 0xa3, 0xe8, 0x78, - 0xce, 0x3a, 0xa3, 0x4c, 0x70, 0xe5, 0xce, 0x9a, 0xf0, 0x57, 0x33, 0x6b, 0x8e, 0xbd, 0xe4, 0x59, - 0x73, 0xfc, 0x45, 0x66, 0xcd, 0xd4, 0xd4, 0x37, 0x71, 0xe2, 0xa9, 0xef, 0x45, 0x66, 0xab, 0x8f, - 0x31, 0x90, 0xb7, 0x56, 0x5b, 0x16, 0x31, 0x5b, 0x5a, 0x90, 0xd4, 0xb2, 0x1f, 0xca, 0x7b, 0x0e, - 0xf8, 0x37, 0x83, 0xe1, 0x6b, 0x19, 0xc2, 0x1d, 0xc0, 0xfe, 0xb6, 0xaa, 0x18, 0xbe, 0xab, 0xf3, - 0xab, 0xfb, 0x40, 0xc3, 0xe2, 0x32, 0xb1, 0x98, 0xe3, 0x92, 0xdb, 0x4f, 0x5b, 0xe2, 0xad, 0x3f, - 0x2d, 0xf0, 0x50, 0x80, 0xff, 0x17, 0xa7, 0xca, 0x17, 0x39, 0x9e, 0xff, 0xb5, 0x38, 0xc9, 0x88, - 0x48, 0x88, 0x12, 0x38, 0x8d, 0xc7, 0x71, 0x7c, 0xc4, 0xf7, 0xd9, 0x77, 0xae, 0x23, 0xc4, 0x46, - 0x7d, 0x56, 0x69, 0x4a, 0x47, 0xee, 0xdc, 0x91, 0x13, 0x80, 0xc8, 0xb5, 0xc2, 0xc1, 0xe6, 0x04, - 0xa0, 0x33, 0x60, 0x84, 0xea, 0x69, 0xcb, 0xe6, 0x39, 0x32, 0x32, 0x6b, 0xf0, 0x4e, 0x3a, 0xcb, - 0x03, 0x5a, 0x59, 0x71, 0x96, 0x07, 0x5d, 0x8d, 0x71, 0xbe, 0x87, 0x6d, 0x38, 0x6f, 0xd3, 0x03, - 0xff, 0x09, 0x7d, 0xb9, 0x62, 0xbf, 0x82, 0x73, 0xa6, 0x40, 0x7e, 0x1f, 0x90, 0x3f, 0x5e, 0xf1, - 0x71, 0xf6, 0x93, 0x17, 0x82, 0x81, 0x3f, 0x79, 0xc1, 0x33, 0xe7, 0xb3, 0x3f, 0xf5, 0x75, 0x03, - 0x71, 0x96, 0x0f, 0x17, 0x4c, 0xe1, 0x95, 0x66, 0x13, 0x5f, 0xcd, 0x6d, 0xb8, 0x6d, 0xc7, 0x8b, - 0xc8, 0x06, 0x8c, 0x69, 0x3f, 0x13, 0x3e, 0x10, 0x0d, 0x23, 0xf6, 0x34, 0x31, 0xc0, 0xc8, 0x3c, - 0x1c, 0x83, 0x2d, 0x0a, 0xe5, 0xa4, 0x7a, 0x98, 0xca, 0xf4, 0x32, 0xab, 0x30, 0xa1, 0xfd, 0x54, - 0x07, 0x05, 0xf8, 0xf1, 0x6b, 0x25, 0x98, 0x0a, 0x33, 0x59, 0xac, 0x06, 0xcc, 0x65, 0x29, 0x8d, - 0xa7, 0xa8, 0x27, 0x4b, 0x71, 0x06, 0xba, 0xde, 0x31, 0xae, 0xa7, 0xf3, 0xb2, 0xcf, 0x59, 0x7f, - 0x6f, 0x00, 0xce, 0x8b, 0xce, 0x78, 0x99, 0x3d, 0x4e, 0x7e, 0x00, 0x63, 0x5a, 0x1f, 0x0b, 0xa5, - 0x5f, 0x96, 0xb7, 0xf9, 0xf2, 0xc6, 0x02, 0xf7, 0xd5, 0x74, 0x10, 0x50, 0x4f, 0x74, 0xf7, 0xf2, - 0x29, 0x5b, 0x17, 0x49, 0x5a, 0x30, 0x69, 0x76, 0xb4, 0x70, 0x57, 0x5d, 0xcd, 0x2c, 0xc4, 0x24, - 0x95, 0xf9, 0xeb, 0x9b, 0xf5, 0xcc, 0xee, 0x5e, 0x3e, 0x65, 0x27, 0x64, 0x93, 0x6f, 0xe0, 0x4c, - 0xaa, 0x97, 0x85, 0x2f, 0xf2, 0xd5, 0xcc, 0x02, 0x53, 0xd4, 0xfc, 0x10, 0x24, 0x40, 0x70, 0x6e, - 0xb1, 0xe9, 0x42, 0x48, 0x13, 0xc6, 0xf5, 0x8e, 0x17, 0xfe, 0xb4, 0x2b, 0x5d, 0x54, 0xc9, 0x09, - 0xf9, 0xe6, 0x4e, 0xe8, 0x12, 0xfb, 0xfe, 0x99, 0x79, 0xb0, 0x63, 0x10, 0x8f, 0xc0, 0x10, 0xff, - 0x6d, 0xfd, 0x5e, 0x01, 0xce, 0x6f, 0x06, 0x34, 0xa4, 0x5e, 0x83, 0x1a, 0xf7, 0x22, 0x5e, 0x70, - 0x44, 0xe4, 0x9d, 0xa9, 0xf4, 0xbd, 0xf0, 0x99, 0x8a, 0xf5, 0x6f, 0x0b, 0x50, 0xca, 0xaa, 0x72, - 0x8d, 0x7a, 0x4d, 0xb2, 0x09, 0xc5, 0x64, 0x1b, 0xc4, 0x17, 0x63, 0xa9, 0xf4, 0xe3, 0xb9, 0xad, - 0x5d, 0x3e, 0x65, 0xa7, 0xb8, 0xc9, 0x3a, 0x9c, 0xd1, 0x60, 0xe2, 0x4c, 0xa3, 0xef, 0x38, 0x67, - 0x1a, 0xac, 0x87, 0x53, 0xac, 0xfa, 0x91, 0xd0, 0x32, 0xae, 0xba, 0x8b, 0xfe, 0x81, 0xe3, 0x7a, - 0x6c, 0x13, 0xad, 0x25, 0xb8, 0x83, 0x18, 0x2a, 0xd4, 0xce, 0x0f, 0x39, 0x10, 0x2a, 0xaf, 0x88, - 0x29, 0x12, 0xeb, 0x43, 0x5c, 0x1d, 0x84, 0x63, 0x93, 0x5f, 0xca, 0x57, 0xc2, 0x2e, 0xc3, 0xe0, - 0xd6, 0x6a, 0x6d, 0xa1, 0x22, 0xae, 0xf8, 0xf3, 0xc4, 0x30, 0xad, 0xb0, 0xde, 0x70, 0x6c, 0x8e, - 0xb0, 0x3e, 0x00, 0x72, 0x8f, 0x46, 0xe2, 0xfd, 0x0b, 0xc5, 0x77, 0x0d, 0x86, 0x05, 0x48, 0x70, - 0xa2, 0xbb, 0x5e, 0xbc, 0xa6, 0x61, 0x4b, 0x9c, 0xb5, 0x29, 0x6d, 0x90, 0x16, 0x75, 0x42, 0x6d, - 0xd1, 0x7f, 0x17, 0x46, 0x02, 0x01, 0x13, 0x6b, 0xfe, 0xa4, 0x7a, 0xde, 0x08, 0xc1, 0xfc, 0x18, - 0x49, 0xd2, 0xd8, 0xea, 0x2f, 0x6b, 0x15, 0x93, 0x38, 0x6d, 0xac, 0x2c, 0x2e, 0x30, 0xad, 0x0a, - 0x65, 0xc9, 0xee, 0xb8, 0x85, 0xb7, 0x42, 0x22, 0xaa, 0x5f, 0xf0, 0x47, 0xd5, 0xe0, 0x04, 0x22, - 0x52, 0x97, 0x69, 0x24, 0xd6, 0x9b, 0x2a, 0x25, 0x54, 0x86, 0xb4, 0xbc, 0x67, 0x7a, 0xd6, 0x31, - 0xd9, 0xd5, 0x3d, 0x0c, 0x80, 0x7b, 0x19, 0x95, 0x70, 0x60, 0x8e, 0x6f, 0x21, 0x58, 0xab, 0xc4, - 0x23, 0xa5, 0xbe, 0x9a, 0x76, 0x17, 0x60, 0x54, 0xc1, 0xd4, 0x69, 0x36, 0xd7, 0x95, 0x41, 0xbf, - 0xf3, 0x26, 0xcf, 0x85, 0xd0, 0x50, 0x02, 0x62, 0x3e, 0x56, 0x04, 0xff, 0xa6, 0xbf, 0xe5, 0x22, - 0x42, 0x1a, 0x44, 0xdf, 0x6a, 0x11, 0x71, 0x36, 0xb4, 0x93, 0x14, 0x61, 0xd0, 0xef, 0xcc, 0x1f, - 0x47, 0x51, 0xdf, 0x72, 0x11, 0x4c, 0x51, 0xdf, 0x5e, 0x11, 0x54, 0xa6, 0x8d, 0xe3, 0x83, 0x34, - 0x55, 0xc8, 0x52, 0xba, 0x10, 0xe9, 0xed, 0x4f, 0x70, 0x74, 0xed, 0x0f, 0x0a, 0x17, 0xb8, 0xb2, - 0x7e, 0x0e, 0xc5, 0x30, 0x85, 0x7d, 0xbb, 0xc5, 0xfc, 0xc3, 0x02, 0x4f, 0x62, 0x57, 0xdb, 0xd0, - 0x9e, 0x07, 0xf6, 0x1e, 0xf9, 0x5a, 0xb0, 0x8d, 0xf6, 0xb5, 0x6b, 0x87, 0x9f, 0x18, 0x6c, 0xe3, - 0x74, 0xa2, 0x7d, 0x95, 0xe4, 0x1d, 0x4f, 0x42, 0x93, 0xd4, 0xe4, 0x3d, 0x98, 0xd0, 0x40, 0x6a, - 0x27, 0xc8, 0x9f, 0xe1, 0xd1, 0xd9, 0xdd, 0xa6, 0x6d, 0x52, 0x5a, 0x7f, 0x59, 0x80, 0xa9, 0x8c, - 0x87, 0xeb, 0xd1, 0x51, 0x82, 0x16, 0x96, 0x9a, 0xa8, 0xc4, 0xc3, 0x79, 0x98, 0x4f, 0xc7, 0x58, - 0x7f, 0x15, 0x21, 0x3e, 0x40, 0xa2, 0x3d, 0xb2, 0xdf, 0xa7, 0x3d, 0x07, 0x99, 0xfd, 0xb0, 0xbe, - 0x4e, 0x4e, 0x42, 0x80, 0xb8, 0x26, 0xc2, 0x25, 0x5d, 0x63, 0xdb, 0x65, 0xed, 0x85, 0xfe, 0x98, - 0xf7, 0x67, 0x87, 0xe5, 0x77, 0x4e, 0x12, 0x2a, 0x1c, 0x8b, 0xb6, 0xb5, 0x62, 0xac, 0x5f, 0xeb, - 0x83, 0xb3, 0x19, 0xed, 0xaf, 0xd1, 0xe8, 0xaf, 0x42, 0x05, 0x4f, 0x60, 0x2c, 0xae, 0x4c, 0x58, - 0xea, 0x47, 0xcf, 0xcd, 0x16, 0xbe, 0x97, 0x11, 0xeb, 0x20, 0x7c, 0x29, 0x4a, 0xd0, 0x0b, 0xb2, - 0xfe, 0xa8, 0x0f, 0xce, 0x6e, 0xb7, 0x43, 0xbc, 0x33, 0xb9, 0xe2, 0x3d, 0xa1, 0x5e, 0xe4, 0x07, - 0xcf, 0xf0, 0x9e, 0x17, 0x79, 0x1b, 0x06, 0x97, 0x69, 0xab, 0xe5, 0x8b, 0xf1, 0x7f, 0x51, 0xc6, - 0x3b, 0x25, 0xa9, 0x91, 0x68, 0xf9, 0x94, 0xcd, 0xa9, 0xc9, 0x7b, 0x30, 0xba, 0x4c, 0x9d, 0x20, - 0xda, 0xa5, 0x8e, 0x34, 0x87, 0xe4, 0xe3, 0x40, 0x1a, 0x8b, 0x20, 0x58, 0x3e, 0x65, 0xc7, 0xd4, - 0x64, 0x1e, 0x06, 0x36, 0x7d, 0x6f, 0x4f, 0xe5, 0x87, 0xc8, 0x29, 0x90, 0xd1, 0x2c, 0x9f, 0xb2, - 0x91, 0x96, 0xac, 0xc1, 0x44, 0x65, 0x8f, 0x7a, 0x51, 0xe2, 0x08, 0xff, 0x5a, 0x1e, 0xb3, 0x41, - 0xbc, 0x7c, 0xca, 0x36, 0xb9, 0xc9, 0x07, 0x30, 0x7c, 0xcf, 0xf7, 0x9b, 0xbb, 0xcf, 0x64, 0x96, - 0x93, 0x72, 0x9e, 0x20, 0x41, 0xb6, 0x7c, 0xca, 0x96, 0x1c, 0xd5, 0x41, 0xe8, 0x5f, 0x0b, 0xf7, - 0xac, 0xc3, 0x02, 0x94, 0x16, 0xfd, 0xa7, 0x5e, 0xa6, 0x56, 0xbf, 0x67, 0x6a, 0x55, 0x8a, 0xcf, - 0xa0, 0x4f, 0xe8, 0xf5, 0x2d, 0x18, 0xd8, 0x74, 0xbd, 0xbd, 0xc4, 0x56, 0x30, 0x83, 0x8f, 0x51, - 0xa1, 0x7a, 0x5c, 0x6f, 0x8f, 0xac, 0xca, 0xfd, 0xbd, 0xf0, 0x63, 0xf6, 0x1b, 0x46, 0x45, 0x06, - 0xb7, 0x4e, 0x1d, 0xef, 0xe3, 0xf9, 0x6f, 0xd9, 0xc0, 0xd7, 0x61, 0x36, 0xa7, 0x5c, 0x2d, 0x24, - 0x65, 0x00, 0x37, 0x36, 0x7f, 0xab, 0x00, 0x33, 0x99, 0x1d, 0x98, 0xa4, 0x64, 0x36, 0x1d, 0x1f, - 0x98, 0x0b, 0x2d, 0xbf, 0xf1, 0xf8, 0x18, 0x61, 0x93, 0x96, 0x7c, 0x50, 0x5f, 0x7c, 0x21, 0x0d, - 0xc6, 0x97, 0x78, 0x34, 0x51, 0x17, 0x69, 0xfd, 0xf3, 0xac, 0xb1, 0xce, 0x95, 0x5b, 0x8a, 0x43, - 0x45, 0xb8, 0xeb, 0x4a, 0x45, 0x87, 0xcc, 0x69, 0x73, 0x81, 0xcc, 0x98, 0x24, 0x3f, 0xf9, 0x1d, - 0x2d, 0x43, 0x1d, 0xff, 0x62, 0xdf, 0x7f, 0x81, 0xef, 0x52, 0xc9, 0x62, 0x65, 0x2e, 0xfb, 0x61, - 0xe4, 0xa9, 0x70, 0x7d, 0x5b, 0xfd, 0x26, 0x37, 0xa0, 0x28, 0x1f, 0xe1, 0x11, 0xaf, 0x7d, 0x05, - 0x22, 0xe6, 0x24, 0x05, 0x27, 0xef, 0xc2, 0x6c, 0x12, 0x26, 0x5b, 0xc9, 0xaf, 0xc5, 0xe6, 0xa1, - 0xad, 0x3f, 0xe9, 0xc3, 0x47, 0x04, 0xba, 0x7c, 0x3a, 0xac, 0xff, 0x36, 0x6a, 0x32, 0xf8, 0x68, - 0xa3, 0x46, 0x2e, 0xc0, 0xe8, 0x46, 0xcd, 0x78, 0xdd, 0xd0, 0x8e, 0x01, 0xac, 0xda, 0xac, 0x09, - 0x95, 0xa0, 0xb1, 0xef, 0x46, 0xb4, 0x11, 0x75, 0x02, 0x19, 0x8d, 0x94, 0x82, 0x13, 0x0b, 0xc6, - 0xef, 0xb5, 0xdc, 0xdd, 0x86, 0x14, 0xc6, 0x55, 0x60, 0xc0, 0xc8, 0xab, 0x30, 0xb9, 0xe2, 0x85, - 0x91, 0xd3, 0x6a, 0xad, 0xd1, 0x68, 0xdf, 0x6f, 0x8a, 0xb7, 0x9b, 0xed, 0x04, 0x94, 0x95, 0xbb, - 0xe0, 0x7b, 0x91, 0xe3, 0x7a, 0x34, 0xb0, 0x3b, 0x5e, 0xe4, 0x1e, 0x50, 0xd1, 0xf6, 0x14, 0x9c, - 0xbc, 0x05, 0x33, 0x0a, 0xb6, 0x11, 0x34, 0xf6, 0x69, 0x18, 0x05, 0xf8, 0xe6, 0x29, 0x06, 0xe6, - 0xd9, 0xd9, 0x48, 0x2c, 0xa1, 0xe5, 0x77, 0x9a, 0x4b, 0xde, 0x13, 0x37, 0xf0, 0xf9, 0x89, 0xf7, - 0x88, 0x28, 0x21, 0x01, 0xb7, 0x7e, 0x67, 0x24, 0x73, 0x66, 0x78, 0x91, 0x31, 0xf8, 0x05, 0x8c, - 0x2f, 0x38, 0x6d, 0x67, 0xd7, 0x6d, 0xb9, 0x91, 0xab, 0x1e, 0x87, 0x7c, 0xbb, 0xc7, 0xb4, 0x22, - 0x9f, 0x65, 0xa2, 0x4d, 0x9d, 0xd9, 0x36, 0x44, 0xcd, 0xfd, 0xc5, 0x10, 0xcc, 0x64, 0xd2, 0x91, - 0xeb, 0xe2, 0x15, 0x49, 0x35, 0x75, 0x8b, 0x27, 0x0a, 0xed, 0x24, 0x98, 0xf5, 0x25, 0x82, 0x16, - 0x5a, 0xd4, 0xf1, 0x3a, 0xe2, 0x81, 0x42, 0xdb, 0x80, 0xb1, 0xbe, 0x64, 0x5b, 0x13, 0x4d, 0x18, - 0xde, 0xb6, 0xb0, 0x13, 0x50, 0x0c, 0x66, 0xeb, 0x44, 0xfb, 0x52, 0xd4, 0x00, 0xbf, 0x17, 0xac, - 0x81, 0x98, 0xa4, 0x75, 0xbf, 0x49, 0x35, 0x49, 0x83, 0x5c, 0x92, 0x09, 0x65, 0x92, 0x18, 0x44, - 0x4a, 0x1a, 0xe2, 0x92, 0x34, 0x10, 0x79, 0x05, 0x26, 0x2a, 0xed, 0xb6, 0x26, 0x08, 0x5f, 0x26, - 0xb4, 0x4d, 0x20, 0xb9, 0x04, 0x50, 0x69, 0xb7, 0xa5, 0x18, 0x7c, 0x75, 0xd0, 0xd6, 0x20, 0xe4, - 0x66, 0x9c, 0x07, 0x52, 0x13, 0x85, 0xa7, 0x21, 0x76, 0x06, 0x86, 0xe9, 0x55, 0x25, 0xcd, 0x13, - 0x42, 0x81, 0xeb, 0x35, 0x01, 0x26, 0x1f, 0xc2, 0xb9, 0x44, 0x3c, 0x8c, 0x56, 0x00, 0x9e, 0x54, - 0xd8, 0xf9, 0x04, 0xe4, 0x1d, 0x38, 0x9b, 0x40, 0xca, 0xe2, 0xf0, 0x50, 0xc2, 0xce, 0xc1, 0x92, - 0xf7, 0xa1, 0x94, 0xc8, 0xf5, 0x10, 0x17, 0x8a, 0x07, 0x10, 0x76, 0x2e, 0x9e, 0x7d, 0x5d, 0x89, - 0x4b, 0xa3, 0xa2, 0x48, 0x3c, 0x6b, 0xb5, 0xb3, 0x91, 0x64, 0x19, 0xca, 0x99, 0x31, 0x46, 0x5a, - 0xc1, 0xf8, 0x9a, 0xa2, 0xdd, 0x8b, 0x8c, 0x54, 0xe1, 0x42, 0x26, 0x89, 0xac, 0x06, 0xbe, 0xb1, - 0x68, 0x77, 0xa5, 0x21, 0xf3, 0x30, 0x1d, 0xc7, 0x5a, 0x69, 0x55, 0xc0, 0xe7, 0x15, 0xed, 0x4c, - 0x1c, 0x79, 0xc3, 0xcc, 0xe8, 0xc1, 0x0b, 0xc3, 0xd7, 0x15, 0xed, 0x34, 0xc2, 0x3a, 0x2a, 0xc0, - 0x85, 0xcc, 0xb5, 0x58, 0x9a, 0x0c, 0x73, 0xc9, 0xbd, 0xa9, 0x36, 0x17, 0xdc, 0x10, 0x01, 0x94, - 0xdc, 0xd5, 0x2d, 0x03, 0xd4, 0x91, 0x9f, 0x8b, 0x7a, 0x10, 0x87, 0x53, 0xde, 0x53, 0x47, 0x9b, - 0xfd, 0xe8, 0x2c, 0xb9, 0x95, 0xdc, 0xa3, 0x65, 0x14, 0xae, 0x1f, 0x71, 0xca, 0xc3, 0xcc, 0x17, - 0x39, 0x45, 0xfa, 0x93, 0x02, 0x94, 0x7b, 0x6c, 0x41, 0x54, 0x9b, 0x0a, 0xc7, 0x68, 0xd3, 0x7d, - 0xd5, 0x26, 0x7e, 0xa1, 0x7e, 0xfe, 0x78, 0xdb, 0x9c, 0x97, 0xdd, 0xac, 0xbf, 0x2c, 0x00, 0x49, - 0x6f, 0x75, 0xc9, 0x77, 0x61, 0xb4, 0x56, 0x5b, 0x36, 0x22, 0x2d, 0x53, 0x87, 0x5b, 0x31, 0x05, - 0xb9, 0x7d, 0xac, 0xd0, 0x4a, 0x3d, 0xb0, 0xf2, 0x93, 0x54, 0x3c, 0x67, 0x7f, 0xd7, 0x78, 0xce, - 0x54, 0x34, 0xe7, 0x52, 0x46, 0x80, 0xe2, 0x40, 0x8f, 0x00, 0xc5, 0x74, 0xf4, 0xa1, 0xb5, 0x08, - 0xa5, 0xbc, 0xdd, 0x32, 0xce, 0x70, 0x3c, 0x7b, 0xa2, 0x76, 0x40, 0xc7, 0x67, 0x38, 0x13, 0x6c, - 0xbd, 0x03, 0x67, 0x15, 0x37, 0x7f, 0x96, 0x49, 0x4b, 0x5b, 0x22, 0x4c, 0x6c, 0x95, 0x1e, 0x25, - 0x06, 0x58, 0x7f, 0x3c, 0x90, 0x62, 0xac, 0x75, 0x0e, 0x0e, 0x9c, 0xe0, 0x19, 0xa9, 0x98, 0x8c, - 0xfd, 0x3d, 0xad, 0x9a, 0xea, 0x00, 0xdb, 0x63, 0x6a, 0xd2, 0xd9, 0xba, 0x80, 0x3b, 0x0c, 0xaf, - 0x41, 0xf9, 0xd1, 0x5e, 0x1f, 0x4f, 0xcd, 0x66, 0x00, 0xc9, 0x0e, 0x4c, 0x88, 0xb5, 0x1b, 0x7f, - 0xcb, 0x6f, 0xec, 0x76, 0xf2, 0x1b, 0x33, 0xaa, 0x77, 0xd3, 0x60, 0xe1, 0xa3, 0xd1, 0x14, 0x43, - 0xbe, 0x80, 0x49, 0xb9, 0x53, 0x13, 0x82, 0x79, 0x30, 0xd6, 0x9d, 0xee, 0x82, 0x4d, 0x1e, 0x2e, - 0x39, 0x21, 0x88, 0x55, 0x59, 0x4e, 0x76, 0x5c, 0xf2, 0xe0, 0x71, 0xaa, 0x6c, 0xb0, 0x88, 0x2a, - 0x1b, 0xb0, 0xb9, 0x4f, 0x81, 0xa4, 0xdb, 0xd5, 0xeb, 0x73, 0x9a, 0xd0, 0x3e, 0xa7, 0xb9, 0x0a, - 0x4c, 0x65, 0x34, 0xe0, 0x44, 0x22, 0x3e, 0x05, 0x92, 0xae, 0xe9, 0x49, 0x24, 0x58, 0xd7, 0xe1, - 0x55, 0xa5, 0x02, 0x35, 0x1a, 0x0c, 0x99, 0xd2, 0xc9, 0xfe, 0x2b, 0x7d, 0x50, 0xee, 0x41, 0x4a, - 0xfe, 0x71, 0x21, 0xa9, 0x6d, 0x3e, 0x1a, 0xdf, 0x4b, 0x6a, 0x3b, 0x9b, 0x3f, 0x43, 0xed, 0xd5, - 0xf7, 0x7f, 0xf5, 0xcf, 0x9e, 0xdb, 0xf2, 0x48, 0x77, 0xd9, 0xc9, 0xb5, 0x35, 0xa0, 0x6b, 0x6b, - 0x07, 0xa6, 0x0d, 0xb3, 0xf0, 0x38, 0x8b, 0x97, 0x05, 0x20, 0x5e, 0x88, 0x5e, 0xf5, 0xf7, 0xc4, - 0x43, 0xd6, 0x7d, 0xa5, 0x82, 0xad, 0x41, 0xad, 0xbb, 0x30, 0x93, 0x90, 0x2b, 0x9c, 0xff, 0xdf, - 0x05, 0x95, 0xa2, 0x02, 0x05, 0xf7, 0x57, 0xcf, 0xfc, 0xec, 0xb0, 0x3c, 0xc1, 0xb6, 0xf5, 0x37, - 0xe3, 0x57, 0x42, 0xe4, 0x5f, 0xd6, 0x9a, 0x7e, 0x7c, 0x51, 0x69, 0xe9, 0xa9, 0xbb, 0xc8, 0x1d, - 0x18, 0xe2, 0x90, 0x44, 0x2e, 0x7e, 0x9d, 0x5a, 0xcc, 0x0b, 0x82, 0xd0, 0x9a, 0xc1, 0x0b, 0xf5, - 0xf8, 0xa3, 0x12, 0x27, 0x80, 0xb1, 0xb6, 0xf9, 0xdb, 0x54, 0x31, 0x58, 0xe5, 0xfb, 0x1f, 0xa8, - 0xc4, 0x89, 0x6a, 0x64, 0x1c, 0x8b, 0xa4, 0xf3, 0xfc, 0xa7, 0x2d, 0xda, 0xe4, 0x8f, 0x8a, 0x56, - 0xc7, 0x85, 0x8d, 0x3b, 0xe0, 0x30, 0x01, 0xc8, 0x66, 0x7d, 0x02, 0x33, 0x6c, 0xb7, 0x10, 0x24, - 0xcb, 0xc3, 0x17, 0x69, 0x18, 0xcc, 0xbc, 0x37, 0xe3, 0x30, 0x10, 0xde, 0x9b, 0x11, 0x48, 0x6b, - 0x15, 0xce, 0x71, 0xe7, 0xa7, 0xde, 0xa4, 0xf8, 0xa8, 0x61, 0x10, 0x7f, 0x27, 0xae, 0x63, 0x67, - 0xb4, 0x9e, 0xd3, 0x59, 0x1f, 0xe3, 0x7d, 0x3f, 0x31, 0x50, 0x5d, 0xdf, 0x8b, 0x3d, 0x9d, 0xc7, - 0x4b, 0x10, 0xf0, 0xff, 0xc3, 0x85, 0x4a, 0xbb, 0x4d, 0xbd, 0x66, 0xcc, 0xb8, 0x15, 0x38, 0xc7, - 0x4c, 0xdf, 0x42, 0x2a, 0x30, 0x88, 0xd4, 0xea, 0x0c, 0x58, 0x54, 0x37, 0xa3, 0x3a, 0x48, 0x27, - 0x92, 0x33, 0x63, 0x01, 0x9c, 0xd3, 0x6a, 0xc2, 0x6c, 0xad, 0xb3, 0x7b, 0xe0, 0x46, 0x78, 0xdb, - 0x06, 0x53, 0x20, 0xc9, 0xb2, 0x57, 0xe4, 0x73, 0x82, 0x5c, 0x19, 0xd7, 0xe3, 0x7b, 0x61, 0x78, - 0x61, 0x47, 0xa4, 0x45, 0x7a, 0x72, 0xe7, 0x66, 0xcc, 0x8a, 0x5e, 0x1e, 0x5e, 0x0a, 0xa2, 0xc5, - 0x93, 0x83, 0xd6, 0x14, 0x9c, 0xd1, 0xcf, 0xbc, 0xf8, 0x08, 0x99, 0x81, 0x29, 0xf3, 0x2c, 0x8b, - 0x83, 0xbf, 0x86, 0x69, 0xee, 0x6b, 0xe7, 0x8f, 0x2b, 0xcc, 0xc7, 0xef, 0x08, 0xf4, 0xed, 0xcc, - 0x27, 0x2e, 0x69, 0x60, 0xec, 0xb6, 0x7a, 0x36, 0x67, 0x67, 0x9e, 0xdf, 0xd9, 0x7e, 0x32, 0x6f, - 0x9c, 0xc6, 0xf6, 0xed, 0xcc, 0x57, 0x87, 0x45, 0x92, 0x6a, 0x26, 0x9d, 0x77, 0xff, 0xb7, 0x22, - 0x7d, 0x1e, 0xd3, 0x84, 0x2c, 0x53, 0x07, 0xaf, 0xf4, 0x65, 0x27, 0x5b, 0x98, 0x84, 0x3e, 0x95, - 0x85, 0xb6, 0xcf, 0x6d, 0x5a, 0xbf, 0x5f, 0x80, 0xeb, 0x7c, 0x43, 0x96, 0xcd, 0x87, 0x07, 0x5b, - 0x39, 0xcc, 0xe4, 0x5d, 0x18, 0x0c, 0xb5, 0x00, 0x0f, 0x4b, 0xd4, 0xbc, 0x9b, 0x24, 0xce, 0x40, - 0x2a, 0x30, 0xae, 0xdf, 0x5c, 0x3b, 0x5e, 0x82, 0x4b, 0x7b, 0xec, 0xe0, 0x91, 0xa3, 0x6e, 0xb3, - 0x3d, 0x86, 0xf3, 0x4b, 0xdf, 0xb0, 0x01, 0x21, 0x56, 0x28, 0x61, 0x3d, 0xc4, 0x97, 0xf9, 0x4f, - 0x6f, 0x89, 0x11, 0x63, 0x9a, 0xf6, 0x49, 0x30, 0xb3, 0x93, 0xe5, 0x22, 0x17, 0x5f, 0x71, 0xb2, - 0x0d, 0x98, 0xf5, 0xc7, 0x05, 0xb8, 0x90, 0x5d, 0x9a, 0x98, 0x58, 0x56, 0xe0, 0xcc, 0x82, 0xe3, - 0xf9, 0x9e, 0xdb, 0x70, 0x5a, 0xb5, 0xc6, 0x3e, 0x6d, 0x76, 0x54, 0x2a, 0x6b, 0x35, 0xcb, 0xec, - 0x51, 0x4f, 0xb2, 0x4b, 0x12, 0x3b, 0xcd, 0xc5, 0x2c, 0x44, 0xbc, 0xba, 0xc2, 0xe7, 0xde, 0x16, - 0x0d, 0x94, 0x3c, 0x5e, 0xb3, 0x1c, 0x2c, 0xb9, 0x2d, 0x0f, 0x15, 0x9a, 0xdb, 0x9e, 0x1b, 0x29, - 0x26, 0xee, 0xea, 0xc9, 0x42, 0x59, 0xff, 0xa1, 0x00, 0xe7, 0xf0, 0xf5, 0x3a, 0xe3, 0x3d, 0xdc, - 0x38, 0xa3, 0xbb, 0x4c, 0x4a, 0x5e, 0x30, 0xae, 0xe2, 0x18, 0xd4, 0x66, 0x76, 0x72, 0xf2, 0x06, - 0x0c, 0xd4, 0x64, 0xc0, 0xd9, 0x64, 0xe2, 0x25, 0x73, 0xc1, 0xc1, 0xf0, 0x36, 0x52, 0x31, 0x1b, - 0x7e, 0x91, 0x86, 0x0d, 0xea, 0xe1, 0x93, 0xf3, 0xdc, 0xf3, 0xa0, 0x41, 0xe2, 0x64, 0x6b, 0x03, - 0x79, 0xc9, 0xd6, 0x06, 0xcd, 0x64, 0x6b, 0xd6, 0x13, 0xfe, 0x76, 0x5d, 0xb2, 0x41, 0xa2, 0x93, - 0x3e, 0x4e, 0xbd, 0x50, 0xcf, 0xd7, 0x81, 0xb3, 0x59, 0x2d, 0x63, 0x9b, 0xf4, 0xc4, 0xe3, 0xf3, - 0xf9, 0x19, 0xd4, 0x37, 0xe1, 0x15, 0x83, 0xb6, 0xd2, 0x6a, 0xf9, 0x4f, 0x69, 0x73, 0x33, 0xf0, - 0x0f, 0xfc, 0xc8, 0x78, 0xbb, 0xeb, 0xb4, 0xa3, 0xd3, 0xa9, 0xc5, 0x38, 0x09, 0xb6, 0xfe, 0x3f, - 0xb8, 0xd6, 0x43, 0xa2, 0x68, 0x54, 0x0d, 0xce, 0x38, 0x09, 0x9c, 0x8c, 0x1c, 0xba, 0x96, 0xd5, - 0xae, 0xa4, 0xa0, 0xd0, 0x4e, 0xf3, 0xdf, 0xd8, 0x32, 0x5e, 0x75, 0x27, 0x25, 0x98, 0xde, 0xb4, - 0x37, 0x16, 0xb7, 0x17, 0xb6, 0xea, 0x5b, 0x5f, 0x6c, 0x2e, 0xd5, 0xb7, 0xd7, 0x1f, 0xac, 0x6f, - 0x3c, 0x5c, 0xe7, 0x4f, 0x10, 0x18, 0x98, 0xad, 0xa5, 0xca, 0x5a, 0xb1, 0x40, 0xa6, 0xa1, 0x68, - 0x80, 0x97, 0xb6, 0xab, 0xc5, 0xbe, 0x1b, 0x5f, 0x1b, 0xaf, 0x95, 0x93, 0x0b, 0x50, 0xaa, 0x6d, - 0x6f, 0x6e, 0x6e, 0xd8, 0x4a, 0xaa, 0xfe, 0x00, 0xc2, 0x0c, 0x9c, 0x31, 0xb0, 0x77, 0xed, 0xa5, - 0xa5, 0x62, 0x81, 0x55, 0xc5, 0x00, 0x6f, 0xda, 0x4b, 0x6b, 0x2b, 0xdb, 0x6b, 0xc5, 0xbe, 0x1b, - 0x75, 0xfd, 0x06, 0x29, 0x39, 0x0f, 0xb3, 0x8b, 0x4b, 0x3b, 0x2b, 0x0b, 0x4b, 0x59, 0xb2, 0xa7, - 0xa1, 0xa8, 0x23, 0xb7, 0x36, 0xb6, 0x36, 0xb9, 0x68, 0x1d, 0xfa, 0x70, 0xa9, 0x5a, 0xd9, 0xde, - 0x5a, 0x5e, 0x2f, 0xf6, 0x5b, 0x03, 0x23, 0x7d, 0xc5, 0xbe, 0x1b, 0x3f, 0x30, 0xae, 0x97, 0xb2, - 0xea, 0x0b, 0xf2, 0xed, 0x5a, 0xe5, 0x5e, 0x7e, 0x11, 0x1c, 0xbb, 0x76, 0xb7, 0x52, 0x2c, 0x90, - 0x8b, 0x70, 0xce, 0x80, 0x6e, 0x56, 0x6a, 0xb5, 0x87, 0x1b, 0xf6, 0xe2, 0xea, 0x52, 0xad, 0x56, - 0xec, 0xbb, 0xb1, 0x63, 0x24, 0x98, 0x64, 0x25, 0xac, 0xdd, 0xad, 0xd4, 0xed, 0xa5, 0xcf, 0xb6, - 0x57, 0xec, 0xa5, 0xc5, 0x74, 0x09, 0x06, 0xf6, 0x8b, 0xa5, 0x5a, 0xb1, 0x40, 0xa6, 0xe0, 0xb4, - 0x01, 0x5d, 0xdf, 0x28, 0xf6, 0xdd, 0x78, 0x55, 0xe4, 0x20, 0x24, 0x93, 0x00, 0x8b, 0x4b, 0xb5, - 0x85, 0xa5, 0xf5, 0xc5, 0x95, 0xf5, 0x7b, 0xc5, 0x53, 0x64, 0x02, 0x46, 0x2b, 0xea, 0x67, 0xe1, - 0xc6, 0xfb, 0x70, 0x3a, 0x61, 0xde, 0x33, 0x0a, 0x65, 0x18, 0x17, 0x4f, 0xa1, 0xfa, 0xe5, 0x4f, - 0xf4, 0xb1, 0x72, 0x4b, 0xbd, 0x58, 0xb8, 0x51, 0x95, 0x0f, 0x5c, 0x6b, 0xdf, 0x39, 0x19, 0x83, - 0xe1, 0xc5, 0xa5, 0xbb, 0x95, 0xed, 0xd5, 0xad, 0xe2, 0x29, 0xf6, 0x63, 0xc1, 0x5e, 0xaa, 0x6c, - 0x2d, 0x2d, 0x16, 0x0b, 0x64, 0x14, 0x06, 0x6b, 0x5b, 0x95, 0xad, 0xa5, 0x62, 0x1f, 0x19, 0x81, - 0x81, 0xed, 0xda, 0x92, 0x5d, 0xec, 0x9f, 0xff, 0xd7, 0xff, 0xa8, 0xc0, 0x1d, 0x8d, 0xf2, 0xa2, - 0xd9, 0xd7, 0x9a, 0x41, 0x29, 0xa6, 0x3c, 0xf1, 0x9a, 0x6f, 0xae, 0xf5, 0x88, 0xbb, 0x80, 0xb9, - 0x2e, 0x87, 0x3b, 0x48, 0x70, 0xbd, 0x70, 0xbb, 0x40, 0x6c, 0x0c, 0x86, 0x49, 0xd8, 0x57, 0x4a, - 0x72, 0xb6, 0x09, 0x3c, 0x77, 0xb1, 0xab, 0x59, 0x46, 0x7e, 0x09, 0x2c, 0x5d, 0x66, 0x8e, 0x15, - 0xf2, 0xdd, 0xe3, 0x59, 0x1b, 0xb2, 0xcc, 0x57, 0x8f, 0x47, 0x4e, 0xee, 0xc3, 0x04, 0xdb, 0x9b, - 0x2b, 0x32, 0x72, 0x3e, 0xc9, 0xa8, 0x99, 0x04, 0x73, 0x17, 0xb2, 0x91, 0xea, 0xc1, 0xad, 0x71, - 0x6c, 0x08, 0x37, 0xae, 0x43, 0x22, 0xf3, 0xd4, 0x48, 0x08, 0x9f, 0xf1, 0xe7, 0xce, 0x24, 0xc0, - 0x3b, 0x77, 0x6e, 0x17, 0x48, 0x0d, 0x93, 0x44, 0x1a, 0x9b, 0x7c, 0x22, 0x6f, 0x3e, 0xa6, 0x77, - 0xff, 0xbc, 0x36, 0x65, 0xf5, 0x3c, 0x6e, 0x8e, 0x75, 0xb0, 0x0e, 0x24, 0xbd, 0x77, 0x26, 0x97, - 0xe3, 0x71, 0x90, 0xbd, 0xad, 0x9e, 0x3b, 0x9b, 0x3a, 0xc8, 0x5a, 0x62, 0xbb, 0x27, 0xb2, 0x04, - 0x93, 0x22, 0x09, 0x85, 0xd8, 0xcd, 0x93, 0x6e, 0xf6, 0x40, 0xae, 0x98, 0x7b, 0xa8, 0x27, 0x65, - 0x11, 0x90, 0xb9, 0xb8, 0x1d, 0x49, 0x33, 0x61, 0xee, 0x7c, 0x26, 0x4e, 0xb4, 0xef, 0x2e, 0x4c, - 0x9a, 0xc6, 0x05, 0x91, 0x1d, 0x94, 0x69, 0x73, 0xe4, 0x56, 0xa8, 0x0e, 0xb3, 0x6b, 0x8e, 0x8b, - 0xe7, 0x25, 0x22, 0x48, 0x4f, 0xc6, 0xc1, 0x91, 0x72, 0x97, 0xc0, 0xb8, 0x1a, 0xf5, 0x9a, 0xaa, - 0x13, 0xf2, 0x1e, 0xcf, 0xc0, 0xcf, 0xa6, 0x26, 0xf7, 0xc8, 0x66, 0x8c, 0x22, 0xb1, 0xcc, 0x27, - 0xcf, 0xb3, 0xc2, 0x4e, 0xe7, 0xf2, 0x22, 0xa5, 0xc9, 0x1a, 0x6e, 0xd2, 0x13, 0x12, 0xb5, 0x31, - 0x71, 0x62, 0x71, 0x25, 0x4c, 0x85, 0x12, 0xb9, 0xc9, 0x90, 0xe7, 0x90, 0xe4, 0x28, 0x2e, 0x57, - 0xd8, 0xed, 0x02, 0xf9, 0x1a, 0xbf, 0xea, 0x4c, 0x71, 0x0f, 0xdd, 0x68, 0x5f, 0xec, 0x7e, 0xce, - 0x67, 0x0a, 0x10, 0x1f, 0x4a, 0x17, 0xe9, 0x36, 0x4c, 0x67, 0x05, 0x67, 0x2b, 0x85, 0x76, 0x89, - 0xdc, 0xce, 0x1d, 0x05, 0x36, 0x33, 0x35, 0x9a, 0xf9, 0x9d, 0xd4, 0x25, 0x36, 0x38, 0x57, 0xe6, - 0x87, 0x30, 0xc9, 0x46, 0xc9, 0x03, 0x4a, 0xdb, 0x95, 0x96, 0xfb, 0x84, 0x86, 0x44, 0x66, 0xf8, - 0x56, 0xa0, 0x3c, 0xde, 0xeb, 0x05, 0xf2, 0x1d, 0x18, 0x7b, 0xe8, 0x44, 0x8d, 0x7d, 0x91, 0xe9, - 0x56, 0x26, 0xc2, 0x45, 0xd8, 0x9c, 0xfc, 0x85, 0xc8, 0xdb, 0x05, 0xf2, 0x11, 0x0c, 0xdf, 0xa3, - 0x11, 0xde, 0x3c, 0xbf, 0xa2, 0x62, 0x09, 0xb9, 0x7f, 0x72, 0xc5, 0x53, 0xb7, 0x90, 0x64, 0x85, - 0x93, 0xce, 0x5c, 0x72, 0x0b, 0x80, 0x4f, 0x08, 0x28, 0x21, 0x89, 0x9e, 0x4b, 0x55, 0x9b, 0xdc, - 0x63, 0x9b, 0x87, 0x16, 0x8d, 0xe8, 0x71, 0x8b, 0xcc, 0xd3, 0xd1, 0x2a, 0x4c, 0xaa, 0x37, 0xca, - 0xd6, 0x31, 0x21, 0x91, 0x95, 0x10, 0x16, 0x9e, 0x40, 0xda, 0xfb, 0xec, 0xab, 0xe0, 0x0f, 0x74, - 0x63, 0xe6, 0x1a, 0x9c, 0x49, 0x67, 0xf5, 0xf4, 0x37, 0xfa, 0x14, 0x2a, 0x95, 0xc8, 0xc9, 0x34, - 0xde, 0x65, 0x3f, 0x8c, 0x4c, 0x5e, 0x05, 0xc9, 0xe6, 0xfd, 0x45, 0x98, 0xd3, 0xcb, 0x35, 0x53, - 0xad, 0xc7, 0x73, 0x6e, 0x5e, 0x06, 0xf7, 0xb9, 0x2b, 0x5d, 0x28, 0x84, 0xfd, 0xd6, 0xff, 0xeb, - 0x7d, 0x05, 0x9c, 0x4e, 0x16, 0x61, 0x4a, 0x96, 0xb5, 0xd1, 0xa6, 0x5e, 0xad, 0xb6, 0x8c, 0xef, - 0x51, 0xc9, 0xc8, 0x15, 0x0d, 0x26, 0xa5, 0x93, 0x34, 0x8a, 0x2d, 0x7d, 0x46, 0x86, 0x1a, 0xd2, - 0x2d, 0x6f, 0x4d, 0xbc, 0xf4, 0x65, 0xe6, 0x00, 0x7f, 0xc0, 0x9d, 0x4a, 0xc6, 0xe6, 0x7f, 0x67, - 0x9e, 0x74, 0x31, 0x80, 0xe6, 0x72, 0x4c, 0x88, 0xdb, 0x05, 0xf2, 0x05, 0x90, 0xb4, 0x49, 0xa2, - 0x54, 0x98, 0x6b, 0x7e, 0x29, 0x15, 0x76, 0xb1, 0x67, 0xee, 0xc1, 0x8c, 0xca, 0x4f, 0xa5, 0x95, - 0x3a, 0x4f, 0x72, 0x6a, 0x93, 0x57, 0x4b, 0xf2, 0x09, 0x4c, 0x89, 0x41, 0xab, 0x23, 0x48, 0x51, - 0xcd, 0x3f, 0xc2, 0x2a, 0xc9, 0x1d, 0xa7, 0xf7, 0x61, 0xa6, 0x96, 0xd0, 0x18, 0x0f, 0xe6, 0x3f, - 0x67, 0x8a, 0x40, 0x60, 0x8d, 0x46, 0x5c, 0x65, 0xd9, 0xb2, 0x1e, 0x00, 0xe1, 0x4e, 0x21, 0x29, - 0xee, 0x89, 0x4b, 0x9f, 0x92, 0x8b, 0x89, 0xaa, 0x33, 0x20, 0x92, 0xe1, 0x04, 0x96, 0xdb, 0xb2, - 0x2d, 0xfe, 0xbc, 0x3c, 0x42, 0x8d, 0x73, 0xf4, 0xcb, 0x06, 0x83, 0x71, 0x14, 0x2f, 0x3a, 0xe0, - 0x5c, 0x2e, 0x05, 0xf9, 0x65, 0x4c, 0x0c, 0xdd, 0xdd, 0xac, 0x22, 0xdf, 0xc9, 0xb2, 0x7e, 0x73, - 0x0c, 0xc3, 0xb9, 0x37, 0x8e, 0x47, 0xac, 0x0c, 0xd9, 0x89, 0x7b, 0x34, 0xda, 0x6c, 0x75, 0xf6, - 0x5c, 0x7c, 0x78, 0x98, 0x28, 0xa7, 0x91, 0x02, 0x89, 0x71, 0x29, 0xf3, 0x31, 0xc6, 0x88, 0x1a, - 0xfd, 0x21, 0x59, 0x81, 0x22, 0x9f, 0xff, 0x35, 0x11, 0x17, 0x53, 0x22, 0x04, 0x89, 0x13, 0x38, - 0x07, 0x61, 0x6e, 0x6f, 0xdd, 0xe2, 0xb1, 0x51, 0x44, 0x7e, 0x93, 0xfa, 0x06, 0x73, 0xca, 0x80, - 0xa9, 0xc7, 0x32, 0x58, 0x8f, 0xd8, 0x34, 0xa4, 0x91, 0xcc, 0x40, 0xc5, 0x9f, 0x9d, 0xbe, 0x1a, - 0x2f, 0xf6, 0x69, 0x6c, 0xfc, 0xe9, 0x27, 0xb2, 0x25, 0xee, 0xbc, 0x49, 0xd4, 0x53, 0xdc, 0x19, - 0x42, 0x5f, 0x35, 0xf6, 0x24, 0x27, 0x93, 0xfb, 0x16, 0xae, 0x41, 0x98, 0x75, 0x6b, 0x26, 0xae, - 0x1b, 0xfb, 0x2d, 0xb9, 0x26, 0x34, 0xae, 0x9d, 0x79, 0x9c, 0xd2, 0xd8, 0x22, 0xc9, 0xb6, 0xb0, - 0x9d, 0x20, 0xa0, 0x1e, 0x67, 0xce, 0xdb, 0x6f, 0x64, 0x71, 0x7f, 0x8c, 0x53, 0x8f, 0xc6, 0xcd, - 0xef, 0x1c, 0xf6, 0x12, 0xc1, 0x9f, 0x49, 0xbb, 0x5d, 0x20, 0xef, 0xc2, 0x88, 0xa8, 0x23, 0x63, - 0x32, 0x2a, 0x1d, 0x76, 0xa9, 0x35, 0x72, 0x02, 0x57, 0x12, 0xd6, 0xd9, 0xa4, 0xc9, 0xeb, 0x7d, - 0x5e, 0xe7, 0x77, 0xd9, 0x62, 0xdb, 0x7c, 0x1e, 0xce, 0x05, 0xb9, 0xea, 0x22, 0x67, 0x49, 0x65, - 0x6a, 0x92, 0xa0, 0x1e, 0xcb, 0x23, 0x17, 0xc2, 0xf6, 0xcd, 0x98, 0xee, 0x54, 0x65, 0x2d, 0x54, - 0xfb, 0x66, 0x03, 0xdc, 0x6b, 0xad, 0x5d, 0x81, 0x62, 0xa5, 0x81, 0x2b, 0x41, 0x8d, 0x1e, 0x38, - 0xed, 0x7d, 0x3f, 0xa0, 0xca, 0x68, 0x49, 0x22, 0xa4, 0xac, 0x19, 0xb5, 0xb3, 0x10, 0x88, 0x55, - 0xea, 0x60, 0x4e, 0xf8, 0x59, 0xb5, 0xb5, 0x48, 0xa0, 0xb2, 0x39, 0xba, 0x18, 0x29, 0xd3, 0x0b, - 0xcc, 0xac, 0x6a, 0xbd, 0x98, 0x98, 0xf7, 0x71, 0xc2, 0x50, 0xc4, 0xa1, 0x5a, 0x21, 0x14, 0x48, - 0x99, 0x73, 0xf2, 0xfa, 0x91, 0x22, 0xad, 0xc8, 0x73, 0xe3, 0x58, 0x2d, 0x79, 0xdc, 0x79, 0xc5, - 0x7f, 0x0f, 0x26, 0x97, 0xd8, 0x84, 0xde, 0x69, 0xba, 0xfc, 0x1d, 0x0c, 0x62, 0x3e, 0x6c, 0x90, - 0xcb, 0xb8, 0x2c, 0x5f, 0x26, 0x44, 0x56, 0x61, 0xfa, 0xcb, 0x35, 0x45, 0x83, 0xc9, 0xfe, 0x98, - 0x96, 0x62, 0xc5, 0x53, 0x24, 0x68, 0x9a, 0x0b, 0x5b, 0x7f, 0x96, 0xef, 0x08, 0x2b, 0xed, 0x76, - 0x4b, 0xba, 0xa4, 0xf9, 0xd9, 0xfb, 0x35, 0xc3, 0x84, 0x4c, 0xe1, 0xa5, 0xec, 0xf4, 0xa6, 0xf1, - 0x73, 0xed, 0xa5, 0xf0, 0x1c, 0x99, 0x39, 0xf8, 0x5e, 0x63, 0x51, 0x65, 0xae, 0xaf, 0xb4, 0x5a, - 0x29, 0xe6, 0x90, 0xbc, 0x6e, 0x4a, 0xcf, 0xa2, 0xe9, 0x55, 0x02, 0x9a, 0xe8, 0x7c, 0xd7, 0x55, - 0x69, 0xb7, 0xf9, 0x64, 0x79, 0x49, 0x4d, 0x18, 0x26, 0x22, 0x6d, 0xa2, 0x27, 0xf1, 0x62, 0x6e, - 0xbf, 0x8f, 0xc3, 0x2c, 0x7e, 0x4e, 0x9c, 0xe8, 0x06, 0x6f, 0xf2, 0x35, 0x75, 0xb5, 0x09, 0x4b, - 0x20, 0xd5, 0x3a, 0x71, 0x1a, 0xb7, 0x3e, 0xf1, 0xdb, 0xe4, 0xca, 0x33, 0x93, 0x80, 0x4b, 0x79, - 0x97, 0xf2, 0xd0, 0xca, 0x53, 0x5a, 0x14, 0x83, 0x29, 0xae, 0xe0, 0x25, 0x63, 0x7d, 0x48, 0xd7, - 0xb1, 0x9c, 0x8b, 0x57, 0x4d, 0x2e, 0x26, 0x5f, 0x8b, 0x57, 0x42, 0x73, 0x9e, 0x91, 0xcf, 0xed, - 0x93, 0xbb, 0x30, 0xad, 0xf7, 0xa8, 0x6a, 0x77, 0xde, 0xec, 0x9f, 0x27, 0x67, 0x0b, 0x66, 0x32, - 0x1f, 0x77, 0x57, 0x4b, 0x6c, 0xb7, 0xa7, 0xdf, 0x73, 0xa5, 0x52, 0x38, 0x2b, 0x2c, 0xfb, 0xc4, - 0x73, 0xf6, 0xe4, 0x15, 0xd3, 0xf0, 0xcf, 0x7e, 0xed, 0x7e, 0xee, 0x5a, 0x0f, 0x2a, 0xa1, 0xd0, - 0xaf, 0x71, 0x05, 0x4c, 0x95, 0x71, 0x45, 0x73, 0x05, 0xe4, 0x14, 0x60, 0x75, 0x23, 0x51, 0x63, - 0x60, 0x3a, 0x03, 0x9d, 0xaf, 0xe2, 0xab, 0xf9, 0x32, 0xe3, 0x81, 0xb5, 0x23, 0x13, 0xb4, 0xe7, - 0x6a, 0x26, 0x1b, 0xdd, 0xdb, 0x96, 0x9c, 0x53, 0xe3, 0xe1, 0xf8, 0x55, 0xce, 0x93, 0xd6, 0x54, - 0x6e, 0x1b, 0xe3, 0x91, 0xfe, 0xa4, 0xdb, 0xc6, 0x40, 0xca, 0x1a, 0x5e, 0xed, 0x4a, 0xa3, 0x59, - 0x74, 0xe4, 0x2b, 0xee, 0xc7, 0x31, 0x8b, 0xd0, 0xfd, 0x38, 0x99, 0xf2, 0x2f, 0xe7, 0x13, 0xe8, - 0xc2, 0x1d, 0x7e, 0x68, 0x6b, 0x92, 0x84, 0x44, 0x37, 0x95, 0x12, 0xb8, 0xe4, 0xd8, 0xc8, 0x24, - 0xd1, 0x8b, 0x78, 0x28, 0xbf, 0xc1, 0x1c, 0x2d, 0x65, 0x21, 0x8f, 0xb5, 0x4d, 0xd9, 0x80, 0x52, - 0xdc, 0x99, 0x89, 0x06, 0x9c, 0xb0, 0x2b, 0xa5, 0x32, 0xce, 0xc5, 0xdf, 0x71, 0x52, 0xe2, 0x6b, - 0xa9, 0x2f, 0x3d, 0x47, 0x31, 0x5d, 0x8b, 0xe0, 0xf3, 0xb9, 0x96, 0xf0, 0xfd, 0x7c, 0xec, 0xc4, - 0x8d, 0xa1, 0x19, 0xf3, 0xb9, 0x8e, 0x54, 0xc6, 0xea, 0xa4, 0x81, 0xc8, 0x6f, 0xf5, 0xc5, 0x2c, - 0x39, 0x61, 0x7a, 0xc6, 0xd5, 0xea, 0x25, 0xf7, 0x69, 0x49, 0xc4, 0x49, 0x66, 0xdc, 0xe3, 0x54, - 0x2d, 0x4f, 0xce, 0x22, 0x8c, 0xf1, 0xda, 0xf2, 0x85, 0xf4, 0x9c, 0xa1, 0x26, 0x63, 0x0d, 0x9d, - 0x33, 0x1a, 0x67, 0x2e, 0x9f, 0x0b, 0xe8, 0x4a, 0x96, 0xe0, 0xfc, 0x5a, 0x9c, 0x4f, 0xcb, 0x30, - 0xdc, 0xc8, 0x4a, 0x0b, 0xbc, 0x36, 0x17, 0x92, 0xca, 0x31, 0x2a, 0x94, 0xdf, 0x24, 0xa2, 0xab, - 0xa6, 0x47, 0x95, 0xf2, 0xf7, 0xaf, 0x53, 0xe2, 0x89, 0x68, 0x7c, 0xa5, 0x49, 0xe6, 0x63, 0x3c, - 0xab, 0x7c, 0x62, 0x1a, 0x14, 0x1d, 0x14, 0xd9, 0x62, 0x36, 0xf1, 0xfa, 0x08, 0x0d, 0xa2, 0x54, - 0xbe, 0xc5, 0x57, 0x8c, 0xcd, 0x5b, 0x12, 0x9d, 0xbf, 0x77, 0x53, 0x73, 0x76, 0xae, 0xc4, 0x6c, - 0x74, 0x2f, 0xb5, 0x7d, 0x5f, 0x9b, 0xb3, 0x93, 0xbc, 0x21, 0xb9, 0x9e, 0xdc, 0xb8, 0xa5, 0x48, - 0x7a, 0xaf, 0x09, 0x22, 0x84, 0x24, 0x11, 0x40, 0x6a, 0x19, 0x7a, 0x30, 0x91, 0xf9, 0x5a, 0xb0, - 0xe5, 0xf8, 0xcf, 0x91, 0x96, 0x85, 0xec, 0x55, 0xc3, 0x2f, 0xb5, 0x89, 0xce, 0xe4, 0x0c, 0x95, - 0x39, 0x9e, 0x47, 0xd0, 0x4b, 0xf6, 0x3a, 0x5e, 0x69, 0x4a, 0x34, 0xd0, 0x6d, 0x50, 0xb5, 0xb3, - 0xc9, 0xc4, 0xe6, 0xb7, 0xff, 0x9e, 0xdc, 0x29, 0x25, 0xe5, 0x9d, 0x4d, 0x38, 0x6d, 0x7b, 0x55, - 0xec, 0x6b, 0x39, 0x19, 0x27, 0xda, 0x84, 0x97, 0x8a, 0x5e, 0xeb, 0xd6, 0x6a, 0xed, 0x31, 0xce, - 0x2e, 0x66, 0xd0, 0xe9, 0x9a, 0xbb, 0xe7, 0xa9, 0x9b, 0x08, 0x35, 0x5b, 0x19, 0x41, 0x1a, 0x2c, - 0x39, 0xc5, 0x18, 0x28, 0x95, 0x9f, 0x67, 0x5a, 0xee, 0xde, 0x15, 0x9a, 0x06, 0x11, 0x49, 0xf1, - 0x68, 0xee, 0xd6, 0xf3, 0x99, 0xb8, 0xb4, 0x40, 0xfd, 0x81, 0x7e, 0x25, 0xd0, 0x7c, 0xb5, 0xdf, - 0x14, 0x98, 0xf9, 0xa2, 0xff, 0x2d, 0xf4, 0xba, 0xd8, 0x7e, 0x8b, 0xea, 0x5e, 0x17, 0xed, 0xc5, - 0xf7, 0x84, 0xd3, 0x83, 0x7c, 0x0c, 0xa3, 0xea, 0x45, 0x7c, 0xe5, 0xdf, 0x4e, 0x3e, 0xca, 0x3f, - 0x57, 0x4a, 0x23, 0x44, 0x81, 0x6f, 0x4b, 0xc7, 0x07, 0x96, 0x59, 0x32, 0x1d, 0x46, 0xf9, 0xc5, - 0xbe, 0x2d, 0xbd, 0x1e, 0x06, 0x5b, 0xea, 0x3d, 0xfc, 0x24, 0xdb, 0xf7, 0x60, 0x3c, 0x7e, 0xfb, - 0x7e, 0x67, 0x5e, 0x63, 0x4c, 0x3c, 0x88, 0x9f, 0x64, 0x7c, 0x57, 0x1e, 0x69, 0x60, 0x79, 0x26, - 0xb2, 0xfb, 0x2a, 0xfe, 0xb1, 0xf4, 0xb2, 0x18, 0x35, 0x4d, 0xbd, 0xa4, 0xdf, 0x65, 0xf2, 0x1d, - 0xd7, 0x1f, 0xa3, 0x55, 0x5d, 0x9b, 0xf1, 0x9c, 0xb4, 0xea, 0xda, 0xac, 0xe7, 0xa0, 0x63, 0x97, - 0xff, 0x17, 0xd2, 0xa5, 0x10, 0x0b, 0xbd, 0x68, 0x54, 0x2b, 0x25, 0xf7, 0x52, 0x1e, 0x3a, 0x29, - 0xba, 0x06, 0xc5, 0xe4, 0xcb, 0xb9, 0xca, 0x1e, 0xcb, 0x79, 0xe2, 0x58, 0x19, 0x79, 0xb9, 0x4f, - 0xee, 0x6e, 0x4a, 0xff, 0xb8, 0x29, 0xf7, 0x4a, 0x76, 0xa5, 0x74, 0xd1, 0xf9, 0x0e, 0xf3, 0x09, - 0xe3, 0x11, 0x5d, 0xdd, 0x52, 0x4e, 0x3d, 0xd2, 0xab, 0xef, 0xac, 0x32, 0xde, 0xdd, 0x75, 0x65, - 0xd2, 0xaa, 0xcc, 0x23, 0x5b, 0xe5, 0x2c, 0xe8, 0xfd, 0xe2, 0x42, 0xcf, 0xe3, 0x5f, 0xf2, 0x0b, - 0x30, 0x9b, 0x93, 0x41, 0x9e, 0x5c, 0x4b, 0x78, 0x5a, 0xb3, 0x33, 0xcc, 0xab, 0x01, 0x92, 0xf9, - 0xba, 0xfd, 0x1a, 0xc6, 0x0d, 0x18, 0x29, 0x24, 0x52, 0x67, 0x71, 0x0f, 0xdd, 0x68, 0x9f, 0x3f, - 0xe2, 0xae, 0x4d, 0x9b, 0x99, 0xb9, 0x27, 0x48, 0x0d, 0x6d, 0x11, 0x03, 0x9a, 0x71, 0x1c, 0x97, - 0x21, 0x70, 0x2e, 0x5b, 0x20, 0x9b, 0x3b, 0xd8, 0x58, 0xc8, 0xc8, 0xef, 0xa1, 0xc6, 0x42, 0x7e, - 0xee, 0x8f, 0xdc, 0x6a, 0x6e, 0xca, 0x3d, 0x52, 0xb6, 0xc4, 0xfc, 0x54, 0x1f, 0xb9, 0x12, 0xef, - 0x33, 0x89, 0xa9, 0xec, 0x1d, 0x24, 0x87, 0xbc, 0xfb, 0xec, 0x61, 0xcb, 0x25, 0xd7, 0xe4, 0x9a, - 0xd7, 0xea, 0x97, 0x97, 0x27, 0x24, 0xb7, 0x7e, 0x4b, 0xf2, 0x7b, 0xca, 0xae, 0xdf, 0x71, 0x17, - 0x5d, 0x75, 0xfe, 0x95, 0x48, 0x20, 0x63, 0x34, 0x54, 0x83, 0xcf, 0xe5, 0xc0, 0xc9, 0x3a, 0x06, - 0x02, 0x25, 0xa1, 0x9a, 0x51, 0x9a, 0x9d, 0xa1, 0x26, 0x57, 0x1e, 0x1f, 0xc7, 0x46, 0x86, 0x8f, - 0x93, 0x8c, 0xe3, 0x44, 0x6a, 0x10, 0x31, 0x8e, 0x0d, 0xe8, 0xc9, 0xc6, 0x71, 0x42, 0xa0, 0x39, - 0x8e, 0x93, 0xd5, 0x4c, 0x5a, 0xfa, 0xb9, 0xbd, 0x9a, 0xac, 0xa6, 0x1a, 0xc7, 0xd9, 0x12, 0xf3, - 0x33, 0xb1, 0xe4, 0x4a, 0x54, 0xe3, 0xd8, 0x94, 0x98, 0x43, 0x7e, 0xcc, 0x71, 0x9c, 0x2c, 0xc4, - 0x1c, 0xc7, 0x27, 0xaa, 0x9f, 0x1a, 0xc7, 0xd9, 0xf5, 0x3b, 0xf1, 0x38, 0x4e, 0xa4, 0x2e, 0x32, - 0x1a, 0x9a, 0x35, 0x8e, 0x93, 0xf4, 0x7c, 0x1c, 0x27, 0xa1, 0x09, 0xe7, 0x4a, 0x97, 0x71, 0x9c, - 0xe4, 0xfc, 0x0c, 0xe5, 0x25, 0xd2, 0xae, 0x1c, 0x67, 0x24, 0xe7, 0x66, 0x6c, 0x21, 0x0f, 0xd1, - 0xbd, 0x97, 0x80, 0x1f, 0x6f, 0x34, 0x5f, 0xc8, 0x13, 0x8a, 0xe3, 0x79, 0x47, 0x2a, 0x31, 0x59, - 0x5d, 0xd3, 0x77, 0x95, 0x9d, 0x75, 0xa6, 0x4b, 0x85, 0x77, 0xd8, 0xb8, 0x69, 0x76, 0x91, 0xdb, - 0x2d, 0x69, 0x4e, 0x17, 0xb9, 0xca, 0x94, 0x49, 0xca, 0xcd, 0x65, 0xe9, 0x3e, 0xbe, 0x3f, 0x97, - 0x07, 0x1c, 0x49, 0xbe, 0xf9, 0x84, 0x71, 0x74, 0xe2, 0x9a, 0x2a, 0x23, 0x29, 0x59, 0xd3, 0x93, - 0x8e, 0xf3, 0x35, 0xb9, 0x7b, 0x48, 0x65, 0xdb, 0x4a, 0x34, 0x5a, 0x1f, 0xeb, 0xb9, 0x18, 0xb2, - 0x85, 0xbe, 0xdc, 0x34, 0x5c, 0xf3, 0x03, 0xe7, 0xa5, 0xf5, 0xea, 0x29, 0x35, 0x95, 0x37, 0x48, - 0x97, 0x9a, 0x97, 0x54, 0x48, 0x49, 0x4d, 0x73, 0x7f, 0x82, 0xde, 0x2f, 0x71, 0xe3, 0xca, 0x7b, - 0xe4, 0xe7, 0x7b, 0x52, 0xa6, 0x8c, 0x60, 0x25, 0x46, 0x8b, 0x31, 0x62, 0x1f, 0x8a, 0x13, 0x3c, - 0x09, 0xcc, 0x55, 0x7e, 0x16, 0x3f, 0xf9, 0x04, 0x8a, 0x62, 0x7a, 0x8b, 0x05, 0x64, 0x11, 0xe6, - 0x76, 0x5d, 0x55, 0x3a, 0xdd, 0x8e, 0x51, 0x83, 0xe3, 0x38, 0xdb, 0x8e, 0xa3, 0x89, 0x7c, 0xcf, - 0x14, 0x5b, 0x0e, 0xb7, 0x82, 0x4e, 0x18, 0xd1, 0x66, 0xda, 0xa3, 0x64, 0x56, 0x46, 0x46, 0x46, - 0x98, 0xe4, 0x3b, 0xf3, 0x64, 0x05, 0xe7, 0x36, 0x13, 0xdc, 0xcd, 0xe5, 0x96, 0x2d, 0x06, 0xa7, - 0x9e, 0x65, 0x75, 0xad, 0xc7, 0xac, 0x53, 0x5e, 0xd9, 0xf9, 0x95, 0x52, 0x2a, 0x3a, 0x66, 0xeb, - 0xf2, 0x54, 0xf4, 0x01, 0x86, 0x01, 0x70, 0xf7, 0x5f, 0x2f, 0xcd, 0x24, 0x2f, 0x1a, 0x91, 0x4f, - 0x61, 0x54, 0x32, 0xf7, 0x56, 0x48, 0x92, 0x1b, 0x15, 0xb2, 0x08, 0x13, 0xc6, 0x2d, 0x2a, 0x65, - 0xdd, 0x64, 0xdd, 0xad, 0xea, 0xd2, 0xcf, 0x13, 0xc6, 0x6d, 0x29, 0x25, 0x25, 0xeb, 0x0e, 0x55, - 0xae, 0x94, 0x8f, 0x60, 0x4c, 0xa8, 0xb4, 0xab, 0x36, 0xf2, 0xfd, 0x6d, 0x33, 0x5a, 0x44, 0x72, - 0xa7, 0xe9, 0x46, 0x0b, 0xbe, 0xf7, 0xc8, 0xdd, 0xeb, 0xa9, 0x98, 0x34, 0xcb, 0xce, 0x3c, 0xf9, - 0x0a, 0x9f, 0x3c, 0x97, 0x0f, 0xd1, 0xd3, 0xe8, 0xa9, 0x1f, 0x3c, 0x76, 0xbd, 0xbd, 0x1e, 0x22, - 0x2f, 0x9b, 0x22, 0x93, 0x7c, 0x32, 0x76, 0xe4, 0x2b, 0x98, 0xab, 0xe5, 0x0b, 0xef, 0x29, 0xa4, - 0xfb, 0xf2, 0x52, 0x83, 0x0b, 0x18, 0x3d, 0x73, 0xd2, 0xba, 0x77, 0x15, 0xfa, 0x05, 0x4f, 0xd8, - 0x28, 0x7d, 0xf5, 0x0d, 0x3f, 0x68, 0xf6, 0x96, 0x58, 0x36, 0x03, 0x69, 0x13, 0x6c, 0x52, 0x19, - 0x5f, 0xc0, 0xb9, 0x5a, 0xae, 0xe8, 0x5e, 0x22, 0x7a, 0xed, 0x24, 0xcf, 0xa3, 0x2a, 0x4e, 0x58, - 0xef, 0xae, 0x32, 0x57, 0x70, 0x4e, 0x63, 0xeb, 0xd0, 0x66, 0x40, 0x1f, 0xd1, 0x00, 0xc3, 0xb5, - 0x7b, 0x05, 0x2a, 0x9b, 0xe4, 0xb2, 0xe5, 0x2b, 0x70, 0xa6, 0x96, 0x12, 0x95, 0xc7, 0xd2, 0xeb, - 0xfc, 0x67, 0x0a, 0x5b, 0x7a, 0xcc, 0x7a, 0xf5, 0x88, 0x12, 0x1a, 0xbb, 0x47, 0xa3, 0xed, 0x95, - 0x1e, 0x5a, 0x92, 0xf7, 0x09, 0x24, 0xe1, 0xce, 0x1d, 0xc6, 0x59, 0xd3, 0x38, 0xd3, 0x14, 0xb9, - 0x1f, 0xef, 0xa7, 0xf2, 0x2c, 0xa4, 0x67, 0xb1, 0x79, 0x12, 0xde, 0xc4, 0xb9, 0x50, 0x84, 0x2c, - 0xcf, 0xc6, 0x5b, 0x00, 0x0e, 0x89, 0x5d, 0x75, 0x5a, 0xf4, 0x72, 0x48, 0x2a, 0xdc, 0xfc, 0xe3, - 0xc3, 0x43, 0xc0, 0x2e, 0xa5, 0x42, 0xd9, 0xbb, 0x8a, 0xe0, 0x5e, 0xd0, 0x55, 0xbf, 0xf1, 0x58, - 0xf7, 0x82, 0xb2, 0xdf, 0x49, 0xf7, 0x20, 0x83, 0xed, 0xcc, 0x8b, 0x19, 0x9f, 0xfd, 0x30, 0x02, - 0xbf, 0x10, 0x10, 0xcf, 0xf8, 0x49, 0xb8, 0xf0, 0x20, 0xbd, 0x29, 0x7d, 0x8b, 0x58, 0xa0, 0x29, - 0x39, 0x57, 0x35, 0xca, 0xad, 0x88, 0x4c, 0xa6, 0x5b, 0x51, 0xaf, 0x68, 0xbe, 0x2f, 0x9f, 0xd8, - 0xb4, 0xdd, 0xc2, 0x28, 0xe8, 0x03, 0x9f, 0xf3, 0xc4, 0x81, 0xb1, 0x69, 0x54, 0xef, 0xf8, 0xad, - 0x29, 0x11, 0xf5, 0x63, 0x28, 0x5e, 0x25, 0x54, 0x4e, 0xe3, 0x62, 0x55, 0xea, 0xc1, 0x48, 0xb7, - 0x0b, 0x64, 0x1d, 0xce, 0xde, 0xa3, 0x91, 0x98, 0xe3, 0x6c, 0x1a, 0x46, 0x81, 0xdb, 0x88, 0xba, - 0x1e, 0x0c, 0x4a, 0xdb, 0x24, 0x83, 0x67, 0xe7, 0x2d, 0x26, 0xaf, 0x96, 0x2d, 0xaf, 0x2b, 0x5f, - 0x97, 0x10, 0x59, 0x71, 0xda, 0x70, 0x92, 0x2a, 0xe6, 0x0f, 0xf1, 0x61, 0x1e, 0x81, 0x93, 0xcf, - 0x5a, 0x8c, 0xb3, 0x9f, 0x08, 0x6b, 0xeb, 0x26, 0x0c, 0x71, 0xa6, 0xdc, 0x05, 0x75, 0x5c, 0xe7, - 0x21, 0x77, 0x60, 0x54, 0x85, 0xd0, 0x10, 0x03, 0x95, 0x5b, 0xaf, 0x3b, 0x30, 0xca, 0x4d, 0xab, - 0xe3, 0xb3, 0x7c, 0x00, 0xa3, 0x2a, 0xe6, 0xe6, 0xc4, 0x2b, 0xfd, 0x27, 0x30, 0xa1, 0x47, 0xdf, - 0x9c, 0x5c, 0x91, 0x1f, 0xe1, 0xf1, 0xad, 0x3c, 0x25, 0xc9, 0xe7, 0x9f, 0x49, 0x24, 0x85, 0x11, - 0x2a, 0xe5, 0x13, 0xa4, 0x04, 0xe6, 0x56, 0xff, 0x4c, 0x8a, 0x9b, 0x7c, 0x20, 0x6f, 0x32, 0x29, - 0xe6, 0x34, 0x51, 0x17, 0x9d, 0x4d, 0x72, 0x35, 0x3f, 0x0f, 0xb3, 0x9a, 0x60, 0x7b, 0x56, 0xfb, - 0x38, 0xc7, 0xcc, 0xbd, 0x55, 0x97, 0x27, 0x65, 0x03, 0x77, 0x69, 0xa9, 0x47, 0x02, 0xf3, 0x05, - 0x5d, 0xca, 0x7f, 0x57, 0x10, 0x3b, 0xe3, 0x3e, 0x5a, 0x81, 0x29, 0x6c, 0x6e, 0xf3, 0xba, 0xbc, - 0x53, 0x18, 0x9b, 0xbd, 0x69, 0x71, 0x5d, 0xd8, 0xba, 0x59, 0xd1, 0xe2, 0x7e, 0xe6, 0x4b, 0x11, - 0xb7, 0x22, 0x83, 0x18, 0x8f, 0xdf, 0xd8, 0xfc, 0x9a, 0x9d, 0xcf, 0x38, 0xd8, 0xee, 0xd9, 0x17, - 0x79, 0xe2, 0x7e, 0x01, 0x77, 0x87, 0x99, 0x59, 0xc1, 0xf2, 0x85, 0x5d, 0xd7, 0x62, 0x23, 0x32, - 0x39, 0xd5, 0xa2, 0xf7, 0x18, 0xaf, 0x88, 0x65, 0x3f, 0xa3, 0xf8, 0x6a, 0x0f, 0x29, 0x52, 0x13, - 0xaf, 0xf5, 0xa4, 0x53, 0xc7, 0xa4, 0xe7, 0xf9, 0x0a, 0x9b, 0x5d, 0x5e, 0x8f, 0x67, 0x21, 0x33, - 0x4e, 0xae, 0x55, 0x84, 0x68, 0xb6, 0x40, 0x33, 0x42, 0xb4, 0x6b, 0x1b, 0xf2, 0xd4, 0xff, 0x19, - 0x94, 0xe3, 0x00, 0x90, 0x93, 0x75, 0x42, 0x7e, 0x60, 0x22, 0x49, 0x69, 0x2a, 0x24, 0xdd, 0x9e, - 0x13, 0x9a, 0xbb, 0x92, 0xa7, 0x61, 0xfd, 0x1a, 0x8c, 0x08, 0x6c, 0x4b, 0x3c, 0x28, 0x9a, 0xf7, - 0x34, 0x69, 0x17, 0x3f, 0xac, 0xb8, 0x33, 0xf7, 0x52, 0x04, 0xa5, 0x7b, 0xfb, 0xe4, 0x82, 0x54, - 0x7c, 0x46, 0x42, 0x90, 0xd5, 0xa5, 0x7b, 0x7b, 0x1f, 0x3d, 0x96, 0x72, 0xfa, 0xf5, 0xe4, 0x1d, - 0xea, 0xc4, 0xf7, 0xc4, 0x12, 0x49, 0x04, 0xf5, 0xbb, 0xb9, 0x69, 0x54, 0xf2, 0x92, 0x53, 0x16, - 0x85, 0x0a, 0x8a, 0x2a, 0xc9, 0x22, 0x18, 0x9c, 0x99, 0x22, 0x7e, 0xe0, 0x46, 0xcf, 0x16, 0xec, - 0xd5, 0xd8, 0xad, 0xa0, 0x23, 0xa4, 0x6c, 0x90, 0x48, 0x7b, 0x95, 0x7c, 0x89, 0x53, 0x89, 0x10, - 0x5f, 0xf5, 0xfd, 0x28, 0x8c, 0x02, 0xa7, 0x5d, 0xc3, 0x87, 0x96, 0x73, 0x1b, 0x1d, 0xc7, 0x70, - 0x67, 0xb1, 0x69, 0x21, 0xa5, 0x22, 0x8f, 0x7d, 0x56, 0xe6, 0x1b, 0x75, 0xad, 0x26, 0x0b, 0xd9, - 0xc5, 0x72, 0xa9, 0xc9, 0xcc, 0xf5, 0x2f, 0x53, 0x68, 0x1d, 0x66, 0x73, 0xf2, 0x05, 0xa9, 0xd3, - 0xdb, 0xee, 0xf9, 0x84, 0xe6, 0xba, 0x17, 0x4c, 0xbe, 0x82, 0x99, 0xcc, 0x84, 0x42, 0xca, 0x03, - 0xdd, 0x2d, 0xdd, 0x50, 0x2f, 0xe1, 0x8f, 0xa1, 0xc4, 0x2f, 0x74, 0x60, 0xdc, 0xb2, 0x91, 0x5b, - 0x26, 0xbe, 0xe6, 0x93, 0x43, 0x90, 0x9c, 0xaf, 0xf3, 0xe9, 0xd4, 0x65, 0xf3, 0x69, 0x4c, 0x2a, - 0x22, 0x5f, 0x86, 0x16, 0xcf, 0xfb, 0xab, 0x0f, 0x2f, 0x0b, 0xd9, 0xed, 0x2e, 0xd1, 0x26, 0xcc, - 0xec, 0xd0, 0xc0, 0x7d, 0xf4, 0x2c, 0x29, 0x50, 0x6a, 0x26, 0x13, 0xdb, 0x4d, 0xe2, 0xe7, 0x30, - 0xbb, 0xe0, 0x1f, 0xb4, 0xc5, 0xad, 0x3d, 0x43, 0xa6, 0x3a, 0x8a, 0xcf, 0xc6, 0xf7, 0x8e, 0x65, - 0x9a, 0x53, 0xd7, 0x0a, 0x75, 0xbe, 0x05, 0xbc, 0xce, 0x7a, 0xdd, 0x0c, 0x27, 0xc8, 0x20, 0x89, - 0x2f, 0x63, 0x48, 0x53, 0x4e, 0xe7, 0xdf, 0xc2, 0x41, 0x98, 0xe0, 0xe3, 0xbe, 0x39, 0x6d, 0x10, - 0x66, 0xe1, 0xbb, 0xdf, 0x01, 0xcb, 0x90, 0xca, 0x0b, 0xcc, 0x97, 0x7a, 0x8c, 0xda, 0xae, 0xcb, - 0xb5, 0xc5, 0x7c, 0xc9, 0x3f, 0x11, 0x34, 0x9d, 0xf9, 0xcc, 0x7f, 0x66, 0x3d, 0xb5, 0xac, 0x0a, - 0xad, 0x56, 0x97, 0x2d, 0x16, 0xd1, 0xd3, 0x2a, 0x30, 0x4a, 0x74, 0xe2, 0x4f, 0xe8, 0xbc, 0xdd, - 0x66, 0xeb, 0x14, 0x33, 0x6e, 0x6a, 0xdf, 0x87, 0xf1, 0x9a, 0x5e, 0x78, 0x46, 0x21, 0xb9, 0x83, - 0x42, 0xdd, 0x02, 0xea, 0x5d, 0xf7, 0x2e, 0xb1, 0xa0, 0x6a, 0xe1, 0x39, 0x56, 0x2b, 0x72, 0x43, - 0x67, 0x8c, 0xb7, 0xe7, 0xd4, 0x2a, 0x90, 0xf5, 0x34, 0xa4, 0x0a, 0x9d, 0xc9, 0x7e, 0xae, 0xae, - 0xce, 0x5f, 0xb4, 0x49, 0xbe, 0xfc, 0x49, 0xac, 0xde, 0x4f, 0xec, 0xaa, 0x98, 0xf8, 0xae, 0x4f, - 0x87, 0xf2, 0x38, 0x9f, 0xf8, 0xb5, 0x3d, 0x3d, 0xce, 0x27, 0xf5, 0x86, 0x9f, 0x1e, 0xe7, 0x93, - 0xf1, 0x40, 0xdf, 0x12, 0xca, 0x8a, 0x9f, 0x02, 0xea, 0xe2, 0x8c, 0x50, 0x62, 0x32, 0x5e, 0x1c, - 0x7a, 0xa0, 0x27, 0xe7, 0xe0, 0x0f, 0x08, 0x75, 0xf1, 0xb5, 0x26, 0x93, 0x72, 0x24, 0x5e, 0x1c, - 0xba, 0x0b, 0x45, 0xfe, 0x96, 0x42, 0x9c, 0xd3, 0x30, 0x0e, 0xfd, 0x4b, 0x3f, 0xf1, 0xd0, 0xa5, - 0x53, 0x8b, 0xc9, 0x4c, 0x70, 0xca, 0x65, 0x96, 0x93, 0x22, 0xae, 0xcb, 0x50, 0x85, 0x38, 0xdf, - 0x9b, 0x72, 0x4c, 0xa5, 0x52, 0xc0, 0xcd, 0x9d, 0xcb, 0xc0, 0xa8, 0x2d, 0xe5, 0xb8, 0x9e, 0x1d, - 0x4e, 0x35, 0x29, 0x23, 0x65, 0xdc, 0xdc, 0xf9, 0x4c, 0x9c, 0x10, 0x14, 0xf1, 0x57, 0xa6, 0xb3, - 0x1f, 0xfd, 0x8e, 0x2f, 0x72, 0x75, 0xa1, 0x91, 0xc5, 0xdc, 0x38, 0x0e, 0xa9, 0x28, 0x95, 0xaa, - 0x87, 0x90, 0x32, 0x5e, 0x1a, 0x7f, 0x2d, 0xe3, 0xae, 0x85, 0x41, 0x11, 0x47, 0x83, 0x75, 0x7f, - 0xf6, 0x9c, 0x3c, 0x94, 0x0f, 0xd3, 0xe4, 0x94, 0xd4, 0x4b, 0x40, 0x6e, 0x0f, 0x3e, 0x94, 0x4f, - 0xd1, 0xbc, 0x6c, 0xc1, 0xbb, 0x70, 0x21, 0x71, 0x81, 0xc3, 0x14, 0x7c, 0x23, 0xfb, 0x96, 0x47, - 0xa6, 0x7a, 0xf2, 0xf7, 0xec, 0x97, 0xd3, 0x17, 0x3d, 0x12, 0xfd, 0x7e, 0xd2, 0x39, 0x6f, 0x0d, - 0x26, 0x71, 0x9a, 0x91, 0x6f, 0xe6, 0xc7, 0xb9, 0x61, 0x4c, 0x70, 0x32, 0x49, 0x51, 0x12, 0xab, - 0xee, 0x8f, 0x8f, 0x8b, 0x4b, 0xc1, 0xfc, 0x05, 0xfe, 0x39, 0xf3, 0xa6, 0x30, 0x02, 0xb3, 0x56, - 0x31, 0xf1, 0xb0, 0x3f, 0xf9, 0x08, 0x4e, 0xc7, 0x77, 0x85, 0xb9, 0x88, 0x0c, 0xb2, 0x2e, 0x8e, - 0xb2, 0xd3, 0xf1, 0x85, 0xe1, 0x93, 0xb3, 0x2f, 0xcb, 0xa5, 0x28, 0x66, 0xbf, 0x98, 0xba, 0xee, - 0x62, 0xb4, 0xe1, 0x38, 0x2b, 0x92, 0xa6, 0xdb, 0x93, 0xf6, 0x4e, 0x03, 0x3f, 0xb7, 0xec, 0xb4, - 0x87, 0xfa, 0xe7, 0xd6, 0x35, 0x35, 0xa3, 0xda, 0xfe, 0xe6, 0xc8, 0x59, 0x83, 0xab, 0x98, 0x2a, - 0x65, 0x93, 0x27, 0xc7, 0xcb, 0xa6, 0xca, 0xaf, 0x7b, 0x32, 0xc1, 0x4a, 0x0b, 0xae, 0xf4, 0xcc, - 0xfb, 0x48, 0x6e, 0x19, 0x21, 0x2e, 0xbd, 0x33, 0x44, 0x76, 0xb1, 0x3c, 0xa6, 0xb3, 0xd2, 0x27, - 0xaa, 0x75, 0xb6, 0x4b, 0x26, 0x47, 0xb5, 0xce, 0x76, 0xcd, 0xbf, 0xf8, 0x39, 0xbe, 0xf6, 0x24, - 0xd6, 0x28, 0x4c, 0x7f, 0x44, 0x3d, 0x9e, 0x14, 0xba, 0xeb, 0xb1, 0xcf, 0x15, 0xf3, 0x50, 0x34, - 0xc5, 0x88, 0x36, 0xcd, 0x25, 0x61, 0x89, 0xe5, 0x09, 0xef, 0x2d, 0xa4, 0x4b, 0x68, 0xf5, 0x25, - 0x3e, 0x00, 0x4f, 0x5c, 0xf3, 0x1c, 0x78, 0x75, 0xf1, 0x27, 0xff, 0xf5, 0x52, 0xe1, 0x27, 0x3f, - 0xbd, 0x54, 0xf8, 0x8f, 0x3f, 0xbd, 0x54, 0xf8, 0x2f, 0x3f, 0xbd, 0x54, 0xf8, 0x72, 0xfe, 0x78, - 0xa9, 0x89, 0xf9, 0xfb, 0x8c, 0xb7, 0xb8, 0xb8, 0x21, 0xfc, 0xef, 0xcd, 0xff, 0x1b, 0x00, 0x00, - 0xff, 0xff, 0x6f, 0x43, 0x95, 0x44, 0x34, 0xef, 0x00, 0x00, + // 15544 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0xbd, 0x5b, 0x6c, 0x5c, 0x49, + 0x7a, 0x18, 0xac, 0x6e, 0xde, 0x3f, 0xde, 0x5a, 0x45, 0x52, 0x6c, 0x51, 0x97, 0x96, 0x8e, 0x46, + 0x33, 0x1a, 0xed, 0xac, 0x2e, 0x9c, 0xcb, 0xce, 0x7d, 0xa6, 0x9b, 0xa4, 0x48, 0x4a, 0xbc, 0xcd, + 0x69, 0x92, 0x9a, 0x9b, 0xb7, 0xf7, 0xb0, 0xbb, 0x44, 0x1e, 0xab, 0xd9, 0xa7, 0xf7, 0x9c, 0xd3, + 0xd2, 0x68, 0xfd, 0xaf, 0x7f, 0xd8, 0xfe, 0xff, 0xc4, 0x41, 0x90, 0xc4, 0x06, 0xe2, 0xc0, 0x86, + 0x03, 0x38, 0x01, 0x1c, 0x20, 0x08, 0x10, 0xc0, 0x2f, 0x81, 0x9f, 0xfc, 0x90, 0xa7, 0x6c, 0x0c, + 0x04, 0x89, 0x61, 0xfb, 0x25, 0x40, 0xe8, 0x64, 0x01, 0xbf, 0x10, 0xc9, 0x83, 0x11, 0x24, 0x40, + 0x36, 0x30, 0x10, 0xd4, 0x57, 0x97, 0x53, 0x75, 0x2e, 0xdd, 0xa4, 0xa4, 0x59, 0xe7, 0x45, 0x62, + 0x7f, 0xb7, 0xaa, 0xfa, 0xaa, 0x4e, 0x55, 0x7d, 0x5f, 0x7d, 0xf5, 0x15, 0xdc, 0x0a, 0x69, 0x93, + 0xb6, 0x3d, 0x3f, 0xbc, 0xdd, 0xa4, 0xfb, 0x4e, 0xfd, 0xd9, 0xed, 0x7a, 0xd3, 0xa5, 0xad, 0xf0, + 0x76, 0xdb, 0xf7, 0x42, 0xef, 0xb6, 0xd3, 0x09, 0x0f, 0x02, 0xea, 0x3f, 0x71, 0xeb, 0xf4, 0x16, + 0x42, 0xc8, 0x00, 0xfe, 0x37, 0x37, 0xbd, 0xef, 0xed, 0x7b, 0x9c, 0x86, 0xfd, 0xc5, 0x91, 0x73, + 0x17, 0xf6, 0x3d, 0x6f, 0xbf, 0x49, 0x39, 0xf3, 0x5e, 0xe7, 0xd1, 0x6d, 0x7a, 0xd8, 0x0e, 0x9f, + 0x09, 0x64, 0x29, 0x8e, 0x0c, 0xdd, 0x43, 0x1a, 0x84, 0xce, 0x61, 0x5b, 0x10, 0xbc, 0xae, 0xaa, + 0xe2, 0x84, 0x21, 0xc3, 0x84, 0xae, 0xd7, 0xba, 0xfd, 0xe4, 0xae, 0xfe, 0x53, 0x90, 0xde, 0xe8, + 0x5a, 0xeb, 0x3a, 0xf5, 0xc3, 0xe0, 0x44, 0x94, 0xf4, 0x09, 0x6d, 0x85, 0x89, 0xe2, 0x05, 0x65, + 0xf8, 0xac, 0x4d, 0x03, 0x4e, 0x22, 0xff, 0x13, 0xa4, 0x57, 0xd3, 0x49, 0xf1, 0x5f, 0x41, 0xf2, + 0xdd, 0x74, 0x92, 0xa7, 0x74, 0x8f, 0xe9, 0xb4, 0xa5, 0xfe, 0xe8, 0x41, 0xee, 0x3b, 0xed, 0x36, + 0xf5, 0xa3, 0x3f, 0x04, 0xf9, 0x79, 0x45, 0x7e, 0xf8, 0xc8, 0x61, 0x2a, 0x3a, 0x7c, 0xe4, 0x24, + 0x9a, 0xd1, 0x09, 0x9c, 0x7d, 0x2a, 0xaa, 0xff, 0xe4, 0xae, 0xfe, 0x93, 0x93, 0x5a, 0xbf, 0x97, + 0x83, 0x81, 0x87, 0x4e, 0x58, 0x3f, 0x20, 0x9f, 0xc0, 0xc0, 0x03, 0xb7, 0xd5, 0x08, 0x8a, 0xb9, + 0x2b, 0x7d, 0x37, 0x46, 0xe7, 0x0b, 0xb7, 0x78, 0x53, 0x10, 0xc9, 0x10, 0x95, 0xd9, 0x9f, 0x1c, + 0x95, 0xce, 0x1c, 0x1f, 0x95, 0x26, 0x1f, 0x33, 0xb2, 0x37, 0xbc, 0x43, 0x37, 0xc4, 0xbe, 0xb5, + 0x39, 0x1f, 0xd9, 0x81, 0xa9, 0x72, 0xb3, 0xe9, 0x3d, 0xdd, 0x72, 0xfc, 0xd0, 0x75, 0x9a, 0xd5, + 0x4e, 0xbd, 0x4e, 0x83, 0xa0, 0x98, 0xbf, 0x92, 0xbb, 0x31, 0x5c, 0xb9, 0x76, 0x7c, 0x54, 0x2a, + 0x39, 0x0c, 0x5d, 0x6b, 0x73, 0x7c, 0x2d, 0xe0, 0x04, 0x9a, 0xa0, 0x34, 0x7e, 0xeb, 0x8f, 0x07, + 0xa1, 0xb0, 0xe2, 0x05, 0xe1, 0x02, 0xeb, 0x51, 0x9b, 0xfe, 0xb0, 0x43, 0x83, 0x90, 0x5c, 0x83, + 0x41, 0x06, 0x5b, 0x5d, 0x2c, 0xe6, 0xae, 0xe4, 0x6e, 0x8c, 0x54, 0x46, 0x8f, 0x8f, 0x4a, 0x43, + 0x07, 0x5e, 0x10, 0xd6, 0xdc, 0x86, 0x2d, 0x50, 0xe4, 0x75, 0x18, 0xde, 0xf0, 0x1a, 0x74, 0xc3, + 0x39, 0xa4, 0x58, 0x8b, 0x91, 0xca, 0xf8, 0xf1, 0x51, 0x69, 0xa4, 0xe5, 0x35, 0x68, 0xad, 0xe5, + 0x1c, 0x52, 0x5b, 0xa1, 0xc9, 0x2e, 0xf4, 0xdb, 0x5e, 0x93, 0x16, 0xfb, 0x90, 0xac, 0x72, 0x7c, + 0x54, 0xea, 0xf7, 0xbd, 0x26, 0xfd, 0xd9, 0x51, 0xe9, 0x9d, 0x7d, 0x37, 0x3c, 0xe8, 0xec, 0xdd, + 0xaa, 0x7b, 0x87, 0xb7, 0xf7, 0x7d, 0xe7, 0x89, 0xcb, 0x07, 0xa1, 0xd3, 0xbc, 0x1d, 0x0d, 0xd5, + 0xb6, 0x2b, 0xfa, 0xbd, 0xfa, 0x2c, 0x08, 0xe9, 0x21, 0x93, 0x64, 0xa3, 0x3c, 0xf2, 0x10, 0xa6, + 0xcb, 0x8d, 0x86, 0xcb, 0x39, 0xb6, 0x7c, 0xb7, 0x55, 0x77, 0xdb, 0x4e, 0x33, 0x28, 0xf6, 0x5f, + 0xe9, 0xbb, 0x31, 0x22, 0x94, 0xa2, 0xf0, 0xb5, 0xb6, 0x22, 0xd0, 0x94, 0x92, 0x2a, 0x80, 0xbc, + 0x09, 0xc3, 0x8b, 0x1b, 0x55, 0x56, 0xf7, 0xa0, 0x38, 0x80, 0xc2, 0x66, 0x8f, 0x8f, 0x4a, 0x53, + 0x8d, 0x56, 0x80, 0x4d, 0xd3, 0x05, 0x28, 0x42, 0xf2, 0x0e, 0x8c, 0x6d, 0x75, 0xf6, 0x9a, 0x6e, + 0x7d, 0x7b, 0xad, 0xfa, 0x80, 0x3e, 0x2b, 0x0e, 0x5e, 0xc9, 0xdd, 0x18, 0xab, 0x90, 0xe3, 0xa3, + 0xd2, 0x44, 0x1b, 0xe1, 0xb5, 0xb0, 0x19, 0xd4, 0x1e, 0xd3, 0x67, 0xb6, 0x41, 0x17, 0xf1, 0x55, + 0xab, 0x2b, 0x8c, 0x6f, 0x28, 0xc1, 0x17, 0x04, 0x07, 0x3a, 0x1f, 0xa7, 0x23, 0xb7, 0x01, 0x6c, + 0x7a, 0xe8, 0x85, 0xb4, 0xdc, 0x68, 0xf8, 0xc5, 0x61, 0xd4, 0xed, 0xe4, 0xf1, 0x51, 0x69, 0xd4, + 0x47, 0x68, 0xcd, 0x69, 0x34, 0x7c, 0x5b, 0x23, 0x21, 0x0b, 0x30, 0x6c, 0x7b, 0x5c, 0xc1, 0xc5, + 0x91, 0x2b, 0xb9, 0x1b, 0xa3, 0xf3, 0x93, 0x62, 0x18, 0x4a, 0x70, 0xe5, 0xdc, 0xf1, 0x51, 0x89, + 0xf8, 0xe2, 0x97, 0xde, 0x4a, 0x49, 0x41, 0x4a, 0x30, 0xb4, 0xe1, 0x2d, 0x38, 0xf5, 0x03, 0x5a, + 0x04, 0x1c, 0x7b, 0x03, 0xc7, 0x47, 0xa5, 0xdc, 0x77, 0x6d, 0x09, 0x25, 0x4f, 0x60, 0x34, 0xea, + 0xa8, 0xa0, 0x38, 0x8a, 0xea, 0xdb, 0x3e, 0x3e, 0x2a, 0x9d, 0x0b, 0x10, 0x5c, 0x63, 0x5d, 0xaf, + 0x69, 0xf0, 0x05, 0x46, 0x81, 0x5e, 0x10, 0xf9, 0x1a, 0x66, 0xa2, 0x9f, 0xe5, 0x20, 0xa0, 0x3e, + 0x93, 0xb1, 0xba, 0x58, 0x1c, 0x47, 0xcd, 0xbc, 0x7a, 0x7c, 0x54, 0xb2, 0xb4, 0x1a, 0xd4, 0x1c, + 0x49, 0x52, 0x73, 0x1b, 0x5a, 0x4b, 0xd3, 0x85, 0xdc, 0xef, 0x1f, 0x1e, 0x2b, 0x8c, 0xdb, 0x97, + 0x76, 0x5a, 0x41, 0xe8, 0xec, 0x35, 0x69, 0x2a, 0x91, 0xf5, 0xd7, 0x39, 0x20, 0x9b, 0x6d, 0xda, + 0xaa, 0x56, 0x57, 0xd8, 0xf7, 0x24, 0x3f, 0xa7, 0x37, 0x60, 0x84, 0x77, 0x1c, 0xeb, 0xdd, 0x3c, + 0xf6, 0xee, 0xc4, 0xf1, 0x51, 0x09, 0x44, 0xef, 0xb2, 0x9e, 0x8d, 0x08, 0xc8, 0x75, 0xe8, 0xdb, + 0xde, 0x5e, 0xc3, 0x6f, 0xa5, 0xaf, 0x32, 0x75, 0x7c, 0x54, 0xea, 0x0b, 0xc3, 0xe6, 0xcf, 0x8e, + 0x4a, 0xc3, 0x8b, 0x1d, 0x1f, 0xd5, 0x62, 0x33, 0x3c, 0xb9, 0x0e, 0x43, 0x0b, 0xcd, 0x4e, 0x10, + 0x52, 0xbf, 0xd8, 0x1f, 0x7d, 0xa4, 0x75, 0x0e, 0xb2, 0x25, 0x8e, 0x7c, 0x07, 0xfa, 0x77, 0x02, + 0xea, 0x17, 0x07, 0xb0, 0xbf, 0xc7, 0x45, 0x7f, 0x33, 0xd0, 0xee, 0x7c, 0x65, 0x98, 0x7d, 0x89, + 0x9d, 0x80, 0xfa, 0x36, 0x12, 0x91, 0x5b, 0x30, 0xc0, 0x3b, 0x6d, 0x10, 0x27, 0xa9, 0x71, 0x35, + 0x3a, 0x9a, 0x74, 0xf7, 0x9d, 0xca, 0xc8, 0xf1, 0x51, 0x69, 0x00, 0x3b, 0xcf, 0xe6, 0x64, 0xf7, + 0xfb, 0x87, 0x73, 0x85, 0xbc, 0x3d, 0xcc, 0x78, 0xd9, 0x67, 0x61, 0x7d, 0x07, 0x46, 0xb5, 0xe6, + 0x93, 0x8b, 0xd0, 0xcf, 0xfe, 0xc7, 0x49, 0x64, 0x8c, 0x17, 0xc6, 0x16, 0x0e, 0x1b, 0xa1, 0xd6, + 0x3f, 0x99, 0x82, 0x02, 0xe3, 0x34, 0x66, 0x9e, 0x5b, 0xba, 0xaa, 0x38, 0x5f, 0xc1, 0x54, 0x55, + 0x31, 0xa7, 0x2b, 0xeb, 0x06, 0xa8, 0xd2, 0xc5, 0x24, 0x34, 0x76, 0x7c, 0x54, 0x1a, 0xee, 0x08, + 0x58, 0x54, 0x37, 0x52, 0x85, 0xa1, 0xa5, 0x6f, 0xda, 0xae, 0x4f, 0x03, 0x54, 0xed, 0xe8, 0xfc, + 0xdc, 0x2d, 0xbe, 0x5c, 0xde, 0x92, 0xcb, 0xe5, 0xad, 0x6d, 0xb9, 0x5c, 0x56, 0x2e, 0x89, 0xc9, + 0xf8, 0x2c, 0xe5, 0x2c, 0xd1, 0xf8, 0xf8, 0x8d, 0xbf, 0x28, 0xe5, 0x6c, 0x29, 0x89, 0xbc, 0x01, + 0x83, 0xf7, 0x3c, 0xff, 0xd0, 0x09, 0x45, 0x1f, 0x4c, 0x1f, 0x1f, 0x95, 0x0a, 0x8f, 0x10, 0xa2, + 0x0d, 0x29, 0x41, 0x43, 0xee, 0xc1, 0x84, 0xed, 0x75, 0x42, 0xba, 0xed, 0xc9, 0x9e, 0x1b, 0x40, + 0xae, 0xcb, 0xc7, 0x47, 0xa5, 0x39, 0x9f, 0x61, 0x6a, 0xa1, 0x57, 0x13, 0x5d, 0xa8, 0xf1, 0xc7, + 0xb8, 0xc8, 0x12, 0x4c, 0x94, 0x71, 0xf6, 0x16, 0x5a, 0xe3, 0xfd, 0x35, 0x52, 0xb9, 0x74, 0x7c, + 0x54, 0x3a, 0xef, 0x20, 0xa6, 0xe6, 0x0b, 0x94, 0x2e, 0xc6, 0x64, 0x22, 0x1b, 0x70, 0xf6, 0x41, + 0x67, 0x8f, 0xfa, 0x2d, 0x1a, 0xd2, 0x40, 0xd6, 0x68, 0x08, 0x6b, 0x74, 0xe5, 0xf8, 0xa8, 0x74, + 0xf1, 0xb1, 0x42, 0xa6, 0xd4, 0x29, 0xc9, 0x4a, 0x28, 0x4c, 0x8a, 0x8a, 0x2e, 0x3a, 0xa1, 0xb3, + 0xe7, 0x04, 0x14, 0x27, 0xa5, 0xd1, 0xf9, 0x73, 0x5c, 0xc5, 0xb7, 0x62, 0xd8, 0xca, 0x35, 0xa1, + 0xe5, 0x0b, 0xaa, 0xed, 0x0d, 0x81, 0xd2, 0x0a, 0x8a, 0xcb, 0x64, 0x73, 0xb3, 0x5a, 0x77, 0x46, + 0xb0, 0xb6, 0x38, 0x37, 0xab, 0x75, 0x47, 0x9f, 0xb5, 0xd4, 0x0a, 0xb4, 0x06, 0x03, 0x3b, 0x6c, + 0x75, 0xc6, 0x39, 0x6b, 0x62, 0xfe, 0xaa, 0xa8, 0x51, 0x7c, 0xfc, 0xdd, 0x62, 0x3f, 0x90, 0x10, + 0xbf, 0xbc, 0x49, 0x5c, 0xd1, 0xf5, 0xb5, 0x18, 0x71, 0xe4, 0x33, 0x00, 0x51, 0xab, 0x72, 0xbb, + 0x5d, 0x1c, 0xc5, 0x46, 0x9e, 0x35, 0x1b, 0x59, 0x6e, 0xb7, 0x2b, 0x97, 0x45, 0xfb, 0xce, 0xa9, + 0xf6, 0x39, 0xed, 0xb6, 0x26, 0x4d, 0x13, 0x42, 0x3e, 0x81, 0x31, 0x9c, 0xd2, 0x64, 0x8f, 0x8e, + 0x61, 0x8f, 0x5e, 0x38, 0x3e, 0x2a, 0xcd, 0xe2, 0x6c, 0x95, 0xd2, 0x9f, 0x06, 0x03, 0xf9, 0x65, + 0x98, 0x11, 0xe2, 0x1e, 0xba, 0xad, 0x86, 0xf7, 0x34, 0x58, 0xa4, 0xc1, 0xe3, 0xd0, 0x6b, 0xe3, + 0xf4, 0x37, 0x3a, 0x7f, 0xd1, 0xac, 0x9e, 0x49, 0x53, 0xb9, 0x29, 0x6a, 0x6a, 0xa9, 0x9a, 0x3e, + 0xe5, 0x04, 0xb5, 0x06, 0xa7, 0xd0, 0x27, 0xc8, 0x54, 0x11, 0x64, 0x15, 0x26, 0x77, 0x02, 0x6a, + 0xb4, 0x61, 0x02, 0xd7, 0x87, 0x12, 0xeb, 0xe1, 0x4e, 0x40, 0x6b, 0x59, 0xed, 0x88, 0xf3, 0x11, + 0x1b, 0xc8, 0xa2, 0xef, 0xb5, 0x63, 0x63, 0x7c, 0x12, 0x35, 0x62, 0x1d, 0x1f, 0x95, 0x2e, 0x37, + 0x7c, 0xaf, 0x5d, 0xcb, 0x1e, 0xe8, 0x29, 0xdc, 0xe4, 0xfb, 0x70, 0x6e, 0xc1, 0x6b, 0xb5, 0x68, + 0x9d, 0xcd, 0xa0, 0x8b, 0xae, 0xb3, 0xdf, 0xf2, 0x82, 0xd0, 0xad, 0xaf, 0x2e, 0x16, 0x0b, 0xd1, + 0xf2, 0x50, 0x57, 0x14, 0xb5, 0x86, 0x22, 0x31, 0x97, 0x87, 0x0c, 0x29, 0xe4, 0x2b, 0x18, 0x17, + 0x65, 0x51, 0x1f, 0x87, 0xe6, 0xd9, 0xee, 0x03, 0x4d, 0x11, 0xf3, 0x85, 0xde, 0x97, 0x3f, 0xf9, + 0xd6, 0xc9, 0x94, 0x45, 0xbe, 0x86, 0xd1, 0xf5, 0x7b, 0x65, 0x9b, 0x06, 0x6d, 0xaf, 0x15, 0xd0, + 0x22, 0xc1, 0x1e, 0xbd, 0x2c, 0x44, 0xaf, 0xdf, 0x2b, 0x97, 0x3b, 0xe1, 0x01, 0x6d, 0x85, 0x6e, + 0xdd, 0x09, 0xa9, 0xa4, 0xaa, 0xcc, 0xb1, 0x91, 0x77, 0xf8, 0xc8, 0xa9, 0xf9, 0x02, 0xa2, 0xb5, + 0x42, 0x17, 0x47, 0xe6, 0x60, 0xb8, 0x5a, 0x5d, 0x59, 0xf3, 0xf6, 0xdd, 0x56, 0x71, 0x8a, 0x29, + 0xc3, 0x56, 0xbf, 0xc9, 0x23, 0x98, 0xd1, 0x6c, 0x83, 0x1a, 0xfb, 0x9f, 0x1e, 0xd2, 0x56, 0x58, + 0x9c, 0xc6, 0x3a, 0x7c, 0x57, 0x19, 0x37, 0xb7, 0x74, 0x13, 0xe2, 0xc9, 0xdd, 0x5b, 0xe5, 0xe8, + 0x67, 0x55, 0x32, 0x55, 0xf2, 0xc5, 0x9c, 0x3d, 0xed, 0xa4, 0x60, 0xc8, 0x36, 0x0c, 0x6d, 0x75, + 0xfc, 0xb6, 0x17, 0xd0, 0xe2, 0x0c, 0x2a, 0xee, 0x5a, 0xb7, 0x2f, 0x54, 0x90, 0x56, 0x66, 0xd8, + 0x14, 0xdd, 0xe6, 0x3f, 0xb4, 0xd6, 0x49, 0x51, 0xe4, 0x53, 0x18, 0xab, 0x56, 0x57, 0xa2, 0x05, + 0xe5, 0x1c, 0x2e, 0x28, 0x17, 0x8f, 0x8f, 0x4a, 0x45, 0xb6, 0xa5, 0x8a, 0x16, 0x15, 0xfd, 0xab, + 0xd2, 0x39, 0x98, 0x84, 0xed, 0xb5, 0x6a, 0x24, 0x61, 0x36, 0x92, 0xc0, 0x36, 0x73, 0xe9, 0x12, + 0x74, 0x0e, 0xf2, 0x2f, 0x73, 0x70, 0x45, 0x17, 0x99, 0xa6, 0x98, 0xe2, 0xf9, 0xe7, 0xd1, 0xe6, + 0xfc, 0xf1, 0x51, 0xe9, 0x96, 0xd9, 0x8e, 0x5a, 0x6a, 0x67, 0x69, 0x75, 0xeb, 0x59, 0x15, 0xac, + 0xaf, 0xde, 0x80, 0xd4, 0xfa, 0xce, 0x3d, 0x77, 0x7d, 0x4d, 0xad, 0xf5, 0xae, 0x6f, 0xaf, 0xaa, + 0x58, 0x9f, 0xc3, 0x88, 0x9a, 0xb4, 0xc9, 0x10, 0xf4, 0x95, 0x9b, 0xcd, 0xc2, 0x19, 0xf6, 0x47, + 0xb5, 0xba, 0x52, 0xc8, 0x91, 0x09, 0x80, 0x68, 0xa5, 0x2a, 0xe4, 0xc9, 0x18, 0x0c, 0xcb, 0x95, + 0xa4, 0xd0, 0x87, 0xf4, 0xed, 0x76, 0xa1, 0x9f, 0x10, 0x98, 0x30, 0xe7, 0xb3, 0xc2, 0x80, 0xf5, + 0x9b, 0x39, 0x18, 0x51, 0xdf, 0x21, 0x99, 0x84, 0xd1, 0x9d, 0x8d, 0xea, 0xd6, 0xd2, 0xc2, 0xea, + 0xbd, 0xd5, 0xa5, 0xc5, 0xc2, 0x19, 0x72, 0x09, 0xce, 0x6f, 0x57, 0x57, 0x6a, 0x8b, 0x95, 0xda, + 0xda, 0xe6, 0x42, 0x79, 0xad, 0xb6, 0x65, 0x6f, 0x7e, 0xfe, 0x45, 0x6d, 0x7b, 0x67, 0x63, 0x63, + 0x69, 0xad, 0x90, 0x23, 0x45, 0x98, 0x66, 0xe8, 0x07, 0x3b, 0x95, 0x25, 0x9d, 0xa0, 0x90, 0x27, + 0x57, 0xe1, 0x52, 0x1a, 0xa6, 0xb6, 0xb2, 0x54, 0x5e, 0x5c, 0x5b, 0xaa, 0x56, 0x0b, 0x7d, 0x64, + 0x16, 0xa6, 0x18, 0x49, 0x79, 0x6b, 0xcb, 0xe0, 0xed, 0xb7, 0x9a, 0x30, 0xaa, 0x7d, 0x00, 0xe4, + 0x22, 0x14, 0x17, 0x96, 0xec, 0xed, 0xda, 0xd6, 0x8e, 0xbd, 0xb5, 0x59, 0x5d, 0xaa, 0x99, 0x35, + 0x8c, 0x63, 0xd7, 0x36, 0x97, 0x57, 0x37, 0x6a, 0x0c, 0x54, 0x2d, 0xe4, 0x58, 0x35, 0x0c, 0x6c, + 0x75, 0x75, 0x63, 0x79, 0x6d, 0xa9, 0xb6, 0x53, 0x5d, 0x12, 0x24, 0x79, 0xeb, 0x57, 0xf3, 0x89, + 0x25, 0x9d, 0xcc, 0xc3, 0x68, 0x95, 0xfb, 0x2b, 0x70, 0x9a, 0xe3, 0x06, 0x22, 0xdb, 0xa3, 0x8d, + 0x09, 0x37, 0x06, 0x9f, 0xc1, 0x74, 0x22, 0xb6, 0x4b, 0xdb, 0x62, 0x5f, 0x73, 0xdd, 0x6b, 0xea, + 0xbb, 0xb4, 0xb6, 0x80, 0xd9, 0x0a, 0x4b, 0xe6, 0xb5, 0xfd, 0x1c, 0xb7, 0x16, 0xd1, 0x22, 0x91, + 0xfb, 0x39, 0x7d, 0x6d, 0x57, 0x3b, 0xbb, 0xf9, 0xa8, 0x4b, 0xc5, 0x36, 0x0c, 0x79, 0x52, 0xf6, + 0x12, 0x8a, 0x8e, 0xbc, 0x2e, 0x77, 0xba, 0xdc, 0xba, 0xc3, 0xc5, 0x3e, 0x66, 0x97, 0x88, 0x4d, + 0xae, 0xd5, 0xc9, 0x58, 0x58, 0xc9, 0x07, 0xf1, 0x31, 0x23, 0x94, 0x81, 0xc2, 0x62, 0xeb, 0xa7, + 0x1d, 0x23, 0x25, 0x25, 0x18, 0xe0, 0x33, 0x2e, 0xd7, 0x07, 0xee, 0xad, 0x9b, 0x0c, 0x60, 0x73, + 0xb8, 0xf5, 0x27, 0x7d, 0xfa, 0x26, 0x83, 0xed, 0xa5, 0x35, 0x7d, 0xe3, 0x5e, 0x1a, 0xf5, 0x8c, + 0x50, 0x66, 0x0a, 0xf2, 0xaf, 0x04, 0x4d, 0xc1, 0xbe, 0xc8, 0x14, 0x14, 0x9f, 0x1a, 0x37, 0x05, + 0x23, 0x12, 0xd6, 0x8b, 0x62, 0xdb, 0x86, 0x52, 0xfb, 0xa3, 0x5e, 0x14, 0x5b, 0x3d, 0xd1, 0x8b, + 0x1a, 0x11, 0x79, 0x1f, 0xa0, 0xfc, 0xb0, 0x8a, 0x36, 0x8f, 0xbd, 0x21, 0xb6, 0xae, 0xb8, 0xc8, + 0x38, 0x4f, 0x03, 0x61, 0x52, 0xf9, 0xba, 0xcd, 0xa8, 0x51, 0x93, 0x0a, 0x8c, 0x97, 0x7f, 0xd4, + 0xf1, 0xe9, 0x6a, 0x83, 0xad, 0x53, 0x21, 0x37, 0x8e, 0x47, 0xf8, 0x44, 0xea, 0x30, 0x44, 0xcd, + 0x15, 0x18, 0x4d, 0x80, 0xc9, 0x42, 0x36, 0xe1, 0xec, 0xf2, 0xc2, 0x96, 0x18, 0x57, 0xe5, 0x7a, + 0xdd, 0xeb, 0xb4, 0x42, 0xb1, 0x5f, 0xbd, 0x7a, 0x7c, 0x54, 0xba, 0xb4, 0x5f, 0x6f, 0xd7, 0xe4, + 0x18, 0x74, 0x38, 0x5a, 0xdf, 0xb0, 0x26, 0x78, 0xc9, 0x35, 0xe8, 0xdb, 0xb1, 0x57, 0x85, 0xe5, + 0x7c, 0xf6, 0xf8, 0xa8, 0x34, 0xde, 0xf1, 0x5d, 0x8d, 0x85, 0x61, 0xc9, 0x7b, 0x00, 0xdb, 0x8e, + 0xbf, 0x4f, 0xc3, 0x2d, 0xcf, 0x0f, 0x71, 0xc3, 0x39, 0x5e, 0x39, 0x7f, 0x7c, 0x54, 0x9a, 0x09, + 0x11, 0x5a, 0x63, 0xd3, 0x9f, 0xde, 0xe8, 0x88, 0xf8, 0x7e, 0xff, 0x70, 0xbe, 0xd0, 0x67, 0x8f, + 0x54, 0x69, 0x10, 0x70, 0xfb, 0xb0, 0x09, 0x13, 0xcb, 0x34, 0x64, 0x03, 0x57, 0xda, 0x3b, 0xdd, + 0xbb, 0xf5, 0x43, 0x18, 0x7d, 0xe8, 0x86, 0x07, 0x55, 0x5a, 0xf7, 0x69, 0x28, 0x7d, 0x3d, 0xa8, + 0xf2, 0xa7, 0x6e, 0x78, 0x50, 0x0b, 0x38, 0x5c, 0x5f, 0xd7, 0x35, 0x72, 0x6b, 0x09, 0x26, 0x45, + 0x69, 0xca, 0xbc, 0x9a, 0x37, 0x05, 0xe6, 0x50, 0x20, 0x76, 0xbb, 0x2e, 0xd0, 0x14, 0xf3, 0xaf, + 0xf2, 0x30, 0xb3, 0x70, 0xe0, 0xb4, 0xf6, 0xe9, 0x96, 0x13, 0x04, 0x4f, 0x3d, 0xbf, 0xa1, 0x55, + 0x1e, 0x6d, 0xcb, 0x44, 0xe5, 0xd1, 0x98, 0x9c, 0x87, 0xd1, 0xcd, 0x66, 0x43, 0xf2, 0x08, 0xbb, + 0x17, 0xcb, 0xf2, 0x9a, 0x8d, 0x5a, 0x5b, 0xca, 0xd2, 0x89, 0x18, 0xcf, 0x06, 0x7d, 0xaa, 0x78, + 0xfa, 0x22, 0x9e, 0x16, 0x7d, 0xaa, 0xf1, 0x68, 0x44, 0x64, 0x09, 0xce, 0x56, 0x69, 0xdd, 0x6b, + 0x35, 0xee, 0x39, 0xf5, 0xd0, 0xf3, 0xb7, 0xbd, 0xc7, 0xb4, 0x25, 0x06, 0x34, 0x1a, 0x06, 0x01, + 0x22, 0x6b, 0x8f, 0x10, 0x5b, 0x0b, 0x19, 0xda, 0x4e, 0x72, 0x90, 0x4d, 0x18, 0x7e, 0x28, 0x3c, + 0x86, 0xc2, 0x58, 0xbe, 0x7e, 0x4b, 0xb9, 0x10, 0x17, 0x7c, 0x8a, 0xa3, 0xd0, 0x69, 0x2a, 0x73, + 0x5f, 0xed, 0xb3, 0x70, 0x2a, 0x93, 0x94, 0xb6, 0x12, 0x62, 0xed, 0xc0, 0xf8, 0x56, 0xb3, 0xb3, + 0xef, 0xb6, 0xd8, 0xa4, 0x53, 0xa5, 0x3f, 0x24, 0x8b, 0x00, 0x11, 0x40, 0xf8, 0x01, 0xa7, 0x84, + 0x89, 0x1d, 0x21, 0x76, 0xdf, 0x14, 0x5f, 0x2e, 0x42, 0xd0, 0x22, 0xb2, 0x35, 0x3e, 0xeb, 0x7f, + 0xf5, 0x01, 0x11, 0x1d, 0x80, 0x8b, 0x60, 0x95, 0x86, 0x6c, 0x79, 0x3a, 0x07, 0x79, 0xe5, 0xae, + 0x1b, 0x3c, 0x3e, 0x2a, 0xe5, 0xdd, 0x86, 0x9d, 0x5f, 0x5d, 0x24, 0x6f, 0xc1, 0x00, 0x92, 0xa1, + 0xfe, 0x27, 0x54, 0x79, 0xba, 0x04, 0x3e, 0xf9, 0xe0, 0xea, 0x6b, 0x73, 0x62, 0xf2, 0x36, 0x8c, + 0x2c, 0xd2, 0x26, 0xdd, 0x77, 0x42, 0x4f, 0x4e, 0x27, 0xdc, 0x01, 0x26, 0x81, 0xda, 0x98, 0x8b, + 0x28, 0x99, 0x39, 0x6c, 0x53, 0x27, 0xf0, 0x5a, 0xba, 0x39, 0xec, 0x23, 0x44, 0x37, 0x87, 0x39, + 0x0d, 0xf9, 0xad, 0x1c, 0x8c, 0x96, 0x5b, 0x2d, 0xe1, 0x58, 0x0a, 0x84, 0xd6, 0x67, 0x6e, 0x29, + 0x4f, 0xec, 0x9a, 0xb3, 0x47, 0x9b, 0xbb, 0x4e, 0xb3, 0x43, 0x83, 0xca, 0xd7, 0xcc, 0x42, 0xf9, + 0x8f, 0x47, 0xa5, 0x0f, 0x4e, 0xe1, 0x2a, 0x8a, 0x7c, 0xba, 0xdb, 0xbe, 0xe3, 0x86, 0x01, 0xfb, + 0x6a, 0x9d, 0xa8, 0x40, 0xfd, 0xbb, 0xd1, 0xea, 0x11, 0xad, 0x0d, 0x83, 0xbd, 0xd6, 0x06, 0x72, + 0x08, 0x93, 0xe5, 0x20, 0xe8, 0x1c, 0xd2, 0x6a, 0xe8, 0xf8, 0xe1, 0xb6, 0x7b, 0x48, 0x71, 0x42, + 0xea, 0xee, 0x5c, 0x78, 0xed, 0x27, 0x47, 0xa5, 0x1c, 0x33, 0x8a, 0x1c, 0x64, 0x65, 0xfb, 0x1e, + 0x3f, 0xac, 0x85, 0xae, 0xbe, 0xbc, 0xa1, 0x9b, 0x21, 0x2e, 0xdb, 0xba, 0xa6, 0x36, 0x24, 0xab, + 0x8b, 0x59, 0x3d, 0x6e, 0x2d, 0xc0, 0xc5, 0x65, 0x1a, 0xda, 0x34, 0xa0, 0xa1, 0xfc, 0x46, 0x70, + 0x84, 0x47, 0xce, 0xdd, 0x21, 0xfc, 0xad, 0x98, 0xb1, 0xfb, 0xf9, 0x77, 0x21, 0x31, 0xd6, 0xff, + 0x97, 0x83, 0xd2, 0x82, 0x4f, 0xb9, 0x3d, 0x91, 0x21, 0xa8, 0xfb, 0xdc, 0x75, 0x11, 0xfa, 0xb7, + 0x9f, 0xb5, 0xa5, 0x57, 0x06, 0xb1, 0xac, 0x53, 0x6c, 0x84, 0x9e, 0xd0, 0xc9, 0x65, 0x3d, 0x82, + 0x19, 0x9b, 0xb6, 0xe8, 0x53, 0x67, 0xaf, 0x49, 0x0d, 0x3f, 0x51, 0x09, 0x06, 0xf8, 0x87, 0x9e, + 0x68, 0x02, 0x87, 0x9f, 0xce, 0xe7, 0x66, 0x8d, 0xc3, 0xe8, 0x96, 0xdb, 0xda, 0x17, 0xd2, 0xad, + 0xbf, 0xec, 0x87, 0x31, 0xfe, 0x5b, 0x98, 0x48, 0xb1, 0xe5, 0x32, 0x77, 0x92, 0xe5, 0xf2, 0x5d, + 0x18, 0x67, 0xeb, 0x0d, 0xf5, 0x77, 0xa9, 0xcf, 0xe6, 0x7f, 0xa1, 0x09, 0x34, 0xf7, 0x02, 0x44, + 0xd4, 0x9e, 0x70, 0x8c, 0x6d, 0x12, 0x92, 0x35, 0x98, 0xe0, 0x80, 0x7b, 0xd4, 0x09, 0x3b, 0x91, + 0xc7, 0x6a, 0x52, 0xd8, 0x44, 0x12, 0xcc, 0x87, 0xa6, 0x90, 0xf5, 0x48, 0x00, 0xed, 0x18, 0x2f, + 0xf9, 0x04, 0x26, 0xb7, 0x7c, 0xef, 0x9b, 0x67, 0xda, 0x06, 0x81, 0x7f, 0x9d, 0xdc, 0x7a, 0x62, + 0xa8, 0x9a, 0xbe, 0x4d, 0x88, 0x53, 0x93, 0xd7, 0x61, 0x78, 0x35, 0xa8, 0x78, 0xbe, 0xdb, 0xda, + 0xc7, 0x6f, 0x74, 0x98, 0x3b, 0xfa, 0xdd, 0xa0, 0xb6, 0x87, 0x40, 0x5b, 0xa1, 0x63, 0x2e, 0xe9, + 0xa1, 0xde, 0x2e, 0xe9, 0x3b, 0x00, 0x6b, 0x9e, 0xd3, 0x28, 0x37, 0x9b, 0x0b, 0xe5, 0x00, 0x57, + 0x62, 0xb1, 0x1e, 0x35, 0x3d, 0xa7, 0x51, 0x73, 0x9a, 0xcd, 0x5a, 0xdd, 0x09, 0x6c, 0x8d, 0x86, + 0x7c, 0x09, 0xe7, 0x03, 0x77, 0xbf, 0x85, 0x8d, 0xab, 0x39, 0xcd, 0x7d, 0xcf, 0x77, 0xc3, 0x83, + 0xc3, 0x5a, 0xd0, 0x71, 0x43, 0xee, 0x0f, 0x9a, 0x98, 0xbf, 0x2c, 0x26, 0xb9, 0xaa, 0xa4, 0x2b, + 0x4b, 0xb2, 0x2a, 0xa3, 0xb2, 0x67, 0x83, 0x74, 0x04, 0x79, 0x08, 0xe3, 0x6b, 0x6e, 0x9d, 0xb6, + 0x02, 0x8a, 0x0e, 0xbe, 0x67, 0xe8, 0x2d, 0xea, 0xfe, 0x31, 0x33, 0x25, 0x8e, 0x37, 0x75, 0x26, + 0xfc, 0x74, 0x4d, 0x39, 0xf7, 0xfb, 0x87, 0x07, 0x0b, 0x43, 0xf6, 0xa4, 0x00, 0x3e, 0x74, 0xfc, + 0x96, 0xdb, 0xda, 0x0f, 0xac, 0xbf, 0x4b, 0x60, 0x58, 0xf5, 0xd3, 0x2d, 0xdd, 0x52, 0x11, 0x4b, + 0x33, 0x0e, 0xd9, 0xc8, 0x0f, 0x67, 0x6b, 0x14, 0xe4, 0x3c, 0xda, 0x2e, 0x62, 0x53, 0x30, 0xc4, + 0x3e, 0x21, 0xa7, 0xdd, 0xb6, 0x19, 0x8c, 0x4d, 0x0d, 0x8b, 0x15, 0x1c, 0x34, 0xc3, 0x7c, 0x6a, + 0x68, 0xec, 0xd9, 0xf9, 0xc5, 0x0a, 0xfb, 0x26, 0x37, 0x57, 0x17, 0x17, 0xb0, 0xff, 0x87, 0xf9, + 0x37, 0xe9, 0xb9, 0x8d, 0xba, 0x8d, 0x50, 0x86, 0xad, 0x96, 0xd7, 0xd7, 0x44, 0x1f, 0x23, 0x36, + 0x70, 0x0e, 0x9b, 0x36, 0x42, 0xd9, 0x6e, 0x97, 0xbb, 0x54, 0x16, 0xbc, 0x56, 0xe8, 0x7b, 0xcd, + 0x00, 0xb7, 0x70, 0xc3, 0x7c, 0x0c, 0x0a, 0x5f, 0x4c, 0x5d, 0xa0, 0xec, 0x18, 0x29, 0x79, 0x08, + 0xb3, 0xe5, 0xc6, 0x13, 0xa7, 0x55, 0xa7, 0x0d, 0x8e, 0x79, 0xe8, 0xf9, 0x8f, 0x1f, 0x35, 0xbd, + 0xa7, 0x01, 0x0e, 0x92, 0x61, 0xe1, 0xba, 0x14, 0x24, 0xd2, 0xb5, 0xf3, 0x54, 0x12, 0xd9, 0x59, + 0xdc, 0x6c, 0x1e, 0x58, 0x68, 0x7a, 0x9d, 0x86, 0x18, 0x3a, 0x38, 0x0f, 0xd4, 0x19, 0xc0, 0xe6, + 0x70, 0xa6, 0xa5, 0x95, 0xea, 0x3a, 0x0e, 0x0c, 0xa1, 0xa5, 0x83, 0xe0, 0xd0, 0x66, 0x30, 0x72, + 0x1d, 0x86, 0xe4, 0xc6, 0x9d, 0x9f, 0x64, 0xa0, 0x07, 0x5d, 0x6e, 0xd8, 0x25, 0x8e, 0x7d, 0xc7, + 0x36, 0xad, 0x7b, 0x4f, 0xa8, 0xff, 0x6c, 0xc1, 0x6b, 0x50, 0xe9, 0xd6, 0x12, 0x6e, 0x1b, 0x8e, + 0xa8, 0xd5, 0x19, 0xc6, 0x36, 0x09, 0x59, 0x01, 0x7c, 0xe1, 0x0e, 0x8a, 0x93, 0x51, 0x01, 0x7c, + 0x61, 0x0f, 0x6c, 0x89, 0x23, 0x8b, 0x70, 0xb6, 0xdc, 0x09, 0xbd, 0x43, 0x27, 0x74, 0xeb, 0x3b, + 0xed, 0x7d, 0xdf, 0x61, 0x85, 0x14, 0x90, 0x01, 0x0d, 0x19, 0x47, 0x22, 0x6b, 0x1d, 0x81, 0xb5, + 0x93, 0x0c, 0xe4, 0x1d, 0x18, 0x5b, 0x0d, 0xb8, 0xeb, 0xd2, 0x09, 0x68, 0x03, 0xfd, 0x4f, 0xa2, + 0x96, 0x6e, 0x50, 0x43, 0x47, 0x66, 0x8d, 0x99, 0x3e, 0x0d, 0xdb, 0xa0, 0x23, 0x16, 0x0c, 0x96, + 0x83, 0xc0, 0x0d, 0x42, 0x74, 0x2b, 0x0d, 0x57, 0xe0, 0xf8, 0xa8, 0x34, 0xe8, 0x20, 0xc4, 0x16, + 0x18, 0xf2, 0x10, 0x46, 0x17, 0x29, 0xdb, 0x39, 0x6f, 0xfb, 0x9d, 0x20, 0x44, 0x27, 0xd1, 0xe8, + 0xfc, 0x79, 0x31, 0x1b, 0x69, 0x18, 0x31, 0x96, 0xf9, 0x16, 0xb5, 0x81, 0xf0, 0x5a, 0xc8, 0x10, + 0xfa, 0x52, 0xab, 0xd1, 0x33, 0xb3, 0x40, 0xf0, 0xac, 0xb8, 0x0d, 0x36, 0xbf, 0x4c, 0x63, 0x1d, + 0xd0, 0x2c, 0x10, 0x13, 0x5a, 0xed, 0x00, 0x31, 0xba, 0x59, 0x60, 0xb0, 0x90, 0x7a, 0xc2, 0x1b, + 0x3e, 0x63, 0x78, 0x3c, 0x4d, 0xa4, 0xac, 0xe2, 0x29, 0x7d, 0xe5, 0x1f, 0xc2, 0xe8, 0x42, 0x27, + 0x08, 0xbd, 0xc3, 0xed, 0x03, 0x7a, 0x48, 0xd1, 0x91, 0x24, 0x8c, 0x9f, 0x3a, 0x82, 0x6b, 0x21, + 0x83, 0xeb, 0xcd, 0xd4, 0xc8, 0xc9, 0x67, 0x40, 0xa4, 0x15, 0xb3, 0xcc, 0xc6, 0x47, 0x8b, 0x8d, + 0x65, 0xf4, 0x25, 0x0d, 0x73, 0xd3, 0x45, 0x1a, 0x3f, 0xb5, 0x7d, 0x85, 0xd6, 0xfd, 0x99, 0x49, + 0x66, 0x56, 0x21, 0x5e, 0xc5, 0x65, 0xdf, 0x69, 0x1f, 0x14, 0x8b, 0x91, 0x69, 0x20, 0x1a, 0xb5, + 0xcf, 0xe0, 0xc6, 0x16, 0x27, 0x22, 0x27, 0x55, 0x00, 0xfe, 0x73, 0x8d, 0x75, 0x3c, 0xf7, 0x3e, + 0x15, 0x0d, 0x7d, 0x31, 0x84, 0xd4, 0x15, 0x9a, 0x3b, 0x42, 0x6c, 0xd3, 0x35, 0x7a, 0x53, 0x13, + 0x43, 0x1e, 0x43, 0x81, 0xff, 0x5a, 0xf7, 0x5a, 0x6e, 0xc8, 0xd7, 0x8b, 0x39, 0xc3, 0x55, 0x19, + 0x47, 0xcb, 0x02, 0xd0, 0x45, 0x2c, 0x0a, 0x38, 0x54, 0x58, 0xad, 0x98, 0x84, 0x60, 0xb2, 0x05, + 0xa3, 0x5b, 0xbe, 0xd7, 0xe8, 0xd4, 0x43, 0xdc, 0x65, 0x5c, 0xc0, 0x89, 0x9f, 0x88, 0x72, 0x34, + 0x0c, 0xd7, 0x49, 0x9b, 0x03, 0x6a, 0x6c, 0x5d, 0xd0, 0x75, 0xa2, 0x11, 0x92, 0x0a, 0x0c, 0x6e, + 0x79, 0x4d, 0xb7, 0xfe, 0xac, 0x78, 0x11, 0x2b, 0x3d, 0x2d, 0x85, 0x21, 0x50, 0x56, 0x15, 0xb7, + 0xb4, 0x6d, 0x04, 0xe9, 0x5b, 0x5a, 0x4e, 0x44, 0xca, 0x30, 0xfe, 0x19, 0x1b, 0x30, 0xae, 0xd7, + 0x6a, 0x39, 0xae, 0x4f, 0x8b, 0x97, 0xb0, 0x5f, 0xd0, 0x8d, 0xff, 0x43, 0x1d, 0xa1, 0x0f, 0x67, + 0x83, 0x83, 0xac, 0xc2, 0xe4, 0x6a, 0x50, 0x0d, 0x7d, 0xb7, 0x4d, 0xd7, 0x9d, 0x96, 0xb3, 0x4f, + 0x1b, 0xc5, 0xcb, 0x91, 0x1f, 0xdd, 0x0d, 0x6a, 0x01, 0xe2, 0x6a, 0x87, 0x1c, 0xa9, 0xfb, 0xd1, + 0x63, 0x7c, 0xe4, 0x73, 0x98, 0x5e, 0xfa, 0x26, 0x64, 0x23, 0xa6, 0x59, 0xee, 0x34, 0xdc, 0xb0, + 0x1a, 0x7a, 0xbe, 0xb3, 0x4f, 0x8b, 0x25, 0x94, 0xf7, 0xca, 0xf1, 0x51, 0xe9, 0x0a, 0x15, 0xf8, + 0x9a, 0xc3, 0x08, 0x6a, 0x01, 0xa7, 0xd0, 0xcf, 0xc7, 0xd3, 0x24, 0x30, 0xed, 0x57, 0x3b, 0x6d, + 0xb6, 0xdb, 0x46, 0xed, 0x5f, 0x31, 0xb4, 0xaf, 0x61, 0xb8, 0xf6, 0x03, 0x0e, 0x48, 0x68, 0x5f, + 0x23, 0x24, 0x36, 0x90, 0xfb, 0x9e, 0xdb, 0x2a, 0xd7, 0x43, 0xf7, 0x09, 0x15, 0x16, 0x73, 0x50, + 0xbc, 0x8a, 0x35, 0x45, 0x9f, 0xff, 0x2f, 0x7a, 0x6e, 0xab, 0xe6, 0x20, 0xba, 0x16, 0x08, 0xbc, + 0xfe, 0x8d, 0x24, 0xb9, 0xc9, 0xf7, 0xe1, 0xdc, 0xba, 0xb7, 0xe7, 0x36, 0x29, 0x9f, 0x72, 0xb8, + 0x5a, 0xd0, 0x7f, 0x69, 0xa1, 0x5c, 0xf4, 0xf9, 0x1f, 0x22, 0x45, 0x4d, 0xcc, 0x56, 0x87, 0x8a, + 0x46, 0xf7, 0xf9, 0xa7, 0x4b, 0x21, 0x4b, 0x30, 0x86, 0xdf, 0x65, 0x13, 0x7f, 0x06, 0xc5, 0x6b, + 0x68, 0xd2, 0x5d, 0x8d, 0xed, 0xd2, 0x6e, 0x2d, 0x69, 0x34, 0x4b, 0xad, 0xd0, 0x7f, 0x66, 0x1b, + 0x6c, 0xe4, 0x63, 0x98, 0x8b, 0x0f, 0xef, 0x05, 0xaf, 0xf5, 0xc8, 0xdd, 0xef, 0xf8, 0xb4, 0x51, + 0x7c, 0x85, 0x55, 0xd5, 0xee, 0x42, 0x41, 0xbe, 0x82, 0x19, 0x5c, 0xeb, 0xca, 0x2d, 0xaf, 0xf5, + 0xec, 0xd0, 0xfd, 0x11, 0xee, 0x9f, 0xd9, 0xb6, 0xf7, 0x3a, 0x6e, 0x7b, 0xaf, 0x1f, 0x1f, 0x95, + 0xae, 0xe2, 0x9a, 0x58, 0x73, 0x74, 0x8a, 0x98, 0xd7, 0x3a, 0x5d, 0xc6, 0xdc, 0x43, 0x38, 0x9b, + 0xa8, 0x3f, 0x29, 0x40, 0xdf, 0x63, 0x71, 0x3e, 0x3b, 0x62, 0xb3, 0x3f, 0xc9, 0x1b, 0x30, 0xf0, + 0x84, 0x19, 0x6a, 0xb8, 0x1d, 0x89, 0x4e, 0xfc, 0x34, 0xd6, 0xd5, 0xd6, 0x23, 0xcf, 0xe6, 0x44, + 0xef, 0xe7, 0xdf, 0xcd, 0xdd, 0xef, 0x1f, 0x1e, 0x2d, 0x8c, 0xf1, 0x63, 0xf5, 0xfb, 0xfd, 0xc3, + 0xe3, 0x85, 0x09, 0xab, 0x0c, 0x93, 0x31, 0x7a, 0x52, 0x84, 0x21, 0xda, 0x62, 0x9b, 0xff, 0x06, + 0xdf, 0x10, 0xd9, 0xf2, 0x27, 0x99, 0x86, 0x81, 0xa6, 0x7b, 0xe8, 0x86, 0x58, 0xe0, 0x80, 0xcd, + 0x7f, 0x58, 0xbf, 0x9d, 0x03, 0x92, 0x5c, 0x8f, 0xc8, 0xed, 0x98, 0x18, 0xbe, 0xf5, 0x15, 0x20, + 0xfd, 0xe0, 0x40, 0x4a, 0xff, 0x0c, 0xa6, 0xf8, 0x80, 0x90, 0x2b, 0xa7, 0x56, 0x16, 0x9f, 0xb1, + 0x53, 0xd0, 0xba, 0xb3, 0x49, 0xa0, 0x71, 0x9d, 0x5d, 0xc3, 0xaa, 0x75, 0x60, 0x26, 0x75, 0x25, + 0x22, 0xeb, 0x30, 0x73, 0xe8, 0xb5, 0xc2, 0x83, 0xe6, 0x33, 0xb9, 0x10, 0x89, 0xd2, 0x72, 0x58, + 0x1a, 0x4e, 0xbe, 0xa9, 0x04, 0xf6, 0x94, 0x00, 0x0b, 0x89, 0x58, 0x8e, 0x70, 0x3a, 0xc9, 0x96, + 0x58, 0x36, 0x9c, 0x4d, 0x4c, 0xe8, 0xe4, 0x23, 0x18, 0xab, 0xa3, 0x71, 0x67, 0x94, 0xc4, 0x97, + 0x33, 0x0d, 0xae, 0x7f, 0xab, 0x1c, 0xce, 0x9b, 0xf2, 0xcf, 0x72, 0x30, 0x9b, 0x31, 0x95, 0x9f, + 0x5e, 0xd5, 0x5f, 0xc0, 0xb9, 0x43, 0xe7, 0x9b, 0x9a, 0x8f, 0xb6, 0x7b, 0xcd, 0x77, 0x5a, 0x31, + 0x6d, 0xe3, 0x34, 0x95, 0x4e, 0xa1, 0xc7, 0x36, 0x1d, 0x3a, 0xdf, 0xd8, 0x48, 0x60, 0x33, 0x3c, + 0xaf, 0xe7, 0xa7, 0x30, 0x6e, 0x4c, 0xde, 0xa7, 0xae, 0x9c, 0x75, 0x17, 0xce, 0x2e, 0xd2, 0x26, + 0x0d, 0xe9, 0x89, 0x7d, 0x76, 0xd6, 0x16, 0x40, 0x95, 0x1e, 0x3a, 0xed, 0x03, 0x8f, 0x6d, 0xea, + 0x2b, 0xfa, 0x2f, 0xe1, 0xf3, 0x21, 0xd2, 0x3c, 0x91, 0x88, 0xdd, 0x37, 0xf9, 0x46, 0x3f, 0x50, + 0x94, 0xb6, 0xc6, 0x65, 0xfd, 0xbb, 0x3c, 0x10, 0x31, 0xfb, 0xfa, 0xd4, 0x39, 0x94, 0xd5, 0x78, + 0x0f, 0xc6, 0xb8, 0x85, 0xce, 0xc1, 0x58, 0x9d, 0xd1, 0xf9, 0x29, 0xf1, 0xe5, 0xe9, 0xa8, 0x95, + 0x33, 0xb6, 0x41, 0xca, 0x58, 0x6d, 0xca, 0x5d, 0x0b, 0xc8, 0x9a, 0x37, 0x58, 0x75, 0x14, 0x63, + 0xd5, 0x7f, 0x93, 0x4f, 0x60, 0x62, 0xc1, 0x3b, 0x6c, 0x33, 0x9d, 0x08, 0xe6, 0x3e, 0xe1, 0xb6, + 0x11, 0xe5, 0x1a, 0xc8, 0x95, 0x33, 0x76, 0x8c, 0x9c, 0x6c, 0xc0, 0xd4, 0xbd, 0x66, 0x27, 0x38, + 0x28, 0xb7, 0x1a, 0x0b, 0x4d, 0x2f, 0x90, 0x52, 0xfa, 0x85, 0xa5, 0x25, 0xe6, 0xce, 0x24, 0xc5, + 0xca, 0x19, 0x3b, 0x8d, 0x91, 0x5c, 0x87, 0x81, 0xa5, 0x27, 0x6c, 0x4e, 0x97, 0x11, 0x2e, 0x22, + 0x00, 0x6f, 0xb3, 0x45, 0x37, 0x1f, 0xad, 0x9c, 0xb1, 0x39, 0xb6, 0x32, 0x02, 0x43, 0xd2, 0xba, + 0xbf, 0xcd, 0xf6, 0xdb, 0x4a, 0x9d, 0xd5, 0xd0, 0x09, 0x3b, 0x01, 0x99, 0x83, 0xe1, 0x9d, 0x36, + 0x33, 0x3a, 0xa5, 0x5b, 0xc4, 0x56, 0xbf, 0xad, 0x37, 0x4c, 0x4d, 0x93, 0x8b, 0x10, 0xf9, 0x74, + 0x05, 0xb1, 0xe6, 0xe4, 0x5d, 0x31, 0x95, 0xdb, 0x9d, 0xda, 0x28, 0x37, 0x1f, 0x2b, 0xb7, 0x10, + 0xd7, 0xb5, 0x35, 0x93, 0xaa, 0x3c, 0xeb, 0x73, 0xb8, 0xbc, 0xd3, 0x0e, 0xa8, 0x1f, 0x96, 0xdb, + 0xed, 0xa6, 0x5b, 0xe7, 0x27, 0x64, 0xe8, 0x05, 0x90, 0x83, 0xe5, 0x1d, 0x18, 0xe4, 0x00, 0x31, + 0x4c, 0xe4, 0x18, 0x2c, 0xb7, 0xdb, 0xc2, 0xf7, 0xf0, 0x26, 0xdf, 0xf9, 0x73, 0x6f, 0x82, 0x2d, + 0xa8, 0xad, 0xdf, 0xc8, 0xc1, 0x65, 0xfe, 0x05, 0x64, 0x8a, 0xfe, 0x0e, 0x8c, 0x60, 0xfc, 0x5b, + 0xdb, 0xa9, 0xcb, 0x6f, 0x82, 0x07, 0x02, 0x4a, 0xa0, 0x1d, 0xe1, 0xb5, 0xc8, 0xc2, 0x7c, 0x76, + 0x64, 0xa1, 0xfc, 0xc0, 0xfa, 0x52, 0x3f, 0xb0, 0xcf, 0xc0, 0x12, 0x35, 0x6a, 0x36, 0x13, 0x95, + 0x0a, 0x9e, 0xa7, 0x56, 0xd6, 0x7f, 0xcb, 0xc3, 0xec, 0x32, 0x6d, 0x51, 0xdf, 0xc1, 0x76, 0x1a, + 0x5e, 0x2e, 0x3d, 0xc2, 0x28, 0xd7, 0x35, 0xc2, 0xa8, 0x24, 0xfd, 0x86, 0x79, 0xf4, 0x1b, 0x26, + 0xc2, 0xa5, 0x98, 0x2d, 0xba, 0x63, 0xaf, 0x8a, 0x66, 0xa1, 0x2d, 0xda, 0xf1, 0x5d, 0x7e, 0xca, + 0xb0, 0x1a, 0x45, 0x27, 0xf5, 0xf7, 0xf4, 0x39, 0x4c, 0x89, 0x68, 0x8d, 0x21, 0x11, 0x9d, 0x64, + 0xc6, 0x24, 0x6d, 0xc0, 0x20, 0x77, 0x77, 0xe2, 0xd9, 0xd6, 0xe8, 0xfc, 0x4d, 0xf1, 0x4d, 0x65, + 0x34, 0x50, 0xf8, 0x46, 0x71, 0x61, 0xe7, 0x43, 0x20, 0x44, 0x80, 0x2d, 0xa4, 0xcc, 0x7d, 0x06, + 0xa3, 0x1a, 0xc9, 0x49, 0xd6, 0x7e, 0xe5, 0x76, 0x65, 0xdb, 0xd1, 0xd6, 0x3e, 0xf7, 0xe0, 0x6a, + 0x6b, 0xbf, 0xf5, 0x01, 0x14, 0x93, 0xb5, 0x11, 0xae, 0xb6, 0x5e, 0x9e, 0x3d, 0x6b, 0x11, 0xa6, + 0x97, 0x69, 0x88, 0x03, 0x17, 0x3f, 0x22, 0x2d, 0xca, 0x2e, 0xf6, 0x9d, 0xc9, 0x59, 0x15, 0x81, + 0x6c, 0x80, 0x69, 0x5f, 0x69, 0x15, 0x66, 0x62, 0x52, 0x44, 0xf9, 0xef, 0xc3, 0x90, 0x00, 0xa9, + 0x19, 0x55, 0x84, 0xea, 0xd2, 0x3d, 0x81, 0xd8, 0x9d, 0xe7, 0xe3, 0x56, 0x48, 0xb6, 0x25, 0x83, + 0x75, 0x00, 0xe7, 0xd8, 0x32, 0x1b, 0x49, 0x55, 0xc3, 0xf1, 0x02, 0x8c, 0xb4, 0xd9, 0x46, 0x21, + 0x70, 0x7f, 0xc4, 0x87, 0xd1, 0x80, 0x3d, 0xcc, 0x00, 0x55, 0xf7, 0x47, 0x94, 0x5c, 0x02, 0x40, + 0x24, 0x36, 0x53, 0xcc, 0x02, 0x48, 0xce, 0x5d, 0x99, 0x04, 0x30, 0x46, 0x8f, 0x8f, 0x1b, 0x1b, + 0xff, 0xb6, 0x7c, 0x98, 0x4d, 0x94, 0x24, 0x1a, 0x70, 0x1b, 0x86, 0xe5, 0xfe, 0x38, 0x76, 0xc8, + 0xa0, 0xb7, 0xc0, 0x56, 0x44, 0xe4, 0x55, 0x98, 0x6c, 0xd1, 0x6f, 0xc2, 0x5a, 0xa2, 0x0e, 0xe3, + 0x0c, 0xbc, 0x25, 0xeb, 0x61, 0xfd, 0x02, 0x3a, 0x96, 0xab, 0x2d, 0xef, 0xe9, 0xa3, 0xa6, 0xf3, + 0x98, 0x26, 0x0a, 0xfe, 0x08, 0x86, 0xab, 0xbd, 0x0b, 0xe6, 0x9f, 0x8f, 0x2c, 0xdc, 0x56, 0x2c, + 0x56, 0x13, 0xe6, 0x58, 0x93, 0xaa, 0xe5, 0xf5, 0xb5, 0xd5, 0xc6, 0xd6, 0xb7, 0xad, 0xc0, 0x27, + 0x70, 0x21, 0xb5, 0xb4, 0x6f, 0x5b, 0x89, 0x7f, 0xd4, 0x0f, 0xb3, 0x7c, 0x31, 0x49, 0x8e, 0xe0, + 0x93, 0x4f, 0x35, 0x3f, 0x97, 0xf3, 0xde, 0x3b, 0x29, 0xe7, 0xbd, 0xc8, 0xa2, 0x9f, 0xf7, 0x1a, + 0xa7, 0xbc, 0xef, 0xa6, 0x9f, 0xf2, 0xa2, 0x13, 0xca, 0x3c, 0xe5, 0x8d, 0x9f, 0xed, 0x2e, 0x65, + 0x9f, 0xed, 0xe2, 0xc1, 0x53, 0xca, 0xd9, 0x6e, 0xda, 0x89, 0x6e, 0x2c, 0x50, 0x6a, 0xf8, 0xe5, + 0x06, 0x4a, 0xbd, 0x0a, 0x43, 0xe5, 0x76, 0x5b, 0x0b, 0x3c, 0xc4, 0xee, 0x71, 0xda, 0x6d, 0xae, + 0x3c, 0x89, 0x94, 0xf3, 0x3c, 0xa4, 0xcc, 0xf3, 0xef, 0x01, 0x2c, 0xe0, 0xf5, 0x08, 0xec, 0xb8, + 0x51, 0xa4, 0xc0, 0x1d, 0x3e, 0xbf, 0x34, 0x81, 0x1d, 0xa7, 0xbb, 0x57, 0x22, 0x62, 0xbe, 0xb1, + 0xb7, 0x76, 0xa1, 0x98, 0x1c, 0x3e, 0x2f, 0x61, 0xea, 0xfa, 0xc3, 0x1c, 0x5c, 0x12, 0x9b, 0x9c, + 0xd8, 0x07, 0x7e, 0xfa, 0xd1, 0xf9, 0x36, 0x8c, 0x09, 0xde, 0xed, 0xe8, 0x43, 0xe0, 0x07, 0xec, + 0x72, 0x32, 0xe6, 0x33, 0xba, 0x41, 0x46, 0xde, 0x86, 0x61, 0xfc, 0x23, 0x3a, 0x18, 0x62, 0x9a, + 0x19, 0x41, 0xd2, 0x5a, 0xfc, 0x78, 0x48, 0x91, 0x5a, 0x5f, 0xc3, 0xe5, 0xac, 0x8a, 0xbf, 0x04, + 0xbd, 0xfc, 0xeb, 0x1c, 0x5c, 0x10, 0xe2, 0x8d, 0xa9, 0xe2, 0xb9, 0x56, 0x9d, 0x53, 0x84, 0x2b, + 0xdf, 0x87, 0x51, 0x56, 0xa0, 0xac, 0x77, 0x9f, 0x58, 0x5a, 0x85, 0xe5, 0x10, 0x61, 0x16, 0x9d, + 0xd0, 0x11, 0xe1, 0x37, 0xce, 0x61, 0x53, 0x7a, 0x46, 0x6c, 0x9d, 0xd9, 0xfa, 0x12, 0x2e, 0xa6, + 0x37, 0xe1, 0x25, 0xe8, 0xe7, 0x3e, 0xcc, 0xa5, 0x2c, 0x0a, 0xcf, 0xb7, 0x26, 0x7f, 0x01, 0x17, + 0x52, 0x65, 0xbd, 0x84, 0x6a, 0xae, 0xb0, 0x1d, 0x47, 0xf8, 0x12, 0xba, 0xd0, 0x7a, 0x08, 0xe7, + 0x53, 0x24, 0xbd, 0x84, 0x2a, 0x2e, 0xc3, 0xac, 0xda, 0x69, 0xbf, 0x50, 0x0d, 0xd7, 0xe1, 0x12, + 0x17, 0xf4, 0x72, 0x7a, 0xe5, 0x01, 0x5c, 0x10, 0xe2, 0x5e, 0x82, 0xf6, 0x56, 0xe0, 0x62, 0x64, + 0x50, 0xa7, 0xec, 0x93, 0x4e, 0x3c, 0xc9, 0x58, 0x6b, 0x70, 0x25, 0x92, 0x94, 0xb1, 0x69, 0x38, + 0xb9, 0x34, 0xbe, 0x1d, 0x8c, 0x7a, 0xe9, 0xa5, 0xf4, 0xe8, 0x43, 0x38, 0x67, 0x08, 0x7d, 0x69, + 0x5b, 0xa5, 0x55, 0x98, 0xe2, 0x82, 0xcd, 0xad, 0xf3, 0xbc, 0xbe, 0x75, 0x1e, 0x9d, 0x3f, 0x1b, + 0x89, 0x44, 0xf0, 0xee, 0x9b, 0x29, 0xbb, 0xe9, 0x75, 0xdc, 0x4d, 0x4b, 0x92, 0xa8, 0x86, 0x6f, + 0xc3, 0x20, 0x87, 0x88, 0xfa, 0xa5, 0x08, 0xe3, 0xc6, 0x02, 0x67, 0x13, 0xc4, 0xd6, 0xf7, 0xe1, + 0x12, 0xb7, 0x44, 0xa3, 0x83, 0x4a, 0xd3, 0x5a, 0xfc, 0x28, 0x66, 0x88, 0x9e, 0x17, 0x72, 0xe3, + 0xf4, 0x19, 0xf6, 0xe8, 0x9e, 0x1c, 0xdb, 0x59, 0xf2, 0x4f, 0x74, 0x75, 0x4d, 0x1a, 0x98, 0xf9, + 0x54, 0x03, 0xf3, 0x1a, 0x5c, 0x55, 0x06, 0x66, 0xbc, 0x18, 0x39, 0xb4, 0xac, 0x2f, 0xe1, 0x02, + 0x6f, 0xa8, 0x0c, 0x29, 0x34, 0xab, 0xf1, 0x41, 0xac, 0x99, 0xb3, 0xa2, 0x99, 0x26, 0x75, 0x46, + 0x23, 0xff, 0x5e, 0x4e, 0x7e, 0x72, 0xe9, 0xc2, 0x7f, 0xde, 0x16, 0xf7, 0x06, 0x94, 0x94, 0x42, + 0xcc, 0x1a, 0x3d, 0x9f, 0xb9, 0xbd, 0x0e, 0x33, 0xba, 0x18, 0xb7, 0x4e, 0x77, 0xef, 0xe2, 0x09, + 0xd2, 0x5b, 0xec, 0xb3, 0x40, 0x80, 0x1c, 0x76, 0xc5, 0x14, 0xbd, 0x21, 0xbd, 0xad, 0x28, 0xad, + 0x1a, 0x5c, 0x4c, 0x76, 0x85, 0x5b, 0x97, 0xf7, 0x09, 0xc8, 0x27, 0xec, 0x13, 0x46, 0x88, 0xe8, + 0x8c, 0x4c, 0xa1, 0xf2, 0x3b, 0xe6, 0xec, 0x92, 0xcb, 0xb2, 0xe4, 0x54, 0x13, 0x6b, 0x3f, 0x2b, + 0x5d, 0x8e, 0x87, 0x1f, 0x03, 0x91, 0xa8, 0x85, 0xaa, 0x2d, 0x8b, 0x3e, 0x0f, 0x7d, 0x0b, 0x55, + 0x5b, 0x5c, 0x64, 0xc2, 0x9d, 0x60, 0x3d, 0xf0, 0x6d, 0x06, 0x8b, 0xef, 0xc8, 0xf3, 0x27, 0xd8, + 0x91, 0xdf, 0xef, 0x1f, 0xee, 0x2b, 0xf4, 0xdb, 0xa4, 0xea, 0xee, 0xb7, 0x1e, 0xba, 0xe1, 0x81, + 0x2a, 0xb0, 0x6c, 0x7d, 0x05, 0x53, 0x46, 0xf1, 0xe2, 0x2b, 0xee, 0x7a, 0x03, 0x8b, 0xed, 0x67, + 0x17, 0xca, 0x18, 0x56, 0x83, 0x2e, 0x8b, 0x31, 0x3e, 0xdf, 0xd4, 0x9d, 0x1a, 0x5e, 0xef, 0xb5, + 0x25, 0xd2, 0xfa, 0xfd, 0x7e, 0x4d, 0xba, 0x76, 0xaf, 0xad, 0x4b, 0xeb, 0xee, 0x02, 0xf0, 0x11, + 0xa2, 0x35, 0x8e, 0x6d, 0x00, 0x47, 0x45, 0xb4, 0x0a, 0x9f, 0x92, 0x6d, 0x8d, 0xe8, 0xa4, 0xf7, + 0xde, 0x44, 0xfc, 0x31, 0x67, 0x92, 0x57, 0x3d, 0x55, 0xfc, 0xb1, 0x10, 0x1d, 0xd8, 0x3a, 0x11, + 0xf9, 0x7e, 0xfc, 0x72, 0xc6, 0x00, 0x1e, 0x58, 0xbd, 0x22, 0x4f, 0xb0, 0x93, 0x6d, 0x3b, 0xdd, + 0xfd, 0x8c, 0xa7, 0x30, 0xc3, 0x78, 0xdd, 0x47, 0x68, 0x58, 0x2c, 0x7d, 0x13, 0xd2, 0x16, 0x9f, + 0xdb, 0x07, 0xb1, 0x9c, 0xeb, 0x5d, 0xca, 0x89, 0x88, 0x85, 0xff, 0x3d, 0x92, 0x53, 0xa3, 0x0a, + 0x67, 0xa7, 0xcb, 0xc7, 0x41, 0x64, 0xaf, 0x2d, 0xb5, 0x1a, 0x6d, 0xcf, 0x55, 0x06, 0x13, 0x1f, + 0x44, 0x7e, 0xb3, 0x46, 0x05, 0xdc, 0xd6, 0x89, 0xac, 0x57, 0xbb, 0x46, 0xb5, 0x0f, 0x43, 0xff, + 0xf6, 0xc2, 0xf6, 0x5a, 0x21, 0x67, 0xdd, 0x06, 0xd0, 0x4a, 0x02, 0x18, 0xdc, 0xd8, 0xb4, 0xd7, + 0xcb, 0x6b, 0x85, 0x33, 0x64, 0x06, 0xce, 0x3e, 0x5c, 0xdd, 0x58, 0xdc, 0x7c, 0x58, 0xad, 0x55, + 0xd7, 0xcb, 0xf6, 0xf6, 0x42, 0xd9, 0x5e, 0x2c, 0xe4, 0xac, 0xaf, 0x61, 0xda, 0x6c, 0xe1, 0x4b, + 0x1d, 0x84, 0x21, 0x4c, 0xa9, 0xfd, 0xcc, 0xfd, 0x87, 0xdb, 0x5a, 0x44, 0xab, 0x30, 0xfe, 0xe2, + 0x91, 0x59, 0xc2, 0x4c, 0x14, 0x9f, 0x91, 0x46, 0x44, 0x5e, 0xe7, 0xdb, 0x82, 0xf8, 0xcd, 0x65, + 0xb6, 0x2d, 0xa8, 0x45, 0xfb, 0x02, 0x9c, 0xfa, 0xbe, 0x07, 0xd3, 0x66, 0xa9, 0x27, 0xf5, 0x52, + 0xbd, 0x82, 0xa1, 0xbe, 0xda, 0xb5, 0x26, 0x42, 0xf4, 0x63, 0x03, 0x31, 0xb3, 0x7e, 0x0f, 0x0a, + 0x82, 0x2a, 0x5a, 0x79, 0xaf, 0x49, 0x37, 0x62, 0x2e, 0xe5, 0x12, 0xa6, 0x0c, 0x4a, 0xf7, 0xa0, + 0xc0, 0x66, 0x4c, 0xc1, 0xc9, 0x0b, 0x98, 0x86, 0x81, 0xb5, 0xe8, 0x38, 0xc7, 0xe6, 0x3f, 0xf0, + 0x76, 0x4f, 0xe8, 0xf8, 0xa1, 0x8c, 0x83, 0x1b, 0xb1, 0xd5, 0x6f, 0xf2, 0x3a, 0x0c, 0xde, 0x73, + 0x9b, 0xa1, 0x70, 0x8d, 0x44, 0x8b, 0x3c, 0x13, 0xcb, 0x11, 0xb6, 0x20, 0xb0, 0x6c, 0x38, 0xab, + 0x15, 0x78, 0x8a, 0xaa, 0x92, 0x22, 0x0c, 0x6d, 0xd0, 0x6f, 0xb4, 0xf2, 0xe5, 0x4f, 0xeb, 0x1d, + 0x38, 0x2b, 0x62, 0x0c, 0x35, 0x35, 0x5d, 0x15, 0x77, 0xc5, 0x73, 0xc6, 0x85, 0x55, 0x21, 0x12, + 0x51, 0x8c, 0x6f, 0xa7, 0xdd, 0x78, 0x4e, 0x3e, 0xb6, 0x50, 0x9c, 0x92, 0xef, 0x35, 0x79, 0x0a, + 0xd4, 0xab, 0x3b, 0xff, 0x56, 0x1e, 0x8a, 0x31, 0x2f, 0xc3, 0xc2, 0x81, 0xd3, 0x6c, 0xd2, 0xd6, + 0x3e, 0x25, 0x37, 0xa0, 0x7f, 0x7b, 0x73, 0x7b, 0x4b, 0x78, 0x49, 0x65, 0x74, 0x01, 0x03, 0x29, + 0x1a, 0x1b, 0x29, 0xc8, 0x03, 0x38, 0x2b, 0xa3, 0x88, 0x15, 0x4a, 0xf4, 0xd0, 0xa5, 0xee, 0x31, + 0xc9, 0x49, 0x3e, 0xf2, 0x96, 0x70, 0x89, 0xfc, 0xb0, 0xe3, 0xfa, 0xb4, 0x81, 0x9e, 0x9f, 0xe8, + 0xa8, 0x5e, 0xc3, 0xd8, 0x3a, 0x19, 0xf9, 0x1e, 0x8c, 0x55, 0xab, 0x9b, 0x51, 0xe9, 0x03, 0xc6, + 0x09, 0x91, 0x8e, 0xb2, 0x0d, 0x42, 0x7e, 0x25, 0xd8, 0xfa, 0xa3, 0x1c, 0xcc, 0x66, 0xb8, 0x5b, + 0xc8, 0xeb, 0x86, 0x1e, 0xa6, 0x34, 0x3d, 0x48, 0x92, 0x95, 0x33, 0x42, 0x11, 0x0b, 0x5a, 0x4c, + 0x76, 0xdf, 0x29, 0x62, 0xb2, 0x57, 0xce, 0x44, 0x71, 0xd8, 0xe4, 0x55, 0xe8, 0xab, 0x56, 0x37, + 0x85, 0x5b, 0x9d, 0x44, 0x2d, 0xd0, 0x88, 0x19, 0x41, 0x05, 0x60, 0x58, 0x82, 0xac, 0x49, 0x18, + 0x37, 0x3a, 0xc6, 0xb2, 0x60, 0x4c, 0xaf, 0x21, 0xeb, 0xfd, 0x05, 0xaf, 0xa1, 0x7a, 0x9f, 0xfd, + 0x6d, 0xfd, 0xd8, 0xd4, 0x19, 0xb9, 0x04, 0x20, 0xcf, 0x6b, 0xdd, 0x86, 0x3c, 0xf9, 0x11, 0x90, + 0xd5, 0x06, 0xb9, 0x0a, 0x63, 0x3e, 0x6d, 0xb8, 0x3e, 0xad, 0x87, 0xb5, 0x8e, 0x2f, 0x2e, 0xc6, + 0xd8, 0xa3, 0x12, 0xb6, 0xe3, 0x37, 0xc9, 0x77, 0x60, 0x90, 0x1f, 0x24, 0x8b, 0xd6, 0x4b, 0x23, + 0xa1, 0x5a, 0xdd, 0x5c, 0xbf, 0x57, 0xe6, 0x07, 0xdd, 0xb6, 0x20, 0xb1, 0x2a, 0x30, 0xaa, 0xb5, + 0xaa, 0x57, 0xe9, 0xd3, 0x30, 0xa0, 0x7b, 0x29, 0xf9, 0x0f, 0xeb, 0x37, 0x73, 0x30, 0x8d, 0xc3, + 0x60, 0xdf, 0x65, 0xcb, 0x43, 0xd4, 0x96, 0x79, 0xa3, 0xd3, 0x2e, 0x1a, 0x9d, 0x16, 0xa3, 0x55, + 0xbd, 0xf7, 0x7e, 0xa2, 0xf7, 0x2e, 0xa6, 0xf5, 0x1e, 0x4e, 0x01, 0xae, 0xd7, 0xd2, 0x3b, 0x4d, + 0x3f, 0xae, 0xfb, 0xed, 0x1c, 0x4c, 0x69, 0x75, 0x52, 0x0d, 0xbc, 0x6b, 0x54, 0xe9, 0x42, 0x4a, + 0x95, 0x12, 0xe3, 0xa9, 0x92, 0xa8, 0xd1, 0x2b, 0xdd, 0x6a, 0x94, 0x36, 0x9c, 0x8c, 0x61, 0xf2, + 0x97, 0x39, 0x98, 0x49, 0xd5, 0x01, 0x39, 0xc7, 0xf6, 0xff, 0x75, 0x9f, 0x86, 0x42, 0xf3, 0xe2, + 0x17, 0x83, 0xaf, 0x06, 0x41, 0x87, 0xfa, 0x42, 0xef, 0xe2, 0x17, 0x79, 0x05, 0xc6, 0xb7, 0xa8, + 0xef, 0x7a, 0x0d, 0x7e, 0x31, 0x81, 0x47, 0xfc, 0x8e, 0xdb, 0x26, 0x90, 0x5c, 0x84, 0x11, 0x15, + 0xb1, 0xca, 0x7d, 0xb8, 0x76, 0x04, 0x60, 0xb2, 0x17, 0xdd, 0x7d, 0x7e, 0xf0, 0xc3, 0x98, 0xc5, + 0x2f, 0x36, 0x01, 0x4b, 0x8f, 0xea, 0x20, 0x9f, 0x80, 0xa5, 0xbb, 0xf4, 0x1c, 0x0c, 0x7e, 0x66, + 0xe3, 0x38, 0xc6, 0x9c, 0x13, 0xb6, 0xf8, 0x45, 0x26, 0x30, 0xb4, 0x1c, 0xef, 0xc5, 0x60, 0x48, + 0xf9, 0xfb, 0x30, 0x9d, 0xa6, 0xd7, 0xb4, 0xaf, 0x40, 0xf0, 0xe6, 0x15, 0xef, 0x97, 0x30, 0x55, + 0x6e, 0x34, 0xa2, 0xe1, 0xca, 0x7b, 0x95, 0xcf, 0x13, 0xdc, 0xa7, 0x29, 0xb6, 0xb5, 0xfd, 0xab, + 0x2d, 0x37, 0xb4, 0xa7, 0x96, 0xbe, 0x71, 0x83, 0xd0, 0x6d, 0xed, 0x6b, 0x8e, 0x57, 0xfb, 0xdc, + 0x06, 0x7d, 0x9a, 0x32, 0x04, 0xd8, 0x8e, 0xc3, 0x94, 0xcd, 0xe1, 0x29, 0xc2, 0xa7, 0x35, 0xb1, + 0xd1, 0xd4, 0x35, 0x6b, 0xca, 0x8d, 0x10, 0x7d, 0xe5, 0xfa, 0x63, 0xeb, 0x7b, 0x70, 0x8e, 0x4f, + 0xfb, 0xdd, 0x2a, 0x2f, 0xaa, 0xad, 0xfb, 0x89, 0xad, 0x77, 0xa5, 0x27, 0xa7, 0x6b, 0xcd, 0xec, + 0x31, 0xa3, 0x2e, 0x58, 0xe4, 0x7f, 0xcd, 0xc1, 0x5c, 0x8c, 0xb5, 0xfa, 0xac, 0x55, 0x97, 0x6b, + 0xce, 0xab, 0xf1, 0xd0, 0x7d, 0xdc, 0x2b, 0x71, 0x07, 0xa9, 0xdb, 0x50, 0xd1, 0xfb, 0xe4, 0x36, + 0x00, 0x67, 0xd6, 0xb6, 0x38, 0x78, 0x3c, 0x20, 0xa2, 0x9c, 0x70, 0x93, 0xa3, 0x91, 0x90, 0x0e, + 0xa4, 0xe9, 0x5d, 0x7c, 0x23, 0xbd, 0xfc, 0xe7, 0x98, 0x67, 0x85, 0x0a, 0xf6, 0x5a, 0x86, 0x23, + 0x3d, 0x4d, 0xbe, 0xf5, 0xf7, 0xfb, 0x60, 0x56, 0xef, 0xc0, 0xe7, 0x69, 0xeb, 0x16, 0x8c, 0x2e, + 0x78, 0xad, 0x90, 0x7e, 0x13, 0x6a, 0x79, 0x2e, 0x88, 0x8a, 0x46, 0x50, 0x18, 0xb1, 0xbd, 0xe6, + 0x80, 0x1a, 0xdb, 0xeb, 0x19, 0xd1, 0x9a, 0x11, 0x21, 0x59, 0x80, 0xf1, 0x0d, 0xfa, 0x34, 0xa1, + 0x40, 0x8c, 0x18, 0x6d, 0xd1, 0xa7, 0x35, 0x4d, 0x89, 0x7a, 0x18, 0x9f, 0xc1, 0x43, 0xf6, 0x60, + 0x42, 0x0e, 0x2e, 0x43, 0x99, 0x73, 0xfa, 0xca, 0x6b, 0x0e, 0x67, 0x9e, 0x07, 0x82, 0x95, 0x90, + 0xa1, 0xc3, 0x98, 0x44, 0xd6, 0x74, 0x5e, 0x22, 0x4f, 0x6d, 0x60, 0x2e, 0xed, 0x1a, 0xc6, 0x88, + 0xc7, 0x8d, 0xa7, 0x34, 0xd0, 0x45, 0x58, 0x5b, 0x50, 0x4c, 0xf6, 0x87, 0x28, 0xed, 0x2d, 0x18, + 0xe4, 0x50, 0xb1, 0x55, 0x92, 0x29, 0x8c, 0x14, 0x35, 0xf7, 0x65, 0x34, 0xc4, 0xaa, 0xc4, 0x61, + 0xd6, 0x0a, 0xfa, 0x97, 0x14, 0x8d, 0xda, 0xac, 0xde, 0x89, 0x77, 0x2f, 0x86, 0x3a, 0xcb, 0xee, + 0xd5, 0x63, 0x71, 0xe4, 0x95, 0x94, 0x05, 0x74, 0xd1, 0xe9, 0x92, 0x44, 0xc5, 0x6e, 0xc2, 0x90, + 0x00, 0xc5, 0x92, 0x2b, 0x45, 0x9f, 0x9f, 0x24, 0xb0, 0xde, 0x87, 0xf3, 0xe8, 0x2f, 0x74, 0x5b, + 0xfb, 0x4d, 0xba, 0x13, 0x18, 0x97, 0x4a, 0x7a, 0x7d, 0xd6, 0x1f, 0xc2, 0x5c, 0x1a, 0x6f, 0xcf, + 0x2f, 0x9b, 0xa7, 0x3b, 0xf9, 0xf3, 0x3c, 0x4c, 0xaf, 0x06, 0xfa, 0x86, 0x4b, 0xa5, 0x3c, 0x49, + 0x49, 0xc3, 0x81, 0x3a, 0x59, 0x39, 0x93, 0x96, 0x66, 0xe3, 0x2d, 0xed, 0xba, 0x6b, 0xbe, 0x5b, + 0x7e, 0x0d, 0xb6, 0x6c, 0xa9, 0x0b, 0xaf, 0xaf, 0x42, 0xff, 0x06, 0x9b, 0xaa, 0xfb, 0x44, 0xdf, + 0x71, 0x0e, 0x06, 0xc2, 0xeb, 0xa6, 0x6c, 0x89, 0x64, 0x3f, 0xc8, 0xbd, 0xc4, 0xa5, 0xd6, 0xfe, + 0xde, 0xf9, 0x23, 0x56, 0xce, 0x24, 0xee, 0xb7, 0xbe, 0x03, 0xa3, 0xe5, 0xc6, 0x21, 0x0f, 0xc9, + 0xf4, 0x5a, 0xb1, 0xcf, 0x52, 0xc3, 0xac, 0x9c, 0xb1, 0x75, 0x42, 0x72, 0x9d, 0xdf, 0x6a, 0x18, + 0xcc, 0xc8, 0xa9, 0xc1, 0x36, 0x6b, 0xe5, 0x76, 0xbb, 0x32, 0x0c, 0x83, 0xfc, 0xa2, 0xa5, 0xf5, + 0x25, 0xcc, 0x89, 0x40, 0x1e, 0xee, 0x1d, 0xc5, 0x70, 0x9f, 0x20, 0x8a, 0xd5, 0xea, 0x16, 0x7c, + 0x73, 0x19, 0x00, 0x6d, 0xa1, 0xd5, 0x56, 0x83, 0x7e, 0x23, 0x22, 0x09, 0x35, 0x88, 0xf5, 0x36, + 0x8c, 0x28, 0x0d, 0xe1, 0x86, 0x5f, 0x5b, 0xec, 0x50, 0x5b, 0xd3, 0xc6, 0x2d, 0x5e, 0x79, 0x75, + 0xf7, 0xbc, 0xd1, 0x76, 0x91, 0x25, 0x87, 0x5b, 0x08, 0x2e, 0xcc, 0xc4, 0x06, 0x41, 0x94, 0x84, + 0x41, 0xed, 0xd1, 0x79, 0xa8, 0xa3, 0xfa, 0x1d, 0xdf, 0xc2, 0xe7, 0x4f, 0xb4, 0x85, 0xb7, 0xfe, + 0x45, 0x1e, 0x8d, 0xcb, 0x84, 0x3e, 0x62, 0x7e, 0x3a, 0xdd, 0x57, 0x58, 0x81, 0x11, 0x6c, 0xfd, + 0xa2, 0xbc, 0x30, 0xd8, 0x3d, 0x0e, 0x65, 0xf8, 0x27, 0x47, 0xa5, 0x33, 0x18, 0x7c, 0x12, 0xb1, + 0x91, 0x8f, 0x61, 0x68, 0xa9, 0xd5, 0x40, 0x09, 0x7d, 0xa7, 0x90, 0x20, 0x99, 0x58, 0x9f, 0x60, + 0x95, 0xb7, 0xd9, 0x27, 0xcc, 0xdd, 0x3b, 0xb6, 0x06, 0x89, 0xac, 0xdc, 0x81, 0x2c, 0x2b, 0x77, + 0x30, 0x66, 0xe5, 0x5a, 0x30, 0xb0, 0xe9, 0x37, 0x44, 0x6e, 0x9b, 0x89, 0xf9, 0x31, 0xa1, 0x38, + 0x84, 0xd9, 0x1c, 0x65, 0xfd, 0xf7, 0x1c, 0xcc, 0x2e, 0xd3, 0x30, 0x75, 0x0c, 0x19, 0x5a, 0xc9, + 0xbd, 0xb0, 0x56, 0xf2, 0xcf, 0xa3, 0x15, 0xd5, 0xea, 0xbe, 0xac, 0x56, 0xf7, 0x67, 0xb5, 0x7a, + 0x20, 0xbb, 0xd5, 0xcb, 0x30, 0xc8, 0x9b, 0xca, 0x2c, 0xf9, 0xd5, 0x90, 0x1e, 0x46, 0x96, 0xbc, + 0x1e, 0x45, 0x67, 0x73, 0x1c, 0xdb, 0x48, 0xae, 0x39, 0x81, 0x6e, 0xc9, 0x8b, 0x9f, 0xd6, 0x0f, + 0xf0, 0xaa, 0xf1, 0x9a, 0x57, 0x7f, 0xac, 0x79, 0x84, 0x87, 0xf8, 0x17, 0x1a, 0x3f, 0x41, 0x60, + 0x54, 0x1c, 0x63, 0x4b, 0x0a, 0x72, 0x05, 0x46, 0x57, 0x5b, 0xf7, 0x3c, 0xbf, 0x4e, 0x37, 0x5b, + 0x4d, 0x2e, 0x7d, 0xd8, 0xd6, 0x41, 0xc2, 0x53, 0x22, 0x4a, 0x88, 0xdc, 0x0f, 0x08, 0x88, 0xb9, + 0x1f, 0x18, 0x6c, 0x77, 0xde, 0xe6, 0x38, 0xe1, 0x88, 0x61, 0x7f, 0x77, 0xb3, 0xdc, 0x95, 0x89, + 0xdf, 0x8b, 0x70, 0x0f, 0xce, 0xdb, 0xb4, 0xdd, 0x74, 0xd8, 0x9e, 0xee, 0xd0, 0xe3, 0xf4, 0xaa, + 0xcd, 0x57, 0x52, 0xae, 0x09, 0x9a, 0x31, 0x15, 0xaa, 0xca, 0xf9, 0x2e, 0x55, 0x3e, 0x84, 0xab, + 0xcb, 0x34, 0x34, 0x27, 0xd4, 0xc8, 0xdf, 0x2c, 0x1a, 0xbf, 0x02, 0xc3, 0x81, 0xe9, 0x2b, 0x97, + 0xd7, 0xde, 0x52, 0x19, 0x77, 0xdf, 0x94, 0xa7, 0x49, 0x42, 0x8e, 0xfa, 0xcb, 0xfa, 0x04, 0x4a, + 0x59, 0xc5, 0x9d, 0x2c, 0xe4, 0xd5, 0x85, 0x2b, 0xd9, 0x02, 0x44, 0x75, 0x97, 0x40, 0xfa, 0xd5, + 0xc5, 0x27, 0xd4, 0xab, 0xb6, 0xa6, 0x2b, 0x5e, 0xfc, 0x61, 0x55, 0x64, 0xf0, 0xdf, 0x0b, 0x54, + 0xb7, 0x86, 0x47, 0xd6, 0xa6, 0x80, 0x48, 0xaf, 0x65, 0x18, 0x96, 0x30, 0xa1, 0xd7, 0xd9, 0xd4, + 0x9a, 0x4a, 0x85, 0x36, 0xa4, 0x00, 0xc5, 0x66, 0xfd, 0x40, 0x1e, 0xdf, 0x98, 0x1c, 0x27, 0xbb, + 0x37, 0x7b, 0x92, 0xf3, 0x1a, 0xcb, 0x83, 0xf3, 0xa6, 0x6c, 0xdd, 0x2d, 0x5f, 0xd0, 0xdc, 0xf2, + 0xdc, 0x1b, 0x7f, 0xc5, 0x74, 0x13, 0x0b, 0x4f, 0x83, 0x06, 0x22, 0x97, 0x75, 0xe7, 0xfb, 0x58, + 0xf2, 0x22, 0xee, 0x1d, 0x98, 0x4b, 0x2b, 0x50, 0xb3, 0x03, 0x95, 0x87, 0x57, 0xec, 0x77, 0x16, + 0xe1, 0xb2, 0xcc, 0x2e, 0xe5, 0x79, 0x61, 0x10, 0xfa, 0x4e, 0xbb, 0x5a, 0xf7, 0xdd, 0x76, 0xc4, + 0x65, 0xc1, 0x20, 0x87, 0x08, 0x4d, 0xf0, 0xa3, 0x30, 0x4e, 0x23, 0x30, 0xd6, 0xaf, 0xe4, 0xc0, + 0x32, 0xe2, 0xb4, 0xb0, 0x9f, 0xb7, 0x7c, 0xef, 0x89, 0xdb, 0xd0, 0x8e, 0x9f, 0x5e, 0x37, 0x5c, + 0x9f, 0xfc, 0x4e, 0x62, 0x3c, 0x44, 0x5c, 0xcc, 0x99, 0x77, 0x62, 0xee, 0x48, 0xbe, 0xf1, 0xc4, + 0xd8, 0x2d, 0xf3, 0x42, 0x84, 0x72, 0x53, 0xfe, 0xcf, 0x1c, 0x5c, 0xeb, 0x5a, 0x07, 0xd1, 0x9e, + 0x3d, 0x28, 0xc4, 0x71, 0x62, 0x04, 0x95, 0xb4, 0xb8, 0x8d, 0xa4, 0x84, 0xdd, 0xbb, 0x3c, 0x0e, + 0x5d, 0xc6, 0x37, 0xb5, 0x95, 0xe4, 0x84, 0xbc, 0xd3, 0xd7, 0x1e, 0xf3, 0x57, 0x78, 0xa1, 0xd3, + 0x5c, 0x40, 0x07, 0x40, 0x5f, 0x74, 0xa7, 0x20, 0x64, 0xd0, 0x5a, 0x3c, 0x4d, 0x86, 0x46, 0x6c, + 0x7d, 0x8a, 0xdf, 0x75, 0x7a, 0xa5, 0x4f, 0xf6, 0xa9, 0x2d, 0xc0, 0xb5, 0x58, 0xec, 0xc0, 0x73, + 0x08, 0x09, 0x61, 0x86, 0xa9, 0x9f, 0xed, 0xbd, 0x97, 0x7d, 0xaf, 0xd3, 0xfe, 0xf9, 0xf4, 0xfa, + 0x1f, 0xe7, 0x78, 0x30, 0xa7, 0x5e, 0xac, 0xe8, 0xe8, 0x05, 0x80, 0x08, 0x1a, 0x0b, 0xea, 0x57, + 0x88, 0xdd, 0xbb, 0xdc, 0xe4, 0xc6, 0x53, 0x85, 0x7d, 0x2e, 0x40, 0x63, 0xfb, 0xf9, 0xf6, 0xe4, + 0x9b, 0x18, 0x30, 0xa0, 0x4a, 0x3f, 0x99, 0xde, 0xdf, 0x91, 0xfe, 0x8f, 0x53, 0xf2, 0x1d, 0xc0, + 0x34, 0x9b, 0x01, 0xca, 0x9d, 0xf0, 0xc0, 0xf3, 0xdd, 0x50, 0x5e, 0x4f, 0x21, 0x5b, 0x22, 0x23, + 0x00, 0xe7, 0xfa, 0xf0, 0x67, 0x47, 0xa5, 0x77, 0x4f, 0x93, 0xf7, 0x53, 0xca, 0xdc, 0x56, 0x59, + 0x04, 0xac, 0x59, 0xe8, 0x5b, 0xb0, 0xd7, 0x70, 0xc2, 0xb3, 0xd7, 0xd4, 0x84, 0x67, 0xaf, 0x59, + 0x7f, 0x95, 0x87, 0x12, 0xcf, 0x59, 0x82, 0x71, 0x26, 0x91, 0xd7, 0x42, 0x0b, 0x5c, 0x39, 0xa9, + 0x83, 0x21, 0x96, 0x93, 0x24, 0x7f, 0x92, 0x9c, 0x24, 0xbf, 0x04, 0x19, 0x2e, 0xab, 0x13, 0x78, + 0x01, 0x5e, 0x3b, 0x3e, 0x2a, 0x5d, 0x8b, 0xbc, 0x00, 0x1c, 0x9b, 0xe6, 0x0e, 0xc8, 0x28, 0x22, + 0xe9, 0xbf, 0xe8, 0x7f, 0x0e, 0xff, 0xc5, 0x1d, 0x18, 0x42, 0x63, 0x66, 0x75, 0x4b, 0x44, 0x7e, + 0xe2, 0xf0, 0xc4, 0x0c, 0x45, 0x35, 0x57, 0x4f, 0x07, 0x28, 0xc9, 0xac, 0x7f, 0x94, 0x87, 0x2b, + 0xd9, 0x3a, 0x17, 0x75, 0x5b, 0x04, 0x88, 0x22, 0x5c, 0xba, 0x45, 0xd4, 0xe0, 0xb7, 0xf3, 0x94, + 0xee, 0xa9, 0x88, 0x36, 0x8d, 0x8f, 0xed, 0x7d, 0xe4, 0x4d, 0xeb, 0xd8, 0x71, 0x8a, 0x71, 0x01, + 0x5b, 0x64, 0xb3, 0x15, 0x20, 0x23, 0x9b, 0xad, 0x80, 0x91, 0x3d, 0x98, 0xdd, 0xf2, 0xdd, 0x27, + 0x4e, 0x48, 0x1f, 0xd0, 0x67, 0xfc, 0xb2, 0xd0, 0x92, 0xb8, 0x21, 0xc4, 0xaf, 0xcf, 0xdf, 0x38, + 0x3e, 0x2a, 0xbd, 0xd2, 0xe6, 0x24, 0x98, 0xb1, 0x8c, 0xdf, 0xfd, 0xac, 0x25, 0x2f, 0x0d, 0x65, + 0x09, 0xb2, 0xfe, 0x6d, 0x0e, 0x2e, 0xe0, 0xb6, 0x5c, 0xb8, 0x5d, 0x65, 0xe1, 0xcf, 0x15, 0x58, + 0xa9, 0x37, 0x50, 0x8c, 0x45, 0x0c, 0xac, 0x34, 0x6e, 0xa2, 0xdb, 0x06, 0x19, 0x59, 0x85, 0x51, + 0xf1, 0x1b, 0xbf, 0xbf, 0x3e, 0x34, 0x08, 0x66, 0xb4, 0x09, 0x0b, 0x87, 0x3a, 0x77, 0x15, 0xe1, + 0xc0, 0x16, 0xc2, 0xf0, 0xc2, 0xa6, 0xad, 0xf3, 0x5a, 0x3f, 0xcd, 0xc3, 0xc5, 0x5d, 0xea, 0xbb, + 0x8f, 0x9e, 0x65, 0x34, 0x66, 0x13, 0xa6, 0x25, 0x88, 0xe7, 0x2d, 0x31, 0x3e, 0x31, 0x9e, 0xcf, + 0x52, 0x56, 0x55, 0x24, 0x3e, 0x91, 0x5f, 0x5c, 0x2a, 0xe3, 0x29, 0x42, 0x26, 0xdf, 0x82, 0xe1, + 0x58, 0xe6, 0x20, 0xec, 0x7f, 0xf9, 0x85, 0x46, 0x5d, 0xb5, 0x72, 0xc6, 0x56, 0x94, 0xe4, 0xd7, + 0xb2, 0x8f, 0xaa, 0x84, 0xeb, 0xa3, 0x97, 0xff, 0x13, 0x3f, 0x58, 0xf6, 0xb1, 0x3a, 0x1a, 0x36, + 0xe5, 0x83, 0x5d, 0x39, 0x63, 0x67, 0x95, 0x54, 0x19, 0x85, 0x91, 0x32, 0x9e, 0xdb, 0x31, 0xcb, + 0xfd, 0x7f, 0xe4, 0xe1, 0xb2, 0xbc, 0xf8, 0x93, 0xa1, 0xe6, 0xcf, 0x61, 0x56, 0x82, 0xca, 0x6d, + 0xb6, 0x61, 0xa0, 0x0d, 0x53, 0xd3, 0x3c, 0xa7, 0xac, 0xd4, 0xb4, 0x23, 0x68, 0x22, 0x65, 0x67, + 0xb1, 0xbf, 0x1c, 0xef, 0xe7, 0xc7, 0x69, 0x79, 0x9c, 0xd0, 0x0b, 0xa9, 0xcf, 0x99, 0x86, 0x6a, + 0x8c, 0xf9, 0xb3, 0x91, 0xf0, 0x9e, 0xf6, 0xbf, 0xa8, 0xf7, 0x74, 0xe5, 0x4c, 0xdc, 0x7f, 0x5a, + 0x99, 0x80, 0xb1, 0x0d, 0xfa, 0x34, 0xd2, 0xfb, 0xff, 0x9f, 0x8b, 0xa5, 0x7a, 0x60, 0x3b, 0x0c, + 0x9e, 0xf3, 0x21, 0x17, 0xa5, 0x02, 0xc2, 0x54, 0x0f, 0xfa, 0x0e, 0x83, 0x93, 0xae, 0xc2, 0x10, + 0x3f, 0xcc, 0x6e, 0x9c, 0xc0, 0xc2, 0x57, 0x37, 0x78, 0xf8, 0xb5, 0xca, 0x06, 0x37, 0xf6, 0x05, + 0xbf, 0xf5, 0x00, 0xae, 0x8a, 0x18, 0x6f, 0xb3, 0xf3, 0xb1, 0xa0, 0x53, 0x2e, 0x5f, 0x96, 0x03, + 0x97, 0x97, 0x69, 0x7c, 0xea, 0x31, 0x6e, 0x38, 0x7d, 0x02, 0x93, 0x06, 0x5c, 0x49, 0xc4, 0x5d, + 0xa9, 0x1a, 0x43, 0x4a, 0x74, 0x9c, 0xda, 0xba, 0x92, 0x56, 0x84, 0x5e, 0x59, 0x8b, 0x62, 0x72, + 0x58, 0x3f, 0x3a, 0x62, 0x0b, 0x4e, 0x31, 0xeb, 0xdd, 0xd0, 0xbe, 0x6b, 0x3e, 0xe3, 0xf1, 0xec, + 0x81, 0x72, 0xe5, 0x55, 0x58, 0x6b, 0xdc, 0x38, 0x0b, 0xb0, 0x26, 0x60, 0x4c, 0xa2, 0x9a, 0x34, + 0x08, 0xac, 0xff, 0x34, 0x00, 0x96, 0x50, 0x6c, 0xda, 0x09, 0xbd, 0xd4, 0xc7, 0x5e, 0xa2, 0xb2, + 0x62, 0xa1, 0x3a, 0xa7, 0xe7, 0x24, 0x8d, 0xb0, 0x7c, 0xe4, 0xe1, 0x3e, 0xaf, 0x1e, 0x41, 0x8d, + 0x91, 0x97, 0x68, 0xfd, 0x57, 0x19, 0xd3, 0x24, 0xff, 0xd8, 0xf0, 0xca, 0x76, 0xc6, 0x34, 0x69, + 0xc8, 0x4d, 0x9f, 0x32, 0x6d, 0xf3, 0x48, 0xa4, 0xef, 0x79, 0x8e, 0x44, 0xd8, 0x17, 0xa9, 0x1f, + 0x8a, 0xec, 0x98, 0xba, 0x14, 0xdf, 0xa3, 0x3c, 0xbd, 0xd7, 0x51, 0x22, 0xe3, 0x82, 0x06, 0x31, + 0xa4, 0x1a, 0x62, 0x88, 0x0b, 0x05, 0xcd, 0x67, 0xb9, 0x70, 0x40, 0xeb, 0x8f, 0x85, 0xaf, 0x58, + 0x1e, 0xe8, 0xa6, 0xf9, 0xcc, 0x79, 0x7e, 0x6a, 0xfe, 0x9d, 0x73, 0x44, 0xad, 0xce, 0x58, 0xf5, + 0x8c, 0x11, 0x71, 0xb1, 0xe4, 0x47, 0x30, 0xa5, 0xba, 0x3a, 0x16, 0xa2, 0x35, 0x3a, 0xff, 0x4a, + 0x94, 0xca, 0xf4, 0xf0, 0x91, 0x73, 0xeb, 0xc9, 0xdd, 0x5b, 0x29, 0xb4, 0x3c, 0x11, 0x41, 0x5d, + 0x22, 0xb4, 0xf8, 0x2c, 0xfd, 0xa0, 0x2b, 0x85, 0x91, 0x7c, 0x01, 0xd3, 0xd5, 0xea, 0x26, 0xbf, + 0xcc, 0x61, 0xcb, 0x03, 0x7e, 0x7b, 0x4d, 0x04, 0x6c, 0x61, 0x77, 0x07, 0x81, 0x57, 0x13, 0x97, + 0x40, 0xf4, 0xb0, 0x00, 0x3d, 0x15, 0x43, 0x9a, 0x08, 0xfd, 0xa4, 0xfc, 0x1f, 0xaa, 0xbb, 0x0a, + 0x6c, 0x2b, 0xe2, 0x36, 0xa9, 0xb8, 0x74, 0x24, 0x07, 0x76, 0xc6, 0x29, 0x5f, 0xee, 0x5b, 0x3e, + 0xe5, 0xfb, 0x83, 0xbc, 0xbc, 0xa1, 0x91, 0x3c, 0x68, 0x3d, 0xf5, 0x61, 0x5f, 0x6a, 0x0b, 0x4e, + 0xb4, 0x4e, 0xa7, 0x56, 0x8e, 0x54, 0xe4, 0x51, 0xa9, 0x4a, 0x56, 0x36, 0xa1, 0x8e, 0x1d, 0x22, + 0x84, 0x71, 0x7a, 0x8a, 0xbb, 0x22, 0x8d, 0x2b, 0x7e, 0x0e, 0xd7, 0xf7, 0xe2, 0xe7, 0x70, 0x3f, + 0x86, 0x19, 0x79, 0x35, 0x6a, 0x81, 0xb6, 0x42, 0xea, 0xcb, 0x13, 0xfb, 0x89, 0x28, 0xe9, 0x1b, + 0xa6, 0xf7, 0x2b, 0x40, 0x5f, 0xd9, 0xde, 0x10, 0x1e, 0x1d, 0xf6, 0x27, 0xb9, 0x62, 0x06, 0xc4, + 0xf1, 0x3b, 0x6f, 0x46, 0xf8, 0xdb, 0x15, 0x56, 0x5d, 0xee, 0x67, 0x71, 0x65, 0xaa, 0x3e, 0x5b, + 0x07, 0x59, 0x0b, 0x70, 0xc1, 0x2c, 0x7e, 0x8b, 0xfa, 0x87, 0x2e, 0xee, 0xbd, 0xab, 0x34, 0x94, + 0x85, 0xe6, 0xa2, 0x42, 0x89, 0x1e, 0x50, 0x2d, 0xcc, 0xc0, 0xff, 0x9d, 0x87, 0x52, 0x6a, 0x23, + 0xca, 0x41, 0xe0, 0xee, 0xb7, 0x30, 0x83, 0xc6, 0x45, 0xe8, 0x7f, 0xe0, 0xb6, 0x1a, 0xba, 0x21, + 0xf9, 0xd8, 0x6d, 0x35, 0x6c, 0x84, 0x32, 0x1b, 0xa4, 0xda, 0xd9, 0x43, 0x02, 0xcd, 0x44, 0x0e, + 0x3a, 0x7b, 0x35, 0x46, 0xa4, 0xdb, 0x20, 0x82, 0x8c, 0x5c, 0x87, 0x21, 0x99, 0x6d, 0xad, 0x2f, + 0xf2, 0x9e, 0xc9, 0x34, 0x6b, 0x12, 0x47, 0x3e, 0x82, 0xe1, 0x75, 0x1a, 0x3a, 0x0d, 0x27, 0x74, + 0xc4, 0xd8, 0x91, 0x0f, 0x61, 0x48, 0x70, 0xa5, 0x20, 0x56, 0xe8, 0xe1, 0x43, 0x01, 0xb1, 0x15, + 0x0b, 0x2a, 0xd0, 0x0d, 0xda, 0x4d, 0xe7, 0x99, 0x0a, 0x26, 0x65, 0x0a, 0x8c, 0x40, 0xe4, 0x1d, + 0x33, 0xe4, 0x22, 0x3a, 0x3e, 0x4b, 0x55, 0x48, 0x14, 0x90, 0xb1, 0x82, 0x61, 0x20, 0x91, 0xaa, + 0x45, 0x36, 0x41, 0x2b, 0x95, 0xdb, 0xa0, 0xb4, 0x4d, 0x46, 0xeb, 0xb7, 0x46, 0xe1, 0xec, 0x96, + 0xb3, 0xef, 0xb6, 0xd8, 0x8e, 0xc2, 0xa6, 0x81, 0xd7, 0xf1, 0xeb, 0x94, 0x94, 0x61, 0xc2, 0x0c, + 0xe0, 0xee, 0x11, 0x9e, 0xce, 0x36, 0x4d, 0x26, 0x8c, 0xcc, 0xc3, 0x88, 0xba, 0x34, 0x2e, 0x76, + 0x3a, 0x29, 0x97, 0xc9, 0x57, 0xce, 0xd8, 0x11, 0x19, 0x79, 0xcf, 0x38, 0x7c, 0x9c, 0x54, 0xf9, + 0x0f, 0x90, 0x76, 0x9e, 0x47, 0xd8, 0xb6, 0xbc, 0x86, 0xb9, 0x5b, 0xe3, 0x27, 0x6c, 0x3f, 0x48, + 0x9c, 0x47, 0x0e, 0x18, 0x35, 0x4e, 0x38, 0x65, 0x71, 0xa3, 0x9a, 0x99, 0xbd, 0x3e, 0xe5, 0xa4, + 0xf2, 0x4b, 0x18, 0x7d, 0xd0, 0xd9, 0xa3, 0xf2, 0xe4, 0x75, 0x50, 0x6c, 0xde, 0xe2, 0xd7, 0x12, + 0x04, 0x7e, 0xf7, 0x4d, 0xfe, 0x15, 0x3f, 0xee, 0xec, 0xd1, 0xe4, 0xb3, 0x08, 0x6c, 0xd5, 0xd4, + 0x84, 0x91, 0x03, 0x28, 0xc4, 0x6f, 0x10, 0x88, 0x2e, 0xed, 0x72, 0xef, 0x01, 0x13, 0xfd, 0x68, + 0x8f, 0x2f, 0xf0, 0xb8, 0x66, 0xa3, 0x90, 0x84, 0x54, 0xf2, 0x63, 0x98, 0x49, 0x75, 0x89, 0xab, + 0x3b, 0x90, 0xdd, 0xbd, 0xed, 0xb8, 0x04, 0xc5, 0xb4, 0x26, 0x2f, 0x5c, 0x1a, 0x25, 0xa7, 0x97, + 0x42, 0x1a, 0x30, 0x19, 0x8b, 0x8c, 0x17, 0x2f, 0xcc, 0x64, 0xc7, 0xda, 0xe3, 0xae, 0x49, 0x26, + 0x69, 0x4e, 0x2d, 0x2b, 0x2e, 0x92, 0xac, 0xc1, 0x88, 0xf2, 0x45, 0x89, 0xdc, 0x7c, 0x69, 0x7e, + 0xb7, 0xe2, 0xf1, 0x51, 0x69, 0x3a, 0xf2, 0xbb, 0x19, 0x32, 0x23, 0x01, 0xe4, 0x97, 0xe1, 0xaa, + 0x1a, 0xa2, 0x9b, 0x7e, 0xba, 0x87, 0x52, 0x3c, 0xee, 0x70, 0x33, 0x3e, 0xc2, 0xb3, 0xe8, 0x77, + 0xef, 0x56, 0xf2, 0xc5, 0xdc, 0xca, 0x19, 0xbb, 0xb7, 0x68, 0xf2, 0xab, 0x39, 0x38, 0x97, 0x51, + 0xea, 0x18, 0x96, 0xda, 0xd3, 0x6d, 0x8c, 0x96, 0x27, 0xde, 0xfb, 0x73, 0x1b, 0xd1, 0xfd, 0x58, + 0xe9, 0x3f, 0x36, 0xda, 0x9d, 0x51, 0x12, 0xb9, 0x03, 0xb0, 0xef, 0x86, 0x62, 0x8c, 0x61, 0x9a, + 0xba, 0xe4, 0x07, 0xca, 0xd4, 0xb6, 0xef, 0x86, 0x62, 0xa4, 0xfd, 0x7e, 0xae, 0xe7, 0xbc, 0x8e, + 0xd9, 0xeb, 0x46, 0xe7, 0x5f, 0xed, 0x36, 0xe9, 0x45, 0xd4, 0x95, 0x3b, 0xc7, 0x47, 0xa5, 0x37, + 0x54, 0x0a, 0xb4, 0x3a, 0x52, 0xc9, 0x5b, 0xbe, 0x35, 0x47, 0xd1, 0x19, 0xed, 0xe9, 0xb9, 0xb4, + 0xbc, 0x01, 0x83, 0xe8, 0x99, 0x0a, 0x8a, 0xe3, 0x68, 0xbb, 0x61, 0xe2, 0x2e, 0xf4, 0x5f, 0xe9, + 0xbb, 0x35, 0x41, 0x43, 0x56, 0x98, 0x0d, 0x84, 0xbb, 0x45, 0x69, 0xb3, 0x88, 0x34, 0x7f, 0xc2, + 0x8e, 0xe6, 0x28, 0x99, 0x7f, 0xc7, 0x78, 0x9e, 0xc4, 0x64, 0xab, 0x00, 0x0c, 0xfb, 0x62, 0xba, + 0xbd, 0xdf, 0x3f, 0xdc, 0x5f, 0x18, 0xe0, 0x33, 0x82, 0xbc, 0x4b, 0xf2, 0xeb, 0xc3, 0xfc, 0xe2, + 0xf9, 0x4e, 0xcb, 0x7d, 0xe4, 0x46, 0x33, 0xb3, 0xee, 0xd3, 0x8e, 0xde, 0x09, 0x13, 0x16, 0x67, + 0xc6, 0x8b, 0x60, 0xca, 0xfd, 0x9d, 0xef, 0xe9, 0xfe, 0x7e, 0x53, 0x3b, 0x28, 0xd6, 0xd2, 0xf9, + 0x72, 0xcb, 0xc2, 0x74, 0x37, 0x47, 0x27, 0xc8, 0x5f, 0xc3, 0x20, 0x66, 0xe0, 0xe5, 0xa7, 0xf0, + 0xa3, 0xf3, 0xb7, 0x44, 0x77, 0x76, 0xa9, 0x3e, 0x4f, 0xd9, 0x2b, 0x92, 0x49, 0x70, 0x8d, 0x23, + 0xc0, 0xd0, 0x38, 0x42, 0xc8, 0x36, 0x4c, 0x6d, 0xb1, 0x8d, 0x2e, 0xbf, 0xd1, 0xd0, 0xf6, 0x85, + 0x4b, 0x90, 0x3b, 0x1b, 0x71, 0xa3, 0xdd, 0x96, 0xe8, 0x1a, 0x55, 0x78, 0x7d, 0xaf, 0x99, 0xc2, + 0x4e, 0x96, 0x60, 0xa2, 0x4a, 0x1d, 0xbf, 0x7e, 0xf0, 0x80, 0x3e, 0x63, 0x46, 0x86, 0xf1, 0x34, + 0x4e, 0x80, 0x18, 0xd6, 0x5e, 0x44, 0xe9, 0x91, 0x55, 0x26, 0x13, 0xf9, 0x14, 0x06, 0xab, 0x9e, + 0x1f, 0x56, 0x9e, 0x89, 0xd9, 0x5a, 0x9e, 0xd3, 0x72, 0x60, 0xe5, 0xbc, 0x7c, 0x1e, 0x28, 0xf0, + 0xfc, 0xb0, 0xb6, 0x67, 0x64, 0x82, 0xe3, 0x24, 0xe4, 0x19, 0x4c, 0x9b, 0x33, 0xa5, 0x08, 0xb4, + 0x1f, 0x16, 0xc6, 0x4d, 0xda, 0x74, 0xcc, 0x49, 0x2a, 0x37, 0x84, 0xf4, 0x2b, 0xf1, 0xf9, 0xf8, + 0x11, 0xe2, 0x75, 0x8b, 0x20, 0x8d, 0x9f, 0xac, 0xe3, 0xbb, 0x4a, 0xbc, 0x45, 0xe5, 0x80, 0x07, + 0xe8, 0x8f, 0x44, 0xb9, 0x06, 0x3b, 0x38, 0xdb, 0xa2, 0x26, 0x9c, 0x20, 0xfe, 0x18, 0x97, 0x9d, + 0x60, 0x25, 0x5b, 0x70, 0x76, 0x27, 0xa0, 0x5b, 0x3e, 0x7d, 0xe2, 0xd2, 0xa7, 0x52, 0x1e, 0x44, + 0x89, 0xd9, 0x98, 0xbc, 0x36, 0xc7, 0xa6, 0x09, 0x4c, 0x32, 0x93, 0xf7, 0x00, 0xb6, 0xdc, 0x56, + 0x8b, 0x36, 0xf0, 0xb0, 0x7f, 0x14, 0x45, 0xe1, 0x41, 0x46, 0x1b, 0xa1, 0x35, 0xaf, 0xd5, 0xd4, + 0x55, 0xaa, 0x11, 0x93, 0x0a, 0x8c, 0xaf, 0xb6, 0xea, 0xcd, 0x8e, 0x08, 0xca, 0x09, 0x70, 0xa6, + 0x14, 0x09, 0x23, 0x5d, 0x8e, 0xa8, 0x25, 0x3e, 0x72, 0x93, 0x85, 0x3c, 0x00, 0x22, 0x00, 0x62, + 0xd4, 0x3a, 0x7b, 0x4d, 0x2a, 0x3e, 0x77, 0x74, 0x50, 0x4a, 0x41, 0x38, 0xdc, 0x8d, 0x3c, 0x8c, + 0x09, 0xb6, 0xb9, 0xf7, 0x60, 0x54, 0x1b, 0xf3, 0x29, 0xd9, 0x51, 0xa6, 0xf5, 0xec, 0x28, 0x23, + 0x7a, 0x16, 0x94, 0x7f, 0x9a, 0x83, 0x8b, 0xe9, 0xdf, 0x92, 0xb0, 0x4d, 0x36, 0x61, 0x44, 0x01, + 0xd5, 0x7d, 0x38, 0x69, 0x70, 0xc7, 0xb6, 0x76, 0xfc, 0x83, 0x96, 0x33, 0x8f, 0xde, 0xfa, 0x48, + 0xc6, 0x73, 0x9c, 0x82, 0xfd, 0xed, 0x61, 0x98, 0xc6, 0x7b, 0x1f, 0xf1, 0x79, 0xea, 0x13, 0xcc, + 0x72, 0x84, 0x30, 0xed, 0x50, 0x47, 0xf8, 0x77, 0x39, 0x3c, 0x9e, 0xef, 0xcf, 0x60, 0x20, 0x6f, + 0xeb, 0x91, 0x48, 0x79, 0xed, 0x1d, 0x27, 0x09, 0xd4, 0x9b, 0x10, 0x85, 0x28, 0xbd, 0x6e, 0x04, + 0xc2, 0x9c, 0x78, 0xd2, 0xeb, 0x3f, 0xe9, 0xa4, 0xb7, 0xa3, 0x26, 0x3d, 0x9e, 0x3d, 0xe7, 0x35, + 0x6d, 0xd2, 0x7b, 0xf9, 0xb3, 0xdd, 0xe0, 0xcb, 0x9e, 0xed, 0x86, 0x5e, 0x6c, 0xb6, 0x1b, 0x7e, + 0xce, 0xd9, 0xee, 0x1e, 0x4c, 0x6c, 0x50, 0xda, 0xd0, 0x8e, 0x27, 0x47, 0xa2, 0xd5, 0xb3, 0x45, + 0xd1, 0xf1, 0x9c, 0x76, 0x46, 0x19, 0xe3, 0xca, 0x9c, 0x35, 0xe1, 0x6f, 0x66, 0xd6, 0x1c, 0x7d, + 0xc9, 0xb3, 0xe6, 0xd8, 0x8b, 0xcc, 0x9a, 0x89, 0xa9, 0x6f, 0xfc, 0xd4, 0x53, 0xdf, 0x8b, 0xcc, + 0x56, 0xff, 0x38, 0x0f, 0xb3, 0xec, 0x03, 0x68, 0x3e, 0xa1, 0xd5, 0xea, 0x8a, 0x08, 0xe0, 0x8a, + 0x02, 0xa5, 0x0e, 0xbc, 0x40, 0xde, 0x75, 0xc0, 0xbf, 0x19, 0xac, 0xed, 0xf9, 0x32, 0xd8, 0x04, + 0xff, 0x26, 0x15, 0x18, 0xe4, 0x5f, 0x48, 0xb1, 0xcf, 0x48, 0x4d, 0x95, 0x21, 0x57, 0xff, 0xbe, + 0x6c, 0xc1, 0x49, 0xee, 0xc2, 0x74, 0xda, 0xa7, 0x22, 0xdc, 0x18, 0x53, 0xed, 0x94, 0xcf, 0xe4, + 0x35, 0x98, 0x8c, 0x7d, 0x0c, 0xfc, 0xd9, 0x17, 0x7b, 0x22, 0x30, 0x3e, 0x84, 0x17, 0x51, 0xcf, + 0x02, 0x14, 0x93, 0xad, 0x10, 0xf3, 0xf8, 0x6b, 0x20, 0x6e, 0x78, 0x0b, 0x6b, 0x3b, 0xbe, 0xbf, + 0xb6, 0x05, 0xda, 0xfa, 0x18, 0x83, 0xa5, 0x95, 0x80, 0x40, 0xd3, 0xef, 0x8a, 0xa6, 0xdf, 0x15, + 0xa1, 0xdf, 0x2d, 0x4d, 0xbf, 0xec, 0x6f, 0xab, 0x82, 0x21, 0xd2, 0x3a, 0xbf, 0xba, 0x73, 0x35, + 0x24, 0x2e, 0x6c, 0x8b, 0x75, 0x24, 0x51, 0x05, 0x89, 0xb7, 0xfe, 0x3c, 0xc7, 0xc3, 0x2d, 0xfe, + 0x6f, 0x5c, 0x8e, 0x5e, 0x24, 0x04, 0xe2, 0xd7, 0xa2, 0x44, 0x2e, 0x22, 0xe9, 0x8c, 0xef, 0xd4, + 0x1f, 0x47, 0x31, 0x28, 0xdf, 0x67, 0x73, 0xa9, 0x8e, 0x10, 0xc6, 0xd0, 0xac, 0xd2, 0x94, 0x8e, + 0xdc, 0xbd, 0x2b, 0x27, 0x59, 0x91, 0xcf, 0x86, 0x83, 0xcd, 0x49, 0x56, 0x67, 0xc0, 0x28, 0xe0, + 0x49, 0xcb, 0xe6, 0x79, 0x48, 0x52, 0x6b, 0xf0, 0x4e, 0x32, 0x93, 0x06, 0x5a, 0xb2, 0x51, 0x26, + 0x0d, 0x5d, 0x8d, 0x51, 0x4e, 0x8d, 0x1d, 0xb8, 0x60, 0xd3, 0x43, 0xef, 0x09, 0x7d, 0xb9, 0x62, + 0xbf, 0x82, 0xf3, 0xa6, 0x40, 0x7e, 0xe7, 0x92, 0x3f, 0x10, 0xf2, 0x71, 0xfa, 0xb3, 0x22, 0x82, + 0x81, 0x3f, 0x2b, 0xc2, 0x5f, 0x27, 0x60, 0x7f, 0xea, 0x6b, 0x33, 0xe2, 0x2c, 0x0f, 0x2e, 0x9a, + 0xc2, 0xcb, 0x8d, 0x06, 0xbe, 0x4c, 0x5c, 0x77, 0xdb, 0x4e, 0x2b, 0x24, 0x9b, 0x30, 0xaa, 0xfd, + 0x8c, 0xf9, 0x99, 0x34, 0x8c, 0xd8, 0x37, 0x46, 0x00, 0x23, 0xbb, 0x73, 0x04, 0xb6, 0x28, 0x94, + 0xe2, 0xea, 0x61, 0x2a, 0xd3, 0xcb, 0xac, 0xc0, 0xb8, 0xf6, 0x53, 0x1d, 0xc6, 0xe0, 0x04, 0xab, + 0x95, 0x60, 0x2a, 0xcc, 0x64, 0xb1, 0xea, 0x30, 0x97, 0xa6, 0x34, 0xfe, 0x0c, 0x00, 0x59, 0x8a, + 0xb2, 0xfc, 0xf5, 0x8e, 0x23, 0x9e, 0xcc, 0xca, 0xf0, 0x67, 0xfd, 0x83, 0x7e, 0xb8, 0x20, 0x3a, + 0xe3, 0x65, 0xf6, 0x38, 0xf9, 0x01, 0x8c, 0x6a, 0x7d, 0x2c, 0x94, 0x7e, 0x45, 0xde, 0x98, 0xcc, + 0x1a, 0x0b, 0xdc, 0x1f, 0xd6, 0x41, 0x40, 0x2d, 0xd6, 0xdd, 0x2b, 0x67, 0x6c, 0x5d, 0x24, 0x69, + 0xc2, 0x84, 0xd9, 0xd1, 0xc2, 0x25, 0x78, 0x2d, 0xb5, 0x10, 0x93, 0x54, 0xbe, 0x11, 0xd0, 0xa8, + 0xa5, 0x76, 0xf7, 0xca, 0x19, 0x3b, 0x26, 0x9b, 0x7c, 0x03, 0x67, 0x13, 0xbd, 0x2c, 0xfc, 0xbd, + 0xaf, 0xa6, 0x16, 0x98, 0xa0, 0xe6, 0x07, 0x4d, 0x3e, 0x82, 0x33, 0x8b, 0x4d, 0x16, 0x42, 0x1a, + 0x30, 0xa6, 0x77, 0xbc, 0xf0, 0x59, 0x5e, 0xed, 0xa2, 0x4a, 0x4e, 0xc8, 0x37, 0xd0, 0x42, 0x97, + 0xd8, 0xf7, 0xcf, 0xcc, 0xc3, 0x33, 0x83, 0x78, 0x18, 0x06, 0xf9, 0x6f, 0xeb, 0x0f, 0x72, 0x70, + 0x61, 0xcb, 0xa7, 0x01, 0x6d, 0xd5, 0xa9, 0x71, 0xf7, 0xe4, 0x05, 0x47, 0x44, 0xd6, 0xb9, 0x55, + 0xfe, 0x85, 0xcf, 0xad, 0xac, 0x7f, 0x93, 0x83, 0x62, 0x5a, 0x95, 0xab, 0xb4, 0xd5, 0x20, 0x5b, + 0x50, 0x88, 0xb7, 0x41, 0x7c, 0x31, 0x96, 0x4a, 0xf1, 0x9e, 0xd9, 0xda, 0x95, 0x33, 0x76, 0x82, + 0x9b, 0x6c, 0xc0, 0x59, 0x0d, 0x26, 0xce, 0x8d, 0xf2, 0x27, 0x39, 0x37, 0x62, 0x3d, 0x9c, 0x60, + 0xd5, 0x8f, 0xdd, 0x56, 0x70, 0xd5, 0x5d, 0xf4, 0x0e, 0x1d, 0xb7, 0xc5, 0x0c, 0x15, 0x2d, 0x89, + 0x20, 0x44, 0x50, 0xa1, 0x76, 0x7e, 0x90, 0x84, 0x50, 0x79, 0x0d, 0x4f, 0x91, 0x58, 0x1f, 0xe2, + 0xea, 0x20, 0x9c, 0xc7, 0x3c, 0xf1, 0x81, 0x12, 0x76, 0x05, 0x06, 0xb6, 0xd7, 0xaa, 0x0b, 0x65, + 0x91, 0x46, 0x81, 0x27, 0xdf, 0x69, 0x06, 0xb5, 0xba, 0x63, 0x73, 0x84, 0xf5, 0x01, 0x90, 0x65, + 0x1a, 0x8a, 0x37, 0x46, 0x14, 0xdf, 0x75, 0x18, 0x12, 0x20, 0xc1, 0x89, 0x47, 0x22, 0xe2, 0xc5, + 0x12, 0x5b, 0xe2, 0xac, 0x2d, 0x69, 0xe7, 0x35, 0xa9, 0x13, 0x68, 0x8b, 0xfe, 0xbb, 0x30, 0xec, + 0x0b, 0x98, 0x58, 0xf3, 0x27, 0xd4, 0x13, 0x52, 0x08, 0xe6, 0x47, 0x75, 0x92, 0xc6, 0x56, 0x7f, + 0x59, 0x6b, 0x98, 0x28, 0x6b, 0x73, 0x75, 0x71, 0x81, 0x69, 0x55, 0x28, 0x4b, 0x76, 0xc7, 0x6d, + 0xbc, 0x79, 0x13, 0x52, 0x3d, 0x89, 0x02, 0xaa, 0x06, 0x27, 0x10, 0x91, 0x1e, 0x4e, 0x23, 0xb1, + 0xde, 0x54, 0x69, 0xb7, 0x52, 0xa4, 0x65, 0x3d, 0x85, 0xb4, 0x81, 0x09, 0xc5, 0x96, 0x31, 0xc8, + 0xf0, 0x65, 0x54, 0xc2, 0x81, 0x39, 0xbe, 0x85, 0x60, 0xad, 0x12, 0x0f, 0xc1, 0x7a, 0x6a, 0xda, + 0x5d, 0x80, 0x11, 0x05, 0x53, 0x11, 0x03, 0x5c, 0x57, 0x06, 0xfd, 0xee, 0x9b, 0x3c, 0xdf, 0x44, + 0x5d, 0x09, 0x88, 0xf8, 0x58, 0x11, 0xfc, 0x9b, 0xfe, 0x96, 0x8b, 0x08, 0xa8, 0x1f, 0x7e, 0xab, + 0x45, 0x44, 0x19, 0xe7, 0x4e, 0x53, 0x84, 0x41, 0xbf, 0x3b, 0x7f, 0x12, 0x45, 0x7d, 0xcb, 0x45, + 0x30, 0x45, 0x7d, 0x7b, 0x45, 0x50, 0x99, 0x9a, 0x8f, 0x0f, 0xd2, 0x44, 0x21, 0x4b, 0xc9, 0x42, + 0xe4, 0x89, 0x4a, 0x8c, 0xa3, 0x6b, 0x7f, 0x50, 0xb8, 0xc8, 0x95, 0xf5, 0x73, 0x28, 0x86, 0x29, + 0xec, 0xdb, 0x2d, 0xe6, 0x77, 0x72, 0x3c, 0x51, 0x60, 0x75, 0x53, 0x7b, 0x82, 0xb9, 0xf5, 0xc8, + 0xd3, 0x02, 0x9a, 0xb4, 0xaf, 0x5d, 0x3b, 0x60, 0xc6, 0x80, 0x26, 0xa7, 0x13, 0x1e, 0xa8, 0x44, + 0xfa, 0x78, 0xda, 0x1c, 0xa7, 0x26, 0xef, 0xc1, 0xb8, 0x06, 0x52, 0x3b, 0x41, 0xfe, 0xd4, 0x91, + 0xce, 0xee, 0x36, 0x6c, 0x93, 0xd2, 0xfa, 0xeb, 0x1c, 0x4c, 0x55, 0x9f, 0x05, 0x21, 0x3d, 0xc4, + 0xa4, 0xa8, 0x32, 0x33, 0x05, 0x3a, 0xa3, 0xd0, 0xc2, 0x52, 0x13, 0x95, 0x78, 0x9c, 0x10, 0x73, + 0x16, 0x19, 0xeb, 0xaf, 0x22, 0xc4, 0x47, 0x5e, 0xa4, 0x04, 0x55, 0x0b, 0xfe, 0xc8, 0x8b, 0x04, + 0x9b, 0xac, 0x3a, 0x39, 0x09, 0x00, 0xa2, 0x9a, 0x08, 0xb7, 0x7f, 0x95, 0x6d, 0x97, 0x03, 0x84, + 0xa2, 0xcf, 0x21, 0xe2, 0xfd, 0xd9, 0x51, 0xe9, 0x9d, 0xd3, 0x84, 0x63, 0x47, 0xa2, 0x6d, 0xad, + 0x18, 0xeb, 0xd7, 0xf2, 0x70, 0x2e, 0xa5, 0xfd, 0x55, 0x1a, 0xfe, 0x4d, 0xa8, 0xe0, 0x09, 0x8c, + 0x46, 0x95, 0xe1, 0x5e, 0x87, 0x91, 0xca, 0x36, 0xbe, 0x49, 0x12, 0xe9, 0x20, 0x78, 0x29, 0x4a, + 0xd0, 0x0b, 0xb2, 0xfe, 0x24, 0x0f, 0xe7, 0x76, 0xda, 0x01, 0xde, 0x4b, 0x5d, 0x6d, 0x3d, 0xa1, + 0xad, 0xd0, 0xf3, 0x9f, 0xe1, 0x5d, 0x3a, 0xf2, 0x36, 0x0c, 0xac, 0xd0, 0x66, 0xd3, 0x13, 0xe3, + 0xff, 0x92, 0x8c, 0x29, 0x8b, 0x53, 0x23, 0xd1, 0xca, 0x19, 0x9b, 0x53, 0x93, 0xf7, 0x60, 0x64, + 0x85, 0x3a, 0x7e, 0xb8, 0x47, 0x1d, 0x69, 0x0e, 0xc9, 0x07, 0x98, 0x34, 0x16, 0x41, 0xb0, 0x72, + 0xc6, 0x8e, 0xa8, 0xc9, 0x3c, 0xf4, 0x6f, 0x79, 0xad, 0x7d, 0x95, 0x83, 0x23, 0xa3, 0x40, 0x46, + 0xb3, 0x72, 0xc6, 0x46, 0x5a, 0xb2, 0x0e, 0xe3, 0xe5, 0x7d, 0xda, 0x0a, 0x63, 0x61, 0x12, 0xd7, + 0xb3, 0x98, 0x0d, 0xe2, 0x95, 0x33, 0xb6, 0xc9, 0x4d, 0x3e, 0x80, 0xa1, 0x65, 0xcf, 0x6b, 0xec, + 0x3d, 0x93, 0x99, 0x64, 0x4a, 0x59, 0x82, 0x04, 0xd9, 0xca, 0x19, 0x5b, 0x72, 0x54, 0x06, 0xa0, + 0x6f, 0x3d, 0xd8, 0xb7, 0x8e, 0x72, 0x50, 0x5c, 0xf4, 0x9e, 0xb6, 0x52, 0xb5, 0xfa, 0x3d, 0x53, + 0xab, 0x52, 0x7c, 0x0a, 0x7d, 0x4c, 0xaf, 0x6f, 0x41, 0xff, 0x96, 0xdb, 0xda, 0x8f, 0x6d, 0x05, + 0x53, 0xf8, 0x18, 0x15, 0xaa, 0xc7, 0x6d, 0xed, 0x93, 0x35, 0xb9, 0xbf, 0x5f, 0x93, 0xee, 0x2c, + 0xdd, 0xa8, 0x48, 0xe1, 0xd6, 0xa9, 0xa3, 0x7d, 0x3c, 0xff, 0x2d, 0x1b, 0xf8, 0x3a, 0xcc, 0x66, + 0x94, 0xab, 0x85, 0xfd, 0xf4, 0xe3, 0xc6, 0xe6, 0xef, 0xe4, 0x60, 0x26, 0xb5, 0x03, 0xe3, 0x94, + 0xcc, 0xa6, 0xe3, 0x03, 0x73, 0xa1, 0xe9, 0xd5, 0x1f, 0x9f, 0x20, 0x34, 0xd5, 0x12, 0x7e, 0x54, + 0xf9, 0x85, 0xd4, 0x19, 0x5f, 0xec, 0x61, 0x4a, 0x5d, 0xa4, 0xf5, 0xcf, 0xd3, 0xc6, 0x3a, 0x57, + 0x6e, 0x31, 0x0a, 0xc7, 0xe1, 0xae, 0x2b, 0x15, 0x81, 0x33, 0xa7, 0xcd, 0x05, 0x32, 0x2b, 0x95, + 0xfc, 0xe4, 0x77, 0xb5, 0x2c, 0x80, 0xfc, 0x8b, 0x7d, 0xff, 0x05, 0xbe, 0x4b, 0x25, 0x8b, 0x95, + 0xb9, 0xe2, 0x05, 0x61, 0x4b, 0x5d, 0x89, 0xb0, 0xd5, 0x6f, 0x72, 0x13, 0x0a, 0xf2, 0xa1, 0x23, + 0xf1, 0xa2, 0x9a, 0x2f, 0xe2, 0x7a, 0x12, 0x70, 0xf2, 0x2e, 0xcc, 0xc6, 0x61, 0xb2, 0x95, 0xfc, + 0xea, 0x71, 0x16, 0xda, 0xfa, 0xb3, 0x3c, 0x3e, 0xd4, 0xd0, 0xe5, 0xd3, 0x61, 0xfd, 0xb7, 0x59, + 0x95, 0x01, 0x5e, 0x9b, 0x55, 0x72, 0x11, 0x46, 0x36, 0xab, 0xc6, 0x0b, 0x92, 0x76, 0x04, 0x60, + 0xd5, 0x66, 0x4d, 0x28, 0xfb, 0xf5, 0x03, 0x37, 0xa4, 0xf5, 0xb0, 0xe3, 0xcb, 0x88, 0xaf, 0x04, + 0x9c, 0x58, 0x30, 0xb6, 0xdc, 0x74, 0xf7, 0xea, 0x52, 0x18, 0x57, 0x81, 0x01, 0x23, 0xaf, 0xc2, + 0xc4, 0x6a, 0x2b, 0x08, 0x9d, 0x66, 0x73, 0x9d, 0x86, 0x07, 0x5e, 0xe4, 0x28, 0x35, 0xa1, 0xac, + 0xdc, 0x05, 0xaf, 0x15, 0x3a, 0x6e, 0x8b, 0xfa, 0x76, 0xa7, 0x15, 0xba, 0x87, 0x54, 0xb4, 0x3d, + 0x01, 0x27, 0x6f, 0xc1, 0x8c, 0x82, 0x6d, 0xfa, 0xf5, 0x03, 0x1a, 0x84, 0x3e, 0xbe, 0x2b, 0x8b, + 0xc1, 0x8f, 0x76, 0x3a, 0x12, 0x4b, 0x68, 0x7a, 0x9d, 0xc6, 0x52, 0xeb, 0x89, 0xeb, 0x7b, 0x3c, + 0xaa, 0x60, 0x58, 0x94, 0x10, 0x83, 0x5b, 0xbf, 0x37, 0x9c, 0x3a, 0x33, 0xbc, 0xc8, 0x18, 0xfc, + 0x02, 0xc6, 0x16, 0x9c, 0xb6, 0xb3, 0xe7, 0x36, 0xdd, 0xd0, 0x55, 0x0f, 0x70, 0xbe, 0xdd, 0x63, + 0x5a, 0x91, 0x4f, 0x5f, 0xd1, 0x86, 0xce, 0x6c, 0x1b, 0xa2, 0xe6, 0xfe, 0x6a, 0x10, 0x66, 0x52, + 0xe9, 0xc8, 0x0d, 0xf1, 0x52, 0xa7, 0x9a, 0xba, 0xc5, 0x33, 0x90, 0x76, 0x1c, 0xcc, 0xfa, 0x12, + 0x41, 0x0b, 0x4d, 0xea, 0xb4, 0x3a, 0xe2, 0x11, 0x48, 0xdb, 0x80, 0xb1, 0xbe, 0x64, 0x5b, 0x13, + 0x4d, 0x18, 0xde, 0x68, 0xb1, 0x63, 0x50, 0x0c, 0x18, 0xec, 0x84, 0x07, 0x52, 0x54, 0x3f, 0xbf, + 0x7b, 0xad, 0x81, 0x98, 0xa4, 0x0d, 0xaf, 0x41, 0x35, 0x49, 0x03, 0x5c, 0x92, 0x09, 0x65, 0x92, + 0x18, 0x44, 0x4a, 0x1a, 0xe4, 0x92, 0x34, 0x10, 0x79, 0x05, 0xc6, 0xcb, 0xed, 0xb6, 0x26, 0x08, + 0x5f, 0x7f, 0xb4, 0x4d, 0x20, 0xb9, 0x0c, 0x50, 0x6e, 0xb7, 0xa5, 0x18, 0x7c, 0xd9, 0xd1, 0xd6, + 0x20, 0xe4, 0x56, 0x94, 0x6b, 0x53, 0x13, 0x85, 0x27, 0x4e, 0x76, 0x0a, 0x86, 0xe9, 0x55, 0x25, + 0x26, 0x14, 0x42, 0x81, 0xeb, 0x35, 0x06, 0x26, 0x1f, 0xc2, 0xf9, 0x58, 0xcc, 0x91, 0x56, 0x00, + 0x9e, 0x06, 0xd9, 0xd9, 0x04, 0xe4, 0x1d, 0x38, 0x17, 0x43, 0xca, 0xe2, 0xf0, 0xe0, 0xc7, 0xce, + 0xc0, 0x92, 0xf7, 0xa1, 0x18, 0xcb, 0xa7, 0x11, 0x15, 0x8a, 0x87, 0x3c, 0x76, 0x26, 0x9e, 0x7d, + 0x5d, 0xb1, 0x8b, 0xb9, 0xa2, 0x48, 0x3c, 0xcf, 0xb6, 0xd3, 0x91, 0x64, 0x05, 0x4a, 0xa9, 0x71, + 0x5c, 0x5a, 0xc1, 0xf8, 0x62, 0xa5, 0xdd, 0x8b, 0x8c, 0x54, 0xe0, 0x62, 0x2a, 0x89, 0xac, 0x06, + 0xbe, 0x63, 0x69, 0x77, 0xa5, 0x21, 0xf3, 0x30, 0x1d, 0xc5, 0xb3, 0x69, 0x55, 0xc0, 0x27, 0x2c, + 0xed, 0x54, 0x1c, 0x79, 0xc3, 0xcc, 0x9a, 0xc2, 0x0b, 0xc3, 0x17, 0x2c, 0xed, 0x24, 0xc2, 0x3a, + 0xce, 0xc1, 0xc5, 0xd4, 0xb5, 0x58, 0x9a, 0x0c, 0x73, 0xf1, 0xbd, 0xa9, 0x36, 0x17, 0xdc, 0x14, + 0x41, 0xaa, 0xdc, 0xd5, 0x2d, 0x2f, 0x01, 0x20, 0x3f, 0x17, 0xf5, 0x20, 0x0a, 0x59, 0x5d, 0x56, + 0xc7, 0xc7, 0xfc, 0x84, 0xeb, 0x76, 0x7c, 0x8f, 0x96, 0x52, 0xb8, 0x79, 0xcc, 0xc5, 0x7f, 0xbc, + 0xc8, 0x51, 0xd4, 0x9f, 0xe5, 0xa0, 0xd4, 0x63, 0x0b, 0xa2, 0xda, 0x94, 0x3b, 0x41, 0x9b, 0xee, + 0xab, 0x36, 0xf1, 0xa4, 0x05, 0xf3, 0x27, 0xdb, 0xe6, 0xbc, 0xec, 0x66, 0xfd, 0x75, 0x0e, 0x48, + 0x72, 0xab, 0x4b, 0xbe, 0x0b, 0x23, 0xd5, 0xea, 0x4a, 0xb5, 0xeb, 0xf9, 0x5a, 0x44, 0x41, 0xee, + 0x9c, 0x28, 0x7c, 0x55, 0x0f, 0x5e, 0xfd, 0x24, 0x11, 0x33, 0xdb, 0xd7, 0x35, 0x66, 0x36, 0x11, + 0x31, 0xbb, 0x94, 0x12, 0x04, 0xda, 0xdf, 0x23, 0x08, 0x34, 0x19, 0xe1, 0x69, 0x2d, 0x42, 0x31, + 0x6b, 0xb7, 0x8c, 0x33, 0x1c, 0xcf, 0x50, 0xa9, 0x1d, 0xd0, 0xf1, 0x19, 0xce, 0x04, 0x5b, 0xef, + 0xc0, 0x39, 0xc5, 0xcd, 0x9f, 0xbe, 0xd2, 0x52, 0xc3, 0x08, 0x13, 0x5b, 0xa5, 0xa0, 0x89, 0x00, + 0xd6, 0x9f, 0xf6, 0x27, 0x18, 0xab, 0x9d, 0xc3, 0x43, 0xc7, 0x7f, 0x46, 0xca, 0x26, 0x63, 0x5f, + 0x4f, 0xab, 0xa6, 0xd2, 0xcf, 0xf6, 0x98, 0x9a, 0x74, 0xb6, 0x2e, 0xe0, 0x0e, 0xa3, 0x55, 0xa7, + 0xfc, 0x68, 0x2f, 0xcf, 0xd3, 0xdf, 0x19, 0x40, 0xb2, 0x0b, 0xe3, 0x62, 0xed, 0xc6, 0xdf, 0xf2, + 0x1b, 0xbb, 0x13, 0xff, 0xc6, 0x8c, 0xea, 0xdd, 0x32, 0x58, 0xf8, 0x68, 0x34, 0xc5, 0x90, 0x2f, + 0x60, 0x42, 0xee, 0xd4, 0x84, 0x60, 0x1e, 0xf0, 0x76, 0xb7, 0xbb, 0x60, 0x93, 0x87, 0x4b, 0x8e, + 0x09, 0x62, 0x55, 0x96, 0x93, 0x1d, 0x97, 0x3c, 0x70, 0x92, 0x2a, 0x1b, 0x2c, 0xa2, 0xca, 0x06, + 0x6c, 0xee, 0x53, 0x20, 0xc9, 0x76, 0xf5, 0xfa, 0x9c, 0xc6, 0xb5, 0xcf, 0x69, 0xae, 0x0c, 0x53, + 0x29, 0x0d, 0x38, 0x95, 0x88, 0x4f, 0x81, 0x24, 0x6b, 0x7a, 0x1a, 0x09, 0xd6, 0x0d, 0x78, 0x55, + 0xa9, 0x40, 0x8d, 0x06, 0x43, 0xa6, 0x74, 0xb2, 0xff, 0x4a, 0x1e, 0x4a, 0x3d, 0x48, 0xc9, 0xef, + 0xe6, 0xe2, 0xda, 0xe6, 0xa3, 0xf1, 0xbd, 0xb8, 0xb6, 0xd3, 0xf9, 0x53, 0xd4, 0x5e, 0x79, 0xff, + 0x57, 0xff, 0xe2, 0xb9, 0x2d, 0x8f, 0x64, 0x97, 0x9d, 0x5e, 0x5b, 0xfd, 0xba, 0xb6, 0x76, 0x61, + 0xda, 0x30, 0x0b, 0x4f, 0xb2, 0x78, 0x59, 0x00, 0xe2, 0x15, 0xee, 0x35, 0x6f, 0x5f, 0x3c, 0x16, + 0x9e, 0x2f, 0xe6, 0x6c, 0x0d, 0x6a, 0xdd, 0x83, 0x99, 0x98, 0x5c, 0xe1, 0xfc, 0xff, 0x2e, 0xa8, + 0x34, 0x20, 0x28, 0xb8, 0xaf, 0x72, 0xf6, 0x67, 0x47, 0xa5, 0x71, 0xb6, 0xad, 0xbf, 0x15, 0xbd, + 0xc4, 0x22, 0xff, 0xb2, 0xd6, 0xf5, 0xe3, 0x8b, 0x72, 0x53, 0x4f, 0x8f, 0x46, 0xee, 0xc2, 0x20, + 0x87, 0xc4, 0xde, 0x3b, 0xd0, 0xa9, 0xc5, 0xbc, 0x20, 0x08, 0xad, 0x19, 0x4c, 0x5a, 0x80, 0x3f, + 0xca, 0x51, 0x92, 0x1d, 0x6b, 0x87, 0xbf, 0xff, 0x15, 0x81, 0xd5, 0x9b, 0x0a, 0xfd, 0xe5, 0x28, + 0x19, 0x90, 0x8c, 0x15, 0x92, 0x74, 0x2d, 0xef, 0x69, 0x93, 0x36, 0xf8, 0xc3, 0xad, 0x95, 0x31, + 0x61, 0xe3, 0xf6, 0x3b, 0x4c, 0x00, 0xb2, 0x59, 0x9f, 0xc0, 0x0c, 0xdb, 0x2d, 0xf8, 0xf1, 0xf2, + 0xf0, 0xd5, 0x1f, 0x06, 0x33, 0xef, 0x26, 0x39, 0x0c, 0x84, 0x77, 0x93, 0x04, 0xd2, 0x5a, 0x83, + 0xf3, 0xdc, 0xf9, 0xa9, 0x37, 0x29, 0x3a, 0x6a, 0x18, 0xc0, 0xdf, 0xb1, 0x2b, 0xef, 0x29, 0xad, + 0xe7, 0x74, 0xd6, 0xc7, 0x78, 0xa7, 0x52, 0x0c, 0x54, 0xd7, 0x6b, 0x45, 0x9e, 0xce, 0x93, 0x25, + 0x61, 0xf8, 0x7f, 0xe1, 0x62, 0xb9, 0xdd, 0xa6, 0xad, 0x46, 0xc4, 0xb8, 0xed, 0x3b, 0x27, 0x4c, + 0x91, 0x43, 0xca, 0x30, 0x80, 0xd4, 0xea, 0x0c, 0x58, 0x54, 0x37, 0xa5, 0x3a, 0x48, 0x27, 0x12, + 0x60, 0x63, 0x01, 0x9c, 0xd3, 0x6a, 0xc0, 0x6c, 0xb5, 0xb3, 0x77, 0xe8, 0x86, 0x78, 0xa3, 0x09, + 0xd3, 0x4c, 0xc9, 0xb2, 0x57, 0xe5, 0x93, 0x8d, 0x5c, 0x19, 0x37, 0xa2, 0xbb, 0x77, 0x78, 0x29, + 0x4a, 0xa4, 0x9e, 0x7a, 0x72, 0xf7, 0x56, 0xc4, 0x8a, 0x5e, 0x1e, 0x5e, 0x0a, 0xa2, 0xc5, 0xb3, + 0x8e, 0xd6, 0x14, 0x9c, 0xd5, 0xcf, 0xbc, 0xf8, 0x08, 0x99, 0x81, 0x29, 0xf3, 0x2c, 0x8b, 0x83, + 0xbf, 0x86, 0x69, 0xee, 0x6b, 0xe7, 0x0f, 0x58, 0xcc, 0x47, 0x6f, 0x35, 0xe4, 0x77, 0xe7, 0x63, + 0x17, 0x61, 0x30, 0x3e, 0x5e, 0x3d, 0x4d, 0xb4, 0x3b, 0xcf, 0xef, 0xc5, 0x3f, 0x99, 0x37, 0x4e, + 0x63, 0xf3, 0xbb, 0xf3, 0x95, 0x21, 0x91, 0x08, 0x9c, 0x49, 0xe7, 0xdd, 0xff, 0xad, 0x48, 0x9f, + 0xc7, 0x54, 0x2c, 0x2b, 0xd4, 0xc1, 0x6b, 0x93, 0xe9, 0x09, 0x2d, 0x26, 0x20, 0xaf, 0x32, 0xfd, + 0xe6, 0xdd, 0x86, 0xf5, 0x87, 0x39, 0xb8, 0xc1, 0x37, 0x64, 0xe9, 0x7c, 0x78, 0xb0, 0x95, 0xc1, + 0x4c, 0xde, 0x85, 0x81, 0x40, 0x0b, 0xf0, 0xb0, 0x44, 0xcd, 0xbb, 0x49, 0xe2, 0x0c, 0xa4, 0x0c, + 0x63, 0xfa, 0xed, 0xc0, 0x93, 0x25, 0x11, 0xb5, 0x47, 0x0f, 0x1f, 0x39, 0xea, 0xc6, 0xe0, 0x63, + 0xb8, 0xb0, 0xf4, 0x0d, 0x1b, 0x10, 0x62, 0x85, 0x12, 0xd6, 0x43, 0x94, 0x30, 0x61, 0x72, 0x5b, + 0x8c, 0x18, 0xd3, 0xb4, 0x8f, 0x83, 0x99, 0x9d, 0x2c, 0x17, 0xb9, 0xe8, 0x1a, 0x99, 0x6d, 0xc0, + 0xac, 0x3f, 0xcd, 0xc1, 0xc5, 0xf4, 0xd2, 0xc4, 0xc4, 0xb2, 0x0a, 0x67, 0x17, 0x9c, 0x96, 0xd7, + 0x72, 0xeb, 0x4e, 0xb3, 0x5a, 0x3f, 0xa0, 0x8d, 0x8e, 0x4a, 0x17, 0xae, 0x66, 0x99, 0x7d, 0xda, + 0x92, 0xec, 0x92, 0xc4, 0x4e, 0x72, 0x31, 0x0b, 0x11, 0xaf, 0x07, 0xf1, 0xb9, 0xb7, 0x49, 0x7d, + 0x25, 0x8f, 0xd7, 0x2c, 0x03, 0x4b, 0xee, 0xc8, 0x43, 0x85, 0xc6, 0x4e, 0xcb, 0x0d, 0x15, 0x13, + 0x77, 0xf5, 0xa4, 0xa1, 0xac, 0x7f, 0x9f, 0x83, 0xf3, 0xf8, 0x42, 0xa0, 0xf1, 0xe6, 0x70, 0x94, + 0x35, 0x5f, 0x26, 0x7e, 0xcf, 0x19, 0xd7, 0x9d, 0x0c, 0x6a, 0x33, 0x03, 0x3c, 0x79, 0x03, 0xfa, + 0xab, 0x32, 0xe0, 0x6c, 0x22, 0xf6, 0x5a, 0xbc, 0xe0, 0x60, 0x78, 0x1b, 0xa9, 0x98, 0x0d, 0xbf, + 0x48, 0x83, 0x3a, 0x6d, 0xe1, 0xb3, 0xfe, 0xdc, 0xf3, 0xa0, 0x41, 0xa2, 0x84, 0x76, 0xfd, 0x59, + 0x09, 0xed, 0x06, 0xcc, 0x84, 0x76, 0xd6, 0x13, 0xfe, 0x3e, 0x60, 0xbc, 0x41, 0xa2, 0x93, 0x3e, + 0x86, 0xd8, 0x93, 0xfd, 0x62, 0x1d, 0x38, 0x97, 0xd6, 0x32, 0xb6, 0x49, 0x8f, 0x3d, 0xf0, 0x9f, + 0x9d, 0xa5, 0x7e, 0x0b, 0x5e, 0x31, 0x68, 0xcb, 0xcd, 0xa6, 0xf7, 0x94, 0x36, 0xb6, 0x7c, 0xef, + 0xd0, 0x0b, 0x8d, 0xf7, 0xd1, 0x26, 0x1d, 0x9d, 0x4e, 0x2d, 0xc6, 0x71, 0xb0, 0xf5, 0xff, 0xc0, + 0xf5, 0x1e, 0x12, 0x45, 0xa3, 0xaa, 0x70, 0xd6, 0x89, 0xe1, 0x64, 0xe4, 0xd0, 0xf5, 0xb4, 0x76, + 0xc5, 0x05, 0x05, 0x76, 0x92, 0xff, 0xe6, 0xb6, 0xf1, 0x72, 0x3e, 0x29, 0xc2, 0xf4, 0x96, 0xbd, + 0xb9, 0xb8, 0xb3, 0xb0, 0x5d, 0xdb, 0xfe, 0x62, 0x6b, 0xa9, 0xb6, 0xb3, 0xf1, 0x60, 0x63, 0xf3, + 0xe1, 0x06, 0x7f, 0xe6, 0xc1, 0xc0, 0x6c, 0x2f, 0x95, 0xd7, 0x0b, 0x39, 0x32, 0x0d, 0x05, 0x03, + 0xbc, 0xb4, 0x53, 0x29, 0xe4, 0x6f, 0x7e, 0x6d, 0xbc, 0x08, 0x4f, 0x2e, 0x42, 0xb1, 0xba, 0xb3, + 0xb5, 0xb5, 0x69, 0x2b, 0xa9, 0xfa, 0x23, 0x13, 0x33, 0x70, 0xd6, 0xc0, 0xde, 0xb3, 0x97, 0x96, + 0x0a, 0x39, 0x56, 0x15, 0x03, 0xbc, 0x65, 0x2f, 0xad, 0xaf, 0xee, 0xac, 0x17, 0xf2, 0x37, 0x6b, + 0xfa, 0x2d, 0x5d, 0x72, 0x01, 0x66, 0x17, 0x97, 0x76, 0x57, 0x17, 0x96, 0xd2, 0x64, 0x4f, 0x43, + 0x41, 0x47, 0x6e, 0x6f, 0x6e, 0x6f, 0x71, 0xd1, 0x3a, 0xf4, 0xe1, 0x52, 0xa5, 0xbc, 0xb3, 0xbd, + 0xb2, 0x51, 0xe8, 0xb3, 0xfa, 0x87, 0xf3, 0x85, 0xfc, 0xcd, 0x1f, 0x18, 0x57, 0x78, 0x59, 0xf5, + 0x05, 0xf9, 0x4e, 0xb5, 0xbc, 0x9c, 0x5d, 0x04, 0xc7, 0xae, 0xdf, 0x2b, 0x17, 0x72, 0xe4, 0x12, + 0x9c, 0x37, 0xa0, 0x5b, 0xe5, 0x6a, 0xf5, 0xe1, 0xa6, 0xbd, 0xb8, 0xb6, 0x54, 0xad, 0x16, 0xf2, + 0x37, 0x77, 0x8d, 0x24, 0x9e, 0xac, 0x84, 0xf5, 0x7b, 0xe5, 0x9a, 0xbd, 0xf4, 0xd9, 0xce, 0xaa, + 0xbd, 0xb4, 0x98, 0x2c, 0xc1, 0xc0, 0x7e, 0xb1, 0x54, 0x2d, 0xe4, 0xc8, 0x14, 0x4c, 0x1a, 0xd0, + 0x8d, 0xcd, 0x42, 0xfe, 0xe6, 0xab, 0x22, 0xcf, 0x23, 0x99, 0x00, 0x58, 0x5c, 0xaa, 0x2e, 0x2c, + 0x6d, 0x2c, 0xae, 0x6e, 0x2c, 0x17, 0xce, 0x90, 0x71, 0x18, 0x29, 0xab, 0x9f, 0xb9, 0x9b, 0xef, + 0xc3, 0x64, 0xcc, 0xbc, 0x67, 0x14, 0xca, 0x30, 0x2e, 0x9c, 0x41, 0xf5, 0xcb, 0x9f, 0xe8, 0x63, + 0xe5, 0x96, 0x7a, 0x21, 0x77, 0xb3, 0x22, 0x1f, 0x11, 0xd7, 0xbe, 0x73, 0x32, 0x0a, 0x43, 0x8b, + 0x4b, 0xf7, 0xca, 0x3b, 0x6b, 0xdb, 0x85, 0x33, 0xec, 0xc7, 0x82, 0xbd, 0x54, 0xde, 0x5e, 0x5a, + 0x2c, 0xe4, 0xc8, 0x08, 0x0c, 0x54, 0xb7, 0xcb, 0xdb, 0x4b, 0x85, 0x3c, 0x19, 0x86, 0xfe, 0x9d, + 0xea, 0x92, 0x5d, 0xe8, 0x9b, 0xff, 0x9d, 0xdf, 0xcd, 0x71, 0x47, 0xa3, 0xbc, 0xcc, 0xf7, 0xb5, + 0x66, 0x50, 0x8a, 0x29, 0x4f, 0xbc, 0x98, 0x9c, 0x69, 0x3d, 0xe2, 0x2e, 0x60, 0xae, 0xcb, 0xe1, + 0x0e, 0x12, 0xdc, 0xc8, 0xdd, 0xc9, 0x11, 0x1b, 0x83, 0x61, 0x62, 0xf6, 0x95, 0x92, 0x9c, 0x6e, + 0x02, 0xcf, 0x5d, 0xea, 0x6a, 0x96, 0x91, 0x5f, 0x02, 0x4b, 0x97, 0x99, 0x61, 0x85, 0x7c, 0xf7, + 0x64, 0xd6, 0x86, 0x2c, 0xf3, 0xd5, 0x93, 0x91, 0x93, 0xfb, 0x30, 0xce, 0xf6, 0xe6, 0x8a, 0x8c, + 0x5c, 0x88, 0x33, 0x6a, 0x26, 0xc1, 0xdc, 0xc5, 0x74, 0xa4, 0x7a, 0xd4, 0x6c, 0x0c, 0x1b, 0xc2, + 0x8d, 0xeb, 0x80, 0xc8, 0x5c, 0x40, 0x12, 0xc2, 0x67, 0xfc, 0xb9, 0xb3, 0x31, 0xf0, 0xee, 0xdd, + 0x3b, 0x39, 0x52, 0xc5, 0x44, 0x9c, 0xc6, 0x26, 0x9f, 0xc8, 0xdb, 0xa5, 0xc9, 0xdd, 0x3f, 0xaf, + 0x4d, 0x49, 0x3d, 0x41, 0x9c, 0x61, 0x1d, 0x6c, 0x00, 0x49, 0xee, 0x9d, 0xc9, 0x95, 0x68, 0x1c, + 0xa4, 0x6f, 0xab, 0xe7, 0xce, 0x25, 0x0e, 0xb2, 0x96, 0xd8, 0xee, 0x89, 0x2c, 0xc1, 0x84, 0x48, + 0xf4, 0x21, 0x76, 0xf3, 0xa4, 0x9b, 0x3d, 0x90, 0x29, 0x66, 0x19, 0xf5, 0xa4, 0x2c, 0x02, 0x32, + 0x17, 0xb5, 0x23, 0x6e, 0x26, 0xcc, 0x5d, 0x48, 0xc5, 0x89, 0xf6, 0xdd, 0x83, 0x09, 0xd3, 0xb8, + 0x20, 0xb2, 0x83, 0x52, 0x6d, 0x8e, 0xcc, 0x0a, 0xd5, 0x60, 0x76, 0xdd, 0x71, 0xf1, 0xbc, 0x44, + 0x04, 0xe9, 0xc9, 0x38, 0x38, 0x52, 0xea, 0x12, 0x18, 0x57, 0xa5, 0xad, 0x86, 0xea, 0x84, 0xac, + 0x07, 0x4a, 0xf0, 0xb3, 0xa9, 0xca, 0x3d, 0xb2, 0x19, 0xa3, 0x48, 0x2c, 0xf3, 0x59, 0xf9, 0xb4, + 0xb0, 0xd3, 0xb9, 0xac, 0x48, 0x69, 0xb2, 0x8e, 0x9b, 0xf4, 0x98, 0x44, 0x6d, 0x4c, 0x9c, 0x5a, + 0x5c, 0x11, 0xd3, 0xcd, 0x84, 0x6e, 0x3c, 0xe4, 0x39, 0x20, 0x19, 0x8a, 0xcb, 0x14, 0x76, 0x27, + 0x47, 0xbe, 0xc6, 0xaf, 0x3a, 0x55, 0xdc, 0x43, 0x37, 0x3c, 0x10, 0xbb, 0x9f, 0x0b, 0xa9, 0x02, + 0xc4, 0x87, 0xd2, 0x45, 0xba, 0x0d, 0xd3, 0x69, 0xc1, 0xd9, 0x4a, 0xa1, 0x5d, 0x22, 0xb7, 0x33, + 0x47, 0x81, 0xcd, 0x4c, 0x8d, 0x46, 0x76, 0x27, 0x75, 0x89, 0x0d, 0xce, 0x94, 0xf9, 0x21, 0x4c, + 0xb0, 0x51, 0xf2, 0x80, 0xd2, 0x76, 0xb9, 0xe9, 0x3e, 0xa1, 0x01, 0x91, 0x59, 0xd4, 0x15, 0x28, + 0x8b, 0xf7, 0x46, 0x8e, 0x7c, 0x07, 0x46, 0x1f, 0x3a, 0x61, 0xfd, 0x40, 0x64, 0x13, 0x96, 0xc9, + 0x86, 0x11, 0x36, 0x27, 0x7f, 0x21, 0xf2, 0x4e, 0x8e, 0x7c, 0x04, 0x43, 0xcb, 0x34, 0xc4, 0xdb, + 0xfd, 0x57, 0x55, 0x2c, 0x21, 0xf7, 0x4f, 0xae, 0xb6, 0xd4, 0x4d, 0x2f, 0x59, 0xe1, 0xb8, 0x33, + 0x97, 0xdc, 0x06, 0xe0, 0x13, 0x02, 0x4a, 0x88, 0xa3, 0xe7, 0x12, 0xd5, 0x26, 0xcb, 0x6c, 0xf3, + 0xd0, 0xa4, 0x21, 0x3d, 0x69, 0x91, 0x59, 0x3a, 0x5a, 0x83, 0x09, 0xf5, 0x0e, 0xdc, 0x06, 0x26, + 0x7d, 0xb2, 0x62, 0xc2, 0x82, 0x53, 0x48, 0x7b, 0x9f, 0x7d, 0x15, 0xfc, 0x11, 0x74, 0xcc, 0x0e, + 0x84, 0x33, 0xe9, 0xac, 0x9e, 0x62, 0x48, 0x9f, 0x42, 0xa5, 0x12, 0x39, 0x99, 0xc6, 0xbb, 0xe2, + 0x05, 0xa1, 0xc9, 0xab, 0x20, 0xe9, 0xbc, 0xbf, 0x08, 0x73, 0x7a, 0xb9, 0x66, 0x3a, 0xfb, 0x68, + 0xce, 0xcd, 0xca, 0x92, 0x3f, 0x77, 0xb5, 0x0b, 0x85, 0xb0, 0xdf, 0xfa, 0x7e, 0x3d, 0x9f, 0xc3, + 0xe9, 0x64, 0x11, 0xa6, 0x64, 0x59, 0x9b, 0x6d, 0xda, 0xaa, 0x56, 0x57, 0xf0, 0xcd, 0x2f, 0x19, + 0xb9, 0xa2, 0xc1, 0xa4, 0x74, 0x92, 0x44, 0xb1, 0xa5, 0xcf, 0xc8, 0x02, 0x44, 0xba, 0xe5, 0x06, + 0x8a, 0x96, 0xbe, 0xd4, 0x3c, 0xeb, 0x0f, 0xb8, 0x53, 0xc9, 0xd8, 0xfc, 0xef, 0xce, 0x93, 0x2e, + 0x06, 0xd0, 0x5c, 0x86, 0x09, 0x71, 0x27, 0x47, 0xbe, 0x00, 0x92, 0x34, 0x49, 0x94, 0x0a, 0x33, + 0xcd, 0x2f, 0xa5, 0xc2, 0x2e, 0xf6, 0xcc, 0x32, 0xcc, 0xa8, 0x1c, 0x60, 0x5a, 0xa9, 0xf3, 0x24, + 0xa3, 0x36, 0x59, 0xb5, 0x24, 0x9f, 0xc0, 0x94, 0x18, 0xb4, 0x3a, 0x82, 0x14, 0xd4, 0xfc, 0x23, + 0xac, 0x92, 0xcc, 0x71, 0x7a, 0x1f, 0x66, 0xaa, 0x31, 0x8d, 0xf1, 0x60, 0xfe, 0xf3, 0xa6, 0x08, + 0x04, 0x56, 0x69, 0xc8, 0x55, 0x96, 0x2e, 0xeb, 0x01, 0x10, 0xee, 0x14, 0x92, 0xe2, 0x9e, 0xb8, + 0xf4, 0x29, 0xb9, 0x14, 0xab, 0x3a, 0x03, 0x22, 0x19, 0x4e, 0x60, 0x99, 0x2d, 0xdb, 0xe6, 0x4f, + 0xf8, 0x23, 0xd4, 0x38, 0x47, 0xbf, 0x62, 0x30, 0x18, 0x47, 0xf1, 0xa2, 0x03, 0xce, 0x67, 0x52, + 0x90, 0x5f, 0xc6, 0xe4, 0xdb, 0xdd, 0xcd, 0x2a, 0xf2, 0x9d, 0x34, 0xeb, 0x37, 0xc3, 0x30, 0x9c, + 0x7b, 0xe3, 0x64, 0xc4, 0xca, 0x90, 0x1d, 0x5f, 0xa6, 0xe1, 0x56, 0xb3, 0xb3, 0xef, 0xe2, 0xe3, + 0xce, 0x44, 0x39, 0x8d, 0x14, 0x48, 0x8c, 0x4b, 0x99, 0xf3, 0x32, 0x42, 0x54, 0xe9, 0x0f, 0xc9, + 0x2a, 0x14, 0xf8, 0xfc, 0xaf, 0x89, 0xb8, 0x94, 0x10, 0x21, 0x48, 0x1c, 0xdf, 0x39, 0x0c, 0x32, + 0x7b, 0xeb, 0x36, 0x8f, 0x8d, 0x22, 0xf2, 0x9b, 0xd4, 0x37, 0x98, 0x53, 0x06, 0x4c, 0x3d, 0x48, + 0xc2, 0x7a, 0xc4, 0xa6, 0x01, 0x0d, 0x65, 0x96, 0x2f, 0xfe, 0xb4, 0xf7, 0xb5, 0x68, 0xb1, 0x4f, + 0x62, 0xa3, 0x4f, 0x3f, 0x96, 0x91, 0x72, 0xf7, 0x4d, 0xa2, 0x9e, 0x3b, 0x4f, 0x11, 0xfa, 0xaa, + 0xb1, 0x27, 0x39, 0x9d, 0xdc, 0xb7, 0x70, 0x0d, 0xc2, 0xcc, 0x66, 0x33, 0x51, 0xdd, 0xd8, 0x6f, + 0xc9, 0x35, 0xae, 0x71, 0xed, 0xce, 0xe3, 0x94, 0xc6, 0x16, 0x49, 0xb6, 0x85, 0xed, 0xf8, 0x3e, + 0x6d, 0x71, 0xe6, 0xac, 0xfd, 0x46, 0x1a, 0xf7, 0xc7, 0x38, 0xf5, 0x68, 0xdc, 0xfc, 0x5e, 0x67, + 0x2f, 0x11, 0xfc, 0x29, 0xba, 0x3b, 0x39, 0xf2, 0x2e, 0x0c, 0x8b, 0x3a, 0x32, 0x26, 0xa3, 0xd2, + 0x41, 0x97, 0x5a, 0x23, 0x27, 0x70, 0x25, 0x61, 0x9d, 0x4d, 0x9a, 0xac, 0xde, 0xe7, 0x75, 0x7e, + 0x97, 0x2d, 0xb6, 0x8d, 0xe7, 0xe1, 0x5c, 0x90, 0xab, 0x2e, 0x72, 0x16, 0x55, 0x36, 0x2c, 0x09, + 0xea, 0xb1, 0x3c, 0x72, 0x21, 0x6c, 0xdf, 0x8c, 0x29, 0x65, 0x55, 0x66, 0x48, 0xb5, 0x6f, 0x36, + 0xc0, 0xbd, 0xd6, 0xda, 0x55, 0x28, 0x94, 0xeb, 0xb8, 0x12, 0x54, 0xe9, 0xa1, 0xd3, 0x3e, 0xf0, + 0x7c, 0xaa, 0x8c, 0x96, 0x38, 0x42, 0xca, 0x9a, 0x51, 0x3b, 0x0b, 0x81, 0x58, 0xa3, 0x0e, 0xe6, + 0xdd, 0x9f, 0x55, 0x5b, 0x8b, 0x18, 0x2a, 0x9d, 0xa3, 0x8b, 0x91, 0x32, 0xbd, 0xc0, 0xcc, 0xaa, + 0xe6, 0x8b, 0x89, 0x79, 0x1f, 0x27, 0x0c, 0x45, 0x1c, 0xa8, 0x15, 0x42, 0x81, 0x94, 0x39, 0x27, + 0xaf, 0x1f, 0x29, 0xd2, 0xb2, 0x3c, 0x37, 0x8e, 0xd4, 0x92, 0xc5, 0x9d, 0x55, 0xfc, 0xf7, 0x60, + 0x62, 0x89, 0x4d, 0xe8, 0x9d, 0x86, 0xcb, 0xdf, 0x1a, 0x21, 0xe6, 0xe3, 0x11, 0x99, 0x8c, 0x2b, + 0xf2, 0xf5, 0x47, 0x64, 0x15, 0xa6, 0xbf, 0x5c, 0x53, 0x34, 0x98, 0xec, 0x8f, 0x69, 0x29, 0x56, + 0x3c, 0xf7, 0x82, 0xa6, 0xb9, 0xb0, 0xf5, 0x67, 0xf9, 0x8e, 0xb0, 0xdc, 0x6e, 0x37, 0xa5, 0x4b, + 0x9a, 0x9f, 0xbd, 0x5f, 0x37, 0x4c, 0xc8, 0x04, 0x5e, 0xca, 0x4e, 0x6e, 0x1a, 0x3f, 0xd7, 0x5e, + 0x63, 0xcf, 0x90, 0x99, 0x81, 0xef, 0x35, 0x16, 0xd5, 0xeb, 0x00, 0xe5, 0x66, 0x33, 0xc1, 0x1c, + 0x90, 0xd7, 0x4d, 0xe9, 0x69, 0x34, 0xbd, 0x4a, 0x40, 0x13, 0x9d, 0xef, 0xba, 0xca, 0xed, 0x36, + 0x9f, 0x2c, 0x2f, 0xab, 0x09, 0xc3, 0x44, 0x24, 0x4d, 0xf4, 0x38, 0x5e, 0xcc, 0xed, 0xf7, 0x71, + 0x98, 0x45, 0x4f, 0xb6, 0x13, 0xdd, 0xe0, 0x8d, 0xbf, 0x58, 0xaf, 0x36, 0x61, 0x31, 0xa4, 0x5a, + 0x27, 0x26, 0x71, 0xeb, 0x13, 0xbd, 0xff, 0xae, 0x3c, 0x33, 0x31, 0xb8, 0x94, 0x77, 0x39, 0x0b, + 0xad, 0x3c, 0xa5, 0x05, 0x31, 0x98, 0xa2, 0x0a, 0x5e, 0x36, 0xd6, 0x87, 0x64, 0x1d, 0x4b, 0x99, + 0x78, 0xd5, 0xe4, 0x42, 0xfc, 0x45, 0x7e, 0x25, 0x34, 0xe3, 0xa9, 0xfe, 0xcc, 0x3e, 0xb9, 0x07, + 0xd3, 0x7a, 0x8f, 0xaa, 0x76, 0x67, 0xcd, 0xfe, 0x59, 0x72, 0xb6, 0x61, 0x26, 0xf5, 0x01, 0x7d, + 0xb5, 0xc4, 0x76, 0x7b, 0x5e, 0x3f, 0x53, 0x2a, 0x85, 0x73, 0xc2, 0xb2, 0x97, 0x8f, 0xdd, 0xca, + 0xf6, 0xbe, 0x62, 0x1a, 0xfe, 0x31, 0xb4, 0x94, 0x7b, 0xbd, 0x07, 0x95, 0x50, 0xe8, 0xd7, 0xb8, + 0x02, 0x26, 0xca, 0xb8, 0xaa, 0xb9, 0x02, 0x32, 0x0a, 0xb0, 0xba, 0x91, 0xa8, 0x31, 0x30, 0x9d, + 0x82, 0xce, 0x56, 0xf1, 0xb5, 0x6c, 0x99, 0xd1, 0xc0, 0xda, 0x95, 0x49, 0xf0, 0x33, 0x35, 0x93, + 0x8e, 0xee, 0x6d, 0x4b, 0xce, 0xa9, 0xf1, 0x70, 0xf2, 0x2a, 0x67, 0x49, 0x6b, 0x28, 0xb7, 0x8d, + 0xcc, 0xf2, 0xc5, 0xeb, 0x18, 0x73, 0xdb, 0x18, 0x48, 0x59, 0xc3, 0x6b, 0x5d, 0x69, 0x34, 0x8b, + 0x8e, 0x7c, 0xc5, 0xfd, 0x38, 0x66, 0x11, 0xba, 0x1f, 0x27, 0x55, 0xfe, 0x95, 0x6c, 0x02, 0x5d, + 0xb8, 0xc3, 0x0f, 0x6d, 0x4d, 0x92, 0x80, 0xe8, 0xa6, 0x52, 0x0c, 0x17, 0x1f, 0x1b, 0xa9, 0x24, + 0x7a, 0x11, 0x0f, 0xe5, 0x37, 0x98, 0xa1, 0xa5, 0x34, 0xe4, 0x89, 0xb6, 0x29, 0x9b, 0x50, 0x8c, + 0x3a, 0x33, 0xd6, 0x80, 0x53, 0x76, 0xa5, 0x54, 0xc6, 0xf9, 0xe8, 0x3b, 0x8e, 0x4b, 0x7c, 0x2d, + 0xf1, 0xa5, 0x67, 0x28, 0xa6, 0x6b, 0x11, 0x7c, 0x3e, 0xd7, 0x92, 0xea, 0x5f, 0x88, 0x9c, 0xb8, + 0x11, 0x34, 0x65, 0x3e, 0xd7, 0x91, 0xca, 0x58, 0x9d, 0x30, 0x10, 0xd9, 0xad, 0xbe, 0x94, 0x26, + 0x27, 0x48, 0xce, 0xb8, 0x5a, 0xbd, 0xe4, 0x3e, 0x2d, 0x8e, 0x38, 0xcd, 0x8c, 0x7b, 0x92, 0xaa, + 0x65, 0xc9, 0x59, 0x84, 0x51, 0x5e, 0x5b, 0xbe, 0x90, 0x9e, 0x37, 0xd4, 0x64, 0xac, 0xa1, 0x73, + 0x46, 0xe3, 0xcc, 0xe5, 0x73, 0x01, 0x5d, 0xc9, 0x12, 0x9c, 0x5d, 0x8b, 0x0b, 0x49, 0x19, 0x86, + 0x1b, 0x59, 0x69, 0x81, 0xd7, 0xe6, 0x62, 0x5c, 0x39, 0x46, 0x85, 0xb2, 0x9b, 0x44, 0x74, 0xd5, + 0xf4, 0xa8, 0x52, 0xf6, 0xfe, 0x75, 0x4a, 0x3c, 0xc3, 0x8d, 0x2f, 0x61, 0xc9, 0x9c, 0x97, 0xe7, + 0x94, 0x4f, 0x4c, 0x83, 0xa2, 0x83, 0x22, 0x5d, 0xcc, 0x16, 0x5e, 0x1f, 0xa1, 0x7e, 0x98, 0xc8, + 0x69, 0xf9, 0x8a, 0xb1, 0x79, 0x8b, 0xa3, 0xb3, 0xf7, 0x6e, 0x6a, 0xce, 0xce, 0x94, 0x98, 0x8e, + 0xee, 0xa5, 0xb6, 0xef, 0x6b, 0x73, 0x76, 0x9c, 0x37, 0x20, 0x37, 0xe2, 0x1b, 0xb7, 0x04, 0x49, + 0xef, 0x35, 0x41, 0x84, 0x90, 0xc4, 0x02, 0x48, 0x2d, 0x43, 0x0f, 0x26, 0x32, 0x5b, 0x0b, 0xb6, + 0x1c, 0xff, 0x19, 0xd2, 0xd2, 0x90, 0xbd, 0x6a, 0xf8, 0xa5, 0x36, 0xd1, 0x99, 0x9c, 0x81, 0x32, + 0xc7, 0xb3, 0x08, 0x7a, 0xc9, 0xde, 0xc0, 0x2b, 0x4d, 0xb1, 0x06, 0xba, 0x75, 0xaa, 0x76, 0x36, + 0xa9, 0xd8, 0xec, 0xf6, 0x2f, 0xcb, 0x9d, 0x52, 0x5c, 0xde, 0xb9, 0x98, 0xd3, 0xb6, 0x57, 0xc5, + 0xbe, 0x96, 0x93, 0x71, 0xac, 0x4d, 0x78, 0xa9, 0xe8, 0xb5, 0x6e, 0xad, 0xd6, 0x1e, 0x3c, 0xed, + 0x62, 0x06, 0x4d, 0x56, 0xdd, 0xfd, 0x96, 0xba, 0x89, 0x50, 0xb5, 0x95, 0x11, 0xa4, 0xc1, 0xe2, + 0x53, 0x8c, 0x81, 0x52, 0xf9, 0x79, 0xa6, 0xe5, 0xee, 0x5d, 0xa1, 0xa9, 0x1f, 0x92, 0x04, 0x8f, + 0xe6, 0x6e, 0xbd, 0x90, 0x8a, 0x4b, 0x0a, 0x54, 0x5b, 0x94, 0xfb, 0x0f, 0xb7, 0x95, 0x40, 0x1d, + 0x18, 0x17, 0x68, 0xe2, 0x54, 0x42, 0x84, 0xa1, 0x65, 0x8a, 0xaf, 0xef, 0xeb, 0x5e, 0x17, 0xed, + 0x55, 0xfd, 0x98, 0xd3, 0x83, 0x7c, 0x0c, 0x23, 0x18, 0xc9, 0x85, 0x8e, 0x92, 0x59, 0x3d, 0x15, + 0x1b, 0x83, 0x48, 0xa6, 0x62, 0x12, 0x21, 0x0a, 0x7c, 0x5b, 0x3a, 0x3e, 0xb0, 0xcc, 0xa2, 0xe9, + 0x30, 0xca, 0x2e, 0xf6, 0x6d, 0xe9, 0xf5, 0x30, 0xd8, 0x22, 0x50, 0x06, 0xdb, 0xf7, 0x60, 0x8c, + 0x8f, 0x51, 0xfc, 0x3d, 0xaf, 0x31, 0x4a, 0x60, 0x06, 0xe3, 0xbb, 0xf2, 0x48, 0x03, 0xcb, 0x33, + 0x91, 0xdd, 0x57, 0xf1, 0x8f, 0xa5, 0x97, 0xc5, 0xa8, 0x69, 0x04, 0xea, 0x35, 0xfa, 0xb6, 0x60, + 0x4c, 0x7f, 0xf0, 0x57, 0x75, 0x6d, 0xca, 0x93, 0xdd, 0xaa, 0x6b, 0xd3, 0x9e, 0xdc, 0x8e, 0x5c, + 0xfe, 0x5f, 0x48, 0x97, 0x42, 0x24, 0xf4, 0x92, 0x51, 0xad, 0x84, 0xdc, 0xcb, 0x59, 0xe8, 0xb8, + 0xe8, 0x2a, 0x14, 0xe2, 0xaf, 0x13, 0x2b, 0x7b, 0x2c, 0xe3, 0x19, 0x69, 0x65, 0xe4, 0x65, 0x3e, + 0x6b, 0xbc, 0x25, 0xfd, 0xe3, 0xa6, 0xdc, 0xab, 0xe9, 0x95, 0xd2, 0x45, 0x67, 0x3b, 0xcc, 0xc7, + 0x8d, 0x87, 0x8a, 0x75, 0x4b, 0x39, 0xf1, 0x10, 0xb2, 0xbe, 0xb3, 0x4a, 0x79, 0xdb, 0xd8, 0x95, + 0x49, 0xab, 0x52, 0x8f, 0x6c, 0x95, 0xb3, 0xa0, 0xf7, 0xab, 0x16, 0x3d, 0x8f, 0x7f, 0xc9, 0x2f, + 0xc0, 0x6c, 0x46, 0x96, 0x7e, 0x72, 0x3d, 0xe6, 0x69, 0x4d, 0xcf, 0xe2, 0xaf, 0x06, 0x48, 0xda, + 0x93, 0xea, 0x64, 0x1d, 0xe3, 0x06, 0x8c, 0x14, 0x12, 0x89, 0xb3, 0xb8, 0x87, 0x6e, 0x78, 0xc0, + 0x1f, 0xca, 0xd7, 0xa6, 0xcd, 0xd4, 0xdc, 0x13, 0xa4, 0x8a, 0xb6, 0x88, 0x01, 0x4d, 0x39, 0x8e, + 0x4b, 0x11, 0x38, 0x97, 0x2e, 0x90, 0xcd, 0x1d, 0x6c, 0x2c, 0xa4, 0xe4, 0xf7, 0x50, 0x63, 0x21, + 0x3b, 0xf7, 0x47, 0x66, 0x35, 0xb7, 0xe4, 0x1e, 0x29, 0x5d, 0x62, 0x76, 0xaa, 0x8f, 0x4c, 0x89, + 0xf7, 0x99, 0xc4, 0x44, 0xf6, 0x0e, 0x92, 0x41, 0xde, 0x7d, 0xf6, 0xb0, 0xe5, 0x92, 0x6b, 0x72, + 0xcd, 0x6b, 0xf5, 0xcb, 0xca, 0x13, 0x92, 0x59, 0xbf, 0x25, 0xf9, 0x3d, 0xa5, 0xd7, 0xef, 0xa4, + 0x8b, 0xae, 0x3a, 0xff, 0x8a, 0x25, 0x90, 0x31, 0x1a, 0xaa, 0xc1, 0xe7, 0x32, 0xe0, 0x64, 0x03, + 0x03, 0x81, 0xe2, 0x50, 0xcd, 0x28, 0x4d, 0xcf, 0x50, 0x93, 0x29, 0x8f, 0x8f, 0x63, 0x23, 0xc3, + 0xc7, 0x69, 0xc6, 0x71, 0x2c, 0x35, 0x88, 0x18, 0xc7, 0x06, 0xf4, 0x74, 0xe3, 0x38, 0x26, 0xd0, + 0x1c, 0xc7, 0xf1, 0x6a, 0xc6, 0x2d, 0xfd, 0xcc, 0x5e, 0x8d, 0x57, 0x53, 0x8d, 0xe3, 0x74, 0x89, + 0xd9, 0x99, 0x58, 0x32, 0x25, 0xaa, 0x71, 0x6c, 0x4a, 0xcc, 0x20, 0x3f, 0xe1, 0x38, 0x8e, 0x17, + 0x62, 0x8e, 0xe3, 0x53, 0xd5, 0x4f, 0x8d, 0xe3, 0xf4, 0xfa, 0x9d, 0x7a, 0x1c, 0xc7, 0x52, 0x17, + 0x19, 0x0d, 0x4d, 0x1b, 0xc7, 0x71, 0x7a, 0x3e, 0x8e, 0xe3, 0xd0, 0x98, 0x73, 0xa5, 0xcb, 0x38, + 0x8e, 0x73, 0x7e, 0x86, 0xf2, 0x62, 0x69, 0x57, 0x4e, 0x32, 0x92, 0x33, 0x33, 0xb6, 0x90, 0x87, + 0xe8, 0xde, 0x8b, 0xc1, 0x4f, 0x36, 0x9a, 0x2f, 0x66, 0x09, 0xc5, 0xf1, 0xbc, 0x2b, 0x95, 0x18, + 0xaf, 0xae, 0xe9, 0xbb, 0x4a, 0xcf, 0x3a, 0xd3, 0xa5, 0xc2, 0xbb, 0x6c, 0xdc, 0x34, 0xba, 0xc8, + 0xed, 0x96, 0x34, 0xa7, 0x8b, 0x5c, 0x65, 0xca, 0xc4, 0xe5, 0x66, 0xb2, 0x74, 0x1f, 0xdf, 0x9f, + 0xcb, 0x03, 0x8e, 0x38, 0xdf, 0x7c, 0xcc, 0x38, 0x3a, 0x75, 0x4d, 0x95, 0x91, 0x14, 0xaf, 0xe9, + 0x69, 0xc7, 0xf9, 0xba, 0xdc, 0x3d, 0x24, 0xb2, 0x6d, 0xc5, 0x1a, 0xad, 0x8f, 0xf5, 0x4c, 0x0c, + 0xd9, 0x46, 0x5f, 0x6e, 0x12, 0xae, 0xf9, 0x81, 0xb3, 0xd2, 0x7a, 0xf5, 0x94, 0x9a, 0xc8, 0x1b, + 0xa4, 0x4b, 0xcd, 0x4a, 0x2a, 0xa4, 0xa4, 0x26, 0xb9, 0x3f, 0x41, 0xef, 0x97, 0xb8, 0x71, 0xd5, + 0x7a, 0xe4, 0x65, 0x7b, 0x52, 0xa6, 0x8c, 0x60, 0x25, 0x46, 0x8b, 0x31, 0x62, 0x1f, 0x8a, 0x13, + 0x3c, 0x09, 0xcc, 0x54, 0x7e, 0x1a, 0x3f, 0xf9, 0x04, 0x0a, 0x62, 0x7a, 0x8b, 0x04, 0xa4, 0x11, + 0x66, 0x76, 0x5d, 0x45, 0x3a, 0xdd, 0x4e, 0x50, 0x83, 0x93, 0x38, 0xdb, 0x4e, 0xa2, 0x89, 0x6c, + 0xcf, 0x14, 0x5b, 0x0e, 0xb7, 0xfd, 0x4e, 0x10, 0xd2, 0x46, 0xd2, 0xa3, 0x64, 0x56, 0x46, 0x46, + 0x46, 0x98, 0xe4, 0xbb, 0xf3, 0x64, 0x15, 0xe7, 0x36, 0x13, 0xdc, 0xcd, 0xe5, 0x96, 0x2e, 0x06, + 0xa7, 0x9e, 0x75, 0x75, 0xad, 0xc7, 0xac, 0x53, 0x56, 0xd9, 0x99, 0x95, 0x92, 0x07, 0xda, 0x42, + 0x4f, 0x27, 0x6c, 0x62, 0x96, 0x9e, 0x3e, 0xc0, 0x58, 0x00, 0xee, 0x03, 0xec, 0xa5, 0x9e, 0xf8, + 0x6d, 0x23, 0xf2, 0x29, 0x8c, 0x48, 0xe6, 0xde, 0x5a, 0x89, 0x73, 0xa3, 0x56, 0x16, 0x61, 0xdc, + 0xb8, 0x4a, 0xa5, 0x4c, 0x9c, 0xb4, 0x0b, 0x56, 0x5d, 0x3a, 0x7b, 0xdc, 0xb8, 0x32, 0xa5, 0xa4, + 0xa4, 0x5d, 0xa4, 0xca, 0x94, 0xf2, 0x11, 0x8c, 0x0a, 0x95, 0x76, 0xd5, 0x46, 0xb6, 0xd3, 0x6d, + 0x46, 0x0b, 0x4b, 0xee, 0x34, 0xdc, 0x70, 0xc1, 0x6b, 0x3d, 0x72, 0xf7, 0x7b, 0x2a, 0x26, 0xc9, + 0xb2, 0x3b, 0x4f, 0xbe, 0xc2, 0xb7, 0xe5, 0xe5, 0x8b, 0xff, 0x34, 0x7c, 0xea, 0xf9, 0x8f, 0xdd, + 0xd6, 0x7e, 0x0f, 0x91, 0x57, 0x4c, 0x91, 0x71, 0x3e, 0x39, 0x78, 0xbe, 0x82, 0xb9, 0x6a, 0xb6, + 0xf0, 0x9e, 0x42, 0xba, 0xaf, 0x31, 0x55, 0xb8, 0x88, 0x21, 0x34, 0xa7, 0xad, 0x7b, 0x57, 0xa1, + 0x5f, 0xf0, 0xac, 0x8d, 0xd2, 0x61, 0x5f, 0xf7, 0xfc, 0x46, 0x6f, 0x89, 0x25, 0x33, 0x9a, 0x36, + 0xc6, 0x26, 0x95, 0xf1, 0x05, 0x9c, 0xaf, 0x66, 0x8a, 0xee, 0x25, 0xa2, 0xd7, 0x76, 0xf2, 0x02, + 0xaa, 0xe2, 0x94, 0xf5, 0xee, 0x2a, 0x73, 0x15, 0x27, 0x36, 0xb6, 0x18, 0x6d, 0xf9, 0xf4, 0x11, + 0xf5, 0x31, 0x66, 0xbb, 0x57, 0xb4, 0xb2, 0x49, 0x2e, 0x5b, 0xbe, 0x0a, 0x67, 0xab, 0x09, 0x51, + 0x59, 0x2c, 0xbd, 0x0e, 0x81, 0xa6, 0xb0, 0xa5, 0x27, 0xac, 0x57, 0x8f, 0x50, 0xa1, 0xd1, 0x65, + 0x1a, 0xee, 0xac, 0xf6, 0xd0, 0x92, 0xbc, 0x54, 0x20, 0x09, 0x77, 0xef, 0x32, 0xce, 0xaa, 0xc6, + 0x99, 0xa4, 0xc8, 0xfc, 0x78, 0x3f, 0x95, 0x07, 0x22, 0x3d, 0x8b, 0xcd, 0x92, 0xf0, 0x26, 0xce, + 0x85, 0x22, 0x6e, 0x79, 0x36, 0xda, 0x07, 0x70, 0x48, 0xe4, 0xaf, 0xd3, 0x42, 0x98, 0x03, 0x52, + 0xe6, 0x36, 0x20, 0x1f, 0x1e, 0x02, 0x76, 0x39, 0x11, 0xcf, 0xde, 0x55, 0x04, 0x77, 0x85, 0xae, + 0x79, 0xf5, 0xc7, 0xba, 0x2b, 0x94, 0xfd, 0x8e, 0xfb, 0x08, 0x19, 0x6c, 0x77, 0x5e, 0xcc, 0xf8, + 0xec, 0x87, 0x11, 0xfd, 0x85, 0x80, 0x68, 0xc6, 0x8f, 0xc3, 0x85, 0x1b, 0xe9, 0x4d, 0xe9, 0x60, + 0xc4, 0x02, 0x4d, 0xc9, 0x99, 0xaa, 0x51, 0xbe, 0x45, 0x64, 0x32, 0x7d, 0x8b, 0x7a, 0x45, 0xb3, + 0x1d, 0xfa, 0xc4, 0xa6, 0xed, 0x26, 0x86, 0x42, 0x1f, 0x7a, 0x9c, 0x27, 0x8a, 0x8e, 0x4d, 0xa2, + 0x7a, 0x07, 0x71, 0x4d, 0x89, 0xd0, 0x1f, 0x43, 0xf1, 0x2a, 0xab, 0x72, 0x12, 0x17, 0xa9, 0x52, + 0x8f, 0x48, 0xba, 0x93, 0x23, 0x1b, 0x70, 0x6e, 0x99, 0x86, 0x62, 0x8e, 0xb3, 0x69, 0x10, 0xfa, + 0x6e, 0x3d, 0xec, 0x7a, 0x3a, 0x28, 0x0d, 0x94, 0x14, 0x9e, 0xdd, 0xb7, 0x98, 0xbc, 0x6a, 0xba, + 0xbc, 0xae, 0x7c, 0x5d, 0xe2, 0x64, 0xc5, 0x91, 0xc3, 0x69, 0xaa, 0x98, 0x3d, 0xc4, 0x87, 0x78, + 0x18, 0x4e, 0x36, 0x6b, 0x21, 0x4a, 0x81, 0x22, 0x4c, 0xae, 0x5b, 0x30, 0xc8, 0x99, 0x32, 0x17, + 0xd4, 0x31, 0x9d, 0x87, 0xdc, 0x85, 0x11, 0x15, 0x47, 0x43, 0x0c, 0x54, 0x66, 0xbd, 0xee, 0xc2, + 0x08, 0xb7, 0xaf, 0x4e, 0xce, 0xf2, 0x01, 0x8c, 0xa8, 0xc0, 0x9b, 0x53, 0xaf, 0xf4, 0x9f, 0xc0, + 0xb8, 0x1e, 0x82, 0x73, 0x7a, 0x45, 0x7e, 0x84, 0x67, 0xb8, 0xf2, 0xa8, 0x24, 0x9b, 0x7f, 0x26, + 0x96, 0x19, 0x46, 0xa8, 0x94, 0x4f, 0x90, 0x12, 0x98, 0x59, 0xfd, 0xb3, 0x09, 0x6e, 0xf2, 0x81, + 0xbc, 0xce, 0xa4, 0x98, 0x93, 0x44, 0x5d, 0x74, 0x36, 0xc1, 0xd5, 0xfc, 0x3c, 0xcc, 0x6a, 0x82, + 0xed, 0x59, 0xed, 0x93, 0x9c, 0x35, 0xf7, 0x56, 0x5d, 0x96, 0x94, 0x4d, 0xdc, 0xa5, 0x25, 0x5e, + 0x63, 0xcc, 0x16, 0x74, 0x39, 0xfb, 0x01, 0x47, 0xec, 0x8c, 0xfb, 0x68, 0x0a, 0x26, 0xb0, 0x99, + 0xcd, 0xeb, 0xf2, 0x20, 0x64, 0x64, 0xfb, 0x26, 0xc5, 0x75, 0x61, 0xeb, 0x66, 0x4a, 0x8b, 0x4b, + 0x9a, 0x2f, 0x45, 0xdc, 0xaa, 0x8c, 0x64, 0x3c, 0x79, 0x63, 0xb3, 0x6b, 0x76, 0x21, 0xe5, 0x74, + 0xbb, 0x67, 0x5f, 0x64, 0x89, 0xfb, 0x05, 0xdc, 0x1d, 0xa6, 0xa6, 0x06, 0xcb, 0x16, 0x76, 0x43, + 0x0b, 0x90, 0x48, 0xe5, 0x54, 0x8b, 0xde, 0x63, 0xbc, 0x27, 0x96, 0xfe, 0x5e, 0xe5, 0xab, 0x3d, + 0xa4, 0x48, 0x4d, 0xbc, 0xd6, 0x93, 0x4e, 0x9d, 0x95, 0x5e, 0xe0, 0x2b, 0x6c, 0x7a, 0x79, 0x3d, + 0xde, 0xdf, 0x4c, 0x39, 0xbe, 0x56, 0x61, 0xa2, 0xe9, 0x02, 0xcd, 0x30, 0xd1, 0xae, 0x6d, 0xc8, + 0x52, 0xff, 0x67, 0x50, 0x8a, 0xa2, 0x40, 0x4e, 0xd7, 0x09, 0xd9, 0xd1, 0x89, 0x24, 0xa1, 0xa9, + 0x80, 0x74, 0x7b, 0xb7, 0x69, 0xee, 0x6a, 0x96, 0x86, 0xf5, 0xbb, 0x30, 0x22, 0xba, 0x2d, 0xf6, + 0x72, 0x6b, 0xd6, 0x1b, 0xb0, 0x5d, 0x9c, 0xb1, 0xe2, 0xe2, 0xdc, 0x4b, 0x11, 0x94, 0xec, 0xed, + 0xd3, 0x0b, 0x52, 0x41, 0x1a, 0x31, 0x41, 0x56, 0x97, 0xee, 0xed, 0x7d, 0xfe, 0x58, 0xcc, 0xe8, + 0xd7, 0xd3, 0x77, 0xa8, 0x13, 0x5d, 0x16, 0x8b, 0x65, 0x12, 0xd4, 0x2f, 0xe8, 0x26, 0x51, 0xf1, + 0x9b, 0x4e, 0x69, 0x14, 0x2a, 0x32, 0xaa, 0x28, 0x8b, 0x60, 0x70, 0x66, 0x8a, 0x78, 0xbe, 0x1b, + 0x3e, 0x5b, 0xb0, 0xd7, 0x22, 0xb7, 0x82, 0x8e, 0x90, 0xb2, 0x41, 0x22, 0xed, 0x35, 0xf2, 0x25, + 0x4e, 0x25, 0x42, 0x7c, 0xc5, 0xf3, 0xc2, 0x20, 0xf4, 0x9d, 0x76, 0x15, 0x5f, 0xb4, 0xce, 0x6c, + 0x74, 0x14, 0xc8, 0x9d, 0xc6, 0xa6, 0xc5, 0x95, 0x8a, 0x64, 0xf6, 0x69, 0xe9, 0x6f, 0xd4, 0xdd, + 0x9a, 0x34, 0x64, 0x17, 0xcb, 0xa5, 0x2a, 0xd3, 0xd7, 0xbf, 0x4c, 0xa1, 0x35, 0x98, 0xcd, 0x48, + 0x1a, 0xa4, 0x8e, 0x70, 0xbb, 0x27, 0x15, 0x9a, 0xeb, 0x5e, 0x30, 0xf9, 0x0a, 0x66, 0x52, 0xb3, + 0x0a, 0x29, 0x37, 0x74, 0xb7, 0x9c, 0x43, 0xbd, 0x84, 0x3f, 0x86, 0x22, 0xbf, 0xd5, 0x81, 0xc1, + 0xcb, 0x46, 0x82, 0x99, 0xe8, 0xae, 0x4f, 0x06, 0x41, 0x7c, 0xbe, 0xce, 0xa6, 0x53, 0x37, 0xce, + 0xa7, 0x31, 0xb3, 0x88, 0x7c, 0x82, 0x9b, 0xd6, 0xbd, 0x27, 0xd4, 0x7f, 0xa6, 0x3e, 0xbc, 0x34, + 0x64, 0xb7, 0x0b, 0x45, 0x5b, 0x30, 0xb3, 0x4b, 0x7d, 0xf7, 0xd1, 0xb3, 0xb8, 0x40, 0xa9, 0x99, + 0x54, 0x6c, 0x37, 0x89, 0x9f, 0xc3, 0xec, 0x82, 0x77, 0xd8, 0x16, 0x57, 0xf7, 0x0c, 0x99, 0xea, + 0x3c, 0x3e, 0x1d, 0xdf, 0x3b, 0xa0, 0x69, 0x4e, 0xdd, 0x2d, 0xd4, 0xf9, 0x16, 0xf0, 0x4e, 0xeb, + 0x0d, 0x33, 0xa6, 0x20, 0x85, 0x24, 0xba, 0x91, 0x21, 0x4d, 0x39, 0x9d, 0x7f, 0x1b, 0x07, 0x61, + 0x8c, 0x8f, 0xfb, 0xe6, 0xb4, 0x41, 0x98, 0x86, 0xef, 0x7e, 0x11, 0x2c, 0x45, 0x2a, 0x2f, 0x30, + 0x5b, 0xea, 0x09, 0x6a, 0xbb, 0x21, 0xd7, 0x96, 0x2d, 0xdf, 0x7d, 0xe2, 0x36, 0xe9, 0xbe, 0x70, + 0x23, 0x9a, 0x91, 0xd3, 0x26, 0xb2, 0x5b, 0x3d, 0xb5, 0xd4, 0x0a, 0xcd, 0x66, 0x97, 0x2d, 0x16, + 0xd1, 0x73, 0x2b, 0x30, 0x4a, 0xf4, 0xe4, 0x8f, 0xeb, 0xbc, 0xdd, 0x66, 0xeb, 0x04, 0x33, 0x6e, + 0x6a, 0xdf, 0x87, 0xb1, 0xaa, 0x5e, 0x78, 0x4a, 0x21, 0x99, 0x83, 0x42, 0x5d, 0x05, 0xea, 0x5d, + 0xf7, 0x2e, 0x01, 0xa1, 0x6a, 0xe1, 0x39, 0x51, 0x2b, 0x32, 0xe3, 0x67, 0x8c, 0x07, 0xe8, 0xd4, + 0x2a, 0x90, 0xf6, 0x06, 0xa7, 0x8a, 0x9f, 0x49, 0x7f, 0xb3, 0xae, 0xc6, 0x9f, 0xb5, 0x89, 0x3f, + 0xb1, 0x4a, 0xac, 0xde, 0x6f, 0x19, 0xab, 0xc0, 0xf8, 0xae, 0x6f, 0xb4, 0xf2, 0x60, 0x9f, 0xe8, + 0xc9, 0x3d, 0x3d, 0xd8, 0x27, 0xf1, 0x90, 0x9f, 0x1e, 0xec, 0x93, 0xf2, 0x4a, 0x5f, 0x15, 0x0a, + 0xf1, 0x37, 0x04, 0x95, 0x5b, 0x29, 0xe3, 0x89, 0x44, 0x15, 0xd6, 0x93, 0xf9, 0xf8, 0xe0, 0x12, + 0x56, 0x30, 0x7a, 0x64, 0xa8, 0x8b, 0x87, 0x43, 0xd5, 0x2d, 0xe5, 0x2d, 0xa3, 0x07, 0x7a, 0xda, + 0x0f, 0xfe, 0x34, 0x51, 0x17, 0x07, 0x6e, 0x3c, 0xdd, 0x47, 0xec, 0x2d, 0xa3, 0x7b, 0x50, 0xe0, + 0xaf, 0x34, 0x44, 0xd9, 0x12, 0xa3, 0xa0, 0xc2, 0xe4, 0xe3, 0x11, 0x5d, 0x46, 0x4a, 0x21, 0x9e, + 0x63, 0x4e, 0x29, 0x2c, 0x23, 0xf9, 0x5c, 0x97, 0xf1, 0x0f, 0x51, 0x26, 0x39, 0xe5, 0xed, 0x4a, + 0x24, 0x97, 0x9b, 0x3b, 0x9f, 0x82, 0x51, 0xfb, 0xd4, 0x31, 0x3d, 0xef, 0x9c, 0x6a, 0x52, 0x4a, + 0x32, 0xba, 0xb9, 0x0b, 0xa9, 0x38, 0x21, 0x28, 0xe4, 0x6f, 0x84, 0xa7, 0x3f, 0xd9, 0x1e, 0x5d, + 0x11, 0xeb, 0x42, 0x23, 0x8b, 0xb9, 0x79, 0x12, 0x52, 0x51, 0x2a, 0x55, 0x4f, 0x2c, 0xa5, 0xbc, + 0x13, 0xff, 0x5a, 0xca, 0x2d, 0x0e, 0x83, 0x22, 0x1a, 0x90, 0xdd, 0x1f, 0xad, 0x27, 0x0f, 0xe5, + 0x93, 0x37, 0x19, 0x25, 0xf5, 0x12, 0x90, 0xd9, 0x83, 0x0f, 0xe5, 0x23, 0x37, 0x2f, 0x5b, 0xf0, + 0x1e, 0x5c, 0x8c, 0x5d, 0x0d, 0x31, 0x05, 0xdf, 0x4c, 0xbf, 0x3f, 0x92, 0xaa, 0x9e, 0x6c, 0x43, + 0xe0, 0x4a, 0xf2, 0x0a, 0x49, 0xac, 0xdf, 0x4f, 0x3b, 0x91, 0xae, 0xc3, 0x04, 0xce, 0x5d, 0x01, + 0xf5, 0x97, 0x7d, 0xaf, 0xd3, 0x8e, 0xb2, 0xce, 0x98, 0xe0, 0x78, 0xfa, 0xa3, 0x38, 0x56, 0xdd, + 0x4c, 0x1f, 0x13, 0xd7, 0x8d, 0x11, 0xa1, 0x67, 0xc3, 0x51, 0xc0, 0xb4, 0xa5, 0x11, 0x11, 0xbb, + 0x77, 0xc9, 0x47, 0x30, 0x19, 0xdd, 0x42, 0xe6, 0x22, 0x52, 0xc8, 0xba, 0x78, 0xdf, 0x26, 0xa3, + 0xab, 0xc8, 0xa7, 0x67, 0x5f, 0x91, 0xeb, 0x5b, 0xc4, 0x7e, 0x29, 0x71, 0x91, 0xc6, 0x68, 0xc3, + 0x49, 0x96, 0x39, 0x4d, 0xb7, 0xa7, 0xed, 0x9d, 0x3a, 0x7e, 0x6e, 0xe9, 0x09, 0x15, 0xf5, 0xcf, + 0xad, 0x6b, 0xd2, 0x47, 0xb5, 0xa7, 0xce, 0x90, 0xb3, 0x0e, 0xd7, 0x30, 0x09, 0xcb, 0x16, 0x4f, + 0xbb, 0x97, 0x4e, 0x95, 0x5d, 0xf7, 0x78, 0xea, 0x96, 0x26, 0x5c, 0xed, 0x99, 0x51, 0x92, 0xdc, + 0x36, 0x82, 0x67, 0x7a, 0xe7, 0x9e, 0xec, 0x62, 0xce, 0x4c, 0xa7, 0x25, 0x66, 0x54, 0x8b, 0x77, + 0x97, 0x1c, 0x91, 0x6a, 0xf1, 0xee, 0x9a, 0xd9, 0xf1, 0x73, 0x7c, 0x47, 0x4a, 0xac, 0x51, 0x98, + 0x58, 0x89, 0xb6, 0x78, 0xba, 0xe9, 0xae, 0x67, 0x49, 0x57, 0xcd, 0x93, 0xd6, 0x04, 0x23, 0x1a, + 0x4a, 0x97, 0x85, 0x79, 0x97, 0x25, 0xbc, 0xb7, 0x90, 0x2e, 0x41, 0xdb, 0x97, 0xf9, 0x00, 0x3c, + 0x75, 0xcd, 0x33, 0xe0, 0x95, 0xc5, 0x9f, 0xfc, 0x97, 0xcb, 0xb9, 0x9f, 0xfc, 0xf4, 0x72, 0xee, + 0x3f, 0xfc, 0xf4, 0x72, 0xee, 0x3f, 0xff, 0xf4, 0x72, 0xee, 0xcb, 0xf9, 0x93, 0x25, 0x3d, 0xe6, + 0x2f, 0x3f, 0xde, 0xe6, 0xe2, 0x06, 0xf1, 0xbf, 0x37, 0xff, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xf4, 0x4b, 0x4f, 0x24, 0xf2, 0xf0, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -17457,6 +17612,8 @@ type AuthServiceClient interface { // GetTrustedClusters gets all current Trusted Cluster resources. GetTrustedClusters(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*types.TrustedClusterV2List, error) // UpsertTrustedCluster upserts a Trusted Cluster in a backend. + // + // Deprecated: Use [teleport.trust.v1.UpsertTrustedCluster] instead. UpsertTrustedCluster(ctx context.Context, in *types.TrustedClusterV2, opts ...grpc.CallOption) (*types.TrustedClusterV2, error) // DeleteTrustedCluster deletes an existing Trusted Cluster in a backend by name. DeleteTrustedCluster(ctx context.Context, in *types.ResourceRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) @@ -17687,6 +17844,8 @@ type AuthServiceClient interface { // which is what we want when handling things like ambiguous host errors and resource-based access requests, // but may result in confusing behavior if it is used outside of those contexts. GetSSHTargets(ctx context.Context, in *GetSSHTargetsRequest, opts ...grpc.CallOption) (*GetSSHTargetsResponse, error) + // ResolveSSHTarget returns the server that would be resolved in an equivalent ssh dial request. + ResolveSSHTarget(ctx context.Context, in *ResolveSSHTargetRequest, opts ...grpc.CallOption) (*ResolveSSHTargetResponse, error) // GetDomainName returns local auth domain of the current auth server GetDomainName(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetDomainNameResponse, error) // GetClusterCACert returns the PEM-encoded TLS certs for the local cluster @@ -19436,6 +19595,7 @@ func (c *authServiceClient) GetTrustedClusters(ctx context.Context, in *emptypb. return out, nil } +// Deprecated: Do not use. func (c *authServiceClient) UpsertTrustedCluster(ctx context.Context, in *types.TrustedClusterV2, opts ...grpc.CallOption) (*types.TrustedClusterV2, error) { out := new(types.TrustedClusterV2) err := c.cc.Invoke(ctx, "/proto.AuthService/UpsertTrustedCluster", in, out, opts...) @@ -20215,6 +20375,15 @@ func (c *authServiceClient) GetSSHTargets(ctx context.Context, in *GetSSHTargets return out, nil } +func (c *authServiceClient) ResolveSSHTarget(ctx context.Context, in *ResolveSSHTargetRequest, opts ...grpc.CallOption) (*ResolveSSHTargetResponse, error) { + out := new(ResolveSSHTargetResponse) + err := c.cc.Invoke(ctx, "/proto.AuthService/ResolveSSHTarget", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *authServiceClient) GetDomainName(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetDomainNameResponse, error) { out := new(GetDomainNameResponse) err := c.cc.Invoke(ctx, "/proto.AuthService/GetDomainName", in, out, opts...) @@ -20835,6 +21004,8 @@ type AuthServiceServer interface { // GetTrustedClusters gets all current Trusted Cluster resources. GetTrustedClusters(context.Context, *emptypb.Empty) (*types.TrustedClusterV2List, error) // UpsertTrustedCluster upserts a Trusted Cluster in a backend. + // + // Deprecated: Use [teleport.trust.v1.UpsertTrustedCluster] instead. UpsertTrustedCluster(context.Context, *types.TrustedClusterV2) (*types.TrustedClusterV2, error) // DeleteTrustedCluster deletes an existing Trusted Cluster in a backend by name. DeleteTrustedCluster(context.Context, *types.ResourceRequest) (*emptypb.Empty, error) @@ -21065,6 +21236,8 @@ type AuthServiceServer interface { // which is what we want when handling things like ambiguous host errors and resource-based access requests, // but may result in confusing behavior if it is used outside of those contexts. GetSSHTargets(context.Context, *GetSSHTargetsRequest) (*GetSSHTargetsResponse, error) + // ResolveSSHTarget returns the server that would be resolved in an equivalent ssh dial request. + ResolveSSHTarget(context.Context, *ResolveSSHTargetRequest) (*ResolveSSHTargetResponse, error) // GetDomainName returns local auth domain of the current auth server GetDomainName(context.Context, *emptypb.Empty) (*GetDomainNameResponse, error) // GetClusterCACert returns the PEM-encoded TLS certs for the local cluster @@ -21817,6 +21990,9 @@ func (*UnimplementedAuthServiceServer) ListUnifiedResources(ctx context.Context, func (*UnimplementedAuthServiceServer) GetSSHTargets(ctx context.Context, req *GetSSHTargetsRequest) (*GetSSHTargetsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetSSHTargets not implemented") } +func (*UnimplementedAuthServiceServer) ResolveSSHTarget(ctx context.Context, req *ResolveSSHTargetRequest) (*ResolveSSHTargetResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ResolveSSHTarget not implemented") +} func (*UnimplementedAuthServiceServer) GetDomainName(ctx context.Context, req *emptypb.Empty) (*GetDomainNameResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetDomainName not implemented") } @@ -26120,6 +26296,24 @@ func _AuthService_GetSSHTargets_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _AuthService_ResolveSSHTarget_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResolveSSHTargetRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).ResolveSSHTarget(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.AuthService/ResolveSSHTarget", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).ResolveSSHTarget(ctx, req.(*ResolveSSHTargetRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _AuthService_GetDomainName_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(emptypb.Empty) if err := dec(in); err != nil { @@ -27433,6 +27627,10 @@ var _AuthService_serviceDesc = grpc.ServiceDesc{ MethodName: "GetSSHTargets", Handler: _AuthService_GetSSHTargets_Handler, }, + { + MethodName: "ResolveSSHTarget", + Handler: _AuthService_ResolveSSHTarget_Handler, + }, { MethodName: "GetDomainName", Handler: _AuthService_GetDomainName_Handler, @@ -36310,6 +36508,121 @@ func (m *ListResourcesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ResolveSSHTargetRequest) 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 *ResolveSSHTargetRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResolveSSHTargetRequest) 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 len(m.SearchKeywords) > 0 { + for iNdEx := len(m.SearchKeywords) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.SearchKeywords[iNdEx]) + copy(dAtA[i:], m.SearchKeywords[iNdEx]) + i = encodeVarintAuthservice(dAtA, i, uint64(len(m.SearchKeywords[iNdEx]))) + i-- + dAtA[i] = 0x2a + } + } + if len(m.PredicateExpression) > 0 { + i -= len(m.PredicateExpression) + copy(dAtA[i:], m.PredicateExpression) + i = encodeVarintAuthservice(dAtA, i, uint64(len(m.PredicateExpression))) + i-- + dAtA[i] = 0x22 + } + if len(m.Labels) > 0 { + for k := range m.Labels { + v := m.Labels[k] + baseI := i + i -= len(v) + copy(dAtA[i:], v) + i = encodeVarintAuthservice(dAtA, i, uint64(len(v))) + i-- + dAtA[i] = 0x12 + i -= len(k) + copy(dAtA[i:], k) + i = encodeVarintAuthservice(dAtA, i, uint64(len(k))) + i-- + dAtA[i] = 0xa + i = encodeVarintAuthservice(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x1a + } + } + if len(m.Port) > 0 { + i -= len(m.Port) + copy(dAtA[i:], m.Port) + i = encodeVarintAuthservice(dAtA, i, uint64(len(m.Port))) + i-- + dAtA[i] = 0x12 + } + if len(m.Host) > 0 { + i -= len(m.Host) + copy(dAtA[i:], m.Host) + i = encodeVarintAuthservice(dAtA, i, uint64(len(m.Host))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ResolveSSHTargetResponse) 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 *ResolveSSHTargetResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResolveSSHTargetResponse) 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.Server != nil { + { + size, err := m.Server.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthservice(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *GetSSHTargetsRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -36682,12 +36995,12 @@ func (m *SessionTrackerUpdateExpiry) MarshalToSizedBuffer(dAtA []byte) (int, err copy(dAtA[i:], m.XXX_unrecognized) } if m.Expires != nil { - n107, err107 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.Expires):]) - if err107 != nil { - return 0, err107 + n108, err108 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.Expires):]) + if err108 != nil { + return 0, err108 } - i -= n107 - i = encodeVarintAuthservice(dAtA, i, uint64(n107)) + i -= n108 + i = encodeVarintAuthservice(dAtA, i, uint64(n108)) i-- dAtA[i] = 0xa } @@ -37971,12 +38284,12 @@ func (m *UpstreamInventoryPong) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n131, err131 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.SystemClock, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.SystemClock):]) - if err131 != nil { - return 0, err131 + n132, err132 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.SystemClock, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.SystemClock):]) + if err132 != nil { + return 0, err132 } - i -= n131 - i = encodeVarintAuthservice(dAtA, i, uint64(n131)) + i -= n132 + i = encodeVarintAuthservice(dAtA, i, uint64(n132)) i-- dAtA[i] = 0x12 if m.ID != 0 { @@ -43754,6 +44067,60 @@ func (m *ListResourcesRequest) Size() (n int) { return n } +func (m *ResolveSSHTargetRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Host) + if l > 0 { + n += 1 + l + sovAuthservice(uint64(l)) + } + l = len(m.Port) + if l > 0 { + n += 1 + l + sovAuthservice(uint64(l)) + } + if len(m.Labels) > 0 { + for k, v := range m.Labels { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovAuthservice(uint64(len(k))) + 1 + len(v) + sovAuthservice(uint64(len(v))) + n += mapEntrySize + 1 + sovAuthservice(uint64(mapEntrySize)) + } + } + l = len(m.PredicateExpression) + if l > 0 { + n += 1 + l + sovAuthservice(uint64(l)) + } + if len(m.SearchKeywords) > 0 { + for _, s := range m.SearchKeywords { + l = len(s) + n += 1 + l + sovAuthservice(uint64(l)) + } + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *ResolveSSHTargetResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Server != nil { + l = m.Server.Size() + n += 1 + l + sovAuthservice(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + func (m *GetSSHTargetsRequest) Size() (n int) { if m == nil { return 0 @@ -67095,6 +67462,399 @@ func (m *ListResourcesRequest) Unmarshal(dAtA []byte) error { } return nil } +func (m *ResolveSSHTargetRequest) 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 ErrIntOverflowAuthservice + } + 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: ResolveSSHTargetRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ResolveSSHTargetRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Host", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + 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 ErrInvalidLengthAuthservice + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Host = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + 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 ErrInvalidLengthAuthservice + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Port = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Labels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Labels == nil { + m.Labels = make(map[string]string) + } + var mapkey string + var mapvalue string + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthAuthservice + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthAuthservice + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthAuthservice + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue < 0 { + return ErrInvalidLengthAuthservice + } + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + } else { + iNdEx = entryPreIndex + skippy, err := skipAuthservice(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthservice + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Labels[mapkey] = mapvalue + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PredicateExpression", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + 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 ErrInvalidLengthAuthservice + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PredicateExpression = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SearchKeywords", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + 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 ErrInvalidLengthAuthservice + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SearchKeywords = append(m.SearchKeywords, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuthservice(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthservice + } + 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 *ResolveSSHTargetResponse) 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 ErrIntOverflowAuthservice + } + 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: ResolveSSHTargetResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ResolveSSHTargetResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Server", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Server == nil { + m.Server = &types.ServerV2{} + } + if err := m.Server.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuthservice(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthservice + } + 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 *GetSSHTargetsRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/api/gen/proto/go/teleport/autoupdate/v1/autoupdate.pb.go b/api/gen/proto/go/teleport/autoupdate/v1/autoupdate.pb.go index 7fc41ec3f3858..8d7e52517814b 100644 --- a/api/gen/proto/go/teleport/autoupdate/v1/autoupdate.pb.go +++ b/api/gen/proto/go/teleport/autoupdate/v1/autoupdate.pb.go @@ -997,6 +997,7 @@ type AutoUpdateAgentRolloutStatus struct { // For example, a group updates every day between 13:00 and 14:00. If the target version changes to 13:30, the group // will not start updating to the new version directly. The controller sees that the group theoretical start time is // before the rollout start time and the maintenance window belongs to the previous rollout. + // When the timestamp is nil, the controller will ignore the start time and check and allow groups to activate. StartTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache diff --git a/api/gen/proto/go/teleport/trust/v1/trust_service.pb.go b/api/gen/proto/go/teleport/trust/v1/trust_service.pb.go index 349eb18c6ae9e..5f6c82ec9c41d 100644 --- a/api/gen/proto/go/teleport/trust/v1/trust_service.pb.go +++ b/api/gen/proto/go/teleport/trust/v1/trust_service.pb.go @@ -38,6 +38,144 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// Request for UpsertTrustedCluster. +type UpsertTrustedClusterRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // TrustedCluster specifies a Trusted Cluster resource. + TrustedCluster *types.TrustedClusterV2 `protobuf:"bytes,1,opt,name=trusted_cluster,json=trustedCluster,proto3" json:"trusted_cluster,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpsertTrustedClusterRequest) Reset() { + *x = UpsertTrustedClusterRequest{} + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpsertTrustedClusterRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpsertTrustedClusterRequest) ProtoMessage() {} + +func (x *UpsertTrustedClusterRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[0] + 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 UpsertTrustedClusterRequest.ProtoReflect.Descriptor instead. +func (*UpsertTrustedClusterRequest) Descriptor() ([]byte, []int) { + return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{0} +} + +func (x *UpsertTrustedClusterRequest) GetTrustedCluster() *types.TrustedClusterV2 { + if x != nil { + return x.TrustedCluster + } + return nil +} + +// Request for CreateTrustedCluster. +type CreateTrustedClusterRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // TrustedCluster specifies a Trusted Cluster resource. + TrustedCluster *types.TrustedClusterV2 `protobuf:"bytes,1,opt,name=trusted_cluster,json=trustedCluster,proto3" json:"trusted_cluster,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateTrustedClusterRequest) Reset() { + *x = CreateTrustedClusterRequest{} + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateTrustedClusterRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTrustedClusterRequest) ProtoMessage() {} + +func (x *CreateTrustedClusterRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[1] + 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 CreateTrustedClusterRequest.ProtoReflect.Descriptor instead. +func (*CreateTrustedClusterRequest) Descriptor() ([]byte, []int) { + return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateTrustedClusterRequest) GetTrustedCluster() *types.TrustedClusterV2 { + if x != nil { + return x.TrustedCluster + } + return nil +} + +// Request for UpdateTrustedCluster. +type UpdateTrustedClusterRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // TrustedCluster specifies a Trusted Cluster resource. + TrustedCluster *types.TrustedClusterV2 `protobuf:"bytes,1,opt,name=trusted_cluster,json=trustedCluster,proto3" json:"trusted_cluster,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateTrustedClusterRequest) Reset() { + *x = UpdateTrustedClusterRequest{} + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateTrustedClusterRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTrustedClusterRequest) ProtoMessage() {} + +func (x *UpdateTrustedClusterRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_trust_v1_trust_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) +} + +// Deprecated: Use UpdateTrustedClusterRequest.ProtoReflect.Descriptor instead. +func (*UpdateTrustedClusterRequest) Descriptor() ([]byte, []int) { + return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{2} +} + +func (x *UpdateTrustedClusterRequest) GetTrustedCluster() *types.TrustedClusterV2 { + if x != nil { + return x.TrustedCluster + } + return nil +} + // Request for GetCertAuthority type GetCertAuthorityRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -53,7 +191,7 @@ type GetCertAuthorityRequest struct { func (x *GetCertAuthorityRequest) Reset() { *x = GetCertAuthorityRequest{} - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[0] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -65,7 +203,7 @@ func (x *GetCertAuthorityRequest) String() string { func (*GetCertAuthorityRequest) ProtoMessage() {} func (x *GetCertAuthorityRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[0] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -78,7 +216,7 @@ func (x *GetCertAuthorityRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCertAuthorityRequest.ProtoReflect.Descriptor instead. func (*GetCertAuthorityRequest) Descriptor() ([]byte, []int) { - return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{0} + return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{3} } func (x *GetCertAuthorityRequest) GetType() string { @@ -115,7 +253,7 @@ type GetCertAuthoritiesRequest struct { func (x *GetCertAuthoritiesRequest) Reset() { *x = GetCertAuthoritiesRequest{} - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[1] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -127,7 +265,7 @@ func (x *GetCertAuthoritiesRequest) String() string { func (*GetCertAuthoritiesRequest) ProtoMessage() {} func (x *GetCertAuthoritiesRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[1] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -140,7 +278,7 @@ func (x *GetCertAuthoritiesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCertAuthoritiesRequest.ProtoReflect.Descriptor instead. func (*GetCertAuthoritiesRequest) Descriptor() ([]byte, []int) { - return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{1} + return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{4} } func (x *GetCertAuthoritiesRequest) GetType() string { @@ -168,7 +306,7 @@ type GetCertAuthoritiesResponse struct { func (x *GetCertAuthoritiesResponse) Reset() { *x = GetCertAuthoritiesResponse{} - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[2] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -180,7 +318,7 @@ func (x *GetCertAuthoritiesResponse) String() string { func (*GetCertAuthoritiesResponse) ProtoMessage() {} func (x *GetCertAuthoritiesResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[2] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -193,7 +331,7 @@ func (x *GetCertAuthoritiesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCertAuthoritiesResponse.ProtoReflect.Descriptor instead. func (*GetCertAuthoritiesResponse) Descriptor() ([]byte, []int) { - return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{2} + return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{5} } func (x *GetCertAuthoritiesResponse) GetCertAuthoritiesV2() []*types.CertAuthorityV2 { @@ -216,7 +354,7 @@ type DeleteCertAuthorityRequest struct { func (x *DeleteCertAuthorityRequest) Reset() { *x = DeleteCertAuthorityRequest{} - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[3] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -228,7 +366,7 @@ func (x *DeleteCertAuthorityRequest) String() string { func (*DeleteCertAuthorityRequest) ProtoMessage() {} func (x *DeleteCertAuthorityRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[3] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -241,7 +379,7 @@ func (x *DeleteCertAuthorityRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteCertAuthorityRequest.ProtoReflect.Descriptor instead. func (*DeleteCertAuthorityRequest) Descriptor() ([]byte, []int) { - return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{3} + return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{6} } func (x *DeleteCertAuthorityRequest) GetType() string { @@ -269,7 +407,7 @@ type UpsertCertAuthorityRequest struct { func (x *UpsertCertAuthorityRequest) Reset() { *x = UpsertCertAuthorityRequest{} - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[4] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -281,7 +419,7 @@ func (x *UpsertCertAuthorityRequest) String() string { func (*UpsertCertAuthorityRequest) ProtoMessage() {} func (x *UpsertCertAuthorityRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[4] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -294,7 +432,7 @@ func (x *UpsertCertAuthorityRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertCertAuthorityRequest.ProtoReflect.Descriptor instead. func (*UpsertCertAuthorityRequest) Descriptor() ([]byte, []int) { - return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{4} + return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{7} } func (x *UpsertCertAuthorityRequest) GetCertAuthority() *types.CertAuthorityV2 { @@ -333,7 +471,7 @@ type RotateCertAuthorityRequest struct { func (x *RotateCertAuthorityRequest) Reset() { *x = RotateCertAuthorityRequest{} - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[5] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -345,7 +483,7 @@ func (x *RotateCertAuthorityRequest) String() string { func (*RotateCertAuthorityRequest) ProtoMessage() {} func (x *RotateCertAuthorityRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[5] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -358,7 +496,7 @@ func (x *RotateCertAuthorityRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RotateCertAuthorityRequest.ProtoReflect.Descriptor instead. func (*RotateCertAuthorityRequest) Descriptor() ([]byte, []int) { - return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{5} + return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{8} } func (x *RotateCertAuthorityRequest) GetType() string { @@ -411,7 +549,7 @@ type RotationSchedule struct { func (x *RotationSchedule) Reset() { *x = RotationSchedule{} - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[6] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -423,7 +561,7 @@ func (x *RotationSchedule) String() string { func (*RotationSchedule) ProtoMessage() {} func (x *RotationSchedule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[6] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -436,7 +574,7 @@ func (x *RotationSchedule) ProtoReflect() protoreflect.Message { // Deprecated: Use RotationSchedule.ProtoReflect.Descriptor instead. func (*RotationSchedule) Descriptor() ([]byte, []int) { - return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{6} + return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{9} } func (x *RotationSchedule) GetUpdateClients() *timestamppb.Timestamp { @@ -469,7 +607,7 @@ type RotateCertAuthorityResponse struct { func (x *RotateCertAuthorityResponse) Reset() { *x = RotateCertAuthorityResponse{} - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[7] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -481,7 +619,7 @@ func (x *RotateCertAuthorityResponse) String() string { func (*RotateCertAuthorityResponse) ProtoMessage() {} func (x *RotateCertAuthorityResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[7] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -494,7 +632,7 @@ func (x *RotateCertAuthorityResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RotateCertAuthorityResponse.ProtoReflect.Descriptor instead. func (*RotateCertAuthorityResponse) Descriptor() ([]byte, []int) { - return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{7} + return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{10} } // Request for RotateExternalCertAuthority. @@ -508,7 +646,7 @@ type RotateExternalCertAuthorityRequest struct { func (x *RotateExternalCertAuthorityRequest) Reset() { *x = RotateExternalCertAuthorityRequest{} - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[8] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -520,7 +658,7 @@ func (x *RotateExternalCertAuthorityRequest) String() string { func (*RotateExternalCertAuthorityRequest) ProtoMessage() {} func (x *RotateExternalCertAuthorityRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[8] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -533,7 +671,7 @@ func (x *RotateExternalCertAuthorityRequest) ProtoReflect() protoreflect.Message // Deprecated: Use RotateExternalCertAuthorityRequest.ProtoReflect.Descriptor instead. func (*RotateExternalCertAuthorityRequest) Descriptor() ([]byte, []int) { - return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{8} + return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{11} } func (x *RotateExternalCertAuthorityRequest) GetCertAuthority() *types.CertAuthorityV2 { @@ -552,7 +690,7 @@ type RotateExternalCertAuthorityResponse struct { func (x *RotateExternalCertAuthorityResponse) Reset() { *x = RotateExternalCertAuthorityResponse{} - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[9] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -564,7 +702,7 @@ func (x *RotateExternalCertAuthorityResponse) String() string { func (*RotateExternalCertAuthorityResponse) ProtoMessage() {} func (x *RotateExternalCertAuthorityResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[9] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -577,7 +715,7 @@ func (x *RotateExternalCertAuthorityResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use RotateExternalCertAuthorityResponse.ProtoReflect.Descriptor instead. func (*RotateExternalCertAuthorityResponse) Descriptor() ([]byte, []int) { - return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{9} + return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{12} } // GenerateHostCertRequest is the request for GenerateHostCert. @@ -603,7 +741,7 @@ type GenerateHostCertRequest struct { func (x *GenerateHostCertRequest) Reset() { *x = GenerateHostCertRequest{} - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[10] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -615,7 +753,7 @@ func (x *GenerateHostCertRequest) String() string { func (*GenerateHostCertRequest) ProtoMessage() {} func (x *GenerateHostCertRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[10] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -628,7 +766,7 @@ func (x *GenerateHostCertRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GenerateHostCertRequest.ProtoReflect.Descriptor instead. func (*GenerateHostCertRequest) Descriptor() ([]byte, []int) { - return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{10} + return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{13} } func (x *GenerateHostCertRequest) GetKey() []byte { @@ -691,7 +829,7 @@ type GenerateHostCertResponse struct { func (x *GenerateHostCertResponse) Reset() { *x = GenerateHostCertResponse{} - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[11] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -703,7 +841,7 @@ func (x *GenerateHostCertResponse) String() string { func (*GenerateHostCertResponse) ProtoMessage() {} func (x *GenerateHostCertResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[11] + mi := &file_teleport_trust_v1_trust_service_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -716,7 +854,7 @@ func (x *GenerateHostCertResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GenerateHostCertResponse.ProtoReflect.Descriptor instead. func (*GenerateHostCertResponse) Descriptor() ([]byte, []int) { - return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{11} + return file_teleport_trust_v1_trust_service_proto_rawDescGZIP(), []int{14} } func (x *GenerateHostCertResponse) GetSshCertificate() []byte { @@ -740,147 +878,183 @@ var file_teleport_trust_v1_trust_service_proto_rawDesc = []byte{ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, - 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x66, 0x0a, 0x17, 0x47, - 0x65, 0x74, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, - 0x69, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x6b, 0x65, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, - 0x4b, 0x65, 0x79, 0x22, 0x50, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x6e, 0x63, 0x6c, 0x75, - 0x64, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x64, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x43, 0x65, 0x72, 0x74, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x13, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x76, 0x32, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x56, 0x32, 0x52, 0x11, 0x63, 0x65, 0x72, 0x74, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x56, 0x32, 0x22, 0x48, 0x0a, 0x1a, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x5f, 0x0a, 0x1b, 0x55, + 0x70, 0x73, 0x65, 0x72, 0x74, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x0f, 0x74, 0x72, + 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x56, 0x32, 0x52, 0x0e, 0x74, 0x72, + 0x75, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x22, 0x5f, 0x0a, 0x1b, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x0f, 0x74, + 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x54, 0x72, 0x75, + 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x56, 0x32, 0x52, 0x0e, 0x74, + 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x22, 0x5f, 0x0a, + 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x0f, + 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x56, 0x32, 0x52, 0x0e, + 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x22, 0x66, + 0x0a, 0x17, 0x47, 0x65, 0x74, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x5b, 0x0a, 0x1a, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x43, - 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x0e, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x79, - 0x70, 0x65, 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, - 0x79, 0x56, 0x32, 0x52, 0x0d, 0x63, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x22, 0xe6, 0x01, 0x0a, 0x1a, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x43, 0x65, 0x72, - 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x0c, 0x67, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x70, - 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x63, 0x65, 0x50, 0x65, 0x72, - 0x69, 0x6f, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x68, - 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x50, 0x68, 0x61, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x3f, 0x0a, 0x08, 0x73, 0x63, - 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x74, - 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, - 0x65, 0x52, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x22, 0xce, 0x01, 0x0a, 0x10, - 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, - 0x12, 0x41, 0x0a, 0x0e, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x73, 0x12, 0x41, 0x0a, 0x0e, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x6e, 0x63, 0x6c, + 0x75, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x50, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x43, 0x65, 0x72, + 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x6e, + 0x63, 0x6c, 0x75, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x64, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x43, + 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x13, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x76, 0x32, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x56, 0x32, 0x52, 0x11, 0x63, 0x65, 0x72, + 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x56, 0x32, 0x22, 0x48, + 0x0a, 0x1a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x5b, 0x0a, 0x1a, 0x55, 0x70, 0x73, 0x65, + 0x72, 0x74, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x0e, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x56, 0x32, 0x52, 0x0d, 0x63, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0xe6, 0x01, 0x0a, 0x1a, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, + 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x0c, 0x67, 0x72, 0x61, 0x63, + 0x65, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x63, 0x65, + 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x50, 0x68, 0x61, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x3f, 0x0a, + 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x23, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, + 0x64, 0x75, 0x6c, 0x65, 0x52, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x22, 0xce, + 0x01, 0x0a, 0x10, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, + 0x75, 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x0e, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x34, 0x0a, 0x07, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x62, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x07, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x62, 0x79, 0x22, 0x1d, 0x0a, 0x1b, - 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x63, 0x0a, 0x22, 0x52, - 0x6f, 0x74, 0x61, 0x74, 0x65, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x65, 0x72, - 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x3d, 0x0a, 0x0e, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x56, - 0x32, 0x52, 0x0d, 0x63, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, - 0x22, 0x25, 0x0a, 0x23, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe5, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x6e, 0x65, - 0x72, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x6f, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1b, - 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x70, - 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0a, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, - 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, - 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, - 0x6c, 0x65, 0x12, 0x2b, 0x0a, 0x03, 0x74, 0x74, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x74, 0x74, 0x6c, 0x22, - 0x43, 0x0a, 0x18, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, - 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x73, - 0x73, 0x68, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x73, 0x73, 0x68, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x32, 0x87, 0x06, 0x0a, 0x0c, 0x54, 0x72, 0x75, 0x73, 0x74, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x56, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x65, 0x72, 0x74, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2a, 0x2e, 0x74, 0x65, 0x6c, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x65, - 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x56, 0x32, 0x12, 0x71, 0x0a, - 0x12, 0x47, 0x65, 0x74, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, - 0x69, 0x65, 0x73, 0x12, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x65, 0x72, 0x74, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2d, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x5c, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2d, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x5c, - 0x0a, 0x13, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2d, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x41, 0x0a, 0x0e, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x34, 0x0a, 0x07, 0x73, 0x74, 0x61, + 0x6e, 0x64, 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x62, 0x79, 0x22, + 0x1d, 0x0a, 0x1b, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x63, + 0x0a, 0x22, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x65, 0x72, - 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x56, 0x32, 0x12, 0x74, 0x0a, 0x13, - 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x12, 0x2d, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x43, 0x65, - 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x43, 0x65, 0x72, - 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1b, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x45, 0x78, 0x74, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x0e, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x56, 0x32, 0x52, 0x0d, 0x63, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x22, 0x25, 0x0a, 0x23, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x12, 0x35, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x45, 0x78, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x74, 0x65, 0x6c, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, - 0x74, 0x61, 0x74, 0x65, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x65, 0x72, 0x74, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x6b, 0x0a, 0x10, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, - 0x74, 0x43, 0x65, 0x72, 0x74, 0x12, 0x2a, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, - 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2b, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x48, 0x6f, - 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x4e, - 0x5a, 0x4c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, - 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x74, 0x72, 0x75, 0x73, 0x74, 0x76, 0x31, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe5, 0x01, 0x0a, 0x17, 0x47, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x6f, 0x73, 0x74, 0x49, + 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, + 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0x21, + 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x2b, 0x0a, 0x03, 0x74, 0x74, 0x6c, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x74, + 0x74, 0x6c, 0x22, 0x43, 0x0a, 0x18, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x48, 0x6f, + 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, + 0x0a, 0x0f, 0x73, 0x73, 0x68, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x73, 0x73, 0x68, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x32, 0xaa, 0x08, 0x0a, 0x0c, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x56, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, + 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2a, 0x2e, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x56, 0x32, + 0x12, 0x71, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x65, + 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, + 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x65, 0x72, 0x74, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x65, 0x72, + 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2d, 0x2e, 0x74, 0x65, 0x6c, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x12, 0x5c, 0x0a, 0x13, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x43, 0x65, 0x72, 0x74, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2d, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, + 0x65, 0x72, 0x74, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x56, 0x32, 0x12, + 0x74, 0x0a, 0x13, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2d, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, + 0x65, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, + 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1b, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, + 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x35, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, + 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, + 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x10, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, + 0x48, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x12, 0x2a, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, + 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, + 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x5f, 0x0a, 0x14, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x2e, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, + 0x73, 0x65, 0x72, 0x74, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x56, 0x32, 0x12, 0x5f, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x2e, 0x2e, 0x74, 0x65, 0x6c, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x2e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x56, 0x32, 0x12, 0x5f, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x72, 0x75, + 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x2e, 0x2e, 0x74, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x56, 0x32, 0x42, 0x4e, 0x5a, 0x4c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, + 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, + 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x2f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x74, 0x72, 0x75, + 0x73, 0x74, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -895,54 +1069,67 @@ func file_teleport_trust_v1_trust_service_proto_rawDescGZIP() []byte { return file_teleport_trust_v1_trust_service_proto_rawDescData } -var file_teleport_trust_v1_trust_service_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_teleport_trust_v1_trust_service_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_teleport_trust_v1_trust_service_proto_goTypes = []any{ - (*GetCertAuthorityRequest)(nil), // 0: teleport.trust.v1.GetCertAuthorityRequest - (*GetCertAuthoritiesRequest)(nil), // 1: teleport.trust.v1.GetCertAuthoritiesRequest - (*GetCertAuthoritiesResponse)(nil), // 2: teleport.trust.v1.GetCertAuthoritiesResponse - (*DeleteCertAuthorityRequest)(nil), // 3: teleport.trust.v1.DeleteCertAuthorityRequest - (*UpsertCertAuthorityRequest)(nil), // 4: teleport.trust.v1.UpsertCertAuthorityRequest - (*RotateCertAuthorityRequest)(nil), // 5: teleport.trust.v1.RotateCertAuthorityRequest - (*RotationSchedule)(nil), // 6: teleport.trust.v1.RotationSchedule - (*RotateCertAuthorityResponse)(nil), // 7: teleport.trust.v1.RotateCertAuthorityResponse - (*RotateExternalCertAuthorityRequest)(nil), // 8: teleport.trust.v1.RotateExternalCertAuthorityRequest - (*RotateExternalCertAuthorityResponse)(nil), // 9: teleport.trust.v1.RotateExternalCertAuthorityResponse - (*GenerateHostCertRequest)(nil), // 10: teleport.trust.v1.GenerateHostCertRequest - (*GenerateHostCertResponse)(nil), // 11: teleport.trust.v1.GenerateHostCertResponse - (*types.CertAuthorityV2)(nil), // 12: types.CertAuthorityV2 - (*durationpb.Duration)(nil), // 13: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 15: google.protobuf.Empty + (*UpsertTrustedClusterRequest)(nil), // 0: teleport.trust.v1.UpsertTrustedClusterRequest + (*CreateTrustedClusterRequest)(nil), // 1: teleport.trust.v1.CreateTrustedClusterRequest + (*UpdateTrustedClusterRequest)(nil), // 2: teleport.trust.v1.UpdateTrustedClusterRequest + (*GetCertAuthorityRequest)(nil), // 3: teleport.trust.v1.GetCertAuthorityRequest + (*GetCertAuthoritiesRequest)(nil), // 4: teleport.trust.v1.GetCertAuthoritiesRequest + (*GetCertAuthoritiesResponse)(nil), // 5: teleport.trust.v1.GetCertAuthoritiesResponse + (*DeleteCertAuthorityRequest)(nil), // 6: teleport.trust.v1.DeleteCertAuthorityRequest + (*UpsertCertAuthorityRequest)(nil), // 7: teleport.trust.v1.UpsertCertAuthorityRequest + (*RotateCertAuthorityRequest)(nil), // 8: teleport.trust.v1.RotateCertAuthorityRequest + (*RotationSchedule)(nil), // 9: teleport.trust.v1.RotationSchedule + (*RotateCertAuthorityResponse)(nil), // 10: teleport.trust.v1.RotateCertAuthorityResponse + (*RotateExternalCertAuthorityRequest)(nil), // 11: teleport.trust.v1.RotateExternalCertAuthorityRequest + (*RotateExternalCertAuthorityResponse)(nil), // 12: teleport.trust.v1.RotateExternalCertAuthorityResponse + (*GenerateHostCertRequest)(nil), // 13: teleport.trust.v1.GenerateHostCertRequest + (*GenerateHostCertResponse)(nil), // 14: teleport.trust.v1.GenerateHostCertResponse + (*types.TrustedClusterV2)(nil), // 15: types.TrustedClusterV2 + (*types.CertAuthorityV2)(nil), // 16: types.CertAuthorityV2 + (*durationpb.Duration)(nil), // 17: google.protobuf.Duration + (*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 19: google.protobuf.Empty } var file_teleport_trust_v1_trust_service_proto_depIdxs = []int32{ - 12, // 0: teleport.trust.v1.GetCertAuthoritiesResponse.cert_authorities_v2:type_name -> types.CertAuthorityV2 - 12, // 1: teleport.trust.v1.UpsertCertAuthorityRequest.cert_authority:type_name -> types.CertAuthorityV2 - 13, // 2: teleport.trust.v1.RotateCertAuthorityRequest.grace_period:type_name -> google.protobuf.Duration - 6, // 3: teleport.trust.v1.RotateCertAuthorityRequest.schedule:type_name -> teleport.trust.v1.RotationSchedule - 14, // 4: teleport.trust.v1.RotationSchedule.update_clients:type_name -> google.protobuf.Timestamp - 14, // 5: teleport.trust.v1.RotationSchedule.update_servers:type_name -> google.protobuf.Timestamp - 14, // 6: teleport.trust.v1.RotationSchedule.standby:type_name -> google.protobuf.Timestamp - 12, // 7: teleport.trust.v1.RotateExternalCertAuthorityRequest.cert_authority:type_name -> types.CertAuthorityV2 - 13, // 8: teleport.trust.v1.GenerateHostCertRequest.ttl:type_name -> google.protobuf.Duration - 0, // 9: teleport.trust.v1.TrustService.GetCertAuthority:input_type -> teleport.trust.v1.GetCertAuthorityRequest - 1, // 10: teleport.trust.v1.TrustService.GetCertAuthorities:input_type -> teleport.trust.v1.GetCertAuthoritiesRequest - 3, // 11: teleport.trust.v1.TrustService.DeleteCertAuthority:input_type -> teleport.trust.v1.DeleteCertAuthorityRequest - 4, // 12: teleport.trust.v1.TrustService.UpsertCertAuthority:input_type -> teleport.trust.v1.UpsertCertAuthorityRequest - 5, // 13: teleport.trust.v1.TrustService.RotateCertAuthority:input_type -> teleport.trust.v1.RotateCertAuthorityRequest - 8, // 14: teleport.trust.v1.TrustService.RotateExternalCertAuthority:input_type -> teleport.trust.v1.RotateExternalCertAuthorityRequest - 10, // 15: teleport.trust.v1.TrustService.GenerateHostCert:input_type -> teleport.trust.v1.GenerateHostCertRequest - 12, // 16: teleport.trust.v1.TrustService.GetCertAuthority:output_type -> types.CertAuthorityV2 - 2, // 17: teleport.trust.v1.TrustService.GetCertAuthorities:output_type -> teleport.trust.v1.GetCertAuthoritiesResponse - 15, // 18: teleport.trust.v1.TrustService.DeleteCertAuthority:output_type -> google.protobuf.Empty - 12, // 19: teleport.trust.v1.TrustService.UpsertCertAuthority:output_type -> types.CertAuthorityV2 - 7, // 20: teleport.trust.v1.TrustService.RotateCertAuthority:output_type -> teleport.trust.v1.RotateCertAuthorityResponse - 9, // 21: teleport.trust.v1.TrustService.RotateExternalCertAuthority:output_type -> teleport.trust.v1.RotateExternalCertAuthorityResponse - 11, // 22: teleport.trust.v1.TrustService.GenerateHostCert:output_type -> teleport.trust.v1.GenerateHostCertResponse - 16, // [16:23] is the sub-list for method output_type - 9, // [9:16] 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 + 15, // 0: teleport.trust.v1.UpsertTrustedClusterRequest.trusted_cluster:type_name -> types.TrustedClusterV2 + 15, // 1: teleport.trust.v1.CreateTrustedClusterRequest.trusted_cluster:type_name -> types.TrustedClusterV2 + 15, // 2: teleport.trust.v1.UpdateTrustedClusterRequest.trusted_cluster:type_name -> types.TrustedClusterV2 + 16, // 3: teleport.trust.v1.GetCertAuthoritiesResponse.cert_authorities_v2:type_name -> types.CertAuthorityV2 + 16, // 4: teleport.trust.v1.UpsertCertAuthorityRequest.cert_authority:type_name -> types.CertAuthorityV2 + 17, // 5: teleport.trust.v1.RotateCertAuthorityRequest.grace_period:type_name -> google.protobuf.Duration + 9, // 6: teleport.trust.v1.RotateCertAuthorityRequest.schedule:type_name -> teleport.trust.v1.RotationSchedule + 18, // 7: teleport.trust.v1.RotationSchedule.update_clients:type_name -> google.protobuf.Timestamp + 18, // 8: teleport.trust.v1.RotationSchedule.update_servers:type_name -> google.protobuf.Timestamp + 18, // 9: teleport.trust.v1.RotationSchedule.standby:type_name -> google.protobuf.Timestamp + 16, // 10: teleport.trust.v1.RotateExternalCertAuthorityRequest.cert_authority:type_name -> types.CertAuthorityV2 + 17, // 11: teleport.trust.v1.GenerateHostCertRequest.ttl:type_name -> google.protobuf.Duration + 3, // 12: teleport.trust.v1.TrustService.GetCertAuthority:input_type -> teleport.trust.v1.GetCertAuthorityRequest + 4, // 13: teleport.trust.v1.TrustService.GetCertAuthorities:input_type -> teleport.trust.v1.GetCertAuthoritiesRequest + 6, // 14: teleport.trust.v1.TrustService.DeleteCertAuthority:input_type -> teleport.trust.v1.DeleteCertAuthorityRequest + 7, // 15: teleport.trust.v1.TrustService.UpsertCertAuthority:input_type -> teleport.trust.v1.UpsertCertAuthorityRequest + 8, // 16: teleport.trust.v1.TrustService.RotateCertAuthority:input_type -> teleport.trust.v1.RotateCertAuthorityRequest + 11, // 17: teleport.trust.v1.TrustService.RotateExternalCertAuthority:input_type -> teleport.trust.v1.RotateExternalCertAuthorityRequest + 13, // 18: teleport.trust.v1.TrustService.GenerateHostCert:input_type -> teleport.trust.v1.GenerateHostCertRequest + 0, // 19: teleport.trust.v1.TrustService.UpsertTrustedCluster:input_type -> teleport.trust.v1.UpsertTrustedClusterRequest + 1, // 20: teleport.trust.v1.TrustService.CreateTrustedCluster:input_type -> teleport.trust.v1.CreateTrustedClusterRequest + 2, // 21: teleport.trust.v1.TrustService.UpdateTrustedCluster:input_type -> teleport.trust.v1.UpdateTrustedClusterRequest + 16, // 22: teleport.trust.v1.TrustService.GetCertAuthority:output_type -> types.CertAuthorityV2 + 5, // 23: teleport.trust.v1.TrustService.GetCertAuthorities:output_type -> teleport.trust.v1.GetCertAuthoritiesResponse + 19, // 24: teleport.trust.v1.TrustService.DeleteCertAuthority:output_type -> google.protobuf.Empty + 16, // 25: teleport.trust.v1.TrustService.UpsertCertAuthority:output_type -> types.CertAuthorityV2 + 10, // 26: teleport.trust.v1.TrustService.RotateCertAuthority:output_type -> teleport.trust.v1.RotateCertAuthorityResponse + 12, // 27: teleport.trust.v1.TrustService.RotateExternalCertAuthority:output_type -> teleport.trust.v1.RotateExternalCertAuthorityResponse + 14, // 28: teleport.trust.v1.TrustService.GenerateHostCert:output_type -> teleport.trust.v1.GenerateHostCertResponse + 15, // 29: teleport.trust.v1.TrustService.UpsertTrustedCluster:output_type -> types.TrustedClusterV2 + 15, // 30: teleport.trust.v1.TrustService.CreateTrustedCluster:output_type -> types.TrustedClusterV2 + 15, // 31: teleport.trust.v1.TrustService.UpdateTrustedCluster:output_type -> types.TrustedClusterV2 + 22, // [22:32] is the sub-list for method output_type + 12, // [12:22] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name } func init() { file_teleport_trust_v1_trust_service_proto_init() } @@ -956,7 +1143,7 @@ func file_teleport_trust_v1_trust_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_teleport_trust_v1_trust_service_proto_rawDesc, NumEnums: 0, - NumMessages: 12, + NumMessages: 15, NumExtensions: 0, NumServices: 1, }, diff --git a/api/gen/proto/go/teleport/trust/v1/trust_service_grpc.pb.go b/api/gen/proto/go/teleport/trust/v1/trust_service_grpc.pb.go index 5a91787708411..4cdc57fe369e1 100644 --- a/api/gen/proto/go/teleport/trust/v1/trust_service_grpc.pb.go +++ b/api/gen/proto/go/teleport/trust/v1/trust_service_grpc.pb.go @@ -42,6 +42,9 @@ const ( TrustService_RotateCertAuthority_FullMethodName = "/teleport.trust.v1.TrustService/RotateCertAuthority" TrustService_RotateExternalCertAuthority_FullMethodName = "/teleport.trust.v1.TrustService/RotateExternalCertAuthority" TrustService_GenerateHostCert_FullMethodName = "/teleport.trust.v1.TrustService/GenerateHostCert" + TrustService_UpsertTrustedCluster_FullMethodName = "/teleport.trust.v1.TrustService/UpsertTrustedCluster" + TrustService_CreateTrustedCluster_FullMethodName = "/teleport.trust.v1.TrustService/CreateTrustedCluster" + TrustService_UpdateTrustedCluster_FullMethodName = "/teleport.trust.v1.TrustService/UpdateTrustedCluster" ) // TrustServiceClient is the client API for TrustService service. @@ -65,6 +68,12 @@ type TrustServiceClient interface { // GenerateHostCert takes a public key in the OpenSSH `authorized_keys` format and returns // a SSH certificate signed by the Host CA. GenerateHostCert(ctx context.Context, in *GenerateHostCertRequest, opts ...grpc.CallOption) (*GenerateHostCertResponse, error) + // UpsertTrustedCluster upserts a Trusted Cluster in a backend. + UpsertTrustedCluster(ctx context.Context, in *UpsertTrustedClusterRequest, opts ...grpc.CallOption) (*types.TrustedClusterV2, error) + // CreateTrustedCluster creates a Trusted Cluster in a backend. + CreateTrustedCluster(ctx context.Context, in *CreateTrustedClusterRequest, opts ...grpc.CallOption) (*types.TrustedClusterV2, error) + // UpdateTrustedCluster updates a Trusted Cluster in a backend. + UpdateTrustedCluster(ctx context.Context, in *UpdateTrustedClusterRequest, opts ...grpc.CallOption) (*types.TrustedClusterV2, error) } type trustServiceClient struct { @@ -145,6 +154,36 @@ func (c *trustServiceClient) GenerateHostCert(ctx context.Context, in *GenerateH return out, nil } +func (c *trustServiceClient) UpsertTrustedCluster(ctx context.Context, in *UpsertTrustedClusterRequest, opts ...grpc.CallOption) (*types.TrustedClusterV2, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(types.TrustedClusterV2) + err := c.cc.Invoke(ctx, TrustService_UpsertTrustedCluster_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *trustServiceClient) CreateTrustedCluster(ctx context.Context, in *CreateTrustedClusterRequest, opts ...grpc.CallOption) (*types.TrustedClusterV2, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(types.TrustedClusterV2) + err := c.cc.Invoke(ctx, TrustService_CreateTrustedCluster_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *trustServiceClient) UpdateTrustedCluster(ctx context.Context, in *UpdateTrustedClusterRequest, opts ...grpc.CallOption) (*types.TrustedClusterV2, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(types.TrustedClusterV2) + err := c.cc.Invoke(ctx, TrustService_UpdateTrustedCluster_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // TrustServiceServer is the server API for TrustService service. // All implementations must embed UnimplementedTrustServiceServer // for forward compatibility. @@ -166,6 +205,12 @@ type TrustServiceServer interface { // GenerateHostCert takes a public key in the OpenSSH `authorized_keys` format and returns // a SSH certificate signed by the Host CA. GenerateHostCert(context.Context, *GenerateHostCertRequest) (*GenerateHostCertResponse, error) + // UpsertTrustedCluster upserts a Trusted Cluster in a backend. + UpsertTrustedCluster(context.Context, *UpsertTrustedClusterRequest) (*types.TrustedClusterV2, error) + // CreateTrustedCluster creates a Trusted Cluster in a backend. + CreateTrustedCluster(context.Context, *CreateTrustedClusterRequest) (*types.TrustedClusterV2, error) + // UpdateTrustedCluster updates a Trusted Cluster in a backend. + UpdateTrustedCluster(context.Context, *UpdateTrustedClusterRequest) (*types.TrustedClusterV2, error) mustEmbedUnimplementedTrustServiceServer() } @@ -197,6 +242,15 @@ func (UnimplementedTrustServiceServer) RotateExternalCertAuthority(context.Conte func (UnimplementedTrustServiceServer) GenerateHostCert(context.Context, *GenerateHostCertRequest) (*GenerateHostCertResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GenerateHostCert not implemented") } +func (UnimplementedTrustServiceServer) UpsertTrustedCluster(context.Context, *UpsertTrustedClusterRequest) (*types.TrustedClusterV2, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpsertTrustedCluster not implemented") +} +func (UnimplementedTrustServiceServer) CreateTrustedCluster(context.Context, *CreateTrustedClusterRequest) (*types.TrustedClusterV2, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateTrustedCluster not implemented") +} +func (UnimplementedTrustServiceServer) UpdateTrustedCluster(context.Context, *UpdateTrustedClusterRequest) (*types.TrustedClusterV2, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateTrustedCluster not implemented") +} func (UnimplementedTrustServiceServer) mustEmbedUnimplementedTrustServiceServer() {} func (UnimplementedTrustServiceServer) testEmbeddedByValue() {} @@ -344,6 +398,60 @@ func _TrustService_GenerateHostCert_Handler(srv interface{}, ctx context.Context return interceptor(ctx, in, info, handler) } +func _TrustService_UpsertTrustedCluster_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpsertTrustedClusterRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TrustServiceServer).UpsertTrustedCluster(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TrustService_UpsertTrustedCluster_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TrustServiceServer).UpsertTrustedCluster(ctx, req.(*UpsertTrustedClusterRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TrustService_CreateTrustedCluster_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateTrustedClusterRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TrustServiceServer).CreateTrustedCluster(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TrustService_CreateTrustedCluster_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TrustServiceServer).CreateTrustedCluster(ctx, req.(*CreateTrustedClusterRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TrustService_UpdateTrustedCluster_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateTrustedClusterRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TrustServiceServer).UpdateTrustedCluster(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TrustService_UpdateTrustedCluster_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TrustServiceServer).UpdateTrustedCluster(ctx, req.(*UpdateTrustedClusterRequest)) + } + return interceptor(ctx, in, info, handler) +} + // TrustService_ServiceDesc is the grpc.ServiceDesc for TrustService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -379,6 +487,18 @@ var TrustService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GenerateHostCert", Handler: _TrustService_GenerateHostCert_Handler, }, + { + MethodName: "UpsertTrustedCluster", + Handler: _TrustService_UpsertTrustedCluster_Handler, + }, + { + MethodName: "CreateTrustedCluster", + Handler: _TrustService_CreateTrustedCluster_Handler, + }, + { + MethodName: "UpdateTrustedCluster", + Handler: _TrustService_UpdateTrustedCluster_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "teleport/trust/v1/trust_service.proto", diff --git a/api/go.mod b/api/go.mod index 57f739e6ad14b..e01ed6bbbfcd6 100644 --- a/api/go.mod +++ b/api/go.mod @@ -24,7 +24,7 @@ require ( go.opentelemetry.io/proto/otlp v1.4.0 golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f - golang.org/x/net v0.31.0 + golang.org/x/net v0.33.0 golang.org/x/term v0.27.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 google.golang.org/grpc v1.68.0 diff --git a/api/go.sum b/api/go.sum index da681baa3b07d..4c35d634358ec 100644 --- a/api/go.sum +++ b/api/go.sum @@ -1058,8 +1058,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/api/proto/teleport/autoupdate/v1/autoupdate.proto b/api/proto/teleport/autoupdate/v1/autoupdate.proto index 0257ec3e023b7..faf7ec93db129 100644 --- a/api/proto/teleport/autoupdate/v1/autoupdate.proto +++ b/api/proto/teleport/autoupdate/v1/autoupdate.proto @@ -180,6 +180,7 @@ message AutoUpdateAgentRolloutStatus { // For example, a group updates every day between 13:00 and 14:00. If the target version changes to 13:30, the group // will not start updating to the new version directly. The controller sees that the group theoretical start time is // before the rollout start time and the maintenance window belongs to the previous rollout. + // When the timestamp is nil, the controller will ignore the start time and check and allow groups to activate. google.protobuf.Timestamp start_time = 3; } diff --git a/api/proto/teleport/legacy/client/proto/authservice.proto b/api/proto/teleport/legacy/client/proto/authservice.proto index 79eb5fc2b56d7..fc6dc146ff248 100644 --- a/api/proto/teleport/legacy/client/proto/authservice.proto +++ b/api/proto/teleport/legacy/client/proto/authservice.proto @@ -2145,6 +2145,36 @@ message ListResourcesRequest { bool IncludeLogins = 13 [(gogoproto.jsontag) = "include_logins,omitempty"]; } +// ResolveSSHTargetRequest provides details about a server to be resolved in +// an equivalent manner to a ssh dial request. +// +// Resolution can happen in two modes: +// 1) searching for hosts based on labels, a predicate expression, or keywords +// 2) searching based on hostname +// +// If a Host is provided, resolution will only operate in the second mode and +// will not perform any resolution based on labels. In order to resolve via +// labels the Host must not be populated. +message ResolveSSHTargetRequest { + // The target host as would be sent to the proxy during a dial request. + string host = 1; + // The ssh port. This value is optional, and both empty string and "0" are typically + // treated as meaning that any port should match. + string port = 2; + // If not empty, a label-based matcher. + map labels = 3; + // Boolean conditions that will be matched against the resource. + string predicate_expression = 4; + // A list of search keywords to match against resource field values. + repeated string search_keywords = 5; +} + +// GetSSHTargetsResponse holds ssh servers that match an ssh targets request. +message ResolveSSHTargetResponse { + // The target matching the supplied request. + types.ServerV2 server = 1; +} + // GetSSHTargetsRequest gets all servers that might match an equivalent ssh dial request. message GetSSHTargetsRequest { // Host is the target host as would be sent to the proxy during a dial request. @@ -3278,7 +3308,11 @@ service AuthService { // GetTrustedClusters gets all current Trusted Cluster resources. rpc GetTrustedClusters(google.protobuf.Empty) returns (types.TrustedClusterV2List); // UpsertTrustedCluster upserts a Trusted Cluster in a backend. - rpc UpsertTrustedCluster(types.TrustedClusterV2) returns (types.TrustedClusterV2); + // + // Deprecated: Use [teleport.trust.v1.UpsertTrustedCluster] instead. + rpc UpsertTrustedCluster(types.TrustedClusterV2) returns (types.TrustedClusterV2) { + option deprecated = true; + } // DeleteTrustedCluster deletes an existing Trusted Cluster in a backend by name. rpc DeleteTrustedCluster(types.ResourceRequest) returns (google.protobuf.Empty); @@ -3553,6 +3587,9 @@ service AuthService { // but may result in confusing behavior if it is used outside of those contexts. rpc GetSSHTargets(GetSSHTargetsRequest) returns (GetSSHTargetsResponse); + // ResolveSSHTarget returns the server that would be resolved in an equivalent ssh dial request. + rpc ResolveSSHTarget(ResolveSSHTargetRequest) returns (ResolveSSHTargetResponse); + // GetDomainName returns local auth domain of the current auth server rpc GetDomainName(google.protobuf.Empty) returns (GetDomainNameResponse); // GetClusterCACert returns the PEM-encoded TLS certs for the local cluster diff --git a/api/proto/teleport/legacy/types/types.proto b/api/proto/teleport/legacy/types/types.proto index f89b6aca16cf7..3d4081073e01e 100644 --- a/api/proto/teleport/legacy/types/types.proto +++ b/api/proto/teleport/legacy/types/types.proto @@ -6470,6 +6470,8 @@ message PluginSpecV1 { PluginEmailSettings email = 17; // Settings for the Microsoft Teams plugin PluginMSTeamsSettings msteams = 18; + // Settings for the OpenTex NetIQ plugin. + PluginNetIQSettings net_iq = 19; } // generation contains a unique ID that should: @@ -6896,6 +6898,18 @@ message PluginMSTeamsSettings { string default_recipient = 5; } +// PluginNetIQSettings defines the settings for a NetIQ integration plugin +message PluginNetIQSettings { + option (gogoproto.equal) = true; + // oauth_issuer_endpoint is the NetIQ Oauth Issuer endpoint. + // Usually, it's equal to https://osp.domain.ext/a/idm/auth/oauth2 + string oauth_issuer_endpoint = 1; + // api_endpoint is the IDM PROV Rest API location. + string api_endpoint = 2; + // insecure_skip_verify controls whether the NetIQ certificate validation should be skipped. + bool insecure_skip_verify = 3; +} + message PluginBootstrapCredentialsV1 { oneof credentials { PluginOAuth2AuthorizationCodeCredentials oauth2_authorization_code = 1; @@ -6934,6 +6948,8 @@ message PluginStatusV1 { PluginOktaStatusV1 okta = 7; // AWSIC holds status details for the AWS Identity Center plugin. PluginAWSICStatusV1 aws_ic = 8; + // NetIQ holds status details for the NetIQ plugin. + PluginNetIQStatusV1 net_iq = 9; } // last_raw_error variable stores the most recent raw error message received from an API or service. @@ -6943,6 +6959,18 @@ message PluginStatusV1 { string last_raw_error = 6; } +// PluginNetIQStatusV1 is the status details for the NetIQ plugin. +message PluginNetIQStatusV1 { + // imported_users is the number of users imported from NetIQ eDirectory. + uint32 imported_users = 1; + // imported_groups is the number of groups imported from NetIQ eDirectory. + uint32 imported_groups = 2; + // imported_roles is the number of roles imported from NetIQ eDirectory. + uint32 imported_roles = 3; + // imported_resources is the number of resources imported from NetIQ eDirectory. + uint32 imported_resources = 4; +} + // PluginGitlabStatusV1 is the status details for the Gitlab plugin. message PluginGitlabStatusV1 { // imported_users is the number of users imported from Gitlab. @@ -8026,12 +8054,14 @@ message OktaOptions { message AccessGraphSync { // AWS is a configuration for AWS Access Graph service poll service. repeated AccessGraphAWSSync AWS = 1 [(gogoproto.jsontag) = "aws,omitempty"]; - // PollInterval is the frequency at which to poll for AWS resources + // PollInterval is the frequency at which to poll for resources google.protobuf.Duration PollInterval = 2 [ (gogoproto.jsontag) = "poll_interval,omitempty", (gogoproto.nullable) = false, (gogoproto.stdduration) = true ]; + // Azure is a configuration for Azure Access Graph service poll service. + repeated AccessGraphAzureSync Azure = 3 [(gogoproto.jsontag) = "azure,omitempty"]; } // AccessGraphAWSSync is a configuration for AWS Access Graph service poll service. @@ -8043,3 +8073,11 @@ message AccessGraphAWSSync { // Integration is the integration name used to generate credentials to interact with AWS APIs. string Integration = 4 [(gogoproto.jsontag) = "integration,omitempty"]; } + +// AccessGraphAzureSync is a configuration for Azure Access Graph service poll service. +message AccessGraphAzureSync { + // SubscriptionID Is the ID of the Azure subscription to sync resources from + string SubscriptionID = 1 [(gogoproto.jsontag) = "subscription_id,omitempty"]; + // Integration is the integration name used to generate credentials to interact with AWS APIs. + string Integration = 2 [(gogoproto.jsontag) = "integration,omitempty"]; +} diff --git a/api/proto/teleport/trust/v1/trust_service.proto b/api/proto/teleport/trust/v1/trust_service.proto index a6b3fc3282c44..7d8748f2375f7 100644 --- a/api/proto/teleport/trust/v1/trust_service.proto +++ b/api/proto/teleport/trust/v1/trust_service.proto @@ -40,6 +40,31 @@ service TrustService { // GenerateHostCert takes a public key in the OpenSSH `authorized_keys` format and returns // a SSH certificate signed by the Host CA. rpc GenerateHostCert(GenerateHostCertRequest) returns (GenerateHostCertResponse); + + // UpsertTrustedCluster upserts a Trusted Cluster in a backend. + rpc UpsertTrustedCluster(UpsertTrustedClusterRequest) returns (types.TrustedClusterV2); + // CreateTrustedCluster creates a Trusted Cluster in a backend. + rpc CreateTrustedCluster(CreateTrustedClusterRequest) returns (types.TrustedClusterV2); + // UpdateTrustedCluster updates a Trusted Cluster in a backend. + rpc UpdateTrustedCluster(UpdateTrustedClusterRequest) returns (types.TrustedClusterV2); +} + +// Request for UpsertTrustedCluster. +message UpsertTrustedClusterRequest { + // TrustedCluster specifies a Trusted Cluster resource. + types.TrustedClusterV2 trusted_cluster = 1; +} + +// Request for CreateTrustedCluster. +message CreateTrustedClusterRequest { + // TrustedCluster specifies a Trusted Cluster resource. + types.TrustedClusterV2 trusted_cluster = 1; +} + +// Request for UpdateTrustedCluster. +message UpdateTrustedClusterRequest { + // TrustedCluster specifies a Trusted Cluster resource. + types.TrustedClusterV2 trusted_cluster = 1; } // Request for GetCertAuthority diff --git a/api/types/discoveryconfig/derived.gen.go b/api/types/discoveryconfig/derived.gen.go index 9053fdd312473..c1f713517c7ca 100644 --- a/api/types/discoveryconfig/derived.gen.go +++ b/api/types/discoveryconfig/derived.gen.go @@ -117,7 +117,8 @@ func deriveTeleportEqual_6(this, that *types.AccessGraphSync) bool { return (this == nil && that == nil) || this != nil && that != nil && deriveTeleportEqual_12(this.AWS, that.AWS) && - this.PollInterval == that.PollInterval + this.PollInterval == that.PollInterval && + deriveTeleportEqual_13(this.Azure, that.Azure) } // deriveTeleportEqual_7 returns whether this and that are equal. @@ -144,12 +145,12 @@ func deriveTeleportEqual_7(this, that map[string]string) bool { func deriveTeleportEqual_8(this, that *types.AWSMatcher) bool { return (this == nil && that == nil) || this != nil && that != nil && - deriveTeleportEqual_13(this.Types, that.Types) && - deriveTeleportEqual_13(this.Regions, that.Regions) && - deriveTeleportEqual_14(this.AssumeRole, that.AssumeRole) && - deriveTeleportEqual_15(this.Tags, that.Tags) && - deriveTeleportEqual_16(this.Params, that.Params) && - deriveTeleportEqual_17(this.SSM, that.SSM) && + deriveTeleportEqual_14(this.Types, that.Types) && + deriveTeleportEqual_14(this.Regions, that.Regions) && + deriveTeleportEqual_15(this.AssumeRole, that.AssumeRole) && + deriveTeleportEqual_16(this.Tags, that.Tags) && + deriveTeleportEqual_17(this.Params, that.Params) && + deriveTeleportEqual_18(this.SSM, that.SSM) && this.Integration == that.Integration && this.KubeAppDiscovery == that.KubeAppDiscovery && this.SetupAccessForARN == that.SetupAccessForARN @@ -159,34 +160,34 @@ func deriveTeleportEqual_8(this, that *types.AWSMatcher) bool { func deriveTeleportEqual_9(this, that *types.AzureMatcher) bool { return (this == nil && that == nil) || this != nil && that != nil && - deriveTeleportEqual_13(this.Subscriptions, that.Subscriptions) && - deriveTeleportEqual_13(this.ResourceGroups, that.ResourceGroups) && - deriveTeleportEqual_13(this.Types, that.Types) && - deriveTeleportEqual_13(this.Regions, that.Regions) && - deriveTeleportEqual_15(this.ResourceTags, that.ResourceTags) && - deriveTeleportEqual_16(this.Params, that.Params) + deriveTeleportEqual_14(this.Subscriptions, that.Subscriptions) && + deriveTeleportEqual_14(this.ResourceGroups, that.ResourceGroups) && + deriveTeleportEqual_14(this.Types, that.Types) && + deriveTeleportEqual_14(this.Regions, that.Regions) && + deriveTeleportEqual_16(this.ResourceTags, that.ResourceTags) && + deriveTeleportEqual_17(this.Params, that.Params) } // deriveTeleportEqual_10 returns whether this and that are equal. func deriveTeleportEqual_10(this, that *types.GCPMatcher) bool { return (this == nil && that == nil) || this != nil && that != nil && - deriveTeleportEqual_13(this.Types, that.Types) && - deriveTeleportEqual_13(this.Locations, that.Locations) && - deriveTeleportEqual_15(this.Tags, that.Tags) && - deriveTeleportEqual_13(this.ProjectIDs, that.ProjectIDs) && - deriveTeleportEqual_13(this.ServiceAccounts, that.ServiceAccounts) && - deriveTeleportEqual_16(this.Params, that.Params) && - deriveTeleportEqual_15(this.Labels, that.Labels) + deriveTeleportEqual_14(this.Types, that.Types) && + deriveTeleportEqual_14(this.Locations, that.Locations) && + deriveTeleportEqual_16(this.Tags, that.Tags) && + deriveTeleportEqual_14(this.ProjectIDs, that.ProjectIDs) && + deriveTeleportEqual_14(this.ServiceAccounts, that.ServiceAccounts) && + deriveTeleportEqual_17(this.Params, that.Params) && + deriveTeleportEqual_16(this.Labels, that.Labels) } // deriveTeleportEqual_11 returns whether this and that are equal. func deriveTeleportEqual_11(this, that *types.KubernetesMatcher) bool { return (this == nil && that == nil) || this != nil && that != nil && - deriveTeleportEqual_13(this.Types, that.Types) && - deriveTeleportEqual_13(this.Namespaces, that.Namespaces) && - deriveTeleportEqual_15(this.Labels, that.Labels) + deriveTeleportEqual_14(this.Types, that.Types) && + deriveTeleportEqual_14(this.Namespaces, that.Namespaces) && + deriveTeleportEqual_16(this.Labels, that.Labels) } // deriveTeleportEqual_12 returns whether this and that are equal. @@ -198,7 +199,7 @@ func deriveTeleportEqual_12(this, that []*types.AccessGraphAWSSync) bool { return false } for i := 0; i < len(this); i++ { - if !(deriveTeleportEqual_18(this[i], that[i])) { + if !(deriveTeleportEqual_19(this[i], that[i])) { return false } } @@ -206,7 +207,7 @@ func deriveTeleportEqual_12(this, that []*types.AccessGraphAWSSync) bool { } // deriveTeleportEqual_13 returns whether this and that are equal. -func deriveTeleportEqual_13(this, that []string) bool { +func deriveTeleportEqual_13(this, that []*types.AccessGraphAzureSync) bool { if this == nil || that == nil { return this == nil && that == nil } @@ -214,7 +215,7 @@ func deriveTeleportEqual_13(this, that []string) bool { return false } for i := 0; i < len(this); i++ { - if !(this[i] == that[i]) { + if !(deriveTeleportEqual_20(this[i], that[i])) { return false } } @@ -222,15 +223,31 @@ func deriveTeleportEqual_13(this, that []string) bool { } // deriveTeleportEqual_14 returns whether this and that are equal. -func deriveTeleportEqual_14(this, that *types.AssumeRole) bool { +func deriveTeleportEqual_14(this, that []string) bool { + if this == nil || that == nil { + return this == nil && that == nil + } + if len(this) != len(that) { + return false + } + for i := 0; i < len(this); i++ { + if !(this[i] == that[i]) { + return false + } + } + return true +} + +// deriveTeleportEqual_15 returns whether this and that are equal. +func deriveTeleportEqual_15(this, that *types.AssumeRole) bool { return (this == nil && that == nil) || this != nil && that != nil && this.RoleARN == that.RoleARN && this.ExternalID == that.ExternalID } -// deriveTeleportEqual_15 returns whether this and that are equal. -func deriveTeleportEqual_15(this, that map[string]utils.Strings) bool { +// deriveTeleportEqual_16 returns whether this and that are equal. +func deriveTeleportEqual_16(this, that map[string]utils.Strings) bool { if this == nil || that == nil { return this == nil && that == nil } @@ -242,15 +259,15 @@ func deriveTeleportEqual_15(this, that map[string]utils.Strings) bool { if !ok { return false } - if !(deriveTeleportEqual_13(v, thatv)) { + if !(deriveTeleportEqual_14(v, thatv)) { return false } } return true } -// deriveTeleportEqual_16 returns whether this and that are equal. -func deriveTeleportEqual_16(this, that *types.InstallerParams) bool { +// deriveTeleportEqual_17 returns whether this and that are equal. +func deriveTeleportEqual_17(this, that *types.InstallerParams) bool { return (this == nil && that == nil) || this != nil && that != nil && this.JoinMethod == that.JoinMethod && @@ -259,28 +276,36 @@ func deriveTeleportEqual_16(this, that *types.InstallerParams) bool { this.InstallTeleport == that.InstallTeleport && this.SSHDConfig == that.SSHDConfig && this.PublicProxyAddr == that.PublicProxyAddr && - deriveTeleportEqual_19(this.Azure, that.Azure) && + deriveTeleportEqual_21(this.Azure, that.Azure) && this.EnrollMode == that.EnrollMode } -// deriveTeleportEqual_17 returns whether this and that are equal. -func deriveTeleportEqual_17(this, that *types.AWSSSM) bool { +// deriveTeleportEqual_18 returns whether this and that are equal. +func deriveTeleportEqual_18(this, that *types.AWSSSM) bool { return (this == nil && that == nil) || this != nil && that != nil && this.DocumentName == that.DocumentName } -// deriveTeleportEqual_18 returns whether this and that are equal. -func deriveTeleportEqual_18(this, that *types.AccessGraphAWSSync) bool { +// deriveTeleportEqual_19 returns whether this and that are equal. +func deriveTeleportEqual_19(this, that *types.AccessGraphAWSSync) bool { return (this == nil && that == nil) || this != nil && that != nil && - deriveTeleportEqual_13(this.Regions, that.Regions) && - deriveTeleportEqual_14(this.AssumeRole, that.AssumeRole) && + deriveTeleportEqual_14(this.Regions, that.Regions) && + deriveTeleportEqual_15(this.AssumeRole, that.AssumeRole) && this.Integration == that.Integration } -// deriveTeleportEqual_19 returns whether this and that are equal. -func deriveTeleportEqual_19(this, that *types.AzureInstallerParams) bool { +// deriveTeleportEqual_20 returns whether this and that are equal. +func deriveTeleportEqual_20(this, that *types.AccessGraphAzureSync) bool { + return (this == nil && that == nil) || + this != nil && that != nil && + this.SubscriptionID == that.SubscriptionID && + this.Integration == that.Integration +} + +// deriveTeleportEqual_21 returns whether this and that are equal. +func deriveTeleportEqual_21(this, that *types.AzureInstallerParams) bool { return (this == nil && that == nil) || this != nil && that != nil && this.ClientID == that.ClientID diff --git a/api/types/plugin.go b/api/types/plugin.go index fc69231cc6622..f6c0b96ec1aa3 100644 --- a/api/types/plugin.go +++ b/api/types/plugin.go @@ -17,6 +17,7 @@ limitations under the License. package types import ( + "net/url" "time" "github.com/gravitational/trace" @@ -83,6 +84,8 @@ const ( PluginTypeEmail = "email" // PluginTypeMSTeams indicates a Microsoft Teams integration PluginTypeMSTeams = "msteams" + // PluginTypeNetIQ indicates a NetIQ integration + PluginTypeNetIQ = "netiq" ) // PluginSubkind represents the type of the plugin, e.g., access request, MDM etc. @@ -377,6 +380,20 @@ func (p *PluginV1) CheckAndSetDefaults() error { if len(staticCreds.Labels) == 0 { return trace.BadParameter("labels must be specified") } + case *PluginSpecV1_NetIq: + if settings.NetIq == nil { + return trace.BadParameter("missing NetIQ settings") + } + if err := settings.NetIq.Validate(); err != nil { + return trace.Wrap(err) + } + staticCreds := p.Credentials.GetStaticCredentialsRef() + if staticCreds == nil { + return trace.BadParameter("NetIQ plugin must be used with the static credentials ref type") + } + if len(staticCreds.Labels) == 0 { + return trace.BadParameter("labels must be specified") + } default: return nil } @@ -837,3 +854,22 @@ func (c *PluginGitlabSettings) Validate() error { return nil } + +func (c *PluginNetIQSettings) Validate() error { + if c.OauthIssuerEndpoint == "" { + return trace.BadParameter("oauth_issuer endpoint must be set") + } + + if _, err := url.Parse(c.OauthIssuerEndpoint); err != nil { + return trace.BadParameter("oauth_issuer endpoint must be a valid URL") + } + + if c.ApiEndpoint == "" { + return trace.BadParameter("api_endpoint must be set") + } + if _, err := url.Parse(c.ApiEndpoint); err != nil { + return trace.BadParameter("api_endpoint endpoint must be a valid URL") + } + + return nil +} diff --git a/api/types/plugin_test.go b/api/types/plugin_test.go index 87d3d6182675b..db33043e3ea7a 100644 --- a/api/types/plugin_test.go +++ b/api/types/plugin_test.go @@ -1336,3 +1336,78 @@ func TestPluginEmailSettings(t *testing.T) { }) } } + +func TestNetIQPluginSettings(t *testing.T) { + + validSettings := func() *PluginSpecV1_NetIq { + return &PluginSpecV1_NetIq{ + NetIq: &PluginNetIQSettings{ + OauthIssuerEndpoint: "https://example.com", + ApiEndpoint: "https://example.com", + }, + } + } + testCases := []struct { + name string + mutateSettings func(*PluginSpecV1_NetIq) + assertErr require.ErrorAssertionFunc + }{ + { + name: "valid", + mutateSettings: nil, + assertErr: require.NoError, + }, + { + name: "missing OauthIssuer", + mutateSettings: func(s *PluginSpecV1_NetIq) { + s.NetIq.OauthIssuerEndpoint = "" + }, + assertErr: requireNamedBadParameterError("oauth_issuer"), + }, + { + name: "missing ApiEndpoint", + mutateSettings: func(s *PluginSpecV1_NetIq) { + s.NetIq.ApiEndpoint = "" + }, + assertErr: requireNamedBadParameterError("api_endpoint"), + }, + { + name: "incorrect OauthIssuer", + mutateSettings: func(s *PluginSpecV1_NetIq) { + s.NetIq.OauthIssuerEndpoint = "invalidURL%%#" + }, + assertErr: requireNamedBadParameterError("oauth_issuer"), + }, + { + name: "missing ApiEndpoint", + mutateSettings: func(s *PluginSpecV1_NetIq) { + s.NetIq.ApiEndpoint = "invalidURL%%#" + }, + assertErr: requireNamedBadParameterError("api_endpoint"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + settings := validSettings() + if tc.mutateSettings != nil { + tc.mutateSettings(settings) + } + + plugin := NewPluginV1( + Metadata{Name: "uut"}, + PluginSpecV1{Settings: settings}, + &PluginCredentialsV1{ + Credentials: &PluginCredentialsV1_StaticCredentialsRef{ + StaticCredentialsRef: &PluginStaticCredentialsRef{ + Labels: map[string]string{ + "label1": "value1", + }, + }, + }, + }) + tc.assertErr(t, plugin.CheckAndSetDefaults()) + }) + } + +} diff --git a/api/types/trustedcluster.go b/api/types/trustedcluster.go index 27d8129f70cfe..17d397525a7df 100644 --- a/api/types/trustedcluster.go +++ b/api/types/trustedcluster.go @@ -29,8 +29,8 @@ import ( // TrustedCluster holds information needed for a cluster that can not be directly // accessed (maybe be behind firewall without any open ports) to join a parent cluster. type TrustedCluster interface { - // Resource provides common resource properties - Resource + // ResourceWithOrigin provides common resource properties + ResourceWithOrigin // SetMetadata sets object metadata SetMetadata(meta Metadata) // GetEnabled returns the state of the TrustedCluster. @@ -184,6 +184,16 @@ func (c *TrustedClusterV2) SetName(e string) { c.Metadata.Name = e } +// Origin returns the origin value of the resource. +func (c *TrustedClusterV2) Origin() string { + return c.Metadata.Origin() +} + +// SetOrigin sets the origin value of the resource. +func (c *TrustedClusterV2) SetOrigin(origin string) { + c.Metadata.SetOrigin(origin) +} + // GetEnabled returns the state of the TrustedCluster. func (c *TrustedClusterV2) GetEnabled() bool { return c.Spec.Enabled @@ -252,14 +262,6 @@ func (c *TrustedClusterV2) CanChangeStateTo(t TrustedCluster) error { if !slices.Equal(c.GetRoles(), t.GetRoles()) { return immutableFieldErr("roles") } - - if c.GetEnabled() == t.GetEnabled() && c.GetRoleMap().IsEqual(t.GetRoleMap()) { - if t.GetEnabled() { - return trace.AlreadyExists("leaf cluster is already enabled, this update would have no effect") - } - return trace.AlreadyExists("leaf cluster is already disabled, this update would have no effect") - } - return nil } diff --git a/api/types/types.pb.go b/api/types/types.pb.go index bede358a5c6e9..10387e25bd555 100644 --- a/api/types/types.pb.go +++ b/api/types/types.pb.go @@ -1226,7 +1226,7 @@ func (x OktaAssignmentSpecV1_OktaAssignmentStatus) String() string { } func (OktaAssignmentSpecV1_OktaAssignmentStatus) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{345, 0} + return fileDescriptor_9198ee693835762e, []int{347, 0} } // OktaAssignmentTargetType is the type of Okta object that an assignment is targeting. @@ -1258,7 +1258,7 @@ func (x OktaAssignmentTargetV1_OktaAssignmentTargetType) String() string { } func (OktaAssignmentTargetV1_OktaAssignmentTargetType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{346, 0} + return fileDescriptor_9198ee693835762e, []int{348, 0} } type KeepAlive struct { @@ -16222,6 +16222,7 @@ type PluginSpecV1 struct { // *PluginSpecV1_AwsIc // *PluginSpecV1_Email // *PluginSpecV1_Msteams + // *PluginSpecV1_NetIq Settings isPluginSpecV1_Settings `protobuf_oneof:"settings"` // generation contains a unique ID that should: // - Be created by the backend on plugin creation. @@ -16325,6 +16326,9 @@ type PluginSpecV1_Email struct { type PluginSpecV1_Msteams struct { Msteams *PluginMSTeamsSettings `protobuf:"bytes,18,opt,name=msteams,proto3,oneof" json:"msteams,omitempty"` } +type PluginSpecV1_NetIq struct { + NetIq *PluginNetIQSettings `protobuf:"bytes,19,opt,name=net_iq,json=netIq,proto3,oneof" json:"net_iq,omitempty"` +} func (*PluginSpecV1_SlackAccessPlugin) isPluginSpecV1_Settings() {} func (*PluginSpecV1_Opsgenie) isPluginSpecV1_Settings() {} @@ -16343,6 +16347,7 @@ func (*PluginSpecV1_Datadog) isPluginSpecV1_Settings() {} func (*PluginSpecV1_AwsIc) isPluginSpecV1_Settings() {} func (*PluginSpecV1_Email) isPluginSpecV1_Settings() {} func (*PluginSpecV1_Msteams) isPluginSpecV1_Settings() {} +func (*PluginSpecV1_NetIq) isPluginSpecV1_Settings() {} func (m *PluginSpecV1) GetSettings() isPluginSpecV1_Settings { if m != nil { @@ -16470,6 +16475,13 @@ func (m *PluginSpecV1) GetMsteams() *PluginMSTeamsSettings { return nil } +func (m *PluginSpecV1) GetNetIq() *PluginNetIQSettings { + if x, ok := m.GetSettings().(*PluginSpecV1_NetIq); ok { + return x.NetIq + } + return nil +} + // XXX_OneofWrappers is for the internal use of the proto package. func (*PluginSpecV1) XXX_OneofWrappers() []interface{} { return []interface{}{ @@ -16490,6 +16502,7 @@ func (*PluginSpecV1) XXX_OneofWrappers() []interface{} { (*PluginSpecV1_AwsIc)(nil), (*PluginSpecV1_Email)(nil), (*PluginSpecV1_Msteams)(nil), + (*PluginSpecV1_NetIq)(nil), } } @@ -17867,6 +17880,53 @@ func (m *PluginMSTeamsSettings) XXX_DiscardUnknown() { var xxx_messageInfo_PluginMSTeamsSettings proto.InternalMessageInfo +// PluginNetIQSettings defines the settings for a NetIQ integration plugin +type PluginNetIQSettings struct { + // oauth_issuer_endpoint is the NetIQ Oauth Issuer endpoint. + // Usually, it's equal to https://osp.domain.ext/a/idm/auth/oauth2 + OauthIssuerEndpoint string `protobuf:"bytes,1,opt,name=oauth_issuer_endpoint,json=oauthIssuerEndpoint,proto3" json:"oauth_issuer_endpoint,omitempty"` + // api_endpoint is the IDM PROV Rest API location. + ApiEndpoint string `protobuf:"bytes,2,opt,name=api_endpoint,json=apiEndpoint,proto3" json:"api_endpoint,omitempty"` + // insecure_skip_verify controls whether the NetIQ certificate validation should be skipped. + InsecureSkipVerify bool `protobuf:"varint,3,opt,name=insecure_skip_verify,json=insecureSkipVerify,proto3" json:"insecure_skip_verify,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +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{304} +} +func (m *PluginNetIQSettings) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PluginNetIQSettings) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PluginNetIQSettings.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 *PluginNetIQSettings) XXX_Merge(src proto.Message) { + xxx_messageInfo_PluginNetIQSettings.Merge(m, src) +} +func (m *PluginNetIQSettings) XXX_Size() int { + return m.Size() +} +func (m *PluginNetIQSettings) XXX_DiscardUnknown() { + xxx_messageInfo_PluginNetIQSettings.DiscardUnknown(m) +} + +var xxx_messageInfo_PluginNetIQSettings proto.InternalMessageInfo + type PluginBootstrapCredentialsV1 struct { // Types that are valid to be assigned to Credentials: // @@ -17883,7 +17943,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{304} + return fileDescriptor_9198ee693835762e, []int{305} } func (m *PluginBootstrapCredentialsV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -17983,7 +18043,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{305} + return fileDescriptor_9198ee693835762e, []int{306} } func (m *PluginIdSecretCredential) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18026,7 +18086,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{306} + return fileDescriptor_9198ee693835762e, []int{307} } func (m *PluginOAuth2AuthorizationCodeCredentials) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18070,6 +18130,7 @@ type PluginStatusV1 struct { // *PluginStatusV1_EntraId // *PluginStatusV1_Okta // *PluginStatusV1_AwsIc + // *PluginStatusV1_NetIq Details isPluginStatusV1_Details `protobuf_oneof:"details"` // last_raw_error variable stores the most recent raw error message received from an API or service. // It is intended to capture the original error message without any modifications or formatting. @@ -18085,7 +18146,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{307} + return fileDescriptor_9198ee693835762e, []int{308} } func (m *PluginStatusV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18132,11 +18193,15 @@ type PluginStatusV1_Okta struct { type PluginStatusV1_AwsIc struct { AwsIc *PluginAWSICStatusV1 `protobuf:"bytes,8,opt,name=aws_ic,json=awsIc,proto3,oneof" json:"aws_ic,omitempty"` } +type PluginStatusV1_NetIq struct { + NetIq *PluginNetIQStatusV1 `protobuf:"bytes,9,opt,name=net_iq,json=netIq,proto3,oneof" json:"net_iq,omitempty"` +} func (*PluginStatusV1_Gitlab) isPluginStatusV1_Details() {} func (*PluginStatusV1_EntraId) isPluginStatusV1_Details() {} func (*PluginStatusV1_Okta) isPluginStatusV1_Details() {} func (*PluginStatusV1_AwsIc) isPluginStatusV1_Details() {} +func (*PluginStatusV1_NetIq) isPluginStatusV1_Details() {} func (m *PluginStatusV1) GetDetails() isPluginStatusV1_Details { if m != nil { @@ -18173,6 +18238,13 @@ func (m *PluginStatusV1) GetAwsIc() *PluginAWSICStatusV1 { return nil } +func (m *PluginStatusV1) GetNetIq() *PluginNetIQStatusV1 { + if x, ok := m.GetDetails().(*PluginStatusV1_NetIq); ok { + return x.NetIq + } + return nil +} + // XXX_OneofWrappers is for the internal use of the proto package. func (*PluginStatusV1) XXX_OneofWrappers() []interface{} { return []interface{}{ @@ -18180,8 +18252,57 @@ func (*PluginStatusV1) XXX_OneofWrappers() []interface{} { (*PluginStatusV1_EntraId)(nil), (*PluginStatusV1_Okta)(nil), (*PluginStatusV1_AwsIc)(nil), + (*PluginStatusV1_NetIq)(nil), + } +} + +// PluginNetIQStatusV1 is the status details for the NetIQ plugin. +type PluginNetIQStatusV1 struct { + // imported_users is the number of users imported from NetIQ eDirectory. + ImportedUsers uint32 `protobuf:"varint,1,opt,name=imported_users,json=importedUsers,proto3" json:"imported_users,omitempty"` + // imported_groups is the number of groups imported from NetIQ eDirectory. + ImportedGroups uint32 `protobuf:"varint,2,opt,name=imported_groups,json=importedGroups,proto3" json:"imported_groups,omitempty"` + // imported_roles is the number of roles imported from NetIQ eDirectory. + ImportedRoles uint32 `protobuf:"varint,3,opt,name=imported_roles,json=importedRoles,proto3" json:"imported_roles,omitempty"` + // imported_resources is the number of resources imported from NetIQ eDirectory. + ImportedResources uint32 `protobuf:"varint,4,opt,name=imported_resources,json=importedResources,proto3" json:"imported_resources,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +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{309} +} +func (m *PluginNetIQStatusV1) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PluginNetIQStatusV1) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PluginNetIQStatusV1.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 *PluginNetIQStatusV1) XXX_Merge(src proto.Message) { + xxx_messageInfo_PluginNetIQStatusV1.Merge(m, src) +} +func (m *PluginNetIQStatusV1) XXX_Size() int { + return m.Size() +} +func (m *PluginNetIQStatusV1) XXX_DiscardUnknown() { + xxx_messageInfo_PluginNetIQStatusV1.DiscardUnknown(m) +} + +var xxx_messageInfo_PluginNetIQStatusV1 proto.InternalMessageInfo // PluginGitlabStatusV1 is the status details for the Gitlab plugin. type PluginGitlabStatusV1 struct { @@ -18200,7 +18321,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{308} + return fileDescriptor_9198ee693835762e, []int{310} } func (m *PluginGitlabStatusV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18244,7 +18365,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{309} + return fileDescriptor_9198ee693835762e, []int{311} } func (m *PluginEntraIDStatusV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18298,7 +18419,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{310} + return fileDescriptor_9198ee693835762e, []int{312} } func (m *PluginOktaStatusV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18346,7 +18467,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{311} + return fileDescriptor_9198ee693835762e, []int{313} } func (m *PluginOktaStatusDetailsSSO) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18403,7 +18524,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{312} + return fileDescriptor_9198ee693835762e, []int{314} } func (m *PluginOktaStatusDetailsAppGroupSync) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18457,7 +18578,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{313} + return fileDescriptor_9198ee693835762e, []int{315} } func (m *PluginOktaStatusDetailsUsersSync) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18500,7 +18621,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{314} + return fileDescriptor_9198ee693835762e, []int{316} } func (m *PluginOktaStatusDetailsSCIM) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18562,7 +18683,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{315} + return fileDescriptor_9198ee693835762e, []int{317} } func (m *PluginOktaStatusDetailsAccessListsSync) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18610,7 +18731,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{316} + return fileDescriptor_9198ee693835762e, []int{318} } func (m *PluginCredentialsV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18721,7 +18842,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{317} + return fileDescriptor_9198ee693835762e, []int{319} } func (m *PluginOAuth2AccessTokenCredentials) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18762,7 +18883,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{318} + return fileDescriptor_9198ee693835762e, []int{320} } func (m *PluginBearerTokenCredentials) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18804,7 +18925,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{319} + return fileDescriptor_9198ee693835762e, []int{321} } func (m *PluginStaticCredentialsRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18846,7 +18967,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{320} + return fileDescriptor_9198ee693835762e, []int{322} } func (m *PluginListV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18889,7 +19010,7 @@ type PluginStaticCredentialsV1 struct { func (m *PluginStaticCredentialsV1) Reset() { *m = PluginStaticCredentialsV1{} } func (*PluginStaticCredentialsV1) ProtoMessage() {} func (*PluginStaticCredentialsV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{321} + return fileDescriptor_9198ee693835762e, []int{323} } func (m *PluginStaticCredentialsV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -18936,7 +19057,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{322} + return fileDescriptor_9198ee693835762e, []int{324} } func (m *PluginStaticCredentialsSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19051,7 +19172,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{323} + return fileDescriptor_9198ee693835762e, []int{325} } func (m *PluginStaticCredentialsBasicAuth) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19097,7 +19218,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{324} + return fileDescriptor_9198ee693835762e, []int{326} } func (m *PluginStaticCredentialsOAuthClientSecret) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19145,7 +19266,7 @@ func (m *PluginStaticCredentialsSSHCertAuthorities) String() string { } func (*PluginStaticCredentialsSSHCertAuthorities) ProtoMessage() {} func (*PluginStaticCredentialsSSHCertAuthorities) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{325} + return fileDescriptor_9198ee693835762e, []int{327} } func (m *PluginStaticCredentialsSSHCertAuthorities) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19188,7 +19309,7 @@ type SAMLIdPServiceProviderV1 struct { func (m *SAMLIdPServiceProviderV1) Reset() { *m = SAMLIdPServiceProviderV1{} } func (*SAMLIdPServiceProviderV1) ProtoMessage() {} func (*SAMLIdPServiceProviderV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{326} + return fileDescriptor_9198ee693835762e, []int{328} } func (m *SAMLIdPServiceProviderV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19259,7 +19380,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{327} + return fileDescriptor_9198ee693835762e, []int{329} } func (m *SAMLIdPServiceProviderSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19306,7 +19427,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{328} + return fileDescriptor_9198ee693835762e, []int{330} } func (m *SAMLAttributeMapping) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19348,7 +19469,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{329} + return fileDescriptor_9198ee693835762e, []int{331} } func (m *IdPOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19390,7 +19511,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{330} + return fileDescriptor_9198ee693835762e, []int{332} } func (m *IdPSAMLOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19440,7 +19561,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{331} + return fileDescriptor_9198ee693835762e, []int{333} } func (m *KubernetesResourceV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19482,7 +19603,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{332} + return fileDescriptor_9198ee693835762e, []int{334} } func (m *KubernetesResourceSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19528,7 +19649,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{333} + return fileDescriptor_9198ee693835762e, []int{335} } func (m *ClusterMaintenanceConfigV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19570,7 +19691,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{334} + return fileDescriptor_9198ee693835762e, []int{336} } func (m *ClusterMaintenanceConfigSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19616,7 +19737,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{335} + return fileDescriptor_9198ee693835762e, []int{337} } func (m *AgentUpgradeWindow) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19663,7 +19784,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{336} + return fileDescriptor_9198ee693835762e, []int{338} } func (m *ScheduledAgentUpgradeWindow) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19706,7 +19827,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{337} + return fileDescriptor_9198ee693835762e, []int{339} } func (m *AgentUpgradeSchedule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19749,7 +19870,7 @@ type UserGroupV1 struct { func (m *UserGroupV1) Reset() { *m = UserGroupV1{} } func (*UserGroupV1) ProtoMessage() {} func (*UserGroupV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{338} + return fileDescriptor_9198ee693835762e, []int{340} } func (m *UserGroupV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19791,7 +19912,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{339} + return fileDescriptor_9198ee693835762e, []int{341} } func (m *UserGroupSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19835,7 +19956,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{340} + return fileDescriptor_9198ee693835762e, []int{342} } func (m *OktaImportRuleSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19879,7 +20000,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{341} + return fileDescriptor_9198ee693835762e, []int{343} } func (m *OktaImportRuleMappingV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19922,7 +20043,7 @@ type OktaImportRuleV1 struct { func (m *OktaImportRuleV1) Reset() { *m = OktaImportRuleV1{} } func (*OktaImportRuleV1) ProtoMessage() {} func (*OktaImportRuleV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{342} + return fileDescriptor_9198ee693835762e, []int{344} } func (m *OktaImportRuleV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -19970,7 +20091,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{343} + return fileDescriptor_9198ee693835762e, []int{345} } func (m *OktaImportRuleMatchV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20013,7 +20134,7 @@ type OktaAssignmentV1 struct { func (m *OktaAssignmentV1) Reset() { *m = OktaAssignmentV1{} } func (*OktaAssignmentV1) ProtoMessage() {} func (*OktaAssignmentV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{344} + return fileDescriptor_9198ee693835762e, []int{346} } func (m *OktaAssignmentV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20067,7 +20188,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{345} + return fileDescriptor_9198ee693835762e, []int{347} } func (m *OktaAssignmentSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20111,7 +20232,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{346} + return fileDescriptor_9198ee693835762e, []int{348} } func (m *OktaAssignmentTargetV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20156,7 +20277,7 @@ type IntegrationV1 struct { func (m *IntegrationV1) Reset() { *m = IntegrationV1{} } func (*IntegrationV1) ProtoMessage() {} func (*IntegrationV1) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{347} + return fileDescriptor_9198ee693835762e, []int{349} } func (m *IntegrationV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20204,7 +20325,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{348} + return fileDescriptor_9198ee693835762e, []int{350} } func (m *IntegrationSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20323,7 +20444,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{349} + return fileDescriptor_9198ee693835762e, []int{351} } func (m *AWSOIDCIntegrationSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20369,7 +20490,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{350} + return fileDescriptor_9198ee693835762e, []int{352} } func (m *AzureOIDCIntegrationSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20411,7 +20532,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{351} + return fileDescriptor_9198ee693835762e, []int{353} } func (m *GitHubIntegrationSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20472,7 +20593,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{352} + return fileDescriptor_9198ee693835762e, []int{354} } func (m *HeadlessAuthentication) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20529,7 +20650,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{353} + return fileDescriptor_9198ee693835762e, []int{355} } func (m *WatchKind) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20579,7 +20700,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{354} + return fileDescriptor_9198ee693835762e, []int{356} } func (m *WatchStatusV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20620,7 +20741,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{355} + return fileDescriptor_9198ee693835762e, []int{357} } func (m *WatchStatusSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20670,7 +20791,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{356} + return fileDescriptor_9198ee693835762e, []int{358} } func (m *ServerInfoV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20712,7 +20833,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{357} + return fileDescriptor_9198ee693835762e, []int{359} } func (m *ServerInfoSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20769,7 +20890,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{358} + return fileDescriptor_9198ee693835762e, []int{360} } func (m *JamfSpecV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20834,7 +20955,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{359} + return fileDescriptor_9198ee693835762e, []int{361} } func (m *JamfInventoryEntry) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20891,7 +21012,7 @@ type MessageWithHeader struct { func (m *MessageWithHeader) Reset() { *m = MessageWithHeader{} } func (*MessageWithHeader) ProtoMessage() {} func (*MessageWithHeader) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{360} + return fileDescriptor_9198ee693835762e, []int{362} } func (m *MessageWithHeader) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -20955,7 +21076,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{361} + return fileDescriptor_9198ee693835762e, []int{363} } func (m *AWSMatcher) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21000,7 +21121,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{362} + return fileDescriptor_9198ee693835762e, []int{364} } func (m *AssumeRole) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21062,7 +21183,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{363} + return fileDescriptor_9198ee693835762e, []int{365} } func (m *InstallerParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21105,7 +21226,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{364} + return fileDescriptor_9198ee693835762e, []int{366} } func (m *AWSSSM) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21148,7 +21269,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{365} + return fileDescriptor_9198ee693835762e, []int{367} } func (m *AzureInstallerParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21202,7 +21323,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{366} + return fileDescriptor_9198ee693835762e, []int{368} } func (m *AzureMatcher) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21257,7 +21378,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{367} + return fileDescriptor_9198ee693835762e, []int{369} } func (m *GCPMatcher) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21303,7 +21424,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{368} + return fileDescriptor_9198ee693835762e, []int{370} } func (m *KubernetesMatcher) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21345,7 +21466,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{369} + return fileDescriptor_9198ee693835762e, []int{371} } func (m *OktaOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21378,18 +21499,20 @@ var xxx_messageInfo_OktaOptions proto.InternalMessageInfo type AccessGraphSync struct { // AWS is a configuration for AWS Access Graph service poll service. AWS []*AccessGraphAWSSync `protobuf:"bytes,1,rep,name=AWS,proto3" json:"aws,omitempty"` - // PollInterval is the frequency at which to poll for AWS resources - PollInterval time.Duration `protobuf:"bytes,2,opt,name=PollInterval,proto3,stdduration" json:"poll_interval,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + // PollInterval is the frequency at which to poll for resources + PollInterval time.Duration `protobuf:"bytes,2,opt,name=PollInterval,proto3,stdduration" json:"poll_interval,omitempty"` + // Azure is a configuration for Azure Access Graph service poll service. + Azure []*AccessGraphAzureSync `protobuf:"bytes,3,rep,name=Azure,proto3" json:"azure,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } 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{370} + return fileDescriptor_9198ee693835762e, []int{372} } func (m *AccessGraphSync) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21435,7 +21558,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{371} + return fileDescriptor_9198ee693835762e, []int{373} } func (m *AccessGraphAWSSync) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -21464,6 +21587,50 @@ func (m *AccessGraphAWSSync) XXX_DiscardUnknown() { var xxx_messageInfo_AccessGraphAWSSync proto.InternalMessageInfo +// AccessGraphAzureSync is a configuration for Azure Access Graph service poll service. +type AccessGraphAzureSync struct { + // SubscriptionID Is the ID of the Azure subscription to sync resources from + SubscriptionID string `protobuf:"bytes,1,opt,name=SubscriptionID,proto3" json:"subscription_id,omitempty"` + // Integration is the integration name used to generate credentials to interact with AWS APIs. + Integration string `protobuf:"bytes,2,opt,name=Integration,proto3" json:"integration,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +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{374} +} +func (m *AccessGraphAzureSync) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AccessGraphAzureSync) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AccessGraphAzureSync.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 *AccessGraphAzureSync) XXX_Merge(src proto.Message) { + xxx_messageInfo_AccessGraphAzureSync.Merge(m, src) +} +func (m *AccessGraphAzureSync) XXX_Size() int { + return m.Size() +} +func (m *AccessGraphAzureSync) XXX_DiscardUnknown() { + xxx_messageInfo_AccessGraphAzureSync.DiscardUnknown(m) +} + +var xxx_messageInfo_AccessGraphAzureSync proto.InternalMessageInfo + func init() { proto.RegisterEnum("types.IAMPolicyStatus", IAMPolicyStatus_name, IAMPolicyStatus_value) proto.RegisterEnum("types.DatabaseTLSMode", DatabaseTLSMode_name, DatabaseTLSMode_value) @@ -21829,10 +21996,12 @@ func init() { proto.RegisterType((*MailgunSpec)(nil), "types.MailgunSpec") proto.RegisterType((*SMTPSpec)(nil), "types.SMTPSpec") proto.RegisterType((*PluginMSTeamsSettings)(nil), "types.PluginMSTeamsSettings") + proto.RegisterType((*PluginNetIQSettings)(nil), "types.PluginNetIQSettings") proto.RegisterType((*PluginBootstrapCredentialsV1)(nil), "types.PluginBootstrapCredentialsV1") proto.RegisterType((*PluginIdSecretCredential)(nil), "types.PluginIdSecretCredential") proto.RegisterType((*PluginOAuth2AuthorizationCodeCredentials)(nil), "types.PluginOAuth2AuthorizationCodeCredentials") proto.RegisterType((*PluginStatusV1)(nil), "types.PluginStatusV1") + proto.RegisterType((*PluginNetIQStatusV1)(nil), "types.PluginNetIQStatusV1") proto.RegisterType((*PluginGitlabStatusV1)(nil), "types.PluginGitlabStatusV1") proto.RegisterType((*PluginEntraIDStatusV1)(nil), "types.PluginEntraIDStatusV1") proto.RegisterType((*PluginOktaStatusV1)(nil), "types.PluginOktaStatusV1") @@ -21901,1911 +22070,1922 @@ func init() { proto.RegisterType((*OktaOptions)(nil), "types.OktaOptions") proto.RegisterType((*AccessGraphSync)(nil), "types.AccessGraphSync") proto.RegisterType((*AccessGraphAWSSync)(nil), "types.AccessGraphAWSSync") + proto.RegisterType((*AccessGraphAzureSync)(nil), "types.AccessGraphAzureSync") } func init() { proto.RegisterFile("teleport/legacy/types/types.proto", fileDescriptor_9198ee693835762e) } var fileDescriptor_9198ee693835762e = []byte{ - // 30381 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xbd, 0x6d, 0x70, 0x1c, 0x49, - 0x76, 0x20, 0x36, 0xdd, 0x8d, 0x8f, 0xc6, 0xc3, 0x57, 0x23, 0x01, 0x92, 0x20, 0x66, 0x86, 0xcd, - 0xa9, 0x99, 0xe1, 0x90, 0xb3, 0x33, 0xe4, 0x12, 0xdc, 0xe1, 0xee, 0xec, 0x7c, 0x6d, 0xa3, 0x1b, - 0x24, 0x9a, 0x04, 0x40, 0x6c, 0x35, 0x40, 0xec, 0x68, 0x3f, 0x6a, 0x0b, 0xdd, 0x09, 0xa0, 0x06, - 0xdd, 0x5d, 0xbd, 0x55, 0xd5, 0x04, 0xa1, 0x3d, 0xf9, 0xee, 0xf4, 0x71, 0xb2, 0x42, 0xd6, 0xe7, - 0x49, 0xa7, 0x3d, 0x87, 0x4e, 0x56, 0xc8, 0x77, 0x3e, 0xc5, 0x39, 0xa4, 0x38, 0x4b, 0x96, 0x7d, - 0xf6, 0x85, 0x65, 0xe9, 0xe2, 0x2c, 0xcb, 0x8a, 0x8b, 0x93, 0xc2, 0x3e, 0xdb, 0xe1, 0xf5, 0x05, - 0x64, 0x59, 0xfe, 0xe1, 0x40, 0x84, 0x23, 0x24, 0x5f, 0x84, 0x23, 0xbc, 0x0e, 0xdd, 0x39, 0xf2, - 0x65, 0x66, 0x55, 0x66, 0x55, 0x75, 0xa3, 0x31, 0xe4, 0xe8, 0xc4, 0x0d, 0xfd, 0x21, 0xd1, 0x2f, - 0xdf, 0x7b, 0x59, 0xf9, 0xfd, 0xf2, 0xe5, 0xfb, 0x80, 0x97, 0x02, 0xda, 0xa4, 0x1d, 0xd7, 0x0b, - 0x6e, 0x34, 0xe9, 0x9e, 0x5d, 0x3f, 0xba, 0x11, 0x1c, 0x75, 0xa8, 0xcf, 0xff, 0xbd, 0xde, 0xf1, - 0xdc, 0xc0, 0x25, 0xc3, 0xf8, 0x63, 0x61, 0x6e, 0xcf, 0xdd, 0x73, 0x11, 0x72, 0x83, 0xfd, 0xc5, - 0x0b, 0x17, 0x2e, 0xed, 0xb9, 0xee, 0x5e, 0x93, 0xde, 0xc0, 0x5f, 0x3b, 0xdd, 0xdd, 0x1b, 0x8d, - 0xae, 0x67, 0x07, 0x8e, 0xdb, 0x16, 0xe5, 0xc5, 0x78, 0x79, 0xe0, 0xb4, 0xa8, 0x1f, 0xd8, 0xad, - 0x4e, 0x2f, 0x06, 0x87, 0x9e, 0xdd, 0xe9, 0x50, 0x4f, 0xd4, 0xbe, 0x70, 0x2d, 0xfc, 0x40, 0x3b, - 0x08, 0x18, 0x25, 0x63, 0x7e, 0xe3, 0xd1, 0x4d, 0xf5, 0xa7, 0x40, 0xbd, 0xdd, 0xa3, 0x2d, 0x5e, - 0xd7, 0x0f, 0x68, 0xc3, 0x6a, 0xd0, 0x47, 0x4e, 0x9d, 0x5a, 0x1e, 0xfd, 0x46, 0xd7, 0xf1, 0x68, - 0x8b, 0xb6, 0x03, 0x41, 0xf7, 0x66, 0x3a, 0x9d, 0xfc, 0x90, 0xd8, 0x17, 0x19, 0xbf, 0x90, 0x83, - 0xb1, 0xfb, 0x94, 0x76, 0x4a, 0x4d, 0xe7, 0x11, 0x25, 0x2f, 0xc3, 0xd0, 0xba, 0xdd, 0xa2, 0xf3, - 0x99, 0xcb, 0x99, 0xab, 0x63, 0x4b, 0xd3, 0x27, 0xc7, 0xc5, 0x71, 0x9f, 0x7a, 0x8f, 0xa8, 0x67, - 0xb5, 0xed, 0x16, 0x35, 0xb1, 0x90, 0x7c, 0x0a, 0xc6, 0xd8, 0xff, 0x7e, 0xc7, 0xae, 0xd3, 0xf9, - 0x2c, 0x62, 0x4e, 0x9e, 0x1c, 0x17, 0xc7, 0xda, 0x12, 0x68, 0x46, 0xe5, 0xa4, 0x0a, 0xa3, 0xcb, - 0x8f, 0x3b, 0x8e, 0x47, 0xfd, 0xf9, 0xa1, 0xcb, 0x99, 0xab, 0xe3, 0x8b, 0x0b, 0xd7, 0x79, 0x1f, - 0x5d, 0x97, 0x7d, 0x74, 0x7d, 0x53, 0x76, 0xe2, 0xd2, 0xec, 0xef, 0x1e, 0x17, 0x9f, 0x3b, 0x39, - 0x2e, 0x8e, 0x52, 0x4e, 0xf2, 0x93, 0x7f, 0x58, 0xcc, 0x98, 0x92, 0x9e, 0xbc, 0x0b, 0x43, 0x9b, - 0x47, 0x1d, 0x3a, 0x3f, 0x76, 0x39, 0x73, 0x75, 0x6a, 0xf1, 0xd2, 0x75, 0x3e, 0xac, 0xe1, 0xc7, - 0x47, 0x7f, 0x31, 0xac, 0xa5, 0xfc, 0xc9, 0x71, 0x71, 0x88, 0xa1, 0x98, 0x48, 0x45, 0xde, 0x84, - 0x91, 0x15, 0xd7, 0x0f, 0xaa, 0x95, 0x79, 0xc0, 0x4f, 0x3e, 0x77, 0x72, 0x5c, 0x9c, 0xd9, 0x77, - 0xfd, 0xc0, 0x72, 0x1a, 0x6f, 0xb8, 0x2d, 0x27, 0xa0, 0xad, 0x4e, 0x70, 0x64, 0x0a, 0x24, 0xe3, - 0x31, 0x4c, 0x6a, 0xfc, 0xc8, 0x38, 0x8c, 0x6e, 0xad, 0xdf, 0x5f, 0x7f, 0xb0, 0xbd, 0x5e, 0x78, - 0x8e, 0xe4, 0x61, 0x68, 0xfd, 0x41, 0x65, 0xb9, 0x90, 0x21, 0xa3, 0x90, 0x2b, 0x6d, 0x6c, 0x14, - 0xb2, 0x64, 0x02, 0xf2, 0x95, 0xd2, 0x66, 0x69, 0xa9, 0x54, 0x5b, 0x2e, 0xe4, 0xc8, 0x2c, 0x4c, - 0x6f, 0x57, 0xd7, 0x2b, 0x0f, 0xb6, 0x6b, 0x56, 0x65, 0xb9, 0x76, 0x7f, 0xf3, 0xc1, 0x46, 0x61, - 0x88, 0x4c, 0x01, 0xdc, 0xdf, 0x5a, 0x5a, 0x36, 0xd7, 0x97, 0x37, 0x97, 0x6b, 0x85, 0x61, 0x32, - 0x07, 0x05, 0x49, 0x62, 0xd5, 0x96, 0xcd, 0x87, 0xd5, 0xf2, 0x72, 0x61, 0xe4, 0xde, 0x50, 0x3e, - 0x57, 0x18, 0x32, 0x47, 0x57, 0xa9, 0xed, 0xd3, 0x6a, 0xc5, 0xf8, 0x0f, 0x73, 0x90, 0x5f, 0xa3, - 0x81, 0xdd, 0xb0, 0x03, 0x9b, 0xbc, 0xa0, 0x8d, 0x0f, 0x36, 0x51, 0x19, 0x98, 0x97, 0x93, 0x03, - 0x33, 0x7c, 0x72, 0x5c, 0xcc, 0xbc, 0xa9, 0x0e, 0xc8, 0x3b, 0x30, 0x5e, 0xa1, 0x7e, 0xdd, 0x73, - 0x3a, 0x6c, 0xb2, 0xcd, 0xe7, 0x10, 0xed, 0xe2, 0xc9, 0x71, 0xf1, 0x5c, 0x23, 0x02, 0x2b, 0x1d, - 0xa2, 0x62, 0x93, 0x2a, 0x8c, 0xac, 0xda, 0x3b, 0xb4, 0xe9, 0xcf, 0x0f, 0x5f, 0xce, 0x5d, 0x1d, - 0x5f, 0x7c, 0x5e, 0x0c, 0x82, 0xfc, 0xc0, 0xeb, 0xbc, 0x74, 0xb9, 0x1d, 0x78, 0x47, 0x4b, 0x73, - 0x27, 0xc7, 0xc5, 0x42, 0x13, 0x01, 0x6a, 0x07, 0x73, 0x14, 0x52, 0x8b, 0x26, 0xc6, 0xc8, 0xa9, - 0x13, 0xe3, 0xc5, 0xdf, 0x3d, 0x2e, 0x66, 0xd8, 0x80, 0x89, 0x89, 0x11, 0xf1, 0xd3, 0xa7, 0xc8, - 0x22, 0xe4, 0x4d, 0xfa, 0xc8, 0xf1, 0x59, 0xcb, 0xf2, 0xd8, 0xb2, 0xf3, 0x27, 0xc7, 0x45, 0xe2, - 0x09, 0x98, 0xf2, 0x19, 0x21, 0xde, 0xc2, 0xdb, 0x30, 0xae, 0x7c, 0x35, 0x29, 0x40, 0xee, 0x80, - 0x1e, 0xf1, 0x1e, 0x36, 0xd9, 0x9f, 0x64, 0x0e, 0x86, 0x1f, 0xd9, 0xcd, 0xae, 0xe8, 0x52, 0x93, - 0xff, 0xf8, 0x7c, 0xf6, 0x73, 0x99, 0x7b, 0x43, 0xf9, 0xd1, 0x42, 0xde, 0xcc, 0x56, 0x2b, 0xc6, - 0x4f, 0x0f, 0x41, 0xde, 0x74, 0xf9, 0x02, 0x26, 0xd7, 0x60, 0xb8, 0x16, 0xd8, 0x81, 0x1c, 0xa6, - 0xd9, 0x93, 0xe3, 0xe2, 0x34, 0x5b, 0xdc, 0x54, 0xa9, 0x9f, 0x63, 0x30, 0xd4, 0x8d, 0x7d, 0xdb, - 0x97, 0xc3, 0x85, 0xa8, 0x1d, 0x06, 0x50, 0x51, 0x11, 0x83, 0x5c, 0x81, 0xa1, 0x35, 0xb7, 0x41, - 0xc5, 0x88, 0x91, 0x93, 0xe3, 0xe2, 0x54, 0xcb, 0x6d, 0xa8, 0x88, 0x58, 0x4e, 0xde, 0x80, 0xb1, - 0x72, 0xd7, 0xf3, 0x68, 0x9b, 0xcd, 0xf5, 0x21, 0x44, 0x9e, 0x3a, 0x39, 0x2e, 0x42, 0x9d, 0x03, - 0x2d, 0xa7, 0x61, 0x46, 0x08, 0x6c, 0x18, 0x6a, 0x81, 0xed, 0x05, 0xb4, 0x31, 0x3f, 0x3c, 0xd0, - 0x30, 0xb0, 0xf5, 0x39, 0xe3, 0x73, 0x92, 0xf8, 0x30, 0x08, 0x4e, 0x64, 0x05, 0xc6, 0xef, 0x7a, - 0x76, 0x9d, 0x6e, 0x50, 0xcf, 0x71, 0x1b, 0x38, 0xbe, 0xb9, 0xa5, 0x2b, 0x27, 0xc7, 0xc5, 0xf3, - 0x7b, 0x0c, 0x6c, 0x75, 0x10, 0x1e, 0x51, 0x7f, 0xe7, 0xb8, 0x98, 0xaf, 0x88, 0xad, 0xd6, 0x54, - 0x49, 0xc9, 0xd7, 0xd9, 0xe0, 0xf8, 0x01, 0x76, 0x2d, 0x6d, 0xcc, 0x8f, 0x9e, 0xfa, 0x89, 0x86, - 0xf8, 0xc4, 0xf3, 0x4d, 0xdb, 0x0f, 0x2c, 0x8f, 0xd3, 0xc5, 0xbe, 0x53, 0x65, 0x49, 0x1e, 0x40, - 0xbe, 0x56, 0xdf, 0xa7, 0x8d, 0x6e, 0x93, 0xe2, 0x94, 0x19, 0x5f, 0xbc, 0x20, 0x26, 0xb5, 0x1c, - 0x4f, 0x59, 0xbc, 0xb4, 0x20, 0x78, 0x13, 0x5f, 0x40, 0xd4, 0xf9, 0x24, 0xb1, 0x3e, 0x9f, 0xff, - 0xd6, 0x2f, 0x16, 0x9f, 0xfb, 0x6b, 0xff, 0xf2, 0xf2, 0x73, 0xc6, 0x7f, 0x9e, 0x85, 0x42, 0x9c, - 0x09, 0xd9, 0x85, 0xc9, 0xad, 0x4e, 0xc3, 0x0e, 0x68, 0xb9, 0xe9, 0xd0, 0x76, 0xe0, 0xe3, 0x24, - 0xe9, 0xdf, 0xa6, 0x57, 0x44, 0xbd, 0xf3, 0x5d, 0x24, 0xb4, 0xea, 0x9c, 0x32, 0xd6, 0x2a, 0x9d, - 0x6d, 0x54, 0x4f, 0x0d, 0x37, 0x70, 0x1f, 0x67, 0xd8, 0xd9, 0xea, 0xe1, 0x5b, 0x7f, 0x8f, 0x7a, - 0x04, 0x5b, 0x31, 0x81, 0xda, 0x8d, 0x9d, 0x23, 0x9c, 0x99, 0x83, 0x4f, 0x20, 0x46, 0x92, 0x32, - 0x81, 0x18, 0xd8, 0xf8, 0x3f, 0x32, 0x30, 0x65, 0x52, 0xdf, 0xed, 0x7a, 0x75, 0xba, 0x42, 0xed, - 0x06, 0xf5, 0xd8, 0xf4, 0xbf, 0xef, 0xb4, 0x1b, 0x62, 0x4d, 0xe1, 0xf4, 0x3f, 0x70, 0xda, 0xea, - 0xd6, 0x8d, 0xe5, 0xe4, 0xd3, 0x30, 0x5a, 0xeb, 0xee, 0x20, 0x6a, 0x36, 0xda, 0x01, 0xfc, 0xee, - 0x8e, 0x15, 0x43, 0x97, 0x68, 0xe4, 0x06, 0x8c, 0x3e, 0xa4, 0x9e, 0x1f, 0xed, 0x86, 0x78, 0x34, - 0x3c, 0xe2, 0x20, 0x95, 0x40, 0x60, 0x91, 0xbb, 0xd1, 0x8e, 0x2c, 0x0e, 0xb5, 0xe9, 0xd8, 0x3e, - 0x18, 0x4d, 0x95, 0x96, 0x80, 0xa8, 0x53, 0x45, 0x62, 0x19, 0x3f, 0x95, 0x85, 0x42, 0xc5, 0x0e, - 0xec, 0x1d, 0xdb, 0x17, 0xfd, 0xf9, 0xf0, 0x16, 0xdb, 0xe3, 0x95, 0x86, 0xe2, 0x1e, 0xcf, 0xbe, - 0xfc, 0x63, 0x37, 0xef, 0xd5, 0x78, 0xf3, 0xc6, 0xd9, 0x09, 0x2b, 0x9a, 0x17, 0x35, 0xea, 0xbd, - 0xd3, 0x1b, 0x55, 0x10, 0x8d, 0xca, 0xcb, 0x46, 0x45, 0x4d, 0x21, 0xef, 0xc1, 0x50, 0xad, 0x43, - 0xeb, 0x62, 0x13, 0x91, 0xe7, 0x82, 0xde, 0x38, 0x86, 0xf0, 0xf0, 0xd6, 0xd2, 0x84, 0x60, 0x33, - 0xe4, 0x77, 0x68, 0xdd, 0x44, 0x32, 0x65, 0xd1, 0xfc, 0xa3, 0x1c, 0xcc, 0xa5, 0x91, 0xa9, 0xed, - 0x18, 0xe9, 0xd3, 0x8e, 0xab, 0x90, 0x67, 0x47, 0x38, 0x3b, 0x16, 0x71, 0xbb, 0x18, 0x5b, 0x9a, - 0x60, 0x9f, 0xbc, 0x2f, 0x60, 0x66, 0x58, 0x4a, 0x5e, 0x0e, 0x25, 0x82, 0x7c, 0xc4, 0x4f, 0x48, - 0x04, 0x52, 0x0e, 0x60, 0x63, 0x2d, 0x97, 0x30, 0x0a, 0x0e, 0x51, 0xb7, 0x48, 0x70, 0x34, 0xd6, - 0x9e, 0x80, 0x68, 0xc7, 0x8c, 0x3c, 0x14, 0x96, 0x21, 0x2f, 0x9b, 0x35, 0x3f, 0x81, 0x8c, 0x66, - 0x62, 0x9d, 0xf4, 0xf0, 0x16, 0x1f, 0xcc, 0x86, 0xf8, 0xad, 0xb2, 0x91, 0x38, 0xe4, 0x16, 0xe4, - 0x37, 0x3c, 0xf7, 0xf1, 0x51, 0xb5, 0xe2, 0xcf, 0x4f, 0x5e, 0xce, 0x5d, 0x1d, 0x5b, 0xba, 0x70, - 0x72, 0x5c, 0x9c, 0xed, 0x30, 0x98, 0xe5, 0x34, 0xd4, 0x93, 0x36, 0x44, 0xbc, 0x37, 0x94, 0xcf, - 0x14, 0xb2, 0xf7, 0x86, 0xf2, 0xd9, 0x42, 0x8e, 0x8b, 0x17, 0xf7, 0x86, 0xf2, 0x43, 0x85, 0xe1, - 0x7b, 0x43, 0xf9, 0x61, 0x14, 0x38, 0xc6, 0x0a, 0x70, 0x6f, 0x28, 0x3f, 0x5e, 0x98, 0xd0, 0x4e, - 0x7b, 0x64, 0x10, 0xb8, 0x75, 0xb7, 0x69, 0xe6, 0xb6, 0xcc, 0xaa, 0x39, 0x52, 0x2e, 0x95, 0xa9, - 0x17, 0x98, 0xb9, 0xd2, 0x76, 0xcd, 0x9c, 0xac, 0x1c, 0xb5, 0xed, 0x96, 0x53, 0xe7, 0x47, 0xa7, - 0x99, 0xbb, 0x5b, 0xde, 0x30, 0x4a, 0x30, 0x15, 0xb5, 0x65, 0xd5, 0xf1, 0x03, 0x72, 0x03, 0xc6, - 0x24, 0x84, 0x6d, 0x74, 0xb9, 0xd4, 0x56, 0x9b, 0x11, 0x8e, 0xf1, 0x3b, 0x59, 0x80, 0xa8, 0xe4, - 0x19, 0x5d, 0x0b, 0x9f, 0xd5, 0xd6, 0xc2, 0xb9, 0xf8, 0x5a, 0xe8, 0xb9, 0x0a, 0xc8, 0x07, 0x30, - 0xc2, 0xc4, 0x82, 0xae, 0x14, 0x89, 0x2e, 0xc4, 0x49, 0xb1, 0xf0, 0xe1, 0xad, 0xa5, 0x29, 0x41, - 0x3c, 0xe2, 0x23, 0xc4, 0x14, 0x64, 0xca, 0x32, 0xfa, 0x85, 0xd1, 0x68, 0x30, 0xc4, 0x02, 0xba, - 0x0a, 0xe1, 0x80, 0x8a, 0x0e, 0xc5, 0x95, 0xd1, 0x91, 0x83, 0x1c, 0x96, 0x92, 0x8b, 0xc0, 0x06, - 0x5c, 0x74, 0xea, 0xe8, 0xc9, 0x71, 0x31, 0xd7, 0xf5, 0x1c, 0x9c, 0x04, 0xe4, 0x06, 0x88, 0x69, - 0x20, 0x3a, 0x90, 0xcd, 0xbe, 0x99, 0xba, 0x6d, 0xd5, 0xa9, 0x17, 0x44, 0x3d, 0x3e, 0x9f, 0x91, - 0xb3, 0x85, 0x74, 0x40, 0x9f, 0x2a, 0xf3, 0x43, 0x38, 0x0d, 0xae, 0xa6, 0xf6, 0xca, 0x75, 0x0d, - 0x95, 0x8b, 0x91, 0x97, 0xe5, 0xa9, 0xd4, 0xe0, 0x65, 0x56, 0x42, 0xa4, 0xd4, 0x2b, 0x20, 0xb7, - 0x80, 0xcd, 0x50, 0xd1, 0xfb, 0x20, 0xea, 0x29, 0x6d, 0xd7, 0x96, 0xce, 0x09, 0x4e, 0x93, 0xf6, - 0xa1, 0x4a, 0xce, 0xb0, 0xc9, 0x3b, 0xc0, 0xa6, 0xb0, 0xe8, 0x77, 0x22, 0x88, 0xee, 0x96, 0x37, - 0xca, 0x4d, 0xb7, 0xdb, 0xa8, 0x7d, 0x71, 0x35, 0x22, 0xde, 0xab, 0x77, 0x54, 0xe2, 0xbb, 0xe5, - 0x0d, 0xf2, 0x0e, 0x0c, 0x97, 0xbe, 0xb7, 0xeb, 0x51, 0x21, 0x9f, 0x4c, 0xc8, 0x3a, 0x19, 0x6c, - 0xe9, 0x82, 0x20, 0x9c, 0xb6, 0xd9, 0x4f, 0x55, 0xae, 0xc3, 0x72, 0x56, 0xf3, 0xe6, 0x6a, 0x4d, - 0xc8, 0x1e, 0x24, 0xd6, 0x2d, 0x9b, 0xab, 0xca, 0x67, 0x07, 0x5a, 0xab, 0x19, 0x15, 0xb9, 0x01, - 0xd9, 0x52, 0x05, 0x6f, 0x44, 0xe3, 0x8b, 0x63, 0xb2, 0xda, 0xca, 0xd2, 0x9c, 0x20, 0x99, 0xb0, - 0xd5, 0x65, 0x90, 0x2d, 0x55, 0xc8, 0x12, 0x0c, 0xaf, 0x1d, 0xd5, 0xbe, 0xb8, 0x2a, 0x36, 0xb3, - 0x59, 0x39, 0xaf, 0x19, 0xec, 0x01, 0x2e, 0x7b, 0x3f, 0xfa, 0xe2, 0xd6, 0x91, 0xff, 0x8d, 0xa6, - 0xfa, 0xc5, 0x88, 0x46, 0x36, 0x60, 0xac, 0xd4, 0x68, 0x39, 0xed, 0x2d, 0x9f, 0x7a, 0xf3, 0xe3, - 0xc8, 0x67, 0x3e, 0xf6, 0xdd, 0x61, 0xf9, 0xd2, 0xfc, 0xc9, 0x71, 0x71, 0xce, 0x66, 0x3f, 0xad, - 0xae, 0x4f, 0x3d, 0x85, 0x5b, 0xc4, 0x84, 0x6c, 0x00, 0xac, 0xb9, 0xed, 0x3d, 0xb7, 0x14, 0x34, - 0x6d, 0x3f, 0xb6, 0x3d, 0x46, 0x05, 0xa1, 0xf8, 0x70, 0xae, 0xc5, 0x60, 0x96, 0xcd, 0x80, 0x0a, - 0x43, 0x85, 0x07, 0xb9, 0x03, 0x23, 0x0f, 0x3c, 0xbb, 0xde, 0xa4, 0xf3, 0x93, 0xc8, 0x6d, 0x4e, - 0x70, 0xe3, 0x40, 0xd9, 0xd2, 0x79, 0xc1, 0xb0, 0xe0, 0x22, 0x58, 0xbd, 0xa6, 0x70, 0xc4, 0x85, - 0x6d, 0x20, 0xc9, 0x39, 0x99, 0x72, 0x49, 0xf8, 0x94, 0x7a, 0x49, 0x88, 0x16, 0x7d, 0xd9, 0x6d, - 0xb5, 0xec, 0x76, 0x03, 0x69, 0x1f, 0x2e, 0x2a, 0x77, 0x07, 0xe3, 0x1b, 0x30, 0x93, 0xe8, 0xac, - 0x53, 0xee, 0x77, 0xef, 0xc3, 0x74, 0x85, 0xee, 0xda, 0xdd, 0x66, 0x10, 0x9e, 0x24, 0x7c, 0x89, - 0xe2, 0x4d, 0xab, 0xc1, 0x8b, 0x2c, 0x79, 0x7c, 0x98, 0x71, 0x64, 0xe3, 0x3d, 0x98, 0xd4, 0x9a, - 0xcf, 0xae, 0x0a, 0xa5, 0x6e, 0xc3, 0x09, 0x70, 0x20, 0x33, 0xd1, 0x55, 0xc1, 0x66, 0x40, 0x1c, - 0x2e, 0x33, 0x42, 0x30, 0xfe, 0xae, 0x2a, 0xad, 0x88, 0x9d, 0x88, 0x5d, 0xab, 0xc5, 0x7e, 0x90, - 0x89, 0x64, 0xa7, 0xc4, 0x7e, 0x10, 0xee, 0x06, 0xd7, 0xf8, 0xda, 0xcc, 0x26, 0xd6, 0xe6, 0xb8, - 0x18, 0x89, 0x9c, 0x7d, 0xe8, 0xf3, 0x15, 0x19, 0xce, 0xd4, 0xdc, 0xc7, 0x9f, 0xa9, 0x1f, 0xc0, - 0xc4, 0x9a, 0xdd, 0xb6, 0xf7, 0x68, 0x83, 0xb5, 0x80, 0xef, 0x3d, 0x63, 0x4b, 0xcf, 0x9f, 0x1c, - 0x17, 0x2f, 0xb4, 0x38, 0x1c, 0x5b, 0xa9, 0x4e, 0x22, 0x8d, 0x80, 0xdc, 0x94, 0x2b, 0x7b, 0x38, - 0x65, 0x65, 0x4f, 0x8a, 0xda, 0x87, 0x71, 0x65, 0x8b, 0xf5, 0x6c, 0xfc, 0xd6, 0x18, 0xb6, 0x91, - 0xbc, 0x01, 0x23, 0x26, 0xdd, 0x63, 0x47, 0x4d, 0x26, 0x1a, 0x24, 0x0f, 0x21, 0x6a, 0xc7, 0x70, - 0x1c, 0x94, 0x33, 0x68, 0xc3, 0xdf, 0x77, 0x76, 0x03, 0xd1, 0x3b, 0xa1, 0x9c, 0x21, 0xc0, 0x8a, - 0x9c, 0x21, 0x20, 0xfa, 0x75, 0x96, 0xc3, 0xd8, 0xee, 0x67, 0x56, 0x6a, 0xa2, 0xd3, 0x64, 0x0f, - 0x9b, 0x15, 0x65, 0x1b, 0xf1, 0x34, 0x29, 0x81, 0x61, 0x93, 0xdb, 0x30, 0x56, 0xaa, 0xd7, 0xdd, - 0xae, 0x72, 0x67, 0xe4, 0xeb, 0x96, 0x03, 0x75, 0x15, 0x49, 0x84, 0x4a, 0x6a, 0x30, 0xbe, 0xcc, - 0x2e, 0x5a, 0x4e, 0xd9, 0xae, 0xef, 0xcb, 0x4e, 0x92, 0x7b, 0x98, 0x52, 0x12, 0xad, 0x5c, 0x8a, - 0xc0, 0x3a, 0x03, 0xaa, 0x4a, 0x06, 0x05, 0x97, 0x6c, 0xc2, 0x78, 0x8d, 0xd6, 0x3d, 0x1a, 0xd4, - 0x02, 0xd7, 0xa3, 0xb1, 0x2d, 0x59, 0x29, 0x59, 0xba, 0x24, 0xef, 0x7a, 0x3e, 0x02, 0x2d, 0x9f, - 0x41, 0x55, 0xae, 0x0a, 0x32, 0x17, 0xda, 0x5b, 0xae, 0x77, 0x54, 0x59, 0x12, 0xdb, 0x74, 0x74, - 0xa6, 0x73, 0xb0, 0x2a, 0xb4, 0x33, 0x48, 0x63, 0x47, 0x17, 0xda, 0x39, 0x16, 0x8e, 0x54, 0xa5, - 0x86, 0xb2, 0x95, 0xd8, 0xb4, 0xa7, 0xa3, 0x5e, 0x46, 0xb0, 0x32, 0x52, 0x0d, 0x1f, 0x25, 0x33, - 0x6d, 0xa4, 0x04, 0x16, 0xe9, 0x00, 0x91, 0xa3, 0xc6, 0x05, 0xdd, 0x26, 0xf5, 0x7d, 0xb1, 0x97, - 0x5f, 0x8c, 0x0d, 0x7e, 0x84, 0xb0, 0xf4, 0xaa, 0x60, 0xfe, 0xa2, 0x9c, 0x06, 0xe2, 0x9e, 0xc6, - 0x0a, 0x95, 0x7a, 0x52, 0x78, 0x93, 0xb7, 0x01, 0x96, 0x1f, 0x07, 0xd4, 0x6b, 0xdb, 0xcd, 0x50, - 0x0f, 0x86, 0xaa, 0x1f, 0x2a, 0xa0, 0xfa, 0x40, 0x2b, 0xc8, 0xa4, 0x0c, 0x93, 0x25, 0xdf, 0xef, - 0xb6, 0xa8, 0xe9, 0x36, 0x69, 0xc9, 0x5c, 0xc7, 0x7d, 0x7f, 0x6c, 0xe9, 0xc5, 0x93, 0xe3, 0xe2, - 0x45, 0x1b, 0x0b, 0x2c, 0xcf, 0x6d, 0x52, 0xcb, 0xf6, 0xd4, 0xd9, 0xad, 0xd3, 0x90, 0x07, 0x00, - 0x0f, 0x3a, 0xb4, 0x5d, 0xa3, 0xb6, 0x57, 0xdf, 0x8f, 0x6d, 0xf3, 0x51, 0xc1, 0xd2, 0x0b, 0xa2, - 0x85, 0x73, 0x6e, 0x87, 0xb6, 0x7d, 0x84, 0xa9, 0x5f, 0x15, 0x61, 0x92, 0x6d, 0x98, 0xae, 0x96, - 0xd6, 0x36, 0xdc, 0xa6, 0x53, 0x3f, 0x12, 0x92, 0xd3, 0x14, 0x6a, 0x07, 0xcf, 0x0b, 0xae, 0xb1, - 0x52, 0xbe, 0x3d, 0x39, 0x76, 0xcb, 0xea, 0x20, 0xd4, 0x12, 0xf2, 0x53, 0x9c, 0x0b, 0xf9, 0x90, - 0xcd, 0x41, 0x9f, 0x09, 0x83, 0x9b, 0xf6, 0x9e, 0x3f, 0x3f, 0xad, 0x69, 0xbb, 0x4a, 0xdb, 0xb5, - 0xeb, 0x4a, 0x29, 0x17, 0x53, 0x16, 0xf8, 0x44, 0x44, 0xa8, 0x15, 0xd8, 0x7b, 0xbe, 0x3e, 0x11, - 0x43, 0x6c, 0x72, 0x0f, 0xa0, 0xe2, 0xd6, 0xbb, 0x2d, 0xda, 0x0e, 0x2a, 0x4b, 0xf3, 0x05, 0xfd, - 0x2a, 0x10, 0x16, 0x44, 0x5b, 0x5b, 0xc3, 0xad, 0x6b, 0x33, 0x51, 0xa1, 0x5e, 0x78, 0x1f, 0x0a, - 0xf1, 0x0f, 0x39, 0xa3, 0x02, 0x6b, 0xb2, 0x30, 0xa5, 0xb4, 0x7e, 0xf9, 0xb1, 0xe3, 0x07, 0xbe, - 0xf1, 0x4d, 0x6d, 0x05, 0xb2, 0xdd, 0xe1, 0x3e, 0x3d, 0xda, 0xf0, 0xe8, 0xae, 0xf3, 0x58, 0x6c, - 0x66, 0xb8, 0x3b, 0x1c, 0xd0, 0x23, 0xab, 0x83, 0x50, 0x75, 0x77, 0x08, 0x51, 0xc9, 0x67, 0x20, - 0x7f, 0x7f, 0xad, 0x76, 0x9f, 0x1e, 0x55, 0x2b, 0xe2, 0xa0, 0xe2, 0x64, 0x2d, 0xdf, 0x62, 0xa4, - 0xda, 0x5c, 0x0b, 0x31, 0x8d, 0xa5, 0x68, 0x27, 0x64, 0x35, 0x97, 0x9b, 0x5d, 0x3f, 0xa0, 0x5e, - 0xb5, 0xa2, 0xd6, 0x5c, 0xe7, 0xc0, 0xd8, 0xbe, 0x14, 0xa2, 0x1a, 0xff, 0x26, 0x8b, 0xbb, 0x20, - 0x9b, 0xf0, 0xd5, 0xb6, 0x1f, 0xd8, 0xed, 0x3a, 0x0d, 0x19, 0xe0, 0x84, 0x77, 0x04, 0x34, 0x36, - 0xe1, 0x23, 0x64, 0xbd, 0xea, 0xec, 0xc0, 0x55, 0xb3, 0x2a, 0xa5, 0xe6, 0xa2, 0x5a, 0x51, 0xd5, - 0xab, 0x9e, 0x80, 0xc6, 0xaa, 0x8c, 0x90, 0xc9, 0x15, 0x18, 0xad, 0x96, 0xd6, 0x4a, 0xdd, 0x60, - 0x1f, 0xf7, 0xe0, 0x3c, 0x97, 0xcf, 0xd9, 0x6c, 0xb5, 0xbb, 0xc1, 0xbe, 0x29, 0x0b, 0xc9, 0x0d, - 0xbc, 0xf7, 0xb4, 0x69, 0xc0, 0xd5, 0xb0, 0xe2, 0xd0, 0xf5, 0x39, 0x28, 0x76, 0xed, 0x61, 0x20, - 0xf2, 0x3a, 0x0c, 0x3f, 0xdc, 0x28, 0x57, 0x2b, 0xe2, 0xe2, 0x8c, 0x27, 0xd1, 0xa3, 0x4e, 0x5d, - 0xff, 0x12, 0x8e, 0x42, 0x96, 0x61, 0xaa, 0x46, 0xeb, 0x5d, 0xcf, 0x09, 0x8e, 0xee, 0x7a, 0x6e, - 0xb7, 0xe3, 0xcf, 0x8f, 0x62, 0x1d, 0xb8, 0xd2, 0x7d, 0x51, 0x62, 0xed, 0x61, 0x91, 0x42, 0x1d, - 0x23, 0x32, 0x7e, 0x3b, 0x13, 0x6d, 0x93, 0xe4, 0x8a, 0x26, 0xd6, 0xa0, 0xee, 0x86, 0x89, 0x35, - 0xaa, 0xee, 0x06, 0x05, 0x1c, 0x13, 0x48, 0xb9, 0xeb, 0x07, 0x6e, 0x6b, 0xb9, 0xdd, 0xe8, 0xb8, - 0x4e, 0x3b, 0x40, 0x2a, 0xde, 0xf9, 0xc6, 0xc9, 0x71, 0xf1, 0x52, 0x1d, 0x4b, 0x2d, 0x2a, 0x8a, - 0xad, 0x18, 0x97, 0x14, 0xea, 0x27, 0x18, 0x0f, 0xe3, 0xf7, 0xb2, 0xda, 0xf1, 0xc6, 0x3e, 0xcf, - 0xa4, 0x9d, 0xa6, 0x53, 0xc7, 0x1b, 0x3d, 0x36, 0x34, 0x9c, 0x55, 0xf8, 0x79, 0x5e, 0x54, 0xca, - 0x7b, 0x48, 0xe7, 0x9d, 0x42, 0x4d, 0xbe, 0x00, 0x13, 0x4c, 0xd2, 0x10, 0x3f, 0xfd, 0xf9, 0x2c, - 0x76, 0xf6, 0x0b, 0xa8, 0x85, 0xf3, 0xa9, 0x17, 0xb2, 0xd1, 0x44, 0x14, 0x95, 0x82, 0x34, 0x60, - 0x7e, 0xd3, 0xb3, 0xdb, 0xbe, 0x13, 0x2c, 0xb7, 0xeb, 0xde, 0x11, 0x4a, 0x46, 0xcb, 0x6d, 0x7b, - 0xa7, 0x49, 0x1b, 0xd8, 0xdc, 0xfc, 0xd2, 0xd5, 0x93, 0xe3, 0xe2, 0x2b, 0x01, 0xc7, 0xb1, 0x68, - 0x88, 0x64, 0x51, 0x8e, 0xa5, 0x70, 0xee, 0xc9, 0x89, 0x49, 0x52, 0xb2, 0x5b, 0xf1, 0x11, 0x86, - 0x0b, 0x09, 0x28, 0x49, 0x85, 0xa3, 0xc1, 0xf6, 0x30, 0xf5, 0x33, 0x55, 0x02, 0xe3, 0xff, 0xc9, - 0x44, 0x07, 0x30, 0x79, 0x17, 0xc6, 0xc5, 0x8a, 0x51, 0xe6, 0x05, 0xee, 0xa0, 0x72, 0x79, 0xc5, - 0x46, 0x56, 0x45, 0x67, 0xf7, 0xfe, 0x52, 0x79, 0x55, 0x99, 0x1b, 0x78, 0xef, 0xb7, 0xeb, 0xcd, - 0x38, 0x95, 0x44, 0x63, 0x93, 0x60, 0x73, 0xb5, 0xa6, 0xf7, 0x0a, 0x4e, 0x82, 0xa0, 0xe9, 0xa7, - 0x74, 0x83, 0x82, 0xfc, 0xe4, 0x0d, 0xff, 0x9f, 0x33, 0x69, 0xe7, 0x3c, 0x59, 0x82, 0xc9, 0x6d, - 0xd7, 0x3b, 0xc0, 0xf1, 0x55, 0x3a, 0x01, 0x47, 0xfe, 0x50, 0x16, 0xc4, 0x1b, 0xa4, 0x93, 0xa8, - 0xdf, 0xa6, 0xf4, 0x86, 0xfe, 0x6d, 0x31, 0x0e, 0x1a, 0x01, 0x1b, 0x87, 0x90, 0x63, 0xb8, 0x3a, - 0x70, 0x1c, 0xa2, 0x4f, 0xd0, 0xa6, 0xb0, 0x8a, 0x6e, 0xfc, 0x57, 0x19, 0xf5, 0x3c, 0x67, 0x9d, - 0x5c, 0x71, 0x5b, 0xb6, 0xd3, 0x56, 0x9a, 0xc3, 0x1f, 0x96, 0x10, 0x1a, 0xff, 0x12, 0x05, 0x99, - 0xdc, 0x82, 0x3c, 0xff, 0x15, 0xee, 0xb5, 0xa8, 0xd5, 0x12, 0x84, 0xfa, 0x41, 0x21, 0x11, 0x13, - 0x23, 0x93, 0x3b, 0xeb, 0xc8, 0xfc, 0x56, 0x46, 0x3d, 0x8a, 0x3f, 0xee, 0x61, 0x13, 0x3b, 0x64, - 0xb2, 0x67, 0x39, 0x64, 0x9e, 0xb8, 0x09, 0x7f, 0x2d, 0x03, 0xe3, 0x8a, 0x96, 0x82, 0xb5, 0x61, - 0xc3, 0x73, 0x3f, 0xa2, 0xf5, 0x40, 0x6f, 0x43, 0x87, 0x03, 0x63, 0x6d, 0x08, 0x51, 0x9f, 0xa0, - 0x0d, 0xc6, 0x9f, 0x66, 0xc4, 0x1d, 0x69, 0xe0, 0x6d, 0x5e, 0xdf, 0x92, 0xb3, 0x67, 0x39, 0x22, - 0xbf, 0x00, 0xc3, 0x26, 0x6d, 0x38, 0xbe, 0xb8, 0xdf, 0xcc, 0xa8, 0xf7, 0x31, 0x2c, 0x88, 0xe4, - 0x26, 0x8f, 0xfd, 0x54, 0xcf, 0x37, 0x2c, 0x67, 0x82, 0x6c, 0xd5, 0xbf, 0xd3, 0xa4, 0x8f, 0x1d, - 0xbe, 0x18, 0xc5, 0x51, 0x8b, 0xc7, 0x9b, 0xe3, 0x5b, 0xbb, 0xac, 0x44, 0x48, 0xd4, 0xea, 0xc2, - 0xd3, 0x68, 0x8c, 0x0f, 0x01, 0xa2, 0x2a, 0xc9, 0x7d, 0x28, 0x88, 0xd9, 0xe0, 0xb4, 0xf7, 0xb8, - 0x20, 0x25, 0xfa, 0xa0, 0x78, 0x72, 0x5c, 0x7c, 0xbe, 0x1e, 0x96, 0x09, 0xa9, 0x53, 0xe1, 0x9b, - 0x20, 0x34, 0xfe, 0x7e, 0x16, 0xb2, 0x25, 0x1c, 0x90, 0xfb, 0xf4, 0x28, 0xb0, 0x77, 0xee, 0x38, - 0x4d, 0x6d, 0x31, 0x1d, 0x20, 0xd4, 0xda, 0x75, 0x34, 0x75, 0x85, 0x82, 0xcc, 0x16, 0xd3, 0x7d, - 0x6f, 0xe7, 0x2d, 0x24, 0x54, 0x16, 0xd3, 0x81, 0xb7, 0xf3, 0x56, 0x9c, 0x2c, 0x44, 0x24, 0x06, - 0x8c, 0xf0, 0x85, 0x25, 0xe6, 0x20, 0x9c, 0x1c, 0x17, 0x47, 0xf8, 0xfa, 0x33, 0x45, 0x09, 0xb9, - 0x08, 0xb9, 0xda, 0xc6, 0xba, 0xd8, 0x01, 0x51, 0x2d, 0xe8, 0x77, 0xda, 0x26, 0x83, 0xb1, 0x3a, - 0x57, 0x2b, 0xa5, 0x0d, 0x54, 0x04, 0x0c, 0x47, 0x75, 0x36, 0x1b, 0x76, 0x27, 0xae, 0x0a, 0x08, - 0x11, 0xc9, 0x7b, 0x30, 0x7e, 0xbf, 0x52, 0x5e, 0x71, 0x7d, 0xbe, 0x7b, 0x8d, 0x44, 0x93, 0xff, - 0xa0, 0x51, 0xb7, 0x50, 0x13, 0x1f, 0x3f, 0x06, 0x14, 0x7c, 0xe3, 0x87, 0xb2, 0x30, 0xae, 0xe8, - 0xc9, 0xc8, 0x67, 0xc4, 0x03, 0x69, 0x46, 0xbb, 0x01, 0x28, 0x18, 0xac, 0x94, 0x2b, 0x55, 0x5a, - 0x6e, 0x83, 0x8a, 0xe7, 0xd2, 0x48, 0x81, 0x91, 0x1d, 0x44, 0x81, 0xf1, 0x36, 0x00, 0x9f, 0x03, - 0xf8, 0xc9, 0x8a, 0x38, 0xa1, 0xd8, 0x49, 0xa8, 0xe3, 0x12, 0x21, 0x93, 0x87, 0x30, 0xbb, 0xe9, - 0x75, 0xfd, 0xa0, 0x76, 0xe4, 0x07, 0xb4, 0xc5, 0xb8, 0x6d, 0xb8, 0x6e, 0x53, 0xcc, 0xbf, 0x57, - 0x4e, 0x8e, 0x8b, 0x97, 0xd1, 0xb8, 0xc3, 0xf2, 0xb1, 0x1c, 0x3f, 0xc0, 0xea, 0xb8, 0xae, 0xaa, - 0xd6, 0x48, 0x63, 0x60, 0x98, 0x30, 0xa1, 0x2a, 0x45, 0xd8, 0xc9, 0x22, 0x1e, 0x93, 0x84, 0xaa, - 0x5b, 0x39, 0x59, 0xc4, 0x57, 0x26, 0x1f, 0xb7, 0x74, 0x12, 0xe3, 0x33, 0xaa, 0x42, 0x6e, 0xd0, - 0x85, 0x6d, 0x7c, 0x7f, 0x26, 0xda, 0x46, 0x1e, 0xde, 0x24, 0xef, 0xc0, 0x08, 0x7f, 0xbc, 0x13, - 0x6f, 0x9c, 0xe7, 0xc2, 0x4b, 0xad, 0xfa, 0xb2, 0xc7, 0x35, 0xe1, 0x7f, 0xc0, 0x1f, 0xf8, 0x9f, - 0x33, 0x05, 0x49, 0xa8, 0x44, 0xd7, 0xf5, 0x69, 0x92, 0x3b, 0xaa, 0x8b, 0x6f, 0xa6, 0x29, 0xd1, - 0x8d, 0x1f, 0x1b, 0x86, 0x29, 0x1d, 0x4d, 0x7d, 0xe1, 0xcb, 0x0c, 0xf4, 0xc2, 0xf7, 0x05, 0xc8, - 0xb3, 0xfe, 0x70, 0xea, 0x54, 0x4a, 0x64, 0xaf, 0xe0, 0xd3, 0x82, 0x80, 0x69, 0x2f, 0xd7, 0xc0, - 0x87, 0x83, 0xdd, 0x71, 0xcd, 0x90, 0x8a, 0x2c, 0x2a, 0xcf, 0x50, 0xb9, 0x48, 0x48, 0x91, 0xcf, - 0x50, 0xea, 0x7a, 0x08, 0x1f, 0xa4, 0xde, 0x84, 0x11, 0x26, 0xdf, 0x87, 0x2a, 0x18, 0xfc, 0x4a, - 0x26, 0xfa, 0xc7, 0x4c, 0x54, 0x38, 0x12, 0xd9, 0x86, 0xfc, 0xaa, 0xed, 0x07, 0x35, 0x4a, 0xdb, - 0x03, 0xbc, 0xdd, 0x17, 0x45, 0x57, 0xcd, 0xe2, 0xc3, 0xb8, 0x4f, 0x69, 0x3b, 0xf6, 0xf8, 0x1a, - 0x32, 0x23, 0x5f, 0x05, 0x28, 0xbb, 0xed, 0xc0, 0x73, 0x9b, 0xab, 0xee, 0xde, 0xfc, 0x08, 0xde, - 0x7d, 0x2f, 0xc5, 0x06, 0x20, 0x42, 0xe0, 0xd7, 0xdf, 0x50, 0xc1, 0x53, 0xe7, 0x05, 0x56, 0xd3, - 0xdd, 0x53, 0xd7, 0x41, 0x84, 0x4f, 0xee, 0x40, 0x41, 0x2a, 0x16, 0xb6, 0x3a, 0x7b, 0x1e, 0x4e, - 0x90, 0xd1, 0x48, 0xf2, 0xa0, 0x8f, 0x03, 0xab, 0x2b, 0xe0, 0xea, 0x4e, 0x19, 0xa7, 0x21, 0x5f, - 0x81, 0x0b, 0x71, 0x98, 0x1c, 0xe5, 0x7c, 0x24, 0x93, 0xab, 0xec, 0x52, 0xe6, 0x7d, 0x2f, 0x16, - 0xe4, 0x2e, 0x4c, 0xb3, 0x0e, 0x59, 0xa3, 0xb6, 0xdf, 0xe5, 0x06, 0x56, 0x42, 0x35, 0xf3, 0xa2, - 0xd4, 0x44, 0xf1, 0x55, 0xd8, 0x74, 0xeb, 0x07, 0x0a, 0x92, 0x19, 0xa7, 0x32, 0x8e, 0xb3, 0x70, - 0x3e, 0x1d, 0x97, 0xfc, 0x55, 0x38, 0x27, 0xfa, 0xa5, 0x49, 0x3d, 0x05, 0x67, 0x00, 0x9b, 0x80, - 0x37, 0x45, 0x7f, 0xbf, 0x54, 0x0f, 0x19, 0x84, 0x1b, 0x07, 0x63, 0x11, 0x1b, 0xdc, 0xf4, 0x7a, - 0xc8, 0xd7, 0x61, 0x5c, 0xad, 0x36, 0x3b, 0xb8, 0x79, 0x45, 0x9f, 0xba, 0x54, 0x96, 0xc4, 0x86, - 0x69, 0x93, 0x7e, 0xa3, 0x4b, 0xfd, 0x40, 0x1a, 0x78, 0x88, 0xa3, 0xfb, 0x62, 0xa2, 0x16, 0x89, - 0x10, 0xea, 0x7f, 0x0a, 0x1e, 0xa7, 0xb4, 0xa4, 0x19, 0xde, 0xb7, 0x18, 0xfb, 0x38, 0x3f, 0xe3, - 0x3b, 0x59, 0xb8, 0xd0, 0x63, 0x5a, 0xb2, 0x9d, 0x0b, 0x05, 0x2b, 0x65, 0xe7, 0x8a, 0xc9, 0x53, - 0xdc, 0x3a, 0xec, 0x32, 0x64, 0x85, 0x28, 0x32, 0xb4, 0x54, 0x38, 0x39, 0x2e, 0x4e, 0x68, 0x2b, - 0x2e, 0x5b, 0xad, 0x90, 0x7b, 0x30, 0xc4, 0xba, 0x61, 0x00, 0x23, 0x07, 0xa9, 0xfd, 0x9b, 0x0a, - 0x1c, 0x75, 0xa1, 0x63, 0xdf, 0x20, 0x0f, 0xf2, 0x19, 0xc8, 0x6d, 0x6e, 0xae, 0xe2, 0x2a, 0xcf, - 0xe1, 0x2c, 0x9d, 0x0c, 0x82, 0xa6, 0xb6, 0xa9, 0x4c, 0x32, 0xda, 0xb0, 0x47, 0x4c, 0x86, 0x4e, - 0xbe, 0x14, 0x33, 0xbe, 0x7a, 0xbd, 0xff, 0x92, 0x1c, 0xdc, 0x16, 0xeb, 0x09, 0x4c, 0xa0, 0x8c, - 0x9f, 0xcf, 0x46, 0xbb, 0xed, 0x1d, 0xa7, 0x19, 0x50, 0x8f, 0x2c, 0xf0, 0xcd, 0x33, 0x12, 0xa3, - 0xcd, 0xf0, 0x37, 0x99, 0x8f, 0x76, 0x62, 0xce, 0x2a, 0xdc, 0x72, 0x5f, 0x57, 0xb6, 0xdc, 0x1c, - 0x6e, 0xb9, 0x53, 0x3d, 0x37, 0xd7, 0xd7, 0x53, 0x76, 0x10, 0xdc, 0x32, 0x53, 0x76, 0x89, 0x57, - 0x60, 0x72, 0xdd, 0x5d, 0x7e, 0x1c, 0x84, 0x88, 0x6c, 0xab, 0xcc, 0x9b, 0x3a, 0x90, 0x71, 0x7c, - 0xd0, 0x6c, 0x50, 0x6f, 0x73, 0xdf, 0x6e, 0x6b, 0x56, 0x06, 0x66, 0x02, 0xce, 0x70, 0xd7, 0xe9, - 0xa1, 0x8e, 0x3b, 0xca, 0x71, 0xe3, 0x70, 0xe3, 0xaf, 0x67, 0x65, 0x67, 0x3c, 0x5c, 0x7c, 0x46, - 0x5f, 0xb3, 0xdf, 0xd2, 0x5e, 0xb3, 0x67, 0x43, 0x3d, 0x7c, 0x68, 0x9a, 0xb1, 0x78, 0x8a, 0x45, - 0xc7, 0xdf, 0x1d, 0x81, 0x09, 0x15, 0x9d, 0xf5, 0x43, 0xa9, 0xd1, 0xf0, 0xd4, 0x7e, 0xb0, 0x1b, - 0x0d, 0xcf, 0x44, 0xa8, 0x66, 0xc0, 0x91, 0xeb, 0x6b, 0xc0, 0xf1, 0x35, 0x18, 0x2b, 0xb7, 0x1a, - 0xda, 0xb3, 0xb2, 0x91, 0xf2, 0x79, 0xd7, 0x43, 0x24, 0xbe, 0x16, 0x42, 0xf5, 0x72, 0xbd, 0xd5, - 0x48, 0x3e, 0x26, 0x47, 0x2c, 0x35, 0xdb, 0x8f, 0xe1, 0x27, 0xb1, 0xfd, 0xb8, 0x0d, 0x63, 0x5b, - 0x3e, 0xdd, 0xec, 0xb6, 0xdb, 0xb4, 0x89, 0xd3, 0x2a, 0xcf, 0x6f, 0x65, 0x5d, 0x9f, 0x5a, 0x01, - 0x42, 0xd5, 0x0f, 0x08, 0x51, 0xd5, 0x01, 0x1e, 0xed, 0x33, 0xc0, 0xb7, 0x20, 0xbf, 0x41, 0xa9, - 0x87, 0x7d, 0x3a, 0x1e, 0x09, 0xdf, 0x1d, 0x4a, 0x3d, 0x8b, 0x75, 0xac, 0x66, 0x13, 0x22, 0x10, - 0x35, 0x43, 0x92, 0x89, 0x01, 0x0d, 0x49, 0xc8, 0x4b, 0x30, 0xd1, 0xe9, 0xee, 0x34, 0x9d, 0x3a, - 0xf2, 0x15, 0x16, 0x28, 0xe6, 0x38, 0x87, 0x31, 0xb6, 0x3e, 0xf9, 0x12, 0x4c, 0xe2, 0x6d, 0x34, - 0x9c, 0x72, 0x53, 0xda, 0xfb, 0xab, 0x56, 0xc6, 0x65, 0xd2, 0x3a, 0x03, 0x59, 0x29, 0x86, 0x52, - 0x3a, 0x23, 0x72, 0x0f, 0x46, 0xf7, 0x9c, 0xc0, 0xda, 0xef, 0xee, 0xcc, 0x4f, 0x6b, 0x56, 0x46, - 0x77, 0x9d, 0x60, 0xa5, 0xbb, 0xc3, 0x87, 0x3c, 0x64, 0x8d, 0x3b, 0xde, 0x9e, 0x13, 0xec, 0x77, - 0x55, 0xe5, 0xf9, 0xc8, 0x1e, 0xe2, 0x2e, 0xd4, 0x60, 0x4a, 0x9f, 0x15, 0x4f, 0xe1, 0x49, 0x37, - 0x34, 0xb0, 0xc9, 0x17, 0xc6, 0xee, 0x0d, 0xe5, 0xa1, 0x30, 0xce, 0x4d, 0x6b, 0x4c, 0xd8, 0x08, - 0xfb, 0xc7, 0x24, 0xf7, 0xbb, 0x3b, 0xd4, 0x6b, 0xd3, 0x80, 0xfa, 0xe2, 0xea, 0xe7, 0x9b, 0x43, - 0xa5, 0x4e, 0xc7, 0x37, 0xfe, 0xd3, 0x2c, 0x8c, 0x96, 0xb6, 0x6b, 0xd5, 0xf6, 0xae, 0x8b, 0x0f, - 0xb3, 0xe1, 0x7b, 0x9c, 0xfa, 0x30, 0x1b, 0xbe, 0xc7, 0xa9, 0xaf, 0x70, 0x37, 0x52, 0x2e, 0xef, - 0x68, 0xbb, 0xad, 0x5c, 0xde, 0x35, 0xb5, 0x43, 0xf4, 0x34, 0x99, 0x1b, 0xe0, 0x69, 0x32, 0xd4, - 0x1e, 0x0f, 0x9d, 0xae, 0x3d, 0x7e, 0x07, 0xc6, 0xab, 0xed, 0x80, 0xee, 0x79, 0xd1, 0xaa, 0x09, - 0x15, 0x09, 0x21, 0x58, 0xbd, 0xd0, 0x29, 0xd8, 0x6c, 0x4a, 0x72, 0x8d, 0x75, 0xa8, 0xa9, 0xc6, - 0x29, 0xc9, 0x15, 0xdb, 0x31, 0x2d, 0x90, 0x44, 0x34, 0x2a, 0xb1, 0xf9, 0x26, 0xcd, 0x3f, 0xb8, - 0x08, 0x35, 0x15, 0x3d, 0xd9, 0xb0, 0x8e, 0x5d, 0x9a, 0x49, 0x37, 0xff, 0x30, 0xfe, 0x66, 0x06, - 0xe6, 0xd2, 0xa6, 0x11, 0x79, 0x1f, 0x26, 0x5c, 0x6f, 0xcf, 0x6e, 0x3b, 0xdf, 0xcb, 0x5b, 0xa4, - 0xa8, 0x2a, 0x55, 0xb8, 0xaa, 0xa0, 0x51, 0xe1, 0xac, 0x43, 0x94, 0x96, 0xeb, 0x9a, 0x95, 0xd4, - 0x0e, 0x51, 0xc0, 0xc6, 0x0f, 0x67, 0x61, 0xbc, 0xd4, 0xe9, 0x3c, 0xe3, 0xa6, 0x81, 0x9f, 0xd3, - 0x0e, 0x10, 0x79, 0x2f, 0x0f, 0xdb, 0x35, 0x90, 0x55, 0xe0, 0xaf, 0x66, 0x61, 0x3a, 0x46, 0xa1, - 0x7e, 0x7d, 0x66, 0x40, 0x83, 0xc0, 0xec, 0x80, 0x06, 0x81, 0xb9, 0xc1, 0x0c, 0x02, 0x87, 0x9e, - 0xe4, 0x50, 0x78, 0x0d, 0x72, 0xa5, 0x4e, 0x27, 0x6e, 0x58, 0xd0, 0xe9, 0x3c, 0xbc, 0xc5, 0x75, - 0x2b, 0x76, 0xa7, 0x63, 0x32, 0x0c, 0x6d, 0xa7, 0x1e, 0x19, 0x70, 0xa7, 0x36, 0xde, 0x84, 0x31, - 0xe4, 0x85, 0x66, 0x78, 0x97, 0x01, 0xb7, 0x18, 0x61, 0x81, 0xa7, 0xd5, 0x25, 0x36, 0x9f, 0xff, - 0x2f, 0x03, 0xc3, 0xf8, 0xfb, 0x19, 0x9d, 0x63, 0x8b, 0xda, 0x1c, 0x2b, 0x28, 0x73, 0x6c, 0x90, - 0xd9, 0xf5, 0x0f, 0x72, 0x00, 0xe5, 0x07, 0x66, 0x8d, 0xab, 0xe0, 0xc8, 0x1d, 0x98, 0xb6, 0x9b, - 0x4d, 0xf7, 0x90, 0x36, 0x2c, 0xd7, 0x73, 0xf6, 0x9c, 0x36, 0xef, 0x39, 0xf9, 0xda, 0xad, 0x17, - 0xa9, 0x6f, 0x60, 0xa2, 0xe8, 0x01, 0x2f, 0x51, 0xf9, 0xb4, 0x68, 0xb0, 0xef, 0x36, 0xa4, 0x32, - 0x41, 0xe3, 0x23, 0x8a, 0x52, 0xf8, 0xac, 0xf1, 0x12, 0x95, 0xcf, 0x3e, 0x2a, 0x47, 0xa4, 0x84, - 0xac, 0xf1, 0x11, 0x45, 0x29, 0x7c, 0xb8, 0x46, 0xc5, 0x27, 0xab, 0x30, 0x83, 0x10, 0xab, 0xee, - 0xd1, 0x06, 0x6d, 0x07, 0x8e, 0xdd, 0xf4, 0x85, 0xfa, 0x09, 0x15, 0x95, 0x89, 0x42, 0xf5, 0xfa, - 0x8d, 0x85, 0xe5, 0xa8, 0x8c, 0x5c, 0x87, 0xd1, 0x96, 0xfd, 0xd8, 0xb2, 0xf7, 0xb8, 0xdd, 0xc7, - 0x24, 0x57, 0x57, 0x08, 0x90, 0x7a, 0x8c, 0xb4, 0xec, 0xc7, 0xa5, 0x3d, 0xca, 0x5a, 0x41, 0x1f, - 0x77, 0x5c, 0x5f, 0x69, 0xc5, 0x48, 0xd4, 0x8a, 0x58, 0x91, 0xda, 0x0a, 0x51, 0x24, 0x5a, 0x61, - 0xfc, 0x4a, 0x06, 0x9e, 0xaf, 0xe2, 0x57, 0x04, 0x47, 0x65, 0xda, 0x0e, 0xa8, 0xb7, 0x41, 0xbd, - 0x96, 0x83, 0xaf, 0xe0, 0x35, 0x1a, 0x90, 0x97, 0x21, 0x57, 0x32, 0xd7, 0xc5, 0xfc, 0xe5, 0xfb, - 0xbd, 0x66, 0x93, 0xc0, 0x4a, 0x43, 0x8d, 0x56, 0xf6, 0x14, 0x55, 0x75, 0x09, 0x26, 0x4a, 0xbe, - 0xef, 0xec, 0xb5, 0x5b, 0xdc, 0x9f, 0x22, 0xa7, 0x59, 0x3d, 0x08, 0x78, 0xe2, 0x8d, 0x45, 0x25, - 0x31, 0xfe, 0xb3, 0x0c, 0xcc, 0x94, 0x3a, 0x1d, 0xfd, 0x93, 0x75, 0x8b, 0x9b, 0xcc, 0xe0, 0x16, - 0x37, 0x0e, 0x4c, 0x69, 0xcd, 0xe5, 0x53, 0x2a, 0x12, 0x7c, 0xfb, 0xf4, 0x0c, 0xff, 0xec, 0x4e, - 0x08, 0xb2, 0x7c, 0xfd, 0xb9, 0x38, 0xc6, 0xd8, 0xf8, 0x0f, 0x46, 0x71, 0x0f, 0x11, 0xbb, 0xad, - 0xb0, 0x09, 0xcd, 0xa4, 0xd8, 0x84, 0xbe, 0x0d, 0x8a, 0x84, 0xa3, 0x1e, 0x71, 0x8a, 0xac, 0xa8, - 0xea, 0x82, 0x22, 0x64, 0x72, 0x10, 0xb7, 0x0e, 0xcd, 0x61, 0x6b, 0x5e, 0x8e, 0x2f, 0xe0, 0xa7, - 0x62, 0x18, 0xba, 0x02, 0xa4, 0xda, 0xc6, 0x27, 0x6c, 0x5a, 0x3b, 0x70, 0x3a, 0x0f, 0xa9, 0xe7, - 0xec, 0x1e, 0x89, 0x05, 0x80, 0x9d, 0xef, 0x88, 0x52, 0xcb, 0x3f, 0x70, 0x3a, 0xd6, 0x23, 0x2c, - 0x37, 0x53, 0x68, 0xc8, 0x07, 0x30, 0x6a, 0xd2, 0x43, 0xcf, 0x09, 0xa4, 0xcd, 0xd3, 0x54, 0xa8, - 0xda, 0x44, 0x28, 0x5f, 0x0b, 0x1e, 0xff, 0xa1, 0xee, 0x8a, 0xa2, 0x9c, 0x2c, 0x72, 0x21, 0x85, - 0xdb, 0x36, 0x4d, 0x46, 0xad, 0x2d, 0x6d, 0xd7, 0x7a, 0xc9, 0x28, 0xe4, 0x1a, 0x0c, 0xa3, 0xa4, - 0x23, 0xee, 0x02, 0xe8, 0x2b, 0x84, 0xb2, 0xb3, 0x2a, 0x86, 0x21, 0x06, 0xb9, 0x04, 0x10, 0xbe, - 0x11, 0xfb, 0xf3, 0x79, 0x94, 0xd2, 0x15, 0x48, 0x5c, 0x4c, 0x1b, 0x3b, 0x93, 0x98, 0xb6, 0x0a, - 0x05, 0x93, 0xbb, 0x1d, 0x36, 0x4a, 0x1d, 0x7c, 0x88, 0xf4, 0xe7, 0x01, 0x57, 0xf2, 0xe5, 0x93, - 0xe3, 0xe2, 0x0b, 0xc2, 0x25, 0xb1, 0x61, 0xd9, 0x1d, 0xfe, 0x7e, 0xa9, 0x6d, 0x23, 0x71, 0x4a, - 0xf2, 0x36, 0x0c, 0xb1, 0xad, 0x57, 0xd8, 0x91, 0xca, 0x07, 0x9d, 0x68, 0x37, 0xe6, 0x8b, 0xb3, - 0xee, 0x6a, 0x7b, 0x02, 0x92, 0x10, 0x0b, 0xa6, 0xf4, 0xe9, 0x2e, 0x4c, 0x8a, 0xe6, 0xa3, 0xfe, - 0xd4, 0xcb, 0xc5, 0x2b, 0x8f, 0x80, 0x59, 0x75, 0x04, 0xaa, 0x2b, 0x20, 0xb6, 0x48, 0x97, 0x21, - 0xbf, 0x59, 0xde, 0xd8, 0x70, 0xbd, 0x80, 0x5f, 0x75, 0xa2, 0x93, 0x85, 0xc1, 0x4c, 0xbb, 0xbd, - 0x47, 0xf9, 0x59, 0x1c, 0xd4, 0x3b, 0x56, 0x87, 0xa1, 0xa9, 0x67, 0xb1, 0x24, 0xfd, 0xe4, 0x6c, - 0x48, 0x7f, 0x35, 0x0b, 0x2f, 0x87, 0x52, 0xd1, 0x03, 0xaf, 0x56, 0x5a, 0x5b, 0xad, 0x36, 0x36, - 0x84, 0x9a, 0x64, 0xc3, 0x73, 0x1f, 0x39, 0x0d, 0xea, 0x3d, 0xbc, 0x79, 0xca, 0x99, 0xbe, 0xca, - 0x97, 0x39, 0x7f, 0x0d, 0xcb, 0x6a, 0xd6, 0x76, 0x8a, 0xf0, 0x29, 0xb6, 0xa7, 0x4e, 0x27, 0xf1, - 0x38, 0xb6, 0xf2, 0x9c, 0x19, 0x31, 0x20, 0xdf, 0x9f, 0x81, 0xf3, 0xe9, 0x1f, 0x22, 0x54, 0x67, - 0x45, 0x79, 0x45, 0xef, 0xf1, 0xb5, 0x4b, 0xaf, 0x9d, 0x1c, 0x17, 0x5f, 0xf6, 0xed, 0x56, 0xd3, - 0x72, 0x1a, 0xbc, 0x36, 0xa7, 0x4e, 0xad, 0x8e, 0x40, 0xd0, 0xea, 0xed, 0x51, 0xd3, 0xe7, 0x41, - 0x1e, 0xed, 0xf3, 0x99, 0x25, 0x80, 0xbc, 0x7c, 0x70, 0x30, 0x7e, 0x23, 0x03, 0xca, 0x12, 0xcc, - 0x9b, 0xb4, 0xe1, 0x78, 0xb4, 0x1e, 0x88, 0xe3, 0x5d, 0xf8, 0x0a, 0x72, 0x58, 0xcc, 0xb8, 0x12, - 0x61, 0xe4, 0x7d, 0x18, 0x15, 0xc7, 0x90, 0xd8, 0x76, 0xe5, 0xd2, 0x15, 0x4f, 0x19, 0xdc, 0xa9, - 0x34, 0x71, 0x84, 0x49, 0x22, 0xb6, 0xeb, 0xdf, 0xdb, 0xde, 0x2c, 0x37, 0x6d, 0xa7, 0xe5, 0x8b, - 0xb3, 0x04, 0xbb, 0xf5, 0xa3, 0xc3, 0xc0, 0xaa, 0x23, 0x54, 0xdd, 0xf5, 0x43, 0x54, 0xe3, 0xae, - 0x7c, 0x49, 0x39, 0xc5, 0x42, 0xb8, 0x08, 0xc3, 0x0f, 0x23, 0x3d, 0xdd, 0xd2, 0xd8, 0xc9, 0x71, - 0x91, 0x4f, 0x17, 0x93, 0xc3, 0x0d, 0x0a, 0x63, 0xe1, 0xd4, 0x65, 0xbc, 0xd8, 0x0f, 0xe4, 0x35, - 0xc9, 0x79, 0xb1, 0x49, 0x6c, 0x22, 0x94, 0x89, 0x7a, 0xcb, 0xed, 0x06, 0x22, 0x64, 0x11, 0x01, - 0xbb, 0x87, 0xb6, 0x1b, 0x38, 0xd3, 0xd5, 0xd6, 0x09, 0x34, 0x45, 0xa0, 0xfa, 0xd1, 0x0c, 0x4c, - 0xe9, 0xd3, 0x96, 0x5c, 0x87, 0x11, 0xe1, 0x0e, 0x98, 0x41, 0xb5, 0x27, 0xe3, 0x36, 0xc2, 0x1d, - 0x01, 0x35, 0xf7, 0x3f, 0x81, 0xc5, 0xe4, 0x46, 0xc1, 0x41, 0x08, 0x4d, 0x28, 0x37, 0xd6, 0x39, - 0xc8, 0x94, 0x65, 0xc4, 0x60, 0x57, 0x59, 0xbf, 0xdb, 0x0c, 0xd4, 0x77, 0x4b, 0x0f, 0x21, 0xa6, - 0x28, 0x31, 0xca, 0x30, 0xc2, 0xb7, 0xd6, 0x98, 0x01, 0x64, 0xe6, 0x0c, 0x06, 0x90, 0xc6, 0x71, - 0x06, 0xa0, 0x56, 0x5b, 0xb9, 0x4f, 0x8f, 0x36, 0x6c, 0x07, 0xcf, 0x6f, 0x7e, 0x8c, 0xdd, 0x17, - 0x6b, 0x78, 0x42, 0x3c, 0xb4, 0xf3, 0x23, 0xef, 0x80, 0x1e, 0x69, 0x0f, 0xed, 0x12, 0x15, 0xcf, - 0x4a, 0xcf, 0x79, 0x64, 0x07, 0x94, 0x11, 0x66, 0x91, 0x90, 0x9f, 0x95, 0x1c, 0x1a, 0xa3, 0x54, - 0x90, 0xc9, 0x57, 0x61, 0x2a, 0xfa, 0x15, 0x9a, 0x0b, 0x4c, 0x85, 0xfb, 0x84, 0x5e, 0xb8, 0x74, - 0xe9, 0xe4, 0xb8, 0xb8, 0xa0, 0x70, 0x8d, 0x1b, 0x12, 0xc4, 0x98, 0x19, 0xbf, 0x94, 0x41, 0x23, - 0x19, 0xd9, 0xc0, 0x2b, 0x30, 0x14, 0x9a, 0x75, 0x4f, 0x88, 0x4d, 0x58, 0x7f, 0x12, 0xc5, 0x72, - 0x26, 0x6e, 0x45, 0x2d, 0xc1, 0xa3, 0x4b, 0x6f, 0x01, 0x2b, 0x25, 0x77, 0x61, 0x74, 0xa0, 0x6f, - 0xc6, 0x29, 0x96, 0xf2, 0xad, 0x92, 0x1a, 0x47, 0xe1, 0xde, 0xf6, 0xe6, 0x77, 0xef, 0x28, 0xfc, - 0x44, 0x16, 0xa6, 0x59, 0xbf, 0x96, 0xba, 0xc1, 0xbe, 0xeb, 0x39, 0xc1, 0xd1, 0x33, 0xab, 0x37, - 0x7e, 0x57, 0xbb, 0x92, 0x2d, 0xc8, 0xc3, 0x4c, 0x6d, 0xdb, 0x40, 0xea, 0xe3, 0x7f, 0x36, 0x0c, - 0xb3, 0x29, 0x54, 0xe4, 0x0d, 0xed, 0x69, 0x67, 0x5e, 0xba, 0xfb, 0x7f, 0xe7, 0xb8, 0x38, 0x21, - 0xd1, 0x37, 0x23, 0xf7, 0xff, 0x45, 0xdd, 0xe2, 0x8c, 0xf7, 0x14, 0xbe, 0xf4, 0xa8, 0x16, 0x67, - 0xba, 0x9d, 0xd9, 0x35, 0x18, 0x36, 0xdd, 0x26, 0x95, 0x56, 0x96, 0x28, 0x70, 0x79, 0x0c, 0xa0, - 0x59, 0x95, 0x30, 0x00, 0x59, 0x81, 0x51, 0xf6, 0xc7, 0x9a, 0xdd, 0x11, 0xef, 0xa5, 0x24, 0x54, - 0x0a, 0x20, 0xb4, 0xe3, 0xb4, 0xf7, 0x54, 0xbd, 0x40, 0x93, 0x5a, 0x2d, 0xbb, 0xa3, 0x49, 0x86, - 0x1c, 0x51, 0xd3, 0x2f, 0xe4, 0x7b, 0xeb, 0x17, 0x32, 0xa7, 0xea, 0x17, 0x76, 0x01, 0x6a, 0xce, - 0x5e, 0xdb, 0x69, 0xef, 0x95, 0x9a, 0x7b, 0x22, 0x68, 0xc2, 0xb5, 0xde, 0xa3, 0x70, 0x3d, 0x42, - 0xc6, 0x89, 0xfb, 0x3c, 0x1a, 0x35, 0x70, 0x98, 0x65, 0x37, 0xf7, 0x34, 0xe7, 0x2e, 0x85, 0x33, - 0x59, 0x07, 0x28, 0xd5, 0x03, 0xe7, 0x11, 0x9b, 0xc2, 0xbe, 0x10, 0xe3, 0xe4, 0x27, 0x97, 0x4b, - 0xf7, 0xe9, 0x11, 0x5e, 0x3d, 0xe4, 0xf3, 0xb0, 0x8d, 0xa8, 0x6c, 0x25, 0x68, 0x9e, 0x3b, 0x11, - 0x07, 0xd2, 0x81, 0x73, 0xa5, 0x46, 0xc3, 0x61, 0x6d, 0xb0, 0x9b, 0x9b, 0x3c, 0xdc, 0x05, 0xb2, - 0x9e, 0x48, 0x67, 0x7d, 0x4d, 0xbe, 0x84, 0xda, 0x21, 0x95, 0x25, 0xa3, 0x64, 0xc4, 0xaa, 0x49, - 0x67, 0x6c, 0xd4, 0x60, 0x4a, 0x6f, 0xbc, 0x1e, 0xec, 0x61, 0x02, 0xf2, 0x66, 0xad, 0x64, 0xd5, - 0x56, 0x4a, 0x37, 0x0b, 0x19, 0x52, 0x80, 0x09, 0xf1, 0x6b, 0xd1, 0x5a, 0x7c, 0xeb, 0x76, 0x21, - 0xab, 0x41, 0xde, 0xba, 0xb9, 0x58, 0xc8, 0x2d, 0x64, 0xe7, 0x33, 0x31, 0x3f, 0xcb, 0xd1, 0x42, - 0x9e, 0xab, 0x84, 0x8d, 0x5f, 0xcb, 0x40, 0x5e, 0x7e, 0x3b, 0xb9, 0x0d, 0xb9, 0x5a, 0x6d, 0x25, - 0xe6, 0x19, 0x19, 0x9d, 0x32, 0x7c, 0x3f, 0xf5, 0x7d, 0xd5, 0xfc, 0x9d, 0x11, 0x30, 0xba, 0xcd, - 0xd5, 0x9a, 0x90, 0x41, 0x24, 0x5d, 0xb4, 0x79, 0x73, 0xba, 0x14, 0x77, 0xb1, 0xdb, 0x90, 0xbb, - 0xb7, 0xbd, 0x29, 0x2e, 0x59, 0x92, 0x2e, 0xda, 0x4f, 0x39, 0xdd, 0x47, 0x87, 0xea, 0x2e, 0xcf, - 0x08, 0x0c, 0x13, 0xc6, 0x95, 0x89, 0xcc, 0x0f, 0xdd, 0x96, 0x1b, 0x46, 0x38, 0x10, 0x87, 0x2e, - 0x83, 0x98, 0xa2, 0x84, 0x89, 0x22, 0xab, 0x6e, 0xdd, 0x6e, 0x8a, 0xd3, 0x1b, 0x45, 0x91, 0x26, - 0x03, 0x98, 0x1c, 0x6e, 0xfc, 0x76, 0x06, 0x0a, 0x28, 0xb0, 0xa1, 0xf9, 0xba, 0x7b, 0x40, 0xdb, - 0x0f, 0x6f, 0x92, 0x37, 0xe5, 0x92, 0xcb, 0x84, 0x8a, 0xae, 0x61, 0x5c, 0x72, 0xb1, 0xb7, 0x40, - 0xb1, 0xec, 0x94, 0x20, 0x12, 0xd9, 0xc1, 0x9d, 0xcf, 0x4f, 0x09, 0x22, 0x51, 0x84, 0x61, 0xfc, - 0x1c, 0xb1, 0x39, 0xe2, 0x97, 0x07, 0x0c, 0x60, 0x72, 0xb8, 0xb2, 0x37, 0xfd, 0x54, 0x36, 0xd1, - 0x86, 0xc5, 0xef, 0x2a, 0x07, 0x6e, 0xbd, 0x71, 0x03, 0xed, 0xd7, 0x1f, 0xc2, 0x5c, 0xbc, 0x4b, - 0x50, 0x09, 0x59, 0x82, 0x69, 0x1d, 0x2e, 0xf5, 0x91, 0x17, 0x52, 0xeb, 0x7a, 0xb8, 0x68, 0xc6, - 0xf1, 0x8d, 0xff, 0x3d, 0x03, 0x63, 0xf8, 0xa7, 0xd9, 0x6d, 0xa2, 0x19, 0x61, 0x69, 0xbb, 0x26, - 0x54, 0x23, 0xaa, 0x30, 0x67, 0x1f, 0xfa, 0x96, 0xd0, 0xa3, 0x68, 0x7b, 0x4c, 0x88, 0x2c, 0x48, - 0xf9, 0xfb, 0x86, 0x54, 0xca, 0x85, 0xa4, 0xfc, 0x21, 0xc4, 0x8f, 0x91, 0x0a, 0x64, 0x34, 0x3e, - 0xde, 0xae, 0xb1, 0xe9, 0xa7, 0xda, 0xf5, 0x20, 0x9d, 0xdb, 0xd4, 0x8d, 0x8f, 0x39, 0x1a, 0x9a, - 0xf5, 0x6c, 0xd7, 0x4a, 0xe6, 0xba, 0x66, 0xd6, 0xc3, 0xbe, 0x51, 0xd3, 0x4b, 0x09, 0x24, 0xe3, - 0xe7, 0xc7, 0xe3, 0x1d, 0x28, 0x0e, 0xbc, 0x33, 0xae, 0x8d, 0x77, 0x60, 0xb8, 0xd4, 0x6c, 0xba, - 0x87, 0x62, 0x97, 0x90, 0x37, 0xd7, 0xb0, 0xff, 0xf8, 0x79, 0x86, 0x6a, 0x3d, 0xcd, 0x29, 0x95, - 0x01, 0x48, 0x19, 0xc6, 0x4a, 0xdb, 0xb5, 0x6a, 0xb5, 0xb2, 0xb9, 0xc9, 0x1d, 0xf0, 0x72, 0x4b, - 0xaf, 0xca, 0xfe, 0x71, 0x9c, 0x86, 0x15, 0xb7, 0x57, 0x88, 0xe4, 0xf7, 0x88, 0x8e, 0xbc, 0x07, - 0x70, 0xcf, 0x75, 0xda, 0x5c, 0x8d, 0x29, 0x1a, 0xcf, 0x6e, 0xe0, 0xe3, 0x1f, 0xb9, 0x4e, 0x5b, - 0xe8, 0x3d, 0xd9, 0xb7, 0x47, 0x48, 0xa6, 0xf2, 0x37, 0xeb, 0xe9, 0x25, 0x97, 0x9b, 0x06, 0x0e, - 0x47, 0x3d, 0xbd, 0xe3, 0x26, 0xf4, 0x6d, 0x12, 0x8d, 0xb4, 0x60, 0xba, 0xd6, 0xdd, 0xdb, 0xa3, - 0x6c, 0x67, 0x17, 0xfa, 0xa4, 0x11, 0x71, 0x95, 0x0e, 0xc3, 0x1e, 0xf1, 0xfb, 0x08, 0xbb, 0x0c, - 0xf9, 0x4b, 0x6f, 0xb0, 0x89, 0xfc, 0xed, 0xe3, 0xa2, 0xb0, 0x83, 0x60, 0xa2, 0x9a, 0x2f, 0xe9, - 0x93, 0xda, 0xa4, 0x38, 0x6f, 0xf2, 0x00, 0x46, 0xf8, 0x9b, 0x91, 0x70, 0x28, 0x7b, 0xa9, 0xcf, - 0xa2, 0xe1, 0x88, 0xbd, 0x5e, 0x25, 0x79, 0x29, 0xd9, 0x86, 0x7c, 0xd9, 0xf1, 0xea, 0x4d, 0x5a, - 0xae, 0x8a, 0xb3, 0xff, 0xe5, 0x3e, 0x2c, 0x25, 0x2a, 0xef, 0x97, 0x3a, 0xfe, 0xaa, 0x3b, 0xaa, - 0x2c, 0x20, 0x31, 0xc8, 0xdf, 0xcc, 0xc0, 0xf3, 0xe1, 0xd7, 0x97, 0xf6, 0x68, 0x3b, 0x58, 0xb3, - 0x83, 0xfa, 0x3e, 0xf5, 0x44, 0x2f, 0x8d, 0xf5, 0xeb, 0xa5, 0xcf, 0x27, 0x7a, 0xe9, 0x6a, 0xd4, - 0x4b, 0x36, 0x63, 0x66, 0xb5, 0x38, 0xb7, 0x64, 0x9f, 0xf5, 0xab, 0x95, 0x58, 0x00, 0xd1, 0x6b, - 0xa8, 0x70, 0x48, 0x7e, 0xb5, 0x4f, 0x83, 0x23, 0x64, 0xe1, 0x48, 0x14, 0xfe, 0xd6, 0x2c, 0x61, - 0x43, 0x28, 0xb9, 0x2f, 0xbd, 0x37, 0xb9, 0x54, 0x72, 0xb9, 0x0f, 0x6f, 0xee, 0xd1, 0x39, 0xdb, - 0xc7, 0x4f, 0x9b, 0x8f, 0xf6, 0xaa, 0xbd, 0x23, 0x04, 0x91, 0x53, 0x46, 0x7b, 0xd5, 0x8e, 0x46, - 0xbb, 0x69, 0xc7, 0x47, 0x7b, 0xd5, 0xde, 0x21, 0x65, 0xee, 0x72, 0xce, 0xfd, 0x93, 0x2f, 0xf5, - 0xe3, 0x56, 0xde, 0xe0, 0x27, 0x73, 0x8a, 0xeb, 0xf9, 0x97, 0x61, 0xac, 0xd6, 0xb1, 0xeb, 0xb4, - 0xe9, 0xec, 0x06, 0xe2, 0xa9, 0xfd, 0x95, 0x3e, 0xac, 0x42, 0x5c, 0xf1, 0xb4, 0x2a, 0x7f, 0xaa, - 0xd7, 0xa4, 0x10, 0x87, 0x7d, 0xe1, 0xe6, 0xc6, 0x9a, 0x78, 0x6d, 0xef, 0xf7, 0x85, 0x9b, 0x1b, - 0x6b, 0x42, 0xe6, 0xe8, 0xb4, 0x34, 0x99, 0x63, 0x63, 0x8d, 0x74, 0x60, 0x6a, 0x93, 0x7a, 0x9e, - 0xbd, 0xeb, 0x7a, 0x2d, 0xae, 0xbf, 0xe4, 0x3e, 0x6f, 0xd7, 0xfa, 0xf1, 0xd3, 0x08, 0xb8, 0xda, - 0x2e, 0x90, 0x30, 0x2b, 0xae, 0xf4, 0x8c, 0xf1, 0x67, 0x7d, 0xb2, 0xe4, 0x04, 0x3b, 0xdd, 0xfa, - 0x01, 0x0d, 0xe6, 0x67, 0x4e, 0xed, 0x93, 0x10, 0x97, 0xf7, 0xc9, 0x8e, 0xfc, 0xa9, 0xf6, 0x49, - 0x88, 0x63, 0xfc, 0xe3, 0x1c, 0x5c, 0xe8, 0xd1, 0x05, 0x64, 0x5d, 0x6e, 0xb9, 0x19, 0x4d, 0x8b, - 0xdd, 0x03, 0xfd, 0xfa, 0xa9, 0xbb, 0xf0, 0x2a, 0x14, 0x96, 0xef, 0xa3, 0xac, 0xce, 0x1f, 0x72, - 0xca, 0x25, 0x79, 0x58, 0xa1, 0xa6, 0x95, 0x1e, 0xa0, 0x8d, 0xb0, 0x7c, 0x00, 0xaa, 0x6b, 0xce, - 0xf0, 0x09, 0xca, 0x85, 0xbf, 0x9e, 0x85, 0x21, 0x3c, 0x38, 0x63, 0x21, 0xc0, 0x32, 0x67, 0x0a, - 0x01, 0xf6, 0x05, 0x98, 0x58, 0xbe, 0xcf, 0x6f, 0xd2, 0x2b, 0xb6, 0xbf, 0x2f, 0xb6, 0x75, 0x34, - 0xe4, 0xa0, 0x07, 0x96, 0xb8, 0x78, 0xef, 0xdb, 0x9a, 0xcc, 0xaa, 0x51, 0x90, 0x2d, 0x98, 0xe5, - 0xdf, 0xe6, 0xec, 0x3a, 0x75, 0x1e, 0x49, 0xc8, 0xb1, 0x9b, 0x62, 0x8f, 0x7f, 0xf9, 0xe4, 0xb8, - 0x58, 0xa4, 0x07, 0x68, 0xfd, 0x2c, 0xca, 0x2d, 0x1f, 0x11, 0x54, 0x33, 0xe8, 0x14, 0x7a, 0x35, - 0xbc, 0x89, 0x39, 0x86, 0x15, 0xb2, 0xda, 0x58, 0xdd, 0x0c, 0x97, 0x23, 0x19, 0x7f, 0x32, 0x0c, - 0x0b, 0xbd, 0xb7, 0x67, 0xf2, 0x45, 0x7d, 0x00, 0xaf, 0x9c, 0xba, 0xa1, 0x9f, 0x3e, 0x86, 0x5f, - 0x82, 0xb9, 0xe5, 0x76, 0x40, 0xbd, 0x8e, 0xe7, 0xc8, 0x80, 0x36, 0x2b, 0xae, 0x2f, 0xad, 0xcd, - 0xd1, 0xec, 0x9b, 0x86, 0xe5, 0x42, 0xb7, 0x8a, 0xb6, 0xef, 0x0a, 0xab, 0x54, 0x0e, 0x64, 0x19, - 0xa6, 0x14, 0x78, 0xb3, 0xbb, 0xa7, 0xbe, 0x4e, 0xa9, 0x3c, 0x9b, 0x5d, 0xd5, 0x14, 0x37, 0x46, - 0x84, 0x16, 0xed, 0xec, 0xca, 0x58, 0xbf, 0xb7, 0x7d, 0xbf, 0x26, 0x86, 0x93, 0x5b, 0xb4, 0x23, - 0xd4, 0xfa, 0xe8, 0xf0, 0x40, 0xdb, 0x5f, 0x23, 0xe4, 0x85, 0x5f, 0xca, 0x89, 0x19, 0xf5, 0x32, - 0xe4, 0x6a, 0xdd, 0x1d, 0xf5, 0xcd, 0xcd, 0xd7, 0x0e, 0x38, 0x56, 0x4a, 0x3e, 0x07, 0x60, 0xd2, - 0x8e, 0xeb, 0x3b, 0x81, 0xeb, 0x1d, 0xa9, 0x2e, 0x95, 0x5e, 0x08, 0xd5, 0xbd, 0x3e, 0x24, 0x94, - 0xac, 0xc0, 0x74, 0xf4, 0xeb, 0xc1, 0x61, 0x5b, 0xe8, 0x92, 0xc7, 0xb8, 0x76, 0x25, 0x22, 0xb7, - 0x5c, 0x56, 0xa6, 0x1e, 0xd9, 0x31, 0x32, 0xb2, 0x08, 0xf9, 0x6d, 0xd7, 0x3b, 0xd8, 0x65, 0x63, - 0x3c, 0x14, 0x09, 0x15, 0x87, 0x02, 0xa6, 0x1e, 0x9e, 0x12, 0x8f, 0x2d, 0x97, 0xe5, 0xf6, 0x23, - 0xc7, 0x73, 0xf1, 0x45, 0x4f, 0xb5, 0x69, 0xa1, 0x11, 0x58, 0x73, 0x66, 0x8f, 0xc0, 0xe4, 0x1a, - 0x0c, 0x97, 0xea, 0x81, 0xeb, 0x09, 0x83, 0x16, 0x3e, 0x53, 0x18, 0x40, 0x9b, 0x29, 0x0c, 0xc0, - 0x3a, 0xd1, 0xa4, 0xbb, 0xe2, 0x75, 0x07, 0x3b, 0xd1, 0xa3, 0xbb, 0x9a, 0xa7, 0x3e, 0xdd, 0x65, - 0x42, 0x91, 0x49, 0x77, 0x51, 0xf1, 0xa1, 0x05, 0xb8, 0xdb, 0x4d, 0xa8, 0xcc, 0x04, 0x9a, 0xf1, - 0xbb, 0x63, 0x3d, 0xa7, 0x3c, 0x3b, 0x85, 0xce, 0x36, 0xe5, 0x57, 0xed, 0x01, 0xa6, 0xfc, 0x1b, - 0xa1, 0x2f, 0x89, 0x1a, 0x9e, 0x02, 0x21, 0xea, 0x31, 0xc8, 0x71, 0x16, 0x7e, 0x39, 0x7f, 0x96, - 0x49, 0x24, 0x3a, 0x29, 0x3b, 0x68, 0x27, 0xe5, 0x06, 0xea, 0x24, 0xb2, 0x04, 0x93, 0x61, 0x88, - 0xc4, 0x0d, 0x3b, 0xd0, 0xb6, 0xb5, 0x30, 0xae, 0xa5, 0xd5, 0xb1, 0x03, 0x75, 0x5b, 0xd3, 0x49, - 0xc8, 0xbb, 0x30, 0x2e, 0x1c, 0xaa, 0x90, 0xc3, 0x70, 0x64, 0x29, 0x24, 0xbd, 0xaf, 0x62, 0xf4, - 0x2a, 0x3a, 0x5b, 0xcd, 0x1b, 0x4e, 0x87, 0x36, 0x9d, 0x36, 0xad, 0xe1, 0x63, 0x85, 0x98, 0x31, - 0xfc, 0xd1, 0x56, 0x94, 0x58, 0xfc, 0x1d, 0x43, 0xd3, 0x1f, 0x6a, 0x44, 0xf1, 0xc9, 0x3a, 0x7a, - 0xa6, 0xc9, 0xca, 0xed, 0x14, 0xbd, 0x55, 0x77, 0xcf, 0x91, 0x36, 0xf4, 0xd2, 0x4e, 0xd1, 0xb3, - 0x9a, 0x0c, 0x1a, 0xb3, 0x53, 0xe4, 0xa8, 0xec, 0x86, 0xc3, 0x7e, 0x54, 0x2b, 0xe2, 0x25, 0x11, - 0x6f, 0x38, 0x48, 0xa4, 0x3b, 0x2e, 0x70, 0x24, 0x59, 0xcd, 0x72, 0xcb, 0x76, 0x9a, 0x22, 0x0a, - 0x41, 0x54, 0x0d, 0x65, 0xd0, 0x78, 0x35, 0x88, 0x4a, 0xea, 0x30, 0x61, 0xd2, 0xdd, 0x0d, 0xcf, - 0x0d, 0x68, 0x3d, 0xa0, 0x0d, 0x21, 0xd5, 0xc9, 0x8b, 0xcd, 0x92, 0xeb, 0x72, 0x89, 0x15, 0x6d, - 0xe3, 0x33, 0xdf, 0x3e, 0x2e, 0x02, 0x03, 0x71, 0xaf, 0x98, 0x93, 0xe3, 0xe2, 0x05, 0x36, 0xfe, - 0x1d, 0x49, 0xac, 0x9e, 0x4e, 0x2a, 0x53, 0xf2, 0x4d, 0xb6, 0x5f, 0x87, 0x5d, 0x12, 0x55, 0x36, - 0xd1, 0xa3, 0xb2, 0xb7, 0x52, 0x2b, 0x2b, 0x2a, 0xbd, 0x9d, 0x5a, 0x69, 0x6a, 0x25, 0xe4, 0x3d, - 0x18, 0x2f, 0x57, 0xcb, 0x6e, 0x7b, 0xd7, 0xd9, 0xab, 0xad, 0x94, 0x50, 0x34, 0x14, 0x1e, 0x51, - 0x75, 0xc7, 0xaa, 0x23, 0xdc, 0xf2, 0xf7, 0x6d, 0xcd, 0x31, 0x36, 0xc2, 0x27, 0x77, 0x61, 0x4a, - 0xfe, 0x34, 0xe9, 0xee, 0x96, 0x59, 0x45, 0x89, 0x50, 0xba, 0xa1, 0x85, 0x1c, 0x58, 0x47, 0x74, - 0x3d, 0xf5, 0xa6, 0x10, 0x23, 0x63, 0x93, 0xb1, 0x42, 0x3b, 0x4d, 0xf7, 0x88, 0x7d, 0xde, 0xa6, - 0x43, 0x3d, 0x94, 0x01, 0xc5, 0x64, 0x6c, 0x84, 0x25, 0x56, 0xe0, 0xe8, 0xef, 0xa7, 0x3a, 0x11, - 0x59, 0x87, 0x19, 0x31, 0xc5, 0x1f, 0x3a, 0xbe, 0xb3, 0xe3, 0x34, 0x9d, 0xe0, 0x08, 0xa5, 0x3f, - 0x21, 0xc0, 0xc8, 0x75, 0xf1, 0x28, 0x2c, 0x55, 0x98, 0x25, 0x49, 0x8d, 0x5f, 0xcb, 0xc2, 0x0b, - 0xfd, 0x6e, 0x42, 0xa4, 0xa6, 0x6f, 0x66, 0x57, 0x07, 0xb8, 0x3d, 0x9d, 0xbe, 0x9d, 0x2d, 0xc3, - 0xd4, 0x03, 0xc5, 0xa4, 0x2f, 0x34, 0xb1, 0xc4, 0xce, 0x50, 0x8d, 0xfd, 0xf4, 0xd9, 0x1e, 0x23, - 0x5a, 0x78, 0x24, 0xb6, 0xb9, 0x8f, 0xeb, 0xa2, 0x79, 0x1b, 0xc6, 0xca, 0x6e, 0x3b, 0xa0, 0x8f, - 0x83, 0x58, 0x40, 0x02, 0x0e, 0x8c, 0xbb, 0xa7, 0x4a, 0x54, 0xe3, 0xdf, 0x64, 0xe1, 0xc5, 0xbe, - 0x57, 0x01, 0xb2, 0xa9, 0xf7, 0xda, 0xb5, 0x41, 0xee, 0x0f, 0xa7, 0x77, 0xdb, 0x62, 0xc2, 0xee, - 0xee, 0x54, 0x0f, 0xa8, 0x85, 0xff, 0x21, 0x23, 0x3a, 0xe9, 0xd3, 0x30, 0x8a, 0x55, 0x85, 0x5d, - 0xc4, 0xb5, 0x64, 0xb8, 0x0b, 0x3b, 0xba, 0x96, 0x8c, 0xa3, 0x91, 0x5b, 0x90, 0x2f, 0xdb, 0xcd, - 0xa6, 0x12, 0xae, 0x01, 0xa5, 0xf9, 0x3a, 0xc2, 0x62, 0xc6, 0xa3, 0x12, 0x91, 0xc9, 0x3e, 0xfc, - 0x6f, 0xe5, 0xac, 0xc0, 0xcd, 0x52, 0x90, 0xc5, 0x8e, 0x0b, 0x05, 0x19, 0x83, 0xbc, 0xd6, 0xdd, - 0xd0, 0x21, 0x9c, 0x07, 0x79, 0x65, 0x00, 0x2d, 0xc8, 0x2b, 0x03, 0x18, 0xbf, 0x9e, 0x83, 0x4b, - 0xfd, 0xef, 0xb3, 0x64, 0x4b, 0x1f, 0x82, 0xd7, 0x07, 0xba, 0x05, 0x9f, 0x3e, 0x06, 0x32, 0x64, - 0x32, 0xef, 0x90, 0xab, 0x49, 0xf7, 0x97, 0xef, 0x1c, 0x17, 0x15, 0x8b, 0xe4, 0x7b, 0xae, 0xd3, - 0x56, 0xde, 0x4c, 0xbe, 0xa1, 0x49, 0x86, 0xfc, 0xf5, 0xfe, 0xf6, 0x60, 0x5f, 0x16, 0xd1, 0xf1, - 0x7d, 0x65, 0x50, 0x89, 0xf2, 0xf3, 0x50, 0x88, 0x93, 0x92, 0x2b, 0x30, 0x84, 0x1f, 0xa0, 0xf8, - 0xf0, 0xc4, 0x38, 0x60, 0xf9, 0xc2, 0x9a, 0x98, 0x3b, 0x18, 0xc1, 0x02, 0xed, 0x01, 0x74, 0xdd, - 0xa0, 0x88, 0x60, 0xc1, 0xcd, 0x09, 0x92, 0xfa, 0xc1, 0x18, 0x91, 0xf1, 0x67, 0x19, 0xb8, 0xd8, - 0x53, 0x53, 0x40, 0x36, 0xf4, 0x01, 0x7b, 0xf5, 0x34, 0xd5, 0xc2, 0xa9, 0x63, 0xb5, 0xf0, 0x63, - 0x72, 0xee, 0xbf, 0x0f, 0x13, 0xb5, 0xee, 0x4e, 0xfc, 0x7e, 0xc6, 0xe3, 0xcb, 0x28, 0x70, 0xf5, - 0x04, 0x53, 0xf1, 0x59, 0xfb, 0xa5, 0xc1, 0x83, 0x30, 0x00, 0x52, 0xac, 0x0e, 0x43, 0x17, 0xeb, - 0x64, 0x04, 0x0f, 0x9d, 0xc8, 0xf8, 0xd5, 0x6c, 0xfa, 0x45, 0xf7, 0x6e, 0x79, 0xe3, 0x2c, 0x17, - 0xdd, 0xbb, 0xe5, 0x8d, 0xd3, 0xdb, 0xfe, 0x4f, 0x64, 0xdb, 0xf1, 0x61, 0x56, 0xec, 0x78, 0x52, - 0xd1, 0x29, 0x1e, 0x66, 0xe5, 0xee, 0xe8, 0xeb, 0x0f, 0xb3, 0x12, 0x99, 0xbc, 0x05, 0x63, 0xab, - 0x2e, 0x0f, 0xae, 0x21, 0x5b, 0xcc, 0x7d, 0x90, 0x25, 0x50, 0xdd, 0x1e, 0x43, 0x4c, 0x76, 0xb7, - 0xd0, 0x07, 0x5e, 0x1a, 0x57, 0xe2, 0xdd, 0x22, 0x36, 0x5d, 0x74, 0x75, 0xa0, 0x4e, 0x66, 0xfc, - 0x27, 0xc3, 0x60, 0x9c, 0xae, 0xcc, 0x20, 0x1f, 0xea, 0x7d, 0x77, 0x7d, 0x60, 0x35, 0xc8, 0x40, - 0x5b, 0x6e, 0xa9, 0xdb, 0x70, 0x68, 0xbb, 0xae, 0x47, 0xc6, 0x10, 0x30, 0x75, 0x0b, 0x94, 0x78, - 0x1f, 0xc7, 0x51, 0x75, 0xe1, 0xbf, 0xcd, 0x45, 0x4b, 0x2d, 0x76, 0x34, 0x66, 0x3e, 0xc6, 0xd1, - 0x48, 0xee, 0x43, 0x41, 0x85, 0x28, 0x2f, 0xb4, 0x28, 0xb9, 0x68, 0x8c, 0x62, 0x1f, 0x95, 0x20, - 0xd4, 0xcf, 0xd7, 0xdc, 0xe0, 0xe7, 0x6b, 0x24, 0xbe, 0x63, 0xfd, 0x43, 0x49, 0xf1, 0x3d, 0xee, - 0x8c, 0xae, 0xa0, 0xcb, 0x48, 0x1a, 0xbe, 0x38, 0xb4, 0x86, 0xf5, 0x48, 0x1a, 0x29, 0x07, 0x97, - 0x8a, 0x2e, 0x83, 0x81, 0xe0, 0x4f, 0xc5, 0x17, 0x3e, 0x0c, 0x06, 0xc2, 0xe9, 0xd3, 0x82, 0x81, - 0x84, 0x24, 0xec, 0x00, 0x34, 0xbb, 0x6d, 0x1e, 0x4d, 0x7c, 0x34, 0x3a, 0x00, 0xbd, 0x6e, 0xdb, - 0x8a, 0x47, 0x14, 0x0f, 0x11, 0x8d, 0x7f, 0x34, 0x94, 0x2e, 0x1c, 0x84, 0xfa, 0xae, 0xb3, 0x08, - 0x07, 0x21, 0xd1, 0x27, 0x33, 0x53, 0xb7, 0x60, 0x56, 0xda, 0xe7, 0x49, 0x43, 0xaf, 0x2d, 0x73, - 0x55, 0x0c, 0x31, 0xea, 0x8d, 0x42, 0xcb, 0x3e, 0x69, 0x2c, 0x66, 0x75, 0x3d, 0x4d, 0x6f, 0x94, - 0x42, 0xbf, 0xf0, 0x1b, 0x52, 0x2d, 0xa6, 0x0e, 0xc2, 0xd6, 0x56, 0x38, 0x97, 0x63, 0x83, 0xd0, - 0xed, 0x6a, 0xc3, 0xa8, 0x93, 0xf0, 0xbd, 0x57, 0xaa, 0x1c, 0x90, 0x89, 0x22, 0x2b, 0x2a, 0x8a, - 0x8a, 0x18, 0x97, 0x18, 0x11, 0xd9, 0x83, 0x8b, 0x91, 0x28, 0xad, 0xdc, 0x14, 0x90, 0x23, 0x6f, - 0xf0, 0xb5, 0x93, 0xe3, 0xe2, 0xab, 0x8a, 0x28, 0xae, 0x5e, 0x38, 0x62, 0xdc, 0x7b, 0xf3, 0x62, - 0xfb, 0xed, 0x92, 0x67, 0xb7, 0xeb, 0xfb, 0xca, 0x9c, 0xc7, 0xfd, 0x76, 0x07, 0xa1, 0x89, 0x70, - 0x06, 0x11, 0xb2, 0xf1, 0x63, 0x59, 0x98, 0xe2, 0x67, 0x35, 0x7f, 0x9d, 0x7b, 0x66, 0x5f, 0x3e, - 0xdf, 0xd1, 0x5e, 0x3e, 0x65, 0xe4, 0x3d, 0xb5, 0x69, 0x03, 0xbd, 0x7b, 0xee, 0x03, 0x49, 0xd2, - 0x10, 0x13, 0x26, 0x54, 0x68, 0xff, 0x27, 0xcf, 0x9b, 0x51, 0x90, 0x46, 0x21, 0x2a, 0xe1, 0xbb, - 0xb3, 0x6f, 0x6a, 0x3c, 0x8c, 0x1f, 0xcd, 0xc2, 0xa4, 0x62, 0xa7, 0xf2, 0xcc, 0x76, 0xfc, 0xe7, - 0xb5, 0x8e, 0x9f, 0x0f, 0x3d, 0x04, 0xc3, 0x96, 0x0d, 0xd4, 0xef, 0x5d, 0x98, 0x49, 0x90, 0xc4, - 0xcd, 0x7d, 0x32, 0x83, 0x98, 0xfb, 0xbc, 0x91, 0x8c, 0xf8, 0xc6, 0x13, 0x27, 0x84, 0xf1, 0x7f, - 0xd4, 0x10, 0x73, 0x3f, 0x91, 0x85, 0x39, 0xf1, 0x0b, 0x43, 0xa4, 0x72, 0x61, 0xf5, 0x99, 0x1d, - 0x8b, 0x92, 0x36, 0x16, 0x45, 0x7d, 0x2c, 0x94, 0x06, 0xf6, 0x1e, 0x12, 0xe3, 0x07, 0x01, 0xe6, - 0x7b, 0x11, 0x0c, 0xec, 0x88, 0x1f, 0xb9, 0x26, 0x66, 0x07, 0x70, 0x4d, 0x5c, 0x85, 0x02, 0x56, - 0x25, 0x82, 0x20, 0xfa, 0x5b, 0x66, 0x55, 0x74, 0x12, 0xea, 0x17, 0x78, 0x1c, 0x5b, 0x11, 0x94, - 0xd1, 0x8f, 0xe9, 0x3c, 0x12, 0x94, 0xe4, 0x97, 0x32, 0x30, 0x85, 0xc0, 0xe5, 0x47, 0xb4, 0x1d, - 0x20, 0xb3, 0x21, 0xe1, 0xb3, 0x16, 0x3e, 0x8c, 0xd6, 0x02, 0xcf, 0x69, 0xef, 0x89, 0x97, 0xd1, - 0x1d, 0xf1, 0x32, 0xfa, 0x2e, 0x7f, 0xd1, 0xbd, 0x5e, 0x77, 0x5b, 0x37, 0xf6, 0x3c, 0xfb, 0x91, - 0xc3, 0x4d, 0xb0, 0xec, 0xe6, 0x8d, 0x28, 0xdf, 0x4f, 0xc7, 0x89, 0x65, 0xe2, 0x11, 0xac, 0xf0, - 0xd5, 0x99, 0x7f, 0x28, 0xc5, 0x6a, 0xe3, 0xaa, 0x19, 0xfd, 0x8b, 0xc8, 0xf7, 0xc0, 0x05, 0x1e, - 0x9a, 0x8c, 0xdd, 0xf0, 0x9d, 0x76, 0xd7, 0xed, 0xfa, 0x4b, 0x76, 0xfd, 0x80, 0x89, 0xf9, 0xdc, - 0xb3, 0x18, 0x5b, 0x5e, 0x0f, 0x0b, 0xad, 0x1d, 0x5e, 0xaa, 0xc5, 0xbc, 0x48, 0x67, 0x40, 0x56, - 0x60, 0x86, 0x17, 0x95, 0xba, 0x81, 0x5b, 0xab, 0xdb, 0x4d, 0xa7, 0xbd, 0x87, 0xb2, 0x44, 0x9e, - 0x8b, 0x32, 0x76, 0x37, 0x70, 0x2d, 0x9f, 0xc3, 0x55, 0x4d, 0x4d, 0x82, 0x88, 0x54, 0x61, 0xda, - 0xa4, 0x76, 0x63, 0xcd, 0x7e, 0x5c, 0xb6, 0x3b, 0x76, 0xdd, 0x09, 0x78, 0xac, 0xd4, 0x1c, 0x17, - 0xe8, 0x3c, 0x6a, 0x37, 0xac, 0x96, 0xfd, 0xd8, 0xaa, 0x8b, 0x42, 0x5d, 0x65, 0xaf, 0xd1, 0x85, - 0xac, 0x9c, 0x76, 0xc8, 0x6a, 0x2c, 0xce, 0xca, 0x69, 0xf7, 0x66, 0x15, 0xd1, 0x49, 0x56, 0x9b, - 0xb6, 0xb7, 0x47, 0x03, 0x6e, 0x28, 0x0d, 0x97, 0x33, 0x57, 0x33, 0x0a, 0xab, 0x00, 0xcb, 0x2c, - 0x34, 0x9a, 0x8e, 0xb3, 0x52, 0xe8, 0xd8, 0xcc, 0xdb, 0xf6, 0x9c, 0x80, 0xaa, 0x2d, 0x1c, 0xc7, - 0xcf, 0xc2, 0xfe, 0x47, 0x13, 0xf3, 0x5e, 0x4d, 0x4c, 0x50, 0x46, 0xdc, 0x94, 0x46, 0x4e, 0x24, - 0xb8, 0xa5, 0xb7, 0x32, 0x41, 0x19, 0x72, 0x53, 0xdb, 0x39, 0x89, 0xed, 0x54, 0xb8, 0xf5, 0x68, - 0x68, 0x82, 0x92, 0xac, 0xb3, 0x4e, 0x0b, 0x98, 0xdc, 0xe4, 0xb6, 0x85, 0x05, 0xf7, 0x14, 0x7e, - 0xda, 0x2b, 0xc2, 0x0c, 0xb1, 0xe0, 0xc9, 0x62, 0x2b, 0xc5, 0x9e, 0x3b, 0x4e, 0x4c, 0xfe, 0x0a, - 0x4c, 0x6f, 0xf9, 0xf4, 0x4e, 0x75, 0xa3, 0x26, 0x23, 0x99, 0xa1, 0x72, 0x71, 0x6a, 0xf1, 0xe6, - 0x29, 0x9b, 0xce, 0x75, 0x95, 0x06, 0xd3, 0xe7, 0xf0, 0x71, 0xeb, 0xfa, 0xd4, 0xda, 0x75, 0x3a, - 0x7e, 0x18, 0x16, 0x52, 0x1d, 0xb7, 0x58, 0x55, 0xc6, 0x0a, 0xcc, 0x24, 0xd8, 0x90, 0x29, 0x00, - 0x06, 0xb4, 0xb6, 0xd6, 0x6b, 0xcb, 0x9b, 0x85, 0xe7, 0x48, 0x01, 0x26, 0xf0, 0xf7, 0xf2, 0x7a, - 0x69, 0x69, 0x75, 0xb9, 0x52, 0xc8, 0x90, 0x19, 0x98, 0x44, 0x48, 0xa5, 0x5a, 0xe3, 0xa0, 0x2c, - 0x4f, 0x9e, 0x60, 0x16, 0xf8, 0xd2, 0x0d, 0xd8, 0x02, 0xc0, 0x33, 0xc5, 0xf8, 0xdb, 0x59, 0xb8, - 0x28, 0x8f, 0x15, 0x1a, 0x30, 0xc1, 0xd1, 0x69, 0xef, 0x3d, 0xe3, 0xa7, 0xc3, 0x1d, 0xed, 0x74, - 0x78, 0x25, 0x76, 0x52, 0xc7, 0x5a, 0xd9, 0xe7, 0x88, 0xf8, 0xad, 0x31, 0x78, 0xb1, 0x2f, 0x15, - 0xf9, 0x22, 0x3b, 0xcd, 0x1d, 0xda, 0x0e, 0xaa, 0x8d, 0x26, 0xdd, 0x74, 0x5a, 0xd4, 0xed, 0x06, - 0xc2, 0x63, 0xe0, 0x65, 0xd4, 0xe7, 0x61, 0xa1, 0xe5, 0x34, 0x9a, 0xd4, 0x0a, 0x78, 0xb1, 0x36, - 0xdd, 0x92, 0xd4, 0x8c, 0x65, 0x98, 0xca, 0xab, 0xda, 0x0e, 0xa8, 0xf7, 0x08, 0xad, 0x12, 0x43, - 0x96, 0x07, 0x94, 0x76, 0x2c, 0x9b, 0x95, 0x5a, 0x8e, 0x28, 0xd6, 0x59, 0x26, 0xa8, 0xc9, 0x1d, - 0x85, 0x65, 0x99, 0xdd, 0xfe, 0xd7, 0xec, 0xc7, 0xc2, 0x4c, 0x4a, 0x44, 0xc6, 0x0d, 0x59, 0x72, - 0x77, 0xbe, 0x96, 0xfd, 0xd8, 0x4c, 0x92, 0x90, 0xaf, 0xc2, 0x39, 0x71, 0x00, 0x89, 0xe0, 0x2d, - 0xb2, 0xc5, 0x3c, 0x34, 0xcc, 0x6b, 0x27, 0xc7, 0xc5, 0x0b, 0x32, 0xa6, 0xb0, 0x0c, 0xac, 0x94, - 0xd6, 0xea, 0x74, 0x2e, 0x64, 0x93, 0x1d, 0xc8, 0xb1, 0xee, 0x58, 0xa3, 0xbe, 0x2f, 0x7d, 0x36, - 0xc5, 0xcd, 0x58, 0xed, 0x4c, 0xab, 0xc5, 0xcb, 0xcd, 0x9e, 0x94, 0x64, 0x05, 0xa6, 0xb6, 0xe9, - 0x8e, 0x3a, 0x3e, 0x23, 0xe1, 0x56, 0x55, 0x38, 0xa4, 0x3b, 0xbd, 0x07, 0x27, 0x46, 0x47, 0x1c, - 0x7c, 0x1f, 0x78, 0x7c, 0xb4, 0xea, 0xf8, 0x01, 0x6d, 0x53, 0x0f, 0xc3, 0xb7, 0x8d, 0xe2, 0x66, - 0x30, 0x1f, 0x49, 0xc8, 0x7a, 0xf9, 0xd2, 0x4b, 0x27, 0xc7, 0xc5, 0x17, 0xb9, 0xf3, 0x73, 0x53, - 0xc0, 0xad, 0x58, 0x22, 0xac, 0x24, 0x57, 0xf2, 0x75, 0x98, 0x36, 0xdd, 0x6e, 0xe0, 0xb4, 0xf7, - 0x6a, 0x81, 0x67, 0x07, 0x74, 0x8f, 0x1f, 0x48, 0x51, 0x9c, 0xb8, 0x58, 0xa9, 0x78, 0x5a, 0xe6, - 0x40, 0xcb, 0x17, 0x50, 0xed, 0x44, 0xd0, 0x09, 0xc8, 0xd7, 0x60, 0x8a, 0x87, 0xed, 0x08, 0x2b, - 0x18, 0xd3, 0x92, 0x78, 0xe8, 0x85, 0x0f, 0x6f, 0x0a, 0xab, 0x16, 0x84, 0xa6, 0x55, 0x10, 0xe3, - 0x46, 0xbe, 0x2c, 0x3a, 0x6b, 0xc3, 0x69, 0xef, 0x85, 0xd3, 0x18, 0xb0, 0xe7, 0xdf, 0x8c, 0xba, - 0xa4, 0xc3, 0x3e, 0x57, 0x4e, 0xe3, 0x1e, 0x26, 0x7a, 0x49, 0x3e, 0x24, 0x80, 0x17, 0x4b, 0xbe, - 0xef, 0xf8, 0x81, 0xf0, 0xab, 0x59, 0x7e, 0x4c, 0xeb, 0x5d, 0x86, 0xcc, 0xae, 0xb7, 0xd4, 0xe3, - 0x76, 0xdd, 0xc3, 0x4b, 0xd7, 0x4f, 0x8e, 0x8b, 0xaf, 0xdb, 0x88, 0x68, 0x09, 0x57, 0x1c, 0x8b, - 0x4a, 0x54, 0xeb, 0x90, 0xe3, 0x2a, 0x6d, 0xe8, 0xcf, 0x94, 0x7c, 0x0d, 0xce, 0x97, 0x6d, 0x9f, - 0x56, 0xdb, 0x3e, 0x6d, 0xfb, 0x4e, 0xe0, 0x3c, 0xa2, 0xa2, 0x53, 0xf1, 0xf0, 0xcb, 0x63, 0xca, - 0x30, 0xa3, 0x6e, 0xfb, 0x6c, 0x61, 0x86, 0x28, 0x96, 0x18, 0x14, 0xa5, 0x9a, 0x1e, 0x5c, 0x88, - 0x09, 0x53, 0xb5, 0xda, 0x4a, 0xc5, 0xb1, 0xc3, 0x75, 0x35, 0x89, 0xfd, 0xf5, 0x3a, 0xaa, 0xf6, - 0xfc, 0x7d, 0xab, 0xe1, 0xd8, 0xe1, 0x82, 0xea, 0xd1, 0x59, 0x31, 0x0e, 0xc6, 0x71, 0x06, 0x0a, - 0xf1, 0xa1, 0x24, 0x5f, 0x82, 0x31, 0x6e, 0xdf, 0x46, 0xfd, 0x7d, 0x11, 0x79, 0x42, 0x9a, 0x4b, - 0x85, 0x70, 0x9d, 0x48, 0xb8, 0xd3, 0x71, 0xeb, 0x39, 0xaa, 0x5a, 0xcb, 0xa0, 0x3b, 0x9d, 0x24, - 0x22, 0x0d, 0x98, 0xe0, 0xa3, 0x45, 0x31, 0x48, 0xa4, 0x30, 0x73, 0x7e, 0x49, 0x5d, 0x1d, 0xa2, - 0x28, 0xc6, 0x1f, 0x5f, 0x0d, 0xc5, 0x9c, 0xe0, 0x08, 0x5a, 0x15, 0x1a, 0xd7, 0x25, 0x80, 0xbc, - 0x24, 0x34, 0x2e, 0xc2, 0x85, 0x1e, 0xdf, 0x6c, 0x3c, 0x42, 0x4b, 0x82, 0x1e, 0x35, 0x92, 0x2f, - 0xc1, 0x1c, 0x12, 0x96, 0xdd, 0x76, 0x9b, 0xd6, 0x03, 0xdc, 0x8e, 0xa4, 0xf6, 0x3d, 0xc7, 0x2d, - 0x5d, 0x78, 0x7b, 0xeb, 0x21, 0x82, 0x15, 0x57, 0xc2, 0xa7, 0x72, 0x30, 0x7e, 0x2e, 0x0b, 0xf3, - 0x62, 0x87, 0x33, 0x69, 0xdd, 0xf5, 0x1a, 0xcf, 0xfe, 0x89, 0xba, 0xac, 0x9d, 0xa8, 0x2f, 0x87, - 0x61, 0x8b, 0xd2, 0x1a, 0xd9, 0xe7, 0x40, 0xfd, 0xd5, 0x0c, 0xbc, 0xd0, 0x8f, 0x88, 0xf5, 0x4e, - 0x18, 0x14, 0x73, 0x2c, 0x11, 0xfc, 0xb2, 0x03, 0xb3, 0x38, 0xa0, 0xe5, 0x7d, 0x5a, 0x3f, 0xf0, - 0x57, 0x5c, 0x3f, 0x40, 0x4f, 0x8b, 0x6c, 0x8f, 0xb7, 0xee, 0x37, 0x52, 0xdf, 0xba, 0xcf, 0xf3, - 0x59, 0x56, 0x47, 0x1e, 0x3c, 0x6c, 0xe7, 0x01, 0x3d, 0xf2, 0xcd, 0x34, 0xd6, 0x68, 0x31, 0x5f, - 0xea, 0x06, 0xfb, 0x1b, 0x1e, 0xdd, 0xa5, 0x1e, 0x6d, 0xd7, 0xe9, 0x77, 0x99, 0xc5, 0xbc, 0xde, - 0xb8, 0x81, 0x34, 0x18, 0xff, 0x64, 0x12, 0xe6, 0xd2, 0xc8, 0x58, 0xbf, 0x28, 0x97, 0xe6, 0x78, - 0x46, 0xd3, 0x1f, 0xc8, 0xc0, 0x44, 0x8d, 0xd6, 0xdd, 0x76, 0xe3, 0x0e, 0x5a, 0x14, 0x89, 0xde, - 0xb1, 0xb8, 0xd0, 0xc0, 0xe0, 0xd6, 0x6e, 0xcc, 0xd4, 0xe8, 0x3b, 0xc7, 0xc5, 0x2f, 0x0c, 0x76, - 0x57, 0xad, 0xbb, 0x18, 0x2e, 0x28, 0xc0, 0x8c, 0x1b, 0x61, 0x15, 0xf8, 0x38, 0xa8, 0x55, 0x4a, - 0x96, 0x60, 0x52, 0x2c, 0x57, 0x57, 0x8d, 0x89, 0xca, 0x23, 0x3b, 0xc9, 0x82, 0x84, 0xea, 0x5a, - 0x23, 0x21, 0xb7, 0x20, 0xb7, 0xb5, 0x78, 0x47, 0x8c, 0x81, 0xcc, 0x59, 0xb2, 0xb5, 0x78, 0x07, - 0xd5, 0x61, 0xec, 0x8a, 0x31, 0xd9, 0x5d, 0xd4, 0x8c, 0x7c, 0xb6, 0x16, 0xef, 0x90, 0xbf, 0x0a, - 0xe7, 0x2a, 0x8e, 0x2f, 0xaa, 0xe0, 0xbe, 0x1b, 0x0d, 0xf4, 0x58, 0x1c, 0xe9, 0x31, 0x7b, 0x3f, - 0x9b, 0x3a, 0x7b, 0x5f, 0x6a, 0x84, 0x4c, 0x2c, 0xee, 0x18, 0xd2, 0x88, 0xc7, 0x7e, 0x4d, 0xaf, - 0x87, 0x7c, 0x04, 0x53, 0xa8, 0xcc, 0x46, 0x77, 0x16, 0x8c, 0xda, 0x3f, 0xda, 0xa3, 0xe6, 0x4f, - 0xa7, 0xd6, 0xbc, 0xc0, 0xe3, 0x6d, 0xa0, 0x53, 0x0c, 0x46, 0xf8, 0xd7, 0x6e, 0xfd, 0x1a, 0x67, - 0x72, 0x0f, 0xa6, 0x85, 0xf8, 0xf5, 0x60, 0x77, 0x73, 0x9f, 0x56, 0xec, 0x23, 0x61, 0x9f, 0x83, - 0x37, 0x3a, 0x21, 0xb3, 0x59, 0xee, 0xae, 0x15, 0xec, 0x53, 0xab, 0x61, 0x6b, 0x82, 0x4a, 0x8c, - 0x90, 0x7c, 0x13, 0xc6, 0x57, 0xdd, 0x3a, 0x93, 0xbc, 0x71, 0x67, 0xe0, 0x26, 0x3b, 0x1f, 0x62, - 0xce, 0x4c, 0x0e, 0x8e, 0x89, 0x53, 0xdf, 0x39, 0x2e, 0xbe, 0x73, 0xd6, 0x49, 0xa3, 0x54, 0x60, - 0xaa, 0xb5, 0x91, 0x32, 0xe4, 0xb7, 0xe9, 0x0e, 0x6b, 0x6d, 0x3c, 0x9f, 0x9e, 0x04, 0x0b, 0x8b, - 0x3c, 0xf1, 0x4b, 0xb3, 0xc8, 0x13, 0x30, 0xe2, 0xc1, 0x0c, 0xf6, 0xcf, 0x86, 0xed, 0xfb, 0x87, - 0xae, 0xd7, 0xc0, 0xc4, 0x29, 0xbd, 0xac, 0x81, 0x16, 0x53, 0x3b, 0xff, 0x05, 0xde, 0xf9, 0x1d, - 0x85, 0x83, 0x2a, 0x40, 0x26, 0xd8, 0x93, 0xaf, 0xc3, 0x94, 0x88, 0x5d, 0xb0, 0x76, 0xa7, 0x84, - 0xab, 0x72, 0x42, 0xf3, 0xfb, 0xd4, 0x0b, 0xb9, 0x94, 0x2a, 0x42, 0x21, 0x48, 0x0d, 0x94, 0xd5, - 0xda, 0xb5, 0x75, 0xa5, 0xbf, 0x4a, 0x42, 0x36, 0x60, 0xbc, 0x82, 0x59, 0x9d, 0xd1, 0x37, 0x4d, - 0xd8, 0x85, 0x87, 0x09, 0xc1, 0xa2, 0x12, 0xae, 0x8b, 0x11, 0x09, 0xa0, 0xd1, 0xd3, 0x4d, 0xb7, - 0xd5, 0x0d, 0x11, 0xc9, 0x6d, 0xc8, 0x55, 0x2b, 0x1b, 0xc2, 0x2c, 0x7c, 0x26, 0x8c, 0x10, 0xb2, - 0x21, 0xd3, 0x27, 0xa1, 0xfd, 0x9c, 0xd3, 0xd0, 0x8c, 0xca, 0xab, 0x95, 0x0d, 0xb2, 0x0b, 0x93, - 0xd8, 0x01, 0x2b, 0xd4, 0xe6, 0x7d, 0x3b, 0xdd, 0xa3, 0x6f, 0xaf, 0xa7, 0xf6, 0xed, 0x3c, 0xef, - 0xdb, 0x7d, 0x41, 0xad, 0xe5, 0x83, 0x51, 0xd9, 0x32, 0x91, 0x56, 0xe4, 0xa8, 0x92, 0x59, 0x4c, - 0x36, 0x57, 0xd1, 0x3e, 0x48, 0x88, 0xb4, 0x32, 0xa5, 0x55, 0x98, 0x56, 0xa5, 0xa7, 0xd7, 0x49, - 0x92, 0x0f, 0xf9, 0x3c, 0x0c, 0x3d, 0x38, 0x08, 0x6c, 0x61, 0x00, 0x2e, 0xfb, 0x91, 0x81, 0x64, - 0xf3, 0x51, 0x0b, 0xe9, 0x1e, 0x68, 0x31, 0xe7, 0x90, 0x86, 0x0d, 0xc5, 0x8a, 0xed, 0x35, 0x0e, - 0x6d, 0x0f, 0x1d, 0x84, 0x67, 0x35, 0x16, 0x4a, 0x09, 0x1f, 0x8a, 0x7d, 0x01, 0x88, 0x79, 0x0d, - 0xab, 0x2c, 0xc8, 0xf7, 0xc0, 0x45, 0xdf, 0xd9, 0x6b, 0xdb, 0x41, 0xd7, 0xa3, 0x96, 0xdd, 0xdc, - 0x73, 0x3d, 0x27, 0xd8, 0x6f, 0x59, 0x7e, 0xd7, 0x09, 0xe8, 0xfc, 0x9c, 0x96, 0xd1, 0xba, 0x26, - 0xf1, 0x4a, 0x12, 0xad, 0xc6, 0xb0, 0xcc, 0x0b, 0x7e, 0x7a, 0x01, 0xf9, 0x32, 0x4c, 0xaa, 0x5b, - 0xb2, 0x3f, 0x7f, 0xee, 0x72, 0xee, 0xea, 0x54, 0x78, 0xf1, 0x88, 0x6f, 0xe0, 0x32, 0x12, 0xb4, - 0x72, 0x42, 0xf8, 0x7a, 0x24, 0x68, 0x85, 0x57, 0x98, 0x23, 0x92, 0x14, 0x66, 0xcd, 0x19, 0x31, - 0x63, 0x45, 0x2f, 0xaf, 0xdd, 0x29, 0x99, 0xa3, 0x1b, 0xd5, 0x87, 0xb5, 0xa6, 0x1b, 0x18, 0xff, - 0x45, 0x06, 0x37, 0x71, 0xf2, 0x3a, 0x06, 0x92, 0x0a, 0x5f, 0xcf, 0x50, 0x7f, 0x6b, 0x77, 0x62, - 0x69, 0x04, 0x38, 0x0a, 0x79, 0x03, 0x46, 0xee, 0xd8, 0x75, 0x19, 0xc4, 0x46, 0x20, 0xef, 0x22, - 0x44, 0x55, 0xf6, 0x72, 0x1c, 0x26, 0x5f, 0xf2, 0xc9, 0x5d, 0x8a, 0x92, 0xa5, 0x97, 0x4b, 0xf2, - 0xb9, 0x1e, 0xe5, 0x4b, 0xb1, 0x28, 0x94, 0x6c, 0xea, 0x31, 0xab, 0xf8, 0x54, 0x0e, 0xc6, 0x9f, - 0x64, 0xa2, 0x5d, 0x89, 0xbc, 0x06, 0x43, 0xe6, 0x46, 0xf8, 0xfd, 0xdc, 0xe9, 0x37, 0xf6, 0xf9, - 0x88, 0x40, 0xbe, 0x0c, 0xe7, 0x14, 0x3e, 0x09, 0x13, 0xfd, 0x57, 0xd1, 0x27, 0x55, 0xf9, 0x92, - 0x74, 0x3b, 0xfd, 0x74, 0x1e, 0x28, 0x4c, 0x47, 0x05, 0x15, 0xda, 0x76, 0x38, 0x6f, 0xa5, 0xb1, - 0x2a, 0xef, 0x06, 0x22, 0xc4, 0x1b, 0x9b, 0xc6, 0x81, 0xbb, 0xa4, 0x1a, 0xbf, 0x99, 0xd1, 0x76, - 0x9b, 0x30, 0xbb, 0x74, 0xe6, 0x94, 0xec, 0xd2, 0x6f, 0x03, 0x94, 0xba, 0x81, 0xbb, 0xdc, 0xf6, - 0xdc, 0x26, 0xd7, 0xa2, 0x88, 0x4c, 0x1a, 0xa8, 0x1b, 0xa6, 0x08, 0xd6, 0x3c, 0xe7, 0x42, 0xe4, - 0x54, 0x6f, 0x86, 0xdc, 0xc7, 0xf5, 0x66, 0x30, 0x7e, 0x2f, 0xa3, 0xad, 0x51, 0x26, 0x25, 0x8a, - 0xa9, 0xa8, 0x5a, 0x8c, 0x75, 0x9c, 0x47, 0x96, 0xdf, 0x74, 0xb5, 0x70, 0x15, 0x02, 0x8d, 0xfc, - 0xbb, 0x19, 0x38, 0xcf, 0xdd, 0x02, 0xd6, 0xbb, 0xad, 0x1d, 0xea, 0x3d, 0xb4, 0x9b, 0x4e, 0x23, - 0x0a, 0xd3, 0x17, 0x99, 0x0f, 0x2a, 0xd5, 0xa4, 0xe3, 0xf3, 0x8b, 0x2a, 0x77, 0x53, 0xb0, 0xda, - 0x58, 0x68, 0x3d, 0x0a, 0x4b, 0xd5, 0x8b, 0x6a, 0x3a, 0xbd, 0xf1, 0x6b, 0x19, 0x78, 0xe9, 0xd4, - 0x5a, 0xc8, 0x0d, 0x18, 0x95, 0x29, 0x4c, 0x32, 0xd8, 0xf1, 0x68, 0x67, 0x9b, 0x4c, 0x5f, 0x22, - 0xb1, 0xc8, 0x57, 0xe0, 0x9c, 0xca, 0x6a, 0xd3, 0xb3, 0x1d, 0x35, 0x51, 0x48, 0xca, 0x57, 0x07, - 0x0c, 0x25, 0x2e, 0xad, 0xa5, 0x33, 0x31, 0xfe, 0xdf, 0x8c, 0x92, 0x6f, 0xfe, 0x19, 0x95, 0xe1, - 0x6f, 0x6b, 0x32, 0xbc, 0x0c, 0x52, 0x1a, 0xb6, 0x8a, 0x95, 0xa5, 0xde, 0xbb, 0xa6, 0x15, 0x7b, - 0x71, 0x04, 0xfc, 0x70, 0x16, 0xc6, 0xb7, 0x7c, 0xea, 0xf1, 0x87, 0xdc, 0xef, 0xae, 0x50, 0x8d, - 0x61, 0xbb, 0x06, 0x0a, 0xa6, 0xf7, 0x47, 0x19, 0x54, 0xf0, 0xab, 0x14, 0xac, 0x37, 0x94, 0x1c, - 0x93, 0xd8, 0x1b, 0x98, 0x5d, 0x12, 0xa1, 0x3c, 0xb4, 0xd8, 0xaa, 0x9e, 0x6e, 0x16, 0x73, 0x0e, - 0xaf, 0x92, 0x2f, 0xc0, 0xf0, 0x16, 0xaa, 0x2b, 0xf5, 0x20, 0x1b, 0x21, 0x7f, 0x2c, 0xe4, 0x9b, - 0x74, 0xd7, 0xd7, 0xe3, 0xce, 0x71, 0x42, 0x52, 0x83, 0xd1, 0xb2, 0x47, 0x31, 0x7b, 0xfc, 0xd0, - 0xe0, 0x2e, 0xe2, 0x75, 0x4e, 0x12, 0x77, 0x11, 0x17, 0x9c, 0x8c, 0x9f, 0xcd, 0x02, 0x89, 0xda, - 0x88, 0xa9, 0xd2, 0xfc, 0x67, 0x76, 0xd0, 0x3f, 0xd0, 0x06, 0xfd, 0xc5, 0xc4, 0xa0, 0xf3, 0xe6, - 0x0d, 0x34, 0xf6, 0xbf, 0x9d, 0x81, 0xf3, 0xe9, 0x84, 0xe4, 0x65, 0x18, 0x79, 0xb0, 0xb9, 0x21, - 0xe3, 0xb4, 0x88, 0xa6, 0xb8, 0x1d, 0xd4, 0x15, 0x98, 0xa2, 0x88, 0xbc, 0x09, 0x23, 0x5f, 0x34, - 0xcb, 0xec, 0x1c, 0x52, 0x92, 0x71, 0x7c, 0xc3, 0xb3, 0xea, 0xfa, 0x51, 0x24, 0x90, 0xd4, 0xb1, - 0xcd, 0x3d, 0xb5, 0xb1, 0xfd, 0x89, 0x2c, 0x4c, 0x97, 0xea, 0x75, 0xea, 0xfb, 0x22, 0xd0, 0xfc, - 0x33, 0x3b, 0xb0, 0xe9, 0x11, 0x58, 0xb4, 0xb6, 0x0d, 0x34, 0xaa, 0xbf, 0x93, 0x81, 0x73, 0x92, - 0xea, 0x91, 0x43, 0x0f, 0x37, 0xf7, 0x3d, 0xea, 0xef, 0xbb, 0xcd, 0xc6, 0xc0, 0x19, 0x7f, 0x98, - 0xa0, 0x87, 0xc1, 0xe1, 0xd5, 0x57, 0xfd, 0x5d, 0x84, 0x68, 0x82, 0x1e, 0x0f, 0x20, 0x7f, 0x03, - 0x46, 0x4b, 0x9d, 0x8e, 0xe7, 0x3e, 0xe2, 0xcb, 0x5e, 0x44, 0x96, 0xb4, 0x39, 0x48, 0xf3, 0xb0, - 0xe7, 0x20, 0xf6, 0x19, 0x15, 0xda, 0xe6, 0xa1, 0xfc, 0x26, 0xf9, 0x67, 0x34, 0x68, 0x5b, 0x95, - 0xc5, 0xb1, 0xdc, 0xa8, 0x01, 0xd9, 0xf0, 0xdc, 0x96, 0x1b, 0xd0, 0x06, 0x6f, 0x0f, 0x06, 0x26, - 0x38, 0x35, 0xa4, 0xd6, 0xa6, 0x13, 0x34, 0xb5, 0x90, 0x5a, 0x01, 0x03, 0x98, 0x1c, 0x6e, 0xfc, - 0xdf, 0xc3, 0x30, 0xa1, 0xf6, 0x0e, 0x31, 0x78, 0x1a, 0x0f, 0xd7, 0x53, 0xa3, 0x63, 0xd8, 0x08, - 0x31, 0x45, 0x49, 0x14, 0x5a, 0x26, 0x7b, 0x6a, 0x68, 0x99, 0x6d, 0x98, 0xdc, 0xf0, 0x5c, 0x0c, - 0x81, 0x89, 0xaf, 0x95, 0x62, 0x2b, 0x9c, 0x55, 0xee, 0x9d, 0x6c, 0x20, 0xf1, 0x3d, 0x14, 0x25, - 0xfb, 0x8e, 0xc0, 0xc6, 0xe4, 0x96, 0x9a, 0xd6, 0x45, 0xe3, 0xc3, 0x4d, 0x2d, 0x6c, 0x5f, 0xc4, - 0xb1, 0x0d, 0x4d, 0x2d, 0x18, 0x44, 0x37, 0xb5, 0x60, 0x10, 0x75, 0xad, 0x0d, 0x3f, 0xad, 0xb5, - 0x46, 0x7e, 0x36, 0x03, 0xe3, 0xa5, 0x76, 0x5b, 0x84, 0xac, 0x39, 0xc5, 0x5b, 0xff, 0x2b, 0xc2, - 0xda, 0xe2, 0x9d, 0x8f, 0x65, 0x6d, 0x81, 0x72, 0x8b, 0x8f, 0x92, 0x6a, 0x54, 0xa1, 0x7a, 0x5b, - 0x53, 0xbe, 0x83, 0xbc, 0x03, 0x85, 0x70, 0x92, 0x57, 0xdb, 0x0d, 0xfa, 0x98, 0xf2, 0x34, 0x88, - 0x93, 0x22, 0xae, 0xb6, 0x2a, 0x99, 0xc6, 0x11, 0xc9, 0x26, 0x80, 0x1d, 0xce, 0xae, 0x58, 0x3e, - 0xd7, 0xe4, 0xf4, 0x13, 0xd2, 0x33, 0xfe, 0xc6, 0x07, 0x2d, 0x55, 0x7a, 0x8e, 0xf8, 0x90, 0x16, - 0x4c, 0xf3, 0x64, 0xaa, 0xb5, 0xc0, 0xf6, 0x02, 0x4c, 0x45, 0x01, 0xa7, 0x8e, 0xc3, 0x6b, 0x42, - 0x7f, 0xf6, 0xbc, 0x48, 0xd1, 0xea, 0x33, 0x5a, 0x2b, 0x25, 0x2f, 0x45, 0x9c, 0x37, 0x8f, 0x62, - 0x6e, 0x5e, 0x48, 0x7e, 0x2f, 0x9f, 0xf4, 0x3f, 0x91, 0x81, 0xf3, 0xea, 0xa4, 0xaf, 0x75, 0x77, - 0x44, 0xe8, 0x50, 0x72, 0x1d, 0xc6, 0xc4, 0x9c, 0x0c, 0x2f, 0x51, 0xc9, 0x8c, 0x1a, 0x11, 0x0a, - 0x59, 0x66, 0xd3, 0x90, 0xf1, 0x10, 0x52, 0xf7, 0x6c, 0x6c, 0x9f, 0x62, 0x45, 0x51, 0xa2, 0x6e, - 0x0f, 0x7f, 0xeb, 0xf3, 0x93, 0x41, 0x8c, 0xf7, 0x61, 0x46, 0x1f, 0x89, 0x1a, 0x0d, 0xc8, 0x35, - 0x18, 0x95, 0xc3, 0x97, 0x49, 0x1f, 0x3e, 0x59, 0x6e, 0x6c, 0x03, 0x49, 0xd0, 0xfb, 0x68, 0x16, - 0xc5, 0xee, 0xa7, 0xdc, 0x6c, 0x4f, 0x3e, 0x4a, 0x26, 0x10, 0x97, 0x66, 0xc5, 0xf7, 0x8d, 0x6b, - 0x6e, 0x09, 0x18, 0x46, 0xf5, 0x4f, 0xa6, 0x60, 0x36, 0x65, 0xcf, 0x3d, 0x45, 0x26, 0x2a, 0xea, - 0x1b, 0xc4, 0x58, 0x18, 0xec, 0x43, 0x6e, 0x0b, 0xef, 0xc3, 0xf0, 0xa9, 0xdb, 0x01, 0x77, 0x4a, - 0x89, 0xed, 0x02, 0x9c, 0xec, 0x13, 0x91, 0x8b, 0xd4, 0x78, 0x3c, 0xc3, 0x4f, 0x2d, 0x1e, 0xcf, - 0x12, 0x4c, 0x8a, 0x56, 0x89, 0xed, 0x4a, 0x31, 0x8e, 0x96, 0x19, 0x62, 0x12, 0xdb, 0x96, 0x4e, - 0xc2, 0x79, 0xf8, 0x6e, 0xf3, 0x11, 0x15, 0x3c, 0x46, 0x55, 0x1e, 0x58, 0x90, 0xca, 0x43, 0x21, - 0x21, 0xff, 0x31, 0x26, 0x72, 0x44, 0x88, 0xba, 0x67, 0xe5, 0xfb, 0xed, 0x59, 0x8d, 0xa7, 0xb3, - 0x67, 0xbd, 0x28, 0xbf, 0x31, 0x7d, 0xef, 0x4a, 0xf9, 0x2c, 0xf2, 0xcb, 0x19, 0x98, 0xe1, 0x41, - 0x61, 0xd4, 0x8f, 0xed, 0x1b, 0xe8, 0xa3, 0xfe, 0x74, 0x3e, 0xf6, 0x05, 0x91, 0x1b, 0x28, 0xfd, - 0x5b, 0x93, 0x1f, 0x45, 0xbe, 0x07, 0x20, 0x5c, 0x51, 0x3c, 0x94, 0xec, 0xf8, 0xe2, 0x0b, 0x29, - 0xbb, 0x40, 0x88, 0x14, 0xa5, 0xf0, 0x08, 0x42, 0x3a, 0x2d, 0x7d, 0x67, 0x08, 0x25, 0x7f, 0x15, - 0xe6, 0xd8, 0x7a, 0x09, 0x21, 0x22, 0x84, 0xd5, 0xfc, 0x38, 0xd6, 0xf2, 0x99, 0xde, 0x32, 0xd1, - 0xf5, 0x34, 0x32, 0x1e, 0x78, 0x38, 0xca, 0xa4, 0x1e, 0xa8, 0xd1, 0x2e, 0x52, 0x2b, 0xc2, 0xc8, - 0x70, 0xf8, 0xf5, 0x3c, 0xcd, 0x46, 0x8f, 0xfd, 0xed, 0xa2, 0x5c, 0x0b, 0x7c, 0x7f, 0xf3, 0x75, - 0x1f, 0x65, 0x04, 0x91, 0x2f, 0x02, 0x09, 0xa3, 0xa9, 0x70, 0x18, 0x95, 0x29, 0x38, 0xb8, 0xba, - 0x39, 0x8a, 0xca, 0xe2, 0xc9, 0x62, 0x75, 0x92, 0x24, 0x89, 0x09, 0x85, 0x39, 0xd1, 0x68, 0x06, - 0x95, 0x59, 0x16, 0xfd, 0xf9, 0x29, 0x2d, 0x40, 0x58, 0x54, 0x12, 0xa5, 0x5c, 0x57, 0x52, 0x35, - 0x6a, 0x2a, 0xa7, 0x34, 0x76, 0xe4, 0x36, 0x8c, 0xa1, 0xa3, 0xf0, 0x8a, 0x34, 0xf6, 0x12, 0x86, - 0x27, 0xe8, 0x52, 0x6c, 0xed, 0xeb, 0x26, 0x5b, 0x11, 0x2a, 0xbb, 0x0e, 0x54, 0xbc, 0x23, 0xb3, - 0xdb, 0x46, 0xa5, 0xb0, 0xd0, 0x77, 0x34, 0xbc, 0x23, 0xcb, 0xeb, 0xea, 0x9e, 0xe4, 0x88, 0x44, - 0xbe, 0x0e, 0xe3, 0x6b, 0xf6, 0xe3, 0x30, 0xcf, 0xd4, 0xcc, 0xe0, 0xd9, 0xac, 0x5a, 0xf6, 0xe3, - 0x30, 0xc9, 0x54, 0x3c, 0x9b, 0x95, 0xc2, 0x92, 0x7c, 0x15, 0x40, 0xd1, 0x54, 0x93, 0x53, 0x2b, - 0x78, 0x49, 0x86, 0xbd, 0x4b, 0xd5, 0x60, 0x23, 0x7f, 0x85, 0x61, 0x4c, 0x72, 0x98, 0xfb, 0xe4, - 0x24, 0x87, 0x73, 0x9f, 0x9c, 0xe4, 0xb0, 0xb0, 0x03, 0x17, 0x7b, 0x2e, 0x9d, 0x94, 0xa0, 0xc7, - 0x37, 0xf4, 0xa0, 0xc7, 0x17, 0x7b, 0x1d, 0xb1, 0xbe, 0x9e, 0x69, 0x65, 0xb6, 0x30, 0xd7, 0x5b, - 0x3a, 0xf9, 0x76, 0x36, 0x76, 0xe4, 0x8a, 0x8b, 0x05, 0xcf, 0xf2, 0xd5, 0x4b, 0x26, 0xc9, 0x62, - 0x0a, 0x6e, 0x7e, 0x28, 0x2b, 0x71, 0xe1, 0xd9, 0xa1, 0xac, 0x1e, 0xea, 0x78, 0x3c, 0x3f, 0xe9, - 0xe9, 0xfb, 0x2e, 0x4c, 0xf1, 0xac, 0xb9, 0xf7, 0xe9, 0xd1, 0xa1, 0xeb, 0x35, 0x78, 0xfe, 0x22, - 0x21, 0x83, 0x27, 0x52, 0xde, 0xc7, 0x70, 0x49, 0x45, 0xfa, 0x9e, 0x0e, 0x63, 0xed, 0x17, 0x53, - 0x77, 0x31, 0x86, 0xd0, 0xcf, 0x2d, 0x95, 0xbc, 0x15, 0x0a, 0x6a, 0xd4, 0x53, 0xf3, 0xad, 0x78, - 0x12, 0x98, 0x22, 0xaf, 0x51, 0xcf, 0xf8, 0x97, 0x39, 0x20, 0xbc, 0xa6, 0xb2, 0xdd, 0xb1, 0xd1, - 0x33, 0xdb, 0xc1, 0x58, 0x4b, 0x05, 0x81, 0x63, 0xef, 0x34, 0xa9, 0x1a, 0xa8, 0x4c, 0x18, 0xd7, - 0x86, 0x65, 0x56, 0xfc, 0xa2, 0x93, 0x20, 0xec, 0xb1, 0xd5, 0x65, 0x9f, 0x64, 0xab, 0xfb, 0x3a, - 0x3c, 0x5f, 0xea, 0x60, 0xfa, 0x6d, 0x59, 0xcb, 0x1d, 0xd7, 0x93, 0x9b, 0x94, 0xe6, 0xf3, 0x67, - 0x87, 0x68, 0x89, 0x2f, 0xed, 0xc7, 0x42, 0x91, 0x53, 0xd8, 0xbc, 0xec, 0x04, 0x6a, 0x0c, 0x09, - 0x29, 0xa7, 0x74, 0xb0, 0x24, 0x45, 0x4e, 0xe1, 0x24, 0x92, 0x87, 0xe3, 0x49, 0x39, 0x05, 0xb3, - 0x95, 0x45, 0x3c, 0x1c, 0x8f, 0xf6, 0x90, 0x75, 0x42, 0x12, 0xf2, 0x2e, 0x8c, 0x97, 0xba, 0x81, - 0x2b, 0x18, 0x0b, 0xab, 0xf0, 0xc8, 0x7e, 0x5b, 0x7c, 0x8a, 0x76, 0xf5, 0x89, 0xd0, 0x8d, 0x3f, - 0xce, 0xc1, 0xc5, 0xe4, 0xf0, 0x8a, 0xd2, 0x70, 0x7d, 0x64, 0x4e, 0x59, 0x1f, 0x69, 0xb3, 0x21, - 0x1b, 0x65, 0x9a, 0x78, 0x1a, 0xb3, 0x81, 0x67, 0xf1, 0xfe, 0x98, 0xb3, 0xa1, 0x06, 0xe3, 0xea, - 0x79, 0x37, 0xf4, 0x71, 0xcf, 0x3b, 0x95, 0x0b, 0xbb, 0xd4, 0xf3, 0xd0, 0x19, 0xc3, 0xd1, 0xd3, - 0x51, 0x3c, 0x6a, 0x06, 0xc7, 0x20, 0xff, 0x0e, 0x5c, 0xe6, 0x7b, 0x52, 0xbc, 0xb1, 0x4b, 0x47, - 0x92, 0xa3, 0x18, 0xb8, 0xc5, 0x93, 0xe3, 0xe2, 0x75, 0xae, 0x2a, 0xb1, 0x12, 0xdd, 0x66, 0xed, - 0x1c, 0x59, 0xf2, 0xcb, 0x94, 0x4a, 0x4e, 0xe5, 0x6d, 0x94, 0xe1, 0xa2, 0x28, 0x8d, 0x9c, 0xb6, - 0x65, 0x21, 0x1b, 0xe4, 0x83, 0x48, 0xdb, 0x85, 0x83, 0x1c, 0x53, 0x64, 0x61, 0x39, 0xe6, 0xff, - 0x56, 0x72, 0x33, 0xbf, 0x99, 0xe6, 0x73, 0xc3, 0xa3, 0x76, 0x73, 0xb0, 0xee, 0x6e, 0x23, 0x75, - 0x6a, 0xd9, 0x54, 0x9d, 0x9a, 0x54, 0xca, 0xe4, 0x52, 0x95, 0x32, 0x15, 0x98, 0xae, 0x75, 0x77, - 0x64, 0xdd, 0x71, 0x7f, 0x4d, 0xbf, 0xbb, 0x93, 0xd6, 0x2b, 0x71, 0x12, 0xe3, 0x47, 0xb2, 0x30, - 0xb1, 0xd1, 0xec, 0xee, 0x39, 0xed, 0x8a, 0x1d, 0xd8, 0xcf, 0xac, 0x9a, 0xef, 0x6d, 0x4d, 0xcd, - 0x17, 0xba, 0x96, 0x85, 0x0d, 0x1b, 0x48, 0xc7, 0xf7, 0x33, 0x19, 0x98, 0x8e, 0x48, 0xf8, 0x61, - 0xbd, 0x02, 0x43, 0xec, 0x87, 0xb8, 0xfc, 0x5e, 0x4e, 0x30, 0xe6, 0x69, 0x26, 0xc3, 0xbf, 0x84, - 0xe2, 0x4d, 0xcf, 0xe1, 0x86, 0x1c, 0x16, 0x3e, 0x0b, 0x63, 0x11, 0xdb, 0xb3, 0xa4, 0x97, 0xfc, - 0xf5, 0x0c, 0x14, 0xe2, 0x2d, 0x21, 0xf7, 0x61, 0x94, 0x71, 0x72, 0xa8, 0xbc, 0x97, 0xbf, 0xd2, - 0xa3, 0xcd, 0xd7, 0x05, 0x1a, 0xff, 0x3c, 0xec, 0x7c, 0xca, 0x21, 0xa6, 0xe4, 0xb0, 0x60, 0xc2, - 0x84, 0x8a, 0x95, 0xf2, 0x75, 0x6f, 0xe8, 0x12, 0xca, 0xf9, 0xf4, 0x7e, 0xd0, 0x92, 0x62, 0x6a, - 0x5f, 0x2d, 0x84, 0x8f, 0x2b, 0xda, 0xe4, 0x4a, 0x5d, 0x55, 0x38, 0x69, 0x16, 0xa3, 0x7c, 0x05, - 0xea, 0x3c, 0x4b, 0x99, 0xd0, 0x21, 0x1e, 0x79, 0x03, 0x46, 0x78, 0x7d, 0x6a, 0x42, 0xb7, 0x0e, - 0x42, 0x54, 0x39, 0x99, 0xe3, 0x18, 0x7f, 0x27, 0x07, 0xe7, 0xa3, 0xcf, 0xdb, 0xea, 0x34, 0xec, - 0x80, 0x6e, 0xd8, 0x9e, 0xdd, 0xf2, 0x4f, 0x59, 0x01, 0x57, 0x13, 0x9f, 0x86, 0xa9, 0xb4, 0xe4, - 0xa7, 0x29, 0x1f, 0x64, 0xc4, 0x3e, 0x08, 0x75, 0xa0, 0xfc, 0x83, 0xe4, 0x67, 0x90, 0xfb, 0x90, - 0xab, 0xd1, 0x40, 0xec, 0xbd, 0x57, 0x12, 0xbd, 0xaa, 0x7e, 0xd7, 0xf5, 0x1a, 0x0d, 0xf8, 0x20, - 0xf2, 0xb8, 0x50, 0x5a, 0x70, 0x3e, 0xc6, 0x85, 0x6c, 0xc3, 0xc8, 0xf2, 0xe3, 0x0e, 0xad, 0x07, - 0x22, 0x39, 0xea, 0xb5, 0xfe, 0xfc, 0x38, 0xae, 0x92, 0x1b, 0x95, 0x22, 0x40, 0xed, 0x2c, 0x8e, - 0xb2, 0x70, 0x1b, 0xf2, 0xb2, 0xf2, 0xb3, 0xcc, 0xdc, 0x85, 0xb7, 0x61, 0x5c, 0xa9, 0xe4, 0x4c, - 0x93, 0xfe, 0x17, 0xd8, 0xbe, 0xea, 0x36, 0x65, 0x3e, 0xd5, 0xe5, 0x84, 0xac, 0xa8, 0x64, 0xa3, - 0xe2, 0xb2, 0xa2, 0x75, 0x20, 0x8a, 0xfa, 0x08, 0x8d, 0x55, 0x98, 0xae, 0x1d, 0x38, 0x9d, 0x28, - 0x50, 0xac, 0x76, 0x22, 0x63, 0xc6, 0x1b, 0x71, 0x71, 0x8f, 0x9f, 0xc8, 0x71, 0x3a, 0xe3, 0xcf, - 0x32, 0x30, 0xc2, 0xfe, 0x7a, 0x78, 0xfb, 0x19, 0xdd, 0x32, 0x6f, 0x69, 0x5b, 0xe6, 0x8c, 0x12, - 0xab, 0x1d, 0x37, 0x8e, 0xdb, 0xa7, 0x6c, 0x96, 0xc7, 0x62, 0x80, 0x38, 0x32, 0xb9, 0x0b, 0xa3, - 0xc2, 0xa4, 0x48, 0xd8, 0x7e, 0xab, 0xc1, 0xdf, 0xa5, 0xb1, 0x51, 0x78, 0xc3, 0x77, 0x3b, 0x71, - 0x95, 0x88, 0xa4, 0x66, 0x72, 0xbd, 0x0c, 0xd9, 0xab, 0xe5, 0x4b, 0x77, 0xd1, 0x59, 0x8f, 0x87, - 0x2e, 0xf7, 0x97, 0x2e, 0x08, 0x4e, 0xbd, 0x7c, 0xeb, 0x4b, 0xe2, 0x35, 0x24, 0xd7, 0x8f, 0xc9, - 0x79, 0x99, 0xa4, 0x38, 0xf5, 0xa1, 0xa4, 0x05, 0xe7, 0x6b, 0xb5, 0x15, 0x34, 0x3f, 0xdc, 0x70, - 0xbd, 0xe0, 0x8e, 0xeb, 0x1d, 0xda, 0x68, 0x5b, 0x8c, 0x1a, 0x3e, 0xc5, 0x06, 0x21, 0xcd, 0x28, - 0xec, 0xb5, 0x54, 0xa3, 0xb0, 0x3e, 0x76, 0x0a, 0x46, 0x1b, 0x2e, 0xd4, 0x6a, 0x2b, 0x3c, 0x70, - 0xf8, 0x9f, 0x47, 0x7d, 0xbf, 0x9e, 0x81, 0x99, 0x5a, 0x6d, 0x25, 0x56, 0xd5, 0xaa, 0x8c, 0x58, - 0x9e, 0xd1, 0xf3, 0x7c, 0xa7, 0x76, 0x04, 0x8e, 0x42, 0x86, 0x4b, 0x78, 0x75, 0x2d, 0x38, 0x25, - 0x67, 0x42, 0x36, 0xc2, 0x18, 0xe9, 0x59, 0xcd, 0x1f, 0xa0, 0x47, 0x43, 0x51, 0xc3, 0x2d, 0xbc, - 0xe9, 0x58, 0xa9, 0xae, 0xe1, 0x66, 0x10, 0xe3, 0xbf, 0x39, 0xcf, 0xa3, 0xb0, 0xcb, 0xd9, 0xf2, - 0x1e, 0x4c, 0x08, 0x7a, 0x34, 0x9a, 0x17, 0x36, 0x21, 0x17, 0xd9, 0x06, 0xb9, 0xcb, 0xe1, 0x3c, - 0x3a, 0xef, 0x77, 0x8e, 0x8b, 0x43, 0xac, 0x6b, 0x4c, 0x0d, 0x9d, 0x3c, 0x80, 0xc9, 0x35, 0xfb, - 0xb1, 0xa2, 0xce, 0xe0, 0x2e, 0x51, 0xd7, 0xd8, 0xae, 0xd2, 0xb2, 0x1f, 0x0f, 0x60, 0x74, 0xa7, - 0xd3, 0x93, 0x03, 0x98, 0xd2, 0xdb, 0x24, 0x66, 0x60, 0x72, 0xc4, 0x6e, 0xa6, 0x8e, 0xd8, 0xc5, - 0x8e, 0xeb, 0x05, 0xd6, 0x6e, 0x48, 0xae, 0x65, 0x1c, 0x88, 0xb1, 0x26, 0xef, 0xc1, 0x8c, 0x12, - 0x02, 0xf4, 0x8e, 0xeb, 0xb5, 0x6c, 0x79, 0xe1, 0x42, 0x1d, 0x3f, 0xda, 0x12, 0xed, 0x22, 0xd8, - 0x4c, 0x62, 0x92, 0x2f, 0xa7, 0xb9, 0x99, 0x0d, 0x47, 0x96, 0x87, 0x29, 0x6e, 0x66, 0xbd, 0x2c, - 0x0f, 0x93, 0x0e, 0x67, 0x7b, 0xfd, 0x2c, 0x93, 0xf3, 0xbc, 0xf5, 0x03, 0x59, 0x1e, 0x87, 0x23, - 0xd7, 0xc3, 0x02, 0x79, 0x11, 0x72, 0x4b, 0x1b, 0x77, 0xf0, 0x65, 0x4a, 0x1a, 0x51, 0xb5, 0xf7, - 0xed, 0x76, 0x1d, 0x2f, 0x42, 0xc2, 0x1b, 0x40, 0x3d, 0x28, 0x97, 0x36, 0xee, 0x10, 0x1b, 0x66, - 0x31, 0xcf, 0x5b, 0xf0, 0xa5, 0x9b, 0x37, 0x95, 0xa1, 0xca, 0xe3, 0xa7, 0xdd, 0x10, 0x9f, 0x56, - 0xc4, 0x2c, 0x71, 0x81, 0xf5, 0xf8, 0xe6, 0xcd, 0xd4, 0x01, 0x09, 0x3f, 0x2c, 0x8d, 0x17, 0x3b, - 0xb0, 0xd6, 0xec, 0xc7, 0x91, 0x13, 0x87, 0x2f, 0x1c, 0x76, 0x5f, 0x94, 0x53, 0x2b, 0x72, 0x00, - 0xd1, 0x0e, 0x2c, 0x9d, 0x88, 0xdd, 0x63, 0xa3, 0x09, 0xe6, 0x0b, 0x57, 0xa7, 0x05, 0xa9, 0xae, - 0x93, 0x5e, 0xdd, 0xea, 0x65, 0x4c, 0x41, 0x27, 0x5b, 0xe1, 0x6d, 0x9c, 0xdf, 0x66, 0x45, 0x66, - 0xe4, 0x1b, 0xea, 0x6d, 0x9c, 0x2b, 0xc9, 0xb4, 0x66, 0x4d, 0x87, 0x2a, 0x1c, 0xee, 0xd5, 0x62, - 0xea, 0x5c, 0x92, 0x97, 0xfc, 0x89, 0xb3, 0x5f, 0xf2, 0x29, 0x0c, 0xad, 0xba, 0xf5, 0x03, 0x11, - 0x9c, 0xef, 0x8b, 0x6c, 0x17, 0xd6, 0xd3, 0xe8, 0x3f, 0xa9, 0xc5, 0x35, 0xb2, 0x27, 0xeb, 0xec, - 0x53, 0xd9, 0x2c, 0x10, 0x7d, 0x22, 0xac, 0x78, 0xe7, 0xc2, 0x5b, 0xae, 0x52, 0xc6, 0xe5, 0x51, - 0x3e, 0x69, 0x64, 0xd7, 0x9a, 0x3a, 0x39, 0xa1, 0x50, 0xa8, 0x50, 0xff, 0x20, 0x70, 0x3b, 0xe5, - 0xa6, 0xd3, 0xd9, 0x71, 0x6d, 0x4f, 0x86, 0x72, 0x1e, 0x78, 0x4f, 0x6e, 0x70, 0x7a, 0xab, 0x2e, - 0x19, 0x98, 0x09, 0x96, 0xe4, 0xcb, 0x30, 0xc5, 0x26, 0xf7, 0xf2, 0xe3, 0x80, 0xb6, 0xf9, 0xc8, - 0xcf, 0xa0, 0x44, 0x37, 0xa7, 0xe4, 0x2e, 0x09, 0x0b, 0xf9, 0x9c, 0xc2, 0xc5, 0x4e, 0x43, 0x02, - 0x2d, 0xb0, 0xa1, 0xc6, 0x8a, 0x34, 0x60, 0x7e, 0xcd, 0x7e, 0xac, 0xe4, 0x60, 0x56, 0x26, 0x29, - 0xc1, 0x09, 0x76, 0xf5, 0xe4, 0xb8, 0xf8, 0x0a, 0x9b, 0x60, 0x51, 0x74, 0xf1, 0x1e, 0xf3, 0xb5, - 0x27, 0x27, 0xf2, 0x4d, 0xb8, 0x20, 0x9a, 0x55, 0xc1, 0xbc, 0x61, 0xae, 0x77, 0x54, 0xdb, 0xb7, - 0xd1, 0x7f, 0x6b, 0xb6, 0x47, 0x87, 0xdd, 0x48, 0xdf, 0x12, 0x65, 0x87, 0x35, 0x24, 0x1f, 0xcb, - 0xe7, 0x8c, 0xcc, 0x5e, 0x35, 0x90, 0x8f, 0x60, 0x8a, 0x3f, 0xc7, 0xad, 0xb8, 0x7e, 0x80, 0xca, - 0x9a, 0xb9, 0xb3, 0xb9, 0x25, 0xf0, 0x37, 0x3e, 0xee, 0xc8, 0x13, 0x53, 0xee, 0xc4, 0x38, 0x93, - 0x77, 0x60, 0x7c, 0xc3, 0x69, 0xf3, 0xd0, 0xa3, 0xd5, 0x0d, 0x54, 0x2b, 0x8b, 0x13, 0xa8, 0xe3, - 0xb4, 0x2d, 0xa9, 0x31, 0xe9, 0x84, 0xdb, 0x85, 0x8a, 0x4d, 0xb6, 0x61, 0xbc, 0x56, 0x5b, 0xb9, - 0xe3, 0x30, 0xb9, 0xa4, 0x73, 0x34, 0x7f, 0xbe, 0xc7, 0x57, 0xbe, 0x9c, 0xfa, 0x95, 0x93, 0xbe, - 0xbf, 0x6f, 0xed, 0x3a, 0x4d, 0x6a, 0xd5, 0xdd, 0xce, 0x91, 0xa9, 0x72, 0x4a, 0x31, 0xd5, 0xbf, - 0xf0, 0x94, 0x4d, 0xf5, 0xab, 0x30, 0xad, 0x18, 0xcf, 0xa2, 0xe1, 0xec, 0x7c, 0x14, 0xaf, 0x4a, - 0x35, 0xcd, 0x8f, 0xbb, 0xa6, 0xc6, 0xe9, 0xa4, 0x8d, 0xfe, 0xc5, 0xb3, 0xda, 0xe8, 0x3b, 0x30, - 0xc3, 0x07, 0x43, 0xcc, 0x03, 0x1c, 0xe9, 0x85, 0x1e, 0x7d, 0x78, 0x2d, 0xb5, 0x0f, 0x67, 0xc5, - 0x48, 0xcb, 0x49, 0x86, 0xcf, 0xcf, 0x49, 0xae, 0x64, 0x17, 0x88, 0x00, 0xda, 0x81, 0xbd, 0x63, - 0xfb, 0x14, 0xeb, 0x7a, 0xbe, 0x47, 0x5d, 0xaf, 0xa4, 0xd6, 0x35, 0x25, 0xeb, 0xda, 0xe1, 0xd5, - 0xa4, 0x70, 0x24, 0x6d, 0x59, 0x8f, 0x9c, 0x5f, 0xd8, 0xb1, 0x2f, 0x68, 0x3a, 0xee, 0x24, 0x02, - 0x0f, 0xfd, 0x14, 0x9f, 0xb4, 0xf1, 0x7e, 0x4f, 0xe1, 0x4c, 0x1e, 0xc3, 0xf9, 0xe4, 0x57, 0x60, - 0x9d, 0x2f, 0x62, 0x9d, 0x2f, 0x6a, 0x75, 0xc6, 0x91, 0xf8, 0xbc, 0xd1, 0x9b, 0x15, 0xaf, 0xb5, - 0x07, 0x7f, 0xf2, 0x83, 0x19, 0xb8, 0xb0, 0x76, 0xa7, 0x84, 0xd9, 0x44, 0x1d, 0x1e, 0x89, 0x2e, - 0x74, 0xe9, 0xbd, 0x24, 0xde, 0x41, 0xe2, 0x6f, 0x33, 0x52, 0xe2, 0xc0, 0xad, 0x82, 0x89, 0xee, - 0x2f, 0xb7, 0x76, 0x6d, 0x9e, 0xa4, 0x54, 0xb0, 0x48, 0xf1, 0xfb, 0xfd, 0xd6, 0x1f, 0x16, 0x33, - 0x66, 0xaf, 0xaa, 0x48, 0x13, 0x16, 0xf4, 0x6e, 0x91, 0x5e, 0x14, 0xfb, 0xb4, 0xd9, 0x9c, 0x2f, - 0xe2, 0x8c, 0x7e, 0xe3, 0xe4, 0xb8, 0x78, 0x35, 0xd1, 0xbb, 0xa1, 0x67, 0x06, 0xc3, 0x54, 0x1a, - 0xdc, 0x87, 0x1f, 0x69, 0xa5, 0x08, 0xdd, 0xf3, 0x97, 0xb5, 0xd8, 0x3f, 0x89, 0xf2, 0xa5, 0x57, - 0x85, 0x44, 0xf2, 0x22, 0x5b, 0xef, 0x3d, 0x05, 0x44, 0x33, 0xc9, 0xf9, 0xde, 0x50, 0x7e, 0xb2, - 0x30, 0x95, 0xe2, 0xb2, 0x60, 0xfc, 0x56, 0x36, 0x76, 0x30, 0x92, 0x2a, 0x8c, 0x8a, 0xf9, 0xde, - 0xf3, 0x92, 0xf1, 0x62, 0xea, 0xac, 0x1e, 0x15, 0x4b, 0xc7, 0x94, 0xf4, 0xe4, 0x90, 0xb1, 0xc2, - 0x46, 0x8b, 0x1b, 0xef, 0x57, 0xf9, 0xb9, 0x87, 0x20, 0xed, 0x84, 0xaf, 0x9c, 0xdd, 0x11, 0x4f, - 0xf7, 0xf3, 0xc4, 0xa3, 0x5e, 0xd6, 0x46, 0x0e, 0x78, 0x2a, 0xa9, 0x5c, 0xe8, 0xcd, 0xa5, 0xe7, - 0x8d, 0x7a, 0x6a, 0x15, 0xb2, 0x5a, 0x8c, 0xdf, 0xcc, 0xc0, 0xa4, 0x76, 0xb2, 0x92, 0xdb, 0x8a, - 0xab, 0x62, 0xe4, 0xbd, 0xaf, 0xe1, 0xe0, 0x66, 0x1b, 0x77, 0x62, 0xbc, 0x2d, 0xfc, 0x0e, 0xb2, - 0xbd, 0xe9, 0x70, 0xb1, 0xc5, 0x3d, 0x57, 0xfb, 0xeb, 0x87, 0xc3, 0x3c, 0x98, 0x43, 0x3d, 0xf2, - 0x60, 0xfe, 0xfd, 0x22, 0x4c, 0xe9, 0x37, 0x62, 0xf2, 0x06, 0x8c, 0xa0, 0x6e, 0x5e, 0xaa, 0x57, - 0x50, 0x2d, 0x84, 0xea, 0x7b, 0xcd, 0x19, 0x85, 0xe3, 0x90, 0x57, 0x01, 0x42, 0x03, 0x70, 0xf9, - 0x32, 0x35, 0x7c, 0x72, 0x5c, 0xcc, 0xbc, 0x69, 0x2a, 0x05, 0xe4, 0x6b, 0x00, 0xeb, 0x6e, 0x83, - 0x86, 0xc9, 0x8d, 0xfb, 0x58, 0x5f, 0xbc, 0x96, 0x48, 0xb3, 0x72, 0xae, 0xed, 0x36, 0x68, 0x32, - 0xa7, 0x8a, 0xc2, 0x91, 0x7c, 0x1e, 0x86, 0xcd, 0x6e, 0x93, 0xca, 0x17, 0x8c, 0x71, 0x79, 0xc2, - 0x75, 0x9b, 0x34, 0xd2, 0x13, 0x78, 0xdd, 0xb8, 0x61, 0x21, 0x03, 0x90, 0x0f, 0x78, 0xfa, 0x15, - 0x11, 0x23, 0x74, 0x38, 0x7a, 0xab, 0x53, 0x24, 0x9f, 0x44, 0x94, 0x50, 0x85, 0x84, 0x3c, 0x80, - 0x51, 0xf5, 0x91, 0x49, 0xf1, 0x79, 0x57, 0x1f, 0x22, 0x15, 0xa5, 0x83, 0xc8, 0x8a, 0x1c, 0x7f, - 0x7f, 0x92, 0x5c, 0xc8, 0xbb, 0x30, 0xc6, 0xd8, 0xb3, 0x9d, 0xc3, 0x17, 0xb7, 0x1a, 0x7c, 0x91, - 0x53, 0x3e, 0x88, 0xed, 0x3e, 0x5a, 0x24, 0xcf, 0x90, 0x80, 0x7c, 0x19, 0xf3, 0xd8, 0x8a, 0xae, - 0xee, 0x6b, 0x95, 0x73, 0x25, 0xd1, 0xd5, 0x98, 0xd8, 0x36, 0xd1, 0xd3, 0x11, 0x3f, 0xb2, 0x17, - 0x86, 0x5c, 0x1b, 0x24, 0x65, 0xce, 0xeb, 0x89, 0x0a, 0xe6, 0x65, 0x14, 0xb1, 0x64, 0x92, 0x6a, - 0x8d, 0x2f, 0xe9, 0x40, 0x21, 0x12, 0x2a, 0x45, 0x5d, 0xd0, 0xaf, 0xae, 0x37, 0x13, 0x75, 0xa9, - 0x03, 0x98, 0xa8, 0x2e, 0xc1, 0x9d, 0x34, 0x60, 0x4a, 0x1e, 0x50, 0xa2, 0xbe, 0xf1, 0x7e, 0xf5, - 0xbd, 0x9a, 0xa8, 0x6f, 0xb6, 0xb1, 0x93, 0xac, 0x27, 0xc6, 0x93, 0xbc, 0x0b, 0x93, 0x12, 0xc2, - 0x53, 0x46, 0x4f, 0x44, 0x39, 0x77, 0x1b, 0x3b, 0x89, 0x44, 0xd1, 0x3a, 0xb2, 0x4a, 0xcd, 0x67, - 0xc7, 0xa4, 0x46, 0x1d, 0x9f, 0x15, 0x3a, 0x32, 0xf9, 0x10, 0xc6, 0xab, 0x2d, 0xd6, 0x10, 0xb7, - 0x6d, 0x07, 0x54, 0xf8, 0x43, 0x4a, 0x0b, 0x23, 0xa5, 0x44, 0x99, 0xaa, 0x3c, 0x19, 0x76, 0x54, - 0xa4, 0x25, 0xc3, 0x8e, 0xc0, 0xac, 0xf3, 0xf8, 0xab, 0xa2, 0x98, 0xc3, 0xd2, 0x57, 0xf2, 0xc5, - 0x14, 0x2b, 0x1f, 0x85, 0xbd, 0x88, 0x07, 0xc9, 0xa0, 0xf2, 0x55, 0x2f, 0x16, 0x8b, 0x57, 0xe5, - 0x49, 0xde, 0x83, 0x71, 0x91, 0x4d, 0xac, 0x64, 0xae, 0xfb, 0xf3, 0x05, 0x6c, 0x3c, 0x46, 0x78, - 0x90, 0x89, 0xc7, 0x2c, 0xdb, 0x8b, 0x99, 0xb3, 0x46, 0xf8, 0xe4, 0x4b, 0x30, 0xb7, 0xed, 0xb4, - 0x1b, 0xee, 0xa1, 0x2f, 0x8e, 0x29, 0xb1, 0xd1, 0xcd, 0x44, 0xce, 0x64, 0x87, 0xbc, 0x3c, 0x94, - 0x05, 0x13, 0x1b, 0x5f, 0x2a, 0x07, 0xf2, 0x7d, 0x09, 0xce, 0x7c, 0x06, 0x91, 0x7e, 0x33, 0x68, - 0x31, 0x31, 0x83, 0x92, 0xd5, 0xc7, 0xa7, 0x53, 0x6a, 0x35, 0xc4, 0x05, 0xa2, 0x9f, 0xef, 0xf7, - 0x5c, 0xa7, 0x3d, 0x3f, 0x8b, 0x7b, 0xe1, 0xf3, 0xf1, 0x98, 0x0a, 0x88, 0x27, 0x92, 0x8a, 0x1b, - 0x27, 0xc7, 0xc5, 0x4b, 0x71, 0x99, 0xff, 0x23, 0x57, 0x7b, 0x2e, 0x49, 0x61, 0x4d, 0x3e, 0x84, - 0x09, 0xf6, 0x7f, 0xa8, 0x94, 0x98, 0xd3, 0xec, 0x42, 0x15, 0x4c, 0x51, 0x0f, 0x8e, 0x11, 0xa6, - 0x3b, 0x4b, 0xd1, 0x57, 0x68, 0xac, 0xc8, 0xdb, 0x00, 0x4c, 0x6c, 0x12, 0xdb, 0xf1, 0xb9, 0x28, - 0xf4, 0x31, 0x4a, 0x5d, 0xc9, 0x8d, 0x38, 0x42, 0x26, 0xef, 0xc2, 0x38, 0xfb, 0x55, 0xeb, 0x36, - 0x5c, 0xb6, 0x36, 0xce, 0x23, 0x2d, 0x77, 0x4d, 0x65, 0xb4, 0x3e, 0x87, 0x6b, 0xae, 0xa9, 0x11, - 0x3a, 0x59, 0x81, 0x69, 0x0c, 0x51, 0x2d, 0x82, 0xa3, 0x3a, 0xd4, 0x9f, 0xbf, 0xa0, 0x58, 0x43, - 0xb0, 0x22, 0xcb, 0x09, 0xcb, 0xd4, 0xbb, 0x4c, 0x8c, 0x8c, 0xf8, 0x30, 0x9b, 0x7c, 0x4e, 0xf6, - 0xe7, 0xe7, 0xb1, 0x93, 0xa4, 0x04, 0x9f, 0xc4, 0xe0, 0xfb, 0x31, 0x1b, 0x11, 0x65, 0xe3, 0x92, - 0x8f, 0x4a, 0x6a, 0x85, 0x69, 0xdc, 0x89, 0x09, 0xe4, 0x6e, 0x79, 0x23, 0x1e, 0xc3, 0xf9, 0x22, - 0xb6, 0x00, 0x87, 0x79, 0xaf, 0x1e, 0x65, 0x11, 0x4f, 0x89, 0xe3, 0x9c, 0x42, 0x4d, 0xbe, 0x17, - 0xce, 0xc9, 0x1d, 0x44, 0x14, 0x89, 0x79, 0xbd, 0x70, 0xc6, 0x9d, 0xb8, 0xb1, 0x13, 0x56, 0x9d, - 0x98, 0xd2, 0xe9, 0x55, 0x10, 0x1b, 0xc6, 0x71, 0x58, 0x45, 0x8d, 0xcf, 0xf7, 0xab, 0xf1, 0x6a, - 0xa2, 0xc6, 0xf3, 0x38, 0x51, 0x92, 0x95, 0xa9, 0x3c, 0xc9, 0x12, 0x4c, 0x8a, 0x75, 0x24, 0x66, - 0xdb, 0x0b, 0xd8, 0x5b, 0xa8, 0xc4, 0x92, 0x2b, 0x30, 0x31, 0xe1, 0x74, 0x12, 0x75, 0x47, 0xe6, - 0x8f, 0x49, 0x2f, 0x6a, 0x3b, 0x72, 0xfc, 0x0d, 0x49, 0x47, 0x66, 0x3b, 0x52, 0x24, 0xc5, 0x2c, - 0x3f, 0xee, 0x78, 0x42, 0x45, 0x75, 0x29, 0xca, 0x8a, 0xa4, 0x08, 0x3f, 0x16, 0x0d, 0x31, 0xd4, - 0x2d, 0x21, 0x8d, 0x03, 0xd9, 0x82, 0xd9, 0xf0, 0xd4, 0x56, 0x18, 0x17, 0xa3, 0x28, 0xc1, 0xd1, - 0x51, 0x9f, 0xce, 0x37, 0x8d, 0x9e, 0xd8, 0x70, 0x41, 0x3b, 0xa7, 0x15, 0xd6, 0x97, 0x91, 0x35, - 0x66, 0xad, 0xd7, 0x0f, 0xf9, 0x74, 0xf6, 0xbd, 0xf8, 0x90, 0x8f, 0x60, 0x21, 0x7e, 0x36, 0x2b, - 0xb5, 0xbc, 0x84, 0xb5, 0xbc, 0x7e, 0x72, 0x5c, 0xbc, 0x92, 0x38, 0xde, 0xd3, 0x2b, 0xea, 0xc3, - 0x8d, 0x7c, 0x0d, 0xe6, 0xf5, 0xf3, 0x59, 0xa9, 0xc9, 0xc0, 0x9a, 0x70, 0xe9, 0x84, 0x07, 0x7b, - 0x7a, 0x0d, 0x3d, 0x79, 0x90, 0x00, 0x8a, 0xa9, 0xb3, 0x5b, 0xa9, 0xe6, 0xe5, 0xa8, 0x41, 0x89, - 0x55, 0x92, 0x5e, 0xdd, 0x69, 0x2c, 0xc9, 0x21, 0x5c, 0x4a, 0x3b, 0x26, 0x94, 0x4a, 0x5f, 0x09, - 0x95, 0xc0, 0x9f, 0x4a, 0x3f, 0x72, 0xd2, 0x6b, 0x3e, 0x85, 0x2d, 0xf9, 0x32, 0x9c, 0x53, 0xd6, - 0x97, 0x52, 0xdf, 0xab, 0x58, 0x1f, 0xba, 0x82, 0xab, 0x0b, 0x33, 0xbd, 0x96, 0x74, 0x1e, 0xa4, - 0x05, 0xb3, 0xb2, 0xe1, 0xa8, 0x6d, 0x17, 0x47, 0xcf, 0x15, 0x6d, 0x57, 0x4d, 0x62, 0x2c, 0x5d, - 0x16, 0xbb, 0xea, 0x7c, 0x63, 0xc7, 0xea, 0x44, 0x84, 0xea, 0x4c, 0x4f, 0xe1, 0x4b, 0x56, 0x60, - 0xa4, 0xb6, 0x51, 0xbd, 0x73, 0x67, 0x79, 0xfe, 0x35, 0xac, 0x41, 0xfa, 0x8d, 0x71, 0xa0, 0x76, - 0x69, 0x12, 0xe6, 0x8a, 0x1d, 0x67, 0x77, 0x57, 0x7b, 0xb0, 0xe2, 0xa8, 0xe4, 0xfb, 0xd0, 0x50, - 0x90, 0xed, 0xa8, 0x25, 0xdf, 0x77, 0xf6, 0x30, 0xea, 0xb4, 0x3f, 0xff, 0xba, 0xf6, 0xde, 0x2f, - 0x23, 0x72, 0x97, 0x31, 0x61, 0x59, 0x02, 0x9d, 0x4b, 0x9b, 0xec, 0xfe, 0x2f, 0x76, 0x6e, 0xcb, - 0x8e, 0x58, 0xa9, 0x9b, 0x78, 0xb2, 0x22, 0xd6, 0x6f, 0x7b, 0x4e, 0x60, 0xed, 0x77, 0xb5, 0xe6, - 0xcf, 0x7f, 0x4a, 0x8b, 0xc0, 0xcc, 0xd3, 0xb8, 0x29, 0xbd, 0xf6, 0x8a, 0xa8, 0xf0, 0x05, 0x7e, - 0x5b, 0xee, 0xd1, 0x73, 0x33, 0x7b, 0x31, 0x3a, 0x9f, 0xfc, 0x40, 0x06, 0xce, 0x6f, 0xbb, 0xde, - 0x41, 0xd3, 0xb5, 0x1b, 0xb2, 0x55, 0x62, 0x0f, 0x7f, 0xa3, 0xdf, 0x1e, 0xfe, 0x99, 0xc4, 0x1e, - 0x6e, 0x1c, 0x0a, 0x36, 0x56, 0x18, 0xd0, 0x3c, 0xb1, 0x9f, 0xf7, 0xa8, 0x8a, 0x7c, 0x1f, 0x5c, - 0x4e, 0x2f, 0x51, 0x26, 0xe5, 0x9b, 0x38, 0x29, 0x6f, 0x9e, 0x1c, 0x17, 0xdf, 0xec, 0x55, 0x53, - 0xfa, 0x04, 0x3d, 0x95, 0xf5, 0xbd, 0xa1, 0xfc, 0xd5, 0xc2, 0xb5, 0x7b, 0x43, 0xf9, 0x6b, 0x85, - 0xd7, 0xcd, 0x17, 0x6a, 0xa5, 0xb5, 0xd5, 0x6a, 0x43, 0x1e, 0xae, 0x32, 0xe6, 0x3a, 0xa7, 0x31, - 0xaf, 0xf4, 0x2b, 0x8d, 0x38, 0x1a, 0x7f, 0x2b, 0x03, 0xc5, 0x53, 0x26, 0x09, 0x3b, 0xcf, 0xa2, - 0x91, 0xa8, 0xd1, 0x40, 0x8d, 0xdc, 0x1e, 0x8d, 0x9f, 0xa5, 0x9b, 0x8d, 0xe8, 0x24, 0xe8, 0x74, - 0x28, 0xd2, 0x85, 0x28, 0xbe, 0xa7, 0xc9, 0x34, 0x21, 0x12, 0xcb, 0x58, 0x85, 0x42, 0x7c, 0xf2, - 0x90, 0xcf, 0xc1, 0xa4, 0x9a, 0xac, 0x40, 0xaa, 0x12, 0x78, 0xa0, 0x11, 0x6f, 0x4f, 0x3b, 0x10, - 0x35, 0x44, 0xe3, 0x17, 0x32, 0x30, 0x9b, 0xb2, 0xc2, 0xc8, 0x15, 0x18, 0xc2, 0x6c, 0x62, 0x8a, - 0xd5, 0x50, 0x2c, 0x8b, 0x18, 0x96, 0x93, 0x4f, 0xc3, 0x68, 0x65, 0xbd, 0x56, 0x2b, 0xad, 0x4b, - 0x65, 0x04, 0x3f, 0x88, 0xdb, 0xbe, 0xe5, 0xdb, 0xba, 0xb1, 0x81, 0x40, 0x23, 0x6f, 0xc2, 0x48, - 0x75, 0x03, 0x09, 0xb8, 0xed, 0x2b, 0xb6, 0xd7, 0xe9, 0xc4, 0xf1, 0x05, 0x92, 0xf1, 0x63, 0x19, - 0x20, 0xc9, 0xed, 0x82, 0xdc, 0x84, 0x71, 0x75, 0x53, 0xe2, 0xed, 0xc5, 0x17, 0x58, 0x65, 0xe1, - 0x98, 0x2a, 0x0e, 0xa9, 0xc0, 0x30, 0xe6, 0x81, 0x0d, 0xad, 0x1c, 0x52, 0x97, 0xc5, 0x85, 0xc4, - 0xb2, 0x18, 0xc6, 0x2c, 0xb3, 0x26, 0x27, 0x36, 0x7e, 0x27, 0x03, 0x24, 0xdd, 0x76, 0x71, 0x20, - 0x2b, 0xab, 0xb7, 0x94, 0xd8, 0x05, 0x6a, 0xbe, 0xa0, 0x30, 0xd9, 0x9b, 0xaa, 0x06, 0x88, 0xa2, - 0x1c, 0x5c, 0xd1, 0xd4, 0x4e, 0xbd, 0x1d, 0x5e, 0xaf, 0xc1, 0xf0, 0x43, 0xea, 0xed, 0x48, 0xb3, - 0x6e, 0x34, 0x05, 0x7d, 0xc4, 0x00, 0xaa, 0x1a, 0x06, 0x31, 0x8c, 0x3f, 0xce, 0xc0, 0x5c, 0xda, - 0x1d, 0xe5, 0x14, 0xbf, 0x54, 0x23, 0xe6, 0x52, 0x8b, 0x16, 0x56, 0xdc, 0x4e, 0x34, 0x74, 0xa4, - 0x2d, 0xc2, 0x30, 0x6b, 0xac, 0x1c, 0x61, 0x54, 0x83, 0xb1, 0xde, 0xf0, 0x4d, 0x0e, 0x67, 0x08, - 0x3c, 0x46, 0xdf, 0x10, 0x86, 0x77, 0x44, 0x04, 0x9c, 0xdd, 0x26, 0x87, 0x33, 0x84, 0x35, 0xb7, - 0x41, 0xa5, 0x7a, 0x08, 0x11, 0x5a, 0x0c, 0x60, 0x72, 0x38, 0xb9, 0x02, 0xa3, 0x0f, 0xda, 0xab, - 0xd4, 0x7e, 0x24, 0x73, 0x56, 0xa0, 0x45, 0x98, 0xdb, 0xb6, 0x9a, 0x0c, 0x66, 0xca, 0x42, 0xe3, - 0x67, 0x32, 0x30, 0x93, 0xb8, 0x1e, 0x9d, 0xee, 0x7a, 0xdb, 0xdf, 0x07, 0x6e, 0x90, 0xf6, 0xf1, - 0xcf, 0x1f, 0x4a, 0xff, 0x7c, 0xe3, 0xbf, 0x1b, 0x81, 0x0b, 0x3d, 0xb4, 0x55, 0x91, 0x8f, 0x6e, - 0xe6, 0x54, 0x1f, 0xdd, 0xaf, 0xc0, 0x64, 0xb9, 0x69, 0x3b, 0x2d, 0x7f, 0xd3, 0x8d, 0xbe, 0x38, - 0x72, 0xf5, 0xc1, 0x32, 0xe1, 0x07, 0x11, 0xfa, 0x84, 0x5c, 0xac, 0x23, 0x85, 0x15, 0xb8, 0x49, - 0x61, 0x59, 0x63, 0x96, 0xf0, 0x92, 0xcd, 0xfd, 0x05, 0xf1, 0x92, 0xd5, 0xfd, 0xb6, 0x86, 0x9e, - 0xaa, 0xdf, 0x56, 0xba, 0xcd, 0xf7, 0xf0, 0x93, 0x78, 0x00, 0x94, 0x61, 0x92, 0x9b, 0xc4, 0x95, - 0x7c, 0x3e, 0x48, 0x23, 0x09, 0x33, 0x3a, 0xdb, 0x4f, 0x8e, 0x85, 0x46, 0x43, 0x56, 0x74, 0x1f, - 0xa3, 0x51, 0x7c, 0x33, 0xbe, 0xd2, 0xdb, 0x87, 0x48, 0xb3, 0x15, 0xd1, 0x7c, 0x89, 0xbe, 0x09, - 0x73, 0x69, 0xd7, 0xdd, 0xf9, 0xbc, 0x66, 0x6d, 0xdb, 0xd3, 0x4a, 0x7b, 0xf0, 0x4b, 0xf3, 0x41, - 0xea, 0xa5, 0x59, 0xfa, 0x7e, 0x8f, 0x69, 0x21, 0x9d, 0x7b, 0xac, 0x05, 0x8e, 0xdb, 0xdf, 0x43, - 0xdc, 0xf8, 0x0a, 0xbc, 0xd8, 0x97, 0x9c, 0xbc, 0xa3, 0xc5, 0x18, 0x7a, 0x2d, 0x19, 0x63, 0xe8, - 0x3b, 0xc7, 0xc5, 0x19, 0xcd, 0x6f, 0x73, 0x2d, 0x54, 0xf8, 0x1b, 0x3f, 0x93, 0xd5, 0x3d, 0x8e, - 0xff, 0x22, 0x2e, 0xd4, 0x6b, 0x30, 0xbc, 0xbd, 0x4f, 0x3d, 0x79, 0x3c, 0xe0, 0x87, 0x1c, 0x32, - 0x80, 0xfa, 0x21, 0x88, 0x41, 0xee, 0xc0, 0xd4, 0x06, 0x9f, 0xb8, 0x72, 0x36, 0x0e, 0x45, 0x3a, - 0x97, 0x8e, 0xd0, 0x0c, 0xa6, 0x4c, 0xc7, 0x18, 0x95, 0x71, 0x37, 0xd6, 0xe9, 0x22, 0x42, 0x12, - 0xf7, 0x8c, 0xe2, 0x02, 0xc4, 0x54, 0xe4, 0x0b, 0x16, 0x6d, 0xb6, 0x66, 0x0c, 0x6a, 0xec, 0xc2, - 0xa5, 0xbe, 0x8c, 0xd8, 0xb9, 0x0d, 0x9d, 0xf0, 0x57, 0xcc, 0xf2, 0xba, 0x2f, 0xa9, 0xa9, 0xd0, - 0x19, 0xdf, 0x84, 0x09, 0xb5, 0x97, 0xf1, 0x08, 0x62, 0xbf, 0xc5, 0xac, 0xe0, 0x47, 0x10, 0x03, - 0x98, 0x1c, 0x1e, 0xbd, 0xe5, 0x64, 0xd3, 0xdf, 0x72, 0xa2, 0xe1, 0xcf, 0x9d, 0x36, 0xfc, 0xac, - 0x72, 0xdc, 0xe1, 0x94, 0xca, 0xf1, 0xb7, 0x5a, 0x39, 0x86, 0x40, 0x32, 0x39, 0xfc, 0xa9, 0x56, - 0xfe, 0x4f, 0x65, 0x92, 0x33, 0x74, 0xbc, 0x92, 0xcb, 0x3d, 0x13, 0x65, 0x2a, 0x4b, 0x5b, 0xbd, - 0x11, 0x66, 0x24, 0x53, 0x64, 0x4f, 0x93, 0x29, 0xce, 0x32, 0x11, 0x51, 0xee, 0xe5, 0x43, 0x3a, - 0x14, 0xc9, 0x81, 0x76, 0xc2, 0xda, 0x45, 0x62, 0x19, 0xdf, 0xca, 0xc0, 0xb9, 0x54, 0x9d, 0x39, - 0xab, 0x95, 0x2b, 0xe7, 0x95, 0x75, 0x18, 0xd7, 0xcc, 0x73, 0x8c, 0xb3, 0xc4, 0xbf, 0x18, 0xbc, - 0x2d, 0xc6, 0x4b, 0x30, 0x16, 0xbe, 0xd8, 0x92, 0x39, 0x39, 0x74, 0x68, 0x17, 0x29, 0x1f, 0xfe, - 0x6a, 0x00, 0xec, 0x0b, 0x9e, 0xaa, 0x69, 0xb5, 0xf1, 0x4f, 0xb3, 0x3c, 0x01, 0xee, 0x33, 0x1b, - 0xca, 0x36, 0xdd, 0x1e, 0x9a, 0x35, 0xa9, 0x77, 0x00, 0x5b, 0xb2, 0x0c, 0x23, 0xb5, 0xc0, 0x0e, - 0xba, 0x32, 0x6c, 0xc7, 0xac, 0x4a, 0x86, 0x05, 0x0f, 0x17, 0xa3, 0xc0, 0x0d, 0x3e, 0x42, 0x34, - 0x2d, 0x01, 0x42, 0x14, 0xb3, 0xea, 0xdf, 0xcf, 0xc0, 0x84, 0x4a, 0x4c, 0x3e, 0x84, 0x29, 0x19, - 0xa0, 0x93, 0x07, 0x33, 0x11, 0xcf, 0xcb, 0xd2, 0x14, 0x4c, 0x06, 0xe8, 0x54, 0x83, 0x9f, 0x68, - 0xf8, 0xea, 0x56, 0xdd, 0x51, 0x91, 0x49, 0x03, 0x48, 0x6b, 0xd7, 0xb6, 0x0e, 0xa9, 0x7d, 0x40, - 0xfd, 0xc0, 0xe2, 0x26, 0x3b, 0xe2, 0x15, 0x5a, 0xb2, 0x5f, 0xbb, 0x53, 0xe2, 0xd6, 0x3a, 0x6c, - 0x24, 0x44, 0xa4, 0xd5, 0x04, 0x8d, 0xfa, 0xb4, 0xd6, 0xda, 0xb5, 0xb7, 0x79, 0x21, 0xa7, 0x33, - 0xfe, 0x64, 0x84, 0x4f, 0x37, 0x11, 0xcf, 0x77, 0x07, 0xa6, 0x1e, 0x54, 0x2b, 0x65, 0x45, 0xd1, - 0xae, 0xa7, 0x83, 0x5a, 0x7e, 0x1c, 0x50, 0xaf, 0x6d, 0x37, 0xe5, 0x7d, 0x37, 0x3a, 0x82, 0x5c, - 0xa7, 0x51, 0x4f, 0x57, 0xc2, 0xc7, 0x38, 0xb2, 0x3a, 0xf8, 0xcd, 0x3a, 0xac, 0x23, 0x3b, 0x60, - 0x1d, 0xbe, 0xdd, 0x6a, 0xf6, 0xa8, 0x43, 0xe7, 0x48, 0xf6, 0xf1, 0xea, 0xbb, 0xdf, 0xdd, 0x51, - 0x6a, 0xc9, 0xf5, 0xaf, 0xe5, 0x65, 0x51, 0xcb, 0xf3, 0x42, 0xad, 0x92, 0x5a, 0x4f, 0x82, 0x6b, - 0xb4, 0x4f, 0x0c, 0x9d, 0xba, 0x4f, 0xfc, 0x8d, 0x0c, 0x8c, 0x70, 0xf1, 0x55, 0x4c, 0xe3, 0x1e, - 0x02, 0xf2, 0xf6, 0xd3, 0x11, 0x90, 0x0b, 0x78, 0x4e, 0x68, 0x13, 0x9a, 0x97, 0x91, 0x4a, 0x6c, - 0x5d, 0x48, 0x6f, 0x00, 0x7c, 0x32, 0xe3, 0x25, 0xa7, 0x2f, 0x0b, 0x52, 0x8d, 0x42, 0x69, 0x8c, - 0x9e, 0xea, 0xad, 0x2d, 0xc3, 0x8f, 0x8c, 0x8a, 0x50, 0x1a, 0x7a, 0x00, 0x8d, 0x55, 0x18, 0x13, - 0x01, 0x3a, 0x96, 0x8e, 0xc4, 0xc3, 0x78, 0x41, 0x33, 0x6d, 0x6a, 0x2c, 0x1d, 0x45, 0xa2, 0xb9, - 0x08, 0xf1, 0x61, 0xed, 0x1c, 0x69, 0xf9, 0x84, 0x25, 0x22, 0x79, 0xc0, 0xf3, 0x6c, 0xf2, 0x88, - 0xc7, 0x7a, 0x8a, 0x83, 0x10, 0x2e, 0x42, 0x7f, 0x49, 0x2f, 0xff, 0x94, 0x00, 0xc7, 0x11, 0x0f, - 0xb2, 0x0a, 0x05, 0x34, 0x87, 0xa3, 0x0d, 0xbe, 0x6a, 0xaa, 0x15, 0x1e, 0x04, 0x42, 0x98, 0x34, - 0x07, 0xbc, 0x4c, 0x2c, 0xb7, 0x98, 0xff, 0x65, 0x82, 0xd2, 0xf8, 0xe9, 0x2c, 0x14, 0xe2, 0xb3, - 0x8f, 0xbc, 0x0b, 0xe3, 0x61, 0xc4, 0xe9, 0xd0, 0x03, 0x1c, 0x1f, 0xc8, 0xa2, 0x10, 0xd5, 0x7a, - 0x76, 0x46, 0x05, 0x9d, 0x2c, 0x42, 0x9e, 0x2d, 0xe2, 0x78, 0x26, 0xe3, 0xae, 0x80, 0xa9, 0x1e, - 0x59, 0x12, 0x8f, 0xd4, 0x60, 0x96, 0x2d, 0x9a, 0x9a, 0xd3, 0xde, 0x6b, 0xd2, 0x55, 0x77, 0xcf, - 0xed, 0x06, 0x51, 0xb2, 0x42, 0x7e, 0x81, 0xb1, 0x5b, 0x4d, 0xad, 0x58, 0x4f, 0x55, 0x98, 0x42, - 0xad, 0xe4, 0x59, 0x1f, 0x1a, 0x20, 0xcf, 0xba, 0xb2, 0xb3, 0xfe, 0x61, 0x16, 0xc6, 0x95, 0xe9, - 0x47, 0xae, 0x41, 0xbe, 0xea, 0xaf, 0xba, 0xf5, 0x83, 0x30, 0x94, 0xe4, 0xe4, 0xc9, 0x71, 0x71, - 0xcc, 0xf1, 0xad, 0x26, 0x02, 0xcd, 0xb0, 0x98, 0x2c, 0xc1, 0x24, 0xff, 0x4b, 0x26, 0x0e, 0xc9, - 0x46, 0xba, 0x35, 0x8e, 0x2c, 0x53, 0x86, 0xa8, 0x9b, 0xad, 0x46, 0x42, 0xbe, 0x0a, 0xc0, 0x01, - 0x18, 0x7c, 0x20, 0x37, 0x78, 0xd8, 0x04, 0x51, 0x41, 0x4a, 0xd8, 0x01, 0x85, 0x21, 0xf9, 0x3a, - 0x0f, 0x68, 0x2d, 0x97, 0xcb, 0xd0, 0xe0, 0x71, 0x1f, 0x18, 0x7f, 0x2b, 0x3d, 0xfc, 0x8c, 0xca, - 0x52, 0xe4, 0xfa, 0x59, 0x30, 0x69, 0xdd, 0x7d, 0x44, 0xbd, 0xa3, 0x52, 0x80, 0x88, 0x0a, 0x86, - 0xf1, 0xbf, 0x64, 0x94, 0x45, 0x46, 0xd6, 0x31, 0x57, 0x37, 0x9f, 0x40, 0xc2, 0xa4, 0x2c, 0xbc, - 0x62, 0x48, 0xb8, 0x49, 0x77, 0x97, 0x9e, 0x17, 0xd6, 0x6d, 0xb3, 0xe1, 0x34, 0x8c, 0xe5, 0xf0, - 0xe6, 0x40, 0xf2, 0x05, 0x18, 0xc2, 0xae, 0xcb, 0x9e, 0xda, 0x34, 0x79, 0xca, 0x0f, 0xb1, 0x3e, - 0xc3, 0x86, 0x20, 0x25, 0xf9, 0xb4, 0x70, 0xdc, 0xe6, 0x9d, 0x3f, 0xa5, 0x1c, 0xd5, 0xec, 0x3b, - 0xc2, 0xe3, 0x3d, 0x8a, 0x40, 0xa4, 0xcc, 0x9e, 0xbf, 0x95, 0x85, 0x42, 0x7c, 0x69, 0x93, 0x0f, - 0x60, 0x42, 0x1e, 0xbf, 0x2b, 0xb6, 0xc8, 0x7a, 0x31, 0x21, 0xb2, 0x4e, 0xc8, 0x33, 0x78, 0xdf, - 0x56, 0x4d, 0xd0, 0x4c, 0x8d, 0x80, 0xc9, 0x42, 0x9b, 0x22, 0x22, 0xa0, 0xb2, 0xa8, 0x02, 0x37, - 0xe8, 0xc4, 0xe2, 0x28, 0x4b, 0x34, 0xf2, 0x16, 0xe4, 0xd6, 0xee, 0x94, 0x84, 0x83, 0x5f, 0x21, - 0x7e, 0x48, 0x73, 0x4b, 0x59, 0xdd, 0x6e, 0x97, 0xe1, 0x93, 0x55, 0x25, 0xe4, 0xf8, 0x88, 0x66, - 0x6e, 0x28, 0xc1, 0x61, 0xe3, 0x4e, 0x8f, 0x3d, 0x7e, 0x6f, 0x28, 0x9f, 0x2b, 0x0c, 0x89, 0x20, - 0xba, 0xff, 0x7d, 0x0e, 0xc6, 0xc2, 0xfa, 0x09, 0x51, 0xdd, 0xa6, 0xb9, 0x8b, 0x34, 0xb9, 0x08, - 0x79, 0x29, 0xdd, 0x09, 0x3f, 0xbf, 0x51, 0x5f, 0x48, 0x76, 0xf3, 0x20, 0xc5, 0x38, 0xbe, 0x2b, - 0x98, 0xf2, 0x27, 0xb9, 0x09, 0xa1, 0x8c, 0xd6, 0x4b, 0x98, 0x1b, 0x62, 0x03, 0x66, 0x86, 0x68, - 0x64, 0x0a, 0xb2, 0x0e, 0x0f, 0xcc, 0x36, 0x66, 0x66, 0x9d, 0x06, 0xf9, 0x00, 0xf2, 0x76, 0xa3, - 0x41, 0x1b, 0x96, 0x2d, 0x6d, 0xb3, 0xfa, 0x4d, 0x9a, 0x3c, 0xe3, 0xc6, 0xcf, 0x0c, 0xa4, 0x2a, - 0x05, 0xa4, 0x04, 0x63, 0x4d, 0x9b, 0x5b, 0x7b, 0x36, 0x06, 0x38, 0x80, 0x22, 0x0e, 0x79, 0x46, - 0xb6, 0xe5, 0xd3, 0x06, 0x79, 0x0d, 0x86, 0xd8, 0x68, 0x8a, 0x13, 0x47, 0x0a, 0x95, 0x6c, 0x30, - 0x79, 0x87, 0xad, 0x3c, 0x67, 0x22, 0x02, 0x79, 0x05, 0x72, 0xdd, 0xc5, 0x5d, 0x71, 0x96, 0x14, - 0xa2, 0xf0, 0xff, 0x21, 0x1a, 0x2b, 0x26, 0xb7, 0x20, 0x7f, 0xa8, 0x47, 0x8e, 0x3f, 0x17, 0x1b, - 0xc6, 0x10, 0x3f, 0x44, 0x24, 0xaf, 0x41, 0xce, 0xf7, 0x5d, 0x61, 0xd0, 0x34, 0x1b, 0x5a, 0x99, - 0x3e, 0x08, 0x47, 0x8d, 0x71, 0xf7, 0x7d, 0x77, 0x29, 0x0f, 0x23, 0xfc, 0x80, 0x31, 0x2e, 0x01, - 0x44, 0xdf, 0x98, 0xf4, 0xdb, 0x34, 0xbe, 0x0a, 0x63, 0xe1, 0xb7, 0x91, 0x17, 0x01, 0x0e, 0xe8, - 0x91, 0xb5, 0x6f, 0xb7, 0x1b, 0x4d, 0x2e, 0x9d, 0x4e, 0x98, 0x63, 0x07, 0xf4, 0x68, 0x05, 0x01, - 0xe4, 0x02, 0x8c, 0x76, 0xd8, 0xf0, 0x8b, 0x39, 0x3e, 0x61, 0x8e, 0x74, 0xba, 0x3b, 0x6c, 0x2a, - 0xcf, 0xc3, 0x28, 0xea, 0x59, 0xc5, 0x8a, 0x9c, 0x34, 0xe5, 0x4f, 0xe3, 0x4f, 0x73, 0x98, 0x5e, - 0x49, 0x69, 0x10, 0x79, 0x19, 0x26, 0xeb, 0x1e, 0xc5, 0xb3, 0xcc, 0x66, 0x12, 0x9a, 0xa8, 0x67, - 0x22, 0x02, 0x56, 0x1b, 0xe4, 0x0a, 0x4c, 0x77, 0xba, 0x3b, 0x4d, 0xa7, 0xce, 0x6a, 0xb3, 0xea, - 0x3b, 0x22, 0x1f, 0xc4, 0x84, 0x39, 0xc9, 0xc1, 0xf7, 0xe9, 0x51, 0x79, 0x07, 0x23, 0x0f, 0x16, - 0xd4, 0xc0, 0xd1, 0x41, 0x98, 0xf8, 0xde, 0x9c, 0x56, 0xe0, 0x68, 0x9b, 0x79, 0x1e, 0x46, 0x6c, - 0x7b, 0xaf, 0xeb, 0xf0, 0x08, 0x61, 0x13, 0xa6, 0xf8, 0x45, 0x3e, 0x05, 0x33, 0x51, 0x2c, 0x73, - 0xd9, 0x8c, 0x61, 0x6c, 0x46, 0x21, 0x2c, 0x28, 0x73, 0x38, 0x79, 0x13, 0x88, 0x5a, 0x9f, 0xbb, - 0xf3, 0x11, 0xad, 0xf3, 0x39, 0x39, 0x61, 0xce, 0x28, 0x25, 0x0f, 0xb0, 0x80, 0xbc, 0x04, 0x13, - 0x1e, 0xf5, 0x51, 0x3a, 0xc4, 0x6e, 0xc3, 0xec, 0x83, 0xe6, 0xb8, 0x84, 0xb1, 0xbe, 0xbb, 0x0a, - 0x05, 0xa5, 0x3b, 0x30, 0x36, 0x37, 0x4f, 0x86, 0x60, 0x4e, 0x45, 0x70, 0xb3, 0x53, 0x6d, 0x90, - 0x2f, 0xc1, 0x82, 0x82, 0xc9, 0x13, 0x21, 0x5a, 0xb4, 0xe9, 0xec, 0x39, 0x3b, 0x4d, 0x2a, 0xe6, - 0x5b, 0x72, 0x56, 0x87, 0x57, 0x48, 0x73, 0x3e, 0xa2, 0xe6, 0x29, 0x12, 0x97, 0x05, 0x2d, 0x59, - 0x85, 0xb9, 0x18, 0x67, 0xda, 0xb0, 0xba, 0x9d, 0x9e, 0x21, 0xf9, 0x22, 0x9e, 0x44, 0xe7, 0x49, - 0x1b, 0x5b, 0x1d, 0xe3, 0x9b, 0x30, 0xa1, 0xce, 0x49, 0xd6, 0x09, 0xaa, 0x5c, 0x22, 0x66, 0xdf, - 0x78, 0x08, 0xab, 0xb2, 0x7b, 0xe1, 0x54, 0x84, 0x12, 0x84, 0x39, 0xfe, 0xcd, 0xc9, 0x10, 0x8a, - 0x43, 0xf8, 0x12, 0x4c, 0x34, 0x1c, 0xbf, 0xd3, 0xb4, 0x8f, 0xac, 0x28, 0xc3, 0xb7, 0x39, 0x2e, - 0x60, 0xa8, 0xf8, 0x59, 0x82, 0x99, 0xc4, 0x3e, 0xa8, 0x48, 0x1a, 0x7c, 0x5f, 0xef, 0x2f, 0x69, - 0x18, 0x6d, 0x98, 0x50, 0xcf, 0xb5, 0x53, 0x12, 0x97, 0x9c, 0xc7, 0x30, 0x3c, 0x7c, 0xd3, 0x1f, - 0x39, 0x39, 0x2e, 0x66, 0x9d, 0x06, 0x06, 0xdf, 0xb9, 0x0a, 0x79, 0x29, 0xb1, 0x09, 0x41, 0x09, - 0x1f, 0x13, 0xe4, 0xd3, 0xa4, 0x19, 0x96, 0x1a, 0xaf, 0xc1, 0xa8, 0x38, 0xba, 0xfa, 0x3f, 0x21, - 0x18, 0x3f, 0x94, 0x85, 0x69, 0x93, 0xb2, 0x8d, 0x95, 0xf2, 0x6c, 0x45, 0xcf, 0xec, 0x15, 0x3d, - 0x3d, 0x98, 0xab, 0xd6, 0xb6, 0x3e, 0x79, 0x82, 0x7e, 0x25, 0x03, 0xb3, 0x29, 0xb8, 0x1f, 0x2b, - 0x4f, 0xee, 0x6d, 0x18, 0xab, 0x38, 0x76, 0xb3, 0xd4, 0x68, 0x84, 0x31, 0x79, 0x50, 0xce, 0xc7, - 0x64, 0x5a, 0x36, 0x83, 0xaa, 0x42, 0x4c, 0x88, 0x4a, 0x5e, 0x17, 0x93, 0x22, 0xca, 0x32, 0x8f, - 0x93, 0xe2, 0x3b, 0xc7, 0x45, 0xe0, 0xdf, 0xb4, 0x19, 0x4e, 0x11, 0x0c, 0xb0, 0xcc, 0x81, 0x91, - 0x5f, 0xd5, 0x33, 0x3b, 0x74, 0xe9, 0x01, 0x96, 0xe3, 0xcd, 0x1b, 0x28, 0x55, 0xd0, 0x8f, 0x67, - 0xe1, 0x7c, 0x3a, 0xe1, 0xc7, 0x4d, 0x79, 0x8c, 0x49, 0x9a, 0x94, 0xa0, 0xf0, 0x98, 0xf2, 0x98, - 0x67, 0x74, 0x42, 0xfc, 0x08, 0x81, 0xec, 0xc2, 0xe4, 0xaa, 0xed, 0x07, 0x2b, 0xd4, 0xf6, 0x82, - 0x1d, 0x6a, 0x07, 0x03, 0x48, 0xf2, 0xd2, 0x9a, 0x62, 0x1e, 0x85, 0x89, 0x7d, 0x49, 0x19, 0x93, - 0xb5, 0x75, 0xb6, 0xe1, 0x44, 0x19, 0x1a, 0x60, 0xa2, 0x7c, 0x03, 0xa6, 0x6b, 0xb4, 0x65, 0x77, - 0xf6, 0x5d, 0x4f, 0xc6, 0x4b, 0xb8, 0x0e, 0x93, 0x21, 0x28, 0x75, 0xb6, 0xe8, 0xc5, 0x1a, 0xbe, - 0xd2, 0x11, 0xd1, 0x56, 0xa2, 0x17, 0x1b, 0x7f, 0x3b, 0x0b, 0x17, 0x4a, 0x75, 0x61, 0x1a, 0x2a, - 0x0a, 0xa4, 0x05, 0xfb, 0x27, 0x5c, 0x37, 0xb9, 0x01, 0x63, 0x6b, 0xf6, 0xe3, 0x55, 0x6a, 0xfb, - 0xd4, 0x17, 0x09, 0x27, 0xb9, 0xd8, 0x6b, 0x3f, 0x8e, 0x1e, 0x7f, 0xcc, 0x08, 0x47, 0x55, 0x23, - 0x0c, 0x3d, 0xa1, 0x1a, 0xc1, 0x80, 0x91, 0x15, 0xb7, 0xd9, 0x10, 0x67, 0xbd, 0x78, 0x71, 0xde, - 0x47, 0x88, 0x29, 0x4a, 0x8c, 0x3f, 0xce, 0xc0, 0x54, 0xf8, 0xc5, 0xf8, 0x09, 0x9f, 0x78, 0x97, - 0x5c, 0x81, 0x51, 0xac, 0x28, 0xcc, 0x8c, 0x8f, 0x87, 0x46, 0x93, 0x62, 0xda, 0xc0, 0x86, 0x29, - 0x0b, 0xd5, 0x9e, 0x18, 0x7e, 0xb2, 0x9e, 0x30, 0xfe, 0x01, 0x3e, 0x66, 0xab, 0xad, 0x64, 0x27, - 0x91, 0xf2, 0x21, 0x99, 0x01, 0x3f, 0x24, 0xfb, 0xd4, 0x86, 0x24, 0xd7, 0x73, 0x48, 0x7e, 0x38, - 0x0b, 0xe3, 0xe1, 0xc7, 0x7e, 0x97, 0x65, 0x26, 0x08, 0xdb, 0x35, 0x50, 0x8c, 0xa3, 0x9a, 0xb2, - 0x57, 0x88, 0x50, 0x42, 0x5f, 0x80, 0x11, 0xb1, 0x98, 0x32, 0x31, 0x4b, 0xee, 0xd8, 0xe8, 0x2e, - 0x4d, 0x09, 0xd6, 0x23, 0x38, 0xa0, 0xbe, 0x29, 0xe8, 0x30, 0x88, 0xd4, 0x36, 0xdd, 0x11, 0xb6, - 0x0d, 0xcf, 0xec, 0x19, 0x95, 0x1e, 0x44, 0x2a, 0x6a, 0xd8, 0x40, 0xa7, 0xd3, 0x3f, 0xcf, 0x43, - 0x21, 0x4e, 0x72, 0x7a, 0xee, 0x87, 0x8d, 0xee, 0x0e, 0xbf, 0xaa, 0xf0, 0xdc, 0x0f, 0x9d, 0xee, - 0x8e, 0xc9, 0x60, 0x68, 0xfa, 0xe4, 0x39, 0x8f, 0xb0, 0xd5, 0x13, 0xc2, 0xf4, 0xc9, 0x73, 0x1e, - 0x69, 0xa6, 0x4f, 0x9e, 0xf3, 0x08, 0x15, 0x09, 0xab, 0x35, 0x0c, 0xb0, 0x80, 0xf7, 0x14, 0xa1, - 0x48, 0x68, 0xfa, 0xf1, 0x3c, 0x6e, 0x12, 0x8d, 0x1d, 0x95, 0x4b, 0xd4, 0xf6, 0x44, 0x9e, 0x02, - 0xb1, 0x9d, 0xe1, 0x51, 0xb9, 0x83, 0x60, 0x2b, 0x60, 0x70, 0x53, 0x45, 0x22, 0x4d, 0x20, 0xca, - 0x4f, 0xb9, 0x80, 0x4f, 0xbf, 0x5b, 0x4b, 0x2b, 0xcc, 0x39, 0x95, 0xb5, 0xa5, 0xae, 0xe6, 0x14, - 0xbe, 0x4f, 0x53, 0xfb, 0xbb, 0x21, 0x82, 0xaf, 0xa2, 0x02, 0x29, 0x7f, 0x2a, 0x33, 0x19, 0x18, - 0x06, 0x78, 0x70, 0xd6, 0x50, 0x8d, 0x14, 0x31, 0x21, 0xef, 0xc3, 0xb8, 0x1a, 0x36, 0x83, 0x07, - 0x77, 0x78, 0x81, 0xc7, 0xd3, 0xec, 0x91, 0xf9, 0x57, 0x25, 0x20, 0x3b, 0x70, 0xa1, 0xec, 0xb6, - 0xfd, 0x6e, 0x4b, 0x46, 0xee, 0x8c, 0xe2, 0x85, 0x03, 0x0e, 0x05, 0xfa, 0xe0, 0xd7, 0x05, 0x8a, - 0x88, 0xd2, 0x20, 0xdd, 0x64, 0xf4, 0x0b, 0x48, 0x2f, 0x46, 0x64, 0x13, 0xc6, 0x51, 0x83, 0x2a, - 0x4c, 0x1e, 0xc7, 0xf5, 0x6d, 0x23, 0x2a, 0xa9, 0xb0, 0x85, 0xc1, 0xa3, 0xc6, 0xd9, 0xad, 0xa6, - 0xf4, 0xd2, 0x50, 0x35, 0xc1, 0x0a, 0x32, 0xf9, 0x2a, 0x4c, 0xf1, 0x2b, 0xda, 0x36, 0xdd, 0xe1, - 0x73, 0x67, 0x42, 0xd3, 0x44, 0xe8, 0x85, 0xfc, 0x31, 0x5f, 0xe8, 0xad, 0x0f, 0xe9, 0x0e, 0x1f, - 0x7b, 0xcd, 0x47, 0x4a, 0xc3, 0x27, 0x5b, 0x30, 0xbb, 0x62, 0xfb, 0x1c, 0xa8, 0xc4, 0x3f, 0x98, - 0x44, 0x0d, 0x2d, 0xda, 0xae, 0xef, 0xdb, 0xbe, 0x54, 0x84, 0xa7, 0xc6, 0x3b, 0x48, 0xa3, 0x27, - 0x3f, 0x94, 0x81, 0x79, 0x4d, 0x4f, 0x2e, 0xec, 0xcc, 0x5a, 0xb4, 0x1d, 0xa0, 0x33, 0xd4, 0xd4, - 0x62, 0x51, 0x0a, 0xa5, 0x3d, 0xd0, 0xf8, 0x90, 0xc4, 0x54, 0xf1, 0x5e, 0x54, 0xae, 0x1a, 0x85, - 0xf7, 0xe2, 0x21, 0x16, 0x2a, 0xae, 0xe9, 0x69, 0x7d, 0xa1, 0xc6, 0xd6, 0xb5, 0x44, 0x33, 0x6e, - 0xc7, 0xfb, 0x5b, 0x28, 0xba, 0x32, 0xa1, 0xa2, 0x6b, 0x0e, 0x86, 0xb1, 0x57, 0x65, 0x14, 0x2d, - 0xfc, 0x61, 0x7c, 0x5a, 0xdd, 0x87, 0x84, 0x58, 0xd8, 0x77, 0x1f, 0x32, 0xfe, 0xc7, 0x11, 0x98, - 0x8e, 0x4d, 0x0b, 0x71, 0x4f, 0xcd, 0x24, 0xee, 0xa9, 0x35, 0x00, 0xae, 0xea, 0x1d, 0x50, 0x27, - 0x2b, 0x1d, 0x31, 0xc7, 0x85, 0x1b, 0x75, 0xb8, 0xa6, 0x14, 0x36, 0x8c, 0x29, 0x5f, 0xb1, 0x03, - 0xea, 0xc8, 0x43, 0xa6, 0x7c, 0xd1, 0x2b, 0x4c, 0x23, 0x36, 0xa4, 0x08, 0xc3, 0x18, 0x3f, 0x57, - 0xf5, 0x83, 0x75, 0x18, 0xc0, 0xe4, 0x70, 0xf2, 0x32, 0x8c, 0x30, 0x21, 0xaa, 0x5a, 0x11, 0x9b, - 0x20, 0x9e, 0x2d, 0x4c, 0xca, 0x62, 0x12, 0x8b, 0x28, 0x22, 0xb7, 0x61, 0x82, 0xff, 0x25, 0xc2, - 0xec, 0x8c, 0xe8, 0xc6, 0x8f, 0x96, 0xd3, 0x90, 0x91, 0x76, 0x34, 0x3c, 0x76, 0xbb, 0xa8, 0x75, - 0x51, 0xad, 0x53, 0xad, 0x88, 0x80, 0xeb, 0x78, 0xbb, 0xf0, 0x39, 0x90, 0x55, 0x11, 0x21, 0x30, - 0x59, 0x46, 0x78, 0xa3, 0xe4, 0xf1, 0x4e, 0x89, 0xb2, 0x0c, 0xf7, 0x42, 0x31, 0x45, 0x09, 0xb9, - 0xc6, 0x5f, 0x62, 0x50, 0x2c, 0xe4, 0x79, 0x2b, 0xf1, 0xdd, 0x02, 0x15, 0x13, 0x28, 0x1b, 0x86, - 0xc5, 0xac, 0x72, 0xf6, 0xf7, 0x72, 0xcb, 0x76, 0x9a, 0x62, 0x5b, 0xc1, 0xca, 0x11, 0x97, 0x32, - 0xa8, 0x19, 0x21, 0x90, 0x77, 0x61, 0x8a, 0xfd, 0x28, 0xbb, 0xad, 0x96, 0xdb, 0x46, 0xf6, 0xe3, - 0x51, 0x20, 0x3d, 0x24, 0xa9, 0x63, 0x11, 0xaf, 0x25, 0x86, 0xcb, 0xce, 0x13, 0x7c, 0xe5, 0xed, - 0xf2, 0x37, 0xa2, 0x89, 0xe8, 0x3c, 0x41, 0x52, 0x9f, 0xc3, 0x4d, 0x15, 0x89, 0xbc, 0x0d, 0x93, - 0xec, 0xe7, 0x5d, 0xe7, 0x11, 0xe5, 0x15, 0x4e, 0x46, 0xe6, 0x0d, 0x48, 0xb5, 0xc7, 0x4a, 0x78, - 0x7d, 0x3a, 0x26, 0xf9, 0x22, 0x9c, 0x43, 0x4e, 0x75, 0xb7, 0x43, 0x1b, 0xa5, 0xdd, 0x5d, 0xa7, - 0xe9, 0x70, 0x6b, 0x34, 0x1e, 0x50, 0x06, 0x75, 0xf0, 0xbc, 0x62, 0xc4, 0xb0, 0xec, 0x08, 0xc5, - 0x4c, 0xa7, 0x24, 0xdb, 0x50, 0x28, 0x77, 0xfd, 0xc0, 0x6d, 0x95, 0x82, 0xc0, 0x73, 0x76, 0xba, - 0x01, 0xf5, 0xe7, 0xa7, 0xb5, 0xb0, 0x2b, 0x6c, 0x71, 0x84, 0x85, 0x5c, 0x1f, 0x54, 0x47, 0x0a, - 0xcb, 0x0e, 0x49, 0xcc, 0x04, 0x13, 0xe3, 0x5f, 0x64, 0x60, 0x52, 0x23, 0x25, 0x6f, 0xc1, 0xc4, - 0x1d, 0xcf, 0xa1, 0xed, 0x46, 0xf3, 0x48, 0xb9, 0xa8, 0xe2, 0x2d, 0x66, 0x57, 0xc0, 0x79, 0xab, - 0x35, 0xb4, 0x50, 0xcf, 0x93, 0x4d, 0x35, 0x15, 0xbd, 0xc1, 0xdd, 0xb1, 0xc5, 0x04, 0xcd, 0x45, - 0x71, 0xa0, 0x70, 0x82, 0x8a, 0xd9, 0xa9, 0xa0, 0x90, 0xf7, 0x60, 0x84, 0xbf, 0x07, 0x0b, 0xbb, - 0xc5, 0x8b, 0x69, 0xcd, 0xe4, 0xae, 0xff, 0x38, 0x11, 0xd1, 0xe8, 0xc7, 0x37, 0x05, 0x91, 0xf1, - 0xb3, 0x19, 0x20, 0x49, 0xd4, 0x53, 0xf4, 0x5e, 0xa7, 0x1a, 0x13, 0x7d, 0x21, 0x5c, 0x8d, 0x39, - 0x4d, 0x67, 0xce, 0x6a, 0xe2, 0x05, 0xbc, 0xe3, 0xc5, 0xaa, 0x53, 0x15, 0x71, 0xbc, 0xd8, 0xf8, - 0xc1, 0x2c, 0x40, 0x84, 0x4d, 0x3e, 0xc7, 0xd3, 0x94, 0x7d, 0xb1, 0x6b, 0x37, 0x9d, 0x5d, 0x47, - 0x8f, 0xdb, 0x8b, 0x4c, 0xbe, 0x21, 0x4b, 0x4c, 0x1d, 0x91, 0x7c, 0x00, 0xd3, 0xb5, 0x0d, 0x9d, - 0x56, 0x31, 0x8b, 0xf7, 0x3b, 0x56, 0x8c, 0x3c, 0x8e, 0x8d, 0xf6, 0xc9, 0xea, 0x68, 0x70, 0xfb, - 0x64, 0x3e, 0x10, 0xa2, 0x84, 0x6d, 0x2c, 0xb5, 0x0d, 0x61, 0xf9, 0xdf, 0x08, 0x5f, 0x35, 0xf1, - 0xeb, 0xfc, 0x8e, 0xd5, 0x11, 0x2e, 0x01, 0x6c, 0x9f, 0xd0, 0xf0, 0xa2, 0x8e, 0x1c, 0xee, 0xe1, - 0xde, 0xff, 0x73, 0xa8, 0xf6, 0x6b, 0xb9, 0x01, 0x15, 0xda, 0x8e, 0x67, 0xf6, 0xde, 0x13, 0x19, - 0x13, 0x0c, 0x6b, 0x5e, 0xcb, 0x5a, 0xeb, 0x84, 0xc1, 0xcc, 0xad, 0xe8, 0x92, 0xc2, 0xcd, 0x0a, - 0x52, 0x6c, 0x6c, 0xfe, 0x5e, 0x06, 0xce, 0xa5, 0xd2, 0x92, 0xeb, 0x00, 0x91, 0x4e, 0x49, 0xf4, - 0x12, 0xee, 0x98, 0x51, 0xf4, 0x23, 0x53, 0xc1, 0x20, 0x5f, 0x89, 0x6b, 0x83, 0x4e, 0x3f, 0x08, - 0x17, 0x64, 0xd0, 0x41, 0x5d, 0x1b, 0x94, 0xa2, 0x03, 0x32, 0x7e, 0x25, 0x07, 0x33, 0x4a, 0x70, - 0x25, 0xfe, 0xad, 0xa7, 0xd8, 0x8b, 0x1f, 0xc0, 0x04, 0x6b, 0x8d, 0x53, 0x17, 0x6e, 0x37, 0xdc, - 0xf0, 0xe5, 0xf5, 0x84, 0xdf, 0xa9, 0xe0, 0x76, 0x5d, 0x45, 0xe6, 0xa1, 0x40, 0x71, 0xeb, 0xc4, - 0x07, 0x89, 0x7a, 0xd2, 0xe5, 0x46, 0x63, 0x4e, 0x7c, 0x98, 0xac, 0x1c, 0xb5, 0xed, 0x56, 0x58, - 0x1b, 0x37, 0x80, 0xf9, 0x54, 0xcf, 0xda, 0x34, 0x6c, 0x5e, 0x5d, 0xe4, 0xa1, 0xc5, 0xcb, 0x52, - 0x82, 0x03, 0x68, 0x54, 0x0b, 0x1f, 0xc0, 0x4c, 0xe2, 0xa3, 0xcf, 0x14, 0x95, 0x74, 0x1b, 0x48, - 0xf2, 0x3b, 0x52, 0x38, 0x7c, 0x4a, 0x8f, 0x79, 0x7b, 0x2e, 0x7c, 0xbc, 0x6e, 0xb5, 0xec, 0x76, - 0x83, 0x9b, 0xd3, 0x2c, 0xaa, 0x31, 0x4b, 0x7f, 0x2e, 0xab, 0xfa, 0xfe, 0x3e, 0xeb, 0xab, 0xee, - 0x0b, 0xda, 0x6d, 0xf8, 0x52, 0xaf, 0x31, 0x1d, 0x48, 0xeb, 0xf0, 0xed, 0x1c, 0x5c, 0xe8, 0x41, - 0x49, 0x8e, 0xe2, 0x93, 0x88, 0x6b, 0x21, 0x6e, 0xf6, 0xaf, 0xf0, 0x69, 0x4c, 0x25, 0xf2, 0x39, - 0x1e, 0xfd, 0xa3, 0xee, 0xb6, 0x77, 0x9d, 0x3d, 0x71, 0xff, 0x46, 0x35, 0xfe, 0x41, 0x08, 0x8d, - 0x87, 0xfd, 0xe0, 0x50, 0xf2, 0x01, 0x0c, 0xa3, 0xe3, 0x77, 0x2c, 0xbc, 0x23, 0xc3, 0x40, 0xb8, - 0x12, 0xa0, 0x94, 0xfd, 0xd4, 0x02, 0x94, 0x32, 0x00, 0xf9, 0x2c, 0xe4, 0x4a, 0xdb, 0x35, 0x31, - 0x2e, 0x53, 0x2a, 0xf9, 0x76, 0x2d, 0x4a, 0xae, 0x62, 0x6b, 0x59, 0x50, 0x18, 0x05, 0x23, 0xbc, - 0x5b, 0xde, 0x10, 0xa3, 0xa2, 0x12, 0xde, 0x2d, 0x6f, 0x44, 0x84, 0x7b, 0x75, 0x2d, 0x58, 0xd6, - 0xdd, 0xf2, 0xc6, 0x27, 0x37, 0xed, 0xff, 0xbd, 0x2c, 0x0f, 0x59, 0xc2, 0x1b, 0xf6, 0x01, 0x4c, - 0x68, 0x31, 0xc9, 0x33, 0x91, 0x3c, 0x16, 0xc6, 0x8f, 0x8f, 0x59, 0x0c, 0x69, 0x04, 0x32, 0x4d, - 0x11, 0xfb, 0x8d, 0x12, 0xaf, 0x6a, 0x6c, 0x13, 0x72, 0x40, 0x99, 0x38, 0x9e, 0xa6, 0x28, 0x24, - 0x21, 0xb7, 0x20, 0xbf, 0x49, 0xdb, 0x76, 0x3b, 0x08, 0x15, 0xa2, 0x68, 0x5c, 0x1c, 0x20, 0x4c, - 0x97, 0x1a, 0x42, 0x44, 0x34, 0x84, 0xed, 0xee, 0xf8, 0x75, 0xcf, 0xc1, 0xd0, 0x46, 0xe1, 0x59, - 0xcc, 0x0d, 0x61, 0x95, 0x12, 0x9d, 0x41, 0x8c, 0xc8, 0xf8, 0xb9, 0x0c, 0x8c, 0x8a, 0x81, 0xe4, - 0xe9, 0xe5, 0xf6, 0xa2, 0xb3, 0x44, 0x38, 0x0f, 0xec, 0x39, 0x71, 0xe7, 0x81, 0x3d, 0x1e, 0x3f, - 0x68, 0x4c, 0x38, 0xd6, 0x85, 0x4f, 0x83, 0x38, 0x1b, 0xa5, 0xdb, 0xa7, 0x9e, 0x3d, 0x2c, 0x44, - 0x1d, 0xd4, 0x21, 0xcb, 0xf8, 0x3b, 0xe2, 0xcb, 0xee, 0x96, 0x37, 0xc8, 0x22, 0xe4, 0x57, 0x5d, - 0x1e, 0x0a, 0x4b, 0xcd, 0x15, 0xdc, 0x14, 0x30, 0xb5, 0x83, 0x24, 0x1e, 0xfb, 0xbe, 0x0d, 0xcf, - 0x15, 0x77, 0x19, 0xe5, 0xfb, 0x3a, 0x1c, 0x18, 0xfb, 0xbe, 0x10, 0x75, 0xe0, 0xef, 0xa3, 0x29, - 0x9b, 0xc4, 0xc3, 0x5b, 0x98, 0xbf, 0xe5, 0x9e, 0xea, 0xe8, 0x26, 0x8a, 0xe4, 0x4e, 0xb1, 0xd0, - 0x6b, 0xa7, 0x78, 0x78, 0xcb, 0x4c, 0xa1, 0xc2, 0x77, 0xb5, 0x08, 0x5c, 0xa3, 0xde, 0xa3, 0x67, - 0x78, 0x97, 0x4e, 0x7f, 0x57, 0x8b, 0x37, 0x6f, 0xa0, 0x4d, 0xfa, 0xf7, 0xb3, 0x70, 0x3e, 0x9d, - 0x50, 0x6d, 0x4b, 0xa6, 0x4f, 0x5b, 0xae, 0x42, 0x7e, 0xc5, 0xf5, 0x03, 0xc5, 0x48, 0x10, 0xd5, - 0xff, 0xfb, 0x02, 0x66, 0x86, 0xa5, 0xec, 0xce, 0xcd, 0xfe, 0x0e, 0x97, 0x27, 0xf2, 0xc3, 0x40, - 0x1d, 0xec, 0xce, 0xcd, 0x8b, 0xc8, 0x5d, 0xc8, 0x9b, 0xc2, 0xd1, 0x2a, 0xd6, 0x35, 0x12, 0x1c, - 0x4a, 0x53, 0xc4, 0x13, 0x10, 0x2d, 0x34, 0xbc, 0x80, 0x91, 0x12, 0x8c, 0x8a, 0xd1, 0x8f, 0x3d, - 0x1d, 0xa7, 0x4c, 0x19, 0x3d, 0x5b, 0x83, 0xa4, 0x63, 0x3b, 0x0a, 0x3e, 0x02, 0x56, 0x2b, 0xd2, - 0x67, 0x0a, 0x77, 0x14, 0xfe, 0x48, 0xa8, 0xdb, 0x63, 0x86, 0x88, 0xc6, 0x0f, 0x65, 0x01, 0xa4, - 0xd6, 0xe6, 0x99, 0x9d, 0x61, 0x9f, 0xd5, 0x66, 0x98, 0x62, 0x6f, 0x34, 0x78, 0x3a, 0xe4, 0x07, - 0x68, 0xce, 0x33, 0x78, 0x32, 0xe4, 0x22, 0x0c, 0x6f, 0x46, 0x0a, 0x2d, 0xe1, 0x92, 0x82, 0xea, - 0x68, 0x0e, 0x37, 0x76, 0x60, 0xee, 0x2e, 0x0d, 0x22, 0xf5, 0x96, 0x7c, 0x7a, 0xec, 0xcf, 0xf6, - 0x0d, 0x18, 0x13, 0xf8, 0xe1, 0xfe, 0xc5, 0x75, 0x31, 0x22, 0xf6, 0x0d, 0xea, 0x62, 0x24, 0x02, - 0xdb, 0x8d, 0x2a, 0xb4, 0x49, 0x03, 0xfa, 0xc9, 0x56, 0x53, 0x03, 0xc2, 0x9b, 0x82, 0x2d, 0x1b, - 0xac, 0x86, 0x53, 0xfb, 0xe7, 0x21, 0x9c, 0x0b, 0xbf, 0xfd, 0x69, 0xf2, 0xbd, 0xc1, 0xae, 0x94, - 0x22, 0xd1, 0x41, 0xc4, 0xb1, 0x8f, 0xed, 0xc9, 0x63, 0x58, 0x90, 0x04, 0xdb, 0x4e, 0x68, 0x38, - 0x39, 0x10, 0x2d, 0x79, 0x17, 0xc6, 0x15, 0x1a, 0x11, 0xa8, 0x1f, 0xd5, 0xd4, 0x87, 0x4e, 0xb0, - 0x6f, 0xf9, 0x1c, 0xae, 0xaa, 0xa9, 0x15, 0x74, 0xe3, 0xcb, 0xf0, 0x7c, 0xe8, 0x36, 0x94, 0x52, - 0x75, 0x8c, 0x79, 0xe6, 0x6c, 0xcc, 0xd7, 0xa3, 0x66, 0x55, 0xdb, 0xa1, 0x67, 0xb4, 0xe4, 0x4d, - 0xd4, 0x66, 0x89, 0xc6, 0xbc, 0x90, 0xf0, 0xb5, 0x56, 0x5c, 0xaa, 0x8d, 0x77, 0x94, 0x8f, 0x4d, - 0x61, 0xa8, 0x11, 0x67, 0xe2, 0xc4, 0x3f, 0x94, 0x85, 0xe9, 0x07, 0xd5, 0x4a, 0x39, 0xb4, 0x3e, - 0xfa, 0x2e, 0x4b, 0xd6, 0xac, 0xb5, 0xad, 0xf7, 0x7e, 0x63, 0x6c, 0xc1, 0x6c, 0xac, 0x1b, 0x50, - 0x74, 0x78, 0x9f, 0x3b, 0x9c, 0x84, 0x60, 0x29, 0x36, 0x9c, 0x4f, 0x63, 0xff, 0xf0, 0x96, 0x19, - 0xc3, 0x36, 0xfe, 0x4b, 0x88, 0xf1, 0x15, 0x5b, 0xd8, 0x1b, 0x30, 0x56, 0xf5, 0xfd, 0x2e, 0xf5, - 0xb6, 0xcc, 0x55, 0x55, 0x55, 0xe0, 0x20, 0xd0, 0xea, 0x7a, 0x4d, 0x33, 0x42, 0x20, 0xd7, 0x20, - 0x2f, 0x82, 0xa4, 0xcb, 0x3d, 0x01, 0xb5, 0xb6, 0x61, 0x8c, 0x75, 0x33, 0x2c, 0x26, 0x6f, 0xc1, - 0x04, 0xff, 0x9b, 0xcf, 0x36, 0xd1, 0xe1, 0xa8, 0x1c, 0x14, 0xe8, 0x7c, 0x76, 0x9a, 0x1a, 0x1a, - 0x79, 0x1d, 0x72, 0xa5, 0xb2, 0x29, 0xd4, 0x41, 0x42, 0x6e, 0xf4, 0x2c, 0xae, 0xb3, 0xd3, 0x2e, - 0x11, 0x65, 0x93, 0x49, 0x7f, 0x32, 0xd8, 0x84, 0xd0, 0x64, 0xe3, 0x0c, 0x90, 0xda, 0xa6, 0xd8, - 0x61, 0x86, 0x30, 0x72, 0x03, 0x46, 0x2b, 0xdc, 0x64, 0x4e, 0xe8, 0xb1, 0x79, 0x26, 0x42, 0x0e, - 0xd2, 0x82, 0x2b, 0x70, 0x10, 0xb9, 0x26, 0x33, 0xb4, 0xe5, 0x23, 0xbf, 0x95, 0x1e, 0x69, 0xd8, - 0xde, 0x80, 0x11, 0x11, 0x4a, 0x7c, 0x4c, 0xc9, 0xdd, 0x12, 0x0f, 0x21, 0x2e, 0x70, 0x92, 0x0e, - 0xac, 0xf0, 0x34, 0x1d, 0x58, 0x77, 0xe0, 0xc2, 0x5d, 0xd4, 0xde, 0xe8, 0x01, 0xb1, 0xb6, 0xcc, - 0xaa, 0xd0, 0x87, 0xe3, 0x33, 0x10, 0x57, 0xf0, 0xc4, 0x63, 0x6a, 0x59, 0x5d, 0x4f, 0x4d, 0xac, - 0xdb, 0x8b, 0x11, 0xf9, 0x12, 0xcc, 0xa5, 0x15, 0x09, 0xad, 0x39, 0x86, 0x7e, 0x4a, 0xaf, 0x40, - 0x0d, 0xfd, 0x94, 0xc6, 0x81, 0xac, 0x42, 0x81, 0xc3, 0x4b, 0x8d, 0x96, 0xd3, 0xe6, 0x9a, 0x7f, - 0xae, 0x55, 0x47, 0x47, 0x12, 0xc1, 0xd5, 0x66, 0x85, 0xfc, 0x05, 0x40, 0x73, 0x3d, 0x8a, 0x51, - 0x92, 0x9f, 0xcc, 0xb0, 0xdb, 0x1c, 0x0f, 0xbc, 0xbd, 0x65, 0xae, 0xfa, 0x22, 0x6c, 0xe0, 0xf9, - 0xc8, 0xab, 0xa8, 0x16, 0x78, 0x4e, 0x7b, 0x4f, 0xb8, 0x15, 0x6d, 0x0a, 0xb7, 0xa2, 0x77, 0x3f, - 0x96, 0x5b, 0x11, 0x67, 0xe5, 0x9f, 0x1c, 0x17, 0x27, 0x3c, 0x51, 0x27, 0xae, 0x22, 0xed, 0x0b, - 0x58, 0xd7, 0xa1, 0x6f, 0xed, 0x56, 0x9b, 0x87, 0xfd, 0xa5, 0x0d, 0xde, 0xc8, 0x69, 0xdc, 0xc1, - 0xb1, 0xeb, 0x30, 0x27, 0x88, 0xd5, 0x0d, 0x11, 0x12, 0x0d, 0x4d, 0xe5, 0xc0, 0x2e, 0x9e, 0xd2, - 0x75, 0x85, 0x7b, 0xe3, 0x16, 0xa2, 0x8b, 0xa7, 0xf4, 0x73, 0xb1, 0x70, 0x1a, 0xa9, 0x93, 0x47, - 0x23, 0x21, 0x37, 0x60, 0x64, 0xcd, 0x7e, 0x5c, 0xda, 0xa3, 0x22, 0xf3, 0xe6, 0xa4, 0xdc, 0xfe, - 0x10, 0xb8, 0x94, 0xff, 0x03, 0xee, 0xeb, 0xf0, 0x9c, 0x29, 0xd0, 0xc8, 0x5f, 0xcb, 0xc0, 0x79, - 0xbe, 0x8c, 0x65, 0x2b, 0x6b, 0x34, 0x08, 0x58, 0x3f, 0x88, 0xf8, 0x81, 0x97, 0x23, 0x83, 0xed, - 0x74, 0x3c, 0xf4, 0xbc, 0x37, 0xc4, 0xce, 0x10, 0x76, 0x9c, 0x2f, 0x4a, 0xb5, 0x40, 0xcc, 0xa9, - 0xf4, 0x64, 0x13, 0xc6, 0xd7, 0xee, 0x94, 0xc2, 0x6a, 0x79, 0x74, 0xf6, 0x62, 0xda, 0xee, 0xa8, - 0xa0, 0xa5, 0x79, 0x1a, 0xa8, 0x6c, 0x84, 0x77, 0xc0, 0x67, 0x65, 0x7f, 0x90, 0x37, 0x55, 0x57, - 0xd4, 0x1c, 0x4a, 0xcf, 0xa3, 0x2d, 0xfb, 0xb1, 0x65, 0xef, 0x51, 0xed, 0x95, 0x5c, 0x68, 0xaf, - 0x7f, 0x26, 0x03, 0x17, 0x7b, 0x36, 0x99, 0xdc, 0x86, 0x0b, 0x36, 0x77, 0xb0, 0xb6, 0xf6, 0x83, - 0xa0, 0xe3, 0x5b, 0xf2, 0x8a, 0x21, 0x9c, 0x57, 0xcd, 0x73, 0xa2, 0x78, 0x85, 0x95, 0xca, 0x5b, - 0x87, 0x4f, 0x3e, 0x80, 0x17, 0x9c, 0xb6, 0x4f, 0xeb, 0x5d, 0x8f, 0x5a, 0x92, 0x41, 0xdd, 0x69, - 0x78, 0x96, 0x67, 0xb7, 0xf7, 0xa4, 0x27, 0xae, 0x79, 0x51, 0xe2, 0x08, 0x27, 0xee, 0xb2, 0xd3, - 0xf0, 0x4c, 0x44, 0x30, 0xfe, 0x45, 0x06, 0xe6, 0x7b, 0x75, 0x09, 0x99, 0x87, 0x51, 0xaa, 0xe4, - 0x69, 0xc9, 0x9b, 0xf2, 0x27, 0x79, 0x1e, 0xa2, 0x9d, 0x5e, 0x9c, 0xfe, 0xf9, 0xba, 0xc8, 0x99, - 0x81, 0xa6, 0xed, 0xea, 0xbe, 0x2e, 0x0c, 0x94, 0x27, 0xea, 0xea, 0xee, 0xfe, 0x22, 0x40, 0xb4, - 0x9d, 0x73, 0xc5, 0x84, 0x39, 0x66, 0xd7, 0x3d, 0xbe, 0xf2, 0xc8, 0x79, 0x18, 0xe1, 0xdb, 0xa5, - 0xf0, 0x7f, 0x10, 0xbf, 0xd8, 0xb9, 0x2d, 0x3a, 0x19, 0xf7, 0xf9, 0xdc, 0xd2, 0x84, 0xd6, 0xd9, - 0x23, 0x2d, 0x1c, 0x1c, 0xe3, 0xa7, 0x26, 0xb9, 0x08, 0x51, 0xea, 0x06, 0xfb, 0x52, 0xe8, 0x58, - 0x4c, 0xf3, 0x17, 0xe3, 0xb6, 0x94, 0x8a, 0x5d, 0xb6, 0xee, 0x25, 0x26, 0xdf, 0x7e, 0xb2, 0xa9, - 0x6f, 0x3f, 0x6f, 0xc0, 0x58, 0x79, 0x9f, 0xd6, 0x0f, 0x42, 0x27, 0x9c, 0xbc, 0x50, 0xae, 0x33, - 0x20, 0x0f, 0x89, 0x1e, 0x21, 0x90, 0x1b, 0x00, 0xe8, 0xa6, 0xca, 0x25, 0x52, 0x25, 0xad, 0x09, - 0x7a, 0xb5, 0x0a, 0xf3, 0x14, 0x05, 0x05, 0xd9, 0xd7, 0xcc, 0x3b, 0xaa, 0x3d, 0x0b, 0x67, 0xef, - 0x7b, 0xbb, 0x02, 0x3d, 0x42, 0x60, 0xcd, 0x53, 0xf6, 0x15, 0x71, 0x0a, 0x16, 0x12, 0x9b, 0x8f, - 0x8a, 0x44, 0xae, 0xc3, 0xd8, 0x86, 0x74, 0x24, 0xc0, 0x43, 0x70, 0x02, 0x29, 0x20, 0x72, 0x3a, - 0x98, 0xcf, 0x98, 0x11, 0x0a, 0xf9, 0x2c, 0x8c, 0x96, 0xa9, 0x17, 0x6c, 0x6e, 0xae, 0xa2, 0xd1, - 0x09, 0xcf, 0xfe, 0x91, 0xc7, 0x4c, 0x0d, 0x41, 0xd0, 0xfc, 0xce, 0x71, 0x71, 0x32, 0x70, 0x5a, - 0x34, 0x8c, 0x6a, 0x6e, 0x4a, 0x6c, 0xb2, 0x04, 0x05, 0xfe, 0x2c, 0x1e, 0xdd, 0x3d, 0xf0, 0x64, - 0xcc, 0xf3, 0x73, 0x5a, 0xbc, 0xa1, 0x1f, 0xd2, 0x9d, 0x30, 0x4f, 0x45, 0x02, 0x9f, 0x2c, 0xcb, - 0xf4, 0x2e, 0x6a, 0x33, 0x21, 0x52, 0x86, 0xc5, 0x77, 0x0c, 0xd6, 0xda, 0x24, 0x05, 0x29, 0xc1, - 0x64, 0xd9, 0x6d, 0x75, 0xec, 0xc0, 0xc1, 0x3c, 0x98, 0x47, 0xe2, 0x10, 0x44, 0x85, 0x5e, 0x5d, - 0x2d, 0xd0, 0x4e, 0x54, 0xb5, 0x80, 0xdc, 0x81, 0x29, 0xd3, 0xed, 0xb2, 0x61, 0x92, 0xb7, 0x70, - 0x7e, 0xce, 0xa1, 0x69, 0x88, 0xc7, 0x4a, 0xd8, 0xb1, 0x2c, 0xae, 0xdc, 0x5a, 0x04, 0x58, 0x8d, - 0x8a, 0xac, 0xa7, 0x3c, 0x87, 0xa8, 0x87, 0x9b, 0x9a, 0xad, 0x22, 0xc1, 0x2c, 0xe5, 0x25, 0xe5, - 0x16, 0x8c, 0xd7, 0x6a, 0x0f, 0x36, 0xa9, 0x1f, 0xdc, 0x69, 0xba, 0x87, 0x78, 0xb6, 0xe5, 0x45, - 0x72, 0x35, 0xdf, 0xb5, 0x02, 0xea, 0x07, 0xd6, 0x6e, 0xd3, 0x3d, 0x34, 0x55, 0x2c, 0xf2, 0x35, - 0xd6, 0x1f, 0x8a, 0x24, 0x28, 0x62, 0xdd, 0xf6, 0x13, 0x56, 0xf1, 0x04, 0x89, 0x16, 0x0d, 0x13, - 0x59, 0xf5, 0xce, 0x52, 0xd0, 0xd1, 0xa7, 0xcc, 0x73, 0x1f, 0x1f, 0x95, 0x1a, 0x0d, 0x8f, 0xfa, - 0xbe, 0x38, 0x84, 0xb8, 0x4f, 0x19, 0x2a, 0x1b, 0x6c, 0x5e, 0xa0, 0xf9, 0x94, 0x29, 0x04, 0xe4, - 0x47, 0x32, 0x70, 0x4e, 0xf5, 0x36, 0xc1, 0xe5, 0x82, 0x66, 0x2e, 0xfc, 0x48, 0x7a, 0xf3, 0xba, - 0x3c, 0x84, 0xaf, 0x2b, 0x68, 0xd7, 0x1f, 0xdd, 0xbc, 0x5e, 0x8a, 0x7e, 0xd6, 0x24, 0x11, 0xc6, - 0xed, 0x2b, 0xa6, 0xf2, 0xd3, 0x72, 0x13, 0xcd, 0xd9, 0x29, 0xc4, 0xa4, 0xcc, 0x24, 0x35, 0x36, - 0xa3, 0xd0, 0x70, 0xaa, 0xba, 0x81, 0x67, 0x9a, 0xd0, 0xa8, 0x8a, 0xf9, 0xc7, 0x4d, 0xac, 0x9c, - 0x8e, 0x2e, 0x90, 0x29, 0x34, 0xa4, 0x0a, 0xd3, 0x1c, 0xc0, 0xb6, 0x05, 0x9e, 0xe6, 0x69, 0x36, - 0x4a, 0x34, 0x21, 0xd8, 0xe0, 0x5b, 0x3f, 0xa6, 0x7a, 0x52, 0x83, 0xb3, 0xc6, 0xe8, 0xc8, 0x07, - 0x30, 0x85, 0x31, 0xf4, 0xa3, 0xf5, 0x3a, 0x87, 0xab, 0x18, 0x63, 0xcc, 0x8a, 0x92, 0x98, 0xe7, - 0xdd, 0x84, 0xef, 0xef, 0x47, 0x2b, 0xfa, 0x03, 0x98, 0x42, 0x5b, 0x9d, 0x88, 0xc1, 0xb9, 0x88, - 0x81, 0x28, 0x89, 0x33, 0x08, 0x9a, 0x7e, 0xc4, 0xe0, 0xa7, 0x33, 0x70, 0x91, 0x55, 0x94, 0x3e, - 0x42, 0xe7, 0x3f, 0xce, 0x08, 0x61, 0xd4, 0xcd, 0x9e, 0x3c, 0x55, 0x71, 0xd4, 0xf7, 0xf7, 0xd3, - 0x38, 0xe0, 0x47, 0xb1, 0x8f, 0x4f, 0xff, 0xa8, 0x0b, 0x1f, 0xfb, 0xa3, 0x7a, 0xf2, 0x54, 0x3f, - 0x2a, 0x68, 0xfa, 0x69, 0x1c, 0xf0, 0x5a, 0x5b, 0x2b, 0xad, 0xad, 0x46, 0x77, 0xb3, 0xef, 0x2e, - 0xb7, 0x15, 0xad, 0x6d, 0x7d, 0xdc, 0x56, 0xb6, 0xb8, 0x17, 0xb5, 0xd2, 0x0d, 0xf2, 0x5a, 0xab, - 0x81, 0xe3, 0xd7, 0xda, 0x18, 0x8d, 0x19, 0xc3, 0x36, 0x7e, 0x09, 0x62, 0x7c, 0x85, 0xa9, 0xaa, - 0x01, 0x23, 0xfc, 0xd6, 0x2a, 0x3a, 0x19, 0x6d, 0x16, 0xf8, 0x9d, 0xd6, 0x14, 0x25, 0xe4, 0x22, - 0xe4, 0x6a, 0xb5, 0x07, 0xa2, 0x93, 0xd1, 0x60, 0xd5, 0xf7, 0x5d, 0x93, 0xc1, 0xd8, 0x08, 0xa1, - 0x15, 0xaa, 0x92, 0x93, 0x80, 0x9d, 0x77, 0x26, 0x42, 0x59, 0x7f, 0xcb, 0x3b, 0xe4, 0x50, 0xd4, - 0xdf, 0xe2, 0x0e, 0x19, 0xdd, 0x1c, 0xcb, 0x30, 0x5f, 0xf2, 0x7d, 0xea, 0xb1, 0x09, 0x21, 0x8c, - 0x1b, 0x3d, 0x71, 0xcf, 0x11, 0x07, 0x3b, 0x56, 0x6a, 0xd7, 0x7d, 0xb3, 0x27, 0x22, 0xb9, 0x0a, - 0xf9, 0x52, 0xb7, 0xe1, 0xd0, 0x76, 0x5d, 0x0b, 0xcb, 0x66, 0x0b, 0x98, 0x19, 0x96, 0x92, 0x2f, - 0xc2, 0xb9, 0x58, 0x04, 0x46, 0xd1, 0x03, 0xa3, 0xd1, 0xde, 0x2b, 0xef, 0x61, 0x91, 0x41, 0x06, - 0xef, 0x92, 0x74, 0x4a, 0x52, 0x82, 0xc2, 0x32, 0xba, 0x69, 0x55, 0x28, 0x7f, 0x1b, 0x72, 0x3d, - 0xee, 0x9f, 0xc7, 0x6f, 0xcd, 0x22, 0xce, 0x64, 0x23, 0x2c, 0x34, 0x13, 0xe8, 0xe4, 0x3e, 0xcc, - 0xc6, 0x61, 0xec, 0x04, 0xe7, 0x17, 0x64, 0xdc, 0x6f, 0x12, 0x5c, 0xf0, 0x0c, 0x4f, 0xa3, 0x22, - 0x3b, 0x30, 0x13, 0x19, 0x24, 0xe9, 0xd7, 0x66, 0x69, 0xe7, 0x1c, 0x96, 0xcb, 0xab, 0xf3, 0xf3, - 0x62, 0x32, 0xce, 0x46, 0xc6, 0x4d, 0xe1, 0xf5, 0xd9, 0x4c, 0xb2, 0x23, 0x0d, 0x98, 0xaa, 0x39, - 0x7b, 0x6d, 0xa7, 0xbd, 0x77, 0x9f, 0x1e, 0x6d, 0xd8, 0x8e, 0x27, 0x2c, 0x4e, 0xa5, 0x3d, 0x79, - 0xc9, 0x3f, 0x6a, 0xb5, 0x68, 0xe0, 0xe1, 0x46, 0xc8, 0xca, 0xd1, 0x07, 0x9d, 0x5d, 0x87, 0x16, - 0x7c, 0x4e, 0x87, 0x6e, 0x9b, 0x1d, 0xdb, 0xd1, 0x84, 0x00, 0x9d, 0xa7, 0xa6, 0xba, 0x98, 0x18, - 0x50, 0x75, 0xd1, 0x84, 0x99, 0xe5, 0x76, 0xdd, 0x3b, 0xc2, 0x27, 0x3a, 0xf9, 0x71, 0x93, 0xa7, - 0x7c, 0xdc, 0x2b, 0xe2, 0xe3, 0x5e, 0xb0, 0xe5, 0x0c, 0x4b, 0xfb, 0xbc, 0x24, 0x63, 0x52, 0x83, - 0x19, 0xbc, 0x38, 0x54, 0x2b, 0x1b, 0xd5, 0xb6, 0x13, 0x38, 0x76, 0x40, 0x1b, 0x42, 0xb8, 0x08, - 0x33, 0xb9, 0xf0, 0x2b, 0xaa, 0xd3, 0xe8, 0x58, 0x8e, 0x44, 0x51, 0x99, 0x26, 0xe8, 0xfb, 0xdd, - 0x13, 0xa7, 0xff, 0x9c, 0xee, 0x89, 0x55, 0x98, 0x8e, 0x87, 0x72, 0x28, 0x44, 0xe7, 0xb0, 0x8f, - 0x45, 0xec, 0x38, 0x77, 0xbb, 0x28, 0x4c, 0x6a, 0xc9, 0x53, 0x63, 0x41, 0x1c, 0x62, 0x57, 0xce, - 0x19, 0xed, 0xca, 0xa9, 0xed, 0x4a, 0x67, 0xb8, 0x72, 0x92, 0x0d, 0x80, 0x3b, 0xae, 0x57, 0xa7, - 0x25, 0xf4, 0x8f, 0x26, 0x5a, 0xbe, 0x2b, 0xc6, 0x34, 0x2a, 0xe4, 0xeb, 0x67, 0x97, 0xfd, 0xb6, - 0xe2, 0x6e, 0xee, 0x0a, 0x0f, 0xe3, 0x47, 0xb3, 0x30, 0xdf, 0xeb, 0x73, 0xfa, 0x5c, 0xf7, 0x3e, - 0x05, 0xc9, 0x15, 0x2e, 0xae, 0x7d, 0x05, 0x1a, 0x5f, 0xe7, 0x8b, 0x90, 0xbe, 0x90, 0xc5, 0x35, - 0x70, 0x36, 0x4e, 0xb0, 0xe5, 0x35, 0xc9, 0x6d, 0x18, 0x57, 0x3e, 0x1e, 0xf7, 0xd2, 0x5e, 0x4d, - 0x35, 0x61, 0x37, 0xfc, 0x9b, 0x5d, 0x13, 0xf9, 0xbe, 0x25, 0xaf, 0x89, 0xfc, 0x17, 0x29, 0x70, - 0x17, 0xf1, 0x11, 0x6e, 0x05, 0xe0, 0xfb, 0x2e, 0x21, 0x80, 0xfb, 0x36, 0xdf, 0x02, 0x4d, 0xfc, - 0xdb, 0xf8, 0x8d, 0x09, 0x7e, 0x22, 0xab, 0xb7, 0xc4, 0x5e, 0xf6, 0xc1, 0xb1, 0xdb, 0x63, 0xf6, - 0x2c, 0xb7, 0xc7, 0xdc, 0xe9, 0xb7, 0xc7, 0xa1, 0xd3, 0x6e, 0x8f, 0xb1, 0xeb, 0xdd, 0xf0, 0x99, - 0xaf, 0x77, 0x23, 0x67, 0xba, 0xde, 0x8d, 0x9e, 0xe9, 0x7a, 0xa7, 0xdd, 0x54, 0xf3, 0xa7, 0xdd, - 0x54, 0xff, 0xf2, 0x32, 0xf8, 0xac, 0x5e, 0x06, 0xd3, 0x44, 0xbc, 0x33, 0x5d, 0x06, 0x7f, 0xb8, - 0xe7, 0x5d, 0xae, 0xf0, 0x71, 0x84, 0xf2, 0x97, 0x07, 0xb8, 0xcb, 0x0d, 0x7a, 0x93, 0x9b, 0x79, - 0x3a, 0x37, 0x39, 0xf2, 0xd4, 0x6e, 0x72, 0xb3, 0x4f, 0x7a, 0x93, 0x9b, 0x7b, 0x9a, 0x37, 0xb9, - 0x73, 0x7f, 0x11, 0x6f, 0x72, 0xe7, 0xff, 0xed, 0xdc, 0xe4, 0xfe, 0x0a, 0x14, 0xe2, 0xc2, 0xe5, - 0xe9, 0x51, 0x8f, 0x9f, 0x5a, 0xc8, 0x49, 0x26, 0xfa, 0xc6, 0x85, 0x3b, 0x72, 0x03, 0x60, 0xc3, - 0x73, 0x1e, 0xd9, 0x01, 0xbd, 0x2f, 0xad, 0xdf, 0x44, 0xc4, 0x6e, 0x0e, 0x65, 0x23, 0x6f, 0x2a, - 0x28, 0xe1, 0xbd, 0x26, 0x9b, 0x76, 0xaf, 0x31, 0x7e, 0x24, 0x0b, 0x33, 0x3c, 0x6e, 0xdb, 0xb3, - 0xff, 0x08, 0xfb, 0xbe, 0x76, 0x5b, 0x7d, 0x21, 0xca, 0x11, 0xa0, 0xb6, 0xae, 0xcf, 0x33, 0xec, - 0x57, 0xe1, 0x5c, 0xa2, 0x2b, 0xf0, 0xc6, 0x5a, 0x91, 0x11, 0xf3, 0x12, 0x77, 0xd6, 0xf9, 0xf4, - 0x4a, 0x1e, 0xde, 0x32, 0x13, 0x14, 0xc6, 0x9f, 0x0e, 0x25, 0xf8, 0x8b, 0x07, 0x59, 0xf5, 0x89, - 0x35, 0x73, 0xb6, 0x27, 0xd6, 0xec, 0x60, 0x4f, 0xac, 0x31, 0xa1, 0x22, 0x37, 0x88, 0x50, 0xf1, - 0x45, 0x98, 0xdc, 0xa4, 0x76, 0xcb, 0xdf, 0x74, 0x45, 0xc2, 0x29, 0xee, 0x6b, 0x21, 0x03, 0xe2, - 0xb1, 0x32, 0x79, 0xe1, 0x0a, 0x6d, 0x46, 0x03, 0x46, 0xc0, 0x8e, 0x41, 0x9e, 0x81, 0xca, 0xd4, - 0x39, 0xa8, 0xb7, 0xe8, 0xe1, 0x3e, 0xb7, 0xe8, 0x1a, 0x4c, 0x08, 0xba, 0x28, 0xd4, 0x73, 0x74, - 0xdd, 0x63, 0x45, 0x08, 0x97, 0xb5, 0x87, 0xd9, 0xf0, 0xc3, 0xda, 0xf9, 0x4d, 0x4f, 0x63, 0xc2, - 0xba, 0x60, 0xb9, 0xdd, 0xe8, 0xb8, 0x4e, 0x1b, 0xbb, 0x60, 0x34, 0xea, 0x02, 0x2a, 0xc0, 0xbc, - 0x0b, 0x14, 0x24, 0xf2, 0x2e, 0x4c, 0x95, 0x36, 0xaa, 0x2a, 0x59, 0x3e, 0x7a, 0xe5, 0xb5, 0x3b, - 0x8e, 0xa5, 0x91, 0xc6, 0x70, 0xfb, 0xdd, 0x7c, 0xc6, 0xfe, 0x7c, 0x6e, 0x3e, 0xc6, 0x3f, 0x9b, - 0x94, 0xcb, 0xfb, 0x93, 0x7d, 0x20, 0xd1, 0x9f, 0x3c, 0x72, 0x67, 0x7c, 0xf2, 0x18, 0x3a, 0x4d, - 0x90, 0xd4, 0xe4, 0xdb, 0xe1, 0x33, 0xc9, 0xb7, 0x23, 0x4f, 0xfc, 0x7c, 0x31, 0x7a, 0x46, 0x89, - 0x35, 0xb6, 0xd6, 0xf2, 0x83, 0xac, 0xb5, 0x54, 0x29, 0x77, 0xec, 0xc9, 0xa5, 0x5c, 0x38, 0xb3, - 0x94, 0x5b, 0x8b, 0x7c, 0x97, 0xc7, 0x4f, 0x75, 0x09, 0x79, 0x51, 0x68, 0x05, 0x66, 0xd2, 0xa3, - 0xf0, 0x85, 0x5e, 0xcc, 0xdf, 0x55, 0xa2, 0xf3, 0xd7, 0xd3, 0x45, 0xe7, 0xfe, 0xe7, 0xcd, 0x99, - 0x84, 0xe7, 0x1f, 0x79, 0xba, 0xc2, 0xf3, 0xd3, 0x7d, 0x08, 0xf9, 0x4b, 0xf1, 0xf9, 0x2f, 0xc5, - 0xe7, 0xc1, 0xc4, 0x67, 0xf2, 0x00, 0x88, 0xdd, 0x0d, 0xf6, 0x69, 0x3b, 0x70, 0xea, 0x18, 0x95, - 0x96, 0x0d, 0x31, 0xbe, 0xca, 0x88, 0xf5, 0x9a, 0x2c, 0x55, 0xd7, 0xab, 0x56, 0x8a, 0x7e, 0xde, - 0x1e, 0xae, 0xd7, 0x6d, 0xdb, 0x6b, 0xa3, 0x1e, 0xeb, 0x06, 0x8c, 0xca, 0xb8, 0xa6, 0x99, 0x48, - 0x45, 0x9d, 0x0c, 0x68, 0x2a, 0xb1, 0xc8, 0x22, 0xe4, 0x25, 0xb1, 0x9a, 0x68, 0xe7, 0x50, 0xc0, - 0xb4, 0x90, 0x91, 0x02, 0x66, 0xfc, 0x47, 0x43, 0xf2, 0x4c, 0x60, 0x9f, 0xb0, 0x61, 0x7b, 0x76, - 0x0b, 0x73, 0xf0, 0x85, 0x4b, 0x56, 0xb9, 0x0d, 0xc4, 0x56, 0x79, 0xcc, 0x57, 0x40, 0x27, 0xf9, - 0x58, 0x81, 0x69, 0xa3, 0x34, 0xc7, 0xb9, 0x01, 0xd2, 0x1c, 0xbf, 0xad, 0xe5, 0x08, 0x1e, 0x8a, - 0x92, 0x52, 0xb2, 0x7d, 0xb2, 0x7f, 0x76, 0xe0, 0xdb, 0x6a, 0x32, 0xdf, 0xe1, 0x28, 0x4c, 0x18, - 0x52, 0xf6, 0x49, 0xe3, 0x1b, 0x5e, 0x6f, 0x46, 0xce, 0x12, 0xf2, 0x79, 0xf4, 0xdf, 0x6a, 0xc8, - 0xe7, 0x65, 0x00, 0x71, 0x76, 0x47, 0xf6, 0x0e, 0xaf, 0xe2, 0x76, 0x22, 0xec, 0x9e, 0x83, 0xa0, - 0xd9, 0x23, 0x27, 0x88, 0x42, 0x68, 0xfc, 0x1e, 0x81, 0x99, 0x5a, 0xed, 0x41, 0xc5, 0xb1, 0xf7, - 0xda, 0xae, 0x1f, 0x38, 0xf5, 0x6a, 0x7b, 0xd7, 0x65, 0xb2, 0x7d, 0x78, 0xbe, 0x28, 0xc1, 0x7a, - 0xa3, 0xb3, 0x25, 0x2c, 0x66, 0x77, 0xc7, 0x65, 0xcf, 0x93, 0x0a, 0x57, 0x7e, 0x77, 0xa4, 0x0c, - 0x60, 0x72, 0x38, 0x13, 0x9f, 0x6b, 0x5d, 0x0c, 0x95, 0x21, 0x8c, 0x50, 0x50, 0x7c, 0xf6, 0x39, - 0xc8, 0x94, 0x65, 0x84, 0x26, 0x27, 0xac, 0xb8, 0x4e, 0x5d, 0xd0, 0x02, 0x47, 0x47, 0xc5, 0x7c, - 0x35, 0x0a, 0xe9, 0x06, 0xf7, 0xe1, 0x0e, 0xc2, 0x55, 0x13, 0xbb, 0xc4, 0x1a, 0x38, 0x82, 0x73, - 0x9a, 0x13, 0xf5, 0xa0, 0xaf, 0x33, 0xaf, 0x0b, 0x71, 0xdd, 0xc0, 0x98, 0x1d, 0x29, 0x4f, 0x34, - 0x6a, 0x52, 0xbd, 0xd4, 0x1a, 0xd8, 0x01, 0xf9, 0x62, 0x6a, 0x49, 0xb8, 0xba, 0xc7, 0xb5, 0xe0, - 0xdd, 0xca, 0xa6, 0xc1, 0xd3, 0x07, 0xf6, 0xaa, 0xda, 0x4a, 0xd9, 0x0a, 0xfa, 0xd7, 0x44, 0xfe, - 0x71, 0x06, 0x2e, 0x68, 0x18, 0xe1, 0xfe, 0xe7, 0x87, 0xf1, 0x45, 0x52, 0xe7, 0xf5, 0x47, 0x4f, - 0x67, 0x5e, 0xbf, 0xac, 0xb7, 0x25, 0xda, 0xa1, 0xd5, 0x36, 0xf4, 0xfa, 0x42, 0xf2, 0x08, 0x66, - 0xb0, 0x48, 0xbe, 0x14, 0xb1, 0x39, 0x2b, 0x1e, 0x98, 0xe6, 0xa2, 0xcf, 0xe6, 0x81, 0x01, 0x30, - 0x05, 0xfc, 0xe2, 0xb7, 0x8f, 0x8b, 0x93, 0x1a, 0xba, 0x0c, 0x87, 0x6d, 0x45, 0xcf, 0x4d, 0x4e, - 0x7b, 0xd7, 0xd5, 0xf2, 0xfb, 0xc7, 0xab, 0x20, 0xff, 0x75, 0x86, 0xbf, 0x4f, 0xf0, 0x66, 0xdc, - 0xf1, 0xdc, 0x56, 0x58, 0x2e, 0x6d, 0x35, 0x7b, 0x74, 0x5b, 0xf3, 0xe9, 0x74, 0xdb, 0xab, 0xf8, - 0xc9, 0x7c, 0x4f, 0xb0, 0x76, 0x3d, 0xb7, 0x15, 0x7d, 0xbe, 0xda, 0x71, 0x3d, 0x3f, 0x92, 0xfc, - 0xf5, 0x0c, 0x5c, 0xd4, 0xd4, 0xa4, 0x6a, 0x6e, 0x12, 0x11, 0x7e, 0x61, 0x36, 0x0c, 0xcc, 0x12, - 0x15, 0x2d, 0x5d, 0x17, 0xf3, 0xff, 0x0a, 0x7e, 0x81, 0x12, 0x07, 0x94, 0x21, 0x59, 0x2d, 0x8e, - 0xa5, 0x7c, 0x42, 0xef, 0x5a, 0x88, 0x03, 0x33, 0x68, 0xb6, 0xa3, 0xd9, 0x14, 0xcf, 0xf5, 0xb6, - 0x29, 0x0e, 0xb3, 0x0e, 0x61, 0x46, 0x82, 0xde, 0x86, 0xc5, 0x49, 0xae, 0xe4, 0xfb, 0xe0, 0x62, - 0x02, 0x18, 0xae, 0xb6, 0x73, 0x3d, 0x57, 0xdb, 0xa7, 0x4e, 0x8e, 0x8b, 0xaf, 0xa5, 0xd5, 0x96, - 0xb6, 0xd2, 0x7a, 0xd7, 0x40, 0x6c, 0x80, 0xa8, 0x50, 0xc8, 0x33, 0xe9, 0x13, 0xf4, 0x53, 0x62, - 0x7e, 0x28, 0xf8, 0x6c, 0x2f, 0x57, 0xbe, 0x41, 0x3d, 0xf2, 0x22, 0x24, 0x42, 0x61, 0x42, 0xc9, - 0xc6, 0x70, 0x24, 0xac, 0x47, 0x7a, 0x54, 0xf2, 0xed, 0xe3, 0xa2, 0x86, 0xcd, 0x6e, 0x58, 0x6a, - 0x9a, 0x07, 0x4d, 0x7c, 0x54, 0x11, 0xc9, 0xaf, 0x67, 0x60, 0x8e, 0x01, 0xa2, 0x49, 0x25, 0x1a, - 0x35, 0xdf, 0x6f, 0xd6, 0xef, 0x3f, 0x9d, 0x59, 0xff, 0x12, 0x7e, 0xa3, 0x3a, 0xeb, 0x13, 0x5d, - 0x92, 0xfa, 0x71, 0x38, 0xdb, 0x35, 0x0b, 0x31, 0x6d, 0xb6, 0x5f, 0x1c, 0x60, 0xb6, 0xf3, 0x01, - 0x38, 0x7d, 0xb6, 0xf7, 0xac, 0x85, 0x6c, 0xc2, 0x84, 0xb8, 0x5c, 0xf1, 0x0e, 0xbb, 0xa4, 0xc5, - 0x85, 0x56, 0x8b, 0xf8, 0x8d, 0x57, 0x24, 0xab, 0x48, 0xb4, 0x50, 0xe3, 0x42, 0xda, 0x30, 0xcb, - 0x7f, 0xeb, 0xca, 0xae, 0x62, 0x4f, 0x65, 0xd7, 0x55, 0xd1, 0xa2, 0xcb, 0x82, 0x7f, 0x4c, 0xe7, - 0xa5, 0xc6, 0x73, 0x4a, 0x61, 0x4c, 0x3a, 0x40, 0x34, 0x30, 0x5f, 0xb4, 0x97, 0xfb, 0xab, 0xb8, - 0x5e, 0x13, 0x75, 0x16, 0xe3, 0x75, 0xc6, 0x57, 0x6e, 0x0a, 0x6f, 0x62, 0xc3, 0xb4, 0x80, 0xba, - 0x07, 0x94, 0xef, 0xf0, 0x2f, 0x69, 0x11, 0xb5, 0x62, 0xa5, 0xfc, 0x56, 0x26, 0x6b, 0xc2, 0x88, - 0x67, 0xb1, 0x0d, 0x3d, 0xce, 0x8f, 0x3c, 0x80, 0x99, 0x52, 0xa7, 0xd3, 0x74, 0x68, 0x03, 0x5b, - 0x69, 0x76, 0x59, 0x9b, 0x8c, 0x28, 0xdf, 0x9b, 0xcd, 0x0b, 0xc5, 0x55, 0xd1, 0xeb, 0xc6, 0xb6, - 0x9b, 0x04, 0xad, 0xf1, 0xc3, 0x99, 0xc4, 0x47, 0x93, 0x37, 0x60, 0x0c, 0x7f, 0x28, 0x41, 0x5a, - 0x50, 0x67, 0xc4, 0x3f, 0x11, 0xb5, 0x51, 0x11, 0x02, 0x13, 0x96, 0xd4, 0x40, 0x8d, 0x39, 0x2e, - 0x2c, 0x09, 0x45, 0x45, 0xa4, 0x9a, 0x28, 0x4a, 0x5f, 0x8f, 0x5c, 0x24, 0x74, 0xa1, 0xaf, 0x87, - 0xf0, 0xf0, 0x30, 0xfe, 0x61, 0x56, 0x9f, 0x76, 0xe4, 0xaa, 0x22, 0xb7, 0x2b, 0xa1, 0x22, 0xa5, - 0xdc, 0xae, 0x48, 0xeb, 0x7f, 0x2f, 0x03, 0xb3, 0x0f, 0x94, 0x44, 0xa1, 0x9b, 0x2e, 0x8e, 0x4b, - 0xff, 0xd4, 0x99, 0x4f, 0x2b, 0x05, 0xa0, 0x9a, 0xa1, 0x94, 0xcd, 0x14, 0x9c, 0x32, 0x66, 0xda, - 0xf7, 0xa0, 0xf7, 0x1c, 0x7e, 0x98, 0x92, 0x89, 0x91, 0xa3, 0x73, 0xf8, 0x19, 0x53, 0x57, 0x18, - 0x3f, 0x9e, 0x85, 0x71, 0x65, 0xc5, 0x90, 0xcf, 0xc0, 0x84, 0x5a, 0xad, 0xaa, 0x70, 0x54, 0xbf, - 0xd2, 0xd4, 0xb0, 0x50, 0xe3, 0x48, 0xed, 0x96, 0xa6, 0x71, 0x64, 0xeb, 0x02, 0xa1, 0x67, 0xbc, - 0x09, 0x7d, 0x90, 0x72, 0x13, 0xc2, 0x59, 0xae, 0x68, 0x8c, 0xfa, 0xde, 0x87, 0xde, 0x4d, 0xde, - 0x87, 0x50, 0x79, 0xa5, 0xd0, 0xf7, 0xbe, 0x15, 0x19, 0x3f, 0x95, 0x81, 0x42, 0x7c, 0x4d, 0x7f, - 0x22, 0xbd, 0x72, 0x86, 0xd7, 0xa5, 0x1f, 0xcb, 0x86, 0x99, 0x5b, 0xa4, 0x0b, 0xf1, 0xb3, 0x6a, - 0xa6, 0xf8, 0x9e, 0xf6, 0xf0, 0xf3, 0xbc, 0x1e, 0x0d, 0x4f, 0x0d, 0xbe, 0x91, 0x1e, 0x02, 0x73, - 0xe8, 0x5b, 0xbf, 0x58, 0x7c, 0xce, 0xf8, 0x10, 0xe6, 0xe2, 0xdd, 0x81, 0x8f, 0x3f, 0x25, 0x98, - 0xd6, 0xe1, 0xf1, 0xbc, 0x4f, 0x71, 0x2a, 0x33, 0x8e, 0x6f, 0xfc, 0x41, 0x36, 0xce, 0x5b, 0x98, - 0x2c, 0xb2, 0x3d, 0x4a, 0x35, 0xc4, 0x11, 0x7b, 0x14, 0x07, 0x99, 0xb2, 0xec, 0x2c, 0xf9, 0xd6, - 0x42, 0x47, 0xd8, 0x5c, 0xba, 0x23, 0x2c, 0xb9, 0x1d, 0xb3, 0xd2, 0x56, 0xa2, 0x36, 0x1d, 0xd2, - 0x1d, 0x2b, 0xb2, 0xd4, 0x8e, 0x19, 0x67, 0x97, 0x61, 0x4e, 0x0b, 0x41, 0x2e, 0xe9, 0x87, 0x23, - 0x5d, 0x7f, 0x80, 0x05, 0x9c, 0x38, 0x15, 0x99, 0xac, 0xc0, 0x28, 0xfb, 0xcc, 0x35, 0xbb, 0x23, - 0xde, 0x74, 0x48, 0xe8, 0x16, 0xdf, 0x0c, 0xef, 0x87, 0x8a, 0x67, 0x7c, 0x93, 0x32, 0x09, 0x41, - 0x9d, 0x58, 0x02, 0xd1, 0xf8, 0x57, 0x19, 0xb6, 0xfe, 0xeb, 0x07, 0xdf, 0x65, 0x49, 0xdb, 0x58, - 0x93, 0xfa, 0x58, 0xd4, 0xfe, 0x51, 0x96, 0xe7, 0xe2, 0x11, 0xd3, 0xe7, 0x6d, 0x18, 0xd9, 0xb4, - 0xbd, 0x3d, 0x91, 0x32, 0x5b, 0xe7, 0xc2, 0x0b, 0xa2, 0x98, 0x52, 0x01, 0xfe, 0x36, 0x05, 0x81, - 0xaa, 0x3a, 0xcb, 0x0e, 0xa4, 0x3a, 0x53, 0xde, 0x05, 0x72, 0x4f, 0xed, 0x5d, 0xe0, 0x7b, 0xc2, - 0xb4, 0x3b, 0xa5, 0x60, 0x80, 0x08, 0xd7, 0x97, 0xe3, 0x59, 0xae, 0x12, 0xb1, 0xc8, 0x23, 0x76, - 0xe4, 0xb6, 0x9a, 0x37, 0x4b, 0xf1, 0x2d, 0x3d, 0x25, 0x43, 0x96, 0xf1, 0x47, 0x39, 0xde, 0xc7, - 0xa2, 0xa3, 0xae, 0x68, 0x7e, 0xe7, 0xb8, 0x4e, 0x62, 0x7a, 0x4a, 0xee, 0x81, 0x7e, 0x05, 0x86, - 0xd8, 0xdc, 0x14, 0xbd, 0x89, 0x78, 0x6c, 0xfe, 0xaa, 0x78, 0xac, 0x9c, 0xad, 0x65, 0x3c, 0x93, - 0xd4, 0x84, 0x88, 0x78, 0x6c, 0xa9, 0x6b, 0x19, 0x31, 0xc8, 0x55, 0x18, 0x5a, 0x77, 0x1b, 0x32, - 0x92, 0xfa, 0x1c, 0x46, 0x1f, 0xd1, 0x32, 0xae, 0xce, 0x67, 0x4c, 0xc4, 0x60, 0x6d, 0x0d, 0xf3, - 0x4f, 0xa8, 0x6d, 0x6d, 0xed, 0xda, 0xc9, 0x44, 0x77, 0x4a, 0xd2, 0x9b, 0x65, 0x98, 0xda, 0x76, - 0xda, 0x0d, 0xf7, 0xd0, 0xaf, 0x50, 0xff, 0x20, 0x70, 0x3b, 0xc2, 0xde, 0x18, 0xb5, 0xfb, 0x87, - 0xbc, 0xc4, 0x6a, 0xf0, 0x22, 0xf5, 0x59, 0x46, 0x27, 0x22, 0x4b, 0x30, 0xa9, 0x45, 0x70, 0x15, - 0x8f, 0xab, 0xa8, 0x0d, 0xd5, 0xe3, 0xbf, 0xaa, 0xda, 0x50, 0x8d, 0x84, 0x9d, 0xe7, 0xe2, 0xfb, - 0x95, 0x27, 0xd6, 0xc4, 0xb7, 0x0b, 0x1c, 0x72, 0x0b, 0xf2, 0x3c, 0xcc, 0x47, 0xb5, 0xa2, 0x3e, - 0x93, 0xf9, 0x08, 0x8b, 0x85, 0xc9, 0x91, 0x88, 0x4a, 0x58, 0x87, 0x4f, 0x43, 0x41, 0x6c, 0x49, - 0x51, 0xae, 0xf6, 0x17, 0x60, 0xa8, 0x5c, 0xad, 0x98, 0xea, 0x36, 0x52, 0x77, 0x1a, 0x9e, 0x89, - 0x50, 0xf4, 0xea, 0x5b, 0xa7, 0xc1, 0xa1, 0xeb, 0x1d, 0x98, 0xd4, 0x0f, 0x3c, 0x87, 0xe7, 0xd3, - 0xc4, 0x85, 0xf8, 0x19, 0xf2, 0x2e, 0x0c, 0xa3, 0xe1, 0x6b, 0xec, 0x64, 0x88, 0xd7, 0xb1, 0x34, - 0x29, 0x26, 0xf0, 0x30, 0x5a, 0xd1, 0x9a, 0x9c, 0x88, 0xbc, 0x0d, 0x43, 0x15, 0xda, 0x3e, 0x8a, - 0xa5, 0xfa, 0x4b, 0x10, 0x87, 0x1b, 0x42, 0x83, 0xb6, 0x8f, 0x4c, 0x24, 0x31, 0x7e, 0x2a, 0x0b, - 0xe7, 0x52, 0x3e, 0xeb, 0xe1, 0x67, 0x9e, 0xd1, 0x5d, 0x71, 0x49, 0xdb, 0x15, 0xe5, 0xfb, 0x78, - 0xcf, 0x8e, 0x4f, 0xdd, 0x24, 0x7f, 0x3e, 0x03, 0x17, 0xf4, 0x09, 0x2a, 0x2c, 0xdd, 0x1f, 0xde, - 0x22, 0xef, 0xc0, 0xc8, 0x0a, 0xb5, 0x1b, 0x54, 0xe6, 0xf5, 0x3a, 0x17, 0x06, 0xe4, 0xe3, 0x31, - 0x0c, 0x78, 0x21, 0x67, 0x1b, 0x79, 0xbc, 0x72, 0x28, 0xa9, 0x88, 0x8f, 0xe3, 0xe2, 0xbb, 0x21, - 0xe3, 0x89, 0xa4, 0x55, 0xd5, 0xc7, 0xca, 0xe4, 0xdb, 0x19, 0x78, 0xbe, 0x0f, 0x0d, 0x1b, 0x38, - 0x36, 0xf4, 0xea, 0xc0, 0xe1, 0x89, 0x8a, 0x50, 0xf2, 0x3e, 0x4c, 0x6f, 0x0a, 0xf1, 0x5f, 0x0e, - 0x47, 0x36, 0x5a, 0x2f, 0xf2, 0x66, 0x60, 0xc9, 0x71, 0x89, 0x23, 0x6b, 0x81, 0x6e, 0x72, 0x7d, - 0x03, 0xdd, 0xa8, 0x71, 0x63, 0x86, 0x06, 0x8d, 0x1b, 0xf3, 0x21, 0xcc, 0xe9, 0x6d, 0x13, 0xe1, - 0x7b, 0xa3, 0xa8, 0x39, 0x99, 0xde, 0x51, 0x73, 0xfa, 0x06, 0x09, 0x35, 0x7e, 0x3c, 0x03, 0x05, - 0x9d, 0xf7, 0x93, 0x8e, 0xe7, 0x7b, 0xda, 0x78, 0x3e, 0x9f, 0x3e, 0x9e, 0xbd, 0x07, 0xf2, 0xff, - 0xca, 0xc4, 0x1b, 0x3b, 0xd0, 0x08, 0x1a, 0x30, 0x52, 0x71, 0x5b, 0xb6, 0xd3, 0x56, 0x53, 0xff, - 0x37, 0x10, 0x62, 0x8a, 0x92, 0xc1, 0x82, 0x0c, 0x5d, 0x86, 0xe1, 0x75, 0xb7, 0x5d, 0xaa, 0x08, - 0x93, 0x62, 0xe4, 0xd3, 0x76, 0xdb, 0x96, 0xdd, 0x30, 0x79, 0x01, 0x59, 0x05, 0xa8, 0xd5, 0x3d, - 0x4a, 0xdb, 0x35, 0xe7, 0x7b, 0x69, 0x4c, 0xd2, 0x60, 0x3d, 0xd4, 0xec, 0xe2, 0xc6, 0xc2, 0x9f, - 0x4e, 0x11, 0xd1, 0xf2, 0x9d, 0xef, 0x55, 0xf7, 0x5b, 0x85, 0x1e, 0xd7, 0x95, 0x88, 0xc3, 0x16, - 0x1b, 0x87, 0x9b, 0x9f, 0xc4, 0xba, 0x4a, 0xad, 0x0a, 0x7b, 0xf8, 0x66, 0xea, 0x70, 0xfc, 0x7e, - 0x06, 0x9e, 0xef, 0x43, 0xf3, 0x14, 0x46, 0xe5, 0xcf, 0xbb, 0xc3, 0x29, 0x40, 0x44, 0x84, 0x99, - 0x94, 0x9d, 0x46, 0xc0, 0x73, 0xf5, 0x4d, 0x8a, 0x4c, 0xca, 0x0c, 0xa0, 0x65, 0x52, 0x66, 0x00, - 0x76, 0x96, 0xae, 0x50, 0x67, 0x6f, 0x9f, 0x9b, 0x87, 0x4d, 0xf2, 0xbd, 0x61, 0x1f, 0x21, 0xea, - 0x59, 0xca, 0x71, 0x8c, 0x7f, 0x3d, 0x0c, 0x17, 0x4d, 0xba, 0xe7, 0xb0, 0x7b, 0xc9, 0x96, 0xef, - 0xb4, 0xf7, 0xb4, 0xb8, 0x3b, 0x46, 0x6c, 0xe5, 0x8a, 0x24, 0x15, 0x0c, 0x12, 0xce, 0xc4, 0x6b, - 0x90, 0x67, 0x62, 0x88, 0xb2, 0x78, 0xf1, 0x8d, 0x8b, 0x09, 0x2b, 0x22, 0xb0, 0xb3, 0x2c, 0x26, - 0xaf, 0x0b, 0x31, 0x49, 0x49, 0x23, 0xc4, 0xc4, 0xa4, 0xef, 0x1c, 0x17, 0xa1, 0x76, 0xe4, 0x07, - 0x14, 0xaf, 0xc8, 0x42, 0x54, 0x0a, 0xef, 0x32, 0x43, 0x3d, 0xee, 0x32, 0x6b, 0x30, 0x57, 0x6a, - 0xf0, 0xd3, 0xd1, 0x6e, 0x6e, 0x78, 0x4e, 0xbb, 0xee, 0x74, 0xec, 0xa6, 0xbc, 0x9f, 0x63, 0x2f, - 0xdb, 0x61, 0xb9, 0xd5, 0x09, 0x11, 0xcc, 0x54, 0x32, 0xd6, 0x8c, 0xca, 0x7a, 0x0d, 0xc3, 0xd3, - 0x88, 0xe7, 0x4b, 0x6c, 0x46, 0xa3, 0xed, 0x63, 0x2b, 0x7c, 0x33, 0x2c, 0xc6, 0x5b, 0x14, 0x1a, - 0x04, 0x6c, 0xae, 0xd6, 0x22, 0x97, 0x6a, 0x9e, 0xe5, 0x80, 0x1b, 0x16, 0x04, 0x4d, 0x1f, 0x4d, - 0x31, 0x35, 0xbc, 0x88, 0xae, 0x56, 0x5b, 0x61, 0x74, 0xf9, 0x04, 0x9d, 0xef, 0xef, 0xab, 0x74, - 0x1c, 0x8f, 0xdc, 0x60, 0x53, 0xa1, 0xe5, 0x06, 0x14, 0xa7, 0xf0, 0x58, 0x74, 0xe7, 0xf2, 0x10, - 0xca, 0xef, 0x5c, 0x0a, 0x0a, 0x79, 0x17, 0x66, 0x97, 0xcb, 0x8b, 0x52, 0xe9, 0x5c, 0x71, 0xeb, - 0x5d, 0x34, 0x04, 0x00, 0xac, 0x0f, 0xc7, 0x90, 0xd6, 0x17, 0xd9, 0x6e, 0x92, 0x86, 0x46, 0xae, - 0xc0, 0x68, 0xb5, 0xc2, 0xfb, 0x7e, 0x5c, 0x4d, 0xe5, 0x25, 0x2c, 0xb3, 0x64, 0x21, 0x79, 0x10, - 0x5d, 0x0a, 0x26, 0x4e, 0x95, 0xde, 0x2f, 0x0e, 0x70, 0x21, 0x78, 0x1b, 0x26, 0x97, 0xdc, 0xa0, - 0xda, 0xf6, 0x03, 0xbb, 0x5d, 0xa7, 0xd5, 0x8a, 0x1a, 0x57, 0x7b, 0xc7, 0x0d, 0x2c, 0x47, 0x94, - 0xb0, 0x2f, 0xd7, 0x31, 0xc9, 0xe7, 0x90, 0xf4, 0x2e, 0x6d, 0x53, 0x2f, 0x8a, 0xa7, 0x3d, 0xcc, - 0xfb, 0x96, 0x91, 0xee, 0x85, 0x25, 0xa6, 0x8e, 0x28, 0xd2, 0x8c, 0xf1, 0xe4, 0xa0, 0x65, 0xb7, - 0x41, 0x7d, 0xbe, 0x5b, 0x7c, 0x17, 0xa5, 0x19, 0x53, 0xda, 0xd6, 0x67, 0x07, 0xfd, 0xf7, 0x31, - 0xcd, 0x58, 0x02, 0x97, 0x7c, 0x0e, 0x86, 0xf1, 0xa7, 0x90, 0x6e, 0x67, 0x53, 0xd8, 0x46, 0x92, - 0x6d, 0x9d, 0x61, 0x9a, 0x9c, 0x80, 0x54, 0x61, 0x54, 0x5c, 0xac, 0xce, 0x92, 0x2c, 0x47, 0xdc, - 0xd0, 0xf8, 0xcc, 0x10, 0xf4, 0x46, 0x03, 0x26, 0xd4, 0x0a, 0xd9, 0x8a, 0x58, 0xb1, 0xfd, 0x7d, - 0xda, 0x60, 0xbf, 0x44, 0x9e, 0x3b, 0x5c, 0x11, 0xfb, 0x08, 0xb5, 0xd8, 0x77, 0x98, 0x0a, 0x0a, - 0x3b, 0x53, 0xab, 0xfe, 0x96, 0x2f, 0x3e, 0x45, 0xa8, 0x5a, 0x1c, 0x54, 0xdb, 0x35, 0x4c, 0x51, - 0x64, 0x7c, 0x0f, 0xcc, 0xad, 0x77, 0x9b, 0x4d, 0x7b, 0xa7, 0x49, 0x65, 0x1e, 0x14, 0x4c, 0x38, - 0xbe, 0x04, 0xc3, 0x35, 0x25, 0x85, 0x79, 0x98, 0x8b, 0x52, 0xc1, 0x41, 0x23, 0xd8, 0x0c, 0x86, - 0x0a, 0x8a, 0x25, 0x2f, 0xe7, 0xa4, 0xc6, 0xef, 0x66, 0x60, 0x4e, 0x9a, 0x0b, 0x78, 0x76, 0xfd, - 0x20, 0xcc, 0x63, 0x7f, 0x45, 0x9b, 0x6b, 0x38, 0x61, 0x63, 0xd3, 0x88, 0xcf, 0xba, 0x7b, 0xf2, - 0x23, 0x74, 0x81, 0x25, 0xed, 0x83, 0x4f, 0xfb, 0x18, 0xf2, 0x2e, 0x8c, 0x8b, 0xe3, 0x51, 0x09, - 0x70, 0x89, 0x51, 0xc4, 0xc4, 0x75, 0x2f, 0x6e, 0xbc, 0xa2, 0xa2, 0xa3, 0x2c, 0xa6, 0x37, 0xe5, - 0x49, 0x65, 0x80, 0x74, 0x59, 0x4c, 0xaf, 0xa3, 0xcf, 0xd4, 0xfd, 0xad, 0xf1, 0x78, 0xdf, 0x8a, - 0xb9, 0x7b, 0x5b, 0x0d, 0x69, 0x97, 0x89, 0x6e, 0xc6, 0x51, 0x48, 0x3b, 0xf5, 0x66, 0x1c, 0xa2, - 0x86, 0x63, 0x92, 0x3d, 0x65, 0x4c, 0xde, 0x97, 0x63, 0x92, 0xeb, 0x3d, 0x31, 0x66, 0xfb, 0x8c, - 0x43, 0x2d, 0x5a, 0x21, 0x43, 0x03, 0xa9, 0x55, 0x9e, 0xc3, 0xd8, 0xfd, 0x9c, 0x24, 0xbe, 0x8b, - 0x0a, 0x4e, 0xaa, 0xae, 0x66, 0x78, 0x70, 0xa6, 0xa7, 0x6c, 0xcd, 0x9f, 0x87, 0x89, 0x52, 0x10, - 0xd8, 0xf5, 0x7d, 0xda, 0xa8, 0xb0, 0xed, 0x49, 0x89, 0xbe, 0x65, 0x0b, 0xb8, 0xfa, 0xc6, 0xa6, - 0xe2, 0xf2, 0x68, 0xb2, 0xb6, 0x2f, 0x8c, 0x69, 0xc3, 0x68, 0xb2, 0x0c, 0xa2, 0x47, 0x93, 0x65, - 0x10, 0x72, 0x03, 0x46, 0xab, 0xed, 0x47, 0x0e, 0xeb, 0x13, 0x1e, 0x80, 0x0b, 0x75, 0x53, 0x0e, - 0x07, 0xa9, 0x9b, 0xab, 0xc0, 0x22, 0x6f, 0x2b, 0x97, 0x9a, 0xb1, 0x48, 0x81, 0xc1, 0x55, 0x5e, - 0x61, 0x84, 0x1d, 0xf5, 0xc2, 0x12, 0xde, 0x72, 0x6e, 0xc3, 0xa8, 0xd4, 0x64, 0x42, 0xa4, 0xb4, - 0x10, 0x94, 0xc9, 0x80, 0x15, 0x12, 0x19, 0x73, 0x92, 0x2b, 0xf9, 0xfa, 0xc6, 0x95, 0x9c, 0xe4, - 0x4a, 0xbe, 0x3e, 0x2d, 0x27, 0xb9, 0x92, 0xb9, 0x2f, 0x54, 0x02, 0x4d, 0x9c, 0xaa, 0x04, 0x7a, - 0x08, 0x13, 0x1b, 0xb6, 0x17, 0x38, 0x4c, 0x46, 0x69, 0x07, 0xfe, 0xfc, 0xa4, 0xa6, 0x37, 0x55, - 0x8a, 0x96, 0x2e, 0xc9, 0xbc, 0xd8, 0x1d, 0x05, 0x5f, 0x4f, 0xe0, 0x1c, 0xc1, 0xd3, 0x4d, 0x69, - 0xa7, 0x9e, 0xc4, 0x94, 0x16, 0x3b, 0x15, 0x75, 0x65, 0xd3, 0x91, 0x46, 0x06, 0x2f, 0x2d, 0x31, - 0x85, 0x59, 0x88, 0x48, 0xbe, 0x02, 0x13, 0xec, 0xef, 0x0d, 0xb7, 0xe9, 0xd4, 0x1d, 0xea, 0xcf, - 0x17, 0xb0, 0x71, 0x97, 0x52, 0x57, 0x3f, 0x22, 0x1d, 0xd5, 0x68, 0xc0, 0x17, 0x30, 0x32, 0x8e, - 0x2b, 0xc1, 0x35, 0x6e, 0xe4, 0x03, 0x98, 0x60, 0xb3, 0x6f, 0xc7, 0xf6, 0xb9, 0x68, 0x3a, 0x13, - 0x19, 0x43, 0x37, 0x04, 0x3c, 0x11, 0xd0, 0x59, 0x25, 0x60, 0xc7, 0x7c, 0xa9, 0xc3, 0x37, 0x48, - 0xa2, 0xcc, 0xf6, 0x4e, 0x62, 0x73, 0x94, 0x68, 0xe4, 0x0b, 0x30, 0x51, 0xea, 0x74, 0xa2, 0x1d, - 0x67, 0x56, 0x51, 0x84, 0x75, 0x3a, 0x56, 0xea, 0xae, 0xa3, 0x51, 0xc4, 0x37, 0xe6, 0xb9, 0x33, - 0x6d, 0xcc, 0xe4, 0xcd, 0x50, 0x5a, 0x3f, 0x17, 0x69, 0x75, 0xc5, 0xc5, 0x51, 0x13, 0xfd, 0xb9, - 0xe0, 0x5e, 0x86, 0x49, 0xae, 0xe6, 0x94, 0xd2, 0xcc, 0xf9, 0xc4, 0xea, 0x49, 0x11, 0x6a, 0x74, - 0x1a, 0xb2, 0x0c, 0x53, 0xdc, 0xdb, 0xbb, 0x29, 0x22, 0x6d, 0xcf, 0x5f, 0xc0, 0x55, 0x8b, 0x5c, - 0xb8, 0x93, 0x78, 0x13, 0x13, 0xb0, 0xd8, 0x1a, 0x97, 0x18, 0x91, 0xf1, 0xc7, 0x19, 0xb8, 0xd0, - 0x63, 0xc4, 0xc3, 0x38, 0xcc, 0x99, 0xfe, 0x71, 0x98, 0xd9, 0xce, 0xa1, 0x6b, 0x45, 0xb0, 0xfd, - 0x42, 0xca, 0x52, 0xc7, 0x4b, 0xca, 0x5b, 0x2e, 0x10, 0x91, 0xe3, 0x48, 0x54, 0x7d, 0xcf, 0x45, - 0xd5, 0x6c, 0x2e, 0x79, 0x08, 0x09, 0x3c, 0xfe, 0x51, 0x4b, 0xc6, 0xc9, 0x71, 0xf1, 0x92, 0x48, - 0xa1, 0x14, 0x0e, 0xeb, 0x47, 0xae, 0xb6, 0x82, 0x53, 0x58, 0x1b, 0xc7, 0x19, 0x18, 0x57, 0xd6, - 0x21, 0xb9, 0xac, 0x78, 0x21, 0x17, 0x78, 0x12, 0x2e, 0x85, 0x43, 0x96, 0x9f, 0x44, 0xb8, 0xa8, - 0xb2, 0xa7, 0x2b, 0xa0, 0xd7, 0x98, 0x28, 0xa4, 0xc4, 0xaa, 0x6e, 0x69, 0xda, 0x62, 0x13, 0xcb, - 0x31, 0x9d, 0xbf, 0xed, 0x07, 0xa5, 0x7a, 0xe0, 0x3c, 0xa2, 0x03, 0x1c, 0x3a, 0x51, 0x3a, 0x7f, - 0xdb, 0x0f, 0x2c, 0x1b, 0xc9, 0x12, 0xe9, 0xfc, 0x43, 0x86, 0xc6, 0xf7, 0x67, 0x00, 0xb6, 0xaa, - 0x65, 0x0c, 0x36, 0xff, 0xa4, 0x42, 0x41, 0x7a, 0x00, 0x5f, 0xc9, 0xbd, 0x8f, 0x38, 0xf0, 0x3f, - 0x65, 0x60, 0x4a, 0x47, 0x23, 0xef, 0xc3, 0x74, 0xad, 0xee, 0xb9, 0xcd, 0xe6, 0x8e, 0x5d, 0x3f, - 0x58, 0x75, 0xda, 0x94, 0x87, 0x4e, 0x1d, 0xe6, 0x67, 0x91, 0x1f, 0x16, 0x59, 0x4d, 0x56, 0x66, - 0xc6, 0x91, 0xc9, 0x0f, 0x64, 0x60, 0xb2, 0xb6, 0xef, 0x1e, 0x86, 0xd1, 0x4e, 0xc5, 0x80, 0x7c, - 0x95, 0xad, 0x6d, 0x7f, 0xdf, 0x3d, 0x8c, 0x32, 0x78, 0x6a, 0xb6, 0xa2, 0xef, 0x0d, 0xf6, 0x8c, - 0x5f, 0x77, 0xf1, 0x26, 0x13, 0xf8, 0xd7, 0xb5, 0x4a, 0x4c, 0xbd, 0x4e, 0xe3, 0xcf, 0x32, 0x30, - 0x8e, 0x77, 0x9e, 0x66, 0x13, 0x65, 0xae, 0xef, 0xa6, 0x74, 0x90, 0x61, 0xbb, 0xfa, 0x0c, 0xec, - 0x5b, 0x30, 0x1d, 0x43, 0x23, 0x06, 0x8c, 0xd4, 0x30, 0xc0, 0x80, 0xaa, 0xa0, 0xe0, 0x21, 0x07, - 0x4c, 0x51, 0x62, 0x2c, 0x2b, 0x64, 0x0f, 0x6f, 0xe2, 0xb3, 0xee, 0x22, 0x80, 0x23, 0x41, 0xf2, - 0x66, 0x43, 0xe2, 0x5f, 0xf2, 0xf0, 0xa6, 0xa9, 0x60, 0x19, 0xeb, 0x30, 0x52, 0x73, 0xbd, 0x60, - 0xe9, 0x88, 0x5f, 0x26, 0x2a, 0xd4, 0xaf, 0xab, 0xef, 0xb6, 0x0e, 0xbe, 0x95, 0xd4, 0x4d, 0x51, - 0x44, 0x8a, 0x30, 0x7c, 0xc7, 0xa1, 0xcd, 0x86, 0x6a, 0xcf, 0xbb, 0xcb, 0x00, 0x26, 0x87, 0xb3, - 0x0b, 0xd7, 0xf9, 0x28, 0x27, 0x4b, 0x64, 0x38, 0xfc, 0xa4, 0xeb, 0xa6, 0xac, 0xf5, 0xef, 0x4b, - 0x61, 0x1e, 0x84, 0x64, 0x4d, 0x7d, 0xba, 0xfa, 0x1f, 0x66, 0x60, 0xa1, 0x37, 0x89, 0x6a, 0x8b, - 0x9c, 0xe9, 0x63, 0x8b, 0xfc, 0x6a, 0xfc, 0x9d, 0x11, 0xd1, 0xc4, 0x3b, 0x63, 0xf4, 0xba, 0x58, - 0x41, 0x53, 0xf0, 0x3a, 0x95, 0x89, 0x58, 0x2e, 0xf7, 0xf9, 0x66, 0x44, 0xe4, 0xc3, 0x1c, 0x20, - 0x8d, 0x29, 0x68, 0x8d, 0xdf, 0x1c, 0x82, 0x8b, 0x3d, 0x29, 0xc8, 0x8a, 0x92, 0xde, 0x69, 0x2a, - 0x4c, 0x2c, 0xd3, 0x13, 0xff, 0x3a, 0xfe, 0x8b, 0xd6, 0x7e, 0x71, 0x6f, 0xb7, 0x07, 0x61, 0x5a, - 0x9f, 0x2c, 0xf2, 0xfa, 0xd4, 0xa9, 0xbc, 0x38, 0x3a, 0x32, 0x83, 0x64, 0x86, 0x1f, 0xf4, 0x8b, - 0xa4, 0x81, 0xed, 0x34, 0x7d, 0x75, 0xd9, 0x35, 0x38, 0xc8, 0x94, 0x65, 0x91, 0x81, 0xf8, 0x50, - 0xba, 0x81, 0xb8, 0xf1, 0xaf, 0x33, 0x30, 0x16, 0x7e, 0x36, 0x59, 0x80, 0xf3, 0x9b, 0x66, 0xa9, - 0xbc, 0x6c, 0x6d, 0x7e, 0xb8, 0xb1, 0x6c, 0x6d, 0xad, 0xd7, 0x36, 0x96, 0xcb, 0xd5, 0x3b, 0xd5, - 0xe5, 0x4a, 0xe1, 0x39, 0x32, 0x03, 0x93, 0x5b, 0xeb, 0xf7, 0xd7, 0x1f, 0x6c, 0xaf, 0x5b, 0xcb, - 0xa6, 0xf9, 0xc0, 0x2c, 0x64, 0xc8, 0x24, 0x8c, 0x99, 0x4b, 0xa5, 0xb2, 0xb5, 0xfe, 0xa0, 0xb2, - 0x5c, 0xc8, 0x92, 0x02, 0x4c, 0x94, 0x1f, 0xac, 0xaf, 0x2f, 0x97, 0x37, 0xab, 0x0f, 0xab, 0x9b, - 0x1f, 0x16, 0x72, 0x84, 0xc0, 0x14, 0x22, 0x6c, 0x98, 0xd5, 0xf5, 0x72, 0x75, 0xa3, 0xb4, 0x5a, - 0x18, 0x62, 0x30, 0x86, 0xaf, 0xc0, 0x86, 0x43, 0x46, 0xf7, 0xb7, 0x96, 0x96, 0x0b, 0x23, 0x0c, - 0x85, 0xfd, 0xa5, 0xa0, 0x8c, 0xb2, 0xea, 0x11, 0xa5, 0x52, 0xda, 0x2c, 0x2d, 0x95, 0x6a, 0xcb, - 0x85, 0x3c, 0xb9, 0x00, 0xb3, 0x1a, 0xc8, 0x5a, 0x7d, 0x70, 0xb7, 0xba, 0x5e, 0x18, 0x23, 0x73, - 0x50, 0x08, 0x61, 0x95, 0x25, 0x6b, 0xab, 0xb6, 0x6c, 0x16, 0x20, 0x0e, 0x5d, 0x2f, 0xad, 0x2d, - 0x17, 0xc6, 0x8d, 0xf7, 0xb8, 0x1f, 0x22, 0xef, 0x6a, 0x72, 0x1e, 0x48, 0x6d, 0xb3, 0xb4, 0xb9, - 0x55, 0x8b, 0x35, 0x7e, 0x1c, 0x46, 0x6b, 0x5b, 0xe5, 0xf2, 0x72, 0xad, 0x56, 0xc8, 0x10, 0x80, - 0x91, 0x3b, 0xa5, 0xea, 0xea, 0x72, 0xa5, 0x90, 0x35, 0x7e, 0x32, 0x03, 0x33, 0x52, 0x02, 0x94, - 0x8f, 0x46, 0x4f, 0xb8, 0x16, 0xdf, 0xd7, 0x2e, 0xb6, 0xd2, 0x49, 0x2c, 0x56, 0x49, 0x9f, 0x65, - 0xf8, 0xf3, 0x19, 0x38, 0x97, 0x8a, 0x4d, 0x3e, 0x84, 0x82, 0xfc, 0x82, 0x35, 0x3b, 0xa8, 0xef, - 0x47, 0xfb, 0xd8, 0xa5, 0x58, 0x2d, 0x31, 0x34, 0xae, 0xd6, 0x8c, 0x12, 0x4e, 0x27, 0xd8, 0x0c, - 0x9e, 0x0e, 0xc1, 0xf8, 0x56, 0x06, 0x2e, 0xf4, 0xa8, 0x86, 0x94, 0x61, 0x24, 0x4c, 0x8c, 0xd3, - 0xc7, 0xe0, 0x6d, 0xee, 0xdb, 0xc7, 0x45, 0x81, 0x88, 0x19, 0x7a, 0xf1, 0x2f, 0x73, 0x24, 0xcc, - 0x74, 0x83, 0xe9, 0x66, 0x78, 0xf7, 0x5d, 0x8c, 0xf5, 0xbc, 0xa8, 0xa9, 0xb4, 0x5d, 0x5b, 0x1a, - 0x17, 0x7d, 0x97, 0xb3, 0x0f, 0x7d, 0xcc, 0x37, 0x63, 0xfc, 0x4c, 0x86, 0x09, 0x77, 0x71, 0x44, - 0x26, 0xf3, 0x96, 0x7c, 0xbf, 0xdb, 0xa2, 0xa6, 0xdb, 0xa4, 0x25, 0x73, 0x5d, 0x1c, 0x1b, 0x28, - 0xad, 0xda, 0x58, 0x80, 0xd7, 0x0a, 0xcb, 0xf6, 0xda, 0xda, 0x6b, 0xb5, 0x4a, 0x43, 0xde, 0x06, - 0x58, 0x7e, 0x1c, 0x50, 0xaf, 0x6d, 0x37, 0xc3, 0x18, 0x31, 0x3c, 0xb2, 0x95, 0x80, 0xea, 0xf2, - 0xb6, 0x82, 0x6c, 0xfc, 0x8d, 0x0c, 0x4c, 0x88, 0x4b, 0x53, 0xa9, 0x49, 0xbd, 0xe0, 0xc9, 0xa6, - 0xd7, 0xdb, 0xda, 0xf4, 0x0a, 0xfd, 0x3b, 0x14, 0xfe, 0xac, 0x38, 0x75, 0x66, 0xfd, 0xf3, 0x0c, - 0x14, 0xe2, 0x88, 0xe4, 0x7d, 0xc8, 0xd7, 0xe8, 0x23, 0xea, 0x39, 0xc1, 0x91, 0xd8, 0x28, 0x65, - 0x0a, 0x41, 0x8e, 0x23, 0xca, 0xf8, 0x7c, 0xf0, 0xc5, 0x2f, 0x33, 0xa4, 0x19, 0x74, 0xbf, 0x57, - 0xd4, 0x1e, 0xb9, 0xa7, 0xa5, 0xf6, 0x30, 0xfe, 0xb7, 0x2c, 0x5c, 0xb8, 0x4b, 0x03, 0xb5, 0x4d, - 0xa1, 0x79, 0xc1, 0xa7, 0x07, 0x6b, 0x97, 0xd2, 0x92, 0x79, 0x18, 0xc5, 0x22, 0x39, 0xbe, 0xa6, - 0xfc, 0x49, 0x96, 0xc2, 0x79, 0x9d, 0xd3, 0x72, 0x94, 0xf5, 0xa8, 0xfb, 0xba, 0x92, 0xb5, 0x28, - 0x9c, 0xd6, 0x57, 0x60, 0x0a, 0xc3, 0xf2, 0x77, 0xd9, 0x72, 0xa0, 0x0d, 0xa1, 0xfe, 0xc9, 0x9b, - 0x31, 0x28, 0x79, 0x1d, 0x0a, 0x0c, 0x52, 0xaa, 0x1f, 0xb4, 0xdd, 0xc3, 0x26, 0x6d, 0xec, 0xd1, - 0x06, 0x1e, 0xeb, 0x79, 0x33, 0x01, 0x97, 0x3c, 0xb7, 0xda, 0xfc, 0xea, 0x46, 0x1b, 0xa8, 0xa3, - 0x11, 0x3c, 0x23, 0xe8, 0xc2, 0xdb, 0x30, 0xfe, 0x31, 0x33, 0x90, 0x19, 0xff, 0x6b, 0x06, 0xe6, - 0xb0, 0x71, 0x4a, 0xc5, 0x32, 0x3b, 0xac, 0xec, 0x2d, 0x25, 0x29, 0x8f, 0xcd, 0x40, 0xfa, 0x52, - 0x08, 0x7b, 0x31, 0xd2, 0x09, 0x65, 0x07, 0xd0, 0x09, 0xd5, 0xce, 0x92, 0x09, 0x7f, 0x40, 0x95, - 0xd6, 0xbd, 0xa1, 0x7c, 0xae, 0x30, 0x14, 0x0d, 0xb9, 0xf1, 0x03, 0x59, 0x18, 0x35, 0x29, 0xa6, - 0x08, 0x27, 0x57, 0x60, 0x74, 0xdd, 0x0d, 0xa8, 0xbf, 0xa6, 0xe5, 0x83, 0x6f, 0x33, 0x90, 0xd5, - 0x6a, 0x98, 0xb2, 0x90, 0x4d, 0xf8, 0x0d, 0xcf, 0x6d, 0x74, 0xeb, 0x81, 0x3a, 0xe1, 0x3b, 0x1c, - 0x64, 0xca, 0x32, 0xf2, 0x06, 0x8c, 0x09, 0xce, 0xe1, 0xa3, 0x2e, 0xda, 0x2e, 0x7b, 0x34, 0x4c, - 0x31, 0x1f, 0x21, 0xa0, 0x4c, 0xcb, 0x05, 0x8c, 0x21, 0x45, 0xa6, 0x4d, 0xc8, 0x0c, 0x52, 0x54, - 0x1f, 0xee, 0x23, 0xaa, 0x7f, 0x1a, 0x46, 0x4a, 0xbe, 0x4f, 0x03, 0x19, 0x45, 0x61, 0x22, 0x0c, - 0x1b, 0xe7, 0xd3, 0x80, 0x33, 0xb6, 0xb1, 0xdc, 0x14, 0x78, 0xc6, 0xbf, 0xca, 0xc2, 0x30, 0xfe, - 0x89, 0x4f, 0xa6, 0x5e, 0x7d, 0x5f, 0x7b, 0x32, 0xf5, 0xea, 0xfb, 0x26, 0x42, 0xc9, 0x4d, 0xd4, - 0x54, 0xc8, 0xfc, 0x51, 0xa2, 0xf5, 0xa8, 0x82, 0x6f, 0x44, 0x60, 0x53, 0xc5, 0x09, 0x5f, 0xf8, - 0x73, 0xa9, 0xb1, 0x53, 0xce, 0x43, 0xf6, 0x41, 0x4d, 0xb4, 0x18, 0x23, 0x72, 0xb9, 0xbe, 0x99, - 0x7d, 0x50, 0xc3, 0xde, 0x58, 0x29, 0x2d, 0xbe, 0x75, 0x5b, 0x34, 0x94, 0xf7, 0xc6, 0xbe, 0xbd, - 0xf8, 0xd6, 0x6d, 0x53, 0x94, 0xb0, 0xfe, 0xc5, 0x6f, 0xc6, 0x87, 0x57, 0xee, 0xf3, 0x8f, 0xfd, - 0x8b, 0x6d, 0xc3, 0x47, 0x56, 0x33, 0x42, 0x20, 0x8b, 0x30, 0x2e, 0x62, 0x4d, 0x20, 0xbe, 0x12, - 0x0b, 0x42, 0xc4, 0xa2, 0xe0, 0x14, 0x2a, 0x12, 0x7f, 0x82, 0x13, 0x03, 0x24, 0xb3, 0xdc, 0x8a, - 0x27, 0x38, 0x39, 0x84, 0xbe, 0xa9, 0xa0, 0xb0, 0x4f, 0xe2, 0x6f, 0x78, 0x91, 0x2f, 0xff, 0x94, - 0x12, 0xb4, 0x00, 0xd3, 0x2c, 0x84, 0x08, 0xc6, 0x2f, 0x67, 0x21, 0xbf, 0xd1, 0xec, 0xee, 0x39, - 0xed, 0x87, 0x37, 0x09, 0x01, 0xbc, 0xc6, 0xc9, 0x3c, 0x1c, 0xec, 0x6f, 0x72, 0x11, 0xf2, 0xf2, - 0xe6, 0x26, 0x37, 0x24, 0x5f, 0xdc, 0xda, 0xe6, 0x41, 0x8e, 0xbb, 0x08, 0xbd, 0x26, 0x7f, 0x92, - 0x9b, 0x10, 0xde, 0xbf, 0x7a, 0x5d, 0xd4, 0x86, 0xd8, 0x62, 0x31, 0x43, 0x34, 0xf2, 0x26, 0xe0, - 0x21, 0x21, 0x2e, 0x0f, 0x52, 0xa1, 0xcd, 0x3f, 0x4d, 0xc8, 0x29, 0x9c, 0x04, 0xd1, 0xc8, 0x2d, - 0x10, 0x13, 0x53, 0x64, 0x53, 0x3f, 0xa7, 0x13, 0xf0, 0xfc, 0x94, 0x92, 0x44, 0xa0, 0x92, 0x77, - 0x61, 0xbc, 0xee, 0x51, 0x7c, 0x75, 0xb4, 0x9b, 0x51, 0x92, 0x74, 0x95, 0xb2, 0x1c, 0x95, 0x3f, - 0xbc, 0x69, 0xaa, 0xe8, 0xc6, 0x2f, 0xe7, 0x61, 0x42, 0xfd, 0x1e, 0x62, 0xc2, 0xac, 0xdf, 0x64, - 0x77, 0x77, 0x61, 0x6c, 0xd6, 0xc1, 0x42, 0x71, 0x9c, 0x5e, 0xd6, 0x3f, 0x88, 0xe1, 0x71, 0xcb, - 0x33, 0x19, 0x24, 0x63, 0xe5, 0x39, 0x73, 0xc6, 0x8f, 0xc0, 0x1c, 0x8f, 0x94, 0x20, 0xef, 0x76, - 0xfc, 0x3d, 0xda, 0x76, 0xe4, 0x7b, 0xcb, 0xcb, 0x1a, 0xa3, 0x07, 0xa2, 0x30, 0xc1, 0x2b, 0x24, - 0x23, 0x6f, 0xc1, 0x88, 0xdb, 0xa1, 0x6d, 0xdb, 0x11, 0x67, 0xdc, 0xf3, 0x31, 0x06, 0xb4, 0x5d, - 0xaa, 0x2a, 0x84, 0x02, 0x99, 0xdc, 0x80, 0x21, 0xf7, 0x20, 0x1c, 0xaf, 0x8b, 0x3a, 0xd1, 0x41, - 0x60, 0x2b, 0x24, 0x88, 0xc8, 0x08, 0x3e, 0xb2, 0x5b, 0xbb, 0x62, 0xc4, 0x74, 0x82, 0x7b, 0x76, - 0x6b, 0x57, 0x25, 0x60, 0x88, 0xe4, 0x03, 0x80, 0x8e, 0xbd, 0x47, 0x3d, 0xab, 0xd1, 0x0d, 0x8e, - 0xc4, 0xb8, 0x5d, 0xd2, 0xc8, 0x36, 0x58, 0x71, 0xa5, 0x1b, 0x1c, 0x29, 0xb4, 0x63, 0x1d, 0x09, - 0x24, 0x25, 0x80, 0x96, 0x1d, 0x04, 0xd4, 0x6b, 0xb9, 0xc2, 0xda, 0x2f, 0x0a, 0x82, 0xc8, 0x19, - 0xac, 0x85, 0xc5, 0x0a, 0x07, 0x85, 0x08, 0x3f, 0xda, 0xf1, 0x6c, 0x91, 0xd3, 0x3e, 0xf6, 0xd1, - 0x8e, 0xa7, 0xb5, 0x92, 0x21, 0x92, 0xcf, 0xc1, 0x68, 0xc3, 0xf1, 0xeb, 0xae, 0xd7, 0x10, 0xd1, - 0x53, 0x5e, 0xd0, 0x68, 0x2a, 0xbc, 0x4c, 0x21, 0x93, 0xe8, 0xec, 0x6b, 0x45, 0x10, 0xd4, 0x75, - 0xf7, 0x10, 0xd5, 0xfc, 0xf1, 0xaf, 0xad, 0x85, 0xc5, 0xea, 0xd7, 0x46, 0x44, 0x6c, 0x28, 0xf7, - 0x9c, 0xa0, 0x69, 0xef, 0x88, 0x77, 0x6e, 0x7d, 0x28, 0xef, 0x62, 0x91, 0x3a, 0x94, 0x1c, 0x99, - 0xbc, 0x0d, 0x79, 0xda, 0x0e, 0x3c, 0xdb, 0x72, 0x1a, 0xc2, 0xa9, 0x52, 0xff, 0x68, 0x76, 0x00, - 0xdb, 0xd5, 0x8a, 0xfa, 0xd1, 0x88, 0x5f, 0x6d, 0xb0, 0xfe, 0xf1, 0xeb, 0x4e, 0x4b, 0xf8, 0x42, - 0xea, 0xfd, 0x53, 0x2b, 0x57, 0xd7, 0xd4, 0xfe, 0x61, 0x88, 0xe4, 0x7d, 0x18, 0x65, 0xeb, 0xb7, - 0xe1, 0xee, 0x89, 0x80, 0x14, 0x86, 0xde, 0x3f, 0xbc, 0x2c, 0x31, 0x5d, 0x25, 0x11, 0x5b, 0xc8, - 0xf6, 0xa1, 0x6f, 0x39, 0x75, 0x11, 0x64, 0x42, 0x5f, 0x8e, 0xa5, 0xed, 0x5a, 0xb5, 0xac, 0x90, - 0x0d, 0xdb, 0x87, 0x7e, 0xb5, 0x4e, 0x16, 0x61, 0x18, 0x53, 0x54, 0x88, 0x40, 0x98, 0x3a, 0x0d, - 0x26, 0xa7, 0x50, 0x69, 0x10, 0x95, 0x0d, 0x64, 0xcb, 0x47, 0xf7, 0x12, 0x91, 0x28, 0x42, 0xef, - 0x93, 0xb5, 0x1a, 0xfa, 0x9c, 0xa8, 0x9f, 0x28, 0xd0, 0xc9, 0x25, 0x80, 0xe8, 0x15, 0x9f, 0xbf, - 0xb9, 0x98, 0x0a, 0xe4, 0xf3, 0x43, 0xff, 0xe7, 0x2f, 0x16, 0x33, 0x4b, 0x00, 0x79, 0x19, 0x21, - 0xc7, 0x58, 0x85, 0x8b, 0x3d, 0xd7, 0x3d, 0xb9, 0x06, 0x85, 0x5d, 0x5b, 0x68, 0xfd, 0xea, 0xfb, - 0x76, 0xbb, 0xfd, 0xff, 0xb3, 0xf7, 0x2d, 0x31, 0x6e, 0x64, 0xd7, 0xa1, 0x2a, 0x92, 0xdd, 0xcd, - 0x3e, 0xec, 0x4f, 0xf5, 0xd5, 0xa7, 0x7b, 0x5a, 0x1a, 0x69, 0x54, 0xa3, 0x91, 0x25, 0x8e, 0x67, - 0x6c, 0x69, 0xde, 0x78, 0x66, 0x6c, 0x8f, 0xc7, 0xd5, 0xec, 0xea, 0x26, 0x25, 0xfe, 0x5c, 0x45, - 0xb6, 0x2c, 0xcb, 0x76, 0xb9, 0x44, 0x56, 0x77, 0x97, 0xcd, 0x66, 0xd1, 0x2c, 0x72, 0x64, 0x19, - 0x0f, 0x78, 0x36, 0x1e, 0x60, 0x03, 0xef, 0x25, 0x71, 0xe2, 0x24, 0xc8, 0x20, 0x1b, 0x2f, 0x62, - 0x04, 0x59, 0x64, 0x1b, 0x24, 0x88, 0xb3, 0xf1, 0xce, 0x80, 0x61, 0xc0, 0x40, 0x76, 0x4e, 0x30, - 0x48, 0x06, 0x48, 0x80, 0x7c, 0x76, 0x41, 0xb2, 0xf0, 0x2a, 0xb8, 0xe7, 0xde, 0x5b, 0x75, 0xeb, - 0x43, 0xaa, 0xe5, 0x19, 0x27, 0x31, 0xe0, 0x55, 0x37, 0xcf, 0x3d, 0xe7, 0xd4, 0xfd, 0xdf, 0x73, - 0xcf, 0x3d, 0x1f, 0x77, 0xc0, 0x77, 0xdc, 0x75, 0x01, 0xaf, 0x30, 0x30, 0xe3, 0xac, 0xbd, 0x05, - 0xe7, 0xb2, 0x06, 0x9c, 0x5c, 0x85, 0x15, 0x39, 0x18, 0x10, 0x67, 0x52, 0x72, 0x46, 0x9e, 0x08, - 0x07, 0xc4, 0x19, 0xfc, 0x40, 0x81, 0x4b, 0xf3, 0xb6, 0x0f, 0xb2, 0x0d, 0xc5, 0xd1, 0xd8, 0xf3, - 0x51, 0x4c, 0xe5, 0xd9, 0x16, 0xc4, 0x6f, 0x4c, 0xa4, 0x80, 0xf2, 0xd4, 0xc4, 0x39, 0xe2, 0x0e, - 0x1e, 0xe6, 0x32, 0x42, 0x3a, 0xce, 0x51, 0x40, 0x5e, 0x84, 0x8d, 0xbe, 0x7b, 0xe8, 0x4c, 0x07, - 0x13, 0x3b, 0xe8, 0x1d, 0xbb, 0x7d, 0x74, 0xc1, 0x42, 0xc3, 0x3d, 0x53, 0xe5, 0x05, 0x96, 0x80, - 0xa7, 0x6a, 0xbc, 0x30, 0xa3, 0xc6, 0x77, 0x0a, 0x45, 0x45, 0xcd, 0x99, 0x68, 0x29, 0xa5, 0x7d, - 0x23, 0x07, 0x5b, 0xb3, 0xd6, 0x0b, 0x79, 0x33, 0xab, 0x0f, 0xd8, 0xc3, 0x85, 0x0c, 0x97, 0x1f, - 0x2e, 0xa4, 0xaf, 0x91, 0xdb, 0x10, 0x3a, 0x50, 0x3d, 0x29, 0x18, 0x82, 0x80, 0x51, 0x9a, 0x91, - 0x13, 0x04, 0x8f, 0xe8, 0x96, 0x90, 0x97, 0x02, 0xea, 0x72, 0x98, 0x4c, 0x23, 0x60, 0xe4, 0x35, - 0x80, 0xde, 0xc0, 0x0f, 0x5c, 0xb4, 0x0f, 0xe0, 0xb2, 0x06, 0x33, 0x0b, 0x0f, 0xa1, 0xf2, 0x83, - 0x30, 0x42, 0x2b, 0x7e, 0xdf, 0xe5, 0x03, 0xe8, 0xc0, 0xe6, 0x8c, 0x0d, 0x92, 0x0e, 0x4f, 0x94, - 0x9d, 0x5e, 0xe4, 0xba, 0x9a, 0x86, 0x39, 0xea, 0x93, 0x3d, 0x9e, 0x9b, 0x35, 0x47, 0x1e, 0x03, - 0x49, 0xef, 0x82, 0x94, 0x3b, 0x37, 0x6e, 0x9e, 0x8e, 0x43, 0xee, 0x0c, 0xd2, 0x1d, 0x0f, 0xc8, - 0x15, 0x28, 0x89, 0x5c, 0x96, 0x54, 0x96, 0x67, 0xcc, 0x81, 0x83, 0xee, 0xba, 0x38, 0x79, 0x30, - 0x62, 0x2a, 0xba, 0xc9, 0x71, 0x29, 0x61, 0x19, 0x21, 0x9d, 0xc7, 0x23, 0xd1, 0xba, 0x4b, 0x62, - 0x7e, 0xc7, 0xcf, 0x26, 0x5e, 0xfa, 0xfb, 0x8a, 0x18, 0xfe, 0xf4, 0xe6, 0xfe, 0xa4, 0xfa, 0x11, - 0x40, 0x2f, 0x25, 0x5e, 0x31, 0xfc, 0x9f, 0x4a, 0x2d, 0x62, 0xd5, 0x71, 0xa9, 0x85, 0xff, 0x24, - 0xd7, 0x61, 0x7d, 0xcc, 0xec, 0x58, 0x27, 0x3e, 0xef, 0x4f, 0x96, 0x37, 0x64, 0x95, 0x81, 0x3b, - 0x3e, 0xf6, 0x29, 0xaf, 0xd7, 0x9d, 0xb0, 0xc3, 0xa4, 0xb3, 0x8e, 0xbc, 0x0c, 0xcb, 0xf4, 0xac, - 0xc3, 0x48, 0x3b, 0x09, 0xf7, 0x08, 0xc4, 0x43, 0xc9, 0xc1, 0x2c, 0x7e, 0x99, 0xff, 0xcf, 0x79, - 0xbd, 0x93, 0x13, 0xcc, 0xe4, 0x93, 0x96, 0x6c, 0xc2, 0x92, 0x3f, 0x3e, 0x92, 0x9a, 0xb6, 0xe8, - 0x8f, 0x8f, 0x68, 0xbb, 0x6e, 0x80, 0xca, 0xbc, 0x75, 0x58, 0xd4, 0x84, 0xe0, 0xf1, 0x90, 0x5d, - 0xc5, 0x8b, 0xe6, 0x1a, 0x83, 0x63, 0xc2, 0xfe, 0xc7, 0xc3, 0x1e, 0xc5, 0x0c, 0x02, 0xdf, 0x96, - 0x03, 0x6c, 0xf1, 0x66, 0xaf, 0x05, 0x81, 0x1f, 0x45, 0xda, 0xea, 0x93, 0x1d, 0x58, 0xa5, 0x7c, - 0xc2, 0x30, 0x5f, 0x5c, 0x10, 0x78, 0x36, 0x2d, 0x08, 0x3c, 0x1e, 0xf6, 0x44, 0x15, 0xcd, 0x95, - 0x40, 0xfa, 0x45, 0xee, 0x82, 0x2a, 0x49, 0x4c, 0xe8, 0xbe, 0x99, 0xb0, 0xa9, 0x8e, 0xd8, 0x48, - 0x92, 0x56, 0x6d, 0x78, 0xe8, 0x9b, 0xeb, 0xbd, 0x38, 0x80, 0x77, 0xcd, 0xf7, 0x14, 0xb1, 0x97, - 0x66, 0x10, 0x11, 0x0d, 0x56, 0x8f, 0x9d, 0xc0, 0x0e, 0x82, 0x13, 0x66, 0x23, 0xc6, 0x03, 0x0b, - 0x97, 0x8e, 0x9d, 0xc0, 0x0a, 0x4e, 0x44, 0xe2, 0x92, 0xf3, 0x14, 0xc7, 0x77, 0xa6, 0x93, 0x63, - 0x5b, 0x96, 0xff, 0x58, 0x8f, 0x9d, 0x3d, 0x76, 0x82, 0x16, 0x2d, 0x93, 0x78, 0x93, 0x6b, 0xb0, - 0x86, 0x7c, 0x7b, 0x9e, 0x60, 0x8c, 0x91, 0x2f, 0xcc, 0x15, 0xca, 0xb8, 0xe7, 0x31, 0xce, 0xbc, - 0x86, 0xff, 0x94, 0x83, 0x0b, 0xd9, 0xbd, 0x83, 0xd3, 0x93, 0xf6, 0x29, 0xfa, 0xe8, 0xf1, 0xba, - 0x2d, 0x53, 0x08, 0x8b, 0x5a, 0x92, 0x35, 0x38, 0xb9, 0xcc, 0xc1, 0x29, 0xc3, 0x06, 0x32, 0xe2, - 0x92, 0xe6, 0xc0, 0x0b, 0x26, 0x3c, 0x18, 0x87, 0xb9, 0x4e, 0x0b, 0xd8, 0x7e, 0x5e, 0xa7, 0x60, - 0xf2, 0x02, 0xac, 0x89, 0x1d, 0xd9, 0x7f, 0x34, 0xa4, 0x1f, 0x66, 0xdb, 0xf1, 0x2a, 0x87, 0xb6, - 0x10, 0x48, 0xce, 0xc3, 0xa2, 0x33, 0x1a, 0xd1, 0x4f, 0xb2, 0x5d, 0x78, 0xc1, 0x19, 0x8d, 0x58, - 0x72, 0x1d, 0xf4, 0x48, 0xb4, 0x0f, 0xd1, 0x4a, 0x88, 0x9b, 0x24, 0x9a, 0x2b, 0x08, 0x64, 0x96, - 0x43, 0x01, 0x5d, 0xf7, 0x94, 0x56, 0xa0, 0x2c, 0x21, 0x0a, 0x38, 0xa3, 0x10, 0xe1, 0x19, 0x28, - 0x8a, 0xf7, 0x6a, 0xe6, 0x58, 0x61, 0x2e, 0x39, 0xfc, 0xad, 0xfa, 0x55, 0xd8, 0xec, 0x7b, 0x01, - 0x4e, 0x5e, 0xd6, 0xa4, 0xd1, 0x88, 0xfb, 0x40, 0xb2, 0x20, 0xbd, 0xe6, 0x39, 0x5e, 0x4c, 0x7b, - 0x52, 0x1f, 0x8d, 0x98, 0x27, 0x24, 0xef, 0xeb, 0xd7, 0x61, 0x9d, 0x4b, 0x5c, 0xfc, 0x88, 0xc4, - 0xba, 0xf0, 0x05, 0x4c, 0xaf, 0x42, 0x3c, 0x9d, 0x11, 0x70, 0x50, 0xad, 0x2f, 0x28, 0xff, 0x56, - 0x81, 0xf3, 0x99, 0x22, 0x1b, 0xf9, 0x12, 0x30, 0x97, 0xaf, 0x89, 0x6f, 0x8f, 0xdd, 0x9e, 0x37, - 0xf2, 0x30, 0x86, 0x06, 0x53, 0x69, 0xde, 0x9e, 0x27, 0xec, 0xa1, 0xfb, 0x58, 0xc7, 0x37, 0x43, - 0x22, 0xa6, 0x6b, 0x51, 0xc7, 0x09, 0xf0, 0xf6, 0x03, 0x38, 0x9f, 0x89, 0x9a, 0xa1, 0x03, 0xf9, - 0x70, 0x3c, 0x99, 0xb4, 0x78, 0xa4, 0x4a, 0x34, 0x5a, 0xd2, 0x8d, 0xf0, 0xe6, 0xfd, 0x30, 0x6c, - 0x5e, 0x42, 0xb8, 0x23, 0x46, 0x72, 0x5d, 0x67, 0xdd, 0x4f, 0x04, 0xd1, 0xec, 0xa5, 0xfd, 0x00, - 0xce, 0xf3, 0xc9, 0x77, 0x34, 0x76, 0x46, 0xc7, 0x11, 0x3b, 0x56, 0xd1, 0x0f, 0x65, 0xb1, 0x63, - 0xb3, 0x72, 0x9f, 0xe2, 0x87, 0x5c, 0xcf, 0x3a, 0x69, 0x20, 0x6f, 0xc3, 0x37, 0x73, 0x62, 0xa9, - 0x67, 0x54, 0x27, 0x63, 0x5a, 0x2b, 0x59, 0xd3, 0xfa, 0xf4, 0x6b, 0xaa, 0x09, 0x44, 0xde, 0xac, - 0x98, 0xd6, 0x93, 0x1b, 0x54, 0x09, 0x39, 0x9d, 0x57, 0x44, 0xda, 0x1a, 0x2c, 0x96, 0xcc, 0x73, - 0xa3, 0x97, 0x04, 0x91, 0x8b, 0xb0, 0x1c, 0xe6, 0xcb, 0xe6, 0x07, 0x47, 0x91, 0x01, 0x6a, 0x7d, - 0xf2, 0x1c, 0xac, 0x30, 0x91, 0x3c, 0xb6, 0xe6, 0x00, 0x61, 0x3a, 0x5d, 0x78, 0xa2, 0x0f, 0x14, - 0x78, 0xee, 0x49, 0x7d, 0x48, 0xee, 0xc1, 0x05, 0x34, 0xeb, 0x08, 0xfc, 0x70, 0x18, 0xec, 0x9e, - 0xd3, 0x3b, 0x76, 0xf9, 0xac, 0xd5, 0x32, 0x07, 0x63, 0x34, 0xb2, 0xac, 0x96, 0x34, 0x0e, 0xa3, - 0x91, 0x15, 0xf8, 0xe2, 0x77, 0x85, 0x92, 0xf3, 0x3a, 0xf4, 0xe1, 0xe2, 0x1c, 0x4a, 0x69, 0xe3, - 0x50, 0xe4, 0x8d, 0xe3, 0x06, 0xa8, 0x87, 0x6e, 0x9f, 0xca, 0xc4, 0x6e, 0x1f, 0xab, 0xf6, 0xf6, - 0x6d, 0x96, 0x21, 0xde, 0x5c, 0x0b, 0xe1, 0x56, 0xe0, 0x1f, 0xdc, 0xe6, 0x5f, 0x39, 0x11, 0x47, - 0x9e, 0x7c, 0xad, 0x20, 0x2f, 0xc3, 0xd9, 0x44, 0x7c, 0x92, 0xc8, 0xe1, 0xdd, 0xdc, 0xa0, 0x45, - 0xf1, 0x68, 0x56, 0x57, 0x61, 0x45, 0xcc, 0x8a, 0x71, 0xe8, 0x07, 0x67, 0x96, 0x38, 0x8c, 0xae, - 0x3a, 0xfe, 0xb9, 0xa9, 0x68, 0x54, 0xe6, 0x8d, 0xe4, 0x14, 0xb2, 0x34, 0x79, 0x09, 0x48, 0x28, - 0xb7, 0x87, 0x1b, 0x05, 0xff, 0xe0, 0x86, 0x28, 0x09, 0x57, 0x38, 0xff, 0xec, 0x5f, 0xe5, 0xe0, - 0x6c, 0xc6, 0x55, 0x86, 0x5e, 0x02, 0xbc, 0xe1, 0xc4, 0x3d, 0x62, 0x57, 0x08, 0xb9, 0x91, 0xeb, - 0x12, 0x9c, 0xeb, 0xa7, 0x16, 0x59, 0x06, 0x74, 0xfe, 0x2d, 0xfe, 0x8b, 0x6e, 0x1e, 0xce, 0x58, - 0xa8, 0x5e, 0xe8, 0xbf, 0xa4, 0x06, 0x1b, 0x98, 0xd6, 0x21, 0xf0, 0x7c, 0xcc, 0x0e, 0x81, 0x42, - 0x48, 0x21, 0x76, 0xd9, 0xc1, 0x5a, 0xb4, 0x25, 0x24, 0x2a, 0x85, 0x98, 0xea, 0x28, 0x01, 0x21, - 0x9f, 0x80, 0x6d, 0xe9, 0xac, 0xb1, 0x13, 0x2b, 0x0f, 0x2d, 0xdd, 0xcd, 0x4d, 0x27, 0x3c, 0x75, - 0x76, 0x63, 0x6b, 0x70, 0x07, 0x2e, 0xe3, 0x20, 0x7a, 0xfd, 0x91, 0x9d, 0xca, 0x03, 0x82, 0x4d, - 0x65, 0x81, 0xf3, 0xb7, 0x29, 0x56, 0xad, 0x3f, 0x4a, 0xa4, 0x04, 0xa1, 0xad, 0xe6, 0xdd, 0xf7, - 0x00, 0xce, 0x67, 0xd6, 0x98, 0x1e, 0x30, 0x68, 0x48, 0x15, 0xc9, 0x46, 0x4b, 0xf4, 0x37, 0x15, - 0x8e, 0xae, 0xc2, 0xca, 0x43, 0xd7, 0x19, 0xbb, 0x63, 0x7e, 0x72, 0xf3, 0x29, 0xc1, 0x60, 0xf2, - 0xc1, 0xdd, 0x8f, 0x0f, 0x0d, 0xd7, 0x19, 0x91, 0x06, 0x9c, 0x65, 0x27, 0xa0, 0x77, 0x82, 0xc2, - 0x20, 0xd7, 0x33, 0x29, 0x31, 0x71, 0x08, 0x49, 0xf0, 0x68, 0xaa, 0x21, 0x16, 0xa3, 0x36, 0x37, - 0x8e, 0x92, 0x20, 0xba, 0xa2, 0x2f, 0x64, 0x63, 0x93, 0x1d, 0x28, 0x31, 0xe6, 0xec, 0x5a, 0xc0, - 0x1e, 0x08, 0xae, 0xce, 0xfd, 0x42, 0x05, 0xed, 0x8b, 0x83, 0xf0, 0x7f, 0x7a, 0x5e, 0xe3, 0x5b, - 0xac, 0x7d, 0x22, 0xbf, 0x7f, 0x98, 0x2b, 0x08, 0xe4, 0xef, 0x1e, 0xda, 0x5f, 0x2b, 0xa2, 0xa9, - 0xb1, 0xcb, 0x31, 0x9d, 0x5a, 0x81, 0x3b, 0x14, 0x6f, 0x40, 0xcb, 0x26, 0xff, 0xf5, 0x94, 0x53, - 0x9d, 0xbc, 0x06, 0x2b, 0x94, 0xed, 0xd1, 0x74, 0xc8, 0xa6, 0x5c, 0x3e, 0x16, 0x97, 0xa7, 0xc1, - 0x8a, 0xe8, 0xb0, 0x55, 0xcf, 0x98, 0xa5, 0x93, 0xe8, 0x27, 0x95, 0x96, 0x83, 0x93, 0xc9, 0x48, - 0x9e, 0xa8, 0x42, 0x51, 0x68, 0x35, 0x3a, 0x6d, 0x4e, 0x52, 0xa4, 0x38, 0x91, 0xb4, 0xbc, 0xb3, - 0xc8, 0x54, 0x85, 0xda, 0x8b, 0x50, 0x92, 0x78, 0xd3, 0xc6, 0x30, 0xcf, 0x19, 0xd1, 0x18, 0xf6, - 0x8b, 0x0f, 0xf6, 0x43, 0x28, 0x0a, 0x96, 0xf4, 0x5a, 0x70, 0xec, 0x07, 0x62, 0x91, 0xe3, 0xff, - 0x14, 0x46, 0x7b, 0x19, 0x1b, 0xb9, 0x60, 0xe2, 0xff, 0x78, 0x96, 0x4c, 0x1c, 0x7a, 0x1f, 0x18, - 0x04, 0xf6, 0x08, 0x2d, 0xb0, 0x42, 0xe1, 0x99, 0xc2, 0x3b, 0x83, 0x80, 0xd9, 0x65, 0xf1, 0x6f, - 0xfc, 0x45, 0x78, 0x08, 0x27, 0xb4, 0x09, 0xb3, 0xf6, 0xcc, 0xd8, 0x91, 0x91, 0x4b, 0x1f, 0x19, - 0x2c, 0xde, 0x0a, 0xa7, 0x64, 0x5f, 0x06, 0x84, 0xe1, 0x91, 0x21, 0xed, 0x0c, 0x85, 0xd8, 0xce, - 0x20, 0xdd, 0xc9, 0xa3, 0xd1, 0x63, 0x27, 0x8e, 0xb8, 0x93, 0x27, 0xf7, 0xa9, 0x3f, 0xce, 0x09, - 0x15, 0xc1, 0x8e, 0xef, 0x4f, 0x82, 0xc9, 0xd8, 0x19, 0xc5, 0x54, 0xa1, 0xe4, 0x04, 0x9e, 0x41, - 0x09, 0xfa, 0x36, 0xa6, 0xd0, 0xf0, 0xc7, 0x22, 0xc4, 0x47, 0x38, 0x73, 0x4b, 0xb7, 0x3f, 0x12, - 0x97, 0xf1, 0x75, 0x8a, 0xad, 0xcb, 0xc8, 0x74, 0xc2, 0x4a, 0x5c, 0xab, 0x67, 0xcc, 0x4d, 0xc6, - 0x33, 0x85, 0x45, 0xaa, 0x19, 0x8b, 0x38, 0xa9, 0x0b, 0xdd, 0x89, 0x56, 0x74, 0x9c, 0xab, 0xbc, - 0xd6, 0xc9, 0xa7, 0x60, 0xd9, 0xeb, 0xcb, 0x99, 0x22, 0x93, 0x5a, 0xb8, 0x5a, 0x9f, 0x45, 0xab, - 0x8e, 0x78, 0xd0, 0x39, 0xe7, 0x71, 0xe8, 0xce, 0x6a, 0x4c, 0x69, 0xac, 0xed, 0x88, 0xdb, 0x68, - 0x9a, 0x8c, 0xac, 0x41, 0x2e, 0x1c, 0xe1, 0x9c, 0xd7, 0x67, 0xcb, 0x2b, 0x8a, 0x97, 0x6d, 0xf2, - 0x5f, 0xda, 0xff, 0x86, 0x1b, 0xa7, 0xed, 0x23, 0xba, 0x14, 0x67, 0x74, 0xf8, 0x32, 0x0b, 0x55, - 0x19, 0xef, 0xb7, 0xab, 0x20, 0x87, 0xfb, 0xf5, 0xc4, 0xe6, 0x27, 0x60, 0xdd, 0xb1, 0xa7, 0xfd, - 0x79, 0x1e, 0xd6, 0xe2, 0x6a, 0x72, 0xf2, 0x22, 0x14, 0xa4, 0x1d, 0x68, 0x33, 0x43, 0x97, 0x8e, - 0xfb, 0x0e, 0x22, 0x9d, 0x6a, 0xc7, 0x21, 0x77, 0x60, 0x0d, 0x0d, 0xf7, 0x50, 0xf4, 0x9c, 0x78, - 0xfc, 0xf1, 0x65, 0xfe, 0xfb, 0x59, 0xf1, 0x47, 0xef, 0x5e, 0x39, 0x83, 0x4f, 0x65, 0x2b, 0x94, - 0x96, 0x4a, 0x7f, 0xb4, 0x50, 0xd2, 0x82, 0x16, 0x66, 0x6b, 0x41, 0x79, 0x53, 0x66, 0x68, 0x41, - 0x17, 0xe6, 0x68, 0x41, 0x23, 0x4a, 0x59, 0x0b, 0x8a, 0xba, 0xf0, 0xa5, 0x59, 0xba, 0xf0, 0x88, - 0x86, 0xe9, 0xc2, 0x23, 0x2d, 0x66, 0x71, 0xa6, 0x16, 0x33, 0xa2, 0xe1, 0x5a, 0xcc, 0x6b, 0xbc, - 0x8f, 0xc6, 0xce, 0x23, 0x1b, 0x3b, 0x8f, 0x1f, 0x8b, 0xd8, 0x7a, 0xd3, 0x79, 0x84, 0xc6, 0x35, - 0x3b, 0xcb, 0x20, 0x2c, 0x72, 0xb4, 0xdf, 0x55, 0x12, 0x9a, 0x40, 0x31, 0x7e, 0x2f, 0xc0, 0x1a, - 0x3b, 0xac, 0x78, 0x38, 0x53, 0x76, 0x5a, 0xad, 0x9a, 0xab, 0x02, 0xca, 0xee, 0x9b, 0x1f, 0x82, - 0xf5, 0x10, 0x8d, 0x5f, 0xb9, 0xd0, 0x53, 0xcf, 0x0c, 0xa9, 0x79, 0xd8, 0x99, 0x17, 0x61, 0x23, - 0x44, 0xe4, 0xda, 0x1c, 0x76, 0xdd, 0x5c, 0x35, 0x55, 0x51, 0xd0, 0xe6, 0x70, 0xed, 0x28, 0x79, - 0xf3, 0xf8, 0x25, 0xd5, 0x4a, 0xfb, 0x61, 0x3e, 0xa6, 0x25, 0x11, 0x9f, 0xa1, 0xa7, 0x68, 0xe0, - 0xdb, 0xbc, 0x93, 0xf8, 0x5e, 0x74, 0x75, 0xc6, 0x98, 0x71, 0x9b, 0x26, 0xcb, 0x6a, 0x99, 0x10, - 0x04, 0xbe, 0x30, 0x71, 0xb2, 0x99, 0x44, 0xcd, 0xce, 0x7d, 0x9c, 0xb3, 0x82, 0x1d, 0xdb, 0x78, - 0xca, 0xf3, 0xd9, 0x89, 0x6b, 0x2a, 0x9d, 0xb2, 0x28, 0x59, 0x87, 0xbf, 0xc4, 0x07, 0xba, 0x80, - 0x4a, 0xc5, 0x20, 0xce, 0x3c, 0x9f, 0x71, 0x77, 0x4a, 0x31, 0xc7, 0x5e, 0x42, 0xce, 0xea, 0x54, - 0xfc, 0x2b, 0xd8, 0x1a, 0xb0, 0x82, 0x3a, 0x0a, 0xc1, 0xb0, 0x90, 0xa1, 0x82, 0x4f, 0x37, 0xbe, - 0x52, 0x6b, 0x98, 0x25, 0x4a, 0x27, 0xd8, 0x1c, 0xc3, 0x33, 0xb2, 0x66, 0x21, 0x5e, 0xc9, 0x05, - 0x11, 0xc5, 0x77, 0x6e, 0x0f, 0x44, 0x0a, 0x08, 0xac, 0xea, 0x05, 0x27, 0x0e, 0xe0, 0x68, 0xda, - 0x31, 0x6c, 0xcf, 0x1e, 0x92, 0x39, 0x19, 0xa2, 0xa2, 0x03, 0x34, 0x27, 0x1f, 0xa0, 0xb2, 0x9e, - 0x21, 0x1f, 0xd3, 0x33, 0x68, 0x7f, 0x94, 0x87, 0xe7, 0x4f, 0x31, 0x5c, 0x73, 0xbe, 0xf9, 0xe9, - 0xb8, 0x78, 0x96, 0x8b, 0xdd, 0x0c, 0x29, 0x53, 0xbe, 0x41, 0xd2, 0x5b, 0x6a, 0xb6, 0x70, 0xf6, - 0x25, 0x58, 0x67, 0xbb, 0x20, 0x33, 0x4b, 0x3c, 0x9c, 0x0e, 0x4e, 0xb1, 0x0d, 0x5e, 0x14, 0x3e, - 0x54, 0x09, 0x52, 0xdc, 0x19, 0x71, 0xc7, 0xb0, 0x42, 0x18, 0xe9, 0x40, 0x09, 0xd1, 0x0e, 0x1d, - 0x6f, 0x70, 0x2a, 0x67, 0x1e, 0xe1, 0xa1, 0x25, 0x93, 0x31, 0x6b, 0x6a, 0x0a, 0xd8, 0xc3, 0xdf, - 0xe4, 0x3a, 0xac, 0x0f, 0xa7, 0x27, 0x54, 0xf0, 0x60, 0x73, 0x81, 0x5b, 0x7f, 0x2c, 0x98, 0xab, - 0xc3, 0xe9, 0x89, 0x3e, 0x1a, 0xe1, 0x90, 0xa2, 0x99, 0xc8, 0x06, 0xc5, 0x63, 0xab, 0x56, 0x60, - 0x2e, 0x22, 0x26, 0x65, 0xc0, 0xd6, 0x2d, 0xc7, 0x3d, 0x07, 0xcc, 0x68, 0x90, 0x67, 0xc8, 0x62, - 0x3f, 0xb4, 0xff, 0xc8, 0x89, 0xfb, 0xee, 0xec, 0x79, 0xff, 0xeb, 0x21, 0xca, 0x18, 0xa2, 0x1b, - 0xa0, 0xd2, 0xae, 0x8f, 0x36, 0x95, 0x70, 0x8c, 0xd6, 0x86, 0xd3, 0x93, 0xb0, 0xef, 0xe4, 0x8e, - 0x5f, 0x94, 0x3b, 0xfe, 0x35, 0x71, 0x1f, 0xce, 0xdc, 0x1e, 0x66, 0x77, 0xb9, 0xf6, 0xaf, 0x79, - 0xb8, 0x7e, 0xba, 0x4d, 0xe0, 0xd7, 0xe3, 0x96, 0x31, 0x6e, 0x09, 0xd5, 0xe9, 0x42, 0x4a, 0x75, - 0x9a, 0xb1, 0xf6, 0x16, 0xb3, 0xd6, 0x5e, 0x4a, 0x51, 0xbb, 0x94, 0xa1, 0xa8, 0xcd, 0x5c, 0xa0, - 0xc5, 0x27, 0x2c, 0xd0, 0x65, 0x79, 0x9e, 0xfc, 0x63, 0xa8, 0xc0, 0x88, 0xdf, 0x07, 0x1e, 0xc0, - 0x59, 0x71, 0x1f, 0x60, 0x27, 0x47, 0xa4, 0x7f, 0x2f, 0xdd, 0xbe, 0x99, 0x75, 0x13, 0x40, 0xb4, - 0x0c, 0x69, 0x7d, 0x83, 0xdf, 0x01, 0xa2, 0xf2, 0xff, 0x39, 0xd2, 0x3f, 0xb9, 0x0f, 0x17, 0x30, - 0xbe, 0x7c, 0x4f, 0x7e, 0x39, 0xb0, 0xc7, 0xee, 0x21, 0x9f, 0x0f, 0x57, 0x53, 0xb2, 0xb2, 0xd7, - 0x93, 0xaa, 0x63, 0xba, 0x87, 0xd5, 0x33, 0xe6, 0xb9, 0x20, 0x03, 0x9e, 0xbc, 0x58, 0xfc, 0xa9, - 0x02, 0xda, 0x93, 0xfb, 0x0b, 0x15, 0x55, 0xc9, 0x0e, 0x5f, 0x36, 0x4b, 0x8e, 0xd4, 0x7b, 0xcf, - 0xc3, 0xea, 0xd8, 0x3d, 0x1c, 0xbb, 0xc1, 0x71, 0x4c, 0x03, 0xb2, 0xc2, 0x81, 0xa2, 0x63, 0x44, - 0x50, 0xca, 0xa7, 0x92, 0xcc, 0x05, 0x91, 0xb6, 0x17, 0xde, 0x17, 0x33, 0xc7, 0x81, 0xce, 0x26, - 0xb9, 0x82, 0xec, 0xc7, 0x9d, 0x42, 0x31, 0xa7, 0xe6, 0x4d, 0x1e, 0x3a, 0xf3, 0xd0, 0x1b, 0xb8, - 0xda, 0x5f, 0x2a, 0x42, 0x22, 0xc8, 0xea, 0x3c, 0xf2, 0x40, 0x32, 0xe6, 0xcd, 0xa7, 0xc4, 0x90, - 0x2c, 0x12, 0xd9, 0xee, 0x91, 0x87, 0x67, 0x44, 0x40, 0x2c, 0x3c, 0x23, 0x42, 0xde, 0x87, 0x45, - 0x22, 0xbf, 0x35, 0xbf, 0x21, 0x2c, 0x82, 0xe8, 0x9e, 0x77, 0x70, 0x8b, 0xdc, 0x84, 0x25, 0x66, - 0x04, 0x24, 0xaa, 0xbb, 0x1e, 0xab, 0xee, 0xc1, 0x2d, 0x53, 0x94, 0x6b, 0xef, 0x84, 0xef, 0x5a, - 0xa9, 0x46, 0x1c, 0xdc, 0x22, 0xaf, 0x9d, 0xce, 0x38, 0xb7, 0x28, 0x8c, 0x73, 0x43, 0xc3, 0xdc, - 0xd7, 0x63, 0x86, 0xb9, 0xd7, 0xe6, 0xf7, 0x16, 0x7f, 0x8d, 0x64, 0xe1, 0x08, 0xa3, 0x30, 0x55, - 0x3f, 0xcb, 0xc1, 0xb3, 0x73, 0x29, 0xc8, 0x25, 0x28, 0xea, 0xed, 0x5a, 0x27, 0x1a, 0x5f, 0xba, - 0x66, 0x04, 0x84, 0xec, 0xc3, 0xf2, 0x8e, 0x13, 0x78, 0x3d, 0x3a, 0x8d, 0x33, 0x9f, 0x07, 0x52, - 0x6c, 0x43, 0xf4, 0xea, 0x19, 0x33, 0xa2, 0x25, 0x36, 0x6c, 0xe0, 0x5a, 0x88, 0xa5, 0x9e, 0xca, - 0x67, 0xe8, 0x1a, 0x52, 0x0c, 0x53, 0x64, 0x74, 0x9f, 0x49, 0x01, 0xc9, 0x43, 0x20, 0x96, 0x55, - 0xad, 0xb8, 0xe3, 0x09, 0xbf, 0x83, 0x4f, 0xbc, 0xd0, 0xd2, 0xf3, 0xa3, 0x4f, 0xe8, 0xbb, 0x14, - 0x5d, 0xf5, 0x8c, 0x99, 0xc1, 0x2d, 0xb9, 0xcc, 0xdf, 0x16, 0xf2, 0xce, 0xec, 0x4e, 0x78, 0x8a, - 0x50, 0xaf, 0x37, 0xa0, 0xd8, 0x16, 0xb6, 0x08, 0x92, 0xc5, 0xbc, 0xb0, 0x3b, 0x30, 0xc3, 0x52, - 0xed, 0x37, 0x14, 0xa1, 0x74, 0x78, 0x72, 0x67, 0x49, 0x99, 0xc1, 0xfa, 0xf3, 0x33, 0x83, 0xf5, - 0x7f, 0xc1, 0xcc, 0x60, 0x9a, 0x07, 0x37, 0x4f, 0xdd, 0xb1, 0xe4, 0x93, 0xa0, 0x62, 0x12, 0x25, - 0x47, 0x1a, 0x24, 0xb6, 0xbe, 0x36, 0xc2, 0xd8, 0xdf, 0x55, 0x9e, 0xa9, 0xce, 0x5c, 0xef, 0xc5, - 0xa9, 0xb5, 0x3f, 0xe1, 0x31, 0xdf, 0x6b, 0xfd, 0x76, 0x42, 0xd1, 0xfc, 0x7e, 0x9d, 0x2c, 0x8c, - 0xd8, 0x62, 0x7b, 0x5e, 0x4a, 0x62, 0x99, 0xfe, 0xd6, 0x6c, 0x5f, 0x0b, 0x69, 0xe5, 0xfd, 0x41, - 0x1e, 0x2e, 0xcd, 0x23, 0xcf, 0x4c, 0x93, 0xad, 0x3c, 0x5d, 0x9a, 0xec, 0x9b, 0x50, 0x64, 0xb0, - 0xd0, 0x83, 0x00, 0xc7, 0x96, 0x93, 0xd2, 0xb1, 0x15, 0xc5, 0xe4, 0x79, 0x58, 0xd4, 0x2b, 0x56, - 0x94, 0xb9, 0x0d, 0x4d, 0x7d, 0x9d, 0x5e, 0x80, 0x46, 0xa4, 0xbc, 0x88, 0x7c, 0x31, 0x9d, 0xac, - 0x90, 0xa7, 0x6c, 0xbb, 0x28, 0x75, 0x48, 0x2a, 0x1d, 0x03, 0xd6, 0x37, 0x4a, 0x1f, 0xc0, 0x23, - 0x72, 0x9b, 0xe9, 0xc4, 0x87, 0x1a, 0x2c, 0xb6, 0xc7, 0x6e, 0xe0, 0x4e, 0x64, 0x33, 0xdc, 0x11, - 0x42, 0x4c, 0x5e, 0xc2, 0x8d, 0x64, 0x9d, 0xc7, 0x2c, 0x26, 0xc2, 0xa2, 0x1c, 0xa7, 0x06, 0xad, - 0x6a, 0x29, 0xd8, 0x94, 0x50, 0x28, 0x41, 0xdd, 0x99, 0x0e, 0x7b, 0xc7, 0x5d, 0xb3, 0xce, 0x25, - 0x27, 0x46, 0x30, 0x40, 0x28, 0x6d, 0x60, 0x60, 0x4a, 0x28, 0xda, 0xb7, 0x15, 0x38, 0x97, 0xd5, - 0x0e, 0x72, 0x09, 0x0a, 0xc3, 0xcc, 0xbc, 0x8c, 0x43, 0xe6, 0xca, 0x5d, 0xa2, 0x7f, 0xed, 0x43, - 0x7f, 0x7c, 0xe2, 0x4c, 0x64, 0x63, 0x65, 0x09, 0x6c, 0x02, 0xfd, 0xb1, 0x87, 0xff, 0x93, 0x2b, - 0xe2, 0xc8, 0xc9, 0xa7, 0x32, 0x39, 0xe2, 0x1f, 0x4d, 0x07, 0xa8, 0xf5, 0xdb, 0xad, 0x11, 0x4b, - 0x07, 0xf0, 0x0a, 0x14, 0x68, 0xb5, 0x12, 0xb3, 0x97, 0xce, 0x1f, 0xbd, 0x51, 0xe7, 0x48, 0xac, - 0x56, 0x81, 0x73, 0x32, 0x30, 0x11, 0x59, 0xbb, 0x07, 0x6b, 0x71, 0x0c, 0x62, 0xc4, 0x23, 0xc2, - 0x96, 0x6e, 0xab, 0x9c, 0xd3, 0x8e, 0xef, 0x33, 0x87, 0x99, 0x9d, 0x67, 0x7e, 0xf6, 0xee, 0x15, - 0xa0, 0x3f, 0x19, 0x4d, 0x56, 0xc4, 0x58, 0xed, 0x3b, 0x39, 0x38, 0x17, 0xf9, 0xe8, 0x8b, 0x35, - 0xf4, 0x2b, 0xeb, 0x30, 0xaa, 0xc7, 0x1c, 0x1a, 0x85, 0xdc, 0x98, 0x6e, 0xe0, 0x1c, 0x3f, 0xaa, - 0x7d, 0xd8, 0x9a, 0x85, 0x4f, 0x5e, 0x84, 0x65, 0x0c, 0xeb, 0x34, 0x72, 0x7a, 0xae, 0xbc, 0xcd, - 0x0e, 0x05, 0xd0, 0x8c, 0xca, 0xb5, 0x9f, 0x28, 0xb0, 0xcd, 0xdd, 0x3c, 0x1a, 0x8e, 0x37, 0xc4, - 0x57, 0x82, 0x9e, 0xfb, 0xc1, 0x38, 0x3c, 0xef, 0xc7, 0xf6, 0xb1, 0x17, 0xe2, 0xde, 0x3c, 0xa9, - 0xaf, 0xcd, 0x6e, 0x2d, 0xb9, 0x89, 0xa1, 0xca, 0xf8, 0x2b, 0x7a, 0x81, 0x05, 0x98, 0x18, 0x52, - 0x80, 0x1c, 0x60, 0x02, 0x31, 0xb4, 0xff, 0x03, 0x97, 0xe7, 0x7f, 0x80, 0x7c, 0x01, 0x56, 0x31, - 0xf7, 0x56, 0x77, 0x74, 0x34, 0x76, 0xfa, 0xae, 0xd0, 0xec, 0x09, 0x6d, 0xac, 0x5c, 0xc6, 0x22, - 0xaf, 0xf1, 0x80, 0x07, 0x47, 0x98, 0xd5, 0x8b, 0x13, 0xc5, 0x7c, 0xa9, 0x64, 0x6e, 0xda, 0x37, - 0x14, 0x20, 0x69, 0x1e, 0xe4, 0x63, 0xb0, 0xd2, 0xed, 0x54, 0xac, 0x89, 0x33, 0x9e, 0x54, 0xfd, - 0xe9, 0x98, 0x87, 0x3d, 0x63, 0xfe, 0xef, 0x93, 0x9e, 0xcd, 0xde, 0x83, 0x8e, 0xfd, 0xe9, 0xd8, - 0x8c, 0xe1, 0x61, 0x8e, 0x27, 0xd7, 0xfd, 0x4a, 0xdf, 0x79, 0x1c, 0xcf, 0xf1, 0xc4, 0x61, 0xb1, - 0x1c, 0x4f, 0x1c, 0xa6, 0x7d, 0x5f, 0x81, 0x8b, 0xc2, 0x38, 0xb2, 0x9f, 0x51, 0x97, 0x0a, 0x46, - 0x79, 0x19, 0x8b, 0x38, 0xbb, 0xf3, 0x24, 0xf4, 0x0d, 0x11, 0x08, 0x09, 0x2b, 0x88, 0xa2, 0x3a, - 0xa3, 0x25, 0x9f, 0x86, 0x82, 0x35, 0xf1, 0x47, 0xa7, 0x88, 0x84, 0xa4, 0x86, 0x23, 0x3a, 0xf1, - 0x47, 0xc8, 0x02, 0x29, 0x35, 0x17, 0xce, 0xc9, 0x95, 0x13, 0x35, 0x26, 0x0d, 0x58, 0xe2, 0x21, - 0xef, 0x12, 0x76, 0x07, 0x73, 0xda, 0xb4, 0xb3, 0x2e, 0xc2, 0x2d, 0xf1, 0x38, 0xaf, 0xa6, 0xe0, - 0xa1, 0xfd, 0x96, 0x02, 0x25, 0x2a, 0xd8, 0xe0, 0xa5, 0xf4, 0xfd, 0x4e, 0xe9, 0xb8, 0x1c, 0x2c, - 0xcc, 0x68, 0x42, 0xf6, 0xa7, 0x3a, 0x8d, 0x5f, 0x85, 0xf5, 0x04, 0x01, 0xd1, 0x30, 0xd0, 0xc6, - 0xc0, 0xeb, 0x39, 0x2c, 0x65, 0x0c, 0x33, 0x41, 0x89, 0xc1, 0xb4, 0xff, 0xa7, 0xc0, 0xb9, 0xd6, - 0x57, 0x26, 0x0e, 0x7b, 0xb6, 0x35, 0xa7, 0x03, 0xb1, 0xde, 0xa9, 0xb0, 0x26, 0xac, 0x6c, 0x59, - 0x10, 0x00, 0x26, 0xac, 0x71, 0x98, 0x19, 0x96, 0x92, 0x2a, 0x14, 0xf9, 0xf9, 0x12, 0xf0, 0xf0, - 0xac, 0x97, 0x25, 0xdd, 0x48, 0xc4, 0x98, 0x23, 0xd1, 0x96, 0xe0, 0x16, 0xc6, 0x69, 0xcc, 0x90, - 0x5a, 0xfb, 0x37, 0x05, 0x36, 0x67, 0xd0, 0x90, 0x37, 0x61, 0x01, 0x1d, 0x14, 0xf9, 0xe8, 0x5d, - 0x9a, 0xf1, 0x89, 0x49, 0xef, 0xf8, 0xe0, 0x16, 0x3b, 0x88, 0x4e, 0xe8, 0x0f, 0x93, 0x51, 0x91, - 0x07, 0xb0, 0xac, 0xf7, 0xfb, 0xfc, 0x76, 0x96, 0x8b, 0xdd, 0xce, 0x66, 0x7c, 0xf1, 0xe5, 0x10, - 0x9f, 0xdd, 0xce, 0x98, 0xab, 0x4c, 0xbf, 0x6f, 0x73, 0xe7, 0xcb, 0x88, 0xdf, 0xf6, 0x27, 0x61, - 0x2d, 0x8e, 0xfc, 0x54, 0xfe, 0x62, 0xef, 0x28, 0xa0, 0xc6, 0xeb, 0xf0, 0xcb, 0x09, 0x14, 0x95, - 0x35, 0xcc, 0x4f, 0x98, 0x54, 0xbf, 0x93, 0x83, 0xf3, 0x99, 0x3d, 0x4c, 0x5e, 0x82, 0x45, 0x7d, - 0x34, 0xaa, 0xed, 0xf2, 0x59, 0xc5, 0x25, 0x24, 0x54, 0x7a, 0xc7, 0x2e, 0xaf, 0x0c, 0x89, 0xbc, - 0x02, 0x45, 0x66, 0x1d, 0xb0, 0x2b, 0x36, 0x1c, 0x8c, 0x7c, 0xc3, 0x4d, 0x17, 0xe2, 0x81, 0x52, - 0x05, 0x22, 0xd9, 0x83, 0x35, 0x1e, 0x33, 0xc6, 0x74, 0x8f, 0xdc, 0xaf, 0x85, 0x11, 0xfb, 0x31, - 0xa9, 0x80, 0xd0, 0xa4, 0xdb, 0x63, 0x56, 0x26, 0x47, 0x4d, 0x89, 0x53, 0x91, 0x3a, 0xa8, 0xc8, - 0x53, 0xe6, 0xc4, 0xa2, 0xb5, 0x62, 0x14, 0x1f, 0x56, 0x89, 0x19, 0xbc, 0x52, 0x94, 0xe1, 0x70, - 0xe9, 0x41, 0xe0, 0x1d, 0x0d, 0x4f, 0xdc, 0xe1, 0xe4, 0x97, 0x37, 0x5c, 0xd1, 0x37, 0x4e, 0x35, - 0x5c, 0xbf, 0x57, 0x60, 0x8b, 0x39, 0x49, 0x46, 0x25, 0x1a, 0x29, 0x40, 0x37, 0x4a, 0x34, 0xf4, - 0x7e, 0xc6, 0xa3, 0xa2, 0xec, 0xc2, 0x12, 0x8b, 0x56, 0x23, 0x56, 0xc6, 0xb3, 0x99, 0x55, 0x60, - 0x38, 0x07, 0xb7, 0x98, 0xf8, 0xc2, 0x3c, 0x25, 0x03, 0x53, 0x90, 0x92, 0x03, 0x28, 0x55, 0x06, - 0xae, 0x33, 0x9c, 0x8e, 0x3a, 0xa7, 0x7b, 0x41, 0xdd, 0xe2, 0x6d, 0x59, 0xe9, 0x31, 0x32, 0x7c, - 0x79, 0xc5, 0x9d, 0x5c, 0x66, 0x44, 0x3a, 0xa1, 0xf3, 0x54, 0x01, 0x15, 0xaf, 0x1f, 0x9d, 0xd3, - 0x3f, 0x49, 0x20, 0xd2, 0xc5, 0x3d, 0x03, 0xb9, 0x77, 0x95, 0x0d, 0x6b, 0x75, 0x27, 0x98, 0x74, - 0xc6, 0xce, 0x30, 0xc0, 0x28, 0x97, 0xa7, 0x88, 0x02, 0x76, 0x51, 0x64, 0x70, 0x46, 0x95, 0xe9, - 0x24, 0x24, 0x65, 0x0a, 0xd9, 0x38, 0x3b, 0x2a, 0x2f, 0xed, 0x79, 0x43, 0x67, 0xe0, 0x7d, 0x5d, - 0xf8, 0x98, 0x32, 0x79, 0xe9, 0x50, 0x00, 0xcd, 0xa8, 0x5c, 0xfb, 0x7c, 0x6a, 0xdc, 0x58, 0x2d, - 0x4b, 0xb0, 0xc4, 0x23, 0x10, 0x30, 0x8f, 0xfc, 0xb6, 0xd1, 0xdc, 0xad, 0x35, 0xf7, 0x55, 0x85, - 0xac, 0x01, 0xb4, 0xcd, 0x56, 0xc5, 0xb0, 0x2c, 0xfa, 0x3b, 0x47, 0x7f, 0x73, 0x77, 0xfd, 0xbd, - 0x6e, 0x5d, 0xcd, 0x4b, 0x1e, 0xfb, 0x05, 0xed, 0xc7, 0x0a, 0x5c, 0xc8, 0x1e, 0x4a, 0xd2, 0x01, - 0x8c, 0xd9, 0xc0, 0xdf, 0xd2, 0x3f, 0x36, 0x77, 0xdc, 0x33, 0xc1, 0xc9, 0xd8, 0x0f, 0x13, 0x16, - 0x53, 0x20, 0x27, 0xde, 0xbe, 0x98, 0x93, 0xa2, 0xd7, 0x37, 0x73, 0x5e, 0x5f, 0xab, 0xc0, 0xd6, - 0x2c, 0x1e, 0xf1, 0xa6, 0xae, 0x43, 0x49, 0x6f, 0xb7, 0xeb, 0xb5, 0x8a, 0xde, 0xa9, 0xb5, 0x9a, - 0xaa, 0x42, 0x96, 0x61, 0x61, 0xdf, 0x6c, 0x75, 0xdb, 0x6a, 0x4e, 0xfb, 0xae, 0x02, 0xab, 0xb5, - 0xc8, 0xea, 0xec, 0xfd, 0x2e, 0xbe, 0x8f, 0xc7, 0x16, 0xdf, 0x56, 0x18, 0xdd, 0x24, 0xfc, 0xc0, - 0xa9, 0x56, 0xde, 0x7b, 0x39, 0xd8, 0x48, 0xd1, 0x10, 0x0b, 0x96, 0xf4, 0x7b, 0x56, 0xab, 0xb6, - 0x5b, 0xe1, 0x35, 0xbb, 0x12, 0x99, 0x4b, 0x61, 0xbe, 0xab, 0xd4, 0x57, 0x98, 0x47, 0xf0, 0xa3, - 0xc0, 0xf6, 0xbd, 0xbe, 0x94, 0xfc, 0xb6, 0x7a, 0xc6, 0x14, 0x9c, 0xf0, 0x24, 0xfb, 0xfa, 0x74, - 0xec, 0x22, 0xdb, 0x5c, 0x4c, 0xaf, 0x1b, 0xc2, 0xd3, 0x8c, 0xd1, 0x7f, 0xc3, 0xa1, 0xe5, 0x69, - 0xd6, 0x11, 0x3f, 0xd2, 0x84, 0xc5, 0x7d, 0x6f, 0x52, 0x9d, 0x3e, 0xe4, 0xeb, 0xf7, 0x72, 0x94, - 0xfd, 0xa8, 0x3a, 0x7d, 0x98, 0x66, 0x8b, 0x2a, 0x4b, 0x16, 0xbd, 0x27, 0xc6, 0x92, 0x73, 0x49, - 0x3a, 0x31, 0x16, 0x9e, 0xca, 0x89, 0x71, 0x67, 0x15, 0x4a, 0xfc, 0x0e, 0x85, 0xd7, 0x93, 0x1f, - 0x2a, 0xb0, 0x35, 0xab, 0xe7, 0xe8, 0xb5, 0x2c, 0x1e, 0xac, 0xe0, 0x42, 0x98, 0x1e, 0x23, 0x1e, - 0xa5, 0x40, 0xa0, 0x91, 0xb7, 0xa0, 0x54, 0x0b, 0x82, 0xa9, 0x3b, 0xb6, 0x5e, 0xe9, 0x9a, 0x35, - 0x3e, 0x5d, 0x9f, 0xfd, 0xe7, 0x77, 0xaf, 0x6c, 0xa2, 0xcf, 0xc7, 0xd8, 0x0e, 0x5e, 0xb1, 0xa7, - 0x63, 0x2f, 0x96, 0x4a, 0x40, 0xa6, 0xa0, 0x52, 0xb4, 0x33, 0xed, 0x7b, 0xae, 0xb8, 0x43, 0x08, - 0x87, 0x6e, 0x0e, 0x93, 0xcf, 0x34, 0x01, 0xd3, 0xbe, 0xa5, 0xc0, 0xf6, 0xec, 0x61, 0xa2, 0xe7, - 0x64, 0x87, 0x99, 0x54, 0x09, 0x97, 0x6a, 0x3c, 0x27, 0x43, 0xbb, 0x2b, 0x99, 0xa7, 0x40, 0xa4, - 0x44, 0x61, 0x6a, 0xfc, 0x5c, 0x2a, 0x1f, 0x76, 0x9c, 0x48, 0x20, 0x6a, 0xf7, 0x61, 0x73, 0xc6, - 0xa0, 0x92, 0x4f, 0x65, 0x26, 0xdd, 0x41, 0x37, 0x25, 0x39, 0xe9, 0x4e, 0x2c, 0x7b, 0x9b, 0x04, - 0xd7, 0xfe, 0x25, 0x07, 0x17, 0xe8, 0xea, 0x1a, 0xb8, 0x41, 0xa0, 0x47, 0xf9, 0x69, 0xe9, 0xae, - 0xf8, 0x1a, 0x2c, 0x1e, 0x3f, 0x9d, 0xaa, 0x98, 0xa1, 0x13, 0x02, 0x78, 0x62, 0x09, 0xe7, 0x18, - 0xfa, 0x3f, 0xb9, 0x0a, 0x72, 0x72, 0xf3, 0x3c, 0x86, 0x37, 0xcd, 0x6d, 0x29, 0xe6, 0xf2, 0x28, - 0xcc, 0x43, 0xfc, 0x3a, 0x2c, 0xa0, 0x3e, 0x85, 0x9f, 0x1d, 0x42, 0xe6, 0xcf, 0xae, 0x1d, 0x6a, - 0x5b, 0x4c, 0x46, 0x40, 0x3e, 0x02, 0x10, 0x65, 0x86, 0xe0, 0x87, 0x83, 0xd0, 0x33, 0x84, 0xc9, - 0x21, 0xcc, 0xe5, 0x93, 0x43, 0x87, 0xa7, 0x5b, 0x28, 0xc3, 0x86, 0xe8, 0xf1, 0x91, 0x88, 0x8a, - 0xc8, 0x5f, 0x31, 0xd7, 0x59, 0x41, 0x6d, 0x24, 0x22, 0x23, 0x5e, 0x4b, 0x25, 0x68, 0xc6, 0xe0, - 0xc8, 0x89, 0x2c, 0xcc, 0xd7, 0x52, 0x59, 0x98, 0x8b, 0x0c, 0x4b, 0x4e, 0xb5, 0xac, 0xfd, 0x43, - 0x0e, 0x96, 0xef, 0x51, 0xa9, 0x0c, 0x75, 0x0d, 0xf3, 0x75, 0x17, 0xb7, 0xa1, 0x54, 0xf7, 0x1d, - 0xfe, 0x5c, 0xc4, 0x7d, 0x4a, 0x98, 0x4f, 0xf7, 0xc0, 0x77, 0xc4, 0xcb, 0x53, 0x60, 0xca, 0x48, - 0x4f, 0xf0, 0x47, 0xbf, 0x03, 0x8b, 0xec, 0xf9, 0x8e, 0xab, 0xd1, 0x84, 0x5c, 0x1e, 0xd6, 0xe8, - 0x65, 0x56, 0x2c, 0xbd, 0x70, 0xb0, 0x27, 0x40, 0x59, 0x48, 0xe4, 0x31, 0x5e, 0x25, 0xcd, 0xca, - 0xc2, 0xe9, 0x34, 0x2b, 0x52, 0x2c, 0xbb, 0xc5, 0xd3, 0xc4, 0xb2, 0xdb, 0x7e, 0x03, 0x4a, 0x52, - 0x7d, 0x9e, 0x4a, 0x4c, 0xff, 0x66, 0x0e, 0x56, 0xb1, 0x55, 0xa1, 0x2d, 0xcf, 0xaf, 0xa6, 0x9e, - 0xe8, 0xe3, 0x31, 0x3d, 0xd1, 0x96, 0x3c, 0x5e, 0xac, 0x65, 0x73, 0x14, 0x44, 0x77, 0x60, 0x23, - 0x85, 0x48, 0x5e, 0x85, 0x05, 0x5a, 0x7d, 0x71, 0xaf, 0x56, 0x93, 0x33, 0x20, 0x8a, 0x7b, 0x4c, - 0x1b, 0x1e, 0x98, 0x0c, 0x5b, 0xfb, 0x77, 0x05, 0x56, 0x78, 0xda, 0x91, 0xe1, 0xa1, 0xff, 0xc4, - 0xee, 0xbc, 0x9e, 0xec, 0x4e, 0x16, 0x5d, 0x85, 0x77, 0xe7, 0x7f, 0x75, 0x27, 0xbe, 0x11, 0xeb, - 0xc4, 0xcd, 0x30, 0x0a, 0xa2, 0x68, 0xce, 0x9c, 0x3e, 0xfc, 0x01, 0xc6, 0x05, 0x8e, 0x23, 0x92, - 0x2f, 0xc2, 0x72, 0xd3, 0x7d, 0x14, 0xbb, 0x9e, 0x5e, 0x9f, 0xc1, 0xf4, 0xe5, 0x10, 0x91, 0xad, - 0x29, 0x3c, 0xd9, 0x87, 0xee, 0x23, 0x3b, 0xf5, 0x72, 0x18, 0xb1, 0xa4, 0x37, 0xd4, 0x38, 0xd9, - 0xd3, 0x4c, 0x7d, 0xee, 0xe0, 0x8a, 0x01, 0x83, 0xbe, 0x9d, 0x07, 0x88, 0x7c, 0x03, 0xe9, 0x02, - 0x8c, 0x19, 0x4d, 0x08, 0xcd, 0x3e, 0x82, 0xe4, 0x39, 0x2e, 0x6c, 0x29, 0xae, 0x73, 0x0d, 0x74, - 0x6e, 0x76, 0x94, 0x4a, 0xd4, 0x45, 0x57, 0xb8, 0x33, 0x5a, 0xdf, 0x1d, 0x38, 0x6c, 0x6f, 0xcf, - 0xef, 0x5c, 0xc3, 0xa0, 0xc4, 0x21, 0x74, 0x46, 0xba, 0x69, 0x74, 0x59, 0xdb, 0xa5, 0x08, 0x29, - 0x7f, 0xdb, 0xc2, 0xd3, 0xf9, 0xdb, 0xb6, 0x61, 0xd9, 0x1b, 0xbe, 0xed, 0x0e, 0x27, 0xfe, 0xf8, - 0x31, 0xaa, 0xdd, 0x23, 0x7d, 0x1e, 0xed, 0x82, 0x9a, 0x28, 0x63, 0xe3, 0x80, 0x67, 0x6e, 0x88, - 0x2f, 0x0f, 0x43, 0x08, 0x0c, 0xfd, 0x85, 0x17, 0xd4, 0xc5, 0x3b, 0x85, 0xe2, 0xa2, 0xba, 0x74, - 0xa7, 0x50, 0x2c, 0xaa, 0xcb, 0x77, 0x0a, 0xc5, 0x65, 0x15, 0x4c, 0xe9, 0xcd, 0x2c, 0x7c, 0x13, - 0x93, 0x9e, 0xb1, 0xe2, 0x4f, 0x54, 0xda, 0xcf, 0x73, 0x40, 0xd2, 0xd5, 0x20, 0x1f, 0x87, 0x12, - 0xdb, 0x60, 0xed, 0x71, 0xf0, 0x55, 0xee, 0x6e, 0xc0, 0xc2, 0x2e, 0x49, 0x60, 0x39, 0xec, 0x12, - 0x03, 0x9b, 0xc1, 0x57, 0x07, 0xe4, 0x0b, 0x70, 0x16, 0xbb, 0x77, 0xe4, 0x8e, 0x3d, 0xbf, 0x6f, - 0x63, 0x8c, 0x5c, 0x67, 0xc0, 0x53, 0x43, 0xbe, 0x84, 0x39, 0x8c, 0xd3, 0xc5, 0x33, 0x86, 0x01, - 0x5d, 0x00, 0xdb, 0x88, 0xd9, 0x66, 0x88, 0xa4, 0x03, 0xaa, 0x4c, 0x7f, 0x38, 0x1d, 0x0c, 0xf8, - 0xc8, 0x96, 0xe9, 0x8d, 0x3e, 0x59, 0x36, 0x83, 0xf1, 0x5a, 0xc4, 0x78, 0x6f, 0x3a, 0x18, 0x90, - 0xd7, 0x00, 0xfc, 0xa1, 0x7d, 0xe2, 0x05, 0x01, 0x7b, 0xcc, 0x09, 0xbd, 0x95, 0x23, 0xa8, 0x3c, - 0x18, 0xfe, 0xb0, 0xc1, 0x80, 0xe4, 0x7f, 0x01, 0x46, 0x6b, 0xc0, 0x30, 0x26, 0xcc, 0x1a, 0x89, - 0x67, 0x6f, 0x11, 0xc0, 0xb8, 0x73, 0xf4, 0x91, 0x6b, 0x79, 0x5f, 0x17, 0xae, 0x1e, 0x9f, 0x83, - 0x0d, 0x6e, 0x3c, 0x7c, 0xcf, 0x9b, 0x1c, 0xf3, 0xab, 0xc4, 0xfb, 0xb9, 0x87, 0x48, 0x77, 0x89, - 0xbf, 0x29, 0x00, 0xe8, 0xf7, 0x2c, 0x11, 0x21, 0xec, 0x26, 0x2c, 0xd0, 0x0b, 0x92, 0x50, 0xb4, - 0xa0, 0x9a, 0x1a, 0xf9, 0xca, 0x6a, 0x6a, 0xc4, 0xa0, 0xab, 0xd1, 0x44, 0xa3, 0x7a, 0xa1, 0x64, - 0xc1, 0xd5, 0xc8, 0xec, 0xec, 0x63, 0x11, 0x9a, 0x39, 0x16, 0xa9, 0x03, 0x44, 0x31, 0xbb, 0xb8, - 0xc8, 0xbf, 0x11, 0x05, 0xbf, 0xe1, 0x05, 0x3c, 0x4b, 0x44, 0x14, 0xf7, 0x4b, 0x9e, 0x3e, 0x11, - 0x1a, 0xb9, 0x0b, 0x85, 0x8e, 0x13, 0xfa, 0xe2, 0xce, 0x88, 0x64, 0xf6, 0x1c, 0x4f, 0xdd, 0x19, - 0x45, 0x33, 0x5b, 0x9b, 0x38, 0xb1, 0x0c, 0xc7, 0xc8, 0x84, 0x18, 0xb0, 0xc8, 0xd3, 0xb2, 0xcf, - 0x88, 0x80, 0xc9, 0xb3, 0xb2, 0xf3, 0xb8, 0xd7, 0x08, 0x94, 0x65, 0x0a, 0x9e, 0x80, 0xfd, 0x36, - 0xe4, 0x2d, 0xab, 0xc1, 0xe3, 0x77, 0xac, 0x46, 0xd7, 0x2f, 0xcb, 0x6a, 0xb0, 0x77, 0xdf, 0x20, - 0x38, 0x91, 0xc8, 0x28, 0x32, 0xf9, 0x04, 0x94, 0x24, 0xa1, 0x98, 0x47, 0xbe, 0xc1, 0x3e, 0x90, - 0xbc, 0x9d, 0xe4, 0x4d, 0x43, 0xc2, 0x26, 0x75, 0x50, 0xef, 0x4e, 0x1f, 0xba, 0xfa, 0x68, 0x84, - 0x6e, 0x90, 0x6f, 0xbb, 0x63, 0x26, 0xb6, 0x15, 0xa3, 0x90, 0xd1, 0xe8, 0x23, 0xd1, 0x17, 0xa5, - 0xb2, 0xb2, 0x29, 0x49, 0x49, 0xda, 0xb0, 0x61, 0xb9, 0x93, 0xe9, 0x88, 0xd9, 0xd7, 0xec, 0xf9, - 0x63, 0x7a, 0xbf, 0x61, 0x71, 0x72, 0x30, 0xba, 0x6e, 0x40, 0x0b, 0x85, 0x51, 0xd3, 0xa1, 0x3f, - 0x4e, 0xdc, 0x75, 0xd2, 0xc4, 0x9a, 0x2b, 0x0f, 0x39, 0x3d, 0x55, 0xe3, 0xb7, 0x26, 0x3c, 0x55, - 0xc5, 0xad, 0x29, 0xba, 0x2b, 0x7d, 0x24, 0x23, 0x96, 0x1b, 0xbe, 0x0c, 0x4a, 0xb1, 0xdc, 0x62, - 0x11, 0xdc, 0xbe, 0x5f, 0x90, 0xc2, 0x89, 0xf2, 0xb1, 0x78, 0x13, 0xe0, 0x8e, 0xef, 0x0d, 0x1b, - 0xee, 0xe4, 0xd8, 0xef, 0x4b, 0x21, 0xe5, 0x4a, 0x5f, 0xf6, 0xbd, 0xa1, 0x7d, 0x82, 0xe0, 0x9f, - 0xbf, 0x7b, 0x45, 0x42, 0x32, 0xa5, 0xff, 0xc9, 0x87, 0x61, 0x99, 0xfe, 0xea, 0x44, 0x56, 0x42, - 0x4c, 0x27, 0x8b, 0xd4, 0x2c, 0xe9, 0x46, 0x84, 0x40, 0xde, 0xc0, 0x34, 0x33, 0xde, 0x68, 0x22, - 0x09, 0xaf, 0x22, 0xa7, 0x8c, 0x37, 0x9a, 0x24, 0x23, 0x44, 0x4b, 0xc8, 0xa4, 0x1a, 0x56, 0x5d, - 0x64, 0x86, 0xe2, 0xd9, 0x6c, 0x50, 0xf1, 0xc8, 0xe7, 0x9a, 0x2d, 0x42, 0xd3, 0xca, 0x29, 0x7f, - 0x13, 0x64, 0x58, 0x09, 0xab, 0xba, 0xcb, 0x5e, 0x8a, 0xb8, 0x50, 0xcb, 0x2a, 0x11, 0x1c, 0xf7, - 0xed, 0x1e, 0x82, 0x63, 0x95, 0x08, 0x91, 0xc9, 0x0e, 0xac, 0x33, 0x19, 0x3f, 0xcc, 0x30, 0xc9, - 0x45, 0x5c, 0xdc, 0xdb, 0xa2, 0x14, 0x94, 0xf2, 0xe7, 0x13, 0x04, 0x64, 0x0f, 0x16, 0xf0, 0xae, - 0xc9, 0x5d, 0x03, 0x2e, 0xca, 0x6a, 0x82, 0xe4, 0x3a, 0xc2, 0x7d, 0x05, 0x15, 0x04, 0xf2, 0xbe, - 0x82, 0xa8, 0xe4, 0xb3, 0x00, 0xc6, 0x70, 0xec, 0x0f, 0x06, 0x18, 0x3c, 0xb9, 0x88, 0x57, 0xa9, - 0x67, 0xe3, 0xeb, 0x11, 0xb9, 0x44, 0x48, 0x3c, 0xd0, 0x1f, 0xfe, 0xb6, 0x13, 0x21, 0x96, 0x25, - 0x5e, 0x5a, 0x0d, 0x16, 0xd9, 0x62, 0xc4, 0x40, 0xe4, 0x3c, 0xb5, 0x8a, 0x14, 0xc6, 0x9a, 0x05, - 0x22, 0xe7, 0xf0, 0x74, 0x20, 0x72, 0x89, 0x40, 0xbb, 0x0b, 0xe7, 0xb2, 0x1a, 0x16, 0xbb, 0x1d, - 0x2b, 0xa7, 0xbd, 0x1d, 0x7f, 0x2f, 0x0f, 0x2b, 0xc8, 0x4d, 0xec, 0xc2, 0x3a, 0xac, 0x5a, 0xd3, - 0x87, 0x61, 0x94, 0x2e, 0xb1, 0x1b, 0x63, 0xfd, 0x02, 0xb9, 0x40, 0x7e, 0xc3, 0x8b, 0x51, 0x10, - 0x03, 0xd6, 0xc4, 0x49, 0xb0, 0x2f, 0x3c, 0x07, 0xc2, 0x18, 0xe0, 0x22, 0xd2, 0x64, 0x3a, 0xc3, - 0x6e, 0x82, 0x28, 0x3a, 0x0f, 0xf2, 0x4f, 0x73, 0x1e, 0x14, 0x4e, 0x75, 0x1e, 0x3c, 0x80, 0x15, - 0xf1, 0x35, 0xdc, 0xc9, 0x17, 0xde, 0xdf, 0x4e, 0x1e, 0x63, 0x46, 0xea, 0xe1, 0x8e, 0xbe, 0x38, - 0x77, 0x47, 0xc7, 0x87, 0x51, 0xb1, 0xca, 0x46, 0x08, 0x4b, 0x6f, 0xec, 0x98, 0x82, 0x72, 0xbf, - 0xd2, 0xfe, 0x05, 0x4e, 0xc9, 0x57, 0x61, 0xb9, 0xee, 0x8b, 0x37, 0x31, 0xe9, 0x31, 0x62, 0x20, - 0x80, 0xb2, 0xb8, 0x10, 0x62, 0x86, 0xa7, 0x5b, 0xfe, 0x83, 0x38, 0xdd, 0xde, 0x00, 0xe0, 0x2e, - 0x29, 0x51, 0xea, 0x38, 0x5c, 0x32, 0x22, 0x42, 0x49, 0xfc, 0x4d, 0x44, 0x42, 0xa6, 0xbb, 0x13, - 0x37, 0xb7, 0xd1, 0x7b, 0x3d, 0x7f, 0x3a, 0x9c, 0xc4, 0x72, 0x2d, 0x0b, 0x0f, 0x56, 0x87, 0x97, - 0xc9, 0xdb, 0x43, 0x82, 0xec, 0x83, 0x1d, 0x10, 0xf2, 0x99, 0xd0, 0xf8, 0x71, 0x69, 0x5e, 0x0f, - 0x69, 0xa9, 0x1e, 0x9a, 0x69, 0xf2, 0xa8, 0xfd, 0x58, 0x91, 0x13, 0x30, 0xfc, 0x02, 0x43, 0xfd, - 0x3a, 0x40, 0x68, 0x94, 0x20, 0xc6, 0x9a, 0xdd, 0x97, 0x42, 0xa8, 0xdc, 0xcb, 0x11, 0xae, 0xd4, - 0x9a, 0xfc, 0x07, 0xd5, 0x9a, 0x0e, 0x94, 0x5a, 0x5f, 0x99, 0x38, 0x91, 0x15, 0x0b, 0x58, 0xa1, - 0x24, 0x8b, 0x3b, 0x53, 0x7e, 0xe7, 0x05, 0x3c, 0x1b, 0x22, 0x39, 0x78, 0x86, 0x08, 0x2c, 0x11, - 0x6a, 0x7f, 0xa6, 0xc0, 0xba, 0xec, 0x76, 0xff, 0x78, 0xd8, 0x23, 0x9f, 0x62, 0xf1, 0x60, 0x95, - 0xd8, 0x95, 0x45, 0x42, 0xa2, 0x5b, 0xee, 0xe3, 0x61, 0x8f, 0x09, 0x40, 0xce, 0x23, 0xb9, 0xb2, - 0x94, 0x90, 0x3c, 0x84, 0x95, 0xb6, 0x3f, 0x18, 0x50, 0xb1, 0x66, 0xfc, 0x36, 0xbf, 0x00, 0x50, - 0x46, 0xc9, 0xa7, 0x11, 0x51, 0xa1, 0x9d, 0xe7, 0xf9, 0x3d, 0x77, 0x73, 0x44, 0xf7, 0x7b, 0x8f, - 0xd3, 0x45, 0x6c, 0xdf, 0x41, 0x3f, 0x39, 0x99, 0xa7, 0xf6, 0x53, 0x05, 0x48, 0xba, 0x4a, 0xf2, - 0x96, 0xa5, 0xfc, 0x37, 0x88, 0xb0, 0x09, 0xd1, 0xaf, 0xf0, 0x34, 0xa2, 0x5f, 0xf9, 0xb7, 0x15, - 0x58, 0xaf, 0xe9, 0x0d, 0x9e, 0x92, 0x81, 0xbd, 0xe0, 0x5c, 0x85, 0x67, 0x6b, 0x7a, 0xc3, 0x6e, - 0xb7, 0xea, 0xb5, 0xca, 0x7d, 0x3b, 0x33, 0xd2, 0xf2, 0xb3, 0xf0, 0x4c, 0x1a, 0x25, 0x7a, 0xe9, - 0xb9, 0x04, 0x5b, 0xe9, 0x62, 0x11, 0x8d, 0x39, 0x9b, 0x58, 0x04, 0x6e, 0xce, 0x97, 0xdf, 0x82, - 0x75, 0x11, 0x79, 0xb8, 0x53, 0xb7, 0x30, 0xb7, 0xc1, 0x3a, 0x94, 0x0e, 0x0c, 0xb3, 0xb6, 0x77, - 0xdf, 0xde, 0xeb, 0xd6, 0xeb, 0xea, 0x19, 0xb2, 0x0a, 0xcb, 0x1c, 0x50, 0xd1, 0x55, 0x85, 0xac, - 0x40, 0xb1, 0xd6, 0xb4, 0x8c, 0x4a, 0xd7, 0x34, 0xd4, 0x5c, 0xf9, 0x2d, 0x58, 0x6b, 0x8f, 0xbd, - 0xb7, 0x9d, 0x89, 0x7b, 0xd7, 0x7d, 0x8c, 0x0f, 0x35, 0x4b, 0x90, 0x37, 0xf5, 0x7b, 0xea, 0x19, - 0x02, 0xb0, 0xd8, 0xbe, 0x5b, 0xb1, 0x6e, 0xdd, 0x52, 0x15, 0x52, 0x82, 0xa5, 0xfd, 0x4a, 0xdb, - 0xbe, 0xdb, 0xb0, 0xd4, 0x1c, 0xfd, 0xa1, 0xdf, 0xb3, 0xf0, 0x47, 0xbe, 0xfc, 0x51, 0xd8, 0x40, - 0x81, 0xa4, 0xee, 0x05, 0x13, 0x77, 0xe8, 0x8e, 0xb1, 0x0e, 0x2b, 0x50, 0xb4, 0x5c, 0xba, 0x93, - 0x4c, 0x5c, 0x56, 0x81, 0xc6, 0x74, 0x30, 0xf1, 0x46, 0x03, 0xf7, 0x6b, 0xaa, 0x52, 0x7e, 0x03, - 0xd6, 0x4d, 0x7f, 0x3a, 0xf1, 0x86, 0x47, 0xd6, 0x84, 0x62, 0x1c, 0x3d, 0x26, 0xe7, 0x61, 0xa3, - 0xdb, 0xd4, 0x1b, 0x3b, 0xb5, 0xfd, 0x6e, 0xab, 0x6b, 0xd9, 0x0d, 0xbd, 0x53, 0xa9, 0xb2, 0x67, - 0xa2, 0x46, 0xcb, 0xea, 0xd8, 0xa6, 0x51, 0x31, 0x9a, 0x1d, 0x55, 0x29, 0x7f, 0x07, 0x75, 0x2b, - 0x3d, 0x7f, 0xd8, 0xdf, 0x73, 0x7a, 0x13, 0x7f, 0x8c, 0x15, 0xd6, 0xe0, 0xb2, 0x65, 0x54, 0x5a, - 0xcd, 0x5d, 0x7b, 0x4f, 0xaf, 0x74, 0x5a, 0x66, 0x56, 0xa8, 0xef, 0x6d, 0xb8, 0x90, 0x81, 0xd3, - 0xea, 0xb4, 0x55, 0x85, 0x5c, 0x81, 0x8b, 0x19, 0x65, 0xf7, 0x8c, 0x1d, 0xbd, 0xdb, 0xa9, 0x36, - 0xd5, 0xdc, 0x0c, 0x62, 0xcb, 0x6a, 0xa9, 0xf9, 0xf2, 0xff, 0x57, 0x60, 0xad, 0x1b, 0x70, 0x93, - 0xf3, 0x2e, 0x7a, 0x9b, 0x3e, 0x07, 0x97, 0xba, 0x96, 0x61, 0xda, 0x9d, 0xd6, 0x5d, 0xa3, 0x69, - 0x77, 0x2d, 0x7d, 0x3f, 0x59, 0x9b, 0x2b, 0x70, 0x51, 0xc2, 0x30, 0x8d, 0x4a, 0xeb, 0xc0, 0x30, - 0xed, 0xb6, 0x6e, 0x59, 0xf7, 0x5a, 0xe6, 0xae, 0xaa, 0xd0, 0x2f, 0x66, 0x20, 0x34, 0xf6, 0x74, - 0x56, 0x9b, 0x58, 0x59, 0xd3, 0xb8, 0xa7, 0xd7, 0xed, 0x9d, 0x56, 0x47, 0xcd, 0x97, 0x1b, 0xf4, - 0x7c, 0xc7, 0x80, 0xbb, 0xcc, 0xb2, 0xb0, 0x08, 0x85, 0x66, 0xab, 0x69, 0x24, 0x1f, 0x17, 0x57, - 0xa0, 0xa8, 0xb7, 0xdb, 0x66, 0xeb, 0x00, 0xa7, 0x18, 0xc0, 0xe2, 0xae, 0xd1, 0xa4, 0x35, 0xcb, - 0xd3, 0x92, 0xb6, 0xd9, 0x6a, 0xb4, 0x3a, 0xc6, 0xae, 0x5a, 0x28, 0x9b, 0x62, 0x09, 0x0b, 0xa6, - 0x3d, 0x9f, 0xbd, 0xe4, 0xed, 0x1a, 0x7b, 0x7a, 0xb7, 0xde, 0xe1, 0x43, 0x74, 0xdf, 0x36, 0x8d, - 0xcf, 0x74, 0x0d, 0xab, 0x63, 0xa9, 0x0a, 0x51, 0x61, 0xa5, 0x69, 0x18, 0xbb, 0x96, 0x6d, 0x1a, - 0x07, 0x35, 0xe3, 0x9e, 0x9a, 0xa3, 0x3c, 0xd9, 0xff, 0xf4, 0x0b, 0xe5, 0xef, 0x2b, 0x40, 0x58, - 0xb0, 0x62, 0x91, 0x01, 0x07, 0x67, 0xcc, 0x65, 0xd8, 0xae, 0xd2, 0xa1, 0xc6, 0xa6, 0x35, 0x5a, - 0xbb, 0xc9, 0x2e, 0xbb, 0x00, 0x24, 0x51, 0xde, 0xda, 0xdb, 0x53, 0x15, 0x72, 0x11, 0xce, 0x26, - 0xe0, 0xbb, 0x66, 0xab, 0xad, 0xe6, 0xb6, 0x73, 0x45, 0x85, 0x6c, 0xa6, 0x0a, 0xef, 0x1a, 0x46, - 0x5b, 0xcd, 0xd3, 0x21, 0x4a, 0x14, 0x88, 0x25, 0xc1, 0xc8, 0x0b, 0xe5, 0x6f, 0x29, 0x70, 0x81, - 0x55, 0x53, 0xac, 0xaf, 0xb0, 0xaa, 0x97, 0x60, 0x8b, 0x87, 0x60, 0xcf, 0xaa, 0xe8, 0x39, 0x50, - 0x63, 0xa5, 0xac, 0x9a, 0xe7, 0x61, 0x23, 0x06, 0xc5, 0x7a, 0xe4, 0xe8, 0xee, 0x11, 0x03, 0xef, - 0x18, 0x56, 0xc7, 0x36, 0xf6, 0xf6, 0x5a, 0x66, 0x87, 0x55, 0x24, 0x5f, 0xd6, 0x60, 0xa3, 0xe2, - 0x8e, 0x27, 0xf4, 0xea, 0x35, 0x0c, 0x3c, 0x7f, 0x88, 0x55, 0x58, 0x85, 0x65, 0xe3, 0xb3, 0x1d, - 0xa3, 0x69, 0xd5, 0x5a, 0x4d, 0xf5, 0x4c, 0xf9, 0x52, 0x02, 0x47, 0xac, 0x63, 0xcb, 0xaa, 0xaa, - 0x67, 0xca, 0x0e, 0xac, 0x0a, 0xc3, 0x6b, 0x36, 0x2b, 0x2e, 0xc3, 0xb6, 0x98, 0x6b, 0xb8, 0xa3, - 0x24, 0x9b, 0xb0, 0x05, 0xe7, 0xd2, 0xe5, 0x46, 0x47, 0x55, 0xe8, 0x28, 0x24, 0x4a, 0x28, 0x3c, - 0x57, 0xfe, 0xbf, 0x0a, 0xac, 0x86, 0x8f, 0x26, 0xa8, 0xa6, 0xbd, 0x02, 0x17, 0x1b, 0x7b, 0xba, - 0xbd, 0x6b, 0x1c, 0xd4, 0x2a, 0x86, 0x7d, 0xb7, 0xd6, 0xdc, 0x4d, 0x7c, 0xe4, 0x19, 0x38, 0x9f, - 0x81, 0x80, 0x5f, 0xd9, 0x82, 0x73, 0xc9, 0xa2, 0x0e, 0x5d, 0xaa, 0x39, 0xda, 0xf5, 0xc9, 0x92, - 0x70, 0x9d, 0xe6, 0xcb, 0x07, 0xb0, 0x66, 0xe9, 0x8d, 0xfa, 0x9e, 0x3f, 0xee, 0xb9, 0xfa, 0x74, - 0x72, 0x3c, 0x24, 0x17, 0x61, 0x73, 0xaf, 0x65, 0x56, 0x0c, 0x1b, 0x51, 0x12, 0x35, 0x38, 0x0b, - 0xeb, 0x72, 0xe1, 0x7d, 0x83, 0x4e, 0x5f, 0x02, 0x6b, 0x32, 0xb0, 0xd9, 0x52, 0x73, 0xe5, 0xcf, - 0xc3, 0x4a, 0x2c, 0x11, 0xde, 0x26, 0x9c, 0x95, 0x7f, 0xb7, 0xdd, 0x61, 0xdf, 0x1b, 0x1e, 0xa9, - 0x67, 0x92, 0x05, 0xe6, 0x74, 0x38, 0xa4, 0x05, 0xb8, 0x9e, 0xe5, 0x82, 0x8e, 0x3b, 0x3e, 0xf1, - 0x86, 0xce, 0xc4, 0xed, 0xab, 0xb9, 0xf2, 0xcb, 0xb0, 0x1a, 0x0b, 0xbf, 0x4d, 0x07, 0xae, 0xde, - 0xe2, 0x1b, 0x70, 0xc3, 0xd8, 0xad, 0x75, 0x1b, 0xea, 0x02, 0x5d, 0xc9, 0xd5, 0xda, 0x7e, 0x55, - 0x85, 0xf2, 0x77, 0x15, 0x7a, 0xcf, 0xc0, 0xa4, 0x3a, 0x8d, 0x3d, 0x5d, 0x0c, 0x35, 0x9d, 0x66, - 0x2c, 0xa8, 0xbf, 0x61, 0x59, 0xec, 0x4d, 0xfd, 0x12, 0x6c, 0xf1, 0x1f, 0xb6, 0xde, 0xdc, 0xb5, - 0xab, 0xba, 0xb9, 0x7b, 0x4f, 0x37, 0xe9, 0xdc, 0xbb, 0xaf, 0xe6, 0x70, 0x41, 0x49, 0x10, 0xbb, - 0xd3, 0xea, 0x56, 0xaa, 0x6a, 0x9e, 0xce, 0xdf, 0x18, 0xbc, 0x5d, 0x6b, 0xaa, 0x05, 0x5c, 0x9e, - 0x29, 0x6c, 0x64, 0x4b, 0xcb, 0x17, 0xca, 0xef, 0x29, 0xb0, 0x69, 0x79, 0x47, 0x43, 0x67, 0x32, - 0x1d, 0xbb, 0xfa, 0xe0, 0xc8, 0x1f, 0x7b, 0x93, 0xe3, 0x13, 0x6b, 0xea, 0x4d, 0x5c, 0x72, 0x13, - 0x5e, 0xb0, 0x6a, 0xfb, 0x4d, 0xbd, 0x43, 0x97, 0x97, 0x5e, 0xdf, 0x6f, 0x99, 0xb5, 0x4e, 0xb5, - 0x61, 0x5b, 0xdd, 0x5a, 0x6a, 0xe6, 0x5d, 0x83, 0xe7, 0x66, 0xa3, 0xd6, 0x8d, 0x7d, 0xbd, 0x72, - 0x5f, 0x55, 0xe6, 0x33, 0xdc, 0xd1, 0xeb, 0x7a, 0xb3, 0x62, 0xec, 0xda, 0x07, 0xb7, 0xd4, 0x1c, - 0x79, 0x01, 0xae, 0xce, 0x46, 0xdd, 0xab, 0xb5, 0x2d, 0x8a, 0x96, 0x9f, 0xff, 0xdd, 0xaa, 0xd5, - 0xa0, 0x58, 0x85, 0xf2, 0x1f, 0x2a, 0xb0, 0x35, 0x2b, 0x06, 0x13, 0xb9, 0x0e, 0x9a, 0xd1, 0xec, - 0x98, 0x7a, 0x6d, 0xd7, 0xae, 0x98, 0xc6, 0xae, 0xd1, 0xec, 0xd4, 0xf4, 0xba, 0x65, 0x5b, 0xad, - 0x2e, 0x9d, 0x4d, 0x91, 0xe9, 0xc3, 0xf3, 0x70, 0x65, 0x0e, 0x5e, 0xab, 0xb6, 0x5b, 0x51, 0x15, - 0x72, 0x0b, 0x5e, 0x9a, 0x83, 0x64, 0xdd, 0xb7, 0x3a, 0x46, 0x43, 0x2e, 0x51, 0x73, 0xe5, 0x0a, - 0x6c, 0xcf, 0x0e, 0xd2, 0x42, 0xb7, 0xe9, 0x78, 0x4f, 0x17, 0xa1, 0xb0, 0x4b, 0x4f, 0x86, 0x58, - 0xee, 0x87, 0xb2, 0x07, 0x6a, 0x32, 0xce, 0x42, 0xca, 0x46, 0xc5, 0xec, 0x36, 0x9b, 0xec, 0x18, - 0x59, 0x87, 0x52, 0xab, 0x53, 0x35, 0x4c, 0x9e, 0x3d, 0x03, 0xd3, 0x65, 0x74, 0x9b, 0x74, 0xe1, - 0xb4, 0xcc, 0xda, 0xe7, 0xf0, 0x3c, 0xd9, 0x82, 0x73, 0x56, 0x5d, 0xaf, 0xdc, 0xb5, 0x9b, 0xad, - 0x8e, 0x5d, 0x6b, 0xda, 0x95, 0xaa, 0xde, 0x6c, 0x1a, 0x75, 0x15, 0xb0, 0x33, 0x67, 0xf9, 0x56, - 0x92, 0x0f, 0xc3, 0x8d, 0xd6, 0xdd, 0x8e, 0x6e, 0xb7, 0xeb, 0xdd, 0xfd, 0x5a, 0xd3, 0xb6, 0xee, - 0x37, 0x2b, 0x42, 0xf6, 0xa9, 0xa4, 0xb7, 0xdc, 0x1b, 0x70, 0x6d, 0x2e, 0x76, 0x94, 0xe7, 0xe2, - 0x3a, 0x68, 0x73, 0x31, 0x79, 0x43, 0xca, 0x3f, 0x51, 0xe0, 0xe2, 0x9c, 0x37, 0x64, 0xf2, 0x12, - 0xdc, 0xac, 0x1a, 0xfa, 0x6e, 0xdd, 0xb0, 0x2c, 0xdc, 0x28, 0xe8, 0x30, 0x30, 0x5b, 0x96, 0xcc, - 0x0d, 0xf5, 0x26, 0xbc, 0x30, 0x1f, 0x3d, 0x3a, 0x9a, 0x6f, 0xc0, 0xb5, 0xf9, 0xa8, 0xfc, 0xa8, - 0xce, 0x91, 0x32, 0x5c, 0x9f, 0x8f, 0x19, 0x1e, 0xf1, 0xf9, 0xf2, 0x6f, 0x2a, 0x70, 0x21, 0x5b, - 0x91, 0x43, 0xeb, 0x56, 0x6b, 0x5a, 0x1d, 0xbd, 0x5e, 0xb7, 0xdb, 0xba, 0xa9, 0x37, 0x6c, 0xa3, - 0x69, 0xb6, 0xea, 0xf5, 0xac, 0xa3, 0xed, 0x1a, 0x3c, 0x37, 0x1b, 0xd5, 0xaa, 0x98, 0xb5, 0x36, - 0xdd, 0xbd, 0x35, 0xb8, 0x3c, 0x1b, 0xcb, 0xa8, 0x55, 0x0c, 0x35, 0xb7, 0xf3, 0xe6, 0x8f, 0xfe, - 0xfe, 0xf2, 0x99, 0x1f, 0xbd, 0x77, 0x59, 0xf9, 0xe9, 0x7b, 0x97, 0x95, 0xbf, 0x7b, 0xef, 0xb2, - 0xf2, 0xb9, 0x17, 0x4f, 0x97, 0x22, 0x0a, 0xe5, 0xfe, 0x87, 0x8b, 0x78, 0x43, 0x79, 0xe5, 0x3f, - 0x03, 0x00, 0x00, 0xff, 0xff, 0x09, 0xed, 0xed, 0x8d, 0x2e, 0xbf, 0x01, 0x00, + // 30542 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xbd, 0x6b, 0x70, 0x1c, 0x49, + 0x7a, 0x20, 0x36, 0xdd, 0x8d, 0x47, 0xe3, 0xc3, 0xab, 0x91, 0x00, 0x49, 0x10, 0xf3, 0x68, 0x4e, + 0xcd, 0x0c, 0x87, 0x9c, 0x9d, 0x21, 0x97, 0xe0, 0x0e, 0x77, 0x67, 0xe7, 0xb5, 0x8d, 0x6e, 0x90, + 0x68, 0x12, 0x00, 0x31, 0xd5, 0x00, 0xb1, 0xa3, 0x7d, 0xd4, 0x16, 0xba, 0x13, 0x40, 0x0d, 0xba, + 0xbb, 0x7a, 0xab, 0xaa, 0x09, 0x42, 0x7b, 0xb2, 0xde, 0x27, 0x2b, 0x64, 0x3d, 0x4f, 0x3a, 0xed, + 0x39, 0x74, 0xb2, 0x42, 0xbe, 0xf3, 0x29, 0xce, 0x21, 0xc5, 0x59, 0xb2, 0xec, 0xb3, 0x2f, 0x2c, + 0x4b, 0x17, 0x67, 0x59, 0x56, 0x5c, 0x9c, 0x14, 0xf6, 0xd9, 0x0e, 0xaf, 0xcf, 0x90, 0x65, 0xf9, + 0x87, 0x03, 0x11, 0x8e, 0x90, 0x7c, 0x11, 0x8e, 0xf0, 0x3a, 0x74, 0x77, 0x91, 0x5f, 0x66, 0x56, + 0x65, 0x56, 0x55, 0x37, 0x1a, 0x43, 0x8e, 0x4e, 0xdc, 0xd0, 0x1f, 0x12, 0xfd, 0xe5, 0xf7, 0x7d, + 0x59, 0xf9, 0xfe, 0xf2, 0xcb, 0xef, 0x01, 0x2f, 0x06, 0xb4, 0x49, 0x3b, 0xae, 0x17, 0x5c, 0x6f, + 0xd2, 0x3d, 0xbb, 0x7e, 0x74, 0x3d, 0x38, 0xea, 0x50, 0x9f, 0xff, 0x7b, 0xad, 0xe3, 0xb9, 0x81, + 0x4b, 0x86, 0xf1, 0xc7, 0xc2, 0xdc, 0x9e, 0xbb, 0xe7, 0x22, 0xe4, 0x3a, 0xfb, 0x8b, 0x17, 0x2e, + 0xbc, 0xb0, 0xe7, 0xba, 0x7b, 0x4d, 0x7a, 0x1d, 0x7f, 0xed, 0x74, 0x77, 0xaf, 0x37, 0xba, 0x9e, + 0x1d, 0x38, 0x6e, 0x5b, 0x94, 0x17, 0xe3, 0xe5, 0x81, 0xd3, 0xa2, 0x7e, 0x60, 0xb7, 0x3a, 0xbd, + 0x18, 0x1c, 0x7a, 0x76, 0xa7, 0x43, 0x3d, 0x51, 0xfb, 0xc2, 0xd5, 0xf0, 0x03, 0xed, 0x20, 0x60, + 0x94, 0x8c, 0xf9, 0xf5, 0x87, 0x37, 0xd4, 0x9f, 0x02, 0xf5, 0x56, 0x8f, 0xb6, 0x78, 0x5d, 0x3f, + 0xa0, 0x0d, 0xab, 0x41, 0x1f, 0x3a, 0x75, 0x6a, 0x79, 0xf4, 0xeb, 0x5d, 0xc7, 0xa3, 0x2d, 0xda, + 0x0e, 0x04, 0xdd, 0x1b, 0xe9, 0x74, 0xf2, 0x43, 0x62, 0x5f, 0x64, 0xfc, 0x62, 0x0e, 0xc6, 0xee, + 0x51, 0xda, 0x29, 0x35, 0x9d, 0x87, 0x94, 0xbc, 0x04, 0x43, 0xeb, 0x76, 0x8b, 0xce, 0x67, 0x2e, + 0x65, 0xae, 0x8c, 0x2d, 0x4d, 0x9f, 0x1c, 0x17, 0xc7, 0x7d, 0xea, 0x3d, 0xa4, 0x9e, 0xd5, 0xb6, + 0x5b, 0xd4, 0xc4, 0x42, 0xf2, 0x29, 0x18, 0x63, 0xff, 0xfb, 0x1d, 0xbb, 0x4e, 0xe7, 0xb3, 0x88, + 0x39, 0x79, 0x72, 0x5c, 0x1c, 0x6b, 0x4b, 0xa0, 0x19, 0x95, 0x93, 0x2a, 0x8c, 0x2e, 0x3f, 0xea, + 0x38, 0x1e, 0xf5, 0xe7, 0x87, 0x2e, 0x65, 0xae, 0x8c, 0x2f, 0x2e, 0x5c, 0xe3, 0x7d, 0x74, 0x4d, + 0xf6, 0xd1, 0xb5, 0x4d, 0xd9, 0x89, 0x4b, 0xb3, 0xbf, 0x77, 0x5c, 0x7c, 0xe6, 0xe4, 0xb8, 0x38, + 0x4a, 0x39, 0xc9, 0x4f, 0xfd, 0x51, 0x31, 0x63, 0x4a, 0x7a, 0xf2, 0x0e, 0x0c, 0x6d, 0x1e, 0x75, + 0xe8, 0xfc, 0xd8, 0xa5, 0xcc, 0x95, 0xa9, 0xc5, 0x17, 0xae, 0xf1, 0x61, 0x0d, 0x3f, 0x3e, 0xfa, + 0x8b, 0x61, 0x2d, 0xe5, 0x4f, 0x8e, 0x8b, 0x43, 0x0c, 0xc5, 0x44, 0x2a, 0xf2, 0x06, 0x8c, 0xac, + 0xb8, 0x7e, 0x50, 0xad, 0xcc, 0x03, 0x7e, 0xf2, 0xb9, 0x93, 0xe3, 0xe2, 0xcc, 0xbe, 0xeb, 0x07, + 0x96, 0xd3, 0x78, 0xdd, 0x6d, 0x39, 0x01, 0x6d, 0x75, 0x82, 0x23, 0x53, 0x20, 0x19, 0x8f, 0x60, + 0x52, 0xe3, 0x47, 0xc6, 0x61, 0x74, 0x6b, 0xfd, 0xde, 0xfa, 0xfd, 0xed, 0xf5, 0xc2, 0x33, 0x24, + 0x0f, 0x43, 0xeb, 0xf7, 0x2b, 0xcb, 0x85, 0x0c, 0x19, 0x85, 0x5c, 0x69, 0x63, 0xa3, 0x90, 0x25, + 0x13, 0x90, 0xaf, 0x94, 0x36, 0x4b, 0x4b, 0xa5, 0xda, 0x72, 0x21, 0x47, 0x66, 0x61, 0x7a, 0xbb, + 0xba, 0x5e, 0xb9, 0xbf, 0x5d, 0xb3, 0x2a, 0xcb, 0xb5, 0x7b, 0x9b, 0xf7, 0x37, 0x0a, 0x43, 0x64, + 0x0a, 0xe0, 0xde, 0xd6, 0xd2, 0xb2, 0xb9, 0xbe, 0xbc, 0xb9, 0x5c, 0x2b, 0x0c, 0x93, 0x39, 0x28, + 0x48, 0x12, 0xab, 0xb6, 0x6c, 0x3e, 0xa8, 0x96, 0x97, 0x0b, 0x23, 0x77, 0x87, 0xf2, 0xb9, 0xc2, + 0x90, 0x39, 0xba, 0x4a, 0x6d, 0x9f, 0x56, 0x2b, 0xc6, 0x7f, 0x98, 0x83, 0xfc, 0x1a, 0x0d, 0xec, + 0x86, 0x1d, 0xd8, 0xe4, 0x39, 0x6d, 0x7c, 0xb0, 0x89, 0xca, 0xc0, 0xbc, 0x94, 0x1c, 0x98, 0xe1, + 0x93, 0xe3, 0x62, 0xe6, 0x0d, 0x75, 0x40, 0xde, 0x86, 0xf1, 0x0a, 0xf5, 0xeb, 0x9e, 0xd3, 0x61, + 0x93, 0x6d, 0x3e, 0x87, 0x68, 0x17, 0x4f, 0x8e, 0x8b, 0xe7, 0x1a, 0x11, 0x58, 0xe9, 0x10, 0x15, + 0x9b, 0x54, 0x61, 0x64, 0xd5, 0xde, 0xa1, 0x4d, 0x7f, 0x7e, 0xf8, 0x52, 0xee, 0xca, 0xf8, 0xe2, + 0xb3, 0x62, 0x10, 0xe4, 0x07, 0x5e, 0xe3, 0xa5, 0xcb, 0xed, 0xc0, 0x3b, 0x5a, 0x9a, 0x3b, 0x39, + 0x2e, 0x16, 0x9a, 0x08, 0x50, 0x3b, 0x98, 0xa3, 0x90, 0x5a, 0x34, 0x31, 0x46, 0x4e, 0x9d, 0x18, + 0xcf, 0xff, 0xde, 0x71, 0x31, 0xc3, 0x06, 0x4c, 0x4c, 0x8c, 0x88, 0x9f, 0x3e, 0x45, 0x16, 0x21, + 0x6f, 0xd2, 0x87, 0x8e, 0xcf, 0x5a, 0x96, 0xc7, 0x96, 0x9d, 0x3f, 0x39, 0x2e, 0x12, 0x4f, 0xc0, + 0x94, 0xcf, 0x08, 0xf1, 0x16, 0xde, 0x82, 0x71, 0xe5, 0xab, 0x49, 0x01, 0x72, 0x07, 0xf4, 0x88, + 0xf7, 0xb0, 0xc9, 0xfe, 0x24, 0x73, 0x30, 0xfc, 0xd0, 0x6e, 0x76, 0x45, 0x97, 0x9a, 0xfc, 0xc7, + 0xe7, 0xb3, 0x9f, 0xcb, 0xdc, 0x1d, 0xca, 0x8f, 0x16, 0xf2, 0x66, 0xb6, 0x5a, 0x31, 0x7e, 0x66, + 0x08, 0xf2, 0xa6, 0xcb, 0x17, 0x30, 0xb9, 0x0a, 0xc3, 0xb5, 0xc0, 0x0e, 0xe4, 0x30, 0xcd, 0x9e, + 0x1c, 0x17, 0xa7, 0xd9, 0xe2, 0xa6, 0x4a, 0xfd, 0x1c, 0x83, 0xa1, 0x6e, 0xec, 0xdb, 0xbe, 0x1c, + 0x2e, 0x44, 0xed, 0x30, 0x80, 0x8a, 0x8a, 0x18, 0xe4, 0x32, 0x0c, 0xad, 0xb9, 0x0d, 0x2a, 0x46, + 0x8c, 0x9c, 0x1c, 0x17, 0xa7, 0x5a, 0x6e, 0x43, 0x45, 0xc4, 0x72, 0xf2, 0x3a, 0x8c, 0x95, 0xbb, + 0x9e, 0x47, 0xdb, 0x6c, 0xae, 0x0f, 0x21, 0xf2, 0xd4, 0xc9, 0x71, 0x11, 0xea, 0x1c, 0x68, 0x39, + 0x0d, 0x33, 0x42, 0x60, 0xc3, 0x50, 0x0b, 0x6c, 0x2f, 0xa0, 0x8d, 0xf9, 0xe1, 0x81, 0x86, 0x81, + 0xad, 0xcf, 0x19, 0x9f, 0x93, 0xc4, 0x87, 0x41, 0x70, 0x22, 0x2b, 0x30, 0x7e, 0xc7, 0xb3, 0xeb, + 0x74, 0x83, 0x7a, 0x8e, 0xdb, 0xc0, 0xf1, 0xcd, 0x2d, 0x5d, 0x3e, 0x39, 0x2e, 0x9e, 0xdf, 0x63, + 0x60, 0xab, 0x83, 0xf0, 0x88, 0xfa, 0xdb, 0xc7, 0xc5, 0x7c, 0x45, 0x6c, 0xb5, 0xa6, 0x4a, 0x4a, + 0xbe, 0xc6, 0x06, 0xc7, 0x0f, 0xb0, 0x6b, 0x69, 0x63, 0x7e, 0xf4, 0xd4, 0x4f, 0x34, 0xc4, 0x27, + 0x9e, 0x6f, 0xda, 0x7e, 0x60, 0x79, 0x9c, 0x2e, 0xf6, 0x9d, 0x2a, 0x4b, 0x72, 0x1f, 0xf2, 0xb5, + 0xfa, 0x3e, 0x6d, 0x74, 0x9b, 0x14, 0xa7, 0xcc, 0xf8, 0xe2, 0x05, 0x31, 0xa9, 0xe5, 0x78, 0xca, + 0xe2, 0xa5, 0x05, 0xc1, 0x9b, 0xf8, 0x02, 0xa2, 0xce, 0x27, 0x89, 0xf5, 0xf9, 0xfc, 0x37, 0x7f, + 0xa9, 0xf8, 0xcc, 0xf7, 0xfd, 0x8b, 0x4b, 0xcf, 0x18, 0xff, 0x79, 0x16, 0x0a, 0x71, 0x26, 0x64, + 0x17, 0x26, 0xb7, 0x3a, 0x0d, 0x3b, 0xa0, 0xe5, 0xa6, 0x43, 0xdb, 0x81, 0x8f, 0x93, 0xa4, 0x7f, + 0x9b, 0x5e, 0x16, 0xf5, 0xce, 0x77, 0x91, 0xd0, 0xaa, 0x73, 0xca, 0x58, 0xab, 0x74, 0xb6, 0x51, + 0x3d, 0x35, 0xdc, 0xc0, 0x7d, 0x9c, 0x61, 0x67, 0xab, 0x87, 0x6f, 0xfd, 0x3d, 0xea, 0x11, 0x6c, + 0xc5, 0x04, 0x6a, 0x37, 0x76, 0x8e, 0x70, 0x66, 0x0e, 0x3e, 0x81, 0x18, 0x49, 0xca, 0x04, 0x62, + 0x60, 0xe3, 0xff, 0xcc, 0xc0, 0x94, 0x49, 0x7d, 0xb7, 0xeb, 0xd5, 0xe9, 0x0a, 0xb5, 0x1b, 0xd4, + 0x63, 0xd3, 0xff, 0x9e, 0xd3, 0x6e, 0x88, 0x35, 0x85, 0xd3, 0xff, 0xc0, 0x69, 0xab, 0x5b, 0x37, + 0x96, 0x93, 0x4f, 0xc3, 0x68, 0xad, 0xbb, 0x83, 0xa8, 0xd9, 0x68, 0x07, 0xf0, 0xbb, 0x3b, 0x56, + 0x0c, 0x5d, 0xa2, 0x91, 0xeb, 0x30, 0xfa, 0x80, 0x7a, 0x7e, 0xb4, 0x1b, 0xe2, 0xd1, 0xf0, 0x90, + 0x83, 0x54, 0x02, 0x81, 0x45, 0xee, 0x44, 0x3b, 0xb2, 0x38, 0xd4, 0xa6, 0x63, 0xfb, 0x60, 0x34, + 0x55, 0x5a, 0x02, 0xa2, 0x4e, 0x15, 0x89, 0x65, 0xfc, 0x74, 0x16, 0x0a, 0x15, 0x3b, 0xb0, 0x77, + 0x6c, 0x5f, 0xf4, 0xe7, 0x83, 0x9b, 0x6c, 0x8f, 0x57, 0x1a, 0x8a, 0x7b, 0x3c, 0xfb, 0xf2, 0x8f, + 0xdd, 0xbc, 0x57, 0xe2, 0xcd, 0x1b, 0x67, 0x27, 0xac, 0x68, 0x5e, 0xd4, 0xa8, 0x77, 0x4f, 0x6f, + 0x54, 0x41, 0x34, 0x2a, 0x2f, 0x1b, 0x15, 0x35, 0x85, 0xbc, 0x0b, 0x43, 0xb5, 0x0e, 0xad, 0x8b, + 0x4d, 0x44, 0x9e, 0x0b, 0x7a, 0xe3, 0x18, 0xc2, 0x83, 0x9b, 0x4b, 0x13, 0x82, 0xcd, 0x90, 0xdf, + 0xa1, 0x75, 0x13, 0xc9, 0x94, 0x45, 0xf3, 0x0f, 0x73, 0x30, 0x97, 0x46, 0xa6, 0xb6, 0x63, 0xa4, + 0x4f, 0x3b, 0xae, 0x40, 0x9e, 0x1d, 0xe1, 0xec, 0x58, 0xc4, 0xed, 0x62, 0x6c, 0x69, 0x82, 0x7d, + 0xf2, 0xbe, 0x80, 0x99, 0x61, 0x29, 0x79, 0x29, 0x94, 0x08, 0xf2, 0x11, 0x3f, 0x21, 0x11, 0x48, + 0x39, 0x80, 0x8d, 0xb5, 0x5c, 0xc2, 0x28, 0x38, 0x44, 0xdd, 0x22, 0xc1, 0xd1, 0x58, 0x7b, 0x02, + 0xa2, 0x1d, 0x33, 0xf2, 0x50, 0x58, 0x86, 0xbc, 0x6c, 0xd6, 0xfc, 0x04, 0x32, 0x9a, 0x89, 0x75, + 0xd2, 0x83, 0x9b, 0x7c, 0x30, 0x1b, 0xe2, 0xb7, 0xca, 0x46, 0xe2, 0x90, 0x9b, 0x90, 0xdf, 0xf0, + 0xdc, 0x47, 0x47, 0xd5, 0x8a, 0x3f, 0x3f, 0x79, 0x29, 0x77, 0x65, 0x6c, 0xe9, 0xc2, 0xc9, 0x71, + 0x71, 0xb6, 0xc3, 0x60, 0x96, 0xd3, 0x50, 0x4f, 0xda, 0x10, 0xf1, 0xee, 0x50, 0x3e, 0x53, 0xc8, + 0xde, 0x1d, 0xca, 0x67, 0x0b, 0x39, 0x2e, 0x5e, 0xdc, 0x1d, 0xca, 0x0f, 0x15, 0x86, 0xef, 0x0e, + 0xe5, 0x87, 0x51, 0xe0, 0x18, 0x2b, 0xc0, 0xdd, 0xa1, 0xfc, 0x78, 0x61, 0x42, 0x3b, 0xed, 0x91, + 0x41, 0xe0, 0xd6, 0xdd, 0xa6, 0x99, 0xdb, 0x32, 0xab, 0xe6, 0x48, 0xb9, 0x54, 0xa6, 0x5e, 0x60, + 0xe6, 0x4a, 0xdb, 0x35, 0x73, 0xb2, 0x72, 0xd4, 0xb6, 0x5b, 0x4e, 0x9d, 0x1f, 0x9d, 0x66, 0xee, + 0x4e, 0x79, 0xc3, 0x28, 0xc1, 0x54, 0xd4, 0x96, 0x55, 0xc7, 0x0f, 0xc8, 0x75, 0x18, 0x93, 0x10, + 0xb6, 0xd1, 0xe5, 0x52, 0x5b, 0x6d, 0x46, 0x38, 0xc6, 0xef, 0x66, 0x01, 0xa2, 0x92, 0xa7, 0x74, + 0x2d, 0x7c, 0x56, 0x5b, 0x0b, 0xe7, 0xe2, 0x6b, 0xa1, 0xe7, 0x2a, 0x20, 0xef, 0xc3, 0x08, 0x13, + 0x0b, 0xba, 0x52, 0x24, 0xba, 0x10, 0x27, 0xc5, 0xc2, 0x07, 0x37, 0x97, 0xa6, 0x04, 0xf1, 0x88, + 0x8f, 0x10, 0x53, 0x90, 0x29, 0xcb, 0xe8, 0x17, 0x47, 0xa3, 0xc1, 0x10, 0x0b, 0xe8, 0x0a, 0x84, + 0x03, 0x2a, 0x3a, 0x14, 0x57, 0x46, 0x47, 0x0e, 0x72, 0x58, 0x4a, 0x2e, 0x02, 0x1b, 0x70, 0xd1, + 0xa9, 0xa3, 0x27, 0xc7, 0xc5, 0x5c, 0xd7, 0x73, 0x70, 0x12, 0x90, 0xeb, 0x20, 0xa6, 0x81, 0xe8, + 0x40, 0x36, 0xfb, 0x66, 0xea, 0xb6, 0x55, 0xa7, 0x5e, 0x10, 0xf5, 0xf8, 0x7c, 0x46, 0xce, 0x16, + 0xd2, 0x01, 0x7d, 0xaa, 0xcc, 0x0f, 0xe1, 0x34, 0xb8, 0x92, 0xda, 0x2b, 0xd7, 0x34, 0x54, 0x2e, + 0x46, 0x5e, 0x92, 0xa7, 0x52, 0x83, 0x97, 0x59, 0x09, 0x91, 0x52, 0xaf, 0x80, 0xdc, 0x04, 0x36, + 0x43, 0x45, 0xef, 0x83, 0xa8, 0xa7, 0xb4, 0x5d, 0x5b, 0x3a, 0x27, 0x38, 0x4d, 0xda, 0x87, 0x2a, + 0x39, 0xc3, 0x26, 0x6f, 0x03, 0x9b, 0xc2, 0xa2, 0xdf, 0x89, 0x20, 0xba, 0x53, 0xde, 0x28, 0x37, + 0xdd, 0x6e, 0xa3, 0xf6, 0xc1, 0x6a, 0x44, 0xbc, 0x57, 0xef, 0xa8, 0xc4, 0x77, 0xca, 0x1b, 0xe4, + 0x6d, 0x18, 0x2e, 0x7d, 0x77, 0xd7, 0xa3, 0x42, 0x3e, 0x99, 0x90, 0x75, 0x32, 0xd8, 0xd2, 0x05, + 0x41, 0x38, 0x6d, 0xb3, 0x9f, 0xaa, 0x5c, 0x87, 0xe5, 0xac, 0xe6, 0xcd, 0xd5, 0x9a, 0x90, 0x3d, + 0x48, 0xac, 0x5b, 0x36, 0x57, 0x95, 0xcf, 0x0e, 0xb4, 0x56, 0x33, 0x2a, 0x72, 0x1d, 0xb2, 0xa5, + 0x0a, 0xde, 0x88, 0xc6, 0x17, 0xc7, 0x64, 0xb5, 0x95, 0xa5, 0x39, 0x41, 0x32, 0x61, 0xab, 0xcb, + 0x20, 0x5b, 0xaa, 0x90, 0x25, 0x18, 0x5e, 0x3b, 0xaa, 0x7d, 0xb0, 0x2a, 0x36, 0xb3, 0x59, 0x39, + 0xaf, 0x19, 0xec, 0x3e, 0x2e, 0x7b, 0x3f, 0xfa, 0xe2, 0xd6, 0x91, 0xff, 0xf5, 0xa6, 0xfa, 0xc5, + 0x88, 0x46, 0x36, 0x60, 0xac, 0xd4, 0x68, 0x39, 0xed, 0x2d, 0x9f, 0x7a, 0xf3, 0xe3, 0xc8, 0x67, + 0x3e, 0xf6, 0xdd, 0x61, 0xf9, 0xd2, 0xfc, 0xc9, 0x71, 0x71, 0xce, 0x66, 0x3f, 0xad, 0xae, 0x4f, + 0x3d, 0x85, 0x5b, 0xc4, 0x84, 0x6c, 0x00, 0xac, 0xb9, 0xed, 0x3d, 0xb7, 0x14, 0x34, 0x6d, 0x3f, + 0xb6, 0x3d, 0x46, 0x05, 0xa1, 0xf8, 0x70, 0xae, 0xc5, 0x60, 0x96, 0xcd, 0x80, 0x0a, 0x43, 0x85, + 0x07, 0xb9, 0x0d, 0x23, 0xf7, 0x3d, 0xbb, 0xde, 0xa4, 0xf3, 0x93, 0xc8, 0x6d, 0x4e, 0x70, 0xe3, + 0x40, 0xd9, 0xd2, 0x79, 0xc1, 0xb0, 0xe0, 0x22, 0x58, 0xbd, 0xa6, 0x70, 0xc4, 0x85, 0x6d, 0x20, + 0xc9, 0x39, 0x99, 0x72, 0x49, 0xf8, 0x94, 0x7a, 0x49, 0x88, 0x16, 0x7d, 0xd9, 0x6d, 0xb5, 0xec, + 0x76, 0x03, 0x69, 0x1f, 0x2c, 0x2a, 0x77, 0x07, 0xe3, 0xeb, 0x30, 0x93, 0xe8, 0xac, 0x53, 0xee, + 0x77, 0xef, 0xc1, 0x74, 0x85, 0xee, 0xda, 0xdd, 0x66, 0x10, 0x9e, 0x24, 0x7c, 0x89, 0xe2, 0x4d, + 0xab, 0xc1, 0x8b, 0x2c, 0x79, 0x7c, 0x98, 0x71, 0x64, 0xe3, 0x5d, 0x98, 0xd4, 0x9a, 0xcf, 0xae, + 0x0a, 0xa5, 0x6e, 0xc3, 0x09, 0x70, 0x20, 0x33, 0xd1, 0x55, 0xc1, 0x66, 0x40, 0x1c, 0x2e, 0x33, + 0x42, 0x30, 0xfe, 0x8e, 0x2a, 0xad, 0x88, 0x9d, 0x88, 0x5d, 0xab, 0xc5, 0x7e, 0x90, 0x89, 0x64, + 0xa7, 0xc4, 0x7e, 0x10, 0xee, 0x06, 0x57, 0xf9, 0xda, 0xcc, 0x26, 0xd6, 0xe6, 0xb8, 0x18, 0x89, + 0x9c, 0x7d, 0xe8, 0xf3, 0x15, 0x19, 0xce, 0xd4, 0xdc, 0xc7, 0x9f, 0xa9, 0xef, 0xc3, 0xc4, 0x9a, + 0xdd, 0xb6, 0xf7, 0x68, 0x83, 0xb5, 0x80, 0xef, 0x3d, 0x63, 0x4b, 0xcf, 0x9e, 0x1c, 0x17, 0x2f, + 0xb4, 0x38, 0x1c, 0x5b, 0xa9, 0x4e, 0x22, 0x8d, 0x80, 0xdc, 0x90, 0x2b, 0x7b, 0x38, 0x65, 0x65, + 0x4f, 0x8a, 0xda, 0x87, 0x71, 0x65, 0x8b, 0xf5, 0x6c, 0xfc, 0xf6, 0x18, 0xb6, 0x91, 0xbc, 0x0e, + 0x23, 0x26, 0xdd, 0x63, 0x47, 0x4d, 0x26, 0x1a, 0x24, 0x0f, 0x21, 0x6a, 0xc7, 0x70, 0x1c, 0x94, + 0x33, 0x68, 0xc3, 0xdf, 0x77, 0x76, 0x03, 0xd1, 0x3b, 0xa1, 0x9c, 0x21, 0xc0, 0x8a, 0x9c, 0x21, + 0x20, 0xfa, 0x75, 0x96, 0xc3, 0xd8, 0xee, 0x67, 0x56, 0x6a, 0xa2, 0xd3, 0x64, 0x0f, 0x9b, 0x15, + 0x65, 0x1b, 0xf1, 0x34, 0x29, 0x81, 0x61, 0x93, 0x5b, 0x30, 0x56, 0xaa, 0xd7, 0xdd, 0xae, 0x72, + 0x67, 0xe4, 0xeb, 0x96, 0x03, 0x75, 0x15, 0x49, 0x84, 0x4a, 0x6a, 0x30, 0xbe, 0xcc, 0x2e, 0x5a, + 0x4e, 0xd9, 0xae, 0xef, 0xcb, 0x4e, 0x92, 0x7b, 0x98, 0x52, 0x12, 0xad, 0x5c, 0x8a, 0xc0, 0x3a, + 0x03, 0xaa, 0x4a, 0x06, 0x05, 0x97, 0x6c, 0xc2, 0x78, 0x8d, 0xd6, 0x3d, 0x1a, 0xd4, 0x02, 0xd7, + 0xa3, 0xb1, 0x2d, 0x59, 0x29, 0x59, 0x7a, 0x41, 0xde, 0xf5, 0x7c, 0x04, 0x5a, 0x3e, 0x83, 0xaa, + 0x5c, 0x15, 0x64, 0x2e, 0xb4, 0xb7, 0x5c, 0xef, 0xa8, 0xb2, 0x24, 0xb6, 0xe9, 0xe8, 0x4c, 0xe7, + 0x60, 0x55, 0x68, 0x67, 0x90, 0xc6, 0x8e, 0x2e, 0xb4, 0x73, 0x2c, 0x1c, 0xa9, 0x4a, 0x0d, 0x65, + 0x2b, 0xb1, 0x69, 0x4f, 0x47, 0xbd, 0x8c, 0x60, 0x65, 0xa4, 0x1a, 0x3e, 0x4a, 0x66, 0xda, 0x48, + 0x09, 0x2c, 0xd2, 0x01, 0x22, 0x47, 0x8d, 0x0b, 0xba, 0x4d, 0xea, 0xfb, 0x62, 0x2f, 0xbf, 0x18, + 0x1b, 0xfc, 0x08, 0x61, 0xe9, 0x15, 0xc1, 0xfc, 0x79, 0x39, 0x0d, 0xc4, 0x3d, 0x8d, 0x15, 0x2a, + 0xf5, 0xa4, 0xf0, 0x26, 0x6f, 0x01, 0x2c, 0x3f, 0x0a, 0xa8, 0xd7, 0xb6, 0x9b, 0xa1, 0x1e, 0x0c, + 0x55, 0x3f, 0x54, 0x40, 0xf5, 0x81, 0x56, 0x90, 0x49, 0x19, 0x26, 0x4b, 0xbe, 0xdf, 0x6d, 0x51, + 0xd3, 0x6d, 0xd2, 0x92, 0xb9, 0x8e, 0xfb, 0xfe, 0xd8, 0xd2, 0xf3, 0x27, 0xc7, 0xc5, 0x8b, 0x36, + 0x16, 0x58, 0x9e, 0xdb, 0xa4, 0x96, 0xed, 0xa9, 0xb3, 0x5b, 0xa7, 0x21, 0xf7, 0x01, 0xee, 0x77, + 0x68, 0xbb, 0x46, 0x6d, 0xaf, 0xbe, 0x1f, 0xdb, 0xe6, 0xa3, 0x82, 0xa5, 0xe7, 0x44, 0x0b, 0xe7, + 0xdc, 0x0e, 0x6d, 0xfb, 0x08, 0x53, 0xbf, 0x2a, 0xc2, 0x24, 0xdb, 0x30, 0x5d, 0x2d, 0xad, 0x6d, + 0xb8, 0x4d, 0xa7, 0x7e, 0x24, 0x24, 0xa7, 0x29, 0xd4, 0x0e, 0x9e, 0x17, 0x5c, 0x63, 0xa5, 0x7c, + 0x7b, 0x72, 0xec, 0x96, 0xd5, 0x41, 0xa8, 0x25, 0xe4, 0xa7, 0x38, 0x17, 0xf2, 0x21, 0x9b, 0x83, + 0x3e, 0x13, 0x06, 0x37, 0xed, 0x3d, 0x7f, 0x7e, 0x5a, 0xd3, 0x76, 0x95, 0xb6, 0x6b, 0xd7, 0x94, + 0x52, 0x2e, 0xa6, 0x2c, 0xf0, 0x89, 0x88, 0x50, 0x2b, 0xb0, 0xf7, 0x7c, 0x7d, 0x22, 0x86, 0xd8, + 0xe4, 0x2e, 0x40, 0xc5, 0xad, 0x77, 0x5b, 0xb4, 0x1d, 0x54, 0x96, 0xe6, 0x0b, 0xfa, 0x55, 0x20, + 0x2c, 0x88, 0xb6, 0xb6, 0x86, 0x5b, 0xd7, 0x66, 0xa2, 0x42, 0xbd, 0xf0, 0x1e, 0x14, 0xe2, 0x1f, + 0x72, 0x46, 0x05, 0xd6, 0x64, 0x61, 0x4a, 0x69, 0xfd, 0xf2, 0x23, 0xc7, 0x0f, 0x7c, 0xe3, 0x1b, + 0xda, 0x0a, 0x64, 0xbb, 0xc3, 0x3d, 0x7a, 0xb4, 0xe1, 0xd1, 0x5d, 0xe7, 0x91, 0xd8, 0xcc, 0x70, + 0x77, 0x38, 0xa0, 0x47, 0x56, 0x07, 0xa1, 0xea, 0xee, 0x10, 0xa2, 0x92, 0xcf, 0x40, 0xfe, 0xde, + 0x5a, 0xed, 0x1e, 0x3d, 0xaa, 0x56, 0xc4, 0x41, 0xc5, 0xc9, 0x5a, 0xbe, 0xc5, 0x48, 0xb5, 0xb9, + 0x16, 0x62, 0x1a, 0x4b, 0xd1, 0x4e, 0xc8, 0x6a, 0x2e, 0x37, 0xbb, 0x7e, 0x40, 0xbd, 0x6a, 0x45, + 0xad, 0xb9, 0xce, 0x81, 0xb1, 0x7d, 0x29, 0x44, 0x35, 0xfe, 0x75, 0x16, 0x77, 0x41, 0x36, 0xe1, + 0xab, 0x6d, 0x3f, 0xb0, 0xdb, 0x75, 0x1a, 0x32, 0xc0, 0x09, 0xef, 0x08, 0x68, 0x6c, 0xc2, 0x47, + 0xc8, 0x7a, 0xd5, 0xd9, 0x81, 0xab, 0x66, 0x55, 0x4a, 0xcd, 0x45, 0xb5, 0xa2, 0xaa, 0x57, 0x3d, + 0x01, 0x8d, 0x55, 0x19, 0x21, 0x93, 0xcb, 0x30, 0x5a, 0x2d, 0xad, 0x95, 0xba, 0xc1, 0x3e, 0xee, + 0xc1, 0x79, 0x2e, 0x9f, 0xb3, 0xd9, 0x6a, 0x77, 0x83, 0x7d, 0x53, 0x16, 0x92, 0xeb, 0x78, 0xef, + 0x69, 0xd3, 0x80, 0xab, 0x61, 0xc5, 0xa1, 0xeb, 0x73, 0x50, 0xec, 0xda, 0xc3, 0x40, 0xe4, 0x35, + 0x18, 0x7e, 0xb0, 0x51, 0xae, 0x56, 0xc4, 0xc5, 0x19, 0x4f, 0xa2, 0x87, 0x9d, 0xba, 0xfe, 0x25, + 0x1c, 0x85, 0x2c, 0xc3, 0x54, 0x8d, 0xd6, 0xbb, 0x9e, 0x13, 0x1c, 0xdd, 0xf1, 0xdc, 0x6e, 0xc7, + 0x9f, 0x1f, 0xc5, 0x3a, 0x70, 0xa5, 0xfb, 0xa2, 0xc4, 0xda, 0xc3, 0x22, 0x85, 0x3a, 0x46, 0x64, + 0xfc, 0x4e, 0x26, 0xda, 0x26, 0xc9, 0x65, 0x4d, 0xac, 0x41, 0xdd, 0x0d, 0x13, 0x6b, 0x54, 0xdd, + 0x0d, 0x0a, 0x38, 0x26, 0x90, 0x72, 0xd7, 0x0f, 0xdc, 0xd6, 0x72, 0xbb, 0xd1, 0x71, 0x9d, 0x76, + 0x80, 0x54, 0xbc, 0xf3, 0x8d, 0x93, 0xe3, 0xe2, 0x0b, 0x75, 0x2c, 0xb5, 0xa8, 0x28, 0xb6, 0x62, + 0x5c, 0x52, 0xa8, 0x1f, 0x63, 0x3c, 0x8c, 0xdf, 0xcf, 0x6a, 0xc7, 0x1b, 0xfb, 0x3c, 0x93, 0x76, + 0x9a, 0x4e, 0x1d, 0x6f, 0xf4, 0xd8, 0xd0, 0x70, 0x56, 0xe1, 0xe7, 0x79, 0x51, 0x29, 0xef, 0x21, + 0x9d, 0x77, 0x0a, 0x35, 0xf9, 0x02, 0x4c, 0x30, 0x49, 0x43, 0xfc, 0xf4, 0xe7, 0xb3, 0xd8, 0xd9, + 0xcf, 0xa1, 0x16, 0xce, 0xa7, 0x5e, 0xc8, 0x46, 0x13, 0x51, 0x54, 0x0a, 0xd2, 0x80, 0xf9, 0x4d, + 0xcf, 0x6e, 0xfb, 0x4e, 0xb0, 0xdc, 0xae, 0x7b, 0x47, 0x28, 0x19, 0x2d, 0xb7, 0xed, 0x9d, 0x26, + 0x6d, 0x60, 0x73, 0xf3, 0x4b, 0x57, 0x4e, 0x8e, 0x8b, 0x2f, 0x07, 0x1c, 0xc7, 0xa2, 0x21, 0x92, + 0x45, 0x39, 0x96, 0xc2, 0xb9, 0x27, 0x27, 0x26, 0x49, 0xc9, 0x6e, 0xc5, 0x47, 0x18, 0x2e, 0x24, + 0xa0, 0x24, 0x15, 0x8e, 0x06, 0xdb, 0xc3, 0xd4, 0xcf, 0x54, 0x09, 0x8c, 0xff, 0x37, 0x13, 0x1d, + 0xc0, 0xe4, 0x1d, 0x18, 0x17, 0x2b, 0x46, 0x99, 0x17, 0xb8, 0x83, 0xca, 0xe5, 0x15, 0x1b, 0x59, + 0x15, 0x9d, 0xdd, 0xfb, 0x4b, 0xe5, 0x55, 0x65, 0x6e, 0xe0, 0xbd, 0xdf, 0xae, 0x37, 0xe3, 0x54, + 0x12, 0x8d, 0x4d, 0x82, 0xcd, 0xd5, 0x9a, 0xde, 0x2b, 0x38, 0x09, 0x82, 0xa6, 0x9f, 0xd2, 0x0d, + 0x0a, 0xf2, 0xe3, 0x37, 0xfc, 0x7f, 0xce, 0xa4, 0x9d, 0xf3, 0x64, 0x09, 0x26, 0xb7, 0x5d, 0xef, + 0x00, 0xc7, 0x57, 0xe9, 0x04, 0x1c, 0xf9, 0x43, 0x59, 0x10, 0x6f, 0x90, 0x4e, 0xa2, 0x7e, 0x9b, + 0xd2, 0x1b, 0xfa, 0xb7, 0xc5, 0x38, 0x68, 0x04, 0x6c, 0x1c, 0x42, 0x8e, 0xe1, 0xea, 0xc0, 0x71, + 0x88, 0x3e, 0x41, 0x9b, 0xc2, 0x2a, 0xba, 0xf1, 0x5f, 0x65, 0xd4, 0xf3, 0x9c, 0x75, 0x72, 0xc5, + 0x6d, 0xd9, 0x4e, 0x5b, 0x69, 0x0e, 0x7f, 0x58, 0x42, 0x68, 0xfc, 0x4b, 0x14, 0x64, 0x72, 0x13, + 0xf2, 0xfc, 0x57, 0xb8, 0xd7, 0xa2, 0x56, 0x4b, 0x10, 0xea, 0x07, 0x85, 0x44, 0x4c, 0x8c, 0x4c, + 0xee, 0xac, 0x23, 0xf3, 0xdb, 0x19, 0xf5, 0x28, 0xfe, 0xb8, 0x87, 0x4d, 0xec, 0x90, 0xc9, 0x9e, + 0xe5, 0x90, 0x79, 0xec, 0x26, 0x7c, 0x5f, 0x06, 0xc6, 0x15, 0x2d, 0x05, 0x6b, 0xc3, 0x86, 0xe7, + 0x7e, 0x44, 0xeb, 0x81, 0xde, 0x86, 0x0e, 0x07, 0xc6, 0xda, 0x10, 0xa2, 0x3e, 0x46, 0x1b, 0x8c, + 0x3f, 0xcb, 0x88, 0x3b, 0xd2, 0xc0, 0xdb, 0xbc, 0xbe, 0x25, 0x67, 0xcf, 0x72, 0x44, 0x7e, 0x01, + 0x86, 0x4d, 0xda, 0x70, 0x7c, 0x71, 0xbf, 0x99, 0x51, 0xef, 0x63, 0x58, 0x10, 0xc9, 0x4d, 0x1e, + 0xfb, 0xa9, 0x9e, 0x6f, 0x58, 0xce, 0x04, 0xd9, 0xaa, 0x7f, 0xbb, 0x49, 0x1f, 0x39, 0x7c, 0x31, + 0x8a, 0xa3, 0x16, 0x8f, 0x37, 0xc7, 0xb7, 0x76, 0x59, 0x89, 0x90, 0xa8, 0xd5, 0x85, 0xa7, 0xd1, + 0x18, 0x1f, 0x02, 0x44, 0x55, 0x92, 0x7b, 0x50, 0x10, 0xb3, 0xc1, 0x69, 0xef, 0x71, 0x41, 0x4a, + 0xf4, 0x41, 0xf1, 0xe4, 0xb8, 0xf8, 0x6c, 0x3d, 0x2c, 0x13, 0x52, 0xa7, 0xc2, 0x37, 0x41, 0x68, + 0xfc, 0xbd, 0x2c, 0x64, 0x4b, 0x38, 0x20, 0xf7, 0xe8, 0x51, 0x60, 0xef, 0xdc, 0x76, 0x9a, 0xda, + 0x62, 0x3a, 0x40, 0xa8, 0xb5, 0xeb, 0x68, 0xea, 0x0a, 0x05, 0x99, 0x2d, 0xa6, 0x7b, 0xde, 0xce, + 0x9b, 0x48, 0xa8, 0x2c, 0xa6, 0x03, 0x6f, 0xe7, 0xcd, 0x38, 0x59, 0x88, 0x48, 0x0c, 0x18, 0xe1, + 0x0b, 0x4b, 0xcc, 0x41, 0x38, 0x39, 0x2e, 0x8e, 0xf0, 0xf5, 0x67, 0x8a, 0x12, 0x72, 0x11, 0x72, + 0xb5, 0x8d, 0x75, 0xb1, 0x03, 0xa2, 0x5a, 0xd0, 0xef, 0xb4, 0x4d, 0x06, 0x63, 0x75, 0xae, 0x56, + 0x4a, 0x1b, 0xa8, 0x08, 0x18, 0x8e, 0xea, 0x6c, 0x36, 0xec, 0x4e, 0x5c, 0x15, 0x10, 0x22, 0x92, + 0x77, 0x61, 0xfc, 0x5e, 0xa5, 0xbc, 0xe2, 0xfa, 0x7c, 0xf7, 0x1a, 0x89, 0x26, 0xff, 0x41, 0xa3, + 0x6e, 0xa1, 0x26, 0x3e, 0x7e, 0x0c, 0x28, 0xf8, 0xc6, 0x0f, 0x67, 0x61, 0x5c, 0xd1, 0x93, 0x91, + 0xcf, 0x88, 0x07, 0xd2, 0x8c, 0x76, 0x03, 0x50, 0x30, 0x58, 0x29, 0x57, 0xaa, 0xb4, 0xdc, 0x06, + 0x15, 0xcf, 0xa5, 0x91, 0x02, 0x23, 0x3b, 0x88, 0x02, 0xe3, 0x2d, 0x00, 0x3e, 0x07, 0xf0, 0x93, + 0x15, 0x71, 0x42, 0xb1, 0x93, 0x50, 0xc7, 0x25, 0x42, 0x26, 0x0f, 0x60, 0x76, 0xd3, 0xeb, 0xfa, + 0x41, 0xed, 0xc8, 0x0f, 0x68, 0x8b, 0x71, 0xdb, 0x70, 0xdd, 0xa6, 0x98, 0x7f, 0x2f, 0x9f, 0x1c, + 0x17, 0x2f, 0xa1, 0x71, 0x87, 0xe5, 0x63, 0x39, 0x7e, 0x80, 0xd5, 0x71, 0x5d, 0x55, 0xad, 0x91, + 0xc6, 0xc0, 0x30, 0x61, 0x42, 0x55, 0x8a, 0xb0, 0x93, 0x45, 0x3c, 0x26, 0x09, 0x55, 0xb7, 0x72, + 0xb2, 0x88, 0xaf, 0x4c, 0x3e, 0x6e, 0xe9, 0x24, 0xc6, 0x67, 0x54, 0x85, 0xdc, 0xa0, 0x0b, 0xdb, + 0xf8, 0x81, 0x4c, 0xb4, 0x8d, 0x3c, 0xb8, 0x41, 0xde, 0x86, 0x11, 0xfe, 0x78, 0x27, 0xde, 0x38, + 0xcf, 0x85, 0x97, 0x5a, 0xf5, 0x65, 0x8f, 0x6b, 0xc2, 0xff, 0x90, 0x3f, 0xf0, 0x3f, 0x63, 0x0a, + 0x92, 0x50, 0x89, 0xae, 0xeb, 0xd3, 0x24, 0x77, 0x54, 0x17, 0xdf, 0x48, 0x53, 0xa2, 0x1b, 0x3f, + 0x3e, 0x0c, 0x53, 0x3a, 0x9a, 0xfa, 0xc2, 0x97, 0x19, 0xe8, 0x85, 0xef, 0x0b, 0x90, 0x67, 0xfd, + 0xe1, 0xd4, 0xa9, 0x94, 0xc8, 0x5e, 0xc6, 0xa7, 0x05, 0x01, 0xd3, 0x5e, 0xae, 0x81, 0x0f, 0x07, + 0xbb, 0xe3, 0x9a, 0x21, 0x15, 0x59, 0x54, 0x9e, 0xa1, 0x72, 0x91, 0x90, 0x22, 0x9f, 0xa1, 0xd4, + 0xf5, 0x10, 0x3e, 0x48, 0xbd, 0x01, 0x23, 0x4c, 0xbe, 0x0f, 0x55, 0x30, 0xf8, 0x95, 0x4c, 0xf4, + 0x8f, 0x99, 0xa8, 0x70, 0x24, 0xb2, 0x0d, 0xf9, 0x55, 0xdb, 0x0f, 0x6a, 0x94, 0xb6, 0x07, 0x78, + 0xbb, 0x2f, 0x8a, 0xae, 0x9a, 0xc5, 0x87, 0x71, 0x9f, 0xd2, 0x76, 0xec, 0xf1, 0x35, 0x64, 0x46, + 0xbe, 0x02, 0x50, 0x76, 0xdb, 0x81, 0xe7, 0x36, 0x57, 0xdd, 0xbd, 0xf9, 0x11, 0xbc, 0xfb, 0xbe, + 0x10, 0x1b, 0x80, 0x08, 0x81, 0x5f, 0x7f, 0x43, 0x05, 0x4f, 0x9d, 0x17, 0x58, 0x4d, 0x77, 0x4f, + 0x5d, 0x07, 0x11, 0x3e, 0xb9, 0x0d, 0x05, 0xa9, 0x58, 0xd8, 0xea, 0xec, 0x79, 0x38, 0x41, 0x46, + 0x23, 0xc9, 0x83, 0x3e, 0x0a, 0xac, 0xae, 0x80, 0xab, 0x3b, 0x65, 0x9c, 0x86, 0x7c, 0x19, 0x2e, + 0xc4, 0x61, 0x72, 0x94, 0xf3, 0x91, 0x4c, 0xae, 0xb2, 0x4b, 0x99, 0xf7, 0xbd, 0x58, 0x90, 0x3b, + 0x30, 0xcd, 0x3a, 0x64, 0x8d, 0xda, 0x7e, 0x97, 0x1b, 0x58, 0x09, 0xd5, 0xcc, 0xf3, 0x52, 0x13, + 0xc5, 0x57, 0x61, 0xd3, 0xad, 0x1f, 0x28, 0x48, 0x66, 0x9c, 0xca, 0x38, 0xce, 0xc2, 0xf9, 0x74, + 0x5c, 0xf2, 0xbd, 0x70, 0x4e, 0xf4, 0x4b, 0x93, 0x7a, 0x0a, 0xce, 0x00, 0x36, 0x01, 0x6f, 0x88, + 0xfe, 0x7e, 0xb1, 0x1e, 0x32, 0x08, 0x37, 0x0e, 0xc6, 0x22, 0x36, 0xb8, 0xe9, 0xf5, 0x90, 0xaf, + 0xc1, 0xb8, 0x5a, 0x6d, 0x76, 0x70, 0xf3, 0x8a, 0x3e, 0x75, 0xa9, 0x2c, 0x89, 0x0d, 0xd3, 0x26, + 0xfd, 0x7a, 0x97, 0xfa, 0x81, 0x34, 0xf0, 0x10, 0x47, 0xf7, 0xc5, 0x44, 0x2d, 0x12, 0x21, 0xd4, + 0xff, 0x14, 0x3c, 0x4e, 0x69, 0x49, 0x33, 0xbc, 0x6f, 0x32, 0xf6, 0x71, 0x7e, 0xc6, 0xb7, 0xb3, + 0x70, 0xa1, 0xc7, 0xb4, 0x64, 0x3b, 0x17, 0x0a, 0x56, 0xca, 0xce, 0x15, 0x93, 0xa7, 0xb8, 0x75, + 0xd8, 0x25, 0xc8, 0x0a, 0x51, 0x64, 0x68, 0xa9, 0x70, 0x72, 0x5c, 0x9c, 0xd0, 0x56, 0x5c, 0xb6, + 0x5a, 0x21, 0x77, 0x61, 0x88, 0x75, 0xc3, 0x00, 0x46, 0x0e, 0x52, 0xfb, 0x37, 0x15, 0x38, 0xea, + 0x42, 0xc7, 0xbe, 0x41, 0x1e, 0xe4, 0x33, 0x90, 0xdb, 0xdc, 0x5c, 0xc5, 0x55, 0x9e, 0xc3, 0x59, + 0x3a, 0x19, 0x04, 0x4d, 0x6d, 0x53, 0x99, 0x64, 0xb4, 0x61, 0x8f, 0x98, 0x0c, 0x9d, 0x7c, 0x31, + 0x66, 0x7c, 0xf5, 0x5a, 0xff, 0x25, 0x39, 0xb8, 0x2d, 0xd6, 0x63, 0x98, 0x40, 0x19, 0xbf, 0x90, + 0x8d, 0x76, 0xdb, 0xdb, 0x4e, 0x33, 0xa0, 0x1e, 0x59, 0xe0, 0x9b, 0x67, 0x24, 0x46, 0x9b, 0xe1, + 0x6f, 0x32, 0x1f, 0xed, 0xc4, 0x9c, 0x55, 0xb8, 0xe5, 0xbe, 0xa6, 0x6c, 0xb9, 0x39, 0xdc, 0x72, + 0xa7, 0x7a, 0x6e, 0xae, 0xaf, 0xa5, 0xec, 0x20, 0xb8, 0x65, 0xa6, 0xec, 0x12, 0x2f, 0xc3, 0xe4, + 0xba, 0xbb, 0xfc, 0x28, 0x08, 0x11, 0xd9, 0x56, 0x99, 0x37, 0x75, 0x20, 0xe3, 0x78, 0xbf, 0xd9, + 0xa0, 0xde, 0xe6, 0xbe, 0xdd, 0xd6, 0xac, 0x0c, 0xcc, 0x04, 0x9c, 0xe1, 0xae, 0xd3, 0x43, 0x1d, + 0x77, 0x94, 0xe3, 0xc6, 0xe1, 0xc6, 0xf7, 0x67, 0x65, 0x67, 0x3c, 0x58, 0x7c, 0x4a, 0x5f, 0xb3, + 0xdf, 0xd4, 0x5e, 0xb3, 0x67, 0x43, 0x3d, 0x7c, 0x68, 0x9a, 0xb1, 0x78, 0x8a, 0x45, 0xc7, 0xdf, + 0x19, 0x81, 0x09, 0x15, 0x9d, 0xf5, 0x43, 0xa9, 0xd1, 0xf0, 0xd4, 0x7e, 0xb0, 0x1b, 0x0d, 0xcf, + 0x44, 0xa8, 0x66, 0xc0, 0x91, 0xeb, 0x6b, 0xc0, 0xf1, 0x55, 0x18, 0x2b, 0xb7, 0x1a, 0xda, 0xb3, + 0xb2, 0x91, 0xf2, 0x79, 0xd7, 0x42, 0x24, 0xbe, 0x16, 0x42, 0xf5, 0x72, 0xbd, 0xd5, 0x48, 0x3e, + 0x26, 0x47, 0x2c, 0x35, 0xdb, 0x8f, 0xe1, 0xc7, 0xb1, 0xfd, 0xb8, 0x05, 0x63, 0x5b, 0x3e, 0xdd, + 0xec, 0xb6, 0xdb, 0xb4, 0x89, 0xd3, 0x2a, 0xcf, 0x6f, 0x65, 0x5d, 0x9f, 0x5a, 0x01, 0x42, 0xd5, + 0x0f, 0x08, 0x51, 0xd5, 0x01, 0x1e, 0xed, 0x33, 0xc0, 0x37, 0x21, 0xbf, 0x41, 0xa9, 0x87, 0x7d, + 0x3a, 0x1e, 0x09, 0xdf, 0x1d, 0x4a, 0x3d, 0x8b, 0x75, 0xac, 0x66, 0x13, 0x22, 0x10, 0x35, 0x43, + 0x92, 0x89, 0x01, 0x0d, 0x49, 0xc8, 0x8b, 0x30, 0xd1, 0xe9, 0xee, 0x34, 0x9d, 0x3a, 0xf2, 0x15, + 0x16, 0x28, 0xe6, 0x38, 0x87, 0x31, 0xb6, 0x3e, 0xf9, 0x22, 0x4c, 0xe2, 0x6d, 0x34, 0x9c, 0x72, + 0x53, 0xda, 0xfb, 0xab, 0x56, 0xc6, 0x65, 0xd2, 0x3a, 0x03, 0x59, 0x29, 0x86, 0x52, 0x3a, 0x23, + 0x72, 0x17, 0x46, 0xf7, 0x9c, 0xc0, 0xda, 0xef, 0xee, 0xcc, 0x4f, 0x6b, 0x56, 0x46, 0x77, 0x9c, + 0x60, 0xa5, 0xbb, 0xc3, 0x87, 0x3c, 0x64, 0x8d, 0x3b, 0xde, 0x9e, 0x13, 0xec, 0x77, 0x55, 0xe5, + 0xf9, 0xc8, 0x1e, 0xe2, 0x2e, 0xd4, 0x60, 0x4a, 0x9f, 0x15, 0x4f, 0xe0, 0x49, 0x37, 0x34, 0xb0, + 0xc9, 0x17, 0xc6, 0xee, 0x0e, 0xe5, 0xa1, 0x30, 0xce, 0x4d, 0x6b, 0x4c, 0xd8, 0x08, 0xfb, 0xc7, + 0x24, 0xf7, 0xba, 0x3b, 0xd4, 0x6b, 0xd3, 0x80, 0xfa, 0xe2, 0xea, 0xe7, 0x9b, 0x43, 0xa5, 0x4e, + 0xc7, 0x37, 0xfe, 0xd3, 0x2c, 0x8c, 0x96, 0xb6, 0x6b, 0xd5, 0xf6, 0xae, 0x8b, 0x0f, 0xb3, 0xe1, + 0x7b, 0x9c, 0xfa, 0x30, 0x1b, 0xbe, 0xc7, 0xa9, 0xaf, 0x70, 0xd7, 0x53, 0x2e, 0xef, 0x68, 0xbb, + 0xad, 0x5c, 0xde, 0x35, 0xb5, 0x43, 0xf4, 0x34, 0x99, 0x1b, 0xe0, 0x69, 0x32, 0xd4, 0x1e, 0x0f, + 0x9d, 0xae, 0x3d, 0x7e, 0x1b, 0xc6, 0xab, 0xed, 0x80, 0xee, 0x79, 0xd1, 0xaa, 0x09, 0x15, 0x09, + 0x21, 0x58, 0xbd, 0xd0, 0x29, 0xd8, 0x6c, 0x4a, 0x72, 0x8d, 0x75, 0xa8, 0xa9, 0xc6, 0x29, 0xc9, + 0x15, 0xdb, 0x31, 0x2d, 0x90, 0x44, 0x34, 0x2a, 0xb1, 0xf9, 0x26, 0xcd, 0x3f, 0xb8, 0x08, 0x35, + 0x15, 0x3d, 0xd9, 0xb0, 0x8e, 0x5d, 0x9a, 0x49, 0x37, 0xff, 0x30, 0xfe, 0x46, 0x06, 0xe6, 0xd2, + 0xa6, 0x11, 0x79, 0x0f, 0x26, 0x5c, 0x6f, 0xcf, 0x6e, 0x3b, 0xdf, 0xcd, 0x5b, 0xa4, 0xa8, 0x2a, + 0x55, 0xb8, 0xaa, 0xa0, 0x51, 0xe1, 0xac, 0x43, 0x94, 0x96, 0xeb, 0x9a, 0x95, 0xd4, 0x0e, 0x51, + 0xc0, 0xc6, 0x8f, 0x64, 0x61, 0xbc, 0xd4, 0xe9, 0x3c, 0xe5, 0xa6, 0x81, 0x9f, 0xd3, 0x0e, 0x10, + 0x79, 0x2f, 0x0f, 0xdb, 0x35, 0x90, 0x55, 0xe0, 0xaf, 0x65, 0x61, 0x3a, 0x46, 0xa1, 0x7e, 0x7d, + 0x66, 0x40, 0x83, 0xc0, 0xec, 0x80, 0x06, 0x81, 0xb9, 0xc1, 0x0c, 0x02, 0x87, 0x1e, 0xe7, 0x50, + 0x78, 0x15, 0x72, 0xa5, 0x4e, 0x27, 0x6e, 0x58, 0xd0, 0xe9, 0x3c, 0xb8, 0xc9, 0x75, 0x2b, 0x76, + 0xa7, 0x63, 0x32, 0x0c, 0x6d, 0xa7, 0x1e, 0x19, 0x70, 0xa7, 0x36, 0xde, 0x80, 0x31, 0xe4, 0x85, + 0x66, 0x78, 0x97, 0x00, 0xb7, 0x18, 0x61, 0x81, 0xa7, 0xd5, 0x25, 0x36, 0x9f, 0xff, 0x3f, 0x03, + 0xc3, 0xf8, 0xfb, 0x29, 0x9d, 0x63, 0x8b, 0xda, 0x1c, 0x2b, 0x28, 0x73, 0x6c, 0x90, 0xd9, 0xf5, + 0xf7, 0x73, 0x00, 0xe5, 0xfb, 0x66, 0x8d, 0xab, 0xe0, 0xc8, 0x6d, 0x98, 0xb6, 0x9b, 0x4d, 0xf7, + 0x90, 0x36, 0x2c, 0xd7, 0x73, 0xf6, 0x9c, 0x36, 0xef, 0x39, 0xf9, 0xda, 0xad, 0x17, 0xa9, 0x6f, + 0x60, 0xa2, 0xe8, 0x3e, 0x2f, 0x51, 0xf9, 0xb4, 0x68, 0xb0, 0xef, 0x36, 0xa4, 0x32, 0x41, 0xe3, + 0x23, 0x8a, 0x52, 0xf8, 0xac, 0xf1, 0x12, 0x95, 0xcf, 0x3e, 0x2a, 0x47, 0xa4, 0x84, 0xac, 0xf1, + 0x11, 0x45, 0x29, 0x7c, 0xb8, 0x46, 0xc5, 0x27, 0xab, 0x30, 0x83, 0x10, 0xab, 0xee, 0xd1, 0x06, + 0x6d, 0x07, 0x8e, 0xdd, 0xf4, 0x85, 0xfa, 0x09, 0x15, 0x95, 0x89, 0x42, 0xf5, 0xfa, 0x8d, 0x85, + 0xe5, 0xa8, 0x8c, 0x5c, 0x83, 0xd1, 0x96, 0xfd, 0xc8, 0xb2, 0xf7, 0xb8, 0xdd, 0xc7, 0x24, 0x57, + 0x57, 0x08, 0x90, 0x7a, 0x8c, 0xb4, 0xec, 0x47, 0xa5, 0x3d, 0xca, 0x5a, 0x41, 0x1f, 0x75, 0x5c, + 0x5f, 0x69, 0xc5, 0x48, 0xd4, 0x8a, 0x58, 0x91, 0xda, 0x0a, 0x51, 0x24, 0x5a, 0x61, 0xfc, 0x6a, + 0x06, 0x9e, 0xad, 0xe2, 0x57, 0x04, 0x47, 0x65, 0xda, 0x0e, 0xa8, 0xb7, 0x41, 0xbd, 0x96, 0x83, + 0xaf, 0xe0, 0x35, 0x1a, 0x90, 0x97, 0x20, 0x57, 0x32, 0xd7, 0xc5, 0xfc, 0xe5, 0xfb, 0xbd, 0x66, + 0x93, 0xc0, 0x4a, 0x43, 0x8d, 0x56, 0xf6, 0x14, 0x55, 0x75, 0x09, 0x26, 0x4a, 0xbe, 0xef, 0xec, + 0xb5, 0x5b, 0xdc, 0x9f, 0x22, 0xa7, 0x59, 0x3d, 0x08, 0x78, 0xe2, 0x8d, 0x45, 0x25, 0x31, 0xfe, + 0xb3, 0x0c, 0xcc, 0x94, 0x3a, 0x1d, 0xfd, 0x93, 0x75, 0x8b, 0x9b, 0xcc, 0xe0, 0x16, 0x37, 0x0e, + 0x4c, 0x69, 0xcd, 0xe5, 0x53, 0x2a, 0x12, 0x7c, 0xfb, 0xf4, 0x0c, 0xff, 0xec, 0x4e, 0x08, 0xb2, + 0x7c, 0xfd, 0xb9, 0x38, 0xc6, 0xd8, 0xf8, 0x0f, 0x46, 0x71, 0x0f, 0x11, 0xbb, 0xad, 0xb0, 0x09, + 0xcd, 0xa4, 0xd8, 0x84, 0xbe, 0x05, 0x8a, 0x84, 0xa3, 0x1e, 0x71, 0x8a, 0xac, 0xa8, 0xea, 0x82, + 0x22, 0x64, 0x72, 0x10, 0xb7, 0x0e, 0xcd, 0x61, 0x6b, 0x5e, 0x8a, 0x2f, 0xe0, 0x27, 0x62, 0x18, + 0xba, 0x02, 0xa4, 0xda, 0xc6, 0x27, 0x6c, 0x5a, 0x3b, 0x70, 0x3a, 0x0f, 0xa8, 0xe7, 0xec, 0x1e, + 0x89, 0x05, 0x80, 0x9d, 0xef, 0x88, 0x52, 0xcb, 0x3f, 0x70, 0x3a, 0xd6, 0x43, 0x2c, 0x37, 0x53, + 0x68, 0xc8, 0xfb, 0x30, 0x6a, 0xd2, 0x43, 0xcf, 0x09, 0xa4, 0xcd, 0xd3, 0x54, 0xa8, 0xda, 0x44, + 0x28, 0x5f, 0x0b, 0x1e, 0xff, 0xa1, 0xee, 0x8a, 0xa2, 0x9c, 0x2c, 0x72, 0x21, 0x85, 0xdb, 0x36, + 0x4d, 0x46, 0xad, 0x2d, 0x6d, 0xd7, 0x7a, 0xc9, 0x28, 0xe4, 0x2a, 0x0c, 0xa3, 0xa4, 0x23, 0xee, + 0x02, 0xe8, 0x2b, 0x84, 0xb2, 0xb3, 0x2a, 0x86, 0x21, 0x06, 0x79, 0x01, 0x20, 0x7c, 0x23, 0xf6, + 0xe7, 0xf3, 0x28, 0xa5, 0x2b, 0x90, 0xb8, 0x98, 0x36, 0x76, 0x26, 0x31, 0x6d, 0x15, 0x0a, 0x26, + 0x77, 0x3b, 0x6c, 0x94, 0x3a, 0xf8, 0x10, 0xe9, 0xcf, 0x03, 0xae, 0xe4, 0x4b, 0x27, 0xc7, 0xc5, + 0xe7, 0x84, 0x4b, 0x62, 0xc3, 0xb2, 0x3b, 0xfc, 0xfd, 0x52, 0xdb, 0x46, 0xe2, 0x94, 0xe4, 0x2d, + 0x18, 0x62, 0x5b, 0xaf, 0xb0, 0x23, 0x95, 0x0f, 0x3a, 0xd1, 0x6e, 0xcc, 0x17, 0x67, 0xdd, 0xd5, + 0xf6, 0x04, 0x24, 0x21, 0x16, 0x4c, 0xe9, 0xd3, 0x5d, 0x98, 0x14, 0xcd, 0x47, 0xfd, 0xa9, 0x97, + 0x8b, 0x57, 0x1e, 0x01, 0xb3, 0xea, 0x08, 0x54, 0x57, 0x40, 0x6c, 0x91, 0x2e, 0x43, 0x7e, 0xb3, + 0xbc, 0xb1, 0xe1, 0x7a, 0x01, 0xbf, 0xea, 0x44, 0x27, 0x0b, 0x83, 0x99, 0x76, 0x7b, 0x8f, 0xf2, + 0xb3, 0x38, 0xa8, 0x77, 0xac, 0x0e, 0x43, 0x53, 0xcf, 0x62, 0x49, 0xfa, 0xc9, 0xd9, 0x90, 0xfe, + 0x5a, 0x16, 0x5e, 0x0a, 0xa5, 0xa2, 0xfb, 0x5e, 0xad, 0xb4, 0xb6, 0x5a, 0x6d, 0x6c, 0x08, 0x35, + 0xc9, 0x86, 0xe7, 0x3e, 0x74, 0x1a, 0xd4, 0x7b, 0x70, 0xe3, 0x94, 0x33, 0x7d, 0x95, 0x2f, 0x73, + 0xfe, 0x1a, 0x96, 0xd5, 0xac, 0xed, 0x14, 0xe1, 0x53, 0x6c, 0x4f, 0x9d, 0x4e, 0xe2, 0x71, 0x6c, + 0xe5, 0x19, 0x33, 0x62, 0x40, 0x7e, 0x20, 0x03, 0xe7, 0xd3, 0x3f, 0x44, 0xa8, 0xce, 0x8a, 0xf2, + 0x8a, 0xde, 0xe3, 0x6b, 0x97, 0x5e, 0x3d, 0x39, 0x2e, 0xbe, 0xe4, 0xdb, 0xad, 0xa6, 0xe5, 0x34, + 0x78, 0x6d, 0x4e, 0x9d, 0x5a, 0x1d, 0x81, 0xa0, 0xd5, 0xdb, 0xa3, 0xa6, 0xcf, 0x83, 0x3c, 0xda, + 0xe7, 0x33, 0x4b, 0x00, 0x79, 0xf9, 0xe0, 0x60, 0xfc, 0x66, 0x06, 0x94, 0x25, 0x98, 0x37, 0x69, + 0xc3, 0xf1, 0x68, 0x3d, 0x10, 0xc7, 0xbb, 0xf0, 0x15, 0xe4, 0xb0, 0x98, 0x71, 0x25, 0xc2, 0xc8, + 0x7b, 0x30, 0x2a, 0x8e, 0x21, 0xb1, 0xed, 0xca, 0xa5, 0x2b, 0x9e, 0x32, 0xb8, 0x53, 0x69, 0xe2, + 0x08, 0x93, 0x44, 0x6c, 0xd7, 0xbf, 0xbb, 0xbd, 0x59, 0x6e, 0xda, 0x4e, 0xcb, 0x17, 0x67, 0x09, + 0x76, 0xeb, 0x47, 0x87, 0x81, 0x55, 0x47, 0xa8, 0xba, 0xeb, 0x87, 0xa8, 0xc6, 0x1d, 0xf9, 0x92, + 0x72, 0x8a, 0x85, 0x70, 0x11, 0x86, 0x1f, 0x44, 0x7a, 0xba, 0xa5, 0xb1, 0x93, 0xe3, 0x22, 0x9f, + 0x2e, 0x26, 0x87, 0x1b, 0x14, 0xc6, 0xc2, 0xa9, 0xcb, 0x78, 0xb1, 0x1f, 0xc8, 0x6b, 0x92, 0xf3, + 0x62, 0x93, 0xd8, 0x44, 0x28, 0x13, 0xf5, 0x96, 0xdb, 0x0d, 0x44, 0xc8, 0x22, 0x02, 0x76, 0x0f, + 0x6d, 0x37, 0x70, 0xa6, 0xab, 0xad, 0x13, 0x68, 0x8a, 0x40, 0xf5, 0x63, 0x19, 0x98, 0xd2, 0xa7, + 0x2d, 0xb9, 0x06, 0x23, 0xc2, 0x1d, 0x30, 0x83, 0x6a, 0x4f, 0xc6, 0x6d, 0x84, 0x3b, 0x02, 0x6a, + 0xee, 0x7f, 0x02, 0x8b, 0xc9, 0x8d, 0x82, 0x83, 0x10, 0x9a, 0x50, 0x6e, 0xac, 0x73, 0x90, 0x29, + 0xcb, 0x88, 0xc1, 0xae, 0xb2, 0x7e, 0xb7, 0x19, 0xa8, 0xef, 0x96, 0x1e, 0x42, 0x4c, 0x51, 0x62, + 0x94, 0x61, 0x84, 0x6f, 0xad, 0x31, 0x03, 0xc8, 0xcc, 0x19, 0x0c, 0x20, 0x8d, 0xe3, 0x0c, 0x40, + 0xad, 0xb6, 0x72, 0x8f, 0x1e, 0x6d, 0xd8, 0x0e, 0x9e, 0xdf, 0xfc, 0x18, 0xbb, 0x27, 0xd6, 0xf0, + 0x84, 0x78, 0x68, 0xe7, 0x47, 0xde, 0x01, 0x3d, 0xd2, 0x1e, 0xda, 0x25, 0x2a, 0x9e, 0x95, 0x9e, + 0xf3, 0xd0, 0x0e, 0x28, 0x23, 0xcc, 0x22, 0x21, 0x3f, 0x2b, 0x39, 0x34, 0x46, 0xa9, 0x20, 0x93, + 0xaf, 0xc0, 0x54, 0xf4, 0x2b, 0x34, 0x17, 0x98, 0x0a, 0xf7, 0x09, 0xbd, 0x70, 0xe9, 0x85, 0x93, + 0xe3, 0xe2, 0x82, 0xc2, 0x35, 0x6e, 0x48, 0x10, 0x63, 0x66, 0xfc, 0x72, 0x06, 0x8d, 0x64, 0x64, + 0x03, 0x2f, 0xc3, 0x50, 0x68, 0xd6, 0x3d, 0x21, 0x36, 0x61, 0xfd, 0x49, 0x14, 0xcb, 0x99, 0xb8, + 0x15, 0xb5, 0x04, 0x8f, 0x2e, 0xbd, 0x05, 0xac, 0x94, 0xdc, 0x81, 0xd1, 0x81, 0xbe, 0x19, 0xa7, + 0x58, 0xca, 0xb7, 0x4a, 0x6a, 0x1c, 0x85, 0xbb, 0xdb, 0x9b, 0xdf, 0xb9, 0xa3, 0xf0, 0x93, 0x59, + 0x98, 0x66, 0xfd, 0x5a, 0xea, 0x06, 0xfb, 0xae, 0xe7, 0x04, 0x47, 0x4f, 0xad, 0xde, 0xf8, 0x1d, + 0xed, 0x4a, 0xb6, 0x20, 0x0f, 0x33, 0xb5, 0x6d, 0x03, 0xa9, 0x8f, 0xff, 0xe9, 0x30, 0xcc, 0xa6, + 0x50, 0x91, 0xd7, 0xb5, 0xa7, 0x9d, 0x79, 0xe9, 0xee, 0xff, 0xed, 0xe3, 0xe2, 0x84, 0x44, 0xdf, + 0x8c, 0xdc, 0xff, 0x17, 0x75, 0x8b, 0x33, 0xde, 0x53, 0xf8, 0xd2, 0xa3, 0x5a, 0x9c, 0xe9, 0x76, + 0x66, 0x57, 0x61, 0xd8, 0x74, 0x9b, 0x54, 0x5a, 0x59, 0xa2, 0xc0, 0xe5, 0x31, 0x80, 0x66, 0x55, + 0xc2, 0x00, 0x64, 0x05, 0x46, 0xd9, 0x1f, 0x6b, 0x76, 0x47, 0xbc, 0x97, 0x92, 0x50, 0x29, 0x80, + 0xd0, 0x8e, 0xd3, 0xde, 0x53, 0xf5, 0x02, 0x4d, 0x6a, 0xb5, 0xec, 0x8e, 0x26, 0x19, 0x72, 0x44, + 0x4d, 0xbf, 0x90, 0xef, 0xad, 0x5f, 0xc8, 0x9c, 0xaa, 0x5f, 0xd8, 0x05, 0xa8, 0x39, 0x7b, 0x6d, + 0xa7, 0xbd, 0x57, 0x6a, 0xee, 0x89, 0xa0, 0x09, 0x57, 0x7b, 0x8f, 0xc2, 0xb5, 0x08, 0x19, 0x27, + 0xee, 0xb3, 0x68, 0xd4, 0xc0, 0x61, 0x96, 0xdd, 0xdc, 0xd3, 0x9c, 0xbb, 0x14, 0xce, 0x64, 0x1d, + 0xa0, 0x54, 0x0f, 0x9c, 0x87, 0x6c, 0x0a, 0xfb, 0x42, 0x8c, 0x93, 0x9f, 0x5c, 0x2e, 0xdd, 0xa3, + 0x47, 0x78, 0xf5, 0x90, 0xcf, 0xc3, 0x36, 0xa2, 0xb2, 0x95, 0xa0, 0x79, 0xee, 0x44, 0x1c, 0x48, + 0x07, 0xce, 0x95, 0x1a, 0x0d, 0x87, 0xb5, 0xc1, 0x6e, 0x6e, 0xf2, 0x70, 0x17, 0xc8, 0x7a, 0x22, + 0x9d, 0xf5, 0x55, 0xf9, 0x12, 0x6a, 0x87, 0x54, 0x96, 0x8c, 0x92, 0x11, 0xab, 0x26, 0x9d, 0xb1, + 0x51, 0x83, 0x29, 0xbd, 0xf1, 0x7a, 0xb0, 0x87, 0x09, 0xc8, 0x9b, 0xb5, 0x92, 0x55, 0x5b, 0x29, + 0xdd, 0x28, 0x64, 0x48, 0x01, 0x26, 0xc4, 0xaf, 0x45, 0x6b, 0xf1, 0xcd, 0x5b, 0x85, 0xac, 0x06, + 0x79, 0xf3, 0xc6, 0x62, 0x21, 0xb7, 0x90, 0x9d, 0xcf, 0xc4, 0xfc, 0x2c, 0x47, 0x0b, 0x79, 0xae, + 0x12, 0x36, 0x7e, 0x3d, 0x03, 0x79, 0xf9, 0xed, 0xe4, 0x16, 0xe4, 0x6a, 0xb5, 0x95, 0x98, 0x67, + 0x64, 0x74, 0xca, 0xf0, 0xfd, 0xd4, 0xf7, 0x55, 0xf3, 0x77, 0x46, 0xc0, 0xe8, 0x36, 0x57, 0x6b, + 0x42, 0x06, 0x91, 0x74, 0xd1, 0xe6, 0xcd, 0xe9, 0x52, 0xdc, 0xc5, 0x6e, 0x41, 0xee, 0xee, 0xf6, + 0xa6, 0xb8, 0x64, 0x49, 0xba, 0x68, 0x3f, 0xe5, 0x74, 0x1f, 0x1d, 0xaa, 0xbb, 0x3c, 0x23, 0x30, + 0x4c, 0x18, 0x57, 0x26, 0x32, 0x3f, 0x74, 0x5b, 0x6e, 0x18, 0xe1, 0x40, 0x1c, 0xba, 0x0c, 0x62, + 0x8a, 0x12, 0x26, 0x8a, 0xac, 0xba, 0x75, 0xbb, 0x29, 0x4e, 0x6f, 0x14, 0x45, 0x9a, 0x0c, 0x60, + 0x72, 0xb8, 0xf1, 0x3b, 0x19, 0x28, 0xa0, 0xc0, 0x86, 0xe6, 0xeb, 0xee, 0x01, 0x6d, 0x3f, 0xb8, + 0x41, 0xde, 0x90, 0x4b, 0x2e, 0x13, 0x2a, 0xba, 0x86, 0x71, 0xc9, 0xc5, 0xde, 0x02, 0xc5, 0xb2, + 0x53, 0x82, 0x48, 0x64, 0x07, 0x77, 0x3e, 0x3f, 0x25, 0x88, 0x44, 0x11, 0x86, 0xf1, 0x73, 0xc4, + 0xe6, 0x88, 0x5f, 0x1e, 0x30, 0x80, 0xc9, 0xe1, 0xca, 0xde, 0xf4, 0xd3, 0xd9, 0x44, 0x1b, 0x16, + 0xbf, 0xa3, 0x1c, 0xb8, 0xf5, 0xc6, 0x0d, 0xb4, 0x5f, 0x7f, 0x08, 0x73, 0xf1, 0x2e, 0x41, 0x25, + 0x64, 0x09, 0xa6, 0x75, 0xb8, 0xd4, 0x47, 0x5e, 0x48, 0xad, 0xeb, 0xc1, 0xa2, 0x19, 0xc7, 0x37, + 0xfe, 0x8f, 0x0c, 0x8c, 0xe1, 0x9f, 0x66, 0xb7, 0x89, 0x66, 0x84, 0xa5, 0xed, 0x9a, 0x50, 0x8d, + 0xa8, 0xc2, 0x9c, 0x7d, 0xe8, 0x5b, 0x42, 0x8f, 0xa2, 0xed, 0x31, 0x21, 0xb2, 0x20, 0xe5, 0xef, + 0x1b, 0x52, 0x29, 0x17, 0x92, 0xf2, 0x87, 0x10, 0x3f, 0x46, 0x2a, 0x90, 0xd1, 0xf8, 0x78, 0xbb, + 0xc6, 0xa6, 0x9f, 0x6a, 0xd7, 0x83, 0x74, 0x6e, 0x53, 0x37, 0x3e, 0xe6, 0x68, 0x68, 0xd6, 0xb3, + 0x5d, 0x2b, 0x99, 0xeb, 0x9a, 0x59, 0x0f, 0xfb, 0x46, 0x4d, 0x2f, 0x25, 0x90, 0x8c, 0x5f, 0x18, + 0x8f, 0x77, 0xa0, 0x38, 0xf0, 0xce, 0xb8, 0x36, 0xde, 0x86, 0xe1, 0x52, 0xb3, 0xe9, 0x1e, 0x8a, + 0x5d, 0x42, 0xde, 0x5c, 0xc3, 0xfe, 0xe3, 0xe7, 0x19, 0xaa, 0xf5, 0x34, 0xa7, 0x54, 0x06, 0x20, + 0x65, 0x18, 0x2b, 0x6d, 0xd7, 0xaa, 0xd5, 0xca, 0xe6, 0x26, 0x77, 0xc0, 0xcb, 0x2d, 0xbd, 0x22, + 0xfb, 0xc7, 0x71, 0x1a, 0x56, 0xdc, 0x5e, 0x21, 0x92, 0xdf, 0x23, 0x3a, 0xf2, 0x2e, 0xc0, 0x5d, + 0xd7, 0x69, 0x73, 0x35, 0xa6, 0x68, 0x3c, 0xbb, 0x81, 0x8f, 0x7f, 0xe4, 0x3a, 0x6d, 0xa1, 0xf7, + 0x64, 0xdf, 0x1e, 0x21, 0x99, 0xca, 0xdf, 0xac, 0xa7, 0x97, 0x5c, 0x6e, 0x1a, 0x38, 0x1c, 0xf5, + 0xf4, 0x8e, 0x9b, 0xd0, 0xb7, 0x49, 0x34, 0xd2, 0x82, 0xe9, 0x5a, 0x77, 0x6f, 0x8f, 0xb2, 0x9d, + 0x5d, 0xe8, 0x93, 0x46, 0xc4, 0x55, 0x3a, 0x0c, 0x7b, 0xc4, 0xef, 0x23, 0xec, 0x32, 0xe4, 0x2f, + 0xbd, 0xce, 0x26, 0xf2, 0xb7, 0x8e, 0x8b, 0xc2, 0x0e, 0x82, 0x89, 0x6a, 0xbe, 0xa4, 0x4f, 0x6a, + 0x93, 0xe2, 0xbc, 0xc9, 0x7d, 0x18, 0xe1, 0x6f, 0x46, 0xc2, 0xa1, 0xec, 0xc5, 0x3e, 0x8b, 0x86, + 0x23, 0xf6, 0x7a, 0x95, 0xe4, 0xa5, 0x64, 0x1b, 0xf2, 0x65, 0xc7, 0xab, 0x37, 0x69, 0xb9, 0x2a, + 0xce, 0xfe, 0x97, 0xfa, 0xb0, 0x94, 0xa8, 0xbc, 0x5f, 0xea, 0xf8, 0xab, 0xee, 0xa8, 0xb2, 0x80, + 0xc4, 0x20, 0x7f, 0x23, 0x03, 0xcf, 0x86, 0x5f, 0x5f, 0xda, 0xa3, 0xed, 0x60, 0xcd, 0x0e, 0xea, + 0xfb, 0xd4, 0x13, 0xbd, 0x34, 0xd6, 0xaf, 0x97, 0x3e, 0x9f, 0xe8, 0xa5, 0x2b, 0x51, 0x2f, 0xd9, + 0x8c, 0x99, 0xd5, 0xe2, 0xdc, 0x92, 0x7d, 0xd6, 0xaf, 0x56, 0x62, 0x01, 0x44, 0xaf, 0xa1, 0xc2, + 0x21, 0xf9, 0x95, 0x3e, 0x0d, 0x8e, 0x90, 0x85, 0x23, 0x51, 0xf8, 0x5b, 0xb3, 0x84, 0x0d, 0xa1, + 0xe4, 0x9e, 0xf4, 0xde, 0xe4, 0x52, 0xc9, 0xa5, 0x3e, 0xbc, 0xb9, 0x47, 0xe7, 0x6c, 0x1f, 0x3f, + 0x6d, 0x3e, 0xda, 0xab, 0xf6, 0x8e, 0x10, 0x44, 0x4e, 0x19, 0xed, 0x55, 0x3b, 0x1a, 0xed, 0xa6, + 0x1d, 0x1f, 0xed, 0x55, 0x7b, 0x87, 0x94, 0xb9, 0xcb, 0x39, 0xf7, 0x4f, 0x7e, 0xa1, 0x1f, 0xb7, + 0xf2, 0x06, 0x3f, 0x99, 0x53, 0x5c, 0xcf, 0xbf, 0x04, 0x63, 0xb5, 0x8e, 0x5d, 0xa7, 0x4d, 0x67, + 0x37, 0x10, 0x4f, 0xed, 0x2f, 0xf7, 0x61, 0x15, 0xe2, 0x8a, 0xa7, 0x55, 0xf9, 0x53, 0xbd, 0x26, + 0x85, 0x38, 0xec, 0x0b, 0x37, 0x37, 0xd6, 0xc4, 0x6b, 0x7b, 0xbf, 0x2f, 0xdc, 0xdc, 0x58, 0x13, + 0x32, 0x47, 0xa7, 0xa5, 0xc9, 0x1c, 0x1b, 0x6b, 0xa4, 0x03, 0x53, 0x9b, 0xd4, 0xf3, 0xec, 0x5d, + 0xd7, 0x6b, 0x71, 0xfd, 0x25, 0xf7, 0x79, 0xbb, 0xda, 0x8f, 0x9f, 0x46, 0xc0, 0xd5, 0x76, 0x81, + 0x84, 0x59, 0x71, 0xa5, 0x67, 0x8c, 0x3f, 0xeb, 0x93, 0x25, 0x27, 0xd8, 0xe9, 0xd6, 0x0f, 0x68, + 0x30, 0x3f, 0x73, 0x6a, 0x9f, 0x84, 0xb8, 0xbc, 0x4f, 0x76, 0xe4, 0x4f, 0xb5, 0x4f, 0x42, 0x1c, + 0xe3, 0x1f, 0xe5, 0xe0, 0x42, 0x8f, 0x2e, 0x20, 0xeb, 0x72, 0xcb, 0xcd, 0x68, 0x5a, 0xec, 0x1e, + 0xe8, 0xd7, 0x4e, 0xdd, 0x85, 0x57, 0xa1, 0xb0, 0x7c, 0x0f, 0x65, 0x75, 0xfe, 0x90, 0x53, 0x2e, + 0xc9, 0xc3, 0x0a, 0x35, 0xad, 0xf4, 0x00, 0x6d, 0x84, 0xe5, 0x03, 0x50, 0x5d, 0x73, 0x86, 0x4f, + 0x50, 0x2e, 0x7c, 0x7f, 0x16, 0x86, 0xf0, 0xe0, 0x8c, 0x85, 0x00, 0xcb, 0x9c, 0x29, 0x04, 0xd8, + 0x17, 0x60, 0x62, 0xf9, 0x1e, 0xbf, 0x49, 0xaf, 0xd8, 0xfe, 0xbe, 0xd8, 0xd6, 0xd1, 0x90, 0x83, + 0x1e, 0x58, 0xe2, 0xe2, 0xbd, 0x6f, 0x6b, 0x32, 0xab, 0x46, 0x41, 0xb6, 0x60, 0x96, 0x7f, 0x9b, + 0xb3, 0xeb, 0xd4, 0x79, 0x24, 0x21, 0xc7, 0x6e, 0x8a, 0x3d, 0xfe, 0xa5, 0x93, 0xe3, 0x62, 0x91, + 0x1e, 0xa0, 0xf5, 0xb3, 0x28, 0xb7, 0x7c, 0x44, 0x50, 0xcd, 0xa0, 0x53, 0xe8, 0xd5, 0xf0, 0x26, + 0xe6, 0x18, 0x56, 0xc8, 0x6a, 0x63, 0x75, 0x33, 0x5c, 0x8e, 0x64, 0xfc, 0xe9, 0x30, 0x2c, 0xf4, + 0xde, 0x9e, 0xc9, 0x07, 0xfa, 0x00, 0x5e, 0x3e, 0x75, 0x43, 0x3f, 0x7d, 0x0c, 0xbf, 0x08, 0x73, + 0xcb, 0xed, 0x80, 0x7a, 0x1d, 0xcf, 0x91, 0x01, 0x6d, 0x56, 0x5c, 0x5f, 0x5a, 0x9b, 0xa3, 0xd9, + 0x37, 0x0d, 0xcb, 0x85, 0x6e, 0x15, 0x6d, 0xdf, 0x15, 0x56, 0xa9, 0x1c, 0xc8, 0x32, 0x4c, 0x29, + 0xf0, 0x66, 0x77, 0x4f, 0x7d, 0x9d, 0x52, 0x79, 0x36, 0xbb, 0xaa, 0x29, 0x6e, 0x8c, 0x08, 0x2d, + 0xda, 0xd9, 0x95, 0xb1, 0x7e, 0x77, 0xfb, 0x5e, 0x4d, 0x0c, 0x27, 0xb7, 0x68, 0x47, 0xa8, 0xf5, + 0xd1, 0xe1, 0x81, 0xb6, 0xbf, 0x46, 0xc8, 0x0b, 0xbf, 0x9c, 0x13, 0x33, 0xea, 0x25, 0xc8, 0xd5, + 0xba, 0x3b, 0xea, 0x9b, 0x9b, 0xaf, 0x1d, 0x70, 0xac, 0x94, 0x7c, 0x0e, 0xc0, 0xa4, 0x1d, 0xd7, + 0x77, 0x02, 0xd7, 0x3b, 0x52, 0x5d, 0x2a, 0xbd, 0x10, 0xaa, 0x7b, 0x7d, 0x48, 0x28, 0x59, 0x81, + 0xe9, 0xe8, 0xd7, 0xfd, 0xc3, 0xb6, 0xd0, 0x25, 0x8f, 0x71, 0xed, 0x4a, 0x44, 0x6e, 0xb9, 0xac, + 0x4c, 0x3d, 0xb2, 0x63, 0x64, 0x64, 0x11, 0xf2, 0xdb, 0xae, 0x77, 0xb0, 0xcb, 0xc6, 0x78, 0x28, + 0x12, 0x2a, 0x0e, 0x05, 0x4c, 0x3d, 0x3c, 0x25, 0x1e, 0x5b, 0x2e, 0xcb, 0xed, 0x87, 0x8e, 0xe7, + 0xe2, 0x8b, 0x9e, 0x6a, 0xd3, 0x42, 0x23, 0xb0, 0xe6, 0xcc, 0x1e, 0x81, 0xc9, 0x55, 0x18, 0x2e, + 0xd5, 0x03, 0xd7, 0x13, 0x06, 0x2d, 0x7c, 0xa6, 0x30, 0x80, 0x36, 0x53, 0x18, 0x80, 0x75, 0xa2, + 0x49, 0x77, 0xc5, 0xeb, 0x0e, 0x76, 0xa2, 0x47, 0x77, 0x35, 0x4f, 0x7d, 0xba, 0xcb, 0x84, 0x22, + 0x93, 0xee, 0xa2, 0xe2, 0x43, 0x0b, 0x70, 0xb7, 0x9b, 0x50, 0x99, 0x09, 0x34, 0xe3, 0xf7, 0xc6, + 0x7a, 0x4e, 0x79, 0x76, 0x0a, 0x9d, 0x6d, 0xca, 0xaf, 0xda, 0x03, 0x4c, 0xf9, 0xd7, 0x43, 0x5f, + 0x12, 0x35, 0x3c, 0x05, 0x42, 0xd4, 0x63, 0x90, 0xe3, 0x2c, 0xfc, 0x4a, 0xfe, 0x2c, 0x93, 0x48, + 0x74, 0x52, 0x76, 0xd0, 0x4e, 0xca, 0x0d, 0xd4, 0x49, 0x64, 0x09, 0x26, 0xc3, 0x10, 0x89, 0x1b, + 0x76, 0xa0, 0x6d, 0x6b, 0x61, 0x5c, 0x4b, 0xab, 0x63, 0x07, 0xea, 0xb6, 0xa6, 0x93, 0x90, 0x77, + 0x60, 0x5c, 0x38, 0x54, 0x21, 0x87, 0xe1, 0xc8, 0x52, 0x48, 0x7a, 0x5f, 0xc5, 0xe8, 0x55, 0x74, + 0xb6, 0x9a, 0x37, 0x9c, 0x0e, 0x6d, 0x3a, 0x6d, 0x5a, 0xc3, 0xc7, 0x0a, 0x31, 0x63, 0xf8, 0xa3, + 0xad, 0x28, 0xb1, 0xf8, 0x3b, 0x86, 0xa6, 0x3f, 0xd4, 0x88, 0xe2, 0x93, 0x75, 0xf4, 0x4c, 0x93, + 0x95, 0xdb, 0x29, 0x7a, 0xab, 0xee, 0x9e, 0x23, 0x6d, 0xe8, 0xa5, 0x9d, 0xa2, 0x67, 0x35, 0x19, + 0x34, 0x66, 0xa7, 0xc8, 0x51, 0xd9, 0x0d, 0x87, 0xfd, 0xa8, 0x56, 0xc4, 0x4b, 0x22, 0xde, 0x70, + 0x90, 0x48, 0x77, 0x5c, 0xe0, 0x48, 0xb2, 0x9a, 0xe5, 0x96, 0xed, 0x34, 0x45, 0x14, 0x82, 0xa8, + 0x1a, 0xca, 0xa0, 0xf1, 0x6a, 0x10, 0x95, 0xd4, 0x61, 0xc2, 0xa4, 0xbb, 0x1b, 0x9e, 0x1b, 0xd0, + 0x7a, 0x40, 0x1b, 0x42, 0xaa, 0x93, 0x17, 0x9b, 0x25, 0xd7, 0xe5, 0x12, 0x2b, 0xda, 0xc6, 0x67, + 0xbe, 0x75, 0x5c, 0x04, 0x06, 0xe2, 0x5e, 0x31, 0x27, 0xc7, 0xc5, 0x0b, 0x6c, 0xfc, 0x3b, 0x92, + 0x58, 0x3d, 0x9d, 0x54, 0xa6, 0xe4, 0x1b, 0x6c, 0xbf, 0x0e, 0xbb, 0x24, 0xaa, 0x6c, 0xa2, 0x47, + 0x65, 0x6f, 0xa6, 0x56, 0x56, 0x54, 0x7a, 0x3b, 0xb5, 0xd2, 0xd4, 0x4a, 0xc8, 0xbb, 0x30, 0x5e, + 0xae, 0x96, 0xdd, 0xf6, 0xae, 0xb3, 0x57, 0x5b, 0x29, 0xa1, 0x68, 0x28, 0x3c, 0xa2, 0xea, 0x8e, + 0x55, 0x47, 0xb8, 0xe5, 0xef, 0xdb, 0x9a, 0x63, 0x6c, 0x84, 0x4f, 0xee, 0xc0, 0x94, 0xfc, 0x69, + 0xd2, 0xdd, 0x2d, 0xb3, 0x8a, 0x12, 0xa1, 0x74, 0x43, 0x0b, 0x39, 0xb0, 0x8e, 0xe8, 0x7a, 0xea, + 0x4d, 0x21, 0x46, 0xc6, 0x26, 0x63, 0x85, 0x76, 0x9a, 0xee, 0x11, 0xfb, 0xbc, 0x4d, 0x87, 0x7a, + 0x28, 0x03, 0x8a, 0xc9, 0xd8, 0x08, 0x4b, 0xac, 0xc0, 0xd1, 0xdf, 0x4f, 0x75, 0x22, 0xb2, 0x0e, + 0x33, 0x62, 0x8a, 0x3f, 0x70, 0x7c, 0x67, 0xc7, 0x69, 0x3a, 0xc1, 0x11, 0x4a, 0x7f, 0x42, 0x80, + 0x91, 0xeb, 0xe2, 0x61, 0x58, 0xaa, 0x30, 0x4b, 0x92, 0x1a, 0xbf, 0x9e, 0x85, 0xe7, 0xfa, 0xdd, + 0x84, 0x48, 0x4d, 0xdf, 0xcc, 0xae, 0x0c, 0x70, 0x7b, 0x3a, 0x7d, 0x3b, 0x5b, 0x86, 0xa9, 0xfb, + 0x8a, 0x49, 0x5f, 0x68, 0x62, 0x89, 0x9d, 0xa1, 0x1a, 0xfb, 0xe9, 0xb3, 0x3d, 0x46, 0xb4, 0xf0, + 0x50, 0x6c, 0x73, 0x1f, 0xd7, 0x45, 0xf3, 0x16, 0x8c, 0x95, 0xdd, 0x76, 0x40, 0x1f, 0x05, 0xb1, + 0x80, 0x04, 0x1c, 0x18, 0x77, 0x4f, 0x95, 0xa8, 0xc6, 0xbf, 0xce, 0xc2, 0xf3, 0x7d, 0xaf, 0x02, + 0x64, 0x53, 0xef, 0xb5, 0xab, 0x83, 0xdc, 0x1f, 0x4e, 0xef, 0xb6, 0xc5, 0x84, 0xdd, 0xdd, 0xa9, + 0x1e, 0x50, 0x0b, 0xff, 0x43, 0x46, 0x74, 0xd2, 0xa7, 0x61, 0x14, 0xab, 0x0a, 0xbb, 0x88, 0x6b, + 0xc9, 0x70, 0x17, 0x76, 0x74, 0x2d, 0x19, 0x47, 0x23, 0x37, 0x21, 0x5f, 0xb6, 0x9b, 0x4d, 0x25, + 0x5c, 0x03, 0x4a, 0xf3, 0x75, 0x84, 0xc5, 0x8c, 0x47, 0x25, 0x22, 0x93, 0x7d, 0xf8, 0xdf, 0xca, + 0x59, 0x81, 0x9b, 0xa5, 0x20, 0x8b, 0x1d, 0x17, 0x0a, 0x32, 0x06, 0x79, 0xad, 0xbb, 0xa1, 0x43, + 0x38, 0x0f, 0xf2, 0xca, 0x00, 0x5a, 0x90, 0x57, 0x06, 0x30, 0x7e, 0x23, 0x07, 0x2f, 0xf4, 0xbf, + 0xcf, 0x92, 0x2d, 0x7d, 0x08, 0x5e, 0x1b, 0xe8, 0x16, 0x7c, 0xfa, 0x18, 0xc8, 0x90, 0xc9, 0xbc, + 0x43, 0xae, 0x24, 0xdd, 0x5f, 0xbe, 0x7d, 0x5c, 0x54, 0x2c, 0x92, 0xef, 0xba, 0x4e, 0x5b, 0x79, + 0x33, 0xf9, 0xba, 0x26, 0x19, 0xf2, 0xd7, 0xfb, 0x5b, 0x83, 0x7d, 0x59, 0x44, 0xc7, 0xf7, 0x95, + 0x41, 0x25, 0xca, 0xcf, 0x43, 0x21, 0x4e, 0x4a, 0x2e, 0xc3, 0x10, 0x7e, 0x80, 0xe2, 0xc3, 0x13, + 0xe3, 0x80, 0xe5, 0x0b, 0x6b, 0x62, 0xee, 0x60, 0x04, 0x0b, 0xb4, 0x07, 0xd0, 0x75, 0x83, 0x22, + 0x82, 0x05, 0x37, 0x27, 0x48, 0xea, 0x07, 0x63, 0x44, 0xc6, 0x9f, 0x67, 0xe0, 0x62, 0x4f, 0x4d, + 0x01, 0xd9, 0xd0, 0x07, 0xec, 0x95, 0xd3, 0x54, 0x0b, 0xa7, 0x8e, 0xd5, 0xc2, 0x8f, 0xcb, 0xb9, + 0xff, 0x1e, 0x4c, 0xd4, 0xba, 0x3b, 0xf1, 0xfb, 0x19, 0x8f, 0x2f, 0xa3, 0xc0, 0xd5, 0x13, 0x4c, + 0xc5, 0x67, 0xed, 0x97, 0x06, 0x0f, 0xc2, 0x00, 0x48, 0xb1, 0x3a, 0x0c, 0x5d, 0xac, 0x93, 0x11, + 0x3c, 0x74, 0x22, 0xe3, 0xd7, 0xb2, 0xe9, 0x17, 0xdd, 0x3b, 0xe5, 0x8d, 0xb3, 0x5c, 0x74, 0xef, + 0x94, 0x37, 0x4e, 0x6f, 0xfb, 0x3f, 0x96, 0x6d, 0xc7, 0x87, 0x59, 0xb1, 0xe3, 0x49, 0x45, 0xa7, + 0x78, 0x98, 0x95, 0xbb, 0xa3, 0xaf, 0x3f, 0xcc, 0x4a, 0x64, 0xf2, 0x26, 0x8c, 0xad, 0xba, 0x3c, + 0xb8, 0x86, 0x6c, 0x31, 0xf7, 0x41, 0x96, 0x40, 0x75, 0x7b, 0x0c, 0x31, 0xd9, 0xdd, 0x42, 0x1f, + 0x78, 0x69, 0x5c, 0x89, 0x77, 0x8b, 0xd8, 0x74, 0xd1, 0xd5, 0x81, 0x3a, 0x99, 0xf1, 0x9f, 0x0c, + 0x83, 0x71, 0xba, 0x32, 0x83, 0x7c, 0xa8, 0xf7, 0xdd, 0xb5, 0x81, 0xd5, 0x20, 0x03, 0x6d, 0xb9, + 0xa5, 0x6e, 0xc3, 0xa1, 0xed, 0xba, 0x1e, 0x19, 0x43, 0xc0, 0xd4, 0x2d, 0x50, 0xe2, 0x7d, 0x1c, + 0x47, 0xd5, 0x85, 0xff, 0x36, 0x17, 0x2d, 0xb5, 0xd8, 0xd1, 0x98, 0xf9, 0x18, 0x47, 0x23, 0xb9, + 0x07, 0x05, 0x15, 0xa2, 0xbc, 0xd0, 0xa2, 0xe4, 0xa2, 0x31, 0x8a, 0x7d, 0x54, 0x82, 0x50, 0x3f, + 0x5f, 0x73, 0x83, 0x9f, 0xaf, 0x91, 0xf8, 0x8e, 0xf5, 0x0f, 0x25, 0xc5, 0xf7, 0xb8, 0x33, 0xba, + 0x82, 0x2e, 0x23, 0x69, 0xf8, 0xe2, 0xd0, 0x1a, 0xd6, 0x23, 0x69, 0xa4, 0x1c, 0x5c, 0x2a, 0xba, + 0x0c, 0x06, 0x82, 0x3f, 0x15, 0x5f, 0xf8, 0x30, 0x18, 0x08, 0xa7, 0x4f, 0x0b, 0x06, 0x12, 0x92, + 0xb0, 0x03, 0xd0, 0xec, 0xb6, 0x79, 0x34, 0xf1, 0xd1, 0xe8, 0x00, 0xf4, 0xba, 0x6d, 0x2b, 0x1e, + 0x51, 0x3c, 0x44, 0x34, 0xfe, 0xe1, 0x50, 0xba, 0x70, 0x10, 0xea, 0xbb, 0xce, 0x22, 0x1c, 0x84, + 0x44, 0x9f, 0xcc, 0x4c, 0xdd, 0x82, 0x59, 0x69, 0x9f, 0x27, 0x0d, 0xbd, 0xb6, 0xcc, 0x55, 0x31, + 0xc4, 0xa8, 0x37, 0x0a, 0x2d, 0xfb, 0xa4, 0xb1, 0x98, 0xd5, 0xf5, 0x34, 0xbd, 0x51, 0x0a, 0xfd, + 0xc2, 0x6f, 0x4a, 0xb5, 0x98, 0x3a, 0x08, 0x5b, 0x5b, 0xe1, 0x5c, 0x8e, 0x0d, 0x42, 0xb7, 0xab, + 0x0d, 0xa3, 0x4e, 0xc2, 0xf7, 0x5e, 0xa9, 0x72, 0x40, 0x26, 0x8a, 0xac, 0xa8, 0x28, 0x2a, 0x62, + 0x5c, 0x62, 0x44, 0x64, 0x0f, 0x2e, 0x46, 0xa2, 0xb4, 0x72, 0x53, 0x40, 0x8e, 0xbc, 0xc1, 0x57, + 0x4f, 0x8e, 0x8b, 0xaf, 0x28, 0xa2, 0xb8, 0x7a, 0xe1, 0x88, 0x71, 0xef, 0xcd, 0x8b, 0xed, 0xb7, + 0x4b, 0x9e, 0xdd, 0xae, 0xef, 0x2b, 0x73, 0x1e, 0xf7, 0xdb, 0x1d, 0x84, 0x26, 0xc2, 0x19, 0x44, + 0xc8, 0xc6, 0x8f, 0x67, 0x61, 0x8a, 0x9f, 0xd5, 0xfc, 0x75, 0xee, 0xa9, 0x7d, 0xf9, 0x7c, 0x5b, + 0x7b, 0xf9, 0x94, 0x91, 0xf7, 0xd4, 0xa6, 0x0d, 0xf4, 0xee, 0xb9, 0x0f, 0x24, 0x49, 0x43, 0x4c, + 0x98, 0x50, 0xa1, 0xfd, 0x9f, 0x3c, 0x6f, 0x44, 0x41, 0x1a, 0x85, 0xa8, 0x84, 0xef, 0xce, 0xbe, + 0xa9, 0xf1, 0x30, 0x7e, 0x2c, 0x0b, 0x93, 0x8a, 0x9d, 0xca, 0x53, 0xdb, 0xf1, 0x9f, 0xd7, 0x3a, + 0x7e, 0x3e, 0xf4, 0x10, 0x0c, 0x5b, 0x36, 0x50, 0xbf, 0x77, 0x61, 0x26, 0x41, 0x12, 0x37, 0xf7, + 0xc9, 0x0c, 0x62, 0xee, 0xf3, 0x7a, 0x32, 0xe2, 0x1b, 0x4f, 0x9c, 0x10, 0xc6, 0xff, 0x51, 0x43, + 0xcc, 0xfd, 0x64, 0x16, 0xe6, 0xc4, 0x2f, 0x0c, 0x91, 0xca, 0x85, 0xd5, 0xa7, 0x76, 0x2c, 0x4a, + 0xda, 0x58, 0x14, 0xf5, 0xb1, 0x50, 0x1a, 0xd8, 0x7b, 0x48, 0x8c, 0x1f, 0x02, 0x98, 0xef, 0x45, + 0x30, 0xb0, 0x23, 0x7e, 0xe4, 0x9a, 0x98, 0x1d, 0xc0, 0x35, 0x71, 0x15, 0x0a, 0x58, 0x95, 0x08, + 0x82, 0xe8, 0x6f, 0x99, 0x55, 0xd1, 0x49, 0xa8, 0x5f, 0xe0, 0x71, 0x6c, 0x45, 0x50, 0x46, 0x3f, + 0xa6, 0xf3, 0x48, 0x50, 0x92, 0x5f, 0xce, 0xc0, 0x14, 0x02, 0x97, 0x1f, 0xd2, 0x76, 0x80, 0xcc, + 0x86, 0x84, 0xcf, 0x5a, 0xf8, 0x30, 0x5a, 0x0b, 0x3c, 0xa7, 0xbd, 0x27, 0x5e, 0x46, 0x77, 0xc4, + 0xcb, 0xe8, 0x3b, 0xfc, 0x45, 0xf7, 0x5a, 0xdd, 0x6d, 0x5d, 0xdf, 0xf3, 0xec, 0x87, 0x0e, 0x37, + 0xc1, 0xb2, 0x9b, 0xd7, 0xa3, 0x7c, 0x3f, 0x1d, 0x27, 0x96, 0x89, 0x47, 0xb0, 0xc2, 0x57, 0x67, + 0xfe, 0xa1, 0x14, 0xab, 0x8d, 0xab, 0x66, 0xf4, 0x2f, 0x22, 0xdf, 0x05, 0x17, 0x78, 0x68, 0x32, + 0x76, 0xc3, 0x77, 0xda, 0x5d, 0xb7, 0xeb, 0x2f, 0xd9, 0xf5, 0x03, 0x26, 0xe6, 0x73, 0xcf, 0x62, + 0x6c, 0x79, 0x3d, 0x2c, 0xb4, 0x76, 0x78, 0xa9, 0x16, 0xf3, 0x22, 0x9d, 0x01, 0x59, 0x81, 0x19, + 0x5e, 0x54, 0xea, 0x06, 0x6e, 0xad, 0x6e, 0x37, 0x9d, 0xf6, 0x1e, 0xca, 0x12, 0x79, 0x2e, 0xca, + 0xd8, 0xdd, 0xc0, 0xb5, 0x7c, 0x0e, 0x57, 0x35, 0x35, 0x09, 0x22, 0x52, 0x85, 0x69, 0x93, 0xda, + 0x8d, 0x35, 0xfb, 0x51, 0xd9, 0xee, 0xd8, 0x75, 0x27, 0xe0, 0xb1, 0x52, 0x73, 0x5c, 0xa0, 0xf3, + 0xa8, 0xdd, 0xb0, 0x5a, 0xf6, 0x23, 0xab, 0x2e, 0x0a, 0x75, 0x95, 0xbd, 0x46, 0x17, 0xb2, 0x72, + 0xda, 0x21, 0xab, 0xb1, 0x38, 0x2b, 0xa7, 0xdd, 0x9b, 0x55, 0x44, 0x27, 0x59, 0x6d, 0xda, 0xde, + 0x1e, 0x0d, 0xb8, 0xa1, 0x34, 0x5c, 0xca, 0x5c, 0xc9, 0x28, 0xac, 0x02, 0x2c, 0xb3, 0xd0, 0x68, + 0x3a, 0xce, 0x4a, 0xa1, 0x63, 0x33, 0x6f, 0xdb, 0x73, 0x02, 0xaa, 0xb6, 0x70, 0x1c, 0x3f, 0x0b, + 0xfb, 0x1f, 0x4d, 0xcc, 0x7b, 0x35, 0x31, 0x41, 0x19, 0x71, 0x53, 0x1a, 0x39, 0x91, 0xe0, 0x96, + 0xde, 0xca, 0x04, 0x65, 0xc8, 0x4d, 0x6d, 0xe7, 0x24, 0xb6, 0x53, 0xe1, 0xd6, 0xa3, 0xa1, 0x09, + 0x4a, 0xb2, 0xce, 0x3a, 0x2d, 0x60, 0x72, 0x93, 0xdb, 0x16, 0x16, 0xdc, 0x53, 0xf8, 0x69, 0x2f, + 0x0b, 0x33, 0xc4, 0x82, 0x27, 0x8b, 0xad, 0x14, 0x7b, 0xee, 0x38, 0x31, 0xf9, 0x6b, 0x30, 0xbd, + 0xe5, 0xd3, 0xdb, 0xd5, 0x8d, 0x9a, 0x8c, 0x64, 0x86, 0xca, 0xc5, 0xa9, 0xc5, 0x1b, 0xa7, 0x6c, + 0x3a, 0xd7, 0x54, 0x1a, 0x4c, 0x9f, 0xc3, 0xc7, 0xad, 0xeb, 0x53, 0x6b, 0xd7, 0xe9, 0xf8, 0x61, + 0x58, 0x48, 0x75, 0xdc, 0x62, 0x55, 0x19, 0x2b, 0x30, 0x93, 0x60, 0x43, 0xa6, 0x00, 0x18, 0xd0, + 0xda, 0x5a, 0xaf, 0x2d, 0x6f, 0x16, 0x9e, 0x21, 0x05, 0x98, 0xc0, 0xdf, 0xcb, 0xeb, 0xa5, 0xa5, + 0xd5, 0xe5, 0x4a, 0x21, 0x43, 0x66, 0x60, 0x12, 0x21, 0x95, 0x6a, 0x8d, 0x83, 0xb2, 0x3c, 0x79, + 0x82, 0x59, 0xe0, 0x4b, 0x37, 0x60, 0x0b, 0x00, 0xcf, 0x14, 0xe3, 0x6f, 0x65, 0xe1, 0xa2, 0x3c, + 0x56, 0x68, 0xc0, 0x04, 0x47, 0xa7, 0xbd, 0xf7, 0x94, 0x9f, 0x0e, 0xb7, 0xb5, 0xd3, 0xe1, 0xe5, + 0xd8, 0x49, 0x1d, 0x6b, 0x65, 0x9f, 0x23, 0xe2, 0xb7, 0xc7, 0xe0, 0xf9, 0xbe, 0x54, 0xe4, 0x03, + 0x76, 0x9a, 0x3b, 0xb4, 0x1d, 0x54, 0x1b, 0x4d, 0xba, 0xe9, 0xb4, 0xa8, 0xdb, 0x0d, 0x84, 0xc7, + 0xc0, 0x4b, 0xa8, 0xcf, 0xc3, 0x42, 0xcb, 0x69, 0x34, 0xa9, 0x15, 0xf0, 0x62, 0x6d, 0xba, 0x25, + 0xa9, 0x19, 0xcb, 0x30, 0x95, 0x57, 0xb5, 0x1d, 0x50, 0xef, 0x21, 0x5a, 0x25, 0x86, 0x2c, 0x0f, + 0x28, 0xed, 0x58, 0x36, 0x2b, 0xb5, 0x1c, 0x51, 0xac, 0xb3, 0x4c, 0x50, 0x93, 0xdb, 0x0a, 0xcb, + 0x32, 0xbb, 0xfd, 0xaf, 0xd9, 0x8f, 0x84, 0x99, 0x94, 0x88, 0x8c, 0x1b, 0xb2, 0xe4, 0xee, 0x7c, + 0x2d, 0xfb, 0x91, 0x99, 0x24, 0x21, 0x5f, 0x81, 0x73, 0xe2, 0x00, 0x12, 0xc1, 0x5b, 0x64, 0x8b, + 0x79, 0x68, 0x98, 0x57, 0x4f, 0x8e, 0x8b, 0x17, 0x64, 0x4c, 0x61, 0x19, 0x58, 0x29, 0xad, 0xd5, + 0xe9, 0x5c, 0xc8, 0x26, 0x3b, 0x90, 0x63, 0xdd, 0xb1, 0x46, 0x7d, 0x5f, 0xfa, 0x6c, 0x8a, 0x9b, + 0xb1, 0xda, 0x99, 0x56, 0x8b, 0x97, 0x9b, 0x3d, 0x29, 0xc9, 0x0a, 0x4c, 0x6d, 0xd3, 0x1d, 0x75, + 0x7c, 0x46, 0xc2, 0xad, 0xaa, 0x70, 0x48, 0x77, 0x7a, 0x0f, 0x4e, 0x8c, 0x8e, 0x38, 0xf8, 0x3e, + 0xf0, 0xe8, 0x68, 0xd5, 0xf1, 0x03, 0xda, 0xa6, 0x1e, 0x86, 0x6f, 0x1b, 0xc5, 0xcd, 0x60, 0x3e, + 0x92, 0x90, 0xf5, 0xf2, 0xa5, 0x17, 0x4f, 0x8e, 0x8b, 0xcf, 0x73, 0xe7, 0xe7, 0xa6, 0x80, 0x5b, + 0xb1, 0x44, 0x58, 0x49, 0xae, 0xe4, 0x6b, 0x30, 0x6d, 0xba, 0xdd, 0xc0, 0x69, 0xef, 0xd5, 0x02, + 0xcf, 0x0e, 0xe8, 0x1e, 0x3f, 0x90, 0xa2, 0x38, 0x71, 0xb1, 0x52, 0xf1, 0xb4, 0xcc, 0x81, 0x96, + 0x2f, 0xa0, 0xda, 0x89, 0xa0, 0x13, 0x90, 0xaf, 0xc2, 0x14, 0x0f, 0xdb, 0x11, 0x56, 0x30, 0xa6, + 0x25, 0xf1, 0xd0, 0x0b, 0x1f, 0xdc, 0x10, 0x56, 0x2d, 0x08, 0x4d, 0xab, 0x20, 0xc6, 0x8d, 0x7c, + 0x49, 0x74, 0xd6, 0x86, 0xd3, 0xde, 0x0b, 0xa7, 0x31, 0x60, 0xcf, 0xbf, 0x11, 0x75, 0x49, 0x87, + 0x7d, 0xae, 0x9c, 0xc6, 0x3d, 0x4c, 0xf4, 0x92, 0x7c, 0x48, 0x00, 0xcf, 0x97, 0x7c, 0xdf, 0xf1, + 0x03, 0xe1, 0x57, 0xb3, 0xfc, 0x88, 0xd6, 0xbb, 0x0c, 0x99, 0x5d, 0x6f, 0xa9, 0xc7, 0xed, 0xba, + 0x87, 0x97, 0xae, 0x9d, 0x1c, 0x17, 0x5f, 0xb3, 0x11, 0xd1, 0x12, 0xae, 0x38, 0x16, 0x95, 0xa8, + 0xd6, 0x21, 0xc7, 0x55, 0xda, 0xd0, 0x9f, 0x29, 0xf9, 0x2a, 0x9c, 0x2f, 0xdb, 0x3e, 0xad, 0xb6, + 0x7d, 0xda, 0xf6, 0x9d, 0xc0, 0x79, 0x48, 0x45, 0xa7, 0xe2, 0xe1, 0x97, 0xc7, 0x94, 0x61, 0x46, + 0xdd, 0xf6, 0xd9, 0xc2, 0x0c, 0x51, 0x2c, 0x31, 0x28, 0x4a, 0x35, 0x3d, 0xb8, 0x10, 0x13, 0xa6, + 0x6a, 0xb5, 0x95, 0x8a, 0x63, 0x87, 0xeb, 0x6a, 0x12, 0xfb, 0xeb, 0x35, 0x54, 0xed, 0xf9, 0xfb, + 0x56, 0xc3, 0xb1, 0xc3, 0x05, 0xd5, 0xa3, 0xb3, 0x62, 0x1c, 0x8c, 0xe3, 0x0c, 0x14, 0xe2, 0x43, + 0x49, 0xbe, 0x08, 0x63, 0xdc, 0xbe, 0x8d, 0xfa, 0xfb, 0x22, 0xf2, 0x84, 0x34, 0x97, 0x0a, 0xe1, + 0x3a, 0x91, 0x70, 0xa7, 0xe3, 0xd6, 0x73, 0x54, 0xb5, 0x96, 0x41, 0x77, 0x3a, 0x49, 0x44, 0x1a, + 0x30, 0xc1, 0x47, 0x8b, 0x62, 0x90, 0x48, 0x61, 0xe6, 0xfc, 0xa2, 0xba, 0x3a, 0x44, 0x51, 0x8c, + 0x3f, 0xbe, 0x1a, 0x8a, 0x39, 0xc1, 0x11, 0xb4, 0x2a, 0x34, 0xae, 0x4b, 0x00, 0x79, 0x49, 0x68, + 0x5c, 0x84, 0x0b, 0x3d, 0xbe, 0xd9, 0x78, 0x88, 0x96, 0x04, 0x3d, 0x6a, 0x24, 0x5f, 0x84, 0x39, + 0x24, 0x2c, 0xbb, 0xed, 0x36, 0xad, 0x07, 0xb8, 0x1d, 0x49, 0xed, 0x7b, 0x8e, 0x5b, 0xba, 0xf0, + 0xf6, 0xd6, 0x43, 0x04, 0x2b, 0xae, 0x84, 0x4f, 0xe5, 0x60, 0xfc, 0x7c, 0x16, 0xe6, 0xc5, 0x0e, + 0x67, 0xd2, 0xba, 0xeb, 0x35, 0x9e, 0xfe, 0x13, 0x75, 0x59, 0x3b, 0x51, 0x5f, 0x0a, 0xc3, 0x16, + 0xa5, 0x35, 0xb2, 0xcf, 0x81, 0xfa, 0x6b, 0x19, 0x78, 0xae, 0x1f, 0x11, 0xeb, 0x9d, 0x30, 0x28, + 0xe6, 0x58, 0x22, 0xf8, 0x65, 0x07, 0x66, 0x71, 0x40, 0xcb, 0xfb, 0xb4, 0x7e, 0xe0, 0xaf, 0xb8, + 0x7e, 0x80, 0x9e, 0x16, 0xd9, 0x1e, 0x6f, 0xdd, 0xaf, 0xa7, 0xbe, 0x75, 0x9f, 0xe7, 0xb3, 0xac, + 0x8e, 0x3c, 0x78, 0xd8, 0xce, 0x03, 0x7a, 0xe4, 0x9b, 0x69, 0xac, 0xd1, 0x62, 0xbe, 0xd4, 0x0d, + 0xf6, 0x37, 0x3c, 0xba, 0x4b, 0x3d, 0xda, 0xae, 0xd3, 0xef, 0x30, 0x8b, 0x79, 0xbd, 0x71, 0x03, + 0x69, 0x30, 0xfe, 0xf1, 0x24, 0xcc, 0xa5, 0x91, 0xb1, 0x7e, 0x51, 0x2e, 0xcd, 0xf1, 0x8c, 0xa6, + 0x3f, 0x98, 0x81, 0x89, 0x1a, 0xad, 0xbb, 0xed, 0xc6, 0x6d, 0xb4, 0x28, 0x12, 0xbd, 0x63, 0x71, + 0xa1, 0x81, 0xc1, 0xad, 0xdd, 0x98, 0xa9, 0xd1, 0xb7, 0x8f, 0x8b, 0x5f, 0x18, 0xec, 0xae, 0x5a, + 0x77, 0x31, 0x5c, 0x50, 0x80, 0x19, 0x37, 0xc2, 0x2a, 0xf0, 0x71, 0x50, 0xab, 0x94, 0x2c, 0xc1, + 0xa4, 0x58, 0xae, 0xae, 0x1a, 0x13, 0x95, 0x47, 0x76, 0x92, 0x05, 0x09, 0xd5, 0xb5, 0x46, 0x42, + 0x6e, 0x42, 0x6e, 0x6b, 0xf1, 0xb6, 0x18, 0x03, 0x99, 0xb3, 0x64, 0x6b, 0xf1, 0x36, 0xaa, 0xc3, + 0xd8, 0x15, 0x63, 0xb2, 0xbb, 0xa8, 0x19, 0xf9, 0x6c, 0x2d, 0xde, 0x26, 0xdf, 0x0b, 0xe7, 0x2a, + 0x8e, 0x2f, 0xaa, 0xe0, 0xbe, 0x1b, 0x0d, 0xf4, 0x58, 0x1c, 0xe9, 0x31, 0x7b, 0x3f, 0x9b, 0x3a, + 0x7b, 0x5f, 0x6c, 0x84, 0x4c, 0x2c, 0xee, 0x18, 0xd2, 0x88, 0xc7, 0x7e, 0x4d, 0xaf, 0x87, 0x7c, + 0x04, 0x53, 0xa8, 0xcc, 0x46, 0x77, 0x16, 0x8c, 0xda, 0x3f, 0xda, 0xa3, 0xe6, 0x4f, 0xa7, 0xd6, + 0xbc, 0xc0, 0xe3, 0x6d, 0xa0, 0x53, 0x0c, 0x46, 0xf8, 0xd7, 0x6e, 0xfd, 0x1a, 0x67, 0x72, 0x17, + 0xa6, 0x85, 0xf8, 0x75, 0x7f, 0x77, 0x73, 0x9f, 0x56, 0xec, 0x23, 0x61, 0x9f, 0x83, 0x37, 0x3a, + 0x21, 0xb3, 0x59, 0xee, 0xae, 0x15, 0xec, 0x53, 0xab, 0x61, 0x6b, 0x82, 0x4a, 0x8c, 0x90, 0x7c, + 0x03, 0xc6, 0x57, 0xdd, 0x3a, 0x93, 0xbc, 0x71, 0x67, 0xe0, 0x26, 0x3b, 0x1f, 0x62, 0xce, 0x4c, + 0x0e, 0x8e, 0x89, 0x53, 0xdf, 0x3e, 0x2e, 0xbe, 0x7d, 0xd6, 0x49, 0xa3, 0x54, 0x60, 0xaa, 0xb5, + 0x91, 0x32, 0xe4, 0xb7, 0xe9, 0x0e, 0x6b, 0x6d, 0x3c, 0x9f, 0x9e, 0x04, 0x0b, 0x8b, 0x3c, 0xf1, + 0x4b, 0xb3, 0xc8, 0x13, 0x30, 0xe2, 0xc1, 0x0c, 0xf6, 0xcf, 0x86, 0xed, 0xfb, 0x87, 0xae, 0xd7, + 0xc0, 0xc4, 0x29, 0xbd, 0xac, 0x81, 0x16, 0x53, 0x3b, 0xff, 0x39, 0xde, 0xf9, 0x1d, 0x85, 0x83, + 0x2a, 0x40, 0x26, 0xd8, 0x93, 0xaf, 0xc1, 0x94, 0x88, 0x5d, 0xb0, 0x76, 0xbb, 0x84, 0xab, 0x72, + 0x42, 0xf3, 0xfb, 0xd4, 0x0b, 0xb9, 0x94, 0x2a, 0x42, 0x21, 0x48, 0x0d, 0x94, 0xd5, 0xda, 0xb5, + 0x75, 0xa5, 0xbf, 0x4a, 0x42, 0x36, 0x60, 0xbc, 0x82, 0x59, 0x9d, 0xd1, 0x37, 0x4d, 0xd8, 0x85, + 0x87, 0x09, 0xc1, 0xa2, 0x12, 0xae, 0x8b, 0x11, 0x09, 0xa0, 0xd1, 0xd3, 0x4d, 0xb7, 0xd5, 0x0d, + 0x11, 0xc9, 0x2d, 0xc8, 0x55, 0x2b, 0x1b, 0xc2, 0x2c, 0x7c, 0x26, 0x8c, 0x10, 0xb2, 0x21, 0xd3, + 0x27, 0xa1, 0xfd, 0x9c, 0xd3, 0xd0, 0x8c, 0xca, 0xab, 0x95, 0x0d, 0xb2, 0x0b, 0x93, 0xd8, 0x01, + 0x2b, 0xd4, 0xe6, 0x7d, 0x3b, 0xdd, 0xa3, 0x6f, 0xaf, 0xa5, 0xf6, 0xed, 0x3c, 0xef, 0xdb, 0x7d, + 0x41, 0xad, 0xe5, 0x83, 0x51, 0xd9, 0x32, 0x91, 0x56, 0xe4, 0xa8, 0x92, 0x59, 0x4c, 0x36, 0x57, + 0xd1, 0x3e, 0x48, 0x88, 0xb4, 0x32, 0xa5, 0x55, 0x98, 0x56, 0xa5, 0xa7, 0xd7, 0x49, 0x92, 0x0f, + 0xf9, 0x3c, 0x0c, 0xdd, 0x3f, 0x08, 0x6c, 0x61, 0x00, 0x2e, 0xfb, 0x91, 0x81, 0x64, 0xf3, 0x51, + 0x0b, 0xe9, 0x1e, 0x68, 0x31, 0xe7, 0x90, 0x86, 0x0d, 0xc5, 0x8a, 0xed, 0x35, 0x0e, 0x6d, 0x0f, + 0x1d, 0x84, 0x67, 0x35, 0x16, 0x4a, 0x09, 0x1f, 0x8a, 0x7d, 0x01, 0x88, 0x79, 0x0d, 0xab, 0x2c, + 0xc8, 0x77, 0xc1, 0x45, 0xdf, 0xd9, 0x6b, 0xdb, 0x41, 0xd7, 0xa3, 0x96, 0xdd, 0xdc, 0x73, 0x3d, + 0x27, 0xd8, 0x6f, 0x59, 0x7e, 0xd7, 0x09, 0xe8, 0xfc, 0x9c, 0x96, 0xd1, 0xba, 0x26, 0xf1, 0x4a, + 0x12, 0xad, 0xc6, 0xb0, 0xcc, 0x0b, 0x7e, 0x7a, 0x01, 0xf9, 0x12, 0x4c, 0xaa, 0x5b, 0xb2, 0x3f, + 0x7f, 0xee, 0x52, 0xee, 0xca, 0x54, 0x78, 0xf1, 0x88, 0x6f, 0xe0, 0x32, 0x12, 0xb4, 0x72, 0x42, + 0xf8, 0x7a, 0x24, 0x68, 0x85, 0x57, 0x98, 0x23, 0x92, 0x14, 0x66, 0xcd, 0x19, 0x31, 0x63, 0x45, + 0x2f, 0xaf, 0xdd, 0x2e, 0x99, 0xa3, 0x1b, 0xd5, 0x07, 0xb5, 0xa6, 0x1b, 0x18, 0xff, 0x45, 0x06, + 0x37, 0x71, 0xf2, 0x1a, 0x06, 0x92, 0x0a, 0x5f, 0xcf, 0x50, 0x7f, 0x6b, 0x77, 0x62, 0x69, 0x04, + 0x38, 0x0a, 0x79, 0x1d, 0x46, 0x6e, 0xdb, 0x75, 0x19, 0xc4, 0x46, 0x20, 0xef, 0x22, 0x44, 0x55, + 0xf6, 0x72, 0x1c, 0x26, 0x5f, 0xf2, 0xc9, 0x5d, 0x8a, 0x92, 0xa5, 0x97, 0x4b, 0xf2, 0xb9, 0x1e, + 0xe5, 0x4b, 0xb1, 0x28, 0x94, 0x6c, 0xea, 0x31, 0xab, 0xf8, 0x54, 0x0e, 0xc6, 0x9f, 0x66, 0xa2, + 0x5d, 0x89, 0xbc, 0x0a, 0x43, 0xe6, 0x46, 0xf8, 0xfd, 0xdc, 0xe9, 0x37, 0xf6, 0xf9, 0x88, 0x40, + 0xbe, 0x04, 0xe7, 0x14, 0x3e, 0x09, 0x13, 0xfd, 0x57, 0xd0, 0x27, 0x55, 0xf9, 0x92, 0x74, 0x3b, + 0xfd, 0x74, 0x1e, 0x28, 0x4c, 0x47, 0x05, 0x15, 0xda, 0x76, 0x38, 0x6f, 0xa5, 0xb1, 0x2a, 0xef, + 0x06, 0x22, 0xc4, 0x1b, 0x9b, 0xc6, 0x81, 0xbb, 0xa4, 0x1a, 0xbf, 0x95, 0xd1, 0x76, 0x9b, 0x30, + 0xbb, 0x74, 0xe6, 0x94, 0xec, 0xd2, 0x6f, 0x01, 0x94, 0xba, 0x81, 0xbb, 0xdc, 0xf6, 0xdc, 0x26, + 0xd7, 0xa2, 0x88, 0x4c, 0x1a, 0xa8, 0x1b, 0xa6, 0x08, 0xd6, 0x3c, 0xe7, 0x42, 0xe4, 0x54, 0x6f, + 0x86, 0xdc, 0xc7, 0xf5, 0x66, 0x30, 0x7e, 0x3f, 0xa3, 0xad, 0x51, 0x26, 0x25, 0x8a, 0xa9, 0xa8, + 0x5a, 0x8c, 0x75, 0x9c, 0x87, 0x96, 0xdf, 0x74, 0xb5, 0x70, 0x15, 0x02, 0x8d, 0xfc, 0xbb, 0x19, + 0x38, 0xcf, 0xdd, 0x02, 0xd6, 0xbb, 0xad, 0x1d, 0xea, 0x3d, 0xb0, 0x9b, 0x4e, 0x23, 0x0a, 0xd3, + 0x17, 0x99, 0x0f, 0x2a, 0xd5, 0xa4, 0xe3, 0xf3, 0x8b, 0x2a, 0x77, 0x53, 0xb0, 0xda, 0x58, 0x68, + 0x3d, 0x0c, 0x4b, 0xd5, 0x8b, 0x6a, 0x3a, 0xbd, 0xf1, 0xeb, 0x19, 0x78, 0xf1, 0xd4, 0x5a, 0xc8, + 0x75, 0x18, 0x95, 0x29, 0x4c, 0x32, 0xd8, 0xf1, 0x68, 0x67, 0x9b, 0x4c, 0x5f, 0x22, 0xb1, 0xc8, + 0x97, 0xe1, 0x9c, 0xca, 0x6a, 0xd3, 0xb3, 0x1d, 0x35, 0x51, 0x48, 0xca, 0x57, 0x07, 0x0c, 0x25, + 0x2e, 0xad, 0xa5, 0x33, 0x31, 0xfe, 0xbf, 0x8c, 0x92, 0x6f, 0xfe, 0x29, 0x95, 0xe1, 0x6f, 0x69, + 0x32, 0xbc, 0x0c, 0x52, 0x1a, 0xb6, 0x8a, 0x95, 0xa5, 0xde, 0xbb, 0xa6, 0x15, 0x7b, 0x71, 0x04, + 0xfc, 0x48, 0x16, 0xc6, 0xb7, 0x7c, 0xea, 0xf1, 0x87, 0xdc, 0xef, 0xac, 0x50, 0x8d, 0x61, 0xbb, + 0x06, 0x0a, 0xa6, 0xf7, 0xc7, 0x19, 0x54, 0xf0, 0xab, 0x14, 0xac, 0x37, 0x94, 0x1c, 0x93, 0xd8, + 0x1b, 0x98, 0x5d, 0x12, 0xa1, 0x3c, 0xb4, 0xd8, 0xaa, 0x9e, 0x6e, 0x16, 0x73, 0x0e, 0xaf, 0x92, + 0x2f, 0xc0, 0xf0, 0x16, 0xaa, 0x2b, 0xf5, 0x20, 0x1b, 0x21, 0x7f, 0x2c, 0xe4, 0x9b, 0x74, 0xd7, + 0xd7, 0xe3, 0xce, 0x71, 0x42, 0x52, 0x83, 0xd1, 0xb2, 0x47, 0x31, 0x7b, 0xfc, 0xd0, 0xe0, 0x2e, + 0xe2, 0x75, 0x4e, 0x12, 0x77, 0x11, 0x17, 0x9c, 0x8c, 0x9f, 0xcb, 0x02, 0x89, 0xda, 0x88, 0xa9, + 0xd2, 0xfc, 0xa7, 0x76, 0xd0, 0xdf, 0xd7, 0x06, 0xfd, 0xf9, 0xc4, 0xa0, 0xf3, 0xe6, 0x0d, 0x34, + 0xf6, 0xbf, 0x93, 0x81, 0xf3, 0xe9, 0x84, 0xe4, 0x25, 0x18, 0xb9, 0xbf, 0xb9, 0x21, 0xe3, 0xb4, + 0x88, 0xa6, 0xb8, 0x1d, 0xd4, 0x15, 0x98, 0xa2, 0x88, 0xbc, 0x01, 0x23, 0x1f, 0x98, 0x65, 0x76, + 0x0e, 0x29, 0xc9, 0x38, 0xbe, 0xee, 0x59, 0x75, 0xfd, 0x28, 0x12, 0x48, 0xea, 0xd8, 0xe6, 0x9e, + 0xd8, 0xd8, 0xfe, 0x64, 0x16, 0xa6, 0x4b, 0xf5, 0x3a, 0xf5, 0x7d, 0x11, 0x68, 0xfe, 0xa9, 0x1d, + 0xd8, 0xf4, 0x08, 0x2c, 0x5a, 0xdb, 0x06, 0x1a, 0xd5, 0xdf, 0xcd, 0xc0, 0x39, 0x49, 0xf5, 0xd0, + 0xa1, 0x87, 0x9b, 0xfb, 0x1e, 0xf5, 0xf7, 0xdd, 0x66, 0x63, 0xe0, 0x8c, 0x3f, 0x4c, 0xd0, 0xc3, + 0xe0, 0xf0, 0xea, 0xab, 0xfe, 0x2e, 0x42, 0x34, 0x41, 0x8f, 0x07, 0x90, 0xbf, 0x0e, 0xa3, 0xa5, + 0x4e, 0xc7, 0x73, 0x1f, 0xf2, 0x65, 0x2f, 0x22, 0x4b, 0xda, 0x1c, 0xa4, 0x79, 0xd8, 0x73, 0x10, + 0xfb, 0x8c, 0x0a, 0x6d, 0xf3, 0x50, 0x7e, 0x93, 0xfc, 0x33, 0x1a, 0xb4, 0xad, 0xca, 0xe2, 0x58, + 0x6e, 0xd4, 0x80, 0x6c, 0x78, 0x6e, 0xcb, 0x0d, 0x68, 0x83, 0xb7, 0x07, 0x03, 0x13, 0x9c, 0x1a, + 0x52, 0x6b, 0xd3, 0x09, 0x9a, 0x5a, 0x48, 0xad, 0x80, 0x01, 0x4c, 0x0e, 0x37, 0xfe, 0x9f, 0x61, + 0x98, 0x50, 0x7b, 0x87, 0x18, 0x3c, 0x8d, 0x87, 0xeb, 0xa9, 0xd1, 0x31, 0x6c, 0x84, 0x98, 0xa2, + 0x24, 0x0a, 0x2d, 0x93, 0x3d, 0x35, 0xb4, 0xcc, 0x36, 0x4c, 0x6e, 0x78, 0x2e, 0x86, 0xc0, 0xc4, + 0xd7, 0x4a, 0xb1, 0x15, 0xce, 0x2a, 0xf7, 0x4e, 0x36, 0x90, 0xf8, 0x1e, 0x8a, 0x92, 0x7d, 0x47, + 0x60, 0x63, 0x72, 0x4b, 0x4d, 0xeb, 0xa2, 0xf1, 0xe1, 0xa6, 0x16, 0xb6, 0x2f, 0xe2, 0xd8, 0x86, + 0xa6, 0x16, 0x0c, 0xa2, 0x9b, 0x5a, 0x30, 0x88, 0xba, 0xd6, 0x86, 0x9f, 0xd4, 0x5a, 0x23, 0x3f, + 0x97, 0x81, 0xf1, 0x52, 0xbb, 0x2d, 0x42, 0xd6, 0x9c, 0xe2, 0xad, 0xff, 0x65, 0x61, 0x6d, 0xf1, + 0xf6, 0xc7, 0xb2, 0xb6, 0x40, 0xb9, 0xc5, 0x47, 0x49, 0x35, 0xaa, 0x50, 0xbd, 0xad, 0x29, 0xdf, + 0x41, 0xde, 0x86, 0x42, 0x38, 0xc9, 0xab, 0xed, 0x06, 0x7d, 0x44, 0x79, 0x1a, 0xc4, 0x49, 0x11, + 0x57, 0x5b, 0x95, 0x4c, 0xe3, 0x88, 0x64, 0x13, 0xc0, 0x0e, 0x67, 0x57, 0x2c, 0x9f, 0x6b, 0x72, + 0xfa, 0x09, 0xe9, 0x19, 0x7f, 0xe3, 0x83, 0x96, 0x2a, 0x3d, 0x47, 0x7c, 0x48, 0x0b, 0xa6, 0x79, + 0x32, 0xd5, 0x5a, 0x60, 0x7b, 0x01, 0xa6, 0xa2, 0x80, 0x53, 0xc7, 0xe1, 0x55, 0xa1, 0x3f, 0x7b, + 0x56, 0xa4, 0x68, 0xf5, 0x19, 0xad, 0x95, 0x92, 0x97, 0x22, 0xce, 0x9b, 0x47, 0x31, 0x37, 0x2f, + 0x24, 0xbf, 0x97, 0x4f, 0xfa, 0x9f, 0xcc, 0xc0, 0x79, 0x75, 0xd2, 0xd7, 0xba, 0x3b, 0x22, 0x74, + 0x28, 0xb9, 0x06, 0x63, 0x62, 0x4e, 0x86, 0x97, 0xa8, 0x64, 0x46, 0x8d, 0x08, 0x85, 0x2c, 0xb3, + 0x69, 0xc8, 0x78, 0x08, 0xa9, 0x7b, 0x36, 0xb6, 0x4f, 0xb1, 0xa2, 0x28, 0x51, 0xb7, 0x87, 0xbf, + 0xf5, 0xf9, 0xc9, 0x20, 0xc6, 0x7b, 0x30, 0xa3, 0x8f, 0x44, 0x8d, 0x06, 0xe4, 0x2a, 0x8c, 0xca, + 0xe1, 0xcb, 0xa4, 0x0f, 0x9f, 0x2c, 0x37, 0xb6, 0x81, 0x24, 0xe8, 0x7d, 0x34, 0x8b, 0x62, 0xf7, + 0x53, 0x6e, 0xb6, 0x27, 0x1f, 0x25, 0x13, 0x88, 0x4b, 0xb3, 0xe2, 0xfb, 0xc6, 0x35, 0xb7, 0x04, + 0x0c, 0xa3, 0xfa, 0xa7, 0x53, 0x30, 0x9b, 0xb2, 0xe7, 0x9e, 0x22, 0x13, 0x15, 0xf5, 0x0d, 0x62, + 0x2c, 0x0c, 0xf6, 0x21, 0xb7, 0x85, 0xf7, 0x60, 0xf8, 0xd4, 0xed, 0x80, 0x3b, 0xa5, 0xc4, 0x76, + 0x01, 0x4e, 0xf6, 0x89, 0xc8, 0x45, 0x6a, 0x3c, 0x9e, 0xe1, 0x27, 0x16, 0x8f, 0x67, 0x09, 0x26, + 0x45, 0xab, 0xc4, 0x76, 0xa5, 0x18, 0x47, 0xcb, 0x0c, 0x31, 0x89, 0x6d, 0x4b, 0x27, 0xe1, 0x3c, + 0x7c, 0xb7, 0xf9, 0x90, 0x0a, 0x1e, 0xa3, 0x2a, 0x0f, 0x2c, 0x48, 0xe5, 0xa1, 0x90, 0x90, 0xff, + 0x18, 0x13, 0x39, 0x22, 0x44, 0xdd, 0xb3, 0xf2, 0xfd, 0xf6, 0xac, 0xc6, 0x93, 0xd9, 0xb3, 0x9e, + 0x97, 0xdf, 0x98, 0xbe, 0x77, 0xa5, 0x7c, 0x16, 0xf9, 0x95, 0x0c, 0xcc, 0xf0, 0xa0, 0x30, 0xea, + 0xc7, 0xf6, 0x0d, 0xf4, 0x51, 0x7f, 0x32, 0x1f, 0xfb, 0x9c, 0xc8, 0x0d, 0x94, 0xfe, 0xad, 0xc9, + 0x8f, 0x22, 0xdf, 0x05, 0x10, 0xae, 0x28, 0x1e, 0x4a, 0x76, 0x7c, 0xf1, 0xb9, 0x94, 0x5d, 0x20, + 0x44, 0x8a, 0x52, 0x78, 0x04, 0x21, 0x9d, 0x96, 0xbe, 0x33, 0x84, 0x92, 0xef, 0x85, 0x39, 0xb6, + 0x5e, 0x42, 0x88, 0x08, 0x61, 0x35, 0x3f, 0x8e, 0xb5, 0x7c, 0xa6, 0xb7, 0x4c, 0x74, 0x2d, 0x8d, + 0x8c, 0x07, 0x1e, 0x8e, 0x32, 0xa9, 0x07, 0x6a, 0xb4, 0x8b, 0xd4, 0x8a, 0x30, 0x32, 0x1c, 0x7e, + 0x3d, 0x4f, 0xb3, 0xd1, 0x63, 0x7f, 0xbb, 0x28, 0xd7, 0x02, 0xdf, 0xdf, 0x7c, 0xdd, 0x47, 0x19, + 0x41, 0xe4, 0x03, 0x20, 0x61, 0x34, 0x15, 0x0e, 0xa3, 0x32, 0x05, 0x07, 0x57, 0x37, 0x47, 0x51, + 0x59, 0x3c, 0x59, 0xac, 0x4e, 0x92, 0x24, 0x31, 0xa1, 0x30, 0x27, 0x1a, 0xcd, 0xa0, 0x32, 0xcb, + 0xa2, 0x3f, 0x3f, 0xa5, 0x05, 0x08, 0x8b, 0x4a, 0xa2, 0x94, 0xeb, 0x4a, 0xaa, 0x46, 0x4d, 0xe5, + 0x94, 0xc6, 0x8e, 0xdc, 0x82, 0x31, 0x74, 0x14, 0x5e, 0x91, 0xc6, 0x5e, 0xc2, 0xf0, 0x04, 0x5d, + 0x8a, 0xad, 0x7d, 0xdd, 0x64, 0x2b, 0x42, 0x65, 0xd7, 0x81, 0x8a, 0x77, 0x64, 0x76, 0xdb, 0xa8, + 0x14, 0x16, 0xfa, 0x8e, 0x86, 0x77, 0x64, 0x79, 0x5d, 0xdd, 0x93, 0x1c, 0x91, 0xc8, 0xd7, 0x60, + 0x7c, 0xcd, 0x7e, 0x14, 0xe6, 0x99, 0x9a, 0x19, 0x3c, 0x9b, 0x55, 0xcb, 0x7e, 0x14, 0x26, 0x99, + 0x8a, 0x67, 0xb3, 0x52, 0x58, 0x92, 0xaf, 0x00, 0x28, 0x9a, 0x6a, 0x72, 0x6a, 0x05, 0x2f, 0xca, + 0xb0, 0x77, 0xa9, 0x1a, 0x6c, 0xe4, 0xaf, 0x30, 0x8c, 0x49, 0x0e, 0x73, 0x9f, 0x9c, 0xe4, 0x70, + 0xee, 0x93, 0x93, 0x1c, 0x16, 0x76, 0xe0, 0x62, 0xcf, 0xa5, 0x93, 0x12, 0xf4, 0xf8, 0xba, 0x1e, + 0xf4, 0xf8, 0x62, 0xaf, 0x23, 0xd6, 0xd7, 0x33, 0xad, 0xcc, 0x16, 0xe6, 0x7a, 0x4b, 0x27, 0xdf, + 0xca, 0xc6, 0x8e, 0x5c, 0x71, 0xb1, 0xe0, 0x59, 0xbe, 0x7a, 0xc9, 0x24, 0x59, 0x4c, 0xc1, 0xcd, + 0x0f, 0x65, 0x25, 0x2e, 0x3c, 0x3b, 0x94, 0xd5, 0x43, 0x1d, 0x8f, 0xe7, 0xc7, 0x3d, 0x7d, 0xdf, + 0x81, 0x29, 0x9e, 0x35, 0xf7, 0x1e, 0x3d, 0x3a, 0x74, 0xbd, 0x06, 0xcf, 0x5f, 0x24, 0x64, 0xf0, + 0x44, 0xca, 0xfb, 0x18, 0x2e, 0xa9, 0x48, 0xdf, 0xd3, 0x61, 0xac, 0xfd, 0x62, 0xea, 0x2e, 0xc6, + 0x10, 0xfa, 0xb9, 0xa5, 0x92, 0x37, 0x43, 0x41, 0x8d, 0x7a, 0x6a, 0xbe, 0x15, 0x4f, 0x02, 0x53, + 0xe4, 0x35, 0xea, 0x19, 0xff, 0x22, 0x07, 0x84, 0xd7, 0x54, 0xb6, 0x3b, 0x36, 0x7a, 0x66, 0x3b, + 0x18, 0x6b, 0xa9, 0x20, 0x70, 0xec, 0x9d, 0x26, 0x55, 0x03, 0x95, 0x09, 0xe3, 0xda, 0xb0, 0xcc, + 0x8a, 0x5f, 0x74, 0x12, 0x84, 0x3d, 0xb6, 0xba, 0xec, 0xe3, 0x6c, 0x75, 0x5f, 0x83, 0x67, 0x4b, + 0x1d, 0x4c, 0xbf, 0x2d, 0x6b, 0xb9, 0xed, 0x7a, 0x72, 0x93, 0xd2, 0x7c, 0xfe, 0xec, 0x10, 0x2d, + 0xf1, 0xa5, 0xfd, 0x58, 0x28, 0x72, 0x0a, 0x9b, 0x97, 0x9d, 0x40, 0x8d, 0x21, 0x21, 0xe5, 0x94, + 0x0e, 0x96, 0xa4, 0xc8, 0x29, 0x9c, 0x44, 0xf2, 0x70, 0x3c, 0x29, 0xa7, 0x60, 0xb6, 0xb2, 0x88, + 0x87, 0xe3, 0xd1, 0x1e, 0xb2, 0x4e, 0x48, 0x42, 0xde, 0x81, 0xf1, 0x52, 0x37, 0x70, 0x05, 0x63, + 0x61, 0x15, 0x1e, 0xd9, 0x6f, 0x8b, 0x4f, 0xd1, 0xae, 0x3e, 0x11, 0xba, 0xf1, 0x27, 0x39, 0xb8, + 0x98, 0x1c, 0x5e, 0x51, 0x1a, 0xae, 0x8f, 0xcc, 0x29, 0xeb, 0x23, 0x6d, 0x36, 0x64, 0xa3, 0x4c, + 0x13, 0x4f, 0x62, 0x36, 0xf0, 0x2c, 0xde, 0x1f, 0x73, 0x36, 0xd4, 0x60, 0x5c, 0x3d, 0xef, 0x86, + 0x3e, 0xee, 0x79, 0xa7, 0x72, 0x61, 0x97, 0x7a, 0x1e, 0x3a, 0x63, 0x38, 0x7a, 0x3a, 0x8a, 0x47, + 0xcd, 0xe0, 0x18, 0xe4, 0xdf, 0x81, 0x4b, 0x7c, 0x4f, 0x8a, 0x37, 0x76, 0xe9, 0x48, 0x72, 0x14, + 0x03, 0xb7, 0x78, 0x72, 0x5c, 0xbc, 0xc6, 0x55, 0x25, 0x56, 0xa2, 0xdb, 0xac, 0x9d, 0x23, 0x4b, + 0x7e, 0x99, 0x52, 0xc9, 0xa9, 0xbc, 0x8d, 0x32, 0x5c, 0x14, 0xa5, 0x91, 0xd3, 0xb6, 0x2c, 0x64, + 0x83, 0x7c, 0x10, 0x69, 0xbb, 0x70, 0x90, 0x63, 0x8a, 0x2c, 0x2c, 0xc7, 0xfc, 0xdf, 0x4a, 0x6e, + 0xe6, 0x37, 0xd2, 0x7c, 0x6e, 0x78, 0xd4, 0x6e, 0x0e, 0xd6, 0xdd, 0x6d, 0xa4, 0x4e, 0x2d, 0x9b, + 0xaa, 0x53, 0x93, 0x4a, 0x99, 0x5c, 0xaa, 0x52, 0xa6, 0x02, 0xd3, 0xb5, 0xee, 0x8e, 0xac, 0x3b, + 0xee, 0xaf, 0xe9, 0x77, 0x77, 0xd2, 0x7a, 0x25, 0x4e, 0x62, 0xfc, 0x68, 0x16, 0x26, 0x36, 0x9a, + 0xdd, 0x3d, 0xa7, 0x5d, 0xb1, 0x03, 0xfb, 0xa9, 0x55, 0xf3, 0xbd, 0xa5, 0xa9, 0xf9, 0x42, 0xd7, + 0xb2, 0xb0, 0x61, 0x03, 0xe9, 0xf8, 0x7e, 0x36, 0x03, 0xd3, 0x11, 0x09, 0x3f, 0xac, 0x57, 0x60, + 0x88, 0xfd, 0x10, 0x97, 0xdf, 0x4b, 0x09, 0xc6, 0x3c, 0xcd, 0x64, 0xf8, 0x97, 0x50, 0xbc, 0xe9, + 0x39, 0xdc, 0x90, 0xc3, 0xc2, 0x67, 0x61, 0x2c, 0x62, 0x7b, 0x96, 0xf4, 0x92, 0xbf, 0x91, 0x81, + 0x42, 0xbc, 0x25, 0xe4, 0x1e, 0x8c, 0x32, 0x4e, 0x0e, 0x95, 0xf7, 0xf2, 0x97, 0x7b, 0xb4, 0xf9, + 0x9a, 0x40, 0xe3, 0x9f, 0x87, 0x9d, 0x4f, 0x39, 0xc4, 0x94, 0x1c, 0x16, 0x4c, 0x98, 0x50, 0xb1, + 0x52, 0xbe, 0xee, 0x75, 0x5d, 0x42, 0x39, 0x9f, 0xde, 0x0f, 0x5a, 0x52, 0x4c, 0xed, 0xab, 0x85, + 0xf0, 0x71, 0x59, 0x9b, 0x5c, 0xa9, 0xab, 0x0a, 0x27, 0xcd, 0x62, 0x94, 0xaf, 0x40, 0x9d, 0x67, + 0x29, 0x13, 0x3a, 0xc4, 0x23, 0xaf, 0xc3, 0x08, 0xaf, 0x4f, 0x4d, 0xe8, 0xd6, 0x41, 0x88, 0x2a, + 0x27, 0x73, 0x1c, 0xe3, 0x6f, 0xe7, 0xe0, 0x7c, 0xf4, 0x79, 0x5b, 0x9d, 0x86, 0x1d, 0xd0, 0x0d, + 0xdb, 0xb3, 0x5b, 0xfe, 0x29, 0x2b, 0xe0, 0x4a, 0xe2, 0xd3, 0x30, 0x95, 0x96, 0xfc, 0x34, 0xe5, + 0x83, 0x8c, 0xd8, 0x07, 0xa1, 0x0e, 0x94, 0x7f, 0x90, 0xfc, 0x0c, 0x72, 0x0f, 0x72, 0x35, 0x1a, + 0x88, 0xbd, 0xf7, 0x72, 0xa2, 0x57, 0xd5, 0xef, 0xba, 0x56, 0xa3, 0x01, 0x1f, 0x44, 0x1e, 0x17, + 0x4a, 0x0b, 0xce, 0xc7, 0xb8, 0x90, 0x6d, 0x18, 0x59, 0x7e, 0xd4, 0xa1, 0xf5, 0x40, 0x24, 0x47, + 0xbd, 0xda, 0x9f, 0x1f, 0xc7, 0x55, 0x72, 0xa3, 0x52, 0x04, 0xa8, 0x9d, 0xc5, 0x51, 0x16, 0x6e, + 0x41, 0x5e, 0x56, 0x7e, 0x96, 0x99, 0xbb, 0xf0, 0x16, 0x8c, 0x2b, 0x95, 0x9c, 0x69, 0xd2, 0xff, + 0x22, 0xdb, 0x57, 0xdd, 0xa6, 0xcc, 0xa7, 0xba, 0x9c, 0x90, 0x15, 0x95, 0x6c, 0x54, 0x5c, 0x56, + 0xb4, 0x0e, 0x44, 0x51, 0x1f, 0xa1, 0xb1, 0x0a, 0xd3, 0xb5, 0x03, 0xa7, 0x13, 0x05, 0x8a, 0xd5, + 0x4e, 0x64, 0xcc, 0x78, 0x23, 0x2e, 0xee, 0xf1, 0x13, 0x39, 0x4e, 0x67, 0xfc, 0x79, 0x06, 0x46, + 0xd8, 0x5f, 0x0f, 0x6e, 0x3d, 0xa5, 0x5b, 0xe6, 0x4d, 0x6d, 0xcb, 0x9c, 0x51, 0x62, 0xb5, 0xe3, + 0xc6, 0x71, 0xeb, 0x94, 0xcd, 0xf2, 0x58, 0x0c, 0x10, 0x47, 0x26, 0x77, 0x60, 0x54, 0x98, 0x14, + 0x09, 0xdb, 0x6f, 0x35, 0xf8, 0xbb, 0x34, 0x36, 0x0a, 0x6f, 0xf8, 0x6e, 0x27, 0xae, 0x12, 0x91, + 0xd4, 0x4c, 0xae, 0x97, 0x21, 0x7b, 0xb5, 0x7c, 0xe9, 0x2e, 0x3a, 0xeb, 0xf1, 0xd0, 0xe5, 0xfe, + 0xd2, 0x05, 0xc1, 0xa9, 0x97, 0x6f, 0x7d, 0x49, 0xbc, 0x86, 0xe4, 0xfa, 0x31, 0x39, 0x2f, 0x93, + 0x14, 0xa7, 0x3e, 0x94, 0xb4, 0xe0, 0x7c, 0xad, 0xb6, 0x82, 0xe6, 0x87, 0x1b, 0xae, 0x17, 0xdc, + 0x76, 0xbd, 0x43, 0x1b, 0x6d, 0x8b, 0x51, 0xc3, 0xa7, 0xd8, 0x20, 0xa4, 0x19, 0x85, 0xbd, 0x9a, + 0x6a, 0x14, 0xd6, 0xc7, 0x4e, 0xc1, 0x68, 0xc3, 0x85, 0x5a, 0x6d, 0x85, 0x07, 0x0e, 0xff, 0x8b, + 0xa8, 0xef, 0x37, 0x32, 0x30, 0x53, 0xab, 0xad, 0xc4, 0xaa, 0x5a, 0x95, 0x11, 0xcb, 0x33, 0x7a, + 0x9e, 0xef, 0xd4, 0x8e, 0xc0, 0x51, 0xc8, 0x70, 0x09, 0xaf, 0xae, 0x05, 0xa7, 0xe4, 0x4c, 0xc8, + 0x46, 0x18, 0x23, 0x3d, 0xab, 0xf9, 0x03, 0xf4, 0x68, 0x28, 0x6a, 0xb8, 0x85, 0x37, 0x1d, 0x2b, + 0xd5, 0x35, 0xdc, 0x0c, 0x62, 0xfc, 0x37, 0xe7, 0x79, 0x14, 0x76, 0x39, 0x5b, 0xde, 0x85, 0x09, + 0x41, 0x8f, 0x46, 0xf3, 0xc2, 0x26, 0xe4, 0x22, 0xdb, 0x20, 0x77, 0x39, 0x9c, 0x47, 0xe7, 0xfd, + 0xf6, 0x71, 0x71, 0x88, 0x75, 0x8d, 0xa9, 0xa1, 0x93, 0xfb, 0x30, 0xb9, 0x66, 0x3f, 0x52, 0xd4, + 0x19, 0xdc, 0x25, 0xea, 0x2a, 0xdb, 0x55, 0x5a, 0xf6, 0xa3, 0x01, 0x8c, 0xee, 0x74, 0x7a, 0x72, + 0x00, 0x53, 0x7a, 0x9b, 0xc4, 0x0c, 0x4c, 0x8e, 0xd8, 0x8d, 0xd4, 0x11, 0xbb, 0xd8, 0x71, 0xbd, + 0xc0, 0xda, 0x0d, 0xc9, 0xb5, 0x8c, 0x03, 0x31, 0xd6, 0xe4, 0x5d, 0x98, 0x51, 0x42, 0x80, 0xde, + 0x76, 0xbd, 0x96, 0x2d, 0x2f, 0x5c, 0xa8, 0xe3, 0x47, 0x5b, 0xa2, 0x5d, 0x04, 0x9b, 0x49, 0x4c, + 0xf2, 0xa5, 0x34, 0x37, 0xb3, 0xe1, 0xc8, 0xf2, 0x30, 0xc5, 0xcd, 0xac, 0x97, 0xe5, 0x61, 0xd2, + 0xe1, 0x6c, 0xaf, 0x9f, 0x65, 0x72, 0x9e, 0xb7, 0x7e, 0x20, 0xcb, 0xe3, 0x70, 0xe4, 0x7a, 0x58, + 0x20, 0x2f, 0x42, 0x6e, 0x69, 0xe3, 0x36, 0xbe, 0x4c, 0x49, 0x23, 0xaa, 0xf6, 0xbe, 0xdd, 0xae, + 0xe3, 0x45, 0x48, 0x78, 0x03, 0xa8, 0x07, 0xe5, 0xd2, 0xc6, 0x6d, 0x62, 0xc3, 0x2c, 0xe6, 0x79, + 0x0b, 0xbe, 0x78, 0xe3, 0x86, 0x32, 0x54, 0x79, 0xfc, 0xb4, 0xeb, 0xe2, 0xd3, 0x8a, 0x98, 0x25, + 0x2e, 0xb0, 0x1e, 0xdd, 0xb8, 0x91, 0x3a, 0x20, 0xe1, 0x87, 0xa5, 0xf1, 0x62, 0x07, 0xd6, 0x9a, + 0xfd, 0x28, 0x72, 0xe2, 0xf0, 0x85, 0xc3, 0xee, 0xf3, 0x72, 0x6a, 0x45, 0x0e, 0x20, 0xda, 0x81, + 0xa5, 0x13, 0xb1, 0x7b, 0x6c, 0x34, 0xc1, 0x7c, 0xe1, 0xea, 0xb4, 0x20, 0xd5, 0x75, 0xd2, 0xab, + 0x5b, 0xbd, 0x8c, 0x29, 0xe8, 0x64, 0x2b, 0xbc, 0x8d, 0xf3, 0xdb, 0xac, 0xc8, 0x8c, 0x7c, 0x5d, + 0xbd, 0x8d, 0x73, 0x25, 0x99, 0xd6, 0xac, 0xe9, 0x50, 0x85, 0xc3, 0xbd, 0x5a, 0x4c, 0x9d, 0x4b, + 0xf2, 0x92, 0x3f, 0x71, 0xf6, 0x4b, 0x3e, 0x85, 0xa1, 0x55, 0xb7, 0x7e, 0x20, 0x82, 0xf3, 0x7d, + 0xc0, 0x76, 0x61, 0x3d, 0x8d, 0xfe, 0xe3, 0x5a, 0x5c, 0x23, 0x7b, 0xb2, 0xce, 0x3e, 0x95, 0xcd, + 0x02, 0xd1, 0x27, 0xc2, 0x8a, 0x77, 0x2e, 0xbc, 0xe5, 0x2a, 0x65, 0x5c, 0x1e, 0xe5, 0x93, 0x46, + 0x76, 0xad, 0xa9, 0x93, 0x13, 0x0a, 0x85, 0x0a, 0xf5, 0x0f, 0x02, 0xb7, 0x53, 0x6e, 0x3a, 0x9d, + 0x1d, 0xd7, 0xf6, 0x64, 0x28, 0xe7, 0x81, 0xf7, 0xe4, 0x06, 0xa7, 0xb7, 0xea, 0x92, 0x81, 0x99, + 0x60, 0x49, 0xbe, 0x04, 0x53, 0x6c, 0x72, 0x2f, 0x3f, 0x0a, 0x68, 0x9b, 0x8f, 0xfc, 0x0c, 0x4a, + 0x74, 0x73, 0x4a, 0xee, 0x92, 0xb0, 0x90, 0xcf, 0x29, 0x5c, 0xec, 0x34, 0x24, 0xd0, 0x02, 0x1b, + 0x6a, 0xac, 0x48, 0x03, 0xe6, 0xd7, 0xec, 0x47, 0x4a, 0x0e, 0x66, 0x65, 0x92, 0x12, 0x9c, 0x60, + 0x57, 0x4e, 0x8e, 0x8b, 0x2f, 0xb3, 0x09, 0x16, 0x45, 0x17, 0xef, 0x31, 0x5f, 0x7b, 0x72, 0x22, + 0xdf, 0x80, 0x0b, 0xa2, 0x59, 0x15, 0xcc, 0x1b, 0xe6, 0x7a, 0x47, 0xb5, 0x7d, 0x1b, 0xfd, 0xb7, + 0x66, 0x7b, 0x74, 0xd8, 0xf5, 0xf4, 0x2d, 0x51, 0x76, 0x58, 0x43, 0xf2, 0xb1, 0x7c, 0xce, 0xc8, + 0xec, 0x55, 0x03, 0xf9, 0x08, 0xa6, 0xf8, 0x73, 0xdc, 0x8a, 0xeb, 0x07, 0xa8, 0xac, 0x99, 0x3b, + 0x9b, 0x5b, 0x02, 0x7f, 0xe3, 0xe3, 0x8e, 0x3c, 0x31, 0xe5, 0x4e, 0x8c, 0x33, 0x79, 0x1b, 0xc6, + 0x37, 0x9c, 0x36, 0x0f, 0x3d, 0x5a, 0xdd, 0x40, 0xb5, 0xb2, 0x38, 0x81, 0x3a, 0x4e, 0xdb, 0x92, + 0x1a, 0x93, 0x4e, 0xb8, 0x5d, 0xa8, 0xd8, 0x64, 0x1b, 0xc6, 0x6b, 0xb5, 0x95, 0xdb, 0x0e, 0x93, + 0x4b, 0x3a, 0x47, 0xf3, 0xe7, 0x7b, 0x7c, 0xe5, 0x4b, 0xa9, 0x5f, 0x39, 0xe9, 0xfb, 0xfb, 0xd6, + 0xae, 0xd3, 0xa4, 0x56, 0xdd, 0xed, 0x1c, 0x99, 0x2a, 0xa7, 0x14, 0x53, 0xfd, 0x0b, 0x4f, 0xd8, + 0x54, 0xbf, 0x0a, 0xd3, 0x8a, 0xf1, 0x2c, 0x1a, 0xce, 0xce, 0x47, 0xf1, 0xaa, 0x54, 0xd3, 0xfc, + 0xb8, 0x6b, 0x6a, 0x9c, 0x4e, 0xda, 0xe8, 0x5f, 0x3c, 0xab, 0x8d, 0xbe, 0x03, 0x33, 0x7c, 0x30, + 0xc4, 0x3c, 0xc0, 0x91, 0x5e, 0xe8, 0xd1, 0x87, 0x57, 0x53, 0xfb, 0x70, 0x56, 0x8c, 0xb4, 0x9c, + 0x64, 0xf8, 0xfc, 0x9c, 0xe4, 0x4a, 0x76, 0x81, 0x08, 0xa0, 0x1d, 0xd8, 0x3b, 0xb6, 0x4f, 0xb1, + 0xae, 0x67, 0x7b, 0xd4, 0xf5, 0x72, 0x6a, 0x5d, 0x53, 0xb2, 0xae, 0x1d, 0x5e, 0x4d, 0x0a, 0x47, + 0xd2, 0x96, 0xf5, 0xc8, 0xf9, 0x85, 0x1d, 0xfb, 0x9c, 0xa6, 0xe3, 0x4e, 0x22, 0xf0, 0xd0, 0x4f, + 0xf1, 0x49, 0x1b, 0xef, 0xf7, 0x14, 0xce, 0xe4, 0x11, 0x9c, 0x4f, 0x7e, 0x05, 0xd6, 0xf9, 0x3c, + 0xd6, 0xf9, 0xbc, 0x56, 0x67, 0x1c, 0x89, 0xcf, 0x1b, 0xbd, 0x59, 0xf1, 0x5a, 0x7b, 0xf0, 0x27, + 0x3f, 0x94, 0x81, 0x0b, 0x6b, 0xb7, 0x4b, 0x98, 0x4d, 0xd4, 0xe1, 0x91, 0xe8, 0x42, 0x97, 0xde, + 0x17, 0xc4, 0x3b, 0x48, 0xfc, 0x6d, 0x46, 0x4a, 0x1c, 0xb8, 0x55, 0x30, 0xd1, 0xfd, 0xa5, 0xd6, + 0xae, 0xcd, 0x93, 0x94, 0x0a, 0x16, 0x29, 0x7e, 0xbf, 0xdf, 0xfc, 0xa3, 0x62, 0xc6, 0xec, 0x55, + 0x15, 0x69, 0xc2, 0x82, 0xde, 0x2d, 0xd2, 0x8b, 0x62, 0x9f, 0x36, 0x9b, 0xf3, 0x45, 0x9c, 0xd1, + 0xaf, 0x9f, 0x1c, 0x17, 0xaf, 0x24, 0x7a, 0x37, 0xf4, 0xcc, 0x60, 0x98, 0x4a, 0x83, 0xfb, 0xf0, + 0x23, 0xad, 0x14, 0xa1, 0x7b, 0xfe, 0x92, 0x16, 0xfb, 0x27, 0x51, 0xbe, 0xf4, 0x8a, 0x90, 0x48, + 0x9e, 0x67, 0xeb, 0xbd, 0xa7, 0x80, 0x68, 0x26, 0x39, 0xdf, 0x1d, 0xca, 0x4f, 0x16, 0xa6, 0x52, + 0x5c, 0x16, 0x8c, 0xdf, 0xce, 0xc6, 0x0e, 0x46, 0x52, 0x85, 0x51, 0x31, 0xdf, 0x7b, 0x5e, 0x32, + 0x9e, 0x4f, 0x9d, 0xd5, 0xa3, 0x62, 0xe9, 0x98, 0x92, 0x9e, 0x1c, 0x32, 0x56, 0xd8, 0x68, 0x71, + 0xe3, 0xfd, 0x0a, 0x3f, 0xf7, 0x10, 0xa4, 0x9d, 0xf0, 0x95, 0xb3, 0x3b, 0xe2, 0xe9, 0x7e, 0x9e, + 0x78, 0xd4, 0xcb, 0xda, 0xc8, 0x01, 0x4f, 0x25, 0x95, 0x0b, 0xbd, 0xb9, 0xf4, 0xbc, 0x51, 0x4f, + 0xac, 0x42, 0x56, 0x8b, 0xf1, 0x5b, 0x19, 0x98, 0xd4, 0x4e, 0x56, 0x72, 0x4b, 0x71, 0x55, 0x8c, + 0xbc, 0xf7, 0x35, 0x1c, 0xdc, 0x6c, 0xe3, 0x4e, 0x8c, 0xb7, 0x84, 0xdf, 0x41, 0xb6, 0x37, 0x1d, + 0x2e, 0xb6, 0xb8, 0xe7, 0x6a, 0x7f, 0xfd, 0x70, 0x98, 0x07, 0x73, 0xa8, 0x47, 0x1e, 0xcc, 0xbf, + 0x57, 0x84, 0x29, 0xfd, 0x46, 0x4c, 0x5e, 0x87, 0x11, 0xd4, 0xcd, 0x4b, 0xf5, 0x0a, 0xaa, 0x85, + 0x50, 0x7d, 0xaf, 0x39, 0xa3, 0x70, 0x1c, 0xf2, 0x0a, 0x40, 0x68, 0x00, 0x2e, 0x5f, 0xa6, 0x86, + 0x4f, 0x8e, 0x8b, 0x99, 0x37, 0x4c, 0xa5, 0x80, 0x7c, 0x15, 0x60, 0xdd, 0x6d, 0xd0, 0x30, 0xb9, + 0x71, 0x1f, 0xeb, 0x8b, 0x57, 0x13, 0x69, 0x56, 0xce, 0xb5, 0xdd, 0x06, 0x4d, 0xe6, 0x54, 0x51, + 0x38, 0x92, 0xcf, 0xc3, 0xb0, 0xd9, 0x6d, 0x52, 0xf9, 0x82, 0x31, 0x2e, 0x4f, 0xb8, 0x6e, 0x93, + 0x46, 0x7a, 0x02, 0xaf, 0x1b, 0x37, 0x2c, 0x64, 0x00, 0xf2, 0x3e, 0x4f, 0xbf, 0x22, 0x62, 0x84, + 0x0e, 0x47, 0x6f, 0x75, 0x8a, 0xe4, 0x93, 0x88, 0x12, 0xaa, 0x90, 0x90, 0xfb, 0x30, 0xaa, 0x3e, + 0x32, 0x29, 0x3e, 0xef, 0xea, 0x43, 0xa4, 0xa2, 0x74, 0x10, 0x59, 0x91, 0xe3, 0xef, 0x4f, 0x92, + 0x0b, 0x79, 0x07, 0xc6, 0x18, 0x7b, 0xb6, 0x73, 0xf8, 0xe2, 0x56, 0x83, 0x2f, 0x72, 0xca, 0x07, + 0xb1, 0xdd, 0x47, 0x8b, 0xe4, 0x19, 0x12, 0x90, 0x2f, 0x61, 0x1e, 0x5b, 0xd1, 0xd5, 0x7d, 0xad, + 0x72, 0x2e, 0x27, 0xba, 0x1a, 0x13, 0xdb, 0x26, 0x7a, 0x3a, 0xe2, 0x47, 0xf6, 0xc2, 0x90, 0x6b, + 0x83, 0xa4, 0xcc, 0x79, 0x2d, 0x51, 0xc1, 0xbc, 0x8c, 0x22, 0x96, 0x4c, 0x52, 0xad, 0xf1, 0x25, + 0x1d, 0x28, 0x44, 0x42, 0xa5, 0xa8, 0x0b, 0xfa, 0xd5, 0xf5, 0x46, 0xa2, 0x2e, 0x75, 0x00, 0x13, + 0xd5, 0x25, 0xb8, 0x93, 0x06, 0x4c, 0xc9, 0x03, 0x4a, 0xd4, 0x37, 0xde, 0xaf, 0xbe, 0x57, 0x12, + 0xf5, 0xcd, 0x36, 0x76, 0x92, 0xf5, 0xc4, 0x78, 0x92, 0x77, 0x60, 0x52, 0x42, 0x78, 0xca, 0xe8, + 0x89, 0x28, 0xe7, 0x6e, 0x63, 0x27, 0x91, 0x28, 0x5a, 0x47, 0x56, 0xa9, 0xf9, 0xec, 0x98, 0xd4, + 0xa8, 0xe3, 0xb3, 0x42, 0x47, 0x26, 0x1f, 0xc2, 0x78, 0xb5, 0xc5, 0x1a, 0xe2, 0xb6, 0xed, 0x80, + 0x0a, 0x7f, 0x48, 0x69, 0x61, 0xa4, 0x94, 0x28, 0x53, 0x95, 0x27, 0xc3, 0x8e, 0x8a, 0xb4, 0x64, + 0xd8, 0x11, 0x98, 0x75, 0x1e, 0x7f, 0x55, 0x14, 0x73, 0x58, 0xfa, 0x4a, 0x3e, 0x9f, 0x62, 0xe5, + 0xa3, 0xb0, 0x17, 0xf1, 0x20, 0x19, 0x54, 0xbe, 0xea, 0xc5, 0x62, 0xf1, 0xaa, 0x3c, 0xc9, 0xbb, + 0x30, 0x2e, 0xb2, 0x89, 0x95, 0xcc, 0x75, 0x7f, 0xbe, 0x80, 0x8d, 0xc7, 0x08, 0x0f, 0x32, 0xf1, + 0x98, 0x65, 0x7b, 0x31, 0x73, 0xd6, 0x08, 0x9f, 0x7c, 0x11, 0xe6, 0xb6, 0x9d, 0x76, 0xc3, 0x3d, + 0xf4, 0xc5, 0x31, 0x25, 0x36, 0xba, 0x99, 0xc8, 0x99, 0xec, 0x90, 0x97, 0x87, 0xb2, 0x60, 0x62, + 0xe3, 0x4b, 0xe5, 0x40, 0xbe, 0x27, 0xc1, 0x99, 0xcf, 0x20, 0xd2, 0x6f, 0x06, 0x2d, 0x26, 0x66, + 0x50, 0xb2, 0xfa, 0xf8, 0x74, 0x4a, 0xad, 0x86, 0xb8, 0x40, 0xf4, 0xf3, 0xfd, 0xae, 0xeb, 0xb4, + 0xe7, 0x67, 0x71, 0x2f, 0x7c, 0x36, 0x1e, 0x53, 0x01, 0xf1, 0x44, 0x52, 0x71, 0xe3, 0xe4, 0xb8, + 0xf8, 0x42, 0x5c, 0xe6, 0xff, 0xc8, 0xd5, 0x9e, 0x4b, 0x52, 0x58, 0x93, 0x0f, 0x61, 0x82, 0xfd, + 0x1f, 0x2a, 0x25, 0xe6, 0x34, 0xbb, 0x50, 0x05, 0x53, 0xd4, 0x83, 0x63, 0x84, 0xe9, 0xce, 0x52, + 0xf4, 0x15, 0x1a, 0x2b, 0xf2, 0x16, 0x00, 0x13, 0x9b, 0xc4, 0x76, 0x7c, 0x2e, 0x0a, 0x7d, 0x8c, + 0x52, 0x57, 0x72, 0x23, 0x8e, 0x90, 0xc9, 0x3b, 0x30, 0xce, 0x7e, 0xd5, 0xba, 0x0d, 0x97, 0xad, + 0x8d, 0xf3, 0x48, 0xcb, 0x5d, 0x53, 0x19, 0xad, 0xcf, 0xe1, 0x9a, 0x6b, 0x6a, 0x84, 0x4e, 0x56, + 0x60, 0x1a, 0x43, 0x54, 0x8b, 0xe0, 0xa8, 0x0e, 0xf5, 0xe7, 0x2f, 0x28, 0xd6, 0x10, 0xac, 0xc8, + 0x72, 0xc2, 0x32, 0xf5, 0x2e, 0x13, 0x23, 0x23, 0x3e, 0xcc, 0x26, 0x9f, 0x93, 0xfd, 0xf9, 0x79, + 0xec, 0x24, 0x29, 0xc1, 0x27, 0x31, 0xf8, 0x7e, 0xcc, 0x46, 0x44, 0xd9, 0xb8, 0xe4, 0xa3, 0x92, + 0x5a, 0x61, 0x1a, 0x77, 0x62, 0x02, 0xb9, 0x53, 0xde, 0x88, 0xc7, 0x70, 0xbe, 0x88, 0x2d, 0xc0, + 0x61, 0xde, 0xab, 0x47, 0x59, 0xc4, 0x53, 0xe2, 0x38, 0xa7, 0x50, 0x93, 0xef, 0x86, 0x73, 0x72, + 0x07, 0x11, 0x45, 0x62, 0x5e, 0x2f, 0x9c, 0x71, 0x27, 0x6e, 0xec, 0x84, 0x55, 0x27, 0xa6, 0x74, + 0x7a, 0x15, 0xc4, 0x86, 0x71, 0x1c, 0x56, 0x51, 0xe3, 0xb3, 0xfd, 0x6a, 0xbc, 0x92, 0xa8, 0xf1, + 0x3c, 0x4e, 0x94, 0x64, 0x65, 0x2a, 0x4f, 0xb2, 0x04, 0x93, 0x62, 0x1d, 0x89, 0xd9, 0xf6, 0x1c, + 0xf6, 0x16, 0x2a, 0xb1, 0xe4, 0x0a, 0x4c, 0x4c, 0x38, 0x9d, 0x44, 0xdd, 0x91, 0xf9, 0x63, 0xd2, + 0xf3, 0xda, 0x8e, 0x1c, 0x7f, 0x43, 0xd2, 0x91, 0xd9, 0x8e, 0x14, 0x49, 0x31, 0xcb, 0x8f, 0x3a, + 0x9e, 0x50, 0x51, 0xbd, 0x10, 0x65, 0x45, 0x52, 0x84, 0x1f, 0x8b, 0x86, 0x18, 0xea, 0x96, 0x90, + 0xc6, 0x81, 0x6c, 0xc1, 0x6c, 0x78, 0x6a, 0x2b, 0x8c, 0x8b, 0x51, 0x94, 0xe0, 0xe8, 0xa8, 0x4f, + 0xe7, 0x9b, 0x46, 0x4f, 0x6c, 0xb8, 0xa0, 0x9d, 0xd3, 0x0a, 0xeb, 0x4b, 0xc8, 0x1a, 0xb3, 0xd6, + 0xeb, 0x87, 0x7c, 0x3a, 0xfb, 0x5e, 0x7c, 0xc8, 0x47, 0xb0, 0x10, 0x3f, 0x9b, 0x95, 0x5a, 0x5e, + 0xc4, 0x5a, 0x5e, 0x3b, 0x39, 0x2e, 0x5e, 0x4e, 0x1c, 0xef, 0xe9, 0x15, 0xf5, 0xe1, 0x46, 0xbe, + 0x0a, 0xf3, 0xfa, 0xf9, 0xac, 0xd4, 0x64, 0x60, 0x4d, 0xb8, 0x74, 0xc2, 0x83, 0x3d, 0xbd, 0x86, + 0x9e, 0x3c, 0x48, 0x00, 0xc5, 0xd4, 0xd9, 0xad, 0x54, 0xf3, 0x52, 0xd4, 0xa0, 0xc4, 0x2a, 0x49, + 0xaf, 0xee, 0x34, 0x96, 0xe4, 0x10, 0x5e, 0x48, 0x3b, 0x26, 0x94, 0x4a, 0x5f, 0x0e, 0x95, 0xc0, + 0x9f, 0x4a, 0x3f, 0x72, 0xd2, 0x6b, 0x3e, 0x85, 0x2d, 0xf9, 0x12, 0x9c, 0x53, 0xd6, 0x97, 0x52, + 0xdf, 0x2b, 0x58, 0x1f, 0xba, 0x82, 0xab, 0x0b, 0x33, 0xbd, 0x96, 0x74, 0x1e, 0xa4, 0x05, 0xb3, + 0xb2, 0xe1, 0xa8, 0x6d, 0x17, 0x47, 0xcf, 0x65, 0x6d, 0x57, 0x4d, 0x62, 0x2c, 0x5d, 0x12, 0xbb, + 0xea, 0x7c, 0x63, 0xc7, 0xea, 0x44, 0x84, 0xea, 0x4c, 0x4f, 0xe1, 0x4b, 0x56, 0x60, 0xa4, 0xb6, + 0x51, 0xbd, 0x7d, 0x7b, 0x79, 0xfe, 0x55, 0xac, 0x41, 0xfa, 0x8d, 0x71, 0xa0, 0x76, 0x69, 0x12, + 0xe6, 0x8a, 0x1d, 0x67, 0x77, 0x57, 0x7b, 0xb0, 0xe2, 0xa8, 0xe4, 0x7b, 0xd0, 0x50, 0x90, 0xed, + 0xa8, 0x25, 0xdf, 0x77, 0xf6, 0x30, 0xea, 0xb4, 0x3f, 0xff, 0x9a, 0xf6, 0xde, 0x2f, 0x23, 0x72, + 0x97, 0x31, 0x61, 0x59, 0x02, 0x9d, 0x4b, 0x9b, 0xec, 0xfe, 0x2f, 0x76, 0x6e, 0xcb, 0x8e, 0x58, + 0xa9, 0x9b, 0x78, 0xb2, 0x22, 0xd6, 0x6f, 0x7b, 0x4e, 0x60, 0xed, 0x77, 0xb5, 0xe6, 0xcf, 0x7f, + 0x4a, 0x8b, 0xc0, 0xcc, 0xd3, 0xb8, 0x29, 0xbd, 0xf6, 0xb2, 0xa8, 0xf0, 0x39, 0x7e, 0x5b, 0xee, + 0xd1, 0x73, 0x33, 0x7b, 0x31, 0x3a, 0x9f, 0xfc, 0x60, 0x06, 0xce, 0x6f, 0xbb, 0xde, 0x41, 0xd3, + 0xb5, 0x1b, 0xb2, 0x55, 0x62, 0x0f, 0x7f, 0xbd, 0xdf, 0x1e, 0xfe, 0x99, 0xc4, 0x1e, 0x6e, 0x1c, + 0x0a, 0x36, 0x56, 0x18, 0xd0, 0x3c, 0xb1, 0x9f, 0xf7, 0xa8, 0x8a, 0x7c, 0x0f, 0x5c, 0x4a, 0x2f, + 0x51, 0x26, 0xe5, 0x1b, 0x38, 0x29, 0x6f, 0x9c, 0x1c, 0x17, 0xdf, 0xe8, 0x55, 0x53, 0xfa, 0x04, + 0x3d, 0x95, 0xf5, 0xdd, 0xa1, 0xfc, 0x95, 0xc2, 0xd5, 0xbb, 0x43, 0xf9, 0xab, 0x85, 0xd7, 0xcc, + 0xe7, 0x6a, 0xa5, 0xb5, 0xd5, 0x6a, 0x43, 0x1e, 0xae, 0x32, 0xe6, 0x3a, 0xa7, 0x31, 0x2f, 0xf7, + 0x2b, 0x8d, 0x38, 0x1a, 0x7f, 0x33, 0x03, 0xc5, 0x53, 0x26, 0x09, 0x3b, 0xcf, 0xa2, 0x91, 0xa8, + 0xd1, 0x40, 0x8d, 0xdc, 0x1e, 0x8d, 0x9f, 0xa5, 0x9b, 0x8d, 0xe8, 0x24, 0xe8, 0x74, 0x28, 0xd2, + 0x85, 0x28, 0xbe, 0xa7, 0xc9, 0x34, 0x21, 0x12, 0xcb, 0x58, 0x85, 0x42, 0x7c, 0xf2, 0x90, 0xcf, + 0xc1, 0xa4, 0x9a, 0xac, 0x40, 0xaa, 0x12, 0x78, 0xa0, 0x11, 0x6f, 0x4f, 0x3b, 0x10, 0x35, 0x44, + 0xe3, 0x17, 0x33, 0x30, 0x9b, 0xb2, 0xc2, 0xc8, 0x65, 0x18, 0xc2, 0x6c, 0x62, 0x8a, 0xd5, 0x50, + 0x2c, 0x8b, 0x18, 0x96, 0x93, 0x4f, 0xc3, 0x68, 0x65, 0xbd, 0x56, 0x2b, 0xad, 0x4b, 0x65, 0x04, + 0x3f, 0x88, 0xdb, 0xbe, 0xe5, 0xdb, 0xba, 0xb1, 0x81, 0x40, 0x23, 0x6f, 0xc0, 0x48, 0x75, 0x03, + 0x09, 0xb8, 0xed, 0x2b, 0xb6, 0xd7, 0xe9, 0xc4, 0xf1, 0x05, 0x92, 0xf1, 0xe3, 0x19, 0x20, 0xc9, + 0xed, 0x82, 0xdc, 0x80, 0x71, 0x75, 0x53, 0xe2, 0xed, 0xc5, 0x17, 0x58, 0x65, 0xe1, 0x98, 0x2a, + 0x0e, 0xa9, 0xc0, 0x30, 0xe6, 0x81, 0x0d, 0xad, 0x1c, 0x52, 0x97, 0xc5, 0x85, 0xc4, 0xb2, 0x18, + 0xc6, 0x2c, 0xb3, 0x26, 0x27, 0x36, 0x7e, 0x37, 0x03, 0x24, 0xdd, 0x76, 0x71, 0x20, 0x2b, 0xab, + 0x37, 0x95, 0xd8, 0x05, 0x6a, 0xbe, 0xa0, 0x30, 0xd9, 0x9b, 0xaa, 0x06, 0x88, 0xa2, 0x1c, 0x5c, + 0xd6, 0xd4, 0x4e, 0xbd, 0x1d, 0x5e, 0xaf, 0xc2, 0xf0, 0x03, 0xea, 0xed, 0x48, 0xb3, 0x6e, 0x34, + 0x05, 0x7d, 0xc8, 0x00, 0xaa, 0x1a, 0x06, 0x31, 0x8c, 0x3f, 0xc9, 0xc0, 0x5c, 0xda, 0x1d, 0xe5, + 0x14, 0xbf, 0x54, 0x23, 0xe6, 0x52, 0x8b, 0x16, 0x56, 0xdc, 0x4e, 0x34, 0x74, 0xa4, 0x2d, 0xc2, + 0x30, 0x6b, 0xac, 0x1c, 0x61, 0x54, 0x83, 0xb1, 0xde, 0xf0, 0x4d, 0x0e, 0x67, 0x08, 0x3c, 0x46, + 0xdf, 0x10, 0x86, 0x77, 0x44, 0x04, 0x9c, 0xdd, 0x26, 0x87, 0x33, 0x84, 0x35, 0xb7, 0x41, 0xa5, + 0x7a, 0x08, 0x11, 0x5a, 0x0c, 0x60, 0x72, 0x38, 0xb9, 0x0c, 0xa3, 0xf7, 0xdb, 0xab, 0xd4, 0x7e, + 0x28, 0x73, 0x56, 0xa0, 0x45, 0x98, 0xdb, 0xb6, 0x9a, 0x0c, 0x66, 0xca, 0x42, 0xe3, 0x67, 0x33, + 0x30, 0x93, 0xb8, 0x1e, 0x9d, 0xee, 0x7a, 0xdb, 0xdf, 0x07, 0x6e, 0x90, 0xf6, 0xf1, 0xcf, 0x1f, + 0x4a, 0xff, 0x7c, 0xe3, 0xbf, 0x1b, 0x81, 0x0b, 0x3d, 0xb4, 0x55, 0x91, 0x8f, 0x6e, 0xe6, 0x54, + 0x1f, 0xdd, 0x2f, 0xc3, 0x64, 0xb9, 0x69, 0x3b, 0x2d, 0x7f, 0xd3, 0x8d, 0xbe, 0x38, 0x72, 0xf5, + 0xc1, 0x32, 0xe1, 0x07, 0x11, 0xfa, 0x84, 0x5c, 0xac, 0x23, 0x85, 0x15, 0xb8, 0x49, 0x61, 0x59, + 0x63, 0x96, 0xf0, 0x92, 0xcd, 0xfd, 0x25, 0xf1, 0x92, 0xd5, 0xfd, 0xb6, 0x86, 0x9e, 0xa8, 0xdf, + 0x56, 0xba, 0xcd, 0xf7, 0xf0, 0xe3, 0x78, 0x00, 0x94, 0x61, 0x92, 0x9b, 0xc4, 0x95, 0x7c, 0x3e, + 0x48, 0x23, 0x09, 0x33, 0x3a, 0xdb, 0x4f, 0x8e, 0x85, 0x46, 0x43, 0x56, 0x74, 0x1f, 0xa3, 0x51, + 0x7c, 0x33, 0xbe, 0xdc, 0xdb, 0x87, 0x48, 0xb3, 0x15, 0xd1, 0x7c, 0x89, 0xbe, 0x01, 0x73, 0x69, + 0xd7, 0xdd, 0xf9, 0xbc, 0x66, 0x6d, 0xdb, 0xd3, 0x4a, 0x7b, 0xf0, 0x4b, 0xf3, 0x41, 0xea, 0xa5, + 0x59, 0xfa, 0x7e, 0x8f, 0x69, 0x21, 0x9d, 0x7b, 0xac, 0x05, 0x8e, 0xdb, 0xdf, 0x43, 0xdc, 0xf8, + 0x32, 0x3c, 0xdf, 0x97, 0x9c, 0xbc, 0xad, 0xc5, 0x18, 0x7a, 0x35, 0x19, 0x63, 0xe8, 0xdb, 0xc7, + 0xc5, 0x19, 0xcd, 0x6f, 0x73, 0x2d, 0x54, 0xf8, 0x1b, 0x3f, 0x9b, 0xd5, 0x3d, 0x8e, 0xff, 0x32, + 0x2e, 0xd4, 0xab, 0x30, 0xbc, 0xbd, 0x4f, 0x3d, 0x79, 0x3c, 0xe0, 0x87, 0x1c, 0x32, 0x80, 0xfa, + 0x21, 0x88, 0x41, 0x6e, 0xc3, 0xd4, 0x06, 0x9f, 0xb8, 0x72, 0x36, 0x0e, 0x45, 0x3a, 0x97, 0x8e, + 0xd0, 0x0c, 0xa6, 0x4c, 0xc7, 0x18, 0x95, 0x71, 0x27, 0xd6, 0xe9, 0x22, 0x42, 0x12, 0xf7, 0x8c, + 0xe2, 0x02, 0xc4, 0x54, 0xe4, 0x0b, 0x16, 0x6d, 0xb6, 0x66, 0x0c, 0x6a, 0xec, 0xc2, 0x0b, 0x7d, + 0x19, 0xb1, 0x73, 0x1b, 0x3a, 0xe1, 0xaf, 0x98, 0xe5, 0x75, 0x5f, 0x52, 0x53, 0xa1, 0x33, 0xbe, + 0x01, 0x13, 0x6a, 0x2f, 0xe3, 0x11, 0xc4, 0x7e, 0x8b, 0x59, 0xc1, 0x8f, 0x20, 0x06, 0x30, 0x39, + 0x3c, 0x7a, 0xcb, 0xc9, 0xa6, 0xbf, 0xe5, 0x44, 0xc3, 0x9f, 0x3b, 0x6d, 0xf8, 0x59, 0xe5, 0xb8, + 0xc3, 0x29, 0x95, 0xe3, 0x6f, 0xb5, 0x72, 0x0c, 0x81, 0x64, 0x72, 0xf8, 0x13, 0xad, 0xfc, 0x9f, + 0xc8, 0x24, 0x67, 0xe8, 0x78, 0x25, 0x97, 0x7b, 0x26, 0xca, 0x54, 0x96, 0xb6, 0x7a, 0x23, 0xcc, + 0x48, 0xa6, 0xc8, 0x9e, 0x26, 0x53, 0x9c, 0x65, 0x22, 0xa2, 0xdc, 0xcb, 0x87, 0x74, 0x28, 0x92, + 0x03, 0xed, 0x84, 0xb5, 0x8b, 0xc4, 0x32, 0xbe, 0x99, 0x81, 0x73, 0xa9, 0x3a, 0x73, 0x56, 0x2b, + 0x57, 0xce, 0x2b, 0xeb, 0x30, 0xae, 0x99, 0xe7, 0x18, 0x67, 0x89, 0x7f, 0x31, 0x78, 0x5b, 0x8c, + 0x17, 0x61, 0x2c, 0x7c, 0xb1, 0x25, 0x73, 0x72, 0xe8, 0xd0, 0x2e, 0x52, 0x3e, 0xfc, 0xd5, 0x00, + 0xd8, 0x17, 0x3c, 0x51, 0xd3, 0x6a, 0xe3, 0x9f, 0x64, 0x79, 0x02, 0xdc, 0xa7, 0x36, 0x94, 0x6d, + 0xba, 0x3d, 0x34, 0x6b, 0x52, 0xef, 0x00, 0xb6, 0x64, 0x19, 0x46, 0x6a, 0x81, 0x1d, 0x74, 0x65, + 0xd8, 0x8e, 0x59, 0x95, 0x0c, 0x0b, 0x1e, 0x2c, 0x46, 0x81, 0x1b, 0x7c, 0x84, 0x68, 0x5a, 0x02, + 0x84, 0x28, 0x66, 0xd5, 0x7f, 0x90, 0x81, 0x09, 0x95, 0x98, 0x7c, 0x08, 0x53, 0x32, 0x40, 0x27, + 0x0f, 0x66, 0x22, 0x9e, 0x97, 0xa5, 0x29, 0x98, 0x0c, 0xd0, 0xa9, 0x06, 0x3f, 0xd1, 0xf0, 0xd5, + 0xad, 0xba, 0xa3, 0x22, 0x93, 0x06, 0x90, 0xd6, 0xae, 0x6d, 0x1d, 0x52, 0xfb, 0x80, 0xfa, 0x81, + 0xc5, 0x4d, 0x76, 0xc4, 0x2b, 0xb4, 0x64, 0xbf, 0x76, 0xbb, 0xc4, 0xad, 0x75, 0xd8, 0x48, 0x88, + 0x48, 0xab, 0x09, 0x1a, 0xf5, 0x69, 0xad, 0xb5, 0x6b, 0x6f, 0xf3, 0x42, 0x4e, 0x67, 0xfc, 0xe9, + 0x08, 0x9f, 0x6e, 0x22, 0x9e, 0xef, 0x0e, 0x4c, 0xdd, 0xaf, 0x56, 0xca, 0x8a, 0xa2, 0x5d, 0x4f, + 0x07, 0xb5, 0xfc, 0x28, 0xa0, 0x5e, 0xdb, 0x6e, 0xca, 0xfb, 0x6e, 0x74, 0x04, 0xb9, 0x4e, 0xa3, + 0x9e, 0xae, 0x84, 0x8f, 0x71, 0x64, 0x75, 0xf0, 0x9b, 0x75, 0x58, 0x47, 0x76, 0xc0, 0x3a, 0x7c, + 0xbb, 0xd5, 0xec, 0x51, 0x87, 0xce, 0x91, 0xec, 0xe3, 0xd5, 0x77, 0xbf, 0xbb, 0xa3, 0xd4, 0x92, + 0xeb, 0x5f, 0xcb, 0x4b, 0xa2, 0x96, 0x67, 0x85, 0x5a, 0x25, 0xb5, 0x9e, 0x04, 0xd7, 0x68, 0x9f, + 0x18, 0x3a, 0x75, 0x9f, 0xf8, 0xeb, 0x19, 0x18, 0xe1, 0xe2, 0xab, 0x98, 0xc6, 0x3d, 0x04, 0xe4, + 0xed, 0x27, 0x23, 0x20, 0x17, 0xf0, 0x9c, 0xd0, 0x26, 0x34, 0x2f, 0x23, 0x95, 0xd8, 0xba, 0x90, + 0xde, 0x00, 0xf8, 0x64, 0xc6, 0x4b, 0x4e, 0x5f, 0x16, 0xa4, 0x1a, 0x85, 0xd2, 0x18, 0x3d, 0xd5, + 0x5b, 0x5b, 0x86, 0x1f, 0x19, 0x15, 0xa1, 0x34, 0xf4, 0x00, 0x1a, 0xab, 0x30, 0x26, 0x02, 0x74, + 0x2c, 0x1d, 0x89, 0x87, 0xf1, 0x82, 0x66, 0xda, 0xd4, 0x58, 0x3a, 0x8a, 0x44, 0x73, 0x11, 0xe2, + 0xc3, 0xda, 0x39, 0xd2, 0xf2, 0x09, 0x4b, 0x44, 0x72, 0x9f, 0xe7, 0xd9, 0xe4, 0x11, 0x8f, 0xf5, + 0x14, 0x07, 0x21, 0x5c, 0x84, 0xfe, 0x92, 0x5e, 0xfe, 0x29, 0x01, 0x8e, 0x23, 0x1e, 0x64, 0x15, + 0x0a, 0x68, 0x0e, 0x47, 0x1b, 0x7c, 0xd5, 0x54, 0x2b, 0x3c, 0x08, 0x84, 0x30, 0x69, 0x0e, 0x78, + 0x99, 0x58, 0x6e, 0x31, 0xff, 0xcb, 0x04, 0xa5, 0xf1, 0x33, 0x59, 0x28, 0xc4, 0x67, 0x1f, 0x79, + 0x07, 0xc6, 0xc3, 0x88, 0xd3, 0xa1, 0x07, 0x38, 0x3e, 0x90, 0x45, 0x21, 0xaa, 0xf5, 0xec, 0x8c, + 0x0a, 0x3a, 0x59, 0x84, 0x3c, 0x5b, 0xc4, 0xf1, 0x4c, 0xc6, 0x5d, 0x01, 0x53, 0x3d, 0xb2, 0x24, + 0x1e, 0xa9, 0xc1, 0x2c, 0x5b, 0x34, 0x35, 0xa7, 0xbd, 0xd7, 0xa4, 0xab, 0xee, 0x9e, 0xdb, 0x0d, + 0xa2, 0x64, 0x85, 0xfc, 0x02, 0x63, 0xb7, 0x9a, 0x5a, 0xb1, 0x9e, 0xaa, 0x30, 0x85, 0x5a, 0xc9, + 0xb3, 0x3e, 0x34, 0x40, 0x9e, 0x75, 0x65, 0x67, 0xfd, 0xa3, 0x2c, 0x8c, 0x2b, 0xd3, 0x8f, 0x5c, + 0x85, 0x7c, 0xd5, 0x5f, 0x75, 0xeb, 0x07, 0x61, 0x28, 0xc9, 0xc9, 0x93, 0xe3, 0xe2, 0x98, 0xe3, + 0x5b, 0x4d, 0x04, 0x9a, 0x61, 0x31, 0x59, 0x82, 0x49, 0xfe, 0x97, 0x4c, 0x1c, 0x92, 0x8d, 0x74, + 0x6b, 0x1c, 0x59, 0xa6, 0x0c, 0x51, 0x37, 0x5b, 0x8d, 0x84, 0x7c, 0x05, 0x80, 0x03, 0x30, 0xf8, + 0x40, 0x6e, 0xf0, 0xb0, 0x09, 0xa2, 0x82, 0x94, 0xb0, 0x03, 0x0a, 0x43, 0xf2, 0x35, 0x1e, 0xd0, + 0x5a, 0x2e, 0x97, 0xa1, 0xc1, 0xe3, 0x3e, 0x30, 0xfe, 0x56, 0x7a, 0xf8, 0x19, 0x95, 0xa5, 0xc8, + 0xf5, 0xb3, 0x60, 0xd2, 0xba, 0xfb, 0x90, 0x7a, 0x47, 0xa5, 0x00, 0x11, 0x15, 0x0c, 0xe3, 0x7f, + 0xc9, 0x28, 0x8b, 0x8c, 0xac, 0x63, 0xae, 0x6e, 0x3e, 0x81, 0x84, 0x49, 0x59, 0x78, 0xc5, 0x90, + 0x70, 0x93, 0xee, 0x2e, 0x3d, 0x2b, 0xac, 0xdb, 0x66, 0xc3, 0x69, 0x18, 0xcb, 0xe1, 0xcd, 0x81, + 0xe4, 0x0b, 0x30, 0x84, 0x5d, 0x97, 0x3d, 0xb5, 0x69, 0xf2, 0x94, 0x1f, 0x62, 0x7d, 0x86, 0x0d, + 0x41, 0x4a, 0xf2, 0x69, 0xe1, 0xb8, 0xcd, 0x3b, 0x7f, 0x4a, 0x39, 0xaa, 0xd9, 0x77, 0x84, 0xc7, + 0x7b, 0x14, 0x81, 0x48, 0x99, 0x3d, 0x7f, 0x33, 0x0b, 0x85, 0xf8, 0xd2, 0x26, 0xef, 0xc3, 0x84, + 0x3c, 0x7e, 0x57, 0x6c, 0x91, 0xf5, 0x62, 0x42, 0x64, 0x9d, 0x90, 0x67, 0xf0, 0xbe, 0xad, 0x9a, + 0xa0, 0x99, 0x1a, 0x01, 0x93, 0x85, 0x36, 0x45, 0x44, 0x40, 0x65, 0x51, 0x05, 0x6e, 0xd0, 0x89, + 0xc5, 0x51, 0x96, 0x68, 0xe4, 0x4d, 0xc8, 0xad, 0xdd, 0x2e, 0x09, 0x07, 0xbf, 0x42, 0xfc, 0x90, + 0xe6, 0x96, 0xb2, 0xba, 0xdd, 0x2e, 0xc3, 0x27, 0xab, 0x4a, 0xc8, 0xf1, 0x11, 0xcd, 0xdc, 0x50, + 0x82, 0xc3, 0xc6, 0x9d, 0x1e, 0x7b, 0xfc, 0xee, 0x50, 0x3e, 0x57, 0x18, 0x12, 0x41, 0x74, 0xff, + 0xfb, 0x1c, 0x8c, 0x85, 0xf5, 0x13, 0xa2, 0xba, 0x4d, 0x73, 0x17, 0x69, 0x72, 0x11, 0xf2, 0x52, + 0xba, 0x13, 0x7e, 0x7e, 0xa3, 0xbe, 0x90, 0xec, 0xe6, 0x41, 0x8a, 0x71, 0x7c, 0x57, 0x30, 0xe5, + 0x4f, 0x72, 0x03, 0x42, 0x19, 0xad, 0x97, 0x30, 0x37, 0xc4, 0x06, 0xcc, 0x0c, 0xd1, 0xc8, 0x14, + 0x64, 0x1d, 0x1e, 0x98, 0x6d, 0xcc, 0xcc, 0x3a, 0x0d, 0xf2, 0x3e, 0xe4, 0xed, 0x46, 0x83, 0x36, + 0x2c, 0x5b, 0xda, 0x66, 0xf5, 0x9b, 0x34, 0x79, 0xc6, 0x8d, 0x9f, 0x19, 0x48, 0x55, 0x0a, 0x48, + 0x09, 0xc6, 0x9a, 0x36, 0xb7, 0xf6, 0x6c, 0x0c, 0x70, 0x00, 0x45, 0x1c, 0xf2, 0x8c, 0x6c, 0xcb, + 0xa7, 0x0d, 0xf2, 0x2a, 0x0c, 0xb1, 0xd1, 0x14, 0x27, 0x8e, 0x14, 0x2a, 0xd9, 0x60, 0xf2, 0x0e, + 0x5b, 0x79, 0xc6, 0x44, 0x04, 0xf2, 0x32, 0xe4, 0xba, 0x8b, 0xbb, 0xe2, 0x2c, 0x29, 0x44, 0xe1, + 0xff, 0x43, 0x34, 0x56, 0x4c, 0x6e, 0x42, 0xfe, 0x50, 0x8f, 0x1c, 0x7f, 0x2e, 0x36, 0x8c, 0x21, + 0x7e, 0x88, 0x48, 0x5e, 0x85, 0x9c, 0xef, 0xbb, 0xc2, 0xa0, 0x69, 0x36, 0xb4, 0x32, 0xbd, 0x1f, + 0x8e, 0x1a, 0xe3, 0xee, 0xfb, 0xee, 0x52, 0x1e, 0x46, 0xf8, 0x01, 0x63, 0xbc, 0x00, 0x10, 0x7d, + 0x63, 0xd2, 0x6f, 0xd3, 0xf8, 0x0a, 0x8c, 0x85, 0xdf, 0x46, 0x9e, 0x07, 0x38, 0xa0, 0x47, 0xd6, + 0xbe, 0xdd, 0x6e, 0x34, 0xb9, 0x74, 0x3a, 0x61, 0x8e, 0x1d, 0xd0, 0xa3, 0x15, 0x04, 0x90, 0x0b, + 0x30, 0xda, 0x61, 0xc3, 0x2f, 0xe6, 0xf8, 0x84, 0x39, 0xd2, 0xe9, 0xee, 0xb0, 0xa9, 0x3c, 0x0f, + 0xa3, 0xa8, 0x67, 0x15, 0x2b, 0x72, 0xd2, 0x94, 0x3f, 0x8d, 0x3f, 0xcb, 0x61, 0x7a, 0x25, 0xa5, + 0x41, 0xe4, 0x25, 0x98, 0xac, 0x7b, 0x14, 0xcf, 0x32, 0x9b, 0x49, 0x68, 0xa2, 0x9e, 0x89, 0x08, + 0x58, 0x6d, 0x90, 0xcb, 0x30, 0xdd, 0xe9, 0xee, 0x34, 0x9d, 0x3a, 0xab, 0xcd, 0xaa, 0xef, 0x88, + 0x7c, 0x10, 0x13, 0xe6, 0x24, 0x07, 0xdf, 0xa3, 0x47, 0xe5, 0x1d, 0x8c, 0x3c, 0x58, 0x50, 0x03, + 0x47, 0x07, 0x61, 0xe2, 0x7b, 0x73, 0x5a, 0x81, 0xa3, 0x6d, 0xe6, 0x79, 0x18, 0xb1, 0xed, 0xbd, + 0xae, 0xc3, 0x23, 0x84, 0x4d, 0x98, 0xe2, 0x17, 0xf9, 0x14, 0xcc, 0x44, 0xb1, 0xcc, 0x65, 0x33, + 0x86, 0xb1, 0x19, 0x85, 0xb0, 0xa0, 0xcc, 0xe1, 0xe4, 0x0d, 0x20, 0x6a, 0x7d, 0xee, 0xce, 0x47, + 0xb4, 0xce, 0xe7, 0xe4, 0x84, 0x39, 0xa3, 0x94, 0xdc, 0xc7, 0x02, 0xf2, 0x22, 0x4c, 0x78, 0xd4, + 0x47, 0xe9, 0x10, 0xbb, 0x0d, 0xb3, 0x0f, 0x9a, 0xe3, 0x12, 0xc6, 0xfa, 0xee, 0x0a, 0x14, 0x94, + 0xee, 0xc0, 0xd8, 0xdc, 0x3c, 0x19, 0x82, 0x39, 0x15, 0xc1, 0xcd, 0x4e, 0xb5, 0x41, 0xbe, 0x08, + 0x0b, 0x0a, 0x26, 0x4f, 0x84, 0x68, 0xd1, 0xa6, 0xb3, 0xe7, 0xec, 0x34, 0xa9, 0x98, 0x6f, 0xc9, + 0x59, 0x1d, 0x5e, 0x21, 0xcd, 0xf9, 0x88, 0x9a, 0xa7, 0x48, 0x5c, 0x16, 0xb4, 0x64, 0x15, 0xe6, + 0x62, 0x9c, 0x69, 0xc3, 0xea, 0x76, 0x7a, 0x86, 0xe4, 0x8b, 0x78, 0x12, 0x9d, 0x27, 0x6d, 0x6c, + 0x75, 0x8c, 0x6f, 0xc0, 0x84, 0x3a, 0x27, 0x59, 0x27, 0xa8, 0x72, 0x89, 0x98, 0x7d, 0xe3, 0x21, + 0xac, 0xca, 0xee, 0x85, 0x53, 0x11, 0x4a, 0x10, 0xe6, 0xf8, 0x37, 0x27, 0x43, 0x28, 0x0e, 0xe1, + 0x8b, 0x30, 0xd1, 0x70, 0xfc, 0x4e, 0xd3, 0x3e, 0xb2, 0xa2, 0x0c, 0xdf, 0xe6, 0xb8, 0x80, 0xa1, + 0xe2, 0x67, 0x09, 0x66, 0x12, 0xfb, 0xa0, 0x22, 0x69, 0xf0, 0x7d, 0xbd, 0xbf, 0xa4, 0x61, 0xb4, + 0x61, 0x42, 0x3d, 0xd7, 0x4e, 0x49, 0x5c, 0x72, 0x1e, 0xc3, 0xf0, 0xf0, 0x4d, 0x7f, 0xe4, 0xe4, + 0xb8, 0x98, 0x75, 0x1a, 0x18, 0x7c, 0xe7, 0x0a, 0xe4, 0xa5, 0xc4, 0x26, 0x04, 0x25, 0x7c, 0x4c, + 0x90, 0x4f, 0x93, 0x66, 0x58, 0x6a, 0xbc, 0x0a, 0xa3, 0xe2, 0xe8, 0xea, 0xff, 0x84, 0x60, 0xfc, + 0x70, 0x16, 0xa6, 0x4d, 0xca, 0x36, 0x56, 0xca, 0xb3, 0x15, 0x3d, 0xb5, 0x57, 0xf4, 0xf4, 0x60, + 0xae, 0x5a, 0xdb, 0xfa, 0xe4, 0x09, 0xfa, 0xd5, 0x0c, 0xcc, 0xa6, 0xe0, 0x7e, 0xac, 0x3c, 0xb9, + 0xb7, 0x60, 0xac, 0xe2, 0xd8, 0xcd, 0x52, 0xa3, 0x11, 0xc6, 0xe4, 0x41, 0x39, 0x1f, 0x93, 0x69, + 0xd9, 0x0c, 0xaa, 0x0a, 0x31, 0x21, 0x2a, 0x79, 0x4d, 0x4c, 0x8a, 0x28, 0xcb, 0x3c, 0x4e, 0x8a, + 0x6f, 0x1f, 0x17, 0x81, 0x7f, 0xd3, 0x66, 0x38, 0x45, 0x30, 0xc0, 0x32, 0x07, 0x46, 0x7e, 0x55, + 0x4f, 0xed, 0xd0, 0xa5, 0x07, 0x58, 0x8e, 0x37, 0x6f, 0xa0, 0x54, 0x41, 0x3f, 0x91, 0x85, 0xf3, + 0xe9, 0x84, 0x1f, 0x37, 0xe5, 0x31, 0x26, 0x69, 0x52, 0x82, 0xc2, 0x63, 0xca, 0x63, 0x9e, 0xd1, + 0x09, 0xf1, 0x23, 0x04, 0xb2, 0x0b, 0x93, 0xab, 0xb6, 0x1f, 0xac, 0x50, 0xdb, 0x0b, 0x76, 0xa8, + 0x1d, 0x0c, 0x20, 0xc9, 0x4b, 0x6b, 0x8a, 0x79, 0x14, 0x26, 0xf6, 0x25, 0x65, 0x4c, 0xd6, 0xd6, + 0xd9, 0x86, 0x13, 0x65, 0x68, 0x80, 0x89, 0xf2, 0x75, 0x98, 0xae, 0xd1, 0x96, 0xdd, 0xd9, 0x77, + 0x3d, 0x19, 0x2f, 0xe1, 0x1a, 0x4c, 0x86, 0xa0, 0xd4, 0xd9, 0xa2, 0x17, 0x6b, 0xf8, 0x4a, 0x47, + 0x44, 0x5b, 0x89, 0x5e, 0x6c, 0xfc, 0xad, 0x2c, 0x5c, 0x28, 0xd5, 0x85, 0x69, 0xa8, 0x28, 0x90, + 0x16, 0xec, 0x9f, 0x70, 0xdd, 0xe4, 0x3a, 0x8c, 0xad, 0xd9, 0x8f, 0x56, 0xa9, 0xed, 0x53, 0x5f, + 0x24, 0x9c, 0xe4, 0x62, 0xaf, 0xfd, 0x28, 0x7a, 0xfc, 0x31, 0x23, 0x1c, 0x55, 0x8d, 0x30, 0xf4, + 0x98, 0x6a, 0x04, 0x03, 0x46, 0x56, 0xdc, 0x66, 0x43, 0x9c, 0xf5, 0xe2, 0xc5, 0x79, 0x1f, 0x21, + 0xa6, 0x28, 0x31, 0xfe, 0x24, 0x03, 0x53, 0xe1, 0x17, 0xe3, 0x27, 0x7c, 0xe2, 0x5d, 0x72, 0x19, + 0x46, 0xb1, 0xa2, 0x30, 0x33, 0x3e, 0x1e, 0x1a, 0x4d, 0x8a, 0x69, 0x03, 0x1b, 0xa6, 0x2c, 0x54, + 0x7b, 0x62, 0xf8, 0xf1, 0x7a, 0xc2, 0xf8, 0xfb, 0xf8, 0x98, 0xad, 0xb6, 0x92, 0x9d, 0x44, 0xca, + 0x87, 0x64, 0x06, 0xfc, 0x90, 0xec, 0x13, 0x1b, 0x92, 0x5c, 0xcf, 0x21, 0xf9, 0x91, 0x2c, 0x8c, + 0x87, 0x1f, 0xfb, 0x1d, 0x96, 0x99, 0x20, 0x6c, 0xd7, 0x40, 0x31, 0x8e, 0x6a, 0xca, 0x5e, 0x21, + 0x42, 0x09, 0x7d, 0x01, 0x46, 0xc4, 0x62, 0xca, 0xc4, 0x2c, 0xb9, 0x63, 0xa3, 0xbb, 0x34, 0x25, + 0x58, 0x8f, 0xe0, 0x80, 0xfa, 0xa6, 0xa0, 0xc3, 0x20, 0x52, 0xdb, 0x74, 0x47, 0xd8, 0x36, 0x3c, + 0xb5, 0x67, 0x54, 0x7a, 0x10, 0xa9, 0xa8, 0x61, 0x03, 0x9d, 0x4e, 0xff, 0x2c, 0x0f, 0x85, 0x38, + 0xc9, 0xe9, 0xb9, 0x1f, 0x36, 0xba, 0x3b, 0xfc, 0xaa, 0xc2, 0x73, 0x3f, 0x74, 0xba, 0x3b, 0x26, + 0x83, 0xa1, 0xe9, 0x93, 0xe7, 0x3c, 0xc4, 0x56, 0x4f, 0x08, 0xd3, 0x27, 0xcf, 0x79, 0xa8, 0x99, + 0x3e, 0x79, 0xce, 0x43, 0x54, 0x24, 0xac, 0xd6, 0x30, 0xc0, 0x02, 0xde, 0x53, 0x84, 0x22, 0xa1, + 0xe9, 0xc7, 0xf3, 0xb8, 0x49, 0x34, 0x76, 0x54, 0x2e, 0x51, 0xdb, 0x13, 0x79, 0x0a, 0xc4, 0x76, + 0x86, 0x47, 0xe5, 0x0e, 0x82, 0xad, 0x80, 0xc1, 0x4d, 0x15, 0x89, 0x34, 0x81, 0x28, 0x3f, 0xe5, + 0x02, 0x3e, 0xfd, 0x6e, 0x2d, 0xad, 0x30, 0xe7, 0x54, 0xd6, 0x96, 0xba, 0x9a, 0x53, 0xf8, 0x3e, + 0x49, 0xed, 0xef, 0x86, 0x08, 0xbe, 0x8a, 0x0a, 0xa4, 0xfc, 0xa9, 0xcc, 0x64, 0x60, 0x18, 0xe0, + 0xc1, 0x59, 0x43, 0x35, 0x52, 0xc4, 0x84, 0xbc, 0x07, 0xe3, 0x6a, 0xd8, 0x0c, 0x1e, 0xdc, 0xe1, + 0x39, 0x1e, 0x4f, 0xb3, 0x47, 0xe6, 0x5f, 0x95, 0x80, 0xec, 0xc0, 0x85, 0xb2, 0xdb, 0xf6, 0xbb, + 0x2d, 0x19, 0xb9, 0x33, 0x8a, 0x17, 0x0e, 0x38, 0x14, 0xe8, 0x83, 0x5f, 0x17, 0x28, 0x22, 0x4a, + 0x83, 0x74, 0x93, 0xd1, 0x2f, 0x20, 0xbd, 0x18, 0x91, 0x4d, 0x18, 0x47, 0x0d, 0xaa, 0x30, 0x79, + 0x1c, 0xd7, 0xb7, 0x8d, 0xa8, 0xa4, 0xc2, 0x16, 0x06, 0x8f, 0x1a, 0x67, 0xb7, 0x9a, 0xd2, 0x4b, + 0x43, 0xd5, 0x04, 0x2b, 0xc8, 0xe4, 0x2b, 0x30, 0xc5, 0xaf, 0x68, 0xdb, 0x74, 0x87, 0xcf, 0x9d, + 0x09, 0x4d, 0x13, 0xa1, 0x17, 0xf2, 0xc7, 0x7c, 0xa1, 0xb7, 0x3e, 0xa4, 0x3b, 0x7c, 0xec, 0x35, + 0x1f, 0x29, 0x0d, 0x9f, 0x6c, 0xc1, 0xec, 0x8a, 0xed, 0x73, 0xa0, 0x12, 0xff, 0x60, 0x12, 0x35, + 0xb4, 0x68, 0xbb, 0xbe, 0x6f, 0xfb, 0x52, 0x11, 0x9e, 0x1a, 0xef, 0x20, 0x8d, 0x9e, 0xfc, 0x70, + 0x06, 0xe6, 0x35, 0x3d, 0xb9, 0xb0, 0x33, 0x6b, 0xd1, 0x76, 0x80, 0xce, 0x50, 0x53, 0x8b, 0x45, + 0x29, 0x94, 0xf6, 0x40, 0xe3, 0x43, 0x12, 0x53, 0xc5, 0x7b, 0x51, 0xb9, 0x6a, 0x14, 0xde, 0x8b, + 0x87, 0x58, 0xa8, 0xb8, 0xa6, 0xa7, 0xf5, 0x85, 0x1a, 0x5b, 0xd7, 0x12, 0xcd, 0xb8, 0x15, 0xef, + 0x6f, 0xa1, 0xe8, 0xca, 0x84, 0x8a, 0xae, 0x39, 0x18, 0xc6, 0x5e, 0x95, 0x51, 0xb4, 0xf0, 0x87, + 0xf1, 0x69, 0x75, 0x1f, 0x12, 0x62, 0x61, 0xdf, 0x7d, 0xc8, 0xf8, 0x1f, 0x47, 0x60, 0x3a, 0x36, + 0x2d, 0xc4, 0x3d, 0x35, 0x93, 0xb8, 0xa7, 0xd6, 0x00, 0xb8, 0xaa, 0x77, 0x40, 0x9d, 0xac, 0x74, + 0xc4, 0x1c, 0x17, 0x6e, 0xd4, 0xe1, 0x9a, 0x52, 0xd8, 0x30, 0xa6, 0x7c, 0xc5, 0x0e, 0xa8, 0x23, + 0x0f, 0x99, 0xf2, 0x45, 0xaf, 0x30, 0x8d, 0xd8, 0x90, 0x22, 0x0c, 0x63, 0xfc, 0x5c, 0xd5, 0x0f, + 0xd6, 0x61, 0x00, 0x93, 0xc3, 0xc9, 0x4b, 0x30, 0xc2, 0x84, 0xa8, 0x6a, 0x45, 0x6c, 0x82, 0x78, + 0xb6, 0x30, 0x29, 0x8b, 0x49, 0x2c, 0xa2, 0x88, 0xdc, 0x82, 0x09, 0xfe, 0x97, 0x08, 0xb3, 0x33, + 0xa2, 0x1b, 0x3f, 0x5a, 0x4e, 0x43, 0x46, 0xda, 0xd1, 0xf0, 0xd8, 0xed, 0xa2, 0xd6, 0x45, 0xb5, + 0x4e, 0xb5, 0x22, 0x02, 0xae, 0xe3, 0xed, 0xc2, 0xe7, 0x40, 0x56, 0x45, 0x84, 0xc0, 0x64, 0x19, + 0xe1, 0x8d, 0x92, 0xc7, 0x3b, 0x25, 0xca, 0x32, 0xdc, 0x0b, 0xc5, 0x14, 0x25, 0xe4, 0x2a, 0x7f, + 0x89, 0x41, 0xb1, 0x90, 0xe7, 0xad, 0xc4, 0x77, 0x0b, 0x54, 0x4c, 0xa0, 0x6c, 0x18, 0x16, 0xb3, + 0xca, 0xd9, 0xdf, 0xcb, 0x2d, 0xdb, 0x69, 0x8a, 0x6d, 0x05, 0x2b, 0x47, 0x5c, 0xca, 0xa0, 0x66, + 0x84, 0x40, 0xde, 0x81, 0x29, 0xf6, 0xa3, 0xec, 0xb6, 0x5a, 0x6e, 0x1b, 0xd9, 0x8f, 0x47, 0x81, + 0xf4, 0x90, 0xa4, 0x8e, 0x45, 0xbc, 0x96, 0x18, 0x2e, 0x3b, 0x4f, 0xf0, 0x95, 0xb7, 0xcb, 0xdf, + 0x88, 0x26, 0xa2, 0xf3, 0x04, 0x49, 0x7d, 0x0e, 0x37, 0x55, 0x24, 0xf2, 0x16, 0x4c, 0xb2, 0x9f, + 0x77, 0x9c, 0x87, 0x94, 0x57, 0x38, 0x19, 0x99, 0x37, 0x20, 0xd5, 0x1e, 0x2b, 0xe1, 0xf5, 0xe9, + 0x98, 0xe4, 0x03, 0x38, 0x87, 0x9c, 0xea, 0x6e, 0x87, 0x36, 0x4a, 0xbb, 0xbb, 0x4e, 0xd3, 0xe1, + 0xd6, 0x68, 0x3c, 0xa0, 0x0c, 0xea, 0xe0, 0x79, 0xc5, 0x88, 0x61, 0xd9, 0x11, 0x8a, 0x99, 0x4e, + 0x49, 0xb6, 0xa1, 0x50, 0xee, 0xfa, 0x81, 0xdb, 0x2a, 0x05, 0x81, 0xe7, 0xec, 0x74, 0x03, 0xea, + 0xcf, 0x4f, 0x6b, 0x61, 0x57, 0xd8, 0xe2, 0x08, 0x0b, 0xb9, 0x3e, 0xa8, 0x8e, 0x14, 0x96, 0x1d, + 0x92, 0x98, 0x09, 0x26, 0xc6, 0x3f, 0xcf, 0xc0, 0xa4, 0x46, 0x4a, 0xde, 0x84, 0x89, 0xdb, 0x9e, + 0x43, 0xdb, 0x8d, 0xe6, 0x91, 0x72, 0x51, 0xc5, 0x5b, 0xcc, 0xae, 0x80, 0xf3, 0x56, 0x6b, 0x68, + 0xa1, 0x9e, 0x27, 0x9b, 0x6a, 0x2a, 0x7a, 0x9d, 0xbb, 0x63, 0x8b, 0x09, 0x9a, 0x8b, 0xe2, 0x40, + 0xe1, 0x04, 0x15, 0xb3, 0x53, 0x41, 0x21, 0xef, 0xc2, 0x08, 0x7f, 0x0f, 0x16, 0x76, 0x8b, 0x17, + 0xd3, 0x9a, 0xc9, 0x5d, 0xff, 0x71, 0x22, 0xa2, 0xd1, 0x8f, 0x6f, 0x0a, 0x22, 0xe3, 0xe7, 0x32, + 0x40, 0x92, 0xa8, 0xa7, 0xe8, 0xbd, 0x4e, 0x35, 0x26, 0xfa, 0x42, 0xb8, 0x1a, 0x73, 0x9a, 0xce, + 0x9c, 0xd5, 0xc4, 0x0b, 0x78, 0xc7, 0x8b, 0x55, 0xa7, 0x2a, 0xe2, 0x78, 0xb1, 0xf1, 0x43, 0x59, + 0x80, 0x08, 0x9b, 0x7c, 0x8e, 0xa7, 0x29, 0xfb, 0xa0, 0x6b, 0x37, 0x9d, 0x5d, 0x47, 0x8f, 0xdb, + 0x8b, 0x4c, 0xbe, 0x2e, 0x4b, 0x4c, 0x1d, 0x91, 0xbc, 0x0f, 0xd3, 0xb5, 0x0d, 0x9d, 0x56, 0x31, + 0x8b, 0xf7, 0x3b, 0x56, 0x8c, 0x3c, 0x8e, 0x8d, 0xf6, 0xc9, 0xea, 0x68, 0x70, 0xfb, 0x64, 0x3e, + 0x10, 0xa2, 0x84, 0x6d, 0x2c, 0xb5, 0x0d, 0x61, 0xf9, 0xdf, 0x08, 0x5f, 0x35, 0xf1, 0xeb, 0xfc, + 0x8e, 0xd5, 0x11, 0x2e, 0x01, 0x6c, 0x9f, 0xd0, 0xf0, 0xa2, 0x8e, 0x1c, 0xee, 0xe1, 0xde, 0xff, + 0xf3, 0xa8, 0xf6, 0x6b, 0xb9, 0x01, 0x15, 0xda, 0x8e, 0xa7, 0xf6, 0xde, 0x13, 0x19, 0x13, 0x0c, + 0x6b, 0x5e, 0xcb, 0x5a, 0xeb, 0x84, 0xc1, 0xcc, 0xcd, 0xe8, 0x92, 0xc2, 0xcd, 0x0a, 0x52, 0x6c, + 0x6c, 0xfe, 0x6e, 0x06, 0xce, 0xa5, 0xd2, 0x92, 0x6b, 0x00, 0x91, 0x4e, 0x49, 0xf4, 0x12, 0xee, + 0x98, 0x51, 0xf4, 0x23, 0x53, 0xc1, 0x20, 0x5f, 0x8e, 0x6b, 0x83, 0x4e, 0x3f, 0x08, 0x17, 0x64, + 0xd0, 0x41, 0x5d, 0x1b, 0x94, 0xa2, 0x03, 0x32, 0x7e, 0x35, 0x07, 0x33, 0x4a, 0x70, 0x25, 0xfe, + 0xad, 0xa7, 0xd8, 0x8b, 0x1f, 0xc0, 0x04, 0x6b, 0x8d, 0x53, 0x17, 0x6e, 0x37, 0xdc, 0xf0, 0xe5, + 0xb5, 0x84, 0xdf, 0xa9, 0xe0, 0x76, 0x4d, 0x45, 0xe6, 0xa1, 0x40, 0x71, 0xeb, 0xc4, 0x07, 0x89, + 0x7a, 0xd2, 0xe5, 0x46, 0x63, 0x4e, 0x7c, 0x98, 0xac, 0x1c, 0xb5, 0xed, 0x56, 0x58, 0x1b, 0x37, + 0x80, 0xf9, 0x54, 0xcf, 0xda, 0x34, 0x6c, 0x5e, 0x5d, 0xe4, 0xa1, 0xc5, 0xcb, 0x52, 0x82, 0x03, + 0x68, 0x54, 0x0b, 0xef, 0xc3, 0x4c, 0xe2, 0xa3, 0xcf, 0x14, 0x95, 0x74, 0x1b, 0x48, 0xf2, 0x3b, + 0x52, 0x38, 0x7c, 0x4a, 0x8f, 0x79, 0x7b, 0x2e, 0x7c, 0xbc, 0x6e, 0xb5, 0xec, 0x76, 0x83, 0x9b, + 0xd3, 0x2c, 0xaa, 0x31, 0x4b, 0x7f, 0x3e, 0xab, 0xfa, 0xfe, 0x3e, 0xed, 0xab, 0xee, 0x0b, 0xda, + 0x6d, 0xf8, 0x85, 0x5e, 0x63, 0x3a, 0x90, 0xd6, 0xe1, 0x5b, 0x39, 0xb8, 0xd0, 0x83, 0x92, 0x1c, + 0xc5, 0x27, 0x11, 0xd7, 0x42, 0xdc, 0xe8, 0x5f, 0xe1, 0x93, 0x98, 0x4a, 0xe4, 0x73, 0x3c, 0xfa, + 0x47, 0xdd, 0x6d, 0xef, 0x3a, 0x7b, 0xe2, 0xfe, 0x8d, 0x6a, 0xfc, 0x83, 0x10, 0x1a, 0x0f, 0xfb, + 0xc1, 0xa1, 0xe4, 0x7d, 0x18, 0x46, 0xc7, 0xef, 0x58, 0x78, 0x47, 0x86, 0x81, 0x70, 0x25, 0x40, + 0x29, 0xfb, 0xa9, 0x05, 0x28, 0x65, 0x00, 0xf2, 0x59, 0xc8, 0x95, 0xb6, 0x6b, 0x62, 0x5c, 0xa6, + 0x54, 0xf2, 0xed, 0x5a, 0x94, 0x5c, 0xc5, 0xd6, 0xb2, 0xa0, 0x30, 0x0a, 0x46, 0x78, 0xa7, 0xbc, + 0x21, 0x46, 0x45, 0x25, 0xbc, 0x53, 0xde, 0x88, 0x08, 0xf7, 0xea, 0x5a, 0xb0, 0xac, 0x3b, 0xe5, + 0x8d, 0x4f, 0x6e, 0xda, 0xff, 0x7b, 0x59, 0x1e, 0xb2, 0x84, 0x37, 0xec, 0x7d, 0x98, 0xd0, 0x62, + 0x92, 0x67, 0x22, 0x79, 0x2c, 0x8c, 0x1f, 0x1f, 0xb3, 0x18, 0xd2, 0x08, 0x64, 0x9a, 0x22, 0xf6, + 0x1b, 0x25, 0x5e, 0xd5, 0xd8, 0x26, 0xe4, 0x80, 0x32, 0x71, 0x3c, 0x4d, 0x51, 0x48, 0x42, 0x6e, + 0x42, 0x7e, 0x93, 0xb6, 0xed, 0x76, 0x10, 0x2a, 0x44, 0xd1, 0xb8, 0x38, 0x40, 0x98, 0x2e, 0x35, + 0x84, 0x88, 0x68, 0x08, 0xdb, 0xdd, 0xf1, 0xeb, 0x9e, 0x83, 0xa1, 0x8d, 0xc2, 0xb3, 0x98, 0x1b, + 0xc2, 0x2a, 0x25, 0x3a, 0x83, 0x18, 0x91, 0xf1, 0xf3, 0x19, 0x18, 0x15, 0x03, 0xc9, 0xd3, 0xcb, + 0xed, 0x45, 0x67, 0x89, 0x70, 0x1e, 0xd8, 0x73, 0xe2, 0xce, 0x03, 0x7b, 0x3c, 0x7e, 0xd0, 0x98, + 0x70, 0xac, 0x0b, 0x9f, 0x06, 0x71, 0x36, 0x4a, 0xb7, 0x4f, 0x3d, 0x7b, 0x58, 0x88, 0x3a, 0xa8, + 0x43, 0x96, 0xf1, 0xb7, 0xc5, 0x97, 0xdd, 0x29, 0x6f, 0x90, 0x45, 0xc8, 0xaf, 0xba, 0x3c, 0x14, + 0x96, 0x9a, 0x2b, 0xb8, 0x29, 0x60, 0x6a, 0x07, 0x49, 0x3c, 0xf6, 0x7d, 0x1b, 0x9e, 0x2b, 0xee, + 0x32, 0xca, 0xf7, 0x75, 0x38, 0x30, 0xf6, 0x7d, 0x21, 0xea, 0xc0, 0xdf, 0x47, 0x53, 0x36, 0x89, + 0x07, 0x37, 0x31, 0x7f, 0xcb, 0x5d, 0xd5, 0xd1, 0x4d, 0x14, 0xc9, 0x9d, 0x62, 0xa1, 0xd7, 0x4e, + 0xf1, 0xe0, 0xa6, 0x99, 0x42, 0x85, 0xef, 0x6a, 0x11, 0xb8, 0x46, 0xbd, 0x87, 0x4f, 0xf1, 0x2e, + 0x9d, 0xfe, 0xae, 0x16, 0x6f, 0xde, 0x40, 0x9b, 0xf4, 0x1f, 0x64, 0xe1, 0x7c, 0x3a, 0xa1, 0xda, + 0x96, 0x4c, 0x9f, 0xb6, 0x5c, 0x81, 0xfc, 0x8a, 0xeb, 0x07, 0x8a, 0x91, 0x20, 0xaa, 0xff, 0xf7, + 0x05, 0xcc, 0x0c, 0x4b, 0xd9, 0x9d, 0x9b, 0xfd, 0x1d, 0x2e, 0x4f, 0xe4, 0x87, 0x81, 0x3a, 0xd8, + 0x9d, 0x9b, 0x17, 0x91, 0x3b, 0x90, 0x37, 0x85, 0xa3, 0x55, 0xac, 0x6b, 0x24, 0x38, 0x94, 0xa6, + 0x88, 0x27, 0x20, 0x5a, 0x68, 0x78, 0x01, 0x23, 0x25, 0x18, 0x15, 0xa3, 0x1f, 0x7b, 0x3a, 0x4e, + 0x99, 0x32, 0x7a, 0xb6, 0x06, 0x49, 0xc7, 0x76, 0x14, 0x7c, 0x04, 0xac, 0x56, 0xa4, 0xcf, 0x14, + 0xee, 0x28, 0xfc, 0x91, 0x50, 0xb7, 0xc7, 0x0c, 0x11, 0x8d, 0x1f, 0xce, 0x02, 0x48, 0xad, 0xcd, + 0x53, 0x3b, 0xc3, 0x3e, 0xab, 0xcd, 0x30, 0xc5, 0xde, 0x68, 0xf0, 0x74, 0xc8, 0xf7, 0xd1, 0x9c, + 0x67, 0xf0, 0x64, 0xc8, 0x45, 0x18, 0xde, 0x8c, 0x14, 0x5a, 0xc2, 0x25, 0x05, 0xd5, 0xd1, 0x1c, + 0x6e, 0xec, 0xc0, 0xdc, 0x1d, 0x1a, 0x44, 0xea, 0x2d, 0xf9, 0xf4, 0xd8, 0x9f, 0xed, 0xeb, 0x30, + 0x26, 0xf0, 0xc3, 0xfd, 0x8b, 0xeb, 0x62, 0x44, 0xec, 0x1b, 0xd4, 0xc5, 0x48, 0x04, 0xb6, 0x1b, + 0x55, 0x68, 0x93, 0x06, 0xf4, 0x93, 0xad, 0xa6, 0x06, 0x84, 0x37, 0x05, 0x5b, 0x36, 0x58, 0x0d, + 0xa7, 0xf6, 0xcf, 0x03, 0x38, 0x17, 0x7e, 0xfb, 0x93, 0xe4, 0x7b, 0x9d, 0x5d, 0x29, 0x45, 0xa2, + 0x83, 0x88, 0x63, 0x1f, 0xdb, 0x93, 0x47, 0xb0, 0x20, 0x09, 0xb6, 0x9d, 0xd0, 0x70, 0x72, 0x20, + 0x5a, 0xf2, 0x0e, 0x8c, 0x2b, 0x34, 0x22, 0x50, 0x3f, 0xaa, 0xa9, 0x0f, 0x9d, 0x60, 0xdf, 0xf2, + 0x39, 0x5c, 0x55, 0x53, 0x2b, 0xe8, 0xc6, 0x97, 0xe0, 0xd9, 0xd0, 0x6d, 0x28, 0xa5, 0xea, 0x18, + 0xf3, 0xcc, 0xd9, 0x98, 0xaf, 0x47, 0xcd, 0xaa, 0xb6, 0x43, 0xcf, 0x68, 0xc9, 0x9b, 0xa8, 0xcd, + 0x12, 0x8d, 0x79, 0x2e, 0xe1, 0x6b, 0xad, 0xb8, 0x54, 0x1b, 0x6f, 0x2b, 0x1f, 0x9b, 0xc2, 0x50, + 0x23, 0xce, 0xc4, 0x89, 0x7f, 0x38, 0x0b, 0xd3, 0xf7, 0xab, 0x95, 0x72, 0x68, 0x7d, 0xf4, 0x1d, + 0x96, 0xac, 0x59, 0x6b, 0x5b, 0xef, 0xfd, 0xc6, 0xd8, 0x82, 0xd9, 0x58, 0x37, 0xa0, 0xe8, 0xf0, + 0x1e, 0x77, 0x38, 0x09, 0xc1, 0x52, 0x6c, 0x38, 0x9f, 0xc6, 0xfe, 0xc1, 0x4d, 0x33, 0x86, 0x6d, + 0xfc, 0x97, 0x10, 0xe3, 0x2b, 0xb6, 0xb0, 0xd7, 0x61, 0xac, 0xea, 0xfb, 0x5d, 0xea, 0x6d, 0x99, + 0xab, 0xaa, 0xaa, 0xc0, 0x41, 0xa0, 0xd5, 0xf5, 0x9a, 0x66, 0x84, 0x40, 0xae, 0x42, 0x5e, 0x04, + 0x49, 0x97, 0x7b, 0x02, 0x6a, 0x6d, 0xc3, 0x18, 0xeb, 0x66, 0x58, 0x4c, 0xde, 0x84, 0x09, 0xfe, + 0x37, 0x9f, 0x6d, 0xa2, 0xc3, 0x51, 0x39, 0x28, 0xd0, 0xf9, 0xec, 0x34, 0x35, 0x34, 0xf2, 0x1a, + 0xe4, 0x4a, 0x65, 0x53, 0xa8, 0x83, 0x84, 0xdc, 0xe8, 0x59, 0x5c, 0x67, 0xa7, 0x5d, 0x22, 0xca, + 0x26, 0x93, 0xfe, 0x64, 0xb0, 0x09, 0xa1, 0xc9, 0xc6, 0x19, 0x20, 0xb5, 0x4d, 0xb1, 0xc3, 0x0c, + 0x61, 0xe4, 0x3a, 0x8c, 0x56, 0xb8, 0xc9, 0x9c, 0xd0, 0x63, 0xf3, 0x4c, 0x84, 0x1c, 0xa4, 0x05, + 0x57, 0xe0, 0x20, 0x72, 0x55, 0x66, 0x68, 0xcb, 0x47, 0x7e, 0x2b, 0x3d, 0xd2, 0xb0, 0xbd, 0x0e, + 0x23, 0x22, 0x94, 0xf8, 0x98, 0x92, 0xbb, 0x25, 0x1e, 0x42, 0x5c, 0xe0, 0x24, 0x1d, 0x58, 0xe1, + 0x49, 0x3a, 0xb0, 0xee, 0xc0, 0x85, 0x3b, 0xa8, 0xbd, 0xd1, 0x03, 0x62, 0x6d, 0x99, 0x55, 0xa1, + 0x0f, 0xc7, 0x67, 0x20, 0xae, 0xe0, 0x89, 0xc7, 0xd4, 0xb2, 0xba, 0x9e, 0x9a, 0x58, 0xb7, 0x17, + 0x23, 0xf2, 0x45, 0x98, 0x4b, 0x2b, 0x12, 0x5a, 0x73, 0x0c, 0xfd, 0x94, 0x5e, 0x81, 0x1a, 0xfa, + 0x29, 0x8d, 0x03, 0x59, 0x85, 0x02, 0x87, 0x97, 0x1a, 0x2d, 0xa7, 0xcd, 0x35, 0xff, 0x5c, 0xab, + 0x8e, 0x8e, 0x24, 0x82, 0xab, 0xcd, 0x0a, 0xf9, 0x0b, 0x80, 0xe6, 0x7a, 0x14, 0xa3, 0x24, 0x3f, + 0x95, 0x61, 0xb7, 0x39, 0x1e, 0x78, 0x7b, 0xcb, 0x5c, 0xf5, 0x45, 0xd8, 0xc0, 0xf3, 0x91, 0x57, + 0x51, 0x2d, 0xf0, 0x9c, 0xf6, 0x9e, 0x70, 0x2b, 0xda, 0x14, 0x6e, 0x45, 0xef, 0x7c, 0x2c, 0xb7, + 0x22, 0xce, 0xca, 0x3f, 0x39, 0x2e, 0x4e, 0x78, 0xa2, 0x4e, 0x5c, 0x45, 0xda, 0x17, 0xb0, 0xae, + 0x43, 0xdf, 0xda, 0xad, 0x36, 0x0f, 0xfb, 0x4b, 0x1b, 0xbc, 0x91, 0xd3, 0xb8, 0x83, 0x63, 0xd7, + 0x61, 0x4e, 0x10, 0xab, 0x1b, 0x22, 0x24, 0x1a, 0x9a, 0xca, 0x81, 0x5d, 0x3c, 0xa5, 0xeb, 0x0a, + 0xf7, 0xc6, 0x2d, 0x44, 0x17, 0x4f, 0xe9, 0xe7, 0x62, 0xe1, 0x34, 0x52, 0x27, 0x8f, 0x46, 0x42, + 0xae, 0xc3, 0xc8, 0x9a, 0xfd, 0xa8, 0xb4, 0x47, 0x45, 0xe6, 0xcd, 0x49, 0xb9, 0xfd, 0x21, 0x70, + 0x29, 0xff, 0x87, 0xdc, 0xd7, 0xe1, 0x19, 0x53, 0xa0, 0x91, 0xef, 0xcb, 0xc0, 0x79, 0xbe, 0x8c, + 0x65, 0x2b, 0x6b, 0x34, 0x08, 0x58, 0x3f, 0x88, 0xf8, 0x81, 0x97, 0x22, 0x83, 0xed, 0x74, 0x3c, + 0xf4, 0xbc, 0x37, 0xc4, 0xce, 0x10, 0x76, 0x9c, 0x2f, 0x4a, 0xb5, 0x40, 0xcc, 0xa9, 0xf4, 0x64, + 0x13, 0xc6, 0xd7, 0x6e, 0x97, 0xc2, 0x6a, 0x79, 0x74, 0xf6, 0x62, 0xda, 0xee, 0xa8, 0xa0, 0xa5, + 0x79, 0x1a, 0xa8, 0x6c, 0x84, 0x77, 0xc0, 0x67, 0x65, 0x7f, 0x90, 0x37, 0x54, 0x57, 0xd4, 0x1c, + 0x4a, 0xcf, 0xa3, 0x2d, 0xfb, 0x91, 0x65, 0xef, 0x51, 0xed, 0x95, 0x5c, 0x68, 0xaf, 0x7f, 0x36, + 0x03, 0x17, 0x7b, 0x36, 0x99, 0xdc, 0x82, 0x0b, 0x36, 0x77, 0xb0, 0xb6, 0xf6, 0x83, 0xa0, 0xe3, + 0x5b, 0xf2, 0x8a, 0x21, 0x9c, 0x57, 0xcd, 0x73, 0xa2, 0x78, 0x85, 0x95, 0xca, 0x5b, 0x87, 0x4f, + 0xde, 0x87, 0xe7, 0x9c, 0xb6, 0x4f, 0xeb, 0x5d, 0x8f, 0x5a, 0x92, 0x41, 0xdd, 0x69, 0x78, 0x96, + 0x67, 0xb7, 0xf7, 0xa4, 0x27, 0xae, 0x79, 0x51, 0xe2, 0x08, 0x27, 0xee, 0xb2, 0xd3, 0xf0, 0x4c, + 0x44, 0x30, 0xfe, 0x79, 0x06, 0xe6, 0x7b, 0x75, 0x09, 0x99, 0x87, 0x51, 0xaa, 0xe4, 0x69, 0xc9, + 0x9b, 0xf2, 0x27, 0x79, 0x16, 0xa2, 0x9d, 0x5e, 0x9c, 0xfe, 0xf9, 0xba, 0xc8, 0x99, 0x81, 0xa6, + 0xed, 0xea, 0xbe, 0x2e, 0x0c, 0x94, 0x27, 0xea, 0xea, 0xee, 0xfe, 0x3c, 0x40, 0xb4, 0x9d, 0x73, + 0xc5, 0x84, 0x39, 0x66, 0xd7, 0x3d, 0xbe, 0xf2, 0xc8, 0x79, 0x18, 0xe1, 0xdb, 0xa5, 0xf0, 0x7f, + 0x10, 0xbf, 0xd8, 0xb9, 0x2d, 0x3a, 0x19, 0xf7, 0xf9, 0xdc, 0xd2, 0x84, 0xd6, 0xd9, 0x23, 0x2d, + 0x1c, 0x1c, 0xe3, 0xa7, 0x27, 0xb9, 0x08, 0x51, 0xea, 0x06, 0xfb, 0x52, 0xe8, 0x58, 0x4c, 0xf3, + 0x17, 0xe3, 0xb6, 0x94, 0x8a, 0x5d, 0xb6, 0xee, 0x25, 0x26, 0xdf, 0x7e, 0xb2, 0xa9, 0x6f, 0x3f, + 0xaf, 0xc3, 0x58, 0x79, 0x9f, 0xd6, 0x0f, 0x42, 0x27, 0x9c, 0xbc, 0x50, 0xae, 0x33, 0x20, 0x0f, + 0x89, 0x1e, 0x21, 0x90, 0xeb, 0x00, 0xe8, 0xa6, 0xca, 0x25, 0x52, 0x25, 0xad, 0x09, 0x7a, 0xb5, + 0x0a, 0xf3, 0x14, 0x05, 0x05, 0xd9, 0xd7, 0xcc, 0xdb, 0xaa, 0x3d, 0x0b, 0x67, 0xef, 0x7b, 0xbb, + 0x02, 0x3d, 0x42, 0x60, 0xcd, 0x53, 0xf6, 0x15, 0x71, 0x0a, 0x16, 0x12, 0x9b, 0x8f, 0x8a, 0x44, + 0xae, 0xc1, 0xd8, 0x86, 0x74, 0x24, 0xc0, 0x43, 0x70, 0x02, 0x29, 0x20, 0x72, 0x3a, 0x98, 0xcf, + 0x98, 0x11, 0x0a, 0xf9, 0x2c, 0x8c, 0x96, 0xa9, 0x17, 0x6c, 0x6e, 0xae, 0xa2, 0xd1, 0x09, 0xcf, + 0xfe, 0x91, 0xc7, 0x4c, 0x0d, 0x41, 0xd0, 0xfc, 0xf6, 0x71, 0x71, 0x32, 0x70, 0x5a, 0x34, 0x8c, + 0x6a, 0x6e, 0x4a, 0x6c, 0xb2, 0x04, 0x05, 0xfe, 0x2c, 0x1e, 0xdd, 0x3d, 0xf0, 0x64, 0xcc, 0xf3, + 0x73, 0x5a, 0xbc, 0xa1, 0x1f, 0xd2, 0x9d, 0x30, 0x4f, 0x45, 0x02, 0x9f, 0x2c, 0xcb, 0xf4, 0x2e, + 0x6a, 0x33, 0x21, 0x52, 0x86, 0xc5, 0x77, 0x0c, 0xd6, 0xda, 0x24, 0x05, 0x29, 0xc1, 0x64, 0xd9, + 0x6d, 0x75, 0xec, 0xc0, 0xc1, 0x3c, 0x98, 0x47, 0xe2, 0x10, 0x44, 0x85, 0x5e, 0x5d, 0x2d, 0xd0, + 0x4e, 0x54, 0xb5, 0x80, 0xdc, 0x86, 0x29, 0xd3, 0xed, 0xb2, 0x61, 0x92, 0xb7, 0x70, 0x7e, 0xce, + 0xa1, 0x69, 0x88, 0xc7, 0x4a, 0xd8, 0xb1, 0x2c, 0xae, 0xdc, 0x5a, 0x04, 0x58, 0x8d, 0x8a, 0xac, + 0xa7, 0x3c, 0x87, 0xa8, 0x87, 0x9b, 0x9a, 0xad, 0x22, 0xc1, 0x2c, 0xe5, 0x25, 0xe5, 0x26, 0x8c, + 0xd7, 0x6a, 0xf7, 0x37, 0xa9, 0x1f, 0xdc, 0x6e, 0xba, 0x87, 0x78, 0xb6, 0xe5, 0x45, 0x72, 0x35, + 0xdf, 0xb5, 0x02, 0xea, 0x07, 0xd6, 0x6e, 0xd3, 0x3d, 0x34, 0x55, 0x2c, 0xf2, 0x55, 0xd6, 0x1f, + 0x8a, 0x24, 0x28, 0x62, 0xdd, 0xf6, 0x13, 0x56, 0xf1, 0x04, 0x89, 0x16, 0x0d, 0x13, 0x59, 0xf5, + 0xce, 0x52, 0xd0, 0xd1, 0xa7, 0xcc, 0x73, 0x1f, 0x1d, 0x95, 0x1a, 0x0d, 0x8f, 0xfa, 0xbe, 0x38, + 0x84, 0xb8, 0x4f, 0x19, 0x2a, 0x1b, 0x6c, 0x5e, 0xa0, 0xf9, 0x94, 0x29, 0x04, 0xe4, 0x47, 0x33, + 0x70, 0x4e, 0xf5, 0x36, 0xc1, 0xe5, 0x82, 0x66, 0x2e, 0xfc, 0x48, 0x7a, 0xe3, 0x9a, 0x3c, 0x84, + 0xaf, 0x29, 0x68, 0xd7, 0x1e, 0xde, 0xb8, 0x56, 0x8a, 0x7e, 0xd6, 0x24, 0x11, 0xc6, 0xed, 0x2b, + 0xa6, 0xf2, 0xd3, 0x72, 0x13, 0xcd, 0xd9, 0x29, 0xc4, 0xa4, 0xcc, 0x24, 0x35, 0x36, 0xa3, 0xd0, + 0x70, 0xaa, 0xba, 0x81, 0x67, 0x9a, 0xd0, 0xa8, 0x8a, 0xf9, 0xc7, 0x4d, 0xac, 0x9c, 0x8e, 0x2e, + 0x90, 0x29, 0x34, 0xa4, 0x0a, 0xd3, 0x1c, 0xc0, 0xb6, 0x05, 0x9e, 0xe6, 0x69, 0x36, 0x4a, 0x34, + 0x21, 0xd8, 0xe0, 0x5b, 0x3f, 0xa6, 0x7a, 0x52, 0x83, 0xb3, 0xc6, 0xe8, 0xc8, 0xfb, 0x30, 0x85, + 0x31, 0xf4, 0xa3, 0xf5, 0x3a, 0x87, 0xab, 0x18, 0x63, 0xcc, 0x8a, 0x92, 0x98, 0xe7, 0xdd, 0x84, + 0xef, 0xef, 0x47, 0x2b, 0xfa, 0x7d, 0x98, 0x42, 0x5b, 0x9d, 0x88, 0xc1, 0xb9, 0x88, 0x81, 0x28, + 0x89, 0x33, 0x08, 0x9a, 0x7e, 0xc4, 0xe0, 0x67, 0x32, 0x70, 0x91, 0x55, 0x94, 0x3e, 0x42, 0xe7, + 0x3f, 0xce, 0x08, 0x61, 0xd4, 0xcd, 0x9e, 0x3c, 0x55, 0x71, 0xd4, 0xf7, 0xf7, 0xd3, 0x38, 0xe0, + 0x47, 0xb1, 0x8f, 0x4f, 0xff, 0xa8, 0x0b, 0x1f, 0xfb, 0xa3, 0x7a, 0xf2, 0x54, 0x3f, 0x2a, 0x68, + 0xfa, 0x69, 0x1c, 0xf0, 0x5a, 0x5b, 0x2b, 0xad, 0xad, 0x46, 0x77, 0xb3, 0xef, 0x2c, 0xb7, 0x15, + 0xad, 0x6d, 0x7d, 0xdc, 0x56, 0xb6, 0xb8, 0x17, 0xb5, 0xd2, 0x0d, 0xf2, 0x5a, 0xab, 0x81, 0xe3, + 0xd7, 0xda, 0x18, 0x8d, 0x19, 0xc3, 0x36, 0x7e, 0x19, 0x62, 0x7c, 0x85, 0xa9, 0xaa, 0x01, 0x23, + 0xfc, 0xd6, 0x2a, 0x3a, 0x19, 0x6d, 0x16, 0xf8, 0x9d, 0xd6, 0x14, 0x25, 0xe4, 0x22, 0xe4, 0x6a, + 0xb5, 0xfb, 0xa2, 0x93, 0xd1, 0x60, 0xd5, 0xf7, 0x5d, 0x93, 0xc1, 0xd8, 0x08, 0xa1, 0x15, 0xaa, + 0x92, 0x93, 0x80, 0x9d, 0x77, 0x26, 0x42, 0x59, 0x7f, 0xcb, 0x3b, 0xe4, 0x50, 0xd4, 0xdf, 0xe2, + 0x0e, 0x19, 0xdd, 0x1c, 0xcb, 0x30, 0x5f, 0xf2, 0x7d, 0xea, 0xb1, 0x09, 0x21, 0x8c, 0x1b, 0x3d, + 0x71, 0xcf, 0x11, 0x07, 0x3b, 0x56, 0x6a, 0xd7, 0x7d, 0xb3, 0x27, 0x22, 0xb9, 0x02, 0xf9, 0x52, + 0xb7, 0xe1, 0xd0, 0x76, 0x5d, 0x0b, 0xcb, 0x66, 0x0b, 0x98, 0x19, 0x96, 0x92, 0x0f, 0xe0, 0x5c, + 0x2c, 0x02, 0xa3, 0xe8, 0x81, 0xd1, 0x68, 0xef, 0x95, 0xf7, 0xb0, 0xc8, 0x20, 0x83, 0x77, 0x49, + 0x3a, 0x25, 0x29, 0x41, 0x61, 0x19, 0xdd, 0xb4, 0x2a, 0x94, 0xbf, 0x0d, 0xb9, 0x1e, 0xf7, 0xcf, + 0xe3, 0xb7, 0x66, 0x11, 0x67, 0xb2, 0x11, 0x16, 0x9a, 0x09, 0x74, 0x72, 0x0f, 0x66, 0xe3, 0x30, + 0x76, 0x82, 0xf3, 0x0b, 0x32, 0xee, 0x37, 0x09, 0x2e, 0x78, 0x86, 0xa7, 0x51, 0x91, 0x1d, 0x98, + 0x89, 0x0c, 0x92, 0xf4, 0x6b, 0xb3, 0xb4, 0x73, 0x0e, 0xcb, 0xe5, 0xd5, 0xf9, 0x59, 0x31, 0x19, + 0x67, 0x23, 0xe3, 0xa6, 0xf0, 0xfa, 0x6c, 0x26, 0xd9, 0x91, 0x06, 0x4c, 0xd5, 0x9c, 0xbd, 0xb6, + 0xd3, 0xde, 0xbb, 0x47, 0x8f, 0x36, 0x6c, 0xc7, 0x13, 0x16, 0xa7, 0xd2, 0x9e, 0xbc, 0xe4, 0x1f, + 0xb5, 0x5a, 0x34, 0xf0, 0x70, 0x23, 0x64, 0xe5, 0xe8, 0x83, 0xce, 0xae, 0x43, 0x0b, 0x3e, 0xa7, + 0x43, 0xb7, 0xcd, 0x8e, 0xed, 0x68, 0x42, 0x80, 0xce, 0x53, 0x53, 0x5d, 0x4c, 0x0c, 0xa8, 0xba, + 0x68, 0xc2, 0xcc, 0x72, 0xbb, 0xee, 0x1d, 0xe1, 0x13, 0x9d, 0xfc, 0xb8, 0xc9, 0x53, 0x3e, 0xee, + 0x65, 0xf1, 0x71, 0xcf, 0xd9, 0x72, 0x86, 0xa5, 0x7d, 0x5e, 0x92, 0x31, 0xa9, 0xc1, 0x0c, 0x5e, + 0x1c, 0xaa, 0x95, 0x8d, 0x6a, 0xdb, 0x09, 0x1c, 0x3b, 0xa0, 0x0d, 0x21, 0x5c, 0x84, 0x99, 0x5c, + 0xf8, 0x15, 0xd5, 0x69, 0x74, 0x2c, 0x47, 0xa2, 0xa8, 0x4c, 0x13, 0xf4, 0xfd, 0xee, 0x89, 0xd3, + 0x7f, 0x41, 0xf7, 0xc4, 0x2a, 0x4c, 0xc7, 0x43, 0x39, 0x14, 0xa2, 0x73, 0xd8, 0xc7, 0x22, 0x76, + 0x9c, 0xbb, 0x5d, 0x14, 0x26, 0xb5, 0xe4, 0xa9, 0xb1, 0x20, 0x0e, 0xb1, 0x2b, 0xe7, 0x8c, 0x76, + 0xe5, 0xd4, 0x76, 0xa5, 0x33, 0x5c, 0x39, 0xc9, 0x06, 0xc0, 0x6d, 0xd7, 0xab, 0xd3, 0x12, 0xfa, + 0x47, 0x13, 0x2d, 0xdf, 0x15, 0x63, 0x1a, 0x15, 0xf2, 0xf5, 0xb3, 0xcb, 0x7e, 0x5b, 0x71, 0x37, + 0x77, 0x85, 0x87, 0xf1, 0x63, 0x59, 0x98, 0xef, 0xf5, 0x39, 0x7d, 0xae, 0x7b, 0x9f, 0x82, 0xe4, + 0x0a, 0x17, 0xd7, 0xbe, 0x02, 0x8d, 0xaf, 0xf3, 0x45, 0x48, 0x5f, 0xc8, 0xe2, 0x1a, 0x38, 0x1b, + 0x27, 0xd8, 0xf2, 0x9a, 0xe4, 0x16, 0x8c, 0x2b, 0x1f, 0x8f, 0x7b, 0x69, 0xaf, 0xa6, 0x9a, 0xb0, + 0x1b, 0xfe, 0xcd, 0xae, 0x89, 0x7c, 0xdf, 0x92, 0xd7, 0x44, 0xfe, 0x8b, 0x14, 0xb8, 0x8b, 0xf8, + 0x08, 0xb7, 0x02, 0xf0, 0x7d, 0x97, 0x10, 0xc0, 0x7d, 0x9b, 0x6f, 0x81, 0x26, 0xfe, 0x6d, 0xfc, + 0xe6, 0x04, 0x3f, 0x91, 0xd5, 0x5b, 0x62, 0x2f, 0xfb, 0xe0, 0xd8, 0xed, 0x31, 0x7b, 0x96, 0xdb, + 0x63, 0xee, 0xf4, 0xdb, 0xe3, 0xd0, 0x69, 0xb7, 0xc7, 0xd8, 0xf5, 0x6e, 0xf8, 0xcc, 0xd7, 0xbb, + 0x91, 0x33, 0x5d, 0xef, 0x46, 0xcf, 0x74, 0xbd, 0xd3, 0x6e, 0xaa, 0xf9, 0xd3, 0x6e, 0xaa, 0x7f, + 0x75, 0x19, 0x7c, 0x5a, 0x2f, 0x83, 0x69, 0x22, 0xde, 0x99, 0x2e, 0x83, 0x3f, 0xd2, 0xf3, 0x2e, + 0x57, 0xf8, 0x38, 0x42, 0xf9, 0x4b, 0x03, 0xdc, 0xe5, 0x06, 0xbd, 0xc9, 0xcd, 0x3c, 0x99, 0x9b, + 0x1c, 0x79, 0x62, 0x37, 0xb9, 0xd9, 0xc7, 0xbd, 0xc9, 0xcd, 0x3d, 0xc9, 0x9b, 0xdc, 0xb9, 0xbf, + 0x8c, 0x37, 0xb9, 0xf3, 0xff, 0x76, 0x6e, 0x72, 0x7f, 0x0d, 0x0a, 0x71, 0xe1, 0xf2, 0xf4, 0xa8, + 0xc7, 0x4f, 0x2c, 0xe4, 0x24, 0x13, 0x7d, 0xe3, 0xc2, 0x1d, 0xb9, 0x0e, 0xb0, 0xe1, 0x39, 0x0f, + 0xed, 0x80, 0xde, 0x93, 0xd6, 0x6f, 0x22, 0x62, 0x37, 0x87, 0xb2, 0x91, 0x37, 0x15, 0x94, 0xf0, + 0x5e, 0x93, 0x4d, 0xbb, 0xd7, 0x18, 0x3f, 0x9a, 0x85, 0x19, 0x1e, 0xb7, 0xed, 0xe9, 0x7f, 0x84, + 0x7d, 0x4f, 0xbb, 0xad, 0x3e, 0x17, 0xe5, 0x08, 0x50, 0x5b, 0xd7, 0xe7, 0x19, 0xf6, 0x2b, 0x70, + 0x2e, 0xd1, 0x15, 0x78, 0x63, 0xad, 0xc8, 0x88, 0x79, 0x89, 0x3b, 0xeb, 0x7c, 0x7a, 0x25, 0x0f, + 0x6e, 0x9a, 0x09, 0x0a, 0xe3, 0xcf, 0x86, 0x12, 0xfc, 0xc5, 0x83, 0xac, 0xfa, 0xc4, 0x9a, 0x39, + 0xdb, 0x13, 0x6b, 0x76, 0xb0, 0x27, 0xd6, 0x98, 0x50, 0x91, 0x1b, 0x44, 0xa8, 0xf8, 0x00, 0x26, + 0x37, 0xa9, 0xdd, 0xf2, 0x37, 0x5d, 0x91, 0x70, 0x8a, 0xfb, 0x5a, 0xc8, 0x80, 0x78, 0xac, 0x4c, + 0x5e, 0xb8, 0x42, 0x9b, 0xd1, 0x80, 0x11, 0xb0, 0x63, 0x90, 0x67, 0xa0, 0x32, 0x75, 0x0e, 0xea, + 0x2d, 0x7a, 0xb8, 0xcf, 0x2d, 0xba, 0x06, 0x13, 0x82, 0x2e, 0x0a, 0xf5, 0x1c, 0x5d, 0xf7, 0x58, + 0x11, 0xc2, 0x65, 0xed, 0x61, 0x36, 0xfc, 0xb0, 0x76, 0x7e, 0xd3, 0xd3, 0x98, 0xb0, 0x2e, 0x58, + 0x6e, 0x37, 0x3a, 0xae, 0xd3, 0xc6, 0x2e, 0x18, 0x8d, 0xba, 0x80, 0x0a, 0x30, 0xef, 0x02, 0x05, + 0x89, 0xbc, 0x03, 0x53, 0xa5, 0x8d, 0xaa, 0x4a, 0x96, 0x8f, 0x5e, 0x79, 0xed, 0x8e, 0x63, 0x69, + 0xa4, 0x31, 0xdc, 0x7e, 0x37, 0x9f, 0xb1, 0xbf, 0x98, 0x9b, 0x8f, 0xf1, 0x4f, 0x27, 0xe5, 0xf2, + 0xfe, 0x64, 0x1f, 0x48, 0xf4, 0x27, 0x8f, 0xdc, 0x19, 0x9f, 0x3c, 0x86, 0x4e, 0x13, 0x24, 0x35, + 0xf9, 0x76, 0xf8, 0x4c, 0xf2, 0xed, 0xc8, 0x63, 0x3f, 0x5f, 0x8c, 0x9e, 0x51, 0x62, 0x8d, 0xad, + 0xb5, 0xfc, 0x20, 0x6b, 0x2d, 0x55, 0xca, 0x1d, 0x7b, 0x7c, 0x29, 0x17, 0xce, 0x2c, 0xe5, 0xd6, + 0x22, 0xdf, 0xe5, 0xf1, 0x53, 0x5d, 0x42, 0x9e, 0x17, 0x5a, 0x81, 0x99, 0xf4, 0x28, 0x7c, 0xa1, + 0x17, 0xf3, 0x77, 0x94, 0xe8, 0xfc, 0xb5, 0x74, 0xd1, 0xb9, 0xff, 0x79, 0x73, 0x26, 0xe1, 0xf9, + 0x47, 0x9f, 0xac, 0xf0, 0xfc, 0x64, 0x1f, 0x42, 0xfe, 0x4a, 0x7c, 0xfe, 0x2b, 0xf1, 0x79, 0x30, + 0xf1, 0x99, 0xdc, 0x07, 0x62, 0x77, 0x83, 0x7d, 0xda, 0x0e, 0x9c, 0x3a, 0x46, 0xa5, 0x65, 0x43, + 0x8c, 0xaf, 0x32, 0x62, 0xbd, 0x26, 0x4b, 0xd5, 0xf5, 0xaa, 0x95, 0xa2, 0x9f, 0xb7, 0x87, 0xeb, + 0x75, 0xdb, 0xf6, 0xda, 0xa8, 0xc7, 0xba, 0x0e, 0xa3, 0x32, 0xae, 0x69, 0x26, 0x52, 0x51, 0x27, + 0x03, 0x9a, 0x4a, 0x2c, 0xb2, 0x08, 0x79, 0x49, 0xac, 0x26, 0xda, 0x39, 0x14, 0x30, 0x2d, 0x64, + 0xa4, 0x80, 0x19, 0xff, 0xd1, 0x90, 0x3c, 0x13, 0xd8, 0x27, 0x6c, 0xd8, 0x9e, 0xdd, 0xc2, 0x1c, + 0x7c, 0xe1, 0x92, 0x55, 0x6e, 0x03, 0xb1, 0x55, 0x1e, 0xf3, 0x15, 0xd0, 0x49, 0x3e, 0x56, 0x60, + 0xda, 0x28, 0xcd, 0x71, 0x6e, 0x80, 0x34, 0xc7, 0x6f, 0x69, 0x39, 0x82, 0x87, 0xa2, 0xa4, 0x94, + 0x6c, 0x9f, 0xec, 0x9f, 0x1d, 0xf8, 0x96, 0x9a, 0xcc, 0x77, 0x38, 0x0a, 0x13, 0x86, 0x94, 0x7d, + 0xd2, 0xf8, 0x86, 0xd7, 0x9b, 0x91, 0xb3, 0x84, 0x7c, 0x1e, 0xfd, 0xb7, 0x1a, 0xf2, 0x79, 0x19, + 0x40, 0x9c, 0xdd, 0x91, 0xbd, 0xc3, 0x2b, 0xb8, 0x9d, 0x08, 0xbb, 0xe7, 0x20, 0x68, 0xf6, 0xc8, + 0x09, 0xa2, 0x10, 0x1a, 0xbf, 0x4f, 0x60, 0xa6, 0x56, 0xbb, 0x5f, 0x71, 0xec, 0xbd, 0xb6, 0xeb, + 0x07, 0x4e, 0xbd, 0xda, 0xde, 0x75, 0x99, 0x6c, 0x1f, 0x9e, 0x2f, 0x4a, 0xb0, 0xde, 0xe8, 0x6c, + 0x09, 0x8b, 0xd9, 0xdd, 0x71, 0xd9, 0xf3, 0xa4, 0xc2, 0x95, 0xdf, 0x1d, 0x29, 0x03, 0x98, 0x1c, + 0xce, 0xc4, 0xe7, 0x5a, 0x17, 0x43, 0x65, 0x08, 0x23, 0x14, 0x14, 0x9f, 0x7d, 0x0e, 0x32, 0x65, + 0x19, 0xa1, 0xc9, 0x09, 0x2b, 0xae, 0x53, 0x17, 0xb4, 0xc0, 0xd1, 0x51, 0x31, 0x5f, 0x8d, 0x42, + 0xba, 0xc1, 0x7d, 0xb8, 0x83, 0x70, 0xd5, 0xc4, 0x2e, 0xb1, 0x06, 0x8e, 0xe0, 0x9c, 0xe6, 0x44, + 0x3d, 0xe8, 0xeb, 0xcc, 0x6b, 0x42, 0x5c, 0x37, 0x30, 0x66, 0x47, 0xca, 0x13, 0x8d, 0x9a, 0x54, + 0x2f, 0xb5, 0x06, 0x76, 0x40, 0x3e, 0x9f, 0x5a, 0x12, 0xae, 0xee, 0x71, 0x2d, 0x78, 0xb7, 0xb2, + 0x69, 0xf0, 0xf4, 0x81, 0xbd, 0xaa, 0xb6, 0x52, 0xb6, 0x82, 0xfe, 0x35, 0x91, 0x7f, 0x94, 0x81, + 0x0b, 0x1a, 0x46, 0xb8, 0xff, 0xf9, 0x61, 0x7c, 0x91, 0xd4, 0x79, 0xfd, 0xd1, 0x93, 0x99, 0xd7, + 0x2f, 0xe9, 0x6d, 0x89, 0x76, 0x68, 0xb5, 0x0d, 0xbd, 0xbe, 0x90, 0x3c, 0x84, 0x19, 0x2c, 0x92, + 0x2f, 0x45, 0x6c, 0xce, 0x8a, 0x07, 0xa6, 0xb9, 0xe8, 0xb3, 0x79, 0x60, 0x00, 0x4c, 0x01, 0xbf, + 0xf8, 0xad, 0xe3, 0xe2, 0xa4, 0x86, 0x2e, 0xc3, 0x61, 0x5b, 0xd1, 0x73, 0x93, 0xd3, 0xde, 0x75, + 0xb5, 0xfc, 0xfe, 0xf1, 0x2a, 0xc8, 0x7f, 0x9d, 0xe1, 0xef, 0x13, 0xbc, 0x19, 0xb7, 0x3d, 0xb7, + 0x15, 0x96, 0x4b, 0x5b, 0xcd, 0x1e, 0xdd, 0xd6, 0x7c, 0x32, 0xdd, 0xf6, 0x0a, 0x7e, 0x32, 0xdf, + 0x13, 0xac, 0x5d, 0xcf, 0x6d, 0x45, 0x9f, 0xaf, 0x76, 0x5c, 0xcf, 0x8f, 0x24, 0xdf, 0x9f, 0x81, + 0x8b, 0x9a, 0x9a, 0x54, 0xcd, 0x4d, 0x22, 0xc2, 0x2f, 0xcc, 0x86, 0x81, 0x59, 0xa2, 0xa2, 0xa5, + 0x6b, 0x62, 0xfe, 0x5f, 0xc6, 0x2f, 0x50, 0xe2, 0x80, 0x32, 0x24, 0xab, 0xc5, 0xb1, 0x94, 0x4f, + 0xe8, 0x5d, 0x0b, 0x71, 0x60, 0x06, 0xcd, 0x76, 0x34, 0x9b, 0xe2, 0xb9, 0xde, 0x36, 0xc5, 0x61, + 0xd6, 0x21, 0xcc, 0x48, 0xd0, 0xdb, 0xb0, 0x38, 0xc9, 0x95, 0x7c, 0x0f, 0x5c, 0x4c, 0x00, 0xc3, + 0xd5, 0x76, 0xae, 0xe7, 0x6a, 0xfb, 0xd4, 0xc9, 0x71, 0xf1, 0xd5, 0xb4, 0xda, 0xd2, 0x56, 0x5a, + 0xef, 0x1a, 0x88, 0x0d, 0x10, 0x15, 0x0a, 0x79, 0x26, 0x7d, 0x82, 0x7e, 0x4a, 0xcc, 0x0f, 0x05, + 0x9f, 0xed, 0xe5, 0xca, 0x37, 0xa8, 0x47, 0x5e, 0x84, 0x44, 0x28, 0x4c, 0x28, 0xd9, 0x18, 0x8e, + 0x84, 0xf5, 0x48, 0x8f, 0x4a, 0xbe, 0x75, 0x5c, 0xd4, 0xb0, 0xd9, 0x0d, 0x4b, 0x4d, 0xf3, 0xa0, + 0x89, 0x8f, 0x2a, 0x22, 0xf9, 0x8d, 0x0c, 0xcc, 0x31, 0x40, 0x34, 0xa9, 0x44, 0xa3, 0xe6, 0xfb, + 0xcd, 0xfa, 0xfd, 0x27, 0x33, 0xeb, 0x5f, 0xc4, 0x6f, 0x54, 0x67, 0x7d, 0xa2, 0x4b, 0x52, 0x3f, + 0x0e, 0x67, 0xbb, 0x66, 0x21, 0xa6, 0xcd, 0xf6, 0x8b, 0x03, 0xcc, 0x76, 0x3e, 0x00, 0xa7, 0xcf, + 0xf6, 0x9e, 0xb5, 0x90, 0x4d, 0x98, 0x10, 0x97, 0x2b, 0xde, 0x61, 0x2f, 0x68, 0x71, 0xa1, 0xd5, + 0x22, 0x7e, 0xe3, 0x15, 0xc9, 0x2a, 0x12, 0x2d, 0xd4, 0xb8, 0x90, 0x36, 0xcc, 0xf2, 0xdf, 0xba, + 0xb2, 0xab, 0xd8, 0x53, 0xd9, 0x75, 0x45, 0xb4, 0xe8, 0x92, 0xe0, 0x1f, 0xd3, 0x79, 0xa9, 0xf1, + 0x9c, 0x52, 0x18, 0x93, 0x0e, 0x10, 0x0d, 0xcc, 0x17, 0xed, 0xa5, 0xfe, 0x2a, 0xae, 0x57, 0x45, + 0x9d, 0xc5, 0x78, 0x9d, 0xf1, 0x95, 0x9b, 0xc2, 0x9b, 0xd8, 0x30, 0x2d, 0xa0, 0xee, 0x01, 0xe5, + 0x3b, 0xfc, 0x8b, 0x5a, 0x44, 0xad, 0x58, 0x29, 0xbf, 0x95, 0xc9, 0x9a, 0x30, 0xe2, 0x59, 0x6c, + 0x43, 0x8f, 0xf3, 0x23, 0xf7, 0x61, 0xa6, 0xd4, 0xe9, 0x34, 0x1d, 0xda, 0xc0, 0x56, 0x9a, 0x5d, + 0xd6, 0x26, 0x23, 0xca, 0xf7, 0x66, 0xf3, 0x42, 0x71, 0x55, 0xf4, 0xba, 0xb1, 0xed, 0x26, 0x41, + 0x6b, 0xfc, 0x48, 0x26, 0xf1, 0xd1, 0xe4, 0x75, 0x18, 0xc3, 0x1f, 0x4a, 0x90, 0x16, 0xd4, 0x19, + 0xf1, 0x4f, 0x44, 0x6d, 0x54, 0x84, 0xc0, 0x84, 0x25, 0x35, 0x50, 0x63, 0x8e, 0x0b, 0x4b, 0x42, + 0x51, 0x11, 0xa9, 0x26, 0x8a, 0xd2, 0xd7, 0x23, 0x17, 0x09, 0x5d, 0xe8, 0xeb, 0x21, 0x3c, 0x3c, + 0x8c, 0x7f, 0x90, 0xd5, 0xa7, 0x1d, 0xb9, 0xa2, 0xc8, 0xed, 0x4a, 0xa8, 0x48, 0x29, 0xb7, 0x2b, + 0xd2, 0xfa, 0xdf, 0xcd, 0xc0, 0xec, 0x7d, 0x25, 0x51, 0xe8, 0xa6, 0x8b, 0xe3, 0xd2, 0x3f, 0x75, + 0xe6, 0x93, 0x4a, 0x01, 0xa8, 0x66, 0x28, 0x65, 0x33, 0x05, 0xa7, 0x8c, 0x99, 0xf6, 0x3d, 0xe8, + 0x3d, 0x87, 0x1f, 0xa6, 0x64, 0x62, 0xe4, 0xe8, 0x1c, 0x7e, 0xc6, 0xd4, 0x15, 0xc6, 0x4f, 0x64, + 0x61, 0x5c, 0x59, 0x31, 0xe4, 0x33, 0x30, 0xa1, 0x56, 0xab, 0x2a, 0x1c, 0xd5, 0xaf, 0x34, 0x35, + 0x2c, 0xd4, 0x38, 0x52, 0xbb, 0xa5, 0x69, 0x1c, 0xd9, 0xba, 0x40, 0xe8, 0x19, 0x6f, 0x42, 0xef, + 0xa7, 0xdc, 0x84, 0x70, 0x96, 0x2b, 0x1a, 0xa3, 0xbe, 0xf7, 0xa1, 0x77, 0x92, 0xf7, 0x21, 0x54, + 0x5e, 0x29, 0xf4, 0xbd, 0x6f, 0x45, 0xc6, 0x4f, 0x67, 0xa0, 0x10, 0x5f, 0xd3, 0x9f, 0x48, 0xaf, + 0x9c, 0xe1, 0x75, 0xe9, 0xc7, 0xb3, 0x61, 0xe6, 0x16, 0xe9, 0x42, 0xfc, 0xb4, 0x9a, 0x29, 0xbe, + 0xab, 0x3d, 0xfc, 0x3c, 0xab, 0x47, 0xc3, 0x53, 0x83, 0x6f, 0xa4, 0x87, 0xc0, 0x1c, 0xfa, 0xe6, + 0x2f, 0x15, 0x9f, 0x31, 0x3e, 0x84, 0xb9, 0x78, 0x77, 0xe0, 0xe3, 0x4f, 0x09, 0xa6, 0x75, 0x78, + 0x3c, 0xef, 0x53, 0x9c, 0xca, 0x8c, 0xe3, 0x1b, 0x7f, 0x98, 0x8d, 0xf3, 0x16, 0x26, 0x8b, 0x6c, + 0x8f, 0x52, 0x0d, 0x71, 0xc4, 0x1e, 0xc5, 0x41, 0xa6, 0x2c, 0x3b, 0x4b, 0xbe, 0xb5, 0xd0, 0x11, + 0x36, 0x97, 0xee, 0x08, 0x4b, 0x6e, 0xc5, 0xac, 0xb4, 0x95, 0xa8, 0x4d, 0x87, 0x74, 0xc7, 0x8a, + 0x2c, 0xb5, 0x63, 0xc6, 0xd9, 0x65, 0x98, 0xd3, 0x42, 0x90, 0x4b, 0xfa, 0xe1, 0x48, 0xd7, 0x1f, + 0x60, 0x01, 0x27, 0x4e, 0x45, 0x26, 0x2b, 0x30, 0xca, 0x3e, 0x73, 0xcd, 0xee, 0x88, 0x37, 0x1d, + 0x12, 0xba, 0xc5, 0x37, 0xc3, 0xfb, 0xa1, 0xe2, 0x19, 0xdf, 0xa4, 0x4c, 0x42, 0x50, 0x27, 0x96, + 0x40, 0x34, 0xfe, 0x65, 0x86, 0xad, 0xff, 0xfa, 0xc1, 0x77, 0x58, 0xd2, 0x36, 0xd6, 0xa4, 0x3e, + 0x16, 0xb5, 0x7f, 0x9c, 0xe5, 0xb9, 0x78, 0xc4, 0xf4, 0x79, 0x0b, 0x46, 0x36, 0x6d, 0x6f, 0x4f, + 0xa4, 0xcc, 0xd6, 0xb9, 0xf0, 0x82, 0x28, 0xa6, 0x54, 0x80, 0xbf, 0x4d, 0x41, 0xa0, 0xaa, 0xce, + 0xb2, 0x03, 0xa9, 0xce, 0x94, 0x77, 0x81, 0xdc, 0x13, 0x7b, 0x17, 0xf8, 0xae, 0x30, 0xed, 0x4e, + 0x29, 0x18, 0x20, 0xc2, 0xf5, 0xa5, 0x78, 0x96, 0xab, 0x44, 0x2c, 0xf2, 0x88, 0x1d, 0xb9, 0xa5, + 0xe6, 0xcd, 0x52, 0x7c, 0x4b, 0x4f, 0xc9, 0x90, 0x65, 0xfc, 0x71, 0x8e, 0xf7, 0xb1, 0xe8, 0xa8, + 0xcb, 0x9a, 0xdf, 0x39, 0xae, 0x93, 0x98, 0x9e, 0x92, 0x7b, 0xa0, 0x5f, 0x86, 0x21, 0x36, 0x37, + 0x45, 0x6f, 0x22, 0x1e, 0x9b, 0xbf, 0x2a, 0x1e, 0x2b, 0x67, 0x6b, 0x19, 0xcf, 0x24, 0x35, 0x21, + 0x22, 0x1e, 0x5b, 0xea, 0x5a, 0x46, 0x0c, 0x72, 0x05, 0x86, 0xd6, 0xdd, 0x86, 0x8c, 0xa4, 0x3e, + 0x87, 0xd1, 0x47, 0xb4, 0x8c, 0xab, 0xf3, 0x19, 0x13, 0x31, 0x58, 0x5b, 0xc3, 0xfc, 0x13, 0x6a, + 0x5b, 0x5b, 0xbb, 0x76, 0x32, 0xd1, 0x9d, 0x92, 0xf4, 0x66, 0x19, 0xa6, 0xb6, 0x9d, 0x76, 0xc3, + 0x3d, 0xf4, 0x2b, 0xd4, 0x3f, 0x08, 0xdc, 0x8e, 0xb0, 0x37, 0x46, 0xed, 0xfe, 0x21, 0x2f, 0xb1, + 0x1a, 0xbc, 0x48, 0x7d, 0x96, 0xd1, 0x89, 0xc8, 0x12, 0x4c, 0x6a, 0x11, 0x5c, 0xc5, 0xe3, 0x2a, + 0x6a, 0x43, 0xf5, 0xf8, 0xaf, 0xaa, 0x36, 0x54, 0x23, 0x61, 0xe7, 0xb9, 0xf8, 0x7e, 0xe5, 0x89, + 0x35, 0xf1, 0xed, 0x02, 0x87, 0xdc, 0x84, 0x3c, 0x0f, 0xf3, 0x51, 0xad, 0xa8, 0xcf, 0x64, 0x3e, + 0xc2, 0x62, 0x61, 0x72, 0x24, 0xa2, 0x12, 0xd6, 0xe1, 0xd3, 0x50, 0x10, 0x5b, 0x52, 0x94, 0xab, + 0xfd, 0x39, 0x18, 0x2a, 0x57, 0x2b, 0xa6, 0xba, 0x8d, 0xd4, 0x9d, 0x86, 0x67, 0x22, 0x14, 0xbd, + 0xfa, 0xd6, 0x69, 0x70, 0xe8, 0x7a, 0x07, 0x26, 0xf5, 0x03, 0xcf, 0xe1, 0xf9, 0x34, 0x71, 0x21, + 0x7e, 0x86, 0xbc, 0x03, 0xc3, 0x68, 0xf8, 0x1a, 0x3b, 0x19, 0xe2, 0x75, 0x2c, 0x4d, 0x8a, 0x09, + 0x3c, 0x8c, 0x56, 0xb4, 0x26, 0x27, 0x22, 0x6f, 0xc1, 0x50, 0x85, 0xb6, 0x8f, 0x62, 0xa9, 0xfe, + 0x12, 0xc4, 0xe1, 0x86, 0xd0, 0xa0, 0xed, 0x23, 0x13, 0x49, 0x8c, 0x9f, 0xce, 0xc2, 0xb9, 0x94, + 0xcf, 0x7a, 0xf0, 0x99, 0xa7, 0x74, 0x57, 0x5c, 0xd2, 0x76, 0x45, 0xf9, 0x3e, 0xde, 0xb3, 0xe3, + 0x53, 0x37, 0xc9, 0x5f, 0xc8, 0xc0, 0x05, 0x7d, 0x82, 0x0a, 0x4b, 0xf7, 0x07, 0x37, 0xc9, 0xdb, + 0x30, 0xb2, 0x42, 0xed, 0x06, 0x95, 0x79, 0xbd, 0xce, 0x85, 0x01, 0xf9, 0x78, 0x0c, 0x03, 0x5e, + 0xc8, 0xd9, 0x46, 0x1e, 0xaf, 0x1c, 0x4a, 0x2a, 0xe2, 0xe3, 0xb8, 0xf8, 0x6e, 0xc8, 0x78, 0x22, + 0x69, 0x55, 0xf5, 0xb1, 0x32, 0xf9, 0x56, 0x06, 0x9e, 0xed, 0x43, 0xc3, 0x06, 0x8e, 0x0d, 0xbd, + 0x3a, 0x70, 0x78, 0xa2, 0x22, 0x94, 0xbc, 0x07, 0xd3, 0x9b, 0x42, 0xfc, 0x97, 0xc3, 0x91, 0x8d, + 0xd6, 0x8b, 0xbc, 0x19, 0x58, 0x72, 0x5c, 0xe2, 0xc8, 0x5a, 0xa0, 0x9b, 0x5c, 0xdf, 0x40, 0x37, + 0x6a, 0xdc, 0x98, 0xa1, 0x41, 0xe3, 0xc6, 0x7c, 0x08, 0x73, 0x7a, 0xdb, 0x44, 0xf8, 0xde, 0x28, + 0x6a, 0x4e, 0xa6, 0x77, 0xd4, 0x9c, 0xbe, 0x41, 0x42, 0x8d, 0x9f, 0xc8, 0x40, 0x41, 0xe7, 0xfd, + 0xb8, 0xe3, 0xf9, 0xae, 0x36, 0x9e, 0xcf, 0xa6, 0x8f, 0x67, 0xef, 0x81, 0xfc, 0xbf, 0x33, 0xf1, + 0xc6, 0x0e, 0x34, 0x82, 0x06, 0x8c, 0x54, 0xdc, 0x96, 0xed, 0xb4, 0xd5, 0xd4, 0xff, 0x0d, 0x84, + 0x98, 0xa2, 0x64, 0xb0, 0x20, 0x43, 0x97, 0x60, 0x78, 0xdd, 0x6d, 0x97, 0x2a, 0xc2, 0xa4, 0x18, + 0xf9, 0xb4, 0xdd, 0xb6, 0x65, 0x37, 0x4c, 0x5e, 0x40, 0x56, 0x01, 0x6a, 0x75, 0x8f, 0xd2, 0x76, + 0xcd, 0xf9, 0x6e, 0x1a, 0x93, 0x34, 0x58, 0x0f, 0x35, 0xbb, 0xb8, 0xb1, 0xf0, 0xa7, 0x53, 0x44, + 0xb4, 0x7c, 0xe7, 0xbb, 0xd5, 0xfd, 0x56, 0xa1, 0xc7, 0x75, 0x25, 0xe2, 0xb0, 0xc5, 0xc6, 0xe1, + 0xc6, 0x27, 0xb1, 0xae, 0x52, 0xab, 0xc2, 0x1e, 0xbe, 0x91, 0x3a, 0x1c, 0x7f, 0x90, 0x81, 0x67, + 0xfb, 0xd0, 0x3c, 0x81, 0x51, 0xf9, 0x8b, 0xee, 0x70, 0x0a, 0x10, 0x11, 0x61, 0x26, 0x65, 0xa7, + 0x11, 0xf0, 0x5c, 0x7d, 0x93, 0x22, 0x93, 0x32, 0x03, 0x68, 0x99, 0x94, 0x19, 0x80, 0x9d, 0xa5, + 0x2b, 0xd4, 0xd9, 0xdb, 0xe7, 0xe6, 0x61, 0x93, 0x7c, 0x6f, 0xd8, 0x47, 0x88, 0x7a, 0x96, 0x72, + 0x1c, 0xe3, 0x5f, 0x0d, 0xc3, 0x45, 0x93, 0xee, 0x39, 0xec, 0x5e, 0xb2, 0xe5, 0x3b, 0xed, 0x3d, + 0x2d, 0xee, 0x8e, 0x11, 0x5b, 0xb9, 0x22, 0x49, 0x05, 0x83, 0x84, 0x33, 0xf1, 0x2a, 0xe4, 0x99, + 0x18, 0xa2, 0x2c, 0x5e, 0x7c, 0xe3, 0x62, 0xc2, 0x8a, 0x08, 0xec, 0x2c, 0x8b, 0xc9, 0x6b, 0x42, + 0x4c, 0x52, 0xd2, 0x08, 0x31, 0x31, 0xe9, 0xdb, 0xc7, 0x45, 0xa8, 0x1d, 0xf9, 0x01, 0xc5, 0x2b, + 0xb2, 0x10, 0x95, 0xc2, 0xbb, 0xcc, 0x50, 0x8f, 0xbb, 0xcc, 0x1a, 0xcc, 0x95, 0x1a, 0xfc, 0x74, + 0xb4, 0x9b, 0x1b, 0x9e, 0xd3, 0xae, 0x3b, 0x1d, 0xbb, 0x29, 0xef, 0xe7, 0xd8, 0xcb, 0x76, 0x58, + 0x6e, 0x75, 0x42, 0x04, 0x33, 0x95, 0x8c, 0x35, 0xa3, 0xb2, 0x5e, 0xc3, 0xf0, 0x34, 0xe2, 0xf9, + 0x12, 0x9b, 0xd1, 0x68, 0xfb, 0xd8, 0x0a, 0xdf, 0x0c, 0x8b, 0xf1, 0x16, 0x85, 0x06, 0x01, 0x9b, + 0xab, 0xb5, 0xc8, 0xa5, 0x9a, 0x67, 0x39, 0xe0, 0x86, 0x05, 0x41, 0xd3, 0x47, 0x53, 0x4c, 0x0d, + 0x2f, 0xa2, 0xab, 0xd5, 0x56, 0x18, 0x5d, 0x3e, 0x41, 0xe7, 0xfb, 0xfb, 0x2a, 0x1d, 0xc7, 0x23, + 0xd7, 0xd9, 0x54, 0x68, 0xb9, 0x01, 0xc5, 0x29, 0x3c, 0x16, 0xdd, 0xb9, 0x3c, 0x84, 0xf2, 0x3b, + 0x97, 0x82, 0x42, 0xde, 0x81, 0xd9, 0xe5, 0xf2, 0xa2, 0x54, 0x3a, 0x57, 0xdc, 0x7a, 0x17, 0x0d, + 0x01, 0x00, 0xeb, 0xc3, 0x31, 0xa4, 0xf5, 0x45, 0xb6, 0x9b, 0xa4, 0xa1, 0x91, 0xcb, 0x30, 0x5a, + 0xad, 0xf0, 0xbe, 0x1f, 0x57, 0x53, 0x79, 0x09, 0xcb, 0x2c, 0x59, 0x48, 0xee, 0x47, 0x97, 0x82, + 0x89, 0x53, 0xa5, 0xf7, 0x8b, 0x03, 0x5c, 0x08, 0xde, 0x82, 0xc9, 0x25, 0x37, 0xa8, 0xb6, 0xfd, + 0xc0, 0x6e, 0xd7, 0x69, 0xb5, 0xa2, 0xc6, 0xd5, 0xde, 0x71, 0x03, 0xcb, 0x11, 0x25, 0xec, 0xcb, + 0x75, 0x4c, 0xf2, 0x39, 0x24, 0xbd, 0x43, 0xdb, 0xd4, 0x8b, 0xe2, 0x69, 0x0f, 0xf3, 0xbe, 0x65, + 0xa4, 0x7b, 0x61, 0x89, 0xa9, 0x23, 0x8a, 0x34, 0x63, 0x3c, 0x39, 0x68, 0xd9, 0x6d, 0x50, 0x9f, + 0xef, 0x16, 0xdf, 0x41, 0x69, 0xc6, 0x94, 0xb6, 0xf5, 0xd9, 0x41, 0xff, 0x7d, 0x4c, 0x33, 0x96, + 0xc0, 0x25, 0x9f, 0x83, 0x61, 0xfc, 0x29, 0xa4, 0xdb, 0xd9, 0x14, 0xb6, 0x91, 0x64, 0x5b, 0x67, + 0x98, 0x26, 0x27, 0x20, 0x55, 0x18, 0x15, 0x17, 0xab, 0xb3, 0x24, 0xcb, 0x11, 0x37, 0x34, 0x3e, + 0x33, 0x04, 0xbd, 0xd1, 0x80, 0x09, 0xb5, 0x42, 0xb6, 0x22, 0x56, 0x6c, 0x7f, 0x9f, 0x36, 0xd8, + 0x2f, 0x91, 0xe7, 0x0e, 0x57, 0xc4, 0x3e, 0x42, 0x2d, 0xf6, 0x1d, 0xa6, 0x82, 0xc2, 0xce, 0xd4, + 0xaa, 0xbf, 0xe5, 0x8b, 0x4f, 0x11, 0xaa, 0x16, 0x07, 0xd5, 0x76, 0x0d, 0x53, 0x14, 0x19, 0xdf, + 0x05, 0x73, 0xeb, 0xdd, 0x66, 0xd3, 0xde, 0x69, 0x52, 0x99, 0x07, 0x05, 0x13, 0x8e, 0x2f, 0xc1, + 0x70, 0x4d, 0x49, 0x61, 0x1e, 0xe6, 0xa2, 0x54, 0x70, 0xd0, 0x08, 0x36, 0x83, 0xa1, 0x82, 0x62, + 0xc9, 0xcb, 0x39, 0xa9, 0xf1, 0x7b, 0x19, 0x98, 0x93, 0xe6, 0x02, 0x9e, 0x5d, 0x3f, 0x08, 0xf3, + 0xd8, 0x5f, 0xd6, 0xe6, 0x1a, 0x4e, 0xd8, 0xd8, 0x34, 0xe2, 0xb3, 0xee, 0xae, 0xfc, 0x08, 0x5d, + 0x60, 0x49, 0xfb, 0xe0, 0xd3, 0x3e, 0x86, 0xbc, 0x03, 0xe3, 0xe2, 0x78, 0x54, 0x02, 0x5c, 0x62, + 0x14, 0x31, 0x71, 0xdd, 0x8b, 0x1b, 0xaf, 0xa8, 0xe8, 0x28, 0x8b, 0xe9, 0x4d, 0x79, 0x5c, 0x19, + 0x20, 0x5d, 0x16, 0xd3, 0xeb, 0xe8, 0x33, 0x75, 0x7f, 0x7b, 0x3c, 0xde, 0xb7, 0x62, 0xee, 0xde, + 0x52, 0x43, 0xda, 0x65, 0xa2, 0x9b, 0x71, 0x14, 0xd2, 0x4e, 0xbd, 0x19, 0x87, 0xa8, 0xe1, 0x98, + 0x64, 0x4f, 0x19, 0x93, 0xf7, 0xe4, 0x98, 0xe4, 0x7a, 0x4f, 0x8c, 0xd9, 0x3e, 0xe3, 0x50, 0x8b, + 0x56, 0xc8, 0xd0, 0x40, 0x6a, 0x95, 0x67, 0x30, 0x76, 0x3f, 0x27, 0x89, 0xef, 0xa2, 0x82, 0x93, + 0xaa, 0xab, 0x19, 0x1e, 0x9c, 0xe9, 0x29, 0x5b, 0xf3, 0xe7, 0x61, 0xa2, 0x14, 0x04, 0x76, 0x7d, + 0x9f, 0x36, 0x2a, 0x6c, 0x7b, 0x52, 0xa2, 0x6f, 0xd9, 0x02, 0xae, 0xbe, 0xb1, 0xa9, 0xb8, 0x3c, + 0x9a, 0xac, 0xed, 0x0b, 0x63, 0xda, 0x30, 0x9a, 0x2c, 0x83, 0xe8, 0xd1, 0x64, 0x19, 0x84, 0x5c, + 0x87, 0xd1, 0x6a, 0xfb, 0xa1, 0xc3, 0xfa, 0x84, 0x07, 0xe0, 0x42, 0xdd, 0x94, 0xc3, 0x41, 0xea, + 0xe6, 0x2a, 0xb0, 0xc8, 0x5b, 0xca, 0xa5, 0x66, 0x2c, 0x52, 0x60, 0x70, 0x95, 0x57, 0x18, 0x61, + 0x47, 0xbd, 0xb0, 0x84, 0xb7, 0x9c, 0x5b, 0x30, 0x2a, 0x35, 0x99, 0x10, 0x29, 0x2d, 0x04, 0x65, + 0x32, 0x60, 0x85, 0x44, 0xc6, 0x9c, 0xe4, 0x4a, 0xbe, 0xbe, 0x71, 0x25, 0x27, 0xb9, 0x92, 0xaf, + 0x4f, 0xcb, 0x49, 0xae, 0x64, 0xee, 0x0b, 0x95, 0x40, 0x13, 0xa7, 0x2a, 0x81, 0x1e, 0xc0, 0xc4, + 0x86, 0xed, 0x05, 0x0e, 0x93, 0x51, 0xda, 0x81, 0x3f, 0x3f, 0xa9, 0xe9, 0x4d, 0x95, 0xa2, 0xa5, + 0x17, 0x64, 0x5e, 0xec, 0x8e, 0x82, 0xaf, 0x27, 0x70, 0x8e, 0xe0, 0xe9, 0xa6, 0xb4, 0x53, 0x8f, + 0x63, 0x4a, 0x8b, 0x9d, 0x8a, 0xba, 0xb2, 0xe9, 0x48, 0x23, 0x83, 0x97, 0x96, 0x98, 0xc2, 0x2c, + 0x44, 0x24, 0x5f, 0x86, 0x09, 0xf6, 0xf7, 0x86, 0xdb, 0x74, 0xea, 0x0e, 0xf5, 0xe7, 0x0b, 0xd8, + 0xb8, 0x17, 0x52, 0x57, 0x3f, 0x22, 0x1d, 0xd5, 0x68, 0xc0, 0x17, 0x30, 0x32, 0x8e, 0x2b, 0xc1, + 0x35, 0x6e, 0xe4, 0x7d, 0x98, 0x60, 0xb3, 0x6f, 0xc7, 0xf6, 0xb9, 0x68, 0x3a, 0x13, 0x19, 0x43, + 0x37, 0x04, 0x3c, 0x11, 0xd0, 0x59, 0x25, 0x60, 0xc7, 0x7c, 0xa9, 0xc3, 0x37, 0x48, 0xa2, 0xcc, + 0xf6, 0x4e, 0x62, 0x73, 0x94, 0x68, 0xe4, 0x0b, 0x30, 0x51, 0xea, 0x74, 0xa2, 0x1d, 0x67, 0x56, + 0x51, 0x84, 0x75, 0x3a, 0x56, 0xea, 0xae, 0xa3, 0x51, 0xc4, 0x37, 0xe6, 0xb9, 0x33, 0x6d, 0xcc, + 0xe4, 0x8d, 0x50, 0x5a, 0x3f, 0x17, 0x69, 0x75, 0xc5, 0xc5, 0x51, 0x13, 0xfd, 0xb9, 0xe0, 0x5e, + 0x86, 0x49, 0xae, 0xe6, 0x94, 0xd2, 0xcc, 0xf9, 0xc4, 0xea, 0x49, 0x11, 0x6a, 0x74, 0x1a, 0xb2, + 0x0c, 0x53, 0xdc, 0xdb, 0xbb, 0x29, 0x22, 0x6d, 0xcf, 0x5f, 0xc0, 0x55, 0x8b, 0x5c, 0xb8, 0x93, + 0x78, 0x13, 0x13, 0xb0, 0xd8, 0x1a, 0x97, 0x18, 0x91, 0xf1, 0x27, 0x19, 0xb8, 0xd0, 0x63, 0xc4, + 0xc3, 0x38, 0xcc, 0x99, 0xfe, 0x71, 0x98, 0xd9, 0xce, 0xa1, 0x6b, 0x45, 0xb0, 0xfd, 0x42, 0xca, + 0x52, 0xc7, 0x4b, 0xca, 0x5b, 0x2e, 0x10, 0x91, 0xe3, 0x48, 0x54, 0x7d, 0xd7, 0x45, 0xd5, 0x6c, + 0x2e, 0x79, 0x08, 0x09, 0x3c, 0xfe, 0x51, 0x4b, 0xc6, 0xc9, 0x71, 0xf1, 0x05, 0x91, 0x42, 0x29, + 0x1c, 0xd6, 0x8f, 0x5c, 0x6d, 0x05, 0xa7, 0xb0, 0x36, 0x8e, 0x33, 0x30, 0xae, 0xac, 0x43, 0x72, + 0x49, 0xf1, 0x42, 0x2e, 0xf0, 0x24, 0x5c, 0x0a, 0x87, 0x2c, 0x3f, 0x89, 0x70, 0x51, 0x65, 0x4f, + 0x57, 0x40, 0xaf, 0x31, 0x51, 0x48, 0x89, 0x55, 0xdd, 0xd2, 0xb4, 0xc5, 0x26, 0x96, 0x63, 0x3a, + 0x7f, 0xdb, 0x0f, 0x4a, 0xf5, 0xc0, 0x79, 0x48, 0x07, 0x38, 0x74, 0xa2, 0x74, 0xfe, 0xb6, 0x1f, + 0x58, 0x36, 0x92, 0x25, 0xd2, 0xf9, 0x87, 0x0c, 0x8d, 0x1f, 0xc8, 0x00, 0x6c, 0x55, 0xcb, 0x18, + 0x6c, 0xfe, 0x71, 0x85, 0x82, 0xf4, 0x00, 0xbe, 0x92, 0x7b, 0x1f, 0x71, 0xe0, 0x7f, 0xca, 0xc0, + 0x94, 0x8e, 0x46, 0xde, 0x83, 0xe9, 0x5a, 0xdd, 0x73, 0x9b, 0xcd, 0x1d, 0xbb, 0x7e, 0xb0, 0xea, + 0xb4, 0x29, 0x0f, 0x9d, 0x3a, 0xcc, 0xcf, 0x22, 0x3f, 0x2c, 0xb2, 0x9a, 0xac, 0xcc, 0x8c, 0x23, + 0x93, 0x1f, 0xcc, 0xc0, 0x64, 0x6d, 0xdf, 0x3d, 0x0c, 0xa3, 0x9d, 0x8a, 0x01, 0xf9, 0x0a, 0x5b, + 0xdb, 0xfe, 0xbe, 0x7b, 0x18, 0x65, 0xf0, 0xd4, 0x6c, 0x45, 0xdf, 0x1d, 0xec, 0x19, 0xbf, 0xee, + 0xe2, 0x4d, 0x26, 0xf0, 0xaf, 0x69, 0x95, 0x98, 0x7a, 0x9d, 0xc6, 0x9f, 0x67, 0x60, 0x1c, 0xef, + 0x3c, 0xcd, 0x26, 0xca, 0x5c, 0xdf, 0x49, 0xe9, 0x20, 0xc3, 0x76, 0xf5, 0x19, 0xd8, 0x37, 0x61, + 0x3a, 0x86, 0x46, 0x0c, 0x18, 0xa9, 0x61, 0x80, 0x01, 0x55, 0x41, 0xc1, 0x43, 0x0e, 0x98, 0xa2, + 0xc4, 0x58, 0x56, 0xc8, 0x1e, 0xdc, 0xc0, 0x67, 0xdd, 0x45, 0x00, 0x47, 0x82, 0xe4, 0xcd, 0x86, + 0xc4, 0xbf, 0xe4, 0xc1, 0x0d, 0x53, 0xc1, 0x32, 0xd6, 0x61, 0xa4, 0xe6, 0x7a, 0xc1, 0xd2, 0x11, + 0xbf, 0x4c, 0x54, 0xa8, 0x5f, 0x57, 0xdf, 0x6d, 0x1d, 0x7c, 0x2b, 0xa9, 0x9b, 0xa2, 0x88, 0x14, + 0x61, 0xf8, 0xb6, 0x43, 0x9b, 0x0d, 0xd5, 0x9e, 0x77, 0x97, 0x01, 0x4c, 0x0e, 0x67, 0x17, 0xae, + 0xf3, 0x51, 0x4e, 0x96, 0xc8, 0x70, 0xf8, 0x71, 0xd7, 0x4d, 0x59, 0xeb, 0xdf, 0x17, 0xc3, 0x3c, + 0x08, 0xc9, 0x9a, 0xfa, 0x74, 0xf5, 0x3f, 0xc8, 0xc0, 0x42, 0x6f, 0x12, 0xd5, 0x16, 0x39, 0xd3, + 0xc7, 0x16, 0xf9, 0x95, 0xf8, 0x3b, 0x23, 0xa2, 0x89, 0x77, 0xc6, 0xe8, 0x75, 0xb1, 0x82, 0xa6, + 0xe0, 0x75, 0x2a, 0x13, 0xb1, 0x5c, 0xea, 0xf3, 0xcd, 0x88, 0xc8, 0x87, 0x39, 0x40, 0x1a, 0x53, + 0xd0, 0x1a, 0xbf, 0x35, 0x04, 0x17, 0x7b, 0x52, 0x90, 0x15, 0x25, 0xbd, 0xd3, 0x54, 0x98, 0x58, + 0xa6, 0x27, 0xfe, 0x35, 0xfc, 0x17, 0xad, 0xfd, 0xe2, 0xde, 0x6e, 0xf7, 0xc3, 0xb4, 0x3e, 0x59, + 0xe4, 0xf5, 0xa9, 0x53, 0x79, 0x71, 0x74, 0x64, 0x06, 0xc9, 0x0c, 0x3f, 0xe8, 0x17, 0x49, 0x03, + 0xdb, 0x69, 0xfa, 0xea, 0xb2, 0x6b, 0x70, 0x90, 0x29, 0xcb, 0x22, 0x03, 0xf1, 0xa1, 0x74, 0x03, + 0x71, 0xe3, 0x5f, 0x65, 0x60, 0x2c, 0xfc, 0x6c, 0xb2, 0x00, 0xe7, 0x37, 0xcd, 0x52, 0x79, 0xd9, + 0xda, 0xfc, 0x70, 0x63, 0xd9, 0xda, 0x5a, 0xaf, 0x6d, 0x2c, 0x97, 0xab, 0xb7, 0xab, 0xcb, 0x95, + 0xc2, 0x33, 0x64, 0x06, 0x26, 0xb7, 0xd6, 0xef, 0xad, 0xdf, 0xdf, 0x5e, 0xb7, 0x96, 0x4d, 0xf3, + 0xbe, 0x59, 0xc8, 0x90, 0x49, 0x18, 0x33, 0x97, 0x4a, 0x65, 0x6b, 0xfd, 0x7e, 0x65, 0xb9, 0x90, + 0x25, 0x05, 0x98, 0x28, 0xdf, 0x5f, 0x5f, 0x5f, 0x2e, 0x6f, 0x56, 0x1f, 0x54, 0x37, 0x3f, 0x2c, + 0xe4, 0x08, 0x81, 0x29, 0x44, 0xd8, 0x30, 0xab, 0xeb, 0xe5, 0xea, 0x46, 0x69, 0xb5, 0x30, 0xc4, + 0x60, 0x0c, 0x5f, 0x81, 0x0d, 0x87, 0x8c, 0xee, 0x6d, 0x2d, 0x2d, 0x17, 0x46, 0x18, 0x0a, 0xfb, + 0x4b, 0x41, 0x19, 0x65, 0xd5, 0x23, 0x4a, 0xa5, 0xb4, 0x59, 0x5a, 0x2a, 0xd5, 0x96, 0x0b, 0x79, + 0x72, 0x01, 0x66, 0x35, 0x90, 0xb5, 0x7a, 0xff, 0x4e, 0x75, 0xbd, 0x30, 0x46, 0xe6, 0xa0, 0x10, + 0xc2, 0x2a, 0x4b, 0xd6, 0x56, 0x6d, 0xd9, 0x2c, 0x40, 0x1c, 0xba, 0x5e, 0x5a, 0x5b, 0x2e, 0x8c, + 0x1b, 0xef, 0x72, 0x3f, 0x44, 0xde, 0xd5, 0xe4, 0x3c, 0x90, 0xda, 0x66, 0x69, 0x73, 0xab, 0x16, + 0x6b, 0xfc, 0x38, 0x8c, 0xd6, 0xb6, 0xca, 0xe5, 0xe5, 0x5a, 0xad, 0x90, 0x21, 0x00, 0x23, 0xb7, + 0x4b, 0xd5, 0xd5, 0xe5, 0x4a, 0x21, 0x6b, 0xfc, 0x54, 0x06, 0x66, 0xa4, 0x04, 0x28, 0x1f, 0x8d, + 0x1e, 0x73, 0x2d, 0xbe, 0xa7, 0x5d, 0x6c, 0xa5, 0x93, 0x58, 0xac, 0x92, 0x3e, 0xcb, 0xf0, 0x17, + 0x32, 0x70, 0x2e, 0x15, 0x9b, 0x7c, 0x08, 0x05, 0xf9, 0x05, 0x6b, 0x76, 0x50, 0xdf, 0x8f, 0xf6, + 0xb1, 0x17, 0x62, 0xb5, 0xc4, 0xd0, 0xb8, 0x5a, 0x33, 0x4a, 0x38, 0x9d, 0x60, 0x33, 0x78, 0x3a, + 0x04, 0xe3, 0x9b, 0x19, 0xb8, 0xd0, 0xa3, 0x1a, 0x52, 0x86, 0x91, 0x30, 0x31, 0x4e, 0x1f, 0x83, + 0xb7, 0xb9, 0x6f, 0x1d, 0x17, 0x05, 0x22, 0x66, 0xe8, 0xc5, 0xbf, 0xcc, 0x91, 0x30, 0xd3, 0x0d, + 0xa6, 0x9b, 0xe1, 0xdd, 0x77, 0x31, 0xd6, 0xf3, 0xa2, 0xa6, 0xd2, 0x76, 0x6d, 0x69, 0x5c, 0xf4, + 0x5d, 0xce, 0x3e, 0xf4, 0x31, 0xdf, 0x8c, 0xf1, 0xb3, 0x19, 0x26, 0xdc, 0xc5, 0x11, 0x99, 0xcc, + 0x5b, 0xf2, 0xfd, 0x6e, 0x8b, 0x9a, 0x6e, 0x93, 0x96, 0xcc, 0x75, 0x71, 0x6c, 0xa0, 0xb4, 0x6a, + 0x63, 0x01, 0x5e, 0x2b, 0x2c, 0xdb, 0x6b, 0x6b, 0xaf, 0xd5, 0x2a, 0x0d, 0x79, 0x0b, 0x60, 0xf9, + 0x51, 0x40, 0xbd, 0xb6, 0xdd, 0x0c, 0x63, 0xc4, 0xf0, 0xc8, 0x56, 0x02, 0xaa, 0xcb, 0xdb, 0x0a, + 0xb2, 0xf1, 0xd7, 0x33, 0x30, 0x21, 0x2e, 0x4d, 0xa5, 0x26, 0xf5, 0x82, 0xc7, 0x9b, 0x5e, 0x6f, + 0x69, 0xd3, 0x2b, 0xf4, 0xef, 0x50, 0xf8, 0xb3, 0xe2, 0xd4, 0x99, 0xf5, 0xcf, 0x32, 0x50, 0x88, + 0x23, 0x92, 0xf7, 0x20, 0x5f, 0xa3, 0x0f, 0xa9, 0xe7, 0x04, 0x47, 0x62, 0xa3, 0x94, 0x29, 0x04, + 0x39, 0x8e, 0x28, 0xe3, 0xf3, 0xc1, 0x17, 0xbf, 0xcc, 0x90, 0x66, 0xd0, 0xfd, 0x5e, 0x51, 0x7b, + 0xe4, 0x9e, 0x94, 0xda, 0xc3, 0xf8, 0xdf, 0xb3, 0x70, 0xe1, 0x0e, 0x0d, 0xd4, 0x36, 0x85, 0xe6, + 0x05, 0x9f, 0x1e, 0xac, 0x5d, 0x4a, 0x4b, 0xe6, 0x61, 0x14, 0x8b, 0xe4, 0xf8, 0x9a, 0xf2, 0x27, + 0x59, 0x0a, 0xe7, 0x75, 0x4e, 0xcb, 0x51, 0xd6, 0xa3, 0xee, 0x6b, 0x4a, 0xd6, 0xa2, 0x70, 0x5a, + 0x5f, 0x86, 0x29, 0x0c, 0xcb, 0xdf, 0x65, 0xcb, 0x81, 0x36, 0x84, 0xfa, 0x27, 0x6f, 0xc6, 0xa0, + 0xe4, 0x35, 0x28, 0x30, 0x48, 0xa9, 0x7e, 0xd0, 0x76, 0x0f, 0x9b, 0xb4, 0xb1, 0x47, 0x1b, 0x78, + 0xac, 0xe7, 0xcd, 0x04, 0x5c, 0xf2, 0xdc, 0x6a, 0xf3, 0xab, 0x1b, 0x6d, 0xa0, 0x8e, 0x46, 0xf0, + 0x8c, 0xa0, 0x0b, 0x6f, 0xc1, 0xf8, 0xc7, 0xcc, 0x40, 0x66, 0xfc, 0xaf, 0x19, 0x98, 0xc3, 0xc6, + 0x29, 0x15, 0xcb, 0xec, 0xb0, 0xb2, 0xb7, 0x94, 0xa4, 0x3c, 0x36, 0x03, 0xe9, 0x4b, 0x21, 0xec, + 0xc5, 0x48, 0x27, 0x94, 0x1d, 0x40, 0x27, 0x54, 0x3b, 0x4b, 0x26, 0xfc, 0x01, 0x55, 0x5a, 0x77, + 0x87, 0xf2, 0xb9, 0xc2, 0x50, 0x34, 0xe4, 0xc6, 0x0f, 0x66, 0x61, 0xd4, 0xa4, 0x98, 0x22, 0x9c, + 0x5c, 0x86, 0xd1, 0x75, 0x37, 0xa0, 0xfe, 0x9a, 0x96, 0x0f, 0xbe, 0xcd, 0x40, 0x56, 0xab, 0x61, + 0xca, 0x42, 0x36, 0xe1, 0x37, 0x3c, 0xb7, 0xd1, 0xad, 0x07, 0xea, 0x84, 0xef, 0x70, 0x90, 0x29, + 0xcb, 0xc8, 0xeb, 0x30, 0x26, 0x38, 0x87, 0x8f, 0xba, 0x68, 0xbb, 0xec, 0xd1, 0x30, 0xc5, 0x7c, + 0x84, 0x80, 0x32, 0x2d, 0x17, 0x30, 0x86, 0x14, 0x99, 0x36, 0x21, 0x33, 0x48, 0x51, 0x7d, 0xb8, + 0x8f, 0xa8, 0xfe, 0x69, 0x18, 0x29, 0xf9, 0x3e, 0x0d, 0x64, 0x14, 0x85, 0x89, 0x30, 0x6c, 0x9c, + 0x4f, 0x03, 0xce, 0xd8, 0xc6, 0x72, 0x53, 0xe0, 0x19, 0xff, 0x32, 0x0b, 0xc3, 0xf8, 0x27, 0x3e, + 0x99, 0x7a, 0xf5, 0x7d, 0xed, 0xc9, 0xd4, 0xab, 0xef, 0x9b, 0x08, 0x25, 0x37, 0x50, 0x53, 0x21, + 0xf3, 0x47, 0x89, 0xd6, 0xa3, 0x0a, 0xbe, 0x11, 0x81, 0x4d, 0x15, 0x27, 0x7c, 0xe1, 0xcf, 0xa5, + 0xc6, 0x4e, 0x39, 0x0f, 0xd9, 0xfb, 0x35, 0xd1, 0x62, 0x8c, 0xc8, 0xe5, 0xfa, 0x66, 0xf6, 0x7e, + 0x0d, 0x7b, 0x63, 0xa5, 0xb4, 0xf8, 0xe6, 0x2d, 0xd1, 0x50, 0xde, 0x1b, 0xfb, 0xf6, 0xe2, 0x9b, + 0xb7, 0x4c, 0x51, 0xc2, 0xfa, 0x17, 0xbf, 0x19, 0x1f, 0x5e, 0xb9, 0xcf, 0x3f, 0xf6, 0x2f, 0xb6, + 0x0d, 0x1f, 0x59, 0xcd, 0x08, 0x81, 0x2c, 0xc2, 0xb8, 0x88, 0x35, 0x81, 0xf8, 0x4a, 0x2c, 0x08, + 0x11, 0x8b, 0x82, 0x53, 0xa8, 0x48, 0xfc, 0x09, 0x4e, 0x0c, 0x90, 0xcc, 0x72, 0x2b, 0x9e, 0xe0, + 0xe4, 0x10, 0xfa, 0xa6, 0x82, 0xc2, 0x3e, 0x89, 0xbf, 0xe1, 0x45, 0xbe, 0xfc, 0x53, 0x4a, 0xd0, + 0x02, 0x4c, 0xb3, 0x10, 0x22, 0x18, 0xbf, 0x92, 0x85, 0xfc, 0x46, 0xb3, 0xbb, 0xe7, 0xb4, 0x1f, + 0xdc, 0x20, 0x04, 0xf0, 0x1a, 0x27, 0xf3, 0x70, 0xb0, 0xbf, 0xc9, 0x45, 0xc8, 0xcb, 0x9b, 0x9b, + 0xdc, 0x90, 0x7c, 0x71, 0x6b, 0x9b, 0x07, 0x39, 0xee, 0x22, 0xf4, 0x9a, 0xfc, 0x49, 0x6e, 0x40, + 0x78, 0xff, 0xea, 0x75, 0x51, 0x1b, 0x62, 0x8b, 0xc5, 0x0c, 0xd1, 0xc8, 0x1b, 0x80, 0x87, 0x84, + 0xb8, 0x3c, 0x48, 0x85, 0x36, 0xff, 0x34, 0x21, 0xa7, 0x70, 0x12, 0x44, 0x23, 0x37, 0x41, 0x4c, + 0x4c, 0x91, 0x4d, 0xfd, 0x9c, 0x4e, 0xc0, 0xf3, 0x53, 0x4a, 0x12, 0x81, 0x4a, 0xde, 0x81, 0xf1, + 0xba, 0x47, 0xf1, 0xd5, 0xd1, 0x6e, 0x46, 0x49, 0xd2, 0x55, 0xca, 0x72, 0x54, 0xfe, 0xe0, 0x86, + 0xa9, 0xa2, 0x1b, 0xff, 0x5b, 0x1e, 0x26, 0xd4, 0xef, 0x21, 0x26, 0xcc, 0xfa, 0x4d, 0x76, 0x77, + 0x17, 0xc6, 0x66, 0x1d, 0x2c, 0x14, 0xc7, 0xe9, 0x25, 0xfd, 0x83, 0x18, 0x1e, 0xb7, 0x3c, 0x93, + 0x41, 0x32, 0x56, 0x9e, 0x31, 0x67, 0xfc, 0x08, 0xcc, 0xf1, 0x48, 0x09, 0xf2, 0x6e, 0xc7, 0xdf, + 0xa3, 0x6d, 0x47, 0xbe, 0xb7, 0xbc, 0xa4, 0x31, 0xba, 0x2f, 0x0a, 0x13, 0xbc, 0x42, 0x32, 0xf2, + 0x26, 0x8c, 0xb8, 0x1d, 0xda, 0xb6, 0x1d, 0x71, 0xc6, 0x3d, 0x1b, 0x63, 0x40, 0xdb, 0xa5, 0xaa, + 0x42, 0x28, 0x90, 0xc9, 0x75, 0x18, 0x72, 0x0f, 0xc2, 0xf1, 0xba, 0xa8, 0x13, 0x1d, 0x04, 0xb6, + 0x42, 0x82, 0x88, 0x8c, 0xe0, 0x23, 0xbb, 0xb5, 0x2b, 0x46, 0x4c, 0x27, 0xb8, 0x6b, 0xb7, 0x76, + 0x55, 0x02, 0x86, 0x48, 0xde, 0x07, 0xe8, 0xd8, 0x7b, 0xd4, 0xb3, 0x1a, 0xdd, 0xe0, 0x48, 0x8c, + 0xdb, 0x0b, 0x1a, 0xd9, 0x06, 0x2b, 0xae, 0x74, 0x83, 0x23, 0x85, 0x76, 0xac, 0x23, 0x81, 0xa4, + 0x04, 0xd0, 0xb2, 0x83, 0x80, 0x7a, 0x2d, 0x57, 0x58, 0xfb, 0x45, 0x41, 0x10, 0x39, 0x83, 0xb5, + 0xb0, 0x58, 0xe1, 0xa0, 0x10, 0xe1, 0x47, 0x3b, 0x9e, 0x2d, 0x72, 0xda, 0xc7, 0x3e, 0xda, 0xf1, + 0xb4, 0x56, 0x32, 0x44, 0xf2, 0x39, 0x18, 0x6d, 0x38, 0x7e, 0xdd, 0xf5, 0x1a, 0x22, 0x7a, 0xca, + 0x73, 0x1a, 0x4d, 0x85, 0x97, 0x29, 0x64, 0x12, 0x9d, 0x7d, 0xad, 0x08, 0x82, 0xba, 0xee, 0x1e, + 0xa2, 0x9a, 0x3f, 0xfe, 0xb5, 0xb5, 0xb0, 0x58, 0xfd, 0xda, 0x88, 0x88, 0x0d, 0xe5, 0x9e, 0x13, + 0x34, 0xed, 0x1d, 0xf1, 0xce, 0xad, 0x0f, 0xe5, 0x1d, 0x2c, 0x52, 0x87, 0x92, 0x23, 0x93, 0xb7, + 0x20, 0x4f, 0xdb, 0x81, 0x67, 0x5b, 0x4e, 0x43, 0x38, 0x55, 0xea, 0x1f, 0xcd, 0x0e, 0x60, 0xbb, + 0x5a, 0x51, 0x3f, 0x1a, 0xf1, 0xab, 0x0d, 0xd6, 0x3f, 0x7e, 0xdd, 0x69, 0x09, 0x5f, 0x48, 0xbd, + 0x7f, 0x6a, 0xe5, 0xea, 0x9a, 0xda, 0x3f, 0x0c, 0x91, 0xbc, 0x07, 0xa3, 0x6c, 0xfd, 0x36, 0xdc, + 0x3d, 0x11, 0x90, 0xc2, 0xd0, 0xfb, 0x87, 0x97, 0x25, 0xa6, 0xab, 0x24, 0x62, 0x0b, 0xd9, 0x3e, + 0xf4, 0x2d, 0xa7, 0x2e, 0x82, 0x4c, 0xe8, 0xcb, 0xb1, 0xb4, 0x5d, 0xab, 0x96, 0x15, 0xb2, 0x61, + 0xfb, 0xd0, 0xaf, 0xd6, 0xc9, 0x22, 0x0c, 0x63, 0x8a, 0x0a, 0x11, 0x08, 0x53, 0xa7, 0xc1, 0xe4, + 0x14, 0x2a, 0x0d, 0xa2, 0xb2, 0x81, 0x6c, 0xf9, 0xe8, 0x5e, 0x22, 0x12, 0x45, 0xe8, 0x7d, 0xb2, + 0x56, 0x43, 0x9f, 0x13, 0xf5, 0x13, 0x05, 0x3a, 0xfb, 0xc4, 0x36, 0x0d, 0x2c, 0xe7, 0xeb, 0x22, + 0xd5, 0x83, 0x5e, 0xdd, 0x3a, 0x0d, 0xaa, 0x1f, 0xa8, 0xd5, 0xb5, 0x69, 0x50, 0xfd, 0x3a, 0x79, + 0x01, 0x20, 0x7a, 0xfa, 0xe7, 0x0f, 0x35, 0xa6, 0x02, 0xf9, 0xfc, 0xd0, 0xff, 0xf5, 0x4b, 0xc5, + 0xcc, 0x12, 0xfc, 0x1b, 0xf6, 0xbe, 0xad, 0xc7, 0x91, 0xe3, 0x3a, 0x78, 0x9b, 0xe4, 0xcc, 0x70, + 0x0e, 0xe7, 0xd2, 0x53, 0x3b, 0xbb, 0x33, 0x9a, 0xbd, 0x69, 0x5b, 0xbb, 0xeb, 0x5d, 0xca, 0x92, + 0xb5, 0xab, 0x4f, 0x96, 0x64, 0x5b, 0x96, 0x7b, 0x38, 0x3d, 0x43, 0xee, 0xf2, 0xe6, 0x6e, 0x72, + 0xd6, 0xeb, 0xb5, 0xdd, 0xee, 0x25, 0x7b, 0x66, 0xda, 0xe2, 0x90, 0x34, 0x9b, 0xd4, 0x7a, 0x8c, + 0x0f, 0xf8, 0x6c, 0x7c, 0x80, 0x0d, 0x7c, 0x5f, 0x12, 0x27, 0x4e, 0x82, 0x08, 0x7e, 0xf1, 0x43, + 0x84, 0x20, 0x0f, 0xf9, 0x01, 0x41, 0x9c, 0x17, 0xbf, 0x19, 0x30, 0x0c, 0x18, 0xc8, 0x9b, 0x13, + 0x08, 0x89, 0x80, 0x04, 0xc8, 0xe5, 0x2d, 0x48, 0x1e, 0x0c, 0x04, 0x08, 0xea, 0x54, 0x55, 0x77, + 0xf5, 0x85, 0xdc, 0x59, 0x4b, 0x4a, 0x62, 0xc0, 0x4f, 0x33, 0x3c, 0x75, 0xea, 0x74, 0x5d, 0x4f, + 0x9d, 0x3a, 0x75, 0x2e, 0x90, 0x17, 0x61, 0x75, 0xb4, 0x2a, 0x3c, 0x33, 0x95, 0x59, 0x90, 0x5b, + 0xa0, 0x1e, 0x38, 0x5c, 0x55, 0xd8, 0x39, 0x72, 0xfa, 0x7d, 0xb7, 0xc7, 0xd9, 0xf4, 0xaa, 0x80, + 0x97, 0x18, 0x98, 0x51, 0xd6, 0xde, 0x84, 0xf5, 0xb4, 0x55, 0x42, 0xae, 0xc2, 0x92, 0x1c, 0x41, + 0x88, 0x13, 0x29, 0x38, 0x43, 0x4f, 0xc4, 0x10, 0xe2, 0x04, 0x7e, 0xa4, 0xc0, 0xc5, 0x59, 0x3c, + 0x87, 0x6c, 0x41, 0x7e, 0x38, 0xf2, 0x06, 0x28, 0xdb, 0xf2, 0x14, 0x0d, 0xe2, 0x37, 0x66, 0x5f, + 0x40, 0x21, 0x6c, 0xec, 0x1c, 0x72, 0xaf, 0x10, 0x73, 0x11, 0x21, 0x2d, 0xe7, 0xd0, 0x27, 0xcf, + 0xc3, 0x5a, 0xd7, 0x3d, 0x70, 0x26, 0xbd, 0xb1, 0xed, 0x77, 0x8e, 0xdc, 0x2e, 0xfa, 0x6d, 0xa1, + 0xb5, 0x9f, 0xa9, 0xf2, 0x02, 0x4b, 0xc0, 0x13, 0x2d, 0x9e, 0x9b, 0xd2, 0xe2, 0xbb, 0xb9, 0xbc, + 0xa2, 0x66, 0x4c, 0x34, 0xaf, 0xd2, 0xbe, 0x95, 0x81, 0xcd, 0x69, 0x9b, 0x8c, 0xbc, 0x91, 0x36, + 0x06, 0xec, 0xb5, 0x43, 0x86, 0xcb, 0xaf, 0x1d, 0xd2, 0xd7, 0xc8, 0x1d, 0x08, 0xbc, 0xae, 0x9e, + 0x14, 0x41, 0x41, 0xc0, 0x68, 0x9d, 0xa1, 0xe3, 0xfb, 0x8f, 0x29, 0x1f, 0xc9, 0x4a, 0x51, 0x78, + 0x39, 0x4c, 0xae, 0x23, 0x60, 0xe4, 0x55, 0x80, 0x4e, 0x6f, 0xe0, 0xbb, 0x68, 0x54, 0xc0, 0x05, + 0x14, 0x66, 0x4b, 0x1e, 0x40, 0xe5, 0x57, 0x64, 0x84, 0x96, 0x06, 0x5d, 0x97, 0x4f, 0xa0, 0x03, + 0x1b, 0x53, 0xb8, 0x2a, 0x9d, 0x9e, 0x30, 0xa5, 0xbd, 0x48, 0x90, 0x35, 0x09, 0x12, 0xdb, 0xc7, + 0x47, 0x3c, 0x33, 0x6d, 0x8d, 0x9c, 0x00, 0x49, 0xb2, 0x4e, 0x4a, 0x9d, 0x5b, 0x44, 0x4f, 0x46, + 0x01, 0x75, 0x06, 0x69, 0x8f, 0x7a, 0xe4, 0x0a, 0x14, 0x44, 0x02, 0x4c, 0x7a, 0x01, 0x60, 0xc4, + 0x81, 0x83, 0xee, 0xb9, 0xb8, 0x78, 0x30, 0xcc, 0x2a, 0xfa, 0xd6, 0x71, 0xd1, 0x62, 0x11, 0x21, + 0xad, 0x93, 0xa1, 0xe8, 0xdd, 0x45, 0xb1, 0xbe, 0xa3, 0x07, 0x1a, 0x2f, 0xfd, 0x43, 0x45, 0x4c, + 0x7f, 0xf2, 0x44, 0x78, 0x52, 0xfb, 0x08, 0xa0, 0x6b, 0x13, 0x6f, 0x18, 0xfe, 0x4f, 0x45, 0x1d, + 0xb1, 0xeb, 0xb8, 0xa8, 0xc3, 0x7f, 0x92, 0x1b, 0xb0, 0x3a, 0x62, 0xc6, 0xaf, 0xe3, 0x01, 0x1f, + 0x4f, 0x96, 0x6c, 0x64, 0x99, 0x81, 0x5b, 0x03, 0x1c, 0x53, 0xde, 0xae, 0xbb, 0xc1, 0x80, 0x49, + 0x07, 0x24, 0x79, 0x11, 0x16, 0xe9, 0x01, 0x89, 0xe1, 0x79, 0x62, 0x3e, 0x15, 0x88, 0x87, 0xe2, + 0x86, 0x99, 0xff, 0x1a, 0xff, 0x9f, 0xd3, 0x7a, 0x27, 0x23, 0x88, 0xc9, 0xc7, 0x33, 0xd9, 0x80, + 0x85, 0xc1, 0xe8, 0x50, 0xea, 0xda, 0xfc, 0x60, 0x74, 0x48, 0xfb, 0x75, 0x13, 0x54, 0xe6, 0xe2, + 0xc3, 0x42, 0x2d, 0xf8, 0x27, 0x7d, 0x76, 0x7f, 0xcf, 0x9b, 0x2b, 0x0c, 0x8e, 0x59, 0xfe, 0x4f, + 0xfa, 0x1d, 0x8a, 0xe9, 0xfb, 0x03, 0x5b, 0x8e, 0xca, 0xc5, 0xbb, 0xbd, 0xe2, 0xfb, 0x83, 0x30, + 0x3c, 0x57, 0x97, 0x6c, 0xc3, 0x32, 0xa5, 0x13, 0xc4, 0x06, 0xe3, 0xd2, 0xc3, 0xa5, 0xa4, 0xf4, + 0x70, 0xd2, 0xef, 0x88, 0x26, 0x9a, 0x4b, 0xbe, 0xf4, 0x8b, 0xdc, 0x03, 0x55, 0x12, 0xb3, 0xd0, + 0xe7, 0x33, 0x66, 0x88, 0x1d, 0x92, 0x91, 0xc4, 0xb3, 0x4a, 0xff, 0x60, 0x60, 0xae, 0x76, 0xa2, + 0x00, 0x3e, 0x34, 0x3f, 0x54, 0x04, 0x2f, 0x4d, 0xa9, 0x44, 0x34, 0x58, 0x3e, 0x72, 0x7c, 0xdb, + 0xf7, 0x8f, 0x99, 0x61, 0x19, 0x8f, 0x46, 0x5c, 0x38, 0x72, 0x7c, 0xcb, 0x3f, 0x16, 0xd9, 0x4e, + 0xce, 0x51, 0x9c, 0x81, 0x33, 0x19, 0x1f, 0xd9, 0xb2, 0xd0, 0xc8, 0x46, 0xec, 0xec, 0x91, 0xe3, + 0x37, 0x68, 0x99, 0x44, 0x9b, 0x5c, 0x83, 0x15, 0xa4, 0xdb, 0xf1, 0x04, 0x61, 0x0c, 0x97, 0x61, + 0x2e, 0x51, 0xc2, 0x1d, 0x8f, 0x51, 0xe6, 0x2d, 0xfc, 0xc7, 0x0c, 0x9c, 0x4f, 0x1f, 0x1d, 0x5c, + 0x9e, 0x74, 0x4c, 0xd1, 0xb1, 0x8f, 0xb7, 0x6d, 0x91, 0x42, 0x58, 0xa8, 0x93, 0xb4, 0xc9, 0xc9, + 0xa4, 0x4e, 0x4e, 0x11, 0xd6, 0x90, 0x10, 0x17, 0x4f, 0x7b, 0x9e, 0x3f, 0xe6, 0x11, 0x3c, 0xcc, + 0x55, 0x5a, 0xc0, 0xf8, 0x79, 0x95, 0x82, 0xc9, 0x75, 0x58, 0x11, 0x1c, 0x79, 0xf0, 0xb8, 0x4f, + 0x3f, 0xcc, 0xd8, 0xf1, 0x32, 0x87, 0x36, 0x10, 0x48, 0xce, 0xc1, 0xbc, 0x33, 0x1c, 0xd2, 0x4f, + 0x32, 0x2e, 0x3c, 0xe7, 0x0c, 0x87, 0x2c, 0x23, 0x0f, 0xba, 0x31, 0xda, 0x07, 0x68, 0x5a, 0xc4, + 0xed, 0x18, 0xcd, 0x25, 0x04, 0x32, 0x73, 0x23, 0x9f, 0xee, 0x7b, 0x5a, 0x57, 0xa0, 0x2c, 0x20, + 0x0a, 0x38, 0xc3, 0x00, 0xe1, 0x19, 0xc8, 0x8b, 0x47, 0x6e, 0xe6, 0x8d, 0x61, 0x2e, 0x38, 0xfc, + 0x81, 0xfb, 0x15, 0xd8, 0xe8, 0x7a, 0x3e, 0x2e, 0x5e, 0xd6, 0xa5, 0xe1, 0x90, 0x3b, 0x4e, 0xb2, + 0xc8, 0xbe, 0xe6, 0x3a, 0x2f, 0xa6, 0x23, 0xa9, 0x0f, 0x87, 0xcc, 0x7d, 0x92, 0x8f, 0xf5, 0x6b, + 0xb0, 0xca, 0xc5, 0x34, 0x7e, 0x44, 0x62, 0x5b, 0xf8, 0x06, 0xa6, 0xf7, 0x27, 0x9e, 0x03, 0x09, + 0x38, 0xa8, 0xd2, 0x15, 0x35, 0xff, 0x46, 0x81, 0x73, 0xa9, 0x72, 0x1e, 0xf9, 0x2a, 0x30, 0x3f, + 0xb1, 0xf1, 0xc0, 0x1e, 0xb9, 0x1d, 0x6f, 0xe8, 0x61, 0xe0, 0x0d, 0xa6, 0x07, 0xbd, 0x33, 0x4b, + 0x42, 0x44, 0x9f, 0xb3, 0xd6, 0xc0, 0x0c, 0x2a, 0x31, 0x05, 0x8d, 0x3a, 0x8a, 0x81, 0xb7, 0x1e, + 0xc2, 0xb9, 0x54, 0xd4, 0x14, 0xc5, 0xc9, 0xc7, 0xa3, 0x19, 0xa8, 0xc5, 0xcb, 0x56, 0xac, 0xd3, + 0x92, 0x42, 0x85, 0x77, 0xef, 0xc7, 0x41, 0xf7, 0x62, 0x12, 0x21, 0x31, 0xe2, 0xfb, 0x3a, 0xed, + 0x52, 0x23, 0x2a, 0x4d, 0xdf, 0xda, 0x0f, 0xe1, 0x1c, 0x5f, 0x7c, 0x87, 0x23, 0x67, 0x78, 0x14, + 0x92, 0x63, 0x0d, 0xfd, 0x58, 0x1a, 0x39, 0xb6, 0x2a, 0xf7, 0x28, 0x7e, 0x40, 0xf5, 0xac, 0x93, + 0x04, 0xf2, 0x3e, 0x7c, 0x3b, 0x23, 0xb6, 0x7a, 0x4a, 0x73, 0x52, 0x96, 0xb5, 0x92, 0xb6, 0xac, + 0x4f, 0xbf, 0xa7, 0xea, 0x40, 0x64, 0x66, 0xc5, 0x54, 0xa5, 0xdc, 0x0a, 0x4b, 0x08, 0xf7, 0xbc, + 0x21, 0x12, 0x6b, 0xb0, 0x58, 0x06, 0xd0, 0xb5, 0x4e, 0x1c, 0x44, 0x2e, 0xc0, 0x62, 0x90, 0x64, + 0x9b, 0x1f, 0x1c, 0x79, 0x06, 0xa8, 0x74, 0xc9, 0xb3, 0xb0, 0xc4, 0xe4, 0xf8, 0xc8, 0x9e, 0x03, + 0x84, 0xe9, 0x74, 0xe3, 0x89, 0x31, 0x50, 0xe0, 0xd9, 0x27, 0x8d, 0x21, 0xb9, 0x0f, 0xe7, 0xd1, + 0x16, 0xc4, 0x1f, 0x04, 0xd3, 0x60, 0x77, 0x9c, 0xce, 0x91, 0xcb, 0x57, 0xad, 0x96, 0x3a, 0x19, + 0xc3, 0xa1, 0x65, 0x35, 0xa4, 0x79, 0x18, 0x0e, 0x2d, 0x7f, 0x20, 0x7e, 0x97, 0x68, 0x75, 0xde, + 0x86, 0x2e, 0x5c, 0x98, 0x51, 0x53, 0x62, 0x1c, 0x8a, 0xcc, 0x38, 0x6e, 0x82, 0x7a, 0xe0, 0x76, + 0xa9, 0x4c, 0xec, 0x76, 0xb1, 0x69, 0x6f, 0xdf, 0x61, 0x69, 0xe5, 0xcd, 0x95, 0x00, 0x6e, 0xf9, + 0x83, 0xfd, 0x3b, 0xfc, 0x2b, 0xc7, 0xe2, 0xc8, 0x93, 0xef, 0x22, 0xe4, 0x45, 0x38, 0x1b, 0x0b, + 0x6a, 0x12, 0x7a, 0xc9, 0x9b, 0x6b, 0xb4, 0x28, 0x1a, 0x02, 0xeb, 0x2a, 0x2c, 0x89, 0x55, 0x31, + 0x0a, 0x9c, 0xe7, 0xcc, 0x02, 0x87, 0xd1, 0x5d, 0xc7, 0x3f, 0x37, 0x11, 0x9d, 0x4a, 0xbd, 0xc6, + 0x9c, 0x42, 0x96, 0x26, 0x2f, 0x00, 0x09, 0xe4, 0xf6, 0x80, 0x51, 0xf0, 0x0f, 0xae, 0x89, 0x92, + 0x60, 0x87, 0xf3, 0xcf, 0xfe, 0x65, 0x06, 0xce, 0xa6, 0xdc, 0x7f, 0xe8, 0x25, 0xc0, 0xeb, 0x8f, + 0xdd, 0x43, 0x76, 0x85, 0x90, 0x3b, 0xb9, 0x2a, 0xc1, 0xb9, 0x52, 0x6b, 0x9e, 0xa5, 0x4d, 0xe7, + 0xdf, 0xe2, 0xbf, 0x28, 0xf3, 0x70, 0x46, 0x42, 0x5f, 0x43, 0xff, 0x25, 0x15, 0x58, 0xc3, 0x5c, + 0x10, 0xbe, 0x37, 0xc0, 0x94, 0x12, 0x28, 0x84, 0xe4, 0x22, 0x37, 0x24, 0x6c, 0x45, 0x53, 0x42, + 0xa2, 0x52, 0x88, 0xa9, 0x0e, 0x63, 0x10, 0xf2, 0x69, 0xd8, 0x92, 0xce, 0x1a, 0x3b, 0xb6, 0xf3, + 0xd0, 0x3c, 0xde, 0xdc, 0x70, 0x82, 0x53, 0x67, 0x27, 0xb2, 0x07, 0xb7, 0xe1, 0x32, 0x4e, 0xa2, + 0xd7, 0x1d, 0xda, 0x89, 0xe4, 0x21, 0xd8, 0x55, 0x16, 0x6d, 0x7f, 0x8b, 0x62, 0x55, 0xba, 0xc3, + 0x58, 0x1e, 0x11, 0xda, 0x6b, 0x3e, 0x7c, 0x0f, 0xe1, 0x5c, 0x6a, 0x8b, 0xe9, 0x01, 0x83, 0xd6, + 0x57, 0xa1, 0x6c, 0xb4, 0x40, 0x7f, 0x53, 0xe1, 0xe8, 0x2a, 0x2c, 0x3d, 0x72, 0x9d, 0x91, 0x3b, + 0xe2, 0x27, 0x37, 0x5f, 0x12, 0x0c, 0x26, 0x1f, 0xdc, 0xdd, 0xe8, 0xd4, 0x70, 0x45, 0x13, 0xa9, + 0xc1, 0x59, 0x76, 0x02, 0x7a, 0xc7, 0x28, 0x0c, 0x72, 0xe5, 0x94, 0x12, 0x11, 0x87, 0xb0, 0x0a, + 0x1e, 0x4d, 0x15, 0xc4, 0x62, 0xb5, 0xcd, 0xb5, 0xc3, 0x38, 0x88, 0xee, 0xe8, 0xf3, 0xe9, 0xd8, + 0x64, 0x1b, 0x0a, 0x8c, 0x38, 0xbb, 0x16, 0xb0, 0x57, 0x85, 0xab, 0x33, 0xbf, 0x50, 0x42, 0xa3, + 0x64, 0x3f, 0xf8, 0x9f, 0x9e, 0xd7, 0xf8, 0x80, 0x6b, 0x1f, 0xcb, 0x8f, 0x26, 0xe6, 0x12, 0x02, + 0xf9, 0x63, 0x89, 0xf6, 0x57, 0x8a, 0xe8, 0x6a, 0xe4, 0x46, 0x4d, 0x97, 0x96, 0xef, 0xf6, 0xc5, + 0xc3, 0xd1, 0xa2, 0xc9, 0x7f, 0x3d, 0xe5, 0x52, 0x27, 0xaf, 0xc2, 0x12, 0x25, 0x7b, 0x38, 0xe9, + 0xb3, 0x25, 0x97, 0x8d, 0x04, 0xf3, 0xa9, 0xb1, 0x22, 0x3a, 0x6d, 0xe5, 0x33, 0x66, 0xe1, 0x38, + 0xfc, 0x49, 0xa5, 0x65, 0xff, 0x78, 0x3c, 0x94, 0x17, 0xaa, 0xd0, 0x2e, 0x5a, 0xb5, 0x56, 0x93, + 0x57, 0xc9, 0x53, 0x9c, 0x50, 0x5a, 0xde, 0x9e, 0x67, 0xfa, 0x45, 0xed, 0x79, 0x28, 0x48, 0xb4, + 0x69, 0x67, 0x98, 0xbb, 0x8d, 0xe8, 0x0c, 0xfb, 0xc5, 0x27, 0xfb, 0x11, 0xe4, 0x05, 0x49, 0x7a, + 0x2d, 0x38, 0x1a, 0xf8, 0x62, 0x93, 0xe3, 0xff, 0x14, 0x46, 0x47, 0x19, 0x3b, 0x39, 0x67, 0xe2, + 0xff, 0x78, 0x96, 0x8c, 0x1d, 0x7a, 0x1f, 0xe8, 0xf9, 0xf6, 0x10, 0xcd, 0xb6, 0x02, 0xe1, 0x99, + 0xc2, 0x5b, 0x3d, 0x9f, 0x19, 0x73, 0xf1, 0x6f, 0xfc, 0x79, 0x70, 0x08, 0xc7, 0x54, 0x10, 0xd3, + 0x78, 0x66, 0xe4, 0xc8, 0xc8, 0x24, 0x8f, 0x0c, 0x16, 0xa4, 0x85, 0xd7, 0x64, 0x5f, 0x06, 0x84, + 0xe1, 0x91, 0x21, 0x71, 0x86, 0x5c, 0x84, 0x33, 0x48, 0x77, 0xf2, 0x70, 0xf6, 0xd8, 0x89, 0x23, + 0xee, 0xe4, 0x71, 0x3e, 0xf5, 0x6e, 0xb0, 0x42, 0x22, 0x4a, 0x10, 0x2a, 0x3c, 0x33, 0xc1, 0x99, + 0xa7, 0xf6, 0x8d, 0x31, 0xc8, 0xb3, 0x58, 0xc8, 0x12, 0xff, 0x04, 0x8c, 0xf2, 0xc9, 0x77, 0x4e, + 0xf2, 0x12, 0xac, 0x07, 0xc9, 0x28, 0xfd, 0xb7, 0xbc, 0xa1, 0x8d, 0xe9, 0x48, 0x4f, 0xb8, 0x48, + 0x4b, 0x44, 0x99, 0xf5, 0x96, 0x37, 0xdc, 0xc7, 0x12, 0xde, 0xcc, 0x3f, 0xc9, 0x08, 0x4d, 0xc6, + 0xf6, 0x60, 0x30, 0xf6, 0xc7, 0x23, 0x67, 0x18, 0x51, 0xf3, 0x92, 0x63, 0x78, 0x06, 0x9b, 0x74, + 0x07, 0xd3, 0x83, 0x0c, 0x46, 0x22, 0x7c, 0x49, 0xb0, 0xc1, 0x0a, 0x77, 0x3e, 0x11, 0xbd, 0x8a, + 0xe8, 0x14, 0x5b, 0x97, 0x91, 0xe9, 0xbe, 0x92, 0xa8, 0x96, 0xcf, 0x98, 0x1b, 0x8c, 0x66, 0x02, + 0x8b, 0x94, 0x53, 0x78, 0x4d, 0x5c, 0xcf, 0xbb, 0x1d, 0x32, 0x9e, 0x28, 0x55, 0x99, 0x25, 0x91, + 0xcf, 0xc2, 0xa2, 0xd7, 0x95, 0xb3, 0x60, 0xc6, 0x35, 0x8c, 0x95, 0x2e, 0x8b, 0xc4, 0x1d, 0xd2, + 0xa0, 0x5b, 0xc3, 0xe3, 0xd0, 0xed, 0xe5, 0x88, 0x42, 0x5c, 0xdb, 0x16, 0x97, 0xe6, 0x64, 0x35, + 0xb2, 0x02, 0x99, 0x60, 0x21, 0x66, 0xbc, 0x2e, 0xe3, 0x02, 0x61, 0x2c, 0x70, 0x93, 0xff, 0xd2, + 0xfe, 0x37, 0xdc, 0x3c, 0xed, 0x18, 0x51, 0x8e, 0x31, 0x65, 0xc0, 0x17, 0x59, 0x18, 0xce, 0xe8, + 0xb8, 0x5d, 0x05, 0x39, 0x94, 0xb1, 0x27, 0x96, 0x88, 0x80, 0xb5, 0x47, 0x9e, 0xf6, 0x2f, 0x59, + 0x58, 0x89, 0x3e, 0x01, 0x90, 0xe7, 0x21, 0x27, 0x31, 0xca, 0x8d, 0x94, 0x77, 0x02, 0x64, 0x8f, + 0x88, 0x74, 0x2a, 0xc6, 0x48, 0xee, 0xc2, 0x0a, 0x1a, 0x25, 0xa2, 0x84, 0x3c, 0xf6, 0xf8, 0xc3, + 0xd2, 0xec, 0xb7, 0xc1, 0xfc, 0x4f, 0xde, 0xbb, 0x72, 0x06, 0x9f, 0x01, 0x97, 0x68, 0x5d, 0x2a, + 0xa4, 0xd2, 0x42, 0x49, 0xc3, 0x9b, 0x9b, 0xae, 0xe1, 0xe5, 0x5d, 0x99, 0xa2, 0xe1, 0x9d, 0x9b, + 0xa1, 0xe1, 0x0d, 0x6b, 0xca, 0x1a, 0x5e, 0xd4, 0xf3, 0x2f, 0x4c, 0xd3, 0xf3, 0x87, 0x75, 0x98, + 0x9e, 0x3f, 0xd4, 0xd0, 0xe6, 0xa7, 0x6a, 0x68, 0xc3, 0x3a, 0x5c, 0x43, 0x1b, 0xea, 0x4c, 0x17, + 0xa7, 0xea, 0x4c, 0xa5, 0x4a, 0x4c, 0x67, 0x7a, 0x8d, 0x0f, 0xec, 0xc8, 0x79, 0x6c, 0xe3, 0x88, + 0xf3, 0x23, 0x1f, 0x87, 0xcc, 0x74, 0x1e, 0xa3, 0xb5, 0xd1, 0xf6, 0x22, 0x08, 0x13, 0x25, 0xed, + 0x47, 0x31, 0x06, 0x24, 0xe6, 0xfc, 0x3a, 0xac, 0xb0, 0x73, 0x98, 0x87, 0x77, 0x65, 0x07, 0xf1, + 0xb2, 0xb9, 0x2c, 0xa0, 0xec, 0x2a, 0xfd, 0x31, 0x58, 0x0d, 0xd0, 0xf8, 0x6d, 0x12, 0x3d, 0x17, + 0xcd, 0xa0, 0x36, 0x0f, 0xc3, 0x23, 0xd3, 0x1b, 0xf1, 0x40, 0x37, 0x11, 0x7a, 0x2c, 0x0a, 0xca, + 0x0b, 0x40, 0x42, 0xb4, 0xc0, 0x60, 0x33, 0x87, 0xa8, 0x6b, 0x01, 0x6a, 0x60, 0x55, 0xf9, 0xfb, + 0x4a, 0x4c, 0x47, 0xfb, 0x51, 0x35, 0xff, 0x79, 0x08, 0xbe, 0x6e, 0x73, 0x3d, 0x9b, 0xe8, 0x81, + 0x2a, 0x0a, 0x9a, 0x1c, 0xae, 0x1d, 0xc6, 0xef, 0x84, 0x1f, 0x51, 0xab, 0xb4, 0x1f, 0x67, 0x23, + 0xfa, 0x2b, 0xf1, 0x19, 0x2a, 0xdf, 0xf8, 0x03, 0x9b, 0x4f, 0x31, 0x67, 0xbf, 0x57, 0xa7, 0x2c, + 0x53, 0x6e, 0xa2, 0x66, 0x59, 0x0d, 0x13, 0x7c, 0x7f, 0x20, 0x2c, 0xd6, 0x6c, 0x76, 0xd7, 0x61, + 0x12, 0x19, 0x6e, 0x53, 0x41, 0x8e, 0xf1, 0xda, 0xe2, 0x6c, 0x72, 0x42, 0x81, 0x40, 0x77, 0x29, + 0xde, 0x79, 0x82, 0x5f, 0xe2, 0x03, 0x6d, 0x40, 0x75, 0xaf, 0x1f, 0x25, 0x9e, 0x4d, 0xb9, 0xd5, + 0x26, 0x88, 0xe3, 0x28, 0x21, 0x65, 0x75, 0x22, 0xfe, 0x15, 0x64, 0x0d, 0x58, 0x42, 0xed, 0x91, + 0x20, 0x98, 0x4b, 0x79, 0x51, 0x49, 0x76, 0xbe, 0x54, 0xa9, 0x99, 0x05, 0x5a, 0x4f, 0x90, 0x39, + 0x82, 0x67, 0x64, 0x9d, 0x4f, 0xb4, 0x91, 0x73, 0x22, 0x28, 0xf3, 0xcc, 0x11, 0x08, 0x55, 0x43, + 0xd8, 0xd4, 0xf3, 0x4e, 0x14, 0xc0, 0xd1, 0xb4, 0x23, 0xd8, 0x9a, 0x3e, 0x25, 0x33, 0x12, 0x7e, + 0x85, 0xa2, 0x4d, 0x46, 0x16, 0x6d, 0x64, 0x0d, 0x50, 0x36, 0xa2, 0x01, 0xd2, 0xfe, 0x38, 0x0b, + 0xcf, 0x9d, 0x62, 0xba, 0x66, 0x7c, 0xf3, 0x73, 0x51, 0xc1, 0x39, 0x13, 0xb9, 0xb3, 0x53, 0xa2, + 0xfc, 0x4c, 0x38, 0xe9, 0x77, 0xa6, 0x88, 0xcd, 0x5f, 0x85, 0x55, 0xc6, 0xf8, 0x99, 0x95, 0xe9, + 0xc1, 0xa4, 0x77, 0x0a, 0xce, 0x7f, 0x41, 0xb8, 0xc4, 0xc5, 0xaa, 0xe2, 0x61, 0x80, 0xfc, 0xce, + 0x0a, 0x60, 0xa4, 0x05, 0x05, 0x44, 0x3b, 0x70, 0xbc, 0xde, 0xa9, 0x7c, 0xb3, 0x84, 0xc3, 0x9d, + 0x5c, 0x8d, 0x19, 0xc7, 0x53, 0xc0, 0x2e, 0xfe, 0x26, 0x37, 0x60, 0xb5, 0x3f, 0x39, 0xa6, 0x22, + 0x21, 0x5b, 0x0b, 0xdc, 0x98, 0x67, 0xce, 0x5c, 0xee, 0x4f, 0x8e, 0xf5, 0xe1, 0x10, 0xa7, 0x14, + 0xad, 0x7e, 0xd6, 0x28, 0x1e, 0xdb, 0xb5, 0x02, 0x73, 0x1e, 0x31, 0x29, 0x01, 0xb6, 0x6f, 0x39, + 0xee, 0x3a, 0x30, 0x1b, 0x50, 0x9e, 0xf0, 0x8c, 0xfd, 0xd0, 0xfe, 0x3d, 0x23, 0x34, 0x11, 0xd3, + 0xd7, 0xfd, 0x6f, 0xa6, 0x28, 0x65, 0x8a, 0x6e, 0x82, 0x4a, 0x87, 0x3e, 0x64, 0x2a, 0xc1, 0x1c, + 0xad, 0xf4, 0x27, 0xc7, 0xc1, 0xd8, 0xc9, 0x03, 0x3f, 0x2f, 0x0f, 0xfc, 0xab, 0x42, 0x53, 0x91, + 0xca, 0x1e, 0xa6, 0x0f, 0x39, 0x95, 0x98, 0x6e, 0x9c, 0x8e, 0x09, 0xfc, 0x66, 0xde, 0x52, 0xe6, + 0x2d, 0xa6, 0xd4, 0x9e, 0x4b, 0x28, 0xb5, 0x53, 0xf6, 0xde, 0x7c, 0xda, 0xde, 0x4b, 0xa8, 0xd0, + 0x17, 0x52, 0x54, 0xe8, 0xa9, 0x1b, 0x34, 0xff, 0x84, 0x0d, 0xba, 0x28, 0xaf, 0x93, 0x7f, 0x08, + 0x54, 0x4b, 0xd1, 0x2b, 0xd0, 0x43, 0x38, 0x2b, 0xae, 0x40, 0xec, 0xe4, 0x08, 0x5f, 0x46, 0x0a, + 0x77, 0x6e, 0xa5, 0x5d, 0x7e, 0x10, 0x2d, 0xe5, 0x82, 0xb2, 0xc6, 0xaf, 0x3d, 0x61, 0xf9, 0xff, + 0x9c, 0x0b, 0x0f, 0x79, 0x00, 0xe7, 0x31, 0x5d, 0x40, 0x47, 0x7e, 0xd3, 0xb1, 0x47, 0xee, 0x01, + 0x5f, 0x0f, 0x57, 0x13, 0xd7, 0x03, 0xaf, 0x23, 0x35, 0xc7, 0x74, 0x0f, 0xca, 0x67, 0xcc, 0x75, + 0x3f, 0x05, 0x1e, 0xbf, 0x4b, 0xfd, 0x99, 0x02, 0xda, 0x93, 0xc7, 0x0b, 0xaf, 0xbd, 0xf1, 0x01, + 0xa7, 0xd7, 0x5e, 0x69, 0xf4, 0x9e, 0x83, 0xe5, 0x91, 0x7b, 0x30, 0x72, 0xfd, 0xa3, 0x88, 0x6e, + 0x6a, 0x89, 0x03, 0xc5, 0xc0, 0x88, 0x18, 0xa3, 0x4f, 0x75, 0x19, 0x11, 0x95, 0xb4, 0xdd, 0xe0, + 0x8a, 0x9c, 0x3a, 0x0f, 0x74, 0x35, 0xc9, 0x0d, 0x64, 0x3f, 0xee, 0xe6, 0xf2, 0x19, 0x35, 0x6b, + 0xf2, 0x48, 0xa8, 0x07, 0x5e, 0xcf, 0xd5, 0xfe, 0x42, 0x11, 0x12, 0x41, 0xda, 0xe0, 0x91, 0x87, + 0x92, 0x6d, 0x76, 0x36, 0x21, 0x86, 0xa4, 0x55, 0x91, 0xcd, 0x58, 0x79, 0xb4, 0x4d, 0x04, 0x44, + 0xa2, 0x6d, 0x22, 0xe4, 0x03, 0x18, 0x98, 0x72, 0x45, 0xc1, 0xeb, 0xc2, 0xc0, 0x8b, 0xf2, 0xbc, + 0xfd, 0xdb, 0xe4, 0x16, 0x2c, 0x30, 0x9b, 0x2e, 0xd1, 0xdc, 0xd5, 0x48, 0x73, 0xf7, 0x6f, 0x9b, + 0xa2, 0x5c, 0x7b, 0x27, 0x78, 0x71, 0x4c, 0x74, 0x62, 0xff, 0x36, 0x79, 0xf5, 0x74, 0xb6, 0xd6, + 0x79, 0x61, 0x6b, 0x1d, 0xd8, 0x59, 0xbf, 0x16, 0xb1, 0xb3, 0xbe, 0x36, 0x7b, 0xb4, 0xf8, 0x3b, + 0x31, 0x8b, 0x2e, 0x19, 0x46, 0x1d, 0xfb, 0x45, 0x06, 0x2e, 0xcd, 0xac, 0x41, 0x2e, 0x42, 0x5e, + 0x6f, 0x56, 0x5a, 0xe1, 0xfc, 0xd2, 0x3d, 0x23, 0x20, 0x64, 0x0f, 0x16, 0xb7, 0x1d, 0xdf, 0xeb, + 0xd0, 0x65, 0x9c, 0xfa, 0x70, 0x93, 0x20, 0x1b, 0xa0, 0x97, 0xcf, 0x98, 0x61, 0x5d, 0x62, 0xc3, + 0x1a, 0xee, 0x85, 0x48, 0x26, 0xb1, 0x6c, 0x8a, 0x7a, 0x25, 0x41, 0x30, 0x51, 0x8d, 0xf2, 0x99, + 0x04, 0x90, 0x3c, 0x02, 0x62, 0x59, 0xe5, 0x92, 0x3b, 0x1a, 0x73, 0xb5, 0xc3, 0xd8, 0x0b, 0x0c, + 0x77, 0x5f, 0x7a, 0xc2, 0xd8, 0x25, 0xea, 0x95, 0xcf, 0x98, 0x29, 0xd4, 0xe2, 0xdb, 0xfc, 0x6d, + 0x21, 0xef, 0x4c, 0x1f, 0x84, 0xa7, 0x88, 0xdc, 0x7b, 0x13, 0xf2, 0x4d, 0x61, 0x25, 0x22, 0x39, + 0x40, 0x08, 0x8b, 0x10, 0x33, 0x28, 0xd5, 0x7e, 0x4b, 0x11, 0x7a, 0x96, 0x27, 0x0f, 0x96, 0x94, + 0xe8, 0xad, 0x3b, 0x3b, 0xd1, 0x5b, 0xf7, 0x57, 0x4c, 0xf4, 0xa6, 0x79, 0x70, 0xeb, 0xd4, 0x03, + 0x4b, 0x3e, 0x03, 0x2a, 0xe6, 0xc4, 0x72, 0xa4, 0x49, 0x62, 0xfb, 0x6b, 0x2d, 0x08, 0xe5, 0x5e, + 0xe6, 0x89, 0x07, 0xcd, 0xd5, 0x4e, 0xb4, 0xb6, 0xf6, 0xa7, 0x3c, 0x84, 0x7f, 0xa5, 0xdb, 0x8c, + 0x3d, 0x01, 0x7c, 0x50, 0x9f, 0x19, 0x23, 0xb2, 0xd9, 0x9e, 0x93, 0x72, 0x92, 0x26, 0xbf, 0x35, + 0xdd, 0x75, 0x46, 0xda, 0x79, 0x7f, 0x94, 0x85, 0x8b, 0xb3, 0xaa, 0xa7, 0x66, 0x3d, 0x57, 0x9e, + 0x2e, 0xeb, 0xf9, 0x2d, 0xc8, 0x33, 0x58, 0xe0, 0x10, 0x82, 0x73, 0xcb, 0xab, 0xd2, 0xb9, 0x15, + 0xc5, 0xe4, 0x39, 0x98, 0xd7, 0x4b, 0x56, 0x98, 0x88, 0x0f, 0x2d, 0xb7, 0x9d, 0x8e, 0x8f, 0x36, + 0xc1, 0xbc, 0x88, 0x7c, 0x25, 0x99, 0x7b, 0x92, 0x67, 0xe0, 0xbb, 0x20, 0x0d, 0x48, 0x22, 0xbb, + 0x06, 0xb6, 0x37, 0xcc, 0x06, 0xc1, 0x03, 0xac, 0x9b, 0xc9, 0x3c, 0x96, 0x1a, 0xcc, 0x37, 0x47, + 0xae, 0xef, 0x8e, 0x65, 0xab, 0xea, 0x21, 0x42, 0x4c, 0x5e, 0xc2, 0x6d, 0x9e, 0x9d, 0x13, 0x16, + 0xe2, 0x62, 0x5e, 0x0e, 0x3b, 0x84, 0x46, 0xd2, 0x14, 0x6c, 0x4a, 0x28, 0xb4, 0x42, 0xd5, 0x99, + 0xf4, 0x3b, 0x47, 0x6d, 0xb3, 0xca, 0x25, 0x27, 0x56, 0xa1, 0x87, 0x50, 0xda, 0x41, 0xdf, 0x94, + 0x50, 0xb4, 0xef, 0x2a, 0xb0, 0x9e, 0xd6, 0x0f, 0x72, 0x11, 0x72, 0xfd, 0xd4, 0x34, 0x9b, 0x7d, + 0xe6, 0x99, 0x5f, 0xa0, 0x7f, 0xed, 0x83, 0xc1, 0xe8, 0xd8, 0x19, 0xcb, 0xb6, 0xe7, 0x12, 0xd8, + 0x04, 0xfa, 0x63, 0x17, 0xff, 0x27, 0x57, 0xc4, 0x91, 0x93, 0x4d, 0x24, 0xe6, 0xc4, 0x3f, 0x9a, + 0x0e, 0x50, 0xe9, 0x36, 0x1b, 0x43, 0x96, 0xdd, 0xe1, 0x65, 0xc8, 0xd1, 0x66, 0xc5, 0x56, 0x2f, + 0x5d, 0x3f, 0x7a, 0xad, 0xca, 0x91, 0x58, 0xab, 0x7c, 0xe7, 0xb8, 0x67, 0x22, 0xb2, 0x76, 0x1f, + 0x56, 0xa2, 0x18, 0xc4, 0x88, 0x06, 0xf8, 0x2d, 0xdc, 0x51, 0x39, 0xa5, 0xed, 0xc1, 0x80, 0xf9, + 0x3f, 0x6d, 0x3f, 0xf3, 0x8b, 0xf7, 0xae, 0x00, 0xfd, 0xc9, 0xea, 0xa4, 0x05, 0x00, 0xd6, 0xbe, + 0x97, 0x81, 0xf5, 0x30, 0xe4, 0x82, 0xd8, 0x43, 0xbf, 0xb6, 0xfe, 0xbf, 0x7a, 0xc4, 0x3f, 0x55, + 0xc8, 0x8d, 0xc9, 0x0e, 0xce, 0x70, 0x8b, 0xdb, 0x83, 0xcd, 0x69, 0xf8, 0xe4, 0x79, 0x58, 0xc4, + 0x28, 0x5d, 0x43, 0xa7, 0xe3, 0xca, 0x6c, 0xb6, 0x2f, 0x80, 0x66, 0x58, 0xae, 0xfd, 0x4c, 0x81, + 0x2d, 0xee, 0xb5, 0x53, 0x73, 0xbc, 0x3e, 0xbe, 0xdf, 0x74, 0xdc, 0x0f, 0xc7, 0x7f, 0x7d, 0x2f, + 0xc2, 0xc7, 0xae, 0x47, 0x9d, 0xb3, 0x12, 0x5f, 0x9b, 0xde, 0x5b, 0x72, 0x0b, 0x23, 0xcf, 0x71, + 0xfb, 0x86, 0x1c, 0x8b, 0x17, 0xd2, 0xa7, 0x00, 0x39, 0x5e, 0x08, 0x62, 0x68, 0xff, 0x07, 0x2e, + 0xcf, 0xfe, 0x00, 0xf9, 0x32, 0x2c, 0x63, 0x2a, 0xb5, 0xf6, 0xf0, 0x70, 0xe4, 0x74, 0x5d, 0xa1, + 0xd9, 0x13, 0x0a, 0x68, 0xb9, 0x8c, 0x05, 0xd2, 0xe3, 0xf1, 0x2b, 0x0e, 0x31, 0x49, 0x1b, 0xaf, + 0x14, 0x71, 0x8d, 0x93, 0xa9, 0x69, 0xdf, 0x52, 0x80, 0x24, 0x69, 0x90, 0x4f, 0xc2, 0x52, 0xbb, + 0x55, 0xb2, 0xc6, 0xce, 0x68, 0x5c, 0x1e, 0x4c, 0x46, 0x3c, 0x8a, 0x1d, 0x0b, 0x67, 0x30, 0xee, + 0xd8, 0xec, 0xa5, 0xee, 0x68, 0x30, 0x19, 0x99, 0x11, 0x3c, 0x4c, 0xd9, 0xe5, 0xba, 0x6f, 0x75, + 0x9d, 0x93, 0x68, 0xca, 0x2e, 0x0e, 0x8b, 0xa4, 0xec, 0xe2, 0x30, 0xed, 0x5d, 0x05, 0x2e, 0x08, + 0xb3, 0xd5, 0x6e, 0x4a, 0x5b, 0x4a, 0x18, 0xb4, 0x67, 0x24, 0xc2, 0x26, 0xcf, 0x92, 0xd0, 0xd7, + 0x44, 0x5c, 0x2b, 0x6c, 0x20, 0x8a, 0xea, 0xac, 0x2e, 0xf9, 0x1c, 0xe4, 0xac, 0xf1, 0x60, 0x78, + 0x8a, 0xc0, 0x56, 0x6a, 0x30, 0xa3, 0xe3, 0xc1, 0x10, 0x49, 0x60, 0x4d, 0xcd, 0x85, 0x75, 0xb9, + 0x71, 0xa2, 0xc5, 0xa4, 0x06, 0x0b, 0x3c, 0x82, 0x61, 0xcc, 0x22, 0x64, 0x46, 0x9f, 0xb6, 0x57, + 0x45, 0xf4, 0x2c, 0x1e, 0xb6, 0xd7, 0x14, 0x34, 0xb4, 0xdf, 0x51, 0xa0, 0x40, 0x05, 0x1b, 0xbc, + 0x94, 0x7e, 0xd0, 0x25, 0x1d, 0x95, 0x83, 0x85, 0x81, 0x53, 0x40, 0xfe, 0x54, 0xa7, 0xf1, 0x2b, + 0xb0, 0x1a, 0xab, 0x40, 0x34, 0x8c, 0x9b, 0xd2, 0xf3, 0x3a, 0x0e, 0xcb, 0x00, 0xc4, 0x8c, 0x83, + 0x22, 0x30, 0xed, 0xff, 0x29, 0xb0, 0xde, 0x78, 0x6b, 0xec, 0xb0, 0x07, 0x75, 0x73, 0xd2, 0x13, + 0xfb, 0x9d, 0x0a, 0x6b, 0xc2, 0xfe, 0x99, 0xc5, 0x74, 0x60, 0xc2, 0x1a, 0x87, 0x99, 0x41, 0x29, + 0x29, 0x43, 0x9e, 0x9f, 0x2f, 0x3e, 0x8f, 0xb6, 0x7b, 0x59, 0xd2, 0x8d, 0x84, 0x84, 0x39, 0x12, + 0xed, 0x09, 0xb2, 0x30, 0x5e, 0xc7, 0x0c, 0x6a, 0x6b, 0xff, 0xaa, 0xc0, 0xc6, 0x94, 0x3a, 0xe4, + 0x0d, 0x98, 0x43, 0x7f, 0x53, 0x3e, 0x7b, 0x17, 0xa7, 0x7c, 0x62, 0xdc, 0x39, 0xda, 0xbf, 0xcd, + 0x0e, 0xa2, 0x63, 0xfa, 0xc3, 0x64, 0xb5, 0xc8, 0x43, 0x58, 0xd4, 0xbb, 0x5d, 0x7e, 0x3b, 0xcb, + 0x44, 0x6e, 0x67, 0x53, 0xbe, 0xf8, 0x62, 0x80, 0xcf, 0x6e, 0x67, 0xcc, 0xf3, 0xa9, 0xdb, 0xb5, + 0xb9, 0x2f, 0x6d, 0x48, 0x6f, 0xeb, 0x33, 0xb0, 0x12, 0x45, 0x7e, 0x2a, 0xf7, 0xbf, 0x77, 0x14, + 0x50, 0xa3, 0x6d, 0xf8, 0x68, 0xe2, 0x7e, 0xa5, 0x4d, 0xf3, 0x13, 0x16, 0xd5, 0xef, 0x65, 0xe0, + 0x5c, 0xea, 0x08, 0x93, 0x17, 0x60, 0x5e, 0x1f, 0x0e, 0x2b, 0x3b, 0x7c, 0x55, 0x71, 0x09, 0x09, + 0x95, 0xde, 0x91, 0xcb, 0x2b, 0x43, 0x22, 0x2f, 0x43, 0x9e, 0xd9, 0x6d, 0xec, 0x08, 0x86, 0x83, + 0x81, 0x8c, 0xb8, 0x51, 0x49, 0x34, 0xee, 0xad, 0x40, 0x24, 0xbb, 0xb0, 0xc2, 0x43, 0x00, 0x99, + 0xee, 0xa1, 0xfb, 0x8d, 0x20, 0x01, 0x03, 0xe6, 0x88, 0x10, 0x9a, 0x74, 0x7b, 0xc4, 0xca, 0xe4, + 0x20, 0x38, 0xd1, 0x5a, 0xa4, 0x0a, 0x2a, 0xd2, 0x94, 0x29, 0xb1, 0xe0, 0xbb, 0x18, 0x94, 0x89, + 0x35, 0x62, 0x0a, 0xad, 0x44, 0xcd, 0x60, 0xba, 0x74, 0xdf, 0xf7, 0x0e, 0xfb, 0xc7, 0x6e, 0x7f, + 0xfc, 0xd1, 0x4d, 0x57, 0xf8, 0x8d, 0x53, 0x4d, 0xd7, 0x1f, 0xe4, 0xd8, 0x66, 0x8e, 0x57, 0xa3, + 0x12, 0x8d, 0x14, 0x6f, 0x1d, 0x25, 0x1a, 0x7a, 0x3f, 0xe3, 0x41, 0x6e, 0x76, 0x60, 0x81, 0x05, + 0x1f, 0x12, 0x3b, 0xe3, 0x52, 0x6a, 0x13, 0x18, 0xce, 0xfe, 0x6d, 0x26, 0xbe, 0x30, 0xc7, 0x57, + 0xdf, 0x14, 0x55, 0xc9, 0x3e, 0x14, 0x4a, 0x3d, 0xd7, 0xe9, 0x4f, 0x86, 0xad, 0xd3, 0x3d, 0x1a, + 0x6f, 0xf2, 0xbe, 0x2c, 0x75, 0x58, 0x35, 0x7c, 0x6c, 0x46, 0x4e, 0x2e, 0x13, 0x22, 0xad, 0xc0, + 0x17, 0x2e, 0x87, 0x8a, 0xd7, 0x97, 0x66, 0x8c, 0x4f, 0x1c, 0x88, 0xf5, 0xa2, 0x8e, 0x9e, 0xdc, + 0x59, 0xce, 0x86, 0x95, 0xaa, 0xe3, 0x8f, 0x5b, 0x23, 0xa7, 0xef, 0x63, 0xd0, 0xd2, 0x53, 0x04, + 0x75, 0xbb, 0x20, 0x12, 0x72, 0xa3, 0xca, 0x74, 0x1c, 0x54, 0x65, 0x0a, 0xd9, 0x28, 0x39, 0x2a, + 0x2f, 0xed, 0x7a, 0x7d, 0xa7, 0xe7, 0x7d, 0x53, 0xb8, 0x0c, 0x33, 0x79, 0xe9, 0x40, 0x00, 0xcd, + 0xb0, 0x5c, 0xfb, 0x52, 0x62, 0xde, 0x58, 0x2b, 0x0b, 0xb0, 0xc0, 0x03, 0x4a, 0xb0, 0x00, 0x0b, + 0x4d, 0xa3, 0xbe, 0x53, 0xa9, 0xef, 0xa9, 0x0a, 0x59, 0x01, 0x68, 0x9a, 0x8d, 0x92, 0x61, 0x59, + 0xf4, 0x77, 0x86, 0xfe, 0xe6, 0xd1, 0x17, 0x76, 0xdb, 0x55, 0x35, 0x2b, 0x05, 0x60, 0xc8, 0x69, + 0x3f, 0x55, 0xe0, 0x7c, 0xfa, 0x54, 0x92, 0x16, 0x60, 0x08, 0x0e, 0x6e, 0x3e, 0xf0, 0xc9, 0x99, + 0xf3, 0x9e, 0x0a, 0x8e, 0x87, 0xf2, 0x18, 0xb3, 0x10, 0x11, 0x19, 0xf1, 0xf6, 0xc5, 0x7c, 0x4e, + 0xbd, 0xae, 0x99, 0xf1, 0xba, 0x5a, 0x09, 0x36, 0xa7, 0xd1, 0x88, 0x76, 0x75, 0x15, 0x0a, 0x7a, + 0xb3, 0x59, 0xad, 0x94, 0xf4, 0x56, 0xa5, 0x51, 0x57, 0x15, 0xb2, 0x08, 0x73, 0x7b, 0x66, 0xa3, + 0xdd, 0x54, 0x33, 0xda, 0xf7, 0x15, 0x58, 0xae, 0x84, 0xf6, 0x80, 0x1f, 0x74, 0xf3, 0x7d, 0x2a, + 0xb2, 0xf9, 0x36, 0x83, 0x60, 0x35, 0xc1, 0x07, 0x4e, 0xb5, 0xf3, 0xde, 0xcf, 0xc0, 0x5a, 0xa2, + 0x0e, 0xb1, 0x60, 0x41, 0xbf, 0x6f, 0x35, 0x2a, 0x3b, 0x25, 0xde, 0xb2, 0x2b, 0xa1, 0x21, 0x1b, + 0xa6, 0x2f, 0x4b, 0x7c, 0x85, 0x39, 0x78, 0x3f, 0xf6, 0xed, 0x81, 0xd7, 0x95, 0x72, 0x19, 0x97, + 0xcf, 0x98, 0x82, 0x12, 0x9e, 0x64, 0xdf, 0x9c, 0x8c, 0x5c, 0x24, 0x9b, 0x89, 0xe8, 0x75, 0x03, + 0x78, 0x92, 0x30, 0x7a, 0xd6, 0x38, 0xb4, 0x3c, 0x49, 0x3a, 0xa4, 0x47, 0xea, 0x30, 0xbf, 0xe7, + 0x8d, 0xcb, 0x93, 0x47, 0x7c, 0xff, 0x5e, 0x0e, 0x93, 0x59, 0x95, 0x27, 0x8f, 0x92, 0x64, 0x51, + 0x65, 0xc9, 0x82, 0x31, 0x45, 0x48, 0x72, 0x2a, 0x71, 0x9f, 0xd4, 0xdc, 0x53, 0xf9, 0xa4, 0x6e, + 0x2f, 0x43, 0x81, 0xdf, 0xa1, 0xf0, 0x7a, 0xf2, 0x63, 0x05, 0x36, 0xa7, 0x8d, 0x1c, 0xbd, 0x96, + 0x45, 0x63, 0x4f, 0x9c, 0x0f, 0xb2, 0x9d, 0x44, 0x83, 0x4e, 0x08, 0x34, 0xf2, 0x26, 0x14, 0x98, + 0x95, 0x96, 0xf5, 0x72, 0xdb, 0xac, 0xf0, 0xe5, 0x7a, 0xe9, 0x9f, 0xde, 0xbb, 0xb2, 0xc1, 0x0d, + 0xbb, 0xfc, 0x97, 0xed, 0xc9, 0xc8, 0x8b, 0x64, 0x86, 0x90, 0x6b, 0x50, 0x29, 0xda, 0x99, 0x74, + 0x3d, 0x57, 0xdc, 0x21, 0x84, 0x7f, 0x3e, 0x87, 0xc9, 0x67, 0x9a, 0x80, 0x69, 0xdf, 0x51, 0x60, + 0x6b, 0xfa, 0x34, 0xd1, 0x73, 0xb2, 0xc5, 0x8c, 0xdd, 0x84, 0x87, 0x3c, 0x9e, 0x93, 0x81, 0x45, + 0x9c, 0x4c, 0x53, 0x20, 0xd2, 0x4a, 0x5c, 0xc3, 0x25, 0x94, 0x24, 0x72, 0x7a, 0xf3, 0x68, 0x25, + 0x81, 0xa8, 0x3d, 0x80, 0x8d, 0x29, 0x93, 0x4a, 0x3e, 0x9b, 0x9a, 0x43, 0x09, 0x1d, 0xc8, 0xe4, + 0x1c, 0x4a, 0x91, 0x64, 0x7c, 0x12, 0x5c, 0xfb, 0xe7, 0x0c, 0x9c, 0xa7, 0xbb, 0xab, 0xe7, 0xfa, + 0xbe, 0x1e, 0xa6, 0x1b, 0xa6, 0x5c, 0xf1, 0x55, 0x98, 0x3f, 0x7a, 0x3a, 0x55, 0x31, 0x43, 0x27, + 0x04, 0xf0, 0xc4, 0x12, 0x6e, 0x4b, 0xf4, 0x7f, 0x72, 0x15, 0xe4, 0x5c, 0xf5, 0x59, 0x8c, 0x56, + 0x9b, 0xd9, 0x54, 0xcc, 0xc5, 0x61, 0x90, 0x56, 0xfa, 0x35, 0x98, 0x43, 0x7d, 0x0a, 0x3f, 0x3b, + 0x84, 0xcc, 0x9f, 0xde, 0x3a, 0xd4, 0xb6, 0x98, 0xac, 0x02, 0xf9, 0x04, 0x40, 0x98, 0xe8, 0x83, + 0x1f, 0x0e, 0x42, 0xcf, 0x10, 0xe4, 0xfa, 0x30, 0x17, 0x8f, 0x0f, 0x1c, 0x9e, 0x3d, 0xa3, 0x08, + 0x6b, 0x62, 0xc4, 0x87, 0x22, 0xc8, 0x25, 0x7f, 0xc5, 0x5c, 0x65, 0x05, 0x95, 0xa1, 0x08, 0x74, + 0x79, 0x2d, 0x91, 0x6f, 0x1b, 0x63, 0x5d, 0xc7, 0x92, 0x6a, 0x5f, 0x4b, 0x24, 0xd5, 0xce, 0x33, + 0x2c, 0x39, 0x73, 0xb6, 0xf6, 0xf7, 0x19, 0x58, 0xbc, 0x4f, 0xa5, 0x32, 0xd4, 0x35, 0xcc, 0xd6, + 0x5d, 0xdc, 0x81, 0x42, 0x75, 0xe0, 0xf0, 0xe7, 0x22, 0xee, 0xed, 0xc3, 0x5c, 0xf4, 0x7b, 0x03, + 0x47, 0xbc, 0x3c, 0xf9, 0xa6, 0x8c, 0xf4, 0x84, 0xf0, 0x02, 0x77, 0x61, 0x9e, 0x3d, 0xdf, 0x71, + 0x35, 0x9a, 0x90, 0xcb, 0x83, 0x16, 0xbd, 0xc8, 0x8a, 0xa5, 0x17, 0x0e, 0xf6, 0x04, 0x28, 0x0b, + 0x89, 0x3c, 0x64, 0xaf, 0xa4, 0x59, 0x99, 0x3b, 0x9d, 0x66, 0x45, 0x0a, 0x4d, 0x38, 0x7f, 0x9a, + 0xd0, 0x84, 0x5b, 0xaf, 0x43, 0x41, 0x6a, 0xcf, 0x53, 0x89, 0xe9, 0xdf, 0xce, 0xc0, 0x32, 0xf6, + 0x2a, 0xb0, 0xe5, 0xf9, 0xf5, 0xd4, 0x13, 0x7d, 0x2a, 0xa2, 0x27, 0xda, 0x94, 0xe7, 0x8b, 0xf5, + 0x6c, 0x86, 0x82, 0xe8, 0x2e, 0xac, 0x25, 0x10, 0xc9, 0x2b, 0x30, 0x47, 0x9b, 0x2f, 0xee, 0xd5, + 0x6a, 0x7c, 0x05, 0x84, 0x61, 0xac, 0x69, 0xc7, 0x7d, 0x93, 0x61, 0x6b, 0xff, 0xa6, 0xc0, 0x12, + 0xcf, 0x22, 0xd3, 0x3f, 0x18, 0x3c, 0x71, 0x38, 0x6f, 0xc4, 0x87, 0x93, 0x05, 0xcb, 0xe1, 0xc3, + 0xf9, 0x5f, 0x3d, 0x88, 0xaf, 0x47, 0x06, 0x71, 0x23, 0x08, 0x6a, 0x29, 0xba, 0x33, 0x63, 0x0c, + 0x7f, 0x84, 0x61, 0x9e, 0xa3, 0x88, 0xe4, 0x2b, 0xb0, 0x58, 0x77, 0x1f, 0x47, 0xae, 0xa7, 0x37, + 0xa6, 0x10, 0x7d, 0x31, 0x40, 0x64, 0x7b, 0x0a, 0x4f, 0xf6, 0xbe, 0xfb, 0xd8, 0x4e, 0xbc, 0x1c, + 0x86, 0x24, 0xe9, 0x0d, 0x35, 0x5a, 0xed, 0x69, 0x96, 0x3e, 0x77, 0x3d, 0xc6, 0xf8, 0x4f, 0xdf, + 0xcd, 0x02, 0x84, 0x5e, 0x9b, 0x74, 0x03, 0x46, 0x8c, 0x26, 0x84, 0x66, 0x1f, 0x41, 0xf2, 0x1a, + 0x17, 0xb6, 0x14, 0x37, 0xb8, 0x06, 0x3a, 0x33, 0x3d, 0xe8, 0x28, 0xea, 0xa2, 0x4b, 0xdc, 0x4d, + 0xb0, 0xeb, 0xf6, 0x1c, 0xc6, 0xdb, 0xb3, 0xdb, 0xd7, 0x30, 0xc6, 0x74, 0x00, 0x9d, 0x92, 0x3d, + 0x1c, 0x9d, 0x09, 0x77, 0x28, 0x42, 0xc2, 0x13, 0x3a, 0xf7, 0x74, 0x9e, 0xd0, 0x4d, 0x58, 0xf4, + 0xfa, 0x6f, 0xbb, 0xfd, 0xf1, 0x60, 0x74, 0x82, 0x6a, 0xf7, 0x50, 0x9f, 0x47, 0x87, 0xa0, 0x22, + 0xca, 0xd8, 0x3c, 0xe0, 0x99, 0x1b, 0xe0, 0xcb, 0xd3, 0x10, 0x00, 0x03, 0x4f, 0xee, 0x39, 0x75, + 0xfe, 0x6e, 0x2e, 0x3f, 0xaf, 0x2e, 0xdc, 0xcd, 0xe5, 0xf3, 0xea, 0xe2, 0xdd, 0x5c, 0x7e, 0x51, + 0x05, 0x53, 0x7a, 0x33, 0x0b, 0xde, 0xc4, 0xa4, 0x67, 0xac, 0xe8, 0x13, 0x95, 0xf6, 0xcb, 0x0c, + 0x90, 0x64, 0x33, 0xc8, 0xa7, 0xa0, 0xc0, 0x18, 0xac, 0x3d, 0xf2, 0xbf, 0xce, 0x1d, 0x41, 0x58, + 0x14, 0x2d, 0x09, 0x2c, 0x47, 0xd1, 0x62, 0x60, 0xd3, 0xff, 0x7a, 0x8f, 0x7c, 0x19, 0xce, 0xe2, + 0xf0, 0x0e, 0xdd, 0x91, 0x37, 0xe8, 0xda, 0x18, 0xf2, 0xd8, 0xe9, 0xf1, 0x4c, 0x9f, 0x2f, 0x60, + 0x4a, 0xea, 0x64, 0xf1, 0x94, 0x69, 0x40, 0xe7, 0xcc, 0x26, 0x62, 0x36, 0x19, 0x22, 0x69, 0x81, + 0x2a, 0xd7, 0x3f, 0x98, 0xf4, 0x7a, 0x7c, 0x66, 0x8b, 0xf4, 0x46, 0x1f, 0x2f, 0x9b, 0x42, 0x78, + 0x25, 0x24, 0xbc, 0x3b, 0xe9, 0xf5, 0xc8, 0xab, 0x00, 0x83, 0xbe, 0x7d, 0xec, 0xf9, 0x3e, 0x7b, + 0xcc, 0x09, 0xfc, 0xc8, 0x43, 0xa8, 0x3c, 0x19, 0x83, 0x7e, 0x8d, 0x01, 0xc9, 0xff, 0x02, 0x0c, + 0xbe, 0x81, 0x51, 0x69, 0x98, 0x35, 0x12, 0x4f, 0xc6, 0x23, 0x80, 0x51, 0xb7, 0xf5, 0x43, 0xd7, + 0xf2, 0xbe, 0x29, 0x9c, 0x70, 0xbe, 0x08, 0x6b, 0xdc, 0x5e, 0xfa, 0xbe, 0x37, 0x3e, 0xe2, 0x57, + 0x89, 0x0f, 0x72, 0x0f, 0x91, 0xee, 0x12, 0x7f, 0x9d, 0x03, 0xd0, 0xef, 0x5b, 0x22, 0xe0, 0xdb, + 0x2d, 0x98, 0xa3, 0x17, 0x24, 0xa1, 0x68, 0x41, 0x35, 0x35, 0xd2, 0x95, 0xd5, 0xd4, 0x88, 0x41, + 0x77, 0xa3, 0x89, 0xee, 0x0e, 0x42, 0xc9, 0x82, 0xbb, 0x91, 0x79, 0x40, 0x44, 0x02, 0x6e, 0x73, + 0x2c, 0x52, 0x05, 0x08, 0x43, 0xb0, 0x71, 0x91, 0x7f, 0x2d, 0x8c, 0x65, 0xc4, 0x0b, 0x78, 0xd2, + 0x8f, 0x30, 0x8c, 0x9b, 0xbc, 0x7c, 0x42, 0x34, 0x72, 0x0f, 0x72, 0x2d, 0x27, 0xf0, 0x92, 0x9e, + 0x12, 0x98, 0xee, 0x59, 0x9e, 0x89, 0x35, 0x0c, 0x4e, 0xb7, 0x32, 0x76, 0x22, 0x09, 0xab, 0x91, + 0x08, 0x31, 0x60, 0x9e, 0x67, 0xd9, 0x9f, 0x12, 0xd0, 0x94, 0x27, 0xd9, 0xe7, 0x61, 0xcc, 0x11, + 0x28, 0xcb, 0x14, 0x3c, 0x9f, 0xfe, 0x1d, 0xc8, 0x5a, 0x56, 0x8d, 0x87, 0x63, 0x59, 0x0e, 0xaf, + 0x5f, 0x96, 0x55, 0x63, 0xef, 0xbe, 0xbe, 0x7f, 0x2c, 0x55, 0xa3, 0xc8, 0xe4, 0xd3, 0x50, 0x90, + 0x84, 0x62, 0x1e, 0xc8, 0x08, 0xc7, 0x40, 0xf2, 0x43, 0x93, 0x99, 0x86, 0x84, 0x4d, 0xaa, 0xa0, + 0xde, 0x9b, 0x3c, 0x72, 0xf5, 0xe1, 0x10, 0x1d, 0x54, 0xdf, 0x76, 0x47, 0x4c, 0x6c, 0xcb, 0x87, + 0x11, 0xc0, 0xd1, 0x7b, 0xa5, 0x2b, 0x4a, 0x65, 0x65, 0x53, 0xbc, 0x26, 0x69, 0xc2, 0x9a, 0xe5, + 0x8e, 0x27, 0x43, 0x66, 0x5f, 0xb3, 0x3b, 0x18, 0xd1, 0xfb, 0x0d, 0x0b, 0x7b, 0x84, 0xc1, 0x92, + 0x7d, 0x5a, 0x28, 0x8c, 0x9a, 0x0e, 0x06, 0xa3, 0xd8, 0x5d, 0x27, 0x59, 0x59, 0x73, 0xe5, 0x29, + 0xa7, 0xa7, 0x6a, 0xf4, 0xd6, 0x84, 0xa7, 0xaa, 0xb8, 0x35, 0x85, 0x77, 0xa5, 0x4f, 0xa4, 0x84, + 0xe6, 0xc3, 0x97, 0x41, 0x29, 0x34, 0x5f, 0x24, 0x20, 0xdf, 0xbb, 0x39, 0x29, 0x3a, 0x2c, 0x9f, + 0x8b, 0x37, 0x00, 0xee, 0x0e, 0xbc, 0x7e, 0xcd, 0x1d, 0x1f, 0x0d, 0xba, 0x52, 0x84, 0xc0, 0xc2, + 0xd7, 0x06, 0x5e, 0xdf, 0x3e, 0x46, 0xf0, 0x2f, 0xdf, 0xbb, 0x22, 0x21, 0x99, 0xd2, 0xff, 0xe4, + 0xe3, 0xb0, 0x48, 0x7f, 0xb5, 0x42, 0x2b, 0x21, 0xa6, 0x93, 0xc5, 0xda, 0x2c, 0x87, 0x4a, 0x88, + 0x40, 0x5e, 0xc7, 0xac, 0x41, 0xde, 0x70, 0x2c, 0x09, 0xaf, 0x22, 0x45, 0x90, 0x37, 0x1c, 0xc7, + 0x03, 0x7e, 0x4b, 0xc8, 0xa4, 0x1c, 0x34, 0x5d, 0x24, 0xfa, 0xe2, 0xc9, 0x89, 0x50, 0xf1, 0xc8, + 0xd7, 0x9a, 0x2d, 0x22, 0x0d, 0xcb, 0x19, 0x9c, 0x63, 0xd5, 0xb0, 0x11, 0x56, 0x79, 0x87, 0xbd, + 0x14, 0x71, 0xa1, 0x96, 0x35, 0xc2, 0x3f, 0xea, 0xda, 0x1d, 0x04, 0x47, 0x1a, 0x11, 0x20, 0x93, + 0x6d, 0x58, 0x65, 0x32, 0x7e, 0x90, 0x30, 0x94, 0x8b, 0xb8, 0xc8, 0xdb, 0xc2, 0x8c, 0xa2, 0xf2, + 0xe7, 0x63, 0x15, 0xc8, 0x2e, 0xcc, 0xe1, 0x5d, 0x93, 0x7b, 0x43, 0x5c, 0x90, 0xd5, 0x04, 0xf1, + 0x7d, 0x84, 0x7c, 0x05, 0x15, 0x04, 0x32, 0x5f, 0x41, 0x54, 0xf2, 0x05, 0x00, 0xa3, 0x3f, 0x1a, + 0xf4, 0x7a, 0x18, 0x0b, 0x3b, 0x8f, 0x57, 0xa9, 0x4b, 0xd1, 0xfd, 0x88, 0x54, 0x42, 0x24, 0x1e, + 0xb7, 0x11, 0x7f, 0xdb, 0xb1, 0x88, 0xd9, 0x12, 0x2d, 0xad, 0x02, 0xf3, 0x6c, 0x33, 0x62, 0x5c, + 0x79, 0x9e, 0x29, 0x47, 0x8a, 0x4a, 0xce, 0xe2, 0xca, 0x73, 0x78, 0x32, 0xae, 0xbc, 0x54, 0x41, + 0xbb, 0x07, 0xeb, 0x69, 0x1d, 0x8b, 0xdc, 0x8e, 0x95, 0xd3, 0xde, 0x8e, 0x7f, 0x98, 0x85, 0x25, + 0xa4, 0x26, 0xb8, 0xb0, 0x0e, 0xcb, 0xd6, 0xe4, 0x51, 0x10, 0x74, 0x4d, 0x70, 0x63, 0x6c, 0x9f, + 0x2f, 0x17, 0xc8, 0x6f, 0x78, 0x91, 0x1a, 0xc4, 0x80, 0x15, 0x71, 0x12, 0xec, 0x09, 0xcf, 0x81, + 0x20, 0xa4, 0xbb, 0x70, 0xa8, 0x48, 0x26, 0x4c, 0x8e, 0x55, 0x0a, 0xcf, 0x83, 0xec, 0xd3, 0x9c, + 0x07, 0xb9, 0x53, 0x9d, 0x07, 0x0f, 0x61, 0x49, 0x7c, 0x0d, 0x39, 0xf9, 0xdc, 0x07, 0xe3, 0xe4, + 0x11, 0x62, 0xa4, 0x1a, 0x70, 0xf4, 0xf9, 0x99, 0x1c, 0x1d, 0x1f, 0x46, 0xc5, 0x2e, 0x1b, 0x22, + 0x2c, 0xc9, 0xd8, 0x31, 0xa3, 0xe8, 0x5e, 0xa9, 0xf9, 0x2b, 0x9c, 0x92, 0xaf, 0xc0, 0x62, 0x75, + 0x20, 0xde, 0xc4, 0xa4, 0xc7, 0x88, 0x9e, 0x00, 0xca, 0xe2, 0x42, 0x80, 0x19, 0x9c, 0x6e, 0xd9, + 0x0f, 0xe3, 0x74, 0x7b, 0x1d, 0x80, 0xbb, 0xa4, 0x84, 0x99, 0x00, 0x71, 0xcb, 0x88, 0xd8, 0x31, + 0xd1, 0x37, 0x11, 0x09, 0x99, 0x72, 0x27, 0x6e, 0x6e, 0xa3, 0x77, 0x3a, 0x83, 0x49, 0x7f, 0x1c, + 0x49, 0x9d, 0x2d, 0x7c, 0x8b, 0x1d, 0x5e, 0x26, 0xb3, 0x87, 0x58, 0xb5, 0x0f, 0x77, 0x42, 0xc8, + 0xe7, 0x03, 0xe3, 0xc7, 0x85, 0x59, 0x23, 0xa4, 0x25, 0x46, 0x68, 0xaa, 0xc9, 0xa3, 0xf6, 0x53, + 0x45, 0xce, 0xa7, 0xf1, 0x2b, 0x4c, 0xf5, 0x6b, 0x00, 0x81, 0x51, 0x82, 0x98, 0x6b, 0x76, 0x5f, + 0x0a, 0xa0, 0xf2, 0x28, 0x87, 0xb8, 0x52, 0x6f, 0xb2, 0x1f, 0x56, 0x6f, 0x5a, 0x50, 0x68, 0xbc, + 0x35, 0x76, 0x42, 0x2b, 0x16, 0xb0, 0x02, 0x49, 0x16, 0x39, 0x53, 0x76, 0xfb, 0x3a, 0x9e, 0x0d, + 0xa1, 0x1c, 0x3c, 0x45, 0x04, 0x96, 0x2a, 0x6a, 0xff, 0xa1, 0xc0, 0xaa, 0x1c, 0x10, 0xe1, 0xa4, + 0xdf, 0x21, 0x9f, 0x65, 0xe1, 0x7d, 0x95, 0xc8, 0x95, 0x45, 0x42, 0xa2, 0x2c, 0xf7, 0xa4, 0xdf, + 0x61, 0x02, 0x90, 0xf3, 0x58, 0x6e, 0x2c, 0xad, 0x48, 0x1e, 0xc1, 0x52, 0x73, 0xd0, 0xeb, 0x51, + 0xb1, 0x66, 0xf4, 0x36, 0xbf, 0x00, 0x50, 0x42, 0xf1, 0xa7, 0x11, 0xd1, 0xa0, 0xed, 0xe7, 0xf8, + 0x3d, 0x77, 0x63, 0x48, 0xf9, 0xbd, 0xc7, 0xeb, 0x85, 0x64, 0xdf, 0x41, 0xd7, 0x40, 0x99, 0x66, + 0x78, 0x36, 0x45, 0xf3, 0x42, 0xc8, 0xad, 0xa4, 0xc5, 0xd8, 0xce, 0x19, 0x67, 0x93, 0xf6, 0x73, + 0x05, 0x48, 0xb2, 0x6b, 0x32, 0xeb, 0x53, 0xfe, 0x1b, 0x44, 0xe1, 0x98, 0x08, 0x99, 0x7b, 0x1a, + 0x11, 0x52, 0xfb, 0x81, 0x02, 0xeb, 0x69, 0xe3, 0x40, 0x4f, 0x10, 0xf9, 0x48, 0x09, 0x0e, 0x34, + 0x3c, 0x41, 0xe4, 0x53, 0x28, 0x7a, 0xac, 0xc5, 0x2a, 0xc5, 0x1b, 0x97, 0x79, 0x9a, 0xc6, 0x15, + 0x7f, 0x57, 0x81, 0xd5, 0x8a, 0x5e, 0xe3, 0x69, 0x44, 0xd8, 0x33, 0xd5, 0x55, 0xb8, 0x54, 0xd1, + 0x6b, 0x76, 0xb3, 0x51, 0xad, 0x94, 0x1e, 0xd8, 0xa9, 0xd1, 0xc1, 0x2f, 0xc1, 0x33, 0x49, 0x94, + 0xf0, 0x39, 0xeb, 0x22, 0x6c, 0x26, 0x8b, 0x45, 0x04, 0xf1, 0xf4, 0xca, 0x22, 0xd8, 0x78, 0xb6, + 0xf8, 0x26, 0xac, 0x8a, 0x68, 0xd9, 0xad, 0xaa, 0x85, 0xf9, 0x38, 0x56, 0xa1, 0xb0, 0x6f, 0x98, + 0x95, 0xdd, 0x07, 0xf6, 0x6e, 0xbb, 0x5a, 0x55, 0xcf, 0x90, 0x65, 0x58, 0xe4, 0x80, 0x92, 0xae, + 0x2a, 0x64, 0x09, 0xf2, 0x95, 0xba, 0x65, 0x94, 0xda, 0xa6, 0xa1, 0x66, 0x8a, 0x6f, 0xc2, 0x4a, + 0x73, 0xe4, 0xbd, 0xed, 0x8c, 0xdd, 0x7b, 0xee, 0x09, 0xbe, 0x46, 0x2d, 0x40, 0xd6, 0xd4, 0xef, + 0xab, 0x67, 0x08, 0xc0, 0x7c, 0xf3, 0x5e, 0xc9, 0xba, 0x7d, 0x5b, 0x55, 0x48, 0x01, 0x16, 0xf6, + 0x4a, 0x4d, 0xfb, 0x5e, 0xcd, 0x52, 0x33, 0xf4, 0x87, 0x7e, 0xdf, 0xc2, 0x1f, 0xd9, 0xe2, 0x4b, + 0xb0, 0x86, 0x52, 0x57, 0xd5, 0xf3, 0xc7, 0x6e, 0xdf, 0x1d, 0x61, 0x1b, 0x96, 0x20, 0x6f, 0xb9, + 0x94, 0x5d, 0x8e, 0x5d, 0xd6, 0x80, 0xda, 0xa4, 0x37, 0xf6, 0x86, 0x3d, 0xf7, 0x1b, 0xaa, 0x52, + 0x7c, 0x1d, 0x56, 0xcd, 0xc1, 0x64, 0xec, 0xf5, 0x0f, 0xad, 0x31, 0xc5, 0x38, 0x3c, 0x21, 0xe7, + 0x60, 0xad, 0x5d, 0xd7, 0x6b, 0xdb, 0x95, 0xbd, 0x76, 0xa3, 0x6d, 0xd9, 0x35, 0xbd, 0x55, 0x2a, + 0xb3, 0xb7, 0xb0, 0x5a, 0xc3, 0x6a, 0xd9, 0xa6, 0x51, 0x32, 0xea, 0x2d, 0x55, 0x29, 0x7e, 0x0f, + 0x15, 0x48, 0x9d, 0x41, 0xbf, 0xbb, 0xeb, 0x74, 0xc6, 0x83, 0x11, 0x36, 0x58, 0x83, 0xcb, 0x96, + 0x51, 0x6a, 0xd4, 0x77, 0xec, 0x5d, 0xbd, 0xd4, 0x6a, 0x98, 0x69, 0xe1, 0xe9, 0xb7, 0xe0, 0x7c, + 0x0a, 0x4e, 0xa3, 0xd5, 0x54, 0x15, 0x72, 0x05, 0x2e, 0xa4, 0x94, 0xdd, 0x37, 0xb6, 0xf5, 0x76, + 0xab, 0x5c, 0x57, 0x33, 0x53, 0x2a, 0x5b, 0x56, 0x43, 0xcd, 0x16, 0xff, 0xbf, 0x02, 0x2b, 0x6d, + 0x9f, 0xdb, 0xd5, 0xb7, 0xd1, 0x8b, 0xf8, 0x59, 0xb8, 0xd8, 0xb6, 0x0c, 0xd3, 0x6e, 0x35, 0xee, + 0x19, 0x75, 0xbb, 0x6d, 0xe9, 0x7b, 0xf1, 0xd6, 0x5c, 0x81, 0x0b, 0x12, 0x86, 0x69, 0x94, 0x1a, + 0xfb, 0x86, 0x69, 0x37, 0x75, 0xcb, 0xba, 0xdf, 0x30, 0x77, 0x54, 0x85, 0x7e, 0x31, 0x05, 0xa1, + 0xb6, 0xab, 0xb3, 0xd6, 0x44, 0xca, 0xea, 0xc6, 0x7d, 0xbd, 0x6a, 0x6f, 0x37, 0x5a, 0x6a, 0xb6, + 0x58, 0xa3, 0x42, 0x0c, 0x06, 0x89, 0x66, 0xe6, 0x93, 0x79, 0xc8, 0xd5, 0x1b, 0x75, 0x23, 0xfe, + 0x82, 0xba, 0x04, 0x79, 0xbd, 0xd9, 0x34, 0x1b, 0xfb, 0xb8, 0xc4, 0x00, 0xe6, 0x77, 0x8c, 0x3a, + 0x6d, 0x59, 0x96, 0x96, 0x34, 0xcd, 0x46, 0xad, 0xd1, 0x32, 0x76, 0xd4, 0x5c, 0xd1, 0x14, 0xfc, + 0x45, 0x10, 0xed, 0x0c, 0xd8, 0x73, 0xe5, 0x8e, 0xb1, 0xab, 0xb7, 0xab, 0x2d, 0x3e, 0x45, 0x0f, + 0x6c, 0xd3, 0xf8, 0x7c, 0xdb, 0xb0, 0x5a, 0x96, 0xaa, 0x10, 0x15, 0x96, 0xea, 0x86, 0xb1, 0x63, + 0xd9, 0xa6, 0xb1, 0x5f, 0x31, 0xee, 0xab, 0x19, 0x4a, 0x93, 0xfd, 0x4f, 0xbf, 0x50, 0x7c, 0x57, + 0x01, 0xc2, 0x02, 0x6c, 0x8b, 0xac, 0x4d, 0xb8, 0x62, 0x2e, 0xc3, 0x56, 0x99, 0x4e, 0x35, 0x76, + 0xad, 0xd6, 0xd8, 0x89, 0x0f, 0xd9, 0x79, 0x20, 0xb1, 0xf2, 0xc6, 0xee, 0xae, 0xaa, 0x90, 0x0b, + 0x70, 0x36, 0x06, 0xdf, 0x31, 0x1b, 0x4d, 0x35, 0xb3, 0x95, 0xc9, 0x2b, 0x64, 0x23, 0x51, 0x78, + 0xcf, 0x30, 0x9a, 0x6a, 0x96, 0x4e, 0x51, 0xac, 0x40, 0x6c, 0x09, 0x56, 0x3d, 0x57, 0xfc, 0x8e, + 0x02, 0xe7, 0x59, 0x33, 0xc5, 0xfe, 0x0a, 0x9a, 0x7a, 0x11, 0x36, 0x79, 0xda, 0x80, 0xb4, 0x86, + 0xae, 0x83, 0x1a, 0x29, 0x65, 0xcd, 0x3c, 0x07, 0x6b, 0x11, 0x28, 0xb6, 0x23, 0x43, 0xb9, 0x47, + 0x04, 0xbc, 0x6d, 0x58, 0x2d, 0xdb, 0xd8, 0xdd, 0x6d, 0x98, 0x2d, 0xd6, 0x90, 0x6c, 0x51, 0x83, + 0xb5, 0x92, 0x3b, 0x1a, 0xd3, 0xfb, 0x65, 0xdf, 0xf7, 0x06, 0x7d, 0x6c, 0xc2, 0x32, 0x2c, 0x1a, + 0x5f, 0x68, 0x19, 0x75, 0xab, 0xd2, 0xa8, 0xab, 0x67, 0x8a, 0x17, 0x63, 0x38, 0x62, 0x1f, 0x5b, + 0x56, 0x59, 0x3d, 0x53, 0x74, 0x60, 0x59, 0x58, 0x97, 0xb3, 0x55, 0x71, 0x19, 0xb6, 0xc4, 0x5a, + 0x43, 0x8e, 0x12, 0xef, 0xc2, 0x26, 0xac, 0x27, 0xcb, 0x8d, 0x96, 0xaa, 0xd0, 0x59, 0x88, 0x95, + 0x50, 0x78, 0xa6, 0xf8, 0x7f, 0x15, 0x58, 0x0e, 0x5e, 0x86, 0x50, 0x17, 0x7d, 0x05, 0x2e, 0xd4, + 0x76, 0x75, 0x7b, 0xc7, 0xd8, 0xaf, 0x94, 0x0c, 0xfb, 0x5e, 0xa5, 0xbe, 0x13, 0xfb, 0xc8, 0x33, + 0x70, 0x2e, 0x05, 0x01, 0xbf, 0xb2, 0x09, 0xeb, 0xf1, 0xa2, 0x16, 0xdd, 0xaa, 0x19, 0x3a, 0xf4, + 0xf1, 0x92, 0x60, 0x9f, 0x66, 0x8b, 0xfb, 0xb0, 0x62, 0xe9, 0xb5, 0xea, 0xee, 0x60, 0xd4, 0x71, + 0xf5, 0xc9, 0xf8, 0xa8, 0x4f, 0x2e, 0xc0, 0xc6, 0x6e, 0xc3, 0x2c, 0x19, 0x36, 0xa2, 0xc4, 0x5a, + 0x70, 0x16, 0x56, 0xe5, 0xc2, 0x07, 0x06, 0x5d, 0xbe, 0x04, 0x56, 0x64, 0x60, 0xbd, 0xa1, 0x66, + 0x8a, 0x5f, 0x82, 0xa5, 0x48, 0xf2, 0xc6, 0x0d, 0x38, 0x2b, 0xff, 0x6e, 0xba, 0xfd, 0xae, 0xd7, + 0x3f, 0x54, 0xcf, 0xc4, 0x0b, 0xcc, 0x49, 0xbf, 0x4f, 0x0b, 0x70, 0x3f, 0xcb, 0x05, 0x2d, 0x77, + 0x74, 0xec, 0xf5, 0x9d, 0xb1, 0xdb, 0x55, 0x33, 0xc5, 0x17, 0x61, 0x39, 0x12, 0x32, 0x9e, 0x4e, + 0x5c, 0xb5, 0xc1, 0x19, 0x70, 0xcd, 0xd8, 0xa9, 0xb4, 0x6b, 0xea, 0x1c, 0xdd, 0xc9, 0xe5, 0xca, + 0x5e, 0x59, 0x85, 0xe2, 0xf7, 0x15, 0x7a, 0x99, 0xc2, 0x44, 0x50, 0xb5, 0x5d, 0x5d, 0x4c, 0x35, + 0x5d, 0x66, 0x2c, 0x11, 0x85, 0x61, 0x59, 0xcc, 0x70, 0xe0, 0x22, 0x6c, 0xf2, 0x1f, 0xb6, 0x5e, + 0xdf, 0xb1, 0xcb, 0xba, 0xb9, 0x73, 0x5f, 0x37, 0xe9, 0xda, 0x7b, 0xa0, 0x66, 0x70, 0x43, 0x49, + 0x10, 0xbb, 0xd5, 0x68, 0x97, 0xca, 0x6a, 0x96, 0xae, 0xdf, 0x08, 0xbc, 0x59, 0xa9, 0xab, 0x39, + 0xdc, 0x9e, 0x09, 0x6c, 0x24, 0x4b, 0xcb, 0xe7, 0x8a, 0xef, 0x2b, 0xb0, 0x61, 0x79, 0x87, 0x7d, + 0x67, 0x3c, 0x19, 0xb9, 0x7a, 0xef, 0x70, 0x30, 0xf2, 0xc6, 0x47, 0xc7, 0xd6, 0xc4, 0x1b, 0xbb, + 0xe4, 0x16, 0x5c, 0xb7, 0x2a, 0x7b, 0x75, 0xbd, 0x45, 0xb7, 0x97, 0x5e, 0xdd, 0x6b, 0x98, 0x95, + 0x56, 0xb9, 0x66, 0x5b, 0xed, 0x4a, 0x62, 0xe5, 0x5d, 0x83, 0x67, 0xa7, 0xa3, 0x56, 0x8d, 0x3d, + 0xbd, 0xf4, 0x40, 0x55, 0x66, 0x13, 0xdc, 0xd6, 0xab, 0x7a, 0xbd, 0x64, 0xec, 0xd8, 0xfb, 0xb7, + 0xd5, 0x0c, 0xb9, 0x0e, 0x57, 0xa7, 0xa3, 0xee, 0x56, 0x9a, 0x16, 0x45, 0xcb, 0xce, 0xfe, 0x6e, + 0xd9, 0xaa, 0x51, 0xac, 0x5c, 0xf1, 0x07, 0x0a, 0x6c, 0x4e, 0x0b, 0x01, 0x46, 0x6e, 0x80, 0x66, + 0xd4, 0x5b, 0xa6, 0x5e, 0xd9, 0xb1, 0x4b, 0xa6, 0xb1, 0x63, 0xd4, 0x5b, 0x15, 0xbd, 0x6a, 0xd9, + 0x56, 0xa3, 0x4d, 0x57, 0x53, 0x68, 0xdf, 0xf1, 0x1c, 0x5c, 0x99, 0x81, 0xd7, 0xa8, 0xec, 0x94, + 0x54, 0x85, 0xdc, 0x86, 0x17, 0x66, 0x20, 0x59, 0x0f, 0xac, 0x96, 0x51, 0x93, 0x4b, 0xd4, 0x4c, + 0xb1, 0x04, 0x5b, 0xd3, 0x63, 0x04, 0x51, 0x36, 0x1d, 0x1d, 0xe9, 0x3c, 0xe4, 0x76, 0xe8, 0xc9, + 0x10, 0xc9, 0x57, 0x52, 0xf4, 0x40, 0x8d, 0xc7, 0xcf, 0x48, 0x18, 0xe2, 0x98, 0xed, 0x7a, 0x9d, + 0x1d, 0x23, 0xab, 0x50, 0x68, 0xb4, 0xca, 0x86, 0xc9, 0x33, 0xbe, 0x60, 0x8a, 0x97, 0x76, 0x9d, + 0x6e, 0x9c, 0x86, 0x59, 0xf9, 0x22, 0x9e, 0x27, 0x9b, 0xb0, 0x6e, 0x55, 0xf5, 0xd2, 0x3d, 0xbb, + 0xde, 0x68, 0xd9, 0x95, 0xba, 0x5d, 0x2a, 0xeb, 0xf5, 0xba, 0x51, 0x55, 0x01, 0x07, 0x73, 0x9a, + 0x03, 0x29, 0xf9, 0x38, 0xdc, 0x6c, 0xdc, 0x6b, 0xe9, 0x76, 0xb3, 0xda, 0xde, 0xab, 0xd4, 0x6d, + 0xeb, 0x41, 0xbd, 0x24, 0x64, 0x9f, 0x52, 0x92, 0xe5, 0xde, 0x84, 0x6b, 0x33, 0xb1, 0xc3, 0xdc, + 0x2c, 0x37, 0x40, 0x9b, 0x89, 0xc9, 0x3b, 0x52, 0xfc, 0x99, 0x02, 0x17, 0x66, 0x3c, 0x94, 0x93, + 0x17, 0xe0, 0x56, 0xd9, 0xd0, 0x77, 0xaa, 0x86, 0x65, 0x21, 0xa3, 0xa0, 0xd3, 0xc0, 0x0c, 0x76, + 0x52, 0x19, 0xea, 0x2d, 0xb8, 0x3e, 0x1b, 0x3d, 0x3c, 0x9a, 0x6f, 0xc2, 0xb5, 0xd9, 0xa8, 0xfc, + 0xa8, 0xce, 0x90, 0x22, 0xdc, 0x98, 0x8d, 0x19, 0x1c, 0xf1, 0xd9, 0xe2, 0x6f, 0x2b, 0x70, 0x3e, + 0x5d, 0x5b, 0x45, 0xdb, 0x56, 0xa9, 0x5b, 0x2d, 0xbd, 0x5a, 0xb5, 0x9b, 0xba, 0xa9, 0xd7, 0x6c, + 0xa3, 0x6e, 0x36, 0xaa, 0xd5, 0xb4, 0xa3, 0xed, 0x1a, 0x3c, 0x3b, 0x1d, 0xd5, 0x2a, 0x99, 0x95, + 0x26, 0xe5, 0xde, 0x1a, 0x5c, 0x9e, 0x8e, 0x65, 0x54, 0x4a, 0x86, 0x9a, 0xd9, 0x7e, 0xe3, 0x27, + 0x7f, 0x77, 0xf9, 0xcc, 0x4f, 0xde, 0xbf, 0xac, 0xfc, 0xfc, 0xfd, 0xcb, 0xca, 0xdf, 0xbe, 0x7f, + 0x59, 0xf9, 0xe2, 0xf3, 0xa7, 0x4b, 0x6b, 0x86, 0x97, 0x92, 0x47, 0xf3, 0x78, 0x0d, 0x7b, 0xf9, + 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xf0, 0xe6, 0xfe, 0x0d, 0xe2, 0xc1, 0x01, 0x00, } func (this *PluginSpecV1) Equal(that interface{}) bool { @@ -24252,6 +24432,30 @@ func (this *PluginSpecV1_Msteams) Equal(that interface{}) bool { } return true } +func (this *PluginSpecV1_NetIq) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*PluginSpecV1_NetIq) + if !ok { + that2, ok := that.(PluginSpecV1_NetIq) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if !this.NetIq.Equal(that1.NetIq) { + return false + } + return true +} func (this *PluginSlackAccessSettings) Equal(that interface{}) bool { if that == nil { return this == nil @@ -25199,6 +25403,39 @@ func (this *PluginMSTeamsSettings) Equal(that interface{}) bool { } return true } +func (this *PluginNetIQSettings) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*PluginNetIQSettings) + if !ok { + that2, ok := that.(PluginNetIQSettings) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.OauthIssuerEndpoint != that1.OauthIssuerEndpoint { + return false + } + if this.ApiEndpoint != that1.ApiEndpoint { + return false + } + if this.InsecureSkipVerify != that1.InsecureSkipVerify { + return false + } + if !bytes.Equal(this.XXX_unrecognized, that1.XXX_unrecognized) { + return false + } + return true +} func (this *PluginStaticCredentialsRef) Equal(that interface{}) bool { if that == nil { return this == nil @@ -45048,6 +45285,29 @@ func (m *PluginSpecV1_Msteams) MarshalToSizedBuffer(dAtA []byte) (int, error) { } return len(dAtA) - i, nil } +func (m *PluginSpecV1_NetIq) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PluginSpecV1_NetIq) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.NetIq != nil { + { + size, err := m.NetIq.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x9a + } + return len(dAtA) - i, nil +} func (m *PluginSlackAccessSettings) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -46472,6 +46732,57 @@ func (m *PluginMSTeamsSettings) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *PluginNetIQSettings) 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 *PluginNetIQSettings) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PluginNetIQSettings) 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.InsecureSkipVerify { + i-- + if m.InsecureSkipVerify { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x18 + } + if len(m.ApiEndpoint) > 0 { + i -= len(m.ApiEndpoint) + copy(dAtA[i:], m.ApiEndpoint) + i = encodeVarintTypes(dAtA, i, uint64(len(m.ApiEndpoint))) + i-- + dAtA[i] = 0x12 + } + if len(m.OauthIssuerEndpoint) > 0 { + i -= len(m.OauthIssuerEndpoint) + copy(dAtA[i:], m.OauthIssuerEndpoint) + i = encodeVarintTypes(dAtA, i, uint64(len(m.OauthIssuerEndpoint))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *PluginBootstrapCredentialsV1) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -46693,12 +47004,12 @@ func (m *PluginStatusV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x32 } - n363, err363 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastSyncTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastSyncTime):]) - if err363 != nil { - return 0, err363 + n364, err364 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastSyncTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastSyncTime):]) + if err364 != nil { + return 0, err364 } - i -= n363 - i = encodeVarintTypes(dAtA, i, uint64(n363)) + i -= n364 + i = encodeVarintTypes(dAtA, i, uint64(n364)) i-- dAtA[i] = 0x1a if len(m.ErrorMessage) > 0 { @@ -46800,6 +47111,74 @@ func (m *PluginStatusV1_AwsIc) MarshalToSizedBuffer(dAtA []byte) (int, error) { } return len(dAtA) - i, nil } +func (m *PluginStatusV1_NetIq) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PluginStatusV1_NetIq) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.NetIq != nil { + { + size, err := m.NetIq.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + } + return len(dAtA) - i, nil +} +func (m *PluginNetIQStatusV1) 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 *PluginNetIQStatusV1) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PluginNetIQStatusV1) 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.ImportedResources != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.ImportedResources)) + i-- + dAtA[i] = 0x20 + } + if m.ImportedRoles != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.ImportedRoles)) + i-- + dAtA[i] = 0x18 + } + if m.ImportedGroups != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.ImportedGroups)) + i-- + dAtA[i] = 0x10 + } + if m.ImportedUsers != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.ImportedUsers)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *PluginGitlabStatusV1) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -47059,22 +47438,22 @@ func (m *PluginOktaStatusDetailsAppGroupSync) MarshalToSizedBuffer(dAtA []byte) dAtA[i] = 0x28 } if m.LastFailed != nil { - n373, err373 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastFailed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastFailed):]) - if err373 != nil { - return 0, err373 + n375, err375 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastFailed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastFailed):]) + if err375 != nil { + return 0, err375 } - i -= n373 - i = encodeVarintTypes(dAtA, i, uint64(n373)) + i -= n375 + i = encodeVarintTypes(dAtA, i, uint64(n375)) i-- dAtA[i] = 0x22 } if m.LastSuccessful != nil { - n374, err374 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastSuccessful, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastSuccessful):]) - if err374 != nil { - return 0, err374 + n376, err376 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastSuccessful, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastSuccessful):]) + if err376 != nil { + return 0, err376 } - i -= n374 - i = encodeVarintTypes(dAtA, i, uint64(n374)) + i -= n376 + i = encodeVarintTypes(dAtA, i, uint64(n376)) i-- dAtA[i] = 0x1a } @@ -47133,22 +47512,22 @@ func (m *PluginOktaStatusDetailsUsersSync) MarshalToSizedBuffer(dAtA []byte) (in dAtA[i] = 0x28 } if m.LastFailed != nil { - n375, err375 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastFailed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastFailed):]) - if err375 != nil { - return 0, err375 + n377, err377 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastFailed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastFailed):]) + if err377 != nil { + return 0, err377 } - i -= n375 - i = encodeVarintTypes(dAtA, i, uint64(n375)) + i -= n377 + i = encodeVarintTypes(dAtA, i, uint64(n377)) i-- dAtA[i] = 0x22 } if m.LastSuccessful != nil { - n376, err376 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastSuccessful, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastSuccessful):]) - if err376 != nil { - return 0, err376 + n378, err378 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastSuccessful, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastSuccessful):]) + if err378 != nil { + return 0, err378 } - i -= n376 - i = encodeVarintTypes(dAtA, i, uint64(n376)) + i -= n378 + i = encodeVarintTypes(dAtA, i, uint64(n378)) i-- dAtA[i] = 0x1a } @@ -47267,22 +47646,22 @@ func (m *PluginOktaStatusDetailsAccessListsSync) MarshalToSizedBuffer(dAtA []byt } } if m.LastFailed != nil { - n377, err377 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastFailed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastFailed):]) - if err377 != nil { - return 0, err377 + n379, err379 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastFailed, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastFailed):]) + if err379 != nil { + return 0, err379 } - i -= n377 - i = encodeVarintTypes(dAtA, i, uint64(n377)) + i -= n379 + i = encodeVarintTypes(dAtA, i, uint64(n379)) i-- dAtA[i] = 0x22 } if m.LastSuccessful != nil { - n378, err378 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastSuccessful, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastSuccessful):]) - if err378 != nil { - return 0, err378 + n380, err380 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastSuccessful, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastSuccessful):]) + if err380 != nil { + return 0, err380 } - i -= n378 - i = encodeVarintTypes(dAtA, i, uint64(n378)) + i -= n380 + i = encodeVarintTypes(dAtA, i, uint64(n380)) i-- dAtA[i] = 0x1a } @@ -47448,12 +47827,12 @@ func (m *PluginOAuth2AccessTokenCredentials) MarshalToSizedBuffer(dAtA []byte) ( i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n383, err383 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err383 != nil { - return 0, err383 + n385, err385 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + if err385 != nil { + return 0, err385 } - i -= n383 - i = encodeVarintTypes(dAtA, i, uint64(n383)) + i -= n385 + i = encodeVarintTypes(dAtA, i, uint64(n385)) i-- dAtA[i] = 0x1a if len(m.RefreshToken) > 0 { @@ -48395,20 +48774,20 @@ func (m *ScheduledAgentUpgradeWindow) MarshalToSizedBuffer(dAtA []byte) (int, er i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n398, err398 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Stop, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Stop):]) - if err398 != nil { - return 0, err398 + n400, err400 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Stop, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Stop):]) + if err400 != nil { + return 0, err400 } - i -= n398 - i = encodeVarintTypes(dAtA, i, uint64(n398)) + i -= n400 + i = encodeVarintTypes(dAtA, i, uint64(n400)) i-- dAtA[i] = 0x12 - n399, err399 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Start, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Start):]) - if err399 != nil { - return 0, err399 + n401, err401 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Start, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Start):]) + if err401 != nil { + return 0, err401 } - i -= n399 - i = encodeVarintTypes(dAtA, i, uint64(n399)) + i -= n401 + i = encodeVarintTypes(dAtA, i, uint64(n401)) i-- dAtA[i] = 0xa return len(dAtA) - i, nil @@ -48835,12 +49214,12 @@ func (m *OktaAssignmentSpecV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x30 } - n406, err406 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastTransition, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastTransition):]) - if err406 != nil { - return 0, err406 + n408, err408 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastTransition, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastTransition):]) + if err408 != nil { + return 0, err408 } - i -= n406 - i = encodeVarintTypes(dAtA, i, uint64(n406)) + i -= n408 + i = encodeVarintTypes(dAtA, i, uint64(n408)) i-- dAtA[i] = 0x2a if m.Status != 0 { @@ -48848,12 +49227,12 @@ func (m *OktaAssignmentSpecV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x20 } - n407, err407 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CleanupTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CleanupTime):]) - if err407 != nil { - return 0, err407 + n409, err409 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CleanupTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CleanupTime):]) + if err409 != nil { + return 0, err409 } - i -= n407 - i = encodeVarintTypes(dAtA, i, uint64(n407)) + i -= n409 + i = encodeVarintTypes(dAtA, i, uint64(n409)) i-- dAtA[i] = 0x1a if len(m.Targets) > 0 { @@ -50363,12 +50742,26 @@ func (m *AccessGraphSync) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n432, err432 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.PollInterval, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.PollInterval):]) - if err432 != nil { - return 0, err432 + if len(m.Azure) > 0 { + for iNdEx := len(m.Azure) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Azure[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + n434, err434 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.PollInterval, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.PollInterval):]) + if err434 != nil { + return 0, err434 } - i -= n432 - i = encodeVarintTypes(dAtA, i, uint64(n432)) + i -= n434 + i = encodeVarintTypes(dAtA, i, uint64(n434)) i-- dAtA[i] = 0x12 if len(m.AWS) > 0 { @@ -50443,6 +50836,47 @@ func (m *AccessGraphAWSSync) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *AccessGraphAzureSync) 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 *AccessGraphAzureSync) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AccessGraphAzureSync) 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 len(m.Integration) > 0 { + i -= len(m.Integration) + copy(dAtA[i:], m.Integration) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Integration))) + i-- + dAtA[i] = 0x12 + } + if len(m.SubscriptionID) > 0 { + i -= len(m.SubscriptionID) + copy(dAtA[i:], m.SubscriptionID) + i = encodeVarintTypes(dAtA, i, uint64(len(m.SubscriptionID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintTypes(dAtA []byte, offset int, v uint64) int { offset -= sovTypes(v) base := offset @@ -59345,6 +59779,18 @@ func (m *PluginSpecV1_Msteams) Size() (n int) { } return n } +func (m *PluginSpecV1_NetIq) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.NetIq != nil { + l = m.NetIq.Size() + n += 2 + l + sovTypes(uint64(l)) + } + return n +} func (m *PluginSlackAccessSettings) Size() (n int) { if m == nil { return 0 @@ -60025,6 +60471,29 @@ func (m *PluginMSTeamsSettings) Size() (n int) { return n } +func (m *PluginNetIQSettings) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.OauthIssuerEndpoint) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + l = len(m.ApiEndpoint) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + if m.InsecureSkipVerify { + n += 2 + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + func (m *PluginBootstrapCredentialsV1) Size() (n int) { if m == nil { return 0 @@ -60192,6 +60661,42 @@ func (m *PluginStatusV1_AwsIc) Size() (n int) { } return n } +func (m *PluginStatusV1_NetIq) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.NetIq != nil { + l = m.NetIq.Size() + n += 1 + l + sovTypes(uint64(l)) + } + return n +} +func (m *PluginNetIQStatusV1) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ImportedUsers != 0 { + n += 1 + sovTypes(uint64(m.ImportedUsers)) + } + if m.ImportedGroups != 0 { + n += 1 + sovTypes(uint64(m.ImportedGroups)) + } + if m.ImportedRoles != 0 { + n += 1 + sovTypes(uint64(m.ImportedRoles)) + } + if m.ImportedResources != 0 { + n += 1 + sovTypes(uint64(m.ImportedResources)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + func (m *PluginGitlabStatusV1) Size() (n int) { if m == nil { return 0 @@ -61808,6 +62313,12 @@ func (m *AccessGraphSync) Size() (n int) { } l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.PollInterval) n += 1 + l + sovTypes(uint64(l)) + if len(m.Azure) > 0 { + for _, e := range m.Azure { + l = e.Size() + n += 1 + l + sovTypes(uint64(l)) + } + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -61840,6 +62351,26 @@ func (m *AccessGraphAWSSync) Size() (n int) { return n } +func (m *AccessGraphAzureSync) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.SubscriptionID) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + l = len(m.Integration) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + func sovTypes(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -119920,6 +120451,41 @@ func (m *PluginSpecV1) Unmarshal(dAtA []byte) error { } m.Settings = &PluginSpecV1_Msteams{v} iNdEx = postIndex + case 19: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NetIq", 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 + } + v := &PluginNetIQSettings{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Settings = &PluginSpecV1_NetIq{v} + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -123846,6 +124412,141 @@ func (m *PluginMSTeamsSettings) Unmarshal(dAtA []byte) error { } return nil } +func (m *PluginNetIQSettings) 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: PluginNetIQSettings: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PluginNetIQSettings: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OauthIssuerEndpoint", 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.OauthIssuerEndpoint = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ApiEndpoint", 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.ApiEndpoint = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field InsecureSkipVerify", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.InsecureSkipVerify = bool(v != 0) + 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 *PluginBootstrapCredentialsV1) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -124517,6 +125218,168 @@ func (m *PluginStatusV1) Unmarshal(dAtA []byte) error { } m.Details = &PluginStatusV1_AwsIc{v} iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NetIq", 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 + } + v := &PluginNetIQStatusV1{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Details = &PluginStatusV1_NetIq{v} + 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 *PluginNetIQStatusV1) 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: PluginNetIQStatusV1: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PluginNetIQStatusV1: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ImportedUsers", wireType) + } + m.ImportedUsers = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ImportedUsers |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ImportedGroups", wireType) + } + m.ImportedGroups = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ImportedGroups |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ImportedRoles", wireType) + } + m.ImportedRoles = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ImportedRoles |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ImportedResources", wireType) + } + m.ImportedResources = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ImportedResources |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -134181,6 +135044,40 @@ func (m *AccessGraphSync) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Azure", 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 + } + m.Azure = append(m.Azure, &AccessGraphAzureSync{}) + if err := m.Azure[len(m.Azure)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -134354,6 +135251,121 @@ func (m *AccessGraphAWSSync) Unmarshal(dAtA []byte) error { } return nil } +func (m *AccessGraphAzureSync) 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: AccessGraphAzureSync: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AccessGraphAzureSync: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SubscriptionID", 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.SubscriptionID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Integration", 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.Integration = string(dAtA[iNdEx:postIndex]) + 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 skipTypes(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/assets/aws/Makefile b/assets/aws/Makefile index ba2ece47b2a23..3ba53240deaa4 100644 --- a/assets/aws/Makefile +++ b/assets/aws/Makefile @@ -2,7 +2,7 @@ # This must be a _released_ version of Teleport, i.e. one which has binaries # available for download on https://goteleport.com/download # Unreleased versions will fail to build. -TELEPORT_VERSION ?= 17.0.4 +TELEPORT_VERSION ?= 17.1.1 # Teleport UID is the UID of a non-privileged 'teleport' user TELEPORT_UID ?= 1007 diff --git a/assets/backport/go.mod b/assets/backport/go.mod index 1a62feb772b02..d6e5e4badd709 100644 --- a/assets/backport/go.mod +++ b/assets/backport/go.mod @@ -15,6 +15,6 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/crypto v0.31.0 // indirect - golang.org/x/net v0.24.0 // indirect + golang.org/x/net v0.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/assets/backport/go.sum b/assets/backport/go.sum index 7d0946671e57b..ab21f712120e0 100644 --- a/assets/backport/go.sum +++ b/assets/backport/go.sum @@ -995,8 +995,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/buf-legacy.yaml b/buf-legacy.yaml index 5dcbbf314a2a4..f50c9f7b20e5b 100644 --- a/buf-legacy.yaml +++ b/buf-legacy.yaml @@ -35,7 +35,3 @@ lint: - RPC_REQUEST_RESPONSE_UNIQUE - RPC_REQUEST_STANDARD_NAME - RPC_RESPONSE_STANDARD_NAME - -breaking: - use: - - "buf-legacy.yaml should not be used for buf breaking" diff --git a/build.assets/tooling/go.mod b/build.assets/tooling/go.mod index cfc1305c2b386..1f54c7da795e5 100644 --- a/build.assets/tooling/go.mod +++ b/build.assets/tooling/go.mod @@ -54,7 +54,7 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect golang.org/x/crypto v0.31.0 // indirect - golang.org/x/net v0.26.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect diff --git a/build.assets/tooling/go.sum b/build.assets/tooling/go.sum index c4ff39d6d8447..8db0e9b8359de 100644 --- a/build.assets/tooling/go.sum +++ b/build.assets/tooling/go.sum @@ -1114,8 +1114,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/build.assets/versions.mk b/build.assets/versions.mk index 15a01e3006f58..94e9e3a12831e 100644 --- a/build.assets/versions.mk +++ b/build.assets/versions.mk @@ -17,7 +17,7 @@ LIBPCSCLITE_VERSION ?= 1.9.9-teleport DEVTOOLSET ?= devtoolset-12 # Protogen related versions. -BUF_VERSION ?= v1.42.0 +BUF_VERSION ?= v1.48.0 # Keep in sync with api/proto/buf.yaml (and buf.lock). GOGO_PROTO_TAG ?= v1.3.2 NODE_GRPC_TOOLS_VERSION ?= 1.12.4 diff --git a/constants.go b/constants.go index 1c7ecf3226a96..63ba08b791c46 100644 --- a/constants.go +++ b/constants.go @@ -21,6 +21,8 @@ package teleport import ( "strings" "time" + + "github.com/gravitational/trace" ) // WebAPIVersion is a current webapi version @@ -823,9 +825,15 @@ const ( UsageWindowsDesktopOnly = "usage:windows_desktop" ) +// ErrNodeIsAmbiguous serves as an identifying error string indicating that +// the proxy subsystem found multiple nodes matching the specified hostname. +var ErrNodeIsAmbiguous = &trace.NotFoundError{Message: "ambiguous host could match multiple nodes"} + const ( // NodeIsAmbiguous serves as an identifying error string indicating that // the proxy subsystem found multiple nodes matching the specified hostname. + // TODO(tross) DELETE IN v20.0.0 + // Deprecated: Prefer using ErrNodeIsAmbiguous NodeIsAmbiguous = "err-node-is-ambiguous" // MaxLeases serves as an identifying error string indicating that the diff --git a/docs/img/access-graph/dac/db-object-contains-relation.png b/docs/img/access-graph/dac/db-object-contains-relation.png new file mode 100644 index 0000000000000..c26f2c541bdaf Binary files /dev/null and b/docs/img/access-graph/dac/db-object-contains-relation.png differ diff --git a/docs/img/access-graph/dac/db-object-details.png b/docs/img/access-graph/dac/db-object-details.png new file mode 100644 index 0000000000000..b0ffdd835149d Binary files /dev/null and b/docs/img/access-graph/dac/db-object-details.png differ diff --git a/docs/img/access-graph/dac/db-object-permissions-label.png b/docs/img/access-graph/dac/db-object-permissions-label.png new file mode 100644 index 0000000000000..8034e19741855 Binary files /dev/null and b/docs/img/access-graph/dac/db-object-permissions-label.png differ diff --git a/docs/img/access-graph/dac/overview.png b/docs/img/access-graph/dac/overview.png new file mode 100644 index 0000000000000..47100c9188ee6 Binary files /dev/null and b/docs/img/access-graph/dac/overview.png differ diff --git a/docs/pages/admin-guides/access-controls/access-request-plugins/opsgenie.mdx b/docs/pages/admin-guides/access-controls/access-request-plugins/opsgenie.mdx index 0492e31ce28c1..01e97b95c59f1 100644 --- a/docs/pages/admin-guides/access-controls/access-request-plugins/opsgenie.mdx +++ b/docs/pages/admin-guides/access-controls/access-request-plugins/opsgenie.mdx @@ -20,13 +20,13 @@ Opsgenie. - A Teleport Enterprise Cloud account. -- The Enterprise `tctl` admin tool and `tsh` client tool version >= (=teleport.version=). +- The `tctl` admin tool and `tsh` client tool version >= (=teleport.version=). You can verify the tools you have installed by running the following commands: ```code $ tctl version - # Teleport Enterprise v(=teleport.version=) go(=teleport.golang=) + # Teleport v(=teleport.version=) go(=teleport.golang=) $ tsh version # Teleport v(=teleport.version=) go(=teleport.golang=) diff --git a/docs/pages/admin-guides/api/rbac.mdx b/docs/pages/admin-guides/api/rbac.mdx index 83b9ae16edc0b..f292d19a1b63e 100644 --- a/docs/pages/admin-guides/api/rbac.mdx +++ b/docs/pages/admin-guides/api/rbac.mdx @@ -859,7 +859,11 @@ spec: enabled: true max_session_ttl: 30h0m0s pin_source_ip: false - port_forwarding: true + ssh_port_forwarding: + remote: + enabled: true + local: + enabled: true record_session: default: best_effort desktop: true @@ -906,7 +910,11 @@ spec: enabled: true max_session_ttl: 30h0m0s pin_source_ip: false - port_forwarding: true + ssh_port_forwarding: + remote: + enabled: true + local: + enabled: true record_session: default: best_effort desktop: true diff --git a/docs/pages/admin-guides/management/admin/trustedclusters.mdx b/docs/pages/admin-guides/management/admin/trustedclusters.mdx index 5322f020927f0..ae6bbe6428094 100644 --- a/docs/pages/admin-guides/management/admin/trustedclusters.mdx +++ b/docs/pages/admin-guides/management/admin/trustedclusters.mdx @@ -151,13 +151,11 @@ To complete the steps in this guide, verify your environment meets the following - The `tctl` admin tool and `tsh` client tool version >= (=teleport.version=). - For Teleport Enterprise and Teleport Enterprise cloud, you should have the - Enterprise version of `tctl` and `tsh` installed. You can verify the tools - you have installed by running the following commands: + You can verify the tools you have installed by running the following commands: ```code $ tctl version - # Teleport Enterprise v(=teleport.version=) go(=teleport.golang=) + # Teleport v(=teleport.version=) go(=teleport.golang=) $ tsh version # Teleport v(=teleport.version=) go(=teleport.golang=) diff --git a/docs/pages/admin-guides/teleport-policy/policy-connections.mdx b/docs/pages/admin-guides/teleport-policy/policy-connections.mdx index 37fae4539620d..85120c116e6f0 100644 --- a/docs/pages/admin-guides/teleport-policy/policy-connections.mdx +++ b/docs/pages/admin-guides/teleport-policy/policy-connections.mdx @@ -71,6 +71,32 @@ can be identified by having `Temporary: true` property. Resource Groups are created from Teleport roles. +### Database Access Controls + +Teleport supports [object-level permissions](../../enroll-resources/database-access/rbac.mdx#executing-database-object-permission-rules) for select database protocols. + +The database objects-level access information is automatically synchronized to Teleport Policy, making it possible to see who has particular levels of access to the different parts of the database. + +When you inspect a particular user's access, the Teleport Access Graph will automatically display the database objects that the user can access. + +![Overview of access including individual database objects](../../../img/access-graph/dac/overview.png) + +To see more details about a specific database object, simply select it. + +
+![Details of an individual database object](../../../img/access-graph/dac/db-object-details.png) +
+ +In the graph, database objects are connected by multiple edges: + +1. There is exactly one edge connecting the object to its parent database resource. This edge has "contains" label. + +![Database object and parent database resource](../../../img/access-graph/dac/db-object-contains-relation.png) + +2. At least one edge shows the permissions associated with the object, such as `INSERT, SELECT, UPDATE`. If multiple roles grant permissions to the same object, additional edges of this type may be present. The permissions are presented as edge labels. + +![Specific object permissions](../../../img/access-graph/dac/db-object-permissions-label.png) + #### Resources Resources are created from Teleport resources like nodes, databases, and Kubernetes clusters. diff --git a/docs/pages/enroll-resources/kubernetes-access/getting-started.mdx b/docs/pages/enroll-resources/kubernetes-access/getting-started.mdx index 552bed0c763f4..e7ced2de49bd4 100644 --- a/docs/pages/enroll-resources/kubernetes-access/getting-started.mdx +++ b/docs/pages/enroll-resources/kubernetes-access/getting-started.mdx @@ -22,19 +22,17 @@ For information about other ways to enroll and discover Kubernetes clusters, see - Access to a running Teleport cluster, `tctl` admin tool, and `tsh` client tool, version >= (=teleport.version=). - - For Teleport Enterprise and Teleport Enterprise Cloud, you should - use the Enterprise version of `tctl`. + You can verify the tools you have installed by running the following commands: ```code $ tctl version - # Teleport Enterprise v(=teleport.version=) go(=teleport.golang=) - + # Teleport v(=teleport.version=) go(=teleport.golang=) + $ tsh version # Teleport v(=teleport.version=) go(=teleport.golang=) ``` - + You can download these tools by following the appropriate [Installation instructions](../../installation.mdx#linux) for your environment. diff --git a/docs/pages/enroll-resources/server-access/rbac.mdx b/docs/pages/enroll-resources/server-access/rbac.mdx index 6cc43636c5d27..036a91e7aa642 100644 --- a/docs/pages/enroll-resources/server-access/rbac.mdx +++ b/docs/pages/enroll-resources/server-access/rbac.mdx @@ -135,8 +135,18 @@ spec: create_host_user_mode: keep # forward_agent controls whether SSH agent forwarding is allowed forward_agent: true - # port_forwarding controls whether TCP port forwarding is allowed for SSH - port_forwarding: true + # ssh_port_forwarding controls which TCP port forwarding modes are allowed over SSH. This replaces + # the deprecated port_forwarding field, which did not differentiate between remote and local + # port forwarding modes. If you have any existing roles that allow forwarding by enabling the + # legacy port_forwarding field then the forwarding controls configured in ssh_port_forwarding will be + # ignored. + ssh_port_forwarding: + # configures remote port forwarding behavior + remote: + enabled: true + # configures local port forwarding behavior + local: + enabled: true # ssh_file_copy controls whether file copying (SCP/SFTP) is allowed. # Defaults to true. ssh_file_copy: false diff --git a/docs/pages/includes/database-access/tctl-auth-sign-3-files.mdx b/docs/pages/includes/database-access/tctl-auth-sign-3-files.mdx index 253c54b2121b4..a84fab0c7d9ad 100644 --- a/docs/pages/includes/database-access/tctl-auth-sign-3-files.mdx +++ b/docs/pages/includes/database-access/tctl-auth-sign-3-files.mdx @@ -30,12 +30,12 @@ the database, follow these instructions on your workstation: ``` 1. Export Teleport's certificate authority and a generate certificate/key pair. - This example generates a certificate with a 1-year validity period. + This example generates a certificate with a 90-day validity period. `db.example.com` is the hostname where the Teleport Database Service can reach the {{ dbname }} server. ```code - $ tctl auth sign --format={{ format }} --host=db.example.com --out=server --ttl=2190h + $ tctl auth sign --format={{ format }} --host=db.example.com --out=server --ttl=2160h ``` (!docs/pages/includes/database-access/ttl-note.mdx!) diff --git a/docs/pages/includes/role-spec.mdx b/docs/pages/includes/role-spec.mdx index ef780959cd30b..1a0f2cfeece0c 100644 --- a/docs/pages/includes/role-spec.mdx +++ b/docs/pages/includes/role-spec.mdx @@ -13,8 +13,18 @@ spec: max_session_ttl: 8h # forward_agent controls whether SSH agent forwarding is allowed forward_agent: true - # port_forwarding controls whether TCP port forwarding is allowed for SSH - port_forwarding: true + # ssh_port_forwarding controls which TCP port forwarding modes are allowed over SSH. This replaces + # the deprecated port_forwarding field, which did not differentiate between remote and local + # port forwarding modes. If you have any existing roles that allow forwarding by enabling the + # legacy port_forwarding field then the forwarding controls configured in ssh_port_forwarding will be + # ignored. + ssh_port_forwarding: + # configures remote port forwarding behavior + remote: + enabled: true + # configures local port forwarding behavior + local: + enabled: true # ssh_file_copy controls whether file copying (SCP/SFTP) is allowed. # Defaults to true. ssh_file_copy: false diff --git a/docs/pages/reference/access-controls/roles.mdx b/docs/pages/reference/access-controls/roles.mdx index 86029bff5012b..c67dd234b8642 100644 --- a/docs/pages/reference/access-controls/roles.mdx +++ b/docs/pages/reference/access-controls/roles.mdx @@ -52,7 +52,7 @@ user: | - | - | - | | `max_session_ttl` | Max. time to live (TTL) of a user's SSH certificates | The shortest TTL wins | | `forward_agent` | Allow SSH agent forwarding | Logical "OR" i.e. if any role allows agent forwarding, it's allowed | -| `port_forwarding` | Allow TCP port forwarding | Logical "OR" i.e. if any role allows port forwarding, it's allowed | +| `ssh_port_forwarding` | Allow TCP port forwarding | Logical "AND" i.e. if any role denies port forwarding, it's denied | | `ssh_file_copy` | Allow SCP/SFTP | Logical "AND" i.e. if all roles allows file copying, it's allowed | | `client_idle_timeout` | Forcefully terminate active sessions after an idle interval | The shortest timeout value wins, i.e. the most restrictive value is selected | | `disconnect_expired_cert` | Forcefully terminate active sessions when a client certificate expires | Logical "OR" i.e. evaluates to "yes" if at least one role requires session termination | diff --git a/docs/pages/reference/cloud-faq.mdx b/docs/pages/reference/cloud-faq.mdx index c6ce43df8e05b..d39fa4107e3f3 100644 --- a/docs/pages/reference/cloud-faq.mdx +++ b/docs/pages/reference/cloud-faq.mdx @@ -95,7 +95,7 @@ $ tctl nodes add --ttl=5m --roles=node,proxy --token=$(uuid) ### How can I access the `tctl` admin tool? -Find the appropriate download at [Installation](../installation.mdx). Use the Enterprise version of `tctl`. +Find the appropriate download at [Installation](../installation.mdx). After downloading the tools, first log in to your cluster using `tsh`, then use `tctl` remotely: diff --git a/docs/pages/reference/terraform-provider/data-sources/data-sources.mdx b/docs/pages/reference/terraform-provider/data-sources/data-sources.mdx index 92e37199d2dd6..0e6dfabb1b4d4 100644 --- a/docs/pages/reference/terraform-provider/data-sources/data-sources.mdx +++ b/docs/pages/reference/terraform-provider/data-sources/data-sources.mdx @@ -34,3 +34,4 @@ The Teleport Terraform provider supports the following data-sources: - [`teleport_trusted_cluster`](./trusted_cluster.mdx) - [`teleport_trusted_device`](./trusted_device.mdx) - [`teleport_user`](./user.mdx) + - [`teleport_workload_identity`](./workload_identity.mdx) diff --git a/docs/pages/reference/terraform-provider/data-sources/workload_identity.mdx b/docs/pages/reference/terraform-provider/data-sources/workload_identity.mdx new file mode 100644 index 0000000000000..2298d5363d77c --- /dev/null +++ b/docs/pages/reference/terraform-provider/data-sources/workload_identity.mdx @@ -0,0 +1,69 @@ +--- +title: Reference for the teleport_workload_identity Terraform data-source +sidebar_label: workload_identity +description: This page describes the supported values of the teleport_workload_identity data-source of the Teleport Terraform provider. +--- + +{/*Auto-generated file. Do not edit.*/} +{/*To regenerate, navigate to integrations/terraform and run `make docs`.*/} + + + + + +{/* schema generated by tfplugindocs */} +## Schema + +### Optional + +- `metadata` (Attributes) Common metadata that all resources share. (see [below for nested schema](#nested-schema-for-metadata)) +- `spec` (Attributes) The configured properties of the WorkloadIdentity (see [below for nested schema](#nested-schema-for-spec)) +- `sub_kind` (String) Differentiates variations of the same kind. All resources should contain one, even if it is never populated. +- `version` (String) The version of the resource being represented. + +### Nested Schema for `metadata` + +Optional: + +- `description` (String) description is object description. +- `expires` (String) expires is a global expiry time header can be set on any resource in the system. +- `labels` (Map of String) labels is a set of labels. +- `name` (String) name is an object name. + + +### Nested Schema for `spec` + +Optional: + +- `rules` (Attributes) The rules which are evaluated before the WorkloadIdentity can be issued. (see [below for nested schema](#nested-schema-for-specrules)) +- `spiffe` (Attributes) Configuration pertaining to the issuance of SPIFFE-compatible workload identity credentials. (see [below for nested schema](#nested-schema-for-specspiffe)) + +### Nested Schema for `spec.rules` + +Optional: + +- `allow` (Attributes List) A list of rules used to determine if a WorkloadIdentity can be issued. If none are provided, it will be considered a pass. If any are provided, then at least one must pass for the rules to be considered passed. (see [below for nested schema](#nested-schema-for-specrulesallow)) + +### Nested Schema for `spec.rules.allow` + +Optional: + +- `conditions` (Attributes List) The conditions that must be met for this rule to be considered passed. (see [below for nested schema](#nested-schema-for-specrulesallowconditions)) + +### Nested Schema for `spec.rules.allow.conditions` + +Optional: + +- `attribute` (String) The name of the attribute to evaluate the condition against. +- `equals` (String) An exact string that the attribute must match. + + + + +### Nested Schema for `spec.spiffe` + +Optional: + +- `hint` (String) A freeform text field which is provided to workloads along with a credential produced by this WorkloadIdentity. This can be used to provide additional context that can be used to select between multiple credentials. +- `id` (String) The path of the SPIFFE ID that will be issued to the workload. This should be prefixed with a forward-slash ("/"). This field supports templating using attributes. + diff --git a/docs/pages/reference/terraform-provider/resources/resources.mdx b/docs/pages/reference/terraform-provider/resources/resources.mdx index 51d7bb8d2e3b3..e962f85c38abb 100644 --- a/docs/pages/reference/terraform-provider/resources/resources.mdx +++ b/docs/pages/reference/terraform-provider/resources/resources.mdx @@ -36,3 +36,4 @@ The Teleport Terraform provider supports the following resources: - [`teleport_trusted_cluster`](./trusted_cluster.mdx) - [`teleport_trusted_device`](./trusted_device.mdx) - [`teleport_user`](./user.mdx) + - [`teleport_workload_identity`](./workload_identity.mdx) diff --git a/docs/pages/reference/terraform-provider/resources/role.mdx b/docs/pages/reference/terraform-provider/resources/role.mdx index 3d573fa65646b..70d9c3edc0f1e 100644 --- a/docs/pages/reference/terraform-provider/resources/role.mdx +++ b/docs/pages/reference/terraform-provider/resources/role.mdx @@ -27,9 +27,17 @@ resource "teleport_role" "example" { spec = { options = { - forward_agent = false - max_session_ttl = "7m" - port_forwarding = false + forward_agent = false + max_session_ttl = "7m" + ssh_port_forwarding = { + remote = { + enabled = false + } + + local = { + enabled = false + } + } client_idle_timeout = "1h" disconnect_expired_cert = true permit_x11_forwarding = false diff --git a/docs/pages/reference/terraform-provider/resources/workload_identity.mdx b/docs/pages/reference/terraform-provider/resources/workload_identity.mdx new file mode 100644 index 0000000000000..a9d3da4bc7a73 --- /dev/null +++ b/docs/pages/reference/terraform-provider/resources/workload_identity.mdx @@ -0,0 +1,94 @@ +--- +title: Reference for the teleport_workload_identity Terraform resource +sidebar_label: workload_identity +description: This page describes the supported values of the teleport_workload_identity resource of the Teleport Terraform provider. +--- + +{/*Auto-generated file. Do not edit.*/} +{/*To regenerate, navigate to integrations/terraform and run `make docs`.*/} + + + +## Example Usage + +```hcl +resource "teleport_workload_identity" "example" { + version = "v1" + metadata = { + name = "example" + } + spec = { + rules = { + allow = [ + { + conditions = [{ + attribute = "user.name" + equals = "noah" + }] + } + ] + } + spiffe = { + id = "/my/spiffe/id/path" + hint = "my-hint" + } + } +} +``` + +{/* schema generated by tfplugindocs */} +## Schema + +### Optional + +- `metadata` (Attributes) Common metadata that all resources share. (see [below for nested schema](#nested-schema-for-metadata)) +- `spec` (Attributes) The configured properties of the WorkloadIdentity (see [below for nested schema](#nested-schema-for-spec)) +- `sub_kind` (String) Differentiates variations of the same kind. All resources should contain one, even if it is never populated. +- `version` (String) The version of the resource being represented. + +### Nested Schema for `metadata` + +Optional: + +- `description` (String) description is object description. +- `expires` (String) expires is a global expiry time header can be set on any resource in the system. +- `labels` (Map of String) labels is a set of labels. +- `name` (String) name is an object name. + + +### Nested Schema for `spec` + +Optional: + +- `rules` (Attributes) The rules which are evaluated before the WorkloadIdentity can be issued. (see [below for nested schema](#nested-schema-for-specrules)) +- `spiffe` (Attributes) Configuration pertaining to the issuance of SPIFFE-compatible workload identity credentials. (see [below for nested schema](#nested-schema-for-specspiffe)) + +### Nested Schema for `spec.rules` + +Optional: + +- `allow` (Attributes List) A list of rules used to determine if a WorkloadIdentity can be issued. If none are provided, it will be considered a pass. If any are provided, then at least one must pass for the rules to be considered passed. (see [below for nested schema](#nested-schema-for-specrulesallow)) + +### Nested Schema for `spec.rules.allow` + +Optional: + +- `conditions` (Attributes List) The conditions that must be met for this rule to be considered passed. (see [below for nested schema](#nested-schema-for-specrulesallowconditions)) + +### Nested Schema for `spec.rules.allow.conditions` + +Optional: + +- `attribute` (String) The name of the attribute to evaluate the condition against. +- `equals` (String) An exact string that the attribute must match. + + + + +### Nested Schema for `spec.spiffe` + +Optional: + +- `hint` (String) A freeform text field which is provided to workloads along with a credential produced by this WorkloadIdentity. This can be used to provide additional context that can be used to select between multiple credentials. +- `id` (String) The path of the SPIFFE ID that will be issued to the workload. This should be prefixed with a forward-slash ("/"). This field supports templating using attributes. + diff --git a/e b/e index ab7c9fca11b43..1ac55fb740b1f 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit ab7c9fca11b43e3a3023fe4fd412b2d4e58377e6 +Subproject commit 1ac55fb740b1f79d44a1457d004bfb0b422b4007 diff --git a/examples/access-plugin-minimal/go.mod b/examples/access-plugin-minimal/go.mod index 85aee3c5a99da..278b61fe25b2c 100644 --- a/examples/access-plugin-minimal/go.mod +++ b/examples/access-plugin-minimal/go.mod @@ -48,7 +48,7 @@ require ( go.opentelemetry.io/proto/otlp v1.1.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb // indirect - golang.org/x/net v0.24.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.15.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect diff --git a/examples/access-plugin-minimal/go.sum b/examples/access-plugin-minimal/go.sum index fe8f19d63479a..7eb7ac9bd2334 100644 --- a/examples/access-plugin-minimal/go.sum +++ b/examples/access-plugin-minimal/go.sum @@ -203,8 +203,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= diff --git a/examples/api-sync-roles/go.mod b/examples/api-sync-roles/go.mod index e4134f2934472..28c2374d1c3d4 100644 --- a/examples/api-sync-roles/go.mod +++ b/examples/api-sync-roles/go.mod @@ -59,7 +59,7 @@ require ( go.opentelemetry.io/proto/otlp v1.1.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb // indirect - golang.org/x/net v0.24.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.15.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect diff --git a/examples/api-sync-roles/go.sum b/examples/api-sync-roles/go.sum index be4b25422f0cd..288a6104afcbe 100644 --- a/examples/api-sync-roles/go.sum +++ b/examples/api-sync-roles/go.sum @@ -234,8 +234,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= diff --git a/examples/aws/terraform/AMIS.md b/examples/aws/terraform/AMIS.md index 31eddc672bbaa..7d865426baf67 100644 --- a/examples/aws/terraform/AMIS.md +++ b/examples/aws/terraform/AMIS.md @@ -6,116 +6,116 @@ This list is updated when new AMI versions are released. ### OSS ``` -# ap-northeast-1 v17.0.4 arm64 OSS: ami-01716a041218b7583 -# ap-northeast-1 v17.0.4 x86_64 OSS: ami-06a0bce35c7d65bf6 -# ap-northeast-2 v17.0.4 arm64 OSS: ami-02742215c192f5bef -# ap-northeast-2 v17.0.4 x86_64 OSS: ami-0a5e52dbdb5eaa650 -# ap-northeast-3 v17.0.4 arm64 OSS: ami-0160778bf23cf0643 -# ap-northeast-3 v17.0.4 x86_64 OSS: ami-0a7a3595539ec2167 -# ap-south-1 v17.0.4 arm64 OSS: ami-09632d32d2cba70c3 -# ap-south-1 v17.0.4 x86_64 OSS: ami-0ab4d472fa9f1b290 -# ap-southeast-1 v17.0.4 arm64 OSS: ami-0bccbfe98fa25f3d0 -# ap-southeast-1 v17.0.4 x86_64 OSS: ami-01ab2d0af845cd0a7 -# ap-southeast-2 v17.0.4 arm64 OSS: ami-022f9be11a0c74479 -# ap-southeast-2 v17.0.4 x86_64 OSS: ami-04db11a0a7eb447f9 -# ca-central-1 v17.0.4 arm64 OSS: ami-09c09ef2b04cdc0bd -# ca-central-1 v17.0.4 x86_64 OSS: ami-09c4ed011317bd4fc -# eu-central-1 v17.0.4 arm64 OSS: ami-066511d9d4a4b6e37 -# eu-central-1 v17.0.4 x86_64 OSS: ami-004a4d77b5b1dacc2 -# eu-north-1 v17.0.4 arm64 OSS: ami-05cdb6f461101593a -# eu-north-1 v17.0.4 x86_64 OSS: ami-0af9705b2af84685a -# eu-west-1 v17.0.4 arm64 OSS: ami-0e5295d6c18225338 -# eu-west-1 v17.0.4 x86_64 OSS: ami-09f90453fcb9d65e7 -# eu-west-2 v17.0.4 arm64 OSS: ami-070763cd4a0edfc01 -# eu-west-2 v17.0.4 x86_64 OSS: ami-00f9fe423c08672a5 -# eu-west-3 v17.0.4 arm64 OSS: ami-065e2413a3d90fde7 -# eu-west-3 v17.0.4 x86_64 OSS: ami-05809768420d06e55 -# sa-east-1 v17.0.4 arm64 OSS: ami-0d3cc91be3e224009 -# sa-east-1 v17.0.4 x86_64 OSS: ami-0e937294d10077f31 -# us-east-1 v17.0.4 arm64 OSS: ami-0af34c1d6e3070279 -# us-east-1 v17.0.4 x86_64 OSS: ami-05ac65c09e4c769bb -# us-east-2 v17.0.4 arm64 OSS: ami-0404817145aa2b1c5 -# us-east-2 v17.0.4 x86_64 OSS: ami-06e28394d0b853fdc -# us-west-1 v17.0.4 arm64 OSS: ami-03054d783cbd721da -# us-west-1 v17.0.4 x86_64 OSS: ami-038de85609360f42a -# us-west-2 v17.0.4 arm64 OSS: ami-0ead7a03ac47a4aca -# us-west-2 v17.0.4 x86_64 OSS: ami-009e6a010be394578 +# ap-northeast-1 v17.1.1 arm64 OSS: ami-0a5f537784eefe27d +# ap-northeast-1 v17.1.1 x86_64 OSS: ami-0fa7217a33b990ade +# ap-northeast-2 v17.1.1 arm64 OSS: ami-07743f3160d7a84d5 +# ap-northeast-2 v17.1.1 x86_64 OSS: ami-09494d6d9a40ba545 +# ap-northeast-3 v17.1.1 arm64 OSS: ami-0796da20b071a04b3 +# ap-northeast-3 v17.1.1 x86_64 OSS: ami-01261fded996c0ef0 +# ap-south-1 v17.1.1 arm64 OSS: ami-0e27bda4740337a18 +# ap-south-1 v17.1.1 x86_64 OSS: ami-0880e06edeb92ecd8 +# ap-southeast-1 v17.1.1 arm64 OSS: ami-07af7f495d88a376f +# ap-southeast-1 v17.1.1 x86_64 OSS: ami-01e50574d67633cce +# ap-southeast-2 v17.1.1 arm64 OSS: ami-037afbc1ff2499bfe +# ap-southeast-2 v17.1.1 x86_64 OSS: ami-0e9edd4ef1b3e31d6 +# ca-central-1 v17.1.1 arm64 OSS: ami-0073a58172aa80af8 +# ca-central-1 v17.1.1 x86_64 OSS: ami-0fce23a5eb19a02db +# eu-central-1 v17.1.1 arm64 OSS: ami-0d5907f1b2a21211f +# eu-central-1 v17.1.1 x86_64 OSS: ami-0ba02341b6b92f1ec +# eu-north-1 v17.1.1 arm64 OSS: ami-0ea705245cecc65c2 +# eu-north-1 v17.1.1 x86_64 OSS: ami-0c3fa661a640c192d +# eu-west-1 v17.1.1 arm64 OSS: ami-01b5104f6f8cd57f3 +# eu-west-1 v17.1.1 x86_64 OSS: ami-0b48b0e504ca31eea +# eu-west-2 v17.1.1 arm64 OSS: ami-076c147bd8e15b442 +# eu-west-2 v17.1.1 x86_64 OSS: ami-09a03b3aacde88e2c +# eu-west-3 v17.1.1 arm64 OSS: ami-0dce47e1114501cb8 +# eu-west-3 v17.1.1 x86_64 OSS: ami-0e6c59fe3f5dc2d10 +# sa-east-1 v17.1.1 arm64 OSS: ami-0f690494a46ee109b +# sa-east-1 v17.1.1 x86_64 OSS: ami-037d098fadee1e2c6 +# us-east-1 v17.1.1 arm64 OSS: ami-07740b1bf08cf934f +# us-east-1 v17.1.1 x86_64 OSS: ami-041cfef0d1d4a87c5 +# us-east-2 v17.1.1 arm64 OSS: ami-031fe3ad6e1dceca7 +# us-east-2 v17.1.1 x86_64 OSS: ami-0536a571102911485 +# us-west-1 v17.1.1 arm64 OSS: ami-0835e194313e390c3 +# us-west-1 v17.1.1 x86_64 OSS: ami-0578b1ed1a3ac9b43 +# us-west-2 v17.1.1 arm64 OSS: ami-09edc5fe54a15763d +# us-west-2 v17.1.1 x86_64 OSS: ami-0cb483d633378e707 ``` ### Enterprise ``` -# ap-northeast-1 v17.0.4 arm64 Enterprise: ami-073c7d1f9f700ad5f -# ap-northeast-1 v17.0.4 x86_64 Enterprise: ami-080f2379b5915b568 -# ap-northeast-2 v17.0.4 arm64 Enterprise: ami-0fe045659a23cff48 -# ap-northeast-2 v17.0.4 x86_64 Enterprise: ami-04ce28140625e2dee -# ap-northeast-3 v17.0.4 arm64 Enterprise: ami-04c911f34da45d0b4 -# ap-northeast-3 v17.0.4 x86_64 Enterprise: ami-029ad395b820c9d13 -# ap-south-1 v17.0.4 arm64 Enterprise: ami-02e80813715c2edce -# ap-south-1 v17.0.4 x86_64 Enterprise: ami-0ff485dc38ba524ee -# ap-southeast-1 v17.0.4 arm64 Enterprise: ami-0ef089f8e3c8b2948 -# ap-southeast-1 v17.0.4 x86_64 Enterprise: ami-0304c1638e8896047 -# ap-southeast-2 v17.0.4 arm64 Enterprise: ami-051c847519cd353b5 -# ap-southeast-2 v17.0.4 x86_64 Enterprise: ami-01a4cb81d27479b71 -# ca-central-1 v17.0.4 arm64 Enterprise: ami-0e7ff49b010ef7510 -# ca-central-1 v17.0.4 x86_64 Enterprise: ami-070729ca8a3cd52d4 -# eu-central-1 v17.0.4 arm64 Enterprise: ami-065ac13475462188a -# eu-central-1 v17.0.4 x86_64 Enterprise: ami-09ba3a9085883f7c5 -# eu-north-1 v17.0.4 arm64 Enterprise: ami-0871621387aea29c0 -# eu-north-1 v17.0.4 x86_64 Enterprise: ami-018bd329b835264ee -# eu-west-1 v17.0.4 arm64 Enterprise: ami-0ad04ae06926ca8a6 -# eu-west-1 v17.0.4 x86_64 Enterprise: ami-0e291eeaac1af9b96 -# eu-west-2 v17.0.4 arm64 Enterprise: ami-0147ea5263de3ff82 -# eu-west-2 v17.0.4 x86_64 Enterprise: ami-0712aa8f4d45d7f63 -# eu-west-3 v17.0.4 arm64 Enterprise: ami-0836ab80362bb163a -# eu-west-3 v17.0.4 x86_64 Enterprise: ami-00e63b301d64501bd -# sa-east-1 v17.0.4 arm64 Enterprise: ami-05c4af22838178da8 -# sa-east-1 v17.0.4 x86_64 Enterprise: ami-0653dc7e72358fdf4 -# us-east-1 v17.0.4 arm64 Enterprise: ami-094bdb3b5c7462211 -# us-east-1 v17.0.4 x86_64 Enterprise: ami-06fa756239fba1686 -# us-east-2 v17.0.4 arm64 Enterprise: ami-0d0799739729e9282 -# us-east-2 v17.0.4 x86_64 Enterprise: ami-01a960de052a65ced -# us-west-1 v17.0.4 arm64 Enterprise: ami-0d8ea901cc1172268 -# us-west-1 v17.0.4 x86_64 Enterprise: ami-041d4d9e8258802a2 -# us-west-2 v17.0.4 arm64 Enterprise: ami-0917840ac9f9e0683 -# us-west-2 v17.0.4 x86_64 Enterprise: ami-0dfdb666a13565a1e +# ap-northeast-1 v17.1.1 arm64 Enterprise: ami-06ae3535ed979aee1 +# ap-northeast-1 v17.1.1 x86_64 Enterprise: ami-07a429464b7ee8c39 +# ap-northeast-2 v17.1.1 arm64 Enterprise: ami-038083abbd8409558 +# ap-northeast-2 v17.1.1 x86_64 Enterprise: ami-09c60722ea157e47f +# ap-northeast-3 v17.1.1 arm64 Enterprise: ami-0462fb982418429a6 +# ap-northeast-3 v17.1.1 x86_64 Enterprise: ami-0edf80412e2acfd7d +# ap-south-1 v17.1.1 arm64 Enterprise: ami-00ee7e18b4dce5b38 +# ap-south-1 v17.1.1 x86_64 Enterprise: ami-0c79727a649925e90 +# ap-southeast-1 v17.1.1 arm64 Enterprise: ami-0d20a3b57c85d3181 +# ap-southeast-1 v17.1.1 x86_64 Enterprise: ami-0fbcb24884a208cb8 +# ap-southeast-2 v17.1.1 arm64 Enterprise: ami-0c0f6083586a83757 +# ap-southeast-2 v17.1.1 x86_64 Enterprise: ami-0b4a4c53dd6a3c041 +# ca-central-1 v17.1.1 arm64 Enterprise: ami-060427412a5b319db +# ca-central-1 v17.1.1 x86_64 Enterprise: ami-08c8208ba96f752d3 +# eu-central-1 v17.1.1 arm64 Enterprise: ami-09c856532e84903a4 +# eu-central-1 v17.1.1 x86_64 Enterprise: ami-04d917c8c4579daf3 +# eu-north-1 v17.1.1 arm64 Enterprise: ami-04626a94f7050e050 +# eu-north-1 v17.1.1 x86_64 Enterprise: ami-02037040a4dec671c +# eu-west-1 v17.1.1 arm64 Enterprise: ami-00b9b4e5b3a88e7e2 +# eu-west-1 v17.1.1 x86_64 Enterprise: ami-071ebf6045a75b052 +# eu-west-2 v17.1.1 arm64 Enterprise: ami-0a236e67285cdb0b1 +# eu-west-2 v17.1.1 x86_64 Enterprise: ami-0c5a3e844c5b0ec71 +# eu-west-3 v17.1.1 arm64 Enterprise: ami-0140989d590989f0d +# eu-west-3 v17.1.1 x86_64 Enterprise: ami-073047ac3039425ed +# sa-east-1 v17.1.1 arm64 Enterprise: ami-05e5977088a7c2f0d +# sa-east-1 v17.1.1 x86_64 Enterprise: ami-00c165883d2b5aaf3 +# us-east-1 v17.1.1 arm64 Enterprise: ami-034f43fd05ada7b8b +# us-east-1 v17.1.1 x86_64 Enterprise: ami-02082755b0ae57bee +# us-east-2 v17.1.1 arm64 Enterprise: ami-0a1f6bc02c3d3bf34 +# us-east-2 v17.1.1 x86_64 Enterprise: ami-0adca974f282e5cff +# us-west-1 v17.1.1 arm64 Enterprise: ami-05d3f25c97a11b669 +# us-west-1 v17.1.1 x86_64 Enterprise: ami-080171387a0b1e9f9 +# us-west-2 v17.1.1 arm64 Enterprise: ami-0cdaf82f5bb71e653 +# us-west-2 v17.1.1 x86_64 Enterprise: ami-07518e397993ac08b ``` ### Enterprise FIPS ``` -# ap-northeast-1 v17.0.4 arm64 Enterprise FIPS: ami-09e383034b7defdaf -# ap-northeast-1 v17.0.4 x86_64 Enterprise FIPS: ami-0df69b8a4380e1de6 -# ap-northeast-2 v17.0.4 arm64 Enterprise FIPS: ami-0f8d28e7bcefe2970 -# ap-northeast-2 v17.0.4 x86_64 Enterprise FIPS: ami-0197da2adec541176 -# ap-northeast-3 v17.0.4 arm64 Enterprise FIPS: ami-06f2fab51b401aa96 -# ap-northeast-3 v17.0.4 x86_64 Enterprise FIPS: ami-0076f778514b9496d -# ap-south-1 v17.0.4 arm64 Enterprise FIPS: ami-07ea988d6c2412e73 -# ap-south-1 v17.0.4 x86_64 Enterprise FIPS: ami-0ac1183e7314ea34d -# ap-southeast-1 v17.0.4 arm64 Enterprise FIPS: ami-0f4a91f7e074c1771 -# ap-southeast-1 v17.0.4 x86_64 Enterprise FIPS: ami-01ee28a10a5e1e11f -# ap-southeast-2 v17.0.4 arm64 Enterprise FIPS: ami-0a65998ce9d562d79 -# ap-southeast-2 v17.0.4 x86_64 Enterprise FIPS: ami-0e1b23bcaaf49287c -# ca-central-1 v17.0.4 arm64 Enterprise FIPS: ami-05ecb968b73974203 -# ca-central-1 v17.0.4 x86_64 Enterprise FIPS: ami-07491a64d004d3291 -# eu-central-1 v17.0.4 arm64 Enterprise FIPS: ami-08a2592c33f8ea84e -# eu-central-1 v17.0.4 x86_64 Enterprise FIPS: ami-0f9e368192f2ec651 -# eu-north-1 v17.0.4 arm64 Enterprise FIPS: ami-0b380397bb540ce29 -# eu-north-1 v17.0.4 x86_64 Enterprise FIPS: ami-023badde3df29ae70 -# eu-west-1 v17.0.4 arm64 Enterprise FIPS: ami-03490ba6be4c23476 -# eu-west-1 v17.0.4 x86_64 Enterprise FIPS: ami-0d5f2c95321fe98f2 -# eu-west-2 v17.0.4 arm64 Enterprise FIPS: ami-03437b2758ba462c9 -# eu-west-2 v17.0.4 x86_64 Enterprise FIPS: ami-0f569292c54f0569f -# eu-west-3 v17.0.4 arm64 Enterprise FIPS: ami-02bf987212952aab2 -# eu-west-3 v17.0.4 x86_64 Enterprise FIPS: ami-0cbe8a49f10fa96ff -# sa-east-1 v17.0.4 arm64 Enterprise FIPS: ami-096f403881ccb12ec -# sa-east-1 v17.0.4 x86_64 Enterprise FIPS: ami-025711384880cb5a8 -# us-east-1 v17.0.4 arm64 Enterprise FIPS: ami-0c1506fe582a035bc -# us-east-1 v17.0.4 x86_64 Enterprise FIPS: ami-083791d36cad90c5b -# us-east-2 v17.0.4 arm64 Enterprise FIPS: ami-0beabe7f3c09fbe87 -# us-east-2 v17.0.4 x86_64 Enterprise FIPS: ami-02d7a281b2c843203 -# us-west-1 v17.0.4 arm64 Enterprise FIPS: ami-0e5a7de7a6163d9da -# us-west-1 v17.0.4 x86_64 Enterprise FIPS: ami-01298486181f8e6b0 -# us-west-2 v17.0.4 arm64 Enterprise FIPS: ami-0aff456ddb4a86a85 -# us-west-2 v17.0.4 x86_64 Enterprise FIPS: ami-0998561252ed04b71 +# ap-northeast-1 v17.1.1 arm64 Enterprise FIPS: ami-095a897c8b13edec7 +# ap-northeast-1 v17.1.1 x86_64 Enterprise FIPS: ami-0a03bc36f642d0795 +# ap-northeast-2 v17.1.1 arm64 Enterprise FIPS: ami-083c2cc73df99c551 +# ap-northeast-2 v17.1.1 x86_64 Enterprise FIPS: ami-038b56cef84d98b26 +# ap-northeast-3 v17.1.1 arm64 Enterprise FIPS: ami-0fd3ed262911c079f +# ap-northeast-3 v17.1.1 x86_64 Enterprise FIPS: ami-03a2e04456b18a65c +# ap-south-1 v17.1.1 arm64 Enterprise FIPS: ami-04e4f5f215d81edc9 +# ap-south-1 v17.1.1 x86_64 Enterprise FIPS: ami-0e81f5bd20bfab210 +# ap-southeast-1 v17.1.1 arm64 Enterprise FIPS: ami-0d6848c15b2f79a03 +# ap-southeast-1 v17.1.1 x86_64 Enterprise FIPS: ami-08c4fc849b2afcc78 +# ap-southeast-2 v17.1.1 arm64 Enterprise FIPS: ami-0321bc57c7f7201b0 +# ap-southeast-2 v17.1.1 x86_64 Enterprise FIPS: ami-058396ee5c2e518bf +# ca-central-1 v17.1.1 arm64 Enterprise FIPS: ami-09f7184cefdb18036 +# ca-central-1 v17.1.1 x86_64 Enterprise FIPS: ami-0b85b4c0019b40956 +# eu-central-1 v17.1.1 arm64 Enterprise FIPS: ami-08fbb4c1ed81bd076 +# eu-central-1 v17.1.1 x86_64 Enterprise FIPS: ami-00c06e92f6992d8c3 +# eu-north-1 v17.1.1 arm64 Enterprise FIPS: ami-0845f4478f4cd5f63 +# eu-north-1 v17.1.1 x86_64 Enterprise FIPS: ami-0b50b04da77f287b1 +# eu-west-1 v17.1.1 arm64 Enterprise FIPS: ami-0689ff77e620de869 +# eu-west-1 v17.1.1 x86_64 Enterprise FIPS: ami-05aa391af6f47f95a +# eu-west-2 v17.1.1 arm64 Enterprise FIPS: ami-090188b6f91c6d7e4 +# eu-west-2 v17.1.1 x86_64 Enterprise FIPS: ami-0c9534dcfaa711757 +# eu-west-3 v17.1.1 arm64 Enterprise FIPS: ami-00af1fadbb5b101cb +# eu-west-3 v17.1.1 x86_64 Enterprise FIPS: ami-0b0a4d9ead7ec1cf1 +# sa-east-1 v17.1.1 arm64 Enterprise FIPS: ami-018f08eeb957e774f +# sa-east-1 v17.1.1 x86_64 Enterprise FIPS: ami-0c4b47928c2097626 +# us-east-1 v17.1.1 arm64 Enterprise FIPS: ami-0fd7d05acc2acee36 +# us-east-1 v17.1.1 x86_64 Enterprise FIPS: ami-0e2c9f308efa1ede7 +# us-east-2 v17.1.1 arm64 Enterprise FIPS: ami-0d6114bd3d19c6291 +# us-east-2 v17.1.1 x86_64 Enterprise FIPS: ami-0463c72427ad61436 +# us-west-1 v17.1.1 arm64 Enterprise FIPS: ami-0c550565b653d7cd6 +# us-west-1 v17.1.1 x86_64 Enterprise FIPS: ami-051ee46c716df5ef6 +# us-west-2 v17.1.1 arm64 Enterprise FIPS: ami-04c32562ed8325ff7 +# us-west-2 v17.1.1 x86_64 Enterprise FIPS: ami-0825c6a88c93a01c7 ``` diff --git a/examples/aws/terraform/ha-autoscale-cluster/README.md b/examples/aws/terraform/ha-autoscale-cluster/README.md index 808929be0b49c..579c13a201c1a 100644 --- a/examples/aws/terraform/ha-autoscale-cluster/README.md +++ b/examples/aws/terraform/ha-autoscale-cluster/README.md @@ -46,7 +46,7 @@ export TF_VAR_cluster_name="teleport.example.com" # OSS: aws ec2 describe-images --owners 146628656107 --filters 'Name=name,Values=teleport-oss-*' # Enterprise: aws ec2 describe-images --owners 146628656107 --filters 'Name=name,Values=teleport-ent-*' # FIPS 140-2 images are also available for Enterprise customers, look for '-fips' on the end of the AMI's name -export TF_VAR_ami_name="teleport-ent-17.0.4-arm64" +export TF_VAR_ami_name="teleport-ent-17.1.1-arm64" # Instance types used for authentication server auto scaling group # This should match to the AMI instance architecture type, ARM or x86 diff --git a/examples/aws/terraform/starter-cluster/README.md b/examples/aws/terraform/starter-cluster/README.md index 0fa8984f8c78b..a7461d5981d86 100644 --- a/examples/aws/terraform/starter-cluster/README.md +++ b/examples/aws/terraform/starter-cluster/README.md @@ -98,7 +98,7 @@ TF_VAR_license_path ?= "/path/to/license" # OSS: aws ec2 describe-images --owners 146628656107 --filters 'Name=name,Values=teleport-oss-*' # Enterprise: aws ec2 describe-images --owners 146628656107 --filters 'Name=name,Values=teleport-ent-*' # FIPS 140-2 images are also available for Enterprise customers, look for '-fips' on the end of the AMI's name -TF_VAR_ami_name ?= "teleport-ent-17.0.4-arm64" +TF_VAR_ami_name ?= "teleport-ent-17.1.1-arm64" # Route 53 hosted zone to use, must be a root zone registered in AWS, e.g. example.com TF_VAR_route53_zone ?= "example.com" diff --git a/examples/chart/tbot/.lint/full.yaml b/examples/chart/tbot/.lint/full.yaml index 706b3d207ee5c..9d076782b506e 100644 --- a/examples/chart/tbot/.lint/full.yaml +++ b/examples/chart/tbot/.lint/full.yaml @@ -1,7 +1,7 @@ clusterName: "test.teleport.sh" teleportAuthAddress: "my-auth:3024" defaultOutput: - enabled: false + enabled: true token: "my-token" joinMethod: "modified-join-method" diff --git a/examples/chart/tbot/templates/_config.tpl b/examples/chart/tbot/templates/_config.tpl index 344b25e403fc5..d8c9aff491862 100644 --- a/examples/chart/tbot/templates/_config.tpl +++ b/examples/chart/tbot/templates/_config.tpl @@ -40,10 +40,10 @@ outputs: name: {{ include "tbot.defaultOutputName" . }} {{- end }} {{- if .Values.outputs }} -{{- toYaml .Values.outputs | nindent 6}} +{{- toYaml .Values.outputs | nindent 2}} {{- end }} {{- end }} {{- if .Values.services }} -services: {{- toYaml .Values.services | nindent 6}} +services: {{- toYaml .Values.services | nindent 2}} {{- end }} {{- end -}} diff --git a/examples/chart/tbot/tests/__snapshot__/config_test.yaml.snap b/examples/chart/tbot/tests/__snapshot__/config_test.yaml.snap index a1e58c58b4643..2dbfb81532f58 100644 --- a/examples/chart/tbot/tests/__snapshot__/config_test.yaml.snap +++ b/examples/chart/tbot/tests/__snapshot__/config_test.yaml.snap @@ -35,6 +35,10 @@ should match the snapshot (full): join_method: modified-join-method token: my-token outputs: + - destination: + name: RELEASE-NAME-tbot-out + type: kubernetes_secret + type: identity - app_name: foo destination: path: /bar diff --git a/examples/chart/tbot/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/tbot/tests/__snapshot__/deployment_test.yaml.snap index d615816877e63..1c2be632d2ae5 100644 --- a/examples/chart/tbot/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/tbot/tests/__snapshot__/deployment_test.yaml.snap @@ -22,7 +22,7 @@ should match the snapshot (full): template: metadata: annotations: - checksum/config: 094cdbfc4e4fe3824a33426d8eea4e9e8a4b2711823d4fbb4102e11caa7f62c0 + checksum/config: 010d3421120a26bed12d1b9df8443e0eeafa362e88bd830e4a81688d13689483 test-key: test-annotation-pod labels: app.kubernetes.io/component: tbot diff --git a/examples/desktop-registration/go.mod b/examples/desktop-registration/go.mod index 5da92f124caa3..6ad053d7b9e9e 100644 --- a/examples/desktop-registration/go.mod +++ b/examples/desktop-registration/go.mod @@ -38,7 +38,7 @@ require ( go.opentelemetry.io/proto/otlp v1.1.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb // indirect - golang.org/x/net v0.24.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect diff --git a/examples/desktop-registration/go.sum b/examples/desktop-registration/go.sum index 0b5eedd0047a0..d42087e9f4793 100644 --- a/examples/desktop-registration/go.sum +++ b/examples/desktop-registration/go.sum @@ -185,8 +185,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= diff --git a/examples/go-client/go.mod b/examples/go-client/go.mod index f843df505b16b..6cb0bf4510302 100644 --- a/examples/go-client/go.mod +++ b/examples/go-client/go.mod @@ -40,7 +40,7 @@ require ( go.opentelemetry.io/proto/otlp v1.1.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb // indirect - golang.org/x/net v0.24.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect diff --git a/examples/go-client/go.sum b/examples/go-client/go.sum index 0b5eedd0047a0..d42087e9f4793 100644 --- a/examples/go-client/go.sum +++ b/examples/go-client/go.sum @@ -185,8 +185,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= diff --git a/examples/resources/admin.yaml b/examples/resources/admin.yaml index 2c8427a632a4a..acb170f290e18 100644 --- a/examples/resources/admin.yaml +++ b/examples/resources/admin.yaml @@ -28,5 +28,9 @@ spec: - network forward_agent: true max_session_ttl: 30h0m0s - port_forwarding: true + ssh_port_forwarding: + remote: + enabled: true + local: + enabled: true version: v3 diff --git a/examples/resources/user.yaml b/examples/resources/user.yaml index 07ab839b3286a..8f47afd3fc0e9 100644 --- a/examples/resources/user.yaml +++ b/examples/resources/user.yaml @@ -56,5 +56,9 @@ spec: - network forward_agent: true max_session_ttl: 30h0m0s - port_forwarding: true + ssh_port_forwarding: + remote: + enabled: true + local: + enabled: true version: v3 diff --git a/examples/service-discovery-api-client/go.mod b/examples/service-discovery-api-client/go.mod index 5cfe290b880fb..004b46d0f3fe3 100644 --- a/examples/service-discovery-api-client/go.mod +++ b/examples/service-discovery-api-client/go.mod @@ -56,7 +56,7 @@ require ( golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb // indirect golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.25.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect diff --git a/examples/service-discovery-api-client/go.sum b/examples/service-discovery-api-client/go.sum index 2511aa0d9ec69..55c047d98343e 100644 --- a/examples/service-discovery-api-client/go.sum +++ b/examples/service-discovery-api-client/go.sum @@ -214,8 +214,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= diff --git a/gen/proto/go/accessgraph/v1alpha/access_graph_service.pb.go b/gen/proto/go/accessgraph/v1alpha/access_graph_service.pb.go index 73e0be8af1866..9b727ca3a2a0c 100644 --- a/gen/proto/go/accessgraph/v1alpha/access_graph_service.pb.go +++ b/gen/proto/go/accessgraph/v1alpha/access_graph_service.pb.go @@ -1554,6 +1554,183 @@ func (*AzureEventsStreamResponse) Descriptor() ([]byte, []int) { return file_accessgraph_v1alpha_access_graph_service_proto_rawDescGZIP(), []int{23} } +// NetIQEventsStreamRequest is a request to send commands to the NetIQ importer +type NetIQEventsStreamRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Operation: + // + // *NetIQEventsStreamRequest_Sync + // *NetIQEventsStreamRequest_Upsert + // *NetIQEventsStreamRequest_Delete + Operation isNetIQEventsStreamRequest_Operation `protobuf_oneof:"operation"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetIQEventsStreamRequest) Reset() { + *x = NetIQEventsStreamRequest{} + mi := &file_accessgraph_v1alpha_access_graph_service_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetIQEventsStreamRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetIQEventsStreamRequest) ProtoMessage() {} + +func (x *NetIQEventsStreamRequest) ProtoReflect() protoreflect.Message { + mi := &file_accessgraph_v1alpha_access_graph_service_proto_msgTypes[24] + 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 NetIQEventsStreamRequest.ProtoReflect.Descriptor instead. +func (*NetIQEventsStreamRequest) Descriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_access_graph_service_proto_rawDescGZIP(), []int{24} +} + +func (x *NetIQEventsStreamRequest) GetOperation() isNetIQEventsStreamRequest_Operation { + if x != nil { + return x.Operation + } + return nil +} + +func (x *NetIQEventsStreamRequest) GetSync() *NetIQSyncOperation { + if x != nil { + if x, ok := x.Operation.(*NetIQEventsStreamRequest_Sync); ok { + return x.Sync + } + } + return nil +} + +func (x *NetIQEventsStreamRequest) GetUpsert() *NetIQResourceList { + if x != nil { + if x, ok := x.Operation.(*NetIQEventsStreamRequest_Upsert); ok { + return x.Upsert + } + } + return nil +} + +func (x *NetIQEventsStreamRequest) GetDelete() *NetIQResourceList { + if x != nil { + if x, ok := x.Operation.(*NetIQEventsStreamRequest_Delete); ok { + return x.Delete + } + } + return nil +} + +type isNetIQEventsStreamRequest_Operation interface { + isNetIQEventsStreamRequest_Operation() +} + +type NetIQEventsStreamRequest_Sync struct { + // sync is a command to sync the access graph with the NetIQ state. + Sync *NetIQSyncOperation `protobuf:"bytes,1,opt,name=sync,proto3,oneof"` +} + +type NetIQEventsStreamRequest_Upsert struct { + // upsert is a command to put a resource into the access graph or update it. + Upsert *NetIQResourceList `protobuf:"bytes,2,opt,name=upsert,proto3,oneof"` +} + +type NetIQEventsStreamRequest_Delete struct { + // delete is a command to delete a resource from the access graph when it's deleted. + Delete *NetIQResourceList `protobuf:"bytes,3,opt,name=delete,proto3,oneof"` +} + +func (*NetIQEventsStreamRequest_Sync) isNetIQEventsStreamRequest_Operation() {} + +func (*NetIQEventsStreamRequest_Upsert) isNetIQEventsStreamRequest_Operation() {} + +func (*NetIQEventsStreamRequest_Delete) isNetIQEventsStreamRequest_Operation() {} + +// NetIQSyncOperation is a command that Teleport sends to the access graph service +// at the end of the sync process. +type NetIQSyncOperation struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetIQSyncOperation) Reset() { + *x = NetIQSyncOperation{} + mi := &file_accessgraph_v1alpha_access_graph_service_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetIQSyncOperation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetIQSyncOperation) ProtoMessage() {} + +func (x *NetIQSyncOperation) ProtoReflect() protoreflect.Message { + mi := &file_accessgraph_v1alpha_access_graph_service_proto_msgTypes[25] + 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 NetIQSyncOperation.ProtoReflect.Descriptor instead. +func (*NetIQSyncOperation) Descriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_access_graph_service_proto_rawDescGZIP(), []int{25} +} + +// NetIQEventsStreamResponse is a response from NetIQEventsStream +type NetIQEventsStreamResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetIQEventsStreamResponse) Reset() { + *x = NetIQEventsStreamResponse{} + mi := &file_accessgraph_v1alpha_access_graph_service_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetIQEventsStreamResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetIQEventsStreamResponse) ProtoMessage() {} + +func (x *NetIQEventsStreamResponse) ProtoReflect() protoreflect.Message { + mi := &file_accessgraph_v1alpha_access_graph_service_proto_msgTypes[26] + 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 NetIQEventsStreamResponse.ProtoReflect.Descriptor instead. +func (*NetIQEventsStreamResponse) Descriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_access_graph_service_proto_rawDescGZIP(), []int{26} +} + var File_accessgraph_v1alpha_access_graph_service_proto protoreflect.FileDescriptor var file_accessgraph_v1alpha_access_graph_service_proto_rawDesc = []byte{ @@ -1573,25 +1750,54 @@ var file_accessgraph_v1alpha_access_graph_service_proto_rawDesc = []byte{ 0x67, 0x72, 0x61, 0x70, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2f, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2f, - 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x23, 0x61, 0x63, 0x63, + 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x22, 0x24, 0x0a, 0x0c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x71, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, - 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x6f, 0x64, - 0x65, 0x52, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x05, 0x65, 0x64, 0x67, 0x65, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, 0x64, - 0x67, 0x65, 0x52, 0x05, 0x65, 0x64, 0x67, 0x65, 0x73, 0x22, 0x2c, 0x0a, 0x0e, 0x47, 0x65, 0x74, - 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x66, - 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, - 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x22, 0x25, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x46, 0x69, - 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xaa, - 0x03, 0x0a, 0x13, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, + 0x2f, 0x6e, 0x65, 0x74, 0x69, 0x71, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x23, 0x61, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0x24, 0x0a, 0x0c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x71, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x05, 0x6e, 0x6f, 0x64, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x6f, + 0x64, 0x65, 0x52, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x05, 0x65, 0x64, 0x67, + 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, + 0x64, 0x67, 0x65, 0x52, 0x05, 0x65, 0x64, 0x67, 0x65, 0x73, 0x22, 0x2c, 0x0a, 0x0e, 0x47, 0x65, + 0x74, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x22, 0x25, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x46, + 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, + 0xaa, 0x03, 0x0a, 0x13, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, + 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x79, 0x6e, 0x63, + 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x04, 0x73, 0x79, 0x6e, + 0x63, 0x12, 0x3b, 0x0a, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x12, 0x41, + 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, + 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x12, 0x5b, 0x0a, 0x14, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, + 0x73, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x27, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, + 0x73, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x12, 0x61, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x6f, + 0x0a, 0x1b, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, + 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x4d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x41, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x42, + 0x0b, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xac, 0x03, 0x0a, + 0x15, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x56, 0x32, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x4f, @@ -1617,202 +1823,201 @@ var file_accessgraph_v1alpha_access_graph_service_proto_rawDesc = []byte{ 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x42, 0x0b, - 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xac, 0x03, 0x0a, 0x15, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x56, 0x32, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, - 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x4f, 0x70, + 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x0f, 0x0a, 0x0d, 0x53, + 0x79, 0x6e, 0x63, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x16, 0x0a, 0x14, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5b, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x56, 0x32, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, + 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x41, 0x75, 0x64, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x22, 0x6f, 0x0a, 0x0a, 0x41, 0x75, 0x64, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x58, 0x0a, 0x13, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x48, 0x00, 0x52, 0x11, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x50, 0x61, + 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x22, 0x54, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, + 0x5f, 0x70, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x68, 0x6f, 0x73, 0x74, + 0x43, 0x61, 0x50, 0x65, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x33, 0x0a, 0x11, + 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x41, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x5f, 0x70, 0x65, 0x6d, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x50, 0x65, + 0x6d, 0x22, 0x14, 0x0a, 0x12, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x41, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe2, 0x01, 0x0a, 0x16, 0x41, 0x57, 0x53, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x25, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x57, 0x53, 0x53, 0x79, 0x6e, 0x63, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x12, - 0x3b, 0x0a, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x21, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x69, + 0x3e, 0x0a, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x24, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x57, 0x53, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x12, + 0x3e, 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x24, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x57, 0x53, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, + 0x0b, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x12, 0x0a, 0x10, + 0x41, 0x57, 0x53, 0x53, 0x79, 0x6e, 0x63, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x19, 0x0a, 0x17, 0x41, 0x57, 0x53, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xee, 0x01, 0x0a, 0x19, + 0x47, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3e, 0x0a, 0x04, 0x73, 0x79, 0x6e, + 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x69, + 0x74, 0x6c, 0x61, 0x62, 0x53, 0x79, 0x6e, 0x63, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x48, 0x00, 0x52, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x41, 0x0a, 0x06, 0x75, 0x70, 0x73, + 0x65, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x61, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, + 0x47, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x12, 0x41, 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, - 0x5b, 0x0a, 0x14, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x73, 0x5f, - 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, - 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x4d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x12, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x4c, 0x69, 0x73, 0x74, 0x73, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x6f, 0x0a, 0x1b, - 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, - 0x69, 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x2e, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x41, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, - 0x73, 0x48, 0x00, 0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x41, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x42, 0x0b, 0x0a, - 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x79, - 0x6e, 0x63, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x16, 0x0a, 0x14, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x5b, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x56, 0x32, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, - 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x41, 0x75, 0x64, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, - 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x22, 0x6f, 0x0a, 0x0a, 0x41, 0x75, 0x64, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x58, - 0x0a, 0x13, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x64, 0x48, 0x00, 0x52, 0x11, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x50, 0x61, 0x74, - 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x22, 0x54, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x5f, - 0x70, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x43, - 0x61, 0x50, 0x65, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, - 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x52, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x33, 0x0a, 0x11, 0x52, - 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x41, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1e, 0x0a, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x5f, 0x70, 0x65, 0x6d, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x50, 0x65, 0x6d, - 0x22, 0x14, 0x0a, 0x12, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x41, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe2, 0x01, 0x0a, 0x16, 0x41, 0x57, 0x53, 0x45, 0x76, + 0x68, 0x61, 0x2e, 0x47, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, + 0x0b, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1c, 0x0a, 0x1a, + 0x47, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xea, 0x01, 0x0a, 0x18, 0x45, + 0x6e, 0x74, 0x72, 0x61, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, + 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, + 0x61, 0x53, 0x79, 0x6e, 0x63, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, + 0x52, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x40, 0x0a, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, + 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, 0x6e, 0x74, + 0x72, 0x61, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, + 0x52, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, + 0x6e, 0x74, 0x72, 0x61, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, + 0x48, 0x00, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1b, 0x0a, 0x19, 0x45, 0x6e, 0x74, 0x72, 0x61, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xea, 0x01, 0x0a, 0x18, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x3b, 0x0a, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x25, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x57, 0x53, 0x53, 0x79, 0x6e, 0x63, 0x4f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x3e, - 0x0a, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, - 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x57, 0x53, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x12, 0x3e, - 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, - 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x57, 0x53, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x0b, - 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x12, 0x0a, 0x10, 0x41, - 0x57, 0x53, 0x53, 0x79, 0x6e, 0x63, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, - 0x19, 0x0a, 0x17, 0x41, 0x57, 0x53, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xee, 0x01, 0x0a, 0x19, 0x47, - 0x69, 0x74, 0x6c, 0x61, 0x62, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3e, 0x0a, 0x04, 0x73, 0x79, 0x6e, 0x63, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, - 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x69, 0x74, - 0x6c, 0x61, 0x62, 0x53, 0x79, 0x6e, 0x63, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x48, 0x00, 0x52, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x41, 0x0a, 0x06, 0x75, 0x70, 0x73, 0x65, - 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, - 0x69, 0x74, 0x6c, 0x61, 0x62, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x69, 0x73, - 0x74, 0x48, 0x00, 0x52, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x12, 0x41, 0x0a, 0x06, 0x64, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x61, 0x63, + 0x74, 0x12, 0x3d, 0x0a, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x27, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x04, 0x73, 0x79, 0x6e, 0x63, + 0x12, 0x40, 0x0a, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x26, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x75, 0x70, 0x73, 0x65, + 0x72, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x64, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x22, 0x14, 0x0a, 0x12, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x4f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1b, 0x0a, 0x19, 0x41, 0x7a, 0x75, 0x72, 0x65, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xea, 0x01, 0x0a, 0x18, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x3d, 0x0a, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x27, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x53, 0x79, 0x6e, 0x63, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x04, 0x73, 0x79, 0x6e, 0x63, + 0x12, 0x40, 0x0a, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x26, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x75, 0x70, 0x73, 0x65, + 0x72, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x64, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x22, 0x14, 0x0a, 0x12, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x53, 0x79, 0x6e, 0x63, 0x4f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1b, 0x0a, 0x19, 0x4e, 0x65, 0x74, 0x49, 0x51, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x9b, 0x09, 0x0a, 0x12, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x47, + 0x72, 0x61, 0x70, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4e, 0x0a, 0x05, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x12, 0x21, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, + 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x07, 0x47, + 0x65, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x23, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, + 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, + 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x2e, 0x47, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x0b, - 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1c, 0x0a, 0x1a, 0x47, - 0x69, 0x74, 0x6c, 0x61, 0x62, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xea, 0x01, 0x0a, 0x18, 0x45, 0x6e, - 0x74, 0x72, 0x61, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, - 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x61, - 0x53, 0x79, 0x6e, 0x63, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, - 0x04, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x40, 0x0a, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, - 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, - 0x61, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, - 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, 0x6e, - 0x74, 0x72, 0x61, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, - 0x00, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1b, 0x0a, 0x19, 0x45, 0x6e, 0x74, 0x72, 0x61, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0xea, 0x01, 0x0a, 0x18, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x3d, 0x0a, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, - 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x4f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x12, - 0x40, 0x0a, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x26, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x75, 0x70, 0x73, 0x65, 0x72, - 0x74, 0x12, 0x40, 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x26, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x64, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x22, 0x14, 0x0a, 0x12, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x4f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1b, 0x0a, 0x19, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x32, 0xa3, 0x08, 0x0a, 0x12, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x47, 0x72, - 0x61, 0x70, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4e, 0x0a, 0x05, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x12, 0x21, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, - 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, - 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x07, 0x47, 0x65, - 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x23, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, - 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x46, - 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x61, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x2e, 0x47, 0x65, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x65, 0x0a, 0x0c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x12, 0x28, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x61, 0x63, 0x63, + 0x61, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x65, 0x0a, 0x0c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x12, 0x28, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x61, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, 0x6d, 0x0a, 0x0e, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x56, 0x32, 0x12, 0x2a, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, 0x6d, 0x0a, 0x0e, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x56, 0x32, 0x12, 0x2a, 0x2e, 0x61, 0x63, 0x63, 0x65, + 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x56, 0x32, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, + 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x56, 0x32, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x57, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x12, 0x24, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, + 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x56, 0x32, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, - 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x56, 0x32, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x57, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x65, 0x72, 0x12, 0x24, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, - 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x5d, 0x0a, 0x0a, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x41, 0x73, 0x12, 0x26, 0x2e, - 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x41, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, - 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x70, 0x6c, - 0x61, 0x63, 0x65, 0x43, 0x41, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, - 0x0a, 0x0f, 0x41, 0x57, 0x53, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x12, 0x2b, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x57, 0x53, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x5d, 0x0a, 0x0a, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x41, 0x73, 0x12, 0x26, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x57, 0x53, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, 0x79, - 0x0a, 0x12, 0x47, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x12, 0x2e, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, - 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x69, 0x74, 0x6c, 0x61, - 0x62, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, - 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x69, 0x74, 0x6c, 0x61, - 0x62, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x76, 0x0a, 0x11, 0x45, 0x6e, 0x74, - 0x72, 0x61, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x2d, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x41, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, + 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x70, + 0x6c, 0x61, 0x63, 0x65, 0x43, 0x41, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x6e, 0x0a, 0x0f, 0x41, 0x57, 0x53, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x12, 0x2b, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x57, 0x53, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2c, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x57, 0x53, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, + 0x79, 0x0a, 0x12, 0x47, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x2e, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, + 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x69, 0x74, 0x6c, + 0x61, 0x62, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, + 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x69, 0x74, 0x6c, + 0x61, 0x62, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x76, 0x0a, 0x11, 0x45, 0x6e, + 0x74, 0x72, 0x61, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, + 0x2d, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x61, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x61, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, - 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x61, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, - 0x01, 0x12, 0x76, 0x0a, 0x11, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x2d, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, + 0x30, 0x01, 0x12, 0x76, 0x0a, 0x11, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x2d, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x7a, + 0x75, 0x72, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, - 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x7a, 0x75, 0x72, - 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x57, 0x5a, 0x55, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x67, - 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x61, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x3b, - 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x76, 0x0a, 0x11, 0x4e, 0x65, + 0x74, 0x49, 0x51, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, + 0x2d, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, + 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, + 0x30, 0x01, 0x42, 0x57, 0x5a, 0x55, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, + 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x3b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, + 0x72, 0x61, 0x70, 0x68, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -1827,7 +2032,7 @@ func file_accessgraph_v1alpha_access_graph_service_proto_rawDescGZIP() []byte { return file_accessgraph_v1alpha_access_graph_service_proto_rawDescData } -var file_accessgraph_v1alpha_access_graph_service_proto_msgTypes = make([]protoimpl.MessageInfo, 24) +var file_accessgraph_v1alpha_access_graph_service_proto_msgTypes = make([]protoimpl.MessageInfo, 27) var file_accessgraph_v1alpha_access_graph_service_proto_goTypes = []any{ (*QueryRequest)(nil), // 0: accessgraph.v1alpha.QueryRequest (*QueryResponse)(nil), // 1: accessgraph.v1alpha.QueryResponse @@ -1853,72 +2058,81 @@ var file_accessgraph_v1alpha_access_graph_service_proto_goTypes = []any{ (*AzureEventsStreamRequest)(nil), // 21: accessgraph.v1alpha.AzureEventsStreamRequest (*AzureSyncOperation)(nil), // 22: accessgraph.v1alpha.AzureSyncOperation (*AzureEventsStreamResponse)(nil), // 23: accessgraph.v1alpha.AzureEventsStreamResponse - (*Node)(nil), // 24: accessgraph.v1alpha.Node - (*Edge)(nil), // 25: accessgraph.v1alpha.Edge - (*ResourceList)(nil), // 26: accessgraph.v1alpha.ResourceList - (*ResourceHeaderList)(nil), // 27: accessgraph.v1alpha.ResourceHeaderList - (*AccessListsMembers)(nil), // 28: accessgraph.v1alpha.AccessListsMembers - (*ExcludeAccessListsMembers)(nil), // 29: accessgraph.v1alpha.ExcludeAccessListsMembers - (*AccessPathChanged)(nil), // 30: accessgraph.v1alpha.AccessPathChanged - (*AWSResourceList)(nil), // 31: accessgraph.v1alpha.AWSResourceList - (*GitlabSyncOperation)(nil), // 32: accessgraph.v1alpha.GitlabSyncOperation - (*GitlabResourceList)(nil), // 33: accessgraph.v1alpha.GitlabResourceList - (*EntraSyncOperation)(nil), // 34: accessgraph.v1alpha.EntraSyncOperation - (*EntraResourceList)(nil), // 35: accessgraph.v1alpha.EntraResourceList - (*AzureResourceList)(nil), // 36: accessgraph.v1alpha.AzureResourceList + (*NetIQEventsStreamRequest)(nil), // 24: accessgraph.v1alpha.NetIQEventsStreamRequest + (*NetIQSyncOperation)(nil), // 25: accessgraph.v1alpha.NetIQSyncOperation + (*NetIQEventsStreamResponse)(nil), // 26: accessgraph.v1alpha.NetIQEventsStreamResponse + (*Node)(nil), // 27: accessgraph.v1alpha.Node + (*Edge)(nil), // 28: accessgraph.v1alpha.Edge + (*ResourceList)(nil), // 29: accessgraph.v1alpha.ResourceList + (*ResourceHeaderList)(nil), // 30: accessgraph.v1alpha.ResourceHeaderList + (*AccessListsMembers)(nil), // 31: accessgraph.v1alpha.AccessListsMembers + (*ExcludeAccessListsMembers)(nil), // 32: accessgraph.v1alpha.ExcludeAccessListsMembers + (*AccessPathChanged)(nil), // 33: accessgraph.v1alpha.AccessPathChanged + (*AWSResourceList)(nil), // 34: accessgraph.v1alpha.AWSResourceList + (*GitlabSyncOperation)(nil), // 35: accessgraph.v1alpha.GitlabSyncOperation + (*GitlabResourceList)(nil), // 36: accessgraph.v1alpha.GitlabResourceList + (*EntraSyncOperation)(nil), // 37: accessgraph.v1alpha.EntraSyncOperation + (*EntraResourceList)(nil), // 38: accessgraph.v1alpha.EntraResourceList + (*AzureResourceList)(nil), // 39: accessgraph.v1alpha.AzureResourceList + (*NetIQResourceList)(nil), // 40: accessgraph.v1alpha.NetIQResourceList } var file_accessgraph_v1alpha_access_graph_service_proto_depIdxs = []int32{ - 24, // 0: accessgraph.v1alpha.QueryResponse.nodes:type_name -> accessgraph.v1alpha.Node - 25, // 1: accessgraph.v1alpha.QueryResponse.edges:type_name -> accessgraph.v1alpha.Edge + 27, // 0: accessgraph.v1alpha.QueryResponse.nodes:type_name -> accessgraph.v1alpha.Node + 28, // 1: accessgraph.v1alpha.QueryResponse.edges:type_name -> accessgraph.v1alpha.Edge 6, // 2: accessgraph.v1alpha.EventsStreamRequest.sync:type_name -> accessgraph.v1alpha.SyncOperation - 26, // 3: accessgraph.v1alpha.EventsStreamRequest.upsert:type_name -> accessgraph.v1alpha.ResourceList - 27, // 4: accessgraph.v1alpha.EventsStreamRequest.delete:type_name -> accessgraph.v1alpha.ResourceHeaderList - 28, // 5: accessgraph.v1alpha.EventsStreamRequest.access_lists_members:type_name -> accessgraph.v1alpha.AccessListsMembers - 29, // 6: accessgraph.v1alpha.EventsStreamRequest.exclude_access_list_members:type_name -> accessgraph.v1alpha.ExcludeAccessListsMembers + 29, // 3: accessgraph.v1alpha.EventsStreamRequest.upsert:type_name -> accessgraph.v1alpha.ResourceList + 30, // 4: accessgraph.v1alpha.EventsStreamRequest.delete:type_name -> accessgraph.v1alpha.ResourceHeaderList + 31, // 5: accessgraph.v1alpha.EventsStreamRequest.access_lists_members:type_name -> accessgraph.v1alpha.AccessListsMembers + 32, // 6: accessgraph.v1alpha.EventsStreamRequest.exclude_access_list_members:type_name -> accessgraph.v1alpha.ExcludeAccessListsMembers 6, // 7: accessgraph.v1alpha.EventsStreamV2Request.sync:type_name -> accessgraph.v1alpha.SyncOperation - 26, // 8: accessgraph.v1alpha.EventsStreamV2Request.upsert:type_name -> accessgraph.v1alpha.ResourceList - 27, // 9: accessgraph.v1alpha.EventsStreamV2Request.delete:type_name -> accessgraph.v1alpha.ResourceHeaderList - 28, // 10: accessgraph.v1alpha.EventsStreamV2Request.access_lists_members:type_name -> accessgraph.v1alpha.AccessListsMembers - 29, // 11: accessgraph.v1alpha.EventsStreamV2Request.exclude_access_list_members:type_name -> accessgraph.v1alpha.ExcludeAccessListsMembers + 29, // 8: accessgraph.v1alpha.EventsStreamV2Request.upsert:type_name -> accessgraph.v1alpha.ResourceList + 30, // 9: accessgraph.v1alpha.EventsStreamV2Request.delete:type_name -> accessgraph.v1alpha.ResourceHeaderList + 31, // 10: accessgraph.v1alpha.EventsStreamV2Request.access_lists_members:type_name -> accessgraph.v1alpha.AccessListsMembers + 32, // 11: accessgraph.v1alpha.EventsStreamV2Request.exclude_access_list_members:type_name -> accessgraph.v1alpha.ExcludeAccessListsMembers 9, // 12: accessgraph.v1alpha.EventsStreamV2Response.event:type_name -> accessgraph.v1alpha.AuditEvent - 30, // 13: accessgraph.v1alpha.AuditEvent.access_path_changed:type_name -> accessgraph.v1alpha.AccessPathChanged + 33, // 13: accessgraph.v1alpha.AuditEvent.access_path_changed:type_name -> accessgraph.v1alpha.AccessPathChanged 15, // 14: accessgraph.v1alpha.AWSEventsStreamRequest.sync:type_name -> accessgraph.v1alpha.AWSSyncOperation - 31, // 15: accessgraph.v1alpha.AWSEventsStreamRequest.upsert:type_name -> accessgraph.v1alpha.AWSResourceList - 31, // 16: accessgraph.v1alpha.AWSEventsStreamRequest.delete:type_name -> accessgraph.v1alpha.AWSResourceList - 32, // 17: accessgraph.v1alpha.GitlabEventsStreamRequest.sync:type_name -> accessgraph.v1alpha.GitlabSyncOperation - 33, // 18: accessgraph.v1alpha.GitlabEventsStreamRequest.upsert:type_name -> accessgraph.v1alpha.GitlabResourceList - 33, // 19: accessgraph.v1alpha.GitlabEventsStreamRequest.delete:type_name -> accessgraph.v1alpha.GitlabResourceList - 34, // 20: accessgraph.v1alpha.EntraEventsStreamRequest.sync:type_name -> accessgraph.v1alpha.EntraSyncOperation - 35, // 21: accessgraph.v1alpha.EntraEventsStreamRequest.upsert:type_name -> accessgraph.v1alpha.EntraResourceList - 35, // 22: accessgraph.v1alpha.EntraEventsStreamRequest.delete:type_name -> accessgraph.v1alpha.EntraResourceList + 34, // 15: accessgraph.v1alpha.AWSEventsStreamRequest.upsert:type_name -> accessgraph.v1alpha.AWSResourceList + 34, // 16: accessgraph.v1alpha.AWSEventsStreamRequest.delete:type_name -> accessgraph.v1alpha.AWSResourceList + 35, // 17: accessgraph.v1alpha.GitlabEventsStreamRequest.sync:type_name -> accessgraph.v1alpha.GitlabSyncOperation + 36, // 18: accessgraph.v1alpha.GitlabEventsStreamRequest.upsert:type_name -> accessgraph.v1alpha.GitlabResourceList + 36, // 19: accessgraph.v1alpha.GitlabEventsStreamRequest.delete:type_name -> accessgraph.v1alpha.GitlabResourceList + 37, // 20: accessgraph.v1alpha.EntraEventsStreamRequest.sync:type_name -> accessgraph.v1alpha.EntraSyncOperation + 38, // 21: accessgraph.v1alpha.EntraEventsStreamRequest.upsert:type_name -> accessgraph.v1alpha.EntraResourceList + 38, // 22: accessgraph.v1alpha.EntraEventsStreamRequest.delete:type_name -> accessgraph.v1alpha.EntraResourceList 22, // 23: accessgraph.v1alpha.AzureEventsStreamRequest.sync:type_name -> accessgraph.v1alpha.AzureSyncOperation - 36, // 24: accessgraph.v1alpha.AzureEventsStreamRequest.upsert:type_name -> accessgraph.v1alpha.AzureResourceList - 36, // 25: accessgraph.v1alpha.AzureEventsStreamRequest.delete:type_name -> accessgraph.v1alpha.AzureResourceList - 0, // 26: accessgraph.v1alpha.AccessGraphService.Query:input_type -> accessgraph.v1alpha.QueryRequest - 2, // 27: accessgraph.v1alpha.AccessGraphService.GetFile:input_type -> accessgraph.v1alpha.GetFileRequest - 4, // 28: accessgraph.v1alpha.AccessGraphService.EventsStream:input_type -> accessgraph.v1alpha.EventsStreamRequest - 5, // 29: accessgraph.v1alpha.AccessGraphService.EventsStreamV2:input_type -> accessgraph.v1alpha.EventsStreamV2Request - 10, // 30: accessgraph.v1alpha.AccessGraphService.Register:input_type -> accessgraph.v1alpha.RegisterRequest - 12, // 31: accessgraph.v1alpha.AccessGraphService.ReplaceCAs:input_type -> accessgraph.v1alpha.ReplaceCAsRequest - 14, // 32: accessgraph.v1alpha.AccessGraphService.AWSEventsStream:input_type -> accessgraph.v1alpha.AWSEventsStreamRequest - 17, // 33: accessgraph.v1alpha.AccessGraphService.GitlabEventsStream:input_type -> accessgraph.v1alpha.GitlabEventsStreamRequest - 19, // 34: accessgraph.v1alpha.AccessGraphService.EntraEventsStream:input_type -> accessgraph.v1alpha.EntraEventsStreamRequest - 21, // 35: accessgraph.v1alpha.AccessGraphService.AzureEventsStream:input_type -> accessgraph.v1alpha.AzureEventsStreamRequest - 1, // 36: accessgraph.v1alpha.AccessGraphService.Query:output_type -> accessgraph.v1alpha.QueryResponse - 3, // 37: accessgraph.v1alpha.AccessGraphService.GetFile:output_type -> accessgraph.v1alpha.GetFileResponse - 7, // 38: accessgraph.v1alpha.AccessGraphService.EventsStream:output_type -> accessgraph.v1alpha.EventsStreamResponse - 8, // 39: accessgraph.v1alpha.AccessGraphService.EventsStreamV2:output_type -> accessgraph.v1alpha.EventsStreamV2Response - 11, // 40: accessgraph.v1alpha.AccessGraphService.Register:output_type -> accessgraph.v1alpha.RegisterResponse - 13, // 41: accessgraph.v1alpha.AccessGraphService.ReplaceCAs:output_type -> accessgraph.v1alpha.ReplaceCAsResponse - 16, // 42: accessgraph.v1alpha.AccessGraphService.AWSEventsStream:output_type -> accessgraph.v1alpha.AWSEventsStreamResponse - 18, // 43: accessgraph.v1alpha.AccessGraphService.GitlabEventsStream:output_type -> accessgraph.v1alpha.GitlabEventsStreamResponse - 20, // 44: accessgraph.v1alpha.AccessGraphService.EntraEventsStream:output_type -> accessgraph.v1alpha.EntraEventsStreamResponse - 23, // 45: accessgraph.v1alpha.AccessGraphService.AzureEventsStream:output_type -> accessgraph.v1alpha.AzureEventsStreamResponse - 36, // [36:46] is the sub-list for method output_type - 26, // [26:36] is the sub-list for method input_type - 26, // [26:26] is the sub-list for extension type_name - 26, // [26:26] is the sub-list for extension extendee - 0, // [0:26] is the sub-list for field type_name + 39, // 24: accessgraph.v1alpha.AzureEventsStreamRequest.upsert:type_name -> accessgraph.v1alpha.AzureResourceList + 39, // 25: accessgraph.v1alpha.AzureEventsStreamRequest.delete:type_name -> accessgraph.v1alpha.AzureResourceList + 25, // 26: accessgraph.v1alpha.NetIQEventsStreamRequest.sync:type_name -> accessgraph.v1alpha.NetIQSyncOperation + 40, // 27: accessgraph.v1alpha.NetIQEventsStreamRequest.upsert:type_name -> accessgraph.v1alpha.NetIQResourceList + 40, // 28: accessgraph.v1alpha.NetIQEventsStreamRequest.delete:type_name -> accessgraph.v1alpha.NetIQResourceList + 0, // 29: accessgraph.v1alpha.AccessGraphService.Query:input_type -> accessgraph.v1alpha.QueryRequest + 2, // 30: accessgraph.v1alpha.AccessGraphService.GetFile:input_type -> accessgraph.v1alpha.GetFileRequest + 4, // 31: accessgraph.v1alpha.AccessGraphService.EventsStream:input_type -> accessgraph.v1alpha.EventsStreamRequest + 5, // 32: accessgraph.v1alpha.AccessGraphService.EventsStreamV2:input_type -> accessgraph.v1alpha.EventsStreamV2Request + 10, // 33: accessgraph.v1alpha.AccessGraphService.Register:input_type -> accessgraph.v1alpha.RegisterRequest + 12, // 34: accessgraph.v1alpha.AccessGraphService.ReplaceCAs:input_type -> accessgraph.v1alpha.ReplaceCAsRequest + 14, // 35: accessgraph.v1alpha.AccessGraphService.AWSEventsStream:input_type -> accessgraph.v1alpha.AWSEventsStreamRequest + 17, // 36: accessgraph.v1alpha.AccessGraphService.GitlabEventsStream:input_type -> accessgraph.v1alpha.GitlabEventsStreamRequest + 19, // 37: accessgraph.v1alpha.AccessGraphService.EntraEventsStream:input_type -> accessgraph.v1alpha.EntraEventsStreamRequest + 21, // 38: accessgraph.v1alpha.AccessGraphService.AzureEventsStream:input_type -> accessgraph.v1alpha.AzureEventsStreamRequest + 24, // 39: accessgraph.v1alpha.AccessGraphService.NetIQEventsStream:input_type -> accessgraph.v1alpha.NetIQEventsStreamRequest + 1, // 40: accessgraph.v1alpha.AccessGraphService.Query:output_type -> accessgraph.v1alpha.QueryResponse + 3, // 41: accessgraph.v1alpha.AccessGraphService.GetFile:output_type -> accessgraph.v1alpha.GetFileResponse + 7, // 42: accessgraph.v1alpha.AccessGraphService.EventsStream:output_type -> accessgraph.v1alpha.EventsStreamResponse + 8, // 43: accessgraph.v1alpha.AccessGraphService.EventsStreamV2:output_type -> accessgraph.v1alpha.EventsStreamV2Response + 11, // 44: accessgraph.v1alpha.AccessGraphService.Register:output_type -> accessgraph.v1alpha.RegisterResponse + 13, // 45: accessgraph.v1alpha.AccessGraphService.ReplaceCAs:output_type -> accessgraph.v1alpha.ReplaceCAsResponse + 16, // 46: accessgraph.v1alpha.AccessGraphService.AWSEventsStream:output_type -> accessgraph.v1alpha.AWSEventsStreamResponse + 18, // 47: accessgraph.v1alpha.AccessGraphService.GitlabEventsStream:output_type -> accessgraph.v1alpha.GitlabEventsStreamResponse + 20, // 48: accessgraph.v1alpha.AccessGraphService.EntraEventsStream:output_type -> accessgraph.v1alpha.EntraEventsStreamResponse + 23, // 49: accessgraph.v1alpha.AccessGraphService.AzureEventsStream:output_type -> accessgraph.v1alpha.AzureEventsStreamResponse + 26, // 50: accessgraph.v1alpha.AccessGraphService.NetIQEventsStream:output_type -> accessgraph.v1alpha.NetIQEventsStreamResponse + 40, // [40:51] is the sub-list for method output_type + 29, // [29:40] is the sub-list for method input_type + 29, // [29:29] is the sub-list for extension type_name + 29, // [29:29] is the sub-list for extension extendee + 0, // [0:29] is the sub-list for field type_name } func init() { file_accessgraph_v1alpha_access_graph_service_proto_init() } @@ -1932,6 +2146,7 @@ func file_accessgraph_v1alpha_access_graph_service_proto_init() { file_accessgraph_v1alpha_events_proto_init() file_accessgraph_v1alpha_gitlab_proto_init() file_accessgraph_v1alpha_graph_proto_init() + file_accessgraph_v1alpha_netiq_proto_init() file_accessgraph_v1alpha_resources_proto_init() file_accessgraph_v1alpha_access_graph_service_proto_msgTypes[4].OneofWrappers = []any{ (*EventsStreamRequest_Sync)(nil), @@ -1973,13 +2188,18 @@ func file_accessgraph_v1alpha_access_graph_service_proto_init() { (*AzureEventsStreamRequest_Upsert)(nil), (*AzureEventsStreamRequest_Delete)(nil), } + file_accessgraph_v1alpha_access_graph_service_proto_msgTypes[24].OneofWrappers = []any{ + (*NetIQEventsStreamRequest_Sync)(nil), + (*NetIQEventsStreamRequest_Upsert)(nil), + (*NetIQEventsStreamRequest_Delete)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_accessgraph_v1alpha_access_graph_service_proto_rawDesc, NumEnums: 0, - NumMessages: 24, + NumMessages: 27, NumExtensions: 0, NumServices: 1, }, diff --git a/gen/proto/go/accessgraph/v1alpha/access_graph_service_grpc.pb.go b/gen/proto/go/accessgraph/v1alpha/access_graph_service_grpc.pb.go index 7bc14a2bb03e0..db84b95c72f89 100644 --- a/gen/proto/go/accessgraph/v1alpha/access_graph_service_grpc.pb.go +++ b/gen/proto/go/accessgraph/v1alpha/access_graph_service_grpc.pb.go @@ -46,6 +46,7 @@ const ( AccessGraphService_GitlabEventsStream_FullMethodName = "/accessgraph.v1alpha.AccessGraphService/GitlabEventsStream" AccessGraphService_EntraEventsStream_FullMethodName = "/accessgraph.v1alpha.AccessGraphService/EntraEventsStream" AccessGraphService_AzureEventsStream_FullMethodName = "/accessgraph.v1alpha.AccessGraphService/AzureEventsStream" + AccessGraphService_NetIQEventsStream_FullMethodName = "/accessgraph.v1alpha.AccessGraphService/NetIQEventsStream" ) // AccessGraphServiceClient is the client API for AccessGraphService service. @@ -95,6 +96,8 @@ type AccessGraphServiceClient interface { EntraEventsStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[EntraEventsStreamRequest, EntraEventsStreamResponse], error) // AzureEventsStream is a stream of commands to the Azure importer AzureEventsStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[AzureEventsStreamRequest, AzureEventsStreamResponse], error) + // NetIQEventsStream is a stream of commands to the NetIQ importer. + NetIQEventsStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[NetIQEventsStreamRequest, NetIQEventsStreamResponse], error) } type accessGraphServiceClient struct { @@ -223,6 +226,19 @@ func (c *accessGraphServiceClient) AzureEventsStream(ctx context.Context, opts . // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. type AccessGraphService_AzureEventsStreamClient = grpc.BidiStreamingClient[AzureEventsStreamRequest, AzureEventsStreamResponse] +func (c *accessGraphServiceClient) NetIQEventsStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[NetIQEventsStreamRequest, NetIQEventsStreamResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &AccessGraphService_ServiceDesc.Streams[6], AccessGraphService_NetIQEventsStream_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[NetIQEventsStreamRequest, NetIQEventsStreamResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type AccessGraphService_NetIQEventsStreamClient = grpc.BidiStreamingClient[NetIQEventsStreamRequest, NetIQEventsStreamResponse] + // AccessGraphServiceServer is the server API for AccessGraphService service. // All implementations must embed UnimplementedAccessGraphServiceServer // for forward compatibility. @@ -270,6 +286,8 @@ type AccessGraphServiceServer interface { EntraEventsStream(grpc.BidiStreamingServer[EntraEventsStreamRequest, EntraEventsStreamResponse]) error // AzureEventsStream is a stream of commands to the Azure importer AzureEventsStream(grpc.BidiStreamingServer[AzureEventsStreamRequest, AzureEventsStreamResponse]) error + // NetIQEventsStream is a stream of commands to the NetIQ importer. + NetIQEventsStream(grpc.BidiStreamingServer[NetIQEventsStreamRequest, NetIQEventsStreamResponse]) error mustEmbedUnimplementedAccessGraphServiceServer() } @@ -310,6 +328,9 @@ func (UnimplementedAccessGraphServiceServer) EntraEventsStream(grpc.BidiStreamin func (UnimplementedAccessGraphServiceServer) AzureEventsStream(grpc.BidiStreamingServer[AzureEventsStreamRequest, AzureEventsStreamResponse]) error { return status.Errorf(codes.Unimplemented, "method AzureEventsStream not implemented") } +func (UnimplementedAccessGraphServiceServer) NetIQEventsStream(grpc.BidiStreamingServer[NetIQEventsStreamRequest, NetIQEventsStreamResponse]) error { + return status.Errorf(codes.Unimplemented, "method NetIQEventsStream not implemented") +} func (UnimplementedAccessGraphServiceServer) mustEmbedUnimplementedAccessGraphServiceServer() {} func (UnimplementedAccessGraphServiceServer) testEmbeddedByValue() {} @@ -445,6 +466,13 @@ func _AccessGraphService_AzureEventsStream_Handler(srv interface{}, stream grpc. // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. type AccessGraphService_AzureEventsStreamServer = grpc.BidiStreamingServer[AzureEventsStreamRequest, AzureEventsStreamResponse] +func _AccessGraphService_NetIQEventsStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(AccessGraphServiceServer).NetIQEventsStream(&grpc.GenericServerStream[NetIQEventsStreamRequest, NetIQEventsStreamResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type AccessGraphService_NetIQEventsStreamServer = grpc.BidiStreamingServer[NetIQEventsStreamRequest, NetIQEventsStreamResponse] + // AccessGraphService_ServiceDesc is the grpc.ServiceDesc for AccessGraphService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -504,6 +532,12 @@ var AccessGraphService_ServiceDesc = grpc.ServiceDesc{ ServerStreams: true, ClientStreams: true, }, + { + StreamName: "NetIQEventsStream", + Handler: _AccessGraphService_NetIQEventsStream_Handler, + ServerStreams: true, + ClientStreams: true, + }, }, Metadata: "accessgraph/v1alpha/access_graph_service.proto", } diff --git a/gen/proto/go/accessgraph/v1alpha/azure.pb.go b/gen/proto/go/accessgraph/v1alpha/azure.pb.go index 399c97bd464e2..e2f78bee99f2a 100644 --- a/gen/proto/go/accessgraph/v1alpha/azure.pb.go +++ b/gen/proto/go/accessgraph/v1alpha/azure.pb.go @@ -684,7 +684,7 @@ func (x *AzureRoleDefinition) GetType() string { return "" } -// AzurePermission defines the actions and not (disallowed) actions for a role definition +// AzureRBACPermission defines the actions and not (disallowed) actions for a role definition type AzureRBACPermission struct { state protoimpl.MessageState `protogen:"open.v1"` // actions define the resources and verbs allowed on the resources diff --git a/gen/proto/go/accessgraph/v1alpha/netiq.pb.go b/gen/proto/go/accessgraph/v1alpha/netiq.pb.go new file mode 100644 index 0000000000000..2d86d8a0b4f94 --- /dev/null +++ b/gen/proto/go/accessgraph/v1alpha/netiq.pb.go @@ -0,0 +1,1328 @@ +// +// 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 . + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.0 +// protoc (unknown) +// source: accessgraph/v1alpha/netiq.proto + +package accessgraphv1alpha + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// RoleRecipientType is the type of the recipient. +type RoleRecipientType int32 + +const ( + // ROLE_RECIPIENT_TYPE_UNSPECIFIED is a unspecified role recipient type. + RoleRecipientType_ROLE_RECIPIENT_TYPE_UNSPECIFIED RoleRecipientType = 0 + // ROLE_RECIPIENT_TYPE_USER represents a user being member of a role. + RoleRecipientType_ROLE_RECIPIENT_TYPE_USER RoleRecipientType = 1 + // ROLE_RECIPIENT_TYPE_GROUP represents a group being member of a role. + RoleRecipientType_ROLE_RECIPIENT_TYPE_GROUP RoleRecipientType = 2 +) + +// Enum value maps for RoleRecipientType. +var ( + RoleRecipientType_name = map[int32]string{ + 0: "ROLE_RECIPIENT_TYPE_UNSPECIFIED", + 1: "ROLE_RECIPIENT_TYPE_USER", + 2: "ROLE_RECIPIENT_TYPE_GROUP", + } + RoleRecipientType_value = map[string]int32{ + "ROLE_RECIPIENT_TYPE_UNSPECIFIED": 0, + "ROLE_RECIPIENT_TYPE_USER": 1, + "ROLE_RECIPIENT_TYPE_GROUP": 2, + } +) + +func (x RoleRecipientType) Enum() *RoleRecipientType { + p := new(RoleRecipientType) + *p = x + return p +} + +func (x RoleRecipientType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (RoleRecipientType) Descriptor() protoreflect.EnumDescriptor { + return file_accessgraph_v1alpha_netiq_proto_enumTypes[0].Descriptor() +} + +func (RoleRecipientType) Type() protoreflect.EnumType { + return &file_accessgraph_v1alpha_netiq_proto_enumTypes[0] +} + +func (x RoleRecipientType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use RoleRecipientType.Descriptor instead. +func (RoleRecipientType) EnumDescriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_netiq_proto_rawDescGZIP(), []int{0} +} + +// NetIQResourceList is a request that contains resources to be sync. +type NetIQResourceList struct { + state protoimpl.MessageState `protogen:"open.v1"` + // resources is a list of NetIQ resources to sync. + Resources []*NetIQObject `protobuf:"bytes,1,rep,name=resources,proto3" json:"resources,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetIQResourceList) Reset() { + *x = NetIQResourceList{} + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetIQResourceList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetIQResourceList) ProtoMessage() {} + +func (x *NetIQResourceList) ProtoReflect() protoreflect.Message { + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[0] + 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 NetIQResourceList.ProtoReflect.Descriptor instead. +func (*NetIQResourceList) Descriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_netiq_proto_rawDescGZIP(), []int{0} +} + +func (x *NetIQResourceList) GetResources() []*NetIQObject { + if x != nil { + return x.Resources + } + return nil +} + +// NetIQObject represents a NetIQ resource +type NetIQObject struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Object: + // + // *NetIQObject_Group + // *NetIQObject_GroupMember + // *NetIQObject_Resource + // *NetIQObject_Role + // *NetIQObject_ParentRoleRef + // *NetIQObject_User + // *NetIQObject_ResourceRoleRef + // *NetIQObject_RoleMemberRef + Object isNetIQObject_Object `protobuf_oneof:"object"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetIQObject) Reset() { + *x = NetIQObject{} + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetIQObject) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetIQObject) ProtoMessage() {} + +func (x *NetIQObject) ProtoReflect() protoreflect.Message { + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[1] + 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 NetIQObject.ProtoReflect.Descriptor instead. +func (*NetIQObject) Descriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_netiq_proto_rawDescGZIP(), []int{1} +} + +func (x *NetIQObject) GetObject() isNetIQObject_Object { + if x != nil { + return x.Object + } + return nil +} + +func (x *NetIQObject) GetGroup() *NetIQGroup { + if x != nil { + if x, ok := x.Object.(*NetIQObject_Group); ok { + return x.Group + } + } + return nil +} + +func (x *NetIQObject) GetGroupMember() *NetIQGroupMember { + if x != nil { + if x, ok := x.Object.(*NetIQObject_GroupMember); ok { + return x.GroupMember + } + } + return nil +} + +func (x *NetIQObject) GetResource() *NetIQResource { + if x != nil { + if x, ok := x.Object.(*NetIQObject_Resource); ok { + return x.Resource + } + } + return nil +} + +func (x *NetIQObject) GetRole() *NetIQRole { + if x != nil { + if x, ok := x.Object.(*NetIQObject_Role); ok { + return x.Role + } + } + return nil +} + +func (x *NetIQObject) GetParentRoleRef() *NetIQRoleRef { + if x != nil { + if x, ok := x.Object.(*NetIQObject_ParentRoleRef); ok { + return x.ParentRoleRef + } + } + return nil +} + +func (x *NetIQObject) GetUser() *NetIQUser { + if x != nil { + if x, ok := x.Object.(*NetIQObject_User); ok { + return x.User + } + } + return nil +} + +func (x *NetIQObject) GetResourceRoleRef() *NetIQResourceAssignmentRef { + if x != nil { + if x, ok := x.Object.(*NetIQObject_ResourceRoleRef); ok { + return x.ResourceRoleRef + } + } + return nil +} + +func (x *NetIQObject) GetRoleMemberRef() *NetIQMemberAssignmentRef { + if x != nil { + if x, ok := x.Object.(*NetIQObject_RoleMemberRef); ok { + return x.RoleMemberRef + } + } + return nil +} + +type isNetIQObject_Object interface { + isNetIQObject_Object() +} + +type NetIQObject_Group struct { + // group represents a NetIQ group in an organization. + Group *NetIQGroup `protobuf:"bytes,1,opt,name=group,proto3,oneof"` +} + +type NetIQObject_GroupMember struct { + // group_member represents a NetIQ group member. + GroupMember *NetIQGroupMember `protobuf:"bytes,2,opt,name=group_member,json=groupMember,proto3,oneof"` +} + +type NetIQObject_Resource struct { + // resource represents a NetIQ resource. + Resource *NetIQResource `protobuf:"bytes,3,opt,name=resource,proto3,oneof"` +} + +type NetIQObject_Role struct { + // role represents a role with certain access levels to a resource. + Role *NetIQRole `protobuf:"bytes,4,opt,name=role,proto3,oneof"` +} + +type NetIQObject_ParentRoleRef struct { + // parent_role_ref represents a parent relationship between roles. + ParentRoleRef *NetIQRoleRef `protobuf:"bytes,5,opt,name=parent_role_ref,json=parentRoleRef,proto3,oneof"` +} + +type NetIQObject_User struct { + // user represents a NetIQ user. + User *NetIQUser `protobuf:"bytes,6,opt,name=user,proto3,oneof"` +} + +type NetIQObject_ResourceRoleRef struct { + // resource_role_ref represents a resource assignment to a role. + ResourceRoleRef *NetIQResourceAssignmentRef `protobuf:"bytes,7,opt,name=resource_role_ref,json=resourceRoleRef,proto3,oneof"` +} + +type NetIQObject_RoleMemberRef struct { + // role_member_ref represents a member being member of a role. + RoleMemberRef *NetIQMemberAssignmentRef `protobuf:"bytes,8,opt,name=role_member_ref,json=roleMemberRef,proto3,oneof"` +} + +func (*NetIQObject_Group) isNetIQObject_Object() {} + +func (*NetIQObject_GroupMember) isNetIQObject_Object() {} + +func (*NetIQObject_Resource) isNetIQObject_Object() {} + +func (*NetIQObject_Role) isNetIQObject_Object() {} + +func (*NetIQObject_ParentRoleRef) isNetIQObject_Object() {} + +func (*NetIQObject_User) isNetIQObject_Object() {} + +func (*NetIQObject_ResourceRoleRef) isNetIQObject_Object() {} + +func (*NetIQObject_RoleMemberRef) isNetIQObject_Object() {} + +// NetIQGroup represents a NetIQ group +type NetIQGroup struct { + state protoimpl.MessageState `protogen:"open.v1"` + // name is the group name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // id is the universal identifier for the group. + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + // description is the group description. + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetIQGroup) Reset() { + *x = NetIQGroup{} + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetIQGroup) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetIQGroup) ProtoMessage() {} + +func (x *NetIQGroup) ProtoReflect() protoreflect.Message { + mi := &file_accessgraph_v1alpha_netiq_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) +} + +// Deprecated: Use NetIQGroup.ProtoReflect.Descriptor instead. +func (*NetIQGroup) Descriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_netiq_proto_rawDescGZIP(), []int{2} +} + +func (x *NetIQGroup) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *NetIQGroup) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *NetIQGroup) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +// NetIQGroupMember represents a NetIQ group member +type NetIQGroupMember struct { + state protoimpl.MessageState `protogen:"open.v1"` + // group_id is the group id. + GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + // user_id is the universal identifier for the user. + UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + // is_group_assignment is a flag that determines whether the member is a group assignment. + IsGroupAssignment bool `protobuf:"varint,3,opt,name=is_group_assignment,json=isGroupAssignment,proto3" json:"is_group_assignment,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetIQGroupMember) Reset() { + *x = NetIQGroupMember{} + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetIQGroupMember) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetIQGroupMember) ProtoMessage() {} + +func (x *NetIQGroupMember) ProtoReflect() protoreflect.Message { + mi := &file_accessgraph_v1alpha_netiq_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) +} + +// Deprecated: Use NetIQGroupMember.ProtoReflect.Descriptor instead. +func (*NetIQGroupMember) Descriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_netiq_proto_rawDescGZIP(), []int{3} +} + +func (x *NetIQGroupMember) GetGroupId() string { + if x != nil { + return x.GroupId + } + return "" +} + +func (x *NetIQGroupMember) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *NetIQGroupMember) GetIsGroupAssignment() bool { + if x != nil { + return x.IsGroupAssignment + } + return false +} + +// NetIQResource represents a NetIQ resource +type NetIQResource struct { + state protoimpl.MessageState `protogen:"open.v1"` + // name is the resource name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // id is the universal identifier for the resource. + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + // description is the project description. + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + // categories is the list of categories the resource belongs to. + Categories []*NetIQCategory `protobuf:"bytes,4,rep,name=categories,proto3" json:"categories,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetIQResource) Reset() { + *x = NetIQResource{} + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetIQResource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetIQResource) ProtoMessage() {} + +func (x *NetIQResource) ProtoReflect() protoreflect.Message { + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[4] + 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 NetIQResource.ProtoReflect.Descriptor instead. +func (*NetIQResource) Descriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_netiq_proto_rawDescGZIP(), []int{4} +} + +func (x *NetIQResource) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *NetIQResource) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *NetIQResource) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *NetIQResource) GetCategories() []*NetIQCategory { + if x != nil { + return x.Categories + } + return nil +} + +// NetIQCategory is a resource category. +type NetIQCategory struct { + state protoimpl.MessageState `protogen:"open.v1"` + // name is the resource name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // id is the universal identifier for the category. + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetIQCategory) Reset() { + *x = NetIQCategory{} + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetIQCategory) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetIQCategory) ProtoMessage() {} + +func (x *NetIQCategory) ProtoReflect() protoreflect.Message { + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[5] + 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 NetIQCategory.ProtoReflect.Descriptor instead. +func (*NetIQCategory) Descriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_netiq_proto_rawDescGZIP(), []int{5} +} + +func (x *NetIQCategory) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *NetIQCategory) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +// NetIQRole represents a NetIQ role +type NetIQRole struct { + state protoimpl.MessageState `protogen:"open.v1"` + // name is the resource name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // id is the universal identifier for the role. + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + // description is the project description. + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + // categories is the list of categories the resource belongs to. + Categories []*NetIQCategory `protobuf:"bytes,4,rep,name=categories,proto3" json:"categories,omitempty"` + Level *NetIQRole_RoleLevel `protobuf:"bytes,5,opt,name=level,proto3" json:"level,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetIQRole) Reset() { + *x = NetIQRole{} + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetIQRole) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetIQRole) ProtoMessage() {} + +func (x *NetIQRole) ProtoReflect() protoreflect.Message { + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[6] + 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 NetIQRole.ProtoReflect.Descriptor instead. +func (*NetIQRole) Descriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_netiq_proto_rawDescGZIP(), []int{6} +} + +func (x *NetIQRole) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *NetIQRole) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *NetIQRole) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *NetIQRole) GetCategories() []*NetIQCategory { + if x != nil { + return x.Categories + } + return nil +} + +func (x *NetIQRole) GetLevel() *NetIQRole_RoleLevel { + if x != nil { + return x.Level + } + return nil +} + +// NetIQRoleRef represents a NetIQ Role reference. +type NetIQRoleRef struct { + state protoimpl.MessageState `protogen:"open.v1"` + // child_role_id is the group id of a role that is a child role of parent_role_id. + ChildRoleId string `protobuf:"bytes,1,opt,name=child_role_id,json=childRoleId,proto3" json:"child_role_id,omitempty"` + // parent_role_id is the universal identifier for the role that is parent to child_role_id. + ParentRoleId string `protobuf:"bytes,2,opt,name=parent_role_id,json=parentRoleId,proto3" json:"parent_role_id,omitempty"` + // level is the level of the role. + Level int32 `protobuf:"varint,3,opt,name=level,proto3" json:"level,omitempty"` + // request_description is the description of the request. + RequestDescription string `protobuf:"bytes,4,opt,name=request_description,json=requestDescription,proto3" json:"request_description,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetIQRoleRef) Reset() { + *x = NetIQRoleRef{} + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetIQRoleRef) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetIQRoleRef) ProtoMessage() {} + +func (x *NetIQRoleRef) ProtoReflect() protoreflect.Message { + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[7] + 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 NetIQRoleRef.ProtoReflect.Descriptor instead. +func (*NetIQRoleRef) Descriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_netiq_proto_rawDescGZIP(), []int{7} +} + +func (x *NetIQRoleRef) GetChildRoleId() string { + if x != nil { + return x.ChildRoleId + } + return "" +} + +func (x *NetIQRoleRef) GetParentRoleId() string { + if x != nil { + return x.ParentRoleId + } + return "" +} + +func (x *NetIQRoleRef) GetLevel() int32 { + if x != nil { + return x.Level + } + return 0 +} + +func (x *NetIQRoleRef) GetRequestDescription() string { + if x != nil { + return x.RequestDescription + } + return "" +} + +// NetIQUser represents a NetIQ user. +type NetIQUser struct { + state protoimpl.MessageState `protogen:"open.v1"` + // id is the id of the user. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // email is the user's email. + Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` + // name is the user's name. + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + // is_disabled indicates if a user is disabled. + IsDisabled bool `protobuf:"varint,4,opt,name=is_disabled,json=isDisabled,proto3" json:"is_disabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetIQUser) Reset() { + *x = NetIQUser{} + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetIQUser) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetIQUser) ProtoMessage() {} + +func (x *NetIQUser) ProtoReflect() protoreflect.Message { + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[8] + 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 NetIQUser.ProtoReflect.Descriptor instead. +func (*NetIQUser) Descriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_netiq_proto_rawDescGZIP(), []int{8} +} + +func (x *NetIQUser) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *NetIQUser) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *NetIQUser) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *NetIQUser) GetIsDisabled() bool { + if x != nil { + return x.IsDisabled + } + return false +} + +// NetIQResourceAssignmentRef represents a NetIQ resource assignment reference. +type NetIQResourceAssignmentRef struct { + state protoimpl.MessageState `protogen:"open.v1"` + // role_id is the group id of a role that is assigned to resource_id. + RoleId string `protobuf:"bytes,1,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty"` + // resource_id is the universal identifier for the resource that is assigned to role_id. + ResourceId string `protobuf:"bytes,2,opt,name=resource_id,json=resourceId,proto3" json:"resource_id,omitempty"` + // mapping_description is the description of the mapping. + MappingDescription string `protobuf:"bytes,4,opt,name=mapping_description,json=mappingDescription,proto3" json:"mapping_description,omitempty"` + // status_code is the status code of the role assignment. + StatusCode uint32 `protobuf:"varint,5,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetIQResourceAssignmentRef) Reset() { + *x = NetIQResourceAssignmentRef{} + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetIQResourceAssignmentRef) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetIQResourceAssignmentRef) ProtoMessage() {} + +func (x *NetIQResourceAssignmentRef) ProtoReflect() protoreflect.Message { + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[9] + 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 NetIQResourceAssignmentRef.ProtoReflect.Descriptor instead. +func (*NetIQResourceAssignmentRef) Descriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_netiq_proto_rawDescGZIP(), []int{9} +} + +func (x *NetIQResourceAssignmentRef) GetRoleId() string { + if x != nil { + return x.RoleId + } + return "" +} + +func (x *NetIQResourceAssignmentRef) GetResourceId() string { + if x != nil { + return x.ResourceId + } + return "" +} + +func (x *NetIQResourceAssignmentRef) GetMappingDescription() string { + if x != nil { + return x.MappingDescription + } + return "" +} + +func (x *NetIQResourceAssignmentRef) GetStatusCode() uint32 { + if x != nil { + return x.StatusCode + } + return 0 +} + +// NetIQMemberAssignmentRef represents a NetIQ resource assignment reference. +type NetIQMemberAssignmentRef struct { + state protoimpl.MessageState `protogen:"open.v1"` + // role_id is the group id of a role that user_id is member of. + RoleId string `protobuf:"bytes,1,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty"` + // dn is the universal identifier for the user that is member of role_id. + Dn string `protobuf:"bytes,2,opt,name=dn,proto3" json:"dn,omitempty"` + // recipient_type identifies the recipient provenance. + RecipientType RoleRecipientType `protobuf:"varint,3,opt,name=recipient_type,json=recipientType,proto3,enum=accessgraph.v1alpha.RoleRecipientType" json:"recipient_type,omitempty"` + // recipient_type_subcontainer is the sub container of the recipient type. + RecipientTypeSubcontainer string `protobuf:"bytes,4,opt,name=recipient_type_subcontainer,json=recipientTypeSubcontainer,proto3" json:"recipient_type_subcontainer,omitempty"` + // status_code is the status code of the role assignment. + StatusCode uint32 `protobuf:"varint,5,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"` + // StatusDstatus_displayisplay is the display of the status. + StatusDisplay string `protobuf:"bytes,6,opt,name=status_display,json=statusDisplay,proto3" json:"status_display,omitempty"` + // effective_date is the effective date of the role assignment. + EffectiveDate *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=effective_date,json=effectiveDate,proto3" json:"effective_date,omitempty"` + // expiry_date is the expiry date of the role assignment. + ExpiryDate *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=expiry_date,json=expiryDate,proto3" json:"expiry_date,omitempty"` + // description is the description of the role assignment. + Description string `protobuf:"bytes,9,opt,name=description,proto3" json:"description,omitempty"` + // grant is a flag that determines whether the role assignment is granted. + Grant bool `protobuf:"varint,10,opt,name=grant,proto3" json:"grant,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetIQMemberAssignmentRef) Reset() { + *x = NetIQMemberAssignmentRef{} + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetIQMemberAssignmentRef) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetIQMemberAssignmentRef) ProtoMessage() {} + +func (x *NetIQMemberAssignmentRef) ProtoReflect() protoreflect.Message { + mi := &file_accessgraph_v1alpha_netiq_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) +} + +// Deprecated: Use NetIQMemberAssignmentRef.ProtoReflect.Descriptor instead. +func (*NetIQMemberAssignmentRef) Descriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_netiq_proto_rawDescGZIP(), []int{10} +} + +func (x *NetIQMemberAssignmentRef) GetRoleId() string { + if x != nil { + return x.RoleId + } + return "" +} + +func (x *NetIQMemberAssignmentRef) GetDn() string { + if x != nil { + return x.Dn + } + return "" +} + +func (x *NetIQMemberAssignmentRef) GetRecipientType() RoleRecipientType { + if x != nil { + return x.RecipientType + } + return RoleRecipientType_ROLE_RECIPIENT_TYPE_UNSPECIFIED +} + +func (x *NetIQMemberAssignmentRef) GetRecipientTypeSubcontainer() string { + if x != nil { + return x.RecipientTypeSubcontainer + } + return "" +} + +func (x *NetIQMemberAssignmentRef) GetStatusCode() uint32 { + if x != nil { + return x.StatusCode + } + return 0 +} + +func (x *NetIQMemberAssignmentRef) GetStatusDisplay() string { + if x != nil { + return x.StatusDisplay + } + return "" +} + +func (x *NetIQMemberAssignmentRef) GetEffectiveDate() *timestamppb.Timestamp { + if x != nil { + return x.EffectiveDate + } + return nil +} + +func (x *NetIQMemberAssignmentRef) GetExpiryDate() *timestamppb.Timestamp { + if x != nil { + return x.ExpiryDate + } + return nil +} + +func (x *NetIQMemberAssignmentRef) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *NetIQMemberAssignmentRef) GetGrant() bool { + if x != nil { + return x.Grant + } + return false +} + +// RoleLevel represents the role level. +type NetIQRole_RoleLevel struct { + state protoimpl.MessageState `protogen:"open.v1"` + // name is the name of the role level. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // level is the level of the role level. + Level int32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"` + // cn is the common name. + Cn string `protobuf:"bytes,3,opt,name=cn,proto3" json:"cn,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetIQRole_RoleLevel) Reset() { + *x = NetIQRole_RoleLevel{} + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetIQRole_RoleLevel) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetIQRole_RoleLevel) ProtoMessage() {} + +func (x *NetIQRole_RoleLevel) ProtoReflect() protoreflect.Message { + mi := &file_accessgraph_v1alpha_netiq_proto_msgTypes[11] + 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 NetIQRole_RoleLevel.ProtoReflect.Descriptor instead. +func (*NetIQRole_RoleLevel) Descriptor() ([]byte, []int) { + return file_accessgraph_v1alpha_netiq_proto_rawDescGZIP(), []int{6, 0} +} + +func (x *NetIQRole_RoleLevel) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *NetIQRole_RoleLevel) GetLevel() int32 { + if x != nil { + return x.Level + } + return 0 +} + +func (x *NetIQRole_RoleLevel) GetCn() string { + if x != nil { + return x.Cn + } + return "" +} + +var File_accessgraph_v1alpha_netiq_proto protoreflect.FileDescriptor + +var file_accessgraph_v1alpha_netiq_proto_rawDesc = []byte{ + 0x0a, 0x1f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2f, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2f, 0x6e, 0x65, 0x74, 0x69, 0x71, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x13, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x53, 0x0a, 0x11, 0x4e, 0x65, 0x74, 0x49, 0x51, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3e, 0x0a, 0x09, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x20, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x22, 0xcf, 0x04, 0x0a, + 0x0b, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x37, 0x0a, 0x05, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x48, 0x00, 0x52, 0x05, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x4a, 0x0a, 0x0c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6d, + 0x65, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x61, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x6d, 0x62, 0x65, + 0x72, 0x12, 0x40, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, + 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x52, 0x6f, 0x6c, + 0x65, 0x48, 0x00, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x4b, 0x0a, 0x0f, 0x70, 0x61, 0x72, + 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x52, 0x6f, + 0x6c, 0x65, 0x52, 0x65, 0x66, 0x48, 0x00, 0x52, 0x0d, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x52, + 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x66, 0x12, 0x34, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, + 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x51, + 0x55, 0x73, 0x65, 0x72, 0x48, 0x00, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x5d, 0x0a, 0x11, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x72, 0x65, + 0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x65, + 0x74, 0x49, 0x51, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x73, 0x73, 0x69, 0x67, + 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x66, 0x48, 0x00, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x66, 0x12, 0x57, 0x0a, 0x0f, 0x72, + 0x6f, 0x6c, 0x65, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, + 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x51, + 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x66, 0x48, 0x00, 0x52, 0x0d, 0x72, 0x6f, 0x6c, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, + 0x72, 0x52, 0x65, 0x66, 0x42, 0x08, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x52, + 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x22, 0x76, 0x0a, 0x10, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x47, 0x72, 0x6f, 0x75, 0x70, + 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, + 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x69, 0x73, + 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x69, 0x73, 0x47, 0x72, 0x6f, 0x75, 0x70, + 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x99, 0x01, 0x0a, 0x0d, 0x4e, + 0x65, 0x74, 0x49, 0x51, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x42, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, + 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x65, 0x74, + 0x49, 0x51, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x0a, 0x63, 0x61, 0x74, 0x65, + 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0x33, 0x0a, 0x0d, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x43, + 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x9c, 0x02, 0x0a, 0x09, + 0x4e, 0x65, 0x74, 0x49, 0x51, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x20, 0x0a, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x42, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, + 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x43, + 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, + 0x69, 0x65, 0x73, 0x12, 0x3e, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x52, 0x6f, + 0x6c, 0x65, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x1a, 0x45, 0x0a, 0x09, 0x52, 0x6f, 0x6c, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x63, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x63, 0x6e, 0x22, 0x9f, 0x01, 0x0a, 0x0c, 0x4e, + 0x65, 0x74, 0x49, 0x51, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x66, 0x12, 0x22, 0x0a, 0x0d, 0x63, + 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x12, + 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x52, + 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x2f, 0x0a, 0x13, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x66, 0x0a, 0x09, + 0x4e, 0x65, 0x74, 0x49, 0x51, 0x55, 0x73, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, + 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x44, 0x69, 0x73, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x22, 0xa8, 0x01, 0x0a, 0x1a, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x66, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2f, 0x0a, + 0x13, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6d, 0x61, 0x70, 0x70, + 0x69, 0x6e, 0x67, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, + 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x22, + 0xd2, 0x03, 0x0a, 0x18, 0x4e, 0x65, 0x74, 0x49, 0x51, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x41, + 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x66, 0x12, 0x17, 0x0a, 0x07, + 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, + 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x64, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x64, 0x6e, 0x12, 0x4d, 0x0a, 0x0e, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, + 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, + 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0d, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x3e, 0x0a, 0x1b, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x73, 0x75, 0x62, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x72, 0x65, 0x63, 0x69, 0x70, + 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x53, 0x75, 0x62, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, + 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x12, 0x41, 0x0a, 0x0e, + 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x0d, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x44, 0x61, 0x74, 0x65, 0x12, + 0x3b, 0x0a, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x44, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, + 0x0a, 0x05, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x67, + 0x72, 0x61, 0x6e, 0x74, 0x2a, 0x75, 0x0a, 0x11, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x69, + 0x70, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x1f, 0x52, 0x4f, 0x4c, + 0x45, 0x5f, 0x52, 0x45, 0x43, 0x49, 0x50, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, + 0x0a, 0x18, 0x52, 0x4f, 0x4c, 0x45, 0x5f, 0x52, 0x45, 0x43, 0x49, 0x50, 0x49, 0x45, 0x4e, 0x54, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, + 0x52, 0x4f, 0x4c, 0x45, 0x5f, 0x52, 0x45, 0x43, 0x49, 0x50, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x47, 0x52, 0x4f, 0x55, 0x50, 0x10, 0x02, 0x42, 0x57, 0x5a, 0x55, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x61, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x3b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x67, 0x72, 0x61, 0x70, 0x68, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_accessgraph_v1alpha_netiq_proto_rawDescOnce sync.Once + file_accessgraph_v1alpha_netiq_proto_rawDescData = file_accessgraph_v1alpha_netiq_proto_rawDesc +) + +func file_accessgraph_v1alpha_netiq_proto_rawDescGZIP() []byte { + file_accessgraph_v1alpha_netiq_proto_rawDescOnce.Do(func() { + file_accessgraph_v1alpha_netiq_proto_rawDescData = protoimpl.X.CompressGZIP(file_accessgraph_v1alpha_netiq_proto_rawDescData) + }) + return file_accessgraph_v1alpha_netiq_proto_rawDescData +} + +var file_accessgraph_v1alpha_netiq_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_accessgraph_v1alpha_netiq_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_accessgraph_v1alpha_netiq_proto_goTypes = []any{ + (RoleRecipientType)(0), // 0: accessgraph.v1alpha.RoleRecipientType + (*NetIQResourceList)(nil), // 1: accessgraph.v1alpha.NetIQResourceList + (*NetIQObject)(nil), // 2: accessgraph.v1alpha.NetIQObject + (*NetIQGroup)(nil), // 3: accessgraph.v1alpha.NetIQGroup + (*NetIQGroupMember)(nil), // 4: accessgraph.v1alpha.NetIQGroupMember + (*NetIQResource)(nil), // 5: accessgraph.v1alpha.NetIQResource + (*NetIQCategory)(nil), // 6: accessgraph.v1alpha.NetIQCategory + (*NetIQRole)(nil), // 7: accessgraph.v1alpha.NetIQRole + (*NetIQRoleRef)(nil), // 8: accessgraph.v1alpha.NetIQRoleRef + (*NetIQUser)(nil), // 9: accessgraph.v1alpha.NetIQUser + (*NetIQResourceAssignmentRef)(nil), // 10: accessgraph.v1alpha.NetIQResourceAssignmentRef + (*NetIQMemberAssignmentRef)(nil), // 11: accessgraph.v1alpha.NetIQMemberAssignmentRef + (*NetIQRole_RoleLevel)(nil), // 12: accessgraph.v1alpha.NetIQRole.RoleLevel + (*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp +} +var file_accessgraph_v1alpha_netiq_proto_depIdxs = []int32{ + 2, // 0: accessgraph.v1alpha.NetIQResourceList.resources:type_name -> accessgraph.v1alpha.NetIQObject + 3, // 1: accessgraph.v1alpha.NetIQObject.group:type_name -> accessgraph.v1alpha.NetIQGroup + 4, // 2: accessgraph.v1alpha.NetIQObject.group_member:type_name -> accessgraph.v1alpha.NetIQGroupMember + 5, // 3: accessgraph.v1alpha.NetIQObject.resource:type_name -> accessgraph.v1alpha.NetIQResource + 7, // 4: accessgraph.v1alpha.NetIQObject.role:type_name -> accessgraph.v1alpha.NetIQRole + 8, // 5: accessgraph.v1alpha.NetIQObject.parent_role_ref:type_name -> accessgraph.v1alpha.NetIQRoleRef + 9, // 6: accessgraph.v1alpha.NetIQObject.user:type_name -> accessgraph.v1alpha.NetIQUser + 10, // 7: accessgraph.v1alpha.NetIQObject.resource_role_ref:type_name -> accessgraph.v1alpha.NetIQResourceAssignmentRef + 11, // 8: accessgraph.v1alpha.NetIQObject.role_member_ref:type_name -> accessgraph.v1alpha.NetIQMemberAssignmentRef + 6, // 9: accessgraph.v1alpha.NetIQResource.categories:type_name -> accessgraph.v1alpha.NetIQCategory + 6, // 10: accessgraph.v1alpha.NetIQRole.categories:type_name -> accessgraph.v1alpha.NetIQCategory + 12, // 11: accessgraph.v1alpha.NetIQRole.level:type_name -> accessgraph.v1alpha.NetIQRole.RoleLevel + 0, // 12: accessgraph.v1alpha.NetIQMemberAssignmentRef.recipient_type:type_name -> accessgraph.v1alpha.RoleRecipientType + 13, // 13: accessgraph.v1alpha.NetIQMemberAssignmentRef.effective_date:type_name -> google.protobuf.Timestamp + 13, // 14: accessgraph.v1alpha.NetIQMemberAssignmentRef.expiry_date:type_name -> google.protobuf.Timestamp + 15, // [15:15] is the sub-list for method output_type + 15, // [15:15] is the sub-list for method input_type + 15, // [15:15] is the sub-list for extension type_name + 15, // [15:15] is the sub-list for extension extendee + 0, // [0:15] is the sub-list for field type_name +} + +func init() { file_accessgraph_v1alpha_netiq_proto_init() } +func file_accessgraph_v1alpha_netiq_proto_init() { + if File_accessgraph_v1alpha_netiq_proto != nil { + return + } + file_accessgraph_v1alpha_netiq_proto_msgTypes[1].OneofWrappers = []any{ + (*NetIQObject_Group)(nil), + (*NetIQObject_GroupMember)(nil), + (*NetIQObject_Resource)(nil), + (*NetIQObject_Role)(nil), + (*NetIQObject_ParentRoleRef)(nil), + (*NetIQObject_User)(nil), + (*NetIQObject_ResourceRoleRef)(nil), + (*NetIQObject_RoleMemberRef)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_accessgraph_v1alpha_netiq_proto_rawDesc, + NumEnums: 1, + NumMessages: 12, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_accessgraph_v1alpha_netiq_proto_goTypes, + DependencyIndexes: file_accessgraph_v1alpha_netiq_proto_depIdxs, + EnumInfos: file_accessgraph_v1alpha_netiq_proto_enumTypes, + MessageInfos: file_accessgraph_v1alpha_netiq_proto_msgTypes, + }.Build() + File_accessgraph_v1alpha_netiq_proto = out.File + file_accessgraph_v1alpha_netiq_proto_rawDesc = nil + file_accessgraph_v1alpha_netiq_proto_goTypes = nil + file_accessgraph_v1alpha_netiq_proto_depIdxs = nil +} diff --git a/gen/proto/ts/google/protobuf/descriptor_pb.ts b/gen/proto/ts/google/protobuf/descriptor_pb.ts index 391723fe0e258..c0f84db4647ad 100644 --- a/gen/proto/ts/google/protobuf/descriptor_pb.ts +++ b/gen/proto/ts/google/protobuf/descriptor_pb.ts @@ -1036,12 +1036,13 @@ export interface MessageOptions { */ export interface FieldOptions { /** + * NOTE: ctype is deprecated. Use `features.(pb.cpp).string_type` instead. * The ctype option instructs the C++ code generator to use a different * representation of the field than it normally would. See the specific * options below. This option is only implemented to support use of * [ctype=CORD] and [ctype=STRING] (the default) on non-repeated fields of - * type "bytes" in the open source release -- sorry, we'll try to include - * other types in a future version! + * type "bytes" in the open source release. + * TODO: make ctype actually deprecated. * * @generated from protobuf field: optional google.protobuf.FieldOptions.CType ctype = 1; */ @@ -1261,8 +1262,6 @@ export enum FieldOptions_JSType { } /** * If set to RETENTION_SOURCE, the option will be omitted from the binary. - * Note: as of January 2023, support for this is in progress and does not yet - * have an effect (b/264593489). * * @generated from protobuf enum google.protobuf.FieldOptions.OptionRetention */ @@ -1283,8 +1282,7 @@ export enum FieldOptions_OptionRetention { /** * This indicates the types of entities that the field may apply to when used * as an option. If it is unset, then the field may be freely used as an - * option on any kind of entity. Note: as of January 2023, support for this is - * in progress and does not yet have an effect (b/264593489). + * option on any kind of entity. * * @generated from protobuf enum google.protobuf.FieldOptions.OptionTargetType */ @@ -2071,7 +2069,7 @@ export enum Edition { EDITION_2024 = 1001, /** * Placeholder editions for testing feature resolution. These should not be - * used or relyed on outside of tests. + * used or relied on outside of tests. * * @generated from protobuf enum value: EDITION_1_TEST_ONLY = 1; */ diff --git a/go.mod b/go.mod index 4c2145a3afd93..eb16212086763 100644 --- a/go.mod +++ b/go.mod @@ -185,6 +185,7 @@ require ( github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb github.com/vulcand/predicate v1.2.0 // replaced github.com/xanzy/go-gitlab v0.114.0 + github.com/yusufpapurcu/wmi v1.2.4 go.etcd.io/etcd/api/v3 v3.5.17 go.etcd.io/etcd/client/v3 v3.5.17 go.mongodb.org/mongo-driver v1.14.0 @@ -201,7 +202,7 @@ require ( golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f golang.org/x/mod v0.22.0 - golang.org/x/net v0.31.0 + golang.org/x/net v0.33.0 golang.org/x/oauth2 v0.24.0 golang.org/x/sync v0.10.0 golang.org/x/sys v0.28.0 @@ -521,7 +522,6 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect github.com/yuin/gopher-lua v1.1.1 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zeebo/errs v1.3.0 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect github.com/zmap/zcrypto v0.0.0-20231219022726-a1f61fb1661c // indirect diff --git a/go.sum b/go.sum index e0b7780b45836..82132a3ee7268 100644 --- a/go.sum +++ b/go.sum @@ -2513,8 +2513,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/integration/helpers/trustedclusters.go b/integration/helpers/trustedclusters.go index 1b3f43b61507c..cfc68f571ce5d 100644 --- a/integration/helpers/trustedclusters.go +++ b/integration/helpers/trustedclusters.go @@ -61,7 +61,66 @@ func TryCreateTrustedCluster(t *testing.T, authServer *auth.Server, trustedClust ctx := context.TODO() for i := 0; i < 10; i++ { log.Debugf("Will create trusted cluster %v, attempt %v.", trustedCluster, i) - _, err := authServer.UpsertTrustedCluster(ctx, trustedCluster) + _, err := authServer.CreateTrustedCluster(ctx, trustedCluster) + if err == nil { + return + } + if trace.IsConnectionProblem(err) { + log.Debugf("Retrying on connection problem: %v.", err) + time.Sleep(500 * time.Millisecond) + continue + } + if trace.IsAccessDenied(err) { + log.Debugf("Retrying on access denied: %v.", err) + time.Sleep(500 * time.Millisecond) + continue + } + require.FailNow(t, "Terminating on unexpected problem", "%v.", err) + } + require.FailNow(t, "Timeout creating trusted cluster") +} + +// TryUpdateTrustedCluster performs several attempts to update a trusted cluster, +// retries on connection problems and access denied errors to let caches +// propagate and services to start +func TryUpdateTrustedCluster(t *testing.T, authServer *auth.Server, trustedCluster types.TrustedCluster) { + t.Helper() + ctx := context.TODO() + for i := 0; i < 10; i++ { + log.Debugf("Will create trusted cluster %v, attempt %v.", trustedCluster, i) + _, err := authServer.UpdateTrustedCluster(ctx, trustedCluster) + if err == nil { + return + } + if trace.IsConnectionProblem(err) { + log.Debugf("Retrying on connection problem: %v.", err) + time.Sleep(500 * time.Millisecond) + continue + } + if trace.IsAccessDenied(err) { + log.Debugf("Retrying on access denied: %v.", err) + time.Sleep(500 * time.Millisecond) + continue + } + require.FailNow(t, "Terminating on unexpected problem", "%v.", err) + } + require.FailNow(t, "Timeout creating trusted cluster") +} + +// TryUpdateTrustedCluster performs several attempts to upsert a trusted cluster, +// retries on connection problems and access denied errors to let caches +// propagate and services to start +func TryUpsertTrustedCluster(t *testing.T, authServer *auth.Server, trustedCluster types.TrustedCluster, skipNameValidation bool) { + t.Helper() + ctx := context.TODO() + for i := 0; i < 10; i++ { + log.Debugf("Will create trusted cluster %v, attempt %v.", trustedCluster, i) + var err error + if skipNameValidation { + _, err = authServer.UpsertTrustedCluster(ctx, trustedCluster) + } else { + _, err = authServer.UpsertTrustedClusterV2(ctx, trustedCluster) + } if err == nil { return } diff --git a/integration/integration_test.go b/integration/integration_test.go index cbc138b498f4f..9dcb2332b44e6 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -196,6 +196,8 @@ func TestIntegrations(t *testing.T) { t.Run("TrustedDisabledClusters", suite.bind(testDisabledTrustedClusters)) t.Run("TrustedClustersRoleMapChanges", suite.bind(testTrustedClustersRoleMapChanges)) t.Run("TrustedClustersWithLabels", suite.bind(testTrustedClustersWithLabels)) + t.Run("TrustedClustersSkipNameValidation", suite.bind(testTrustedClustersSkipNameValidation)) + t.Run("CreateAndUpdateTrustedClusters", suite.bind(testCreateAndUpdateTrustedClusters)) t.Run("TrustedTunnelNode", suite.bind(testTrustedTunnelNode)) t.Run("TwoClustersProxy", suite.bind(testTwoClustersProxy)) t.Run("TwoClustersTunnel", suite.bind(testTwoClustersTunnel)) @@ -809,9 +811,7 @@ func testUUIDBasedProxy(t *testing.T, suite *integrationTestSuite) { // attempting to run a command by hostname should generate NodeIsAmbiguous error. _, err = runCommand(t, teleportSvr, []string{"echo", "Hello there!"}, helpers.ClientConfig{Login: suite.Me.Username, Cluster: helpers.Site, Host: Host}, 1) require.Error(t, err) - if !strings.Contains(err.Error(), teleport.NodeIsAmbiguous) { - require.FailNowf(t, "Expected %s, got %s", teleport.NodeIsAmbiguous, err.Error()) - } + require.ErrorContains(t, err, "ambiguous") // attempting to run a command by uuid should succeed. _, err = runCommand(t, teleportSvr, []string{"echo", "Hello there!"}, helpers.ClientConfig{Login: suite.Me.Username, Cluster: helpers.Site, Host: uuid1}, 1) @@ -2746,17 +2746,14 @@ func testMapRoles(t *testing.T, suite *integrationTestSuite) { {Remote: mainDevs, Local: []string{auxDevs}}, }) - // modify trusted cluster resource name so it would not - // match the cluster name to check that it does not matter - trustedCluster.SetName(main.Secrets.SiteName + "-cluster") - require.NoError(t, main.Start()) require.NoError(t, aux.Start()) require.NoError(t, services.CheckAndSetDefaults(trustedCluster)) // try and upsert a trusted cluster - helpers.TryCreateTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster) + const skipNameValidation = false + helpers.TryUpsertTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster, skipNameValidation) helpers.WaitForTunnelConnections(t, main.Process.GetAuthServer(), clusterAux, 1) sshPort, _, _ := aux.StartNodeAndProxy(t, "aux-node") @@ -2906,6 +2903,9 @@ type trustedClusterTest struct { // useLabels turns on trusted cluster labels and // verifies RBAC useLabels bool + // skipNameValidation uses the deprecated UpsertTrustedCluster and skips + // cluster name validation. + skipNameValidation bool } // TestTrustedClusters tests remote clusters scenarios @@ -2942,6 +2942,15 @@ func testTrustedClustersWithLabels(t *testing.T, suite *integrationTestSuite) { trustedClusters(t, suite, trustedClusterTest{multiplex: false, useLabels: true}) } +// TestTrustedClustersSkipNameValidation tests remote clusters scenarios +// skipping name validation. +func testTrustedClustersSkipNameValidation(t *testing.T, suite *integrationTestSuite) { + tr := utils.NewTracer(utils.ThisFunction()).Start() + defer tr.Stop() + + trustedClusters(t, suite, trustedClusterTest{skipNameValidation: true}) +} + // TestJumpTrustedClusters tests remote clusters scenarios // using trusted clusters feature using jumphost connection func testJumpTrustedClusters(t *testing.T, suite *integrationTestSuite) { @@ -2969,6 +2978,15 @@ func testMultiplexingTrustedClusters(t *testing.T, suite *integrationTestSuite) trustedClusters(t, suite, trustedClusterTest{multiplex: true}) } +// TestCreateAndUpdateTrustedClusters tests the basic create and update +// operations for a trusted cluster. +func testCreateAndUpdateTrustedClusters(t *testing.T, suite *integrationTestSuite) { + tr := utils.NewTracer(utils.ThisFunction()).Start() + defer tr.Stop() + + createAndUpdateTrustedClusters(t, suite, trustedClusterTest{}) +} + func standardPortsOrMuxSetup(t *testing.T, mux bool, fds *[]*servicecfg.FileDescriptor) *helpers.InstanceListeners { if mux { return helpers.WebReverseTunnelMuxPortSetup(t, fds) @@ -2976,6 +2994,116 @@ func standardPortsOrMuxSetup(t *testing.T, mux bool, fds *[]*servicecfg.FileDesc return helpers.StandardListenerSetup(t, fds) } +func createAndUpdateTrustedClusters(t *testing.T, suite *integrationTestSuite, test trustedClusterTest) { + ctx := context.Background() + username := suite.Me.Username + + clusterMain := "cluster-main" + clusterAux := "cluster-aux" + mainCfg := helpers.InstanceConfig{ + ClusterName: clusterMain, + HostID: helpers.HostID, + NodeName: Host, + Priv: suite.Priv, + Pub: suite.Pub, + Log: suite.Log, + } + mainCfg.Listeners = standardPortsOrMuxSetup(t, test.multiplex, &mainCfg.Fds) + main := helpers.NewInstance(t, mainCfg) + aux := suite.newNamedTeleportInstance(t, clusterAux) + + // main cluster has a local user and belongs to role "main-devs" and "main-admins" + mainDevs := "main-devs" + mainRole, err := types.NewRole(mainDevs, types.RoleSpecV6{ + Allow: types.RoleConditions{ + Logins: []string{username}, + NodeLabels: types.Labels{types.Wildcard: []string{types.Wildcard}}, + }, + }) + require.NoError(t, err) + + mainAdmins := "main-admins" + adminsRole, err := types.NewRole(mainAdmins, types.RoleSpecV6{ + Allow: types.RoleConditions{ + Logins: []string{"superuser"}, + NodeLabels: types.Labels{types.Wildcard: []string{types.Wildcard}}, + }, + }) + require.NoError(t, err) + + main.AddUserWithRole(username, mainRole, adminsRole) + + // for role mapping test we turn on Web API on the main cluster + // as it's used + makeConfig := func(instance *helpers.TeleInstance, enableSSH bool) (*testing.T, *servicecfg.Config) { + tconf := suite.defaultServiceConfig() + tconf.Proxy.DisableWebService = false + tconf.Proxy.DisableWebInterface = true + tconf.SSH.Enabled = enableSSH + tconf, err := instance.GenerateConfig(t, nil, tconf) + require.NoError(t, err) + + tconf.CachePolicy.Enabled = false + return t, tconf + } + lib.SetInsecureDevMode(true) + defer lib.SetInsecureDevMode(false) + + require.NoError(t, main.CreateWithConf(makeConfig(main, false))) + require.NoError(t, aux.CreateWithConf(makeConfig(aux, true))) + + // auxiliary cluster has only a role aux-devs + // connect aux cluster to main cluster + // using trusted clusters, so remote user will be allowed to assume + // role specified by mapping remote role "devs" to local role "local-devs" + auxDevs := "aux-devs" + auxRole, err := types.NewRole(auxDevs, types.RoleSpecV6{ + Allow: types.RoleConditions{ + Logins: []string{username}, + NodeLabels: types.Labels{types.Wildcard: []string{types.Wildcard}}, + }, + }) + require.NoError(t, err) + _, err = aux.Process.GetAuthServer().UpsertRole(ctx, auxRole) + require.NoError(t, err) + + trustedClusterToken := "trusted-cluster-token" + tokenResource, err := types.NewProvisionToken(trustedClusterToken, []types.SystemRole{types.RoleTrustedCluster}, time.Time{}) + require.NoError(t, err) + err = main.Process.GetAuthServer().UpsertToken(ctx, tokenResource) + require.NoError(t, err) + + trustedCluster := main.AsTrustedCluster(trustedClusterToken, types.RoleMap{ + {Remote: mainDevs, Local: []string{auxDevs}}, + }) + + require.NoError(t, main.Start()) + require.NoError(t, aux.Start()) + + require.NoError(t, services.CheckAndSetDefaults(trustedCluster)) + + // Note that the trusted cluster resource name must match the cluster name. + // Modify the trusted cluster resource name and expect the create to fail. + trustedCluster.SetName(main.Secrets.SiteName + "-cluster") + _, err = aux.Process.GetAuthServer().CreateTrustedCluster(ctx, trustedCluster) + require.ErrorContains(t, err, "trusted cluster resource name must be the same as the remote cluster name", "expected failure due to tc name mismatch") + + // Modify the trusted cluster resource name back to what it was originally. + // Try and create a trusted cluster + trustedCluster.SetName(main.Secrets.SiteName) + helpers.TryCreateTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster) + + // Update the trusted cluster resource with new role mappings. + trustedCluster.SetRoleMap(types.RoleMap{ + {Remote: mainAdmins, Local: []string{auxDevs}}, + }) + helpers.TryUpdateTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster) + + // stop clusters and remaining nodes + require.NoError(t, main.StopAll()) + require.NoError(t, aux.StopAll()) +} + func trustedClusters(t *testing.T, suite *integrationTestSuite, test trustedClusterTest) { ctx := context.Background() username := suite.Me.Username @@ -3079,17 +3207,25 @@ func trustedClusters(t *testing.T, suite *integrationTestSuite, test trustedClus {Remote: mainOps, Local: []string{auxDevs}}, }) - // modify trusted cluster resource name, so it would not - // match the cluster name to check that it does not matter - trustedCluster.SetName(main.Secrets.SiteName + "-cluster") - require.NoError(t, main.Start()) require.NoError(t, aux.Start()) require.NoError(t, services.CheckAndSetDefaults(trustedCluster)) + // Note that the trusted cluster resource name must match the cluster name. + // Modify the trusted cluster resource name and expect the upsert to fail. + trustedCluster.SetName(main.Secrets.SiteName + "-cluster") + + _, err = aux.Process.GetAuthServer().UpsertTrustedClusterV2(ctx, trustedCluster) + require.ErrorContains(t, err, "trusted cluster resource name must be the same as the remote cluster name", "expected failure due to tc name mismatch") + + if !test.skipNameValidation { + // Modify the trusted cluster resource name back to what it was originally. + trustedCluster.SetName(main.Secrets.SiteName) + } + // try and upsert a trusted cluster - helpers.TryCreateTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster) + helpers.TryUpsertTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster, test.skipNameValidation) helpers.WaitForTunnelConnections(t, main.Process.GetAuthServer(), clusterAux, 1) sshPort, _, _ := aux.StartNodeAndProxy(t, "aux-node") @@ -3176,7 +3312,7 @@ func trustedClusters(t *testing.T, suite *integrationTestSuite, test trustedClus require.Error(t, err, "expected tunnel to close and SSH client to start failing") // recreating the trusted cluster should re-establish connection - _, err = aux.Process.GetAuthServer().UpsertTrustedCluster(ctx, trustedCluster) + _, err = aux.Process.GetAuthServer().UpsertTrustedClusterV2(ctx, trustedCluster) require.NoError(t, err) // check that remote cluster has been re-provisioned @@ -3322,9 +3458,6 @@ func trustedDisabledCluster(t *testing.T, suite *integrationTestSuite, test trus {Remote: mainOps, Local: []string{auxDevs}}, }) - // modify trusted cluster resource name, so it would not - // match the cluster name to check that it does not matter - trustedCluster.SetName(main.Secrets.SiteName + "-cluster") // disable cluster trustedCluster.SetEnabled(false) @@ -3334,11 +3467,11 @@ func trustedDisabledCluster(t *testing.T, suite *integrationTestSuite, test trus require.NoError(t, services.CheckAndSetDefaults(trustedCluster)) // try and upsert a trusted cluster while disabled - helpers.TryCreateTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster) + helpers.TryUpsertTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster, test.skipNameValidation) // try to enable disabled cluster trustedCluster.SetEnabled(true) - helpers.TryCreateTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster) + helpers.TryUpsertTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster, test.skipNameValidation) helpers.WaitForTunnelConnections(t, main.Process.GetAuthServer(), clusterAux, 1) helpers.CheckTrustedClustersCanConnect(ctx, t, helpers.TrustedClusterSetup{ @@ -3462,16 +3595,12 @@ func trustedClustersRoleMapChanges(t *testing.T, suite *integrationTestSuite, te {Remote: mainOps, Local: []string{auxDevs}}, }) - // modify trusted cluster resource name, so it would not - // match the cluster name to check that it does not matter - trustedCluster.SetName(main.Secrets.SiteName + "-cluster") - require.NoError(t, main.Start()) require.NoError(t, aux.Start()) require.NoError(t, services.CheckAndSetDefaults(trustedCluster)) - helpers.TryCreateTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster) + helpers.TryUpsertTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster, test.skipNameValidation) helpers.WaitForTunnelConnections(t, main.Process.GetAuthServer(), clusterAux, 1) // change role mapping to ensure updating works @@ -3479,7 +3608,7 @@ func trustedClustersRoleMapChanges(t *testing.T, suite *integrationTestSuite, te {Remote: mainDevs, Local: []string{auxDevs}}, }) - helpers.TryCreateTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster) + helpers.TryUpsertTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster, test.skipNameValidation) helpers.WaitForTunnelConnections(t, main.Process.GetAuthServer(), clusterAux, 1) helpers.CheckTrustedClustersCanConnect(ctx, t, helpers.TrustedClusterSetup{ @@ -3492,7 +3621,7 @@ func trustedClustersRoleMapChanges(t *testing.T, suite *integrationTestSuite, te // disable the enabled trusted cluster and ensure it no longer works trustedCluster.SetEnabled(false) - helpers.TryCreateTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster) + helpers.TryUpsertTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster, test.skipNameValidation) // Wait for both cluster to no longer see each other via reverse tunnels. require.Eventually(t, helpers.WaitForClusters(main.Tunnel, 0), 10*time.Second, 1*time.Second, @@ -3563,17 +3692,14 @@ func testTrustedTunnelNode(t *testing.T, suite *integrationTestSuite) { {Remote: mainDevs, Local: []string{auxDevs}}, }) - // modify trusted cluster resource name, so it would not - // match the cluster name to check that it does not matter - trustedCluster.SetName(main.Secrets.SiteName + "-cluster") - require.NoError(t, main.Start()) require.NoError(t, aux.Start()) require.NoError(t, services.CheckAndSetDefaults(trustedCluster)) // try and upsert a trusted cluster - helpers.TryCreateTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster) + const skipNameValidation = false + helpers.TryUpsertTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster, skipNameValidation) helpers.WaitForTunnelConnections(t, main.Process.GetAuthServer(), clusterAux, 1) // Create a Teleport instance with a node that dials back to the aux cluster. @@ -3747,17 +3873,14 @@ func testTrustedClusterAgentless(t *testing.T, suite *integrationTestSuite) { {Remote: devsRoleName, Local: []string{devsRoleName}}, }) - // modify trusted cluster resource name, so it would not - // match the cluster name to check that it does not matter - trustedCluster.SetName(main.Secrets.SiteName + "-cluster") - require.NoError(t, main.Start()) require.NoError(t, leaf.Start()) require.NoError(t, services.CheckAndSetDefaults(trustedCluster)) // try and upsert a trusted cluster - helpers.TryCreateTrustedCluster(t, leaf.Process.GetAuthServer(), trustedCluster) + const skipNameValidation = false + helpers.TryUpsertTrustedCluster(t, leaf.Process.GetAuthServer(), trustedCluster, skipNameValidation) helpers.WaitForTunnelConnections(t, main.Process.GetAuthServer(), clusterAux, 1) // Wait for both cluster to see each other via reverse tunnels. @@ -5585,7 +5708,8 @@ func testRotateTrustedClusters(t *testing.T, suite *integrationTestSuite) { lib.SetInsecureDevMode(true) defer lib.SetInsecureDevMode(false) - helpers.TryCreateTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster) + const skipNameValidation = false + helpers.TryUpsertTrustedCluster(t, aux.Process.GetAuthServer(), trustedCluster, skipNameValidation) helpers.WaitForTunnelConnections(t, svc.GetAuthServer(), aux.Secrets.SiteName, 1) // capture credentials before reload has started to simulate old client @@ -7484,7 +7608,9 @@ func createTrustedClusterPair(t *testing.T, suite *integrationTestSuite, extraSe t.Cleanup(func() { leaf.StopAll() }) require.NoError(t, services.CheckAndSetDefaults(trustedCluster)) - helpers.TryCreateTrustedCluster(t, leaf.Process.GetAuthServer(), trustedCluster) + + const skipNameValidation = false + helpers.TryUpsertTrustedCluster(t, leaf.Process.GetAuthServer(), trustedCluster, skipNameValidation) helpers.WaitForTunnelConnections(t, root.Process.GetAuthServer(), leafName, 1) _, _, rootProxySSHPort := root.StartNodeAndProxy(t, "root-zero") diff --git a/integration/kube_integration_test.go b/integration/kube_integration_test.go index ad7d745d89452..2127486ff7a7f 100644 --- a/integration/kube_integration_test.go +++ b/integration/kube_integration_test.go @@ -695,7 +695,7 @@ func testKubeTrustedClustersClientCert(t *testing.T, suite *KubeSuite) { var upsertSuccess bool for i := 0; i < 10; i++ { log.Debugf("Will create trusted cluster %v, attempt %v", trustedCluster, i) - _, err = aux.Process.GetAuthServer().UpsertTrustedCluster(ctx, trustedCluster) + _, err = aux.Process.GetAuthServer().UpsertTrustedClusterV2(ctx, trustedCluster) if err != nil { if trace.IsConnectionProblem(err) { log.Debugf("retrying on connection problem: %v", err) @@ -971,7 +971,7 @@ func testKubeTrustedClustersSNI(t *testing.T, suite *KubeSuite) { var upsertSuccess bool for i := 0; i < 10; i++ { log.Debugf("Will create trusted cluster %v, attempt %v", trustedCluster, i) - _, err = aux.Process.GetAuthServer().UpsertTrustedCluster(ctx, trustedCluster) + _, err = aux.Process.GetAuthServer().UpsertTrustedClusterV2(ctx, trustedCluster) if err != nil { if trace.IsConnectionProblem(err) { log.Debugf("retrying on connection problem: %v", err) @@ -1768,7 +1768,7 @@ func testKubeExecWeb(t *testing.T, suite *KubeSuite) { ws := openWebsocketAndReadSession(t, endpoint, req) - wsStream := terminal.NewWStream(context.Background(), ws, suite.log, nil) + wsStream := terminal.NewWStream(context.Background(), ws, utils.NewSlogLoggerForTests(), nil) // Check for the expected string in the output. findTextInReader(t, wsStream, testNamespace, time.Second*2) @@ -1789,7 +1789,7 @@ func testKubeExecWeb(t *testing.T, suite *KubeSuite) { ws := openWebsocketAndReadSession(t, endpoint, req) - wsStream := terminal.NewWStream(context.Background(), ws, suite.log, nil) + wsStream := terminal.NewWStream(context.Background(), ws, utils.NewSlogLoggerForTests(), nil) // Read first prompt from the server. readData := make([]byte, 255) diff --git a/integration/proxy/proxy_helpers.go b/integration/proxy/proxy_helpers.go index e0e3c7b587224..9de514caf73e1 100644 --- a/integration/proxy/proxy_helpers.go +++ b/integration/proxy/proxy_helpers.go @@ -184,7 +184,8 @@ func newSuite(t *testing.T, opts ...proxySuiteOptionsFunc) *Suite { } if options.trustedCluster != nil { - helpers.TryCreateTrustedCluster(t, suite.leaf.Process.GetAuthServer(), options.trustedCluster) + const skipNameValidation = false + helpers.TryUpsertTrustedCluster(t, suite.leaf.Process.GetAuthServer(), options.trustedCluster, skipNameValidation) helpers.WaitForTunnelConnections(t, suite.root.Process.GetAuthServer(), suite.leaf.Secrets.SiteName, 1) } diff --git a/integrations/access/slack/bot.go b/integrations/access/slack/bot.go index dff6c17bbad27..9c58093cb9897 100644 --- a/integrations/access/slack/bot.go +++ b/integrations/access/slack/bot.go @@ -291,6 +291,7 @@ func (b Bot) slackAccessListReminderMsgSection(accessList *accesslist.AccessList if b.webProxyURL != nil { reqURL := *b.webProxyURL reqURL.Path = lib.BuildURLPath("web", "accesslists", accessList.Metadata.Name) + reqURL.Fragment = "review" link = fmt.Sprintf("*Link*: %s", reqURL.String()) } diff --git a/integrations/event-handler/go.mod b/integrations/event-handler/go.mod index 218c2a5118595..56ac4bd95bd73 100644 --- a/integrations/event-handler/go.mod +++ b/integrations/event-handler/go.mod @@ -15,7 +15,7 @@ require ( github.com/peterbourgon/diskv/v3 v3.0.1 github.com/sethvargo/go-limiter v1.0.0 github.com/stretchr/testify v1.10.0 - golang.org/x/net v0.31.0 + golang.org/x/net v0.33.0 golang.org/x/time v0.8.0 google.golang.org/protobuf v1.36.0 ) diff --git a/integrations/event-handler/go.sum b/integrations/event-handler/go.sum index 08fc1add428b4..2b0f134829fcd 100644 --- a/integrations/event-handler/go.sum +++ b/integrations/event-handler/go.sum @@ -1795,8 +1795,8 @@ golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/integrations/terraform/Makefile b/integrations/terraform/Makefile index 0d1c0ed65e6d6..572a07d4d45dc 100644 --- a/integrations/terraform/Makefile +++ b/integrations/terraform/Makefile @@ -115,10 +115,18 @@ endif --terraform_out=config=protoc-gen-terraform-statichostuser.yaml:./tfschema \ teleport/userprovisioning/v2/statichostuser.proto + @protoc \ + -I=../../api/proto \ + -I=$(PROTOBUF_MOD_PATH) \ + --plugin=$(PROTOC_GEN_TERRAFORM) \ + --terraform_out=config=protoc-gen-terraform-workloadidentity.yaml:./tfschema \ + teleport/workloadidentity/v1/resource.proto + mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/loginrule/v1/loginrule_terraform.go ./tfschema/loginrule/v1/ mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1/accesslist_terraform.go ./tfschema/accesslist/v1/ mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/accessmonitoringrules/v1/access_monitoring_rules_terraform.go ./tfschema/accessmonitoringrules/v1/ mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/userprovisioning/v2/statichostuser_terraform.go ./tfschema/userprovisioning/v2/ + mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1/resource_terraform.go ./tfschema/workloadidentity/v1/ mv ./tfschema/github.com/gravitational/teleport/api/types/device_terraform.go ./tfschema/devicetrust/v1/ rm -r ./tfschema/github.com/ @go run ./gen/main.go diff --git a/integrations/terraform/examples/resources/teleport_role/resource.tf b/integrations/terraform/examples/resources/teleport_role/resource.tf index c5ac6c920e5d9..c76e71248e962 100644 --- a/integrations/terraform/examples/resources/teleport_role/resource.tf +++ b/integrations/terraform/examples/resources/teleport_role/resource.tf @@ -13,9 +13,17 @@ resource "teleport_role" "example" { spec = { options = { - forward_agent = false - max_session_ttl = "7m" - port_forwarding = false + forward_agent = false + max_session_ttl = "7m" + ssh_port_forwarding = { + remote = { + enabled = false + } + + local = { + enabled = false + } + } client_idle_timeout = "1h" disconnect_expired_cert = true permit_x11_forwarding = false diff --git a/integrations/terraform/examples/resources/teleport_workload_identity/resource.tf b/integrations/terraform/examples/resources/teleport_workload_identity/resource.tf new file mode 100644 index 0000000000000..e48ab1e5d0dd2 --- /dev/null +++ b/integrations/terraform/examples/resources/teleport_workload_identity/resource.tf @@ -0,0 +1,22 @@ +resource "teleport_workload_identity" "example" { + version = "v1" + metadata = { + name = "example" + } + spec = { + rules = { + allow = [ + { + conditions = [{ + attribute = "user.name" + equals = "noah" + }] + } + ] + } + spiffe = { + id = "/my/spiffe/id/path" + hint = "my-hint" + } + } +} \ No newline at end of file diff --git a/integrations/terraform/gen/main.go b/integrations/terraform/gen/main.go index 3053e7b56098a..ca639ac758ac2 100644 --- a/integrations/terraform/gen/main.go +++ b/integrations/terraform/gen/main.go @@ -519,6 +519,31 @@ var ( ExtraImports: []string{"apitypes \"github.com/gravitational/teleport/api/types\""}, ForceSetKind: "apitypes.KindStaticHostUser", } + + workloadIdentity = payload{ + Name: "WorkloadIdentity", + TypeName: "WorkloadIdentity", + VarName: "workloadIdentity", + GetMethod: "GetWorkloadIdentity", + CreateMethod: "CreateWorkloadIdentity", + UpsertMethodArity: 2, + UpdateMethod: "UpsertWorkloadIdentity", + DeleteMethod: "DeleteWorkloadIdentity", + ID: "workloadIdentity.Metadata.Name", + Kind: "workload_identity", + HasStaticID: false, + ProtoPackage: "workloadidentityv1", + ProtoPackagePath: "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1", + SchemaPackage: "schemav1", + SchemaPackagePath: "github.com/gravitational/teleport/integrations/terraform/tfschema/workloadidentity/v1", + TerraformResourceType: "teleport_workload_identity", + // Since [RFD 153](https://github.com/gravitational/teleport/blob/master/rfd/0153-resource-guidelines.md) + // resources are plain structs + IsPlainStruct: true, + // As 153-style resources don't have CheckAndSetDefaults, we must set the Kind manually. + // We import the package containing kinds, then use ForceSetKind. + ForceSetKind: `"workload_identity"`, + } ) func main() { @@ -570,6 +595,8 @@ func genTFSchema() { generateDataSource(accessMonitoringRule, pluralDataSource) generateResource(staticHostUser, pluralResource) generateDataSource(staticHostUser, pluralDataSource) + generateResource(workloadIdentity, pluralResource) + generateDataSource(workloadIdentity, pluralDataSource) } func generateResource(p payload, tpl string) { diff --git a/integrations/terraform/go.mod b/integrations/terraform/go.mod index b2bd7f4e5536f..e6e5624117945 100644 --- a/integrations/terraform/go.mod +++ b/integrations/terraform/go.mod @@ -352,7 +352,7 @@ require ( golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.31.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect diff --git a/integrations/terraform/go.sum b/integrations/terraform/go.sum index 92822ceaf79ee..e2d3ece037acc 100644 --- a/integrations/terraform/go.sum +++ b/integrations/terraform/go.sum @@ -2140,8 +2140,8 @@ golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/integrations/terraform/protoc-gen-terraform-workloadidentity.yaml b/integrations/terraform/protoc-gen-terraform-workloadidentity.yaml new file mode 100644 index 0000000000000..016f3209037ba --- /dev/null +++ b/integrations/terraform/protoc-gen-terraform-workloadidentity.yaml @@ -0,0 +1,68 @@ +--- +target_package_name: "v1" +default_package_name: "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" +duration_custom_type: Duration +use_state_for_unknown_by_default: true + +# Top-level type names to export +types: + - "WorkloadIdentity" + +# These import paths were not being automatically picked up by +# protoc-gen-terraform without these overrides +import_path_overrides: + "types": "github.com/gravitational/teleport/api/types" + "wrappers": "github.com/gravitational/teleport/api/types/wrappers" + "durationpb": "google.golang.org/protobuf/types/known/durationpb" + "timestamppb": "google.golang.org/protobuf/types/known/timestamppb" + "structpb": "google.golang.org/protobuf/types/known/structpb" + "v1": "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + "v11": "github.com/gravitational/teleport/api/gen/proto/go/teleport/label/v1" + "github_com_gravitational_teleport_integrations_terraform_tfschema": "github.com/gravitational/teleport/integrations/terraform/tfschema" + + +# id field is required for integration tests. It is not used by provider. +# We have to add it manually (might be removed in the future versions). +injected_fields: + WorkloadIdentity: + - name: id + type: github.com/hashicorp/terraform-plugin-framework/types.StringType + computed: true + plan_modifiers: + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" + +# These fields will be excluded +exclude_fields: + # Metadata (we id resources by name on our side) + - "WorkloadIdentity.metadata.id" + +# These fields will be marked as Computed: true +computed_fields: + # Metadata + - "WorkloadIdentity.metadata.namespace" + - "WorkloadIdentity.kind" + +# These fields will be marked as Required: true +required_fields: [] + + +plan_modifiers: + # Force to recreate resource if it's name changes + Metadata.name: + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" + +# This must be defined for the generator to be happy, but in reality all time +# fields are overridden (because the protobuf timestamps contain locks and the +# linter gets mad if we use raw structs instead of pointers). +time_type: + type: "PlaceholderType" +duration_type: + type: "PlaceholderType" + +validators: + # Expires must be in the future + Metadata.expires: + - github_com_gravitational_teleport_integrations_terraform_tfschema.MustTimeBeInFuture() + +custom_types: + "WorkloadIdentity.metadata.expires": Timestamp \ No newline at end of file diff --git a/integrations/terraform/provider/data_source_teleport_workload_identity.go b/integrations/terraform/provider/data_source_teleport_workload_identity.go new file mode 100755 index 0000000000000..1b1d15fb99dcd --- /dev/null +++ b/integrations/terraform/provider/data_source_teleport_workload_identity.go @@ -0,0 +1,82 @@ +// Code generated by _gen/main.go DO NOT EDIT +/* +Copyright 2015-2024 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package provider + +import ( + "context" + + + "github.com/gravitational/trace" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + + schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/workloadidentity/v1" +) + +// dataSourceTeleportWorkloadIdentityType is the data source metadata type +type dataSourceTeleportWorkloadIdentityType struct{} + +// dataSourceTeleportWorkloadIdentity is the resource +type dataSourceTeleportWorkloadIdentity struct { + p Provider +} + +// GetSchema returns the data source schema +func (r dataSourceTeleportWorkloadIdentityType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { + return schemav1.GenSchemaWorkloadIdentity(ctx) +} + +// NewDataSource creates the empty data source +func (r dataSourceTeleportWorkloadIdentityType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { + return dataSourceTeleportWorkloadIdentity{ + p: *(p.(*Provider)), + }, nil +} + +// Read reads teleport WorkloadIdentity +func (r dataSourceTeleportWorkloadIdentity) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { + var id types.String + diags := req.Config.GetAttribute(ctx, path.Root("metadata").AtName("name"), &id) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + workloadIdentityI, err := r.p.Client.GetWorkloadIdentity(ctx, id.Value) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + + var state types.Object + workloadIdentity := workloadIdentityI + + diags = schemav1.CopyWorkloadIdentityToTerraform(ctx, workloadIdentity, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/integrations/terraform/provider/provider.go b/integrations/terraform/provider/provider.go index ec2648df1b274..13b20d20c434f 100644 --- a/integrations/terraform/provider/provider.go +++ b/integrations/terraform/provider/provider.go @@ -504,6 +504,7 @@ func (p *Provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceTyp "teleport_installer": resourceTeleportInstallerType{}, "teleport_access_monitoring_rule": resourceTeleportAccessMonitoringRuleType{}, "teleport_static_host_user": resourceTeleportStaticHostUserType{}, + "teleport_workload_identity": resourceTeleportWorkloadIdentityType{}, }, nil } @@ -531,6 +532,7 @@ func (p *Provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourc "teleport_installer": dataSourceTeleportInstallerType{}, "teleport_access_monitoring_rule": dataSourceTeleportAccessMonitoringRuleType{}, "teleport_static_host_user": dataSourceTeleportStaticHostUserType{}, + "teleport_workload_identity": dataSourceTeleportWorkloadIdentityType{}, }, nil } diff --git a/integrations/terraform/provider/resource_teleport_workload_identity.go b/integrations/terraform/provider/resource_teleport_workload_identity.go new file mode 100755 index 0000000000000..e5c59e0993b44 --- /dev/null +++ b/integrations/terraform/provider/resource_teleport_workload_identity.go @@ -0,0 +1,317 @@ +// Code generated by _gen/main.go DO NOT EDIT +/* +Copyright 2015-2024 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package provider + +import ( + "context" + "fmt" + + workloadidentityv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" + + "github.com/gravitational/teleport/integrations/lib/backoff" + "github.com/gravitational/trace" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/jonboulle/clockwork" + + schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/workloadidentity/v1" +) + +// resourceTeleportWorkloadIdentityType is the resource metadata type +type resourceTeleportWorkloadIdentityType struct{} + +// resourceTeleportWorkloadIdentity is the resource +type resourceTeleportWorkloadIdentity struct { + p Provider +} + +// GetSchema returns the resource schema +func (r resourceTeleportWorkloadIdentityType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { + return schemav1.GenSchemaWorkloadIdentity(ctx) +} + +// NewResource creates the empty resource +func (r resourceTeleportWorkloadIdentityType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return resourceTeleportWorkloadIdentity{ + p: *(p.(*Provider)), + }, nil +} + +// Create creates the WorkloadIdentity +func (r resourceTeleportWorkloadIdentity) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var err error + if !r.p.IsConfigured(resp.Diagnostics) { + return + } + + var plan types.Object + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + workloadIdentity := &workloadidentityv1.WorkloadIdentity{} + diags = schemav1.CopyWorkloadIdentityFromTerraform(ctx, plan, workloadIdentity) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + + workloadIdentityResource := workloadIdentity + + workloadIdentityResource.Kind = "workload_identity" + + id := workloadIdentityResource.Metadata.Name + + _, err = r.p.Client.GetWorkloadIdentity(ctx, id) + if !trace.IsNotFound(err) { + if err == nil { + existErr := fmt.Sprintf("WorkloadIdentity exists in Teleport. Either remove it (tctl rm workload_identity/%v)"+ + " or import it to the existing state (terraform import teleport_workload_identity.%v %v)", id, id, id) + + resp.Diagnostics.Append(diagFromErr("WorkloadIdentity exists in Teleport", trace.Errorf(existErr))) + return + } + + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + + _, err = r.p.Client.CreateWorkloadIdentity(ctx, workloadIdentityResource) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error creating WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + var workloadIdentityI *workloadidentityv1.WorkloadIdentity + tries := 0 + backoff := backoff.NewDecorr(r.p.RetryConfig.Base, r.p.RetryConfig.Cap, clockwork.NewRealClock()) + for { + tries = tries + 1 + workloadIdentityI, err = r.p.Client.GetWorkloadIdentity(ctx, id) + if trace.IsNotFound(err) { + if bErr := backoff.Do(ctx); bErr != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", trace.Wrap(bErr), "workload_identity")) + return + } + if tries >= r.p.RetryConfig.MaxTries { + diagMessage := fmt.Sprintf("Error reading WorkloadIdentity (tried %d times) - state outdated, please import resource", tries) + resp.Diagnostics.AddError(diagMessage, "workload_identity") + } + continue + } + break + } + + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + + workloadIdentityResource = workloadIdentityI + + workloadIdentity = workloadIdentityResource + + diags = schemav1.CopyWorkloadIdentityToTerraform(ctx, workloadIdentity, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + plan.Attrs["id"] = types.String{Value: workloadIdentity.Metadata.Name} + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read reads teleport WorkloadIdentity +func (r resourceTeleportWorkloadIdentity) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { + var state types.Object + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + var id types.String + diags = req.State.GetAttribute(ctx, path.Root("metadata").AtName("name"), &id) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + workloadIdentityI, err := r.p.Client.GetWorkloadIdentity(ctx, id.Value) + if trace.IsNotFound(err) { + resp.State.RemoveResource(ctx) + return + } + + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + workloadIdentity := workloadIdentityI + diags = schemav1.CopyWorkloadIdentityToTerraform(ctx, workloadIdentity, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Update updates teleport WorkloadIdentity +func (r resourceTeleportWorkloadIdentity) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + if !r.p.IsConfigured(resp.Diagnostics) { + return + } + + var plan types.Object + diags := req.Plan.Get(ctx, &plan) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + workloadIdentity := &workloadidentityv1.WorkloadIdentity{} + diags = schemav1.CopyWorkloadIdentityFromTerraform(ctx, plan, workloadIdentity) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + workloadIdentityResource := workloadIdentity + + + + name := workloadIdentityResource.Metadata.Name + + workloadIdentityBefore, err := r.p.Client.GetWorkloadIdentity(ctx, name) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", err, "workload_identity")) + return + } + + _, err = r.p.Client.UpsertWorkloadIdentity(ctx, workloadIdentityResource) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error updating WorkloadIdentity", err, "workload_identity")) + return + } + var workloadIdentityI *workloadidentityv1.WorkloadIdentity + + tries := 0 + backoff := backoff.NewDecorr(r.p.RetryConfig.Base, r.p.RetryConfig.Cap, clockwork.NewRealClock()) + for { + tries = tries + 1 + workloadIdentityI, err = r.p.Client.GetWorkloadIdentity(ctx, name) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", err, "workload_identity")) + return + } + if workloadIdentityBefore.GetMetadata().Revision != workloadIdentityI.GetMetadata().Revision || false { + break + } + + if err := backoff.Do(ctx); err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + if tries >= r.p.RetryConfig.MaxTries { + diagMessage := fmt.Sprintf("Error reading WorkloadIdentity (tried %d times) - state outdated, please import resource", tries) + resp.Diagnostics.AddError(diagMessage, "workload_identity") + return + } + } + + workloadIdentityResource = workloadIdentityI + + diags = schemav1.CopyWorkloadIdentityToTerraform(ctx, workloadIdentity, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Delete deletes Teleport WorkloadIdentity +func (r resourceTeleportWorkloadIdentity) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + var id types.String + diags := req.State.GetAttribute(ctx, path.Root("metadata").AtName("name"), &id) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + err := r.p.Client.DeleteWorkloadIdentity(ctx, id.Value) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error deleting WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + + resp.State.RemoveResource(ctx) +} + +// ImportState imports WorkloadIdentity state +func (r resourceTeleportWorkloadIdentity) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + workloadIdentity, err := r.p.Client.GetWorkloadIdentity(ctx, req.ID) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + + workloadIdentityResource := workloadIdentity + + + var state types.Object + + diags := resp.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = schemav1.CopyWorkloadIdentityToTerraform(ctx, workloadIdentityResource, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + id := workloadIdentity.Metadata.Name + + state.Attrs["id"] = types.String{Value: id} + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/integrations/terraform/reference.mdx b/integrations/terraform/reference.mdx index 078a1bb73ca08..a5df9cc060e46 100755 --- a/integrations/terraform/reference.mdx +++ b/integrations/terraform/reference.mdx @@ -2051,7 +2051,8 @@ Options is for OpenSSH options like agent forwarding. | max_sessions | number | | MaxSessions defines the maximum number of concurrent sessions per connection. | | permit_x11_forwarding | bool | | PermitX11Forwarding authorizes use of X11 forwarding. | | pin_source_ip | bool | | PinSourceIP forces the same client IP for certificate generation and usage | -| port_forwarding | bool | | | +| ssh_port_forwarding | object | | SSHPortForwarding configures what types of SSH port forwarding are allowed by a role. | +| port_forwarding | bool | | Deprecated: Use SSHPortForwarding instead. | | record_session | object | | RecordDesktopSession indicates whether desktop access sessions should be recorded. It defaults to true unless explicitly set to false. | | request_access | string | | RequestAccess defines the access request strategy (optional|note|always) where optional is the default. | | request_prompt | string | | RequestPrompt is an optional message which tells users what they aught to request. | @@ -2085,6 +2086,31 @@ SAML are options related to the Teleport SAML IdP. |---------|------|----------|-------------| | enabled | bool | | | +##### spec.options.ssh_port_forwarding + +SSHPortForwarding configures what types of SSH port forwarding are allowed by a role. + +| Name | Type | Required | Description | +|--------|--------|----------|-----------------------------------------------------------| +| remote | object | | remote contains options related to remote port forwarding | +| local | object | | local contains options related to local port forwarding | + +###### spec.options.ssh_port_forwarding.remote + +remote contains options related to remote port forwarding + +| Name | Type | Required | Description | +|---------|------|----------|-------------| +| enabled | bool | | | + +###### spec.options.ssh_port_forwarding.local + +local contains options related to local port forwarding + +| Name | Type | Required | Description | +|---------|------|----------|-------------| +| enabled | bool | | | + ##### spec.options.record_session RecordDesktopSession indicates whether desktop access sessions should be recorded. It defaults to true unless explicitly set to false. @@ -2114,11 +2140,19 @@ resource "teleport_role" "example" { options = { forward_agent = false max_session_ttl = "7m" - port_forwarding = false client_idle_timeout = "1h" disconnect_expired_cert = true permit_x11_forwarding = false request_access = "denied" + ssh_port_forwarding = { + remote = { + enabled = false + } + + local = { + enabled = false + } + } } allow = { diff --git a/integrations/terraform/testlib/fixtures/workload_identity_0_create.tf b/integrations/terraform/testlib/fixtures/workload_identity_0_create.tf new file mode 100644 index 0000000000000..b5d0ebe8aae08 --- /dev/null +++ b/integrations/terraform/testlib/fixtures/workload_identity_0_create.tf @@ -0,0 +1,21 @@ +resource "teleport_workload_identity" "test" { + version = "v1" + metadata = { + name = "test" + } + spec = { + rules = { + allow = [ + { + conditions = [{ + attribute = "user.name" + equals = "foo" + }] + } + ] + } + spiffe = { + id = "/test" + } + } +} \ No newline at end of file diff --git a/integrations/terraform/testlib/fixtures/workload_identity_1_update.tf b/integrations/terraform/testlib/fixtures/workload_identity_1_update.tf new file mode 100644 index 0000000000000..cced0a4f8ecdd --- /dev/null +++ b/integrations/terraform/testlib/fixtures/workload_identity_1_update.tf @@ -0,0 +1,21 @@ +resource "teleport_workload_identity" "test" { + version = "v1" + metadata = { + name = "test" + } + spec = { + rules = { + allow = [ + { + conditions = [{ + attribute = "user.name" + equals = "foo" + }] + } + ] + } + spiffe = { + id = "/test/updated" + } + } +} \ No newline at end of file diff --git a/integrations/terraform/testlib/workload_identity_test.go b/integrations/terraform/testlib/workload_identity_test.go new file mode 100644 index 0000000000000..1e6d84cf6feb9 --- /dev/null +++ b/integrations/terraform/testlib/workload_identity_test.go @@ -0,0 +1,143 @@ +// Copyright 2024 Gravitational, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testlib + +import ( + "context" + "fmt" + "time" + + "github.com/gravitational/trace" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/stretchr/testify/require" + + v1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" + "github.com/gravitational/teleport/api/types" +) + +func (s *TerraformSuiteOSS) TestWorkloadIdentity() { + t := s.T() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + checkDestroyed := func(state *terraform.State) error { + _, err := s.client.GetWorkloadIdentity(ctx, "test") + if trace.IsNotFound(err) { + return nil + } + return trace.Errorf("expected not found, actual: %v", err) + } + + name := "teleport_workload_identity.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + CheckDestroy: checkDestroyed, + IsUnitTest: true, + Steps: []resource.TestStep{ + { + Config: s.getFixture("workload_identity_0_create.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "kind", "workload_identity"), + resource.TestCheckResourceAttr(name, "spec.spiffe.id", "/test"), + resource.TestCheckResourceAttr(name, "spec.rules.allow.0.conditions.0.attribute", "user.name"), + resource.TestCheckResourceAttr(name, "spec.rules.allow.0.conditions.0.equals", "foo"), + ), + }, + { + Config: s.getFixture("workload_identity_0_create.tf"), + PlanOnly: true, + }, + { + Config: s.getFixture("workload_identity_1_update.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "kind", "workload_identity"), + resource.TestCheckResourceAttr(name, "spec.spiffe.id", "/test/updated"), + resource.TestCheckResourceAttr(name, "spec.rules.allow.0.conditions.0.attribute", "user.name"), + resource.TestCheckResourceAttr(name, "spec.rules.allow.0.conditions.0.equals", "foo"), + ), + }, + { + Config: s.getFixture("workload_identity_1_update.tf"), + PlanOnly: true, + }, + }, + }) +} + +func (s *TerraformSuiteOSS) TestImportWorkloadIdentity() { + t := s.T() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + r := "teleport_workload_identity" + id := "test_import" + name := r + "." + id + + shu := &workloadidentityv1pb.WorkloadIdentity{ + Metadata: &v1.Metadata{ + Name: id, + }, + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + { + Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + Attribute: "user.name", + Equals: "foo", + }, + }, + }, + }, + }, + Spiffe: &workloadidentityv1pb.WorkloadIdentitySPIFFE{ + Id: "/test", + }, + }, + } + shu, err := s.client.CreateWorkloadIdentity(ctx, shu) + require.NoError(t, err) + + require.Eventually(t, func() bool { + _, err := s.client.GetWorkloadIdentity(ctx, shu.GetMetadata().Name) + return err == nil + }, 5*time.Second, time.Second) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + IsUnitTest: true, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf("%s\nresource %q %q { }", s.terraformConfig, r, id), + ResourceName: name, + ImportState: true, + ImportStateId: id, + ImportStateCheck: func(state []*terraform.InstanceState) error { + require.Equal(t, types.KindWorkloadIdentity, state[0].Attributes["kind"]) + require.Equal(t, "/test", state[0].Attributes["spec.spiffe.id"]) + require.Equal(t, "user.name", state[0].Attributes["spec.rules.allow.0.conditions.0.attribute"]) + require.Equal(t, "foo", state[0].Attributes["spec.rules.allow.0.conditions.0.equals"]) + + return nil + }, + }, + }, + }) +} diff --git a/integrations/terraform/tfschema/workloadidentity/v1/custom_types.go b/integrations/terraform/tfschema/workloadidentity/v1/custom_types.go new file mode 100644 index 0000000000000..eb615d5b1888b --- /dev/null +++ b/integrations/terraform/tfschema/workloadidentity/v1/custom_types.go @@ -0,0 +1,25 @@ +// Teleport +// Copyright (C) 2024 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package v1 + +import "github.com/gravitational/teleport/integrations/terraform/tfschema/resource153" + +var ( + GenSchemaTimestamp = resource153.GenSchemaTimestamp + CopyToTimestamp = resource153.CopyToTimestamp + CopyFromTimestamp = resource153.CopyFromTimestamp +) diff --git a/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go b/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go new file mode 100644 index 0000000000000..f6510d28f7294 --- /dev/null +++ b/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go @@ -0,0 +1,1177 @@ +/* +Copyright 2015-2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: teleport/workloadidentity/v1/resource.proto + +package v1 + +import ( + context "context" + fmt "fmt" + math "math" + + proto "github.com/gogo/protobuf/proto" + _ "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + github_com_gravitational_teleport_api_gen_proto_go_teleport_header_v1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" + github_com_gravitational_teleport_integrations_terraform_tfschema "github.com/gravitational/teleport/integrations/terraform/tfschema" + github_com_hashicorp_terraform_plugin_framework_attr "github.com/hashicorp/terraform-plugin-framework/attr" + github_com_hashicorp_terraform_plugin_framework_diag "github.com/hashicorp/terraform-plugin-framework/diag" + github_com_hashicorp_terraform_plugin_framework_tfsdk "github.com/hashicorp/terraform-plugin-framework/tfsdk" + github_com_hashicorp_terraform_plugin_framework_types "github.com/hashicorp/terraform-plugin-framework/types" + github_com_hashicorp_terraform_plugin_go_tftypes "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// GenSchemaWorkloadIdentity returns tfsdk.Schema definition for WorkloadIdentity +func GenSchemaWorkloadIdentity(ctx context.Context) (github_com_hashicorp_terraform_plugin_framework_tfsdk.Schema, github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics) { + return github_com_hashicorp_terraform_plugin_framework_tfsdk.Schema{Attributes: map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "id": { + Computed: true, + Optional: false, + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.UseStateForUnknown()}, + Required: false, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "kind": { + Computed: true, + Description: "The kind of resource represented.", + Optional: true, + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.UseStateForUnknown()}, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "metadata": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "description": { + Description: "description is object description.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "expires": GenSchemaTimestamp(ctx, github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + Description: "expires is a global expiry time header can be set on any resource in the system.", + Optional: true, + Validators: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributeValidator{github_com_gravitational_teleport_integrations_terraform_tfschema.MustTimeBeInFuture()}, + }), + "labels": { + Description: "labels is a set of labels.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.MapType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType}, + }, + "name": { + Description: "name is an object name.", + Optional: true, + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.RequiresReplace()}, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "namespace": { + Computed: true, + Description: "namespace is object namespace. The field should be called \"namespace\" when it returns in Teleport 2.4.", + Optional: true, + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.UseStateForUnknown()}, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "revision": { + Description: "revision is an opaque identifier which tracks the versions of a resource over time. Clients should ignore and not alter its value but must return the revision in any updates of a resource.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), + Description: "Common metadata that all resources share.", + Optional: true, + }, + "spec": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "rules": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"allow": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.ListNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"conditions": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.ListNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "attribute": { + Description: "The name of the attribute to evaluate the condition against.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "equals": { + Description: "An exact string that the attribute must match.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), + Description: "The conditions that must be met for this rule to be considered passed.", + Optional: true, + }}), + Description: "A list of rules used to determine if a WorkloadIdentity can be issued. If none are provided, it will be considered a pass. If any are provided, then at least one must pass for the rules to be considered passed.", + Optional: true, + }}), + Description: "The rules which are evaluated before the WorkloadIdentity can be issued.", + Optional: true, + }, + "spiffe": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "hint": { + Description: "A freeform text field which is provided to workloads along with a credential produced by this WorkloadIdentity. This can be used to provide additional context that can be used to select between multiple credentials.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "id": { + Description: "The path of the SPIFFE ID that will be issued to the workload. This should be prefixed with a forward-slash (\"/\"). This field supports templating using attributes.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), + Description: "Configuration pertaining to the issuance of SPIFFE-compatible workload identity credentials.", + Optional: true, + }, + }), + Description: "The configured properties of the WorkloadIdentity", + Optional: true, + }, + "sub_kind": { + Description: "Differentiates variations of the same kind. All resources should contain one, even if it is never populated.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "version": { + Description: "The version of the resource being represented.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }}, nil +} + +// CopyWorkloadIdentityFromTerraform copies contents of the source Terraform object into a target struct +func CopyWorkloadIdentityFromTerraform(_ context.Context, tf github_com_hashicorp_terraform_plugin_framework_types.Object, obj *github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentity) github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics { + var diags github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics + { + a, ok := tf.Attrs["kind"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.kind"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.kind", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Kind = t + } + } + } + { + a, ok := tf.Attrs["sub_kind"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.sub_kind"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.sub_kind", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.SubKind = t + } + } + } + { + a, ok := tf.Attrs["version"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.version"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.version", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Version = t + } + } + } + { + a, ok := tf.Attrs["metadata"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.metadata"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.metadata", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.Metadata = nil + if !v.Null && !v.Unknown { + tf := v + obj.Metadata = &github_com_gravitational_teleport_api_gen_proto_go_teleport_header_v1.Metadata{} + obj := obj.Metadata + { + a, ok := tf.Attrs["name"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.metadata.name"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.metadata.name", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Name = t + } + } + } + { + a, ok := tf.Attrs["namespace"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.metadata.namespace"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.metadata.namespace", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Namespace = t + } + } + } + { + a, ok := tf.Attrs["description"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.metadata.description"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.metadata.description", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Description = t + } + } + } + { + a, ok := tf.Attrs["labels"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.metadata.labels"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Map) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.metadata.labels", "github.com/hashicorp/terraform-plugin-framework/types.Map"}) + } else { + obj.Labels = make(map[string]string, len(v.Elems)) + if !v.Null && !v.Unknown { + for k, a := range v.Elems { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.metadata.labels", "github_com_hashicorp_terraform_plugin_framework_types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Labels[k] = t + } + } + } + } + } + } + { + a, ok := tf.Attrs["expires"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.metadata.expires"}) + } + CopyFromTimestamp(diags, a, &obj.Expires) + } + { + a, ok := tf.Attrs["revision"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.metadata.revision"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.metadata.revision", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Revision = t + } + } + } + } + } + } + } + { + a, ok := tf.Attrs["spec"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.Spec = nil + if !v.Null && !v.Unknown { + tf := v + obj.Spec = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentitySpec{} + obj := obj.Spec + { + a, ok := tf.Attrs["rules"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.Rules = nil + if !v.Null && !v.Unknown { + tf := v + obj.Rules = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityRules{} + obj := obj.Rules + { + a, ok := tf.Attrs["allow"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow", "github.com/hashicorp/terraform-plugin-framework/types.List"}) + } else { + obj.Allow = make([]*github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityRule, len(v.Elems)) + if !v.Null && !v.Unknown { + for k, a := range v.Elems { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow", "github_com_hashicorp_terraform_plugin_framework_types.Object"}) + } else { + var t *github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityRule + if !v.Null && !v.Unknown { + tf := v + t = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityRule{} + obj := t + { + a, ok := tf.Attrs["conditions"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions", "github.com/hashicorp/terraform-plugin-framework/types.List"}) + } else { + obj.Conditions = make([]*github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition, len(v.Elems)) + if !v.Null && !v.Unknown { + for k, a := range v.Elems { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions", "github_com_hashicorp_terraform_plugin_framework_types.Object"}) + } else { + var t *github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition + if !v.Null && !v.Unknown { + tf := v + t = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition{} + obj := t + { + a, ok := tf.Attrs["attribute"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.attribute"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.attribute", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Attribute = t + } + } + } + { + a, ok := tf.Attrs["equals"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.equals"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.equals", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Equals = t + } + } + } + } + obj.Conditions[k] = t + } + } + } + } + } + } + } + obj.Allow[k] = t + } + } + } + } + } + } + } + } + } + } + { + a, ok := tf.Attrs["spiffe"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.spiffe"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.spiffe", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.Spiffe = nil + if !v.Null && !v.Unknown { + tf := v + obj.Spiffe = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentitySPIFFE{} + obj := obj.Spiffe + { + a, ok := tf.Attrs["id"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.spiffe.id"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.spiffe.id", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Id = t + } + } + } + { + a, ok := tf.Attrs["hint"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.spiffe.hint"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.spiffe.hint", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Hint = t + } + } + } + } + } + } + } + } + } + } + } + return diags +} + +// CopyWorkloadIdentityToTerraform copies contents of the source Terraform object into a target struct +func CopyWorkloadIdentityToTerraform(ctx context.Context, obj *github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentity, tf *github_com_hashicorp_terraform_plugin_framework_types.Object) github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics { + var diags github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics + tf.Null = false + tf.Unknown = false + if tf.Attrs == nil { + tf.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value) + } + { + t, ok := tf.AttrTypes["kind"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.kind"}) + } else { + v, ok := tf.Attrs["kind"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.kind", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.kind", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Kind) == "" + } + v.Value = string(obj.Kind) + v.Unknown = false + tf.Attrs["kind"] = v + } + } + { + t, ok := tf.AttrTypes["sub_kind"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.sub_kind"}) + } else { + v, ok := tf.Attrs["sub_kind"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.sub_kind", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.sub_kind", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.SubKind) == "" + } + v.Value = string(obj.SubKind) + v.Unknown = false + tf.Attrs["sub_kind"] = v + } + } + { + t, ok := tf.AttrTypes["version"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.version"}) + } else { + v, ok := tf.Attrs["version"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.version", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.version", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Version) == "" + } + v.Value = string(obj.Version) + v.Unknown = false + tf.Attrs["version"] = v + } + } + { + a, ok := tf.AttrTypes["metadata"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.metadata"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.metadata", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["metadata"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + if obj.Metadata == nil { + v.Null = true + } else { + obj := obj.Metadata + tf := &v + { + t, ok := tf.AttrTypes["name"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.metadata.name"}) + } else { + v, ok := tf.Attrs["name"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.metadata.name", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.metadata.name", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Name) == "" + } + v.Value = string(obj.Name) + v.Unknown = false + tf.Attrs["name"] = v + } + } + { + t, ok := tf.AttrTypes["namespace"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.metadata.namespace"}) + } else { + v, ok := tf.Attrs["namespace"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.metadata.namespace", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.metadata.namespace", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Namespace) == "" + } + v.Value = string(obj.Namespace) + v.Unknown = false + tf.Attrs["namespace"] = v + } + } + { + t, ok := tf.AttrTypes["description"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.metadata.description"}) + } else { + v, ok := tf.Attrs["description"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.metadata.description", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.metadata.description", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Description) == "" + } + v.Value = string(obj.Description) + v.Unknown = false + tf.Attrs["description"] = v + } + } + { + a, ok := tf.AttrTypes["labels"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.metadata.labels"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.MapType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.metadata.labels", "github.com/hashicorp/terraform-plugin-framework/types.MapType"}) + } else { + c, ok := tf.Attrs["labels"].(github_com_hashicorp_terraform_plugin_framework_types.Map) + if !ok { + c = github_com_hashicorp_terraform_plugin_framework_types.Map{ + + ElemType: o.ElemType, + Elems: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Labels)), + Null: true, + } + } else { + if c.Elems == nil { + c.Elems = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Labels)) + } + } + if obj.Labels != nil { + t := o.ElemType + for k, a := range obj.Labels { + v, ok := tf.Attrs["labels"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.metadata.labels", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.metadata.labels", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = false + } + v.Value = string(a) + v.Unknown = false + c.Elems[k] = v + } + if len(obj.Labels) > 0 { + c.Null = false + } + } + c.Unknown = false + tf.Attrs["labels"] = c + } + } + } + { + t, ok := tf.AttrTypes["expires"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.metadata.expires"}) + } else { + v := CopyToTimestamp(diags, obj.Expires, t, tf.Attrs["expires"]) + tf.Attrs["expires"] = v + } + } + { + t, ok := tf.AttrTypes["revision"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.metadata.revision"}) + } else { + v, ok := tf.Attrs["revision"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.metadata.revision", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.metadata.revision", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Revision) == "" + } + v.Value = string(obj.Revision) + v.Unknown = false + tf.Attrs["revision"] = v + } + } + } + v.Unknown = false + tf.Attrs["metadata"] = v + } + } + } + { + a, ok := tf.AttrTypes["spec"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["spec"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + if obj.Spec == nil { + v.Null = true + } else { + obj := obj.Spec + tf := &v + { + a, ok := tf.AttrTypes["rules"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["rules"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + if obj.Rules == nil { + v.Null = true + } else { + obj := obj.Rules + tf := &v + { + a, ok := tf.AttrTypes["allow"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ListType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow", "github.com/hashicorp/terraform-plugin-framework/types.ListType"}) + } else { + c, ok := tf.Attrs["allow"].(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + c = github_com_hashicorp_terraform_plugin_framework_types.List{ + + ElemType: o.ElemType, + Elems: make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Allow)), + Null: true, + } + } else { + if c.Elems == nil { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Allow)) + } + } + if obj.Allow != nil { + o := o.ElemType.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if len(obj.Allow) != len(c.Elems) { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Allow)) + } + for k, a := range obj.Allow { + v, ok := tf.Attrs["allow"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + if a == nil { + v.Null = true + } else { + obj := a + tf := &v + { + a, ok := tf.AttrTypes["conditions"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ListType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions", "github.com/hashicorp/terraform-plugin-framework/types.ListType"}) + } else { + c, ok := tf.Attrs["conditions"].(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + c = github_com_hashicorp_terraform_plugin_framework_types.List{ + + ElemType: o.ElemType, + Elems: make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Conditions)), + Null: true, + } + } else { + if c.Elems == nil { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Conditions)) + } + } + if obj.Conditions != nil { + o := o.ElemType.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if len(obj.Conditions) != len(c.Elems) { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Conditions)) + } + for k, a := range obj.Conditions { + v, ok := tf.Attrs["conditions"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + if a == nil { + v.Null = true + } else { + obj := a + tf := &v + { + t, ok := tf.AttrTypes["attribute"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.attribute"}) + } else { + v, ok := tf.Attrs["attribute"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.spec.rules.allow.conditions.attribute", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.attribute", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Attribute) == "" + } + v.Value = string(obj.Attribute) + v.Unknown = false + tf.Attrs["attribute"] = v + } + } + { + t, ok := tf.AttrTypes["equals"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.equals"}) + } else { + v, ok := tf.Attrs["equals"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.spec.rules.allow.conditions.equals", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.equals", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Equals) == "" + } + v.Value = string(obj.Equals) + v.Unknown = false + tf.Attrs["equals"] = v + } + } + } + v.Unknown = false + c.Elems[k] = v + } + if len(obj.Conditions) > 0 { + c.Null = false + } + } + c.Unknown = false + tf.Attrs["conditions"] = c + } + } + } + } + v.Unknown = false + c.Elems[k] = v + } + if len(obj.Allow) > 0 { + c.Null = false + } + } + c.Unknown = false + tf.Attrs["allow"] = c + } + } + } + } + v.Unknown = false + tf.Attrs["rules"] = v + } + } + } + { + a, ok := tf.AttrTypes["spiffe"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.spiffe"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.spiffe", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["spiffe"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + if obj.Spiffe == nil { + v.Null = true + } else { + obj := obj.Spiffe + tf := &v + { + t, ok := tf.AttrTypes["id"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.spiffe.id"}) + } else { + v, ok := tf.Attrs["id"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.spec.spiffe.id", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.spiffe.id", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Id) == "" + } + v.Value = string(obj.Id) + v.Unknown = false + tf.Attrs["id"] = v + } + } + { + t, ok := tf.AttrTypes["hint"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.spiffe.hint"}) + } else { + v, ok := tf.Attrs["hint"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.spec.spiffe.hint", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.spiffe.hint", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Hint) == "" + } + v.Value = string(obj.Hint) + v.Unknown = false + tf.Attrs["hint"] = v + } + } + } + v.Unknown = false + tf.Attrs["spiffe"] = v + } + } + } + } + v.Unknown = false + tf.Attrs["spec"] = v + } + } + } + return diags +} + +// attrReadMissingDiag represents diagnostic message on an attribute missing in the source object +type attrReadMissingDiag struct { + Path string +} + +func (d attrReadMissingDiag) Severity() github_com_hashicorp_terraform_plugin_framework_diag.Severity { + return github_com_hashicorp_terraform_plugin_framework_diag.SeverityError +} + +func (d attrReadMissingDiag) Summary() string { + return "Error reading from Terraform object" +} + +func (d attrReadMissingDiag) Detail() string { + return fmt.Sprintf("A value for %v is missing in the source Terraform object Attrs", d.Path) +} + +func (d attrReadMissingDiag) Equal(o github_com_hashicorp_terraform_plugin_framework_diag.Diagnostic) bool { + return (d.Severity() == o.Severity()) && (d.Summary() == o.Summary()) && (d.Detail() == o.Detail()) +} + +// attrReadConversionFailureDiag represents diagnostic message on a failed type conversion on read +type attrReadConversionFailureDiag struct { + Path string + Type string +} + +func (d attrReadConversionFailureDiag) Severity() github_com_hashicorp_terraform_plugin_framework_diag.Severity { + return github_com_hashicorp_terraform_plugin_framework_diag.SeverityError +} + +func (d attrReadConversionFailureDiag) Summary() string { + return "Error reading from Terraform object" +} + +func (d attrReadConversionFailureDiag) Detail() string { + return fmt.Sprintf("A value for %v can not be converted to %v", d.Path, d.Type) +} + +func (d attrReadConversionFailureDiag) Equal(o github_com_hashicorp_terraform_plugin_framework_diag.Diagnostic) bool { + return (d.Severity() == o.Severity()) && (d.Summary() == o.Summary()) && (d.Detail() == o.Detail()) +} + +// attrWriteMissingDiag represents diagnostic message on an attribute missing in the target object +type attrWriteMissingDiag struct { + Path string +} + +func (d attrWriteMissingDiag) Severity() github_com_hashicorp_terraform_plugin_framework_diag.Severity { + return github_com_hashicorp_terraform_plugin_framework_diag.SeverityError +} + +func (d attrWriteMissingDiag) Summary() string { + return "Error writing to Terraform object" +} + +func (d attrWriteMissingDiag) Detail() string { + return fmt.Sprintf("A value for %v is missing in the source Terraform object AttrTypes", d.Path) +} + +func (d attrWriteMissingDiag) Equal(o github_com_hashicorp_terraform_plugin_framework_diag.Diagnostic) bool { + return (d.Severity() == o.Severity()) && (d.Summary() == o.Summary()) && (d.Detail() == o.Detail()) +} + +// attrWriteConversionFailureDiag represents diagnostic message on a failed type conversion on write +type attrWriteConversionFailureDiag struct { + Path string + Type string +} + +func (d attrWriteConversionFailureDiag) Severity() github_com_hashicorp_terraform_plugin_framework_diag.Severity { + return github_com_hashicorp_terraform_plugin_framework_diag.SeverityError +} + +func (d attrWriteConversionFailureDiag) Summary() string { + return "Error writing to Terraform object" +} + +func (d attrWriteConversionFailureDiag) Detail() string { + return fmt.Sprintf("A value for %v can not be converted to %v", d.Path, d.Type) +} + +func (d attrWriteConversionFailureDiag) Equal(o github_com_hashicorp_terraform_plugin_framework_diag.Diagnostic) bool { + return (d.Severity() == o.Severity()) && (d.Summary() == o.Summary()) && (d.Detail() == o.Detail()) +} + +// attrWriteGeneralError represents diagnostic message on a generic error on write +type attrWriteGeneralError struct { + Path string + Err error +} + +func (d attrWriteGeneralError) Severity() github_com_hashicorp_terraform_plugin_framework_diag.Severity { + return github_com_hashicorp_terraform_plugin_framework_diag.SeverityError +} + +func (d attrWriteGeneralError) Summary() string { + return "Error writing to Terraform object" +} + +func (d attrWriteGeneralError) Detail() string { + return fmt.Sprintf("%s: %s", d.Path, d.Err.Error()) +} + +func (d attrWriteGeneralError) Equal(o github_com_hashicorp_terraform_plugin_framework_diag.Diagnostic) bool { + return (d.Severity() == o.Severity()) && (d.Summary() == o.Summary()) && (d.Detail() == o.Detail()) +} diff --git a/lib/auth/auth.go b/lib/auth/auth.go index c240ad6fc585f..7d15e272af2c2 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -652,7 +652,7 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (*Server, error) { // Add in a login hook for generating state during user login. as.ulsGenerator, err = userloginstate.NewGenerator(userloginstate.GeneratorConfig{ - Log: log, + Log: as.logger, AccessLists: &as, Access: &as, UsageEvents: &as, @@ -1508,12 +1508,11 @@ func (a *Server) runPeriodicOperations() { heartbeatsMissedByAuth.Inc() } + if srv.GetSubKind() != types.SubKindOpenSSHNode { + return false, nil + } // TODO(tross) DELETE in v20.0.0 - all invalid hostnames should have been sanitized by then. if !validServerHostname(srv.GetHostname()) { - if srv.GetSubKind() != types.SubKindOpenSSHNode { - return false, nil - } - logger := a.logger.With("server", srv.GetName(), "hostname", srv.GetHostname()) logger.DebugContext(a.closeCtx, "sanitizing invalid static SSH server hostname") @@ -1527,6 +1526,17 @@ func (a *Server) runPeriodicOperations() { if _, err := a.Services.UpdateNode(a.closeCtx, srv); err != nil && !trace.IsCompareFailed(err) { logger.WarnContext(a.closeCtx, "failed to update SSH server hostname", "error", err) } + } else if oldHostname, ok := srv.GetLabel(replacedHostnameLabel); ok && validServerHostname(oldHostname) { + // If the hostname has been replaced by a sanitized version, revert it back to the original + // if the original is valid under the most recent rules. + logger := a.logger.With("server", srv.GetName(), "old_hostname", oldHostname, "sanitized_hostname", srv.GetHostname()) + if err := restoreSanitizedHostname(srv); err != nil { + logger.WarnContext(a.closeCtx, "failed to restore sanitized static SSH server hostname", "error", err) + return false, nil + } + if _, err := a.Services.UpdateNode(a.closeCtx, srv); err != nil && !trace.IsCompareFailed(err) { + log.Warnf("Failed to update node hostname: %v", err) + } } return false, nil @@ -5650,7 +5660,7 @@ func (a *Server) KeepAliveServer(ctx context.Context, h types.KeepAlive) error { const ( serverHostnameMaxLen = 256 - serverHostnameRegexPattern = `^[a-zA-Z0-9]([\.-]?[a-zA-Z0-9]+)*$` + serverHostnameRegexPattern = `^[a-zA-Z0-9]+[a-zA-Z0-9\.-]*$` replacedHostnameLabel = types.TeleportInternalLabelPrefix + "invalid-hostname" ) @@ -5658,7 +5668,7 @@ var serverHostnameRegex = regexp.MustCompile(serverHostnameRegexPattern) // validServerHostname returns false if the hostname is longer than 256 characters or // does not entirely consist of alphanumeric characters as well as '-' and '.'. A valid hostname also -// cannot begin with a symbol, and a symbol cannot be followed immediately by another symbol. +// cannot begin with a symbol. func validServerHostname(hostname string) bool { return len(hostname) <= serverHostnameMaxLen && serverHostnameRegex.MatchString(hostname) } @@ -5697,6 +5707,26 @@ func sanitizeHostname(server types.Server) error { return nil } +// restoreSanitizedHostname restores the original hostname of a server and removes the label. +func restoreSanitizedHostname(server types.Server) error { + oldHostname, ok := server.GetLabels()[replacedHostnameLabel] + // if the label is not present or the hostname is invalid under the most recent rules, do nothing. + if !ok || !validServerHostname(oldHostname) { + return nil + } + + switch s := server.(type) { + case *types.ServerV2: + // restore the original hostname and remove the label. + s.Spec.Hostname = oldHostname + delete(s.Metadata.Labels, replacedHostnameLabel) + default: + return trace.BadParameter("invalid server provided") + } + + return nil +} + // UpsertNode implements [services.Presence] by delegating to [Server.Services] // and potentially emitting a [usagereporter] event. func (a *Server) UpsertNode(ctx context.Context, server types.Server) (*types.KeepAlive, error) { diff --git a/lib/auth/auth_test.go b/lib/auth/auth_test.go index e4978e32e358a..b9e9eb0a5aa8b 100644 --- a/lib/auth/auth_test.go +++ b/lib/auth/auth_test.go @@ -1262,7 +1262,7 @@ func TestTrustedClusterCRUDEventEmitted(t *testing.T) { // test create event for switch case: when tc exists but enabled is false tc.SetEnabled(false) - _, err = s.a.UpsertTrustedCluster(ctx, tc) + _, err = s.a.UpsertTrustedClusterV2(ctx, tc) require.NoError(t, err) require.Equal(t, events.TrustedClusterCreateEvent, s.mockEmitter.LastEvent().GetType()) createEvt := s.mockEmitter.LastEvent().(*apievents.TrustedClusterCreate) @@ -1272,7 +1272,7 @@ func TestTrustedClusterCRUDEventEmitted(t *testing.T) { // test create event for switch case: when tc exists but enabled is true tc.SetEnabled(true) - _, err = s.a.UpsertTrustedCluster(ctx, tc) + _, err = s.a.UpsertTrustedClusterV2(ctx, tc) require.NoError(t, err) require.Equal(t, events.TrustedClusterCreateEvent, s.mockEmitter.LastEvent().GetType()) createEvt = s.mockEmitter.LastEvent().(*apievents.TrustedClusterCreate) @@ -4478,6 +4478,10 @@ func TestServerHostnameSanitization(t *testing.T) { name: "uuid dns hostname", hostname: uuid.NewString() + ".example.com", }, + { + name: "valid dns hostname with multi-dots", + hostname: "llama..example.com", + }, { name: "empty hostname", hostname: "", @@ -4488,11 +4492,6 @@ func TestServerHostnameSanitization(t *testing.T) { hostname: strings.Repeat("a", serverHostnameMaxLen*2), invalidHostname: true, }, - { - name: "invalid dns hostname", - hostname: "llama..example.com", - invalidHostname: true, - }, { name: "spaces in hostname", hostname: "the quick brown fox jumps over the lazy dog", @@ -4562,3 +4561,74 @@ func TestServerHostnameSanitization(t *testing.T) { }) } } + +func TestValidServerHostname(t *testing.T) { + t.Parallel() + tests := []struct { + name string + hostname string + want bool + }{ + { + name: "valid dns hostname", + hostname: "llama.example.com", + want: true, + }, + { + name: "valid friendly hostname", + hostname: "llama", + want: true, + }, + { + name: "uuid hostname", + hostname: uuid.NewString(), + want: true, + }, + { + name: "valid hostname with multi-dashes", + hostname: "llama--example.com", + want: true, + }, + { + name: "valid hostname with multi-dots", + hostname: "llama..example.com", + want: true, + }, + { + name: "valid hostname with numbers", + hostname: "llama9", + want: true, + }, + { + name: "hostname with invalid characters", + hostname: "llama?!$", + want: false, + }, + { + name: "super long hostname", + hostname: strings.Repeat("a", serverHostnameMaxLen*2), + want: false, + }, + { + name: "hostname with spaces", + hostname: "the quick brown fox jumps over the lazy dog", + want: false, + }, + { + name: "hostname with ;", + hostname: "llama;example.com", + want: false, + }, + { + name: "empty hostname", + hostname: "", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := validServerHostname(tt.hostname) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index e5fdaa9dab8ce..c48bdf2684c83 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -1648,22 +1648,23 @@ func (a *ServerWithRoles) GetSSHTargets(ctx context.Context, req *proto.GetSSHTa return nil, trace.Wrap(err) } - lreq := proto.ListResourcesRequest{ - ResourceType: types.KindNode, + lreq := &proto.ListUnifiedResourcesRequest{ + Kinds: []string{types.KindNode}, + SortBy: types.SortBy{Field: types.ResourceMetadataName}, UseSearchAsRoles: true, } var servers []*types.ServerV2 for { - // note that we're calling ServerWithRoles.ListResources here rather than some internal method. This method + // note that we're calling ServerWithRoles.ListUnifiedResources here rather than some internal method. This method // delegates all RBAC filtering to ListResources, and then performs additional filtering on top of that. - lrsp, err := a.ListResources(ctx, lreq) + lrsp, err := a.ListUnifiedResources(ctx, lreq) if err != nil { return nil, trace.Wrap(err) } for _, rsc := range lrsp.Resources { - srv, ok := rsc.(*types.ServerV2) - if !ok { + srv := rsc.GetNode() + if srv == nil { log.Warnf("Unexpected resource type %T, expected *types.ServerV2 (skipping)", rsc) continue } @@ -1687,6 +1688,92 @@ func (a *ServerWithRoles) GetSSHTargets(ctx context.Context, req *proto.GetSSHTa }, nil } +// ResolveSSHTarget gets a server that would match an equivalent ssh dial request. +func (a *ServerWithRoles) ResolveSSHTarget(ctx context.Context, req *proto.ResolveSSHTargetRequest) (*proto.ResolveSSHTargetResponse, error) { + // try to detect case-insensitive routing setting, but default to false if we can't load + // networking config (equivalent to proxy routing behavior). + var routeToMostRecent bool + if cfg, err := a.authServer.GetReadOnlyClusterNetworkingConfig(ctx); err == nil { + routeToMostRecent = cfg.GetRoutingStrategy() == types.RoutingStrategy_MOST_RECENT + } + + var servers []*types.ServerV2 + switch { + case req.Host != "": + if len(req.Labels) > 0 || req.PredicateExpression != "" || len(req.SearchKeywords) > 0 { + a.authServer.logger.WarnContext(ctx, "ssh target resolution request contained both host and a resource matcher - ignoring resource matcher") + } + + resp, err := a.GetSSHTargets(ctx, &proto.GetSSHTargetsRequest{ + Host: req.Host, + Port: req.Port, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + servers = resp.Servers + case len(req.Labels) > 0 || req.PredicateExpression != "" || len(req.SearchKeywords) > 0: + lreq := &proto.ListUnifiedResourcesRequest{ + Kinds: []string{types.KindNode}, + SortBy: types.SortBy{Field: types.ResourceMetadataName}, + Labels: req.Labels, + PredicateExpression: req.PredicateExpression, + SearchKeywords: req.SearchKeywords, + } + for { + // note that we're calling ServerWithRoles.ListUnifiedResources here rather than some internal method. This method + // delegates all RBAC filtering to ListResources, and then performs additional filtering on top of that. + lrsp, err := a.ListUnifiedResources(ctx, lreq) + if err != nil { + return nil, trace.Wrap(err) + } + + for _, rsc := range lrsp.Resources { + srv := rsc.GetNode() + if srv == nil { + log.Warnf("Unexpected resource type %T, expected *types.ServerV2 (skipping)", rsc) + continue + } + + servers = append(servers, srv) + } + + // If the routing strategy doesn't permit ambiguous matches, then abort + // early if more than one server has been found already + if !routeToMostRecent && len(servers) > 1 { + break + } + + if lrsp.NextKey == "" || len(lrsp.Resources) == 0 { + break + } + + lreq.StartKey = lrsp.NextKey + + } + default: + return nil, trace.BadParameter("request did not contain any host information or resource matcher") + } + + switch len(servers) { + case 1: + return &proto.ResolveSSHTargetResponse{Server: servers[0]}, nil + case 0: + return nil, trace.NotFound("no matching hosts") + default: + if !routeToMostRecent { + return nil, trace.Wrap(teleport.ErrNodeIsAmbiguous) + } + + // Return the most recent version of the resource. + server := slices.MaxFunc(servers, func(a, b *types.ServerV2) int { + return a.Expiry().Compare(b.Expiry()) + }) + return &proto.ResolveSSHTargetResponse{Server: server}, nil + } +} + // ListResources returns a paginated list of resources filtered by user access. func (a *ServerWithRoles) ListResources(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error) { // Check if auth server has a license for this resource type but only return an @@ -3021,7 +3108,7 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC if err != nil { return nil, trace.Wrap(err) } - if err := a.verifyUserDeviceForCertIssuance(req.Usage, readOnlyAuthPref.GetDeviceTrust()); err != nil { + if err := a.verifyUserDeviceForCertIssuance(ctx, req.Usage, readOnlyAuthPref.GetDeviceTrust()); err != nil { return nil, trace.Wrap(err) } @@ -3417,14 +3504,14 @@ func (a *ServerWithRoles) GetAccessRequestAllowedPromotions(ctx context.Context, // is not paramount to the access system itself, but it stops bad attempts from // progressing further and provides better feedback than other protocol-specific // failures. -func (a *ServerWithRoles) verifyUserDeviceForCertIssuance(usage proto.UserCertsRequest_CertUsage, dt *types.DeviceTrust) error { +func (a *ServerWithRoles) verifyUserDeviceForCertIssuance(ctx context.Context, usage proto.UserCertsRequest_CertUsage, dt *types.DeviceTrust) error { // Ignore App or WindowsDeskop requests, they do not support device trust. if usage == proto.UserCertsRequest_App || usage == proto.UserCertsRequest_WindowsDesktop { return nil } identity := a.context.Identity.GetIdentity() - return trace.Wrap(dtauthz.VerifyTLSUser(dt, identity)) + return trace.Wrap(dtauthz.VerifyTLSUser(ctx, dt, identity)) } func (a *ServerWithRoles) CreateResetPasswordToken(ctx context.Context, req authclient.CreateUserTokenRequest) (types.UserToken, error) { @@ -6813,7 +6900,7 @@ func (a *ServerWithRoles) enforceGlobalModeTrustedDevice(ctx context.Context) er return trace.Wrap(err) } - err = dtauthz.VerifyTLSUser(readOnlyAuthPref.GetDeviceTrust(), a.context.Identity.GetIdentity()) + err = dtauthz.VerifyTLSUser(ctx, readOnlyAuthPref.GetDeviceTrust(), a.context.Identity.GetIdentity()) return trace.Wrap(err) } diff --git a/lib/auth/authclient/clt.go b/lib/auth/authclient/clt.go index 4217b12a5991e..09e4caff54d29 100644 --- a/lib/auth/authclient/clt.go +++ b/lib/auth/authclient/clt.go @@ -1875,6 +1875,9 @@ type ClientI interface { // but may result in confusing behavior if it is used outside of those contexts. GetSSHTargets(ctx context.Context, req *proto.GetSSHTargetsRequest) (*proto.GetSSHTargetsResponse, error) + // ResolveSSHTarget returns the server that would be resolved in an equivalent ssh dial request. + ResolveSSHTarget(ctx context.Context, req *proto.ResolveSSHTargetRequest) (*proto.ResolveSSHTargetResponse, error) + // PerformMFACeremony retrieves an MFA challenge from the server with the given challenge extensions // and prompts the user to answer the challenge with the given promptOpts, and ultimately returning // an MFA challenge response for the user. diff --git a/lib/auth/bot.go b/lib/auth/bot.go index d2ce2518abb50..104518ea7687e 100644 --- a/lib/auth/bot.go +++ b/lib/auth/bot.go @@ -26,7 +26,6 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "google.golang.org/protobuf/types/known/timestamppb" "github.com/gravitational/teleport/api/client/proto" @@ -42,6 +41,7 @@ import ( "github.com/gravitational/teleport/lib/events" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/sshutils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) // legacyValidateGenerationLabel validates and updates a generation label. @@ -121,7 +121,10 @@ func (a *Server) legacyValidateGenerationLabel(ctx context.Context, username str // The current generations must match to continue: if currentIdentityGeneration != currentUserGeneration { if err := a.tryLockBotDueToGenerationMismatch(ctx, user.GetName()); err != nil { - log.WithError(err).Warnf("Failed to lock bot %q when a generation mismatch was detected", user.GetName()) + a.logger.WarnContext(ctx, "Failed to lock bot when a generation mismatch was detected", + "error", err, + "bot", user.GetName(), + ) } return trace.AccessDenied( @@ -248,7 +251,7 @@ func (a *Server) tryLockBotDueToGenerationMismatch(ctx context.Context, username }, UserMetadata: userMetadata, }); err != nil { - log.WithError(err).Warn("Failed to emit renewable cert generation mismatch event") + a.logger.WarnContext(ctx, "Failed to emit renewable cert generation mismatch event", "error", err) } return nil @@ -348,11 +351,11 @@ func (a *Server) updateBotInstance( authRecord.Generation = 1 } - log.WithFields(logrus.Fields{ - "bot_name": botName, - "invalid_instance_id": botInstanceID, - "new_instance_id": instanceID.String(), - }).Info("bot has no valid instance ID, a new instance will be generated") + a.logger.InfoContext(ctx, "bot has no valid instance ID, a new instance will be generated", + "bot_name", botName, + "invalid_instance_id", botInstanceID, + "new_instance_id", logutils.StringerAttr(instanceID), + ) expires := a.GetClock().Now().Add(req.ttl + machineidv1.ExpiryMargin) @@ -371,14 +374,14 @@ func (a *Server) updateBotInstance( return nil } - l := log.WithFields(logrus.Fields{ - "bot_name": botName, - "bot_instance_id": botInstanceID, - }) + log := a.logger.With( + "bot_name", botName, + "bot_instance_id", botInstanceID, + ) if currentIdentityGeneration == 0 { // Nothing to do. - l.Warn("bot attempted to fetch certificates without providing a current identity generation, this is not allowed") + log.WarnContext(ctx, "bot attempted to fetch certificates without providing a current identity generation, this is not allowed") return trace.AccessDenied("a current identity generation must be provided") } else if currentIdentityGeneration > 0 && currentIdentityGeneration != instanceGeneration { @@ -386,7 +389,7 @@ func (a *Server) updateBotInstance( // renewable (i.e. token) identities. if req.renewable { if err := a.tryLockBotDueToGenerationMismatch(ctx, username); err != nil { - l.WithError(err).Warn("Failed to lock bot when a generation mismatch was detected") + log.WarnContext(ctx, "Failed to lock bot when a generation mismatch was detected", "error", err) } return trace.AccessDenied( @@ -398,12 +401,13 @@ func (a *Server) updateBotInstance( // We'll still log the check failure, but won't deny access. This // log data will help make an informed decision about reliability of // the generation counter for all join methods in the future. - l.WithFields(logrus.Fields{ - "bot_instance_generation": instanceGeneration, - "bot_identity_generation": currentIdentityGeneration, - "bot_join_method": authRecord.JoinMethod, - }).Warn("Bot generation counter mismatch detected. This check is not enforced for this join method, " + - "but may indicate multiple uses of a bot identity and possibly a compromised certificate.") + const msg = "Bot generation counter mismatch detected. This check is not enforced for this join method, " + + "but may indicate multiple uses of a bot identity and possibly a compromised certificate." + log.WarnContext(ctx, msg, + "bot_instance_generation", instanceGeneration, + "bot_identity_generation", currentIdentityGeneration, + "bot_join_method", authRecord.JoinMethod, + ) } } @@ -419,7 +423,7 @@ func (a *Server) updateBotInstance( // setting this for other methods will break compatibility. if req.renewable { if err := a.commitLegacyGenerationCounterToBotUser(ctx, username, uint64(newGeneration)); err != nil { - l.WithError(err).Warn("unable to commit legacy generation counter to bot user") + log.WarnContext(ctx, "unable to commit legacy generation counter to bot user", "error", err) } } @@ -442,7 +446,7 @@ func (a *Server) updateBotInstance( // An initial auth record should have been added during initial join, // but if not, add it now. if bi.Status.InitialAuthentication == nil { - l.Warn("bot instance is missing its initial authentication record, a new one will be added") + log.WarnContext(ctx, "bot instance is missing its initial authentication record, a new one will be added") bi.Status.InitialAuthentication = authRecord } @@ -500,13 +504,16 @@ func (a *Server) generateInitialBotCerts( // permissions to read user data. userState, err := a.GetUserOrLoginState(ctx, username) if err != nil { - log.WithError(err).Debugf("Could not impersonate user %v. The user could not be fetched from local store.", username) + a.logger.DebugContext(ctx, "Could not impersonate user - the user could not be fetched from local store", + "error", err, + "user", username, + ) return nil, "", trace.AccessDenied("access denied") } // Do not allow SSO users to be impersonated. if userState.GetUserType() == types.UserTypeSSO { - log.Warningf("Tried to issue a renewable cert for externally managed user %v, this is not supported.", username) + a.logger.WarnContext(ctx, "Tried to issue a renewable cert for externally managed user, this is not supported", "user", username) return nil, "", trace.AccessDenied("access denied") } diff --git a/lib/auth/github.go b/lib/auth/github.go index a447f9dca3594..64a147f584a7a 100644 --- a/lib/auth/github.go +++ b/lib/auth/github.go @@ -167,8 +167,7 @@ func (a *Server) CreateGithubAuthRequest(ctx context.Context, req types.GithubAu config := newGithubOAuth2Config(connector) req.RedirectURL = config.AuthCodeURL(req.StateToken) - log.WithFields(logrus.Fields{teleport.ComponentKey: "github"}).Debugf( - "Redirect URL: %v.", req.RedirectURL) + a.logger.DebugContext(ctx, "Creating github auth request", "redirect_url", req.RedirectURL) req.SetExpiry(a.GetClock().Now().UTC().Add(defaults.GithubAuthRequestTTL)) err = a.Services.CreateGithubAuthRequest(ctx, req) if err != nil { @@ -197,7 +196,7 @@ func (a *Server) upsertGithubConnector(ctx context.Context, connector types.Gith }, ConnectionMetadata: authz.ConnectionMetadata(ctx), }); err != nil { - log.WithError(err).Warn("Failed to emit GitHub connector create event.") + a.logger.WarnContext(ctx, "Failed to emit GitHub connector create event", "error", err) } return upserted, nil @@ -224,7 +223,7 @@ func (a *Server) createGithubConnector(ctx context.Context, connector types.Gith }, ConnectionMetadata: authz.ConnectionMetadata(ctx), }); err != nil { - log.WithError(err).Warn("Failed to emit GitHub connector create event.") + a.logger.WarnContext(ctx, "Failed to emit GitHub connector create event", "error", err) } return created, nil @@ -251,7 +250,7 @@ func (a *Server) updateGithubConnector(ctx context.Context, connector types.Gith }, ConnectionMetadata: authz.ConnectionMetadata(ctx), }); err != nil { - log.WithError(err).Warn("Failed to emit GitHub connector update event.") + a.logger.WarnContext(ctx, "Failed to emit GitHub connector update event", "error", err) } return updated, nil @@ -407,7 +406,7 @@ func (a *Server) deleteGithubConnector(ctx context.Context, connectorName string }, ConnectionMetadata: authz.ConnectionMetadata(ctx), }); err != nil { - log.WithError(err).Warn("Failed to emit GitHub connector delete event.") + a.logger.WarnContext(ctx, "Failed to emit GitHub connector delete event", "error", err) } return nil @@ -433,10 +432,10 @@ type githubManager interface { // ValidateGithubAuthCallback validates Github auth callback redirect func (a *Server) ValidateGithubAuthCallback(ctx context.Context, q url.Values) (*authclient.GithubAuthResponse, error) { diagCtx := NewSSODiagContext(types.KindGithub, a) - return validateGithubAuthCallbackHelper(ctx, a, diagCtx, q, a.emitter) + return validateGithubAuthCallbackHelper(ctx, a, diagCtx, q, a.emitter, a.logger) } -func validateGithubAuthCallbackHelper(ctx context.Context, m githubManager, diagCtx *SSODiagContext, q url.Values, emitter apievents.Emitter) (*authclient.GithubAuthResponse, error) { +func validateGithubAuthCallbackHelper(ctx context.Context, m githubManager, diagCtx *SSODiagContext, q url.Values, emitter apievents.Emitter, logger *slog.Logger) (*authclient.GithubAuthResponse, error) { event := &apievents.UserLogin{ Metadata: apievents.Metadata{ Type: events.UserLoginEvent, @@ -456,7 +455,7 @@ func validateGithubAuthCallbackHelper(ctx context.Context, m githubManager, diag attributes, err := apievents.EncodeMapStrings(claims.OrganizationToTeams) if err != nil { event.Status.UserMessage = fmt.Sprintf("Failed to encode identity attributes: %v", err.Error()) - log.WithError(err).Debug("Failed to encode identity attributes.") + logger.DebugContext(ctx, "Failed to encode identity attributes", "error", err) } else { event.IdentityAttributes = attributes } @@ -472,7 +471,7 @@ func validateGithubAuthCallbackHelper(ctx context.Context, m githubManager, diag event.Status.UserMessage = err.Error() if err := emitter.EmitAuditEvent(ctx, event); err != nil { - log.WithError(err).Warn("Failed to emit GitHub login failed event.") + logger.WarnContext(ctx, "Failed to emit GitHub login failed event", "error", err) } return nil, trace.Wrap(err) } @@ -484,7 +483,7 @@ func validateGithubAuthCallbackHelper(ctx context.Context, m githubManager, diag event.User = auth.Username if err := emitter.EmitAuditEvent(ctx, event); err != nil { - log.WithError(err).Warn("Failed to emit GitHub login event.") + logger.WarnContext(ctx, "Failed to emit GitHub login event", "error", err) } return auth, nil @@ -602,6 +601,16 @@ func (a *Server) validateGithubAuthCallback(ctx context.Context, diagCtx *SSODia if err != nil { return nil, trace.Wrap(err, "Failed to query GitHub API for user claims.") } + + logger.DebugContext(ctx, "Retrieved GitHub claims", + slog.Group("claims", + slog.String("user_name", claims.Username), + slog.String("user_id", claims.UserID), + slog.Any("organization_to_teams", claims.Teams), + slog.Any("roles", claims.OrganizationToTeams), + ), + ) + diagCtx.Info.GithubClaims = claims // Calculate (figure out name, roles, traits, session TTL) of user and @@ -945,9 +954,12 @@ func (a *Server) calculateGithubUser(ctx context.Context, diagCtx *SSODiagContex } func (a *Server) createGithubUser(ctx context.Context, p *CreateUserParams, dryRun bool) (types.User, error) { - log.WithFields(logrus.Fields{teleport.ComponentKey: "github"}).Debugf( - "Generating dynamic GitHub identity %v/%v with roles: %v. Dry run: %v.", - p.ConnectorName, p.Username, p.Roles, dryRun) + a.logger.DebugContext(ctx, "Generating dynamic GitHub identity", + "connector_name", p.ConnectorName, + "user_name", p.Username, + "role", p.Roles, + "dry_run", dryRun, + ) expires := a.GetClock().Now().UTC().Add(p.SessionTTL) @@ -1125,15 +1137,12 @@ func populateGithubClaims(user *GithubUserResponse, teams []GithubTeamResponse) return nil, trace.AccessDenied( "list of user teams is empty, did you grant access?") } - claims := &types.GithubClaims{ + return &types.GithubClaims{ Username: user.Login, OrganizationToTeams: orgToTeams, Teams: teamList, UserID: user.getIDStr(), - } - log.WithFields(logrus.Fields{teleport.ComponentKey: "github"}).Debugf( - "Claims: %#v.", claims) - return claims, nil + }, nil } // githubAPIClient is a tiny wrapper around some of Github APIs @@ -1223,11 +1232,11 @@ func (c *githubAPIClient) getTeams(ctx context.Context) ([]GithubTeamResponse, e // of pages, print an error when it does happen, and return the results up // to that point. if count > MaxPages { - warningMessage := "Truncating list of teams used to populate claims: " + + const warningMessage = "Truncating list of teams used to populate claims: " + "hit maximum number pages that can be fetched from GitHub." // Print warning to Teleport logs as well as the Audit Log. - log.Warn(warningMessage) + c.authServer.logger.WarnContext(ctx, warningMessage) if err := c.authServer.emitter.EmitAuditEvent(c.authServer.closeCtx, &apievents.UserLogin{ Metadata: apievents.Metadata{ Type: events.UserLoginEvent, @@ -1240,7 +1249,7 @@ func (c *githubAPIClient) getTeams(ctx context.Context) ([]GithubTeamResponse, e }, ConnectionMetadata: authz.ConnectionMetadata(ctx), }); err != nil { - log.WithError(err).Warn("Failed to emit GitHub login failure event.") + c.authServer.logger.WarnContext(ctx, "Failed to emit GitHub login failure event", "error", err) } return result, nil } diff --git a/lib/auth/github_test.go b/lib/auth/github_test.go index 56d33fcf91711..862f3ce2d03c0 100644 --- a/lib/auth/github_test.go +++ b/lib/auth/github_test.go @@ -188,6 +188,7 @@ func TestValidateGithubAuthCallbackEventsEmitted(t *testing.T) { clientAddr := &net.TCPAddr{IP: net.IPv4(10, 255, 0, 0)} ctx := authz.ContextWithClientSrcAddr(context.Background(), clientAddr) tt := setupGithubContext(ctx, t) + logger := utils.NewSlogLoggerForTests() auth := &authclient.GithubAuthResponse{ Username: "test-name", @@ -220,7 +221,7 @@ func TestValidateGithubAuthCallbackEventsEmitted(t *testing.T) { diagCtx.Info.AppliedLoginRules = []string{"login-rule"} return auth, nil } - _, _ = validateGithubAuthCallbackHelper(ctx, m, diagCtx, nil, tt.a.emitter) + _, _ = validateGithubAuthCallbackHelper(ctx, m, diagCtx, nil, tt.a.emitter, logger) require.Equal(t, events.UserLoginEvent, tt.mockEmitter.LastEvent().GetType()) require.Equal(t, events.UserSSOLoginCode, tt.mockEmitter.LastEvent().GetCode()) loginEvt := tt.mockEmitter.LastEvent().(*apievents.UserLogin) @@ -235,7 +236,7 @@ func TestValidateGithubAuthCallbackEventsEmitted(t *testing.T) { diagCtx.Info.GithubClaims = claims return auth, trace.BadParameter("") } - _, _ = validateGithubAuthCallbackHelper(ctx, m, diagCtx, nil, tt.a.emitter) + _, _ = validateGithubAuthCallbackHelper(ctx, m, diagCtx, nil, tt.a.emitter, logger) require.Equal(t, events.UserLoginEvent, tt.mockEmitter.LastEvent().GetType()) require.Equal(t, events.UserSSOLoginFailureCode, tt.mockEmitter.LastEvent().GetCode()) loginEvt = tt.mockEmitter.LastEvent().(*apievents.UserLogin) @@ -248,7 +249,7 @@ func TestValidateGithubAuthCallbackEventsEmitted(t *testing.T) { diagCtx.Info.GithubClaims = claims return auth, nil } - _, _ = validateGithubAuthCallbackHelper(ctx, m, diagCtx, nil, tt.a.emitter) + _, _ = validateGithubAuthCallbackHelper(ctx, m, diagCtx, nil, tt.a.emitter, logger) require.Equal(t, events.UserLoginEvent, tt.mockEmitter.LastEvent().GetType()) require.Equal(t, events.UserSSOTestFlowLoginCode, tt.mockEmitter.LastEvent().GetCode()) loginEvt = tt.mockEmitter.LastEvent().(*apievents.UserLogin) @@ -262,7 +263,7 @@ func TestValidateGithubAuthCallbackEventsEmitted(t *testing.T) { diagCtx.Info.GithubClaims = claims return auth, trace.BadParameter("") } - _, _ = validateGithubAuthCallbackHelper(ctx, m, diagCtx, nil, tt.a.emitter) + _, _ = validateGithubAuthCallbackHelper(ctx, m, diagCtx, nil, tt.a.emitter, logger) require.Equal(t, events.UserLoginEvent, tt.mockEmitter.LastEvent().GetType()) require.Equal(t, events.UserSSOTestFlowLoginFailureCode, tt.mockEmitter.LastEvent().GetCode()) loginEvt = tt.mockEmitter.LastEvent().(*apievents.UserLogin) diff --git a/lib/auth/grpcserver.go b/lib/auth/grpcserver.go index 2ba196f37fd39..d5dfc1f553f2b 100644 --- a/lib/auth/grpcserver.go +++ b/lib/auth/grpcserver.go @@ -24,6 +24,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net" "os" "strconv" @@ -34,7 +35,6 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/trace/trail" "github.com/prometheus/client_golang/prometheus" - "github.com/sirupsen/logrus" collectortracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -123,6 +123,7 @@ import ( "github.com/gravitational/teleport/lib/srv/server/installer" usagereporter "github.com/gravitational/teleport/lib/usagereporter/teleport" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) var ( @@ -179,7 +180,7 @@ var ( type GRPCServer struct { authpb.UnimplementedAuthServiceServer auditlogpb.UnimplementedAuditLogServiceServer - *logrus.Entry + logger *slog.Logger APIConfig server *grpc.Server @@ -256,11 +257,11 @@ func (g *GRPCServer) SendKeepAlives(stream authpb.AuthService_SendKeepAlivesServ } keepAlive, err := stream.Recv() if errors.Is(err, io.EOF) { - g.Logger.Debug("Connection closed.") + g.logger.DebugContext(stream.Context(), "Connection closed") return nil } if err != nil { - g.Logger.Debugf("Failed to receive heartbeat: %v", err) + g.logger.DebugContext(stream.Context(), "Failed to receive heartbeat", "error", err) return trace.Wrap(err) } err = auth.KeepAliveServer(stream.Context(), *keepAlive) @@ -268,7 +269,10 @@ func (g *GRPCServer) SendKeepAlives(stream authpb.AuthService_SendKeepAlivesServ return trace.Wrap(err) } if firstIteration { - g.Logger.Debugf("Got %s heartbeat connection from %v.", keepAlive.GetType(), auth.User.GetName()) + g.logger.DebugContext(stream.Context(), "Got heartbeat connection", + "heartbeat_type", keepAlive.GetType(), + "identity", auth.User.GetName(), + ) heartbeatConnectionsReceived.Inc() metric, ok := connectedResourceGauges[keepAlive.GetType()] @@ -276,7 +280,7 @@ func (g *GRPCServer) SendKeepAlives(stream authpb.AuthService_SendKeepAlivesServ metric.Inc() defer metric.Dec() } else { - g.Logger.Warnf("missing connected resources gauge for keep alive %s (this is a bug)", keepAlive.GetType()) + g.logger.WarnContext(stream.Context(), "missing connected resources gauge for keep alive (this is a bug)", "heartbeat_type", keepAlive.GetType()) } firstIteration = false @@ -308,7 +312,7 @@ func (g *GRPCServer) CreateAuditStream(stream authpb.AuthService_CreateAuditStre var eventStream apievents.Stream var sessionID session.ID - g.Debugf("CreateAuditStream connection from %v.", auth.User.GetName()) + g.logger.DebugContext(stream.Context(), "CreateAuditStream connection", "identity", auth.User.GetName()) streamStart := time.Now() processed := int64(0) counter := 0 @@ -319,7 +323,7 @@ func (g *GRPCServer) CreateAuditStream(stream authpb.AuthService_CreateAuditStre return case statusUpdate := <-eventStream.Status(): if err := stream.Send(&statusUpdate); err != nil { - g.WithError(err).Debugf("Failed to send status update.") + g.logger.DebugContext(stream.Context(), "Failed to send status update", "error", err) } } } @@ -328,10 +332,10 @@ func (g *GRPCServer) CreateAuditStream(stream authpb.AuthService_CreateAuditStre closeStream := func(eventStream apievents.Stream) { if err := eventStream.Close(auth.CloseContext()); err != nil { if auth.CloseContext().Err() == nil { - g.WithError(err).Warn("Failed to flush close the stream.") + g.logger.WarnContext(stream.Context(), "Failed to flush close the stream", "error", err) } } else { - g.Debugf("Flushed and closed the stream.") + g.logger.DebugContext(stream.Context(), "Flushed and closed the stream") } } @@ -342,7 +346,7 @@ func (g *GRPCServer) CreateAuditStream(stream authpb.AuthService_CreateAuditStre } if err != nil { if stream.Context().Err() == nil { - g.WithError(err).Debug("Failed to receive stream request.") + g.logger.DebugContext(stream.Context(), "Failed to receive stream request", "error", err) } return trace.Wrap(err) } @@ -355,11 +359,11 @@ func (g *GRPCServer) CreateAuditStream(stream authpb.AuthService_CreateAuditStre // Log the reason why audit stream creation failed. This will // surface things like AWS/GCP/MinIO credential/configuration // errors. - g.Errorf("Failed to create audit stream: %q.", err) + g.logger.ErrorContext(stream.Context(), "Failed to create audit stream", "error", err) return trace.Wrap(err) } sessionID = session.ID(create.SessionID) - g.Debugf("Created stream for session %v", sessionID) + g.logger.DebugContext(stream.Context(), "Created stream for session", "session_id", sessionID) go forwardEvents(eventStream) defer closeStream(eventStream) } else if resume := request.GetResumeStream(); resume != nil { @@ -370,7 +374,7 @@ func (g *GRPCServer) CreateAuditStream(stream authpb.AuthService_CreateAuditStre if err != nil { return trace.Wrap(err) } - g.Debugf("Resumed stream for session %v", resume.SessionID) + g.logger.DebugContext(stream.Context(), "Resumed stream for session", "session_id", resume.SessionID) go forwardEvents(eventStream) defer closeStream(eventStream) } else if complete := request.GetCompleteStream(); complete != nil { @@ -408,7 +412,7 @@ func (g *GRPCServer) CreateAuditStream(stream authpb.AuthService_CreateAuditStre return trace.Wrap(err) } } - g.Debugf("Completed stream for session %v", sessionID) + g.logger.DebugContext(stream.Context(), "Completed stream for session", "session_id", sessionID) return nil } else if flushAndClose := request.GetFlushAndCloseStream(); flushAndClose != nil { if eventStream == nil { @@ -422,7 +426,7 @@ func (g *GRPCServer) CreateAuditStream(stream authpb.AuthService_CreateAuditStre } event, err := apievents.FromOneOf(*oneof) if err != nil { - g.WithError(err).Debug("Failed to decode event.") + g.logger.DebugContext(stream.Context(), "Failed to decode event", "error", err) return trace.Wrap(err) } // Currently only api/client.auditStreamer calls with an event @@ -451,8 +455,15 @@ func (g *GRPCServer) CreateAuditStream(stream authpb.AuthService_CreateAuditStre if err != nil { switch { case events.IsPermanentEmitError(err): - g.WithError(err).WithField("event", event). - Error("Failed to EmitAuditEvent due to a permanent error. Event wil be omitted.") + g.logger.ErrorContext(stream.Context(), "Failed to EmitAuditEvent due to a permanent error, event wil be omitted", + slog.Any("error", err), + slog.Group("event", + slog.String("type", event.GetType()), + slog.String("code", event.GetCode()), + slog.String("id", event.GetID()), + slog.Int64("index", event.GetIndex()), + ), + ) continue default: return trace.Wrap(err) @@ -465,15 +476,18 @@ func (g *GRPCServer) CreateAuditStream(stream authpb.AuthService_CreateAuditStre if counter%logInterval == 0 { if seconds > 0 { kbytes := float64(processed) / 1000 - g.Debugf("Processed %v events, tx rate kbytes %v/second.", counter, kbytes/float64(seconds)) + g.logger.DebugContext(stream.Context(), "Processed events", "event_count", counter, "tx_rate", kbytes/float64(seconds)) } } diff := time.Since(start) if diff > 100*time.Millisecond { - g.Warningf("RecordEvent(%v) took longer than 100ms: %v", event.GetType(), time.Since(event.GetTime())) + g.logger.WarnContext(stream.Context(), "RecordEvent took longer than 100ms", + "event_type", event.GetType(), + "duration", time.Since(event.GetTime()), + ) } } else { - g.Errorf("Rejecting unsupported stream request: %v.", request) + g.logger.ErrorContext(stream.Context(), "Rejecting unsupported stream request", "request", request) return trace.BadParameter("unsupported stream request") } } @@ -569,7 +583,7 @@ func (g *GRPCServer) GenerateUserCerts(ctx context.Context, req *authpb.UserCert return nil, trace.Wrap(err) } if err := validateUserCertsRequest(auth, req); err != nil { - g.Entry.Debugf("Validation of user certs request failed: %v", err) + g.logger.DebugContext(ctx, "Validation of user certs request failed", "error", err) return nil, trace.Wrap(err) } @@ -645,7 +659,7 @@ func (g *GRPCServer) generateUserSingleUseCerts(ctx context.Context, actx *grpcC actx, *req) if err != nil { - g.Entry.Warningf("Failed to generate single-use cert: %v", err) + g.logger.WarnContext(ctx, "Failed to generate single-use cert", "error", err) return nil, trace.Wrap(err) } @@ -806,7 +820,10 @@ func (g *GRPCServer) GetInstances(filter *types.InstanceFilter, stream authpb.Au for instances.Next() { instance, ok := instances.Item().(*types.InstanceV1) if !ok { - log.Warnf("Skipping unexpected instance type %T, expected %T.", instances.Item(), instance) + g.logger.WarnContext(stream.Context(), "Skipping unexpected instance type", + "instance_type", logutils.TypeAttr(instances.Item()), + "expected_instance_type", logutils.TypeAttr(instance), + ) continue } if err := stream.Send(instance); err != nil { @@ -904,7 +921,10 @@ func (g *GRPCServer) GetCurrentUserRoles(_ *emptypb.Empty, stream authpb.AuthSer for _, role := range roles { v6, ok := role.(*types.RoleV6) if !ok { - log.Warnf("expected type RoleV6, got %T for role %q", role, role.GetName()) + g.logger.WarnContext(stream.Context(), "expected type RoleV6, got unexpected for role type", + "role_type", logutils.TypeAttr(role), + "role", role.GetName(), + ) return trace.Errorf("encountered unexpected role type") } if err := stream.Send(v6); err != nil { @@ -2097,7 +2117,10 @@ func (g *GRPCServer) ListRoles(ctx context.Context, req *authpb.ListRolesRequest for _, role := range rsp.Roles { downgraded, err := maybeDowngradeRole(ctx, role) if err != nil { - log.Warnf("Failed to downgrade role %q, this is a bug and may result in spurious access denied errors. err=%q", role.GetName(), err) + g.logger.WarnContext(ctx, "Failed to downgrade role, this is a bug and may result in spurious access denied errors", + "role", role.GetName(), + "error", err, + ) continue } downgradedRoles = append(downgradedRoles, downgraded) @@ -2132,11 +2155,14 @@ func (g *GRPCServer) CreateRole(ctx context.Context, req *authpb.CreateRoleReque return nil, trace.Wrap(err) } - g.Debugf("%q role upserted", req.Role.GetName()) + g.logger.DebugContext(ctx, "role upserted", "role_name", req.Role.GetName()) v6, ok := created.(*types.RoleV6) if !ok { - log.Warnf("expected type RoleV6, got %T for role %q", created, created.GetName()) + g.logger.WarnContext(ctx, "expected type RoleV6, got unexpected type", + "role_type", logutils.TypeAttr(created), + "role", created.GetName(), + ) return nil, trace.BadParameter("encountered unexpected role type") } @@ -2168,11 +2194,14 @@ func (g *GRPCServer) UpdateRole(ctx context.Context, req *authpb.UpdateRoleReque return nil, trace.Wrap(err) } - g.Debugf("%q role upserted", req.Role.GetName()) + g.logger.DebugContext(ctx, "role upserted", "role", req.Role.GetName()) v6, ok := updated.(*types.RoleV6) if !ok { - log.Warnf("expected type RoleV6, got %T for role %q", updated, updated.GetName()) + g.logger.WarnContext(ctx, "expected type RoleV6, got unexpected type", + "role_type", logutils.TypeAttr(updated), + "role", updated.GetName(), + ) return nil, trace.BadParameter("encountered unexpected role type") } @@ -2204,11 +2233,14 @@ func (g *GRPCServer) UpsertRoleV2(ctx context.Context, req *authpb.UpsertRoleReq return nil, trace.Wrap(err) } - g.Debugf("%q role upserted", req.Role.GetName()) + g.logger.DebugContext(ctx, "role upserted", "role", req.Role.GetName()) v6, ok := upserted.(*types.RoleV6) if !ok { - log.Warnf("expected type RoleV6, got %T for role %q", upserted, upserted.GetName()) + g.logger.WarnContext(ctx, "expected type RoleV6, got unexpected type", + "role_type", logutils.TypeAttr(upserted), + "role", upserted.GetName(), + ) return nil, trace.BadParameter("encountered unexpected role type") } @@ -2231,7 +2263,7 @@ func (g *GRPCServer) DeleteRole(ctx context.Context, req *authpb.DeleteRoleReque return nil, trace.Wrap(err) } - g.Debugf("%q role deleted", req.GetName()) + g.logger.DebugContext(ctx, "role deleted", "role", req.GetName()) return &emptypb.Empty{}, nil } @@ -2907,7 +2939,10 @@ func (g *GRPCServer) GetServerInfos(_ *emptypb.Empty, stream authpb.AuthService_ for infos.Next() { si, ok := infos.Item().(*types.ServerInfoV1) if !ok { - log.Warnf("Skipping unexpected instance type %T, expected %T.", infos.Item(), si) + g.logger.WarnContext(stream.Context(), "expected type ServerInfoV1, got unexpected type", + "server_info_type", logutils.TypeAttr(infos.Item()), + "server_info_name", infos.Item().GetName(), + ) } if err := stream.Send(si); err != nil { infos.Done() @@ -3014,6 +3049,8 @@ func (g *GRPCServer) GetTrustedClusters(ctx context.Context, _ *emptypb.Empty) ( } // UpsertTrustedCluster upserts a Trusted Cluster. +// +// Deprecated: Use UpsertTrustedClusterV2 instead. func (g *GRPCServer) UpsertTrustedCluster(ctx context.Context, cluster *types.TrustedClusterV2) (*types.TrustedClusterV2, error) { auth, err := g.authenticate(ctx) if err != nil { @@ -3836,7 +3873,7 @@ func (g *GRPCServer) UpsertWindowsDesktopService(ctx context.Context, service *t // the closest thing we have to a public IP for the service. clientAddr, err := authz.ClientSrcAddrFromContext(ctx) if err != nil { - g.Logger.WithError(err).Warn("error getting client address from context") + g.logger.WarnContext(ctx, "error getting client address from context", "error", err) return nil, status.Errorf(codes.FailedPrecondition, "client address not found in request context") } service.Spec.Addr = utils.ReplaceLocalhost(service.GetAddr(), clientAddr.String()) @@ -4200,6 +4237,21 @@ func (g *GRPCServer) GetSSHTargets(ctx context.Context, req *authpb.GetSSHTarget return rsp, nil } +// ResolveSSHTarget gets a server that would match an equivalent ssh dial request. +func (g *GRPCServer) ResolveSSHTarget(ctx context.Context, req *authpb.ResolveSSHTargetRequest) (*authpb.ResolveSSHTargetResponse, error) { + auth, err := g.authenticate(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + rsp, err := auth.ServerWithRoles.ResolveSSHTarget(ctx, req) + if err != nil { + return nil, trace.Wrap(err) + } + + return rsp, nil +} + // CreateSessionTracker creates a tracker resource for an active session. func (g *GRPCServer) CreateSessionTracker(ctx context.Context, req *authpb.CreateSessionTrackerRequest) (*types.SessionTrackerV1, error) { auth, err := g.authenticate(ctx) @@ -4208,7 +4260,7 @@ func (g *GRPCServer) CreateSessionTracker(ctx context.Context, req *authpb.Creat } if req.SessionTracker == nil { - g.Errorf("Missing SessionTracker in CreateSessionTrackerRequest. This can be caused by an outdated Teleport node running against your cluster.") + g.logger.ErrorContext(ctx, "Missing SessionTracker in CreateSessionTrackerRequest, this can be caused by an outdated Teleport node running against your cluster") return nil, trace.BadParameter("missing SessionTracker from CreateSessionTrackerRequest") } @@ -5063,7 +5115,13 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) { if err := cfg.CheckAndSetDefaults(); err != nil { return nil, trace.Wrap(err) } - log.Debugf("gRPC(SERVER): keep alive %v count: %v.", cfg.KeepAlivePeriod, cfg.KeepAliveCount) + + logger := slog.With(teleport.ComponentKey, teleport.Component(teleport.ComponentAuth, teleport.ComponentGRPC)) + + logger.DebugContext(context.Background(), "creating gRPC server", + "keep_alive_period", cfg.KeepAlivePeriod, + "keep_alive_count", cfg.KeepAliveCount, + ) // httplib.TLSCreds are explicitly used instead of credentials.NewTLS because the latter // modifies the tls.Config.NextProtos which causes problems due to multiplexing on the auth @@ -5104,6 +5162,7 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) { Emitter: cfg.Emitter, Reporter: cfg.AuthServer.Services.UsageReporter, Clock: cfg.AuthServer.GetClock(), + Logger: cfg.AuthServer.logger.With(teleport.ComponentKey, "users.service"), }) if err != nil { return nil, trace.Wrap(err) @@ -5118,6 +5177,7 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) { Emitter: cfg.Emitter, Reporter: cfg.AuthServer.Services.UsageReporter, Clock: cfg.AuthServer.GetClock(), + Logger: cfg.AuthServer.logger.With(teleport.ComponentKey, "presence.service"), }) if err != nil { return nil, trace.Wrap(err) @@ -5186,6 +5246,23 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) { } workloadidentityv1pb.RegisterWorkloadIdentityResourceServiceServer(server, workloadIdentityResourceService) + clusterName, err := cfg.AuthServer.GetClusterName() + if err != nil { + return nil, trace.Wrap(err, "getting cluster name") + } + workloadIdentityIssuanceService, err := workloadidentityv1.NewIssuanceService(&workloadidentityv1.IssuanceServiceConfig{ + Authorizer: cfg.Authorizer, + Cache: cfg.AuthServer.Cache, + Emitter: cfg.Emitter, + Clock: cfg.AuthServer.GetClock(), + KeyStore: cfg.AuthServer.keyStore, + ClusterName: clusterName.GetClusterName(), + }) + if err != nil { + return nil, trace.Wrap(err, "creating workload identity issuance service") + } + workloadidentityv1pb.RegisterWorkloadIdentityIssuanceServiceServer(server, workloadIdentityIssuanceService) + dbObjectImportRuleService, err := dbobjectimportrulev1.NewDatabaseObjectImportRuleService(dbobjectimportrulev1.DatabaseObjectImportRuleServiceConfig{ Authorizer: cfg.Authorizer, Backend: cfg.AuthServer.Services, @@ -5208,20 +5285,18 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) { authServer := &GRPCServer{ APIConfig: cfg.APIConfig, - Entry: logrus.WithFields(logrus.Fields{ - teleport.ComponentKey: teleport.Component(teleport.ComponentAuth, teleport.ComponentGRPC), - }), - server: server, + logger: logger, + server: server, } if en := os.Getenv("TELEPORT_UNSTABLE_CREATEAUDITSTREAM_INFLIGHT_LIMIT"); en != "" { inflightLimit, err := strconv.ParseInt(en, 10, 64) if err != nil { - log.Error("Failed to parse the TELEPORT_UNSTABLE_CREATEAUDITSTREAM_INFLIGHT_LIMIT envvar, limit will not be enforced.") + logger.ErrorContext(context.Background(), "Failed to parse the TELEPORT_UNSTABLE_CREATEAUDITSTREAM_INFLIGHT_LIMIT envvar, limit will not be enforced") inflightLimit = -1 } if inflightLimit == 0 { - log.Warn("TELEPORT_UNSTABLE_CREATEAUDITSTREAM_INFLIGHT_LIMIT is set to 0, no CreateAuditStream RPCs will be allowed.") + logger.WarnContext(context.Background(), "TELEPORT_UNSTABLE_CREATEAUDITSTREAM_INFLIGHT_LIMIT is set to 0, no CreateAuditStream RPCs will be allowed") } metrics.RegisterPrometheusCollectors( createAuditStreamAcceptedTotalMetric, diff --git a/lib/auth/grpcserver_test.go b/lib/auth/grpcserver_test.go index 8a91f952e001e..c92e521e386c0 100644 --- a/lib/auth/grpcserver_test.go +++ b/lib/auth/grpcserver_test.go @@ -2988,6 +2988,69 @@ func TestGetSSHTargets(t *testing.T) { require.ElementsMatch(t, []string{rsp.Servers[0].GetHostname(), rsp.Servers[1].GetHostname()}, []string{"foo", "Foo"}) } +func TestResolveSSHTarget(t *testing.T) { + t.Parallel() + ctx := context.Background() + srv := newTestTLSServer(t) + + clt, err := srv.NewClient(TestAdmin()) + require.NoError(t, err) + + upper, err := types.NewServerWithLabels(uuid.New().String(), types.KindNode, types.ServerSpecV2{ + Hostname: "Foo", + UseTunnel: true, + }, nil) + require.NoError(t, err) + upper.SetExpiry(time.Now().Add(time.Hour)) + + lower, err := types.NewServerWithLabels(uuid.New().String(), types.KindNode, types.ServerSpecV2{ + Hostname: "foo", + UseTunnel: true, + }, nil) + require.NoError(t, err) + + other, err := types.NewServerWithLabels(uuid.New().String(), types.KindNode, types.ServerSpecV2{ + Hostname: "bar", + UseTunnel: true, + }, nil) + require.NoError(t, err) + + for _, node := range []types.Server{upper, lower, other} { + _, err = clt.UpsertNode(ctx, node) + require.NoError(t, err) + } + + rsp, err := clt.ResolveSSHTarget(ctx, &proto.ResolveSSHTargetRequest{ + Host: "foo", + Port: "0", + }) + require.NoError(t, err) + require.Equal(t, "foo", rsp.Server.GetHostname()) + + cnc := types.DefaultClusterNetworkingConfig() + cnc.SetCaseInsensitiveRouting(true) + _, err = clt.UpsertClusterNetworkingConfig(ctx, cnc) + require.NoError(t, err) + + rsp, err = clt.ResolveSSHTarget(ctx, &proto.ResolveSSHTargetRequest{ + Host: "foo", + Port: "0", + }) + require.Error(t, err) + require.Nil(t, rsp) + + cnc.SetRoutingStrategy(types.RoutingStrategy_MOST_RECENT) + _, err = clt.UpsertClusterNetworkingConfig(ctx, cnc) + require.NoError(t, err) + + rsp, err = clt.ResolveSSHTarget(ctx, &proto.ResolveSSHTargetRequest{ + Host: "foo", + Port: "0", + }) + require.NoError(t, err) + require.Equal(t, "Foo", rsp.Server.GetHostname()) +} + func TestNodesCRUD(t *testing.T) { t.Parallel() ctx := context.Background() diff --git a/lib/auth/init.go b/lib/auth/init.go index 215dd4aaae512..3e0c0fbe1a970 100644 --- a/lib/auth/init.go +++ b/lib/auth/init.go @@ -71,11 +71,13 @@ import ( "github.com/gravitational/teleport/lib/tlsca" usagereporter "github.com/gravitational/teleport/lib/usagereporter/teleport" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) -var log = logrus.WithFields(logrus.Fields{ - teleport.ComponentKey: teleport.ComponentAuth, -}) +var ( + log = logrus.WithField(teleport.ComponentKey, teleport.ComponentAuth) + logger = logutils.NewPackageLogger(teleport.ComponentKey, teleport.ComponentAuth) +) // VersionStorage local storage for saving the version. type VersionStorage interface { @@ -401,7 +403,7 @@ func initCluster(ctx context.Context, cfg InitConfig, asrv *Server) error { // on initial startup. if len(cfg.BootstrapResources) > 0 { if firstStart { - log.Infof("Applying %v bootstrap resources (first initialization)", len(cfg.BootstrapResources)) + asrv.logger.InfoContext(ctx, "Applying bootstrap resources (first initialization)", "resource_count", len(cfg.BootstrapResources)) if err := checkResourceConsistency(ctx, asrv.keyStore, domainName, cfg.BootstrapResources...); err != nil { return trace.Wrap(err, "refusing to bootstrap backend") } @@ -409,13 +411,13 @@ func initCluster(ctx context.Context, cfg InitConfig, asrv *Server) error { return trace.Wrap(err, "backend bootstrap failed") } } else { - log.Warnf("Ignoring %v bootstrap resources (previously initialized)", len(cfg.BootstrapResources)) + asrv.logger.WarnContext(ctx, "Ignoring bootstrap resources (previously initialized)", "resource_count", len(cfg.BootstrapResources)) } } // if apply-on-startup resources are supplied, apply them if len(cfg.ApplyOnStartupResources) > 0 { - log.Infof("Applying %v resources (apply-on-startup)", len(cfg.ApplyOnStartupResources)) + asrv.logger.InfoContext(ctx, "Applying resources (apply-on-startup)", "resource_count", len(cfg.ApplyOnStartupResources)) if err := applyResources(ctx, asrv.Services, cfg.ApplyOnStartupResources); err != nil { return trace.Wrap(err, "applying resources failed") @@ -432,7 +434,7 @@ func initCluster(ctx context.Context, cfg InitConfig, asrv *Server) error { if _, err := asrv.UpsertRole(ctx, role); err != nil { return trace.Wrap(err) } - log.Infof("Created role: %v.", role) + asrv.logger.InfoContext(ctx, "Created role", "role", role.GetName()) } for i := range cfg.Authorities { ca := cfg.Authorities[i] @@ -450,14 +452,17 @@ func initCluster(ctx context.Context, cfg InitConfig, asrv *Server) error { return trace.Wrap(err) } } else { - log.Infof("Created trusted certificate authority: %q, type: %q.", ca.GetName(), ca.GetType()) + asrv.logger.InfoContext(ctx, "Created trusted certificate authority", + "ca_name", ca.GetName(), + "ca_type", ca.GetType(), + ) } } for _, tunnel := range cfg.ReverseTunnels { if err := asrv.UpsertReverseTunnel(ctx, tunnel); err != nil { return trace.Wrap(err) } - log.Infof("Created reverse tunnel: %v.", tunnel) + asrv.logger.InfoContext(ctx, "Created reverse tunnel", "tunnel", tunnel.GetName()) } g, gctx := errgroup.WithContext(ctx) @@ -494,14 +499,14 @@ func initCluster(ctx context.Context, cfg InitConfig, asrv *Server) error { g.Go(func() error { _, span := cfg.Tracer.Start(gctx, "auth/SetStaticTokens") defer span.End() - log.Infof("Updating cluster configuration: %v.", cfg.StaticTokens) + asrv.logger.InfoContext(ctx, "Updating cluster configuration", "static_tokens", cfg.StaticTokens) return trace.Wrap(asrv.SetStaticTokens(cfg.StaticTokens)) }) g.Go(func() error { _, span := cfg.Tracer.Start(gctx, "auth/SetClusterNamespace") defer span.End() - log.Infof("Creating namespace: %q.", apidefaults.Namespace) + asrv.logger.InfoContext(ctx, "Creating namespace", "namespace", apidefaults.Namespace) return trace.Wrap(asrv.UpsertNamespace(types.DefaultNamespace())) }) @@ -530,18 +535,18 @@ func initCluster(ctx context.Context, cfg InitConfig, asrv *Server) error { return trace.Wrap(err) } if cn.GetClusterName() != cfg.ClusterName.GetClusterName() { - msg := "Cannot rename cluster: continuing with current cluster name. Teleport " + + const msg = "Cannot rename cluster: continuing with current cluster name. Teleport " + "clusters can not be renamed once they are created. You are seeing this " + "message for one of two reasons. Either you have not set \"cluster_name\" in " + "Teleport configuration and changed the hostname of the auth server or you " + "are trying to change the value of \"cluster_name\"." - log.WithFields(logrus.Fields{ - "current_cluster_name": cn.GetClusterName(), - "configured_cluster_name": cfg.ClusterName.GetClusterName(), - }).Error(msg) + asrv.logger.ErrorContext(ctx, msg, + "current_cluster_name", cn.GetClusterName(), + "configured_cluster_name", cfg.ClusterName.GetClusterName(), + ) } - log.Debugf("Cluster configuration: %v.", cn.GetClusterName()) + asrv.logger.DebugContext(ctx, "Cluster configuration", "cluster_name", cn.GetClusterName()) return nil }) @@ -563,10 +568,10 @@ func initCluster(ctx context.Context, cfg InitConfig, asrv *Server) error { } if lib.IsInsecureDevMode() { - warningMessage := "Starting teleport in insecure mode. This is " + + const warningMessage = "Starting teleport in insecure mode. This is " + "dangerous! Sensitive information will be logged to console and " + "certificates will not be verified. Proceed with caution!" - log.Warn(warningMessage) + asrv.logger.WarnContext(ctx, warningMessage) } span.AddEvent("migrating legacy resources") @@ -593,18 +598,18 @@ func initCluster(ctx context.Context, cfg InitConfig, asrv *Server) error { span.AddEvent("creating preset database object import rules") if err := createPresetDatabaseObjectImportRule(ctx, asrv); err != nil { // merely raise a warning; this is not a fatal error. - log.WithError(err).Warn("error creating preset database object import rules") + asrv.logger.WarnContext(ctx, "error creating preset database object import rules", "error", err) } span.AddEvent("completed creating database object import rules") } else { - log.Info("skipping preset role and user creation") + asrv.logger.InfoContext(ctx, "skipping preset role and user creation") } if !cfg.SkipPeriodicOperations { - log.Infof("Auth server is running periodic operations.") + asrv.logger.InfoContext(ctx, "Auth server is running periodic operations") go asrv.runPeriodicOperations() } else { - log.Infof("Auth server is skipping periodic operations.") + asrv.logger.InfoContext(ctx, "Auth server is skipping periodic operations") } return nil @@ -650,7 +655,7 @@ func initializeAuthorities(ctx context.Context, asrv *Server, cfg *InitConfig) e // Key deletion is best-effort, log a warning if it fails and carry on. // We don't want to prevent a CA rotation, which may be necessary in // some cases where this would fail. - log.Warnf("An attempt to clean up unused HSM or KMS CA keys has failed unexpectedly: %v", err) + asrv.logger.WarnContext(ctx, "An attempt to clean up unused HSM or KMS CA keys has failed unexpectedly", "error", err) } return nil } @@ -662,7 +667,7 @@ func initializeAuthority(ctx context.Context, asrv *Server, caID types.CertAuthI return nil, nil, trace.Wrap(err) } - log.Infof("First start: generating %s certificate authority.", caID.Type) + asrv.logger.InfoContext(ctx, "First start: generating certificate authority", "ca_type", caID.Type) if ca, err = generateAuthority(ctx, asrv, caID); err != nil { return nil, nil, trace.Wrap(err) } @@ -705,28 +710,34 @@ func initializeAuthority(ctx context.Context, asrv *Server, caID types.CertAuthI return nil, nil, trace.Wrap(err) } } else { - log.Warnf("This Auth Service is configured to use %s but the %s CA contains only %s. "+ - "No new certificates can be signed with the existing keys. "+ - "You must perform a CA rotation to generate new keys, or adjust your configuration to use the existing keys.", - usableKeysResult.PreferredKeyType, - caID.Type, - strings.Join(usableKeysResult.CAKeyTypes, " and ")) + const msg = "This Auth Service is configured to use key types that the CA does not contain. " + + "No new certificates can be signed with the existing keys. " + + "You must perform a CA rotation to generate new keys, or adjust your configuration to use the existing keys." + asrv.logger.WarnContext(ctx, msg, + "configured_key_type", usableKeysResult.PreferredKeyType, + "ca_type", caID.Type, + "available_key_types", usableKeysResult.CAKeyTypes, + ) } } else if !usableKeysResult.CAHasPreferredKeyType { - log.Warnf("This Auth Service is configured to use %s but the %s CA contains only %s. "+ - "New certificates will continue to be signed with raw software keys but you must perform a CA rotation to begin using %s.", - usableKeysResult.PreferredKeyType, - caID.Type, - strings.Join(usableKeysResult.CAKeyTypes, " and "), - usableKeysResult.PreferredKeyType) + const msg = "This Auth Service is configured to use key types that the CA does not contain. " + + "New certificates will continue to be signed with raw software keys but you must perform a CA rotation to begin using the new key type." + asrv.logger.WarnContext(ctx, msg, + "configured_key_type", usableKeysResult.PreferredKeyType, + "ca_type", caID.Type, + "available_key_types", usableKeysResult.CAKeyTypes, + ) } allKeyTypes := ca.AllKeyTypes() numKeyTypes := len(allKeyTypes) if numKeyTypes > 1 { - log.Warnf("%s CA contains a combination of %s and %s keys. If you are attempting to"+ - " configure HSM or KMS key storage, make sure it is configured on all auth servers in"+ - " this cluster and then perform a CA rotation: https://goteleport.com/docs/management/operations/ca-rotation/", - caID.Type, strings.Join(allKeyTypes[:numKeyTypes-1], ", "), allKeyTypes[numKeyTypes-1]) + const msg = "CA contains a combination of key typess. If you are attempting to" + + " configure HSM or KMS key storage, make sure it is configured on all auth servers in" + + " this cluster and then perform a CA rotation: https://goteleport.com/docs/management/operations/ca-rotation/" + asrv.logger.WarnContext(ctx, msg, + "ca_type", caID.Type, + "key_types", []string{strings.Join(allKeyTypes[:numKeyTypes-1], ", "), allKeyTypes[numKeyTypes-1]}, + ) } for _, keySet := range []types.CAKeySet{ca.GetActiveKeys(), ca.GetAdditionalTrustedKeys()} { @@ -785,7 +796,7 @@ func initializeAuthPreference(ctx context.Context, asrv *Server, newAuthPref typ return trace.Wrap(err) } - shouldReplace, err := shouldInitReplaceResourceWithOrigin(storedAuthPref, newAuthPref) + shouldReplace, err := shouldInitReplaceResourceWithOrigin(ctx, storedAuthPref, newAuthPref, asrv.logger) if err != nil { return trace.Wrap(err) } @@ -830,7 +841,7 @@ func initializeAuthPreference(ctx context.Context, asrv *Server, newAuthPref typ } if storedAuthPref == nil { - log.Infof("Creating cluster auth preference: %v.", newAuthPref) + asrv.logger.InfoContext(ctx, "Creating cluster auth preference", "auth_preference", newAuthPref) _, err := asrv.CreateAuthPreference(ctx, newAuthPref) if trace.IsAlreadyExists(err) { continue @@ -861,7 +872,7 @@ func initializeClusterNetworkingConfig(ctx context.Context, asrv *Server, newNet return trace.Wrap(err) } - shouldReplace, err := shouldInitReplaceResourceWithOrigin(storedNetConfig, newNetConfig) + shouldReplace, err := shouldInitReplaceResourceWithOrigin(ctx, storedNetConfig, newNetConfig, asrv.logger) if err != nil { return trace.Wrap(err) } @@ -871,7 +882,7 @@ func initializeClusterNetworkingConfig(ctx context.Context, asrv *Server, newNet } if storedNetConfig == nil { - log.Infof("Creating cluster networking configuration: %v.", newNetConfig) + asrv.logger.InfoContext(ctx, "Creating cluster networking configuration", "networking_confg", newNetConfig) _, err = asrv.CreateClusterNetworkingConfig(ctx, newNetConfig) if trace.IsAlreadyExists(err) { continue @@ -880,7 +891,7 @@ func initializeClusterNetworkingConfig(ctx context.Context, asrv *Server, newNet return trace.Wrap(err) } - log.Infof("Updating cluster networking configuration: %v.", newNetConfig) + asrv.logger.InfoContext(ctx, "Updating cluster networking configuration", "networking_config", newNetConfig) newNetConfig.SetRevision(storedNetConfig.GetRevision()) _, err = asrv.UpdateClusterNetworkingConfig(ctx, newNetConfig) if trace.IsCompareFailed(err) { @@ -901,7 +912,7 @@ func initializeSessionRecordingConfig(ctx context.Context, asrv *Server, newRecC return trace.Wrap(err) } - shouldReplace, err := shouldInitReplaceResourceWithOrigin(storedRecConfig, newRecConfig) + shouldReplace, err := shouldInitReplaceResourceWithOrigin(ctx, storedRecConfig, newRecConfig, asrv.logger) if err != nil { return trace.Wrap(err) } @@ -911,7 +922,7 @@ func initializeSessionRecordingConfig(ctx context.Context, asrv *Server, newRecC } if storedRecConfig == nil { - log.Infof("Creating session recording config: %v.", newRecConfig) + asrv.logger.InfoContext(ctx, "Creating session recording config", "recording_config", newRecConfig) _, err := asrv.CreateSessionRecordingConfig(ctx, newRecConfig) if trace.IsAlreadyExists(err) { continue @@ -920,7 +931,7 @@ func initializeSessionRecordingConfig(ctx context.Context, asrv *Server, newRecC return trace.Wrap(err) } - log.Infof("Updating session recording config: %v.", newRecConfig) + asrv.logger.InfoContext(ctx, "Updating session recording config", "recording_config", newRecConfig) newRecConfig.SetRevision(storedRecConfig.GetRevision()) _, err = asrv.UpdateSessionRecordingConfig(ctx, newRecConfig) if trace.IsCompareFailed(err) { @@ -949,7 +960,7 @@ func initializeAccessGraphSettings(ctx context.Context, asrv *Server) error { return trace.Wrap(err) } - log.Infof("Creating access graph settings: %v.", stored) + asrv.logger.InfoContext(ctx, "Creating access graph settings", "settings", stored) _, err = asrv.CreateAccessGraphSettings(ctx, stored) if trace.IsAlreadyExists(err) { return nil @@ -962,7 +973,7 @@ func initializeAccessGraphSettings(ctx context.Context, asrv *Server) error { // resource should be used to replace the stored resource during auth server // initialization. Dynamically configured resources must not be overwritten // when the corresponding file config is left unspecified (i.e., by defaults). -func shouldInitReplaceResourceWithOrigin(stored, candidate types.ResourceWithOrigin) (bool, error) { +func shouldInitReplaceResourceWithOrigin(ctx context.Context, stored, candidate types.ResourceWithOrigin, logger *slog.Logger) (bool, error) { if candidate == nil || (candidate.Origin() != types.OriginDefaults && candidate.Origin() != types.OriginConfigFile) { return false, trace.BadParameter("candidate origin must be either defaults or config-file (this is a bug)") } @@ -978,7 +989,7 @@ func shouldInitReplaceResourceWithOrigin(stored, candidate types.ResourceWithOri if candidate.Origin() == types.OriginConfigFile { // Log a warning when about to overwrite a dynamically configured resource. if stored.Origin() == types.OriginDynamic { - log.Warnf("Stored %v resource that was configured dynamically is about to be discarded in favor of explicit file configuration.", stored.GetKind()) + logger.WarnContext(ctx, "Stored resource that was configured dynamically is about to be discarded in favor of explicit file configuration", "resource", stored.GetKind()) } return true, nil } @@ -991,15 +1002,15 @@ func shouldInitReplaceResourceWithOrigin(stored, candidate types.ResourceWithOri // migrationStart marks the migration as active. // It should be called when a migration starts. -func migrationStart(ctx context.Context, migrationName string) { - log.Debugf("Migrations: %q migration started.", migrationName) +func migrationStart(ctx context.Context, migrationName string, logger *slog.Logger) { + logger.DebugContext(ctx, "Migration started", "migration_name", migrationName) migrations.WithLabelValues(migrationName).Set(1) } // migrationEnd marks the migration as inactive. // It should be called when a migration ends. -func migrationEnd(ctx context.Context, migrationName string) { - log.Debugf("Migrations: %q migration ended.", migrationName) +func migrationEnd(ctx context.Context, migrationName string, logger *slog.Logger) { + logger.DebugContext(ctx, "Migration ended", "migration_name", migrationName) migrations.WithLabelValues(migrationName).Set(0) } @@ -1080,7 +1091,7 @@ func createPresetRoles(ctx context.Context, rm PresetRoleManager) error { return trace.Wrap(err) } - role, err := services.AddRoleDefaults(currentRole) + role, err := services.AddRoleDefaults(gctx, currentRole) if trace.IsAlreadyExists(err) { return nil } @@ -1340,8 +1351,8 @@ func CertAuthorityInfo(ca types.CertAuthority) string { // where the presence of remote cluster was identified only by presence // of host certificate authority with cluster name not equal local cluster name func migrateRemoteClusters(ctx context.Context, asrv *Server) error { - migrationStart(ctx, "remote_clusters") - defer migrationEnd(ctx, "remote_clusters") + migrationStart(ctx, "remote_clusters", asrv.logger) + defer migrationEnd(ctx, "remote_clusters", asrv.logger) clusterName, err := asrv.Services.GetClusterName() if err != nil { @@ -1355,13 +1366,13 @@ func migrateRemoteClusters(ctx context.Context, asrv *Server) error { // forward and forward agent allowed for _, certAuthority := range certAuthorities { if certAuthority.GetName() == clusterName.GetClusterName() { - log.Debugf("Migrations: skipping local cluster cert authority %q.", certAuthority.GetName()) + asrv.logger.DebugContext(ctx, "Migrations: skipping local cluster cert authority", "cert_authority", certAuthority.GetName()) continue } // remote cluster already exists _, err = asrv.Services.GetRemoteCluster(ctx, certAuthority.GetName()) if err == nil { - log.Debugf("Migrations: remote cluster already exists for cert authority %q.", certAuthority.GetName()) + asrv.logger.DebugContext(ctx, "Migrations: remote cluster already exists for cert authority", "cert_authority", certAuthority.GetName()) continue } if !trace.IsNotFound(err) { @@ -1370,7 +1381,7 @@ func migrateRemoteClusters(ctx context.Context, asrv *Server) error { // the cert authority is associated with trusted cluster _, err = asrv.Services.GetTrustedCluster(ctx, certAuthority.GetName()) if err == nil { - log.Debugf("Migrations: trusted cluster resource exists for cert authority %q.", certAuthority.GetName()) + asrv.logger.DebugContext(ctx, "Migrations: trusted cluster resource exists for cert authority", "cert_authority", certAuthority.GetName()) continue } if !trace.IsNotFound(err) { @@ -1386,7 +1397,7 @@ func migrateRemoteClusters(ctx context.Context, asrv *Server) error { return trace.Wrap(err) } } - log.Infof("Migrations: added remote cluster resource for cert authority %q.", certAuthority.GetName()) + asrv.logger.InfoContext(ctx, "Migrations: added remote cluster resource for cert authority", "cert_authority", certAuthority.GetName()) } return nil diff --git a/lib/auth/integration/integrationv1/awsoidc.go b/lib/auth/integration/integrationv1/awsoidc.go index dfb1b154f5934..bcdff34276968 100644 --- a/lib/auth/integration/integrationv1/awsoidc.go +++ b/lib/auth/integration/integrationv1/awsoidc.go @@ -495,6 +495,58 @@ func (s *AWSOIDCService) DeployDatabaseService(ctx context.Context, req *integra }, nil } +// ListDeployedDatabaseServices lists Database Services deployed into Amazon ECS. +func (s *AWSOIDCService) ListDeployedDatabaseServices(ctx context.Context, req *integrationpb.ListDeployedDatabaseServicesRequest) (*integrationpb.ListDeployedDatabaseServicesResponse, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindIntegration, types.VerbUse); err != nil { + return nil, trace.Wrap(err) + } + + clusterName, err := s.cache.GetClusterName() + if err != nil { + return nil, trace.Wrap(err) + } + + awsClientReq, err := s.awsClientReq(ctx, req.Integration, req.Region) + if err != nil { + return nil, trace.Wrap(err) + } + + listDatabaseServicesClient, err := awsoidc.NewListDeployedDatabaseServicesClient(ctx, awsClientReq) + if err != nil { + return nil, trace.Wrap(err) + } + + listDatabaseServicesResponse, err := awsoidc.ListDeployedDatabaseServices(ctx, listDatabaseServicesClient, awsoidc.ListDeployedDatabaseServicesRequest{ + Integration: req.Integration, + TeleportClusterName: clusterName.GetClusterName(), + Region: req.Region, + NextToken: req.NextToken, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + deployedDatabaseServices := make([]*integrationpb.DeployedDatabaseService, 0, len(listDatabaseServicesResponse.DeployedDatabaseServices)) + for _, deployedService := range listDatabaseServicesResponse.DeployedDatabaseServices { + deployedDatabaseServices = append(deployedDatabaseServices, &integrationpb.DeployedDatabaseService{ + Name: deployedService.Name, + ServiceDashboardUrl: deployedService.ServiceDashboardURL, + ContainerEntryPoint: deployedService.ContainerEntryPoint, + ContainerCommand: deployedService.ContainerCommand, + }) + } + + return &integrationpb.ListDeployedDatabaseServicesResponse{ + DeployedDatabaseServices: deployedDatabaseServices, + NextToken: listDatabaseServicesResponse.NextToken, + }, nil +} + // EnrollEKSClusters enrolls EKS clusters into Teleport by installing teleport-kube-agent chart on the clusters. func (s *AWSOIDCService) EnrollEKSClusters(ctx context.Context, req *integrationpb.EnrollEKSClustersRequest) (*integrationpb.EnrollEKSClustersResponse, error) { authCtx, err := s.authorizer.Authorize(ctx) diff --git a/lib/auth/integration/integrationv1/awsoidc_test.go b/lib/auth/integration/integrationv1/awsoidc_test.go index f6cd0e925a48f..6a2497229ab38 100644 --- a/lib/auth/integration/integrationv1/awsoidc_test.go +++ b/lib/auth/integration/integrationv1/awsoidc_test.go @@ -423,6 +423,16 @@ func TestRBAC(t *testing.T) { return err }, }, + { + name: "ListDeployedDatabaseServices", + fn: func() error { + _, err := awsoidService.ListDeployedDatabaseServices(userCtx, &integrationv1.ListDeployedDatabaseServicesRequest{ + Integration: integrationName, + Region: "my-region", + }) + return err + }, + }, } { t.Run(tt.name, func(t *testing.T) { err := tt.fn() diff --git a/lib/auth/join.go b/lib/auth/join.go index 53880bf9955e2..00d4f8847f1e9 100644 --- a/lib/auth/join.go +++ b/lib/auth/join.go @@ -22,13 +22,12 @@ import ( "context" "crypto/rand" "encoding/base64" - "fmt" + "log/slog" "net" "slices" "strings" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "google.golang.org/grpc/peer" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -60,7 +59,11 @@ func (a *Server) checkTokenJoinRequestCommon(ctx context.Context, req *types.Reg // make sure the token is valid provisionToken, err := a.ValidateToken(ctx, req.Token) if err != nil { - log.Warningf("%q can not join the cluster with role %s, token error: %v", req.NodeName, req.Role, err) + a.logger.WarnContext(ctx, "cannot join the cluster with invalid token", + "node_name", req.NodeName, + "role", req.Role, + "error", err, + ) msg := "the token is not valid" // default to most generic message if strings.Contains(err.Error(), TokenExpiredOrNotFound) { // propagate ExpiredOrNotFound message so that clients can attempt @@ -80,17 +83,22 @@ func (a *Server) checkTokenJoinRequestCommon(ctx context.Context, req *types.Reg } } if !hasLocalServiceRole { - msg := fmt.Sprintf("%q [%v] cannot requisition instance certs (token contains no local service roles)", req.NodeName, req.HostID) - log.Warn(msg) - return nil, trace.AccessDenied(msg) + a.logger.WarnContext(ctx, "cannot requisition instance certs (token contains no local service roles)", + "node_name", req.NodeName, + "host_id", req.HostID, + ) + return nil, trace.AccessDenied("%s [%v] cannot requisition instance certs (token contains no local service roles)", req.NodeName, req.HostID) } } // make sure the caller is requesting a role allowed by the token if !provisionToken.GetRoles().Include(req.Role) && req.Role != types.RoleInstance { - msg := fmt.Sprintf("node %q [%v] can not join the cluster, the token does not allow %q role", req.NodeName, req.HostID, req.Role) - log.Warn(msg) - return nil, trace.BadParameter(msg) + a.logger.WarnContext(ctx, "token does not allow role to join the cluster", + "node_name", req.NodeName, + "host_id", req.HostID, + "role", req.Role, + ) + return nil, trace.BadParameter("node %q [%v] can not join the cluster, the token does not allow %q role", req.NodeName, req.HostID, req.Role) } return provisionToken, nil @@ -121,17 +129,20 @@ func setRemoteAddrFromContext(ctx context.Context, req *types.RegisterUsingToken // handleJoinFailure logs and audits the failure of a join. It is intentionally // designed to handle potential nullness of the input parameters. func (a *Server) handleJoinFailure( + ctx context.Context, origErr error, pt types.ProvisionToken, attributeSource joinAttributeSourcer, req *types.RegisterUsingTokenRequest, ) { - fields := logrus.Fields{} + attrs := []slog.Attr{slog.Any("error", origErr)} if req != nil { - fields["role"] = req.Role - fields["host_id"] = req.HostID - fields["node_name"] = req.NodeName - fields["remote_addr"] = req.RemoteAddr + attrs = append(attrs, []slog.Attr{ + slog.String("role", string(req.Role)), + slog.String("host_id", req.HostID), + slog.String("node_name", req.NodeName), + slog.String("remote_addr", req.RemoteAddr), + }...) } // Fetch and encode attributes if they are available. @@ -140,21 +151,21 @@ func (a *Server) handleJoinFailure( var err error attributes, err := attributeSource.JoinAuditAttributes() if err != nil { - log.WithError(err).Warn("Unable to fetch join attributes from join method") + a.logger.WarnContext(ctx, "Unable to fetch join attributes from join method", "error", err) } - fields["attributes"] = attributes + attrs = append(attrs, slog.Any("attributes", attributes)) attributesProto, err = apievents.EncodeMap(attributes) if err != nil { - log.WithError(err).Warn("Unable to encode join attributes for audit event") + a.logger.WarnContext(ctx, "Unable to encode join attributes for audit event", "error", err) } } // Add log fields from token if available. if pt != nil { - fields["join_method"] = string(pt.GetJoinMethod()) - fields["token_name"] = pt.GetSafeName() + attrs = append(attrs, slog.String("join_method", string(pt.GetJoinMethod()))) + attrs = append(attrs, slog.String("token_name", pt.GetSafeName())) } - log.WithError(origErr).WithFields(fields).Warn("Failure to join cluster occurred") + a.logger.LogAttrs(ctx, slog.LevelWarn, "Failure to join cluster occurred", attrs...) var evt apievents.AuditEvent status := apievents.Status{ @@ -201,7 +212,7 @@ func (a *Server) handleJoinFailure( evt = instanceJoinEvent } if err := a.emitter.EmitAuditEvent(a.closeCtx, evt); err != nil { - log.WithError(err).Warn("Failed to emit failed join event") + a.logger.WarnContext(ctx, "Failed to emit failed join event", "error", err) } } @@ -222,7 +233,7 @@ func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin defer func() { // Emit a log message and audit event on join failure. if err != nil { - a.handleJoinFailure(err, provisionToken, joinAttributeSrc, req) + a.handleJoinFailure(ctx, err, provisionToken, joinAttributeSrc, req) } }() @@ -398,16 +409,16 @@ func (a *Server) generateCertsBot( if joinAttributeSrc != nil { attributes, err := joinAttributeSrc.JoinAuditAttributes() if err != nil { - log.WithError(err).Warn("Unable to fetch join attributes from join method.") + a.logger.WarnContext(ctx, "Unable to fetch join attributes from join method", "error", err) } joinEvent.Attributes, err = apievents.EncodeMap(attributes) if err != nil { - log.WithError(err).Warn("Unable to encode join attributes for audit event.") + a.logger.WarnContext(ctx, "Unable to encode join attributes for audit event", "error", err) } auth.Metadata, err = structpb.NewStruct(attributes) if err != nil { - log.WithError(err).Warn("Unable to encode struct value for join metadata.") + a.logger.WarnContext(ctx, "Unable to encode struct value for join metadata", "error", err) } } @@ -432,16 +443,20 @@ func (a *Server) generateCertsBot( if shouldDeleteToken { // delete ephemeral bot join tokens so they can't be re-used if err := a.DeleteToken(ctx, provisionToken.GetName()); err != nil { - log.WithError(err).Warnf("Could not delete bot provision token %q after generating certs", - provisionToken.GetSafeName(), + a.logger.WarnContext(ctx, "Could not delete bot provision token after generating certs", + "provision_token", provisionToken.GetSafeName(), + "error", err, ) } } // Emit audit event for bot join. - log.Infof("Bot %q (instance: %s) has joined the cluster.", botName, botInstanceID) + a.logger.InfoContext(ctx, "Bot has joined the cluster", + "bot_name", botName, + "bot_instance", botInstanceID, + ) if err := a.emitter.EmitAuditEvent(ctx, joinEvent); err != nil { - log.WithError(err).Warn("Failed to emit bot join event.") + a.logger.WarnContext(ctx, "Failed to emit bot join event", "error", err) } return certs, nil } @@ -464,7 +479,7 @@ func (a *Server) generateCerts( if r.IsLocalService() { systemRoles = append(systemRoles, r) } else { - log.Warnf("Omitting non-service system role from instance cert: %q", r) + a.logger.WarnContext(ctx, "Omitting non-service system role from instance cert", "system_role", string(r)) } } } @@ -488,9 +503,18 @@ func (a *Server) generateCerts( // Emit audit event if req.Role == types.RoleInstance { - log.Infof("Instance %q [%v] has joined the cluster. role=%s, systemRoles=%+v", req.NodeName, req.HostID, req.Role, systemRoles) + a.logger.InfoContext(ctx, "Instance has joined the cluster", + "node_name", req.NodeName, + "host_id", req.HostID, + "role", req.Role, + "system_roles", systemRoles, + ) } else { - log.Infof("Instance %q [%v] has joined the cluster. role=%s", req.NodeName, req.HostID, req.Role) + a.logger.InfoContext(ctx, "Instance has joined the cluster", + "node_name", req.NodeName, + "host_id", req.HostID, + "role", req.Role, + ) } joinEvent := &apievents.InstanceJoin{ Metadata: apievents.Metadata{ @@ -513,15 +537,15 @@ func (a *Server) generateCerts( if joinAttributeSrc != nil { attributes, err := joinAttributeSrc.JoinAuditAttributes() if err != nil { - log.WithError(err).Warn("Unable to fetch join attributes from join method.") + a.logger.WarnContext(ctx, "Unable to fetch join attributes from join method", "error", err) } joinEvent.Attributes, err = apievents.EncodeMap(attributes) if err != nil { - log.WithError(err).Warn("Unable to encode join attributes for audit event.") + a.logger.WarnContext(ctx, "Unable to encode join attributes for audit event", "error", err) } } if err := a.emitter.EmitAuditEvent(ctx, joinEvent); err != nil { - log.WithError(err).Warn("Failed to emit instance join event.") + a.logger.WarnContext(ctx, "Failed to emit instance join event", "error", err) } return certs, nil } diff --git a/lib/auth/join_azure.go b/lib/auth/join_azure.go index 721a53ff2d7fa..70ae17918b7fa 100644 --- a/lib/auth/join_azure.go +++ b/lib/auth/join_azure.go @@ -366,9 +366,7 @@ func (a *Server) RegisterUsingAzureMethodWithOpts( defer func() { // Emit a log message and audit event on join failure. if err != nil { - a.handleJoinFailure( - err, provisionToken, nil, joinRequest, - ) + a.handleJoinFailure(ctx, err, provisionToken, nil, joinRequest) } }() diff --git a/lib/auth/join_gcp.go b/lib/auth/join_gcp.go index a6cf1db4ccd7e..9cfecca822637 100644 --- a/lib/auth/join_gcp.go +++ b/lib/auth/join_gcp.go @@ -24,7 +24,6 @@ import ( "strings" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/gcp" @@ -50,17 +49,18 @@ func (a *Server) checkGCPJoinRequest(ctx context.Context, req *types.RegisterUsi claims, err := a.gcpIDTokenValidator.Validate(ctx, req.IDToken) if err != nil { - log.WithFields(logrus.Fields{ - "claims": claims, - "token": pt.GetName(), - }).WithError(err).Warn("Unable to validate GCP IDToken") + a.logger.WarnContext(ctx, "Unable to validate GCP IDToken", + "error", err, + "claims", claims, + "token", pt.GetName(), + ) return nil, trace.Wrap(err) } - log.WithFields(logrus.Fields{ - "claims": claims, - "token": pt.GetName(), - }).Info("GCP VM trying to join cluster") + a.logger.InfoContext(ctx, "GCP VM trying to join cluster", + "claims", claims, + "token", pt.GetName(), + ) if err := checkGCPAllowRules(token, claims); err != nil { return nil, trace.Wrap(err) diff --git a/lib/auth/join_github.go b/lib/auth/join_github.go index 226f1c501c6b0..87d9e0aaa3ea4 100644 --- a/lib/auth/join_github.go +++ b/lib/auth/join_github.go @@ -24,7 +24,6 @@ import ( "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/githubactions" @@ -87,10 +86,10 @@ func (a *Server) checkGitHubJoinRequest(ctx context.Context, req *types.Register } } - log.WithFields(logrus.Fields{ - "claims": claims, - "token": pt.GetName(), - }).Info("Github actions run trying to join cluster") + a.logger.InfoContext(ctx, "Github actions run trying to join cluster", + "claims", claims, + "token", pt.GetName(), + ) return claims, trace.Wrap(checkGithubAllowRules(token, claims)) } diff --git a/lib/auth/join_gitlab.go b/lib/auth/join_gitlab.go index e20b9f8f343d8..0296c9ad3ed24 100644 --- a/lib/auth/join_gitlab.go +++ b/lib/auth/join_gitlab.go @@ -24,7 +24,6 @@ import ( "strings" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/gitlab" @@ -57,10 +56,10 @@ func (a *Server) checkGitLabJoinRequest(ctx context.Context, req *types.Register return nil, trace.Wrap(err) } - log.WithFields(logrus.Fields{ - "claims": claims, - "token": pt.GetName(), - }).Info("GitLab CI run trying to join cluster") + a.logger.InfoContext(ctx, "GitLab CI run trying to join cluster", + "claims", claims, + "token", pt.GetName(), + ) return claims, trace.Wrap(checkGitLabAllowRules(token, claims)) } diff --git a/lib/auth/join_iam.go b/lib/auth/join_iam.go index ba2209105c7c0..5ca3f4af1877d 100644 --- a/lib/auth/join_iam.go +++ b/lib/auth/join_iam.go @@ -344,9 +344,7 @@ func (a *Server) RegisterUsingIAMMethodWithOpts( defer func() { // Emit a log message and audit event on join failure. if err != nil { - a.handleJoinFailure( - err, provisionToken, nil, joinRequest, - ) + a.handleJoinFailure(ctx, err, provisionToken, nil, joinRequest) } }() diff --git a/lib/auth/join_kubernetes.go b/lib/auth/join_kubernetes.go index d5bbc6586d831..317f6cc7dfd3e 100644 --- a/lib/auth/join_kubernetes.go +++ b/lib/auth/join_kubernetes.go @@ -24,7 +24,6 @@ import ( "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" kubetoken "github.com/gravitational/teleport/lib/kube/token" @@ -82,10 +81,10 @@ func (a *Server) checkKubernetesJoinRequest(ctx context.Context, req *types.Regi ) } - log.WithFields(logrus.Fields{ - "validated_identity": result, - "token": token.GetName(), - }).Info("Kubernetes workload trying to join cluster") + a.logger.InfoContext(ctx, "Kubernetes workload trying to join cluster", + "validated_identity", result, + "token", token.GetName(), + ) return result, trace.Wrap(checkKubernetesAllowRules(token, result)) } diff --git a/lib/auth/join_spacelift.go b/lib/auth/join_spacelift.go index 30fecf5424abd..67b84de5f0e38 100644 --- a/lib/auth/join_spacelift.go +++ b/lib/auth/join_spacelift.go @@ -23,7 +23,6 @@ import ( "fmt" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/modules" @@ -64,10 +63,10 @@ func (a *Server) checkSpaceliftJoinRequest(ctx context.Context, req *types.Regis return nil, trace.Wrap(err) } - log.WithFields(logrus.Fields{ - "claims": claims, - "token": pt.GetName(), - }).Info("Spacelift run trying to join cluster") + a.logger.InfoContext(ctx, "Spacelift run trying to join cluster", + "claims", claims, + "token", pt.GetName(), + ) return claims, trace.Wrap(checkSpaceliftAllowRules(token, claims)) } diff --git a/lib/auth/join_terraformcloud.go b/lib/auth/join_terraformcloud.go index 9302004858608..827319733a39a 100644 --- a/lib/auth/join_terraformcloud.go +++ b/lib/auth/join_terraformcloud.go @@ -23,7 +23,6 @@ import ( "fmt" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/modules" @@ -75,10 +74,10 @@ func (a *Server) checkTerraformCloudJoinRequest(ctx context.Context, req *types. return nil, trace.Wrap(err) } - log.WithFields(logrus.Fields{ - "claims": claims, - "token": pt.GetName(), - }).Info("Terraform Cloud run trying to join cluster") + a.logger.InfoContext(ctx, "Terraform Cloud run trying to join cluster", + "claims", claims, + "token", pt.GetName(), + ) return claims, trace.Wrap(checkTerraformCloudAllowRules(token, claims)) } diff --git a/lib/auth/join_tpm.go b/lib/auth/join_tpm.go index 05bf9e3c35a54..12463e8ecd811 100644 --- a/lib/auth/join_tpm.go +++ b/lib/auth/join_tpm.go @@ -43,9 +43,7 @@ func (a *Server) RegisterUsingTPMMethod( defer func() { // Emit a log message and audit event on join failure. if err != nil { - a.handleJoinFailure( - err, provisionToken, attributeSrc, initReq.JoinRequest, - ) + a.handleJoinFailure(ctx, err, provisionToken, attributeSrc, initReq.JoinRequest) } }() diff --git a/lib/auth/machineid/workloadidentityv1/experiment/experiment.go b/lib/auth/machineid/workloadidentityv1/experiment/experiment.go new file mode 100644 index 0000000000000..fafe51ea83d1f --- /dev/null +++ b/lib/auth/machineid/workloadidentityv1/experiment/experiment.go @@ -0,0 +1,41 @@ +// 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 experiment + +import ( + "os" + "sync" +) + +var mu sync.Mutex + +var experimentEnabled = os.Getenv("TELEPORT_WORKLOAD_IDENTITY_UX_EXPERIMENT") == "1" + +// Enabled returns true if the workload identity UX experiment is +// enabled. +func Enabled() bool { + mu.Lock() + defer mu.Unlock() + return experimentEnabled +} + +// SetEnabled sets the experiment enabled flag. +func SetEnabled(enabled bool) { + mu.Lock() + defer mu.Unlock() + experimentEnabled = enabled +} diff --git a/lib/auth/machineid/workloadidentityv1/issuer_service.go b/lib/auth/machineid/workloadidentityv1/issuer_service.go new file mode 100644 index 0000000000000..70a7fa1197974 --- /dev/null +++ b/lib/auth/machineid/workloadidentityv1/issuer_service.go @@ -0,0 +1,608 @@ +// Teleport +// Copyright (C) 2024 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package workloadidentityv1 + +import ( + "context" + "crypto" + "crypto/rand" + "crypto/x509" + "log/slog" + "math/big" + "net/url" + "regexp" + "slices" + "strings" + "time" + + "github.com/gravitational/trace" + "github.com/jonboulle/clockwork" + "github.com/spiffe/go-spiffe/v2/spiffeid" + "go.opentelemetry.io/otel" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/gravitational/teleport" + workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" + "github.com/gravitational/teleport/api/observability/tracing" + "github.com/gravitational/teleport/api/types" + apievents "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/lib/auth/machineid/workloadidentityv1/experiment" + "github.com/gravitational/teleport/lib/authz" + "github.com/gravitational/teleport/lib/events" + "github.com/gravitational/teleport/lib/jwt" + "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/tlsca" + "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/teleport/lib/utils/oidc" +) + +var tracer = otel.Tracer("github.com/gravitational/teleport/lib/auth/machineid/workloadidentityv1") + +// KeyStorer is an interface that provides methods to retrieve keys and +// certificates from the backend. +type KeyStorer interface { + GetTLSCertAndSigner(ctx context.Context, ca types.CertAuthority) ([]byte, crypto.Signer, error) + GetJWTSigner(ctx context.Context, ca types.CertAuthority) (crypto.Signer, error) +} + +type issuerCache interface { + workloadIdentityReader + GetProxies() ([]types.Server, error) + GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool) (types.CertAuthority, error) +} + +// IssuanceServiceConfig holds configuration options for the IssuanceService. +type IssuanceServiceConfig struct { + Authorizer authz.Authorizer + Cache issuerCache + Clock clockwork.Clock + Emitter apievents.Emitter + Logger *slog.Logger + KeyStore KeyStorer + + ClusterName string +} + +// IssuanceService is the gRPC service for managing workload identity resources. +// It implements the workloadidentityv1pb.WorkloadIdentityIssuanceServiceServer. +type IssuanceService struct { + workloadidentityv1pb.UnimplementedWorkloadIdentityIssuanceServiceServer + + authorizer authz.Authorizer + cache issuerCache + clock clockwork.Clock + emitter apievents.Emitter + logger *slog.Logger + keyStore KeyStorer + + clusterName string +} + +// NewIssuanceService returns a new instance of the IssuanceService. +func NewIssuanceService(cfg *IssuanceServiceConfig) (*IssuanceService, error) { + switch { + case cfg.Cache == nil: + return nil, trace.BadParameter("cache service is required") + case cfg.Authorizer == nil: + return nil, trace.BadParameter("authorizer is required") + case cfg.Emitter == nil: + return nil, trace.BadParameter("emitter is required") + case cfg.KeyStore == nil: + return nil, trace.BadParameter("key store is required") + case cfg.ClusterName == "": + return nil, trace.BadParameter("cluster name is required") + } + + if cfg.Logger == nil { + cfg.Logger = slog.With(teleport.ComponentKey, "workload_identity_issuance.service") + } + if cfg.Clock == nil { + cfg.Clock = clockwork.NewRealClock() + } + return &IssuanceService{ + authorizer: cfg.Authorizer, + cache: cfg.Cache, + clock: cfg.Clock, + emitter: cfg.Emitter, + logger: cfg.Logger, + keyStore: cfg.KeyStore, + clusterName: cfg.ClusterName, + }, nil +} + +// getFieldStringValue returns a string value from the given attribute set. +// The attribute is specified as a dot-separated path to the field in the +// attribute set. +// +// The specified attribute must be a string field. If the attribute is not +// found, an error is returned. +// +// TODO(noah): This function will be replaced by the Teleport predicate language +// in a coming PR. +func getFieldStringValue(attrs *workloadidentityv1pb.Attrs, attr string) (string, error) { + attrParts := strings.Split(attr, ".") + message := attrs.ProtoReflect() + // TODO(noah): Improve errors by including the fully qualified attribute + // (e.g add up the parts of the attribute path processed thus far) + for i, part := range attrParts { + fieldDesc := message.Descriptor().Fields().ByTextName(part) + if fieldDesc == nil { + return "", trace.NotFound("attribute %q not found", part) + } + // We expect the final key to point to a string field - otherwise - we + // return an error. + if i == len(attrParts)-1 { + if !slices.Contains([]protoreflect.Kind{ + protoreflect.StringKind, + protoreflect.BoolKind, + protoreflect.Int32Kind, + protoreflect.Int64Kind, + protoreflect.Uint64Kind, + protoreflect.Uint32Kind, + }, fieldDesc.Kind()) { + return "", trace.BadParameter("attribute %q of type %q cannot be converted to string", part, fieldDesc.Kind()) + } + return message.Get(fieldDesc).String(), nil + } + // If we're not processing the final key part, we expect this to point + // to a message that we can further explore. + if fieldDesc.Kind() != protoreflect.MessageKind { + return "", trace.BadParameter("attribute %q is not a message", part) + } + message = message.Get(fieldDesc).Message() + } + return "", nil +} + +// templateString takes a given input string and replaces any values within +// {{ }} with values from the attribute set. +// +// If the specified value is not found in the attribute set, an error is +// returned. +// +// TODO(noah): In a coming PR, this will be replaced by evaluating the values +// within the handlebars as expressions. +func templateString(in string, attrs *workloadidentityv1pb.Attrs) (string, error) { + re := regexp.MustCompile(`\{\{([^{}]+?)\}\}`) + matches := re.FindAllStringSubmatch(in, -1) + + for _, match := range matches { + attrKey := strings.TrimSpace(match[1]) + value, err := getFieldStringValue(attrs, attrKey) + if err != nil { + return "", trace.Wrap(err, "fetching attribute value for %q", attrKey) + } + // We want to have an implicit rule here that if an attribute is + // included in the template, but is not set, we should refuse to issue + // the credential. + if value == "" { + return "", trace.NotFound("attribute %q unset", attrKey) + } + in = strings.Replace(in, match[0], value, 1) + } + + return in, nil +} + +func evaluateRules( + wi *workloadidentityv1pb.WorkloadIdentity, + attrs *workloadidentityv1pb.Attrs, +) error { + if len(wi.GetSpec().GetRules().GetAllow()) == 0 { + return nil + } +ruleLoop: + for _, rule := range wi.GetSpec().GetRules().GetAllow() { + for _, condition := range rule.GetConditions() { + val, err := getFieldStringValue(attrs, condition.Attribute) + if err != nil { + return trace.Wrap(err) + } + if val != condition.Equals { + continue ruleLoop + } + } + return nil + } + // TODO: Eventually, we'll need to work support for deny rules into here. + return trace.AccessDenied("no matching rule found") +} + +func (s *IssuanceService) deriveAttrs( + authzCtx *authz.Context, + workloadAttrs *workloadidentityv1pb.WorkloadAttrs, +) (*workloadidentityv1pb.Attrs, error) { + attrs := &workloadidentityv1pb.Attrs{ + Workload: workloadAttrs, + User: &workloadidentityv1pb.UserAttrs{ + Name: authzCtx.Identity.GetIdentity().Username, + IsBot: authzCtx.Identity.GetIdentity().BotName != "", + BotName: authzCtx.Identity.GetIdentity().BotName, + Labels: authzCtx.User.GetAllLabels(), + }, + } + + return attrs, nil +} + +var defaultMaxTTL = 24 * time.Hour + +func (s *IssuanceService) IssueWorkloadIdentity( + ctx context.Context, + req *workloadidentityv1pb.IssueWorkloadIdentityRequest, +) (*workloadidentityv1pb.IssueWorkloadIdentityResponse, error) { + if !experiment.Enabled() { + return nil, trace.AccessDenied("workload identity issuance experiment is disabled") + } + + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + switch { + case req.GetName() == "": + return nil, trace.BadParameter("name: is required") + case req.GetCredential() == nil: + return nil, trace.BadParameter("at least one credential type must be requested") + } + + wi, err := s.cache.GetWorkloadIdentity(ctx, req.GetName()) + if err != nil { + return nil, trace.Wrap(err) + } + // Check the principal has access to the workload identity resource by + // virtue of WorkloadIdentityLabels on a role. + if err := authCtx.Checker.CheckAccess( + types.Resource153ToResourceWithLabels(wi), + services.AccessState{}, + ); err != nil { + return nil, trace.Wrap(err) + } + + attrs, err := s.deriveAttrs(authCtx, req.GetWorkloadAttrs()) + if err != nil { + return nil, trace.Wrap(err, "deriving attributes") + } + // Evaluate any rules explicitly configured by the user + if err := evaluateRules(wi, attrs); err != nil { + return nil, trace.Wrap(err) + } + + // Perform any templating + spiffeIDPath, err := templateString(wi.GetSpec().GetSpiffe().GetId(), attrs) + if err != nil { + return nil, trace.Wrap(err, "templating spec.spiffe.id") + } + spiffeID, err := spiffeid.FromURI(&url.URL{ + Scheme: "spiffe", + Host: s.clusterName, + Path: spiffeIDPath, + }) + if err != nil { + return nil, trace.Wrap(err, "creating SPIFFE ID") + } + + hint, err := templateString(wi.GetSpec().GetSpiffe().GetHint(), attrs) + if err != nil { + return nil, trace.Wrap(err, "templating spec.spiffe.hint") + } + + // TODO(noah): Add more sophisticated control of the TTL. + ttl := time.Hour + if req.RequestedTtl != nil && req.RequestedTtl.AsDuration() != 0 { + ttl = req.RequestedTtl.AsDuration() + if ttl > defaultMaxTTL { + ttl = defaultMaxTTL + } + } + + now := s.clock.Now() + notBefore := now.Add(-1 * time.Minute) + notAfter := now.Add(ttl) + + // Prepare event + evt := &apievents.SPIFFESVIDIssued{ + Metadata: apievents.Metadata{ + Type: events.SPIFFESVIDIssuedEvent, + Code: events.SPIFFESVIDIssuedSuccessCode, + }, + UserMetadata: authz.ClientUserMetadata(ctx), + ConnectionMetadata: authz.ConnectionMetadata(ctx), + SPIFFEID: spiffeID.String(), + Hint: hint, + WorkloadIdentity: wi.GetMetadata().GetName(), + WorkloadIdentityRevision: wi.GetMetadata().GetRevision(), + } + cred := &workloadidentityv1pb.Credential{ + WorkloadIdentityName: wi.GetMetadata().GetName(), + WorkloadIdentityRevision: wi.GetMetadata().GetRevision(), + + SpiffeId: spiffeID.String(), + Hint: hint, + + ExpiresAt: timestamppb.New(notAfter), + Ttl: durationpb.New(ttl), + } + + switch v := req.GetCredential().(type) { + case *workloadidentityv1pb.IssueWorkloadIdentityRequest_X509SvidParams: + evt.SVIDType = "x509" + certDer, certSerial, err := s.issueX509SVID( + ctx, + v.X509SvidParams, + notBefore, + notAfter, + spiffeID, + ) + if err != nil { + return nil, trace.Wrap(err, "issuing X509 SVID") + } + serialStr := serialString(certSerial) + cred.Credential = &workloadidentityv1pb.Credential_X509Svid{ + X509Svid: &workloadidentityv1pb.X509SVIDCredential{ + Cert: certDer, + SerialNumber: serialStr, + }, + } + evt.SerialNumber = serialStr + case *workloadidentityv1pb.IssueWorkloadIdentityRequest_JwtSvidParams: + evt.SVIDType = "jwt" + signedJwt, jti, err := s.issueJWTSVID( + ctx, + v.JwtSvidParams, + now, + notAfter, + spiffeID, + ) + if err != nil { + return nil, trace.Wrap(err, "issuing JWT SVID") + } + cred.Credential = &workloadidentityv1pb.Credential_JwtSvid{ + JwtSvid: &workloadidentityv1pb.JWTSVIDCredential{ + Jwt: signedJwt, + Jti: jti, + }, + } + evt.JTI = jti + default: + return nil, trace.BadParameter("credential: unknown type %T", req.GetCredential()) + } + + if err := s.emitter.EmitAuditEvent(ctx, evt); err != nil { + s.logger.WarnContext( + ctx, + "failed to emit audit event for SVID issuance", + "error", err, + "event", evt, + ) + } + + return &workloadidentityv1pb.IssueWorkloadIdentityResponse{ + Credential: cred, + }, nil +} + +func generateCertSerial() (*big.Int, error) { + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + return rand.Int(rand.Reader, serialNumberLimit) +} + +func x509Template( + serialNumber *big.Int, + notBefore time.Time, + notAfter time.Time, + spiffeID spiffeid.ID, +) *x509.Certificate { + return &x509.Certificate{ + SerialNumber: serialNumber, + NotBefore: notBefore, + NotAfter: notAfter, + // SPEC(X509-SVID) 4.3. Key Usage: + // - Leaf SVIDs MUST NOT set keyCertSign or cRLSign. + // - Leaf SVIDs MUST set digitalSignature + // - They MAY set keyEncipherment and/or keyAgreement; + KeyUsage: x509.KeyUsageDigitalSignature | + x509.KeyUsageKeyEncipherment | + x509.KeyUsageKeyAgreement, + // SPEC(X509-SVID) 4.4. Extended Key Usage: + // - Leaf SVIDs SHOULD include this extension, and it MAY be marked as critical. + // - When included, fields id-kp-serverAuth and id-kp-clientAuth MUST be set. + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, + }, + // SPEC(X509-SVID) 4.1. Basic Constraints: + // - leaf certificates MUST set the cA field to false + BasicConstraintsValid: true, + IsCA: false, + + // SPEC(X509-SVID) 2. SPIFFE ID: + // - The corresponding SPIFFE ID is set as a URI type in the Subject Alternative Name extension + // - An X.509 SVID MUST contain exactly one URI SAN, and by extension, exactly one SPIFFE ID. + // - An X.509 SVID MAY contain any number of other SAN field types, including DNS SANs. + URIs: []*url.URL{spiffeID.URL()}, + } +} + +func (s *IssuanceService) getX509CA( + ctx context.Context, +) (_ *tlsca.CertAuthority, err error) { + ctx, span := tracer.Start(ctx, "IssuanceService/getX509CA") + defer func() { tracing.EndSpan(span, err) }() + + ca, err := s.cache.GetCertAuthority(ctx, types.CertAuthID{ + Type: types.SPIFFECA, + DomainName: s.clusterName, + }, true) + tlsCert, tlsSigner, err := s.keyStore.GetTLSCertAndSigner(ctx, ca) + if err != nil { + return nil, trace.Wrap(err, "getting CA cert and key") + } + tlsCA, err := tlsca.FromCertAndSigner(tlsCert, tlsSigner) + if err != nil { + return nil, trace.Wrap(err) + } + return tlsCA, nil +} + +func (s *IssuanceService) issueX509SVID( + ctx context.Context, + params *workloadidentityv1pb.X509SVIDParams, + notBefore time.Time, + notAfter time.Time, + spiffeID spiffeid.ID, +) (_ []byte, _ *big.Int, err error) { + ctx, span := tracer.Start(ctx, "IssuanceService/issueX509SVID") + defer func() { tracing.EndSpan(span, err) }() + + switch { + case params == nil: + return nil, nil, trace.BadParameter("x509_svid_params: is required") + case len(params.PublicKey) == 0: + return nil, nil, trace.BadParameter("x509_svid_params.public_key: is required") + } + + pubKey, err := x509.ParsePKIXPublicKey(params.PublicKey) + if err != nil { + return nil, nil, trace.Wrap(err, "parsing public key") + } + + certSerial, err := generateCertSerial() + if err != nil { + return nil, nil, trace.Wrap(err, "generating certificate serial") + } + template := x509Template(certSerial, notBefore, notAfter, spiffeID) + + ca, err := s.getX509CA(ctx) + if err != nil { + return nil, nil, trace.Wrap(err, "fetching CA to sign X509 SVID") + } + certBytes, err := x509.CreateCertificate( + rand.Reader, template, ca.Cert, pubKey, ca.Signer, + ) + if err != nil { + return nil, nil, trace.Wrap(err) + } + + return certBytes, certSerial, nil +} + +const jtiLength = 16 + +func (s *IssuanceService) getJWTIssuerKey( + ctx context.Context, +) (_ *jwt.Key, err error) { + ctx, span := tracer.Start(ctx, "IssuanceService/getJWTIssuerKey") + defer func() { tracing.EndSpan(span, err) }() + + ca, err := s.cache.GetCertAuthority(ctx, types.CertAuthID{ + Type: types.SPIFFECA, + DomainName: s.clusterName, + }, true) + if err != nil { + return nil, trace.Wrap(err, "getting SPIFFE CA") + } + + jwtSigner, err := s.keyStore.GetJWTSigner(ctx, ca) + if err != nil { + return nil, trace.Wrap(err, "getting JWT signer") + } + + jwtKey, err := services.GetJWTSigner( + jwtSigner, s.clusterName, s.clock, + ) + if err != nil { + return nil, trace.Wrap(err, "creating JWT signer") + } + return jwtKey, nil +} + +func (s *IssuanceService) issueJWTSVID( + ctx context.Context, + params *workloadidentityv1pb.JWTSVIDParams, + now time.Time, + notAfter time.Time, + spiffeID spiffeid.ID, +) (_ string, _ string, err error) { + ctx, span := tracer.Start(ctx, "IssuanceService/issueJWTSVID") + defer func() { tracing.EndSpan(span, err) }() + + switch { + case params == nil: + return "", "", trace.BadParameter("jwt_svid_params: is required") + case len(params.Audiences) == 0: + return "", "", trace.BadParameter("jwt_svid_params.audiences: at least one audience should be specified") + } + + jti, err := utils.CryptoRandomHex(jtiLength) + if err != nil { + return "", "", trace.Wrap(err, "generating JTI") + } + + key, err := s.getJWTIssuerKey(ctx) + if err != nil { + return "", "", trace.Wrap(err, "getting JWT issuer key") + } + + // Determine the public address of the proxy for inclusion in the JWT as + // the issuer for purposes of OIDC compatibility. + issuer, err := oidc.IssuerForCluster(ctx, s.cache, "/workload-identity") + if err != nil { + return "", "", trace.Wrap(err, "determining issuer URI") + } + + signed, err := key.SignJWTSVID(jwt.SignParamsJWTSVID{ + Audiences: params.Audiences, + SPIFFEID: spiffeID, + JTI: jti, + Issuer: issuer, + + SetIssuedAt: now, + SetExpiry: notAfter, + }) + if err != nil { + return "", "", trace.Wrap(err, "signing jwt") + } + + return signed, jti, nil +} + +func (s *IssuanceService) IssueWorkloadIdentities( + ctx context.Context, + req *workloadidentityv1pb.IssueWorkloadIdentitiesRequest, +) (*workloadidentityv1pb.IssueWorkloadIdentitiesResponse, error) { + // TODO(noah): Coming to a PR near you soon! + return nil, trace.NotImplemented("not implemented") +} + +func serialString(serial *big.Int) string { + hex := serial.Text(16) + if len(hex)%2 == 1 { + hex = "0" + hex + } + + out := strings.Builder{} + for i := 0; i < len(hex); i += 2 { + if i != 0 { + out.WriteString(":") + } + out.WriteString(hex[i : i+2]) + } + return out.String() +} diff --git a/lib/auth/machineid/workloadidentityv1/issuer_service_test.go b/lib/auth/machineid/workloadidentityv1/issuer_service_test.go new file mode 100644 index 0000000000000..bf18594416609 --- /dev/null +++ b/lib/auth/machineid/workloadidentityv1/issuer_service_test.go @@ -0,0 +1,223 @@ +// Teleport +// Copyright (C) 2024 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package workloadidentityv1 + +import ( + "testing" + + "github.com/stretchr/testify/require" + + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" + "github.com/gravitational/teleport/api/types" +) + +func Test_getFieldStringValue(t *testing.T) { + tests := []struct { + name string + in *workloadidentityv1pb.Attrs + attr string + want string + requireErr require.ErrorAssertionFunc + }{ + { + name: "success", + in: &workloadidentityv1pb.Attrs{ + User: &workloadidentityv1pb.UserAttrs{ + Name: "jeff", + }, + }, + attr: "user.name", + want: "jeff", + requireErr: require.NoError, + }, + { + name: "bool", + in: &workloadidentityv1pb.Attrs{ + User: &workloadidentityv1pb.UserAttrs{ + Name: "jeff", + }, + Workload: &workloadidentityv1pb.WorkloadAttrs{ + Unix: &workloadidentityv1pb.WorkloadAttrsUnix{ + Attested: true, + }, + }, + }, + attr: "workload.unix.attested", + want: "true", + requireErr: require.NoError, + }, + { + name: "int32", + in: &workloadidentityv1pb.Attrs{ + User: &workloadidentityv1pb.UserAttrs{ + Name: "jeff", + }, + Workload: &workloadidentityv1pb.WorkloadAttrs{ + Unix: &workloadidentityv1pb.WorkloadAttrsUnix{ + Pid: 123, + }, + }, + }, + attr: "workload.unix.pid", + want: "123", + requireErr: require.NoError, + }, + { + name: "uint32", + in: &workloadidentityv1pb.Attrs{ + User: &workloadidentityv1pb.UserAttrs{ + Name: "jeff", + }, + Workload: &workloadidentityv1pb.WorkloadAttrs{ + Unix: &workloadidentityv1pb.WorkloadAttrsUnix{ + Gid: 123, + }, + }, + }, + attr: "workload.unix.gid", + want: "123", + requireErr: require.NoError, + }, + { + name: "non-string final field", + in: &workloadidentityv1pb.Attrs{ + User: &workloadidentityv1pb.UserAttrs{ + Name: "user", + }, + }, + attr: "user", + requireErr: func(t require.TestingT, err error, i ...interface{}) { + require.ErrorContains(t, err, "attribute \"user\" of type \"message\" cannot be converted to string") + }, + }, + { + // We mostly just want this to not panic. + name: "nil root", + in: nil, + attr: "user.name", + want: "", + requireErr: require.NoError, + }, + { + // We mostly just want this to not panic. + name: "nil submessage", + in: &workloadidentityv1pb.Attrs{ + User: nil, + }, + attr: "user.name", + want: "", + requireErr: require.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, gotErr := getFieldStringValue(tt.in, tt.attr) + tt.requireErr(t, gotErr) + require.Equal(t, tt.want, got) + }) + } +} + +func Test_templateString(t *testing.T) { + tests := []struct { + name string + in string + want string + attrs *workloadidentityv1pb.Attrs + requireErr require.ErrorAssertionFunc + }{ + { + name: "success mixed", + in: "hello{{user.name}}.{{user.name}} {{ workload.kubernetes.pod_name }}//{{ workload.kubernetes.namespace}}", + want: "hellojeff.jeff pod1//default", + attrs: &workloadidentityv1pb.Attrs{ + User: &workloadidentityv1pb.UserAttrs{ + Name: "jeff", + }, + Workload: &workloadidentityv1pb.WorkloadAttrs{ + Kubernetes: &workloadidentityv1pb.WorkloadAttrsKubernetes{ + PodName: "pod1", + Namespace: "default", + }, + }, + }, + requireErr: require.NoError, + }, + { + name: "success with spaces", + in: "hello {{user.name}}", + want: "hello jeff", + attrs: &workloadidentityv1pb.Attrs{ + User: &workloadidentityv1pb.UserAttrs{ + Name: "jeff", + }, + }, + requireErr: require.NoError, + }, + { + name: "fail due to unset", + in: "hello {{workload.kubernetes.pod_name}}", + attrs: &workloadidentityv1pb.Attrs{ + User: &workloadidentityv1pb.UserAttrs{ + Name: "jeff", + }, + }, + requireErr: func(t require.TestingT, err error, i ...interface{}) { + require.ErrorContains(t, err, "attribute \"workload.kubernetes.pod_name\" unset") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, gotErr := templateString(tt.in, tt.attrs) + tt.requireErr(t, gotErr) + require.Equal(t, tt.want, got) + }) + } +} + +func Test_evaluateRules(t *testing.T) { + attrs := &workloadidentityv1pb.Attrs{ + User: &workloadidentityv1pb.UserAttrs{ + Name: "foo", + }, + } + wi := &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + { + Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + Attribute: "user.name", + Equals: "foo", + }, + }, + }, + }, + }, + }, + } + err := evaluateRules(wi, attrs) + require.NoError(t, err) +} diff --git a/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go b/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go index 1c0601a34dd54..3b7a7b1d85759 100644 --- a/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go +++ b/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go @@ -18,6 +18,7 @@ package workloadidentityv1_test import ( "context" + "crypto/x509" "errors" "fmt" "net" @@ -26,6 +27,7 @@ import ( "testing" "time" + "github.com/go-jose/go-jose/v3/jwt" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/gravitational/trace" @@ -33,6 +35,7 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/durationpb" headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" @@ -40,9 +43,13 @@ import ( "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth/authclient" + "github.com/gravitational/teleport/lib/auth/machineid/workloadidentityv1/experiment" + "github.com/gravitational/teleport/lib/cryptosuites" libevents "github.com/gravitational/teleport/lib/events" "github.com/gravitational/teleport/lib/events/eventstest" + libjwt "github.com/gravitational/teleport/lib/jwt" "github.com/gravitational/teleport/lib/modules" + "github.com/gravitational/teleport/lib/services" ) func TestMain(m *testing.M) { @@ -74,6 +81,397 @@ func newTestTLSServer(t testing.TB) (*auth.TestTLSServer, *eventstest.MockRecord return srv, emitter } +func TestIssueWorkloadIdentity(t *testing.T) { + experimentStatus := experiment.Enabled() + defer experiment.SetEnabled(experimentStatus) + experiment.SetEnabled(true) + + srv, eventRecorder := newTestTLSServer(t) + ctx := context.Background() + clock := srv.Auth().GetClock() + + // Upsert a fake proxy to ensure we have a public address to use for the + // issuer. + proxy, err := types.NewServer("proxy", types.KindProxy, types.ServerSpecV2{ + PublicAddrs: []string{"teleport.example.com"}, + }) + require.NoError(t, err) + err = srv.Auth().UpsertProxy(ctx, proxy) + require.NoError(t, err) + wantIssuer := "https://teleport.example.com/workload-identity" + + // Fetch X509 SPIFFE CA for validation of signature later + spiffeX509CA, err := srv.Auth().GetCertAuthority(ctx, types.CertAuthID{ + Type: types.SPIFFECA, + DomainName: srv.ClusterName(), + }, false) + require.NoError(t, err) + spiffeX509CAPool, err := services.CertPool(spiffeX509CA) + require.NoError(t, err) + // Fetch JWT CA to validate JWTs + jwtCA, err := srv.Auth().GetCertAuthority(ctx, types.CertAuthID{ + Type: types.SPIFFECA, + DomainName: "localhost", + }, true) + require.NoError(t, err) + jwtSigner, err := srv.Auth().GetKeyStore().GetJWTSigner(ctx, jwtCA) + require.NoError(t, err) + kid, err := libjwt.KeyID(jwtSigner.Public()) + require.NoError(t, err) + + wildcardAccess, _, err := auth.CreateUserAndRole( + srv.Auth(), + "dog", + []string{}, + []types.Rule{}, + auth.WithRoleMutator(func(role types.Role) { + role.SetWorkloadIdentityLabels(types.Allow, types.Labels{ + types.Wildcard: []string{types.Wildcard}, + }) + }), + ) + require.NoError(t, err) + wilcardAccessClient, err := srv.NewClient(auth.TestUser(wildcardAccess.GetName())) + require.NoError(t, err) + + specificAccess, _, err := auth.CreateUserAndRole( + srv.Auth(), + "cat", + []string{}, + []types.Rule{}, + auth.WithRoleMutator(func(role types.Role) { + role.SetWorkloadIdentityLabels(types.Allow, types.Labels{ + "foo": []string{"bar"}, + }) + }), + ) + require.NoError(t, err) + specificAccessClient, err := srv.NewClient(auth.TestUser(specificAccess.GetName())) + require.NoError(t, err) + + // Generate a keypair to generate x509 SVIDs for. + workloadKey, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256) + require.NoError(t, err) + workloadKeyPubBytes, err := x509.MarshalPKIXPublicKey(workloadKey.Public()) + require.NoError(t, err) + + // Create some WorkloadIdentity resources + full, err := srv.Auth().CreateWorkloadIdentity(ctx, &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "full", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + { + Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + Attribute: "user.name", + Equals: "dog", + }, + { + Attribute: "workload.kubernetes.namespace", + Equals: "default", + }, + }, + }, + }, + }, + Spiffe: &workloadidentityv1pb.WorkloadIdentitySPIFFE{ + Id: "/example/{{user.name}}/{{ workload.kubernetes.namespace }}/{{ workload.kubernetes.service_account }}", + Hint: "Wow - what a lovely hint, {{user.name}}!", + }, + }, + }) + require.NoError(t, err) + + workloadAttrs := func(f func(attrs *workloadidentityv1pb.WorkloadAttrs)) *workloadidentityv1pb.WorkloadAttrs { + attrs := &workloadidentityv1pb.WorkloadAttrs{ + Kubernetes: &workloadidentityv1pb.WorkloadAttrsKubernetes{ + Attested: true, + Namespace: "default", + PodName: "test", + ServiceAccount: "bar", + }, + } + if f != nil { + f(attrs) + } + return attrs + } + tests := []struct { + name string + client *authclient.Client + req *workloadidentityv1pb.IssueWorkloadIdentityRequest + requireErr require.ErrorAssertionFunc + assert func(*testing.T, *workloadidentityv1pb.IssueWorkloadIdentityResponse) + }{ + { + name: "jwt svid", + client: wilcardAccessClient, + req: &workloadidentityv1pb.IssueWorkloadIdentityRequest{ + Name: full.GetMetadata().GetName(), + Credential: &workloadidentityv1pb.IssueWorkloadIdentityRequest_JwtSvidParams{ + JwtSvidParams: &workloadidentityv1pb.JWTSVIDParams{ + Audiences: []string{"example.com", "test.example.com"}, + }, + }, + WorkloadAttrs: workloadAttrs(nil), + }, + requireErr: require.NoError, + assert: func(t *testing.T, res *workloadidentityv1pb.IssueWorkloadIdentityResponse) { + cred := res.Credential + require.NotNil(t, res.Credential) + + wantTTL := time.Hour + wantSPIFFEID := "spiffe://localhost/example/dog/default/bar" + require.Empty(t, cmp.Diff( + cred, + &workloadidentityv1pb.Credential{ + Ttl: durationpb.New(wantTTL), + SpiffeId: wantSPIFFEID, + Hint: "Wow - what a lovely hint, dog!", + WorkloadIdentityName: full.GetMetadata().GetName(), + WorkloadIdentityRevision: full.GetMetadata().GetRevision(), + }, + protocmp.Transform(), + protocmp.IgnoreFields( + &workloadidentityv1pb.Credential{}, + "expires_at", + ), + protocmp.IgnoreOneofs( + &workloadidentityv1pb.Credential{}, + "credential", + ), + )) + // Check expiry makes sense + require.WithinDuration(t, clock.Now().Add(wantTTL), cred.GetExpiresAt().AsTime(), time.Second) + + // Check the JWT + parsed, err := jwt.ParseSigned(cred.GetJwtSvid().GetJwt()) + require.NoError(t, err) + + claims := jwt.Claims{} + err = parsed.Claims(jwtSigner.Public(), &claims) + require.NoError(t, err) + // Check headers + require.Len(t, parsed.Headers, 1) + require.Equal(t, kid, parsed.Headers[0].KeyID) + // Check claims + require.Equal(t, wantSPIFFEID, claims.Subject) + require.NotEmpty(t, claims.ID) + require.Equal(t, jwt.Audience{"example.com", "test.example.com"}, claims.Audience) + require.Equal(t, wantIssuer, claims.Issuer) + require.WithinDuration(t, clock.Now().Add(wantTTL), claims.Expiry.Time(), 5*time.Second) + require.WithinDuration(t, clock.Now(), claims.IssuedAt.Time(), 5*time.Second) + + // Check audit log event + evt, ok := eventRecorder.LastEvent().(*events.SPIFFESVIDIssued) + require.True(t, ok) + require.NotEmpty(t, evt.ConnectionMetadata.RemoteAddr) + require.Equal(t, claims.ID, evt.JTI) + require.Equal(t, claims.ID, cred.GetJwtSvid().GetJti()) + require.Empty(t, cmp.Diff( + evt, + &events.SPIFFESVIDIssued{ + Metadata: events.Metadata{ + Type: libevents.SPIFFESVIDIssuedEvent, + Code: libevents.SPIFFESVIDIssuedSuccessCode, + }, + UserMetadata: events.UserMetadata{ + User: wildcardAccess.GetName(), + UserKind: events.UserKind_USER_KIND_HUMAN, + }, + SPIFFEID: "spiffe://localhost/example/dog/default/bar", + SVIDType: "jwt", + Hint: "Wow - what a lovely hint, dog!", + WorkloadIdentity: full.GetMetadata().GetName(), + WorkloadIdentityRevision: full.GetMetadata().GetRevision(), + }, + cmpopts.IgnoreFields( + events.SPIFFESVIDIssued{}, + "ConnectionMetadata", + "JTI", + ), + )) + }, + }, + { + name: "x509 svid", + client: wilcardAccessClient, + req: &workloadidentityv1pb.IssueWorkloadIdentityRequest{ + Name: full.GetMetadata().GetName(), + Credential: &workloadidentityv1pb.IssueWorkloadIdentityRequest_X509SvidParams{ + X509SvidParams: &workloadidentityv1pb.X509SVIDParams{ + PublicKey: workloadKeyPubBytes, + }, + }, + WorkloadAttrs: workloadAttrs(nil), + }, + requireErr: require.NoError, + assert: func(t *testing.T, res *workloadidentityv1pb.IssueWorkloadIdentityResponse) { + cred := res.Credential + require.NotNil(t, res.Credential) + + wantSPIFFEID := "spiffe://localhost/example/dog/default/bar" + wantTTL := time.Hour + require.Empty(t, cmp.Diff( + cred, + &workloadidentityv1pb.Credential{ + Ttl: durationpb.New(wantTTL), + SpiffeId: wantSPIFFEID, + Hint: "Wow - what a lovely hint, dog!", + WorkloadIdentityName: full.GetMetadata().GetName(), + WorkloadIdentityRevision: full.GetMetadata().GetRevision(), + }, + protocmp.Transform(), + protocmp.IgnoreFields( + &workloadidentityv1pb.Credential{}, + "expires_at", + ), + protocmp.IgnoreOneofs( + &workloadidentityv1pb.Credential{}, + "credential", + ), + )) + // Check expiry makes sense + require.WithinDuration(t, clock.Now().Add(wantTTL), cred.GetExpiresAt().AsTime(), time.Second) + + // Check the X509 + cert, err := x509.ParseCertificate(cred.GetX509Svid().GetCert()) + require.NoError(t, err) + // Check included public key matches + require.Equal(t, workloadKey.Public(), cert.PublicKey) + // Check cert expiry + require.WithinDuration(t, clock.Now().Add(wantTTL), cert.NotAfter, time.Second) + // Check cert nbf + require.WithinDuration(t, clock.Now().Add(-1*time.Minute), cert.NotBefore, time.Second) + // Check cert TTL + require.Equal(t, cert.NotAfter.Sub(cert.NotBefore), wantTTL+time.Minute) + + // Check against SPIFFE SPEC + // References are to https://github.com/spiffe/spiffe/blob/main/standards/X509-SVID.md + // 2: An X.509 SVID MUST contain exactly one URI SAN, and by extension, exactly one SPIFFE ID + require.Len(t, cert.URIs, 1) + require.Equal(t, wantSPIFFEID, cert.URIs[0].String()) + // 4.1: leaf certificates MUST set the cA field to false. + require.False(t, cert.IsCA) + require.Greater(t, cert.KeyUsage&x509.KeyUsageDigitalSignature, 0) + // 4.3: They MAY set keyEncipherment and/or keyAgreement + require.Greater(t, cert.KeyUsage&x509.KeyUsageKeyEncipherment, 0) + require.Greater(t, cert.KeyUsage&x509.KeyUsageKeyAgreement, 0) + // 4.3: Leaf SVIDs MUST NOT set keyCertSign or cRLSign + require.EqualValues(t, 0, cert.KeyUsage&x509.KeyUsageCertSign) + require.EqualValues(t, 0, cert.KeyUsage&x509.KeyUsageCRLSign) + // 4.4: When included, fields id-kp-serverAuth and id-kp-clientAuth MUST be set. + require.Contains(t, cert.ExtKeyUsage, x509.ExtKeyUsageServerAuth) + require.Contains(t, cert.ExtKeyUsage, x509.ExtKeyUsageClientAuth) + + // Check cert signature is valid + _, err = cert.Verify(x509.VerifyOptions{ + Roots: spiffeX509CAPool, + CurrentTime: srv.Auth().GetClock().Now(), + }) + require.NoError(t, err) + + // Check audit log event + evt, ok := eventRecorder.LastEvent().(*events.SPIFFESVIDIssued) + require.True(t, ok) + require.NotEmpty(t, evt.ConnectionMetadata.RemoteAddr) + require.Equal(t, cred.GetX509Svid().GetSerialNumber(), evt.SerialNumber) + require.Empty(t, cmp.Diff( + evt, + &events.SPIFFESVIDIssued{ + Metadata: events.Metadata{ + Type: libevents.SPIFFESVIDIssuedEvent, + Code: libevents.SPIFFESVIDIssuedSuccessCode, + }, + UserMetadata: events.UserMetadata{ + User: wildcardAccess.GetName(), + UserKind: events.UserKind_USER_KIND_HUMAN, + }, + SPIFFEID: "spiffe://localhost/example/dog/default/bar", + SVIDType: "x509", + Hint: "Wow - what a lovely hint, dog!", + WorkloadIdentity: full.GetMetadata().GetName(), + WorkloadIdentityRevision: full.GetMetadata().GetRevision(), + }, + cmpopts.IgnoreFields( + events.SPIFFESVIDIssued{}, + "ConnectionMetadata", + "SerialNumber", + ), + )) + }, + }, + { + name: "unauthorized by rules", + client: wilcardAccessClient, + req: &workloadidentityv1pb.IssueWorkloadIdentityRequest{ + Name: full.GetMetadata().GetName(), + Credential: &workloadidentityv1pb.IssueWorkloadIdentityRequest_JwtSvidParams{ + JwtSvidParams: &workloadidentityv1pb.JWTSVIDParams{ + Audiences: []string{"example.com", "test.example.com"}, + }, + }, + WorkloadAttrs: workloadAttrs(func(attrs *workloadidentityv1pb.WorkloadAttrs) { + attrs.Kubernetes.Namespace = "not-default" + }), + }, + requireErr: func(t require.TestingT, err error, i ...interface{}) { + require.True(t, trace.IsAccessDenied(err)) + }, + }, + { + name: "unauthorized by labels", + client: specificAccessClient, + req: &workloadidentityv1pb.IssueWorkloadIdentityRequest{ + Name: full.GetMetadata().GetName(), + Credential: &workloadidentityv1pb.IssueWorkloadIdentityRequest_JwtSvidParams{ + JwtSvidParams: &workloadidentityv1pb.JWTSVIDParams{ + Audiences: []string{"example.com", "test.example.com"}, + }, + }, + WorkloadAttrs: workloadAttrs(nil), + }, + requireErr: func(t require.TestingT, err error, i ...interface{}) { + require.True(t, trace.IsAccessDenied(err)) + }, + }, + { + name: "does not exist", + client: specificAccessClient, + req: &workloadidentityv1pb.IssueWorkloadIdentityRequest{ + Name: "does-not-exist", + Credential: &workloadidentityv1pb.IssueWorkloadIdentityRequest_JwtSvidParams{ + JwtSvidParams: &workloadidentityv1pb.JWTSVIDParams{ + Audiences: []string{"example.com", "test.example.com"}, + }, + }, + WorkloadAttrs: workloadAttrs(nil), + }, + requireErr: func(t require.TestingT, err error, i ...interface{}) { + require.True(t, trace.IsNotFound(err)) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + eventRecorder.Reset() + c := workloadidentityv1pb.NewWorkloadIdentityIssuanceServiceClient( + tt.client.GetConnection(), + ) + res, err := c.IssueWorkloadIdentity(ctx, tt.req) + tt.requireErr(t, err) + if tt.assert != nil { + tt.assert(t, res) + } + }) + } +} + func TestResourceService_CreateWorkloadIdentity(t *testing.T) { t.Parallel() srv, eventRecorder := newTestTLSServer(t) diff --git a/lib/auth/middleware.go b/lib/auth/middleware.go index 8e21c071eb4a5..4d5b913f9db56 100644 --- a/lib/auth/middleware.go +++ b/lib/auth/middleware.go @@ -24,6 +24,7 @@ import ( "crypto/x509" "encoding/json" "fmt" + "log/slog" "net" "net/http" "os" @@ -36,7 +37,6 @@ import ( "github.com/gravitational/trace" grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus" "github.com/prometheus/client_golang/prometheus" - "github.com/sirupsen/logrus" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "golang.org/x/net/http2" "google.golang.org/grpc" @@ -57,6 +57,7 @@ import ( "github.com/gravitational/teleport/lib/observability/metrics" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) const ( @@ -139,7 +140,7 @@ type TLSServer struct { // cfg is TLS server configuration used for auth server cfg TLSServerConfig // log is TLS server logging entry - log *logrus.Entry + log *slog.Logger // mux is a listener that multiplexes HTTP/2 and HTTP/1.1 // on different listeners mux *multiplexer.TLSListener @@ -215,9 +216,7 @@ func NewTLSServer(ctx context.Context, cfg TLSServerConfig) (*TLSServer, error) return authz.ContextWithConn(ctx, c) }, }, - log: logrus.WithFields(logrus.Fields{ - teleport.ComponentKey: cfg.Component, - }), + log: slog.With(teleport.ComponentKey, cfg.Component), } tlsConfig := cfg.TLS.Clone() @@ -306,7 +305,7 @@ func (t *TLSServer) Serve() error { errC := make(chan error, 2) go func() { err := t.mux.Serve() - t.log.WithError(err).Warningf("Mux serve failed.") + t.log.WarnContext(context.Background(), "Mux serve failed", "error", err) }() go func() { errC <- t.httpServer.Serve(t.mux.HTTP()) @@ -372,7 +371,9 @@ func getCustomRate(endpoint string) *limiter.RateSet { rates := limiter.NewRateSet() // This limit means: 1 request per minute with bursts up to 10 requests. if err := rates.Add(time.Minute, 1, 10); err != nil { - log.WithError(err).Debugf("Failed to define a custom rate for rpc method %q, using default rate", endpoint) + logger.DebugContext(context.Background(), "Failed to define a custom rate for rpc method, using default rate", + "error", err, + "rpc_method", endpoint) return nil } return rates @@ -383,7 +384,10 @@ func getCustomRate(endpoint string) *limiter.RateSet { const burst = defaults.LimiterBurst rates := limiter.NewRateSet() if err := rates.Add(period, average, burst); err != nil { - log.WithError(err).Debugf("Failed to define a custom rate for rpc method %q, using default rate", endpoint) + logger.DebugContext(context.Background(), "Failed to define a custom rate for rpc method, using default rate", + "error", err, + "rpc_method", endpoint, + ) return nil } return rates @@ -406,24 +410,29 @@ func (a *Middleware) ValidateClientVersion(ctx context.Context, info IdentityInf ua := metadata.UserAgentFromContext(ctx) - logger := log.WithFields(logrus.Fields{"user_agent": ua, "identity": info.IdentityGetter.GetIdentity().Username, "version": clientVersionString, "addr": info.Conn.RemoteAddr().String()}) + logger := slog.With( + "user_agent", ua, + "identity", info.IdentityGetter.GetIdentity().Username, + "version", clientVersionString, + "addr", logutils.StringerAttr(info.Conn.RemoteAddr()), + ) clientVersion, err := semver.NewVersion(clientVersionString) if err != nil { - logger.WithError(err).Warn("Failed to determine client version") + logger.WarnContext(ctx, "Failed to determine client version", "error", err) a.displayRejectedClientAlert(ctx, clientVersionString, info.Conn.RemoteAddr(), ua, info.IdentityGetter) if err := info.Conn.Close(); err != nil { - logger.WithError(err).Warn("Failed to close client connection") + logger.WarnContext(ctx, "Failed to close client connection", "error", err) } return trace.AccessDenied("client version is unsupported") } if clientVersion.LessThan(*a.OldestSupportedVersion) { - logger.Info("Terminating connection of client using unsupported version") + logger.InfoContext(ctx, "Terminating connection of client using unsupported version") a.displayRejectedClientAlert(ctx, clientVersionString, info.Conn.RemoteAddr(), ua, info.IdentityGetter) if err := info.Conn.Close(); err != nil { - logger.WithError(err).Warn("Failed to close client connection") + logger.WarnContext(ctx, "Failed to close client connection", "error", err) } return trace.AccessDenied("client version is unsupported") @@ -486,12 +495,12 @@ func (a *Middleware) displayRejectedClientAlert(ctx context.Context, clientVersi types.WithAlertLabel(types.AlertVerbPermit, fmt.Sprintf("%s:%s", types.KindToken, types.VerbCreate)), ) if err != nil { - log.WithError(err).Warn("failed to create rejected-unsupported-connection alert") + logger.WarnContext(ctx, "failed to create rejected-unsupported-connection alert", "error", err) return } if err := a.AlertCreator(ctx, alert); err != nil { - log.WithError(err).Warn("failed to persist rejected-unsupported-connection alert") + logger.WarnContext(ctx, "failed to persist rejected-unsupported-connection alert", "error", err) return } } @@ -656,7 +665,7 @@ func (a *Middleware) GetUser(connState tls.ConnectionState) (authz.IdentityGette if certClusterName == "" { certClusterName, err = tlsca.ClusterName(clientCert.Issuer) if err != nil { - log.Warnf("Failed to parse client certificate %v.", err) + logger.WarnContext(context.Background(), "Failed to parse client certificate", "error", err) return nil, trace.AccessDenied("access denied: invalid client certificate") } identity.TeleportCluster = certClusterName @@ -667,8 +676,11 @@ func (a *Middleware) GetUser(connState tls.ConnectionState) (authz.IdentityGette // against auth server. Later on we can extend more // advanced cert usage, but for now this is the safest option. if len(identity.Usage) != 0 && !slices.Equal(a.AcceptedUsage, identity.Usage) { - log.Warningf("Restricted certificate of user %q with usage %v rejected while accessing the auth endpoint with acceptable usage %v.", - identity.Username, identity.Usage, a.AcceptedUsage) + logger.WarnContext(context.Background(), "Restricted certificate rejected while accessing the auth endpoint", + "user", identity.Username, + "cert_usage", identity.Usage, + "acceptable_usage", a.AcceptedUsage, + ) return nil, trace.AccessDenied("access denied: invalid client certificate") } @@ -734,7 +746,7 @@ func extractAdditionalSystemRoles(roles []string) types.SystemRoles { if err != nil { // ignore unknown system roles rather than rejecting them, since new unknown system // roles may be present on certs if we rolled back from a newer version. - log.Warnf("Ignoring unknown system role: %q", role) + logger.WarnContext(context.Background(), "Ignoring unknown system role", "unknown_role", role) continue } systemRoles = append(systemRoles, systemRole) diff --git a/lib/auth/presence/presencev1/service.go b/lib/auth/presence/presencev1/service.go index c83e36fc453e3..3eb28af2e740e 100644 --- a/lib/auth/presence/presencev1/service.go +++ b/lib/auth/presence/presencev1/service.go @@ -20,10 +20,10 @@ package presencev1 import ( "context" + "log/slog" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "google.golang.org/protobuf/types/known/emptypb" "github.com/gravitational/teleport" @@ -34,6 +34,7 @@ import ( "github.com/gravitational/teleport/lib/services" usagereporter "github.com/gravitational/teleport/lib/usagereporter/teleport" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) // Backend is the subset of the backend resources that the Service modifies. @@ -65,7 +66,7 @@ type ServiceConfig struct { AuthServer AuthServer Backend Backend Cache Cache - Logger logrus.FieldLogger + Logger *slog.Logger Emitter apievents.Emitter Reporter usagereporter.UsageReporter Clock clockwork.Clock @@ -79,7 +80,7 @@ type Service struct { authServer AuthServer backend Backend cache Cache - logger logrus.FieldLogger + logger *slog.Logger emitter apievents.Emitter reporter usagereporter.UsageReporter clock clockwork.Clock @@ -103,7 +104,7 @@ func NewService(cfg ServiceConfig) (*Service, error) { } if cfg.Logger == nil { - cfg.Logger = logrus.WithField(teleport.ComponentKey, "presence.service") + cfg.Logger = slog.With(teleport.ComponentKey, "presence.service") } if cfg.Clock == nil { cfg.Clock = clockwork.NewRealClock() @@ -149,7 +150,11 @@ func (s *Service) GetRemoteCluster( v3, ok := rc.(*types.RemoteClusterV3) if !ok { - s.logger.Warnf("expected type RemoteClusterV3, got %T for %q", rc, rc.GetName()) + s.logger.WarnContext(ctx, "unexpected remote cluster type", + "got_type", logutils.TypeAttr(rc), + "expected_type", "RemoteClusterV3", + "remote_cluster", rc.GetName(), + ) return nil, trace.BadParameter("encountered unexpected remote cluster type") } @@ -180,7 +185,11 @@ func (s *Service) ListRemoteClusters( for _, rc := range page { v3, ok := rc.(*types.RemoteClusterV3) if !ok { - s.logger.Warnf("expected type RemoteClusterV3, got %T for %q", rc, rc.GetName()) + s.logger.WarnContext(ctx, "unexpected remote cluster type", + "got_type", logutils.TypeAttr(rc), + "expected_type", "RemoteClusterV3", + "remote_cluster", rc.GetName(), + ) continue } concretePage = append(concretePage, v3) @@ -234,7 +243,11 @@ func (s *Service) UpdateRemoteCluster( } v3, ok := rc.(*types.RemoteClusterV3) if !ok { - s.logger.Warnf("expected type RemoteClusterV3, got %T for user %q", rc, rc.GetName()) + s.logger.WarnContext(ctx, "unexpected remote cluster type", + "got_type", logutils.TypeAttr(rc), + "expected_type", "RemoteClusterV3", + "remote_cluster", rc.GetName(), + ) return nil, trace.BadParameter("encountered unexpected remote cluster type") } return v3, nil @@ -271,7 +284,11 @@ func (s *Service) UpdateRemoteCluster( } v3, ok := rc.(*types.RemoteClusterV3) if !ok { - s.logger.Warnf("expected type RemoteClusterV3, got %T for user %q", rc, rc.GetName()) + s.logger.WarnContext(ctx, "unexpected remote cluster type", + "got_type", logutils.TypeAttr(rc), + "expected_type", "RemoteClusterV3", + "remote_cluster", rc.GetName(), + ) return nil, trace.BadParameter("encountered unexpected remote cluster type") } @@ -330,7 +347,11 @@ func (s *Service) ListReverseTunnels( for _, rc := range page { v3, ok := rc.(*types.ReverseTunnelV2) if !ok { - s.logger.Warnf("expected type ReverseTunnelV2, got %T for %q", rc, rc.GetName()) + s.logger.WarnContext(ctx, "unexpected reverse tunnel type", + "got_type", logutils.TypeAttr(rc), + "expected_type", "ReverseTunnelV2", + "reverse_tunnel", rc.GetName(), + ) continue } concretePage = append(concretePage, v3) diff --git a/lib/auth/rotate.go b/lib/auth/rotate.go index 5110909d53654..5f7d708f71551 100644 --- a/lib/auth/rotate.go +++ b/lib/auth/rotate.go @@ -165,9 +165,14 @@ func (a *Server) RotateCertAuthority(ctx context.Context, req types.RotateReques rotation := rotated.GetRotation() switch rotation.State { case types.RotationStateInProgress: - log.WithFields(logrus.Fields{"type": req.Type}).Infof("Updated rotation state, set current phase to: %q.", rotation.Phase) + a.logger.InfoContext(ctx, "Updated rotation state", + "current_phase", rotation.Phase, + "ca_type", req.Type, + ) case types.RotationStateStandby: - log.WithFields(logrus.Fields{"type": req.Type}).Infof("Updated and completed rotation.") + a.logger.InfoContext(ctx, "Updated and completed rotation", + "ca_type", req.Type, + ) } return nil diff --git a/lib/auth/sessions.go b/lib/auth/sessions.go index 9947279aad248..7f202bd9110b3 100644 --- a/lib/auth/sessions.go +++ b/lib/auth/sessions.go @@ -25,7 +25,6 @@ import ( "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" @@ -154,7 +153,7 @@ func (a *Server) augmentSessionForDeviceTrust( }) switch { case err != nil: - log.WithError(err).Warn("Failed to create DeviceWebToken for user") + a.logger.WarnContext(ctx, "Failed to create DeviceWebToken for user", "error", err) case webToken != nil: // May be nil even if err==nil. session.SetDeviceWebToken(&types.DeviceWebToken{ Id: webToken.Id, @@ -210,7 +209,7 @@ func (a *Server) newWebSession( } if req.LoginIP == "" { // TODO(antonam): consider turning this into error after all use cases are covered (before v14.0 testplan) - log.Debug("Creating new web session without login IP specified.") + a.logger.DebugContext(ctx, "Creating new web session without login IP specified") } clusterName, err := a.GetClusterName() @@ -351,17 +350,15 @@ func (a *Server) newWebSession( if tdr, err := a.calculateTrustedDeviceMode(ctx, func() ([]types.Role, error) { return checker.Roles(), nil }); err != nil { - log. - WithError(err). - Warn("Failed to calculate trusted device mode for session") + a.logger.WarnContext(ctx, "Failed to calculate trusted device mode for session", "error", err) } else { sess.SetTrustedDeviceRequirement(tdr) if tdr != types.TrustedDeviceRequirement_TRUSTED_DEVICE_REQUIREMENT_UNSPECIFIED { - log.WithFields(logrus.Fields{ - "user": req.User, - "trusted_device_requirement": tdr, - }).Debug("Calculated trusted device requirement for session") + a.logger.DebugContext(ctx, "Calculated trusted device requirement for session", + "user", req.User, + "trusted_device_requirement", tdr, + ) } } @@ -597,7 +594,7 @@ func (a *Server) CreateAppSessionFromReq(ctx context.Context, req NewAppSessionR if err = a.UpsertAppSession(ctx, session); err != nil { return nil, trace.Wrap(err) } - log.Debugf("Generated application web session for %v with TTL %v.", req.User, req.SessionTTL) + a.logger.DebugContext(ctx, "Generated application web session", "user", req.User, "ttl", req.SessionTTL) UserLoginCount.Inc() // Extract the identity of the user from the certificate, this will include metadata from any actively assumed access requests. @@ -643,7 +640,7 @@ func (a *Server) CreateAppSessionFromReq(ctx context.Context, req NewAppSessionR }, }) if err != nil { - log.WithError(err).Warn("Failed to emit app session start event") + a.logger.WarnContext(ctx, "Failed to emit app session start event", "error", err) } return session, nil @@ -784,7 +781,7 @@ func (a *Server) CreateSnowflakeSession(ctx context.Context, req types.CreateSno if err = a.UpsertSnowflakeSession(ctx, session); err != nil { return nil, trace.Wrap(err) } - log.Debugf("Generated Snowflake web session for %v with TTL %v.", req.Username, ttl) + a.logger.DebugContext(ctx, "Generated Snowflake web session", "user", req.Username, "ttl", ttl) return session, nil } @@ -808,7 +805,7 @@ func (a *Server) CreateSAMLIdPSession(ctx context.Context, req types.CreateSAMLI if err = a.UpsertSAMLIdPSession(ctx, session); err != nil { return nil, trace.Wrap(err) } - log.Debugf("Generated SAML IdP web session for %v.", req.Username) + a.logger.DebugContext(ctx, "Generated SAML IdP web session", "user", req.Username) return session, nil } diff --git a/lib/auth/touchid/api.go b/lib/auth/touchid/api.go index 7769b6dd5d2f7..0d96a61f05297 100644 --- a/lib/auth/touchid/api.go +++ b/lib/auth/touchid/api.go @@ -20,6 +20,7 @@ package touchid import ( "bytes" + "context" "crypto/ecdsa" "crypto/sha256" "encoding/base64" @@ -38,10 +39,11 @@ import ( "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/protocol/webauthncose" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" + "github.com/gravitational/teleport" wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes" "github.com/gravitational/teleport/lib/darwin" + logutils "github.com/gravitational/teleport/lib/utils/log" ) var ( @@ -52,6 +54,8 @@ var ( PromptPlatformMessage = "Using platform authenticator, follow the OS prompt" // PromptWriter is the writer used for prompt messages. PromptWriter io.Writer = os.Stderr + + logger = logutils.NewPackageLogger(teleport.ComponentKey, "TouchID") ) func promptPlatform() { @@ -167,7 +171,7 @@ func IsAvailable() bool { var err error cachedDiag, err = Diag() if err != nil { - log.WithError(err).Warn("Touch ID self-diagnostics failed") + logger.WarnContext(context.Background(), "self-diagnostics failed", "error", err) return false } } @@ -356,7 +360,7 @@ func HasCredentials(rpid, user string) bool { } creds, err := native.FindCredentials(rpid, user) if err != nil { - log.WithError(err).Debug("Touch ID: Could not find credentials") + logger.DebugContext(context.Background(), "Could not find credentials", "error", err) return false } return len(creds) > 0 @@ -494,7 +498,7 @@ func Login(origin, user string, assertion *wantypes.CredentialAssertion, picker if err != nil { return nil, "", trace.Wrap(err) } - log.Debugf("Touch ID: using credential %q", cred.CredentialID) + logger.DebugContext(context.Background(), "using credential", "credential_id", cred.CredentialID) attData, err := makeAttestationData(protocol.AssertCeremony, origin, rpID, assertion.Response.Challenge, nil /* cred */) if err != nil { @@ -609,7 +613,7 @@ func ListCredentials() ([]CredentialInfo, error) { info := &infos[i] key, err := darwin.ECDSAPublicKeyFromRaw(info.publicKeyRaw) if err != nil { - log.Warnf("Failed to convert public key: %v", err) + logger.WarnContext(context.Background(), "Failed to convert public key", "error", err) } info.PublicKey = key // this is OK, even if it's nil info.publicKeyRaw = nil diff --git a/lib/auth/touchid/api_darwin.go b/lib/auth/touchid/api_darwin.go index a7ac71653a863..723660e831c50 100644 --- a/lib/auth/touchid/api_darwin.go +++ b/lib/auth/touchid/api_darwin.go @@ -33,6 +33,7 @@ package touchid import "C" import ( + "context" "encoding/base64" "fmt" "runtime/cgo" @@ -42,7 +43,8 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" + + logutils "github.com/gravitational/teleport/lib/utils/log" ) const ( @@ -110,7 +112,7 @@ func (touchIDImpl) Diag() (*DiagResult, error) { laErrorDomain := C.GoString(resC.la_error_domain) laErrorDescription := C.GoString(resC.la_error_description) if !passedLA && laErrorDescription != "" { - log.Debugf("Touch ID: LAError description: %v", laErrorDescription) + logger.DebugContext(context.Background(), "Received non-empty LAError description", "description", laErrorDescription) } isAvailable := signed && entitled && passedLA && passedEnclave @@ -141,7 +143,7 @@ func runGoFuncHandle(handle C.uintptr_t) { val := cgo.Handle(handle).Value() fn, ok := val.(func()) if !ok { - log.Warnf("Touch ID: received unexpected function handle: %T", val) + logger.WarnContext(context.Background(), "received unexpected function handle", "handle", logutils.TypeAttr(val)) return } fn() @@ -304,6 +306,8 @@ func readCredentialInfos(find func(**C.CredentialInfo) C.int) ([]CredentialInfo, var infosC *C.CredentialInfo defer func() { C.free(unsafe.Pointer(infosC)) }() + ctx := context.Background() + res := find(&infosC) if res < 0 { return nil, int(res) @@ -338,21 +342,30 @@ func readCredentialInfos(find func(**C.CredentialInfo) C.int) ([]CredentialInfo, // user@rpid parsedLabel, err := parseLabel(label) if err != nil { - log.Debugf("Skipping credential %q: %v", credentialID, err) + logger.DebugContext(ctx, "Skipping credential", + "credential_id", credentialID, + "error", err, + ) continue } // user handle userHandle, err := base64.RawURLEncoding.DecodeString(appTag) if err != nil { - log.Debugf("Skipping credential %q: unexpected application tag: %q", credentialID, appTag) + logger.DebugContext(ctx, "Skipping credential, unexpected application tag", + "credential_id", credentialID, + "app_tag", appTag, + ) continue } // ECDSA public key pubKeyRaw, err := base64.StdEncoding.DecodeString(pubKeyB64) if err != nil { - log.WithError(err).Warnf("Failed to decode public key for credential %q", credentialID) + logger.WarnContext(ctx, "Failed to decode public key for credential", + "credential_id", credentialID, + "error", err, + ) // Do not return or break out of the loop, it needs to run in order to // deallocate the structs within. } @@ -361,7 +374,11 @@ func readCredentialInfos(find func(**C.CredentialInfo) C.int) ([]CredentialInfo, const iso8601Format = "2006-01-02T15:04:05Z0700" createTime, err := time.Parse(iso8601Format, creationDate) if err != nil { - log.WithError(err).Warnf("Failed to parse creation time %q for credential %q", creationDate, credentialID) + logger.WarnContext(ctx, "Failed to parse creation time for credential", + "creation_time", creationDate, + "credential_id", credentialID, + "error", err, + ) } infos = append(infos, CredentialInfo{ diff --git a/lib/auth/trust/trustv1/service.go b/lib/auth/trust/trustv1/service.go index 91f99a4819be9..345bb8419940d 100644 --- a/lib/auth/trust/trustv1/service.go +++ b/lib/auth/trust/trustv1/service.go @@ -23,10 +23,8 @@ import ( "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "google.golang.org/protobuf/types/known/emptypb" - "github.com/gravitational/teleport" trustpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/trust/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/authz" @@ -44,6 +42,13 @@ type authServer interface { // RotateCertAuthority starts or restarts certificate authority rotation process. RotateCertAuthority(ctx context.Context, req types.RotateRequest) error + + // UpsertTrustedClusterV2 upserts a Trusted Cluster. + UpsertTrustedClusterV2(ctx context.Context, tc types.TrustedCluster) (types.TrustedCluster, error) + // CreateTrustedCluster creates a Trusted Cluster. + CreateTrustedCluster(ctx context.Context, tc types.TrustedCluster) (types.TrustedCluster, error) + // UpdateTrustedCluster updates a Trusted Cluster. + UpdateTrustedCluster(ctx context.Context, tc types.TrustedCluster) (types.TrustedCluster, error) } // ServiceConfig holds configuration options for @@ -52,7 +57,6 @@ type ServiceConfig struct { Authorizer authz.Authorizer Cache services.AuthorityGetter Backend services.TrustInternal - Logger *logrus.Entry AuthServer authServer } @@ -63,7 +67,6 @@ type Service struct { cache services.AuthorityGetter backend services.TrustInternal authServer authServer - logger *logrus.Entry } // NewService returns a new trust gRPC service. @@ -77,12 +80,9 @@ func NewService(cfg *ServiceConfig) (*Service, error) { return nil, trace.BadParameter("authorizer is required") case cfg.AuthServer == nil: return nil, trace.BadParameter("authServer is required") - case cfg.Logger == nil: - cfg.Logger = logrus.WithField(teleport.ComponentKey, "trust.service") } return &Service{ - logger: cfg.Logger, authorizer: cfg.Authorizer, cache: cfg.Cache, backend: cfg.Backend, diff --git a/lib/auth/trust/trustv1/service_test.go b/lib/auth/trust/trustv1/service_test.go index 152c2558c22b3..23ae66a5149b5 100644 --- a/lib/auth/trust/trustv1/service_test.go +++ b/lib/auth/trust/trustv1/service_test.go @@ -100,6 +100,18 @@ func (f *fakeAuthServer) RotateCertAuthority(ctx context.Context, req types.Rota return f.rotateCertAuthorityData[string(req.Type)] } +func (f *fakeAuthServer) UpsertTrustedClusterV2(ctx context.Context, tc types.TrustedCluster) (types.TrustedCluster, error) { + return tc, nil +} + +func (f *fakeAuthServer) CreateTrustedCluster(ctx context.Context, tc types.TrustedCluster) (types.TrustedCluster, error) { + return tc, nil +} + +func (f *fakeAuthServer) UpdateTrustedCluster(ctx context.Context, tc types.TrustedCluster) (types.TrustedCluster, error) { + return tc, nil +} + type fakeChecker struct { services.AccessChecker allow map[check]bool diff --git a/lib/auth/trust/trustv1/trustedcluster.go b/lib/auth/trust/trustv1/trustedcluster.go new file mode 100644 index 0000000000000..48f57a43ba0ec --- /dev/null +++ b/lib/auth/trust/trustv1/trustedcluster.go @@ -0,0 +1,126 @@ +/* + * 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 trustv1 + +import ( + "context" + + "github.com/gravitational/trace" + + trustpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/trust/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/modules" + "github.com/gravitational/teleport/lib/services" +) + +// UpsertTrustedCluster upserts a Trusted Cluster. +func (s *Service) UpsertTrustedCluster(ctx context.Context, req *trustpb.UpsertTrustedClusterRequest) (*types.TrustedClusterV2, error) { + // Don't allow a Cloud tenant to be a leaf cluster. + if modules.GetModules().Features().Cloud { + return nil, trace.NotImplemented("cloud tenants cannot be leaf clusters") + } + + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + if err := authCtx.CheckAccessToKind(types.KindTrustedCluster, types.VerbCreate, types.VerbUpdate); err != nil { + return nil, trace.Wrap(err) + } + if err := authCtx.AuthorizeAdminActionAllowReusedMFA(); err != nil { + return nil, trace.Wrap(err) + } + + if err = services.ValidateTrustedCluster(req.GetTrustedCluster()); err != nil { + return nil, trace.Wrap(err) + } + tc, err := s.authServer.UpsertTrustedClusterV2(ctx, req.GetTrustedCluster()) + if err != nil { + return nil, trace.Wrap(err) + } + trustedClusterV2, ok := tc.(*types.TrustedClusterV2) + if !ok { + return nil, trace.Errorf("encountered unexpected Trusted Cluster type: %T", tc) + } + return trustedClusterV2, nil +} + +// CreateTrustedCluster creates a Trusted Cluster. +func (s *Service) CreateTrustedCluster(ctx context.Context, req *trustpb.CreateTrustedClusterRequest) (*types.TrustedClusterV2, error) { + // Don't allow a Cloud tenant to be a leaf cluster. + if modules.GetModules().Features().Cloud { + return nil, trace.NotImplemented("cloud tenants cannot be leaf clusters") + } + + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + if err := authCtx.CheckAccessToKind(types.KindTrustedCluster, types.VerbCreate); err != nil { + return nil, trace.Wrap(err) + } + if err := authCtx.AuthorizeAdminActionAllowReusedMFA(); err != nil { + return nil, trace.Wrap(err) + } + + if err = services.ValidateTrustedCluster(req.GetTrustedCluster()); err != nil { + return nil, trace.Wrap(err) + } + tc, err := s.authServer.CreateTrustedCluster(ctx, req.GetTrustedCluster()) + if err != nil { + return nil, trace.Wrap(err) + } + trustedClusterV2, ok := tc.(*types.TrustedClusterV2) + if !ok { + return nil, trace.Errorf("encountered unexpected Trusted Cluster type: %T", tc) + } + return trustedClusterV2, nil +} + +// UpdateTrustedCluster updates a Trusted Cluster. +func (s *Service) UpdateTrustedCluster(ctx context.Context, req *trustpb.UpdateTrustedClusterRequest) (*types.TrustedClusterV2, error) { + // Don't allow a Cloud tenant to be a leaf cluster. + if modules.GetModules().Features().Cloud { + return nil, trace.NotImplemented("cloud tenants cannot be leaf clusters") + } + + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + if err := authCtx.CheckAccessToKind(types.KindTrustedCluster, types.VerbUpdate); err != nil { + return nil, trace.Wrap(err) + } + if err := authCtx.AuthorizeAdminActionAllowReusedMFA(); err != nil { + return nil, trace.Wrap(err) + } + + if err = services.ValidateTrustedCluster(req.GetTrustedCluster()); err != nil { + return nil, trace.Wrap(err) + } + tc, err := s.authServer.UpdateTrustedCluster(ctx, req.GetTrustedCluster()) + if err != nil { + return nil, trace.Wrap(err) + } + trustedClusterV2, ok := tc.(*types.TrustedClusterV2) + if !ok { + return nil, trace.Errorf("encountered unexpected Trusted Cluster type: %T", tc) + } + return trustedClusterV2, nil +} diff --git a/lib/auth/trust/trustv1/trustedcluster_test.go b/lib/auth/trust/trustv1/trustedcluster_test.go new file mode 100644 index 0000000000000..d4aea850fedc4 --- /dev/null +++ b/lib/auth/trust/trustv1/trustedcluster_test.go @@ -0,0 +1,281 @@ +/* + * 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 trustv1 + +import ( + "context" + "testing" + + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport" + trustpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/trust/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/modules" + "github.com/gravitational/teleport/lib/services/local" +) + +// TestCloudProhibited verifies that Trusted Clusters cannot be created or updated +// in a Cloud hosted environment. +// Tests cannot be run in parallel because it relies on environment variables. +func TestCloudProhibited(t *testing.T) { + ctx := context.Background() + p := newTestPack(t) + + trust := local.NewCAService(p.mem) + cfg := &ServiceConfig{ + Cache: trust, + Backend: trust, + Authorizer: &fakeAuthorizer{}, + AuthServer: &fakeAuthServer{}, + } + + service, err := NewService(cfg) + require.NoError(t, err) + + modules.SetTestModules(t, &modules.TestModules{ + TestFeatures: modules.Features{Cloud: true}, + }) + + tc, err := types.NewTrustedCluster("test", types.TrustedClusterSpecV2{ + RoleMap: []types.RoleMapping{ + {Remote: teleport.PresetAccessRoleName, Local: []string{teleport.PresetAccessRoleName}}, + }, + }) + require.NoError(t, err, "creating trusted cluster resource") + + trustedClusterV2, ok := tc.(*types.TrustedClusterV2) + require.True(t, ok) + + t.Run("Cloud prohibits being a leaf cluster (UpsertTrustedCluster)", func(t *testing.T) { + _, err = service.UpsertTrustedCluster(ctx, &trustpb.UpsertTrustedClusterRequest{ + TrustedCluster: trustedClusterV2, + }) + require.True(t, trace.IsNotImplemented(err), "UpsertTrustedClusterV2 returned an unexpected error, got = %v (%T), want trace.NotImplementedError", err, err) + }) + + t.Run("Cloud prohibits being a leaf cluster (CreateTrustedCluster)", func(t *testing.T) { + _, err = service.CreateTrustedCluster(ctx, &trustpb.CreateTrustedClusterRequest{ + TrustedCluster: trustedClusterV2, + }) + require.True(t, trace.IsNotImplemented(err), "CreateTrustedCluster returned an unexpected error, got = %v (%T), want trace.NotImplementedError", err, err) + }) + + t.Run("Cloud prohibits being a leaf cluster (UpdateTrustedCluster)", func(t *testing.T) { + _, err = service.UpdateTrustedCluster(ctx, &trustpb.UpdateTrustedClusterRequest{ + TrustedCluster: trustedClusterV2, + }) + require.True(t, trace.IsNotImplemented(err), "UpdateTrustedCluster returned an unexpected error, got = %v (%T), want trace.NotImplementedError", err, err) + }) +} + +func TestTrustedClusterRBAC(t *testing.T) { + t.Parallel() + ctx := context.Background() + p := newTestPack(t) + + tc, err := types.NewTrustedCluster("test", types.TrustedClusterSpecV2{ + RoleMap: []types.RoleMapping{ + {Remote: teleport.PresetAccessRoleName, Local: []string{teleport.PresetAccessRoleName}}, + }, + }) + require.NoError(t, err, "creating trusted cluster resource") + + trustedClusterV2, ok := tc.(*types.TrustedClusterV2) + require.True(t, ok) + + tests := []struct { + desc string + f func(t *testing.T, service *Service) + authorizer fakeAuthorizer + expectChecks []check + }{ + { + desc: "upsert no access", + f: func(t *testing.T, service *Service) { + _, err := service.UpsertTrustedCluster(ctx, &trustpb.UpsertTrustedClusterRequest{ + TrustedCluster: trustedClusterV2, + }) + require.True(t, trace.IsAccessDenied(err), "expected AccessDenied error, got %v", err) + }, + authorizer: fakeAuthorizer{ + checker: &fakeChecker{ + allow: map[check]bool{}, + }, + }, + expectChecks: []check{ + {types.KindTrustedCluster, types.VerbCreate}, + {types.KindTrustedCluster, types.VerbUpdate}, + }, + }, + { + desc: "upsert no create access", + f: func(t *testing.T, service *Service) { + _, err := service.UpsertTrustedCluster(ctx, &trustpb.UpsertTrustedClusterRequest{ + TrustedCluster: trustedClusterV2, + }) + require.True(t, trace.IsAccessDenied(err), "expected AccessDenied error, got %v", err) + }, + authorizer: fakeAuthorizer{ + checker: &fakeChecker{ + allow: map[check]bool{ + {types.KindTrustedCluster, types.VerbCreate}: false, + {types.KindTrustedCluster, types.VerbUpdate}: true, + }, + }, + }, + expectChecks: []check{ + {types.KindTrustedCluster, types.VerbCreate}, + {types.KindTrustedCluster, types.VerbUpdate}, + }, + }, + { + desc: "upsert no update access", + f: func(t *testing.T, service *Service) { + _, err := service.UpsertTrustedCluster(ctx, &trustpb.UpsertTrustedClusterRequest{ + TrustedCluster: trustedClusterV2, + }) + require.True(t, trace.IsAccessDenied(err), "expected AccessDenied error, got %v", err) + }, + authorizer: fakeAuthorizer{ + checker: &fakeChecker{ + allow: map[check]bool{ + {types.KindTrustedCluster, types.VerbCreate}: true, + {types.KindTrustedCluster, types.VerbUpdate}: false, + }, + }, + }, + expectChecks: []check{ + {types.KindTrustedCluster, types.VerbCreate}, + {types.KindTrustedCluster, types.VerbUpdate}, + }, + }, + { + desc: "upsert ok", + f: func(t *testing.T, service *Service) { + _, err := service.UpsertTrustedCluster(ctx, &trustpb.UpsertTrustedClusterRequest{ + TrustedCluster: trustedClusterV2, + }) + require.NoError(t, err) + }, + authorizer: fakeAuthorizer{ + checker: &fakeChecker{ + allow: map[check]bool{ + {types.KindTrustedCluster, types.VerbCreate}: true, + {types.KindTrustedCluster, types.VerbUpdate}: true, + }, + }, + }, + expectChecks: []check{ + {types.KindTrustedCluster, types.VerbCreate}, + {types.KindTrustedCluster, types.VerbUpdate}, + }, + }, + { + desc: "create no access", + f: func(t *testing.T, service *Service) { + _, err := service.CreateTrustedCluster(ctx, &trustpb.CreateTrustedClusterRequest{ + TrustedCluster: trustedClusterV2, + }) + require.True(t, trace.IsAccessDenied(err), "expected AccessDenied error, got %v", err) + }, + authorizer: fakeAuthorizer{ + checker: &fakeChecker{ + allow: map[check]bool{}, + }, + }, + expectChecks: []check{ + {types.KindTrustedCluster, types.VerbCreate}, + }, + }, + { + desc: "create ok", + f: func(t *testing.T, service *Service) { + _, err := service.CreateTrustedCluster(ctx, &trustpb.CreateTrustedClusterRequest{ + TrustedCluster: trustedClusterV2, + }) + require.NoError(t, err) + }, + authorizer: fakeAuthorizer{ + checker: &fakeChecker{ + allow: map[check]bool{ + {types.KindTrustedCluster, types.VerbCreate}: true, + }, + }, + }, + expectChecks: []check{ + {types.KindTrustedCluster, types.VerbCreate}, + }, + }, + { + desc: "update no access", + f: func(t *testing.T, service *Service) { + _, err := service.UpdateTrustedCluster(ctx, &trustpb.UpdateTrustedClusterRequest{ + TrustedCluster: trustedClusterV2, + }) + require.True(t, trace.IsAccessDenied(err), "expected AccessDenied error, got %v", err) + }, + authorizer: fakeAuthorizer{ + checker: &fakeChecker{ + allow: map[check]bool{}, + }, + }, + expectChecks: []check{ + {types.KindTrustedCluster, types.VerbUpdate}, + }, + }, + { + desc: "update ok", + f: func(t *testing.T, service *Service) { + _, err := service.UpdateTrustedCluster(ctx, &trustpb.UpdateTrustedClusterRequest{ + TrustedCluster: trustedClusterV2, + }) + require.NoError(t, err) + }, + authorizer: fakeAuthorizer{ + checker: &fakeChecker{ + allow: map[check]bool{ + {types.KindTrustedCluster, types.VerbUpdate}: true, + }, + }, + }, + expectChecks: []check{ + {types.KindTrustedCluster, types.VerbUpdate}, + }, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + trust := local.NewCAService(p.mem) + cfg := &ServiceConfig{ + Cache: trust, + Backend: trust, + Authorizer: &test.authorizer, + AuthServer: &fakeAuthServer{}, + } + + service, err := NewService(cfg) + require.NoError(t, err) + test.f(t, service) + require.ElementsMatch(t, test.expectChecks, test.authorizer.checker.checks) + }) + } +} diff --git a/lib/auth/trustedcluster.go b/lib/auth/trustedcluster.go index 784d6aad010dc..e86008679d31e 100644 --- a/lib/auth/trustedcluster.go +++ b/lib/auth/trustedcluster.go @@ -30,6 +30,7 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/client/webclient" tracehttp "github.com/gravitational/teleport/api/observability/tracing/http" "github.com/gravitational/teleport/api/types" apievents "github.com/gravitational/teleport/api/types/events" @@ -45,7 +46,53 @@ import ( ) // UpsertTrustedCluster creates or toggles a Trusted Cluster relationship. +// Deprecated: UpsertTrustedClusterV2 should be preferred instead. func (a *Server) UpsertTrustedCluster(ctx context.Context, tc types.TrustedCluster) (newTrustedCluster types.TrustedCluster, returnErr error) { + const validateNameFalse = false + upserted, err := a.upsertTrustedCluster(ctx, tc, validateNameFalse) + return upserted, trace.Wrap(err) +} + +// UpsertTrustedClusterV2 creates or toggles a Trusted Cluster relationship. +// The trusted cluster resource name must match the cluster name. +func (a *Server) UpsertTrustedClusterV2(ctx context.Context, tc types.TrustedCluster) (newTrustedCluster types.TrustedCluster, returnErr error) { + const validateNameTrue = true + upserted, err := a.upsertTrustedCluster(ctx, tc, validateNameTrue) + return upserted, trace.Wrap(err) +} + +// CreateTrustedCluster creates a Trusted Cluster relationship. +func (a *Server) CreateTrustedCluster(ctx context.Context, tc types.TrustedCluster) (newTrustedCluster types.TrustedCluster, returnErr error) { + // verify that trusted cluster role map does not reference non-existent roles + if err := a.checkLocalRoles(ctx, tc.GetRoleMap()); err != nil { + return nil, trace.Wrap(err) + } + + const validateNameTrue = true + created, err := a.createTrustedCluster(ctx, tc, validateNameTrue) + return created, trace.Wrap(err) +} + +// UpdateTrustedCluster updates a Trusted Cluster relationship. +func (a *Server) UpdateTrustedCluster(ctx context.Context, tc types.TrustedCluster) (newTrustedCluster types.TrustedCluster, returnErr error) { + // verify that trusted cluster role map does not reference non-existent roles + if err := a.checkLocalRoles(ctx, tc.GetRoleMap()); err != nil { + return nil, trace.Wrap(err) + } + + existingCluster, err := a.GetTrustedCluster(ctx, tc.GetName()) + if err != nil { + return nil, trace.Wrap(err) + } + + updated, err := a.updateTrustedCluster(ctx, tc, existingCluster) + return updated, trace.Wrap(err) +} + +// upsertTrustedCluster upserts the trusted cluster. +// If validateName is true, the trusted cluster resource name must be validated +// before the trusted cluster is created. +func (a *Server) upsertTrustedCluster(ctx context.Context, tc types.TrustedCluster, validateName bool) (types.TrustedCluster, error) { // verify that trusted cluster role map does not reference non-existent roles if err := a.checkLocalRoles(ctx, tc.GetRoleMap()); err != nil { return nil, trace.Wrap(err) @@ -67,7 +114,7 @@ func (a *Server) UpsertTrustedCluster(ctx context.Context, tc types.TrustedClust // if there is no existing cluster, switch to the create case if existingCluster == nil { - return a.createTrustedCluster(ctx, tc) + return a.createTrustedCluster(ctx, tc, validateName) } if err := existingCluster.CanChangeStateTo(tc); err != nil { @@ -103,8 +150,11 @@ func (a *Server) UpsertTrustedCluster(ctx context.Context, tc types.TrustedClust return tc, nil } -func (a *Server) createTrustedCluster(ctx context.Context, tc types.TrustedCluster) (types.TrustedCluster, error) { - remoteCAs, err := a.establishTrust(ctx, tc) +// createTrustedCluster creates the trusted cluster. +// If validateName is true, the trusted cluster resource name must be validated +// before the trusted cluster is created. +func (a *Server) createTrustedCluster(ctx context.Context, tc types.TrustedCluster, validateName bool) (types.TrustedCluster, error) { + remoteCAs, err := a.establishTrust(ctx, tc, validateName) if err != nil { return nil, trace.Wrap(err) } @@ -130,6 +180,38 @@ func (a *Server) createTrustedCluster(ctx context.Context, tc types.TrustedClust return tc, nil } +// updateTrustedCluster updates the trusted cluster. +func (a *Server) updateTrustedCluster(ctx context.Context, tc types.TrustedCluster, existingCluster types.TrustedCluster) (types.TrustedCluster, error) { + if err := existingCluster.CanChangeStateTo(tc); err != nil { + return nil, trace.Wrap(err) + } + + // always load all current CAs. even if we aren't changing them as part of + // this function, Services.UpdateTrustedCluster will only correctly activate/deactivate + // CAs that are explicitly passed to it. note that we pass in the existing cluster state + // since where CAs are stored depends on the current state of the trusted cluster. + cas, err := a.getCAsForTrustedCluster(ctx, existingCluster) + if err != nil { + return nil, trace.Wrap(err) + } + + // propagate any role map changes to cas + configureCAsForTrustedCluster(tc, cas) + + revision, err := a.Services.UpdateTrustedCluster(ctx, tc, cas) + if err != nil { + return nil, trace.Wrap(err) + } + + tc.SetRevision(revision) + + if err := a.onTrustedClusterWrite(ctx, tc); err != nil { + return nil, trace.Wrap(err) + } + + return tc, nil +} + // configureCAsForTrustedCluster modifies remote CAs for use as trusted cluster CAs. func configureCAsForTrustedCluster(tc types.TrustedCluster, cas []types.CertAuthority) { // modify the remote CAs for use as tc cas. @@ -267,7 +349,7 @@ func (a *Server) DeleteTrustedCluster(ctx context.Context, name string) error { return nil } -func (a *Server) establishTrust(ctx context.Context, trustedCluster types.TrustedCluster) ([]types.CertAuthority, error) { +func (a *Server) establishTrust(ctx context.Context, trustedCluster types.TrustedCluster, validateName bool) ([]types.CertAuthority, error) { var localCertAuthorities []types.CertAuthority domainName, err := a.GetDomainName() @@ -286,6 +368,14 @@ func (a *Server) establishTrust(ctx context.Context, trustedCluster types.Truste } } + // Validate cluster names before establishing trust to avoid unnecessarily + // creating a remote_cluster resource on the root cluster. + if validateName { + if err := a.validateTrustedClusterName(ctx, trustedCluster); err != nil { + return nil, trace.Wrap(err) + } + } + // create a request to validate a trusted cluster (token and local certificate authorities) validateRequest := authclient.ValidateTrustedClusterRequest{ Token: trustedCluster.GetToken(), @@ -322,9 +412,10 @@ func (a *Server) establishTrust(ctx context.Context, trustedCluster types.Truste if remoteClusterName == domainName { return nil, trace.BadParameter("remote cluster name can not be the same as local cluster name") } - // TODO(klizhentas) in 2.5.0 prohibit adding trusted cluster resource name - // different from cluster name (we had no way of checking this before x509, - // because SSH CA was a public key, not a cert with metadata) + if validateName && trustedCluster.GetName() != remoteClusterName { + return nil, trace.BadParameter("trusted cluster resource name must be the same as the remote cluster name. got: %q", + trustedCluster.GetName()) + } } } @@ -586,7 +677,6 @@ func (a *Server) sendValidateRequestToProxy(host string, validateRequest *authcl if err != nil { return nil, trace.Wrap(err) } - validateRequestRaw, err := validateRequest.ToRaw() if err != nil { return nil, trace.Wrap(err) @@ -611,6 +701,24 @@ func (a *Server) sendValidateRequestToProxy(host string, validateRequest *authcl return validateResponse, nil } +// validateTrustedClusterName validates that the trusted cluster resource name +// matches the cluster name. +func (a *Server) validateTrustedClusterName(ctx context.Context, trustedCluster types.TrustedCluster) error { + resp, err := webclient.Find(&webclient.Config{ + Context: ctx, + ProxyAddr: trustedCluster.GetProxyAddress(), + Insecure: lib.IsInsecureDevMode(), + }) + if err != nil { + return trace.Wrap(err) + } + if trustedCluster.GetName() != resp.ClusterName { + return trace.BadParameter("trusted cluster resource name must be the same as the remote cluster name. got: %q", + trustedCluster.GetName()) + } + return nil +} + // createReverseTunnel will create a services.ReverseTunnel givenin the // services.TrustedCluster resource. func (a *Server) createReverseTunnel(ctx context.Context, t types.TrustedCluster) error { diff --git a/lib/auth/trustedcluster_test.go b/lib/auth/trustedcluster_test.go index c65287c5064ff..aca02a7dc4943 100644 --- a/lib/auth/trustedcluster_test.go +++ b/lib/auth/trustedcluster_test.go @@ -453,17 +453,23 @@ func TestUpsertTrustedCluster(t *testing.T) { err = a.SetStaticTokens(tks) require.NoError(t, err) - trustedCluster, err := types.NewTrustedCluster("trustedcluster", - types.TrustedClusterSpecV2{ - Enabled: true, - RoleMap: []types.RoleMapping{ - { - Local: []string{"someRole"}, - Remote: "someRole", - }, + role, err := types.NewRole("test-role", types.RoleSpecV6{}) + require.NoError(t, err) + _, err = a.UpsertRole(ctx, role) + require.NoError(t, err) + + trustedClusterSpec := types.TrustedClusterSpecV2{ + Enabled: true, + RoleMap: []types.RoleMapping{ + { + Local: []string{"test-role"}, + Remote: "someRole", }, - ProxyAddress: "localhost", - }) + }, + ProxyAddress: "localhost", + } + + trustedCluster, err := types.NewTrustedCluster("trustedcluster", trustedClusterSpec) require.NoError(t, err) ca := suite.NewTestCA(types.UserCA, "trustedcluster") @@ -489,7 +495,7 @@ func TestUpsertTrustedCluster(t *testing.T) { ProxyAddress: "localhost", }) require.NoError(t, err) - _, err = a.UpsertTrustedCluster(ctx, trustedCluster) + _, err = a.UpsertTrustedClusterV2(ctx, trustedCluster) require.ErrorContains(t, err, "someNewRole") }) t.Run("Change role map of existing enabled trusted cluster", func(t *testing.T) { @@ -505,7 +511,7 @@ func TestUpsertTrustedCluster(t *testing.T) { ProxyAddress: "localhost", }) require.NoError(t, err) - _, err = a.UpsertTrustedCluster(ctx, trustedCluster) + _, err = a.UpsertTrustedClusterV2(ctx, trustedCluster) require.NoError(t, err) }) t.Run("Disable existing trusted cluster", func(t *testing.T) { @@ -521,7 +527,7 @@ func TestUpsertTrustedCluster(t *testing.T) { ProxyAddress: "localhost", }) require.NoError(t, err) - _, err = a.UpsertTrustedCluster(ctx, trustedCluster) + _, err = a.UpsertTrustedClusterV2(ctx, trustedCluster) require.NoError(t, err) }) t.Run("Change role map of existing disabled trusted cluster", func(t *testing.T) { @@ -537,7 +543,7 @@ func TestUpsertTrustedCluster(t *testing.T) { ProxyAddress: "localhost", }) require.NoError(t, err) - _, err = a.UpsertTrustedCluster(ctx, trustedCluster) + _, err = a.UpsertTrustedClusterV2(ctx, trustedCluster) require.NoError(t, err) }) t.Run("Enable existing trusted cluster", func(t *testing.T) { @@ -553,23 +559,177 @@ func TestUpsertTrustedCluster(t *testing.T) { ProxyAddress: "localhost", }) require.NoError(t, err) - _, err = a.UpsertTrustedCluster(ctx, trustedCluster) + _, err = a.UpsertTrustedClusterV2(ctx, trustedCluster) require.NoError(t, err) }) - t.Run("Cloud prohibits being a leaf cluster", func(t *testing.T) { - modules.SetTestModules(t, &modules.TestModules{ - TestFeatures: modules.Features{Cloud: true}, - }) + t.Run("Upsert unmodified trusted cluster", func(t *testing.T) { + trustedCluster, err := types.NewTrustedCluster("trustedcluster", trustedClusterSpec) + require.NoError(t, err) + _, err = a.UpsertTrustedClusterV2(ctx, trustedCluster) + require.NoError(t, err) + }) +} + +func TestUpdateTrustedCluster(t *testing.T) { + ctx := context.Background() + testAuth, err := NewTestAuthServer(TestAuthServerConfig{ + ClusterName: "localcluster", + Dir: t.TempDir(), + }) + require.NoError(t, err) + a := testAuth.AuthServer + + const validToken = "validtoken" + tks, err := types.NewStaticTokens(types.StaticTokensSpecV2{ + StaticTokens: []types.ProvisionTokenV1{{ + Roles: []types.SystemRole{types.RoleTrustedCluster}, + Token: validToken, + }}, + }) + require.NoError(t, err) + + err = a.SetStaticTokens(tks) + require.NoError(t, err) - tc, err := types.NewTrustedCluster("test", types.TrustedClusterSpecV2{ - RoleMap: []types.RoleMapping{ - {Remote: teleport.PresetAccessRoleName, Local: []string{teleport.PresetAccessRoleName}}, + role, err := types.NewRole("test-role", types.RoleSpecV6{}) + require.NoError(t, err) + _, err = a.UpsertRole(ctx, role) + require.NoError(t, err) + + trustedClusterSpec := types.TrustedClusterSpecV2{ + Enabled: true, + RoleMap: []types.RoleMapping{ + { + Local: []string{"test-role"}, + Remote: "someRole", }, - }) - require.NoError(t, err, "creating trusted cluster resource") + }, + ProxyAddress: "localhost", + } - server := ServerWithRoles{authServer: a} - _, err = server.UpsertTrustedCluster(ctx, tc) - require.True(t, trace.IsNotImplemented(err), "UpsertTrustedCluster returned an unexpected error, got = %v (%T), want trace.NotImplementedError", err, err) + testClusterName := "trustedcluster" + trustedCluster, err := types.NewTrustedCluster(testClusterName, trustedClusterSpec) + require.NoError(t, err) + + ca := suite.NewTestCA(types.UserCA, testClusterName) + + configureCAsForTrustedCluster(trustedCluster, []types.CertAuthority{ca}) + + _, err = a.Services.CreateTrustedCluster(ctx, trustedCluster, []types.CertAuthority{ca}) + require.NoError(t, err) + + err = a.createReverseTunnel(ctx, trustedCluster) + require.NoError(t, err) + + t.Run("Invalid role change", func(t *testing.T) { + existing, err := a.GetTrustedCluster(ctx, testClusterName) + require.NoError(t, err) + trustedCluster, err := types.NewTrustedCluster(testClusterName, + types.TrustedClusterSpecV2{ + Enabled: true, + RoleMap: []types.RoleMapping{ + { + Local: []string{"someNewRole"}, + Remote: "someRole", + }, + }, + ProxyAddress: "localhost", + }) + require.NoError(t, err) + trustedCluster.SetRevision(existing.GetRevision()) + _, err = a.UpdateTrustedCluster(ctx, trustedCluster) + require.ErrorContains(t, err, "someNewRole") + }) + t.Run("Change role map of existing enabled trusted cluster", func(t *testing.T) { + existing, err := a.GetTrustedCluster(ctx, testClusterName) + require.NoError(t, err) + trustedCluster, err := types.NewTrustedCluster(testClusterName, + types.TrustedClusterSpecV2{ + Enabled: true, + RoleMap: []types.RoleMapping{ + { + Local: []string{constants.DefaultImplicitRole}, + Remote: "someRole", + }, + }, + ProxyAddress: "localhost", + }) + require.NoError(t, err) + trustedCluster.SetRevision(existing.GetRevision()) + _, err = a.UpdateTrustedCluster(ctx, trustedCluster) + require.NoError(t, err) + }) + t.Run("Disable existing trusted cluster", func(t *testing.T) { + existing, err := a.GetTrustedCluster(ctx, testClusterName) + require.NoError(t, err) + trustedCluster, err := types.NewTrustedCluster(testClusterName, + types.TrustedClusterSpecV2{ + Enabled: false, + RoleMap: []types.RoleMapping{ + { + Local: []string{constants.DefaultImplicitRole}, + Remote: "someRole", + }, + }, + ProxyAddress: "localhost", + }) + require.NoError(t, err) + trustedCluster.SetRevision(existing.GetRevision()) + _, err = a.UpdateTrustedCluster(ctx, trustedCluster) + require.NoError(t, err) + }) + t.Run("Change role map of existing disabled trusted cluster", func(t *testing.T) { + existing, err := a.GetTrustedCluster(ctx, testClusterName) + require.NoError(t, err) + trustedCluster, err := types.NewTrustedCluster(testClusterName, + types.TrustedClusterSpecV2{ + Enabled: false, + RoleMap: []types.RoleMapping{ + { + Local: []string{constants.DefaultImplicitRole}, + Remote: "someOtherRole", + }, + }, + ProxyAddress: "localhost", + }) + require.NoError(t, err) + trustedCluster.SetRevision(existing.GetRevision()) + _, err = a.UpdateTrustedCluster(ctx, trustedCluster) + require.NoError(t, err) + }) + t.Run("Enable existing trusted cluster", func(t *testing.T) { + existing, err := a.GetTrustedCluster(ctx, testClusterName) + require.NoError(t, err) + trustedCluster, err := types.NewTrustedCluster(testClusterName, + types.TrustedClusterSpecV2{ + Enabled: true, + RoleMap: []types.RoleMapping{ + { + Local: []string{constants.DefaultImplicitRole}, + Remote: "someOtherRole", + }, + }, + ProxyAddress: "localhost", + }) + require.NoError(t, err) + trustedCluster.SetRevision(existing.GetRevision()) + _, err = a.UpdateTrustedCluster(ctx, trustedCluster) + require.NoError(t, err) + }) + t.Run("Update unmodified trusted cluster", func(t *testing.T) { + existing, err := a.GetTrustedCluster(ctx, testClusterName) + require.NoError(t, err) + trustedCluster, err := types.NewTrustedCluster(testClusterName, trustedClusterSpec) + require.NoError(t, err) + trustedCluster.SetRevision(existing.GetRevision()) + _, err = a.UpdateTrustedCluster(ctx, trustedCluster) + require.NoError(t, err) + }) + + t.Run("Invalid revision", func(t *testing.T) { + trustedCluster, err := types.NewTrustedCluster(testClusterName, trustedClusterSpec) + require.NoError(t, err) + _, err = a.UpdateTrustedCluster(ctx, trustedCluster) + require.Error(t, err) }) } diff --git a/lib/auth/userloginstate/generator.go b/lib/auth/userloginstate/generator.go index f9c72ac1a5f07..1eddc9c81ea53 100644 --- a/lib/auth/userloginstate/generator.go +++ b/lib/auth/userloginstate/generator.go @@ -21,10 +21,10 @@ package userloginstate import ( "context" "fmt" + "log/slog" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/client/proto" usageeventsv1 "github.com/gravitational/teleport/api/gen/proto/go/usageevents/v1" @@ -49,7 +49,7 @@ type AccessListsAndLockGetter interface { // GeneratorConfig is the configuration for the user login state generator. type GeneratorConfig struct { // Log is a logger to use for the generator. - Log *logrus.Entry + Log *slog.Logger // AccessLists is a service for retrieving access lists and locks from the backend. AccessLists AccessListsAndLockGetter @@ -107,7 +107,7 @@ func (g *GeneratorConfig) CheckAndSetDefaults() error { // Generator will generate a user login state from a user. type Generator struct { - log *logrus.Entry + log *slog.Logger accessLists AccessListsAndLockGetter access services.Access usageEvents UsageEventsClient @@ -191,7 +191,7 @@ func (g *Generator) Generate(ctx context.Context, user types.User, ulsService se if g.usageEvents != nil { // Emit the usage event metadata. if err := g.emitUsageEvent(ctx, user, uls, inheritedRoles, inheritedTraits); err != nil { - g.log.WithError(err).Debug("Error emitting usage event during user login state generation, skipping") + g.log.DebugContext(ctx, "Error emitting usage event during user login state generation, skipping", "error", err) } } @@ -245,7 +245,7 @@ func (g *Generator) handleAccessListMembership(ctx context.Context, user types.U if err != nil || membershipKind == accesslists.MembershipOrOwnershipTypeNone { // Log any error. if err != nil { - g.log.WithError(err).Warn("checking access list membership") + g.log.WarnContext(ctx, "checking access list membership", "error", err) } return inheritedRoles, inheritedTraits, nil } @@ -290,7 +290,7 @@ func (g *Generator) handleAccessListOwnership(ctx context.Context, user types.Us if err != nil || ownershipType == accesslists.MembershipOrOwnershipTypeNone { // Log any error. if err != nil { - g.log.WithError(err).Warn("checking access list ownership") + g.log.WarnContext(ctx, "checking access list ownership", "error", err) } return inheritedRoles, inheritedTraits, nil } @@ -497,6 +497,6 @@ func (g *Generator) emitSkippedAccessListEvent(ctx context.Context, accessListNa UserMessage: "access list skipped because it references non-existent role(s)", }, }); err != nil { - g.log.WithError(err).Warn("Failed to emit access list skipped warning audit event.") + g.log.WarnContext(ctx, "Failed to emit access list skipped warning audit event", "error", err) } } diff --git a/lib/auth/userloginstate/generator_test.go b/lib/auth/userloginstate/generator_test.go index 1154dec233de3..a263297c416c9 100644 --- a/lib/auth/userloginstate/generator_test.go +++ b/lib/auth/userloginstate/generator_test.go @@ -26,7 +26,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "github.com/gravitational/teleport/api/client/proto" @@ -42,6 +41,7 @@ import ( "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/services/local" + "github.com/gravitational/teleport/lib/utils" ) const ownerUser = "owner" @@ -729,7 +729,6 @@ func initGeneratorSvc(t *testing.T) (*Generator, *svc) { ulsService, err := local.NewUserLoginStateService(mem) require.NoError(t, err) - log := logrus.WithField("test", "logger") svc := &svc{ AccessLists: accessListsSvc, Access: accessSvc, @@ -739,7 +738,7 @@ func initGeneratorSvc(t *testing.T) (*Generator, *svc) { emitter := &eventstest.MockRecorderEmitter{} generator, err := NewGenerator(GeneratorConfig{ - Log: log, + Log: utils.NewSlogLoggerForTests(), AccessLists: svc, Access: svc, UsageEvents: svc, diff --git a/lib/auth/userloginstate/userloginstatev1/service.go b/lib/auth/userloginstate/userloginstatev1/service.go index 1b4bbb43cc2a1..fc51ac2b299c6 100644 --- a/lib/auth/userloginstate/userloginstatev1/service.go +++ b/lib/auth/userloginstate/userloginstatev1/service.go @@ -23,10 +23,8 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "google.golang.org/protobuf/types/known/emptypb" - "github.com/gravitational/teleport" userloginstatev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/userloginstate/v1" "github.com/gravitational/teleport/api/types" conv "github.com/gravitational/teleport/api/types/userloginstate/convert/v1" @@ -36,8 +34,6 @@ import ( // ServiceConfig is the service config for the Access Lists gRPC service. type ServiceConfig struct { - // Logger is the logger to use. - Logger logrus.FieldLogger // Authorizer is the authorizer to use. Authorizer authz.Authorizer @@ -58,10 +54,6 @@ func (c *ServiceConfig) checkAndSetDefaults() error { return trace.BadParameter("user login states service is missing") } - if c.Logger == nil { - c.Logger = logrus.WithField(teleport.ComponentKey, "user_login_state_crud_service") - } - if c.Clock == nil { c.Clock = clockwork.NewRealClock() } @@ -72,7 +64,6 @@ func (c *ServiceConfig) checkAndSetDefaults() error { type Service struct { userloginstatev1.UnimplementedUserLoginStateServiceServer - log logrus.FieldLogger authorizer authz.Authorizer userLoginStates services.UserLoginStates clock clockwork.Clock @@ -85,7 +76,6 @@ func NewService(cfg ServiceConfig) (*Service, error) { } return &Service{ - log: cfg.Logger, authorizer: cfg.Authorizer, userLoginStates: cfg.UserLoginStates, clock: cfg.Clock, diff --git a/lib/auth/userpreferences/userpreferencesv1/service.go b/lib/auth/userpreferences/userpreferencesv1/service.go index 0a0b8605c5f11..2bf7b1b339979 100644 --- a/lib/auth/userpreferences/userpreferencesv1/service.go +++ b/lib/auth/userpreferences/userpreferencesv1/service.go @@ -22,10 +22,8 @@ import ( "context" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "google.golang.org/protobuf/types/known/emptypb" - "github.com/gravitational/teleport" userpreferences "github.com/gravitational/teleport/api/gen/proto/go/userpreferences/v1" "github.com/gravitational/teleport/lib/authz" "github.com/gravitational/teleport/lib/services" @@ -35,7 +33,6 @@ import ( type ServiceConfig struct { Backend services.UserPreferences Authorizer authz.Authorizer - Logger *logrus.Entry } // Service implements the teleport.userpreferences.v1.UserPreferencesService RPC service. @@ -44,7 +41,6 @@ type Service struct { backend services.UserPreferences authorizer authz.Authorizer - log *logrus.Entry } // NewService returns a new user preferences gRPC service. @@ -54,14 +50,11 @@ func NewService(cfg *ServiceConfig) (*Service, error) { return nil, trace.BadParameter("backend is required") case cfg.Authorizer == nil: return nil, trace.BadParameter("authorizer is required") - case cfg.Logger == nil: - cfg.Logger = logrus.WithField(teleport.ComponentKey, "userpreferences.service") } return &Service{ backend: cfg.Backend, authorizer: cfg.Authorizer, - log: cfg.Logger, }, nil } diff --git a/lib/auth/users/usersv1/service.go b/lib/auth/users/usersv1/service.go index 217a258002392..3a7a3bd630e27 100644 --- a/lib/auth/users/usersv1/service.go +++ b/lib/auth/users/usersv1/service.go @@ -20,10 +20,10 @@ package usersv1 import ( "context" + "log/slog" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "google.golang.org/protobuf/types/known/emptypb" "github.com/gravitational/teleport" @@ -37,6 +37,7 @@ import ( "github.com/gravitational/teleport/lib/events" "github.com/gravitational/teleport/lib/services" usagereporter "github.com/gravitational/teleport/lib/usagereporter/teleport" + logutils "github.com/gravitational/teleport/lib/utils/log" ) // Cache is the subset of the cached resources that the Service queries. @@ -69,7 +70,7 @@ type ServiceConfig struct { Authorizer authz.Authorizer Cache Cache Backend Backend - Logger logrus.FieldLogger + Logger *slog.Logger Emitter apievents.Emitter Reporter usagereporter.UsageReporter Clock clockwork.Clock @@ -82,7 +83,7 @@ type Service struct { authorizer authz.Authorizer cache Cache backend Backend - logger logrus.FieldLogger + logger *slog.Logger emitter apievents.Emitter reporter usagereporter.UsageReporter clock clockwork.Clock @@ -104,7 +105,7 @@ func NewService(cfg ServiceConfig) (*Service, error) { } if cfg.Logger == nil { - cfg.Logger = logrus.WithField(teleport.ComponentKey, "users.service") + cfg.Logger = slog.With(teleport.ComponentKey, "users.service") } if cfg.Clock == nil { cfg.Clock = clockwork.NewRealClock() @@ -171,7 +172,7 @@ func (s *Service) GetUser(ctx context.Context, req *userspb.GetUserRequest) (*us // migrated to that model. if !authz.HasBuiltinRole(*authCtx, string(types.RoleAdmin)) { err := trace.AccessDenied("user %q requested access to user %q with secrets", authCtx.User.GetName(), req.Name) - s.logger.Warn(err) + s.logger.WarnContext(ctx, "use does not have permission to read user with sercrets", "error", err) if err := s.emitter.EmitAuditEvent(ctx, &apievents.UserLogin{ Metadata: apievents.Metadata{ Type: events.UserLoginEvent, @@ -184,7 +185,7 @@ func (s *Service) GetUser(ctx context.Context, req *userspb.GetUserRequest) (*us UserMessage: err.Error(), }, }); err != nil { - s.logger.WithError(err).Warn("Failed to emit local login failure event.") + s.logger.WarnContext(ctx, "Failed to emit local login failure event", "error", err) } return nil, trace.AccessDenied("this request can be only executed by an admin") } @@ -206,7 +207,11 @@ func (s *Service) GetUser(ctx context.Context, req *userspb.GetUserRequest) (*us v2, ok := user.(*types.UserV2) if !ok { - s.logger.Warnf("expected type UserV2, got %T for user %q", user, user.GetName()) + s.logger.WarnContext(ctx, "unexpected user type", + "got_type", logutils.TypeAttr(user), + "expected_type", "UserV2", + "user", user.GetName(), + ) return nil, trace.BadParameter("encountered unexpected user type") } @@ -271,14 +276,18 @@ func (s *Service) CreateUser(ctx context.Context, req *userspb.CreateUserRequest Roles: created.GetRoles(), ConnectionMetadata: authz.ConnectionMetadata(ctx), }); err != nil { - s.logger.WithError(err).Warn("Failed to emit user create event.") + s.logger.WarnContext(ctx, "Failed to emit user create event", "error", err) } usagereporter.EmitEditorChangeEvent(created.GetName(), nil, created.GetRoles(), s.reporter.AnonymizeAndSubmit) v2, ok := created.(*types.UserV2) if !ok { - s.logger.Warnf("expected type UserV2, got %T for user %q", created, created.GetName()) + s.logger.WarnContext(ctx, "unexpected user type", + "got_type", logutils.TypeAttr(created), + "expected_type", "UserV2", + "user", created.GetName(), + ) return nil, trace.BadParameter("encountered unexpected user type") } @@ -319,7 +328,7 @@ func (s *Service) UpdateUser(ctx context.Context, req *userspb.UpdateUserRequest var omitEditorEvent bool if err != nil { // don't return error here since this call is for event emitting purposes only - s.logger.WithError(err).Warn("Failed getting previous user during update") + s.logger.WarnContext(ctx, "Failed getting previous user during update", "error", err) omitEditorEvent = true } @@ -356,7 +365,7 @@ func (s *Service) UpdateUser(ctx context.Context, req *userspb.UpdateUserRequest Roles: updated.GetRoles(), ConnectionMetadata: authz.ConnectionMetadata(ctx), }); err != nil { - s.logger.WithError(err).Warn("Failed to emit user update event.") + s.logger.WarnContext(ctx, "Failed to emit user update event", "error", err) } if !omitEditorEvent { @@ -365,7 +374,11 @@ func (s *Service) UpdateUser(ctx context.Context, req *userspb.UpdateUserRequest v2, ok := updated.(*types.UserV2) if !ok { - s.logger.Warnf("expected type UserV2, got %T for user %q", updated, updated.GetName()) + s.logger.WarnContext(ctx, "unexpected user type", + "got_type", logutils.TypeAttr(updated), + "expected_type", "UserV2", + "user", updated.GetName(), + ) return nil, trace.BadParameter("encountered unexpected user type") } @@ -405,7 +418,7 @@ func (s *Service) UpsertUser(ctx context.Context, req *userspb.UpsertUserRequest var omitEditorEvent bool if err != nil { // don't return error here since this call is for event emitting purposes only - s.logger.WithError(err).Warn("Failed getting previous user during update") + s.logger.WarnContext(ctx, "Failed getting previous user during update", "error", err) omitEditorEvent = true } @@ -446,7 +459,7 @@ func (s *Service) UpsertUser(ctx context.Context, req *userspb.UpsertUserRequest Roles: upserted.GetRoles(), ConnectionMetadata: authz.ConnectionMetadata(ctx), }); err != nil { - s.logger.WithError(err).Warn("Failed to emit user upsert event.") + s.logger.WarnContext(ctx, "Failed to emit user upsert event", "error", err) } if !omitEditorEvent { @@ -455,7 +468,11 @@ func (s *Service) UpsertUser(ctx context.Context, req *userspb.UpsertUserRequest v2, ok := upserted.(*types.UserV2) if !ok { - s.logger.Warnf("expected type UserV2, got %T for user %q", upserted, upserted.GetName()) + s.logger.WarnContext(ctx, "unexpected user type", + "got_type", logutils.TypeAttr(upserted), + "expected_type", "UserV2", + "user", upserted.GetName(), + ) return nil, trace.BadParameter("encountered unexpected user type") } @@ -480,7 +497,7 @@ func (s *Service) DeleteUser(ctx context.Context, req *userspb.DeleteUserRequest var omitEditorEvent bool if err != nil && !trace.IsNotFound(err) { // don't return error here, delete may still succeed - s.logger.WithError(err).Warn("Failed getting previous user during delete operation") + s.logger.WarnContext(ctx, "Failed getting previous user during delete operation", "error", err) prevUser = nil omitEditorEvent = true } @@ -518,7 +535,7 @@ func (s *Service) DeleteUser(ctx context.Context, req *userspb.DeleteUserRequest }, ConnectionMetadata: authz.ConnectionMetadata(ctx), }); err != nil { - s.logger.WithError(err).Warn("Failed to emit user delete event.") + s.logger.WarnContext(ctx, "Failed to emit user delete event", "error", err) } if !omitEditorEvent { @@ -539,7 +556,7 @@ func (s *Service) ListUsers(ctx context.Context, req *userspb.ListUsersRequest) // migrated to that model. if !authz.HasBuiltinRole(*authCtx, string(types.RoleAdmin)) { err := trace.AccessDenied("user %q requested access to all users with secrets", authCtx.User.GetName()) - s.logger.Warn(err) + s.logger.WarnContext(ctx, "use does not have permission to read all users with sercrets", "error", err) if err := s.emitter.EmitAuditEvent(ctx, &apievents.UserLogin{ Metadata: apievents.Metadata{ Type: events.UserLoginEvent, @@ -552,7 +569,7 @@ func (s *Service) ListUsers(ctx context.Context, req *userspb.ListUsersRequest) UserMessage: err.Error(), }, }); err != nil { - s.logger.WithError(err).Warn("Failed to emit local login failure event.") + s.logger.WarnContext(ctx, "Failed to emit local login failure event", "error", err) } return nil, trace.AccessDenied("this request can be only executed by an admin") } diff --git a/lib/auth/webauthn/attestation.go b/lib/auth/webauthn/attestation.go index a382229dce048..fa14c23a21ed1 100644 --- a/lib/auth/webauthn/attestation.go +++ b/lib/auth/webauthn/attestation.go @@ -19,17 +19,22 @@ package webauthn import ( + "context" "crypto/x509" "encoding/pem" + "log/slog" "slices" "github.com/go-webauthn/webauthn/protocol" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" + "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" + logutils "github.com/gravitational/teleport/lib/utils/log" ) +var log = logutils.NewPackageLogger(teleport.ComponentKey, "WebAuthn") + // x5cFormats enumerates all attestation formats that supply an attestation // chain through the "x5c" field. // See https://www.w3.org/TR/webauthn/#sctn-defined-attestation-formats. @@ -138,13 +143,17 @@ func getChainFromX5C(obj protocol.AttestationObject) ([]*x509.Certificate, error // Print out attestation certs if debug is enabled. // This may come in handy for people having trouble with their setups. - if log.IsLevelEnabled(log.DebugLevel) { + ctx := context.Background() + if log.Handler().Enabled(ctx, slog.LevelDebug) { for _, cert := range chain { certPEM := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: cert.Raw, }) - log.Debugf("WebAuthn: got %q attestation certificate:\n\n%s", obj.Format, certPEM) + log.DebugContext(context.Background(), "got attestation certificate", + "format", obj.Format, + "certificate", string(certPEM), + ) } } diff --git a/lib/auth/webauthn/device.go b/lib/auth/webauthn/device.go index a78ec9f7fea4f..700f671b4d264 100644 --- a/lib/auth/webauthn/device.go +++ b/lib/auth/webauthn/device.go @@ -19,6 +19,7 @@ package webauthn import ( + "context" "crypto/ecdsa" "crypto/x509" @@ -26,7 +27,6 @@ import ( "github.com/go-webauthn/webauthn/protocol/webauthncose" wan "github.com/go-webauthn/webauthn/webauthn" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" ) @@ -51,7 +51,7 @@ func deviceToCredential( var err error pubKeyCBOR, err = u2fDERKeyToCBOR(dev.U2F.PubKey) if err != nil { - log.Warnf("WebAuthn: failed to convert U2F device key to CBOR: %v", err) + log.WarnContext(context.Background(), "failed to convert U2F device key to CBOR", "error", err) return wan.Credential{}, false } } diff --git a/lib/auth/webauthn/login.go b/lib/auth/webauthn/login.go index 2ed9f085155a6..9948602beeba6 100644 --- a/lib/auth/webauthn/login.go +++ b/lib/auth/webauthn/login.go @@ -31,7 +31,6 @@ import ( wan "github.com/go-webauthn/webauthn/webauthn" gogotypes "github.com/gogo/protobuf/types" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" mfav1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v1" "github.com/gravitational/teleport/api/types" @@ -107,13 +106,14 @@ func (f *loginFlow) begin(ctx context.Context, user string, challengeExtensions continue } - log.Errorf(""+ - "User device %q/%q has unexpected RPID=%q, excluding from allowed credentials. "+ - "RPID changes are not supported by WebAuthn, this is likely to cause permanent authentication problems for your users. "+ - "Consider reverting the change or reset your users so they may register their devices again.", - user, - devices[i].GetName(), - webDev.CredentialRpId) + const msg = "User device has unexpected RPID, excluding from allowed credentials. " + + "RPID changes are not supported by WebAuthn, this is likely to cause permanent authentication problems for your users. " + + "Consider reverting the change or reset your users so they may register their devices again." + log.ErrorContext(ctx, msg, + "user", user, + "device", devices[i].GetName(), + "rpid", webDev.CredentialRpId, + ) // "Cut" device from slice. devices = slices.Delete(devices, i, i+1) @@ -249,7 +249,7 @@ func (f *loginFlow) finish(ctx context.Context, user string, resp *wantypes.Cred origin := parsedResp.Response.CollectedClientData.Origin if err := validateOrigin(origin, f.Webauthn.RPID); err != nil { - log.WithError(err).Debugf("WebAuthn: origin validation failed") + log.DebugContext(ctx, "origin validation failed", "error", err) return nil, trace.Wrap(err) } @@ -330,9 +330,9 @@ func (f *loginFlow) finish(ctx context.Context, user string, resp *wantypes.Cred if (discoverableLogin || uvr == protocol.VerificationRequired) && sd.UserVerification != string(protocol.VerificationRequired) { // This is not a failure yet, but will likely become one. sd.UserVerification = string(protocol.VerificationRequired) - log.Warnf(""+ - "WebAuthn: User verification required by extensions but not by challenge. "+ - "Increased SessionData.UserVerification to %s.", sd.UserVerification) + const msg = "User verification required by extensions but not by challenge. " + + "Increased SessionData.UserVerification." + log.WarnContext(ctx, msg, "user_verification", sd.UserVerification) } sessionData := wantypes.SessionDataToProtocol(sd) @@ -371,8 +371,10 @@ func (f *loginFlow) finish(ctx context.Context, user string, resp *wantypes.Cred return nil, trace.Wrap(err) } if credential.Authenticator.CloneWarning { - log.Warnf( - "WebAuthn: Clone warning detected for user %q / device %q. Device counter may be malfunctioning.", user, dev.GetName()) + log.WarnContext(ctx, "Clone warning detected for device, the device counter may be malfunctioning", + "user", user, + "device", dev.GetName(), + ) } // Update last used timestamp and device counter. @@ -381,7 +383,11 @@ func (f *loginFlow) finish(ctx context.Context, user string, resp *wantypes.Cred } // Retroactively write the credential RPID, now that it cleared authn. if webDev := dev.GetWebauthn(); webDev != nil && webDev.CredentialRpId == "" { - log.Debugf("WebAuthn: Recording RPID=%q in device %q/%q", rpID, user, dev.GetName()) + log.DebugContext(ctx, "Recording RPID in device", + "rpid", rpID, + "user", user, + "device", dev.GetName(), + ) webDev.CredentialRpId = rpID } @@ -395,7 +401,10 @@ func (f *loginFlow) finish(ctx context.Context, user string, resp *wantypes.Cred // passes. if sd.ChallengeExtensions.AllowReuse != mfav1.ChallengeAllowReuse_CHALLENGE_ALLOW_REUSE_YES { if err := f.sessionData.Delete(ctx, user, challenge); err != nil { - log.Warnf("WebAuthn: failed to delete login SessionData for user %v (scope = %s)", user, sd.ChallengeExtensions.Scope) + log.WarnContext(ctx, "failed to delete login SessionData for user", + "user", user, + "scope", sd.ChallengeExtensions.Scope, + ) } } @@ -463,19 +472,19 @@ func updateCredentialAndTimestamps( d.Webauthn.CredentialBackupEligible = &gogotypes.BoolValue{ Value: credential.Flags.BackupEligible, } - log.WithFields(log.Fields{ - "device": dest.GetName(), - "be": credential.Flags.BackupEligible, - }).Debug("Backfilled Webauthn device BE flag") + log.DebugContext(context.Background(), "Backfilled Webauthn device BE flag", + "device", dest.GetName(), + "be", credential.Flags.BackupEligible, + ) } if d.Webauthn.CredentialBackedUp == nil { d.Webauthn.CredentialBackedUp = &gogotypes.BoolValue{ Value: credential.Flags.BackupState, } - log.WithFields(log.Fields{ - "device": dest.GetName(), - "bs": credential.Flags.BackupState, - }).Debug("Backfilled Webauthn device BS flag") + log.DebugContext(context.Background(), "Backfilled Webauthn device BS flag", + "device", dest.GetName(), + "bs", credential.Flags.BackupState, + ) } default: diff --git a/lib/auth/webauthn/register.go b/lib/auth/webauthn/register.go index 50eb3420391eb..9d6a0726af232 100644 --- a/lib/auth/webauthn/register.go +++ b/lib/auth/webauthn/register.go @@ -32,7 +32,6 @@ import ( gogotypes "github.com/gogo/protobuf/types" "github.com/google/uuid" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes" @@ -284,7 +283,7 @@ func (f *RegistrationFlow) Finish(ctx context.Context, req RegisterResponse) (*t origin := parsedResp.Response.CollectedClientData.Origin if err := validateOrigin(origin, f.Webauthn.RPID); err != nil { - log.WithError(err).Debugf("WebAuthn: origin validation failed") + log.DebugContext(ctx, "origin validation failed", "error", err) return nil, trace.Wrap(err) } @@ -332,7 +331,7 @@ func (f *RegistrationFlow) Finish(ctx context.Context, req RegisterResponse) (*t protocolErr.Type == protocol.ErrVerification.Type && passwordless && !parsedResp.Response.AttestationObject.AuthData.Flags.UserVerified() { - log.WithError(err).Debug("WebAuthn: Replacing verification error with PIN message") + log.DebugContext(ctx, "WebAuthn: Replacing verification error with PIN message", "error", err) return nil, trace.BadParameter("authenticator doesn't support passwordless, setting up a PIN may fix this") } @@ -378,7 +377,7 @@ func (f *RegistrationFlow) Finish(ctx context.Context, req RegisterResponse) (*t // Registration complete, remove the registration challenge we just used. if err := f.Identity.DeleteWebauthnSessionData(ctx, req.User, scopeSession); err != nil { - log.Warnf("WebAuthn: failed to delete registration SessionData for user %v", req.User) + log.WarnContext(ctx, "failed to delete registration SessionData for user", "user", req.User, "error", err) } return newDevice, nil diff --git a/lib/auth/webauthncli/api.go b/lib/auth/webauthncli/api.go index 8278c77a03634..5ee0f9e4f7d3c 100644 --- a/lib/auth/webauthncli/api.go +++ b/lib/auth/webauthncli/api.go @@ -24,14 +24,20 @@ import ( "strings" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" oteltrace "go.opentelemetry.io/otel/trace" + "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/observability/tracing" "github.com/gravitational/teleport/lib/auth/touchid" wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes" wanwin "github.com/gravitational/teleport/lib/auth/webauthnwin" + logutils "github.com/gravitational/teleport/lib/utils/log" +) + +var ( + log = logutils.NewPackageLogger(teleport.ComponentKey, "WebAuthn") + fidoLog = logutils.NewPackageLogger(teleport.ComponentKey, "FIDO2") ) // ErrUsingNonRegisteredDevice is returned from Login when the user attempts to @@ -137,10 +143,10 @@ func Login( switch { case origin == "", assertion == nil: // let downstream handle empty/nil case !strings.HasPrefix(origin, "https://"+assertion.Response.RelyingPartyID): - log.Warnf(""+ - "WebAuthn: origin and RPID mismatch, "+ - "if you are having authentication problems double check your proxy address "+ - "(%q vs %q)", origin, assertion.Response.RelyingPartyID) + log.WarnContext(ctx, "origin and RPID mismatch, if you are having authentication problems double check your proxy address", + "origin", origin, + "rpid", assertion.Response.RelyingPartyID, + ) } var attachment AuthenticatorAttachment @@ -151,7 +157,7 @@ func Login( } if wanwin.IsAvailable() { - log.Debug("WebAuthnWin: Using windows webauthn for credential assertion") + log.DebugContext(ctx, "Using windows webauthn for credential assertion") return wanwin.Login(ctx, origin, assertion, &wanwin.LoginOpts{ AuthenticatorAttachment: wanwin.AuthenticatorAttachment(attachment), }) @@ -159,19 +165,19 @@ func Login( switch attachment { case AttachmentCrossPlatform: - log.Debug("Cross-platform login") + log.DebugContext(ctx, "Cross-platform login") return crossPlatformLogin(ctx, origin, assertion, prompt, opts) case AttachmentPlatform: - log.Debug("Platform login") + log.DebugContext(ctx, "Platform login") return platformLogin(origin, user, assertion, prompt) default: - log.Debug("Attempting platform login") + log.DebugContext(ctx, "Attempting platform login") resp, credentialUser, err := platformLogin(origin, user, assertion, prompt) if !errors.Is(err, &touchid.ErrAttemptFailed{}) { return resp, credentialUser, trace.Wrap(err) } - log.WithError(err).Debug("Platform login failed, falling back to cross-platform") + log.DebugContext(ctx, "Platform login failed, falling back to cross-platform", "error", err) return crossPlatformLogin(ctx, origin, assertion, prompt, opts) } } @@ -180,7 +186,7 @@ func crossPlatformLogin( ctx context.Context, origin string, assertion *wantypes.CredentialAssertion, prompt LoginPrompt, opts *LoginOpts, ) (*proto.MFAAuthenticateResponse, string, error) { - log.Debug("FIDO2: Using libfido2 for assertion") + fidoLog.DebugContext(ctx, "Using libfido2 for assertion") resp, user, err := FIDO2Login(ctx, origin, assertion, prompt, opts) return resp, user, trace.Wrap(err) } @@ -223,11 +229,11 @@ func Register( ctx context.Context, origin string, cc *wantypes.CredentialCreation, prompt RegisterPrompt) (*proto.MFARegisterResponse, error) { if wanwin.IsAvailable() { - log.Debug("WebAuthnWin: Using windows webauthn for credential creation") + log.DebugContext(ctx, "Using windows webauthn for credential creation") return wanwin.Register(ctx, origin, cc) } - log.Debug("FIDO2: Using libfido2 for credential creation") + fidoLog.DebugContext(ctx, "Using libfido2 for credential creation") resp, err := FIDO2Register(ctx, origin, cc, prompt) return resp, trace.Wrap(err) } diff --git a/lib/auth/webauthncli/fido2.go b/lib/auth/webauthncli/fido2.go index a20b714fc2fcb..33a033a26b383 100644 --- a/lib/auth/webauthncli/fido2.go +++ b/lib/auth/webauthncli/fido2.go @@ -29,6 +29,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "sync" "time" @@ -37,7 +38,6 @@ import ( "github.com/go-webauthn/webauthn/protocol/webauthncose" "github.com/gravitational/trace" "github.com/keys-pub/go-libfido2" - log "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/client/proto" wanpb "github.com/gravitational/teleport/api/types/webauthn" @@ -165,7 +165,11 @@ func fido2Login( // Presence of any allowed credential is interpreted as the user identity // being partially established, aka non-passwordless. passwordless := len(allowedCreds) == 0 - log.Debugf("FIDO2: assertion: passwordless=%v, uv=%v, %v allowed credentials", passwordless, uv, len(allowedCreds)) + fidoLog.DebugContext(ctx, "assertion", + "passwordless", passwordless, + "uv", uv, + "allowed_cred_count", len(allowedCreds), + ) // Prepare challenge data for the device. ccdJSON, err := json.Marshal(&CollectedClientData{ @@ -209,7 +213,11 @@ func fido2Login( deviceCallback := func(dev FIDODevice, info *deviceInfo, pin string) error { actualRPID := rpID if usesAppID(dev, info, ccdHash[:], allowedCreds, rpID, appID) { - log.Debugf("FIDO2: Device %v registered for AppID (%q) instead of RPID", info.path, appID) + fidoLog.DebugContext(ctx, "Device registered for AppID instead of RPID", + "device", info.path, + "app_id", appID, + "rp_id", rpID, + ) actualRPID = appID } @@ -227,7 +235,7 @@ func fido2Login( // Happens inconsistently in some authenticator series (YubiKey 5). // We are relying on the fact that, because the PIN is set, the // authenticator will set the UV bit regardless of it being requested. - log.Debugf("FIDO2: Device %v: retrying assertion without UV", info.path) + fidoLog.DebugContext(ctx, "Retrying assertion without UV", "device", info.path) opts.UV = libfido2.Default assertions, err = devAssertion(dev, info, actualRPID, ccdHash[:], allowedCreds, pin, opts) } @@ -239,7 +247,7 @@ func fido2Login( // touch - this causes another slew of problems with abandoned U2F // goroutines during registration. if !info.fido2 { - log.Debugf("FIDO2: U2F device %v not registered, ignoring it", info.path) + fidoLog.DebugContext(ctx, "U2F device not registered, ignoring it", "device", info.path) err = &nonInteractiveError{err: err} } else { err = ErrUsingNonRegisteredDevice // "Upgrade" error message. @@ -248,7 +256,7 @@ func fido2Login( if err != nil { return trace.Wrap(err) } - log.Debugf("FIDO2: Got %v assertions", len(assertions)) + fidoLog.DebugContext(ctx, "Got assertions", "assertion_count", len(assertions)) // Find assertion for target user, or show the prompt. assertion, err := pickAssertion(assertions, prompt, user, passwordless) @@ -256,9 +264,11 @@ func fido2Login( return trace.Wrap(err) } - log.Debugf( - "FIDO2: Authenticated: credential ID (b64) = %v, user ID (hex) = %x, user name = %q", - base64.RawURLEncoding.EncodeToString(assertion.CredentialID), assertion.User.ID, assertion.User.Name) + fidoLog.DebugContext(ctx, "Authenticated", + "credential_id", base64.RawURLEncoding.EncodeToString(assertion.CredentialID), + "user_id", assertion.User.ID, + "user_name", assertion.User.Name, + ) // Use the first successful assertion. // In practice it is very unlikely we'd hit this twice. @@ -342,13 +352,16 @@ func devAssertion( return nil, trace.Wrap(libfido2.ErrUserPresenceRequired) } - if log.IsLevelEnabled(log.DebugLevel) { + if fidoLog.Enabled(context.Background(), slog.LevelDebug) { credPrefix := hex.EncodeToString(cred) const prefixLen = 10 if len(credPrefix) > prefixLen { credPrefix = credPrefix[:prefixLen] } - log.Debugf("FIDO2: Device %v: Using credential %v...", info.path, credPrefix) + fidoLog.DebugContext(context.Background(), "Using credential", + "device", info.path, + "credential", credPrefix, + ) } allowedCreds = [][]byte{cred} @@ -451,7 +464,7 @@ func fido2Register( if err != nil { return nil, trace.Wrap(err) } - log.Debugf("FIDO2: registration: resident key=%v", rrk) + fidoLog.DebugContext(ctx, "registration", "resident_key", rrk) // Can we create ES256 keys? // TODO(codingllama): Consider supporting other algorithms and respecting @@ -526,12 +539,13 @@ func fido2Register( case err != nil: // Swallow unexpected errors: a double registration is better than // aborting the ceremony. - log.Debugf( - "FIDO2: Device %v: excluded credential assertion failed, letting device through: err=%q", - info.path, err) + fidoLog.DebugContext(ctx, "excluded credential assertion failed, letting device through", + "device", info.path, + "error", err, + ) return nil default: - log.Debugf("FIDO2: Device %v: filtered due to presence of excluded credential", info.path) + fidoLog.DebugContext(ctx, "filtered due to presence of excluded credential", "device", info.path) return errHasExcludedCredential } } @@ -619,7 +633,7 @@ func makeAttStatement(attestation *libfido2.Attestation) (string, map[string]int case none: return format, nil, nil default: - log.Debugf(`FIDO2: Unsupported attestation format %q, using "none"`, format) + fidoLog.DebugContext(context.Background(), `Unsupported attestation format, using "none"`, "attestation_format", format) return none, nil, nil } @@ -686,11 +700,11 @@ func runOnFIDO2Devices( case <-devicesC: receiveCount++ case <-maxWait.C: - log.Debugf("FIDO2: Abandoning device goroutines after %s", fido2DeviceMaxWait) + fidoLog.DebugContext(ctx, "Abandoning device goroutines after exceeding wait time", "max_wait_time", fido2DeviceMaxWait) return } } - log.Debug("FIDO2: Device goroutines exited cleanly") + fidoLog.DebugContext(ctx, "Device goroutines exited cleanly") }() // First "interactive" response wins. @@ -701,7 +715,7 @@ func runOnFIDO2Devices( // Keep going on cancels or non-interactive errors. if errors.Is(err, libfido2.ErrKeepaliveCancel) || errors.Is(err, &nonInteractiveError{}) { - log.Debugf("FIDO2: Got cancel or non-interactive device error: %v", err) + fidoLog.DebugContext(ctx, "Got cancel or non-interactive device error", "error", err) continue } @@ -729,7 +743,10 @@ func startDevices( for i, dev := range fidoDevs { path := openDevs[i].path err := dev.Close() - log.Debugf("FIDO2: Close device %v, err=%v", path, err) + fidoLog.DebugContext(context.Background(), "Close device", + "device", path, + "error", err, + ) } } @@ -747,7 +764,10 @@ func startDevices( // This is largely safe to ignore, as opening is fairly consistent in // other situations and failures are likely from a non-chosen device in // multi-device scenarios. - log.Debugf("FIDO2: Device %v failed to open, skipping: %v", path, err) + fidoLog.DebugContext(context.Background(), "Device failed to open, skipping", + "device", path, + "error", err, + ) continue } @@ -828,7 +848,10 @@ func (l *openedDevices) cancelAll(except FIDODevice) { // Note that U2F devices fail Cancel with "invalid argument". err := d.dev.Cancel() - log.Debugf("FIDO2: Cancel device %v, err=%v", d.path, err) + fidoLog.DebugContext(context.Background(), "Cancel device", + "device", d.path, + "error", err, + ) } } @@ -841,10 +864,14 @@ func handleDevice( firstTouchAck func() error, pinPrompt runPrompt, ) error { + ctx := context.Background() // handleDevice owns the device, thus it has the privilege to shut it down. defer func() { err := dev.Close() - log.Debugf("FIDO2: Close device %v, err=%v", path, err) + fidoLog.DebugContext(ctx, "Close device", + "device", path, + "error", err, + ) }() if err := dev.SetTimeout(fido2DeviceTimeout); err != nil { @@ -862,16 +889,22 @@ func handleDevice( if err != nil { return trace.Wrap(&nonInteractiveError{err: err}) } - log.Debugf("FIDO2: Device %v: info %#v", path, info) + fidoLog.DebugContext(ctx, "Device", + "path", path, + "info", info, + ) } else { - log.Debugf("FIDO2: Device %v: not a FIDO2 device", path) + fidoLog.DebugContext(ctx, "not a FIDO2 device", "device", path) } di := makeDevInfo(path, info, isFIDO2) // Apply initial filters, waiting for confirmation if the filter fails before // relaying the error. if err := filter(dev, di); err != nil { - log.Debugf("FIDO2: Device %v filtered, err=%v", path, err) + fidoLog.DebugContext(ctx, "Device filtered", + "device", path, + "error", err, + ) // If the device is chosen then treat the error as interactive. if touched, _ := waitForTouch(dev); touched { @@ -885,7 +918,11 @@ func handleDevice( // Run the callback. cb := withPINHandler(withRetries(deviceCallback)) requiresPIN, err := cb(dev, di, "" /* pin */) - log.Debugf("FIDO2: Device %v: callback returned, requiresPIN=%v, err=%v", path, requiresPIN, err) + fidoLog.DebugContext(ctx, "callback returned", + "device", path, + "requires_pin", requiresPIN, + "error", err, + ) if err != nil { return trace.Wrap(err) } @@ -932,7 +969,11 @@ func devInfo(path string, dev FIDODevice) (*libfido2.DeviceInfo, error) { } lastErr = err - log.Debugf("FIDO2: Device %v: Info failed, retrying after %s: %v", path, fido2RetryInterval, err) + fidoLog.DebugContext(context.Background(), "Info failed, retrying", + "device", path, + "backoff_duration", fido2RetryInterval, + "error", err, + ) time.Sleep(fido2RetryInterval) } @@ -955,7 +996,7 @@ func withRetries(callback deviceCallbackFunc) deviceCallbackFunc { // ErrOperationDenied happens when fingerprint reading fails (UV=false). if errors.Is(err, libfido2.ErrOperationDenied) { fmt.Println("Gesture validation failed, make sure you use a registered fingerprint") - log.Debug("FIDO2: Retrying libfido2 error 'operation denied'") + fidoLog.DebugContext(context.Background(), "Retrying libfido2 error 'operation denied'") continue } @@ -975,7 +1016,7 @@ func withRetries(callback deviceCallbackFunc) deviceCallbackFunc { "Alternatively, you may unblock your device by using it in the Web UI." return trace.Wrap(err, msg) case 63: // FIDO_ERR_UV_INVALID, 0x3f - log.Debug("FIDO2: Retrying libfido2 error 63") + fidoLog.DebugContext(context.Background(), "Retrying libfido2 error 63") continue default: // Unexpected code. return err @@ -1041,7 +1082,7 @@ func waitForTouch(dev FIDODevice) (touched bool, err error) { touch, err := dev.TouchBegin() if err != nil { // Error logged here as it's mostly ignored by callers. - log.Debugf("FIDO2: Device touch begin error: %v", err) + fidoLog.DebugContext(context.Background(), "Device touch begin error", "error", err) return false, trace.Wrap(err) } defer touch.Stop() @@ -1051,7 +1092,7 @@ func waitForTouch(dev FIDODevice) (touched bool, err error) { touched, err := touch.Status(fido2TouchMaxWait) if err != nil { // Error logged here as it's mostly ignored by callers. - log.Debugf("FIDO2: Device touch status error: %v", err) + fidoLog.DebugContext(context.Background(), "Device touch status error", "error", err) return false, trace.Wrap(err) } if touched { diff --git a/lib/auth/webauthnwin/api.go b/lib/auth/webauthnwin/api.go index 4e8de09029911..5d3cdcefe22e5 100644 --- a/lib/auth/webauthnwin/api.go +++ b/lib/auth/webauthnwin/api.go @@ -27,13 +27,13 @@ import ( "context" "fmt" "io" + "log/slog" "os" "sync" "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/protocol/webauthncose" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/client/proto" wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes" @@ -221,7 +221,7 @@ type CheckSupportResult struct { func IsAvailable() bool { supports := CheckSupport() if supports.HasCompileSupport && !supports.IsAvailable { - log.Warn("Webauthn is not supported on this version of Windows, supported from version 1903") + slog.WarnContext(context.Background(), "Webauthn is not supported on this version of Windows, supported from version 1903") } return supports.IsAvailable diff --git a/lib/auth/webauthnwin/webauthn_windows.go b/lib/auth/webauthnwin/webauthn_windows.go index 681120739aa92..0f07071ba498e 100644 --- a/lib/auth/webauthnwin/webauthn_windows.go +++ b/lib/auth/webauthnwin/webauthn_windows.go @@ -19,17 +19,19 @@ package webauthnwin import ( + "context" "encoding/base64" "errors" "fmt" + "log/slog" "syscall" "unsafe" "github.com/go-webauthn/webauthn/protocol" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "golang.org/x/sys/windows" + "github.com/gravitational/teleport" wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes" ) @@ -52,27 +54,26 @@ func newNativeImpl() *nativeImpl { hasCompileSupport: true, } + logger := slog.With(teleport.ComponentKey, "WebAuthnWin") + ctx := context.Background() + // Explicitly loading the module avoids a panic when calling DLL functions if // the DLL is missing. // https://github.com/gravitational/teleport/issues/36851 if err := modWebAuthn.Load(); err != nil { - log. - WithError(err). - Debug("WebAuthnWin: failed to load WebAuthn.dll (it's likely missing)") + logger.DebugContext(ctx, "failed to load WebAuthn.dll (it's likely missing)", "error", err) return n } // Load WebAuthNGetApiVersionNumber explicitly too, it avoids a panic on some // Windows Server 2019 installs. if err := procWebAuthNGetApiVersionNumber.Find(); err != nil { - log. - WithError(err). - Debug("WebAuthnWin: failed to load WebAuthNGetApiVersionNumber") + logger.DebugContext(ctx, "failed to load WebAuthNGetApiVersionNumber", "error", err) return n } v, err := webAuthNGetApiVersionNumber() if err != nil { - log.WithError(err).Debug("WebAuthnWin: failed to check version") + logger.DebugContext(ctx, "failed to check version", "error", err) return n } n.webauthnAPIVersion = v @@ -86,7 +87,7 @@ func newNativeImpl() *nativeImpl { if err != nil { // This should not happen if dll exists, however we are fine with // to proceed without uvPlatform. - log.WithError(err).Debug("WebAuthnWin: failed to check isUVPlatformAuthenticatorAvailable") + logger.DebugContext(ctx, "failed to check isUVPlatformAuthenticatorAvailable", "error", err) } return n diff --git a/lib/auth/windows/certificate_authority.go b/lib/auth/windows/certificate_authority.go index 11c7049374be4..2c3a2789b3a51 100644 --- a/lib/auth/windows/certificate_authority.go +++ b/lib/auth/windows/certificate_authority.go @@ -20,9 +20,9 @@ package windows import ( "context" + "log/slog" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth/authclient" @@ -45,7 +45,7 @@ type CertificateStoreConfig struct { // LDAPConfig is the ldap configuration LDAPConfig // Log is the logging sink for the service - Log logrus.FieldLogger + Logger *slog.Logger // ClusterName is the name of this cluster ClusterName string // LC is the LDAPClient @@ -114,9 +114,9 @@ func (c *CertificateStoreClient) updateCRL(ctx context.Context, crlDER []byte, c ); err != nil { return trace.Wrap(err) } - c.cfg.Log.Info("Updated CRL for Windows logins via LDAP") + c.cfg.Logger.InfoContext(ctx, "Updated CRL for Windows logins via LDAP") } else { - c.cfg.Log.Info("Added CRL for Windows logins via LDAP") + c.cfg.Logger.InfoContext(ctx, "Added CRL for Windows logins via LDAP") } return nil } diff --git a/lib/authz/permissions.go b/lib/authz/permissions.go index 66fb0c6a86eeb..e905d6d9d479f 100644 --- a/lib/authz/permissions.go +++ b/lib/authz/permissions.go @@ -443,7 +443,7 @@ func (a *authorizer) Authorize(ctx context.Context) (authCtx *Context, err error // Device Trust: authorize device extensions. if !a.disableGlobalDeviceMode { - if err := dtauthz.VerifyTLSUser(authPref.GetDeviceTrust(), authContext.Identity.GetIdentity()); err != nil { + if err := dtauthz.VerifyTLSUser(ctx, authPref.GetDeviceTrust(), authContext.Identity.GetIdentity()); err != nil { return nil, trace.Wrap(err) } } diff --git a/lib/autoupdate/rollout/controller.go b/lib/autoupdate/rollout/controller.go index 1c45a6ac4fc5b..eb253f366b6a7 100644 --- a/lib/autoupdate/rollout/controller.go +++ b/lib/autoupdate/rollout/controller.go @@ -71,6 +71,7 @@ func NewController(client Client, log *slog.Logger, clock clockwork.Clock, perio reconciler: reconciler{ clt: client, log: log, + clock: clock, rolloutStrategies: []rolloutStrategy{ // TODO(hugoShaka): add the strategies here as we implement them }, diff --git a/lib/autoupdate/rollout/reconciler.go b/lib/autoupdate/rollout/reconciler.go index 1e186156a9daa..a6264ba3cbf27 100644 --- a/lib/autoupdate/rollout/reconciler.go +++ b/lib/autoupdate/rollout/reconciler.go @@ -153,7 +153,7 @@ func (r *reconciler) tryReconcile(ctx context.Context) error { return trace.Wrap(err, "computing rollout status") } - // there was an existing rollout, we must figure if something changed + // We compute if something changed. specChanged := !proto.Equal(existingRollout.GetSpec(), newSpec) statusChanged := !proto.Equal(existingRollout.GetStatus(), newStatus) rolloutChanged := specChanged || statusChanged @@ -273,6 +273,8 @@ func (r *reconciler) computeStatus( // We create a new status if the rollout should be reset or the previous status was nil if shouldResetRollout || existingRollout.GetStatus() == nil { status = new(autoupdate.AutoUpdateAgentRolloutStatus) + // We set the start time if this is a new rollout + status.StartTime = timestamppb.New(r.clock.Now()) } else { status = utils.CloneProtoMsg(existingRollout.GetStatus()) } @@ -302,8 +304,9 @@ func (r *reconciler) computeStatus( return nil, trace.Wrap(err, "creating groups status") } } + status.Groups = groups - err = r.progressRollout(ctx, newSpec.GetStrategy(), groups) + err = r.progressRollout(ctx, newSpec.GetStrategy(), status) // Failing to progress the update is not a hard failure. // We want to update the status even if something went wrong to surface the failed reconciliation and potential errors to the user. if err != nil { @@ -311,7 +314,6 @@ func (r *reconciler) computeStatus( "error", err) } - status.Groups = groups status.State = computeRolloutState(groups) return status, nil } @@ -320,10 +322,10 @@ func (r *reconciler) computeStatus( // groups are updated in place. // If an error is returned, the groups should still be upserted, depending on the strategy, // failing to update a group might not be fatal (other groups can still progress independently). -func (r *reconciler) progressRollout(ctx context.Context, strategyName string, groups []*autoupdate.AutoUpdateAgentRolloutStatusGroup) error { +func (r *reconciler) progressRollout(ctx context.Context, strategyName string, status *autoupdate.AutoUpdateAgentRolloutStatus) error { for _, strategy := range r.rolloutStrategies { if strategy.name() == strategyName { - return strategy.progressRollout(ctx, groups) + return strategy.progressRollout(ctx, status) } } return trace.NotImplemented("rollout strategy %q not implemented", strategyName) diff --git a/lib/autoupdate/rollout/reconciler_test.go b/lib/autoupdate/rollout/reconciler_test.go index 9518fe66280c2..b5b77cd735edb 100644 --- a/lib/autoupdate/rollout/reconciler_test.go +++ b/lib/autoupdate/rollout/reconciler_test.go @@ -139,6 +139,8 @@ func TestTryReconcile(t *testing.T) { t.Parallel() log := utils.NewSlogLoggerForTests() ctx := context.Background() + clock := clockwork.NewFakeClock() + // Test setup: creating fixtures configOK, err := update.NewAutoUpdateConfig(&autoupdate.AutoUpdateConfigSpec{ Tools: &autoupdate.AutoUpdateConfigSpecTools{ @@ -186,7 +188,7 @@ func TestTryReconcile(t *testing.T) { Strategy: update.AgentsStrategyHaltOnError, }) require.NoError(t, err) - upToDateRollout.Status = &autoupdate.AutoUpdateAgentRolloutStatus{} + upToDateRollout.Status = &autoupdate.AutoUpdateAgentRolloutStatus{StartTime: timestamppb.New(clock.Now())} outOfDateRollout, err := update.NewAutoUpdateAgentRollout(&autoupdate.AutoUpdateAgentRolloutSpec{ StartVersion: "1.2.2", @@ -315,8 +317,9 @@ func TestTryReconcile(t *testing.T) { // Test execution: Running the reconciliation reconciler := &reconciler{ - clt: client, - log: log, + clt: client, + log: log, + clock: clock, } require.NoError(t, reconciler.tryReconcile(ctx)) @@ -330,6 +333,7 @@ func TestTryReconcile(t *testing.T) { func TestReconciler_Reconcile(t *testing.T) { log := utils.NewSlogLoggerForTests() ctx := context.Background() + clock := clockwork.NewFakeClock() // Test setup: creating fixtures config, err := update.NewAutoUpdateConfig(&autoupdate.AutoUpdateConfigSpec{ Tools: &autoupdate.AutoUpdateConfigSpecTools{ @@ -361,7 +365,7 @@ func TestReconciler_Reconcile(t *testing.T) { Strategy: update.AgentsStrategyHaltOnError, }) require.NoError(t, err) - upToDateRollout.Status = &autoupdate.AutoUpdateAgentRolloutStatus{} + upToDateRollout.Status = &autoupdate.AutoUpdateAgentRolloutStatus{StartTime: timestamppb.New(clock.Now())} outOfDateRollout, err := update.NewAutoUpdateAgentRollout(&autoupdate.AutoUpdateAgentRolloutSpec{ StartVersion: "1.2.2", @@ -407,8 +411,9 @@ func TestReconciler_Reconcile(t *testing.T) { client := newMockClient(t, stubs) reconciler := &reconciler{ - clt: client, - log: log, + clt: client, + log: log, + clock: clock, } // Test execution: run the reconciliation loop @@ -431,8 +436,9 @@ func TestReconciler_Reconcile(t *testing.T) { client := newMockClient(t, stubs) reconciler := &reconciler{ - clt: client, - log: log, + clt: client, + log: log, + clock: clock, } // Test execution: run the reconciliation loop @@ -471,8 +477,9 @@ func TestReconciler_Reconcile(t *testing.T) { client := newMockClient(t, stubs) reconciler := &reconciler{ - clt: client, - log: log, + clt: client, + log: log, + clock: clock, } // Test execution: run the reconciliation loop @@ -509,8 +516,9 @@ func TestReconciler_Reconcile(t *testing.T) { client := newMockClient(t, stubs) reconciler := &reconciler{ - clt: client, - log: log, + clt: client, + log: log, + clock: clock, } // Test execution: run the reconciliation loop @@ -533,8 +541,9 @@ func TestReconciler_Reconcile(t *testing.T) { client := newMockClient(t, stubs) reconciler := &reconciler{ - clt: client, - log: log, + clt: client, + log: log, + clock: clock, } // Test execution: run the reconciliation loop @@ -563,8 +572,9 @@ func TestReconciler_Reconcile(t *testing.T) { client := newMockClient(t, stubs) reconciler := &reconciler{ - clt: client, - log: log, + clt: client, + log: log, + clock: clock, } // Test execution: run the reconciliation loop @@ -704,7 +714,7 @@ func (f *fakeRolloutStrategy) name() string { return f.strategyName } -func (f *fakeRolloutStrategy) progressRollout(ctx context.Context, groups []*autoupdate.AutoUpdateAgentRolloutStatusGroup) error { +func (f *fakeRolloutStrategy) progressRollout(ctx context.Context, status *autoupdate.AutoUpdateAgentRolloutStatus) error { f.calls++ return nil } @@ -742,8 +752,9 @@ func Test_reconciler_computeStatus(t *testing.T) { newGroups, err := r.makeGroupsStatus(ctx, schedules, clock.Now()) require.NoError(t, err) newStatus := &autoupdate.AutoUpdateAgentRolloutStatus{ - Groups: newGroups, - State: autoupdate.AutoUpdateAgentRolloutState_AUTO_UPDATE_AGENT_ROLLOUT_STATE_UNSTARTED, + Groups: newGroups, + State: autoupdate.AutoUpdateAgentRolloutState_AUTO_UPDATE_AGENT_ROLLOUT_STATE_UNSTARTED, + StartTime: timestamppb.New(clock.Now()), } tests := []struct { @@ -835,7 +846,9 @@ func Test_reconciler_computeStatus(t *testing.T) { Strategy: fakeRolloutStrategyName, }, // groups should be unset - expectedStatus: &autoupdate.AutoUpdateAgentRolloutStatus{}, + expectedStatus: &autoupdate.AutoUpdateAgentRolloutStatus{ + StartTime: timestamppb.New(clock.Now()), + }, expectedStrategyCalls: 0, }, { @@ -843,7 +856,9 @@ func Test_reconciler_computeStatus(t *testing.T) { existingRollout: &autoupdate.AutoUpdateAgentRollout{ Spec: oldSpec, // old groups were empty - Status: &autoupdate.AutoUpdateAgentRolloutStatus{}, + Status: &autoupdate.AutoUpdateAgentRolloutStatus{ + StartTime: timestamppb.New(clock.Now()), + }, }, // no spec change newSpec: oldSpec, diff --git a/lib/autoupdate/rollout/strategy.go b/lib/autoupdate/rollout/strategy.go index 025d6ae01570d..b3e214f6cebd7 100644 --- a/lib/autoupdate/rollout/strategy.go +++ b/lib/autoupdate/rollout/strategy.go @@ -33,13 +33,14 @@ const ( // Common update reasons updateReasonCreated = "created" updateReasonReconcilerError = "reconciler_error" + updateReasonRolloutChanged = "rollout_changed_during_window" ) // rolloutStrategy is responsible for rolling out the update across groups. // This interface allows us to inject dummy strategies for simpler testing. type rolloutStrategy interface { name() string - progressRollout(context.Context, []*autoupdate.AutoUpdateAgentRolloutStatusGroup) error + progressRollout(context.Context, *autoupdate.AutoUpdateAgentRolloutStatus) error } func inWindow(group *autoupdate.AutoUpdateAgentRolloutStatusGroup, now time.Time) (bool, error) { @@ -53,6 +54,16 @@ func inWindow(group *autoupdate.AutoUpdateAgentRolloutStatusGroup, now time.Time return int(group.ConfigStartHour) == now.Hour(), nil } +// rolloutChangedInWindow checks if the rollout got created after the theoretical group start time +func rolloutChangedInWindow(group *autoupdate.AutoUpdateAgentRolloutStatusGroup, now, rolloutStart time.Time) (bool, error) { + // If the rollout is older than 24h, we know it did not change during the window + if now.Sub(rolloutStart) > 24*time.Hour { + return false, nil + } + // Else we check if the rollout happened in the group window. + return inWindow(group, rolloutStart) +} + func canUpdateToday(allowedDays []string, now time.Time) (bool, error) { for _, allowedDay := range allowedDays { if allowedDay == types.Wildcard { diff --git a/lib/autoupdate/rollout/strategy_haltonerror.go b/lib/autoupdate/rollout/strategy_haltonerror.go index c93438aaa1941..0ab57a052768d 100644 --- a/lib/autoupdate/rollout/strategy_haltonerror.go +++ b/lib/autoupdate/rollout/strategy_haltonerror.go @@ -60,7 +60,7 @@ func newHaltOnErrorStrategy(log *slog.Logger, clock clockwork.Clock) (rolloutStr }, nil } -func (h *haltOnErrorStrategy) progressRollout(ctx context.Context, groups []*autoupdate.AutoUpdateAgentRolloutStatusGroup) error { +func (h *haltOnErrorStrategy) progressRollout(ctx context.Context, status *autoupdate.AutoUpdateAgentRolloutStatus) error { now := h.clock.Now() // We process every group in order, all the previous groups must be in the DONE state // for the next group to become active. Even if some early groups are not DONE, @@ -72,12 +72,12 @@ func (h *haltOnErrorStrategy) progressRollout(ctx context.Context, groups []*aut // to transition "staging" to DONE. previousGroupsAreDone := true - for i, group := range groups { + for i, group := range status.Groups { switch group.State { case autoupdate.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED: var previousGroup *autoupdate.AutoUpdateAgentRolloutStatusGroup if i != 0 { - previousGroup = groups[i-1] + previousGroup = status.Groups[i-1] } canStart, err := canStartHaltOnError(group, previousGroup, now) if err != nil { @@ -86,16 +86,31 @@ func (h *haltOnErrorStrategy) progressRollout(ctx context.Context, groups []*aut setGroupState(group, group.State, updateReasonReconcilerError, now) return err } + + // Check if the rollout got created after the theoretical group start time + rolloutChangedDuringWindow, err := rolloutChangedInWindow(group, now, status.StartTime.AsTime()) + if err != nil { + setGroupState(group, group.State, updateReasonReconcilerError, now) + return err + } + switch { - case previousGroupsAreDone && canStart: - // We can start - setGroupState(group, autoupdate.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE, updateReasonCanStart, now) - case previousGroupsAreDone: - // All previous groups are OK, but time-related criteria are not OK + case !previousGroupsAreDone: + // All previous groups are not DONE + setGroupState(group, group.State, updateReasonPreviousGroupsNotDone, now) + case !canStart: + // All previous groups are DONE, but time-related criteria are not met + // This can be because we are outside an update window, or because the + // specified wait_hours doesn't let us update yet. setGroupState(group, group.State, updateReasonCannotStart, now) + case rolloutChangedDuringWindow: + // All previous groups are DONE and time-related criteria are met. + // However, the rollout changed during the maintenance window. + setGroupState(group, group.State, updateReasonRolloutChanged, now) default: - // At least one previous group is not DONE - setGroupState(group, group.State, updateReasonPreviousGroupsNotDone, now) + // All previous groups are DONE and time-related criteria are met. + // We can start. + setGroupState(group, autoupdate.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE, updateReasonCanStart, now) } previousGroupsAreDone = false case autoupdate.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ROLLEDBACK: @@ -132,10 +147,10 @@ func canStartHaltOnError(group, previousGroup *autoupdate.AutoUpdateAgentRollout previousStart := previousGroup.StartTime.AsTime() if previousStart.IsZero() || previousStart.Unix() == 0 { - return false, trace.BadParameter("the previous group doesn't have a start time, cannot check the 'wait_hours' criteria") + return false, trace.BadParameter("the previous group doesn't have a start time, cannot check the 'wait_hours' criterion") } - // Check if the wait_hours criteria is OK, if we are at least after 'wait_hours' hours since the previous start. + // Check if the wait_hours criterion is OK, if we are at least after 'wait_hours' hours since the previous start. if now.Before(previousGroup.StartTime.AsTime().Add(time.Duration(group.ConfigWaitHours) * time.Hour)) { return false, nil } diff --git a/lib/autoupdate/rollout/strategy_haltonerror_test.go b/lib/autoupdate/rollout/strategy_haltonerror_test.go index 71a653c760361..84d4de069efe3 100644 --- a/lib/autoupdate/rollout/strategy_haltonerror_test.go +++ b/lib/autoupdate/rollout/strategy_haltonerror_test.go @@ -148,9 +148,10 @@ func Test_progressGroupsHaltOnError(t *testing.T) { group3Name := "group3" tests := []struct { - name string - initialState []*autoupdate.AutoUpdateAgentRolloutStatusGroup - expectedState []*autoupdate.AutoUpdateAgentRolloutStatusGroup + name string + initialState []*autoupdate.AutoUpdateAgentRolloutStatusGroup + rolloutStartTime *timestamppb.Timestamp + expectedState []*autoupdate.AutoUpdateAgentRolloutStatusGroup }{ { name: "single group unstarted -> unstarted", @@ -175,6 +176,30 @@ func Test_progressGroupsHaltOnError(t *testing.T) { }, }, }, + { + name: "single group unstarted -> unstarted because rollout changed in window", + initialState: []*autoupdate.AutoUpdateAgentRolloutStatusGroup{ + { + Name: group1Name, + State: autoupdate.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED, + LastUpdateTime: timestamppb.New(yesterday), + LastUpdateReason: updateReasonCreated, + ConfigDays: canStartToday, + ConfigStartHour: matchingStartHour, + }, + }, + rolloutStartTime: timestamppb.New(clock.Now()), + expectedState: []*autoupdate.AutoUpdateAgentRolloutStatusGroup{ + { + Name: group1Name, + State: autoupdate.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED, + LastUpdateTime: timestamppb.New(clock.Now()), + LastUpdateReason: updateReasonRolloutChanged, + ConfigDays: canStartToday, + ConfigStartHour: matchingStartHour, + }, + }, + }, { name: "single group unstarted -> active", initialState: []*autoupdate.AutoUpdateAgentRolloutStatusGroup{ @@ -470,7 +495,12 @@ func Test_progressGroupsHaltOnError(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := strategy.progressRollout(ctx, tt.initialState) + status := &autoupdate.AutoUpdateAgentRolloutStatus{ + Groups: tt.initialState, + State: 0, + StartTime: tt.rolloutStartTime, + } + err := strategy.progressRollout(ctx, status) require.NoError(t, err) // We use require.Equal instead of Elements match because group order matters. // It's not super important for time-based, but is crucial for halt-on-error. diff --git a/lib/autoupdate/rollout/strategy_test.go b/lib/autoupdate/rollout/strategy_test.go index fbb7ec768d644..1348716ba6c1d 100644 --- a/lib/autoupdate/rollout/strategy_test.go +++ b/lib/autoupdate/rollout/strategy_test.go @@ -151,13 +151,70 @@ func Test_inWindow(t *testing.T) { } } +func Test_rolloutChangedInWindow(t *testing.T) { + // Test setup: creating fixtures. + group := &autoupdate.AutoUpdateAgentRolloutStatusGroup{ + Name: "test-group", + ConfigDays: everyWeekdayButSunday, + ConfigStartHour: 12, + } + tests := []struct { + name string + now time.Time + rolloutStart time.Time + want bool + }{ + { + name: "zero rollout start time", + now: testSaturday, + rolloutStart: time.Time{}, + want: false, + }, + { + name: "epoch rollout start time", + now: testSaturday, + // tspb counts since epoch, wile go's zero is 0000-00-00 00:00:00 UTC + rolloutStart: (×tamppb.Timestamp{}).AsTime(), + want: false, + }, + { + name: "rollout changed a week ago", + now: testSaturday, + rolloutStart: testSaturday.Add(-7 * 24 * time.Hour), + want: false, + }, + { + name: "rollout changed the same day, before the window", + now: testSaturday, + rolloutStart: testSaturday.Add(-2 * time.Hour), + want: false, + }, + { + name: "rollout changed the same day, during the window", + now: testSaturday, + rolloutStart: testSaturday.Add(-2 * time.Minute), + want: true, + }, + { + name: "rollout just changed but we are not in a window", + now: testSunday, + rolloutStart: testSunday.Add(-2 * time.Minute), + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Test execution. + result, err := rolloutChangedInWindow(group, tt.now, tt.rolloutStart) + require.NoError(t, err) + require.Equal(t, tt.want, result) + }) + } +} + func Test_setGroupState(t *testing.T) { groupName := "test-group" - // TODO(hugoShaka) remove those two variables once the strategies are merged and the constants are defined. - updateReasonCanStart := "can_start" - updateReasonCannotStart := "cannot_start" - clock := clockwork.NewFakeClock() // oldUpdateTime is 5 minutes in the past oldUpdateTime := clock.Now() diff --git a/lib/autoupdate/rollout/strategy_timebased.go b/lib/autoupdate/rollout/strategy_timebased.go index c5abc34be5588..13e844f0e4a5a 100644 --- a/lib/autoupdate/rollout/strategy_timebased.go +++ b/lib/autoupdate/rollout/strategy_timebased.go @@ -56,11 +56,11 @@ func newTimeBasedStrategy(log *slog.Logger, clock clockwork.Clock) (rolloutStrat }, nil } -func (h *timeBasedStrategy) progressRollout(ctx context.Context, groups []*autoupdate.AutoUpdateAgentRolloutStatusGroup) error { +func (h *timeBasedStrategy) progressRollout(ctx context.Context, status *autoupdate.AutoUpdateAgentRolloutStatus) error { now := h.clock.Now() // We always process every group regardless of the order. var errs []error - for _, group := range groups { + for _, group := range status.Groups { switch group.State { case autoupdate.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED, autoupdate.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_DONE: @@ -76,10 +76,22 @@ func (h *timeBasedStrategy) progressRollout(ctx context.Context, groups []*autou errs = append(errs, err) continue } - if shouldBeActive { - setGroupState(group, autoupdate.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE, updateReasonInWindow, now) - } else { + + // Check if the rollout got created after the theoretical group start time + rolloutChangedDuringWindow, err := rolloutChangedInWindow(group, now, status.StartTime.AsTime()) + if err != nil { + setGroupState(group, group.State, updateReasonReconcilerError, now) + errs = append(errs, err) + continue + } + + switch { + case !shouldBeActive: setGroupState(group, group.State, updateReasonOutsideWindow, now) + case rolloutChangedDuringWindow: + setGroupState(group, group.State, updateReasonRolloutChanged, now) + default: + setGroupState(group, autoupdate.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE, updateReasonInWindow, now) } case autoupdate.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ROLLEDBACK: // We don't touch any group that was manually rolled back. diff --git a/lib/autoupdate/rollout/strategy_timebased_test.go b/lib/autoupdate/rollout/strategy_timebased_test.go index 91db29d42e469..6402da3b21c44 100644 --- a/lib/autoupdate/rollout/strategy_timebased_test.go +++ b/lib/autoupdate/rollout/strategy_timebased_test.go @@ -44,9 +44,10 @@ func Test_progressGroupsTimeBased(t *testing.T) { ctx := context.Background() tests := []struct { - name string - initialState []*autoupdate.AutoUpdateAgentRolloutStatusGroup - expectedState []*autoupdate.AutoUpdateAgentRolloutStatusGroup + name string + initialState []*autoupdate.AutoUpdateAgentRolloutStatusGroup + rolloutStartTime *timestamppb.Timestamp + expectedState []*autoupdate.AutoUpdateAgentRolloutStatusGroup }{ { name: "unstarted -> unstarted", @@ -71,6 +72,30 @@ func Test_progressGroupsTimeBased(t *testing.T) { }, }, }, + { + name: "unstarted -> unstarted because rollout just changed", + initialState: []*autoupdate.AutoUpdateAgentRolloutStatusGroup{ + { + Name: groupName, + State: autoupdate.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED, + LastUpdateTime: lastUpdate, + LastUpdateReason: updateReasonCreated, + ConfigDays: canStartToday, + ConfigStartHour: matchingStartHour, + }, + }, + rolloutStartTime: timestamppb.New(clock.Now()), + expectedState: []*autoupdate.AutoUpdateAgentRolloutStatusGroup{ + { + Name: groupName, + State: autoupdate.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED, + LastUpdateTime: timestamppb.New(clock.Now()), + LastUpdateReason: updateReasonRolloutChanged, + ConfigDays: canStartToday, + ConfigStartHour: matchingStartHour, + }, + }, + }, { name: "unstarted -> active", initialState: []*autoupdate.AutoUpdateAgentRolloutStatusGroup{ @@ -302,13 +327,18 @@ func Test_progressGroupsTimeBased(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := strategy.progressRollout(ctx, tt.initialState) + status := &autoupdate.AutoUpdateAgentRolloutStatus{ + Groups: tt.initialState, + State: 0, + StartTime: tt.rolloutStartTime, + } + err := strategy.progressRollout(ctx, status) require.NoError(t, err) // We use require.Equal instead of Elements match because group order matters. // It's not super important for time-based, but is crucial for halt-on-error. // So it's better to be more conservative and validate order never changes for // both strategies. - require.Equal(t, tt.expectedState, tt.initialState) + require.Equal(t, tt.expectedState, status.Groups) }) } } diff --git a/lib/client/api.go b/lib/client/api.go index 88693bc768bb4..9dd82e6af631a 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -1489,7 +1489,7 @@ type TargetNode struct { func (tc *TeleportClient) GetTargetNodes(ctx context.Context, clt client.ListUnifiedResourcesClient, options SSHOptions) ([]TargetNode, error) { ctx, span := tc.Tracer.Start( ctx, - "teleportClient/getTargetNodes", + "teleportClient/GetTargetNodes", oteltrace.WithSpanKind(oteltrace.SpanKindClient), ) defer span.End() @@ -1553,6 +1553,116 @@ func (tc *TeleportClient) GetTargetNodes(ctx context.Context, clt client.ListUni }, nil } +// GetTargetNode returns a single host matching the target host provided by users. Host resolution +// honors an explicit host, i.e. tsh ssh user@hostname, label based hosts, i.e. tsh ssh user@foo=bar, +// as well as respecting any proxy templates that are specified. +func (tc *TeleportClient) GetTargetNode(ctx context.Context, clt authclient.ClientI, options *SSHOptions) (*TargetNode, error) { + ctx, span := tc.Tracer.Start( + ctx, + "teleportClient/GetTargetNode", + oteltrace.WithSpanKind(oteltrace.SpanKindClient), + ) + defer span.End() + + if options != nil && options.HostAddress != "" { + return &TargetNode{ + Hostname: options.HostAddress, + Addr: options.HostAddress, + }, nil + } + + if len(tc.Labels) == 0 && len(tc.SearchKeywords) == 0 && tc.PredicateExpression == "" { + log.Debugf("Using provided host %s", tc.Host) + + // detect the common error when users use host:port address format + _, port, err := net.SplitHostPort(tc.Host) + // client has used host:port notation + if err == nil { + return nil, trace.BadParameter("please use ssh subcommand with '--port=%v' flag instead of semicolon", port) + } + + addr := net.JoinHostPort(tc.Host, strconv.Itoa(tc.HostPort)) + return &TargetNode{ + Hostname: tc.Host, + Addr: addr, + }, nil + } + + // Query for nodes if labels, fuzzy search, or predicate expressions were provided. + log.Debugf("Attempting to resolve matching host from labels=%v|search=%v|predicate=%v", tc.Labels, tc.SearchKeywords, tc.PredicateExpression) + resp, err := clt.ResolveSSHTarget(ctx, &proto.ResolveSSHTargetRequest{ + PredicateExpression: tc.PredicateExpression, + SearchKeywords: tc.SearchKeywords, + Labels: tc.Labels, + }) + switch { + //TODO(tross): DELETE IN v20.0.0 + case trace.IsNotImplemented(err): + resources, err := client.GetAllUnifiedResources(ctx, clt, &proto.ListUnifiedResourcesRequest{ + Kinds: []string{types.KindNode}, + SortBy: types.SortBy{Field: types.ResourceMetadataName}, + Labels: tc.Labels, + SearchKeywords: tc.SearchKeywords, + PredicateExpression: tc.PredicateExpression, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + switch len(resources) { + case 0: + return nil, trace.NotFound("no matching SSH hosts found for search terms or query expression") + case 1: + node, ok := resources[0].ResourceWithLabels.(*types.ServerV2) + if !ok { + return nil, trace.BadParameter("expected node resource, got %T", resources[0].ResourceWithLabels) + } + return &TargetNode{ + Hostname: node.GetHostname(), + Addr: node.GetName() + ":0", + }, nil + default: + // If routing does not allow choosing the most recent host, then abort with + // an ambiguous host error. + cnc, err := clt.GetClusterNetworkingConfig(ctx) + if err != nil || cnc.GetRoutingStrategy() != types.RoutingStrategy_MOST_RECENT { + return nil, trace.BadParameter("found multiple matching SSH hosts %v", resources[:2]) + } + + // Get the most recent version of the resource. + enrichedResource := slices.MaxFunc(resources, func(a, b *types.EnrichedResource) int { + return a.Expiry().Compare(b.Expiry()) + }) + server, ok := enrichedResource.ResourceWithLabels.(types.Server) + if !ok { + return nil, trace.BadParameter("received unexpected resource type %T", resources[0].ResourceWithLabels) + } + + // Dialing is happening by UUID but a port is still required by + // the Proxy dial request. Zero is an indicator to the Proxy that + // it may chose the appropriate port based on the target server. + return &TargetNode{ + Hostname: server.GetHostname(), + Addr: server.GetName() + ":0", + }, nil + } + case err == nil: + if resp.GetServer() == nil { + return nil, trace.NotFound("no matching SSH hosts found") + } + + // Dialing is happening by UUID but a port is still required by + // the Proxy dial request. Zero is an indicator to the Proxy that + // it may chose the appropriate port based on the target server. + return &TargetNode{ + Hostname: resp.GetServer().GetHostname(), + Addr: resp.GetServer().GetName() + ":0", + }, nil + default: + return nil, trace.Wrap(err) + } +} + // ReissueUserCerts issues new user certs based on params and stores them in // the local key agent (usually on disk in ~/.tsh). func (tc *TeleportClient) ReissueUserCerts(ctx context.Context, cachePolicy CertCachePolicy, params ReissueParams) error { @@ -2434,19 +2544,11 @@ func (tc *TeleportClient) SFTP(ctx context.Context, source []string, destination defer clt.Close() // Respect any proxy templates and attempt host resolution. - resolvedNodes, err := tc.GetTargetNodes(ctx, clt.AuthClient, SSHOptions{}) + target, err := tc.GetTargetNode(ctx, clt.AuthClient, nil) if err != nil { return trace.Wrap(err) } - switch len(resolvedNodes) { - case 1: - case 0: - return trace.NotFound("no matching hosts found") - default: - return trace.BadParameter("multiple matching hosts found") - } - var cfg *sftp.Config switch { case isDownload: @@ -2469,7 +2571,7 @@ func (tc *TeleportClient) SFTP(ctx context.Context, source []string, destination } } - return trace.Wrap(tc.TransferFiles(ctx, clt, tc.HostLogin, resolvedNodes[0].Addr, cfg)) + return trace.Wrap(tc.TransferFiles(ctx, clt, tc.HostLogin, target.Addr, cfg)) } // TransferFiles copies files between the current machine and the @@ -5032,19 +5134,6 @@ func findActiveApps(keyRing *KeyRing) ([]tlsca.RouteToApp, error) { return apps, nil } -// getDesktopEventWebURL returns the web UI URL users can access to -// watch a desktop session recording in the browser -func getDesktopEventWebURL(proxyHost string, cluster string, sid *session.ID, events []events.EventFields) string { - if len(events) < 1 { - return "" - } - start := events[0].GetTimestamp() - end := events[len(events)-1].GetTimestamp() - duration := end.Sub(start) - - return fmt.Sprintf("https://%s/web/cluster/%s/session/%s?recordingType=desktop&durationMs=%d", proxyHost, cluster, sid, duration/time.Millisecond) -} - // SearchSessionEvents allows searching for session events with a full pagination support. func (tc *TeleportClient) SearchSessionEvents(ctx context.Context, fromUTC, toUTC time.Time, pageSize int, order types.EventOrder, max int) ([]apievents.AuditEvent, error) { ctx, span := tc.Tracer.Start( diff --git a/lib/client/api_test.go b/lib/client/api_test.go index 9da016c7af401..320922e551ef7 100644 --- a/lib/client/api_test.go +++ b/lib/client/api_test.go @@ -43,12 +43,11 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/utils/grpc/interceptors" "github.com/gravitational/teleport/api/utils/keys" + "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/cryptosuites" "github.com/gravitational/teleport/lib/defaults" - "github.com/gravitational/teleport/lib/events" "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/observability/tracing" - "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/utils" ) @@ -929,78 +928,6 @@ func TestFormatConnectToProxyErr(t *testing.T) { } } -func TestGetDesktopEventWebURL(t *testing.T) { - initDate := time.Date(2021, 1, 1, 12, 0, 0, 0, time.UTC) - - tt := []struct { - name string - proxyHost string - cluster string - sid session.ID - events []events.EventFields - expected string - }{ - { - name: "nil events", - events: nil, - expected: "", - }, - { - name: "empty events", - events: make([]events.EventFields, 0), - expected: "", - }, - { - name: "two events, 1000 ms duration", - proxyHost: "host", - cluster: "cluster", - sid: "session_id", - events: []events.EventFields{ - { - "time": initDate, - }, - { - "time": initDate.Add(1000 * time.Millisecond), - }, - }, - expected: "https://host/web/cluster/cluster/session/session_id?recordingType=desktop&durationMs=1000", - }, - { - name: "multiple events", - proxyHost: "host", - cluster: "cluster", - sid: "session_id", - events: []events.EventFields{ - { - "time": initDate, - }, - { - "time": initDate.Add(10 * time.Millisecond), - }, - { - "time": initDate.Add(20 * time.Millisecond), - }, - { - "time": initDate.Add(30 * time.Millisecond), - }, - { - "time": initDate.Add(40 * time.Millisecond), - }, - { - "time": initDate.Add(50 * time.Millisecond), - }, - }, - expected: "https://host/web/cluster/cluster/session/session_id?recordingType=desktop&durationMs=50", - }, - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.expected, getDesktopEventWebURL(tc.proxyHost, tc.cluster, &tc.sid, tc.events)) - }) - } -} - type mockRoleGetter func(ctx context.Context) ([]types.Role, error) func (m mockRoleGetter) GetRoles(ctx context.Context) ([]types.Role, error) { @@ -1380,6 +1307,192 @@ func TestGetTargetNodes(t *testing.T) { } } +type fakeGetTargetNodeClient struct { + authclient.ClientI + + nodes []*types.ServerV2 + resolved *types.ServerV2 + resolveErr error + routeToMostRecent bool +} + +func (f fakeGetTargetNodeClient) ListUnifiedResources(ctx context.Context, req *proto.ListUnifiedResourcesRequest) (*proto.ListUnifiedResourcesResponse, error) { + out := make([]*proto.PaginatedResource, 0, len(f.nodes)) + for _, n := range f.nodes { + out = append(out, &proto.PaginatedResource{Resource: &proto.PaginatedResource_Node{Node: n}}) + } + + return &proto.ListUnifiedResourcesResponse{Resources: out}, nil +} + +func (f fakeGetTargetNodeClient) ResolveSSHTarget(ctx context.Context, req *proto.ResolveSSHTargetRequest) (*proto.ResolveSSHTargetResponse, error) { + if f.resolveErr != nil { + return nil, f.resolveErr + } + + return &proto.ResolveSSHTargetResponse{Server: f.resolved}, nil +} + +func (f fakeGetTargetNodeClient) GetClusterNetworkingConfig(ctx context.Context) (types.ClusterNetworkingConfig, error) { + cfg := types.DefaultClusterNetworkingConfig() + if f.routeToMostRecent { + cfg.SetRoutingStrategy(types.RoutingStrategy_MOST_RECENT) + } + + return cfg, nil +} + +func TestGetTargetNode(t *testing.T) { + now := time.Now() + then := now.Add(-5 * time.Hour) + + tests := []struct { + name string + options *SSHOptions + labels map[string]string + search []string + predicate string + host string + port int + clt fakeGetTargetNodeClient + errAssertion require.ErrorAssertionFunc + expected TargetNode + }{ + { + name: "options override", + options: &SSHOptions{ + HostAddress: "test:1234", + }, + host: "llama", + port: 56789, + errAssertion: require.NoError, + expected: TargetNode{Hostname: "test:1234", Addr: "test:1234"}, + }, + { + name: "explicit target", + host: "test", + port: 1234, + errAssertion: require.NoError, + expected: TargetNode{Hostname: "test", Addr: "test:1234"}, + }, + { + name: "resolved labels", + labels: map[string]string{"foo": "bar"}, + errAssertion: require.NoError, + expected: TargetNode{Hostname: "resolved-labels", Addr: "abcd:0"}, + clt: fakeGetTargetNodeClient{ + nodes: []*types.ServerV2{{Metadata: types.Metadata{Name: "abcd"}, Spec: types.ServerSpecV2{Hostname: "labels"}}}, + resolved: &types.ServerV2{Metadata: types.Metadata{Name: "abcd"}, Spec: types.ServerSpecV2{Hostname: "resolved-labels"}}, + }, + }, + { + name: "fallback labels", + labels: map[string]string{"foo": "bar"}, + errAssertion: require.NoError, + expected: TargetNode{Hostname: "labels", Addr: "abcd:0"}, + clt: fakeGetTargetNodeClient{ + nodes: []*types.ServerV2{{Metadata: types.Metadata{Name: "abcd"}, Spec: types.ServerSpecV2{Hostname: "labels"}}}, + resolved: &types.ServerV2{Metadata: types.Metadata{Name: "abcd"}, Spec: types.ServerSpecV2{Hostname: "resolved-labels"}}, + resolveErr: trace.NotImplemented(""), + }, + }, + { + name: "resolved search", + search: []string{"foo", "bar"}, + errAssertion: require.NoError, + expected: TargetNode{Hostname: "resolved-search", Addr: "abcd:0"}, + clt: fakeGetTargetNodeClient{ + nodes: []*types.ServerV2{{Metadata: types.Metadata{Name: "abcd"}, Spec: types.ServerSpecV2{Hostname: "search"}}}, + resolved: &types.ServerV2{Metadata: types.Metadata{Name: "abcd"}, Spec: types.ServerSpecV2{Hostname: "resolved-search"}}, + }, + }, + + { + name: "fallback search", + search: []string{"foo", "bar"}, + errAssertion: require.NoError, + expected: TargetNode{Hostname: "search", Addr: "abcd:0"}, + clt: fakeGetTargetNodeClient{ + nodes: []*types.ServerV2{{Metadata: types.Metadata{Name: "abcd"}, Spec: types.ServerSpecV2{Hostname: "search"}}}, + resolveErr: trace.NotImplemented(""), + resolved: &types.ServerV2{Metadata: types.Metadata{Name: "abcd"}, Spec: types.ServerSpecV2{Hostname: "resolved-search"}}, + }, + }, + { + name: "resolved predicate", + predicate: `resource.spec.hostname == "test"`, + errAssertion: require.NoError, + expected: TargetNode{Hostname: "resolved-predicate", Addr: "abcd:0"}, + clt: fakeGetTargetNodeClient{ + nodes: []*types.ServerV2{{Metadata: types.Metadata{Name: "abcd"}, Spec: types.ServerSpecV2{Hostname: "predicate"}}}, + resolved: &types.ServerV2{Metadata: types.Metadata{Name: "abcd"}, Spec: types.ServerSpecV2{Hostname: "resolved-predicate"}}, + }, + }, + { + name: "fallback predicate", + predicate: `resource.spec.hostname == "test"`, + errAssertion: require.NoError, + expected: TargetNode{Hostname: "predicate", Addr: "abcd:0"}, + clt: fakeGetTargetNodeClient{ + nodes: []*types.ServerV2{{Metadata: types.Metadata{Name: "abcd"}, Spec: types.ServerSpecV2{Hostname: "predicate"}}}, + resolveErr: trace.NotImplemented(""), + resolved: &types.ServerV2{Metadata: types.Metadata{Name: "abcd"}, Spec: types.ServerSpecV2{Hostname: "resolved-predicate"}}, + }, + }, + { + name: "fallback ambiguous hosts", + predicate: `resource.spec.hostname == "test"`, + errAssertion: require.Error, + clt: fakeGetTargetNodeClient{ + nodes: []*types.ServerV2{ + {Metadata: types.Metadata{Name: "abcd-1"}, Spec: types.ServerSpecV2{Hostname: "predicate"}}, + {Metadata: types.Metadata{Name: "abcd-2"}, Spec: types.ServerSpecV2{Hostname: "predicate"}}, + }, + resolveErr: trace.NotImplemented(""), + resolved: &types.ServerV2{Metadata: types.Metadata{Name: "abcd"}, Spec: types.ServerSpecV2{Hostname: "resolved-predicate"}}, + }, + }, + { + name: "fallback and route to recent", + predicate: `resource.spec.hostname == "test"`, + errAssertion: require.NoError, + expected: TargetNode{Hostname: "predicate-now", Addr: "abcd-1:0"}, + clt: fakeGetTargetNodeClient{ + nodes: []*types.ServerV2{ + {Metadata: types.Metadata{Name: "abcd-0", Expires: &then}, Spec: types.ServerSpecV2{Hostname: "predicate-then"}}, + {Metadata: types.Metadata{Name: "abcd-1", Expires: &now}, Spec: types.ServerSpecV2{Hostname: "predicate-now"}}, + {Metadata: types.Metadata{Name: "abcd-2", Expires: &then}, Spec: types.ServerSpecV2{Hostname: "predicate-then-again"}}, + }, + resolveErr: trace.NotImplemented(""), + routeToMostRecent: true, + resolved: &types.ServerV2{Metadata: types.Metadata{Name: "abcd"}, Spec: types.ServerSpecV2{Hostname: "resolved-predicate"}}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + clt := TeleportClient{ + Config: Config{ + Tracer: tracing.NoopTracer(""), + Labels: test.labels, + SearchKeywords: test.search, + PredicateExpression: test.predicate, + Host: test.host, + HostPort: test.port, + }, + } + + match, err := clt.GetTargetNode(context.Background(), test.clt, test.options) + test.errAssertion(t, err) + if match == nil { + match = &TargetNode{} + } + require.EqualValues(t, test.expected, *match) + }) + } +} + func TestNonRetryableError(t *testing.T) { orgError := trace.AccessDenied("do not enter") err := &NonRetryableError{ diff --git a/lib/client/clientcache/clientcache.go b/lib/client/clientcache/clientcache.go index 5a9c4df29e7de..f5e8f44aafdf9 100644 --- a/lib/client/clientcache/clientcache.go +++ b/lib/client/clientcache/clientcache.go @@ -18,11 +18,11 @@ package clientcache import ( "context" + "log/slog" "slices" "sync" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "golang.org/x/sync/singleflight" "github.com/gravitational/teleport" @@ -53,7 +53,7 @@ type RetryWithReloginFunc func(ctx context.Context, tc *client.TeleportClient, f type Config struct { NewClientFunc NewClientFunc RetryWithReloginFunc RetryWithReloginFunc - Log logrus.FieldLogger + Logger *slog.Logger } func (c *Config) checkAndSetDefaults() error { @@ -63,8 +63,8 @@ func (c *Config) checkAndSetDefaults() error { if c.RetryWithReloginFunc == nil { return trace.BadParameter("RetryWithReloginFunc is required") } - if c.Log == nil { - c.Log = logrus.WithField(teleport.ComponentKey, "clientcache") + if c.Logger == nil { + c.Logger = slog.With(teleport.ComponentKey, "clientcache") } return nil } @@ -99,7 +99,7 @@ func (c *Cache) Get(ctx context.Context, profileName, leafClusterName string) (* k := key{profile: profileName, leafCluster: leafClusterName} groupClt, err, _ := c.group.Do(k.String(), func() (any, error) { if fromCache := c.getFromCache(k); fromCache != nil { - c.cfg.Log.WithField("cluster", k).Debug("Retrieved client from cache.") + c.cfg.Logger.DebugContext(ctx, "Retrieved client from cache", "cluster", k) return fromCache, nil } @@ -123,7 +123,7 @@ func (c *Cache) Get(ctx context.Context, profileName, leafClusterName string) (* // Save the client in the cache, so we don't have to build a new connection next time. c.addToCache(k, newClient) - c.cfg.Log.WithField("cluster", k).Info("Added client to cache.") + c.cfg.Logger.InfoContext(ctx, "Added client to cache", "cluster", k) return newClient, nil }) @@ -159,9 +159,10 @@ func (c *Cache) ClearForRoot(profileName string) error { } } - c.cfg.Log.WithFields( - logrus.Fields{"cluster": profileName, "clients": deleted}, - ).Info("Invalidated cached clients for root cluster.") + c.cfg.Logger.InfoContext(context.Background(), "Invalidated cached clients for root cluster", + "cluster", profileName, + "clients", deleted, + ) return trace.NewAggregate(errors...) diff --git a/lib/client/db/dbcmd/dbcmd.go b/lib/client/db/dbcmd/dbcmd.go index 603a284ea7ec9..376435b260d40 100644 --- a/lib/client/db/dbcmd/dbcmd.go +++ b/lib/client/db/dbcmd/dbcmd.go @@ -21,6 +21,7 @@ package dbcmd import ( "context" "fmt" + "log/slog" "net/url" "os" "os/exec" @@ -30,7 +31,6 @@ import ( "strings" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "go.mongodb.org/mongo-driver/x/mongo/driver/connstring" "github.com/gravitational/teleport/api/constants" @@ -143,8 +143,8 @@ func NewCmdBuilder(tc *client.TeleportClient, profile *client.ProfileStatus, host, port = tc.DatabaseProxyHostPort(db) } - if options.log == nil { - options.log = logrus.NewEntry(logrus.StandardLogger()) + if options.logger == nil { + options.logger = slog.Default() } if options.exe == nil { @@ -256,8 +256,11 @@ func (c *CLICommandBuilder) getPostgresCommand() *exec.Cmd { func (c *CLICommandBuilder) getCockroachCommand() *exec.Cmd { // If cockroach CLI client is not available, fallback to psql. if _, err := c.options.exe.LookPath(cockroachBin); err != nil { - c.options.log.Debugf("Couldn't find %q client in PATH, falling back to %q: %v.", - cockroachBin, postgresBin, err) + c.options.logger.DebugContext(context.Background(), "Couldn't find cockroach client in PATH, falling back to postgres client", + "cockroach_client", cockroachBin, + "postgres_client", postgresBin, + "error", err, + ) return c.getPostgresCommand() } return exec.Command(cockroachBin, "sql", "--url", c.getPostgresConnString()) @@ -560,7 +563,10 @@ func (c *CLICommandBuilder) getMongoAddress() string { // force a different timeout for debugging purpose or extreme situations. serverSelectionTimeoutMS := "5000" if envValue := os.Getenv(envVarMongoServerSelectionTimeoutMS); envValue != "" { - c.options.log.Infof("Using environment variable %s=%s.", envVarMongoServerSelectionTimeoutMS, envValue) + c.options.logger.InfoContext(context.Background(), "Using server selection timeout value from environment variable", + "environment_variable", envVarMongoServerSelectionTimeoutMS, + "server_selection_timeout", envValue, + ) serverSelectionTimeoutMS = envValue } query.Set("serverSelectionTimeoutMS", serverSelectionTimeoutMS) @@ -905,7 +911,7 @@ type connectionCommandOpts struct { noTLS bool printFormat bool tolerateMissingCLIClient bool - log *logrus.Entry + logger *slog.Logger exe Execer password string gcp types.GCPCloudSQL @@ -969,9 +975,9 @@ func WithPrintFormat() ConnectCommandFunc { // WithLogger is the connect command option that allows the caller to pass a logger that will be // used by CLICommandBuilder. -func WithLogger(log *logrus.Entry) ConnectCommandFunc { +func WithLogger(log *slog.Logger) ConnectCommandFunc { return func(opts *connectionCommandOpts) { - opts.log = log + opts.logger = log } } diff --git a/lib/client/weblogin.go b/lib/client/weblogin.go index 7edf946c0e39f..c3415e340417d 100644 --- a/lib/client/weblogin.go +++ b/lib/client/weblogin.go @@ -113,6 +113,8 @@ type MFAChallengeResponse struct { WebauthnResponse *wantypes.CredentialAssertionResponse `json:"webauthn_response,omitempty"` // SSOResponse is a response from an SSO MFA flow. SSOResponse *SSOResponse `json:"sso_response"` + // TODO(Joerger): DELETE IN v19.0.0, WebauthnResponse used instead. + WebauthnAssertionResponse *wantypes.CredentialAssertionResponse `json:"webauthnAssertionResponse"` } // SSOResponse is a json compatible [proto.SSOResponse]. @@ -124,25 +126,57 @@ type SSOResponse struct { // GetOptionalMFAResponseProtoReq converts response to a type proto.MFAAuthenticateResponse, // if there were any responses set. Otherwise returns nil. func (r *MFAChallengeResponse) GetOptionalMFAResponseProtoReq() (*proto.MFAAuthenticateResponse, error) { - if r.TOTPCode != "" && r.WebauthnResponse != nil { + var availableResponses int + if r.TOTPCode != "" { + availableResponses++ + } + if r.WebauthnResponse != nil { + availableResponses++ + } + if r.SSOResponse != nil { + availableResponses++ + } + + if availableResponses > 1 { return nil, trace.BadParameter("only one MFA response field can be set") } - if r.TOTPCode != "" { + switch { + case r.WebauthnResponse != nil: + return &proto.MFAAuthenticateResponse{Response: &proto.MFAAuthenticateResponse_Webauthn{ + Webauthn: wantypes.CredentialAssertionResponseToProto(r.WebauthnResponse), + }}, nil + case r.SSOResponse != nil: + return &proto.MFAAuthenticateResponse{Response: &proto.MFAAuthenticateResponse_SSO{ + SSO: &proto.SSOResponse{ + RequestId: r.SSOResponse.RequestID, + Token: r.SSOResponse.Token, + }, + }}, nil + case r.TOTPCode != "": return &proto.MFAAuthenticateResponse{Response: &proto.MFAAuthenticateResponse_TOTP{ TOTP: &proto.TOTPResponse{Code: r.TOTPCode}, }}, nil - } - - if r.WebauthnResponse != nil { + case r.WebauthnAssertionResponse != nil: return &proto.MFAAuthenticateResponse{Response: &proto.MFAAuthenticateResponse_Webauthn{ - Webauthn: wantypes.CredentialAssertionResponseToProto(r.WebauthnResponse), + Webauthn: wantypes.CredentialAssertionResponseToProto(r.WebauthnAssertionResponse), }}, nil } return nil, nil } +// ParseMFAChallengeResponse parses [MFAChallengeResponse] from JSON and returns it as a [proto.MFAAuthenticateResponse]. +func ParseMFAChallengeResponse(mfaResponseJSON []byte) (*proto.MFAAuthenticateResponse, error) { + var resp MFAChallengeResponse + if err := json.Unmarshal(mfaResponseJSON, &resp); err != nil { + return nil, trace.Wrap(err) + } + + protoResp, err := resp.GetOptionalMFAResponseProtoReq() + return protoResp, trace.Wrap(err) +} + // CreateSSHCertReq is passed by tsh to authenticate a local user without MFA // and receive short-lived certificates. type CreateSSHCertReq struct { diff --git a/lib/config/configuration.go b/lib/config/configuration.go index 54dc6a2c82820..929bb757a85ca 100644 --- a/lib/config/configuration.go +++ b/lib/config/configuration.go @@ -1754,6 +1754,12 @@ kubernetes matchers are present`) AssumeRole: assumeRole, }) } + for _, azureMatcher := range fc.Discovery.AccessGraph.Azure { + subscriptionID := azureMatcher.SubscriptionID + tMatcher.Azure = append(tMatcher.Azure, &types.AccessGraphAzureSync{ + SubscriptionID: subscriptionID, + }) + } if fc.Discovery.AccessGraph.PollInterval > 0 { tMatcher.PollInterval = fc.Discovery.AccessGraph.PollInterval } diff --git a/lib/config/fileconf.go b/lib/config/fileconf.go index a4bc53787ce9f..0c417106924e7 100644 --- a/lib/config/fileconf.go +++ b/lib/config/fileconf.go @@ -1523,6 +1523,8 @@ type GCPMatcher struct { type AccessGraphSync struct { // AWS is the AWS configuration for the AccessGraph Sync service. AWS []AccessGraphAWSSync `yaml:"aws,omitempty"` + // Azure is the Azure configuration for the AccessGraph Sync service. + Azure []AccessGraphAzureSync `yaml:"azure,omitempty"` // PollInterval is the frequency at which to poll for AWS resources PollInterval time.Duration `yaml:"poll_interval,omitempty"` } @@ -1538,6 +1540,12 @@ type AccessGraphAWSSync struct { ExternalID string `yaml:"external_id,omitempty"` } +// AccessGraphAzureSync represents the configuration for the Azure AccessGraph Sync service. +type AccessGraphAzureSync struct { + // SubscriptionID is the Azure subscription ID configured for syncing + SubscriptionID string `yaml:"subscription_id,omitempty"` +} + // CommandLabel is `command` section of `ssh_service` in the config file type CommandLabel struct { Name string `yaml:"name"` diff --git a/lib/decision/tls_identity.go b/lib/decision/tls_identity.go new file mode 100644 index 0000000000000..d0cf1c7905eab --- /dev/null +++ b/lib/decision/tls_identity.go @@ -0,0 +1,278 @@ +// 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 decision + +import ( + "time" + + "google.golang.org/protobuf/types/known/timestamppb" + + decisionpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/decision/v1alpha1" + traitpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/trait/v1" + "github.com/gravitational/teleport/api/types" + apitrait "github.com/gravitational/teleport/api/types/trait" + apitraitconvert "github.com/gravitational/teleport/api/types/trait/convert/v1" + "github.com/gravitational/teleport/api/types/wrappers" + "github.com/gravitational/teleport/api/utils/keys" + "github.com/gravitational/teleport/lib/tlsca" +) + +// TLSIdentityToTLSCA transforms a [decisionpb.TLSIdentity] into its +// equivalent [tlsca.Identity]. +// Note that certain types, like slices, are not deep-copied. +func TLSIdentityToTLSCA(id *decisionpb.TLSIdentity) *tlsca.Identity { + if id == nil { + return nil + } + + return &tlsca.Identity{ + Username: id.Username, + Impersonator: id.Impersonator, + Groups: id.Groups, + SystemRoles: id.SystemRoles, + Usage: id.Usage, + Principals: id.Principals, + KubernetesGroups: id.KubernetesGroups, + KubernetesUsers: id.KubernetesUsers, + Expires: timestampToGoTime(id.Expires), + RouteToCluster: id.RouteToCluster, + KubernetesCluster: id.KubernetesCluster, + Traits: traitToWrappers(id.Traits), + RouteToApp: routeToAppFromProto(id.RouteToApp), + TeleportCluster: id.TeleportCluster, + RouteToDatabase: routeToDatabaseFromProto(id.RouteToDatabase), + DatabaseNames: id.DatabaseNames, + DatabaseUsers: id.DatabaseUsers, + MFAVerified: id.MfaVerified, + PreviousIdentityExpires: timestampToGoTime(id.PreviousIdentityExpires), + LoginIP: id.LoginIp, + PinnedIP: id.PinnedIp, + AWSRoleARNs: id.AwsRoleArns, + AzureIdentities: id.AzureIdentities, + GCPServiceAccounts: id.GcpServiceAccounts, + ActiveRequests: id.ActiveRequests, + DisallowReissue: id.DisallowReissue, + Renewable: id.Renewable, + Generation: id.Generation, + BotName: id.BotName, + BotInstanceID: id.BotInstanceId, + AllowedResourceIDs: resourceIDsToTypes(id.AllowedResourceIds), + PrivateKeyPolicy: keys.PrivateKeyPolicy(id.PrivateKeyPolicy), + ConnectionDiagnosticID: id.ConnectionDiagnosticId, + DeviceExtensions: deviceExtensionsFromProto(id.DeviceExtensions), + UserType: types.UserType(id.UserType), + } +} + +// TLSIdentityFromTLSCA transforms a [tlsca.Identity] into its equivalent +// [decisionpb.TLSIdentity]. +// Note that certain types, like slices, are not deep-copied. +func TLSIdentityFromTLSCA(id *tlsca.Identity) *decisionpb.TLSIdentity { + if id == nil { + return nil + } + + return &decisionpb.TLSIdentity{ + Username: id.Username, + Impersonator: id.Impersonator, + Groups: id.Groups, + SystemRoles: id.SystemRoles, + Usage: id.Usage, + Principals: id.Principals, + KubernetesGroups: id.KubernetesGroups, + KubernetesUsers: id.KubernetesUsers, + Expires: timestampFromGoTime(id.Expires), + RouteToCluster: id.RouteToCluster, + KubernetesCluster: id.KubernetesCluster, + Traits: traitFromWrappers(id.Traits), + RouteToApp: routeToAppToProto(&id.RouteToApp), + TeleportCluster: id.TeleportCluster, + RouteToDatabase: routeToDatabaseToProto(&id.RouteToDatabase), + DatabaseNames: id.DatabaseNames, + DatabaseUsers: id.DatabaseUsers, + MfaVerified: id.MFAVerified, + PreviousIdentityExpires: timestampFromGoTime(id.PreviousIdentityExpires), + LoginIp: id.LoginIP, + PinnedIp: id.PinnedIP, + AwsRoleArns: id.AWSRoleARNs, + AzureIdentities: id.AzureIdentities, + GcpServiceAccounts: id.GCPServiceAccounts, + ActiveRequests: id.ActiveRequests, + DisallowReissue: id.DisallowReissue, + Renewable: id.Renewable, + Generation: id.Generation, + BotName: id.BotName, + BotInstanceId: id.BotInstanceID, + AllowedResourceIds: resourceIDsFromTypes(id.AllowedResourceIDs), + PrivateKeyPolicy: string(id.PrivateKeyPolicy), + ConnectionDiagnosticId: id.ConnectionDiagnosticID, + DeviceExtensions: deviceExtensionsToProto(&id.DeviceExtensions), + UserType: string(id.UserType), + } +} + +func timestampToGoTime(t *timestamppb.Timestamp) time.Time { + // nil or "zero" Timestamps are mapped to Go's zero time (0-0-0 0:0.0) instead + // of unix epoch. The latter avoids problems with tooling (eg, Terraform) that + // sets structs to their defaults instead of using nil. + if t == nil || (t.Seconds == 0 && t.Nanos == 0) { + return time.Time{} + } + return t.AsTime() +} + +func timestampFromGoTime(t time.Time) *timestamppb.Timestamp { + if t.IsZero() { + return nil + } + return timestamppb.New(t) +} + +func traitToWrappers(traits []*traitpb.Trait) wrappers.Traits { + apiTraits := apitraitconvert.FromProto(traits) + return wrappers.Traits(apiTraits) +} + +func traitFromWrappers(traits wrappers.Traits) []*traitpb.Trait { + if len(traits) == 0 { + return nil + } + apiTraits := apitrait.Traits(traits) + return apitraitconvert.ToProto(apiTraits) +} + +func routeToAppFromProto(routeToApp *decisionpb.RouteToApp) tlsca.RouteToApp { + if routeToApp == nil { + return tlsca.RouteToApp{} + } + + return tlsca.RouteToApp{ + SessionID: routeToApp.SessionId, + PublicAddr: routeToApp.PublicAddr, + ClusterName: routeToApp.ClusterName, + Name: routeToApp.Name, + AWSRoleARN: routeToApp.AwsRoleArn, + AzureIdentity: routeToApp.AzureIdentity, + GCPServiceAccount: routeToApp.GcpServiceAccount, + URI: routeToApp.Uri, + TargetPort: int(routeToApp.TargetPort), + } +} + +func routeToAppToProto(routeToApp *tlsca.RouteToApp) *decisionpb.RouteToApp { + if routeToApp == nil { + return nil + } + + return &decisionpb.RouteToApp{ + SessionId: routeToApp.SessionID, + PublicAddr: routeToApp.PublicAddr, + ClusterName: routeToApp.ClusterName, + Name: routeToApp.Name, + AwsRoleArn: routeToApp.AWSRoleARN, + AzureIdentity: routeToApp.AzureIdentity, + GcpServiceAccount: routeToApp.GCPServiceAccount, + Uri: routeToApp.URI, + TargetPort: int32(routeToApp.TargetPort), + } +} + +func routeToDatabaseFromProto(routeToDatabase *decisionpb.RouteToDatabase) tlsca.RouteToDatabase { + if routeToDatabase == nil { + return tlsca.RouteToDatabase{} + } + + return tlsca.RouteToDatabase{ + ServiceName: routeToDatabase.ServiceName, + Protocol: routeToDatabase.Protocol, + Username: routeToDatabase.Username, + Database: routeToDatabase.Database, + Roles: routeToDatabase.Roles, + } +} + +func routeToDatabaseToProto(routeToDatabase *tlsca.RouteToDatabase) *decisionpb.RouteToDatabase { + if routeToDatabase == nil { + return nil + } + + return &decisionpb.RouteToDatabase{ + ServiceName: routeToDatabase.ServiceName, + Protocol: routeToDatabase.Protocol, + Username: routeToDatabase.Username, + Database: routeToDatabase.Database, + Roles: routeToDatabase.Roles, + } +} + +func resourceIDsToTypes(resourceIDs []*decisionpb.ResourceId) []types.ResourceID { + if len(resourceIDs) == 0 { + return nil + } + + ret := make([]types.ResourceID, len(resourceIDs)) + for i, r := range resourceIDs { + ret[i] = types.ResourceID{ + ClusterName: r.ClusterName, + Kind: r.Kind, + Name: r.Name, + SubResourceName: r.SubResourceName, + } + } + return ret +} + +func resourceIDsFromTypes(resourceIDs []types.ResourceID) []*decisionpb.ResourceId { + if len(resourceIDs) == 0 { + return nil + } + + ret := make([]*decisionpb.ResourceId, len(resourceIDs)) + for i, r := range resourceIDs { + ret[i] = &decisionpb.ResourceId{ + ClusterName: r.ClusterName, + Kind: r.Kind, + Name: r.Name, + SubResourceName: r.SubResourceName, + } + } + return ret +} + +func deviceExtensionsFromProto(exts *decisionpb.DeviceExtensions) tlsca.DeviceExtensions { + if exts == nil { + return tlsca.DeviceExtensions{} + } + + return tlsca.DeviceExtensions{ + DeviceID: exts.DeviceId, + AssetTag: exts.AssetTag, + CredentialID: exts.CredentialId, + } +} + +func deviceExtensionsToProto(exts *tlsca.DeviceExtensions) *decisionpb.DeviceExtensions { + if exts == nil { + return nil + } + + return &decisionpb.DeviceExtensions{ + DeviceId: exts.DeviceID, + AssetTag: exts.AssetTag, + CredentialId: exts.CredentialID, + } +} diff --git a/lib/decision/tls_identity_test.go b/lib/decision/tls_identity_test.go new file mode 100644 index 0000000000000..8ac417c3b47da --- /dev/null +++ b/lib/decision/tls_identity_test.go @@ -0,0 +1,171 @@ +// 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 decision_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" + + decisionpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/decision/v1alpha1" + traitpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/trait/v1" + "github.com/gravitational/teleport/lib/decision" + "github.com/gravitational/teleport/lib/tlsca" +) + +func TestTLSIdentity_roundtrip(t *testing.T) { + t.Parallel() + + minimalTLSIdentity := &decisionpb.TLSIdentity{ + // tlsca.Identity has no pointer fields, so these are always non-nil after + // copying. + RouteToApp: &decisionpb.RouteToApp{}, + RouteToDatabase: &decisionpb.RouteToDatabase{}, + DeviceExtensions: &decisionpb.DeviceExtensions{}, + } + + fullIdentity := &decisionpb.TLSIdentity{ + Username: "user", + Impersonator: "impersonator", + Groups: []string{"role1", "role2"}, + SystemRoles: []string{"system1", "system2"}, + Usage: []string{"usage1", "usage2"}, + Principals: []string{"login1", "login2"}, + KubernetesGroups: []string{"kgroup1", "kgroup2"}, + KubernetesUsers: []string{"kuser1", "kuser2"}, + Expires: timestamppb.Now(), + RouteToCluster: "route-to-cluster", + KubernetesCluster: "k8s-cluster", + Traits: []*traitpb.Trait{ + // Note: sorted by key on conversion. + {Key: "", Values: []string{"missingkey"}}, + {Key: "missingvalues", Values: nil}, + {Key: "trait1", Values: []string{"val1"}}, + {Key: "trait2", Values: []string{"val1", "val2"}}, + }, + RouteToApp: &decisionpb.RouteToApp{ + SessionId: "session-id", + PublicAddr: "public-addr", + ClusterName: "cluster-name", + Name: "name", + AwsRoleArn: "aws-role-arn", + AzureIdentity: "azure-id", + GcpServiceAccount: "gcp-service-account", + Uri: "uri", + TargetPort: 111, + }, + TeleportCluster: "teleport-cluster", + RouteToDatabase: &decisionpb.RouteToDatabase{ + ServiceName: "service-name", + Protocol: "protocol", + Username: "username", + Database: "database", + Roles: []string{"role1", "role2"}, + }, + DatabaseNames: []string{"db1", "db2"}, + DatabaseUsers: []string{"dbuser1", "dbuser2"}, + MfaVerified: "mfa-device-id", + PreviousIdentityExpires: timestamppb.Now(), + LoginIp: "login-ip", + PinnedIp: "pinned-ip", + AwsRoleArns: []string{"arn1", "arn2"}, + AzureIdentities: []string{"azure-id-1", "azure-id-2"}, + GcpServiceAccounts: []string{"gcp-account-1", "gcp-account-2"}, + ActiveRequests: []string{"accessrequest1", "accessrequest2"}, + DisallowReissue: true, + Renewable: true, + Generation: 112, + BotName: "bot-name", + BotInstanceId: "bot-instance-id", + AllowedResourceIds: []*decisionpb.ResourceId{ + { + ClusterName: "cluster1", + Kind: "kind1", + Name: "name1", + SubResourceName: "sub-resource1", + }, + { + ClusterName: "cluster2", + Kind: "kind2", + Name: "name2", + SubResourceName: "sub-resource2", + }, + }, + PrivateKeyPolicy: "private-key-policy", + ConnectionDiagnosticId: "connection-diag-id", + DeviceExtensions: &decisionpb.DeviceExtensions{ + DeviceId: "device-id", + AssetTag: "asset-tag", + CredentialId: "credential-id", + }, + UserType: "user-type", + } + + tests := []struct { + name string + start, want *decisionpb.TLSIdentity + }{ + { + name: "nil-to-nil", + start: nil, + want: nil, + }, + { + name: "zero-to-zero", + start: &decisionpb.TLSIdentity{}, + want: minimalTLSIdentity, + }, + { + name: "full identity", + start: fullIdentity, + want: fullIdentity, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := decision.TLSIdentityFromTLSCA( + decision.TLSIdentityToTLSCA(test.start), + ) + if diff := cmp.Diff(test.want, got, protocmp.Transform()); diff != "" { + t.Errorf("TLSIdentity conversion mismatch (-want +got)\n%s", diff) + } + }) + } + + t.Run("zero tlsca.Identity", func(t *testing.T) { + var id tlsca.Identity + got := decision.TLSIdentityFromTLSCA(&id) + want := minimalTLSIdentity + if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" { + t.Errorf("TLSIdentity conversion mismatch (-want +got)\n%s", diff) + } + }) +} + +func TestTLSIdentityToTLSCA_zeroTimestamp(t *testing.T) { + t.Parallel() + + id := decision.TLSIdentityToTLSCA(&decisionpb.TLSIdentity{ + Expires: ×tamppb.Timestamp{}, + PreviousIdentityExpires: ×tamppb.Timestamp{}, + }) + assert.Zero(t, id.Expires, "id.Expires") + assert.Zero(t, id.PreviousIdentityExpires, "id.PreviousIdentityExpires") +} diff --git a/lib/devicetrust/authz/authz.go b/lib/devicetrust/authz/authz.go index 3bcc0ad3bf812..5f95b8af8eace 100644 --- a/lib/devicetrust/authz/authz.go +++ b/lib/devicetrust/authz/authz.go @@ -19,8 +19,10 @@ package authz import ( + "context" + "log/slog" + "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" @@ -45,8 +47,8 @@ func IsTLSDeviceVerified(ext *tlsca.DeviceExtensions) bool { // VerifyTLSUser verifies if the TLS identity has the required extensions to // fulfill the device trust configuration. -func VerifyTLSUser(dt *types.DeviceTrust, identity tlsca.Identity) error { - return verifyDeviceExtensions(dt, identity.Username, IsTLSDeviceVerified(&identity.DeviceExtensions)) +func VerifyTLSUser(ctx context.Context, dt *types.DeviceTrust, identity tlsca.Identity) error { + return verifyDeviceExtensions(ctx, dt, identity.Username, IsTLSDeviceVerified(&identity.DeviceExtensions)) } // IsSSHDeviceVerified returns true if cert contains all required device @@ -83,24 +85,22 @@ func HasDeviceTrustExtensions(extensions []string) bool { // VerifySSHUser verifies if the SSH certificate has the required extensions to // fulfill the device trust configuration. -func VerifySSHUser(dt *types.DeviceTrust, cert *ssh.Certificate) error { +func VerifySSHUser(ctx context.Context, dt *types.DeviceTrust, cert *ssh.Certificate) error { if cert == nil { return trace.BadParameter("cert required") } username := cert.KeyId - return verifyDeviceExtensions(dt, username, IsSSHDeviceVerified(cert)) + return verifyDeviceExtensions(ctx, dt, username, IsSSHDeviceVerified(cert)) } -func verifyDeviceExtensions(dt *types.DeviceTrust, username string, verified bool) error { +func verifyDeviceExtensions(ctx context.Context, dt *types.DeviceTrust, username string, verified bool) error { mode := dtconfig.GetEnforcementMode(dt) switch { case mode != constants.DeviceTrustModeRequired: return nil // OK, extensions not enforced. case !verified: - log. - WithField("User", username). - Debug("Device Trust: denied access for unidentified device") + slog.DebugContext(ctx, "Device Trust: denied access for unidentified device", "user", username) return trace.Wrap(ErrTrustedDeviceRequired) default: return nil diff --git a/lib/devicetrust/authz/authz_test.go b/lib/devicetrust/authz/authz_test.go index fa5a415508581..511a6be820d70 100644 --- a/lib/devicetrust/authz/authz_test.go +++ b/lib/devicetrust/authz/authz_test.go @@ -19,6 +19,7 @@ package authz_test import ( + "context" "testing" "github.com/gravitational/trace" @@ -131,7 +132,7 @@ func testIsDeviceVerified(t *testing.T, name string, fn func(ext *tlsca.DeviceEx func TestVerifyTLSUser(t *testing.T) { runVerifyUserTest(t, "VerifyTLSUser", func(dt *types.DeviceTrust, ext *tlsca.DeviceExtensions) error { - return authz.VerifyTLSUser(dt, tlsca.Identity{ + return authz.VerifyTLSUser(context.Background(), dt, tlsca.Identity{ Username: "llama", DeviceExtensions: *ext, }) @@ -140,7 +141,7 @@ func TestVerifyTLSUser(t *testing.T) { func TestVerifySSHUser(t *testing.T) { runVerifyUserTest(t, "VerifySSHUser", func(dt *types.DeviceTrust, ext *tlsca.DeviceExtensions) error { - return authz.VerifySSHUser(dt, &ssh.Certificate{ + return authz.VerifySSHUser(context.Background(), dt, &ssh.Certificate{ KeyId: "llama", Permissions: ssh.Permissions{ Extensions: map[string]string{ diff --git a/lib/devicetrust/enroll/enroll.go b/lib/devicetrust/enroll/enroll.go index b7e8a18c662eb..bdf3cea00c1b4 100644 --- a/lib/devicetrust/enroll/enroll.go +++ b/lib/devicetrust/enroll/enroll.go @@ -22,10 +22,10 @@ import ( "context" "errors" "io" + "log/slog" "github.com/gravitational/trace" "github.com/gravitational/trace/trail" - log "github.com/sirupsen/logrus" devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" "github.com/gravitational/teleport/lib/devicetrust" @@ -94,8 +94,7 @@ func (c *Ceremony) RunAdmin( rewordAccessDenied := func(err error, action string) error { if trace.IsAccessDenied(trail.FromGRPC(err)) { - log.WithError(err).Debug( - "Device Trust: Redacting access denied error with user-friendly message") + slog.DebugContext(ctx, "Device Trust: Redacting access denied error with user-friendly message", "error", err) return trace.AccessDenied( "User does not have permissions to %s. Contact your cluster device administrator.", action, @@ -115,9 +114,12 @@ func (c *Ceremony) RunAdmin( for _, dev := range findResp.Devices { if dev.OsType == osType { currentDev = dev - log.Debugf( - "Device Trust: Found device %q/%v, id=%q", - currentDev.AssetTag, devicetrust.FriendlyOSType(currentDev.OsType), currentDev.Id, + slog.DebugContext(ctx, "Device Trust: Found device", + slog.Group("device", + slog.String("asset_tag", currentDev.AssetTag), + slog.String("os", devicetrust.FriendlyOSType(currentDev.OsType)), + slog.String("id", currentDev.Id), + ), ) break } @@ -148,10 +150,13 @@ func (c *Ceremony) RunAdmin( if err != nil { return currentDev, outcome, trace.Wrap(rewordAccessDenied(err, "create device enrollment tokens")) } - log.Debugf( - "Device Trust: Created enrollment token for device %q/%s", - currentDev.AssetTag, - devicetrust.FriendlyOSType(currentDev.OsType)) + slog.DebugContext(ctx, "Device Trust: Created enrollment token for device", + slog.Group("device", + slog.String("asset_tag", currentDev.AssetTag), + slog.String("os", devicetrust.FriendlyOSType(currentDev.OsType)), + slog.String("id", currentDev.Id), + ), + ) } token := currentDev.EnrollToken.GetToken() diff --git a/lib/devicetrust/errors.go b/lib/devicetrust/errors.go index 4ca5e7c41107a..b2df583f57f14 100644 --- a/lib/devicetrust/errors.go +++ b/lib/devicetrust/errors.go @@ -19,11 +19,12 @@ package devicetrust import ( + "context" "errors" "io" + "log/slog" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -48,14 +49,14 @@ func HandleUnimplemented(err error) error { const notSupportedMsg = "device trust not supported by remote cluster" if errors.Is(err, io.EOF) { - log.Debug("Device Trust: interpreting EOF as an older Teleport cluster") + slog.DebugContext(context.Background(), "Device Trust: interpreting EOF as an older Teleport cluster") return trace.NotImplemented(notSupportedMsg) } for e := err; e != nil; { switch s, ok := status.FromError(e); { case ok && s.Code() == codes.Unimplemented: - log.WithError(err).Debug("Device Trust: interpreting gRPC Unimplemented as OSS or older Enterprise cluster") + slog.DebugContext(context.Background(), "Device Trust: interpreting gRPC Unimplemented as OSS or older Enterprise cluster", "error", err) return trace.NotImplemented(notSupportedMsg) case ok: return err // Unexpected status error. diff --git a/lib/devicetrust/native/api.go b/lib/devicetrust/native/api.go index 87cab17649979..ad13f1189da54 100644 --- a/lib/devicetrust/native/api.go +++ b/lib/devicetrust/native/api.go @@ -19,12 +19,13 @@ package native import ( + "context" + "log/slog" "runtime" "sync" "time" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" @@ -88,7 +89,7 @@ func readCachedDeviceDataUnderLock(mode CollectDataMode) (cdd *devicepb.DeviceCo return nil, false } - log.Debug("Device Trust: Using in-process cached device data") + slog.DebugContext(context.Background(), "Device Trust: Using in-process cached device data") cdd = proto.Clone(cachedDeviceData.value).(*devicepb.DeviceCollectedData) cdd.CollectTime = timestamppb.Now() return cdd, true diff --git a/lib/devicetrust/native/device_darwin.go b/lib/devicetrust/native/device_darwin.go index 9167b567aec9a..b3776ccd7e221 100644 --- a/lib/devicetrust/native/device_darwin.go +++ b/lib/devicetrust/native/device_darwin.go @@ -27,11 +27,13 @@ import "C" import ( "bytes" + "context" "crypto/sha256" "crypto/x509" "errors" "fmt" "io/fs" + "log/slog" "os/exec" "os/user" "strings" @@ -40,7 +42,6 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "google.golang.org/protobuf/types/known/timestamppb" devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" @@ -139,7 +140,7 @@ func collectDeviceData(_ CollectDataMode) (*devicepb.DeviceCollectedData, error) defer wg.Done() out, err := spec.fn() if err != nil { - log.WithError(err).Warnf("Device Trust: Failed to get %v", spec.desc) + slog.WarnContext(context.Background(), "Device Trust: Failed to get device details", "details", spec.desc, "error", err) return } *spec.out = out @@ -179,7 +180,7 @@ func getJamfBinaryVersion() (string, error) { // Jamf binary may not exist. This is alright. pathErr := &fs.PathError{} if errors.As(err, &pathErr) { - log.Debugf("Device Trust: Jamf binary not found: %q", pathErr.Path) + slog.DebugContext(context.Background(), "Device Trust: Jamf binary not found", "binary_path", pathErr.Path) return "", nil } diff --git a/lib/devicetrust/native/device_linux.go b/lib/devicetrust/native/device_linux.go index 63b8363c73393..b0771c1bc72b3 100644 --- a/lib/devicetrust/native/device_linux.go +++ b/lib/devicetrust/native/device_linux.go @@ -26,6 +26,7 @@ import ( "fmt" "io" "io/fs" + "log/slog" "os" "os/exec" "os/user" @@ -34,9 +35,9 @@ import ( "github.com/google/go-attestation/attest" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "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/lib/linux" ) @@ -104,9 +105,10 @@ func rewriteTPMPermissionError(err error) error { if !errors.As(err, &pathErr) || pathErr.Path != "/dev/tpmrm0" { return err } - log. - WithError(err). - Debug("TPM: Replacing TPM permission error with a more friendly one") + slog.DebugContext(context.Background(), "Replacing TPM permission error with a more friendly one", + teleport.ComponentKey, "TPM", + "error", err, + ) return errors.New("" + "Failed to open the TPM device. " + @@ -141,7 +143,10 @@ func collectDeviceData(mode CollectDataMode) (*devicepb.DeviceCollectedData, err go func() { osRelease, err := cddFuncs.parseOSRelease() if err != nil { - log.WithError(err).Debug("TPM: Failed to parse /etc/os-release file") + slog.DebugContext(context.Background(), "Failed to parse /etc/os-release file", + teleport.ComponentKey, "TPM", + "error", err, + ) // err swallowed on purpose. osRelease = &linux.OSRelease{} @@ -187,26 +192,29 @@ func collectDeviceData(mode CollectDataMode) (*devicepb.DeviceCollectedData, err } func readDMIInfoAccordingToMode(mode CollectDataMode) (*linux.DMIInfo, error) { + ctx := context.Background() + logger := slog.With(teleport.ComponentKey, "TPM") + dmiInfo, err := cddFuncs.dmiInfoFromSysfs() if err == nil { return dmiInfo, nil } - log.WithError(err).Warn("TPM: Failed to read device model and/or serial numbers") + logger.WarnContext(ctx, "Failed to read device model and/or serial numbers", "error", err) if !errors.Is(err, fs.ErrPermission) { return dmiInfo, nil // original info } switch mode { case CollectedDataNeverEscalate, CollectedDataMaybeEscalate: - log.Debug("TPM: Reading cached DMI info") + logger.DebugContext(ctx, "Reading cached DMI info") dmiCached, err := cddFuncs.readDMIInfoCached() if err == nil { return dmiCached, nil // successful cache hit } - log.WithError(err).Debug("TPM: Failed to read cached DMI info") + logger.DebugContext(ctx, "Failed to read cached DMI info", "error", err) if mode == CollectedDataNeverEscalate { return dmiInfo, nil // original info } @@ -214,7 +222,7 @@ func readDMIInfoAccordingToMode(mode CollectDataMode) (*linux.DMIInfo, error) { fallthrough case CollectedDataAlwaysEscalate: - log.Debug("TPM: Running escalated `tsh device dmi-info`") + logger.DebugContext(ctx, "Running escalated `tsh device dmi-info`") dmiInfo, err = cddFuncs.readDMIInfoEscalated() if err != nil { @@ -222,7 +230,7 @@ func readDMIInfoAccordingToMode(mode CollectDataMode) (*linux.DMIInfo, error) { } if err := cddFuncs.saveDMIInfoToCache(dmiInfo); err != nil { - log.WithError(err).Warn("TPM: Failed to write DMI cache") + logger.WarnContext(ctx, "Failed to write DMI cache", "error", err) // err swallowed on purpose. } } @@ -250,9 +258,7 @@ func readDMIInfoCached() (*linux.DMIInfo, error) { return nil, trace.Wrap(err) } if dec.More() { - log. - WithField("Path", path). - Warn("DMI cache file contains multiple JSON entries, only one expected") + slog.WarnContext(context.Background(), "DMI cache file contains multiple JSON entries, only one expected", "path", path) // Warn but keep going. } @@ -320,7 +326,7 @@ func saveDMIInfoToCache(dmiInfo *linux.DMIInfo) error { if err := f.Close(); err != nil { return trace.Wrap(err, "closing dmi.json after write") } - log.Debug("TPM: Saved DMI information to local cache") + slog.DebugContext(context.Background(), "Saved DMI information to local cache", teleport.ComponentKey, "TPM") return nil } diff --git a/lib/devicetrust/native/device_linux_test.go b/lib/devicetrust/native/device_linux_test.go index 6f1f545437ccb..7848c6a05767b 100644 --- a/lib/devicetrust/native/device_linux_test.go +++ b/lib/devicetrust/native/device_linux_test.go @@ -28,7 +28,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" @@ -39,9 +38,6 @@ import ( ) func TestCollectDeviceData_linux(t *testing.T) { - // Silence logging for tests. - log.SetLevel(log.PanicLevel) - // Do not cache data during testing. skipCacheBefore := cachedDeviceData.skipCache cachedDeviceData.skipCache = true diff --git a/lib/devicetrust/native/device_windows.go b/lib/devicetrust/native/device_windows.go index 036f1e48e514d..575e238af5623 100644 --- a/lib/devicetrust/native/device_windows.go +++ b/lib/devicetrust/native/device_windows.go @@ -19,17 +19,20 @@ package native import ( - "bytes" + "context" "encoding/base64" "errors" + "fmt" + "log/slog" "os" - "os/exec" "os/user" + "strconv" "time" "github.com/google/go-attestation/attest" + "github.com/gravitational/teleport" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" + "github.com/yusufpapurcu/wmi" "golang.org/x/sync/errgroup" "golang.org/x/sys/windows" "google.golang.org/protobuf/types/known/timestamppb" @@ -78,125 +81,110 @@ func handleTPMActivateCredential(encryptedCredential, encryptedCredentialSecret return windowsDevice.handleTPMActivateCredential(encryptedCredential, encryptedCredentialSecret) } -// getDeviceSerial returns the serial number of the device using PowerShell to -// grab the correct WMI objects. Getting it without calling into PS is possible, -// but requires interfacing with the ancient Win32 COM APIs. func getDeviceSerial() (string, error) { - cmd := exec.Command( - "powershell", - "-NoProfile", - "Get-WmiObject Win32_BIOS | Select -ExpandProperty SerialNumber", - ) // ThinkPad P P14s: // PS > Get-WmiObject Win32_BIOS | Select -ExpandProperty SerialNumber // PF47WND6 - out, err := cmd.Output() - if err != nil { + + type Win32_BIOS struct { + SerialNumber string + } + + var bios []Win32_BIOS + query := wmi.CreateQuery(&bios, "") + if err := wmi.Query(query, &bios); err != nil { return "", trace.Wrap(err) } - return string(bytes.TrimSpace(out)), nil + + if len(bios) == 0 { + return "", trace.BadParameter("could not read serial number from Win32_BIOS") + } + + return bios[0].SerialNumber, nil } func getReportedAssetTag() (string, error) { - cmd := exec.Command( - "powershell", - "-NoProfile", - "Get-WmiObject Win32_SystemEnclosure | Select -ExpandProperty SMBIOSAssetTag", - ) // ThinkPad P P14s: // PS > Get-WmiObject Win32_SystemEnclosure | Select -ExpandProperty SMBIOSAssetTag // winaia_1337 - out, err := cmd.Output() - if err != nil { + + type Win32_SystemEnclosure struct { + SMBIOSAssetTag string + } + + var system []Win32_SystemEnclosure + query := wmi.CreateQuery(&system, "") + if err := wmi.Query(query, &system); err != nil { return "", trace.Wrap(err) } - return string(bytes.TrimSpace(out)), nil + + if len(system) == 0 { + return "", trace.BadParameter("could not read asset tag from Win32_SystemEnclosure") + } + + return system[0].SMBIOSAssetTag, nil } func getDeviceModel() (string, error) { - cmd := exec.Command( - "powershell", - "-NoProfile", - "Get-WmiObject Win32_ComputerSystem | Select -ExpandProperty Model", - ) // ThinkPad P P14s: // PS> Get-WmiObject Win32_ComputerSystem | Select -ExpandProperty Model // 21J50013US - out, err := cmd.Output() - if err != nil { + + type Win32_ComputerSystem struct { + Model string + } + var cs []Win32_ComputerSystem + query := wmi.CreateQuery(&cs, "") + if err := wmi.Query(query, &cs); err != nil { return "", trace.Wrap(err) } - return string(bytes.TrimSpace(out)), nil + + if len(cs) == 0 { + return "", trace.BadParameter("could not read model from Win32_ComputerSystem") + } + + return cs[0].Model, nil } func getDeviceBaseBoardSerial() (string, error) { - cmd := exec.Command( - "powershell", - "-NoProfile", - "Get-WmiObject Win32_BaseBoard | Select -ExpandProperty SerialNumber", - ) // ThinkPad P P14s: // PS> Get-WmiObject Win32_BaseBoard | Select -ExpandProperty SerialNumber // L1HF2CM03ZT - out, err := cmd.Output() - if err != nil { - return "", trace.Wrap(err) - } - - return string(bytes.TrimSpace(out)), nil -} -func getOSVersion() (string, error) { - cmd := exec.Command( - "powershell", - "-NoProfile", - "Get-WmiObject Win32_OperatingSystem | Select -ExpandProperty Version", - ) - // ThinkPad P P14s: - // PS> Get-WmiObject Win32_OperatingSystem | Select -ExpandProperty Version - // 10.0.22621 - out, err := cmd.Output() - if err != nil { + type Win32_BaseBoard struct { + SerialNumber string + } + var bb []Win32_BaseBoard + query := wmi.CreateQuery(&bb, "") + if err := wmi.Query(query, &bb); err != nil { return "", trace.Wrap(err) } - return string(bytes.TrimSpace(out)), nil -} - -func getOSBuildNumber() (string, error) { - cmd := exec.Command( - "powershell", - "-NoProfile", - "Get-WmiObject Win32_OperatingSystem | Select -ExpandProperty BuildNumber", - ) - // ThinkPad P P14s: - // PS> Get-WmiObject Win32_OperatingSystem | Select -ExpandProperty BuildNumber - // 22621 - out, err := cmd.Output() - if err != nil { - return "", trace.Wrap(err) + if len(bb) == 0 { + return "", trace.BadParameter("could not read serial from Win32_BaseBoard") } - return string(bytes.TrimSpace(out)), nil + return bb[0].SerialNumber, nil } func collectDeviceData(_ CollectDataMode) (*devicepb.DeviceCollectedData, error) { - log.Debug("TPM: Collecting device data.") + ctx := context.Background() + logger := slog.With(teleport.ComponentKey, "TPM") + + logger.DebugContext(ctx, "Collecting device data") var g errgroup.Group const groupLimit = 4 // arbitrary g.SetLimit(groupLimit) // Run exec-ed commands concurrently. - var systemSerial, baseBoardSerial, reportedAssetTag, model, osVersion, osBuildNumber string + var systemSerial, baseBoardSerial, reportedAssetTag, model string for _, spec := range []struct { fn func() (string, error) out *string desc string }{ {fn: getDeviceModel, out: &model, desc: "device model"}, - {fn: getOSVersion, out: &osVersion, desc: "os version"}, - {fn: getOSBuildNumber, out: &osBuildNumber, desc: "os build number"}, {fn: getDeviceSerial, out: &systemSerial, desc: "system serial"}, {fn: getDeviceBaseBoardSerial, out: &baseBoardSerial, desc: "base board serial"}, {fn: getReportedAssetTag, out: &reportedAssetTag, desc: "reported asset tag"}, @@ -205,7 +193,7 @@ func collectDeviceData(_ CollectDataMode) (*devicepb.DeviceCollectedData, error) g.Go(func() error { val, err := spec.fn() if err != nil { - log.WithError(err).Debugf("TPM: Failed to fetch %v", spec.desc) + logger.DebugContext(ctx, "Failed to fetch device details", "details", spec.desc, "error", err) return nil // Swallowed on purpose. } @@ -214,6 +202,8 @@ func collectDeviceData(_ CollectDataMode) (*devicepb.DeviceCollectedData, error) }) } + ver := windows.RtlGetVersion() + // We want to fetch as much info as possible, so errors are ignored. _ = g.Wait() @@ -232,16 +222,14 @@ func collectDeviceData(_ CollectDataMode) (*devicepb.DeviceCollectedData, error) OsType: devicepb.OSType_OS_TYPE_WINDOWS, SerialNumber: serial, ModelIdentifier: model, - OsVersion: osVersion, - OsBuild: osBuildNumber, + OsVersion: fmt.Sprintf("%v.%v.%v", ver.MajorVersion, ver.MinorVersion, ver.BuildNumber), + OsBuild: strconv.FormatInt(int64(ver.BuildNumber), 10), OsUsername: u.Username, SystemSerialNumber: systemSerial, BaseBoardSerialNumber: baseBoardSerial, ReportedAssetTag: reportedAssetTag, } - log.WithField( - "device_collected_data", dcd, - ).Debug("TPM: Device data collected.") + logger.DebugContext(ctx, "Device data collected", "device_collected_data", dcd) return dcd, nil } @@ -285,7 +273,7 @@ func activateCredentialInElevatedChild( params = append(params, "--debug") } - log.Debug("Starting elevated process.") + slog.DebugContext(context.Background(), "Starting elevated process.") // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew err = windowsexec.RunAsAndWait( exe, @@ -301,7 +289,7 @@ func activateCredentialInElevatedChild( // it. defer func() { if err := os.Remove(credActivationPath); err != nil { - log.WithError(err).Debug("Failed to clean up credential activation result") + slog.DebugContext(context.Background(), "Failed to clean up credential activation result", "error", err) } }() diff --git a/lib/devicetrust/native/tpm_device.go b/lib/devicetrust/native/tpm_device.go index 112647f456c90..cbfbc1b51012e 100644 --- a/lib/devicetrust/native/tpm_device.go +++ b/lib/devicetrust/native/tpm_device.go @@ -21,18 +21,20 @@ package native import ( + "context" "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/base64" "fmt" + "log/slog" "math/big" "os" "github.com/google/go-attestation/attest" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" + "github.com/gravitational/teleport" devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" "github.com/gravitational/teleport/lib/devicetrust" ) @@ -121,6 +123,8 @@ func createAndSaveAK( } func (d *tpmDevice) enrollDeviceInit() (*devicepb.EnrollDeviceInit, error) { + ctx := context.Background() + logger := slog.With(teleport.ComponentKey, "TPM") stateDir, err := setupDeviceStateDir(userDirFunc) if err != nil { return nil, trace.Wrap(err, "setting up device state directory") @@ -134,7 +138,7 @@ func (d *tpmDevice) enrollDeviceInit() (*devicepb.EnrollDeviceInit, error) { } defer func() { if err := tpm.Close(); err != nil { - log.WithError(err).Debug("TPM: Failed to close TPM.") + logger.DebugContext(ctx, "Failed to close TPM", "error", err) } }() @@ -145,13 +149,13 @@ func (d *tpmDevice) enrollDeviceInit() (*devicepb.EnrollDeviceInit, error) { if !trace.IsNotFound(err) { return nil, trace.Wrap(err, "loading ak") } - log.Debug("TPM: No existing AK was found on disk, an AK will be created.") + logger.DebugContext(ctx, "No existing AK was found on disk, an AK will be created") ak, err = createAndSaveAK(tpm, stateDir.attestationKeyPath) if err != nil { return nil, trace.Wrap(err, "creating ak") } } else { - log.Debug("TPM: Existing AK was found on disk, it will be reused.") + logger.DebugContext(ctx, "Existing AK was found on disk, it will be reused") } defer ak.Close(tpm) @@ -245,7 +249,10 @@ func (d *tpmDevice) getDeviceCredential() (*devicepb.DeviceCredential, error) { } defer func() { if err := tpm.Close(); err != nil { - log.WithError(err).Debug("TPM: Failed to close TPM.") + slog.DebugContext(context.Background(), "Failed to close TPM", + teleport.ComponentKey, "TPM", + "error", err, + ) } }() @@ -270,6 +277,9 @@ func (d *tpmDevice) solveTPMEnrollChallenge( challenge *devicepb.TPMEnrollChallenge, debug bool, ) (*devicepb.TPMEnrollChallengeResponse, error) { + ctx := context.Background() + logger := slog.With(teleport.ComponentKey, "TPM") + stateDir, err := setupDeviceStateDir(userDirFunc) if err != nil { return nil, trace.Wrap(err, "setting up device state directory") @@ -283,7 +293,7 @@ func (d *tpmDevice) solveTPMEnrollChallenge( } defer func() { if err := tpm.Close(); err != nil { - log.WithError(err).Debug("TPM: Failed to close TPM.") + logger.DebugContext(ctx, "Failed to close TPM", "error", err) } }() @@ -302,7 +312,7 @@ func (d *tpmDevice) solveTPMEnrollChallenge( // First perform the credential activation challenge provided by the // auth server. - log.Debug("TPM: Activating credential.") + logger.DebugContext(ctx, "Activating credential") encryptedCredential := devicetrust.EncryptedCredentialFromProto( challenge.EncryptedCredential, ) @@ -318,7 +328,7 @@ func (d *tpmDevice) solveTPMEnrollChallenge( var activationSolution []byte if elevated { - log.Debug("TPM: Detected current process is elevated. Will run credential activation in current process.") + logger.DebugContext(ctx, "Detected current process is elevated. Will run credential activation in current process") // If we are running with elevated privileges, we can just complete the // credential activation here. activationSolution, err = ak.ActivateCredential( @@ -341,7 +351,7 @@ func (d *tpmDevice) solveTPMEnrollChallenge( fmt.Fprintln(os.Stderr, "Successfully completed credential activation in elevated process.") } - log.Debug("TPM: Enrollment challenge completed.") + logger.DebugContext(ctx, "Enrollment challenge completed.") return &devicepb.TPMEnrollChallengeResponse{ Solution: activationSolution, PlatformParameters: devicetrust.PlatformParametersToProto( @@ -352,7 +362,10 @@ func (d *tpmDevice) solveTPMEnrollChallenge( //nolint:unused // Used by Windows builds. func (d *tpmDevice) handleTPMActivateCredential(encryptedCredential, encryptedCredentialSecret string) error { - log.Debug("Performing credential activation.") + ctx := context.Background() + logger := slog.With(teleport.ComponentKey, "TPM") + + logger.DebugContext(ctx, "Performing credential activation") // The two input parameters are base64 encoded, so decode them. credentialBytes, err := base64.StdEncoding.DecodeString(encryptedCredential) if err != nil { @@ -376,7 +389,7 @@ func (d *tpmDevice) handleTPMActivateCredential(encryptedCredential, encryptedCr } defer func() { if err := tpm.Close(); err != nil { - log.WithError(err).Debug("TPM: Failed to close TPM.") + logger.DebugContext(ctx, "Failed to close TPM", "error", err) } }() @@ -398,7 +411,7 @@ func (d *tpmDevice) handleTPMActivateCredential(encryptedCredential, encryptedCr return trace.Wrap(err, "activating credential with challenge") } - log.Debug("Completed credential activation. Returning result to original process.") + logger.DebugContext(ctx, "Completed credential activation, returning result to original process") return trace.Wrap( os.WriteFile(stateDir.credentialActivationPath, solution, 0600), ) @@ -407,6 +420,9 @@ func (d *tpmDevice) handleTPMActivateCredential(encryptedCredential, encryptedCr func (d *tpmDevice) solveTPMAuthnDeviceChallenge( challenge *devicepb.TPMAuthenticateDeviceChallenge, ) (*devicepb.TPMAuthenticateDeviceChallengeResponse, error) { + ctx := context.Background() + logger := slog.With(teleport.ComponentKey, "TPM") + stateDir, err := setupDeviceStateDir(userDirFunc) if err != nil { return nil, trace.Wrap(err, "setting up device state directory") @@ -420,7 +436,7 @@ func (d *tpmDevice) solveTPMAuthnDeviceChallenge( } defer func() { if err := tpm.Close(); err != nil { - log.WithError(err).Debug("TPM: Failed to close TPM") + logger.DebugContext(ctx, "Failed to close TPM", "error", err) } }() @@ -437,7 +453,7 @@ func (d *tpmDevice) solveTPMAuthnDeviceChallenge( return nil, trace.Wrap(err) } - log.Debug("TPM: Authenticate device challenge completed.") + logger.DebugContext(ctx, "Authenticate device challenge completed") return &devicepb.TPMAuthenticateDeviceChallengeResponse{ PlatformParameters: devicetrust.PlatformParametersToProto( platformsParams, @@ -446,9 +462,12 @@ func (d *tpmDevice) solveTPMAuthnDeviceChallenge( } func attestPlatform(tpm *attest.TPM, ak *attest.AK, nonce []byte) (*attest.PlatformParameters, error) { + ctx := context.Background() + logger := slog.With(teleport.ComponentKey, "TPM") + config := &attest.PlatformAttestConfig{} - log.Debug("TPM: Performing platform attestation.") + logger.DebugContext(ctx, "Performing platform attestation") platformsParams, err := tpm.AttestPlatform(ak, nonce, config) if err == nil { return platformsParams, nil @@ -458,9 +477,7 @@ func attestPlatform(tpm *attest.TPM, ak *attest.AK, nonce []byte) (*attest.Platf // errors.Is(err, fs.ErrPermission), but the go-attestation version at time of // writing (v0.5.0) doesn't wrap the underlying error. // This is a common occurrence for Linux devices. - log. - WithError(err). - Debug("TPM: Platform attestation failed with permission error, attempting without event log") + logger.DebugContext(ctx, "Platform attestation failed with permission error, attempting without event log", "error", err) config.EventLog = []byte{} platformsParams, err = tpm.AttestPlatform(ak, nonce, config) return platformsParams, trace.Wrap(err, "attesting platform") diff --git a/lib/events/auditlog.go b/lib/events/auditlog.go index 51180746cbe7f..3570171f40996 100644 --- a/lib/events/auditlog.go +++ b/lib/events/auditlog.go @@ -555,6 +555,7 @@ func (l *AuditLog) StreamSessionEvents(ctx context.Context, sessionID session.ID } protoReader := NewProtoReader(rawSession) + defer protoReader.Close() for { if ctx.Err() != nil { diff --git a/lib/httplib/httplib.go b/lib/httplib/httplib.go index 98775ec69040c..f241f6d36ddb8 100644 --- a/lib/httplib/httplib.go +++ b/lib/httplib/httplib.go @@ -41,7 +41,6 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/observability/tracing" tracehttp "github.com/gravitational/teleport/api/observability/tracing/http" - "github.com/gravitational/teleport/lib/httplib/csrf" "github.com/gravitational/teleport/lib/utils" ) @@ -155,23 +154,6 @@ func MakeStdHandlerWithErrorWriter(fn StdHandlerFunc, errWriter ErrorWriter) htt } } -// WithCSRFProtection ensures that request to unauthenticated API is checked against CSRF attacks -func WithCSRFProtection(fn HandlerFunc) httprouter.Handle { - handlerFn := MakeHandler(fn) - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - if r.Method != http.MethodGet && r.Method != http.MethodHead { - errHeader := csrf.VerifyHTTPHeader(r) - errForm := csrf.VerifyFormField(r) - if errForm != nil && errHeader != nil { - slog.WarnContext(r.Context(), "unable to validate CSRF token", "header_error", errHeader, "form_error", errForm) - trace.WriteError(w, trace.AccessDenied("access denied")) - return - } - } - handlerFn(w, r, p) - } -} - // ReadJSON reads HTTP json request and unmarshals it // into passed any obj. A reasonable maximum size is enforced // to mitigate resource exhaustion attacks. @@ -188,6 +170,7 @@ func ReadResourceJSON(r *http.Request, val any) error { func readJSON(r *http.Request, val any, maxSize int64) error { // Check content type to mitigate CSRF attack. + // (Form POST requests don't support application/json payloads.) contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) if err != nil { slog.WarnContext(r.Context(), "Error parsing media type for reading JSON", "error", err) diff --git a/lib/integrations/awsoidc/credprovider/credentialscache_test.go b/lib/integrations/awsoidc/credprovider/credentialscache_test.go index 6384bed0b8db0..03fe165bbaf10 100644 --- a/lib/integrations/awsoidc/credprovider/credentialscache_test.go +++ b/lib/integrations/awsoidc/credprovider/credentialscache_test.go @@ -29,7 +29,6 @@ import ( ststypes "github.com/aws/aws-sdk-go-v2/service/sts/types" "github.com/google/uuid" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -75,8 +74,6 @@ func (f *fakeSTSClient) AssumeRoleWithWebIdentity(ctx context.Context, params *s } func TestCredentialsCache(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) diff --git a/lib/integrations/awsoidc/deployservice.go b/lib/integrations/awsoidc/deployservice.go index b9fbc4b99c458..17bfe3a470954 100644 --- a/lib/integrations/awsoidc/deployservice.go +++ b/lib/integrations/awsoidc/deployservice.go @@ -34,6 +34,7 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" + apiaws "github.com/gravitational/teleport/api/utils/aws" "github.com/gravitational/teleport/api/utils/retryutils" "github.com/gravitational/teleport/lib/integrations/awsoidc/tags" "github.com/gravitational/teleport/lib/utils/teleportassets" @@ -445,16 +446,24 @@ func DeployService(ctx context.Context, clt DeployServiceClient, req DeployServi return nil, trace.Wrap(err) } - serviceDashboardURL := fmt.Sprintf("https://%s.console.aws.amazon.com/ecs/v2/clusters/%s/services/%s", req.Region, aws.ToString(req.ClusterName), aws.ToString(req.ServiceName)) - return &DeployServiceResponse{ ClusterARN: aws.ToString(cluster.ClusterArn), ServiceARN: aws.ToString(service.ServiceArn), TaskDefinitionARN: taskDefinitionARN, - ServiceDashboardURL: serviceDashboardURL, + ServiceDashboardURL: serviceDashboardURL(req.Region, aws.ToString(req.ClusterName), aws.ToString(service.ServiceName)), }, nil } +// serviceDashboardURL builds the ECS Service dashboard URL using the AWS Region, the ECS Cluster and Service Names. +// It returns an empty string if region is not valid. +func serviceDashboardURL(region, clusterName, serviceName string) string { + if err := apiaws.IsValidRegion(region); err != nil { + return "" + } + + return fmt.Sprintf("https://%s.console.aws.amazon.com/ecs/v2/clusters/%s/services/%s", region, clusterName, serviceName) +} + type upsertTaskRequest struct { TaskName string TaskRoleARN string diff --git a/lib/integrations/awsoidc/deployserviceconfig/deployservice_config.go b/lib/integrations/awsoidc/deployserviceconfig/deployservice_config.go index 1f2624b94d6c7..941ba7681f7c0 100644 --- a/lib/integrations/awsoidc/deployserviceconfig/deployservice_config.go +++ b/lib/integrations/awsoidc/deployserviceconfig/deployservice_config.go @@ -89,3 +89,26 @@ func GenerateTeleportConfigString(proxyHostPort, iamTokenName string, resourceMa return teleportConfigString, nil } + +// ParseResourceLabelMatchers receives a teleport config string and returns the Resource Matcher Label. +// The expected input is a base64 encoded yaml string containing a teleport configuration, +// the same format that GenerateTeleportConfigString returns. +func ParseResourceLabelMatchers(teleportConfigStringBase64 string) (types.Labels, error) { + teleportConfigString, err := base64.StdEncoding.DecodeString(teleportConfigStringBase64) + if err != nil { + return nil, trace.BadParameter("invalid base64 value, error=%v", err) + } + + var teleportConfig config.FileConfig + if err := yaml.Unmarshal(teleportConfigString, &teleportConfig); err != nil { + return nil, trace.BadParameter("invalid teleport config, error=%v", err) + } + + if len(teleportConfig.Databases.ResourceMatchers) == 0 { + return nil, trace.BadParameter("valid yaml configuration but db_service.resources has 0 items") + } + + resourceMatchers := teleportConfig.Databases.ResourceMatchers[0] + + return resourceMatchers.Labels, nil +} diff --git a/lib/integrations/awsoidc/deployserviceconfig/deployservice_config_test.go b/lib/integrations/awsoidc/deployserviceconfig/deployservice_config_test.go index 1f47d96e2dac4..3b40912ac9160 100644 --- a/lib/integrations/awsoidc/deployserviceconfig/deployservice_config_test.go +++ b/lib/integrations/awsoidc/deployserviceconfig/deployservice_config_test.go @@ -23,8 +23,10 @@ import ( "testing" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/utils" ) func TestDeployServiceConfig(t *testing.T) { @@ -39,3 +41,45 @@ func TestDeployServiceConfig(t *testing.T) { require.Contains(t, base64Config, base64SeverityDebug) }) } + +func TestParseResourceLabelMatchers(t *testing.T) { + labels := types.Labels{ + "vpc": utils.Strings{"vpc-1", "vpc-2"}, + "region": utils.Strings{"us-west-2"}, + "xyz": utils.Strings{}, + } + base64Config, err := GenerateTeleportConfigString("host:port", "iam-token", labels) + require.NoError(t, err) + + t.Run("recover matching labels", func(t *testing.T) { + gotLabels, err := ParseResourceLabelMatchers(base64Config) + require.NoError(t, err) + + require.Equal(t, labels, gotLabels) + }) + + t.Run("fails if invalid base64 string", func(t *testing.T) { + _, err := ParseResourceLabelMatchers("invalid base 64") + require.ErrorContains(t, err, "base64") + }) + + t.Run("invalid yaml", func(t *testing.T) { + input := base64.StdEncoding.EncodeToString([]byte("invalid yaml")) + _, err := ParseResourceLabelMatchers(input) + require.ErrorContains(t, err, "yaml") + }) + + t.Run("valid yaml but not a teleport config", func(t *testing.T) { + yamlInput := struct { + DBService string `yaml:"db_service"` + }{ + DBService: "not a valid teleport config", + } + yamlBS, err := yaml.Marshal(yamlInput) + require.NoError(t, err) + input := base64.StdEncoding.EncodeToString(yamlBS) + + _, err = ParseResourceLabelMatchers(input) + require.ErrorContains(t, err, "invalid teleport config") + }) +} diff --git a/lib/integrations/awsoidc/listdeployeddatabaseservice.go b/lib/integrations/awsoidc/listdeployeddatabaseservice.go new file mode 100644 index 0000000000000..c2894902f78fe --- /dev/null +++ b/lib/integrations/awsoidc/listdeployeddatabaseservice.go @@ -0,0 +1,194 @@ +/* + * 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 awsoidc + +import ( + "context" + "log/slog" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ecs" + ecstypes "github.com/aws/aws-sdk-go-v2/service/ecs/types" + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/lib/integrations/awsoidc/tags" +) + +// ListDeployedDatabaseServicesRequest contains the required fields to list the deployed database services in Amazon ECS. +type ListDeployedDatabaseServicesRequest struct { + // Region is the AWS Region. + Region string + // Integration is the AWS OIDC Integration name + Integration string + // TeleportClusterName is the name of the Teleport Cluster. + // Used to uniquely identify the ECS Cluster in Amazon. + TeleportClusterName string + // NextToken is the token to be used to fetch the next page. + // If empty, the first page is fetched. + NextToken string +} + +func (req *ListDeployedDatabaseServicesRequest) checkAndSetDefaults() error { + if req.Region == "" { + return trace.BadParameter("region is required") + } + + if req.Integration == "" { + return trace.BadParameter("integration is required") + } + + if req.TeleportClusterName == "" { + return trace.BadParameter("teleport cluster name is required") + } + + return nil +} + +// ListDeployedDatabaseServicesResponse contains a page of Deployed Database Services. +type ListDeployedDatabaseServicesResponse struct { + // DeployedDatabaseServices contains the page of Deployed Database Services. + DeployedDatabaseServices []DeployedDatabaseService `json:"deployedDatabaseServices"` + + // NextToken is used for pagination. + // If non-empty, it can be used to request the next page. + NextToken string `json:"nextToken"` +} + +// DeployedDatabaseService contains a database service that was deployed to Amazon ECS. +type DeployedDatabaseService struct { + // Name is the ECS Service name. + Name string + // ServiceDashboardURL is the Amazon Web Console URL for this ECS Service. + ServiceDashboardURL string + // ContainerEntryPoint is the entry point for the container 0 that is running in the ECS Task. + ContainerEntryPoint []string + // ContainerCommand is the list of arguments that are passed into the ContainerEntryPoint. + ContainerCommand []string +} + +// ListDeployedDatabaseServicesClient describes the required methods to list AWS VPCs. +type ListDeployedDatabaseServicesClient interface { + // ListServices returns a list of services. + ListServices(ctx context.Context, params *ecs.ListServicesInput, optFns ...func(*ecs.Options)) (*ecs.ListServicesOutput, error) + // DescribeServices returns ECS Services details. + DescribeServices(ctx context.Context, params *ecs.DescribeServicesInput, optFns ...func(*ecs.Options)) (*ecs.DescribeServicesOutput, error) + // DescribeTaskDefinition returns an ECS Task Definition. + DescribeTaskDefinition(ctx context.Context, params *ecs.DescribeTaskDefinitionInput, optFns ...func(*ecs.Options)) (*ecs.DescribeTaskDefinitionOutput, error) +} + +type defaultListDeployedDatabaseServicesClient struct { + *ecs.Client +} + +// NewListDeployedDatabaseServicesClient creates a new ListDeployedDatabaseServicesClient using an AWSClientRequest. +func NewListDeployedDatabaseServicesClient(ctx context.Context, req *AWSClientRequest) (ListDeployedDatabaseServicesClient, error) { + ecsClient, err := newECSClient(ctx, req) + if err != nil { + return nil, trace.Wrap(err) + } + + return &defaultListDeployedDatabaseServicesClient{ + Client: ecsClient, + }, nil +} + +// ListDeployedDatabaseServices calls the following AWS API: +// https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ListServices.html +// https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DescribeServices.html +// https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DescribeTaskDefinition.html +// It returns a list of ECS Services running Teleport Database Service and an optional NextToken that can be used to fetch the next page. +func ListDeployedDatabaseServices(ctx context.Context, clt ListDeployedDatabaseServicesClient, req ListDeployedDatabaseServicesRequest) (*ListDeployedDatabaseServicesResponse, error) { + if err := req.checkAndSetDefaults(); err != nil { + return nil, trace.Wrap(err) + } + + clusterName := normalizeECSClusterName(req.TeleportClusterName) + + log := slog.With( + "integration", req.Integration, + "aws_region", req.Region, + "ecs_cluster", clusterName, + ) + + // Do not increase this value because ecs.DescribeServices only allows up to 10 services per API call. + maxServicesPerPage := aws.Int32(10) + listServicesInput := &ecs.ListServicesInput{ + Cluster: &clusterName, + MaxResults: maxServicesPerPage, + LaunchType: ecstypes.LaunchTypeFargate, + } + if req.NextToken != "" { + listServicesInput.NextToken = &req.NextToken + } + + listServicesOutput, err := clt.ListServices(ctx, listServicesInput) + if err != nil { + return nil, trace.Wrap(err) + } + + describeServicesOutput, err := clt.DescribeServices(ctx, &ecs.DescribeServicesInput{ + Services: listServicesOutput.ServiceArns, + Include: []ecstypes.ServiceField{ecstypes.ServiceFieldTags}, + Cluster: &clusterName, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + ownershipTags := tags.DefaultResourceCreationTags(req.TeleportClusterName, req.Integration) + + deployedDatabaseServices := []DeployedDatabaseService{} + for _, ecsService := range describeServicesOutput.Services { + log := log.With("ecs_service", aws.ToString(ecsService.ServiceName)) + if !ownershipTags.MatchesECSTags(ecsService.Tags) { + log.WarnContext(ctx, "Missing ownership tags in ECS Service, skipping") + continue + } + + taskDefinitionOut, err := clt.DescribeTaskDefinition(ctx, &ecs.DescribeTaskDefinitionInput{ + TaskDefinition: ecsService.TaskDefinition, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + if len(taskDefinitionOut.TaskDefinition.ContainerDefinitions) == 0 { + log.WarnContext(ctx, "Task has no containers defined, skipping", + "ecs_task_family", aws.ToString(taskDefinitionOut.TaskDefinition.Family), + "ecs_task_revision", taskDefinitionOut.TaskDefinition.Revision, + ) + continue + } + + entryPoint := taskDefinitionOut.TaskDefinition.ContainerDefinitions[0].EntryPoint + command := taskDefinitionOut.TaskDefinition.ContainerDefinitions[0].Command + + deployedDatabaseServices = append(deployedDatabaseServices, DeployedDatabaseService{ + Name: aws.ToString(ecsService.ServiceName), + ServiceDashboardURL: serviceDashboardURL(req.Region, clusterName, aws.ToString(ecsService.ServiceName)), + ContainerEntryPoint: entryPoint, + ContainerCommand: command, + }) + } + + return &ListDeployedDatabaseServicesResponse{ + DeployedDatabaseServices: deployedDatabaseServices, + NextToken: aws.ToString(listServicesOutput.NextToken), + }, nil +} diff --git a/lib/integrations/awsoidc/listdeployeddatabaseservice_test.go b/lib/integrations/awsoidc/listdeployeddatabaseservice_test.go new file mode 100644 index 0000000000000..67f332d495c2b --- /dev/null +++ b/lib/integrations/awsoidc/listdeployeddatabaseservice_test.go @@ -0,0 +1,360 @@ +/* + * 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 awsoidc + +import ( + "context" + "fmt" + "strconv" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ecs" + ecstypes "github.com/aws/aws-sdk-go-v2/service/ecs/types" + "github.com/google/go-cmp/cmp" + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" +) + +func TestListDeployedDatabaseServicesRequest(t *testing.T) { + isBadParamErrFn := func(tt require.TestingT, err error, i ...any) { + require.True(tt, trace.IsBadParameter(err), "expected bad parameter, got %v", err) + } + + baseReqFn := func() ListDeployedDatabaseServicesRequest { + return ListDeployedDatabaseServicesRequest{ + TeleportClusterName: "mycluster", + Region: "eu-west-2", + Integration: "my-integration", + } + } + + for _, tt := range []struct { + name string + req func() ListDeployedDatabaseServicesRequest + errCheck require.ErrorAssertionFunc + reqWithDefaults ListDeployedDatabaseServicesRequest + }{ + { + name: "no fields", + req: func() ListDeployedDatabaseServicesRequest { + return ListDeployedDatabaseServicesRequest{} + }, + errCheck: isBadParamErrFn, + }, + { + name: "missing teleport cluster name", + req: func() ListDeployedDatabaseServicesRequest { + r := baseReqFn() + r.TeleportClusterName = "" + return r + }, + errCheck: isBadParamErrFn, + }, + { + name: "missing region", + req: func() ListDeployedDatabaseServicesRequest { + r := baseReqFn() + r.Region = "" + return r + }, + errCheck: isBadParamErrFn, + }, + { + name: "missing integration", + req: func() ListDeployedDatabaseServicesRequest { + r := baseReqFn() + r.Integration = "" + return r + }, + errCheck: isBadParamErrFn, + }, + } { + t.Run(tt.name, func(t *testing.T) { + r := tt.req() + err := r.checkAndSetDefaults() + tt.errCheck(t, err) + + if err != nil { + return + } + + require.Empty(t, cmp.Diff(tt.reqWithDefaults, r)) + }) + } +} + +type mockListECSClient struct { + pageSize int + + clusterName string + services []*ecstypes.Service + mapServices map[string]ecstypes.Service + taskDefinition map[string]*ecstypes.TaskDefinition +} + +func (m *mockListECSClient) ListServices(ctx context.Context, params *ecs.ListServicesInput, optFns ...func(*ecs.Options)) (*ecs.ListServicesOutput, error) { + ret := &ecs.ListServicesOutput{} + if aws.ToString(params.Cluster) != m.clusterName { + return ret, nil + } + + requestedPage := 1 + + totalEndpoints := len(m.services) + + if params.NextToken != nil { + currentMarker, err := strconv.Atoi(*params.NextToken) + if err != nil { + return nil, trace.Wrap(err) + } + requestedPage = currentMarker + } + + sliceStart := m.pageSize * (requestedPage - 1) + sliceEnd := m.pageSize * requestedPage + if sliceEnd > totalEndpoints { + sliceEnd = totalEndpoints + } + + for _, service := range m.services[sliceStart:sliceEnd] { + ret.ServiceArns = append(ret.ServiceArns, aws.ToString(service.ServiceArn)) + } + + if sliceEnd < totalEndpoints { + nextToken := strconv.Itoa(requestedPage + 1) + ret.NextToken = &nextToken + } + + return ret, nil +} + +func (m *mockListECSClient) DescribeServices(ctx context.Context, params *ecs.DescribeServicesInput, optFns ...func(*ecs.Options)) (*ecs.DescribeServicesOutput, error) { + ret := &ecs.DescribeServicesOutput{} + if aws.ToString(params.Cluster) != m.clusterName { + return ret, nil + } + + for _, serviceARN := range params.Services { + ret.Services = append(ret.Services, m.mapServices[serviceARN]) + } + return ret, nil +} + +func (m *mockListECSClient) DescribeTaskDefinition(ctx context.Context, params *ecs.DescribeTaskDefinitionInput, optFns ...func(*ecs.Options)) (*ecs.DescribeTaskDefinitionOutput, error) { + ret := &ecs.DescribeTaskDefinitionOutput{} + ret.TaskDefinition = m.taskDefinition[aws.ToString(params.TaskDefinition)] + + return ret, nil +} + +func dummyServiceTask(idx int) (ecstypes.Service, *ecstypes.TaskDefinition) { + taskName := fmt.Sprintf("task-family-name-%d", idx) + serviceARN := fmt.Sprintf("arn:eks:service-%d", idx) + + ecsTask := &ecstypes.TaskDefinition{ + Family: aws.String(taskName), + ContainerDefinitions: []ecstypes.ContainerDefinition{{ + EntryPoint: []string{"teleport"}, + Command: []string{"start"}, + }}, + } + + ecsService := ecstypes.Service{ + ServiceArn: aws.String(serviceARN), + ServiceName: aws.String(fmt.Sprintf("database-service-vpc-%d", idx)), + TaskDefinition: aws.String(taskName), + Tags: []ecstypes.Tag{ + {Key: aws.String("teleport.dev/cluster"), Value: aws.String("my-cluster")}, + {Key: aws.String("teleport.dev/integration"), Value: aws.String("my-integration")}, + {Key: aws.String("teleport.dev/origin"), Value: aws.String("integration_awsoidc")}, + }, + } + + return ecsService, ecsTask +} + +func TestListDeployedDatabaseServices(t *testing.T) { + ctx := context.Background() + + const pageSize = 100 + t.Run("pagination", func(t *testing.T) { + totalServices := 203 + + allServices := make([]*ecstypes.Service, 0, totalServices) + mapServices := make(map[string]ecstypes.Service, totalServices) + allTasks := make(map[string]*ecstypes.TaskDefinition, totalServices) + for i := 0; i < totalServices; i++ { + ecsService, ecsTask := dummyServiceTask(i) + allTasks[aws.ToString(ecsTask.Family)] = ecsTask + mapServices[aws.ToString(ecsService.ServiceArn)] = ecsService + allServices = append(allServices, &ecsService) + } + + mockListClient := &mockListECSClient{ + pageSize: pageSize, + clusterName: "my-cluster-teleport", + mapServices: mapServices, + services: allServices, + taskDefinition: allTasks, + } + + // First page must return pageSize number of Endpoints + resp, err := ListDeployedDatabaseServices(ctx, mockListClient, ListDeployedDatabaseServicesRequest{ + Integration: "my-integration", + TeleportClusterName: "my-cluster", + Region: "us-east-1", + }) + require.NoError(t, err) + require.NotEmpty(t, resp.NextToken) + require.Len(t, resp.DeployedDatabaseServices, pageSize) + require.Equal(t, "database-service-vpc-0", resp.DeployedDatabaseServices[0].Name) + require.Equal(t, "https://us-east-1.console.aws.amazon.com/ecs/v2/clusters/my-cluster-teleport/services/database-service-vpc-0", resp.DeployedDatabaseServices[0].ServiceDashboardURL) + require.Equal(t, []string{"teleport"}, resp.DeployedDatabaseServices[0].ContainerEntryPoint) + require.Equal(t, []string{"start"}, resp.DeployedDatabaseServices[0].ContainerCommand) + + // Second page must return pageSize number of Endpoints + nextPageToken := resp.NextToken + resp, err = ListDeployedDatabaseServices(ctx, mockListClient, ListDeployedDatabaseServicesRequest{ + Integration: "my-integration", + TeleportClusterName: "my-cluster", + Region: "us-east-1", + NextToken: nextPageToken, + }) + require.NoError(t, err) + require.NotEmpty(t, resp.NextToken) + require.Len(t, resp.DeployedDatabaseServices, pageSize) + require.Equal(t, "https://us-east-1.console.aws.amazon.com/ecs/v2/clusters/my-cluster-teleport/services/database-service-vpc-100", resp.DeployedDatabaseServices[0].ServiceDashboardURL) + + // Third page must return only the remaining Endpoints and an empty nextToken + nextPageToken = resp.NextToken + resp, err = ListDeployedDatabaseServices(ctx, mockListClient, ListDeployedDatabaseServicesRequest{ + Integration: "my-integration", + TeleportClusterName: "my-cluster", + Region: "us-east-1", + NextToken: nextPageToken, + }) + require.NoError(t, err) + require.Empty(t, resp.NextToken) + require.Len(t, resp.DeployedDatabaseServices, 3) + }) + + for _, tt := range []struct { + name string + req ListDeployedDatabaseServicesRequest + mockClient func() *mockListECSClient + errCheck require.ErrorAssertionFunc + respCheck func(*testing.T, *ListDeployedDatabaseServicesResponse) + }{ + { + name: "ignores ECS Services without ownership tags", + req: ListDeployedDatabaseServicesRequest{ + Integration: "my-integration", + TeleportClusterName: "my-cluster", + Region: "us-east-1", + }, + mockClient: func() *mockListECSClient { + ret := &mockListECSClient{ + pageSize: 10, + clusterName: "my-cluster-teleport", + } + ecsService, ecsTask := dummyServiceTask(0) + + ecsServiceAnotherIntegration, ecsTaskAnotherIntegration := dummyServiceTask(1) + ecsServiceAnotherIntegration.Tags = []ecstypes.Tag{{Key: aws.String("teleport.dev/integration"), Value: aws.String("another-integration")}} + + ret.taskDefinition = map[string]*ecstypes.TaskDefinition{ + aws.ToString(ecsTask.Family): ecsTask, + aws.ToString(ecsTaskAnotherIntegration.Family): ecsTaskAnotherIntegration, + } + ret.mapServices = map[string]ecstypes.Service{ + aws.ToString(ecsService.ServiceArn): ecsService, + aws.ToString(ecsServiceAnotherIntegration.ServiceArn): ecsServiceAnotherIntegration, + } + ret.services = append(ret.services, &ecsService) + ret.services = append(ret.services, &ecsServiceAnotherIntegration) + return ret + }, + respCheck: func(t *testing.T, resp *ListDeployedDatabaseServicesResponse) { + require.Len(t, resp.DeployedDatabaseServices, 1, "expected 1 service, got %d", len(resp.DeployedDatabaseServices)) + require.Empty(t, resp.NextToken, "expected an empty NextToken") + + expectedService := DeployedDatabaseService{ + Name: "database-service-vpc-0", + ServiceDashboardURL: "https://us-east-1.console.aws.amazon.com/ecs/v2/clusters/my-cluster-teleport/services/database-service-vpc-0", + ContainerEntryPoint: []string{"teleport"}, + ContainerCommand: []string{"start"}, + } + require.Empty(t, cmp.Diff(expectedService, resp.DeployedDatabaseServices[0])) + }, + errCheck: require.NoError, + }, + { + name: "ignores ECS Services without containers", + req: ListDeployedDatabaseServicesRequest{ + Integration: "my-integration", + TeleportClusterName: "my-cluster", + Region: "us-east-1", + }, + mockClient: func() *mockListECSClient { + ret := &mockListECSClient{ + pageSize: 10, + clusterName: "my-cluster-teleport", + } + ecsService, ecsTask := dummyServiceTask(0) + + ecsServiceWithoutContainers, ecsTaskWithoutContainers := dummyServiceTask(1) + ecsTaskWithoutContainers.ContainerDefinitions = []ecstypes.ContainerDefinition{} + + ret.taskDefinition = map[string]*ecstypes.TaskDefinition{ + aws.ToString(ecsTask.Family): ecsTask, + aws.ToString(ecsTaskWithoutContainers.Family): ecsTaskWithoutContainers, + } + ret.mapServices = map[string]ecstypes.Service{ + aws.ToString(ecsService.ServiceArn): ecsService, + aws.ToString(ecsServiceWithoutContainers.ServiceArn): ecsServiceWithoutContainers, + } + ret.services = append(ret.services, &ecsService) + ret.services = append(ret.services, &ecsServiceWithoutContainers) + return ret + }, + respCheck: func(t *testing.T, resp *ListDeployedDatabaseServicesResponse) { + require.Len(t, resp.DeployedDatabaseServices, 1, "expected 1 service, got %d", len(resp.DeployedDatabaseServices)) + require.Empty(t, resp.NextToken, "expected an empty NextToken") + + expectedService := DeployedDatabaseService{ + Name: "database-service-vpc-0", + ServiceDashboardURL: "https://us-east-1.console.aws.amazon.com/ecs/v2/clusters/my-cluster-teleport/services/database-service-vpc-0", + ContainerEntryPoint: []string{"teleport"}, + ContainerCommand: []string{"start"}, + } + require.Empty(t, cmp.Diff(expectedService, resp.DeployedDatabaseServices[0])) + }, + errCheck: require.NoError, + }, + } { + t.Run(tt.name, func(t *testing.T) { + resp, err := ListDeployedDatabaseServices(ctx, tt.mockClient(), tt.req) + tt.errCheck(t, err) + if tt.respCheck != nil { + tt.respCheck(t, resp) + } + }) + } +} diff --git a/lib/integrations/diagnostics/profile.go b/lib/integrations/diagnostics/profile.go index 5d185a271c8bd..f34466d749aac 100644 --- a/lib/integrations/diagnostics/profile.go +++ b/lib/integrations/diagnostics/profile.go @@ -17,6 +17,8 @@ package diagnostics import ( + "context" + "log/slog" "os" "path/filepath" "runtime" @@ -26,7 +28,6 @@ import ( "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" ) // Profile captures various Go pprof profiles and writes @@ -34,6 +35,7 @@ import ( // with the same epoch time so that profiles can easily be associated // as being captured from the same call. func Profile(dir string) error { + ctx := context.Background() if err := os.MkdirAll(dir, 0o755); err != nil { return trace.Wrap(err, "creating profile directory %v", dir) } @@ -69,37 +71,37 @@ func Profile(dir string) error { } defer blockFile.Close() - logrus.Debugf("capturing trace profile to %s", traceFile.Name()) + slog.DebugContext(ctx, "capturing trace profile", "file", traceFile.Name()) if err := runtimetrace.Start(traceFile); err != nil { return trace.Wrap(err, "capturing trace profile") } - logrus.Debugf("capturing cpu profile to %s", cpuFile.Name()) + slog.DebugContext(ctx, "capturing cpu profile", "file", cpuFile.Name()) if err := pprof.StartCPUProfile(cpuFile); err != nil { return trace.Wrap(err, "capturing cpu profile") } defer func() { - logrus.Debugf("capturing goroutine profile to %s", cpuFile.Name()) + slog.DebugContext(ctx, "capturing goroutine profile", "file", cpuFile.Name()) if err := pprof.Lookup("goroutine").WriteTo(goroutineFile, 0); err != nil { - logrus.WithError(err).Warn("failed to capture goroutine profile") + slog.WarnContext(ctx, "failed to capture goroutine profile", "error", err) } - logrus.Debugf("capturing block profile to %s", cpuFile.Name()) + slog.DebugContext(ctx, "capturing block profile", "file", cpuFile.Name()) if err := pprof.Lookup("block").WriteTo(blockFile, 0); err != nil { - logrus.WithError(err).Warn("failed to capture block profile") + slog.WarnContext(ctx, "failed to capture block profile", "error", err) } runtime.GC() - logrus.Debugf("capturing heap profile to %s", cpuFile.Name()) + slog.DebugContext(ctx, "capturing heap profile", "file", cpuFile.Name()) if err := pprof.WriteHeapProfile(heapFile); err != nil { - logrus.WithError(err).Warn("failed to capture heap profile") + slog.WarnContext(ctx, "failed to capture heap profile", "error", err) } pprof.StopCPUProfile() diff --git a/lib/integrations/externalauditstorage/configurator_test.go b/lib/integrations/externalauditstorage/configurator_test.go index ba86e5f8e0c27..28d231b081b8e 100644 --- a/lib/integrations/externalauditstorage/configurator_test.go +++ b/lib/integrations/externalauditstorage/configurator_test.go @@ -30,7 +30,6 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/google/uuid" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -179,7 +178,6 @@ func TestConfiguratorIsUsed(t *testing.T) { } func TestCredentialsCache(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/lib/integrations/externalauditstorage/error_counter.go b/lib/integrations/externalauditstorage/error_counter.go index 1cc0f650c90f9..7525cd5631ffb 100644 --- a/lib/integrations/externalauditstorage/error_counter.go +++ b/lib/integrations/externalauditstorage/error_counter.go @@ -29,7 +29,6 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport" auditlogpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/auditlog/v1" @@ -38,6 +37,7 @@ import ( apievents "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/lib/events" "github.com/gravitational/teleport/lib/session" + logutils "github.com/gravitational/teleport/lib/utils/log" ) const ( @@ -53,7 +53,7 @@ const ( syncInterval = 30 * time.Second ) -var log = logrus.WithField(teleport.ComponentKey, "ExternalAuditStorage") +var log = logutils.NewPackageLogger(teleport.ComponentKey, "ExternalAuditStorage") // ClusterAlertService abstracts a service providing Upsert and Delete // operations for cluster alerts. @@ -189,16 +189,25 @@ func (c *ErrorCounter) sync(ctx context.Context) { types.WithAlertLabel(types.AlertOnLogin, "yes"), types.WithAlertLabel(types.AlertVerbPermit, "external_audit_storage:create")) if err != nil { - log.Infof("ErrorCounter failed to create cluster alert %s: %s", newAlert.name, err) + log.InfoContext(ctx, "ErrorCounter failed to create cluster alert", + "alert_name", newAlert.name, + "error", err, + ) continue } if err := c.alertService.UpsertClusterAlert(ctx, alert); err != nil { - log.Infof("ErrorCounter failed to upsert cluster alert %s: %s", newAlert.name, err) + log.InfoContext(ctx, "ErrorCounter failed to upsert cluster alert", + "alert_name", newAlert.name, + "error", err, + ) } } for _, alertToClear := range allAlertActions.clearAlerts { if err := c.alertService.DeleteClusterAlert(ctx, alertToClear); err != nil && !trace.IsNotFound(err) { - log.Infof("ErrorCounter failed to delete cluster alert %s: %s", alertToClear, err) + log.InfoContext(ctx, "ErrorCounter failed to delete cluster alert", + "alert_name", alertToClear, + "error", err, + ) } } } diff --git a/lib/inventory/controller.go b/lib/inventory/controller.go index 8ea733c950dc6..b1825c240e439 100644 --- a/lib/inventory/controller.go +++ b/lib/inventory/controller.go @@ -36,6 +36,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/utils/retryutils" "github.com/gravitational/teleport/lib/inventory/internal/delay" + "github.com/gravitational/teleport/lib/services" usagereporter "github.com/gravitational/teleport/lib/usagereporter/teleport" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/interval" @@ -118,11 +119,13 @@ const ( keepAliveKubeTick = "keep-alive-kube-tick" ) -// instanceHBStepSize is the step size used for the variable instance heartbeat duration. This value is -// basically arbitrary. It was selected because it produces a scaling curve that makes a fairly reasonable -// tradeoff between heartbeat availability and load scaling. See test coverage in the 'interval' package -// for a demonstration of the relationship between step sizes and interval/duration scaling. -const instanceHBStepSize = 1024 +// heartbeatStepSize is the step size used for the variable heartbeat intervals. +// This value is basically arbitrary. It was selected because it produces a +// scaling curve that makes a fairly reasonable tradeoff between heartbeat +// availability and load scaling. See test coverage in the 'interval' package +// for a demonstration of the relationship between step sizes and +// interval/duration scaling. +const heartbeatStepSize = 1024 type controllerOptions struct { serverKeepAlive time.Duration @@ -233,6 +236,10 @@ type Controller struct { instanceTTL time.Duration instanceHBEnabled bool instanceHBVariableDuration *interval.VariableDuration + sshHBVariableDuration *interval.VariableDuration + appHBVariableDuration *interval.VariableDuration + dbHBVariableDuration *interval.VariableDuration + kubeHBVariableDuration *interval.VariableDuration maxKeepAliveErrs int usageReporter usagereporter.UsageReporter testEvents chan testEvent @@ -254,18 +261,55 @@ func NewController(auth Auth, usageReporter usagereporter.UsageReporter, opts .. instanceHBVariableDuration := interval.NewVariableDuration(interval.VariableDurationConfig{ MinDuration: options.instanceHBInterval, MaxDuration: apidefaults.MaxInstanceHeartbeatInterval, - Step: instanceHBStepSize, + Step: heartbeatStepSize, }) + var ( + sshHBVariableDuration *interval.VariableDuration + appHBVariableDuration *interval.VariableDuration + dbHBVariableDuration *interval.VariableDuration + kubeHBVariableDuration *interval.VariableDuration + ) + serverTTL := apidefaults.ServerAnnounceTTL + if !variableRateHeartbeatsDisabledEnv() { + // by default, heartbeats will scale from 1.5 to 6 minutes, and will + // have a TTL of 15 minutes + serverTTL = apidefaults.ServerAnnounceTTL * 3 / 2 + sshHBVariableDuration = interval.NewVariableDuration(interval.VariableDurationConfig{ + MinDuration: options.serverKeepAlive, + MaxDuration: options.serverKeepAlive * 4, + Step: heartbeatStepSize, + }) + appHBVariableDuration = interval.NewVariableDuration(interval.VariableDurationConfig{ + MinDuration: options.serverKeepAlive, + MaxDuration: options.serverKeepAlive * 4, + Step: heartbeatStepSize, + }) + dbHBVariableDuration = interval.NewVariableDuration(interval.VariableDurationConfig{ + MinDuration: options.serverKeepAlive, + MaxDuration: options.serverKeepAlive * 4, + Step: heartbeatStepSize, + }) + kubeHBVariableDuration = interval.NewVariableDuration(interval.VariableDurationConfig{ + MinDuration: options.serverKeepAlive, + MaxDuration: options.serverKeepAlive * 4, + Step: heartbeatStepSize, + }) + } + ctx, cancel := context.WithCancel(context.Background()) return &Controller{ store: NewStore(), serviceCounter: &serviceCounter{}, serverKeepAlive: options.serverKeepAlive, - serverTTL: apidefaults.ServerAnnounceTTL, + serverTTL: serverTTL, instanceTTL: apidefaults.InstanceHeartbeatTTL, instanceHBEnabled: !instanceHeartbeatsDisabledEnv(), instanceHBVariableDuration: instanceHBVariableDuration, + sshHBVariableDuration: sshHBVariableDuration, + appHBVariableDuration: appHBVariableDuration, + dbHBVariableDuration: dbHBVariableDuration, + kubeHBVariableDuration: kubeHBVariableDuration, maxKeepAliveErrs: options.maxKeepAliveErrs, auth: auth, authID: options.authID, @@ -417,23 +461,36 @@ func (c *Controller) handleControlStream(handle *upstreamHandle) { if handle.sshServer != nil { c.onDisconnectFunc(constants.KeepAliveNode, 1) + if c.sshHBVariableDuration != nil { + c.sshHBVariableDuration.Dec() + } + handle.sshServer = nil } if len(handle.appServers) > 0 { c.onDisconnectFunc(constants.KeepAliveApp, len(handle.appServers)) + if c.appHBVariableDuration != nil { + c.appHBVariableDuration.Add(-len(handle.appServers)) + } + clear(handle.appServers) } if len(handle.databaseServers) > 0 { c.onDisconnectFunc(constants.KeepAliveDatabase, len(handle.databaseServers)) + if c.dbHBVariableDuration != nil { + c.dbHBVariableDuration.Add(-len(handle.databaseServers)) + } + clear(handle.databaseServers) } if len(handle.kubernetesServers) > 0 { c.onDisconnectFunc(constants.KeepAliveKube, len(handle.kubernetesServers)) + if c.kubeHBVariableDuration != nil { + c.kubeHBVariableDuration.Add(-len(handle.kubernetesServers)) + } + clear(handle.kubernetesServers) } - clear(handle.appServers) - clear(handle.databaseServers) - clear(handle.kubernetesServers) c.testEvent(handlerClose) }() @@ -448,40 +505,60 @@ func (c *Controller) handleControlStream(handle *upstreamHandle) { case proto.UpstreamInventoryAgentMetadata: c.handleAgentMetadata(handle, m) case proto.InventoryHeartbeat: - if err := c.handleHeartbeatMsg(handle, m); err != nil { - handle.CloseWithError(err) - return + // XXX: when adding new services to the heartbeat logic, make + // sure to also update the 'icsServiceToMetricName' mapping in + // auth/grpcserver.go in order to ensure that metrics start + // counting the control stream as a registered keepalive stream + // for that service. + + if m.SSHServer != nil { + // we initialize sshKeepAliveDelay before calling + // handleSSHServerHB unlike the other heartbeat types + // because handleSSHServerHB needs the delay to reset it + // after an announce, including the first one + if sshKeepAliveDelay == nil { + sshKeepAliveDelay = c.createKeepAliveDelay(c.sshHBVariableDuration) + } + + if err := c.handleSSHServerHB(handle, m.SSHServer, sshKeepAliveDelay); err != nil { + handle.CloseWithError(trace.Wrap(err)) + return + } } - // we initialize delays lazily here, depending on the protocol - if sshKeepAliveDelay == nil && m.SSHServer != nil { - sshKeepAliveDelay = delay.New(delay.Params{ - FirstInterval: retryutils.HalfJitter(c.serverKeepAlive), - FixedInterval: c.serverKeepAlive, - Jitter: retryutils.SeventhJitter, - }) - } - if appKeepAliveDelay == nil && m.AppServer != nil { - appKeepAliveDelay = delay.New(delay.Params{ - FirstInterval: retryutils.HalfJitter(c.serverKeepAlive), - FixedInterval: c.serverKeepAlive, - Jitter: retryutils.SeventhJitter, - }) + if m.AppServer != nil { + if err := c.handleAppServerHB(handle, m.AppServer); err != nil { + handle.CloseWithError(err) + return + } + + if appKeepAliveDelay == nil { + appKeepAliveDelay = c.createKeepAliveDelay(c.appHBVariableDuration) + } } - if dbKeepAliveDelay == nil && m.DatabaseServer != nil { - dbKeepAliveDelay = delay.New(delay.Params{ - FirstInterval: retryutils.HalfJitter(c.serverKeepAlive), - FixedInterval: c.serverKeepAlive, - Jitter: retryutils.SeventhJitter, - }) + + if m.DatabaseServer != nil { + if err := c.handleDatabaseServerHB(handle, m.DatabaseServer); err != nil { + handle.CloseWithError(err) + return + } + + if dbKeepAliveDelay == nil { + dbKeepAliveDelay = c.createKeepAliveDelay(c.dbHBVariableDuration) + } } - if kubeKeepAliveDelay == nil && m.KubernetesServer != nil { - kubeKeepAliveDelay = delay.New(delay.Params{ - FirstInterval: retryutils.HalfJitter(c.serverKeepAlive), - FixedInterval: c.serverKeepAlive, - Jitter: retryutils.SeventhJitter, - }) + + if m.KubernetesServer != nil { + if err := c.handleKubernetesServerHB(handle, m.KubernetesServer); err != nil { + handle.CloseWithError(err) + return + } + + if kubeKeepAliveDelay == nil { + kubeKeepAliveDelay = c.createKeepAliveDelay(c.kubeHBVariableDuration) + } } + case proto.UpstreamInventoryPong: c.handlePong(handle, m) case proto.UpstreamInventoryGoodbye: @@ -570,6 +647,12 @@ func instanceHeartbeatsDisabledEnv() bool { return os.Getenv("TELEPORT_UNSTABLE_DISABLE_INSTANCE_HB") == "yes" } +// variableRateHeartbeatsDisabledEnv checks if variable rate heartbeats have +// been explicitly disabled via environment variable. +func variableRateHeartbeatsDisabledEnv() bool { + return os.Getenv("TELEPORT_UNSTABLE_DISABLE_VARIABLE_RATE_HEARTBEATS") == "yes" +} + func (c *Controller) heartbeatInstanceState(handle *upstreamHandle, now time.Time) error { if !c.instanceHBEnabled { return nil @@ -673,39 +756,7 @@ func (c *Controller) handlePingRequest(handle *upstreamHandle, req pingRequest) return nil } -func (c *Controller) handleHeartbeatMsg(handle *upstreamHandle, hb proto.InventoryHeartbeat) error { - // XXX: when adding new services to the heartbeat logic, make sure to also update the - // 'icsServiceToMetricName' mapping in auth/grpcserver.go in order to ensure that metrics - // start counting the control stream as a registered keepalive stream for that service. - - if hb.SSHServer != nil { - if err := c.handleSSHServerHB(handle, hb.SSHServer); err != nil { - return trace.Wrap(err) - } - } - - if hb.AppServer != nil { - if err := c.handleAppServerHB(handle, hb.AppServer); err != nil { - return trace.Wrap(err) - } - } - - if hb.DatabaseServer != nil { - if err := c.handleDatabaseServerHB(handle, hb.DatabaseServer); err != nil { - return trace.Wrap(err) - } - } - - if hb.KubernetesServer != nil { - if err := c.handleKubernetesServerHB(handle, hb.KubernetesServer); err != nil { - return trace.Wrap(err) - } - } - - return nil -} - -func (c *Controller) handleSSHServerHB(handle *upstreamHandle, sshServer *types.ServerV2) error { +func (c *Controller) handleSSHServerHB(handle *upstreamHandle, sshServer *types.ServerV2, sshDelay *delay.Delay) error { // the auth layer verifies that a stream's hello message matches the identity and capabilities of the // client cert. after that point it is our responsibility to ensure that heartbeated information is // consistent with the identity and capabilities claimed in the initial hello. @@ -722,31 +773,48 @@ func (c *Controller) handleSSHServerHB(handle *upstreamHandle, sshServer *types. sshServer.SetAddr(utils.ReplaceLocalhost(sshServer.GetAddr(), handle.PeerAddr())) } + sshServer.SetExpiry(time.Now().Add(c.serverTTL).UTC()) + if handle.sshServer == nil { c.onConnectFunc(constants.KeepAliveNode) - handle.sshServer = &heartBeatInfo[*types.ServerV2]{} + if c.sshHBVariableDuration != nil { + c.sshHBVariableDuration.Inc() + } + handle.sshServer = &heartBeatInfo[*types.ServerV2]{ + resource: sshServer, + } + } else if handle.sshServer.keepAliveErrs == 0 && services.CompareServers(handle.sshServer.resource, sshServer) < services.Different { + // if we have successfully upserted this exact server the last time + // (except for the expiry), we don't need to upsert it again right now + return nil + } else { + handle.sshServer.resource = sshServer } - now := c.clock.Now() - - sshServer.SetExpiry(now.Add(c.serverTTL).UTC()) - - lease, err := c.auth.UpsertNode(c.closeContext, sshServer) - if err == nil { + if _, err := c.auth.UpsertNode(c.closeContext, handle.sshServer.resource); err == nil { c.testEvent(sshUpsertOk) - // store the new lease and reset retry state - handle.sshServer.lease = lease + // reset the error status + handle.sshServer.keepAliveErrs = 0 handle.sshServer.retryUpsert = false + + sshDelay.Reset() } else { c.testEvent(sshUpsertErr) - slog.WarnContext(c.closeContext, "Failed to upsert ssh server on heartbeat", + slog.WarnContext(c.closeContext, "Failed to announce SSH server", "server_id", handle.Hello().ServerID, "error", err, ) - // blank old lease if any and set retry state. next time handleKeepAlive is called - // we will attempt to upsert the server again. - handle.sshServer.lease = nil + // we use keepAliveErrs as a general upsert error count for SSH, + // retryUpsert as a flag to signify that we MUST succeed the very next + // upsert: if we're here it means that we have a new resource to upsert + // and we have failed to do so once, so if we fail again we are going to + // fall too far behind and we should let the instance go and connect to + // a healthier auth server + handle.sshServer.keepAliveErrs++ + if handle.sshServer.retryUpsert || handle.sshServer.keepAliveErrs > c.maxKeepAliveErrs { + return trace.Wrap(err, "failed to announce SSH server") + } handle.sshServer.retryUpsert = true } handle.sshServer.resource = sshServer @@ -772,6 +840,9 @@ func (c *Controller) handleAppServerHB(handle *upstreamHandle, appServer *types. if _, ok := handle.appServers[appKey]; !ok { c.onConnectFunc(constants.KeepAliveApp) + if c.appHBVariableDuration != nil { + c.appHBVariableDuration.Inc() + } handle.appServers[appKey] = &heartBeatInfo[*types.AppServerV3]{} } @@ -823,6 +894,9 @@ func (c *Controller) handleDatabaseServerHB(handle *upstreamHandle, databaseServ if _, ok := handle.databaseServers[dbKey]; !ok { c.onConnectFunc(constants.KeepAliveDatabase) + if c.dbHBVariableDuration != nil { + c.dbHBVariableDuration.Inc() + } handle.databaseServers[dbKey] = &heartBeatInfo[*types.DatabaseServerV3]{} } @@ -874,6 +948,9 @@ func (c *Controller) handleKubernetesServerHB(handle *upstreamHandle, kubernetes if _, ok := handle.kubernetesServers[kubeKey]; !ok { c.onConnectFunc(constants.KeepAliveKube) + if c.kubeHBVariableDuration != nil { + c.kubeHBVariableDuration.Inc() + } handle.kubernetesServers[kubeKey] = &heartBeatInfo[*types.KubernetesServerV3]{} } @@ -951,6 +1028,9 @@ func (c *Controller) keepAliveAppServer(handle *upstreamHandle, now time.Time) e if shouldRemove { c.testEvent(appKeepAliveDel) c.onDisconnectFunc(constants.KeepAliveApp, 1) + if c.appHBVariableDuration != nil { + c.appHBVariableDuration.Dec() + } delete(handle.appServers, name) } } else { @@ -1002,6 +1082,9 @@ func (c *Controller) keepAliveDatabaseServer(handle *upstreamHandle, now time.Ti if shouldRemove { c.testEvent(dbKeepAliveDel) c.onDisconnectFunc(constants.KeepAliveDatabase, 1) + if c.dbHBVariableDuration != nil { + c.dbHBVariableDuration.Dec() + } delete(handle.databaseServers, name) } } else { @@ -1053,6 +1136,9 @@ func (c *Controller) keepAliveKubernetesServer(handle *upstreamHandle, now time. if shouldRemove { c.testEvent(kubeKeepAliveDel) c.onDisconnectFunc(constants.KeepAliveKube, 1) + if c.kubeHBVariableDuration != nil { + c.kubeHBVariableDuration.Dec() + } delete(handle.kubernetesServers, name) } } else { @@ -1088,50 +1174,55 @@ func (c *Controller) keepAliveSSHServer(handle *upstreamHandle, now time.Time) e return nil } - if handle.sshServer.lease != nil { - lease := *handle.sshServer.lease - lease.Expires = now.Add(c.serverTTL).UTC() - if err := c.auth.KeepAliveServer(c.closeContext, lease); err != nil { - c.testEvent(sshKeepAliveErr) - handle.sshServer.keepAliveErrs++ - shouldClose := handle.sshServer.keepAliveErrs > c.maxKeepAliveErrs - - slog.WarnContext(c.closeContext, "Failed to keep alive ssh server", - "server_id", handle.Hello().ServerID, - "error", err, - "error_count", handle.sshServer.keepAliveErrs, - "should_remove", shouldClose, - ) - - if shouldClose { - return trace.Errorf("failed to keep alive ssh server: %v", err) - } + handle.sshServer.resource.SetExpiry(now.Add(c.serverTTL).UTC()) + if _, err := c.auth.UpsertNode(c.closeContext, handle.sshServer.resource); err == nil { + if handle.sshServer.retryUpsert { + c.testEvent(sshUpsertRetryOk) } else { - handle.sshServer.keepAliveErrs = 0 c.testEvent(sshKeepAliveOk) } - } else if handle.sshServer.retryUpsert { - handle.sshServer.resource.SetExpiry(c.clock.Now().Add(c.serverTTL).UTC()) - lease, err := c.auth.UpsertNode(c.closeContext, handle.sshServer.resource) - if err != nil { + handle.sshServer.keepAliveErrs = 0 + handle.sshServer.retryUpsert = false + } else { + if handle.sshServer.retryUpsert { c.testEvent(sshUpsertRetryErr) - slog.WarnContext(c.closeContext, "Failed to upsert ssh server on retry", + slog.WarnContext(c.closeContext, "Failed to upsert SSH server on retry", "server_id", handle.Hello().ServerID, "error", err, ) - // since this is retry-specific logic, an error here means that upsert failed twice in - // a row. Missing upserts is more problematic than missing keepalives so we don'resource bother - // attempting a third time. - return trace.Errorf("failed to upsert ssh server on retry: %v", err) + // retryUpsert is set when we get a new resource and we fail to + // upsert it; if we're here it means that we have failed to upsert + // it _again_, so we have fallen quite far behind + return trace.Wrap(err, "failed to upsert SSH server on retry") + } + + c.testEvent(sshKeepAliveErr) + handle.sshServer.keepAliveErrs++ + closing := handle.sshServer.keepAliveErrs > c.maxKeepAliveErrs + slog.WarnContext(c.closeContext, "Failed to upsert SSH server on keepalive", + "server_id", handle.Hello().ServerID, + "error", err, + "count", handle.sshServer.keepAliveErrs, + "closing", closing, + ) + + if closing { + return trace.Wrap(err, "failed to keep alive SSH server") } - c.testEvent(sshUpsertRetryOk) - handle.sshServer.lease = lease - handle.sshServer.retryUpsert = false } return nil } +func (c *Controller) createKeepAliveDelay(variableDuration *interval.VariableDuration) *delay.Delay { + return delay.New(delay.Params{ + FirstInterval: retryutils.HalfJitter(c.serverKeepAlive), + FixedInterval: c.serverKeepAlive, + VariableInterval: variableDuration, + Jitter: retryutils.SeventhJitter, + }) +} + // Close terminates all control streams registered with this controller. Control streams // registered after Close() is called are closed immediately. func (c *Controller) Close() error { diff --git a/lib/inventory/controller_test.go b/lib/inventory/controller_test.go index 9ec509f725293..badc1e6920d97 100644 --- a/lib/inventory/controller_test.go +++ b/lib/inventory/controller_test.go @@ -247,14 +247,13 @@ func TestSSHServerBasics(t *testing.T) { // set up to induce some failures, but not enough to cause the control // stream to be closed. auth.mu.Lock() - auth.failUpserts = 1 - auth.failKeepAlives = 2 + auth.failUpserts = 2 auth.mu.Unlock() // keepalive should fail twice, but since the upsert is already known // to have succeeded, we should not see an upsert failure yet. awaitEvents(t, events, - expect(sshKeepAliveErr, sshKeepAliveErr), + expect(sshKeepAliveErr, sshKeepAliveErr, sshKeepAliveOk), deny(sshUpsertErr, handlerClose), ) @@ -270,6 +269,32 @@ func TestSSHServerBasics(t *testing.T) { }) require.NoError(t, err) + // this explicit upsert will not happen since the server is the same, but + // keepalives should work + awaitEvents(t, events, + expect(sshKeepAliveOk), + deny(sshKeepAliveErr, sshUpsertErr, sshUpsertRetryOk, handlerClose), + ) + + err = downstream.Send(ctx, proto.InventoryHeartbeat{ + SSHServer: &types.ServerV2{ + Metadata: types.Metadata{ + Name: serverID, + Labels: map[string]string{ + "changed": "changed", + }, + }, + Spec: types.ServerSpecV2{ + Addr: zeroAddr, + }, + }, + }) + require.NoError(t, err) + + auth.mu.Lock() + auth.failUpserts = 1 + auth.mu.Unlock() + // we should now see an upsert failure, but no additional // keepalive failures, and the upsert should succeed on retry. awaitEvents(t, events, diff --git a/lib/inventory/internal/delay/delay.go b/lib/inventory/internal/delay/delay.go index 7a5ac8a06d74a..bb94478daf875 100644 --- a/lib/inventory/internal/delay/delay.go +++ b/lib/inventory/internal/delay/delay.go @@ -74,7 +74,7 @@ type Delay struct { } // Elapsed returns the channel on which the ticks are delivered. This method can -// be called on a nil delay, resulting in a nil channel. The [*Delay.Advance] +// be called on a nil delay, resulting in a nil channel. The [Delay.Advance] // method must be called after receiving a tick from the channel. // // select { @@ -102,7 +102,7 @@ func (i *Delay) interval() time.Duration { } // Advance sets up the next tick of the delay. Must be called after receiving -// from the [*Delay.Elapsed] channel; specifically, to maintain compatibility +// from the [Delay.Elapsed] channel; specifically, to maintain compatibility // with [clockwork.Clock], it must only be called with a drained timer channel. // For consistency, the value passed to Advance should be the value received // from the Elapsed channel (passing the current time will also work, but will @@ -111,8 +111,20 @@ func (i *Delay) Advance(now time.Time) { i.timer.Reset(i.interval() - i.clock.Since(now)) } -// Stop stops the delay. Only needed for [clockwork.Clock] compatibility. Can be -// called on a nil delay, as a no-op. The delay should not be used afterwards. +// Reset restarts the ticker from the current time. Must only be called while +// the timer is running (i.e. it must not be called between receiving from +// [Delay.Elapsed] and calling [Delay.Advance]). +func (i *Delay) Reset() { + // the drain is for Go earlier than 1.23 and for [clockwork.Clock] + if !i.timer.Stop() { + <-i.timer.Chan() + } + i.timer.Reset(i.interval()) +} + +// Stop stops the delay. Only needed for Go 1.22 and [clockwork.Clock] +// compatibility. Can be called on a nil delay, as a no-op. The delay should not +// be used afterwards. func (i *Delay) Stop() { if i == nil { return diff --git a/lib/joinserver/joinserver.go b/lib/joinserver/joinserver.go index 20933b02a4f99..2f16af283cfc5 100644 --- a/lib/joinserver/joinserver.go +++ b/lib/joinserver/joinserver.go @@ -29,7 +29,6 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "google.golang.org/grpc/peer" "github.com/gravitational/teleport/api/client" @@ -110,7 +109,7 @@ func (s *JoinServiceGRPCServer) RegisterUsingIAMMethod(srv proto.JoinService_Reg if peerInfo, ok := peer.FromContext(srv.Context()); ok { nodeAddr = peerInfo.Addr.String() } - logrus.Warnf("IAM join attempt timed out, node at (%s) is misbehaving or did not close the connection after encountering an error.", nodeAddr) + slog.WarnContext(srv.Context(), "IAM join attempt timed out, agent is misbehaving or did not close the connection after encountering an error", "agent_addr", nodeAddr) // Returning here should cancel any blocked Send or Recv operations. return trace.LimitExceeded("RegisterUsingIAMMethod timed out after %s, terminating the stream on the server", iamJoinRequestTimeout) } @@ -181,7 +180,7 @@ func (s *JoinServiceGRPCServer) RegisterUsingAzureMethod(srv proto.JoinService_R if peerInfo, ok := peer.FromContext(srv.Context()); ok { nodeAddr = peerInfo.Addr.String() } - logrus.Warnf("Azure join attempt timed out, node at (%s) is misbehaving or did not close the connection after encountering an error.", nodeAddr) + slog.WarnContext(srv.Context(), "Azure join attempt timed out, agent is misbehaving or did not close the connection after encountering an error", "agent_addr", nodeAddr) // Returning here should cancel any blocked Send or Recv operations. return trace.LimitExceeded("RegisterUsingAzureMethod timed out after %s, terminating the stream on the server", azureJoinRequestTimeout) } @@ -231,10 +230,10 @@ func setBotParameters(ctx context.Context, req *types.RegisterUsingTokenRequest) if ident.BotInstanceID != "" { // Trust the instance ID from the incoming identity: bots will // attempt to provide it on renewal, assuming it's still valid. - logrus.WithFields(logrus.Fields{ - "bot_name": ident.BotName, - "bot_instance_id": ident.BotInstanceID, - }).Info("bot is rejoining") + slog.InfoContext(ctx, "bot is rejoining", + "bot_name", ident.BotName, + "bot_instance_id", ident.BotInstanceID, + ) req.BotInstanceID = ident.BotInstanceID } else { // Clear any other value from the request: the value must come from a diff --git a/lib/jwt/jwt.go b/lib/jwt/jwt.go index a0305acf55971..5afe58f20d96a 100644 --- a/lib/jwt/jwt.go +++ b/lib/jwt/jwt.go @@ -279,6 +279,12 @@ type SignParamsJWTSVID struct { // Issuer is the value that should be included in the `iss` claim of the // created token. Issuer string + + // SetExpiry overrides the expiry time of the token. This causes the value + // of TTL to be ignored. + SetExpiry time.Time + // SetIssuedAt overrides the issued at time of the token. + SetIssuedAt time.Time } // SignJWTSVID signs a JWT SVID token. @@ -310,6 +316,12 @@ func (k *Key) SignJWTSVID(p SignParamsJWTSVID) (string, error) { // understand OIDC. Issuer: p.Issuer, } + if !p.SetIssuedAt.IsZero() { + claims.IssuedAt = jwt.NewNumericDate(p.SetIssuedAt) + } + if !p.SetExpiry.IsZero() { + claims.Expiry = jwt.NewNumericDate(p.SetExpiry) + } // > 2.2. Key ID: // >The kid header is optional. diff --git a/lib/kube/grpc/grpc.go b/lib/kube/grpc/grpc.go index 67f17fc4d5079..bbd982cd12da0 100644 --- a/lib/kube/grpc/grpc.go +++ b/lib/kube/grpc/grpc.go @@ -21,11 +21,11 @@ package kubev1 import ( "context" "errors" + "log/slog" "slices" "github.com/gravitational/trace" "github.com/gravitational/trace/trail" - "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -85,7 +85,7 @@ type Config struct { // Authz authenticates user. Authz authz.Authorizer // Log is the logger function. - Log logrus.FieldLogger + Log *slog.Logger // Emitter is used to emit audit events. Emitter apievents.Emitter // Component name to include in log output. @@ -139,9 +139,9 @@ func (c *Config) CheckAndSetDefaults() error { c.Component = "kube.grpc" } if c.Log == nil { - c.Log = logrus.New() + c.Log = slog.Default() } - c.Log = c.Log.WithFields(logrus.Fields{teleport.ComponentKey: c.Component}) + c.Log = c.Log.With(teleport.ComponentKey, c.Component) return nil } diff --git a/lib/kube/kubeconfig/kubeconfig.go b/lib/kube/kubeconfig/kubeconfig.go index 30e587da834d1..dc9f8d92833f1 100644 --- a/lib/kube/kubeconfig/kubeconfig.go +++ b/lib/kube/kubeconfig/kubeconfig.go @@ -21,13 +21,13 @@ package kubeconfig import ( "bytes" + "context" "fmt" "os" "path/filepath" "strings" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "golang.org/x/exp/maps" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/clientcmd" @@ -36,11 +36,10 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) -var log = logrus.WithFields(logrus.Fields{ - teleport.ComponentKey: teleport.ComponentKubeClient, -}) +var log = logutils.NewPackageLogger(teleport.ComponentKey, teleport.ComponentKubeClient) const ( // teleportKubeClusterNameExtension is the name of the extension that @@ -268,7 +267,7 @@ func UpdateConfig(path string, v Values, storeAllCAs bool, fs ConfigFS) error { } else if !trace.IsBadParameter(err) { return trace.Wrap(err) } - log.WithError(err).Warn("Kubernetes integration is not supported when logging in with a hardware private key.") + log.WarnContext(context.Background(), "Kubernetes integration is not supported when logging in with a hardware private key", "error", err) } return SaveConfig(path, *config, fs) @@ -493,7 +492,7 @@ func PathFromEnv() string { var configPath string if len(parts) > 0 { configPath = parts[0] - log.Debugf("Using kubeconfig from environment: %q.", configPath) + log.DebugContext(context.Background(), "Using kubeconfig from environment", "config_path", configPath) } return configPath diff --git a/lib/kube/proxy/auth.go b/lib/kube/proxy/auth.go index 16ee58685e1a2..61842aca77bf0 100644 --- a/lib/kube/proxy/auth.go +++ b/lib/kube/proxy/auth.go @@ -23,12 +23,12 @@ import ( "context" "crypto/tls" "fmt" + "log/slog" "net" "net/http" "net/url" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" authzapi "k8s.io/api/authorization/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilnet "k8s.io/apimachinery/pkg/util/net" @@ -77,11 +77,11 @@ func (f *Forwarder) getKubeDetails(ctx context.Context) error { kubeClusterName := f.cfg.KubeClusterName tpClusterName := f.cfg.ClusterName - f.log. - WithField("kubeconfigPath", kubeconfigPath). - WithField("kubeClusterName", kubeClusterName). - WithField("serviceType", serviceType). - Debug("Reading Kubernetes details.") + f.log.DebugContext(ctx, "Reading Kubernetes details", + "kubeconfig_path", kubeconfigPath, + "kube_cluster_name", kubeClusterName, + "service_type", serviceType, + ) // Proxy service should never have creds, forwards to kube service if serviceType == ProxyService { @@ -100,7 +100,7 @@ func (f *Forwarder) getKubeDetails(ctx context.Context) error { case KubeService: return trace.BadParameter("no Kubernetes credentials found; Kubernetes_service requires either a valid kubeconfig_file or to run inside of a Kubernetes pod") case LegacyProxyService: - f.log.Debugf("Could not load Kubernetes credentials. This proxy will still handle Kubernetes requests for trusted teleport clusters or Kubernetes nodes in this teleport cluster") + f.log.DebugContext(ctx, "Could not load Kubernetes credentials. This proxy will still handle Kubernetes requests for trusted teleport clusters or Kubernetes nodes in this teleport cluster") } return nil } @@ -124,14 +124,20 @@ func (f *Forwarder) getKubeDetails(ctx context.Context) error { for cluster, clientCfg := range cfg.Contexts { clusterCreds, err := extractKubeCreds(ctx, serviceType, cluster, clientCfg, f.log, f.cfg.CheckImpersonationPermissions) if err != nil { - f.log.WithError(err).Warnf("failed to load credentials for cluster %q.", cluster) + f.log.WarnContext(ctx, "failed to load credentials for cluster", + "cluster", cluster, + "error", err, + ) continue } kubeCluster, err := types.NewKubernetesClusterV3(types.Metadata{ Name: cluster, }, types.KubernetesClusterSpecV3{}) if err != nil { - f.log.WithError(err).Warnf("failed to create KubernetesClusterV3 from credentials for cluster %q.", cluster) + f.log.WarnContext(ctx, "failed to create KubernetesClusterV3 from credentials for cluster", + "cluster", cluster, + "error", err, + ) continue } @@ -139,13 +145,16 @@ func (f *Forwarder) getKubeDetails(ctx context.Context) error { clusterDetailsConfig{ cluster: kubeCluster, kubeCreds: clusterCreds, - log: f.log.WithField("cluster", kubeCluster.GetName()), + log: f.log.With("cluster", kubeCluster.GetName()), checker: f.cfg.CheckImpersonationPermissions, component: serviceType, clock: f.cfg.Clock, }) if err != nil { - f.log.WithError(err).Warnf("Failed to create cluster details for cluster %q.", cluster) + f.log.WarnContext(ctx, "Failed to create cluster details for cluster", + "cluster", cluster, + "error", err, + ) return trace.Wrap(err) } f.clusterDetails[cluster] = details @@ -153,10 +162,10 @@ func (f *Forwarder) getKubeDetails(ctx context.Context) error { return nil } -func extractKubeCreds(ctx context.Context, component string, cluster string, clientCfg *rest.Config, log logrus.FieldLogger, checkPermissions servicecfg.ImpersonationPermissionsChecker) (*staticKubeCreds, error) { - log = log.WithField("cluster", cluster) +func extractKubeCreds(ctx context.Context, component string, cluster string, clientCfg *rest.Config, log *slog.Logger, checkPermissions servicecfg.ImpersonationPermissionsChecker) (*staticKubeCreds, error) { + log = log.With("cluster", cluster) - log.Debug("Checking Kubernetes impersonation permissions.") + log.DebugContext(ctx, "Checking Kubernetes impersonation permissions") client, err := kubernetes.NewForConfig(clientCfg) if err != nil { return nil, trace.Wrap(err, "failed to generate Kubernetes client for cluster %q", cluster) @@ -165,9 +174,11 @@ func extractKubeCreds(ctx context.Context, component string, cluster string, cli // For each loaded cluster, check impersonation permissions. This // check only logs when permissions are not configured, but does not fail startup. if err := checkPermissions(ctx, cluster, client.AuthorizationV1().SelfSubjectAccessReviews()); err != nil { - log.WithError(err).Warning("Failed to test the necessary Kubernetes permissions. The target Kubernetes cluster may be down or have misconfigured RBAC. This teleport instance will still handle Kubernetes requests towards this Kubernetes cluster.") + log.WarnContext(ctx, "Failed to test the necessary Kubernetes permissions. The target Kubernetes cluster may be down or have misconfigured RBAC. This teleport instance will still handle Kubernetes requests towards this Kubernetes cluster.", + "error", err, + ) } else { - log.Debug("Have all necessary Kubernetes impersonation permissions.") + log.DebugContext(ctx, "Have all necessary Kubernetes impersonation permissions") } targetAddr, err := parseKubeHost(clientCfg.Host) @@ -192,7 +203,7 @@ func extractKubeCreds(ctx context.Context, component string, cluster string, cli return nil, trace.Wrap(err, "failed to generate transport from kubeconfig: %v", err) } - log.Debug("Initialized Kubernetes credentials") + log.DebugContext(ctx, "Initialized Kubernetes credentials") return &staticKubeCreds{ tlsConfig: tlsConfig, transportConfig: transportConfig, diff --git a/lib/kube/proxy/auth_test.go b/lib/kube/proxy/auth_test.go index 1263a0e8b9ad2..9d8269297f8f1 100644 --- a/lib/kube/proxy/auth_test.go +++ b/lib/kube/proxy/auth_test.go @@ -140,7 +140,6 @@ func TestGetKubeCreds(t *testing.T) { rbacSupportedTypes[allowedResourcesKey{apiGroup: "resources.teleport.dev", resourceKind: "teleportroles"}] = utils.KubeCustomResource rbacSupportedTypes[allowedResourcesKey{apiGroup: "resources.teleport.dev", resourceKind: "teleportroles/status"}] = utils.KubeCustomResource - logger := utils.NewLoggerForTests() ctx := context.TODO() const teleClusterName = "teleport-cluster" dir := t.TempDir() @@ -351,7 +350,7 @@ current-context: foo CheckImpersonationPermissions: tt.impersonationCheck, Clock: clockwork.NewFakeClock(), }, - log: logger, + log: utils.NewSlogLoggerForTests(), } err := fwd.getKubeDetails(ctx) tt.assertErr(t, err) diff --git a/lib/kube/proxy/cluster_details.go b/lib/kube/proxy/cluster_details.go index c949494ac982d..1a66ce0562978 100644 --- a/lib/kube/proxy/cluster_details.go +++ b/lib/kube/proxy/cluster_details.go @@ -21,6 +21,7 @@ package proxy import ( "context" "encoding/base64" + "log/slog" "strings" "sync" "time" @@ -29,7 +30,6 @@ import ( "github.com/aws/aws-sdk-go/service/eks" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/version" @@ -91,7 +91,7 @@ type clusterDetailsConfig struct { // cluster is the cluster to create a proxied cluster for. cluster types.KubeCluster // log is the logger to use. - log *logrus.Entry + log *slog.Logger // checker is the permissions checker to use. checker servicecfg.ImpersonationPermissionsChecker // resourceMatchers is the list of resource matchers to match the cluster against @@ -135,7 +135,7 @@ func newClusterDetails(ctx context.Context, cfg clusterDetailsConfig) (_ *kubeDe // Create the codec factory and the list of supported types for RBAC. codecFactory, rbacSupportedTypes, gvkSupportedRes, err := newClusterSchemaBuilder(cfg.log, creds.getKubeClient()) if err != nil { - cfg.log.WithError(err).Warn("Failed to create cluster schema. Possibly the cluster is offline.") + cfg.log.WarnContext(ctx, "Failed to create cluster schema, the cluster may be offline", "error", err) // If the cluster is offline, we will not be able to create the codec factory // and the list of supported types for RBAC. // We mark the cluster as offline and continue to create the kubeDetails but @@ -145,7 +145,7 @@ func newClusterDetails(ctx context.Context, cfg clusterDetailsConfig) (_ *kubeDe kubeVersion, err := creds.getKubeClient().Discovery().ServerVersion() if err != nil { - cfg.log.WithError(err).Warn("Failed to get Kubernetes cluster version. Possibly the cluster is offline.") + cfg.log.WarnContext(ctx, "Failed to get Kubernetes cluster version, the cluster may be offline", "error", err) } ctx, cancel := context.WithCancel(ctx) @@ -198,13 +198,13 @@ func newClusterDetails(ctx context.Context, cfg clusterDetailsConfig) (_ *kubeDe } else { refreshDelay.Inc() } - cfg.log.WithError(err).Error("Failed to update cluster schema") + cfg.log.ErrorContext(ctx, "Failed to update cluster schema", "error", err) continue } kubeVersion, err := creds.getKubeClient().Discovery().ServerVersion() if err != nil { - cfg.log.WithError(err).Warn("Failed to get Kubernetes cluster version. Possibly the cluster is offline.") + cfg.log.WarnContext(ctx, "Failed to get Kubernetes cluster version, the cluster may be offline", "error", err) } // Restore details refresh delay to the default value, in case previously cluster was offline. @@ -389,7 +389,7 @@ func getAWSClientRestConfig(cloudClients cloud.Clients, clock clockwork.Clock, r // getStaticCredentialsFromKubeconfig loads a kubeconfig from the cluster and returns the access credentials for the cluster. // If the config defines multiple contexts, it will pick one (the order is not guaranteed). -func getStaticCredentialsFromKubeconfig(ctx context.Context, component KubeServiceType, cluster types.KubeCluster, log *logrus.Entry, checker servicecfg.ImpersonationPermissionsChecker) (*staticKubeCreds, error) { +func getStaticCredentialsFromKubeconfig(ctx context.Context, component KubeServiceType, cluster types.KubeCluster, log *slog.Logger, checker servicecfg.ImpersonationPermissionsChecker) (*staticKubeCreds, error) { config, err := clientcmd.Load(cluster.GetKubeconfig()) if err != nil { return nil, trace.WrapWithMessage(err, "unable to parse kubeconfig for cluster %q", cluster.GetName()) diff --git a/lib/kube/proxy/cluster_details_test.go b/lib/kube/proxy/cluster_details_test.go index 116a575bc4143..9b52a695752da 100644 --- a/lib/kube/proxy/cluster_details_test.go +++ b/lib/kube/proxy/cluster_details_test.go @@ -26,7 +26,6 @@ import ( "time" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/version" @@ -34,12 +33,12 @@ import ( "k8s.io/client-go/kubernetes" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/utils" ) func TestNewClusterDetails(t *testing.T) { t.Parallel() ctx := context.Background() - log := logrus.New().WithContext(ctx) getClusterDetailsConfig := func(c clockwork.FakeClock) (clusterDetailsConfig, *clusterDetailsClientSet) { client := &clusterDetailsClientSet{} @@ -48,7 +47,7 @@ func TestNewClusterDetails(t *testing.T) { kubeClient: client, }, cluster: &types.KubernetesClusterV3{}, - log: log, + log: utils.NewSlogLoggerForTests(), clock: c, }, client } diff --git a/lib/kube/proxy/ephemeral_containers.go b/lib/kube/proxy/ephemeral_containers.go index 1c9ae08e417a4..61947055c0067 100644 --- a/lib/kube/proxy/ephemeral_containers.go +++ b/lib/kube/proxy/ephemeral_containers.go @@ -83,7 +83,7 @@ func (f *Forwarder) ephemeralContainers(authCtx *authContext, w http.ResponseWri if err != nil { // This error goes to kubernetes client and is not visible in the logs // of the teleport server if not logged here. - f.log.Errorf("Failed to create cluster session: %v.", err) + f.log.ErrorContext(req.Context(), "Failed to create cluster session", "error", err) return nil, trace.Wrap(err) } // sess.Close cancels the connection monitor context to release it sooner. @@ -101,7 +101,7 @@ func (f *Forwarder) ephemeralContainers(authCtx *authContext, w http.ResponseWri if err := f.setupForwardingHeaders(sess, req, true /* withImpersonationHeaders */); err != nil { // This error goes to kubernetes client and is not visible in the logs // of the teleport server if not logged here. - f.log.Errorf("Failed to set up forwarding headers: %v.", err) + f.log.ErrorContext(req.Context(), "Failed to set up forwarding headers", "error", err) return nil, trace.Wrap(err) } if !sess.isLocalKubernetesCluster { diff --git a/lib/kube/proxy/forwarder.go b/lib/kube/proxy/forwarder.go index b3df6d6c0b153..aeed6d7c631ac 100644 --- a/lib/kube/proxy/forwarder.go +++ b/lib/kube/proxy/forwarder.go @@ -41,7 +41,6 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" "github.com/julienschmidt/httprouter" - "github.com/sirupsen/logrus" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" oteltrace "go.opentelemetry.io/otel/trace" kubeerrors "k8s.io/apimachinery/pkg/api/errors" @@ -86,6 +85,7 @@ import ( "github.com/gravitational/teleport/lib/srv" "github.com/gravitational/teleport/lib/sshca" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) // KubeServiceType specifies a Teleport service type which can forward Kubernetes requests @@ -157,7 +157,7 @@ type ForwarderConfig struct { // PROXYSigner is used to sign PROXY headers for securely propagating client IP address PROXYSigner multiplexer.PROXYHeaderSigner // log is the logger function - log logrus.FieldLogger + log *slog.Logger // TracerProvider is used to create tracers capable // of starting spans. TracerProvider oteltrace.TracerProvider @@ -272,7 +272,7 @@ func (f *ForwarderConfig) CheckAndSetDefaults() error { f.KubeClusterName = f.ClusterName } if f.log == nil { - f.log = logrus.New() + f.log = slog.Default() } return nil } @@ -347,7 +347,7 @@ func NewForwarder(cfg ForwarderConfig) (*Forwarder, error) { fwd.router = instrumentHTTPHandler(fwd.cfg.KubeServiceType, router) if cfg.ClusterOverride != "" { - fwd.log.Debugf("Cluster override is set, forwarder will send all requests to remote cluster %v.", cfg.ClusterOverride) + fwd.log.DebugContext(closeCtx, "Cluster override is set, forwarder will send all requests to remote cluster", "cluster_override", cfg.ClusterOverride) } if len(cfg.KubeClusterName) > 0 || len(cfg.KubeconfigPath) > 0 || cfg.KubeServiceType != KubeService { if err := fwd.getKubeDetails(cfg.Context); err != nil { @@ -363,7 +363,7 @@ func NewForwarder(cfg ForwarderConfig) (*Forwarder, error) { // however some requests like exec sessions it intercepts and records. type Forwarder struct { mu sync.Mutex - log logrus.FieldLogger + log *slog.Logger router http.Handler cfg ForwarderConfig // activeRequests is a map used to serialize active CSR requests to the auth server @@ -540,7 +540,7 @@ func (f *Forwarder) authenticate(req *http.Request) (*authContext, error) { var isRemoteUser bool userTypeI, err := authz.UserFromContext(ctx) if err != nil { - f.log.WithError(err).Warn("error getting user from context") + f.log.WarnContext(ctx, "error getting user from context", "error", err) return nil, trace.AccessDenied(accessDeniedMsg) } switch userTypeI.(type) { @@ -549,10 +549,12 @@ func (f *Forwarder) authenticate(req *http.Request) (*authContext, error) { case authz.RemoteUser: isRemoteUser = true case authz.BuiltinRole: - f.log.Warningf("Denying proxy access to unauthenticated user of type %T - this can sometimes be caused by inadvertently using an HTTP load balancer instead of a TCP load balancer on the Kubernetes port.", userTypeI) + f.log.WarnContext(ctx, "Denying proxy access to unauthenticated user - this can sometimes be caused by inadvertently using an HTTP load balancer instead of a TCP load balancer on the Kubernetes port", + "user_type", logutils.TypeAttr(userTypeI), + ) return nil, trace.AccessDenied(accessDeniedMsg) default: - f.log.Warningf("Denying proxy access to unsupported user type: %T.", userTypeI) + f.log.WarnContext(ctx, "Denying proxy access to unsupported user type", "user_type", logutils.TypeAttr(userTypeI)) return nil, trace.AccessDenied(accessDeniedMsg) } @@ -563,7 +565,7 @@ func (f *Forwarder) authenticate(req *http.Request) (*authContext, error) { authContext, err := f.setupContext(ctx, *userContext, req, isRemoteUser) if err != nil { - f.log.WithError(err).Warn("Unable to setup context.") + f.log.WarnContext(ctx, "Unable to setup context", "error", err) if trace.IsAccessDenied(err) { return nil, trace.AccessDenied(accessDeniedMsg) } @@ -726,7 +728,7 @@ func (f *Forwarder) formatStatusResponseError(rw http.ResponseWriter, respErr er } data, err := runtime.Encode(globalKubeCodecs.LegacyCodec(), status) if err != nil { - f.log.Warningf("Failed encoding error into kube Status object: %v", err) + f.log.WarnContext(f.ctx, "Failed encoding error into kube Status object", "error", err) trace.WriteError(rw, respErr) return } @@ -737,7 +739,7 @@ func (f *Forwarder) formatStatusResponseError(rw http.ResponseWriter, respErr er // has prevented the request from succeeding`` instead of the correct reason. rw.WriteHeader(trace.ErrorToCode(respErr)) if _, err := rw.Write(data); err != nil { - f.log.Warningf("Failed writing kube error response body: %v", err) + f.log.WarnContext(f.ctx, "Failed writing kube error response body", "error", err) } } @@ -919,7 +921,7 @@ func (f *Forwarder) emitAuditEvent(req *http.Request, sess *clusterSession, stat r.populateEvent(event) if err := f.cfg.AuthClient.EmitAuditEvent(f.ctx, event); err != nil { - f.log.WithError(err).Warn("Failed to emit event.") + f.log.WarnContext(f.ctx, "Failed to emit event", "error", err) } } @@ -1033,13 +1035,17 @@ func (f *Forwarder) authorize(ctx context.Context, actx *authContext) error { if actx.teleportCluster.isRemote { // Authorization for a remote kube cluster will happen on the remote // end (by their proxy), after that cluster has remapped used roles. - f.log.WithField("auth_context", actx.String()).Debug("Skipping authorization for a remote kubernetes cluster name") + f.log.DebugContext(ctx, "Skipping authorization for a remote kubernetes cluster name", + "auth_context", logutils.StringerAttr(actx), + ) return nil } if actx.kubeClusterName == "" { // This should only happen for remote clusters (filtered above), but // check and report anyway. - f.log.WithField("auth_context", actx.String()).Debug("Skipping authorization due to unknown kubernetes cluster name") + f.log.DebugContext(ctx, "Skipping authorization due to unknown kubernetes cluster name", + "auth_context", logutils.StringerAttr(actx), + ) return nil } @@ -1135,7 +1141,9 @@ func (f *Forwarder) authorize(ctx context.Context, actx *authContext) error { return nil } if actx.kubeClusterName == f.cfg.ClusterName { - f.log.WithField("auth_context", actx.String()).Debug("Skipping authorization for proxy-based kubernetes cluster,") + f.log.DebugContext(ctx, "Skipping authorization for proxy-based kubernetes cluster", + "auth_context", logutils.StringerAttr(actx), + ) return nil } return trace.AccessDenied(notFoundMessage) @@ -1168,7 +1176,7 @@ func (f *Forwarder) join(ctx *authContext, w http.ResponseWriter, req *http.Requ joinSessionsInFlightGauge.WithLabelValues(f.cfg.KubeServiceType).Inc() defer joinSessionsInFlightGauge.WithLabelValues(f.cfg.KubeServiceType).Dec() - f.log.Debugf("Join %v.", req.URL.String()) + f.log.DebugContext(req.Context(), "Joining session", "join_url", logutils.StringerAttr(req.URL)) sess, err := f.newClusterSession(req.Context(), *ctx) if err != nil { @@ -1241,7 +1249,11 @@ func (f *Forwarder) join(ctx *authContext, w http.ResponseWriter, req *http.Requ close(closeC) if _, err := session.leave(party.ID); err != nil { - f.log.WithError(err).Debugf("Participant %q was unable to leave session %s", party.ID, session.id) + f.log.DebugContext(req.Context(), "Participant was unable to leave session", + "participant_id", party.ID, + "session_id", session.id, + "error", err, + ) } wg.Wait() @@ -1249,7 +1261,7 @@ func (f *Forwarder) join(ctx *authContext, w http.ResponseWriter, req *http.Requ }(); err != nil { writeErr := ws.WriteControl(gwebsocket.CloseMessage, gwebsocket.FormatCloseMessage(gwebsocket.CloseInternalServerErr, err.Error()), time.Now().Add(time.Second*10)) if writeErr != nil { - f.log.WithError(writeErr).Warn("Failed to send early-exit websocket close message.") + f.log.WarnContext(req.Context(), "Failed to send early-exit websocket close message", "error", writeErr) } } @@ -1337,7 +1349,7 @@ func (f *Forwarder) remoteJoin(ctx *authContext, w http.ResponseWriter, req *htt } defer wsSource.Close() - wsProxy(f.log, wsSource, wsTarget) + wsProxy(req.Context(), f.log, wsSource, wsTarget) return nil, nil } @@ -1362,7 +1374,7 @@ func (f *Forwarder) getSessionHostID(ctx context.Context, authCtx *authContext, // wsProxy proxies a websocket connection between two clusters transparently to allow for // remote joins. -func wsProxy(log logrus.FieldLogger, wsSource *gwebsocket.Conn, wsTarget *gwebsocket.Conn) { +func wsProxy(ctx context.Context, log *slog.Logger, wsSource *gwebsocket.Conn, wsTarget *gwebsocket.Conn) { errS := make(chan error, 1) errT := make(chan error, 1) wg := &sync.WaitGroup{} @@ -1416,7 +1428,7 @@ func wsProxy(log logrus.FieldLogger, wsSource *gwebsocket.Conn, wsTarget *gwebso var websocketErr *gwebsocket.CloseError if errors.As(err, &websocketErr) && websocketErr.Code == gwebsocket.CloseAbnormalClosure { - log.WithError(err).Debugf("websocket proxy: Error when copying from %s to %s", from, to) + log.DebugContext(ctx, "websocket proxying failed", "src", from, "target", to, "error", err) } wg.Wait() } @@ -1496,7 +1508,7 @@ func (f *Forwarder) execNonInteractive(ctx *authContext, req *http.Request, _ ht } if err := f.cfg.Emitter.EmitAuditEvent(f.ctx, sessionStartEvent); err != nil { - f.log.WithError(err).Warn("Failed to emit event.") + f.log.WarnContext(f.ctx, "Failed to emit event", "error", err) return trace.Wrap(err) } @@ -1518,7 +1530,7 @@ func (f *Forwarder) execNonInteractive(ctx *authContext, req *http.Request, _ ht defer func() { if err := f.cfg.Emitter.EmitAuditEvent(f.ctx, execEvent); err != nil { - f.log.WithError(err).Warn("Failed to emit exec event.") + f.log.WarnContext(f.ctx, "Failed to emit exec event", "error", err) } sessionEndEvent := &apievents.SessionEnd{ @@ -1541,7 +1553,7 @@ func (f *Forwarder) execNonInteractive(ctx *authContext, req *http.Request, _ ht } if err := f.cfg.Emitter.EmitAuditEvent(f.ctx, sessionEndEvent); err != nil { - f.log.WithError(err).Warn("Failed to emit session end event.") + f.log.WarnContext(f.ctx, "Failed to emit session end event", "error", err) } }() @@ -1550,7 +1562,7 @@ func (f *Forwarder) execNonInteractive(ctx *authContext, req *http.Request, _ ht execEvent.Code = events.ExecFailureCode execEvent.Error, execEvent.ExitCode = exitCode(err) - f.log.WithError(err).Warning("Failed creating executor.") + f.log.WarnContext(f.ctx, "Failed creating executor", "error", err) return trace.Wrap(err) } @@ -1560,7 +1572,7 @@ func (f *Forwarder) execNonInteractive(ctx *authContext, req *http.Request, _ ht execEvent.Code = events.ExecFailureCode execEvent.Error, execEvent.ExitCode = exitCode(err) - f.log.WithError(err).Warning("Executor failed while streaming.") + f.log.WarnContext(f.ctx, "Executor failed while streaming", "error", err) return trace.Wrap(err) } @@ -1629,10 +1641,10 @@ func (f *Forwarder) exec(authCtx *authContext, w http.ResponseWriter, req *http. ) defer span.End() - f.log.Debugf("Exec %v.", req.URL.String()) + f.log.DebugContext(ctx, "Starting exec", "exec_url", logutils.StringerAttr(req.URL)) defer func() { if err != nil { - f.log.WithError(err).Debug("Exec request failed") + f.log.DebugContext(ctx, "Exec request failed", "error", err) } }() @@ -1640,7 +1652,7 @@ func (f *Forwarder) exec(authCtx *authContext, w http.ResponseWriter, req *http. if err != nil { // This error goes to kubernetes client and is not visible in the logs // of the teleport server if not logged here. - f.log.Errorf("Failed to create cluster session: %v.", err) + f.log.ErrorContext(ctx, "Failed to create cluster session", "error", err) return nil, trace.Wrap(err) } // sess.Close cancels the connection monitor context to release it sooner. @@ -1702,7 +1714,11 @@ func (f *Forwarder) exec(authCtx *authContext, w http.ResponseWriter, req *http. err = <-party.closeC if _, errLeave := session.leave(party.ID); errLeave != nil { - f.log.WithError(errLeave).Debugf("Participant %q was unable to leave session %s", party.ID, session.id) + f.log.DebugContext(ctx, "Participant was unable to leave session", + "participant_id", party.ID, + "session_id", session.id, + "error", errLeave, + ) } return trace.Wrap(err) @@ -1714,13 +1730,13 @@ func (f *Forwarder) exec(authCtx *authContext, w http.ResponseWriter, req *http. func (f *Forwarder) remoteExec(req *http.Request, sess *clusterSession, proxy *remoteCommandProxy) error { executor, err := f.getExecutor(sess, req) if err != nil { - f.log.WithError(err).Warning("Failed creating executor.") + f.log.WarnContext(req.Context(), "Failed creating executor", "error", err) return trace.Wrap(err) } streamOptions := proxy.options() err = executor.StreamWithContext(req.Context(), streamOptions) if err != nil { - f.log.WithError(err).Warning("Executor failed while streaming.") + f.log.WarnContext(req.Context(), "Executor failed while streaming", "error", err) } return trace.Wrap(err) @@ -1745,12 +1761,15 @@ func (f *Forwarder) portForward(authCtx *authContext, w http.ResponseWriter, req ) defer span.End() - f.log.Debugf("Port forward: %v. req headers: %v.", req.URL.String(), req.Header) + f.log.DebugContext(ctx, "Handling port forward request", + "request_url", logutils.StringerAttr(req.URL), + "request_headers", req.Header, + ) sess, err := f.newClusterSession(ctx, *authCtx) if err != nil { // This error goes to kubernetes client and is not visible in the logs // of the teleport server if not logged here. - f.log.Errorf("Failed to create cluster session: %v.", err) + f.log.ErrorContext(ctx, "Failed to create cluster session", "error", err) return nil, trace.Wrap(err) } // sess.Close cancels the connection monitor context to release it sooner. @@ -1765,7 +1784,7 @@ func (f *Forwarder) portForward(authCtx *authContext, w http.ResponseWriter, req } if err := f.setupForwardingHeaders(sess, req, true /* withImpersonationHeaders */); err != nil { - f.log.Debugf("DENIED Port forward: %v.", req.URL.String()) + f.log.DebugContext(ctx, "DENIED Port forward", "request_url", logutils.StringerAttr(req.URL)) return nil, trace.Wrap(err) } @@ -1805,7 +1824,7 @@ func (f *Forwarder) portForward(authCtx *authContext, w http.ResponseWriter, req portForward.Code = events.PortForwardFailureCode } if err := f.cfg.Emitter.EmitAuditEvent(f.ctx, portForward); err != nil { - f.log.WithError(err).Warn("Failed to emit event.") + f.log.WarnContext(ctx, "Failed to emit event", "error", err) } } defer func() { @@ -1829,7 +1848,7 @@ func (f *Forwarder) portForward(authCtx *authContext, w http.ResponseWriter, req }, } if err := f.cfg.Emitter.EmitAuditEvent(f.ctx, portForward); err != nil { - f.log.WithError(err).Warn("Failed to emit event.") + f.log.WarnContext(ctx, "Failed to emit event", "error", err) } } }() @@ -1847,12 +1866,12 @@ func (f *Forwarder) portForward(authCtx *authContext, w http.ResponseWriter, req pingPeriod: f.cfg.ConnPingPeriod, idleTimeout: sess.clientIdleTimeout, } - f.log.Debugf("Starting %v.", request) + f.log.DebugContext(ctx, "Starting port forwarding", "request", request) err = runPortForwarding(request) if err != nil { return nil, trace.Wrap(err) } - f.log.Debugf("Done %v.", request) + f.log.DebugContext(ctx, "Completed port forwarding", "request", request) return nil, nil } @@ -2057,11 +2076,11 @@ func (f *Forwarder) catchAll(authCtx *authContext, w http.ResponseWriter, req *h req = req.WithContext(ctx) defer span.End() - sess, err := f.newClusterSession(req.Context(), *authCtx) + sess, err := f.newClusterSession(ctx, *authCtx) if err != nil { // This error goes to kubernetes client and is not visible in the logs // of the teleport server if not logged here. - f.log.Errorf("Failed to create cluster session: %v.", err) + f.log.ErrorContext(ctx, "Failed to create cluster session", "error", err) return nil, trace.Wrap(err) } // sess.Close cancels the connection monitor context to release it sooner. @@ -2079,7 +2098,7 @@ func (f *Forwarder) catchAll(authCtx *authContext, w http.ResponseWriter, req *h if err := f.setupForwardingHeaders(sess, req, true /* withImpersonationHeaders */); err != nil { // This error goes to kubernetes client and is not visible in the logs // of the teleport server if not logged here. - f.log.Errorf("Failed to set up forwarding headers: %v.", err) + f.log.ErrorContext(ctx, "Failed to set up forwarding headers", "error", err) return nil, trace.Wrap(err) } @@ -2151,7 +2170,10 @@ func (f *Forwarder) getWebsocketRestConfig(sess *clusterSession, req *http.Reque } func (f *Forwarder) getWebsocketExecutor(sess *clusterSession, req *http.Request) (remotecommand.Executor, error) { - f.log.Debugf("Creating websocket remote executor for request %s %s", req.Method, req.RequestURI) + f.log.DebugContext(req.Context(), "Creating websocket remote executor for request", + "request_method", req.Method, + "request_uri", req.RequestURI, + ) cfg, err := f.getWebsocketRestConfig(sess, req) if err != nil { return nil, trace.Wrap(err, "unable to create websocket executor") @@ -2190,7 +2212,10 @@ func (f *Forwarder) getExecutor(sess *clusterSession, req *http.Request) (remote } func (f *Forwarder) getSPDYExecutor(sess *clusterSession, req *http.Request) (remotecommand.Executor, error) { - f.log.Debugf("Creating SPDY remote executor for request %s %s", req.Method, req.RequestURI) + f.log.DebugContext(req.Context(), "Creating SPDY remote executor for request", + "request_method", req.Method, + "request_uri", req.RequestURI, + ) tlsConfig, useImpersonation, err := f.getTLSConfig(sess) if err != nil { @@ -2381,11 +2406,9 @@ func (s *clusterSession) monitorConn(conn net.Conn, err error, hostID string) (n Context: s.connCtx, TeleportUser: s.User.GetName(), ServerID: s.parent.cfg.HostID, - // TODO(tross) update this to use the child logger - // once Forwarder is converted to use a slog.Logger - Logger: slog.Default(), - Emitter: s.parent.cfg.AuthClient, - EmitterContext: s.parent.ctx, + Logger: s.parent.log, + Emitter: s.parent.cfg.AuthClient, + EmitterContext: s.parent.ctx, }) if err != nil { tc.CloseWithCause(err) @@ -2462,7 +2485,7 @@ func (f *Forwarder) newClusterSession(ctx context.Context, authCtx authContext) } func (f *Forwarder) newClusterSessionRemoteCluster(ctx context.Context, authCtx authContext) (*clusterSession, error) { - f.log.Debugf("Forwarding kubernetes session for %v to remote cluster.", authCtx) + f.log.DebugContext(ctx, "Forwarding kubernetes session to remote cluster", "auth_context", logutils.StringerAttr(authCtx)) connCtx, cancel := context.WithCancelCause(ctx) return &clusterSession{ parent: f, @@ -2511,7 +2534,7 @@ func (f *Forwarder) newClusterSessionLocal(ctx context.Context, authCtx authCont return nil, trace.Wrap(err) } connCtx, cancel := context.WithCancelCause(ctx) - f.log.Debugf("Handling kubernetes session for %v using local credentials.", authCtx) + f.log.DebugContext(ctx, "Handling kubernetes session using local credentials", "auth_context", logutils.StringerAttr(authCtx)) return &clusterSession{ parent: f, authContext: authCtx, @@ -2550,8 +2573,7 @@ func (f *Forwarder) makeSessionForwarder(sess *clusterSession) (*reverseproxy.Fo opts := []reverseproxy.Option{ reverseproxy.WithFlushInterval(100 * time.Millisecond), reverseproxy.WithRoundTripper(transport), - // TODO(tross): convert this to use f.log once it has been converted to use slog - reverseproxy.WithLogger(slog.Default()), + reverseproxy.WithLogger(f.log), reverseproxy.WithErrorHandler(f.formatForwardResponseError), } if sess.isLocalKubernetesCluster { diff --git a/lib/kube/proxy/forwarder_test.go b/lib/kube/proxy/forwarder_test.go index ef10408506f5d..860746ab97213 100644 --- a/lib/kube/proxy/forwarder_test.go +++ b/lib/kube/proxy/forwarder_test.go @@ -40,7 +40,6 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" "github.com/julienschmidt/httprouter" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" kubeerrors "k8s.io/apimachinery/pkg/api/errors" @@ -132,7 +131,7 @@ func TestAuthenticate(t *testing.T) { }, } f := &Forwarder{ - log: logrus.NewEntry(logrus.New()), + log: utils.NewSlogLoggerForTests(), cfg: ForwarderConfig{ ClusterName: "local", CachingAuthClient: ap, @@ -1112,7 +1111,7 @@ func newMockForwader(ctx context.Context, t *testing.T) *Forwarder { require.NoError(t, err) return &Forwarder{ - log: logrus.NewEntry(logrus.New()), + log: utils.NewSlogLoggerForTests(), router: httprouter.New(), cfg: ForwarderConfig{ Keygen: testauthority.New(), @@ -1316,7 +1315,7 @@ func (m *mockWatcher) Done() <-chan struct{} { func newTestForwarder(ctx context.Context, cfg ForwarderConfig) *Forwarder { return &Forwarder{ - log: logrus.NewEntry(logrus.New()), + log: utils.NewSlogLoggerForTests(), router: httprouter.New(), cfg: cfg, activeRequests: make(map[string]context.Context), @@ -1676,7 +1675,7 @@ func TestForwarderTLSConfigCAs(t *testing.T) { return x509.NewCertPool(), nil }, }, - log: logrus.NewEntry(logrus.New()), + log: utils.NewSlogLoggerForTests(), ctx: context.Background(), } diff --git a/lib/kube/proxy/kube_creds.go b/lib/kube/proxy/kube_creds.go index 19fd6edb2bd69..fd7e367d5f8cf 100644 --- a/lib/kube/proxy/kube_creds.go +++ b/lib/kube/proxy/kube_creds.go @@ -21,13 +21,13 @@ package proxy import ( "context" "crypto/tls" + "log/slog" "net/http" "sync" "time" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/transport" @@ -151,7 +151,7 @@ type dynamicKubeCreds struct { ctx context.Context renewTicker clockwork.Ticker staticCreds *staticKubeCreds - log logrus.FieldLogger + log *slog.Logger closeC chan struct{} client dynamicCredsClient checker servicecfg.ImpersonationPermissionsChecker @@ -164,7 +164,7 @@ type dynamicKubeCreds struct { // dynamicCredsConfig contains configuration for dynamicKubeCreds. type dynamicCredsConfig struct { kubeCluster types.KubeCluster - log logrus.FieldLogger + log *slog.Logger client dynamicCredsClient checker servicecfg.ImpersonationPermissionsChecker clock clockwork.Clock @@ -224,7 +224,7 @@ func newDynamicKubeCreds(ctx context.Context, cfg dynamicCredsConfig) (*dynamicK return case <-dyn.renewTicker.Chan(): if err := dyn.renewClientset(cfg.kubeCluster); err != nil { - logrus.WithError(err).Warnf("Unable to renew cluster %q credentials.", cfg.kubeCluster.GetName()) + cfg.log.WarnContext(ctx, "Unable to renew cluster credentials", "cluster", cfg.kubeCluster.GetName(), "error", err) } } } diff --git a/lib/kube/proxy/kube_creds_test.go b/lib/kube/proxy/kube_creds_test.go index ef8950691c0a1..b032964021b73 100644 --- a/lib/kube/proxy/kube_creds_test.go +++ b/lib/kube/proxy/kube_creds_test.go @@ -30,19 +30,19 @@ import ( "github.com/aws/aws-sdk-go/service/eks" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" authztypes "k8s.io/client-go/kubernetes/typed/authorization/v1" "k8s.io/client-go/rest" "github.com/gravitational/teleport/api/types" - "github.com/gravitational/teleport/api/utils" + apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/lib/cloud" "github.com/gravitational/teleport/lib/cloud/azure" "github.com/gravitational/teleport/lib/cloud/gcp" "github.com/gravitational/teleport/lib/cloud/mocks" "github.com/gravitational/teleport/lib/fixtures" "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/utils" ) // Test_DynamicKubeCreds tests the dynamic kube credrentials generator for @@ -54,7 +54,6 @@ func Test_DynamicKubeCreds(t *testing.T) { t.Parallel() var ( fakeClock = clockwork.NewFakeClock() - log = logrus.New() notify = make(chan struct{}, 1) ttl = 14 * time.Minute ) @@ -303,7 +302,7 @@ func Test_DynamicKubeCreds(t *testing.T) { ) error { return nil }, - log: log, + log: utils.NewSlogLoggerForTests(), kubeCluster: tt.args.cluster, client: tt.args.client, initialRenewInterval: ttl / 2, @@ -332,8 +331,8 @@ func Test_DynamicKubeCreds(t *testing.T) { } require.NoError(t, got.close()) - require.Equal(t, tt.wantAssumedRole, utils.Deduplicate(sts.GetAssumedRoleARNs())) - require.Equal(t, tt.wantExternalIds, utils.Deduplicate(sts.GetAssumedRoleExternalIDs())) + require.Equal(t, tt.wantAssumedRole, apiutils.Deduplicate(sts.GetAssumedRoleARNs())) + require.Equal(t, tt.wantExternalIds, apiutils.Deduplicate(sts.GetAssumedRoleExternalIDs())) sts.ResetAssumeRoleHistory() }) } diff --git a/lib/kube/proxy/portforward_spdy.go b/lib/kube/proxy/portforward_spdy.go index 561750239250d..1745536fc44f1 100644 --- a/lib/kube/proxy/portforward_spdy.go +++ b/lib/kube/proxy/portforward_spdy.go @@ -19,6 +19,7 @@ package proxy import ( "context" "fmt" + "log/slog" "net" "net/http" "strconv" @@ -26,7 +27,6 @@ import ( "time" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/util/httpstream" spdystream "k8s.io/apimachinery/pkg/util/httpstream/spdy" @@ -91,10 +91,10 @@ func runPortForwardingHTTPStreams(req portForwardRequest) error { defer conn.Close() h := &portForwardProxy{ - Entry: log.WithFields(log.Fields{ - teleport.ComponentKey: teleport.Component(teleport.ComponentProxyKube), - events.RemoteAddr: req.httpRequest.RemoteAddr, - }), + logger: slog.With( + teleport.ComponentKey, teleport.Component(teleport.ComponentProxyKube), + events.RemoteAddr, req.httpRequest.RemoteAddr, + ), portForwardRequest: req, sourceConn: conn, streamChan: streamChan, @@ -104,7 +104,7 @@ func runPortForwardingHTTPStreams(req portForwardRequest) error { } defer h.Close() - h.Debugf("Setting port forwarding streaming connection idle timeout to %s.", req.idleTimeout) + h.logger.DebugContext(req.context, "Setting port forwarding streaming connection idle timeout", "idle_timeout", req.idleTimeout) conn.SetIdleTimeout(req.idleTimeout) h.run() @@ -149,7 +149,7 @@ func httpStreamReceived(ctx context.Context, streams chan httpstream.Stream) fun // portForwardProxy is capable of processing multiple port forward // requests over a single httpstream.Connection. type portForwardProxy struct { - *log.Entry + logger *slog.Logger portForwardRequest sourceConn httpstream.Connection streamChan chan httpstream.Stream @@ -200,7 +200,7 @@ func (h *portForwardProxy) forwardStreamPair(p *httpStreamPair, remotePort int64 go func() { defer wg.Done() if err := utils.ProxyConn(h.context, p.errorStream, targetErrorStream); err != nil { - h.WithError(err).Debugf("Unable to proxy portforward error-stream.") + h.logger.DebugContext(h.context, "Unable to proxy portforward error-stream", "error", err) } }() @@ -222,14 +222,14 @@ func (h *portForwardProxy) forwardStreamPair(p *httpStreamPair, remotePort int64 go func() { defer wg.Done() if err := utils.ProxyConn(h.context, p.dataStream, targetDataStream); err != nil { - h.WithError(err).Debugf("Unable to proxy portforward data-stream.") + h.logger.DebugContext(h.context, "Unable to proxy portforward data-stream", "error", err) } }() - h.Debugf("Streams have been created, Waiting for copy to complete.") + h.logger.DebugContext(h.context, "Streams have been created, Waiting for copy to complete") // wait for the copies to complete before returning. wg.Wait() - h.Debugf("Port forwarding pair completed.") + h.logger.DebugContext(h.context, "Port forwarding pair completed") return nil } @@ -241,11 +241,11 @@ func (h *portForwardProxy) getStreamPair(requestID string) (*httpStreamPair, boo defer h.streamPairsLock.Unlock() if p, ok := h.streamPairs[requestID]; ok { - log.Debugf("Request %s, found existing stream pair", requestID) + h.logger.DebugContext(h.context, "Found existing stream pair for request", "request_id", requestID) return p, false } - h.Debugf("Request %s, creating new stream pair.", requestID) + h.logger.DebugContext(h.context, "Creating new stream pair for request", "request_id", requestID) p := newPortForwardPair(requestID) h.streamPairs[requestID] = p @@ -261,9 +261,9 @@ func (h *portForwardProxy) monitorStreamPair(p *httpStreamPair) { defer timeC.Stop() select { case <-timeC.C: - h.Errorf("Request %s, timed out waiting for streams.", p.requestID) + h.logger.ErrorContext(h.context, "Request timed out waiting for streams", "request_id", p.requestID) case <-p.complete: - h.Debugf("Request %s, successfully received error and data streams.", p.requestID) + h.logger.DebugContext(h.context, "Request successfully received error and data streams", "request_id", p.requestID) } h.removeStreamPair(p.requestID) } @@ -296,23 +296,24 @@ func (h *portForwardProxy) requestID(stream httpstream.Stream) (string, error) { // streams, invoking portForward for each complete stream pair. The loop exits // when the httpstream.Connection is closed. func (h *portForwardProxy) run() { - h.Debugf("Waiting for port forward streams.") + h.logger.DebugContext(h.context, "Waiting for port forward streams") for { select { case <-h.context.Done(): - h.Debugf("Context is closing, returning.") + h.logger.DebugContext(h.context, "Context is closing, returning") return case <-h.sourceConn.CloseChan(): - h.Debugf("Upgraded connection closed.") + h.logger.DebugContext(h.context, "Upgraded connection closed") return case stream := <-h.streamChan: requestID, err := h.requestID(stream) if err != nil { - h.Warningf("Failed to parse request id: %v.", err) + h.logger.WarnContext(h.context, "Failed to parse request id", "error", err) return } + streamType := stream.Headers().Get(StreamType) - h.Debugf("Received new stream %v of type %v.", requestID, streamType) + h.logger.DebugContext(h.context, "Received new stream", "request_id", requestID, "stream_type", streamType) p, created := h.getStreamPair(requestID) if created { @@ -336,13 +337,15 @@ func (h *portForwardProxy) portForward(p *httpStreamPair) { portString := p.dataStream.Headers().Get(PortHeader) port, _ := strconv.ParseInt(portString, 10, 32) - h.Debugf("Forwarding port %v -> %v.", p.requestID, portString) + logger := h.logger.With("request_id", p.requestID, "port", portString) + + logger.DebugContext(h.context, "Forwarding port") if err := h.forwardStreamPair(p, port); err != nil { - h.WithError(err).Debugf("Error forwarding port %v -> %v.", p.requestID, portString) + logger.DebugContext(h.context, "Error forwarding port", "error", err) return } - h.Debugf("Completed forwarding port %v -> %v.", p.requestID, portString) + h.logger.DebugContext(h.context, "Completed forwarding port") } // httpStreamPair represents the error and data streams for a port diff --git a/lib/kube/proxy/portforward_test.go b/lib/kube/proxy/portforward_test.go index b923ca591129e..7dc7a147e22b7 100644 --- a/lib/kube/proxy/portforward_test.go +++ b/lib/kube/proxy/portforward_test.go @@ -31,7 +31,6 @@ import ( "time" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" kubeerrors "k8s.io/apimachinery/pkg/api/errors" @@ -43,6 +42,7 @@ import ( "k8s.io/client-go/transport/spdy" testingkubemock "github.com/gravitational/teleport/lib/kube/proxy/testing/kube_server" + "github.com/gravitational/teleport/lib/utils" ) func TestPortForwardKubeService(t *testing.T) { @@ -269,7 +269,6 @@ type portForwarder interface { // connection, it will leak memory. func TestPortForwardProxy_run_connsClosed(t *testing.T) { t.Parallel() - logger := log.NewEntry(&log.Logger{Out: io.Discard}) const ( reqID = "reqID" // portHeaderValue is the value of the port header in the stream. @@ -285,7 +284,7 @@ func TestPortForwardProxy_run_connsClosed(t *testing.T) { context: context.Background(), onPortForward: func(addr string, success bool) {}, }, - Entry: logger, + logger: utils.NewSlogLoggerForTests(), sourceConn: sourceConn, targetConn: targetConn, streamChan: make(chan httpstream.Stream), diff --git a/lib/kube/proxy/portforward_websocket.go b/lib/kube/proxy/portforward_websocket.go index af1cb04ab525a..c2cb4cb6c97a9 100644 --- a/lib/kube/proxy/portforward_websocket.go +++ b/lib/kube/proxy/portforward_websocket.go @@ -23,13 +23,13 @@ import ( "encoding/binary" "fmt" "io" + "log/slog" "net/http" "strings" "sync" gwebsocket "github.com/gorilla/websocket" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/util/httpstream" spdystream "k8s.io/apimachinery/pkg/util/httpstream/spdy" "k8s.io/apimachinery/pkg/util/httpstream/wsstream" @@ -148,10 +148,10 @@ func runPortForwardingWebSocket(req portForwardRequest) error { podName: req.podName, targetConn: targetConn, onPortForward: req.onPortForward, - FieldLogger: logrus.WithFields(logrus.Fields{ - teleport.ComponentKey: teleport.Component(teleport.ComponentProxyKube), - events.RemoteAddr: req.httpRequest.RemoteAddr, - }), + logger: slog.With( + teleport.ComponentKey, teleport.Component(teleport.ComponentProxyKube), + events.RemoteAddr, req.httpRequest.RemoteAddr, + ), context: req.context, } // run the portforward request until termination. @@ -213,8 +213,8 @@ type websocketPortforwardHandler struct { podName string targetConn httpstream.Connection onPortForward portForwardCallback - logrus.FieldLogger - context context.Context + logger *slog.Logger + context context.Context } // run invokes the targetConn SPDY connection and copies the client data into @@ -237,10 +237,12 @@ func (h *websocketPortforwardHandler) run() { // portForward copies the client and upstream streams. func (h *websocketPortforwardHandler) portForward(p *websocketChannelPair) { - h.Debugf("Forwarding port %v -> %v.", p.requestID, p.port) + logger := h.logger.With("request_id", p.requestID, "port", p.port) + + logger.DebugContext(h.context, "Forwarding port") h.forwardStreamPair(p) - h.Debugf("Completed forwarding port %v -> %v.", p.requestID, p.port) + logger.DebugContext(h.context, "Completed forwarding port") } func (h *websocketPortforwardHandler) forwardStreamPair(p *websocketChannelPair) { @@ -269,7 +271,7 @@ func (h *websocketPortforwardHandler) forwardStreamPair(p *websocketChannelPair) go func() { defer wg.Done() if err := utils.ProxyConn(h.context, p.errorStream, targetErrorStream); err != nil { - h.WithError(err).Debugf("Unable to proxy portforward error-stream.") + h.logger.DebugContext(h.context, "Unable to proxy portforward error-stream", "error", err) } }() @@ -292,15 +294,15 @@ func (h *websocketPortforwardHandler) forwardStreamPair(p *websocketChannelPair) go func() { defer wg.Done() if err := utils.ProxyConn(h.context, p.dataStream, targetDataStream); err != nil { - h.WithError(err).Debugf("Unable to proxy portforward data-stream.") + h.logger.DebugContext(h.context, "Unable to proxy portforward data-stream", "error", err) } }() - h.Debugf("Streams have been created, Waiting for copy to complete.") + h.logger.DebugContext(h.context, "Streams have been created, Waiting for copy to complete") // Wait until every goroutine exits. wg.Wait() - h.Debugf("Port forwarding pair completed.") + h.logger.DebugContext(h.context, "Port forwarding pair completed") } // runPortForwardingTunneledHTTPStreams handles a port-forwarding request that uses SPDY protocol @@ -341,10 +343,10 @@ func runPortForwardingTunneledHTTPStreams(req portForwardRequest) error { defer conn.Close() h := &portForwardProxy{ - Entry: logrus.WithFields(logrus.Fields{ - teleport.ComponentKey: teleport.Component(teleport.ComponentProxyKube), - events.RemoteAddr: req.httpRequest.RemoteAddr, - }), + logger: slog.With( + teleport.ComponentKey, teleport.Component(teleport.ComponentProxyKube), + events.RemoteAddr, req.httpRequest.RemoteAddr, + ), portForwardRequest: req, sourceConn: spdyConn, streamChan: streamChan, @@ -354,7 +356,7 @@ func runPortForwardingTunneledHTTPStreams(req portForwardRequest) error { } defer h.Close() - h.Debugf("Setting port forwarding streaming connection idle timeout to %s.", req.idleTimeout) + h.logger.DebugContext(context.Background(), "Setting port forwarding streaming connection idle timeout to", "idle_timeout", req.idleTimeout) spdyConn.SetIdleTimeout(req.idleTimeout) h.run() diff --git a/lib/kube/proxy/remotecommand.go b/lib/kube/proxy/remotecommand.go index 09a9c868b43ca..2cd03c870d70b 100644 --- a/lib/kube/proxy/remotecommand.go +++ b/lib/kube/proxy/remotecommand.go @@ -22,12 +22,12 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "strings" "time" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -75,7 +75,7 @@ func (req remoteCommandRequest) eventPodMeta(ctx context.Context, creds kubeCred // here shouldn't prevent a session from starting. pod, err := creds.getKubeClient().CoreV1().Pods(req.podNamespace).Get(ctx, req.podName, metav1.GetOptions{}) if err != nil { - log.WithError(err).Debugf("Failed fetching pod from kubernetes API; skipping additional metadata on the audit event") + slog.DebugContext(ctx, "Failed fetching pod from kubernetes API; skipping additional metadata on the audit event", "error", err) return meta } meta.KubernetesNodeName = pod.Spec.NodeName @@ -121,7 +121,7 @@ func upgradeRequestToRemoteCommandProxy(req remoteCommandRequest, exec func(*rem err = nil } if err := proxy.sendStatus(err); err != nil { - log.Warningf("Failed to send status: %v", err) + slog.WarnContext(req.context, "Failed to send status", "error", err) } // return rsp=nil, err=nil to indicate that the request has been handled // by the hijacked connection. If we return an error, the request will be @@ -162,10 +162,10 @@ func createSPDYStreams(req remoteCommandRequest) (*remoteCommandProxy, error) { var handler protocolHandler switch protocol { case "": - log.Warningf("Client did not request protocol negotiation.") + slog.WarnContext(ctx, "Client did not request protocol negotiation") fallthrough case StreamProtocolV4Name: - log.Infof("Negotiated protocol %v.", protocol) + slog.InfoContext(ctx, "Negotiated protocol", "protocol", protocol) handler = &v4ProtocolHandler{} default: err = trace.BadParameter("protocol %v is not supported. upgrade the client", protocol) @@ -357,7 +357,7 @@ func (t *termQueue) handleResizeEvents(stream io.Reader) { size := remotecommand.TerminalSize{} if err := decoder.Decode(&size); err != nil { if !errors.Is(err, io.EOF) { - log.Warningf("Failed to decode resize event: %v", err) + slog.WarnContext(t.done, "Failed to decode resize event", "error", err) } t.cancel() return @@ -412,7 +412,7 @@ WaitForStreams: remoteProxy.resizeStream = stream go waitStreamReply(stopCtx, stream.replySent, replyChan) default: - log.Warningf("Ignoring unexpected stream type: %q", streamType) + slog.WarnContext(stopCtx, "Ignoring unexpected stream type", "stream_type", streamType) } case <-replyChan: receivedStreams++ diff --git a/lib/kube/proxy/resource_deletecollection.go b/lib/kube/proxy/resource_deletecollection.go index f73c5f2043426..0e9fc22540c02 100644 --- a/lib/kube/proxy/resource_deletecollection.go +++ b/lib/kube/proxy/resource_deletecollection.go @@ -21,10 +21,10 @@ package proxy import ( "context" "io" + "log/slog" "net/http" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" oteltrace "go.opentelemetry.io/otel/trace" appsv1 "k8s.io/api/apps/v1" @@ -566,7 +566,7 @@ func (f *Forwarder) handleDeleteCustomResourceCollection(w http.ResponseWriter, type deleteResourcesCommonParams struct { ctx context.Context - log logrus.FieldLogger + log *slog.Logger authCtx *authContext header http.Header kubeDetails *kubeDetails diff --git a/lib/kube/proxy/resource_filters.go b/lib/kube/proxy/resource_filters.go index 735aab9648f9d..16d8a6076c2d4 100644 --- a/lib/kube/proxy/resource_filters.go +++ b/lib/kube/proxy/resource_filters.go @@ -20,12 +20,13 @@ package proxy import ( "bytes" + "context" "io" + "log/slog" "mime" "net/http" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" certificatesv1 "k8s.io/api/certificates/v1" @@ -50,7 +51,7 @@ import ( // - deniedResources: excluded if (namespace,name) matches an entry even if it matches // the allowedResources's list. // - allowedResources: excluded if (namespace,name) not match a single entry. -func newResourceFilterer(kind, verb string, codecs *serializer.CodecFactory, allowedResources, deniedResources []types.KubernetesResource, log logrus.FieldLogger) responsewriters.FilterWrapper { +func newResourceFilterer(kind, verb string, codecs *serializer.CodecFactory, allowedResources, deniedResources []types.KubernetesResource, log *slog.Logger) responsewriters.FilterWrapper { // If the list of allowed resources contains a wildcard and no deniedResources, then we // don't need to filter anything. if containsWildcard(allowedResources) && len(deniedResources) == 0 { @@ -113,7 +114,7 @@ type resourceFilterer struct { // deniedResources is the list of kubernetes resources the user must not access. deniedResources []types.KubernetesResource // log is the logger. - log logrus.FieldLogger + log *slog.Logger // kind is the type of the resource. kind string // verb is the kube API verb based on HTTP verb. @@ -176,6 +177,8 @@ func pointerArrayToArray[T any](arr []*T) []T { // with the object. // The isListObj boolean returned indicates if the object is a list of resources. func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList bool, err error) { + ctx := context.Background() + switch o := obj.(type) { case *metav1.Status: // Status object is returned when the Kubernetes API returns an error and @@ -184,7 +187,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *corev1.Pod: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -198,7 +201,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *corev1.Secret: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -212,7 +215,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *corev1.ConfigMap: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -226,7 +229,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *corev1.Namespace: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -240,7 +243,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *corev1.Service: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -254,7 +257,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *corev1.Endpoints: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -268,7 +271,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *corev1.ServiceAccount: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -282,7 +285,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *corev1.Node: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -296,7 +299,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *corev1.PersistentVolume: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -310,7 +313,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *corev1.PersistentVolumeClaim: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -325,7 +328,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *appsv1.Deployment: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -340,7 +343,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *appsv1.ReplicaSet: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -354,7 +357,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *appsv1.StatefulSet: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -369,7 +372,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *appsv1.DaemonSet: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -383,7 +386,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *authv1.ClusterRole: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -397,7 +400,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *authv1.Role: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -412,7 +415,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *authv1.ClusterRoleBinding: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -427,7 +430,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *authv1.RoleBinding: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -442,7 +445,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *batchv1.CronJob: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -457,7 +460,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *batchv1.Job: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -472,7 +475,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *certificatesv1.CertificateSigningRequest: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -486,7 +489,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *networkingv1.Ingress: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -500,7 +503,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *extensionsv1beta1.Ingress: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -515,7 +518,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *extensionsv1beta1.DaemonSet: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -530,7 +533,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *extensionsv1beta1.Deployment: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -545,7 +548,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList case *extensionsv1beta1.ReplicaSet: result, err := filterResource(d.kind, d.verb, o, d.allowedResources, d.deniedResources) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -569,7 +572,7 @@ func (d *resourceFilterer) FilterObj(obj runtime.Object) (isAllowed bool, isList d.allowedResources, d.deniedResources, ) if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expressions within kubernetes_resources.") + d.log.WarnContext(ctx, "Unable to compile regex expressions within kubernetes_resources", "error", err) } // if err is not nil or result is false, we should not include it. return result, false, nil @@ -632,13 +635,13 @@ func (d *resourceFilterer) encode(obj runtime.Object, w io.Writer) error { } // filterResourceList excludes resources the user should not have access to. -func filterResourceList[T kubeObjectInterface](kind, verb string, originalList []T, allowed, denied []types.KubernetesResource, log logrus.FieldLogger) []T { +func filterResourceList[T kubeObjectInterface](kind, verb string, originalList []T, allowed, denied []types.KubernetesResource, log *slog.Logger) []T { filteredList := make([]T, 0, len(originalList)) for _, resource := range originalList { if result, err := filterResource(kind, verb, resource, allowed, denied); err == nil && result { filteredList = append(filteredList, resource) } else if err != nil { - log.WithError(err).Warnf("Unable to compile regex expressions within kubernetes_resources.") + slog.WarnContext(context.Background(), "Unable to compile regex expressions within kubernetes_resources", "error", err) } } return filteredList @@ -686,7 +689,7 @@ func (d *resourceFilterer) filterMetaV1Table(table *metav1.Table, allowedResourc if result, err := matchKubernetesResource(resource, allowedResources, deniedResources); err == nil && result { resources = append(resources, *row) } else if err != nil { - d.log.WithError(err).Warn("Unable to compile regex expression.") + d.log.WarnContext(context.Background(), "Unable to compile regex expression", "error", err) } } table.Rows = resources @@ -799,7 +802,7 @@ func filterBuffer(filterWrapper responsewriters.FilterWrapper, src *responsewrit // filterUnstructuredList filters the unstructured list object to exclude resources // that the user must not have access to. // The filtered list is re-assigned to `obj.Object["items"]`. -func filterUnstructuredList(verb string, obj *unstructured.Unstructured, allowed, denied []types.KubernetesResource, log logrus.FieldLogger) (hasElems bool) { +func filterUnstructuredList(verb string, obj *unstructured.Unstructured, allowed, denied []types.KubernetesResource, log *slog.Logger) (hasElems bool) { const ( itemsKey = "items" ) @@ -809,7 +812,7 @@ func filterUnstructuredList(verb string, obj *unstructured.Unstructured, allowed objList, err := obj.ToList() if err != nil { // This should never happen, but if it does, we should log it. - log.WithError(err).Warnf("Unable to convert unstructured object to list.") + slog.WarnContext(context.Background(), "Unable to convert unstructured object to list", "error", err) return false } @@ -822,7 +825,7 @@ func filterUnstructuredList(verb string, obj *unstructured.Unstructured, allowed ); result { filteredList = append(filteredList, resource.Object) } else if err != nil { - log.WithError(err).Warnf("Unable to compile regex expressions within kubernetes_resources.") + slog.WarnContext(context.Background(), "Unable to compile regex expressions within kubernetes_resources", "error", err) } } obj.Object[itemsKey] = filteredList diff --git a/lib/kube/proxy/resource_filters_test.go b/lib/kube/proxy/resource_filters_test.go index 3d49563712b81..aecfae1f3e154 100644 --- a/lib/kube/proxy/resource_filters_test.go +++ b/lib/kube/proxy/resource_filters_test.go @@ -30,7 +30,6 @@ import ( "text/template" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" @@ -42,10 +41,10 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/kube/proxy/responsewriters" + "github.com/gravitational/teleport/lib/utils" ) func Test_filterBuffer(t *testing.T) { - log := logrus.New() type objectAndAPI struct { obj string api string @@ -175,7 +174,7 @@ func Test_filterBuffer(t *testing.T) { buf, decompress := newMemoryResponseWriter(t, data.Bytes(), tt.args.contentEncoding) - err = filterBuffer(newResourceFilterer(r, types.KubeVerbList, &globalKubeCodecs, allowedResources, nil, log), buf) + err = filterBuffer(newResourceFilterer(r, types.KubeVerbList, &globalKubeCodecs, allowedResources, nil, utils.NewSlogLoggerForTests()), buf) require.NoError(t, err) // Decompress the buffer to compare the result. diff --git a/lib/kube/proxy/resource_list.go b/lib/kube/proxy/resource_list.go index d0401a600fe5d..f892c91bb8eb5 100644 --- a/lib/kube/proxy/resource_list.go +++ b/lib/kube/proxy/resource_list.go @@ -237,7 +237,7 @@ func (f *Forwarder) sendEphemeralContainerEvents(done <-chan struct{}, req *http podName, ) if err != nil { - f.log.WithError(err).Warn("error getting user ephemeral containers") + f.log.WarnContext(req.Context(), "error getting user ephemeral containers", "error", err) return } @@ -247,7 +247,7 @@ func (f *Forwarder) sendEphemeralContainerEvents(done <-chan struct{}, req *http } evt, err := f.getPatchedPodEvent(req.Context(), sess, wc) if err != nil { - f.log.WithError(err).Warn("error pushing pod event") + f.log.WarnContext(req.Context(), "error pushing pod event", "error", err) continue } sentDebugContainers[wc.Spec.ContainerName] = struct{}{} diff --git a/lib/kube/proxy/resource_rbac_test.go b/lib/kube/proxy/resource_rbac_test.go index 9ee1f0b931824..faebea646681c 100644 --- a/lib/kube/proxy/resource_rbac_test.go +++ b/lib/kube/proxy/resource_rbac_test.go @@ -32,7 +32,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -51,6 +50,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/kube/proxy/responsewriters" testingkubemock "github.com/gravitational/teleport/lib/kube/proxy/testing/kube_server" + "github.com/gravitational/teleport/lib/utils" ) func TestListPodRBAC(t *testing.T) { @@ -518,8 +518,6 @@ func TestListPodRBAC(t *testing.T) { func TestWatcherResponseWriter(t *testing.T) { defaultNamespace := "default" devNamespace := "dev" - log := logrus.New() - log.SetLevel(logrus.DebugLevel) t.Parallel() statusErr := &metav1.Status{ TypeMeta: metav1.TypeMeta{ @@ -633,7 +631,7 @@ func TestWatcherResponseWriter(t *testing.T) { t.Run(tt.name, func(t *testing.T) { userReader, userWriter := io.Pipe() negotiator := newClientNegotiator(&globalKubeCodecs) - filterWrapper := newResourceFilterer(types.KindKubePod, types.KubeVerbWatch, &globalKubeCodecs, tt.args.allowed, tt.args.denied, log) + filterWrapper := newResourceFilterer(types.KindKubePod, types.KubeVerbWatch, &globalKubeCodecs, tt.args.allowed, tt.args.denied, utils.NewSlogLoggerForTests()) // watcher parses the data written into itself and if the user is allowed to // receive the update, it writes the event into target. watcher, err := responsewriters.NewWatcherResponseWriter(newFakeResponseWriter(userWriter) /*target*/, negotiator, filterWrapper) diff --git a/lib/kube/proxy/response_rewriter.go b/lib/kube/proxy/response_rewriter.go index 7fccfe0eb5132..1060762c16b87 100644 --- a/lib/kube/proxy/response_rewriter.go +++ b/lib/kube/proxy/response_rewriter.go @@ -87,7 +87,7 @@ func (f *Forwarder) rewriteResponseForbidden(s *clusterSession) func(r *http.Res newClientNegotiator(&globalKubeCodecs), ) if err != nil { - f.log.WithError(err).Error("Failed to create encoder") + f.log.ErrorContext(r.Request.Context(), "Failed to create encoder", "error", err) return nil } @@ -107,7 +107,7 @@ func (f *Forwarder) rewriteResponseForbidden(s *clusterSession) func(r *http.Res // Encode the new response. if err = encoder.Encode(status, b); err != nil { - f.log.WithError(err).Error("Failed to encode response") + f.log.ErrorContext(r.Request.Context(), "Failed to encode response", "error", err) return trace.Wrap(err) } diff --git a/lib/kube/proxy/roundtrip.go b/lib/kube/proxy/roundtrip.go index 3630f3e898dd7..7fb1bf9c8517a 100644 --- a/lib/kube/proxy/roundtrip.go +++ b/lib/kube/proxy/roundtrip.go @@ -24,6 +24,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net" "net/http" "net/url" @@ -31,7 +32,6 @@ import ( "time" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -90,7 +90,7 @@ type roundTripperConfig struct { // headers instead of relying on the certificate to transport it. useIdentityForwarding bool // log specifies the logger. - log log.FieldLogger + log *slog.Logger proxier func(*http.Request) (*url.URL, error) } diff --git a/lib/kube/proxy/scheme.go b/lib/kube/proxy/scheme.go index 85d80739ada50..0d88a0fdaef9c 100644 --- a/lib/kube/proxy/scheme.go +++ b/lib/kube/proxy/scheme.go @@ -19,11 +19,12 @@ package proxy import ( + "context" "errors" + "log/slog" "strings" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "golang.org/x/exp/maps" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -113,7 +114,7 @@ type gvkSupportedResources map[gvkSupportedResourcesKey]*schema.GroupVersionKind // This schema includes all well-known Kubernetes types and all namespaced // custom resources. // It also returns a map of resources that we support RBAC restrictions for. -func newClusterSchemaBuilder(log logrus.FieldLogger, client kubernetes.Interface) (*serializer.CodecFactory, rbacSupportedResources, gvkSupportedResources, error) { +func newClusterSchemaBuilder(log *slog.Logger, client kubernetes.Interface) (*serializer.CodecFactory, rbacSupportedResources, gvkSupportedResources, error) { kubeScheme := runtime.NewScheme() kubeCodecs := serializer.NewCodecFactory(kubeScheme) supportedResources := maps.Clone(defaultRBACResources) @@ -135,7 +136,10 @@ func newClusterSchemaBuilder(log logrus.FieldLogger, client kubernetes.Interface // reachable. // In this case, we still want to register the other resources that are // available in the cluster. - log.WithError(err).Debugf("Failed to discover some API groups: %v", maps.Keys(discoveryErr.Groups)) + log.DebugContext(context.Background(), "Failed to discover some API groups", + "groups", maps.Keys(discoveryErr.Groups), + "error", err, + ) case err != nil: return nil, nil, nil, trace.Wrap(err) } diff --git a/lib/kube/proxy/scheme_test.go b/lib/kube/proxy/scheme_test.go index d13dbbc94df8f..ae7075db251f4 100644 --- a/lib/kube/proxy/scheme_test.go +++ b/lib/kube/proxy/scheme_test.go @@ -21,17 +21,18 @@ package proxy import ( "testing" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/discovery" "k8s.io/client-go/kubernetes" + + "github.com/gravitational/teleport/lib/utils" ) // TestNewClusterSchemaBuilder tests that newClusterSchemaBuilder doesn't panic // when it's given types already registered in the global scheme. func Test_newClusterSchemaBuilder(t *testing.T) { - _, _, _, err := newClusterSchemaBuilder(logrus.StandardLogger(), &clientSet{}) + _, _, _, err := newClusterSchemaBuilder(utils.NewSlogLoggerForTests(), &clientSet{}) require.NoError(t, err) } diff --git a/lib/kube/proxy/self_subject_reviews.go b/lib/kube/proxy/self_subject_reviews.go index 2130cfdaed034..25fb264a0db38 100644 --- a/lib/kube/proxy/self_subject_reviews.go +++ b/lib/kube/proxy/self_subject_reviews.go @@ -63,7 +63,7 @@ func (f *Forwarder) selfSubjectAccessReviews(authCtx *authContext, w http.Respon if err != nil { // This error goes to kubernetes client and is not visible in the logs // of the teleport server if not logged here. - f.log.Errorf("Failed to create cluster session: %v.", err) + f.log.ErrorContext(req.Context(), "Failed to create cluster session", "error", err) return nil, trace.Wrap(err) } // sess.Close cancels the connection monitor context to release it sooner. @@ -91,7 +91,7 @@ func (f *Forwarder) selfSubjectAccessReviews(authCtx *authContext, w http.Respon if err := f.setupForwardingHeaders(sess, req, true /* withImpersonationHeaders */); err != nil { // This error goes to kubernetes client and is not visible in the logs // of the teleport server if not logged here. - f.log.Errorf("Failed to set up forwarding headers: %v.", err) + f.log.ErrorContext(req.Context(), "Failed to set up forwarding headers", "error", err) return nil, trace.Wrap(err) } rw := httplib.NewResponseStatusRecorder(w) diff --git a/lib/kube/proxy/server.go b/lib/kube/proxy/server.go index 6f05f2a13a22f..6ac466746b51f 100644 --- a/lib/kube/proxy/server.go +++ b/lib/kube/proxy/server.go @@ -29,7 +29,6 @@ import ( "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "golang.org/x/net/http2" "github.com/gravitational/teleport" @@ -68,7 +67,7 @@ type TLSServerConfig struct { // ConnectedProxyGetter gets the proxies teleport is connected to. ConnectedProxyGetter *reversetunnel.ConnectedProxyGetter // Log is the logger. - Log logrus.FieldLogger + Log *slog.Logger // Selectors is a list of resource monitor selectors. ResourceMatchers []services.ResourceMatcher // OnReconcile is called after each kube_cluster resource reconciliation. @@ -134,7 +133,7 @@ func (c *TLSServerConfig) CheckAndSetDefaults() error { } if c.Log == nil { - c.Log = logrus.New() + c.Log = slog.Default() } if c.CloudClients == nil { cloudClients, err := cloud.NewClients() @@ -180,7 +179,7 @@ type TLSServer struct { monitoredKubeClusters monitoredKubeClusters // reconcileCh triggers reconciliation of proxied kube_clusters. reconcileCh chan struct{} - log *logrus.Entry + log *slog.Logger } // NewTLSServer returns new unstarted TLS server @@ -188,9 +187,7 @@ func NewTLSServer(cfg TLSServerConfig) (*TLSServer, error) { if err := cfg.CheckAndSetDefaults(); err != nil { return nil, trace.Wrap(err) } - log := cfg.Log.WithFields(logrus.Fields{ - teleport.ComponentKey: cfg.Component, - }) + log := cfg.Log.With(teleport.ComponentKey, cfg.Component) // limiter limits requests by frequency and amount of simultaneous // connections per client limiter, err := limiter.NewLimiter(cfg.LimiterConfig) @@ -422,8 +419,7 @@ func (t *TLSServer) close(ctx context.Context) error { // and server's GetConfigForClient reloads the list of trusted // local and remote certificate authorities func (t *TLSServer) GetConfigForClient(info *tls.ClientHelloInfo) (*tls.Config, error) { - // TODO(tross): remove slog.Default once the TLSServer is updated to use a slog.Logger - return authclient.WithClusterCAs(t.TLS, t.AccessPoint, t.ClusterName, slog.Default())(info) + return authclient.WithClusterCAs(t.TLS, t.AccessPoint, t.ClusterName, t.log)(info) } // GetServerInfo returns a services.Server object for heartbeats (aka @@ -523,7 +519,7 @@ func (t *TLSServer) startHeartbeat(name string) error { func (t *TLSServer) getRotationState() types.Rotation { rotation, err := t.TLSServerConfig.GetRotation(types.RoleKube) if err != nil && !trace.IsNotFound(err) { - t.log.WithError(err).Warn("Failed to get rotation state.") + t.log.WarnContext(t.closeContext, "Failed to get rotation state", "error", err) } if rotation != nil { return *rotation @@ -539,14 +535,14 @@ func (t *TLSServer) startStaticClustersHeartbeat() error { // proxy_service will pretend to also be kube_server. if t.KubeServiceType == KubeService || t.KubeServiceType == LegacyProxyService { - t.log.Debugf("Starting kubernetes_service heartbeats for %q", t.Component) + t.log.DebugContext(t.closeContext, "Starting kubernetes_service heartbeats") for _, cluster := range t.fwd.kubeClusters() { if err := t.startHeartbeat(cluster.GetName()); err != nil { return trace.Wrap(err) } } } else { - t.log.Debug("No local kube credentials on proxy, will not start kubernetes_service heartbeats") + t.log.DebugContext(t.closeContext, "No local kube credentials on proxy, will not start kubernetes_service heartbeats") } return nil diff --git a/lib/kube/proxy/server_test.go b/lib/kube/proxy/server_test.go index 091f33599c96f..a5a28823080ed 100644 --- a/lib/kube/proxy/server_test.go +++ b/lib/kube/proxy/server_test.go @@ -35,7 +35,6 @@ import ( "time" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "github.com/gravitational/teleport/api/client/proto" @@ -45,6 +44,7 @@ import ( testingkubemock "github.com/gravitational/teleport/lib/kube/proxy/testing/kube_server" "github.com/gravitational/teleport/lib/reversetunnel" "github.com/gravitational/teleport/lib/tlsca" + "github.com/gravitational/teleport/lib/utils" ) func TestServeConfigureError(t *testing.T) { @@ -111,10 +111,9 @@ func TestMTLSClientCAs(t *testing.T) { } hostCert := genCert(t, "localhost", "localhost", "127.0.0.1", "::1") userCert := genCert(t, "user") - log := logrus.New() srv := &TLSServer{ TLSServerConfig: TLSServerConfig{ - Log: log, + Log: utils.NewSlogLoggerForTests(), ForwarderConfig: ForwarderConfig{ ClusterName: mainClusterName, }, @@ -125,7 +124,7 @@ func TestMTLSClientCAs(t *testing.T) { }, GetRotation: func(role types.SystemRole) (*types.Rotation, error) { return &types.Rotation{}, nil }, }, - log: logrus.NewEntry(log), + log: utils.NewSlogLoggerForTests(), } lis, err := net.Listen("tcp", "localhost:0") @@ -207,7 +206,7 @@ func TestGetServerInfo(t *testing.T) { srv := &TLSServer{ TLSServerConfig: TLSServerConfig{ - Log: logrus.New(), + Log: utils.NewSlogLoggerForTests(), ForwarderConfig: ForwarderConfig{ Clock: clockwork.NewFakeClock(), ClusterName: "kube-cluster", diff --git a/lib/kube/proxy/sess.go b/lib/kube/proxy/sess.go index bbf2f308a1a25..f017b59dbd851 100644 --- a/lib/kube/proxy/sess.go +++ b/lib/kube/proxy/sess.go @@ -22,6 +22,7 @@ import ( "context" "fmt" "io" + "log/slog" "net/http" "path" "reflect" @@ -33,7 +34,6 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" "github.com/julienschmidt/httprouter" - log "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" @@ -55,6 +55,7 @@ import ( tsession "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/srv" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) const sessionRecorderID = "session-recorder" @@ -378,7 +379,7 @@ type session struct { // This is used for audit trails. partiesHistorical map[uuid.UUID]*party - log *log.Entry + log *slog.Logger io *srv.TermManager @@ -440,8 +441,8 @@ type session struct { // newSession creates a new session in pending mode. func newSession(ctx authContext, forwarder *Forwarder, req *http.Request, params httprouter.Params, initiator *party, sess *clusterSession) (*session, error) { id := uuid.New() - log := forwarder.log.WithField("session", id.String()) - log.Debug("Creating session") + log := forwarder.log.With("session", id.String()) + log.DebugContext(req.Context(), "Creating session") var policySets []*types.SessionTrackerPolicySet roles := ctx.Checker.Roles() @@ -502,7 +503,7 @@ func newSession(ctx authContext, forwarder *Forwarder, req *http.Request, params if _, open := <-s.io.TerminateNotifier(); open { err := s.Close() if err != nil { - s.log.Errorf("Failed to close session: %v.", err) + s.log.ErrorContext(req.Context(), "Failed to close session", "error", err) } } }() @@ -518,24 +519,33 @@ func newSession(ctx authContext, forwarder *Forwarder, req *http.Request, params // It is used to properly handle client disconnections. func (s *session) disconnectPartyOnErr(idString string, err error) { if idString == sessionRecorderID { - s.log.Error("Failed to write to session recorder, closing session.") + s.log.ErrorContext(s.sess.connCtx, "Failed to write to session recorder, closing session") s.Close() return } id, uuidParseErr := uuid.Parse(idString) if uuidParseErr != nil { - s.log.WithError(uuidParseErr).Errorf("Unable to decode %q into a UUID.", idString) + s.log.ErrorContext(s.sess.connCtx, "Unable to decode party id", + "party_id", idString, + "error", uuidParseErr, + ) return } wasActive, leaveErr := s.leave(id) if leaveErr != nil { - s.log.WithError(leaveErr).Errorf("Failed to disconnect party %v from the session.", idString) + s.log.ErrorContext(s.sess.connCtx, "Failed to disconnect party from the session", + "party_id", idString, + "error", leaveErr, + ) } if wasActive { // log the error only if it was the reason for the user disconnection. - s.log.Errorf("Encountered error: %v with party %v. Disconnecting them from the session.", err, idString) + s.log.ErrorContext(s.sess.connCtx, "Encountered error with party, disconnecting them from the session", + "error", err, + "party_id", idString, + ) } } @@ -551,11 +561,14 @@ func (s *session) checkPresence() error { } if participant.Mode == string(types.SessionModeratorMode) && time.Now().UTC().After(participant.LastActive.Add(PresenceMaxDifference)) { - s.log.Debugf("Participant %v is not active, kicking.", participant.ID) + s.log.DebugContext(s.sess.connCtx, "Participant is not active, kicking", "participant_id", participant.ID) id, _ := uuid.Parse(participant.ID) _, err := s.unlockedLeave(id) if err != nil { - s.log.WithError(err).Warnf("Failed to kick participant %v for inactivity.", participant.ID) + s.log.WarnContext(s.sess.connCtx, "Failed to kick participant for inactivity", + "participant_id", participant.ID, + "error", err, + ) } } } @@ -569,11 +582,14 @@ func (s *session) launch(ephemeralContainerStatus *corev1.ContainerStatus) (retu defer func() { err := s.Close() if err != nil { - s.log.WithError(err).Errorf("Failed to close session: %v", s.id) + s.log.ErrorContext(s.req.Context(), "Failed to close session", + "session_id", s.id, + "error", err, + ) } }() - s.log.Debugf("Launching session: %v", s.id) + s.log.DebugContext(s.req.Context(), "Launching session", "session_id", s.id) q := s.req.URL.Query() namespace := s.params.ByName("podNamespace") @@ -603,7 +619,7 @@ func (s *session) launch(ephemeralContainerStatus *corev1.ContainerStatus) (retu if returnErr != nil { s.setTerminationErr(returnErr) s.reportErrorToSessionRecorder(returnErr) - s.log.WithError(returnErr).Warning("Executor failed while streaming.") + s.log.WarnContext(s.req.Context(), "Executor failed while streaming", "error", returnErr) } // call onFinished to emit the session.end and exec events. // onFinished is never nil. @@ -640,13 +656,13 @@ func (s *session) launch(ephemeralContainerStatus *corev1.ContainerStatus) (retu }) if err == nil { if err := s.recorder.RecordEvent(s.forwarder.ctx, sessionStartEvent); err != nil { - s.forwarder.log.WithError(err).Warn("Failed to record session start event.") + s.forwarder.log.WarnContext(s.forwarder.ctx, "Failed to record session start event", "error", err) } if err := s.emitter.EmitAuditEvent(s.forwarder.ctx, sessionStartEvent.GetAuditEvent()); err != nil { - s.forwarder.log.WithError(err).Warn("Failed to emit session start event.") + s.forwarder.log.WarnContext(s.forwarder.ctx, "Failed to emit session start event", "error", err) } } else { - s.forwarder.log.WithError(err).Warn("Failed to set up session start event - event will not be recorded") + s.forwarder.log.WarnContext(s.forwarder.ctx, "Failed to set up session start event - event will not be recorded", "error", err) } s.weakEventsWaiter.Add(1) @@ -660,21 +676,21 @@ func (s *session) launch(ephemeralContainerStatus *corev1.ContainerStatus) (retu s.BroadcastMessage("Session expired, closing...") err := s.Close() if err != nil { - s.log.WithError(err).Error("Failed to close session") + s.log.ErrorContext(s.forwarder.ctx, "Failed to close session", "error", err) } case <-s.closeC: } }() if err = s.tracker.UpdateState(s.forwarder.ctx, types.SessionState_SessionStateRunning); err != nil { - s.log.WithError(err).Warn("Failed to set tracker state to running") + s.log.WarnContext(s.forwarder.ctx, "Failed to set tracker state to running", "error", err) } var executor remotecommand.Executor executor, err = s.forwarder.getExecutor(s.sess, s.req) if err != nil { - s.log.WithError(err).Warning("Failed creating executor.") + s.log.WarnContext(s.forwarder.ctx, "Failed creating executor", "error", err) return trace.Wrap(err) } @@ -759,7 +775,10 @@ func (s *session) lockedSetupLaunch(request *remoteCommandRequest, eventPodMeta } err := p.Client.resize(termSize.size) if err != nil { - s.log.WithError(err).Errorf("Failed to resize client: %v", id.String()) + s.log.ErrorContext(s.forwarder.ctx, "Failed to resize participant", + "party_id", id.String(), + "error", err, + ) } } @@ -789,10 +808,10 @@ func (s *session) lockedSetupLaunch(request *remoteCommandRequest, eventPodMeta // Report the updated window size to the event log (this is so the sessions // can be replayed correctly). if err := s.recorder.RecordEvent(s.forwarder.ctx, resizeEvent); err != nil { - s.forwarder.log.WithError(err).Warn("Failed to emit terminal resize event.") + s.forwarder.log.WarnContext(s.forwarder.ctx, "Failed to emit terminal resize event", "error", err) } } else { - s.forwarder.log.WithError(err).Warn("Failed to set up terminal resize event - event will not be recorded") + s.forwarder.log.WarnContext(s.forwarder.ctx, "Failed to set up terminal resize event - event will not be recorded", "error", err) } } } else { @@ -844,7 +863,7 @@ func (s *session) lockedSetupLaunch(request *remoteCommandRequest, eventPodMeta } if err := s.emitter.EmitAuditEvent(s.forwarder.ctx, execEvent); err != nil { - s.forwarder.log.WithError(err).Warn("Failed to emit exec event.") + s.forwarder.log.WarnContext(s.forwarder.ctx, "Failed to emit exec event", "error", err) } sessionDataEvent := &apievents.SessionData{ @@ -864,7 +883,7 @@ func (s *session) lockedSetupLaunch(request *remoteCommandRequest, eventPodMeta } if err := s.emitter.EmitAuditEvent(s.forwarder.ctx, sessionDataEvent); err != nil { - s.forwarder.log.WithError(err).Warn("Failed to emit session data event.") + s.forwarder.log.WarnContext(s.forwarder.ctx, "Failed to emit session data event", "error", err) } sessionEndEvent, err := s.recorder.PrepareSessionEvent(&apievents.SessionEnd{ @@ -888,13 +907,13 @@ func (s *session) lockedSetupLaunch(request *remoteCommandRequest, eventPodMeta }) if err == nil { if err := s.recorder.RecordEvent(s.forwarder.ctx, sessionEndEvent); err != nil { - s.forwarder.log.WithError(err).Warn("Failed to record session end event.") + s.forwarder.log.WarnContext(s.forwarder.ctx, "Failed to record session end event", "error", err) } if err := s.emitter.EmitAuditEvent(s.forwarder.ctx, sessionEndEvent.GetAuditEvent()); err != nil { - s.forwarder.log.WithError(err).Warn("Failed to emit session end event.") + s.forwarder.log.WarnContext(s.forwarder.ctx, "Failed to emit session end event", "error", err) } } else { - s.forwarder.log.WithError(err).Warn("Failed to set up session end event - event will not be recorded") + s.forwarder.log.WarnContext(s.forwarder.ctx, "Failed to set up session end event - event will not be recorded", "error", err) } } @@ -934,9 +953,9 @@ func (s *session) lockedSetupLaunch(request *remoteCommandRequest, eventPodMeta case <-ticker.C: err := s.checkPresence() if err != nil { - s.log.WithError(err).Error("Failed to check presence, closing session as a security measure") + s.log.ErrorContext(s.forwarder.ctx, "Failed to check presence, closing session as a security measure", "error", err) if err := s.Close(); err != nil { - s.log.WithError(err).Error("Failed to close session") + s.log.ErrorContext(s.forwarder.ctx, "Failed to close session", "error", err) } return } @@ -969,7 +988,7 @@ func (s *session) join(p *party, emitJoinEvent bool) error { return trace.AccessDenied("The requested session is not active") } - s.log.Debugf("Tracking participant: %s", p.ID) + s.log.DebugContext(s.forwarder.ctx, "Tracking participant", "participant_id", p.ID) participant := &types.Participant{ ID: p.ID.String(), User: p.Ctx.User.GetName(), @@ -989,7 +1008,7 @@ func (s *session) join(p *party, emitJoinEvent bool) error { recentWrites := s.io.GetRecentHistory() if _, err := p.Client.stdoutStream().Write(recentWrites); err != nil { - s.log.Warnf("Failed to write history to client: %v.", err) + s.log.WarnContext(s.forwarder.ctx, "Failed to write history to participant", "error", err) } s.BroadcastMessage("User %v joined the session with participant mode: %v.", p.Ctx.User.GetName(), p.Mode) @@ -1009,7 +1028,10 @@ func (s *session) join(p *party, emitJoinEvent bool) error { // other parties' terminals and no discrepancies are present. if lastQueueSize := s.terminalSizeQueue.getLastSize(); lastQueueSize != nil { if err := p.Client.resize(lastQueueSize); err != nil { - s.log.WithError(err).Errorf("Failed to resize client: %v", stringID) + s.log.ErrorContext(s.forwarder.ctx, "Failed to resize participant", + "participant_id", stringID, + "error", err, + ) } } @@ -1022,7 +1044,10 @@ func (s *session) join(p *party, emitJoinEvent bool) error { if p.Ctx.User.GetName() != s.ctx.User.GetName() { err := srv.MsgParticipantCtrls(p.Client.stdoutStream(), p.Mode) if err != nil { - s.log.Errorf("Could not send intro message to participant: %v", err) + s.log.ErrorContext(s.forwarder.ctx, "Could not send intro message to participant", + "error", err, + "participant_id", stringID, + ) } } @@ -1036,10 +1061,10 @@ func (s *session) join(p *party, emitJoinEvent bool) error { case <-c: s.setTerminationErr(sessionTerminatedByModeratorErr) go func() { - s.log.Debugf("Received force termination request") + s.log.DebugContext(s.forwarder.ctx, "Received force termination request") err := s.Close() if err != nil { - s.log.Errorf("Failed to close session: %v.", err) + s.log.ErrorContext(s.forwarder.ctx, "Failed to close session", "error", err) } }() case <-s.closeC: @@ -1064,11 +1089,11 @@ func (s *session) join(p *party, emitJoinEvent bool) error { // we must inform all parties that the session is closing. s.setTerminationErrUnlocked(err) s.reportErrorToSessionRecorder(err) - s.log.WithError(err).Warning("Executor failed while creating ephemeral pod.") + s.log.WarnContext(s.forwarder.ctx, "Executor failed while creating ephemeral pod", "error", err) go func() { err := s.Close() if err != nil { - s.log.WithError(err).Error("Failed to close session") + s.log.ErrorContext(s.forwarder.ctx, "Failed to close session", "error", err) } }() return trace.Wrap(err) @@ -1076,7 +1101,7 @@ func (s *session) join(p *party, emitJoinEvent bool) error { go func() { if err := s.launch(startedEphemeralCont); err != nil { - s.log.WithError(err).Warning("Failed to launch Kubernetes session.") + s.log.WarnContext(s.forwarder.ctx, "Failed to launch Kubernetes session", "error", err) } }() } else if len(s.parties) == 1 { @@ -1098,7 +1123,7 @@ func (s *session) join(p *party, emitJoinEvent bool) error { // types.SessionState_SessionStatePending marks a session that is waiting for // a moderator to rejoin. if err := s.tracker.UpdateState(s.forwarder.ctx, types.SessionState_SessionStateRunning); err != nil { - s.log.Warnf("Failed to set tracker state to %v", types.SessionState_SessionStateRunning) + s.log.WarnContext(s.forwarder.ctx, "Failed to update tracker to running state") } } return nil @@ -1141,7 +1166,7 @@ func (s *session) createEphemeralContainer() (*corev1.ContainerStatus, error) { return nil, trace.Wrap(err) } - s.log.Debugf("Creating ephemeral container %s on pod %s", container, podName) + s.log.DebugContext(s.forwarder.ctx, "Creating ephemeral container on pod", "container", container, "pod", podName) containerStatus, err := s.patchAndWaitForPodEphemeralContainer(s.forwarder.ctx, &initUser.Ctx, s.req.Header, waitingCont) return containerStatus, trace.Wrap(err) } @@ -1179,7 +1204,7 @@ func (s *session) emitSessionJoinEvent(p *party) { } if err := s.emitter.EmitAuditEvent(s.forwarder.ctx, sessionJoinEvent); err != nil { - s.forwarder.log.WithError(err).Warn("Failed to emit event.") + s.forwarder.log.WarnContext(s.forwarder.ctx, "Failed to emit event", "error", err) } } @@ -1228,10 +1253,10 @@ func (s *session) unlockedLeave(id uuid.UUID) (bool, error) { } if err := s.emitter.EmitAuditEvent(s.forwarder.ctx, sessionLeaveEvent); err != nil { - s.forwarder.log.WithError(err).Warn("Failed to emit event.") + s.forwarder.log.WarnContext(s.forwarder.ctx, "Failed to emit event", "error", err) } - s.log.Debugf("No longer tracking participant: %v", party.ID) + s.log.DebugContext(s.forwarder.ctx, "No longer tracking participant", "participant_id", party.ID) err := s.tracker.RemoveParticipant(s.forwarder.ctx, party.ID.String()) if err != nil { errs = append(errs, trace.Wrap(err)) @@ -1246,7 +1271,7 @@ func (s *session) unlockedLeave(id uuid.UUID) (bool, error) { // close session err := s.Close() if err != nil { - s.log.WithError(err).Errorf("Failed to close session") + s.log.ErrorContext(s.forwarder.ctx, "Failed to close session", "error", err) } }() return true, trace.NewAggregate(errs...) @@ -1267,7 +1292,7 @@ func (s *session) unlockedLeave(id uuid.UUID) (bool, error) { if options.OnLeaveAction == types.OnSessionLeaveTerminate { go func() { if err := s.Close(); err != nil { - s.log.WithError(err).Errorf("Failed to close session") + s.log.ErrorContext(s.forwarder.ctx, "Failed to close session", "error", err) } }() return true, nil @@ -1277,7 +1302,7 @@ func (s *session) unlockedLeave(id uuid.UUID) (bool, error) { s.io.Off() s.BroadcastMessage("Session paused, Waiting for required participants...") if err := s.tracker.UpdateState(s.forwarder.ctx, types.SessionState_SessionStatePending); err != nil { - s.log.Warnf("Failed to set tracker state to %v", types.SessionState_SessionStatePending) + s.log.WarnContext(s.forwarder.ctx, "Failed to set tracker state to pending") } go func() { @@ -1335,7 +1360,7 @@ func (s *session) Close() error { // Once tracker is closed parties cannot join the session. // check session.join for logic. if err := s.tracker.Close(s.forwarder.ctx); err != nil { - s.log.WithError(err).Debug("Failed to close session tracker") + s.log.DebugContext(s.forwarder.ctx, "Failed to close session tracker", "error", err) } s.mu.Lock() terminationErr := s.terminationErr @@ -1345,7 +1370,7 @@ func (s *session) Close() error { } recorder := s.recorder s.mu.Unlock() - s.log.Debugf("Closing session %v.", s.id.String()) + s.log.DebugContext(s.forwarder.ctx, "Closing session", "session_id", logutils.StringerAttr(s.id)) close(s.closeC) // Wait until every party leaves the session and emits the session leave // event before closing the recorder - if available. @@ -1405,18 +1430,18 @@ func (s *session) trackSession(p *party, policySet []*types.SessionTrackerPolicy InitialCommand: command, } - s.log.Debug("Creating session tracker") + s.log.DebugContext(ctx, "Creating session tracker") sessionTrackerService := s.forwarder.cfg.AuthClient tracker, err := srv.NewSessionTracker(ctx, trackerSpec, sessionTrackerService) switch { // there was an error creating the tracker for a moderated session - terminate the session case err != nil && s.accessEvaluator.IsModerated(): - s.log.WithError(err).Warn("Failed to create session tracker, unable to proceed for moderated session") + s.log.WarnContext(ctx, "Failed to create session tracker, unable to proceed for moderated session", "error", err) return trace.Wrap(err) // there was an error creating the tracker for a non-moderated session - permit the session with a local tracker case err != nil && !s.accessEvaluator.IsModerated(): - s.log.Warn("Failed to create session tracker, proceeding with local session tracker for non-moderated session") + s.log.WarnContext(ctx, "Failed to create session tracker, proceeding with local session tracker for non-moderated session") localTracker, err := srv.NewSessionTracker(ctx, trackerSpec, nil) // this error means there are problems with the trackerSpec, we need to return it @@ -1435,7 +1460,7 @@ func (s *session) trackSession(p *party, policySet []*types.SessionTrackerPolicy go func() { if err := s.tracker.UpdateExpirationLoop(s.forwarder.ctx, s.forwarder.cfg.Clock); err != nil { - s.log.WithError(err).Warn("Failed to update session tracker expiration") + s.log.WarnContext(ctx, "Failed to update session tracker expiration", "error", err) } }() @@ -1549,7 +1574,7 @@ func (s *session) retrieveAlreadyStoppedPodLogs(namespace, podName, container st func (s *session) retrieveEphemeralContainerCommand(ctx context.Context, username, containerName string) []string { containers, err := s.forwarder.getUserEphemeralContainersForPod(ctx, username, s.ctx.kubeClusterName, s.podNamespace, s.podName) if err != nil { - s.log.WithError(err).Warn("Failed to retrieve ephemeral containers") + s.log.WarnContext(ctx, "Failed to retrieve ephemeral containers", "error", err) return nil } if len(containers) == 0 { @@ -1569,7 +1594,7 @@ func (s *session) retrieveEphemeralContainerCommand(ctx context.Context, usernam newClientNegotiator(s.sess.codecFactory), ) if err != nil { - s.log.WithError(err).Warn("Failed to create encoder and decoder") + s.log.WarnContext(ctx, "Failed to create encoder and decoder", "error", err) return nil } pod, _, err := s.forwarder.mergeEphemeralPatchWithCurrentPod( @@ -1585,7 +1610,7 @@ func (s *session) retrieveEphemeralContainerCommand(ctx context.Context, usernam }, ) if err != nil { - s.log.WithError(err).Warn("Failed to merge ephemeral patch with current pod") + s.log.WarnContext(ctx, "Failed to merge ephemeral patch with current pod", "error", err) return nil } for _, ephemeral := range pod.Spec.EphemeralContainers { diff --git a/lib/kube/proxy/sess_test.go b/lib/kube/proxy/sess_test.go index 469fe2c4df27a..2c87ec555845d 100644 --- a/lib/kube/proxy/sess_test.go +++ b/lib/kube/proxy/sess_test.go @@ -34,13 +34,11 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/remotecommand" - "github.com/gravitational/teleport" kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1" "github.com/gravitational/teleport/api/types" apievents "github.com/gravitational/teleport/api/types/events" @@ -49,6 +47,7 @@ import ( "github.com/gravitational/teleport/lib/authz" "github.com/gravitational/teleport/lib/events" testingkubemock "github.com/gravitational/teleport/lib/kube/proxy/testing/kube_server" + "github.com/gravitational/teleport/lib/utils" ) func TestSessionEndError(t *testing.T) { @@ -284,7 +283,7 @@ func Test_session_trackSession(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { sess := &session{ - log: logrus.New().WithField(teleport.ComponentKey, "test"), + log: utils.NewSlogLoggerForTests(), id: uuid.New(), req: &http.Request{ URL: &url.URL{ diff --git a/lib/kube/proxy/streamproto/proto.go b/lib/kube/proxy/streamproto/proto.go index e2ee646dfe2b7..605f8e00748c4 100644 --- a/lib/kube/proxy/streamproto/proto.go +++ b/lib/kube/proxy/streamproto/proto.go @@ -19,16 +19,17 @@ package streamproto import ( + "context" "errors" "fmt" "io" + "log/slog" "sync" "sync/atomic" "time" "github.com/gorilla/websocket" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "k8s.io/client-go/tools/remotecommand" "github.com/gravitational/teleport/api/types" @@ -170,7 +171,7 @@ func (s *SessionStream) readTask() { ty, data, err := s.conn.ReadMessage() if err != nil { if !errors.Is(err, io.EOF) && !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseAbnormalClosure, websocket.CloseNoStatusReceived) { - log.WithError(err).Warn("Failed to read message from websocket") + slog.WarnContext(context.Background(), "Failed to read message from websocket", "error", err) } var closeErr *websocket.CloseError @@ -293,7 +294,7 @@ func (s *SessionStream) Close() error { if atomic.CompareAndSwapInt32(&s.closed, 0, 1) { err := s.conn.WriteMessage(websocket.CloseMessage, []byte{}) if err != nil { - log.Warnf("Failed to gracefully close websocket connection: %v", err) + slog.WarnContext(context.Background(), "Failed to gracefully close websocket connection", "error", err) } t := time.NewTimer(time.Second * 5) defer t.Stop() diff --git a/lib/kube/proxy/testing/kube_server/kube_mock.go b/lib/kube/proxy/testing/kube_server/kube_mock.go index 332d7327c6d69..0bdc27eeb4d67 100644 --- a/lib/kube/proxy/testing/kube_server/kube_mock.go +++ b/lib/kube/proxy/testing/kube_server/kube_mock.go @@ -26,6 +26,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "net/http/httptest" "strings" @@ -36,7 +37,6 @@ import ( gwebsocket "github.com/gorilla/websocket" "github.com/gravitational/trace" "github.com/julienschmidt/httprouter" - log "github.com/sirupsen/logrus" "golang.org/x/net/http2" v1 "k8s.io/api/authorization/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -151,7 +151,7 @@ type KubeUpgradeRequests struct { type KubeMockServer struct { router *httprouter.Router - log *log.Entry + log *slog.Logger server *httptest.Server TLS *tls.Config URL string @@ -178,7 +178,7 @@ type KubeMockServer struct { func NewKubeAPIMock(opts ...Option) (*KubeMockServer, error) { s := &KubeMockServer{ router: httprouter.New(), - log: log.NewEntry(log.New()), + log: slog.Default(), deletedResources: make(map[deletedResource][]string), version: &apimachineryversion.Info{ Major: "1", @@ -273,7 +273,7 @@ func (s *KubeMockServer) writeResponseError(rw http.ResponseWriter, respErr erro status = status.DeepCopy() data, err := runtime.Encode(kubeCodecs.LegacyCodec(), status) if err != nil { - s.log.Warningf("Failed encoding error into kube Status object: %v", err) + s.log.WarnContext(context.Background(), "Failed encoding error into kube Status object", "error", err) trace.WriteError(rw, respErr) return } @@ -283,7 +283,7 @@ func (s *KubeMockServer) writeResponseError(rw http.ResponseWriter, respErr erro // embedded. rw.WriteHeader(int(status.Code)) if _, err := rw.Write(data); err != nil { - s.log.Warningf("Failed writing kube error response body: %v", err) + s.log.WarnContext(context.Background(), "Failed writing kube error response body", "error", err) } } @@ -323,13 +323,13 @@ func (s *KubeMockServer) exec(w http.ResponseWriter, req *http.Request, p httpro if request.stdout { if _, err := proxy.stdoutStream.Write([]byte(request.containerName + "\n")); err != nil { - s.log.WithError(err).Errorf("unable to send to stdout") + s.log.ErrorContext(request.context, "unable to send to stdout", "error", err) } } if request.stderr { if _, err := proxy.stderrStream.Write([]byte(request.containerName + "\n")); err != nil { - s.log.WithError(err).Errorf("unable to send to stderr") + s.log.ErrorContext(request.context, "unable to send to stderr", "error", err) } } @@ -341,7 +341,7 @@ func (s *KubeMockServer) exec(w http.ResponseWriter, req *http.Request, p httpro if errors.Is(err, io.EOF) && n == 0 { break } else if err != nil && n == 0 { - s.log.WithError(err).Errorf("unable to receive from stdin") + s.log.ErrorContext(request.context, "unable to receive from stdin", "error", err) break } @@ -359,13 +359,13 @@ func (s *KubeMockServer) exec(w http.ResponseWriter, req *http.Request, p httpro if request.stdout { if _, err := proxy.stdoutStream.Write(buffer); err != nil { - s.log.WithError(err).Errorf("unable to send to stdout") + s.log.ErrorContext(request.context, "unable to send to stdout", "error", err) } } if request.stderr { if _, err := proxy.stderrStream.Write(buffer); err != nil { - s.log.WithError(err).Errorf("unable to send to stdout") + s.log.ErrorContext(request.context, "unable to send to stdout", "error", err) } } @@ -536,10 +536,10 @@ func createSPDYStreams(req remoteCommandRequest) (*remoteCommandProxy, error) { var handler protocolHandler switch protocol { case "": - log.Warningf("Client did not request protocol negotiation.") + slog.WarnContext(req.context, "Client did not request protocol negotiation.") fallthrough case StreamProtocolV4Name: - log.Infof("Negotiated protocol %v.", protocol) + slog.InfoContext(req.context, "Negotiated protocol", "protocol", protocol) handler = &v4ProtocolHandler{} default: return nil, trace.BadParameter("protocol %v is not supported. upgrade the client", protocol) @@ -641,7 +641,7 @@ func (t *termQueue) handleResizeEvents(stream io.Reader) { size := remotecommand.TerminalSize{} if err := decoder.Decode(&size); err != nil { if !errors.Is(err, io.EOF) { - log.Warningf("Failed to decode resize event: %v", err) + slog.WarnContext(t.done, "Failed to decode resize event", "error", err) } t.cancel() return @@ -696,7 +696,7 @@ WaitForStreams: remoteProxy.resizeStream = stream go waitStreamReply(stopCtx, stream.replySent, replyChan) default: - log.Warningf("Ignoring unexpected stream type: %q", streamType) + slog.WarnContext(stopCtx, "Ignoring unexpected stream type", "stream_type", streamType) } case <-replyChan: receivedStreams++ diff --git a/lib/kube/proxy/utils_testing.go b/lib/kube/proxy/utils_testing.go index 462638df203c4..4f3f596ec0a82 100644 --- a/lib/kube/proxy/utils_testing.go +++ b/lib/kube/proxy/utils_testing.go @@ -35,7 +35,6 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/client-go/kubernetes" @@ -66,6 +65,7 @@ import ( "github.com/gravitational/teleport/lib/services" sessPkg "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/tlsca" + "github.com/gravitational/teleport/lib/utils" ) type TestContext struct { @@ -226,8 +226,6 @@ func SetupTestContext(ctx context.Context, t *testing.T, cfg TestConfig) *TestCo require.NoError(t, err) testCtx.kubeProxyListener, err = net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) - log := logrus.New() - log.SetLevel(logrus.DebugLevel) inventoryHandle := inventory.NewDownstreamHandle(client.InventoryControlStream, proto.UpstreamInventoryHello{ ServerID: testCtx.HostID, @@ -281,7 +279,7 @@ func SetupTestContext(ctx context.Context, t *testing.T, cfg TestConfig) *TestCo GetRotation: func(role types.SystemRole) (*types.Rotation, error) { return &types.Rotation{}, nil }, ResourceMatchers: cfg.ResourceMatchers, OnReconcile: cfg.OnReconcile, - Log: log, + Log: utils.NewSlogLoggerForTests(), InventoryHandle: inventoryHandle, }) require.NoError(t, err) @@ -358,7 +356,7 @@ func SetupTestContext(ctx context.Context, t *testing.T, cfg TestConfig) *TestCo LimiterConfig: limiter.Config{ MaxConnections: 1000, }, - Log: log, + Log: utils.NewSlogLoggerForTests(), InventoryHandle: inventoryHandle, GetRotation: func(role types.SystemRole) (*types.Rotation, error) { return &types.Rotation{}, nil diff --git a/lib/kube/proxy/watcher.go b/lib/kube/proxy/watcher.go index 24e52e2d9c923..56bea639d5260 100644 --- a/lib/kube/proxy/watcher.go +++ b/lib/kube/proxy/watcher.go @@ -20,7 +20,6 @@ package proxy import ( "context" - "log/slog" "sync" "time" @@ -37,7 +36,7 @@ import ( // kubernetes clusters according to the up-to-date list of kube_cluster resources. func (s *TLSServer) startReconciler(ctx context.Context) (err error) { if len(s.ResourceMatchers) == 0 || s.KubeServiceType != KubeService { - s.log.Debug("Not initializing Kube Cluster resource watcher.") + s.log.DebugContext(ctx, "Not initializing Kube Cluster resource watcher") return nil } s.reconciler, err = services.NewReconciler(services.ReconcilerConfig[types.KubeCluster]{ @@ -47,8 +46,7 @@ func (s *TLSServer) startReconciler(ctx context.Context) (err error) { OnCreate: s.onCreate, OnUpdate: s.onUpdate, OnDelete: s.onDelete, - // TODO(tross): update to use the server logger once it has been converted to slog - Logger: slog.With("kind", types.KindKubernetesCluster), + Logger: s.log.With("kind", types.KindKubernetesCluster), }) if err != nil { return trace.Wrap(err) @@ -71,16 +69,16 @@ func (s *TLSServer) startReconciler(ctx context.Context) (err error) { select { case <-reconcileTicker.C: if err := s.reconciler.Reconcile(ctx); err != nil { - s.log.WithError(err).Error("Failed to reconcile.") + s.log.ErrorContext(ctx, "Failed to reconcile", "error", err) } case <-s.reconcileCh: if err := s.reconciler.Reconcile(ctx); err != nil { - s.log.WithError(err).Error("Failed to reconcile.") + s.log.ErrorContext(ctx, "Failed to reconcile", "error", err) } else if s.OnReconcile != nil { s.OnReconcile(s.fwd.kubeClusters()) } case <-ctx.Done(): - s.log.Debug("Reconciler done.") + s.log.DebugContext(ctx, "Reconciler done") return } } @@ -92,16 +90,15 @@ func (s *TLSServer) startReconciler(ctx context.Context) (err error) { // 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.log.Debug("Not initializing Kube Cluster resource watcher.") + s.log.DebugContext(ctx, "Not initializing Kube Cluster resource watcher") return nil, nil } - s.log.Debug("Initializing Kube Cluster resource watcher.") + s.log.DebugContext(ctx, "Initializing Kube Cluster resource watcher") watcher, err := services.NewKubeClusterWatcher(ctx, services.KubeClusterWatcherConfig{ ResourceWatcherConfig: services.ResourceWatcherConfig{ Component: s.Component, - // TODO(tross): update this once converted to use slog - // Logger: s.log, - Client: s.AccessPoint, + Logger: s.log, + Client: s.AccessPoint, }, KubernetesClusterGetter: s.AccessPoint, }) @@ -120,7 +117,7 @@ func (s *TLSServer) startKubeClusterResourceWatcher(ctx context.Context) (*servi return } case <-ctx.Done(): - s.log.Debug("Kube Cluster resource watcher done.") + s.log.DebugContext(ctx, "Kube Cluster resource watcher done") return } } diff --git a/lib/proxy/router.go b/lib/proxy/router.go index 1fc06a5e34754..61ba7f466c5de 100644 --- a/lib/proxy/router.go +++ b/lib/proxy/router.go @@ -492,7 +492,10 @@ func getServerWithResolver(ctx context.Context, host, port string, site site, re } } case len(matches) > 1: - return nil, trace.NotFound(teleport.NodeIsAmbiguous) + // TODO(tross) DELETE IN V20.0.0 + // NodeIsAmbiguous is included in the error message for backwards compatibility + // with older nodes that expect to see that string in the error message. + return nil, trace.Wrap(teleport.ErrNodeIsAmbiguous, teleport.NodeIsAmbiguous) case len(matches) == 1: server = matches[0] } diff --git a/lib/proxy/router_test.go b/lib/proxy/router_test.go index 6be18370437a2..e83ca44a2811d 100644 --- a/lib/proxy/router_test.go +++ b/lib/proxy/router_test.go @@ -211,7 +211,7 @@ func TestRouteScoring(t *testing.T) { t.Run(tt.desc, func(t *testing.T) { srv, err := getServerWithResolver(ctx, tt.host, tt.port, site, resolver) if tt.ambiguous { - require.ErrorIs(t, err, trace.NotFound(teleport.NodeIsAmbiguous)) + require.ErrorIs(t, err, teleport.ErrNodeIsAmbiguous) return } require.Equal(t, tt.expect, srv.GetHostname()) @@ -375,7 +375,7 @@ func TestGetServers(t *testing.T) { site: testSite{cfg: &unambiguousCfg, nodes: servers}, host: "sheep", errAssertion: func(t require.TestingT, err error, i ...interface{}) { - require.ErrorIs(t, err, trace.NotFound(teleport.NodeIsAmbiguous)) + require.ErrorIs(t, err, teleport.ErrNodeIsAmbiguous) }, serverAssertion: func(t *testing.T, srv types.Server) { require.Empty(t, srv) @@ -456,7 +456,7 @@ func TestGetServers(t *testing.T) { site: testSite{cfg: &unambiguousInsensitiveCfg, nodes: servers}, host: "platypus", errAssertion: func(t require.TestingT, err error, i ...interface{}) { - require.ErrorIs(t, err, trace.NotFound(teleport.NodeIsAmbiguous)) + require.ErrorIs(t, err, teleport.ErrNodeIsAmbiguous) }, serverAssertion: func(t *testing.T, srv types.Server) { require.Empty(t, srv) @@ -667,7 +667,7 @@ func TestRouter_DialHost(t *testing.T) { router: Router{ clusterName: "test", tracer: tracing.NoopTracer("test"), - serverResolver: serverResolver(nil, trace.NotFound(teleport.NodeIsAmbiguous)), + serverResolver: serverResolver(nil, teleport.ErrNodeIsAmbiguous), }, assertion: func(t *testing.T, params reversetunnelclient.DialParams, conn net.Conn, err error) { require.Error(t, err) diff --git a/lib/reversetunnel/agent.go b/lib/reversetunnel/agent.go index da41578411067..4bd870c3418d6 100644 --- a/lib/reversetunnel/agent.go +++ b/lib/reversetunnel/agent.go @@ -27,13 +27,13 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "strings" "sync" "time" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" "github.com/gravitational/teleport/api/constants" @@ -42,6 +42,7 @@ import ( "github.com/gravitational/teleport/lib/multiplexer" "github.com/gravitational/teleport/lib/reversetunnel/track" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) type AgentState string @@ -113,8 +114,8 @@ type agentConfig struct { // clock is use to get the current time. Mock clocks can be used for // testing. clock clockwork.Clock - // log is an optional logger. - log logrus.FieldLogger + // logger is an optional logger. + logger *slog.Logger // localAuthAddresses is a list of auth servers to use when dialing back to // the local cluster. localAuthAddresses []string @@ -145,12 +146,13 @@ func (c *agentConfig) checkAndSetDefaults() error { if c.clock == nil { c.clock = clockwork.NewRealClock() } - if c.log == nil { - c.log = logrus.New() + if c.logger == nil { + c.logger = slog.Default() } - c.log = c.log. - WithField("leaseID", c.lease.ID()). - WithField("target", c.addr.String()) + c.logger = c.logger.With( + "lease_id", c.lease.ID(), + "target", c.addr.String(), + ) return nil } @@ -284,7 +286,10 @@ func (a *agent) updateState(state AgentState) (AgentState, error) { prevState := a.state a.state = state - a.log.Debugf("Changing state %s -> %s.", prevState, state) + a.logger.DebugContext(a.ctx, "Agent state updated", + "previous_state", prevState, + "current_state", state, + ) if a.agentConfig.stateCallback != nil { go a.agentConfig.stateCallback(a.state) @@ -296,7 +301,7 @@ func (a *agent) updateState(state AgentState) (AgentState, error) { // Start starts an agent returning after successfully connecting and sending // the first heartbeat. func (a *agent) Start(ctx context.Context) error { - a.log.Debugf("Starting agent %v", a.addr) + a.logger.DebugContext(ctx, "Starting agent", "addr", a.addr.FullAddress()) var err error defer func() { @@ -325,7 +330,7 @@ func (a *agent) Start(ctx context.Context) error { a.wg.Add(1) go func() { if err := a.handleGlobalRequests(a.ctx, a.client.GlobalRequests()); err != nil { - a.log.WithError(err).Debug("Failed to handle global requests.") + a.logger.DebugContext(a.ctx, "Failed to handle global requests", "error", err) } a.wg.Done() a.Stop() @@ -336,7 +341,7 @@ func (a *agent) Start(ctx context.Context) error { a.wg.Add(1) go func() { if err := a.handleDrainChannels(); err != nil { - a.log.WithError(err).Debug("Failed to handle drainable channels.") + a.logger.DebugContext(a.ctx, "Failed to handle drainable channels", "error", err) } a.wg.Done() a.Stop() @@ -345,7 +350,7 @@ func (a *agent) Start(ctx context.Context) error { a.wg.Add(1) go func() { if err := a.handleChannels(); err != nil { - a.log.WithError(err).Debug("Failed to handle channels.") + a.logger.DebugContext(a.ctx, "Failed to handle channels", "error", err) } a.wg.Done() a.Stop() @@ -460,23 +465,23 @@ func (a *agent) handleGlobalRequests(ctx context.Context, requests <-chan *ssh.R case versionRequest: version, err := a.versionGetter.getVersion(ctx) if err != nil { - a.log.WithError(err).Warnf("Failed to retrieve auth version in response to %v request.", r.Type) + a.logger.WarnContext(ctx, "Failed to retrieve auth version in response to x-teleport-version request", "error", err) if err := a.client.Reply(r, false, []byte("Failed to retrieve auth version")); err != nil { - a.log.Debugf("Failed to reply to %v request: %v.", r.Type, err) + a.logger.DebugContext(ctx, "Failed to reply to x-teleport-version request", "error", err) continue } } if err := a.client.Reply(r, true, []byte(version)); err != nil { - a.log.Debugf("Failed to reply to %v request: %v.", r.Type, err) + a.logger.DebugContext(ctx, "Failed to reply to x-teleport-version request", "error", err) continue } case reconnectRequest: - a.log.Debugf("Received reconnect advisory request from proxy.") + a.logger.DebugContext(ctx, "Received reconnect advisory request from proxy") if r.WantReply { err := a.client.Reply(r, true, nil) if err != nil { - a.log.Debugf("Failed to reply to %v request: %v.", r.Type, err) + a.logger.DebugContext(ctx, "Failed to reply to reconnect@goteleport.com request", "error", err) } } @@ -487,7 +492,7 @@ func (a *agent) handleGlobalRequests(ctx context.Context, requests <-chan *ssh.R // This handles keep-alive messages and matches the behavior of OpenSSH. err := a.client.Reply(r, false, nil) if err != nil { - a.log.Debugf("Failed to reply to %v request: %v.", r.Type, err) + a.logger.DebugContext(ctx, "Failed to reply to global request", "request_type", r.Type, "error", err) continue } } @@ -555,10 +560,10 @@ func (a *agent) handleDrainChannels() error { bytes, _ := a.clock.Now().UTC().MarshalText() _, err := a.hbChannel.SendRequest(a.ctx, "ping", false, bytes) if err != nil { - a.log.Error(err) + a.logger.ErrorContext(a.ctx, "failed to send ping request", "error", err) return trace.Wrap(err) } - a.log.Debugf("Ping -> %v.", a.client.RemoteAddr()) + a.logger.DebugContext(a.ctx, "Sent ping request", "target_addr", logutils.StringerAttr(a.client.RemoteAddr())) // Handle transport requests. case nch := <-a.transportC: if nch == nil { @@ -567,15 +572,15 @@ func (a *agent) handleDrainChannels() error { if a.isDraining() { err := nch.Reject(ssh.ConnectionFailed, "agent connection is draining") if err != nil { - a.log.WithError(err).Warningf("Failed to reject transport channel.") + a.logger.WarnContext(a.ctx, "Failed to reject transport channel", "error", err) } continue } - a.log.Debugf("Transport request: %v.", nch.ChannelType()) + a.logger.DebugContext(a.ctx, "Received trransport request", "channel_type", nch.ChannelType()) ch, req, err := nch.Accept() if err != nil { - a.log.Warningf("Failed to accept transport request: %v.", err) + a.logger.WarnContext(a.ctx, "Failed to accept transport request", "error", err) continue } @@ -601,10 +606,10 @@ func (a *agent) handleChannels() error { if nch == nil { continue } - a.log.Debugf("Discovery request channel opened: %v.", nch.ChannelType()) + a.logger.DebugContext(a.ctx, "Discovery request channel opened", "channel_type", nch.ChannelType()) ch, req, err := nch.Accept() if err != nil { - a.log.Warningf("Failed to accept discovery channel request: %v.", err) + a.logger.WarnContext(a.ctx, "Failed to accept discovery channel request", "error", err) continue } @@ -624,11 +629,11 @@ func (a *agent) handleChannels() error { // ch : SSH channel which received "teleport-transport" out-of-band request // reqC : request payload func (a *agent) handleDiscovery(ch ssh.Channel, reqC <-chan *ssh.Request) { - a.log.Debugf("handleDiscovery requests channel.") + a.logger.DebugContext(a.ctx, "handleDiscovery requests channel") sshutils.DiscardChannelData(ch) defer func() { if err := ch.Close(); err != nil { - a.log.Warnf("Failed to close discovery channel: %v", err) + a.logger.WarnContext(a.ctx, "Failed to close discovery channel", "error", err) } }() @@ -639,17 +644,17 @@ func (a *agent) handleDiscovery(ch ssh.Channel, reqC <-chan *ssh.Request) { return case req = <-reqC: if req == nil { - a.log.Infof("Connection closed, returning") + a.logger.InfoContext(a.ctx, "Connection closed, returning") return } var r discoveryRequest if err := json.Unmarshal(req.Payload, &r); err != nil { - a.log.WithError(err).Warn("Bad payload") + a.logger.WarnContext(a.ctx, "Received discovery request with bad payload", "error", err) return } - a.log.Debugf("Received discovery request: %s", &r) + a.logger.DebugContext(a.ctx, "Received discovery request", "discovery_request", logutils.StringerAttr(&r)) a.tracker.TrackExpected(r.TrackProxies()...) } } diff --git a/lib/reversetunnel/agent_dialer.go b/lib/reversetunnel/agent_dialer.go index 56c710733a343..01f79397c3dff 100644 --- a/lib/reversetunnel/agent_dialer.go +++ b/lib/reversetunnel/agent_dialer.go @@ -20,10 +20,10 @@ package reversetunnel import ( "context" + "log/slog" "strings" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" apidefaults "github.com/gravitational/teleport/api/defaults" @@ -55,7 +55,7 @@ type agentDialer struct { authMethods []ssh.AuthMethod fips bool options []proxy.DialerOptionFunc - log logrus.FieldLogger + logger *slog.Logger isClaimed func(principals ...string) bool } @@ -65,7 +65,7 @@ func (d *agentDialer) DialContext(ctx context.Context, addr utils.NetAddr) (SSHC dialer := proxy.DialerFromEnvironment(addr.Addr, d.options...) pconn, err := dialer.DialTimeout(ctx, addr.AddrNetwork, addr.Addr, apidefaults.DefaultIOTimeout) if err != nil { - d.log.WithError(err).Debugf("Failed to dial %s.", addr.Addr) + d.logger.DebugContext(ctx, "Failed to dial", "error", err, "target_addr", addr.Addr) return nil, trace.Wrap(err) } @@ -75,7 +75,7 @@ func (d *agentDialer) DialContext(ctx context.Context, addr utils.NetAddr) (SSHC GetHostCheckers: d.hostCheckerFunc(ctx), OnCheckCert: func(c *ssh.Certificate) error { if d.isClaimed != nil && d.isClaimed(c.ValidPrincipals...) { - d.log.Debugf("Aborting SSH handshake because the proxy %q is already claimed by some other agent.", c.ValidPrincipals[0]) + d.logger.DebugContext(ctx, "Aborting SSH handshake because the proxy is already claimed by some other agent.", "proxy_id", c.ValidPrincipals[0]) // the error message must end with // [proxyAlreadyClaimedError] to be recognized by // [isProxyAlreadyClaimed] @@ -88,7 +88,7 @@ func (d *agentDialer) DialContext(ctx context.Context, addr utils.NetAddr) (SSHC FIPS: d.fips, }) if err != nil { - d.log.Debugf("Failed to create host key callback for %v: %v.", addr.Addr, err) + d.logger.DebugContext(ctx, "Failed to create host key callback", "target_addr", addr.Addr, "error", err) return nil, trace.Wrap(err) } diff --git a/lib/reversetunnel/agent_dialer_test.go b/lib/reversetunnel/agent_dialer_test.go index 2293c7b7a2620..ec912a51fa690 100644 --- a/lib/reversetunnel/agent_dialer_test.go +++ b/lib/reversetunnel/agent_dialer_test.go @@ -23,7 +23,6 @@ import ( "testing" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh" @@ -90,7 +89,7 @@ func TestAgentCertChecker(t *testing.T) { dialer := agentDialer{ client: &fakeClient{caKey: ca.PublicKey()}, authMethods: []ssh.AuthMethod{ssh.PublicKeys(signer)}, - log: logrus.New(), + logger: utils.NewSlogLoggerForTests(), } _, err = dialer.DialContext(context.Background(), *utils.MustParseAddr(sshServer.Addr())) diff --git a/lib/reversetunnel/agentpool.go b/lib/reversetunnel/agentpool.go index 25a59cc1cdebe..c4ea000758570 100644 --- a/lib/reversetunnel/agentpool.go +++ b/lib/reversetunnel/agentpool.go @@ -25,13 +25,13 @@ import ( "errors" "fmt" "io" + "log/slog" "net" "sync" "time" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" @@ -94,7 +94,7 @@ type AgentPool struct { // backoff limits the rate at which new agents are created. backoff retryutils.Retry - log logrus.FieldLogger + logger *slog.Logger } // AgentPoolConfig holds configuration parameters for the agent pool @@ -201,13 +201,11 @@ func NewAgentPool(ctx context.Context, config AgentPoolConfig) (*AgentPool, erro active: newAgentStore(), events: make(chan Agent), backoff: retry, - log: logrus.WithFields(logrus.Fields{ - teleport.ComponentKey: teleport.ComponentReverseTunnelAgent, - teleport.ComponentFields: logrus.Fields{ - "targetCluster": config.Cluster, - "localCluster": config.LocalCluster, - }, - }), + logger: slog.With( + teleport.ComponentKey, teleport.ComponentReverseTunnelAgent, + "target_cluster", config.Cluster, + "local_cluster", config.LocalCluster, + ), runtimeConfig: newAgentPoolRuntimeConfig(), } @@ -239,7 +237,7 @@ func (p *AgentPool) updateConnectedProxies() { } proxies := p.active.proxyIDs() - p.log.Debugf("Updating connected proxies: %v", proxies) + p.logger.DebugContext(p.ctx, "Updating connected proxies", "proxies", proxies) p.AgentPoolConfig.ConnectedProxyGetter.setProxyIDs(proxies) } @@ -250,12 +248,15 @@ func (p *AgentPool) Count() int { // Start starts the agent pool in the background. func (p *AgentPool) Start() error { - p.log.Debugf("Starting agent pool %s.%s...", p.HostUUID, p.Cluster) + p.logger.DebugContext(p.ctx, "Starting agent pool", + "host_uuid", p.HostUUID, + "cluster", p.Cluster, + ) p.wg.Add(1) go func() { if err := p.run(); err != nil { - p.log.WithError(err).Warn("Agent pool exited.") + p.logger.WarnContext(p.ctx, "Agent pool exited", "error", err) } p.cancel() @@ -274,9 +275,9 @@ func (p *AgentPool) run() error { } else if isProxyAlreadyClaimed(err) { // "proxy already claimed" is a fairly benign error, we should not // spam the log with stack traces for it - p.log.Debugf("Failed to connect agent: %v.", err) + p.logger.DebugContext(p.ctx, "Failed to connect agent", "error", err) } else { - p.log.WithError(err).Debugf("Failed to connect agent.") + p.logger.DebugContext(p.ctx, "Failed to connect agent", "error", err) } } else { p.wg.Add(1) @@ -288,7 +289,7 @@ func (p *AgentPool) run() error { if p.ctx.Err() != nil { return nil } else if err != nil { - p.log.WithError(err).Debugf("Failed to wait for backoff.") + p.logger.DebugContext(p.ctx, "Failed to wait for backoff", "error", err) } } } @@ -337,7 +338,10 @@ func (p *AgentPool) updateRuntimeConfig(ctx context.Context) error { restrictConnectionCount := p.runtimeConfig.restrictConnectionCount() connectionCount := p.runtimeConfig.getConnectionCount() - p.log.Debugf("Runtime config: restrict_connection_count: %v connection_count: %v", restrictConnectionCount, connectionCount) + p.logger.DebugContext(ctx, "Runtime config updated", + "restrict_connection_count", restrictConnectionCount, + "connection_count", connectionCount, + ) if restrictConnectionCount { p.tracker.SetConnectionCount(connectionCount) @@ -420,7 +424,7 @@ func (p *AgentPool) handleEvent(ctx context.Context, agent Agent) { } } p.updateConnectedProxies() - p.log.Debugf("Active agent count: %d", p.active.len()) + p.logger.DebugContext(ctx, "Processed agent event", "active_agent_count", p.active.len()) } // stateCallback adds events to the queue for each agent state change. @@ -444,7 +448,7 @@ func (p *AgentPool) newAgent(ctx context.Context, tracker *track.Tracker, lease err = p.runtimeConfig.updateRemote(ctx, addr) if err != nil { - p.log.WithError(err).Debugf("Failed to update remote config.") + p.logger.DebugContext(ctx, "Failed to update remote config", "error", err) } options := []proxy.DialerOptionFunc{proxy.WithInsecureSkipTLSVerify(lib.IsInsecureDevMode())} @@ -458,7 +462,7 @@ func (p *AgentPool) newAgent(ctx context.Context, tracker *track.Tracker, lease authMethods: p.AuthMethods, options: options, username: p.HostUUID, - log: p.log, + logger: p.logger, isClaimed: p.tracker.IsClaimed, } @@ -471,7 +475,7 @@ func (p *AgentPool) newAgent(ctx context.Context, tracker *track.Tracker, lease tracker: tracker, lease: lease, clock: p.Clock, - log: p.log, + logger: p.logger, localAuthAddresses: p.LocalAuthAddresses, proxySigner: p.PROXYSigner, }) @@ -536,7 +540,7 @@ func (p *AgentPool) handleTransport(ctx context.Context, channel ssh.Channel, re sconn: conn, channel: channel, requestCh: requests, - log: p.log, + logger: p.logger, authServers: p.LocalAuthAddresses, proxySigner: p.PROXYSigner, forwardClientAddress: true, @@ -566,7 +570,7 @@ func (p *AgentPool) handleLocalTransport(ctx context.Context, channel ssh.Channe return case <-time.After(apidefaults.DefaultIOTimeout): go ssh.DiscardRequests(reqC) - p.log.Warn("Timed out waiting for transport dial request.") + p.logger.WarnContext(ctx, "Timed out waiting for transport dial request") return case r, ok := <-reqC: if !ok { @@ -579,14 +583,14 @@ func (p *AgentPool) handleLocalTransport(ctx context.Context, channel ssh.Channe // sconn should never be nil, but it's sourced from the agent state and // starts as nil, and the original transport code checked against it if sconn == nil || p.Server == nil { - p.log.Error("Missing client or server (this is a bug).") + p.logger.ErrorContext(ctx, "Missing client or server (this is a bug)") fmt.Fprintf(channel.Stderr(), "internal server error") req.Reply(false, nil) return } if err := req.Reply(true, nil); err != nil { - p.log.Errorf("Failed to respond to dial request: %v.", err) + p.logger.ErrorContext(ctx, "Failed to respond to dial request", "error", err) return } @@ -596,8 +600,9 @@ func (p *AgentPool) handleLocalTransport(ctx context.Context, channel ssh.Channe switch dialReq.Address { case reversetunnelclient.LocalNode, reversetunnelclient.LocalKubernetes, reversetunnelclient.LocalWindowsDesktop: default: - p.log.WithField("address", dialReq.Address). - Warn("Received dial request for unexpected address, routing to the local service anyway.") + p.logger.WarnContext(ctx, "Received dial request for unexpected address, routing to the local service anyway", + "dial_addr", dialReq.Address, + ) } if src, err := utils.ParseAddr(dialReq.ClientSrcAddr); err == nil { conn = utils.NewConnWithSrcAddr(conn, getTCPAddr(src)) @@ -768,7 +773,10 @@ func (c *agentPoolRuntimeConfig) updateRemote(ctx context.Context, addr *utils.N c.remoteTLSRoutingEnabled = tlsRoutingEnabled if c.remoteTLSRoutingEnabled { c.tlsRoutingConnUpgradeRequired = client.IsALPNConnUpgradeRequired(ctx, addr.Addr, lib.IsInsecureDevMode()) - logrus.Debugf("ALPN upgrade required for remote %v: %v", addr.Addr, c.tlsRoutingConnUpgradeRequired) + slog.DebugContext(ctx, "ALPN upgrade required for remote cluster", + "remot_addr", addr.Addr, + "conn_upgrade_required", c.tlsRoutingConnUpgradeRequired, + ) } return nil } @@ -802,7 +810,7 @@ func (c *agentPoolRuntimeConfig) update(ctx context.Context, netConfig types.Clu if err == nil { c.tlsRoutingConnUpgradeRequired = client.IsALPNConnUpgradeRequired(ctx, addr.Addr, lib.IsInsecureDevMode()) } else { - logrus.WithError(err).Warnf("Failed to resolve addr.") + slog.WarnContext(ctx, "Failed to resolve addr", "error", err) } } } diff --git a/lib/reversetunnel/conn.go b/lib/reversetunnel/conn.go index b78f1b97d9378..8a678690f97cd 100644 --- a/lib/reversetunnel/conn.go +++ b/lib/reversetunnel/conn.go @@ -19,8 +19,10 @@ package reversetunnel import ( + "context" "encoding/json" "fmt" + "log/slog" "net" "sync" "sync/atomic" @@ -28,12 +30,12 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/utils/sshutils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) // connKey is a key used to identify tunnel connections. It contains the UUID @@ -54,8 +56,8 @@ type remoteConn struct { lastHeartbeat atomic.Int64 *connConfig - mu sync.Mutex - log *logrus.Entry + mu sync.Mutex + logger *slog.Logger // discoveryCh is the SSH channel over which discovery requests are sent. discoveryCh ssh.Channel @@ -109,9 +111,7 @@ type connConfig struct { func newRemoteConn(cfg *connConfig) *remoteConn { c := &remoteConn{ - log: logrus.WithFields(logrus.Fields{ - teleport.ComponentKey: "discovery", - }), + logger: slog.With(teleport.ComponentKey, "discovery"), connConfig: cfg, clock: clockwork.NewRealClock(), newProxiesC: make(chan []types.Server, 100), @@ -181,7 +181,11 @@ func (c *remoteConn) markInvalid(err error) { c.lastError = err c.invalid.Store(true) - c.log.Warnf("Unhealthy connection to %v %v: %v.", c.clusterName, c.conn.RemoteAddr(), err) + c.logger.WarnContext(context.Background(), "Unhealthy reverse tunnel connection", + "cluster", c.clusterName, + "remote_addr", logutils.StringerAttr(c.conn.RemoteAddr()), + "error", err, + ) } func (c *remoteConn) markValid() { @@ -256,7 +260,7 @@ func (c *remoteConn) updateProxies(proxies []types.Server) { default: // Missing proxies update is no longer critical with more permissive // discovery protocol that tolerates conflicting, stale or missing updates - c.log.Warnf("Discovery channel overflow at %v.", len(c.newProxiesC)) + c.logger.WarnContext(context.Background(), "Discovery channel overflow", "new_proxy_count", len(c.newProxiesC)) } } @@ -267,7 +271,7 @@ func (c *remoteConn) adviseReconnect() error { // sendDiscoveryRequest sends a discovery request with up to date // list of connected proxies -func (c *remoteConn) sendDiscoveryRequest(req discoveryRequest) error { +func (c *remoteConn) sendDiscoveryRequest(ctx context.Context, req discoveryRequest) error { discoveryCh, err := c.openDiscoveryChannel() if err != nil { return trace.Wrap(err) @@ -282,7 +286,10 @@ func (c *remoteConn) sendDiscoveryRequest(req discoveryRequest) error { // Log the discovery request being sent. Useful for debugging to know what // proxies the tunnel server thinks exist. - c.log.Debugf("Sending discovery request with proxies %v to %v.", req.ProxyNames(), c.sconn.RemoteAddr()) + c.logger.DebugContext(ctx, "Sending discovery request", + "proxies", req.ProxyNames(), + "target_addr", logutils.StringerAttr(c.sconn.RemoteAddr()), + ) if _, err := discoveryCh.SendRequest(chanDiscoveryReq, false, payload); err != nil { c.markInvalid(err) diff --git a/lib/reversetunnel/localsite.go b/lib/reversetunnel/localsite.go index 7c89ea25273b0..3446a882cec23 100644 --- a/lib/reversetunnel/localsite.go +++ b/lib/reversetunnel/localsite.go @@ -21,6 +21,7 @@ package reversetunnel import ( "context" "fmt" + "log/slog" "net" "slices" "sync" @@ -29,7 +30,6 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" "github.com/prometheus/client_golang/prometheus" - log "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" @@ -48,6 +48,7 @@ import ( "github.com/gravitational/teleport/lib/srv/forward" "github.com/gravitational/teleport/lib/teleagent" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" proxyutils "github.com/gravitational/teleport/lib/utils/proxy" ) @@ -102,12 +103,10 @@ func newLocalSite(srv *server, domainName string, authServers []string, opts ... authServers: authServers, remoteConns: make(map[connKey][]*remoteConn), clock: srv.Clock, - log: log.WithFields(log.Fields{ - teleport.ComponentKey: teleport.ComponentReverseTunnelServer, - teleport.ComponentFields: map[string]string{ - "cluster": domainName, - }, - }), + logger: slog.With( + teleport.ComponentKey, teleport.ComponentReverseTunnelServer, + "cluster", domainName, + ), offlineThreshold: srv.offlineThreshold, peerClient: srv.PeerClient, periodicFunctionInterval: periodicFunctionInterval, @@ -129,7 +128,7 @@ func newLocalSite(srv *server, domainName string, authServers []string, opts ... // // it implements RemoteSite interface type localSite struct { - log log.FieldLogger + logger *slog.Logger domainName string authServers []string srv *server @@ -292,13 +291,15 @@ func (s *localSite) maybeSendSignedPROXYHeader(params reversetunnelclient.DialPa // TODO(awly): unit test this func (s *localSite) DialTCP(params reversetunnelclient.DialParams) (net.Conn, error) { - s.log.Debugf("Dialing %v.", params) + ctx := s.srv.ctx + logger := s.logger.With("dial_params", logutils.StringerAttr(params)) + logger.DebugContext(ctx, "Initiating dia request") conn, useTunnel, err := s.getConn(params) if err != nil { return nil, trace.Wrap(err) } - s.log.Debugf("Succeeded dialing %v.", params) + logger.DebugContext(ctx, "Succeeded dialing") if err := s.maybeSendSignedPROXYHeader(params, conn, useTunnel); err != nil { return nil, trace.Wrap(err) @@ -320,12 +321,12 @@ func (s *localSite) adviseReconnect(ctx context.Context) { s.remoteConnsMtx.Lock() for _, conns := range s.remoteConns { for _, conn := range conns { - s.log.Debugf("Sending reconnect: %s", conn.nodeID) + s.logger.DebugContext(ctx, "Sending reconnect to server ", "server_id", conn.nodeID) wg.Add(1) go func(conn *remoteConn) { if err := conn.adviseReconnect(); err != nil { - s.log.WithError(err).Warn("Failed sending reconnect advisory") + s.logger.WarnContext(ctx, "Failed sending reconnect advisory", "error", err) } wg.Done() }(conn) @@ -346,10 +347,15 @@ func (s *localSite) adviseReconnect(ctx context.Context) { } func (s *localSite) dialAndForward(params reversetunnelclient.DialParams) (_ net.Conn, retErr error) { + ctx := s.srv.ctx + if params.GetUserAgent == nil && !params.IsAgentlessNode { return nil, trace.BadParameter("agentless node require an agent getter") } - s.log.Debugf("Dialing and forwarding from %v to %v.", params.From, params.To) + s.logger.DebugContext(ctx, "Initiating dial and forwarding request", + "source_addr", logutils.StringerAttr(params.From), + "target_addr", logutils.StringerAttr(params.To), + ) // request user agent connection if a SSH user agent is set var userAgent teleagent.Agent @@ -378,7 +384,7 @@ func (s *localSite) dialAndForward(params reversetunnelclient.DialParams) (_ net } // Get a host certificate for the forwarding node from the cache. - hostCertificate, err := s.certificateCache.getHostCertificate(context.TODO(), params.Address, params.Principals) + hostCertificate, err := s.certificateCache.getHostCertificate(ctx, params.Address, params.Principals) if err != nil { return nil, trace.Wrap(err) } @@ -438,7 +444,10 @@ func (s *localSite) dialTunnel(dreq *sshutils.DialReq) (net.Conn, error) { return nil, trace.NotFound("no tunnel connection found: %v", err) } - s.log.Debugf("Tunnel dialing to %v, client source %v", dreq.ServerID, dreq.ClientSrcAddr) + s.logger.DebugContext(s.srv.ctx, "Tunnel dialing to host", + "target_host_id", dreq.ServerID, + "src_addr", dreq.ClientSrcAddr, + ) conn, err := s.chanTransportConn(rconn, dreq) if err != nil { @@ -607,7 +616,7 @@ func (s *localSite) getConn(params reversetunnelclient.DialParams) (conn net.Con peeringEnabled := s.tryProxyPeering(params) if peeringEnabled { - s.log.Info("Dialing over peer proxy") + s.logger.InfoContext(s.srv.ctx, "Dialing over peer proxy") conn, peerErr = s.peerClient.DialNode( params.ProxyIDs, params.ServerID, params.From, params.To, params.ConnType, ) @@ -645,7 +654,7 @@ func (s *localSite) getConn(params reversetunnelclient.DialParams) (conn net.Con dialTimeout := apidefaults.DefaultIOTimeout if cnc, err := s.accessPoint.GetClusterNetworkingConfig(s.srv.Context); err != nil { - s.log.WithError(err).Warn("Failed to get cluster networking config - using default dial timeout") + s.logger.WarnContext(s.srv.ctx, "Failed to get cluster networking config - using default dial timeout", "error", err) } else { dialTimeout = cnc.GetSSHDialTimeout() } @@ -653,7 +662,12 @@ func (s *localSite) getConn(params reversetunnelclient.DialParams) (conn net.Con conn, directErr = dialer.DialTimeout(s.srv.Context, params.To.Network(), params.To.String(), dialTimeout) if directErr != nil { directMsg := getTunnelErrorMessage(params, "direct dial", directErr) - s.log.WithField("address", params.To.String()).Debugf("All attempted dial methods failed. tunnel=%q, peer=%q, direct=%q", tunnelErr, peerErr, directErr) + s.logger.DebugContext(s.srv.ctx, "All attempted dial methods failed", + "target_addr", logutils.StringerAttr(params.To), + "tunnel_error", tunnelErr, + "peer_error", peerErr, + "direct_error", directErr, + ) aggregateErr := trace.NewAggregate(tunnelErr, peerErr, directErr) return nil, false, trace.ConnectionProblem(aggregateErr, directMsg) } @@ -701,29 +715,29 @@ func (s *localSite) fanOutProxies(proxies []types.Server) { // handleHeartbeat receives heartbeat messages from the connected agent // if the agent has missed several heartbeats in a row, Proxy marks // the connection as invalid. -func (s *localSite) handleHeartbeat(rconn *remoteConn, ch ssh.Channel, reqC <-chan *ssh.Request) { +func (s *localSite) handleHeartbeat(ctx context.Context, rconn *remoteConn, ch ssh.Channel, reqC <-chan *ssh.Request) { sshutils.DiscardChannelData(ch) if ch != nil { defer func() { if err := ch.Close(); err != nil { - s.log.Warnf("Failed to close heartbeat channel: %v", err) + s.logger.WarnContext(ctx, "Failed to close heartbeat channel", "error", err) } }() } - logger := s.log.WithFields(log.Fields{ - "serverID": rconn.nodeID, - "addr": rconn.conn.RemoteAddr().String(), - }) + logger := s.logger.With( + "server_id", rconn.nodeID, + "addr", logutils.StringerAttr(rconn.conn.RemoteAddr()), + ) firstHeartbeat := true proxyResyncTicker := s.clock.NewTicker(s.proxySyncInterval) defer func() { proxyResyncTicker.Stop() - logger.Warn("Closing remote connection to agent.") + logger.WarnContext(ctx, "Closing remote connection to agent") s.removeRemoteConn(rconn) if err := rconn.Close(); err != nil && !utils.IsOKNetworkError(err) { - logger.WithError(err).Warn("Failed to close remote connection") + logger.WarnContext(ctx, "Failed to close remote connection", "error", err) } if !firstHeartbeat { reverseSSHTunnels.WithLabelValues(rconn.tunnelType).Dec() @@ -735,18 +749,18 @@ func (s *localSite) handleHeartbeat(rconn *remoteConn, ch ssh.Channel, reqC <-ch for { select { case <-s.srv.ctx.Done(): - logger.Info("Closing") + logger.InfoContext(ctx, "Closing") return case <-proxyResyncTicker.Chan(): var req discoveryRequest - proxies, err := s.srv.proxyWatcher.CurrentResources(s.srv.ctx) + proxies, err := s.srv.proxyWatcher.CurrentResources(ctx) if err != nil { - logger.WithError(err).Warn("Failed to get proxy set") + logger.WarnContext(ctx, "Failed to get proxy set", "error", err) } req.SetProxies(proxies) - if err := rconn.sendDiscoveryRequest(req); err != nil { - logger.WithError(err).Debug("Marking connection invalid on error") + if err := rconn.sendDiscoveryRequest(ctx, req); err != nil { + logger.DebugContext(ctx, "Marking connection invalid on error", "error", err) rconn.markInvalid(err) return } @@ -754,14 +768,14 @@ func (s *localSite) handleHeartbeat(rconn *remoteConn, ch ssh.Channel, reqC <-ch var req discoveryRequest req.SetProxies(proxies) - if err := rconn.sendDiscoveryRequest(req); err != nil { - logger.WithError(err).Debug("Failed to send discovery request to agent") + if err := rconn.sendDiscoveryRequest(ctx, req); err != nil { + logger.DebugContext(ctx, "Failed to send discovery request to agent", "error", err) rconn.markInvalid(err) return } case req := <-reqC: if req == nil { - logger.Debug("Agent disconnected.") + logger.DebugContext(ctx, "Agent disconnected") rconn.markInvalid(trace.ConnectionProblem(nil, "agent disconnected")) return } @@ -770,7 +784,7 @@ func (s *localSite) handleHeartbeat(rconn *remoteConn, ch ssh.Channel, reqC <-ch // send it the list of current proxies back proxies, err := s.srv.proxyWatcher.CurrentResources(s.srv.ctx) if err != nil { - logger.WithError(err).Warn("Failed to get proxy set") + logger.WarnContext(ctx, "Failed to get proxy set", "error", err) } if len(proxies) > 0 { rconn.updateProxies(proxies) @@ -788,9 +802,9 @@ func (s *localSite) handleHeartbeat(rconn *remoteConn, ch ssh.Channel, reqC <-ch log := logger if roundtrip != 0 { - log = logger.WithField("latency", roundtrip.String()) + log = logger.With("latency", logutils.StringerAttr(roundtrip)) } - log.Debugf("Ping <- %v", rconn.conn.RemoteAddr()) + log.DebugContext(ctx, "Received ping request", "remote_addr", logutils.StringerAttr(rconn.conn.RemoteAddr())) rconn.setLastHeartbeat(s.clock.Now().UTC()) rconn.markValid() @@ -799,10 +813,10 @@ func (s *localSite) handleHeartbeat(rconn *remoteConn, ch ssh.Channel, reqC <-ch // terminate and remove the connection if offline, otherwise warn and wait for the next heartbeat if rconn.isOffline(t, s.offlineThreshold*missedHeartBeatThreshold) { - logger.Errorf("Closing unhealthy and idle connection. Heartbeat last received at %s", rconn.getLastHeartbeat()) + logger.ErrorContext(ctx, "Closing unhealthy and idle connection", "last_heartbeat", rconn.getLastHeartbeat()) return } - logger.Warnf("Deferring closure of unhealthy connection due to %d active connections", rconn.activeSessions()) + logger.WarnContext(ctx, "Deferring closure of unhealthy connection due to active connections", "active_conn_count", rconn.activeSessions()) offlineThresholdTimer.Reset(s.offlineThreshold) continue @@ -878,7 +892,7 @@ func (s *localSite) getRemoteConn(dreq *sshutils.DialReq) (*remoteConn, error) { } func (s *localSite) chanTransportConn(rconn *remoteConn, dreq *sshutils.DialReq) (net.Conn, error) { - s.log.Debugf("Connecting to %v through tunnel.", rconn.conn.RemoteAddr()) + s.logger.DebugContext(s.srv.ctx, "Connecting to target through tunnel", "target_addr", logutils.StringerAttr(rconn.conn.RemoteAddr())) conn, markInvalid, err := sshutils.ConnectProxyTransport(rconn.sconn, dreq, false) if err != nil { @@ -934,7 +948,7 @@ func (s *localSite) periodicFunctions() { return case <-ticker.Chan(): if err := s.sshTunnelStats(); err != nil { - s.log.Warningf("Failed to report SSH tunnel statistics for: %v: %v.", s.domainName, err) + s.logger.WarnContext(s.srv.ctx, "Failed to report SSH tunnel statistics ", "cluster", s.domainName, "error", err) } } } @@ -988,7 +1002,11 @@ func (s *localSite) sshTunnelStats() error { if n > 10 { n = 10 } - s.log.Debugf("Cluster %v is missing %v tunnels. A small number of missing tunnels is normal, for example, a node could have just been shut down, the proxy restarted, etc. However, if this error persists with an elevated number of missing tunnels, it often indicates nodes can not discover all registered proxies. Check that all of your proxies are behind a load balancer and the load balancer is using a round robin strategy. Some of the missing hosts: %v.", s.domainName, len(missing), missing[:n]) + s.logger.DebugContext(s.srv.ctx, "Cluster is missing some tunnels. A small number of missing tunnels is normal, for example, a node could have just been shut down, the proxy restarted, etc. However, if this error persists with an elevated number of missing tunnels, it often indicates nodes can not discover all registered proxies. Check that all of your proxies are behind a load balancer and the load balancer is using a round robin strategy", + "cluster", s.domainName, + "missing_count", len(missing), + "missing", missing[:n], + ) } return nil } diff --git a/lib/reversetunnel/localsite_test.go b/lib/reversetunnel/localsite_test.go index 195a1e76510c2..543ecfd894c2e 100644 --- a/lib/reversetunnel/localsite_test.go +++ b/lib/reversetunnel/localsite_test.go @@ -77,7 +77,7 @@ func TestRemoteConnCleanup(t *testing.T) { ctx: ctx, Config: Config{Clock: clock}, localAuthClient: &mockLocalSiteClient{}, - log: utils.NewLoggerForTests(), + logger: utils.NewSlogLoggerForTests(), offlineThreshold: time.Second, proxyWatcher: watcher, } @@ -102,7 +102,7 @@ func TestRemoteConnCleanup(t *testing.T) { // terminated by too many missed heartbeats go func() { - site.handleHeartbeat(conn1, nil, reqs) + site.handleHeartbeat(ctx, conn1, nil, reqs) cancel() }() @@ -273,7 +273,7 @@ func TestProxyResync(t *testing.T) { ctx: ctx, Config: Config{Clock: clock}, localAuthClient: &mockLocalSiteClient{}, - log: utils.NewLoggerForTests(), + logger: utils.NewSlogLoggerForTests(), offlineThreshold: 24 * time.Hour, proxyWatcher: watcher, } @@ -312,7 +312,7 @@ func TestProxyResync(t *testing.T) { // terminated by canceled context go func() { - site.handleHeartbeat(conn1, nil, reqs) + site.handleHeartbeat(ctx, conn1, nil, reqs) }() expected := []types.Server{proxy1, proxy2} diff --git a/lib/reversetunnel/peer.go b/lib/reversetunnel/peer.go index 570be5edf4bbe..675ad71e4522a 100644 --- a/lib/reversetunnel/peer.go +++ b/lib/reversetunnel/peer.go @@ -26,7 +26,6 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - log "github.com/sirupsen/logrus" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" @@ -155,14 +154,8 @@ func (p *clusterPeers) Close() error { return nil } // newClusterPeer returns new cluster peer func newClusterPeer(srv *server, connInfo types.TunnelConnection, offlineThreshold time.Duration) (*clusterPeer, error) { clusterPeer := &clusterPeer{ - srv: srv, - connInfo: connInfo, - log: log.WithFields(log.Fields{ - teleport.ComponentKey: teleport.ComponentReverseTunnelServer, - teleport.ComponentFields: map[string]string{ - "cluster": connInfo.GetClusterName(), - }, - }), + srv: srv, + connInfo: connInfo, clock: clockwork.NewRealClock(), offlineThreshold: offlineThreshold, } @@ -173,8 +166,6 @@ func newClusterPeer(srv *server, connInfo types.TunnelConnection, offlineThresho // clusterPeer is a remote cluster that has established // a tunnel to the peers type clusterPeer struct { - log *log.Entry - mu sync.Mutex connInfo types.TunnelConnection srv *server diff --git a/lib/reversetunnel/rc_manager.go b/lib/reversetunnel/rc_manager.go index 03db3f13f613f..f1e539ac3bf8b 100644 --- a/lib/reversetunnel/rc_manager.go +++ b/lib/reversetunnel/rc_manager.go @@ -20,12 +20,12 @@ package reversetunnel import ( "context" + "log/slog" "sync" "time" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" @@ -81,8 +81,8 @@ type RemoteClusterTunnelManagerConfig struct { KubeDialAddr utils.NetAddr // FIPS indicates if Teleport was started in FIPS mode. FIPS bool - // Log is the logger - Log logrus.FieldLogger + // Logger is the logger + Logger *slog.Logger // LocalAuthAddresses is a list of auth servers to use when dialing back to // the local cluster. LocalAuthAddresses []string @@ -109,8 +109,8 @@ func (c *RemoteClusterTunnelManagerConfig) CheckAndSetDefaults() error { if c.Clock == nil { c.Clock = clockwork.NewRealClock() } - if c.Log == nil { - c.Log = logrus.New() + if c.Logger == nil { + c.Logger = slog.Default() } return nil @@ -153,7 +153,7 @@ func (w *RemoteClusterTunnelManager) Run(ctx context.Context) { w.mu.Unlock() if err := w.Sync(ctx); err != nil { - w.cfg.Log.Warningf("Failed to sync reverse tunnels: %v.", err) + w.cfg.Logger.WarnContext(ctx, "Failed to sync reverse tunnels", "error", err) } ticker := time.NewTicker(defaults.ResyncInterval) @@ -162,11 +162,11 @@ func (w *RemoteClusterTunnelManager) Run(ctx context.Context) { for { select { case <-ctx.Done(): - w.cfg.Log.Debugf("Closing.") + w.cfg.Logger.DebugContext(ctx, "Closing") return case <-ticker.C: if err := w.Sync(ctx); err != nil { - w.cfg.Log.Warningf("Failed to sync reverse tunnels: %v.", err) + w.cfg.Logger.WarnContext(ctx, "Failed to sync reverse tunnels", "error", err) continue } } @@ -247,7 +247,7 @@ func realNewAgentPool(ctx context.Context, cfg RemoteClusterTunnelManagerConfig, } if err := pool.Start(); err != nil { - cfg.Log.WithError(err).Error("Failed to start agent pool") + cfg.Logger.ErrorContext(ctx, "Failed to start agent pool", "error", err) } return pool, nil diff --git a/lib/reversetunnel/remotesite.go b/lib/reversetunnel/remotesite.go index f9617f33b87d5..bfb3fa91412b4 100644 --- a/lib/reversetunnel/remotesite.go +++ b/lib/reversetunnel/remotesite.go @@ -23,13 +23,13 @@ import ( "crypto/tls" "crypto/x509" "fmt" + "log/slog" "net" "sync" "time" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - log "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" @@ -46,6 +46,7 @@ import ( "github.com/gravitational/teleport/lib/srv/forward" "github.com/gravitational/teleport/lib/teleagent" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) // remoteSite is a remote site that established the inbound connection to @@ -54,7 +55,7 @@ import ( type remoteSite struct { sync.RWMutex - logger *log.Entry + logger *slog.Logger domainName string connections []*remoteConn lastUsed int @@ -115,7 +116,7 @@ func (s *remoteSite) getRemoteClient() (authclient.ClientI, bool, error) { // The fact that cluster has keys to remote CA means that the key exchange // has completed. - s.logger.Debug("Using TLS client to remote cluster.") + s.logger.DebugContext(s.ctx, "Using TLS client to remote cluster") tlsConfig := utils.TLSConfig(s.srv.ClientTLSCipherSuites) // encode the name of this cluster to identify this cluster, // connecting to the remote one (it is used to find the right certificate @@ -272,7 +273,7 @@ func (s *remoteSite) removeInvalidConns() { } else { go func(conn *remoteConn) { if err := conn.Close(); err != nil { - s.logger.WithError(err).Warn("Failed to close invalid connection") + s.logger.WarnContext(s.ctx, "Failed to close invalid connection", "error", err) } }(s.connections[i]) } @@ -305,12 +306,12 @@ func (s *remoteSite) adviseReconnect(ctx context.Context) { s.RLock() for _, conn := range s.connections { - s.logger.Debugf("Sending reconnect: %s", conn.nodeID) + s.logger.DebugContext(ctx, "Sending reconnect to server", "server_id", conn.nodeID) wg.Add(1) go func(conn *remoteConn) { if err := conn.adviseReconnect(); err != nil { - s.logger.WithError(err).Warn("Failed to send reconnection advisory") + s.logger.WarnContext(ctx, "Failed to send reconnection advisory", "error", err) } wg.Done() }(conn) @@ -365,7 +366,7 @@ func (s *remoteSite) registerHeartbeat(t time.Time) { s.setLastConnInfo(connInfo) err := s.localAccessPoint.UpsertTunnelConnection(connInfo) if err != nil { - s.logger.WithError(err).Warn("Failed to register heartbeat") + s.logger.WarnContext(s.ctx, "Failed to register heartbeat", "error", err) } } @@ -373,7 +374,7 @@ func (s *remoteSite) registerHeartbeat(t time.Time) { // that this node lost the connection and needs to be discovered func (s *remoteSite) deleteConnectionRecord() { if err := s.localAccessPoint.DeleteTunnelConnection(s.connInfo.GetClusterName(), s.connInfo.GetName()); err != nil { - s.logger.WithError(err).Warn("Failed to delete tunnel connection") + s.logger.WarnContext(s.ctx, "Failed to delete tunnel connection", "error", err) } } @@ -391,17 +392,17 @@ func (s *remoteSite) fanOutProxies(proxies []types.Server) { // handleHeartbeat receives heartbeat messages from the connected agent // if the agent has missed several heartbeats in a row, Proxy marks // the connection as invalid. -func (s *remoteSite) handleHeartbeat(conn *remoteConn, ch ssh.Channel, reqC <-chan *ssh.Request) { - logger := s.logger.WithFields(log.Fields{ - "serverID": conn.nodeID, - "addr": conn.conn.RemoteAddr().String(), - }) +func (s *remoteSite) handleHeartbeat(ctx context.Context, conn *remoteConn, ch ssh.Channel, reqC <-chan *ssh.Request) { + logger := s.logger.With( + "server_id", conn.nodeID, + "addr", logutils.StringerAttr(conn.conn.RemoteAddr()), + ) sshutils.DiscardChannelData(ch) if ch != nil { defer func() { if err := ch.Close(); err != nil { - logger.Warnf("Failed to close heartbeat channel: %v", err) + logger.WarnContext(ctx, "Failed to close heartbeat channel", "error", err) } }() } @@ -410,14 +411,14 @@ func (s *remoteSite) handleHeartbeat(conn *remoteConn, ch ssh.Channel, reqC <-ch proxyResyncTicker := s.clock.NewTicker(s.proxySyncInterval) defer func() { proxyResyncTicker.Stop() - logger.Info("Cluster connection closed.") + logger.InfoContext(ctx, "Cluster connection closed") if err := conn.Close(); err != nil && !utils.IsUseOfClosedNetworkError(err) { - logger.WithError(err).Warnf("Failed to close remote connection for remote site") + logger.WarnContext(ctx, "Failed to close remote connection for remote site", "error", err) } if err := s.srv.onSiteTunnelClose(s); err != nil { - logger.WithError(err).Warn("Failed to close remote site") + logger.WarnContext(ctx, "Failed to close remote site", "error", err) } }() @@ -426,18 +427,18 @@ func (s *remoteSite) handleHeartbeat(conn *remoteConn, ch ssh.Channel, reqC <-ch for { select { case <-s.ctx.Done(): - logger.Infof("closing") + logger.InfoContext(ctx, "closing") return case <-proxyResyncTicker.Chan(): var req discoveryRequest proxies, err := s.srv.proxyWatcher.CurrentResources(s.srv.ctx) if err != nil { - logger.WithError(err).Warn("Failed to get proxy set") + logger.WarnContext(ctx, "Failed to get proxy set", "error", err) } req.SetProxies(proxies) - if err := conn.sendDiscoveryRequest(req); err != nil { - logger.WithError(err).Debug("Marking connection invalid on error") + if err := conn.sendDiscoveryRequest(ctx, req); err != nil { + logger.DebugContext(ctx, "Marking connection invalid on error", "error", err) conn.markInvalid(err) return } @@ -445,17 +446,17 @@ func (s *remoteSite) handleHeartbeat(conn *remoteConn, ch ssh.Channel, reqC <-ch var req discoveryRequest req.SetProxies(proxies) - if err := conn.sendDiscoveryRequest(req); err != nil { - logger.WithError(err).Debug("Marking connection invalid on error") + if err := conn.sendDiscoveryRequest(ctx, req); err != nil { + logger.DebugContext(ctx, "Marking connection invalid on error", "error", err) conn.markInvalid(err) return } case req := <-reqC: if req == nil { - logger.Info("Cluster agent disconnected.") + logger.InfoContext(ctx, "Cluster agent disconnected") conn.markInvalid(trace.ConnectionProblem(nil, "agent disconnected")) if !s.HasValidConnections() { - logger.Debug("Deleting connection record.") + logger.DebugContext(ctx, "Deleting connection record") s.deleteConnectionRecord() } return @@ -463,9 +464,9 @@ func (s *remoteSite) handleHeartbeat(conn *remoteConn, ch ssh.Channel, reqC <-ch if firstHeartbeat { // as soon as the agent connects and sends a first heartbeat // send it the list of current proxies back - proxies, err := s.srv.proxyWatcher.CurrentResources(s.srv.ctx) + proxies, err := s.srv.proxyWatcher.CurrentResources(ctx) if err != nil { - logger.WithError(err).Warn("Failed to get proxy set") + logger.WarnContext(ctx, "Failed to get proxy set", "error", err) } if len(proxies) > 0 { conn.updateProxies(proxies) @@ -482,9 +483,9 @@ func (s *remoteSite) handleHeartbeat(conn *remoteConn, ch ssh.Channel, reqC <-ch pinglog := logger if roundtrip != 0 { - pinglog = pinglog.WithField("latency", roundtrip) + pinglog = pinglog.With("latency", roundtrip) } - pinglog.Debugf("Ping <- %v", conn.conn.RemoteAddr()) + pinglog.DebugContext(ctx, "Received ping request", "remote_addr", logutils.StringerAttr(conn.conn.RemoteAddr())) tm := s.clock.Now().UTC() conn.setLastHeartbeat(tm) @@ -498,11 +499,11 @@ func (s *remoteSite) handleHeartbeat(conn *remoteConn, ch ssh.Channel, reqC <-ch if t.After(hb.Add(s.offlineThreshold * missedHeartBeatThreshold)) { count := conn.activeSessions() if count == 0 { - logger.Errorf("Closing unhealthy and idle connection. Heartbeat last received at %s", hb) + logger.ErrorContext(ctx, "Closing unhealthy and idle connection", "last_heartbeat", hb) return } - logger.Warnf("Deferring closure of unhealthy connection due to %d active connections", count) + logger.WarnContext(ctx, "Deferring closure of unhealthy connection due to active connections", "active_conn_count", count) } offlineThresholdTimer.Reset(s.offlineThreshold) @@ -554,24 +555,24 @@ func (s *remoteSite) updateCertAuthorities(retry retryutils.Retry, remoteWatcher if err != nil { switch { case trace.IsNotFound(err): - s.logger.Debug("Remote cluster does not support cert authorities rotation yet.") + s.logger.DebugContext(s.ctx, "Remote cluster does not support cert authorities rotation yet") case trace.IsCompareFailed(err): - s.logger.Info("Remote cluster has updated certificate authorities, going to force reconnect.") + s.logger.InfoContext(s.ctx, "Remote cluster has updated certificate authorities, going to force reconnect") if err := s.srv.onSiteTunnelClose(&alwaysClose{RemoteSite: s}); err != nil { - s.logger.WithError(err).Warn("Failed to close remote site") + s.logger.WarnContext(s.ctx, "Failed to close remote site", "error", err) } return case trace.IsConnectionProblem(err): - s.logger.Debug("Remote cluster is offline.") + s.logger.DebugContext(s.ctx, "Remote cluster is offline") default: - s.logger.Warnf("Could not perform cert authorities update: %v.", trace.DebugReport(err)) + s.logger.WarnContext(s.ctx, "Could not perform cert authorities update", "error", err) } } startedWaiting := s.clock.Now() select { case t := <-retry.After(): - s.logger.Debugf("Initiating new cert authority watch after waiting %v.", t.Sub(startedWaiting)) + s.logger.DebugContext(s.ctx, "Initiating new cert authority watch after applying backoff", "backoff_duration", t.Sub(startedWaiting)) retry.Inc() case <-s.ctx.Done(): return @@ -592,7 +593,7 @@ func (s *remoteSite) watchCertAuthorities(remoteWatcher *services.CertAuthorityW } defer func() { if err := localWatch.Close(); err != nil { - s.logger.WithError(err).Warn("Failed to close local ca watcher subscription.") + s.logger.WarnContext(s.ctx, "Failed to close local ca watcher subscription", "error", err) } }() @@ -607,7 +608,7 @@ func (s *remoteSite) watchCertAuthorities(remoteWatcher *services.CertAuthorityW } defer func() { if err := remoteWatch.Close(); err != nil { - s.logger.WithError(err).Warn("Failed to close remote ca watcher subscription.") + s.logger.WarnContext(s.ctx, "Failed to close remote ca watcher subscription", "error", err) } }() @@ -624,7 +625,7 @@ func (s *remoteSite) watchCertAuthorities(remoteWatcher *services.CertAuthorityW if err := s.remoteClient.RotateExternalCertAuthority(s.ctx, ca); err != nil { return trace.Wrap(err, "failed to push local cert authority") } - s.logger.Debugf("Pushed local cert authority %v", caID.String()) + s.logger.DebugContext(s.ctx, "Pushed local cert authority", "cert_authority", logutils.StringerAttr(caID)) localCAs[caType] = ca } @@ -650,7 +651,7 @@ func (s *remoteSite) watchCertAuthorities(remoteWatcher *services.CertAuthorityW // if CA is changed or does not exist, update backend if err != nil || !services.CertAuthoritiesEquivalent(oldRemoteCA, remoteCA) { - s.logger.Debugf("Ingesting remote cert authority %v", remoteCA.GetID()) + s.logger.DebugContext(s.ctx, "Ingesting remote cert authority", "cert_authority", logutils.StringerAttr(remoteCA.GetID())) if err := s.localClient.UpsertCertAuthority(s.ctx, remoteCA); err != nil { return trace.Wrap(err) } @@ -668,17 +669,16 @@ func (s *remoteSite) watchCertAuthorities(remoteWatcher *services.CertAuthorityW return trace.Wrap(err) } - s.logger.Debugf("Watching for cert authority changes.") + s.logger.DebugContext(s.ctx, "Watching for cert authority changes") for { select { case <-s.ctx.Done(): - s.logger.WithError(s.ctx.Err()).Debug("Context is closing.") return trace.Wrap(s.ctx.Err()) case <-localWatch.Done(): - s.logger.Warn("Local CertAuthority watcher subscription has closed") + s.logger.WarnContext(s.ctx, "Local CertAuthority watcher subscription has closed") return fmt.Errorf("local ca watcher for cluster %s has closed", s.srv.ClusterName) case <-remoteWatch.Done(): - s.logger.Warn("Remote CertAuthority watcher subscription has closed") + s.logger.WarnContext(s.ctx, "Remote CertAuthority watcher subscription has closed") return fmt.Errorf("remote ca watcher for cluster %s has closed", s.domainName) case evt := <-localWatch.Events(): switch evt.Type { @@ -699,7 +699,7 @@ func (s *remoteSite) watchCertAuthorities(remoteWatcher *services.CertAuthorityW // TODO(espadolini): figure out who should be responsible for validating the CA *once* newCA = newCA.Clone() if err := s.remoteClient.RotateExternalCertAuthority(s.ctx, newCA); err != nil { - log.WithError(err).Warn("Failed to rotate external ca") + s.logger.WarnContext(s.ctx, "Failed to rotate external ca", "error", err) return trace.Wrap(err) } @@ -724,13 +724,13 @@ func (s *remoteSite) watchCertAuthorities(remoteWatcher *services.CertAuthorityW } func (s *remoteSite) updateLocks(retry retryutils.Retry) { - s.logger.Debugf("Watching for remote lock changes.") + s.logger.DebugContext(s.ctx, "Watching for remote lock changes") for { startedWaiting := s.clock.Now() select { case t := <-retry.After(): - s.logger.Debugf("Initiating new lock watch after waiting %v.", t.Sub(startedWaiting)) + s.logger.DebugContext(s.ctx, "Initiating new lock watch after applying backoff", "backoff_duration", t.Sub(startedWaiting)) retry.Inc() case <-s.ctx.Done(): return @@ -739,11 +739,11 @@ func (s *remoteSite) updateLocks(retry retryutils.Retry) { if err := s.watchLocks(); err != nil { switch { case trace.IsNotImplemented(err): - s.logger.Debugf("Remote cluster %v does not support locks yet.", s.domainName) + s.logger.DebugContext(s.ctx, "Remote cluster does not support locks yet", "cluster", s.domainName) case trace.IsConnectionProblem(err): - s.logger.Debugf("Remote cluster %v is offline.", s.domainName) + s.logger.DebugContext(s.ctx, "Remote cluster is offline", "cluster", s.domainName) default: - s.logger.WithError(err).Warn("Could not update remote locks.") + s.logger.WarnContext(s.ctx, "Could not update remote locks", "error", err) } } } @@ -752,22 +752,21 @@ func (s *remoteSite) updateLocks(retry retryutils.Retry) { func (s *remoteSite) watchLocks() error { watcher, err := s.srv.LockWatcher.Subscribe(s.ctx) if err != nil { - s.logger.WithError(err).Error("Failed to subscribe to LockWatcher") + s.logger.ErrorContext(s.ctx, "Failed to subscribe to LockWatcher", "error", err) return err } defer func() { if err := watcher.Close(); err != nil { - s.logger.WithError(err).Warn("Failed to close lock watcher subscription.") + s.logger.WarnContext(s.ctx, "Failed to close lock watcher subscription", "error", err) } }() for { select { case <-watcher.Done(): - s.logger.WithError(watcher.Error()).Warn("Lock watcher subscription has closed") + s.logger.WarnContext(s.ctx, "Lock watcher subscription has closed", "error", watcher.Error()) return trace.Wrap(watcher.Error()) case <-s.ctx.Done(): - s.logger.WithError(s.ctx.Err()).Debug("Context is closing.") return trace.Wrap(s.ctx.Err()) case evt := <-watcher.Events(): switch evt.Type { @@ -822,7 +821,10 @@ func (s *remoteSite) Dial(params reversetunnelclient.DialParams) (net.Conn, erro } func (s *remoteSite) DialTCP(params reversetunnelclient.DialParams) (net.Conn, error) { - s.logger.Debugf("Dialing from %v to %v.", params.From, params.To) + s.logger.DebugContext(s.ctx, "Initiating dial request", + "source_addr", logutils.StringerAttr(params.From), + "target_addr", logutils.StringerAttr(params.To), + ) conn, err := s.connThroughTunnel(&sshutils.DialReq{ Address: params.To.String(), @@ -843,7 +845,10 @@ func (s *remoteSite) dialAndForward(params reversetunnelclient.DialParams) (_ ne if params.GetUserAgent == nil && !params.IsAgentlessNode { return nil, trace.BadParameter("user agent getter is required for teleport nodes") } - s.logger.Debugf("Dialing and forwarding from %v to %v.", params.From, params.To) + s.logger.DebugContext(s.ctx, "Initiating dial and forward request", + "source_addr", logutils.StringerAttr(params.From), + "target_addr", logutils.StringerAttr(params.To), + ) // request user agent connection if a SSH user agent is set var userAgent teleagent.Agent @@ -930,7 +935,7 @@ func (s *remoteSite) dialAndForward(params reversetunnelclient.DialParams) (_ ne // UseTunnel makes a channel request asking for the type of connection. If // the other side does not respond (older cluster) or takes to long to // respond, be on the safe side and assume it's not a tunnel connection. -func UseTunnel(logger *log.Entry, c *sshutils.ChConn) bool { +func UseTunnel(logger *slog.Logger, c *sshutils.ChConn) bool { responseCh := make(chan bool, 1) go func() { @@ -946,13 +951,16 @@ func UseTunnel(logger *log.Entry, c *sshutils.ChConn) bool { case response := <-responseCh: return response case <-time.After(1 * time.Second): - logger.Debugf("Timed out waiting for response: returning false.") + logger.DebugContext(context.Background(), "Timed out waiting for response: returning false") return false } } func (s *remoteSite) connThroughTunnel(req *sshutils.DialReq) (*sshutils.ChConn, error) { - s.logger.Debugf("Requesting connection to %v [%v] in remote cluster.", req.Address, req.ServerID) + s.logger.DebugContext(s.ctx, "Requesting connection in remote cluster.", + "target_address", req.Address, + "target_server_id", req.ServerID, + ) // Loop through existing remote connections and try and establish a // connection over the "reverse tunnel". @@ -963,7 +971,7 @@ func (s *remoteSite) connThroughTunnel(req *sshutils.DialReq) (*sshutils.ChConn, if err == nil { return conn, nil } - s.logger.WithError(err).Warn("Request for connection to remote site failed") + s.logger.WarnContext(s.ctx, "Request for connection to remote site failed", "error", err) } // Didn't connect and no error? This means we didn't have any connected diff --git a/lib/reversetunnel/srv.go b/lib/reversetunnel/srv.go index 10591e2042bdd..eb7483eec6477 100644 --- a/lib/reversetunnel/srv.go +++ b/lib/reversetunnel/srv.go @@ -31,7 +31,6 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" "github.com/prometheus/client_golang/prometheus" - log "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" @@ -55,6 +54,7 @@ import ( "github.com/gravitational/teleport/lib/sshca" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) var ( @@ -111,8 +111,8 @@ type server struct { // ctx is a context used for signaling and broadcast ctx context.Context - // log specifies the logger - log log.FieldLogger + // logger specifies the logger + logger *slog.Logger // proxyWatcher monitors changes to the proxies // and broadcasts updates @@ -186,10 +186,6 @@ type Config struct { // Component is a component used in logs Component string - // Log specifies the logger - // TODO(tross): remove this once Logger is used everywhere - Log log.FieldLogger - // Logger specifies the logger Logger *slog.Logger @@ -265,10 +261,6 @@ func (cfg *Config) CheckAndSetDefaults() error { if cfg.Component == "" { cfg.Component = teleport.Component(teleport.ComponentProxy, teleport.ComponentServer) } - if cfg.Log == nil { - cfg.Log = log.StandardLogger() - } - cfg.Log = cfg.Log.WithField(teleport.ComponentKey, cfg.Component) if cfg.Logger == nil { cfg.Logger = slog.Default() @@ -331,7 +323,7 @@ func NewServer(cfg Config) (reversetunnelclient.Server, error) { cancel: cancel, proxyWatcher: proxyWatcher, clusterPeers: make(map[string]*clusterPeers), - log: cfg.Log, + logger: cfg.Logger, offlineThreshold: offlineThreshold, proxySigner: cfg.PROXYSigner, } @@ -384,9 +376,9 @@ func remoteClustersMap(rc []types.RemoteCluster) map[string]types.RemoteCluster func (s *server) disconnectClusters(connectedRemoteClusters []*remoteSite, remoteMap map[string]types.RemoteCluster) error { for _, cluster := range connectedRemoteClusters { if _, ok := remoteMap[cluster.GetName()]; !ok { - s.log.Infof("Remote cluster %q has been deleted. Disconnecting it from the proxy.", cluster.GetName()) + s.logger.InfoContext(s.ctx, "Remote cluster has been deleted, disconnecting it from the proxy", "remote_cluster", cluster.GetName()) if err := s.onSiteTunnelClose(&alwaysClose{RemoteSite: cluster}); err != nil { - s.log.Debugf("Failure closing cluster %q: %v.", cluster.GetName(), err) + s.logger.DebugContext(s.ctx, "Failure closing cluster", "remote_cluster", cluster.GetName(), "error", err) } remoteClustersStats.DeleteLabelValues(cluster.GetName()) } @@ -399,36 +391,36 @@ func (s *server) periodicFunctions() { defer ticker.Stop() if err := s.fetchClusterPeers(); err != nil { - s.log.Warningf("Failed to fetch cluster peers: %v.", err) + s.logger.WarnContext(s.Context, "Failed to fetch cluster peers", "error", err) } for { select { case <-s.ctx.Done(): - s.log.Debugf("Closing.") + s.logger.DebugContext(s.ctx, "Closing") return // Proxies have been updated, notify connected agents about the update. case proxies := <-s.proxyWatcher.ResourcesC: s.fanOutProxies(proxies) case <-ticker.C: if err := s.fetchClusterPeers(); err != nil { - s.log.WithError(err).Warn("Failed to fetch cluster peers") + s.logger.WarnContext(s.ctx, "Failed to fetch cluster peers", "error", err) } connectedRemoteClusters := s.getRemoteClusters() remoteClusters, err := s.localAccessPoint.GetRemoteClusters(s.ctx) if err != nil { - s.log.WithError(err).Warn("Failed to get remote clusters") + s.logger.WarnContext(s.ctx, "Failed to get remote clusters", "error", err) } remoteMap := remoteClustersMap(remoteClusters) if err := s.disconnectClusters(connectedRemoteClusters, remoteMap); err != nil { - s.log.Warningf("Failed to disconnect clusters: %v.", err) + s.logger.WarnContext(s.ctx, "Failed to disconnect clusters", "error", err) } if err := s.reportClusterStats(connectedRemoteClusters, remoteMap); err != nil { - s.log.Warningf("Failed to report cluster stats: %v.", err) + s.logger.WarnContext(s.ctx, "Failed to report cluster stats", "error", err) } } } @@ -555,11 +547,11 @@ func (s *server) removeClusterPeers(conns []types.TunnelConnection) { for _, conn := range conns { peers, ok := s.clusterPeers[conn.GetClusterName()] if !ok { - s.log.Warningf("failed to remove cluster peer, not found peers for %v.", conn) + s.logger.WarnContext(s.ctx, "failed to remove missing cluster peer", "tunnel_connection", logutils.StringerAttr(conn)) continue } peers.removePeer(conn) - s.log.Debugf("Removed cluster peer %v.", conn) + s.logger.DebugContext(s.ctx, "Removed cluster peer", "tunnel_connection", logutils.StringerAttr(conn)) } } @@ -620,11 +612,11 @@ func (s *server) DrainConnections(ctx context.Context) error { // Ensure listener is closed before sending reconnects. err := s.srv.Close() s.RLock() - s.log.Debugf("Advising reconnect to local site: %s", s.localSite.GetName()) + s.logger.DebugContext(ctx, "Advising reconnect to local site", "local_site", s.localSite.GetName()) go s.localSite.adviseReconnect(ctx) for _, site := range s.remoteSites { - s.log.Debugf("Advising reconnect to remote site: %s", site.GetName()) + s.logger.DebugContext(ctx, "Advising reconnect to remote site", "remote_site", site.GetName()) go site.adviseReconnect(ctx) } s.RUnlock() @@ -650,7 +642,7 @@ func (s *server) HandleNewChan(ctx context.Context, ccx *sshutils.ConnectionCont switch channelType { // Heartbeats can come from nodes or proxies. case chanHeartbeat: - s.handleHeartbeat(conn, sconn, nch) + s.handleHeartbeat(ctx, conn, sconn, nch) // Transport requests come from nodes requesting a connection to the Auth // Server through the reverse tunnel. case constants.ChanTransport: @@ -665,19 +657,20 @@ func (s *server) HandleNewChan(ctx context.Context, ccx *sshutils.ConnectionCont if channelType == "session" { msg = "Cannot open new SSH session on reverse tunnel. Are you connecting to the right port?" } - s.log.Warn(msg) + //nolint:sloglint // message should be a constant but in this case we are creating it at runtime. + s.logger.WarnContext(ctx, msg) s.rejectRequest(nch, ssh.ConnectionFailed, msg) return } } func (s *server) handleTransport(sconn *ssh.ServerConn, nch ssh.NewChannel) { - s.log.Debug("Received transport request.") + s.logger.DebugContext(s.ctx, "Received transport request") channel, requestC, err := nch.Accept() if err != nil { sconn.Close() // avoid WithError to reduce log spam on network errors - s.log.Warnf("Failed to accept request: %v.", err) + s.logger.WarnContext(s.ctx, "Failed to accept request", "error", err) return } @@ -696,7 +689,7 @@ func (s *server) handleTransportChannel(sconn *ssh.ServerConn, ch ssh.Channel, r return case <-time.After(apidefaults.DefaultIOTimeout): go ssh.DiscardRequests(reqC) - s.log.Warn("Timed out waiting for transport dial request.") + s.logger.WarnContext(s.ctx, "Timed out waiting for transport dial request") return case r, ok := <-reqC: if !ok { @@ -708,13 +701,12 @@ func (s *server) handleTransportChannel(sconn *ssh.ServerConn, ch ssh.Channel, r dialReq := parseDialReq(req.Payload) if dialReq.Address != constants.RemoteAuthServer { - s.log.WithField("address", dialReq.Address). - Warn("Received dial request for unexpected address, routing to the auth server anyway.") + s.logger.WarnContext(s.ctx, "Received dial request for unexpected address, routing to the auth server anyway", "address", dialReq.Address) } authAddress := utils.ChooseRandomString(s.LocalAuthAddresses) if authAddress == "" { - s.log.Error("No auth servers configured.") + s.logger.ErrorContext(s.ctx, "No auth servers configured") fmt.Fprint(ch.Stderr(), "internal server error") req.Reply(false, nil) return @@ -726,7 +718,7 @@ func (s *server) handleTransportChannel(sconn *ssh.ServerConn, ch ssh.Channel, r if s.proxySigner != nil && clientSrcAddr != nil && clientDstAddr != nil { h, err := s.proxySigner.SignPROXYHeader(clientSrcAddr, clientDstAddr) if err != nil { - s.log.WithError(err).Error("Failed to create signed PROXY header.") + s.logger.ErrorContext(s.ctx, "Failed to create signed PROXY header", "error", err) fmt.Fprint(ch.Stderr(), "internal server error") req.Reply(false, nil) } @@ -736,7 +728,7 @@ func (s *server) handleTransportChannel(sconn *ssh.ServerConn, ch ssh.Channel, r d := net.Dialer{Timeout: apidefaults.DefaultIOTimeout} conn, err := d.DialContext(s.ctx, "tcp", authAddress) if err != nil { - s.log.Errorf("Failed to dial auth: %v.", err) + s.logger.ErrorContext(s.ctx, "Failed to dial auth", "error", err) fmt.Fprint(ch.Stderr(), "failed to dial auth server") req.Reply(false, nil) return @@ -745,7 +737,7 @@ func (s *server) handleTransportChannel(sconn *ssh.ServerConn, ch ssh.Channel, r _ = conn.SetWriteDeadline(time.Now().Add(apidefaults.DefaultIOTimeout)) if _, err := conn.Write(proxyHeader); err != nil { - s.log.Errorf("Failed to send PROXY header: %v.", err) + s.logger.ErrorContext(s.ctx, "Failed to send PROXY header", "error", err) fmt.Fprint(ch.Stderr(), "failed to dial auth server") req.Reply(false, nil) return @@ -753,7 +745,7 @@ func (s *server) handleTransportChannel(sconn *ssh.ServerConn, ch ssh.Channel, r _ = conn.SetWriteDeadline(time.Time{}) if err := req.Reply(true, nil); err != nil { - s.log.Errorf("Failed to respond to dial request: %v.", err) + s.logger.ErrorContext(s.ctx, "Failed to respond to dial request", "error", err) return } @@ -761,10 +753,10 @@ func (s *server) handleTransportChannel(sconn *ssh.ServerConn, ch ssh.Channel, r } // TODO(awly): unit test this -func (s *server) handleHeartbeat(conn net.Conn, sconn *ssh.ServerConn, nch ssh.NewChannel) { - s.log.Debugf("New tunnel from %v.", sconn.RemoteAddr()) +func (s *server) handleHeartbeat(ctx context.Context, conn net.Conn, sconn *ssh.ServerConn, nch ssh.NewChannel) { + s.logger.DebugContext(ctx, "New tunnel established", "remote_addr", logutils.StringerAttr(sconn.RemoteAddr())) if sconn.Permissions.Extensions[utils.ExtIntCertType] != utils.ExtIntCertTypeHost { - s.log.Error(trace.BadParameter("can't retrieve certificate type in certType")) + s.logger.ErrorContext(ctx, "can't retrieve certificate type in certtype@teleport extension") return } @@ -772,7 +764,7 @@ func (s *server) handleHeartbeat(conn net.Conn, sconn *ssh.ServerConn, nch ssh.N // nodes it's a node dialing back. val, ok := sconn.Permissions.Extensions[extCertRole] if !ok { - s.log.Errorf("Failed to accept connection, missing %q extension", extCertRole) + s.logger.ErrorContext(ctx, "Failed to accept connection, missing role extension") s.rejectRequest(nch, ssh.ConnectionFailed, "unknown role") return } @@ -781,64 +773,64 @@ func (s *server) handleHeartbeat(conn net.Conn, sconn *ssh.ServerConn, nch ssh.N switch role { // Node is dialing back. case types.RoleNode: - s.handleNewService(role, conn, sconn, nch, types.NodeTunnel) + s.handleNewService(ctx, role, conn, sconn, nch, types.NodeTunnel) // App is dialing back. case types.RoleApp: - s.handleNewService(role, conn, sconn, nch, types.AppTunnel) + s.handleNewService(ctx, role, conn, sconn, nch, types.AppTunnel) // Kubernetes service is dialing back. case types.RoleKube: - s.handleNewService(role, conn, sconn, nch, types.KubeTunnel) + s.handleNewService(ctx, role, conn, sconn, nch, types.KubeTunnel) // Database proxy is dialing back. case types.RoleDatabase: - s.handleNewService(role, conn, sconn, nch, types.DatabaseTunnel) + s.handleNewService(ctx, role, conn, sconn, nch, types.DatabaseTunnel) // Proxy is dialing back. case types.RoleProxy: - s.handleNewCluster(conn, sconn, nch) + s.handleNewCluster(ctx, conn, sconn, nch) case types.RoleWindowsDesktop: - s.handleNewService(role, conn, sconn, nch, types.WindowsDesktopTunnel) + s.handleNewService(ctx, role, conn, sconn, nch, types.WindowsDesktopTunnel) case types.RoleOkta: - s.handleNewService(role, conn, sconn, nch, types.OktaTunnel) + s.handleNewService(ctx, role, conn, sconn, nch, types.OktaTunnel) // Unknown role. default: - s.log.Errorf("Unsupported role attempting to connect: %v", val) + s.logger.ErrorContext(ctx, "Unsupported role attempting to connect", "role", val) s.rejectRequest(nch, ssh.ConnectionFailed, fmt.Sprintf("unsupported role %v", val)) } } -func (s *server) handleNewService(role types.SystemRole, conn net.Conn, sconn *ssh.ServerConn, nch ssh.NewChannel, connType types.TunnelType) { +func (s *server) handleNewService(ctx context.Context, role types.SystemRole, conn net.Conn, sconn *ssh.ServerConn, nch ssh.NewChannel, connType types.TunnelType) { cluster, rconn, err := s.upsertServiceConn(conn, sconn, connType) if err != nil { - s.log.Errorf("Failed to upsert %s: %v.", role, err) + s.logger.ErrorContext(ctx, "Failed to upsert service connection", "role", role, "error", err) sconn.Close() return } ch, req, err := nch.Accept() if err != nil { - s.log.Errorf("Failed to accept on channel: %v.", err) + s.logger.ErrorContext(ctx, "Failed to accept on channel", "error", err) sconn.Close() return } - go cluster.handleHeartbeat(rconn, ch, req) + go cluster.handleHeartbeat(ctx, rconn, ch, req) } -func (s *server) handleNewCluster(conn net.Conn, sshConn *ssh.ServerConn, nch ssh.NewChannel) { +func (s *server) handleNewCluster(ctx context.Context, conn net.Conn, sshConn *ssh.ServerConn, nch ssh.NewChannel) { // add the incoming site (cluster) to the list of active connections: site, remoteConn, err := s.upsertRemoteCluster(conn, sshConn) if err != nil { - s.log.Error(trace.Wrap(err)) + s.logger.ErrorContext(ctx, "failed to upsert remote cluster connection", "error", err) s.rejectRequest(nch, ssh.ConnectionFailed, "failed to accept incoming cluster connection") return } // accept the request and start the heartbeat on it: ch, req, err := nch.Accept() if err != nil { - s.log.Error(trace.Wrap(err)) + s.logger.ErrorContext(ctx, "Failed to accept on channel", "error", err) sshConn.Close() return } - go site.handleHeartbeat(remoteConn, ch, req) + go site.handleHeartbeat(ctx, remoteConn, ch, req) } func (s *server) requireLocalAgentForConn(sconn *ssh.ServerConn, connType types.TunnelType) error { @@ -864,15 +856,15 @@ func (s *server) getTrustedCAKeysByID(id types.CertAuthID) ([]ssh.PublicKey, err } func (s *server) keyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (perm *ssh.Permissions, err error) { - logger := s.log.WithFields(log.Fields{ - "remote": conn.RemoteAddr(), - "user": conn.User(), - }) + logger := s.logger.With( + "remote_addr", logutils.StringerAttr(conn.RemoteAddr()), + "user", conn.User(), + ) // The crypto/x/ssh package won't log the returned error for us, do it // manually. defer func() { if err != nil { - logger.Warnf("Failed to authenticate client, err: %v.", err) + logger.WarnContext(s.ctx, "Failed to authenticate client", "error", err) } }() @@ -920,7 +912,7 @@ func (s *server) keyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (perm *ssh.Pe return nil, trace.BadParameter("unsupported cert type: %v.", cert.CertType) } - if err := s.checkClientCert(logger, conn.User(), clusterName, cert, caType); err != nil { + if err := s.checkClientCert(conn.User(), clusterName, cert, caType); err != nil { return nil, trace.Wrap(err) } return &ssh.Permissions{ @@ -935,7 +927,7 @@ func (s *server) keyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (perm *ssh.Pe // checkClientCert verifies that client certificate is signed by the recognized // certificate authority. -func (s *server) checkClientCert(logger *log.Entry, user string, clusterName string, cert *ssh.Certificate, caType types.CertAuthType) error { +func (s *server) checkClientCert(user string, clusterName string, cert *ssh.Certificate, caType types.CertAuthType) error { // fetch keys of the certificate authority to check // if there is a match keys, err := s.getTrustedCAKeysByID(types.CertAuthID{ @@ -1024,7 +1016,10 @@ func (s *server) upsertRemoteCluster(conn net.Conn, sshConn *ssh.ServerConn) (*r } s.remoteSites = append(s.remoteSites, site) } - site.logger.Infof("Connection <- %v, clusters: %d.", conn.RemoteAddr(), len(s.remoteSites)) + site.logger.InfoContext(s.ctx, "Processed inbound connection from remote cluster", + "source_addr", logutils.StringerAttr(conn.RemoteAddr()), + "tunnel_count", len(s.remoteSites), + ) // treat first connection as a registered heartbeat, // otherwise the connection information will appear after initial // heartbeat delay @@ -1146,7 +1141,7 @@ func (s *server) fanOutProxies(proxies []types.Server) { func (s *server) rejectRequest(ch ssh.NewChannel, reason ssh.RejectionReason, msg string) { if err := ch.Reject(reason, msg); err != nil { - s.log.Warnf("Failed rejecting new channel request: %v", err) + s.logger.WarnContext(s.ctx, "Failed rejecting new channel request", "error", err) } } @@ -1182,12 +1177,10 @@ func newRemoteSite(srv *server, domainName string, sconn ssh.Conn) (*remoteSite, srv: srv, domainName: domainName, connInfo: connInfo, - logger: log.WithFields(log.Fields{ - teleport.ComponentKey: teleport.ComponentReverseTunnelServer, - teleport.ComponentFields: log.Fields{ - "cluster": domainName, - }, - }), + logger: slog.With( + teleport.ComponentKey, teleport.ComponentReverseTunnelServer, + "cluster", domainName, + ), ctx: closeContext, cancel: cancel, clock: srv.Clock, diff --git a/lib/reversetunnel/srv_test.go b/lib/reversetunnel/srv_test.go index 327b194ac6ae7..2477739df359a 100644 --- a/lib/reversetunnel/srv_test.go +++ b/lib/reversetunnel/srv_test.go @@ -29,7 +29,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh" @@ -64,7 +63,7 @@ func TestServerKeyAuth(t *testing.T) { require.NoError(t, err) s := &server{ - log: utils.NewLoggerForTests(), + logger: utils.NewSlogLoggerForTests(), Config: Config{Clock: clockwork.NewRealClock()}, localAccessPoint: mockAccessPoint{ ca: ca, @@ -204,8 +203,8 @@ func TestOnlyAuthDial(t *testing.T) { badListenerAddr := acceptAndCloseListener(t, true) srv := &server{ - log: logrus.StandardLogger(), - ctx: ctx, + logger: utils.NewSlogLoggerForTests(), + ctx: ctx, Config: Config{ LocalAuthAddresses: []string{goodListenerAddr}, }, diff --git a/lib/reversetunnel/transport.go b/lib/reversetunnel/transport.go index 2f3d5cfc697f4..b3d338e9bb62b 100644 --- a/lib/reversetunnel/transport.go +++ b/lib/reversetunnel/transport.go @@ -23,12 +23,12 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net" "net/netip" "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" @@ -61,7 +61,7 @@ func parseDialReq(payload []byte) *sshutils.DialReq { // transport is used to build a connection to the target host. type transport struct { component string - log logrus.FieldLogger + logger *slog.Logger closeContext context.Context authClient authclient.ProxyAccessPoint authServers []string @@ -126,7 +126,7 @@ func (p *transport) start() { return } case <-time.After(apidefaults.DefaultIOTimeout): - p.log.Warnf("Transport request failed: timed out waiting for request.") + p.logger.WarnContext(p.closeContext, "Transport request failed: timed out waiting for request") return } @@ -140,8 +140,12 @@ func (p *transport) start() { if !p.forwardClientAddress { // This shouldn't happen in normal operation. Either malicious user or misconfigured client. if dreq.ClientSrcAddr != "" || dreq.ClientDstAddr != "" { - p.log.Warnf("Received unexpected dial request with client source address %q, "+ - "client destination address %q, when they should be empty.", dreq.ClientSrcAddr, dreq.ClientDstAddr) + const msg = "Received unexpected dial request with client source address and " + + "client destination address populated, when they should be empty." + p.logger.WarnContext(p.closeContext, msg, + "client_src_addr", dreq.ClientSrcAddr, + "client_dest_addr", dreq.ClientDstAddr, + ) } // Make sure address fields are overwritten. @@ -154,7 +158,11 @@ func (p *transport) start() { } } - p.log.Debugf("Received out-of-band proxy transport request for %v [%v], from %v.", dreq.Address, dreq.ServerID, dreq.ClientSrcAddr) + p.logger.DebugContext(p.closeContext, "Received out-of-band proxy transport request", + "target_address", dreq.Address, + "taget_server_id", dreq.ServerID, + "client_addr", dreq.ClientSrcAddr, + ) // directAddress will hold the address of the node to dial to, if we don't // have a tunnel for it. @@ -165,7 +173,7 @@ func (p *transport) start() { // Connect to an Auth Server. case reversetunnelclient.RemoteAuthServer: if len(p.authServers) == 0 { - p.log.Errorf("connection rejected: no auth servers configured") + p.logger.ErrorContext(p.closeContext, "connection rejected: no auth servers configured") p.reply(req, false, []byte("no auth servers configured")) return @@ -190,11 +198,14 @@ func (p *transport) start() { return } if err := req.Reply(true, []byte("Connected.")); err != nil { - p.log.Errorf("Failed responding OK to %q request: %v", req.Type, err) + p.logger.ErrorContext(p.closeContext, "Failed responding OK to request", + "request_type", req.Type, + "error", err, + ) return } - p.log.Debug("Handing off connection to a local kubernetes service") + p.logger.DebugContext(p.closeContext, "Handing off connection to a local kubernetes service") // If dreq has ClientSrcAddr we wrap connection var clientConn net.Conn = sshutils.NewChConn(p.sconn, p.channel) @@ -211,7 +222,7 @@ func (p *transport) start() { p.reply(req, false, []byte("connection rejected: configure kubernetes proxy for this cluster.")) return } - p.log.Debugf("Forwarding connection to %q", p.kubeDialAddr.Addr) + p.logger.DebugContext(p.closeContext, "Forwarding connection to kubernetes proxy", "kube_proxy_addr", p.kubeDialAddr.Addr) directAddress = p.kubeDialAddr.Addr } @@ -227,17 +238,20 @@ func (p *transport) start() { if p.server != nil { if p.sconn == nil { - p.log.Debug("Connection rejected: server connection missing") + p.logger.DebugContext(p.closeContext, "Connection rejected: server connection missing") p.reply(req, false, []byte("connection rejected: server connection missing")) return } if err := req.Reply(true, []byte("Connected.")); err != nil { - p.log.Errorf("Failed responding OK to %q request: %v", req.Type, err) + p.logger.ErrorContext(p.closeContext, "Failed responding OK to request", + "request_type", req.Type, + "error", err, + ) return } - p.log.Debugf("Handing off connection to a local %q service.", dreq.ConnType) + p.logger.DebugContext(p.closeContext, "Handing off connection to a local service.", "conn_type", dreq.ConnType) // If dreq has ClientSrcAddr we wrap connection var clientConn net.Conn = sshutils.NewChConn(p.sconn, p.channel) @@ -294,13 +308,19 @@ func (p *transport) start() { // Dial was successful. if err := req.Reply(true, []byte("Connected.")); err != nil { - p.log.Errorf("Failed responding OK to %q request: %v", req.Type, err) + p.logger.ErrorContext(p.closeContext, "Failed responding OK to request", + "request_type", req.Type, + "error", err, + ) if err := conn.Close(); err != nil { - p.log.Errorf("Failed closing connection: %v", err) + p.logger.ErrorContext(p.closeContext, "Failed closing connection", "error", err) } return } - p.log.Debugf("Successfully dialed to %v %q, start proxying.", dreq.Address, dreq.ServerID) + p.logger.DebugContext(p.closeContext, "Successfully dialed to target, starting to proxy", + "target_addr", dreq.Address, + "target_server_id", dreq.ServerID, + ) // Start processing channel requests. Pass in a context that wraps the passed // in context with a context that closes when this function returns to @@ -314,9 +334,9 @@ func (p *transport) start() { if len(signedHeader) > 0 { _, err = conn.Write(signedHeader) if err != nil { - p.log.Errorf("Could not write PROXY header to the connection: %v", err) + p.logger.ErrorContext(p.closeContext, "Could not write PROXY header to the connection", "error", err) if err := conn.Close(); err != nil { - p.log.Errorf("Failed closing connection: %v", err) + p.logger.ErrorContext(p.closeContext, "Failed closing connection", "error", err) } return } @@ -342,7 +362,7 @@ func (p *transport) start() { select { case <-errorCh: case <-p.closeContext.Done(): - p.log.Warnf("Proxy transport failed: closing context.") + p.logger.WarnContext(p.closeContext, "Proxy transport failed: closing context") return } } @@ -375,7 +395,7 @@ func (p *transport) handleChannelRequests(closeContext context.Context, useTunne func (p *transport) getConn(addr string, r *sshutils.DialReq) (net.Conn, bool, error) { // This function doesn't attempt to dial if a host with one of the // search names is not registered. It's a fast check. - p.log.Debugf("Attempting to dial through tunnel with server ID %q.", r.ServerID) + p.logger.DebugContext(p.closeContext, "Attempting to dial server through tunnel", "target_server_id", r.ServerID) conn, err := p.tunnelDial(r) if err != nil { if !trace.IsNotFound(err) { @@ -394,13 +414,13 @@ func (p *transport) getConn(addr string, r *sshutils.DialReq) (net.Conn, bool, e } errTun := err - p.log.Debugf("Attempting to dial directly %q.", addr) + p.logger.DebugContext(p.closeContext, "Attempting to dial server directly", "taget_addr", addr) conn, err = p.directDial(addr) if err != nil { return nil, false, trace.ConnectionProblem(err, "failed dialing through tunnel (%v) or directly (%v)", errTun, err) } - p.log.Debugf("Returning direct dialed connection to %q.", addr) + p.logger.DebugContext(p.closeContext, "Returning direct dialed connection", "target_addr", addr) // Requests to get a connection to the remote auth server do not provide a ConnType, // and since an empty ConnType is converted to [types.NodeTunnel] in CheckAndSetDefaults, @@ -413,7 +433,7 @@ func (p *transport) getConn(addr string, r *sshutils.DialReq) (net.Conn, bool, e return conn, false, nil } - p.log.Debugf("Returning connection dialed through tunnel with server ID %v.", r.ServerID) + p.logger.DebugContext(p.closeContext, "Returning connection to server dialed through tunnel", "target_server_id", r.ServerID) if r.ConnType == types.NodeTunnel { return proxy.NewProxiedMetricConn(conn), true, nil @@ -449,10 +469,10 @@ func (p *transport) tunnelDial(r *sshutils.DialReq) (net.Conn, error) { func (p *transport) reply(req *ssh.Request, ok bool, msg []byte) { if !ok { - p.log.Debugf("Non-ok reply to %q request: %s", req.Type, msg) + p.logger.DebugContext(p.closeContext, "Non-ok reply to request", "request_type", req.Type, "error", string(msg)) } if err := req.Reply(ok, msg); err != nil { - p.log.Warnf("Failed sending reply to %q request on SSH channel: %v", req.Type, err) + p.logger.WarnContext(p.closeContext, "Failed sending reply to request", "request_type", req.Type, "error", err) } } diff --git a/lib/reversetunnelclient/api_with_roles.go b/lib/reversetunnelclient/api_with_roles.go index 4b4eeff886871..ddeb7ff090f50 100644 --- a/lib/reversetunnelclient/api_with_roles.go +++ b/lib/reversetunnelclient/api_with_roles.go @@ -20,9 +20,9 @@ package reversetunnelclient import ( "context" + "log/slog" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/services" @@ -75,7 +75,7 @@ func (t *TunnelWithRoles) GetSites() ([]RemoteSite, error) { if !trace.IsNotFound(err) { return nil, trace.Wrap(err) } - logrus.Warningf("Skipping dangling cluster %q, no remote cluster resource found.", cluster.GetName()) + slog.WarnContext(ctx, "Skipping dangling cluster, no remote cluster resource found", "cluster", cluster.GetName()) continue } if err := t.accessChecker.CheckAccessToRemoteCluster(rc); err != nil { diff --git a/lib/service/kubernetes.go b/lib/service/kubernetes.go index c642da43f4669..f25b46e42da4c 100644 --- a/lib/service/kubernetes.go +++ b/lib/service/kubernetes.go @@ -240,7 +240,7 @@ func (process *TeleportProcess) initKubernetesService(logger *slog.Logger, conn StaticLabels: cfg.Kube.StaticLabels, DynamicLabels: dynLabels, CloudLabels: process.cloudLabels, - Log: process.log.WithField(teleport.ComponentKey, teleport.Component(teleport.ComponentKube, process.id)), + Log: process.logger.With(teleport.ComponentKey, teleport.Component(teleport.ComponentKube, process.id)), PROXYProtocolMode: multiplexer.PROXYProtocolOff, // Kube service doesn't need to process unsigned PROXY headers. InventoryHandle: process.inventoryHandle, }) diff --git a/lib/service/service.go b/lib/service/service.go index fb2b70e19934c..e2155affc208c 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -4443,7 +4443,6 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { PollingPeriod: process.Config.PollingPeriod, FIPS: cfg.FIPS, Emitter: streamEmitter, - Log: process.log, Logger: process.logger, LockWatcher: lockWatcher, PeerClient: peerClient, @@ -4724,7 +4723,7 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { }, }, Handler: webHandler, - Log: process.log.WithField(teleport.ComponentKey, teleport.Component(teleport.ComponentReverseTunnelServer, process.id)), + Log: process.logger.With(teleport.ComponentKey, teleport.Component(teleport.ComponentReverseTunnelServer, process.id)), }) if err != nil { return trace.Wrap(err) @@ -5019,9 +5018,7 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { return nil }) - rcWatchLog := logrus.WithFields(logrus.Fields{ - teleport.ComponentKey: teleport.Component(teleport.ComponentReverseTunnelAgent, process.id), - }) + rcWatchLog := process.logger.With(teleport.ComponentKey, teleport.Component(teleport.ComponentReverseTunnelAgent, process.id)) // Create and register reverse tunnel AgentPool. rcWatcher, err := reversetunnel.NewRemoteClusterTunnelManager(reversetunnel.RemoteClusterTunnelManagerConfig{ @@ -5033,7 +5030,7 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { KubeDialAddr: utils.DialAddrFromListenAddr(kubeDialAddr(cfg.Proxy, clusterNetworkConfig.GetProxyListenerMode())), ReverseTunnelServer: tsrv, FIPS: process.Config.FIPS, - Log: rcWatchLog, + Logger: rcWatchLog, LocalAuthAddresses: utils.NetAddrsToStrings(process.Config.AuthServerAddresses()), PROXYSigner: proxySigner, }) @@ -5042,7 +5039,7 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { } process.RegisterCriticalFunc("proxy.reversetunnel.watcher", func() error { - rcWatchLog.Infof("Starting reverse tunnel agent pool.") + rcWatchLog.InfoContext(process.ExitContext(), "Starting reverse tunnel agent pool") done := make(chan struct{}) go func() { defer close(done) @@ -5122,7 +5119,7 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { AccessPoint: accessPoint, GetRotation: process.GetRotation, OnHeartbeat: process.OnHeartbeat(component), - Log: process.log.WithField(teleport.ComponentKey, teleport.Component(teleport.ComponentReverseTunnelServer, process.id)), + Log: process.logger.With(teleport.ComponentKey, teleport.Component(teleport.ComponentReverseTunnelServer, process.id)), IngressReporter: ingressReporter, KubernetesServersWatcher: kubeServerWatcher, PROXYProtocolMode: cfg.Proxy.PROXYProtocolMode, @@ -5520,7 +5517,7 @@ func (process *TeleportProcess) initMinimalReverseTunnel(listeners *proxyListene return nil }) - log := process.log.WithField(teleport.ComponentKey, teleport.Component(teleport.ComponentReverseTunnelServer, process.id)) + log := process.logger.With(teleport.ComponentKey, teleport.Component(teleport.ComponentReverseTunnelServer, process.id)) minimalWebServer, err := web.NewServer(web.ServerConfig{ Server: &http.Server{ @@ -5529,7 +5526,7 @@ func (process *TeleportProcess) initMinimalReverseTunnel(listeners *proxyListene ReadHeaderTimeout: defaults.ReadHeadersTimeout, WriteTimeout: apidefaults.DefaultIOTimeout, IdleTimeout: apidefaults.DefaultIdleTimeout, - ErrorLog: utils.NewStdlogger(log.Error, teleport.ComponentReverseTunnelServer), + ErrorLog: slog.NewLogLogger(log.Handler(), slog.LevelError), }, Handler: minimalWebHandler, Log: log, @@ -6751,7 +6748,7 @@ func (process *TeleportProcess) initSecureGRPCServer(cfg initSecureGRPCServerCfg kubeServer, err := kubegrpc.New(kubegrpc.Config{ AccessPoint: cfg.accessPoint, Authz: authorizer, - Log: process.log, + Log: process.logger, Emitter: cfg.emitter, KubeProxyAddr: cfg.kubeProxyAddr.String(), ClusterName: clusterName, diff --git a/lib/services/access_checker.go b/lib/services/access_checker.go index 1d3639bc742c8..acea13514adea 100644 --- a/lib/services/access_checker.go +++ b/lib/services/access_checker.go @@ -22,13 +22,13 @@ import ( "cmp" "context" "fmt" + "log/slog" "net" "slices" "strings" "time" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" "github.com/gravitational/teleport/api/constants" @@ -39,6 +39,7 @@ import ( "github.com/gravitational/teleport/lib/services/readonly" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) // AccessChecker interface checks access to resources based on roles, traits, @@ -406,10 +407,11 @@ func (a *accessChecker) checkAllowedResources(r AccessCheckable) error { return nil } - // Note: logging in this function only happens in debug mode. This is because + // Note: logging in this function only happens in trace mode. This is because // adding logging to this function (which is called on every resource returned // by the backend) can slow down this function by 50x for large clusters! - isDebugEnabled, debugf := rbacDebugLogger() + ctx := context.Background() + isLoggingEnabled := rbacLogger.Enabled(ctx, logutils.TraceLevel) for _, resourceID := range a.info.AllowedResourceIDs { if resourceID.ClusterName == a.localCluster && @@ -421,23 +423,33 @@ func (a *accessChecker) checkAllowedResources(r AccessCheckable) error { (resourceID.Kind == r.GetKind() || (slices.Contains(types.KubernetesResourcesKinds, resourceID.Kind) && r.GetKind() == types.KindKubernetesCluster)) && resourceID.Name == r.GetName() { // Allowed to access this resource by resource ID, move on to role checks. - if isDebugEnabled { - debugf("Matched allowed resource ID %q", types.ResourceIDToString(resourceID)) + + if isLoggingEnabled { + rbacLogger.LogAttrs(ctx, logutils.TraceLevel, "Matched allowed resource ID", + slog.String("resource_id", types.ResourceIDToString(resourceID)), + ) } + return nil } } - if isDebugEnabled { + if isLoggingEnabled { allowedResources, err := types.ResourceIDsToString(a.info.AllowedResourceIDs) if err != nil { return trace.Wrap(err) } - err = trace.AccessDenied("access to %v denied, %q not in allowed resource IDs %s", + + slog.LogAttrs(ctx, logutils.TraceLevel, "Access to resource denied, not in allowed resource IDs", + slog.String("resource_kind", r.GetKind()), + slog.String("resource_name", r.GetName()), + slog.Any("allowed_resources", allowedResources), + ) + + return trace.AccessDenied("access to %v denied, %q not in allowed resource IDs %s", r.GetKind(), r.GetName(), allowedResources) - debugf("Access denied: %v", err) - return err } + return trace.AccessDenied("access to %v denied, not in allowed resource IDs", r.GetKind()) } @@ -875,10 +887,11 @@ func (a *accessChecker) CheckAccessToRemoteCluster(rc types.RemoteCluster) error return trace.AccessDenied("access to cluster denied") } - // Note: logging in this function only happens in debug mode, this is because + // Note: logging in this function only happens in trace mode, this is because // adding logging to this function (which is called on every server returned // by GetRemoteClusters) can slow down this function by 50x for large clusters! - isDebugEnabled, debugf := rbacDebugLogger() + ctx := context.Background() + isLoggingEnabled := rbacLogger.Enabled(ctx, logutils.TraceLevel) rcLabels := rc.GetMetadata().Labels @@ -898,8 +911,10 @@ func (a *accessChecker) CheckAccessToRemoteCluster(rc types.RemoteCluster) error } if !usesLabels && len(rcLabels) == 0 { - debugf("Grant access to cluster %v - no role in %v uses cluster labels and the cluster is not labeled.", - rc.GetName(), a.RoleNames()) + rbacLogger.LogAttrs(ctx, logutils.TraceLevel, "Grant access to cluster - no role uses cluster labels and the cluster is not labeled", + slog.String("cluster_name", rc.GetName()), + slog.Any("roles", a.RoleNames()), + ) return nil } @@ -907,21 +922,24 @@ func (a *accessChecker) CheckAccessToRemoteCluster(rc types.RemoteCluster) error // the deny role set prohibits access. var errs []error for _, role := range a.RoleSet { - matchLabels, labelsMessage, err := checkRoleLabelsMatch(types.Deny, role, a.info.Traits, rc, isDebugEnabled) + matchLabels, labelsMessage, err := checkRoleLabelsMatch(types.Deny, role, a.info.Traits, rc, isLoggingEnabled) if err != nil { return trace.Wrap(err) } if matchLabels { // This condition avoids formatting calls on large scale. - debugf("Access to cluster %v denied, deny rule in %v matched; match(%s)", - rc.GetName(), role.GetName(), labelsMessage) + rbacLogger.LogAttrs(ctx, logutils.TraceLevel, "Access to cluster denied, deny rule matched", + slog.String("cluster", rc.GetName()), + slog.String("role", role.GetName()), + slog.String("label_message", labelsMessage), + ) return trace.AccessDenied("access to cluster denied") } } // Check allow rules: label has to match in any role in the role set to be granted access. for _, role := range a.RoleSet { - matchLabels, labelsMessage, err := checkRoleLabelsMatch(types.Allow, role, a.info.Traits, rc, isDebugEnabled) + matchLabels, labelsMessage, err := checkRoleLabelsMatch(types.Allow, role, a.info.Traits, rc, isLoggingEnabled) if err != nil { return trace.Wrap(err) } @@ -929,22 +947,32 @@ func (a *accessChecker) CheckAccessToRemoteCluster(rc types.RemoteCluster) error if err != nil { return trace.Wrap(err) } - debugf("Check access to role(%v) rc(%v, labels=%v) matchLabels=%v, msg=%v, err=%v allow=%v rcLabels=%v", - role.GetName(), rc.GetName(), rcLabels, matchLabels, labelsMessage, err, labelMatchers, rcLabels) + rbacLogger.LogAttrs(ctx, logutils.TraceLevel, "Check access to role", + slog.String("role", role.GetName()), + slog.String("cluster", rc.GetName()), + slog.Any("cluster_labels", rcLabels), + slog.Any("match_labels", matchLabels), + slog.String("labels_message", labelsMessage), + slog.Any("error", err), + slog.Any("allow", labelMatchers), + ) if err != nil { return trace.Wrap(err) } if matchLabels { return nil } - if isDebugEnabled { + if isLoggingEnabled { deniedError := trace.AccessDenied("role=%v, match(%s)", role.GetName(), labelsMessage) errs = append(errs, deniedError) } } - debugf("Access to cluster %v denied, no allow rule matched; %v", rc.GetName(), errs) + rbacLogger.LogAttrs(ctx, logutils.TraceLevel, "Access to cluster denied, no allow rule matched", + slog.String("cluster", rc.GetName()), + slog.Any("error", errs), + ) return trace.AccessDenied("access to cluster denied") } @@ -1095,7 +1123,10 @@ func (a *accessChecker) HostUsers(s types.Server) (*HostUsersInfo, error) { fmt.Fprintf(b, "%s=%v ", shell, roles) } - log.Warnf("Host user shell resolution is ambiguous due to conflicting roles. %q will be used, but consider unifying roles around a single shell. Current shell assignments: %s", shell, b) + slog.WarnContext(context.Background(), "Host user shell resolution is ambiguous due to conflicting roles, consider unifying roles around a single shell", + "selected_shell", shell, + "shell_assignments", b, + ) } for _, role := range a.RoleSet { @@ -1247,8 +1278,11 @@ func AccessInfoFromRemoteCertificate(cert *ssh.Certificate, roleMap types.RoleMa if len(roles) == 0 { return nil, trace.AccessDenied("no roles mapped for user with remote roles %v", unmappedRoles) } - log.Debugf("Mapped remote roles %v to local roles %v and traits %v.", - unmappedRoles, roles, traits) + slog.DebugContext(context.Background(), "Mapped remote roles to local roles and traits", + "remote_roles", unmappedRoles, + "local_roles", roles, + "traits", traits, + ) allowedResourceIDs, err := ExtractAllowedResourcesFromCert(cert) if err != nil { @@ -1281,10 +1315,10 @@ func AccessInfoFromLocalIdentity(identity tlsca.Identity, access UserGetter) (*A return nil, trace.Wrap(err) } - log.Warnf("Failed to find roles in x509 identity for %v. Fetching "+ - "from backend. If the identity provider allows username changes, this can "+ - "potentially allow an attacker to change the role of the existing user.", - identity.Username) + const msg = "Failed to find roles in x509 identity. Fetching " + + "from backend. If the identity provider allows username changes, this can " + + "potentially allow an attacker to change the role of the existing user." + slog.WarnContext(context.Background(), msg, "username", identity.Username) roles = u.GetRoles() traits = u.GetTraits() } @@ -1335,8 +1369,12 @@ func AccessInfoFromRemoteIdentity(identity tlsca.Identity, roleMap types.RoleMap if len(roles) == 0 { return nil, trace.AccessDenied("no roles mapped for remote user %q from cluster %q with remote roles %v", identity.Username, identity.TeleportCluster, unmappedRoles) } - log.Debugf("Mapped roles %v of remote user %q to local roles %v and traits %v.", - unmappedRoles, identity.Username, roles, traits) + slog.DebugContext(context.Background(), "Mapped roles of remote user to local roles and traits", + "remote_roles", unmappedRoles, + "user", identity.Username, + "local_roles", roles, + "traits", traits, + ) allowedResourceIDs := identity.AllowedResourceIDs diff --git a/lib/services/access_request.go b/lib/services/access_request.go index 67da35cde7381..2d5c407f05e48 100644 --- a/lib/services/access_request.go +++ b/lib/services/access_request.go @@ -41,6 +41,7 @@ import ( apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" "github.com/gravitational/teleport/lib/utils/parse" "github.com/gravitational/teleport/lib/utils/typical" ) @@ -2170,10 +2171,9 @@ func (m *RequestValidator) pruneResourceRequestRoles( for _, resourceID := range resourceIDs { if resourceID.ClusterName != localClusterName { - _, debugf := rbacDebugLogger() - debugf("Requested resource %q is in a foreign cluster, unable to prune roles. "+ - `All available "search_as_roles" will be requested.`, - types.ResourceIDToString(resourceID)) + rbacLogger.LogAttrs(ctx, logutils.TraceLevel, `Requested resource is in a foreign cluster, unable to prune roles - All available "search_as_roles" will be requested`, + slog.Any("requested_resources", types.ResourceIDToString(resourceID)), + ) return roles, nil } } diff --git a/lib/services/database.go b/lib/services/database.go index a151f2956fc6f..9a3aba1ad9560 100644 --- a/lib/services/database.go +++ b/lib/services/database.go @@ -21,6 +21,7 @@ package services import ( "context" "errors" + "log/slog" "net" "net/netip" "net/url" @@ -28,7 +29,6 @@ import ( "strings" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "go.mongodb.org/mongo-driver/mongo/readpref" "go.mongodb.org/mongo-driver/x/mongo/driver/connstring" @@ -277,7 +277,11 @@ func validateMongoDB(db types.Database) error { // DNS errors here by replacing the scheme and then ParseAndValidate again // to validate as much as we can. if isDNSError(err) { - log.Warnf("MongoDB database %q (connection string %q) failed validation with DNS error: %v.", db.GetName(), db.GetURI(), err) + slog.WarnContext(context.Background(), "MongoDB database %q (connection string %q) failed validation with DNS error", + "database_name", db.GetName(), + "database_uri", db.GetURI(), + "error", err, + ) connString, err = connstring.ParseAndValidate(strings.Replace( db.GetURI(), diff --git a/lib/services/local/access.go b/lib/services/local/access.go index eb467e76ed815..55f53a1715268 100644 --- a/lib/services/local/access.go +++ b/lib/services/local/access.go @@ -20,11 +20,11 @@ package local import ( "context" + "log/slog" "strings" "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client/proto" @@ -36,14 +36,14 @@ import ( // AccessService manages roles type AccessService struct { backend.Backend - log *logrus.Entry + logger *slog.Logger } // NewAccessService returns new access service instance func NewAccessService(backend backend.Backend) *AccessService { return &AccessService{ Backend: backend, - log: logrus.WithFields(logrus.Fields{teleport.ComponentKey: "AccessService"}), + logger: slog.With(teleport.ComponentKey, "AccessService"), } } @@ -124,7 +124,10 @@ func (s *AccessService) ListRoles(ctx context.Context, req *proto.ListRolesReque services.WithRevision(item.Revision), ) if err != nil { - s.log.Warnf("Failed to unmarshal role at %q: %v", item.Key, err) + s.logger.WarnContext(ctx, "Failed to unmarshal role", + "key", item.Key, + "error", err, + ) continue } diff --git a/lib/services/local/access_list.go b/lib/services/local/access_list.go index 611d9de23f094..955b691d503bc 100644 --- a/lib/services/local/access_list.go +++ b/lib/services/local/access_list.go @@ -27,9 +27,7 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" - "github.com/gravitational/teleport" accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/accesslist" @@ -75,7 +73,6 @@ const ( // consistent view to the rest of the Teleport application. It makes no decisions // about granting or withholding list membership. type AccessListService struct { - log logrus.FieldLogger clock clockwork.Clock service *generic.Service[*accesslist.AccessList] memberService *generic.Service[*accesslist.AccessListMember] @@ -144,7 +141,6 @@ func NewAccessListService(b backend.Backend, clock clockwork.Clock, opts ...Serv } return &AccessListService{ - log: logrus.WithFields(logrus.Fields{teleport.ComponentKey: "access-list:local-service"}), clock: clock, service: service, memberService: memberService, diff --git a/lib/services/local/dynamic_access.go b/lib/services/local/dynamic_access.go index e11aa5a51b16a..cdacfbecd566b 100644 --- a/lib/services/local/dynamic_access.go +++ b/lib/services/local/dynamic_access.go @@ -20,11 +20,11 @@ package local import ( "context" + "log/slog" "slices" "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client/proto" @@ -38,14 +38,14 @@ import ( // DynamicAccessService manages dynamic RBAC type DynamicAccessService struct { backend.Backend - log *logrus.Entry + logger *slog.Logger } // NewDynamicAccessService returns new dynamic access service instance func NewDynamicAccessService(backend backend.Backend) *DynamicAccessService { return &DynamicAccessService{ Backend: backend, - log: logrus.WithFields(logrus.Fields{teleport.ComponentKey: "DynamicAccess"}), + logger: slog.With(teleport.ComponentKey, "DynamicAccess"), } } @@ -348,7 +348,10 @@ func (s *DynamicAccessService) ListAccessRequests(ctx context.Context, req *prot accessRequest, err := itemToAccessRequest(item) if err != nil { - s.log.Warnf("Failed to unmarshal access request at %q: %v", item.Key, err) + s.logger.WarnContext(ctx, "Failed to unmarshal access request", + "key", item.Key, + "error", err, + ) continue } diff --git a/lib/services/local/events.go b/lib/services/local/events.go index 9931b80857500..58e56ada292a4 100644 --- a/lib/services/local/events.go +++ b/lib/services/local/events.go @@ -20,11 +20,11 @@ package local import ( "context" + "log/slog" "strings" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport" apidefaults "github.com/gravitational/teleport/api/defaults" @@ -48,14 +48,14 @@ import ( // EventsService implements service to watch for events type EventsService struct { - *logrus.Entry + logger *slog.Logger backend backend.Backend } // NewEventsService returns new events service instance func NewEventsService(b backend.Backend) *EventsService { return &EventsService{ - Entry: logrus.WithFields(logrus.Fields{teleport.ComponentKey: "Events"}), + logger: slog.With(teleport.ComponentKey, "Events"), backend: b, } } @@ -287,13 +287,13 @@ func (e *EventsService) NewWatcher(ctx context.Context, watch types.Watch) (type if err != nil { return nil, trace.Wrap(err) } - return newWatcher(w, e.Entry, parsers, validKinds), nil + return newWatcher(w, e.logger, parsers, validKinds), nil } -func newWatcher(backendWatcher backend.Watcher, l *logrus.Entry, parsers []resourceParser, kinds []types.WatchKind) *watcher { +func newWatcher(backendWatcher backend.Watcher, l *slog.Logger, parsers []resourceParser, kinds []types.WatchKind) *watcher { w := &watcher{ backendWatcher: backendWatcher, - Entry: l, + logger: l, parsers: parsers, eventsC: make(chan types.Event), kinds: kinds, @@ -303,7 +303,7 @@ func newWatcher(backendWatcher backend.Watcher, l *logrus.Entry, parsers []resou } type watcher struct { - *logrus.Entry + logger *slog.Logger parsers []resourceParser backendWatcher backend.Watcher eventsC chan types.Event @@ -350,7 +350,7 @@ func (w *watcher) forwardEvents() { // node events as well, and there could be no // handler registered for nodes, only for namespaces if !trace.IsNotFound(err) { - w.Warning(trace.DebugReport(err)) + w.logger.WarnContext(context.Background(), "failed parsing event", "error", err) } } for _, c := range converted { @@ -2944,7 +2944,7 @@ func WaitForEvent(ctx context.Context, watcher types.Watcher, m EventMatcher, cl return res, nil } if !trace.IsCompareFailed(err) { - logrus.WithError(err).Debug("Failed to match event.") + slog.DebugContext(ctx, "Failed to match event", "error", err) } case <-watcher.Done(): // Watcher closed, probably due to a network error. diff --git a/lib/services/local/externalauditstorage.go b/lib/services/local/externalauditstorage.go index 96cd050bfd5be..fdb59e827d03a 100644 --- a/lib/services/local/externalauditstorage.go +++ b/lib/services/local/externalauditstorage.go @@ -22,9 +22,7 @@ import ( "context" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" - "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types/externalauditstorage" "github.com/gravitational/teleport/api/types/header" "github.com/gravitational/teleport/lib/backend" @@ -45,14 +43,12 @@ var ( // ExternalAuditStorageService manages External Audit Storage resources in the Backend. type ExternalAuditStorageService struct { backend backend.Backend - logger *logrus.Entry } // NewExternalAuditStorageService returns a new *ExternalAuditStorageService or an error if it fails. func NewExternalAuditStorageService(backend backend.Backend) *ExternalAuditStorageService { return &ExternalAuditStorageService{ backend: backend, - logger: logrus.WithField(teleport.ComponentKey, "ExternalAuditStorage.backend"), } } diff --git a/lib/services/local/externalauditstorage_watcher.go b/lib/services/local/externalauditstorage_watcher.go index 633e8a5c53f3e..f962e2e5cdcce 100644 --- a/lib/services/local/externalauditstorage_watcher.go +++ b/lib/services/local/externalauditstorage_watcher.go @@ -21,11 +21,11 @@ package local import ( "context" "errors" + "log/slog" "sync" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" @@ -38,8 +38,8 @@ import ( type ClusterExternalAuditStorageWatcherConfig struct { // Backend is the storage backend used to create watchers. Backend backend.Backend - // Log is a logger. - Log logrus.FieldLogger + // Logger is a logger. + Logger *slog.Logger // Clock is used to control time. Clock clockwork.Clock // OnChange is the action to take when the cluster ExternalAuditStorage @@ -52,8 +52,8 @@ func (cfg *ClusterExternalAuditStorageWatcherConfig) CheckAndSetDefaults() error if cfg.Backend == nil { return trace.BadParameter("missing parameter Backend") } - if cfg.Log == nil { - cfg.Log = logrus.StandardLogger().WithField(teleport.ComponentKey, "ExternalAuditStorage.watcher") + if cfg.Logger == nil { + cfg.Logger = slog.With(teleport.ComponentKey, "ExternalAuditStorage.watcher") } if cfg.Clock == nil { cfg.Clock = cfg.Backend.Clock() @@ -67,7 +67,7 @@ func (cfg *ClusterExternalAuditStorageWatcherConfig) CheckAndSetDefaults() error // ClusterExternalAuditWatcher is a light weight backend watcher for the cluster external audit resource. type ClusterExternalAuditWatcher struct { backend backend.Backend - log logrus.FieldLogger + logger *slog.Logger clock clockwork.Clock onChange func() retry retryutils.Retry @@ -97,7 +97,7 @@ func NewClusterExternalAuditWatcher(ctx context.Context, cfg ClusterExternalAudi w := &ClusterExternalAuditWatcher{ backend: cfg.Backend, - log: cfg.Log, + logger: cfg.Logger, clock: cfg.Clock, onChange: cfg.OnChange, retry: retry, @@ -137,7 +137,10 @@ func (w *ClusterExternalAuditWatcher) runWatchLoop(ctx context.Context) { startedWaiting := w.clock.Now() select { case t := <-w.retry.After(): - w.log.Warningf("Restarting watch on error after waiting %v. Error: %v.", t.Sub(startedWaiting), err) + w.logger.WarnContext(ctx, "Restarting watch on error", + "backoff", t.Sub(startedWaiting), + "error", err, + ) w.retry.Inc() case <-ctx.Done(): return @@ -156,7 +159,7 @@ func (w *ClusterExternalAuditWatcher) watch(ctx context.Context) error { for { select { case <-watcher.Events(): - w.log.Infof("Detected change to cluster ExternalAuditStorage config") + w.logger.InfoContext(ctx, "Detected change to cluster ExternalAuditStorage config") w.onChange() case w.running <- struct{}{}: case <-watcher.Done(): diff --git a/lib/services/local/headlessauthn_watcher.go b/lib/services/local/headlessauthn_watcher.go index d3ded3d60439c..dee036fa363df 100644 --- a/lib/services/local/headlessauthn_watcher.go +++ b/lib/services/local/headlessauthn_watcher.go @@ -21,12 +21,12 @@ package local import ( "context" "errors" + "log/slog" "sync" "time" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" apiutils "github.com/gravitational/teleport/api/utils" @@ -51,8 +51,8 @@ var ErrHeadlessAuthenticationWatcherClosed = errors.New("headless authentication type HeadlessAuthenticationWatcherConfig struct { // Backend is the storage backend used to create watchers. Backend backend.Backend - // Log is a logger. - Log logrus.FieldLogger + // Logger is a logger. + Logger *slog.Logger // Clock is used to control time. Clock clockwork.Clock // MaxRetryPeriod is the maximum retry period on failed watchers. @@ -64,9 +64,8 @@ func (cfg *HeadlessAuthenticationWatcherConfig) CheckAndSetDefaults() error { if cfg.Backend == nil { return trace.BadParameter("missing parameter Backend") } - if cfg.Log == nil { - cfg.Log = logrus.StandardLogger() - cfg.Log.WithField("resource-kind", types.KindHeadlessAuthentication) + if cfg.Logger == nil { + cfg.Logger = slog.With("resource_kind", types.KindHeadlessAuthentication) } if cfg.MaxRetryPeriod == 0 { // On watcher failure, we eagerly retry in order to avoid login delays. @@ -159,12 +158,15 @@ func (h *HeadlessAuthenticationWatcher) runWatchLoop(ctx context.Context) { startedWaiting := h.Clock.Now() select { case t := <-h.retry.After(): - h.Log.Warningf("Restarting watch on error after waiting %v. Error: %v.", t.Sub(startedWaiting), err) + h.Logger.WarnContext(ctx, "Restarting watch on error", + "backoff", t.Sub(startedWaiting), + "error", err, + ) h.retry.Inc() case <-ctx.Done(): return case <-h.closed: - h.Log.Debug("Watcher closed. Returning from watch loop.") + h.Logger.DebugContext(ctx, "Watcher closed, terminating watch loop") return } } @@ -191,7 +193,7 @@ func (h *HeadlessAuthenticationWatcher) watch(ctx context.Context) error { case types.OpPut: headlessAuthn, err := unmarshalHeadlessAuthenticationFromItem(&event.Item) if err != nil { - h.Log.WithError(err).Debug("failed to unmarshal headless authentication from put event") + h.Logger.DebugContext(ctx, "failed to unmarshal headless authentication from put event", "error", err) } else { h.notify(headlessAuthn) } diff --git a/lib/services/local/inventory.go b/lib/services/local/inventory.go index 2b488e49e58aa..0eba8cadd618b 100644 --- a/lib/services/local/inventory.go +++ b/lib/services/local/inventory.go @@ -54,11 +54,17 @@ func (s *PresenceService) GetInstances(ctx context.Context, req types.InstanceFi return stream.FilterMap(items, func(item backend.Item) (types.Instance, bool) { instance, err := generic.FastUnmarshal[*types.InstanceV1](item) if err != nil { - s.log.Warnf("Skipping instance at %s, failed to unmarshal: %v", item.Key, err) + s.logger.WarnContext(ctx, "Skipping instance failed to unmarshal", + "key", item.Key, + "error", err, + ) return nil, false } if err := instance.CheckAndSetDefaults(); err != nil { - s.log.Warnf("Skipping instance at %s: %v", item.Key, err) + s.logger.WarnContext(ctx, "Skipping invalid instance", + "key", item.Key, + "error", err, + ) return nil, false } if !req.Match(instance) { diff --git a/lib/services/local/okta.go b/lib/services/local/okta.go index b1ab4e8f3b65d..dfbca6b4d29ee 100644 --- a/lib/services/local/okta.go +++ b/lib/services/local/okta.go @@ -24,9 +24,7 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" - "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/services" @@ -43,7 +41,6 @@ const ( // OktaService manages Okta resources in the Backend. type OktaService struct { - log logrus.FieldLogger clock clockwork.Clock importRuleSvc *generic.Service[types.OktaImportRule] assignmentSvc *generic.Service[types.OktaAssignment] @@ -76,7 +73,6 @@ func NewOktaService(b backend.Backend, clock clockwork.Clock) (*OktaService, err } return &OktaService{ - log: logrus.WithFields(logrus.Fields{teleport.ComponentKey: "okta:local-service"}), clock: clock, importRuleSvc: importRuleSvc, assignmentSvc: assignmentSvc, diff --git a/lib/services/local/presence.go b/lib/services/local/presence.go index ff95884151b34..520eae0f33c87 100644 --- a/lib/services/local/presence.go +++ b/lib/services/local/presence.go @@ -26,7 +26,6 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client/proto" @@ -45,7 +44,7 @@ import ( // PresenceService records and reports the presence of all components // of the cluster - Nodes, Proxies and SSH nodes type PresenceService struct { - log *logrus.Entry + logger *slog.Logger jitter retryutils.Jitter backend.Backend } @@ -57,7 +56,7 @@ type backendItemToResourceFunc func(item backend.Item) (types.ResourceWithLabels // NewPresenceService returns new presence service instance func NewPresenceService(b backend.Backend) *PresenceService { return &PresenceService{ - log: logrus.WithFields(logrus.Fields{teleport.ComponentKey: "Presence"}), + logger: slog.With(teleport.ComponentKey, "Presence"), jitter: retryutils.FullJitter, Backend: b, } @@ -160,7 +159,10 @@ func (s *PresenceService) GetServerInfos(ctx context.Context) stream.Stream[type services.WithRevision(item.Revision), ) if err != nil { - s.log.Warnf("Skipping server info at %s, failed to unmarshal: %v", item.Key, err) + s.logger.WarnContext(ctx, "Failed to unmarshal server info", + "key", item.Key, + "error", err, + ) return nil, false } return si, true diff --git a/lib/services/local/saml_idp_service_provider.go b/lib/services/local/saml_idp_service_provider.go index 6b08cf084afd9..eadeb347367ab 100644 --- a/lib/services/local/saml_idp_service_provider.go +++ b/lib/services/local/saml_idp_service_provider.go @@ -22,6 +22,7 @@ import ( "context" "encoding/xml" "fmt" + "log/slog" "net/http" "net/url" "time" @@ -29,7 +30,6 @@ import ( "github.com/crewjam/saml" "github.com/crewjam/saml/samlsp" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" @@ -55,7 +55,7 @@ type SAMLIdPServiceProviderService struct { // backend is used to spawn Plugins storage service so that // it can be queried from the SAML service. backend backend.Backend - log logrus.FieldLogger + logger *slog.Logger httpClient *http.Client } @@ -86,7 +86,7 @@ func NewSAMLIdPServiceProviderService(b backend.Backend, opts ...SAMLIdPOption) samlSPService := &SAMLIdPServiceProviderService{ svc: *svc, backend: b, - log: logrus.WithFields(logrus.Fields{teleport.ComponentKey: "saml-idp"}), + logger: slog.With(teleport.ComponentKey, "saml-idp"), } for _, opt := range opts { @@ -120,13 +120,17 @@ func (s *SAMLIdPServiceProviderService) CreateSAMLIdPServiceProvider(ctx context if err := services.ValidateSAMLIdPACSURLAndRelayStateInputs(sp); err != nil { // logging instead of returning an error cause we do not want to break cache writes on a cluster // that already has a service provider with unsupported characters/scheme in the acs_url or relay_state. - s.log.Warn(err) + s.logger.WarnContext(ctx, "Provided SAML IdP service provided is invalid", "error", err) } if sp.GetEntityDescriptor() == "" { if err := s.configureEntityDescriptorPerPreset(sp); err != nil { errMsg := fmt.Errorf("failed to configure entity descriptor with the given entity_id %q and acs_url %q: %w", sp.GetEntityID(), sp.GetACSURL(), err) - s.log.Errorf(errMsg.Error()) + s.logger.ErrorContext(ctx, "failed to configure entity descriptor", + "entity_id", sp.GetEntityID(), + "acs_url", sp.GetACSURL(), + "error", err, + ) return trace.BadParameter(errMsg.Error()) } } @@ -166,7 +170,7 @@ func (s *SAMLIdPServiceProviderService) UpdateSAMLIdPServiceProvider(ctx context if err := services.ValidateSAMLIdPACSURLAndRelayStateInputs(sp); err != nil { // logging instead of returning an error cause we do not want to break cache writes on a cluster // that already has a service provider with unsupported characters/scheme in the acs_url or relay_state. - s.log.Warn(err) + s.logger.WarnContext(ctx, "Provided SAML IdP service provided is invalid", "error", err) } // we only verify if the entity ID field in the spec matches with the entity descriptor. @@ -250,7 +254,10 @@ func (s *SAMLIdPServiceProviderService) configureEntityDescriptorPerPreset(sp ty // fetchAndSetEntityDescriptor is expected to return error if it fails // to fetch a valid entity descriptor. if err := s.fetchAndSetEntityDescriptor(sp); err != nil { - s.log.Debugf("Failed to fetch entity descriptor from %q: %v.", sp.GetEntityID(), err) + s.logger.DebugContext(context.Background(), "Failed to fetch entity descriptor", + "entity_id", sp.GetEntityID(), + "error", err, + ) // We aren't interested in checking error type as any occurrence of error // mean entity descriptor was not set. return trace.Wrap(s.generateAndSetEntityDescriptor(sp)) @@ -295,7 +302,10 @@ func (s *SAMLIdPServiceProviderService) fetchAndSetEntityDescriptor(sp types.SAM // generateAndSetEntityDescriptor generates and sets Service Provider entity descriptor // with ACS URL, Entity ID and unspecified NameID format. func (s *SAMLIdPServiceProviderService) generateAndSetEntityDescriptor(sp types.SAMLIdPServiceProvider) error { - s.log.Infof("Generating a default entity_descriptor with entity_id %q and acs_url %q.", sp.GetEntityID(), sp.GetACSURL()) + s.logger.InfoContext(context.Background(), "Generating a default entity_descriptor", + "entity_id", sp.GetEntityID(), + "acs_url", sp.GetACSURL(), + ) acsURL, err := url.Parse(sp.GetACSURL()) if err != nil { @@ -335,7 +345,9 @@ func (s *SAMLIdPServiceProviderService) embedAttributeMapping(sp types.SAMLIdPSe switch attrMapLen := len(sp.GetAttributeMapping()); { case attrMapLen == 0: if teleportSPSSODescriptorIndex == 0 { - s.log.Debugf("No custom attribute mapping values provided for %s. SAML assertion will default to uid and eduPersonAffiliate", sp.GetEntityID()) + s.logger.DebugContext(context.Background(), "No custom attribute mapping values provided,SAML assertion will default to uid and eduPersonAffiliate", + "entity_id", sp.GetEntityID(), + ) return nil } else { // delete Teleport SPSSODescriptor diff --git a/lib/services/local/secreports.go b/lib/services/local/secreports.go index c08ac60fe3a36..36307ce17d528 100644 --- a/lib/services/local/secreports.go +++ b/lib/services/local/secreports.go @@ -23,9 +23,7 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" - "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/secreports" "github.com/gravitational/teleport/lib/backend" @@ -46,7 +44,6 @@ var ( // SecReportsService is the local implementation of the SecReports service. type SecReportsService struct { - log logrus.FieldLogger clock clockwork.Clock auditQuerySvc *generic.Service[*secreports.AuditQuery] securityReportSvc *generic.Service[*secreports.Report] @@ -99,7 +96,6 @@ func NewSecReportsService(backend backend.Backend, clock clockwork.Clock) (*SecR } return &SecReportsService{ - log: logrus.WithFields(logrus.Fields{teleport.ComponentKey: "secreports:local-service"}), clock: clock, auditQuerySvc: auditQuerySvc, securityReportSvc: securityReportSvc, diff --git a/lib/services/local/session.go b/lib/services/local/session.go index c2fde121ddce7..ce032ca6fc3b2 100644 --- a/lib/services/local/session.go +++ b/lib/services/local/session.go @@ -23,7 +23,6 @@ import ( "slices" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/types" @@ -315,7 +314,7 @@ func (s *IdentityService) DeleteAllSAMLIdPSessions(ctx context.Context) error { // WebSessions returns the web sessions manager. func (s *IdentityService) WebSessions() types.WebSessionInterface { - return &webSessions{backend: s.Backend, log: s.log} + return &webSessions{backend: s.Backend} } // Get returns the web session state described with req. @@ -423,12 +422,11 @@ func (r *webSessions) listLegacySessions(ctx context.Context) ([]types.WebSessio type webSessions struct { backend backend.Backend - log logrus.FieldLogger } // WebTokens returns the web token manager. func (s *IdentityService) WebTokens() types.WebTokenInterface { - return &webTokens{backend: s.Backend, log: s.log} + return &webTokens{backend: s.Backend} } // Get returns the web token described with req. @@ -504,7 +502,6 @@ func (r *webTokens) DeleteAll(ctx context.Context) error { type webTokens struct { backend backend.Backend - log logrus.FieldLogger } func webSessionKey(sessionID string) backend.Key { diff --git a/lib/services/local/sessiontracker.go b/lib/services/local/sessiontracker.go index ad6fc5e9d06f1..95ec34895d78e 100644 --- a/lib/services/local/sessiontracker.go +++ b/lib/services/local/sessiontracker.go @@ -20,10 +20,10 @@ package local import ( "context" + "log/slog" "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/types" @@ -160,7 +160,7 @@ func (s *sessionTracker) getActiveSessionTrackers(ctx context.Context, filter *t for _, item := range noExpiry { if err := s.bk.Delete(ctx, item.Key); err != nil { if !trace.IsNotFound(err) { - logrus.WithError(err).Error("Failed to remove stale session tracker") + slog.ErrorContext(ctx, "Failed to remove stale session tracker", "error", err) } } } diff --git a/lib/services/local/status.go b/lib/services/local/status.go index 9af78ee0d4f2b..b6046e9bde34a 100644 --- a/lib/services/local/status.go +++ b/lib/services/local/status.go @@ -20,10 +20,10 @@ package local import ( "context" + "log/slog" "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client/proto" @@ -35,13 +35,13 @@ import ( // StatusService manages cluster status info. type StatusService struct { backend.Backend - log logrus.FieldLogger + logger *slog.Logger } func NewStatusService(bk backend.Backend) *StatusService { return &StatusService{ Backend: bk, - log: logrus.WithField(teleport.ComponentKey, "status"), + logger: slog.With(teleport.ComponentKey, "status"), } } @@ -68,7 +68,7 @@ func (s *StatusService) GetClusterAlerts(ctx context.Context, query types.GetClu filtered := alerts[:0] for _, alert := range alerts { if err := alert.CheckAndSetDefaults(); err != nil { - s.log.Warnf("Skipping invalid cluster alert: %v", err) + s.logger.WarnContext(ctx, "Skipping invalid cluster alert", "error", err) } if !query.Match(alert) { diff --git a/lib/services/local/user_login_state.go b/lib/services/local/user_login_state.go index 76e12b6b4c95d..a73c77b238dc4 100644 --- a/lib/services/local/user_login_state.go +++ b/lib/services/local/user_login_state.go @@ -22,9 +22,7 @@ import ( "context" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" - "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/userloginstate" "github.com/gravitational/teleport/lib/backend" @@ -38,7 +36,6 @@ const ( // UserLoginStateService manages user login state resources in the Backend. type UserLoginStateService struct { - log logrus.FieldLogger svc *generic.Service[*userloginstate.UserLoginState] } @@ -56,7 +53,6 @@ func NewUserLoginStateService(b backend.Backend) (*UserLoginStateService, error) } return &UserLoginStateService{ - log: logrus.WithFields(logrus.Fields{teleport.ComponentKey: "user-login-state:local-service"}), svc: svc, }, nil } diff --git a/lib/services/local/users.go b/lib/services/local/users.go index 760634dfa65b3..1c4790bcda886 100644 --- a/lib/services/local/users.go +++ b/lib/services/local/users.go @@ -26,6 +26,7 @@ import ( "encoding/base64" "encoding/json" "io" + "log/slog" "sort" "strings" "sync" @@ -37,7 +38,6 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "golang.org/x/crypto/bcrypt" "golang.org/x/sync/errgroup" @@ -65,43 +65,30 @@ var GlobalSessionDataMaxEntries = 5000 // arbitrary // user accounts as well type IdentityService struct { backend.Backend - log logrus.FieldLogger + logger *slog.Logger bcryptCost int notificationsSvc *NotificationsService } -// TODO(rudream): Rename to NewIdentityService. +// TODO(tross): DELETE ONCE e is updated to use NewIdentityService // NewIdentityServiceV2 returns a new instance of IdentityService object func NewIdentityServiceV2(backend backend.Backend) (*IdentityService, error) { - notificationsSvc, err := NewNotificationsService(backend, backend.Clock()) - if err != nil { - return nil, trace.Wrap(err) - } - - return &IdentityService{ - Backend: backend, - log: logrus.WithField(teleport.ComponentKey, "identity"), - bcryptCost: bcrypt.DefaultCost, - notificationsSvc: notificationsSvc, - }, nil + return NewIdentityService(backend) } -// TODO(rudream): Remove once NewIdentityServiceV2 is merged. // NewIdentityService returns a new instance of IdentityService object -func NewIdentityService(backend backend.Backend) *IdentityService { +func NewIdentityService(backend backend.Backend) (*IdentityService, error) { notificationsSvc, err := NewNotificationsService(backend, backend.Clock()) - - log := logrus.WithField(teleport.ComponentKey, "identity") if err != nil { - log.Warnf("error initializing notifications service with identity service: %v", err) + return nil, trace.Wrap(err) } return &IdentityService{ Backend: backend, - log: log, + logger: slog.With(teleport.ComponentKey, "identity"), bcryptCost: bcrypt.DefaultCost, notificationsSvc: notificationsSvc, - } + }, nil } // NewTestIdentityService returns a new instance of IdentityService object to be @@ -201,7 +188,10 @@ func (s *IdentityService) streamUsersWithSecrets(itemStream stream.Stream[backen collectorStream := stream.FilterMap(itemStream, func(item backend.Item) (collector, bool) { name, suffix, err := splitUsernameAndSuffix(item.Key) if err != nil { - s.log.Warnf("Failed to extract name/suffix for user item at %q: %v", item.Key, err) + s.logger.WarnContext(context.Background(), "Failed to extract name/suffix for user item", + "key", item.Key, + "error", err, + ) return collector{}, false } @@ -242,7 +232,10 @@ func (s *IdentityService) streamUsersWithSecrets(itemStream stream.Stream[backen userStream := stream.FilterMap(collectorStream, func(c collector) (*types.UserV2, bool) { user, err := userFromUserItems(c.name, c.items) if err != nil { - s.log.Warnf("Failed to build user %q from user item aggregator: %v", c.name, err) + s.logger.WarnContext(context.Background(), "Failed to build user from user item aggregator", + "user", c.name, + "error", err, + ) return nil, false } @@ -263,7 +256,10 @@ func (s *IdentityService) streamUsersWithoutSecrets(itemStream stream.Stream[bac user, err := services.UnmarshalUser(item.Value, services.WithRevision(item.Revision)) if err != nil { - s.log.Warnf("Failed to unmarshal user at %q: %v", item.Key, err) + s.logger.WarnContext(context.Background(), "Failed to unmarshal user", + "key", item.Key, + "error", err, + ) return nil, false } @@ -874,6 +870,7 @@ func (s *IdentityService) DeleteUserLoginAttempts(user string) error { // `PasswordState` status flag accordingly. Returns an error if the user doesn't // exist. func (s *IdentityService) UpsertPassword(user string, password []byte) error { + ctx := context.TODO() if user == "" { return trace.BadParameter("missing username") } @@ -891,7 +888,7 @@ func (s *IdentityService) UpsertPassword(user string, password []byte) error { } _, err = s.UpdateAndSwapUser( - context.TODO(), + ctx, user, false, /*withSecrets*/ func(u types.User) (bool, error) { @@ -900,10 +897,10 @@ func (s *IdentityService) UpsertPassword(user string, password []byte) error { }) if err != nil { // Don't let the password state flag change fail the entire operation. - s.log. - WithError(err). - WithField("user", user). - Warn("Failed to set password state") + s.logger.WarnContext(ctx, "Failed to set password state", + "user", user, + "error", err, + ) } return nil @@ -924,7 +921,7 @@ func (s *IdentityService) DeletePassword(ctx context.Context, user string) error } if _, err := s.UpdateAndSwapUser( - context.TODO(), + ctx, user, false, /*withSecrets*/ func(u types.User) (bool, error) { @@ -933,10 +930,10 @@ func (s *IdentityService) DeletePassword(ctx context.Context, user string) error }, ); err != nil { // Don't let the password state flag change fail the entire operation. - s.log. - WithError(err). - WithField("user", user). - Warn("Failed to set password state") + s.logger.WarnContext(ctx, "Failed to set password state", + "user", user, + "error", err, + ) } // Now is the time to return the delete operation, if any. @@ -987,7 +984,7 @@ func (s *IdentityService) UpsertWebauthnLocalAuth(ctx context.Context, user stri // lib/auth/webauthn is prepared to deal with eventual inconsistencies // between "web/users/.../webauthnlocalauth" and "webauthn/users/" keys. if err := s.Delete(ctx, wlaKey); err != nil { - s.log.WithError(err).Warn("Failed to undo WebauthnLocalAuth update") + s.logger.WarnContext(ctx, "Failed to undo WebauthnLocalAuth update", "error", err) } return trace.Wrap(err, "writing webauthn user") } @@ -1208,7 +1205,7 @@ func (s *IdentityService) UpsertMFADevice(ctx context.Context, user string, d *t return trace.Wrap(err) } if err := s.upsertUserStatusMFADevice(ctx, user); err != nil { - s.log.WithError(err).Warn("Unable to update user status after adding MFA device") + s.logger.WarnContext(ctx, "Unable to update user status after adding MFA device", "error", err) } return nil } @@ -1308,7 +1305,7 @@ func (s *IdentityService) buildAndSetWeakestMFADeviceKind(ctx context.Context, u } state, err := s.buildWeakestMFADeviceKind(ctx, user.GetName(), upsertingMFA...) if err != nil { - s.log.WithError(err).Warn("Failed to determine weakest mfa device kind for user") + s.logger.WarnContext(ctx, "Failed to determine weakest mfa device kind for user", "error", err) return } user.SetWeakestDevice(state) @@ -1370,7 +1367,7 @@ func (s *IdentityService) DeleteMFADevice(ctx context.Context, user, id string) return trace.Wrap(err) } if err := s.upsertUserStatusMFADevice(ctx, user); err != nil { - s.log.WithError(err).Warn("Unable to update user status after deleting MFA device") + s.logger.WarnContext(ctx, "Unable to update user status after deleting MFA device", "error", err) } return nil } @@ -1607,10 +1604,10 @@ func (s *IdentityService) GetOIDCConnectors(ctx context.Context, withSecrets boo for _, item := range result.Items { conn, err := services.UnmarshalOIDCConnector(item.Value, services.WithExpires(item.Expires), services.WithRevision(item.Revision)) if err != nil { - logrus. - WithError(err). - WithField("key", item.Key). - Errorf("Error unmarshaling OIDC Connector") + s.logger.ErrorContext(ctx, "Error unmarshaling OIDC Connector", + "key", item.Key, + "error", err, + ) continue } if !withSecrets { @@ -1775,10 +1772,10 @@ func (s *IdentityService) GetSAMLConnectors(ctx context.Context, withSecrets boo for _, item := range result.Items { conn, err := services.UnmarshalSAMLConnector(item.Value, services.WithExpires(item.Expires), services.WithRevision(item.Revision)) if err != nil { - logrus. - WithError(err). - WithField("key", item.Key). - Errorf("Error unmarshaling SAML Connector") + s.logger.ErrorContext(ctx, "Error unmarshaling SAML Connector", + "key", item.Key, + "error", err, + ) continue } if !withSecrets { @@ -2016,10 +2013,10 @@ func (s *IdentityService) GetGithubConnectors(ctx context.Context, withSecrets b for _, item := range result.Items { connector, err := services.UnmarshalGithubConnector(item.Value, services.WithRevision(item.Revision)) if err != nil { - logrus. - WithError(err). - WithField("key", item.Key). - Errorf("Error unmarshaling GitHub Connector") + s.logger.ErrorContext(ctx, "Error unmarshaling GitHub Connector", + "key", item.Key, + "error", err, + ) continue } if !withSecrets { diff --git a/lib/services/matchers.go b/lib/services/matchers.go index e625d4ca59fab..84e8ba87bd793 100644 --- a/lib/services/matchers.go +++ b/lib/services/matchers.go @@ -19,10 +19,11 @@ package services import ( + "context" + "log/slog" "slices" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" apiutils "github.com/gravitational/teleport/api/utils" @@ -115,8 +116,11 @@ func MatchResourceLabels(matchers []ResourceMatcher, labels map[string]string) b } match, _, err := MatchLabels(matcher.Labels, labels) if err != nil { - logrus.WithError(err).Errorf("Failed to match labels %v: %v.", - matcher.Labels, labels) + slog.ErrorContext(context.Background(), "Failed to match labels", + "error", err, + "matcher_labels", matcher.Labels, + "resource_labels", labels, + ) return false } if match { diff --git a/lib/services/parser.go b/lib/services/parser.go index 8d70f0f53b8fe..1ea6b6d7cdd60 100644 --- a/lib/services/parser.go +++ b/lib/services/parser.go @@ -19,14 +19,14 @@ package services import ( + "context" "fmt" - "io" + "log/slog" "slices" "strings" "time" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "github.com/vulcand/predicate" "github.com/vulcand/predicate/builder" @@ -35,6 +35,7 @@ import ( "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/api/types/wrappers" "github.com/gravitational/teleport/lib/session" + logutils "github.com/gravitational/teleport/lib/utils/log" "github.com/gravitational/teleport/lib/utils/typical" ) @@ -231,34 +232,41 @@ func NewActionsParser(ctx RuleContext) (predicate.Parser, error) { // NewLogActionFn creates logger functions func NewLogActionFn(ctx RuleContext) interface{} { l := &LogAction{ctx: ctx} - writer, ok := ctx.(io.Writer) - if ok && writer != nil { - l.writer = writer - } + return l.Log } // LogAction represents action that will emit log entry // when specified in the actions of a matched rule type LogAction struct { - ctx RuleContext - writer io.Writer + ctx RuleContext } -// Log logs with specified level and formatting string with arguments -func (l *LogAction) Log(level, format string, args ...interface{}) predicate.BoolPredicate { +// Log logs with specified level and message string and attributes +func (l *LogAction) Log(level, msg string, args ...any) predicate.BoolPredicate { return func() bool { - ilevel, err := log.ParseLevel(level) - if err != nil { - ilevel = log.DebugLevel + slevel := slog.LevelDebug + switch strings.ToLower(level) { + case "error": + slevel = slog.LevelError + case "warn", "warning": + slevel = slog.LevelWarn + case "info": + slevel = slog.LevelInfo + case "debug": + slevel = slog.LevelDebug + case "trace": + slevel = logutils.TraceLevel } - var writer io.Writer - if l.writer != nil { - writer = l.writer - } else { - writer = log.StandardLogger().WriterLevel(ilevel) + + ctx := context.Background() + // Expicitly check whether logging is enabled for the level + // to avoid formatting the message if the log won't be sampled. + if !slog.Default().Handler().Enabled(ctx, slevel) { + //nolint:sloglint // msg cannot be constant + slog.Log(context.Background(), slevel, fmt.Sprintf(msg, args...)) } - writer.Write([]byte(fmt.Sprintf(format, args...))) + return true } } diff --git a/lib/services/presets.go b/lib/services/presets.go index ec3f8ad529c9d..9f59ff004073e 100644 --- a/lib/services/presets.go +++ b/lib/services/presets.go @@ -19,10 +19,11 @@ package services import ( + "context" + "log/slog" "slices" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/constants" @@ -801,7 +802,7 @@ func defaultAllowAccountAssignments(enterprise bool) map[string][]types.Identity // AddRoleDefaults adds default role attributes to a preset role. // Only attributes whose resources are not already defined (either allowing or denying) are added. -func AddRoleDefaults(role types.Role) (types.Role, error) { +func AddRoleDefaults(ctx context.Context, role types.Role) (types.Role, error) { changed := false oldLabels := role.GetAllLabels() @@ -855,7 +856,10 @@ func AddRoleDefaults(role types.Role) (types.Role, error) { continue } - log.Debugf("Adding default allow rule %v for role %q", defaultRule, role.GetName()) + slog.DebugContext(ctx, "Adding default allow rule to role", + "rule", defaultRule, + "role", role.GetName(), + ) rules := role.GetRules(types.Allow) rules = append(rules, defaultRule) role.SetRules(types.Allow, rules) diff --git a/lib/services/presets_test.go b/lib/services/presets_test.go index 3e4f12c5d4084..8e0490c82f714 100644 --- a/lib/services/presets_test.go +++ b/lib/services/presets_test.go @@ -19,6 +19,7 @@ package services import ( + "context" "testing" "github.com/google/go-cmp/cmp" @@ -639,7 +640,7 @@ func TestAddRoleDefaults(t *testing.T) { }) } - role, err := AddRoleDefaults(test.role) + role, err := AddRoleDefaults(context.Background(), test.role) test.expectedErr(t, err) require.Empty(t, cmp.Diff(role, test.expected)) diff --git a/lib/services/readonly/readonly.go b/lib/services/readonly/readonly.go index c4ed3185ace66..744f2b4cd3a5c 100644 --- a/lib/services/readonly/readonly.go +++ b/lib/services/readonly/readonly.go @@ -71,6 +71,7 @@ func sealAuthPreference(p types.AuthPreference) AuthPreference { type ClusterNetworkingConfig interface { GetCaseInsensitiveRouting() bool GetWebIdleTimeout() time.Duration + GetRoutingStrategy() types.RoutingStrategy Clone() types.ClusterNetworkingConfig } diff --git a/lib/services/role.go b/lib/services/role.go index 37418d27c41a0..b9e3e04d83816 100644 --- a/lib/services/role.go +++ b/lib/services/role.go @@ -35,7 +35,6 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" jsoniter "github.com/json-iterator/go" - log "github.com/sirupsen/logrus" "github.com/vulcand/predicate" "golang.org/x/crypto/ssh" @@ -51,6 +50,7 @@ import ( "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" awsutils "github.com/gravitational/teleport/lib/utils/aws" + logutils "github.com/gravitational/teleport/lib/utils/log" "github.com/gravitational/teleport/lib/utils/parse" ) @@ -1598,7 +1598,7 @@ func (set RoleSet) CheckGCPServiceAccounts(ttl time.Duration, overrideTTL bool) // //nolint:revive // Because we want this to be IdP. func (set RoleSet) CheckAccessToSAMLIdP(authPref readonly.AuthPreference, state AccessState) error { - _, debugf := rbacDebugLogger() + ctx := context.Background() if authPref != nil { if !authPref.IsSAMLIdPEnabled() { @@ -1607,7 +1607,7 @@ func (set RoleSet) CheckAccessToSAMLIdP(authPref readonly.AuthPreference, state } if state.MFARequired == MFARequiredAlways && !state.MFAVerified { - debugf("Access to SAML IdP denied, cluster requires per-session MFA") + rbacLogger.LogAttrs(ctx, logutils.TraceLevel, "Access to SAML IdP denied, cluster requires per-session MFA") return trace.Wrap(ErrSessionMFARequired) } @@ -1627,7 +1627,10 @@ func (set RoleSet) CheckAccessToSAMLIdP(authPref readonly.AuthPreference, state } if !mfaAllowed && options.RequireMFAType.IsSessionMFARequired() { - debugf("Access to SAML IdP denied, role %q requires per-session MFA", role.GetName()) + rbacLogger.LogAttrs(ctx, logutils.TraceLevel, "Access to SAML IdP denied, role requires per-session MFA", + slog.String("role", role.GetName()), + ) + return trace.Wrap(ErrSessionMFARequired) } } @@ -2539,19 +2542,7 @@ type AccessCheckable interface { GetAllLabels() map[string]string } -// rbacDebugLogger creates a debug logger for Teleport's RBAC component. -// It also returns a flag indicating whether debug logging is enabled, -// allowing the RBAC system to generate more verbose errors in debug mode. -func rbacDebugLogger() (debugEnabled bool, debugf func(format string, args ...interface{})) { - debugEnabled = log.IsLevelEnabled(log.TraceLevel) - debugf = func(format string, args ...interface{}) {} - - if debugEnabled { - debugf = log.WithField(teleport.ComponentKey, teleport.ComponentRBAC).Tracef - } - - return -} +var rbacLogger = logutils.NewPackageLogger(teleport.ComponentKey, teleport.ComponentRBAC) // resourceRequiresLabelMatching decides if a resource requires lapel matching // when making RBAC access decisions. @@ -2567,13 +2558,18 @@ func resourceRequiresLabelMatching(r AccessCheckable) bool { } func (set RoleSet) checkAccess(r AccessCheckable, traits wrappers.Traits, state AccessState, matchers ...RoleMatcher) error { - // Note: logging in this function only happens in debug mode. This is because + // Note: logging in this function only happens in trace mode. This is because // adding logging to this function (which is called on every resource returned // by the backend) can slow down this function by 50x for large clusters! - isDebugEnabled, debugf := rbacDebugLogger() + ctx := context.Background() + logger := rbacLogger + isLoggingEnabled := logger.Handler().Enabled(ctx, logutils.TraceLevel) + if isLoggingEnabled { + logger.With("resource_kind", r.GetKind(), "resource_name", r.GetName()) + } if !state.MFAVerified && state.MFARequired == MFARequiredAlways { - debugf("Access to %v %q denied, cluster requires per-session MFA", r.GetKind(), r.GetName()) + logger.LogAttrs(ctx, logutils.TraceLevel, "Access to resource denied, cluster requires per-session MFA") return ErrSessionMFARequired } @@ -2601,18 +2597,21 @@ func (set RoleSet) checkAccess(r AccessCheckable, traits wrappers.Traits, state continue } if requiresLabelMatching { - matchLabels, labelsMessage, err := checkRoleLabelsMatch(types.Deny, role, traits, r, isDebugEnabled) + matchLabels, labelsMessage, err := checkRoleLabelsMatch(types.Deny, role, traits, r, isLoggingEnabled) if err != nil { return trace.Wrap(err) } if matchLabels { - debugf("Access to %v %q denied, deny rule in role %q matched; match(namespace=%v, %s)", - r.GetKind(), r.GetName(), role.GetName(), namespaceMessage, labelsMessage) + logger.LogAttrs(ctx, logutils.TraceLevel, "Access to resource denied, deny rule in role matched", + slog.String("role", role.GetName()), + slog.String("namespace_message", namespaceMessage), + slog.String("label_message", labelsMessage), + ) return trace.AccessDenied("access to %v denied. User does not have permissions. %v", r.GetKind(), additionalDeniedMessage) } } else { - debugf("Role label matching skipped for %v %q", r.GetKind(), r.GetName()) + logger.LogAttrs(ctx, logutils.TraceLevel, "Role label matching skipped") } // Deny rules are greedy on purpose. They will always match if // at least one of the matchers returns true. @@ -2621,8 +2620,10 @@ func (set RoleSet) checkAccess(r AccessCheckable, traits wrappers.Traits, state return trace.Wrap(err) } if matchMatchers { - debugf("Access to %v %q denied, deny rule in role %q matched; match(matcher=%v)", - r.GetKind(), r.GetName(), role.GetName(), matchersMessage) + logger.LogAttrs(ctx, logutils.TraceLevel, "Access to resource denied, deny rule in role matched", + slog.String("role", role.GetName()), + slog.Any("matcher_message", matchersMessage), + ) return trace.AccessDenied("access to %v denied. User does not have permissions. %v", r.GetKind(), additionalDeniedMessage) } @@ -2640,7 +2641,7 @@ func (set RoleSet) checkAccess(r AccessCheckable, traits wrappers.Traits, state for _, role := range set { matchNamespace, namespaceMessage := MatchNamespace(role.GetNamespaces(types.Allow), namespace) if !matchNamespace { - if isDebugEnabled { + if isLoggingEnabled { errs = append(errs, trace.AccessDenied("role=%v, match(namespace=%v)", role.GetName(), namespaceMessage)) } @@ -2648,20 +2649,20 @@ func (set RoleSet) checkAccess(r AccessCheckable, traits wrappers.Traits, state } if requiresLabelMatching { - matchLabels, labelsMessage, err := checkRoleLabelsMatch(types.Allow, role, traits, r, isDebugEnabled) + matchLabels, labelsMessage, err := checkRoleLabelsMatch(types.Allow, role, traits, r, isLoggingEnabled) if err != nil { return trace.Wrap(err) } if !matchLabels { - if isDebugEnabled { + if isLoggingEnabled { errs = append(errs, trace.AccessDenied("role=%v, match(%s)", role.GetName(), labelsMessage)) } continue } } else { - debugf("Role label matching skipped for %v %q", r.GetKind(), r.GetName()) + logger.LogAttrs(ctx, logutils.TraceLevel, "Role label matching skipped for resource") } // Allow rules are not greedy. They will match only if all of the @@ -2671,7 +2672,7 @@ func (set RoleSet) checkAccess(r AccessCheckable, traits wrappers.Traits, state return trace.Wrap(err) } if !matchMatchers { - if isDebugEnabled { + if isLoggingEnabled { errs = append(errs, fmt.Errorf("role=%v, match(matchers=%v)", role.GetName(), matchers)) } @@ -2689,37 +2690,43 @@ func (set RoleSet) checkAccess(r AccessCheckable, traits wrappers.Traits, state // ensure the access is permitted. if mfaAllowed && deviceAllowed { - debugf("Access to %v %q granted, allow rule in role %q matched.", - r.GetKind(), r.GetName(), role.GetName()) + logger.LogAttrs(ctx, logutils.TraceLevel, "Access to resource granted, allow rule in role matched", + slog.String("role", role.GetName()), + ) return nil } // MFA verification. if !mfaAllowed && role.GetOptions().RequireMFAType.IsSessionMFARequired() { - debugf("Access to %v %q denied, role %q requires per-session MFA", - r.GetKind(), r.GetName(), role.GetName()) + logger.LogAttrs(ctx, logutils.TraceLevel, "Access to resource denied, role requires per-session MFA", + slog.String("role", role.GetName()), + ) return ErrSessionMFARequired } // Device verification. if !deviceAllowed && role.GetOptions().DeviceTrustMode == constants.DeviceTrustModeRequired { - debugf("Access to %v %q denied, role %q requires a trusted device", - r.GetKind(), r.GetName(), role.GetName()) + logger.LogAttrs(ctx, logutils.TraceLevel, "Access to resource denied, role requires a trusted device", + slog.String("role", role.GetName()), + ) return ErrTrustedDeviceRequired } // Current role allows access, but keep looking for a more restrictive // setting. allowed = true - debugf("Access to %v %q granted, allow rule in role %q matched.", - r.GetKind(), r.GetName(), role.GetName()) + logger.LogAttrs(ctx, logutils.TraceLevel, "Access to resource granted, allow rule in role matched", + slog.String("role", role.GetName()), + ) } if allowed { return nil } - debugf("Access to %v %q denied, no allow rule matched; %v", r.GetKind(), r.GetName(), errs) + logger.LogAttrs(ctx, logutils.TraceLevel, "Access to resource denied, no allow rule matched", + slog.Any("errors", errs), + ) return trace.AccessDenied("access to %v denied. User does not have permissions. %v", r.GetKind(), additionalDeniedMessage) } @@ -3224,6 +3231,8 @@ func (a *accessExplicitlyDenied) Unwrap() error { } func (set RoleSet) checkAccessToRuleImpl(p checkAccessParams) (err error) { + ctx := context.Background() + // Every unknown error, which could be due to a bad role or an expression // that can't parse, should be considered an explicit denial. explicitDeny := true @@ -3247,10 +3256,13 @@ func (set RoleSet) checkAccessToRuleImpl(p checkAccessParams) (err error) { return trace.Wrap(err) } if matched { - log.WithFields(log.Fields{ - teleport.ComponentKey: teleport.ComponentRBAC, - }).Tracef("Access to %v %v in namespace %v denied to %v: deny rule matched.", - p.verb, p.resource, p.namespace, role.GetName()) + rbacLogger.LogAttrs(ctx, logutils.TraceLevel, "Access denied, deny rule matched", + slog.String("verb", p.verb), + slog.String("resource", p.resource), + slog.String("namespace", p.namespace), + slog.String("role", role.GetName()), + ) + return trace.AccessDenied("access denied to perform action %q on %q", p.verb, p.resource) } } @@ -3270,10 +3282,12 @@ func (set RoleSet) checkAccessToRuleImpl(p checkAccessParams) (err error) { } } - log.WithFields(log.Fields{ - teleport.ComponentKey: teleport.ComponentRBAC, - }).Tracef("Access to %v %v in namespace %v denied to %v: no allow rule matched.", - p.verb, p.resource, p.namespace, set) + rbacLogger.LogAttrs(ctx, logutils.TraceLevel, "Access denied, no allow rule matched", + slog.String("verb", p.verb), + slog.String("resource", p.resource), + slog.String("namespace", p.namespace), + slog.Any("set", set), + ) // At this point no deny rule has matched and there are no more unknown // errors, so this is only an implicit denial. diff --git a/lib/services/role_test.go b/lib/services/role_test.go index 7c85f6a07e0a8..13f2cc3f18c72 100644 --- a/lib/services/role_test.go +++ b/lib/services/role_test.go @@ -19,7 +19,6 @@ package services import ( - "bytes" "cmp" "context" "encoding/json" @@ -2180,27 +2179,13 @@ func makeAccessCheckerWithRoleSet(roleSet RoleSet) AccessChecker { return NewAccessCheckerWithRoleSet(accessInfo, "clustername", roleSet) } -// testContext overrides context and captures log writes in action -type testContext struct { - Context - // Buffer captures log writes - buffer *bytes.Buffer -} - -// Write is implemented explicitly to avoid collision -// of String methods when embedding -func (t *testContext) Write(data []byte) (int, error) { - return t.buffer.Write(data) -} - func TestCheckRuleAccess(t *testing.T) { type check struct { - hasAccess bool - verb string - namespace string - rule string - context testContext - matchBuffer string + hasAccess bool + verb string + namespace string + rule string + context Context } testCases := []struct { name string @@ -2320,9 +2305,6 @@ func TestCheckRuleAccess(t *testing.T) { Resources: []string{types.KindSession}, Verbs: []string{types.VerbRead}, Where: `contains(user.spec.traits["group"], "prod")`, - Actions: []string{ - `log("info", "4 - tc match for user %v", user.metadata.name)`, - }, }, }, }, @@ -2333,17 +2315,14 @@ func TestCheckRuleAccess(t *testing.T) { {rule: types.KindSession, verb: types.VerbRead, namespace: apidefaults.Namespace, hasAccess: false}, {rule: types.KindSession, verb: types.VerbList, namespace: apidefaults.Namespace, hasAccess: false}, { - context: testContext{ - buffer: &bytes.Buffer{}, - Context: Context{ - User: &types.UserV2{ - Metadata: types.Metadata{ - Name: "bob", - }, - Spec: types.UserSpecV2{ - Traits: map[string][]string{ - "group": {"dev", "prod"}, - }, + context: Context{ + User: &types.UserV2{ + Metadata: types.Metadata{ + Name: "bob", + }, + Spec: types.UserSpecV2{ + Traits: map[string][]string{ + "group": {"dev", "prod"}, }, }, }, @@ -2354,14 +2333,11 @@ func TestCheckRuleAccess(t *testing.T) { hasAccess: true, }, { - context: testContext{ - buffer: &bytes.Buffer{}, - Context: Context{ - User: &types.UserV2{ - Spec: types.UserSpecV2{ - Traits: map[string][]string{ - "group": {"dev"}, - }, + context: Context{ + User: &types.UserV2{ + Spec: types.UserSpecV2{ + Traits: map[string][]string{ + "group": {"dev"}, }, }, }, @@ -2389,9 +2365,6 @@ func TestCheckRuleAccess(t *testing.T) { Resources: []string{types.KindRole}, Verbs: []string{types.VerbRead}, Where: `equals(resource.metadata.labels["team"], "dev")`, - Actions: []string{ - `log("error", "4 - tc match")`, - }, }, }, }, @@ -2402,13 +2375,10 @@ func TestCheckRuleAccess(t *testing.T) { {rule: types.KindRole, verb: types.VerbRead, namespace: apidefaults.Namespace, hasAccess: false}, {rule: types.KindRole, verb: types.VerbList, namespace: apidefaults.Namespace, hasAccess: false}, { - context: testContext{ - buffer: &bytes.Buffer{}, - Context: Context{ - Resource: &types.RoleV6{ - Metadata: types.Metadata{ - Labels: map[string]string{"team": "dev"}, - }, + context: Context{ + Resource: &types.RoleV6{ + Metadata: types.Metadata{ + Labels: map[string]string{"team": "dev"}, }, }, }, @@ -2439,9 +2409,6 @@ func TestCheckRuleAccess(t *testing.T) { Resources: []string{types.KindRole}, Verbs: []string{types.VerbRead}, Where: `equals(resource.metadata.labels["team"], "dev")`, - Actions: []string{ - `log("info", "matched more specific rule")`, - }, }, }, }, @@ -2450,21 +2417,17 @@ func TestCheckRuleAccess(t *testing.T) { }, checks: []check{ { - context: testContext{ - buffer: &bytes.Buffer{}, - Context: Context{ - Resource: &types.RoleV6{ - Metadata: types.Metadata{ - Labels: map[string]string{"team": "dev"}, - }, + context: Context{ + Resource: &types.RoleV6{ + Metadata: types.Metadata{ + Labels: map[string]string{"team": "dev"}, }, }, }, - rule: types.KindRole, - verb: types.VerbRead, - namespace: apidefaults.Namespace, - hasAccess: true, - matchBuffer: "more specific rule", + rule: types.KindRole, + verb: types.VerbRead, + namespace: apidefaults.Namespace, + hasAccess: true, }, }, }, @@ -2486,9 +2449,6 @@ func TestCheckRuleAccess(t *testing.T) { } else { require.True(t, trace.IsAccessDenied(result), comment) } - if check.matchBuffer != "" { - require.Contains(t, check.context.buffer.String(), check.matchBuffer, comment) - } } } } @@ -2498,7 +2458,7 @@ func TestDefaultImplicitRules(t *testing.T) { hasAccess bool verb string rule string - context testContext + context Context } testCases := []struct { name string diff --git a/lib/services/saml.go b/lib/services/saml.go index ad8e2dccaa36c..ab6988047e0a6 100644 --- a/lib/services/saml.go +++ b/lib/services/saml.go @@ -24,6 +24,7 @@ import ( "crypto/x509/pkix" "encoding/base64" "encoding/xml" + "log/slog" "net/http" "strings" "time" @@ -33,7 +34,6 @@ import ( saml2 "github.com/russellhaering/gosaml2" samltypes "github.com/russellhaering/gosaml2/types" dsig "github.com/russellhaering/goxmldsig" - log "github.com/sirupsen/logrus" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" @@ -87,7 +87,11 @@ func ValidateSAMLConnector(sc types.SAMLConnector, rg RoleGetter) error { } sc.SetEntityDescriptor(entityDescriptor) - log.Debugf("[SAML] Successfully fetched entity descriptor from %v for connector %v", url, sc.GetName()) + slog.DebugContext(context.Background(), " Successfully fetched entity descriptor for connector", + teleport.ComponentKey, teleport.ComponentSAML, + "entity_descriptor_url", url, + "connector", sc.GetName(), + ) } if ed := sc.GetEntityDescriptor(); ed != "" { @@ -173,9 +177,12 @@ func ValidateSAMLConnector(sc types.SAMLConnector, rg RoleGetter) error { sc.SetMFASettings(mfa) } - log.Debugf("[SAML] SSO: %v", sc.GetSSO()) - log.Debugf("[SAML] Issuer: %v", sc.GetIssuer()) - log.Debugf("[SAML] ACS: %v", sc.GetAssertionConsumerService()) + slog.DebugContext(context.Background(), "connector validated", + teleport.ComponentKey, teleport.ComponentSAML, + "sso", sc.GetSSO(), + "issuer", sc.GetIssuer(), + "acs", sc.GetAssertionConsumerService(), + ) return nil } @@ -271,7 +278,9 @@ func GetSAMLServiceProvider(sc types.SAMLConnector, clock clockwork.Clock) (*sam // Case 1: Only the signing key pair is set. This means that SAML encryption is not expected // and we therefore configure the main key that gets used for all operations as the signing key. // This is done because gosaml2 mandates an encryption key even if not used. - log.Info("No assertion_key_pair was detected. Falling back to signing key for all SAML operations.") + slog.InfoContext(context.Background(), "No assertion_key_pair was detected, falling back to signing key for all SAML operations", + teleport.ComponentKey, teleport.ComponentSAML, + ) keyStore, err = utils.ParseKeyStorePEM(signingKeyPair.PrivateKey, signingKeyPair.Cert) signingKeyStore = keyStore if err != nil { @@ -281,7 +290,9 @@ func GetSAMLServiceProvider(sc types.SAMLConnector, clock clockwork.Clock) (*sam // Case 2: An encryption keypair is configured. This means that encrypted SAML responses are expected. // Since gosaml2 always uses the main key for encryption, we set it to assertion_key_pair. // To handle signing correctly, we now instead set the optional signing key in gosaml2 to signing_key_pair. - log.Info("Detected assertion_key_pair and configured it to decrypt SAML responses.") + slog.InfoContext(context.Background(), "Detected assertion_key_pair and configured it to decrypt SAML responses", + teleport.ComponentKey, teleport.ComponentSAML, + ) keyStore, err = utils.ParseKeyStorePEM(encryptionKeyPair.PrivateKey, encryptionKeyPair.Cert) if err != nil { return nil, trace.Wrap(err, "failed to parse certificate or private key defined in assertion_key_pair") @@ -313,9 +324,7 @@ func GetSAMLServiceProvider(sc types.SAMLConnector, clock clockwork.Clock) (*sam // be used. switch sc.GetProvider() { case teleport.ADFS, teleport.JumpCloud: - log.WithFields(log.Fields{ - teleport.ComponentKey: teleport.ComponentSAML, - }).Debug("Setting ADFS/JumpCloud values.") + slog.DebugContext(context.Background(), "Setting ADFS/JumpCloud values", teleport.ComponentKey, teleport.ComponentSAML) if sp.SignAuthnRequests { sp.SignAuthnRequestsCanonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList(dsig.DefaultPrefix) diff --git a/lib/services/saml_idp_service_provider.go b/lib/services/saml_idp_service_provider.go index 3d5882c5e5e67..51c60fa1e807f 100644 --- a/lib/services/saml_idp_service_provider.go +++ b/lib/services/saml_idp_service_provider.go @@ -21,6 +21,7 @@ package services import ( "context" "fmt" + "log/slog" "net/url" "slices" "strings" @@ -28,7 +29,6 @@ import ( "github.com/crewjam/saml" "github.com/crewjam/saml/samlsp" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/utils" @@ -139,7 +139,10 @@ func FilterSAMLEntityDescriptor(ed *saml.EntityDescriptor, quiet bool) error { filtered := slices.DeleteFunc(ed.SPSSODescriptors[i].AssertionConsumerServices, func(acs saml.IndexedEndpoint) bool { if err := ValidateAssertionConsumerService(acs); err != nil { if !quiet { - log.Warnf("AssertionConsumerService binding for entity %q is invalid and will be ignored: %v", ed.EntityID, err) + slog.WarnContext(context.Background(), "AssertionConsumerService binding for entity is invalid and will be ignored", + "entity_id", ed.EntityID, + "error", err, + ) } return true } diff --git a/lib/services/semaphore.go b/lib/services/semaphore.go index 52211acbfa33c..4e1a672423ced 100644 --- a/lib/services/semaphore.go +++ b/lib/services/semaphore.go @@ -20,12 +20,12 @@ package services import ( "context" + "log/slog" "sync" "time" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - log "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/utils/retryutils" @@ -200,10 +200,17 @@ func (l *SemaphoreLock) keepAlive(ctx context.Context) { defer cancel() err = l.cfg.Service.CancelSemaphoreLease(cancelContext, lease) if err != nil { - log.Warnf("Failed to cancel semaphore lease %s/%s: %v", lease.SemaphoreKind, lease.SemaphoreName, err) + slog.WarnContext(cancelContext, "Failed to cancel semaphore lease %s/%s: %v", + "semaphore_kind", lease.SemaphoreKind, + "semaphore_name", lease.SemaphoreName, + "error", err, + ) } } else { - log.Errorf("Semaphore lease expired: %s/%s", lease.SemaphoreKind, lease.SemaphoreName) + slog.ErrorContext(context.Background(), "Semaphore lease expired", + "semaphore_kind", lease.SemaphoreKind, + "semaphore_name", lease.SemaphoreName, + ) } }() Outer: @@ -219,7 +226,11 @@ Outer: leaseCancel() // semaphore and/or lease no longer exist; best to log the error // and exit immediately. - log.Warnf("Halting keepalive on semaphore %s/%s early: %v", lease.SemaphoreKind, lease.SemaphoreName, err) + slog.WarnContext(leaseContext, "Halting keepalive on semaphore", + "semaphore_kind", lease.SemaphoreKind, + "semaphore_name", lease.SemaphoreName, + "error", err, + ) nodrop = true return } @@ -233,7 +244,11 @@ Outer: } continue Outer } - log.Debugf("Failed to renew semaphore lease %s/%s: %v", lease.SemaphoreKind, lease.SemaphoreName, err) + slog.DebugContext(leaseContext, "Failed to renew semaphore lease", + "semaphore_kind", lease.SemaphoreKind, + "semaphore_name", lease.SemaphoreName, + "error", err, + ) l.retry.Inc() select { case <-l.retry.After(): diff --git a/lib/services/simple/access_list.go b/lib/services/simple/access_list.go index 9effb4475c973..f8b8b78bdbe44 100644 --- a/lib/services/simple/access_list.go +++ b/lib/services/simple/access_list.go @@ -23,9 +23,7 @@ import ( "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" - "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/accesslist" "github.com/gravitational/teleport/lib/backend" @@ -46,7 +44,6 @@ const ( // AccessListService is a simple access list backend service for use specifically by the cache. type AccessListService struct { - log logrus.FieldLogger service *generic.Service[*accesslist.AccessList] memberService *generic.Service[*accesslist.AccessListMember] reviewService *generic.Service[*accesslist.Review] @@ -93,7 +90,6 @@ func NewAccessListService(b backend.Backend) (*AccessListService, error) { } return &AccessListService{ - log: logrus.WithFields(logrus.Fields{teleport.ComponentKey: "access-list:simple-service"}), service: service, memberService: memberService, reviewService: reviewService, diff --git a/lib/services/suite/suite.go b/lib/services/suite/suite.go index ad2cb4695b3f2..3c1a445480166 100644 --- a/lib/services/suite/suite.go +++ b/lib/services/suite/suite.go @@ -22,6 +22,7 @@ import ( "context" "crypto/x509/pkix" "fmt" + "log/slog" "sort" "sync" "sync/atomic" @@ -33,7 +34,6 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - log "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "golang.org/x/crypto/bcrypt" protobuf "google.golang.org/protobuf/proto" @@ -2140,7 +2140,7 @@ skiploop: for { select { case event := <-w.Events(): - log.Debugf("Skipping pre-test event: %v", event) + slog.DebugContext(ctx, "Skipping pre-test event", "event", event) continue skiploop default: break skiploop @@ -2215,11 +2215,11 @@ waitLoop: t.Fatalf("Watcher exited with error %v", w.Error()) case event := <-w.Events(): if event.Type != types.OpPut { - log.Debugf("Skipping event %+v", event) + slog.DebugContext(context.Background(), "Skipping event", "event", event) continue } if resource.GetName() != event.Resource.GetName() || resource.GetKind() != event.Resource.GetKind() || resource.GetSubKind() != event.Resource.GetSubKind() { - log.Debugf("Skipping event %v resource %v, expecting %v", event.Type, event.Resource.GetMetadata(), event.Resource.GetMetadata()) + slog.DebugContext(context.Background(), "Skipping event", "event", event) continue waitLoop } require.Empty(t, cmp.Diff(resource, event.Resource)) @@ -2240,7 +2240,10 @@ waitLoop: t.Fatalf("Watcher exited with error %v", w.Error()) case event := <-w.Events(): if event.Type != types.OpDelete { - log.Debugf("Skipping stale event %v %v", event.Type, event.Resource.GetName()) + slog.DebugContext(context.Background(), "Skipping stale event", + "event_type", event.Type, + "resource_name", event.Resource.GetName(), + ) continue } diff --git a/lib/services/traits.go b/lib/services/traits.go index 4c6fa2294dbf1..fb27619f98539 100644 --- a/lib/services/traits.go +++ b/lib/services/traits.go @@ -19,11 +19,12 @@ package services import ( + "context" "fmt" + "log/slog" "regexp" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" apiutils "github.com/gravitational/teleport/api/utils" @@ -144,13 +145,19 @@ TraitMappingLoop: // show at most maxMismatchedTraitValuesLogged trait values to prevent huge log lines switch l := len(mismatched); { case l > maxMismatchedTraitValuesLogged: - log.WithField("expression", mapping.Value). - WithField("values", mismatched[0:maxMismatchedTraitValuesLogged]). - Debugf("%d trait value(s) did not match (showing first %d values)", len(mismatched), maxMismatchedTraitValuesLogged) + slog. + DebugContext(context.Background(), "trait value(s) did not match (showing first %d values)", + "mismatch_count", len(mismatched), + "max_mismatch_logged", maxMismatchedTraitValuesLogged, + "expression", mapping.Value, + "values", mismatched[0:maxMismatchedTraitValuesLogged], + ) case l > 0: - log.WithField("expression", mapping.Value). - WithField("values", mismatched). - Debugf("%d trait value(s) did not match", len(mismatched)) + slog.DebugContext(context.Background(), "trait value(s) did not match", + "mismatch_count", len(mismatched), + "expression", mapping.Value, + "values", mismatched, + ) } } } diff --git a/lib/services/unified_resource.go b/lib/services/unified_resource.go index 2cf2e1c89995f..1bf8bd95747c0 100644 --- a/lib/services/unified_resource.go +++ b/lib/services/unified_resource.go @@ -20,6 +20,7 @@ package services import ( "context" + "log/slog" "strings" "sync" "time" @@ -28,7 +29,6 @@ import ( "github.com/google/btree" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - log "github.com/sirupsen/logrus" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client/proto" @@ -37,6 +37,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" "github.com/gravitational/teleport/lib/utils/pagination" ) @@ -68,9 +69,9 @@ type UnifiedResourceCacheConfig struct { // UnifiedResourceCache contains a representation of all resources that are displayable in the UI type UnifiedResourceCache struct { - rw sync.RWMutex - log *log.Entry - cfg UnifiedResourceCacheConfig + rw sync.RWMutex + logger *slog.Logger + cfg UnifiedResourceCacheConfig // nameTree is a BTree with items sorted by (hostname)/name/type nameTree *btree.BTreeG[*item] // typeTree is a BTree with items sorted by type/(hostname)/name @@ -101,10 +102,8 @@ func NewUnifiedResourceCache(ctx context.Context, cfg UnifiedResourceCacheConfig } m := &UnifiedResourceCache{ - log: log.WithFields(log.Fields{ - teleport.ComponentKey: cfg.Component, - }), - cfg: cfg, + logger: slog.With(teleport.ComponentKey, cfg.Component), + cfg: cfg, nameTree: btree.NewG(cfg.BTreeDegree, func(a, b *item) bool { return a.Less(b) }), @@ -267,7 +266,10 @@ func (c *UnifiedResourceCache) getRange(ctx context.Context, startKey backend.Ke } if len(res) == backend.DefaultRangeLimit { - c.log.Warnf("Range query hit backend limit. (this is a bug!) startKey=%q,limit=%d", startKey, backend.DefaultRangeLimit) + c.logger.WarnContext(ctx, "Range query hit backend limit. (this is a bug!)", + "start_key", startKey, + "range_limit", backend.DefaultRangeLimit, + ) } return res, nextKey, nil @@ -747,7 +749,11 @@ func (c *UnifiedResourceCache) processEventsAndUpdateCurrent(ctx context.Context for _, event := range events { if event.Resource == nil { - c.log.Warnf("Unexpected event: %v.", event) + c.logger.WarnContext(ctx, "Unexpected event", + "event_type", event.Type, + "resource_kind", event.Resource.GetKind(), + "resource_name", event.Resource.GetName(), + ) continue } @@ -775,15 +781,15 @@ func (c *UnifiedResourceCache) processEventsAndUpdateCurrent(ctx context.Context c.putLocked(types.Resource153ToUnifiedResource(unwrapped)) default: - c.log.Warnf("unsupported Resource153 type %T.", unwrapped) + c.logger.WarnContext(ctx, "unsupported Resource153 type", "resource_type", logutils.TypeAttr(unwrapped)) } default: - c.log.Warnf("unsupported Resource type %T.", r) + c.logger.WarnContext(ctx, "unsupported Resource type", "resource_type", logutils.TypeAttr(r)) } default: - c.log.Warnf("unsupported event type %s.", event.Type) + c.logger.WarnContext(ctx, "unsupported event type", "event_type", event.Type) continue } } diff --git a/lib/srv/authhandlers.go b/lib/srv/authhandlers.go index 03de6e26c779a..5d6d12ad05dff 100644 --- a/lib/srv/authhandlers.go +++ b/lib/srv/authhandlers.go @@ -633,7 +633,7 @@ func (a *ahLoginChecker) canLoginWithRBAC(cert *ssh.Certificate, ca types.CertAu // Use the server's shutdown context. ctx := a.c.Server.Context() - a.log.DebugContext(ctx, "Checking permissions for (%v,%v) to login to node with RBAC checks.", teleportUser, osUser) + a.log.DebugContext(ctx, "Checking permissions to login to node with RBAC checks", "teleport_user", teleportUser, "os_user", osUser) // get roles assigned to this user accessInfo, err := fetchAccessInfo(cert, ca, teleportUser, clusterName) diff --git a/lib/srv/desktop/rdp/rdpclient/client.go b/lib/srv/desktop/rdp/rdpclient/client.go index c7be6dd702016..821408d2208fa 100644 --- a/lib/srv/desktop/rdp/rdpclient/client.go +++ b/lib/srv/desktop/rdp/rdpclient/client.go @@ -73,6 +73,7 @@ import "C" import ( "context" "fmt" + "log/slog" "os" "runtime/cgo" "sync" @@ -81,7 +82,6 @@ import ( "unsafe" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/srv/desktop/tdp" @@ -98,14 +98,15 @@ func init() { // assume the user knows what they want) rl := os.Getenv("RUST_LOG") if rl == "" { - switch l := logrus.GetLevel(); l { - case logrus.TraceLevel: + ctx := context.Background() + switch { + case slog.Default().Enabled(ctx, logutils.TraceLevel): rustLogLevel = "trace" - case logrus.DebugLevel: + case slog.Default().Enabled(ctx, slog.LevelDebug): rustLogLevel = "debug" - case logrus.InfoLevel: + case slog.Default().Enabled(ctx, slog.LevelInfo): rustLogLevel = "info" - case logrus.WarnLevel: + case slog.Default().Enabled(ctx, slog.LevelWarn): rustLogLevel = "warn" default: rustLogLevel = "error" diff --git a/lib/srv/desktop/windows_server.go b/lib/srv/desktop/windows_server.go index 30e95a51d5840..8dbbad96b3fb6 100644 --- a/lib/srv/desktop/windows_server.go +++ b/lib/srv/desktop/windows_server.go @@ -36,7 +36,6 @@ import ( "github.com/go-ldap/ldap/v3" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport" apidefaults "github.com/gravitational/teleport/api/defaults" @@ -382,7 +381,7 @@ func NewWindowsService(cfg WindowsServiceConfig) (*WindowsService, error) { s.ca = windows.NewCertificateStoreClient(windows.CertificateStoreConfig{ AccessPoint: s.cfg.AccessPoint, LDAPConfig: caLDAPConfig, - Log: logrus.NewEntry(logrus.StandardLogger()), + Logger: slog.Default(), ClusterName: s.clusterName, LC: s.lc, }) diff --git a/lib/srv/server/ec2_watcher.go b/lib/srv/server/ec2_watcher.go index 79d20905408ac..cf3bb13a62367 100644 --- a/lib/srv/server/ec2_watcher.go +++ b/lib/srv/server/ec2_watcher.go @@ -20,6 +20,7 @@ package server import ( "context" + "log/slog" "sync" "time" @@ -27,7 +28,6 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ec2" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" usageeventsv1 "github.com/gravitational/teleport/api/gen/proto/go/usageevents/v1" "github.com/gravitational/teleport/api/types" @@ -305,7 +305,7 @@ func newEC2InstanceFetcher(cfg ec2FetcherConfig) *ec2InstanceFetcher { }) } } else { - log.Debug("Not setting any tag filters as there is a '*:...' tag present and AWS doesnt allow globbing on keys") + slog.DebugContext(context.Background(), "Not setting any tag filters as there is a '*:...' tag present and AWS doesnt allow globbing on keys") } var parameters map[string]string if cfg.Matcher.Params == nil { diff --git a/lib/srv/server/watcher.go b/lib/srv/server/watcher.go index 8fa5de1ee9a90..436cd0f128cbc 100644 --- a/lib/srv/server/watcher.go +++ b/lib/srv/server/watcher.go @@ -20,10 +20,10 @@ package server import ( "context" + "log/slog" "time" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" ) @@ -80,7 +80,7 @@ func (w *Watcher) sendInstancesOrLogError(instancesColl []Instances, err error) if trace.IsNotFound(err) { return } - log.WithError(err).Error("Failed to fetch instances") + slog.ErrorContext(context.Background(), "Failed to fetch instances", "error", err) return } for _, inst := range instancesColl { diff --git a/lib/srv/session_control.go b/lib/srv/session_control.go index 748aa111062eb..536cfd8948ff2 100644 --- a/lib/srv/session_control.go +++ b/lib/srv/session_control.go @@ -236,7 +236,7 @@ func (s *SessionController) AcquireSessionContext(ctx context.Context, identity } // Device Trust: authorize device extensions. - if err := dtauthz.VerifySSHUser(authPref.GetDeviceTrust(), identity.Certificate); err != nil { + if err := dtauthz.VerifySSHUser(ctx, authPref.GetDeviceTrust(), identity.Certificate); err != nil { return ctx, trace.Wrap(err) } diff --git a/lib/srv/transport/transportv1/transport.go b/lib/srv/transport/transportv1/transport.go index 820626ecfd211..e719941ee7098 100644 --- a/lib/srv/transport/transportv1/transport.go +++ b/lib/srv/transport/transportv1/transport.go @@ -290,6 +290,10 @@ func (s *Service) ProxySSH(stream transportv1pb.TransportService_ProxySSHServer) signer := s.cfg.SignerFn(authzContext, req.DialTarget.Cluster) hostConn, err := s.cfg.Dialer.DialHost(ctx, p.Addr, clientDst, host, port, req.DialTarget.Cluster, authzContext.Checker, s.cfg.agentGetterFn(agentStreamRW), signer) if err != nil { + // Return ambiguous errors unadorned so that clients can detect them easily. + if errors.Is(err, teleport.ErrNodeIsAmbiguous) { + return trace.Wrap(err) + } return trace.Wrap(err, "failed to dial target host") } diff --git a/lib/tbot/service_ssh_multiplexer.go b/lib/tbot/service_ssh_multiplexer.go index e979905d32039..de07a21eea848 100644 --- a/lib/tbot/service_ssh_multiplexer.go +++ b/lib/tbot/service_ssh_multiplexer.go @@ -667,7 +667,7 @@ func (s *SSHMultiplexerService) handleConn( host = cleanTargetHost(host, proxyHost, clusterName) target = net.JoinHostPort(host, port) } else { - node, err := resolveTargetHostWithClient(ctx, authClient, expanded.Search, expanded.Query) + node, err := resolveTargetHostWithClient(ctx, authClient.APIClient, expanded.Search, expanded.Query) if err != nil { return trace.Wrap(err, "resolving target host") } diff --git a/lib/tbot/ssh_proxy.go b/lib/tbot/ssh_proxy.go index 6769fd83103ae..3a048f6a43570 100644 --- a/lib/tbot/ssh_proxy.go +++ b/lib/tbot/ssh_proxy.go @@ -23,6 +23,7 @@ import ( "log/slog" "net" "path/filepath" + "slices" "strings" "github.com/gravitational/trace" @@ -220,38 +221,58 @@ func resolveTargetHost(ctx context.Context, cfg client.Config, search, query str // resolveTargetHostWithClient resolves the target host using the provided // client and search and query parameters. func resolveTargetHostWithClient( - ctx context.Context, clt client.ListUnifiedResourcesClient, search, query string, + ctx context.Context, clt *client.Client, search, query string, ) (types.Server, error) { - resources, _, err := client.GetUnifiedResourcePage(ctx, clt, &proto.ListUnifiedResourcesRequest{ - // We only want a single node, but, we set limit=2 so we can throw a - // helpful error when multiple match. In the happy path, where a single - // node matches, this does not degrade performance because even if - // limit=1 the UnifiedResource cache will still iterate to the end to - // determine if there is a NextKey to return. - Limit: 2, - Kinds: []string{types.KindNode}, + resp, err := clt.ResolveSSHTarget(ctx, &proto.ResolveSSHTargetRequest{ SearchKeywords: libclient.ParseSearchKeywords(search, ','), PredicateExpression: query, - SortBy: types.SortBy{Field: types.ResourceKind}, }) - if err != nil { - return nil, trace.Wrap(err) - } - if len(resources) == 0 { - return nil, trace.NotFound("no matching SSH hosts found for search terms or query expression") - } - if len(resources) > 1 { - names := make([]string, len(resources)) - for i, res := range resources { - names[i] = res.GetName() + switch { + //TODO(tross): DELETE IN v20.0.0 + case trace.IsNotImplemented(err): + resources, err := client.GetAllUnifiedResources(ctx, clt, &proto.ListUnifiedResourcesRequest{ + Kinds: []string{types.KindNode}, + SearchKeywords: libclient.ParseSearchKeywords(search, ','), + PredicateExpression: query, + SortBy: types.SortBy{Field: types.ResourceMetadataName}, + }) + if err != nil { + return nil, trace.Wrap(err) } - return nil, trace.BadParameter("found multiple matching SSH hosts %v", names) - } - node := resources[0].ResourceWithLabels.(*types.ServerV2) - if node == nil { - return nil, trace.BadParameter("expected node resource, got %T", resources[0].ResourceWithLabels) + + switch len(resources) { + case 0: + return nil, trace.NotFound("no matching SSH hosts found for search terms or query expression") + case 1: + node, ok := resources[0].ResourceWithLabels.(*types.ServerV2) + if !ok { + return nil, trace.BadParameter("expected node resource, got %T", resources[0].ResourceWithLabels) + } + return node, nil + default: + // If routing does not allow choosing the most recent host, then abort with + // an ambiguous host error. + cnc, err := clt.GetClusterNetworkingConfig(ctx) + if err != nil || cnc.GetRoutingStrategy() != types.RoutingStrategy_MOST_RECENT { + return nil, trace.BadParameter("found multiple matching SSH hosts %v", resources[:2]) + } + + // Get the most recent version of the resource. + enrichedResource := slices.MaxFunc(resources, func(a, b *types.EnrichedResource) int { + return a.Expiry().Compare(b.Expiry()) + }) + server, ok := enrichedResource.ResourceWithLabels.(types.Server) + if !ok { + return nil, trace.BadParameter("received unexpected resource type %T", resources[0].ResourceWithLabels) + } + + return server, nil + } + case err == nil: + return resp.GetServer(), nil + default: + return nil, trace.Wrap(err) } - return node, nil } func parseIdentity(destPath, proxy, cluster string, insecure, fips bool) (*identity.Facade, agent.ExtendedAgent, error) { diff --git a/lib/teleterm/apiserver/apiserver.go b/lib/teleterm/apiserver/apiserver.go index f622fe5614437..42916c94b571a 100644 --- a/lib/teleterm/apiserver/apiserver.go +++ b/lib/teleterm/apiserver/apiserver.go @@ -19,11 +19,12 @@ package apiserver import ( + "context" "fmt" + "log/slog" "net" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "google.golang.org/grpc" api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1" @@ -70,7 +71,7 @@ func New(cfg Config) (*APIServer, error) { } grpcServer := grpc.NewServer(cfg.TshdServerCreds, - grpc.ChainUnaryInterceptor(withErrorHandling(cfg.Log)), + grpc.ChainUnaryInterceptor(withErrorHandling(cfg.Logger)), grpc.MaxConcurrentStreams(defaults.GRPCMaxConcurrentStreams), ) @@ -96,7 +97,7 @@ func (s *APIServer) Stop() { // immediate. Closing the VNet service before the gRPC server gives some time for the VNet admin // process to notice that the client is gone and shut down as well. if err := s.vnetService.Close(); err != nil { - log.WithError(err).Error("Error while closing VNet service") + slog.ErrorContext(context.Background(), "Error while closing VNet service", "error", err) } s.grpcServer.GracefulStop() @@ -120,7 +121,7 @@ func newListener(hostAddr string, listeningC chan<- utils.NetAddr) (net.Listener listeningC <- addr } - log.Infof("tsh daemon is listening on %v.", addr.FullAddress()) + slog.InfoContext(context.Background(), "tsh daemon listener created", "listen_addr", addr.FullAddress()) return lis, nil } diff --git a/lib/teleterm/apiserver/config.go b/lib/teleterm/apiserver/config.go index 76495b8a181b2..37e233a33f706 100644 --- a/lib/teleterm/apiserver/config.go +++ b/lib/teleterm/apiserver/config.go @@ -19,9 +19,10 @@ package apiserver import ( + "log/slog" + "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "google.golang.org/grpc" "github.com/gravitational/teleport" @@ -39,8 +40,8 @@ type Config struct { Daemon *daemon.Service ClusterIDCache *clusteridcache.Cache InstallationID string - // Log is a component logger - Log logrus.FieldLogger + // Logger is a component logger + Logger *slog.Logger TshdServerCreds grpc.ServerOption Clock clockwork.Clock // ListeningC propagates the address on which the gRPC server listens. Mostly useful in tests, as @@ -66,8 +67,8 @@ func (c *Config) CheckAndSetDefaults() error { return trace.BadParameter("missing TshdServerCreds") } - if c.Log == nil { - c.Log = logrus.WithField(teleport.ComponentKey, "conn:apiserver") + if c.Logger == nil { + c.Logger = slog.With(teleport.ComponentKey, "conn:apiserver") } if c.InstallationID == "" { diff --git a/lib/teleterm/apiserver/middleware.go b/lib/teleterm/apiserver/middleware.go index 7f8bb787a4038..520b97bb76565 100644 --- a/lib/teleterm/apiserver/middleware.go +++ b/lib/teleterm/apiserver/middleware.go @@ -20,14 +20,14 @@ package apiserver import ( "context" + "log/slog" "github.com/gravitational/trace/trail" - "github.com/sirupsen/logrus" "google.golang.org/grpc" ) // withErrorHandling is gRPC middleware that maps internal errors to proper gRPC error codes -func withErrorHandling(log logrus.FieldLogger) grpc.UnaryServerInterceptor { +func withErrorHandling(log *slog.Logger) grpc.UnaryServerInterceptor { return func( ctx context.Context, req interface{}, @@ -36,7 +36,7 @@ func withErrorHandling(log logrus.FieldLogger) grpc.UnaryServerInterceptor { ) (interface{}, error) { resp, err := handler(ctx, req) if err != nil { - log.WithError(err).Error("Request failed.") + log.ErrorContext(ctx, "Request failed", "error", err) return resp, trail.ToGRPC(err) } diff --git a/lib/teleterm/clusters/cluster.go b/lib/teleterm/clusters/cluster.go index 3899dc64fadff..ef8f22e461c86 100644 --- a/lib/teleterm/clusters/cluster.go +++ b/lib/teleterm/clusters/cluster.go @@ -20,10 +20,10 @@ package clusters import ( "context" + "log/slog" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/metadata" @@ -49,8 +49,8 @@ type Cluster struct { Name string // ProfileName is the name of the tsh profile ProfileName string - // Log is a component logger - Log *logrus.Entry + // Logger is a component logger + Logger *slog.Logger // dir is the directory where cluster certificates are stored dir string // Status is the cluster status @@ -192,9 +192,7 @@ func (c *Cluster) GetWithDetails(ctx context.Context, authClient authclient.Clie return roles, nil }) if err != nil { - c.Log. - WithError(err). - Warn("Failed to calculate trusted device requirement") + c.Logger.WarnContext(ctx, "Failed to calculate trusted device requirement", "error", err) } roleSet := services.NewRoleSet(roles...) diff --git a/lib/teleterm/clusters/cluster_auth.go b/lib/teleterm/clusters/cluster_auth.go index c8b8b4ebe1a40..e50256410ee48 100644 --- a/lib/teleterm/clusters/cluster_auth.go +++ b/lib/teleterm/clusters/cluster_auth.go @@ -22,10 +22,10 @@ import ( "context" "encoding/json" "errors" + "log/slog" "sort" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/api/constants" @@ -47,9 +47,9 @@ func (c *Cluster) SyncAuthPreference(ctx context.Context) (*webclient.WebConfigA } pingResponseJSON, err := json.Marshal(pingResponse) if err != nil { - c.Log.WithError(err).Debugln("Could not marshal ping response to JSON") + c.Logger.DebugContext(ctx, "Could not marshal ping response to JSON", "error", err) } else { - c.Log.WithField("response", string(pingResponseJSON)).Debugln("Got ping response") + c.Logger.DebugContext(ctx, "Got ping response", "response", string(pingResponseJSON)) } if err := c.clusterClient.SaveProfile(false); err != nil { @@ -227,7 +227,7 @@ func (c *Cluster) passwordlessLogin(stream api.TerminalService_LoginPasswordless response, err := client.SSHAgentPasswordlessLogin(ctx, client.SSHLoginPasswordless{ SSHLogin: sshLogin, AuthenticatorAttachment: c.clusterClient.AuthenticatorAttachment, - CustomPrompt: newPwdlessLoginPrompt(ctx, c.Log, stream), + CustomPrompt: newPwdlessLoginPrompt(ctx, c.Logger, stream), WebauthnLogin: c.clusterClient.WebauthnLogin, }) if err != nil { @@ -239,11 +239,11 @@ func (c *Cluster) passwordlessLogin(stream api.TerminalService_LoginPasswordless // pwdlessLoginPrompt is a implementation for wancli.LoginPrompt for teleterm passwordless logins. type pwdlessLoginPrompt struct { - log *logrus.Entry + log *slog.Logger Stream api.TerminalService_LoginPasswordlessServer } -func newPwdlessLoginPrompt(ctx context.Context, log *logrus.Entry, stream api.TerminalService_LoginPasswordlessServer) *pwdlessLoginPrompt { +func newPwdlessLoginPrompt(ctx context.Context, log *slog.Logger, stream api.TerminalService_LoginPasswordlessServer) *pwdlessLoginPrompt { return &pwdlessLoginPrompt{ log: log, Stream: stream, @@ -283,7 +283,7 @@ func (p *pwdlessLoginPrompt) ackTouch() error { // The current gRPC message type switch in teleterm client code will reject // any new message types, making this difficult to add without breaking // older clients. - p.log.Debug("Detected security key tap") + p.log.DebugContext(context.Background(), "Detected security key tap") return nil } diff --git a/lib/teleterm/clusters/cluster_auth_test.go b/lib/teleterm/clusters/cluster_auth_test.go index f9c7cd8e2c4ea..36165f053c1eb 100644 --- a/lib/teleterm/clusters/cluster_auth_test.go +++ b/lib/teleterm/clusters/cluster_auth_test.go @@ -20,20 +20,17 @@ package clusters import ( "context" + "log/slog" "testing" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "google.golang.org/grpc" - "github.com/gravitational/teleport" api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1" wancli "github.com/gravitational/teleport/lib/auth/webauthncli" ) -var log = logrus.WithField(teleport.ComponentKey, "cluster_auth_test") - func TestPwdlessLoginPrompt_PromptPIN(t *testing.T) { stream := &mockLoginPwdlessStream{} @@ -49,7 +46,7 @@ func TestPwdlessLoginPrompt_PromptPIN(t *testing.T) { }}, nil } - prompt := newPwdlessLoginPrompt(context.Background(), log, stream) + prompt := newPwdlessLoginPrompt(context.Background(), slog.Default(), stream) pin, err := prompt.PromptPIN() require.NoError(t, err) require.Equal(t, "1234", pin) @@ -74,7 +71,7 @@ func TestPwdlessLoginPrompt_PromptTouch(t *testing.T) { return nil } - prompt := newPwdlessLoginPrompt(context.Background(), log, stream) + prompt := newPwdlessLoginPrompt(context.Background(), slog.Default(), stream) ackTouch, err := prompt.PromptTouch() require.NoError(t, err) require.NoError(t, ackTouch()) @@ -110,7 +107,7 @@ func TestPwdlessLoginPrompt_PromptCredential(t *testing.T) { }}, nil } - prompt := newPwdlessLoginPrompt(context.Background(), log, stream) + prompt := newPwdlessLoginPrompt(context.Background(), slog.Default(), stream) cred, err := prompt.PromptCredential(unsortedCreds) require.NoError(t, err) require.Equal(t, "foo", cred.User.Name) diff --git a/lib/teleterm/clusters/cluster_gateways.go b/lib/teleterm/clusters/cluster_gateways.go index 590fa27611f21..64577c35cf7dd 100644 --- a/lib/teleterm/clusters/cluster_gateways.go +++ b/lib/teleterm/clusters/cluster_gateways.go @@ -105,7 +105,7 @@ func (c *Cluster) createDBGateway(ctx context.Context, params CreateGatewayParam Cert: cert, Insecure: c.clusterClient.InsecureSkipVerify, WebProxyAddr: c.clusterClient.WebProxyAddr, - Log: c.Log, + Logger: c.Logger, TCPPortAllocator: params.TCPPortAllocator, OnExpiredCert: params.OnExpiredCert, Clock: c.clock, @@ -145,7 +145,7 @@ func (c *Cluster) createKubeGateway(ctx context.Context, params CreateGatewayPar Cert: cert, Insecure: c.clusterClient.InsecureSkipVerify, WebProxyAddr: c.clusterClient.WebProxyAddr, - Log: c.Log, + Logger: c.Logger, TCPPortAllocator: params.TCPPortAllocator, OnExpiredCert: params.OnExpiredCert, Clock: c.clock, @@ -187,7 +187,7 @@ func (c *Cluster) createAppGateway(ctx context.Context, params CreateGatewayPara Protocol: app.GetProtocol(), Insecure: c.clusterClient.InsecureSkipVerify, WebProxyAddr: c.clusterClient.WebProxyAddr, - Log: c.Log, + Logger: c.Logger, TCPPortAllocator: params.TCPPortAllocator, OnExpiredCert: params.OnExpiredCert, Clock: c.clock, diff --git a/lib/teleterm/clusters/config.go b/lib/teleterm/clusters/config.go index 6af0ad1bbfad3..ff94f4fdb533a 100644 --- a/lib/teleterm/clusters/config.go +++ b/lib/teleterm/clusters/config.go @@ -19,9 +19,10 @@ package clusters import ( + "log/slog" + "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/utils/keys" @@ -37,8 +38,8 @@ type Config struct { Clock clockwork.Clock // InsecureSkipVerify is an option to skip TLS cert check InsecureSkipVerify bool - // Log is a component logger - Log *logrus.Entry + // Logger is a component logger + Logger *slog.Logger // WebauthnLogin allows tests to override the Webauthn Login func. // Defaults to wancli.Login. WebauthnLogin client.WebauthnLoginFunc @@ -63,8 +64,8 @@ func (c *Config) CheckAndSetDefaults() error { c.Clock = clockwork.NewRealClock() } - if c.Log == nil { - c.Log = logrus.WithField(teleport.ComponentKey, "conn:storage") + if c.Logger == nil { + c.Logger = slog.With(teleport.ComponentKey, "conn:storage") } if c.AddKeysToAgent == "" { diff --git a/lib/teleterm/clusters/storage.go b/lib/teleterm/clusters/storage.go index f00adfc73c15c..7d5b1292aa25e 100644 --- a/lib/teleterm/clusters/storage.go +++ b/lib/teleterm/clusters/storage.go @@ -179,13 +179,13 @@ func (s *Storage) addCluster(ctx context.Context, dir, webProxyAddress string) ( return nil, nil, trace.Wrap(err) } - clusterLog := s.Log.WithField("cluster", clusterURI) + clusterLog := s.Logger.With("cluster", clusterURI) pingResponseJSON, err := json.Marshal(pingResponse) if err != nil { - clusterLog.WithError(err).Debugln("Could not marshal ping response to JSON") + clusterLog.DebugContext(ctx, "Could not marshal ping response to JSON", "error", err) } else { - clusterLog.WithField("response", string(pingResponseJSON)).Debugln("Got ping response") + clusterLog.DebugContext(ctx, "Got ping response", "response", string(pingResponseJSON)) } if err := clusterClient.SaveProfile(false); err != nil { @@ -201,7 +201,7 @@ func (s *Storage) addCluster(ctx context.Context, dir, webProxyAddress string) ( clusterClient: clusterClient, dir: s.Dir, clock: s.Clock, - Log: clusterLog, + Logger: clusterLog, }, clusterClient, nil } @@ -241,7 +241,7 @@ func (s *Storage) fromProfile(profileName, leafClusterName string) (*Cluster, *c dir: s.Dir, clock: s.Clock, statusError: err, - Log: s.Log.WithField("cluster", clusterURI), + Logger: s.Logger.With("cluster", clusterURI), } if status != nil { cluster.status = *status @@ -258,7 +258,7 @@ func (s *Storage) loadProfileStatusAndClusterKey(clusterClient *client.TeleportC _, err := clusterClient.LocalAgent().GetKeyRing(clusterNameForKey) if err != nil { if trace.IsNotFound(err) { - s.Log.Infof("No keys found for cluster %v.", clusterNameForKey) + s.Logger.InfoContext(context.Background(), "No keys found for cluster", "cluster", clusterNameForKey) } else { return nil, trace.Wrap(err) } diff --git a/lib/teleterm/cmd/db_test.go b/lib/teleterm/cmd/db_test.go index cd165b850cdc4..68f7a82fdcec4 100644 --- a/lib/teleterm/cmd/db_test.go +++ b/lib/teleterm/cmd/db_test.go @@ -21,12 +21,12 @@ package cmd import ( "context" "fmt" + "log/slog" "os/exec" "path/filepath" "testing" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "github.com/gravitational/teleport/api/client/proto" @@ -66,7 +66,7 @@ func (m fakeDatabaseGateway) TargetName() string { return m.targetURI func (m fakeDatabaseGateway) TargetUser() string { return "alice" } func (m fakeDatabaseGateway) TargetSubresourceName() string { return m.subresourceName } func (m fakeDatabaseGateway) Protocol() string { return m.protocol } -func (m fakeDatabaseGateway) Log() *logrus.Entry { return nil } +func (m fakeDatabaseGateway) Log() *slog.Logger { return nil } func (m fakeDatabaseGateway) LocalAddress() string { return "localhost" } func (m fakeDatabaseGateway) LocalPortInt() int { return 8888 } func (m fakeDatabaseGateway) LocalPort() string { return "8888" } diff --git a/lib/teleterm/daemon/config.go b/lib/teleterm/daemon/config.go index 80cc79d081946..3646b78f05e2b 100644 --- a/lib/teleterm/daemon/config.go +++ b/lib/teleterm/daemon/config.go @@ -20,10 +20,10 @@ package daemon import ( "context" + "log/slog" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "google.golang.org/grpc" "github.com/gravitational/teleport" @@ -52,8 +52,8 @@ type Config struct { Clock clockwork.Clock // Storage is a storage service that reads/writes to tsh profiles Storage Storage - // Log is a component logger - Log *logrus.Entry + // Logger is a component logger + Logger *slog.Logger // PrehogAddr is the URL where prehog events should be submitted. PrehogAddr string // KubeconfigsDir is the directory containing kubeconfigs for Kubernetes @@ -121,8 +121,8 @@ func (c *Config) CheckAndSetDefaults() error { c.GatewayCreator = clusters.NewGatewayCreator(c.Storage) } - if c.Log == nil { - c.Log = logrus.NewEntry(logrus.StandardLogger()).WithField(teleport.ComponentKey, "daemon") + if c.Logger == nil { + c.Logger = slog.With(teleport.ComponentKey, "daemon") } if c.ConnectMyComputerRoleSetup == nil { @@ -172,7 +172,7 @@ func (c *Config) CheckAndSetDefaults() error { return clusters.AddMetadataToRetryableError(ctx, fn) } return clientcache.New(clientcache.Config{ - Log: c.Log, + Logger: c.Logger, NewClientFunc: newClientFunc, RetryWithReloginFunc: clientcache.RetryWithReloginFunc(retryWithRelogin), }) diff --git a/lib/teleterm/daemon/daemon.go b/lib/teleterm/daemon/daemon.go index 13f12f4dfa253..d3528793a4b99 100644 --- a/lib/teleterm/daemon/daemon.go +++ b/lib/teleterm/daemon/daemon.go @@ -357,7 +357,7 @@ func (s *Service) createGateway(ctx context.Context, params CreateGatewayParams) go func() { if err := gateway.Serve(); err != nil { - gateway.Log().WithError(err).Warn("Failed to handle a gateway connection.") + gateway.Log().WarnContext(ctx, "Failed to handle a gateway connection", "error", err) } }() @@ -416,7 +416,7 @@ func (s *Service) reissueGatewayCerts(ctx context.Context, g gateway.Gateway) (t }, }) if notifyErr != nil { - s.cfg.Log.WithError(notifyErr).Error("Failed to send a notification for an error encountered during gateway cert reissue") + s.cfg.Logger.ErrorContext(ctx, "Failed to send a notification for an error encountered during gateway cert reissue", "error", notifyErr) } // Return the error to the alpn.LocalProxy's middleware. @@ -559,9 +559,9 @@ func (s *Service) SetGatewayLocalPort(gatewayURI, localPort string) (gateway.Gat // Rather than continuing in presence of the race condition, let's attempt to close the new // gateway (since it shouldn't be used anyway) and return the error. if newGatewayCloseErr := newGateway.Close(); newGatewayCloseErr != nil { - newGateway.Log().Warnf( - "Failed to close the new gateway after failing to close the old gateway: %v", - newGatewayCloseErr, + newGateway.Log().WarnContext(s.closeContext, + "Failed to close the new gateway after failing to close the old gateway", + "error", newGatewayCloseErr, ) } return nil, trace.Wrap(err) @@ -571,7 +571,7 @@ func (s *Service) SetGatewayLocalPort(gatewayURI, localPort string) (gateway.Gat go func() { if err := newGateway.Serve(); err != nil { - newGateway.Log().WithError(err).Warn("Failed to handle a gateway connection.") + newGateway.Log().WarnContext(s.closeContext, "Failed to handle a gateway connection", "error", err) } }() @@ -842,7 +842,7 @@ func (s *Service) Stop() { s.mu.RLock() defer s.mu.RUnlock() - s.cfg.Log.Info("Stopping") + s.cfg.Logger.InfoContext(s.closeContext, "Stopping") for _, gateway := range s.gateways { gateway.Close() @@ -851,14 +851,14 @@ func (s *Service) Stop() { s.StopHeadlessWatchers() if err := s.clientCache.Clear(); err != nil { - s.cfg.Log.WithError(err).Error("Failed to close remote clients") + s.cfg.Logger.ErrorContext(s.closeContext, "Failed to close remote clients", "error", err) } timeoutCtx, cancel := context.WithTimeout(s.closeContext, time.Second*10) defer cancel() if err := s.usageReporter.GracefulStop(timeoutCtx); err != nil { - s.cfg.Log.WithError(err).Error("Gracefully stopping usage reporter failed") + s.cfg.Logger.ErrorContext(timeoutCtx, "Gracefully stopping usage reporter failed", "error", err) } // s.closeContext is used for the tshd events client which might make requests as long as any of diff --git a/lib/teleterm/daemon/daemon_headless.go b/lib/teleterm/daemon/daemon_headless.go index 9ddf02a52405c..310b853d229c3 100644 --- a/lib/teleterm/daemon/daemon_headless.go +++ b/lib/teleterm/daemon/daemon_headless.go @@ -30,6 +30,7 @@ import ( api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/teleterm/clusters" + logutils "github.com/gravitational/teleport/lib/utils/log" ) // UpdateHeadlessAuthenticationState updates a headless authentication state. @@ -96,7 +97,7 @@ func (s *Service) startHeadlessWatcher(rootCluster *clusters.Cluster, waitInit b watchCtx, watchCancel := context.WithCancel(s.closeContext) s.headlessWatcherClosers[rootCluster.URI.String()] = watchCancel - log := s.cfg.Log.WithField("cluster", rootCluster.URI.String()) + log := s.cfg.Logger.With("cluster", logutils.StringerAttr(rootCluster.URI)) pendingRequests := make(map[string]context.CancelFunc) pendingRequestsMu := sync.Mutex{} @@ -180,7 +181,7 @@ func (s *Service) startHeadlessWatcher(rootCluster *clusters.Cluster, waitInit b defer cancelSend() if err := s.sendPendingHeadlessAuthentication(sendCtx, ha, rootCluster.URI.String()); err != nil { if !strings.Contains(err.Error(), context.Canceled.Error()) && !strings.Contains(err.Error(), context.DeadlineExceeded.Error()) { - log.WithError(err).Debug("sendPendingHeadlessAuthentication resulted in unexpected error.") + log.DebugContext(sendCtx, "sendPendingHeadlessAuthentication resulted in unexpected error", "error", err) } } }() @@ -210,7 +211,7 @@ func (s *Service) startHeadlessWatcher(rootCluster *clusters.Cluster, waitInit b } } - log.Debugf("Starting headless watch loop.") + log.DebugContext(watchCtx, "Starting headless watch loop") go func() { defer func() { s.headlessWatcherClosersMu.Lock() @@ -222,31 +223,36 @@ func (s *Service) startHeadlessWatcher(rootCluster *clusters.Cluster, waitInit b default: // watcher closed due to error or cluster disconnect. if err := s.stopHeadlessWatcher(rootCluster.URI.String()); err != nil { - log.WithError(err).Debug("Failed to remove headless watcher.") + log.DebugContext(watchCtx, "Failed to remove headless watcher", "error", err) } } }() for { if !rootCluster.Connected() { - log.Debugf("Not connected to cluster. Returning from headless watch loop.") + log.DebugContext(watchCtx, "Not connected to cluster, terminating headless watch loop") return } err := watch() if trace.IsNotImplemented(err) { // Don't retry watch if we are connecting to an old Auth Server. - log.WithError(err).Debug("Headless watcher not supported.") + log.DebugContext(watchCtx, "Headless watcher not supported", "error", err) return } startedWaiting := s.cfg.Clock.Now() select { case t := <-retry.After(): - log.WithError(err).Debugf("Restarting watch on error after waiting %v.", t.Sub(startedWaiting)) + log.DebugContext(watchCtx, "Restarting watch on error", + "backoff", t.Sub(startedWaiting), + "error", err, + ) retry.Inc() case <-watchCtx.Done(): - log.WithError(watchCtx.Err()).Debugf("Context closed with err. Returning from headless watch loop.") + log.DebugContext(watchCtx, "Context closed with error, ending headless watch loop", + "error", watchCtx.Err(), + ) return } } @@ -295,7 +301,10 @@ func (s *Service) StopHeadlessWatchers() { for uri := range s.headlessWatcherClosers { if err := s.stopHeadlessWatcher(uri); err != nil { - s.cfg.Log.WithField("cluster", uri).WithError(err).Debug("Encountered unexpected error closing headless watcher") + s.cfg.Logger.DebugContext(s.closeContext, "Encountered unexpected error closing headless watcher", + "error", err, + "cluster", uri, + ) } } } diff --git a/lib/teleterm/daemon/daemon_test.go b/lib/teleterm/daemon/daemon_test.go index 9c46057f7ac30..6f5670d61fe57 100644 --- a/lib/teleterm/daemon/daemon_test.go +++ b/lib/teleterm/daemon/daemon_test.go @@ -21,6 +21,7 @@ package daemon import ( "context" "errors" + "log/slog" "net" "net/http" "net/http/httptest" @@ -30,7 +31,6 @@ import ( "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc" @@ -726,7 +726,7 @@ func (m fakeGateway) TargetName() string { return m.targetURI.GetDbNa func (m fakeGateway) TargetUser() string { return "alice" } func (m fakeGateway) TargetSubresourceName() string { return m.subresourceName } func (m fakeGateway) Protocol() string { return defaults.ProtocolSQLServer } -func (m fakeGateway) Log() *logrus.Entry { return nil } +func (m fakeGateway) Log() *slog.Logger { return nil } func (m fakeGateway) LocalAddress() string { return "localhost" } func (m fakeGateway) LocalPortInt() int { return 8888 } func (m fakeGateway) LocalPort() string { return "8888" } diff --git a/lib/teleterm/gateway/app.go b/lib/teleterm/gateway/app.go index 603d640a05a9c..110d36604aeff 100644 --- a/lib/teleterm/gateway/app.go +++ b/lib/teleterm/gateway/app.go @@ -56,7 +56,7 @@ func makeAppGateway(cfg Config) (Gateway, error) { } middleware := &appMiddleware{ - log: a.cfg.Log, + logger: a.cfg.Logger, onExpiredCert: func(ctx context.Context) (tls.Certificate, error) { cert, err := a.cfg.OnExpiredCert(ctx, a) return cert, trace.Wrap(err) diff --git a/lib/teleterm/gateway/app_middleware.go b/lib/teleterm/gateway/app_middleware.go index 8af69271ade03..9b58de8624016 100644 --- a/lib/teleterm/gateway/app_middleware.go +++ b/lib/teleterm/gateway/app_middleware.go @@ -21,16 +21,16 @@ import ( "crypto/tls" "crypto/x509" "errors" + "log/slog" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" alpn "github.com/gravitational/teleport/lib/srv/alpnproxy" ) type appMiddleware struct { onExpiredCert func(context.Context) (tls.Certificate, error) - log *logrus.Entry + logger *slog.Logger } // OnNewConnection calls m.onExpiredCert to get a fresh cert if the cert has expired and then sets @@ -48,7 +48,7 @@ func (m *appMiddleware) OnNewConnection(ctx context.Context, lp *alpn.LocalProxy return trace.Wrap(err) } - m.log.WithError(err).Debug("Gateway certificates have expired") + m.logger.DebugContext(ctx, "Gateway certificates have expired", "error", err) cert, err := m.onExpiredCert(ctx) if err != nil { diff --git a/lib/teleterm/gateway/base.go b/lib/teleterm/gateway/base.go index e0a9a33cc4d86..3a8b076307c60 100644 --- a/lib/teleterm/gateway/base.go +++ b/lib/teleterm/gateway/base.go @@ -21,11 +21,11 @@ package gateway import ( "context" "fmt" + "log/slog" "net" "strconv" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" alpn "github.com/gravitational/teleport/lib/srv/alpnproxy" "github.com/gravitational/teleport/lib/teleterm/api/uri" @@ -107,8 +107,8 @@ func (b *base) Close() error { // Serve starts the underlying ALPN proxy. Blocks until closeContext is canceled. func (b *base) Serve() error { - b.cfg.Log.Info("Gateway is open.") - defer b.cfg.Log.Info("Gateway has closed.") + b.cfg.Logger.InfoContext(b.closeContext, "Gateway is open") + defer b.cfg.Logger.InfoContext(b.closeContext, "Gateway has closed") if b.forwardProxy != nil { return trace.Wrap(b.serveWithForwardProxy()) @@ -165,8 +165,8 @@ func (b *base) SetTargetSubresourceName(value string) { b.cfg.TargetSubresourceName = value } -func (b *base) Log() *logrus.Entry { - return b.cfg.Log +func (b *base) Log() *slog.Logger { + return b.cfg.Logger } func (b *base) LocalAddress() string { diff --git a/lib/teleterm/gateway/config.go b/lib/teleterm/gateway/config.go index c870df9075728..67768d05900db 100644 --- a/lib/teleterm/gateway/config.go +++ b/lib/teleterm/gateway/config.go @@ -22,13 +22,13 @@ import ( "context" "crypto/tls" "crypto/x509" + "log/slog" "net" "runtime" "github.com/google/uuid" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/lib/defaults" @@ -69,8 +69,8 @@ type Config struct { Username string // WebProxyAddr WebProxyAddr string - // Log is a component logger - Log *logrus.Entry + // Logger is a component logger + Logger *slog.Logger // TCPPortAllocator creates listeners on the given ports. This interface lets us avoid occupying // hardcoded ports in tests. TCPPortAllocator TCPPortAllocator @@ -125,8 +125,8 @@ func (c *Config) CheckAndSetDefaults() error { c.LocalPort = "0" } - if c.Log == nil { - c.Log = logrus.NewEntry(logrus.StandardLogger()) + if c.Logger == nil { + c.Logger = slog.Default() } if c.TargetName == "" { @@ -154,10 +154,10 @@ func (c *Config) CheckAndSetDefaults() error { } } - c.Log = c.Log.WithFields(logrus.Fields{ - "resource": c.TargetURI.String(), - "gateway": c.URI.String(), - }) + c.Logger = c.Logger.With( + "resource", c.TargetURI.String(), + "gateway", c.URI.String(), + ) return nil } diff --git a/lib/teleterm/gateway/db.go b/lib/teleterm/gateway/db.go index a6b25e685b9c8..b1602d0648b08 100644 --- a/lib/teleterm/gateway/db.go +++ b/lib/teleterm/gateway/db.go @@ -54,7 +54,7 @@ func makeDatabaseGateway(cfg Config) (Database, error) { } middleware := &dbMiddleware{ - log: d.cfg.Log, + logger: d.cfg.Logger, onExpiredCert: func(ctx context.Context) (tls.Certificate, error) { cert, err := d.cfg.OnExpiredCert(ctx, d) return cert, trace.Wrap(err) diff --git a/lib/teleterm/gateway/db_middleware.go b/lib/teleterm/gateway/db_middleware.go index cd189fff048a0..110f6969d41a8 100644 --- a/lib/teleterm/gateway/db_middleware.go +++ b/lib/teleterm/gateway/db_middleware.go @@ -23,9 +23,9 @@ import ( "crypto/tls" "crypto/x509" "errors" + "log/slog" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" alpn "github.com/gravitational/teleport/lib/srv/alpnproxy" "github.com/gravitational/teleport/lib/tlsca" @@ -33,7 +33,7 @@ import ( type dbMiddleware struct { onExpiredCert func(context.Context) (tls.Certificate, error) - log *logrus.Entry + logger *slog.Logger dbRoute tlsca.RouteToDatabase } @@ -54,7 +54,7 @@ func (m *dbMiddleware) OnNewConnection(ctx context.Context, lp *alpn.LocalProxy) return trace.Wrap(err) } - m.log.WithError(err).Debug("Gateway certificates have expired") + m.logger.DebugContext(ctx, "Gateway certificates have expired", "error", err) cert, err := m.onExpiredCert(ctx) if err != nil { diff --git a/lib/teleterm/gateway/db_middleware_test.go b/lib/teleterm/gateway/db_middleware_test.go index cecf12306de2e..1f786a5d8226c 100644 --- a/lib/teleterm/gateway/db_middleware_test.go +++ b/lib/teleterm/gateway/db_middleware_test.go @@ -21,11 +21,11 @@ package gateway import ( "context" "crypto/tls" + "log/slog" "testing" "time" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "github.com/gravitational/teleport" @@ -112,7 +112,7 @@ func TestDBMiddleware_OnNewConnection(t *testing.T) { hasCalledOnExpiredCert = true return tls.Certificate{}, nil }, - log: logrus.WithField(teleport.ComponentKey, "middleware"), + logger: slog.With(teleport.ComponentKey, "middleware"), dbRoute: tt.dbRoute, } diff --git a/lib/teleterm/gateway/interfaces.go b/lib/teleterm/gateway/interfaces.go index 4cedf02e7ffd7..27bc6735a2b9d 100644 --- a/lib/teleterm/gateway/interfaces.go +++ b/lib/teleterm/gateway/interfaces.go @@ -19,8 +19,9 @@ package gateway import ( + "log/slog" + "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/lib/teleterm/api/uri" "github.com/gravitational/teleport/lib/tlsca" @@ -41,7 +42,7 @@ type Gateway interface { TargetUser() string TargetSubresourceName() string SetTargetSubresourceName(value string) - Log() *logrus.Entry + Log() *slog.Logger LocalAddress() string LocalPort() string LocalPortInt() int diff --git a/lib/teleterm/grpccredentials.go b/lib/teleterm/grpccredentials.go index f0c7c7562927f..8228f2e7ae631 100644 --- a/lib/teleterm/grpccredentials.go +++ b/lib/teleterm/grpccredentials.go @@ -21,11 +21,11 @@ package teleterm import ( "crypto/tls" "crypto/x509" + "log/slog" "os" "path/filepath" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -65,21 +65,21 @@ func createServerCredentials(serverKeyPair tls.Certificate, clientCertPaths []st Certificates: []tls.Certificate{serverKeyPair}, } - config.GetConfigForClient = func(_ *tls.ClientHelloInfo) (*tls.Config, error) { + config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) { certPool := x509.NewCertPool() for _, clientCertPath := range clientCertPaths { - log := log.WithField("cert_path", clientCertPath) + log := slog.With("cert_path", clientCertPath) clientCert, err := os.ReadFile(clientCertPath) if err != nil { - log.WithError(err).Error("Failed to read the client cert file") + log.ErrorContext(info.Context(), "Failed to read the client cert file", "error", err) // Fall back to the default config. return nil, nil } if !certPool.AppendCertsFromPEM(clientCert) { - log.Error("Failed to add the client cert to the pool") + log.ErrorContext(info.Context(), "Failed to add the client cert to the pool") // Fall back to the default config. return nil, nil } diff --git a/lib/teleterm/services/connectmycomputer/connectmycomputer.go b/lib/teleterm/services/connectmycomputer/connectmycomputer.go index 26ecc8aafe8d9..d26e17621c9b5 100644 --- a/lib/teleterm/services/connectmycomputer/connectmycomputer.go +++ b/lib/teleterm/services/connectmycomputer/connectmycomputer.go @@ -21,6 +21,7 @@ package connectmycomputer import ( "context" "fmt" + "log/slog" "os" "os/user" "path/filepath" @@ -31,7 +32,6 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport" apidefaults "github.com/gravitational/teleport/api/defaults" @@ -105,8 +105,9 @@ func (s *RoleSetup) Run(ctx context.Context, accessAndIdentity AccessAndIdentity reloadCerts := false + logger := s.cfg.Logger.With("role", roleName) if !doesRoleExist { - s.cfg.Log.Infof("Creating the role %v.", roleName) + logger.InfoContext(ctx, "Creating the role") role, err := types.NewRole(roleName, types.RoleSpecV6{ Allow: types.RoleConditions{ @@ -123,7 +124,7 @@ func (s *RoleSetup) Run(ctx context.Context, accessAndIdentity AccessAndIdentity return noCertsReloaded, trace.Wrap(err, "creating role %v", roleName) } } else { - s.cfg.Log.Infof("The role %v already exists", roleName) + logger.InfoContext(ctx, "The role already exists") isRoleDirty := false // Ensure that the current system username is in the role. @@ -134,7 +135,9 @@ func (s *RoleSetup) Run(ctx context.Context, accessAndIdentity AccessAndIdentity allowedLogins := existingRole.GetLogins(types.Allow) if !slices.Contains(allowedLogins, systemUser.Username) { - s.cfg.Log.Infof("Adding %v to the logins of the role %v.", systemUser.Username, roleName) + logger.InfoContext(ctx, "Adding username to the logins of the role", + "username", systemUser.Username, + ) existingRole.SetLogins(types.Allow, append(allowedLogins, systemUser.Username)) isRoleDirty = true @@ -156,7 +159,7 @@ func (s *RoleSetup) Run(ctx context.Context, accessAndIdentity AccessAndIdentity expectedOwnerNodeLabelValue := []string{clusterUser.GetName()} if !slices.Equal(ownerNodeLabelValue, expectedOwnerNodeLabelValue) { - s.cfg.Log.Infof("Overwriting the owner node label in the role %v.", roleName) + logger.InfoContext(ctx, "Overwriting the owner node label in the role") allowedNodeLabels[types.ConnectMyComputerNodeOwnerLabel] = expectedOwnerNodeLabelValue isRoleDirty = true @@ -178,9 +181,9 @@ func (s *RoleSetup) Run(ctx context.Context, accessAndIdentity AccessAndIdentity hasCMCRole := slices.Contains(clusterUser.GetRoles(), roleName) if hasCMCRole { - s.cfg.Log.Infof("The user %v already has the role %v.", clusterUser.GetName(), roleName) + logger.InfoContext(ctx, "The user already has the role", "user", clusterUser.GetName()) } else { - s.cfg.Log.Infof("Adding the role %v to the user %v.", roleName, clusterUser.GetName()) + logger.InfoContext(ctx, "Adding the role to the user", "user", clusterUser.GetName()) clusterUser.AddRole(roleName) timeoutCtx, cancel := context.WithTimeout(ctx, resourceUpdateTimeout) defer cancel() @@ -197,7 +200,7 @@ func (s *RoleSetup) Run(ctx context.Context, accessAndIdentity AccessAndIdentity } if reloadCerts { - s.cfg.Log.Info("Reissuing certs.") + s.cfg.Logger.InfoContext(ctx, "Reissuing certs") // ReissueUserCerts called with CertCacheDrop and a bogus access request ID in DropAccessRequests // allows us to refresh the role list in the certs without forcing the user to relogin. // @@ -273,12 +276,12 @@ type CertManager interface { } type RoleSetupConfig struct { - Log *logrus.Entry + Logger *slog.Logger } func (c *RoleSetupConfig) CheckAndSetDefaults() error { - if c.Log == nil { - c.Log = logrus.NewEntry(logrus.StandardLogger()).WithField(teleport.ComponentKey, "CMC role") + if c.Logger == nil { + c.Logger = slog.With(teleport.ComponentKey, "CMC role") } return nil diff --git a/lib/teleterm/teleterm.go b/lib/teleterm/teleterm.go index c8bc92b4e0e8a..0c6cde8efd031 100644 --- a/lib/teleterm/teleterm.go +++ b/lib/teleterm/teleterm.go @@ -20,6 +20,7 @@ package teleterm import ( "context" + "log/slog" "os" "os/signal" "path/filepath" @@ -28,7 +29,6 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - log "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -112,9 +112,9 @@ func Serve(ctx context.Context, cfg Config) error { select { case <-ctx.Done(): - log.Info("Context closed, stopping service.") + slog.InfoContext(ctx, "Context closed, stopping service") case sig := <-c: - log.Infof("Captured %s, stopping service.", sig) + slog.InfoContext(ctx, "Captured signal, stopping service", "signal", sig) } daemonService.Stop() diff --git a/lib/utils/addr.go b/lib/utils/addr.go index b0ab5e4c4e258..3ed10bec068b5 100644 --- a/lib/utils/addr.go +++ b/lib/utils/addr.go @@ -19,7 +19,9 @@ package utils import ( + "context" "fmt" + "log/slog" "net" "net/url" "strconv" @@ -27,7 +29,6 @@ import ( "unicode/utf8" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" apiutils "github.com/gravitational/teleport/api/utils" ) @@ -290,7 +291,7 @@ func GuessHostIP() (ip net.IP, err error) { for _, iface := range ifaces { ifadrs, err := iface.Addrs() if err != nil { - log.Warn(err) + slog.WarnContext(context.Background(), "Unable to get addresses for interface", "interface", iface.Name, "error", err) } else { adrs = append(adrs, ifadrs...) } diff --git a/lib/utils/agentconn/agent_windows.go b/lib/utils/agentconn/agent_windows.go index d00eb81637ab2..850f791eb044a 100644 --- a/lib/utils/agentconn/agent_windows.go +++ b/lib/utils/agentconn/agent_windows.go @@ -22,8 +22,10 @@ package agentconn import ( + "context" "encoding/binary" "encoding/hex" + "log/slog" "net" "os" "os/exec" @@ -33,7 +35,6 @@ import ( "github.com/Microsoft/go-winio" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" apiutils "github.com/gravitational/teleport/api/utils" ) @@ -214,7 +215,7 @@ func getCygwinUIDFromPS() (uint32, error) { // preform a successful handshake with it. Handshake details here: // https://stackoverflow.com/questions/23086038/what-mechanism-is-used-by-msys-cygwin-to-emulate-unix-domain-sockets func attemptCygwinHandshake(port, key string, uid uint32) (net.Conn, error) { - logrus.Debugf("[KEY AGENT] attempting a handshake with Cygwin ssh-agent socket; port=%s uid=%d", port, uid) + slog.DebugContext(context.Background(), "[KEY AGENT] attempting a handshake with Cygwin ssh-agent socket", "port", port, "uid", uid) conn, err := net.Dial("tcp", "localhost:"+port) if err != nil { diff --git a/lib/utils/aws/credentials.go b/lib/utils/aws/credentials.go index 257d6606d42a9..47c3105174943 100644 --- a/lib/utils/aws/credentials.go +++ b/lib/utils/aws/credentials.go @@ -20,6 +20,7 @@ package aws import ( "context" + "log/slog" "sort" "strings" "time" @@ -33,7 +34,6 @@ import ( "github.com/aws/aws-sdk-go/service/sts" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/utils" @@ -70,8 +70,11 @@ func NewCredentialsGetter() CredentialsGetter { } // Get obtains STS credentials. -func (g *credentialsGetter) Get(_ context.Context, request GetCredentialsRequest) (*credentials.Credentials, error) { - logrus.Debugf("Creating STS session %q for %q.", request.SessionName, request.RoleARN) +func (g *credentialsGetter) Get(ctx context.Context, request GetCredentialsRequest) (*credentials.Credentials, error) { + slog.DebugContext(ctx, "Creating STS session", + "session_name", request.SessionName, + "role_arn", request.RoleARN, + ) return stscreds.NewCredentials(request.Provider, request.RoleARN, func(cred *stscreds.AssumeRoleProvider) { cred.RoleSessionName = MaybeHashRoleSessionName(request.SessionName) diff --git a/lib/utils/config.go b/lib/utils/config.go index 2a68f2efdce6c..969ccb8a8508b 100644 --- a/lib/utils/config.go +++ b/lib/utils/config.go @@ -19,12 +19,13 @@ package utils import ( + "context" + "log/slog" "os" "path/filepath" "strings" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" ) // TryReadValueAsFile is a utility function to read a value @@ -44,7 +45,7 @@ func TryReadValueAsFile(value string) (string, error) { out := strings.TrimSpace(string(contents)) if out == "" { - log.Warnf("Empty config value file: %v", value) + slog.WarnContext(context.Background(), "Empty config value file", "file", value) } return out, nil } diff --git a/lib/utils/diagnostics/latency/monitor.go b/lib/utils/diagnostics/latency/monitor.go index bbd2e98fa0782..cdfe08248c1f2 100644 --- a/lib/utils/diagnostics/latency/monitor.go +++ b/lib/utils/diagnostics/latency/monitor.go @@ -20,19 +20,20 @@ package latency import ( "context" + "errors" "sync/atomic" "time" "github.com/google/uuid" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/utils/retryutils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) -var log = logrus.WithField(teleport.ComponentKey, "latency") +var logger = logutils.NewPackageLogger(teleport.ComponentKey, "latency") // Statistics contain latency measurements for both // legs of a proxied connection. @@ -188,8 +189,8 @@ func (m *Monitor) Run(ctx context.Context) { for { select { case <-m.reportTimer.Chan(): - if err := m.reporter.Report(ctx, m.GetStats()); err != nil { - log.WithError(err).Warn("failed to report latency stats") + if err := m.reporter.Report(ctx, m.GetStats()); err != nil && !errors.Is(err, context.Canceled) { + logger.WarnContext(ctx, "failed to report latency stats", "error", err) } m.reportTimer.Reset(retryutils.SeventhJitter(m.reportInterval)) case <-ctx.Done(): @@ -205,8 +206,8 @@ func (m *Monitor) pingLoop(ctx context.Context, pinger Pinger, timer clockwork.T return case <-timer.Chan(): then := m.clock.Now() - if err := pinger.Ping(ctx); err != nil { - log.WithError(err).Warn("unexpected failure sending ping") + if err := pinger.Ping(ctx); err != nil && !errors.Is(err, context.Canceled) { + logger.WarnContext(ctx, "unexpected failure sending ping", "error", err) } else { latency.Store(m.clock.Now().Sub(then).Milliseconds()) } diff --git a/lib/utils/envutils/environment.go b/lib/utils/envutils/environment.go index 19380be212f5f..ab3e34ad908cb 100644 --- a/lib/utils/envutils/environment.go +++ b/lib/utils/envutils/environment.go @@ -20,13 +20,13 @@ package envutils import ( "bufio" + "context" "fmt" "io" + "log/slog" "os" "strings" - log "github.com/sirupsen/logrus" - "github.com/gravitational/teleport" "github.com/gravitational/teleport/lib/utils" ) @@ -39,7 +39,10 @@ func ReadEnvironmentFile(filename string) ([]string, error) { // having this file for the user is optional. file, err := utils.OpenFileNoUnsafeLinks(filename) if err != nil { - log.Warnf("Unable to open environment file %v: %v, skipping", filename, err) + slog.WarnContext(context.Background(), "Unable to open environment file, skipping", + "file", filename, + "error", err, + ) return []string{}, nil } defer file.Close() @@ -51,6 +54,7 @@ func readEnvironment(r io.Reader) ([]string, error) { var lineno int env := &SafeEnv{} + ctx := context.Background() scanner := bufio.NewScanner(r) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) @@ -59,7 +63,9 @@ func readEnvironment(r io.Reader) ([]string, error) { // https://github.com/openssh/openssh-portable/blob/master/session.c#L873-L874 lineno = lineno + 1 if lineno > teleport.MaxEnvironmentFileLines { - log.Warnf("Too many lines in environment file, returning first %v lines", teleport.MaxEnvironmentFileLines) + slog.WarnContext(ctx, "Too many lines in environment file, limiting how many are consumed", + "lines_consumed", teleport.MaxEnvironmentFileLines, + ) return *env, nil } @@ -71,7 +77,7 @@ func readEnvironment(r io.Reader) ([]string, error) { // split on first =, if not found, log it and continue idx := strings.Index(line, "=") if idx == -1 { - log.Debugf("Bad line %v while reading environment file: no = separator found", lineno) + slog.DebugContext(ctx, "Bad line while reading environment file: no = separator found", "line_number", lineno) continue } @@ -79,7 +85,7 @@ func readEnvironment(r io.Reader) ([]string, error) { key := line[:idx] value := line[idx+1:] if strings.TrimSpace(key) == "" { - log.Debugf("Bad line %v while reading environment file: key without name", lineno) + slog.DebugContext(ctx, "Bad line while reading environment file: key without name", "line_number", lineno) continue } @@ -88,7 +94,7 @@ func readEnvironment(r io.Reader) ([]string, error) { } if err := scanner.Err(); err != nil { - log.Warnf("Unable to read environment file: %v", err) + slog.WarnContext(ctx, "Unable to read environment file", "error", err) return []string{}, nil } diff --git a/lib/utils/fanoutbuffer/buffer.go b/lib/utils/fanoutbuffer/buffer.go index 180ef0aa6bd4a..e7a1a600e935f 100644 --- a/lib/utils/fanoutbuffer/buffer.go +++ b/lib/utils/fanoutbuffer/buffer.go @@ -21,13 +21,13 @@ package fanoutbuffer import ( "context" "errors" + "log/slog" "runtime" "sync" "sync/atomic" "time" "github.com/jonboulle/clockwork" - log "github.com/sirupsen/logrus" ) // ErrGracePeriodExceeded is an error returned by Cursor.Read indicating that the cursor fell @@ -380,7 +380,7 @@ func finalizeCursor[T any](cursor *Cursor[T]) { } cursor.closeLocked() - log.Warn("Fanout buffer cursor was never closed. (this is a bug)") + slog.WarnContext(context.Background(), "Fanout buffer cursor was never closed. (this is a bug)") } // Close closes the cursor. Close is safe to double-call and should be called as soon as possible if diff --git a/lib/utils/host/hostusers.go b/lib/utils/host/hostusers.go index 3123d1a5d8879..de3ce20b5d69d 100644 --- a/lib/utils/host/hostusers.go +++ b/lib/utils/host/hostusers.go @@ -21,7 +21,9 @@ package host import ( "bufio" "bytes" + "context" "errors" + "log/slog" "os" "os/exec" "os/user" @@ -29,7 +31,6 @@ import ( "strings" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" ) // man GROUPADD(8), exit codes section @@ -55,7 +56,10 @@ func GroupAdd(groupname string, gid string) (exitCode int, err error) { cmd := exec.Command(groupaddBin, args...) output, err := cmd.CombinedOutput() - log.Debugf("%s output: %s", cmd.Path, string(output)) + slog.DebugContext(context.Background(), "groupadd command completed", + "command_path", cmd.Path, + "output", string(output), + ) switch code := cmd.ProcessState.ExitCode(); code { case GroupExistExit: @@ -122,7 +126,7 @@ func UserAdd(username string, groups []string, opts UserOpts) (exitCode int, err if opts.Shell != "" { if shell, err := exec.LookPath(opts.Shell); err != nil { - log.Warnf("configured shell %q not found, falling back to host default", opts.Shell) + slog.WarnContext(context.Background(), "configured shell not found, falling back to host default", "shell", opts.Shell) } else { args = append(args, "--shell", shell) } @@ -130,7 +134,10 @@ func UserAdd(username string, groups []string, opts UserOpts) (exitCode int, err cmd := exec.Command(useraddBin, args...) output, err := cmd.CombinedOutput() - log.Debugf("%s output: %s", cmd.Path, string(output)) + slog.DebugContext(context.Background(), "useradd command completed", + "command_path", cmd.Path, + "output", string(output), + ) if cmd.ProcessState.ExitCode() == UserExistExit { return cmd.ProcessState.ExitCode(), trace.AlreadyExists("user already exists") } @@ -147,7 +154,10 @@ func SetUserGroups(username string, groups []string) (exitCode int, err error) { // usermod -G (replace groups) (username) cmd := exec.Command(usermodBin, "-G", strings.Join(groups, ","), username) output, err := cmd.CombinedOutput() - log.Debugf("%s output: %s", cmd.Path, string(output)) + slog.DebugContext(context.Background(), "usermod completed", + "command_path", cmd.Path, + "output", string(output), + ) return cmd.ProcessState.ExitCode(), trace.Wrap(err) } @@ -170,7 +180,10 @@ func UserDel(username string) (exitCode int, err error) { // userdel --remove (remove home) username cmd := exec.Command(userdelBin, args...) output, err := cmd.CombinedOutput() - log.Debugf("%s output: %s", cmd.Path, string(output)) + slog.DebugContext(context.Background(), "usedel command completed", + "command_path", cmd.Path, + "output", string(output), + ) return cmd.ProcessState.ExitCode(), trace.Wrap(err) } diff --git a/lib/utils/loadbalancer.go b/lib/utils/loadbalancer.go index c201f8e60a79c..6a256e9113bbe 100644 --- a/lib/utils/loadbalancer.go +++ b/lib/utils/loadbalancer.go @@ -22,15 +22,16 @@ import ( "context" "errors" "io" + "log/slog" "math/rand/v2" "net" "sync" "time" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "github.com/gravitational/teleport" + logutils "github.com/gravitational/teleport/lib/utils/log" ) // NewLoadBalancer returns new load balancer listening on frontend @@ -58,12 +59,10 @@ func newLoadBalancer(ctx context.Context, frontend NetAddr, policy loadBalancerP policy: policy, waitCtx: waitCtx, waitCancel: waitCancel, - Entry: log.WithFields(log.Fields{ - teleport.ComponentKey: "loadbalancer", - teleport.ComponentFields: log.Fields{ - "listen": frontend.String(), - }, - }), + logger: slog.With( + teleport.ComponentKey, "loadbalancer", + "frontend_addr", frontend.FullAddress(), + ), connections: make(map[NetAddr]map[int64]net.Conn), }, nil } @@ -103,8 +102,8 @@ func randomPolicy() loadBalancerPolicy { // balancer used in tests. type LoadBalancer struct { sync.RWMutex - connID int64 - *log.Entry + connID int64 + logger *slog.Logger frontend NetAddr backends []NetAddr ctx context.Context @@ -156,7 +155,7 @@ func (l *LoadBalancer) AddBackend(b NetAddr) { l.Lock() defer l.Unlock() l.backends = append(l.backends, b) - l.Debugf("Backends %v.", l.backends) + l.logger.DebugContext(l.ctx, "Backends updated", "backends", l.backends) } // RemoveBackend removes backend @@ -205,7 +204,9 @@ func (l *LoadBalancer) Listen() error { if err != nil { return trace.ConvertSystemError(err) } - l.Debugf("created listening socket on %q", l.listener.Addr()) + l.logger.DebugContext(l.ctx, "created listening socket", + "listen_addr", logutils.StringerAttr(l.listener.Addr()), + ) return nil } @@ -231,7 +232,7 @@ func (l *LoadBalancer) Serve() error { case <-l.ctx.Done(): return trace.Wrap(net.ErrClosed, "context is closing") case <-time.After(5. * time.Second): - l.Debugf("Backoff on network error.") + l.logger.DebugContext(l.ctx, "Backoff on network error") } } else { go l.forwardConnection(conn) @@ -242,7 +243,7 @@ func (l *LoadBalancer) Serve() error { func (l *LoadBalancer) forwardConnection(conn net.Conn) { err := l.forward(conn) if err != nil { - l.Warningf("Failed to forward connection: %v.", err) + l.logger.WarnContext(l.ctx, "Failed to forward connection", "error", err) } } @@ -278,11 +279,11 @@ func (l *LoadBalancer) forward(conn net.Conn) error { backendConnID := l.trackConnection(backend, backendConn) defer l.untrackConnection(backend, backendConnID) - logger := l.WithFields(log.Fields{ - "source": conn.RemoteAddr(), - "dest": backendConn.RemoteAddr(), - }) - logger.Debugf("forward") + logger := l.logger.With( + "source_addr", logutils.StringerAttr(conn.RemoteAddr()), + "dest_addr", logutils.StringerAttr(backendConn.RemoteAddr()), + ) + logger.DebugContext(l.ctx, "forwarding data") messagesC := make(chan error, 2) @@ -305,7 +306,7 @@ func (l *LoadBalancer) forward(conn net.Conn) error { select { case err := <-messagesC: if err != nil && !errors.Is(err, io.EOF) { - logger.Warningf("connection problem: %v %T", trace.DebugReport(err), err) + logger.WarnContext(l.ctx, "connection problem", "error", err) lastErr = err } case <-l.ctx.Done(): diff --git a/lib/utils/log/buffer.go b/lib/utils/log/buffer.go index c158808bd545c..d12ac9e11bab0 100644 --- a/lib/utils/log/buffer.go +++ b/lib/utils/log/buffer.go @@ -21,6 +21,14 @@ func newBuffer() *buffer { return bufPool.Get().(*buffer) } +func (b *buffer) Len() int { + return len(*b) +} + +func (b *buffer) SetLen(n int) { + *b = (*b)[:n] +} + func (b *buffer) Free() { // To reduce peak allocation, return only smaller buffers to the pool. const maxBufferSize = 16 << 10 @@ -49,35 +57,6 @@ func (b *buffer) WriteByte(c byte) error { return nil } -func (b *buffer) WritePosInt(i int) { - b.WritePosIntWidth(i, 0) -} - -// WritePosIntWidth writes non-negative integer i to the buffer, padded on the left -// by zeroes to the given width. Use a width of 0 to omit padding. -func (b *buffer) WritePosIntWidth(i, width int) { - // Cheap integer to fixed-width decimal ASCII. - // Copied from log/log.go. - - if i < 0 { - panic("negative int") - } - - // Assemble decimal in reverse order. - var bb [20]byte - bp := len(bb) - 1 - for i >= 10 || width > 1 { - width-- - q := i / 10 - bb[bp] = byte('0' + i - q*10) - bp-- - i = q - } - // i < 10 - bb[bp] = byte('0' + i) - b.Write(bb[bp:]) -} - func (b *buffer) String() string { return string(*b) } diff --git a/lib/utils/log/formatter_test.go b/lib/utils/log/formatter_test.go index 39c717df3425d..e11a9f63620fb 100644 --- a/lib/utils/log/formatter_test.go +++ b/lib/utils/log/formatter_test.go @@ -45,7 +45,7 @@ import ( "github.com/gravitational/teleport" ) -const message = "Adding diagnostic debugging handlers.\t To connect with profiler, use `go tool pprof diag_addr`." +const message = "Adding diagnostic debugging handlers.\t To connect with profiler, use go tool pprof diag_addr." var ( logErr = errors.New("the quick brown fox jumped really high") @@ -76,7 +76,6 @@ func TestOutput(t *testing.T) { loc, err := time.LoadLocation("Africa/Cairo") require.NoError(t, err, "failed getting timezone") clock := clockwork.NewFakeClockAt(time.Now().In(loc)) - formattedNow := clock.Now().UTC().Format(time.RFC3339) t.Run("text", func(t *testing.T) { // fieldsRegex matches all the key value pairs emitted after the message and before the caller. All fields are @@ -88,7 +87,7 @@ func TestOutput(t *testing.T) { // 2) the message // 3) the fields // 4) the caller - outputRegex := regexp.MustCompile("(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z)(\\s+.*)(\".*diag_addr`\\.\")(.*)(\\slog/formatter_test.go:\\d{3})") + outputRegex := regexp.MustCompile(`(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)(\s+.*)(".*diag_addr\.")(.*)(\slog/formatter_test.go:\d{3})`) tests := []struct { name string @@ -149,7 +148,7 @@ func TestOutput(t *testing.T) { EnableColors: true, ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { if a.Key == slog.TimeKey { - a.Value = slog.StringValue(formattedNow) + a.Value = slog.TimeValue(clock.Now().UTC()) } return a }, @@ -188,7 +187,7 @@ func TestOutput(t *testing.T) { // Match level, and component: DEBU [TEST] assert.Empty(t, cmp.Diff(logrusMatches[2], slogMatches[2]), "level, and component to be identical") - // Match the log message: "Adding diagnostic debugging handlers.\t To connect with profiler, use `go tool pprof diag_addr`.\n" + // Match the log message: "Adding diagnostic debugging handlers.\t To connect with profiler, use go tool pprof diag_addr.\n" assert.Empty(t, cmp.Diff(logrusMatches[3], slogMatches[3]), "expected output messages to be identical") // The last matches are the caller information assert.Equal(t, fmt.Sprintf(" log/formatter_test.go:%d", logrusTestLogLineNumber), logrusMatches[5]) @@ -461,7 +460,13 @@ func TestConcurrentOutput(t *testing.T) { wg.Add(1) go func(i int) { defer wg.Done() - logger.InfoContext(ctx, "Teleport component entered degraded state", "component", i) + logger.InfoContext(ctx, "Teleport component entered degraded state", + slog.Int("component", i), + slog.Group("group", + slog.String("test", "123"), + slog.String("animal", "llama"), + ), + ) }(i) } wg.Wait() diff --git a/lib/utils/log/handle_state.go b/lib/utils/log/handle_state.go new file mode 100644 index 0000000000000..c60132b28e48e --- /dev/null +++ b/lib/utils/log/handle_state.go @@ -0,0 +1,352 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package log + +import ( + "encoding" + "fmt" + "log/slog" + "reflect" + "strconv" + "sync" + "time" + + "github.com/gravitational/trace" + "github.com/sirupsen/logrus" + + "github.com/gravitational/teleport" +) + +// handleState adapted from go/src/log/slog/handler.go +type handleState struct { + h *SlogTextHandler + buf *buffer + freeBuf bool // should buf be freed? + prefix *buffer // for text: key prefix + groups *[]string // pool-allocated slice of active groups, for ReplaceAttr +} + +var groupPool = sync.Pool{New: func() any { + s := make([]string, 0, 10) + return &s +}} + +func (s *handleState) free() { + if s.freeBuf { + s.buf.Free() + } + if gs := s.groups; gs != nil { + *gs = (*gs)[:0] + groupPool.Put(gs) + } + s.prefix.Free() +} + +func (s *handleState) openGroups() { + for _, n := range s.h.groups[s.h.nOpenGroups:] { + s.openGroup(n) + } +} + +// openGroup starts a new group of attributes +// with the given name. +func (s *handleState) openGroup(name string) { + s.prefix.WriteString(name) + s.prefix.WriteByte('.') + + // Collect group names for ReplaceAttr. + if s.groups != nil { + *s.groups = append(*s.groups, name) + } +} + +// closeGroup ends the group with the given name. +func (s *handleState) closeGroup(name string) { + *s.prefix = (*s.prefix)[:len(*s.prefix)-len(name)-1 /* for keyComponentSep */] + + if s.groups != nil { + *s.groups = (*s.groups)[:len(*s.groups)-1] + } +} + +// appendAttrs appends the slice of Attrs. +// It reports whether something was appended. +func (s *handleState) appendAttrs(as []slog.Attr) bool { + nonEmpty := false + for _, a := range as { + if s.appendAttr(a) { + nonEmpty = true + } + } + return nonEmpty +} + +// appendAttr appends the Attr's key and value. +// It handles replacement and checking for an empty key. +// It reports whether something was appended. +func (s *handleState) appendAttr(a slog.Attr) bool { + a.Value = a.Value.Resolve() + if rep := s.h.cfg.ReplaceAttr; rep != nil && a.Value.Kind() != slog.KindGroup { + var gs []string + if s.groups != nil { + gs = *s.groups + } + // a.Value is resolved before calling ReplaceAttr, so the user doesn't have to. + a = rep(gs, a) + // The ReplaceAttr function may return an unresolved Attr. + a.Value = a.Value.Resolve() + } + // Elide empty Attrs. + if a.Equal(slog.Attr{}) { + return false + } + + // Handle nested attributes from within component fields. + if a.Key == teleport.ComponentFields { + nonEmpty := false + switch fields := a.Value.Any().(type) { + case map[string]any: + for k, v := range fields { + if s.appendAttr(slog.Any(k, v)) { + nonEmpty = true + } + } + return nonEmpty + case logrus.Fields: + for k, v := range fields { + if s.appendAttr(slog.Any(k, v)) { + nonEmpty = true + } + } + return nonEmpty + } + } + + // Handle special cases before formatting. + if a.Value.Kind() == slog.KindAny { + switch v := a.Value.Any().(type) { + case *slog.Source: + a.Value = slog.StringValue(fmt.Sprintf(" %s:%d", v.File, v.Line)) + case trace.Error: + a.Value = slog.StringValue("[" + v.DebugReport() + "]") + case error: + a.Value = slog.StringValue(fmt.Sprintf("[%v]", v)) + } + } + + if a.Value.Kind() == slog.KindGroup { + attrs := a.Value.Group() + // Output only non-empty groups. + if len(attrs) > 0 { + // The group may turn out to be empty even though it has attrs (for + // example, ReplaceAttr may delete all the attrs). + // So remember where we are in the buffer, to restore the position + // later if necessary. + pos := s.buf.Len() + // Inline a group with an empty key. + if a.Key != "" { + s.openGroup(a.Key) + } + if !s.appendAttrs(attrs) { + s.buf.SetLen(pos) + return false + } + if a.Key != "" { + s.closeGroup(a.Key) + } + } + + return true + } + + if a.Value.Kind() == slog.KindString && a.Key != slog.LevelKey { + val := a.Value.String() + if needsQuoting(val) { + a.Value = slog.StringValue(strconv.Quote(val)) + } + } + + s.appendKey(a.Key) + + // Write the log key directly to avoid quoting + // color formatting that exists. + if a.Key == slog.LevelKey { + s.buf.WriteString(a.Value.String()) + } else { + s.appendValue(a.Value) + } + + return true +} + +func (s *handleState) appendError(err error) { + s.appendString(fmt.Sprintf("!ERROR:%v", err)) +} + +func (s *handleState) appendKey(key string) { + if s.buf.Len() > 0 { + s.buf.WriteString(" ") + } + + // These keys should not be included in the output to match + // the behavior of the lorgus formatter. + if key == slog.TimeKey || + key == teleport.ComponentKey || + key == slog.LevelKey || + key == CallerField || + key == slog.MessageKey || + key == slog.SourceKey { + return + } + + if s.prefix != nil && len(*s.prefix) > 0 { + // TODO: optimize by avoiding allocation. + s.appendString(string(*s.prefix) + key) + } else { + s.appendString(key) + } + + s.buf.WriteByte(':') +} + +func (s *handleState) appendString(str string) { + if str == "" { + return + } + + if needsQuoting(str) { + *s.buf = strconv.AppendQuote(*s.buf, str) + } else { + s.buf.WriteString(str) + } +} + +func (s *handleState) appendValue(v slog.Value) { + defer func() { + if r := recover(); r != nil { + // If it panics with a nil pointer, the most likely cases are + // an encoding.TextMarshaler or error fails to guard against nil, + // in which case "" seems to be the feasible choice. + // + // Adapted from the code in fmt/print.go. + if v := reflect.ValueOf(v.Any()); v.Kind() == reflect.Pointer && v.IsNil() { + s.appendString("") + return + } + + // Otherwise just print the original panic message. + s.appendString(fmt.Sprintf("!PANIC: %v", r)) + } + }() + + if err := appendTextValue(s, v); err != nil { + s.appendError(err) + } +} + +func (s *handleState) appendTime(t time.Time) { + *s.buf = appendRFC3339Millis(*s.buf, t) +} + +func (s *handleState) appendNonBuiltIns(r slog.Record) { + // preformatted Attrs + if pfa := s.h.preformatted; len(pfa) > 0 { + s.buf.WriteString(" ") + s.buf.Write(pfa) + } + // Attrs in Record -- unlike the built-in ones, they are in groups started + // from WithGroup. + // If the record has no Attrs, don't output any groups. + if r.NumAttrs() > 0 { + s.prefix.WriteString(s.h.groupPrefix) + // The group may turn out to be empty even though it has attrs (for + // example, ReplaceAttr may delete all the attrs). + // So remember where we are in the buffer, to restore the position + // later if necessary. + pos := s.buf.Len() + s.openGroups() + empty := true + r.Attrs(func(a slog.Attr) bool { + // The component is handled by the top level handler. + if a.Key == teleport.ComponentKey { + return true + } + if s.appendAttr(a) { + empty = false + } + return true + }) + if empty { + s.buf.SetLen(pos) + } + } +} + +func byteSlice(a any) ([]byte, bool) { + if bs, ok := a.([]byte); ok { + return bs, true + } + // Like Printf's %s, we allow both the slice type and the byte element type to be named. + t := reflect.TypeOf(a) + if t != nil && t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 { + return reflect.ValueOf(a).Bytes(), true + } + return nil, false +} + +func appendTextValue(s *handleState, v slog.Value) error { + switch v.Kind() { + case slog.KindString: + s.appendString(v.String()) + case slog.KindTime: + s.appendTime(v.Time()) + case slog.KindAny: + if tm, ok := v.Any().(encoding.TextMarshaler); ok { + data, err := tm.MarshalText() + if err != nil { + return err + } + // TODO: avoid the conversion to string. + s.appendString(string(data)) + return nil + } + if bs, ok := byteSlice(v.Any()); ok { + // As of Go 1.19, this only allocates for strings longer than 32 bytes. + s.buf.WriteString(strconv.Quote(string(bs))) + return nil + } + s.appendString(fmt.Sprintf("%+v", v.Any())) + case slog.KindInt64: + *s.buf = strconv.AppendInt(*s.buf, v.Int64(), 10) + case slog.KindUint64: + *s.buf = strconv.AppendUint(*s.buf, v.Uint64(), 10) + case slog.KindFloat64: + *s.buf = strconv.AppendFloat(*s.buf, v.Float64(), 'g', -1, 64) + case slog.KindBool: + *s.buf = strconv.AppendBool(*s.buf, v.Bool()) + case slog.KindDuration: + *s.buf = append(*s.buf, v.Duration().String()...) + case slog.KindGroup: + *s.buf = fmt.Append(*s.buf, v.Group()) + case slog.KindLogValuer: + *s.buf = fmt.Append(*s.buf, v.Any()) + default: + panic(fmt.Sprintf("bad kind: %s", v.Kind())) + } + return nil +} + +func appendRFC3339Millis(b []byte, t time.Time) []byte { + // Format according to time.RFC3339Nano since it is highly optimized, + // but truncate it to use millisecond resolution. + // Unfortunately, that format trims trailing 0s, so add 1/10 millisecond + // to guarantee that there are exactly 4 digits after the period. + const prefixLen = len("2006-01-02T15:04:05.000") + n := len(b) + t = t.Truncate(time.Millisecond).Add(time.Millisecond / 10) + b = t.AppendFormat(b, time.RFC3339Nano) + b = append(b[:n+prefixLen], b[n+prefixLen+1:]...) // drop the 4th digit + return b +} diff --git a/lib/utils/log/logrus_formatter.go b/lib/utils/log/logrus_formatter.go index 87b3bff3bdc24..a21d922adf809 100644 --- a/lib/utils/log/logrus_formatter.go +++ b/lib/utils/log/logrus_formatter.go @@ -145,7 +145,7 @@ func (tf *TextFormatter) Format(e *logrus.Entry) ([]byte, error) { // write timestamp first if enabled if tf.timestampEnabled { - writeTimeRFC3339(w.b, e.Time) + *w.b = appendRFC3339Millis(*w.b, e.Time.Round(0)) } for _, field := range tf.ExtraFields { diff --git a/lib/utils/log/slog.go b/lib/utils/log/slog.go new file mode 100644 index 0000000000000..b1b0678ec5487 --- /dev/null +++ b/lib/utils/log/slog.go @@ -0,0 +1,131 @@ +/* + * Teleport + * Copyright (C) 2023 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package log + +import ( + "context" + "fmt" + "log/slog" + "reflect" + "strings" + + oteltrace "go.opentelemetry.io/otel/trace" +) + +const ( + // TraceLevel is the logging level when set to Trace verbosity. + TraceLevel = slog.LevelDebug - 1 + + // TraceLevelText is the text representation of Trace verbosity. + TraceLevelText = "TRACE" +) + +// DiscardHandler is a [slog.Handler] that discards all messages. It +// is more efficient than a [slog.Handler] which outputs to [io.Discard] since +// it performs zero formatting. +// TODO(tross): Use slog.DiscardHandler once upgraded to Go 1.24. +type DiscardHandler struct{} + +func (dh DiscardHandler) Enabled(context.Context, slog.Level) bool { return false } +func (dh DiscardHandler) Handle(context.Context, slog.Record) error { return nil } +func (dh DiscardHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return dh } +func (dh DiscardHandler) WithGroup(name string) slog.Handler { return dh } + +func addTracingContextToRecord(ctx context.Context, r *slog.Record) { + const ( + traceID = "trace_id" + spanID = "span_id" + ) + + span := oteltrace.SpanFromContext(ctx) + if span == nil { + return + } + + spanContext := span.SpanContext() + if spanContext.HasTraceID() { + r.AddAttrs(slog.String(traceID, spanContext.TraceID().String())) + } + + if spanContext.HasSpanID() { + r.AddAttrs(slog.String(spanID, spanContext.SpanID().String())) + } +} + +// getCaller retrieves source information from the attribute +// and returns the file and line of the caller. The file is +// truncated from the absolute path to package/filename. +func getCaller(s *slog.Source) (file string, line int) { + count := 0 + idx := strings.LastIndexFunc(s.File, func(r rune) bool { + if r == '/' { + count++ + } + + return count == 2 + }) + file = s.File[idx+1:] + line = s.Line + + return file, line +} + +type stringerAttr struct { + fmt.Stringer +} + +// StringerAttr creates a [slog.LogValuer] that will defer to +// the provided [fmt.Stringer]. All slog attributes are always evaluated, +// even if the log event is discarded due to the configured log level. +// A text [slog.Handler] will try to defer evaluation if the attribute is a +// [fmt.Stringer], however, the JSON [slog.Handler] only defers to [json.Marshaler]. +// This means that to defer evaluation and creation of the string representation, +// the object must implement [fmt.Stringer] and [json.Marshaler], otherwise additional +// and unwanted values may be emitted if the logger is configured to use JSON +// instead of text. This wrapping mechanism allows a value that implements [fmt.Stringer], +// to be guaranteed to be lazily constructed and always output the same +// content regardless of the output format. +func StringerAttr(s fmt.Stringer) slog.LogValuer { + return stringerAttr{Stringer: s} +} + +func (s stringerAttr) LogValue() slog.Value { + if s.Stringer == nil { + return slog.StringValue("") + } + return slog.StringValue(s.Stringer.String()) +} + +type typeAttr struct { + val any +} + +// TypeAttr creates a lazily evaluated log value that presents the pretty type name of a value +// as a string. It is roughly equivalent to the '%T' format option, and should only perform +// reflection in the event that logs are actually being generated. +func TypeAttr(val any) slog.LogValuer { + return typeAttr{val} +} + +func (a typeAttr) LogValue() slog.Value { + if t := reflect.TypeOf(a.val); t != nil { + return slog.StringValue(t.String()) + } + return slog.StringValue("nil") +} diff --git a/lib/utils/log/slog_handler.go b/lib/utils/log/slog_handler.go deleted file mode 100644 index 14363bca8584e..0000000000000 --- a/lib/utils/log/slog_handler.go +++ /dev/null @@ -1,692 +0,0 @@ -/* - * Teleport - * Copyright (C) 2023 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package log - -import ( - "context" - "fmt" - "io" - "log/slog" - "reflect" - "runtime" - "slices" - "strconv" - "strings" - "sync" - "time" - - "github.com/gravitational/trace" - "github.com/sirupsen/logrus" - oteltrace "go.opentelemetry.io/otel/trace" - - "github.com/gravitational/teleport" -) - -// TraceLevel is the logging level when set to Trace verbosity. -const TraceLevel = slog.LevelDebug - 1 - -// TraceLevelText is the text representation of Trace verbosity. -const TraceLevelText = "TRACE" - -// DiscardHandler is a [slog.Handler] that discards all messages. It -// is more efficient than a [slog.Handler] which outputs to [io.Discard] since -// it performs zero formatting. -// TODO(tross): Use slog.DiscardHandler once upgraded to Go 1.24. -type DiscardHandler struct{} - -func (dh DiscardHandler) Enabled(context.Context, slog.Level) bool { return false } -func (dh DiscardHandler) Handle(context.Context, slog.Record) error { return nil } -func (dh DiscardHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return dh } -func (dh DiscardHandler) WithGroup(name string) slog.Handler { return dh } - -// SlogTextHandler is a [slog.Handler] that outputs messages in a textual -// manner as configured by the Teleport configuration. -type SlogTextHandler struct { - cfg SlogTextHandlerConfig - // withCaller indicates whether the location the log was emitted from - // should be included in the output message. - withCaller bool - // withTimestamp indicates whether the times that the log was emitted at - // should be included in the output message. - withTimestamp bool - // component is the Teleport subcomponent that emitted the log. - component string - // preformatted data from previous calls to WithGroup and WithAttrs. - preformatted []byte - // groupPrefix is for the text handler only. - // It holds the prefix for groups that were already pre-formatted. - // A group will appear here when a call to WithGroup is followed by - // a call to WithAttrs. - groupPrefix buffer - // groups passed in via WithGroup and WithAttrs. - groups []string - // nOpenGroups the number of groups opened in preformatted. - nOpenGroups int - - // mu protects out - it needs to be a pointer so that all cloned - // SlogTextHandler returned from WithAttrs and WithGroup share the - // same mutex. Otherwise, output may be garbled since each clone - // will use its own copy of the mutex to protect out. See - // https://github.com/golang/go/issues/61321 for more details. - mu *sync.Mutex - out io.Writer -} - -// SlogTextHandlerConfig allow the SlogTextHandler functionality -// to be tweaked. -type SlogTextHandlerConfig struct { - // Level is the minimum record level that will be logged. - Level slog.Leveler - // EnableColors allows the level to be printed in color. - EnableColors bool - // Padding to use for various components. - Padding int - // ConfiguredFields are fields explicitly set by users to be included in - // the output message. If there are any entries configured, they will be honored. - // If empty, the default fields will be populated and included in the output. - ConfiguredFields []string - // ReplaceAttr is called to rewrite each non-group attribute before - // it is logged. - ReplaceAttr func(groups []string, a slog.Attr) slog.Attr -} - -// NewSlogTextHandler creates a SlogTextHandler that writes messages to w. -func NewSlogTextHandler(w io.Writer, cfg SlogTextHandlerConfig) *SlogTextHandler { - if cfg.Padding == 0 { - cfg.Padding = defaultComponentPadding - } - - handler := SlogTextHandler{ - cfg: cfg, - withCaller: len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, CallerField), - withTimestamp: len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, TimestampField), - out: w, - mu: &sync.Mutex{}, - } - - if handler.cfg.ConfiguredFields == nil { - handler.cfg.ConfiguredFields = defaultFormatFields - } - - return &handler -} - -// Enabled returns whether the provided level will be included in output. -func (s *SlogTextHandler) Enabled(ctx context.Context, level slog.Level) bool { - minLevel := slog.LevelInfo - if s.cfg.Level != nil { - minLevel = s.cfg.Level.Level() - } - return level >= minLevel -} - -func (s *SlogTextHandler) appendAttr(buf []byte, a slog.Attr) []byte { - if rep := s.cfg.ReplaceAttr; rep != nil && a.Value.Kind() != slog.KindGroup { - var gs []string - if s.groups != nil { - gs = s.groups - } - // Resolve before calling ReplaceAttr, so the user doesn't have to. - a.Value = a.Value.Resolve() - a = rep(gs, a) - } - - // Resolve the Attr's value before doing anything else. - a.Value = a.Value.Resolve() - // Ignore empty Attrs. - if a.Equal(slog.Attr{}) { - return buf - } - - switch a.Value.Kind() { - case slog.KindString: - value := a.Value.String() - if a.Key == slog.TimeKey { - buf = fmt.Append(buf, value) - break - } - - if a.Key == teleport.ComponentFields { - switch fields := a.Value.Any().(type) { - case map[string]any: - for k, v := range fields { - buf = s.appendAttr(buf, slog.Any(k, v)) - } - case logrus.Fields: - for k, v := range fields { - buf = s.appendAttr(buf, slog.Any(k, v)) - } - } - } - - if needsQuoting(value) { - if a.Key == teleport.ComponentKey || a.Key == slog.LevelKey || a.Key == CallerField || a.Key == slog.MessageKey { - if len(buf) > 0 { - buf = fmt.Append(buf, " ") - } - } else { - if len(buf) > 0 { - buf = fmt.Append(buf, " ") - } - buf = fmt.Appendf(buf, "%s%s:", s.groupPrefix, a.Key) - } - buf = strconv.AppendQuote(buf, value) - break - } - - if a.Key == teleport.ComponentKey || a.Key == slog.LevelKey || a.Key == CallerField || a.Key == slog.MessageKey { - if len(buf) > 0 { - buf = fmt.Append(buf, " ") - } - buf = fmt.Appendf(buf, "%s", a.Value.String()) - break - } - - buf = fmt.Appendf(buf, " %s%s:%s", s.groupPrefix, a.Key, a.Value.String()) - case slog.KindGroup: - attrs := a.Value.Group() - // Ignore empty groups. - if len(attrs) == 0 { - return buf - } - // If the key is non-empty, write it out and indent the rest of the attrs. - // Otherwise, inline the attrs. - if a.Key != "" { - s.groupPrefix = fmt.Append(s.groupPrefix, a.Key) - s.groupPrefix = fmt.Append(s.groupPrefix, ".") - } - for _, ga := range attrs { - buf = s.appendAttr(buf, ga) - } - if a.Key != "" { - s.groupPrefix = s.groupPrefix[:len(s.groupPrefix)-len(a.Key)-1 /* for keyComponentSep */] - if s.groups != nil { - s.groups = (s.groups)[:len(s.groups)-1] - } - } - default: - switch err := a.Value.Any().(type) { - case trace.Error: - buf = fmt.Appendf(buf, " error:[%v]", err.DebugReport()) - case error: - buf = fmt.Appendf(buf, " error:[%v]", a.Value) - default: - buf = fmt.Appendf(buf, " %s:%s", a.Key, a.Value) - } - } - return buf -} - -// writeTimeRFC3339 writes the time in [time.RFC3339Nano] to the buffer. -// This takes half the time of [time.Time.AppendFormat]. Adapted from -// go/src/log/slog/handler.go. -func writeTimeRFC3339(buf *buffer, t time.Time) { - year, month, day := t.Date() - buf.WritePosIntWidth(year, 4) - buf.WriteByte('-') - buf.WritePosIntWidth(int(month), 2) - buf.WriteByte('-') - buf.WritePosIntWidth(day, 2) - buf.WriteByte('T') - hour, min, sec := t.Clock() - buf.WritePosIntWidth(hour, 2) - buf.WriteByte(':') - buf.WritePosIntWidth(min, 2) - buf.WriteByte(':') - buf.WritePosIntWidth(sec, 2) - _, offsetSeconds := t.Zone() - if offsetSeconds == 0 { - buf.WriteByte('Z') - } else { - offsetMinutes := offsetSeconds / 60 - if offsetMinutes < 0 { - buf.WriteByte('-') - offsetMinutes = -offsetMinutes - } else { - buf.WriteByte('+') - } - buf.WritePosIntWidth(offsetMinutes/60, 2) - buf.WriteByte(':') - buf.WritePosIntWidth(offsetMinutes%60, 2) - } -} - -// Handle formats the provided record and writes the output to the -// destination. -func (s *SlogTextHandler) Handle(ctx context.Context, r slog.Record) error { - buf := newBuffer() - defer buf.Free() - - addTracingContextToRecord(ctx, &r) - - if s.withTimestamp && !r.Time.IsZero() { - if s.cfg.ReplaceAttr != nil { - *buf = s.appendAttr(*buf, slog.Time(slog.TimeKey, r.Time)) - } else { - writeTimeRFC3339(buf, r.Time) - } - } - - // Processing fields in this manner allows users to - // configure the level and component position in the output. - // This matches the behavior of the original logrus. All other - // fields location in the output message are static. - for _, field := range s.cfg.ConfiguredFields { - switch field { - case LevelField: - var color int - var level string - switch r.Level { - case TraceLevel: - level = "TRACE" - color = gray - case slog.LevelDebug: - level = "DEBUG" - color = gray - case slog.LevelInfo: - level = "INFO" - color = blue - case slog.LevelWarn: - level = "WARN" - color = yellow - case slog.LevelError: - level = "ERROR" - color = red - case slog.LevelError + 1: - level = "FATAL" - color = red - default: - color = blue - level = r.Level.String() - } - - if !s.cfg.EnableColors { - color = noColor - } - - level = padMax(level, defaultLevelPadding) - if color == noColor { - *buf = s.appendAttr(*buf, slog.String(slog.LevelKey, level)) - } else { - *buf = fmt.Appendf(*buf, " \u001B[%dm%s\u001B[0m", color, level) - } - case ComponentField: - // If a component is provided with the attributes, it should be used instead of - // the component set on the handler. Note that if there are multiple components - // specified in the arguments, the one with the lowest index is used and the others are ignored. - // In the example below, the resulting component in the message output would be "alpaca". - // - // logger := logger.With(teleport.ComponentKey, "fish") - // logger.InfoContext(ctx, "llama llama llama", teleport.ComponentKey, "alpaca", "foo", 123, teleport.ComponentKey, "shark") - component := s.component - r.Attrs(func(attr slog.Attr) bool { - if attr.Key == teleport.ComponentKey { - component = fmt.Sprintf("[%v]", attr.Value) - component = strings.ToUpper(padMax(component, s.cfg.Padding)) - if component[len(component)-1] != ' ' { - component = component[:len(component)-1] + "]" - } - - return false - } - - return true - }) - - *buf = s.appendAttr(*buf, slog.String(teleport.ComponentKey, component)) - default: - if _, ok := knownFormatFields[field]; !ok { - return trace.BadParameter("invalid log format key: %v", field) - } - } - } - - *buf = s.appendAttr(*buf, slog.String(slog.MessageKey, r.Message)) - - // Insert preformatted attributes just after built-in ones. - *buf = append(*buf, s.preformatted...) - if r.NumAttrs() > 0 { - if len(s.groups) > 0 { - for _, n := range s.groups[s.nOpenGroups:] { - s.groupPrefix = fmt.Append(s.groupPrefix, n) - s.groupPrefix = fmt.Append(s.groupPrefix, ".") - } - } - - r.Attrs(func(a slog.Attr) bool { - // Skip adding any component attrs since they are processed above. - if a.Key == teleport.ComponentKey { - return true - } - - *buf = s.appendAttr(*buf, a) - return true - }) - } - - if r.PC != 0 && s.withCaller { - fs := runtime.CallersFrames([]uintptr{r.PC}) - f, _ := fs.Next() - - src := &slog.Source{ - Function: f.Function, - File: f.File, - Line: f.Line, - } - - file, line := getCaller(src) - *buf = fmt.Appendf(*buf, " %s:%d", file, line) - } - - buf.WriteByte('\n') - - s.mu.Lock() - defer s.mu.Unlock() - _, err := s.out.Write(*buf) - return err -} - -// WithAttrs clones the current handler with the provided attributes -// added to any existing attributes. The values are preformatted here -// so that they do not need to be formatted per call to Handle. -func (s *SlogTextHandler) WithAttrs(attrs []slog.Attr) slog.Handler { - if len(attrs) == 0 { - return s - } - s2 := *s - // Force an append to copy the underlying arrays. - s2.preformatted = slices.Clip(s.preformatted) - s2.groups = slices.Clip(s.groups) - - // Add all groups from WithGroup that haven't already been added to the prefix. - if len(s.groups) > 0 { - for _, n := range s.groups[s.nOpenGroups:] { - s2.groupPrefix = fmt.Append(s2.groupPrefix, n) - s2.groupPrefix = fmt.Append(s2.groupPrefix, ".") - } - } - - // Now all groups have been opened. - s2.nOpenGroups = len(s2.groups) - - component := s.component - - // Pre-format the attributes. - for _, a := range attrs { - switch a.Key { - case teleport.ComponentKey: - component = fmt.Sprintf("[%v]", a.Value.String()) - component = strings.ToUpper(padMax(component, s.cfg.Padding)) - if component[len(component)-1] != ' ' { - component = component[:len(component)-1] + "]" - } - case teleport.ComponentFields: - switch fields := a.Value.Any().(type) { - case map[string]any: - for k, v := range fields { - s2.appendAttr(s2.preformatted, slog.Any(k, v)) - } - case logrus.Fields: - for k, v := range fields { - s2.preformatted = s2.appendAttr(s2.preformatted, slog.Any(k, v)) - } - } - default: - s2.preformatted = s2.appendAttr(s2.preformatted, a) - } - } - - s2.component = component - // Remember how many opened groups are in preformattedAttrs, - // so we don't open them again when we handle a Record. - s2.nOpenGroups = len(s2.groups) - return &s2 -} - -// WithGroup opens a new group. -func (s *SlogTextHandler) WithGroup(name string) slog.Handler { - if name == "" { - return s - } - - s2 := *s - s2.groups = append(s2.groups, name) - return &s2 -} - -// SlogJSONHandlerConfig allow the SlogJSONHandler functionality -// to be tweaked. -type SlogJSONHandlerConfig struct { - // Level is the minimum record level that will be logged. - Level slog.Leveler - // ConfiguredFields are fields explicitly set by users to be included in - // the output message. If there are any entries configured, they will be honored. - // If empty, the default fields will be populated and included in the output. - ConfiguredFields []string - // ReplaceAttr is called to rewrite each non-group attribute before - // it is logged. - ReplaceAttr func(groups []string, a slog.Attr) slog.Attr -} - -// SlogJSONHandler is a [slog.Handler] that outputs messages in a json -// format per the config file. -type SlogJSONHandler struct { - *slog.JSONHandler -} - -// NewSlogJSONHandler creates a SlogJSONHandler that outputs to w. -func NewSlogJSONHandler(w io.Writer, cfg SlogJSONHandlerConfig) *SlogJSONHandler { - withCaller := len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, CallerField) - withComponent := len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, ComponentField) - withTimestamp := len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, TimestampField) - - return &SlogJSONHandler{ - JSONHandler: slog.NewJSONHandler(w, &slog.HandlerOptions{ - AddSource: true, - Level: cfg.Level, - ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { - switch a.Key { - case teleport.ComponentKey: - if !withComponent { - return slog.Attr{} - } - if a.Value.Kind() != slog.KindString { - return a - } - - a.Key = ComponentField - case slog.LevelKey: - // The slog.JSONHandler will inject "level" Attr. - // However, this lib's consumer might add an Attr using the same key ("level") and we end up with two records named "level". - // We must check its type before assuming this was injected by the slog.JSONHandler. - lvl, ok := a.Value.Any().(slog.Level) - if !ok { - return a - } - - var level string - switch lvl { - case TraceLevel: - level = "trace" - case slog.LevelDebug: - level = "debug" - case slog.LevelInfo: - level = "info" - case slog.LevelWarn: - level = "warning" - case slog.LevelError: - level = "error" - case slog.LevelError + 1: - level = "fatal" - default: - level = strings.ToLower(lvl.String()) - } - - a.Value = slog.StringValue(level) - case slog.TimeKey: - if !withTimestamp { - return slog.Attr{} - } - - // The slog.JSONHandler will inject "time" Attr. - // However, this lib's consumer might add an Attr using the same key ("time") and we end up with two records named "time". - // We must check its type before assuming this was injected by the slog.JSONHandler. - if a.Value.Kind() != slog.KindTime { - return a - } - - t := a.Value.Time() - if t.IsZero() { - return a - } - - a.Key = TimestampField - a.Value = slog.StringValue(t.Format(time.RFC3339)) - case slog.MessageKey: - // The slog.JSONHandler will inject "msg" Attr. - // However, this lib's consumer might add an Attr using the same key ("msg") and we end up with two records named "msg". - // We must check its type before assuming this was injected by the slog.JSONHandler. - if a.Value.Kind() != slog.KindString { - return a - } - a.Key = messageField - case slog.SourceKey: - if !withCaller { - return slog.Attr{} - } - - // The slog.JSONHandler will inject "source" Attr when AddSource is true. - // However, this lib's consumer might add an Attr using the same key ("source") and we end up with two records named "source". - // We must check its type before assuming this was injected by the slog.JSONHandler. - s, ok := a.Value.Any().(*slog.Source) - if !ok { - return a - } - - file, line := getCaller(s) - a = slog.String(CallerField, fmt.Sprintf("%s:%d", file, line)) - } - - // Convert [slog.KindAny] values that are backed by an [error] or [fmt.Stringer] - // to strings so that only the message is output instead of a json object. The kind is - // first checked to avoid allocating an interface for the values stored inline - // in [slog.Attr]. - if a.Value.Kind() == slog.KindAny { - if err, ok := a.Value.Any().(error); ok { - a.Value = slog.StringValue(err.Error()) - } - - if stringer, ok := a.Value.Any().(fmt.Stringer); ok { - a.Value = slog.StringValue(stringer.String()) - } - } - - return a - }, - }), - } -} - -const ( - traceID = "trace_id" - spanID = "span_id" -) - -func addTracingContextToRecord(ctx context.Context, r *slog.Record) { - span := oteltrace.SpanFromContext(ctx) - if span == nil { - return - } - - spanContext := span.SpanContext() - if spanContext.HasTraceID() { - r.AddAttrs(slog.String(traceID, spanContext.TraceID().String())) - } - - if spanContext.HasSpanID() { - r.AddAttrs(slog.String(spanID, spanContext.SpanID().String())) - } -} - -func (j *SlogJSONHandler) Handle(ctx context.Context, r slog.Record) error { - addTracingContextToRecord(ctx, &r) - return j.JSONHandler.Handle(ctx, r) -} - -// getCaller retrieves source information from the attribute -// and returns the file and line of the caller. The file is -// truncated from the absolute path to package/filename. -func getCaller(s *slog.Source) (file string, line int) { - count := 0 - idx := strings.LastIndexFunc(s.File, func(r rune) bool { - if r == '/' { - count++ - } - - return count == 2 - }) - file = s.File[idx+1:] - line = s.Line - - return file, line -} - -type stringerAttr struct { - fmt.Stringer -} - -// StringerAttr creates a [slog.LogValuer] that will defer to -// the provided [fmt.Stringer]. All slog attributes are always evaluated, -// even if the log event is discarded due to the configured log level. -// A text [slog.Handler] will try to defer evaluation if the attribute is a -// [fmt.Stringer], however, the JSON [slog.Handler] only defers to [json.Marshaler]. -// This means that to defer evaluation and creation of the string representation, -// the object must implement [fmt.Stringer] and [json.Marshaler], otherwise additional -// and unwanted values may be emitted if the logger is configured to use JSON -// instead of text. This wrapping mechanism allows a value that implements [fmt.Stringer], -// to be guaranteed to be lazily constructed and always output the same -// content regardless of the output format. -func StringerAttr(s fmt.Stringer) slog.LogValuer { - return stringerAttr{Stringer: s} -} - -func (s stringerAttr) LogValue() slog.Value { - if s.Stringer == nil { - return slog.StringValue("") - } - return slog.StringValue(s.Stringer.String()) -} - -type typeAttr struct { - val any -} - -// TypeAttr creates a lazily evaluated log value that presents the pretty type name of a value -// as a string. It is roughly equivalent to the '%T' format option, and should only perform -// reflection in the event that logs are actually being generated. -func TypeAttr(val any) slog.LogValuer { - return typeAttr{val} -} - -func (a typeAttr) LogValue() slog.Value { - if t := reflect.TypeOf(a.val); t != nil { - return slog.StringValue(t.String()) - } - return slog.StringValue("nil") -} diff --git a/lib/utils/log/slog_handler_test.go b/lib/utils/log/slog_handler_test.go index 20876b6c4df1c..a75d57fc66994 100644 --- a/lib/utils/log/slog_handler_test.go +++ b/lib/utils/log/slog_handler_test.go @@ -22,13 +22,11 @@ import ( "bytes" "context" "encoding/json" - "fmt" "log/slog" "regexp" "strings" "testing" "testing/slogtest" - "time" "github.com/jonboulle/clockwork" "github.com/stretchr/testify/assert" @@ -41,16 +39,17 @@ import ( func TestSlogTextHandler(t *testing.T) { t.Parallel() clock := clockwork.NewFakeClock() - now := clock.Now().UTC().Format(time.RFC3339) + now := clock.Now().UTC() // Create a handler that doesn't report the caller and automatically sets // the time to whatever time the fake clock has in UTC time. Since the timestamp // is not important for this test overriding, it allows the regex to be simpler. var buf bytes.Buffer h := NewSlogTextHandler(&buf, SlogTextHandlerConfig{ + ConfiguredFields: []string{LevelField, ComponentField, TimestampField}, ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { if a.Key == slog.TimeKey { - a.Value = slog.StringValue(now) + a.Value = slog.TimeValue(now) } return a }, @@ -62,8 +61,7 @@ func TestSlogTextHandler(t *testing.T) { // Group 2: verbosity level of output // Group 3: message contents // Group 4: additional attributes - regex := fmt.Sprintf("^(?:(%s)?)\\s?([A-Z]{4})\\s+(\\w+)(?:\\s(.*))?$", now) - lineRegex := regexp.MustCompile(regex) + lineRegex := regexp.MustCompile(`^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)?\s?([A-Z]{4})\s+(\w+)(?:\s(.*))?$`) results := func() []map[string]any { var ms []map[string]any @@ -75,7 +73,7 @@ func TestSlogTextHandler(t *testing.T) { var m map[string]any matches := lineRegex.FindSubmatch(line) if len(matches) == 0 { - assert.Failf(t, "log output did not match regular expression", "regex: %s output: %s", regex, string(line)) + assert.Failf(t, "log output did not match regular expression", "regex: %s output: %s", lineRegex.String(), string(line)) ms = append(ms, m) continue } diff --git a/lib/utils/log/slog_json_handler.go b/lib/utils/log/slog_json_handler.go new file mode 100644 index 0000000000000..f5c3ec4062b09 --- /dev/null +++ b/lib/utils/log/slog_json_handler.go @@ -0,0 +1,167 @@ +// Teleport +// Copyright (C) 2024 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package log + +import ( + "context" + "fmt" + "io" + "log/slog" + "slices" + "strings" + "time" + + "github.com/gravitational/teleport" +) + +// SlogJSONHandlerConfig allows the SlogJSONHandler functionality +// to be tweaked. +type SlogJSONHandlerConfig struct { + // Level is the minimum record level that will be logged. + Level slog.Leveler + // ConfiguredFields are fields explicitly set by users to be included in + // the output message. If there are any entries configured, they will be honored. + // If empty, the default fields will be populated and included in the output. + ConfiguredFields []string + // ReplaceAttr is called to rewrite each non-group attribute before + // it is logged. + ReplaceAttr func(groups []string, a slog.Attr) slog.Attr +} + +// SlogJSONHandler is a [slog.Handler] that outputs messages in a json +// format per the config file. +type SlogJSONHandler struct { + *slog.JSONHandler +} + +// NewSlogJSONHandler creates a SlogJSONHandler that outputs to w. +func NewSlogJSONHandler(w io.Writer, cfg SlogJSONHandlerConfig) *SlogJSONHandler { + withCaller := len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, CallerField) + withComponent := len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, ComponentField) + withTimestamp := len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, TimestampField) + + return &SlogJSONHandler{ + JSONHandler: slog.NewJSONHandler(w, &slog.HandlerOptions{ + AddSource: true, + Level: cfg.Level, + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + switch a.Key { + case teleport.ComponentKey: + if !withComponent { + return slog.Attr{} + } + if a.Value.Kind() != slog.KindString { + return a + } + + a.Key = ComponentField + case slog.LevelKey: + // The slog.JSONHandler will inject "level" Attr. + // However, this lib's consumer might add an Attr using the same key ("level") and we end up with two records named "level". + // We must check its type before assuming this was injected by the slog.JSONHandler. + lvl, ok := a.Value.Any().(slog.Level) + if !ok { + return a + } + + var level string + switch lvl { + case TraceLevel: + level = "trace" + case slog.LevelDebug: + level = "debug" + case slog.LevelInfo: + level = "info" + case slog.LevelWarn: + level = "warning" + case slog.LevelError: + level = "error" + case slog.LevelError + 1: + level = "fatal" + default: + level = strings.ToLower(lvl.String()) + } + + a.Value = slog.StringValue(level) + case slog.TimeKey: + if !withTimestamp { + return slog.Attr{} + } + + // The slog.JSONHandler will inject "time" Attr. + // However, this lib's consumer might add an Attr using the same key ("time") and we end up with two records named "time". + // We must check its type before assuming this was injected by the slog.JSONHandler. + if a.Value.Kind() != slog.KindTime { + return a + } + + t := a.Value.Time() + if t.IsZero() { + return a + } + + a.Key = TimestampField + a.Value = slog.StringValue(t.Format(time.RFC3339)) + case slog.MessageKey: + // The slog.JSONHandler will inject "msg" Attr. + // However, this lib's consumer might add an Attr using the same key ("msg") and we end up with two records named "msg". + // We must check its type before assuming this was injected by the slog.JSONHandler. + if a.Value.Kind() != slog.KindString { + return a + } + a.Key = messageField + case slog.SourceKey: + if !withCaller { + return slog.Attr{} + } + + // The slog.JSONHandler will inject "source" Attr when AddSource is true. + // However, this lib's consumer might add an Attr using the same key ("source") and we end up with two records named "source". + // We must check its type before assuming this was injected by the slog.JSONHandler. + s, ok := a.Value.Any().(*slog.Source) + if !ok { + return a + } + + file, line := getCaller(s) + a = slog.String(CallerField, fmt.Sprintf("%s:%d", file, line)) + } + + // Convert [slog.KindAny] values that are backed by an [error] or [fmt.Stringer] + // to strings so that only the message is output instead of a json object. The kind is + // first checked to avoid allocating an interface for the values stored inline + // in [slog.Attr]. + if a.Value.Kind() == slog.KindAny { + if err, ok := a.Value.Any().(error); ok { + a.Value = slog.StringValue(err.Error()) + } + + if stringer, ok := a.Value.Any().(fmt.Stringer); ok { + a.Value = slog.StringValue(stringer.String()) + } + } + + return a + }, + }), + } +} + +func (j *SlogJSONHandler) Handle(ctx context.Context, r slog.Record) error { + addTracingContextToRecord(ctx, &r) + return j.JSONHandler.Handle(ctx, r) +} diff --git a/lib/utils/log/slog_text_handler.go b/lib/utils/log/slog_text_handler.go new file mode 100644 index 0000000000000..b3bc4900ac64c --- /dev/null +++ b/lib/utils/log/slog_text_handler.go @@ -0,0 +1,359 @@ +// Teleport +// Copyright (C) 2024 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package log + +import ( + "context" + "fmt" + "io" + "log/slog" + "runtime" + "slices" + "strings" + "sync" + + "github.com/gravitational/trace" + "github.com/sirupsen/logrus" + + "github.com/gravitational/teleport" +) + +// SlogTextHandler is a [slog.Handler] that outputs messages in a textual +// manner as configured by the Teleport configuration. +type SlogTextHandler struct { + cfg SlogTextHandlerConfig + // withCaller indicates whether the location the log was emitted from + // should be included in the output message. + withCaller bool + // withTimestamp indicates whether the times that the log was emitted at + // should be included in the output message. + withTimestamp bool + // component is the Teleport subcomponent that emitted the log. + component string + // preformatted data from previous calls to WithGroup and WithAttrs. + preformatted []byte + // groupPrefix is for the text handler only. + // It holds the prefix for groups that were already pre-formatted. + // A group will appear here when a call to WithGroup is followed by + // a call to WithAttrs. + groupPrefix string + // groups passed in via WithGroup and WithAttrs. + groups []string + // nOpenGroups the number of groups opened in preformatted. + nOpenGroups int + + // mu protects out - it needs to be a pointer so that all cloned + // SlogTextHandler returned from WithAttrs and WithGroup share the + // same mutex. Otherwise, output may be garbled since each clone + // will use its own copy of the mutex to protect out. See + // https://github.com/golang/go/issues/61321 for more details. + mu *sync.Mutex + out io.Writer +} + +// SlogTextHandlerConfig allow the SlogTextHandler functionality +// to be tweaked. +type SlogTextHandlerConfig struct { + // Level is the minimum record level that will be logged. + Level slog.Leveler + // EnableColors allows the level to be printed in color. + EnableColors bool + // Padding to use for various components. + Padding int + // ConfiguredFields are fields explicitly set by users to be included in + // the output message. If there are any entries configured, they will be honored. + // If empty, the default fields will be populated and included in the output. + ConfiguredFields []string + // ReplaceAttr is called to rewrite each non-group attribute before + // it is logged. + ReplaceAttr func(groups []string, a slog.Attr) slog.Attr +} + +// NewSlogTextHandler creates a SlogTextHandler that writes messages to w. +func NewSlogTextHandler(w io.Writer, cfg SlogTextHandlerConfig) *SlogTextHandler { + if cfg.Padding == 0 { + cfg.Padding = defaultComponentPadding + } + + handler := SlogTextHandler{ + cfg: cfg, + withCaller: len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, CallerField), + withTimestamp: len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, TimestampField), + out: w, + mu: &sync.Mutex{}, + } + + if handler.cfg.ConfiguredFields == nil { + handler.cfg.ConfiguredFields = defaultFormatFields + } + + return &handler +} + +// Enabled returns whether the provided level will be included in output. +func (s *SlogTextHandler) Enabled(ctx context.Context, level slog.Level) bool { + minLevel := slog.LevelInfo + if s.cfg.Level != nil { + minLevel = s.cfg.Level.Level() + } + return level >= minLevel +} + +func (s *SlogTextHandler) newHandleState(buf *buffer, freeBuf bool) handleState { + state := handleState{ + h: s, + buf: buf, + freeBuf: freeBuf, + prefix: newBuffer(), + } + if s.cfg.ReplaceAttr != nil { + state.groups = groupPool.Get().(*[]string) + *state.groups = append(*state.groups, s.groups[:s.nOpenGroups]...) + } + return state +} + +// Handle formats the provided record and writes the output to the +// destination. +func (s *SlogTextHandler) Handle(ctx context.Context, r slog.Record) error { + state := s.newHandleState(newBuffer(), true) + defer state.free() + + addTracingContextToRecord(ctx, &r) + + // Built-in attributes. They are not in a group. + stateGroups := state.groups + state.groups = nil // So ReplaceAttrs sees no groups instead of the pre groups. + rep := s.cfg.ReplaceAttr + + if s.withTimestamp && !r.Time.IsZero() { + if rep == nil { + state.appendKey(slog.TimeKey) + state.appendTime(r.Time.Round(0)) + } else { + state.appendAttr(slog.Time(slog.TimeKey, r.Time.Round(0))) + } + } + + // Processing fields in this manner allows users to + // configure the level and component position in the output. + // This matches the behavior of the original logrus. All other + // fields location in the output message are static. + for _, field := range s.cfg.ConfiguredFields { + switch field { + case LevelField: + var color int + var level string + switch r.Level { + case TraceLevel: + level = "TRACE" + color = gray + case slog.LevelDebug: + level = "DEBUG" + color = gray + case slog.LevelInfo: + level = "INFO" + color = blue + case slog.LevelWarn: + level = "WARN" + color = yellow + case slog.LevelError: + level = "ERROR" + color = red + case slog.LevelError + 1: + level = "FATAL" + color = red + default: + color = blue + level = r.Level.String() + } + + if !s.cfg.EnableColors { + color = noColor + } + + level = padMax(level, defaultLevelPadding) + if color != noColor { + level = fmt.Sprintf("\u001B[%dm%s\u001B[0m", color, level) + } + + if rep == nil { + state.appendKey(slog.LevelKey) + // Write the level directly to stat to avoid quoting + // color formatting that exists. + state.buf.WriteString(level) + } else { + state.appendAttr(slog.String(slog.LevelKey, level)) + } + case ComponentField: + // If a component is provided with the attributes, it should be used instead of + // the component set on the handler. Note that if there are multiple components + // specified in the arguments, the one with the lowest index is used and the others are ignored. + // In the example below, the resulting component in the message output would be "alpaca". + // + // logger := logger.With(teleport.ComponentKey, "fish") + // logger.InfoContext(ctx, "llama llama llama", teleport.ComponentKey, "alpaca", "foo", 123, teleport.ComponentKey, "shark") + component := s.component + r.Attrs(func(attr slog.Attr) bool { + if attr.Key != teleport.ComponentKey { + return true + } + component = fmt.Sprintf("[%v]", attr.Value) + component = strings.ToUpper(padMax(component, s.cfg.Padding)) + if component[len(component)-1] != ' ' { + component = component[:len(component)-1] + "]" + } + + return false + }) + + if rep == nil { + state.appendKey(teleport.ComponentKey) + state.appendString(component) + } else { + state.appendAttr(slog.String(teleport.ComponentKey, component)) + } + default: + if _, ok := knownFormatFields[field]; !ok { + return trace.BadParameter("invalid log format key: %v", field) + } + } + } + + if rep == nil { + state.appendKey(slog.MessageKey) + state.appendString(r.Message) + } else { + state.appendAttr(slog.String(slog.MessageKey, r.Message)) + } + + state.groups = stateGroups // Restore groups passed to ReplaceAttrs. + state.appendNonBuiltIns(r) + + if r.PC != 0 && s.withCaller { + fs := runtime.CallersFrames([]uintptr{r.PC}) + f, _ := fs.Next() + + src := slog.Source{ + Function: f.Function, + File: f.File, + Line: f.Line, + } + src.File, src.Line = getCaller(&src) + + if rep == nil { + state.appendKey(slog.SourceKey) + state.appendString(fmt.Sprintf("%s:%d", src.File, src.Line)) + } else { + state.appendAttr(slog.Any(slog.SourceKey, &src)) + } + + } + + state.buf.WriteByte('\n') + + s.mu.Lock() + defer s.mu.Unlock() + _, err := s.out.Write(*state.buf) + return err +} + +func (s *SlogTextHandler) clone() *SlogTextHandler { + // We can't use assignment because we can't copy the mutex. + return &SlogTextHandler{ + cfg: s.cfg, + withCaller: s.withCaller, + withTimestamp: s.withTimestamp, + component: s.component, + preformatted: slices.Clip(s.preformatted), + groupPrefix: s.groupPrefix, + groups: slices.Clip(s.groups), + nOpenGroups: s.nOpenGroups, + out: s.out, + mu: s.mu, // mutex shared among all clones of this handler + } +} + +// WithAttrs clones the current handler with the provided attributes +// added to any existing attributes. The values are preformatted here +// so that they do not need to be formatted per call to Handle. +func (s *SlogTextHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + if len(attrs) == 0 { + return s + } + + s2 := s.clone() + // Pre-format the attributes as an optimization. + state := s2.newHandleState((*buffer)(&s2.preformatted), false) + defer state.free() + state.prefix.WriteString(s.groupPrefix) + + // Remember the position in the buffer, in case all attrs are empty. + pos := state.buf.Len() + state.openGroups() + + nonEmpty := false + for _, a := range attrs { + switch a.Key { + case teleport.ComponentKey: + component := fmt.Sprintf("[%v]", a.Value.String()) + component = strings.ToUpper(padMax(component, s.cfg.Padding)) + if component[len(component)-1] != ' ' { + component = component[:len(component)-1] + "]" + } + s2.component = component + case teleport.ComponentFields: + switch fields := a.Value.Any().(type) { + case map[string]any: + for k, v := range fields { + if state.appendAttr(slog.Any(k, v)) { + nonEmpty = true + } + } + case logrus.Fields: + for k, v := range fields { + if state.appendAttr(slog.Any(k, v)) { + nonEmpty = true + } + } + } + default: + if state.appendAttr(a) { + nonEmpty = true + } + } + } + + if !nonEmpty { + state.buf.SetLen(pos) + } else { + // Remember the new prefix for later keys. + s2.groupPrefix = state.prefix.String() + // Remember how many opened groups are in preformattedAttrs, + // so we don't open them again when we handle a Record. + s2.nOpenGroups = len(s2.groups) + } + + return s2 +} + +// WithGroup opens a new group. +func (s *SlogTextHandler) WithGroup(name string) slog.Handler { + s2 := s.clone() + s2.groups = append(s2.groups, name) + return s2 +} diff --git a/lib/utils/proxy/proxy.go b/lib/utils/proxy/proxy.go index c34356dd981a5..f9995b0c12a92 100644 --- a/lib/utils/proxy/proxy.go +++ b/lib/utils/proxy/proxy.go @@ -25,18 +25,16 @@ import ( "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" apiclient "github.com/gravitational/teleport/api/client" tracessh "github.com/gravitational/teleport/api/observability/tracing/ssh" apiutils "github.com/gravitational/teleport/api/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) -var log = logrus.WithFields(logrus.Fields{ - teleport.ComponentKey: teleport.ComponentConnectProxy, -}) +var log = logutils.NewPackageLogger(teleport.ComponentKey, teleport.ComponentConnectProxy) // A Dialer is a means for a client to establish a SSH connection. type Dialer interface { @@ -194,13 +192,15 @@ func DialerFromEnvironment(addr string, opts ...DialerOptionFunc) Dialer { // If no proxy settings are in environment return regular ssh dialer, // otherwise return a proxy dialer. if proxyURL == nil { - log.Debugf("No proxy set in environment, returning direct dialer.") + log.DebugContext(context.Background(), "No proxy set in environment, returning direct dialer") return directDial{ alpnDialer: options.alpnDialer, proxyHeaderGetter: options.proxyHeaderGetter, } } - log.Debugf("Found proxy %q in environment, returning proxy dialer.", proxyURL) + log.DebugContext(context.Background(), "Found proxy in environment, returning proxy dialer", + "proxy_url", logutils.StringerAttr(proxyURL), + ) return proxyDial{ proxyURL: proxyURL, insecure: options.insecureSkipTLSVerify, diff --git a/lib/utils/proxyconn_test.go b/lib/utils/proxyconn_test.go index cf7a8a82faf35..0eae440a4f72b 100644 --- a/lib/utils/proxyconn_test.go +++ b/lib/utils/proxyconn_test.go @@ -22,6 +22,7 @@ import ( "context" "fmt" "io" + "log/slog" "net" "strings" "testing" @@ -29,7 +30,6 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "github.com/gravitational/teleport" @@ -115,7 +115,7 @@ func TestProxyConnCancel(t *testing.T) { type echoServer struct { listener net.Listener - log logrus.FieldLogger + log *slog.Logger } func newEchoServer() (*echoServer, error) { @@ -125,7 +125,7 @@ func newEchoServer() (*echoServer, error) { } return &echoServer{ listener: listener, - log: logrus.WithField(teleport.ComponentKey, "echo"), + log: slog.With(teleport.ComponentKey, "echo"), }, nil } @@ -154,7 +154,7 @@ func (s *echoServer) handleConn(conn net.Conn) error { if err != nil { return trace.Wrap(err) } - s.log.Infof("Received message: %s.", b) + s.log.InfoContext(context.Background(), "Received message", "receieved_message", string(b)) _, err = conn.Write(b) if err != nil { diff --git a/lib/utils/registry/registry_windows.go b/lib/utils/registry/registry_windows.go index 9b41f3b505fbf..5b254bc127ae8 100644 --- a/lib/utils/registry/registry_windows.go +++ b/lib/utils/registry/registry_windows.go @@ -19,12 +19,13 @@ package registry import ( + "context" "errors" + "log/slog" "os" "strconv" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "golang.org/x/sys/windows/registry" ) @@ -34,14 +35,17 @@ func GetOrCreateRegistryKey(name string) (registry.Key, error) { reg, err := registry.OpenKey(registry.CURRENT_USER, name, registry.QUERY_VALUE|registry.CREATE_SUB_KEY|registry.SET_VALUE) switch { case errors.Is(err, os.ErrNotExist): - log.Debugf("Registry key %v doesn't exist, trying to create it", name) + slog.DebugContext(context.Background(), "Registry key doesn't exist, trying to create it", "key_name", name) reg, _, err = registry.CreateKey(registry.CURRENT_USER, name, registry.QUERY_VALUE|registry.CREATE_SUB_KEY|registry.SET_VALUE) if err != nil { - log.Debugf("Can't create registry key %v: %v", name, err) + slog.DebugContext(context.Background(), "Can't create registry key", + "key_name", name, + "error", err, + ) return reg, err } case err != nil: - log.Errorf("registry.OpenKey returned error: %v", err) + slog.ErrorContext(context.Background(), "registry.OpenKey returned error", "error", err) return reg, err default: return reg, nil @@ -53,12 +57,20 @@ func GetOrCreateRegistryKey(name string) (registry.Key, error) { func WriteDword(k registry.Key, name string, value string) error { dwordValue, err := strconv.ParseUint(value, 10, 32) if err != nil { - log.Debugf("Failed to convert value %v to uint32: %v", value, err) + slog.DebugContext(context.Background(), "Failed to convert value to uint32", + "value", value, + "error", err, + ) return trace.Wrap(err) } err = k.SetDWordValue(name, uint32(dwordValue)) if err != nil { - log.Debugf("Failed to write dword %v: %v to registry key %v: %v", name, value, k, err) + slog.DebugContext(context.Background(), "Failed to write dword to registry key", + "name", name, + "value", value, + "key_name", k, + "error", err, + ) return trace.Wrap(err) } return nil @@ -68,7 +80,12 @@ func WriteDword(k registry.Key, name string, value string) error { func WriteString(k registry.Key, name string, value string) error { err := k.SetStringValue(name, value) if err != nil { - log.Debugf("Failed to write string %v: %v to registry key %v: %v", name, value, k, err) + slog.DebugContext(context.Background(), "Failed to write string to registry key", + "name", name, + "value", value, + "key_name", k, + "error", err, + ) return trace.Wrap(err) } return nil @@ -78,7 +95,12 @@ func WriteString(k registry.Key, name string, value string) error { func WriteMultiString(k registry.Key, name string, values []string) error { err := k.SetStringsValue(name, values) if err != nil { - log.Debugf("Failed to write strings %v: %v to registry key %v: %v", name, values, k, err) + slog.DebugContext(context.Background(), "Failed to write strings to registry key", + "name", name, + "values", values, + "key_name", k, + "error", err, + ) return trace.Wrap(err) } return nil diff --git a/lib/utils/socks/socks.go b/lib/utils/socks/socks.go index 374c1a7c41659..afcd5bdacd424 100644 --- a/lib/utils/socks/socks.go +++ b/lib/utils/socks/socks.go @@ -27,15 +27,8 @@ import ( "strconv" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" - - "github.com/gravitational/teleport" ) -var log = logrus.WithFields(logrus.Fields{ - teleport.ComponentKey: teleport.ComponentSOCKS, -}) - const ( socks5Version byte = 0x05 socks5Reserved byte = 0x00 diff --git a/lib/utils/socks/socks_test.go b/lib/utils/socks/socks_test.go index 4babebc5f8882..0dcc2ddcb44c1 100644 --- a/lib/utils/socks/socks_test.go +++ b/lib/utils/socks/socks_test.go @@ -19,7 +19,9 @@ package socks import ( + "context" "io" + "log/slog" "net" "os" "testing" @@ -99,7 +101,7 @@ func (d *debugServer) Serve() { for { conn, err := d.ln.Accept() if err != nil { - log.Debugf("Failed to accept connection: %v.", err) + slog.DebugContext(context.Background(), "Failed to accept connection", "error", err) break } @@ -114,17 +116,17 @@ func (d *debugServer) handle(conn net.Conn) { remoteAddr, err := Handshake(conn) if err != nil { - log.Debugf("Handshake failed: %v.", err) + slog.DebugContext(context.Background(), "Handshake failed", "error", err) return } n, err := conn.Write([]byte(remoteAddr)) if err != nil { - log.Debugf("Failed to write to connection: %v.", err) + slog.DebugContext(context.Background(), "Failed to write to connection", "error", err) return } if n != len(remoteAddr) { - log.Debugf("Short write, wrote %v wanted %v.", n, len(remoteAddr)) + slog.DebugContext(context.Background(), "Short write", "wrote", n, "wanted", len(remoteAddr)) return } } diff --git a/lib/utils/typical/cached_parser.go b/lib/utils/typical/cached_parser.go index 5a60d3724fc35..27b61defd31bf 100644 --- a/lib/utils/typical/cached_parser.go +++ b/lib/utils/typical/cached_parser.go @@ -19,13 +19,14 @@ package typical import ( + "context" + "log/slog" "os" "strconv" "sync/atomic" "github.com/gravitational/trace" lru "github.com/hashicorp/golang-lru/v2" - "github.com/sirupsen/logrus" ) const ( @@ -56,7 +57,7 @@ type CachedParser[TEnv, TResult any] struct { Parser[TEnv, TResult] cache *lru.Cache[string, Expression[TEnv, TResult]] evictedCount atomic.Uint32 - logger logger + logger *slog.Logger } // NewCachedParser creates a cached predicate expression parser with the given specification. @@ -72,7 +73,7 @@ func NewCachedParser[TEnv, TResult any](spec ParserSpec[TEnv], opts ...ParserOpt return &CachedParser[TEnv, TResult]{ Parser: *parser, cache: cache, - logger: logrus.StandardLogger(), + logger: slog.Default(), }, nil } @@ -88,12 +89,9 @@ func (c *CachedParser[TEnv, TResult]) Parse(expression string) (Expression[TEnv, return nil, trace.Wrap(err) } if evicted := c.cache.Add(expression, parsedExpr); evicted && c.evictedCount.Add(1)%logAfterEvictions == 0 { - c.logger.Infof("%d entries have been evicted from the predicate expression cache, consider increasing TELEPORT_EXPRESSION_CACHE_SIZE", - logAfterEvictions) + c.logger.InfoContext(context.Background(), "entries have been evicted from the predicate expression cache, consider increasing TELEPORT_EXPRESSION_CACHE_SIZE", + "eviticiton_count", logAfterEvictions, + ) } return parsedExpr, nil } - -type logger interface { - Infof(fmt string, args ...any) -} diff --git a/lib/utils/typical/cached_parser_test.go b/lib/utils/typical/cached_parser_test.go index 1bfb67bd7f3c3..1f15207ab3f68 100644 --- a/lib/utils/typical/cached_parser_test.go +++ b/lib/utils/typical/cached_parser_test.go @@ -25,14 +25,6 @@ import ( "github.com/stretchr/testify/require" ) -type testLogger struct { - loggedCount int -} - -func (t *testLogger) Infof(msg string, args ...any) { - t.loggedCount++ -} - func TestCachedParser(t *testing.T) { // A simple cached parser with no environment that always returns an int. p, err := NewCachedParser[struct{}, int](ParserSpec[struct{}]{ @@ -45,9 +37,6 @@ func TestCachedParser(t *testing.T) { require.NoError(t, err) require.NotNil(t, p) - var logger testLogger - p.logger = &logger - // Sanity check we still get errors. _, err = p.Parse(`"hello"`) require.Error(t, err) @@ -93,9 +82,6 @@ func TestCachedParser(t *testing.T) { // Check that each new parsed expression results in an eviction. require.Equal(t, uint32(i+1), p.evictedCount.Load()) - - // Check that no log was printed - require.Equal(t, 0, logger.loggedCount) } // Parse one more expression @@ -103,9 +89,6 @@ func TestCachedParser(t *testing.T) { _, err = p.Parse(expr) require.NoError(t, err) - // Check a log was printed once after $logAfterEvictions evictions. - require.Equal(t, 1, logger.loggedCount) - // Parse another $logAfterEvictions unique expressions to cause // another $logAfterEvictions cache evictions and one more log for i := 0; i < logAfterEvictions; i++ { @@ -113,7 +96,4 @@ func TestCachedParser(t *testing.T) { _, err := p.Parse(expr) require.NoError(t, err) } - - // Check a log was printed twice after 2*$logAfterEvictions evictions. - require.Equal(t, 2, logger.loggedCount) } diff --git a/lib/utils/unpack.go b/lib/utils/unpack.go index 14b213f08a173..e42d38283eba2 100644 --- a/lib/utils/unpack.go +++ b/lib/utils/unpack.go @@ -20,15 +20,16 @@ package utils import ( "archive/tar" + "context" "errors" "io" + "log/slog" "os" "path" "path/filepath" "strings" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "github.com/gravitational/teleport" ) @@ -140,7 +141,10 @@ func extractFile(tarball *tar.Reader, header *tar.Header, dir string, dirMode os case tar.TypeSymlink: return writeSymbolicLink(filepath.Join(dir, header.Name), header.Linkname, dirMode) default: - log.Warnf("Unsupported type flag %v for %v.", header.Typeflag, header.Name) + slog.WarnContext(context.Background(), "Unsupported type flag for taball", + "type_flag", header.Typeflag, + "header", header.Name, + ) } return nil } diff --git a/lib/utils/utils.go b/lib/utils/utils.go index 1990f39ad23e2..4008b452ba6a6 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -24,6 +24,7 @@ import ( "fmt" "io" "io/fs" + "log/slog" "math/rand/v2" "net" "net/url" @@ -38,7 +39,6 @@ import ( "unicode" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/util/validation" "github.com/gravitational/teleport" @@ -124,13 +124,17 @@ func NewTracer(description string) *Tracer { // Start logs start of the trace func (t *Tracer) Start() *Tracer { - log.Debugf("Tracer started %v.", t.Description) + slog.DebugContext(context.Background(), "Tracer started", + "trace", t.Description) return t } // Stop logs stop of the trace func (t *Tracer) Stop() *Tracer { - log.Debugf("Tracer completed %v in %v.", t.Description, time.Since(t.Started)) + slog.DebugContext(context.Background(), "Tracer completed", + "trace", t.Description, + "duration", time.Since(t.Started), + ) return t } diff --git a/lib/web/addr.go b/lib/web/addr.go index f82173b6f7796..3c23bf0637c85 100644 --- a/lib/web/addr.go +++ b/lib/web/addr.go @@ -20,13 +20,14 @@ package web import ( "bufio" + "context" + "log/slog" "net" "net/http" "net/netip" "strings" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/lib/authz" "github.com/gravitational/teleport/lib/utils" @@ -56,7 +57,7 @@ func NewXForwardedForMiddleware(next http.Handler) http.Handler { // Serve with updated client source address. default: next.ServeHTTP( - responseWriterWithClientSrcAddr(w, clientSrcAddr), + responseWriterWithClientSrcAddr(r.Context(), w, clientSrcAddr), requestWithClientSrcAddr(r, clientSrcAddr), ) } @@ -113,11 +114,11 @@ func requestWithClientSrcAddr(r *http.Request, clientSrcAddr net.Addr) *http.Req return r } -func responseWriterWithClientSrcAddr(w http.ResponseWriter, clientSrcAddr net.Addr) http.ResponseWriter { +func responseWriterWithClientSrcAddr(ctx context.Context, w http.ResponseWriter, clientSrcAddr net.Addr) http.ResponseWriter { // Returns the original ResponseWriter if not a http.Hijacker. _, ok := w.(http.Hijacker) if !ok { - logrus.Debug("Provided ResponseWriter is not a hijacker.") + slog.DebugContext(ctx, "Provided ResponseWriter is not a hijacker") return w } diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index a21d165e382de..9a7a0ac625be8 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -49,7 +49,6 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" "github.com/julienschmidt/httprouter" - "github.com/sirupsen/logrus" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" oteltrace "go.opentelemetry.io/otel/trace" tracepb "go.opentelemetry.io/proto/otlp/trace/v1" @@ -102,6 +101,7 @@ import ( "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" "github.com/gravitational/teleport/lib/web/app" websession "github.com/gravitational/teleport/lib/web/session" "github.com/gravitational/teleport/lib/web/terminal" @@ -146,8 +146,6 @@ type healthCheckAppServerFunc func(ctx context.Context, publicAddr string, clust // Handler is HTTP web proxy handler type Handler struct { - // TODO(greedy52) deprecate logrus.FieldLogger. - log logrus.FieldLogger logger *slog.Logger sync.Mutex @@ -356,12 +354,12 @@ func (h *APIHandler) handlePreflight(w http.ResponseWriter, r *http.Request) { servers, err := app.Match(r.Context(), h.handler.cfg.AccessPoint, app.MatchPublicAddr(publicAddr)) if err != nil { - h.handler.log.Info("failed to match application with public addr %s", publicAddr) + h.handler.logger.InfoContext(r.Context(), "failed to match application with public addr", "public_addr", publicAddr) return } if len(servers) == 0 { - h.handler.log.Info("failed to match application with public addr %s", publicAddr) + h.handler.logger.InfoContext(r.Context(), "failed to match application with public addr", "public_addr", publicAddr) return } @@ -454,7 +452,6 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { h := &Handler{ cfg: cfg, - log: newPackageLogger(), logger: slog.Default().With(teleport.ComponentKey, teleport.ComponentWeb), clock: clockwork.NewRealClock(), clusterFeatures: cfg.ClusterFeatures, @@ -512,6 +509,7 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { clock: h.clock, sessionLingeringThreshold: sessionLingeringThreshold, proxySigner: cfg.PROXYSigner, + logger: h.logger, }) if err != nil { return nil, trace.Wrap(err) @@ -521,8 +519,11 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { if cfg.ProxySSHAddr.String() != "" { _, sshPort, err := net.SplitHostPort(cfg.ProxySSHAddr.String()) if err != nil { - h.log.WithError(err).Warnf("Invalid SSH proxy address %q, will use default port %v.", - cfg.ProxySSHAddr.String(), defaults.SSHProxyListenPort) + h.logger.WarnContext(h.cfg.Context, "Invalid SSH proxy address, will use default port", + "error", err, + "ssh_proxy_addr", logutils.StringerAttr(&cfg.ProxySSHAddr), + "default_port", defaults.SSHProxyListenPort, + ) } else { sshPortValue = sshPort } @@ -581,7 +582,7 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { if cfg.StaticFS != nil { index, err := cfg.StaticFS.Open("/index.html") if err != nil { - h.log.WithError(err).Error("Failed to open index file.") + h.logger.ErrorContext(h.cfg.Context, "Failed to open index file", "error", err) return nil, trace.Wrap(err) } defer index.Close() @@ -598,7 +599,7 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { etagFromAppHash, err := readEtagFromAppHash(cfg.StaticFS) if err != nil { - h.log.WithError(err).Error("Could not read apphash from embedded webassets. Using version only as ETag for Web UI assets.") + h.logger.ErrorContext(h.cfg.Context, "Could not read apphash from embedded webassets. Using version only as ETag for Web UI assets", "error", err) } else { etag = etagFromAppHash } @@ -653,12 +654,12 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { } else if strings.HasPrefix(r.URL.Path, "/web/") || r.URL.Path == "/web" { csrfToken, err := csrf.AddCSRFProtection(w, r) if err != nil { - h.log.WithError(err).Warn("Failed to generate CSRF token.") + h.logger.WarnContext(r.Context(), "Failed to generate CSRF token", "error", err) } session, err := h.authenticateWebSession(w, r) if err != nil { - h.log.Debugf("Could not authenticate: %v", err) + h.logger.DebugContext(r.Context(), "Could not authenticate", "error", err) } session.XCSRF = csrfToken @@ -666,7 +667,7 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { httplib.SetIndexContentSecurityPolicy(w.Header(), cfg.ClusterFeatures, r.URL.Path) if err := indexPage.Execute(w, session); err != nil { - h.log.WithError(err).Error("Failed to execute index page template.") + h.logger.ErrorContext(r.Context(), "Failed to execute index page template", "error", err) } } else { http.NotFound(w, r) @@ -780,7 +781,7 @@ func (h *Handler) bindDefaultEndpoints() { h.POST("/webapi/sessions/app", h.WithAuth(h.createAppSession)) // Web sessions - h.POST("/webapi/sessions/web", httplib.WithCSRFProtection(h.WithLimiterHandlerFunc(h.createWebSession))) + h.POST("/webapi/sessions/web", h.WithLimiter(h.createWebSession)) h.DELETE("/webapi/sessions/web", h.WithAuth(h.deleteWebSession)) h.POST("/webapi/sessions/web/renew", h.WithAuth(h.renewWebSession)) h.POST("/webapi/users", h.WithAuth(h.createUserHandle)) @@ -793,7 +794,7 @@ func (h *Handler) bindDefaultEndpoints() { // h.GET("/webapi/users/password/token/:token", h.WithLimiter(h.getResetPasswordTokenHandle)) h.GET("/webapi/users/*wildcard", h.handleGetUserOrResetToken) - h.PUT("/webapi/users/password/token", httplib.WithCSRFProtection(h.changeUserAuthentication)) + h.PUT("/webapi/users/password/token", h.WithLimiter(h.changeUserAuthentication)) h.PUT("/webapi/users/password", h.WithAuth(h.changePassword)) h.POST("/webapi/users/password/token", h.WithAuth(h.createResetPasswordToken)) h.POST("/webapi/users/privilege/token", h.WithAuth(h.createPrivilegeTokenHandle)) @@ -844,6 +845,7 @@ func (h *Handler) bindDefaultEndpoints() { h.GET("/webapi/sites/:site/events/search", h.WithClusterAuth(h.clusterSearchEvents)) // search site events h.GET("/webapi/sites/:site/events/search/sessions", h.WithClusterAuth(h.clusterSearchSessionEvents)) // search site session events h.GET("/webapi/sites/:site/ttyplayback/:sid", h.WithClusterAuth(h.ttyPlaybackHandle)) + h.GET("/webapi/sites/:site/sessionlength/:sid", h.WithClusterAuth(h.sessionLengthHandle)) // scp file transfer h.GET("/webapi/sites/:site/nodes/:server/:login/scp", h.WithClusterAuth(h.transferFile)) @@ -994,6 +996,7 @@ func (h *Handler) bindDefaultEndpoints() { h.GET("/webapi/scripts/integrations/configure/listdatabases-iam.sh", h.WithLimiter(h.awsOIDCConfigureListDatabasesIAM)) h.POST("/webapi/sites/:site/integrations/aws-oidc/:name/deployservice", h.WithClusterAuth(h.awsOIDCDeployService)) h.POST("/webapi/sites/:site/integrations/aws-oidc/:name/deploydatabaseservices", h.WithClusterAuth(h.awsOIDCDeployDatabaseServices)) + h.POST("/webapi/sites/:site/integrations/aws-oidc/:name/listdeployeddatabaseservices", h.WithClusterAuth(h.awsOIDCListDeployedDatabaseService)) h.GET("/webapi/scripts/integrations/configure/deployservice-iam.sh", h.WithLimiter(h.awsOIDCConfigureDeployServiceIAM)) h.POST("/webapi/sites/:site/integrations/aws-oidc/:name/ec2", h.WithClusterAuth(h.awsOIDCListEC2)) h.POST("/webapi/sites/:site/integrations/aws-oidc/:name/eksclusters", h.WithClusterAuth(h.awsOIDCListEKSClusters)) @@ -1273,7 +1276,7 @@ func (h *Handler) AccessGraphAddr() utils.NetAddr { return h.cfg.AccessGraphAddr } -func localSettings(cap types.AuthPreference) (webclient.AuthenticationSettings, error) { +func localSettings(ctx context.Context, cap types.AuthPreference, logger *slog.Logger) (webclient.AuthenticationSettings, error) { as := webclient.AuthenticationSettings{ Type: constants.Local, SecondFactor: types.LegacySecondFactorFromSecondFactors(cap.GetSecondFactors()), @@ -1297,7 +1300,7 @@ func localSettings(cap types.AuthPreference) (webclient.AuthenticationSettings, case err == nil: as.U2F = &webclient.U2FSettings{AppID: u2f.AppID} case !trace.IsNotFound(err): - log.WithError(err).Warnf("Error reading U2F settings") + logger.WarnContext(ctx, "Error reading U2F settings", "error", err) } // Webauthn settings. @@ -1307,7 +1310,7 @@ func localSettings(cap types.AuthPreference) (webclient.AuthenticationSettings, RPID: webConfig.RPID, } case !trace.IsNotFound(err): - log.WithError(err).Warnf("Error reading WebAuthn settings") + logger.WarnContext(ctx, "Error reading WebAuthn settings", "error", err) } return as, nil @@ -1385,7 +1388,7 @@ func deviceTrustDisabled(cap types.AuthPreference) bool { return dtconfig.GetEffectiveMode(cap.GetDeviceTrust()) == constants.DeviceTrustModeOff } -func getAuthSettings(ctx context.Context, authClient authclient.ClientI) (webclient.AuthenticationSettings, error) { +func getAuthSettings(ctx context.Context, authClient authclient.ClientI, logger *slog.Logger) (webclient.AuthenticationSettings, error) { authPreference, err := authClient.GetAuthPreference(ctx) if err != nil { return webclient.AuthenticationSettings{}, trace.Wrap(err) @@ -1395,7 +1398,7 @@ func getAuthSettings(ctx context.Context, authClient authclient.ClientI) (webcli switch authPreference.GetType() { case constants.Local: - as, err = localSettings(authPreference) + as, err = localSettings(ctx, authPreference, logger) if err != nil { return webclient.AuthenticationSettings{}, trace.Wrap(err) } @@ -1474,18 +1477,18 @@ func getAuthSettings(ctx context.Context, authClient authclient.ClientI) (webcli func (h *Handler) traces(w http.ResponseWriter, r *http.Request, _ httprouter.Params, _ *SessionContext) (interface{}, error) { body, err := utils.ReadAtMost(r.Body, teleport.MaxHTTPResponseSize) if err != nil { - h.log.WithError(err).Error("Failed to read traces request") + h.logger.ErrorContext(r.Context(), "Failed to read traces request", "error", err) w.WriteHeader(http.StatusBadRequest) return nil, nil } if err := r.Body.Close(); err != nil { - h.log.WithError(err).Warn("Failed to close traces request body") + h.logger.WarnContext(r.Context(), "Failed to close traces request body", "error", err) } var data tracepb.TracesData if err := protojson.Unmarshal(body, &data); err != nil { - h.log.WithError(err).Error("Failed to unmarshal traces request") + h.logger.ErrorContext(r.Context(), "Failed to unmarshal traces request", "error", err) w.WriteHeader(http.StatusBadRequest) return nil, nil } @@ -1530,7 +1533,7 @@ func (h *Handler) traces(w http.ResponseWriter, r *http.Request, _ httprouter.Pa ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := h.cfg.TraceClient.UploadTraces(ctx, data.ResourceSpans); err != nil { - h.log.WithError(err).Error("Failed to upload traces") + h.logger.ErrorContext(ctx, "Failed to upload traces", "error", err) } }() @@ -1540,7 +1543,7 @@ func (h *Handler) traces(w http.ResponseWriter, r *http.Request, _ httprouter.Pa func (h *Handler) ping(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) { var err error - authSettings, err := getAuthSettings(r.Context(), h.cfg.ProxyClient) + authSettings, err := getAuthSettings(r.Context(), h.cfg.ProxyClient, h.logger) if err != nil { return nil, trace.Wrap(err) } @@ -1634,7 +1637,7 @@ func (h *Handler) pingWithConnector(w http.ResponseWriter, r *http.Request, p ht hasMessageOfTheDay := cap.GetMessageOfTheDay() != "" if slices.Contains(constants.SystemConnectors, connectorName) { - response.Auth, err = localSettings(cap) + response.Auth, err = localSettings(r.Context(), cap, h.logger) if err != nil { return nil, trace.Wrap(err) } @@ -1705,7 +1708,7 @@ func (h *Handler) getWebConfig(w http.ResponseWriter, r *http.Request, p httprou // get all OIDC connectors oidcConnectors, err := h.cfg.ProxyClient.GetOIDCConnectors(r.Context(), false) if err != nil { - h.log.WithError(err).Error("Cannot retrieve OIDC connectors.") + h.logger.ErrorContext(r.Context(), "Cannot retrieve OIDC connectors", "error", err) } for _, item := range oidcConnectors { authProviders = append(authProviders, webclient.WebConfigAuthProvider{ @@ -1719,7 +1722,7 @@ func (h *Handler) getWebConfig(w http.ResponseWriter, r *http.Request, p httprou // get all SAML connectors samlConnectors, err := h.cfg.ProxyClient.GetSAMLConnectors(r.Context(), false) if err != nil { - h.log.WithError(err).Error("Cannot retrieve SAML connectors.") + h.logger.ErrorContext(r.Context(), "Cannot retrieve SAML connectors", "error", err) } for _, item := range samlConnectors { authProviders = append(authProviders, webclient.WebConfigAuthProvider{ @@ -1733,7 +1736,7 @@ func (h *Handler) getWebConfig(w http.ResponseWriter, r *http.Request, p httprou // get all Github connectors githubConnectors, err := h.cfg.ProxyClient.GetGithubConnectors(r.Context(), false) if err != nil { - h.log.WithError(err).Error("Cannot retrieve GitHub connectors.") + h.logger.ErrorContext(r.Context(), "Cannot retrieve GitHub connectors", "error", err) } for _, item := range githubConnectors { authProviders = append(authProviders, webclient.WebConfigAuthProvider{ @@ -1747,7 +1750,7 @@ func (h *Handler) getWebConfig(w http.ResponseWriter, r *http.Request, p httprou // get auth type & second factor type var authSettings webclient.WebConfigAuthSettings if cap, err := h.cfg.ProxyClient.GetAuthPreference(r.Context()); err != nil { - h.log.WithError(err).Error("Cannot retrieve AuthPreferences.") + h.logger.ErrorContext(r.Context(), "Cannot retrieve AuthPreferences", "error", err) authSettings = webclient.WebConfigAuthSettings{ Providers: authProviders, SecondFactor: constants.SecondFactorOff, @@ -1781,7 +1784,7 @@ func (h *Handler) getWebConfig(w http.ResponseWriter, r *http.Request, p httprou tunnelPublicAddr := "" proxyConfig, err := h.cfg.ProxySettings.GetProxySettings(r.Context()) if err != nil { - h.log.WithError(err).Warn("Cannot retrieve ProxySettings, tunnel address won't be set in Web UI.") + h.logger.WarnContext(r.Context(), "Cannot retrieve ProxySettings, tunnel address won't be set in Web UI", "error", err) } else { if clusterFeatures.GetCloud() { tunnelPublicAddr = proxyConfig.SSH.TunnelPublicAddr @@ -1792,7 +1795,7 @@ func (h *Handler) getWebConfig(w http.ResponseWriter, r *http.Request, p httprou canJoinSessions := true recCfg, err := h.cfg.ProxyClient.GetSessionRecordingConfig(r.Context()) if err != nil { - h.log.WithError(err).Error("Cannot retrieve SessionRecordingConfig.") + h.logger.ErrorContext(r.Context(), "Cannot retrieve SessionRecordingConfig", "error", err) } else { canJoinSessions = !services.IsRecordAtProxy(recCfg.GetMode()) } @@ -1802,7 +1805,7 @@ func (h *Handler) getWebConfig(w http.ResponseWriter, r *http.Request, p httprou if automaticUpgradesEnabled { automaticUpgradesTargetVersion, err = h.cfg.AutomaticUpgradesChannels.DefaultVersion(r.Context()) if err != nil { - h.log.WithError(err).Error("Cannot read target version") + h.logger.ErrorContext(r.Context(), "Cannot read target version", "error", err) } } @@ -1833,7 +1836,7 @@ func (h *Handler) getWebConfig(w http.ResponseWriter, r *http.Request, p httprou resource, err := h.cfg.ProxyClient.GetClusterName() if err != nil { - h.log.WithError(err).Warn("Failed to query cluster name.") + h.logger.WarnContext(r.Context(), "Failed to query cluster name", "error", err) } else { webCfg.ProxyClusterName = resource.GetClusterName() } @@ -1978,23 +1981,22 @@ func (h *Handler) motd(w http.ResponseWriter, r *http.Request, p httprouter.Para } func (h *Handler) githubLoginWeb(w http.ResponseWriter, r *http.Request, p httprouter.Params) string { - logger := h.log.WithField("auth", "github") - logger.Debug("Web login start.") + logger := h.logger.With("auth", "github") + logger.DebugContext(r.Context(), "Web login start") req, err := ParseSSORequestParams(r) if err != nil { - logger.WithError(err).Error("Failed to extract SSO parameters from request.") + logger.ErrorContext(r.Context(), "Failed to extract SSO parameters from request", "error", err) return client.LoginFailedRedirectURL } remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { - logger.WithError(err).Error("Failed to parse request remote address.") + logger.ErrorContext(r.Context(), "Failed to parse request remote address", "error", err) return client.LoginFailedRedirectURL } response, err := h.cfg.ProxyClient.CreateGithubAuthRequest(r.Context(), types.GithubAuthRequest{ - CSRFToken: req.CSRFToken, ConnectorID: req.ConnectorID, CreateWebSession: true, ClientRedirectURL: req.ClientRedirectURL, @@ -2002,32 +2004,31 @@ func (h *Handler) githubLoginWeb(w http.ResponseWriter, r *http.Request, p httpr ClientUserAgent: r.UserAgent(), }) if err != nil { - logger.WithError(err).Error("Error creating auth request.") + logger.ErrorContext(r.Context(), "Error creating auth request", "error", err) return client.LoginFailedRedirectURL - } return response.RedirectURL } func (h *Handler) githubLoginConsole(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) { - logger := h.log.WithField("auth", "github") - logger.Debug("Console login start.") + logger := h.logger.With("auth", "github") + logger.DebugContext(r.Context(), "Console login start") req := new(client.SSOLoginConsoleReq) if err := httplib.ReadResourceJSON(r, req); err != nil { - logger.WithError(err).Error("Error reading json.") + logger.ErrorContext(r.Context(), "Error reading json", "error", err) return nil, trace.AccessDenied(SSOLoginFailureMessage) } if err := req.CheckAndSetDefaults(); err != nil { - logger.WithError(err).Error("Missing request parameters.") + logger.ErrorContext(r.Context(), "Missing request parameters", "error", err) return nil, trace.AccessDenied(SSOLoginFailureMessage) } remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { - logger.WithError(err).Error("Failed to parse request remote address.") + logger.ErrorContext(r.Context(), "Failed to parse request remote address", "error", err) return nil, trace.AccessDenied(SSOLoginFailureMessage) } @@ -2045,7 +2046,7 @@ func (h *Handler) githubLoginConsole(w http.ResponseWriter, r *http.Request, p h ClientLoginIP: remoteAddr, }) if err != nil { - logger.WithError(err).Error("Failed to create GitHub auth request.") + logger.ErrorContext(r.Context(), "Failed to create GitHub auth request", "error", err) if strings.Contains(err.Error(), auth.InvalidClientRedirectErrorMessage) { return nil, trace.AccessDenied(SSOLoginFailureInvalidRedirect) } @@ -2058,12 +2059,12 @@ func (h *Handler) githubLoginConsole(w http.ResponseWriter, r *http.Request, p h } func (h *Handler) githubCallback(w http.ResponseWriter, r *http.Request, p httprouter.Params) string { - logger := h.log.WithField("auth", "github") - logger.Debugf("Callback start: %v.", r.URL.Query()) + logger := h.logger.With("auth", "github") + logger.DebugContext(r.Context(), "Callback start", "query", r.URL.Query()) response, err := h.cfg.ProxyClient.ValidateGithubAuthCallback(r.Context(), r.URL.Query()) if err != nil { - logger.WithError(err).Error("Error while processing callback.") + logger.ErrorContext(r.Context(), "Error while processing callback", "error", err) // try to find the auth request, which bears the original client redirect URL. // if found, use it to terminate the flow. @@ -2085,7 +2086,7 @@ func (h *Handler) githubCallback(w http.ResponseWriter, r *http.Request, p httpr // if we created web session, set session cookie and redirect to original url if response.Req.CreateWebSession { - logger.Infof("Redirecting to web browser.") + logger.InfoContext(r.Context(), "Redirecting to web browser") res := &SSOCallbackResponse{ CSRFToken: response.Req.CSRFToken, @@ -2095,26 +2096,26 @@ func (h *Handler) githubCallback(w http.ResponseWriter, r *http.Request, p httpr } if err := SSOSetWebSessionAndRedirectURL(w, r, res, true); err != nil { - logger.WithError(err).Error("Error setting web session.") + logger.ErrorContext(r.Context(), "Error setting web session.", "error", err) return client.LoginFailedRedirectURL } if dwt := response.Session.GetDeviceWebToken(); dwt != nil { - logger.Debug("GitHub WebSession created with device web token") + logger.DebugContext(r.Context(), "GitHub WebSession created with device web token") // if a device web token is present, we must send the user to the device authorize page // to upgrade the session. redirectPath, err := BuildDeviceWebRedirectPath(dwt, res.ClientRedirectURL) if err != nil { - logger.WithError(err).Debug("Invalid device web token.") + logger.DebugContext(r.Context(), "Invalid device web token", "error", err) } return redirectPath } return res.ClientRedirectURL } - logger.Infof("Callback is redirecting to console login.") + logger.InfoContext(r.Context(), "Callback is redirecting to console login") if len(response.Req.SSHPubKey)+len(response.Req.TLSPubKey) == 0 { - logger.Error("Not a web or console login request.") + logger.ErrorContext(r.Context(), "Not a web or console login request") return client.LoginFailedRedirectURL } @@ -2129,7 +2130,7 @@ func (h *Handler) githubCallback(w http.ResponseWriter, r *http.Request, p httpr FIPS: h.cfg.FIPS, }) if err != nil { - logger.WithError(err).Error("Error constructing ssh response") + logger.ErrorContext(r.Context(), "Error constructing ssh response", "error", err) return client.LoginFailedRedirectURL } @@ -2408,7 +2409,7 @@ func (h *Handler) createWebSession(w http.ResponseWriter, r *http.Request, p htt return nil, trace.AccessDenied("direct login with password+otp not supported by this cluster") } if err != nil { - h.log.WithError(err).Warnf("Access attempt denied for user %q.", req.User) + h.logger.WarnContext(r.Context(), "Access attempt denied for user", "user", req.User, "error", err) // Since checking for private key policy meant that they passed authn, // return policy error as is to help direct user. if keys.IsPrivateKeyPolicyError(err) { @@ -2424,7 +2425,7 @@ func (h *Handler) createWebSession(w http.ResponseWriter, r *http.Request, p htt ctx, err := h.auth.newSessionContextFromSession(r.Context(), webSession) if err != nil { - h.log.WithError(err).Warnf("Access attempt denied for user %q.", req.User) + h.logger.WarnContext(r.Context(), "Access attempt denied for user", "user", req.User, "error", err) return nil, trace.AccessDenied("need auth") } @@ -2454,9 +2455,10 @@ func clientMetaFromReq(r *http.Request) *authclient.ForwardedClientMetadata { func (h *Handler) deleteWebSession(w http.ResponseWriter, r *http.Request, _ httprouter.Params, ctx *SessionContext) (interface{}, error) { clt, err := ctx.GetClient() if err != nil { - h.log. - WithError(err). - Warnf("Failed to retrieve user client, SAML single logout will be skipped for user %s.", ctx.GetUser()) + h.logger.WarnContext(r.Context(), "Failed to retrieve user client, SAML single logout will be skipped for user", + "user", ctx.GetUser(), + "error", err, + ) } var user types.User @@ -2464,9 +2466,10 @@ func (h *Handler) deleteWebSession(w http.ResponseWriter, r *http.Request, _ htt if err == nil { user, err = clt.GetUser(r.Context(), ctx.GetUser(), false) if err != nil { - h.log. - WithError(err). - Warnf("Failed to retrieve user during logout, SAML single logout will be skipped for user %s.", ctx.GetUser()) + h.logger.WarnContext(r.Context(), "Failed to retrieve user during logout, SAML single logout will be skipped for user", + "user", ctx.GetUser(), + "error", err, + ) } } @@ -2487,17 +2490,17 @@ func (h *Handler) deleteWebSession(w http.ResponseWriter, r *http.Request, _ htt func (h *Handler) logout(ctx context.Context, w http.ResponseWriter, sctx *SessionContext) error { if err := sctx.Invalidate(ctx); err != nil { - h.log. - WithError(err). - WithField("user", sctx.GetUser()). - Warn("Failed to invalidate sessions") + h.logger.WarnContext(ctx, "Failed to invalidate sessions", + "user", sctx.GetUser(), + "error", err, + ) } - if err := h.auth.releaseResources(sctx.GetUser(), sctx.GetSessionID()); err != nil { - h.log. - WithError(err). - WithField("session_id", sctx.GetSessionID()). - Debug("sessionCache: Failed to release web session") + if err := h.auth.releaseResources(ctx, sctx.GetUser(), sctx.GetSessionID()); err != nil { + h.logger.DebugContext(ctx, "sessionCache: Failed to release web session", + "session_id", sctx.GetSessionID(), + "error", err, + ) } clearSessionCookies(w) @@ -2680,7 +2683,7 @@ func (h *Handler) createResetPasswordToken(w http.ResponseWriter, r *http.Reques func (h *Handler) getResetPasswordTokenHandle(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) { result, err := h.getResetPasswordToken(r.Context(), p.ByName("token")) if err != nil { - h.log.WithError(err).Warn("Failed to fetch a reset password token.") + h.logger.WarnContext(r.Context(), "Failed to fetch a reset password token", "error", err) // We hide the error from the remote user to avoid giving any hints. return nil, trace.AccessDenied("bad or expired token") } @@ -2764,7 +2767,7 @@ func (h *Handler) mfaLoginBegin(w http.ResponseWriter, r *http.Request, p httpro return nil, trace.AccessDenied("invalid credentials") } - return makeAuthenticateChallenge(mfaChallenge), nil + return makeAuthenticateChallenge(mfaChallenge, "" /*channelID*/), nil } // mfaLoginFinish completes the MFA login ceremony, returning a new SSH @@ -3040,7 +3043,7 @@ func (h *Handler) getUserGroupLookup(ctx context.Context, clt apiclient.GetResou UseSearchAsRoles: true, }) if err != nil { - h.log.Infof("Unable to fetch user groups while listing applications, unable to display associated user groups: %v", err) + h.logger.InfoContext(ctx, "Unable to fetch user groups while listing applications, unable to display associated user groups", "error", err) } for _, userGroup := range userGroups { @@ -3110,7 +3113,7 @@ func (h *Handler) clusterUnifiedResourcesGet(w http.ResponseWriter, request *htt AppClusterName: site.GetName(), AllowedAWSRolesLookup: allowedAWSRolesLookup, UserGroupLookup: getUserGroupLookup(), - Logger: h.log, + Logger: h.logger, RequiresRequest: enriched.RequiresRequest, }) unifiedResources = append(unifiedResources, app) @@ -3130,7 +3133,7 @@ func (h *Handler) clusterUnifiedResourcesGet(w http.ResponseWriter, request *htt AppClusterName: site.GetName(), AllowedAWSRolesLookup: allowedAWSRolesLookup, UserGroupLookup: getUserGroupLookup(), - Logger: h.log, + Logger: h.logger, RequiresRequest: enriched.RequiresRequest, }) unifiedResources = append(unifiedResources, app) @@ -3534,9 +3537,9 @@ func (h *Handler) siteNodeConnect( clusterName := site.GetName() if req.SessionID.IsZero() { // An existing session ID was not provided so we need to create a new one. - sessionData, err = h.generateSession(&req, clusterName, sessionCtx) + sessionData, err = h.generateSession(r.Context(), &req, clusterName, sessionCtx) if err != nil { - h.log.WithError(err).Debug("Unable to generate new ssh session.") + h.logger.DebugContext(r.Context(), "Unable to generate new ssh session", "error", err) return nil, trace.Wrap(err) } } else { @@ -3557,19 +3560,23 @@ func (h *Handler) siteNodeConnect( } } - h.log.Debugf("New terminal request for server=%s, login=%s, sid=%s, websid=%s.", - req.Server, req.Login, sessionData.ID, sessionCtx.GetSessionID()) + h.logger.DebugContext(r.Context(), "New terminal request", + "server", req.Server, + "login", req.Login, + "sid", sessionData.ID, + "websid", sessionCtx.GetSessionID(), + ) authAccessPoint, err := site.CachingAccessPoint() if err != nil { - h.log.Debugf("Unable to get auth access point: %v", err) + h.logger.DebugContext(r.Context(), "Unable to get auth access point", "error", err) return nil, trace.Wrap(err) } dialTimeout := apidefaults.DefaultIOTimeout keepAliveInterval := apidefaults.KeepAliveInterval() if netConfig, err := authAccessPoint.GetClusterNetworkingConfig(ctx); err != nil { - h.log.WithError(err).Debug("Unable to fetch cluster networking config.") + h.logger.DebugContext(r.Context(), "Unable to fetch cluster networking config", "error", err) } else { dialTimeout = netConfig.GetSSHDialTimeout() keepAliveInterval = netConfig.GetKeepAliveInterval() @@ -3622,7 +3629,7 @@ func (h *Handler) siteNodeConnect( }, }) if err != nil { - h.log.WithError(err).Error("Unable to create terminal.") + h.logger.ErrorContext(r.Context(), "Unable to create terminal", "error", err) return nil, trace.Wrap(err) } @@ -3630,7 +3637,6 @@ func (h *Handler) siteNodeConnect( defer h.userConns.Add(-1) // start the websocket session with a web-based terminal: - h.log.Infof("Getting terminal to %#v.", req) httplib.MakeTracingHandler(term, teleport.ComponentProxy).ServeHTTP(w, r) return nil, nil @@ -3704,8 +3710,13 @@ func (h *Handler) podConnect( Command: execReq.Command, } - h.log.Debugf("New kube exec request for namespace=%s pod=%s container=%s, sid=%s, websid=%s.", - execReq.Namespace, execReq.Pod, execReq.Container, sess.ID, sctx.GetSessionID()) + h.logger.DebugContext(r.Context(), "New kube exec request", + "namespace", execReq.Namespace, + "pod", execReq.Pod, + "container", execReq.Container, + "sid", sess.ID, + "websid", sctx.GetSessionID(), + ) authAccessPoint, err := site.CachingAccessPoint() if err != nil { @@ -3739,7 +3750,6 @@ func (h *Handler) podConnect( teleportCluster: site.GetName(), ws: ws, keepAliveInterval: keepAliveInterval, - log: h.log.WithField(teleport.ComponentKey, "pod"), logger: h.logger.With(teleport.ComponentKey, "pod"), userClient: clt, localCA: hostCA, @@ -3803,9 +3813,9 @@ func (h *Handler) getKubeExecClusterData(netConfig types.ClusterNetworkingConfig return "https://" + net.JoinHostPort(host, port), tlsServerName, nil } -func (h *Handler) generateSession(req *TerminalRequest, clusterName string, scx *SessionContext) (session.Session, error) { +func (h *Handler) generateSession(ctx context.Context, req *TerminalRequest, clusterName string, scx *SessionContext) (session.Session, error) { owner := scx.cfg.User - h.log.Infof("Generating new session for %s\n", clusterName) + h.logger.InfoContext(ctx, "Generating new session", "cluster", clusterName) host, port, err := serverHostPort(req.Server) if err != nil { @@ -3841,7 +3851,7 @@ func (h *Handler) fetchExistingSession(ctx context.Context, clt authclient.Clien if err != nil { return session.Session{}, nil, trace.Wrap(err) } - h.log.Infof("Attempting to join existing session: %s\n", sessionID) + h.logger.InfoContext(ctx, "Attempting to join existing session", "session_id", sessionID) tracker, err := clt.GetSessionTracker(ctx, string(*sessionID)) if err != nil { @@ -4352,7 +4362,7 @@ func (h *Handler) validateTrustedCluster(w http.ResponseWriter, r *http.Request, validateResponse, err := h.auth.ValidateTrustedCluster(r.Context(), validateRequest) if err != nil { - h.log.WithError(err).Error("Failed validating trusted cluster") + h.logger.ErrorContext(r.Context(), "Failed validating trusted cluster", "error", err) if trace.IsAccessDenied(err) { return nil, trace.AccessDenied("access denied: the cluster token has been rejected") } @@ -4399,7 +4409,7 @@ func (h *Handler) WithClusterAuth(fn ClusterHandler) httprouter.Handle { }) } -func (h *Handler) writeErrToWebSocket(ws *websocket.Conn, err error) { +func (h *Handler) writeErrToWebSocket(ctx context.Context, ws *websocket.Conn, err error) { if err == nil { return } @@ -4410,11 +4420,11 @@ func (h *Handler) writeErrToWebSocket(ws *websocket.Conn, err error) { } env, err := errEnvelope.Marshal() if err != nil { - h.log.WithError(err).Error("error marshaling proto") + h.logger.ErrorContext(ctx, "error marshaling proto", "error", err) return } if err := ws.WriteMessage(websocket.BinaryMessage, env); err != nil { - h.log.WithError(err).Error("error writing proto") + h.logger.ErrorContext(ctx, "error writing proto", "error", err) return } } @@ -4444,7 +4454,7 @@ func (h *Handler) WithClusterAuthWebSocket(fn ClusterWebsocketHandler) httproute // which should be done by downstream users defer ws.Close() if _, err := fn(w, r, p, sctx, site, ws); err != nil { - h.writeErrToWebSocket(ws, err) + h.writeErrToWebSocket(r.Context(), ws, err) } return nil, nil }) @@ -4461,7 +4471,7 @@ func (h *Handler) authenticateWSRequestWithCluster(w http.ResponseWriter, r *htt return nil, nil, nil, trace.Wrap(err) } - site, err := h.getSiteByParams(sctx, p) + site, err := h.getSiteByParams(r.Context(), sctx, p) if err != nil { return nil, nil, nil, trace.Wrap(err) } @@ -4479,7 +4489,7 @@ func (h *Handler) authenticateRequestWithCluster(w http.ResponseWriter, r *http. return nil, nil, trace.Wrap(err) } - site, err := h.getSiteByParams(sctx, p) + site, err := h.getSiteByParams(r.Context(), sctx, p) if err != nil { return nil, nil, trace.Wrap(err) } @@ -4489,18 +4499,18 @@ func (h *Handler) authenticateRequestWithCluster(w http.ResponseWriter, r *http. // getSiteByParams gets the remoteSite (which can represent this local cluster or a // remote trusted cluster) as specified by the ":site" url parameter. -func (h *Handler) getSiteByParams(sctx *SessionContext, p httprouter.Params) (reversetunnelclient.RemoteSite, error) { +func (h *Handler) getSiteByParams(ctx context.Context, sctx *SessionContext, p httprouter.Params) (reversetunnelclient.RemoteSite, error) { clusterName := p.ByName("site") if clusterName == currentSiteShortcut { res, err := h.cfg.ProxyClient.GetClusterName() if err != nil { - h.log.WithError(err).Warn("Failed to query cluster name.") + h.logger.WarnContext(ctx, "Failed to query cluster name", "error", err) return nil, trace.Wrap(err) } clusterName = res.GetClusterName() } - site, err := h.getSiteByClusterName(sctx, clusterName) + site, err := h.getSiteByClusterName(ctx, sctx, clusterName) if err != nil { return nil, trace.Wrap(err) } @@ -4508,16 +4518,16 @@ func (h *Handler) getSiteByParams(sctx *SessionContext, p httprouter.Params) (re return site, nil } -func (h *Handler) getSiteByClusterName(ctx *SessionContext, clusterName string) (reversetunnelclient.RemoteSite, error) { - proxy, err := h.ProxyWithRoles(ctx) +func (h *Handler) getSiteByClusterName(ctx context.Context, sctx *SessionContext, clusterName string) (reversetunnelclient.RemoteSite, error) { + proxy, err := h.ProxyWithRoles(ctx, sctx) if err != nil { - h.log.WithError(err).Warn("Failed to get proxy with roles.") + h.logger.WarnContext(ctx, "Failed to get proxy with roles", "error", err) return nil, trace.Wrap(err) } site, err := proxy.GetSite(clusterName) if err != nil { - h.log.WithError(err).WithField("cluster-name", clusterName).Warn("Failed to query site.") + h.logger.WarnContext(ctx, "Failed to query site", "error", err, "cluster", clusterName) return nil, trace.Wrap(err) } @@ -4541,7 +4551,7 @@ type clusterClientProvider struct { // UserClientForCluster returns a client to the local or remote cluster // identified by clusterName and is authenticated with the identity of the user. func (r clusterClientProvider) UserClientForCluster(ctx context.Context, clusterName string) (authclient.ClientI, error) { - site, err := r.h.getSiteByClusterName(r.ctx, clusterName) + site, err := r.h.getSiteByClusterName(ctx, r.ctx, clusterName) if err != nil { return nil, trace.Wrap(err) } @@ -4595,7 +4605,7 @@ func (h *Handler) WithProvisionTokenAuth(fn ProvisionTokenHandler) httprouter.Ha site, err := h.cfg.Proxy.GetSite(h.auth.clusterName) if err != nil { - h.log.WithError(err).WithField("cluster-name", h.auth.clusterName).Warn("Failed to query cluster.") + h.logger.WarnContext(ctx, "Failed to query cluster", "error", err, "cluster", h.auth.clusterName) return nil, trace.Wrap(err) } @@ -4676,7 +4686,7 @@ func (h *Handler) WithMetaRedirect(fn redirectHandlerFunc) httprouter.Handle { } err := app.MetaRedirect(w, redirectURL) if err != nil { - h.log.WithError(err).Warn("Failed to issue a redirect.") + h.logger.WarnContext(r.Context(), "Failed to issue a redirect", "error", err) } } } @@ -4705,21 +4715,6 @@ func (h *Handler) WithSession(fn ContextHandler) httprouter.Handle { }) } -// WithAuthCookieAndCSRF ensures that a request is authenticated -// for plain old non-AJAX requests (does not check the Bearer header). -// It enforces CSRF checks (except for "safe" methods). -func (h *Handler) WithAuthCookieAndCSRF(fn ContextHandler) httprouter.Handle { - f := func(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) { - sctx, err := h.AuthenticateRequest(w, r, false) - if err != nil { - return nil, trace.Wrap(err) - } - return fn(w, r, p, sctx) - } - - return httplib.WithCSRFProtection(f) -} - // WithUnauthenticatedLimiter adds a conditional IP-based rate limiting that will limit only unauthenticated requests. // This is a good default to use as both Cluster and User auth are checked here, but `WithLimiter` can be used if // you're certain that no authenticated requests will be made. @@ -4863,16 +4858,12 @@ func parseMFAResponseFromRequest(r *http.Request) error { // context and returned. func contextWithMFAResponseFromRequestHeader(ctx context.Context, requestHeader http.Header) (context.Context, error) { if mfaResponseJSON := requestHeader.Get("Teleport-MFA-Response"); mfaResponseJSON != "" { - var resp mfaResponse - if err := json.Unmarshal([]byte(mfaResponseJSON), &resp); err != nil { + mfaResp, err := client.ParseMFAChallengeResponse([]byte(mfaResponseJSON)) + if err != nil { return nil, trace.Wrap(err) } - return mfa.ContextWithMFAResponse(ctx, &proto.MFAAuthenticateResponse{ - Response: &proto.MFAAuthenticateResponse_Webauthn{ - Webauthn: wantypes.CredentialAssertionResponseToProto(resp.WebauthnAssertionResponse), - }, - }), nil + return mfa.ContextWithMFAResponse(ctx, mfaResp), nil } return ctx, nil @@ -4919,7 +4910,7 @@ func (h *Handler) AuthenticateRequestWS(w http.ResponseWriter, r *http.Request) Message: "invalid token", }) if writeErr != nil { - log.Errorf("Error while writing invalid token error to websocket: %s", writeErr) + h.logger.ErrorContext(r.Context(), "Error while writing invalid token error to websocket", "error", writeErr) } return nil, nil, trace.Wrap(err) @@ -4946,10 +4937,10 @@ func (h *Handler) AuthenticateRequestWS(w http.ResponseWriter, r *http.Request) // ProxyWithRoles returns a reverse tunnel proxy verifying the permissions // of the given user. -func (h *Handler) ProxyWithRoles(ctx *SessionContext) (reversetunnelclient.Tunnel, error) { - accessChecker, err := ctx.GetUserAccessChecker() +func (h *Handler) ProxyWithRoles(ctx context.Context, sctx *SessionContext) (reversetunnelclient.Tunnel, error) { + accessChecker, err := sctx.GetUserAccessChecker() if err != nil { - h.log.WithError(err).Warn("Failed to get client roles.") + h.logger.WarnContext(ctx, "Failed to get client roles", "error", err) return nil, trace.Wrap(err) } @@ -5054,8 +5045,6 @@ type SSORequestParams struct { // ConnectorID identifies the SSO connector to use to log in, from // the connector_id query parameter. ConnectorID string - // CSRFToken is the token in the CSRF cookie header. - CSRFToken string } // ParseSSORequestParams extracts the SSO request parameters from an http.Request, @@ -5088,15 +5077,9 @@ func ParseSSORequestParams(r *http.Request) (*SSORequestParams, error) { return nil, trace.BadParameter("missing connector_id query parameter") } - csrfToken, err := csrf.ExtractTokenFromCookie(r) - if err != nil { - return nil, trace.Wrap(err) - } - return &SSORequestParams{ ClientRedirectURL: clientRedirectURL, ConnectorID: connectorID, - CSRFToken: csrfToken, }, nil } @@ -5162,7 +5145,7 @@ func (h *Handler) authExportPublic(w http.ResponseWriter, r *http.Request, p htt }, ) if err != nil { - h.log.WithError(err).Debug("Failed to generate CA Certs.") + h.logger.DebugContext(r.Context(), "Failed to generate CA Certs", "error", err) http.Error(w, err.Error(), trace.ErrorToCode(err)) return } diff --git a/lib/web/apiserver_test.go b/lib/web/apiserver_test.go index 962d52514cad9..2816f2f92b7bb 100644 --- a/lib/web/apiserver_test.go +++ b/lib/web/apiserver_test.go @@ -117,7 +117,6 @@ import ( "github.com/gravitational/teleport/lib/events" "github.com/gravitational/teleport/lib/events/eventstest" "github.com/gravitational/teleport/lib/httplib" - "github.com/gravitational/teleport/lib/httplib/csrf" "github.com/gravitational/teleport/lib/inventory" kubeproxy "github.com/gravitational/teleport/lib/kube/proxy" "github.com/gravitational/teleport/lib/limiter" @@ -947,10 +946,6 @@ func TestWebSessionsCRUD(t *testing.T) { func TestCSRF(t *testing.T) { t.Parallel() s := newWebSuite(t) - type input struct { - reqToken string - cookieToken string - } // create a valid user user := "csrfuser" @@ -958,39 +953,25 @@ func TestCSRF(t *testing.T) { otpSecret := newOTPSharedSecret() s.createUser(t, user, user, pass, otpSecret) - encodedToken1 := "2ebcb768d0090ea4368e42880c970b61865c326172a4a2343b645cf5d7f20992" - encodedToken2 := "bf355921bbf3ef3672a03e410d4194077dfa5fe863c652521763b3e7f81e7b11" - invalid := []input{ - {reqToken: encodedToken2, cookieToken: encodedToken1}, - {reqToken: "", cookieToken: encodedToken1}, - {reqToken: "", cookieToken: ""}, - {reqToken: encodedToken1, cookieToken: ""}, - } - clt := s.client(t) ctx := context.Background() // valid validReq := loginWebOTPParams{ - webClient: clt, - clock: s.clock, - user: user, - password: pass, - otpSecret: otpSecret, - cookieCSRF: &encodedToken1, - headerCSRF: &encodedToken1, + webClient: clt, + clock: s.clock, + user: user, + password: pass, + otpSecret: otpSecret, } loginWebOTP(t, ctx, validReq) - // invalid - for i := range invalid { - req := validReq - req.cookieCSRF = &invalid[i].cookieToken - req.headerCSRF = &invalid[i].reqToken - httpResp, _, err := rawLoginWebOTP(ctx, req) - require.NoError(t, err, "Login via /webapi/sessions/new failed unexpectedly") - assert.Equal(t, http.StatusForbidden, httpResp.StatusCode, "HTTP status code mismatch") - } + // invalid - wrong content-type header + invalidReq := validReq + invalidReq.overrideContentType = "multipart/form-data" + httpResp, _, err := rawLoginWebOTP(ctx, invalidReq) + require.NoError(t, err, "Login via /webapi/sessions/new failed unexpectedly") + require.Equal(t, http.StatusBadRequest, httpResp.StatusCode, "HTTP status code mismatch") } func TestPasswordChange(t *testing.T) { @@ -1795,7 +1776,7 @@ func TestNewTerminalHandler(t *testing.T) { require.Equal(t, validCfg.Term, term.term) require.Equal(t, validCfg.DisplayLogin, term.displayLogin) // newly added - require.NotNil(t, term.log) + require.NotNil(t, term.logger) } func TestUIConfig(t *testing.T) { @@ -5592,10 +5573,6 @@ func TestCreateAppSession_RequireSessionMFA(t *testing.T) { require.NoError(t, err) mfaResp, err := webauthnDev.SolveAuthn(chal) require.NoError(t, err) - mfaRespJSON, err := json.Marshal(mfaResponse{ - WebauthnAssertionResponse: wantypes.CredentialAssertionResponseFromProto(mfaResp.GetWebauthn()), - }) - require.NoError(t, err) // Extract the session ID and bearer token for the current session. rawCookie := *pack.cookies[0] @@ -5629,7 +5606,9 @@ func TestCreateAppSession_RequireSessionMFA(t *testing.T) { PublicAddr: "panel.example.com", ClusterName: "localhost", }, - MFAResponse: string(mfaRespJSON), + MFAResponse: client.MFAChallengeResponse{ + WebauthnAssertionResponse: wantypes.CredentialAssertionResponseFromProto(mfaResp.GetWebauthn()), + }, }, expectMFAVerified: true, }, @@ -5953,13 +5932,9 @@ func TestChangeUserAuthentication_WithPrivacyPolicyEnabledError(t *testing.T) { httpReqData, err := json.Marshal(req) require.NoError(t, err) - // CSRF protected endpoint. - csrfToken := "2ebcb768d0090ea4368e42880c970b61865c326172a4a2343b645cf5d7f20992" httpReq, err := http.NewRequest("PUT", clt.Endpoint("webapi", "users", "password", "token"), bytes.NewBuffer(httpReqData)) require.NoError(t, err) - addCSRFCookieToReq(httpReq, csrfToken) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set(csrf.HeaderName, csrfToken) httpRes, err := httplib.ConvertResponse(clt.RoundTrip(func() (*http.Response, error) { return clt.HTTPClient().Do(httpReq) })) @@ -6104,10 +6079,6 @@ func TestChangeUserAuthentication_settingDefaultClusterAuthPreference(t *testing req, err := http.NewRequest("PUT", clt.Endpoint("webapi", "users", "password", "token"), bytes.NewBuffer(body)) require.NoError(t, err) - csrfToken, err := csrf.GenerateToken() - require.NoError(t, err) - addCSRFCookieToReq(req, csrfToken) - req.Header.Set(csrf.HeaderName, csrfToken) req.Header.Set("Content-Type", "application/json") re, err := clt.Client.RoundTrip(func() (*http.Response, error) { @@ -6129,8 +6100,6 @@ func TestChangeUserAuthentication_settingDefaultClusterAuthPreference(t *testing func TestParseSSORequestParams(t *testing.T) { t.Parallel() - token := "someMeaninglessTokenString" - tests := []struct { name, url string wantErr bool @@ -6142,7 +6111,6 @@ func TestParseSSORequestParams(t *testing.T) { expected: &SSORequestParams{ ClientRedirectURL: "https://localhost:8080/web/cluster/im-a-cluster-name/nodes?search=tunnel&sort=hostname:asc", ConnectorID: "oidc", - CSRFToken: token, }, }, { @@ -6151,7 +6119,6 @@ func TestParseSSORequestParams(t *testing.T) { expected: &SSORequestParams{ ClientRedirectURL: "https://localhost:8080/web/cluster/im-a-cluster-name/nodes?search=tunnel&sort=hostname:asc", ConnectorID: "github", - CSRFToken: token, }, }, { @@ -6160,7 +6127,6 @@ func TestParseSSORequestParams(t *testing.T) { expected: &SSORequestParams{ ClientRedirectURL: "https://localhost:8080/web/cluster/im-a-cluster-name/apps?query=search(%22watermelon%22%2C%20%22this%22)%20%26%26%20labels%5B%22unique-id%22%5D%20%3D%3D%20%22hi%22&sort=name:asc", ConnectorID: "saml", - CSRFToken: token, }, }, { @@ -6179,7 +6145,6 @@ func TestParseSSORequestParams(t *testing.T) { t.Run(tc.name, func(t *testing.T) { req, err := http.NewRequest("", tc.url, nil) require.NoError(t, err) - addCSRFCookieToReq(req, token) params, err := ParseSSORequestParams(req) @@ -7932,15 +7897,6 @@ func (s *WebSuite) url() *url.URL { return u } -func addCSRFCookieToReq(req *http.Request, token string) { - cookie := &http.Cookie{ - Name: csrf.CookieName, - Value: token, - } - - req.AddCookie(cookie) -} - func removeSpace(in string) string { for _, c := range []string{"\n", "\r", "\t"} { in = strings.Replace(in, c, " ", -1) @@ -8251,7 +8207,7 @@ func createProxy(ctx context.Context, t *testing.T, proxyID string, node *regula go func() { if err := mux.Serve(); err != nil && !utils.IsOKNetworkError(err) { - log.WithError(err).Error("Mux encountered err serving") + slog.ErrorContext(context.Background(), "Mux encountered error serving", "error", err) } }() @@ -8325,7 +8281,7 @@ func createProxy(ctx context.Context, t *testing.T, proxyID string, node *regula go func() { if err := sshGRPCServer.Serve(mux.TLS()); err != nil && !utils.IsOKNetworkError(err) { - log.WithError(err).Error("gRPC proxy server terminated unexpectedly") + slog.ErrorContext(context.Background(), "gRPC proxy server terminated unexpectedly", "error", err) } }() @@ -8400,7 +8356,7 @@ func createProxy(ctx context.Context, t *testing.T, proxyID string, node *regula t.Cleanup(webServer.Close) go func() { if err := proxyServer.Serve(mux.SSH()); err != nil && !utils.IsOKNetworkError(err) { - log.WithError(err).Error("SSH proxy server terminated unexpectedly") + slog.ErrorContext(context.Background(), "SSH proxy server terminated unexpectedly", "error", err) } }() @@ -10719,7 +10675,7 @@ func TestWebSocketClosedBeforeSSHSessionCreated(t *testing.T) { // not yet established. stream := terminal.NewStream(ctx, terminal.StreamConfig{ WS: ws, - Logger: utils.NewLogger(), + Logger: utils.NewSlogLoggerForTests(), Handlers: map[string]terminal.WSHandlerFunc{ defaults.WebsocketSessionMetadata: func(ctx context.Context, envelope terminal.Envelope) { if envelope.Type != defaults.WebsocketSessionMetadata { diff --git a/lib/web/apiserver_test_utils.go b/lib/web/apiserver_test_utils.go index e3d1f3c4ba9b1..d7fe5cd0bb3d7 100644 --- a/lib/web/apiserver_test_utils.go +++ b/lib/web/apiserver_test_utils.go @@ -19,6 +19,8 @@ package web import ( + "context" + "log/slog" "net/http" "os" "path/filepath" @@ -38,7 +40,7 @@ func newDebugFileSystem() (http.FileSystem, error) { return nil, trace.Wrap(err) } } - log.Infof("Using filesystem for serving web assets: %s.", assetsPath) + slog.InfoContext(context.TODO(), "Using filesystem for serving web assets", "assets_path", assetsPath) return http.Dir(assetsPath), nil } diff --git a/lib/web/apps.go b/lib/web/apps.go index 5e809d2df29e1..0facc0436d03a 100644 --- a/lib/web/apps.go +++ b/lib/web/apps.go @@ -22,7 +22,6 @@ package web import ( "context" - "encoding/json" "net/http" "sort" @@ -33,7 +32,7 @@ import ( "github.com/gravitational/teleport/api/client/proto" apidefaults "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" - wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes" + "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/httplib" "github.com/gravitational/teleport/lib/reversetunnelclient" "github.com/gravitational/teleport/lib/utils" @@ -69,7 +68,7 @@ func (h *Handler) clusterAppsGet(w http.ResponseWriter, r *http.Request, p httpr UseSearchAsRoles: true, }) if err != nil { - h.log.Debugf("Unable to fetch user groups while listing applications, unable to display associated user groups: %v", err) + h.logger.DebugContext(r.Context(), "Unable to fetch user groups while listing applications, unable to display associated user groups", "error", err) } userGroupLookup := make(map[string]types.UserGroup, len(userGroups)) @@ -94,7 +93,7 @@ func (h *Handler) clusterAppsGet(w http.ResponseWriter, r *http.Request, p httpr if app.IsAWSConsole() { allowedAWSRoles, err := accessChecker.GetAllowedLoginsForResource(app) if err != nil { - h.log.Debugf("Unable to find allowed AWS Roles for app %s, skipping", app.GetName()) + h.logger.DebugContext(r.Context(), "Unable to find allowed AWS Roles for app, skipping", "app", app.GetName()) continue } @@ -105,7 +104,7 @@ func (h *Handler) clusterAppsGet(w http.ResponseWriter, r *http.Request, p httpr for _, userGroupName := range app.GetUserGroups() { userGroup := userGroupLookup[userGroupName] if userGroup == nil { - h.log.Debugf("Unable to find user group %s when creating user groups, skipping", userGroupName) + h.logger.DebugContext(r.Context(), "Unable to find user group when creating user groups, skipping", "user_group", userGroupName) continue } @@ -172,7 +171,7 @@ func (h *Handler) getAppDetails(w http.ResponseWriter, r *http.Request, p httpro for _, required := range requiredAppNames { res, err := h.resolveApp(r.Context(), ctx, ResolveAppParams{ClusterName: clusterName, AppName: required}) if err != nil { - h.log.Errorf("Error getting app details for %s, a required app for %s", required, result.App.GetName()) + h.logger.ErrorContext(r.Context(), "Error getting app details for associated required app", "required_app", required, "app", result.App.GetName()) continue } resp.RequiredAppFQDNs = append(resp.RequiredAppFQDNs, res.App.GetPublicAddr()) @@ -191,7 +190,10 @@ type CreateAppSessionRequest struct { // AWSRole is the AWS role ARN when accessing AWS management console. AWSRole string `json:"arn,omitempty"` // MFAResponse is an optional MFA response used to create an MFA verified app session. - MFAResponse string `json:"mfa_response"` + MFAResponse client.MFAChallengeResponse `json:"mfaResponse"` + // TODO(Joerger): DELETE IN v19.0.0 + // Backwards compatible version of MFAResponse + MFAResponseJSON string `json:"mfa_response"` } // CreateAppSessionResponse is a response to POST /v1/webapi/sessions/app @@ -218,29 +220,28 @@ func (h *Handler) createAppSession(w http.ResponseWriter, r *http.Request, p htt return nil, trace.Wrap(err, "unable to resolve FQDN: %v", req.FQDNHint) } - h.log.Debugf("Creating application web session for %v in %v.", result.App.GetPublicAddr(), result.ClusterName) + h.logger.DebugContext(r.Context(), "Creating application web session", "app_public_addr", result.App.GetPublicAddr(), "cluster", result.ClusterName) // Ensuring proxy can handle the connection is only done when the request is // coming from the WebUI. if h.healthCheckAppServer != nil && !app.HasClientCert(r) { - h.log.Debugf("Ensuring proxy can handle requests requests for application %q.", result.App.GetName()) + h.logger.DebugContext(r.Context(), "Ensuring proxy can handle requests requests for application", "app", result.App.GetName()) err := h.healthCheckAppServer(r.Context(), result.App.GetPublicAddr(), result.ClusterName) if err != nil { return nil, trace.ConnectionProblem(err, "Unable to serve application requests. Please try again. If the issue persists, verify if the Application Services are connected to Teleport.") } } - var mfaProtoResponse *proto.MFAAuthenticateResponse - if req.MFAResponse != "" { - var resp mfaResponse - if err := json.Unmarshal([]byte(req.MFAResponse), &resp); err != nil { - return nil, trace.Wrap(err) - } + mfaResponse, err := req.MFAResponse.GetOptionalMFAResponseProtoReq() + if err != nil { + return nil, trace.Wrap(err) + } - mfaProtoResponse = &proto.MFAAuthenticateResponse{ - Response: &proto.MFAAuthenticateResponse_Webauthn{ - Webauthn: wantypes.CredentialAssertionResponseToProto(resp.WebauthnAssertionResponse), - }, + // Fallback to backwards compatible mfa response. + if mfaResponse == nil && req.MFAResponseJSON != "" { + mfaResponse, err = client.ParseMFAChallengeResponse([]byte(req.MFAResponseJSON)) + if err != nil { + return nil, trace.Wrap(err) } } @@ -263,7 +264,7 @@ func (h *Handler) createAppSession(w http.ResponseWriter, r *http.Request, p htt PublicAddr: result.App.GetPublicAddr(), ClusterName: result.ClusterName, AWSRoleARN: req.AWSRole, - MFAResponse: mfaProtoResponse, + MFAResponse: mfaResponse, AppName: result.App.GetName(), URI: result.App.GetURI(), ClientAddr: r.RemoteAddr, @@ -315,7 +316,7 @@ func (h *Handler) resolveApp(ctx context.Context, scx *SessionContext, params Re } // Get a reverse tunnel proxy aware of the user's permissions. - proxy, err := h.ProxyWithRoles(scx) + proxy, err := h.ProxyWithRoles(ctx, scx) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/web/autoupdate_rfd109.go b/lib/web/autoupdate_rfd109.go index 3bbdd0175b106..7b2a438e7f477 100644 --- a/lib/web/autoupdate_rfd109.go +++ b/lib/web/autoupdate_rfd109.go @@ -62,10 +62,10 @@ func (h *Handler) automaticUpgrades109(w http.ResponseWriter, r *http.Request, p // Finally, we treat the request based on its type switch requestType { case "version": - h.log.Debugf("Agent requesting version for channel %s", channelName) + h.logger.DebugContext(r.Context(), "Agent requesting version for channel", "channel", channelName) return h.automaticUpgradesVersion109(w, r, channelName) case "critical": - h.log.Debugf("Agent requesting criticality for channel %s", channelName) + h.logger.DebugContext(r.Context(), "Agent requesting criticality for channel", "channel", channelName) return h.automaticUpgradesCritical109(w, r, channelName) default: return nil, trace.BadParameter("requestType path must end with 'version' or 'critical'") diff --git a/lib/web/desktop.go b/lib/web/desktop.go index b72d4108b5ef8..63a6bdee24508 100644 --- a/lib/web/desktop.go +++ b/lib/web/desktop.go @@ -25,6 +25,7 @@ import ( "crypto/tls" "errors" "io" + "log/slog" "math/rand/v2" "net" "net/http" @@ -33,7 +34,6 @@ import ( "github.com/gorilla/websocket" "github.com/gravitational/trace" "github.com/julienschmidt/httprouter" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/constants" @@ -48,6 +48,7 @@ import ( "github.com/gravitational/teleport/lib/reversetunnelclient" "github.com/gravitational/teleport/lib/srv/desktop/tdp" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) // GET /webapi/sites/:site/desktops/:desktopName/connect?access_token=&username= @@ -64,15 +65,18 @@ func (h *Handler) desktopConnectHandle( return nil, trace.BadParameter("missing desktopName in request URL") } - log := sctx.cfg.Log.WithField("desktop-name", desktopName).WithField("cluster-name", site.GetName()) - log.Debug("New desktop access websocket connection") + log := sctx.cfg.Log.With( + "desktop_name", desktopName, + "cluster_name", site.GetName(), + ) + log.DebugContext(r.Context(), "New desktop access websocket connection") if err := h.createDesktopConnection(r, desktopName, site.GetName(), log, sctx, site, ws); err != nil { // createDesktopConnection makes a best effort attempt to send an error to the user // (via websocket) before terminating the connection. We log the error here, but // return nil because our HTTP middleware will try to write the returned error in JSON // format, and this will fail since the HTTP connection has been upgraded to websockets. - log.Error(err) + log.ErrorContext(r.Context(), "creating desktop connection failed", "error", err) } return nil, nil @@ -82,7 +86,7 @@ func (h *Handler) createDesktopConnection( r *http.Request, desktopName string, clusterName string, - log *logrus.Entry, + log *slog.Logger, sctx *SessionContext, site reversetunnelclient.RemoteSite, ws *websocket.Conn, @@ -102,7 +106,7 @@ func (h *Handler) createDesktopConnection( if err != nil { return sendTDPError(err) } - log.Debugf("Attempting to connect to desktop using username=%v\n", username) + log.DebugContext(ctx, "Attempting to connect to desktop", "username", username) // Read the tdp.ClientScreenSpec from the websocket. // This is always the first thing sent by the client. @@ -123,7 +127,11 @@ func (h *Handler) createDesktopConnection( )) } - log.Debugf("Attempting to connect to desktop using username=%v, width=%v, height=%v\n", username, width, height) + log.DebugContext(ctx, "Attempting to connect to desktop", + "username", username, + "width", width, + "height", height, + ) // Pick a random Windows desktop service as our gateway. // When agent mode is implemented in the service, we'll have to filter out @@ -190,7 +198,7 @@ func (h *Handler) createDesktopConnection( clientSrcAddr: clientSrcAddr, clientDstAddr: clientDstAddr, } - serviceConn, _, err := c.connectToWindowsService(clusterName, validServiceIDs) + serviceConn, _, err := c.connectToWindowsService(ctx, clusterName, validServiceIDs) if err != nil { return sendTDPError(trace.Wrap(err, "cannot connect to Windows Desktop Service")) } @@ -201,7 +209,7 @@ func (h *Handler) createDesktopConnection( if err := serviceConnTLS.HandshakeContext(ctx); err != nil { return sendTDPError(err) } - log.Debug("Connected to windows_desktop_service") + log.DebugContext(ctx, "Connected to windows_desktop_service") tdpConn := tdp.NewConn(serviceConnTLS) @@ -217,7 +225,7 @@ func (h *Handler) createDesktopConnection( return sendTDPError(err) } for _, msg := range withheld { - log.Debugf("Sending withheld message: %v", msg) + log.DebugContext(ctx, "Sending withheld message", "messgage", logutils.TypeAttr(msg)) if err := tdpConn.WriteMessage(msg); err != nil { return sendTDPError(err) } @@ -228,7 +236,10 @@ func (h *Handler) createDesktopConnection( // proxyWebsocketConn hangs here until connection is closed handleProxyWebsocketConnErr( - proxyWebsocketConn(ws, serviceConnTLS), log) + ctx, + proxyWebsocketConn(ws, serviceConnTLS), + log, + ) return nil } @@ -410,7 +421,7 @@ func (h *Handler) performSessionMFACeremony( if tdp.MessageType(buf[0]) != tdp.TypeMFA { // This is not an MFA message, withhold it for later. msg, err := tdp.Decode(buf) - h.log.Debugf("Received non-MFA message, withholding:", msg) + h.logger.DebugContext(ctx, "Received non-MFA message, withholding", "msg_type", logutils.TypeAttr(msg)) if err != nil { return nil, trace.Wrap(err) } @@ -465,7 +476,7 @@ func readClientScreenSpec(ws *websocket.Conn) (*tdp.ClientScreenSpec, error) { } type connector struct { - log *logrus.Entry + log *slog.Logger clt authclient.ClientI site reversetunnelclient.RemoteSite clientSrcAddr net.Addr @@ -476,17 +487,21 @@ type connector struct { // by trying each of the services provided. It returns an error if it could not connect // to any of the services or if it encounters an error that is not a connection problem. func (c *connector) connectToWindowsService( + ctx context.Context, clusterName string, desktopServiceIDs []string, ) (conn net.Conn, version string, err error) { for _, id := range desktopServiceIDs { - conn, ver, err := c.tryConnect(clusterName, id) + conn, ver, err := c.tryConnect(ctx, clusterName, id) if err != nil && !trace.IsConnectionProblem(err) { return nil, "", trace.WrapWithMessage(err, "error connecting to windows_desktop_service %q", id) } if trace.IsConnectionProblem(err) { - c.log.Warnf("failed to connect to windows_desktop_service %q: %v", id, err) + c.log.WarnContext(ctx, "failed to connect to windows_desktop_service", + "windows_desktop_service_id", id, + "error", err, + ) continue } if err == nil { @@ -496,17 +511,19 @@ func (c *connector) connectToWindowsService( return nil, "", trace.Errorf("failed to connect to any windows_desktop_service") } -func (c *connector) tryConnect(clusterName, desktopServiceID string) (conn net.Conn, version string, err error) { - service, err := c.clt.GetWindowsDesktopService(context.Background(), desktopServiceID) +func (c *connector) tryConnect(ctx context.Context, clusterName, desktopServiceID string) (conn net.Conn, version string, err error) { + service, err := c.clt.GetWindowsDesktopService(ctx, desktopServiceID) if err != nil { - log.Errorf("Error finding service with id %s", desktopServiceID) + c.log.ErrorContext(ctx, "Error finding service", "service_id", desktopServiceID, "error", err) return nil, "", trace.NotFound("could not find windows desktop service %s: %v", desktopServiceID, err) } ver := service.GetTeleportVersion() - *c.log = *c.log.WithField("windows-service-version", ver) - *c.log = *c.log.WithField("windows-service-uuid", service.GetName()) - *c.log = *c.log.WithField("windows-service-addr", service.GetAddr()) + *c.log = *c.log.With( + "windows_service_version", ver, + "windows_service_uuid", service.GetName(), + "windows_service_addr", service.GetAddr(), + ) conn, err = c.site.DialTCP(reversetunnelclient.DialParams{ From: c.clientSrcAddr, @@ -625,9 +642,9 @@ func proxyWebsocketConn(ws *websocket.Conn, wds net.Conn) error { // handleProxyWebsocketConnErr handles the error returned by proxyWebsocketConn by // unwrapping it and determining whether to log an error. -func handleProxyWebsocketConnErr(proxyWsConnErr error, log *logrus.Entry) { +func handleProxyWebsocketConnErr(ctx context.Context, proxyWsConnErr error, log *slog.Logger) { if proxyWsConnErr == nil { - log.Debug("proxyWebsocketConn returned with no error") + log.DebugContext(ctx, "proxyWebsocketConn returned with no error") return } @@ -645,7 +662,7 @@ func handleProxyWebsocketConnErr(proxyWsConnErr error, log *logrus.Entry) { switch closeErr.Code { case websocket.CloseNormalClosure, // when the user hits "disconnect" from the menu websocket.CloseGoingAway: // when the user closes the tab - log.Debugf("Web socket closed by client with code: %v", closeErr.Code) + log.DebugContext(ctx, "Web socket closed by client", "close_code", closeErr.Code) return } return @@ -656,7 +673,7 @@ func handleProxyWebsocketConnErr(proxyWsConnErr error, log *logrus.Entry) { } } - log.WithError(proxyWsConnErr).Warning("Error proxying a desktop protocol websocket to windows_desktop_service") + log.WarnContext(ctx, "Error proxying a desktop protocol websocket to windows_desktop_service", "error", proxyWsConnErr) } // sendTDPAlert sends a tdp Notification over the supplied websocket with the diff --git a/lib/web/desktop/playback.go b/lib/web/desktop/playback.go index 38fcb8c38b0b9..db283af906c7d 100644 --- a/lib/web/desktop/playback.go +++ b/lib/web/desktop/playback.go @@ -22,10 +22,10 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "time" "github.com/gorilla/websocket" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/lib/player" @@ -65,7 +65,8 @@ type actionMessage struct { // ReceivePlaybackActions handles logic for receiving playbackAction messages // over the websocket and updating the player state accordingly. func ReceivePlaybackActions( - log logrus.FieldLogger, + ctx context.Context, + logger *slog.Logger, ws *websocket.Conn, player *player.Player) { // playback always starts in a playing state @@ -78,7 +79,7 @@ func ReceivePlaybackActions( // Connection close errors are expected if the user closes the tab. // Only log unexpected errors to avoid cluttering the logs. if !utils.IsOKNetworkError(err) { - log.Warnf("websocket read error: %v", err) + logger.WarnContext(ctx, "websocket read error", "error", err) } return } @@ -98,7 +99,7 @@ func ReceivePlaybackActions( case actionSeek: player.SetPos(time.Duration(action.Pos) * time.Millisecond) default: - log.Warnf("invalid desktop playback action: %v", action.Action) + slog.WarnContext(ctx, "invalid desktop playback action", "action", action.Action) return } } @@ -108,7 +109,7 @@ func ReceivePlaybackActions( // over a websocket. func PlayRecording( ctx context.Context, - log logrus.FieldLogger, + log *slog.Logger, ws *websocket.Conn, player *player.Player) { player.Play() @@ -122,18 +123,18 @@ func PlayRecording( // Attempt to JSONify the error (escaping any quotes) msg, err := json.Marshal(playerErr.Error()) if err != nil { - log.Warnf("failed to marshal player error message: %v", err) + log.WarnContext(ctx, "failed to marshal player error message", "error", err) msg = []byte(`"internal server error"`) } //lint:ignore QF1012 this write needs to happen in a single operation bytes := []byte(fmt.Sprintf(`{"message":"error", "errorText":%s}`, string(msg))) if err := ws.WriteMessage(websocket.BinaryMessage, bytes); err != nil { - log.Errorf("failed to write error message: %v", err) + log.ErrorContext(ctx, "failed to write error message", "error", err) } return } if err := ws.WriteMessage(websocket.BinaryMessage, []byte(`{"message":"end"}`)); err != nil { - log.Errorf("failed to write end message: %v", err) + log.ErrorContext(ctx, "failed to write end message", "error", err) } return } @@ -145,7 +146,7 @@ func PlayRecording( } msg, err := utils.FastMarshal(evt) if err != nil { - log.Errorf("failed to marshal desktop event: %v", err) + log.ErrorContext(ctx, "failed to marshal desktop event", "error", err) ws.WriteMessage(websocket.BinaryMessage, []byte(`{"message":"error","errorText":"server error"}`)) return } @@ -153,7 +154,7 @@ func PlayRecording( // Connection close errors are expected if the user closes the tab. // Only log unexpected errors to avoid cluttering the logs. if !utils.IsOKNetworkError(err) { - log.Warnf("websocket write error: %v", err) + log.WarnContext(ctx, "websocket write error", "error", err) } return } diff --git a/lib/web/desktop/playback_test.go b/lib/web/desktop/playback_test.go index 12c52ad7e2862..da284e4d18792 100644 --- a/lib/web/desktop/playback_test.go +++ b/lib/web/desktop/playback_test.go @@ -82,7 +82,6 @@ func newServer(t *testing.T, streamInterval time.Duration, events []apievents.Au fs := eventstest.NewFakeStreamer(events, streamInterval) log := utils.NewSlogLoggerForTests() - logrusLogger := utils.NewLoggerForTests() s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { upgrader := websocket.Upgrader{ @@ -103,7 +102,7 @@ func newServer(t *testing.T, streamInterval time.Duration, events []apievents.Au }) assert.NoError(t, err) player.Play() - desktop.PlayRecording(r.Context(), logrusLogger, ws, player) + desktop.PlayRecording(r.Context(), log, ws, player) })) t.Cleanup(s.Close) diff --git a/lib/web/desktop_playback.go b/lib/web/desktop_playback.go index 1467c14a28165..37611058a8131 100644 --- a/lib/web/desktop_playback.go +++ b/lib/web/desktop_playback.go @@ -58,7 +58,7 @@ func (h *Handler) desktopPlaybackHandle( Context: r.Context(), }) if err != nil { - h.log.Errorf("couldn't create player for session %v: %v", sID, err) + h.logger.ErrorContext(r.Context(), "couldn't create player for session", "session_id", sID, "error", err) ws.WriteMessage(websocket.BinaryMessage, []byte(`{"message": "error", "errorText": "Internal server error"}`)) return nil, nil @@ -71,13 +71,13 @@ func (h *Handler) desktopPlaybackHandle( go func() { defer cancel() - desktop.ReceivePlaybackActions(h.log, ws, player) + desktop.ReceivePlaybackActions(ctx, h.logger, ws, player) }() go func() { defer cancel() defer ws.Close() - desktop.PlayRecording(ctx, h.log, ws, player) + desktop.PlayRecording(ctx, h.logger, ws, player) }() <-ctx.Done() diff --git a/lib/web/device_trust.go b/lib/web/device_trust.go index dce54528bdcc7..1739ff583faf0 100644 --- a/lib/web/device_trust.go +++ b/lib/web/device_trust.go @@ -66,17 +66,17 @@ func (h *Handler) deviceWebConfirm(w http.ResponseWriter, r *http.Request, _ htt }) switch { case err != nil: - h.log. - WithError(err). - WithField("user", sessionCtx.GetUser()). - Warn("Device web authentication confirm failed") + h.logger.WarnContext(ctx, "Device web authentication confirm failed", + "error", err, + "user", sessionCtx.GetUser(), + ) // err swallowed on purpose. default: // Preemptively release session from cache, as its certificates are now // updated. // The WebSession watcher takes care of this in other proxy instances // (see [sessionCache.watchWebSessions]). - h.auth.releaseResources(sessionCtx.GetUser(), sessionCtx.GetSessionID()) + h.auth.releaseResources(r.Context(), sessionCtx.GetUser(), sessionCtx.GetSessionID()) } // Always redirect back to the dashboard, regardless of outcome. @@ -84,10 +84,10 @@ func (h *Handler) deviceWebConfirm(w http.ResponseWriter, r *http.Request, _ htt redirectTo, err := h.getRedirectPath(unsafeRedirectURI) if err != nil { - h.log. - WithError(err). - WithField("redirect_uri", unsafeRedirectURI). - Debug("Unable to parse redirectURI") + h.logger.DebugContext(ctx, "Unable to parse redirectURI", + "error", err, + "redirect_uri", unsafeRedirectURI, + ) } http.Redirect(w, r, redirectTo, http.StatusSeeOther) diff --git a/lib/web/features.go b/lib/web/features.go index 7eeb70eb9f060..a05085417e6f9 100644 --- a/lib/web/features.go +++ b/lib/web/features.go @@ -33,7 +33,7 @@ func (h *Handler) SetClusterFeatures(features proto.Features) { defer h.Mutex.Unlock() if !bytes.Equal(h.clusterFeatures.CloudAnonymizationKey, features.CloudAnonymizationKey) { - h.log.Info("Received new cloud anonymization key from server") + h.logger.InfoContext(h.cfg.Context, "Received new cloud anonymization key from server") } entitlements.BackfillFeatures(&features) @@ -54,25 +54,25 @@ func (h *Handler) GetClusterFeatures() proto.Features { // The watcher doesn't ping the auth server immediately upon start because features are // already set by the config object in `NewHandler`. func (h *Handler) startFeatureWatcher() { - ticker := h.clock.NewTicker(h.cfg.FeatureWatchInterval) - h.log.WithField("interval", h.cfg.FeatureWatchInterval).Info("Proxy handler features watcher has started") ctx := h.cfg.Context + ticker := h.clock.NewTicker(h.cfg.FeatureWatchInterval) + h.logger.InfoContext(ctx, "Proxy handler features watcher has started", "interval", h.cfg.FeatureWatchInterval) defer ticker.Stop() for { select { case <-ticker.Chan(): - h.log.Info("Pinging auth server for features") + h.logger.InfoContext(ctx, "Pinging auth server for features") pingResponse, err := h.GetProxyClient().Ping(ctx) if err != nil { - h.log.WithError(err).Error("Auth server ping failed") + h.logger.ErrorContext(ctx, "Auth server ping failed", "error", err) continue } h.SetClusterFeatures(*pingResponse.ServerFeatures) - h.log.WithField("features", pingResponse.ServerFeatures).Info("Done updating proxy features") + h.logger.InfoContext(ctx, "Done updating proxy features", "features", pingResponse.ServerFeatures) case <-ctx.Done(): - h.log.Info("Feature service has stopped") + h.logger.InfoContext(ctx, "Feature service has stopped") return } } diff --git a/lib/web/features_test.go b/lib/web/features_test.go index 3798e819b46eb..52fcbddc91d0a 100644 --- a/lib/web/features_test.go +++ b/lib/web/features_test.go @@ -71,7 +71,6 @@ func TestFeaturesWatcher(t *testing.T) { }, clock: clock, clusterFeatures: proto.Features{}, - log: newPackageLogger(), logger: slog.Default().With(teleport.ComponentKey, teleport.ComponentWeb), } diff --git a/lib/web/files.go b/lib/web/files.go index 53248258dd034..1c48dbf4f745e 100644 --- a/lib/web/files.go +++ b/lib/web/files.go @@ -20,7 +20,6 @@ package web import ( "context" - "encoding/json" "errors" "net/http" "time" @@ -35,7 +34,6 @@ import ( "github.com/gravitational/teleport/api/utils/keys" "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/teleport/lib/auth/authclient" - wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/multiplexer" "github.com/gravitational/teleport/lib/reversetunnelclient" @@ -56,8 +54,8 @@ type fileTransferRequest struct { remoteLocation string // filename is a file name filename string - // webauthn is an optional parameter that contains a webauthn response string used to issue single use certs - webauthn string + // mfaResponse is an optional parameter that contains an mfa response string used to issue single use certs + mfaResponse string // fileTransferRequestID is used to find a FileTransferRequest on a session fileTransferRequestID string // moderatedSessonID is an ID of a moderated session that has completed a @@ -74,11 +72,25 @@ func (h *Handler) transferFile(w http.ResponseWriter, r *http.Request, p httprou remoteLocation: query.Get("location"), filename: query.Get("filename"), namespace: defaults.Namespace, - webauthn: query.Get("webauthn"), + mfaResponse: query.Get("mfaResponse"), fileTransferRequestID: query.Get("fileTransferRequestId"), moderatedSessionID: query.Get("moderatedSessionId"), } + // Check for old query parameter, uses the same data structure. + // TODO(Joerger): DELETE IN v19.0.0 + if req.mfaResponse == "" { + req.mfaResponse = query.Get("webauthn") + } + + var mfaResponse *proto.MFAAuthenticateResponse + if req.mfaResponse != "" { + var err error + if mfaResponse, err = client.ParseMFAChallengeResponse([]byte(req.mfaResponse)); err != nil { + return nil, trace.Wrap(err) + } + } + // Send an error if only one of these params has been sent. Both should exist or not exist together if (req.fileTransferRequestID != "") != (req.moderatedSessionID != "") { return nil, trace.BadParameter("fileTransferRequestId and moderatedSessionId must both be included in the same request.") @@ -107,7 +119,7 @@ func (h *Handler) transferFile(w http.ResponseWriter, r *http.Request, p httprou return nil, trace.Wrap(err) } - if mfaReq.Required && query.Get("webauthn") == "" { + if mfaReq.Required && mfaResponse == nil { return nil, trace.AccessDenied("MFA required for file transfer") } @@ -135,8 +147,8 @@ func (h *Handler) transferFile(w http.ResponseWriter, r *http.Request, p httprou return nil, trace.Wrap(err) } - if req.webauthn != "" { - err = ft.issueSingleUseCert(req.webauthn, r, tc) + if req.mfaResponse != "" { + err = ft.issueSingleUseCert(mfaResponse, r, tc) if err != nil { return nil, trace.Wrap(err) } @@ -216,21 +228,10 @@ func (f *fileTransfer) createClient(req fileTransferRequest, httpReq *http.Reque return tc, nil } -type mfaResponse struct { - // WebauthnResponse is the response from authenticators. - WebauthnAssertionResponse *wantypes.CredentialAssertionResponse `json:"webauthnAssertionResponse"` -} - // issueSingleUseCert will take an assertion response sent from a solved challenge in the web UI // and use that to generate a cert. This cert is added to the Teleport Client as an authmethod that // can be used to connect to a node. -func (f *fileTransfer) issueSingleUseCert(webauthn string, httpReq *http.Request, tc *client.TeleportClient) error { - var mfaResp mfaResponse - err := json.Unmarshal([]byte(webauthn), &mfaResp) - if err != nil { - return trace.Wrap(err) - } - +func (f *fileTransfer) issueSingleUseCert(mfaResponse *proto.MFAAuthenticateResponse, httpReq *http.Request, tc *client.TeleportClient) error { pk, err := keys.ParsePrivateKey(f.sctx.cfg.Session.GetSSHPriv()) if err != nil { return trace.Wrap(err) @@ -241,11 +242,7 @@ func (f *fileTransfer) issueSingleUseCert(webauthn string, httpReq *http.Request SSHPublicKey: pk.MarshalSSHPublicKey(), Username: f.sctx.GetUser(), Expires: time.Now().Add(time.Minute).UTC(), - MFAResponse: &proto.MFAAuthenticateResponse{ - Response: &proto.MFAAuthenticateResponse_Webauthn{ - Webauthn: wantypes.CredentialAssertionResponseToProto(mfaResp.WebauthnAssertionResponse), - }, - }, + MFAResponse: mfaResponse, }) if err != nil { return trace.Wrap(err) diff --git a/lib/web/headless.go b/lib/web/headless.go index 882bc1f374d3c..4542a4dd0522b 100644 --- a/lib/web/headless.go +++ b/lib/web/headless.go @@ -48,7 +48,7 @@ func (h *Handler) getHeadless(_ http.ResponseWriter, r *http.Request, params htt if err != nil { // Log the error, but return something more user-friendly. // Context exceeded or invalid request states are more confusing than helpful. - h.log.Debug("failed to get headless session: %v", err) + h.logger.DebugContext(r.Context(), "failed to get headless session", "error", err) return nil, trace.BadParameter("requested invalid headless session") } diff --git a/lib/web/integrations_awsoidc.go b/lib/web/integrations_awsoidc.go index 0597faa8e5425..844391b1523f9 100644 --- a/lib/web/integrations_awsoidc.go +++ b/lib/web/integrations_awsoidc.go @@ -23,6 +23,8 @@ import ( "encoding/base64" "encoding/json" "fmt" + "log/slog" + "maps" "net/http" "net/url" "slices" @@ -31,6 +33,7 @@ import ( "github.com/google/safetext/shsprintf" "github.com/gravitational/trace" "github.com/julienschmidt/httprouter" + "google.golang.org/grpc" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client" @@ -39,6 +42,7 @@ import ( apidefaults "github.com/gravitational/teleport/api/defaults" integrationv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/discoveryconfig" "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/api/utils/aws" "github.com/gravitational/teleport/lib/auth/authclient" @@ -49,6 +53,7 @@ import ( kubeutils "github.com/gravitational/teleport/lib/kube/utils" "github.com/gravitational/teleport/lib/reversetunnelclient" "github.com/gravitational/teleport/lib/services" + libui "github.com/gravitational/teleport/lib/ui" libutils "github.com/gravitational/teleport/lib/utils" awsutils "github.com/gravitational/teleport/lib/utils/aws" "github.com/gravitational/teleport/lib/utils/oidc" @@ -260,6 +265,228 @@ func (h *Handler) awsOIDCDeployDatabaseServices(w http.ResponseWriter, r *http.R }, nil } +// awsOIDCListDeployedDatabaseService lists the deployed Database Services in Amazon ECS. +func (h *Handler) awsOIDCListDeployedDatabaseService(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnelclient.RemoteSite) (any, error) { + ctx := r.Context() + clt, err := sctx.GetUserClient(ctx, site) + if err != nil { + return nil, trace.Wrap(err) + } + + integrationName := p.ByName("name") + if integrationName == "" { + return nil, trace.BadParameter("an integration name is required") + } + + regions, err := fetchRelevantAWSRegions(ctx, clt, clt.DiscoveryConfigClient()) + if err != nil { + return nil, trace.Wrap(err) + } + + services, err := listDeployedDatabaseServices(ctx, h.logger, integrationName, regions, clt.IntegrationAWSOIDCClient()) + if err != nil { + return nil, trace.Wrap(err) + } + + return ui.AWSOIDCListDeployedDatabaseServiceResponse{ + Services: services, + }, nil +} + +type databaseGetter interface { + GetResources(ctx context.Context, req *proto.ListResourcesRequest) (*proto.ListResourcesResponse, error) + GetDatabases(context.Context) ([]types.Database, error) +} + +type discoveryConfigLister interface { + ListDiscoveryConfigs(ctx context.Context, pageSize int, nextToken string) ([]*discoveryconfig.DiscoveryConfig, string, error) +} + +func fetchRelevantAWSRegions(ctx context.Context, authClient databaseGetter, discoveryConfigsClient discoveryConfigLister) ([]string, error) { + regionsSet := make(map[string]struct{}) + + // Collect Regions from Database resources. + databases, err := authClient.GetDatabases(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + for _, resource := range databases { + regionsSet[resource.GetAWS().Region] = struct{}{} + regionsSet[resource.GetAllLabels()[types.DiscoveryLabelRegion]] = struct{}{} + } + + // Iterate over all DatabaseServices and fetch their AWS Region in the matchers. + var nextPageKey string + for { + req := &proto.ListResourcesRequest{ + ResourceType: types.KindDatabaseService, + Limit: defaults.MaxIterationLimit, + StartKey: nextPageKey, + Labels: map[string]string{types.AWSOIDCAgentLabel: types.True}, + } + page, err := client.GetResourcePage[types.DatabaseService](ctx, authClient, req) + if err != nil { + return nil, trace.Wrap(err) + } + + maps.Copy(regionsSet, extractRegionsFromDatabaseServicesPage(page.Resources)) + + if page.NextKey == "" { + break + } + nextPageKey = page.NextKey + } + + // Iterate over all DiscoveryConfigs and fetch their AWS Region in AWS Matchers. + nextPageKey = "" + for { + resp, respNextPageKey, err := discoveryConfigsClient.ListDiscoveryConfigs(ctx, defaults.MaxIterationLimit, nextPageKey) + if err != nil { + return nil, trace.Wrap(err) + } + + maps.Copy(regionsSet, extractRegionsFromDiscoveryConfigPage(resp)) + + if respNextPageKey == "" { + break + } + nextPageKey = respNextPageKey + } + + // Drop any invalid region. + ret := make([]string, 0, len(regionsSet)) + for region := range regionsSet { + if aws.IsValidRegion(region) == nil { + ret = append(ret, region) + } + } + + return ret, nil +} + +func extractRegionsFromDatabaseServicesPage(dbServices []types.DatabaseService) map[string]struct{} { + regionsSet := make(map[string]struct{}) + for _, resource := range dbServices { + for _, matcher := range resource.GetResourceMatchers() { + if matcher.Labels == nil { + continue + } + for labelKey, labelValues := range *matcher.Labels { + if labelKey != types.DiscoveryLabelRegion { + continue + } + for _, labelValue := range labelValues { + regionsSet[labelValue] = struct{}{} + } + } + } + } + + return regionsSet +} + +func extractRegionsFromDiscoveryConfigPage(discoveryConfigs []*discoveryconfig.DiscoveryConfig) map[string]struct{} { + regionsSet := make(map[string]struct{}) + + for _, dc := range discoveryConfigs { + for _, awsMatcher := range dc.Spec.AWS { + for _, region := range awsMatcher.Regions { + regionsSet[region] = struct{}{} + } + } + } + + return regionsSet +} + +type deployedDatabaseServiceLister interface { + ListDeployedDatabaseServices(ctx context.Context, in *integrationv1.ListDeployedDatabaseServicesRequest, opts ...grpc.CallOption) (*integrationv1.ListDeployedDatabaseServicesResponse, error) +} + +func listDeployedDatabaseServices(ctx context.Context, + logger *slog.Logger, + integrationName string, + regions []string, + awsOIDCClient deployedDatabaseServiceLister, +) ([]ui.AWSOIDCDeployedDatabaseService, error) { + var services []ui.AWSOIDCDeployedDatabaseService + for _, region := range regions { + var nextToken string + for { + resp, err := awsOIDCClient.ListDeployedDatabaseServices(ctx, &integrationv1.ListDeployedDatabaseServicesRequest{ + Integration: integrationName, + Region: region, + NextToken: nextToken, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + for _, deployedDatabaseService := range resp.DeployedDatabaseServices { + matchingLabels, err := matchingLabelsFromDeployedService(deployedDatabaseService) + if err != nil { + logger.WarnContext(ctx, "Failed to obtain teleport config string from ECS Service", + "ecs_service", deployedDatabaseService.ServiceDashboardUrl, + "error", err, + ) + } + validTeleportConfigFound := err == nil + + services = append(services, ui.AWSOIDCDeployedDatabaseService{ + Name: deployedDatabaseService.Name, + DashboardURL: deployedDatabaseService.ServiceDashboardUrl, + MatchingLabels: matchingLabels, + ValidTeleportConfig: validTeleportConfigFound, + }) + } + + if resp.NextToken == "" { + break + } + nextToken = resp.NextToken + } + } + return services, nil +} + +func matchingLabelsFromDeployedService(deployedDatabaseService *integrationv1.DeployedDatabaseService) ([]libui.Label, error) { + commandArgs := deployedDatabaseService.ContainerCommand + // This command is what starts the teleport agent in the ECS Service Fargate container. + // See deployservice.go/upsertTask for details. + // It is expected to have at least 3 values, even if dumb-init is removed in the future. + if len(commandArgs) < 3 { + return nil, trace.BadParameter("unexpected command size, expected at least 3 args, got %d", len(commandArgs)) + } + + // The command should have a --config-string flag and then the teleport's base64 encoded configuration as argument + teleportConfigStringFlagIdx := slices.Index(commandArgs, "--config-string") + if teleportConfigStringFlagIdx == -1 { + return nil, trace.BadParameter("missing --config-string flag in container command") + } + if len(commandArgs) < teleportConfigStringFlagIdx+1 { + return nil, trace.BadParameter("missing --config-string argument in container command") + } + teleportConfigString := commandArgs[teleportConfigStringFlagIdx+1] + + labelMatchers, err := deployserviceconfig.ParseResourceLabelMatchers(teleportConfigString) + if err != nil { + return nil, trace.Wrap(err) + } + + var matchingLabels []libui.Label + for labelKey, labelValues := range labelMatchers { + for _, labelValue := range labelValues { + matchingLabels = append(matchingLabels, libui.Label{ + Name: labelKey, + Value: labelValue, + }) + } + } + + return matchingLabels, nil +} + // awsOIDCConfigureDeployServiceIAM returns a script that configures the required IAM permissions to enable the usage of DeployService action. func (h *Handler) awsOIDCConfigureDeployServiceIAM(w http.ResponseWriter, r *http.Request, p httprouter.Params) (any, error) { ctx := r.Context() @@ -1035,7 +1262,7 @@ func (h *Handler) awsOIDCCreateAWSAppAccess(w http.ResponseWriter, r *http.Reque AppClusterName: site.GetName(), AllowedAWSRolesLookup: allowedAWSRolesLookup, UserGroupLookup: getUserGroupLookup(), - Logger: h.log, + Logger: h.logger, }), nil } diff --git a/lib/web/integrations_awsoidc_test.go b/lib/web/integrations_awsoidc_test.go index 9b2660fe36e99..b8414570999dc 100644 --- a/lib/web/integrations_awsoidc_test.go +++ b/lib/web/integrations_awsoidc_test.go @@ -23,6 +23,7 @@ import ( "encoding/base64" "fmt" "net/url" + "strconv" "strings" "testing" @@ -31,13 +32,18 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" "github.com/stretchr/testify/require" + "google.golang.org/grpc" "github.com/gravitational/teleport/api" "github.com/gravitational/teleport/api/client/proto" integrationv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/discoveryconfig" "github.com/gravitational/teleport/lib/integrations/awsoidc" + "github.com/gravitational/teleport/lib/integrations/awsoidc/deployserviceconfig" "github.com/gravitational/teleport/lib/services" + libui "github.com/gravitational/teleport/lib/ui" + "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/web/ui" ) @@ -1146,3 +1152,331 @@ func TestAWSOIDCAppAccessAppServerCreationDeletion(t *testing.T) { require.NoError(t, err) }) } + +type mockDeployedDatabaseServices struct { + integration string + servicesPerRegion map[string][]*integrationv1.DeployedDatabaseService +} + +func (m *mockDeployedDatabaseServices) ListDeployedDatabaseServices(ctx context.Context, in *integrationv1.ListDeployedDatabaseServicesRequest, opts ...grpc.CallOption) (*integrationv1.ListDeployedDatabaseServicesResponse, error) { + const pageSize = 10 + ret := &integrationv1.ListDeployedDatabaseServicesResponse{} + if in.Integration != m.integration { + return ret, nil + } + + services := m.servicesPerRegion[in.Region] + if len(services) == 0 { + return ret, nil + } + + requestedPage := 1 + totalResources := len(services) + + if in.NextToken != "" { + currentMarker, err := strconv.Atoi(in.NextToken) + if err != nil { + return nil, trace.Wrap(err) + } + requestedPage = currentMarker + } + + sliceStart := pageSize * (requestedPage - 1) + sliceEnd := pageSize * requestedPage + if sliceEnd > totalResources { + sliceEnd = totalResources + } + + ret.DeployedDatabaseServices = services[sliceStart:sliceEnd] + if sliceEnd < totalResources { + ret.NextToken = strconv.Itoa(requestedPage + 1) + } + + return ret, nil +} + +func TestAWSOIDCListDeployedDatabaseServices(t *testing.T) { + ctx := context.Background() + logger := utils.NewSlogLoggerForTests() + + for _, tt := range []struct { + name string + integration string + regions []string + servicesPerRegion func(t *testing.T) map[string][]*integrationv1.DeployedDatabaseService + expectedServices func(t *testing.T) []ui.AWSOIDCDeployedDatabaseService + }{ + { + name: "valid", + integration: "my-integration", + regions: []string{"us-west-2"}, + servicesPerRegion: func(t *testing.T) map[string][]*integrationv1.DeployedDatabaseService { + command := buildCommandDeployedDatabaseService(t, true, types.Labels{"vpc": []string{"vpc1", "vpc2"}}) + return map[string][]*integrationv1.DeployedDatabaseService{ + "us-west-2": dummyDeployedDatabaseServices(1, command), + } + }, + expectedServices: func(t *testing.T) []ui.AWSOIDCDeployedDatabaseService { + return []ui.AWSOIDCDeployedDatabaseService{{ + Name: "database-service-vpc-0", + DashboardURL: "url", + ValidTeleportConfig: true, + MatchingLabels: []libui.Label{ + {Name: "vpc", Value: "vpc1"}, + {Name: "vpc", Value: "vpc2"}, + }, + }} + }, + }, + { + name: "no regions", + integration: "my-integration", + regions: []string{}, + servicesPerRegion: func(t *testing.T) map[string][]*integrationv1.DeployedDatabaseService { + return make(map[string][]*integrationv1.DeployedDatabaseService) + }, + expectedServices: func(t *testing.T) []ui.AWSOIDCDeployedDatabaseService { return nil }, + }, + { + name: "no services", + integration: "my-integration", + regions: []string{"us-west-2"}, + servicesPerRegion: func(t *testing.T) map[string][]*integrationv1.DeployedDatabaseService { + return make(map[string][]*integrationv1.DeployedDatabaseService) + }, + expectedServices: func(t *testing.T) []ui.AWSOIDCDeployedDatabaseService { return nil }, + }, + { + name: "services exist but for another region", + integration: "my-integration", + regions: []string{"us-west-2"}, + servicesPerRegion: func(t *testing.T) map[string][]*integrationv1.DeployedDatabaseService { + return map[string][]*integrationv1.DeployedDatabaseService{ + "us-west-1": dummyDeployedDatabaseServices(1, []string{}), + } + }, + expectedServices: func(t *testing.T) []ui.AWSOIDCDeployedDatabaseService { return nil }, + }, + { + name: "services exist for multiple regions", + integration: "my-integration", + regions: []string{"us-west-2"}, + servicesPerRegion: func(t *testing.T) map[string][]*integrationv1.DeployedDatabaseService { + command := buildCommandDeployedDatabaseService(t, true, types.Labels{"vpc": []string{"vpc1", "vpc2"}}) + return map[string][]*integrationv1.DeployedDatabaseService{ + "us-west-1": dummyDeployedDatabaseServices(1, command), + "us-west-2": dummyDeployedDatabaseServices(1, command), + } + }, + expectedServices: func(t *testing.T) []ui.AWSOIDCDeployedDatabaseService { + return []ui.AWSOIDCDeployedDatabaseService{{ + Name: "database-service-vpc-0", + DashboardURL: "url", + ValidTeleportConfig: true, + MatchingLabels: []libui.Label{ + {Name: "vpc", Value: "vpc1"}, + {Name: "vpc", Value: "vpc2"}, + }, + }} + }, + }, + { + name: "service exist but has invalid configuration", + integration: "my-integration", + regions: []string{"us-west-2"}, + servicesPerRegion: func(t *testing.T) map[string][]*integrationv1.DeployedDatabaseService { + command := buildCommandDeployedDatabaseService(t, false, nil) + return map[string][]*integrationv1.DeployedDatabaseService{ + "us-west-2": dummyDeployedDatabaseServices(1, command), + } + }, + expectedServices: func(t *testing.T) []ui.AWSOIDCDeployedDatabaseService { + return []ui.AWSOIDCDeployedDatabaseService{{ + Name: "database-service-vpc-0", + DashboardURL: "url", + ValidTeleportConfig: false, + }} + }, + }, + { + name: "service exist but was changed and --config-string argument is missing", + integration: "my-integration", + regions: []string{"us-west-2"}, + servicesPerRegion: func(t *testing.T) map[string][]*integrationv1.DeployedDatabaseService { + command := buildCommandDeployedDatabaseService(t, true, types.Labels{"vpc": []string{"vpc1", "vpc2"}}) + command = command[:len(command)-1] + return map[string][]*integrationv1.DeployedDatabaseService{ + "us-west-2": dummyDeployedDatabaseServices(1, command), + } + }, + expectedServices: func(t *testing.T) []ui.AWSOIDCDeployedDatabaseService { + return []ui.AWSOIDCDeployedDatabaseService{{ + Name: "database-service-vpc-0", + DashboardURL: "url", + ValidTeleportConfig: false, + }} + }, + }, + { + name: "service exist but was changed and --config-string flag is missing", + integration: "my-integration", + regions: []string{"us-west-2"}, + servicesPerRegion: func(t *testing.T) map[string][]*integrationv1.DeployedDatabaseService { + command := buildCommandDeployedDatabaseService(t, true, types.Labels{"vpc": []string{"vpc1", "vpc2"}}) + command[1] = "--no-config-string" + return map[string][]*integrationv1.DeployedDatabaseService{ + "us-west-2": dummyDeployedDatabaseServices(1, command), + } + }, + expectedServices: func(t *testing.T) []ui.AWSOIDCDeployedDatabaseService { + return []ui.AWSOIDCDeployedDatabaseService{{ + Name: "database-service-vpc-0", + DashboardURL: "url", + ValidTeleportConfig: false, + }} + }, + }, + { + name: "supports pagination", + integration: "my-integration", + regions: []string{"us-west-2"}, + servicesPerRegion: func(t *testing.T) map[string][]*integrationv1.DeployedDatabaseService { + command := buildCommandDeployedDatabaseService(t, true, types.Labels{"vpc": []string{"vpc1", "vpc2"}}) + return map[string][]*integrationv1.DeployedDatabaseService{ + "us-west-2": dummyDeployedDatabaseServices(1_024, command), + } + }, + expectedServices: func(t *testing.T) []ui.AWSOIDCDeployedDatabaseService { + var ret []ui.AWSOIDCDeployedDatabaseService + for i := 0; i < 1_024; i++ { + ret = append(ret, ui.AWSOIDCDeployedDatabaseService{ + Name: fmt.Sprintf("database-service-vpc-%d", i), + DashboardURL: "url", + ValidTeleportConfig: true, + MatchingLabels: []libui.Label{ + {Name: "vpc", Value: "vpc1"}, + {Name: "vpc", Value: "vpc2"}, + }, + }) + } + return ret + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + clt := &mockDeployedDatabaseServices{ + integration: tt.integration, + servicesPerRegion: tt.servicesPerRegion(t), + } + actual, err := listDeployedDatabaseServices(ctx, logger, tt.integration, tt.regions, clt) + require.NoError(t, err) + expected := tt.expectedServices(t) + require.Equal(t, expected, actual) + }) + } +} + +func buildCommandDeployedDatabaseService(t *testing.T, valid bool, matchingLabels types.Labels) []string { + t.Helper() + if !valid { + return []string{"not valid"} + } + + ret, err := deployserviceconfig.GenerateTeleportConfigString("host", "token", matchingLabels) + require.NoError(t, err) + + return []string{"start", "--config-string", ret} +} + +func dummyDeployedDatabaseServices(count int, command []string) []*integrationv1.DeployedDatabaseService { + var ret []*integrationv1.DeployedDatabaseService + for i := 0; i < count; i++ { + ret = append(ret, &integrationv1.DeployedDatabaseService{ + Name: fmt.Sprintf("database-service-vpc-%d", i), + ServiceDashboardUrl: "url", + ContainerEntryPoint: []string{"teleport"}, + ContainerCommand: command, + }) + } + return ret +} + +func TestFetchRelevantAWSRegions(t *testing.T) { + ctx := context.Background() + + t.Run("resources do not provide any region", func(t *testing.T) { + clt := &mockRelevantAWSRegionsClient{ + databaseServices: &proto.ListResourcesResponse{ + Resources: []*proto.PaginatedResource{}, + }, + databases: make([]types.Database, 0), + discoveryConfigs: make([]*discoveryconfig.DiscoveryConfig, 0), + } + gotRegions, err := fetchRelevantAWSRegions(ctx, clt, clt) + require.NoError(t, err) + require.Empty(t, gotRegions) + }) + + t.Run("resources provide multiple regions", func(t *testing.T) { + clt := &mockRelevantAWSRegionsClient{ + databaseServices: &proto.ListResourcesResponse{ + Resources: []*proto.PaginatedResource{{Resource: &proto.PaginatedResource_DatabaseService{ + DatabaseService: &types.DatabaseServiceV1{Spec: types.DatabaseServiceSpecV1{ + ResourceMatchers: []*types.DatabaseResourceMatcher{ + {Labels: &types.Labels{"region": []string{"us-east-1"}}}, + {Labels: &types.Labels{"region": []string{"us-east-2"}}}, + }, + }}, + }}}, + }, + databases: []types.Database{ + &types.DatabaseV3{Spec: types.DatabaseSpecV3{AWS: types.AWS{Region: "us-west-1"}}}, + &types.DatabaseV3{Metadata: types.Metadata{Labels: map[string]string{"region": "us-west-2"}}}, + }, + discoveryConfigs: []*discoveryconfig.DiscoveryConfig{{ + Spec: discoveryconfig.Spec{AWS: []types.AWSMatcher{{ + Regions: []string{"eu-west-1", "eu-west-2"}, + }}}, + }}, + } + gotRegions, err := fetchRelevantAWSRegions(ctx, clt, clt) + require.NoError(t, err) + expectedRegions := []string{"us-east-1", "us-east-2", "us-west-1", "us-west-2", "eu-west-1", "eu-west-2"} + require.ElementsMatch(t, expectedRegions, gotRegions) + }) + + t.Run("invalid regions are ignored", func(t *testing.T) { + clt := &mockRelevantAWSRegionsClient{ + databaseServices: &proto.ListResourcesResponse{ + Resources: []*proto.PaginatedResource{}, + }, + databases: []types.Database{ + &types.DatabaseV3{Spec: types.DatabaseSpecV3{AWS: types.AWS{Region: "us-west-1"}}}, + &types.DatabaseV3{Metadata: types.Metadata{Labels: map[string]string{"region": "bad-region"}}}, + }, + discoveryConfigs: make([]*discoveryconfig.DiscoveryConfig, 0), + } + gotRegions, err := fetchRelevantAWSRegions(ctx, clt, clt) + require.NoError(t, err) + expectedRegions := []string{"us-west-1"} + require.ElementsMatch(t, expectedRegions, gotRegions) + }) +} + +type mockRelevantAWSRegionsClient struct { + databaseServices *proto.ListResourcesResponse + databases []types.Database + discoveryConfigs []*discoveryconfig.DiscoveryConfig +} + +func (m *mockRelevantAWSRegionsClient) GetResources(ctx context.Context, req *proto.ListResourcesRequest) (*proto.ListResourcesResponse, error) { + return m.databaseServices, nil +} + +func (m *mockRelevantAWSRegionsClient) GetDatabases(context.Context) ([]types.Database, error) { + return m.databases, nil +} + +func (m *mockRelevantAWSRegionsClient) ListDiscoveryConfigs(ctx context.Context, pageSize int, nextToken string) ([]*discoveryconfig.DiscoveryConfig, string, error) { + return m.discoveryConfigs, "", nil +} diff --git a/lib/web/join_tokens.go b/lib/web/join_tokens.go index 16da9906cbd2f..033040a8545e0 100644 --- a/lib/web/join_tokens.go +++ b/lib/web/join_tokens.go @@ -382,7 +382,7 @@ func (h *Handler) getAutoUpgrades(ctx context.Context) (bool, string, error) { if autoUpgrades { autoUpgradesVersion, err = h.cfg.AutomaticUpgradesChannels.DefaultVersion(ctx) if err != nil { - log.WithError(err).Info("Failed to get auto upgrades version.") + h.logger.InfoContext(ctx, "Failed to get auto upgrades version", "error", err) return false, "", trace.Wrap(err) } } @@ -409,14 +409,14 @@ func (h *Handler) getNodeJoinScriptHandle(w http.ResponseWriter, r *http.Request script, err := getJoinScript(r.Context(), settings, h.GetProxyClient()) if err != nil { - log.WithError(err).Info("Failed to return the node install script.") + h.logger.InfoContext(r.Context(), "Failed to return the node install script", "error", err) w.Write(scripts.ErrorBashScript) return nil, nil } w.WriteHeader(http.StatusOK) if _, err := fmt.Fprintln(w, script); err != nil { - log.WithError(err).Info("Failed to return the node install script.") + h.logger.InfoContext(r.Context(), "Failed to return the node install script", "error", err) w.Write(scripts.ErrorBashScript) } @@ -429,14 +429,20 @@ func (h *Handler) getAppJoinScriptHandle(w http.ResponseWriter, r *http.Request, name, err := url.QueryUnescape(queryValues.Get("name")) if err != nil { - log.WithField("query-param", "name").WithError(err).Debug("Failed to return the app install script.") + h.logger.DebugContext(r.Context(), "Failed to return the app install script", + "query_param", "name", + "error", err, + ) w.Write(scripts.ErrorBashScript) return nil, nil } uri, err := url.QueryUnescape(queryValues.Get("uri")) if err != nil { - log.WithField("query-param", "uri").WithError(err).Debug("Failed to return the app install script.") + h.logger.DebugContext(r.Context(), "Failed to return the app install script", + "query_param", "uri", + "error", err, + ) w.Write(scripts.ErrorBashScript) return nil, nil } @@ -458,14 +464,14 @@ func (h *Handler) getAppJoinScriptHandle(w http.ResponseWriter, r *http.Request, script, err := getJoinScript(r.Context(), settings, h.GetProxyClient()) if err != nil { - log.WithError(err).Info("Failed to return the app install script.") + h.logger.InfoContext(r.Context(), "Failed to return the app install script", "error", err) w.Write(scripts.ErrorBashScript) return nil, nil } w.WriteHeader(http.StatusOK) if _, err := fmt.Fprintln(w, script); err != nil { - log.WithError(err).Debug("Failed to return the app install script.") + h.logger.DebugContext(r.Context(), "Failed to return the app install script", "error", err) w.Write(scripts.ErrorBashScript) } @@ -490,14 +496,14 @@ func (h *Handler) getDatabaseJoinScriptHandle(w http.ResponseWriter, r *http.Req script, err := getJoinScript(r.Context(), settings, h.GetProxyClient()) if err != nil { - log.WithError(err).Info("Failed to return the database install script.") + h.logger.InfoContext(r.Context(), "Failed to return the database install script", "error", err) w.Write(scripts.ErrorBashScript) return nil, nil } w.WriteHeader(http.StatusOK) if _, err := fmt.Fprintln(w, script); err != nil { - log.WithError(err).Debug("Failed to return the database install script.") + h.logger.DebugContext(r.Context(), "Failed to return the database install script", "error", err) w.Write(scripts.ErrorBashScript) } @@ -517,12 +523,17 @@ func (h *Handler) getDiscoveryJoinScriptHandle(w http.ResponseWriter, r *http.Re discoveryGroup, err := url.QueryUnescape(queryValues.Get(discoveryGroupQueryParam)) if err != nil { - log.WithField("query-param", discoveryGroupQueryParam).WithError(err).Debug("Failed to return the discovery install script.") + h.logger.DebugContext(r.Context(), "Failed to return the discovery install script", + "error", err, + "query_param", discoveryGroupQueryParam, + ) w.Write(scripts.ErrorBashScript) return nil, nil } if discoveryGroup == "" { - log.WithField("query-param", discoveryGroupQueryParam).Debug("Failed to return the discovery install script. Missing required fields.") + h.logger.DebugContext(r.Context(), "Failed to return the discovery install script. Missing required fields", + "query_param", discoveryGroupQueryParam, + ) w.Write(scripts.ErrorBashScript) return nil, nil } @@ -537,14 +548,14 @@ func (h *Handler) getDiscoveryJoinScriptHandle(w http.ResponseWriter, r *http.Re script, err := getJoinScript(r.Context(), settings, h.GetProxyClient()) if err != nil { - log.WithError(err).Info("Failed to return the discovery install script.") + h.logger.InfoContext(r.Context(), "Failed to return the discovery install script", "error", err) w.Write(scripts.ErrorBashScript) return nil, nil } w.WriteHeader(http.StatusOK) if _, err := fmt.Fprintln(w, script); err != nil { - log.WithError(err).Debug("Failed to return the discovery install script.") + h.logger.DebugContext(r.Context(), "Failed to return the discovery install script", "error", err) w.Write(scripts.ErrorBashScript) } diff --git a/lib/web/kube.go b/lib/web/kube.go index ccfd76380103f..195c6674d92ad 100644 --- a/lib/web/kube.go +++ b/lib/web/kube.go @@ -32,7 +32,6 @@ import ( "github.com/gogo/protobuf/proto" "github.com/gorilla/websocket" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" oteltrace "go.opentelemetry.io/otel/trace" v1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" @@ -49,6 +48,7 @@ import ( "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/session" + logutils "github.com/gravitational/teleport/lib/utils/log" "github.com/gravitational/teleport/lib/web/terminal" ) @@ -62,7 +62,6 @@ type podHandler struct { sctx *SessionContext ws *websocket.Conn keepAliveInterval time.Duration - log *logrus.Entry logger *slog.Logger userClient authclient.ClientI localCA types.CertAuthority @@ -130,7 +129,10 @@ func (p *podHandler) ServeHTTP(_ http.ResponseWriter, r *http.Request) { sessionMetadataResponse, err := json.Marshal(siteSessionGenerateResponse{Session: p.sess}) if err != nil { - p.sendAndLogError(err) + p.logger.ErrorContext(r.Context(), "failed marshaling session data", "error", err) + if err := p.sendErrorMessage(err); err != nil { + p.logger.ErrorContext(r.Context(), "failed to send error message to client", "error", err) + } return } @@ -142,18 +144,29 @@ func (p *podHandler) ServeHTTP(_ http.ResponseWriter, r *http.Request) { envelopeBytes, err := proto.Marshal(envelope) if err != nil { - p.sendAndLogError(err) + p.logger.ErrorContext(r.Context(), "failed marshaling message envelope", "error", err) + if err := p.sendErrorMessage(err); err != nil { + p.logger.ErrorContext(r.Context(), "failed to send error message to client", "error", err) + } + return } err = p.ws.WriteMessage(websocket.BinaryMessage, envelopeBytes) if err != nil { - p.sendAndLogError(err) + p.logger.ErrorContext(r.Context(), "failed write session data message", "error", err) + if err := p.sendErrorMessage(err); err != nil { + p.logger.ErrorContext(r.Context(), "failed to send error message to client", "error", err) + } + return } if err := p.handler(r); err != nil { - p.sendAndLogError(err) + p.logger.ErrorContext(r.Context(), "handling kube session unexpectedly terminated", "error", err) + if err := p.sendErrorMessage(err); err != nil { + p.logger.ErrorContext(r.Context(), "failed to send error message to client", "error", err) + } } } @@ -161,11 +174,9 @@ func (p *podHandler) Close() error { return trace.Wrap(p.ws.Close()) } -func (p *podHandler) sendAndLogError(err error) { - p.log.Error(err) - +func (p *podHandler) sendErrorMessage(err error) error { if p.closedByClient.Load() { - return + return nil } envelope := &terminal.Envelope{ @@ -176,16 +187,17 @@ func (p *podHandler) sendAndLogError(err error) { envelopeBytes, err := proto.Marshal(envelope) if err != nil { - p.log.WithError(err).Error("failed to marshal error message") - return + return trace.Wrap(err, "creating envelope payload") } if err := p.ws.WriteMessage(websocket.BinaryMessage, envelopeBytes); err != nil { - p.log.WithError(err).Error("failed to send error message") + return trace.Wrap(err, "writing error message") } + + return nil } func (p *podHandler) handler(r *http.Request) error { - p.log.Debug("Creating websocket stream for a kube exec request") + p.logger.DebugContext(r.Context(), "Creating websocket stream for a kube exec request") // Create a context for signaling when the terminal session is over and // link it first with the trace context from the request context @@ -196,7 +208,7 @@ func (p *podHandler) handler(r *http.Request) error { defaultCloseHandler := p.ws.CloseHandler() p.ws.SetCloseHandler(func(code int, text string) error { p.closedByClient.Store(true) - p.log.Debug("websocket connection was closed by client") + p.logger.DebugContext(r.Context(), "websocket connection was closed by client") cancel() // Call the default close handler if one was set. @@ -229,7 +241,13 @@ func (p *podHandler) handler(r *http.Request) error { Width: p.req.Term.Winsize().Width, Height: p.req.Term.Winsize().Height, }) - stream := terminal.NewStream(ctx, terminal.StreamConfig{WS: p.ws, Logger: p.log, Handlers: map[string]terminal.WSHandlerFunc{defaults.WebsocketResize: p.handleResize(resizeQueue)}}) + stream := terminal.NewStream(ctx, terminal.StreamConfig{ + WS: p.ws, + Logger: p.logger, + Handlers: map[string]terminal.WSHandlerFunc{ + defaults.WebsocketResize: p.handleResize(resizeQueue), + }, + }) certsReq := clientproto.UserCertsRequest{ TLSPublicKey: publicKeyPEM, @@ -284,7 +302,7 @@ func (p *podHandler) handler(r *http.Request) error { } kubeReq.VersionedParams(option, scheme.ParameterCodec) - p.log.Debugf("Web kube exec request URL: %s", kubeReq.URL()) + p.logger.DebugContext(ctx, "Web kube exec request created", "url", logutils.StringerAttr(kubeReq.URL())) wsExec, err := remotecommand.NewWebSocketExecutor(restConfig, "POST", kubeReq.URL().String()) if err != nil { @@ -315,16 +333,16 @@ func (p *podHandler) handler(r *http.Request) error { if p.req.IsInteractive { // Send close envelope to web terminal upon exit without an error. if err := stream.SendCloseMessage(""); err != nil { - p.log.WithError(err).Error("unable to send close event to web client.") + p.logger.ErrorContext(ctx, "unable to send close event to web client", "error", err) } } if err := stream.Close(); err != nil { - p.log.WithError(err).Error("unable to close websocket stream to web client.") + p.logger.ErrorContext(ctx, "unable to close websocket stream to web client", "error", err) return nil } - p.log.Debug("Sent close event to web client.") + p.logger.DebugContext(ctx, "Sent close event to web client", "error", err) return nil } @@ -333,19 +351,19 @@ func (p *podHandler) handleResize(termSizeQueue *termSizeQueue) func(context.Con return func(ctx context.Context, envelope terminal.Envelope) { var e map[string]any if err := json.Unmarshal([]byte(envelope.Payload), &e); err != nil { - p.log.Warnf("Failed to parse resize payload: %v", err) + p.logger.WarnContext(ctx, "Failed to parse resize payload", "error", err) return } size, ok := e["size"].(string) if !ok { - p.log.Errorf("expected size to be of type string, got type %T instead", size) + p.logger.ErrorContext(ctx, "got unexpected size type, expected type string", "size_type", logutils.TypeAttr(size)) return } params, err := session.UnmarshalTerminalParams(size) if err != nil { - p.log.Warnf("Failed to retrieve terminal size: %v", err) + p.logger.WarnContext(ctx, "Failed to retrieve terminal size", "error", err) return } diff --git a/lib/web/login_helper_test.go b/lib/web/login_helper_test.go index ae7f2ddbd93c8..2829a2d1400d7 100644 --- a/lib/web/login_helper_test.go +++ b/lib/web/login_helper_test.go @@ -18,6 +18,7 @@ package web import ( "bytes" + "cmp" "context" "encoding/base32" "encoding/json" @@ -34,7 +35,6 @@ import ( "github.com/gravitational/teleport/lib/auth/mocku2f" "github.com/gravitational/teleport/lib/client" - "github.com/gravitational/teleport/lib/httplib/csrf" ) // newOTPSharedSecret returns an OTP shared secret, encoded as a base32 string. @@ -54,9 +54,8 @@ type loginWebOTPParams struct { // If empty then no OTP is sent in the request. otpSecret string - userAgent string // Optional. - - cookieCSRF, headerCSRF *string // Explicit CSRF tokens. Optional. + userAgent string // Optional. + overrideContentType string // Optional. } // DrainedHTTPResponse mimics an http.Response, but without a body. @@ -124,24 +123,11 @@ func rawLoginWebOTP(ctx context.Context, params loginWebOTPParams) (resp *Draine } // Set assorted headers. - req.Header.Set("Content-Type", "application/json") + req.Header.Set("Content-Type", cmp.Or(params.overrideContentType, "application/json")) if params.userAgent != "" { req.Header.Set("User-Agent", params.userAgent) } - // Set CSRF cookie and header. - const defaultCSRFToken = "2ebcb768d0090ea4368e42880c970b61865c326172a4a2343b645cf5d7f20992" - cookieCSRF := defaultCSRFToken - if params.cookieCSRF != nil { - cookieCSRF = *params.cookieCSRF - } - addCSRFCookieToReq(req, cookieCSRF) - headerCSRF := defaultCSRFToken - if params.headerCSRF != nil { - headerCSRF = *params.headerCSRF - } - req.Header.Set(csrf.HeaderName, headerCSRF) - httpResp, err := webClient.HTTPClient().Do(req) if err != nil { return nil, nil, trace.Wrap(err, "do HTTP request") diff --git a/lib/web/mfa.go b/lib/web/mfa.go index 485a4eff460bc..c59b0ae10cbd7 100644 --- a/lib/web/mfa.go +++ b/lib/web/mfa.go @@ -21,8 +21,10 @@ package web import ( "context" "net/http" + "net/url" "strings" + "github.com/google/uuid" "github.com/gravitational/trace" "github.com/julienschmidt/httprouter" @@ -201,6 +203,22 @@ func (h *Handler) createAuthenticateChallengeHandle(w http.ResponseWriter, r *ht allowReuse = mfav1.ChallengeAllowReuse_CHALLENGE_ALLOW_REUSE_YES } + // Prepare an sso client redirect URL in case the user has an SSO MFA device. + ssoClientRedirectURL, err := url.Parse(sso.WebMFARedirect) + if err != nil { + return nil, trace.Wrap(err) + } + + // id is used by the front end to differentiate between separate ongoing SSO challenges. + id, err := uuid.NewRandom() + if err != nil { + return nil, trace.Wrap(err) + } + channelID := id.String() + query := ssoClientRedirectURL.Query() + query.Set("channel_id", channelID) + ssoClientRedirectURL.RawQuery = query.Encode() + chal, err := clt.CreateAuthenticateChallenge(r.Context(), &proto.CreateAuthenticateChallengeRequest{ Request: &proto.CreateAuthenticateChallengeRequest_ContextUser{ ContextUser: &proto.ContextUser{}, @@ -211,13 +229,13 @@ func (h *Handler) createAuthenticateChallengeHandle(w http.ResponseWriter, r *ht AllowReuse: allowReuse, UserVerificationRequirement: req.UserVerificationRequirement, }, - SSOClientRedirectURL: sso.WebMFARedirect, + SSOClientRedirectURL: ssoClientRedirectURL.String(), }) if err != nil { return nil, trace.Wrap(err) } - return makeAuthenticateChallenge(chal), nil + return makeAuthenticateChallenge(chal, channelID), nil } // createAuthenticateChallengeWithTokenHandle creates and returns MFA authenticate challenges for the user defined in token. @@ -235,7 +253,7 @@ func (h *Handler) createAuthenticateChallengeWithTokenHandle(w http.ResponseWrit return nil, trace.Wrap(err) } - return makeAuthenticateChallenge(chal), nil + return makeAuthenticateChallenge(chal, "" /*channelID*/), nil } type createRegisterChallengeWithTokenRequest struct { @@ -581,7 +599,7 @@ func (h *Handler) checkMFARequired(ctx context.Context, req *isMFARequiredReques } // makeAuthenticateChallenge converts proto to JSON format. -func makeAuthenticateChallenge(protoChal *proto.MFAAuthenticateChallenge) *client.MFAAuthenticateChallenge { +func makeAuthenticateChallenge(protoChal *proto.MFAAuthenticateChallenge, ssoChannelID string) *client.MFAAuthenticateChallenge { chal := &client.MFAAuthenticateChallenge{ TOTPChallenge: protoChal.GetTOTP() != nil, } @@ -590,6 +608,7 @@ func makeAuthenticateChallenge(protoChal *proto.MFAAuthenticateChallenge) *clien } if protoChal.GetSSOChallenge() != nil { chal.SSOChallenge = client.SSOChallengeFromProto(protoChal.GetSSOChallenge()) + chal.SSOChallenge.ChannelID = ssoChannelID } return chal } diff --git a/lib/web/mfajson/mfajson.go b/lib/web/mfajson/mfajson.go index 70abb8ecfec32..2105b0178b3a9 100644 --- a/lib/web/mfajson/mfajson.go +++ b/lib/web/mfajson/mfajson.go @@ -28,7 +28,7 @@ import ( "github.com/gravitational/teleport/lib/client" ) -// TODO(Joerger): DELETE IN v18.0.0 and use client.MFAChallengeResponse instead. +// TODO(Joerger): DELETE IN v19.0.0 and use client.MFAChallengeResponse instead. // Before v17, the WebUI sends a flattened webauthn response instead of a full // MFA challenge response. Newer WebUI versions v17+ will send both for // backwards compatibility. @@ -45,33 +45,17 @@ func Decode(b []byte, typ string) (*authproto.MFAAuthenticateResponse, error) { return nil, trace.Wrap(err) } - switch { - case resp.CredentialAssertionResponse != nil: - return &authproto.MFAAuthenticateResponse{ - Response: &authproto.MFAAuthenticateResponse_Webauthn{ - Webauthn: wantypes.CredentialAssertionResponseToProto(resp.CredentialAssertionResponse), - }, - }, nil - case resp.WebauthnResponse != nil: - return &authproto.MFAAuthenticateResponse{ - Response: &authproto.MFAAuthenticateResponse_Webauthn{ - Webauthn: wantypes.CredentialAssertionResponseToProto(resp.WebauthnResponse), - }, - }, nil - case resp.SSOResponse != nil: - return &authproto.MFAAuthenticateResponse{ - Response: &authproto.MFAAuthenticateResponse_SSO{ - SSO: &authproto.SSOResponse{ - RequestId: resp.SSOResponse.RequestID, - Token: resp.SSOResponse.Token, - }, - }, - }, nil - case resp.TOTPCode != "": - // Note: we can support TOTP through the websocket if desired, we just need to add - // a TOTP prompt modal and flip the switch here. - return nil, trace.BadParameter("totp is not supported in the WebUI") - default: + // Move flattened webauthn response into resp. + resp.MFAChallengeResponse.WebauthnAssertionResponse = resp.CredentialAssertionResponse + + protoResp, err := resp.GetOptionalMFAResponseProtoReq() + if err != nil { + return nil, trace.Wrap(err) + } + + if protoResp == nil { return nil, trace.BadParameter("invalid MFA response from web") } + + return protoResp, trace.Wrap(err) } diff --git a/lib/web/password.go b/lib/web/password.go index 6ae5923787d7e..824c8b00ecb5a 100644 --- a/lib/web/password.go +++ b/lib/web/password.go @@ -108,5 +108,5 @@ func (h *Handler) createAuthenticateChallengeWithPassword(w http.ResponseWriter, return nil, trace.Wrap(err) } - return makeAuthenticateChallenge(chal), nil + return makeAuthenticateChallenge(chal, "" /*channelID*/), nil } diff --git a/lib/web/scripts/node-join/install.sh b/lib/web/scripts/node-join/install.sh index 29ed2ee8cb73a..3d8403c00787d 100755 --- a/lib/web/scripts/node-join/install.sh +++ b/lib/web/scripts/node-join/install.sh @@ -840,7 +840,9 @@ install_from_file() { tar -xzf "${TEMP_DIR}/${DOWNLOAD_FILENAME}" -C "${TEMP_DIR}" # install binaries to /usr/local/bin for BINARY in ${TELEPORT_BINARY_LIST}; do - ${COPY_COMMAND} "${TELEPORT_ARCHIVE_PATH}/${BINARY}" "${TELEPORT_BINARY_DIR}/" + if [ -e "${TELEPORT_ARCHIVE_PATH}/${BINARY}" ]; then + ${COPY_COMMAND} "${TELEPORT_ARCHIVE_PATH}/${BINARY}" "${TELEPORT_BINARY_DIR}/" + fi done elif [[ ${TELEPORT_FORMAT} == "deb" ]]; then # convert teleport arch to deb arch diff --git a/lib/web/server.go b/lib/web/server.go index 0af94831d30e6..a1f113cd53995 100644 --- a/lib/web/server.go +++ b/lib/web/server.go @@ -20,17 +20,16 @@ package web import ( "context" + "log/slog" "net" "net/http" "sync" "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport" "github.com/gravitational/teleport/lib/defaults" - "github.com/gravitational/teleport/lib/utils" ) // ServerConfig provides dependencies required to create a [Server]. @@ -40,7 +39,7 @@ type ServerConfig struct { // Handler web handler Handler *APIHandler // Log to write log messages - Log logrus.FieldLogger + Log *slog.Logger // ShutdownPollPeriod sets polling period for shutdown ShutdownPollPeriod time.Duration } @@ -60,7 +59,7 @@ func (c *ServerConfig) CheckAndSetDefaults() error { } if c.Log == nil { - c.Log = utils.NewLogger().WithField(teleport.ComponentKey, teleport.ComponentProxy) + c.Log = slog.With(teleport.ComponentKey, teleport.ComponentProxy) } return nil @@ -138,7 +137,7 @@ func (s *Server) Shutdown(ctx context.Context) error { return trace.NewAggregate(err, s.cfg.Handler.Close()) } - s.cfg.Log.Infof("Shutdown: waiting for %v connections to finish.", activeConnections) + s.cfg.Log.InfoContext(ctx, "Shutdown: waiting for active connections to finish", "active_connection_count", activeConnections) lastReport := time.Time{} ticker := time.NewTicker(s.cfg.ShutdownPollPeriod) defer ticker.Stop() @@ -151,11 +150,11 @@ func (s *Server) Shutdown(ctx context.Context) error { return trace.NewAggregate(err, s.cfg.Handler.Close()) } if time.Since(lastReport) > 10*s.cfg.ShutdownPollPeriod { - s.cfg.Log.Infof("Shutdown: waiting for %v connections to finish.", activeConnections) + s.cfg.Log.InfoContext(ctx, "Shutdown: waiting for active connections to finish", "active_connection_count", activeConnections) lastReport = time.Now() } case <-ctx.Done(): - s.cfg.Log.Infof("Context canceled wait, returning.") + s.cfg.Log.InfoContext(ctx, "Context canceled wait, returning") return trace.ConnectionProblem(trace.NewAggregate(err, s.cfg.Handler.Close()), "context canceled") } } diff --git a/lib/web/sessions.go b/lib/web/sessions.go index b429920d45708..67f21be47db5c 100644 --- a/lib/web/sessions.go +++ b/lib/web/sessions.go @@ -25,6 +25,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net" "sync" "sync/atomic" @@ -32,7 +33,6 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" @@ -62,6 +62,7 @@ import ( "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) // SessionContext is a context associated with a user's @@ -86,7 +87,7 @@ type SessionContext struct { type SessionContextConfig struct { // Log is used to emit logs - Log *logrus.Entry + Log *slog.Logger // User is the name of the current user User string @@ -139,10 +140,10 @@ func (c *SessionContextConfig) CheckAndSetDefaults() error { } if c.Log == nil { - c.Log = log.WithFields(logrus.Fields{ - "user": c.User, - "session": c.Session.GetShortName(), - }) + c.Log = slog.With( + "user", c.User, + "session", c.Session.GetShortName(), + ) } if c.newRemoteClient == nil { @@ -202,8 +203,12 @@ func (c *SessionContext) validateBearerToken(ctx context.Context, token string) } if fetchedToken.GetUser() != c.cfg.User { - c.cfg.Log.Warnf("Failed validating bearer token: the user[%s] in bearer token[%s] did not match the user[%s] for session[%s]", - fetchedToken.GetUser(), token, c.cfg.User, c.GetSessionID()) + c.cfg.Log.WarnContext(ctx, "Failed validating bearer token: the user in bearer token did not match the user for session", + "token_user", fetchedToken.GetUser(), + "token", token, + "session_user", c.cfg.User, + "session_id", c.GetSessionID(), + ) return trace.AccessDenied("access denied") } @@ -260,7 +265,10 @@ func (c *SessionContext) remoteClient(ctx context.Context, site reversetunnelcli // the session context is closed. err = c.remoteClientCache.addRemoteClient(site, rClt) if err != nil { - c.cfg.Log.WithError(err).Info("Failed closing stale remote client for site: ", site.GetName()) + c.cfg.Log.InfoContext(ctx, "Failed closing stale remote client for site", + "remote_site", site.GetName(), + "error", err, + ) } return rClt, nil @@ -595,7 +603,7 @@ func (c *SessionContext) expired(ctx context.Context) bool { // was removed during user logout, expire the session immediately. return true default: - c.cfg.Log.WithError(err).Debug("Failed to query web session.") + c.cfg.Log.DebugContext(ctx, "Failed to query web session", "error", err) } // If the session has no expiry time, then also by definition it @@ -634,6 +642,7 @@ type sessionCacheOptions struct { sessionWatcherStartImmediately bool // See [sessionCache.sessionWatcherEventProcessedChannel]. Used for testing. sessionWatcherEventProcessedChannel chan struct{} + logger *slog.Logger } // newSessionCache creates a [sessionCache] from the provided [config] and @@ -649,6 +658,10 @@ func newSessionCache(ctx context.Context, config sessionCacheOptions) (*sessionC config.clock = clockwork.NewRealClock() } + if config.logger == nil { + config.logger = slog.Default() + } + cache := &sessionCache{ clusterName: clusterName.GetClusterName(), proxyClient: config.proxyClient, @@ -658,7 +671,7 @@ func newSessionCache(ctx context.Context, config sessionCacheOptions) (*sessionC authServers: config.servers, closer: utils.NewCloseBroadcaster(), cipherSuites: config.cipherSuites, - log: newPackageLogger(), + log: config.logger, clock: config.clock, sessionLingeringThreshold: config.sessionLingeringThreshold, proxySigner: config.proxySigner, @@ -678,7 +691,7 @@ 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 logrus.FieldLogger + log *slog.Logger proxyClient authclient.ClientI authServers []utils.NetAddr accessPoint authclient.ReadProxyAccessPoint @@ -724,7 +737,7 @@ type sessionCache struct { // Close closes all allocated resources and stops goroutines func (s *sessionCache) Close() error { - s.log.Info("Closing session cache.") + s.log.InfoContext(context.Background(), "Closing session cache") return s.closer.Close() } @@ -757,8 +770,8 @@ func (s *sessionCache) clearExpiredSessions(ctx context.Context) { if !c.expired(ctx) { continue } - s.removeSessionContextLocked(c.cfg.Session.GetUser(), c.cfg.Session.GetName()) - s.log.WithField("ctx", c.String()).Debug("Context expired.") + s.removeSessionContextLocked(ctx, c.cfg.Session.GetUser(), c.cfg.Session.GetName()) + s.log.DebugContext(ctx, "Context expired", "context", logutils.StringerAttr(c)) } } @@ -775,12 +788,12 @@ func (s *sessionCache) watchWebSessions(ctx context.Context) { linear.First = 0 } - s.log.Debug("sessionCache: Starting WebSession watcher") + s.log.DebugContext(ctx, "sessionCache: Starting WebSession watcher") for { select { // Stop when the context tells us to. case <-ctx.Done(): - s.log.Debug("sessionCache: Stopping WebSession watcher") + s.log.DebugContext(ctx, "sessionCache: Stopping WebSession watcher") return case <-linear.After(): @@ -791,7 +804,7 @@ func (s *sessionCache) watchWebSessions(ctx context.Context) { const msg = "" + "sessionCache: WebSession watcher aborted, re-connecting. " + "This may have an impact in device trust web sessions." - s.log.WithError(err).Warn(msg) + s.log.WarnContext(ctx, msg, "error", err) } } } @@ -840,9 +853,9 @@ func (s *sessionCache) watchWebSessionsOnce(ctx context.Context, reset func()) e case event := <-watcher.Events(): reset() // Reset linear backoff attempts. - s.log. - WithField("event", event). - Trace("sessionCache: Received watcher event") + s.log.Log(ctx, logutils.TraceLevel, "sessionCache: Received watcher event", + "event", logutils.StringerAttr(event), + ) if event.Type != types.OpPut { continue // We only care about OpPut at the moment. @@ -850,25 +863,25 @@ func (s *sessionCache) watchWebSessionsOnce(ctx context.Context, reset func()) e session, ok := event.Resource.(types.WebSession) if !ok { - s.log. - WithField("resource_type", fmt.Sprintf("%T", event.Resource)). - Warn("sessionCache: Received unexpected resource type") + s.log.WarnContext(ctx, "sessionCache: Received unexpected resource type", + "resource_type", logutils.TypeAttr(event.Resource), + ) continue } if !session.GetHasDeviceExtensions() { - s.log. - WithField("session_id", session.GetName()). - Debug("sessionCache: Updated session doesn't have device extensions, skipping") + s.log.DebugContext(ctx, "sessionCache: Updated session doesn't have device extensions, skipping", + "session_id", session.GetName(), + ) notifyProcessed() continue } // Release existing and non-device-aware session. - if err := s.releaseResourcesIfNoDeviceExtensions(session.GetUser(), session.GetName()); err != nil { - s.log. - WithError(err). - WithField("session_id", session.GetName()). - Debug("sessionCache: Failed to release updated session") + if err := s.releaseResourcesIfNoDeviceExtensions(ctx, session.GetUser(), session.GetName()); err != nil { + s.log.DebugContext(ctx, "sessionCache: Failed to release updated session", + "error", err, + "session_id", session.GetName(), + ) } notifyProcessed() @@ -876,7 +889,7 @@ func (s *sessionCache) watchWebSessionsOnce(ctx context.Context, reset func()) e } } -func (s *sessionCache) releaseResourcesIfNoDeviceExtensions(user, sessionID string) error { +func (s *sessionCache) releaseResourcesIfNoDeviceExtensions(ctx context.Context, user, sessionID string) error { s.mu.Lock() defer s.mu.Unlock() @@ -885,16 +898,16 @@ func (s *sessionCache) releaseResourcesIfNoDeviceExtensions(user, sessionID stri case !ok: return nil // Session not found case sessionCtx.cfg.Session.GetHasDeviceExtensions(): - s.log. - WithField("session_id", sessionID). - Debug("sessionCache: Session already has device extensions, skipping") + s.log.DebugContext(ctx, "sessionCache: Session already has device extensions, skipping", + "session_id", sessionID, + ) return nil } - s.log. - WithField("session_id", sessionID). - Debug("sessionCache: Releasing session resources due to device extensions upgrade") - return s.releaseResourcesLocked(user, sessionID) + s.log.DebugContext(ctx, "sessionCache: Releasing session resources due to device extensions upgrade", + "session_id", sessionID, + ) + return s.releaseResourcesLocked(ctx, user, sessionID) } // AuthWithOTP authenticates the specified user with the given password and OTP token. @@ -1055,40 +1068,40 @@ func (s *sessionCache) insertContext(user string, sctx *SessionContext) (exists return false } -func (s *sessionCache) releaseResources(user, sessionID string) error { +func (s *sessionCache) releaseResources(ctx context.Context, user, sessionID string) error { s.mu.Lock() defer s.mu.Unlock() - return s.releaseResourcesLocked(user, sessionID) + return s.releaseResourcesLocked(ctx, user, sessionID) } -func (s *sessionCache) removeSessionContextLocked(user, sessionID string) error { +func (s *sessionCache) removeSessionContextLocked(ctx context.Context, user, sessionID string) error { id := sessionKey(user, sessionID) - ctx, ok := s.sessions[id] + sess, ok := s.sessions[id] if !ok { return nil } delete(s.sessions, id) - err := ctx.Close() + err := sess.Close() if err != nil { - s.log.WithFields(logrus.Fields{ - "ctx": ctx.String(), - logrus.ErrorKey: err, - }).Warn("Failed to close session context.") + s.log.WarnContext(ctx, "Failed to close session context", + "context", logutils.StringerAttr(sess), + "error", err, + ) return trace.Wrap(err) } return nil } -func (s *sessionCache) releaseResourcesLocked(user, sessionID string) error { +func (s *sessionCache) releaseResourcesLocked(ctx context.Context, user, sessionID string) error { var errors []error - err := s.removeSessionContextLocked(user, sessionID) + err := s.removeSessionContextLocked(ctx, user, sessionID) if err != nil { errors = append(errors, err) } - if ctx, ok := s.resources[user]; ok { + if sess, ok := s.resources[user]; ok { delete(s.resources, user) - if err := ctx.Close(); err != nil { - s.log.WithError(err).Warn("Failed to clean up session context.") + if err := sess.Close(); err != nil { + s.log.WarnContext(ctx, "Failed to clean up session context", "error", err) errors = append(errors, err) } } @@ -1102,10 +1115,10 @@ func (s *sessionCache) upsertSessionContext(user string) *sessionResources { return ctx } ctx := &sessionResources{ - log: s.log.WithFields(logrus.Fields{ - teleport.ComponentKey: "user-session", - "user": user, - }), + log: s.log.With( + teleport.ComponentKey, "user-session", + "user", user, + ), } s.resources[user] = ctx return ctx @@ -1143,10 +1156,10 @@ func (s *sessionCache) newSessionContextFromSession(ctx context.Context, session } sctx, err := NewSessionContext(SessionContextConfig{ - Log: s.log.WithFields(logrus.Fields{ - "user": session.GetUser(), - "session": session.GetShortName(), - }), + Log: s.log.With( + "user", session.GetUser(), + "session", session.GetShortName(), + ), User: session.GetUser(), RootClient: userClient, UnsafeCachedAuthClient: s.accessPoint, @@ -1217,7 +1230,6 @@ func (c *sessionResources) Close() error { closers := c.transferClosers() var errors []error for _, closer := range closers { - c.log.Debugf("Closing %v.", closer) if err := closer.Close(); err != nil { errors = append(errors, err) } @@ -1228,7 +1240,7 @@ func (c *sessionResources) Close() error { // sessionResources persists resources initiated by a web session // but which might outlive the session. type sessionResources struct { - log logrus.FieldLogger + log *slog.Logger mu sync.Mutex closers []io.Closer @@ -1336,7 +1348,7 @@ const ( // the server to send the session ID it's using. The returned function // will return the current session ID from the server or a reason why // one wasn't received. -func prepareToReceiveSessionID(ctx context.Context, log *logrus.Entry, nc *client.NodeClient) func() (session.ID, sessionIDStatus) { +func prepareToReceiveSessionID(ctx context.Context, log *slog.Logger, nc *client.NodeClient) func() (session.ID, sessionIDStatus) { // send the session ID received from the server var gotSessionID atomic.Bool sessionIDFromServer := make(chan session.ID, 1) @@ -1349,7 +1361,7 @@ func prepareToReceiveSessionID(ctx context.Context, log *logrus.Entry, nc *clien sid, err := session.ParseID(string(req.Payload)) if err != nil { - log.WithError(err).Warn("Unable to parse session ID.") + log.WarnContext(ctx, "Unable to parse session ID", "error", err) return nil } @@ -1368,7 +1380,7 @@ func prepareToReceiveSessionID(ctx context.Context, log *logrus.Entry, nc *clien go func() { resp, _, err := nc.Client.SendRequest(ctx, teleport.SessionIDQueryRequest, true, nil) if err != nil { - log.WithError(err).Warn("Failed to send session ID query request") + log.WarnContext(ctx, "Failed to send session ID query request", "error", err) serverWillSetSessionID <- false } else { serverWillSetSessionID <- resp diff --git a/lib/web/terminal.go b/lib/web/terminal.go index 9326140447eac..9b3a109714eed 100644 --- a/lib/web/terminal.go +++ b/lib/web/terminal.go @@ -39,7 +39,6 @@ import ( "github.com/gorilla/websocket" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" oteltrace "go.opentelemetry.io/otel/trace" "golang.org/x/crypto/ssh" @@ -123,10 +122,6 @@ func NewTerminal(ctx context.Context, cfg TerminalHandlerConfig) (*TerminalHandl return &TerminalHandler{ sshBaseHandler: sshBaseHandler{ - log: logrus.WithFields(logrus.Fields{ - teleport.ComponentKey: teleport.ComponentWebsocket, - "session_id": cfg.SessionData.ID.String(), - }), logger: cfg.Logger.With( teleport.ComponentKey, teleport.ComponentWebsocket, "session_id", cfg.SessionData.ID.String(), @@ -268,8 +263,6 @@ func (t *TerminalHandlerConfig) CheckAndSetDefaults() error { // sshBaseHandler is a base handler for web SSH connections. type sshBaseHandler struct { - // log holds the structured logger. - log *logrus.Entry // logger holds the structured logger. logger *slog.Logger // ctx is a web session context for the currently logged-in user. @@ -365,14 +358,14 @@ func (t *TerminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { err := ws.SetReadDeadline(deadlineForInterval(t.keepAliveInterval)) if err != nil { - t.log.WithError(err).Error("Error setting websocket readline") + t.logger.ErrorContext(r.Context(), "Error setting websocket readline", "error", err) return } t.handler(ws, r) } -func (t *TerminalHandler) writeSessionData() error { +func (t *TerminalHandler) writeSessionData(ctx context.Context) error { envelope := &terminal.Envelope{ Version: defaults.WebsocketVersion, Type: defaults.WebsocketSessionMetadata, @@ -387,7 +380,7 @@ func (t *TerminalHandler) writeSessionData() error { sessionDataTemp.Login = t.displayLogin sessionMetadataResponse, err := json.Marshal(siteSessionGenerateResponse{Session: sessionDataTemp}) if err != nil { - t.sendError("unable to marshal session response", err, t.stream) + t.sendError(ctx, "unable to marshal session response", err, t.stream) return trace.Wrap(err) } envelope.Payload = string(sessionMetadataResponse) @@ -406,7 +399,7 @@ func (t *TerminalHandler) writeSessionData() error { sessionMetadataResponse, err := json.Marshal(siteSessionGenerateResponse{Session: sessionDataTemp}) if err != nil { - t.sendError("unable to marshal session response", err, t.stream) + t.sendError(ctx, "unable to marshal session response", err, t.stream) return trace.Wrap(err) } envelope.Payload = string(sessionMetadataResponse) @@ -414,12 +407,12 @@ func (t *TerminalHandler) writeSessionData() error { envelopeBytes, err := proto.Marshal(envelope) if err != nil { - t.sendError("unable to marshal session data event for web client", err, t.stream) + t.sendError(ctx, "unable to marshal session data event for web client", err, t.stream) return trace.Wrap(err) } if err := t.stream.WriteMessage(websocket.BinaryMessage, envelopeBytes); err != nil { - t.sendError("unable to write message to socket", err, t.stream) + t.sendError(ctx, "unable to write message to socket", err, t.stream) return trace.Wrap(err) } @@ -455,23 +448,23 @@ func (t *TerminalHandler) handler(ws *websocket.Conn, r *http.Request) { tctx := oteltrace.ContextWithRemoteSpanContext(context.Background(), oteltrace.SpanContextFromContext(r.Context())) ctx, cancel := context.WithCancel(tctx) defer cancel() - t.stream = terminal.NewStream(ctx, terminal.StreamConfig{WS: ws, Logger: t.log}) + t.stream = terminal.NewStream(ctx, terminal.StreamConfig{WS: ws, Logger: t.logger}) // Create a Teleport client, if not able to, show the reason to the user in // the terminal. tc, err := t.makeClient(ctx, t.stream, ws.RemoteAddr().String()) if err != nil { - t.log.WithError(err).Info("Failed creating a client for session") - t.stream.WriteError(err.Error()) + t.logger.InfoContext(ctx, "Failed creating a client for session", "error", err) + t.stream.WriteError(ctx, err.Error()) return } - t.log.Debug("Creating websocket stream") + t.logger.DebugContext(ctx, "Creating websocket stream") defaultCloseHandler := ws.CloseHandler() ws.SetCloseHandler(func(code int, text string) error { t.closedByClient.Store(true) - t.log.Debug("web socket was closed by client - terminating session") + t.logger.DebugContext(ctx, "web socket was closed by client - terminating session") // Call the default close handler if one was set. if defaultCloseHandler != nil { @@ -490,7 +483,7 @@ func (t *TerminalHandler) handler(ws *websocket.Conn, r *http.Request) { // Block until the terminal session is complete. t.streamTerminal(ctx, tc) - t.log.Debug("Closing websocket stream") + t.logger.DebugContext(ctx, "Closing websocket stream") } type stderrWriter struct { @@ -498,7 +491,7 @@ type stderrWriter struct { } func (s stderrWriter) Write(b []byte) (int, error) { - s.stream.WriteError(string(b)) + s.stream.WriteError(context.Background(), string(b)) return len(b), nil } @@ -547,12 +540,12 @@ func (t *TerminalHandler) makeClient(ctx context.Context, stream *terminal.Strea // The web session was closed by the client while the ssh connection was being established. // Attempt to close the SSH session instead of proceeding with the window change request. if t.closedByClient.Load() { - t.log.Debug("websocket was closed by client, terminating established ssh connection to host") + t.logger.DebugContext(ctx, "websocket was closed by client, terminating established ssh connection to host") return false, trace.Wrap(s.Close()) } if err := s.WindowChange(ctx, t.term.H, t.term.W); err != nil { - t.log.Error(err) + t.logger.ErrorContext(ctx, "failed to send window change request", "error", err) } return false, nil @@ -569,7 +562,7 @@ func (t *sshBaseHandler) issueSessionMFACerts(ctx context.Context, tc *client.Te ctx, span := t.tracer.Start(ctx, "terminal/issueSessionMFACerts") defer span.End() - log.Debug("Attempting to issue a single-use user certificate with an MFA check.") + t.logger.DebugContext(ctx, "Attempting to issue a single-use user certificate with an MFA check") // Prepare MFA check request. mfaRequiredReq := &authproto.IsMFARequiredRequest{ @@ -812,8 +805,8 @@ func (t *TerminalHandler) streamTerminal(ctx context.Context, tc *client.Telepor nc, err := t.connectToHost(ctx, t.stream, tc, t.connectToNodeWithMFA) if err != nil { - t.log.WithError(err).Warn("Unable to stream terminal - failure connecting to host") - t.stream.WriteError(err.Error()) + t.logger.WarnContext(ctx, "Unable to stream terminal - failure connecting to host", "error", err) + t.stream.WriteError(ctx, err.Error()) return } defer nc.Close() @@ -823,7 +816,7 @@ func (t *TerminalHandler) streamTerminal(ctx context.Context, tc *client.Telepor // by the client from here on out should either get caught in the OnShellCreated callback // set on the [tc] or in [TerminalHandler.Close]. if t.closedByClient.Load() { - t.log.Debug("websocket was closed by client, aborting establishing ssh connection to host") + t.logger.DebugContext(ctx, "websocket was closed by client, aborting establishing ssh connection to host") return } @@ -833,7 +826,7 @@ func (t *TerminalHandler) streamTerminal(ctx context.Context, tc *client.Telepor nc.OnMFA = func() { baseCeremony := newMFACeremony(t.stream.WSStream, nil) if err := t.presenceChecker(ctx, out, t.userAuthClient, t.sessionData.ID.String(), baseCeremony); err != nil { - t.log.WithError(err).Warn("Unable to stream terminal - failure performing presence checks") + t.logger.WarnContext(ctx, "Unable to stream terminal - failure performing presence checks", "error", err) return } } @@ -844,7 +837,7 @@ func (t *TerminalHandler) streamTerminal(ctx context.Context, tc *client.Telepor defer monitorCancel() go func() { if err := monitorSessionLatency(monitorCtx, t.clock, t.stream.WSStream, nc.Client); err != nil { - t.log.WithError(err).Warn("failure monitoring session latency") + t.logger.WarnContext(monitorCtx, "failure monitoring session latency", "error", err) } }() @@ -852,8 +845,8 @@ func (t *TerminalHandler) streamTerminal(ctx context.Context, tc *client.Telepor // If we are joining a session, send the session data right away, we // know the session ID if t.tracker != nil { - if err := t.writeSessionData(); err != nil { - t.log.WithError(err).Warn("Failure sending session data") + if err := t.writeSessionData(ctx); err != nil { + t.logger.WarnContext(ctx, "Failure sending session data", "error", err) } close(sessionDataSent) } else { @@ -862,7 +855,7 @@ func (t *TerminalHandler) streamTerminal(ctx context.Context, tc *client.Telepor // created and the server sends us the session ID it is using writeSessionCtx, writeSessionCancel := context.WithCancel(ctx) defer writeSessionCancel() - waitForSessionID := prepareToReceiveSessionID(writeSessionCtx, t.log, nc) + waitForSessionID := prepareToReceiveSessionID(writeSessionCtx, t.logger, nc) // wait in a new goroutine because the server won't set a // session ID until we open a shell @@ -875,13 +868,13 @@ func (t *TerminalHandler) streamTerminal(ctx context.Context, tc *client.Telepor t.sessionData.ID = sid fallthrough case sessionIDNotModified: - if err := t.writeSessionData(); err != nil { - t.log.WithError(err).Warn("Failure sending session data") + if err := t.writeSessionData(ctx); err != nil { + t.logger.WarnContext(ctx, "Failure sending session data", "error", err) } case sessionIDNotSent: - t.log.Warn("Failed to receive session data") + t.logger.WarnContext(ctx, "Failed to receive session data") default: - t.log.Warnf("Invalid session ID status %v", status) + t.logger.WarnContext(ctx, "Invalid session ID status", "status", status) } }() } @@ -890,7 +883,7 @@ func (t *TerminalHandler) streamTerminal(ctx context.Context, tc *client.Telepor // either an error occurs or it completes successfully. if err = nc.RunInteractiveShell(ctx, t.participantMode, t.tracker, nil, beforeStart); err != nil { if !t.closedByClient.Load() { - t.stream.WriteError(err.Error()) + t.stream.WriteError(ctx, err.Error()) } return } @@ -904,15 +897,15 @@ func (t *TerminalHandler) streamTerminal(ctx context.Context, tc *client.Telepor // Send close envelope to web terminal upon exit without an error. if err := t.stream.SendCloseMessage(t.sessionData.ServerID); err != nil { - t.log.WithError(err).Error("Unable to send close event to web client.") + t.logger.ErrorContext(ctx, "Unable to send close event to web client", "error", err) } if err := t.stream.Close(); err != nil && !errors.Is(err, io.EOF) { - t.log.WithError(err).Error("Unable to close client web socket.") + t.logger.ErrorContext(ctx, "Unable to close client web socket", "error", err) return } - t.log.Debug("Sent close event to web client.") + t.logger.DebugContext(ctx, "Sent close event to web client") } // connectToNode attempts to connect to the host with the already @@ -920,9 +913,9 @@ func (t *TerminalHandler) streamTerminal(ctx context.Context, tc *client.Telepor func (t *sshBaseHandler) connectToNode(ctx context.Context, ws terminal.WSConn, tc *client.TeleportClient, accessChecker services.AccessChecker, getAgent teleagent.Getter, signer agentless.SignerCreator) (*client.NodeClient, error) { conn, err := t.router.DialHost(ctx, ws.RemoteAddr(), ws.LocalAddr(), t.sessionData.ServerID, strconv.Itoa(t.sessionData.ServerHostPort), tc.SiteName, accessChecker, getAgent, signer) if err != nil { - t.log.WithError(err).Warn("Unable to stream terminal - failed to dial host.") + t.logger.WarnContext(ctx, "Unable to stream terminal - failed to dial host", "error", err) - if errors.Is(err, trace.NotFound(teleport.NodeIsAmbiguous)) { + if errors.Is(err, teleport.ErrNodeIsAmbiguous) { const message = "error: ambiguous host could match multiple nodes\n\nHint: try addressing the node by unique id (ex: user@node-id)\n" return nil, trace.NotFound(message) } @@ -1010,7 +1003,7 @@ func (t *sshBaseHandler) connectToNodeWithMFABase(ctx context.Context, ws termin } // sendError sends an error message to the client using the provided websocket. -func (t *sshBaseHandler) sendError(errMsg string, err error, ws terminal.WSConn) { +func (t *sshBaseHandler) sendError(ctx context.Context, errMsg string, err error, ws terminal.WSConn) { envelope := &terminal.Envelope{ Version: defaults.WebsocketVersion, Type: defaults.WebsocketError, @@ -1019,10 +1012,10 @@ func (t *sshBaseHandler) sendError(errMsg string, err error, ws terminal.WSConn) envelopeBytes, err := proto.Marshal(envelope) if err != nil { - t.log.WithError(err).Error("failed to marshal error message") + t.logger.ErrorContext(ctx, "failed to marshal error message", "error", err) } if err := ws.WriteMessage(websocket.BinaryMessage, envelopeBytes); err != nil { - t.log.WithError(err).Error("failed to send error message") + t.logger.ErrorContext(ctx, "failed to send error message", "error", err) } } @@ -1033,22 +1026,22 @@ func (t *TerminalHandler) streamEvents(ctx context.Context, tc *client.TeleportC select { // Send push events that come over the events channel to the web client. case event := <-tc.EventsChannel(): - logger := t.log.WithField("event", event.GetType()) + logger := t.logger.With("event", event.GetType()) data, err := json.Marshal(event) if err != nil { - logger.WithError(err).Error("Unable to marshal audit event") + logger.ErrorContext(ctx, "Unable to marshal audit event", "error", err) continue } - logger.Debug("Sending audit event to web client.") + logger.DebugContext(ctx, "Sending audit event to web client") if err := t.stream.WriteAuditEvent(data); err != nil { if errors.Is(err, websocket.ErrCloseSent) { - logger.WithError(err).Debug("Websocket was closed, no longer streaming events") + logger.DebugContext(ctx, "Websocket was closed, no longer streaming events", "error", err) return } - logger.WithError(err).Error("Unable to send audit event to web client") + logger.ErrorContext(ctx, "Unable to send audit event to web client", "error", err) continue } diff --git a/lib/web/terminal/terminal.go b/lib/web/terminal/terminal.go index 334b7adb1b440..72c9b2d7499c1 100644 --- a/lib/web/terminal/terminal.go +++ b/lib/web/terminal/terminal.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net" "strings" "sync" @@ -30,7 +31,6 @@ import ( "github.com/gogo/protobuf/proto" "github.com/gorilla/websocket" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "golang.org/x/text/encoding" "golang.org/x/text/encoding/unicode" @@ -41,6 +41,7 @@ import ( "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) // WSConn is a gorilla/websocket minimal interface used by our web implementation. @@ -95,7 +96,7 @@ type WSStream struct { WSConn // log holds the structured logger. - log logrus.FieldLogger + log *slog.Logger } // Replace \n with \r\n so the message is correctly aligned. @@ -114,9 +115,9 @@ func (t *WSStream) WriteMessage(messageType int, data []byte) error { } // WriteError displays an error in the terminal window. -func (t *WSStream) WriteError(msg string) { +func (t *WSStream) WriteError(ctx context.Context, msg string) { if _, err := replacer.WriteString(t, msg); err != nil && !errors.Is(err, websocket.ErrCloseSent) { - t.log.WithError(err).Warnf("Unable to send error to terminal: %v", msg) + t.log.WarnContext(ctx, "Unable to send error to terminal", "message", msg, "error", err) } } @@ -156,19 +157,19 @@ func (t *WSStream) processMessages(ctx context.Context) { select { case <-ctx.Done(): default: - t.WriteError(msg) + t.WriteError(ctx, msg) return } } if ty != websocket.BinaryMessage { - t.WriteError(fmt.Sprintf("Expected binary message, got %v", ty)) + t.WriteError(ctx, fmt.Sprintf("Expected binary message, got %v", ty)) return } var envelope Envelope if err := proto.Unmarshal(bytes, &envelope); err != nil { - t.WriteError(fmt.Sprintf("Unable to parse message payload %v", err)) + t.WriteError(ctx, fmt.Sprintf("Unable to parse message payload %v", err)) return } @@ -196,7 +197,7 @@ func (t *WSStream) processMessages(ctx context.Context) { handler, ok := t.handlers[envelope.Type] if !ok { - t.log.Warnf("Received web socket envelope with unknown type %v", envelope.Type) + t.log.WarnContext(ctx, "Received web socket envelope with unknown type", "envelope_type", logutils.TypeAttr(envelope.Type)) continue } @@ -427,13 +428,13 @@ type StreamConfig struct { // The websocket to operate over. Required. WS WSConn // A logger to emit log messages. Optional. - Logger logrus.FieldLogger + Logger *slog.Logger // A custom set of handlers to process messages received // over the websocket. Optional. Handlers map[string]WSHandlerFunc } -func NewWStream(ctx context.Context, ws WSConn, log logrus.FieldLogger, handlers map[string]WSHandlerFunc) *WSStream { +func NewWStream(ctx context.Context, ws WSConn, log *slog.Logger, handlers map[string]WSHandlerFunc) *WSStream { w := &WSStream{ log: log, WSConn: ws, @@ -473,7 +474,7 @@ func NewStream(ctx context.Context, cfg StreamConfig) *Stream { } if cfg.Logger == nil { - cfg.Logger = utils.NewLogger() + cfg.Logger = slog.Default() } t.WSStream = NewWStream(ctx, cfg.WS, cfg.Logger, cfg.Handlers) @@ -497,19 +498,19 @@ func (t *Stream) handleWindowResize(ctx context.Context, envelope Envelope) { var e map[string]interface{} err := json.Unmarshal([]byte(envelope.Payload), &e) if err != nil { - t.log.Warnf("Failed to parse resize payload: %v", err) + t.log.WarnContext(ctx, "Failed to parse resize payload", "error", err) return } size, ok := e["size"].(string) if !ok { - t.log.Errorf("expected size to be of type string, got type %T instead", size) + t.log.ErrorContext(ctx, "got unexpected size type, expected type string", "size_type", logutils.TypeAttr(size)) return } params, err := session.UnmarshalTerminalParams(size) if err != nil { - t.log.Warnf("Failed to retrieve terminal size: %v", err) + t.log.WarnContext(ctx, "Failed to retrieve terminal size", "error", err) return } @@ -519,7 +520,7 @@ func (t *Stream) handleWindowResize(ctx context.Context, envelope Envelope) { } if err := t.sshSession.WindowChange(ctx, params.H, params.W); err != nil { - t.log.Error(err) + t.log.ErrorContext(ctx, "failed to send window change request", "error", err) } } @@ -541,7 +542,7 @@ func (t *Stream) handleFileTransferDecision(ctx context.Context, envelope Envelo } approved, ok := e["approved"].(bool) if !ok { - t.log.Error("Unable to find approved status on response") + t.log.ErrorContext(ctx, "Unable to find approved status on response") return } @@ -551,7 +552,7 @@ func (t *Stream) handleFileTransferDecision(ctx context.Context, envelope Envelo err = t.sshSession.DenyFileTransferRequest(ctx, e.GetString("requestId")) } if err != nil { - t.log.WithError(err).Error("Unable to respond to file transfer request") + t.log.ErrorContext(ctx, "Unable to respond to file transfer request", "error", err) } } @@ -573,7 +574,7 @@ func (t *Stream) handleFileTransferRequest(ctx context.Context, envelope Envelop } download, ok := e["download"].(bool) if !ok { - t.log.Error("Unable to find download param in response") + t.log.ErrorContext(ctx, "Unable to find download param in response") return } @@ -582,7 +583,7 @@ func (t *Stream) handleFileTransferRequest(ctx context.Context, envelope Envelop Location: e.GetString("location"), Filename: e.GetString("filename"), }); err != nil { - t.log.WithError(err).Error("Unable to request file transfer") + t.log.ErrorContext(ctx, "Unable to request file transfer", "error", err) } } diff --git a/lib/web/terminal_test.go b/lib/web/terminal_test.go index 615993c89710a..ea6182464bf9b 100644 --- a/lib/web/terminal_test.go +++ b/lib/web/terminal_test.go @@ -24,6 +24,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net/http" "net/http/httptest" "net/url" @@ -77,7 +78,7 @@ func TestTerminalReadFromClosedConn(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - stream := terminal.NewStream(ctx, terminal.StreamConfig{WS: conn, Logger: utils.NewLoggerForTests()}) + stream := terminal.NewStream(ctx, terminal.StreamConfig{WS: conn, Logger: utils.NewSlogLoggerForTests()}) // close the stream before we attempt to read from it, // this will produce a net.ErrClosed error on the read @@ -203,7 +204,7 @@ func connectToHost(ctx context.Context, cfg connectConfig) (*testTerminal, error t.stream = terminal.NewStream(ctx, terminal.StreamConfig{ WS: ws, - Logger: utils.NewLogger(), + Logger: slog.Default(), Handlers: cfg.handlers, }) diff --git a/lib/web/tty_playback.go b/lib/web/tty_playback.go index f601f4237666c..838c1b45d1efd 100644 --- a/lib/web/tty_playback.go +++ b/lib/web/tty_playback.go @@ -34,6 +34,7 @@ import ( "github.com/gravitational/teleport/lib/reversetunnelclient" "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/utils" + logutils "github.com/gravitational/teleport/lib/utils/log" ) const ( @@ -53,6 +54,47 @@ const ( actionPause = byte(1) ) +func (h *Handler) sessionLengthHandle( + w http.ResponseWriter, + r *http.Request, + p httprouter.Params, + sctx *SessionContext, + site reversetunnelclient.RemoteSite, +) (interface{}, error) { + sID := p.ByName("sid") + if sID == "" { + return nil, trace.BadParameter("missing session ID in request URL") + } + + ctx, cancel := context.WithCancel(r.Context()) + defer cancel() + + clt, err := sctx.GetUserClient(ctx, site) + if err != nil { + return nil, trace.Wrap(err) + } + + evts, errs := clt.StreamSessionEvents(ctx, session.ID(sID), 0) + for { + select { + case err := <-errs: + return nil, trace.Wrap(err) + case evt, ok := <-evts: + if !ok { + return nil, trace.NotFound("could not find end event for session %v", sID) + } + switch evt := evt.(type) { + case *events.SessionEnd: + return map[string]any{"durationMs": evt.EndTime.Sub(evt.StartTime).Milliseconds()}, nil + case *events.WindowsDesktopSessionEnd: + return map[string]any{"durationMs": evt.EndTime.Sub(evt.StartTime).Milliseconds()}, nil + case *events.DatabaseSessionEnd: + return map[string]any{"durationMs": evt.EndTime.Sub(evt.StartTime).Milliseconds()}, nil + } + } + } +} + func (h *Handler) ttyPlaybackHandle( w http.ResponseWriter, r *http.Request, @@ -70,14 +112,14 @@ func (h *Handler) ttyPlaybackHandle( return nil, trace.Wrap(err) } - h.log.Debug("upgrading to websocket") + h.logger.DebugContext(r.Context(), "upgrading to websocket") upgrader := websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } ws, err := upgrader.Upgrade(w, r, nil) if err != nil { - h.log.Warn("failed upgrade", err) + h.logger.WarnContext(r.Context(), "failed upgrade", "error", err) // if Upgrade fails, it automatically replies with an HTTP error // (this means we don't need to return an error here) return nil, nil @@ -91,7 +133,7 @@ func (h *Handler) ttyPlaybackHandle( Context: r.Context(), }) if err != nil { - h.log.Warn("player error", err) + h.logger.WarnContext(r.Context(), "player error", "error", err) writeError(ws, err) return nil, nil } @@ -105,18 +147,18 @@ func (h *Handler) ttyPlaybackHandle( typ, b, err := ws.ReadMessage() if err != nil { if !utils.IsOKNetworkError(err) { - log.Warnf("websocket read error: %v", err) + h.logger.WarnContext(ctx, "websocket read error", "error", err) } return } if typ != websocket.BinaryMessage { - log.Debugf("skipping unknown websocket message type %v", typ) + h.logger.DebugContext(ctx, "skipping unknown websocket message type", "message_type", logutils.TypeAttr(typ)) continue } if err := handlePlaybackAction(b, player); err != nil { - log.Warnf("skipping bad action: %v", err) + h.logger.WarnContext(ctx, "skipping bad action", "error", err) continue } } @@ -125,12 +167,12 @@ func (h *Handler) ttyPlaybackHandle( go func() { defer cancel() defer func() { - h.log.Debug("closing websocket") + h.logger.DebugContext(ctx, "closing websocket") if err := ws.WriteMessage(websocket.CloseMessage, nil); err != nil { - h.log.Debugf("error sending close message: %v", err) + h.logger.DebugContext(r.Context(), "error sending close message", "error", err) } if err := ws.Close(); err != nil { - h.log.Debugf("error closing websocket: %v", err) + h.logger.DebugContext(ctx, "error closing websocket", "error", err) } }() @@ -168,7 +210,7 @@ func (h *Handler) ttyPlaybackHandle( writeSize := func(size string) error { ts, err := session.UnmarshalTerminalParams(size) if err != nil { - h.log.Debugf("Ignoring invalid terminal size %q", size) + h.logger.DebugContext(ctx, "Ignoring invalid terminal size", "terminal_size", size) return nil // don't abort playback due to a bad event } @@ -189,7 +231,7 @@ func (h *Handler) ttyPlaybackHandle( if !ok { // send any playback errors to the browser if err := writeError(ws, player.Err()); err != nil { - h.log.Warnf("failed to send error message to browser: %v", err) + h.logger.WarnContext(ctx, "failed to send error message to browser", "error", err) } return } @@ -197,13 +239,13 @@ func (h *Handler) ttyPlaybackHandle( switch evt := evt.(type) { case *events.SessionStart: if err := writeSize(evt.TerminalSize); err != nil { - h.log.Debugf("Failed to write resize: %v", err) + h.logger.DebugContext(ctx, "Failed to write resize", "error", err) return } case *events.SessionPrint: if err := writePTY(evt.Data, uint64(evt.DelayMilliseconds)); err != nil { - h.log.Debugf("Failed to send PTY data: %v", err) + h.logger.DebugContext(ctx, "Failed to send PTY data", "error", err) return } @@ -212,20 +254,20 @@ func (h *Handler) ttyPlaybackHandle( // at the end of the recording is processed and the allow // the progress bar to go to 100% if err := writePTY(nil, uint64(evt.EndTime.Sub(evt.StartTime)/time.Millisecond)); err != nil { - h.log.Debugf("Failed to send session end data: %v", err) + h.logger.DebugContext(ctx, "Failed to send session end data", "error", err) return } case *events.Resize: if err := writeSize(evt.TerminalSize); err != nil { - h.log.Debugf("Failed to write resize: %v", err) + h.logger.DebugContext(ctx, "Failed to write resize", "error", err) return } case *events.SessionLeave: // do nothing default: - h.log.Debugf("unexpected event type %T", evt) + h.logger.DebugContext(ctx, "unexpected event type", "event_type", logutils.TypeAttr(evt)) } } } diff --git a/lib/web/ui/app.go b/lib/web/ui/app.go index 5661d3290960c..934978ccb8d44 100644 --- a/lib/web/ui/app.go +++ b/lib/web/ui/app.go @@ -20,10 +20,10 @@ package ui import ( "cmp" + "context" + "log/slog" "sort" - "github.com/sirupsen/logrus" - "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/ui" "github.com/gravitational/teleport/lib/utils" @@ -113,7 +113,7 @@ type MakeAppsConfig struct { // UserGroupLookup is a map of user groups to provide to each App UserGroupLookup map[string]types.UserGroup // Logger is a logger used for debugging while making an app - Logger logrus.FieldLogger + Logger *slog.Logger // RequireRequest indicates if a returned resource is only accessible after an access request RequiresRequest bool } @@ -126,7 +126,7 @@ func MakeApp(app types.Application, c MakeAppsConfig) App { for _, userGroupName := range app.GetUserGroups() { userGroup := c.UserGroupLookup[userGroupName] if userGroup == nil { - c.Logger.Debugf("Unable to find user group %s when creating user groups, skipping", userGroupName) + c.Logger.DebugContext(context.Background(), "Unable to find user group when creating user groups, skipping", "user_group", userGroupName) continue } diff --git a/lib/web/ui/integration.go b/lib/web/ui/integration.go index b08143978df21..3614a51d09a7f 100644 --- a/lib/web/ui/integration.go +++ b/lib/web/ui/integration.go @@ -371,6 +371,26 @@ type AWSOIDCDeployDatabaseServiceResponse struct { ClusterDashboardURL string `json:"clusterDashboardUrl"` } +// AWSOIDCDeployedDatabaseService represents a Teleport Database Service that is deployed in Amazon ECS. +type AWSOIDCDeployedDatabaseService struct { + // Name is the ECS Service name. + Name string `json:"name,omitempty"` + // DashboardURL is the link to the ECS Service in Amazon Web Console. + DashboardURL string `json:"dashboardUrl,omitempty"` + // ValidTeleportConfig returns whether this ECS Service has a valid Teleport Configuration for a deployed Database Service. + // ECS Services with non-valid configuration require the user to take action on them. + // No MatchingLabels are returned with an invalid configuration. + ValidTeleportConfig bool `json:"validTeleportConfig,omitempty"` + // MatchingLabels are the labels that are used by the Teleport Database Service to know which databases it should proxy. + MatchingLabels []ui.Label `json:"matchingLabels,omitempty"` +} + +// AWSOIDCListDeployedDatabaseServiceResponse is a list of Teleport Database Services that are deployed as ECS Services. +type AWSOIDCListDeployedDatabaseServiceResponse struct { + // Services are the ECS Services. + Services []AWSOIDCDeployedDatabaseService `json:"services"` +} + // AWSOIDCEnrollEKSClustersRequest is a request to ListEKSClusters using the AWS OIDC Integration. type AWSOIDCEnrollEKSClustersRequest struct { // Region is the AWS Region. diff --git a/lib/web/user_groups.go b/lib/web/user_groups.go index 4e0927dabf55e..a61340491e8d8 100644 --- a/lib/web/user_groups.go +++ b/lib/web/user_groups.go @@ -57,7 +57,7 @@ func (h *Handler) getUserGroups(_ http.ResponseWriter, r *http.Request, params h UseSearchAsRoles: true, }) if err != nil { - h.log.Debugf("Unable to fetch applications while listing user groups, unable to display associated applications: %v", err) + h.logger.DebugContext(r.Context(), "Unable to fetch applications while listing user groups, unable to display associated applications", "error", err) } appServerLookup := make(map[string]types.AppServer, len(appServers)) @@ -71,7 +71,7 @@ func (h *Handler) getUserGroups(_ http.ResponseWriter, r *http.Request, params h for _, appName := range userGroup.GetApplications() { app := appServerLookup[appName] if app == nil { - h.log.Debugf("Unable to find application %s when creating user groups, skipping", appName) + h.logger.DebugContext(r.Context(), "Unable to find application when creating user groups, skipping", "app", appName) continue } apps = append(apps, app.GetApp()) diff --git a/lib/web/web.go b/lib/web/web.go deleted file mode 100644 index ab73f43324f92..0000000000000 --- a/lib/web/web.go +++ /dev/null @@ -1,34 +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 web - -import ( - "github.com/sirupsen/logrus" - - "github.com/gravitational/teleport" -) - -var log = newPackageLogger() - -// newPackageLogger returns a new instance of the logger -// configured for the package -func newPackageLogger(subcomponents ...string) logrus.FieldLogger { - return logrus.WithField(teleport.ComponentKey, - teleport.Component(append([]string{teleport.ComponentWeb}, subcomponents...)...)) -} diff --git a/proto/accessgraph/v1alpha/access_graph_service.proto b/proto/accessgraph/v1alpha/access_graph_service.proto index b78b473327ffe..7e61325e64a66 100644 --- a/proto/accessgraph/v1alpha/access_graph_service.proto +++ b/proto/accessgraph/v1alpha/access_graph_service.proto @@ -26,6 +26,7 @@ import "accessgraph/v1alpha/entra.proto"; import "accessgraph/v1alpha/events.proto"; import "accessgraph/v1alpha/gitlab.proto"; import "accessgraph/v1alpha/graph.proto"; +import "accessgraph/v1alpha/netiq.proto"; import "accessgraph/v1alpha/resources.proto"; option go_package = "github.com/gravitational/teleport/gen/proto/go/accessgraph/v1alpha;accessgraphv1alpha"; @@ -90,6 +91,9 @@ service AccessGraphService { // AzureEventsStream is a stream of commands to the Azure importer rpc AzureEventsStream(stream AzureEventsStreamRequest) returns (stream AzureEventsStreamResponse); + + // NetIQEventsStream is a stream of commands to the NetIQ importer. + rpc NetIQEventsStream(stream NetIQEventsStreamRequest) returns (stream NetIQEventsStreamResponse); } // QueryRequest is a request to query the access graph. @@ -289,3 +293,22 @@ message AzureSyncOperation {} // AzureEventsStreamResponse is a response from AzureEventsStream message AzureEventsStreamResponse {} + +// NetIQEventsStreamRequest is a request to send commands to the NetIQ importer +message NetIQEventsStreamRequest { + oneof operation { + // sync is a command to sync the access graph with the NetIQ state. + NetIQSyncOperation sync = 1; + // upsert is a command to put a resource into the access graph or update it. + NetIQResourceList upsert = 2; + // delete is a command to delete a resource from the access graph when it's deleted. + NetIQResourceList delete = 3; + } +} + +// NetIQSyncOperation is a command that Teleport sends to the access graph service +// at the end of the sync process. +message NetIQSyncOperation {} + +// NetIQEventsStreamResponse is a response from NetIQEventsStream +message NetIQEventsStreamResponse {} diff --git a/proto/accessgraph/v1alpha/azure.proto b/proto/accessgraph/v1alpha/azure.proto index 1050c3c98f75e..58bef9b36e97b 100644 --- a/proto/accessgraph/v1alpha/azure.proto +++ b/proto/accessgraph/v1alpha/azure.proto @@ -127,7 +127,7 @@ message AzureRoleDefinition { string type = 9; } -// AzurePermission defines the actions and not (disallowed) actions for a role definition +// AzureRBACPermission defines the actions and not (disallowed) actions for a role definition message AzureRBACPermission { // actions define the resources and verbs allowed on the resources repeated string actions = 1; diff --git a/proto/accessgraph/v1alpha/netiq.proto b/proto/accessgraph/v1alpha/netiq.proto new file mode 100644 index 0000000000000..eb108781bbaaa --- /dev/null +++ b/proto/accessgraph/v1alpha/netiq.proto @@ -0,0 +1,187 @@ +/* + * 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 . + */ + +syntax = "proto3"; + +package accessgraph.v1alpha; + +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/gravitational/teleport/gen/proto/go/accessgraph/v1alpha;accessgraphv1alpha"; + +// NetIQResourceList is a request that contains resources to be sync. +message NetIQResourceList { + // resources is a list of NetIQ resources to sync. + repeated NetIQObject resources = 1; +} + +// NetIQObject represents a NetIQ resource +message NetIQObject { + oneof object { + // group represents a NetIQ group in an organization. + NetIQGroup group = 1; + // group_member represents a NetIQ group member. + NetIQGroupMember group_member = 2; + // resource represents a NetIQ resource. + NetIQResource resource = 3; + // role represents a role with certain access levels to a resource. + NetIQRole role = 4; + // parent_role_ref represents a parent relationship between roles. + NetIQRoleRef parent_role_ref = 5; + // user represents a NetIQ user. + NetIQUser user = 6; + // resource_role_ref represents a resource assignment to a role. + NetIQResourceAssignmentRef resource_role_ref = 7; + //role_member_ref represents a member being member of a role. + NetIQMemberAssignmentRef role_member_ref = 8; + } +} + +// NetIQGroup represents a NetIQ group +message NetIQGroup { + // name is the group name. + string name = 1; + // id is the universal identifier for the group. + string id = 2; + // description is the group description. + string description = 3; +} + +// NetIQGroupMember represents a NetIQ group member +message NetIQGroupMember { + // group_id is the group id. + string group_id = 1; + // user_id is the universal identifier for the user. + string user_id = 2; + // is_group_assignment is a flag that determines whether the member is a group assignment. + bool is_group_assignment = 3; +} + +// NetIQResource represents a NetIQ resource +message NetIQResource { + // name is the resource name. + string name = 1; + // id is the universal identifier for the resource. + string id = 2; + // description is the project description. + string description = 3; + + // categories is the list of categories the resource belongs to. + repeated NetIQCategory categories = 4; +} + +// NetIQCategory is a resource category. +message NetIQCategory { + // name is the resource name. + string name = 1; + // id is the universal identifier for the category. + string id = 2; +} + +// NetIQRole represents a NetIQ role +message NetIQRole { + // name is the resource name. + string name = 1; + // id is the universal identifier for the role. + string id = 2; + // description is the project description. + string description = 3; + // categories is the list of categories the resource belongs to. + repeated NetIQCategory categories = 4; + + // RoleLevel represents the role level. + message RoleLevel { + // name is the name of the role level. + string name = 1; + // level is the level of the role level. + int32 level = 2; + // cn is the common name. + string cn = 3; + } + RoleLevel level = 5; +} + +// NetIQRoleRef represents a NetIQ Role reference. +message NetIQRoleRef { + // child_role_id is the group id of a role that is a child role of parent_role_id. + string child_role_id = 1; + // parent_role_id is the universal identifier for the role that is parent to child_role_id. + string parent_role_id = 2; + // level is the level of the role. + int32 level = 3; + // request_description is the description of the request. + string request_description = 4; +} + +// NetIQUser represents a NetIQ user. +message NetIQUser { + // id is the id of the user. + string id = 1; + // email is the user's email. + string email = 2; + // name is the user's name. + string name = 3; + // is_disabled indicates if a user is disabled. + bool is_disabled = 4; +} + +// NetIQResourceAssignmentRef represents a NetIQ resource assignment reference. +message NetIQResourceAssignmentRef { + // role_id is the group id of a role that is assigned to resource_id. + string role_id = 1; + // resource_id is the universal identifier for the resource that is assigned to role_id. + string resource_id = 2; + // mapping_description is the description of the mapping. + string mapping_description = 4; + // status_code is the status code of the role assignment. + uint32 status_code = 5; +} + +// NetIQMemberAssignmentRef represents a NetIQ resource assignment reference. +message NetIQMemberAssignmentRef { + // role_id is the group id of a role that user_id is member of. + string role_id = 1; + // dn is the universal identifier for the user that is member of role_id. + string dn = 2; + // recipient_type identifies the recipient provenance. + RoleRecipientType recipient_type = 3; + // recipient_type_subcontainer is the sub container of the recipient type. + string recipient_type_subcontainer = 4; + // status_code is the status code of the role assignment. + uint32 status_code = 5; + // StatusDstatus_displayisplay is the display of the status. + string status_display = 6; + // effective_date is the effective date of the role assignment. + google.protobuf.Timestamp effective_date = 7; + // expiry_date is the expiry date of the role assignment. + google.protobuf.Timestamp expiry_date = 8; + // description is the description of the role assignment. + string description = 9; + // grant is a flag that determines whether the role assignment is granted. + bool grant = 10; +} + +// RoleRecipientType is the type of the recipient. +enum RoleRecipientType { + // ROLE_RECIPIENT_TYPE_UNSPECIFIED is a unspecified role recipient type. + ROLE_RECIPIENT_TYPE_UNSPECIFIED = 0; + // ROLE_RECIPIENT_TYPE_USER represents a user being member of a role. + ROLE_RECIPIENT_TYPE_USER = 1; + // ROLE_RECIPIENT_TYPE_GROUP represents a group being member of a role. + ROLE_RECIPIENT_TYPE_GROUP = 2; +} diff --git a/rfd/0007-rbac-oss.md b/rfd/0007-rbac-oss.md index 33848f7780105..23d33cb443f8d 100644 --- a/rfd/0007-rbac-oss.md +++ b/rfd/0007-rbac-oss.md @@ -90,7 +90,11 @@ role: name: user spec: options: - port_forwarding: true + ssh_port_forwarding: + remote: + enabled: true + local: + enabled: true max_session_ttl: 30h forward_agent: true enhanced_recording: ['command', 'network'] diff --git a/rfd/0008-application-access.md b/rfd/0008-application-access.md index 89ec8b837a3fd..adf15b34e8ede 100644 --- a/rfd/0008-application-access.md +++ b/rfd/0008-application-access.md @@ -303,7 +303,11 @@ version: v3 spec: options: forward_agent: true - port_forwarding: false + ssh_port_forwarding: + remote: + enabled: false + local: + enabled: false allow: logins: ["rjones"] # Application labels define labels that an application must match for this diff --git a/tool/tbot/kube.go b/tool/tbot/kube.go index 2314ec83bc984..997b668087e11 100644 --- a/tool/tbot/kube.go +++ b/tool/tbot/kube.go @@ -73,6 +73,10 @@ func onKubeCredentialsCommand( return trace.Wrap(err) } + if err = destination.CheckAndSetDefaults(); err != nil { + return trace.Wrap(err) + } + idData, err := destination.Read(ctx, config.IdentityFilePath) if err != nil { return trace.Wrap(err) diff --git a/tool/tctl/common/collection.go b/tool/tctl/common/collection.go index 3c844a28637a7..c31d2a25ed0bf 100644 --- a/tool/tctl/common/collection.go +++ b/tool/tctl/common/collection.go @@ -1939,6 +1939,28 @@ func (c *autoUpdateVersionCollection) writeText(w io.Writer, verbose bool) error return trace.Wrap(err) } +type autoUpdateAgentRolloutCollection struct { + rollout *autoupdatev1pb.AutoUpdateAgentRollout +} + +func (c *autoUpdateAgentRolloutCollection) resources() []types.Resource { + return []types.Resource{types.Resource153ToLegacy(c.rollout)} +} + +func (c *autoUpdateAgentRolloutCollection) writeText(w io.Writer, verbose bool) error { + t := asciitable.MakeTable([]string{"Name", "Start Version", "Target Version", "Mode", "Schedule", "Strategy"}) + t.AddRow([]string{ + c.rollout.GetMetadata().GetName(), + fmt.Sprintf("%v", c.rollout.GetSpec().GetStartVersion()), + fmt.Sprintf("%v", c.rollout.GetSpec().GetTargetVersion()), + fmt.Sprintf("%v", c.rollout.GetSpec().GetAutoupdateMode()), + fmt.Sprintf("%v", c.rollout.GetSpec().GetSchedule()), + fmt.Sprintf("%v", c.rollout.GetSpec().GetStrategy()), + }) + _, err := t.AsBuffer().WriteTo(w) + return trace.Wrap(err) +} + type accessMonitoringRuleCollection struct { items []*accessmonitoringrulesv1pb.AccessMonitoringRule } diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go index e77b7eb4aaf4a..cdb8115e9b9a6 100644 --- a/tool/tctl/common/resource_command.go +++ b/tool/tctl/common/resource_command.go @@ -180,6 +180,7 @@ func (rc *ResourceCommand) Initialize(app *kingpin.Application, _ *tctlcfg.Globa types.KindAutoUpdateConfig: rc.createAutoUpdateConfig, types.KindAutoUpdateVersion: rc.createAutoUpdateVersion, types.KindGitServer: rc.createGitServer, + types.KindAutoUpdateAgentRollout: rc.createAutoUpdateAgentRollout, } rc.UpdateHandlers = map[ResourceKind]ResourceCreateHandler{ types.KindUser: rc.updateUser, @@ -201,6 +202,7 @@ func (rc *ResourceCommand) Initialize(app *kingpin.Application, _ *tctlcfg.Globa types.KindAutoUpdateVersion: rc.updateAutoUpdateVersion, types.KindDynamicWindowsDesktop: rc.updateDynamicWindowsDesktop, types.KindGitServer: rc.updateGitServer, + types.KindAutoUpdateAgentRollout: rc.updateAutoUpdateAgentRollout, } rc.config = config @@ -407,6 +409,9 @@ func (rc *ResourceCommand) createTrustedCluster(ctx context.Context, client *aut return trace.AlreadyExists("trusted cluster %q already exists", name) } + //nolint:staticcheck // SA1019. UpsertTrustedCluster is deprecated but will + // continue being supported for tctl clients. + // TODO(bernardjkim) consider using UpsertTrustedClusterV2 in VX.0.0 out, err := client.UpsertTrustedCluster(ctx, tc) if err != nil { // If force is used and UpsertTrustedCluster returns trace.AlreadyExists, @@ -1617,6 +1622,7 @@ func (rc *ResourceCommand) Delete(ctx context.Context, client *authclient.Client types.KindNetworkRestrictions, types.KindAutoUpdateConfig, types.KindAutoUpdateVersion, + types.KindAutoUpdateAgentRollout, } if !slices.Contains(singletonResources, rc.ref.Kind) && (rc.ref.Kind == "" || rc.ref.Name == "") { return trace.BadParameter("provide a full resource name to delete, for example:\n$ tctl rm cluster/east\n") @@ -2039,6 +2045,11 @@ func (rc *ResourceCommand) Delete(ctx context.Context, client *authclient.Client return trace.Wrap(err) } fmt.Printf("AutoUpdateVersion has been deleted\n") + case types.KindAutoUpdateAgentRollout: + if err := client.DeleteAutoUpdateAgentRollout(ctx); err != nil { + return trace.Wrap(err) + } + fmt.Printf("AutoUpdateAgentRollout has been deleted\n") default: return trace.BadParameter("deleting resources of type %q is not supported", rc.ref.Kind) } @@ -3283,6 +3294,12 @@ func (rc *ResourceCommand) getCollection(ctx context.Context, client *authclient return nil, trace.Wrap(err) } return &autoUpdateVersionCollection{version}, nil + case types.KindAutoUpdateAgentRollout: + version, err := client.GetAutoUpdateAgentRollout(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + return &autoUpdateAgentRolloutCollection{version}, nil case types.KindAccessMonitoringRule: if rc.ref.Name != "" { rule, err := client.AccessMonitoringRuleClient().GetAccessMonitoringRule(ctx, rc.ref.Name) @@ -3744,6 +3761,37 @@ func (rc *ResourceCommand) updateAutoUpdateVersion(ctx context.Context, client * return nil } +func (rc *ResourceCommand) createAutoUpdateAgentRollout(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error { + version, err := services.UnmarshalProtoResource[*autoupdatev1pb.AutoUpdateAgentRollout](raw.Raw) + if err != nil { + return trace.Wrap(err) + } + + if rc.IsForced() { + _, err = client.UpsertAutoUpdateAgentRollout(ctx, version) + } else { + _, err = client.CreateAutoUpdateAgentRollout(ctx, version) + } + if err != nil { + return trace.Wrap(err) + } + + fmt.Println("autoupdate_agent_rollout has been created") + return nil +} + +func (rc *ResourceCommand) updateAutoUpdateAgentRollout(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error { + version, err := services.UnmarshalProtoResource[*autoupdatev1pb.AutoUpdateAgentRollout](raw.Raw) + if err != nil { + return trace.Wrap(err) + } + if _, err := client.UpdateAutoUpdateAgentRollout(ctx, version); err != nil { + return trace.Wrap(err) + } + fmt.Println("autoupdate_version has been updated") + return nil +} + func (rc *ResourceCommand) createGitServer(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error { server, err := services.UnmarshalGitServer(raw.Raw) if err != nil { diff --git a/tool/tctl/common/resource_command_test.go b/tool/tctl/common/resource_command_test.go index b3e13e9bffb6d..61b2c2650f53a 100644 --- a/tool/tctl/common/resource_command_test.go +++ b/tool/tctl/common/resource_command_test.go @@ -1427,6 +1427,10 @@ func TestCreateResources(t *testing.T) { kind: types.KindAutoUpdateVersion, create: testCreateAutoUpdateVersion, }, + { + kind: types.KindAutoUpdateAgentRollout, + create: testCreateAutoUpdateAgentRollout, + }, { kind: types.KindDynamicWindowsDesktop, create: testCreateDynamicWindowsDesktop, @@ -2387,6 +2391,58 @@ version: v1 require.ErrorContains(t, err, "autoupdate_version \"autoupdate-version\" doesn't exist") } +func testCreateAutoUpdateAgentRollout(t *testing.T, clt *authclient.Client) { + const resourceYAML = `kind: autoupdate_agent_rollout +metadata: + name: autoupdate-agent-rollout + revision: 3a43b44a-201e-4d7f-aef1-ae2f6d9811ed +spec: + start_version: 1.2.3 + target_version: 1.2.3 + autoupdate_mode: "suspended" + schedule: "regular" + strategy: "halt-on-error" +status: + groups: + - name: my-group + state: 1 + config_days: ["*"] + config_start_hour: 12 + config_wait_hours: 0 +version: v1 +` + _, err := runResourceCommand(t, clt, []string{"get", types.KindAutoUpdateAgentRollout, "--format=json"}) + require.ErrorContains(t, err, "doesn't exist") + + // Create the resource. + resourceYAMLPath := filepath.Join(t.TempDir(), "resource.yaml") + require.NoError(t, os.WriteFile(resourceYAMLPath, []byte(resourceYAML), 0644)) + _, err = runResourceCommand(t, clt, []string{"create", resourceYAMLPath}) + require.NoError(t, err) + + // Get the resource + buf, err := runResourceCommand(t, clt, []string{"get", types.KindAutoUpdateAgentRollout, "--format=json"}) + require.NoError(t, err) + resources := mustDecodeJSON[[]*autoupdate.AutoUpdateAgentRollout](t, buf) + require.Len(t, resources, 1) + + var expected autoupdate.AutoUpdateAgentRollout + require.NoError(t, yaml.Unmarshal([]byte(resourceYAML), &expected)) + + require.Empty(t, cmp.Diff( + []*autoupdate.AutoUpdateAgentRollout{&expected}, + resources, + protocmp.IgnoreFields(&headerv1.Metadata{}, "revision"), + protocmp.Transform(), + )) + + // Delete the resource + _, err = runResourceCommand(t, clt, []string{"rm", types.KindAutoUpdateAgentRollout}) + require.NoError(t, err) + _, err = runResourceCommand(t, clt, []string{"get", types.KindAutoUpdateAgentRollout}) + require.ErrorContains(t, err, "autoupdate_agent_rollout \"autoupdate-agent-rollout\" doesn't exist") +} + func testCreateDynamicWindowsDesktop(t *testing.T, clt *authclient.Client) { const resourceYAML = `kind: dynamic_windows_desktop metadata: diff --git a/tool/teleport/testenv/test_server.go b/tool/teleport/testenv/test_server.go index 5d4607223c292..3e034d9fdf0d6 100644 --- a/tool/teleport/testenv/test_server.go +++ b/tool/teleport/testenv/test_server.go @@ -417,7 +417,7 @@ func SetupTrustedCluster(ctx context.Context, t *testing.T, rootServer, leafServ rootProxyTunnelAddr, err := rootServer.ProxyTunnelAddr() require.NoError(t, err) - tc, err := types.NewTrustedCluster("root-cluster", types.TrustedClusterSpecV2{ + tc, err := types.NewTrustedCluster(rootServer.Config.Auth.ClusterName.GetClusterName(), types.TrustedClusterSpecV2{ Enabled: true, Token: StaticToken, ProxyAddress: rootProxyAddr.String(), @@ -431,7 +431,7 @@ func SetupTrustedCluster(ctx context.Context, t *testing.T, rootServer, leafServ }) require.NoError(t, err) - _, err = leafServer.GetAuthServer().UpsertTrustedCluster(ctx, tc) + _, err = leafServer.GetAuthServer().UpsertTrustedClusterV2(ctx, tc) require.NoError(t, err) require.EventuallyWithT(t, func(t *assert.CollectT) { diff --git a/tool/tsh/common/db.go b/tool/tsh/common/db.go index d26a215a8b4e4..baf222ab69f90 100644 --- a/tool/tsh/common/db.go +++ b/tool/tsh/common/db.go @@ -539,7 +539,7 @@ func onDatabaseConfig(cf *CLIConf) error { case dbFormatCommand: cmd, err := dbcmd.NewCmdBuilder(tc, profile, *database, rootCluster, dbcmd.WithPrintFormat(), - dbcmd.WithLogger(log), + dbcmd.WithLogger(logger), dbcmd.WithGetDatabaseFunc(getDatabase), ).GetConnectCommand(cf.Context) if err != nil { @@ -779,7 +779,7 @@ func onDatabaseConnect(cf *CLIConf) error { return trace.Wrap(err) } opts = append(opts, - dbcmd.WithLogger(log), + dbcmd.WithLogger(logger), dbcmd.WithGetDatabaseFunc(dbInfo.getDatabaseForDBCmd), ) diff --git a/tool/tsh/common/proxy.go b/tool/tsh/common/proxy.go index 4f0f1fee92135..2feae62df3081 100644 --- a/tool/tsh/common/proxy.go +++ b/tool/tsh/common/proxy.go @@ -36,7 +36,6 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport" - "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/types" libclient "github.com/gravitational/teleport/lib/client" @@ -63,39 +62,25 @@ func onProxyCommandSSH(cf *CLIConf) error { return trace.Wrap(err) } - var target string - switch { - case tc.Host != "": - targetHost, targetPort, err := net.SplitHostPort(tc.Host) - if err != nil { - targetHost = tc.Host - targetPort = strconv.Itoa(tc.HostPort) - } - targetHost = cleanTargetHost(targetHost, tc.WebProxyHost(), clt.ClusterName()) - target = net.JoinHostPort(targetHost, targetPort) - case len(tc.SearchKeywords) != 0 || tc.PredicateExpression != "": - nodes, err := client.GetAllResources[types.Server](cf.Context, clt.AuthClient, tc.ResourceFilter(types.KindNode)) - if err != nil { - return trace.Wrap(err) - } - - if len(nodes) == 0 { - return trace.NotFound("no matching SSH hosts found for search terms or query expression") - } - - if len(nodes) > 1 { - return trace.BadParameter("found multiple matching SSH hosts %v", nodes[:2]) - } + targetHost, targetPort, err := net.SplitHostPort(tc.Host) + if err != nil { + targetHost = tc.Host + targetPort = strconv.Itoa(tc.HostPort) + } + targetHost = cleanTargetHost(targetHost, tc.WebProxyHost(), clt.ClusterName()) + tc.Host = targetHost + port, err := strconv.Atoi(targetPort) + if err != nil { + return trace.Wrap(err) + } + tc.HostPort = port - // Dialing is happening by UUID but a port is still required by - // the Proxy dial request. Zero is an indicator to the Proxy that - // it may chose the appropriate port based on the target server. - target = fmt.Sprintf("%s:0", nodes[0].GetName()) - default: - return trace.BadParameter("no hostname, search terms or query expression provided") + target, err := tc.GetTargetNode(cf.Context, clt.AuthClient, nil) + if err != nil { + return trace.Wrap(err) } - conn, _, err := clt.DialHostWithResumption(cf.Context, target, clt.ClusterName(), tc.LocalAgent().ExtendedAgent) + conn, _, err := clt.DialHostWithResumption(cf.Context, target.Addr, clt.ClusterName(), tc.LocalAgent().ExtendedAgent) if err != nil { return trace.Wrap(err) } @@ -233,7 +218,7 @@ func onProxyCommandDB(cf *CLIConf) error { opts := []dbcmd.ConnectCommandFunc{ dbcmd.WithLocalProxy("localhost", addr.Port(0), ""), dbcmd.WithNoTLS(), - dbcmd.WithLogger(log), + dbcmd.WithLogger(logger), dbcmd.WithPrintFormat(), dbcmd.WithTolerateMissingCLIClient(), dbcmd.WithGetDatabaseFunc(dbInfo.getDatabaseForDBCmd), diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index 582cce0fcd08d..454f37fe52168 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -3722,19 +3722,15 @@ func onSSHLatency(cf *CLIConf) error { } defer clt.Close() - // detect the common error when users use host:port address format - _, port, err := net.SplitHostPort(tc.Host) - // client has used host:port notation - if err == nil { - return trace.BadParameter("please use ssh subcommand with '--port=%v' flag instead of semicolon", port) + target, err := tc.GetTargetNode(cf.Context, clt.AuthClient, nil) + if err != nil { + return trace.Wrap(err) } - addr := net.JoinHostPort(tc.Host, strconv.Itoa(tc.HostPort)) - nodeClient, err := tc.ConnectToNode( cf.Context, clt, - client.NodeDetails{Addr: addr, Namespace: tc.Namespace, Cluster: tc.SiteName}, + client.NodeDetails{Addr: target.Addr, Namespace: tc.Namespace, Cluster: tc.SiteName}, tc.Config.HostLogin, ) if err != nil { @@ -3920,7 +3916,9 @@ func onSSH(cf *CLIConf) error { err = client.RetryWithRelogin(cf.Context, tc, sshFunc) } if err != nil { - if strings.Contains(utils.UserMessageFromError(err), teleport.NodeIsAmbiguous) { + if errors.Is(err, teleport.ErrNodeIsAmbiguous) || + // TODO(tross) DELETE IN v20.0.0 + strings.Contains(utils.UserMessageFromError(err), teleport.NodeIsAmbiguous) { clt, err := tc.ConnectToCluster(cf.Context) if err != nil { return trace.Wrap(err) diff --git a/tool/tsh/common/tsh_helper_test.go b/tool/tsh/common/tsh_helper_test.go index 380b012805d3c..85ec86097b3bd 100644 --- a/tool/tsh/common/tsh_helper_test.go +++ b/tool/tsh/common/tsh_helper_test.go @@ -230,7 +230,7 @@ func (s *suite) setupLeafCluster(t *testing.T, options testSuiteOptions) { tunnelAddr = s.root.Config.Proxy.ReverseTunnelListenAddr.String() } - tc, err := types.NewTrustedCluster("root-cluster", types.TrustedClusterSpecV2{ + tc, err := types.NewTrustedCluster(s.root.Config.Auth.ClusterName.GetClusterName(), types.TrustedClusterSpecV2{ Enabled: true, Token: staticToken, ProxyAddress: s.root.Config.Proxy.WebAddr.String(), @@ -249,7 +249,7 @@ func (s *suite) setupLeafCluster(t *testing.T, options testSuiteOptions) { } s.leaf = runTeleport(t, cfg) - _, err = s.leaf.GetAuthServer().UpsertTrustedCluster(s.leaf.ExitContext(), tc) + _, err = s.leaf.GetAuthServer().UpsertTrustedClusterV2(s.leaf.ExitContext(), tc) require.NoError(t, err) } diff --git a/tool/tsh/common/tsh_test.go b/tool/tsh/common/tsh_test.go index e19a3b945517d..9302a62ce29ae 100644 --- a/tool/tsh/common/tsh_test.go +++ b/tool/tsh/common/tsh_test.go @@ -2543,7 +2543,7 @@ func tryCreateTrustedCluster(t *testing.T, authServer *auth.Server, trustedClust ctx := context.TODO() for i := 0; i < 10; i++ { log.Debugf("Will create trusted cluster %v, attempt %v.", trustedCluster, i) - _, err := authServer.UpsertTrustedCluster(ctx, trustedCluster) + _, err := authServer.UpsertTrustedClusterV2(ctx, trustedCluster) if err == nil { return } @@ -7028,8 +7028,7 @@ func TestSCP(t *testing.T) { return filepath.Join(dir, targetFile1) }, assertion: func(tt require.TestingT, err error, i ...any) { - require.Error(tt, err, i...) - require.ErrorContains(tt, err, "multiple matching hosts", i...) + require.ErrorIs(tt, err, teleport.ErrNodeIsAmbiguous, i...) }, }, { @@ -7088,8 +7087,7 @@ func TestSCP(t *testing.T) { return "dev.example.com:" + filepath.Join(dir, targetFile1) }, assertion: func(tt require.TestingT, err error, i ...any) { - require.Error(tt, err, i...) - require.ErrorContains(tt, err, "multiple matching hosts", i...) + require.ErrorIs(tt, err, teleport.ErrNodeIsAmbiguous, i...) }, }, { diff --git a/web/packages/teleport/src/Account/ManageDevices/wizards/AddAuthDeviceWizard.test.tsx b/web/packages/teleport/src/Account/ManageDevices/wizards/AddAuthDeviceWizard.test.tsx index 96a2fc05a404f..b4fb5a0303fe2 100644 --- a/web/packages/teleport/src/Account/ManageDevices/wizards/AddAuthDeviceWizard.test.tsx +++ b/web/packages/teleport/src/Account/ManageDevices/wizards/AddAuthDeviceWizard.test.tsx @@ -23,7 +23,7 @@ import { userEvent, UserEvent } from '@testing-library/user-event'; import { ContextProvider } from 'teleport'; import auth from 'teleport/services/auth'; -import MfaService from 'teleport/services/mfa'; +import MfaService, { SsoChallenge } from 'teleport/services/mfa'; import TeleportContext from 'teleport/teleportContext'; import { AddAuthDeviceWizardStepProps } from './AddAuthDeviceWizard'; @@ -170,11 +170,16 @@ describe('flow without reauthentication', () => { }); describe('flow with reauthentication', () => { + const dummyMfaChallenge = { + totpChallenge: true, + webauthnPublicKey: {} as PublicKeyCredentialRequestOptions, + ssoChallenge: {} as SsoChallenge, + }; + beforeEach(() => { - jest.spyOn(auth, 'getMfaChallenge').mockResolvedValueOnce({ - totpChallenge: true, - webauthnPublicKey: {} as PublicKeyCredentialRequestOptions, - }); + jest + .spyOn(auth, 'getMfaChallenge') + .mockResolvedValueOnce(dummyMfaChallenge); jest.spyOn(auth, 'getMfaChallengeResponse').mockResolvedValueOnce({}); jest .spyOn(auth, 'createPrivilegeToken') @@ -194,6 +199,11 @@ describe('flow with reauthentication', () => { expect(screen.getByTestId('create-step')).toBeInTheDocument(); }); await user.click(screen.getByRole('button', { name: 'Create a passkey' })); + expect(auth.getMfaChallengeResponse).toHaveBeenCalledWith( + dummyMfaChallenge, + 'webauthn', + '' + ); expect(auth.createNewWebAuthnDevice).toHaveBeenCalledWith({ tokenId: 'privilege-token', deviceUsage: 'passwordless', @@ -228,6 +238,46 @@ describe('flow with reauthentication', () => { expect(screen.getByTestId('create-step')).toBeInTheDocument(); }); await user.click(screen.getByRole('button', { name: 'Create a passkey' })); + expect(auth.getMfaChallengeResponse).toHaveBeenCalledWith( + dummyMfaChallenge, + 'totp', + '654987' + ); + expect(auth.createNewWebAuthnDevice).toHaveBeenCalledWith({ + tokenId: 'privilege-token', + deviceUsage: 'passwordless', + }); + + expect(screen.getByTestId('save-step')).toBeInTheDocument(); + await user.type(screen.getByLabelText('Passkey Nickname'), 'new-passkey'); + await user.click(screen.getByRole('button', { name: 'Save the Passkey' })); + expect(ctx.mfaService.saveNewWebAuthnDevice).toHaveBeenCalledWith({ + credential: dummyCredential, + addRequest: { + deviceName: 'new-passkey', + deviceUsage: 'passwordless', + tokenId: 'privilege-token', + }, + }); + expect(onSuccess).toHaveBeenCalled(); + }); + + test('adds a passkey with SSO reauthentication', async () => { + render(); + + await waitFor(() => { + expect(screen.getByTestId('reauthenticate-step')).toBeInTheDocument(); + }); + await user.click(screen.getByText('SSO')); + await user.click(screen.getByText('Verify my identity')); + + expect(screen.getByTestId('create-step')).toBeInTheDocument(); + await user.click(screen.getByRole('button', { name: 'Create a passkey' })); + expect(auth.getMfaChallengeResponse).toHaveBeenCalledWith( + dummyMfaChallenge, + 'sso', + '' + ); expect(auth.createNewWebAuthnDevice).toHaveBeenCalledWith({ tokenId: 'privilege-token', deviceUsage: 'passwordless', diff --git a/web/packages/teleport/src/Account/ManageDevices/wizards/DeleteAuthDeviceWizard.test.tsx b/web/packages/teleport/src/Account/ManageDevices/wizards/DeleteAuthDeviceWizard.test.tsx index dd780c4f3996f..c4e77e1365df7 100644 --- a/web/packages/teleport/src/Account/ManageDevices/wizards/DeleteAuthDeviceWizard.test.tsx +++ b/web/packages/teleport/src/Account/ManageDevices/wizards/DeleteAuthDeviceWizard.test.tsx @@ -23,7 +23,7 @@ import { userEvent, UserEvent } from '@testing-library/user-event'; import TeleportContext from 'teleport/teleportContext'; import { ContextProvider } from 'teleport'; -import MfaService from 'teleport/services/mfa'; +import MfaService, { SsoChallenge } from 'teleport/services/mfa'; import auth from 'teleport/services/auth'; import { DeleteAuthDeviceWizardStepProps } from './DeleteAuthDeviceWizard'; @@ -36,15 +36,18 @@ let ctx: TeleportContext; let user: UserEvent; let onSuccess: jest.Mock; +const dummyMfaChallenge = { + totpChallenge: true, + webauthnPublicKey: {} as PublicKeyCredentialRequestOptions, + ssoChallenge: {} as SsoChallenge, +}; + beforeEach(() => { ctx = new TeleportContext(); user = userEvent.setup(); onSuccess = jest.fn(); - jest.spyOn(auth, 'getMfaChallenge').mockResolvedValueOnce({ - totpChallenge: true, - webauthnPublicKey: {} as PublicKeyCredentialRequestOptions, - }); + jest.spyOn(auth, 'getMfaChallenge').mockResolvedValueOnce(dummyMfaChallenge); jest.spyOn(auth, 'getMfaChallengeResponse').mockResolvedValueOnce({}); jest .spyOn(auth, 'createPrivilegeToken') @@ -80,6 +83,11 @@ test('deletes a device with WebAuthn reauthentication', async () => { expect(screen.getByTestId('delete-step')).toBeInTheDocument(); await user.click(screen.getByRole('button', { name: 'Delete' })); + expect(auth.getMfaChallengeResponse).toHaveBeenCalledWith( + dummyMfaChallenge, + 'webauthn', + '' + ); expect(ctx.mfaService.removeDevice).toHaveBeenCalledWith( 'privilege-token', 'TouchID' @@ -100,6 +108,34 @@ test('deletes a device with OTP reauthentication', async () => { expect(screen.getByTestId('delete-step')).toBeInTheDocument(); await user.click(screen.getByRole('button', { name: 'Delete' })); + expect(auth.getMfaChallengeResponse).toHaveBeenCalledWith( + dummyMfaChallenge, + 'totp', + '654987' + ); + expect(ctx.mfaService.removeDevice).toHaveBeenCalledWith( + 'privilege-token', + 'TouchID' + ); +}); + +test('deletes a device with SSO reauthentication', async () => { + render(); + + await waitFor(() => { + expect(screen.getByTestId('reauthenticate-step')).toBeInTheDocument(); + }); + await user.click(screen.getByText('SSO')); + await user.click(screen.getByText('Verify my identity')); + + expect(screen.getByTestId('delete-step')).toBeInTheDocument(); + await user.click(screen.getByRole('button', { name: 'Delete' })); + + expect(auth.getMfaChallengeResponse).toHaveBeenCalledWith( + dummyMfaChallenge, + 'sso', + '' + ); expect(ctx.mfaService.removeDevice).toHaveBeenCalledWith( 'privilege-token', 'TouchID' diff --git a/web/packages/teleport/src/AppLauncher/AppLauncher.test.tsx b/web/packages/teleport/src/AppLauncher/AppLauncher.test.tsx index ae561d4950532..6b9e7fdf3400b 100644 --- a/web/packages/teleport/src/AppLauncher/AppLauncher.test.tsx +++ b/web/packages/teleport/src/AppLauncher/AppLauncher.test.tsx @@ -16,13 +16,13 @@ * along with this program. If not, see . */ -import { render, waitFor, screen } from 'design/utils/testing'; +import { render, screen, waitFor } from 'design/utils/testing'; import { createMemoryHistory } from 'history'; import { Router } from 'react-router'; import { Route } from 'teleport/components/Router'; -import api from 'teleport/services/api'; import cfg from 'teleport/config'; +import api from 'teleport/services/api'; import service from 'teleport/services/apps'; import { AppLauncher } from './AppLauncher'; diff --git a/web/packages/teleport/src/AppLauncher/AppLauncher.tsx b/web/packages/teleport/src/AppLauncher/AppLauncher.tsx index 97d3559bb6365..78db3d6733f2d 100644 --- a/web/packages/teleport/src/AppLauncher/AppLauncher.tsx +++ b/web/packages/teleport/src/AppLauncher/AppLauncher.tsx @@ -26,8 +26,11 @@ import { AccessDenied } from 'design/CardError'; import useAttempt from 'shared/hooks/useAttemptNext'; -import { UrlLauncherParams } from 'teleport/config'; +import AuthnDialog from 'teleport/components/AuthnDialog'; +import { CreateAppSessionParams, UrlLauncherParams } from 'teleport/config'; +import { useMfa } from 'teleport/lib/useMfa'; import service from 'teleport/services/apps'; +import { MfaChallengeScope } from 'teleport/services/auth/auth'; export function AppLauncher() { const { attempt, setAttempt } = useAttempt('processing'); @@ -37,6 +40,19 @@ export function AppLauncher() { const queryParams = new URLSearchParams(search); const isRedirectFlow = queryParams.get('required-apps'); + const mfa = useMfa({ + req: { + scope: MfaChallengeScope.USER_SESSION, + isMfaRequiredRequest: { + app: { + fqdn: pathParams.fqdn, + cluster_name: pathParams.clusterId, + public_addr: pathParams.publicAddr, + }, + }, + }, + }); + const createAppSession = useCallback(async (params: UrlLauncherParams) => { let fqdn = params.fqdn; const port = location.port ? `:${location.port}` : ''; @@ -101,7 +117,10 @@ export function AppLauncher() { if (params.arn) { params.arn = decodeURIComponent(params.arn); } - const session = await service.createAppSession(params); + + const createAppSessionParams = params as CreateAppSessionParams; + createAppSessionParams.mfaResponse = await mfa.getChallengeResponse(); + const session = await service.createAppSession(createAppSessionParams); // Set all the fields expected by server to validate request. const url = getXTeleportAuthUrl({ fqdn, port }); @@ -142,11 +161,16 @@ export function AppLauncher() { createAppSession(pathParams); }, [pathParams]); - if (attempt.status === 'failed') { - return ; - } - - return ; + return ( +
+ {attempt.status === 'failed' ? ( + + ) : ( + + )} + +
+ ); } export function AppLauncherProcessing() { diff --git a/web/packages/teleport/src/Console/DocumentDb/DocumentDb.tsx b/web/packages/teleport/src/Console/DocumentDb/DocumentDb.tsx index 0d6d333141b2a..6c024edfe7331 100644 --- a/web/packages/teleport/src/Console/DocumentDb/DocumentDb.tsx +++ b/web/packages/teleport/src/Console/DocumentDb/DocumentDb.tsx @@ -15,20 +15,20 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { useRef, useEffect } from 'react'; +import { useEffect, useRef } from 'react'; -import { useTheme } from 'styled-components'; import { Box, Indicator } from 'design'; +import { useTheme } from 'styled-components'; -import * as stores from 'teleport/Console/stores/types'; import { Terminal, TerminalRef } from 'teleport/Console/DocumentSsh/Terminal'; -import { useMfa } from 'teleport/lib/useMfa'; +import * as stores from 'teleport/Console/stores/types'; +import { useMfaTty } from 'teleport/lib/useMfa'; import Document from 'teleport/Console/Document'; import AuthnDialog from 'teleport/components/AuthnDialog'; -import { useDbSession } from './useDbSession'; import { ConnectDialog } from './ConnectDialog'; +import { useDbSession } from './useDbSession'; type Props = { visible: boolean; @@ -38,11 +38,11 @@ type Props = { export function DocumentDb({ doc, visible }: Props) { const terminalRef = useRef(); const { tty, status, closeDocument, sendDbConnectData } = useDbSession(doc); - const mfa = useMfa(tty); + const mfa = useMfaTty(tty); useEffect(() => { // when switching tabs or closing tabs, focus on visible terminal terminalRef.current?.focus(); - }, [visible, mfa.requested, status]); + }, [visible, mfa, status]); const theme = useTheme(); return ( @@ -52,7 +52,7 @@ export function DocumentDb({ doc, visible }: Props) { )} - {mfa.requested && } + {status === 'waiting' && ( (); const { tty, status, closeDocument, sendKubeExecData } = useKubeExecSession(doc); - const mfa = useMfa(tty); + const mfa = useMfaTty(tty); useEffect(() => { // when switching tabs or closing tabs, focus on visible terminal terminalRef.current?.focus(); - }, [visible, mfa.requested]); + }, [visible, mfa.challenge]); const theme = useTheme(); const terminal = ( @@ -63,7 +63,7 @@ export default function DocumentKubeExec({ doc, visible }: Props) { )} - {mfa.requested && } + {status === 'waiting-for-exec-data' && ( diff --git a/web/packages/teleport/src/Console/DocumentSsh/DocumentSsh.tsx b/web/packages/teleport/src/Console/DocumentSsh/DocumentSsh.tsx index c378216dd66fb..4902d90845bf1 100644 --- a/web/packages/teleport/src/Console/DocumentSsh/DocumentSsh.tsx +++ b/web/packages/teleport/src/Console/DocumentSsh/DocumentSsh.tsx @@ -16,31 +16,32 @@ * along with this program. If not, see . */ -import { useRef, useEffect, useState, useCallback } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { useTheme } from 'styled-components'; -import { Indicator, Box } from 'design'; +import { Box, Indicator } from 'design'; import { - FileTransferActionBar, FileTransfer, - FileTransferRequests, + FileTransferActionBar, FileTransferContextProvider, + FileTransferRequests, } from 'shared/components/FileTransfer'; import { TerminalSearch } from 'shared/components/TerminalSearch'; import * as stores from 'teleport/Console/stores'; import AuthnDialog from 'teleport/components/AuthnDialog'; -import { useMfa } from 'teleport/lib/useMfa'; +import { useMfa, useMfaTty } from 'teleport/lib/useMfa'; +import { MfaChallengeScope } from 'teleport/services/auth/auth'; import Document from '../Document'; import { useConsoleContext } from '../consoleContextProvider'; import { Terminal, TerminalRef } from './Terminal'; -import useSshSession from './useSshSession'; import { useFileTransfer } from './useFileTransfer'; +import useSshSession from './useSshSession'; export default function DocumentSshWrapper(props: PropTypes) { return ( @@ -56,13 +57,15 @@ function DocumentSsh({ doc, visible }: PropTypes) { const terminalRef = useRef(); const { tty, status, closeDocument, session } = useSshSession(doc); const [showSearch, setShowSearch] = useState(false); - const mfa = useMfa(tty); - const { - getMfaResponseAttempt, - getDownloader, - getUploader, - fileTransferRequests, - } = useFileTransfer(tty, session, doc, mfa.addMfaToScpUrls); + + const ttyMfa = useMfaTty(tty); + const ftMfa = useMfa({ + isMfaRequired: ttyMfa.required, + req: { + scope: MfaChallengeScope.USER_SESSION, + }, + }); + const ft = useFileTransfer(tty, session, doc, ftMfa); const theme = useTheme(); function handleCloseFileTransfer() { @@ -75,8 +78,13 @@ function DocumentSsh({ doc, visible }: PropTypes) { useEffect(() => { // when switching tabs or closing tabs, focus on visible terminal - terminalRef.current?.focus(); - }, [visible, mfa.requested]); + if ( + ttyMfa.attempt.status === 'processing' || + ftMfa.attempt.status === 'processing' + ) { + terminalRef.current?.focus(); + } + }, [visible, ttyMfa.attempt.status, ftMfa.attempt.status]); const onSearchClose = useCallback(() => { setShowSearch(false); @@ -110,21 +118,15 @@ function DocumentSsh({ doc, visible }: PropTypes) { } beforeClose={() => window.confirm('Are you sure you want to cancel file transfers?') } - errorText={ - getMfaResponseAttempt.status === 'failed' - ? getMfaResponseAttempt.statusText - : null - } afterClose={handleCloseFileTransfer} transferHandlers={{ - getDownloader, - getUploader, + ...ft, }} /> @@ -143,7 +145,8 @@ function DocumentSsh({ doc, visible }: PropTypes) { )} - {mfa.requested && } + + {status === 'initialized' && terminal} ); diff --git a/web/packages/teleport/src/Console/DocumentSsh/useFileTransfer.ts b/web/packages/teleport/src/Console/DocumentSsh/useFileTransfer.ts index 90c3625a902cf..92c4c9976a198 100644 --- a/web/packages/teleport/src/Console/DocumentSsh/useFileTransfer.ts +++ b/web/packages/teleport/src/Console/DocumentSsh/useFileTransfer.ts @@ -16,18 +16,21 @@ * along with this program. If not, see . */ -import { useEffect, useState, useCallback } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useFileTransferContext } from 'shared/components/FileTransfer'; -import Tty from 'teleport/lib/term/tty'; +import { DocumentSsh } from 'teleport/Console/stores'; import { EventType } from 'teleport/lib/term/enums'; +import Tty from 'teleport/lib/term/tty'; import { Session } from 'teleport/services/session'; -import { DocumentSsh } from 'teleport/Console/stores'; + +import cfg from 'teleport/config'; + +import { MfaState } from 'teleport/lib/useMfa'; import { useConsoleContext } from '../consoleContextProvider'; import { getHttpFileTransferHandlers } from './httpFileTransferHandlers'; -import useGetScpUrl from './useGetScpUrl'; export type FileTransferRequest = { sid: string; @@ -51,7 +54,7 @@ export const useFileTransfer = ( tty: Tty, session: Session, currentDoc: DocumentSsh, - addMfaToScpUrls: boolean + mfa: MfaState ) => { const { filesStore } = useFileTransferContext(); const startTransfer = filesStore.start; @@ -60,8 +63,6 @@ export const useFileTransfer = ( const [fileTransferRequests, setFileTransferRequests] = useState< FileTransferRequest[] >([]); - const { getScpUrl, attempt: getMfaResponseAttempt } = - useGetScpUrl(addMfaToScpUrls); const { clusterId, serverId, login } = currentDoc; const download = useCallback( @@ -70,7 +71,8 @@ export const useFileTransfer = ( abortController: AbortController, moderatedSessionParams?: ModeratedSessionParams ) => { - const url = await getScpUrl({ + const mfaResponse = await mfa.getChallengeResponse(); + const url = cfg.getScpUrl({ location, clusterId, serverId, @@ -78,7 +80,9 @@ export const useFileTransfer = ( filename: location, moderatedSessionId: moderatedSessionParams?.moderatedSessionId, fileTransferRequestId: moderatedSessionParams?.fileRequestId, + mfaResponse, }); + if (!url) { // if we return nothing here, the file transfer will not be added to the // file transfer list. If we add it to the list, the file will continue to @@ -88,7 +92,7 @@ export const useFileTransfer = ( } return getHttpFileTransferHandlers().download(url, abortController); }, - [clusterId, login, serverId, getScpUrl] + [clusterId, login, serverId, mfa] ); const upload = useCallback( @@ -98,7 +102,9 @@ export const useFileTransfer = ( abortController: AbortController, moderatedSessionParams?: ModeratedSessionParams ) => { - const url = await getScpUrl({ + const mfaResponse = await mfa.getChallengeResponse(); + + const url = cfg.getScpUrl({ location, clusterId, serverId, @@ -106,6 +112,7 @@ export const useFileTransfer = ( filename: file.name, moderatedSessionId: moderatedSessionParams?.moderatedSessionId, fileTransferRequestId: moderatedSessionParams?.fileRequestId, + mfaResponse, }); if (!url) { // if we return nothing here, the file transfer will not be added to the @@ -116,7 +123,7 @@ export const useFileTransfer = ( } return getHttpFileTransferHandlers().upload(url, file, abortController); }, - [clusterId, serverId, login, getScpUrl] + [clusterId, serverId, login, mfa] ); /* @@ -256,7 +263,6 @@ export const useFileTransfer = ( return { fileTransferRequests, - getMfaResponseAttempt, getUploader, getDownloader, }; diff --git a/web/packages/teleport/src/Console/DocumentSsh/useGetScpUrl.ts b/web/packages/teleport/src/Console/DocumentSsh/useGetScpUrl.ts deleted file mode 100644 index 478ccbcc5fa59..0000000000000 --- a/web/packages/teleport/src/Console/DocumentSsh/useGetScpUrl.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Teleport - * Copyright (C) 2023 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import { useCallback } from 'react'; -import useAttempt from 'shared/hooks/useAttemptNext'; - -import cfg, { UrlScpParams } from 'teleport/config'; -import auth, { MfaChallengeScope } from 'teleport/services/auth/auth'; - -export default function useGetScpUrl(addMfaToScpUrls: boolean) { - const { setAttempt, attempt, handleError } = useAttempt(''); - - const getScpUrl = useCallback( - async (params: UrlScpParams) => { - setAttempt({ - status: 'processing', - statusText: '', - }); - if (!addMfaToScpUrls) { - return cfg.getScpUrl(params); - } - try { - const challenge = await auth.getMfaChallenge({ - scope: MfaChallengeScope.USER_SESSION, - }); - - const response = await auth.getMfaChallengeResponse( - challenge, - 'webauthn' - ); - - setAttempt({ - status: 'success', - statusText: '', - }); - return cfg.getScpUrl({ - webauthn: response.webauthn_response, - ...params, - }); - } catch (error) { - handleError(error); - } - }, - [addMfaToScpUrls, handleError, setAttempt] - ); - - return { - getScpUrl, - attempt, - }; -} diff --git a/web/packages/teleport/src/DesktopSession/DesktopSession.story.tsx b/web/packages/teleport/src/DesktopSession/DesktopSession.story.tsx index 97606b1ea3b86..e401ab43de9f1 100644 --- a/web/packages/teleport/src/DesktopSession/DesktopSession.story.tsx +++ b/web/packages/teleport/src/DesktopSession/DesktopSession.story.tsx @@ -16,16 +16,16 @@ * along with this program. If not, see . */ -import { useState } from 'react'; import { ButtonPrimary } from 'design/Button'; +import { useState } from 'react'; import { NotificationItem } from 'shared/components/Notification'; import { throttle } from 'shared/utils/highbar'; import { TdpClient, TdpClientEvent } from 'teleport/lib/tdp'; import { makeDefaultMfaState } from 'teleport/lib/useMfa'; -import { State } from './useDesktopSession'; import { DesktopSession } from './DesktopSession'; +import { State } from './useDesktopSession'; export default { title: 'Teleport/DesktopSession', @@ -261,14 +261,17 @@ export const WebAuthnPrompt = () => ( }} wsConnection={{ status: 'open' }} mfa={{ - errorText: '', - requested: true, - setErrorText: () => null, - addMfaToScpUrls: false, - onWebauthnAuthenticate: () => null, - onSsoAuthenticate: () => null, - webauthnPublicKey: null, - ssoChallenge: null, + ...makeDefaultMfaState(), + attempt: { + status: 'processing', + statusText: '', + data: null, + }, + challenge: { + webauthnPublicKey: { + challenge: new ArrayBuffer(1), + }, + }, }} /> ); diff --git a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx index f5105f7d0246e..851c72b769fe4 100644 --- a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx +++ b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx @@ -184,12 +184,10 @@ export function DesktopSession(props: State) { const MfaDialog = ({ mfa }: { mfa: MfaState }) => { return ( { - mfa.setErrorText( - 'This session requires multi factor authentication to continue. Please hit "Retry" and follow the prompts given by your browser to complete authentication.' - ); - }} + mfaState={mfa} + replaceErrorText={ + 'This session requires multi factor authentication to continue. Please hit try again and follow the prompts given by your browser to complete authentication.' + } /> ); }; @@ -294,7 +292,7 @@ const nextScreenState = ( // Otherwise, calculate a new screen state. const showAnotherSessionActive = showAnotherSessionActiveDialog; - const showMfa = webauthn.requested; + const showMfa = webauthn.challenge; const showAlert = fetchAttempt.status === 'failed' || // Fetch attempt failed tdpConnection.status === 'failed' || // TDP connection failed diff --git a/web/packages/teleport/src/DesktopSession/useDesktopSession.tsx b/web/packages/teleport/src/DesktopSession/useDesktopSession.tsx index 1f642d38d8d96..f14482669f471 100644 --- a/web/packages/teleport/src/DesktopSession/useDesktopSession.tsx +++ b/web/packages/teleport/src/DesktopSession/useDesktopSession.tsx @@ -22,7 +22,7 @@ import { useParams } from 'react-router'; import useAttempt from 'shared/hooks/useAttemptNext'; import { ButtonState } from 'teleport/lib/tdp'; -import { useMfa } from 'teleport/lib/useMfa'; +import { useMfaTty } from 'teleport/lib/useMfa'; import desktopService from 'teleport/services/desktops'; import userService from 'teleport/services/user'; @@ -130,7 +130,7 @@ export default function useDesktopSession() { }); const tdpClient = clientCanvasProps.tdpClient; - const mfa = useMfa(tdpClient); + const mfa = useMfaTty(tdpClient); const onShareDirectory = () => { try { diff --git a/web/packages/teleport/src/Login/Login.test.tsx b/web/packages/teleport/src/Login/Login.test.tsx index f993acf319533..ce51ae5cdf160 100644 --- a/web/packages/teleport/src/Login/Login.test.tsx +++ b/web/packages/teleport/src/Login/Login.test.tsx @@ -22,6 +22,7 @@ import { render, fireEvent, screen, waitFor } from 'design/utils/testing'; import auth from 'teleport/services/auth/auth'; import history from 'teleport/services/history'; +import session from 'teleport/services/websession'; import cfg from 'teleport/config'; import { Login } from './Login'; @@ -31,6 +32,7 @@ let user: UserEvent; beforeEach(() => { jest.restoreAllMocks(); jest.spyOn(history, 'push').mockImplementation(); + jest.spyOn(history, 'replace').mockImplementation(); jest.spyOn(history, 'getRedirectParam').mockImplementation(() => '/'); jest.spyOn(history, 'hasAccessChangedParam').mockImplementation(() => false); user = userEvent.setup(); @@ -194,3 +196,25 @@ describe('test MOTD', () => { expect(screen.getByText(/Your access has changed/i)).toBeInTheDocument(); }); }); + +test('redirect to root if session is valid and path is not "/enterprise/saml-idp/sso"', () => { + jest.spyOn(session, 'isValid').mockImplementation(() => true); + jest + .spyOn(history, 'getRedirectParam') + .mockReturnValue( + 'http://localhost/web/login?redirect_url=http://localhost/web/cluster/localhost/resources' + ); + render(); + + expect(history.replace).toHaveBeenCalledWith('/web'); +}); + +test('redirect if session is valid and path matches "/enterprise/saml-idp/sso"', () => { + const samlIdPPath = new URL('http://localhost' + cfg.routes.samlIdpSso); + jest.spyOn(session, 'isValid').mockImplementation(() => true); + jest + .spyOn(history, 'getRedirectParam') + .mockReturnValue(samlIdPPath.toString()); + render(); + expect(history.push).toHaveBeenCalledWith(samlIdPPath, true); +}); diff --git a/web/packages/teleport/src/Login/useLogin.test.tsx b/web/packages/teleport/src/Login/useLogin.test.tsx new file mode 100644 index 0000000000000..9d2b461fe8ffe --- /dev/null +++ b/web/packages/teleport/src/Login/useLogin.test.tsx @@ -0,0 +1,100 @@ +/** + * 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 history from 'teleport/services/history'; +import session from 'teleport/services/websession'; +import cfg from 'teleport/config'; + +import { renderHook } from '@testing-library/react'; + +import useLogin from './useLogin'; + +beforeEach(() => { + jest.restoreAllMocks(); + jest.spyOn(session, 'isValid').mockImplementation(() => true); + jest.spyOn(history, 'push').mockImplementation(); + jest.spyOn(history, 'replace').mockImplementation(); + jest.mock('shared/hooks', () => ({ + useAttempt: () => { + return [ + { status: 'success', statusText: 'Success Text' }, + { + clear: jest.fn(), + }, + ]; + }, + })); +}); + +afterEach(() => { + jest.resetAllMocks(); +}); + +it('redirect to root on path not matching "/enterprise/saml-idp/sso"', () => { + jest.spyOn(history, 'getRedirectParam').mockReturnValue('http://localhost'); + renderHook(() => useLogin()); + expect(history.replace).toHaveBeenCalledWith('/web'); + + jest + .spyOn(history, 'getRedirectParam') + .mockReturnValue('http://localhost/web/cluster/name/resources'); + renderHook(() => useLogin()); + expect(history.replace).toHaveBeenCalledWith('/web'); +}); + +it('redirect to SAML SSO path on matching "/enterprise/saml-idp/sso"', () => { + const samlIdpPath = new URL('http://localhost' + cfg.routes.samlIdpSso); + cfg.baseUrl = 'http://localhost'; + jest + .spyOn(history, 'getRedirectParam') + .mockReturnValue(samlIdpPath.toString()); + renderHook(() => useLogin()); + expect(history.push).toHaveBeenCalledWith(samlIdpPath, true); +}); + +it('non-base domain redirects with base domain for a matching "/enterprise/saml-idp/sso"', async () => { + const samlIdpPath = new URL('http://different-base' + cfg.routes.samlIdpSso); + jest + .spyOn(history, 'getRedirectParam') + .mockReturnValue(samlIdpPath.toString()); + renderHook(() => useLogin()); + const expectedPath = new URL('http://localhost' + cfg.routes.samlIdpSso); + expect(history.push).toHaveBeenCalledWith(expectedPath, true); +}); + +it('base domain with different path is redirected to root', async () => { + const nonSamlIdpPath = new URL('http://localhost/web/cluster/name/resources'); + jest + .spyOn(history, 'getRedirectParam') + .mockReturnValue(nonSamlIdpPath.toString()); + renderHook(() => useLogin()); + expect(history.replace).toHaveBeenCalledWith('/web'); +}); + +it('invalid session does nothing', async () => { + const samlIdpPathWithDifferentBase = new URL( + 'http://different-base' + cfg.routes.samlIdpSso + ); + jest + .spyOn(history, 'getRedirectParam') + .mockReturnValue(samlIdpPathWithDifferentBase.toString()); + jest.spyOn(session, 'isValid').mockImplementation(() => false); + renderHook(() => useLogin()); + 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 8a36d5aba3e8e..aec71d83fe94c 100644 --- a/web/packages/teleport/src/Login/useLogin.ts +++ b/web/packages/teleport/src/Login/useLogin.ts @@ -17,6 +17,7 @@ */ import { useState, useEffect } from 'react'; +import { matchPath } from 'react-router'; import { useAttempt } from 'shared/hooks'; import { AuthProvider } from 'shared/services'; import { TrustedDeviceRequirement } from 'gen-proto-ts/teleport/legacy/types/trusted_device_requirement_pb'; @@ -69,8 +70,25 @@ export default function useLogin() { useEffect(() => { if (session.isValid()) { - history.replace(cfg.routes.root); - return; + try { + const redirectUrlWithBase = new URL(getEntryRoute()); + const matched = matchPath(redirectUrlWithBase.pathname, { + path: cfg.routes.samlIdpSso, + strict: true, + exact: true, + }); + if (matched) { + history.push(redirectUrlWithBase, true); + return; + } else { + history.replace(cfg.routes.root); + return; + } + } catch (e) { + console.error(e); + history.replace(cfg.routes.root); + return; + } } setCheckingValidSession(false); }, []); @@ -149,6 +167,11 @@ function loginSuccess() { 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) { diff --git a/web/packages/teleport/src/Player/Player.tsx b/web/packages/teleport/src/Player/Player.tsx index 190f8afb1b558..7d26a785d1830 100644 --- a/web/packages/teleport/src/Player/Player.tsx +++ b/web/packages/teleport/src/Player/Player.tsx @@ -16,20 +16,22 @@ * along with this program. If not, see . */ +import { useCallback, useEffect } from 'react'; import styled from 'styled-components'; -import { Flex, Box } from 'design'; - +import { Flex, Box, Indicator } from 'design'; import { Danger } from 'design/Alert'; -import { useParams, useLocation } from 'teleport/components/Router'; +import { makeSuccessAttempt, useAsync } from 'shared/hooks/useAsync'; +import { useParams, useLocation } from 'teleport/components/Router'; import session from 'teleport/services/websession'; import { UrlPlayerParams } from 'teleport/config'; import { getUrlParameter } from 'teleport/services/history'; - import { RecordingType } from 'teleport/services/recordings'; +import useTeleport from 'teleport/useTeleport'; + import ActionBar from './ActionBar'; import { DesktopPlayer } from './DesktopPlayer'; import SshPlayer from './SshPlayer'; @@ -38,19 +40,44 @@ import Tabs, { TabItem } from './PlayerTabs'; const validRecordingTypes = ['ssh', 'k8s', 'desktop', 'database']; export function Player() { + const ctx = useTeleport(); const { sid, clusterId } = useParams(); const { search } = useLocation(); + useEffect(() => { + document.title = `Play ${sid} • ${clusterId}`; + }, [sid, clusterId]); + const recordingType = getUrlParameter( 'recordingType', search ) as RecordingType; - const durationMs = Number(getUrlParameter('durationMs', search)); + + // In order to render the progress bar, we need to know the length of the session. + // All in-product links to the session player should include the session duration in the URL. + // Some users manually build the URL based on the session ID and don't specify the session duration. + // For those cases, we make a separate API call to get the duration. + const [fetchDurationAttempt, fetchDuration] = useAsync( + useCallback( + () => ctx.recordingsService.fetchRecordingDuration(clusterId, sid), + [ctx.recordingsService, clusterId, sid] + ) + ); const validRecordingType = validRecordingTypes.includes(recordingType); - const validDurationMs = Number.isInteger(durationMs) && durationMs > 0; + const durationMs = Number(getUrlParameter('durationMs', search)); + const shouldFetchSessionDuration = + validRecordingType && (!Number.isInteger(durationMs) || durationMs <= 0); + + useEffect(() => { + if (shouldFetchSessionDuration) { + fetchDuration(); + } + }, [fetchDuration, shouldFetchSessionDuration]); - document.title = `Play ${sid} • ${clusterId}`; + const combinedAttempt = shouldFetchSessionDuration + ? fetchDurationAttempt + : makeSuccessAttempt({ durationMs }); function onLogout() { session.logout(); @@ -69,13 +96,25 @@ export function Player() { ); } - if (!validDurationMs) { + if ( + combinedAttempt.status === '' || + combinedAttempt.status === 'processing' + ) { + return ( + + + + + + ); + } + if (combinedAttempt.status === 'error') { return ( - Invalid query parameter durationMs:{' '} - {getUrlParameter('durationMs', search)}, should be an integer. + Unable to determine the length of this session. The session + recording may be incomplete or corrupted. @@ -101,15 +140,20 @@ export function Player() { ) : ( - + )} ); } + const StyledPlayer = styled.div` display: flex; height: 100%; diff --git a/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.story.tsx b/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.story.tsx index 14efa8fc69588..9fc5ba22f78ce 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.story.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.story.tsx @@ -30,7 +30,7 @@ import { YamlSupportedResourceKind } from 'teleport/services/yaml/types'; import { Access } from 'teleport/services/user'; import useResources from 'teleport/components/useResources'; -import { withDefaults } from './withDefaults'; +import { withDefaults } from './StandardEditor/withDefaults'; import { RoleEditor } from './RoleEditor'; import { RoleEditorDialog } from './RoleEditorDialog'; diff --git a/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.test.tsx b/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.test.tsx index 0fa38219bd398..aeec6288e2431 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.test.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.test.tsx @@ -31,7 +31,7 @@ import { import { CaptureEvent, userEventService } from 'teleport/services/userEvent'; import { RoleEditor, RoleEditorProps } from './RoleEditor'; -import { defaultOptions, withDefaults } from './withDefaults'; +import { defaultOptions, withDefaults } from './StandardEditor/withDefaults'; // The Ace editor is very difficult to deal with in tests, especially that for // handling its state, we are using input event, which is asynchronous. Thus, diff --git a/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.tsx b/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.tsx index 729360ecb4254..2eeb3dc9cc9ad 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/RoleEditor.tsx @@ -32,11 +32,11 @@ import { newRole, StandardEditorModel, roleToRoleEditorModel as roleToRoleEditorModel, -} from './standardmodel'; +} from './StandardEditor/standardmodel'; import { YamlEditorModel } from './yamlmodel'; import { EditorTab } from './EditorTabs'; import { EditorHeader } from './EditorHeader'; -import { StandardEditor } from './StandardEditor'; +import { StandardEditor } from './StandardEditor/StandardEditor'; import { YamlEditor } from './YamlEditor'; export type RoleEditorProps = { diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor.tsx b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor.tsx deleted file mode 100644 index 49c5c5e30feb8..0000000000000 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor.tsx +++ /dev/null @@ -1,1279 +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 React, { useId, useState } from 'react'; -import { - Box, - ButtonIcon, - ButtonSecondary, - Flex, - H3, - H4, - Input, - LabelInput, - Mark, - Text, -} from 'design'; -import FieldInput from 'shared/components/FieldInput'; -import { useValidation } from 'shared/components/Validation'; -import { - precomputed, - ValidationResult, -} from 'shared/components/Validation/rules'; -import * as Icon from 'design/Icon'; -import { HoverTooltip, IconTooltip } from 'design/Tooltip'; -import styled, { useTheme } from 'styled-components'; -import { MenuButton, MenuItem } from 'shared/components/MenuAction'; -import { - FieldSelect, - FieldSelectCreatable, -} from 'shared/components/FieldSelect'; -import { SlideTabs } from 'design/SlideTabs'; -import { RadioGroup } from 'design/RadioGroup'; -import Select from 'shared/components/Select'; - -import { components, MultiValueProps } from 'react-select'; - -import { Role, RoleWithYaml } from 'teleport/services/resources'; -import { LabelsInput } from 'teleport/components/LabelsInput'; - -import { FieldMultiInput } from '../../../../shared/components/FieldMultiInput/FieldMultiInput'; - -import { - roleEditorModelToRole, - hasModifiedFields, - MetadataModel, - RoleEditorModel, - StandardEditorModel, - AccessSpecKind, - AccessSpec, - ServerAccessSpec, - newAccessSpec, - KubernetesAccessSpec, - newKubernetesResourceModel, - kubernetesResourceKindOptions, - kubernetesVerbOptions, - KubernetesResourceModel, - AppAccessSpec, - DatabaseAccessSpec, - WindowsDesktopAccessSpec, - RuleModel, - resourceKindOptions, - verbOptions, - newRuleModel, - OptionsModel, - requireMFATypeOptions, - createHostUserModeOptions, - createDBUserModeOptions, - sessionRecordingModeOptions, - resourceKindOptionsMap, - ResourceKindOption, -} from './standardmodel'; -import { - validateRoleEditorModel, - MetadataValidationResult, - AccessSpecValidationResult, - ServerSpecValidationResult, - KubernetesSpecValidationResult, - KubernetesResourceValidationResult, - AppSpecValidationResult, - DatabaseSpecValidationResult, - WindowsDesktopSpecValidationResult, - AccessRuleValidationResult, -} from './validation'; -import { EditorSaveCancelButton } from './Shared'; -import { RequiresResetToStandard } from './RequiresResetToStandard'; - -export type StandardEditorProps = { - originalRole: RoleWithYaml; - standardEditorModel: StandardEditorModel; - isProcessing?: boolean; - onSave?(r: Role): void; - onCancel?(): void; - onChange?(s: StandardEditorModel): void; -}; - -/** - * A structured editor that represents a role with a series of UI controls, as - * opposed to a YAML text editor. - */ -export const StandardEditor = ({ - originalRole, - standardEditorModel, - isProcessing, - onSave, - onCancel, - onChange, -}: StandardEditorProps) => { - const isEditing = !!originalRole; - const { roleModel } = standardEditorModel; - const validation = validateRoleEditorModel(roleModel); - - /** All spec kinds except those that are already in the role. */ - const allowedSpecKinds = allAccessSpecKinds.filter(k => - roleModel.accessSpecs.every(as => as.kind !== k) - ); - - enum StandardEditorTab { - Overview, - Resources, - AccessRules, - Options, - } - - const [currentTab, setCurrentTab] = useState(StandardEditorTab.Overview); - const idPrefix = useId(); - const overviewTabId = `${idPrefix}-overview`; - const resourcesTabId = `${idPrefix}-resources`; - const accessRulesTabId = `${idPrefix}-access-rules`; - const optionsTabId = `${idPrefix}-options`; - - const validator = useValidation(); - - function handleSave() { - if (!validator.validate()) { - return; - } - onSave?.(roleEditorModelToRole(standardEditorModel.roleModel)); - } - - function handleChange(modified: Partial) { - const updatedResourceModel: RoleEditorModel = { - ...roleModel, - ...modified, - }; - - onChange?.({ - ...standardEditorModel, - roleModel: updatedResourceModel, - isDirty: hasModifiedFields(updatedResourceModel, originalRole?.object), - }); - } - - function addAccessSpec(kind: AccessSpecKind) { - handleChange({ - ...standardEditorModel.roleModel, - accessSpecs: [ - ...standardEditorModel.roleModel.accessSpecs, - newAccessSpec(kind), - ], - }); - } - - function removeAccessSpec(kind: AccessSpecKind) { - handleChange({ - ...standardEditorModel.roleModel, - accessSpecs: standardEditorModel.roleModel.accessSpecs.filter( - s => s.kind !== kind - ), - }); - } - - function setAccessSpec(value: AccessSpec) { - handleChange({ - ...standardEditorModel.roleModel, - accessSpecs: standardEditorModel.roleModel.accessSpecs.map(original => - original.kind === value.kind ? value : original - ), - }); - } - - function setRules(rules: RuleModel[]) { - handleChange({ - ...standardEditorModel.roleModel, - rules, - }); - } - - function setOptions(options: OptionsModel) { - handleChange({ - ...standardEditorModel, - options, - }); - } - - return ( - <> - {roleModel.requiresReset && ( - - - - )} - - - !s.valid) - ? validationErrorTabStatus - : undefined, - }, - { - key: StandardEditorTab.AccessRules, - title: 'Access Rules', - controls: accessRulesTabId, - status: - validator.state.validating && - validation.rules.some(s => !s.valid) - ? validationErrorTabStatus - : undefined, - }, - { - key: StandardEditorTab.Options, - title: 'Options', - controls: optionsTabId, - }, - ]} - activeIndex={currentTab} - onChange={setCurrentTab} - /> - - - - handleChange({ ...roleModel, metadata })} - /> - - - - {roleModel.accessSpecs.map((spec, i) => { - const validationResult = validation.accessSpecs[i]; - return ( - setAccessSpec(value)} - onRemove={() => removeAccessSpec(spec.kind)} - /> - ); - })} - - - - Add New Specifications - - } - buttonProps={{ - size: 'medium', - fill: 'filled', - disabled: isProcessing || allowedSpecKinds.length === 0, - }} - > - {allowedSpecKinds.map(kind => ( - addAccessSpec(kind)}> - {specSections[kind].title} - - ))} - - - - - - - - - - - - - handleSave()} - onCancel={onCancel} - disabled={ - isProcessing || - standardEditorModel.roleModel.requiresReset || - !standardEditorModel.isDirty - } - isEditing={isEditing} - /> - - ); -}; - -export type SectionProps = { - value: Model; - isProcessing: boolean; - validation?: ValidationResult; - onChange?(value: Model): void; -}; - -const validationErrorTabStatus = { - kind: 'danger', - ariaLabel: 'Invalid data', -} as const; - -const MetadataSection = ({ - value, - isProcessing, - validation, - onChange, -}: SectionProps) => ( -
- onChange({ ...value, name: e.target.value })} - /> - ) => - onChange({ ...value, description: e.target.value }) - } - /> - - Labels - - onChange?.({ ...value, labels })} - rule={precomputed(validation.fields.labels)} - /> -
-); - -/** - * A wrapper for editor section. Its responsibility is rendering a header, - * expanding, collapsing, and removing the section. - */ -const Section = ({ - title, - tooltip, - children, - removable, - isProcessing, - validation, - onRemove, -}: React.PropsWithChildren<{ - title: string; - tooltip: string; - removable?: boolean; - isProcessing: boolean; - validation?: ValidationResult; - onRemove?(): void; -}>) => { - const theme = useTheme(); - const [expanded, setExpanded] = useState(true); - const ExpandIcon = expanded ? Icon.Minus : Icon.Plus; - const expandTooltip = expanded ? 'Collapse' : 'Expand'; - const validator = useValidation(); - - const handleExpand = (e: React.MouseEvent) => { - // Don't let handle the event, we'll do it ourselves to keep - // track of the state. - e.preventDefault(); - setExpanded(expanded => !expanded); - }; - - const handleRemove = (e: React.MouseEvent) => { - // Don't let handle the event. - e.stopPropagation(); - onRemove?.(); - }; - - return ( - - - {/* TODO(bl-nero): Show validation result in the summary. */} - -

{title}

- {tooltip && {tooltip}} -
- {removable && ( - - - - - - - - )} - - - -
- - {children} - -
- ); -}; - -/** - * All access spec kinds, in order of appearance in the resource kind dropdown. - */ -const allAccessSpecKinds: AccessSpecKind[] = [ - 'kube_cluster', - 'node', - 'app', - 'db', - 'windows_desktop', -]; - -/** Maps access specification kind to UI component configuration. */ -const specSections: Record< - AccessSpecKind, - { - title: string; - tooltip: string; - component: React.ComponentType>; - } -> = { - kube_cluster: { - title: 'Kubernetes', - tooltip: 'Configures access to Kubernetes clusters', - component: KubernetesAccessSpecSection, - }, - node: { - title: 'Servers', - tooltip: 'Configures access to SSH servers', - component: ServerAccessSpecSection, - }, - app: { - title: 'Applications', - tooltip: 'Configures access to applications', - component: AppAccessSpecSection, - }, - db: { - title: 'Databases', - tooltip: 'Configures access to databases', - component: DatabaseAccessSpecSection, - }, - windows_desktop: { - title: 'Windows Desktops', - tooltip: 'Configures access to Windows desktops', - component: WindowsDesktopAccessSpecSection, - }, -}; - -/** - * A generic access spec section. Details are rendered by components from the - * `specSections` map. - */ -const AccessSpecSection = < - T extends AccessSpec, - V extends AccessSpecValidationResult, ->({ - value, - isProcessing, - validation, - onChange, - onRemove, -}: SectionProps & { - onRemove?(): void; -}) => { - const { component: Body, title, tooltip } = specSections[value.kind]; - return ( -
- -
- ); -}; - -export function ServerAccessSpecSection({ - value, - isProcessing, - validation, - onChange, -}: SectionProps) { - return ( - <> - - Labels - - onChange?.({ ...value, labels })} - rule={precomputed(validation.fields.labels)} - /> - `Login: ${label}`} - components={{ - DropdownIndicator: null, - }} - openMenuOnClick={false} - value={value.logins} - onChange={logins => onChange?.({ ...value, logins })} - rule={precomputed(validation.fields.logins)} - mt={3} - mb={0} - /> - - ); -} - -export function KubernetesAccessSpecSection({ - value, - isProcessing, - validation, - onChange, -}: SectionProps) { - return ( - <> - `Group: ${label}`} - components={{ - DropdownIndicator: null, - }} - openMenuOnClick={false} - value={value.groups} - onChange={groups => onChange?.({ ...value, groups })} - /> - - - Labels - - onChange?.({ ...value, labels })} - /> - - - {value.resources.map((resource, index) => ( - - onChange?.({ - ...value, - resources: value.resources.map((res, i) => - i === index ? newRes : res - ), - }) - } - onRemove={() => - onChange?.({ - ...value, - resources: value.resources.toSpliced(index, 1), - }) - } - /> - ))} - - - - onChange?.({ - ...value, - resources: [...value.resources, newKubernetesResourceModel()], - }) - } - > - - {value.resources.length > 0 - ? 'Add Another Resource' - : 'Add a Resource'} - - - - - ); -} - -function KubernetesResourceView({ - value, - validation, - isProcessing, - onChange, - onRemove, -}: { - value: KubernetesResourceModel; - validation: KubernetesResourceValidationResult; - isProcessing: boolean; - onChange(m: KubernetesResourceModel): void; - onRemove(): void; -}) { - const { kind, name, namespace, verbs } = value; - const theme = useTheme(); - return ( - - - -

Resource

-
- - - -
- onChange?.({ ...value, kind: k })} - /> - - Name of the resource. Special value *{' '} - means any name. - - } - disabled={isProcessing} - value={name} - rule={precomputed(validation.name)} - onChange={e => onChange?.({ ...value, name: e.target.value })} - /> - - Namespace that contains the resource. Special value{' '} - * means any namespace. - - } - disabled={isProcessing} - value={namespace} - rule={precomputed(validation.namespace)} - onChange={e => onChange?.({ ...value, namespace: e.target.value })} - /> - onChange?.({ ...value, verbs: v })} - mb={0} - /> -
- ); -} - -export function AppAccessSpecSection({ - value, - validation, - isProcessing, - onChange, -}: SectionProps) { - return ( - - - - Labels - - onChange?.({ ...value, labels })} - rule={precomputed(validation.fields.labels)} - /> - - onChange?.({ ...value, awsRoleARNs: arns })} - rule={precomputed(validation.fields.awsRoleARNs)} - /> - onChange?.({ ...value, azureIdentities: ids })} - rule={precomputed(validation.fields.azureIdentities)} - /> - onChange?.({ ...value, gcpServiceAccounts: accts })} - rule={precomputed(validation.fields.gcpServiceAccounts)} - /> - - ); -} - -export function DatabaseAccessSpecSection({ - value, - isProcessing, - validation, - onChange, -}: SectionProps) { - return ( - <> - - - Labels - - onChange?.({ ...value, labels })} - rule={precomputed(validation.fields.labels)} - /> - - - List of database names that this role is allowed to connect to. - Special value * means any name. - - } - isDisabled={isProcessing} - formatCreateLabel={label => `Database Name: ${label}`} - components={{ - DropdownIndicator: null, - }} - openMenuOnClick={false} - value={value.names} - onChange={names => onChange?.({ ...value, names })} - /> - - List of database users that this role is allowed to connect as. - Special value * means any user. - - } - isDisabled={isProcessing} - formatCreateLabel={label => `Database User: ${label}`} - components={{ - DropdownIndicator: null, - }} - openMenuOnClick={false} - value={value.users} - onChange={users => onChange?.({ ...value, users })} - /> - `Database Role: ${label}`} - components={{ - DropdownIndicator: null, - }} - openMenuOnClick={false} - value={value.roles} - onChange={roles => onChange?.({ ...value, roles })} - rule={precomputed(validation.fields.roles)} - mb={0} - /> - - ); -} - -export function WindowsDesktopAccessSpecSection({ - value, - isProcessing, - validation, - onChange, -}: SectionProps) { - return ( - <> - - - Labels - - onChange?.({ ...value, labels })} - rule={precomputed(validation.fields.labels)} - /> - - `Login: ${label}`} - components={{ - DropdownIndicator: null, - }} - openMenuOnClick={false} - value={value.logins} - onChange={logins => onChange?.({ ...value, logins })} - /> - - ); -} - -export function AccessRules({ - value, - isProcessing, - validation, - onChange, -}: SectionProps) { - function addRule() { - onChange?.([...value, newRuleModel()]); - } - function setRule(rule: RuleModel) { - onChange?.(value.map(r => (r.id === rule.id ? rule : r))); - } - function removeRule(id: string) { - onChange?.(value.filter(r => r.id !== id)); - } - return ( - - {value.map((rule, i) => ( - removeRule(rule.id)} - /> - ))} - - - Add New - - - ); -} - -function AccessRule({ - value, - isProcessing, - validation, - onChange, - onRemove, -}: SectionProps & { - onRemove?(): void; -}) { - const { resources, verbs } = value; - return ( -
- onChange?.({ ...value, resources: r })} - rule={precomputed(validation.fields.resources)} - /> - onChange?.({ ...value, verbs: v })} - rule={precomputed(validation.fields.verbs)} - mb={0} - /> -
- ); -} - -const ResourceKindSelect = styled( - FieldSelectCreatable -)` - .teleport-resourcekind__value--unknown { - background: ${props => props.theme.colors.interactive.solid.alert.default}; - .react-select__multi-value__label, - .react-select__multi-value__remove { - color: ${props => props.theme.colors.text.primaryInverse}; - } - } -`; - -function ResourceKindMultiValue(props: MultiValueProps) { - if (resourceKindOptionsMap.has(props.data.value)) { - return ; - } - return ( - - - - ); -} - -function Options({ - value, - isProcessing, - onChange, -}: SectionProps) { - const theme = useTheme(); - const id = useId(); - const maxSessionTTLId = `${id}-max-session-ttl`; - const clientIdleTimeoutId = `${id}-client-idle-timeout`; - const requireMFATypeId = `${id}-require-mfa-type`; - const createHostUserModeId = `${id}-create-host-user-mode`; - const createDBUserModeId = `${id}-create-db-user-mode`; - const defaultSessionRecordingModeId = `${id}-default-session-recording-mode`; - const sshSessionRecordingModeId = `${id}-ssh-session-recording-mode`; - return ( - - Global Settings - - Max Session TTL - onChange({ ...value, maxSessionTTL: e.target.value })} - /> - - - Client Idle Timeout - - - onChange({ ...value, clientIdleTimeout: e.target.value }) - } - /> - - Disconnect When Certificate Expires - onChange({ ...value, disconnectExpiredCert: d })} - /> - - Require Session MFA - onChange?.({ ...value, defaultSessionRecordingMode: m })} - /> - - SSH - - - Create Host User Mode - - onChange?.({ ...value, sshSessionRecordingMode: m })} - /> - - Database - - Create Database User - onChange({ ...value, createDBUser: c })} - /> - - {/* TODO(bl-nero): a bug in YAML unmarshalling backend breaks the - createDBUserMode field. Fix it and add the field here. */} - - Create Database User Mode - - onChange({ ...value, maxSessionTTL: e.target.value })} + /> + + + Client Idle Timeout + + + onChange({ ...value, clientIdleTimeout: e.target.value }) + } + /> + + Disconnect When Certificate Expires + onChange({ ...value, disconnectExpiredCert: d })} + /> + + Require Session MFA + onChange?.({ ...value, defaultSessionRecordingMode: m })} + /> + + SSH + + + Create Host User Mode + + onChange?.({ ...value, sshSessionRecordingMode: m })} + /> + + Database + + Create Database User + onChange({ ...value, createDBUser: c })} + /> + + {/* TODO(bl-nero): a bug in YAML unmarshalling backend breaks the + createDBUserMode field. Fix it and add the field here. */} + + Create Database User Mode + +