From f2d2c04ffab984c73f5e1b1ee8058c9fcfe0df4c Mon Sep 17 00:00:00 2001 From: Paul Gottschling Date: Fri, 11 Oct 2024 16:07:45 -0400 Subject: [PATCH 01/53] Fix docs issues that break Docusaurus builds (#47491) Backports #47471 - Add index pages to top-level docs sections. These pages do not appear in the sidebar of the current docs engine, but are required for the Docusaurus site to render properly. We can build them into fully developed introductory pages in subsequent changes. --- docs/pages/admin-guides/admin-guides.mdx | 6 ++++++ docs/pages/connect-your-client/connect-your-client.mdx | 6 ++++++ docs/pages/enroll-resources/enroll-resources.mdx | 6 ++++++ docs/pages/reference/reference.mdx | 6 ++++++ 4 files changed, 24 insertions(+) create mode 100644 docs/pages/admin-guides/admin-guides.mdx create mode 100644 docs/pages/connect-your-client/connect-your-client.mdx create mode 100644 docs/pages/enroll-resources/enroll-resources.mdx create mode 100644 docs/pages/reference/reference.mdx diff --git a/docs/pages/admin-guides/admin-guides.mdx b/docs/pages/admin-guides/admin-guides.mdx new file mode 100644 index 0000000000000..6ee7b110f6d83 --- /dev/null +++ b/docs/pages/admin-guides/admin-guides.mdx @@ -0,0 +1,6 @@ +--- +title: Teleport Admin Guides +description: Provides step-by-step instructions for completing administrative tasks in Teleport. +--- + +(!toc!) diff --git a/docs/pages/connect-your-client/connect-your-client.mdx b/docs/pages/connect-your-client/connect-your-client.mdx new file mode 100644 index 0000000000000..844e0a9d842dc --- /dev/null +++ b/docs/pages/connect-your-client/connect-your-client.mdx @@ -0,0 +1,6 @@ +--- +title: Teleport User Guides +description: Provides instructions to help users connect to infrastructure resources with Teleport. +--- + +(!toc!) diff --git a/docs/pages/enroll-resources/enroll-resources.mdx b/docs/pages/enroll-resources/enroll-resources.mdx new file mode 100644 index 0000000000000..6d0f6ab1192d4 --- /dev/null +++ b/docs/pages/enroll-resources/enroll-resources.mdx @@ -0,0 +1,6 @@ +--- +title: Enrolling Teleport Resources +description: Provides step-by-step instructions for enrolling servers, databases, and other infrastructure resources with your Teleport cluster. +--- + +(!toc!) diff --git a/docs/pages/reference/reference.mdx b/docs/pages/reference/reference.mdx new file mode 100644 index 0000000000000..6b28695deeb91 --- /dev/null +++ b/docs/pages/reference/reference.mdx @@ -0,0 +1,6 @@ +--- +title: Teleport Reference Guides +description: Provides comprehensive information on configuration fields, Teleport commands, and other ways of interacting with Teleport. +--- + +(!toc!) From 90f1479c1669e3198231be2dd84b3c7e6523ebfe Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Mon, 14 Oct 2024 07:35:37 -0600 Subject: [PATCH 02/53] tctl: add a --with-secrets flag to tctl tokens ls (#47547) Show the "safe name" for tokens by default, which is the name of the token for non-sensitive join tokens, and a redacted version of the name for shared secret tokens. Note: for --format=json or --format=yaml we currently maintain the original behavior (always show the raw token contents). The tctl get tokens command has also not been touched - it continues to return the raw token resource from the backend. Updates #47254 --- tool/tctl/common/token_command.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tool/tctl/common/token_command.go b/tool/tctl/common/token_command.go index d096dd5a440e2..0eac663eef2cd 100644 --- a/tool/tctl/common/token_command.go +++ b/tool/tctl/common/token_command.go @@ -60,6 +60,8 @@ Use this token to add an MDM service to Teleport. type TokensCommand struct { config *servicecfg.Config + withSecrets bool + // format is the output format, e.g. text or json format string @@ -134,6 +136,7 @@ func (c *TokensCommand) Initialize(app *kingpin.Application, config *servicecfg. // "tctl tokens ls" c.tokenList = tokens.Command("ls", "List node and user invitation tokens.") c.tokenList.Flag("format", "Output format, 'text', 'json' or 'yaml'").EnumVar(&c.format, formats...) + c.tokenList.Flag("with-secrets", "Do not redact join tokens").BoolVar(&c.withSecrets) if c.stdout == nil { c.stdout = os.Stdout @@ -382,6 +385,11 @@ func (c *TokensCommand) List(ctx context.Context, client *authclient.Client) err // Sort by expire time. sort.Slice(tokens, func(i, j int) bool { return tokens[i].Expiry().Unix() < tokens[j].Expiry().Unix() }) + nameFunc := (types.ProvisionToken).GetSafeName + if c.withSecrets { + nameFunc = (types.ProvisionToken).GetName + } + switch c.format { case teleport.JSON: err := utils.WriteJSONArray(c.stdout, tokens) @@ -395,7 +403,7 @@ func (c *TokensCommand) List(ctx context.Context, client *authclient.Client) err } case teleport.Text: for _, token := range tokens { - fmt.Fprintln(c.stdout, token.GetName()) + fmt.Fprintln(c.stdout, nameFunc(token)) } default: tokensView := func() string { @@ -403,12 +411,12 @@ func (c *TokensCommand) List(ctx context.Context, client *authclient.Client) err now := time.Now() for _, t := range tokens { expiry := "never" - if !t.Expiry().IsZero() { + if !t.Expiry().IsZero() && t.Expiry().Unix() != 0 { exptime := t.Expiry().Format(time.RFC822) expdur := t.Expiry().Sub(now).Round(time.Second) expiry = fmt.Sprintf("%s (%s)", exptime, expdur.String()) } - table.AddRow([]string{t.GetName(), t.GetRoles().String(), printMetadataLabels(t.GetMetadata().Labels), expiry}) + table.AddRow([]string{nameFunc(t), t.GetRoles().String(), printMetadataLabels(t.GetMetadata().Labels), expiry}) } return table.AsBuffer().String() } From 3f39c0d9ea827916368af8a9842402f164f29b5b Mon Sep 17 00:00:00 2001 From: Steven Martin Date: Mon, 14 Oct 2024 19:18:30 -0400 Subject: [PATCH 03/53] [v14] docs: include description as optional field for app config reference (#47537) * docs: include description as optional field for app config reference * docs: include amazon linux 2023 in supported enhanced logging --- .../server-access/guides/bpf-session-recording.mdx | 2 +- docs/pages/includes/config-reference/app-service.yaml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/pages/enroll-resources/server-access/guides/bpf-session-recording.mdx b/docs/pages/enroll-resources/server-access/guides/bpf-session-recording.mdx index 8a05e1b4cefc2..eef05d0216272 100644 --- a/docs/pages/enroll-resources/server-access/guides/bpf-session-recording.mdx +++ b/docs/pages/enroll-resources/server-access/guides/bpf-session-recording.mdx @@ -88,7 +88,7 @@ Teleport supports enhanced session recording with BPF on `amd64` and `arm64` arc | Fedora | 33 | 5.8+ | | Arch Linux | 2020.09.01 | 5.8.5+ | | Flatcar | 2765.2.2 | 5.10.25+ | -| Amazon Linux | 2 | 5.10+ | +| Amazon Linux | 2 and 2023 | 5.10+ | - (!docs/pages/includes/tctl.mdx!) diff --git a/docs/pages/includes/config-reference/app-service.yaml b/docs/pages/includes/config-reference/app-service.yaml index 079c1d7379528..b3e7b12edf0f8 100644 --- a/docs/pages/includes/config-reference/app-service.yaml +++ b/docs/pages/includes/config-reference/app-service.yaml @@ -22,6 +22,8 @@ app_service: # Optional: For access to cloud provider APIs, specify the cloud # provider. Allowed values are "AWS", "Azure", and "GCP". cloud: "" + # Optional: Free-form description of the application. + description: "Kubernetes Dashboard to development cluster" # URI of Application. For TCP applications # use tcp, ex: tcp://localhost:5432. uri: "http://10.0.1.27:8000" From bf8b4eab634f2ec34df05986d48fef05b762851e Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Tue, 15 Oct 2024 07:48:15 -0600 Subject: [PATCH 04/53] Fix active session filtering for legacy sessions (#47562) This code never worked correctly, but mostly went unnoticed because it is only triggered when using legacy roles prior to RoleV5. Prior to moderated sessions, RBAC for viewing active sessions was based on whether or not you could join a session as the OS login that is being used, along with a pseudo-resource of kind "ssh_session". With moderated sessions we introduced more flexible RBAC semantics that allow you to join sessions in different modes (peer, observer, moderator), even if you don't actually have permission to start sessions. In #11223 we decided that we need to support both types of RBAC checks (legacy checks against the "ssh_session" resource, and newer checks against the session_tracker and join_sessions policies). The code that was doing the legacy checks was flawed for two reasons: 1. It used (types.SessionTracker).GetKind() (which will always be "session_tracker") instead of (types.SessionTracker).GetSessionKind(). 2. When checking whether the session was SSH, it was checking for the legacy "ssh_session" value, instead of the "ssh" value that session trackers actually use. --- api/types/constants.go | 5 ++++- api/types/session_tracker.go | 5 +++++ lib/auth/auth_with_roles.go | 10 ++++----- lib/auth/auth_with_roles_test.go | 36 +++++++++++++++++++++++++++++++- 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/api/types/constants.go b/api/types/constants.go index 23e733a1d99db..66ea9bbb614a2 100644 --- a/api/types/constants.go +++ b/api/types/constants.go @@ -106,7 +106,10 @@ const ( // KindSession is a recorded SSH session. KindSession = "session" - // KindSSHSession is an active SSH session. + // KindSSHSession represents an active SSH session in early versions of Teleport + // prior to the introduction of moderated sessions. Note that ssh_session is not + // a "real" resource, and it is never used as the "session kind" value in the + // session_tracker resource. KindSSHSession = "ssh_session" // KindWebSession is a web session resource diff --git a/api/types/session_tracker.go b/api/types/session_tracker.go index 2d205ca31e8ff..8574c092db114 100644 --- a/api/types/session_tracker.go +++ b/api/types/session_tracker.go @@ -28,7 +28,12 @@ import ( // SessionKind is a type of session. type SessionKind string +// These represent the possible values for the kind field in session trackers. const ( + // SSHSessionKind is the kind used for session tracking with the + // session_tracker resource used in Teleport 9+. Note that it is + // different from the legacy [types.KindSSHSession] value that was + // used prior to the introduction of moderated sessions. SSHSessionKind SessionKind = "ssh" KubernetesSessionKind SessionKind = "k8s" DatabaseSessionKind SessionKind = "db" diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index 36eb6f9975386..2bb1961df3839 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -417,12 +417,12 @@ func (a *ServerWithRoles) CreateSessionTracker(ctx context.Context, tracker type return tracker, nil } -func (a *ServerWithRoles) filterSessionTracker(ctx context.Context, joinerRoles []types.Role, tracker types.SessionTracker, verb string) bool { +func (a *ServerWithRoles) filterSessionTracker(joinerRoles []types.Role, tracker types.SessionTracker, verb string) bool { // Apply RFD 45 RBAC rules to the session if it's SSH. // This is a bit of a hack. It converts to the old legacy format // which we don't have all data for, luckily the fields we don't have aren't made available // to the RBAC filter anyway. - if tracker.GetKind() == types.KindSSHSession { + if tracker.GetSessionKind() == types.SSHSessionKind { ruleCtx := &services.Context{User: a.context.User} ruleCtx.SSHSession = &session.Session{ Kind: tracker.GetSessionKind(), @@ -573,7 +573,7 @@ func (a *ServerWithRoles) GetSessionTracker(ctx context.Context, sessionID strin return nil, trace.Wrap(err) } - ok := a.filterSessionTracker(ctx, joinerRoles, tracker, types.VerbRead) + ok := a.filterSessionTracker(joinerRoles, tracker, types.VerbRead) if !ok { return nil, trace.NotFound("session %v not found", sessionID) } @@ -600,7 +600,7 @@ func (a *ServerWithRoles) GetActiveSessionTrackers(ctx context.Context) ([]types } for _, sess := range sessions { - ok := a.filterSessionTracker(ctx, joinerRoles, sess, types.VerbList) + ok := a.filterSessionTracker(joinerRoles, sess, types.VerbList) if ok { filteredSessions = append(filteredSessions, sess) } @@ -628,7 +628,7 @@ func (a *ServerWithRoles) GetActiveSessionTrackersWithFilter(ctx context.Context } for _, sess := range sessions { - ok := a.filterSessionTracker(ctx, joinerRoles, sess, types.VerbList) + ok := a.filterSessionTracker(joinerRoles, sess, types.VerbList) if ok { filteredSessions = append(filteredSessions, sess) } diff --git a/lib/auth/auth_with_roles_test.go b/lib/auth/auth_with_roles_test.go index d97f64aa3d759..4d45cf82cd5a5 100644 --- a/lib/auth/auth_with_roles_test.go +++ b/lib/auth/auth_with_roles_test.go @@ -5387,7 +5387,41 @@ func TestGetActiveSessionTrackers(t *testing.T) { require.NoError(t, err) return getActiveSessionsTestCase{"no access with match expression", tracker, role, false} - }()} + }(), func() getActiveSessionsTestCase { + tracker, err := types.NewSessionTracker(types.SessionTrackerSpecV1{ + SessionID: "1", + Kind: string(types.SSHSessionKind), + }) + require.NoError(t, err) + + role, err := types.NewRoleWithVersion("dev", types.V3, types.RoleSpecV6{ + Allow: types.RoleConditions{ + AppLabels: types.Labels{"*": []string{"*"}}, + DatabaseLabels: types.Labels{"*": []string{"*"}}, + KubernetesLabels: types.Labels{"*": []string{"*"}}, + KubernetesResources: []types.KubernetesResource{ + {Kind: types.KindKubePod, Name: "*", Namespace: "*", Verbs: []string{"*"}}, + }, + NodeLabels: types.Labels{"*": []string{"*"}}, + NodeLabelsExpression: `contains(user.spec.traits["cluster_ids"], labels["cluster_id"]) || contains(user.spec.traits["sub"], labels["owner"])`, + Logins: []string{"{{external.sub}}"}, + WindowsDesktopLabels: types.Labels{"cluster_id": []string{"{{external.cluster_ids}}"}}, + WindowsDesktopLogins: []string{"{{external.sub}}", "{{external.windows_logins}}"}, + }, + Deny: types.RoleConditions{ + Rules: []types.Rule{ + { + Resources: []string{types.KindDatabaseServer, types.KindAppServer, types.KindSession, types.KindSSHSession, types.KindKubeService, types.KindSessionTracker}, + Verbs: []string{"list", "read"}, + }, + }, + }, + }) + require.NoError(t, err) + + return getActiveSessionsTestCase{"filter bug v3 role", tracker, role, false} + }(), + } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { From ac878f31b0c3ed8fa09cc374447623f37b684770 Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:12:18 +0000 Subject: [PATCH 05/53] Fix backends with prefixes (#47238) (#47490) #46701 incorrectly changed how the prefixes for the etcd and dynamo backends were applied and removed. In addition, the backend lock key was not being constructed correctly and caused acquisition of locks to fail on a cluster that had been upgraded. Tests have been added to ensure that keys are now being constructed correctly in all cases. Additionally, backend/helpers.go was moved to backed/lock.go to better described what functionality the file contains. Backends with prefixes (etcd, dynamo) were incorrectly constructing the key for locks. In addition the prefixes were not being trimmed correctly which broke CAS operations. --- lib/backend/dynamo/dynamodbbk.go | 2 +- lib/backend/dynamo/dynamodbbk_test.go | 19 +++++++++++++ lib/backend/etcdbk/etcd.go | 6 ++--- lib/backend/etcdbk/etcd_test.go | 27 +++++++++++++++++++ lib/backend/{helpers.go => lock.go} | 0 lib/backend/{helpers_test.go => lock_test.go} | 15 +++++++++++ 6 files changed, 65 insertions(+), 4 deletions(-) rename lib/backend/{helpers.go => lock.go} (100%) rename lib/backend/{helpers_test.go => lock_test.go} (88%) diff --git a/lib/backend/dynamo/dynamodbbk.go b/lib/backend/dynamo/dynamodbbk.go index 3aef1d04b7c2d..44772550310a9 100644 --- a/lib/backend/dynamo/dynamodbbk.go +++ b/lib/backend/dynamo/dynamodbbk.go @@ -837,7 +837,7 @@ const ( // prependPrefix adds leading 'teleport/' to the key for backwards compatibility // with previous implementation of DynamoDB backend func prependPrefix(key backend.Key) string { - return keyPrefix + string(key) + return keyPrefix + key.String() } // trimPrefix removes leading 'teleport' from the key diff --git a/lib/backend/dynamo/dynamodbbk_test.go b/lib/backend/dynamo/dynamodbbk_test.go index 0688990279d77..ce0420e00f1fc 100644 --- a/lib/backend/dynamo/dynamodbbk_test.go +++ b/lib/backend/dynamo/dynamodbbk_test.go @@ -29,6 +29,7 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/gravitational/teleport/lib/backend" @@ -214,3 +215,21 @@ func TestCreateTable(t *testing.T) { }) } } + +func TestKeyPrefix(t *testing.T) { + t.Run("leading separator in key", func(t *testing.T) { + prefixed := prependPrefix(backend.NewKey("test", "llama")) + assert.Equal(t, "teleport/test/llama", prefixed) + + key := trimPrefix(prefixed) + assert.Equal(t, "/test/llama", key.String()) + }) + + t.Run("no leading separator in key", func(t *testing.T) { + prefixed := prependPrefix(backend.Key(".locks/test/llama")) + assert.Equal(t, "teleport.locks/test/llama", prefixed) + + key := trimPrefix(prefixed) + assert.Equal(t, ".locks/test/llama", key.String()) + }) +} diff --git a/lib/backend/etcdbk/etcd.go b/lib/backend/etcdbk/etcd.go index 68e0e7267f668..02cd015e7c1fc 100644 --- a/lib/backend/etcdbk/etcd.go +++ b/lib/backend/etcdbk/etcd.go @@ -1016,10 +1016,10 @@ func fromType(eventType mvccpb.Event_EventType) types.OpType { } } -func (b *EtcdBackend) trimPrefix(in backend.Key) backend.Key { - return in.TrimPrefix(backend.Key(b.cfg.Key)) +func (b *EtcdBackend) trimPrefix(in []byte) backend.Key { + return backend.Key(in).TrimPrefix(backend.Key(b.cfg.Key)) } func (b *EtcdBackend) prependPrefix(in backend.Key) string { - return b.cfg.Key + string(in) + return b.cfg.Key + in.String() } diff --git a/lib/backend/etcdbk/etcd_test.go b/lib/backend/etcdbk/etcd_test.go index df1f76f1326ef..bd48c7184be3d 100644 --- a/lib/backend/etcdbk/etcd_test.go +++ b/lib/backend/etcdbk/etcd_test.go @@ -26,6 +26,7 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/gravitational/teleport/lib/backend" @@ -255,3 +256,29 @@ func etcdTestEndpoint() string { } return "https://127.0.0.1:2379" } + +func TestKeyPrefix(t *testing.T) { + prefixes := []string{"teleport", "/teleport", "/teleport/"} + + for _, prefix := range prefixes { + t.Run("prefix="+prefix, func(t *testing.T) { + bk := EtcdBackend{cfg: &Config{Key: prefix}} + + t.Run("leading separator in key", func(t *testing.T) { + prefixed := bk.prependPrefix(backend.NewKey("test", "llama")) + assert.Equal(t, prefix+"/test/llama", prefixed) + + key := bk.trimPrefix([]byte(prefixed)) + assert.Equal(t, "/test/llama", key.String()) + }) + + t.Run("no leading separator in key", func(t *testing.T) { + prefixed := bk.prependPrefix(backend.Key(".locks/test/llama")) + assert.Equal(t, prefix+".locks/test/llama", prefixed) + + key := bk.trimPrefix([]byte(prefixed)) + assert.Equal(t, ".locks/test/llama", key.String()) + }) + }) + } +} diff --git a/lib/backend/helpers.go b/lib/backend/lock.go similarity index 100% rename from lib/backend/helpers.go rename to lib/backend/lock.go diff --git a/lib/backend/helpers_test.go b/lib/backend/lock_test.go similarity index 88% rename from lib/backend/helpers_test.go rename to lib/backend/lock_test.go index 496c476d674ca..822ede66236f0 100644 --- a/lib/backend/helpers_test.go +++ b/lib/backend/lock_test.go @@ -21,9 +21,24 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func TestLockKey(t *testing.T) { + t.Run("empty parts", func(t *testing.T) { + key := lockKey() + assert.Equal(t, ".locks", key.String()) + assert.Equal(t, [][]byte{[]byte(".locks")}, key.Components()) + }) + + t.Run("with parts", func(t *testing.T) { + key := lockKey("test", "llama") + assert.Equal(t, ".locks/test/llama", key.String()) + assert.Equal(t, [][]byte{[]byte(".locks"), []byte("test"), []byte("llama")}, key.Components()) + }) +} + func TestLockConfiguration_CheckAndSetDefaults(t *testing.T) { type mockBackend struct { Backend From 58d8daa34cfc74ef7f81dfe2e7a478b393320822 Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Tue, 15 Oct 2024 11:06:50 -0400 Subject: [PATCH 06/53] [v14] Add filesystem lock for unix and windows platforms (#47196) * Add filesystem lock for unix and windows platforms * Add non-blocking flag * Replace lock with gofrs/flock --- lib/utils/fs.go | 22 ++++++++++++++++++- lib/utils/fs_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/lib/utils/fs.go b/lib/utils/fs.go index 36322877f83b3..49254628f0ac4 100644 --- a/lib/utils/fs.go +++ b/lib/utils/fs.go @@ -201,6 +201,16 @@ func FSTryWriteLock(filePath string) (unlock func() error, err error) { return fileLock.Unlock, nil } +// FSWriteLock tries to grab write lock and block if lock is already acquired by someone else. +func FSWriteLock(filePath string) (unlock func() error, err error) { + fileLock := flock.New(getPlatformLockFilePath(filePath)) + if err := fileLock.Lock(); err != nil { + return nil, trace.ConvertSystemError(err) + } + + return fileLock.Unlock, nil +} + // FSTryWriteLockTimeout tries to grab write lock, it's doing it until locks is acquired, or timeout is expired, // or context is expired. func FSTryWriteLockTimeout(ctx context.Context, filePath string, timeout time.Duration) (unlock func() error, err error) { @@ -214,7 +224,7 @@ func FSTryWriteLockTimeout(ctx context.Context, filePath string, timeout time.Du return fileLock.Unlock, nil } -// FSTryReadLock tries to grab write lock, returns ErrUnsuccessfulLockTry +// FSTryReadLock tries to grab shared lock, returns ErrUnsuccessfulLockTry // if lock is already acquired by someone else func FSTryReadLock(filePath string) (unlock func() error, err error) { fileLock := flock.New(getPlatformLockFilePath(filePath)) @@ -229,6 +239,16 @@ func FSTryReadLock(filePath string) (unlock func() error, err error) { return fileLock.Unlock, nil } +// FSReadLock tries to grab shared lock and block if lock is already acquired by someone else. +func FSReadLock(filePath string) (unlock func() error, err error) { + fileLock := flock.New(getPlatformLockFilePath(filePath)) + if err := fileLock.RLock(); err != nil { + return nil, trace.ConvertSystemError(err) + } + + return fileLock.Unlock, nil +} + // FSTryReadLockTimeout tries to grab read lock, it's doing it until locks is acquired, or timeout is expired, // or context is expired. func FSTryReadLockTimeout(ctx context.Context, filePath string, timeout time.Duration) (unlock func() error, err error) { diff --git a/lib/utils/fs_test.go b/lib/utils/fs_test.go index dc352df644fff..e4d192f3d2d79 100644 --- a/lib/utils/fs_test.go +++ b/lib/utils/fs_test.go @@ -18,9 +18,11 @@ package utils import ( "context" + "fmt" "os" "path/filepath" "runtime" + "sync/atomic" "testing" "time" @@ -326,6 +328,56 @@ func TestLocks(t *testing.T) { require.NoError(t, unlock()) } +// TestLockWithBlocking verifies that second lock call is blocked until first is released. +func TestLockWithBlocking(t *testing.T) { + var locked atomic.Bool + + lockFile := filepath.Join(os.TempDir(), ".lock") + t.Cleanup(func() { + require.NoError(t, os.Remove(lockFile)) + }) + + // Acquire first lock should not return any error. + unlock, err := FSWriteLock(lockFile) + require.NoError(t, err) + locked.Store(true) + + signal := make(chan struct{}) + errChan := make(chan error) + go func() { + signal <- struct{}{} + unlock, err := FSWriteLock(lockFile) + if err != nil { + errChan <- err + return + } + if locked.Load() { + errChan <- fmt.Errorf("first lock is still acquired, second lock must be blocking") + return + } + if err := unlock(); err != nil { + errChan <- err + return + } + signal <- struct{}{} + }() + + <-signal + // We have to wait till next lock is reached to ensure we block execution of goroutine. + // Since this is system call we can't track if the function reach blocking state already. + time.Sleep(100 * time.Millisecond) + locked.Store(false) + require.NoError(t, unlock()) + + select { + case err := <-errChan: + require.NoError(t, err) + case <-signal: + case <-time.After(5 * time.Second): + require.Fail(t, "second lock is not released") + } +} + func TestOverwriteFile(t *testing.T) { have := []byte("Sensitive Information") fName := filepath.Join(t.TempDir(), "teleport-overwrite-file-test") From ce0fe10e9ec40acf3c1706557cbc40cc4320ee92 Mon Sep 17 00:00:00 2001 From: Forrest <30576607+fspmarshall@users.noreply.github.com> Date: Tue, 15 Oct 2024 10:08:15 -0700 Subject: [PATCH 07/53] high level event exporter & cursor helpers (#47370) --- lib/events/export/cursor.go | 276 +++++++++++++++++ lib/events/export/cursor_test.go | 243 +++++++++++++++ lib/events/export/date_exporter.go | 18 ++ lib/events/export/exporter.go | 440 +++++++++++++++++++++++++++ lib/events/export/exporter_test.go | 163 ++++++++++ lib/events/export/helpers.go | 27 ++ tool/tctl/common/loadtest_command.go | 145 ++++----- 7 files changed, 1233 insertions(+), 79 deletions(-) create mode 100644 lib/events/export/cursor.go create mode 100644 lib/events/export/cursor_test.go create mode 100644 lib/events/export/exporter.go create mode 100644 lib/events/export/exporter_test.go create mode 100644 lib/events/export/helpers.go diff --git a/lib/events/export/cursor.go b/lib/events/export/cursor.go new file mode 100644 index 0000000000000..8dff088d00831 --- /dev/null +++ b/lib/events/export/cursor.go @@ -0,0 +1,276 @@ +/* + * 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 export + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "slices" + "strings" + "sync" + "time" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport" +) + +const ( + // completedName is the completed file name + completedName = "completed-chunks" + + // chunkSuffix is the suffix for per-chunk cursor files + chunkSuffix = ".chunk" +) + +// CursorConfig configures a cursor. +type CursorConfig struct { + // Dir is the cursor directory. This directory will be created if it does not exist + // and should not be used for any other purpose. + Dir string +} + +// CheckAndSetDefaults validates configuration and sets default values for optional parameters. +func (c *CursorConfig) CheckAndSetDefaults() error { + if c.Dir == "" { + return trace.BadParameter("missing required parameter Dir in CursorConfig") + } + + return nil +} + +// Cursor manages an event export cursor directory and keeps a copy of its state in-memory, +// improving the efficiency of updates by only writing diffs to disk. the cursor directory +// contains a sub-directory per date. each date's state is tracked using an append-only list +// of completed chunks, along with a per-chunk cursor file. cursor directories are not intended +// for true concurrent use, but concurrent use in the context of a graceful restart won't have +// any consequences more dire than duplicate events. +type Cursor struct { + cfg CursorConfig + mu sync.Mutex + state ExporterState +} + +// NewCursor creates a new cursor, loading any preexisting state from disk. +func NewCursor(cfg CursorConfig) (*Cursor, error) { + if err := cfg.CheckAndSetDefaults(); err != nil { + return nil, trace.Wrap(err) + } + + state, err := loadInitialState(cfg.Dir) + if err != nil { + return nil, trace.Wrap(err) + } + return &Cursor{ + cfg: cfg, + state: *state, + }, nil +} + +// GetState gets the current state as seen by this cursor. +func (c *Cursor) GetState() ExporterState { + c.mu.Lock() + defer c.mu.Unlock() + + return c.state.Clone() +} + +// Sync synchronizes the cursor's in-memory state with the provided state, writing any diffs to disk. +func (c *Cursor) Sync(newState ExporterState) error { + c.mu.Lock() + defer c.mu.Unlock() + + for d, s := range newState.Dates { + if err := c.syncDate(d, s); err != nil { + return trace.Wrap(err) + } + } + + for d := range c.state.Dates { + if _, ok := newState.Dates[d]; !ok { + if err := c.deleteDate(d); err != nil { + return trace.Wrap(err) + } + } + } + + return nil +} + +func (c *Cursor) syncDate(date time.Time, state DateExporterState) error { + // ensure date directory exists. the existence of the date directory + // is meaningful even if it contains no files. + dateDir := filepath.Join(c.cfg.Dir, date.Format(time.DateOnly)) + if err := os.MkdirAll(dateDir, teleport.SharedDirMode); err != nil { + return trace.ConvertSystemError(err) + } + + // open completed file in append mode + completedFile, err := os.OpenFile(filepath.Join(dateDir, completedName), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return trace.ConvertSystemError(err) + } + defer completedFile.Close() + + current, ok := c.state.Dates[date] + if !ok { + current = DateExporterState{ + Cursors: make(map[string]string), + } + } + defer func() { + c.state.Dates[date] = current + }() + + for _, chunk := range state.Completed { + if slices.Contains(current.Completed, chunk) { + // already written to disk + continue + } + + // add chunk to completed file + if _, err := fmt.Fprintln(completedFile, chunk); err != nil { + return trace.ConvertSystemError(err) + } + + // ensure chunk is flushed to disk successfully before removing the cursor file + // and updating in-memory state. + if err := completedFile.Sync(); err != nil { + return trace.ConvertSystemError(err) + } + + // delete cursor file if it exists + if err := os.Remove(filepath.Join(dateDir, chunk+chunkSuffix)); err != nil && !os.IsNotExist(err) { + return trace.ConvertSystemError(err) + } + + // update current state + current.Completed = append(current.Completed, chunk) + delete(current.Cursors, chunk) + } + + for chunk, cursor := range state.Cursors { + if current.Cursors[chunk] == cursor { + continue + } + + // write cursor file + if err := os.WriteFile(filepath.Join(dateDir, chunk+chunkSuffix), []byte(cursor), 0644); err != nil { + return trace.ConvertSystemError(err) + } + + // update current state + current.Cursors[chunk] = cursor + } + + return nil +} + +func (c *Cursor) deleteDate(date time.Time) error { + if _, ok := c.state.Dates[date]; !ok { + return nil + } + + // delete the date directory and all its contents + if err := os.RemoveAll(filepath.Join(c.cfg.Dir, date.Format(time.DateOnly))); err != nil { + return trace.ConvertSystemError(err) + } + + delete(c.state.Dates, date) + + return nil +} + +func loadInitialState(dir string) (*ExporterState, error) { + state := ExporterState{ + Dates: make(map[time.Time]DateExporterState), + } + // list subdirectories of the cursors v2 directory + entries, err := os.ReadDir(dir) + if err != nil { + if os.IsNotExist(err) { + return &state, nil + } + return nil, trace.ConvertSystemError(err) + } + + for _, entry := range entries { + if !entry.IsDir() { + // ignore non-directories + continue + } + + // attempt to parse dir name as date + date, err := time.Parse(time.DateOnly, entry.Name()) + if err != nil { + // ignore non-date directories + continue + } + + dateState := DateExporterState{ + Cursors: make(map[string]string), + } + + dateEntries, err := os.ReadDir(filepath.Join(dir, entry.Name())) + if err != nil { + return nil, trace.ConvertSystemError(err) + } + + for _, dateEntry := range dateEntries { + if dateEntry.IsDir() { + continue + } + + if dateEntry.Name() == completedName { + // load the completed file + b, err := os.ReadFile(filepath.Join(dir, entry.Name(), completedName)) + if err != nil { + return nil, trace.ConvertSystemError(err) + } + + // split the completed file into whitespace-separated chunks + dateState.Completed = strings.Fields(string(b)) + continue + } + + if !strings.HasSuffix(dateEntry.Name(), chunkSuffix) { + continue + } + + chunk := strings.TrimSuffix(dateEntry.Name(), chunkSuffix) + b, err := os.ReadFile(filepath.Join(dir, entry.Name(), dateEntry.Name())) + if err != nil { + return nil, trace.ConvertSystemError(err) + } + + if cc := bytes.TrimSpace(b); len(cc) != 0 { + dateState.Cursors[chunk] = string(cc) + } + } + + // note that some dates may not contain any chunks. we still want to track the + // fact that these dates have had their dirs initialized since that still indicates + // how far we've gotten in the export process. + state.Dates[date] = dateState + } + + return &state, nil +} diff --git a/lib/events/export/cursor_test.go b/lib/events/export/cursor_test.go new file mode 100644 index 0000000000000..9853141139345 --- /dev/null +++ b/lib/events/export/cursor_test.go @@ -0,0 +1,243 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package export + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport" +) + +// testState is a helper for easily/cleanly representing a cursor/exporter state as a literal when +// writing tests. the values of the inner mapping are generally strings, but some helpers support +// using []string for the completed key. +type testState map[string]map[string]any + +// writeRawState writes the test state to a directory structure. unlike `newState`, this helper +// is designed to inject non-conforming data, so it will accept non-date dir names and expects a string +// value for the completed key. +func writeRawState(t *testing.T, dir string, ts testState) { + for d, s := range ts { + dateDir := filepath.Join(dir, d) + require.NoError(t, os.MkdirAll(dateDir, teleport.SharedDirMode)) + + for k, v := range s { + fileName := filepath.Join(dateDir, k) + require.NoError(t, os.WriteFile(fileName, []byte(v.(string)), 0644)) + } + } +} + +// newState converts a testState into a real ExporterState. this helper only works +// with a well-formed testState. +func newState(t *testing.T, ts testState) ExporterState { + state := ExporterState{ + Dates: make(map[time.Time]DateExporterState), + } + for d, s := range ts { + date, err := time.Parse(time.DateOnly, d) + require.NoError(t, err) + + dateState := DateExporterState{ + Cursors: make(map[string]string), + Completed: []string{}, // avoids require.Equal rejecting nil slices as unequal to empty slices + } + + Entries: + for k, v := range s { + if k == completedName { + dateState.Completed = v.([]string) + continue Entries + } + dateState.Cursors[k] = v.(string) + } + + state.Dates[date] = dateState + } + return state +} + +func syncAndVerifyState(t *testing.T, cursor *Cursor, ts testState) { + state := newState(t, ts) + + // sync the state + require.NoError(t, cursor.Sync(state)) + + // verify in-memory state is as expected + require.Equal(t, state, cursor.GetState()) + + // attempt to load the state from disk + loaded, err := NewCursor(CursorConfig{ + Dir: cursor.cfg.Dir, + }) + require.NoError(t, err) + + // verify that the loaded state is the same as the original state + require.Equal(t, state, loaded.GetState()) +} + +// verifyRawState asserts that the raw state on disk matches the provided test state. chunk names +// need to be suffixed to match and the completed key should be a string. +func verifyRawState(t *testing.T, dir string, ts testState) { + for d, s := range ts { + dateDir := filepath.Join(dir, d) + for k, v := range s { + fileName := filepath.Join(dateDir, k) + data, err := os.ReadFile(fileName) + require.NoError(t, err) + + require.Equal(t, v.(string), string(data)) + } + } +} + +// TestCursorBasics verifies basic syncing/loading of cursor state. +func TestCursorBasics(t *testing.T) { + dir := t.TempDir() + + cursor, err := NewCursor(CursorConfig{ + Dir: dir, + }) + require.NoError(t, err) + state := cursor.GetState() + require.True(t, state.IsEmpty()) + + // sync and verify a typical state + syncAndVerifyState(t, cursor, testState{ + "2021-01-01": { + completedName: []string{"chunk1", "chunk2"}, + }, + "2021-01-02": { + completedName: []string{"chunk1", "chunk2"}, + "chunk3": "cursor1", + "chunk4": "cursor2", + }, + "2021-01-03": { + "chunk3": "cursor1", + "chunk4": "cursor2", + }, + "2021-01-04": {}, + }) + + verifyRawState(t, dir, testState{ + "2021-01-01": { + completedName: "chunk1\nchunk2\n", + }, + "2021-01-02": { + completedName: "chunk1\nchunk2\n", + "chunk3" + chunkSuffix: "cursor1", + "chunk4" + chunkSuffix: "cursor2", + }, + "2021-01-03": { + "chunk3" + chunkSuffix: "cursor1", + "chunk4" + chunkSuffix: "cursor2", + }, + "2021-01-04": {}, + }) + + // sync and verify updated state + syncAndVerifyState(t, cursor, testState{ + "2021-01-01": { + completedName: []string{"chunk1", "chunk2", "chunk3"}, + }, + "2021-01-02": { + completedName: []string{"chunk1", "chunk2"}, + "chunk3": "cursor1", + "chunk4": "cursor2", + "chunk5": "cursor4", + }, + "2021-01-03": { + "chunk3": "cursor2", + "chunk4": "cursor3", + }, + "2021-01-04": {}, + "2021-01-05": {}, + }) + + verifyRawState(t, dir, testState{ + "2021-01-01": { + completedName: "chunk1\nchunk2\nchunk3\n", + }, + "2021-01-02": { + completedName: "chunk1\nchunk2\n", + "chunk3" + chunkSuffix: "cursor1", + "chunk4" + chunkSuffix: "cursor2", + "chunk5" + chunkSuffix: "cursor4", + }, + "2021-01-03": { + "chunk3" + chunkSuffix: "cursor2", + "chunk4" + chunkSuffix: "cursor3", + }, + "2021-01-04": {}, + "2021-01-05": {}, + }) + + // sync & verify heavily truncated state + syncAndVerifyState(t, cursor, testState{ + "2021-01-05": {}, + }) + + verifyRawState(t, dir, testState{ + "2021-01-05": {}, + }) +} + +func TestCursorBadState(t *testing.T) { + dir := t.TempDir() + + writeRawState(t, dir, testState{ + "2021-01-01": { + completedName: "\n\nchunk1\n\n", // extra whitespace should be ignored + }, + "not-a-date": { + completedName: "chunk1", + "no-suffix": "cursor1", // unknown suffix should be ignored + }, + "2021-01-02": { + completedName: "\n", // whitespace-only completed file should count as empty + "chunk1" + chunkSuffix: "cursor1", + "chunk2" + chunkSuffix: "cursor2\n", // extra whitespace should be ignored + "chunk3" + chunkSuffix: " ", // whitespace-only cursor file should count as empty + "no-suffix": "cursor3", // unknown suffix should be ignored + }, + }) + + expected := newState(t, testState{ + "2021-01-01": { + completedName: []string{"chunk1"}, + }, + "2021-01-02": { + "chunk1": "cursor1", + "chunk2": "cursor2", + }, + }) + + loaded, err := NewCursor(CursorConfig{ + Dir: dir, + }) + require.NoError(t, err) + + // verify that the loaded state is the same as expected state + require.Equal(t, expected, loaded.GetState()) +} diff --git a/lib/events/export/date_exporter.go b/lib/events/export/date_exporter.go index cee3e3a36278c..698c67d8c156e 100644 --- a/lib/events/export/date_exporter.go +++ b/lib/events/export/date_exporter.go @@ -117,6 +117,24 @@ type DateExporterState struct { Cursors map[string]string } +// IsEmpty returns true if no state is defined. +func (s *DateExporterState) IsEmpty() bool { + return len(s.Completed) == 0 && len(s.Cursors) == 0 +} + +// Clone returns a deep copy of the date exporter state. +func (s *DateExporterState) Clone() DateExporterState { + cloned := DateExporterState{ + Completed: make([]string, len(s.Completed)), + Cursors: make(map[string]string, len(s.Cursors)), + } + copy(cloned.Completed, s.Completed) + for chunk, cursor := range s.Cursors { + cloned.Cursors[chunk] = cursor + } + return cloned +} + // DateExporter is a utility for exporting events for a given date using the chunked event export APIs. Note that // it is specifically designed to prioritize performance and ensure that events aren't missed. It may not yield events // in time order, and does not provide a mechanism to decide when export for a given date should be considered complete, diff --git a/lib/events/export/exporter.go b/lib/events/export/exporter.go new file mode 100644 index 0000000000000..9506a80b81adc --- /dev/null +++ b/lib/events/export/exporter.go @@ -0,0 +1,440 @@ +/* + * 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 export + +import ( + "context" + "log/slog" + "slices" + "sync" + "time" + + "github.com/gravitational/trace" + "golang.org/x/time/rate" + + auditlogpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/auditlog/v1" + "github.com/gravitational/teleport/api/utils/retryutils" + "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/teleport/lib/utils/interval" +) + +type ExporterState struct { + // Dates is a map of dates to their respective state. Note that an empty + // state for a date is still meaningful and either indicates that the date + // itself contains no events, or that no progress has been made against that + // date yet. + Dates map[time.Time]DateExporterState +} + +// IsEmpty returns true if no state is defined. +func (s *ExporterState) IsEmpty() bool { + return len(s.Dates) == 0 +} + +// Clone creates a deep copy of the exporter state. +func (s *ExporterState) Clone() ExporterState { + out := ExporterState{ + Dates: make(map[time.Time]DateExporterState, len(s.Dates)), + } + for date, state := range s.Dates { + out.Dates[date] = state.Clone() + } + return out +} + +// ExporterConfig configured an exporter. +type ExporterConfig struct { + // Client is the audit event client used to fetch and export events. + Client Client + // StartDate is the date from which to start exporting events. + StartDate time.Time + // Export is the callback used to export events. Must be safe for concurrent use if + // the Concurrency parameter is greater than 1. + Export func(ctx context.Context, event *auditlogpb.ExportEventUnstructured) error + // OnIdle is an optional callback that gets invoked periodically when the exporter is idle. Note that it is + // safe to close the exporter or inspect its state from within this callback, but waiting on the exporter's + // Done channel within this callback will deadlock. This callback is an asynchronous signal and additional + // events may be discovered concurrently with its invocation. + OnIdle func(ctx context.Context) + // PreviousState is an optional parameter used to resume from a previous date export run. + PreviousState ExporterState + // Concurrency sets the maximum number of event chunks that will be processed concurrently + // for a given date (defaults to 1). Note that the total number of inflight chunk processing + // may be up to Conurrency * (BacklogSize + 1). + Concurrency int + // BacklogSize optionally overrides the default size of the export backlog (i.e. the number of + // previous dates for which polling continues after initial idleness). default is 1. + BacklogSize int + // MaxBackoff optionally overrides the default maximum backoff applied when errors are hit. + MaxBackoff time.Duration + // PollInterval optionally overrides the default poll interval used to fetch event chunks. + PollInterval time.Duration +} + +// CheckAndSetDefaults validates configuration and sets default values for optional parameters. +func (cfg *ExporterConfig) CheckAndSetDefaults() error { + if cfg.Client == nil { + return trace.BadParameter("missing required parameter Client in ExporterConfig") + } + if cfg.StartDate.IsZero() { + return trace.BadParameter("missing required parameter StartDate in ExporterConfig") + } + if cfg.Export == nil { + return trace.BadParameter("missing required parameter Export in ExporterConfig") + } + if cfg.Concurrency == 0 { + cfg.Concurrency = 1 + } + if cfg.BacklogSize == 0 { + cfg.BacklogSize = 1 + } + if cfg.MaxBackoff == 0 { + cfg.MaxBackoff = 90 * time.Second + } + if cfg.PollInterval == 0 { + cfg.PollInterval = 16 * time.Second + } + return nil +} + +// Exporter is a utility for exporting events starting from a given date using the chunked event export APIs. Note that +// it is specifically designed to prioritize performance and ensure that events aren't missed. Events may not be yielded +// in time order. Export of events is performed by consuming all currently available events for a given date, then moving +// to the next date. In order to account for replication delays, a backlog of previous dates are also polled. +type Exporter struct { + cfg ExporterConfig + mu sync.Mutex + current *DateExporter + currentDate time.Time + previous map[time.Time]*DateExporter + cancel context.CancelFunc + idle chan struct{} + done chan struct{} +} + +// NewExporter creates a new exporter and begins background processing of events. Processing will continue indefinitely +// until Exporter.Close is called. +func NewExporter(cfg ExporterConfig) (*Exporter, error) { + if err := cfg.CheckAndSetDefaults(); err != nil { + return nil, trace.Wrap(err) + } + + ctx, cancel := context.WithCancel(context.Background()) + + e := &Exporter{ + cfg: cfg, + cancel: cancel, + idle: make(chan struct{}, 1), + done: make(chan struct{}), + previous: make(map[time.Time]*DateExporter, len(cfg.PreviousState.Dates)), + } + + // start initial event processing + var initError error + e.withLock(func() { + var resumed int + for date, state := range cfg.PreviousState.Dates { + date = normalizeDate(date) + if cfg.StartDate.After(date) { + // skip dates that are older than the start date + continue + } + if err := e.resumeExportLocked(ctx, date, state); err != nil { + initError = err + return + } + slog.InfoContext(ctx, "resumed event export", "date", date.Format(time.DateOnly)) + resumed++ + } + + if resumed == 0 { + // no previous state at/after start date, start at the beginning + if err := e.startExportLocked(ctx, cfg.StartDate); err != nil { + initError = err + return + } + slog.InfoContext(ctx, "started event export", "date", cfg.StartDate.Format(time.DateOnly)) + } + }) + if initError != nil { + e.Close() + return nil, trace.Wrap(initError) + } + + go e.run(ctx) + return e, nil + +} + +// Close terminates all event processing. Note that shutdown is asynchronous. Any operation that needs to wait for export to fully +// terminate should wait on Done after calling Close. +func (e *Exporter) Close() { + e.cancel() +} + +// Done provides a channel that will be closed when the exporter has completed processing all inflight dates. When saving the +// final state of the exporter for future resumption, this channel must be waited upon before state is loaded. Note that the date +// exporter never termiantes unless Close is called, so waiting on Done is only meaningful after Close has been called. +func (e *Exporter) Done() <-chan struct{} { + return e.done +} + +// GetState loads the current state of the exporter. Note that there may be concurrent export operations +// in progress, meaning that by the time state is observed it may already be outdated. +func (e *Exporter) GetState() ExporterState { + e.mu.Lock() + defer e.mu.Unlock() + state := ExporterState{ + Dates: make(map[time.Time]DateExporterState, len(e.previous)+1), + } + + // Add the current date state. + state.Dates[e.currentDate] = e.current.GetState() + + for date, exporter := range e.previous { + state.Dates[date] = exporter.GetState() + } + + return state +} + +func (e *Exporter) run(ctx context.Context) { + defer func() { + // on exit we close all date exporters and block on their completion + // before signaling that we are done. + var doneChans []<-chan struct{} + e.withLock(func() { + doneChans = make([]<-chan struct{}, 0, len(e.previous)+1) + e.current.Close() + doneChans = append(doneChans, e.current.Done()) + for _, exporter := range e.previous { + exporter.Close() + doneChans = append(doneChans, exporter.Done()) + } + }) + + for _, done := range doneChans { + <-done + } + close(e.done) + }() + + poll := interval.New(interval.Config{ + Duration: e.cfg.PollInterval, + FirstDuration: utils.FullJitter(e.cfg.PollInterval / 2), + Jitter: retryutils.NewSeventhJitter(), + }) + defer poll.Stop() + + logLimiter := rate.NewLimiter(rate.Every(time.Minute), 1) + + for { + idle, err := e.poll(ctx) + if err != nil && logLimiter.Allow() { + var dates []string + e.withLock(func() { + dates = make([]string, 0, len(e.previous)+1) + dates = append(dates, e.currentDate.Format(time.DateOnly)) + for date := range e.previous { + dates = append(dates, date.Format(time.DateOnly)) + } + }) + slices.Sort(dates) + slog.WarnContext(ctx, "event export poll failed", "error", err, "dates", dates) + } + + if idle && e.cfg.OnIdle != nil { + e.cfg.OnIdle(ctx) + } + + select { + case <-e.idle: + case <-poll.Next(): + case <-ctx.Done(): + return + } + } +} + +// poll advances the exporter to the next date if the current date is idle and in the past, and prunes any idle exporters that +// are outside of the target backlog range. if the exporter is caught up with the current date and all sub-exporters are idle, +// poll returns true. otherwise, poll returns false. +func (e *Exporter) poll(ctx context.Context) (bool, error) { + e.mu.Lock() + defer e.mu.Unlock() + + var caughtUp bool + if e.current.IsIdle() { + if normalizeDate(time.Now()).After(e.currentDate) { + nextDate := e.currentDate.AddDate(0, 0, 1) + // current date is idle and in the past, advance to the next date + if err := e.startExportLocked(ctx, nextDate); err != nil { + return false, trace.Wrap(err) + } + slog.InfoContext(ctx, "advanced to next event export target", "date", nextDate.Format(time.DateOnly)) + } else { + caughtUp = true + } + } + + // prune any dangling exporters that appear idle + e.pruneBacklogLocked(ctx) + + if !caughtUp { + return false, nil + } + + // check if all backlog exporters are idle + for _, exporter := range e.previous { + if !exporter.IsIdle() { + return false, nil + } + } + + // all exporters are idle and we are caught up with the current date + return true, nil +} + +// pruneBacklogLocked prunes any idle exporters that are outside of the target backlog range. +func (e *Exporter) pruneBacklogLocked(ctx context.Context) { + if len(e.previous) <= e.cfg.BacklogSize { + return + } + + dates := make([]time.Time, 0, len(e.previous)) + for date := range e.previous { + dates = append(dates, date) + } + + // sort dates with most recent first + slices.SortFunc(dates, func(a, b time.Time) int { + if a.After(b) { + return -1 + } + if b.After(a) { + return 1 + } + return 0 + }) + + // close any idle exporters that are older than the backlog size + for _, date := range dates[e.cfg.BacklogSize:] { + if !e.previous[date].IsIdle() { + continue + } + + e.previous[date].Close() + + doneC := e.previous[date].Done() + + var closing bool + e.withoutLock(func() { + select { + case <-doneC: + case <-ctx.Done(): + closing = true + } + }) + + if closing { + return + } + + delete(e.previous, date) + + slog.InfoContext(ctx, "halted historical event export", "date", date.Format(time.DateOnly)) + } +} + +// startExport starts export of events for the given date. +func (e *Exporter) startExportLocked(ctx context.Context, date time.Time) error { + return e.resumeExportLocked(ctx, date, DateExporterState{}) +} + +// resumeExport resumes export of events for the given date with the given state. +func (e *Exporter) resumeExportLocked(ctx context.Context, date time.Time, state DateExporterState) error { + date = normalizeDate(date) + + // check if the date is already being exported + if _, ok := e.previous[date]; ok || e.currentDate.Equal(date) { + return nil + } + + onIdle := func(ctx context.Context) { + var isCurrent bool + e.withLock(func() { + isCurrent = e.currentDate.Equal(date) + }) + if !isCurrent { + // idle callbacks from an exporter in the backlog + // can be ignored. + return + } + + // current date is idle, wake up the poll loop + select { + case e.idle <- struct{}{}: + default: + } + } + + // set up exporter + exporter, err := NewDateExporter(DateExporterConfig{ + Client: e.cfg.Client, + Date: date, + Export: e.cfg.Export, + OnIdle: onIdle, + PreviousState: state, + Concurrency: e.cfg.Concurrency, + MaxBackoff: e.cfg.MaxBackoff, + PollInterval: e.cfg.PollInterval, + }) + if err != nil { + return trace.Wrap(err) + } + + // if a current export is in progress and is newer than this export, + // add this export to the backlog. + if e.current != nil && e.currentDate.After(date) { + // historical export is being started, add to backlog + e.previous[date] = exporter + return nil + } + + // bump previous export to backlog + if e.current != nil { + e.previous[e.currentDate] = e.current + } + e.current = exporter + e.currentDate = date + + return nil +} + +func (e *Exporter) withLock(fn func()) { + e.mu.Lock() + defer e.mu.Unlock() + fn() +} + +func (e *Exporter) withoutLock(fn func()) { + e.mu.Unlock() + defer e.mu.Lock() + fn() +} diff --git a/lib/events/export/exporter_test.go b/lib/events/export/exporter_test.go new file mode 100644 index 0000000000000..436cd3ecccf52 --- /dev/null +++ b/lib/events/export/exporter_test.go @@ -0,0 +1,163 @@ +/* + * 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 export + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + auditlogpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/auditlog/v1" +) + +const day = time.Hour * 24 + +// TestExporterBasics tests the basic functionality of the exporter with and without random flake. +func TestExporterBasics(t *testing.T) { + t.Parallel() + + now := normalizeDate(time.Now()) + startDate := now.Add(-7 * day) + + for _, randomFlake := range []bool{false, true} { + + // empty case verified export of a time range larger than backlog size with no events in it. + t.Run(fmt.Sprintf("case=empty,randomFlake=%v", randomFlake), func(t *testing.T) { + t.Parallel() + clt := newFakeClient() + clt.setRandomFlake(randomFlake) + + testExportAll(t, exportTestCase{ + clt: clt, + startDate: startDate, + expected: []*auditlogpb.ExportEventUnstructured{}, + }) + }) + + // sparse case verifies export of a time range with gaps larger than the backlog size. + t.Run(fmt.Sprintf("case=sparse,randomFlake=%v", randomFlake), func(t *testing.T) { + t.Parallel() + + clt := newFakeClient() + clt.setRandomFlake(randomFlake) + + var allEvents []*auditlogpb.ExportEventUnstructured + allEvents = append(allEvents, addEvents(t, clt, startDate, 1, 1)...) + allEvents = append(allEvents, addEvents(t, clt, startDate.Add(4*day), 3, 2)...) + + testExportAll(t, exportTestCase{ + clt: clt, + startDate: startDate, + expected: allEvents, + }) + }) + + // dense case verifies export of a time range with many events in every date. + t.Run(fmt.Sprintf("case=dense,randomFlake=%v", randomFlake), func(t *testing.T) { + t.Parallel() + + clt := newFakeClient() + clt.setRandomFlake(randomFlake) + + var allEvents []*auditlogpb.ExportEventUnstructured + allEvents = append(allEvents, addEvents(t, clt, startDate, 100, 1)...) + allEvents = append(allEvents, addEvents(t, clt, startDate.Add(day), 50, 2)...) + allEvents = append(allEvents, addEvents(t, clt, startDate.Add(2*day), 5, 20)...) + allEvents = append(allEvents, addEvents(t, clt, startDate.Add(3*day), 20, 5)...) + allEvents = append(allEvents, addEvents(t, clt, startDate.Add(4*day), 14, 7)...) + allEvents = append(allEvents, addEvents(t, clt, startDate.Add(5*day), 7, 14)...) + allEvents = append(allEvents, addEvents(t, clt, startDate.Add(6*day), 1, 100)...) + + testExportAll(t, exportTestCase{ + clt: clt, + startDate: startDate, + expected: allEvents, + }) + }) + } +} + +// addEvents is a helper for generating events in tests. It both inserts the specified event chunks/counts into the fake client +// and returns the generated events for comparison. +func addEvents(t *testing.T, clt *fakeClient, date time.Time, chunks, eventsPerChunk int) []*auditlogpb.ExportEventUnstructured { + var allEvents []*auditlogpb.ExportEventUnstructured + for i := 0; i < chunks; i++ { + chunk := makeEventChunk(t, date, eventsPerChunk) + allEvents = append(allEvents, chunk...) + clt.addChunk(date.Format(time.DateOnly), uuid.NewString(), chunk) + } + + return allEvents +} + +type exportTestCase struct { + clt Client + startDate time.Time + expected []*auditlogpb.ExportEventUnstructured +} + +// testExportAll verifies that the expected events are exported by the exporter given +// the supplied client state. +func testExportAll(t *testing.T, tc exportTestCase) { + var exportedMu sync.Mutex + var exported []*auditlogpb.ExportEventUnstructured + + exportFn := func(ctx context.Context, event *auditlogpb.ExportEventUnstructured) error { + exportedMu.Lock() + defer exportedMu.Unlock() + exported = append(exported, event) + return nil + } + + getExported := func() []*auditlogpb.ExportEventUnstructured { + exportedMu.Lock() + defer exportedMu.Unlock() + return append([]*auditlogpb.ExportEventUnstructured(nil), exported...) + } + + var idleOnce sync.Once + idleCh := make(chan struct{}) + + exporter, err := NewExporter(ExporterConfig{ + Client: tc.clt, + StartDate: tc.startDate, + Export: exportFn, + OnIdle: func(_ context.Context) { idleOnce.Do(func() { close(idleCh) }) }, + Concurrency: 2, + BacklogSize: 2, + MaxBackoff: 600 * time.Millisecond, + PollInterval: 200 * time.Millisecond, + }) + require.NoError(t, err) + defer exporter.Close() + + timeout := time.After(30 * time.Second) + select { + case <-idleCh: + case <-timeout: + require.FailNow(t, "timeout waiting for exporter to become idle") + } + + require.ElementsMatch(t, tc.expected, getExported()) +} diff --git a/lib/events/export/helpers.go b/lib/events/export/helpers.go new file mode 100644 index 0000000000000..35f7fe0997fd5 --- /dev/null +++ b/lib/events/export/helpers.go @@ -0,0 +1,27 @@ +/* + * 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 export + +import "time" + +// normalizeDate normalizes a timestamp to the beginning of the day in UTC. +func normalizeDate(t time.Time) time.Time { + t = t.UTC() + return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC) +} diff --git a/tool/tctl/common/loadtest_command.go b/tool/tctl/common/loadtest_command.go index 2aa72c816dee1..a764d954103db 100644 --- a/tool/tctl/common/loadtest_command.go +++ b/tool/tctl/common/loadtest_command.go @@ -19,6 +19,7 @@ package common import ( "context" "fmt" + "log/slog" "os" "os/signal" "runtime" @@ -31,12 +32,12 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" log "github.com/sirupsen/logrus" - "google.golang.org/protobuf/types/known/timestamppb" auditlogpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/auditlog/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/cache" + "github.com/gravitational/teleport/lib/events/export" "github.com/gravitational/teleport/lib/service/servicecfg" "github.com/gravitational/teleport/lib/utils" ) @@ -83,7 +84,7 @@ func (c *LoadtestCommand) Initialize(app *kingpin.Application, config *servicecf c.auditEvents = loadtest.Command("export-audit-events", "Bulk export audit events").Hidden() c.auditEvents.Flag("date", "Date to dump events for").StringVar(&c.date) - c.auditEvents.Flag("cursor", "Cursor to start from").StringVar(&c.cursor) + c.auditEvents.Flag("cursor", "Specify an optional cursor directory").StringVar(&c.cursor) } // TryRun takes the CLI command as an argument (like "loadtest node-heartbeats") and executes it. @@ -303,6 +304,9 @@ func printEvent(ekind string, rsc types.Resource) { } func (c *LoadtestCommand) AuditEvents(ctx context.Context, client *authclient.Client) error { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + date := time.Now() if c.date != "" { var err error @@ -315,6 +319,8 @@ func (c *LoadtestCommand) AuditEvents(ctx context.Context, client *authclient.Cl outch := make(chan *auditlogpb.ExportEventUnstructured, 1024) defer close(outch) + var eventsProcessed atomic.Uint64 + go func() { for event := range outch { s, err := utils.FastMarshal(event.Event.Unstructured) @@ -322,96 +328,77 @@ func (c *LoadtestCommand) AuditEvents(ctx context.Context, client *authclient.Cl panic(err) } fmt.Println(string(s)) + eventsProcessed.Add(1) } }() - chunksProcessed := make(map[string]struct{}) + exportFn := func(ctx context.Context, event *auditlogpb.ExportEventUnstructured) error { + select { + case outch <- event: + case <-ctx.Done(): + return ctx.Err() + } + return nil + } -Outer: - for { - chunks := client.GetEventExportChunks(ctx, &auditlogpb.GetEventExportChunksRequest{ - Date: timestamppb.New(date), + var cursor *export.Cursor + if c.cursor != "" { + var err error + cursor, err = export.NewCursor(export.CursorConfig{ + Dir: c.cursor, }) + if err != nil { + return trace.Wrap(err) + } + } - Chunks: - for chunks.Next() { - if _, ok := chunksProcessed[chunks.Item().Chunk]; ok { - log.WithFields(log.Fields{ - "date": date.Format(time.DateOnly), - "chunk": chunks.Item().Chunk, - }).Info("skipping already processed chunk") - continue Chunks - } - - var cursor string - ProcessChunk: - for { - - eventStream := client.ExportUnstructuredEvents(ctx, &auditlogpb.ExportUnstructuredEventsRequest{ - Date: timestamppb.New(date), - Chunk: chunks.Item().Chunk, - Cursor: cursor, - }) + var state export.ExporterState + if cursor != nil { + state = cursor.GetState() + } - Events: - for eventStream.Next() { - cursor = eventStream.Item().Cursor - select { - case outch <- eventStream.Item(): - continue Events - default: - log.Warn("backpressure in event stream") - } + exporter, err := export.NewExporter(export.ExporterConfig{ + Client: client, + StartDate: date, + PreviousState: state, + Export: exportFn, + Concurrency: 3, + BacklogSize: 1, + }) + if err != nil { + return trace.Wrap(err) + } + defer exporter.Close() - select { - case outch <- eventStream.Item(): - case <-ctx.Done(): - return nil - } - } + if cursor != nil { + go func() { + syncTicker := time.NewTicker(time.Millisecond * 666) + defer syncTicker.Stop() - if err := eventStream.Done(); err != nil { - log.WithFields(log.Fields{ - "date": date.Format(time.DateOnly), - "chunk": chunks.Item().Chunk, - "error": err, - }).Error("event stream failed, will attempt to reestablish") - continue ProcessChunk + for { + select { + case <-ctx.Done(): + return + case <-syncTicker.C: + cursor.Sync(exporter.GetState()) } - - chunksProcessed[chunks.Item().Chunk] = struct{}{} - break ProcessChunk } - } + }() + } - if err := chunks.Done(); err != nil { - log.WithFields(log.Fields{ - "date": date.Format(time.DateOnly), - "error": err, - }).Error("event chunk stream failed, will attempt to reestablish") - continue Outer - } + logTicker := time.NewTicker(time.Minute) - nextDate := date.AddDate(0, 0, 1) - if nextDate.After(time.Now()) { - delay := utils.SeventhJitter(time.Second * 7) - log.WithFields(log.Fields{ - "date": date.Format(time.DateOnly), - "delay": delay, - }).Info("finished processing known event chunks for current date, will re-poll after delay") - select { - case <-time.After(delay): - case <-ctx.Done(): - return nil - } - continue Outer + var prevEventsProcessed uint64 + for { + select { + case <-ctx.Done(): + return nil + case <-logTicker.C: + processed := eventsProcessed.Load() + slog.InfoContext(ctx, "event processing", "total", processed, "per_minute", processed-prevEventsProcessed) + prevEventsProcessed = processed + case <-exporter.Done(): + return trace.Errorf("exporter exited unexpected with state: %+v", exporter.GetState()) } - - log.WithFields(log.Fields{ - "date": date.Format(time.DateOnly), - "next": nextDate.Format(time.DateOnly), - }).Info("finished processing known event chunks for historical date, moving to next") - date = nextDate - clear(chunksProcessed) } } From 3d83c1f17698c91f95ee9851cd1262370e2528aa Mon Sep 17 00:00:00 2001 From: Steven Martin Date: Tue, 15 Oct 2024 14:58:44 -0400 Subject: [PATCH 08/53] [v14] docs: update database faq (#47591) * docs: update database faq * docs: verbiage update on db faq Co-authored-by: Paul Gottschling --------- Co-authored-by: Paul Gottschling --- .../pages/enroll-resources/database-access/faq.mdx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/pages/enroll-resources/database-access/faq.mdx b/docs/pages/enroll-resources/database-access/faq.mdx index 762048a8e8507..59ab92704e03f 100644 --- a/docs/pages/enroll-resources/database-access/faq.mdx +++ b/docs/pages/enroll-resources/database-access/faq.mdx @@ -16,6 +16,7 @@ The Teleport Database Service currently supports the following protocols: - MongoDB - MySQL - Oracle +- OpenSearch - PostgreSQL - Redis - Snowflake @@ -58,17 +59,14 @@ This is useful when the Teleport Web UI is running behind an L7 load balancer (e.g. ALB in AWS), in which case the PostgreSQL/MySQL proxy needs to be exposed on a plain TCP load balancer (e.g. NLB in AWS). +Using [TLS routing](../../reference/architecture/tls-routing.mdx) for the Teleport Proxy Service allows for all +database connections with the web public address. + -In Teleport Enterprise Cloud, the Proxy Service uses the following ports for -Database Service client traffic: - -|Configuration setting|Port| -|---|---| -|`mysql_public_addr`|`3036`| -|`postgres_public_addr`|`443`| -|`mongo_public_addr`|`443`| +In Teleport Enterprise (Cloud), database connections use the web public address +since [TLS routing](../../reference/architecture/tls-routing.mdx) is applied. From cbaab9d66c6bb7d74d4479f81dd8c46a95511afc Mon Sep 17 00:00:00 2001 From: Paul Gottschling Date: Tue, 15 Oct 2024 15:10:49 -0400 Subject: [PATCH 09/53] Add `tctl request create` to the reference (#47407) --- docs/pages/reference/cli/tctl.mdx | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/pages/reference/cli/tctl.mdx b/docs/pages/reference/cli/tctl.mdx index 1f06ae3064fe9..b18943770aa97 100644 --- a/docs/pages/reference/cli/tctl.mdx +++ b/docs/pages/reference/cli/tctl.mdx @@ -825,6 +825,45 @@ $ tctl request approve [token] $ tctl request approve request-id-1, request-id-2 ``` +## tctl request create + +Create a pending Access Request. + +```code +$ tctl request create +``` + +### Arguments + +- `` - Name of target user (required). + +### Flags + +| Name | Default Value(s) | Allowed Value(s) | Description | +| - | - | - | - | +|`roles`|none|Comma-separated list of strings|Roles to be requested| +|`resource`|none|Comma-separated list of strings|Resource IDs to be requested| +|`reason`|none|String|Optional reason message| +|`dry-run`|none|Boolean|Don't actually generate the Access Request| + +Use the `dry-run` flag if you want to validate whether Teleport can create an +Access Request for the user in the `username` argument, given the user's static +roles. + +### Global flags + +These flags are available for all commands `--debug, --config`. Run +`tctl help ` or see the [Global Flags section](#tctl-global-flags). + +### Examples + +Create an Access Request for user `myuser` for the `prod` role, providing a +reason: + +```code +$ tctl request create myuser --roles=prod --reason="Fix an outage" +``` + ## tctl request deny Denies a user's request: From 6ab05bb500d1b9f1bef8b240b864cdda500f9306 Mon Sep 17 00:00:00 2001 From: Paul Gottschling Date: Tue, 15 Oct 2024 15:29:33 -0400 Subject: [PATCH 10/53] [v14] Add a guide to metrics for monitoring Teleport (#47411) * Add a guide to metrics for monitoring Teleport Closes #40664 This change turns the Metrics guide in `admin-guides` into a conceptual guide to the most important metrics for monitoring a Teleport cluster. Since Agent metrics have inconsistent comprehensiveness across Teleport services--and to reduce the scope of this change--this guide focuses on self-hosted clusters. To make this a conceptual guide instead of a reference, this change removes the reference table from the `admin-guides` metrics page. There is already a table in the dedicated metrics reference guide. Note that, while the new metrics guide is specific to self-hosted clusters, this change does not move the guide to the subsection of Admin Guides related to self-hosting Teleport. Doing this would mean having one subsection of Admin Guides for diagnostics-related guides and one subsection for self-hosted-specific diagnostics, which is potentially confusing. We may also want to add Agent-specific metrics eventually. Finally, this change does not include alert thresholds for the metrics it describes. We can define these in a subsequent change. * Respond to evanfreed feedback - Describe `backend_write_requests_failed_precondition_total` - Include the precondition metric in the write availability formula. - Turn the `registered_servers` discussion into a discussion of Teleport instance version, since it's not possible to group this metric by service and subtract the count of Auth Service/Proxy Service instances from the count of all registered services. --- .../management/diagnostics/metrics.mdx | 199 +++++++++++++++++- 1 file changed, 189 insertions(+), 10 deletions(-) diff --git a/docs/pages/admin-guides/management/diagnostics/metrics.mdx b/docs/pages/admin-guides/management/diagnostics/metrics.mdx index bbea5111e27bd..3888390bcd795 100644 --- a/docs/pages/admin-guides/management/diagnostics/metrics.mdx +++ b/docs/pages/admin-guides/management/diagnostics/metrics.mdx @@ -1,11 +1,20 @@ --- -title: Metrics -description: How to enable and consume metrics +title: Key Metrics for Self-Hosted Clusters +description: Describes important metrics to monitor if you are self-hosting Teleport. +tocDepth: 3 --- -Teleport exposes metrics for all of its components, helping you get insight -into the state of your cluster. This guide explains the metrics that you can -collect from your Teleport cluster. +This guide explains the metrics you should use to get started monitoring your +self-hosted Teleport cluster, focusing on metrics reported by the Auth Service +and Proxy Service. If you use Teleport Enterprise (Cloud), the Teleport team +monitors and responds to these metrics for you. + +For a reference of all available metrics, see the [Teleport Metrics +Reference](../../../reference/monitoring/metrics.mdx). + +This guide assumes that you already monitor compute resources on all instances +that run the Teleport Auth Service and Proxy Service (e.g., CPU, memory, disk, +bandwidth, and open file descriptors). ## Enabling metrics @@ -14,12 +23,182 @@ collect from your Teleport cluster. This will enable the `http://127.0.0.1:3000/metrics` endpoint, which serves the metrics that Teleport tracks. It is compatible with [Prometheus](https://prometheus.io/) collectors. -The following metrics are available: +## Backend operations + +A Teleport cluster cannot function if the Auth Service does not have a healthy +cluster state backend. You need to track the ability of the Auth Service to read +from and write to its backend. + +The Auth Service can connect to [several possible +backends](../../../reference/backends.mdx). In addition to Teleport backend +metrics, you should set up monitoring for your backend of choice so that, if +these metrics show problematic values, you can correlate them with metrics on +your backend infrastructure. + +### Backend operation throughput and availability + +On each backend operation, the Auth Service increments a metric. Backend +operation metrics have the following format: + +```text +teleport_backend_[_failed]_total +``` + +If an operation results in an error, the Auth Service adds the `_failed` segment +to the metric name. For example, successfully creating a record increments the +`teleport_backend_write_requests_total` metric. If the create operation fails, +the Auth Service increments `teleport_backend_write_requests_failed_total` +instead. + +The following backend operation metrics are available: + +|Operation|Incremented metric name| +|---|---| +|Create an item|`write_requests`| +|Modify an item, creating it if it does not exist|`write_requests`| +|Update an item|`write_requests`| +|Conditionally update an item if versions match|`write_requests`| +|List a range of items|`batch_read_requests`| +|Get a single item|`read_requests`| +|Compare and swap items|`write_requests`| +|Delete an item|`write_requests`| +|Conditionally delete an item if versions match|`write_requests`| +|Write a batch of updates atomically, failing the write if any update fails|Both `write_requests` and `atomic_write_requests`| +|Delete a range of items|`batch_write_requests`| +|Update the keepalive status of an item|`write_requests`| + +During failed backend writes, a Teleport process also increments the +`backend_write_requests_failed_precondition_total` metric if the cause of the +failure is expected. For example, the metric increments during a create +operation if a record already exists, during an update or delete operation if +the record is not found, and during an atomic write if the resource was modified +concurrently. All of these conditions can hold in a well-functioning Teleport +cluster. + +`backend_write_requests_failed_precondition_total` increments whenever +`backend_write_requests_failed_total` increments, and you can use it to +distinguish potentially expected write failures from unexpected, problematic +ones. + +You can use backend operation metrics to define an availability formula, i.e., +the percentage of reads or writes that succeeded. For example, in Prometheus, +you can define a query similar to the following. This takes the percentage of +write requests that failed for unexpected reasons and subtracts it from 1 to get +a percentage of successful writes: + +``` +1- (sum(rate(backend_write_requests_failed_total -sum(rate(teleport_backend_write_requests_failed_precondition_total)) / sum(rate(backend_write_requests_total)) +``` + +If your backend begins to appear unavailable, you can investigate your backend +infrastructure. + +### Backend operation performance + +To help you track backend operation performance, the Auth Service also exposes +Prometheus [histogram metrics](https://prometheus.io/docs/practices/histograms/) +for read and write operations: + +- `teleport_backend_read_seconds_bucket` +- `teleport_backend_write_seconds_bucket` +- `teleport_backend_batch_write_seconds_bucket` +- `teleport_backend_batch_read_seconds_bucket` +- `teleport_backend_atomic_write_seconds_bucket` + +The backend throughput metrics discussed in the previous section map on to +latency metrics. Whenever the Auth Service increments one of the throughput +metrics, it reports one of the corresponding latency metrics. See the table +below for which throughput metrics map to which latency metrics. Each metric +name excludes the standard prefixes and suffixes. + +|Throughput|Latency| +|---|---| +|`read_requests`|`read_seconds_bucket`| +|`read_requests`|`write_seconds_bucket`| +|`batch_read_requests`|`batch_write_seconds_bucket`| +|`batch_write_requests`|`batch_read_seconds_bucket`| +|`atomic_write_requests`|`atomic_write_seconds_bucket`| + +## Agents and connected resources + +To enable users to access most infrastructure with Teleport, you must join a +[Teleport Agent](../../../enroll-resources/agents/agents.mdx) to your Teleport +cluster and configure it to proxy your infrastructure. In a typical setup, an +Agent establishes an SSH reverse tunnel with the Proxy Service. User traffic to +Teleport-protected resources flows through the Proxy Service, an Agent, and +finally the infrastructure resource the Agent proxies. Return traffic from the +resource takes this path in reverse. + +### Number of connected resources by type + +Teleport-connected resources periodically send heartbeat (keepalive) messages to +the Auth Service. The Auth Service uses these heartbeats to track the number of +Teleport-protected resources by type with the `teleport_connected_resources` +metric. + +The Auth Service tracks this metric for the following resources: + +- SSH servers +- Kubernetes clusters +- Applications +- Databases +- Teleport Database Service instances +- Windows desktops + +You can use this metric to: +- Compare the number of resources that are protected by Teleport with those that + are not so you can plan your Teleport rollout. +- Correlate changes in Teleport usage with resource utilization on Auth Service + and Proxy Service compute instances to determine scaling needs. + +You can include this query in your Grafana configuration to break this metric +down by resource type: + +```text +sum(teleport_connected_resources) by (type) +``` + +### Reverse tunnels by type + +Every Teleport service that starts up establishes an SSH reverse tunnel to the +Proxy Service. (Self-hosted clusters can configure Agent services to connect to +the Auth Service directly without establishing a reverse tunnel.) The Proxy +Service tracks the number of reverse tunnels using the metric, +`teleport_reverse_tunnels_connected`. + +With an improperly scaled Proxy Service pool, the Proxy Service can become a +bottleneck for traffic to Teleport-protected resources. If Proxy Service +instances display heavy utilization of compute resources while the number of +connected infrastructure resources is high, you can consider scaling out your +Proxy Service pool and using [Proxy Peering](../operations/proxy-peering.mdx). + +Use the following Grafana query to track the maximum number of reverse tunnels +by type over a given interval: + +```text +max(teleport_reverse_tunnels_connected) by (type)) +``` + +## Teleport instance versions + +At regular intervals (around 7 seconds with jitter), the Auth Service refreshes +its count of registered Teleport instances, including Agents and Teleport +processes that run the Auth Service and Proxy Service. You can measure this +count with the metric, `teleport_registered_servers`. To get the number of +registered instances by version, you can use this query in Grafana: - +```text +sum by (version)(teleport_registered_servers) +``` - Teleport Cloud does not expose monitoring endpoints for the Auth Service and Proxy Service. +You can use this metric to tell how many of your registered Teleport instances +are behind the version of the Auth Service and Proxy Service, which can help you +identify any that are at risk of violating the Teleport [version compatibility +guarantees](../../../upgrading/overview.mdx). - +We strongly encourage self-hosted Teleport users to enroll their Agents in +automatic updates. You can track the count of Teleport Agents that are not +enrolled in automatic updates using the metric, `teleport_enrolled_in_upgrades`. +[Read the documentation](../../../upgrading.mdx) for how to enroll Agents in +automatic updates. -(!docs/pages/includes/metrics.mdx!) \ No newline at end of file From 9179d2e8313832679255b164787683323778e039 Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Tue, 15 Oct 2024 20:47:29 +0000 Subject: [PATCH 11/53] Prep for upcoming backend.Key type change (#46675) (#47483) Adds a (Key) IsZero method to determine if a key is populated. Some Backend implementations today validate keys before operations via a `len(key) == 0` check, however, that will no longer work once the key is migrated. Starts migrating the backend.LockConfiguration away from prepopulating the lock name to passing in a list of components. There were a few locks constructing a portion of the name manually, which will not work when the key type is changed. --- lib/auth/init.go | 6 +-- lib/backend/key.go | 5 ++ lib/backend/key_test.go | 7 +++ lib/backend/lock.go | 19 +++++-- lib/backend/lock_test.go | 51 ++++++++++--------- lib/backend/test/suite.go | 14 ++--- lib/events/athena/consumer.go | 4 +- lib/services/local/access.go | 6 +-- lib/services/local/access_list.go | 9 ++-- lib/services/local/externalauditstorage.go | 18 +++---- lib/services/local/generic/generic.go | 10 ++-- lib/services/local/generic/generic_test.go | 2 +- lib/services/local/integrations.go | 6 +-- .../local/saml_idp_service_provider.go | 4 +- 14 files changed, 92 insertions(+), 69 deletions(-) diff --git a/lib/auth/init.go b/lib/auth/init.go index 491a3629f3793..737f1566ac416 100644 --- a/lib/auth/init.go +++ b/lib/auth/init.go @@ -311,9 +311,9 @@ func Init(ctx context.Context, cfg InitConfig, opts ...ServerOption) (*Server, e if err := backend.RunWhileLocked(ctx, backend.RunWhileLockedConfig{ LockConfiguration: backend.LockConfiguration{ - Backend: cfg.Backend, - LockName: domainName, - TTL: 30 * time.Second, + Backend: cfg.Backend, + LockNameComponents: []string{domainName}, + TTL: 30 * time.Second, }, RefreshLockInterval: 20 * time.Second, }, func(ctx context.Context) error { diff --git a/lib/backend/key.go b/lib/backend/key.go index 4c7f25c604edc..1dcb213d5c93e 100644 --- a/lib/backend/key.go +++ b/lib/backend/key.go @@ -52,6 +52,11 @@ func (k Key) String() string { return string(k) } +// IsZero reports whether k represents the zero key. +func (k Key) IsZero() bool { + return len(k) == 0 +} + // HasPrefix reports whether the key begins with prefix. func (k Key) HasPrefix(prefix Key) bool { return bytes.HasPrefix(k, prefix) diff --git a/lib/backend/key_test.go b/lib/backend/key_test.go index d554fb6922357..894c39f49aff0 100644 --- a/lib/backend/key_test.go +++ b/lib/backend/key_test.go @@ -473,3 +473,10 @@ func TestKeyCompare(t *testing.T) { }) } } + +func TestKeyIsZero(t *testing.T) { + assert.True(t, backend.Key{}.IsZero()) + assert.True(t, backend.NewKey().IsZero()) + assert.False(t, backend.NewKey("a", "b").IsZero()) + assert.False(t, backend.ExactKey("a", "b").IsZero()) +} diff --git a/lib/backend/lock.go b/lib/backend/lock.go index f9eae94437032..220f1f7a315e2 100644 --- a/lib/backend/lock.go +++ b/lib/backend/lock.go @@ -50,8 +50,14 @@ func randomID() ([]byte, error) { } type LockConfiguration struct { - Backend Backend + // Backend to create the lock in. + Backend Backend + // LockName the precomputed lock name. + // TODO(tross) DELETE WHEN teleport.e is updated to use LockNameComponents. LockName string + // LockNameComponents are subcomponents to be used when constructing + // the lock name. + LockNameComponents []string // TTL defines when lock will be released automatically TTL time.Duration // RetryInterval defines interval which is used to retry locking after @@ -63,9 +69,14 @@ func (l *LockConfiguration) CheckAndSetDefaults() error { if l.Backend == nil { return trace.BadParameter("missing Backend") } - if l.LockName == "" { - return trace.BadParameter("missing LockName") + if l.LockName == "" && len(l.LockNameComponents) == 0 { + return trace.BadParameter("missing LockName/LockNameComponents") } + + if len(l.LockNameComponents) == 0 { + l.LockNameComponents = []string{l.LockName} + } + if l.TTL == 0 { return trace.BadParameter("missing TTL") } @@ -81,7 +92,7 @@ func AcquireLock(ctx context.Context, cfg LockConfiguration) (Lock, error) { if err != nil { return Lock{}, trace.Wrap(err) } - key := lockKey(cfg.LockName) + key := lockKey(cfg.LockNameComponents...) id, err := randomID() if err != nil { return Lock{}, trace.Wrap(err) diff --git a/lib/backend/lock_test.go b/lib/backend/lock_test.go index 822ede66236f0..a9c807a2ec0fd 100644 --- a/lib/backend/lock_test.go +++ b/lib/backend/lock_test.go @@ -51,30 +51,30 @@ func TestLockConfiguration_CheckAndSetDefaults(t *testing.T) { { name: "minimum valid", in: LockConfiguration{ - Backend: mockBackend{}, - LockName: "lock", - TTL: 30 * time.Second, + Backend: mockBackend{}, + LockNameComponents: []string{"lock"}, + TTL: 30 * time.Second, }, want: LockConfiguration{ - Backend: mockBackend{}, - LockName: "lock", - TTL: 30 * time.Second, - RetryInterval: 250 * time.Millisecond, + Backend: mockBackend{}, + LockNameComponents: []string{"lock"}, + TTL: 30 * time.Second, + RetryInterval: 250 * time.Millisecond, }, }, { name: "set RetryAcquireLockTimeout", in: LockConfiguration{ - Backend: mockBackend{}, - LockName: "lock", - TTL: 30 * time.Second, - RetryInterval: 10 * time.Second, + Backend: mockBackend{}, + LockNameComponents: []string{"lock"}, + TTL: 30 * time.Second, + RetryInterval: 10 * time.Second, }, want: LockConfiguration{ - Backend: mockBackend{}, - LockName: "lock", - TTL: 30 * time.Second, - RetryInterval: 10 * time.Second, + Backend: mockBackend{}, + LockNameComponents: []string{"lock"}, + TTL: 30 * time.Second, + RetryInterval: 10 * time.Second, }, }, { @@ -95,9 +95,9 @@ func TestLockConfiguration_CheckAndSetDefaults(t *testing.T) { { name: "missing TTL", in: LockConfiguration{ - Backend: mockBackend{}, - LockName: "lock", - TTL: 0, + Backend: mockBackend{}, + LockNameComponents: []string{"lock"}, + TTL: 0, }, wantErr: "missing TTL", }, @@ -124,9 +124,9 @@ func TestRunWhileLockedConfigCheckAndSetDefaults(t *testing.T) { ttl := 1 * time.Minute minimumValidConfig := RunWhileLockedConfig{ LockConfiguration: LockConfiguration{ - Backend: mockBackend{}, - LockName: lockName, - TTL: ttl, + Backend: mockBackend{}, + LockNameComponents: []string{lockName}, + TTL: ttl, }, } tests := []struct { @@ -142,10 +142,10 @@ func TestRunWhileLockedConfigCheckAndSetDefaults(t *testing.T) { }, want: RunWhileLockedConfig{ LockConfiguration: LockConfiguration{ - Backend: mockBackend{}, - LockName: lockName, - TTL: ttl, - RetryInterval: 250 * time.Millisecond, + Backend: mockBackend{}, + LockNameComponents: []string{lockName}, + TTL: ttl, + RetryInterval: 250 * time.Millisecond, }, ReleaseCtxTimeout: time.Second, // defaults to halft of TTL. @@ -157,6 +157,7 @@ func TestRunWhileLockedConfigCheckAndSetDefaults(t *testing.T) { input: func() RunWhileLockedConfig { cfg := minimumValidConfig cfg.LockName = "" + cfg.LockNameComponents = nil return cfg }, wantErr: "missing LockName", diff --git a/lib/backend/test/suite.go b/lib/backend/test/suite.go index 950e3a025e738..5597ccd1d051b 100644 --- a/lib/backend/test/suite.go +++ b/lib/backend/test/suite.go @@ -833,7 +833,7 @@ func testLocking(t *testing.T, newBackend Constructor) { defer requireNoAsyncErrors() // Given a lock named `tok1` on the backend... - lock, err := backend.AcquireLock(ctx, backend.LockConfiguration{Backend: uut, LockName: tok1, TTL: ttl}) + lock, err := backend.AcquireLock(ctx, backend.LockConfiguration{Backend: uut, LockNameComponents: []string{tok1}, TTL: ttl}) require.NoError(t, err) // When I asynchronously release the lock... @@ -848,7 +848,7 @@ func testLocking(t *testing.T, newBackend Constructor) { }() // ...and simultaneously attempt to create a new lock with the same name - lock, err = backend.AcquireLock(ctx, backend.LockConfiguration{Backend: uut, LockName: tok1, TTL: ttl}) + lock, err = backend.AcquireLock(ctx, backend.LockConfiguration{Backend: uut, LockNameComponents: []string{tok1}, TTL: ttl}) // expect that the asynchronous Release() has executed - we're using the // change in the value of the marker value as a proxy for the Release(). @@ -860,7 +860,7 @@ func testLocking(t *testing.T, newBackend Constructor) { require.NoError(t, lock.Release(ctx, uut)) // Given a lock with the same name as previously-existing, manually-released lock - lock, err = backend.AcquireLock(ctx, backend.LockConfiguration{Backend: uut, LockName: tok1, TTL: ttl}) + lock, err = backend.AcquireLock(ctx, backend.LockConfiguration{Backend: uut, LockNameComponents: []string{tok1}, TTL: ttl}) require.NoError(t, err) atomic.StoreInt32(&marker, 7) @@ -875,7 +875,7 @@ func testLocking(t *testing.T, newBackend Constructor) { }() // ...and simultaneously try to acquire another lock with the same name - lock, err = backend.AcquireLock(ctx, backend.LockConfiguration{Backend: uut, LockName: tok1, TTL: ttl}) + lock, err = backend.AcquireLock(ctx, backend.LockConfiguration{Backend: uut, LockNameComponents: []string{tok1}, TTL: ttl}) // expect that the asynchronous Release() has executed - we're using the // change in the value of the marker value as a proxy for the call to @@ -889,9 +889,9 @@ func testLocking(t *testing.T, newBackend Constructor) { // Given a pair of locks named `tok1` and `tok2` y := int32(0) - lock1, err := backend.AcquireLock(ctx, backend.LockConfiguration{Backend: uut, LockName: tok1, TTL: ttl}) + lock1, err := backend.AcquireLock(ctx, backend.LockConfiguration{Backend: uut, LockNameComponents: []string{tok1}, TTL: ttl}) require.NoError(t, err) - lock2, err := backend.AcquireLock(ctx, backend.LockConfiguration{Backend: uut, LockName: tok2, TTL: ttl}) + lock2, err := backend.AcquireLock(ctx, backend.LockConfiguration{Backend: uut, LockNameComponents: []string{tok2}, TTL: ttl}) require.NoError(t, err) // When I asynchronously release the locks... @@ -908,7 +908,7 @@ func testLocking(t *testing.T, newBackend Constructor) { } }() - lock, err = backend.AcquireLock(ctx, backend.LockConfiguration{Backend: uut, LockName: tok1, TTL: ttl}) + lock, err = backend.AcquireLock(ctx, backend.LockConfiguration{Backend: uut, LockNameComponents: []string{tok1}, TTL: ttl}) require.NoError(t, err) require.Equal(t, int32(15), atomic.LoadInt32(&y)) require.NoError(t, lock.Release(ctx, uut)) diff --git a/lib/events/athena/consumer.go b/lib/events/athena/consumer.go index 2332667f4b213..df1a27f909a73 100644 --- a/lib/events/athena/consumer.go +++ b/lib/events/athena/consumer.go @@ -253,8 +253,8 @@ func (c *consumer) runContinuouslyOnSingleAuth(ctx context.Context, eventsProces default: err := backend.RunWhileLocked(ctx, backend.RunWhileLockedConfig{ LockConfiguration: backend.LockConfiguration{ - Backend: c.backend, - LockName: "athena_lock", + Backend: c.backend, + LockNameComponents: []string{"athena_lock"}, // TTL is higher then batchMaxInterval because we want to optimize // for low backend writes. TTL: 5 * c.batchMaxInterval, diff --git a/lib/services/local/access.go b/lib/services/local/access.go index d985d81e17af9..2afb234647341 100644 --- a/lib/services/local/access.go +++ b/lib/services/local/access.go @@ -327,9 +327,9 @@ func (s *AccessService) DeleteAllLocks(ctx context.Context) error { func (s *AccessService) ReplaceRemoteLocks(ctx context.Context, clusterName string, newRemoteLocks []types.Lock) error { return backend.RunWhileLocked(ctx, backend.RunWhileLockedConfig{ LockConfiguration: backend.LockConfiguration{ - Backend: s.Backend, - LockName: "ReplaceRemoteLocks/" + clusterName, - TTL: time.Minute, + Backend: s.Backend, + LockNameComponents: []string{"ReplaceRemoteLocks", clusterName}, + TTL: time.Minute, }, }, func(ctx context.Context) error { remoteLocksKey := backend.ExactKey(locksPrefix, clusterName) diff --git a/lib/services/local/access_list.go b/lib/services/local/access_list.go index 5a155327c7611..19e7c76d49343 100644 --- a/lib/services/local/access_list.go +++ b/lib/services/local/access_list.go @@ -18,7 +18,6 @@ package local import ( "context" - "strings" "time" "github.com/google/go-cmp/cmp" @@ -196,7 +195,7 @@ func (a *AccessListService) runOpWithLock(ctx context.Context, accessList *acces var err error if feature := modules.GetModules().Features(); !feature.IGSEnabled() { - err = a.service.RunWhileLocked(ctx, "createAccessListLimitLock", accessListLockTTL, func(ctx context.Context, _ backend.Backend) error { + err = a.service.RunWhileLocked(ctx, []string{"createAccessListLimitLock"}, accessListLockTTL, func(ctx context.Context, _ backend.Backend) error { if err := a.VerifyAccessListCreateLimit(ctx, accessList.GetName()); err != nil { return trace.Wrap(err) } @@ -453,7 +452,7 @@ func (a *AccessListService) UpsertAccessListWithMembers(ctx context.Context, acc var err error if feature := modules.GetModules().Features(); !feature.IGSEnabled() { - err = a.service.RunWhileLocked(ctx, "createAccessListWithMembersLimitLock", accessListLockTTL, func(ctx context.Context, _ backend.Backend) error { + err = a.service.RunWhileLocked(ctx, []string{"createAccessListWithMembersLimitLock"}, accessListLockTTL, func(ctx context.Context, _ backend.Backend) error { if err := a.VerifyAccessListCreateLimit(ctx, accessList.GetName()); err != nil { return trace.Wrap(err) } @@ -638,8 +637,8 @@ func (a *AccessListService) DeleteAllAccessListReviews(ctx context.Context) erro return trace.Wrap(a.reviewService.DeleteAllResources(ctx)) } -func lockName(accessListName string) string { - return strings.Join([]string{"access_list", accessListName}, string(backend.Separator)) +func lockName(accessListName string) []string { + return []string{"access_list", accessListName} } // VerifyAccessListCreateLimit ensures creating access list is limited to no more than 1 (updating is allowed). diff --git a/lib/services/local/externalauditstorage.go b/lib/services/local/externalauditstorage.go index f42e0ec33fca8..9f6d3bc04921a 100644 --- a/lib/services/local/externalauditstorage.go +++ b/lib/services/local/externalauditstorage.go @@ -83,9 +83,9 @@ func (s *ExternalAuditStorageService) CreateDraftExternalAuditStorage(ctx contex var lease *backend.Lease err = backend.RunWhileLocked(ctx, backend.RunWhileLockedConfig{ LockConfiguration: backend.LockConfiguration{ - Backend: s.backend, - LockName: externalAuditStorageLockName, - TTL: externalAuditStorageLockTTL, + Backend: s.backend, + LockNameComponents: []string{externalAuditStorageLockName}, + TTL: externalAuditStorageLockTTL, }, }, func(ctx context.Context) error { // Check that the referenced AWS OIDC integration actually exists. @@ -122,9 +122,9 @@ func (s *ExternalAuditStorageService) UpsertDraftExternalAuditStorage(ctx contex var lease *backend.Lease err = backend.RunWhileLocked(ctx, backend.RunWhileLockedConfig{ LockConfiguration: backend.LockConfiguration{ - Backend: s.backend, - LockName: externalAuditStorageLockName, - TTL: externalAuditStorageLockTTL, + Backend: s.backend, + LockNameComponents: []string{externalAuditStorageLockName}, + TTL: externalAuditStorageLockTTL, }, }, func(ctx context.Context) error { // Check that the referenced AWS OIDC integration actually exists. @@ -185,9 +185,9 @@ func (s *ExternalAuditStorageService) GetClusterExternalAuditStorage(ctx context func (s *ExternalAuditStorageService) PromoteToClusterExternalAuditStorage(ctx context.Context) error { err := backend.RunWhileLocked(ctx, backend.RunWhileLockedConfig{ LockConfiguration: backend.LockConfiguration{ - Backend: s.backend, - LockName: externalAuditStorageLockName, - TTL: externalAuditStorageLockTTL, + Backend: s.backend, + LockNameComponents: []string{externalAuditStorageLockName}, + TTL: externalAuditStorageLockTTL, }, }, func(ctx context.Context) error { draft, err := s.GetDraftExternalAuditStorage(ctx) diff --git a/lib/services/local/generic/generic.go b/lib/services/local/generic/generic.go index 3682b7946aa20..2f4940fe69e0d 100644 --- a/lib/services/local/generic/generic.go +++ b/lib/services/local/generic/generic.go @@ -421,14 +421,14 @@ func (s *Service[T]) MakeKey(name string) backend.Key { } // RunWhileLocked will run the given function in a backend lock. This is a wrapper around the backend.RunWhileLocked function. -func (s *Service[T]) RunWhileLocked(ctx context.Context, lockName string, ttl time.Duration, fn func(context.Context, backend.Backend) error) error { +func (s *Service[T]) RunWhileLocked(ctx context.Context, lockNameComponents []string, ttl time.Duration, fn func(context.Context, backend.Backend) error) error { return trace.Wrap(backend.RunWhileLocked(ctx, backend.RunWhileLockedConfig{ LockConfiguration: backend.LockConfiguration{ - Backend: s.backend, - LockName: lockName, - TTL: ttl, - RetryInterval: s.runWhileLockedRetryInterval, + Backend: s.backend, + LockNameComponents: lockNameComponents, + TTL: ttl, + RetryInterval: s.runWhileLockedRetryInterval, }, }, func(ctx context.Context) error { return fn(ctx, s.backend) diff --git a/lib/services/local/generic/generic_test.go b/lib/services/local/generic/generic_test.go index a530dca746616..2b6d267db212f 100644 --- a/lib/services/local/generic/generic_test.go +++ b/lib/services/local/generic/generic_test.go @@ -256,7 +256,7 @@ func TestGenericCRUD(t *testing.T) { require.ErrorIs(t, err, trace.NotFound(`generic resource "doesnotexist" doesn't exist`)) // Test running while locked. - err = service.RunWhileLocked(ctx, "test-lock", time.Second*5, func(ctx context.Context, backend backend.Backend) error { + err = service.RunWhileLocked(ctx, []string{"test-lock"}, time.Second*5, func(ctx context.Context, backend backend.Backend) error { item, err := backend.Get(ctx, service.MakeKey(r1.GetName())) require.NoError(t, err) diff --git a/lib/services/local/integrations.go b/lib/services/local/integrations.go index 5d8f340b9bde5..9eb7c9099c098 100644 --- a/lib/services/local/integrations.go +++ b/lib/services/local/integrations.go @@ -124,9 +124,9 @@ func (s *IntegrationsService) DeleteIntegration(ctx context.Context, name string // so that no new EAS integrations can be concurrently created. err := backend.RunWhileLocked(ctx, backend.RunWhileLockedConfig{ LockConfiguration: backend.LockConfiguration{ - Backend: s.backend, - LockName: externalAuditStorageLockName, - TTL: externalAuditStorageLockTTL, + Backend: s.backend, + LockNameComponents: []string{externalAuditStorageLockName}, + TTL: externalAuditStorageLockTTL, }, }, func(ctx context.Context) error { if err := notReferencedByEAS(ctx, s.backend, name); err != nil { diff --git a/lib/services/local/saml_idp_service_provider.go b/lib/services/local/saml_idp_service_provider.go index adeb0f9cecb1e..0c7f624d9e601 100644 --- a/lib/services/local/saml_idp_service_provider.go +++ b/lib/services/local/saml_idp_service_provider.go @@ -143,7 +143,7 @@ func (s *SAMLIdPServiceProviderService) CreateSAMLIdPServiceProvider(ctx context return trace.Wrap(err) } - return trace.Wrap(s.svc.RunWhileLocked(ctx, samlIDPServiceProviderModifyLock, samlIDPServiceProviderModifyLockTTL, + return trace.Wrap(s.svc.RunWhileLocked(ctx, []string{samlIDPServiceProviderModifyLock}, samlIDPServiceProviderModifyLockTTL, func(ctx context.Context, backend backend.Backend) error { if err := s.ensureEntityIDIsUnique(ctx, sp); err != nil { return trace.Wrap(err) @@ -181,7 +181,7 @@ func (s *SAMLIdPServiceProviderService) UpdateSAMLIdPServiceProvider(ctx context return trace.Wrap(err) } - return trace.Wrap(s.svc.RunWhileLocked(ctx, samlIDPServiceProviderModifyLock, samlIDPServiceProviderModifyLockTTL, + return trace.Wrap(s.svc.RunWhileLocked(ctx, []string{samlIDPServiceProviderModifyLock}, samlIDPServiceProviderModifyLockTTL, func(ctx context.Context, backend backend.Backend) error { if err := s.ensureEntityIDIsUnique(ctx, sp); err != nil { return trace.Wrap(err) From 9de80bd654204ffa42d6c9235c96799070d13f52 Mon Sep 17 00:00:00 2001 From: Leszek Charkiewicz Date: Wed, 16 Oct 2024 15:24:54 +0200 Subject: [PATCH 12/53] [v14] Add support for custom SQS consumer lock and disabling consumer (#47612) * Add support for custom SQS consumer lock and disabling consumer * Update lib/events/athena/athena.go Co-authored-by: Edoardo Spadolini * Use LockNameComponents for constructing a lock name * Add a test for disabled consumer * Fix URI in disabled consumer test * Address feedback about ConsumerLockName being a single string * Update lib/events/athena/athena.go Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com> * Make linter happy --------- Co-authored-by: Edoardo Spadolini Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com> --- lib/events/athena/athena.go | 45 ++++++++++++++++++++++++-------- lib/events/athena/athena_test.go | 6 ++++- lib/events/athena/consumer.go | 8 +++++- lib/service/service_test.go | 10 +++++++ 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/lib/events/athena/athena.go b/lib/events/athena/athena.go index 493cc09d9063e..5b6a30f6bab72 100644 --- a/lib/events/athena/athena.go +++ b/lib/events/athena/athena.go @@ -122,6 +122,12 @@ type Config struct { BatchMaxItems int // BatchMaxInterval defined interval at which parquet files will be created (optional). BatchMaxInterval time.Duration + // ConsumerLockName defines a name of a SQS consumer lock (optional). + // If provided, it will be prefixed with "athena/" to avoid accidental + // collision with existing locks. + ConsumerLockName string + // ConsumerDisabled defines if SQS consumer should be disabled (optional). + ConsumerDisabled bool // Clock is a clock interface, used in tests. Clock clockwork.Clock @@ -414,6 +420,16 @@ func (cfg *Config) SetFromURL(url *url.URL) error { } cfg.BatchMaxInterval = dur } + if consumerLockName := url.Query().Get("consumerLockName"); consumerLockName != "" { + cfg.ConsumerLockName = consumerLockName + } + if val := url.Query().Get("consumerDisabled"); val != "" { + boolVal, err := strconv.ParseBool(val) + if err != nil { + return trace.BadParameter("invalid consumerDisabled value: %v", err) + } + cfg.ConsumerDisabled = boolVal + } return nil } @@ -481,20 +497,23 @@ func New(ctx context.Context, cfg Config) (*Log, error) { return nil, trace.Wrap(err) } - consumerCtx, consumerCancel := context.WithCancel(ctx) - - consumer, err := newConsumer(cfg, consumerCancel) - if err != nil { - return nil, trace.Wrap(err) - } - l := &Log{ - publisher: newPublisherFromAthenaConfig(cfg), - querier: querier, - consumerCloser: consumer, + publisher: newPublisherFromAthenaConfig(cfg), + querier: querier, } - go consumer.run(consumerCtx) + if !cfg.ConsumerDisabled { + consumerCtx, consumerCancel := context.WithCancel(ctx) + + consumer, err := newConsumer(cfg, consumerCancel) + if err != nil { + return nil, trace.Wrap(err) + } + + l.consumerCloser = consumer + + go consumer.run(consumerCtx) + } return l, nil } @@ -524,6 +543,10 @@ func (l *Log) Close() error { return trace.Wrap(l.consumerCloser.Close()) } +func (l *Log) IsConsumerDisabled() bool { + return l.consumerCloser == nil +} + var isAlphanumericOrUnderscoreRe = regexp.MustCompile("^[a-zA-Z0-9_]+$") func isAlphanumericOrUnderscore(s string) bool { diff --git a/lib/events/athena/athena_test.go b/lib/events/athena/athena_test.go index fb908c5aeb3de..baa4430db6a0d 100644 --- a/lib/events/athena/athena_test.go +++ b/lib/events/athena/athena_test.go @@ -89,13 +89,15 @@ func TestConfig_SetFromURL(t *testing.T) { }, { name: "params to batcher", - url: "athena://db.tbl/?queueURL=https://queueURL&batchMaxItems=1000&batchMaxInterval=10s", + url: "athena://db.tbl/?queueURL=https://queueURL&batchMaxItems=1000&batchMaxInterval=10s&consumerLockName=mylock&consumerDisabled=true", want: Config{ TableName: "tbl", Database: "db", QueueURL: "https://queueURL", BatchMaxItems: 1000, BatchMaxInterval: 10 * time.Second, + ConsumerLockName: "mylock", + ConsumerDisabled: true, }, }, { @@ -183,6 +185,7 @@ func TestConfig_CheckAndSetDefaults(t *testing.T) { GetQueryResultsInterval: 100 * time.Millisecond, BatchMaxItems: 20000, BatchMaxInterval: 1 * time.Minute, + ConsumerLockName: "", PublisherConsumerAWSConfig: dummyAWSCfg, StorerQuerierAWSConfig: dummyAWSCfg, Backend: mockBackend{}, @@ -208,6 +211,7 @@ func TestConfig_CheckAndSetDefaults(t *testing.T) { GetQueryResultsInterval: 100 * time.Millisecond, BatchMaxItems: 20000, BatchMaxInterval: 1 * time.Minute, + ConsumerLockName: "", PublisherConsumerAWSConfig: dummyAWSCfg, StorerQuerierAWSConfig: dummyAWSCfg, Backend: mockBackend{}, diff --git a/lib/events/athena/consumer.go b/lib/events/athena/consumer.go index df1a27f909a73..2b0417ec07a44 100644 --- a/lib/events/athena/consumer.go +++ b/lib/events/athena/consumer.go @@ -75,6 +75,7 @@ type consumer struct { storeLocationBucket string batchMaxItems int batchMaxInterval time.Duration + consumerLockName string // perDateFileParquetWriter returns file writer per date. // Added in config to allow testing. @@ -153,6 +154,7 @@ func newConsumer(cfg Config, cancelFn context.CancelFunc) (*consumer, error) { storeLocationBucket: cfg.locationS3Bucket, batchMaxItems: cfg.BatchMaxItems, batchMaxInterval: cfg.BatchMaxInterval, + consumerLockName: cfg.ConsumerLockName, collectConfig: collectCfg, sqsDeleter: sqsClient, queueURL: cfg.QueueURL, @@ -251,10 +253,14 @@ func (c *consumer) runContinuouslyOnSingleAuth(ctx context.Context, eventsProces case <-ctx.Done(): return default: + lockName := []string{"athena", c.consumerLockName} + if c.consumerLockName == "" { + lockName = []string{"athena_lock"} + } err := backend.RunWhileLocked(ctx, backend.RunWhileLockedConfig{ LockConfiguration: backend.LockConfiguration{ Backend: c.backend, - LockNameComponents: []string{"athena_lock"}, + LockNameComponents: lockName, // TTL is higher then batchMaxInterval because we want to optimize // for low backend writes. TTL: 5 * c.batchMaxInterval, diff --git a/lib/service/service_test.go b/lib/service/service_test.go index 9162d5b46c686..a735e325d1e9a 100644 --- a/lib/service/service_test.go +++ b/lib/service/service_test.go @@ -530,6 +530,16 @@ func TestAthenaAuditLogSetup(t *testing.T) { require.True(t, ok, "invalid logger type, got %T", v) }, }, + { + name: "valid athena config with disabled consumer", + uris: []string{sampleAthenaURI + "&consumerDisabled=true"}, + externalAudit: externalAuditStorageDisabled, + wantFn: func(t *testing.T, alog events.AuditLogger) { + v, ok := alog.(*athena.Log) + require.True(t, ok, "invalid logger type, got %T", v) + require.True(t, v.IsConsumerDisabled(), "consumer is not disabled") + }, + }, { name: "config with rate limit - should use events.SearchEventsLimiter", uris: []string{sampleAthenaURI + "&limiterRefillAmount=3&limiterBurst=2"}, From 1f17c4872439785da273d0e9a3ebdb00e5798f36 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Wed, 16 Oct 2024 15:59:34 +0200 Subject: [PATCH 13/53] Load appropriate TLS config for clusters (#40293) (#47625) Updates the proxy.Client to allow loading specific tls.Config for individual clusters. This prevents issues when trying to access leaf resources via the root cluster if WithAllCAs is not set. (cherry picked from commit ad26913c71710fa3fd1ac2b53360936ce08986c6) Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com> --- api/client/proxy/client.go | 62 +++++++++++++++++++++------------ api/client/proxy/client_test.go | 4 ++- integration/integration_test.go | 3 +- lib/client/api.go | 31 +++++++++++------ lib/client/cluster_client.go | 13 +++++-- 5 files changed, 77 insertions(+), 36 deletions(-) diff --git a/api/client/proxy/client.go b/api/client/proxy/client.go index 179c7ad0383fd..f8237c0f0c3aa 100644 --- a/api/client/proxy/client.go +++ b/api/client/proxy/client.go @@ -48,8 +48,8 @@ type ClientConfig struct { ProxyAddress string // TLSRoutingEnabled indicates if the cluster is using TLS Routing. TLSRoutingEnabled bool - // TLSConfig contains the tls.Config required for mTLS connections. - TLSConfig *tls.Config + // TLSConfigFunc produces the [tls.Config] required for mTLS connections to a specific cluster. + TLSConfigFunc func(cluster string) (*tls.Config, error) // UnaryInterceptors are optional [grpc.UnaryClientInterceptor] to apply // to the gRPC client. UnaryInterceptors []grpc.UnaryClientInterceptor @@ -76,9 +76,9 @@ type ClientConfig struct { // The below items are intended to be used by tests to connect without mTLS. // The gRPC transport credentials to use when establishing the connection to proxy. - creds func() credentials.TransportCredentials + creds func(cluster string) (credentials.TransportCredentials, error) // The client credentials to use when establishing the connection to auth. - clientCreds func() client.Credentials + clientCreds func(cluster string) (client.Credentials, error) } // CheckAndSetDefaults ensures required options are present and @@ -93,13 +93,21 @@ func (c *ClientConfig) CheckAndSetDefaults() error { if c.DialTimeout <= 0 { c.DialTimeout = defaults.DefaultIOTimeout } - if c.TLSConfig != nil { - c.clientCreds = func() client.Credentials { - return client.LoadTLS(c.TLSConfig.Clone()) + if c.TLSConfigFunc != nil { + c.clientCreds = func(cluster string) (client.Credentials, error) { + cfg, err := c.TLSConfigFunc(cluster) + if err != nil { + return nil, trace.Wrap(err) + } + + return client.LoadTLS(cfg), nil } - c.creds = func() credentials.TransportCredentials { - tlsCfg := c.TLSConfig.Clone() - if !slices.Contains(c.TLSConfig.NextProtos, protocolProxySSHGRPC) { + c.creds = func(cluster string) (credentials.TransportCredentials, error) { + tlsCfg, err := c.TLSConfigFunc(cluster) + if err != nil { + return nil, trace.Wrap(err) + } + if !slices.Contains(tlsCfg.NextProtos, protocolProxySSHGRPC) { tlsCfg.NextProtos = append(tlsCfg.NextProtos, protocolProxySSHGRPC) } @@ -114,14 +122,14 @@ func (c *ClientConfig) CheckAndSetDefaults() error { } } - return credentials.NewTLS(tlsCfg) + return credentials.NewTLS(tlsCfg), nil } } else { - c.clientCreds = func() client.Credentials { - return insecureCredentials{} + c.clientCreds = func(cluster string) (client.Credentials, error) { + return insecureCredentials{}, nil } - c.creds = func() credentials.TransportCredentials { - return insecure.NewCredentials() + c.creds = func(cluster string) (credentials.TransportCredentials, error) { + return insecure.NewCredentials(), nil } } @@ -266,12 +274,18 @@ func newGRPCClient(ctx context.Context, cfg *ClientConfig) (_ *Client, err error defer cancel() c := &clusterName{} + + creds, err := cfg.creds("") + if err != nil { + return nil, trace.Wrap(err) + } + conn, err := grpc.DialContext( dialCtx, cfg.ProxyAddress, append([]grpc.DialOption{ grpc.WithContextDialer(newDialerForGRPCClient(ctx, cfg)), - grpc.WithTransportCredentials(&clusterCredentials{TransportCredentials: cfg.creds(), clusterName: c}), + grpc.WithTransportCredentials(&clusterCredentials{TransportCredentials: creds, clusterName: c}), grpc.WithChainUnaryInterceptor( append(cfg.UnaryInterceptors, //nolint:staticcheck // SA1019. There is a data race in the stats.Handler that is replacing @@ -362,22 +376,27 @@ type ClusterDetails struct { // Auth server in the provided cluster via [client.New] or similar. The [client.Config] // returned will have the correct credentials and dialer set based on the ClientConfig // that was provided to create this Client. -func (c *Client) ClientConfig(ctx context.Context, cluster string) client.Config { +func (c *Client) ClientConfig(ctx context.Context, cluster string) (client.Config, error) { + creds, err := c.cfg.clientCreds(cluster) + if err != nil { + return client.Config{}, trace.Wrap(err) + } + if c.cfg.TLSRoutingEnabled { return client.Config{ Context: ctx, Addrs: []string{c.cfg.ProxyAddress}, - Credentials: []client.Credentials{c.cfg.clientCreds()}, + Credentials: []client.Credentials{creds}, ALPNSNIAuthDialClusterName: cluster, CircuitBreakerConfig: breaker.NoopBreakerConfig(), ALPNConnUpgradeRequired: c.cfg.ALPNConnUpgradeRequired, DialOpts: c.cfg.DialOpts, - } + }, nil } return client.Config{ Context: ctx, - Credentials: []client.Credentials{c.cfg.clientCreds()}, + Credentials: []client.Credentials{creds}, CircuitBreakerConfig: breaker.NoopBreakerConfig(), DialInBackground: true, Dialer: client.ContextDialerFunc(func(dialCtx context.Context, _ string, _ string) (net.Conn, error) { @@ -385,8 +404,7 @@ func (c *Client) ClientConfig(ctx context.Context, cluster string) client.Config return conn, trace.Wrap(err) }), DialOpts: c.cfg.DialOpts, - } - + }, nil } // DialHost establishes a connection to the `target` in cluster named `cluster`. If a keyring diff --git a/api/client/proxy/client_test.go b/api/client/proxy/client_test.go index 2897fed37cdab..2859ee8909bca 100644 --- a/api/client/proxy/client_test.go +++ b/api/client/proxy/client_test.go @@ -507,7 +507,9 @@ func TestClient_DialCluster(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { require.NoError(t, clt.Close()) }) - authCfg := clt.ClientConfig(ctx, "cluster") + authCfg, err := clt.ClientConfig(ctx, "cluster") + require.NoError(t, err) + authCfg.DialOpts = []grpc.DialOption{ grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithReturnConnectionError(), diff --git a/integration/integration_test.go b/integration/integration_test.go index ad169976b5eef..e163329276273 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -1662,7 +1662,8 @@ func testIPPropagation(t *testing.T, suite *integrationTestSuite) { // The above dialer does not work clt.AuthClient as it requires a // custom transport from ProxyClient when TLS routing is disabled. // Recreating the authClient without the above dialer. - authClientCfg := clt.ProxyClient.ClientConfig(ctx, clusterName) + authClientCfg, err := clt.ProxyClient.ClientConfig(ctx, clusterName) + require.NoError(t, err) authClientCfg.DialOpts = nil authClient, err := authclient.NewClient(authClientCfg) require.NoError(t, err) diff --git a/lib/client/api.go b/lib/client/api.go index 25211825118db..70ed835477379 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -1520,7 +1520,12 @@ func (tc *TeleportClient) NewTracingClient(ctx context.Context) (*apitracing.Cli return nil, trace.Wrap(err) } - tracingClient, err := client.NewTracingClient(ctx, clusterClient.ProxyClient.ClientConfig(ctx, clusterClient.ClusterName())) + cfg, err := clusterClient.ProxyClient.ClientConfig(ctx, clusterClient.ClusterName()) + if err != nil { + return nil, trace.Wrap(err) + } + + tracingClient, err := client.NewTracingClient(ctx, cfg) return tracingClient, trace.Wrap(err) } @@ -2899,15 +2904,18 @@ func (tc *TeleportClient) ConnectToCluster(ctx context.Context) (_ *ClusterClien return nil, trace.Wrap(err) } - tlsConfig, err := tc.LoadTLSConfig() - if err != nil { - return nil, trace.Wrap(err) - } - pclt, err := proxyclient.NewClient(ctx, proxyclient.ClientConfig{ - ProxyAddress: cfg.proxyAddress, - TLSRoutingEnabled: tc.TLSRoutingEnabled, - TLSConfig: tlsConfig, + ProxyAddress: cfg.proxyAddress, + TLSRoutingEnabled: tc.TLSRoutingEnabled, + TLSConfigFunc: func(cluster string) (*tls.Config, error) { + if cluster == "" { + tlsCfg, err := tc.LoadTLSConfig() + return tlsCfg, trace.Wrap(err) + } + + tlsCfg, err := tc.LoadTLSConfigForClusters([]string{cluster}) + return tlsCfg, trace.Wrap(err) + }, DialOpts: tc.Config.DialOpts, UnaryInterceptors: []grpc.UnaryClientInterceptor{interceptors.GRPCClientUnaryErrorInterceptor}, StreamInterceptors: []grpc.StreamClientInterceptor{interceptors.GRPCClientStreamErrorInterceptor}, @@ -2930,7 +2938,10 @@ func (tc *TeleportClient) ConnectToCluster(ctx context.Context) (_ *ClusterClien cluster = connected } - authClientCfg := pclt.ClientConfig(ctx, cluster) + authClientCfg, err := pclt.ClientConfig(ctx, cluster) + if err != nil { + return nil, trace.NewAggregate(err, pclt.Close()) + } authClientCfg.PromptAdminRequestMFA = tc.NewMFAPrompt(mfa.WithHintBeforePrompt(mfa.AdminMFAHintBeforePrompt)) authClient, err := authclient.NewClient(authClientCfg) if err != nil { diff --git a/lib/client/cluster_client.go b/lib/client/cluster_client.go index 5c9327d1ab78f..e66315d165c69 100644 --- a/lib/client/cluster_client.go +++ b/lib/client/cluster_client.go @@ -69,7 +69,11 @@ func (c *ClusterClient) ConnectToCluster(ctx context.Context, clusterName string return c.CurrentCluster(), nil } - clientConfig := c.ProxyClient.ClientConfig(ctx, clusterName) + clientConfig, err := c.ProxyClient.ClientConfig(ctx, clusterName) + if err != nil { + return nil, trace.Wrap(err) + } + authClient, err := authclient.NewClient(clientConfig) return authClient, trace.Wrap(err) } @@ -128,7 +132,12 @@ func (c *ClusterClient) SessionSSHConfig(ctx context.Context, user string, targe mfaClt := c if target.Cluster != rootClusterName { - authClient, err := authclient.NewClient(c.ProxyClient.ClientConfig(ctx, rootClusterName)) + cfg, err := c.ProxyClient.ClientConfig(ctx, rootClusterName) + if err != nil { + return nil, trace.Wrap(err) + } + + authClient, err := authclient.NewClient(cfg) if err != nil { return nil, trace.Wrap(MFARequiredUnknown(err)) } From ff0d651ff73e3865754b174da01843b02357ab37 Mon Sep 17 00:00:00 2001 From: Tiago Silva Date: Wed, 16 Oct 2024 16:00:57 +0100 Subject: [PATCH 14/53] [aws] exclude session recordings from S3 sync in `teleport-renew-cert` (#47623) This PR excludes `records` directory from the sync when renewing the letsencrypt certificate. When doing the wildcard sync, `aws sync` downloads the `records` folder which contains audit logs for the cluster which causes failures because of no space left on the disk. The certbot hook `teleport-upload-cert` doesn't use the `--delete` flag so the records are never purged from S3 on upload Fixes #27884 Signed-off-by: Tiago Silva --- assets/aws/files/bin/teleport-renew-cert | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/aws/files/bin/teleport-renew-cert b/assets/aws/files/bin/teleport-renew-cert index b0d3ec9824a0a..13b8c03dce89b 100755 --- a/assets/aws/files/bin/teleport-renew-cert +++ b/assets/aws/files/bin/teleport-renew-cert @@ -17,7 +17,7 @@ if [ ! -f /etc/teleport.d/role.auth ] && [ ! -f /etc/teleport.d/role.all ]; then fi # Fetching certbot state -aws s3 sync --exact-timestamps "s3://${TELEPORT_S3_BUCKET}" /etc/letsencrypt/ --sse=AES256 +aws s3 sync '--exclude=records/*' --exact-timestamps "s3://${TELEPORT_S3_BUCKET}" /etc/letsencrypt/ --sse=AES256 # s3 does not support symlinks, we have to create them after the sync, else certbot will fail. # live/ symlinks point to the latest archive//XX.pem where XX is incremented at each cert-renewal. From 97dc48e9177fec245435ab4f67fbd19e68c818d4 Mon Sep 17 00:00:00 2001 From: Tiago Silva Date: Wed, 16 Oct 2024 19:14:44 +0100 Subject: [PATCH 15/53] [v14] [gcp] support project discovery (#47566) * [gcp] support project discovery This PR extends Teleport discovery service to be able to support find all GKE and VM servers in every project a user has access to. ``` discovery_service: enabled: true discovery_group: "test" gcp: - types: ["gke"] locations: ["*"] project_ids: ["*"] tags: '*': '*' ``` * simplify docs by adding examples --- api/types/matchers_gcp.go | 4 +- api/types/matchers_gcp_test.go | 4 +- .../discovery/google-cloud.mdx | 14 +- lib/cloud/clients.go | 16 +++ lib/cloud/gcp/projects.go | 105 +++++++++++++++ lib/config/configuration_test.go | 47 +++++++ lib/srv/discovery/discovery.go | 28 ++-- lib/srv/discovery/discovery_test.go | 124 +++++++++++++++++- lib/srv/discovery/fetchers/gke.go | 51 ++++++- lib/srv/discovery/fetchers/gke_test.go | 89 +++++++++++-- lib/srv/server/gcp_watcher.go | 40 +++++- 11 files changed, 482 insertions(+), 40 deletions(-) create mode 100644 lib/cloud/gcp/projects.go diff --git a/api/types/matchers_gcp.go b/api/types/matchers_gcp.go index 095aef9386449..20eec0d6d89c1 100644 --- a/api/types/matchers_gcp.go +++ b/api/types/matchers_gcp.go @@ -101,8 +101,8 @@ func (m *GCPMatcher) CheckAndSetDefaults() error { m.Locations = []string{Wildcard} } - if slices.Contains(m.ProjectIDs, Wildcard) { - return trace.BadParameter("GCP discovery service project_ids does not support wildcards; please specify at least one value in project_ids.") + if slices.Contains(m.ProjectIDs, Wildcard) && len(m.ProjectIDs) > 1 { + return trace.BadParameter("GCP discovery service either supports wildcard project_ids or multiple values, but not both.") } if len(m.ProjectIDs) == 0 { return trace.BadParameter("GCP discovery service project_ids does cannot be empty; please specify at least one value in project_ids.") diff --git a/api/types/matchers_gcp_test.go b/api/types/matchers_gcp_test.go index 46eb18fe248d3..f56c93172b654 100644 --- a/api/types/matchers_gcp_test.go +++ b/api/types/matchers_gcp_test.go @@ -92,12 +92,12 @@ func TestGCPMatcherCheckAndSetDefaults(t *testing.T) { errCheck: isBadParameterErr, }, { - name: "wildcard is invalid for project ids", + name: "wildcard is valid for project ids", in: &GCPMatcher{ Types: []string{"gce"}, ProjectIDs: []string{"*"}, }, - errCheck: isBadParameterErr, + errCheck: require.NoError, }, { name: "invalid type", diff --git a/docs/pages/enroll-resources/kubernetes-access/discovery/google-cloud.mdx b/docs/pages/enroll-resources/kubernetes-access/discovery/google-cloud.mdx index 1e54ead88ae09..83f892516dd79 100644 --- a/docs/pages/enroll-resources/kubernetes-access/discovery/google-cloud.mdx +++ b/docs/pages/enroll-resources/kubernetes-access/discovery/google-cloud.mdx @@ -452,8 +452,18 @@ value, `gke`. #### `discovery_service.gcp[0].project_ids` In your matcher, replace `myproject` with the ID of your Google Cloud project. -The `project_ids` field must include at least one value, and it must not be the -wildcard character (`*`). + +Ensure that the `project_ids` field follows these rules: +- It must include at least one value. +- It must not combine the wildcard character (`*`) with other values. + +##### Examples of valid configurations +- `["p1", "p2"]` +- `["*"]` +- `["p1"]` + +##### Example of an invalid configuration +- `["p1", "*"]` #### `discovery_service.gcp[0].locations` diff --git a/lib/cloud/clients.go b/lib/cloud/clients.go index 48ccacbca296d..a01ce81692a18 100644 --- a/lib/cloud/clients.go +++ b/lib/cloud/clients.go @@ -98,6 +98,8 @@ type GCPClients interface { GetGCPSQLAdminClient(context.Context) (gcp.SQLAdminClient, error) // GetGCPGKEClient returns GKE client. GetGCPGKEClient(context.Context) (gcp.GKEClient, error) + // GetGCPProjectsClient returns Projects client. + GetGCPProjectsClient(context.Context) (gcp.ProjectsClient, error) // GetGCPInstancesClient returns instances client. GetGCPInstancesClient(context.Context) (gcp.InstancesClient, error) } @@ -255,6 +257,7 @@ func NewClients(opts ...ClientsOption) (Clients, error) { gcpClients: gcpClients{ gcpSQLAdmin: newClientCache[gcp.SQLAdminClient](gcp.NewSQLAdminClient), gcpGKE: newClientCache[gcp.GKEClient](gcp.NewGKEClient), + gcpProjects: newClientCache[gcp.ProjectsClient](gcp.NewProjectsClient), gcpInstances: newClientCache[gcp.InstancesClient](gcp.NewInstancesClient), }, azureClients: azClients, @@ -313,6 +316,8 @@ type gcpClients struct { gcpSQLAdmin *clientCache[gcp.SQLAdminClient] // gcpGKE is the cached GCP Cloud GKE client. gcpGKE *clientCache[gcp.GKEClient] + // gcpProjects is the cached GCP Cloud Projects client. + gcpProjects *clientCache[gcp.ProjectsClient] // gcpInstances is the cached GCP instances client. gcpInstances *clientCache[gcp.InstancesClient] } @@ -639,6 +644,11 @@ func (c *cloudClients) GetGCPGKEClient(ctx context.Context) (gcp.GKEClient, erro return c.gcpGKE.GetClient(ctx) } +// GetGCPProjectsClient returns Project client. +func (c *cloudClients) GetGCPProjectsClient(ctx context.Context) (gcp.ProjectsClient, error) { + return c.gcpProjects.GetClient(ctx) +} + // GetGCPInstancesClient returns instances client. func (c *cloudClients) GetGCPInstancesClient(ctx context.Context) (gcp.InstancesClient, error) { return c.gcpInstances.GetClient(ctx) @@ -982,6 +992,7 @@ type TestCloudClients struct { STS stsiface.STSAPI GCPSQL gcp.SQLAdminClient GCPGKE gcp.GKEClient + GCPProjects gcp.ProjectsClient GCPInstances gcp.InstancesClient EC2 ec2iface.EC2API SSM ssmiface.SSMAPI @@ -1178,6 +1189,11 @@ func (c *TestCloudClients) GetGCPGKEClient(ctx context.Context) (gcp.GKEClient, return c.GCPGKE, nil } +// GetGCPGKEClient returns GKE client. +func (c *TestCloudClients) GetGCPProjectsClient(ctx context.Context) (gcp.ProjectsClient, error) { + return c.GCPProjects, nil +} + // GetGCPInstancesClient returns instances client. func (c *TestCloudClients) GetGCPInstancesClient(ctx context.Context) (gcp.InstancesClient, error) { return c.GCPInstances, nil diff --git a/lib/cloud/gcp/projects.go b/lib/cloud/gcp/projects.go new file mode 100644 index 0000000000000..ee5b178be9f9c --- /dev/null +++ b/lib/cloud/gcp/projects.go @@ -0,0 +1,105 @@ +/* + * 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 gcp + +import ( + "context" + + "github.com/gravitational/trace" + "google.golang.org/api/cloudresourcemanager/v1" +) + +// Project is a GCP project. +type Project struct { + // ID is the project ID. + ID string + // Name is the project name. + Name string +} + +// ProjectsClient is an interface to interact with GCP Projects API. +type ProjectsClient interface { + // ListProjects lists the GCP projects that the authenticated user has access to. + ListProjects(ctx context.Context) ([]Project, error) +} + +// ProjectsClientConfig is the client configuration for ProjectsClient. +type ProjectsClientConfig struct { + // Client is the GCP client for resourcemanager service. + Client *cloudresourcemanager.Service +} + +// CheckAndSetDefaults check and set defaults for ProjectsClientConfig. +func (c *ProjectsClientConfig) CheckAndSetDefaults(ctx context.Context) (err error) { + if c.Client == nil { + c.Client, err = cloudresourcemanager.NewService(ctx) + if err != nil { + return trace.Wrap(err) + } + } + return nil +} + +// NewProjectsClient returns a ProjectsClient interface wrapping resourcemanager.ProjectsClient +// for interacting with GCP Projects API. +func NewProjectsClient(ctx context.Context) (ProjectsClient, error) { + var cfg ProjectsClientConfig + client, err := NewProjectsClientWithConfig(ctx, cfg) + return client, trace.Wrap(err) +} + +// NewProjectsClientWithConfig returns a ProjectsClient interface wrapping resourcemanager.ProjectsClient +// for interacting with GCP Projects API. +func NewProjectsClientWithConfig(ctx context.Context, cfg ProjectsClientConfig) (ProjectsClient, error) { + if err := cfg.CheckAndSetDefaults(ctx); err != nil { + return nil, trace.Wrap(err) + } + return &projectsClient{cfg}, nil +} + +type projectsClient struct { + ProjectsClientConfig +} + +// ListProjects lists the GCP Projects that the authenticated user has access to. +func (g *projectsClient) ListProjects(ctx context.Context) ([]Project, error) { + + var pageToken string + var projects []Project + for { + projectsCall, err := g.Client.Projects.List().PageToken(pageToken).Do() + if err != nil { + return nil, trace.Wrap(err) + } + for _, project := range projectsCall.Projects { + projects = append(projects, + Project{ + ID: project.ProjectId, + Name: project.Name, + }, + ) + } + if projectsCall.NextPageToken == "" { + break + } + pageToken = projectsCall.NextPageToken + } + + return projects, nil +} diff --git a/lib/config/configuration_test.go b/lib/config/configuration_test.go index 835f02a09313d..23f3e0152b0fd 100644 --- a/lib/config/configuration_test.go +++ b/lib/config/configuration_test.go @@ -4346,6 +4346,53 @@ func TestDiscoveryConfig(t *testing.T) { ProjectIDs: []string{"p1", "p2"}, }}, }, + { + desc: "GCP section is filled with wildcard project ids", + expectError: require.NoError, + expectEnabled: require.True, + mutate: func(cfg cfgMap) { + cfg["discovery_service"].(cfgMap)["enabled"] = "yes" + cfg["discovery_service"].(cfgMap)["gcp"] = []cfgMap{ + { + "types": []string{"gke"}, + "locations": []string{"eucentral1"}, + "tags": cfgMap{ + "discover_teleport": "yes", + }, + "project_ids": []string{"*"}, + }, + } + }, + expectedGCPMatchers: []types.GCPMatcher{{ + Types: []string{"gke"}, + Locations: []string{"eucentral1"}, + Labels: map[string]apiutils.Strings{ + "discover_teleport": []string{"yes"}, + }, + Tags: map[string]apiutils.Strings{ + "discover_teleport": []string{"yes"}, + }, + ProjectIDs: []string{"*"}, + }}, + }, + { + desc: "GCP section mixes wildcard and specific project ids", + expectError: require.Error, + expectEnabled: require.True, + mutate: func(cfg cfgMap) { + cfg["discovery_service"].(cfgMap)["enabled"] = "yes" + cfg["discovery_service"].(cfgMap)["gcp"] = []cfgMap{ + { + "types": []string{"gke"}, + "locations": []string{"eucentral1"}, + "tags": cfgMap{ + "discover_teleport": "yes", + }, + "project_ids": []string{"p1", "*"}, + }, + } + }, + }, { desc: "GCP section is filled with installer", expectError: require.NoError, diff --git a/lib/srv/discovery/discovery.go b/lib/srv/discovery/discovery.go index 6f612c763b249..78fc1937e92b7 100644 --- a/lib/srv/discovery/discovery.go +++ b/lib/srv/discovery/discovery.go @@ -562,7 +562,12 @@ func (s *Server) gcpServerFetchersFromMatchers(ctx context.Context, matchers []t return nil, trace.Wrap(err) } - return server.MatchersToGCPInstanceFetchers(serverMatchers, client), nil + projectsClient, err := s.CloudClients.GetGCPProjectsClient(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + return server.MatchersToGCPInstanceFetchers(serverMatchers, client, projectsClient), nil } // databaseFetchersFromMatchers converts Matchers into a set of Database Fetchers. @@ -704,19 +709,26 @@ func (s *Server) initGCPWatchers(ctx context.Context, matchers []types.GCPMatche if err != nil { return trace.Wrap(err) } + projectClient, err := s.CloudClients.GetGCPProjectsClient(ctx) + if err != nil { + return trace.Wrap(err, "unable to create gcp project client") + } for _, matcher := range otherMatchers { for _, projectID := range matcher.ProjectIDs { for _, location := range matcher.Locations { for _, t := range matcher.Types { switch t { case types.GCPMatcherKubernetes: - fetcher, err := fetchers.NewGKEFetcher(fetchers.GKEFetcherConfig{ - Client: kubeClient, - Location: location, - FilterLabels: matcher.GetLabels(), - ProjectID: projectID, - Log: s.Log, - }) + fetcher, err := fetchers.NewGKEFetcher( + ctx, + fetchers.GKEFetcherConfig{ + GKEClient: kubeClient, + ProjectClient: projectClient, + Location: location, + FilterLabels: matcher.GetLabels(), + ProjectID: projectID, + Log: s.Log, + }) if err != nil { return trace.Wrap(err) } diff --git a/lib/srv/discovery/discovery_test.go b/lib/srv/discovery/discovery_test.go index f8997975aa575..dcbefc5e0290f 100644 --- a/lib/srv/discovery/discovery_test.go +++ b/lib/srv/discovery/discovery_test.go @@ -950,6 +950,24 @@ func TestDiscoveryInCloudKube(t *testing.T) { }, wantEvents: 2, }, + { + name: "no clusters in auth server, import 3 prod clusters from GKE across multiple projects", + existingKubeClusters: []types.KubeCluster{}, + gcpMatchers: []types.GCPMatcher{ + { + Types: []string{"gke"}, + Locations: []string{"*"}, + ProjectIDs: []string{"*"}, + Tags: map[string]utils.Strings{"env": {"prod"}}, + }, + }, + expectedClustersToExistInAuth: []types.KubeCluster{ + mustConvertGKEToKubeCluster(t, gkeMockClusters[0], mainDiscoveryGroup), + mustConvertGKEToKubeCluster(t, gkeMockClusters[1], mainDiscoveryGroup), + mustConvertGKEToKubeCluster(t, gkeMockClusters[4], mainDiscoveryGroup), + }, + wantEvents: 3, + }, } for _, tc := range tcs { @@ -963,6 +981,7 @@ func TestDiscoveryInCloudKube(t *testing.T) { AzureAKSClient: newPopulatedAKSMock(), EKS: newPopulatedEKSMock(), GCPGKE: newPopulatedGCPMock(), + GCPProjects: newPopulatedGCPProjectsMock(), } ctx := context.Background() @@ -1447,6 +1466,28 @@ var gkeMockClusters = []gcp.GKECluster{ Location: "central-1", Description: "desc1", }, + { + Name: "cluster5", + Status: containerpb.Cluster_RUNNING, + Labels: map[string]string{ + "env": "prod", + "location": "central-1", + }, + ProjectID: "p2", + Location: "central-1", + Description: "desc1", + }, + { + Name: "cluster6", + Status: containerpb.Cluster_RUNNING, + Labels: map[string]string{ + "env": "stg", + "location": "central-1", + }, + ProjectID: "p2", + Location: "central-1", + Description: "desc1", + }, } func mustConvertGKEToKubeCluster(t *testing.T, gkeCluster gcp.GKECluster, discoveryGroup string) types.KubeCluster { @@ -1464,7 +1505,15 @@ type mockGKEAPI struct { } func (m *mockGKEAPI) ListClusters(ctx context.Context, projectID string, location string) ([]gcp.GKECluster, error) { - return m.clusters, nil + var clusters []gcp.GKECluster + for _, cluster := range m.clusters { + if cluster.ProjectID != projectID { + continue + } + clusters = append(clusters, cluster) + } + + return clusters, nil } func TestDiscoveryDatabase(t *testing.T) { @@ -2377,12 +2426,21 @@ type mockGCPClient struct { vms []*gcp.Instance } -func (m *mockGCPClient) ListInstances(_ context.Context, _, _ string) ([]*gcp.Instance, error) { - return m.vms, nil +func (m *mockGCPClient) getVMSForProject(projectID string) []*gcp.Instance { + var vms []*gcp.Instance + for _, vm := range m.vms { + if vm.ProjectID == projectID { + vms = append(vms, vm) + } + } + return vms +} +func (m *mockGCPClient) ListInstances(_ context.Context, projectID, _ string) ([]*gcp.Instance, error) { + return m.getVMSForProject(projectID), nil } -func (m *mockGCPClient) StreamInstances(_ context.Context, _, _ string) stream.Stream[*gcp.Instance] { - return stream.Slice(m.vms) +func (m *mockGCPClient) StreamInstances(_ context.Context, projectID, _ string) stream.Stream[*gcp.Instance] { + return stream.Slice(m.getVMSForProject(projectID)) } func (m *mockGCPClient) GetInstance(_ context.Context, _ *gcp.InstanceRequest) (*gcp.Instance, error) { @@ -2471,6 +2529,37 @@ func TestGCPVMDiscovery(t *testing.T) { staticMatchers: defaultStaticMatcher, wantInstalledInstances: []string{"myinstance"}, }, + { + name: "no nodes present, 2 found for different projects", + presentVMs: []types.Server{}, + foundGCPVMs: []*gcp.Instance{ + { + ProjectID: "p1", + Zone: "myzone", + Name: "myinstance1", + Labels: map[string]string{ + "teleport": "yes", + }, + }, + { + ProjectID: "p2", + Zone: "myzone", + Name: "myinstance2", + Labels: map[string]string{ + "teleport": "yes", + }, + }, + }, + staticMatchers: Matchers{ + GCP: []types.GCPMatcher{{ + Types: []string{"gce"}, + ProjectIDs: []string{"*"}, + Locations: []string{"myzone"}, + Labels: types.Labels{"teleport": {"yes"}}, + }}, + }, + wantInstalledInstances: []string{"myinstance1", "myinstance2"}, + }, { name: "nodes present, instance filtered", presentVMs: []types.Server{ @@ -2556,6 +2645,7 @@ func TestGCPVMDiscovery(t *testing.T) { GCPInstances: &mockGCPClient{ vms: tc.foundGCPVMs, }, + GCPProjects: newPopulatedGCPProjectsMock(), } ctx := context.Background() @@ -2800,3 +2890,27 @@ func (m fakeWatcher) Close() error { func (m fakeWatcher) Error() error { return nil } + +type mockProjectsAPI struct { + gcp.ProjectsClient + projects []gcp.Project +} + +func (m *mockProjectsAPI) ListProjects(ctx context.Context) ([]gcp.Project, error) { + return m.projects, nil +} + +func newPopulatedGCPProjectsMock() *mockProjectsAPI { + return &mockProjectsAPI{ + projects: []gcp.Project{ + { + ID: "p1", + Name: "project1", + }, + { + ID: "p2", + Name: "project2", + }, + }, + } +} diff --git a/lib/srv/discovery/fetchers/gke.go b/lib/srv/discovery/fetchers/gke.go index 402d4233ccdb4..e4988a9ba456c 100644 --- a/lib/srv/discovery/fetchers/gke.go +++ b/lib/srv/discovery/fetchers/gke.go @@ -32,8 +32,10 @@ import ( // GKEFetcherConfig configures the GKE fetcher. type GKEFetcherConfig struct { - // Client is the GCP GKE client. - Client gcp.GKEClient + // GKEClient is the GCP GKE client. + GKEClient gcp.GKEClient + // ProjectClient is the GCP project client. + ProjectClient gcp.ProjectsClient // ProjectID is the projectID the cluster should belong to. ProjectID string // Location is the GCP's location where the clusters should be located. @@ -47,9 +49,12 @@ type GKEFetcherConfig struct { // CheckAndSetDefaults validates and sets the defaults values. func (c *GKEFetcherConfig) CheckAndSetDefaults() error { - if c.Client == nil { + if c.GKEClient == nil { return trace.BadParameter("missing Client field") } + if c.ProjectClient == nil { + return trace.BadParameter("missing ProjectClient field") + } if len(c.Location) == 0 { return trace.BadParameter("missing Location field") } @@ -70,7 +75,7 @@ type gkeFetcher struct { } // NewGKEFetcher creates a new GKE fetcher configuration. -func NewGKEFetcher(cfg GKEFetcherConfig) (common.Fetcher, error) { +func NewGKEFetcher(ctx context.Context, cfg GKEFetcherConfig) (common.Fetcher, error) { if err := cfg.CheckAndSetDefaults(); err != nil { return nil, trace.Wrap(err) } @@ -79,19 +84,31 @@ func NewGKEFetcher(cfg GKEFetcherConfig) (common.Fetcher, error) { } func (a *gkeFetcher) Get(ctx context.Context) (types.ResourcesWithLabels, error) { - clusters, err := a.getGKEClusters(ctx) + + // Get the project IDs that this fetcher is configured to query. + projectIDs, err := a.getProjectIDs(ctx) if err != nil { return nil, trace.Wrap(err) } + a.Log.Debugf("Fetching GKE clusters for project IDs: %v", projectIDs) + var clusters types.KubeClusters + for _, projectID := range projectIDs { + lClusters, err := a.getGKEClusters(ctx, projectID) + if err != nil { + return nil, trace.Wrap(err) + } + clusters = append(clusters, lClusters...) + } + a.rewriteKubeClusters(clusters) return clusters.AsResources(), nil } -func (a *gkeFetcher) getGKEClusters(ctx context.Context) (types.KubeClusters, error) { +func (a *gkeFetcher) getGKEClusters(ctx context.Context, projectID string) (types.KubeClusters, error) { var clusters types.KubeClusters - gkeClusters, err := a.Client.ListClusters(ctx, a.ProjectID, a.Location) + gkeClusters, err := a.GKEClient.ListClusters(ctx, projectID, a.Location) for _, gkeCluster := range gkeClusters { cluster, err := a.getMatchingKubeCluster(gkeCluster) // trace.CompareFailed is returned if the cluster did not match the matcher filtering labels @@ -157,3 +174,23 @@ func (a *gkeFetcher) getMatchingKubeCluster(gkeCluster gcp.GKECluster) (types.Ku return cluster, nil } + +// getProjectIDs returns the project ids that this fetcher is configured to query. +// This will make an API call to list project IDs when the fetcher is configured to match "*" projectID, +// in order to discover and query new projectID. +// Otherwise, a list containing the fetcher's non-wildcard project is returned. +func (a *gkeFetcher) getProjectIDs(ctx context.Context) ([]string, error) { + if a.ProjectID != types.Wildcard { + return []string{a.ProjectID}, nil + } + + gcpProjects, err := a.ProjectClient.ListProjects(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + var projectIDs []string + for _, prj := range gcpProjects { + projectIDs = append(projectIDs, prj.ID) + } + return projectIDs, nil +} diff --git a/lib/srv/discovery/fetchers/gke_test.go b/lib/srv/discovery/fetchers/gke_test.go index ee5dbfaa43c31..c85f08ba982bf 100644 --- a/lib/srv/discovery/fetchers/gke_test.go +++ b/lib/srv/discovery/fetchers/gke_test.go @@ -33,6 +33,7 @@ func TestGKEFetcher(t *testing.T) { type args struct { location string filterLabels types.Labels + projectID string } tests := []struct { name string @@ -46,8 +47,9 @@ func TestGKEFetcher(t *testing.T) { filterLabels: types.Labels{ types.Wildcard: []string{types.Wildcard}, }, + projectID: "p1", }, - want: gkeClustersToResources(t, gkeMockClusters...), + want: gkeClustersToResources(t, gkeMockClusters[:4]...), }, { name: "list prod clusters", @@ -56,6 +58,7 @@ func TestGKEFetcher(t *testing.T) { filterLabels: types.Labels{ "env": []string{"prod"}, }, + projectID: "p1", }, want: gkeClustersToResources(t, gkeMockClusters[:2]...), }, @@ -67,8 +70,9 @@ func TestGKEFetcher(t *testing.T) { "env": []string{"stg"}, "location": []string{"central-1"}, }, + projectID: "p1", }, - want: gkeClustersToResources(t, gkeMockClusters[2:]...), + want: gkeClustersToResources(t, gkeMockClusters[2:4]...), }, { name: "filter not found", @@ -77,6 +81,7 @@ func TestGKEFetcher(t *testing.T) { filterLabels: types.Labels{ "env": []string{"none"}, }, + projectID: "p1", }, want: gkeClustersToResources(t), }, @@ -88,6 +93,18 @@ func TestGKEFetcher(t *testing.T) { filterLabels: types.Labels{ "env": []string{"prod", "stg"}, }, + projectID: "p1", + }, + want: gkeClustersToResources(t, gkeMockClusters[:4]...), + }, + { + name: "list everything with wildcard project", + args: args{ + location: "uswest2", + filterLabels: types.Labels{ + "env": []string{"prod", "stg"}, + }, + projectID: "*", }, want: gkeClustersToResources(t, gkeMockClusters...), }, @@ -95,12 +112,14 @@ func TestGKEFetcher(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cfg := GKEFetcherConfig{ - Client: newPopulatedGCPMock(), - FilterLabels: tt.args.filterLabels, - Location: tt.args.location, - Log: logrus.New(), + GKEClient: newPopulatedGCPMock(), + ProjectClient: newPopulatedGCPProjectsMock(), + FilterLabels: tt.args.filterLabels, + Location: tt.args.location, + ProjectID: tt.args.projectID, + Log: logrus.New(), } - fetcher, err := NewGKEFetcher(cfg) + fetcher, err := NewGKEFetcher(context.Background(), cfg) require.NoError(t, err) resources, err := fetcher.Get(context.Background()) require.NoError(t, err) @@ -116,7 +135,15 @@ type mockGKEAPI struct { } func (m *mockGKEAPI) ListClusters(ctx context.Context, projectID string, location string) ([]gcp.GKECluster, error) { - return m.clusters, nil + var clusters []gcp.GKECluster + for _, cluster := range m.clusters { + if cluster.ProjectID != projectID { + continue + } + clusters = append(clusters, cluster) + } + + return clusters, nil } func newPopulatedGCPMock() *mockGKEAPI { @@ -170,6 +197,28 @@ var gkeMockClusters = []gcp.GKECluster{ Location: "central-1", Description: "desc1", }, + { + Name: "cluster5", + Status: containerpb.Cluster_RUNNING, + Labels: map[string]string{ + "env": "stg", + "location": "central-1", + }, + ProjectID: "p2", + Location: "central-1", + Description: "desc1", + }, + { + Name: "cluster6", + Status: containerpb.Cluster_RUNNING, + Labels: map[string]string{ + "env": "stg", + "location": "central-1", + }, + ProjectID: "p2", + Location: "central-1", + Description: "desc1", + }, } func gkeClustersToResources(t *testing.T, clusters ...gcp.GKECluster) types.ResourcesWithLabels { @@ -183,3 +232,27 @@ func gkeClustersToResources(t *testing.T, clusters ...gcp.GKECluster) types.Reso } return kubeClusters.AsResources() } + +type mockProjectsAPI struct { + gcp.ProjectsClient + projects []gcp.Project +} + +func (m *mockProjectsAPI) ListProjects(ctx context.Context) ([]gcp.Project, error) { + return m.projects, nil +} + +func newPopulatedGCPProjectsMock() *mockProjectsAPI { + return &mockProjectsAPI{ + projects: []gcp.Project{ + { + ID: "p1", + Name: "project1", + }, + { + ID: "p2", + Name: "project2", + }, + }, + } +} diff --git a/lib/srv/server/gcp_watcher.go b/lib/srv/server/gcp_watcher.go index 7e3aa35ba360a..2ad34aea49225 100644 --- a/lib/srv/server/gcp_watcher.go +++ b/lib/srv/server/gcp_watcher.go @@ -88,13 +88,14 @@ func NewGCPWatcher(ctx context.Context, fetchersFn func() []Fetcher, opts ...Opt } // MatchersToGCPInstanceFetchers converts a list of GCP GCE Matchers into a list of GCP GCE Fetchers. -func MatchersToGCPInstanceFetchers(matchers []types.GCPMatcher, gcpClient gcp.InstancesClient) []Fetcher { +func MatchersToGCPInstanceFetchers(matchers []types.GCPMatcher, gcpClient gcp.InstancesClient, projectsClient gcp.ProjectsClient) []Fetcher { fetchers := make([]Fetcher, 0, len(matchers)) for _, matcher := range matchers { fetchers = append(fetchers, newGCPInstanceFetcher(gcpFetcherConfig{ - Matcher: matcher, - GCPClient: gcpClient, + Matcher: matcher, + GCPClient: gcpClient, + projectsClient: projectsClient, })) } @@ -102,8 +103,9 @@ func MatchersToGCPInstanceFetchers(matchers []types.GCPMatcher, gcpClient gcp.In } type gcpFetcherConfig struct { - Matcher types.GCPMatcher - GCPClient gcp.InstancesClient + Matcher types.GCPMatcher + GCPClient gcp.InstancesClient + projectsClient gcp.ProjectsClient } type gcpInstanceFetcher struct { @@ -114,6 +116,7 @@ type gcpInstanceFetcher struct { ServiceAccounts []string Labels types.Labels Parameters map[string]string + projectsClient gcp.ProjectsClient } func newGCPInstanceFetcher(cfg gcpFetcherConfig) *gcpInstanceFetcher { @@ -123,6 +126,7 @@ func newGCPInstanceFetcher(cfg gcpFetcherConfig) *gcpInstanceFetcher { ProjectIDs: cfg.Matcher.ProjectIDs, ServiceAccounts: cfg.Matcher.ServiceAccounts, Labels: cfg.Matcher.GetLabels(), + projectsClient: cfg.projectsClient, } if cfg.Matcher.Params != nil { fetcher.Parameters = map[string]string{ @@ -142,7 +146,11 @@ func (*gcpInstanceFetcher) GetMatchingInstances(_ []types.Server, _ bool) ([]Ins func (f *gcpInstanceFetcher) GetInstances(ctx context.Context, _ bool) ([]Instances, error) { // Key by project ID, then by zone. instanceMap := make(map[string]map[string][]*gcp.Instance) - for _, projectID := range f.ProjectIDs { + projectIDs, err := f.getProjectIDs(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + for _, projectID := range projectIDs { instanceMap[projectID] = make(map[string][]*gcp.Instance) for _, zone := range f.Zones { instanceMap[projectID][zone] = make([]*gcp.Instance, 0) @@ -182,3 +190,23 @@ func (f *gcpInstanceFetcher) GetInstances(ctx context.Context, _ bool) ([]Instan return instances, nil } + +// getProjectIDs returns the project ids that this fetcher is configured to query. +// This will make an API call to list project IDs when the fetcher is configured to match "*" projectID, +// in order to discover and query new projectID. +// Otherwise, a list containing the fetcher's non-wildcard project is returned. +func (f *gcpInstanceFetcher) getProjectIDs(ctx context.Context) ([]string, error) { + if len(f.ProjectIDs) != 1 || len(f.ProjectIDs) == 1 && f.ProjectIDs[0] != types.Wildcard { + return f.ProjectIDs, nil + } + + gcpProjects, err := f.projectsClient.ListProjects(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + var projectIDs []string + for _, prj := range gcpProjects { + projectIDs = append(projectIDs, prj.ID) + } + return projectIDs, nil +} From 157e73fc411df5ac9c0e1c3ca225fbfa7b07e2cd Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Thu, 17 Oct 2024 00:08:12 +0000 Subject: [PATCH 16/53] Adds support to Firestore backends for custom databases (#47540) (#47585) Firestore has supported multiple databases in a project for a while (see https://cloud.google.com/blog/products/databases/manage-multiple-firestore-databases-in-a-project), however, Teleport only allowed using the default database in the project. The DatabaseID is now exposed in the file config, and if provided both the state and events backends will use the appropriate database. Closes https://github.com/gravitational/teleport/issues/37227. --- docs/pages/reference/backends.mdx | 6 +++- lib/backend/firestore/firestorebk.go | 29 ++++++++++++++----- lib/events/firestoreevents/firestoreevents.go | 8 +++-- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/docs/pages/reference/backends.mdx b/docs/pages/reference/backends.mdx index 2bd9aad5f3aca..8b0b73ecb3465 100644 --- a/docs/pages/reference/backends.mdx +++ b/docs/pages/reference/backends.mdx @@ -1270,6 +1270,10 @@ teleport: # Name of the Firestore table. collection_name: Example_TELEPORT_FIRESTORE_TABLE_NAME + # An optional database id to use. If not provided the default + # database for the project is used. + database_id: Example_TELEPORT_FIRESTORE_DATABASE_ID + credentials_path: /var/lib/teleport/gcs_creds # This setting configures Teleport to send the audit events to three places: @@ -1278,7 +1282,7 @@ teleport: # database table, so attempting to use the same table for both will result in errors. # When using highly available storage like Firestore, you should make sure that the list always specifies # the High Availability storage method first, as this is what the Teleport web UI uses as its source of events to display. - audit_events_uri: ['firestore://Example_TELEPORT_FIRESTORE_EVENTS_TABLE_NAME', 'file:///var/lib/teleport/audit/events', 'stdout://'] + audit_events_uri: ['firestore://Example_TELEPORT_FIRESTORE_EVENTS_TABLE_NAME?projectID=$PROJECT_ID&credentialsPath=$CREDENTIALS_PATH&databaseID=$DATABASE_ID', 'file:///var/lib/teleport/audit/events', 'stdout://'] # This setting configures Teleport to save the recorded sessions in GCP storage: audit_sessions_uri: gs://Example_TELEPORT_GCS_BUCKET/records diff --git a/lib/backend/firestore/firestorebk.go b/lib/backend/firestore/firestorebk.go index 3056b245c6ae9..c7483aede60f8 100644 --- a/lib/backend/firestore/firestorebk.go +++ b/lib/backend/firestore/firestorebk.go @@ -16,6 +16,7 @@ package firestore import ( "bytes" + "cmp" "context" "encoding/base64" "errors" @@ -57,6 +58,9 @@ type Config struct { DisableExpiredDocumentPurge bool `json:"disable_expired_document_purge,omitempty"` // EndPoint is used to point the Firestore clients at emulated Firestore storage. EndPoint string `json:"endpoint,omitempty"` + // DatabaseID is the identifier of a specific Firestore database to use. If not specified, the + // default database for the ProjectID is used. + DatabaseID string `json:"database_id,omitempty"` } type backendConfig struct { @@ -265,14 +269,14 @@ func (t ownerCredentials) GetRequestMetadata(context.Context, ...string) (map[st func (t ownerCredentials) RequireTransportSecurity() bool { return false } // CreateFirestoreClients creates a firestore admin and normal client given the supplied parameters -func CreateFirestoreClients(ctx context.Context, projectID string, endPoint string, credentialsFile string) (*apiv1.FirestoreAdminClient, *firestore.Client, error) { +func CreateFirestoreClients(ctx context.Context, projectID, database string, endpoint string, credentialsFile string) (*apiv1.FirestoreAdminClient, *firestore.Client, error) { var args []option.ClientOption - if endPoint != "" { + if endpoint != "" { args = append(args, option.WithTelemetryDisabled(), option.WithoutAuthentication(), - option.WithEndpoint(endPoint), + option.WithEndpoint(endpoint), option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())), option.WithGRPCDialOption(grpc.WithPerRPCCredentials(ownerCredentials{})), ) @@ -280,11 +284,21 @@ func CreateFirestoreClients(ctx context.Context, projectID string, endPoint stri args = append(args, option.WithCredentialsFile(credentialsFile)) } - firestoreClient, err := firestore.NewClient(ctx, projectID, args...) + firestoreAdminClient, err := apiv1.NewFirestoreAdminClient(ctx, args...) if err != nil { return nil, nil, ConvertGRPCError(err) } - firestoreAdminClient, err := apiv1.NewFirestoreAdminClient(ctx, args...) + + if database == "" { + firestoreClient, err := firestore.NewClient(ctx, projectID, args...) + if err != nil { + return nil, nil, ConvertGRPCError(err) + } + + return firestoreAdminClient, firestoreClient, nil + } + + firestoreClient, err := firestore.NewClientWithDatabase(ctx, projectID, database, args...) if err != nil { return nil, nil, ConvertGRPCError(err) } @@ -328,7 +342,7 @@ func New(ctx context.Context, params backend.Params, options Options) (*Backend, } closeCtx, cancel := context.WithCancel(ctx) - firestoreAdminClient, firestoreClient, err := CreateFirestoreClients(closeCtx, cfg.ProjectID, cfg.EndPoint, cfg.CredentialsPath) + firestoreAdminClient, firestoreClient, err := CreateFirestoreClients(closeCtx, cfg.ProjectID, cfg.DatabaseID, cfg.EndPoint, cfg.CredentialsPath) if err != nil { cancel() return nil, trace.Wrap(err) @@ -886,7 +900,8 @@ func ConvertGRPCError(err error, args ...interface{}) error { } func (b *Backend) getIndexParent() string { - return "projects/" + b.ProjectID + "/databases/(default)/collectionGroups/" + b.CollectionName + database := cmp.Or(b.backendConfig.Config.DatabaseID, "(default)") + return "projects/" + b.ProjectID + "/databases/" + database + "/collectionGroups/" + b.CollectionName } func (b *Backend) ensureIndexes(adminSvc *apiv1.FirestoreAdminClient) error { diff --git a/lib/events/firestoreevents/firestoreevents.go b/lib/events/firestoreevents/firestoreevents.go index 8caea7ac46e73..fe6ab9a0e267c 100644 --- a/lib/events/firestoreevents/firestoreevents.go +++ b/lib/events/firestoreevents/firestoreevents.go @@ -15,6 +15,7 @@ package firestoreevents import ( + "cmp" "context" "encoding/json" "errors" @@ -198,6 +199,8 @@ func (cfg *EventsConfig) SetFromURL(url *url.URL) error { } cfg.ProjectID = projectIDParamString + cfg.DatabaseID = url.Query().Get("databaseID") + eventRetentionPeriodParamString := url.Query().Get(eventRetentionPeriodPropertyKey) if eventRetentionPeriodParamString == "" { cfg.RetentionPeriod = defaultEventRetentionPeriod @@ -282,7 +285,7 @@ func New(cfg EventsConfig) (*Log, error) { }) l.Info("Initializing event backend.") closeCtx, cancel := context.WithCancel(context.Background()) - firestoreAdminClient, firestoreClient, err := firestorebk.CreateFirestoreClients(closeCtx, cfg.ProjectID, cfg.EndPoint, cfg.CredentialsPath) + firestoreAdminClient, firestoreClient, err := firestorebk.CreateFirestoreClients(closeCtx, cfg.ProjectID, cfg.DatabaseID, cfg.EndPoint, cfg.CredentialsPath) if err != nil { cancel() return nil, trace.Wrap(err) @@ -572,7 +575,8 @@ type searchEventsFilter struct { } func (l *Log) getIndexParent() string { - return "projects/" + l.ProjectID + "/databases/(default)/collectionGroups/" + l.CollectionName + database := cmp.Or(l.Config.DatabaseID, "(default)") + return "projects/" + l.ProjectID + "/databases/" + database + "/collectionGroups/" + l.CollectionName } func (l *Log) ensureIndexes(adminSvc *apiv1.FirestoreAdminClient) error { From b6fa86012bdedb4b5ab04ed606961e0bb5136f0a Mon Sep 17 00:00:00 2001 From: Paul Gottschling Date: Thu, 17 Oct 2024 08:55:50 -0400 Subject: [PATCH 17/53] Clarify lookups of Microsoft IdP attributes (#47597) Closes #19118 Edit the Role Reference, Azure AD, and AD FS guides to explain that you must use bracket notation to look up Azure AD and AD FS attributes in roles. --- .../admin-guides/access-controls/sso/adfs.mdx | 7 +++--- .../access-controls/sso/azuread.mdx | 24 ++++++++++++++----- .../pages/reference/access-controls/roles.mdx | 10 ++++++-- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/docs/pages/admin-guides/access-controls/sso/adfs.mdx b/docs/pages/admin-guides/access-controls/sso/adfs.mdx index b75d19bc9b374..d65c7e22bce02 100644 --- a/docs/pages/admin-guides/access-controls/sso/adfs.mdx +++ b/docs/pages/admin-guides/access-controls/sso/adfs.mdx @@ -130,9 +130,10 @@ The login `{{external["http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]}}` configures Teleport to look at the `http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname` -ADFS claim and use that field as an allowed login for each user. Note the -double quotes (`"`) and square brackets (`[]`) around the claim name—these are -important. +attribute and use that field as an allowed login for each user. Since the name +of the attribute contains characters besides letters, numbers, and underscores, +you must use double quotes (`"`) and square brackets (`[]`) around the name of +the attribute. ## Step 3/3. Create a SAML connector diff --git a/docs/pages/admin-guides/access-controls/sso/azuread.mdx b/docs/pages/admin-guides/access-controls/sso/azuread.mdx index 3d488c583bcd9..44cd2ba25d8fa 100644 --- a/docs/pages/admin-guides/access-controls/sso/azuread.mdx +++ b/docs/pages/admin-guides/access-controls/sso/azuread.mdx @@ -173,10 +173,7 @@ $ tctl create -f azure-connector.yaml Create a Teleport role resource that will use external username data from the Azure AD connector to determine which Linux logins to allow on a host. -Users with the following `dev` role are only allowed to log in to nodes with -the `access: relaxed` Teleport label. They can log in as either `ubuntu` or a -username that is passed in from the Azure AD connector. Users with this role can't -obtain admin access to Teleport. +Create a file called `dev.yaml` with the following content: ```yaml kind: role @@ -187,12 +184,27 @@ spec: options: max_session_ttl: 24h allow: - logins: [ "{{external.username}}", ubuntu ] + # only allow login as either ubuntu or the 'windowsaccountname' claim + logins: [ '{{external["http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]}}', ubuntu ] node_labels: access: relaxed ``` -Replace `ubuntu` with the Linux login available on your servers. +Users with the `dev` role are only allowed to log in to nodes with the `access: +relaxed` Teleport label. They can log in as either `ubuntu` or a username that +is passed in from the Azure AD connector using the `windowsaccountname` +attribute. + +The login +`{{external["http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]}}` +configures Teleport to look at the +`http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname` +attribute and use that field as an allowed login for each user. Since the name +of the attribute contains characters besides letters, numbers, and underscores, +you must use double quotes (`"`) and square brackets (`[]`) around the name of +the attribute. + +Create the role: ```code $ tctl create dev.yaml diff --git a/docs/pages/reference/access-controls/roles.mdx b/docs/pages/reference/access-controls/roles.mdx index 267c8c03057b6..48737637e5552 100644 --- a/docs/pages/reference/access-controls/roles.mdx +++ b/docs/pages/reference/access-controls/roles.mdx @@ -558,7 +558,6 @@ logins: - '{{external["http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]}}' ``` - In role templates, you can refer to these variables using the following two formats, where `trait` is the name of the trait: @@ -570,7 +569,14 @@ attribute or OIDC claim called `trait`. You can specify an external trait in dot syntax if it begins with a letter and contains only letters, numbers, and underscores. Otherwise, you must use bracket -syntax to specify a trait. +syntax to specify a trait. + +When using Azure AD or ADFS as your IdP, you must use bracket notation, as these +IdPs assign attribute keys to URLs such as the following: + +```text +http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname +``` Common examples of external traits available through an identity provider include the following: From 4ddc0690b27a5c5190d55d31cd96b930e89c8f8e Mon Sep 17 00:00:00 2001 From: Alan Parra Date: Thu, 17 Oct 2024 14:00:14 -0300 Subject: [PATCH 18/53] Check bounds of tpm.EKs() slice before indexing (#47669) --- lib/tpm/tpm.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/tpm/tpm.go b/lib/tpm/tpm.go index b720df596a822..6175efdedb018 100644 --- a/lib/tpm/tpm.go +++ b/lib/tpm/tpm.go @@ -125,6 +125,11 @@ func QueryWithTPM( if err != nil { return nil, trace.Wrap(err, "querying EKs") } + // Be a good citizen and check the slice bounds. This is not expected to + // happen. + if len(eks) == 0 { + return nil, trace.BadParameter("no endorsement keys found in tpm") + } // The first EK returned by `go-attestation` will be an RSA based EK key or // EK cert. On Windows, ECC certs may also be returned following this. At From c786a24819c360eff6ccc81f0b0c275dfd6512f1 Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Thu, 17 Oct 2024 19:33:02 +0000 Subject: [PATCH 19/53] Display more user friendly error for invalid os logins in Web UI (#47420) (#47603) Host resolution no longer happens prior to connections in the Web UI and all dial attempts are by UUID. When an invalid login is attempted the error message constructed only contained the info provided to the dial request: a login and a Host UUID. To make this error more user friendly access denied errors are now augmented with the hostname if the user has permissions to that host and the error occurs only due to an invalid login. Closes https://github.com/gravitational/teleport/issues/23719 --- lib/web/apiserver_test.go | 4 ++++ lib/web/terminal.go | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/lib/web/apiserver_test.go b/lib/web/apiserver_test.go index 2c77ee51a0e3a..f038cf502625c 100644 --- a/lib/web/apiserver_test.go +++ b/lib/web/apiserver_test.go @@ -7451,6 +7451,10 @@ type authProviderMock struct { server types.ServerV2 } +func (mock authProviderMock) ListUnifiedResources(ctx context.Context, req *authproto.ListUnifiedResourcesRequest) (*authproto.ListUnifiedResourcesResponse, error) { + return nil, nil +} + func (mock authProviderMock) GetNodes(ctx context.Context, n string) ([]types.Server, error) { return []types.Server{&mock.server}, nil } diff --git a/lib/web/terminal.go b/lib/web/terminal.go index 2117a79a639ac..690e6884fce0a 100644 --- a/lib/web/terminal.go +++ b/lib/web/terminal.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "io" "net" "net/http" @@ -100,6 +101,7 @@ type AuthProvider interface { IsMFARequired(ctx context.Context, req *authproto.IsMFARequiredRequest) (*authproto.IsMFARequiredResponse, error) GenerateUserSingleUseCerts(ctx context.Context) (authproto.AuthService_GenerateUserSingleUseCertsClient, error) MaintainSessionPresence(ctx context.Context) (authproto.AuthService_MaintainSessionPresenceClient, error) + ListUnifiedResources(ctx context.Context, req *authproto.ListUnifiedResourcesRequest) (*authproto.ListUnifiedResourcesResponse, error) } // NewTerminal creates a web-based terminal based on WebSockets and returns a @@ -885,6 +887,21 @@ func (t *sshBaseHandler) connectToNode(ctx context.Context, ws terminal.WSConn, // The close error is ignored instead of using [trace.NewAggregate] because // aggregate errors do not allow error inspection with things like [trace.IsAccessDenied]. _ = conn.Close() + + // Since connection attempts are made via UUID and not hostname, any access denied errors + // will not contain the resolved host address. To provide an easier troubleshooting experience + // for users, attempt to resolve the hostname of the server and augment the error message with it. + if trace.IsAccessDenied(err) { + if resp, err := t.authProvider.ListUnifiedResources(ctx, &authproto.ListUnifiedResourcesRequest{ + SortBy: types.SortBy{Field: types.ResourceKind}, + Kinds: []string{types.KindNode}, + Limit: 1, + PredicateExpression: fmt.Sprintf(`resource.metadata.name == "%s"`, t.sessionData.ServerID), + }); err == nil && len(resp.Resources) > 0 { + return nil, trace.AccessDenied("access denied to %q connecting to %v", sshConfig.User, resp.Resources[0].GetNode().GetHostname()) + } + } + return nil, trace.Wrap(err) } From 3e4ee24509ce39fe28f9abcf595a1b5230e70736 Mon Sep 17 00:00:00 2001 From: Alan Parra Date: Thu, 17 Oct 2024 17:45:54 -0300 Subject: [PATCH 20/53] [v14] chore: Bump e/ reference (#47688) --- e | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e b/e index 633e4577a551c..93725f2cb251a 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit 633e4577a551c2d8ddd4564f932c012cf74e33ce +Subproject commit 93725f2cb251a4a590afee935c63a9809c77f1f8 From 2c5f0c0aa484725f70015c4379016781ee30a4e3 Mon Sep 17 00:00:00 2001 From: Alan Parra Date: Fri, 18 Oct 2024 12:45:34 -0300 Subject: [PATCH 21/53] fix: Avoid needless user escalation during auto-enroll (#47676) (#47697) --- lib/devicetrust/enroll/auto_enroll.go | 19 ++++++++++--------- lib/devicetrust/enroll/auto_enroll_test.go | 1 - lib/devicetrust/enroll/enroll.go | 11 ++++++++++- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/devicetrust/enroll/auto_enroll.go b/lib/devicetrust/enroll/auto_enroll.go index 7eaf469ac4452..d6b9588198d51 100644 --- a/lib/devicetrust/enroll/auto_enroll.go +++ b/lib/devicetrust/enroll/auto_enroll.go @@ -20,22 +20,18 @@ import ( "github.com/gravitational/trace" devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" - "github.com/gravitational/teleport/lib/devicetrust/native" ) // AutoEnrollCeremony is the auto-enrollment version of [Ceremony]. type AutoEnrollCeremony struct { *Ceremony - - CollectDeviceData func() (*devicepb.DeviceCollectedData, error) } // NewAutoEnrollCeremony creates a new [AutoEnrollCeremony] based on the regular // ceremony provided by [NewCeremony]. func NewAutoEnrollCeremony() *AutoEnrollCeremony { return &AutoEnrollCeremony{ - Ceremony: NewCeremony(), - CollectDeviceData: native.CollectDeviceData, + Ceremony: NewCeremony(), } } @@ -49,18 +45,23 @@ func AutoEnroll(ctx context.Context, devicesClient devicepb.DeviceTrustServiceCl // [devicepb.DeviceTrustServiceClient.CreateDeviceEnrollToken] and enrolls the // device using a regular [Ceremony]. func (c *AutoEnrollCeremony) Run(ctx context.Context, devicesClient devicepb.DeviceTrustServiceClient) (*devicepb.Device, error) { - cd, err := c.CollectDeviceData() + // Creating the init message straight away aborts the process cleanly if the + // device cannot create the device key (for example, if it lacks a TPM). + // This avoids a situation where we ask for escalation, like a sudo prompt or + // admin credentials, then fail a few steps after the prompt. + init, err := c.EnrollDeviceInit() if err != nil { - return nil, trace.Wrap(err, "collecting device data") + return nil, trace.Wrap(err) } token, err := devicesClient.CreateDeviceEnrollToken(ctx, &devicepb.CreateDeviceEnrollTokenRequest{ - DeviceData: cd, + DeviceData: init.DeviceData, }) if err != nil { return nil, trace.Wrap(err, "creating auto-token") } + init.Token = token.Token - dev, err := c.Ceremony.Run(ctx, devicesClient, false, token.Token) + dev, err := c.run(ctx, devicesClient, false /* debug */, init) return dev, trace.Wrap(err) } diff --git a/lib/devicetrust/enroll/auto_enroll_test.go b/lib/devicetrust/enroll/auto_enroll_test.go index 292268cc5ba7c..71a958274bdb6 100644 --- a/lib/devicetrust/enroll/auto_enroll_test.go +++ b/lib/devicetrust/enroll/auto_enroll_test.go @@ -55,7 +55,6 @@ func TestAutoEnrollCeremony_Run(t *testing.T) { SignChallenge: test.dev.SignChallenge, SolveTPMEnrollChallenge: test.dev.SolveTPMEnrollChallenge, }, - CollectDeviceData: test.dev.CollectDeviceData, } dev, err := c.Run(ctx, devices) diff --git a/lib/devicetrust/enroll/enroll.go b/lib/devicetrust/enroll/enroll.go index cd36a170b2a4d..b96d1fdc102de 100644 --- a/lib/devicetrust/enroll/enroll.go +++ b/lib/devicetrust/enroll/enroll.go @@ -182,6 +182,15 @@ func (c *Ceremony) Run(ctx context.Context, devicesClient devicepb.DeviceTrustSe } init.Token = enrollToken + return c.run(ctx, devicesClient, debug, init) +} + +func (c *Ceremony) run(ctx context.Context, devicesClient devicepb.DeviceTrustServiceClient, debug bool, init *devicepb.EnrollDeviceInit) (*devicepb.Device, error) { + // Sanity check. + if init.GetToken() == "" { + return nil, trace.BadParameter("enroll init message lacks enrollment token") + } + // 1. Init. stream, err := devicesClient.EnrollDevice(ctx) if err != nil { @@ -201,7 +210,7 @@ func (c *Ceremony) Run(ctx context.Context, devicesClient devicepb.DeviceTrustSe // Unimplemented errors are not expected to happen after this point. // 2. Challenge. - switch osType { + switch c.GetDeviceOSType() { case devicepb.OSType_OS_TYPE_MACOS: err = c.enrollDeviceMacOS(stream, resp) // err handled below From 6f2fdf7c238f5ddfbfcc5c1ece750a58d2fbff4c Mon Sep 17 00:00:00 2001 From: Alan Parra Date: Fri, 18 Oct 2024 18:32:12 -0300 Subject: [PATCH 22/53] [v14] feat: Disable auto-enroll via environment variable (#47718) * feat: Disable auto-enroll via environment variable * Fix TestAutoEnroll_disabledByEnv flakiness (#47723) * Fix TestAutoEnroll_disabledByEnv flakiness * Use t.Setenv Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com> --------- Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com> --------- Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com> --- lib/devicetrust/enroll/auto_enroll.go | 14 ++++++++++++++ lib/devicetrust/enroll/auto_enroll_test.go | 7 +++++++ 2 files changed, 21 insertions(+) diff --git a/lib/devicetrust/enroll/auto_enroll.go b/lib/devicetrust/enroll/auto_enroll.go index d6b9588198d51..75ead77630d58 100644 --- a/lib/devicetrust/enroll/auto_enroll.go +++ b/lib/devicetrust/enroll/auto_enroll.go @@ -16,12 +16,21 @@ package enroll import ( "context" + "errors" + "os" + "strconv" "github.com/gravitational/trace" devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" ) +// ErrAutoEnrollDisabled signifies that auto-enroll is disabled in the current +// device. +// Setting the TELEPORT_DEVICE_AUTO_ENROLL_DISABLED=1 environment disables +// auto-enroll. +var ErrAutoEnrollDisabled = errors.New("auto-enroll disabled") + // AutoEnrollCeremony is the auto-enrollment version of [Ceremony]. type AutoEnrollCeremony struct { *Ceremony @@ -45,6 +54,11 @@ func AutoEnroll(ctx context.Context, devicesClient devicepb.DeviceTrustServiceCl // [devicepb.DeviceTrustServiceClient.CreateDeviceEnrollToken] and enrolls the // device using a regular [Ceremony]. func (c *AutoEnrollCeremony) Run(ctx context.Context, devicesClient devicepb.DeviceTrustServiceClient) (*devicepb.Device, error) { + const autoEnrollDisabledKey = "TELEPORT_DEVICE_AUTO_ENROLL_DISABLED" + if disabled, _ := strconv.ParseBool(os.Getenv(autoEnrollDisabledKey)); disabled { + return nil, trace.Wrap(ErrAutoEnrollDisabled) + } + // Creating the init message straight away aborts the process cleanly if the // device cannot create the device key (for example, if it lacks a TPM). // This avoids a situation where we ask for escalation, like a sudo prompt or diff --git a/lib/devicetrust/enroll/auto_enroll_test.go b/lib/devicetrust/enroll/auto_enroll_test.go index 71a958274bdb6..e8a788dc31da8 100644 --- a/lib/devicetrust/enroll/auto_enroll_test.go +++ b/lib/devicetrust/enroll/auto_enroll_test.go @@ -63,3 +63,10 @@ func TestAutoEnrollCeremony_Run(t *testing.T) { }) } } + +func TestAutoEnroll_disabledByEnv(t *testing.T) { + t.Setenv("TELEPORT_DEVICE_AUTO_ENROLL_DISABLED", "1") + + _, err := enroll.AutoEnroll(context.Background(), nil /* devicesClient */) + assert.ErrorIs(t, err, enroll.ErrAutoEnrollDisabled, "AutoEnroll() error mismatch") +} From 59e4c0b0e7cfd318f8dd79771c593a28b7740dbd Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Mon, 21 Oct 2024 15:35:25 +0200 Subject: [PATCH 23/53] Replace logo in webauthn graphics (#47739) (#47746) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Replace webauthn graphics with a new logo * `Login to` -> `Log in to` * Run the svg through ImageOptim --------- Co-authored-by: Rafał Cieślak (cherry picked from commit 27d888eacab570be8080bdf690c383c78e67ecf3) --- .../ui/ClusterConnect/ClusterLogin/ClusterLogin.tsx | 2 +- .../ClusterLogin/FormLogin/PromptWebauthn/hardware.svg | 10 +--------- web/packages/teleterm/src/ui/Search/SearchBar.test.tsx | 2 +- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.tsx index cf26965964b71..c3de0e250a4e3 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.tsx @@ -57,7 +57,7 @@ export function ClusterLoginPresentation({ <> - Login to {title} + Log in to {title} diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/PromptWebauthn/hardware.svg b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/PromptWebauthn/hardware.svg index 57a457f62f97c..a60e9e7513ddb 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/PromptWebauthn/hardware.svg +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/PromptWebauthn/hardware.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/web/packages/teleterm/src/ui/Search/SearchBar.test.tsx b/web/packages/teleterm/src/ui/Search/SearchBar.test.tsx index 8e495c28d893b..02f7dce66c785 100644 --- a/web/packages/teleterm/src/ui/Search/SearchBar.test.tsx +++ b/web/packages/teleterm/src/ui/Search/SearchBar.test.tsx @@ -305,7 +305,7 @@ it('shows a login modal when a request to a cluster from the current workspace f await waitFor(() => { expect(screen.getByTestId('Modal')).toBeInTheDocument(); }); - expect(screen.getByTestId('Modal')).toHaveTextContent('Login to'); + expect(screen.getByTestId('Modal')).toHaveTextContent('Log in to'); // Verify that the search bar stays open after closing the modal. screen.getByLabelText('Close').click(); From 5aed8c06e4e8eb6f894d44d0ccb3c447488e0674 Mon Sep 17 00:00:00 2001 From: Gus Luxton Date: Mon, 21 Oct 2024 17:15:20 -0300 Subject: [PATCH 24/53] teleport-cluster: set automountServiceAccountToken to false on ServiceAccounts when using newer Kubernetes distributions (#47701) --- .../templates/auth/serviceaccount.yaml | 4 ++++ .../templates/proxy/serviceaccount.yaml | 4 ++++ .../tests/auth_serviceaccount_test.yaml | 22 +++++++++++++++++++ .../tests/proxy_serviceaccount_test.yaml | 22 +++++++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/examples/chart/teleport-cluster/templates/auth/serviceaccount.yaml b/examples/chart/teleport-cluster/templates/auth/serviceaccount.yaml index 0eb96f032e54c..d060ea83844ff 100644 --- a/examples/chart/teleport-cluster/templates/auth/serviceaccount.yaml +++ b/examples/chart/teleport-cluster/templates/auth/serviceaccount.yaml @@ -1,4 +1,5 @@ {{- $auth := mustMergeOverwrite (mustDeepCopy .Values) .Values.auth -}} +{{- $projectedServiceAccountToken := semverCompare ">=1.20.0-0" .Capabilities.KubeVersion.Version }} {{- if $auth.serviceAccount.create -}} apiVersion: v1 kind: ServiceAccount @@ -19,4 +20,7 @@ metadata: azure.workload.identity/client-id: "{{ $auth.azure.clientID }}" {{- end }} {{- end -}} +{{- if $projectedServiceAccountToken }} +automountServiceAccountToken: false +{{- end }} {{- end }} diff --git a/examples/chart/teleport-cluster/templates/proxy/serviceaccount.yaml b/examples/chart/teleport-cluster/templates/proxy/serviceaccount.yaml index 7f5ecd8c2d6f4..4e26c23852c91 100644 --- a/examples/chart/teleport-cluster/templates/proxy/serviceaccount.yaml +++ b/examples/chart/teleport-cluster/templates/proxy/serviceaccount.yaml @@ -1,4 +1,5 @@ {{- $proxy := mustMergeOverwrite (mustDeepCopy .Values) .Values.proxy -}} +{{- $projectedServiceAccountToken := semverCompare ">=1.20.0-0" .Capabilities.KubeVersion.Version }} {{- if $proxy.serviceAccount.create -}} apiVersion: v1 kind: ServiceAccount @@ -13,4 +14,7 @@ metadata: {{- if $proxy.annotations.serviceAccount }} annotations: {{- toYaml $proxy.annotations.serviceAccount | nindent 4 }} {{- end -}} +{{- if $projectedServiceAccountToken }} +automountServiceAccountToken: false +{{- end }} {{- end }} diff --git a/examples/chart/teleport-cluster/tests/auth_serviceaccount_test.yaml b/examples/chart/teleport-cluster/tests/auth_serviceaccount_test.yaml index 49e279933a97b..2165131bac9f4 100644 --- a/examples/chart/teleport-cluster/tests/auth_serviceaccount_test.yaml +++ b/examples/chart/teleport-cluster/tests/auth_serviceaccount_test.yaml @@ -50,3 +50,25 @@ tests: - equal: path: metadata.labels.baz value: overridden + + - it: does not set automountServiceAccountToken if cluster version is <1.20 + set: + clusterName: helm-lint + capabilities: + majorVersion: 1 + minorVersion: 18 + asserts: + - notEqual: + path: automountServiceAccountToken + value: false + + - it: sets automountServiceAccountToken to false if cluster version is >=1.20 + set: + clusterName: helm-lint + capabilities: + majorVersion: 1 + minorVersion: 20 + asserts: + - equal: + path: automountServiceAccountToken + value: false diff --git a/examples/chart/teleport-cluster/tests/proxy_serviceaccount_test.yaml b/examples/chart/teleport-cluster/tests/proxy_serviceaccount_test.yaml index 70198bd939021..fe3dee41bbc00 100644 --- a/examples/chart/teleport-cluster/tests/proxy_serviceaccount_test.yaml +++ b/examples/chart/teleport-cluster/tests/proxy_serviceaccount_test.yaml @@ -40,3 +40,25 @@ tests: - equal: path: metadata.labels.baz value: overridden + + - it: does not set automountServiceAccountToken if cluster version is <1.20 + set: + clusterName: helm-lint + capabilities: + majorVersion: 1 + minorVersion: 18 + asserts: + - notEqual: + path: automountServiceAccountToken + value: false + + - it: sets automountServiceAccountToken to false if cluster version is >=1.20 + set: + clusterName: helm-lint + capabilities: + majorVersion: 1 + minorVersion: 20 + asserts: + - equal: + path: automountServiceAccountToken + value: false From c0076f785b1b244ad18185e80f6873ddac7b73cf Mon Sep 17 00:00:00 2001 From: Marco Dinis Date: Tue, 22 Oct 2024 14:30:28 +0100 Subject: [PATCH 25/53] Discover Wizard: drop v13 edge case for Kube Access (#47794) --- .../src/Discover/Kubernetes/HelmChart/HelmChart.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/web/packages/teleport/src/Discover/Kubernetes/HelmChart/HelmChart.tsx b/web/packages/teleport/src/Discover/Kubernetes/HelmChart/HelmChart.tsx index fd253f8fc06f8..5996aa5db3dcc 100644 --- a/web/packages/teleport/src/Discover/Kubernetes/HelmChart/HelmChart.tsx +++ b/web/packages/teleport/src/Discover/Kubernetes/HelmChart/HelmChart.tsx @@ -350,14 +350,6 @@ const generateCmd = (data: { // AutomaticUpgradesTargetVersion contains a v, eg, v13.4.2. // However, helm chart expects no 'v', eg, 13.4.2. deployVersion = data.automaticUpgradesTargetVersion.replace(/^v/, ''); - - // TODO(marco): remove when stable/cloud moves to v14 - // For v13 releases of the helm chart, we must remove the App role. - // We get the following error otherwise: - // Error: INSTALLATION FAILED: execution error at (teleport-kube-agent/templates/statefulset.yaml:26:28): at least one of 'apps' and 'appResources' is required in chart values when app role is enabled, see README - if (deployVersion.startsWith('13.')) { - roles = ['Kube']; - } } const yamlRoles = roles.join(',').toLowerCase(); From 913f401f4012d4b8d9ce6c96fdb8994d98055e93 Mon Sep 17 00:00:00 2001 From: Gus Luxton Date: Tue, 22 Oct 2024 10:39:08 -0300 Subject: [PATCH 26/53] [v14] docs: Fix username attribute transformation in Azure AD guide (#47800) * docs: Fix username attribute transformation in Azure AD guide * Value -> Transformation --- docs/img/azuread/azuread-8c-usernameclaim.png | Bin 221189 -> 163647 bytes .../access-controls/sso/azuread.mdx | 10 +++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/img/azuread/azuread-8c-usernameclaim.png b/docs/img/azuread/azuread-8c-usernameclaim.png index c4522140a05e5ec6f55e607f03367c8be91d8305..884df14c9eca109e19a225b521da4da480a8c380 100644 GIT binary patch literal 163647 zcmagG1zc2H_diT2EhQ!0N(~{+(B0kLh~yAMgLESyT_Vz5(h}0GbW3*+F~mFG``mn< z|NDc(XZDs3=KeqP;+agM-7Am61?`gL~=?2ls>y?nv7_JL$A zswfHvR~?6bXZi&8ozh%JO%V>xiyjUxAQ%qr7IrIO2M*4S4GwP41P+cr6%LNjDYIEs z5ca^qQb*QGQ4x+2Rz`tCfX9VHgq7f7zi?r;a7e$E;ox8`!G4euLgA2MpSZ9;gIxH( z-pob#&$_HP|P{9qvUZ*TC!%D-*{s40JY#nlc-t)r+y zDemZENy*L1#>z%5ghok8Dd=Kh#jhqI^`GjnZ$N4rS63%~0Kmh;gVlqR)zQToz|P0V z2Vmm>aB#4|-e3WFI=GsGSsXw#zc=znI}(;4a~E4DS6fF1%3tl8nmM|;0;#Egb@b=Y z@8h%t+y2#)1L!}~f=v+c>j{9Jl@0K}s=3-){XeSxdh&a<-{$pucY?nf;}>^)>*%8H zWNL0H#4h+-`+vXn*WiACTg4V^`BqoL)*iM7u!#tUQ zRp_r({7>b-Km2#a-x}8WTc6xqe1GrrZ;$>~_1Bd7m0fIM-I@NP`LEgi*S-I|F9`Ux zAb(q+-&g;)yRhXKLK6i1$x1?KQLU#i))#@3l@Qec!yjZJXHH2FegiO5wp}$}JVVfm zL<|3@R=^r%g7_S#U{>T*T|}}vAU9wV>EL2XUfa1Zc0aOwiS$cFHqy*{NYU};#~<2;%N9~}!d8Q@V*Xdn03@|YxNq4> z)MMuffAs(91%lKnI~n)FQNi38-5-PcCb7Xk-^h!eM$L(owU}?=1_dpP}BX_H2$pY zWQdbH3o=|%GQ#*{L?Q;LL^yqZnS~q&JexY{Zr)98m=%ft9Or+CD&hjGooMjMll(uU zr3}=N{))nH6@l(lxzMP-^yGhbDuOWc1UAo^Z}uCKe=ZL#7ko=mklT5u=OHwqnc{8E z|EB9A(o4vf2&arcp00+{ugZTpts7&#T^wHppZ9?jZ#&}8byh=!Z~SWB zLsqMRIrCNM&o%mUG_Zof3al%Gm8%qO=|5<`8Wf=4U#)#vKV}FRZ-%UY`G2fNge0s# zv-cJPGr+($2EgESK4ArPAL9a{uY#Yo^1Q zxSHLl_-akn6j69|{^Y!tRJ%sr4!_?QBS0L=Do6I|BH^*jJnI&HCpDovr2z1^phI@ z&-cC@sAK6qio$}PjT>Y{&W1`z?ho#j8uvD(Pe466eYC!hith%-J~ePbM|VM(_xCbt zIgG0iYst(H&T=QHKPZ#d(jGT+zLR6!yxnvzzFX6Z;Po*&4jT_TbS_l_m*pgc_za=y+Fa8l|U|hl=2xb|I<2;_eP8{*3cSfieNVG zPxjuh^OTR@j6CSh7a}?seCF6MJ)X`x$0a+m2r>)X3vKSJR6lm$MGA3 --tfHqjGFb{Y6uAbzx2kauB=XTtKbQc* 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
)7a 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

TxD4Al=1@$ZZ>pKGYZX@87RwVPTQ? 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 Date: Tue, 22 Oct 2024 16:57:58 -0700 Subject: [PATCH 27/53] audit log postgres session PID (#47645) --- .../teleport/legacy/types/events/events.proto | 4 + api/types/events/events.pb.go | 1198 +++++++++-------- lib/srv/db/audit_test.go | 5 +- lib/srv/db/common/audit.go | 1 + lib/srv/db/common/session.go | 2 + lib/srv/db/postgres/engine.go | 2 + 6 files changed, 628 insertions(+), 584 deletions(-) diff --git a/api/proto/teleport/legacy/types/events/events.proto b/api/proto/teleport/legacy/types/events/events.proto index 9b59f0350e743..8dc8fbeab3a72 100644 --- a/api/proto/teleport/legacy/types/events/events.proto +++ b/api/proto/teleport/legacy/types/events/events.proto @@ -2751,6 +2751,10 @@ message DatabaseSessionStart { (gogoproto.embed) = true, (gogoproto.jsontag) = "" ]; + // PostgresPID is the Postgres backend PID that was created for a Postgres + // connection. This can be useful for backend process cancellation or + // termination and it is not a sensitive or secret value. + uint32 PostgresPID = 8 [(gogoproto.jsontag) = "postgres_pid,omitempty"]; } // DatabaseSessionQuery is emitted when a user executes a database query. diff --git a/api/types/events/events.pb.go b/api/types/events/events.pb.go index 08db3a3842b87..4bb617162401d 100644 --- a/api/types/events/events.pb.go +++ b/api/types/events/events.pb.go @@ -4655,7 +4655,11 @@ type DatabaseSessionStart struct { // Status indicates whether the connection was successful or denied. Status `protobuf:"bytes,6,opt,name=Status,proto3,embedded=Status" json:""` // Database contains database related metadata. - DatabaseMetadata `protobuf:"bytes,7,opt,name=Database,proto3,embedded=Database" json:""` + DatabaseMetadata `protobuf:"bytes,7,opt,name=Database,proto3,embedded=Database" json:""` + // PostgresPID is the Postgres backend PID that was created for a Postgres + // connection. This can be useful for backend process cancellation or + // termination and it is not a sensitive or secret value. + PostgresPID uint32 `protobuf:"varint,8,opt,name=PostgresPID,proto3" json:"postgres_pid,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -12381,16 +12385,16 @@ func init() { } var fileDescriptor_007ba1c3d6266d56 = []byte{ - // 13912 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xbd, 0x7b, 0x6c, 0x24, 0x47, - 0x7a, 0x18, 0xce, 0x79, 0xf0, 0x55, 0x7c, 0x0d, 0x6b, 0x5f, 0x2d, 0x6a, 0x77, 0x47, 0x6a, 0xdd, - 0xad, 0x76, 0x75, 0x2b, 0xee, 0x69, 0x77, 0x4f, 0x3a, 0xe9, 0xa4, 0x93, 0x86, 0x1c, 0x72, 0x39, - 0x5a, 0x3e, 0x46, 0x3d, 0xdc, 0x5d, 0xe9, 0x1e, 0x1a, 0x37, 0xa7, 0x6b, 0xc9, 0x16, 0x67, 0xba, - 0xe7, 0xba, 0x7b, 0x96, 0x4b, 0xfd, 0x7e, 0x49, 0x7c, 0x8e, 0x9f, 0xc1, 0xdd, 0xe1, 0xe0, 0x20, - 0xb0, 0x13, 0x07, 0x88, 0x1f, 0x70, 0xe2, 0x18, 0xb6, 0xcf, 0x76, 0x02, 0xdb, 0x67, 0xc7, 0x88, - 0x9d, 0x73, 0x10, 0x39, 0x17, 0x07, 0xb6, 0x13, 0x18, 0x41, 0xe2, 0xf0, 0x9c, 0x0b, 0x9c, 0x3f, - 0x88, 0x04, 0x70, 0x90, 0x43, 0xec, 0x38, 0x4e, 0x10, 0xd4, 0x57, 0xd5, 0xdd, 0x55, 0xfd, 0x18, - 0x92, 0x4b, 0xca, 0x14, 0x8f, 0xfc, 0x67, 0x97, 0xf3, 0x7d, 0x5f, 0x7d, 0x55, 0xfd, 0xd5, 0x57, + // 13933 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xbd, 0x7b, 0x70, 0x24, 0xc7, + 0x79, 0x18, 0x8e, 0x7d, 0xe0, 0xd5, 0x78, 0x2d, 0xfa, 0x5e, 0x43, 0xf0, 0xee, 0x96, 0x1c, 0x4a, + 0xc7, 0x3b, 0xea, 0x88, 0x13, 0xef, 0x4e, 0xa4, 0x48, 0x91, 0x22, 0x17, 0x58, 0xe0, 0xb0, 0x3c, + 0x3c, 0x96, 0xb3, 0xb8, 0x3b, 0x52, 0x0f, 0xae, 0x07, 0x3b, 0x7d, 0xc0, 0x10, 0xbb, 0x33, 0xab, + 0x99, 0xd9, 0xc3, 0x81, 0xbf, 0x5f, 0x12, 0xcb, 0xf1, 0x33, 0x91, 0x54, 0x2a, 0xa7, 0x52, 0x76, + 0xe2, 0x54, 0xc5, 0x8f, 0x72, 0xe2, 0xb8, 0x6c, 0xcb, 0x76, 0x52, 0xb6, 0x65, 0xc7, 0x15, 0x3b, + 0x72, 0x2a, 0x74, 0x94, 0xa4, 0x6c, 0x27, 0xe5, 0x4a, 0x25, 0x0e, 0xe4, 0x28, 0xe5, 0xfc, 0x81, + 0x4a, 0xaa, 0x9c, 0x8a, 0x2a, 0x76, 0x1c, 0x27, 0x95, 0xea, 0xaf, 0x7b, 0x66, 0xba, 0xe7, 0xb1, + 0x00, 0x0e, 0xa0, 0x41, 0x08, 0xf8, 0xe7, 0x0e, 0xfb, 0x7d, 0x5f, 0x7f, 0xdd, 0xf3, 0xf5, 0xd7, 0xef, 0xef, 0x81, 0xae, 0x78, 0xa4, 0x49, 0xda, 0xb6, 0xe3, 0x5d, 0x6b, 0x92, 0x55, 0xbd, 0xb1, 0x79, 0xcd, 0xdb, 0x6c, 0x13, 0xf7, 0x1a, 0x79, 0x40, 0x2c, 0xcf, 0xff, 0x6f, 0xb2, 0xed, 0xd8, 0x9e, 0x8d, 0xfb, 0xd8, 0xaf, 0x89, 0xd3, 0xab, 0xf6, 0xaa, 0x0d, 0xa0, 0x6b, 0xf4, 0x2f, 0x86, @@ -12433,7 +12437,7 @@ var fileDescriptor_007ba1c3d6266d56 = []byte{ 0x84, 0x36, 0x5f, 0xd9, 0xde, 0x2a, 0x7e, 0xd8, 0xe1, 0x34, 0xf5, 0xae, 0x6a, 0x97, 0xca, 0x0a, 0xcf, 0xa0, 0x01, 0xaa, 0x4d, 0xb7, 0x4d, 0xcb, 0x50, 0xd0, 0x13, 0x99, 0xcb, 0xa3, 0xd7, 0x0b, 0x7e, 0xeb, 0x7d, 0xf8, 0xd4, 0xb9, 0xed, 0xad, 0xe2, 0x29, 0xaa, 0x83, 0xf5, 0x75, 0xd3, 0x12, - 0xa7, 0x88, 0xa0, 0xa8, 0xfa, 0xa7, 0x79, 0x34, 0x4a, 0x85, 0x23, 0xe8, 0x71, 0x89, 0x0e, 0x49, + 0xa7, 0x88, 0xa0, 0xa8, 0xfa, 0x27, 0x79, 0x34, 0x4a, 0x85, 0x23, 0xe8, 0x71, 0x89, 0x0e, 0x49, 0x0a, 0xa1, 0x23, 0xd4, 0x6d, 0xeb, 0x0d, 0xc2, 0x55, 0x1a, 0xd8, 0x59, 0x3e, 0x50, 0x60, 0x17, 0xa5, 0xc7, 0x57, 0xd0, 0x00, 0x03, 0x55, 0xca, 0x5c, 0xcb, 0x47, 0xb6, 0xb7, 0x8a, 0x83, 0x2e, 0xc0, 0xea, 0xa6, 0xa1, 0x05, 0x68, 0xaa, 0x66, 0xec, 0xef, 0x39, 0xdb, 0xf5, 0x28, 0x73, 0xae, @@ -12461,7 +12465,7 @@ var fileDescriptor_007ba1c3d6266d56 = []byte{ 0x08, 0x41, 0xb7, 0x1c, 0xbb, 0xd3, 0x76, 0x95, 0x1c, 0xb0, 0x2a, 0x6e, 0x6f, 0x15, 0x1f, 0x17, 0x58, 0xad, 0x02, 0x52, 0x5c, 0x49, 0xa3, 0x05, 0xf1, 0x77, 0x67, 0x44, 0x6e, 0x7c, 0x14, 0xe6, 0x61, 0x14, 0xbe, 0xe0, 0x8f, 0xc2, 0x54, 0x21, 0x4d, 0x46, 0x4b, 0xf2, 0x41, 0x19, 0x69, 0x46, - 0x6c, 0x50, 0xc6, 0x6a, 0x9c, 0x98, 0x46, 0x67, 0x12, 0x79, 0xed, 0x49, 0xab, 0xff, 0x38, 0x27, + 0x6c, 0x50, 0xc6, 0x6a, 0x9c, 0x98, 0x46, 0x67, 0x12, 0x79, 0xed, 0x49, 0xab, 0xff, 0x28, 0x27, 0x72, 0xa9, 0xda, 0x46, 0xd0, 0x99, 0x4b, 0x62, 0x67, 0x56, 0x6d, 0x03, 0xb6, 0x4b, 0x99, 0x70, 0x11, 0x13, 0x1a, 0xdb, 0xb6, 0x8d, 0xe8, 0xae, 0x29, 0x5e, 0x16, 0xbf, 0x8d, 0xce, 0xc6, 0x80, 0x6c, 0xba, 0x66, 0xda, 0x7f, 0x69, 0x7b, 0xab, 0xa8, 0x26, 0x70, 0x8d, 0xce, 0xde, 0x29, 0x5c, @@ -12522,7 +12526,7 @@ var fileDescriptor_007ba1c3d6266d56 = []byte{ 0x90, 0xc5, 0xd0, 0xf5, 0xf1, 0x49, 0x76, 0xed, 0x4b, 0x89, 0x18, 0x62, 0x6a, 0x94, 0xcf, 0x06, 0x7d, 0x1e, 0xfc, 0xd6, 0x78, 0x01, 0xf5, 0x1b, 0xbd, 0x68, 0x98, 0x5f, 0x51, 0xc2, 0x6c, 0x8e, 0x5f, 0x0a, 0x2f, 0x7d, 0xf9, 0xf4, 0x15, 0x5c, 0xd3, 0x04, 0xd7, 0x4b, 0xc3, 0x94, 0xd9, 0xef, - 0x6e, 0x15, 0x33, 0xdb, 0x5b, 0xc5, 0x1e, 0x6d, 0x40, 0x38, 0x54, 0x86, 0xeb, 0x8d, 0xb0, 0xc0, + 0x6c, 0x15, 0x33, 0xdb, 0x5b, 0xc5, 0x1e, 0x6d, 0x40, 0x38, 0x54, 0x86, 0xeb, 0x8d, 0xb0, 0xc0, 0x8a, 0x97, 0x8e, 0x91, 0xb2, 0x6c, 0xfd, 0x79, 0x15, 0xf5, 0xf3, 0x36, 0x70, 0x8d, 0x3b, 0x17, 0xde, 0x65, 0x48, 0x57, 0xad, 0x91, 0xd2, 0x7e, 0x29, 0xfc, 0x32, 0xea, 0x63, 0x67, 0x7b, 0x2e, 0x80, 0xb3, 0xc9, 0x77, 0x21, 0x91, 0xe2, 0xbc, 0x0c, 0x9e, 0x43, 0x28, 0x3c, 0xd7, 0x07, 0x37, @@ -12534,7 +12538,7 @@ var fileDescriptor_007ba1c3d6266d56 = []byte{ 0x74, 0xcb, 0x50, 0x06, 0x43, 0x4d, 0x36, 0x19, 0xa6, 0xde, 0x60, 0x28, 0x51, 0x93, 0xe5, 0x42, 0xf4, 0x58, 0xce, 0xfb, 0x50, 0x23, 0x0d, 0xdb, 0xa1, 0x7b, 0x01, 0xb8, 0x1c, 0xe4, 0xc7, 0x72, 0x97, 0xe1, 0xea, 0x8e, 0x8f, 0x14, 0x37, 0xdb, 0xd1, 0x82, 0xaf, 0xe7, 0x07, 0x86, 0x0a, 0xc3, - 0xd1, 0xfb, 0x5c, 0xf5, 0x1f, 0xe6, 0xd0, 0x10, 0x27, 0xa5, 0x4b, 0xe9, 0x89, 0x82, 0xef, 0x47, + 0xd1, 0xfb, 0x5c, 0xf5, 0x1f, 0xe4, 0xd0, 0x10, 0x27, 0xa5, 0x4b, 0xe9, 0x89, 0x82, 0xef, 0x47, 0xc1, 0x13, 0x15, 0xb5, 0xef, 0xa0, 0x14, 0x55, 0xfd, 0x42, 0x36, 0x98, 0x8d, 0xaa, 0x8e, 0x69, 0xed, 0x6f, 0x36, 0xba, 0x84, 0xd0, 0xf4, 0x5a, 0xc7, 0x5a, 0x67, 0xef, 0x56, 0xd9, 0xf0, 0xdd, 0xaa, 0x61, 0x6a, 0x02, 0x06, 0x5f, 0x40, 0xf9, 0x32, 0xe5, 0x4f, 0x7b, 0x66, 0x78, 0x6a, 0xf0, @@ -12544,7 +12548,7 @@ var fileDescriptor_007ba1c3d6266d56 = []byte{ 0xc0, 0x2a, 0xea, 0x5b, 0xba, 0x7f, 0xdf, 0x25, 0x1e, 0x88, 0x2f, 0x37, 0x85, 0xe8, 0xe4, 0x6c, 0x03, 0x44, 0xe3, 0x18, 0xf5, 0x2b, 0x19, 0x7a, 0x7a, 0x71, 0xd7, 0x3d, 0xbb, 0x1d, 0x68, 0xf9, 0xbe, 0x44, 0x72, 0x25, 0xdc, 0x57, 0x64, 0xe1, 0x6b, 0xc7, 0xf8, 0xd7, 0xf6, 0xf3, 0xbd, 0x45, - 0xb8, 0xa3, 0x48, 0xfc, 0xaa, 0xdc, 0x0e, 0x5f, 0xa5, 0xfe, 0x49, 0x16, 0x9d, 0xe3, 0x2d, 0x9e, + 0xb8, 0xa3, 0x48, 0xfc, 0xaa, 0xdc, 0x0e, 0x5f, 0xa5, 0xfe, 0x71, 0x16, 0x9d, 0xe3, 0x2d, 0x9e, 0x6e, 0x9a, 0xed, 0x15, 0x5b, 0x77, 0x0c, 0x8d, 0x34, 0x88, 0xf9, 0x80, 0x1c, 0xcd, 0x81, 0x27, 0x0f, 0x9d, 0xfc, 0x3e, 0x86, 0xce, 0x75, 0x38, 0x08, 0x52, 0xc9, 0xc0, 0x85, 0x2f, 0xdb, 0x54, 0x14, 0xb6, 0xb7, 0x8a, 0xc3, 0x06, 0x03, 0xc3, 0x95, 0xbf, 0x26, 0x12, 0x51, 0x25, 0x99, 0x27, @@ -12559,12 +12563,12 @@ var fileDescriptor_007ba1c3d6266d56 = []byte{ 0x24, 0xca, 0x57, 0x75, 0x6f, 0x8d, 0xbf, 0x83, 0xc3, 0x9b, 0xf0, 0x7d, 0xb3, 0x49, 0xea, 0x6d, 0xdd, 0x5b, 0xd3, 0x00, 0x25, 0xcc, 0x19, 0x08, 0x38, 0x26, 0xcc, 0x19, 0xc2, 0x62, 0x3f, 0xf4, 0x44, 0xe6, 0x72, 0x3e, 0x71, 0xb1, 0xff, 0x46, 0x3e, 0x6d, 0x5e, 0xb9, 0xe7, 0x98, 0x1e, 0x39, - 0xd1, 0xb0, 0x13, 0x0d, 0xdb, 0xa7, 0x86, 0xfd, 0x7e, 0x16, 0x8d, 0x04, 0x87, 0xa6, 0x77, 0x48, + 0xd1, 0xb0, 0x13, 0x0d, 0xdb, 0xa7, 0x86, 0xfd, 0x5e, 0x16, 0x8d, 0x04, 0x87, 0xa6, 0x77, 0x48, 0xe3, 0x70, 0xd6, 0xaa, 0xf0, 0x28, 0x93, 0xdb, 0xf7, 0x51, 0x66, 0x3f, 0x0a, 0xa5, 0x06, 0x57, 0x9e, 0x6c, 0x6b, 0x00, 0x12, 0x63, 0x57, 0x9e, 0xc1, 0x45, 0xe7, 0x93, 0xa8, 0x7f, 0x41, 0x7f, 0x68, 0xb6, 0x3a, 0x2d, 0xbe, 0x4b, 0x07, 0xbb, 0xae, 0x96, 0xfe, 0x50, 0xf3, 0xe1, 0xea, 0xbf, 0xcd, 0xa0, 0x51, 0x2e, 0x54, 0xce, 0x7c, 0x5f, 0x52, 0x0d, 0xa5, 0x93, 0xdd, 0xb7, 0x74, 0x72, - 0x8f, 0x2e, 0x1d, 0xf5, 0xef, 0xe6, 0x90, 0x32, 0x6b, 0x36, 0xc9, 0xb2, 0xa3, 0x5b, 0xee, 0x7d, + 0x8f, 0x2e, 0x1d, 0xf5, 0xef, 0xe4, 0x90, 0x32, 0x6b, 0x36, 0xc9, 0xb2, 0xa3, 0x5b, 0xee, 0x7d, 0xe2, 0xf0, 0xe3, 0xf4, 0x0c, 0x65, 0xb5, 0xaf, 0x0f, 0x14, 0xa6, 0x94, 0xec, 0x23, 0x4d, 0x29, 0x1f, 0x41, 0x83, 0xbc, 0x31, 0x81, 0x4d, 0x21, 0x8c, 0x1a, 0xc7, 0x07, 0x6a, 0x21, 0x9e, 0x12, 0x97, 0xda, 0x6d, 0xc7, 0x7e, 0x40, 0x1c, 0xf6, 0x4a, 0xc5, 0x89, 0x75, 0x1f, 0xa8, 0x85, 0x78, @@ -12573,7 +12577,7 @@ var fileDescriptor_007ba1c3d6266d56 = []byte{ 0xca, 0xb2, 0xbd, 0x61, 0x35, 0x6d, 0x9d, 0x19, 0xbf, 0x0c, 0x30, 0x4a, 0x83, 0xc3, 0xb4, 0x00, 0x4b, 0x29, 0xa9, 0xcc, 0xc1, 0xa8, 0x68, 0x20, 0xe4, 0x79, 0x9f, 0xc3, 0xb4, 0x00, 0xab, 0x7e, 0x25, 0x4f, 0xb5, 0xd7, 0x35, 0xdf, 0x3d, 0xf6, 0xeb, 0x42, 0x38, 0x60, 0x7a, 0x1f, 0x61, 0xc0, - 0x1c, 0x9b, 0x0b, 0x3b, 0xf5, 0x4f, 0xfb, 0x11, 0xe2, 0xd2, 0x9f, 0x39, 0x39, 0x1c, 0xee, 0x4f, + 0x1c, 0x9b, 0x0b, 0x3b, 0xf5, 0x4f, 0xfa, 0x11, 0xe2, 0xd2, 0x9f, 0x39, 0x39, 0x1c, 0xee, 0x4f, 0x6b, 0xca, 0x68, 0x7c, 0xc6, 0x5a, 0xd3, 0xad, 0x06, 0x31, 0xc2, 0x6b, 0xcb, 0x3e, 0x18, 0xda, 0x60, 0xd3, 0x4b, 0x38, 0x32, 0xbc, 0xb7, 0xd4, 0xe2, 0x05, 0xf0, 0x73, 0x68, 0xa8, 0x62, 0x79, 0xc4, 0xd1, 0x1b, 0x9e, 0xf9, 0x80, 0xf0, 0xa9, 0x01, 0x5e, 0x86, 0xcd, 0x10, 0xac, 0x89, 0x34, @@ -12586,7 +12590,7 @@ var fileDescriptor_007ba1c3d6266d56 = []byte{ 0xd0, 0x54, 0x75, 0x36, 0x18, 0xbd, 0x8f, 0xa1, 0x5c, 0x95, 0x5b, 0x2a, 0xe4, 0xd9, 0x7e, 0xa6, 0x6d, 0x1a, 0x1a, 0x85, 0xe1, 0x2b, 0x68, 0x60, 0x1a, 0xcc, 0xdf, 0xf8, 0x2b, 0x62, 0x9e, 0xad, 0x7f, 0x0d, 0x80, 0x81, 0x15, 0xac, 0x8f, 0xc6, 0x1f, 0x46, 0xfd, 0x55, 0xc7, 0x5e, 0x75, 0xf4, - 0x16, 0x5f, 0x83, 0xc1, 0x54, 0xa4, 0xcd, 0x40, 0x9a, 0x8f, 0x53, 0xff, 0x66, 0xc6, 0xdf, 0xb6, + 0x16, 0x5f, 0x83, 0xc1, 0x54, 0xa4, 0xcd, 0x40, 0x9a, 0x8f, 0x53, 0xff, 0x46, 0xc6, 0xdf, 0xb6, 0xd3, 0x12, 0xb5, 0x0e, 0x5c, 0xcd, 0x43, 0xdd, 0x03, 0xac, 0x84, 0xcb, 0x40, 0x9a, 0x8f, 0xc3, 0x57, 0x50, 0xef, 0x8c, 0xe3, 0xd8, 0x8e, 0x68, 0x6c, 0x4e, 0x28, 0x40, 0x7c, 0xee, 0x05, 0x0a, 0xfc, 0x02, 0x1a, 0x62, 0x73, 0x0e, 0xbb, 0xd1, 0xcc, 0x75, 0x7b, 0x29, 0x15, 0x29, 0xd5, 0xaf, @@ -12612,11 +12616,11 @@ var fileDescriptor_007ba1c3d6266d56 = []byte{ 0xf5, 0xde, 0x52, 0x80, 0xf7, 0x50, 0xb8, 0x31, 0x68, 0x99, 0x9e, 0x47, 0x0c, 0xbe, 0x4a, 0xc0, 0x7b, 0xa1, 0xf7, 0x50, 0x8b, 0xe1, 0xf1, 0x55, 0x34, 0x02, 0x30, 0xfe, 0x44, 0xc8, 0xce, 0xc7, 0xbc, 0x80, 0xf3, 0x50, 0x93, 0x91, 0xea, 0xd7, 0xc3, 0xd7, 0xe1, 0x79, 0xa2, 0x1f, 0xd5, 0x17, - 0xc5, 0x0f, 0x48, 0x7f, 0xa9, 0x7f, 0x91, 0x67, 0x2e, 0x20, 0xcc, 0x71, 0xef, 0x30, 0x44, 0x19, + 0xc5, 0x0f, 0x48, 0x7f, 0xa9, 0x7f, 0x9e, 0x67, 0x2e, 0x20, 0xcc, 0x71, 0xef, 0x30, 0x44, 0x19, 0x5e, 0xe9, 0xe6, 0xf6, 0x70, 0xa5, 0x7b, 0x15, 0xf5, 0x2d, 0x10, 0x6f, 0xcd, 0xf6, 0x0d, 0xbf, 0xc0, 0x42, 0xaf, 0x05, 0x10, 0xd1, 0x42, 0x8f, 0xd1, 0xe0, 0x75, 0x84, 0x7d, 0xaf, 0xbc, 0xc0, 0x10, 0xdb, 0xbf, 0x42, 0x3e, 0x17, 0x3b, 0xa7, 0xd4, 0xc0, 0x25, 0x17, 0x6c, 0xec, 0x4f, 0x07, - 0x86, 0xde, 0x82, 0x25, 0xd6, 0x9f, 0x6f, 0x15, 0xfb, 0x18, 0x8d, 0x96, 0xc0, 0x16, 0xbf, 0x81, + 0x86, 0xde, 0x82, 0x25, 0xd6, 0x9f, 0x6d, 0x15, 0xfb, 0x18, 0x8d, 0x96, 0xc0, 0x16, 0xbf, 0x81, 0x06, 0x17, 0x66, 0x4b, 0xdc, 0x43, 0x8f, 0x59, 0x45, 0x3c, 0x16, 0x48, 0xd1, 0x47, 0x04, 0x22, 0x01, 0x7f, 0x9b, 0xd6, 0x7d, 0x3d, 0xee, 0xa0, 0x17, 0x72, 0xa1, 0xda, 0xc2, 0x3c, 0x77, 0xf8, 0xed, 0x42, 0xa0, 0x2d, 0xb2, 0x3f, 0x4f, 0x54, 0x56, 0x0c, 0x1b, 0xd1, 0x96, 0x81, 0x7d, 0x8c, @@ -12627,11 +12631,11 @@ var fileDescriptor_007ba1c3d6266d56 = []byte{ 0x61, 0x2f, 0xf0, 0x23, 0xe7, 0x38, 0x61, 0x45, 0x62, 0xa7, 0x4d, 0x06, 0x06, 0x97, 0x28, 0x66, 0x3e, 0x3e, 0xe5, 0xbb, 0xd9, 0x32, 0x97, 0x28, 0x6e, 0x66, 0x2e, 0xf9, 0xb8, 0x85, 0xa4, 0xf8, 0x29, 0x94, 0x5b, 0x5e, 0x9e, 0xe7, 0xda, 0x08, 0xee, 0xcd, 0x9e, 0x27, 0xfa, 0x7c, 0x51, 0xac, - 0xfa, 0x87, 0x59, 0x84, 0xa8, 0xd2, 0x4f, 0x3b, 0x44, 0x3f, 0xa4, 0xc7, 0x9c, 0x29, 0x34, 0xe0, + 0xfa, 0x07, 0x59, 0x84, 0xa8, 0xd2, 0x4f, 0x3b, 0x44, 0x3f, 0xa4, 0xc7, 0x9c, 0x29, 0x34, 0xe0, 0x0b, 0x9c, 0x0f, 0xb8, 0xc0, 0xf2, 0x39, 0xda, 0x11, 0xd1, 0xba, 0x03, 0x2b, 0xf7, 0xa2, 0x6f, 0x8c, 0xcb, 0xee, 0x52, 0x61, 0x77, 0x08, 0xc6, 0xb8, 0xbe, 0x09, 0xee, 0x47, 0xd0, 0x20, 0xd7, 0x1a, 0x5b, 0xba, 0x43, 0x6d, 0xf8, 0x40, 0x2d, 0xc4, 0x47, 0xd4, 0xb3, 0x6f, 0x1f, 0x93, 0xd9, - 0x97, 0xb8, 0x78, 0x99, 0x99, 0xfe, 0x91, 0x15, 0xef, 0x81, 0x5d, 0x70, 0xa9, 0xbf, 0x9f, 0x41, + 0x97, 0xb8, 0x78, 0x99, 0x99, 0xfe, 0x91, 0x15, 0xef, 0x81, 0x5d, 0x70, 0xa9, 0xbf, 0x97, 0x41, 0x98, 0x36, 0xab, 0xaa, 0xbb, 0xee, 0x86, 0xed, 0x18, 0xcc, 0x02, 0xf5, 0x50, 0x04, 0x73, 0x70, 0x8f, 0x12, 0x5f, 0x1b, 0x40, 0xa7, 0x24, 0xeb, 0xbe, 0x23, 0x3e, 0x9a, 0xae, 0xc8, 0xa3, 0xa9, 0x9b, 0x69, 0xfb, 0x87, 0xc4, 0x57, 0x8f, 0x5e, 0xc9, 0xcb, 0x44, 0x78, 0xee, 0x78, 0x16, 0x0d, @@ -12655,7 +12659,7 @@ var fileDescriptor_007ba1c3d6266d56 = []byte{ 0x0d, 0xa0, 0x14, 0x2b, 0xf8, 0x9b, 0x02, 0x16, 0x5e, 0xee, 0xd8, 0xb6, 0xa5, 0x8c, 0xc6, 0x6a, 0x9d, 0x15, 0xbf, 0x6e, 0xc1, 0x79, 0x06, 0xbc, 0xe9, 0xdd, 0xce, 0x4a, 0xe0, 0x71, 0x26, 0xc5, 0x2a, 0x90, 0x8b, 0xa8, 0x5f, 0xc9, 0x44, 0x66, 0xc1, 0x43, 0x5c, 0xf4, 0x3e, 0x14, 0x7f, 0x8c, - 0x8d, 0x4f, 0x4b, 0xea, 0xdf, 0xcb, 0xa2, 0xa1, 0xaa, 0xed, 0x78, 0x3c, 0x2e, 0xc0, 0xd1, 0x5e, + 0x8d, 0x4f, 0x4b, 0xea, 0xdf, 0xcd, 0xa2, 0xa1, 0xaa, 0xed, 0x78, 0x3c, 0x2e, 0xc0, 0xd1, 0x5e, 0x85, 0x84, 0x63, 0x4b, 0x7e, 0x0f, 0xc7, 0x96, 0xf3, 0x28, 0x2f, 0xd8, 0x21, 0xb2, 0xcb, 0x4f, 0xc3, 0x70, 0x34, 0x80, 0xaa, 0xdf, 0x99, 0x45, 0xe8, 0xcd, 0xe7, 0x9e, 0x3b, 0xc6, 0x02, 0x52, 0x7f, 0x24, 0x83, 0xc6, 0xf8, 0x6d, 0xbc, 0x10, 0x59, 0xa7, 0xdf, 0x7f, 0x47, 0x11, 0xc7, 0x25, @@ -12663,7 +12667,7 @@ var fileDescriptor_007ba1c3d6266d56 = []byte{ 0xc3, 0xc4, 0x25, 0xc0, 0xa7, 0xc3, 0xcf, 0xfa, 0xef, 0x0c, 0xb9, 0x70, 0xdd, 0xa3, 0x05, 0x66, 0x12, 0xdf, 0x1a, 0xd4, 0x5f, 0xca, 0xa3, 0xfc, 0xcc, 0x43, 0xd2, 0x38, 0xe2, 0x5d, 0x23, 0xdc, 0x5e, 0xe4, 0xf7, 0x79, 0x7b, 0xf1, 0x28, 0x0f, 0xa7, 0xaf, 0x86, 0xfd, 0xd9, 0x27, 0x57, 0x1f, - 0xe9, 0xf9, 0x68, 0xf5, 0x7e, 0x4f, 0x1f, 0xbd, 0x77, 0xf7, 0x7f, 0x9e, 0x43, 0xb9, 0xda, 0x74, + 0xe9, 0xf9, 0x68, 0xf5, 0x7e, 0x4f, 0x1f, 0xbd, 0x77, 0xf7, 0x7f, 0x96, 0x43, 0xb9, 0xda, 0x74, 0xf5, 0x44, 0x6f, 0x0e, 0x55, 0x6f, 0xba, 0x3f, 0x4c, 0xa9, 0xc1, 0x5d, 0xf3, 0x40, 0x68, 0x0a, 0x16, 0xb9, 0x56, 0xfe, 0x56, 0x0e, 0x8d, 0xd6, 0x66, 0x97, 0xab, 0xc2, 0x75, 0xcf, 0x6d, 0x66, 0xae, 0x03, 0x86, 0x23, 0xac, 0x4b, 0xcf, 0xc7, 0xf6, 0x33, 0x77, 0x2a, 0x96, 0xf7, 0xfc, 0xcd, @@ -12674,7 +12678,7 @@ var fileDescriptor_007ba1c3d6266d56 = []byte{ 0xe7, 0xec, 0xee, 0x4c, 0xe8, 0x49, 0x65, 0xa8, 0x1d, 0x16, 0x02, 0x66, 0x22, 0x17, 0xfc, 0x16, 0x42, 0x6c, 0x8f, 0xb2, 0xcb, 0x68, 0x6d, 0x17, 0x60, 0xdf, 0xcf, 0xb6, 0x96, 0x09, 0x7b, 0x3c, 0x81, 0x19, 0x5e, 0x47, 0x85, 0x05, 0xdb, 0x30, 0xef, 0x9b, 0xcc, 0xbe, 0x0a, 0x2a, 0xe8, 0xdb, - 0xd9, 0xaa, 0x81, 0x6e, 0x25, 0x5b, 0x42, 0xb9, 0xa4, 0x6a, 0x62, 0x8c, 0xd5, 0x7f, 0xda, 0x8b, + 0xd9, 0xaa, 0x81, 0x6e, 0x25, 0x5b, 0x42, 0xb9, 0xa4, 0x6a, 0x62, 0x8c, 0xd5, 0x7f, 0xd2, 0x8b, 0xf2, 0xb4, 0xdb, 0x4f, 0xc6, 0xef, 0x7e, 0xc6, 0x6f, 0x09, 0x15, 0xee, 0xd9, 0xce, 0xba, 0x69, 0xad, 0x06, 0xa6, 0xaf, 0xfc, 0x6c, 0x0a, 0xcf, 0xf5, 0x1b, 0x0c, 0x57, 0x0f, 0xac, 0x64, 0xb5, 0x18, 0xf9, 0x0e, 0x23, 0xf8, 0x45, 0x84, 0x98, 0x43, 0x2b, 0xd0, 0x0c, 0x84, 0x1e, 0xe9, 0xcc, @@ -12691,567 +12695,568 @@ var fileDescriptor_007ba1c3d6266d56 = []byte{ 0x84, 0xeb, 0x72, 0xcd, 0x6e, 0x92, 0xa3, 0xfd, 0x19, 0x07, 0x78, 0x5d, 0xee, 0x0b, 0xe4, 0x00, 0xae, 0x52, 0xbe, 0x3d, 0x04, 0xf2, 0xc3, 0x59, 0x74, 0x9a, 0x87, 0x09, 0xe5, 0x87, 0xa3, 0x13, 0x5d, 0x49, 0x15, 0xcd, 0x89, 0xd6, 0x70, 0xd1, 0xfc, 0x54, 0x0e, 0x9d, 0x86, 0x60, 0x6a, 0x74, - 0xcf, 0xf8, 0x6d, 0x30, 0x51, 0xe2, 0x86, 0xfc, 0x42, 0xb3, 0x90, 0xf0, 0x42, 0xf3, 0xe7, 0x5b, + 0xcf, 0xf8, 0x6d, 0x30, 0x51, 0xe2, 0x86, 0xfc, 0x42, 0xb3, 0x90, 0xf0, 0x42, 0xf3, 0x67, 0x5b, 0xc5, 0xe7, 0x57, 0x4d, 0x6f, 0xad, 0xb3, 0x32, 0xd9, 0xb0, 0x5b, 0xd7, 0x56, 0x1d, 0xfd, 0x81, 0xc9, 0xde, 0x26, 0xf4, 0xe6, 0xb5, 0x20, 0xe2, 0xb6, 0xde, 0x36, 0x79, 0x2c, 0xee, 0x1a, 0x6c, 0xc4, 0x28, 0x57, 0xff, 0x6d, 0xc7, 0x45, 0xe8, 0x75, 0xdb, 0xb4, 0xb8, 0x55, 0x03, 0x5b, 0x85, 0x6b, 0x74, 0xf3, 0xfa, 0x8e, 0x6d, 0x5a, 0xf5, 0xa8, 0x69, 0xc3, 0x5e, 0xeb, 0x0b, 0x59, 0x6b, - 0x42, 0x35, 0xea, 0xbf, 0xc9, 0xa0, 0xc7, 0x64, 0x2d, 0xfe, 0x76, 0x58, 0xd8, 0xfe, 0x76, 0x16, + 0x42, 0x35, 0xea, 0xbf, 0xc9, 0xa0, 0xc7, 0x64, 0x2d, 0xfe, 0x76, 0x58, 0xd8, 0xfe, 0x56, 0x16, 0x9d, 0xb9, 0x05, 0xc2, 0x09, 0x5e, 0x99, 0x4f, 0xe6, 0x2d, 0x3e, 0x38, 0x13, 0x64, 0x73, 0x32, - 0x71, 0x71, 0xd9, 0xfc, 0x4e, 0x06, 0x9d, 0x5a, 0xaa, 0x94, 0xa7, 0xbf, 0x4d, 0xb4, 0x26, 0xfe, - 0x3d, 0x47, 0xbb, 0xa7, 0xe1, 0x7b, 0x6a, 0xa5, 0x85, 0xf9, 0x6f, 0xa7, 0xfe, 0x91, 0xbe, 0xe7, - 0x88, 0xf7, 0xcf, 0x6f, 0xf5, 0xa1, 0xa1, 0xdb, 0x9d, 0x15, 0xc2, 0xdf, 0xfc, 0x8e, 0xf5, 0x81, - 0xfa, 0x3a, 0x1a, 0xe2, 0x62, 0x80, 0xcb, 0x28, 0x21, 0xf0, 0x08, 0x77, 0x24, 0x65, 0xbe, 0xdd, - 0x22, 0x11, 0x3e, 0x8f, 0xf2, 0x77, 0x89, 0xb3, 0x22, 0xda, 0xe4, 0x3f, 0x20, 0xce, 0x8a, 0x06, - 0x50, 0x3c, 0x1f, 0x9a, 0xca, 0x95, 0xaa, 0x15, 0x08, 0x42, 0xcd, 0xef, 0xc1, 0x20, 0xaa, 0x76, - 0x60, 0x4e, 0xa0, 0xb7, 0x4d, 0x16, 0xbe, 0x5a, 0xf4, 0x07, 0x8a, 0x96, 0xc4, 0x8b, 0x68, 0x5c, - 0x7c, 0x4f, 0x66, 0x11, 0x98, 0x07, 0x12, 0xd8, 0x25, 0xc5, 0x5e, 0x8e, 0x17, 0xc5, 0xaf, 0xa2, - 0x61, 0x1f, 0x08, 0x2f, 0xe3, 0x83, 0x61, 0xd8, 0xcf, 0x80, 0x55, 0x24, 0xbc, 0xbb, 0x54, 0x40, - 0x64, 0x00, 0xb7, 0x3b, 0x28, 0x81, 0x41, 0xc4, 0xd2, 0x40, 0x2a, 0x80, 0x3f, 0x06, 0x0c, 0xda, - 0xb6, 0xe5, 0x12, 0x78, 0x03, 0x1c, 0x02, 0x83, 0x75, 0x30, 0xc5, 0x73, 0x38, 0x9c, 0xb9, 0x25, - 0x48, 0x64, 0x78, 0x09, 0xa1, 0xf0, 0xad, 0x86, 0x3b, 0x7f, 0xed, 0xf9, 0x15, 0x49, 0x60, 0x21, - 0xde, 0xb2, 0x8e, 0x3c, 0xca, 0x2d, 0xab, 0xfa, 0x7b, 0x59, 0x34, 0x54, 0x6a, 0xb7, 0x83, 0xa1, - 0xf0, 0x2c, 0xea, 0x2b, 0xb5, 0xdb, 0x77, 0xb4, 0x8a, 0x18, 0x06, 0x52, 0x6f, 0xb7, 0xeb, 0x1d, - 0xc7, 0x14, 0x4d, 0x6d, 0x18, 0x11, 0x9e, 0x46, 0x23, 0xa5, 0x76, 0xbb, 0xda, 0x59, 0x69, 0x9a, - 0x0d, 0x21, 0xaa, 0x3c, 0x4b, 0x80, 0xd1, 0x6e, 0xd7, 0xdb, 0x80, 0x89, 0xa6, 0x16, 0x90, 0xcb, - 0xe0, 0xb7, 0xc1, 0x65, 0x9a, 0x07, 0x35, 0x67, 0x61, 0x93, 0xd5, 0x20, 0x00, 0x64, 0xd8, 0xb6, - 0xc9, 0x80, 0x88, 0x05, 0xca, 0x3c, 0xef, 0x87, 0x1b, 0xa5, 0x15, 0xc5, 0x82, 0x97, 0x87, 0x2c, - 0xf1, 0x47, 0x51, 0x7f, 0xa9, 0xdd, 0x16, 0xae, 0xf1, 0xe0, 0xad, 0x96, 0x96, 0x8a, 0xf4, 0xb1, - 0x4f, 0x36, 0xf1, 0x32, 0x1a, 0x95, 0x2b, 0xdb, 0x53, 0xa0, 0xcd, 0x3f, 0xcb, 0xc0, 0x07, 0x1d, - 0x71, 0x53, 0xb1, 0x1b, 0x28, 0x57, 0x6a, 0xb7, 0xf9, 0x7c, 0x74, 0x2a, 0xa1, 0x3f, 0xa2, 0xee, - 0x23, 0xa5, 0x76, 0xdb, 0xff, 0x74, 0x66, 0xaa, 0x7a, 0xbc, 0x3e, 0xfd, 0x6b, 0xec, 0xd3, 0x8f, - 0xb6, 0x3d, 0xa8, 0xfa, 0x4b, 0x39, 0x34, 0x56, 0x6a, 0xb7, 0x4f, 0x02, 0x74, 0x1e, 0x94, 0x93, - 0xca, 0x73, 0x08, 0x09, 0xd3, 0x63, 0x7f, 0x60, 0xb2, 0x3d, 0x24, 0x4c, 0x8d, 0x4a, 0x46, 0x13, - 0x88, 0x7c, 0xf5, 0x1b, 0xd8, 0x93, 0xfa, 0x7d, 0x3e, 0x07, 0x53, 0xf1, 0x51, 0x77, 0xb8, 0xff, - 0xa0, 0x74, 0x1b, 0xef, 0x83, 0xbe, 0x3d, 0xf5, 0xc1, 0x6f, 0x4a, 0x83, 0x07, 0x02, 0x3e, 0x9e, - 0xf4, 0x42, 0xef, 0xbe, 0xb6, 0xc5, 0xa3, 0xa2, 0x30, 0xb9, 0x17, 0xb0, 0x1f, 0x84, 0x9e, 0xfb, - 0xa4, 0x37, 0x28, 0xaa, 0x6e, 0x1a, 0x5a, 0x84, 0xd6, 0xef, 0xc3, 0xfe, 0x3d, 0xf5, 0xe1, 0x56, - 0x16, 0xfc, 0x4e, 0x02, 0x9f, 0xf6, 0xfd, 0x9f, 0x2e, 0xae, 0x21, 0xc4, 0x1e, 0x74, 0x02, 0x6b, - 0xb1, 0x11, 0xe6, 0xbe, 0xca, 0x62, 0xd3, 0x73, 0xf7, 0xd5, 0x90, 0x24, 0x78, 0x78, 0xce, 0x25, - 0x3e, 0x3c, 0x5f, 0x41, 0x03, 0x9a, 0xbe, 0xf1, 0x46, 0x87, 0x38, 0x9b, 0x7c, 0x3b, 0xc3, 0x42, - 0xc6, 0xe8, 0x1b, 0xf5, 0xcf, 0x51, 0xa0, 0x16, 0xa0, 0xb1, 0x1a, 0x38, 0x2e, 0x09, 0x0f, 0x6d, - 0xec, 0x76, 0x2f, 0x70, 0x57, 0x7a, 0x14, 0x45, 0xc7, 0x2f, 0xa1, 0x5c, 0xe9, 0x5e, 0x8d, 0x4b, - 0x36, 0xe8, 0xda, 0xd2, 0xbd, 0x1a, 0x97, 0x57, 0x6a, 0xd9, 0x7b, 0x35, 0xf5, 0xf3, 0x59, 0x84, - 0xe3, 0x94, 0xf8, 0x79, 0x34, 0x08, 0xd0, 0x55, 0xaa, 0x33, 0x62, 0x52, 0xa3, 0x0d, 0xb7, 0xee, - 0x00, 0x54, 0xda, 0xdc, 0xf9, 0xa4, 0xf8, 0x45, 0xc8, 0xdf, 0xc6, 0xd3, 0x6a, 0x48, 0x49, 0x8d, - 0x36, 0x5c, 0x3f, 0xe3, 0x59, 0x24, 0x7d, 0x1b, 0x27, 0x86, 0x7d, 0xe1, 0xbd, 0xda, 0x9c, 0xed, - 0x7a, 0x5c, 0xd4, 0x6c, 0x5f, 0xb8, 0xe1, 0x42, 0x36, 0x2d, 0x69, 0x5f, 0xc8, 0xc8, 0x20, 0x23, - 0xc0, 0xbd, 0x1a, 0xb3, 0xfe, 0x35, 0x34, 0xbb, 0xe9, 0x6f, 0x28, 0x59, 0x46, 0x80, 0x0d, 0xb7, - 0xce, 0x2c, 0x87, 0x0d, 0x48, 0x1c, 0x27, 0x65, 0x04, 0x90, 0x4a, 0xa9, 0x5f, 0x1c, 0x40, 0x85, - 0xb2, 0xee, 0xe9, 0x2b, 0xba, 0x4b, 0x84, 0xd3, 0xf4, 0x98, 0x0f, 0xf3, 0x3f, 0x47, 0x90, 0x83, - 0xb1, 0x92, 0xf0, 0x35, 0xd1, 0x02, 0xf8, 0x13, 0x21, 0xdf, 0x20, 0x5f, 0x93, 0x98, 0x00, 0x62, - 0xa5, 0xde, 0xe6, 0x60, 0x2d, 0x46, 0x88, 0xaf, 0xa2, 0x21, 0x1f, 0x46, 0x0f, 0x00, 0xb9, 0x50, - 0x67, 0x8c, 0x15, 0xba, 0xff, 0xd7, 0x44, 0x34, 0x7e, 0x11, 0x0d, 0xfb, 0x3f, 0x85, 0xad, 0x35, - 0xcb, 0x66, 0xb1, 0x12, 0x3b, 0x3d, 0x89, 0xa4, 0x62, 0x51, 0x98, 0xdf, 0x7a, 0xa5, 0xa2, 0x91, - 0x84, 0x11, 0x12, 0x29, 0xfe, 0x1c, 0x1a, 0xf5, 0x7f, 0xf3, 0x03, 0x03, 0xcb, 0xad, 0x71, 0x35, - 0xc8, 0x4b, 0x17, 0x11, 0xeb, 0xa4, 0x4c, 0xce, 0x8e, 0x0e, 0x8f, 0xfb, 0x39, 0x10, 0x8c, 0x95, - 0xf8, 0xc9, 0x21, 0x52, 0x01, 0xae, 0xa0, 0x71, 0x1f, 0x12, 0x6a, 0x68, 0x7f, 0x78, 0x62, 0x34, - 0x56, 0xea, 0x89, 0x4a, 0x1a, 0x2f, 0x85, 0x9b, 0xe8, 0xbc, 0x04, 0x34, 0xdc, 0x35, 0xf3, 0xbe, - 0xc7, 0x8f, 0x7b, 0x3c, 0x7e, 0x1b, 0x4f, 0x7a, 0x13, 0x70, 0x65, 0x34, 0x7e, 0xf6, 0x2a, 0x39, - 0xb2, 0x7e, 0x57, 0x6e, 0xb8, 0x86, 0x4e, 0xfb, 0xf8, 0x5b, 0xd3, 0xd5, 0xaa, 0x63, 0xbf, 0x43, - 0x1a, 0x5e, 0xa5, 0xcc, 0x8f, 0xcb, 0x10, 0xd7, 0xc3, 0x58, 0xa9, 0xaf, 0x36, 0xda, 0x54, 0x29, - 0x28, 0x4e, 0x66, 0x9e, 0x58, 0x18, 0xdf, 0x45, 0x67, 0x04, 0x78, 0xc5, 0x72, 0x3d, 0xdd, 0x6a, - 0x90, 0x4a, 0x99, 0x9f, 0xa1, 0xe1, 0x3c, 0xcf, 0xb9, 0x9a, 0x1c, 0x29, 0xb3, 0x4d, 0x2e, 0x8e, - 0x5f, 0x46, 0x23, 0x3e, 0x82, 0xbd, 0x7f, 0x0c, 0xc1, 0xfb, 0x07, 0x0c, 0x49, 0x63, 0xa5, 0x1e, - 0x75, 0x52, 0x91, 0x89, 0x45, 0x8d, 0x82, 0xb4, 0xa0, 0xc3, 0x92, 0x46, 0x79, 0x9b, 0xed, 0x44, - 0x65, 0x84, 0x54, 0xa1, 0xaf, 0x86, 0x1a, 0xb5, 0xe4, 0x98, 0xab, 0x26, 0x3b, 0x49, 0xfb, 0x7e, - 0x29, 0x2b, 0x75, 0x1b, 0x80, 0x49, 0xfa, 0xc1, 0xc8, 0x27, 0x4a, 0xe8, 0x54, 0x82, 0x8e, 0xed, - 0xe9, 0xc4, 0xf8, 0x85, 0x6c, 0xd8, 0x88, 0x23, 0x7e, 0x6c, 0x9c, 0x42, 0x03, 0xfe, 0x97, 0xf0, - 0xcd, 0x83, 0x92, 0x36, 0x34, 0xa3, 0x3c, 0x7c, 0xbc, 0x24, 0x8e, 0x23, 0x7e, 0x94, 0x3c, 0x08, - 0x71, 0xbc, 0x97, 0x09, 0xc5, 0x71, 0xc4, 0x8f, 0x97, 0xbf, 0x93, 0x0b, 0xe7, 0xa4, 0x93, 0x33, - 0xe6, 0x41, 0x6d, 0x93, 0x43, 0xf3, 0xa2, 0xbe, 0x3d, 0xf8, 0x87, 0x88, 0xaa, 0xd9, 0xff, 0x88, - 0xaa, 0xf9, 0x07, 0xf1, 0xfe, 0x64, 0x5b, 0xcf, 0x23, 0xd9, 0x9f, 0x07, 0x30, 0x58, 0xf1, 0xf5, - 0x70, 0x1d, 0x63, 0x7b, 0xf4, 0x5e, 0x21, 0x3c, 0xca, 0x0a, 0xdf, 0xa2, 0xcb, 0x24, 0xf8, 0xd3, - 0xe8, 0x9c, 0x04, 0xa8, 0xea, 0x8e, 0xde, 0x22, 0x5e, 0x98, 0xad, 0x05, 0x1c, 0xde, 0xfd, 0xd2, - 0xf5, 0x76, 0x80, 0x16, 0x33, 0xc0, 0xa4, 0x70, 0x10, 0x94, 0xa3, 0x7f, 0x0f, 0xb6, 0x67, 0xff, - 0x39, 0x8b, 0x46, 0xaa, 0xb6, 0xeb, 0xad, 0x3a, 0xc4, 0xad, 0xea, 0x8e, 0x4b, 0x8e, 0x6f, 0x8f, - 0x7e, 0x1c, 0x8d, 0x80, 0xff, 0x65, 0x8b, 0x58, 0x9e, 0x90, 0x16, 0x91, 0x85, 0x6c, 0xf4, 0x11, - 0x3c, 0x3a, 0xaf, 0x44, 0x88, 0x8b, 0xa8, 0x97, 0xe9, 0x80, 0xe0, 0x15, 0xcb, 0x14, 0x80, 0xc1, - 0xd5, 0x1f, 0xcf, 0xa1, 0x61, 0x5f, 0xca, 0x53, 0xe6, 0x51, 0xbd, 0xb3, 0x39, 0x5c, 0x21, 0x5f, - 0x43, 0xa8, 0x6a, 0x3b, 0x9e, 0xde, 0x14, 0x92, 0xab, 0xc3, 0x61, 0xa7, 0x0d, 0x50, 0x56, 0x46, - 0x20, 0xc1, 0x93, 0x08, 0x09, 0x03, 0xac, 0x1f, 0x06, 0xd8, 0xe8, 0xf6, 0x56, 0x11, 0x85, 0xe3, - 0x4a, 0x13, 0x28, 0xd4, 0x5f, 0xcb, 0xa2, 0x31, 0xbf, 0x93, 0x66, 0x1e, 0x92, 0x46, 0xc7, 0x3b, - 0xc6, 0x83, 0x41, 0x96, 0x76, 0xef, 0x8e, 0xd2, 0x56, 0xff, 0x87, 0x30, 0x91, 0x4c, 0x37, 0xed, - 0x93, 0x89, 0xe4, 0x2f, 0x43, 0xc7, 0xd5, 0xef, 0xce, 0xa1, 0xd3, 0xbe, 0xd4, 0x67, 0x3b, 0x16, - 0x6c, 0x13, 0xa6, 0xf5, 0x66, 0xf3, 0x38, 0xaf, 0xcb, 0x43, 0xbe, 0x20, 0x96, 0x78, 0x40, 0x03, - 0x1e, 0x29, 0xfd, 0x3e, 0x07, 0xd7, 0x6d, 0xd3, 0xd0, 0x44, 0x22, 0xfc, 0x2a, 0x1a, 0xf6, 0x7f, - 0x96, 0x9c, 0x55, 0x7f, 0x31, 0x86, 0x43, 0x7f, 0x50, 0x48, 0x77, 0x24, 0xbf, 0x0d, 0xa9, 0x80, - 0xfa, 0x5f, 0xfa, 0xd0, 0xc4, 0x3d, 0xd3, 0x32, 0xec, 0x0d, 0xd7, 0x0f, 0xb4, 0x7f, 0xe4, 0x37, - 0xbd, 0x87, 0x1d, 0x60, 0xff, 0x0d, 0x74, 0x26, 0x2a, 0x52, 0x27, 0x08, 0x7f, 0xc4, 0x7b, 0x67, - 0x83, 0x11, 0xd4, 0xfd, 0x90, 0xfb, 0xfc, 0xe6, 0x4c, 0x4b, 0x2e, 0x19, 0x8d, 0xd9, 0xdf, 0xbf, - 0x9b, 0x98, 0xfd, 0xcf, 0xa0, 0xbe, 0xb2, 0xdd, 0xd2, 0x4d, 0xdf, 0x83, 0x0f, 0x46, 0x71, 0x50, - 0x2f, 0x60, 0x34, 0x4e, 0x41, 0xf9, 0xf3, 0x8a, 0xa1, 0xcb, 0x06, 0x43, 0xfe, 0x7e, 0x81, 0x8e, - 0x4b, 0x1c, 0x4d, 0x24, 0xc2, 0x36, 0x1a, 0xe1, 0xd5, 0xf1, 0x7b, 0x2e, 0x04, 0xf7, 0x5c, 0x41, - 0x66, 0xc4, 0x74, 0xb5, 0x9a, 0x94, 0xca, 0xb1, 0x0b, 0x2f, 0x96, 0x4a, 0x80, 0x7f, 0x0c, 0xbb, - 0xf1, 0xd2, 0x64, 0xfe, 0x82, 0x10, 0x60, 0x92, 0x19, 0x8a, 0x0b, 0x01, 0x66, 0x19, 0x91, 0x08, - 0xcf, 0xa0, 0xf1, 0x52, 0xb3, 0x69, 0x6f, 0x04, 0x71, 0x86, 0xa8, 0x4a, 0x0c, 0x43, 0xac, 0x55, - 0xb8, 0x3e, 0xd1, 0x29, 0x12, 0x3e, 0xae, 0xde, 0xe0, 0x68, 0x2d, 0x5e, 0x62, 0xe2, 0x35, 0x84, - 0xe3, 0x6d, 0xde, 0xd3, 0x05, 0xca, 0x17, 0xb3, 0x08, 0x47, 0xce, 0x21, 0x33, 0xc7, 0x78, 0x3b, - 0xa5, 0xfe, 0x5c, 0x06, 0x8d, 0xc7, 0xe2, 0x7f, 0xe1, 0x1b, 0x08, 0x31, 0x88, 0x10, 0x77, 0x02, - 0x1c, 0xb9, 0xc2, 0x98, 0x60, 0x7c, 0x29, 0x09, 0xc9, 0xf0, 0x35, 0x34, 0xc0, 0x7e, 0x05, 0x89, - 0x3e, 0xa3, 0x45, 0x3a, 0x1d, 0xd3, 0xd0, 0x02, 0xa2, 0xb0, 0x16, 0xb8, 0x89, 0xcb, 0x25, 0x16, - 0xf1, 0x36, 0xdb, 0x41, 0x2d, 0x94, 0x8c, 0x76, 0xe0, 0x70, 0xd0, 0xe0, 0x92, 0x71, 0x58, 0x5d, - 0xd7, 0xc7, 0x43, 0xa9, 0xe5, 0x76, 0x0a, 0xa5, 0x16, 0x99, 0x9b, 0x78, 0xec, 0xb4, 0x83, 0x33, - 0x0f, 0xfd, 0x72, 0x16, 0x8d, 0x05, 0xb5, 0x1e, 0xe2, 0xa5, 0xcf, 0x07, 0x48, 0x24, 0x5f, 0xca, - 0x20, 0x65, 0xca, 0x6c, 0x36, 0x4d, 0x6b, 0xb5, 0x62, 0xdd, 0xb7, 0x9d, 0x16, 0x4c, 0x1e, 0x87, - 0x77, 0x3f, 0xa8, 0x7e, 0x5f, 0x06, 0x8d, 0xf3, 0x06, 0x4d, 0xeb, 0x8e, 0x71, 0x78, 0x17, 0xb7, - 0xd1, 0x96, 0x1c, 0x9e, 0xbe, 0xa8, 0x5f, 0xcd, 0x22, 0x34, 0x6f, 0x37, 0xd6, 0x8f, 0xb8, 0x0d, - 0xfc, 0x27, 0x76, 0xce, 0x6f, 0x5b, 0x90, 0xf3, 0xdb, 0x2a, 0x19, 0x3f, 0xc3, 0x2d, 0xad, 0x94, - 0xd2, 0xf1, 0x5d, 0x4d, 0x50, 0xa9, 0x98, 0x40, 0x97, 0x55, 0xba, 0xbd, 0x55, 0xcc, 0x37, 0xed, - 0xc6, 0xba, 0x06, 0xf4, 0xea, 0x5f, 0x64, 0x98, 0xec, 0x8e, 0xb8, 0x8d, 0xbc, 0xff, 0xf9, 0xf9, - 0x3d, 0x7e, 0xfe, 0xdf, 0xc8, 0xa0, 0xd3, 0x1a, 0x69, 0xd8, 0x0f, 0x88, 0xb3, 0x39, 0x6d, 0x1b, - 0xe4, 0x16, 0xb1, 0x88, 0x73, 0x58, 0x23, 0xea, 0x9f, 0x40, 0xb0, 0xc8, 0xb0, 0x31, 0x77, 0x5c, - 0x62, 0x1c, 0x9d, 0x90, 0xa5, 0xea, 0x3f, 0xee, 0x47, 0x4a, 0xe2, 0x0e, 0xf1, 0xc8, 0xee, 0x8a, - 0x52, 0xb7, 0xfd, 0xf9, 0x83, 0xda, 0xf6, 0xf7, 0xee, 0x6d, 0xdb, 0xdf, 0xb7, 0xd7, 0x6d, 0x7f, - 0xff, 0x6e, 0xb6, 0xfd, 0xad, 0xe8, 0xb6, 0x7f, 0x00, 0xb6, 0xfd, 0x37, 0xba, 0x6e, 0xfb, 0x67, - 0x2c, 0xe3, 0x11, 0x37, 0xfd, 0x47, 0x36, 0x51, 0xc7, 0xa3, 0x9c, 0x56, 0x2e, 0xd3, 0x49, 0xb1, - 0x61, 0x3b, 0x06, 0x31, 0xf8, 0x21, 0x05, 0x6e, 0xe5, 0x1d, 0x0e, 0xd3, 0x02, 0x6c, 0x2c, 0xeb, - 0xc9, 0xc8, 0x6e, 0xb2, 0x9e, 0x1c, 0xc0, 0x31, 0xe6, 0x0b, 0x59, 0x34, 0x3e, 0x4d, 0x1c, 0x8f, - 0x45, 0x04, 0x39, 0x88, 0xa7, 0xe0, 0x12, 0x1a, 0x13, 0x18, 0xc2, 0x8e, 0x5c, 0xc8, 0xd6, 0xdf, - 0x20, 0x8e, 0x17, 0x7d, 0x1d, 0x8f, 0xd2, 0xd3, 0xea, 0xfd, 0xc8, 0xc3, 0x7c, 0xec, 0x06, 0xd5, - 0xfb, 0x70, 0x26, 0x48, 0x93, 0xff, 0xd2, 0x02, 0x7a, 0x21, 0x98, 0x70, 0x7e, 0xef, 0xc1, 0x84, - 0xd5, 0x9f, 0xc9, 0xa0, 0x4b, 0x1a, 0xb1, 0xc8, 0x86, 0xbe, 0xd2, 0x24, 0x42, 0xb3, 0xf8, 0xca, - 0x40, 0x67, 0x0d, 0xd3, 0x6d, 0xe9, 0x5e, 0x63, 0x6d, 0x5f, 0x32, 0x9a, 0x45, 0xc3, 0xe2, 0xfc, - 0xb5, 0x87, 0xb9, 0x4d, 0x2a, 0xa7, 0xfe, 0x6a, 0x0e, 0xf5, 0x4f, 0xd9, 0xde, 0xbe, 0x73, 0x7e, - 0x87, 0x53, 0x7e, 0x76, 0x0f, 0xf7, 0x22, 0x1f, 0x85, 0xca, 0x85, 0x58, 0x80, 0x60, 0x3a, 0xb1, - 0x62, 0xc7, 0x62, 0x26, 0xfa, 0x64, 0x7b, 0x8c, 0x6b, 0xfd, 0x3c, 0x1a, 0x04, 0x7f, 0x4d, 0xe1, - 0xe6, 0x12, 0x0c, 0x93, 0x3c, 0x0a, 0x8c, 0xd6, 0x11, 0x92, 0xe2, 0x4f, 0x4b, 0x21, 0x4c, 0xfa, - 0xf6, 0x1f, 0x07, 0x5b, 0x8c, 0x66, 0x72, 0x60, 0xe1, 0xa6, 0xd5, 0x6f, 0xe5, 0xd1, 0xb0, 0x6f, - 0x8e, 0x72, 0x48, 0x3d, 0xf8, 0x2c, 0xea, 0x9b, 0xb3, 0x85, 0xb8, 0x86, 0x60, 0xbe, 0xb2, 0x66, - 0xbb, 0x11, 0xbb, 0x1c, 0x4e, 0x84, 0x6f, 0xa0, 0x81, 0x45, 0xdb, 0x10, 0x8d, 0xaf, 0x60, 0x4c, - 0x5b, 0xb6, 0x11, 0x73, 0x5e, 0x09, 0x08, 0xf1, 0x25, 0x94, 0x07, 0xbb, 0x35, 0xe1, 0xea, 0x39, - 0x62, 0xab, 0x06, 0x78, 0x41, 0x37, 0xfa, 0xf6, 0xaa, 0x1b, 0xfd, 0x8f, 0xaa, 0x1b, 0x03, 0x07, - 0xab, 0x1b, 0x6f, 0xa1, 0x61, 0xa8, 0xc9, 0x8f, 0xdb, 0xbd, 0xf3, 0xf2, 0xf6, 0x18, 0x5f, 0x81, - 0x46, 0x58, 0xbb, 0x79, 0xf4, 0x6e, 0x58, 0x78, 0x24, 0x56, 0x11, 0xb5, 0x43, 0xfb, 0x50, 0xbb, - 0x3f, 0xc8, 0xa0, 0xfe, 0x3b, 0xd6, 0xba, 0x65, 0x6f, 0xec, 0x4f, 0xe3, 0x6e, 0xa0, 0x21, 0xce, - 0x46, 0x98, 0xe3, 0xc1, 0x1f, 0xa9, 0xc3, 0xc0, 0x75, 0xe0, 0xa4, 0x89, 0x54, 0xf8, 0xe5, 0xa0, - 0x10, 0x98, 0xa6, 0xe6, 0xc2, 0xc8, 0xa0, 0x7e, 0xa1, 0x86, 0x1c, 0xcc, 0x50, 0x24, 0xc7, 0xe7, - 0x79, 0x16, 0x7b, 0x21, 0x34, 0x0e, 0x6d, 0x0a, 0x4b, 0x62, 0xaf, 0xfe, 0xab, 0x2c, 0x1a, 0x8d, - 0x5c, 0x3f, 0x3d, 0x83, 0x06, 0xf9, 0xf5, 0x8f, 0xe9, 0x47, 0x57, 0x04, 0xd3, 0xd5, 0x00, 0xa8, - 0x0d, 0xb0, 0x3f, 0x2b, 0x06, 0xfe, 0x24, 0xea, 0xb7, 0x5d, 0x58, 0x9a, 0xe0, 0x5b, 0x46, 0xc3, - 0x21, 0xb4, 0x54, 0xa3, 0x6d, 0x67, 0x83, 0x83, 0x93, 0x88, 0x1a, 0x69, 0xbb, 0xf0, 0x69, 0x37, - 0xd1, 0xa0, 0xee, 0xba, 0xc4, 0xab, 0x7b, 0xfa, 0xaa, 0x18, 0x70, 0x31, 0x00, 0x8a, 0xa3, 0x03, - 0x80, 0xcb, 0xfa, 0x2a, 0x7e, 0x0d, 0x8d, 0x34, 0x1c, 0x02, 0x8b, 0x97, 0xde, 0xa4, 0xad, 0x14, - 0x36, 0x97, 0x12, 0x42, 0xbc, 0xf1, 0x0f, 0x11, 0x15, 0x03, 0xdf, 0x45, 0x23, 0xfc, 0x73, 0x98, - 0xdd, 0x18, 0x0c, 0xb4, 0xd1, 0x70, 0x31, 0x61, 0x22, 0x61, 0x96, 0x63, 0xdc, 0x7c, 0x50, 0x24, - 0x17, 0xf9, 0x1a, 0x02, 0xa9, 0xfa, 0xf5, 0x0c, 0xdd, 0xf0, 0x50, 0x40, 0x90, 0x10, 0xb3, 0xb5, - 0x47, 0x5d, 0x69, 0x85, 0x31, 0xef, 0xfb, 0xdc, 0x2e, 0xb3, 0x93, 0xc6, 0xb1, 0x78, 0x12, 0xf5, - 0x19, 0xe2, 0xdd, 0xcf, 0x59, 0xf9, 0x23, 0xfc, 0x7a, 0x34, 0x4e, 0x85, 0x2f, 0xa3, 0x3c, 0xdd, - 0xd0, 0x46, 0x0f, 0x7e, 0xe2, 0x1a, 0xa9, 0x01, 0x85, 0xfa, 0x9d, 0x59, 0x34, 0x2c, 0x7c, 0xcd, - 0xf5, 0x7d, 0x7d, 0xce, 0x4b, 0xbb, 0x6b, 0x26, 0xb7, 0x64, 0x05, 0x58, 0xd0, 0xe4, 0x9b, 0x81, - 0x28, 0x76, 0xf5, 0x04, 0xc1, 0x05, 0xf3, 0x3c, 0xff, 0xd0, 0xbe, 0xdd, 0x1f, 0x82, 0x28, 0xfd, - 0xeb, 0xf9, 0x81, 0x6c, 0x21, 0xf7, 0x7a, 0x7e, 0x20, 0x5f, 0xe8, 0x05, 0xdf, 0x78, 0x88, 0x07, - 0xc5, 0x4e, 0x98, 0xd6, 0x7d, 0x73, 0xf5, 0x88, 0x5b, 0xfe, 0x1d, 0x6c, 0xdc, 0x80, 0x88, 0x6c, - 0x8e, 0xb8, 0x19, 0xe0, 0xfb, 0x2a, 0x9b, 0x93, 0x14, 0x04, 0x5c, 0x36, 0xff, 0x2e, 0x83, 0x94, - 0x44, 0xd9, 0x94, 0x0e, 0xe9, 0xe5, 0xfb, 0xe0, 0x12, 0x11, 0x7c, 0x33, 0x8b, 0xc6, 0x2b, 0x96, - 0x47, 0x56, 0xd9, 0xb9, 0xe7, 0x88, 0x4f, 0x15, 0xb7, 0x59, 0x22, 0x52, 0xfe, 0x31, 0xbc, 0xcf, - 0x1f, 0x0f, 0x4e, 0x95, 0x21, 0x2a, 0x85, 0x93, 0x58, 0xfa, 0x00, 0x13, 0x14, 0x45, 0x84, 0x7c, - 0xc4, 0xe7, 0x9c, 0xa3, 0x21, 0xe4, 0x23, 0x3e, 0x79, 0x7d, 0x40, 0x85, 0xfc, 0xdf, 0x33, 0xe8, - 0x54, 0x42, 0xe5, 0x90, 0xde, 0xaf, 0xb3, 0x02, 0x41, 0x13, 0x32, 0x42, 0x7a, 0xbf, 0xce, 0x0a, - 0xc4, 0x4b, 0xd0, 0x7c, 0x24, 0x5e, 0x06, 0xd7, 0xa8, 0xa5, 0x4a, 0x79, 0x9a, 0x4b, 0x55, 0x15, - 0x9c, 0xbc, 0x28, 0x38, 0xe9, 0xcb, 0x02, 0xf7, 0x29, 0xdb, 0x34, 0x1a, 0x11, 0xf7, 0x29, 0x5a, - 0x06, 0x7f, 0x06, 0x0d, 0x96, 0xde, 0xed, 0x38, 0x04, 0xf8, 0x32, 0x89, 0x7f, 0x28, 0xe0, 0xeb, - 0x23, 0x92, 0x38, 0x33, 0x4f, 0x30, 0x4a, 0x11, 0xe5, 0x1d, 0x32, 0x54, 0xbf, 0x98, 0x41, 0x13, - 0xe9, 0xad, 0xc3, 0x1f, 0x45, 0xfd, 0xf4, 0x64, 0x5b, 0xd2, 0x16, 0xf9, 0xa7, 0xb3, 0xa4, 0x1d, - 0x76, 0x93, 0xd4, 0x75, 0x47, 0xdc, 0x78, 0xfb, 0x64, 0xf8, 0x15, 0x34, 0x54, 0x71, 0xdd, 0x0e, - 0x71, 0x6a, 0x37, 0xee, 0x68, 0x15, 0x7e, 0xa6, 0x82, 0x3d, 0xbb, 0x09, 0xe0, 0xba, 0x7b, 0x23, - 0x12, 0x16, 0x41, 0xa4, 0x57, 0xbf, 0x3f, 0x83, 0xce, 0x77, 0xfb, 0x2a, 0x7a, 0x80, 0x5f, 0x26, - 0x96, 0x6e, 0x79, 0x3c, 0x39, 0x2e, 0x3f, 0xa2, 0x78, 0x00, 0x93, 0x0f, 0x19, 0x01, 0x21, 0x2d, - 0xc4, 0x6e, 0xc7, 0x82, 0xe7, 0x78, 0x76, 0x93, 0x07, 0xb0, 0x48, 0x21, 0x9f, 0x50, 0xfd, 0xe9, - 0x37, 0x51, 0xef, 0x92, 0x45, 0x96, 0xee, 0xe3, 0xe7, 0x84, 0x14, 0x6c, 0x7c, 0xa0, 0x8d, 0x8b, - 0x03, 0x06, 0x10, 0x73, 0x3d, 0x9a, 0x90, 0xa8, 0xed, 0xa6, 0x98, 0x47, 0x8a, 0xab, 0x03, 0x16, - 0xcb, 0x30, 0xcc, 0x5c, 0x8f, 0x26, 0xe6, 0x9b, 0xba, 0x29, 0xa6, 0x47, 0xe2, 0x9d, 0x2d, 0x95, - 0x62, 0x18, 0xbf, 0x14, 0x9f, 0x06, 0xe6, 0x93, 0x72, 0x08, 0x45, 0xf7, 0x04, 0x71, 0x8a, 0xb9, - 0x1e, 0x2d, 0x39, 0xf7, 0xd0, 0xb0, 0x68, 0x18, 0x13, 0x7d, 0x90, 0x13, 0x71, 0x73, 0x3d, 0x9a, - 0x44, 0x8b, 0x5f, 0x08, 0x12, 0x35, 0xbe, 0x6e, 0x9b, 0x56, 0xd4, 0x3f, 0x52, 0x40, 0xcd, 0xf5, - 0x68, 0x22, 0xa5, 0x50, 0x69, 0xd5, 0x31, 0x83, 0x2c, 0x6a, 0xd1, 0x4a, 0x01, 0x27, 0x54, 0x0a, - 0xbf, 0xf1, 0x2b, 0x68, 0x24, 0x70, 0x3c, 0x7d, 0x87, 0x34, 0x3c, 0x7e, 0x25, 0x72, 0x26, 0x52, - 0x98, 0x21, 0xe7, 0x7a, 0x34, 0x99, 0x1a, 0x5f, 0xf6, 0x53, 0xf4, 0xf3, 0xbb, 0x8e, 0x51, 0x61, - 0x3a, 0x33, 0xdf, 0xa5, 0x52, 0xf2, 0x53, 0xf8, 0xdf, 0x14, 0x53, 0xb3, 0xf3, 0x0b, 0x0c, 0x1c, - 0xa9, 0x65, 0xc6, 0x32, 0x68, 0xef, 0x08, 0x0f, 0x47, 0xaf, 0x45, 0x93, 0x18, 0xf3, 0xd4, 0xd8, - 0x67, 0x23, 0x25, 0x39, 0x76, 0xae, 0x47, 0x8b, 0x26, 0x3d, 0x7e, 0x41, 0x4a, 0xa0, 0xcb, 0x23, - 0xa0, 0x44, 0xa5, 0x4a, 0x51, 0x82, 0x54, 0x21, 0xd5, 0xee, 0x6b, 0xd1, 0x8c, 0xae, 0x3c, 0xde, - 0xc9, 0xd9, 0xe4, 0xbc, 0x9f, 0x42, 0xd5, 0x7e, 0x06, 0xd8, 0x17, 0xa4, 0xcc, 0x9b, 0x90, 0xdc, - 0x3a, 0xa1, 0x6a, 0xdd, 0xd3, 0xc5, 0xaa, 0xd9, 0xf9, 0x52, 0xca, 0x01, 0x09, 0x29, 0x6a, 0xe2, - 0x1d, 0x0a, 0x38, 0xa1, 0x43, 0x59, 0xbe, 0xc8, 0x17, 0xa4, 0x34, 0x24, 0x3c, 0x07, 0x4d, 0x50, - 0xa9, 0x80, 0xa2, 0x95, 0x8a, 0x09, 0x4b, 0x6e, 0x8a, 0xd9, 0x39, 0x94, 0x71, 0xb9, 0x83, 0x42, - 0x0c, 0xed, 0x20, 0x21, 0x8b, 0x47, 0x11, 0x22, 0xff, 0x2b, 0x18, 0xc8, 0x87, 0x82, 0x16, 0x4e, - 0x57, 0xe7, 0x7a, 0x34, 0xc8, 0x09, 0xa0, 0xb2, 0x9c, 0x12, 0xca, 0x29, 0xa0, 0x18, 0x0e, 0x32, - 0x9c, 0x3e, 0x24, 0x8d, 0xb9, 0x1e, 0x8d, 0xe5, 0x9b, 0x78, 0x4e, 0x88, 0xde, 0xac, 0x9c, 0x96, - 0xa7, 0x88, 0x00, 0x41, 0xa7, 0x88, 0x30, 0xc6, 0xf3, 0x6c, 0x3c, 0xc2, 0xb1, 0x72, 0x46, 0x5e, - 0x51, 0xa3, 0xf8, 0xb9, 0x1e, 0x2d, 0x1e, 0x15, 0xf9, 0x05, 0x29, 0xe8, 0xaf, 0x72, 0x36, 0xe2, - 0x94, 0x1c, 0xa2, 0xa8, 0xb8, 0xc4, 0xf0, 0xc0, 0x4b, 0x89, 0x69, 0xba, 0x94, 0x73, 0xf2, 0x72, - 0x9c, 0x40, 0x32, 0xd7, 0xa3, 0x25, 0x26, 0xf8, 0x9a, 0x8e, 0x85, 0xde, 0x55, 0x14, 0xf9, 0xdd, - 0x32, 0x82, 0x9e, 0xeb, 0xd1, 0x62, 0xc1, 0x7a, 0x6f, 0x8a, 0x31, 0x6f, 0x95, 0xc7, 0xe4, 0x4e, - 0x0c, 0x31, 0xb4, 0x13, 0x85, 0xd8, 0xb8, 0x37, 0xc5, 0xc0, 0xb0, 0xca, 0x44, 0xbc, 0x54, 0x38, - 0x73, 0x0a, 0x01, 0x64, 0xb5, 0xe4, 0xe8, 0xa9, 0xca, 0xe3, 0x3c, 0xb8, 0x3e, 0x2f, 0x9f, 0x44, - 0x33, 0xd7, 0xa3, 0x25, 0x47, 0x5e, 0xd5, 0x92, 0xc3, 0x8e, 0x2a, 0xe7, 0xbb, 0xf1, 0x0c, 0x5a, - 0x97, 0x1c, 0xb2, 0x54, 0xef, 0x12, 0x04, 0x52, 0xb9, 0x20, 0x47, 0x44, 0x4a, 0x25, 0x9c, 0xeb, - 0xd1, 0xba, 0x84, 0x92, 0xbc, 0x93, 0x12, 0x91, 0x51, 0xb9, 0x28, 0xe7, 0xd6, 0x48, 0x24, 0x9a, - 0xeb, 0xd1, 0x52, 0xe2, 0x39, 0xde, 0x49, 0x09, 0x66, 0xa8, 0x14, 0xbb, 0xb2, 0x0d, 0xe4, 0x91, - 0x12, 0x0a, 0x71, 0x29, 0x31, 0x0e, 0xa0, 0xf2, 0x84, 0xac, 0xba, 0x09, 0x24, 0x54, 0x75, 0x93, - 0x22, 0x08, 0x2e, 0x25, 0x06, 0xe2, 0x53, 0x9e, 0xec, 0xc2, 0x30, 0x68, 0x63, 0x62, 0x08, 0xbf, - 0xa5, 0xc4, 0x48, 0x78, 0x8a, 0x2a, 0x33, 0x4c, 0x20, 0xa1, 0x0c, 0x93, 0x62, 0xe8, 0x2d, 0x25, - 0x86, 0xa2, 0x53, 0x9e, 0xea, 0xc2, 0x30, 0x6c, 0x61, 0x52, 0x10, 0xbb, 0x17, 0xa4, 0x58, 0x70, - 0xca, 0x87, 0xe4, 0x79, 0x43, 0x40, 0xd1, 0x79, 0x43, 0x8c, 0x1a, 0x37, 0x1d, 0x8b, 0x76, 0xa3, - 0x7c, 0x58, 0x1e, 0xe6, 0x11, 0x34, 0x1d, 0xe6, 0xd1, 0xf8, 0x38, 0xd3, 0xb1, 0xa8, 0x1f, 0xca, - 0xa5, 0x34, 0x26, 0x80, 0x96, 0x99, 0xb0, 0x38, 0x21, 0x95, 0x84, 0xb0, 0x13, 0xca, 0xd3, 0xb2, - 0xcd, 0x5d, 0x8c, 0x60, 0xae, 0x47, 0x4b, 0x08, 0x56, 0xa1, 0x25, 0xfb, 0x58, 0x2a, 0x97, 0xe5, - 0x61, 0x9b, 0x44, 0x43, 0x87, 0x6d, 0xa2, 0x7f, 0xe6, 0x7c, 0x92, 0x7d, 0xad, 0x72, 0x45, 0xde, - 0x98, 0xc5, 0x29, 0xe8, 0xc6, 0x2c, 0xc1, 0x2e, 0x57, 0x4b, 0xf6, 0x1a, 0x54, 0x9e, 0xe9, 0xda, - 0x42, 0xa0, 0x49, 0x68, 0x21, 0x73, 0xa2, 0x0b, 0xf7, 0x4e, 0x77, 0xda, 0x4d, 0x5b, 0x37, 0x94, - 0x8f, 0x24, 0xee, 0x9d, 0x18, 0x52, 0xd8, 0x3b, 0x31, 0x00, 0x5d, 0xe5, 0x45, 0xfb, 0x53, 0xe5, - 0xaa, 0xbc, 0xca, 0x8b, 0x38, 0xba, 0xca, 0x4b, 0xb6, 0xaa, 0xd3, 0x31, 0x5b, 0x4d, 0xe5, 0x59, - 0x59, 0x01, 0x22, 0x68, 0xaa, 0x00, 0x51, 0xeb, 0xce, 0xb7, 0xd3, 0xad, 0x1b, 0x95, 0x49, 0xe0, - 0xf6, 0x44, 0x90, 0xc3, 0x3d, 0x85, 0x6e, 0xae, 0x47, 0x4b, 0xb7, 0x90, 0xac, 0x24, 0x18, 0x2b, - 0x2a, 0xd7, 0x64, 0x05, 0x8b, 0x11, 0x50, 0x05, 0x8b, 0x9b, 0x38, 0x56, 0x12, 0xac, 0x0d, 0x95, - 0x8f, 0xa6, 0xb2, 0x0a, 0xbe, 0x39, 0xc1, 0x46, 0xf1, 0xa6, 0x68, 0x2e, 0xa8, 0x3c, 0x27, 0x2f, - 0x76, 0x21, 0x86, 0x2e, 0x76, 0x82, 0x59, 0xe1, 0x4d, 0xd1, 0x50, 0x4e, 0xb9, 0x1e, 0x2f, 0x15, - 0x2e, 0x91, 0x82, 0x41, 0x9d, 0x96, 0x6c, 0x5f, 0xa6, 0xdc, 0x90, 0xb5, 0x2e, 0x89, 0x86, 0x6a, - 0x5d, 0xa2, 0x6d, 0xda, 0x6c, 0xdc, 0x4c, 0x4c, 0xb9, 0x19, 0xbd, 0x4b, 0x90, 0xf1, 0x74, 0xe7, - 0x13, 0x33, 0x2d, 0x7b, 0x2d, 0x1a, 0x00, 0x40, 0xf9, 0x58, 0xe4, 0x31, 0x43, 0xc2, 0xd2, 0xfd, - 0x6d, 0x24, 0x60, 0xc0, 0x6b, 0x51, 0x9f, 0x79, 0xe5, 0xf9, 0x64, 0x0e, 0x81, 0xae, 0x44, 0x7d, - 0xec, 0x5f, 0x8b, 0xba, 0x99, 0x2b, 0x2f, 0x24, 0x73, 0x08, 0xa4, 0x1b, 0x75, 0x4b, 0x7f, 0x4e, - 0x08, 0x7c, 0xa7, 0x7c, 0x5c, 0xde, 0x3a, 0x06, 0x08, 0xba, 0x75, 0x0c, 0xc3, 0xe3, 0x3d, 0x27, - 0x04, 0x8c, 0x53, 0x5e, 0x8c, 0x15, 0x09, 0x1a, 0x2b, 0x84, 0x95, 0x7b, 0x4e, 0x08, 0xb4, 0xa6, - 0xbc, 0x14, 0x2b, 0x12, 0xb4, 0x4e, 0x08, 0xc7, 0x66, 0x74, 0xf3, 0xc3, 0x51, 0x3e, 0x21, 0x5f, - 0x71, 0xa4, 0x53, 0xce, 0xf5, 0x68, 0xdd, 0xfc, 0x79, 0xde, 0x4e, 0x37, 0xba, 0x53, 0x5e, 0x96, - 0x87, 0x70, 0x1a, 0x1d, 0x1d, 0xc2, 0xa9, 0x86, 0x7b, 0xaf, 0x44, 0x7c, 0x72, 0x95, 0x57, 0xe4, - 0x29, 0x4e, 0x42, 0xd2, 0x29, 0x2e, 0xea, 0xc1, 0x2b, 0x39, 0x9b, 0x2a, 0x9f, 0x94, 0xa7, 0x38, - 0x11, 0x47, 0xa7, 0x38, 0xc9, 0x31, 0x75, 0x3a, 0xe6, 0x03, 0xa9, 0xbc, 0x2a, 0x4f, 0x71, 0x11, - 0x34, 0x9d, 0xe2, 0xa2, 0x5e, 0x93, 0xaf, 0x44, 0x5c, 0x01, 0x95, 0xd7, 0x92, 0xdb, 0x0f, 0x48, - 0xb1, 0xfd, 0xcc, 0x71, 0x50, 0x4b, 0xf6, 0x69, 0x53, 0x4a, 0xf2, 0xf8, 0x4d, 0xa2, 0xa1, 0xe3, - 0x37, 0xd1, 0x1f, 0x6e, 0x29, 0x31, 0xb3, 0xa5, 0x32, 0xd5, 0xe5, 0xe0, 0x10, 0x6e, 0x45, 0x92, - 0x72, 0x62, 0x8a, 0x67, 0x64, 0x76, 0x10, 0x9a, 0x4e, 0x39, 0x23, 0xfb, 0xc7, 0xa0, 0x08, 0x3d, - 0x9d, 0x5d, 0x63, 0x36, 0x60, 0x4a, 0x59, 0x9e, 0x5d, 0x63, 0x04, 0x74, 0x76, 0x8d, 0x5b, 0x8e, - 0xcd, 0xa2, 0x02, 0xd7, 0x22, 0x66, 0xda, 0x66, 0x5a, 0xab, 0xca, 0x4c, 0xc4, 0xa5, 0x24, 0x82, - 0xa7, 0xb3, 0x53, 0x14, 0x06, 0xeb, 0x35, 0x83, 0x4d, 0x37, 0xcd, 0xf6, 0x8a, 0xad, 0x3b, 0x46, - 0x8d, 0x58, 0x86, 0x32, 0x1b, 0x59, 0xaf, 0x13, 0x68, 0x60, 0xbd, 0x4e, 0x80, 0x83, 0xd3, 0x7b, - 0x04, 0xae, 0x91, 0x06, 0x31, 0x1f, 0x10, 0xe5, 0x16, 0xb0, 0x2d, 0xa6, 0xb1, 0xe5, 0x64, 0x73, - 0x3d, 0x5a, 0x1a, 0x07, 0xba, 0x57, 0x5f, 0xd8, 0xac, 0xbd, 0x31, 0x1f, 0xb8, 0x51, 0x56, 0x1d, - 0xd2, 0xd6, 0x1d, 0xa2, 0xcc, 0xc9, 0x7b, 0xf5, 0x44, 0x22, 0xba, 0x57, 0x4f, 0x44, 0xc4, 0xd9, - 0xfa, 0x63, 0xa1, 0xd2, 0x8d, 0x6d, 0x38, 0x22, 0x92, 0x4b, 0xd3, 0xd9, 0x49, 0x46, 0x50, 0x01, - 0xcd, 0xdb, 0xd6, 0x2a, 0xdc, 0x54, 0xbc, 0x2e, 0xcf, 0x4e, 0xe9, 0x94, 0x74, 0x76, 0x4a, 0xc7, - 0x52, 0x55, 0x97, 0xb1, 0x6c, 0x0c, 0xde, 0x96, 0x55, 0x3d, 0x81, 0x84, 0xaa, 0x7a, 0x02, 0x38, - 0xce, 0x50, 0x23, 0x2e, 0xf1, 0x94, 0xf9, 0x6e, 0x0c, 0x81, 0x24, 0xce, 0x10, 0xc0, 0x71, 0x86, - 0xb3, 0xc4, 0x6b, 0xac, 0x29, 0x0b, 0xdd, 0x18, 0x02, 0x49, 0x9c, 0x21, 0x80, 0xe9, 0x61, 0x53, - 0x06, 0x4f, 0x75, 0x9a, 0xeb, 0x7e, 0x9f, 0x2d, 0xca, 0x87, 0xcd, 0x54, 0x42, 0x7a, 0xd8, 0x4c, - 0x45, 0xe2, 0xef, 0xdf, 0xb5, 0x8d, 0xa2, 0xb2, 0x04, 0x15, 0x4e, 0x86, 0xfb, 0x82, 0xdd, 0x94, - 0x9a, 0xeb, 0xd1, 0x76, 0x6b, 0x03, 0xf9, 0x91, 0xc0, 0x94, 0x48, 0xa9, 0x42, 0x55, 0x63, 0xc1, - 0x5d, 0x05, 0x03, 0xcf, 0xf5, 0x68, 0x81, 0xb1, 0xd1, 0x0b, 0x68, 0x08, 0x3e, 0xaa, 0x62, 0x99, - 0x5e, 0x79, 0x4a, 0x79, 0x43, 0x3e, 0x32, 0x09, 0x28, 0x7a, 0x64, 0x12, 0x7e, 0xd2, 0x49, 0x1c, - 0x7e, 0xb2, 0x29, 0xa6, 0x3c, 0xa5, 0x68, 0xf2, 0x24, 0x2e, 0x21, 0xe9, 0x24, 0x2e, 0x01, 0x82, - 0x7a, 0xcb, 0x8e, 0xdd, 0x2e, 0x4f, 0x29, 0xb5, 0x84, 0x7a, 0x19, 0x2a, 0xa8, 0x97, 0xfd, 0x0c, - 0xea, 0xad, 0xad, 0x75, 0xbc, 0x32, 0xfd, 0xc6, 0xe5, 0x84, 0x7a, 0x7d, 0x64, 0x50, 0xaf, 0x0f, - 0xa0, 0x53, 0x21, 0x00, 0xaa, 0x8e, 0x4d, 0x27, 0xed, 0xdb, 0x66, 0xb3, 0xa9, 0xdc, 0x91, 0xa7, - 0xc2, 0x28, 0x9e, 0x4e, 0x85, 0x51, 0x18, 0xdd, 0x7a, 0xb2, 0x56, 0x91, 0x95, 0xce, 0xaa, 0x72, - 0x57, 0xde, 0x7a, 0x86, 0x18, 0xba, 0xf5, 0x0c, 0x7f, 0xc1, 0xe9, 0x82, 0xfe, 0xd2, 0xc8, 0x7d, - 0x87, 0xb8, 0x6b, 0xca, 0xbd, 0xc8, 0xe9, 0x42, 0xc0, 0xc1, 0xe9, 0x42, 0xf8, 0x8d, 0x57, 0xd1, - 0xe3, 0xd2, 0x42, 0xe3, 0xbf, 0x3d, 0xd5, 0x88, 0xee, 0x34, 0xd6, 0x94, 0x37, 0x81, 0xd5, 0x53, - 0x89, 0x4b, 0x95, 0x4c, 0x3a, 0xd7, 0xa3, 0x75, 0xe3, 0x04, 0xc7, 0xf2, 0x37, 0xe6, 0x59, 0x74, - 0x1a, 0xad, 0x3a, 0xed, 0x1f, 0x42, 0xdf, 0x8a, 0x1c, 0xcb, 0xe3, 0x24, 0x70, 0x2c, 0x8f, 0x83, - 0x71, 0x1b, 0x5d, 0x8c, 0x1c, 0xd5, 0x16, 0xf4, 0x26, 0x3d, 0x97, 0x10, 0xa3, 0xaa, 0x37, 0xd6, - 0x89, 0xa7, 0x7c, 0x0a, 0x78, 0x5f, 0x4a, 0x39, 0xf0, 0x45, 0xa8, 0xe7, 0x7a, 0xb4, 0x1d, 0xf8, - 0x61, 0x95, 0xe5, 0x4e, 0x54, 0x3e, 0x2d, 0xdf, 0x6f, 0x52, 0xd8, 0x5c, 0x8f, 0xc6, 0xf2, 0x2a, - 0xbe, 0x8d, 0x94, 0x3b, 0xed, 0x55, 0x47, 0x37, 0x08, 0xdb, 0x68, 0xc1, 0xde, 0x8d, 0x6f, 0x40, - 0x3f, 0x23, 0xef, 0xd2, 0xd2, 0xe8, 0xe8, 0x2e, 0x2d, 0x0d, 0x47, 0x15, 0x55, 0x0a, 0xc4, 0xaa, - 0x7c, 0x56, 0x56, 0x54, 0x09, 0x49, 0x15, 0x55, 0x0e, 0xdb, 0xfa, 0x26, 0x3a, 0x1b, 0x9c, 0xe7, - 0xf9, 0xfa, 0xcb, 0x3a, 0x4d, 0x79, 0x1b, 0xf8, 0x5c, 0x8c, 0x3d, 0x06, 0x48, 0x54, 0x73, 0x3d, - 0x5a, 0x4a, 0x79, 0xba, 0xe2, 0xc6, 0x62, 0x8c, 0xf3, 0xed, 0xc5, 0x77, 0xc8, 0x2b, 0x6e, 0x0a, - 0x19, 0x5d, 0x71, 0x53, 0x50, 0x89, 0xcc, 0xb9, 0x50, 0xf5, 0x1d, 0x98, 0x07, 0x32, 0x4d, 0xe3, - 0x90, 0xc8, 0x9c, 0xef, 0xd4, 0x56, 0x76, 0x60, 0x1e, 0xec, 0xd6, 0xd2, 0x38, 0xe0, 0xcb, 0xa8, - 0xaf, 0x56, 0x5b, 0xd0, 0x3a, 0x96, 0xd2, 0x88, 0xd8, 0x80, 0x01, 0x74, 0xae, 0x47, 0xe3, 0x78, - 0xba, 0x0d, 0x9a, 0x69, 0xea, 0xae, 0x67, 0x36, 0x5c, 0x18, 0x31, 0xfe, 0x08, 0x31, 0xe4, 0x6d, - 0x50, 0x12, 0x0d, 0xdd, 0x06, 0x25, 0xc1, 0xe9, 0x7e, 0x71, 0x5a, 0x77, 0x5d, 0xdd, 0x32, 0x1c, - 0x7d, 0x0a, 0x96, 0x09, 0x12, 0xb1, 0x94, 0x97, 0xb0, 0x74, 0xbf, 0x28, 0x43, 0xe0, 0xf2, 0xdd, - 0x87, 0xf8, 0xdb, 0x9c, 0xfb, 0x91, 0xcb, 0xf7, 0x08, 0x1e, 0x2e, 0xdf, 0x23, 0x30, 0xd8, 0x77, - 0xfa, 0x30, 0x8d, 0xac, 0x9a, 0x90, 0xe9, 0x78, 0x35, 0xb2, 0xef, 0x8c, 0x12, 0xc0, 0xbe, 0x33, - 0x0a, 0x94, 0x9a, 0xe4, 0x2f, 0xb7, 0x6b, 0x29, 0x4d, 0x0a, 0x57, 0xd9, 0x58, 0x19, 0xba, 0x7e, - 0x87, 0x83, 0xa3, 0xbc, 0x69, 0xe9, 0x2d, 0xbb, 0x3c, 0xe5, 0x4b, 0xdd, 0x94, 0xd7, 0xef, 0x54, - 0x42, 0xba, 0x7e, 0xa7, 0x22, 0xe9, 0xec, 0xea, 0x1f, 0xb4, 0xd6, 0x74, 0x87, 0x18, 0x41, 0xfe, - 0x4f, 0x76, 0x34, 0x7c, 0x47, 0x9e, 0x5d, 0xbb, 0x90, 0xd2, 0xd9, 0xb5, 0x0b, 0x9a, 0x6e, 0xf2, - 0x92, 0xd1, 0x1a, 0xd1, 0x0d, 0x65, 0x5d, 0xde, 0xe4, 0xa5, 0x53, 0xd2, 0x4d, 0x5e, 0x3a, 0x36, - 0xfd, 0x73, 0xee, 0x39, 0xa6, 0x47, 0x94, 0xe6, 0x6e, 0x3e, 0x07, 0x48, 0xd3, 0x3f, 0x07, 0xd0, - 0xf4, 0x40, 0x18, 0xed, 0x90, 0x96, 0x7c, 0x20, 0x8c, 0x77, 0x43, 0xb4, 0x04, 0xdd, 0xb1, 0x70, - 0x87, 0x09, 0xc5, 0x92, 0x77, 0x2c, 0x1c, 0x4c, 0x77, 0x2c, 0xa1, 0x4b, 0x85, 0x64, 0xa0, 0xaf, - 0xd8, 0xf2, 0x1a, 0x2a, 0xe2, 0xe8, 0x1a, 0x2a, 0x19, 0xf3, 0xbf, 0x20, 0x59, 0xcf, 0x2a, 0x6d, - 0x79, 0xd7, 0x21, 0xa0, 0xe8, 0xae, 0x43, 0xb4, 0xb3, 0x9d, 0x46, 0x63, 0xf0, 0x0a, 0xae, 0x75, - 0x82, 0x77, 0x9c, 0xcf, 0xc9, 0x9f, 0x19, 0x41, 0xd3, 0xcf, 0x8c, 0x80, 0x24, 0x26, 0x7c, 0xda, - 0x72, 0x52, 0x98, 0x84, 0xf7, 0x83, 0x11, 0x10, 0x9e, 0x47, 0xb8, 0x56, 0x5a, 0x98, 0xaf, 0x18, - 0x55, 0xf1, 0x89, 0xcc, 0x95, 0x6f, 0x60, 0xe3, 0x14, 0x73, 0x3d, 0x5a, 0x42, 0x39, 0xfc, 0x0e, - 0x3a, 0xcf, 0xa1, 0xdc, 0x1b, 0x0e, 0x92, 0xa8, 0x19, 0xc1, 0x82, 0xe0, 0xc9, 0xd6, 0x19, 0xdd, - 0x68, 0xe7, 0x7a, 0xb4, 0xae, 0xbc, 0xd2, 0xeb, 0xe2, 0xeb, 0x43, 0x67, 0x37, 0x75, 0x05, 0x8b, - 0x44, 0x57, 0x5e, 0xe9, 0x75, 0x71, 0xb9, 0x3f, 0xd8, 0x4d, 0x5d, 0x41, 0x27, 0x74, 0xe5, 0x85, - 0x5d, 0x54, 0xec, 0x86, 0x2f, 0x35, 0x9b, 0xca, 0x06, 0x54, 0xf7, 0xf4, 0x6e, 0xaa, 0x2b, 0xc1, - 0x86, 0x73, 0x27, 0x8e, 0x74, 0x96, 0x5e, 0x6a, 0x13, 0xab, 0x26, 0x2d, 0x40, 0x0f, 0xe5, 0x59, - 0x3a, 0x46, 0x40, 0x67, 0xe9, 0x18, 0x90, 0x0e, 0x28, 0xd1, 0x08, 0x5b, 0xd9, 0x94, 0x07, 0x94, - 0x88, 0xa3, 0x03, 0x4a, 0x32, 0xd8, 0x5e, 0x42, 0xa7, 0x96, 0xd6, 0x3d, 0xdd, 0xdf, 0x41, 0xba, - 0xbc, 0x2b, 0xdf, 0x8d, 0x3c, 0x32, 0xc5, 0x49, 0xe0, 0x91, 0x29, 0x0e, 0xa6, 0x63, 0x84, 0x82, - 0x6b, 0x9b, 0x56, 0x63, 0x56, 0x37, 0x9b, 0x1d, 0x87, 0x28, 0xff, 0x9f, 0x3c, 0x46, 0x22, 0x68, - 0x3a, 0x46, 0x22, 0x20, 0xba, 0x40, 0x53, 0x50, 0xc9, 0x75, 0xcd, 0x55, 0x8b, 0x9f, 0x2b, 0x3b, - 0x4d, 0x4f, 0xf9, 0xff, 0xe5, 0x05, 0x3a, 0x89, 0x86, 0x2e, 0xd0, 0x49, 0x70, 0xb8, 0x75, 0x4a, - 0x48, 0x30, 0xa8, 0xfc, 0x95, 0xc8, 0xad, 0x53, 0x02, 0x0d, 0xdc, 0x3a, 0x25, 0x25, 0x27, 0x9c, - 0x45, 0x05, 0xb6, 0x27, 0x9b, 0x37, 0x83, 0xb7, 0xea, 0xbf, 0x2a, 0xaf, 0x8f, 0x51, 0x3c, 0x5d, - 0x1f, 0xa3, 0x30, 0x99, 0x0f, 0xef, 0x82, 0xbf, 0x96, 0xc6, 0x27, 0x90, 0x7f, 0xac, 0x0c, 0xbe, - 0x25, 0xf2, 0xe1, 0x23, 0xe5, 0x3b, 0x33, 0x69, 0x8c, 0x82, 0xe1, 0x11, 0x2b, 0x24, 0x33, 0xd2, - 0xc8, 0x03, 0x93, 0x6c, 0x28, 0x9f, 0x4f, 0x65, 0xc4, 0x08, 0x64, 0x46, 0x0c, 0x86, 0xdf, 0x42, - 0x67, 0x43, 0xd8, 0x02, 0x69, 0xad, 0x04, 0x33, 0xd3, 0x77, 0x65, 0xe4, 0x6d, 0x70, 0x32, 0x19, - 0xdd, 0x06, 0x27, 0x63, 0x92, 0x58, 0x73, 0xd1, 0xfd, 0xf5, 0x1d, 0x58, 0x07, 0x12, 0x4c, 0x61, - 0x90, 0xc4, 0x9a, 0x4b, 0xf3, 0xbb, 0x77, 0x60, 0x1d, 0xc8, 0x34, 0x85, 0x01, 0xfe, 0x81, 0x0c, - 0xba, 0x94, 0x8c, 0x2a, 0x35, 0x9b, 0xb3, 0xb6, 0x13, 0xe2, 0x94, 0xef, 0xc9, 0xc8, 0x17, 0x0d, - 0xbb, 0x2b, 0x36, 0xd7, 0xa3, 0xed, 0xb2, 0x02, 0xfc, 0x49, 0x34, 0x52, 0xea, 0x18, 0xa6, 0x07, - 0x0f, 0x6f, 0x74, 0xe3, 0xfc, 0xbd, 0x99, 0xc8, 0x11, 0x47, 0xc4, 0xc2, 0x11, 0x47, 0x04, 0xe0, - 0xd7, 0xd1, 0x78, 0x8d, 0x34, 0x3a, 0x8e, 0xe9, 0x6d, 0x6a, 0x90, 0x3c, 0x92, 0xf2, 0xf8, 0xbe, - 0x8c, 0x3c, 0x89, 0xc5, 0x28, 0xe8, 0x24, 0x16, 0x03, 0x62, 0x82, 0x26, 0x66, 0x1e, 0x7a, 0xc4, - 0xb1, 0xf4, 0x26, 0x54, 0x52, 0xf3, 0x6c, 0x47, 0x5f, 0x25, 0x33, 0x96, 0xbe, 0xd2, 0x24, 0xca, - 0x17, 0x33, 0xf2, 0xbe, 0x2a, 0x9d, 0x94, 0xee, 0xab, 0xd2, 0xb1, 0x78, 0x0d, 0x3d, 0x9e, 0x84, - 0x2d, 0x9b, 0x2e, 0xd4, 0xf3, 0xa5, 0x8c, 0xbc, 0xb1, 0xea, 0x42, 0x4b, 0x37, 0x56, 0x5d, 0xd0, - 0x10, 0x60, 0x3b, 0xc9, 0x2f, 0x44, 0xf9, 0xd1, 0x8c, 0x7c, 0xc9, 0x98, 0x48, 0x35, 0xd7, 0xa3, - 0xa5, 0xb8, 0x95, 0xdc, 0x4d, 0xf1, 0xa9, 0x50, 0x7e, 0xac, 0x3b, 0xdf, 0x40, 0xe9, 0x53, 0x5c, - 0x32, 0xee, 0xa6, 0xf8, 0x23, 0x28, 0x3f, 0xde, 0x9d, 0x6f, 0x68, 0x17, 0x91, 0xec, 0xce, 0x50, - 0x4f, 0xb7, 0xe5, 0x57, 0x7e, 0x22, 0x23, 0x9f, 0xd3, 0xd3, 0x08, 0xe9, 0x39, 0x3d, 0xd5, 0x21, - 0xe0, 0xf5, 0x04, 0x8b, 0x7a, 0xe5, 0x27, 0x23, 0x5a, 0x18, 0xa3, 0xa0, 0x5a, 0x18, 0x37, 0xc4, - 0x7f, 0x3d, 0xc1, 0x70, 0x5c, 0xf9, 0xfb, 0xe9, 0xbc, 0x02, 0xa1, 0x26, 0xd8, 0x9b, 0xbf, 0x9e, - 0x60, 0x1f, 0xad, 0xfc, 0x83, 0x74, 0x5e, 0xe1, 0xf3, 0x6a, 0xdc, 0xac, 0x9a, 0x4e, 0x48, 0x1d, - 0xcf, 0x66, 0x9c, 0x25, 0x6d, 0xfa, 0xd9, 0xe8, 0x84, 0x94, 0x48, 0x06, 0x13, 0x52, 0x22, 0x26, - 0x89, 0x35, 0xff, 0xee, 0x9f, 0xdb, 0x81, 0xb5, 0x30, 0x8d, 0x26, 0x62, 0x92, 0x58, 0x73, 0x31, - 0x7c, 0x65, 0x07, 0xd6, 0xc2, 0x34, 0x9a, 0x88, 0xc1, 0x9f, 0x41, 0xe7, 0x42, 0xcc, 0x5d, 0xe2, - 0xb8, 0x61, 0xd7, 0xff, 0x7c, 0x46, 0xbe, 0x4a, 0x48, 0xa1, 0x9b, 0xeb, 0xd1, 0xd2, 0x58, 0x24, - 0x72, 0xe7, 0x42, 0xf9, 0x85, 0x9d, 0xb8, 0x87, 0xb7, 0x20, 0x29, 0xa8, 0x44, 0xee, 0x5c, 0x2e, - 0xbf, 0xb8, 0x13, 0xf7, 0xf0, 0x1a, 0x24, 0x05, 0x35, 0xd5, 0x8f, 0x7a, 0x61, 0x6f, 0xa7, 0xfe, - 0x68, 0x06, 0x0d, 0xd7, 0x3c, 0x87, 0xe8, 0x2d, 0xee, 0x97, 0x3c, 0x81, 0x06, 0x98, 0x91, 0x84, - 0x6f, 0xa7, 0xac, 0x05, 0xbf, 0xf1, 0x25, 0x34, 0x3a, 0xaf, 0xbb, 0x1e, 0x94, 0xac, 0x58, 0x06, - 0x79, 0x08, 0x06, 0xc2, 0x39, 0x2d, 0x02, 0xc5, 0xf3, 0x8c, 0x8e, 0x95, 0x83, 0x80, 0x10, 0xb9, - 0x1d, 0xdd, 0x71, 0x07, 0xde, 0xdb, 0x2a, 0xf6, 0x80, 0xf7, 0x6d, 0xa4, 0xac, 0xfa, 0xf5, 0x0c, - 0x8a, 0x99, 0x6f, 0x3c, 0xba, 0xff, 0xc0, 0x12, 0x1a, 0x8b, 0x04, 0x21, 0xe1, 0x56, 0xce, 0xbb, - 0x8c, 0x51, 0x12, 0x2d, 0x8d, 0x9f, 0x0e, 0xac, 0x6b, 0xef, 0x68, 0xf3, 0xdc, 0xd5, 0xba, 0x7f, - 0x7b, 0xab, 0x98, 0xeb, 0x38, 0x4d, 0x4d, 0x40, 0x71, 0x57, 0xc0, 0x7f, 0x54, 0x08, 0x23, 0x2c, - 0xe0, 0x4b, 0xdc, 0x99, 0x21, 0x13, 0x3a, 0x68, 0x47, 0x12, 0x62, 0x30, 0xe7, 0x85, 0x4f, 0xa2, - 0xe1, 0x4a, 0xab, 0x4d, 0x1c, 0xd7, 0xb6, 0x74, 0xcf, 0xf6, 0x13, 0xef, 0x81, 0xf3, 0xae, 0x29, - 0xc0, 0x45, 0x87, 0x52, 0x91, 0x1e, 0x5f, 0xf1, 0xf3, 0x4c, 0xe7, 0x20, 0xb6, 0xc5, 0xa9, 0x84, - 0x3c, 0xd3, 0x7e, 0xb6, 0xe8, 0x2b, 0xa8, 0xf7, 0x8e, 0xab, 0x83, 0x1d, 0x76, 0x40, 0xda, 0xa1, - 0x00, 0x91, 0x14, 0x28, 0xf0, 0x55, 0xd4, 0x07, 0xe7, 0x56, 0x57, 0xe9, 0x05, 0x5a, 0x70, 0x1b, - 0x6f, 0x02, 0x44, 0x74, 0xd2, 0x65, 0x34, 0xf8, 0x36, 0x2a, 0x84, 0x97, 0x72, 0x90, 0x2a, 0xd2, - 0x8f, 0xb1, 0x09, 0xc9, 0x29, 0xd6, 0x03, 0x1c, 0xcb, 0x31, 0x29, 0xb2, 0x88, 0x15, 0xc4, 0x73, - 0x68, 0x2c, 0x84, 0x51, 0x11, 0xf9, 0xb1, 0x7d, 0x21, 0x39, 0x8b, 0xc0, 0x8b, 0x8a, 0x53, 0x64, - 0x15, 0x2d, 0x86, 0x2b, 0xa8, 0xdf, 0xf7, 0x19, 0x1f, 0xd8, 0x51, 0x49, 0x4f, 0x71, 0x9f, 0xf1, - 0x7e, 0xd1, 0x5b, 0xdc, 0x2f, 0x8f, 0x67, 0xd1, 0xa8, 0x66, 0x77, 0x3c, 0xb2, 0x6c, 0xf3, 0x3b, - 0x47, 0x1e, 0xfc, 0x11, 0xda, 0xe4, 0x50, 0x4c, 0xdd, 0xb3, 0xfd, 0xdc, 0x1e, 0x62, 0x8e, 0x09, - 0xb9, 0x14, 0x5e, 0x44, 0xe3, 0xb1, 0xeb, 0x4b, 0x31, 0xe3, 0x86, 0xf0, 0x79, 0x71, 0x66, 0xf1, - 0xa2, 0xf8, 0x7b, 0x33, 0xa8, 0x6f, 0xd9, 0xd1, 0x4d, 0xcf, 0xe5, 0x26, 0xdc, 0x67, 0x26, 0x37, - 0x1c, 0xbd, 0x4d, 0xf5, 0x63, 0x12, 0x82, 0x97, 0xdc, 0xd5, 0x9b, 0x1d, 0xe2, 0x4e, 0xdd, 0xa3, - 0x5f, 0xf7, 0xef, 0xb7, 0x8a, 0x9f, 0xd8, 0x43, 0xfe, 0xef, 0x6b, 0x01, 0x27, 0x56, 0x03, 0x55, - 0x01, 0x0f, 0xfe, 0x12, 0x55, 0x80, 0xe1, 0xf0, 0x22, 0x42, 0xfc, 0x53, 0x4b, 0xed, 0x36, 0xb7, - 0x07, 0x17, 0x8c, 0x5d, 0x7d, 0x0c, 0x53, 0xec, 0x40, 0x60, 0x7a, 0x5b, 0x4c, 0x38, 0x2a, 0x70, - 0xa0, 0x5a, 0xb0, 0xcc, 0x5b, 0xe4, 0x8b, 0x69, 0x24, 0x94, 0xb8, 0xdf, 0xd8, 0x04, 0x21, 0x45, - 0x8b, 0xe1, 0x15, 0x34, 0xc6, 0xf9, 0x06, 0xd1, 0x18, 0x47, 0xe5, 0x59, 0x21, 0x82, 0x66, 0x4a, - 0x1b, 0xb4, 0xd1, 0xe0, 0x60, 0xb1, 0x8e, 0x48, 0x09, 0x3c, 0x15, 0x06, 0x8b, 0x87, 0xec, 0xa6, - 0xca, 0x18, 0x68, 0xec, 0xf9, 0xed, 0xad, 0xa2, 0xe2, 0x97, 0x67, 0x49, 0x51, 0x93, 0x52, 0x9f, - 0x40, 0x11, 0x91, 0x07, 0xd3, 0xfa, 0x42, 0x02, 0x8f, 0xa8, 0xce, 0xcb, 0x45, 0xf0, 0x34, 0x1a, - 0x09, 0xcc, 0xd1, 0xee, 0xdc, 0xa9, 0x94, 0xc1, 0xe0, 0x9c, 0xa7, 0xf1, 0x8c, 0x04, 0x7a, 0x14, - 0x99, 0x48, 0x65, 0x04, 0xcf, 0x14, 0x66, 0x81, 0x1e, 0xf1, 0x4c, 0x69, 0x27, 0x78, 0xa6, 0x54, - 0xf1, 0x2b, 0x68, 0xa8, 0x74, 0xaf, 0xc6, 0x3d, 0x6e, 0x5c, 0xe5, 0x54, 0x18, 0x61, 0x17, 0xb2, - 0xdf, 0x70, 0xef, 0x1c, 0xb1, 0xe9, 0x22, 0x3d, 0x9e, 0x41, 0xa3, 0xd2, 0x8b, 0x96, 0xab, 0x9c, - 0x06, 0x0e, 0x2c, 0x01, 0x29, 0x60, 0xea, 0x3c, 0x07, 0xae, 0x94, 0xe2, 0x47, 0x2e, 0x44, 0xb5, - 0x86, 0x6e, 0xbf, 0x9b, 0x4d, 0x7b, 0x43, 0x23, 0xe0, 0xdc, 0x03, 0xe6, 0xeb, 0x03, 0x4c, 0x6b, - 0x0c, 0x8e, 0xaa, 0x3b, 0x0c, 0x27, 0x25, 0x60, 0x92, 0x8b, 0xe1, 0x77, 0x10, 0x86, 0xf8, 0xa6, - 0xc4, 0xf0, 0x2f, 0x38, 0x2a, 0x65, 0x57, 0x39, 0x0b, 0x41, 0x9c, 0x70, 0xd4, 0xbb, 0xac, 0x52, - 0x9e, 0xba, 0xc4, 0xa7, 0x8f, 0x8b, 0x3a, 0x2b, 0x55, 0x0f, 0xf2, 0xcf, 0x9a, 0x86, 0xd8, 0xe2, - 0x04, 0xae, 0x78, 0x03, 0x9d, 0xab, 0x3a, 0xe4, 0x81, 0x69, 0x77, 0x5c, 0x7f, 0xf9, 0xf0, 0xe7, - 0xad, 0x73, 0x3b, 0xce, 0x5b, 0x4f, 0xf2, 0x8a, 0xcf, 0xb4, 0x1d, 0xf2, 0xa0, 0xee, 0x87, 0xee, - 0x91, 0x62, 0x5e, 0xa4, 0x71, 0xa7, 0xe2, 0x02, 0xc7, 0x26, 0x0e, 0x37, 0x89, 0xab, 0x28, 0xe1, - 0x54, 0xcb, 0xfc, 0xb4, 0xcc, 0x00, 0x27, 0x8a, 0x2b, 0x52, 0x0c, 0x6b, 0x08, 0xdf, 0x9a, 0xf6, - 0x2f, 0xbb, 0x4a, 0x8d, 0x86, 0xdd, 0xb1, 0x3c, 0x57, 0x79, 0x0c, 0x98, 0xa9, 0x54, 0x2c, 0xab, - 0x8d, 0x20, 0x8c, 0x57, 0x5d, 0xe7, 0x78, 0x51, 0x2c, 0xf1, 0xd2, 0x78, 0x1e, 0x15, 0xaa, 0x8e, - 0xf9, 0x40, 0xf7, 0xc8, 0x6d, 0xb2, 0x59, 0xb5, 0x9b, 0x66, 0x63, 0x13, 0xac, 0xe8, 0xf9, 0x54, - 0xd9, 0x66, 0xb8, 0xfa, 0x3a, 0xd9, 0xac, 0xb7, 0x01, 0x2b, 0x2e, 0x2b, 0xd1, 0x92, 0x62, 0x58, - 0x9d, 0xc7, 0x77, 0x17, 0x56, 0x87, 0xa0, 0x02, 0xbf, 0x2a, 0x7b, 0xe8, 0x11, 0x8b, 0x2e, 0xf5, - 0x2e, 0xb7, 0x98, 0x57, 0x22, 0x57, 0x6b, 0x01, 0x9e, 0x27, 0x63, 0x62, 0xa3, 0x8c, 0x04, 0x60, - 0xb1, 0x61, 0xd1, 0x22, 0xea, 0x97, 0x72, 0xe2, 0xd4, 0x89, 0xcf, 0xa3, 0xbc, 0x10, 0xd5, 0x15, - 0xa2, 0x71, 0x40, 0x04, 0xac, 0x3c, 0x0f, 0xf5, 0x33, 0xc8, 0xb7, 0x1d, 0x81, 0xdb, 0x18, 0x84, - 0xbc, 0xf7, 0x43, 0x6d, 0x99, 0x86, 0x16, 0x12, 0x40, 0xb8, 0xf1, 0x30, 0x67, 0x65, 0x4e, 0x08, - 0x37, 0x1e, 0xe6, 0xac, 0x94, 0x32, 0x56, 0x5e, 0x47, 0x43, 0x7c, 0xda, 0x14, 0xa2, 0xd1, 0x40, - 0xb8, 0x2c, 0x3f, 0x6d, 0x15, 0x8b, 0xc6, 0x25, 0x10, 0xe1, 0x97, 0x20, 0x71, 0x9b, 0xef, 0x92, - 0xd7, 0x1b, 0x6e, 0x5f, 0xc4, 0x81, 0x1f, 0xc9, 0xdc, 0xe6, 0x7b, 0xe6, 0x4d, 0xa1, 0x11, 0x51, - 0x93, 0xfc, 0x04, 0x0b, 0x30, 0xe7, 0x49, 0xea, 0xb7, 0x29, 0x65, 0x1d, 0x16, 0x8b, 0xe0, 0x25, - 0x34, 0x1e, 0x53, 0x1e, 0x1e, 0xbb, 0x06, 0xd2, 0x6d, 0x24, 0x68, 0x9e, 0xb8, 0xa6, 0xc6, 0xca, - 0xaa, 0xdf, 0x95, 0x8d, 0xad, 0x18, 0x54, 0x30, 0x9c, 0x4a, 0xe8, 0x1c, 0x10, 0x8c, 0xcf, 0x9a, - 0x09, 0x46, 0x20, 0xc2, 0x97, 0xd1, 0x40, 0x24, 0x77, 0x1b, 0x38, 0x69, 0x06, 0x89, 0xdb, 0x02, - 0x2c, 0xbe, 0x8e, 0x06, 0xe8, 0xfc, 0x6d, 0x45, 0x62, 0x3e, 0x75, 0x38, 0x4c, 0x9c, 0x70, 0x7d, - 0x3a, 0x5a, 0x46, 0x8a, 0x2e, 0xec, 0xa7, 0xd8, 0x8a, 0xaf, 0x56, 0x61, 0xec, 0xf4, 0x60, 0xaf, - 0xd8, 0xbb, 0xd3, 0x5e, 0x51, 0xfd, 0x8d, 0x4c, 0x5c, 0xfb, 0xf1, 0xcd, 0x78, 0xe0, 0x17, 0x96, - 0x5d, 0xcb, 0x07, 0x8a, 0xb5, 0x06, 0x21, 0x60, 0xa4, 0x10, 0x2e, 0xd9, 0x47, 0x0e, 0xe1, 0x92, - 0xdb, 0x63, 0x08, 0x17, 0xf5, 0x7f, 0xe7, 0xbb, 0x1a, 0x5c, 0x1c, 0x8a, 0xab, 0xf2, 0x8b, 0xf4, - 0xbc, 0x43, 0x6b, 0x2f, 0xb9, 0xb1, 0x5d, 0x3b, 0x7b, 0x4f, 0xae, 0xeb, 0x6c, 0xd4, 0xb8, 0x9a, - 0x4c, 0x29, 0xe6, 0x3a, 0x87, 0xd0, 0x40, 0xf9, 0x84, 0x5c, 0xe7, 0xd1, 0x04, 0x69, 0x62, 0x01, - 0xfc, 0x31, 0x34, 0x18, 0x66, 0x6d, 0xef, 0x15, 0x02, 0x4d, 0x25, 0x24, 0x6b, 0x0f, 0x29, 0xf1, - 0x67, 0x51, 0x9f, 0x94, 0xa1, 0xef, 0xda, 0x2e, 0x2c, 0x54, 0x26, 0xc5, 0xf0, 0x85, 0xec, 0xec, - 0x10, 0xcd, 0xce, 0xc7, 0x99, 0xe2, 0x65, 0x74, 0xaa, 0xea, 0x10, 0x03, 0x6c, 0xa1, 0x66, 0x1e, - 0xb6, 0x1d, 0x1e, 0x5c, 0x92, 0x0d, 0x60, 0x58, 0x3a, 0xda, 0x3e, 0x9a, 0x2e, 0x6a, 0x1c, 0x2f, - 0x30, 0x4a, 0x2a, 0x4e, 0xf7, 0x13, 0xac, 0x25, 0xb7, 0xc9, 0xe6, 0x86, 0xed, 0x18, 0x2c, 0xfe, - 0x22, 0xdf, 0x4f, 0x70, 0x41, 0xaf, 0x73, 0x94, 0xb8, 0x9f, 0x90, 0x0b, 0x4d, 0xbc, 0x88, 0x86, - 0x1e, 0x35, 0x04, 0xe0, 0x2f, 0x64, 0x53, 0x4c, 0x17, 0x8f, 0x6f, 0xea, 0x86, 0x20, 0x8d, 0x4e, - 0x6f, 0x4a, 0x1a, 0x9d, 0x6f, 0x65, 0x53, 0xec, 0x32, 0x8f, 0x75, 0xba, 0x8b, 0x40, 0x18, 0x72, - 0xba, 0x8b, 0x30, 0xd3, 0x88, 0x69, 0x68, 0x22, 0x51, 0x24, 0x31, 0x4e, 0xdf, 0x8e, 0x89, 0x71, - 0x7e, 0x2a, 0xd7, 0xcd, 0x6e, 0xf5, 0x44, 0xf6, 0x7b, 0x91, 0xfd, 0x75, 0x34, 0x14, 0x48, 0x96, - 0xa7, 0x39, 0x1e, 0x09, 0x02, 0x8e, 0x32, 0x30, 0x94, 0x11, 0x88, 0xf0, 0x15, 0xd6, 0xd6, 0x9a, - 0xf9, 0x2e, 0x0b, 0xba, 0x37, 0xc2, 0xc3, 0xa9, 0xe9, 0x9e, 0x5e, 0x77, 0xcd, 0x77, 0x89, 0x16, - 0xa0, 0xd5, 0x7f, 0x96, 0x4d, 0x34, 0xfe, 0x3d, 0xe9, 0xa3, 0x3d, 0xf4, 0x51, 0x82, 0x10, 0x99, - 0xd9, 0xf2, 0x89, 0x10, 0xf7, 0x20, 0xc4, 0x3f, 0xc9, 0x26, 0x1a, 0x79, 0x9f, 0x08, 0x71, 0x2f, - 0xb3, 0xc5, 0x55, 0x34, 0xa8, 0xd9, 0x1b, 0xee, 0x34, 0x9c, 0x59, 0xd8, 0x5c, 0x01, 0x13, 0xb5, - 0x63, 0x6f, 0xb8, 0x75, 0x38, 0x8d, 0x68, 0x21, 0x81, 0xfa, 0x67, 0xd9, 0x2e, 0x66, 0xf0, 0x27, - 0x82, 0x7f, 0x3f, 0x97, 0xc8, 0x5f, 0xce, 0x4a, 0x66, 0xf6, 0xc7, 0x3a, 0x6f, 0x5c, 0xad, 0xb1, - 0x46, 0x5a, 0x7a, 0x34, 0x6f, 0x9c, 0x0b, 0x50, 0x9e, 0x76, 0x26, 0x24, 0x51, 0xbf, 0x9a, 0x8d, - 0xf8, 0x19, 0x9c, 0xc8, 0x6e, 0xd7, 0xb2, 0x0b, 0xb4, 0x8e, 0xbb, 0x4e, 0x9c, 0x48, 0x6e, 0xb7, - 0x92, 0xfb, 0xfe, 0x6c, 0xc4, 0xcb, 0xe4, 0xf8, 0xa6, 0x90, 0xfa, 0x6a, 0x36, 0xee, 0x31, 0x73, - 0x7c, 0x35, 0xe9, 0x2a, 0x1a, 0xe4, 0x72, 0x08, 0x96, 0x0a, 0x36, 0xef, 0x33, 0x20, 0x5c, 0xa0, - 0x06, 0x04, 0xea, 0xf7, 0x64, 0x91, 0xec, 0xfd, 0x73, 0x4c, 0x75, 0xe8, 0x97, 0xb3, 0xb2, 0xdf, - 0xd3, 0xf1, 0xd5, 0x9f, 0x49, 0x84, 0x6a, 0x9d, 0x95, 0x06, 0x0f, 0x9b, 0xd5, 0x2b, 0xdc, 0xc0, - 0x07, 0x50, 0x4d, 0xa0, 0x50, 0xff, 0x4f, 0x36, 0xd1, 0x19, 0xeb, 0xf8, 0x0a, 0xf0, 0x06, 0xdc, - 0x8a, 0x37, 0xac, 0x70, 0x22, 0x87, 0x4b, 0x48, 0x3a, 0xfe, 0x62, 0xd1, 0xee, 0x7d, 0x42, 0xfc, - 0xf1, 0x84, 0xed, 0x1a, 0xc4, 0x12, 0x4c, 0x4c, 0xa1, 0x2d, 0x6e, 0xdc, 0xfe, 0x65, 0x76, 0x27, - 0xdf, 0xb5, 0xe3, 0xbc, 0xaa, 0xf6, 0x57, 0xf5, 0x4d, 0x88, 0xb1, 0x42, 0x7b, 0x62, 0x98, 0xc5, - 0x62, 0x6f, 0x33, 0x90, 0xf8, 0x22, 0xc6, 0xa9, 0xd4, 0x3f, 0xee, 0x4d, 0x76, 0x9c, 0x3a, 0xbe, - 0x22, 0x3c, 0x8f, 0xf2, 0x55, 0xdd, 0x5b, 0xe3, 0x9a, 0x0c, 0xaf, 0x75, 0x6d, 0xdd, 0x5b, 0xd3, - 0x00, 0x8a, 0xaf, 0xa0, 0x01, 0x4d, 0xdf, 0x10, 0x53, 0x87, 0xc3, 0xc5, 0x8e, 0xa3, 0x6f, 0xf0, - 0xfc, 0xf1, 0x01, 0x1a, 0xab, 0x41, 0x9e, 0x06, 0x76, 0xf3, 0x0d, 0x41, 0xce, 0x59, 0x9e, 0x86, - 0x20, 0x3b, 0xc3, 0x79, 0x94, 0x9f, 0xb2, 0x8d, 0x4d, 0x30, 0x66, 0x19, 0x66, 0x95, 0xad, 0xd8, - 0xc6, 0xa6, 0x06, 0x50, 0xfc, 0x03, 0x19, 0xd4, 0x3f, 0x47, 0x74, 0x83, 0x8e, 0x90, 0xc1, 0x6e, - 0xb6, 0x20, 0x6f, 0x1e, 0x8c, 0x2d, 0xc8, 0xf8, 0x1a, 0xab, 0x4c, 0x54, 0x14, 0x5e, 0x3f, 0xbe, - 0x85, 0x06, 0xa6, 0x75, 0x8f, 0xac, 0xda, 0xce, 0x26, 0x58, 0xb7, 0x8c, 0x86, 0xd6, 0xa3, 0x92, - 0xfe, 0xf8, 0x44, 0xec, 0x65, 0xac, 0xc1, 0x7f, 0x69, 0x41, 0x61, 0x2a, 0x16, 0x9e, 0xbf, 0x6d, - 0x28, 0x14, 0x0b, 0x4b, 0xd4, 0x16, 0xa4, 0x69, 0x0b, 0xae, 0x95, 0x87, 0x93, 0xaf, 0x95, 0x61, - 0xf7, 0x08, 0x16, 0x70, 0x90, 0x1d, 0x61, 0x04, 0x16, 0x7d, 0xb6, 0x7b, 0x04, 0x28, 0x24, 0x47, - 0xd0, 0x04, 0x12, 0xf5, 0x1b, 0xbd, 0x28, 0xd1, 0xcd, 0xe2, 0x44, 0xc9, 0x4f, 0x94, 0x3c, 0x54, - 0xf2, 0x72, 0x4c, 0xc9, 0x27, 0xe2, 0x8e, 0x3b, 0x1f, 0x50, 0x0d, 0xff, 0xa1, 0x7c, 0xcc, 0xed, - 0xef, 0x78, 0x9f, 0x2e, 0x43, 0xe9, 0xf5, 0xee, 0x28, 0xbd, 0x60, 0x40, 0xf4, 0xed, 0x38, 0x20, - 0xfa, 0x77, 0x3b, 0x20, 0x06, 0x52, 0x07, 0x44, 0xa8, 0x20, 0x83, 0xa9, 0x0a, 0x52, 0xe1, 0x83, - 0x06, 0x75, 0xcf, 0xbc, 0x73, 0x7e, 0x7b, 0xab, 0x38, 0x4a, 0x47, 0x53, 0x62, 0xce, 0x1d, 0x60, - 0xa1, 0x7e, 0x3d, 0xdf, 0xc5, 0x57, 0xf7, 0x50, 0x74, 0xe4, 0x06, 0xca, 0x95, 0xda, 0x6d, 0xae, - 0x1f, 0xa7, 0x04, 0x37, 0xe1, 0x94, 0x52, 0x94, 0x1a, 0xbf, 0x84, 0x72, 0xa5, 0x7b, 0xb5, 0x68, - 0xc4, 0xe1, 0xd2, 0xbd, 0x1a, 0xff, 0x92, 0xd4, 0xb2, 0xf7, 0x6a, 0xf8, 0xe5, 0x30, 0xf4, 0xcf, - 0x5a, 0xc7, 0x5a, 0xe7, 0x07, 0x45, 0x6e, 0x04, 0xeb, 0x5b, 0xda, 0x34, 0x28, 0x8a, 0x1e, 0x17, - 0x23, 0xb4, 0x11, 0x6d, 0xea, 0xdb, 0xbd, 0x36, 0xf5, 0xef, 0xa8, 0x4d, 0x03, 0xbb, 0xd5, 0xa6, - 0xc1, 0x5d, 0x68, 0x13, 0xda, 0x51, 0x9b, 0x86, 0xf6, 0xaf, 0x4d, 0x6d, 0x34, 0x11, 0x8f, 0xaf, - 0x10, 0x68, 0x84, 0x86, 0x70, 0x1c, 0xcb, 0x0d, 0x4b, 0xe0, 0xe9, 0xbf, 0xc3, 0xb0, 0x75, 0x96, - 0x67, 0x31, 0x9a, 0xa5, 0x50, 0x4b, 0x28, 0xad, 0xfe, 0x42, 0x36, 0x3d, 0x2c, 0xc4, 0xd1, 0x9c, - 0xe2, 0xbe, 0x23, 0x51, 0x4a, 0x79, 0xd9, 0x21, 0x2a, 0x5d, 0xca, 0x11, 0xb6, 0x49, 0x32, 0xfb, - 0x5a, 0x26, 0x2d, 0x56, 0xc5, 0xbe, 0x24, 0xf6, 0xe1, 0xb8, 0xb1, 0x1a, 0x58, 0xcf, 0xbb, 0xb2, - 0x95, 0x5a, 0x34, 0x6d, 0x5f, 0xee, 0x11, 0xd3, 0xf6, 0xfd, 0x46, 0x06, 0x9d, 0xba, 0xdd, 0x59, - 0x21, 0xdc, 0x38, 0x2d, 0x68, 0xc6, 0x3b, 0x08, 0x51, 0x30, 0x37, 0x62, 0xc9, 0x80, 0x11, 0xcb, - 0x47, 0xc4, 0x38, 0x13, 0x91, 0x02, 0x93, 0x21, 0x35, 0x33, 0x60, 0xb9, 0xe0, 0x9b, 0x58, 0xae, - 0x77, 0x56, 0x48, 0x3d, 0x66, 0xc9, 0x22, 0x70, 0x9f, 0x78, 0x85, 0x19, 0xaf, 0x3f, 0xaa, 0xd1, - 0xc8, 0xcf, 0x65, 0x53, 0x43, 0x7b, 0x1c, 0xd9, 0xcc, 0x0a, 0x9f, 0x4e, 0xec, 0x95, 0x68, 0x86, - 0x85, 0x04, 0x92, 0x08, 0xc7, 0x24, 0x2e, 0xc9, 0x02, 0x3b, 0xe2, 0xf9, 0x3e, 0xde, 0x57, 0x81, - 0xfd, 0x5e, 0x26, 0x35, 0x04, 0xcb, 0x51, 0x15, 0x98, 0xfa, 0x5b, 0x59, 0x3f, 0xf2, 0xcb, 0xbe, - 0x3e, 0xe1, 0x2a, 0x1a, 0xe4, 0xe1, 0xed, 0x65, 0xdb, 0x5a, 0x7e, 0x95, 0x07, 0x57, 0xc3, 0x01, - 0x01, 0x5d, 0xe6, 0xfd, 0xc8, 0x14, 0x41, 0xa2, 0x47, 0x58, 0xe6, 0x4d, 0x0e, 0xa5, 0xf4, 0x02, - 0x09, 0x5d, 0xc8, 0x67, 0x1e, 0x9a, 0x1e, 0xec, 0x0a, 0x68, 0x5f, 0xe6, 0xd8, 0x42, 0x4e, 0x1e, - 0x9a, 0x1e, 0xdb, 0x13, 0x04, 0x68, 0xba, 0x48, 0xd7, 0xc2, 0x6c, 0x66, 0x7c, 0x91, 0x76, 0x79, - 0x52, 0x37, 0xee, 0xcc, 0x75, 0x15, 0x0d, 0x72, 0x83, 0x55, 0x6e, 0x66, 0xc2, 0x5b, 0xcb, 0x4d, - 0x5c, 0xa1, 0xb5, 0x01, 0x01, 0xe5, 0xa8, 0x91, 0xd5, 0xd0, 0xb0, 0x0e, 0x38, 0x3a, 0x00, 0xd1, - 0x38, 0x46, 0xdd, 0xce, 0xc6, 0x03, 0xd0, 0x1c, 0xdf, 0x43, 0xc1, 0x15, 0xd9, 0x58, 0x0d, 0x2c, - 0x34, 0x61, 0xc3, 0x25, 0xda, 0xca, 0xb2, 0x7d, 0xd7, 0x75, 0x34, 0x70, 0x9b, 0x6c, 0x32, 0xbb, - 0xca, 0xbe, 0xd0, 0x14, 0x77, 0x9d, 0xc3, 0xc4, 0x1b, 0x4d, 0x9f, 0x4e, 0xfd, 0xf5, 0x6c, 0x3c, - 0xb4, 0xce, 0xf1, 0x15, 0xf6, 0x47, 0x51, 0x3f, 0x88, 0xb2, 0xe2, 0x5f, 0xa9, 0x83, 0x00, 0x41, - 0xdc, 0xb2, 0x85, 0xaf, 0x4f, 0xa6, 0xfe, 0x58, 0x5f, 0x34, 0xde, 0xd2, 0xf1, 0x95, 0xde, 0x27, - 0xd0, 0xd0, 0xb4, 0x6d, 0xb9, 0xa6, 0xeb, 0x11, 0xab, 0xe1, 0x2b, 0xec, 0x63, 0x74, 0xc3, 0xd2, - 0x08, 0xc1, 0xa2, 0xe7, 0x8d, 0x40, 0xfd, 0x28, 0xca, 0x8b, 0x9f, 0x47, 0x83, 0x20, 0x72, 0xb0, - 0x43, 0x16, 0xd2, 0xc4, 0xae, 0x50, 0x60, 0xd4, 0x08, 0x39, 0x24, 0xc5, 0x77, 0xd0, 0xc0, 0xf4, - 0x9a, 0xd9, 0x34, 0x1c, 0x62, 0xf1, 0x7c, 0xe8, 0x4f, 0x26, 0x47, 0xc7, 0x9a, 0x84, 0x7f, 0x81, - 0x96, 0x35, 0xa7, 0xc1, 0x8b, 0x49, 0xbe, 0x47, 0x1c, 0x36, 0xf1, 0xb7, 0xb2, 0x08, 0x85, 0x05, - 0xf0, 0x13, 0x28, 0x1b, 0x24, 0xe2, 0x01, 0x33, 0x10, 0x49, 0x83, 0xb2, 0x30, 0x15, 0xf3, 0xb1, - 0x9d, 0xdd, 0x71, 0x6c, 0xdf, 0x41, 0x7d, 0xec, 0x46, 0x09, 0x2c, 0xb5, 0x85, 0x10, 0x30, 0xa9, - 0x0d, 0x9e, 0x04, 0x7a, 0x76, 0x58, 0x84, 0x9d, 0x9d, 0x64, 0xf5, 0xcc, 0x98, 0x4d, 0x34, 0x50, - 0x2f, 0xfc, 0x85, 0x2f, 0xa1, 0x3c, 0x48, 0x31, 0x03, 0xe7, 0x44, 0x70, 0x13, 0x8d, 0xc8, 0x0f, - 0xf0, 0xb4, 0x9b, 0xa6, 0x6d, 0xcb, 0xa3, 0x55, 0x43, 0xab, 0x87, 0xb9, 0x5c, 0x38, 0x4c, 0x92, - 0x0b, 0x87, 0xa9, 0xff, 0x22, 0x9b, 0x10, 0x09, 0xec, 0xf8, 0x0e, 0x93, 0x17, 0x11, 0x02, 0x47, - 0x66, 0x2a, 0x4f, 0xdf, 0x05, 0x02, 0x46, 0x09, 0x30, 0x02, 0xb5, 0x95, 0xb6, 0xf5, 0x21, 0xb1, - 0xfa, 0xdb, 0x99, 0x58, 0xf8, 0xa8, 0x7d, 0xc9, 0x51, 0xdc, 0xf5, 0x64, 0x1f, 0x71, 0x9b, 0xe8, - 0xf7, 0x45, 0x6e, 0x6f, 0x7d, 0x21, 0x7f, 0xcb, 0x01, 0xec, 0xfc, 0x0e, 0xf3, 0x5b, 0xbe, 0x91, - 0x4d, 0x0a, 0xa6, 0x75, 0x34, 0x55, 0x3c, 0xcc, 0x35, 0x9e, 0xdf, 0x43, 0xae, 0xf1, 0xb7, 0xd1, - 0x58, 0x24, 0xc4, 0x14, 0xcf, 0x8e, 0x75, 0xa9, 0x7b, 0xac, 0xaa, 0x74, 0x17, 0x78, 0x89, 0x4c, - 0xfd, 0xbf, 0x99, 0xee, 0x01, 0xc6, 0x0e, 0x5d, 0x75, 0x12, 0x04, 0x90, 0xfb, 0xcb, 0x11, 0xc0, - 0x01, 0x1c, 0x33, 0x8f, 0xb6, 0x00, 0x3e, 0x20, 0x93, 0xc7, 0xfb, 0x2d, 0x80, 0x1f, 0xcb, 0xec, - 0x18, 0x1f, 0xee, 0xb0, 0x65, 0xa0, 0xfe, 0xc7, 0x4c, 0x62, 0x1c, 0xb7, 0x7d, 0xb5, 0xeb, 0x65, - 0xd4, 0xc7, 0xcc, 0x56, 0x78, 0xab, 0x84, 0xc8, 0xf7, 0x14, 0x9a, 0x52, 0x9e, 0x97, 0xc1, 0xf3, - 0xa8, 0x9f, 0xb5, 0xc1, 0x88, 0x66, 0x88, 0x4c, 0x68, 0xa7, 0x91, 0x36, 0x39, 0x72, 0xb4, 0xfa, - 0x9b, 0x99, 0x58, 0x58, 0xb9, 0x43, 0xfc, 0xb6, 0x70, 0xaa, 0xce, 0xed, 0x7e, 0xaa, 0x56, 0xff, - 0x28, 0x9b, 0x1c, 0xd5, 0xee, 0x10, 0x3f, 0xe4, 0x20, 0xae, 0xab, 0x1e, 0x6d, 0xdd, 0x5a, 0x46, - 0xa3, 0xb2, 0x2c, 0xf8, 0xb2, 0x75, 0x31, 0x39, 0xb6, 0x5f, 0x4a, 0x2b, 0x22, 0x3c, 0xd4, 0xf7, - 0x32, 0xf1, 0x80, 0x7c, 0x87, 0x3e, 0x3f, 0x3d, 0x9a, 0xb6, 0xc8, 0x9f, 0xf2, 0x01, 0x59, 0x6b, - 0x0e, 0xe2, 0x53, 0x3e, 0x20, 0xab, 0xc6, 0xa3, 0x7d, 0xca, 0x4f, 0x67, 0xd3, 0xe2, 0x19, 0x1e, - 0xfa, 0x07, 0x7d, 0x4a, 0x14, 0x32, 0x6b, 0x19, 0xff, 0xb4, 0x27, 0xd2, 0x02, 0x08, 0xa6, 0xf0, - 0x8c, 0xf1, 0x79, 0xb4, 0x31, 0x9e, 0x28, 0xac, 0x0f, 0x88, 0x22, 0x1f, 0x0d, 0x61, 0x7d, 0x40, - 0x86, 0xca, 0x07, 0x4f, 0x58, 0xbf, 0x9a, 0xdd, 0x6d, 0x10, 0xcd, 0x13, 0xe1, 0xc5, 0x84, 0xf7, - 0xe5, 0x6c, 0x3c, 0xb8, 0xeb, 0xa1, 0x8b, 0x69, 0x16, 0xf5, 0xf1, 0x30, 0xb3, 0xa9, 0xc2, 0x61, - 0xf8, 0xb4, 0x1d, 0x0d, 0xff, 0x8e, 0x9b, 0x88, 0x3f, 0x94, 0xec, 0x4e, 0x24, 0x8c, 0x56, 0xfd, - 0xb3, 0x4c, 0x24, 0x12, 0xea, 0xa1, 0x5c, 0x21, 0x3c, 0xd2, 0x92, 0x84, 0x5f, 0xf1, 0x2f, 0x33, - 0xf3, 0x91, 0x24, 0x93, 0xc1, 0xf7, 0x94, 0x89, 0xa7, 0x9b, 0xcd, 0x68, 0x79, 0xee, 0x73, 0xff, - 0xeb, 0x59, 0x34, 0x1e, 0x23, 0xc5, 0x97, 0xa4, 0x28, 0x34, 0x70, 0x2d, 0x19, 0x31, 0xce, 0x66, - 0xf1, 0x68, 0xf6, 0x70, 0x93, 0x7a, 0x09, 0xe5, 0xcb, 0xfa, 0x26, 0xfb, 0xb6, 0x5e, 0xc6, 0xd2, - 0xd0, 0x37, 0xc5, 0x1b, 0x37, 0xc0, 0xe3, 0x15, 0x74, 0x86, 0xbd, 0x87, 0x98, 0xb6, 0xb5, 0x6c, - 0xb6, 0x48, 0xc5, 0x5a, 0x30, 0x9b, 0x4d, 0xd3, 0xe5, 0x8f, 0x66, 0x57, 0xb7, 0xb7, 0x8a, 0x97, - 0x3d, 0xdb, 0xd3, 0x9b, 0x75, 0xe2, 0x93, 0xd5, 0x3d, 0xb3, 0x45, 0xea, 0xa6, 0x55, 0x6f, 0x01, - 0xa5, 0xc0, 0x32, 0x99, 0x15, 0xae, 0xb0, 0x2c, 0x98, 0xb5, 0x86, 0x6e, 0x59, 0xc4, 0xa8, 0x58, - 0x53, 0x9b, 0x1e, 0x61, 0x8f, 0x6d, 0x39, 0x76, 0x25, 0xc8, 0x7c, 0xaf, 0x19, 0x9a, 0x32, 0x5e, - 0xa1, 0x04, 0x5a, 0x42, 0x21, 0xf5, 0xd7, 0xf2, 0x09, 0x41, 0x70, 0x8f, 0x90, 0xfa, 0xf8, 0x3d, - 0x9d, 0xdf, 0xa1, 0xa7, 0xaf, 0xa1, 0x7e, 0x1e, 0x67, 0x92, 0x3f, 0x30, 0x80, 0xb1, 0xf8, 0x03, - 0x06, 0x12, 0x5f, 0x68, 0x38, 0x15, 0x6e, 0xa2, 0x89, 0x65, 0xda, 0x4d, 0xc9, 0x9d, 0xd9, 0xf7, - 0x08, 0x9d, 0xd9, 0x85, 0x1f, 0x7e, 0x0b, 0x9d, 0x03, 0x6c, 0x42, 0xb7, 0xf6, 0x43, 0x55, 0x10, - 0x99, 0x89, 0x55, 0x95, 0xdc, 0xb9, 0x69, 0xe5, 0xf1, 0xa7, 0xd0, 0x70, 0x30, 0x40, 0x4c, 0xe2, - 0xf2, 0x97, 0x8b, 0x2e, 0xe3, 0x8c, 0x85, 0x3d, 0xa3, 0x60, 0x30, 0xd1, 0x92, 0x43, 0x67, 0x49, - 0xbc, 0xd4, 0xff, 0x90, 0xe9, 0x16, 0xf6, 0xf8, 0xd0, 0x67, 0xe5, 0x57, 0x50, 0xbf, 0xc1, 0x3e, - 0x8a, 0xeb, 0x54, 0xf7, 0xc0, 0xc8, 0x8c, 0x54, 0xf3, 0xcb, 0xa8, 0x7f, 0x98, 0xe9, 0x1a, 0x6d, - 0xf9, 0xa8, 0x7f, 0xde, 0x97, 0x73, 0x29, 0x9f, 0xc7, 0x27, 0xd1, 0x2b, 0xa8, 0x60, 0x86, 0x91, - 0x7c, 0xeb, 0x61, 0x78, 0x27, 0x6d, 0x4c, 0x80, 0xc3, 0xe8, 0xba, 0x89, 0xce, 0xfa, 0x86, 0x85, - 0x8e, 0x6f, 0x81, 0xe5, 0xd6, 0x3b, 0x8e, 0xc9, 0xc6, 0xa5, 0x76, 0xda, 0x8d, 0x98, 0x67, 0xb9, - 0x77, 0x1c, 0x93, 0x56, 0xa0, 0x7b, 0x6b, 0xc4, 0xd2, 0xeb, 0x1b, 0xb6, 0xb3, 0x0e, 0xb1, 0x35, - 0xd9, 0xe0, 0xd4, 0xc6, 0x18, 0xfc, 0x9e, 0x0f, 0xc6, 0x4f, 0xa1, 0x91, 0xd5, 0x66, 0x87, 0x04, - 0xd1, 0x0c, 0xd9, 0x5b, 0x9f, 0x36, 0x4c, 0x81, 0xc1, 0x0b, 0xc9, 0x05, 0x84, 0x80, 0xc8, 0x83, - 0x58, 0xd8, 0xf0, 0xb0, 0xa7, 0x0d, 0x52, 0xc8, 0x32, 0xef, 0xae, 0x09, 0xa6, 0xd5, 0x4c, 0x48, - 0xf5, 0xa6, 0x6d, 0xad, 0xd6, 0x3d, 0xe2, 0xb4, 0xa0, 0xa1, 0x60, 0x9c, 0xa8, 0x9d, 0x05, 0x0a, - 0x78, 0x3a, 0x71, 0xe7, 0x6d, 0x6b, 0x75, 0x99, 0x38, 0x2d, 0xda, 0xd4, 0xab, 0x08, 0xf3, 0xa6, - 0x3a, 0x70, 0xe9, 0xc1, 0x3e, 0x0e, 0xec, 0x14, 0x35, 0xfe, 0x11, 0xec, 0x36, 0x04, 0x3e, 0xac, - 0x88, 0x86, 0x58, 0x48, 0x37, 0x26, 0x34, 0x30, 0x55, 0xd4, 0x10, 0x03, 0x81, 0xbc, 0xce, 0x22, - 0x6e, 0xbd, 0xc0, 0xac, 0xa6, 0x35, 0xfe, 0x4b, 0xfd, 0x3b, 0xd9, 0xb4, 0x40, 0xc9, 0x47, 0xf5, - 0x8d, 0x03, 0xcf, 0x21, 0xc4, 0x33, 0x4a, 0xd2, 0xcf, 0x8d, 0x18, 0xb4, 0x86, 0x98, 0x14, 0x1e, - 0x42, 0x59, 0xf5, 0x0b, 0xd9, 0xb4, 0x50, 0xcf, 0xfb, 0x12, 0x4e, 0xb8, 0xee, 0x64, 0xf7, 0xb0, - 0xee, 0x1c, 0xbe, 0x38, 0x92, 0x74, 0xe5, 0x68, 0xbf, 0x87, 0x1d, 0xa0, 0x70, 0x7e, 0x24, 0x9b, - 0x1a, 0x60, 0xfb, 0x44, 0x3a, 0xea, 0x17, 0xb3, 0xa9, 0x01, 0xc2, 0x8f, 0xe5, 0x50, 0x4a, 0xd4, - 0x96, 0x93, 0xb1, 0xc4, 0x28, 0x9e, 0x59, 0x60, 0xe1, 0x1d, 0x6f, 0x9b, 0x96, 0x81, 0x1f, 0x43, - 0x67, 0xee, 0xd4, 0x66, 0xb4, 0xfa, 0xed, 0xca, 0x62, 0xb9, 0x7e, 0x67, 0xb1, 0x56, 0x9d, 0x99, - 0xae, 0xcc, 0x56, 0x66, 0xca, 0x85, 0x1e, 0x7c, 0x0a, 0x8d, 0x85, 0xa8, 0xb9, 0x3b, 0x0b, 0xa5, - 0xc5, 0x42, 0x06, 0x8f, 0xa3, 0x91, 0x10, 0x38, 0xb5, 0xb4, 0x5c, 0xc8, 0x3e, 0xf3, 0x34, 0x1a, - 0x82, 0xf5, 0xb5, 0x04, 0xdc, 0xf1, 0x30, 0x1a, 0x58, 0x9a, 0xaa, 0xcd, 0x68, 0x77, 0x81, 0x09, - 0x42, 0x7d, 0xe5, 0x99, 0x45, 0xca, 0x30, 0xf3, 0xcc, 0xff, 0xca, 0x20, 0x54, 0x9b, 0x5d, 0xae, - 0x72, 0xc2, 0x21, 0xd4, 0x5f, 0x59, 0xbc, 0x5b, 0x9a, 0xaf, 0x50, 0xba, 0x01, 0x94, 0x5f, 0xaa, - 0xce, 0xd0, 0x1a, 0x06, 0x51, 0xef, 0xf4, 0xfc, 0x52, 0x6d, 0xa6, 0x90, 0xa5, 0x40, 0x6d, 0xa6, - 0x54, 0x2e, 0xe4, 0x28, 0xf0, 0x9e, 0x56, 0x59, 0x9e, 0x29, 0xe4, 0xe9, 0x9f, 0xf3, 0xb5, 0xe5, - 0xd2, 0x72, 0xa1, 0x97, 0xfe, 0x39, 0x0b, 0x7f, 0xf6, 0x51, 0x66, 0xb5, 0x99, 0x65, 0xf8, 0xd1, - 0x4f, 0x9b, 0x30, 0xeb, 0xff, 0x1a, 0xa0, 0x28, 0xca, 0xba, 0x5c, 0xd1, 0x0a, 0x83, 0xf4, 0x07, - 0x65, 0x49, 0x7f, 0x20, 0xda, 0x38, 0x6d, 0x66, 0x61, 0xe9, 0xee, 0x4c, 0x61, 0x88, 0xf2, 0x5a, - 0xb8, 0x4d, 0xc1, 0xc3, 0xf4, 0x4f, 0x6d, 0x81, 0xfe, 0x39, 0x42, 0x39, 0x69, 0x33, 0xa5, 0xf9, - 0x6a, 0x69, 0x79, 0xae, 0x30, 0x4a, 0xdb, 0x03, 0x3c, 0xc7, 0x58, 0xc9, 0xc5, 0xd2, 0xc2, 0x4c, - 0xa1, 0xc0, 0x69, 0xca, 0xf3, 0x95, 0xc5, 0xdb, 0x85, 0x71, 0x68, 0xc8, 0x5b, 0x0b, 0xf0, 0x03, - 0xd3, 0x02, 0xf0, 0xd7, 0xa9, 0x67, 0x3e, 0x83, 0xfa, 0x96, 0x6a, 0x60, 0x39, 0x73, 0x0e, 0x9d, - 0x5a, 0xaa, 0xd5, 0x97, 0xdf, 0xaa, 0xce, 0x44, 0xe4, 0x3d, 0x8e, 0x46, 0x7c, 0xc4, 0x7c, 0x65, - 0xf1, 0xce, 0x9b, 0x4c, 0xda, 0x3e, 0x68, 0xa1, 0x34, 0xbd, 0x54, 0x2b, 0x64, 0x69, 0xaf, 0xf8, - 0xa0, 0x7b, 0x95, 0xc5, 0xf2, 0xd2, 0xbd, 0x5a, 0x21, 0xf7, 0xcc, 0x03, 0x3f, 0xe3, 0xd2, 0x92, - 0x63, 0xae, 0x9a, 0x16, 0xbe, 0x80, 0x1e, 0x2b, 0xcf, 0xdc, 0xad, 0x4c, 0xcf, 0xd4, 0x97, 0xb4, - 0xca, 0xad, 0xca, 0x62, 0xa4, 0xa6, 0x33, 0x68, 0x5c, 0x46, 0x97, 0xaa, 0x95, 0x42, 0x06, 0x9f, - 0x45, 0x58, 0x06, 0xbf, 0x5e, 0x5a, 0x98, 0x2d, 0x64, 0xb1, 0x82, 0x4e, 0xcb, 0xf0, 0xca, 0xe2, - 0xf2, 0x9d, 0xc5, 0x99, 0x42, 0xee, 0x99, 0x9f, 0xc8, 0xa0, 0x33, 0x89, 0x0e, 0x94, 0x58, 0x45, - 0x17, 0x67, 0xe6, 0x4b, 0xb5, 0xe5, 0xca, 0x74, 0x6d, 0xa6, 0xa4, 0x4d, 0xcf, 0xd5, 0xa7, 0x4b, - 0xcb, 0x33, 0xb7, 0x96, 0xb4, 0xb7, 0xea, 0xb7, 0x66, 0x16, 0x67, 0xb4, 0xd2, 0x7c, 0xa1, 0x07, - 0x3f, 0x85, 0x8a, 0x29, 0x34, 0xb5, 0x99, 0xe9, 0x3b, 0x5a, 0x65, 0xf9, 0xad, 0x42, 0x06, 0x3f, - 0x89, 0x2e, 0xa4, 0x12, 0xd1, 0xdf, 0x85, 0x2c, 0xbe, 0x88, 0x26, 0xd2, 0x48, 0xde, 0x98, 0x2f, - 0xe4, 0x9e, 0xf9, 0xe1, 0x0c, 0xc2, 0x71, 0x0f, 0x38, 0xfc, 0x04, 0x3a, 0x4f, 0xf5, 0xa2, 0x9e, - 0xde, 0xc0, 0x27, 0xd1, 0x85, 0x44, 0x0a, 0xa1, 0x79, 0x45, 0xf4, 0x78, 0x0a, 0x09, 0x6f, 0xdc, - 0x79, 0xa4, 0x24, 0x13, 0xd0, 0xa6, 0x4d, 0x95, 0xdf, 0xfb, 0x4f, 0x17, 0x7b, 0xde, 0xfb, 0xe6, - 0xc5, 0xcc, 0xef, 0x7e, 0xf3, 0x62, 0xe6, 0x8f, 0xbe, 0x79, 0x31, 0xf3, 0xa9, 0xeb, 0x7b, 0x71, - 0x10, 0x64, 0xa3, 0x7f, 0xa5, 0x0f, 0x5c, 0x61, 0x6e, 0xfc, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xac, 0x7f, 0x9a, 0xda, 0xe2, 0x35, 0x01, 0x00, + 0x71, 0x71, 0xd9, 0xfc, 0xab, 0x0c, 0x3a, 0xb5, 0x54, 0x29, 0x4f, 0x7f, 0x9b, 0x68, 0x4d, 0xfc, + 0x7b, 0x8e, 0x76, 0x4f, 0xc3, 0xf7, 0xd4, 0x4a, 0x0b, 0xf3, 0xdf, 0x4e, 0xfd, 0x23, 0x7d, 0xcf, + 0x11, 0xef, 0x9f, 0xdf, 0xea, 0x43, 0x43, 0xb7, 0x3b, 0x2b, 0x84, 0xbf, 0xf9, 0x1d, 0xeb, 0x03, + 0xf5, 0x75, 0x34, 0xc4, 0xc5, 0x00, 0x97, 0x51, 0x42, 0xe0, 0x11, 0xee, 0x48, 0xca, 0x7c, 0xbb, + 0x45, 0x22, 0x7c, 0x1e, 0xe5, 0xef, 0x12, 0x67, 0x45, 0xb4, 0xc9, 0x7f, 0x40, 0x9c, 0x15, 0x0d, + 0xa0, 0x78, 0x3e, 0x34, 0x95, 0x2b, 0x55, 0x2b, 0x10, 0x84, 0x9a, 0xdf, 0x83, 0x41, 0x54, 0xed, + 0xc0, 0x9c, 0x40, 0x6f, 0x9b, 0x2c, 0x7c, 0xb5, 0xe8, 0x0f, 0x14, 0x2d, 0x89, 0x17, 0xd1, 0xb8, + 0xf8, 0x9e, 0xcc, 0x22, 0x30, 0x0f, 0x24, 0xb0, 0x4b, 0x8a, 0xbd, 0x1c, 0x2f, 0x8a, 0x5f, 0x45, + 0xc3, 0x3e, 0x10, 0x5e, 0xc6, 0x07, 0xc3, 0xb0, 0x9f, 0x01, 0xab, 0x48, 0x78, 0x77, 0xa9, 0x80, + 0xc8, 0x00, 0x6e, 0x77, 0x50, 0x02, 0x83, 0x88, 0xa5, 0x81, 0x54, 0x00, 0x7f, 0x0c, 0x18, 0xb4, + 0x6d, 0xcb, 0x25, 0xf0, 0x06, 0x38, 0x04, 0x06, 0xeb, 0x60, 0x8a, 0xe7, 0x70, 0x38, 0x73, 0x4b, + 0x90, 0xc8, 0xf0, 0x12, 0x42, 0xe1, 0x5b, 0x0d, 0x77, 0xfe, 0xda, 0xf3, 0x2b, 0x92, 0xc0, 0x42, + 0xbc, 0x65, 0x1d, 0x79, 0x94, 0x5b, 0x56, 0xf5, 0x77, 0xb3, 0x68, 0xa8, 0xd4, 0x6e, 0x07, 0x43, + 0xe1, 0x59, 0xd4, 0x57, 0x6a, 0xb7, 0xef, 0x68, 0x15, 0x31, 0x0c, 0xa4, 0xde, 0x6e, 0xd7, 0x3b, + 0x8e, 0x29, 0x9a, 0xda, 0x30, 0x22, 0x3c, 0x8d, 0x46, 0x4a, 0xed, 0x76, 0xb5, 0xb3, 0xd2, 0x34, + 0x1b, 0x42, 0x54, 0x79, 0x96, 0x00, 0xa3, 0xdd, 0xae, 0xb7, 0x01, 0x13, 0x4d, 0x2d, 0x20, 0x97, + 0xc1, 0x6f, 0x83, 0xcb, 0x34, 0x0f, 0x6a, 0xce, 0xc2, 0x26, 0xab, 0x41, 0x00, 0xc8, 0xb0, 0x6d, + 0x93, 0x01, 0x11, 0x0b, 0x94, 0x79, 0xde, 0x0f, 0x37, 0x4a, 0x2b, 0x8a, 0x05, 0x2f, 0x0f, 0x59, + 0xe2, 0x8f, 0xa2, 0xfe, 0x52, 0xbb, 0x2d, 0x5c, 0xe3, 0xc1, 0x5b, 0x2d, 0x2d, 0x15, 0xe9, 0x63, + 0x9f, 0x6c, 0xe2, 0x65, 0x34, 0x2a, 0x57, 0xb6, 0xa7, 0x40, 0x9b, 0x7f, 0x9a, 0x81, 0x0f, 0x3a, + 0xe2, 0xa6, 0x62, 0x37, 0x50, 0xae, 0xd4, 0x6e, 0xf3, 0xf9, 0xe8, 0x54, 0x42, 0x7f, 0x44, 0xdd, + 0x47, 0x4a, 0xed, 0xb6, 0xff, 0xe9, 0xcc, 0x54, 0xf5, 0x78, 0x7d, 0xfa, 0xd7, 0xd8, 0xa7, 0x1f, + 0x6d, 0x7b, 0x50, 0xf5, 0x97, 0x72, 0x68, 0xac, 0xd4, 0x6e, 0x9f, 0x04, 0xe8, 0x3c, 0x28, 0x27, + 0x95, 0xe7, 0x10, 0x12, 0xa6, 0xc7, 0xfe, 0xc0, 0x64, 0x7b, 0x48, 0x98, 0x1a, 0x95, 0x8c, 0x26, + 0x10, 0xf9, 0xea, 0x37, 0xb0, 0x27, 0xf5, 0xfb, 0x7c, 0x0e, 0xa6, 0xe2, 0xa3, 0xee, 0x70, 0xff, + 0x41, 0xe9, 0x36, 0xde, 0x07, 0x7d, 0x7b, 0xea, 0x83, 0xdf, 0x94, 0x06, 0x0f, 0x04, 0x7c, 0x3c, + 0xe9, 0x85, 0xde, 0x7d, 0x6d, 0x8b, 0x47, 0x45, 0x61, 0x72, 0x2f, 0x60, 0x3f, 0x08, 0x3d, 0xf7, + 0x49, 0x6f, 0x50, 0x54, 0xdd, 0x34, 0xb4, 0x08, 0xad, 0xdf, 0x87, 0xfd, 0x7b, 0xea, 0xc3, 0xad, + 0x2c, 0xf8, 0x9d, 0x04, 0x3e, 0xed, 0xfb, 0x3f, 0x5d, 0x5c, 0x43, 0x88, 0x3d, 0xe8, 0x04, 0xd6, + 0x62, 0x23, 0xcc, 0x7d, 0x95, 0xc5, 0xa6, 0xe7, 0xee, 0xab, 0x21, 0x49, 0xf0, 0xf0, 0x9c, 0x4b, + 0x7c, 0x78, 0xbe, 0x82, 0x06, 0x34, 0x7d, 0xe3, 0x8d, 0x0e, 0x71, 0x36, 0xf9, 0x76, 0x86, 0x85, + 0x8c, 0xd1, 0x37, 0xea, 0x9f, 0xa3, 0x40, 0x2d, 0x40, 0x63, 0x35, 0x70, 0x5c, 0x12, 0x1e, 0xda, + 0xd8, 0xed, 0x5e, 0xe0, 0xae, 0xf4, 0x28, 0x8a, 0x8e, 0x5f, 0x42, 0xb9, 0xd2, 0xbd, 0x1a, 0x97, + 0x6c, 0xd0, 0xb5, 0xa5, 0x7b, 0x35, 0x2e, 0xaf, 0xd4, 0xb2, 0xf7, 0x6a, 0xea, 0xe7, 0xb3, 0x08, + 0xc7, 0x29, 0xf1, 0xf3, 0x68, 0x10, 0xa0, 0xab, 0x54, 0x67, 0xc4, 0xa4, 0x46, 0x1b, 0x6e, 0xdd, + 0x01, 0xa8, 0xb4, 0xb9, 0xf3, 0x49, 0xf1, 0x8b, 0x90, 0xbf, 0x8d, 0xa7, 0xd5, 0x90, 0x92, 0x1a, + 0x6d, 0xb8, 0x7e, 0xc6, 0xb3, 0x48, 0xfa, 0x36, 0x4e, 0x0c, 0xfb, 0xc2, 0x7b, 0xb5, 0x39, 0xdb, + 0xf5, 0xb8, 0xa8, 0xd9, 0xbe, 0x70, 0xc3, 0x85, 0x6c, 0x5a, 0xd2, 0xbe, 0x90, 0x91, 0x41, 0x46, + 0x80, 0x7b, 0x35, 0x66, 0xfd, 0x6b, 0x68, 0x76, 0xd3, 0xdf, 0x50, 0xb2, 0x8c, 0x00, 0x1b, 0x6e, + 0x9d, 0x59, 0x0e, 0x1b, 0x90, 0x38, 0x4e, 0xca, 0x08, 0x20, 0x95, 0x52, 0xbf, 0x38, 0x80, 0x0a, + 0x65, 0xdd, 0xd3, 0x57, 0x74, 0x97, 0x08, 0xa7, 0xe9, 0x31, 0x1f, 0xe6, 0x7f, 0x8e, 0x20, 0x07, + 0x63, 0x25, 0xe1, 0x6b, 0xa2, 0x05, 0xf0, 0x27, 0x42, 0xbe, 0x41, 0xbe, 0x26, 0x31, 0x01, 0xc4, + 0x4a, 0xbd, 0xcd, 0xc1, 0x5a, 0x8c, 0x10, 0x5f, 0x45, 0x43, 0x3e, 0x8c, 0x1e, 0x00, 0x72, 0xa1, + 0xce, 0x18, 0x2b, 0x74, 0xff, 0xaf, 0x89, 0x68, 0xfc, 0x22, 0x1a, 0xf6, 0x7f, 0x0a, 0x5b, 0x6b, + 0x96, 0xcd, 0x62, 0x25, 0x76, 0x7a, 0x12, 0x49, 0xc5, 0xa2, 0x30, 0xbf, 0xf5, 0x4a, 0x45, 0x23, + 0x09, 0x23, 0x24, 0x52, 0xfc, 0x39, 0x34, 0xea, 0xff, 0xe6, 0x07, 0x06, 0x96, 0x5b, 0xe3, 0x6a, + 0x90, 0x97, 0x2e, 0x22, 0xd6, 0x49, 0x99, 0x9c, 0x1d, 0x1d, 0x1e, 0xf7, 0x73, 0x20, 0x18, 0x2b, + 0xf1, 0x93, 0x43, 0xa4, 0x02, 0x5c, 0x41, 0xe3, 0x3e, 0x24, 0xd4, 0xd0, 0xfe, 0xf0, 0xc4, 0x68, + 0xac, 0xd4, 0x13, 0x95, 0x34, 0x5e, 0x0a, 0x37, 0xd1, 0x79, 0x09, 0x68, 0xb8, 0x6b, 0xe6, 0x7d, + 0x8f, 0x1f, 0xf7, 0x78, 0xfc, 0x36, 0x9e, 0xf4, 0x26, 0xe0, 0xca, 0x68, 0xfc, 0xec, 0x55, 0x72, + 0x64, 0xfd, 0xae, 0xdc, 0x70, 0x0d, 0x9d, 0xf6, 0xf1, 0xb7, 0xa6, 0xab, 0x55, 0xc7, 0x7e, 0x87, + 0x34, 0xbc, 0x4a, 0x99, 0x1f, 0x97, 0x21, 0xae, 0x87, 0xb1, 0x52, 0x5f, 0x6d, 0xb4, 0xa9, 0x52, + 0x50, 0x9c, 0xcc, 0x3c, 0xb1, 0x30, 0xbe, 0x8b, 0xce, 0x08, 0xf0, 0x8a, 0xe5, 0x7a, 0xba, 0xd5, + 0x20, 0x95, 0x32, 0x3f, 0x43, 0xc3, 0x79, 0x9e, 0x73, 0x35, 0x39, 0x52, 0x66, 0x9b, 0x5c, 0x1c, + 0xbf, 0x8c, 0x46, 0x7c, 0x04, 0x7b, 0xff, 0x18, 0x82, 0xf7, 0x0f, 0x18, 0x92, 0xc6, 0x4a, 0x3d, + 0xea, 0xa4, 0x22, 0x13, 0x8b, 0x1a, 0x05, 0x69, 0x41, 0x87, 0x25, 0x8d, 0xf2, 0x36, 0xdb, 0x89, + 0xca, 0x08, 0xa9, 0x42, 0x5f, 0x0d, 0x35, 0x6a, 0xc9, 0x31, 0x57, 0x4d, 0x76, 0x92, 0xf6, 0xfd, + 0x52, 0x56, 0xea, 0x36, 0x00, 0x93, 0xf4, 0x83, 0x91, 0x4f, 0x94, 0xd0, 0xa9, 0x04, 0x1d, 0xdb, + 0xd3, 0x89, 0xf1, 0x0b, 0xd9, 0xb0, 0x11, 0x47, 0xfc, 0xd8, 0x38, 0x85, 0x06, 0xfc, 0x2f, 0xe1, + 0x9b, 0x07, 0x25, 0x6d, 0x68, 0x46, 0x79, 0xf8, 0x78, 0x49, 0x1c, 0x47, 0xfc, 0x28, 0x79, 0x10, + 0xe2, 0x78, 0x2f, 0x13, 0x8a, 0xe3, 0x88, 0x1f, 0x2f, 0xff, 0x7a, 0x3e, 0x9c, 0x93, 0x4e, 0xce, + 0x98, 0x07, 0xb5, 0x4d, 0x0e, 0xcd, 0x8b, 0xfa, 0xf6, 0xe0, 0x1f, 0x22, 0xaa, 0x66, 0xff, 0xa3, + 0xa9, 0x26, 0x7e, 0x19, 0x0d, 0x55, 0x6d, 0xd7, 0x5b, 0x75, 0x88, 0x5b, 0x0d, 0xe2, 0x8f, 0x82, + 0x6f, 0x51, 0x9b, 0x83, 0xeb, 0x6d, 0x69, 0xf6, 0x17, 0xc9, 0xd5, 0xdf, 0xcf, 0xc5, 0xb4, 0x81, + 0x6d, 0x5c, 0x8f, 0xa4, 0x36, 0x1c, 0xc0, 0x50, 0xc7, 0xd7, 0xc3, 0x55, 0x90, 0xed, 0xf0, 0x7b, + 0x85, 0xe0, 0x2a, 0x2b, 0x7c, 0x83, 0x2f, 0x93, 0xe0, 0x4f, 0xa3, 0x73, 0x12, 0xa0, 0xaa, 0x3b, + 0x7a, 0x8b, 0x78, 0x61, 0xae, 0x17, 0x70, 0x97, 0xf7, 0x4b, 0xd7, 0xdb, 0x01, 0x5a, 0xcc, 0x1f, + 0x93, 0xc2, 0x41, 0x50, 0xad, 0xfe, 0x3d, 0x58, 0xae, 0xfd, 0xe7, 0x2c, 0x1a, 0x09, 0x3a, 0x5a, + 0x77, 0x5c, 0x72, 0x7c, 0x7b, 0xf4, 0xe3, 0x68, 0x04, 0xbc, 0x37, 0x5b, 0xc4, 0xf2, 0x84, 0xa4, + 0x8a, 0x2c, 0xe0, 0xa3, 0x8f, 0xe0, 0xb1, 0x7d, 0x25, 0x42, 0x5c, 0x44, 0xbd, 0x4c, 0x07, 0x04, + 0x9f, 0x5a, 0xa6, 0x00, 0x0c, 0xae, 0xfe, 0x78, 0x0e, 0x0d, 0xfb, 0x52, 0x9e, 0x32, 0x8f, 0xea, + 0x8d, 0xcf, 0xe1, 0x0a, 0xf9, 0x1a, 0x42, 0x55, 0xdb, 0xf1, 0xf4, 0xa6, 0x90, 0x9a, 0x1d, 0x8e, + 0x4a, 0x6d, 0x80, 0xb2, 0x32, 0x02, 0x09, 0x9e, 0x44, 0x48, 0x18, 0x60, 0xfd, 0x30, 0xc0, 0x46, + 0xb7, 0xb7, 0x8a, 0x28, 0x1c, 0x57, 0x9a, 0x40, 0xa1, 0xfe, 0x5a, 0x16, 0x8d, 0xf9, 0x9d, 0x34, + 0xf3, 0x90, 0x34, 0x3a, 0xde, 0x31, 0x1e, 0x0c, 0xb2, 0xb4, 0x7b, 0x77, 0x94, 0xb6, 0xfa, 0x3f, + 0x84, 0x89, 0x64, 0xba, 0x69, 0x9f, 0x4c, 0x24, 0x7f, 0x11, 0x3a, 0xae, 0x7e, 0x77, 0x0e, 0x9d, + 0xf6, 0xa5, 0x3e, 0xdb, 0xb1, 0x60, 0x93, 0x31, 0xad, 0x37, 0x9b, 0xc7, 0x79, 0x5d, 0x1e, 0xf2, + 0x05, 0xb1, 0xc4, 0xc3, 0x21, 0xf0, 0x38, 0xeb, 0xf7, 0x39, 0xb8, 0x6e, 0x9b, 0x86, 0x26, 0x12, + 0xe1, 0x57, 0xd1, 0xb0, 0xff, 0xb3, 0xe4, 0xac, 0xfa, 0x8b, 0x31, 0x5c, 0x19, 0x04, 0x85, 0x74, + 0x47, 0xf2, 0xfa, 0x90, 0x0a, 0xa8, 0xff, 0xa5, 0x0f, 0x4d, 0xdc, 0x33, 0x2d, 0xc3, 0xde, 0x70, + 0xfd, 0x30, 0xfd, 0x47, 0x7e, 0xcb, 0x7c, 0xd8, 0xe1, 0xf9, 0xdf, 0x40, 0x67, 0xa2, 0x22, 0x75, + 0x82, 0xe0, 0x49, 0xbc, 0x77, 0x36, 0x18, 0x41, 0xdd, 0x0f, 0xd8, 0xcf, 0xef, 0xdd, 0xb4, 0xe4, + 0x92, 0xd1, 0x88, 0xff, 0xfd, 0xbb, 0x89, 0xf8, 0xff, 0x0c, 0xea, 0x2b, 0xdb, 0x2d, 0xdd, 0xf4, + 0xfd, 0xff, 0x60, 0x14, 0x07, 0xf5, 0x02, 0x46, 0xe3, 0x14, 0x94, 0x3f, 0xaf, 0x18, 0xba, 0x6c, + 0x30, 0xe4, 0xef, 0x17, 0xe8, 0xb8, 0xc4, 0xd1, 0x44, 0x22, 0x6c, 0xa3, 0x11, 0x5e, 0x1d, 0xbf, + 0x25, 0x43, 0x70, 0x4b, 0x16, 0xe4, 0x55, 0x4c, 0x57, 0xab, 0x49, 0xa9, 0x1c, 0xbb, 0x2e, 0x63, + 0x89, 0x08, 0xf8, 0xc7, 0xb0, 0xfb, 0x32, 0x4d, 0xe6, 0x2f, 0x08, 0x01, 0x26, 0x99, 0xa1, 0xb8, + 0x10, 0x60, 0x96, 0x11, 0x89, 0xf0, 0x0c, 0x1a, 0x2f, 0x35, 0x9b, 0xf6, 0x46, 0x10, 0xa5, 0x88, + 0xaa, 0xc4, 0x30, 0x44, 0x6a, 0x85, 0xcb, 0x17, 0x9d, 0x22, 0xe1, 0xe3, 0xea, 0x0d, 0x8e, 0xd6, + 0xe2, 0x25, 0x26, 0x5e, 0x43, 0x38, 0xde, 0xe6, 0x3d, 0x5d, 0xbf, 0x7c, 0x31, 0x8b, 0x70, 0xe4, + 0x1c, 0x32, 0x73, 0x8c, 0xb7, 0x53, 0xea, 0xcf, 0x65, 0xd0, 0x78, 0x2c, 0x7a, 0x18, 0xbe, 0x81, + 0x10, 0x83, 0x08, 0x51, 0x2b, 0xc0, 0x0d, 0x2c, 0x8c, 0x28, 0xc6, 0x97, 0x92, 0x90, 0x0c, 0x5f, + 0x43, 0x03, 0xec, 0x57, 0x90, 0x26, 0x34, 0x5a, 0xa4, 0xd3, 0x31, 0x0d, 0x2d, 0x20, 0x0a, 0x6b, + 0x81, 0x7b, 0xbc, 0x5c, 0x62, 0x11, 0x6f, 0xb3, 0x1d, 0xd4, 0x42, 0xc9, 0x68, 0x07, 0x0e, 0x07, + 0x0d, 0x2e, 0x19, 0x87, 0xd5, 0x75, 0x7d, 0x3c, 0x10, 0x5b, 0x6e, 0xa7, 0x40, 0x6c, 0x91, 0xb9, + 0x89, 0x47, 0x5e, 0x3b, 0x38, 0xe3, 0xd2, 0x2f, 0x67, 0xd1, 0x58, 0x50, 0xeb, 0x21, 0x5e, 0x19, + 0x7d, 0x80, 0x44, 0xf2, 0xa5, 0x0c, 0x52, 0xa6, 0xcc, 0x66, 0xd3, 0xb4, 0x56, 0x2b, 0xd6, 0x7d, + 0xdb, 0x69, 0xc1, 0xe4, 0x71, 0x78, 0xb7, 0x8b, 0xea, 0xf7, 0x65, 0xd0, 0x38, 0x6f, 0xd0, 0xb4, + 0xee, 0x18, 0x87, 0x77, 0xed, 0x1b, 0x6d, 0xc9, 0xe1, 0xe9, 0x8b, 0xfa, 0xd5, 0x2c, 0x42, 0xf3, + 0x76, 0x63, 0xfd, 0x88, 0x5b, 0xd0, 0x7f, 0x62, 0xe7, 0xec, 0xb8, 0x05, 0x39, 0x3b, 0xae, 0x92, + 0xf1, 0xf3, 0xe3, 0xd2, 0x4a, 0x29, 0x1d, 0xdf, 0xd5, 0x04, 0x95, 0x8a, 0xe9, 0x77, 0x59, 0xa5, + 0xdb, 0x5b, 0xc5, 0x7c, 0xd3, 0x6e, 0xac, 0x6b, 0x40, 0xaf, 0xfe, 0x79, 0x86, 0xc9, 0xee, 0x88, + 0x5b, 0xd8, 0xfb, 0x9f, 0x9f, 0xdf, 0xe3, 0xe7, 0xff, 0xb5, 0x0c, 0x3a, 0xad, 0x91, 0x86, 0xfd, + 0x80, 0x38, 0x9b, 0xd3, 0xb6, 0x41, 0x6e, 0x11, 0x8b, 0x38, 0x87, 0x35, 0xa2, 0xfe, 0x31, 0x84, + 0x9a, 0x0c, 0x1b, 0x73, 0xc7, 0x25, 0xc6, 0xd1, 0x09, 0x78, 0xaa, 0xfe, 0xa3, 0x7e, 0xa4, 0x24, + 0xee, 0x10, 0x8f, 0xec, 0xae, 0x28, 0x75, 0xdb, 0x9f, 0x3f, 0xa8, 0x6d, 0x7f, 0xef, 0xde, 0xb6, + 0xfd, 0x7d, 0x7b, 0xdd, 0xf6, 0xf7, 0xef, 0x66, 0xdb, 0xdf, 0x8a, 0x6e, 0xfb, 0x07, 0x60, 0xdb, + 0x7f, 0xa3, 0xeb, 0xb6, 0x7f, 0xc6, 0x32, 0x1e, 0x71, 0xd3, 0x7f, 0x64, 0xd3, 0x7c, 0x3c, 0xca, + 0x69, 0xe5, 0x32, 0x9d, 0x14, 0x1b, 0xb6, 0x63, 0x10, 0x83, 0x1f, 0x52, 0xe0, 0x56, 0xde, 0xe1, + 0x30, 0x2d, 0xc0, 0xc6, 0x72, 0xa6, 0x8c, 0xec, 0x26, 0x67, 0xca, 0x01, 0x1c, 0x63, 0xbe, 0x90, + 0x45, 0xe3, 0xd3, 0xc4, 0xf1, 0x58, 0x3c, 0x91, 0x83, 0x78, 0x48, 0x2e, 0xa1, 0x31, 0x81, 0x21, + 0xec, 0xc8, 0x85, 0x5c, 0xff, 0x0d, 0xe2, 0x78, 0xd1, 0xb7, 0xf5, 0x28, 0x3d, 0xad, 0xde, 0x8f, + 0x5b, 0xcc, 0xc7, 0x6e, 0x50, 0xbd, 0x0f, 0x67, 0x82, 0x34, 0xf9, 0x2f, 0x2d, 0xa0, 0x17, 0x42, + 0x11, 0xe7, 0xf7, 0x1e, 0x8a, 0x58, 0xfd, 0x99, 0x0c, 0xba, 0xa4, 0x11, 0x8b, 0x6c, 0xe8, 0x2b, + 0x4d, 0x22, 0x34, 0x8b, 0xaf, 0x0c, 0x74, 0xd6, 0x30, 0xdd, 0x96, 0xee, 0x35, 0xd6, 0xf6, 0x25, + 0xa3, 0x59, 0x34, 0x2c, 0xce, 0x5f, 0x7b, 0x98, 0xdb, 0xa4, 0x72, 0xea, 0xaf, 0xe6, 0x50, 0xff, + 0x94, 0xed, 0xed, 0x3b, 0x63, 0x78, 0x38, 0xe5, 0x67, 0xf7, 0x70, 0x2f, 0xf2, 0x51, 0xa8, 0x5c, + 0x88, 0x24, 0x08, 0x86, 0x17, 0x2b, 0x76, 0x2c, 0xe2, 0xa2, 0x4f, 0xb6, 0xc7, 0xa8, 0xd8, 0xcf, + 0xa3, 0x41, 0xf0, 0xf6, 0x14, 0x6e, 0x2e, 0xc1, 0xac, 0xc9, 0xa3, 0xc0, 0x68, 0x1d, 0x21, 0x29, + 0xfe, 0xb4, 0x14, 0x00, 0xa5, 0x6f, 0xff, 0x51, 0xb4, 0xc5, 0x58, 0x28, 0x07, 0x16, 0xac, 0x5a, + 0xfd, 0x56, 0x1e, 0x0d, 0xfb, 0xc6, 0x2c, 0x87, 0xd4, 0x83, 0xcf, 0xa2, 0xbe, 0x39, 0x5b, 0x88, + 0x8a, 0x08, 0xc6, 0x2f, 0x6b, 0xb6, 0x1b, 0xb1, 0xea, 0xe1, 0x44, 0xf8, 0x06, 0x1a, 0x58, 0xb4, + 0x0d, 0xd1, 0x74, 0x0b, 0xc6, 0xb4, 0x65, 0x1b, 0x31, 0xd7, 0x97, 0x80, 0x10, 0x5f, 0x42, 0x79, + 0xb0, 0x7a, 0x13, 0xae, 0x9e, 0x23, 0x96, 0x6e, 0x80, 0x17, 0x74, 0xa3, 0x6f, 0xaf, 0xba, 0xd1, + 0xff, 0xa8, 0xba, 0x31, 0x70, 0xb0, 0xba, 0xf1, 0x16, 0x1a, 0x86, 0x9a, 0xfc, 0xa8, 0xdf, 0x3b, + 0x2f, 0x6f, 0x8f, 0xf1, 0x15, 0x68, 0x84, 0xb5, 0x9b, 0xc7, 0xfe, 0x86, 0x85, 0x47, 0x62, 0x15, + 0x51, 0x3b, 0xb4, 0x0f, 0xb5, 0xfb, 0xfd, 0x0c, 0xea, 0xbf, 0x63, 0xad, 0x5b, 0xf6, 0xc6, 0xfe, + 0x34, 0xee, 0x06, 0x1a, 0xe2, 0x6c, 0x84, 0x39, 0x1e, 0xbc, 0x99, 0x3a, 0x0c, 0x5c, 0x07, 0x4e, + 0x9a, 0x48, 0x85, 0x5f, 0x0e, 0x0a, 0x81, 0x61, 0x6b, 0x2e, 0x8c, 0x2b, 0xea, 0x17, 0x6a, 0xc8, + 0xa1, 0x10, 0x45, 0x72, 0x7c, 0x9e, 0xe7, 0xc0, 0x17, 0x02, 0xeb, 0xd0, 0xa6, 0xb0, 0x14, 0xf8, + 0xea, 0xbf, 0xcc, 0xa2, 0xd1, 0xc8, 0xf5, 0xd3, 0x33, 0x68, 0x90, 0x5f, 0xff, 0x98, 0x7e, 0x6c, + 0x46, 0x30, 0x7c, 0x0d, 0x80, 0xda, 0x00, 0xfb, 0xb3, 0x62, 0xe0, 0x4f, 0xa2, 0x7e, 0xdb, 0x85, + 0xa5, 0x09, 0xbe, 0x65, 0x34, 0x1c, 0x42, 0x4b, 0x35, 0xda, 0x76, 0x36, 0x38, 0x38, 0x89, 0xa8, + 0x91, 0xb6, 0x0b, 0x9f, 0x76, 0x13, 0x0d, 0xea, 0xae, 0x4b, 0xbc, 0xba, 0xa7, 0xaf, 0x8a, 0xe1, + 0x1a, 0x03, 0xa0, 0x38, 0x3a, 0x00, 0xb8, 0xac, 0xaf, 0xe2, 0xd7, 0xd0, 0x48, 0xc3, 0x21, 0xb0, + 0x78, 0xe9, 0x4d, 0xda, 0x4a, 0x61, 0x73, 0x29, 0x21, 0xc4, 0x1b, 0xff, 0x10, 0x51, 0x31, 0xf0, + 0x5d, 0x34, 0xc2, 0x3f, 0x87, 0x59, 0x9d, 0xc1, 0x40, 0x1b, 0x0d, 0x17, 0x13, 0x26, 0x12, 0x66, + 0x77, 0xc6, 0x8d, 0x0f, 0x45, 0x72, 0x91, 0xaf, 0x21, 0x90, 0xaa, 0x5f, 0xcf, 0xd0, 0x0d, 0x0f, + 0x05, 0x04, 0xe9, 0x34, 0x5b, 0x7b, 0xd4, 0x95, 0x56, 0x18, 0x31, 0xbf, 0xcf, 0xed, 0x32, 0x3b, + 0x69, 0x1c, 0x8b, 0x27, 0x51, 0x9f, 0x21, 0xde, 0xfd, 0x9c, 0x95, 0x3f, 0xc2, 0xaf, 0x47, 0xe3, + 0x54, 0xf8, 0x32, 0xca, 0xd3, 0x0d, 0x6d, 0xf4, 0xe0, 0x27, 0xae, 0x91, 0x1a, 0x50, 0xa8, 0xdf, + 0x99, 0x45, 0xc3, 0xc2, 0xd7, 0x5c, 0xdf, 0xd7, 0xe7, 0xbc, 0xb4, 0xbb, 0x66, 0x72, 0x3b, 0x58, + 0x80, 0x05, 0x4d, 0xbe, 0x19, 0x88, 0x62, 0x57, 0x4f, 0x10, 0x5c, 0x30, 0xcf, 0xf3, 0x0f, 0xed, + 0xdb, 0xfd, 0x21, 0x88, 0xd2, 0xbf, 0x9e, 0x1f, 0xc8, 0x16, 0x72, 0xaf, 0xe7, 0x07, 0xf2, 0x85, + 0x5e, 0xf0, 0xac, 0x87, 0x68, 0x52, 0xec, 0x84, 0x69, 0xdd, 0x37, 0x57, 0x8f, 0xb8, 0xdd, 0xe0, + 0xc1, 0x46, 0x1d, 0x88, 0xc8, 0xe6, 0x88, 0x1b, 0x11, 0xbe, 0xaf, 0xb2, 0x39, 0x49, 0x60, 0xc0, + 0x65, 0xf3, 0xef, 0x32, 0x48, 0x49, 0x94, 0x4d, 0xe9, 0x90, 0x5e, 0xbe, 0x0f, 0x2e, 0x8d, 0xc1, + 0x37, 0xb3, 0x68, 0xbc, 0x62, 0x79, 0x64, 0x95, 0x9d, 0x7b, 0x8e, 0xf8, 0x54, 0x71, 0x9b, 0xa5, + 0x31, 0xe5, 0x1f, 0xc3, 0xfb, 0xfc, 0xf1, 0xe0, 0x54, 0x19, 0xa2, 0x52, 0x38, 0x89, 0xa5, 0x0f, + 0x30, 0xbd, 0x51, 0x44, 0xc8, 0x47, 0x7c, 0xce, 0x39, 0x1a, 0x42, 0x3e, 0xe2, 0x93, 0xd7, 0x07, + 0x54, 0xc8, 0xff, 0x3d, 0x83, 0x4e, 0x25, 0x54, 0x0e, 0xc9, 0x01, 0x3b, 0x2b, 0x10, 0x72, 0x21, + 0x23, 0x24, 0x07, 0xec, 0xac, 0x40, 0xb4, 0x05, 0xcd, 0x47, 0xe2, 0x65, 0x70, 0xac, 0x5a, 0xaa, + 0x94, 0xa7, 0xb9, 0x54, 0x55, 0xc1, 0x45, 0x8c, 0x82, 0x93, 0xbe, 0x2c, 0x70, 0xbe, 0xb2, 0x4d, + 0xa3, 0x11, 0x71, 0xbe, 0xa2, 0x65, 0xf0, 0x67, 0xd0, 0x60, 0xe9, 0xdd, 0x8e, 0x43, 0x80, 0x2f, + 0x93, 0xf8, 0x87, 0x02, 0xbe, 0x3e, 0x22, 0x89, 0x33, 0xf3, 0x23, 0xa3, 0x14, 0x51, 0xde, 0x21, + 0x43, 0xf5, 0x8b, 0x19, 0x34, 0x91, 0xde, 0x3a, 0xfc, 0x51, 0xd4, 0x4f, 0x4f, 0xb6, 0x25, 0x6d, + 0x91, 0x7f, 0x3a, 0x4b, 0xf9, 0x61, 0x37, 0x49, 0x5d, 0x77, 0xc4, 0x8d, 0xb7, 0x4f, 0x86, 0x5f, + 0x41, 0x43, 0x15, 0xd7, 0xed, 0x10, 0xa7, 0x76, 0xe3, 0x8e, 0x56, 0xe1, 0x67, 0x2a, 0xd8, 0xb3, + 0x9b, 0x00, 0xae, 0xbb, 0x37, 0x22, 0x41, 0x15, 0x44, 0x7a, 0xf5, 0xfb, 0x33, 0xe8, 0x7c, 0xb7, + 0xaf, 0xa2, 0x07, 0xf8, 0x65, 0x62, 0xe9, 0x96, 0xc7, 0x53, 0xeb, 0xf2, 0x23, 0x8a, 0x07, 0x30, + 0xf9, 0x90, 0x11, 0x10, 0xd2, 0x42, 0xec, 0x76, 0x2c, 0x78, 0x8e, 0x67, 0x37, 0x79, 0x00, 0x8b, + 0x14, 0xf2, 0x09, 0xd5, 0x9f, 0x7e, 0x13, 0xf5, 0x2e, 0x59, 0x64, 0xe9, 0x3e, 0x7e, 0x4e, 0x48, + 0xe0, 0xc6, 0x07, 0xda, 0xb8, 0x38, 0x60, 0x00, 0x31, 0xd7, 0xa3, 0x09, 0x69, 0xde, 0x6e, 0x8a, + 0x59, 0xa8, 0xb8, 0x3a, 0x60, 0xb1, 0x0c, 0xc3, 0xcc, 0xf5, 0x68, 0x62, 0xb6, 0xaa, 0x9b, 0x62, + 0x72, 0x25, 0xde, 0xd9, 0x52, 0x29, 0x86, 0xf1, 0x4b, 0xf1, 0x69, 0x60, 0x3e, 0x29, 0x03, 0x51, + 0x74, 0x4f, 0x10, 0xa7, 0x98, 0xeb, 0xd1, 0x92, 0x33, 0x17, 0x0d, 0x8b, 0x86, 0x31, 0xd1, 0x07, + 0x39, 0x11, 0x37, 0xd7, 0xa3, 0x49, 0xb4, 0xf8, 0x85, 0x20, 0xcd, 0xe3, 0xeb, 0xb6, 0x69, 0x45, + 0xbd, 0x2b, 0x05, 0xd4, 0x5c, 0x8f, 0x26, 0x52, 0x0a, 0x95, 0x56, 0x1d, 0x33, 0xc8, 0xc1, 0x16, + 0xad, 0x14, 0x70, 0x42, 0xa5, 0xf0, 0x1b, 0xbf, 0x82, 0x46, 0x02, 0xb7, 0xd5, 0x77, 0x48, 0xc3, + 0xe3, 0x57, 0x22, 0x67, 0x22, 0x85, 0x19, 0x72, 0xae, 0x47, 0x93, 0xa9, 0xf1, 0x65, 0x3f, 0xc1, + 0x3f, 0xbf, 0xeb, 0x18, 0x15, 0xa6, 0x33, 0xf3, 0x5d, 0x2a, 0x25, 0x8e, 0xa7, 0xbd, 0x13, 0xbe, + 0x1d, 0xf0, 0x0b, 0x0c, 0x1c, 0xa9, 0x65, 0xc6, 0x32, 0x68, 0xef, 0x08, 0x0f, 0x47, 0xaf, 0x45, + 0x53, 0x20, 0xf3, 0xc4, 0xda, 0x67, 0x23, 0x25, 0x39, 0x76, 0xae, 0x47, 0x8b, 0xa6, 0x4c, 0x7e, + 0x41, 0x4a, 0xbf, 0xcb, 0xe3, 0xa7, 0x44, 0xa5, 0x4a, 0x51, 0x82, 0x54, 0x21, 0x51, 0xef, 0x6b, + 0xd1, 0x7c, 0xb0, 0x3c, 0x5a, 0xca, 0xd9, 0xe4, 0xac, 0xa1, 0x42, 0xd5, 0x7e, 0xfe, 0xd8, 0x17, + 0xa4, 0xbc, 0x9d, 0x90, 0x1a, 0x3b, 0xa1, 0x6a, 0xdd, 0xd3, 0xc5, 0xaa, 0xd9, 0xf9, 0x52, 0xca, + 0x20, 0x09, 0x09, 0x6e, 0xe2, 0x1d, 0x0a, 0x38, 0xa1, 0x43, 0x59, 0xb6, 0xc9, 0x17, 0xa4, 0x24, + 0x26, 0x3c, 0x83, 0x4d, 0x50, 0xa9, 0x80, 0xa2, 0x95, 0x8a, 0xe9, 0x4e, 0x6e, 0x8a, 0xb9, 0x3d, + 0x94, 0x71, 0xb9, 0x83, 0x42, 0x0c, 0xed, 0x20, 0x21, 0x07, 0x48, 0x11, 0xf2, 0x06, 0x28, 0x18, + 0xc8, 0x87, 0x82, 0x16, 0x4e, 0x57, 0xe7, 0x7a, 0x34, 0xc8, 0x28, 0xa0, 0xb2, 0x8c, 0x14, 0xca, + 0x29, 0xa0, 0x18, 0x0e, 0xf2, 0xa3, 0x3e, 0x24, 0x8d, 0xb9, 0x1e, 0x8d, 0x65, 0xab, 0x78, 0x4e, + 0x88, 0xfd, 0xac, 0x9c, 0x96, 0xa7, 0x88, 0x00, 0x41, 0xa7, 0x88, 0x30, 0x42, 0xf4, 0x6c, 0x3c, + 0x3e, 0xb2, 0x72, 0x46, 0x5e, 0x51, 0xa3, 0xf8, 0xb9, 0x1e, 0x2d, 0x1e, 0x53, 0xf9, 0x05, 0x29, + 0x64, 0xb0, 0x72, 0x36, 0xe2, 0xd2, 0x1c, 0xa2, 0xa8, 0xb8, 0xc4, 0xe0, 0xc2, 0x4b, 0x89, 0x49, + 0xbe, 0x94, 0x73, 0xf2, 0x72, 0x9c, 0x40, 0x32, 0xd7, 0xa3, 0x25, 0xa6, 0x07, 0x9b, 0x8e, 0x05, + 0xee, 0x55, 0x14, 0xf9, 0xdd, 0x32, 0x82, 0x9e, 0xeb, 0xd1, 0x62, 0xa1, 0x7e, 0x6f, 0x8a, 0x11, + 0x73, 0x95, 0xc7, 0xe4, 0x4e, 0x0c, 0x31, 0xb4, 0x13, 0x85, 0xc8, 0xba, 0x37, 0xc5, 0xb0, 0xb2, + 0xca, 0x44, 0xbc, 0x54, 0x38, 0x73, 0x0a, 0xe1, 0x67, 0xb5, 0xe4, 0xd8, 0xab, 0xca, 0xe3, 0x3c, + 0x34, 0x3f, 0x2f, 0x9f, 0x44, 0x33, 0xd7, 0xa3, 0x25, 0xc7, 0x6d, 0xd5, 0x92, 0x83, 0x96, 0x2a, + 0xe7, 0xbb, 0xf1, 0x0c, 0x5a, 0x97, 0x1c, 0xf0, 0x54, 0xef, 0x12, 0x42, 0x52, 0xb9, 0x20, 0xc7, + 0x53, 0x4a, 0x25, 0x9c, 0xeb, 0xd1, 0xba, 0x04, 0xa2, 0xbc, 0x93, 0x12, 0xcf, 0x51, 0xb9, 0x28, + 0x67, 0xe6, 0x48, 0x24, 0x9a, 0xeb, 0xd1, 0x52, 0xa2, 0x41, 0xde, 0x49, 0x09, 0x85, 0xa8, 0x14, + 0xbb, 0xb2, 0x0d, 0xe4, 0x91, 0x12, 0x48, 0x71, 0x29, 0x31, 0x8a, 0xa0, 0xf2, 0x84, 0xac, 0xba, + 0x09, 0x24, 0x54, 0x75, 0x93, 0xe2, 0x0f, 0x2e, 0x25, 0x86, 0xf1, 0x53, 0x9e, 0xec, 0xc2, 0x30, + 0x68, 0x63, 0x62, 0x00, 0xc0, 0xa5, 0xc4, 0x38, 0x7a, 0x8a, 0x2a, 0x33, 0x4c, 0x20, 0xa1, 0x0c, + 0x93, 0x22, 0xf0, 0x2d, 0x25, 0x06, 0xb2, 0x53, 0x9e, 0xea, 0xc2, 0x30, 0x6c, 0x61, 0x52, 0x08, + 0xbc, 0x17, 0xa4, 0x48, 0x72, 0xca, 0x87, 0xe4, 0x79, 0x43, 0x40, 0xd1, 0x79, 0x43, 0x8c, 0x39, + 0x37, 0x1d, 0x8b, 0x95, 0xa3, 0x7c, 0x58, 0x1e, 0xe6, 0x11, 0x34, 0x1d, 0xe6, 0xd1, 0xe8, 0x3a, + 0xd3, 0xb1, 0x98, 0x21, 0xca, 0xa5, 0x34, 0x26, 0x80, 0x96, 0x99, 0xb0, 0x28, 0x23, 0x95, 0x84, + 0xa0, 0x15, 0xca, 0xd3, 0xb2, 0xcd, 0x5d, 0x8c, 0x60, 0xae, 0x47, 0x4b, 0x08, 0x75, 0xa1, 0x25, + 0x7b, 0x68, 0x2a, 0x97, 0xe5, 0x61, 0x9b, 0x44, 0x43, 0x87, 0x6d, 0xa2, 0x77, 0xe7, 0x7c, 0x92, + 0x7d, 0xad, 0x72, 0x45, 0xde, 0x98, 0xc5, 0x29, 0xe8, 0xc6, 0x2c, 0xc1, 0x2e, 0x57, 0x4b, 0xf6, + 0x1a, 0x54, 0x9e, 0xe9, 0xda, 0x42, 0xa0, 0x49, 0x68, 0x21, 0x73, 0xa2, 0x0b, 0xf7, 0x4e, 0x77, + 0xda, 0x4d, 0x5b, 0x37, 0x94, 0x8f, 0x24, 0xee, 0x9d, 0x18, 0x52, 0xd8, 0x3b, 0x31, 0x00, 0x5d, + 0xe5, 0x45, 0xfb, 0x53, 0xe5, 0xaa, 0xbc, 0xca, 0x8b, 0x38, 0xba, 0xca, 0x4b, 0xb6, 0xaa, 0xd3, + 0x31, 0x5b, 0x4d, 0xe5, 0x59, 0x59, 0x01, 0x22, 0x68, 0xaa, 0x00, 0x51, 0xeb, 0xce, 0xb7, 0xd3, + 0xad, 0x1b, 0x95, 0x49, 0xe0, 0xf6, 0x44, 0x90, 0x01, 0x3e, 0x85, 0x6e, 0xae, 0x47, 0x4b, 0xb7, + 0x90, 0xac, 0x24, 0x18, 0x2b, 0x2a, 0xd7, 0x64, 0x05, 0x8b, 0x11, 0x50, 0x05, 0x8b, 0x9b, 0x38, + 0x56, 0x12, 0xac, 0x0d, 0x95, 0x8f, 0xa6, 0xb2, 0x0a, 0xbe, 0x39, 0xc1, 0x46, 0xf1, 0xa6, 0x68, + 0x2e, 0xa8, 0x3c, 0x27, 0x2f, 0x76, 0x21, 0x86, 0x2e, 0x76, 0x82, 0x59, 0xe1, 0x4d, 0xd1, 0x50, + 0x4e, 0xb9, 0x1e, 0x2f, 0x15, 0x2e, 0x91, 0x82, 0x41, 0x9d, 0x96, 0x6c, 0x5f, 0xa6, 0xdc, 0x90, + 0xb5, 0x2e, 0x89, 0x86, 0x6a, 0x5d, 0xa2, 0x6d, 0xda, 0x6c, 0xdc, 0x4c, 0x4c, 0xb9, 0x19, 0xbd, + 0x4b, 0x90, 0xf1, 0x74, 0xe7, 0x13, 0x33, 0x2d, 0x7b, 0x2d, 0x1a, 0x3e, 0x40, 0xf9, 0x58, 0xe4, + 0x31, 0x43, 0xc2, 0xd2, 0xfd, 0x6d, 0x24, 0xdc, 0xc0, 0x6b, 0x51, 0x8f, 0x7b, 0xe5, 0xf9, 0x64, + 0x0e, 0x81, 0xae, 0x44, 0x3d, 0xf4, 0x5f, 0x8b, 0x3a, 0xa9, 0x2b, 0x2f, 0x24, 0x73, 0x08, 0xa4, + 0x1b, 0x75, 0x6a, 0x7f, 0x4e, 0x08, 0x9b, 0xa7, 0x7c, 0x5c, 0xde, 0x3a, 0x06, 0x08, 0xba, 0x75, + 0x0c, 0x83, 0xeb, 0x3d, 0x27, 0x84, 0x9b, 0x53, 0x5e, 0x8c, 0x15, 0x09, 0x1a, 0x2b, 0x04, 0xa5, + 0x7b, 0x4e, 0x08, 0xd3, 0xa6, 0xbc, 0x14, 0x2b, 0x12, 0xb4, 0x4e, 0x08, 0xe6, 0x66, 0x74, 0xf3, + 0xc3, 0x51, 0x3e, 0x21, 0x5f, 0x71, 0xa4, 0x53, 0xce, 0xf5, 0x68, 0xdd, 0xfc, 0x79, 0xde, 0x4e, + 0x37, 0xba, 0x53, 0x5e, 0x96, 0x87, 0x70, 0x1a, 0x1d, 0x1d, 0xc2, 0xa9, 0x86, 0x7b, 0xaf, 0x44, + 0x7c, 0x72, 0x95, 0x57, 0xe4, 0x29, 0x4e, 0x42, 0xd2, 0x29, 0x2e, 0xea, 0xc1, 0x2b, 0x39, 0x9b, + 0x2a, 0x9f, 0x94, 0xa7, 0x38, 0x11, 0x47, 0xa7, 0x38, 0xc9, 0x31, 0x75, 0x3a, 0xe6, 0x03, 0xa9, + 0xbc, 0x2a, 0x4f, 0x71, 0x11, 0x34, 0x9d, 0xe2, 0xa2, 0x5e, 0x93, 0xaf, 0x44, 0x5c, 0x01, 0x95, + 0xd7, 0x92, 0xdb, 0x0f, 0x48, 0xb1, 0xfd, 0xcc, 0x71, 0x50, 0x4b, 0xf6, 0x69, 0x53, 0x4a, 0xf2, + 0xf8, 0x4d, 0xa2, 0xa1, 0xe3, 0x37, 0xd1, 0x1f, 0x6e, 0x29, 0x31, 0x2f, 0xa6, 0x32, 0xd5, 0xe5, + 0xe0, 0x10, 0x6e, 0x45, 0x92, 0x32, 0x6a, 0x8a, 0x67, 0x64, 0x76, 0x10, 0x9a, 0x4e, 0x39, 0x23, + 0xfb, 0xc7, 0xa0, 0x08, 0x3d, 0x9d, 0x5d, 0x63, 0x36, 0x60, 0x4a, 0x59, 0x9e, 0x5d, 0x63, 0x04, + 0x74, 0x76, 0x8d, 0x5b, 0x8e, 0xcd, 0xa2, 0x02, 0xd7, 0x22, 0x66, 0xda, 0x66, 0x5a, 0xab, 0xca, + 0x4c, 0xc4, 0xa5, 0x24, 0x82, 0xa7, 0xb3, 0x53, 0x14, 0x06, 0xeb, 0x35, 0x83, 0x4d, 0x37, 0xcd, + 0xf6, 0x8a, 0xad, 0x3b, 0x46, 0x8d, 0x58, 0x86, 0x32, 0x1b, 0x59, 0xaf, 0x13, 0x68, 0x60, 0xbd, + 0x4e, 0x80, 0x83, 0xd3, 0x7b, 0x04, 0xae, 0x91, 0x06, 0x31, 0x1f, 0x10, 0xe5, 0x16, 0xb0, 0x2d, + 0xa6, 0xb1, 0xe5, 0x64, 0x73, 0x3d, 0x5a, 0x1a, 0x07, 0xba, 0x57, 0x5f, 0xd8, 0xac, 0xbd, 0x31, + 0x1f, 0xb8, 0x51, 0x56, 0x1d, 0xd2, 0xd6, 0x1d, 0xa2, 0xcc, 0xc9, 0x7b, 0xf5, 0x44, 0x22, 0xba, + 0x57, 0x4f, 0x44, 0xc4, 0xd9, 0xfa, 0x63, 0xa1, 0xd2, 0x8d, 0x6d, 0x38, 0x22, 0x92, 0x4b, 0xd3, + 0xd9, 0x49, 0x46, 0x50, 0x01, 0xcd, 0xdb, 0xd6, 0x2a, 0xdc, 0x54, 0xbc, 0x2e, 0xcf, 0x4e, 0xe9, + 0x94, 0x74, 0x76, 0x4a, 0xc7, 0x52, 0x55, 0x97, 0xb1, 0x6c, 0x0c, 0xde, 0x96, 0x55, 0x3d, 0x81, + 0x84, 0xaa, 0x7a, 0x02, 0x38, 0xce, 0x50, 0x23, 0x2e, 0xf1, 0x94, 0xf9, 0x6e, 0x0c, 0x81, 0x24, + 0xce, 0x10, 0xc0, 0x71, 0x86, 0xb3, 0xc4, 0x6b, 0xac, 0x29, 0x0b, 0xdd, 0x18, 0x02, 0x49, 0x9c, + 0x21, 0x80, 0xe9, 0x61, 0x53, 0x06, 0x4f, 0x75, 0x9a, 0xeb, 0x7e, 0x9f, 0x2d, 0xca, 0x87, 0xcd, + 0x54, 0x42, 0x7a, 0xd8, 0x4c, 0x45, 0xe2, 0xef, 0xdf, 0xb5, 0x8d, 0xa2, 0xb2, 0x04, 0x15, 0x4e, + 0x86, 0xfb, 0x82, 0xdd, 0x94, 0x9a, 0xeb, 0xd1, 0x76, 0x6b, 0x03, 0xf9, 0x91, 0xc0, 0x94, 0x48, + 0xa9, 0x42, 0x55, 0x63, 0xc1, 0x5d, 0x05, 0x03, 0xcf, 0xf5, 0x68, 0x81, 0xb1, 0xd1, 0x0b, 0x68, + 0x08, 0x3e, 0xaa, 0x62, 0x99, 0x5e, 0x79, 0x4a, 0x79, 0x43, 0x3e, 0x32, 0x09, 0x28, 0x7a, 0x64, + 0x12, 0x7e, 0xd2, 0x49, 0x1c, 0x7e, 0xb2, 0x29, 0xa6, 0x3c, 0xa5, 0x68, 0xf2, 0x24, 0x2e, 0x21, + 0xe9, 0x24, 0x2e, 0x01, 0x82, 0x7a, 0xcb, 0x8e, 0xdd, 0x2e, 0x4f, 0x29, 0xb5, 0x84, 0x7a, 0x19, + 0x2a, 0xa8, 0x97, 0xfd, 0x0c, 0xea, 0xad, 0xad, 0x75, 0xbc, 0x32, 0xfd, 0xc6, 0xe5, 0x84, 0x7a, + 0x7d, 0x64, 0x50, 0xaf, 0x0f, 0xa0, 0x53, 0x21, 0x00, 0xaa, 0x8e, 0x4d, 0x27, 0xed, 0xdb, 0x66, + 0xb3, 0xa9, 0xdc, 0x91, 0xa7, 0xc2, 0x28, 0x9e, 0x4e, 0x85, 0x51, 0x18, 0xdd, 0x7a, 0xb2, 0x56, + 0x91, 0x95, 0xce, 0xaa, 0x72, 0x57, 0xde, 0x7a, 0x86, 0x18, 0xba, 0xf5, 0x0c, 0x7f, 0xc1, 0xe9, + 0x82, 0xfe, 0xd2, 0xc8, 0x7d, 0x87, 0xb8, 0x6b, 0xca, 0xbd, 0xc8, 0xe9, 0x42, 0xc0, 0xc1, 0xe9, + 0x42, 0xf8, 0x8d, 0x57, 0xd1, 0xe3, 0xd2, 0x42, 0xe3, 0xbf, 0x3d, 0xd5, 0x88, 0xee, 0x34, 0xd6, + 0x94, 0x37, 0x81, 0xd5, 0x53, 0x89, 0x4b, 0x95, 0x4c, 0x3a, 0xd7, 0xa3, 0x75, 0xe3, 0x04, 0xc7, + 0xf2, 0x37, 0xe6, 0x59, 0x6c, 0x1b, 0xad, 0x3a, 0xed, 0x1f, 0x42, 0xdf, 0x8a, 0x1c, 0xcb, 0xe3, + 0x24, 0x70, 0x2c, 0x8f, 0x83, 0x71, 0x1b, 0x5d, 0x8c, 0x1c, 0xd5, 0x16, 0xf4, 0x26, 0x3d, 0x97, + 0x10, 0xa3, 0xaa, 0x37, 0xd6, 0x89, 0xa7, 0x7c, 0x0a, 0x78, 0x5f, 0x4a, 0x39, 0xf0, 0x45, 0xa8, + 0xe7, 0x7a, 0xb4, 0x1d, 0xf8, 0x61, 0x95, 0x65, 0x5e, 0x54, 0x3e, 0x2d, 0xdf, 0x6f, 0x52, 0xd8, + 0x5c, 0x8f, 0xc6, 0xb2, 0x32, 0xbe, 0x8d, 0x94, 0x3b, 0xed, 0x55, 0x47, 0x37, 0x08, 0xdb, 0x68, + 0xc1, 0xde, 0x8d, 0x6f, 0x40, 0x3f, 0x23, 0xef, 0xd2, 0xd2, 0xe8, 0xe8, 0x2e, 0x2d, 0x0d, 0x47, + 0x15, 0x55, 0x0a, 0xe3, 0xaa, 0x7c, 0x56, 0x56, 0x54, 0x09, 0x49, 0x15, 0x55, 0x0e, 0xfa, 0xfa, + 0x26, 0x3a, 0x1b, 0x9c, 0xe7, 0xf9, 0xfa, 0xcb, 0x3a, 0x4d, 0x79, 0x1b, 0xf8, 0x5c, 0x8c, 0x3d, + 0x06, 0x48, 0x54, 0x73, 0x3d, 0x5a, 0x4a, 0x79, 0xba, 0xe2, 0xc6, 0x22, 0x94, 0xf3, 0xed, 0xc5, + 0x77, 0xc8, 0x2b, 0x6e, 0x0a, 0x19, 0x5d, 0x71, 0x53, 0x50, 0x89, 0xcc, 0xb9, 0x50, 0xf5, 0x1d, + 0x98, 0x07, 0x32, 0x4d, 0xe3, 0x90, 0xc8, 0x9c, 0xef, 0xd4, 0x56, 0x76, 0x60, 0x1e, 0xec, 0xd6, + 0xd2, 0x38, 0xe0, 0xcb, 0xa8, 0xaf, 0x56, 0x5b, 0xd0, 0x3a, 0x96, 0xd2, 0x88, 0xd8, 0x80, 0x01, + 0x74, 0xae, 0x47, 0xe3, 0x78, 0xba, 0x0d, 0x9a, 0x69, 0xea, 0xae, 0x67, 0x36, 0x5c, 0x18, 0x31, + 0xfe, 0x08, 0x31, 0xe4, 0x6d, 0x50, 0x12, 0x0d, 0xdd, 0x06, 0x25, 0xc1, 0xe9, 0x7e, 0x71, 0x5a, + 0x77, 0x5d, 0xdd, 0x32, 0x1c, 0x7d, 0x0a, 0x96, 0x09, 0x12, 0xb1, 0x94, 0x97, 0xb0, 0x74, 0xbf, + 0x28, 0x43, 0xe0, 0xf2, 0xdd, 0x87, 0xf8, 0xdb, 0x9c, 0xfb, 0x91, 0xcb, 0xf7, 0x08, 0x1e, 0x2e, + 0xdf, 0x23, 0x30, 0xd8, 0x77, 0xfa, 0x30, 0x8d, 0xac, 0x9a, 0x90, 0x27, 0x79, 0x35, 0xb2, 0xef, + 0x8c, 0x12, 0xc0, 0xbe, 0x33, 0x0a, 0x94, 0x9a, 0xe4, 0x2f, 0xb7, 0x6b, 0x29, 0x4d, 0x0a, 0x57, + 0xd9, 0x58, 0x19, 0xba, 0x7e, 0x87, 0x83, 0xa3, 0xbc, 0x69, 0xe9, 0x2d, 0xbb, 0x3c, 0xe5, 0x4b, + 0xdd, 0x94, 0xd7, 0xef, 0x54, 0x42, 0xba, 0x7e, 0xa7, 0x22, 0xe9, 0xec, 0xea, 0x1f, 0xb4, 0xd6, + 0x74, 0x87, 0x18, 0x41, 0xf6, 0x50, 0x76, 0x34, 0x7c, 0x47, 0x9e, 0x5d, 0xbb, 0x90, 0xd2, 0xd9, + 0xb5, 0x0b, 0x9a, 0x6e, 0xf2, 0x92, 0xd1, 0x1a, 0xd1, 0x0d, 0x65, 0x5d, 0xde, 0xe4, 0xa5, 0x53, + 0xd2, 0x4d, 0x5e, 0x3a, 0x36, 0xfd, 0x73, 0xee, 0x39, 0xa6, 0x47, 0x94, 0xe6, 0x6e, 0x3e, 0x07, + 0x48, 0xd3, 0x3f, 0x07, 0xd0, 0xf4, 0x40, 0x18, 0xed, 0x90, 0x96, 0x7c, 0x20, 0x8c, 0x77, 0x43, + 0xb4, 0x04, 0xdd, 0xb1, 0x70, 0x87, 0x09, 0xc5, 0x92, 0x77, 0x2c, 0x1c, 0x4c, 0x77, 0x2c, 0xa1, + 0x4b, 0x85, 0x64, 0xa0, 0xaf, 0xd8, 0xf2, 0x1a, 0x2a, 0xe2, 0xe8, 0x1a, 0x2a, 0x19, 0xf3, 0xbf, + 0x20, 0x59, 0xcf, 0x2a, 0x6d, 0x79, 0xd7, 0x21, 0xa0, 0xe8, 0xae, 0x43, 0xb4, 0xb3, 0x9d, 0x46, + 0x63, 0xf0, 0x0a, 0xae, 0x75, 0x82, 0x77, 0x9c, 0xcf, 0xc9, 0x9f, 0x19, 0x41, 0xd3, 0xcf, 0x8c, + 0x80, 0x24, 0x26, 0x7c, 0xda, 0x72, 0x52, 0x98, 0x84, 0xf7, 0x83, 0x11, 0x10, 0x9e, 0x47, 0xb8, + 0x56, 0x5a, 0x98, 0xaf, 0x18, 0x55, 0xf1, 0x89, 0xcc, 0x95, 0x6f, 0x60, 0xe3, 0x14, 0x73, 0x3d, + 0x5a, 0x42, 0x39, 0xfc, 0x0e, 0x3a, 0xcf, 0xa1, 0xdc, 0x1b, 0x0e, 0x52, 0xb0, 0x19, 0xc1, 0x82, + 0xe0, 0xc9, 0xd6, 0x19, 0xdd, 0x68, 0xe7, 0x7a, 0xb4, 0xae, 0xbc, 0xd2, 0xeb, 0xe2, 0xeb, 0x43, + 0x67, 0x37, 0x75, 0x05, 0x8b, 0x44, 0x57, 0x5e, 0xe9, 0x75, 0x71, 0xb9, 0x3f, 0xd8, 0x4d, 0x5d, + 0x41, 0x27, 0x74, 0xe5, 0x85, 0x5d, 0x54, 0xec, 0x86, 0x2f, 0x35, 0x9b, 0xca, 0x06, 0x54, 0xf7, + 0xf4, 0x6e, 0xaa, 0x2b, 0xc1, 0x86, 0x73, 0x27, 0x8e, 0x74, 0x96, 0x5e, 0x6a, 0x13, 0xab, 0x26, + 0x2d, 0x40, 0x0f, 0xe5, 0x59, 0x3a, 0x46, 0x40, 0x67, 0xe9, 0x18, 0x90, 0x0e, 0x28, 0xd1, 0x08, + 0x5b, 0xd9, 0x94, 0x07, 0x94, 0x88, 0xa3, 0x03, 0x4a, 0x32, 0xd8, 0x5e, 0x42, 0xa7, 0x96, 0xd6, + 0x3d, 0xdd, 0xdf, 0x41, 0xba, 0xbc, 0x2b, 0xdf, 0x8d, 0x3c, 0x32, 0xc5, 0x49, 0xe0, 0x91, 0x29, + 0x0e, 0xa6, 0x63, 0x84, 0x82, 0x6b, 0x9b, 0x56, 0x63, 0x56, 0x37, 0x9b, 0x1d, 0x87, 0x28, 0xff, + 0x9f, 0x3c, 0x46, 0x22, 0x68, 0x3a, 0x46, 0x22, 0x20, 0xba, 0x40, 0x53, 0x50, 0xc9, 0x75, 0xcd, + 0x55, 0x8b, 0x9f, 0x2b, 0x3b, 0x4d, 0x4f, 0xf9, 0xff, 0xe5, 0x05, 0x3a, 0x89, 0x86, 0x2e, 0xd0, + 0x49, 0x70, 0xb8, 0x75, 0x4a, 0x48, 0x4f, 0xa8, 0xfc, 0xa5, 0xc8, 0xad, 0x53, 0x02, 0x0d, 0xdc, + 0x3a, 0x25, 0xa5, 0x36, 0x9c, 0x45, 0x05, 0xb6, 0x27, 0x9b, 0x37, 0x83, 0xb7, 0xea, 0xbf, 0x2c, + 0xaf, 0x8f, 0x51, 0x3c, 0x5d, 0x1f, 0xa3, 0x30, 0x99, 0x0f, 0xef, 0x82, 0xbf, 0x92, 0xc6, 0x27, + 0x90, 0x7f, 0xac, 0x0c, 0xbe, 0x25, 0xf2, 0xe1, 0x23, 0xe5, 0x3b, 0x33, 0x69, 0x8c, 0x82, 0xe1, + 0x11, 0x2b, 0x24, 0x33, 0xd2, 0xc8, 0x03, 0x93, 0x6c, 0x28, 0x9f, 0x4f, 0x65, 0xc4, 0x08, 0x64, + 0x46, 0x0c, 0x86, 0xdf, 0x42, 0x67, 0x43, 0xd8, 0x02, 0x69, 0xad, 0x04, 0x33, 0xd3, 0x77, 0x65, + 0xe4, 0x6d, 0x70, 0x32, 0x19, 0xdd, 0x06, 0x27, 0x63, 0x92, 0x58, 0x73, 0xd1, 0xfd, 0xd5, 0x1d, + 0x58, 0x07, 0x12, 0x4c, 0x61, 0x90, 0xc4, 0x9a, 0x4b, 0xf3, 0xbb, 0x77, 0x60, 0x1d, 0xc8, 0x34, + 0x85, 0x01, 0xfe, 0x81, 0x0c, 0xba, 0x94, 0x8c, 0x2a, 0x35, 0x9b, 0xb3, 0xb6, 0x13, 0xe2, 0x94, + 0xef, 0xc9, 0xc8, 0x17, 0x0d, 0xbb, 0x2b, 0x36, 0xd7, 0xa3, 0xed, 0xb2, 0x02, 0xfc, 0x49, 0x34, + 0x52, 0xea, 0x18, 0xa6, 0x07, 0x0f, 0x6f, 0x74, 0xe3, 0xfc, 0xbd, 0x99, 0xc8, 0x11, 0x47, 0xc4, + 0xc2, 0x11, 0x47, 0x04, 0xe0, 0xd7, 0xd1, 0x78, 0x8d, 0x34, 0x3a, 0x8e, 0xe9, 0x6d, 0x6a, 0x90, + 0x7a, 0x92, 0xf2, 0xf8, 0xbe, 0x8c, 0x3c, 0x89, 0xc5, 0x28, 0xe8, 0x24, 0x16, 0x03, 0x62, 0x82, + 0x26, 0x66, 0x1e, 0x7a, 0xc4, 0xb1, 0xf4, 0x26, 0x54, 0x52, 0xf3, 0x6c, 0x47, 0x5f, 0x25, 0x33, + 0x96, 0xbe, 0xd2, 0x24, 0xca, 0x17, 0x33, 0xf2, 0xbe, 0x2a, 0x9d, 0x94, 0xee, 0xab, 0xd2, 0xb1, + 0x78, 0x0d, 0x3d, 0x9e, 0x84, 0x2d, 0x9b, 0x2e, 0xd4, 0xf3, 0xa5, 0x8c, 0xbc, 0xb1, 0xea, 0x42, + 0x4b, 0x37, 0x56, 0x5d, 0xd0, 0x10, 0x9e, 0x3b, 0xc9, 0x2f, 0x44, 0xf9, 0xd1, 0x8c, 0x7c, 0xc9, + 0x98, 0x48, 0x35, 0xd7, 0xa3, 0xa5, 0xb8, 0x95, 0xdc, 0x4d, 0xf1, 0xa9, 0x50, 0x7e, 0xac, 0x3b, + 0xdf, 0x40, 0xe9, 0x53, 0x5c, 0x32, 0xee, 0xa6, 0xf8, 0x23, 0x28, 0x3f, 0xde, 0x9d, 0x6f, 0x68, + 0x17, 0x91, 0xec, 0xce, 0x50, 0x4f, 0xb7, 0xe5, 0x57, 0x7e, 0x22, 0x23, 0x9f, 0xd3, 0xd3, 0x08, + 0xe9, 0x39, 0x3d, 0xd5, 0x21, 0xe0, 0xf5, 0x04, 0x8b, 0x7a, 0xe5, 0x27, 0x23, 0x5a, 0x18, 0xa3, + 0xa0, 0x5a, 0x18, 0x37, 0xc4, 0x7f, 0x3d, 0xc1, 0x70, 0x5c, 0xf9, 0x7b, 0xe9, 0xbc, 0x02, 0xa1, + 0x26, 0xd8, 0x9b, 0xbf, 0x9e, 0x60, 0x1f, 0xad, 0xfc, 0xfd, 0x74, 0x5e, 0xe1, 0xf3, 0x6a, 0xdc, + 0xac, 0x9a, 0x4e, 0x48, 0x1d, 0xcf, 0x66, 0x9c, 0x25, 0x6d, 0xfa, 0xd9, 0xe8, 0x84, 0x94, 0x48, + 0x06, 0x13, 0x52, 0x22, 0x26, 0x89, 0x35, 0xff, 0xee, 0x9f, 0xdb, 0x81, 0xb5, 0x30, 0x8d, 0x26, + 0x62, 0x92, 0x58, 0x73, 0x31, 0x7c, 0x65, 0x07, 0xd6, 0xc2, 0x34, 0x9a, 0x88, 0xc1, 0x9f, 0x41, + 0xe7, 0x42, 0xcc, 0x5d, 0xe2, 0xb8, 0x61, 0xd7, 0xff, 0x7c, 0x46, 0xbe, 0x4a, 0x48, 0xa1, 0x9b, + 0xeb, 0xd1, 0xd2, 0x58, 0x24, 0x72, 0xe7, 0x42, 0xf9, 0x85, 0x9d, 0xb8, 0x87, 0xb7, 0x20, 0x29, + 0xa8, 0x44, 0xee, 0x5c, 0x2e, 0xbf, 0xb8, 0x13, 0xf7, 0xf0, 0x1a, 0x24, 0x05, 0x35, 0xd5, 0x8f, + 0x7a, 0x61, 0x6f, 0xa7, 0xfe, 0x68, 0x06, 0x0d, 0xd7, 0x3c, 0x87, 0xe8, 0x2d, 0xee, 0x97, 0x3c, + 0x81, 0x06, 0x98, 0x91, 0x84, 0x6f, 0xa7, 0xac, 0x05, 0xbf, 0xf1, 0x25, 0x34, 0x3a, 0xaf, 0xbb, + 0x1e, 0x94, 0xac, 0x58, 0x06, 0x79, 0x08, 0x06, 0xc2, 0x39, 0x2d, 0x02, 0xc5, 0xf3, 0x8c, 0x8e, + 0x95, 0x83, 0x80, 0x10, 0xb9, 0x1d, 0xdd, 0x71, 0x07, 0xde, 0xdb, 0x2a, 0xf6, 0x80, 0xf7, 0x6d, + 0xa4, 0xac, 0xfa, 0xf5, 0x0c, 0x8a, 0x99, 0x6f, 0x3c, 0xba, 0xff, 0xc0, 0x12, 0x1a, 0x8b, 0x04, + 0x21, 0xe1, 0x56, 0xce, 0xbb, 0x8c, 0x51, 0x12, 0x2d, 0x8d, 0x9f, 0x0e, 0xac, 0x6b, 0xef, 0x68, + 0xf3, 0xdc, 0xd5, 0xba, 0x7f, 0x7b, 0xab, 0x98, 0xeb, 0x38, 0x4d, 0x4d, 0x40, 0x71, 0x57, 0xc0, + 0x7f, 0x58, 0x08, 0x23, 0x2c, 0xe0, 0x4b, 0xdc, 0x99, 0x21, 0x13, 0x3a, 0x68, 0x47, 0xd2, 0x69, + 0x30, 0xe7, 0x85, 0x4f, 0xa2, 0xe1, 0x4a, 0xab, 0x4d, 0x1c, 0xd7, 0xb6, 0x74, 0xcf, 0xf6, 0xd3, + 0xf6, 0x81, 0xf3, 0xae, 0x29, 0xc0, 0x45, 0x87, 0x52, 0x91, 0x1e, 0x5f, 0xf1, 0xb3, 0x54, 0xe7, + 0x20, 0xb6, 0xc5, 0xa9, 0x84, 0x2c, 0xd5, 0x7e, 0xae, 0xe9, 0x2b, 0xa8, 0xf7, 0x8e, 0xab, 0x83, + 0x1d, 0x76, 0x40, 0xda, 0xa1, 0x00, 0x91, 0x14, 0x28, 0xf0, 0x55, 0xd4, 0x07, 0xe7, 0x56, 0x57, + 0xe9, 0x05, 0x5a, 0x70, 0x1b, 0x6f, 0x02, 0x44, 0x74, 0xd2, 0x65, 0x34, 0xf8, 0x36, 0x2a, 0x84, + 0x97, 0x72, 0x90, 0x68, 0xd2, 0x8f, 0xb1, 0x09, 0xa9, 0x2d, 0xd6, 0x03, 0x1c, 0xcb, 0x50, 0x29, + 0xb2, 0x88, 0x15, 0xc4, 0x73, 0x68, 0x2c, 0x84, 0x51, 0x11, 0xf9, 0xb1, 0x7d, 0x21, 0xb5, 0x8b, + 0xc0, 0x8b, 0x8a, 0x53, 0x64, 0x15, 0x2d, 0x86, 0x2b, 0xa8, 0xdf, 0xf7, 0x19, 0x1f, 0xd8, 0x51, + 0x49, 0x4f, 0x71, 0x9f, 0xf1, 0x7e, 0xd1, 0x5b, 0xdc, 0x2f, 0x8f, 0x67, 0xd1, 0xa8, 0x66, 0x77, + 0x3c, 0xb2, 0x6c, 0xf3, 0x3b, 0x47, 0x1e, 0xfc, 0x11, 0xda, 0xe4, 0x50, 0x4c, 0xdd, 0xb3, 0xfd, + 0xcc, 0x20, 0x62, 0x86, 0x0a, 0xb9, 0x14, 0x5e, 0x44, 0xe3, 0xb1, 0xeb, 0x4b, 0x31, 0x5f, 0x87, + 0xf0, 0x79, 0x71, 0x66, 0xf1, 0xa2, 0xf8, 0x7b, 0x33, 0xa8, 0x6f, 0xd9, 0xd1, 0x4d, 0xcf, 0xe5, + 0x26, 0xdc, 0x67, 0x26, 0x37, 0x1c, 0xbd, 0x4d, 0xf5, 0x63, 0x12, 0x82, 0x97, 0xdc, 0xd5, 0x9b, + 0x1d, 0xe2, 0x4e, 0xdd, 0xa3, 0x5f, 0xf7, 0xef, 0xb7, 0x8a, 0x9f, 0xd8, 0x43, 0xf6, 0xf0, 0x6b, + 0x01, 0x27, 0x56, 0x03, 0x55, 0x01, 0x0f, 0xfe, 0x12, 0x55, 0x80, 0xe1, 0xf0, 0x22, 0x42, 0xfc, + 0x53, 0x4b, 0xed, 0x36, 0xb7, 0x07, 0x17, 0x8c, 0x5d, 0x7d, 0x0c, 0x53, 0xec, 0x40, 0x60, 0x7a, + 0x5b, 0x4c, 0x57, 0x2a, 0x70, 0xa0, 0x5a, 0xb0, 0xcc, 0x5b, 0xe4, 0x8b, 0x69, 0x24, 0x94, 0xb8, + 0xdf, 0xd8, 0x04, 0x21, 0x45, 0x8b, 0xe1, 0x15, 0x34, 0xc6, 0xf9, 0x06, 0xd1, 0x18, 0x47, 0xe5, + 0x59, 0x21, 0x82, 0x66, 0x4a, 0x1b, 0xb4, 0xd1, 0xe0, 0x60, 0xb1, 0x8e, 0x48, 0x09, 0x3c, 0x15, + 0x06, 0x8b, 0x87, 0xdc, 0xa8, 0xca, 0x18, 0x68, 0xec, 0xf9, 0xed, 0xad, 0xa2, 0xe2, 0x97, 0x67, + 0x29, 0x55, 0x93, 0x12, 0xa7, 0x40, 0x11, 0x91, 0x07, 0xd3, 0xfa, 0x42, 0x02, 0x8f, 0xa8, 0xce, + 0xcb, 0x45, 0xf0, 0x34, 0x1a, 0x09, 0xcc, 0xd1, 0xee, 0xdc, 0xa9, 0x94, 0xc1, 0xe0, 0x9c, 0x27, + 0x01, 0x8d, 0x04, 0x7a, 0x14, 0x99, 0x48, 0x65, 0x04, 0xcf, 0x14, 0x66, 0x81, 0x1e, 0xf1, 0x4c, + 0x69, 0x27, 0x78, 0xa6, 0x54, 0xf1, 0x2b, 0x68, 0xa8, 0x74, 0xaf, 0xc6, 0x3d, 0x6e, 0x5c, 0xe5, + 0x54, 0x18, 0x61, 0x17, 0x72, 0xe7, 0x70, 0xef, 0x1c, 0xb1, 0xe9, 0x22, 0x3d, 0x9e, 0x41, 0xa3, + 0xd2, 0x8b, 0x96, 0xab, 0x9c, 0x06, 0x0e, 0x2c, 0x7d, 0x29, 0x60, 0xea, 0x3c, 0x83, 0xae, 0x94, + 0x20, 0x48, 0x2e, 0x44, 0xb5, 0x86, 0x6e, 0xbf, 0x9b, 0x4d, 0x7b, 0x43, 0x23, 0xe0, 0xdc, 0x03, + 0xe6, 0xeb, 0x03, 0x4c, 0x6b, 0x0c, 0x8e, 0xaa, 0x3b, 0x0c, 0x27, 0xa5, 0x6f, 0x92, 0x8b, 0xe1, + 0x77, 0x10, 0x86, 0xf8, 0xa6, 0xc4, 0xf0, 0x2f, 0x38, 0x2a, 0x65, 0x57, 0x39, 0x0b, 0x41, 0x9c, + 0x70, 0xd4, 0xbb, 0xac, 0x52, 0x9e, 0xba, 0xc4, 0xa7, 0x8f, 0x8b, 0x3a, 0x2b, 0x55, 0x0f, 0xb2, + 0xd7, 0x9a, 0x86, 0xd8, 0xe2, 0x04, 0xae, 0x78, 0x03, 0x9d, 0xab, 0x3a, 0xe4, 0x81, 0x69, 0x77, + 0x5c, 0x7f, 0xf9, 0xf0, 0xe7, 0xad, 0x73, 0x3b, 0xce, 0x5b, 0x4f, 0xf2, 0x8a, 0xcf, 0xb4, 0x1d, + 0xf2, 0xa0, 0xee, 0x87, 0xee, 0x91, 0x62, 0x5e, 0xa4, 0x71, 0xa7, 0xe2, 0x02, 0xc7, 0x26, 0x0e, + 0x37, 0x89, 0xab, 0x28, 0xe1, 0x54, 0xcb, 0xfc, 0xb4, 0xcc, 0x00, 0x27, 0x8a, 0x2b, 0x52, 0x0c, + 0x6b, 0x08, 0xdf, 0x9a, 0xf6, 0x2f, 0xbb, 0x4a, 0x8d, 0x86, 0xdd, 0xb1, 0x3c, 0x57, 0x79, 0x0c, + 0x98, 0xa9, 0x54, 0x2c, 0xab, 0x8d, 0x20, 0x8c, 0x57, 0x5d, 0xe7, 0x78, 0x51, 0x2c, 0xf1, 0xd2, + 0x78, 0x1e, 0x15, 0xaa, 0x8e, 0xf9, 0x40, 0xf7, 0xc8, 0x6d, 0xb2, 0x59, 0xb5, 0x9b, 0x66, 0x63, + 0x13, 0xac, 0xe8, 0xf9, 0x54, 0xd9, 0x66, 0xb8, 0xfa, 0x3a, 0xd9, 0xac, 0xb7, 0x01, 0x2b, 0x2e, + 0x2b, 0xd1, 0x92, 0x62, 0x58, 0x9d, 0xc7, 0x77, 0x17, 0x56, 0x87, 0xa0, 0x02, 0xbf, 0x2a, 0x7b, + 0xe8, 0x11, 0x8b, 0x2e, 0xf5, 0x2e, 0xb7, 0x98, 0x57, 0x22, 0x57, 0x6b, 0x01, 0x9e, 0xa7, 0x72, + 0x62, 0xa3, 0x8c, 0x04, 0x60, 0xb1, 0x61, 0xd1, 0x22, 0xea, 0x97, 0x72, 0xe2, 0xd4, 0x89, 0xcf, + 0xa3, 0xbc, 0x10, 0xd5, 0x15, 0xa2, 0x71, 0x40, 0x04, 0xac, 0x3c, 0x0f, 0xf5, 0x33, 0xc8, 0xb7, + 0x1d, 0x81, 0xdb, 0x18, 0x84, 0xbc, 0xf7, 0x43, 0x6d, 0x99, 0x86, 0x16, 0x12, 0x40, 0xb8, 0xf1, + 0x30, 0xe3, 0x65, 0x4e, 0x08, 0x37, 0x1e, 0x66, 0xbc, 0x94, 0xf2, 0x5d, 0x5e, 0x47, 0x43, 0x7c, + 0xda, 0x14, 0xa2, 0xd1, 0x40, 0xb8, 0x2c, 0x3f, 0xe9, 0x15, 0x8b, 0xc6, 0x25, 0x10, 0xe1, 0x97, + 0x20, 0xed, 0x9b, 0xef, 0x92, 0xd7, 0x1b, 0x6e, 0x5f, 0xc4, 0x81, 0x1f, 0xc9, 0xfb, 0xe6, 0x7b, + 0xe6, 0x4d, 0xa1, 0x11, 0x51, 0x93, 0xfc, 0x04, 0x0b, 0x30, 0xe7, 0x49, 0xea, 0xb7, 0x29, 0xe5, + 0x2c, 0x16, 0x8b, 0xe0, 0x25, 0x34, 0x1e, 0x53, 0x1e, 0x1e, 0xbb, 0x06, 0xd2, 0x6d, 0x24, 0x68, + 0x9e, 0xb8, 0xa6, 0xc6, 0xca, 0xaa, 0xdf, 0x95, 0x8d, 0xad, 0x18, 0x54, 0x30, 0x9c, 0x4a, 0xe8, + 0x1c, 0x10, 0x8c, 0xcf, 0x9a, 0x09, 0x46, 0x20, 0xc2, 0x97, 0xd1, 0x40, 0x24, 0xf3, 0x1b, 0x38, + 0x69, 0x06, 0x69, 0xdf, 0x02, 0x2c, 0xbe, 0x8e, 0x06, 0xe8, 0xfc, 0x6d, 0x45, 0x62, 0x3e, 0x75, + 0x38, 0x4c, 0x9c, 0x70, 0x7d, 0x3a, 0x5a, 0x46, 0x8a, 0x2e, 0xec, 0x27, 0xe8, 0x8a, 0xaf, 0x56, + 0x61, 0xec, 0xf4, 0x60, 0xaf, 0xd8, 0xbb, 0xd3, 0x5e, 0x51, 0xfd, 0x8d, 0x4c, 0x5c, 0xfb, 0xf1, + 0xcd, 0x78, 0xe0, 0x17, 0x96, 0x9b, 0xcb, 0x07, 0x8a, 0xb5, 0x06, 0x21, 0x60, 0xa4, 0x10, 0x2e, + 0xd9, 0x47, 0x0e, 0xe1, 0x92, 0xdb, 0x63, 0x08, 0x17, 0xf5, 0x7f, 0xe7, 0xbb, 0x1a, 0x5c, 0x1c, + 0x8a, 0xab, 0xf2, 0x8b, 0xf4, 0xbc, 0x43, 0x6b, 0x2f, 0xb9, 0xb1, 0x5d, 0x3b, 0x7b, 0x4f, 0xae, + 0xeb, 0x6c, 0xd4, 0xb8, 0x9a, 0x4c, 0x29, 0x66, 0x4a, 0x87, 0xd0, 0x40, 0xf9, 0x84, 0x4c, 0xe9, + 0xd1, 0xf4, 0x6a, 0x62, 0x01, 0xfc, 0x31, 0x34, 0x18, 0xe6, 0x7c, 0xef, 0x15, 0x02, 0x4d, 0x25, + 0xa4, 0x7a, 0x0f, 0x29, 0xf1, 0x67, 0x51, 0x9f, 0x94, 0xdf, 0xef, 0xda, 0x2e, 0x2c, 0x54, 0x26, + 0xc5, 0xf0, 0x85, 0xec, 0xec, 0x10, 0xcd, 0xed, 0xc7, 0x99, 0xe2, 0x65, 0x74, 0xaa, 0xea, 0x10, + 0x03, 0x6c, 0xa1, 0x66, 0x1e, 0xb6, 0x1d, 0x1e, 0x5c, 0x92, 0x0d, 0x60, 0x58, 0x3a, 0xda, 0x3e, + 0x9a, 0x2e, 0x6a, 0x1c, 0x2f, 0x30, 0x4a, 0x2a, 0x4e, 0xf7, 0x13, 0xac, 0x25, 0xb7, 0xc9, 0xe6, + 0x86, 0xed, 0x18, 0x2c, 0xfe, 0x22, 0xdf, 0x4f, 0x70, 0x41, 0xaf, 0x73, 0x94, 0xb8, 0x9f, 0x90, + 0x0b, 0x4d, 0xbc, 0x88, 0x86, 0x1e, 0x35, 0x04, 0xe0, 0x2f, 0x64, 0x53, 0x4c, 0x17, 0x8f, 0x6f, + 0xea, 0x86, 0x20, 0x8d, 0x4e, 0x6f, 0x4a, 0x1a, 0x9d, 0x6f, 0x65, 0x53, 0xec, 0x32, 0x8f, 0x75, + 0xba, 0x8b, 0x40, 0x18, 0x72, 0xba, 0x8b, 0x30, 0xd3, 0x88, 0x69, 0x68, 0x22, 0x51, 0x24, 0x31, + 0x4e, 0xdf, 0x8e, 0x89, 0x71, 0x7e, 0x2a, 0xd7, 0xcd, 0x6e, 0xf5, 0x44, 0xf6, 0x7b, 0x91, 0xfd, + 0x75, 0x34, 0x14, 0x48, 0x96, 0x27, 0x49, 0x1e, 0x09, 0x02, 0x8e, 0x32, 0x30, 0x94, 0x11, 0x88, + 0xf0, 0x15, 0xd6, 0xd6, 0x9a, 0xf9, 0x2e, 0x0b, 0xba, 0x37, 0xc2, 0xc3, 0xa9, 0xe9, 0x9e, 0x5e, + 0x77, 0xcd, 0x77, 0x89, 0x16, 0xa0, 0xd5, 0x7f, 0x9a, 0x4d, 0x34, 0xfe, 0x3d, 0xe9, 0xa3, 0x3d, + 0xf4, 0x51, 0x82, 0x10, 0x99, 0xd9, 0xf2, 0x89, 0x10, 0xf7, 0x20, 0xc4, 0x3f, 0xce, 0x26, 0x1a, + 0x79, 0x9f, 0x08, 0x71, 0x2f, 0xb3, 0xc5, 0x55, 0x34, 0xa8, 0xd9, 0x1b, 0xee, 0x34, 0x9c, 0x59, + 0xd8, 0x5c, 0x01, 0x13, 0xb5, 0x63, 0x6f, 0xb8, 0x75, 0x38, 0x8d, 0x68, 0x21, 0x81, 0xfa, 0xa7, + 0xd9, 0x2e, 0x66, 0xf0, 0x27, 0x82, 0x7f, 0x3f, 0x97, 0xc8, 0x5f, 0xce, 0x4a, 0x66, 0xf6, 0xc7, + 0x3a, 0x6f, 0x5c, 0xad, 0xb1, 0x46, 0x5a, 0x7a, 0x34, 0x6f, 0x9c, 0x0b, 0x50, 0x9e, 0x76, 0x26, + 0x24, 0x51, 0xbf, 0x9a, 0x8d, 0xf8, 0x19, 0x9c, 0xc8, 0x6e, 0xd7, 0xb2, 0x0b, 0xb4, 0x8e, 0xbb, + 0x4e, 0x9c, 0x48, 0x6e, 0xb7, 0x92, 0xfb, 0xfe, 0x6c, 0xc4, 0xcb, 0xe4, 0xf8, 0xa6, 0x90, 0xfa, + 0x6a, 0x36, 0xee, 0x31, 0x73, 0x7c, 0x35, 0xe9, 0x2a, 0x1a, 0xe4, 0x72, 0x08, 0x96, 0x0a, 0x36, + 0xef, 0x33, 0x20, 0x5c, 0xa0, 0x06, 0x04, 0xea, 0xf7, 0x64, 0x91, 0xec, 0xfd, 0x73, 0x4c, 0x75, + 0xe8, 0x97, 0xb3, 0xb2, 0xdf, 0xd3, 0xf1, 0xd5, 0x9f, 0x49, 0x84, 0x6a, 0x9d, 0x95, 0x06, 0x0f, + 0x9b, 0xd5, 0x2b, 0xdc, 0xc0, 0x07, 0x50, 0x4d, 0xa0, 0x50, 0xff, 0x4f, 0x36, 0xd1, 0x19, 0xeb, + 0xf8, 0x0a, 0xf0, 0x06, 0xdc, 0x8a, 0x37, 0xac, 0x70, 0x22, 0x87, 0x4b, 0x48, 0x3a, 0xfe, 0x62, + 0xd1, 0xee, 0x7d, 0x42, 0xfc, 0xf1, 0x84, 0xed, 0x1a, 0xc4, 0x12, 0x4c, 0x4c, 0xa1, 0x2d, 0x6e, + 0xdc, 0xfe, 0x45, 0x76, 0x27, 0xdf, 0xb5, 0xe3, 0xbc, 0xaa, 0xf6, 0x57, 0xf5, 0x4d, 0x88, 0xb1, + 0x42, 0x7b, 0x62, 0x98, 0xc5, 0x62, 0x6f, 0x33, 0x90, 0xf8, 0x22, 0xc6, 0xa9, 0xd4, 0x3f, 0xea, + 0x4d, 0x76, 0x9c, 0x3a, 0xbe, 0x22, 0x3c, 0x8f, 0xf2, 0x55, 0xdd, 0x5b, 0xe3, 0x9a, 0x0c, 0xaf, + 0x75, 0x6d, 0xdd, 0x5b, 0xd3, 0x00, 0x8a, 0xaf, 0xa0, 0x01, 0x4d, 0xdf, 0x10, 0x53, 0x87, 0xc3, + 0xc5, 0x8e, 0xa3, 0x6f, 0xf0, 0xfc, 0xf1, 0x01, 0x1a, 0xab, 0x41, 0x9e, 0x06, 0x76, 0xf3, 0x0d, + 0x41, 0xce, 0x59, 0x9e, 0x86, 0x20, 0x3b, 0xc3, 0x79, 0x94, 0x9f, 0xb2, 0x8d, 0x4d, 0x30, 0x66, + 0x19, 0x66, 0x95, 0xad, 0xd8, 0xc6, 0xa6, 0x06, 0x50, 0xfc, 0x03, 0x19, 0xd4, 0x3f, 0x47, 0x74, + 0x83, 0x8e, 0x90, 0xc1, 0x6e, 0xb6, 0x20, 0x6f, 0x1e, 0x8c, 0x2d, 0xc8, 0xf8, 0x1a, 0xab, 0x4c, + 0x54, 0x14, 0x5e, 0x3f, 0xbe, 0x85, 0x06, 0xa6, 0x75, 0x8f, 0xac, 0xda, 0xce, 0x26, 0x58, 0xb7, + 0x8c, 0x86, 0xd6, 0xa3, 0x92, 0xfe, 0xf8, 0x44, 0xec, 0x65, 0xac, 0xc1, 0x7f, 0x69, 0x41, 0x61, + 0x2a, 0x16, 0x9e, 0xbf, 0x6d, 0x28, 0x14, 0x0b, 0x4b, 0xd4, 0x16, 0xa4, 0x69, 0x0b, 0xae, 0x95, + 0x87, 0x93, 0xaf, 0x95, 0x61, 0xf7, 0x08, 0x16, 0x70, 0x90, 0x1d, 0x61, 0x04, 0x16, 0x7d, 0xb6, + 0x7b, 0x04, 0x28, 0x24, 0x47, 0xd0, 0x04, 0x12, 0xf5, 0x1b, 0xbd, 0x28, 0xd1, 0xcd, 0xe2, 0x44, + 0xc9, 0x4f, 0x94, 0x3c, 0x54, 0xf2, 0x72, 0x4c, 0xc9, 0x27, 0xe2, 0x8e, 0x3b, 0x1f, 0x50, 0x0d, + 0xff, 0xa1, 0x7c, 0xcc, 0xed, 0xef, 0x78, 0x9f, 0x2e, 0x43, 0xe9, 0xf5, 0xee, 0x28, 0xbd, 0x60, + 0x40, 0xf4, 0xed, 0x38, 0x20, 0xfa, 0x77, 0x3b, 0x20, 0x06, 0x52, 0x07, 0x44, 0xa8, 0x20, 0x83, + 0xa9, 0x0a, 0x52, 0xe1, 0x83, 0x06, 0x75, 0xcf, 0xbc, 0x73, 0x7e, 0x7b, 0xab, 0x38, 0x4a, 0x47, + 0x53, 0x62, 0xce, 0x1d, 0x60, 0xa1, 0x7e, 0x3d, 0xdf, 0xc5, 0x57, 0xf7, 0x50, 0x74, 0xe4, 0x06, + 0xca, 0x95, 0xda, 0x6d, 0xae, 0x1f, 0xa7, 0x04, 0x37, 0xe1, 0x94, 0x52, 0x94, 0x1a, 0xbf, 0x84, + 0x72, 0xa5, 0x7b, 0xb5, 0x68, 0xc4, 0xe1, 0xd2, 0xbd, 0x1a, 0xff, 0x92, 0xd4, 0xb2, 0xf7, 0x6a, + 0xf8, 0xe5, 0x30, 0xf4, 0xcf, 0x5a, 0xc7, 0x5a, 0xe7, 0x07, 0x45, 0x6e, 0x04, 0xeb, 0x5b, 0xda, + 0x34, 0x28, 0x8a, 0x1e, 0x17, 0x23, 0xb4, 0x11, 0x6d, 0xea, 0xdb, 0xbd, 0x36, 0xf5, 0xef, 0xa8, + 0x4d, 0x03, 0xbb, 0xd5, 0xa6, 0xc1, 0x5d, 0x68, 0x13, 0xda, 0x51, 0x9b, 0x86, 0xf6, 0xaf, 0x4d, + 0x6d, 0x34, 0x11, 0x8f, 0xaf, 0x10, 0x68, 0x84, 0x86, 0x70, 0x1c, 0xcb, 0x0d, 0x4b, 0xe0, 0xe9, + 0xbf, 0xc3, 0xb0, 0x75, 0x96, 0x67, 0x31, 0x9a, 0xa5, 0x50, 0x4b, 0x28, 0xad, 0xfe, 0x42, 0x36, + 0x3d, 0x2c, 0xc4, 0xd1, 0x9c, 0xe2, 0xbe, 0x23, 0x51, 0x4a, 0x79, 0xd9, 0x21, 0x2a, 0x5d, 0xca, + 0x11, 0xb6, 0x49, 0x32, 0xfb, 0x5a, 0x26, 0x2d, 0x56, 0xc5, 0xbe, 0x24, 0xf6, 0xe1, 0xb8, 0xb1, + 0x1a, 0x58, 0xcf, 0xbb, 0xb2, 0x95, 0x5a, 0x34, 0x6d, 0x5f, 0xee, 0x11, 0xd3, 0xf6, 0xfd, 0x46, + 0x06, 0x9d, 0xba, 0xdd, 0x59, 0x21, 0xdc, 0x38, 0x2d, 0x68, 0xc6, 0x3b, 0x08, 0x51, 0x30, 0x37, + 0x62, 0xc9, 0x80, 0x11, 0xcb, 0x47, 0xc4, 0x38, 0x13, 0x91, 0x02, 0x93, 0x21, 0x35, 0x33, 0x60, + 0xb9, 0xe0, 0x9b, 0x58, 0xae, 0x77, 0x56, 0x48, 0x3d, 0x66, 0xc9, 0x22, 0x70, 0x9f, 0x78, 0x85, + 0x19, 0xaf, 0x3f, 0xaa, 0xd1, 0xc8, 0xcf, 0x65, 0x53, 0x43, 0x7b, 0x1c, 0xd9, 0xcc, 0x0a, 0x9f, + 0x4e, 0xec, 0x95, 0x68, 0x86, 0x85, 0x04, 0x92, 0x08, 0xc7, 0x24, 0x2e, 0xc9, 0x02, 0x3b, 0xe2, + 0xf9, 0x3e, 0xde, 0x57, 0x81, 0xfd, 0x6e, 0x26, 0x35, 0x04, 0xcb, 0x51, 0x15, 0x98, 0xfa, 0x5b, + 0x59, 0x3f, 0xf2, 0xcb, 0xbe, 0x3e, 0xe1, 0x2a, 0x1a, 0xe4, 0xe1, 0xed, 0x65, 0xdb, 0x5a, 0x7e, + 0x95, 0x07, 0x57, 0xc3, 0x01, 0x01, 0x5d, 0xe6, 0xfd, 0xc8, 0x14, 0x41, 0xa2, 0x47, 0x58, 0xe6, + 0x4d, 0x0e, 0xa5, 0xf4, 0x02, 0x09, 0x5d, 0xc8, 0x67, 0x1e, 0x9a, 0x1e, 0xec, 0x0a, 0x68, 0x5f, + 0xe6, 0xd8, 0x42, 0x4e, 0x1e, 0x9a, 0x1e, 0xdb, 0x13, 0x04, 0x68, 0xba, 0x48, 0xd7, 0xc2, 0x6c, + 0x66, 0x7c, 0x91, 0x76, 0x79, 0x52, 0x37, 0xee, 0xcc, 0x75, 0x15, 0x0d, 0x72, 0x83, 0x55, 0x6e, + 0x66, 0xc2, 0x5b, 0xcb, 0x4d, 0x5c, 0xa1, 0xb5, 0x01, 0x01, 0xe5, 0xa8, 0x91, 0xd5, 0xd0, 0xb0, + 0x0e, 0x38, 0x3a, 0x00, 0xd1, 0x38, 0x46, 0xdd, 0xce, 0xc6, 0x03, 0xd0, 0x1c, 0xdf, 0x43, 0xc1, + 0x15, 0xd9, 0x58, 0x0d, 0x2c, 0x34, 0x61, 0xc3, 0x25, 0xda, 0xca, 0xb2, 0x7d, 0xd7, 0x75, 0x34, + 0x70, 0x9b, 0x6c, 0x32, 0xbb, 0xca, 0xbe, 0xd0, 0x14, 0x77, 0x9d, 0xc3, 0xc4, 0x1b, 0x4d, 0x9f, + 0x4e, 0xfd, 0xf5, 0x6c, 0x3c, 0xb4, 0xce, 0xf1, 0x15, 0xf6, 0x47, 0x51, 0x3f, 0x88, 0xb2, 0xe2, + 0x5f, 0xa9, 0x83, 0x00, 0x41, 0xdc, 0xb2, 0x85, 0xaf, 0x4f, 0xa6, 0xfe, 0x58, 0x5f, 0x34, 0xde, + 0xd2, 0xf1, 0x95, 0xde, 0x27, 0xd0, 0xd0, 0xb4, 0x6d, 0xb9, 0xa6, 0xeb, 0x11, 0xab, 0xe1, 0x2b, + 0xec, 0x63, 0x74, 0xc3, 0xd2, 0x08, 0xc1, 0xa2, 0xe7, 0x8d, 0x40, 0xfd, 0x28, 0xca, 0x8b, 0x9f, + 0x47, 0x83, 0x20, 0x72, 0xb0, 0x43, 0x16, 0xd2, 0xc4, 0xae, 0x50, 0x60, 0xd4, 0x08, 0x39, 0x24, + 0xc5, 0x77, 0xd0, 0xc0, 0xf4, 0x9a, 0xd9, 0x34, 0x1c, 0x62, 0xf1, 0x7c, 0xe8, 0x4f, 0x26, 0x47, + 0xc7, 0x9a, 0x84, 0x7f, 0x81, 0x96, 0x35, 0xa7, 0xc1, 0x8b, 0x49, 0xbe, 0x47, 0x1c, 0x36, 0xf1, + 0x37, 0xb3, 0x08, 0x85, 0x05, 0xf0, 0x13, 0x28, 0x1b, 0x24, 0xe2, 0x01, 0x33, 0x10, 0x49, 0x83, + 0xb2, 0x30, 0x15, 0xf3, 0xb1, 0x9d, 0xdd, 0x71, 0x6c, 0xdf, 0x41, 0x7d, 0xec, 0x46, 0x09, 0x2c, + 0xb5, 0x85, 0x10, 0x30, 0xa9, 0x0d, 0x9e, 0x04, 0x7a, 0x76, 0x58, 0x84, 0x9d, 0x9d, 0x64, 0xf5, + 0xcc, 0x98, 0x4d, 0x34, 0x50, 0x2f, 0xfc, 0x85, 0x2f, 0xa1, 0x3c, 0x48, 0x31, 0x03, 0xe7, 0x44, + 0x70, 0x13, 0x8d, 0xc8, 0x0f, 0xf0, 0xb4, 0x9b, 0xa6, 0x6d, 0xcb, 0xa3, 0x55, 0x43, 0xab, 0x87, + 0xb9, 0x5c, 0x38, 0x4c, 0x92, 0x0b, 0x87, 0xa9, 0xff, 0x3c, 0x9b, 0x10, 0x09, 0xec, 0xf8, 0x0e, + 0x93, 0x17, 0x11, 0x02, 0x47, 0x66, 0x2a, 0x4f, 0xdf, 0x05, 0x02, 0x46, 0x09, 0x30, 0x02, 0xb5, + 0x95, 0xb6, 0xf5, 0x21, 0xb1, 0xfa, 0xdb, 0x99, 0x58, 0xf8, 0xa8, 0x7d, 0xc9, 0x51, 0xdc, 0xf5, + 0x64, 0x1f, 0x71, 0x9b, 0xe8, 0xf7, 0x45, 0x6e, 0x6f, 0x7d, 0x21, 0x7f, 0xcb, 0x01, 0xec, 0xfc, + 0x0e, 0xf3, 0x5b, 0xbe, 0x91, 0x4d, 0x0a, 0xa6, 0x75, 0x34, 0x55, 0x3c, 0xcc, 0x35, 0x9e, 0xdf, + 0x43, 0xae, 0xf1, 0xb7, 0xd1, 0x58, 0x24, 0xc4, 0x14, 0xcf, 0x8e, 0x75, 0xa9, 0x7b, 0xac, 0xaa, + 0x74, 0x17, 0x78, 0x89, 0x4c, 0xfd, 0xbf, 0x99, 0xee, 0x01, 0xc6, 0x0e, 0x5d, 0x75, 0x12, 0x04, + 0x90, 0xfb, 0x8b, 0x11, 0xc0, 0x01, 0x1c, 0x33, 0x8f, 0xb6, 0x00, 0x3e, 0x20, 0x93, 0xc7, 0xfb, + 0x2d, 0x80, 0x1f, 0xcb, 0xec, 0x18, 0x1f, 0xee, 0xb0, 0x65, 0xa0, 0xfe, 0xc7, 0x4c, 0x62, 0x1c, + 0xb7, 0x7d, 0xb5, 0xeb, 0x65, 0xd4, 0xc7, 0xcc, 0x56, 0x78, 0xab, 0x84, 0xc8, 0xf7, 0x14, 0x9a, + 0x52, 0x9e, 0x97, 0xc1, 0xf3, 0xa8, 0x9f, 0xb5, 0xc1, 0x88, 0x66, 0x88, 0x4c, 0x68, 0xa7, 0x91, + 0x36, 0x39, 0x72, 0xb4, 0xfa, 0x9b, 0x99, 0x58, 0x58, 0xb9, 0x43, 0xfc, 0xb6, 0x70, 0xaa, 0xce, + 0xed, 0x7e, 0xaa, 0x56, 0xff, 0x30, 0x9b, 0x1c, 0xd5, 0xee, 0x10, 0x3f, 0xe4, 0x20, 0xae, 0xab, + 0x1e, 0x6d, 0xdd, 0x5a, 0x46, 0xa3, 0xb2, 0x2c, 0xf8, 0xb2, 0x75, 0x31, 0x39, 0xb6, 0x5f, 0x4a, + 0x2b, 0x22, 0x3c, 0xd4, 0xf7, 0x32, 0xf1, 0x80, 0x7c, 0x87, 0x3e, 0x3f, 0x3d, 0x9a, 0xb6, 0xc8, + 0x9f, 0xf2, 0x01, 0x59, 0x6b, 0x0e, 0xe2, 0x53, 0x3e, 0x20, 0xab, 0xc6, 0xa3, 0x7d, 0xca, 0x4f, + 0x67, 0xd3, 0xe2, 0x19, 0x1e, 0xfa, 0x07, 0x7d, 0x4a, 0x14, 0x32, 0x6b, 0x19, 0xff, 0xb4, 0x27, + 0xd2, 0x02, 0x08, 0xa6, 0xf0, 0x8c, 0xf1, 0x79, 0xb4, 0x31, 0x9e, 0x28, 0xac, 0x0f, 0x88, 0x22, + 0x1f, 0x0d, 0x61, 0x7d, 0x40, 0x86, 0xca, 0x07, 0x4f, 0x58, 0xbf, 0x9a, 0xdd, 0x6d, 0x10, 0xcd, + 0x13, 0xe1, 0xc5, 0x84, 0xf7, 0xe5, 0x6c, 0x3c, 0xb8, 0xeb, 0xa1, 0x8b, 0x69, 0x16, 0xf5, 0xf1, + 0x30, 0xb3, 0xa9, 0xc2, 0x61, 0xf8, 0xb4, 0x1d, 0x0d, 0xff, 0x8e, 0x9b, 0x88, 0x3f, 0x94, 0xec, + 0x4e, 0x24, 0x8c, 0x56, 0xfd, 0xd3, 0x4c, 0x24, 0x12, 0xea, 0xa1, 0x5c, 0x21, 0x3c, 0xd2, 0x92, + 0x84, 0x5f, 0xf1, 0x2f, 0x33, 0xf3, 0x91, 0x24, 0x93, 0xc1, 0xf7, 0x94, 0x89, 0xa7, 0x9b, 0xcd, + 0x68, 0x79, 0xee, 0x73, 0xff, 0xeb, 0x59, 0x34, 0x1e, 0x23, 0xc5, 0x97, 0xa4, 0x28, 0x34, 0x70, + 0x2d, 0x19, 0x31, 0xce, 0x66, 0xf1, 0x68, 0xf6, 0x70, 0x93, 0x7a, 0x09, 0xe5, 0xcb, 0xfa, 0x26, + 0xfb, 0xb6, 0x5e, 0xc6, 0xd2, 0xd0, 0x37, 0xc5, 0x1b, 0x37, 0xc0, 0xe3, 0x15, 0x74, 0x86, 0xbd, + 0x87, 0x98, 0xb6, 0xb5, 0x6c, 0xb6, 0x48, 0xc5, 0x5a, 0x30, 0x9b, 0x4d, 0xd3, 0xe5, 0x8f, 0x66, + 0x57, 0xb7, 0xb7, 0x8a, 0x97, 0x3d, 0xdb, 0xd3, 0x9b, 0x75, 0xe2, 0x93, 0xd5, 0x3d, 0xb3, 0x45, + 0xea, 0xa6, 0x55, 0x6f, 0x01, 0xa5, 0xc0, 0x32, 0x99, 0x15, 0xae, 0xb0, 0x2c, 0x98, 0xb5, 0x86, + 0x6e, 0x59, 0xc4, 0xa8, 0x58, 0x53, 0x9b, 0x1e, 0x61, 0x8f, 0x6d, 0x39, 0x76, 0x25, 0xc8, 0x7c, + 0xaf, 0x19, 0x9a, 0x32, 0x5e, 0xa1, 0x04, 0x5a, 0x42, 0x21, 0xf5, 0xd7, 0xf2, 0x09, 0x41, 0x70, + 0x8f, 0x90, 0xfa, 0xf8, 0x3d, 0x9d, 0xdf, 0xa1, 0xa7, 0xaf, 0xa1, 0x7e, 0x1e, 0x67, 0x92, 0x3f, + 0x30, 0x80, 0xb1, 0xf8, 0x03, 0x06, 0x12, 0x5f, 0x68, 0x38, 0x15, 0x6e, 0xa2, 0x89, 0x65, 0xda, + 0x4d, 0xc9, 0x9d, 0xd9, 0xf7, 0x08, 0x9d, 0xd9, 0x85, 0x1f, 0x7e, 0x0b, 0x9d, 0x03, 0x6c, 0x42, + 0xb7, 0xf6, 0x43, 0x55, 0x10, 0x99, 0x89, 0x55, 0x95, 0xdc, 0xb9, 0x69, 0xe5, 0xf1, 0xa7, 0xd0, + 0x70, 0x30, 0x40, 0x4c, 0xe2, 0xf2, 0x97, 0x8b, 0x2e, 0xe3, 0x8c, 0x85, 0x3d, 0xa3, 0x60, 0x30, + 0xd1, 0x92, 0x43, 0x67, 0x49, 0xbc, 0xd4, 0xff, 0x90, 0xe9, 0x16, 0xf6, 0xf8, 0xd0, 0x67, 0xe5, + 0x57, 0x50, 0xbf, 0xc1, 0x3e, 0x8a, 0xeb, 0x54, 0xf7, 0xc0, 0xc8, 0x8c, 0x54, 0xf3, 0xcb, 0xa8, + 0x7f, 0x90, 0xe9, 0x1a, 0x6d, 0xf9, 0xa8, 0x7f, 0xde, 0x97, 0x73, 0x29, 0x9f, 0xc7, 0x27, 0xd1, + 0x2b, 0xa8, 0x60, 0x86, 0x91, 0x7c, 0xeb, 0x61, 0x78, 0x27, 0x6d, 0x4c, 0x80, 0xc3, 0xe8, 0xba, + 0x89, 0xce, 0xfa, 0x86, 0x85, 0x8e, 0x6f, 0x81, 0xe5, 0xd6, 0x3b, 0x8e, 0xc9, 0xc6, 0xa5, 0x76, + 0xda, 0x8d, 0x98, 0x67, 0xb9, 0x77, 0x1c, 0x93, 0x56, 0xa0, 0x7b, 0x6b, 0xc4, 0xd2, 0xeb, 0x1b, + 0xb6, 0xb3, 0x0e, 0xb1, 0x35, 0xd9, 0xe0, 0xd4, 0xc6, 0x18, 0xfc, 0x9e, 0x0f, 0xc6, 0x4f, 0xa1, + 0x91, 0xd5, 0x66, 0x87, 0x04, 0xd1, 0x0c, 0xd9, 0x5b, 0x9f, 0x36, 0x4c, 0x81, 0xc1, 0x0b, 0xc9, + 0x05, 0x84, 0x80, 0xc8, 0x83, 0x58, 0xd8, 0xf0, 0xb0, 0xa7, 0x0d, 0x52, 0xc8, 0x32, 0xef, 0xae, + 0x09, 0xa6, 0xd5, 0x4c, 0x48, 0xf5, 0xa6, 0x6d, 0xad, 0xd6, 0x3d, 0xe2, 0xb4, 0xa0, 0xa1, 0x60, + 0x9c, 0xa8, 0x9d, 0x05, 0x0a, 0x78, 0x3a, 0x71, 0xe7, 0x6d, 0x6b, 0x75, 0x99, 0x38, 0x2d, 0xda, + 0xd4, 0xab, 0x08, 0xf3, 0xa6, 0x3a, 0x70, 0xe9, 0xc1, 0x3e, 0x0e, 0xec, 0x14, 0x35, 0xfe, 0x11, + 0xec, 0x36, 0x04, 0x3e, 0xac, 0x88, 0x86, 0x58, 0x48, 0x37, 0x26, 0x34, 0x30, 0x55, 0xd4, 0x10, + 0x03, 0x81, 0xbc, 0xce, 0x22, 0x6e, 0xbd, 0xc0, 0xac, 0xa6, 0x35, 0xfe, 0x4b, 0xfd, 0xdb, 0xd9, + 0xb4, 0x40, 0xc9, 0x47, 0xf5, 0x8d, 0x03, 0xcf, 0x21, 0xc4, 0x33, 0x4a, 0xd2, 0xcf, 0x8d, 0x18, + 0xb4, 0x86, 0x98, 0x14, 0x1e, 0x42, 0x59, 0xf5, 0x0b, 0xd9, 0xb4, 0x50, 0xcf, 0xfb, 0x12, 0x4e, + 0xb8, 0xee, 0x64, 0xf7, 0xb0, 0xee, 0x1c, 0xbe, 0x38, 0x92, 0x74, 0xe5, 0x68, 0xbf, 0x87, 0x1d, + 0xa0, 0x70, 0x7e, 0x24, 0x9b, 0x1a, 0x60, 0xfb, 0x44, 0x3a, 0xea, 0x17, 0xb3, 0xa9, 0x01, 0xc2, + 0x8f, 0xe5, 0x50, 0x4a, 0xd4, 0x96, 0x93, 0xb1, 0xc4, 0x28, 0x9e, 0x59, 0x60, 0xe1, 0x1d, 0x6f, + 0x9b, 0x96, 0x81, 0x1f, 0x43, 0x67, 0xee, 0xd4, 0x66, 0xb4, 0xfa, 0xed, 0xca, 0x62, 0xb9, 0x7e, + 0x67, 0xb1, 0x56, 0x9d, 0x99, 0xae, 0xcc, 0x56, 0x66, 0xca, 0x85, 0x1e, 0x7c, 0x0a, 0x8d, 0x85, + 0xa8, 0xb9, 0x3b, 0x0b, 0xa5, 0xc5, 0x42, 0x06, 0x8f, 0xa3, 0x91, 0x10, 0x38, 0xb5, 0xb4, 0x5c, + 0xc8, 0x3e, 0xf3, 0x34, 0x1a, 0x82, 0xf5, 0xb5, 0x04, 0xdc, 0xf1, 0x30, 0x1a, 0x58, 0x9a, 0xaa, + 0xcd, 0x68, 0x77, 0x81, 0x09, 0x42, 0x7d, 0xe5, 0x99, 0x45, 0xca, 0x30, 0xf3, 0xcc, 0xff, 0xca, + 0x20, 0x54, 0x9b, 0x5d, 0xae, 0x72, 0xc2, 0x21, 0xd4, 0x5f, 0x59, 0xbc, 0x5b, 0x9a, 0xaf, 0x50, + 0xba, 0x01, 0x94, 0x5f, 0xaa, 0xce, 0xd0, 0x1a, 0x06, 0x51, 0xef, 0xf4, 0xfc, 0x52, 0x6d, 0xa6, + 0x90, 0xa5, 0x40, 0x6d, 0xa6, 0x54, 0x2e, 0xe4, 0x28, 0xf0, 0x9e, 0x56, 0x59, 0x9e, 0x29, 0xe4, + 0xe9, 0x9f, 0xf3, 0xb5, 0xe5, 0xd2, 0x72, 0xa1, 0x97, 0xfe, 0x39, 0x0b, 0x7f, 0xf6, 0x51, 0x66, + 0xb5, 0x99, 0x65, 0xf8, 0xd1, 0x4f, 0x9b, 0x30, 0xeb, 0xff, 0x1a, 0xa0, 0x28, 0xca, 0xba, 0x5c, + 0xd1, 0x0a, 0x83, 0xf4, 0x07, 0x65, 0x49, 0x7f, 0x20, 0xda, 0x38, 0x6d, 0x66, 0x61, 0xe9, 0xee, + 0x4c, 0x61, 0x88, 0xf2, 0x5a, 0xb8, 0x4d, 0xc1, 0xc3, 0xf4, 0x4f, 0x6d, 0x81, 0xfe, 0x39, 0x42, + 0x39, 0x69, 0x33, 0xa5, 0xf9, 0x6a, 0x69, 0x79, 0xae, 0x30, 0x4a, 0xdb, 0x03, 0x3c, 0xc7, 0x58, + 0xc9, 0xc5, 0xd2, 0xc2, 0x4c, 0xa1, 0xc0, 0x69, 0xca, 0xf3, 0x95, 0xc5, 0xdb, 0x85, 0x71, 0x68, + 0xc8, 0x5b, 0x0b, 0xf0, 0x03, 0xd3, 0x02, 0xf0, 0xd7, 0xa9, 0x67, 0x3e, 0x83, 0xfa, 0x96, 0x6a, + 0x60, 0x39, 0x73, 0x0e, 0x9d, 0x5a, 0xaa, 0xd5, 0x97, 0xdf, 0xaa, 0xce, 0x44, 0xe4, 0x3d, 0x8e, + 0x46, 0x7c, 0xc4, 0x7c, 0x65, 0xf1, 0xce, 0x9b, 0x4c, 0xda, 0x3e, 0x68, 0xa1, 0x34, 0xbd, 0x54, + 0x2b, 0x64, 0x69, 0xaf, 0xf8, 0xa0, 0x7b, 0x95, 0xc5, 0xf2, 0xd2, 0xbd, 0x5a, 0x21, 0xf7, 0xcc, + 0x03, 0x3f, 0xe3, 0xd2, 0x92, 0x63, 0xae, 0x9a, 0x16, 0xbe, 0x80, 0x1e, 0x2b, 0xcf, 0xdc, 0xad, + 0x4c, 0xcf, 0xd4, 0x97, 0xb4, 0xca, 0xad, 0xca, 0x62, 0xa4, 0xa6, 0x33, 0x68, 0x5c, 0x46, 0x97, + 0xaa, 0x95, 0x42, 0x06, 0x9f, 0x45, 0x58, 0x06, 0xbf, 0x5e, 0x5a, 0x98, 0x2d, 0x64, 0xb1, 0x82, + 0x4e, 0xcb, 0xf0, 0xca, 0xe2, 0xf2, 0x9d, 0xc5, 0x99, 0x42, 0xee, 0x99, 0x9f, 0xc8, 0xa0, 0x33, + 0x89, 0x0e, 0x94, 0x58, 0x45, 0x17, 0x67, 0xe6, 0x4b, 0xb5, 0xe5, 0xca, 0x74, 0x6d, 0xa6, 0xa4, + 0x4d, 0xcf, 0xd5, 0xa7, 0x4b, 0xcb, 0x33, 0xb7, 0x96, 0xb4, 0xb7, 0xea, 0xb7, 0x66, 0x16, 0x67, + 0xb4, 0xd2, 0x7c, 0xa1, 0x07, 0x3f, 0x85, 0x8a, 0x29, 0x34, 0xb5, 0x99, 0xe9, 0x3b, 0x5a, 0x65, + 0xf9, 0xad, 0x42, 0x06, 0x3f, 0x89, 0x2e, 0xa4, 0x12, 0xd1, 0xdf, 0x85, 0x2c, 0xbe, 0x88, 0x26, + 0xd2, 0x48, 0xde, 0x98, 0x2f, 0xe4, 0x9e, 0xf9, 0xe1, 0x0c, 0xc2, 0x71, 0x0f, 0x38, 0xfc, 0x04, + 0x3a, 0x4f, 0xf5, 0xa2, 0x9e, 0xde, 0xc0, 0x27, 0xd1, 0x85, 0x44, 0x0a, 0xa1, 0x79, 0x45, 0xf4, + 0x78, 0x0a, 0x09, 0x6f, 0xdc, 0x79, 0xa4, 0x24, 0x13, 0xd0, 0xa6, 0x4d, 0x95, 0xdf, 0xfb, 0x4f, + 0x17, 0x7b, 0xde, 0xfb, 0xe6, 0xc5, 0xcc, 0xef, 0x7c, 0xf3, 0x62, 0xe6, 0x0f, 0xbf, 0x79, 0x31, + 0xf3, 0xa9, 0xeb, 0x7b, 0x71, 0x10, 0x64, 0xa3, 0x7f, 0xa5, 0x0f, 0x5c, 0x61, 0x6e, 0xfc, 0xbf, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x0d, 0xb0, 0x44, 0xda, 0x20, 0x36, 0x01, 0x00, } func (m *Metadata) Marshal() (dAtA []byte, err error) { @@ -19596,6 +19601,11 @@ func (m *DatabaseSessionStart) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if m.PostgresPID != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.PostgresPID)) + i-- + dAtA[i] = 0x40 + } { size, err := m.DatabaseMetadata.MarshalToSizedBuffer(dAtA[:i]) if err != nil { @@ -33338,6 +33348,9 @@ func (m *DatabaseSessionStart) Size() (n int) { n += 1 + l + sovEvents(uint64(l)) l = m.DatabaseMetadata.Size() n += 1 + l + sovEvents(uint64(l)) + if m.PostgresPID != 0 { + n += 1 + sovEvents(uint64(m.PostgresPID)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -57543,6 +57556,25 @@ func (m *DatabaseSessionStart) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PostgresPID", wireType) + } + m.PostgresPID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PostgresPID |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) diff --git a/lib/srv/db/audit_test.go b/lib/srv/db/audit_test.go index 6810ebdb9b531..dee1af78791ee 100644 --- a/lib/srv/db/audit_test.go +++ b/lib/srv/db/audit_test.go @@ -52,7 +52,10 @@ func TestAuditPostgres(t *testing.T) { // Connect should trigger successful session start event. psql, err := testCtx.postgresClient(ctx, "alice", "postgres", "postgres", "postgres") require.NoError(t, err) - requireEvent(t, testCtx, libevents.DatabaseSessionStartCode) + startEvt, ok := requireEvent(t, testCtx, libevents.DatabaseSessionStartCode).(*events.DatabaseSessionStart) + require.True(t, ok) + require.NotNil(t, startEvt) + require.NotZero(t, startEvt.PostgresPID) // Simple query should trigger the query event. _, err = psql.Exec(ctx, "select 1").ReadAll() diff --git a/lib/srv/db/common/audit.go b/lib/srv/db/common/audit.go index 5a92f1480fcb8..ca5c851a164fa 100644 --- a/lib/srv/db/common/audit.go +++ b/lib/srv/db/common/audit.go @@ -114,6 +114,7 @@ func (a *audit) OnSessionStart(ctx context.Context, session *Session, sessionErr Status: events.Status{ Success: true, }, + PostgresPID: session.PostgresPID, } // If the database session wasn't started successfully, emit diff --git a/lib/srv/db/common/session.go b/lib/srv/db/common/session.go index 7f002a40e49ee..c24a16fb8f6b0 100644 --- a/lib/srv/db/common/session.go +++ b/lib/srv/db/common/session.go @@ -60,6 +60,8 @@ type Session struct { LockTargets []types.LockTarget // AuthContext is the identity context of the user. AuthContext *authz.Context + // PostgresPID is the Postgres backend PID for the session. + PostgresPID uint32 } // String returns string representation of the session parameters. diff --git a/lib/srv/db/postgres/engine.go b/lib/srv/db/postgres/engine.go index 61997bfb06a7a..61d66b21eda32 100644 --- a/lib/srv/db/postgres/engine.go +++ b/lib/srv/db/postgres/engine.go @@ -146,6 +146,8 @@ func (e *Engine) HandleConnection(ctx context.Context, sessionCtx *common.Sessio cancelAutoUserLease() return trace.Wrap(err) } + sessionCtx.PostgresPID = hijackedConn.PID + e.Log = e.Log.WithField("pg_backend_pid", hijackedConn.PID) e.rawServerConn = hijackedConn.Conn // Release the auto-users semaphore now that we've successfully connected. cancelAutoUserLease() From 7241d39826626b688b32feb0806b211963fcaaea Mon Sep 17 00:00:00 2001 From: Steven Martin Date: Wed, 23 Oct 2024 18:16:17 -0400 Subject: [PATCH 28/53] docs: remove deny in impersonation (#47871) --- .../access-controls/guides/impersonation.mdx | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/docs/pages/admin-guides/access-controls/guides/impersonation.mdx b/docs/pages/admin-guides/access-controls/guides/impersonation.mdx index 3d401c4eecf25..0d679c5db3f69 100644 --- a/docs/pages/admin-guides/access-controls/guides/impersonation.mdx +++ b/docs/pages/admin-guides/access-controls/guides/impersonation.mdx @@ -86,11 +86,6 @@ spec: users: ['jenkins'] roles: ['jenkins'] - # The deny section uses the identical format as the 'allow' section. - # The deny rules always override allow rules. - deny: - node_labels: - '*': '*' ``` Create the `role` resource: @@ -207,12 +202,6 @@ spec: where: > equals(impersonate_role.metadata.labels["group"], "security") && equals(impersonate_user.metadata.labels["group"], "security") - - # The deny section uses the identical format as the 'allow' section. - # The deny rules always override allow rules. - deny: - node_labels: - '*': '*' ``` ```code @@ -283,12 +272,6 @@ spec: where: > contains(user.spec.traits["group"], impersonate_role.metadata.labels["group"]) && contains(user.spec.traits["group"], impersonate_user.metadata.labels["group"]) - - # The deny section uses the identical format as the 'allow' section. - # The deny rules always override allow rules. - deny: - node_labels: - '*': '*' ``` While user traits typically come from an external identity provider, we can test From c5efc3792850b19930613dee86eafe2891faeb01 Mon Sep 17 00:00:00 2001 From: Erik Tate Date: Fri, 25 Oct 2024 10:45:07 -0400 Subject: [PATCH 29/53] checking for a valid home directory now ensures that the local user has (#47918) access and child processes fallback to the root directory ("/") in the case that they do not Co-authored-by: Tim Ross --- constants.go | 4 ++ lib/srv/exec_linux_test.go | 39 +++++++++--- lib/srv/reexec.go | 115 ++++++++++++++++++++++++++++------- lib/srv/reexec_test.go | 75 +++++++++++++++++++++++ lib/srv/regular/sshserver.go | 2 +- 5 files changed, 204 insertions(+), 31 deletions(-) diff --git a/constants.go b/constants.go index 3277b34035fb9..e44ad1aca8913 100644 --- a/constants.go +++ b/constants.go @@ -526,6 +526,10 @@ const ( // HomeDirNotFound is returned when a the "teleport checkhomedir" command cannot // find the user's home directory. HomeDirNotFound = 254 + // HomeDirNotAccessible is returned when a the "teleport checkhomedir" command has + // found the user's home directory, but the user does NOT have permissions to + // access it. + HomeDirNotAccessible = 253 ) // MaxEnvironmentFileLines is the maximum number of lines in a environment file. diff --git a/lib/srv/exec_linux_test.go b/lib/srv/exec_linux_test.go index b0e1c6dca1ee1..480b3a202863a 100644 --- a/lib/srv/exec_linux_test.go +++ b/lib/srv/exec_linux_test.go @@ -24,6 +24,7 @@ import ( "os" "os/exec" "os/user" + "path/filepath" "strconv" "syscall" "testing" @@ -31,20 +32,47 @@ import ( "github.com/gravitational/trace" "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/lib/utils/host" ) func TestOSCommandPrep(t *testing.T) { + if os.Geteuid() != 0 { + t.Skip("This test will be skipped because tests are not being run as root") + } + srv := newMockServer(t) scx := newExecServerContext(t, srv) - usr, err := user.Current() + // because CheckHomeDir now inspects access to the home directory as the actual user after a rexec, + // we need to setup a real, non-root user with a valid home directory in order for this test to + // exercise the correct paths + tempHome := t.TempDir() + require.NoError(t, os.Chmod(filepath.Dir(tempHome), 0777)) + + username := "test-os-command-prep" + scx.Identity.Login = username + _, err := host.UserAdd(username, nil, tempHome, "", "") require.NoError(t, err) + t.Cleanup(func() { + // change homedir back so user deletion doesn't fail + changeHomeDir(t, username, tempHome) + _, err := host.UserDel(username) + require.NoError(t, err) + }) + usr, err := user.Lookup(username) + require.NoError(t, err) + + uid, err := strconv.Atoi(usr.Uid) + require.NoError(t, err) + + require.NoError(t, os.Chown(tempHome, uid, -1)) expectedEnv := []string{ "LANG=en_US.UTF-8", - getDefaultEnvPath(strconv.Itoa(os.Geteuid()), defaultLoginDefsPath), + getDefaultEnvPath(usr.Uid, defaultLoginDefsPath), fmt.Sprintf("HOME=%s", usr.HomeDir), - fmt.Sprintf("USER=%s", usr.Username), + fmt.Sprintf("USER=%s", username), "SHELL=/bin/sh", "SSH_CLIENT=10.0.0.5 4817 3022", "SSH_CONNECTION=10.0.0.5 4817 127.0.0.1 3022", @@ -97,12 +125,9 @@ func TestOSCommandPrep(t *testing.T) { require.Equal(t, []string{"/bin/sh", "-c", "top"}, cmd.Args) require.Equal(t, syscall.SIGKILL, cmd.SysProcAttr.Pdeathsig) - if os.Geteuid() != 0 { - t.Skip("skipping portion of test which must run as root") - } - // Missing home directory - HOME should still be set to the given // home dir, but the command should set it's CWD to root instead. + changeHomeDir(t, username, "/wrong/place") usr.HomeDir = "/wrong/place" root := string(os.PathSeparator) expectedEnv[2] = "HOME=/wrong/place" diff --git a/lib/srv/reexec.go b/lib/srv/reexec.go index 9c94dcd4cf31f..e848a5df78851 100644 --- a/lib/srv/reexec.go +++ b/lib/srv/reexec.go @@ -610,6 +610,8 @@ func (o *osWrapper) startNewParker(ctx context.Context, credential *syscall.Cred return nil } +const rootDirectory = "/" + // RunForward reads in the command to run from the parent process (over a // pipe) then port forwards. func RunForward() (errw io.Writer, code int, err error) { @@ -713,16 +715,21 @@ func RunForward() (errw io.Writer, code int, err error) { } } -// runCheckHomeDir check's if the active user's $HOME dir exists. +// runCheckHomeDir checks if the active user's $HOME dir exists and is accessible. func runCheckHomeDir() (errw io.Writer, code int, err error) { - home, err := os.UserHomeDir() - if err != nil { - return io.Discard, teleport.HomeDirNotFound, nil - } - if !utils.IsDir(home) { - return io.Discard, teleport.HomeDirNotFound, nil + code = teleport.RemoteCommandSuccess + if err := hasAccessibleHomeDir(); err != nil { + switch { + case trace.IsNotFound(err), trace.IsBadParameter(err): + code = teleport.HomeDirNotFound + case trace.IsAccessDenied(err): + code = teleport.HomeDirNotAccessible + default: + code = teleport.RemoteCommandFailure + } } - return io.Discard, teleport.RemoteCommandSuccess, nil + + return io.Discard, code, nil } // runPark does nothing, forever. @@ -896,18 +903,20 @@ func buildCommand(c *ExecCommand, localUser *user.User, tty *os.File, pamEnviron // Set the command's cwd to the user's $HOME, or "/" if // they don't have an existing home dir. // TODO (atburke): Generalize this to support Windows. - exists, err := CheckHomeDir(localUser) + hasAccess, err := CheckHomeDir(localUser) if err != nil { return nil, trace.Wrap(err) - } else if exists { + } + + if hasAccess { cmd.Dir = localUser.HomeDir - } else if !exists { + } else { // Write failure to find home dir to stdout, same as OpenSSH. - msg := fmt.Sprintf("Could not set shell's cwd to home directory %q, defaulting to %q\n", localUser.HomeDir, string(os.PathSeparator)) + msg := fmt.Sprintf("Could not set shell's cwd to home directory %q, defaulting to %q\n", localUser.HomeDir, rootDirectory) if _, err := cmd.Stdout.Write([]byte(msg)); err != nil { return nil, trace.Wrap(err) } - cmd.Dir = string(os.PathSeparator) + cmd.Dir = rootDirectory } // Only set process credentials if the UID/GID of the requesting user are @@ -1041,16 +1050,73 @@ func copyCommand(ctx *ServerContext, cmdmsg *ExecCommand) { } } -// CheckHomeDir checks if the user's home dir exists +func coerceHomeDirError(usr *user.User, err error) error { + if os.IsNotExist(err) { + return trace.NotFound("home directory %q not found for user %q", usr.HomeDir, usr.Name) + } + + if os.IsPermission(err) { + return trace.AccessDenied("%q does not have permission to access %q", usr.Name, usr.HomeDir) + } + + return err +} + +// hasAccessibleHomeDir checks if the current user has access to an existing home directory. +func hasAccessibleHomeDir() error { + // this should usually be fetching a cached value + currentUser, err := user.Current() + if err != nil { + return trace.Wrap(err) + } + + fi, err := os.Stat(currentUser.HomeDir) + if err != nil { + return trace.Wrap(coerceHomeDirError(currentUser, err)) + } + + if !fi.IsDir() { + return trace.BadParameter("%q is not a directory", currentUser.HomeDir) + } + + cwd, err := os.Getwd() + if err != nil { + return trace.Wrap(err) + } + // make sure we return to the original working directory + defer os.Chdir(cwd) + + // attemping to cd into the target directory is the easiest, cross-platform way to test + // whether or not the current user has access + if err := os.Chdir(currentUser.HomeDir); err != nil { + return trace.Wrap(coerceHomeDirError(currentUser, err)) + } + + return nil +} + +// CheckHomeDir checks if the user's home directory exists and is accessible to the user. Only catastrophic +// errors will be returned, which means a missing, inaccessible, or otherwise invalid home directory will result +// in a return of (false, nil) func CheckHomeDir(localUser *user.User) (bool, error) { - if fi, err := os.Stat(localUser.HomeDir); err == nil { - return fi.IsDir(), nil + currentUser, err := user.Current() + if err != nil { + return false, trace.Wrap(err) + } + + // don't spawn a subcommand if already running as the user in question + if currentUser.Uid == localUser.Uid { + if err := hasAccessibleHomeDir(); err != nil { + if trace.IsNotFound(err) || trace.IsAccessDenied(err) || trace.IsBadParameter(err) { + return false, nil + } + + return false, trace.Wrap(err) + } + + return true, nil } - // In some environments, the user's home directory exists but isn't visible to - // root, e.g. /home is mounted to an nfs export with root_squash enabled. - // In case we are in that scenario, re-exec teleport as the user to check - // if the home dir actually does exist. executable, err := os.Executable() if err != nil { return false, trace.Wrap(err) @@ -1066,6 +1132,7 @@ func CheckHomeDir(localUser *user.User) (bool, error) { Path: executable, Args: []string{executable, teleport.CheckHomeDirSubCommand}, Env: []string{"HOME=" + localUser.HomeDir}, + Dir: rootDirectory, SysProcAttr: &syscall.SysProcAttr{ Setsid: true, Credential: credential, @@ -1076,11 +1143,13 @@ func CheckHomeDir(localUser *user.User) (bool, error) { reexecCommandOSTweaks(cmd) if err := cmd.Run(); err != nil { - if cmd.ProcessState.ExitCode() == teleport.HomeDirNotFound { - return false, nil + if cmd.ProcessState.ExitCode() == teleport.RemoteCommandFailure { + return false, trace.Wrap(err) } - return false, trace.Wrap(err) + + return false, nil } + return true, nil } diff --git a/lib/srv/reexec_test.go b/lib/srv/reexec_test.go index 33c3e21e15387..5818c8af2a2f5 100644 --- a/lib/srv/reexec_test.go +++ b/lib/srv/reexec_test.go @@ -21,16 +21,20 @@ package srv import ( "context" "fmt" + "os" "os/exec" "os/user" + "path/filepath" "strconv" "syscall" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/utils/host" ) type stubUser struct { @@ -173,3 +177,74 @@ func TestStartNewParker(t *testing.T) { }) } } + +func TestRootCheckHomeDir(t *testing.T) { + if os.Geteuid() != 0 { + t.Skip("This test will be skipped because tests are not being run as root") + } + + tmp := t.TempDir() + require.NoError(t, os.Chmod(filepath.Dir(tmp), 0777)) + require.NoError(t, os.Chmod(tmp, 0777)) + + home := filepath.Join(tmp, "home") + noAccess := filepath.Join(tmp, "no_access") + file := filepath.Join(tmp, "file") + notFound := filepath.Join(tmp, "not_found") + + require.NoError(t, os.Mkdir(home, 0700)) + require.NoError(t, os.Mkdir(noAccess, 0700)) + _, err := os.Create(file) + require.NoError(t, err) + + login := "test-user-check-home-dir" + _, err = host.UserAdd(login, nil, home, "", "") + require.NoError(t, err) + t.Cleanup(func() { + // change back to accessible home so deletion works + changeHomeDir(t, login, home) + _, err := host.UserDel(login) + require.NoError(t, err) + }) + + testUser, err := user.Lookup(login) + require.NoError(t, err) + + uid, err := strconv.Atoi(testUser.Uid) + require.NoError(t, err) + + gid, err := strconv.Atoi(testUser.Gid) + require.NoError(t, err) + + require.NoError(t, os.Chown(home, uid, gid)) + require.NoError(t, os.Chown(file, uid, gid)) + + hasAccess, err := CheckHomeDir(testUser) + require.NoError(t, err) + require.True(t, hasAccess) + + changeHomeDir(t, login, file) + hasAccess, err = CheckHomeDir(testUser) + require.NoError(t, err) + require.False(t, hasAccess) + + changeHomeDir(t, login, notFound) + hasAccess, err = CheckHomeDir(testUser) + require.NoError(t, err) + require.False(t, hasAccess) + + changeHomeDir(t, login, noAccess) + hasAccess, err = CheckHomeDir(testUser) + require.NoError(t, err) + require.False(t, hasAccess) +} + +func changeHomeDir(t *testing.T, username, home string) { + usermodBin, err := exec.LookPath("usermod") + assert.NoError(t, err, "usermod binary must be present") + + cmd := exec.Command(usermodBin, "--home", home, username) + _, err = cmd.CombinedOutput() + assert.NoError(t, err, "changing home should not error") + assert.Equal(t, 0, cmd.ProcessState.ExitCode(), "changing home should exit 0") +} diff --git a/lib/srv/regular/sshserver.go b/lib/srv/regular/sshserver.go index e2e06f1b80b05..5916e1dbfd20d 100644 --- a/lib/srv/regular/sshserver.go +++ b/lib/srv/regular/sshserver.go @@ -1282,7 +1282,7 @@ func (s *Server) HandleNewConn(ctx context.Context, ccx *sshutils.ConnectionCont // Create host user. created, userCloser, err := s.termHandlers.SessionRegistry.UpsertHostUser(identityContext) if err != nil { - log.Infof("error while creating host users: %s", err) + log.Warnf("error while creating host users: %s", err) } // Indicate that the user was created by Teleport. From 25d0715898e940be0a34f8cbb109af0e83df784a Mon Sep 17 00:00:00 2001 From: Edoardo Spadolini Date: Fri, 25 Oct 2024 19:11:43 +0200 Subject: [PATCH 30/53] Fix connected resource counts after keepalive errors (#47931) (#47951) * Fix connected resource counts after keepalive errors * Log server_id when cleaning up resources --- lib/auth/auth.go | 4 +-- lib/inventory/controller.go | 32 ++++++++++++---------- lib/inventory/controller_test.go | 47 +++++++++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 17 deletions(-) diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 5cf485c64c09a..c8a53890645e8 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -410,9 +410,9 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (*Server, error) { log.Warnf("missing connected resources gauge for keep alive %s (this is a bug)", s) } }), - inventory.WithOnDisconnect(func(s string) { + inventory.WithOnDisconnect(func(s string, c int) { if g, ok := connectedResourceGauges[s]; ok { - g.Dec() + g.Sub(float64(c)) } else { log.Warnf("missing connected resources gauge for keep alive %s (this is a bug)", s) } diff --git a/lib/inventory/controller.go b/lib/inventory/controller.go index 953ab8e9fcfc7..5b501cf33e032 100644 --- a/lib/inventory/controller.go +++ b/lib/inventory/controller.go @@ -98,7 +98,7 @@ type controllerOptions struct { authID string instanceHeartbeats bool onConnectFunc func(string) - onDisconnectFunc func(string) + onDisconnectFunc func(string, int) } func (options *controllerOptions) SetDefaults() { @@ -125,11 +125,11 @@ func (options *controllerOptions) SetDefaults() { } if options.onConnectFunc == nil { - options.onConnectFunc = func(s string) {} + options.onConnectFunc = func(string) {} } if options.onDisconnectFunc == nil { - options.onDisconnectFunc = func(s string) {} + options.onDisconnectFunc = func(string, int) {} } } @@ -159,12 +159,12 @@ func WithOnConnect(f func(heartbeatKind string)) ControllerOption { } } -// WithOnDisconnect sets a function to be called every time an existing -// instance disconnects from the inventory control stream. The value -// provided to the callback is the keep alive type of the disconnected -// resource. The callback should return quickly so as not to prevent -// processing of heartbeats. -func WithOnDisconnect(f func(heartbeatKind string)) ControllerOption { +// WithOnDisconnect sets a function to be called every time an existing instance +// disconnects from the inventory control stream. The values provided to the +// callback are the keep alive type of the disconnected resource, as well as a +// count of how many resources disconnected at once. The callback should return +// quickly so as not to prevent processing of heartbeats. +func WithOnDisconnect(f func(heartbeatKind string, amount int)) ControllerOption { return func(opts *controllerOptions) { opts.onDisconnectFunc = f } @@ -204,7 +204,7 @@ type Controller struct { usageReporter usagereporter.UsageReporter testEvents chan testEvent onConnectFunc func(string) - onDisconnectFunc func(string) + onDisconnectFunc func(string, int) closeContext context.Context cancel context.CancelFunc @@ -312,7 +312,10 @@ func (c *Controller) handleControlStream(handle *upstreamHandle) { defer func() { if handle.goodbye.GetDeleteResources() { - log.WithField("apps", len(handle.appServers)).Debug("Cleaning up resources in response to instance termination") + log.WithFields(log.Fields{ + "apps": len(handle.appServers), + "server_id": handle.Hello().ServerID, + }).Debug("Cleaning up resources in response to instance termination") for _, app := range handle.appServers { if err := c.auth.DeleteApplicationServer(c.closeContext, apidefaults.Namespace, app.resource.GetHostID(), app.resource.GetName()); err != nil && !trace.IsNotFound(err) { log.Warnf("Failed to remove app server %q on termination: %v.", handle.Hello().ServerID, err) @@ -328,11 +331,11 @@ func (c *Controller) handleControlStream(handle *upstreamHandle) { handle.ticker.Stop() if handle.sshServer != nil { - c.onDisconnectFunc(constants.KeepAliveNode) + c.onDisconnectFunc(constants.KeepAliveNode, 1) } - for range handle.appServers { - c.onDisconnectFunc(constants.KeepAliveApp) + if len(handle.appServers) > 0 { + c.onDisconnectFunc(constants.KeepAliveApp, len(handle.appServers)) } clear(handle.appServers) @@ -671,6 +674,7 @@ func (c *Controller) keepAliveAppServer(handle *upstreamHandle, now time.Time) e if shouldRemove { c.testEvent(appKeepAliveDel) + c.onDisconnectFunc(constants.KeepAliveApp, 1) delete(handle.appServers, name) } } else { diff --git a/lib/inventory/controller_test.go b/lib/inventory/controller_test.go index d5eab3c9b1efb..03b8b9e67c00a 100644 --- a/lib/inventory/controller_test.go +++ b/lib/inventory/controller_test.go @@ -142,11 +142,14 @@ func TestSSHServerBasics(t *testing.T) { expectAddr: wantAddr, } + rc := &resourceCounter{} controller := NewController( auth, usagereporter.DiscardUsageReporter{}, withServerKeepAlive(time.Millisecond*200), withTestEventsChannel(events), + WithOnConnect(rc.onConnect), + WithOnDisconnect(rc.onDisconnect), ) defer controller.Close() @@ -272,6 +275,9 @@ func TestSSHServerBasics(t *testing.T) { t.Fatal("timeout waiting for handle closure") } + // verify that metrics have been updated correctly + require.Zero(t, 0, rc.count()) + // verify that the peer address of the control stream was used to override // zero-value IPs for heartbeats. auth.mu.Lock() @@ -295,11 +301,14 @@ func TestAppServerBasics(t *testing.T) { auth := &fakeAuth{} + rc := &resourceCounter{} controller := NewController( auth, usagereporter.DiscardUsageReporter{}, withServerKeepAlive(time.Millisecond*200), withTestEventsChannel(events), + WithOnConnect(rc.onConnect), + WithOnDisconnect(rc.onDisconnect), ) defer controller.Close() @@ -482,6 +491,9 @@ func TestAppServerBasics(t *testing.T) { case <-closeTimeout: t.Fatal("timeout waiting for handle closure") } + + // verify that metrics have been updated correctly + require.Zero(t, rc.count()) } // TestInstanceHeartbeat verifies basic expected behaviors for instance heartbeat. @@ -857,7 +869,6 @@ func TestGoodbye(t *testing.T) { } func TestGetSender(t *testing.T) { - controller := NewController( &fakeAuth{}, usagereporter.DiscardUsageReporter{}, @@ -968,3 +979,37 @@ func awaitEvents(t *testing.T, ch <-chan testEvent, opts ...eventOption) { } } } + +type resourceCounter struct { + mu sync.Mutex + c map[string]int +} + +func (r *resourceCounter) onConnect(typ string) { + r.mu.Lock() + defer r.mu.Unlock() + if r.c == nil { + r.c = make(map[string]int) + } + r.c[typ]++ +} + +func (r *resourceCounter) onDisconnect(typ string, amount int) { + r.mu.Lock() + defer r.mu.Unlock() + if r.c == nil { + r.c = make(map[string]int) + } + r.c[typ] -= amount +} + +func (r *resourceCounter) count() int { + r.mu.Lock() + defer r.mu.Unlock() + + var count int + for _, v := range r.c { + count += v + } + return count +} From 102658f5168bff2985838a8660a5439762990d4d Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Fri, 25 Oct 2024 17:21:07 +0000 Subject: [PATCH 31/53] Prevent racy access to session parties (#47936) Prefer using session.getParties instead of using session.parties directly to prevent races when new parties are added. Any functions that are using session.parties AND are called from another function that already obtains the lock have been renamed to reflect that they must only be called if the session lock is held. --- lib/srv/sess.go | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/lib/srv/sess.go b/lib/srv/sess.go index 94abc49b08c53..26211ee32dca7 100644 --- a/lib/srv/sess.go +++ b/lib/srv/sess.go @@ -413,7 +413,9 @@ func (s *SessionRegistry) OpenExecSession(ctx context.Context, channel ssh.Chann return trace.Wrap(err) } - canStart, _, err := sess.checkIfStart() + sess.mu.Lock() + canStart, _, err := sess.checkIfStartUnderLock() + sess.mu.Unlock() if err != nil { return trace.Wrap(err) } @@ -500,7 +502,7 @@ func (s *SessionRegistry) isApprovedFileTransfer(scx *ServerContext) (bool, erro sess.fileTransferReq = nil sess.BroadcastMessage("file transfer request %s denied due to %s attempting to transfer files", req.ID, scx.Identity.TeleportUser) - _ = s.NotifyFileTransferRequest(req, FileTransferDenied, scx) + _ = s.notifyFileTransferRequestUnderLock(req, FileTransferDenied, scx) return false, trace.AccessDenied("Teleport user does not match original requester") } @@ -533,9 +535,9 @@ const ( FileTransferDenied FileTransferRequestEvent = "file_transfer_request_deny" ) -// NotifyFileTransferRequest is called to notify all members of a party that a file transfer request has been created/approved/denied. +// notifyFileTransferRequestUnderLock is called to notify all members of a party that a file transfer request has been created/approved/denied. // The notification is a global ssh request and requires the client to update its UI state accordingly. -func (s *SessionRegistry) NotifyFileTransferRequest(req *FileTransferRequest, res FileTransferRequestEvent, scx *ServerContext) error { +func (s *SessionRegistry) notifyFileTransferRequestUnderLock(req *FileTransferRequest, res FileTransferRequestEvent, scx *ServerContext) error { session := scx.getSession() if session == nil { s.log.Debugf("Unable to notify %s, no session found in context.", res) @@ -1074,7 +1076,7 @@ func (s *session) emitSessionJoinEvent(ctx *ServerContext) { // Notify all members of the party that a new member has joined over the // "x-teleport-event" channel. - for _, p := range s.parties { + for _, p := range s.getParties() { if len(notifyPartyPayload) == 0 { s.log.Warnf("No join event to send to %v", p.sconn.RemoteAddr()) continue @@ -1092,10 +1094,10 @@ func (s *session) emitSessionJoinEvent(ctx *ServerContext) { } } -// emitSessionLeaveEvent emits a session leave event to both the Audit Log as +// emitSessionLeaveEventUnderLock emits a session leave event to both the Audit Log as // well as sending a "x-teleport-event" global request on the SSH connection. // Must be called under session Lock. -func (s *session) emitSessionLeaveEvent(ctx *ServerContext) { +func (s *session) emitSessionLeaveEventUnderLock(ctx *ServerContext) { sessionLeaveEvent := &apievents.SessionLeave{ Metadata: apievents.Metadata{ Type: events.SessionLeaveEvent, @@ -1289,7 +1291,9 @@ func (s *session) launch() { // startInteractive starts a new interactive process (or a shell) in the // current session. func (s *session) startInteractive(ctx context.Context, scx *ServerContext, p *party) error { - canStart, _, err := s.checkIfStart() + s.mu.Lock() + canStart, _, err := s.checkIfStartUnderLock() + s.mu.Unlock() if err != nil { return trace.Wrap(err) } @@ -1554,11 +1558,8 @@ func (s *session) startExec(ctx context.Context, channel ssh.Channel, scx *Serve } func (s *session) broadcastResult(r ExecResult) { - s.mu.Lock() - defer s.mu.Unlock() - payload := ssh.Marshal(struct{ C uint32 }{C: uint32(r.Code)}) - for _, p := range s.parties { + for _, p := range s.getParties() { if _, err := p.ch.SendRequest("exit-status", false, payload); err != nil { s.log.Infof("Failed to send exit status for %v: %v", r.Command, err) } @@ -1566,7 +1567,7 @@ func (s *session) broadcastResult(r ExecResult) { } func (s *session) String() string { - return fmt.Sprintf("session(id=%v, parties=%v)", s.id, len(s.parties)) + return fmt.Sprintf("session(id=%v, parties=%v)", s.id, len(s.getParties())) } // removePartyUnderLock removes the party from the in-memory map that holds all party members @@ -1592,9 +1593,9 @@ func (s *session) removePartyUnderLock(p *party) error { // Emit session leave event to both the Audit Log and over the // "x-teleport-event" channel in the SSH connection. - s.emitSessionLeaveEvent(p.ctx) + s.emitSessionLeaveEventUnderLock(p.ctx) - canRun, policyOptions, err := s.checkIfStart() + canRun, policyOptions, err := s.checkIfStartUnderLock() if err != nil { return trace.Wrap(err) } @@ -1819,7 +1820,7 @@ func (s *session) addFileTransferRequest(params *rsession.FileTransferRequestPar } else { s.BroadcastMessage("User %s would like to upload %s to: %s", params.Requester, params.Filename, params.Location) } - err = s.registry.NotifyFileTransferRequest(s.fileTransferReq, FileTransferUpdate, scx) + err = s.registry.notifyFileTransferRequestUnderLock(s.fileTransferReq, FileTransferUpdate, scx) return trace.Wrap(err) } @@ -1862,7 +1863,7 @@ func (s *session) approveFileTransferRequest(params *rsession.FileTransferDecisi } else { eventType = FileTransferUpdate } - err = s.registry.NotifyFileTransferRequest(s.fileTransferReq, eventType, scx) + err = s.registry.notifyFileTransferRequestUnderLock(s.fileTransferReq, eventType, scx) return trace.Wrap(err) } @@ -1895,12 +1896,15 @@ func (s *session) denyFileTransferRequest(params *rsession.FileTransferDecisionP s.fileTransferReq = nil s.BroadcastMessage("%s denied file transfer request %s", scx.Identity.TeleportUser, req.ID) - err := s.registry.NotifyFileTransferRequest(req, FileTransferDenied, scx) + err := s.registry.notifyFileTransferRequestUnderLock(req, FileTransferDenied, scx) return trace.Wrap(err) } -func (s *session) checkIfStart() (bool, auth.PolicyOptions, error) { +// checkIfStartUnderLock determines if any moderation policies associated with +// the session are satisfied. +// Must be called under session Lock. +func (s *session) checkIfStartUnderLock() (bool, auth.PolicyOptions, error) { var participants []auth.SessionAccessContext for _, party := range s.parties { @@ -1939,7 +1943,7 @@ func (s *session) addParty(p *party, mode types.SessionParticipantMode) error { } if len(s.parties) == 0 { - canStart, _, err := s.checkIfStart() + canStart, _, err := s.checkIfStartUnderLock() if err != nil { return trace.Wrap(err) } @@ -1992,7 +1996,7 @@ func (s *session) addParty(p *party, mode types.SessionParticipantMode) error { } if s.tracker.GetState() == types.SessionState_SessionStatePending { - canStart, _, err := s.checkIfStart() + canStart, _, err := s.checkIfStartUnderLock() if err != nil { return trace.Wrap(err) } From 5fdae8b51758bf9a2644cbb8315658c79abafeb3 Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Fri, 25 Oct 2024 17:21:48 +0000 Subject: [PATCH 32/53] Fix flay TestIntegrations/ProxyHostKeyCheck (#47939) --- integration/integration_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/integration/integration_test.go b/integration/integration_test.go index e163329276273..116d9c1f02c01 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -5052,6 +5052,13 @@ func testProxyHostKeyCheck(t *testing.T, suite *integrationTestSuite) { _, err = clt.UpsertNode(context.Background(), server) require.NoError(t, err) + // Wait for the node to be visible before continuing. + require.EventuallyWithT(t, func(t *assert.CollectT) { + found, err := clt.GetNodes(context.Background(), defaults.Namespace) + assert.NoError(t, err) + assert.Len(t, found, 2) + }, 10*time.Second, 100*time.Millisecond) + _, err = runCommand(t, instance, []string{"echo hello"}, clientConfig, 1) // check if we were able to exec the command or not From 31957409573118710a01a672cbc59d78f8844dcd Mon Sep 17 00:00:00 2001 From: "teleport-post-release-automation[bot]" <128860004+teleport-post-release-automation[bot]@users.noreply.github.com> Date: Sat, 26 Oct 2024 09:17:14 -0400 Subject: [PATCH 33/53] [auto] docs: Update version to v14.3.32 (#47151) Co-authored-by: GitHub --- docs/config.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/config.json b/docs/config.json index c7554e98e3095..fd37c89f43fdd 100644 --- a/docs/config.json +++ b/docs/config.json @@ -206,19 +206,19 @@ }, "teleport": { "major_version": "14", - "version": "14.3.31", + "version": "14.3.32", "git": "api/14.0.0-gd1e081e", "url": "teleport.example.com", "golang": "1.21", "plugin": { - "version": "14.3.31" + "version": "14.3.32" }, "helm_repo_url": "https://charts.releases.teleport.dev", - "latest_oss_docker_image": "public.ecr.aws/gravitational/teleport-distroless:14.3.31", - "latest_oss_debug_docker_image": "public.ecr.aws/gravitational/teleport-distroless-debug:14.3.31", - "latest_ent_docker_image": "public.ecr.aws/gravitational/teleport-ent-distroless:14.3.31", - "latest_ent_debug_docker_image": "public.ecr.aws/gravitational/teleport-ent-distroless-debug:14.3.31", - "teleport_install_script_url": "https://cdn.teleport.dev/install-v14.3.31.sh" + "latest_oss_docker_image": "public.ecr.aws/gravitational/teleport-distroless:14.3.32", + "latest_oss_debug_docker_image": "public.ecr.aws/gravitational/teleport-distroless-debug:14.3.32", + "latest_ent_docker_image": "public.ecr.aws/gravitational/teleport-ent-distroless:14.3.32", + "latest_ent_debug_docker_image": "public.ecr.aws/gravitational/teleport-ent-distroless-debug:14.3.32", + "teleport_install_script_url": "https://cdn.teleport.dev/install-v14.3.32.sh" }, "terraform": { "version": "1.0.0" From 4d6b4bb2f0588c2a20908855b81ff06c7c125ddf Mon Sep 17 00:00:00 2001 From: Marco Dinis Date: Mon, 28 Oct 2024 10:03:30 +0000 Subject: [PATCH 34/53] [v14] Fix UserContext SSO detection in UI for Okta Users (#47944) (#47959) * Fix UserContext SSO detection in UI for Okta Users (#47944) * Fix UserContext SSO detection in UI for Okta Users Okta imported users are not being properly identified as SSO users. Okta does not set any of the Users' identities and instead only sets the User.Connector.CreatedBy field. When building the UserContext, which is used by the WebUI, it was returning `local` user type for Okta users. * move usertype check to types.User * remove User.Status field which only exists on 15+ --- api/types/user.go | 10 +++++++--- lib/web/ui/usercontext.go | 4 +--- lib/web/ui/usercontext_test.go | 18 +++++++++++++++++- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/api/types/user.go b/api/types/user.go index 8a27369e21532..5769e53fd62b9 100644 --- a/api/types/user.go +++ b/api/types/user.go @@ -497,11 +497,15 @@ func (u UserV2) GetGCPServiceAccounts() []string { // GetUserType indicates if the User was created by an SSO Provider or locally. func (u UserV2) GetUserType() UserType { - if u.GetCreatedBy().Connector == nil { - return UserTypeLocal + if u.GetCreatedBy().Connector != nil || + len(u.GetOIDCIdentities()) > 0 || + len(u.GetGithubIdentities()) > 0 || + len(u.GetSAMLIdentities()) > 0 { + + return UserTypeSSO } - return UserTypeSSO + return UserTypeLocal } // IsBot returns true if the user is a bot. diff --git a/lib/web/ui/usercontext.go b/lib/web/ui/usercontext.go index 0ccef5c69ba31..cf16947ba82a0 100644 --- a/lib/web/ui/usercontext.go +++ b/lib/web/ui/usercontext.go @@ -100,9 +100,7 @@ func NewUserContext(user types.User, userRoles services.RoleSet, features proto. authType := authLocal // check for any SSO identities - isSSO := len(user.GetOIDCIdentities()) > 0 || - len(user.GetGithubIdentities()) > 0 || - len(user.GetSAMLIdentities()) > 0 + isSSO := user.GetUserType() == types.UserTypeSSO if isSSO { // SSO user diff --git a/lib/web/ui/usercontext_test.go b/lib/web/ui/usercontext_test.go index 569a42269c26c..cd54e0cccc241 100644 --- a/lib/web/ui/usercontext_test.go +++ b/lib/web/ui/usercontext_test.go @@ -61,7 +61,23 @@ func TestNewUserContext(t *testing.T) { user.Spec.GithubIdentities = []types.ExternalIdentity{{ConnectorID: "foo", Username: "bar"}} userContext, err = NewUserContext(user, roleSet, proto.Features{}, true, false) require.NoError(t, err) - require.Equal(t, userContext.AuthType, authSSO) + require.Equal(t, authSSO, userContext.AuthType) + + // test sso auth type for users with the CreatedBy.Connector field set. + // Eg users import from okta do not have any Identities, so the CreatedBy.Connector must be checked. + userCreatedExternally := &types.UserV2{ + Metadata: types.Metadata{ + Name: "root", + }, + Spec: types.UserSpecV2{ + CreatedBy: types.CreatedBy{ + Connector: &types.ConnectorRef{}, + }, + }, + } + userContext, err = NewUserContext(userCreatedExternally, roleSet, proto.Features{}, true, false) + require.NoError(t, err) + require.Equal(t, authSSO, userContext.AuthType) } func TestNewUserContextCloud(t *testing.T) { From cca8ebe5ada9d99c124ce54805511df9db99dbbf Mon Sep 17 00:00:00 2001 From: Tiago Silva Date: Tue, 29 Oct 2024 17:09:22 +0000 Subject: [PATCH 35/53] [kube] add server_id to targets when monitoring exec/portforward connections (#48078) This PR adds the target server_id (kubernetes service) when proxy establishes a connection to support kubectl exec and portforward. This allows proxies to terminate early the connection without relying on the upstream to terminate it. --- lib/kube/proxy/forwarder.go | 22 +++++++++++++++++----- lib/kube/proxy/roundtrip.go | 1 - lib/kube/proxy/transport.go | 23 ++++++++++++++++++++--- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/lib/kube/proxy/forwarder.go b/lib/kube/proxy/forwarder.go index 98160c470d98d..ba0beb4369792 100644 --- a/lib/kube/proxy/forwarder.go +++ b/lib/kube/proxy/forwarder.go @@ -2176,7 +2176,7 @@ func (s *clusterSession) close() { } } -func (s *clusterSession) monitorConn(conn net.Conn, err error) (net.Conn, error) { +func (s *clusterSession) monitorConn(conn net.Conn, err error, hostID string) (net.Conn, error) { if err != nil { return nil, trace.Wrap(err) } @@ -2191,10 +2191,18 @@ func (s *clusterSession) monitorConn(conn net.Conn, err error) (net.Conn, error) s.connMonitorCancel(err) return nil, trace.Wrap(err) } - + lockTargets := s.LockTargets() + // when the target is not a kubernetes_service instance, we don't need to lock it. + // the target could be a remote cluster or a local Kubernetes API server. In both cases, + // hostID is empty. + if hostID != "" { + lockTargets = append(lockTargets, types.LockTarget{ + ServerID: hostID, + }) + } err = srv.StartMonitor(srv.MonitorConfig{ LockWatcher: s.parent.cfg.LockWatcher, - LockTargets: s.LockTargets(), + LockTargets: lockTargets, DisconnectExpiredCert: s.disconnectExpiredCert, ClientIdleTimeout: s.clientIdleTimeout, Clock: s.parent.cfg.Clock, @@ -2226,12 +2234,16 @@ func (s *clusterSession) getServerMetadata() apievents.ServerMetadata { } func (s *clusterSession) Dial(network, addr string) (net.Conn, error) { - return s.monitorConn(s.dial(s.requestContext, network, addr)) + var hostID string + conn, err := s.dial(s.requestContext, network, addr, withHostIDCollection(&hostID)) + return s.monitorConn(conn, err, hostID) } func (s *clusterSession) DialWithContext(opts ...contextDialerOption) func(ctx context.Context, network, addr string) (net.Conn, error) { return func(ctx context.Context, network, addr string) (net.Conn, error) { - return s.monitorConn(s.dial(ctx, network, addr, opts...)) + var hostID string + conn, err := s.dial(ctx, network, addr, append(opts, withHostIDCollection(&hostID))...) + return s.monitorConn(conn, err, hostID) } } diff --git a/lib/kube/proxy/roundtrip.go b/lib/kube/proxy/roundtrip.go index f5507170ffecc..9049dabb61488 100644 --- a/lib/kube/proxy/roundtrip.go +++ b/lib/kube/proxy/roundtrip.go @@ -110,7 +110,6 @@ func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) { if err != nil { return nil, err } - if err := req.Write(conn); err != nil { conn.Close() return nil, err diff --git a/lib/kube/proxy/transport.go b/lib/kube/proxy/transport.go index ec5115501a2d2..e426a30901c12 100644 --- a/lib/kube/proxy/transport.go +++ b/lib/kube/proxy/transport.go @@ -493,6 +493,7 @@ func (f *Forwarder) localClusterDialer(kubeClusterName string, opts ...contextDi ProxyIDs: s.GetProxyIDs(), }) if err == nil { + opt.collect(s.GetHostID()) return conn, nil } errs = append(errs, trace.Wrap(err)) @@ -584,13 +585,21 @@ func (f *Forwarder) getContextDialerFunc(s *clusterSession, opts ...contextDiale // contextDialerOptions is a set of options that can be used to filter // the hosts that the dialer connects to. type contextDialerOptions struct { - hostID string + hostIDFilter string + collectHostID *string } // matches returns true if the host matches the hostID of the dialer options or // if the dialer hostID is empty. func (c *contextDialerOptions) matches(hostID string) bool { - return c.hostID == "" || c.hostID == hostID + return c.hostIDFilter == "" || c.hostIDFilter == hostID +} + +// collect sets the hostID that the dialer connected to if collectHostID is not nil. +func (c *contextDialerOptions) collect(hostID string) { + if c.collectHostID != nil { + *c.collectHostID = hostID + } } // contextDialerOption is a functional option for the contextDialerOptions. @@ -603,6 +612,14 @@ type contextDialerOption func(*contextDialerOptions) // error. func withTargetHostID(hostID string) contextDialerOption { return func(o *contextDialerOptions) { - o.hostID = hostID + o.hostIDFilter = hostID + } +} + +// withHostIDCollection is a functional option that sets the hostID of the dialer +// to the provided pointer. +func withHostIDCollection(hostID *string) contextDialerOption { + return func(o *contextDialerOptions) { + o.collectHostID = hostID } } From 039e83ebd604febf57535fc0940ec968f7388567 Mon Sep 17 00:00:00 2001 From: Russell Jones Date: Tue, 29 Oct 2024 11:14:34 -0700 Subject: [PATCH 36/53] Added support for OpenSSH compatibility flags. (#46879) (#48018) Added support for OpenSSH compatibility flags for interactivity (-t and -T) and version information (-V). This is for customers that alias "ssh" to "tsh ssh". --- lib/client/session.go | 7 --- tool/tsh/common/tsh.go | 47 ++++++++++++++- tool/tsh/common/tsh_test.go | 115 ++++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 9 deletions(-) diff --git a/lib/client/session.go b/lib/client/session.go index 0c9ffb7f799f4..bf33ba64fa994 100644 --- a/lib/client/session.go +++ b/lib/client/session.go @@ -549,13 +549,6 @@ func (ns *NodeSession) runCommand(ctx context.Context, mode types.SessionPartici ) defer span.End() - // If stdin is not a terminal, refuse to allocate terminal on the server and - // fallback to non-interactive mode - if interactive && !ns.terminal.IsAttached() { - interactive = false - fmt.Fprintf(ns.nodeClient.TC.Stderr, "TTY will not be allocated on the server because stdin is not a terminal\n") - } - // Start a interactive session ("exec" request with a TTY). // // Note that because a TTY was allocated, the terminal is in raw mode and any diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index 80d4e8b2b3e5e..c3bce55a74c2d 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -43,6 +43,7 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/dustin/go-humanize" "github.com/ghodss/yaml" + "github.com/google/uuid" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" "github.com/sirupsen/logrus" @@ -215,8 +216,17 @@ type CLIConf struct { DatabaseRoles string // AppName specifies proxied application name. AppName string - // Interactive, when set to true, launches remote command with the terminal attached + // Interactive sessions will allocate a PTY and create interactive "shell" + // sessions. Interactive bool + // NonInteractive sessions will not allocate a PTY and create + // non-interactive "exec" sessions. This variable is needed due to + // limitations in kingpin (lack of an inverse short flag) which forces + // the registration of both flags. + NonInteractive bool + // ShowVersion is an OpenSSH compatibility flag that prints out the version + // of tsh. Useful for users that alias ssh to "tsh ssh". + ShowVersion bool // Quiet mode, -q command (disables progress printing) Quiet bool // Namespace is used to select cluster namespace @@ -731,7 +741,7 @@ func Run(ctx context.Context, args []string, opts ...CliOption) error { // ssh // Use Interspersed(false) to forward all flags to ssh. ssh := app.Command("ssh", "Run shell or execute a command on a remote SSH node.").Interspersed(false) - ssh.Arg("[user@]host", "Remote hostname and the login to use").Required().StringVar(&cf.UserHost) + ssh.Arg("[user@]host", "Remote hostname and the login to use, this argument is required").StringVar(&cf.UserHost) ssh.Arg("command", "Command to execute on a remote host").StringsVar(&cf.RemoteCommand) app.Flag("jumphost", "SSH jumphost").Short('J').StringVar(&cf.ProxyJump) ssh.Flag("port", "SSH port on a remote host").Short('p').Int32Var(&cf.NodePort) @@ -751,6 +761,14 @@ func Run(ctx context.Context, args []string, opts ...CliOption) error { ssh.Flag("participant-req", "Displays a verbose list of required participants in a moderated session.").BoolVar(&cf.displayParticipantRequirements) ssh.Flag("request-reason", "Reason for requesting access").StringVar(&cf.RequestReason) ssh.Flag("disable-access-request", "Disable automatic resource access requests").BoolVar(&cf.disableAccessRequest) + // The following flags are OpenSSH compatibility flags. They are used for + // users that alias "ssh" to "tsh ssh." The following OpenSSH flags are + // implemented. From "man 1 ssh": + // + // * "-V Display the version number and exit." + // * "-T Disable pseudo-terminal allocation." + ssh.Flag(uuid.New().String(), "").Short('T').Hidden().BoolVar(&cf.NonInteractive) + ssh.Flag(uuid.New().String(), "").Short('V').Hidden().BoolVar(&cf.ShowVersion) // Daemon service for teleterm client daemon := app.Command("daemon", "Daemon is the tsh daemon service.").Hidden() @@ -3483,6 +3501,31 @@ func runLocalCommand(hostLogin string, command []string) error { // onSSH executes 'tsh ssh' command func onSSH(cf *CLIConf) error { + // If "tsh ssh -V" is invoked, tsh is in OpenSSH compatibility mode, show + // the version and exit. + if cf.ShowVersion { + modules.GetModules().PrintVersion() + return nil + } + + // If "tsh ssh" is invoked with the "-t" or "-T" flag, manually validate + // "-t" and "-T" flags for "tsh ssh" due to the lack of inverse short flags + // in kingpin. + if cf.Interactive && cf.NonInteractive { + return trace.BadParameter("either -t or -T can be specified, not both") + } + if cf.NonInteractive { + cf.Interactive = false + } + + // If "tsh ssh" is invoked the user must specify some host to connect to. + // In the past, this was handled by making "UserHost" required in kingpin. + // However, to support "tsh ssh -V" this was no longer possible. This + // property is how enforced in this function. + if cf.UserHost == "" { + return trace.BadParameter("required argument '[user@]host' not provided") + } + tc, err := makeClient(cf) if err != nil { return trace.Wrap(err) diff --git a/tool/tsh/common/tsh_test.go b/tool/tsh/common/tsh_test.go index 36b767805c398..4301a7ebdffce 100644 --- a/tool/tsh/common/tsh_test.go +++ b/tool/tsh/common/tsh_test.go @@ -44,6 +44,7 @@ import ( "time" "github.com/ghodss/yaml" + "github.com/google/uuid" "github.com/gravitational/trace" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -5394,3 +5395,117 @@ func TestFlatten(t *testing.T) { conf.IdentityFileIn = identityPath require.NoError(t, flattenIdentity(&conf), "unexpected error when overwriting a tsh profile") } + +// TestInteractiveCompatibilityFlags verifies that "tsh ssh -t" and "tsh ssh -T" +// behave similar to OpenSSH. +func TestInteractiveCompatibilityFlags(t *testing.T) { + // Require the "tty" program exist somewhere in $PATH, otherwise fail. + tty, err := exec.LookPath("tty") + require.NoError(t, err) + + // Create roles and users that will be used in test. + local, err := user.Current() + require.NoError(t, err) + nodeAccess, err := types.NewRole("foo", types.RoleSpecV6{ + Allow: types.RoleConditions{ + Logins: []string{local.Username}, + NodeLabels: types.Labels{types.Wildcard: []string{types.Wildcard}}, + }, + }) + require.NoError(t, err) + user, err := types.NewUser("bar@example.com") + require.NoError(t, err) + user.SetRoles([]string{"access", "foo"}) + + // Create Auth, Proxy, and Node Service used in tests. + hostname := uuid.NewString() + connector := mockConnector(t) + authProcess, proxyProcess := makeTestServers(t, + withBootstrap(connector, nodeAccess, user), + withConfig(func(cfg *servicecfg.Config) { + cfg.Hostname = hostname + cfg.SSH.Enabled = true + cfg.SSH.Addr = utils.NetAddr{ + AddrNetwork: "tcp", + Addr: net.JoinHostPort("127.0.0.1", ports.Pop()), + } + })) + + // Extract Auth Service and Proxy Service address. + authServer := authProcess.GetAuthServer() + require.NotNil(t, authServer) + proxyAddr, err := proxyProcess.ProxyWebAddr() + require.NoError(t, err) + + // Run "tsh login". + home := t.TempDir() + err = Run(context.Background(), []string{ + "login", + "--insecure", + "--debug", + "--proxy", proxyAddr.String()}, + setHomePath(home), + setMockSSOLogin(authServer, user, connector.GetName())) + require.NoError(t, err) + + // Test compatibility with OpenSSH "-T" and "-t" flags. Note that multiple + // -t options is still not supported. + // + // From "man 1 ssh". + // + // -T Disable pseudo-terminal allocation. + // -t Force pseudo-terminal allocation. This can be used to execute + // arbitrary screen-based programs on a remote machine, which can + // be very useful, e.g. when implementing menu services. Multiple + // -t options force tty allocation, even if ssh has no local tty. + tests := []struct { + name string + flag string + assertError require.ErrorAssertionFunc + }{ + { + name: "disable pseudo-terminal allocation", + flag: "-T", + assertError: func(t require.TestingT, err error, i ...interface{}) { + var exiterr *exec.ExitError + if errors.As(err, &exiterr) { + require.Equal(t, 1, exiterr.ExitCode()) + } else { + require.Fail(t, "Non *exec.ExitError type: %T.", err) + } + }, + }, + { + name: "force pseudo-terminal allocation", + flag: "-t", + assertError: require.NoError, + }, + } + + // Turn the binary running tests into a fake "tsh" binary so it can + // re-execute itself. + t.Setenv(types.HomeEnvVar, home) + t.Setenv(tshBinMainTestEnv, "1") + tshBin, err := os.Executable() + require.NoError(t, err) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + err := exec.Command(tshBin, "ssh", tt.flag, hostname, tty).Run() + tt.assertError(t, err) + }) + } +} + +// TestVersionCompatibilityFlags verifies that "tsh ssh -V" returns Teleport +// version similar to OpenSSH. +func TestVersionCompatibilityFlags(t *testing.T) { + t.Setenv(tshBinMainTestEnv, "1") + tshBin, err := os.Executable() + require.NoError(t, err) + + output, err := exec.Command(tshBin, "ssh", "-V").CombinedOutput() + require.NoError(t, err, output) + require.Equal(t, "Teleport CLI", string(bytes.TrimSpace(output))) +} From e06404f78c87e611710a3195262c978704035374 Mon Sep 17 00:00:00 2001 From: Marco Dinis Date: Wed, 30 Oct 2024 08:44:06 +0000 Subject: [PATCH 37/53] Improve message when sortBy field is missing in ListUnifiedResources (#47892) --- lib/auth/auth_with_roles_test.go | 8 ++++++++ lib/services/unified_resource.go | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/auth/auth_with_roles_test.go b/lib/auth/auth_with_roles_test.go index 4d45cf82cd5a5..60ab8b4fe0fd4 100644 --- a/lib/auth/auth_with_roles_test.go +++ b/lib/auth/auth_with_roles_test.go @@ -4457,6 +4457,14 @@ func TestListUnifiedResources_KindsFilter(t *testing.T) { r := resource.GetDatabaseServer() require.Equal(t, types.KindDatabaseServer, r.GetKind()) } + + // Check for invalid sort error message + _, err = clt.ListUnifiedResources(ctx, &proto.ListUnifiedResourcesRequest{ + Kinds: []string{types.KindDatabase}, + Limit: 5, + SortBy: types.SortBy{}, + }) + require.ErrorContains(t, err, "sort field is required") } func TestListUnifiedResources_WithPinnedResources(t *testing.T) { diff --git a/lib/services/unified_resource.go b/lib/services/unified_resource.go index 2d17948a6bb89..48885aecb9745 100644 --- a/lib/services/unified_resource.go +++ b/lib/services/unified_resource.go @@ -195,8 +195,10 @@ func (c *UnifiedResourceCache) getSortTree(sortField string) (*btree.BTreeG[*ite return c.nameTree, nil case sortByKind: return c.typeTree, nil + case "": + return nil, trace.BadParameter("sort field is required") default: - return nil, trace.NotImplemented("sorting by %v is not supporting in unified resources", sortField) + return nil, trace.NotImplemented("sorting by %v is not supported in unified resources", sortField) } } From e9209770051ae86b45f5e830d399cb99233756a9 Mon Sep 17 00:00:00 2001 From: Paul Gottschling Date: Wed, 30 Oct 2024 14:22:12 -0400 Subject: [PATCH 38/53] Restructure docs menu pages (#48151) Backports #47797 Docusaurus [sidebar generation](https://docusaurus.io/docs/next/sidebar/autogenerated) expects category index pages to have one of three file path conventions: - `section/index.mdx` - `section/README.mdx` - `section/section.mdx` This change standardizes category index paths on the third convention so Docusaurus sidebar generation succeeds. We can then add checks to the current docs site to prevent additional menu pages from violating this convention. This change also adds redirects to the new category index pages, and updates internal links to pages that were moved. Note that this change does not move all relevant menu pages. We still need to reorganize the `reference/terraform-provider` section. Since this section is automatically generated, we need another approach to restructuring it. --- CHANGELOG.md | 35 ++++----- docs/config.json | 2 +- .../access-controls/access-controls.mdx | 8 +- .../{ => access-lists}/access-lists.mdx | 4 +- .../access-request-plugins.mdx | 2 +- .../{ => access-requests}/access-requests.mdx | 10 +-- .../access-requests/oss-role-requests.mdx | 2 +- .../access-requests/resource-requests.mdx | 4 +- .../access-requests/role-requests.mdx | 2 +- .../compliance-frameworks.mdx | 4 +- .../compliance-frameworks/soc2.mdx | 8 +- .../{ => device-trust}/device-trust.mdx | 8 +- .../device-trust/jamf-integration.mdx | 2 +- .../access-controls/guides/dual-authz.mdx | 2 +- .../access-controls/{ => guides}/guides.mdx | 0 .../access-controls/guides/locking.mdx | 2 +- .../admin-guides/access-controls/idps.mdx | 13 ---- .../access-controls/idps/idps.mdx | 13 ++++ .../access-controls/login-rules/guide.mdx | 2 +- .../{ => login-rules}/login-rules.mdx | 8 +- .../access-controls/{ => sso}/sso.mdx | 44 +++++------ docs/pages/admin-guides/api/access-plugin.mdx | 4 +- docs/pages/admin-guides/api/api.mdx | 4 +- .../api/automatically-register-agents.mdx | 6 -- .../admin-guides/api/getting-started.mdx | 2 +- docs/pages/admin-guides/api/rbac.mdx | 2 +- .../access-graph/self-hosted-helm.mdx | 2 +- .../deploy-a-cluster/deployments.mdx | 19 ----- .../aws-ha-autoscale-cluster-terraform.mdx | 2 +- .../aws-starter-cluster-terraform.mdx | 2 +- .../deployments/deployments.mdx | 19 +++++ .../helm-deployments.mdx | 18 ++--- .../helm-deployments/kubernetes-cluster.mdx | 2 +- .../deploy-a-cluster/high-availability.mdx | 12 +-- .../infrastructure-as-code.mdx | 20 ++--- .../infrastructure-as-code/kubernetes.mdx | 4 +- .../teleport-operator.mdx | 5 +- .../terraform-provider.mdx | 10 +-- .../infrastructure-as-code/terraform.mdx | 4 +- docs/pages/admin-guides/management/admin.mdx | 30 -------- .../admin-guides/management/admin/admin.mdx | 30 ++++++++ .../management/admin/trustedclusters.mdx | 4 +- .../admin-guides/management/admin/users.mdx | 2 +- .../admin-guides/management/diagnostics.mdx | 11 --- .../management/diagnostics/diagnostics.mdx | 11 +++ .../management/diagnostics/metrics.mdx | 2 +- .../export-audit-events.mdx | 7 +- .../management/external-audit-storage.mdx | 2 +- .../management/guides/ec2-tags.mdx | 2 +- .../management/{ => guides}/guides.mdx | 7 +- .../admin-guides/management/operations.mdx | 19 ----- .../management/operations/db-ca-rotation.mdx | 4 +- .../management/operations/operations.mdx | 16 ++++ .../management/operations/tls-routing.mdx | 2 +- .../security/reduce-blast-radius.mdx | 2 +- .../management/{ => security}/security.mdx | 8 +- docs/pages/ai-assist.mdx | 2 +- .../pages/connect-your-client/gui-clients.mdx | 2 +- docs/pages/connect-your-client/tsh.mdx | 2 +- .../{ => documentation}/documentation.mdx | 10 +-- docs/pages/core-concepts.mdx | 2 +- .../agents/deploy-agents-terraform.mdx | 9 ++- .../enroll-resources/agents/introduction.mdx | 4 +- .../agents/join-services-to-your-cluster.mdx | 22 ------ .../join-services-to-your-cluster/azure.mdx | 2 +- .../join-services-to-your-cluster.mdx | 22 ++++++ .../kubernetes.mdx | 6 +- .../cloud-apis/aws-console.mdx | 2 +- .../application-access/cloud-apis/azure.mdx | 2 +- .../{ => cloud-apis}/cloud-apis.mdx | 8 +- .../cloud-apis/google-cloud.mdx | 2 +- .../application-access/controls.mdx | 4 +- .../enroll-kubernetes-applications.mdx | 6 +- .../kubernetes-applications.mdx | 26 +++++++ .../application-access/guides.mdx | 19 ----- .../guides/dynamic-registration.mdx | 5 -- .../application-access/guides/guides.mdx | 19 +++++ .../application-access/introduction.mdx | 4 +- .../application-access/{ => jwt}/jwt.mdx | 4 +- .../application-access/okta.mdx | 12 --- .../application-access/okta/okta.mdx | 12 +++ .../database-access/architecture.mdx | 4 +- .../auto-user-provisioning.mdx | 13 ---- .../auto-user-provisioning.mdx | 16 ++++ .../database-access/database-access.mdx | 2 +- .../enroll-resources/database-access/faq.mdx | 2 +- .../database-access/getting-started.mdx | 4 +- .../guides/aws-cross-account.mdx | 5 +- .../database-access/guides/aws-discovery.mdx | 4 +- .../guides/dynamic-registration.mdx | 7 +- .../database-access/guides/elastic.mdx | 2 +- .../database-access/{ => guides}/guides.mdx | 1 + .../database-access/guides/ha.mdx | 5 +- .../database-access/guides/mysql-cloudsql.mdx | 2 +- .../guides/mysql-self-hosted.mdx | 2 +- .../guides/oracle-self-hosted.mdx | 4 +- .../guides/postgres-cloudsql.mdx | 2 +- .../guides/postgres-self-hosted.mdx | 2 +- .../database-access/guides/rds.mdx | 4 +- .../database-access/guides/vitess.mdx | 2 +- .../enroll-resources/database-access/rbac.mdx | 15 ---- .../rbac/configuring-access.mdx | 2 +- .../database-access/rbac/rbac.mdx | 8 ++ .../database-access/troubleshooting.mdx | 2 +- .../kubernetes-access/controls.mdx | 8 -- .../{ => discovery}/discovery.mdx | 6 +- .../kubernetes-access/faq.mdx | 3 - .../kubernetes-access/getting-started.mdx | 6 +- .../kubernetes-access/introduction.mdx | 4 +- .../{ => manage-access}/manage-access.mdx | 6 +- .../kubernetes-access/manage-access/rbac.mdx | 2 +- .../register-clusters.mdx | 10 +-- .../machine-id/access-guides.mdx | 25 ------- .../access-guides/access-guides.mdx | 25 +++++++ .../machine-id/access-guides/ansible.mdx | 2 +- .../machine-id/access-guides/applications.mdx | 2 +- .../machine-id/access-guides/databases.mdx | 4 +- .../machine-id/access-guides/kubernetes.mdx | 2 +- .../machine-id/access-guides/ssh.mdx | 2 +- .../machine-id/access-guides/tctl.mdx | 2 +- .../machine-id/access-guides/terraform.mdx | 2 +- .../machine-id/deployment.mdx | 74 ------------------- .../machine-id/deployment/aws.mdx | 2 +- .../machine-id/deployment/azure.mdx | 2 +- .../machine-id/deployment/circleci.mdx | 2 +- .../machine-id/deployment/deployment.mdx | 73 ++++++++++++++++++ .../machine-id/deployment/gcp.mdx | 2 +- .../machine-id/deployment/gitlab.mdx | 2 +- .../machine-id/deployment/kubernetes.mdx | 4 +- .../machine-id/deployment/linux.mdx | 2 +- .../machine-id/getting-started.mdx | 6 +- .../machine-id/introduction.mdx | 4 +- .../server-access/getting-started.mdx | 2 +- .../enroll-resources/server-access/guides.mdx | 22 ------ .../server-access/guides/guides.mdx | 22 ++++++ .../guides/host-user-creation.mdx | 2 +- .../server-access/guides/jetbrains-sftp.mdx | 4 +- .../server-access/guides/openssh.mdx | 9 --- .../server-access/guides/openssh/openssh.mdx | 9 +++ .../server-access/guides/vscode.mdx | 4 +- .../server-access/introduction.mdx | 2 +- .../enroll-resources/server-access/rbac.mdx | 2 +- docs/pages/faq.mdx | 2 +- .../aws-auto-discovery-prerequisite.mdx | 3 +- .../includes/database-access/create-user.mdx | 4 +- .../database-access/db-introduction.mdx | 2 +- .../database-access/guides-next-steps.mdx | 2 +- docs/pages/includes/edition-comparison.mdx | 5 +- .../includes/machine-id/configure-outputs.mdx | 5 +- .../machine-id/plugin-prerequisites.mdx | 5 +- docs/pages/index.mdx | 8 +- docs/pages/installation.mdx | 4 +- .../access-controls/access-lists.mdx | 2 +- .../pages/reference/access-controls/roles.mdx | 6 +- .../database-access-reference/cli.mdx | 2 +- .../architecture/agent-update-management.mdx | 7 +- .../architecture/api-architecture.mdx | 2 +- .../reference/architecture/architecture.mdx | 4 +- .../reference/architecture/authorization.mdx | 4 +- docs/pages/reference/architecture/nodes.mdx | 2 +- .../reference/architecture/tls-routing.mdx | 2 +- docs/pages/reference/{ => cli}/cli.mdx | 10 +-- docs/pages/reference/cloud-faq.mdx | 2 +- .../{ => helm-reference}/helm-reference.mdx | 16 ++-- .../helm-reference/teleport-cluster.mdx | 2 +- .../helm-reference/teleport-kube-agent.mdx | 10 +-- docs/pages/reference/monitoring/audit.mdx | 2 +- docs/pages/reference/predicate-language.mdx | 2 +- docs/pages/reference/resources.mdx | 8 +- docs/pages/upgrading/overview.mdx | 2 +- .../self-hosted-automatic-agent-updates.mdx | 2 +- docs/pages/{ => upgrading}/upgrading.mdx | 12 +-- 172 files changed, 638 insertions(+), 652 deletions(-) rename docs/pages/admin-guides/access-controls/{ => access-lists}/access-lists.mdx (74%) rename docs/pages/admin-guides/access-controls/{ => access-request-plugins}/access-request-plugins.mdx (98%) rename docs/pages/admin-guides/access-controls/{ => access-requests}/access-requests.mdx (85%) rename docs/pages/admin-guides/access-controls/{ => compliance-frameworks}/compliance-frameworks.mdx (83%) rename docs/pages/admin-guides/access-controls/{ => device-trust}/device-trust.mdx (94%) rename docs/pages/admin-guides/access-controls/{ => guides}/guides.mdx (100%) delete mode 100644 docs/pages/admin-guides/access-controls/idps.mdx create mode 100644 docs/pages/admin-guides/access-controls/idps/idps.mdx rename docs/pages/admin-guides/access-controls/{ => login-rules}/login-rules.mdx (90%) rename docs/pages/admin-guides/access-controls/{ => sso}/sso.mdx (89%) delete mode 100644 docs/pages/admin-guides/deploy-a-cluster/deployments.mdx create mode 100644 docs/pages/admin-guides/deploy-a-cluster/deployments/deployments.mdx rename docs/pages/admin-guides/deploy-a-cluster/{ => helm-deployments}/helm-deployments.mdx (55%) rename docs/pages/admin-guides/{ => infrastructure-as-code}/infrastructure-as-code.mdx (93%) rename docs/pages/admin-guides/infrastructure-as-code/{ => teleport-operator}/teleport-operator.mdx (97%) rename docs/pages/admin-guides/infrastructure-as-code/{ => terraform-provider}/terraform-provider.mdx (93%) delete mode 100644 docs/pages/admin-guides/management/admin.mdx create mode 100644 docs/pages/admin-guides/management/admin/admin.mdx delete mode 100644 docs/pages/admin-guides/management/diagnostics.mdx create mode 100644 docs/pages/admin-guides/management/diagnostics/diagnostics.mdx rename docs/pages/admin-guides/management/{ => export-audit-events}/export-audit-events.mdx (81%) rename docs/pages/admin-guides/management/{ => guides}/guides.mdx (68%) delete mode 100644 docs/pages/admin-guides/management/operations.mdx create mode 100644 docs/pages/admin-guides/management/operations/operations.mdx rename docs/pages/admin-guides/management/{ => security}/security.mdx (76%) rename docs/pages/contributing/{ => documentation}/documentation.mdx (76%) delete mode 100644 docs/pages/enroll-resources/agents/join-services-to-your-cluster.mdx create mode 100644 docs/pages/enroll-resources/agents/join-services-to-your-cluster/join-services-to-your-cluster.mdx rename docs/pages/enroll-resources/application-access/{ => cloud-apis}/cloud-apis.mdx (80%) rename docs/pages/enroll-resources/application-access/{ => enroll-kubernetes-applications}/enroll-kubernetes-applications.mdx (80%) create mode 100644 docs/pages/enroll-resources/application-access/enroll-kubernetes-applications/kubernetes-applications.mdx delete mode 100644 docs/pages/enroll-resources/application-access/guides.mdx create mode 100644 docs/pages/enroll-resources/application-access/guides/guides.mdx rename docs/pages/enroll-resources/application-access/{ => jwt}/jwt.mdx (62%) delete mode 100644 docs/pages/enroll-resources/application-access/okta.mdx create mode 100644 docs/pages/enroll-resources/application-access/okta/okta.mdx delete mode 100644 docs/pages/enroll-resources/database-access/auto-user-provisioning.mdx create mode 100644 docs/pages/enroll-resources/database-access/auto-user-provisioning/auto-user-provisioning.mdx rename docs/pages/enroll-resources/database-access/{ => guides}/guides.mdx (99%) delete mode 100644 docs/pages/enroll-resources/database-access/rbac.mdx create mode 100644 docs/pages/enroll-resources/database-access/rbac/rbac.mdx rename docs/pages/enroll-resources/kubernetes-access/{ => discovery}/discovery.mdx (96%) rename docs/pages/enroll-resources/kubernetes-access/{ => manage-access}/manage-access.mdx (81%) rename docs/pages/enroll-resources/kubernetes-access/{ => register-clusters}/register-clusters.mdx (60%) delete mode 100644 docs/pages/enroll-resources/machine-id/access-guides.mdx create mode 100644 docs/pages/enroll-resources/machine-id/access-guides/access-guides.mdx delete mode 100644 docs/pages/enroll-resources/machine-id/deployment.mdx create mode 100644 docs/pages/enroll-resources/machine-id/deployment/deployment.mdx delete mode 100644 docs/pages/enroll-resources/server-access/guides.mdx create mode 100644 docs/pages/enroll-resources/server-access/guides/guides.mdx delete mode 100644 docs/pages/enroll-resources/server-access/guides/openssh.mdx create mode 100644 docs/pages/enroll-resources/server-access/guides/openssh/openssh.mdx rename docs/pages/reference/{ => cli}/cli.mdx (79%) rename docs/pages/reference/{ => helm-reference}/helm-reference.mdx (60%) rename docs/pages/{ => upgrading}/upgrading.mdx (77%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b789e53cf89b..49acd47ed4f65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -859,7 +859,8 @@ applications in Kubernetes clusters. When connected to a Kubernetes cluster (or deployed as a Helm chart), the Teleport Discovery Service will automatically find and enroll web applications with your Teleport cluster. -See documentation [here](docs/pages/enroll-resources/application-access/enroll-kubernetes-applications.mdx). +See documentation +[here](docs/pages/enroll-resources/application-access/enroll-kubernetes-applications/kubernetes-applications.mdx). #### Extended Kubernetes per-resource RBAC @@ -1761,7 +1762,7 @@ This will allow users to view the OpenSSH nodes in Web UI and using `tsh ls` and use RBAC to control access to them. See the updated [OpenSSH integration -guide](docs/pages/enroll-resources/server-access/guides/openssh.mdx). +guide](docs/pages/enroll-resources/server-access/guides/openssh/openssh.mdx). ### Cross-cluster search for Teleport Connect @@ -2962,7 +2963,7 @@ is more than one major version behind them. You can use the `--skip-version-chec bypass the version check. Take a look at component compatibility guarantees in the -[documentation](docs/pages/upgrading.mdx). +[documentation](docs/pages/upgrading/upgrading.mdx). #### HTTP_PROXY for reverse tunnels @@ -3951,7 +3952,7 @@ if err = clt.CreateAccessRequest(ctx, accessRequest); err != nil { ### Upgrade Notes -Please follow our [standard upgrade procedure](docs/pages/admin-guides/management/admin.mdx) to upgrade your cluster. +Please follow our [standard upgrade procedure](docs/pages/admin-guides/management/admin/admin.mdx) to upgrade your cluster. Note, for clusters using GitHub SSO and Trusted Clusters, when upgrading SSO users will lose connectivity to leaf clusters. Local users will not be affected. @@ -4201,8 +4202,8 @@ Teleport 5.0 also iterates on the UI Refresh from 4.3. We've moved the cluster l Other updates: * We now provide local user management via `https://[cluster-url]/web/users`, providing the ability to edit, reset and delete local users. -* Teleport Node & App Install scripts. This is currently an Enterprise-only feature that provides customers with an installer script. Enterprise customers can enable this feature by modifying the 'token' resource. See note above. -* We've added a Waiting Room for customers using Access Workflows. [Docs](docs/pages/admin-guides/access-controls/access-request-plugins.mdx) +* Teleport Node & App Install scripts. This is currently an Enterprise-only feature that provides customers with an 'auto-magic' installer script. Enterprise customers can enable this feature by modifying the 'token' resource. See note above. +* We've added a Waiting Room for customers using Access Workflows. [Docs](docs/pages/admin-guides/access-controls/access-request-plugins/access-request-plugins.mdx) ##### Signed RPM and Releases @@ -4236,7 +4237,7 @@ We've added an [API Guide](docs/pages/admin-guides/api/api.mdx) to simply develo #### Upgrade Notes -Please follow our [standard upgrade procedure](./docs/pages/upgrading.mdx). +Please follow our [standard upgrade procedure](docs/pages/upgrading/upgrading.mdx). * Optional: Consider updating `https_key_file` & `https_cert_file` to our new `https_keypairs:` format. * Optional: Consider migrating Kubernetes access from `proxy_service` to `kubernetes_service` after the upgrade. @@ -4380,7 +4381,7 @@ auth_service: #### Upgrade Notes Please follow our [standard upgrade -procedure](docs/pages/upgrading.mdx). +procedure](docs/pages/upgrading/upgrading.mdx). ## 4.3.9 @@ -4465,7 +4466,7 @@ Teleport's Web UI now exposes Teleport’s Audit log, letting auditors and admin ##### Teleport Plugins -Teleport 4.3 introduces four new plugins that work out of the box with [Approval Workflow](docs/pages/admin-guides/access-controls/access-request-plugins.mdx). These plugins allow you to automatically support role escalation with commonly used third party services. The built-in plugins are listed below. +Teleport 4.3 introduces four new plugins that work out of the box with [Approval Workflow](docs/pages/admin-guides/access-controls/access-request-plugins/access-request-plugins.mdx). These plugins allow you to automatically support role escalation with commonly used third party services. The built-in plugins are listed below. * [PagerDuty](docs/pages/admin-guides/access-controls/access-request-plugins/ssh-approval-pagerduty.mdx) * [Jira](docs/pages/admin-guides/access-controls/access-request-plugins/ssh-approval-jira.mdx) @@ -4501,7 +4502,7 @@ Teleport 4.3 introduces four new plugins that work out of the box with [Approval #### Upgrade Notes Always follow the [recommended upgrade -procedure](./docs/pages/upgrading.mdx) to upgrade to this version. +procedure](docs/pages/upgrading/upgrading.mdx) to upgrade to this version. ##### New Signing Algorithm @@ -4542,7 +4543,7 @@ permissions](./docs/pages/enroll-resources/kubernetes-access/controls.mdx). The [etcd backend](docs/pages/reference/backends.mdx#etcd) now correctly uses the “prefix” config value when storing data. Upgrading from 4.2 to 4.3 will migrate the data as needed at startup. Make sure you follow our Teleport -[upgrade guidance](docs/pages/upgrading.mdx). +[upgrade guidance](docs/pages/upgrading/upgrading.mdx). **Note: If you use an etcd backend with a non-default prefix and need to downgrade from 4.3 to 4.2, you should [backup Teleport data and restore it](docs/pages/admin-guides/management/operations/backup-restore.mdx) into the downgraded cluster.** @@ -4665,7 +4666,7 @@ This is a minor Teleport release with a focus on new features and bug fixes. ### Improvements * Alpha: Enhanced Session Recording lets you know what's really happening during a Teleport Session. [#2948](https://github.com/gravitational/teleport/issues/2948) -* Alpha: Workflows API lets admins escalate RBAC roles in response to user requests. [Read the docs](docs/pages/admin-guides/access-controls/access-requests.mdx). [#3006](https://github.com/gravitational/teleport/issues/3006) +* Alpha: Workflows API lets admins escalate RBAC roles in response to user requests. [Read the docs](docs/pages/admin-guides/access-controls/access-requests/access-requests.mdx). [#3006](https://github.com/gravitational/teleport/issues/3006) * Beta: Teleport provides HA Support using Firestore and Google Cloud Storage using Google Cloud Platform. [Read the docs](docs/pages/admin-guides/deploy-a-cluster/deployments/gcp.mdx). [#2821](https://github.com/gravitational/teleport/pull/2821) * Remote tctl execution is now possible. [Read the docs](./docs/pages/reference/cli/tctl.mdx). [#1525](https://github.com/gravitational/teleport/issues/1525) [#2991](https://github.com/gravitational/teleport/issues/2991) @@ -4921,7 +4922,7 @@ The lists of improvements and bug fixes above mention only the significant chang ### Upgrading -Teleport 4.0 is backwards compatible with Teleport 3.2 and later. [Follow the recommended upgrade procedure to upgrade to this version.](docs/pages/upgrading.mdx) +Teleport 4.0 is backwards compatible with Teleport 3.2 and later. [Follow the recommended upgrade procedure to upgrade to this version.](docs/pages/upgrading/upgrading.mdx) Note that due to substantial changes between Teleport 3.2 and 4.0, we recommend creating a backup of the backend datastore (DynamoDB, etcd, or dir) before upgrading a cluster to Teleport 4.0 to allow downgrades. @@ -5189,7 +5190,7 @@ on Github for more. #### Upgrading to 3.0 Follow the [recommended upgrade -procedure](docs/pages/upgrading.mdx) to upgrade to this +procedure](docs/pages/upgrading/upgrading.mdx) to upgrade to this version. **WARNING:** if you are using Teleport with the etcd back-end, make sure your @@ -5295,7 +5296,7 @@ As always, this release contains several bug fixes. The full list can be seen [h #### Upgrading Follow the [recommended upgrade -procedure](docs/pages/upgrading.mdx) to upgrade to this +procedure](docs/pages/upgrading/upgrading.mdx) to upgrade to this version. ## 2.6.9 @@ -5425,7 +5426,7 @@ You can see the full list of 2.6.0 changes [here](https://github.com/gravitation #### Upgrading Follow the [recommended upgrade -procedure](docs/pages/upgrading.mdx) to upgrade to this +procedure](docs/pages/upgrading/upgrading.mdx) to upgrade to this version. ## 2.5.7 @@ -5512,7 +5513,7 @@ release, which includes: * The Teleport daemon now implements built-in connection draining which allows zero-downtime upgrades. [See - documentation](docs/pages/upgrading.mdx). + documentation](docs/pages/upgrading/upgrading.mdx). * Dynamic join tokens for new nodes can now be explicitly set via `tctl node add --token`. This allows Teleport admins to use an external mechanism for generating diff --git a/docs/config.json b/docs/config.json index fd37c89f43fdd..2a59b881febb5 100644 --- a/docs/config.json +++ b/docs/config.json @@ -22,7 +22,7 @@ }, { "title": "Upgrading", - "slug": "/upgrading/", + "slug": "/upgrading/upgrading/", "entries": [ { "title": "Compatibility Overview", diff --git a/docs/pages/admin-guides/access-controls/access-controls.mdx b/docs/pages/admin-guides/access-controls/access-controls.mdx index fbdb8af924a03..bf510140e1614 100644 --- a/docs/pages/admin-guides/access-controls/access-controls.mdx +++ b/docs/pages/admin-guides/access-controls/access-controls.mdx @@ -32,7 +32,7 @@ that specifies access policies for resources in your Teleport cluster. Assigning a role to a Teleport user applies the policies listed in the role to the user. -See the [Cluster Access and RBAC](./guides.mdx) section for instructions on +See the [Cluster Access and RBAC](guides/guides.mdx) section for instructions on setting up Teleport roles. ## Integrate with your Single Sign-On provider @@ -46,7 +46,7 @@ automatically assigns roles to the user based on data provided by the IdP. This means that you can implement a fully fledged infrastructure RBAC system based on your existing Single Sign-On solution. -Read our [Single Sign-On guide](./sso.mdx) to get started. +Read our [Single Sign-On guide](sso/sso.mdx) to get started. ## Enable Access Requests @@ -55,13 +55,13 @@ resources in your infrastructure based on the approval of other users. You can set up your RBAC so all privileged access is short lived, and there are no longstanding admin roles for attackers to hijack. -[Get started with Access Requests](./access-requests.mdx). +[Get started with Access Requests](access-requests/access-requests.mdx). You can integrate Teleport with your existing communication tool, e.g., Slack, PagerDuty, or Microsoft Teams, so Teleport users can easily create and approve Access Requests. -[Get started with Access Request plugins](access-request-plugins.mdx). +[Get started with Access Request plugins](access-request-plugins/access-request-plugins.mdx). ## Achieve compliance diff --git a/docs/pages/admin-guides/access-controls/access-lists.mdx b/docs/pages/admin-guides/access-controls/access-lists/access-lists.mdx similarity index 74% rename from docs/pages/admin-guides/access-controls/access-lists.mdx rename to docs/pages/admin-guides/access-controls/access-lists/access-lists.mdx index f2b504c966559..c7501f5f6e368 100644 --- a/docs/pages/admin-guides/access-controls/access-lists.mdx +++ b/docs/pages/admin-guides/access-controls/access-lists/access-lists.mdx @@ -9,6 +9,6 @@ managed within Teleport. With Access Lists, administrators and access list owners can regularly audit and control membership to specific roles and traits, which then tie easily back into Teleport's existing RBAC system. -[Getting Started with Access Lists](./access-lists/guide.mdx) +[Getting Started with Access Lists](guide.mdx) -[Access List Reference](../../reference/access-controls/access-lists.mdx) +[Access List Reference](../../../reference/access-controls/access-lists.mdx) diff --git a/docs/pages/admin-guides/access-controls/access-request-plugins.mdx b/docs/pages/admin-guides/access-controls/access-request-plugins/access-request-plugins.mdx similarity index 98% rename from docs/pages/admin-guides/access-controls/access-request-plugins.mdx rename to docs/pages/admin-guides/access-controls/access-request-plugins/access-request-plugins.mdx index e98548fd80f70..3d9dbd61c1c0c 100644 --- a/docs/pages/admin-guides/access-controls/access-request-plugins.mdx +++ b/docs/pages/admin-guides/access-controls/access-request-plugins/access-request-plugins.mdx @@ -55,4 +55,4 @@ workflows by reading our setup guides: To read more about the architecture of an Access Request plugin, and start writing your own, read our [Access Request plugin development -guide](../api/access-plugin.mdx). +guide](../../api/access-plugin.mdx). diff --git a/docs/pages/admin-guides/access-controls/access-requests.mdx b/docs/pages/admin-guides/access-controls/access-requests/access-requests.mdx similarity index 85% rename from docs/pages/admin-guides/access-controls/access-requests.mdx rename to docs/pages/admin-guides/access-controls/access-requests/access-requests.mdx index 9e820ac3a8a7c..6ca980f2db8c2 100644 --- a/docs/pages/admin-guides/access-controls/access-requests.mdx +++ b/docs/pages/admin-guides/access-controls/access-requests/access-requests.mdx @@ -16,7 +16,7 @@ be configured with limited cluster access so they are not high value targets. Access Requests are designed to provide temporary permissions to users. If you want to grant longstanding permissions to a group of users, with the option to renew these permissions after a recurring interval (such as three months), -consider [Access Lists](access-lists.mdx). +consider [Access Lists](../access-lists/access-lists.mdx). ## See how Access Requests work @@ -26,12 +26,12 @@ and **Resource Access Requests**. With Role Access Requests, engineers can request temporary credentials with elevated roles in order to perform critical system-wide tasks. -[Get started with Role Access Requests](./access-requests/role-requests.mdx). +[Get started with Role Access Requests](role-requests.mdx). With Resource Access Requests, engineers can easily get access to only the individual resources they need, when they need it. -[Get started with Resource Access Requests](./access-requests/resource-requests.mdx). +[Get started with Resource Access Requests](resource-requests.mdx). ## Configure Access Requests @@ -44,7 +44,7 @@ including: - How many users can approve or deny different kinds of requests. Read the [Access Request -Configuration](access-requests/access-request-configuration.mdx) guide for an +Configuration](access-request-configuration.mdx) guide for an overview of the configuration options available for Access Requests. ## Teleport Community Edition users @@ -56,6 +56,6 @@ including Resource Access Requests managing Access Requests via the Web UI are available in Teleport Enterprise. For information on how to use Just-in-time Access Requests with Teleport Community -Edition, see [Teleport Community Access Requests](./access-requests/oss-role-requests.mdx). +Edition, see [Teleport Community Access Requests](oss-role-requests.mdx). diff --git a/docs/pages/admin-guides/access-controls/access-requests/oss-role-requests.mdx b/docs/pages/admin-guides/access-controls/access-requests/oss-role-requests.mdx index 7e08b72e09aad..cd364ddc76544 100644 --- a/docs/pages/admin-guides/access-controls/access-requests/oss-role-requests.mdx +++ b/docs/pages/admin-guides/access-controls/access-requests/oss-role-requests.mdx @@ -153,7 +153,7 @@ $ tctl request approve \ ## Next Steps -- Learn more about [Access Requests](../access-requests.mdx) +- Learn more about [Access Requests](access-requests.mdx) - See what additional features are available for [role requests](./role-requests.mdx) in Teleport Enterprise - Request access to [specific resources](./resource-requests.mdx) with Teleport Enterprise \ No newline at end of file diff --git a/docs/pages/admin-guides/access-controls/access-requests/resource-requests.mdx b/docs/pages/admin-guides/access-controls/access-requests/resource-requests.mdx index d377f2d5aac66..a25c57eb0295a 100644 --- a/docs/pages/admin-guides/access-controls/access-requests/resource-requests.mdx +++ b/docs/pages/admin-guides/access-controls/access-requests/resource-requests.mdx @@ -165,7 +165,7 @@ However, it prevents you from access any resources belonging to another namespac Advanced filters and queries are supported. See our -[filtering reference](../../../reference/cli.mdx) for more information. +[filtering reference](../../../reference/cli/cli.mdx) for more information. Try narrowing your search to a specific resource you want to access. @@ -636,4 +636,4 @@ within your organization's existing messaging and project management solutions. ## Next Steps -- Learn more about [Access Lists](../access-lists.mdx) +- Learn more about [Access Lists](../access-lists/access-lists.mdx) diff --git a/docs/pages/admin-guides/access-controls/access-requests/role-requests.mdx b/docs/pages/admin-guides/access-controls/access-requests/role-requests.mdx index e6d9d16a7b043..cf225d2e7c6b8 100644 --- a/docs/pages/admin-guides/access-controls/access-requests/role-requests.mdx +++ b/docs/pages/admin-guides/access-controls/access-requests/role-requests.mdx @@ -170,5 +170,5 @@ just-in-time Access Request workflow for your organization. Access Lists enable you to assign privileges to groups of users for a fixed period of time. Learn more about Access Lists in the -[documentation](../access-lists.mdx). +[documentation](../access-lists/access-lists.mdx). diff --git a/docs/pages/admin-guides/access-controls/compliance-frameworks.mdx b/docs/pages/admin-guides/access-controls/compliance-frameworks/compliance-frameworks.mdx similarity index 83% rename from docs/pages/admin-guides/access-controls/compliance-frameworks.mdx rename to docs/pages/admin-guides/access-controls/compliance-frameworks/compliance-frameworks.mdx index 7bc35e8c84a49..6ee2dcd7f4484 100644 --- a/docs/pages/admin-guides/access-controls/compliance-frameworks.mdx +++ b/docs/pages/admin-guides/access-controls/compliance-frameworks/compliance-frameworks.mdx @@ -10,5 +10,5 @@ settings within Teleport. Follow our guides to see how to use Teleport to achieve compliance: -- [FedRAMP](./compliance-frameworks/fedramp.mdx) -- [SOC 2](./compliance-frameworks/soc2.mdx) +- [FedRAMP](fedramp.mdx) +- [SOC 2](soc2.mdx) diff --git a/docs/pages/admin-guides/access-controls/compliance-frameworks/soc2.mdx b/docs/pages/admin-guides/access-controls/compliance-frameworks/soc2.mdx index 784bb3c83edf0..2b2f04847c331 100644 --- a/docs/pages/admin-guides/access-controls/compliance-frameworks/soc2.mdx +++ b/docs/pages/admin-guides/access-controls/compliance-frameworks/soc2.mdx @@ -58,16 +58,16 @@ Each principle has many "Points of Focus" which will apply differently to differ | CC6.1 - Manages Credentials for Infrastructure and Software | New internal and external infrastructure and software are registered, authorized, and documented prior to being granted access credentials and implemented on the network or access point. Credentials are removed and access is disabled when access is no longer required or the infrastructure and software are no longer in use. | [Invite nodes to your cluster with short lived tokens](../../../enroll-resources/agents/join-services-to-your-cluster/join-token.mdx) | | CC6.1 - Uses Encryption to Protect Data | The entity uses encryption to supplement other measures used to protect data at rest, when such protections are deemed appropriate based on assessed risk. | Teleport Audit logs can use DynamoDB encryption at rest. | | CC6.1 - Protects Encryption Keys | Processes are in place to protect encryption keys during generation, storage, use, and destruction. | Teleport acts as a Certificate Authority to issue SSH and x509 user certificates that are signed by the CA and are (by default) short-lived. SSH host certificates are also signed by the CA and rotated automatically | -| CC6.2 - Controls Access Credentials to Protected Assets | Information asset access credentials are created based on an authorization from the system's asset owner or authorized custodian. | [Request Approval from the command line](../../../reference/cli/tctl.mdx)

[Build Approval Workflows with Access Requests](../../access-controls/access-requests.mdx)

[Use Plugins to send approvals to tools like Slack or Jira](../../access-controls/access-requests.mdx) | -| CC6.2 - Removes Access to Protected Assets When Appropriate | Processes are in place to remove credential access when an individual no longer requires such access. | [Teleport issues temporary credentials based on an employees role and are revoked upon job change, termination or end of a maintenance window](../../access-controls/access-requests.mdx) | +| CC6.2 - Controls Access Credentials to Protected Assets | Information asset access credentials are created based on an authorization from the system's asset owner or authorized custodian. | [Request Approval from the command line](../../../reference/cli/tctl.mdx)

[Build Approval Workflows with Access Requests](../access-requests/access-requests.mdx)

[Use Plugins to send approvals to tools like Slack or Jira](../access-requests/access-requests.mdx) | +| CC6.2 - Removes Access to Protected Assets When Appropriate | Processes are in place to remove credential access when an individual no longer requires such access. | [Teleport issues temporary credentials based on an employees role and are revoked upon job change, termination or end of a maintenance window](../access-requests/access-requests.mdx) | | CC6.2 - Reviews Appropriateness of Access Credentials | The appropriateness of access credentials is reviewed on a periodic basis for unnecessary and inappropriate individuals with credentials. | Teleport maintains a live list of all nodes within a cluster. This node list can be queried by users (who see a subset they have access to) and administrators any time. | -| CC6.3 - Creates or Modifies Access to Protected Information Assets | Processes are in place to create or modify access to protected information assets based on authorization from the asset’s owner. | [Build Approval Workflows with Access Requests](../../access-controls/access-requests.mdx) to get authorization from asset owners. | +| CC6.3 - Creates or Modifies Access to Protected Information Assets | Processes are in place to create or modify access to protected information assets based on authorization from the asset’s owner. | [Build Approval Workflows with Access Requests](../access-requests/access-requests.mdx) to get authorization from asset owners. | | CC6.3 - Removes Access to Protected Information Assets | Processes are in place to remove access to protected information assets when an individual no longer requires access. | Teleport uses temporary credentials and can be integrated with your version control system or even your HR system to [revoke access with the Access requests API](../../api/api.mdx) | | CC6.3 - Uses Role-Based Access Controls | Role-based access control is utilized to support segregation of incompatible functions. | [Role based access control ("RBAC") allows Teleport administrators to grant granular access permissions to users.](../access-controls.mdx) | | CC6.3 - Reviews Access Roles and Rules | The appropriateness of access roles and access rules is reviewed on a periodic basis for unnecessary and inappropriate individuals with access and access rules are modified as appropriate. | Teleport maintains a live list of all nodes within a cluster. This node list can be queried by users (who see a subset they have access to) and administrators any time. | | CC6.6 - Restricts Access | The types of activities that can occur through a communication channel (for example, FTP site, router port) are restricted. | Teleport makes it easy to restrict access to common ports like 21, 22 and instead have users [tunnel to the server](../../../faq.mdx) using Teleport. [Teleport uses the following default ports.](../../../reference/networking.mdx) | | CC6.6 - Protects Identification and Authentication Credentials | Identification and authentication credentials are protected during transmission outside system boundaries. | [Yes, Teleport protects credentials outside your network allowing for Zero Trust network architecture](https://goteleport.com/blog/applying-principles-of-zero-trust-to-ssh/) | -| CC6.6 - Requires Additional Authentication or Credentials | Additional authentication information or credentials are required when accessing the system from outside its boundaries. | [Yes, Teleport can manage MFA with TOTP, WebAuthn or U2F Standards or connect to your Identity Provider using SAML, OAUTH or OIDC](../../access-controls/sso.mdx) | +| CC6.6 - Requires Additional Authentication or Credentials | Additional authentication information or credentials are required when accessing the system from outside its boundaries. | [Yes, Teleport can manage MFA with TOTP, WebAuthn or U2F Standards or connect to your Identity Provider using SAML, OAUTH or OIDC](../sso/sso.mdx) | | CC6.6 - Implements Boundary Protection Systems | Boundary protection systems (for example, firewalls, demilitarized zones, and intrusion detection systems) are implemented to protect external access points from attempts and unauthorized access and are monitored to detect such attempts. | [Trusted clusters](../../management/admin/trustedclusters.mdx) | | CC6.7 - Uses Encryption Technologies or Secure Communication Channels to Protect Data | Encryption technologies or secured communication channels are used to protect transmission of data and other communications beyond connectivity access points. | [Teleport has strong encryption including a FedRAMP compliant FIPS mode](./fedramp.mdx#start-teleport-in-fips-mode) | | CC7.2 - Implements Detection Policies, Procedures, and Tools | Processes are in place to detect changes to software and configuration parameters that may be indicative of unauthorized or malicious software. | [Teleport creates detailed SSH Audit Logs with Metadata](../../../reference/monitoring/audit.mdx)

[Use BPF Session Recording to catch malicious program execution](../../../enroll-resources/server-access/guides/bpf-session-recording.mdx) | diff --git a/docs/pages/admin-guides/access-controls/device-trust.mdx b/docs/pages/admin-guides/access-controls/device-trust/device-trust.mdx similarity index 94% rename from docs/pages/admin-guides/access-controls/device-trust.mdx rename to docs/pages/admin-guides/access-controls/device-trust/device-trust.mdx index c918933bf3e85..acb1da81d4520 100644 --- a/docs/pages/admin-guides/access-controls/device-trust.mdx +++ b/docs/pages/admin-guides/access-controls/device-trust/device-trust.mdx @@ -91,7 +91,7 @@ enforcement and Cluster-wide enforcement. ## Guides -- [Getting Started with Device Trust](./device-trust/guide.mdx) -- [Device Management](./device-trust/device-management.mdx) -- [Enforcing Device Trust](./device-trust/enforcing-device-trust.mdx) -- [Jamf Pro Integration](./device-trust/jamf-integration.mdx) +- [Getting Started with Device Trust](guide.mdx) +- [Device Management](device-management.mdx) +- [Enforcing Device Trust](enforcing-device-trust.mdx) +- [Jamf Pro Integration](jamf-integration.mdx) diff --git a/docs/pages/admin-guides/access-controls/device-trust/jamf-integration.mdx b/docs/pages/admin-guides/access-controls/device-trust/jamf-integration.mdx index 38a4c553582a4..6bd2d53cbe122 100644 --- a/docs/pages/admin-guides/access-controls/device-trust/jamf-integration.mdx +++ b/docs/pages/admin-guides/access-controls/device-trust/jamf-integration.mdx @@ -14,7 +14,7 @@ Teleport if a computer is removed from Jamf Pro. Syncing devices from Jamf Pro is an **inventory management** step, equivalent to automatically running the corresponding `tctl devices add` commands. -See the [Device Trust guide](../device-trust.mdx) for fundamental Device Trust concepts +See the [Device Trust guide](device-trust.mdx) for fundamental Device Trust concepts and behavior.

diff --git a/docs/pages/admin-guides/access-controls/guides/dual-authz.mdx b/docs/pages/admin-guides/access-controls/guides/dual-authz.mdx index a2911d4988df2..74819d3612c15 100644 --- a/docs/pages/admin-guides/access-controls/guides/dual-authz.mdx +++ b/docs/pages/admin-guides/access-controls/guides/dual-authz.mdx @@ -14,7 +14,7 @@ In this guide, we will set up Teleport's Just-in-Time Access Requests to require the approval of two team members for a privileged role `dbadmin`. The steps below describe how to use Teleport with Mattermost. You can also -[integrate with many other providers](../access-requests.mdx). +[integrate with many other providers](../access-requests/access-requests.mdx). diff --git a/docs/pages/admin-guides/access-controls/guides.mdx b/docs/pages/admin-guides/access-controls/guides/guides.mdx similarity index 100% rename from docs/pages/admin-guides/access-controls/guides.mdx rename to docs/pages/admin-guides/access-controls/guides/guides.mdx diff --git a/docs/pages/admin-guides/access-controls/guides/locking.mdx b/docs/pages/admin-guides/access-controls/guides/locking.mdx index 1141a216a01d6..bb8cef918c4b2 100644 --- a/docs/pages/admin-guides/access-controls/guides/locking.mdx +++ b/docs/pages/admin-guides/access-controls/guides/locking.mdx @@ -22,7 +22,7 @@ A lock can target the following objects or attributes: - a Teleport agent by the agent's server UUID (effectively unregistering it from the cluster) - a Windows desktop by the desktop's name -- an [Access Request](../access-requests.mdx) by UUID +- an [Access Request](../access-requests/access-requests.mdx) by UUID ## Prerequisites diff --git a/docs/pages/admin-guides/access-controls/idps.mdx b/docs/pages/admin-guides/access-controls/idps.mdx deleted file mode 100644 index 347b7840b9391..0000000000000 --- a/docs/pages/admin-guides/access-controls/idps.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Configure Teleport as an identity provider -description: How to set up Teleport's identity provider functionality ---- - -Users can authenticate to both internal and external applications -through the use of a built in identity provider in Teleport. - -- [SAML Guide](./idps/saml-guide.mdx): A guide for setting up an example application to integration with the SAML identity provider. -- [SAML Attribute Mapping](./idps/saml-attribute-mapping.mdx): A reference on how attribute mapping works in Teleport and how to -use it to assert custom user attribute name and values in a SAML response. -- [Use Teleport's SAML Provider to authenticate with Grafana](./idps/saml-grafana.mdx): Configure Grafana to authenticate using Teleport identities. -- [SAML Reference](../../reference/access-controls/saml-idp.mdx): A reference for Teleport's SAML identity provider. diff --git a/docs/pages/admin-guides/access-controls/idps/idps.mdx b/docs/pages/admin-guides/access-controls/idps/idps.mdx new file mode 100644 index 0000000000000..2c4daa37a4ae8 --- /dev/null +++ b/docs/pages/admin-guides/access-controls/idps/idps.mdx @@ -0,0 +1,13 @@ +--- +title: Configure Teleport as an identity provider +description: How to set up Teleport's identity provider functionality +--- + +Users can authenticate to both internal and external applications +through the use of a built in identity provider in Teleport. + +- [SAML Guide](saml-guide.mdx): A guide for setting up an example application to integration with the SAML identity provider. +- [SAML Attribute Mapping](saml-attribute-mapping.mdx): A reference on how attribute mapping works in Teleport and how to +use it to assert custom user attribute name and values in a SAML response. +- [Use Teleport's SAML Provider to authenticate with Grafana](saml-grafana.mdx): Configure Grafana to authenticate using Teleport identities. +- [SAML Reference](../../../reference/access-controls/saml-idp.mdx): A reference for Teleport's SAML identity provider. diff --git a/docs/pages/admin-guides/access-controls/login-rules/guide.mdx b/docs/pages/admin-guides/access-controls/login-rules/guide.mdx index c42a66e4548c3..9ddcc3203a72e 100644 --- a/docs/pages/admin-guides/access-controls/login-rules/guide.mdx +++ b/docs/pages/admin-guides/access-controls/login-rules/guide.mdx @@ -17,7 +17,7 @@ cluster on version `11.3.1` or greater. Login Rules only operate on SSO logins, so make sure you have configured an OIDC, SAML, or GitHub connector before you begin. -Check the [Single Sign-On](../sso.mdx) docs to learn how to set this up. +Check the [Single Sign-On](../sso/sso.mdx) docs to learn how to set this up. ## Step 1/5. Configure RBAC diff --git a/docs/pages/admin-guides/access-controls/login-rules.mdx b/docs/pages/admin-guides/access-controls/login-rules/login-rules.mdx similarity index 90% rename from docs/pages/admin-guides/access-controls/login-rules.mdx rename to docs/pages/admin-guides/access-controls/login-rules/login-rules.mdx index 3b4ca696ae8d6..f1aa1fdb8cb57 100644 --- a/docs/pages/admin-guides/access-controls/login-rules.mdx +++ b/docs/pages/admin-guides/access-controls/login-rules/login-rules.mdx @@ -20,7 +20,7 @@ Some use cases for Login Rules are: traits will be included in your user's SSH certificates and JWTs, which can become too large for some third-party applications to handle. Login Rules can filter out unnecessary traits and keep just the ones you need. -- When you have multiple [Role Templates](./guides/role-templates.mdx) repeating +- When you have multiple [Role Templates](../guides/role-templates.mdx) repeating the same logic to combine and transform external traits, consider using Login Rules to consolidate the logic to one place and simplify your Roles. @@ -43,13 +43,13 @@ traits_map: - 'ifelse(external.groups.contains("db-admins"), external.groups.add("db-users"), external.groups)' ``` -Check out the [Login Rules guide](./login-rules/guide.mdx) for a quick walkthrough +Check out the [Login Rules guide](guide.mdx) for a quick walkthrough that will show you how to write, test, and add the first Login Rule to your -cluster. See [example Login Rules](./login-rules/guide.mdx#example-login-rules) to +cluster. See [example Login Rules](guide.mdx) to learn how to address common use cases. When you're ready to take full advantage of Login Rules in your cluster, see the -[Login Rules Reference](../../reference/access-controls/login-rules.mdx) for details on the expression +[Login Rules Reference](../../../reference/access-controls/login-rules.mdx) for details on the expression language that powers them. ## FAQ diff --git a/docs/pages/admin-guides/access-controls/sso.mdx b/docs/pages/admin-guides/access-controls/sso/sso.mdx similarity index 89% rename from docs/pages/admin-guides/access-controls/sso.mdx rename to docs/pages/admin-guides/access-controls/sso/sso.mdx index 68944e85814a1..f0c336b62e517 100644 --- a/docs/pages/admin-guides/access-controls/sso.mdx +++ b/docs/pages/admin-guides/access-controls/sso/sso.mdx @@ -7,15 +7,15 @@ Teleport users can log in to servers, Kubernetes clusters, databases, web applications, and Windows desktops through their organization's Single Sign-On (SSO) provider. -- [Azure Active Directory (AD)](./sso/azuread.mdx): Configure Azure Active Directory SSO for SSH, Kubernetes, databases, desktops and web apps. -- [Active Directory (ADFS)](./sso/adfs.mdx): Configure Windows Active Directory SSO for SSH, Kubernetes, databases, desktops and web apps. -- [Google Workspace](./sso/google-workspace.mdx): Configure Google Workspace SSO for SSH, Kubernetes, databases, desktops and web apps. -- [GitHub](./sso/github-sso.mdx): Configure GitHub SSO for SSH, +- [Azure Active Directory (AD)](azuread.mdx): Configure Azure Active Directory SSO for SSH, Kubernetes, databases, desktops and web apps. +- [Active Directory (ADFS)](adfs.mdx): Configure Windows Active Directory SSO for SSH, Kubernetes, databases, desktops and web apps. +- [Google Workspace](google-workspace.mdx): Configure Google Workspace SSO for SSH, Kubernetes, databases, desktops and web apps. +- [GitHub](github-sso.mdx): Configure GitHub SSO for SSH, Kubernetes, databases, desktops, and web apps. -- [GitLab](./sso/gitlab.mdx): Configure GitLab SSO for SSH, Kubernetes, databases, desktops and web apps. -- [OneLogin](./sso/one-login.mdx): Configure OneLogin SSO for SSH, Kubernetes, databases, desktops and web apps. -- [OIDC](./sso/oidc.mdx): Configure OIDC SSO for SSH, Kubernetes, databases, desktops and web apps. -- [Okta](./sso/okta.mdx): Configure Okta SSO for SSH, Kubernetes, databases, desktops and web apps. +- [GitLab](gitlab.mdx): Configure GitLab SSO for SSH, Kubernetes, databases, desktops and web apps. +- [OneLogin](one-login.mdx): Configure OneLogin SSO for SSH, Kubernetes, databases, desktops and web apps. +- [OIDC](oidc.mdx): Configure OIDC SSO for SSH, Kubernetes, databases, desktops and web apps. +- [Okta](okta.mdx): Configure Okta SSO for SSH, Kubernetes, databases, desktops and web apps. ## How Teleport uses SSO @@ -392,9 +392,9 @@ flow. These provider-specific changes can be enabled by setting the values to match your identity provider: - `adfs` (SAML): Required for compatibility with Active Directory (ADFS); refer - to the full [ADFS guide](./sso/adfs.mdx#step-23-create-teleport-roles) for details. + to the full [ADFS guide](adfs.mdx) for details. - `netiq` (OIDC): Used to enable NetIQ-specific ACR value processing; refer to - the [OIDC guide](./sso/oidc.mdx#optional-acr-values) for details. + the [OIDC guide](oidc.mdx) for details. - `ping` (SAML and OIDC): Required for compatibility with Ping Identity (including PingOne and PingFederate). - `okta` (OIDC): Required when using Okta as an OIDC provider. @@ -446,7 +446,7 @@ $ tctl get connectors ``` To delete/update connectors, use the usual `tctl rm` and `tctl create` commands -as described in the [Resources Reference](../../reference/resources.mdx). +as described in the [Resources Reference](../../../reference/resources.mdx). If multiple authentication connectors exist, the clients must supply a connector name to `tsh login` via `--auth` argument: @@ -462,10 +462,10 @@ $ tsh --proxy=proxy.example.com login --auth=local --user=admin Refer to the following guides to configure authentication connectors of both SAML and OIDC types: -- [SSH Authentication with Okta](./sso/okta.mdx) -- [SSH Authentication with OneLogin](./sso/one-login.mdx) -- [SSH Authentication with ADFS](./sso/adfs.mdx) -- [SSH Authentication with OAuth2 / OpenID Connect](./sso/oidc.mdx) +- [SSH Authentication with Okta](okta.mdx) +- [SSH Authentication with OneLogin](one-login.mdx) +- [SSH Authentication with ADFS](adfs.mdx) +- [SSH Authentication with OAuth2 / OpenID Connect](oidc.mdx) ## SSO customization @@ -474,11 +474,11 @@ of SSO buttons in the Teleport Web UI. | Provider | YAML | Example | | - | - | - | -| GitHub | `display: GitHub` | ![github](../../../img/teleport-sso/github@2x.png) | -| Microsoft | `display: Microsoft` | ![microsoft](../../../img/teleport-sso/microsoft@2x.png) | -| Google | `display: Google` | ![google](../../../img/teleport-sso/google@2x.png) | -| BitBucket | `display: Bitbucket` | ![bitbucket](../../../img/teleport-sso/bitbucket@2x.png) | -| OpenID | `display: Okta` | ![Okta](../../../img/teleport-sso/openId@2x.png) | +| GitHub | `display: GitHub` | ![github](../../../../img/teleport-sso/github@2x.png) | +| Microsoft | `display: Microsoft` | ![microsoft](../../../../img/teleport-sso/microsoft@2x.png) | +| Google | `display: Google` | ![google](../../../../img/teleport-sso/google@2x.png) | +| BitBucket | `display: Bitbucket` | ![bitbucket](../../../../img/teleport-sso/bitbucket@2x.png) | +| OpenID | `display: Okta` | ![Okta](../../../../img/teleport-sso/openId@2x.png) | ## Troubleshooting @@ -506,7 +506,7 @@ If something is not working, we recommend to: If you get "access denied" or other login errors, the number one place to check is the Audit Log. You can access it in the **Activity** tab of the Teleport Web UI. -![Audit Log Entry for SSO Login error](../../../img/sso/teleportauditlogssofailed.png) +![Audit Log Entry for SSO Login error](../../../../img/sso/teleportauditlogssofailed.png) Example of a user being denied because the role `clusteradmin` wasn't set up: @@ -551,5 +551,5 @@ The roles we illustrated in this guide use `external` traits, which Teleport replaces with values from the single sign-on provider that the user used to authenticate with Teleport. For full details on how variable expansion works in Teleport roles, see the [Teleport Access Controls -Reference](../../reference/access-controls/roles.mdx). +Reference](../../../reference/access-controls/roles.mdx). diff --git a/docs/pages/admin-guides/api/access-plugin.mdx b/docs/pages/admin-guides/api/access-plugin.mdx index c15e7a9c95c11..329885add716d 100644 --- a/docs/pages/admin-guides/api/access-plugin.mdx +++ b/docs/pages/admin-guides/api/access-plugin.mdx @@ -3,12 +3,12 @@ title: How to Build an Access Request Plugin description: Manage Access Requests using custom workflows with the Teleport API --- -With Teleport [Access Requests](../access-controls/access-requests.mdx), you can +With Teleport [Access Requests](../access-controls/access-requests/access-requests.mdx), you can assign Teleport users to less privileged roles by default and allow them to temporarily escalate their privileges. Reviewers can grant or deny Access Requests within your organization's existing communication workflows (e.g., Slack, email, and PagerDuty) using [Access Request -plugins](../access-controls/access-request-plugins.mdx). +plugins](../access-controls/access-request-plugins/access-request-plugins.mdx). You can use Teleport's API client library to build an Access Request plugin that integrates with your organization's unique workflows. diff --git a/docs/pages/admin-guides/api/api.mdx b/docs/pages/admin-guides/api/api.mdx index 7a4233680e87e..c376b1271bfb0 100644 --- a/docs/pages/admin-guides/api/api.mdx +++ b/docs/pages/admin-guides/api/api.mdx @@ -11,13 +11,13 @@ cluster. In this section, we will show you how to use Teleport's API. Teleport has a public [Go client](https://pkg.go.dev/github.com/gravitational/teleport/api/client) to -programatically interact with the API. [tsh and tctl](../../reference/cli.mdx) use +programatically interact with the API. [tsh and tctl](../../reference/cli/cli.mdx) use the same API. Here is what you can do with the Go Client: - Integrate with external tools, e.g., to write an [Access Request - plugin](../access-controls/access-request-plugins.mdx). Teleport + plugin](../access-controls/access-request-plugins/access-request-plugins.mdx). Teleport maintains Access Request plugins for tools like Slack, Jira, and Mattermost. - Perform CRUD actions on resources, such as roles, authentication connectors, and provisioning tokens. diff --git a/docs/pages/admin-guides/api/automatically-register-agents.mdx b/docs/pages/admin-guides/api/automatically-register-agents.mdx index 173b93a8f9e03..edbbbd006d5ec 100644 --- a/docs/pages/admin-guides/api/automatically-register-agents.mdx +++ b/docs/pages/admin-guides/api/automatically-register-agents.mdx @@ -6,12 +6,6 @@ description: Learn how to use the Teleport API to start agents automatically whe You can use Teleport's API to automatically register resources in your infrastructure with your Teleport cluster. -Teleport already supports the automatic discovery of [Kubernetes -clusters](../../enroll-resources/kubernetes-access/discovery.mdx) in AWS, Azure, and Google Cloud, -as well as [servers](../../enroll-resources/server-access/guides/ec2-discovery.mdx) on Amazon EC2. -To support other resources and cloud providers, you can use the API to write -your own workflow. - In this guide, we will demonstrate some libraries you can use to automatically register resources with Teleport. We will use an example you can run locally on your workstation. diff --git a/docs/pages/admin-guides/api/getting-started.mdx b/docs/pages/admin-guides/api/getting-started.mdx index 4f9287fc5e14e..cfdbe207dedc5 100644 --- a/docs/pages/admin-guides/api/getting-started.mdx +++ b/docs/pages/admin-guides/api/getting-started.mdx @@ -127,4 +127,4 @@ $ go run main.go - Read about Teleport [API architecture](../../reference/architecture/api-architecture.mdx) for an in-depth overview of the API and API clients. - Read [API authorization](../../reference/architecture/api-architecture.mdx) to learn more about defining custom roles for your API client. - Review the `client` [pkg.go reference documentation](https://pkg.go.dev/github.com/gravitational/teleport/api/client) for more information about working with the Teleport API programmatically. -- Familiarize yourself with the [admin manual](../management/admin.mdx) to make the best use of the API. +- Familiarize yourself with the [admin manual](../management/admin/admin.mdx) to make the best use of the API. diff --git a/docs/pages/admin-guides/api/rbac.mdx b/docs/pages/admin-guides/api/rbac.mdx index 83e1fe363e3c6..c15efe0a06138 100644 --- a/docs/pages/admin-guides/api/rbac.mdx +++ b/docs/pages/admin-guides/api/rbac.mdx @@ -938,7 +938,7 @@ See the links below for guides to fields related to different infrastructure resources: - [Servers](../../enroll-resources/server-access/rbac.mdx) -- [Databases](../../enroll-resources/database-access/rbac.mdx) +- [Databases](../../enroll-resources/database-access/rbac/rbac.mdx) - [Kubernetes clusters](../../enroll-resources/kubernetes-access/manage-access/rbac.mdx) - [Windows Desktops](../../enroll-resources/desktop-access/rbac.mdx) - [Applications](../../enroll-resources/application-access/controls.mdx) diff --git a/docs/pages/admin-guides/deploy-a-cluster/access-graph/self-hosted-helm.mdx b/docs/pages/admin-guides/deploy-a-cluster/access-graph/self-hosted-helm.mdx index cf346f50edfe0..9451bb2489b92 100644 --- a/docs/pages/admin-guides/deploy-a-cluster/access-graph/self-hosted-helm.mdx +++ b/docs/pages/admin-guides/deploy-a-cluster/access-graph/self-hosted-helm.mdx @@ -24,7 +24,7 @@ to Teleport Enterprise customers. - Helm >= (=helm.version=) - A running Teleport Enterprise cluster v14.3.6 or later. - For the purposes of this guide, we assume that the Teleport cluster is set up - [using the `teleport-cluster` Helm chart](../../deploy-a-cluster/helm-deployments.mdx) + [using the `teleport-cluster` Helm chart](../helm-deployments/helm-deployments.mdx) in the same Kubernetes cluster that will be used to deploy Teleport Access Graph. - An updated `license.pem` with Teleport Policy enabled. - A PostgreSQL database server v14 or later. diff --git a/docs/pages/admin-guides/deploy-a-cluster/deployments.mdx b/docs/pages/admin-guides/deploy-a-cluster/deployments.mdx deleted file mode 100644 index 706eb1c405ea9..0000000000000 --- a/docs/pages/admin-guides/deploy-a-cluster/deployments.mdx +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Reference Deployment Guides -description: Teleport Installation and Configuration Reference Deployment Guides. -layout: tocless-doc ---- - -These guides show you how to set up a full self-hosted Teleport deployment on -the platform of your choice. - -- [AWS High Availability Deployment with Terraform](./deployments/aws-ha-autoscale-cluster-terraform.mdx): Deploy HA Teleport with - Terraform on AWS. -- [AWS Single-Instance Deployment with Terraform](./deployments/aws-starter-cluster-terraform.mdx): Deploy Teleport on a single instance with - Terraform on AWS. -- [AWS Multi-Region Proxy - Deployment](./deployments/aws-gslb-proxy-peering-ha-deployment.mdx): Deploy HA - Teleport with Proxy Service instances in multiple regions for low-latency - access. -- [GCP](./deployments/gcp.mdx): Deploy HA Teleport on GCP. -- [IBM Cloud](./deployments/ibm.mdx): Deploy HA Teleport on IBM cloud. diff --git a/docs/pages/admin-guides/deploy-a-cluster/deployments/aws-ha-autoscale-cluster-terraform.mdx b/docs/pages/admin-guides/deploy-a-cluster/deployments/aws-ha-autoscale-cluster-terraform.mdx index b74e7e8b3c141..cad211bff1af8 100644 --- a/docs/pages/admin-guides/deploy-a-cluster/deployments/aws-ha-autoscale-cluster-terraform.mdx +++ b/docs/pages/admin-guides/deploy-a-cluster/deployments/aws-ha-autoscale-cluster-terraform.mdx @@ -811,7 +811,7 @@ To add new nodes/EC2 servers that you can "SSH into" you'll need to: - [Run Teleport - we recommend using systemd](../../management/admin/daemon.mdx) - [Set the correct settings in /etc/teleport.yaml](../../../reference/config.mdx) - [Add Nodes to the Teleport - cluster](../../../enroll-resources/agents/join-services-to-your-cluster.mdx) + cluster](../../../enroll-resources/agents/join-services-to-your-cluster/join-services-to-your-cluster.mdx) ### Getting the SSH Service join token diff --git a/docs/pages/admin-guides/deploy-a-cluster/deployments/aws-starter-cluster-terraform.mdx b/docs/pages/admin-guides/deploy-a-cluster/deployments/aws-starter-cluster-terraform.mdx index 1d23659ac8845..2c2d66d69b2e3 100644 --- a/docs/pages/admin-guides/deploy-a-cluster/deployments/aws-starter-cluster-terraform.mdx +++ b/docs/pages/admin-guides/deploy-a-cluster/deployments/aws-starter-cluster-terraform.mdx @@ -726,7 +726,7 @@ To add new nodes/EC2 servers that you can "SSH into" you'll need to: - [Run Teleport - we recommend using systemd](../../management/admin/daemon.mdx) - [Set the correct settings in /etc/teleport.yaml](../../../reference/config.mdx) - [Add Nodes to the Teleport - cluster](../../../enroll-resources/agents/join-services-to-your-cluster.mdx) + cluster](../../../enroll-resources/agents/join-services-to-your-cluster/join-services-to-your-cluster.mdx) ## Troubleshooting diff --git a/docs/pages/admin-guides/deploy-a-cluster/deployments/deployments.mdx b/docs/pages/admin-guides/deploy-a-cluster/deployments/deployments.mdx new file mode 100644 index 0000000000000..a30782f9ca3c4 --- /dev/null +++ b/docs/pages/admin-guides/deploy-a-cluster/deployments/deployments.mdx @@ -0,0 +1,19 @@ +--- +title: Reference Deployment Guides +description: Teleport Installation and Configuration Reference Deployment Guides. +layout: tocless-doc +--- + +These guides show you how to set up a full self-hosted Teleport deployment on +the platform of your choice. + +- [AWS High Availability Deployment with Terraform](aws-ha-autoscale-cluster-terraform.mdx): Deploy HA Teleport with + Terraform on AWS. +- [AWS Single-Instance Deployment with Terraform](aws-starter-cluster-terraform.mdx): Deploy Teleport on a single instance with + Terraform on AWS. +- [AWS Multi-Region Proxy + Deployment](aws-gslb-proxy-peering-ha-deployment.mdx): Deploy HA + Teleport with Proxy Service instances in multiple regions for low-latency + access. +- [GCP](gcp.mdx): Deploy HA Teleport on GCP. +- [IBM Cloud](ibm.mdx): Deploy HA Teleport on IBM cloud. diff --git a/docs/pages/admin-guides/deploy-a-cluster/helm-deployments.mdx b/docs/pages/admin-guides/deploy-a-cluster/helm-deployments/helm-deployments.mdx similarity index 55% rename from docs/pages/admin-guides/deploy-a-cluster/helm-deployments.mdx rename to docs/pages/admin-guides/deploy-a-cluster/helm-deployments/helm-deployments.mdx index fb62933678eea..e35528834cf72 100644 --- a/docs/pages/admin-guides/deploy-a-cluster/helm-deployments.mdx +++ b/docs/pages/admin-guides/deploy-a-cluster/helm-deployments/helm-deployments.mdx @@ -15,24 +15,24 @@ order to protect a Kubernetes cluster with Teleport, and it is possible to enroll a Kubernetes cluster on Teleport Cloud or by running the Teleport Kubernetes Service on a Linux server. For instructions on enrolling a Kubernetes cluster with Teleport, read the [Kubernetes -Access](../../enroll-resources/kubernetes-access/introduction.mdx) documentation. +Access](../../../enroll-resources/kubernetes-access/introduction.mdx) documentation. ## Helm deployment guides These guides show you how to set up a full self-hosted Teleport deployment using our `teleport-cluster` Helm chart. -- [Deploy Teleport on Kubernetes](./helm-deployments/kubernetes-cluster.mdx): Run a Teleport cluster in a Kubernetes cluster using +- [Deploy Teleport on Kubernetes](kubernetes-cluster.mdx): Run a Teleport cluster in a Kubernetes cluster using the default configuration. This deployment is a great starting point to try a self-hosted Teleport with minimal resources. -- [HA AWS Teleport Cluster](./helm-deployments/aws.mdx): Running an HA Teleport cluster in Kubernetes using an AWS EKS Cluster -- [HA Azure Teleport Cluster](./helm-deployments/azure.mdx): Running an HA Teleport cluster in Kubernetes using an Azure AKS Cluster -- [HA GCP Teleport Cluster](./helm-deployments/gcp.mdx): Running an HA Teleport cluster in Kubernetes using a Google Cloud GKE Cluster -- [DigitalOcean Kubernetes Cluster](./helm-deployments/digitalocean.mdx): +- [HA AWS Teleport Cluster](aws.mdx): Running an HA Teleport cluster in Kubernetes using an AWS EKS Cluster +- [HA Azure Teleport Cluster](azure.mdx): Running an HA Teleport cluster in Kubernetes using an Azure AKS Cluster +- [HA GCP Teleport Cluster](gcp.mdx): Running an HA Teleport cluster in Kubernetes using a Google Cloud GKE Cluster +- [DigitalOcean Kubernetes Cluster](digitalocean.mdx): Running Teleport on DigitalOcean Kubernetes. -- [Custom Teleport config](./helm-deployments/custom.mdx): Running a Teleport cluster in Kubernetes with a custom Teleport config +- [Custom Teleport config](custom.mdx): Running a Teleport cluster in Kubernetes with a custom Teleport config ## Migration Guides -- [Migrating from v11 to v12](./helm-deployments/migration-v12.mdx) -- [Kubernetes 1.25 and PSP removal](./helm-deployments/migration-kubernetes-1-25-psp.mdx) +- [Migrating from v11 to v12](migration-v12.mdx) +- [Kubernetes 1.25 and PSP removal](migration-kubernetes-1-25-psp.mdx) diff --git a/docs/pages/admin-guides/deploy-a-cluster/helm-deployments/kubernetes-cluster.mdx b/docs/pages/admin-guides/deploy-a-cluster/helm-deployments/kubernetes-cluster.mdx index e7af64df52e3b..0152ee196ba3e 100644 --- a/docs/pages/admin-guides/deploy-a-cluster/helm-deployments/kubernetes-cluster.mdx +++ b/docs/pages/admin-guides/deploy-a-cluster/helm-deployments/kubernetes-cluster.mdx @@ -369,7 +369,7 @@ cluster. - **Set up Single Sign-On:** In this guide, we showed you how to create a local user, which is appropriate for demo environments. For a production deployment, you should set up Single Sign-On with your provider of choice. See our [Single - Sign-On guides](../../access-controls/sso.mdx) for how to do this. + Sign-On guides](../../access-controls/sso/sso.mdx) for how to do this. - **Configure your Teleport deployment:** To see all of the options you can set in the values file for the `teleport-cluster` Helm chart, consult our [reference guide](../../../reference/helm-reference/teleport-cluster.mdx). diff --git a/docs/pages/admin-guides/deploy-a-cluster/high-availability.mdx b/docs/pages/admin-guides/deploy-a-cluster/high-availability.mdx index 8cb1ef96326d3..88896d5e47d6b 100644 --- a/docs/pages/admin-guides/deploy-a-cluster/high-availability.mdx +++ b/docs/pages/admin-guides/deploy-a-cluster/high-availability.mdx @@ -296,7 +296,7 @@ pod or virtual machine in your group. If you plan to run Teleport on Kubernetes, the `teleport-cluster` Helm chart deploys the Auth Service and Proxy Service pools for you. To see how to use this -Helm chart, read our [Helm Deployments](helm-deployments.mdx) documentation. +Helm chart, read our [Helm Deployments](helm-deployments/helm-deployments.mdx) documentation. @@ -353,7 +353,7 @@ Create a configuration file and provide it to each of your Proxy Service instances at `/etc/teleport.yaml`. We will explain the required configuration fields for a high-availability Teleport deployment below. These are the minimum requirements, and when planning your high-availability deployment, you will want -to follow a more specific [deployment guide](../../index.mdx) for your +to follow a more specific [deployment guide](deployments/deployments.mdx) for your environment. #### `proxy_service` and `auth_service` @@ -467,7 +467,7 @@ Create a configuration file and provide it to each of your Auth Service instances at `/etc/teleport.yaml`. We will explain the required configuration fields for a high-availability Teleport deployment below. These are the minimum requirements, and when planning your high-availability deployment, you will want -to follow a more specific [deployment guide](../../index.mdx) for your +to follow a more specific [deployment guide](deployments/deployments.mdx) for your environment. #### `storage` @@ -540,8 +540,8 @@ deployment, read about how to design your own deployment on Kubernetes or a cluster of virtual machines in your cloud of choice: - [High-availability Teleport Deployments on Kubernetes with - Helm](helm-deployments.mdx) -- [Reference Deployments](deployments.mdx) for running Teleport on a cluster of + Helm](helm-deployments/helm-deployments.mdx) +- [Reference Deployments](deployments/deployments.mdx) for running Teleport on a cluster of virtual machines ### Ensure high performance @@ -550,7 +550,7 @@ You should also get familiar with how to ensure that your Teleport deployment is performing as expected: - [Scaling a Teleport cluster](../management/operations/scaling.mdx) -- [Monitoring a Teleport cluster](../management/diagnostics.mdx) +- [Monitoring a Teleport cluster](../management/diagnostics/diagnostics.mdx) ### Deploy Teleport services diff --git a/docs/pages/admin-guides/infrastructure-as-code.mdx b/docs/pages/admin-guides/infrastructure-as-code/infrastructure-as-code.mdx similarity index 93% rename from docs/pages/admin-guides/infrastructure-as-code.mdx rename to docs/pages/admin-guides/infrastructure-as-code/infrastructure-as-code.mdx index 0bb2e06bda487..4097d107b6ba8 100644 --- a/docs/pages/admin-guides/infrastructure-as-code.mdx +++ b/docs/pages/admin-guides/infrastructure-as-code/infrastructure-as-code.mdx @@ -27,7 +27,7 @@ There are two ways to configure a Teleport cluster: This approach makes it possible to incrementally adjust your Teleport configuration without restarting Teleport instances. -![Architecture of dynamic resources](../../img/dynamic-resources.png) +![Architecture of dynamic resources](../../../img/dynamic-resources.png) A cluster is composed of different objects (i.e., resources) and there are three common operations that can be performed on them: `get` , `create` , and `remove` @@ -64,7 +64,7 @@ infrastructure-as-code and GitOps approaches. For more information on Teleport roles, including the `internal.logins` trait we use in these example roles, see the [Teleport Access -Controls Reference](../reference/access-controls/roles.mdx). +Controls Reference](../../reference/access-controls/roles.mdx). ### YAML documents with `tctl` @@ -86,7 +86,7 @@ spec: Since `tctl` works from the local filesystem, you can write commands that apply all configuration documents in a directory tree. See the [CLI -reference](../reference/cli/tctl.mdx) for more information on `tctl`. +reference](../../reference/cli/tctl.mdx) for more information on `tctl`. ### Teleport Terraform provider @@ -114,7 +114,7 @@ resource "teleport_role" "developer" { ``` [Get started with the Terraform -provider](infrastructure-as-code/terraform-provider.mdx). +provider](terraform-provider/terraform-provider.mdx). ### Teleport Kubernetes Operator @@ -135,7 +135,7 @@ spec: 'env': 'test' ``` -[Get started with the Kubernetes Operator](infrastructure-as-code/teleport-operator.mdx). +[Get started with the Kubernetes Operator](teleport-operator/teleport-operator.mdx). ## Reconciling the configuration file with dynamic resources @@ -245,16 +245,16 @@ configuration resources with the `teleport.dev/origin=config-file` label. ### Configuration references - For a comprehensive reference of Teleport's static configuration options, read - the [Configuration Reference](../reference/config.mdx). + the [Configuration Reference](../../reference/config.mdx). - To see the dynamic configuration resources available to apply, read the - [Configuration Resource Reference](../reference/resources.mdx). There are also + [Configuration Resource Reference](../../reference/resources.mdx). There are also dedicated configuration resource references for - [applications](../reference/agent-services/application-access.mdx) and - [databases](../reference/agent-services/database-access-reference/configuration.mdx). + [applications](../../reference/agent-services/application-access.mdx) and + [databases](../../reference/agent-services/database-access-reference/configuration.mdx). ### Other ways to use the Teleport API The Teleport Kubernetes Operator, Terraform provider, and `tctl` are all clients of the Teleport Auth Service's gRPC API. To build your own API client to extend Teleport for your organization's needs, read our [API -guides](api/api.mdx). +guides](../api/api.mdx). diff --git a/docs/pages/admin-guides/infrastructure-as-code/kubernetes.mdx b/docs/pages/admin-guides/infrastructure-as-code/kubernetes.mdx index 7f743695be3d4..6186730f756d8 100644 --- a/docs/pages/admin-guides/infrastructure-as-code/kubernetes.mdx +++ b/docs/pages/admin-guides/infrastructure-as-code/kubernetes.mdx @@ -39,7 +39,7 @@ This guide is applicable if you self-host Teleport in Kubernetes using the -- Follow the [Teleport operator guides](teleport-operator.mdx) +- Follow the [Teleport operator guides](teleport-operator/teleport-operator.mdx) to install the Teleport Operator in your Kubernetes cluster. Make sure to follow the Enterprise instructions. @@ -245,7 +245,7 @@ logins: ## Next Steps -- Read the [Teleport Operator Guide](teleport-operator.mdx) to +- Read the [Teleport Operator Guide](teleport-operator/teleport-operator.mdx) to learn more about the Teleport Operator. - Read the [Login Rules reference](../../reference/access-controls/login-rules.mdx) to learn mode about the Login Rule expression syntax. diff --git a/docs/pages/admin-guides/infrastructure-as-code/teleport-operator.mdx b/docs/pages/admin-guides/infrastructure-as-code/teleport-operator/teleport-operator.mdx similarity index 97% rename from docs/pages/admin-guides/infrastructure-as-code/teleport-operator.mdx rename to docs/pages/admin-guides/infrastructure-as-code/teleport-operator/teleport-operator.mdx index cf4579641f646..aa240845072a0 100644 --- a/docs/pages/admin-guides/infrastructure-as-code/teleport-operator.mdx +++ b/docs/pages/admin-guides/infrastructure-as-code/teleport-operator/teleport-operator.mdx @@ -271,9 +271,8 @@ Kubernetes cluster or namespace. Then redeploy the Auth Server pods. When the ## Next steps -Helm Chart parameters are documented in the [`teleport-cluster` Helm chart reference](../../reference/helm-reference/teleport-cluster.mdx). +Helm Chart parameters are documented in the [`teleport-cluster` Helm chart reference](../../../reference/helm-reference/teleport-cluster.mdx). -See the [Helm Deployment guides](../deploy-a-cluster/helm-deployments.mdx) detailing specific setups like running Teleport on AWS or GCP. +Check out [access controls documentation](../../access-controls/access-controls.mdx) -Check out [access controls documentation](../access-controls/access-controls.mdx) diff --git a/docs/pages/admin-guides/infrastructure-as-code/terraform-provider.mdx b/docs/pages/admin-guides/infrastructure-as-code/terraform-provider/terraform-provider.mdx similarity index 93% rename from docs/pages/admin-guides/infrastructure-as-code/terraform-provider.mdx rename to docs/pages/admin-guides/infrastructure-as-code/terraform-provider/terraform-provider.mdx index bb9b73d5578f2..da662137e2a98 100644 --- a/docs/pages/admin-guides/infrastructure-as-code/terraform-provider.mdx +++ b/docs/pages/admin-guides/infrastructure-as-code/terraform-provider/terraform-provider.mdx @@ -11,7 +11,7 @@ This guide demonstrates how to: For instructions on managing the Teleport dynamic resources as code using GitOps, read the guide to using the Teleport Terraform provider with [Spacelift -and Machine ID](../../enroll-resources/machine-id/deployment/spacelift.mdx). +and Machine ID](../../../enroll-resources/machine-id/deployment/spacelift.mdx). ## Prerequisites @@ -31,13 +31,13 @@ and Machine ID](../../enroll-resources/machine-id/deployment/spacelift.mdx). Terraform needs a signed identity file from the Teleport cluster certificate authority to manage resources in the cluster. You can create a local Teleport user for this purpose or you can use the machine identity agent -[(Machine ID)](../../enroll-resources/machine-id/introduction.mdx) to generate credentials. +[(Machine ID)](../../../enroll-resources/machine-id/introduction.mdx) to generate credentials. If you intend to run Terraform from a CI/CD platform, Machine ID is often a better option for generating credentials. Machine ID can provision ephemeral short-lived certificates that are appropriate for CI/CD workflows instead of using manually-generated credentials that have a longer time-to-live (TTL) period. For more information about -using Machine ID, see the [Machine ID Getting Started Guide](../../enroll-resources/machine-id/getting-started.mdx). +using Machine ID, see the [Machine ID Getting Started Guide](../../../enroll-resources/machine-id/getting-started.mdx). To prepare credentials for a local Teleport user: @@ -186,6 +186,6 @@ following command: ## Next steps -- Explore the full list of supported [Terraform provider resources](../../reference/terraform-provider.mdx). -- Read more about [impersonation](../access-controls/guides/impersonation.mdx). +- Explore the full list of supported [Terraform provider resources](../../../reference/terraform-provider.mdx). +- Read more about [impersonation](../../access-controls/guides/impersonation.mdx). diff --git a/docs/pages/admin-guides/infrastructure-as-code/terraform.mdx b/docs/pages/admin-guides/infrastructure-as-code/terraform.mdx index ebf167a520c84..aec689bee696f 100644 --- a/docs/pages/admin-guides/infrastructure-as-code/terraform.mdx +++ b/docs/pages/admin-guides/infrastructure-as-code/terraform.mdx @@ -27,7 +27,7 @@ For simplicity, this guide will configure the Terraform provider to use your current logged-in user's Teleport credentials obtained from `tsh login`. -The [Terraform provider guide](terraform-provider.mdx) +The [Terraform provider guide](terraform-provider/terraform-provider.mdx) includes instructions for configuring a dedicated `terraform` user and role, which is a better option when running Terraform in a non-interactive environment. @@ -152,7 +152,7 @@ logins: ## Next Steps -- Read the [Terraform Guide](terraform-provider.mdx) to +- Read the [Terraform Guide](terraform-provider/terraform-provider.mdx) to learn more about configuring the Terraform provider. - Read the [Login Rules reference](../../reference/access-controls/login-rules.mdx) to learn mode about the Login Rule expression syntax. diff --git a/docs/pages/admin-guides/management/admin.mdx b/docs/pages/admin-guides/management/admin.mdx deleted file mode 100644 index 817618350db8d..0000000000000 --- a/docs/pages/admin-guides/management/admin.mdx +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: Cluster Administration Guides -description: Teleport Cluster Administration Guides. -layout: tocless-doc ---- - -The guides in this section show you the fundamentals of setting up and running a -Teleport cluster. You will learn how to run the `teleport` daemon, manage users -and resources, and troubleshoot any issues that arise. - -If you already understand how to set up a Teleport cluster, consult the -[Operations](./operations.mdx) section so you can start conducting periodic -cluster maintenance tasks. - -## Run Teleport - -- [Teleport Daemon](./admin/daemon.mdx): Set up Teleport as a daemon on Linux with systemd. -- [Run Teleport with Self-Signed Certificates](./admin/self-signed-certs.mdx): Set up Teleport in a local -environment without configuring TLS certificates. - -## Manage users and resources - -- [Trusted Clusters](./admin/trustedclusters.mdx): Connect multiple Teleport clusters using trusted clusters. -- [Labels](./admin/labels.mdx): Manage resource metadata with labels. -- [Local Users](./admin/users.mdx): Manage local user accounts. - -## Troubleshoot issues - -- [Troubleshooting](./admin/troubleshooting.mdx): Collect metrics and diagnostic information from Teleport. -- [Uninstall Teleport](./admin/uninstall-teleport.mdx): Uninstall Teleport from your system. diff --git a/docs/pages/admin-guides/management/admin/admin.mdx b/docs/pages/admin-guides/management/admin/admin.mdx new file mode 100644 index 0000000000000..4e11195231369 --- /dev/null +++ b/docs/pages/admin-guides/management/admin/admin.mdx @@ -0,0 +1,30 @@ +--- +title: Cluster Administration Guides +description: Teleport Cluster Administration Guides. +layout: tocless-doc +--- + +The guides in this section show you the fundamentals of setting up and running a +Teleport cluster. You will learn how to run the `teleport` daemon, manage users +and resources, and troubleshoot any issues that arise. + +If you already understand how to set up a Teleport cluster, consult the +[Operations](../operations/operations.mdx) section so you can start conducting periodic +cluster maintenance tasks. + +## Run Teleport + +- [Teleport Daemon](daemon.mdx): Set up Teleport as a daemon on Linux with systemd. +- [Run Teleport with Self-Signed Certificates](self-signed-certs.mdx): Set up Teleport in a local +environment without configuring TLS certificates. + +## Manage users and resources + +- [Trusted Clusters](trustedclusters.mdx): Connect multiple Teleport clusters using trusted clusters. +- [Labels](labels.mdx): Manage resource metadata with labels. +- [Local Users](users.mdx): Manage local user accounts. + +## Troubleshoot issues + +- [Troubleshooting](troubleshooting.mdx): Collect metrics and diagnostic information from Teleport. +- [Uninstall Teleport](uninstall-teleport.mdx): Uninstall Teleport from your system. diff --git a/docs/pages/admin-guides/management/admin/trustedclusters.mdx b/docs/pages/admin-guides/management/admin/trustedclusters.mdx index b992196341440..50dde7b22b1a1 100644 --- a/docs/pages/admin-guides/management/admin/trustedclusters.mdx +++ b/docs/pages/admin-guides/management/admin/trustedclusters.mdx @@ -110,7 +110,7 @@ configured with a single sign-on identity provider that authenticates her identi Based on the information from the identity provider, the root cluster assigns Alice the `full-access` role and issues her a certificate. The mapping of single sign-on properties to Teleport roles is configured when you add an authentication connector to the Teleport cluster. To learn more about configuring single sign-on -through an external identity provider, see [Configure Single Sign-on](../../access-controls/sso.mdx). +through an external identity provider, see [Configure Single Sign-on](../../access-controls/sso/sso.mdx). Alice receives the certificate that specifies the roles assigned to her in the root cluster. This metadata about her roles is contained in the certificate extensions and is protected by the signature of the root @@ -167,7 +167,7 @@ To complete the steps in this guide, verify your environment meets the following - A Teleport SSH server that is joined to the cluster you plan to use as the **leaf cluster**. For information about how to enroll a resource in your cluster, see - [Join Services to your Cluster](../../../enroll-resources/agents/join-services-to-your-cluster.mdx). + [Join Services to your Cluster](../../../enroll-resources/agents/join-services-to-your-cluster/join-services-to-your-cluster.mdx). (!docs/pages/includes/permission-warning.mdx!) diff --git a/docs/pages/admin-guides/management/admin/users.mdx b/docs/pages/admin-guides/management/admin/users.mdx index 9182c65719387..d6c3b06f1ff7e 100644 --- a/docs/pages/admin-guides/management/admin/users.mdx +++ b/docs/pages/admin-guides/management/admin/users.mdx @@ -123,7 +123,7 @@ For all available `tctl` commands and flags, see our [CLI Reference](../../../re You can also configure Teleport so that users can log in using an SSO provider. For more information, see: -- [Single Sign-On](../../access-controls/sso.mdx) +- [Single Sign-On](../../access-controls/sso/sso.mdx) diff --git a/docs/pages/admin-guides/management/diagnostics.mdx b/docs/pages/admin-guides/management/diagnostics.mdx deleted file mode 100644 index dbfe87be11f73..0000000000000 --- a/docs/pages/admin-guides/management/diagnostics.mdx +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: Monitoring your Cluster -description: Monitoring your Teleport deployment -layout: tocless-doc ---- - -- [Health Monitoring](./diagnostics/monitoring.mdx): How to monitor the health of a Teleport instance. -- [Metrics](./diagnostics/metrics.mdx): How to enable exporting Prometheus metrics. -- [Collecting Profiles](./diagnostics/profiles.mdx): How to collect runtime profiling data from a Teleport instance. -- [Distributed Tracing](./diagnostics/tracing.mdx): How to enable distributed tracing for a Teleport instance. - diff --git a/docs/pages/admin-guides/management/diagnostics/diagnostics.mdx b/docs/pages/admin-guides/management/diagnostics/diagnostics.mdx new file mode 100644 index 0000000000000..8513e6f0b1357 --- /dev/null +++ b/docs/pages/admin-guides/management/diagnostics/diagnostics.mdx @@ -0,0 +1,11 @@ +--- +title: Monitoring your Cluster +description: Monitoring your Teleport deployment +layout: tocless-doc +--- + +- [Health Monitoring](monitoring.mdx): How to monitor the health of a Teleport instance. +- [Metrics](metrics.mdx): How to enable exporting Prometheus metrics. +- [Collecting Profiles](profiles.mdx): How to collect runtime profiling data from a Teleport instance. +- [Distributed Tracing](tracing.mdx): How to enable distributed tracing for a Teleport instance. + diff --git a/docs/pages/admin-guides/management/diagnostics/metrics.mdx b/docs/pages/admin-guides/management/diagnostics/metrics.mdx index 3888390bcd795..f150e4d1fc3f7 100644 --- a/docs/pages/admin-guides/management/diagnostics/metrics.mdx +++ b/docs/pages/admin-guides/management/diagnostics/metrics.mdx @@ -199,6 +199,6 @@ guarantees](../../../upgrading/overview.mdx). We strongly encourage self-hosted Teleport users to enroll their Agents in automatic updates. You can track the count of Teleport Agents that are not enrolled in automatic updates using the metric, `teleport_enrolled_in_upgrades`. -[Read the documentation](../../../upgrading.mdx) for how to enroll Agents in +[Read the documentation](../../../upgrading/upgrading.mdx) for how to enroll Agents in automatic updates. diff --git a/docs/pages/admin-guides/management/export-audit-events.mdx b/docs/pages/admin-guides/management/export-audit-events/export-audit-events.mdx similarity index 81% rename from docs/pages/admin-guides/management/export-audit-events.mdx rename to docs/pages/admin-guides/management/export-audit-events/export-audit-events.mdx index c287f63bb8c91..8a48fa02922bf 100644 --- a/docs/pages/admin-guides/management/export-audit-events.mdx +++ b/docs/pages/admin-guides/management/export-audit-events/export-audit-events.mdx @@ -10,7 +10,7 @@ You can use Teleport's Event Handler plugin to export audit events from Teleport so you can store them in a log management platform or custom backend. If you are new to exporting audit events with Teleport, read [Forwarding Events -with Fluentd](./export-audit-events/fluentd.mdx) to learn the basics of how our +with Fluentd](fluentd.mdx) to learn the basics of how our Event Handler plugin works. While this guide focuses on Fluentd, the Event Handler plugin can export audit events to any endpoint that ingests JSON messages via HTTP. @@ -19,9 +19,10 @@ Next, read our guides to setting up the Event Handler plugin to export audit events to your solution of choice: - [Monitor Teleport Audit Events with the Elastic - Stack](./export-audit-events/elastic-stack.mdx): How to configure the Event + Stack](elastic-stack.mdx): How to configure the Event Handler plugin to forward Teleport audit logs to Logstash for ingestion in Elasticsearch so you can explore them in Kibana. -- [Monitor Teleport Audit Events with Splunk](./export-audit-events/splunk.mdx): +- [Monitor Teleport Audit Events with Splunk](splunk.mdx): How to configure the Event Handler plugin to send logs to Splunk's Universal Forwarder so you can explore your audit events in Splunk. + diff --git a/docs/pages/admin-guides/management/external-audit-storage.mdx b/docs/pages/admin-guides/management/external-audit-storage.mdx index 9fc3d4f3f16a0..6f5d9c97d583e 100644 --- a/docs/pages/admin-guides/management/external-audit-storage.mdx +++ b/docs/pages/admin-guides/management/external-audit-storage.mdx @@ -154,7 +154,7 @@ recordings will be stored in your S3 bucket, and they will *not* be stored in the Teleport Cloud infrastructure. If you currently use the -[Event Handler](export-audit-events.mdx) plugin to export +[Event Handler](export-audit-events/export-audit-events.mdx) plugin to export events, it will follow the switch from the old to new backends and new events will continue to be exported. Only events emitted after the transition to External Audit Storage will be visible in the Teleport UI or accessible to diff --git a/docs/pages/admin-guides/management/guides/ec2-tags.mdx b/docs/pages/admin-guides/management/guides/ec2-tags.mdx index e89eddfdcb0e2..6c4c64cb381df 100644 --- a/docs/pages/admin-guides/management/guides/ec2-tags.mdx +++ b/docs/pages/admin-guides/management/guides/ec2-tags.mdx @@ -28,7 +28,7 @@ fakehost.example.com 127.0.0.1:3022 env=example,hostname=ip-172-31-53-70,aws/Nam (!docs/pages/includes/edition-prereqs-tabs.mdx!) - One Teleport agent running on an Amazon EC2 instance. See - [our guides](../../../enroll-resources/agents/join-services-to-your-cluster.mdx) for how to set up Teleport agents. + [our guides](../../../enroll-resources/agents/join-services-to-your-cluster/join-services-to-your-cluster.mdx) for how to set up Teleport agents. ## Enable tags in instance metadata diff --git a/docs/pages/admin-guides/management/guides.mdx b/docs/pages/admin-guides/management/guides/guides.mdx similarity index 68% rename from docs/pages/admin-guides/management/guides.mdx rename to docs/pages/admin-guides/management/guides/guides.mdx index 66dc8665edf60..5724a24fd2930 100644 --- a/docs/pages/admin-guides/management/guides.mdx +++ b/docs/pages/admin-guides/management/guides/guides.mdx @@ -8,8 +8,9 @@ You can integrate Teleport with third-party tools in order to complete various tasks in your cluster. These guides describe Teleport integrations that are not documented elsewhere: - - [EC2 tags as Teleport Node labels](./guides/ec2-tags.mdx). How to set up - Teleport Node labels based on EC2 tags. + - [EC2 tags as Teleport agent labels](ec2-tags.mdx). How to set up + Teleport agent labels based on EC2 tags. - [Using Teleport's Certificate Authority with - GitHub](./guides/ssh-key-extensions.mdx). Use Teleport's short-lived + GitHub](ssh-key-extensions.mdx). Use Teleport's short-lived certificates with GitHub's Certificate Authority. + diff --git a/docs/pages/admin-guides/management/operations.mdx b/docs/pages/admin-guides/management/operations.mdx deleted file mode 100644 index 602159573383b..0000000000000 --- a/docs/pages/admin-guides/management/operations.mdx +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Operations -description: Teleport Operations - Scaling and High-Availability. -layout: tocless-doc ---- - -The guides in this section show you how to carry out common administration tasks -on an already running Teleport cluster. - -For guides on the fundamentals of setting up your cluster, you should consult -the [Cluster Administration Guides](./admin.mdx) section. - -- [Scaling](./operations/scaling.mdx): How to configure Teleport for large-scale deployments. -- [Backup and Restore](./operations/backup-restore.mdx): Backing up and restoring the cluster. -- [CA Rotation](./operations/ca-rotation.mdx): Rotating Teleport certificate authorities. -- [Database CA Rotation](./operations/db-ca-rotation.mdx): Rotating Teleport's `db` or `db_client` certificate authorities. -- [TLS Routing Migration](./operations/tls-routing.mdx): Migrating your Teleport cluster to single-port TLS routing mode. -- [Proxy Peering Migration](./operations/proxy-peering.mdx): Migrating your Teleport cluster to Proxy Peering mode. -- [Database CA Migrations](./operations/db-ca-migrations.mdx): Completing Teleport's Database CA migrations. diff --git a/docs/pages/admin-guides/management/operations/db-ca-rotation.mdx b/docs/pages/admin-guides/management/operations/db-ca-rotation.mdx index 630167aff1697..2a2a24e5260ed 100644 --- a/docs/pages/admin-guides/management/operations/db-ca-rotation.mdx +++ b/docs/pages/admin-guides/management/operations/db-ca-rotation.mdx @@ -128,7 +128,7 @@ You do not need to reconfigure databases at this point if you are rotating only the `db` CA, although there is no harm in doing so. Consult the appropriate -[Teleport Database Access Guide](../../../enroll-resources/database-access/guides.mdx) for your +[Teleport Database Access Guide](../../../enroll-resources/database-access/guides/guides.mdx) for your databases before proceeding to the `update_clients` rotation phase. @@ -172,7 +172,7 @@ lose access** to those databases after transitioning to the `standby` phase in this final step. To avoid down time, consult the appropriate -[Teleport Database Access Guide](../../../enroll-resources/database-access/guides.mdx) and reconfigure +[Teleport Database Access Guide](../../../enroll-resources/database-access/guides/guides.mdx) and reconfigure your databases before proceeding. Otherwise, access may still be restored by reconfiguring your self-hosted databases after this step. diff --git a/docs/pages/admin-guides/management/operations/operations.mdx b/docs/pages/admin-guides/management/operations/operations.mdx new file mode 100644 index 0000000000000..6225dcaa3bbac --- /dev/null +++ b/docs/pages/admin-guides/management/operations/operations.mdx @@ -0,0 +1,16 @@ +--- +title: Operations +description: Teleport Operations - Scaling and High-Availability. +layout: tocless-doc +--- + +The guides in this section show you how to carry out common administration tasks +on an already running Teleport cluster. + +- [Scaling](scaling.mdx): How to configure Teleport for large-scale deployments. +- [Backup and Restore](backup-restore.mdx): Backing up and restoring the cluster. +- [CA Rotation](ca-rotation.mdx): Rotating Teleport certificate authorities. +- [Database CA Rotation](db-ca-rotation.mdx): Rotating Teleport's `db` or `db_client` certificate authorities. +- [TLS Routing Migration](tls-routing.mdx): Migrating your Teleport cluster to single-port TLS routing mode. +- [Proxy Peering Migration](proxy-peering.mdx): Migrating your Teleport cluster to Proxy Peering mode. +- [Database CA Migrations](db-ca-migrations.mdx): Completing Teleport's Database CA migrations. diff --git a/docs/pages/admin-guides/management/operations/tls-routing.mdx b/docs/pages/admin-guides/management/operations/tls-routing.mdx index 657848187d9e0..d53f75ee4f97c 100644 --- a/docs/pages/admin-guides/management/operations/tls-routing.mdx +++ b/docs/pages/admin-guides/management/operations/tls-routing.mdx @@ -42,7 +42,7 @@ $ curl https://mytenant.teleport.sh/webapi/ping | jq '.proxy' Download Teleport from the [downloads page](https://goteleport.com/download) or your enterprise portal and follow the standard [upgrade -procedure](../../../upgrading.mdx). Make sure to upgrade both root and leaf clusters +procedure](../../../upgrading/upgrading.mdx). Make sure to upgrade both root and leaf clusters as well as `tsh` client. ## Step 2/7. Enable proxy multiplexing diff --git a/docs/pages/admin-guides/management/security/reduce-blast-radius.mdx b/docs/pages/admin-guides/management/security/reduce-blast-radius.mdx index 7397a25ec2bc9..dc20d1bf7643e 100644 --- a/docs/pages/admin-guides/management/security/reduce-blast-radius.mdx +++ b/docs/pages/admin-guides/management/security/reduce-blast-radius.mdx @@ -284,7 +284,7 @@ Two `user`s can grant elevated privileges to another `user` temporarily without - [Per-session MFA](../../access-controls/guides/per-session-mfa.mdx) - [Dual authorization](../../access-controls/guides/dual-authz.mdx) - [Role templates, allow/deny rules, and traits](../../access-controls/guides/role-templates.mdx) -- [Access Requests](../../access-controls/access-requests.mdx) +- [Access Requests](../../access-controls/access-requests/access-requests.mdx) ### Background reading - [Authentication connectors](../../../reference/access-controls/authentication.mdx) diff --git a/docs/pages/admin-guides/management/security.mdx b/docs/pages/admin-guides/management/security/security.mdx similarity index 76% rename from docs/pages/admin-guides/management/security.mdx rename to docs/pages/admin-guides/management/security/security.mdx index 02f0cbf168111..ab9c62e9cc316 100644 --- a/docs/pages/admin-guides/management/security.mdx +++ b/docs/pages/admin-guides/management/security/security.mdx @@ -15,10 +15,10 @@ You should note that the security practices covered in this section aren't neces examples used in the documentation. Examples in the documentation are primarily intended for demonstration purposes and for development environments. -- [Restrict Access for Privileged Accounts](./security/restrict-privileges.mdx). Learn about potential - risks of allowing privileged access and how to mitigate them. -- [Reducing the Blast Radius of Attacks](./security/reduce-blast-radius.mdx). +- [Restrict Access for Privileged Accounts](restrict-privileges.mdx). Learn about potential + risks of allowing privileged access and how to mitigate them. +- [Reducing the Blast Radius of Attacks](reduce-blast-radius.mdx). Prevent attackers from accessing your infrastructure even if they manage to obtain passwords or certificates. -- [Revoking Access](./security/revoking-access.mdx). Revoke access in the event +- [Revoking Access](revoking-access.mdx). Revoke access in the event of a compromise. diff --git a/docs/pages/ai-assist.mdx b/docs/pages/ai-assist.mdx index 94bf345c043a1..b7eb60bc754ed 100644 --- a/docs/pages/ai-assist.mdx +++ b/docs/pages/ai-assist.mdx @@ -139,4 +139,4 @@ our documentation. - [Server Access](enroll-resources/server-access/introduction.mdx) - [Access controls](admin-guides/access-controls/getting-started.mdx) - [Resource filtering](reference/predicate-language.mdx) -- [Access Request plugins](admin-guides/access-controls/access-request-plugins.mdx) +- [Access Request plugins](admin-guides/access-controls/access-request-plugins/access-request-plugins.mdx) diff --git a/docs/pages/connect-your-client/gui-clients.mdx b/docs/pages/connect-your-client/gui-clients.mdx index f82a2cd7bc38e..519f9b7acb346 100644 --- a/docs/pages/connect-your-client/gui-clients.mdx +++ b/docs/pages/connect-your-client/gui-clients.mdx @@ -14,7 +14,7 @@ work with Teleport. - (!docs/pages/includes/tctl.mdx!) - The Teleport Database Service configured to access a database. See one of our - [guides](../enroll-resources/database-access/guides.mdx) for how to set up the Teleport + [guides](../enroll-resources/database-access/guides/guides.mdx) for how to set up the Teleport Database Service for your database. ### Get connection information diff --git a/docs/pages/connect-your-client/tsh.mdx b/docs/pages/connect-your-client/tsh.mdx index 751985a9025d7..5fbb5c1432eab 100644 --- a/docs/pages/connect-your-client/tsh.mdx +++ b/docs/pages/connect-your-client/tsh.mdx @@ -1136,4 +1136,4 @@ To remove `tsh` and associated user data see [Uninstalling Teleport](../admin-guides/management/admin/uninstall-teleport.mdx). ## Further reading -- [CLI Reference](../reference/cli.mdx). +- [CLI Reference](../reference/cli/cli.mdx). diff --git a/docs/pages/contributing/documentation.mdx b/docs/pages/contributing/documentation/documentation.mdx similarity index 76% rename from docs/pages/contributing/documentation.mdx rename to docs/pages/contributing/documentation/documentation.mdx index e0e994f402dea..5b9ef11a3e155 100644 --- a/docs/pages/contributing/documentation.mdx +++ b/docs/pages/contributing/documentation/documentation.mdx @@ -24,14 +24,14 @@ the kind of documentation we want to transform our current documentation into. -- [How to Contribute to the Teleport Documentation](./documentation/how-to-contribute.mdx) describes +- [How to Contribute to the Teleport Documentation](how-to-contribute.mdx) describes how to set up a local environment and contribute changes to the Teleport documentation. -- [How to Review Documentation Changes](./documentation/reviewing-docs.mdx) explains how to +- [How to Review Documentation Changes](reviewing-docs.mdx) explains how to set up a development server to preview documentation changes so you can review pull requests. -- [Documentation UI Components](./documentation/reference.mdx) provides reference +- [Documentation UI Components](reference.mdx) provides reference information for how to include user interface components in Teleport documentation. -- [Style Guide](./documentation/style-guide.mdx) describes the documentation principles and +- [Style Guide](style-guide.mdx) describes the documentation principles and conventions to follow to ensure contributions are consistent and effective. -- [Creating Documentation Issues](./documentation/issues.mdx) offers guidelines for +- [Creating Documentation Issues](issues.mdx) offers guidelines for creating issues on GitHub to request changes to Teleport documentation. diff --git a/docs/pages/core-concepts.mdx b/docs/pages/core-concepts.mdx index 1661824926cfb..1803c40dcdcae 100644 --- a/docs/pages/core-concepts.mdx +++ b/docs/pages/core-concepts.mdx @@ -195,7 +195,7 @@ subject of the certificate—including its username and Teleport roles—to authorize the user. Read more about [local users](reference/access-controls/authentication.mdx) and how [SSO -authentication works in Teleport](admin-guides/access-controls/sso.mdx). +authentication works in Teleport](admin-guides/access-controls/sso/sso.mdx). ### Authentication connector diff --git a/docs/pages/enroll-resources/agents/deploy-agents-terraform.mdx b/docs/pages/enroll-resources/agents/deploy-agents-terraform.mdx index f696eb9aee750..25c723b9776d2 100644 --- a/docs/pages/enroll-resources/agents/deploy-agents-terraform.mdx +++ b/docs/pages/enroll-resources/agents/deploy-agents-terraform.mdx @@ -13,9 +13,10 @@ machines by declaring it as code using Terraform. There are several methods you can use to join a Teleport agent to your cluster, which we discuss in the [Joining Services to your -Cluster](join-services-to-your-cluster.mdx) guide. In this guide, we will use -the **join token** method, where the operator stores a secure token on the Auth -Service, and an agent presents the token in order to join a cluster. +Cluster](join-services-to-your-cluster/join-services-to-your-cluster.mdx) guide. +In this guide, we will use the **join token** method, where the operator stores +a secure token on the Auth Service, and an agent presents the token in order to +join a cluster. No matter which join method you use, it will involve the following Terraform resources: @@ -56,7 +57,7 @@ a demo cluster using: - An identity file for the Teleport Terraform provider. Make sure you are familiar with [how to set up the Teleport Terraform - provider](../../admin-guides/infrastructure-as-code/terraform-provider.mdx) before + provider](../../admin-guides/infrastructure-as-code/terraform-provider/terraform-provider.mdx) before following this guide. - (!docs/pages/includes/tctl.mdx!) diff --git a/docs/pages/enroll-resources/agents/introduction.mdx b/docs/pages/enroll-resources/agents/introduction.mdx index 344dc8b656cbc..3d8f8bfb42769 100644 --- a/docs/pages/enroll-resources/agents/introduction.mdx +++ b/docs/pages/enroll-resources/agents/introduction.mdx @@ -46,7 +46,7 @@ Teleport agents need to establish trust with the Teleport Auth Service in order to join a cluster. There are several ways to join an agent to your Teleport cluster, making it possible to automate the join process for your environment. Read about the available join methods in our [Join Services to your -Cluster](./join-services-to-your-cluster.mdx) guides. +Cluster](join-services-to-your-cluster/join-services-to-your-cluster.mdx) guides. When a Teleport process first runs, it checks its configuration file to determine which services are enabled. Each service then connects separately to @@ -88,7 +88,7 @@ There are two ways to enroll infrastructure resources with Teleport agents: - **Static**: Edit an agent's configuration file to configure a specific infrastructure resource to proxy. - **Dynamic**: Apply a [configuration - resource](../../admin-guides/infrastructure-as-code.mdx) that configures a resource to + resource](../../admin-guides/infrastructure-as-code/infrastructure-as-code.mdx) that configures a resource to proxy. The dynamic method allows Teleport to discover resources automatically. The diff --git a/docs/pages/enroll-resources/agents/join-services-to-your-cluster.mdx b/docs/pages/enroll-resources/agents/join-services-to-your-cluster.mdx deleted file mode 100644 index caa23b19d00d1..0000000000000 --- a/docs/pages/enroll-resources/agents/join-services-to-your-cluster.mdx +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Join Services to your Teleport Cluster -description: How to register the Proxy Service, Database Service, and other Teleport services with your cluster. ---- - -A **Teleport service** manages access to resources in your infrastructure, such -as Kubernetes clusters, Windows desktops, internal web applications, and -databases. A single **Teleport process** can run multiple Teleport services. - -There are multiple methods you can use to join a Teleport process to your -cluster in order to run Teleport services, including an instance of the Proxy -Service. Choose the method that best suits your infrastructure: - -|Method|Description|When to use| -|------|-----------|-----------| -|[EC2 Identity Document](./join-services-to-your-cluster/aws-ec2.mdx)|A Teleport process running on an EC2 instance authenticates to your cluster via a signed EC2 instance identity document.|Your Teleport process will run on EC2 and your Teleport cluster is self hosted.| -|[AWS IAM](./join-services-to-your-cluster/aws-iam.mdx)|A Teleport process uses AWS credentials to join the cluster, whether running on EC2 or not.|At least some of your infrastructure runs on AWS.| -|[Azure Managed Identity](./join-services-to-your-cluster/azure.mdx)|A Teleport process demonstrates that it runs in your Azure subscription by sending a signed attested data document and access token to the Teleport Auth Service.|Your Teleport process will run on Azure.| -|[Kubernetes ServiceAccount](./join-services-to-your-cluster/kubernetes.mdx)|A Teleport process uses a Kubernetes-signed proof to establish a trust relationship with your Teleport cluster.|Your Teleport process will run on Kubernetes.| -|[GCP IAM](./join-services-to-your-cluster/gcp.mdx)|A Teleport process uses a GCP-signed token to establish a trust relationship with your Teleport cluster.|Your Teleport process will run on a GCP VM.| -|[Join Token](./join-services-to-your-cluster/join-token.mdx)|A Teleport process presents a join token provided when starting the service.|There is no other supported method for your cloud provider.| - diff --git a/docs/pages/enroll-resources/agents/join-services-to-your-cluster/azure.mdx b/docs/pages/enroll-resources/agents/join-services-to-your-cluster/azure.mdx index 499c9d0938535..551395ee186bb 100644 --- a/docs/pages/enroll-resources/agents/join-services-to-your-cluster/azure.mdx +++ b/docs/pages/enroll-resources/agents/join-services-to-your-cluster/azure.mdx @@ -12,7 +12,7 @@ Azure Virtual Machine. Support for joining a cluster with the Proxy Service behind a layer 7 load balancer or reverse proxy is available in Teleport 13.0+. For other methods of joining a Teleport process to a cluster, see [Joining -Teleport Services to a Cluster](../join-services-to-your-cluster.mdx). +Teleport Services to a Cluster](join-services-to-your-cluster.mdx). ## Prerequisites diff --git a/docs/pages/enroll-resources/agents/join-services-to-your-cluster/join-services-to-your-cluster.mdx b/docs/pages/enroll-resources/agents/join-services-to-your-cluster/join-services-to-your-cluster.mdx new file mode 100644 index 0000000000000..c2443619e1517 --- /dev/null +++ b/docs/pages/enroll-resources/agents/join-services-to-your-cluster/join-services-to-your-cluster.mdx @@ -0,0 +1,22 @@ +--- +title: Join Services to your Teleport Cluster +description: How to register the Proxy Service, Database Service, and other Teleport services with your cluster. +--- + +A **Teleport service** manages access to resources in your infrastructure, such +as Kubernetes clusters, Windows desktops, internal web applications, and +databases. A single **Teleport process** can run multiple Teleport services. + +There are multiple methods you can use to join a Teleport process to your +cluster in order to run Teleport services, including an instance of the Proxy +Service. Choose the method that best suits your infrastructure: + +|Method|Description|When to use| +|------|-----------|-----------| +|[EC2 Identity Document](aws-ec2.mdx)|A Teleport process running on an EC2 instance authenticates to your cluster via a signed EC2 instance identity document.|Your Teleport process will run on EC2 and your Teleport cluster is self hosted.| +|[AWS IAM](aws-iam.mdx)|A Teleport process uses AWS credentials to join the cluster, whether running on EC2 or not.|At least some of your infrastructure runs on AWS.| +|[Azure Managed Identity](azure.mdx)|A Teleport process demonstrates that it runs in your Azure subscription by sending a signed attested data document and access token to the Teleport Auth Service.|Your Teleport process will run on Azure.| +|[Kubernetes ServiceAccount](kubernetes.mdx)|A Teleport process uses a Kubernetes-signed proof to establish a trust relationship with your Teleport cluster.|Your Teleport process will run on Kubernetes.| +|[GCP IAM](gcp.mdx)|A Teleport process uses a GCP-signed token to establish a trust relationship with your Teleport cluster.|Your Teleport process will run on a GCP VM.| +|[Join Token](join-token.mdx)|A Teleport process presents a join token provided when starting the service.|There is no other supported method for your cloud provider.| + diff --git a/docs/pages/enroll-resources/agents/join-services-to-your-cluster/kubernetes.mdx b/docs/pages/enroll-resources/agents/join-services-to-your-cluster/kubernetes.mdx index 4372887169ab6..38d70223dbd13 100644 --- a/docs/pages/enroll-resources/agents/join-services-to-your-cluster/kubernetes.mdx +++ b/docs/pages/enroll-resources/agents/join-services-to-your-cluster/kubernetes.mdx @@ -27,7 +27,7 @@ as the Auth Service. ## Prerequisites - A running Teleport cluster in Kubernetes. For details on how to set this up, - see [Guides for running Teleport using Helm](../../../admin-guides/deploy-a-cluster/helm-deployments.mdx). + see [Guides for running Teleport using Helm](../../../admin-guides/deploy-a-cluster/helm-deployments/helm-deployments.mdx). - Editor access to the Kubernetes cluster running the Teleport cluster. You must be able to create Namespaces and Deployments. - A Teleport user with `access` role, or any other role that allows access to @@ -240,5 +240,5 @@ namespace "teleport-agent" deleted - The possible values for `teleport-kube-agent` chart are documented [in its reference](../../../reference/helm-reference/teleport-kube-agent.mdx). -- See [Application Access Guides](../../application-access/guides.mdx) -- See [Database Access Guides](../../database-access/guides.mdx) +- See [Application Access Guides](../../application-access/guides/guides.mdx) +- See [Database Access Guides](../../database-access/guides/guides.mdx) diff --git a/docs/pages/enroll-resources/application-access/cloud-apis/aws-console.mdx b/docs/pages/enroll-resources/application-access/cloud-apis/aws-console.mdx index b6b3fd16627a3..71ad6cccb8a4c 100644 --- a/docs/pages/enroll-resources/application-access/cloud-apis/aws-console.mdx +++ b/docs/pages/enroll-resources/application-access/cloud-apis/aws-console.mdx @@ -809,7 +809,7 @@ applications](../guides/dynamic-registration.mdx). This guide shows you how to use the **join token method** to enroll the Teleport Application Service in your cluster. This is one of several available methods, and we recommend reading the [Join Services to your Teleport -Cluster](../../agents/join-services-to-your-cluster.mdx) guide to configure the +Cluster](../../agents/join-services-to-your-cluster/join-services-to-your-cluster.mdx) guide to configure the most appropriate method for your environment. ## Further reading diff --git a/docs/pages/enroll-resources/application-access/cloud-apis/azure.mdx b/docs/pages/enroll-resources/application-access/cloud-apis/azure.mdx index e91ad6c96405e..bb4facb76aef8 100644 --- a/docs/pages/enroll-resources/application-access/cloud-apis/azure.mdx +++ b/docs/pages/enroll-resources/application-access/cloud-apis/azure.mdx @@ -538,7 +538,7 @@ background and uses it to execute the command. longstanding admin roles for attackers to hijack. View our documentation on [Role Access Requests](../../../admin-guides/access-controls/access-requests/role-requests.mdx) and - [Access Request plugins](../../../admin-guides/access-controls/access-request-plugins.mdx). + [Access Request plugins](../../../admin-guides/access-controls/access-request-plugins/access-request-plugins.mdx). - Consult the Azure documentation for information about [Azure managed identities](https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) and how to [manage user-assigned managed diff --git a/docs/pages/enroll-resources/application-access/cloud-apis.mdx b/docs/pages/enroll-resources/application-access/cloud-apis/cloud-apis.mdx similarity index 80% rename from docs/pages/enroll-resources/application-access/cloud-apis.mdx rename to docs/pages/enroll-resources/application-access/cloud-apis/cloud-apis.mdx index 4a5505b5ef6bc..0a9cc948028cb 100644 --- a/docs/pages/enroll-resources/application-access/cloud-apis.mdx +++ b/docs/pages/enroll-resources/application-access/cloud-apis/cloud-apis.mdx @@ -15,8 +15,6 @@ longstanding admin accounts to target. Learn how to protect your cloud provider APIs with Teleport: -- [AWS (console and CLI applications)](./cloud-apis/aws-console.mdx) -- [Azure CLI applications](./cloud-apis/azure.mdx) -- [Google Cloud CLI applications](./cloud-apis/google-cloud.mdx) - - +- [AWS (console and CLI applications)](aws-console.mdx) +- [Azure CLI applications](azure.mdx) +- [Google Cloud CLI applications](google-cloud.mdx) diff --git a/docs/pages/enroll-resources/application-access/cloud-apis/google-cloud.mdx b/docs/pages/enroll-resources/application-access/cloud-apis/google-cloud.mdx index 4cb993b5c7e29..a48c6d1a8ac04 100644 --- a/docs/pages/enroll-resources/application-access/cloud-apis/google-cloud.mdx +++ b/docs/pages/enroll-resources/application-access/cloud-apis/google-cloud.mdx @@ -631,7 +631,7 @@ command. temporarily, with no longstanding admin roles for attackers to hijack. View our documentation on [Role Access Requests](../../../admin-guides/access-controls/access-requests/role-requests.mdx) and [Access - Request plugins](../../../admin-guides/access-controls/access-request-plugins.mdx). + Request plugins](../../../admin-guides/access-controls/access-request-plugins/access-request-plugins.mdx). - You can proxy any `gcloud` or `gsutil` command via Teleport. For a full reference of commands, view the Google Cloud documentation for [`gcloud`](https://cloud.google.com/sdk/gcloud/reference) and diff --git a/docs/pages/enroll-resources/application-access/controls.mdx b/docs/pages/enroll-resources/application-access/controls.mdx index ee3fa03b7f0f6..8ed46d000aa2b 100644 --- a/docs/pages/enroll-resources/application-access/controls.mdx +++ b/docs/pages/enroll-resources/application-access/controls.mdx @@ -133,12 +133,12 @@ for more information on enabling access to Azure managed identities. ## Next steps - View access controls [Getting Started](../../admin-guides/access-controls/getting-started.mdx) - and other available [guides](../../admin-guides/access-controls/guides.mdx). + and other available [guides](../../admin-guides/access-controls/guides/guides.mdx). - For full details on how Teleport populates the `internal` and `external` traits we illustrated in this guide, see the [Teleport Access Controls Reference](../../reference/access-controls/roles.mdx). - View access controls [Getting Started](../../admin-guides/access-controls/getting-started.mdx) - and other available [guides](../../admin-guides/access-controls/guides.mdx). + and other available [guides](../../admin-guides/access-controls/guides/guides.mdx). - Learn about using [JWT tokens](./jwt/introduction.mdx) to implement access controls in your application. - Integrate with your identity provider: diff --git a/docs/pages/enroll-resources/application-access/enroll-kubernetes-applications.mdx b/docs/pages/enroll-resources/application-access/enroll-kubernetes-applications/enroll-kubernetes-applications.mdx similarity index 80% rename from docs/pages/enroll-resources/application-access/enroll-kubernetes-applications.mdx rename to docs/pages/enroll-resources/application-access/enroll-kubernetes-applications/enroll-kubernetes-applications.mdx index 38f96ec67e768..097a4106c7652 100644 --- a/docs/pages/enroll-resources/application-access/enroll-kubernetes-applications.mdx +++ b/docs/pages/enroll-resources/application-access/enroll-kubernetes-applications/enroll-kubernetes-applications.mdx @@ -16,11 +16,11 @@ applications, and registers these applications with your cluster. The Teleport Application Service then detects the new application resources and proxies user traffic to them. -- [Get started](./enroll-kubernetes-applications/get-started.mdx): Set up automatic +- [Get started](get-started.mdx): Set up automatic application discovery with the `teleport-kube-agent` Helm chart. -- [Architecture](../../reference/architecture/kubernetes-applications-architecture.mdx): Learn how +- [Architecture](../../../reference/architecture/kubernetes-applications-architecture.mdx): Learn how automatic application discovery works. -- [Reference](../../reference/agent-services/kubernetes-application-discovery.mdx): Consult this guide +- [Reference](../../../reference/agent-services/kubernetes-application-discovery.mdx): Consult this guide for options and Kubernetes annotations you can use to configure automatic Kubernetes application discovery. diff --git a/docs/pages/enroll-resources/application-access/enroll-kubernetes-applications/kubernetes-applications.mdx b/docs/pages/enroll-resources/application-access/enroll-kubernetes-applications/kubernetes-applications.mdx new file mode 100644 index 0000000000000..097a4106c7652 --- /dev/null +++ b/docs/pages/enroll-resources/application-access/enroll-kubernetes-applications/kubernetes-applications.mdx @@ -0,0 +1,26 @@ +--- +title: "Enroll Kubernetes Services as Teleport Applications" +description: "Teleport can automatically detect applications running in your Kubernetes clusters and register them with Teleport for secure access." +--- + +Teleport can automatically detect applications running in your Kubernetes +clusters and register them with your Teleport cluster. In this setup, users with +Kubernetes-hosted infrastructure can configure secure access to any new +applications they deploy with no need for manual intervention beyond the initial +setup step. + +To enroll Kubernetes applications automatically, your Teleport cluster requires +the Teleport Discovery Service and Teleport Application Service. The Teleport +Discovery Service queries your Kubernetes clusters to detect running +applications, and registers these applications with your cluster. The Teleport +Application Service then detects the new application resources and proxies user +traffic to them. + +- [Get started](get-started.mdx): Set up automatic + application discovery with the `teleport-kube-agent` Helm chart. +- [Architecture](../../../reference/architecture/kubernetes-applications-architecture.mdx): Learn how + automatic application discovery works. +- [Reference](../../../reference/agent-services/kubernetes-application-discovery.mdx): Consult this guide + for options and Kubernetes annotations you can use to configure automatic + Kubernetes application discovery. + diff --git a/docs/pages/enroll-resources/application-access/guides.mdx b/docs/pages/enroll-resources/application-access/guides.mdx deleted file mode 100644 index 96f2ec2807276..0000000000000 --- a/docs/pages/enroll-resources/application-access/guides.mdx +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Application Access Guides -description: Guides for configuring Teleport application access. -layout: tocless-doc ---- - -These guides explain how to use the Teleport Application Service, which allows -your teams to connect to applications within private networks with fine-grained -RBAC and audit logging. - -Manage access to internal applications: - -- [Web App Access](./guides/connecting-apps.mdx): How to access web apps with Teleport. -- [TCP App Access](./guides/tcp.mdx): How to access plain TCP apps with Teleport. -- [API Access](./guides/api-access.mdx): How to access REST APIs with Teleport. -- [Dynamic Registration](./guides/dynamic-registration.mdx): Register/unregister apps without restarting Teleport. -- [Amazon Athena Access](./guides/amazon-athena.mdx): How to access Amazon Athena with Teleport. -- [Amazon DynamoDB Access](./guides/dynamodb.mdx): How to access Amazon DynamoDB as an application. -- [Application Access HA](./guides/ha.mdx): How to configure the Teleport Application Service for high availability. diff --git a/docs/pages/enroll-resources/application-access/guides/dynamic-registration.mdx b/docs/pages/enroll-resources/application-access/guides/dynamic-registration.mdx index 3f5b195b5049e..8b1ae98838a9e 100644 --- a/docs/pages/enroll-resources/application-access/guides/dynamic-registration.mdx +++ b/docs/pages/enroll-resources/application-access/guides/dynamic-registration.mdx @@ -11,11 +11,6 @@ Application Service instances periodically query the Teleport Auth Service for `app` resources, each of which includes the information that the Application Service needs to proxy an application. -Dynamic registration is useful for [managing pools of Application Service -instances](../../agents/deploy-agents-terraform.mdx). And behind the scenes, the -Teleport Discovery Service uses dynamic registration to [register Kubernetes -applications](../enroll-kubernetes-applications.mdx). - ## Required permissions In order to interact with dynamically registered applications, a user must have diff --git a/docs/pages/enroll-resources/application-access/guides/guides.mdx b/docs/pages/enroll-resources/application-access/guides/guides.mdx new file mode 100644 index 0000000000000..3c33e215cd6ff --- /dev/null +++ b/docs/pages/enroll-resources/application-access/guides/guides.mdx @@ -0,0 +1,19 @@ +--- +title: Application Access Guides +description: Guides for configuring Teleport application access. +layout: tocless-doc +--- + +These guides explain how to use the Teleport Application Service, which allows +your teams to connect to applications within private networks with fine-grained +RBAC and audit logging. + +Manage access to internal applications: + +- [Web App Access](connecting-apps.mdx): How to access web apps with Teleport. +- [TCP App Access](tcp.mdx): How to access plain TCP apps with Teleport. +- [API Access](api-access.mdx): How to access REST APIs with Teleport. +- [Dynamic Registration](dynamic-registration.mdx): Register/unregister apps without restarting Teleport. +- [Amazon Athena Access](amazon-athena.mdx): How to access Amazon Athena with Teleport. +- [Amazon DynamoDB Access](dynamodb.mdx): How to access Amazon DynamoDB as an application. +- [Application Access HA](ha.mdx): How to configure the Teleport Application Service for high availability. diff --git a/docs/pages/enroll-resources/application-access/introduction.mdx b/docs/pages/enroll-resources/application-access/introduction.mdx index d42a4d6e47f92..92636bc8e8fce 100644 --- a/docs/pages/enroll-resources/application-access/introduction.mdx +++ b/docs/pages/enroll-resources/application-access/introduction.mdx @@ -56,7 +56,7 @@ These guides explain how to protect internal applications with Teleport: ## Automatically enroll Kubernetes applications If you are running applications on Kubernetes, you can [enroll them in your -Teleport cluster automatically](./enroll-kubernetes-applications.mdx). +Teleport cluster automatically](enroll-kubernetes-applications/enroll-kubernetes-applications.mdx). ## Teleport-signed JSON Web Tokens @@ -73,4 +73,4 @@ can access Okta applications through the Teleport Web UI and `tsh`, and administrators can manage access to these applications by defining RBAC policies in Teleport roles. -Learn more about the [Teleport Okta integration](./okta.mdx). +Learn more about the [Teleport Okta integration](okta/okta.mdx). diff --git a/docs/pages/enroll-resources/application-access/jwt.mdx b/docs/pages/enroll-resources/application-access/jwt/jwt.mdx similarity index 62% rename from docs/pages/enroll-resources/application-access/jwt.mdx rename to docs/pages/enroll-resources/application-access/jwt/jwt.mdx index b1574b11aa7a6..aab990bf03f89 100644 --- a/docs/pages/enroll-resources/application-access/jwt.mdx +++ b/docs/pages/enroll-resources/application-access/jwt/jwt.mdx @@ -8,5 +8,5 @@ These guides explain how web apps behind the Teleport Application Service can leverage Teleport-signed JWT tokens to implement authentication and authorization. -- [Introduction](./jwt/introduction.mdx): Introduction to JWT tokens with application access. -- [Elasticsearch](./jwt/elasticsearch.mdx): How to use JWT authentication with Elasticsearch. +- [Introduction](introduction.mdx): Introduction to JWT tokens with application access. +- [Elasticsearch](elasticsearch.mdx): How to use JWT authentication with Elasticsearch. diff --git a/docs/pages/enroll-resources/application-access/okta.mdx b/docs/pages/enroll-resources/application-access/okta.mdx deleted file mode 100644 index 11bf968f47bef..0000000000000 --- a/docs/pages/enroll-resources/application-access/okta.mdx +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Okta Integration with Application Access -description: Guides for using Teleport Okta integration. -layout: tocless-doc ---- - -Configure Teleport to import and grant access to Okta applications and user groups. - -- [Setting up the Okta Service](./okta/guide.mdx): A guide for setting up a simple Okta service in Teleport. -- [Architecture](./okta/architecture.mdx): The architecture of the Okta service. -- [Reference](../../reference/agent-services/okta.mdx): A reference for the Okta service. - diff --git a/docs/pages/enroll-resources/application-access/okta/okta.mdx b/docs/pages/enroll-resources/application-access/okta/okta.mdx new file mode 100644 index 0000000000000..00912982f79f1 --- /dev/null +++ b/docs/pages/enroll-resources/application-access/okta/okta.mdx @@ -0,0 +1,12 @@ +--- +title: Okta Integration with Application Access +description: Guides for using Teleport Okta integration. +layout: tocless-doc +--- + +Configure Teleport to import and grant access to Okta applications and user groups. + +- [Setting up the Okta Service](guide.mdx): A guide for setting up a simple Okta service in Teleport. +- [Architecture](architecture.mdx): The architecture of the Okta service. +- [Reference](../../../reference/agent-services/okta.mdx): A reference for the Okta service. + diff --git a/docs/pages/enroll-resources/database-access/architecture.mdx b/docs/pages/enroll-resources/database-access/architecture.mdx index beeb268bf05cd..53a9f0a42e4fe 100644 --- a/docs/pages/enroll-resources/database-access/architecture.mdx +++ b/docs/pages/enroll-resources/database-access/architecture.mdx @@ -128,7 +128,7 @@ databases hosted by AWS. Teleport Database Service uses client certificate authentication with self-hosted database servers. -See respective [guides](./guides.mdx) for details on configuring client +See respective [guides](guides/guides.mdx) for details on configuring client certificate authentication. #### Cloud-hosted @@ -137,7 +137,7 @@ Teleport Database Service uses IAM authentication for Amazon-hosted and Google-Cloud-hosted database servers, and uses Active Directory authentication for Azure-hosted database servers. -See respective [guides](./guides.mdx) for details on configuring IAM/AD +See respective [guides](guides/guides.mdx) for details on configuring IAM/AD authentication. ## Discovery diff --git a/docs/pages/enroll-resources/database-access/auto-user-provisioning.mdx b/docs/pages/enroll-resources/database-access/auto-user-provisioning.mdx deleted file mode 100644 index f04d14b1278d1..0000000000000 --- a/docs/pages/enroll-resources/database-access/auto-user-provisioning.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Database Automatic User Provisioning -description: Configure automatic user provisioning for databases. ---- - -(!docs/pages/includes/database-access/auto-user-provisioning/intro.mdx!) - -Currently, automatic user provisioning is supported for the following databases: -- [Self-hosted and AWS RDS PostgreSQL databases](./auto-user-provisioning/postgres.mdx) -- [Self-hosted and AWS RDS MySQL databases](./auto-user-provisioning/mysql.mdx) -- [Self-hosted and AWS RDS MariaDB databases](./auto-user-provisioning/mariadb.mdx) -- [AWS Redshift databases](./auto-user-provisioning/aws-redshift.mdx) -- [Self-hosted MongoDB](./auto-user-provisioning/mongodb.mdx) diff --git a/docs/pages/enroll-resources/database-access/auto-user-provisioning/auto-user-provisioning.mdx b/docs/pages/enroll-resources/database-access/auto-user-provisioning/auto-user-provisioning.mdx new file mode 100644 index 0000000000000..cfe99bfb4c339 --- /dev/null +++ b/docs/pages/enroll-resources/database-access/auto-user-provisioning/auto-user-provisioning.mdx @@ -0,0 +1,16 @@ +--- +title: Database Automatic User Provisioning +description: Configure automatic user provisioning for databases. +--- + +(!docs/pages/includes/database-access/auto-user-provisioning/intro.mdx!) + +Currently, automatic user provisioning is supported for the following databases: +- [PostgreSQL databases (self-hosted and Amazon RDS)](postgres.mdx) +- [MySQL databases (self-hosted and Amazon RDS)](mysql.mdx) +- [MariaDB databases (self-hosted and Amazon RDS)](mariadb.mdx) +- [Amazon Redshift databases](aws-redshift.mdx) +- [MongoDB databases (self-hosted)](mongodb.mdx) + + + diff --git a/docs/pages/enroll-resources/database-access/database-access.mdx b/docs/pages/enroll-resources/database-access/database-access.mdx index 850266b192d6a..f4dab9b131b02 100644 --- a/docs/pages/enroll-resources/database-access/database-access.mdx +++ b/docs/pages/enroll-resources/database-access/database-access.mdx @@ -11,7 +11,7 @@ Some of the things you can do with database access: - Enable users to retrieve short-lived database certificates using a Single Sign-On flow, thus maintaining their organization-wide identity. - Configure role-based access controls for databases and implement custom - [Access Request](../../admin-guides/access-controls/access-requests.mdx) workflows. + [Access Request](../../admin-guides/access-controls/access-requests/access-requests.mdx) workflows. - Capture database activity in the Teleport audit log. Teleport protects databases through the Teleport Database Service, which is a diff --git a/docs/pages/enroll-resources/database-access/faq.mdx b/docs/pages/enroll-resources/database-access/faq.mdx index 59ab92704e03f..eaf628f7fbf4e 100644 --- a/docs/pages/enroll-resources/database-access/faq.mdx +++ b/docs/pages/enroll-resources/database-access/faq.mdx @@ -29,7 +29,7 @@ For PostgreSQL and MySQL, the following Cloud-hosted versions are supported in a - Google Cloud SQL - Azure Database -See the available [guides](./guides.mdx) for all supported configurations. +See the available [guides](guides/guides.mdx) for all supported configurations. ## Which PostgreSQL protocol features are not supported? diff --git a/docs/pages/enroll-resources/database-access/getting-started.mdx b/docs/pages/enroll-resources/database-access/getting-started.mdx index 3249d7ada471e..2ea177df6b973 100644 --- a/docs/pages/enroll-resources/database-access/getting-started.mdx +++ b/docs/pages/enroll-resources/database-access/getting-started.mdx @@ -239,8 +239,8 @@ $ tsh db connect --db-user=alice --db-name postgres aurora For the next steps, dive deeper into the topics relevant to your Database Access use-case, for example: -- Check out configuration [guides](./guides.mdx). +- Check out configuration [guides](guides/guides.mdx). - Learn how to configure [GUI clients](../../connect-your-client/gui-clients.mdx). -- Learn about database access [role-based access control](./rbac.mdx). +- Learn about database access [role-based access control](rbac/rbac.mdx). - See [frequently asked questions](./faq.mdx). diff --git a/docs/pages/enroll-resources/database-access/guides/aws-cross-account.mdx b/docs/pages/enroll-resources/database-access/guides/aws-cross-account.mdx index fd8d766c9fc0e..d91eac7e6435d 100644 --- a/docs/pages/enroll-resources/database-access/guides/aws-cross-account.mdx +++ b/docs/pages/enroll-resources/database-access/guides/aws-cross-account.mdx @@ -30,8 +30,7 @@ Teleport Database Service to connect to the databases. This guide does not cover AWS network configuration, because it depends on your specific AWS network setup and the kind(s) of AWS databases you wish to connect -to Teleport. For more information, see: -[how to connect your database](../guides.mdx#how-to-connect-your-database-to-teleport). +to Teleport. ## Teleport configuration @@ -232,4 +231,4 @@ role, then the trust policy might look like: ## Next steps -- Get started by [connecting](../guides.mdx) your database. +- Get started by [connecting](../guides/guides.mdx) your database. diff --git a/docs/pages/enroll-resources/database-access/guides/aws-discovery.mdx b/docs/pages/enroll-resources/database-access/guides/aws-discovery.mdx index 61cbbe46a30a4..2e72f7c0854e2 100644 --- a/docs/pages/enroll-resources/database-access/guides/aws-discovery.mdx +++ b/docs/pages/enroll-resources/database-access/guides/aws-discovery.mdx @@ -168,5 +168,5 @@ tag to the AWS database resource. ## Next - Learn about [Dynamic Registration](./dynamic-registration.mdx) by the Teleport Database Service. -- Get started by [connecting](../guides.mdx) your database. -- Connect AWS databases in [external AWS accounts](./aws-cross-account.mdx). +- Get started by [connecting](../../database-access/guides/guides.mdx) your database. + diff --git a/docs/pages/enroll-resources/database-access/guides/dynamic-registration.mdx b/docs/pages/enroll-resources/database-access/guides/dynamic-registration.mdx index 92e6204aafc3f..4f94ebf80c400 100644 --- a/docs/pages/enroll-resources/database-access/guides/dynamic-registration.mdx +++ b/docs/pages/enroll-resources/database-access/guides/dynamic-registration.mdx @@ -121,10 +121,9 @@ $ tctl rm db/example ``` Aside from `tctl`, dynamic resources can also be added by: -- [AWS Discovery](./aws-discovery.mdx) -- [Terraform Provider](../../../admin-guides/infrastructure-as-code/terraform-provider.mdx) -- [Kubernetes Operator](../../../admin-guides/infrastructure-as-code/teleport-operator.mdx) +- [Terraform Provider](../../../admin-guides/infrastructure-as-code/terraform-provider/terraform-provider.mdx) +- [Kubernetes Operator](../../../admin-guides/infrastructure-as-code/teleport-operator/teleport-operator.mdx) - [Teleport API](../../../admin-guides/api/api.mdx) -See [Using Dynamic Resources](../../../admin-guides/infrastructure-as-code.mdx) to learn +See [Using Dynamic Resources](../../../admin-guides/infrastructure-as-code/infrastructure-as-code.mdx) to learn more about managing Teleport's dynamic resources in general. diff --git a/docs/pages/enroll-resources/database-access/guides/elastic.mdx b/docs/pages/enroll-resources/database-access/guides/elastic.mdx index e3d95ead8c121..68902e0de0c1c 100644 --- a/docs/pages/enroll-resources/database-access/guides/elastic.mdx +++ b/docs/pages/enroll-resources/database-access/guides/elastic.mdx @@ -68,7 +68,7 @@ $ curl -u elastic:your_elasticsearch_password -X POST "https://elasticsearch.exa
-In a scenario where Teleport is using [single sign-on](../../../admin-guides/access-controls/sso.mdx) you may want to define a mapping for all users to a role: +In a scenario where Teleport is using [single sign-on](../../../admin-guides/access-controls/sso/sso.mdx) you may want to define a mapping for all users to a role: ```code $ curl -u elastic:your_elasticsearch_password -X POST "https://elasticsearch.example.com:9200/_security/role_mapping/mapping1?pretty" -H 'Content-Type: application/json' -d' diff --git a/docs/pages/enroll-resources/database-access/guides.mdx b/docs/pages/enroll-resources/database-access/guides/guides.mdx similarity index 99% rename from docs/pages/enroll-resources/database-access/guides.mdx rename to docs/pages/enroll-resources/database-access/guides/guides.mdx index 2d552d12d88cc..0368c3001b4e7 100644 --- a/docs/pages/enroll-resources/database-access/guides.mdx +++ b/docs/pages/enroll-resources/database-access/guides/guides.mdx @@ -5,3 +5,4 @@ layout: tocless-doc --- (!docs/pages/includes/database-access/guides.mdx!) + diff --git a/docs/pages/enroll-resources/database-access/guides/ha.mdx b/docs/pages/enroll-resources/database-access/guides/ha.mdx index 1445e5ef466f6..54d8aa901f7fa 100644 --- a/docs/pages/enroll-resources/database-access/guides/ha.mdx +++ b/docs/pages/enroll-resources/database-access/guides/ha.mdx @@ -133,8 +133,5 @@ you're using to connect. ## Next steps -- Get started by [connecting](../guides.mdx) your database. -- Review the [architecture](../architecture.mdx) of the Teleport Database - Service. - +- Get started by [connecting](guides.mdx) your database. diff --git a/docs/pages/enroll-resources/database-access/guides/mysql-cloudsql.mdx b/docs/pages/enroll-resources/database-access/guides/mysql-cloudsql.mdx index 2992f6c9bf4af..2eeabe5f1af0d 100644 --- a/docs/pages/enroll-resources/database-access/guides/mysql-cloudsql.mdx +++ b/docs/pages/enroll-resources/database-access/guides/mysql-cloudsql.mdx @@ -175,7 +175,7 @@ $ tsh db ls type="note" > You will only be able to see databases that your Teleport role has -access to. See our [RBAC](../rbac.mdx) guide for more details. +access to. See our [RBAC](../rbac/rbac.mdx) guide for more details. When connecting to the database, use either the database user name or the diff --git a/docs/pages/enroll-resources/database-access/guides/mysql-self-hosted.mdx b/docs/pages/enroll-resources/database-access/guides/mysql-self-hosted.mdx index e416a396db94b..543d905a4b87e 100644 --- a/docs/pages/enroll-resources/database-access/guides/mysql-self-hosted.mdx +++ b/docs/pages/enroll-resources/database-access/guides/mysql-self-hosted.mdx @@ -190,7 +190,7 @@ $ tsh db ls Note that you will only be able to see databases your role has access to. See -the [RBAC](../rbac.mdx) guide for more details. +the [RBAC](../rbac/rbac.mdx) guide for more details. To retrieve credentials for a database and connect to it: diff --git a/docs/pages/enroll-resources/database-access/guides/oracle-self-hosted.mdx b/docs/pages/enroll-resources/database-access/guides/oracle-self-hosted.mdx index 20620e7d699f1..48074e7afa2f1 100644 --- a/docs/pages/enroll-resources/database-access/guides/oracle-self-hosted.mdx +++ b/docs/pages/enroll-resources/database-access/guides/oracle-self-hosted.mdx @@ -28,7 +28,7 @@ description: How to configure Teleport database access with Oracle. -To modify an existing user to provide access to the Database Service, see [Database Access Access Controls](../../database-access/rbac.mdx) +To modify an existing user to provide access to the Database Service, see [Database Access Access Controls](../rbac/rbac.mdx) @@ -53,7 +53,7 @@ $ tctl users add \ For more detailed information about database access controls and how to restrict -access see [RBAC](../../database-access/rbac.mdx) documentation. +access see [RBAC](../rbac/rbac.mdx) documentation. ## Step 2/6. Create a certificate/key pair and Teleport Oracle Wallet diff --git a/docs/pages/enroll-resources/database-access/guides/postgres-cloudsql.mdx b/docs/pages/enroll-resources/database-access/guides/postgres-cloudsql.mdx index deeca985bb731..6a12b656eef62 100644 --- a/docs/pages/enroll-resources/database-access/guides/postgres-cloudsql.mdx +++ b/docs/pages/enroll-resources/database-access/guides/postgres-cloudsql.mdx @@ -131,7 +131,7 @@ $ tsh db ls type="note" > You will only be able to see databases that your Teleport role has -access to. See our [RBAC](../rbac.mdx) guide for more details. +access to. See our [RBAC](../rbac/rbac.mdx) guide for more details. When connecting to the database, use the name of the database's service account diff --git a/docs/pages/enroll-resources/database-access/guides/postgres-self-hosted.mdx b/docs/pages/enroll-resources/database-access/guides/postgres-self-hosted.mdx index 97a78a71bd8e7..f08b08131c417 100644 --- a/docs/pages/enroll-resources/database-access/guides/postgres-self-hosted.mdx +++ b/docs/pages/enroll-resources/database-access/guides/postgres-self-hosted.mdx @@ -139,7 +139,7 @@ $ tsh db ls Note that you will only be able to see databases your role has access to. See -[RBAC](../rbac.mdx) section for more details. +[RBAC](../rbac/rbac.mdx) section for more details. To retrieve credentials for a database and connect to it: diff --git a/docs/pages/enroll-resources/database-access/guides/rds.mdx b/docs/pages/enroll-resources/database-access/guides/rds.mdx index b3edd742202e8..0a3639a866411 100644 --- a/docs/pages/enroll-resources/database-access/guides/rds.mdx +++ b/docs/pages/enroll-resources/database-access/guides/rds.mdx @@ -6,7 +6,7 @@ description: How to configure Teleport database access with AWS RDS and Aurora f Access to AWS or RDS Aurora databases can be provided by [Teleport Database Access](../../../index.mdx). This allows for -fine-grain access control through [Teleport's RBAC](../rbac.mdx). +fine-grain access control through [Teleport's RBAC](../rbac/rbac.mdx). This guide demonstrates how to use Teleport to connect to AWS or RDS Aurora databases. @@ -440,5 +440,5 @@ $ tsh db logout ## Next steps (!docs/pages/includes/database-access/guides-next-steps.mdx!) -- Set up [automatic database user provisioning](../auto-user-provisioning.mdx). +- Set up [automatic database user provisioning](../auto-user-provisioning/auto-user-provisioning.mdx). diff --git a/docs/pages/enroll-resources/database-access/guides/vitess.mdx b/docs/pages/enroll-resources/database-access/guides/vitess.mdx index daf1b6bc65728..ab4671b754f0b 100644 --- a/docs/pages/enroll-resources/database-access/guides/vitess.mdx +++ b/docs/pages/enroll-resources/database-access/guides/vitess.mdx @@ -197,7 +197,7 @@ $ tsh db ls Note that you will only be able to see databases your role has access to. See -the [RBAC](../rbac.mdx) guide for more details. +the [RBAC](../rbac/rbac.mdx) guide for more details. To retrieve credentials for a database and connect to it: diff --git a/docs/pages/enroll-resources/database-access/rbac.mdx b/docs/pages/enroll-resources/database-access/rbac.mdx deleted file mode 100644 index 3593139bec23c..0000000000000 --- a/docs/pages/enroll-resources/database-access/rbac.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Database Access Control Guides -description: Role-based access control guides for Teleport database access. ---- - -These guides cover configuring access control policies for database users. - -Read the [RBAC](./rbac/configuring-access.mdx) guide to get a general understanding -of how to configure Teleport roles to grant or deny access to your database users. - -The [Automatic User Provisioning](./auto-user-provisioning.mdx) guides explain -how to get Teleport to create database user accounts on demand for MySQL, -PostgreSQL, and more. - - diff --git a/docs/pages/enroll-resources/database-access/rbac/configuring-access.mdx b/docs/pages/enroll-resources/database-access/rbac/configuring-access.mdx index 9f663b0f8eaea..ad927998c8466 100644 --- a/docs/pages/enroll-resources/database-access/rbac/configuring-access.mdx +++ b/docs/pages/enroll-resources/database-access/rbac/configuring-access.mdx @@ -99,7 +99,7 @@ is not currently enforced on MySQL connection attempts. Similar to other role fields, `db_*` fields support templating variables. The `external.xyz` traits are replaced with values from external [single -sign-on](../../../admin-guides/access-controls/sso.mdx) providers. For OIDC, they will be +sign-on](../../../admin-guides/access-controls/sso/sso.mdx) providers. For OIDC, they will be replaced with the value of an "xyz" claim. For SAML, they are replaced with an "xyz" assertion value. diff --git a/docs/pages/enroll-resources/database-access/rbac/rbac.mdx b/docs/pages/enroll-resources/database-access/rbac/rbac.mdx new file mode 100644 index 0000000000000..5071c9b2cd2d4 --- /dev/null +++ b/docs/pages/enroll-resources/database-access/rbac/rbac.mdx @@ -0,0 +1,8 @@ +--- +title: Database Access Control Guides +description: Role-based access control guides for Teleport database access. +--- + +These guides cover configuring access control policies for database users. + +(!toc!) diff --git a/docs/pages/enroll-resources/database-access/troubleshooting.mdx b/docs/pages/enroll-resources/database-access/troubleshooting.mdx index 0e32547e81662..fd24ec33816e0 100644 --- a/docs/pages/enroll-resources/database-access/troubleshooting.mdx +++ b/docs/pages/enroll-resources/database-access/troubleshooting.mdx @@ -145,7 +145,7 @@ Now Alice can connect to any database in the Teleport cluster using any database This example is intentionally simple; we could have configured Alice's permissions using more fine-grained control. For more detailed information about database access controls and how to restrict -access see the [RBAC](../database-access/rbac.mdx) documentation. +access see the [RBAC](rbac/rbac.mdx) documentation. ## Connection to MySQL database results in "Unknown system variable 'query_cache_size'" error diff --git a/docs/pages/enroll-resources/kubernetes-access/controls.mdx b/docs/pages/enroll-resources/kubernetes-access/controls.mdx index 235da2e0f7e65..4887bc00bda89 100644 --- a/docs/pages/enroll-resources/kubernetes-access/controls.mdx +++ b/docs/pages/enroll-resources/kubernetes-access/controls.mdx @@ -336,14 +336,6 @@ Below is a Kubernetes `ClusterRole` that grants the minimum set of permissions to enable impersonation, and a `ClusterRoleBinding` that grants these permissions to a service account. - - -There is usually no need to define these resources manually. The [manual -methods](./register-clusters.mdx) and [automatic methods](./discovery.mdx) for -registering Kubernetes clusters with Teleport include steps for setting up the -Kubernetes RBAC resources that Teleport needs to allow access to clusters. - - ```yaml apiVersion: rbac.authorization.k8s.io/v1 diff --git a/docs/pages/enroll-resources/kubernetes-access/discovery.mdx b/docs/pages/enroll-resources/kubernetes-access/discovery/discovery.mdx similarity index 96% rename from docs/pages/enroll-resources/kubernetes-access/discovery.mdx rename to docs/pages/enroll-resources/kubernetes-access/discovery/discovery.mdx index 6d5fb3b2f7a0b..944a4a28a363f 100644 --- a/docs/pages/enroll-resources/kubernetes-access/discovery.mdx +++ b/docs/pages/enroll-resources/kubernetes-access/discovery/discovery.mdx @@ -13,9 +13,9 @@ minimal access permissions. ## Supported clouds -- [AWS](./discovery/aws.mdx): Discovery for AWS EKS clusters. -- [Azure](./discovery/azure.mdx): Discovery for Azure AKS clusters. -- [Google Cloud](./discovery/google-cloud.mdx): Discovery for +- [AWS](aws.mdx): Discovery for AWS EKS clusters. +- [Azure](azure.mdx): Discovery for Azure AKS clusters. +- [Google Cloud](google-cloud.mdx): Discovery for Google Kubernetes Engine clusters. ## How Kubernetes Clusters Discovery works diff --git a/docs/pages/enroll-resources/kubernetes-access/faq.mdx b/docs/pages/enroll-resources/kubernetes-access/faq.mdx index 83d6bca552a19..f258b50794633 100644 --- a/docs/pages/enroll-resources/kubernetes-access/faq.mdx +++ b/docs/pages/enroll-resources/kubernetes-access/faq.mdx @@ -29,6 +29,3 @@ more information and examples. Since version 11, Teleport can discover your Kubernetes clusters on AWS, GCP, and Azure. -Check out the [Kubernetes Service Discovery Guide](./discovery.mdx) for more -documentation and examples. - diff --git a/docs/pages/enroll-resources/kubernetes-access/getting-started.mdx b/docs/pages/enroll-resources/kubernetes-access/getting-started.mdx index 6d9a31e9fe1bd..ba466c242dd63 100644 --- a/docs/pages/enroll-resources/kubernetes-access/getting-started.mdx +++ b/docs/pages/enroll-resources/kubernetes-access/getting-started.mdx @@ -16,7 +16,7 @@ Teleport Kubernetes Service running on the Kubernetes cluster: ![Enroll a Kubernetes cluster](../../../img/k8s/enroll-kubernetes.png) For information about other ways to enroll and discover Kubernetes clusters, see -[Registering Kubernetes Clusters with Teleport](./register-clusters.mdx). +[Registering Kubernetes Clusters with Teleport](register-clusters/register-clusters.mdx). ## Prerequisites @@ -205,9 +205,7 @@ To set up and test access: This guide demonstrated how to enroll a Kubernetes cluster by running the Teleport Kubernetes Service within the Kubernetes cluster. -- For information about discovering Kubernetes clusters hosted on cloud providers, see -[Kubernetes Cluster Discovery](./discovery.mdx). - To learn about other ways you can register a Kubernetes cluster with Teleport, see -[Registering Kubernetes Clusters with Teleport](./register-clusters.mdx). +[Registering Kubernetes Clusters with Teleport](register-clusters/register-clusters.mdx). - For a complete list of the parameters you can configure in the `teleport-kube-agent` helm chart, see the [Chart Reference](../../reference/helm-reference/teleport-kube-agent.mdx). diff --git a/docs/pages/enroll-resources/kubernetes-access/introduction.mdx b/docs/pages/enroll-resources/kubernetes-access/introduction.mdx index d936f2213941c..d81444985ac82 100644 --- a/docs/pages/enroll-resources/kubernetes-access/introduction.mdx +++ b/docs/pages/enroll-resources/kubernetes-access/introduction.mdx @@ -15,7 +15,7 @@ Teleport provides secure access to Kubernetes clusters: The guides in this section show you how to protect Kubernetes clusters with Teleport. For instructions on self-hosting Teleport Community Edition or Teleport Enterprise on Kubernetes, see the [Kubernetes Deployment -Guides](../../admin-guides/deploy-a-cluster/helm-deployments.mdx). +Guides](../../admin-guides/deploy-a-cluster/helm-deployments/helm-deployments.mdx). Here is an example of using Teleport to access a Kubernetes cluster, execute commands, and view your `kubectl` activity in Teleport's audit log: @@ -36,7 +36,7 @@ your cloud provider. When you create or destroy a Kubernetes cluster, Teleport registers or deregisters the cluster so your access controls stay up to date with your infrastructure. -[Read our overview](discovery.mdx) of how Teleport automatically discovers +[Read our overview](discovery/discovery.mdx) of how Teleport automatically discovers Kubernetes clusters. Read our guides to automatically registering Kubernetes clusters with Teleport diff --git a/docs/pages/enroll-resources/kubernetes-access/manage-access.mdx b/docs/pages/enroll-resources/kubernetes-access/manage-access/manage-access.mdx similarity index 81% rename from docs/pages/enroll-resources/kubernetes-access/manage-access.mdx rename to docs/pages/enroll-resources/kubernetes-access/manage-access/manage-access.mdx index e44be1e143dd2..219c95250592f 100644 --- a/docs/pages/enroll-resources/kubernetes-access/manage-access.mdx +++ b/docs/pages/enroll-resources/kubernetes-access/manage-access/manage-access.mdx @@ -6,13 +6,13 @@ description: Use Teleport's sophisticated RBAC and trusted clusters to ensure th Once you register a Kubernetes cluster with Teleport, you can apply sophisticated policies to manage the way users access your cluster. -Read our [Kubernetes RBAC guide](./manage-access/rbac.mdx) for step-by-step +Read our [Kubernetes RBAC guide](rbac.mdx) for step-by-step instructions on giving your users the correct access to Kubernetes clusters, groups, users, and resources. See how to federate your Kubernetes access controls using [Teleport Trusted -Clusters](./manage-access/federation.mdx). +Clusters](federation.mdx). For a comprehensive reference to configuring access controls in your Teleport-registered Kubernetes clusters, see our [Access Controls -Reference](./controls.mdx). +Reference](../controls.mdx). diff --git a/docs/pages/enroll-resources/kubernetes-access/manage-access/rbac.mdx b/docs/pages/enroll-resources/kubernetes-access/manage-access/rbac.mdx index b97a3379a6319..135079951bcfc 100644 --- a/docs/pages/enroll-resources/kubernetes-access/manage-access/rbac.mdx +++ b/docs/pages/enroll-resources/kubernetes-access/manage-access/rbac.mdx @@ -450,6 +450,6 @@ Now that you know how to configure Teleport's RBAC system to control access to Kubernetes clusters, learn how to set up [Resource Access Requests](../../../admin-guides/access-controls/access-requests/resource-requests.mdx) for just-in-time access and [Access Request -plugins](../../../admin-guides/access-controls/access-request-plugins.mdx) so you can manage +plugins](../../../admin-guides/access-controls/access-request-plugins/access-request-plugins.mdx) so you can manage access with your communication workflow of choice. diff --git a/docs/pages/enroll-resources/kubernetes-access/register-clusters.mdx b/docs/pages/enroll-resources/kubernetes-access/register-clusters/register-clusters.mdx similarity index 60% rename from docs/pages/enroll-resources/kubernetes-access/register-clusters.mdx rename to docs/pages/enroll-resources/kubernetes-access/register-clusters/register-clusters.mdx index 5d84fbc36a5c1..288c18813ffa8 100644 --- a/docs/pages/enroll-resources/kubernetes-access/register-clusters.mdx +++ b/docs/pages/enroll-resources/kubernetes-access/register-clusters/register-clusters.mdx @@ -5,16 +5,16 @@ layout: tocless-doc --- In some cases, you will want to register a Kubernetes cluster with Teleport -manually, rather than letting Teleport [discover the cluster -automatically](./discovery.mdx). There are a few ways to do this: +manually, rather than letting Teleport discover the cluster automatically. There +are a few ways to do this: - [Deploy the Teleport Kubernetes - Service with IAM Joining](./register-clusters/iam-joining.mdx) on your cluster of + Service with IAM Joining](iam-joining.mdx) on your cluster of choice. - Deploy the Teleport Kubernetes Service outside your Kubernetes cluster (e.g., directly on a virtual machine) and [give it access to a - kubeconfig](./register-clusters/static-kubeconfig.mdx). + kubeconfig](static-kubeconfig.mdx). - Deploy the Teleport Kubernetes Service outside of Kubernetes and [use dynamic - configuration resources](./register-clusters/dynamic-registration.mdx) to + configuration resources](dynamic-registration.mdx) to register your clusters. diff --git a/docs/pages/enroll-resources/machine-id/access-guides.mdx b/docs/pages/enroll-resources/machine-id/access-guides.mdx deleted file mode 100644 index 5b38f57dc64c6..0000000000000 --- a/docs/pages/enroll-resources/machine-id/access-guides.mdx +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Access your Infrastructure with Machine ID -description: How to use Machine ID to enable secure access to Teleport resources. -layout: tocless-doc ---- - -These guides cover how to configure a deployed Machine ID to produce credentials -that can be used for machine to machine access to different Teleport resources. - -It is a pre-requisite of these guides that Machine ID has been configured for -your platform, see the [Deploy Machine ID](./deployment.mdx) guides for information -on how to do so. - -## Resource Access - -- [Server Access](./access-guides/ssh.mdx): How to use Machine ID to access servers via SSH. -- [Kubernetes Access](./access-guides/kubernetes.mdx): How to use Machine ID to access Kubernetes clusters. -- [Database Access](./access-guides/databases.mdx): How to use Machine ID to access Database servers. -- [Application Access](./access-guides/applications.mdx): How to use Machine ID to access Applications. - -## Specific Tools - -- [tctl](./access-guides/tctl.mdx): How to use Machine ID with `tctl` to manage your Teleport configuration. -- [Teleport Terraform](./access-guides/terraform.mdx): How to use Machine ID with the Teleport Terraform provider to manage your Teleport configuration as IaC. -- [Ansible](./access-guides/ansible.mdx): How to use Machine ID with Ansible. diff --git a/docs/pages/enroll-resources/machine-id/access-guides/access-guides.mdx b/docs/pages/enroll-resources/machine-id/access-guides/access-guides.mdx new file mode 100644 index 0000000000000..a61e9b4b03124 --- /dev/null +++ b/docs/pages/enroll-resources/machine-id/access-guides/access-guides.mdx @@ -0,0 +1,25 @@ +--- +title: Access your Infrastructure with Machine ID +description: How to use Machine ID to enable secure access to Teleport resources. +layout: tocless-doc +--- + +These guides cover how to configure a deployed Machine ID to produce credentials +that can be used for machine to machine access to different Teleport resources. + +It is a pre-requisite of these guides that Machine ID has been configured for +your platform, see the [Deploy Machine ID](../deployment/deployment.mdx) guides for information +on how to do so. + +## Resource Access + +- [Server Access](ssh.mdx): How to use Machine ID to access servers via SSH. +- [Kubernetes Access](kubernetes.mdx): How to use Machine ID to access Kubernetes clusters. +- [Database Access](databases.mdx): How to use Machine ID to access Database servers. +- [Application Access](applications.mdx): How to use Machine ID to access Applications. + +## Specific Tools + +- [tctl](tctl.mdx): How to use Machine ID with `tctl` to manage your Teleport configuration. +- [Teleport Terraform](terraform.mdx): How to use Machine ID with the Teleport Terraform provider to manage your Teleport configuration as IaC. +- [Ansible](ansible.mdx): How to use Machine ID with Ansible. diff --git a/docs/pages/enroll-resources/machine-id/access-guides/ansible.mdx b/docs/pages/enroll-resources/machine-id/access-guides/ansible.mdx index 92518e21d4e75..5894a1bd41b41 100644 --- a/docs/pages/enroll-resources/machine-id/access-guides/ansible.mdx +++ b/docs/pages/enroll-resources/machine-id/access-guides/ansible.mdx @@ -26,7 +26,7 @@ You will need the following tools to use Teleport with Ansible. - `tbot` must already be installed and configured on the machine that will run Ansible. For more information, see the - [deployment guides](../deployment.mdx). + [deployment guides](../deployment/deployment.mdx). - If you followed the above guide, note the `--destination-dir=/opt/machine-id` flag, which defines the directory where SSH certificates and OpenSSH configuration diff --git a/docs/pages/enroll-resources/machine-id/access-guides/applications.mdx b/docs/pages/enroll-resources/machine-id/access-guides/applications.mdx index ea42911de9062..7b0f80449fca7 100644 --- a/docs/pages/enroll-resources/machine-id/access-guides/applications.mdx +++ b/docs/pages/enroll-resources/machine-id/access-guides/applications.mdx @@ -20,7 +20,7 @@ used to access an application enrolled in your Teleport cluster. - (!docs/pages/includes/tctl.mdx!) - `tbot` must already be installed and configured on the machine that will access applications. For more information, see the - [deployment guides](../deployment.mdx). + [deployment guides](../deployment/deployment.mdx). ## Step 1/3. Configure RBAC diff --git a/docs/pages/enroll-resources/machine-id/access-guides/databases.mdx b/docs/pages/enroll-resources/machine-id/access-guides/databases.mdx index cffe895247bd6..2ad694b40d283 100644 --- a/docs/pages/enroll-resources/machine-id/access-guides/databases.mdx +++ b/docs/pages/enroll-resources/machine-id/access-guides/databases.mdx @@ -23,7 +23,7 @@ used to access a database configured in Teleport. follow the [database access getting started guide](../../database-access/getting-started.mdx). The Teleport Database Service supports databases like PostgreSQL, MongoDB, Redis, and much more. See our [database access - guides](../../database-access/guides.mdx) for a complete list. + guides](../../database-access/guides/guides.mdx) for a complete list. - (!docs/pages/includes/tctl.mdx!) - The `tsh` binary must be installed on the machine that will access the database. Depending on how `tbot` was installed, this may already be @@ -31,7 +31,7 @@ used to access a database configured in Teleport. details. - `tbot` must already be installed and configured on the machine that will access the database. For more information, see the - [deployment guides](../deployment.mdx). + [deployment guides](../deployment/deployment.mdx). ## Step 1/4. Configure RBAC diff --git a/docs/pages/enroll-resources/machine-id/access-guides/kubernetes.mdx b/docs/pages/enroll-resources/machine-id/access-guides/kubernetes.mdx index bb76d56c33cbb..9bc2adba2fe58 100644 --- a/docs/pages/enroll-resources/machine-id/access-guides/kubernetes.mdx +++ b/docs/pages/enroll-resources/machine-id/access-guides/kubernetes.mdx @@ -28,7 +28,7 @@ used to access a Kubernetes cluster enrolled with your Teleport cluster. installation instructions. - `tbot` must already be installed and configured on the machine that will access Kubernetes clusters. For more information, see the - [deployment guides](../deployment.mdx). + [deployment guides](../deployment/deployment.mdx). - To demonstrate connecting to the Kubernetes cluster, the machine that will access Kubernetes clusters will need to have `kubectl` installed. See the [Kubernetes documentation](https://kubernetes.io/docs/tasks/tools/) for diff --git a/docs/pages/enroll-resources/machine-id/access-guides/ssh.mdx b/docs/pages/enroll-resources/machine-id/access-guides/ssh.mdx index 3a5582b2cbb55..9fcc0cc6b116c 100644 --- a/docs/pages/enroll-resources/machine-id/access-guides/ssh.mdx +++ b/docs/pages/enroll-resources/machine-id/access-guides/ssh.mdx @@ -21,7 +21,7 @@ will cover access using the Teleport CLI `tsh` as well as the OpenSSH client. - (!docs/pages/includes/tctl.mdx!) - `tbot` must already be installed and configured on the machine that will connect to Linux hosts with SSH. For more information, see the - [deployment guides](../deployment.mdx). + [deployment guides](../deployment/deployment.mdx). ## Step 1/3. Configure RBAC diff --git a/docs/pages/enroll-resources/machine-id/access-guides/tctl.mdx b/docs/pages/enroll-resources/machine-id/access-guides/tctl.mdx index 43999f3bd1356..be80f28a70bed 100644 --- a/docs/pages/enroll-resources/machine-id/access-guides/tctl.mdx +++ b/docs/pages/enroll-resources/machine-id/access-guides/tctl.mdx @@ -18,7 +18,7 @@ then use `tctl` to deploy Teleport roles defined in files. - (!docs/pages/includes/tctl.mdx!) - `tbot` must already be installed and configured on the machine that will use `tctl`. For more information, see the - [deployment guides](../deployment.mdx). + [deployment guides](../deployment/deployment.mdx). ## Step 1/3. Configure RBAC diff --git a/docs/pages/enroll-resources/machine-id/access-guides/terraform.mdx b/docs/pages/enroll-resources/machine-id/access-guides/terraform.mdx index 1e554afea6158..a697f4cdbc0f1 100644 --- a/docs/pages/enroll-resources/machine-id/access-guides/terraform.mdx +++ b/docs/pages/enroll-resources/machine-id/access-guides/terraform.mdx @@ -25,7 +25,7 @@ Terraform Provider and use Terraform to configure a Teleport role. - `tbot` must already be installed and configured on the machine that will run Terraform. For more information, see the - [deployment guides](../deployment.mdx). + [deployment guides](../deployment/deployment.mdx). ## Step 1/3. Configure RBAC diff --git a/docs/pages/enroll-resources/machine-id/deployment.mdx b/docs/pages/enroll-resources/machine-id/deployment.mdx deleted file mode 100644 index b65083201f155..0000000000000 --- a/docs/pages/enroll-resources/machine-id/deployment.mdx +++ /dev/null @@ -1,74 +0,0 @@ ---- -title: Deploy Machine ID -description: Explains how to deploy Machine ID on your platform and join it to your Teleport cluster. -tocDepth: 3 ---- - -The first step to set up Machine ID is to deploy the `tbot` binary and join a -Machine ID bot to your Teleport cluster. You can run the `tbot` binary on a -number of platforms, from AWS and GitHub Actions to a generic Linux server or -Kubernetes cluster. This guide shows you how to deploy Machine ID on your -infrastructure. - -## Choosing a deployment method - -There are two considerations to make when determining how to deploy Machine ID on -your infrastructure. - -### Your infrastructure - -The `tbot` binary runs as a container or on a Linux virtual machine. If you run -`tbot` on GitHub Actions, you can use one of the ready-made [Teleport GitHub -Actions workflows](https://github.com/teleport-actions). - -### Join method - -Machine ID joins your Teleport cluster by using one of the following -authentication methods: - -- **Platform-signed document:** The platform that hosts `tbot`, such as a - Kubernetes cluster or Amazon EC2 instance, provides a signed identity document - that Teleport can verify using the platform's certificate authority. This is - the recommended approach because it avoids the use of shared secrets. -- **Static join token:** Your Teleport client tool generates a string and stores - it on the Teleport Auth Service. Machine ID provides this string when it first - connects to your Teleport cluster, demonstrating to the Auth Service that it - belongs in the cluster. From then on, Machine ID authenticates to your - Teleport cluster with a renewable certificate. - -## Deployment guides - -The guides in this section show you how to deploy Machine ID and join it -to your cluster. Choose a guide based on the platform where you intend to run -Machine ID. - -If a specific guide does not exist for your platform, the [Linux -guide](./deployment/linux.mdx) is compatible with most platforms. For -custom approaches, you can also read the [Machine ID Reference](../../reference/machine-id/machine-id.mdx) -and [Architecture](../../reference/architecture/machine-id-architecture.mdx) to plan your deployment. - -### Self-hosted infrastructure - -Read the following guides for how to deploy Machine ID on your cloud platform or -on-prem infrastructure. - -|Platform|Installation method|Join method| -|---|---|---| -|[Linux](./deployment/linux.mdx)|Package manager or TAR archive|Static join token| -|[GCP](./deployment/gcp.mdx)|Package manager, TAR archive, or Kubernetes pod|Identity document signed by GCP| -|[AWS](./deployment/aws.mdx)|Package manager, TAR archive, or Kubernetes pod|Identity document signed by AWS| -|[Azure](./deployment/azure.mdx)|Package manager or TAR archive|Identity document signed by Azure| -|[Kubernetes](./deployment/kubernetes.mdx)|Kubernetes pod|Identity document signed by your Kubernetes cluster| - -### CI/CD - -Read the following guides for how to deploy Machine ID on a continuous -integration and continuous deployment platform - -|Platform|Installation method|Join method| -|---|---|---| -|[CircleCI](./deployment/circleci.mdx)|TAR archive|CircleCI-signed identity document| -|[GitLab](./deployment/gitlab.mdx)|TAR archive|GitLab-signed identity document| -|[GitHub Actions](./deployment/github-actions.mdx)|Teleport job available through the GitHub Actions marketplace|GitHub-signed identity document.| -|[Jenkins](./deployment/jenkins.mdx)|Package manager or TAR archive|Static join token| -|[Spacelift](./deployment/spacelift.mdx)|Docker Image|Spacelift-signed identity document| diff --git a/docs/pages/enroll-resources/machine-id/deployment/aws.mdx b/docs/pages/enroll-resources/machine-id/deployment/aws.mdx index 2b3693897425d..45b7a3bb61d2b 100644 --- a/docs/pages/enroll-resources/machine-id/deployment/aws.mdx +++ b/docs/pages/enroll-resources/machine-id/deployment/aws.mdx @@ -123,7 +123,7 @@ Replace: ## Next steps -- Follow the [access guides](../access-guides.mdx) to finish configuring `tbot` for +- Follow the [access guides](../access-guides/access-guides.mdx) to finish configuring `tbot` for your environment. - Read the [configuration reference](../../../reference/machine-id/configuration.mdx) to explore all the available configuration options. diff --git a/docs/pages/enroll-resources/machine-id/deployment/azure.mdx b/docs/pages/enroll-resources/machine-id/deployment/azure.mdx index 0567904dadc1e..11178e840732d 100644 --- a/docs/pages/enroll-resources/machine-id/deployment/azure.mdx +++ b/docs/pages/enroll-resources/machine-id/deployment/azure.mdx @@ -124,7 +124,7 @@ Replace: ## Next steps -- Follow the [access guides](../access-guides.mdx) to finish configuring `tbot` for +- Follow the [access guides](../access-guides/access-guides.mdx) to finish configuring `tbot` for your environment. - Read the [configuration reference](../../../reference/machine-id/configuration.mdx) to explore all the available configuration options. diff --git a/docs/pages/enroll-resources/machine-id/deployment/circleci.mdx b/docs/pages/enroll-resources/machine-id/deployment/circleci.mdx index 8a759109c66a2..bb255e4a60606 100644 --- a/docs/pages/enroll-resources/machine-id/deployment/circleci.mdx +++ b/docs/pages/enroll-resources/machine-id/deployment/circleci.mdx @@ -213,7 +213,7 @@ resources in your Teleport cluster that your CI/CD needs to interact with. ## Further steps -- Follow the [access guides](../access-guides.mdx) to finish configuring `tbot` for +- Follow the [access guides](../access-guides/access-guides.mdx) to finish configuring `tbot` for your environment. - Read the [configuration reference](../../../reference/machine-id/configuration.mdx) to explore all the available configuration options. diff --git a/docs/pages/enroll-resources/machine-id/deployment/deployment.mdx b/docs/pages/enroll-resources/machine-id/deployment/deployment.mdx new file mode 100644 index 0000000000000..da8051effae55 --- /dev/null +++ b/docs/pages/enroll-resources/machine-id/deployment/deployment.mdx @@ -0,0 +1,73 @@ +--- +title: Deploy Machine ID +description: Explains how to deploy Machine ID on your platform and join it to your Teleport cluster. +tocDepth: 3 +--- + +The first step to set up Machine ID is to deploy the `tbot` binary and join a +Machine ID bot to your Teleport cluster. You can run the `tbot` binary on a +number of platforms, from AWS and GitHub Actions to a generic Linux server or +Kubernetes cluster. This guide shows you how to deploy Machine ID on your +infrastructure. + +## Choosing a deployment method + +There are two considerations to make when determining how to deploy Machine ID on +your infrastructure. + +### Your infrastructure + +The `tbot` binary runs as a container or on a Linux virtual machine. If you run +`tbot` on GitHub Actions, you can use one of the ready-made [Teleport GitHub +Actions workflows](https://github.com/teleport-actions). + +### Join method + +Machine ID joins your Teleport cluster by using one of the following +authentication methods: + +- **Platform-signed document:** The platform that hosts `tbot`, such as a + Kubernetes cluster or Amazon EC2 instance, provides a signed identity document + that Teleport can verify using the platform's certificate authority. This is + the recommended approach because it avoids the use of shared secrets. +- **Static join token:** Your Teleport client tool generates a string and stores + it on the Teleport Auth Service. Machine ID provides this string when it first + connects to your Teleport cluster, demonstrating to the Auth Service that it + belongs in the cluster. From then on, Machine ID authenticates to your + Teleport cluster with a renewable certificate. + +## Deployment guides + +The guides in this section show you how to deploy Machine ID and join it +to your cluster. Choose a guide based on the platform where you intend to run +Machine ID. + +If a specific guide does not exist for your platform, the [Linux +guide](linux.mdx) is compatible with most platforms. For +custom approaches, you can also read the [Machine ID Reference](../../../reference/machine-id/machine-id.mdx) +and [Architecture](../../../reference/architecture/machine-id-architecture.mdx) to plan your deployment. + +### Self-hosted infrastructure + +Read the following guides for how to deploy Machine ID on your cloud platform or +on-prem infrastructure. + +| Platform | Installation method | Join method | +|-------------------------------------------|-------------------------------------------------|-----------------------------------------------------| +| [Linux](linux.mdx) | Package manager or TAR archive | Static join token | +| [GCP](gcp.mdx) | Package manager, TAR archive, or Kubernetes pod | Identity document signed by GCP | +| [AWS](aws.mdx) | Package manager, TAR archive, or Kubernetes pod | Identity document signed by AWS | +| [Azure](azure.mdx) | Package manager or TAR archive | Identity document signed by Azure | +| [Kubernetes](kubernetes.mdx) | Kubernetes pod | Identity document signed by your Kubernetes cluster | + +### CI/CD + +Read the following guides for how to deploy Machine ID on a continuous +integration and continuous deployment platform + +| Platform | Installation method | Join method | +|-----------------------------------------------------------------------------------------------------|---------------------------------------------------------------|------------------------------------| +| [CircleCI](circleci.mdx) | TAR archive | CircleCI-signed identity document | +| [GitLab](gitlab.mdx) | TAR archive | GitLab-signed identity document | +| [GitHub Actions](github-actions.mdx) | Teleport job available through the GitHub Actions marketplace | GitHub-signed identity document. | +| [Jenkins](jenkins.mdx) | Package manager or TAR archive | Static join token | diff --git a/docs/pages/enroll-resources/machine-id/deployment/gcp.mdx b/docs/pages/enroll-resources/machine-id/deployment/gcp.mdx index d0c4da83941c8..e5cf2a5e0f50d 100644 --- a/docs/pages/enroll-resources/machine-id/deployment/gcp.mdx +++ b/docs/pages/enroll-resources/machine-id/deployment/gcp.mdx @@ -125,7 +125,7 @@ Replace: ## Next steps -- Follow the [access guides](../access-guides.mdx) to finish configuring `tbot` for +- Follow the [access guides](../access-guides/access-guides.mdx) to finish configuring `tbot` for your environment. - Read the [configuration reference](../../../reference/machine-id/configuration.mdx) to explore all the available configuration options. diff --git a/docs/pages/enroll-resources/machine-id/deployment/gitlab.mdx b/docs/pages/enroll-resources/machine-id/deployment/gitlab.mdx index 24d2b73e4958c..43b08f6d399b1 100644 --- a/docs/pages/enroll-resources/machine-id/deployment/gitlab.mdx +++ b/docs/pages/enroll-resources/machine-id/deployment/gitlab.mdx @@ -179,7 +179,7 @@ failure. [GitLab CI reference page.](../../../reference/machine-id/gitlab.mdx) - For more information about GitLab itself, read [their documentation](https://docs.gitlab.com/ee/ci/). -- Follow the [access guides](../access-guides.mdx) to finish configuring `tbot` for +- Follow the [access guides](../access-guides/access-guides.mdx) to finish configuring `tbot` for your environment. - Read the [configuration reference](../../../reference/machine-id/configuration.mdx) to explore all the available configuration options. diff --git a/docs/pages/enroll-resources/machine-id/deployment/kubernetes.mdx b/docs/pages/enroll-resources/machine-id/deployment/kubernetes.mdx index 17c8fc18e6c4c..5c65640d26819 100644 --- a/docs/pages/enroll-resources/machine-id/deployment/kubernetes.mdx +++ b/docs/pages/enroll-resources/machine-id/deployment/kubernetes.mdx @@ -311,7 +311,7 @@ However, it is not yet producing any useful output. ## Step 4/4. Configure outputs -Follow one of the [access guides](../access-guides.mdx) to configure an output +Follow one of the [access guides](../access-guides/access-guides.mdx) to configure an output that meets your access needs. In order to adjust the access guides to work well with Kubernetes, use the @@ -357,7 +357,7 @@ spec: ## Next steps -- Follow the [access guides](../access-guides.mdx) to finish configuring `tbot` for +- Follow the [access guides](../access-guides/access-guides.mdx) to finish configuring `tbot` for your environment. - Read the [configuration reference](../../../reference/machine-id/configuration.mdx) to explore all the available configuration options. diff --git a/docs/pages/enroll-resources/machine-id/deployment/linux.mdx b/docs/pages/enroll-resources/machine-id/deployment/linux.mdx index 86f97ac6a04de..8f75891322ff3 100644 --- a/docs/pages/enroll-resources/machine-id/deployment/linux.mdx +++ b/docs/pages/enroll-resources/machine-id/deployment/linux.mdx @@ -112,7 +112,7 @@ $ sudo chown teleport:teleport /var/lib/teleport/bot ## Next steps -- Follow the [access guides](../access-guides.mdx) to finish configuring `tbot` for +- Follow the [access guides](../access-guides/access-guides.mdx) to finish configuring `tbot` for your environment. - Read the [configuration reference](../../../reference/machine-id/configuration.mdx) to explore all the available configuration options. diff --git a/docs/pages/enroll-resources/machine-id/getting-started.mdx b/docs/pages/enroll-resources/machine-id/getting-started.mdx index 6ae666a074f2b..131c97edbbceb 100644 --- a/docs/pages/enroll-resources/machine-id/getting-started.mdx +++ b/docs/pages/enroll-resources/machine-id/getting-started.mdx @@ -15,7 +15,7 @@ Here's an overview of what you will do: This guide covers configuring Machine ID for development and learning purposes. For a production-ready configuration of Machine ID, visit the [Deploying Machine -ID](./deployment.mdx) guides. +ID](deployment/deployment.mdx) guides. ## Prerequisites @@ -300,9 +300,9 @@ and controlled with all the familiar Teleport access controls. - Read the [architecture overview](../../reference/architecture/machine-id-architecture.mdx) to learn about how Machine ID works in more detail. -- Check out the [deployment guides](./deployment.mdx) to learn about +- Check out the [deployment guides](deployment/deployment.mdx) to learn about configuring `tbot` in a production-ready way for your platform. -- Check out the [access guides](./access-guides.mdx) to learn about configuring +- Check out the [access guides](access-guides/access-guides.mdx) to learn about configuring `tbot` for different use cases than SSH. - Read the [configuration reference](../../reference/machine-id/configuration.mdx) to explore all the available configuration options. diff --git a/docs/pages/enroll-resources/machine-id/introduction.mdx b/docs/pages/enroll-resources/machine-id/introduction.mdx index 010f60bae65aa..734159446437c 100644 --- a/docs/pages/enroll-resources/machine-id/introduction.mdx +++ b/docs/pages/enroll-resources/machine-id/introduction.mdx @@ -73,9 +73,9 @@ For a quickstart non-production introduction to Machine ID, read the Production-ready guidance on deploying Machine ID is broken out into two parts: -- [Deploying Machine ID](./deployment.mdx): How to install and configure +- [Deploying Machine ID](deployment/deployment.mdx): How to install and configure Machine ID for a specific platform. -- [Access your Infrastructure with Machine ID](./access-guides.mdx): How to use Machine ID to access +- [Access your Infrastructure with Machine ID](access-guides/access-guides.mdx): How to use Machine ID to access Teleport and Teleport resources. ## Further reading diff --git a/docs/pages/enroll-resources/server-access/getting-started.mdx b/docs/pages/enroll-resources/server-access/getting-started.mdx index 139813b29b00f..669be0463eb53 100644 --- a/docs/pages/enroll-resources/server-access/getting-started.mdx +++ b/docs/pages/enroll-resources/server-access/getting-started.mdx @@ -392,7 +392,7 @@ further Getting Started exercises. - While this guide shows you how to create a local user in order to access a server, you can also enable Teleport users to authenticate through a single sign-on provider. Read the - [documentation](../../admin-guides/access-controls/sso.mdx) to learn more. + [documentation](../../admin-guides/access-controls/sso/sso.mdx) to learn more. - Learn more about Teleport `tsh` through the [reference documentation](../../reference/cli/tsh.mdx#tsh-ssh). - Learn more about [Teleport servers](../../reference/architecture/nodes.mdx) - For a complete list of ports used by Teleport, read the [Networking Guide](../../reference/networking.mdx). diff --git a/docs/pages/enroll-resources/server-access/guides.mdx b/docs/pages/enroll-resources/server-access/guides.mdx deleted file mode 100644 index 18a3fbf80ffae..0000000000000 --- a/docs/pages/enroll-resources/server-access/guides.mdx +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Server Access Guides -description: Teleport server access guides. -layout: tocless-doc ---- - -- [Using Teleport with PAM](./guides/ssh-pam.mdx): How to configure Teleport SSH with PAM (Pluggable Authentication Modules). -- [Agentless OpenSSH Integration](guides/openssh/openssh-agentless.mdx): How to use Teleport in agentless mode on systems with OpenSSH and `sshd`. -- [Agentless OpenSSH Integration (Manual Installation)](./guides/openssh/openssh-manual-install.mdx): How to use Teleport in agentless mode - on systems with OpenSSH and `sshd` that can't run `teleport`. -- [Recording Proxy Mode](./guides/recording-proxy-mode.mdx): How to use Teleport Recording Proxy Mode to capture activity on OpenSSH servers. -- [BPF Session Recording](./guides/bpf-session-recording.mdx): How to use BPF to record SSH session commands, modified files and network connections. -- [Restricted Session](./guides/restricted-session.mdx): How to configure and use Restricted Session to apply security policies to SSH sessions. -- [Visual Studio Code](./guides/vscode.mdx): How to remotely develop with Visual Studio Code and Teleport. -- [JetBrains SFTP](./guides/jetbrains-sftp.mdx): How to use a JetBrains IDE to access SFTP with Teleport. -- [Host User Creation](./guides/host-user-creation.mdx): How to configure Teleport to automatically create transient host users. -- [Linux Auditing System](./guides/auditd.mdx): How to integrate Teleport with the Linux Auditing System (auditd). -- [EC2 Instance Discovery](./guides/ec2-discovery.mdx): How to configure Teleport to automatically enroll EC2 instances. -- [Azure Instance Discovery](./guides/azure-discovery.mdx): How to configure Teleport to automatically enroll Azure virtual machines. -- [GCP Instance Discovery](./guides/gcp-discovery.mdx): How to configure Teleport to automatically enroll GCP instances. -- [Using Teleport with Ansible](./guides/ansible.mdx): How to use Ansible with - Teleport-issued SSH credentials. diff --git a/docs/pages/enroll-resources/server-access/guides/guides.mdx b/docs/pages/enroll-resources/server-access/guides/guides.mdx new file mode 100644 index 0000000000000..53e2cb4c8ed33 --- /dev/null +++ b/docs/pages/enroll-resources/server-access/guides/guides.mdx @@ -0,0 +1,22 @@ +--- +title: Server Access Guides +description: Teleport server access guides. +layout: tocless-doc +--- + +- [Using Teleport with PAM](ssh-pam.mdx): How to configure Teleport SSH with PAM (Pluggable Authentication Modules). +- [Agentless OpenSSH Integration](openssh/openssh-agentless.mdx): How to use Teleport in agentless mode on systems with OpenSSH and `sshd`. +- [Agentless OpenSSH Integration (Manual Installation)](openssh/openssh-manual-install.mdx): How to use Teleport in agentless mode + on systems with OpenSSH and `sshd` that can't run `teleport`. +- [Recording Proxy Mode](recording-proxy-mode.mdx): How to use Teleport Recording Proxy Mode to capture activity on OpenSSH servers. +- [BPF Session Recording](bpf-session-recording.mdx): How to use BPF to record SSH session commands, modified files and network connections. +- [Restricted Session](restricted-session.mdx): How to configure and use Restricted Session to apply security policies to SSH sessions. +- [Visual Studio Code](vscode.mdx): How to remotely develop with Visual Studio Code and Teleport. +- [JetBrains SFTP](jetbrains-sftp.mdx): How to use a JetBrains IDE to access SFTP with Teleport. +- [Host User Creation](host-user-creation.mdx): How to configure Teleport to automatically create transient host users. +- [Linux Auditing System](auditd.mdx): How to integrate Teleport with the Linux Auditing System (auditd). +- [EC2 Instance Discovery](ec2-discovery.mdx): How to configure Teleport to automatically enroll EC2 instances. +- [Azure Instance Discovery](azure-discovery.mdx): How to configure Teleport to automatically enroll Azure virtual machines. +- [GCP Instance Discovery](gcp-discovery.mdx): How to configure Teleport to automatically enroll GCP instances. +- [Using Teleport with Ansible](ansible.mdx): How to use Ansible with + Teleport-issued SSH credentials. diff --git a/docs/pages/enroll-resources/server-access/guides/host-user-creation.mdx b/docs/pages/enroll-resources/server-access/guides/host-user-creation.mdx index 73778804ca847..8ecb5c2cad0cf 100644 --- a/docs/pages/enroll-resources/server-access/guides/host-user-creation.mdx +++ b/docs/pages/enroll-resources/server-access/guides/host-user-creation.mdx @@ -237,4 +237,4 @@ them to the `teleport-keep` group directly on the hosts you wish to migrate. ## Next steps -- Configure automatic user provisioning for [database access](../../database-access/auto-user-provisioning.mdx). +- Configure automatic user provisioning for [database access](../../../index.mdx). diff --git a/docs/pages/enroll-resources/server-access/guides/jetbrains-sftp.mdx b/docs/pages/enroll-resources/server-access/guides/jetbrains-sftp.mdx index a29b79c275ad8..604bb625cd406 100644 --- a/docs/pages/enroll-resources/server-access/guides/jetbrains-sftp.mdx +++ b/docs/pages/enroll-resources/server-access/guides/jetbrains-sftp.mdx @@ -66,8 +66,6 @@ $ ssh user@[server name].[cluster name] Include the port number for OpenSSH servers, by default `22`, or you can experience an error. - See the [OpenSSH guide](./openssh.mdx) for more information. - Example connecting to a OpenSSH server: ```code $ ssh -p 22 user@[server name].[cluster name] @@ -128,7 +126,7 @@ After closing the SSH configuration window, you should see `Remote Host` menu in ### Using OpenSSH clients This guide makes use of `tsh config`; refer to the -[dedicated guide](./openssh.mdx) for additional information. +[dedicated guide](openssh/openssh.mdx) for additional information. ## Further reading - [JetBrains - Create a remote server configuration](https://www.jetbrains.com/help/idea/creating-a-remote-server-configuration.html#overload) diff --git a/docs/pages/enroll-resources/server-access/guides/openssh.mdx b/docs/pages/enroll-resources/server-access/guides/openssh.mdx deleted file mode 100644 index e7632cfd9b097..0000000000000 --- a/docs/pages/enroll-resources/server-access/guides/openssh.mdx +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: OpenSSH Guides -description: Teleport Agentless OpenSSH integration guides. -layout: tocless-doc ---- - -- [Agentless OpenSSH Integration](openssh/openssh-agentless.mdx): How to use Teleport in agentless mode on systems with OpenSSH and `sshd`. -- [Agentless OpenSSH Integration (Manual Installation)](./openssh/openssh-manual-install.mdx): How to use Teleport in agentless mode - on systems with OpenSSH and `sshd` that can't run `teleport`. diff --git a/docs/pages/enroll-resources/server-access/guides/openssh/openssh.mdx b/docs/pages/enroll-resources/server-access/guides/openssh/openssh.mdx new file mode 100644 index 0000000000000..2af40693b1fc5 --- /dev/null +++ b/docs/pages/enroll-resources/server-access/guides/openssh/openssh.mdx @@ -0,0 +1,9 @@ +--- +title: OpenSSH Guides +description: Teleport Agentless OpenSSH integration guides. +layout: tocless-doc +--- + +- [Agentless OpenSSH Integration](openssh-agentless.mdx): How to use Teleport in agentless mode on systems with OpenSSH and `sshd`. +- [Agentless OpenSSH Integration (Manual Installation)](openssh-manual-install.mdx): How to use Teleport in agentless mode + on systems with OpenSSH and `sshd` that can't run `teleport`. diff --git a/docs/pages/enroll-resources/server-access/guides/vscode.mdx b/docs/pages/enroll-resources/server-access/guides/vscode.mdx index ae8d33e1d975f..78d13787d754a 100644 --- a/docs/pages/enroll-resources/server-access/guides/vscode.mdx +++ b/docs/pages/enroll-resources/server-access/guides/vscode.mdx @@ -151,14 +151,14 @@ The Window Indicator in the bottom left highlights the currently connected remot It's possible to remotely develop on any OpenSSH host joined to a Teleport cluster so long as its host OS is supported by VS Code. Refer to the -[OpenSSH guide](./openssh.mdx) to configure the remote host to authenticate via +[OpenSSH guide](openssh/openssh.mdx) to configure the remote host to authenticate via Teleport certificates, after which the procedure outlined above can be used to connect to the host in VS Code. ### Using OpenSSH clients This guide makes use of `tsh config`; refer to the -[dedicated guide](./openssh.mdx) for additional information. +[dedicated guide](openssh/openssh.mdx) for additional information. ## Further reading - [VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview) diff --git a/docs/pages/enroll-resources/server-access/introduction.mdx b/docs/pages/enroll-resources/server-access/introduction.mdx index a0cef2b718d9b..d40557c143100 100644 --- a/docs/pages/enroll-resources/server-access/introduction.mdx +++ b/docs/pages/enroll-resources/server-access/introduction.mdx @@ -25,7 +25,7 @@ Teleport server access is designed for the following kinds of scenarios: ## Guides - [Using Teleport with PAM](./guides/ssh-pam.mdx): How to configure Teleport SSH with PAM (Pluggable Authentication Modules). -- [Agentless OpenSSH Integration](./guides/openssh.mdx): How to use Teleport in agentless mode on systems with OpenSSH and `sshd`. +- [Agentless OpenSSH Integration](./guides/openssh/openssh.mdx): How to use Teleport in agentless mode on systems with OpenSSH and `sshd`. - [Recording Proxy Mode](./guides/recording-proxy-mode.mdx): How to use Teleport Recording Proxy Mode to capture activity on OpenSSH servers. - [BPF Session Recording](./guides/bpf-session-recording.mdx): How to use BPF to record SSH session commands, modified files and network connections. - [Restricted Session](./guides/restricted-session.mdx): How to configure and use Restricted Session to apply security policies to SSH sessions. diff --git a/docs/pages/enroll-resources/server-access/rbac.mdx b/docs/pages/enroll-resources/server-access/rbac.mdx index 34e21fbfd4daa..31f07b562ae36 100644 --- a/docs/pages/enroll-resources/server-access/rbac.mdx +++ b/docs/pages/enroll-resources/server-access/rbac.mdx @@ -71,7 +71,7 @@ spec: Similar to role fields for accessing other resources in Teleport, server-related fields support template variables. -Variables with the format `{{external.xyz}}` are replaced with values from external [SSO](../../admin-guides/access-controls/sso.mdx) +Variables with the format `{{external.xyz}}` are replaced with values from external [SSO](../../admin-guides/access-controls/sso/sso.mdx) providers. For OIDC logins, `{{external.xyz}}` refers to the "xyz" claim; for SAML logins, `{{external.xyz}}` refers to the "xyz" assertion. diff --git a/docs/pages/faq.mdx b/docs/pages/faq.mdx index 85f422346e383..a99f40ced1c87 100644 --- a/docs/pages/faq.mdx +++ b/docs/pages/faq.mdx @@ -29,7 +29,7 @@ functionality without a net addition of an agent on your system. ## Can I use OpenSSH with a Teleport cluster? Yes, this question comes up often and is related to the previous one. Take a -look at [Using OpenSSH Guide](./enroll-resources/server-access/guides/openssh.mdx). +look at [Using OpenSSH Guide](index.mdx). ## Can I connect to nodes behind a firewall? diff --git a/docs/pages/includes/database-access/aws-auto-discovery-prerequisite.mdx b/docs/pages/includes/database-access/aws-auto-discovery-prerequisite.mdx index f964e69e993d8..d0b7e86b8d9f5 100644 --- a/docs/pages/includes/database-access/aws-auto-discovery-prerequisite.mdx +++ b/docs/pages/includes/database-access/aws-auto-discovery-prerequisite.mdx @@ -1,3 +1,2 @@ A running Teleport Discovery Service if you plan to use [Database -Auto-Discovery](./../../enroll-resources/database-access/guides/aws-discovery.mdx). - +Auto-Discovery](../../enroll-resources/database-access/guides/aws-discovery.mdx). diff --git a/docs/pages/includes/database-access/create-user.mdx b/docs/pages/includes/database-access/create-user.mdx index 3f2cf60989bcb..9ea550c3cff8a 100644 --- a/docs/pages/includes/database-access/create-user.mdx +++ b/docs/pages/includes/database-access/create-user.mdx @@ -1,6 +1,6 @@ -To modify an existing user to provide access to the Database Service, see [Database Access Access Controls](../../enroll-resources/database-access/rbac.mdx) +To modify an existing user to provide access to the Database Service, see [Database Access Access Controls](../../enroll-resources/database-access/rbac/rbac.mdx) @@ -40,4 +40,4 @@ $ tctl users add \ For more detailed information about database access controls and how to restrict -access see [RBAC](../../enroll-resources/database-access/rbac.mdx) documentation. +access see [RBAC](../../enroll-resources/database-access/rbac/rbac.mdx) documentation. diff --git a/docs/pages/includes/database-access/db-introduction.mdx b/docs/pages/includes/database-access/db-introduction.mdx index a85170658cdda..b7c55d905a1ee 100644 --- a/docs/pages/includes/database-access/db-introduction.mdx +++ b/docs/pages/includes/database-access/db-introduction.mdx @@ -1,6 +1,6 @@ Teleport can provide secure access to {{ dbName }} via the [Teleport Database Service](../../enroll-resources/database-access/database-access.mdx). This allows for -fine-grained access control through [Teleport's RBAC](../../enroll-resources/database-access/rbac.mdx). +fine-grained access control through [Teleport's RBAC](../../enroll-resources/database-access/rbac/rbac.mdx). In this guide, you will: diff --git a/docs/pages/includes/database-access/guides-next-steps.mdx b/docs/pages/includes/database-access/guides-next-steps.mdx index cf274a8296cd6..4a14a11079497 100644 --- a/docs/pages/includes/database-access/guides-next-steps.mdx +++ b/docs/pages/includes/database-access/guides-next-steps.mdx @@ -1,5 +1,5 @@ {/* lint ignore list-item-spacing remark-lint */} -- Learn how to [restrict access](../../enroll-resources/database-access/rbac.mdx) to certain users and databases. +- Learn how to [restrict access](../../enroll-resources/database-access/rbac/rbac.mdx) to certain users and databases. {/* lint ignore list-item-spacing remark-lint */} - Learn more about [dynamic database registration](../../enroll-resources/database-access/guides/dynamic-registration.mdx). diff --git a/docs/pages/includes/edition-comparison.mdx b/docs/pages/includes/edition-comparison.mdx index 9ad0f72662085..4e2c57600b491 100644 --- a/docs/pages/includes/edition-comparison.mdx +++ b/docs/pages/includes/edition-comparison.mdx @@ -6,7 +6,7 @@ |[Hardware Key Support](../admin-guides/access-controls/guides/hardware-key-support.mdx)|✖|✔|✔| |[Moderated Sessions](../admin-guides/access-controls/guides/moderated-sessions.mdx)|✖|✔|✔| |[Role-Based Access Control](../admin-guides/access-controls/guides/role-templates.mdx)|✔|✔|✔| -|[Single Sign-On](../admin-guides/access-controls/sso.mdx)|GitHub|GitHub, Google Workspace, OIDC, SAML, Teleport|GitHub, Google Workspace, OIDC, SAML, Teleport| +|[Single Sign-On](../admin-guides/access-controls/sso/sso.mdx)|GitHub|GitHub, Google Workspace, OIDC, SAML, Teleport|GitHub, Google Workspace, OIDC, SAML, Teleport| ### Audit logging and session recording @@ -33,8 +33,7 @@ _Available as an add-on to Teleport Enterprise_ ||Community Edition|Enterprise|Cloud| |---|---|---|---| -|Access Monitoring & Response|✖|✔|✔| -|[Access Lists & Access Reviews](../admin-guides/access-controls/access-lists.mdx)|✖|✔|✔| +|[Access Lists & Access Reviews](../admin-guides/access-controls/access-lists/access-lists.mdx)|✖|✔|✔| |[Device Trust](../admin-guides/access-controls/device-trust/guide.mdx)|✖|✔|✔| |[Endpoint Management: Jamf](../admin-guides/access-controls/device-trust/jamf-integration.mdx)|✖|✔|✔| |[JIT Access Requests](../admin-guides/access-controls/guides/dual-authz.mdx)|Limited|✔|✔| diff --git a/docs/pages/includes/machine-id/configure-outputs.mdx b/docs/pages/includes/machine-id/configure-outputs.mdx index 9020f2b367b25..b002290094ddc 100644 --- a/docs/pages/includes/machine-id/configure-outputs.mdx +++ b/docs/pages/includes/machine-id/configure-outputs.mdx @@ -2,5 +2,6 @@ You have now prepared the base configuration for `tbot`. At this point, it identifies itself to the Teleport cluster and renews its own credentials but does not output any credentials for other applications to use. -Follow one of the [access guides](../../enroll-resources/machine-id/access-guides.mdx) to configure an output -that meets your access needs. \ No newline at end of file +Follow one of the [access +guides](../../enroll-resources/machine-id/access-guides/access-guides.mdx) to +configure an output that meets your access needs. diff --git a/docs/pages/includes/machine-id/plugin-prerequisites.mdx b/docs/pages/includes/machine-id/plugin-prerequisites.mdx index 88b6390ae2b68..22d43626da035 100644 --- a/docs/pages/includes/machine-id/plugin-prerequisites.mdx +++ b/docs/pages/includes/machine-id/plugin-prerequisites.mdx @@ -1,6 +1,5 @@ **Recommended:** Configure Machine ID to provide short-lived Teleport credentials to the plugin. Before following this guide, follow a Machine ID -[deployment guide](../../enroll-resources/machine-id/deployment.mdx) to run the `tbot` binary on -your infrastructure. - +[deployment guide](../../enroll-resources/machine-id/deployment/deployment.mdx) +to run the `tbot` binary on your infrastructure. diff --git a/docs/pages/index.mdx b/docs/pages/index.mdx index bdd32740d8d36..a3d9cbe09af35 100644 --- a/docs/pages/index.mdx +++ b/docs/pages/index.mdx @@ -60,7 +60,7 @@ Get started with Teleport Access: - [Set up passwordless authentication](admin-guides/access-controls/guides/passwordless.mdx) to enable users to access resources with hardware keys, including biometric credentials like Touch ID and YubiKey Bio. -- [Integrate your Single Sign-On provider](admin-guides/access-controls/sso.mdx): Allow users +- [Integrate your Single Sign-On provider](admin-guides/access-controls/sso/sso.mdx): Allow users to access infrastructure resources with IdPs like Okta. - [Use Teleport as an identity provider](admin-guides/access-controls/idps/saml-guide.mdx) to authenticate to external services. @@ -84,12 +84,12 @@ restrictions and potential security breaches. Get started with Teleport Identity: -- [Access Requests](admin-guides/access-controls/access-requests.mdx): Temporarily +- [Access Requests](admin-guides/access-controls/access-requests/access-requests.mdx): Temporarily provision minimal privileges to complete a task. -- [Access Lists](admin-guides/access-controls/access-lists.mdx): Regularly audit and +- [Access Lists](admin-guides/access-controls/access-lists/access-lists.mdx): Regularly audit and control membership to specific roles and traits, which then tie easily back into Teleport's existing RBAC system. -- [Device Trust](admin-guides/access-controls/device-trust.mdx): Require an up-to-date, +- [Device Trust](admin-guides/access-controls/device-trust/device-trust.mdx): Require an up-to-date, registered device for each authentication by giving every device a cryptographic identity. - [Session & Identity Locks](admin-guides/access-controls/guides/locking.mdx): Lock diff --git a/docs/pages/installation.mdx b/docs/pages/installation.mdx index daec8197f9595..bcc9ce212b229 100644 --- a/docs/pages/installation.mdx +++ b/docs/pages/installation.mdx @@ -20,7 +20,7 @@ version as the cluster they are connecting to. Teleport servers are compatible with clients that are on the same major version or one major version older. Teleport servers do not support clients that are on a newer major version. -See our [Upgrading](./upgrading.mdx) guide for more information. +See our [Upgrading](upgrading/upgrading.mdx) guide for more information. ## Operating system support @@ -467,7 +467,7 @@ chart. and `tctl` you run on your local machine are compatible with the versions you run on your infrastructure. Homebrew usually ships the latest release of Teleport, which may be incompatible with older versions. See our - [compatibility policy](upgrading.mdx) for details. + [compatibility policy](upgrading/upgrading.mdx) for details. To verify versions, log in to your cluster and compare the output of `tctl status` against `tsh version` and `tctl version`. diff --git a/docs/pages/reference/access-controls/access-lists.mdx b/docs/pages/reference/access-controls/access-lists.mdx index 560505b10501e..e36b2c143bf3e 100644 --- a/docs/pages/reference/access-controls/access-lists.mdx +++ b/docs/pages/reference/access-controls/access-lists.mdx @@ -139,4 +139,4 @@ above) and run `tctl create `. Access Lists can be updated by using `t `tctl` also supports a subset of Access List focused commands under the `tctl acl` subcommand. Through these you can list Access Lists, get information about a particular Access Lists, and manage Access List users. To see more details, run `tctl acl --help`. More detail can be seen in the -[CLI Reference](../../reference/cli.mdx). +[CLI Reference](../cli/cli.mdx). diff --git a/docs/pages/reference/access-controls/roles.mdx b/docs/pages/reference/access-controls/roles.mdx index 48737637e5552..c0b2b6ee11f51 100644 --- a/docs/pages/reference/access-controls/roles.mdx +++ b/docs/pages/reference/access-controls/roles.mdx @@ -22,7 +22,7 @@ resources: - [Custom API clients](../../admin-guides/api/rbac.mdx) To read more about managing dynamic resources, see the [Dynamic -Resources](../../admin-guides/infrastructure-as-code.mdx) guide. +Resources](../../admin-guides/infrastructure-as-code/infrastructure-as-code.mdx) guide. You can view all roles in your cluster on your local workstation by running the following commands: @@ -70,7 +70,7 @@ user: | `pin_source_ip` | Enable source IP pinning for SSH certificates. | Logical "OR" i.e. evaluates to "yes" if at least one role requires session termination | | `cert_extensions` | Specifies extensions to be included in SSH certificates | | | `create_host_user_mode` | Allow users to be automatically created on a host | Logical "AND" i.e. if all roles matching a server specify host user creation (`off`, `drop`, `keep`), it will evaluate to the option specified by all of the roles. If some roles specify both `drop` or `keep` it will evaluate to `keep`| -| `create_db_user_mode` | Allow [database user auto provisioning](../../enroll-resources/database-access/auto-user-provisioning.mdx). Options: `off` (disable database user auto-provisioning), `keep` (disables the user at session end, removing the roles and locking it), and `best_effort_drop` (try to drop the user at session end, if it doesn't succeed, fallback to disabling it). | Logical "OR" i.e. if any role allows database user auto-provisioning, it's allowed | +| `create_db_user_mode` | Allow database user auto provisioning. Options: `off` (disable database user auto-provisioning), `keep` (disables the user at session end, removing the roles and locking it), and `best_effort_drop` (try to drop the user at session end, if it doesn't succeed, fallback to disabling it). | Logical "OR" i.e. if any role allows database user auto-provisioning, it's allowed | ## Preset roles @@ -438,7 +438,7 @@ Labels for resources enrolled with Teleport: |---|---| |`app_labels`|[Applications](../../enroll-resources/application-access/controls.mdx)| |`cluster_labels`|[Trusted Clusters](../../admin-guides/management/admin/trustedclusters.mdx)| -|`db_labels`|[Databases](../../enroll-resources/database-access/rbac.mdx)| +|`db_labels`|[Databases](../../enroll-resources/database-access/rbac/rbac.mdx)| |`db_service_labels`|[Database Service](../../enroll-resources/database-access/database-access.mdx) instances| |`kubernetes_labels`|[Kubernetes clusters](../../enroll-resources/kubernetes-access/controls.mdx)| |`node_labels`|[SSH Servers](../../enroll-resources/server-access/server-access.mdx)| diff --git a/docs/pages/reference/agent-services/database-access-reference/cli.mdx b/docs/pages/reference/agent-services/database-access-reference/cli.mdx index 94c68b36c6246..0f8781b0b1cc3 100644 --- a/docs/pages/reference/agent-services/database-access-reference/cli.mdx +++ b/docs/pages/reference/agent-services/database-access-reference/cli.mdx @@ -282,7 +282,7 @@ Lists available databases and their connection information. $ tsh db ls ``` -Displays only the databases a user has access to (see [RBAC](../../../enroll-resources/database-access/rbac.mdx)). +Displays only the databases a user has access to (see [RBAC](../../../enroll-resources/database-access/rbac/rbac.mdx)). ## tsh db login diff --git a/docs/pages/reference/architecture/agent-update-management.mdx b/docs/pages/reference/architecture/agent-update-management.mdx index 8c8c3ee1c7508..5234b43235811 100644 --- a/docs/pages/reference/architecture/agent-update-management.mdx +++ b/docs/pages/reference/architecture/agent-update-management.mdx @@ -56,7 +56,7 @@ The agent version is subject to the following constraints: The best practice is to always align the agent version with the Proxy and Auth ones. To upgrade Auth and Proxy, follow [the Teleport Cluster upgrade guide -](../../upgrading.mdx). +](../../upgrading/upgrading.mdx). For this reason, all updaters must subscribe to a release channel targeting versions that are compatible with their Teleport cluster. Teleport Cloud users @@ -90,6 +90,5 @@ ensure every agent is healthy and running the correct version. Self-hosted users must first [set up self-hosted automatic agent upgrades ](../../upgrading/self-hosted-automatic-agent-updates.mdx). -After that, you can set enroll agents in automatic upgrades as part of the -[upgrading procedure](../../upgrading.mdx). - +After that, you can set enroll agents in automatic updates as part of the +[upgrading procedure](../../upgrading/upgrading.mdx). diff --git a/docs/pages/reference/architecture/api-architecture.mdx b/docs/pages/reference/architecture/api-architecture.mdx index 7ebb9bba6541c..c02c311a7d51e 100644 --- a/docs/pages/reference/architecture/api-architecture.mdx +++ b/docs/pages/reference/architecture/api-architecture.mdx @@ -53,7 +53,7 @@ The Teleport Go client requires credentials in order to authenticate with a Teleport cluster. Credentials are created by using Credential loaders, which gather certificates -and data generated by [Teleport CLIs](../cli.mdx). +and data generated by [Teleport CLIs](../cli/cli.mdx). Since there are several Credential loaders to choose from with distinct benefits, here's a quick breakdown: diff --git a/docs/pages/reference/architecture/architecture.mdx b/docs/pages/reference/architecture/architecture.mdx index 965b65ac25524..ebbae3df3961e 100644 --- a/docs/pages/reference/architecture/architecture.mdx +++ b/docs/pages/reference/architecture/architecture.mdx @@ -83,7 +83,7 @@ deny access to a resource. Agents must establish trust with the Teleport Auth Service when first joining a cluster, and there is are [variety of -methods](../../enroll-resources/agents/join-services-to-your-cluster.mdx) that +methods](../../enroll-resources/agents/join-services-to-your-cluster/join-services-to-your-cluster.mdx) that Agents use for this. Read about the architecture of Teleport Agent features: @@ -109,7 +109,7 @@ Instances of the `tbot` binary communicate with the Teleport Auth Service to continuously refresh credentials. As with Agents, administrators must deploy `tbot` instances on their own infrastructure, including on CI/CD platforms such as GitHub Actions, and [join -them](../../enroll-resources/machine-id/deployment.mdx) to a cluster. +them](../../enroll-resources/machine-id/deployment/deployment.mdx) to a cluster. Read more about [Machine ID Architecture](machine-id-architecture.mdx). diff --git a/docs/pages/reference/architecture/authorization.mdx b/docs/pages/reference/architecture/authorization.mdx index 9f11b2dcf969a..cf2a96a1f359e 100644 --- a/docs/pages/reference/architecture/authorization.mdx +++ b/docs/pages/reference/architecture/authorization.mdx @@ -52,7 +52,7 @@ that this cluster trusts. In this case, Teleport activates [trusted cluster mapp Local interactive users have a record in Teleport's backend with credentials. A cluster administrator have to create account entries for every Teleport user with -[`tctl users add`](../cli.mdx) or API call. +[`tctl users add`](../cli/cli.mdx) or API call. Every local Teleport User must be associated with a list of one or more roles. This list is called "role mappings". @@ -394,7 +394,7 @@ spec: - [Access Control Reference](../access-controls/roles.mdx). - [Teleport Predicate Language](../predicate-language.mdx). -- [Access Requests Guides](../../admin-guides/access-controls/access-requests.mdx) +- [Access Requests Guides](../../admin-guides/access-controls/access-requests/access-requests.mdx) - [Architecture Overview](../../core-concepts.mdx) - [Teleport Auth](authentication.mdx) - [Teleport Nodes](nodes.mdx) diff --git a/docs/pages/reference/architecture/nodes.mdx b/docs/pages/reference/architecture/nodes.mdx index 1ba38ef624de6..3da39b4d11028 100644 --- a/docs/pages/reference/architecture/nodes.mdx +++ b/docs/pages/reference/architecture/nodes.mdx @@ -17,7 +17,7 @@ Here is why we recommend Teleport Node service instead of OpenSSH: Just like with OpenSSH, the `node` service provides SSH access to every node with any clients supporting client SSH certificates: -- [OpenSSH: `ssh`](../../enroll-resources/server-access/guides/openssh.mdx) +- [OpenSSH: `ssh`](../../enroll-resources/server-access/guides/openssh/openssh.mdx) - [Teleport CLI client: `tsh ssh`](../cli/tsh.mdx) - [Teleport Proxy UI](./proxy.mdx) accessed via a web browser. - Ansible and other SSH compatible clients. diff --git a/docs/pages/reference/architecture/tls-routing.mdx b/docs/pages/reference/architecture/tls-routing.mdx index 70d6cb24783ec..cb2410ece8e13 100644 --- a/docs/pages/reference/architecture/tls-routing.mdx +++ b/docs/pages/reference/architecture/tls-routing.mdx @@ -77,7 +77,7 @@ which can be used as a `ProxyCommand`. Similarly to `tsh ssh`, `tsh proxy ssh` establishes a TLS tunnel to Teleport proxy with `teleport-proxy-ssh` ALPN protocol, which `ssh` then connects over. -See the [OpenSSH client](../../enroll-resources/server-access/guides/openssh.mdx) guide for details on +See the [OpenSSH client](../../index.mdx) guide for details on how it's configured. ## Reverse tunnels diff --git a/docs/pages/reference/cli.mdx b/docs/pages/reference/cli/cli.mdx similarity index 79% rename from docs/pages/reference/cli.mdx rename to docs/pages/reference/cli/cli.mdx index 4a6960a7bdfef..68093e7bc6b9b 100644 --- a/docs/pages/reference/cli.mdx +++ b/docs/pages/reference/cli/cli.mdx @@ -6,10 +6,10 @@ description: Detailed guide and reference documentation for Teleport's command l Teleport is made up of four CLI tools. -- [teleport](./cli/teleport.mdx): Supports the Teleport Access Platform by starting and configuring various Teleport services. -- [tsh](./cli/tsh.mdx): Allows end users to authenticate to Teleport and access resources in a cluster. -- [tctl](./cli/tctl.mdx): Used to configure the Teleport Auth Service. -- [tbot](./cli/tbot.mdx): Supports Machine ID, which provides short lived credentials to service accounts (e.g, a CI/CD server). +- [teleport](teleport.mdx): Supports the Teleport Access Platform by starting and configuring various Teleport services. +- [tsh](tsh.mdx): Allows end users to authenticate to Teleport and access resources in a cluster. +- [tctl](tctl.mdx): Used to configure the Teleport Auth Service. +- [tbot](tbot.mdx): Supports Machine ID, which provides short lived credentials to service accounts (e.g, a CI/CD server). (!docs/pages/includes/permission-warning.mdx!) @@ -52,7 +52,7 @@ desktops, and Kubernetes clusters using the `--search` and `--query` flags. The `--search` flag performs a simple fuzzy search on resource fields. For example, `--search=mac` searches for resources containing `mac`. -The `--query` flag allows you to perform more sophisticated searches using a [predicate language](predicate-language.mdx#resource-filtering). +The `--query` flag allows you to perform more sophisticated searches using a [predicate language](../predicate-language.mdx). In both cases, you can further refine the results by appending a list of comma-separated labels to the command. For example: diff --git a/docs/pages/reference/cloud-faq.mdx b/docs/pages/reference/cloud-faq.mdx index 0eb5fa16c5c0d..8922728f96403 100644 --- a/docs/pages/reference/cloud-faq.mdx +++ b/docs/pages/reference/cloud-faq.mdx @@ -77,7 +77,7 @@ S3, are established using encryption provided by AWS, both at rest and in transi You can connect servers, Kubernetes clusters, databases, desktops, and applications using [reverse -tunnels](../enroll-resources/agents/join-services-to-your-cluster.mdx). +tunnels](../enroll-resources/agents/join-services-to-your-cluster/join-services-to-your-cluster.mdx). There is no need to open any ports on your infrastructure for inbound traffic. diff --git a/docs/pages/reference/helm-reference.mdx b/docs/pages/reference/helm-reference/helm-reference.mdx similarity index 60% rename from docs/pages/reference/helm-reference.mdx rename to docs/pages/reference/helm-reference/helm-reference.mdx index 1aece251ec4e6..9f4821b8c6b41 100644 --- a/docs/pages/reference/helm-reference.mdx +++ b/docs/pages/reference/helm-reference/helm-reference.mdx @@ -5,26 +5,26 @@ description: Comprehensive lists of configuration values in Teleport's Helm char layout: tocless-doc --- -- [teleport-cluster](./helm-reference/teleport-cluster.mdx): Deploy the +- [teleport-cluster](teleport-cluster.mdx): Deploy the `teleport` daemon on Kubernetes with preset configurations for the Auth and Proxy Services and support for any Teleport service configuration. -- [teleport-kube-agent](./helm-reference/teleport-kube-agent.mdx): Deploy the +- [teleport-kube-agent](teleport-kube-agent.mdx): Deploy the Teleport Kubernetes Service, Application Service, or Database Service on Kubernetes. -- [teleport-access-graph](./helm-reference/teleport-access-graph.mdx): Deploy the +- [teleport-access-graph](teleport-access-graph.mdx): Deploy the Teleport Access Graph service. -- [teleport-plugin-event-handler](./helm-reference/teleport-plugin-event-handler.mdx): +- [teleport-plugin-event-handler](teleport-plugin-event-handler.mdx): Deploy the Teleport Event Handler plugin which sends events and session logs to Fluentd. -- [teleport-plugin-jira](./helm-reference/teleport-plugin-jira.mdx): Deploy +- [teleport-plugin-jira](teleport-plugin-jira.mdx): Deploy the Teleport Jira Access Request Plugin, which allows approving of denying Access Requests via a Jira Project. -- [teleport-plugin-pagerduty](./helm-reference/teleport-plugin-pagerduty.mdx): +- [teleport-plugin-pagerduty](teleport-plugin-pagerduty.mdx): Deploy the Teleport PagerDuty Plugin, which allows sending PagerDuty alerts when Access Requests are made. -- [teleport-plugin-mattermost](./helm-reference/teleport-plugin-mattermost.mdx): +- [teleport-plugin-mattermost](teleport-plugin-mattermost.mdx): Deploy the Teleport Mattermost Access Request Plugin, which allows approving or denying Access Requests via Mattermost. -- [teleport-plugin-slack](./helm-reference/teleport-plugin-slack.mdx): Deploy +- [teleport-plugin-slack](teleport-plugin-slack.mdx): Deploy the Teleport Slack Plugin, which allows notifying Slack users and channels when Access Requests are made. diff --git a/docs/pages/reference/helm-reference/teleport-cluster.mdx b/docs/pages/reference/helm-reference/teleport-cluster.mdx index 56a3b5d6081ab..962e64582c9b1 100644 --- a/docs/pages/reference/helm-reference/teleport-cluster.mdx +++ b/docs/pages/reference/helm-reference/teleport-cluster.mdx @@ -256,7 +256,7 @@ Possible values are `local` and `github` for Teleport Community Edition, plus `o | `string` | `""` | No | `auth_service.authentication.connector_name` | `authentication.connectorName` sets the default authentication connector. -[The SSO documentation](../../admin-guides/access-controls/sso.mdx) explains how to create +[The SSO documentation](../../admin-guides/access-controls/sso/sso.mdx) explains how to create authentication connectors for common identity providers. In addition to SSO connector names, the following built-in connectors are supported: diff --git a/docs/pages/reference/helm-reference/teleport-kube-agent.mdx b/docs/pages/reference/helm-reference/teleport-kube-agent.mdx index 9475c6f76c4da..b78c8124c14f1 100644 --- a/docs/pages/reference/helm-reference/teleport-kube-agent.mdx +++ b/docs/pages/reference/helm-reference/teleport-kube-agent.mdx @@ -20,11 +20,11 @@ This reference details available values for the `teleport-kube-agent` chart. The `teleport-kube-agent` chart can run any or all of three Teleport services: -| Teleport service | Name for `roles` and `tctl tokens add` | Purpose | -|--------------------------------------------------------------|----------------------------------------|----------------------------------------------------------------------------------------| -| [`kubernetes_service`](../../enroll-resources/kubernetes-access/introduction.mdx) | `kube` | Uses Teleport to handle authentication
with and proxy access to a Kubernetes cluster | -| [`application_service`](../../enroll-resources/application-access/guides.mdx) | `app` | Uses Teleport to handle authentication
with and proxy access to web-based applications | -| [`database_service`](../../enroll-resources/database-access/guides.mdx) | `db` | Uses Teleport to handle authentication
with and proxy access to databases | +| Teleport service | Name for `roles` and `tctl tokens add` | Purpose | +|---------------------------------------------------------------------------|----------------------------------------|----------------------------------------------------------------------------------------------| +| [`kubernetes_service`](../../enroll-resources/kubernetes-access/introduction.mdx) | `kube` | Uses Teleport to handle authentication
with and proxy access to a Kubernetes cluster | +| [`application_service`](../../enroll-resources/application-access/guides/guides.mdx) | `app` | Uses Teleport to handle authentication
with and proxy access to web-based applications | +| [`database_service`](../../enroll-resources/database-access/guides/guides.mdx) | `db` | Uses Teleport to handle authentication
with and proxy access to databases | ### Legacy releases diff --git a/docs/pages/reference/monitoring/audit.mdx b/docs/pages/reference/monitoring/audit.mdx index d772f076a33ba..c547c3c91e717 100644 --- a/docs/pages/reference/monitoring/audit.mdx +++ b/docs/pages/reference/monitoring/audit.mdx @@ -127,7 +127,7 @@ Below are some possible types of audit events. This list is not comprehensive. We recommend exporting audit events to a platform that automatically parses event payloads so you can group and filter them by their `event` key and discover trends. To set up audit event exporting, -read [Exporting Teleport Audit Events](../../admin-guides/management/export-audit-events.mdx). +read [Exporting Teleport Audit Events](../../admin-guides/management/export-audit-events/export-audit-events.mdx). diff --git a/docs/pages/reference/predicate-language.mdx b/docs/pages/reference/predicate-language.mdx index 2da0ce3c7e110..921436f125519 100644 --- a/docs/pages/reference/predicate-language.mdx +++ b/docs/pages/reference/predicate-language.mdx @@ -72,7 +72,7 @@ The language also supports the following functions: | `split(labels["foo"], ",")` | converts a delimited string into a list | | `contains(split(labels["foo"], ","), "bar")` | determines if a value exists in a list | -See some [examples](cli.mdx#filter-examples) of the different ways you can filter resources. +See some [examples](cli/cli.mdx) of the different ways you can filter resources. ## Label expressions diff --git a/docs/pages/reference/resources.mdx b/docs/pages/reference/resources.mdx index 8c9b8a47b7e99..74c8c8830dc6e 100644 --- a/docs/pages/reference/resources.mdx +++ b/docs/pages/reference/resources.mdx @@ -6,7 +6,7 @@ description: Reference documentation for Teleport resources This reference guide lists dynamic resources you can manage with Teleport. For more information on dynamic resources, see our guide to [Using Dynamic -Resources](../admin-guides/infrastructure-as-code.mdx). +Resources](../admin-guides/infrastructure-as-code/infrastructure-as-code.mdx). Examples of applying dynamic resources with `tctl`: @@ -51,11 +51,11 @@ Here's the list of resources currently exposed via [`tctl`](./cli/tctl.mdx): | - | - | | [user](#user) | A user record in the internal Teleport user DB. | | [role](#role) | A role assumed by interactive and non-interactive users. | -| connector | Authentication connectors for [Single Sign-On](../admin-guides/access-controls/sso.mdx) (SSO) for SAML, OIDC and GitHub. | -| node | A registered SSH node. The same record is displayed via `tctl nodes ls` | +| connector | Authentication connectors for [Single Sign-On](../admin-guides/access-controls/sso/sso.mdx) (SSO) for SAML, OIDC and GitHub. | +| node | A registered SSH node. The same record is displayed via `tctl nodes ls`. | | windows_desktop | A registered Windows desktop. | | cluster | A trusted cluster. See [here](../admin-guides/management/admin/trustedclusters.mdx) for more details on connecting clusters together. | -| [login_rule](#login-rules) | A Login Rule, see the [Login Rules guide](../admin-guides/access-controls/login-rules.mdx) for more info. | +| [login_rule](#login-rules) | A Login Rule, see the [Login Rules guide](../admin-guides/access-controls/login-rules/login-rules.mdx) for more info. | | [device](#device) | A Teleport Trusted Device, see the [Device Trust guide](../admin-guides/access-controls/device-trust/guide.mdx) for more info. | | [ui_config](#ui-config) | Configuration for the Web UI served by the Proxy Service | | [cluster_auth_preference](#cluster-auth-preferences) | Configuration for the cluster's auth preferences. | diff --git a/docs/pages/upgrading/overview.mdx b/docs/pages/upgrading/overview.mdx index f6fd3c03e0740..0f229ba86f285 100644 --- a/docs/pages/upgrading/overview.mdx +++ b/docs/pages/upgrading/overview.mdx @@ -69,5 +69,5 @@ upgrade from v10 to v11. ## Next steps -Return to the [Upgrading Introduction](../upgrading.mdx) for how to upgrade +Return to the [Upgrading Introduction](upgrading.mdx) for how to upgrade individual components within your Teleport cluster. diff --git a/docs/pages/upgrading/self-hosted-automatic-agent-updates.mdx b/docs/pages/upgrading/self-hosted-automatic-agent-updates.mdx index 6bf84210e0900..14746f2b98554 100644 --- a/docs/pages/upgrading/self-hosted-automatic-agent-updates.mdx +++ b/docs/pages/upgrading/self-hosted-automatic-agent-updates.mdx @@ -10,7 +10,7 @@ clusters. Teleport agents run an **upgrader** that queries a **version server** to determine whether they are out of date. This guide describes how to set up your infrastructure to support automatic upgrades. If you are a Teleport Cloud user -or run a version server already, return to the [Upgrading](../upgrading.mdx) +or run a version server already, return to the [Upgrading](upgrading.mdx) menu for the appropriate next steps to upgrade Teleport. The [Automatic Update Architecture](../reference/architecture/agent-update-management.mdx) diff --git a/docs/pages/upgrading.mdx b/docs/pages/upgrading/upgrading.mdx similarity index 77% rename from docs/pages/upgrading.mdx rename to docs/pages/upgrading/upgrading.mdx index 45f8cdc811d23..aba4f806e069f 100644 --- a/docs/pages/upgrading.mdx +++ b/docs/pages/upgrading/upgrading.mdx @@ -13,7 +13,7 @@ Since Teleport is a distributed system with a number of services that run on potentially many hosts, you should take care when upgrading the cluster to ensure that all components remain compatible. -The [Upgrading Compatibility Overview](./upgrading/overview.mdx) explains how to +The [Upgrading Compatibility Overview](overview.mdx) explains how to upgrade components in your Teleport cluster to ensure that they communicate as expected. @@ -26,13 +26,13 @@ services: Teleport Cloud: -- [Linux Servers](./upgrading/cloud-linux.mdx) -- [Kubernetes](./upgrading/cloud-kubernetes.mdx) +- [Linux Servers](cloud-linux.mdx) +- [Kubernetes](cloud-kubernetes.mdx) Self-hosted deployments: -- [Linux Servers](./upgrading/self-hosted-linux.mdx) -- [Kubernetes](./upgrading/self-hosted-kubernetes.mdx) +- [Linux Servers](self-hosted-linux.mdx) +- [Kubernetes](self-hosted-kubernetes.mdx) ## Automatic agent upgrades @@ -43,6 +43,6 @@ and need to install a new version of Teleport. On Teleport Cloud, the version server is managed for you. If you are running Teleport Enterprise, read the [Self-Hosted Automatic Agent -Updates](./upgrading/self-hosted-automatic-agent-updates.mdx) guide to set up +Updates](self-hosted-automatic-agent-updates.mdx) guide to set up the version server yourself. From 0175288a237ce1d1deaa9505a0eae6c0605b1fe8 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Wed, 30 Oct 2024 16:12:48 -0400 Subject: [PATCH 39/53] [16.4.3] bump cloud docs (#48111) --- docs/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.json b/docs/config.json index 2a59b881febb5..e1d1a0fa1554d 100644 --- a/docs/config.json +++ b/docs/config.json @@ -162,7 +162,7 @@ "aws_secret_access_key": "zyxw9876-this-is-an-example" }, "cloud": { - "version": "16.4.2", + "version": "16.4.3", "major_version": "16", "sla": { "monthly_percentage": "99.9%", From 039d7a307f61f3ed57d846a5c4c71c1577deff71 Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Wed, 30 Oct 2024 15:28:54 -0600 Subject: [PATCH 40/53] [v14] Fix the RDP licensing flow (#47544) * Fix the RDP licensing flow on v14 Licenses are stored in-memory, so if the agent restarts it will be forced to go through the "new license" flow for the first session. * Store RDP licenses in a LRU cache instead of a HashMap This ensures memory doesn't grow unbounded if we end up caching a large number of licenses. --- Cargo.lock | 59 ++++++++--- lib/srv/desktop/rdp/rdpclient/Cargo.toml | 8 +- lib/srv/desktop/rdp/rdpclient/client.go | 9 +- .../desktop/rdp/rdpclient/client_common.go | 6 +- lib/srv/desktop/rdp/rdpclient/src/cliprdr.rs | 1 + lib/srv/desktop/rdp/rdpclient/src/lib.rs | 97 ++++++++++++++++++- lib/srv/desktop/windows_server.go | 1 + 7 files changed, 159 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7148f803bba23..0261bb969c4ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "asn1-rs" version = "0.5.1" @@ -452,6 +458,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.1" @@ -504,6 +516,12 @@ dependencies = [ "miniz_oxide 0.6.2", ] +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "foreign-types" version = "0.5.0" @@ -566,16 +584,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "gethostname" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "getrandom" version = "0.1.16" @@ -619,6 +627,17 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "heapless" version = "0.7.16" @@ -685,7 +704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -819,6 +838,15 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.0", +] + [[package]] name = "md-5" version = "0.8.0" @@ -1218,6 +1246,7 @@ dependencies = [ "iso7816-tlv", "libc", "log", + "lru", "num-derive", "num-traits", "png", @@ -1233,12 +1262,11 @@ dependencies = [ [[package]] name = "rdp-rs" version = "0.1.0" -source = "git+https://github.com/gravitational/rdp-rs?rev=edfb5330a11d11eaf36d65e4300555368b4c6b02#edfb5330a11d11eaf36d65e4300555368b4c6b02" +source = "git+https://github.com/gravitational/rdp-rs?rev=2b0d99cc60c7b6474a1e2224a0bd6b2beca56b63#2b0d99cc60c7b6474a1e2224a0bd6b2beca56b63" dependencies = [ "boring", "bufstream", "byteorder", - "gethostname", "hmac", "indexmap", "md-5", @@ -1251,6 +1279,7 @@ dependencies = [ "ring", "rsa 0.6.1", "rustls", + "uuid", "x509-parser", "yasna", ] @@ -1706,9 +1735,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.4.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom 0.2.8", ] diff --git a/lib/srv/desktop/rdp/rdpclient/Cargo.toml b/lib/srv/desktop/rdp/rdpclient/Cargo.toml index 64d155318516d..d7f696f1be9f7 100644 --- a/lib/srv/desktop/rdp/rdpclient/Cargo.toml +++ b/lib/srv/desktop/rdp/rdpclient/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "rdp-client" version = "0.1.0" -authors = ["Andrew Lytvynov ", "Zac Bergquist "] +authors = [ + "Andrew Lytvynov ", + "Zac Bergquist ", +] edition = "2018" [lib] @@ -20,10 +23,11 @@ num-traits = "0.2.16" rand = { version = "0.8.5", features = ["getrandom"] } rand_chacha = "0.3.1" rsa = "0.9.2" -rdp-rs = { git = "https://github.com/gravitational/rdp-rs", rev = "edfb5330a11d11eaf36d65e4300555368b4c6b02" } +rdp-rs = { git = "https://github.com/gravitational/rdp-rs", rev = "2b0d99cc60c7b6474a1e2224a0bd6b2beca56b63" } uuid = { version = "1.4.1", features = ["v4"] } utf16string = "0.2.0" png = "0.17.10" +lru = "0.12.5" [build-dependencies] cbindgen = "0.25.0" diff --git a/lib/srv/desktop/rdp/rdpclient/client.go b/lib/srv/desktop/rdp/rdpclient/client.go index 8e4fc0b551af6..7c0bb25556c23 100644 --- a/lib/srv/desktop/rdp/rdpclient/client.go +++ b/lib/srv/desktop/rdp/rdpclient/client.go @@ -237,12 +237,14 @@ func (c *Client) connect(ctx context.Context) error { return trace.Wrap(err) } - // Addr and username strings only need to be valid for the duration of + // These strings only need to be valid for the duration of // C.connect_rdp. They are copied on the Rust side and can be freed here. addr := C.CString(c.cfg.Addr) defer C.free(unsafe.Pointer(addr)) username := C.CString(c.username) defer C.free(unsafe.Pointer(username)) + hostID := C.CString(c.cfg.HostID) + defer C.free(unsafe.Pointer(hostID)) cert_der, err := utils.UnsafeSliceData(userCertDER) if err != nil { @@ -261,8 +263,9 @@ func (c *Client) connect(ctx context.Context) error { res := C.connect_rdp( C.uintptr_t(c.handle), C.CGOConnectParams{ - go_addr: addr, - go_username: username, + go_addr: addr, + go_username: username, + go_client_id: hostID, // cert length and bytes. cert_der_len: C.uint32_t(len(userCertDER)), cert_der: (*C.uint8_t)(cert_der), diff --git a/lib/srv/desktop/rdp/rdpclient/client_common.go b/lib/srv/desktop/rdp/rdpclient/client_common.go index 5763598aa81d4..582d5e798faa4 100644 --- a/lib/srv/desktop/rdp/rdpclient/client_common.go +++ b/lib/srv/desktop/rdp/rdpclient/client_common.go @@ -32,10 +32,14 @@ import ( type Config struct { // Addr is the network address of the RDP server, in the form host:port. Addr string - // UserCertGenerator generates user certificates for RDP authentication. + + // GenerateUserCert generates user certificates for RDP authentication. GenerateUserCert GenerateUserCertFn CertTTL time.Duration + // HostID uniquely identifies the Teleport agent running the RDP client. + HostID string + // AuthorizeFn is called to authorize a user connecting to a Windows desktop. AuthorizeFn func(login string) error diff --git a/lib/srv/desktop/rdp/rdpclient/src/cliprdr.rs b/lib/srv/desktop/rdp/rdpclient/src/cliprdr.rs index 7bbb592d74834..2e5990adcc43c 100644 --- a/lib/srv/desktop/rdp/rdpclient/src/cliprdr.rs +++ b/lib/srv/desktop/rdp/rdpclient/src/cliprdr.rs @@ -749,6 +749,7 @@ enum ClipboardFormat { /// Sent as a reply to the format list PDU - used to indicate whether /// the format list PDU was processed succesfully. #[derive(Debug)] +#[allow(dead_code)] struct FormatListResponsePDU { // empty, the only information needed is the flags in the header } diff --git a/lib/srv/desktop/rdp/rdpclient/src/lib.rs b/lib/srv/desktop/rdp/rdpclient/src/lib.rs index d3e30365266db..7f8e0052c3deb 100644 --- a/lib/srv/desktop/rdp/rdpclient/src/lib.rs +++ b/lib/srv/desktop/rdp/rdpclient/src/lib.rs @@ -50,6 +50,7 @@ extern crate log; extern crate num_derive; use errors::try_error; +use lru::LruCache; use rand::Rng; use rand::SeedableRng; use rdp::core::event::*; @@ -60,6 +61,7 @@ use rdp::core::mcs; use rdp::core::sec; use rdp::core::tpkt; use rdp::core::x224; +use rdp::core::LicenseStore; use rdp::model::error::{Error as RdpError, RdpError as RdpProtocolError, RdpErrorKind, RdpResult}; use rdp::model::link::{Link, Stream}; use rdpdr::path::UnixPath; @@ -72,8 +74,10 @@ use std::io::ErrorKind; use std::io::{Cursor, Read, Write}; use std::net; use std::net::{TcpStream, ToSocketAddrs}; +use std::num::NonZeroUsize; use std::os::raw::c_char; use std::os::unix::io::AsRawFd; +use std::sync::OnceLock; use std::sync::{Arc, Mutex}; use std::{mem, ptr, slice, time}; @@ -182,6 +186,7 @@ pub unsafe extern "C" fn connect_rdp(go_ref: usize, params: CGOConnectParams) -> // Convert from C to Rust types. let addr = from_c_string(params.go_addr); let username = from_c_string(params.go_username); + let client_id = from_c_string(params.go_client_id); let cert_der = from_go_array(params.cert_der, params.cert_der_len); let key_der = from_go_array(params.key_der, params.key_der_len); @@ -190,6 +195,7 @@ pub unsafe extern "C" fn connect_rdp(go_ref: usize, params: CGOConnectParams) -> ConnectParams { addr, username, + client_id, cert_der, key_der, screen_width: params.screen_width, @@ -230,6 +236,7 @@ const RDPSND_CHANNEL_NAME: &str = "rdpsnd"; pub struct CGOConnectParams { go_addr: *const c_char, go_username: *const c_char, + go_client_id: *const c_char, cert_der_len: u32, cert_der: *mut u8, key_der_len: u32, @@ -244,6 +251,7 @@ pub struct CGOConnectParams { struct ConnectParams { addr: String, username: String, + client_id: String, cert_der: Vec, key_der: Vec, screen_width: u16, @@ -253,6 +261,8 @@ struct ConnectParams { show_desktop_wallpaper: bool, } +static LICENSE_STORE: OnceLock = OnceLock::new(); + fn connect_rdp_inner(go_ref: usize, params: ConnectParams) -> Result { // Connect and authenticate. let addr = params @@ -288,7 +298,7 @@ fn connect_rdp_inner(go_ref: usize, params: ConnectParams) -> Result Result Result>>, +} + +impl LruLicenseStore { + pub fn new() -> Self { + Self { + licenses: Mutex::new(LruCache::new( + NonZeroUsize::new(MAX_SAVED_LICENSES).unwrap(), + )), + } + } +} + +impl LicenseStore for &LruLicenseStore { + fn write_license( + &mut self, + major: u16, + minor: u16, + company: &str, + issuer: &str, + product_id: &str, + license: &[u8], + ) { + info!("Saving {major}.{minor} license from {issuer}"); + self.licenses.lock().unwrap().put( + LicenseStoreKey { + major, + minor, + company: company.to_owned(), + issuer: issuer.to_owned(), + product_id: product_id.to_owned(), + }, + license.to_vec(), + ); + } + + fn read_license( + &self, + major: u16, + minor: u16, + company: &str, + issuer: &str, + product_id: &str, + ) -> Option> { + let license = self + .licenses + .lock() + .unwrap() + .get(&LicenseStoreKey { + major, + minor, + company: company.to_owned(), + issuer: issuer.to_owned(), + product_id: product_id.to_owned(), + }) + .cloned(); + + if license.is_some() { + info!("Found existing {major}.{minor} license from {issuer}"); + } else { + info!("No existing {major}.{minor} license from {issuer}"); + } + + license + } +} + +#[derive(PartialEq, Eq, Hash)] +struct LicenseStoreKey { + major: u16, + minor: u16, + company: String, + issuer: String, + product_id: String, +} + // These functions are defined on the Go side. Look for functions with '//export funcname' // comments. extern "C" { diff --git a/lib/srv/desktop/windows_server.go b/lib/srv/desktop/windows_server.go index 07d2cc7ebfd8b..803df2a81a644 100644 --- a/lib/srv/desktop/windows_server.go +++ b/lib/srv/desktop/windows_server.go @@ -877,6 +877,7 @@ func (s *WindowsService) connectRDP(ctx context.Context, log logrus.FieldLogger, }, CertTTL: windows.CertTTL, Addr: addr.String(), + HostID: s.cfg.Heartbeat.HostUUID, Conn: tdpConn, AuthorizeFn: authorize, AllowClipboard: authCtx.Checker.DesktopClipboard(), From 53b8ba85edff1319bf82e2658605c48a910ea5f6 Mon Sep 17 00:00:00 2001 From: Nic Klaassen Date: Wed, 30 Oct 2024 14:56:12 -0700 Subject: [PATCH 41/53] [v14] fix EAS bootstrap script (#48179) Backport #37068 to branch/v14 --- lib/integrations/externalauditstorage/bootstrap.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/integrations/externalauditstorage/bootstrap.go b/lib/integrations/externalauditstorage/bootstrap.go index b32dad2eebae5..3806f601dae1c 100644 --- a/lib/integrations/externalauditstorage/bootstrap.go +++ b/lib/integrations/externalauditstorage/bootstrap.go @@ -184,8 +184,7 @@ func createTransientBucket(ctx context.Context, clt BootstrapS3Client, bucketNam Status: s3types.ExpirationStatusEnabled, ID: aws.String("ExpireNonCurrentVersionsAndDeleteMarkers"), NoncurrentVersionExpiration: &s3types.NoncurrentVersionExpiration{ - NewerNoncurrentVersions: aws.Int32(0), - NoncurrentDays: aws.Int32(1), + NoncurrentDays: aws.Int32(1), }, AbortIncompleteMultipartUpload: &s3types.AbortIncompleteMultipartUpload{ DaysAfterInitiation: aws.Int32(7), From 24f3e897ded32c5f08f875d0f0bbefbaa353b661 Mon Sep 17 00:00:00 2001 From: Cam Hutchison Date: Thu, 31 Oct 2024 09:44:21 +1100 Subject: [PATCH 42/53] Release 14.3.33 (#48183) * Release 14.3.33 * docs: Add "keepalive" to spelling list "keepalives" was already there, we just need the singular for a changelog entry. --- CHANGELOG.md | 27 ++++++++ Makefile | 2 +- api/version.go | 2 +- .../macos/tsh/tsh.app/Contents/Info.plist | 4 +- .../macos/tshdev/tsh.app/Contents/Info.plist | 4 +- docs/cspell.json | 1 + examples/chart/teleport-cluster/Chart.yaml | 2 +- .../charts/teleport-operator/Chart.yaml | 2 +- .../auth_clusterrole_test.yaml.snap | 4 +- .../__snapshot__/auth_config_test.yaml.snap | 4 +- .../auth_deployment_test.yaml.snap | 10 +-- .../__snapshot__/proxy_config_test.yaml.snap | 4 +- .../proxy_deployment_test.yaml.snap | 32 +++++----- examples/chart/teleport-kube-agent/Chart.yaml | 2 +- .../__snapshot__/deployment_test.yaml.snap | 58 ++++++++--------- .../tests/__snapshot__/job_test.yaml.snap | 10 +-- .../__snapshot__/statefulset_test.yaml.snap | 64 +++++++++---------- .../updater_deployment_test.yaml.snap | 4 +- 18 files changed, 132 insertions(+), 104 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49acd47ed4f65..cb067bbb2682a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## 14.3.33 (10/30/24) + +* Fixed a bug in the External Audit Storage bootstrap script that broke S3 bucket creation. [#48179](https://github.com/gravitational/teleport/pull/48179) +* During the Set Up Access of the Enroll New Resource flows, Okta users will be asked to change the role instead of entering the principals and getting an error afterwards. [#47959](https://github.com/gravitational/teleport/pull/47959) +* Fixed `teleport_connected_resource` metric overshooting after keepalive errors. [#47951](https://github.com/gravitational/teleport/pull/47951) +* Fixed an issue preventing connections with users whose configured home directories were inaccessible. [#47918](https://github.com/gravitational/teleport/pull/47918) +* Auto-enroll may be locally disabled using the `TELEPORT_DEVICE_AUTO_ENROLL_DISABLED=1` environment variable. [#47718](https://github.com/gravitational/teleport/pull/47718) +* Alter ServiceAccounts in the teleport-cluster Helm chart to automatically disable mounting of service account tokens on newer Kubernetes distributions, helping satisfy security linters. [#47701](https://github.com/gravitational/teleport/pull/47701) +* Avoid tsh auto-enroll escalation in machines without a TPM. [#47697](https://github.com/gravitational/teleport/pull/47697) +* Postgres database session start events now include the Postgres backend PID for the session. [#47645](https://github.com/gravitational/teleport/pull/47645) +* Fixes a bug where Let's Encrypt certificate renewal failed in AMI and HA deployments due to insufficient disk space caused by syncing audit logs. [#47623](https://github.com/gravitational/teleport/pull/47623) +* Adds support for custom SQS consumer lock name and disabling a consumer. [#47612](https://github.com/gravitational/teleport/pull/47612) +* Include host name instead of host uuid in error messages when SSH connections are prevented due to an invalid login. [#47603](https://github.com/gravitational/teleport/pull/47603) +* Allow using a custom database for Firestore backends. [#47585](https://github.com/gravitational/teleport/pull/47585) +* Extended Teleport Discovery Service to support resource discovery across all projects accessible by the service account. [#47566](https://github.com/gravitational/teleport/pull/47566) +* Fixed a bug that could allow users to list active sessions even when prohibited by RBAC. [#47562](https://github.com/gravitational/teleport/pull/47562) +* The `tctl tokens ls` command redacts secret join tokens by default. To include the token values, provide the new `--with-secrets` flag. [#47547](https://github.com/gravitational/teleport/pull/47547) +* Fixed an issue with the Microsoft license negotiation for RDP sessions. [#47544](https://github.com/gravitational/teleport/pull/47544) +* Fixed a bug where tsh logout failed to parse flags passed with spaces. [#47461](https://github.com/gravitational/teleport/pull/47461) +* Added kubeconfig context name to the output table of `tsh proxy kube` command for enhanced clarity. [#47381](https://github.com/gravitational/teleport/pull/47381) +* Improve error messaging when connections to offline agents are attempted. [#47363](https://github.com/gravitational/teleport/pull/47363) +* Teleport Connect for Linux now requires glibc 2.31 or later. [#47264](https://github.com/gravitational/teleport/pull/47264) +* Updates self-hosted db discover flow to generate 2190h TTL certs, not 12h. [#47128](https://github.com/gravitational/teleport/pull/47128) + +Enterprise: +* Device auto-enroll failures are now recorded in the audit log. + ## 14.3.32 (10/03/24) * Fixes an issue preventing access requests from displaying user friendly resource names. [#47110](https://github.com/gravitational/teleport/pull/47110) diff --git a/Makefile b/Makefile index bbfe72a349803..9f2996a8fa96e 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ # Stable releases: "1.0.0" # Pre-releases: "1.0.0-alpha.1", "1.0.0-beta.2", "1.0.0-rc.3" # Master/dev branch: "1.0.0-dev" -VERSION=14.3.32 +VERSION=14.3.33 DOCKER_IMAGE ?= teleport diff --git a/api/version.go b/api/version.go index 85c33d9532400..b22cecce071de 100644 --- a/api/version.go +++ b/api/version.go @@ -3,6 +3,6 @@ package api import "github.com/coreos/go-semver/semver" -const Version = "14.3.32" +const Version = "14.3.33" var SemVersion = semver.New(Version) diff --git a/build.assets/macos/tsh/tsh.app/Contents/Info.plist b/build.assets/macos/tsh/tsh.app/Contents/Info.plist index 6735f8f8479f7..6da038d33d6b0 100644 --- a/build.assets/macos/tsh/tsh.app/Contents/Info.plist +++ b/build.assets/macos/tsh/tsh.app/Contents/Info.plist @@ -19,13 +19,13 @@ CFBundlePackageType APPL CFBundleShortVersionString - 14.3.32 + 14.3.33 CFBundleSupportedPlatforms MacOSX CFBundleVersion - 14.3.32 + 14.3.33 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild diff --git a/build.assets/macos/tshdev/tsh.app/Contents/Info.plist b/build.assets/macos/tshdev/tsh.app/Contents/Info.plist index 9c13203eef9be..9dc8dc8ca0723 100644 --- a/build.assets/macos/tshdev/tsh.app/Contents/Info.plist +++ b/build.assets/macos/tshdev/tsh.app/Contents/Info.plist @@ -17,13 +17,13 @@ CFBundlePackageType APPL CFBundleShortVersionString - 14.3.32 + 14.3.33 CFBundleSupportedPlatforms MacOSX CFBundleVersion - 14.3.32 + 14.3.33 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild diff --git a/docs/cspell.json b/docs/cspell.json index 7d5437f292cb9..93d8b6d9825de 100644 --- a/docs/cspell.json +++ b/docs/cspell.json @@ -510,6 +510,7 @@ "jumphost", "jwks", "jwkset", + "keepalive", "keepalives", "keyfile", "keymap", diff --git a/examples/chart/teleport-cluster/Chart.yaml b/examples/chart/teleport-cluster/Chart.yaml index 2bfe5c89f2534..48f3067b105f4 100644 --- a/examples/chart/teleport-cluster/Chart.yaml +++ b/examples/chart/teleport-cluster/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "14.3.32" +.version: &version "14.3.33" name: teleport-cluster apiVersion: v2 diff --git a/examples/chart/teleport-cluster/charts/teleport-operator/Chart.yaml b/examples/chart/teleport-cluster/charts/teleport-operator/Chart.yaml index 22e452ea60dbe..24ebc3c002392 100644 --- a/examples/chart/teleport-cluster/charts/teleport-operator/Chart.yaml +++ b/examples/chart/teleport-cluster/charts/teleport-operator/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "14.3.32" +.version: &version "14.3.33" name: teleport-operator apiVersion: v2 diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/auth_clusterrole_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/auth_clusterrole_test.yaml.snap index 9d5b45504687b..729891357908e 100644 --- a/examples/chart/teleport-cluster/tests/__snapshot__/auth_clusterrole_test.yaml.snap +++ b/examples/chart/teleport-cluster/tests/__snapshot__/auth_clusterrole_test.yaml.snap @@ -8,8 +8,8 @@ adds operator permissions to ClusterRole: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-cluster - app.kubernetes.io/version: 14.3.32 - helm.sh/chart: teleport-cluster-14.3.32 + app.kubernetes.io/version: 14.3.33 + helm.sh/chart: teleport-cluster-14.3.33 teleport.dev/majorVersion: "14" name: RELEASE-NAME rules: diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/auth_config_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/auth_config_test.yaml.snap index 3acd63f6974fb..f78be8a2a897a 100644 --- a/examples/chart/teleport-cluster/tests/__snapshot__/auth_config_test.yaml.snap +++ b/examples/chart/teleport-cluster/tests/__snapshot__/auth_config_test.yaml.snap @@ -1797,8 +1797,8 @@ sets clusterDomain on Configmap: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-cluster - app.kubernetes.io/version: 14.3.32 - helm.sh/chart: teleport-cluster-14.3.32 + app.kubernetes.io/version: 14.3.33 + helm.sh/chart: teleport-cluster-14.3.33 teleport.dev/majorVersion: "14" name: RELEASE-NAME-auth namespace: NAMESPACE diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/auth_deployment_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/auth_deployment_test.yaml.snap index c610ba967a077..2a7b6e989e597 100644 --- a/examples/chart/teleport-cluster/tests/__snapshot__/auth_deployment_test.yaml.snap +++ b/examples/chart/teleport-cluster/tests/__snapshot__/auth_deployment_test.yaml.snap @@ -1,6 +1,6 @@ should add an operator side-car when operator is enabled: 1: | - image: public.ecr.aws/gravitational/teleport-operator:14.3.32 + image: public.ecr.aws/gravitational/teleport-operator:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -41,7 +41,7 @@ should add an operator side-car when operator is enabled: - args: - --diag-addr=0.0.0.0:3000 - --apply-on-startup=/etc/teleport/apply-on-startup.yaml - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -174,7 +174,7 @@ should set nodeSelector when set in values: - args: - --diag-addr=0.0.0.0:3000 - --apply-on-startup=/etc/teleport/apply-on-startup.yaml - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -271,7 +271,7 @@ should set resources when set in values: - args: - --diag-addr=0.0.0.0:3000 - --apply-on-startup=/etc/teleport/apply-on-startup.yaml - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -357,7 +357,7 @@ should set securityContext when set in values: - args: - --diag-addr=0.0.0.0:3000 - --apply-on-startup=/etc/teleport/apply-on-startup.yaml - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/proxy_config_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/proxy_config_test.yaml.snap index 4e2b7ed924172..2ff5e7bbca6e0 100644 --- a/examples/chart/teleport-cluster/tests/__snapshot__/proxy_config_test.yaml.snap +++ b/examples/chart/teleport-cluster/tests/__snapshot__/proxy_config_test.yaml.snap @@ -567,8 +567,8 @@ sets clusterDomain on Configmap: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-cluster - app.kubernetes.io/version: 14.3.32 - helm.sh/chart: teleport-cluster-14.3.32 + app.kubernetes.io/version: 14.3.33 + helm.sh/chart: teleport-cluster-14.3.33 teleport.dev/majorVersion: "14" name: RELEASE-NAME-proxy namespace: NAMESPACE diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/proxy_deployment_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/proxy_deployment_test.yaml.snap index 54434b2169674..9afd89752a83d 100644 --- a/examples/chart/teleport-cluster/tests/__snapshot__/proxy_deployment_test.yaml.snap +++ b/examples/chart/teleport-cluster/tests/__snapshot__/proxy_deployment_test.yaml.snap @@ -11,8 +11,8 @@ sets clusterDomain on Deployment Pods: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-cluster - app.kubernetes.io/version: 14.3.32 - helm.sh/chart: teleport-cluster-14.3.32 + app.kubernetes.io/version: 14.3.33 + helm.sh/chart: teleport-cluster-14.3.33 teleport.dev/majorVersion: "14" name: RELEASE-NAME-proxy namespace: NAMESPACE @@ -26,7 +26,7 @@ sets clusterDomain on Deployment Pods: template: metadata: annotations: - checksum/config: 75a5fce8eb5dc94193c7eaf8a5bbcef77dab3e1b320ab45ff2e8dc5cbbca24bc + checksum/config: f7106583f842c0c8420a999ea1f7f57be18184cb5688bb9c59d0cf769a824842 kubernetes.io/pod: test-annotation kubernetes.io/pod-different: 4 labels: @@ -34,8 +34,8 @@ sets clusterDomain on Deployment Pods: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-cluster - app.kubernetes.io/version: 14.3.32 - helm.sh/chart: teleport-cluster-14.3.32 + app.kubernetes.io/version: 14.3.33 + helm.sh/chart: teleport-cluster-14.3.33 teleport.dev/majorVersion: "14" spec: affinity: @@ -44,7 +44,7 @@ sets clusterDomain on Deployment Pods: containers: - args: - --diag-addr=0.0.0.0:3000 - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -105,7 +105,7 @@ sets clusterDomain on Deployment Pods: - wait - no-resolve - RELEASE-NAME-auth-v13.NAMESPACE.svc.test.com - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 name: wait-auth-update serviceAccountName: RELEASE-NAME-proxy terminationGracePeriodSeconds: 60 @@ -137,7 +137,7 @@ should provision initContainer correctly when set in values: - wait - no-resolve - RELEASE-NAME-auth-v13.NAMESPACE.svc.cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 name: wait-auth-update - args: - echo test @@ -194,7 +194,7 @@ should set nodeSelector when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -255,7 +255,7 @@ should set nodeSelector when set in values: - wait - no-resolve - RELEASE-NAME-auth-v13.NAMESPACE.svc.cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 name: wait-auth-update nodeSelector: environment: security @@ -306,7 +306,7 @@ should set resources when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -374,7 +374,7 @@ should set resources when set in values: - wait - no-resolve - RELEASE-NAME-auth-v13.NAMESPACE.svc.cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 name: wait-auth-update serviceAccountName: RELEASE-NAME-proxy terminationGracePeriodSeconds: 60 @@ -407,7 +407,7 @@ should set securityContext for initContainers when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -475,7 +475,7 @@ should set securityContext for initContainers when set in values: - wait - no-resolve - RELEASE-NAME-auth-v13.NAMESPACE.svc.cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 name: wait-auth-update securityContext: allowPrivilegeEscalation: false @@ -515,7 +515,7 @@ should set securityContext when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -583,7 +583,7 @@ should set securityContext when set in values: - wait - no-resolve - RELEASE-NAME-auth-v13.NAMESPACE.svc.cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 name: wait-auth-update securityContext: allowPrivilegeEscalation: false diff --git a/examples/chart/teleport-kube-agent/Chart.yaml b/examples/chart/teleport-kube-agent/Chart.yaml index c342bd60ee3e8..cfbc4678346ac 100644 --- a/examples/chart/teleport-kube-agent/Chart.yaml +++ b/examples/chart/teleport-kube-agent/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "14.3.32" +.version: &version "14.3.33" name: teleport-kube-agent apiVersion: v2 diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap index 4dc27e1516e72..5590660e7945c 100644 --- a/examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap @@ -32,7 +32,7 @@ sets Deployment annotations when specified if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -107,7 +107,7 @@ sets Deployment labels when specified if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -169,7 +169,7 @@ sets Pod annotations when specified if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -231,7 +231,7 @@ sets Pod labels when specified if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -310,7 +310,7 @@ should add emptyDir for data when existingDataVolume is not set if action is Upg value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -373,7 +373,7 @@ should add insecureSkipProxyTLSVerify to args when set in values if action is Up value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -435,7 +435,7 @@ should correctly configure existingDataVolume when set if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -495,7 +495,7 @@ should expose diag port if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -569,7 +569,7 @@ should have multiple replicas when replicaCount is set (using .replicaCount, dep value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -643,7 +643,7 @@ should have multiple replicas when replicaCount is set (using highAvailability.r value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -705,7 +705,7 @@ should have one replica when replicaCount is not set if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -767,7 +767,7 @@ should mount extraVolumes and extraVolumeMounts if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -836,7 +836,7 @@ should mount tls.existingCASecretName and set environment when set in values if value: cluster.local - name: SSL_CERT_FILE value: /etc/teleport-tls-ca/ca.pem - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -908,7 +908,7 @@ should mount tls.existingCASecretName and set extra environment when set in valu value: http://username:password@my.proxy.host:3128 - name: SSL_CERT_FILE value: /etc/teleport-tls-ca/ca.pem - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -976,7 +976,7 @@ should provision initContainer correctly when set in values if action is Upgrade value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1074,7 +1074,7 @@ should set SecurityContext if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1156,7 +1156,7 @@ should set affinity when set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1218,7 +1218,7 @@ should set default serviceAccountName when not set in values if action is Upgrad value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1293,7 +1293,7 @@ should set environment when extraEnv set in values if action is Upgrade: value: cluster.local - name: HTTPS_PROXY value: http://username:password@my.proxy.host:3128 - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1417,7 +1417,7 @@ should set imagePullPolicy when set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: Always livenessProbe: failureThreshold: 6 @@ -1479,7 +1479,7 @@ should set nodeSelector if set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1543,7 +1543,7 @@ should set not set priorityClassName when not set in values if action is Upgrade value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1617,7 +1617,7 @@ should set preferred affinity when more than one replica is used if action is Up value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1679,7 +1679,7 @@ should set priorityClassName when set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1742,7 +1742,7 @@ should set probeTimeoutSeconds when set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1814,7 +1814,7 @@ should set required affinity when highAvailability.requireAntiAffinity is set if value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1876,7 +1876,7 @@ should set resources when set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1945,7 +1945,7 @@ should set serviceAccountName when set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2007,7 +2007,7 @@ should set tolerations when set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/job_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/job_test.yaml.snap index 07a87fa76c4f8..c678bf20142b0 100644 --- a/examples/chart/teleport-kube-agent/tests/__snapshot__/job_test.yaml.snap +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/job_test.yaml.snap @@ -25,7 +25,7 @@ should create ServiceAccount for post-delete hook by default: fieldPath: metadata.namespace - name: RELEASE_NAME value: RELEASE-NAME - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent name: post-delete-job securityContext: @@ -106,7 +106,7 @@ should not create ServiceAccount for post-delete hook if serviceAccount.create i fieldPath: metadata.namespace - name: RELEASE_NAME value: RELEASE-NAME - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent name: post-delete-job securityContext: @@ -134,7 +134,7 @@ should not create ServiceAccount, Role or RoleBinding for post-delete hook if se fieldPath: metadata.namespace - name: RELEASE_NAME value: RELEASE-NAME - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent name: post-delete-job securityContext: @@ -162,7 +162,7 @@ should set nodeSelector in post-delete hook: fieldPath: metadata.namespace - name: RELEASE_NAME value: RELEASE-NAME - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent name: post-delete-job securityContext: @@ -192,7 +192,7 @@ should set securityContext in post-delete hook: fieldPath: metadata.namespace - name: RELEASE_NAME value: RELEASE-NAME - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent name: post-delete-job securityContext: diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap index b14ad8269dbe1..d4d2b8b85207f 100644 --- a/examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap @@ -18,7 +18,7 @@ sets Pod annotations when specified: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -88,7 +88,7 @@ sets Pod labels when specified: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -182,7 +182,7 @@ sets StatefulSet labels when specified: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -280,7 +280,7 @@ should add insecureSkipProxyTLSVerify to args when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -350,7 +350,7 @@ should add volumeClaimTemplate for data volume when using StatefulSet and action value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -440,7 +440,7 @@ should add volumeClaimTemplate for data volume when using StatefulSet and is Fre value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -520,7 +520,7 @@ should add volumeMount for data volume when using StatefulSet: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -590,7 +590,7 @@ should expose diag port: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -660,7 +660,7 @@ should generate Statefulset when storage is disabled and mode is a Upgrade: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -744,7 +744,7 @@ should have multiple replicas when replicaCount is set (using .replicaCount, dep value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -826,7 +826,7 @@ should have multiple replicas when replicaCount is set (using highAvailability.r value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -896,7 +896,7 @@ should have one replica when replicaCount is not set: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -966,7 +966,7 @@ should install Statefulset when storage is disabled and mode is a Fresh Install: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1038,7 +1038,7 @@ should mount extraVolumes and extraVolumeMounts: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1115,7 +1115,7 @@ should mount tls.existingCASecretName and set environment when set in values: value: cluster.local - name: SSL_CERT_FILE value: /etc/teleport-tls-ca/ca.pem - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1197,7 +1197,7 @@ should mount tls.existingCASecretName and set extra environment when set in valu value: /etc/teleport-tls-ca/ca.pem - name: HTTPS_PROXY value: http://username:password@my.proxy.host:3128 - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1275,7 +1275,7 @@ should not add emptyDir for data when using StatefulSet: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1345,7 +1345,7 @@ should provision initContainer correctly when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1451,7 +1451,7 @@ should set SecurityContext: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1541,7 +1541,7 @@ should set affinity when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1611,7 +1611,7 @@ should set default serviceAccountName when not set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1694,7 +1694,7 @@ should set environment when extraEnv set in values: value: cluster.local - name: HTTPS_PROXY value: http://username:password@my.proxy.host:3128 - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1834,7 +1834,7 @@ should set imagePullPolicy when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: Always livenessProbe: failureThreshold: 6 @@ -1904,7 +1904,7 @@ should set nodeSelector if set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1988,7 +1988,7 @@ should set preferred affinity when more than one replica is used: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2058,7 +2058,7 @@ should set probeTimeoutSeconds when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2138,7 +2138,7 @@ should set required affinity when highAvailability.requireAntiAffinity is set: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2208,7 +2208,7 @@ should set resources when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2285,7 +2285,7 @@ should set serviceAccountName when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2355,7 +2355,7 @@ should set storage.requests when set in values and action is an Upgrade: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2425,7 +2425,7 @@ should set storage.storageClassName when set in values and action is an Upgrade: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2495,7 +2495,7 @@ should set tolerations when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:14.3.32 + image: public.ecr.aws/gravitational/teleport-distroless:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/updater_deployment_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/updater_deployment_test.yaml.snap index 68dd5827bce9a..b78fdb8ecdec8 100644 --- a/examples/chart/teleport-kube-agent/tests/__snapshot__/updater_deployment_test.yaml.snap +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/updater_deployment_test.yaml.snap @@ -27,7 +27,7 @@ sets the affinity: - --base-image=public.ecr.aws/gravitational/teleport-distroless - --version-server=https://my-custom-version-server/v1 - --version-channel=custom/preview - image: public.ecr.aws/gravitational/teleport-kube-agent-updater:14.3.32 + image: public.ecr.aws/gravitational/teleport-kube-agent-updater:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -71,7 +71,7 @@ sets the tolerations: - --base-image=public.ecr.aws/gravitational/teleport-distroless - --version-server=https://my-custom-version-server/v1 - --version-channel=custom/preview - image: public.ecr.aws/gravitational/teleport-kube-agent-updater:14.3.32 + image: public.ecr.aws/gravitational/teleport-kube-agent-updater:14.3.33 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 From 883d52a21b4cfb7cfb53182844fa8c74dc4a4ffd Mon Sep 17 00:00:00 2001 From: "teleport-post-release-automation[bot]" <128860004+teleport-post-release-automation[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 00:40:05 +0000 Subject: [PATCH 43/53] [auto] docs: Update version to v14.3.33 (#48190) Co-authored-by: GitHub --- docs/config.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/config.json b/docs/config.json index e1d1a0fa1554d..dc0f078446b9c 100644 --- a/docs/config.json +++ b/docs/config.json @@ -206,19 +206,19 @@ }, "teleport": { "major_version": "14", - "version": "14.3.32", + "version": "14.3.33", "git": "api/14.0.0-gd1e081e", "url": "teleport.example.com", "golang": "1.21", "plugin": { - "version": "14.3.32" + "version": "14.3.33" }, "helm_repo_url": "https://charts.releases.teleport.dev", - "latest_oss_docker_image": "public.ecr.aws/gravitational/teleport-distroless:14.3.32", - "latest_oss_debug_docker_image": "public.ecr.aws/gravitational/teleport-distroless-debug:14.3.32", - "latest_ent_docker_image": "public.ecr.aws/gravitational/teleport-ent-distroless:14.3.32", - "latest_ent_debug_docker_image": "public.ecr.aws/gravitational/teleport-ent-distroless-debug:14.3.32", - "teleport_install_script_url": "https://cdn.teleport.dev/install-v14.3.32.sh" + "latest_oss_docker_image": "public.ecr.aws/gravitational/teleport-distroless:14.3.33", + "latest_oss_debug_docker_image": "public.ecr.aws/gravitational/teleport-distroless-debug:14.3.33", + "latest_ent_docker_image": "public.ecr.aws/gravitational/teleport-ent-distroless:14.3.33", + "latest_ent_debug_docker_image": "public.ecr.aws/gravitational/teleport-ent-distroless-debug:14.3.33", + "teleport_install_script_url": "https://cdn.teleport.dev/install-v14.3.33.sh" }, "terraform": { "version": "1.0.0" From 180d6c3cd848388cbacd3aa1c21011df4ea47238 Mon Sep 17 00:00:00 2001 From: Alan Parra Date: Thu, 31 Oct 2024 10:36:07 -0300 Subject: [PATCH 44/53] [v14] Add auto-enroll troubleshooting to docs (#47700) * Add auto-enroll troubleshooting to docs (#47681) * Update version to v14.3.33 --- .../device-trust/enroll-troubleshooting.mdx | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/pages/includes/device-trust/enroll-troubleshooting.mdx b/docs/pages/includes/device-trust/enroll-troubleshooting.mdx index c259d908bfb7b..5a43f0ac39074 100644 --- a/docs/pages/includes/device-trust/enroll-troubleshooting.mdx +++ b/docs/pages/includes/device-trust/enroll-troubleshooting.mdx @@ -10,3 +10,25 @@ A trusted device needs to be registered and enrolled before it is recognized by Teleport as such. Follow the [registration](../../admin-guides/access-controls/device-trust/device-management.mdx) and [enrollment](../../admin-guides/access-controls/device-trust/device-management.mdx) steps and make sure to `tsh logout` and `tsh login` after enrollment is done. + +### Auto enrollment not working + +Auto-enrollment ceremonies, due to their automated nature, are stricter than +regular enrollment. Additional auto-enrollment checks include: + +1. Verifying device profile data, such as data originated from Jamf, against the + actual device +2. Verifying that the device is not enrolled by another user (auto-enroll cannot + take devices that are already enrolled) + +Check you audit log for clues: look for failed "Device Enroll Token Created" +events and see the "message" field in the details (auto-enroll audit log details +available since Teleport v14.3.33). + +If you suspect (1) is the issue, compare the actual device against its inventory +definition (`tsh device collect` executed in the actual device vs `tctl get +device/`). Tweaking the device profile, manual enrollment or waiting +for the next MDM sync may solve the issue. + +If you suspect (2), you can unenroll the device using `tctl edit +device/` and changing the "enroll_status" field to "not_enrolled". From 6ae5b4585e2aa8d6ea7e16ea2e093fd99e7756e1 Mon Sep 17 00:00:00 2001 From: Taras <9948629+taraspos@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:35:10 +0000 Subject: [PATCH 45/53] docs: configure second webhook in "Update docs webhook" job (#48219) --- .github/workflows/update-docs-webhook.yaml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update-docs-webhook.yaml b/.github/workflows/update-docs-webhook.yaml index 47ff3f4bfb042..6ff9e5703107d 100644 --- a/.github/workflows/update-docs-webhook.yaml +++ b/.github/workflows/update-docs-webhook.yaml @@ -1,6 +1,8 @@ name: Update docs webhook on: push: + paths: + - 'docs/**' branches: - master - branch/v* @@ -11,11 +13,19 @@ jobs: name: Update docs webhook runs-on: ubuntu-latest environment: update-docs + strategy: + fail-fast: false + matrix: + webhooks: + - url_secret_name: DOCS_DEPLOY_HOOK + http_method: GET + - url_secret_name: AMPLIFY_DOCS_DEPLOY_HOOK + http_method: POST steps: - name: Call deployment webhook env: - WEBHOOK_URL: ${{ secrets.DOCS_DEPLOY_HOOK }} + WEBHOOK_URL: ${{ secrets[matrix.webhooks.url_secret_name] }} run: | - if curl --silent --fail --show-error "$WEBHOOK_URL" > /dev/null; then + if curl -X ${{ matrix.webhooks.http_method }} --silent --fail --show-error "$WEBHOOK_URL" > /dev/null; then echo "Triggered successfully" fi From 84ea1e593dcd9c76e692dc839b3a35edc53052c0 Mon Sep 17 00:00:00 2001 From: Erik Tate Date: Thu, 31 Oct 2024 16:37:36 -0400 Subject: [PATCH 46/53] adding an ending step to host user upsert that removes any expirations or password locks for managed teleport users (#48161) --- integration/hostuser_test.go | 88 ++++++++++++++++++++++++++++++++++++ lib/srv/usermgmt.go | 60 ++++++++++++++++++------ lib/srv/usermgmt_linux.go | 16 +++++++ lib/srv/usermgmt_test.go | 4 ++ lib/utils/host/hostusers.go | 77 +++++++++++++++++++++++++++++++ 5 files changed, 231 insertions(+), 14 deletions(-) diff --git a/integration/hostuser_test.go b/integration/hostuser_test.go index e0199470e0539..eb767f72963af 100644 --- a/integration/hostuser_test.go +++ b/integration/hostuser_test.go @@ -444,6 +444,94 @@ func TestRootHostUsers(t *testing.T) { }) } }) + + t.Run("Test expiration removal", func(t *testing.T) { + expiredUser := "expired-user" + backendExpiredUser := "backend-expired-user" + t.Cleanup(func() { cleanupUsersAndGroups([]string{expiredUser, backendExpiredUser}, []string{"test-group"}) }) + + defaultBackend, err := srv.DefaultHostUsersBackend() + require.NoError(t, err) + + backend := &hostUsersBackendWithExp{HostUsersBackend: defaultBackend} + users := srv.NewHostUsers(context.Background(), presence, "host_uuid", srv.WithHostUsersBackend(backend)) + + // Make sure the backend actually creates expired users + err = backend.CreateUser("backend-expired-user", nil, "", "", "") + require.NoError(t, err) + + hasExpirations, _, err := host.UserHasExpirations(backendExpiredUser) + require.NoError(t, err) + require.True(t, hasExpirations) + + // Upsert a new user which should have the expirations removed + _, err = users.UpsertUser(expiredUser, services.HostUsersInfo{ + Mode: types.CreateHostUserMode_HOST_USER_MODE_KEEP, + }) + require.NoError(t, err) + + hasExpirations, _, err = host.UserHasExpirations(expiredUser) + require.NoError(t, err) + require.False(t, hasExpirations) + + // Expire existing user so we can test that updates also remove expirations + expireUser := func(username string) error { + chageBin, err := exec.LookPath("chage") + require.NoError(t, err) + + cmd := exec.Command(chageBin, "-E", "1", "-I", "1", "-M", "1", username) + return cmd.Run() + } + require.NoError(t, expireUser(expiredUser)) + hasExpirations, _, err = host.UserHasExpirations(expiredUser) + require.NoError(t, err) + require.True(t, hasExpirations) + + // Update user without any changes + _, err = users.UpsertUser(expiredUser, services.HostUsersInfo{ + Mode: types.CreateHostUserMode_HOST_USER_MODE_KEEP, + }) + require.NoError(t, err) + + hasExpirations, _, err = host.UserHasExpirations(expiredUser) + require.NoError(t, err) + require.False(t, hasExpirations) + + // Reinstate expirations again + require.NoError(t, expireUser(expiredUser)) + hasExpirations, _, err = host.UserHasExpirations(expiredUser) + require.NoError(t, err) + require.True(t, hasExpirations) + + // Update user with changes + _, err = users.UpsertUser(expiredUser, services.HostUsersInfo{ + Mode: types.CreateHostUserMode_HOST_USER_MODE_KEEP, + Groups: []string{"test-group"}, + }) + require.NoError(t, err) + + hasExpirations, _, err = host.UserHasExpirations(expiredUser) + require.NoError(t, err) + require.False(t, hasExpirations) + }) +} + +type hostUsersBackendWithExp struct { + srv.HostUsersBackend +} + +func (u *hostUsersBackendWithExp) CreateUser(name string, groups []string, home, uid, gid string) error { + if err := u.HostUsersBackend.CreateUser(name, groups, home, uid, gid); err != nil { + return trace.Wrap(err) + } + + chageBin, err := exec.LookPath("chage") + if err != nil { + return trace.Wrap(err) + } + + cmd := exec.Command(chageBin, "-E", "1", "-I", "1", "-M", "1", name) + return cmd.Run() } func TestRootLoginAsHostUser(t *testing.T) { diff --git a/lib/srv/usermgmt.go b/lib/srv/usermgmt.go index 5a1bc714afd8f..b114ee5f95658 100644 --- a/lib/srv/usermgmt.go +++ b/lib/srv/usermgmt.go @@ -38,26 +38,53 @@ import ( "github.com/gravitational/teleport/lib/services/local" ) -// NewHostUsers initialize a new HostUsers object -func NewHostUsers(ctx context.Context, storage *local.PresenceService, uuid string) HostUsers { - //nolint:staticcheck // SA4023. False positive on macOS. - backend, err := newHostUsersBackend() - switch { - case trace.IsNotImplemented(err): - log.Debugf("Skipping host user management: %v", err) - return nil - case err != nil: //nolint:staticcheck // linter fails on non-linux system as only linux implementation returns useful values. - log.Warnf("Error making new HostUsersBackend: %s", err) - return nil +type HostUsersOpt = func(hostUsers *HostUserManagement) + +// WithHostUsersBackend injects a custom backend to be used within HostUserManagement +func WithHostUsersBackend(backend HostUsersBackend) HostUsersOpt { + return func(hostUsers *HostUserManagement) { + hostUsers.backend = backend } +} + +// DefaultHostUsersBackend returns the default HostUsersBackend for the host operating system +func DefaultHostUsersBackend() (HostUsersBackend, error) { + return newHostUsersBackend() +} + +// NewHostUsers initialize a new HostUsers object +func NewHostUsers(ctx context.Context, storage *local.PresenceService, uuid string, opts ...HostUsersOpt) HostUsers { + // handle fields that must be specified or aren't configurable cancelCtx, cancelFunc := context.WithCancel(ctx) - return &HostUserManagement{ - backend: backend, + hostUsers := &HostUserManagement{ ctx: cancelCtx, cancel: cancelFunc, storage: storage, userGrace: time.Second * 30, } + + // set configurable fields that don't have to be specified + for _, opt := range opts { + opt(hostUsers) + } + + // set default values for required fields that don't have to be specified + if hostUsers.backend == nil { + //nolint:staticcheck // SA4023. False positive on macOS. + backend, err := newHostUsersBackend() + switch { + case trace.IsNotImplemented(err), trace.IsNotFound(err): + log.WithError(err).Debug("Skipping host user management") + return nil + case err != nil: //nolint:staticcheck // linter fails on non-linux system as only linux implementation returns useful values. + log.WithError(err).Debug(ctx, "Error making new HostUsersBackend") + return nil + } + + hostUsers.backend = backend + } + + return hostUsers } func NewHostSudoers(uuid string) HostSudoers { @@ -107,7 +134,10 @@ type HostUsersBackend interface { // CreateHomeDirectory creates the users home directory and copies in /etc/skel CreateHomeDirectory(userHome string, uid, gid string) error // GetDefaultHomeDirectory returns the default home directory path for the given user - GetDefaultHomeDirectory(user string) (string, error) + GetDefaultHomeDirectory(name string) (string, error) + // RemoveExpirations removes any sort of password or account expiration from the user + // that may have been placed by password policies. + RemoveExpirations(name string) error } type userCloser struct { @@ -433,6 +463,8 @@ func (u *HostUserManagement) UpsertUser(name string, ui services.HostUsersInfo) } } + // attempt to remove password expirations from managed users if they've been added + defer u.backend.RemoveExpirations(name) if err := u.updateUser(name, ui); err != nil { if !errors.Is(err, user.UnknownUserError(name)) { return nil, trace.Wrap(err) diff --git a/lib/srv/usermgmt_linux.go b/lib/srv/usermgmt_linux.go index 2c3fb0ef34b7a..3dfa35c5ae4bd 100644 --- a/lib/srv/usermgmt_linux.go +++ b/lib/srv/usermgmt_linux.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "os" + "os/exec" "os/user" "path/filepath" "strconv" @@ -47,6 +48,16 @@ type HostSudoersProvisioningBackend struct { // newHostUsersBackend initializes a new OS specific HostUsersBackend func newHostUsersBackend() (HostUsersBackend, error) { + var missing []string + for _, requiredBin := range []string{"usermod", "useradd", "getent", "groupadd", "visudo", "chage"} { + if _, err := exec.LookPath(requiredBin); err != nil { + missing = append(missing, requiredBin) + } + } + if len(missing) != 0 { + return nil, trace.NotFound("missing required binaries: %s", strings.Join(missing, ",")) + } + return &HostUsersProvisioningBackend{}, nil } @@ -285,3 +296,8 @@ func (u *HostUsersProvisioningBackend) CreateHomeDirectory(user string, uidS, gi return nil } + +func (u *HostUsersProvisioningBackend) RemoveExpirations(username string) error { + _, err := host.RemoveUserExpirations(username) + return trace.Wrap(err) +} diff --git a/lib/srv/usermgmt_test.go b/lib/srv/usermgmt_test.go index 10a2e836c62ef..a8c05961de5d0 100644 --- a/lib/srv/usermgmt_test.go +++ b/lib/srv/usermgmt_test.go @@ -181,6 +181,10 @@ func (*testHostUserBackend) CheckSudoers(contents []byte) error { return errors.New("invalid") } +func (*testHostUserBackend) RemoveExpirations(user string) error { + return nil +} + // WriteSudoersFile implements HostUsersBackend func (tm *testHostUserBackend) WriteSudoersFile(user string, entries []byte) error { entry := strings.TrimSpace(string(entries)) diff --git a/lib/utils/host/hostusers.go b/lib/utils/host/hostusers.go index 8db299a1018b0..614d83d3a339a 100644 --- a/lib/utils/host/hostusers.go +++ b/lib/utils/host/hostusers.go @@ -17,6 +17,7 @@ limitations under the License. package host import ( + "bufio" "bytes" "errors" "os/exec" @@ -154,6 +155,82 @@ func GetAllUsers() ([]string, int, error) { return users, -1, nil } +// UserHasExpirations determines if the given username has an expired password, inactive password, or expired account +// by parsing the output of 'chage -l '. +func UserHasExpirations(username string) (bool bool, exitCode int, err error) { + chageBin, err := exec.LookPath("chage") + if err != nil { + return false, -1, trace.NotFound("cannot find chage binary: %s", err) + } + + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + cmd := exec.Command(chageBin, "-l", username) + cmd.Stdout = stdout + cmd.Stderr = stderr + if err := cmd.Run(); err != nil { + return false, cmd.ProcessState.ExitCode(), trace.WrapWithMessage(err, "running chage: %s", stderr.String()) + } + + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + line := scanner.Text() + if line == "" { + // ignore empty lines + continue + } + + key, value, validLine := strings.Cut(line, ":") + if !validLine { + return false, -1, trace.Errorf("chage output invalid") + } + + if strings.TrimSpace(value) == "never" { + continue + } + + switch strings.TrimSpace(key) { + case "Password expires", "Password inactive", "Account expires": + return true, 0, nil + } + } + + return false, cmd.ProcessState.ExitCode(), nil +} + +// RemoveUserExpirations uses chage to remove any future or past expirations associated with the given username. It also uses usermod to remove any account locks that may have been placed. +func RemoveUserExpirations(username string) (exitCode int, err error) { + chageBin, err := exec.LookPath("chage") + if err != nil { + return -1, trace.NotFound("cannot find chage binary: %s", err) + } + + usermodBin, err := exec.LookPath("usermod") + if err != nil { + return -1, trace.NotFound("cannot find usermod binary: %s", err) + } + + // remove all expirations from user + // chage -E -1 -I -1 + cmd := exec.Command(chageBin, "-E", "-1", "-I", "-1", "-M", "-1", username) + var errs []error + if err := cmd.Run(); err != nil { + errs = append(errs, trace.Wrap(err, "removing expirations with chage")) + } + + // unlock user password if locked + cmd = exec.Command(usermodBin, "-U", username) + if err := cmd.Run(); err != nil { + errs = append(errs, trace.Wrap(err, "removing lock with usermod")) + } + + if len(errs) > 0 { + return cmd.ProcessState.ExitCode(), trace.NewAggregate(errs...) + } + + return cmd.ProcessState.ExitCode(), nil +} + var ErrInvalidSudoers = errors.New("visudo: invalid sudoers file") // CheckSudoers tests a suders file using `visudo`. The contents From 5355aa9835bc15f7a1c2c94c9d7e80157881378e Mon Sep 17 00:00:00 2001 From: Paul Gottschling Date: Fri, 1 Nov 2024 08:56:17 -0400 Subject: [PATCH 47/53] [v14] Add troubleshooting info to Usage and Billing page (#48083) * Add troubleshooting info to Usage and Billing page Turn the section on validating usage data into the intro paragraph for a troubleshooting section. We don't actually need to compare the usage reporting system to audit events, and removing the comparison makes this information concise enough for an introductory paragraph. Add H2s for issues we have identified with usage reporting and billing on self-hosted clusters. * Fix grammar in the Usage/Billing guide --- docs/pages/usage-billing.mdx | 70 ++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/docs/pages/usage-billing.mdx b/docs/pages/usage-billing.mdx index f3a0f1a2ab4c9..5a91ced765815 100644 --- a/docs/pages/usage-billing.mdx +++ b/docs/pages/usage-billing.mdx @@ -64,25 +64,6 @@ Set the `TELEPORT_REPORTING_HTTPS_PROXY` and `TELEPORT_REPORTING_HTTP_PROXY` environment variables to your proxy address. That will apply as the HTTP connect proxy setting overriding `HTTPS_PROXY` and `HTTP_PROXY` just for outbound usage reporting. -### Validating usage reports - -The system that Teleport uses for submitting usage reports is independent of the -system that Teleport uses for submitting audit events. - -Teleport processes submit audit events to the Teleport Auth Service, which -stores them on its audit event backend for retrieval by Teleport API clients. In -contrast, usage reports are aggregated on a submission service that runs either -on self-hosted Teleport infrastructure or Teleport Cloud, depending on the -user's plan. The submission service persists usage reports in the case of a -submission failure. After a successful submission, the submission service -deletes the reports. - -It is not possible for Teleport users to independently validate usage event -data, as there is no way to set up a third-party usage event destination or -retrieve usage events from a Teleport backend. Reach out to -support@goteleport.com if you have questions about usage reporting on your -Teleport account. - ## Billing metrics Teleport uses the anonymized usage data described in the previous section to @@ -144,6 +125,11 @@ to compute a daily TPR. Then we average the daily TPR over a monthly period, which starts on the subscription start date and ends on each monthly anniversary thereafter. +If you recreate a single resource more than once an hour, this will affect the +hourly average. For example, if you were to create then delete 10 servers three +times in one hour, Teleport would display 10 servers at any given time. However, +for the entire hour, Teleport would report 30 protected servers. + ## Usage measurement for billing We aggregate all counts of the billing metrics on a monthly basis starting on @@ -155,3 +141,49 @@ Subscription, also known as a high water mark calculation. Reach out to sales@goteleport.com if you have questions about the commercial editions of Teleport. + +## Troubleshooting usage and billing + +Teleport aggregates usage reports on a submission service that runs either on +self-hosted Teleport infrastructure or Teleport Cloud, depending on the user's +plan. The submission service persists usage reports in the case of a submission +failure, and deletes the reports after a successful submission. It is not +possible to set up a third-party destination for usage events to independently +verify usage event data. + +If you are using Teleport Enterprise (Cloud), your usage data is accurate as +long as Teleport-managed reporting infrastructure works as expected (check the +[status page](https://status.teleport.sh/) for any incidents). On self-hosted +Teleport Enterprise clusters, some conditions can interfere with data reporting. +This section describes some scenarios that can lead to inaccurate data on +self-hosted clusters. + +If you suspect that any of these scenarios describe your Teleport cluster, or +your usage data appears inaccurate, reach out to support@goteleport.com. + +### Multiple Teleport clusters + +In versions older than v14.3.1, Teleport does not de-duplicate users as expected +across multiple Teleport clusters that belong to the same account. If you are +running multiple Teleport clusters with affected versions, the count of active +users may be higher than expected. + +### Unexpected license differences + +When distributing copies of your Teleport Enterprise (Self-Hosted) license +across Auth Service instances, you must not download a license multiple times +from your Teleport account. Instead, you must download a license once and copy +that license across Auth Service instances. Otherwise, the Teleport usage +reporting infrastructure will identify multiple licenses and misrepresent your +usage numbers. + +### SSO users + +In Teleport, single sign-on (SSO) users are ephemeral. Teleport deletes an SSO +user when its session expires. To count the number of SSO users in your cluster, +you can examine Teleport audit events for unique SSO users that have +authenticated to Teleport during a given time period. The Teleport documentation +includes how-to guides for exporting audit events to common log management +solutions so you can identify users that have authenticated using an SSO +provider. + From 1823162772513b08da8fd69943b76efd643bb2d2 Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Fri, 1 Nov 2024 10:41:45 -0400 Subject: [PATCH 48/53] Fix TestEmitAuditEventForLargeEvents (#48228) The test relied on the fact that AppSessionRequests did not implement TrimToMaxSize. However, now that all events are forced to implemement TrimToMaxSize the test was always failing. To fix a wrapper around the event was added that overrides TrimToMaxSize that does no trimming. --- lib/events/dynamoevents/dynamoevents_test.go | 23 +++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/events/dynamoevents/dynamoevents_test.go b/lib/events/dynamoevents/dynamoevents_test.go index 21a9ed30a6195..656fb52eec4ae 100644 --- a/lib/events/dynamoevents/dynamoevents_test.go +++ b/lib/events/dynamoevents/dynamoevents_test.go @@ -312,17 +312,30 @@ func TestEmitAuditEventForLargeEvents(t *testing.T) { assert.Len(t, result, 1) }, 10*time.Second, 500*time.Millisecond) - appReqEvent := &apievents.AppSessionRequest{ - Metadata: apievents.Metadata{ - Time: tt.suite.Clock.Now().UTC(), - Type: events.AppSessionRequestEvent, + appReqEvent := &testAuditEvent{ + AppSessionRequest: apievents.AppSessionRequest{ + Metadata: apievents.Metadata{ + Time: tt.suite.Clock.Now().UTC(), + Type: events.AppSessionRequestEvent, + }, + Path: strings.Repeat("A", maxItemSize), }, - Path: strings.Repeat("A", maxItemSize), } err = tt.suite.Log.EmitAuditEvent(ctx, appReqEvent) require.ErrorContains(t, err, "ValidationException: Item size has exceeded the maximum allowed size") } +// testAuditEvent wraps an existing AuditEvent, but overrides +// the TrimToMaxSize to be a noop so that functionality can +// be tested if an event exceeds the size limits. +type testAuditEvent struct { + apievents.AppSessionRequest +} + +func (t *testAuditEvent) TrimToMaxSize(maxSizeBytes int) apievents.AuditEvent { + return t +} + func TestConfig_SetFromURL(t *testing.T) { useFipsCfg := Config{ UseFIPSEndpoint: types.ClusterAuditConfigSpecV2_FIPS_ENABLED, From 9c1624d6d4fb2ddac3b5f051194d3a5fb386c765 Mon Sep 17 00:00:00 2001 From: Gus Rivera Date: Fri, 1 Nov 2024 10:57:25 -0500 Subject: [PATCH 49/53] [v14] Increasing timeout for pam smoke tests (#48232) * Increasing timeout for pam tests * Adding commentary --- build.assets/charts/smoke_tests/01_pam/test.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build.assets/charts/smoke_tests/01_pam/test.sh b/build.assets/charts/smoke_tests/01_pam/test.sh index 442f2cea38d8f..8ae6c34f8f529 100755 --- a/build.assets/charts/smoke_tests/01_pam/test.sh +++ b/build.assets/charts/smoke_tests/01_pam/test.sh @@ -6,4 +6,8 @@ # # If teleport is still up when the timeout expires, then we're # probably OK -timeout --preserve-status 10s docker run --platform $1 --rm --entrypoint /usr/local/bin/teleport -v "$(pwd):/etc/teleport" $2 start -c /etc/teleport/config.yaml \ No newline at end of file +# +# A timeout of 20s is set to account for potentially slow startup times. +# QEMU eumlation may slow startup time for some platforms and during that +# time the process won't shutdown gracefully resulting in a nonzero code. +timeout --preserve-status 20s docker run --platform $1 --rm --entrypoint /usr/local/bin/teleport -v "$(pwd):/etc/teleport" $2 start -c /etc/teleport/config.yaml From 0effea884f86aeef0edac3a94c38524a3f2a4d53 Mon Sep 17 00:00:00 2001 From: Tiago Silva Date: Mon, 4 Nov 2024 18:58:42 +0000 Subject: [PATCH 50/53] [kube] use local image for kubernetes integration tests (#48321) (#48386) * [kube] use local image for kubernetes integration tests This PR replaces the use of the `nginx:latest` Docker image in Kubernetes integration tests with a custom-built image based on `alpine:3.20.3`. This custom image includes a shell for `kubectl exec` integration tests and a compiled binary to act as an HTTP server. This change addresses recent issues where test workflows failed due to problems downloading the `nginx` image. * add information about the image * source files from alpine cdn * copy files from alpine * Update fixtures/alpine/README.md * Update fixtures/alpine/README.md * source files from alpine cdn (again) --------- Co-authored-by: Alan Parra --- .../kube-integration-tests-non-root.yaml | 33 ++++++++++++ .gitignore | 2 + fixtures/alpine/Dockerfile | 9 ++++ fixtures/alpine/README.md | 50 ++++++++++++++++++ .../alpine-ncopa.at.alpinelinux.org.asc | 52 +++++++++++++++++++ fixtures/alpine/webserver.go | 26 ++++++++++ integration/kube_integration_test.go | 13 +++-- 7 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 fixtures/alpine/Dockerfile create mode 100644 fixtures/alpine/README.md create mode 100644 fixtures/alpine/alpine-ncopa.at.alpinelinux.org.asc create mode 100644 fixtures/alpine/webserver.go diff --git a/.github/workflows/kube-integration-tests-non-root.yaml b/.github/workflows/kube-integration-tests-non-root.yaml index ac2892e75cbcd..af1c4613b4630 100644 --- a/.github/workflows/kube-integration-tests-non-root.yaml +++ b/.github/workflows/kube-integration-tests-non-root.yaml @@ -28,6 +28,7 @@ on: env: TEST_KUBE: true KUBECONFIG: /home/.kube/config + ALPINE_VERSION: 3.20.3 jobs: test: @@ -81,6 +82,38 @@ jobs: cp -r $HOME/.kube /home/ chown -R ci:ci /home/.kube + - name: Build Alpine image with webserver + run: | + + export SHORT_VERSION=${ALPINE_VERSION%.*} + + # download the alpine image + # store the files in the fixtures/alpine directory + # to avoid passing all the repository files to the docker build context. + cd ./fixtures/alpine + + # download alpine minirootfs and signature + curl -fSsLO https://dl-cdn.alpinelinux.org/alpine/v$SHORT_VERSION/releases/x86_64/alpine-minirootfs-$ALPINE_VERSION-x86_64.tar.gz + curl -fSsLO https://dl-cdn.alpinelinux.org/alpine/v$SHORT_VERSION/releases/x86_64/alpine-minirootfs-$ALPINE_VERSION-x86_64.tar.gz.asc + curl -fSsLO https://dl-cdn.alpinelinux.org/alpine/v$SHORT_VERSION/releases/x86_64/alpine-minirootfs-$ALPINE_VERSION-x86_64.tar.gz.sha256 + + # verify the checksum + sha256sum -c alpine-minirootfs-$ALPINE_VERSION-x86_64.tar.gz.sha256 + + # verify the signature + gpg --import ./alpine-ncopa.at.alpinelinux.org.asc + gpg --verify ./alpine-minirootfs-$ALPINE_VERSION-x86_64.tar.gz.asc ./alpine-minirootfs-$ALPINE_VERSION-x86_64.tar.gz + + # build the webserver + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./webserver ./webserver.go + + docker build -t alpine-webserver:v1 --build-arg=ALPINE_VERSION=$ALPINE_VERSION -f ./Dockerfile . + + # load the image into the kind cluster + kind load docker-image alpine-webserver:v1 + + cd - + - name: Run tests timeout-minutes: 40 run: | diff --git a/.gitignore b/.gitignore index 6adc620d19d35..c87cd2d47bda5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ default.etcd # usually release tarballs get in the way *.gz +# ignore all tarballs except the alpine one +!fixtures/alpine/alpine-minirootfs-*.tar.gz *.zip # editors diff --git a/fixtures/alpine/Dockerfile b/fixtures/alpine/Dockerfile new file mode 100644 index 0000000000000..4b66c356c5466 --- /dev/null +++ b/fixtures/alpine/Dockerfile @@ -0,0 +1,9 @@ +FROM scratch + +ARG ALPINE_VERSION + +ADD alpine-minirootfs-$ALPINE_VERSION-x86_64.tar.gz / + +COPY webserver /webserver + +CMD [ "/webserver" ] diff --git a/fixtures/alpine/README.md b/fixtures/alpine/README.md new file mode 100644 index 0000000000000..a6506854275fd --- /dev/null +++ b/fixtures/alpine/README.md @@ -0,0 +1,50 @@ +# `alpine-webserver:v1` Build Process + +## Source + +The `alpine-webserver:v1` image is based on `alpine` `minirootfs`, but instead of relying on Docker Hub's official Alpine image, we source the original files directly from Alpine's CDN. This approach mitigates issues with Docker Hub and GitHub Action network failures, which have been a common cause of integration test failures. + +The build process is specified in the `.github/workflows/kube-integration-tests-non-root.yaml` file. + +## Download + +To download the new `alpine-minirootfs` image, follow the instructions: + +```bash +$ export ALPINE_VERSION=3.20.3 +$ export SHORT_VERSION=${ALPINE_VERSION%.*} + +# download alpine minirootfs and signature +$ curl -fSsLO https://dl-cdn.alpinelinux.org/alpine/v$SHORT_VERSION/releases/x86_64/alpine-minirootfs-$ALPINE_VERSION-x86_64.tar.gz +$ curl -fSsLO https://dl-cdn.alpinelinux.org/alpine/v$SHORT_VERSION/releases/x86_64/alpine-minirootfs-$ALPINE_VERSION-x86_64.tar.gz.asc +$ curl -fSsLO https://dl-cdn.alpinelinux.org/alpine/v$SHORT_VERSION/releases/x86_64/alpine-minirootfs-$ALPINE_VERSION-x86_64.tar.gz.sha256 + +``` + +## Source Validation + +The build process in `.github/workflows/kube-integration-tests-non-root.yaml` validates both the SHA-256 checksum and the GPG signature. The signature verification uses `alpine-ncopa.at.alpinelinux.org.asc`, which is the official public key used by Alpine Linux to sign its assets. This public key is available on the [Alpine Linux Downloads page](https://www.alpinelinux.org/downloads/). + +## Image Build Process + +The image is constructed from a scratch filesystem and incorporates only the necessary components to run the web server. Here’s the basic Dockerfile configuration: + +```Dockerfile +FROM scratch + +ARG ALPINE_VERSION + +ADD alpine-minirootfs-$ALPINE_VERSION-x86_64.tar.gz / + +COPY webserver /webserver + +CMD [ "/webserver" ] +``` + +This minimalist configuration ensures the image remains lightweight, secure, and tailored to only the required functionalities. + +## Image distribution + +After sucessfull build, the image is loaded into our `kind` cluster with the tag `alpine-webserver:v1`. + +Note: `:latest` can't be used otherwise Kubernetes will try loading the image from dockerhub and fail. \ No newline at end of file diff --git a/fixtures/alpine/alpine-ncopa.at.alpinelinux.org.asc b/fixtures/alpine/alpine-ncopa.at.alpinelinux.org.asc new file mode 100644 index 0000000000000..b4b34a5b3da96 --- /dev/null +++ b/fixtures/alpine/alpine-ncopa.at.alpinelinux.org.asc @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2 + +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 +Mkc= +=FcYC +-----END PGP PUBLIC KEY BLOCK----- diff --git a/fixtures/alpine/webserver.go b/fixtures/alpine/webserver.go new file mode 100644 index 0000000000000..b41a818ffc18c --- /dev/null +++ b/fixtures/alpine/webserver.go @@ -0,0 +1,26 @@ +// 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 main + +import "net/http" + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello, world!")) + }) + http.ListenAndServe(":80", nil) +} diff --git a/integration/kube_integration_test.go b/integration/kube_integration_test.go index 2e74ff76ea2db..9c5140a28023c 100644 --- a/integration/kube_integration_test.go +++ b/integration/kube_integration_test.go @@ -1436,7 +1436,7 @@ func testKubeEphemeralContainers(t *testing.T, suite *KubeSuite) { sessCreatorTerm := NewTerminal(250) group := &errgroup.Group{} group.Go(func() error { - cmd := []string{"/bin/sh", "echo", "hello from an ephemeral container"} + cmd := []string{"/bin/sh", "-c", "echo hello from an ephemeral container"} debugPod, _, err := generateDebugContainer(contName, cmd, pod) if err != nil { return trace.Wrap(err) @@ -1541,7 +1541,7 @@ func generateDebugContainer(name string, cmd []string, pod *v1.Pod) (*v1.Pod, *v ec := &v1.EphemeralContainer{ EphemeralContainerCommon: v1.EphemeralContainerCommon{ Name: name, - Image: "alpine:latest", + Image: localPodImage, Command: cmd, ImagePullPolicy: v1.PullIfNotPresent, Stdin: true, @@ -1704,6 +1704,13 @@ func newNamespace(name string) *v1.Namespace { } } +// localPodImage is a container image that is used for testing +// It's a docker image that runs a simple web server +// that listens on port 80 and returns "Hello, World!" on GET / +// This image is vendored in the Teleport repository in the +// fixtures/alpine directory. Check the Dockerfile there for details. +const localPodImage = "alpine-webserver:v1" + func newPod(ns, name string) *v1.Pod { return &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -1713,7 +1720,7 @@ func newPod(ns, name string) *v1.Pod { Spec: v1.PodSpec{ Containers: []v1.Container{{ Name: "nginx", - Image: "nginx:alpine", + Image: localPodImage, }}, }, } From 699817ac15fd22738b013340136c632ed8dfa972 Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:42:21 -0500 Subject: [PATCH 51/53] [v14] Support double dash delimiter in tsh ssh (#47495) * Support double dash delimiter in tsh ssh This PR extends the tsh ssh command by adding support for the double dash (--) delimiter before remote commands (e.g. `tsh ssh -- echo test`), aligning its behavior with the standard ssh binary. This improves compatibility with tools that rely on the standard ssh binary behavior, such as sshuttle. Fixes #18453, #16589. Signed-off-by: Tim Ross * Support double dash delimiter in tsh ssh This PR extends the tsh ssh command by adding support for the double dash (--) delimiter before remote commands (e.g. `tsh ssh -- echo test`), aligning its behavior with the standard ssh binary. This improves compatibility with tools that rely on the standard ssh binary behavior, such as sshuttle. Fixes #18453, #16589. Signed-off-by: Tim Ross * fix: update tests --------- Signed-off-by: Tim Ross Co-authored-by: Samir Aguiar --- tool/tsh/common/tsh.go | 5 + tool/tsh/common/tsh_test.go | 177 ++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index c3bce55a74c2d..a65866b796d18 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -3533,6 +3533,11 @@ func onSSH(cf *CLIConf) error { tc.AllowHeadless = true + // Support calling `tsh ssh -- ` (with a double dash before the command) + if len(cf.RemoteCommand) > 0 && strings.TrimSpace(cf.RemoteCommand[0]) == "--" { + cf.RemoteCommand = cf.RemoteCommand[1:] + } + tc.Stdin = os.Stdin err = retryWithAccessRequest(cf, tc, func() error { err = client.RetryWithRelogin(cf.Context, tc, func() error { diff --git a/tool/tsh/common/tsh_test.go b/tool/tsh/common/tsh_test.go index 4301a7ebdffce..7e8506fb2eaf8 100644 --- a/tool/tsh/common/tsh_test.go +++ b/tool/tsh/common/tsh_test.go @@ -2041,6 +2041,183 @@ func TestAccessRequestOnLeaf(t *testing.T) { require.NoError(t, err) } +// TestSSHCommand tests that a user can access a single SSH node and run commands. +func TestSSHCommands(t *testing.T) { + modules.SetTestModules(t, &modules.TestModules{TestBuildType: modules.BuildEnterprise}) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + accessRoleName := "access" + sshHostname := "test-ssh-server" + + accessUser, err := types.NewUser(accessRoleName) + require.NoError(t, err) + accessUser.SetRoles([]string{accessRoleName}) + + user, err := user.Current() + require.NoError(t, err) + accessUser.SetLogins([]string{user.Username}) + + traits := map[string][]string{ + constants.TraitLogins: {user.Username}, + } + accessUser.SetTraits(traits) + + connector := mockConnector(t) + rootServerOpts := []testserver.TestServerOptFunc{ + testserver.WithBootstrap(connector, accessUser), + testserver.WithHostname(sshHostname), + testserver.WithClusterName(t, "root"), + testserver.WithSSHLabel(accessRoleName, "true"), + testserver.WithSSHPublicAddrs("127.0.0.1:0"), + testserver.WithConfig(func(cfg *servicecfg.Config) { + cfg.SSH.Enabled = true + cfg.SSH.PublicAddrs = []utils.NetAddr{cfg.SSH.Addr} + cfg.SSH.DisableCreateHostUser = true + }), + } + rootServer := testserver.MakeTestServer(t, rootServerOpts...) + + rootProxyAddr, err := rootServer.ProxyWebAddr() + require.NoError(t, err) + + require.EventuallyWithT(t, func(t *assert.CollectT) { + rootNodes, err := rootServer.GetAuthServer().GetNodes(ctx, apidefaults.Namespace) + if !assert.NoError(t, err) || !assert.Len(t, rootNodes, 1) { + return + } + }, 10*time.Second, 100*time.Millisecond) + + tmpHomePath := t.TempDir() + rootAuth := rootServer.GetAuthServer() + + err = Run(ctx, []string{ + "login", + "--insecure", + "--proxy", rootProxyAddr.String(), + "--user", user.Username, + }, setHomePath(tmpHomePath), setMockSSOLogin(rootAuth, accessUser, connector.GetName())) + require.NoError(t, err) + + tests := []struct { + name string + args []string + expected string + shouldErr bool + }{ + { + // Test that a simple echo works. + name: "ssh simple command", + expected: "this is a test message", + args: []string{ + fmt.Sprintf("%s@%s", user.Username, sshHostname), + "echo", + "this is a test message", + }, + shouldErr: false, + }, + { + // Test that commands can be prefixed with a double dash. + name: "ssh command with double dash", + expected: "this is a test message", + args: []string{ + fmt.Sprintf("%s@%s", user.Username, sshHostname), + "--", + "echo", + "this is a test message", + }, + shouldErr: false, + }, + { + // Test that a double dash is not removed from the middle of a command. + name: "ssh command with double dash in the middle", + expected: "-- this is a test message", + args: []string{ + fmt.Sprintf("%s@%s", user.Username, sshHostname), + "echo", + "--", + "this is a test message", + }, + shouldErr: false, + }, + { + // Test that quoted commands work (e.g. `tsh ssh 'echo test'`) + name: "ssh command literal", + expected: "this is a test message", + args: []string{ + fmt.Sprintf("%s@%s", user.Username, sshHostname), + "echo this is a test message", + }, + shouldErr: false, + }, + { + // Test that a double dash is passed as-is in a quoted command (which should fail). + name: "ssh command literal with double dash err", + expected: "", + args: []string{ + fmt.Sprintf("%s@%s", user.Username, sshHostname), + "-- echo this is a test message", + }, + shouldErr: true, + }, + { + // Test that a double dash is not removed from the middle of a quoted command. + name: "ssh command literal with double dash in the middle", + expected: "-- this is a test message", + args: []string{ + fmt.Sprintf("%s@%s", user.Username, sshHostname), + "echo", "-- this is a test message", + }, + shouldErr: false, + }, + { + // Test tsh ssh -- hostname command + name: "delimiter before host and command", + expected: "this is a test message", + args: []string{ + "--", sshHostname, "echo", "this is a test message", + }, + shouldErr: false, + }, + } + + for _, test := range tests { + test := test + ctx := context.Background() + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + stdout := &output{buf: bytes.Buffer{}} + stderr := &output{buf: bytes.Buffer{}} + args := append( + []string{ + "ssh", + "--insecure", + "--proxy", rootProxyAddr.String(), + }, + test.args..., + ) + + err := Run(ctx, args, setHomePath(tmpHomePath), + func(conf *CLIConf) error { + conf.overrideStdin = &bytes.Buffer{} + conf.OverrideStdout = stdout + conf.overrideStderr = stderr + return nil + }, + ) + + if test.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, test.expected, strings.TrimSpace(stdout.String())) + require.Empty(t, stderr.String()) + } + }) + } +} + // tryCreateTrustedCluster performs several attempts to create a trusted cluster, // retries on connection problems and access denied errors to let caches // propagate and services to start From 33928aecb517a1819412f3d95e4ef42159bddeb5 Mon Sep 17 00:00:00 2001 From: Paul Gottschling Date: Mon, 4 Nov 2024 15:41:45 -0500 Subject: [PATCH 52/53] Fix markdown-lint issues (#48370) Running markdown-lint in the Docusaurus site flags some issues that the gravitational/docs markdown-lint configuration doesn't catch. This change resolves these issues, fixing indentation and list item spacing. --- .../access-controls/sso/google-workspace.mdx | 2 +- .../admin-guides/deploy-a-cluster/gcp-kms.mdx | 1 - .../helm-deployments/kubernetes-cluster.mdx | 12 ++++++------ docs/pages/admin-guides/deploy-a-cluster/hsm.mdx | 1 + .../contributing/documentation/style-guide.mdx | 6 +++--- docs/pages/upgrading/cloud-kubernetes.mdx | 3 ++- .../self-hosted-automatic-agent-updates.mdx | 8 ++++---- docs/pages/upgrading/self-hosted-kubernetes.mdx | 14 +++++++------- 8 files changed, 24 insertions(+), 23 deletions(-) diff --git a/docs/pages/admin-guides/access-controls/sso/google-workspace.mdx b/docs/pages/admin-guides/access-controls/sso/google-workspace.mdx index 6563c15c528ce..45399cac067a5 100644 --- a/docs/pages/admin-guides/access-controls/sso/google-workspace.mdx +++ b/docs/pages/admin-guides/access-controls/sso/google-workspace.mdx @@ -31,7 +31,7 @@ Before you get started, verify the following: - You have a Teleport cluster, Enterprise edition, and command-line tools. - (!docs/pages/includes/commercial-prereqs-tabs.mdx!) + (!docs/pages/includes/commercial-prereqs-tabs.mdx!) - (!docs/pages/includes/tctl.mdx!) diff --git a/docs/pages/admin-guides/deploy-a-cluster/gcp-kms.mdx b/docs/pages/admin-guides/deploy-a-cluster/gcp-kms.mdx index fd98f68ae03bf..5b138fba609d0 100644 --- a/docs/pages/admin-guides/deploy-a-cluster/gcp-kms.mdx +++ b/docs/pages/admin-guides/deploy-a-cluster/gcp-kms.mdx @@ -36,7 +36,6 @@ higher. (!docs/pages/includes/commercial-prereqs-tabs.mdx!) - (!docs/pages/includes/tctl.mdx!) - - A Google Cloud account. ## Step 1/5. Create a key ring in GCP diff --git a/docs/pages/admin-guides/deploy-a-cluster/helm-deployments/kubernetes-cluster.mdx b/docs/pages/admin-guides/deploy-a-cluster/helm-deployments/kubernetes-cluster.mdx index 0152ee196ba3e..aab51ee388180 100644 --- a/docs/pages/admin-guides/deploy-a-cluster/helm-deployments/kubernetes-cluster.mdx +++ b/docs/pages/admin-guides/deploy-a-cluster/helm-deployments/kubernetes-cluster.mdx @@ -198,12 +198,12 @@ chart. 1. Install the `teleport-cluster` Helm chart using the values file you wrote: - ```code - $ helm install teleport-cluster teleport/teleport-cluster \ - --create-namespace \ - --version (=teleport.version=) \ - --values teleport-cluster-values.yaml - ``` + ```code + $ helm install teleport-cluster teleport/teleport-cluster \ + --create-namespace \ + --version (=teleport.version=) \ + --values teleport-cluster-values.yaml + ``` 1. After installing the `teleport-cluster` chart, wait a minute or so and ensure that both the Auth Service and Proxy Service pods are running: diff --git a/docs/pages/admin-guides/deploy-a-cluster/hsm.mdx b/docs/pages/admin-guides/deploy-a-cluster/hsm.mdx index 749e68cd9f2d0..d538c885a0e2f 100644 --- a/docs/pages/admin-guides/deploy-a-cluster/hsm.mdx +++ b/docs/pages/admin-guides/deploy-a-cluster/hsm.mdx @@ -185,6 +185,7 @@ to use. DEBU[0000] preflight complete cert= config= key= pid=73502 seccomp=false serial= syslog=false timeout=0s version=3.0.3 DEBU[0000] takeoff TLS=false listen="localhost:12345" pid=73502 ``` + 1. Use `yubihsm-shell` to create a new authentication key to be used by Teleport with the necessary capabilities. diff --git a/docs/pages/contributing/documentation/style-guide.mdx b/docs/pages/contributing/documentation/style-guide.mdx index 389046770adbe..9b965f8ad769b 100644 --- a/docs/pages/contributing/documentation/style-guide.mdx +++ b/docs/pages/contributing/documentation/style-guide.mdx @@ -163,9 +163,9 @@ for the audience of a specific guide. - Prefer putting commands (e.g., Bash scripts) into full-line code snippets. These will render with a handy copy button. - ```code - $ echo "This is an example." - ``` + ```code + $ echo "This is an example." + ``` ### Diagrams diff --git a/docs/pages/upgrading/cloud-kubernetes.mdx b/docs/pages/upgrading/cloud-kubernetes.mdx index cdf11b7050e42..1698f44963947 100644 --- a/docs/pages/upgrading/cloud-kubernetes.mdx +++ b/docs/pages/upgrading/cloud-kubernetes.mdx @@ -39,6 +39,7 @@ updating works. Automatic agent upgrades require: $ curl -s https://mytenant.teleport.sh/webapi/ping | jq '.automatic_upgrades' true ``` + - At least one Teleport Enterprise agent deployed via the `teleport-kube-agent` Helm chart. @@ -187,7 +188,7 @@ Run the following commands to upgrade Teleport agents running on Kubernetes. 1. Update the Teleport Helm chart repository so you can install the latest version of the `teleport-kube-agent` chart: - (!docs/pages/includes/kubernetes-access/helm/helm-repo-add.mdx!) + (!docs/pages/includes/kubernetes-access/helm/helm-repo-add.mdx!) 1. Upgrade the Helm release: diff --git a/docs/pages/upgrading/self-hosted-automatic-agent-updates.mdx b/docs/pages/upgrading/self-hosted-automatic-agent-updates.mdx index 14746f2b98554..d72c43dd28ab2 100644 --- a/docs/pages/upgrading/self-hosted-automatic-agent-updates.mdx +++ b/docs/pages/upgrading/self-hosted-automatic-agent-updates.mdx @@ -340,10 +340,10 @@ This section assumes that the name of your `teleport-kube-agent` release is 1. Check for any deployment issues by checking the updater logs: - ```code - $ kubectl -n logs deployment/-updater - 2023-04-28T13:13:30Z INFO StatefulSet is already up-to-date, not updating. {"controller": "statefulset", "controllerGroup": "apps", "controllerKind": "StatefulSet", "StatefulSet": {"name":"my-agent","namespace":"agent"}, "namespace": "agent", "name": "my-agent", "reconcileID": "10419f20-a4c9-45d4-a16f-406866b7fc05", "namespacedname": "agent/my-agent", "kind": "StatefulSet", "err": "no new version (current: \"v12.2.3\", next: \"v12.2.3\")"} - ``` + ```code + $ kubectl -n logs deployment/-updater + 2023-04-28T13:13:30Z INFO StatefulSet is already up-to-date, not updating. {"controller": "statefulset", "controllerGroup": "apps", "controllerKind": "StatefulSet", "StatefulSet": {"name":"my-agent","namespace":"agent"}, "namespace": "agent", "name": "my-agent", "reconcileID": "10419f20-a4c9-45d4-a16f-406866b7fc05", "namespacedname": "agent/my-agent", "kind": "StatefulSet", "err": "no new version (current: \"v12.2.3\", next: \"v12.2.3\")"} + ``` ### Troubleshooting automatic agent upgrades on Kubernetes diff --git a/docs/pages/upgrading/self-hosted-kubernetes.mdx b/docs/pages/upgrading/self-hosted-kubernetes.mdx index 6ac6e837a079e..2e87bf4fc129a 100644 --- a/docs/pages/upgrading/self-hosted-kubernetes.mdx +++ b/docs/pages/upgrading/self-hosted-kubernetes.mdx @@ -45,15 +45,15 @@ running on Kubernetes. 1. Update the Teleport Helm chart repository so you can install the latest version of the `teleport-cluster` chart: - (!docs/pages/includes/kubernetes-access/helm/helm-repo-add.mdx!) + (!docs/pages/includes/kubernetes-access/helm/helm-repo-add.mdx!) 1. Upgrade the Helm release: - ```code - $ helm upgrade teleport-cluster teleport/teleport-cluster \ - --version= \ - --values=values.yaml - ``` + ```code + $ helm upgrade teleport-cluster teleport/teleport-cluster \ + --version= \ + --values=values.yaml + ``` The `teleport-cluster` Helm chart automatically waits for the previous version of the Proxy Service to stop responding to requests before running a new version @@ -66,7 +66,7 @@ Run the following commands to upgrade Teleport agents running on Kubernetes. 1. Update the Teleport Helm chart repository so you can install the latest version of the `teleport-kube-agent` chart: - (!docs/pages/includes/kubernetes-access/helm/helm-repo-add.mdx!) + (!docs/pages/includes/kubernetes-access/helm/helm-repo-add.mdx!) 1. Upgrade the Helm release: From b8b7bab53a81e6056a5bea64868e6a7a87d61372 Mon Sep 17 00:00:00 2001 From: Hugo Shaka Date: Tue, 5 Nov 2024 09:48:09 -0500 Subject: [PATCH 53/53] operator: fix oidc conenctor max age (#48378) --- integrations/operator/Makefile | 4 ++ .../apis/resources/v3/oidcconnector_types.go | 38 +++++++++++++++- .../resources/v3/oidcconnector_types_test.go | 43 +++++++++++++++++++ .../oidc_connector_controller_test.go | 2 + 4 files changed, 86 insertions(+), 1 deletion(-) diff --git a/integrations/operator/Makefile b/integrations/operator/Makefile index 7a2e1a81a7db3..4ac5e5cb0e8a7 100644 --- a/integrations/operator/Makefile +++ b/integrations/operator/Makefile @@ -120,6 +120,10 @@ test: export KUBEBUILDER_ASSETS=$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p test: go test ./... -coverprofile cover.out +.PHONY: echo-kubebuilder-assets +echo-kubebuilder-assets: + @echo KUBEBUILDER_ASSETS=$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path) + .PHONY: crdgen-test crdgen-test: ## Run crdgen tests. make -C crdgen test diff --git a/integrations/operator/apis/resources/v3/oidcconnector_types.go b/integrations/operator/apis/resources/v3/oidcconnector_types.go index db558031c6125..a91c90f545604 100644 --- a/integrations/operator/apis/resources/v3/oidcconnector_types.go +++ b/integrations/operator/apis/resources/v3/oidcconnector_types.go @@ -19,6 +19,7 @@ package v3 import ( "encoding/json" + "github.com/gravitational/trace" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/gravitational/teleport/api/types" @@ -102,14 +103,49 @@ func (spec *TeleportOIDCConnectorSpec) DeepCopyInto(out *TeleportOIDCConnectorSp } } +// Custom json.Marshaller and json.Unmarshaler are here to cope with inconsistencies between our CRD and go types. +// They are invoked when the kubernetes client converts the unstructured object into a typed resource. +// We have two inconsistencies: +// - the utils.Strings typr that marshals inconsistently: single elements are strings, multiple elements are lists +// - the max_age setting which is an embedded pointer to another single-value message, which breaks JSON parsing + // MarshalJSON serializes a spec into a JSON string func (spec TeleportOIDCConnectorSpec) MarshalJSON() ([]byte, error) { type Alias TeleportOIDCConnectorSpec + + var maxAge types.Duration + if spec.MaxAge != nil { + maxAge = spec.MaxAge.Value + } + return json.Marshal(&struct { - RedirectURLs []string `json:"redirect_url"` + RedirectURLs []string `json:"redirect_url,omitempty"` + MaxAge types.Duration `json:"max_age,omitempty"` Alias }{ RedirectURLs: spec.RedirectURLs, + MaxAge: maxAge, Alias: (Alias)(spec), }) } + +// UnmarshalJSON serializes a JSON string into a spec. This override is required to deal with the +// MaxAge field which is special case because it' an object embedded into the spec. +func (spec *TeleportOIDCConnectorSpec) UnmarshalJSON(data []byte) error { + *spec = *new(TeleportOIDCConnectorSpec) + type Alias TeleportOIDCConnectorSpec + + temp := &struct { + MaxAge types.Duration `json:"max_age"` + *Alias + }{ + Alias: (*Alias)(spec), + } + if err := json.Unmarshal(data, &temp); err != nil { + return trace.Wrap(err, "unmarshalling custom teleport oidc connector spec") + } + if temp.MaxAge != 0 { + spec.MaxAge = &types.MaxAge{Value: temp.MaxAge} + } + return nil +} diff --git a/integrations/operator/apis/resources/v3/oidcconnector_types_test.go b/integrations/operator/apis/resources/v3/oidcconnector_types_test.go index fd6146385d301..7f380c038da0d 100644 --- a/integrations/operator/apis/resources/v3/oidcconnector_types_test.go +++ b/integrations/operator/apis/resources/v3/oidcconnector_types_test.go @@ -19,9 +19,11 @@ package v3 import ( "encoding/json" "testing" + "time" "github.com/stretchr/testify/require" + "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/wrappers" ) @@ -48,6 +50,11 @@ func TestTeleportOIDCConnectorSpec_MarshalJSON(t *testing.T) { TeleportOIDCConnectorSpec{RedirectURLs: wrappers.Strings{"foo", "bar"}}, `{"redirect_url":["foo","bar"],"issuer_url":"","client_id":"","client_secret":""}`, }, + { + "MaxAge", + TeleportOIDCConnectorSpec{MaxAge: &types.MaxAge{Value: types.Duration(time.Hour)}}, + `{"max_age":"1h0m0s","issuer_url":"","client_id":"","client_secret":""}`, + }, } for _, tc := range tests { tc := tc @@ -58,3 +65,39 @@ func TestTeleportOIDCConnectorSpec_MarshalJSON(t *testing.T) { }) } } +func TestTeleportOIDCConnectorSpec_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + expectedSpec TeleportOIDCConnectorSpec + inputJSON string + }{ + { + "Empty string", + TeleportOIDCConnectorSpec{RedirectURLs: wrappers.Strings{""}}, + `{"redirect_url":[""],"issuer_url":"","client_id":"","client_secret":""}`, + }, + { + "Single string", + TeleportOIDCConnectorSpec{RedirectURLs: wrappers.Strings{"foo"}}, + `{"redirect_url":["foo"],"issuer_url":"","client_id":"","client_secret":""}`, + }, + { + "Multiple strings", + TeleportOIDCConnectorSpec{RedirectURLs: wrappers.Strings{"foo", "bar"}}, + `{"redirect_url":["foo","bar"],"issuer_url":"","client_id":"","client_secret":""}`, + }, + { + "MaxAge", + TeleportOIDCConnectorSpec{MaxAge: &types.MaxAge{Value: types.Duration(time.Hour)}}, + `{"max_age":"1h0m0s","issuer_url":"","client_id":"","client_secret":""}`, + }, + } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + var spec TeleportOIDCConnectorSpec + require.NoError(t, json.Unmarshal([]byte(tc.inputJSON), &spec)) + require.Equal(t, tc.expectedSpec, spec) + }) + } +} diff --git a/integrations/operator/controllers/resources/oidc_connector_controller_test.go b/integrations/operator/controllers/resources/oidc_connector_controller_test.go index 910abf8bcbbbb..2cf98152cc554 100644 --- a/integrations/operator/controllers/resources/oidc_connector_controller_test.go +++ b/integrations/operator/controllers/resources/oidc_connector_controller_test.go @@ -19,6 +19,7 @@ package resources_test import ( "context" "testing" + "time" "github.com/google/go-cmp/cmp" "github.com/gravitational/trace" @@ -40,6 +41,7 @@ var oidcSpec = types.OIDCConnectorSpecV3{ Roles: []string{"roleA"}, }}, RedirectURLs: []string{"https://redirect"}, + MaxAge: &types.MaxAge{Value: types.Duration(time.Hour)}, } type oidcTestingPrimitives struct {