/* Copyright 2015-2021 Gravitational, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package auth import ( "context" "strings" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh" "golang.org/x/exp/slices" kyaml "k8s.io/apimachinery/pkg/util/yaml" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/types" apisshutils "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/teleport/lib/auth/keygen" "github.com/gravitational/teleport/lib/auth/keystore" "github.com/gravitational/teleport/lib/auth/testauthority" "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/lite" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/services/suite" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/proxy" ) // TestReadIdentity makes parses identity from private key and certificate // and checks that all parameters are valid func TestReadIdentity(t *testing.T) { clock := clockwork.NewFakeClock() a := testauthority.NewWithClock(clock) priv, pub, err := a.GenerateKeyPair() require.NoError(t, err) caSigner, err := ssh.ParsePrivateKey(priv) require.NoError(t, err) cert, err := a.GenerateHostCert(services.HostCertParams{ CASigner: caSigner, PublicHostKey: pub, HostID: "id1", NodeName: "node-name", ClusterName: "example.com", Role: types.RoleNode, TTL: 0, }) require.NoError(t, err) id, err := ReadSSHIdentityFromKeyPair(priv, cert) require.NoError(t, err) require.Equal(t, id.ClusterName, "example.com") require.Equal(t, id.ID, IdentityID{HostUUID: "id1.example.com", Role: types.RoleNode}) require.Equal(t, id.CertBytes, cert) require.Equal(t, id.KeyBytes, priv) // test TTL by converting the generated cert to text -> back and making sure ExpireAfter is valid ttl := 10 * time.Second expiryDate := clock.Now().Add(ttl) bytes, err := a.GenerateHostCert(services.HostCertParams{ CASigner: caSigner, PublicHostKey: pub, HostID: "id1", NodeName: "node-name", ClusterName: "example.com", Role: types.RoleNode, TTL: ttl, }) require.NoError(t, err) copy, err := apisshutils.ParseCertificate(bytes) require.NoError(t, err) require.Equal(t, uint64(expiryDate.Unix()), copy.ValidBefore) } func TestBadIdentity(t *testing.T) { a := testauthority.New() priv, pub, err := a.GenerateKeyPair() require.NoError(t, err) caSigner, err := ssh.ParsePrivateKey(priv) require.NoError(t, err) // bad cert type _, err = ReadSSHIdentityFromKeyPair(priv, pub) require.IsType(t, trace.BadParameter(""), err) // missing authority domain cert, err := a.GenerateHostCert(services.HostCertParams{ CASigner: caSigner, PublicHostKey: pub, HostID: "id2", NodeName: "", ClusterName: "", Role: types.RoleNode, TTL: 0, }) require.NoError(t, err) _, err = ReadSSHIdentityFromKeyPair(priv, cert) require.IsType(t, trace.BadParameter(""), err) // missing host uuid cert, err = a.GenerateHostCert(services.HostCertParams{ CASigner: caSigner, PublicHostKey: pub, HostID: "example.com", NodeName: "", ClusterName: "", Role: types.RoleNode, TTL: 0, }) require.NoError(t, err) _, err = ReadSSHIdentityFromKeyPair(priv, cert) require.IsType(t, trace.BadParameter(""), err) // unrecognized role cert, err = a.GenerateHostCert(services.HostCertParams{ CASigner: caSigner, PublicHostKey: pub, HostID: "example.com", NodeName: "", ClusterName: "id1", Role: "bad role", TTL: 0, }) require.NoError(t, err) _, err = ReadSSHIdentityFromKeyPair(priv, cert) require.IsType(t, trace.BadParameter(""), err) } type testDynamicallyConfigurableParams struct { withDefaults, withConfigFile, withAnotherConfigFile func(*testing.T, *InitConfig) types.ResourceWithOrigin setDynamic func(*testing.T, *Server) getStored func(*testing.T, *Server) types.ResourceWithOrigin } func testDynamicallyConfigurable(t *testing.T, p testDynamicallyConfigurableParams) { initAuthServer := func(t *testing.T, conf InitConfig) *Server { authServer, err := Init(conf) require.NoError(t, err) t.Cleanup(func() { authServer.Close() }) return authServer } t.Run("start with config file, reinit with defaults", func(t *testing.T) { t.Parallel() conf := setupConfig(t) // Simulate a server with a config-file resource. configFileRes := p.withConfigFile(t, &conf) authServer := initAuthServer(t, conf) stored := p.getStored(t, authServer) require.Equal(t, types.OriginConfigFile, stored.Origin()) require.Empty(t, resourceDiff(configFileRes, stored)) // Reinitialize with the default resource. defaultRes := p.withDefaults(t, &conf) authServer = initAuthServer(t, conf) // Verify the stored resource is now labeled as originating from defaults. stored = p.getStored(t, authServer) require.Equal(t, types.OriginDefaults, stored.Origin()) require.Empty(t, resourceDiff(defaultRes, stored)) }) t.Run("start with dynamic, reinit with defaults", func(t *testing.T) { t.Parallel() conf := setupConfig(t) // Simulate a server with dynamic configuration. authServer := initAuthServer(t, conf) p.setDynamic(t, authServer) dynamic := p.getStored(t, authServer) require.Equal(t, types.OriginDynamic, dynamic.Origin()) // Attempt to reinitialize with the default resource should be a no-op. p.withDefaults(t, &conf) authServer = initAuthServer(t, conf) // Verify the stored resource remains unchanged. stored := p.getStored(t, authServer) require.Equal(t, types.OriginDynamic, stored.Origin()) require.Empty(t, resourceDiff(dynamic, stored)) }) t.Run("start with dynamic, reinit with config file", func(t *testing.T) { t.Parallel() conf := setupConfig(t) // Simulate a server with dynamic configuration. authServer := initAuthServer(t, conf) p.setDynamic(t, authServer) dynamic := p.getStored(t, authServer) require.Equal(t, types.OriginDynamic, dynamic.Origin()) // Reinitialize with a config-file resource. configFileRes := p.withConfigFile(t, &conf) authServer = initAuthServer(t, conf) // Verify the stored resource is updated. stored := p.getStored(t, authServer) require.Equal(t, types.OriginConfigFile, stored.Origin()) require.Empty(t, resourceDiff(configFileRes, stored)) }) t.Run("start with defaults, reinit with config file", func(t *testing.T) { t.Parallel() conf := setupConfig(t) // Simulate a server with the default resource. defaultRes := p.withDefaults(t, &conf) authServer := initAuthServer(t, conf) stored := p.getStored(t, authServer) require.Equal(t, types.OriginDefaults, stored.Origin()) require.Empty(t, resourceDiff(defaultRes, stored)) // Reinitialize with a config-file resource. configFileRes := p.withConfigFile(t, &conf) authServer = initAuthServer(t, conf) // Verify the stored resource is updated. stored = p.getStored(t, authServer) require.Equal(t, types.OriginConfigFile, stored.Origin()) require.Empty(t, resourceDiff(configFileRes, stored)) }) t.Run("start with config file, reinit with another config file", func(t *testing.T) { t.Parallel() conf := setupConfig(t) // Simulate a server with a config-file resource. configFileRes := p.withConfigFile(t, &conf) authServer := initAuthServer(t, conf) stored := p.getStored(t, authServer) require.Equal(t, types.OriginConfigFile, stored.Origin()) require.Empty(t, resourceDiff(configFileRes, stored)) // Reinitialize with another config-file resource. anotherConfigFileRes := p.withAnotherConfigFile(t, &conf) authServer = initAuthServer(t, conf) // Verify the stored resource is updated. stored = p.getStored(t, authServer) require.Equal(t, types.OriginConfigFile, stored.Origin()) require.Empty(t, resourceDiff(anotherConfigFileRes, stored)) }) } func TestAuthPreference(t *testing.T) { t.Parallel() ctx := context.Background() testDynamicallyConfigurable(t, testDynamicallyConfigurableParams{ withDefaults: func(t *testing.T, conf *InitConfig) types.ResourceWithOrigin { conf.AuthPreference = types.DefaultAuthPreference() return conf.AuthPreference }, withConfigFile: func(t *testing.T, conf *InitConfig) types.ResourceWithOrigin { fromConfigFile, err := types.NewAuthPreferenceFromConfigFile(types.AuthPreferenceSpecV2{ Type: constants.OIDC, }) require.NoError(t, err) conf.AuthPreference = fromConfigFile return conf.AuthPreference }, withAnotherConfigFile: func(t *testing.T, conf *InitConfig) types.ResourceWithOrigin { conf.AuthPreference = newWebauthnAuthPreferenceConfigFromFile(t) return conf.AuthPreference }, setDynamic: func(t *testing.T, authServer *Server) { dynamically, err := types.NewAuthPreference(types.AuthPreferenceSpecV2{ SecondFactor: constants.SecondFactorOff, }) require.NoError(t, err) err = authServer.SetAuthPreference(ctx, dynamically) require.NoError(t, err) }, getStored: func(t *testing.T, authServer *Server) types.ResourceWithOrigin { authPref, err := authServer.GetAuthPreference(ctx) require.NoError(t, err) return authPref }, }) } func TestClusterNetworkingConfig(t *testing.T) { t.Parallel() ctx := context.Background() testDynamicallyConfigurable(t, testDynamicallyConfigurableParams{ withDefaults: func(t *testing.T, conf *InitConfig) types.ResourceWithOrigin { conf.ClusterNetworkingConfig = types.DefaultClusterNetworkingConfig() return conf.ClusterNetworkingConfig }, withConfigFile: func(t *testing.T, conf *InitConfig) types.ResourceWithOrigin { fromConfigFile, err := types.NewClusterNetworkingConfigFromConfigFile(types.ClusterNetworkingConfigSpecV2{ ClientIdleTimeout: types.Duration(7 * time.Minute), }) require.NoError(t, err) conf.ClusterNetworkingConfig = fromConfigFile return conf.ClusterNetworkingConfig }, withAnotherConfigFile: func(t *testing.T, conf *InitConfig) types.ResourceWithOrigin { anotherFromConfigFile, err := types.NewClusterNetworkingConfigFromConfigFile(types.ClusterNetworkingConfigSpecV2{ ClientIdleTimeout: types.Duration(10 * time.Minute), KeepAliveInterval: types.Duration(3 * time.Minute), }) require.NoError(t, err) conf.ClusterNetworkingConfig = anotherFromConfigFile return conf.ClusterNetworkingConfig }, setDynamic: func(t *testing.T, authServer *Server) { dynamically, err := types.NewClusterNetworkingConfigFromConfigFile(types.ClusterNetworkingConfigSpecV2{ KeepAliveInterval: types.Duration(4 * time.Minute), }) require.NoError(t, err) dynamically.SetOrigin(types.OriginDynamic) err = authServer.SetClusterNetworkingConfig(ctx, dynamically) require.NoError(t, err) }, getStored: func(t *testing.T, authServer *Server) types.ResourceWithOrigin { authPref, err := authServer.GetClusterNetworkingConfig(ctx) require.NoError(t, err) return authPref }, }) } func TestSessionRecordingConfig(t *testing.T) { t.Parallel() ctx := context.Background() testDynamicallyConfigurable(t, testDynamicallyConfigurableParams{ withDefaults: func(t *testing.T, conf *InitConfig) types.ResourceWithOrigin { conf.SessionRecordingConfig = types.DefaultSessionRecordingConfig() return conf.SessionRecordingConfig }, withConfigFile: func(t *testing.T, conf *InitConfig) types.ResourceWithOrigin { fromConfigFile, err := types.NewSessionRecordingConfigFromConfigFile(types.SessionRecordingConfigSpecV2{ Mode: types.RecordOff, }) require.NoError(t, err) conf.SessionRecordingConfig = fromConfigFile return conf.SessionRecordingConfig }, withAnotherConfigFile: func(t *testing.T, conf *InitConfig) types.ResourceWithOrigin { anotherFromConfigFile, err := types.NewSessionRecordingConfigFromConfigFile(types.SessionRecordingConfigSpecV2{ Mode: types.RecordAtProxySync, }) require.NoError(t, err) conf.SessionRecordingConfig = anotherFromConfigFile return conf.SessionRecordingConfig }, setDynamic: func(t *testing.T, authServer *Server) { dynamically, err := types.NewSessionRecordingConfigFromConfigFile(types.SessionRecordingConfigSpecV2{ Mode: types.RecordAtNodeSync, }) require.NoError(t, err) dynamically.SetOrigin(types.OriginDynamic) err = authServer.SetSessionRecordingConfig(ctx, dynamically) require.NoError(t, err) }, getStored: func(t *testing.T, authServer *Server) types.ResourceWithOrigin { authPref, err := authServer.GetSessionRecordingConfig(ctx) require.NoError(t, err) return authPref }, }) } func TestClusterID(t *testing.T) { conf := setupConfig(t) authServer, err := Init(conf) require.NoError(t, err) defer authServer.Close() cc, err := authServer.GetClusterName() require.NoError(t, err) clusterID := cc.GetClusterID() require.NotEqual(t, clusterID, "") // do it again and make sure cluster ID hasn't changed authServer, err = Init(conf) require.NoError(t, err) defer authServer.Close() cc, err = authServer.GetClusterName() require.NoError(t, err) require.Equal(t, cc.GetClusterID(), clusterID) } // TestClusterName ensures that a cluster can not be renamed. func TestClusterName(t *testing.T) { conf := setupConfig(t) authServer, err := Init(conf) require.NoError(t, err) defer authServer.Close() // Start the auth server with a different cluster name. The auth server // should start, but with the original name. newConfig := conf newConfig.ClusterName, err = services.NewClusterNameWithRandomID(types.ClusterNameSpecV2{ ClusterName: "dev.localhost", }) require.NoError(t, err) authServer, err = Init(newConfig) require.NoError(t, err) defer authServer.Close() cn, err := authServer.GetClusterName() require.NoError(t, err) require.NotEqual(t, newConfig.ClusterName.GetClusterName(), cn.GetClusterName()) require.Equal(t, conf.ClusterName.GetClusterName(), cn.GetClusterName()) } // TestPresets tests behavior of presets func TestPresets(t *testing.T) { ctx := context.Background() roles := []types.Role{ services.NewPresetEditorRole(), services.NewPresetAccessRole(), services.NewPresetAuditorRole(), } t.Run("EmptyCluster", func(t *testing.T) { as := newTestAuthServer(ctx, t) clock := clockwork.NewFakeClock() as.SetClock(clock) err := createPresets(ctx, as) require.NoError(t, err) // Second call should not fail err = createPresets(ctx, as) require.NoError(t, err) // Presets were created for _, role := range roles { _, err := as.GetRole(ctx, role.GetName()) require.NoError(t, err) } }) // Makes sure that existing role with the same name is not modified t.Run("ExistingRole", func(t *testing.T) { as := newTestAuthServer(ctx, t) clock := clockwork.NewFakeClock() as.SetClock(clock) access := services.NewPresetEditorRole() access.SetLogins(types.Allow, []string{"root"}) err := as.CreateRole(ctx, access) require.NoError(t, err) err = createPresets(ctx, as) require.NoError(t, err) // Presets were created for _, role := range roles { _, err := as.GetRole(ctx, role.GetName()) require.NoError(t, err) } out, err := as.GetRole(ctx, access.GetName()) require.NoError(t, err) require.Equal(t, access.GetLogins(types.Allow), out.GetLogins(types.Allow)) }) // If a default allow condition is not present, ensure it gets added. t.Run("AddDefaultAllowConditions", func(t *testing.T) { as := newTestAuthServer(ctx, t) clock := clockwork.NewFakeClock() as.SetClock(clock) editorRole := services.NewPresetEditorRole() rules := editorRole.GetRules(types.Allow) // Create a new set of rules based on the Editor Role, excluding the ConnectioDiagnostic. // ConnectionDiagnostic is part of the default allow rules outdatedRules := []types.Rule{} for _, r := range rules { if slices.Contains(r.Resources, types.KindConnectionDiagnostic) { continue } outdatedRules = append(outdatedRules, r) } editorRole.SetRules(types.Allow, outdatedRules) err := as.CreateRole(ctx, editorRole) require.NoError(t, err) // Set up an old Access Role. // Remove the new DatabaseServiceLabels default accessRole := services.NewPresetAccessRole() accessRole.SetDatabaseServiceLabels(types.Allow, types.Labels{}) err = as.CreateRole(ctx, accessRole) require.NoError(t, err) err = createPresets(ctx, as) require.NoError(t, err) outEditor, err := as.GetRole(ctx, editorRole.GetName()) require.NoError(t, err) allowRules := outEditor.GetRules(types.Allow) require.Condition(t, func() (success bool) { for _, r := range allowRules { if slices.Contains(r.Resources, types.KindConnectionDiagnostic) { return true } } return false }, "missing default rule") outAccess, err := as.GetRole(ctx, accessRole.GetName()) require.NoError(t, err) allowedDatabaseServiceLabels := outAccess.GetDatabaseServiceLabels(types.Allow) require.Equal(t, types.Labels{types.Wildcard: []string{types.Wildcard}}, allowedDatabaseServiceLabels, "missing default DatabaseServiceLabels") }) // Don't set a default allow rule if the resource is present in the role. // Either as part of allowing or denying rules. t.Run("DefaultAllowRulesNotAppliedIfExplicitlyDefined", func(t *testing.T) { as := newTestAuthServer(ctx, t) clock := clockwork.NewFakeClock() as.SetClock(clock) // Set up a changed Editor Role editorRole := services.NewPresetEditorRole() allowRules := editorRole.GetRules(types.Allow) // Create a new set of rules based on the Editor Role, // setting a deny rule for a default allow rule outdateAllowRules := []types.Rule{} for _, r := range allowRules { if slices.Contains(r.Resources, types.KindConnectionDiagnostic) { continue } outdateAllowRules = append(outdateAllowRules, r) } editorRole.SetRules(types.Allow, outdateAllowRules) // Explicitly deny Create to ConnectionDiagnostic denyRules := editorRole.GetRules(types.Deny) denyConnectionDiagnosticRule := types.NewRule(types.KindConnectionDiagnostic, []string{types.VerbCreate}) denyRules = append(denyRules, denyConnectionDiagnosticRule) editorRole.SetRules(types.Deny, denyRules) err := as.CreateRole(ctx, editorRole) require.NoError(t, err) // Set up a changed Access Role accessRole := services.NewPresetAccessRole() // Remove a default allow label as well. accessRole.SetDatabaseServiceLabels(types.Allow, types.Labels{}) // Explicitly deny DatabaseServiceLabels accessRole.SetDatabaseServiceLabels(types.Deny, types.Labels{types.Wildcard: []string{types.Wildcard}}) err = as.CreateRole(ctx, accessRole) require.NoError(t, err) // Apply defaults. err = createPresets(ctx, as) require.NoError(t, err) outEditor, err := as.GetRole(ctx, editorRole.GetName()) require.NoError(t, err) allowRules = outEditor.GetRules(types.Allow) require.Condition(t, func() (success bool) { for _, r := range allowRules { if slices.Contains(r.Resources, types.KindConnectionDiagnostic) { return false } } return true }, "missing default rule") outAccess, err := as.GetRole(ctx, accessRole.GetName()) require.NoError(t, err) allowedDatabaseServiceLabels := outAccess.GetDatabaseServiceLabels(types.Allow) require.Nil(t, allowedDatabaseServiceLabels, "does not set Allowed DatabaseService Labels") deniedDatabaseServiceLabels := outAccess.GetDatabaseServiceLabels(types.Deny) require.Equal(t, types.Labels{types.Wildcard: []string{types.Wildcard}}, deniedDatabaseServiceLabels, "keeps the deny label for DatabaseService") }) t.Run("Does not upsert roles if nothing changes", func(t *testing.T) { presetRoleCount := 3 roleManager := &mockRoleManager{ roles: make(map[string]types.Role, presetRoleCount), } err := createPresets(ctx, roleManager) require.NoError(t, err) require.Equal(t, 0, roleManager.upsertRoleCallsCount, "unexpectd call to UpsertRole") require.Equal(t, 0, roleManager.getRoleCallsCount, "unexpectd call to GetRole") require.Equal(t, presetRoleCount, roleManager.createRoleCallsCount, "unexpected number of calls to CreateRole, got %d calls", roleManager.createRoleCallsCount) // Running a second time should return Already Exists, so it fetches the role. // The role was not changed, so it can't call the UpsertRole method. roleManager.ResetCallCounters() err = createPresets(ctx, roleManager) require.NoError(t, err) require.Equal(t, 0, roleManager.upsertRoleCallsCount, "unexpectd call to UpsertRole") require.Equal(t, presetRoleCount, roleManager.getRoleCallsCount, "unexpected number of calls to CreateRole, got %d calls", roleManager.getRoleCallsCount) require.Equal(t, presetRoleCount, roleManager.createRoleCallsCount, "unexpected number of calls to CreateRole, got %d calls", roleManager.createRoleCallsCount) // Removing a specific resource which is part of the Default Allow Rules should trigger an UpsertRole call editorRole := roleManager.roles[teleport.PresetEditorRoleName] allowRulesWithoutConnectionDiag := []types.Rule{} for _, r := range editorRole.GetRules(types.Allow) { if slices.Contains(r.Resources, types.KindConnectionDiagnostic) { continue } allowRulesWithoutConnectionDiag = append(allowRulesWithoutConnectionDiag, r) } editorRole.SetRules(types.Allow, allowRulesWithoutConnectionDiag) roleManager.UpsertRole(ctx, editorRole) roleManager.ResetCallCounters() err = createPresets(ctx, roleManager) require.NoError(t, err) require.Equal(t, 1, roleManager.upsertRoleCallsCount, "unexpectd call to UpsertRole") require.Equal(t, presetRoleCount, roleManager.getRoleCallsCount, "unexpected number of calls to CreateRole, got %d calls", roleManager.getRoleCallsCount) require.Equal(t, presetRoleCount, roleManager.createRoleCallsCount, "unexpected number of calls to CreateRole, got %d calls", roleManager.createRoleCallsCount) }) } type mockRoleManager struct { roles map[string]types.Role getRoleCallsCount int createRoleCallsCount int upsertRoleCallsCount int } // ResetCallCounters resets the method call counters. func (m *mockRoleManager) ResetCallCounters() { m.getRoleCallsCount = 0 m.createRoleCallsCount = 0 m.upsertRoleCallsCount = 0 } // GetRole returns role by name. func (m *mockRoleManager) GetRole(ctx context.Context, name string) (types.Role, error) { m.getRoleCallsCount = m.getRoleCallsCount + 1 role, ok := m.roles[name] if !ok { return nil, trace.NotFound("role not found") } return role, nil } // CreateRole creates a role. func (m *mockRoleManager) CreateRole(ctx context.Context, role types.Role) error { m.createRoleCallsCount = m.createRoleCallsCount + 1 _, ok := m.roles[role.GetName()] if ok { return trace.AlreadyExists("role not found") } m.roles[role.GetName()] = role return nil } // UpsertRole creates or updates a role and emits a related audit event. func (m *mockRoleManager) UpsertRole(ctx context.Context, role types.Role) error { m.upsertRoleCallsCount = m.upsertRoleCallsCount + 1 m.roles[role.GetName()] = role return nil } func setupConfig(t *testing.T) InitConfig { tempDir := t.TempDir() bk, err := lite.New(context.TODO(), backend.Params{"path": tempDir}) require.NoError(t, err) clusterName, err := services.NewClusterNameWithRandomID(types.ClusterNameSpecV2{ ClusterName: "me.localhost", }) require.NoError(t, err) return InitConfig{ DataDir: tempDir, HostUUID: "00000000-0000-0000-0000-000000000000", NodeName: "foo", Backend: bk, Authority: testauthority.New(), ClusterAuditConfig: types.DefaultClusterAuditConfig(), ClusterNetworkingConfig: types.DefaultClusterNetworkingConfig(), SessionRecordingConfig: types.DefaultSessionRecordingConfig(), ClusterName: clusterName, StaticTokens: types.DefaultStaticTokens(), AuthPreference: types.DefaultAuthPreference(), SkipPeriodicOperations: true, KeyStoreConfig: keystore.Config{ Software: keystore.SoftwareConfig{ RSAKeyPairSource: testauthority.New().GenerateKeyPair, }, }, } } func newWebauthnAuthPreferenceConfigFromFile(t *testing.T) types.AuthPreference { ap, err := types.NewAuthPreferenceFromConfigFile(types.AuthPreferenceSpecV2{ Type: constants.Local, SecondFactor: constants.SecondFactorWebauthn, Webauthn: &types.Webauthn{ RPID: "localhost", }, }) require.NoError(t, err) return ap } // Example resources generated using `tctl get all --with-secrets`. const ( hostCAYAML = `kind: cert_authority metadata: id: 1630515580178991000 name: me.localhost spec: active_keys: ssh: - private_key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBMGpYWHA5SmM3amFVVThydC9NUXZyZjY3TXZoZnk3ZG5ldHoyenBKWFFNNmJQUG5PCjdQVGdIV1o1TFFndVVJTldXLzN6ci9xa2V6UHhmU0lXaUVlcDlRTy9HSytBWHVNWTdmUThNTEc2cS93ckM3UU8KeWxaY3htWGZuS2ZXSkV5RUdralA5VlZmakd3UEdxRUNGWmZtT1pJdXFMVWVadEJKVzduZTZWbkYvWnYzbUhBZwpkc1FFRHphTjdlQnN2NHJyOVhQeHI4anh4Tzg5TjZZN0lsRStoUXNiT3FNSVVsSUVvSk0xYjBOcVFldlhHLzhVClBWWXJLK21hR2dNclNqZHppTjl6VHUzRzNCKzJ3SkJNZzd3VitMQjlJRUU0Y2NHYVVDbUM0QzBuZlByM283emgKTE1UNTZpdGZLdGw0TlkrN1NjRHVSSkN2TkFra3IrNkZtVUc5NXdJREFRQUJBb0lCQVFEUUVJTVlsVnR1WFkrTApNTDFIQjFpNk8veEdneGt1cHFaQ01od0ljMGp4Mkk1SFdHdThsdFNOeFRRRG9xbFUvK3FtdTBKTTJTV2MzTmtXCkpudHZBSi8wNkhScGxxelZQcXNhUERpbmFnTiszK1lyZTFsNFpPc0haU1prQkt3czJaK1g5S0lDRHpLMzV1MDgKU2ttcDNlUCs3L1pHL3A3TTNUVC9HWWJPS2hHUzV0VXdTY291NXV3aFMxQjFJOFNQQW05UGdsMmkyTjVib2s2YgpQTHBqT0xwOUZPVmpVMlNXYUhVa0ZiUUc2TENzOGlJdVdlVzdvMEtkY3B1QndXeGQxd0p3bm5nbjlJVzJ0aUdyCnZLTGlsYzJLR2xITnJxZldiRWw4Z2N5eXBKeUNqZEIvVXkvOFFBOG9uOHBnK1hldThrcnFyRjNTZm5iZHNOaDQKVmNKV09zTUJBb0dCQVBvY1E1ZWd0enZSL1djR0ZtbUxNbTUxTEVDMytPOWxVSUMwMGZ0Q1lkYW5vK0NGUTdEbgpQd0IyLzUyZWZMeUhLUlpjc0lrd2IreXZjYXp3eG9BWlNzMnRlV1FqRUJoOXdRbGlzWlNacVJIVEVMbnY0WjdvCkFMM3JmUjNtM3FHR0tmMm9UQnQ3dEVCcEMwZEVITDlxOVRpbkJJVDVKWGVSSVNzZUcyOGFKUG9IQW9HQkFOY3AKREtjc2hVKzJnaSs2V3p4eThOU3gyMkV5Y29FdUFKbWlSdm1jcExNNE9OTW03dUlQQ2RDYWYyY0UxYzJjVUg5OAppRGw2ZzBYN1ViUjQ4NzdJeVluMi9TU2hiOG56R3ZBNmVBbGJYZk9PYlYvTkIrczUxQzduTGFVSUE1ZEkyTE1WClRJL2xRTnV4cDhieXZDNjU3UVFQZk9jVXlGMVZIVVo5VzNPbU1hVWhBb0dCQU5RREZZRDQ2Wm81M1RaeHdKbmoKTnZMUFBKMzMxWHNKUlA1MVNQSldTUjF1cWNudTdYeU42YWY1TjZGaThaWFdkUXZSc292NGxVZnJTTTh5b3ZGLwpmeHR1aTlKSXJxSTBKMmhQVXYwR2JIMEJqOUl0OS9GOTlQTUpKZHd0RWxlVnBRNnlsU0ZPOFhNUUdGRm0rWCtCCnFURkcwdHZ0WHNkR0xQbWg0ZHVDTEFvTkFvR0FLSS9aamM2TDEwbzk0c2VNR2FwRmtxTnhDekxhZVZYMTBRRFIKeG83c1Vja2dsVlg2cE8xVzJWZTIrdkhqYUo2MllrSlU0QmtqbEZiYndWMG4vbWlWN2dkOUU2SEhsRmZiVlR5QQprcXNCM0QrV2lQLzdKVEpDdVJEbC92Mnl4NXQ1RnRIR0hENkk2cUhrVWxKQ2ZjQ1pXVEdlUjJZWW05Zkc3Qm9ICjJwYVROMkVDZ1lFQXRUQ2o1M253M213RDE1ZjAyZ2pKcWNjeHBkRk1lYlJpNUltMXJ5RUFaSzhwTmNlL2xNclgKVXM1KzM2eFpHWG1CR1ByMVl0UmxIVThQL1owWFdJM3ZDOFpLRHB6SnB4dnB6QmRFV0pUZGlPZEhpV2J4TzZOOQpiYjh3ZUM4STl4ZDlwd0cyMHdYNUVnR0krekdjMlNGa1VDTWROd1JkV3NSTWNCMklMeVV0MmZzPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= public_key: c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFEU05kZW4wbHp1TnBSVHl1Mzh4Qyt0L3JzeStGL0x0MmQ2M1BiT2tsZEF6cHM4K2M3czlPQWRabmt0Q0M1UWcxWmIvZk92K3FSN00vRjlJaGFJUjZuMUE3OFlyNEJlNHhqdDlEd3dzYnFyL0NzTHRBN0tWbHpHWmQrY3A5WWtUSVFhU00vMVZWK01iQThhb1FJVmwrWTVraTZvdFI1bTBFbGJ1ZDdwV2NYOW0vZVljQ0IyeEFRUE5vM3Q0R3kvaXV2MWMvR3Z5UEhFN3owM3Bqc2lVVDZGQ3hzNm93aFNVZ1Nna3pWdlEycEI2OWNiL3hROVZpc3I2Wm9hQXl0S04zT0kzM05PN2NiY0g3YkFrRXlEdkJYNHNIMGdRVGh4d1pwUUtZTGdMU2Q4K3ZlanZPRXN4UG5xSzE4cTJYZzFqN3RKd081RWtLODBDU1N2N29XWlFiM24K tls: - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURlVENDQW1HZ0F3SUJBZ0lSQUlvWW1QRUhNV0wwZmp6VkZVZ2l2Ykl3RFFZSktvWklodmNOQVFFTEJRQXcKVmpFUU1BNEdBMVVFQ2hNSGVtRnljWFZ2YmpFUU1BNEdBMVVFQXhNSGVtRnljWFZ2YmpFd01DNEdBMVVFQlJNbgpNVGd6TlRZeE1UZ3dOVFkxTXprMk1qUTNNRFV4TXpRM05qa3pOREV5TmpNME56Y3dPRFkyTUI0WERUSXhNRGt3Ck1URTJOVGswTUZvWERUTXhNRGd6TURFMk5UazBNRm93VmpFUU1BNEdBMVVFQ2hNSGVtRnljWFZ2YmpFUU1BNEcKQTFVRUF4TUhlbUZ5Y1hWdmJqRXdNQzRHQTFVRUJSTW5NVGd6TlRZeE1UZ3dOVFkxTXprMk1qUTNNRFV4TXpRMwpOamt6TkRFeU5qTTBOemN3T0RZMk1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBCjlzNGgrUTlpKzBlQ0c0VWNtSThFbEg1MHBlWmFkV2JzdzZDUStCZmF0em1NQWJEa016WGpSTHlNZy8xNFdSRGsKWTU4OUpCWVgrTzRBYXBRNno5MFRhTzhHamN1RmxvcWlFcTZOci9VTjJMYnc2am9KdTYvQ0dmcWIzNXZKT1NDdApKMWp5am5Cc2ZVNTRzRGFGcWpPRG1BL2l3YjNsSlZSV1pmYUdQSGZtRTRUcHJCSzdXV2FHbmlDZktUdGQza3lHCnkzWXpGZEJzSDU0OU9Lc1BFUlJOdTVCdlpzcmZWazRDdnlKNWVxREE1NlBaU1pmSVptZEk4VUdsRmk0V3lhMHEKUHRtWmg4bURGSnpVNnNXZWY5bTRqM3grMmF1UFEra2M3cjJneFdLZ2lTR1FBT2hLU0VhaEZDUW9UU3NzemVReApoZG9xamZUK1NsUWlQQlhJbDhTbzlRSURBUUFCbzBJd1FEQU9CZ05WSFE4QkFmOEVCQU1DQXFRd0R3WURWUjBUCkFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVlSDJQTUhjRmwwTnkvQmozcE4xTExlSStPZTB3RFFZSktvWkkKaHZjTkFRRUxCUUFEZ2dFQkFMeU5aRXByOWtWbk5ORERiWWxWd0M1bnRlUEU2Z0ZQdGRjU3JjYmcyU01temtMMgppSjhCQnJ1ZVBmTllrMVE0c2xlN0FDeTRQL1dzU2xYZThqQkY0bm5KaUkxM2kvaUtQOEJzNVVIQWwwUTJ6RnlMCmp0Sk9JRlFuT1hiUmxQU1RQY21jcWdVUUcrL3lGOGhQVndHNnRwOVZoeXlCVkdlMDRlWU44eVNnbkZKTUloNGcKeDEzcytTWjE1RFkzSERnTGNCSjA5dXA4Q3NJdGV1RG05aG1jbGJ1bUc4OVEyQ3I4T25TcmYzTDNFSVhyelZBbwp3eEUwN21IWnkxSjBvNnljdWJXOWVuS2J3bTRKRHRhaHc4QzlERitvQXJlUEdaSXJXdVZpWFZYeTV6S2NOQW9LCjVXY0Q5RHdjcTFFUnVreGNFWWlrV05BV1FFNnVaMGVYZ0pKWlJ2OD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBOXM0aCtROWkrMGVDRzRVY21JOEVsSDUwcGVaYWRXYnN3NkNRK0JmYXR6bU1BYkRrCk16WGpSTHlNZy8xNFdSRGtZNTg5SkJZWCtPNEFhcFE2ejkwVGFPOEdqY3VGbG9xaUVxNk5yL1VOMkxidzZqb0oKdTYvQ0dmcWIzNXZKT1NDdEoxanlqbkJzZlU1NHNEYUZxak9EbUEvaXdiM2xKVlJXWmZhR1BIZm1FNFRwckJLNwpXV2FHbmlDZktUdGQza3lHeTNZekZkQnNINTQ5T0tzUEVSUk51NUJ2WnNyZlZrNEN2eUo1ZXFEQTU2UFpTWmZJClptZEk4VUdsRmk0V3lhMHFQdG1aaDhtREZKelU2c1dlZjltNGozeCsyYXVQUStrYzdyMmd4V0tnaVNHUUFPaEsKU0VhaEZDUW9UU3NzemVReGhkb3FqZlQrU2xRaVBCWElsOFNvOVFJREFRQUJBb0lCQUhkODZ1TzYrRS94bWVNYQorZkkrWTVoRTlOS1JDTUNJT1I2cE1TWjczZzhSRkdDSk5LSTZkN0tDbW9FWWlWaU5uaFZCTmdldmpxR2RFS1NJCjZVUlRveDhOZ2gzS0ovM3ZWbkkzQWkvck0yMzFmQVBhWDNYM3JNQ0pIVWdRRTBiT05DYTFvSkVuaXM3TDNCQnMKQlNDVzJpSVhwcy9uMFBYV3RCR2ZYZlFPbEZ4ajdKbXdwdDlqYTYyankxa3hlTVdMemlqQUZ1QzRwRTl3WUVaVApGa0ovb2FVdzR4WXM5N0pnelRNUHFJSlUzMStNYTNoNGFPQ2lPK2RzSExhK2tRUkZIN0tHOUtRaWlRYzdrTmdGCnljN2s3L2s3UVJSbG1mZmZLN3E3YUtVNkNueHVxOG05UThob2Q5QVdjQ3ZyTm9jSmVjSzcvSzFzNlhMYXhSTFgKRGFLc0xRMENnWUVBK0lBOGNRVmcveU5xbGQ5c2lHZ0NETy9DVUNwM21uZjdxZTNQeEZIWHVSdFNzaHJheE9qVQpRVXFtbHV5VWRCZTZLQnA4ZWxoVmFlYlZsdFNNd2xyWXNpUlIxaTV0SDNnRGUybXRheE5VZ0xtTGoyZUhVWGN3Cmk1UW1yQ1AyTzY4V0xQZjg4SkkwQWtOWWx3em5aWUg2VEZKbFp0MkxoU3ZRTG1qeFArWDZTT01DZ1lFQS9rREwKNndJMjVoYU1DbTBSWitrQWM1T2FJcUx0OUNWeWNTaGdzblBjK2xBQll0ZVRkclhjVW1FaklQYW1uZ1pKZUpvSgpoUDNxaHBja1JVcWwrSldhN3lXRnJocTdyTHlpTVNBb0VwaXdQMkRUMkxROWFXR0lONjdwSU9ObWtpamU2NmNNCjBsd05hQTBtL3Q5ZWZUZ2RLOUpHYnNoaTVnRFFiTFVtMW5Gd1prY0NnWUFMenJ3UWVycnpKSkdwOFdYTXpYUmIKZlFEMG9pL3dyUWJPT2ppSEVZUjRqUzNPdkt2c2MwdXlsb04zNUdIaGFrYzBKSjRKaWl6MHpUMFUzNkNZazR4OApXbkZ4QmQrMWdSUlpSdG93bmtpRG5VMWVVUU1EQWZEU2tRV05aR0FNMGZMeHpBNit0NU8xRDlJanl6OHJlWk9WCkVNMDBxQTQ3RTZ2ZXFLbmQ2V1dORlFLQmdBa3Z6aTV2cGd3cVJHVWNDOFQxWms3R3hvcjUyQjg2T3loYmpTTGwKak5aK2pZNUV1ODlPUXVlM0dzM1dHNjhhQ3cyUWcwZUs1UzUzeDVlNVdzWGdvZmlDSXBKbjVPQVk4TU5WcGgwRgo1MWhpNTBTdFBvclFPMXZIdGlTNkVycTFQMWpFY0hJcFlWS2hKd2VPaXB0N3E1SXB4dUc1MjlqenJwUSs5MmhJCk1RZUJBb0dCQUkrNnE2OGFvdW41bE5sU3NoTVg0citoZlNtbFhXYmcvdU1OQXROdWhtZndmSE0zWmR3SU9ZWlYKRTloalFNQXh6RFduZkx1ajljRkJQM2NER0s0NytoU0U2N082eHBrYk9BYzk4dUlXZVEzb3lyWEd5akQ3TGdrYwpxajFRa2ZiZE4wMU1BdEN0MDhaSlRwZzR1OUdXUUVVQWY2ckJaKzJPN2ZVdEJuUG40TXpsCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== additional_trusted_keys: {} checking_keys: - c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFEU05kZW4wbHp1TnBSVHl1Mzh4Qyt0L3JzeStGL0x0MmQ2M1BiT2tsZEF6cHM4K2M3czlPQWRabmt0Q0M1UWcxWmIvZk92K3FSN00vRjlJaGFJUjZuMUE3OFlyNEJlNHhqdDlEd3dzYnFyL0NzTHRBN0tWbHpHWmQrY3A5WWtUSVFhU00vMVZWK01iQThhb1FJVmwrWTVraTZvdFI1bTBFbGJ1ZDdwV2NYOW0vZVljQ0IyeEFRUE5vM3Q0R3kvaXV2MWMvR3Z5UEhFN3owM3Bqc2lVVDZGQ3hzNm93aFNVZ1Nna3pWdlEycEI2OWNiL3hROVZpc3I2Wm9hQXl0S04zT0kzM05PN2NiY0g3YkFrRXlEdkJYNHNIMGdRVGh4d1pwUUtZTGdMU2Q4K3ZlanZPRXN4UG5xSzE4cTJYZzFqN3RKd081RWtLODBDU1N2N29XWlFiM24K cluster_name: me.localhost signing_alg: 3 signing_keys: - LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBMGpYWHA5SmM3amFVVThydC9NUXZyZjY3TXZoZnk3ZG5ldHoyenBKWFFNNmJQUG5PCjdQVGdIV1o1TFFndVVJTldXLzN6ci9xa2V6UHhmU0lXaUVlcDlRTy9HSytBWHVNWTdmUThNTEc2cS93ckM3UU8KeWxaY3htWGZuS2ZXSkV5RUdralA5VlZmakd3UEdxRUNGWmZtT1pJdXFMVWVadEJKVzduZTZWbkYvWnYzbUhBZwpkc1FFRHphTjdlQnN2NHJyOVhQeHI4anh4Tzg5TjZZN0lsRStoUXNiT3FNSVVsSUVvSk0xYjBOcVFldlhHLzhVClBWWXJLK21hR2dNclNqZHppTjl6VHUzRzNCKzJ3SkJNZzd3VitMQjlJRUU0Y2NHYVVDbUM0QzBuZlByM283emgKTE1UNTZpdGZLdGw0TlkrN1NjRHVSSkN2TkFra3IrNkZtVUc5NXdJREFRQUJBb0lCQVFEUUVJTVlsVnR1WFkrTApNTDFIQjFpNk8veEdneGt1cHFaQ01od0ljMGp4Mkk1SFdHdThsdFNOeFRRRG9xbFUvK3FtdTBKTTJTV2MzTmtXCkpudHZBSi8wNkhScGxxelZQcXNhUERpbmFnTiszK1lyZTFsNFpPc0haU1prQkt3czJaK1g5S0lDRHpLMzV1MDgKU2ttcDNlUCs3L1pHL3A3TTNUVC9HWWJPS2hHUzV0VXdTY291NXV3aFMxQjFJOFNQQW05UGdsMmkyTjVib2s2YgpQTHBqT0xwOUZPVmpVMlNXYUhVa0ZiUUc2TENzOGlJdVdlVzdvMEtkY3B1QndXeGQxd0p3bm5nbjlJVzJ0aUdyCnZLTGlsYzJLR2xITnJxZldiRWw4Z2N5eXBKeUNqZEIvVXkvOFFBOG9uOHBnK1hldThrcnFyRjNTZm5iZHNOaDQKVmNKV09zTUJBb0dCQVBvY1E1ZWd0enZSL1djR0ZtbUxNbTUxTEVDMytPOWxVSUMwMGZ0Q1lkYW5vK0NGUTdEbgpQd0IyLzUyZWZMeUhLUlpjc0lrd2IreXZjYXp3eG9BWlNzMnRlV1FqRUJoOXdRbGlzWlNacVJIVEVMbnY0WjdvCkFMM3JmUjNtM3FHR0tmMm9UQnQ3dEVCcEMwZEVITDlxOVRpbkJJVDVKWGVSSVNzZUcyOGFKUG9IQW9HQkFOY3AKREtjc2hVKzJnaSs2V3p4eThOU3gyMkV5Y29FdUFKbWlSdm1jcExNNE9OTW03dUlQQ2RDYWYyY0UxYzJjVUg5OAppRGw2ZzBYN1ViUjQ4NzdJeVluMi9TU2hiOG56R3ZBNmVBbGJYZk9PYlYvTkIrczUxQzduTGFVSUE1ZEkyTE1WClRJL2xRTnV4cDhieXZDNjU3UVFQZk9jVXlGMVZIVVo5VzNPbU1hVWhBb0dCQU5RREZZRDQ2Wm81M1RaeHdKbmoKTnZMUFBKMzMxWHNKUlA1MVNQSldTUjF1cWNudTdYeU42YWY1TjZGaThaWFdkUXZSc292NGxVZnJTTTh5b3ZGLwpmeHR1aTlKSXJxSTBKMmhQVXYwR2JIMEJqOUl0OS9GOTlQTUpKZHd0RWxlVnBRNnlsU0ZPOFhNUUdGRm0rWCtCCnFURkcwdHZ0WHNkR0xQbWg0ZHVDTEFvTkFvR0FLSS9aamM2TDEwbzk0c2VNR2FwRmtxTnhDekxhZVZYMTBRRFIKeG83c1Vja2dsVlg2cE8xVzJWZTIrdkhqYUo2MllrSlU0QmtqbEZiYndWMG4vbWlWN2dkOUU2SEhsRmZiVlR5QQprcXNCM0QrV2lQLzdKVEpDdVJEbC92Mnl4NXQ1RnRIR0hENkk2cUhrVWxKQ2ZjQ1pXVEdlUjJZWW05Zkc3Qm9ICjJwYVROMkVDZ1lFQXRUQ2o1M253M213RDE1ZjAyZ2pKcWNjeHBkRk1lYlJpNUltMXJ5RUFaSzhwTmNlL2xNclgKVXM1KzM2eFpHWG1CR1ByMVl0UmxIVThQL1owWFdJM3ZDOFpLRHB6SnB4dnB6QmRFV0pUZGlPZEhpV2J4TzZOOQpiYjh3ZUM4STl4ZDlwd0cyMHdYNUVnR0krekdjMlNGa1VDTWROd1JkV3NSTWNCMklMeVV0MmZzPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= tls_key_pairs: - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURlVENDQW1HZ0F3SUJBZ0lSQUlvWW1QRUhNV0wwZmp6VkZVZ2l2Ykl3RFFZSktvWklodmNOQVFFTEJRQXcKVmpFUU1BNEdBMVVFQ2hNSGVtRnljWFZ2YmpFUU1BNEdBMVVFQXhNSGVtRnljWFZ2YmpFd01DNEdBMVVFQlJNbgpNVGd6TlRZeE1UZ3dOVFkxTXprMk1qUTNNRFV4TXpRM05qa3pOREV5TmpNME56Y3dPRFkyTUI0WERUSXhNRGt3Ck1URTJOVGswTUZvWERUTXhNRGd6TURFMk5UazBNRm93VmpFUU1BNEdBMVVFQ2hNSGVtRnljWFZ2YmpFUU1BNEcKQTFVRUF4TUhlbUZ5Y1hWdmJqRXdNQzRHQTFVRUJSTW5NVGd6TlRZeE1UZ3dOVFkxTXprMk1qUTNNRFV4TXpRMwpOamt6TkRFeU5qTTBOemN3T0RZMk1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBCjlzNGgrUTlpKzBlQ0c0VWNtSThFbEg1MHBlWmFkV2JzdzZDUStCZmF0em1NQWJEa016WGpSTHlNZy8xNFdSRGsKWTU4OUpCWVgrTzRBYXBRNno5MFRhTzhHamN1RmxvcWlFcTZOci9VTjJMYnc2am9KdTYvQ0dmcWIzNXZKT1NDdApKMWp5am5Cc2ZVNTRzRGFGcWpPRG1BL2l3YjNsSlZSV1pmYUdQSGZtRTRUcHJCSzdXV2FHbmlDZktUdGQza3lHCnkzWXpGZEJzSDU0OU9Lc1BFUlJOdTVCdlpzcmZWazRDdnlKNWVxREE1NlBaU1pmSVptZEk4VUdsRmk0V3lhMHEKUHRtWmg4bURGSnpVNnNXZWY5bTRqM3grMmF1UFEra2M3cjJneFdLZ2lTR1FBT2hLU0VhaEZDUW9UU3NzemVReApoZG9xamZUK1NsUWlQQlhJbDhTbzlRSURBUUFCbzBJd1FEQU9CZ05WSFE4QkFmOEVCQU1DQXFRd0R3WURWUjBUCkFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVlSDJQTUhjRmwwTnkvQmozcE4xTExlSStPZTB3RFFZSktvWkkKaHZjTkFRRUxCUUFEZ2dFQkFMeU5aRXByOWtWbk5ORERiWWxWd0M1bnRlUEU2Z0ZQdGRjU3JjYmcyU01temtMMgppSjhCQnJ1ZVBmTllrMVE0c2xlN0FDeTRQL1dzU2xYZThqQkY0bm5KaUkxM2kvaUtQOEJzNVVIQWwwUTJ6RnlMCmp0Sk9JRlFuT1hiUmxQU1RQY21jcWdVUUcrL3lGOGhQVndHNnRwOVZoeXlCVkdlMDRlWU44eVNnbkZKTUloNGcKeDEzcytTWjE1RFkzSERnTGNCSjA5dXA4Q3NJdGV1RG05aG1jbGJ1bUc4OVEyQ3I4T25TcmYzTDNFSVhyelZBbwp3eEUwN21IWnkxSjBvNnljdWJXOWVuS2J3bTRKRHRhaHc4QzlERitvQXJlUEdaSXJXdVZpWFZYeTV6S2NOQW9LCjVXY0Q5RHdjcTFFUnVreGNFWWlrV05BV1FFNnVaMGVYZ0pKWlJ2OD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBOXM0aCtROWkrMGVDRzRVY21JOEVsSDUwcGVaYWRXYnN3NkNRK0JmYXR6bU1BYkRrCk16WGpSTHlNZy8xNFdSRGtZNTg5SkJZWCtPNEFhcFE2ejkwVGFPOEdqY3VGbG9xaUVxNk5yL1VOMkxidzZqb0oKdTYvQ0dmcWIzNXZKT1NDdEoxanlqbkJzZlU1NHNEYUZxak9EbUEvaXdiM2xKVlJXWmZhR1BIZm1FNFRwckJLNwpXV2FHbmlDZktUdGQza3lHeTNZekZkQnNINTQ5T0tzUEVSUk51NUJ2WnNyZlZrNEN2eUo1ZXFEQTU2UFpTWmZJClptZEk4VUdsRmk0V3lhMHFQdG1aaDhtREZKelU2c1dlZjltNGozeCsyYXVQUStrYzdyMmd4V0tnaVNHUUFPaEsKU0VhaEZDUW9UU3NzemVReGhkb3FqZlQrU2xRaVBCWElsOFNvOVFJREFRQUJBb0lCQUhkODZ1TzYrRS94bWVNYQorZkkrWTVoRTlOS1JDTUNJT1I2cE1TWjczZzhSRkdDSk5LSTZkN0tDbW9FWWlWaU5uaFZCTmdldmpxR2RFS1NJCjZVUlRveDhOZ2gzS0ovM3ZWbkkzQWkvck0yMzFmQVBhWDNYM3JNQ0pIVWdRRTBiT05DYTFvSkVuaXM3TDNCQnMKQlNDVzJpSVhwcy9uMFBYV3RCR2ZYZlFPbEZ4ajdKbXdwdDlqYTYyankxa3hlTVdMemlqQUZ1QzRwRTl3WUVaVApGa0ovb2FVdzR4WXM5N0pnelRNUHFJSlUzMStNYTNoNGFPQ2lPK2RzSExhK2tRUkZIN0tHOUtRaWlRYzdrTmdGCnljN2s3L2s3UVJSbG1mZmZLN3E3YUtVNkNueHVxOG05UThob2Q5QVdjQ3ZyTm9jSmVjSzcvSzFzNlhMYXhSTFgKRGFLc0xRMENnWUVBK0lBOGNRVmcveU5xbGQ5c2lHZ0NETy9DVUNwM21uZjdxZTNQeEZIWHVSdFNzaHJheE9qVQpRVXFtbHV5VWRCZTZLQnA4ZWxoVmFlYlZsdFNNd2xyWXNpUlIxaTV0SDNnRGUybXRheE5VZ0xtTGoyZUhVWGN3Cmk1UW1yQ1AyTzY4V0xQZjg4SkkwQWtOWWx3em5aWUg2VEZKbFp0MkxoU3ZRTG1qeFArWDZTT01DZ1lFQS9rREwKNndJMjVoYU1DbTBSWitrQWM1T2FJcUx0OUNWeWNTaGdzblBjK2xBQll0ZVRkclhjVW1FaklQYW1uZ1pKZUpvSgpoUDNxaHBja1JVcWwrSldhN3lXRnJocTdyTHlpTVNBb0VwaXdQMkRUMkxROWFXR0lONjdwSU9ObWtpamU2NmNNCjBsd05hQTBtL3Q5ZWZUZ2RLOUpHYnNoaTVnRFFiTFVtMW5Gd1prY0NnWUFMenJ3UWVycnpKSkdwOFdYTXpYUmIKZlFEMG9pL3dyUWJPT2ppSEVZUjRqUzNPdkt2c2MwdXlsb04zNUdIaGFrYzBKSjRKaWl6MHpUMFUzNkNZazR4OApXbkZ4QmQrMWdSUlpSdG93bmtpRG5VMWVVUU1EQWZEU2tRV05aR0FNMGZMeHpBNit0NU8xRDlJanl6OHJlWk9WCkVNMDBxQTQ3RTZ2ZXFLbmQ2V1dORlFLQmdBa3Z6aTV2cGd3cVJHVWNDOFQxWms3R3hvcjUyQjg2T3loYmpTTGwKak5aK2pZNUV1ODlPUXVlM0dzM1dHNjhhQ3cyUWcwZUs1UzUzeDVlNVdzWGdvZmlDSXBKbjVPQVk4TU5WcGgwRgo1MWhpNTBTdFBvclFPMXZIdGlTNkVycTFQMWpFY0hJcFlWS2hKd2VPaXB0N3E1SXB4dUc1MjlqenJwUSs5MmhJCk1RZUJBb0dCQUkrNnE2OGFvdW41bE5sU3NoTVg0citoZlNtbFhXYmcvdU1OQXROdWhtZndmSE0zWmR3SU9ZWlYKRTloalFNQXh6RFduZkx1ajljRkJQM2NER0s0NytoU0U2N082eHBrYk9BYzk4dUlXZVEzb3lyWEd5akQ3TGdrYwpxajFRa2ZiZE4wMU1BdEN0MDhaSlRwZzR1OUdXUUVVQWY2ckJaKzJPN2ZVdEJuUG40TXpsCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== type: host sub_kind: host version: v2` userCAYAML = `kind: cert_authority metadata: id: 1630515579836524000 name: me.localhost spec: active_keys: ssh: - private_key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBMnZ1ekRQRHZWTElYZTdicDZIc1ZiM3NHak0xZ3lFa2hkWVBUMVVzZ01Kd1JuL1VSCk5EQTZST2pya2NUSEhyRXhnL002dVlQaGN2K2UweUFOZk9JMXROWVgvQ3hGcWdCcGFpRE5YRGpRajdyV2E4RDQKWmNyNkdIVXoza3VDN0k0QTJaUlJXK2M3ajhlZDFEbTlzZ0lpOW1BU3NXL0M2RTZTblEvVGlXVk9hTEdqdEVTdwo5L0lpenc1S2pPYnJLMGRXUXZiMXY1SW8rTUpVbko1NE5jWUlMRm9pYk9WeDdQU1FzWCtDK1JwQ3BIbFIrWnNWClVmSzFrd0l4eXg1Q25BTEMzcnlkOUFaSnY2aVdoci9uczZyWmU5b21xMHd6VUpnT045VE5aQjdZTjRhUmlYSmMKNkpsZmpjdUpnZ2ZDbVZaSWNyRm9wd3kzdnp4LzBxNVVCOVRNRFFJREFRQUJBb0lCQUhVNHkyNGdBMTJwUDl6ZgoyM0t4Z0pYK20xRUFGOURmSk9RTlAzWXNFdjB5YmxUY0VPdUk3WWc1enZCbkQ5Z2tMa2RlQ28rSVEwVVdCT1VyCmdVemFvcms4NmZYNWxRa2QwMUFXWXhmODZkZ213ZVZJbFMrWWFpeHhnT1I4TTRlQnRIN0VZSkQ3eE95QWhNSTQKYm8wOWk0MnJmQll6cDNoSHAwQWdXckp2NG5zenJtVGl2d2s4b0JRS2pGbDZ5VjU2L0g4K3VxaWlSS28vRDlPTApXSU51M1JXRTd4RVpJdlNBZlFMYUFyVG1XaXVVMkhrTklTSmJqRGxrd1phMmtjQ1VQUnVkbFJsOWw2WVM0ZUpSClhNU3RaVmQrcVRpVHhFc29TcTBjRmI3dTlYcEs0a3U3TXR5dDFHY0V0MnIvQzRrcXJFNFAyK3c3RmkvalN2VjYKdUZoWW41a0NnWUVBM3VuK0JMN21hSUhsOGxNclVrcG5SUFJNenllOGFyREplbnM4RXYzTUNyVkxLR0p5NUV6cwppc2ZmVythYTJSejhGOFk0U09aZjZaTk9CS3RzOHFYbkVxL0RhVEgyY3NxMFZXUWxNOFZQNkl3SHpTemNXaThwCnVVRUFOTFg3aG9maTFmZERGNnE2cTFrcmJtdjREa085RGdnRVpxVUR6KzB1dWZhTFNWWG05TzhDZ1lFQSszeFoKTGRJdjVjbXNHWTZhL0xXVjgwRjZHMVMxYUVJVDlBdXNpTThFcnhybEZpbjRSL2dDcEMzSjJod0ZKYldzUjN0OApMVUgxdWxqdE82Mmk2enkzUUlJa1RFNDRveUo0V1RVSEpWQ0VEYXFvcldySklWWTZxMFBlWGs5VDVwa1dzRFpOCldGeDRKQzZhdDZDcEtSTEJPSHdoSm1mYU53K1daamVob251L1pzTUNnWUJpMGI2UFlnV0luTlZRYUxoU3diTW8KS1ZrSG1LajVieWZTU1dGblZlV25kWms4N08vYjc1SUpML1AvcktwR3g0ZW1EblNUTkxXZU9YUWpzODhYZnA2QwpkVEtlcHN5SE5QOWV2NGVTZk0wZzNUcjBKUWdHWHRRVFVSS0RTNDJXcFJUVkg4azVhN0ZYRnErZlF2UHpkdW9QCmwxUkVJTEVnOHhkOHp5UU9QYXVtTndLQmdRREprZXlrMW5DL3ZMcXRyV2k2bncwMmNjZmVlaklCQTkyY1lYTUUKSVBJL0s4NXN5bTBQdWxEYnFUdStEM0ZzdlVYOThaTWhiMW4yNStvV1NHRnFMVHN3Z0Y5NXJjU2x0UzVEU2thVQorUWt2THhlT0VDWndDdjV4WWErdFplWDQwY0dtc1krakFGTG5wVmNyVWFIa292eXVPb2dUa1hBTmEvZi9yQjFvCjc4a0ZJd0tCZ0Q1cHVRd3NodXRYdmNOOCs1bUZDc2d6YW5hckYxSllPdTE2MkhVU2ZPQ2k4aS9VRDNWSUpYQmwKbWpNQVN4elhRamFSa2dQZXNqTmtsTEoyekJsK3ZLaG1jb3NJelNOdzM5b1ZMcC9ObTBiRzlpZmdUdmlha1N6MwpHNFhLYUdNczdRQ3h4bjZsc2ptYTRxYlFlU3JwWmVSMXVtOXJ4TVhLaVdGSUtERXN5TUpNCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== public_key: c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFEYSs3TU04TzlVc2hkN3R1bm9leFZ2ZXdhTXpXRElTU0YxZzlQVlN5QXduQkdmOVJFME1EcEU2T3VSeE1jZXNUR0Q4enE1ZytGeS81N1RJQTE4NGpXMDFoZjhMRVdxQUdscUlNMWNPTkNQdXRacndQaGx5dm9ZZFRQZVM0THNqZ0RabEZGYjV6dVB4NTNVT2IyeUFpTDJZQkt4YjhMb1RwS2REOU9KWlU1b3NhTzBSTEQzOGlMUERrcU01dXNyUjFaQzl2Vy9raWo0d2xTY25uZzF4Z2dzV2lKczVYSHM5SkN4ZjRMNUdrS2tlVkg1bXhWUjhyV1RBakhMSGtLY0FzTGV2SjMwQmttL3FKYUd2K2V6cXRsNzJpYXJURE5RbUE0MzFNMWtIdGczaHBHSmNsem9tVitOeTRtQ0I4S1pWa2h5c1dpbkRMZS9QSC9TcmxRSDFNd04K tls: - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURlRENDQW1DZ0F3SUJBZ0lRVENnQUVvV3BBa2U4YWdHandmVmtkVEFOQmdrcWhraUc5dzBCQVFzRkFEQlcKTVJBd0RnWURWUVFLRXdkNllYSnhkVzl1TVJBd0RnWURWUVFERXdkNllYSnhkVzl1TVRBd0xnWURWUVFGRXljeApNREV5TWprd01qRXdNakUwTmpjM05UQXlOREF6TnpBNE5qQXpPVEV6TmpNd056Y3lNemN3SGhjTk1qRXdPVEF4Ck1UWTFPVE01V2hjTk16RXdPRE13TVRZMU9UTTVXakJXTVJBd0RnWURWUVFLRXdkNllYSnhkVzl1TVJBd0RnWUQKVlFRREV3ZDZZWEp4ZFc5dU1UQXdMZ1lEVlFRRkV5Y3hNREV5TWprd01qRXdNakUwTmpjM05UQXlOREF6TnpBNApOakF6T1RFek5qTXdOemN5TXpjd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUM5Cjg5dFE1YTB0bzQ5dkN3dmxFTUpYTnBiYkpzUVIram96QzJScFFTLzZBSHlHbytueXRKUTZSclp6UWlZeTVkb2UKUTArUmhPTGM0R1Q4R0JQdTBrRmUwejk4Zlp4TC8wb2kvZ0trVTgrM0VOaDFuN1krbjV0RVBvbHZsZTBZUm5lWAp3NWpna1lDZG1TTjFMZXVqYW96OUpyYXVUMzRPSGwzdFNoZ3pBUkV1ZlBVVDJQNXlaQXNCdW9nYzBHd055MUo4Cml1UXpDb0tNektOM2ZidFhYU2llU3pubWxQeWMyeXpIcW1UZGdQNnVDNWFrVXpnT3E5ZGFjUkpxVWJRVXlXOUUKVk80MWRQK2wrMXdzNmdSaFNuK29Sc0JLOG1TVW1JNjc4VjE2b3RxT1I2ZDl5VnZKVTZSbjEvMWIzYk5ES3I2awpYSWwwR1FYczNtajd0QUEzVHNCRkFnTUJBQUdqUWpCQU1BNEdBMVVkRHdFQi93UUVBd0lDcERBUEJnTlZIUk1CCkFmOEVCVEFEQVFIL01CMEdBMVVkRGdRV0JCVFovZGxCRDdncWpKTU9rR2laSitGL0NXbkh5akFOQmdrcWhraUcKOXcwQkFRc0ZBQU9DQVFFQXU4cVZUZTYzZnd4UkM3cFU2bzRscldSby9ldnB3VkhkV04yT1NES0t4UU5ISURhbQovRDlnamhtdnphQWFKY3E2UmdkaTNCWlRuZDhsSDYzUlZuVHk4RzljUTNTYmFBMHlSQzMyQnQ3bjh6aUYxTTNqCkFxck5zV0hIWkZydkJKWkx2UDBqbms5RmRtSmFYc3o0cGgyRndYQjFTUkdFT3lVcFAvSS9OeVUyNFovRGFvOG4KWlRSazAvbHBVUis2SGp0UGliUTdJejkxcWtxNmsxcEY0SXdlbG5xU1Vqc2FjLzBGa1lib1I0UHkvT1BUbmtIRgpVR2doRUFHRzNnZXFsaGI4Q3FUYmRwKzhLb0R6RzNBOUtRUURBWUlMNkh5Mm9nVEJmU241Uk5WY2pLdHI0QS9UClhnSnlUdjdHZ0FabkZHT1dMc3B2TDhJQVdGYmN4T2hwV2hEaDNRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdmZQYlVPV3RMYU9QYndzTDVSRENWemFXMnliRUVmbzZNd3RrYVVFditnQjhocVBwCjhyU1VPa2EyYzBJbU11WGFIa05Qa1lUaTNPQmsvQmdUN3RKQlh0TS9mSDJjUy85S0l2NENwRlBQdHhEWWRaKzIKUHArYlJENkpiNVh0R0VaM2w4T1k0SkdBblpramRTM3JvMnFNL1NhMnJrOStEaDVkN1VvWU13RVJMbnoxRTlqKwpjbVFMQWJxSUhOQnNEY3RTZklya013cUNqTXlqZDMyN1YxMG9ua3M1NXBUOG5Oc3N4NnBrM1lEK3JndVdwRk00CkRxdlhXbkVTYWxHMEZNbHZSRlR1TlhUL3BmdGNMT29FWVVwL3FFYkFTdkprbEppT3UvRmRlcUxhamtlbmZjbGIKeVZPa1o5ZjlXOTJ6UXlxK3BGeUpkQmtGN041bys3UUFOMDdBUlFJREFRQUJBb0lCQVFDWkFiTHBxUGdrU1JtaQpncTFrS0duQ3dwQWxtMFpZak0wUWpONm5BZ0ZaU2NjRTFVZi9Yb0gvcHpJVUNYYW5qUXB6VWhqbnlMak0zbHU1CnpOTlJqajlsMkpmTStZbEtsaXJyb053VDdnYmxHVWFqQ0xGT0pGWjNWRUIwaDduaDBmRkhhQ0RlMDVWY1hSeDQKcVRLa0FaSHI0S0ZLSzNJSWdXRjdZREc1OCtRWkl0N01BdDQyc1NrUlhaenNyaUVzVWxZNU9xT1dlVHlkYy93dQpDK292V1FlOUU2bnRSc0EvY25nS0M1bnp6T0pCano3SlVTMGQ2UnBGK1Zodk9SRlZ6YTZKMnpRRXJsWWtFdVVUCkJYdThsVmhhcDVOU09nd3ByZXVmb2tNZVY4eGRNdUxuVEthdHVjajIwT3JFRUlHZVFrb08yTzhTeDJtNitOS0kKbzBTT2kzU0pBb0dCQU5LVkk4R0JHQUpSUlV2NVBnVkNDQ1VHSkFBRmtxY0FMTFlaZ0NMZCtLYStBVW41L2VONwpWemR4b2VqNG1UcnFOb0RLTnk5b0N2Z1RBWTlWZ3RZZllpdlNQbW5ZZk9FbTJUR2pLRTJSYzhBd0R5UWdHWFJVCm5WOUVueHRUUHF5dU9tQTFqa0ladXBLeUU0b0drdTE4MDRVaTB2d2pJUWI3cDdML0ZGYksvaGlmQW9HQkFPYnIKclBrOHdicnBlVk9QWmVudzVzbjVhTUpjdDM3emt4dHZzejVSVWlsZnZtVkdja1lyMU41SGpmZjVVVGcwNmNWdgp1N0dGY0NLQmhncFdqSXM3dHZ4cDhUVFM1VEZaWHZqY2VhRC91ZkFiN3ZUVnR0L1hRMFUwQ28rcDdzMENKWlZKCjY2MUtUU1RCYlFtR0xYYmNqNmFYdVM3bzFJM1FRU1l4NW5LV1F5aWJBb0dCQUtBTWZocUtGVWRkb1g5MnRhNmwKV3k5WWxXLzJ6RmxsQnBaNGx5em83QjArK0JmVGl5V2tEc3V5NzgzemMvS1ZKRXVLWlpzQVJxWDVQQXhHZjZSaQpRZWp3YUVObUtMT3ZKUkJXNDBEaE5jcHlQRy9HZmRJdXBWVk5BR2h5UW9aWC9VSTJNaU1IRHdpRGs5b3AyTzNyCkc1QnF3VlNsRm1zS1JaRUQwZCtOZE1ZZEFvR0FkVDNQRXJQd1FHL3RzNmtvdTBBZVRRbWVVS0EyWWZSVkNpY0sKUUdlVmFZQTg4THAxcG43Mmt1eU5mZ3ROVzFZeUlwWDZHOFYrQzJicm9UQVVKMVRvTVB1eEJYclY5dHBEUitMWQp0ZzlnWGpJd2ZvcExVUmJBQnRESFUrMlpXdWp1SC8vcDhvKzQzeUo5czhvMkp4VVFzaXB5VVFqUmNqYjcvT0owCitGU21RR1VDZ1lBdU5XcFhrbVkzbGtRSEFpU3RlblhTT2Zqc0xsbm5XWFJoTDVBYTZqRVZRT3FnUldVQnZ2YWkKK1RQRTNUTHQ5MGwveTZOdjU0dDdrT1QvSlEvREU4WmFiMERlTjdBRzRwRjMwNkxpYlpZNmswc3M1UDNXME8vbAozekJzQ0lEY3BHOWw4bDZSNkUwdHN0Z0I1c25hOE4rRzA2V3Q1R0M0UitRSVd1YTVpUmtVTXc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= additional_trusted_keys: {} checking_keys: - c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFEYSs3TU04TzlVc2hkN3R1bm9leFZ2ZXdhTXpXRElTU0YxZzlQVlN5QXduQkdmOVJFME1EcEU2T3VSeE1jZXNUR0Q4enE1ZytGeS81N1RJQTE4NGpXMDFoZjhMRVdxQUdscUlNMWNPTkNQdXRacndQaGx5dm9ZZFRQZVM0THNqZ0RabEZGYjV6dVB4NTNVT2IyeUFpTDJZQkt4YjhMb1RwS2REOU9KWlU1b3NhTzBSTEQzOGlMUERrcU01dXNyUjFaQzl2Vy9raWo0d2xTY25uZzF4Z2dzV2lKczVYSHM5SkN4ZjRMNUdrS2tlVkg1bXhWUjhyV1RBakhMSGtLY0FzTGV2SjMwQmttL3FKYUd2K2V6cXRsNzJpYXJURE5RbUE0MzFNMWtIdGczaHBHSmNsem9tVitOeTRtQ0I4S1pWa2h5c1dpbkRMZS9QSC9TcmxRSDFNd04K cluster_name: me.localhost signing_alg: 3 signing_keys: - LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBMnZ1ekRQRHZWTElYZTdicDZIc1ZiM3NHak0xZ3lFa2hkWVBUMVVzZ01Kd1JuL1VSCk5EQTZST2pya2NUSEhyRXhnL002dVlQaGN2K2UweUFOZk9JMXROWVgvQ3hGcWdCcGFpRE5YRGpRajdyV2E4RDQKWmNyNkdIVXoza3VDN0k0QTJaUlJXK2M3ajhlZDFEbTlzZ0lpOW1BU3NXL0M2RTZTblEvVGlXVk9hTEdqdEVTdwo5L0lpenc1S2pPYnJLMGRXUXZiMXY1SW8rTUpVbko1NE5jWUlMRm9pYk9WeDdQU1FzWCtDK1JwQ3BIbFIrWnNWClVmSzFrd0l4eXg1Q25BTEMzcnlkOUFaSnY2aVdoci9uczZyWmU5b21xMHd6VUpnT045VE5aQjdZTjRhUmlYSmMKNkpsZmpjdUpnZ2ZDbVZaSWNyRm9wd3kzdnp4LzBxNVVCOVRNRFFJREFRQUJBb0lCQUhVNHkyNGdBMTJwUDl6ZgoyM0t4Z0pYK20xRUFGOURmSk9RTlAzWXNFdjB5YmxUY0VPdUk3WWc1enZCbkQ5Z2tMa2RlQ28rSVEwVVdCT1VyCmdVemFvcms4NmZYNWxRa2QwMUFXWXhmODZkZ213ZVZJbFMrWWFpeHhnT1I4TTRlQnRIN0VZSkQ3eE95QWhNSTQKYm8wOWk0MnJmQll6cDNoSHAwQWdXckp2NG5zenJtVGl2d2s4b0JRS2pGbDZ5VjU2L0g4K3VxaWlSS28vRDlPTApXSU51M1JXRTd4RVpJdlNBZlFMYUFyVG1XaXVVMkhrTklTSmJqRGxrd1phMmtjQ1VQUnVkbFJsOWw2WVM0ZUpSClhNU3RaVmQrcVRpVHhFc29TcTBjRmI3dTlYcEs0a3U3TXR5dDFHY0V0MnIvQzRrcXJFNFAyK3c3RmkvalN2VjYKdUZoWW41a0NnWUVBM3VuK0JMN21hSUhsOGxNclVrcG5SUFJNenllOGFyREplbnM4RXYzTUNyVkxLR0p5NUV6cwppc2ZmVythYTJSejhGOFk0U09aZjZaTk9CS3RzOHFYbkVxL0RhVEgyY3NxMFZXUWxNOFZQNkl3SHpTemNXaThwCnVVRUFOTFg3aG9maTFmZERGNnE2cTFrcmJtdjREa085RGdnRVpxVUR6KzB1dWZhTFNWWG05TzhDZ1lFQSszeFoKTGRJdjVjbXNHWTZhL0xXVjgwRjZHMVMxYUVJVDlBdXNpTThFcnhybEZpbjRSL2dDcEMzSjJod0ZKYldzUjN0OApMVUgxdWxqdE82Mmk2enkzUUlJa1RFNDRveUo0V1RVSEpWQ0VEYXFvcldySklWWTZxMFBlWGs5VDVwa1dzRFpOCldGeDRKQzZhdDZDcEtSTEJPSHdoSm1mYU53K1daamVob251L1pzTUNnWUJpMGI2UFlnV0luTlZRYUxoU3diTW8KS1ZrSG1LajVieWZTU1dGblZlV25kWms4N08vYjc1SUpML1AvcktwR3g0ZW1EblNUTkxXZU9YUWpzODhYZnA2QwpkVEtlcHN5SE5QOWV2NGVTZk0wZzNUcjBKUWdHWHRRVFVSS0RTNDJXcFJUVkg4azVhN0ZYRnErZlF2UHpkdW9QCmwxUkVJTEVnOHhkOHp5UU9QYXVtTndLQmdRREprZXlrMW5DL3ZMcXRyV2k2bncwMmNjZmVlaklCQTkyY1lYTUUKSVBJL0s4NXN5bTBQdWxEYnFUdStEM0ZzdlVYOThaTWhiMW4yNStvV1NHRnFMVHN3Z0Y5NXJjU2x0UzVEU2thVQorUWt2THhlT0VDWndDdjV4WWErdFplWDQwY0dtc1krakFGTG5wVmNyVWFIa292eXVPb2dUa1hBTmEvZi9yQjFvCjc4a0ZJd0tCZ0Q1cHVRd3NodXRYdmNOOCs1bUZDc2d6YW5hckYxSllPdTE2MkhVU2ZPQ2k4aS9VRDNWSUpYQmwKbWpNQVN4elhRamFSa2dQZXNqTmtsTEoyekJsK3ZLaG1jb3NJelNOdzM5b1ZMcC9ObTBiRzlpZmdUdmlha1N6MwpHNFhLYUdNczdRQ3h4bjZsc2ptYTRxYlFlU3JwWmVSMXVtOXJ4TVhLaVdGSUtERXN5TUpNCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== tls_key_pairs: - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURlRENDQW1DZ0F3SUJBZ0lRVENnQUVvV3BBa2U4YWdHandmVmtkVEFOQmdrcWhraUc5dzBCQVFzRkFEQlcKTVJBd0RnWURWUVFLRXdkNllYSnhkVzl1TVJBd0RnWURWUVFERXdkNllYSnhkVzl1TVRBd0xnWURWUVFGRXljeApNREV5TWprd01qRXdNakUwTmpjM05UQXlOREF6TnpBNE5qQXpPVEV6TmpNd056Y3lNemN3SGhjTk1qRXdPVEF4Ck1UWTFPVE01V2hjTk16RXdPRE13TVRZMU9UTTVXakJXTVJBd0RnWURWUVFLRXdkNllYSnhkVzl1TVJBd0RnWUQKVlFRREV3ZDZZWEp4ZFc5dU1UQXdMZ1lEVlFRRkV5Y3hNREV5TWprd01qRXdNakUwTmpjM05UQXlOREF6TnpBNApOakF6T1RFek5qTXdOemN5TXpjd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUM5Cjg5dFE1YTB0bzQ5dkN3dmxFTUpYTnBiYkpzUVIram96QzJScFFTLzZBSHlHbytueXRKUTZSclp6UWlZeTVkb2UKUTArUmhPTGM0R1Q4R0JQdTBrRmUwejk4Zlp4TC8wb2kvZ0trVTgrM0VOaDFuN1krbjV0RVBvbHZsZTBZUm5lWAp3NWpna1lDZG1TTjFMZXVqYW96OUpyYXVUMzRPSGwzdFNoZ3pBUkV1ZlBVVDJQNXlaQXNCdW9nYzBHd055MUo4Cml1UXpDb0tNektOM2ZidFhYU2llU3pubWxQeWMyeXpIcW1UZGdQNnVDNWFrVXpnT3E5ZGFjUkpxVWJRVXlXOUUKVk80MWRQK2wrMXdzNmdSaFNuK29Sc0JLOG1TVW1JNjc4VjE2b3RxT1I2ZDl5VnZKVTZSbjEvMWIzYk5ES3I2awpYSWwwR1FYczNtajd0QUEzVHNCRkFnTUJBQUdqUWpCQU1BNEdBMVVkRHdFQi93UUVBd0lDcERBUEJnTlZIUk1CCkFmOEVCVEFEQVFIL01CMEdBMVVkRGdRV0JCVFovZGxCRDdncWpKTU9rR2laSitGL0NXbkh5akFOQmdrcWhraUcKOXcwQkFRc0ZBQU9DQVFFQXU4cVZUZTYzZnd4UkM3cFU2bzRscldSby9ldnB3VkhkV04yT1NES0t4UU5ISURhbQovRDlnamhtdnphQWFKY3E2UmdkaTNCWlRuZDhsSDYzUlZuVHk4RzljUTNTYmFBMHlSQzMyQnQ3bjh6aUYxTTNqCkFxck5zV0hIWkZydkJKWkx2UDBqbms5RmRtSmFYc3o0cGgyRndYQjFTUkdFT3lVcFAvSS9OeVUyNFovRGFvOG4KWlRSazAvbHBVUis2SGp0UGliUTdJejkxcWtxNmsxcEY0SXdlbG5xU1Vqc2FjLzBGa1lib1I0UHkvT1BUbmtIRgpVR2doRUFHRzNnZXFsaGI4Q3FUYmRwKzhLb0R6RzNBOUtRUURBWUlMNkh5Mm9nVEJmU241Uk5WY2pLdHI0QS9UClhnSnlUdjdHZ0FabkZHT1dMc3B2TDhJQVdGYmN4T2hwV2hEaDNRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdmZQYlVPV3RMYU9QYndzTDVSRENWemFXMnliRUVmbzZNd3RrYVVFditnQjhocVBwCjhyU1VPa2EyYzBJbU11WGFIa05Qa1lUaTNPQmsvQmdUN3RKQlh0TS9mSDJjUy85S0l2NENwRlBQdHhEWWRaKzIKUHArYlJENkpiNVh0R0VaM2w4T1k0SkdBblpramRTM3JvMnFNL1NhMnJrOStEaDVkN1VvWU13RVJMbnoxRTlqKwpjbVFMQWJxSUhOQnNEY3RTZklya013cUNqTXlqZDMyN1YxMG9ua3M1NXBUOG5Oc3N4NnBrM1lEK3JndVdwRk00CkRxdlhXbkVTYWxHMEZNbHZSRlR1TlhUL3BmdGNMT29FWVVwL3FFYkFTdkprbEppT3UvRmRlcUxhamtlbmZjbGIKeVZPa1o5ZjlXOTJ6UXlxK3BGeUpkQmtGN041bys3UUFOMDdBUlFJREFRQUJBb0lCQVFDWkFiTHBxUGdrU1JtaQpncTFrS0duQ3dwQWxtMFpZak0wUWpONm5BZ0ZaU2NjRTFVZi9Yb0gvcHpJVUNYYW5qUXB6VWhqbnlMak0zbHU1CnpOTlJqajlsMkpmTStZbEtsaXJyb053VDdnYmxHVWFqQ0xGT0pGWjNWRUIwaDduaDBmRkhhQ0RlMDVWY1hSeDQKcVRLa0FaSHI0S0ZLSzNJSWdXRjdZREc1OCtRWkl0N01BdDQyc1NrUlhaenNyaUVzVWxZNU9xT1dlVHlkYy93dQpDK292V1FlOUU2bnRSc0EvY25nS0M1bnp6T0pCano3SlVTMGQ2UnBGK1Zodk9SRlZ6YTZKMnpRRXJsWWtFdVVUCkJYdThsVmhhcDVOU09nd3ByZXVmb2tNZVY4eGRNdUxuVEthdHVjajIwT3JFRUlHZVFrb08yTzhTeDJtNitOS0kKbzBTT2kzU0pBb0dCQU5LVkk4R0JHQUpSUlV2NVBnVkNDQ1VHSkFBRmtxY0FMTFlaZ0NMZCtLYStBVW41L2VONwpWemR4b2VqNG1UcnFOb0RLTnk5b0N2Z1RBWTlWZ3RZZllpdlNQbW5ZZk9FbTJUR2pLRTJSYzhBd0R5UWdHWFJVCm5WOUVueHRUUHF5dU9tQTFqa0ladXBLeUU0b0drdTE4MDRVaTB2d2pJUWI3cDdML0ZGYksvaGlmQW9HQkFPYnIKclBrOHdicnBlVk9QWmVudzVzbjVhTUpjdDM3emt4dHZzejVSVWlsZnZtVkdja1lyMU41SGpmZjVVVGcwNmNWdgp1N0dGY0NLQmhncFdqSXM3dHZ4cDhUVFM1VEZaWHZqY2VhRC91ZkFiN3ZUVnR0L1hRMFUwQ28rcDdzMENKWlZKCjY2MUtUU1RCYlFtR0xYYmNqNmFYdVM3bzFJM1FRU1l4NW5LV1F5aWJBb0dCQUtBTWZocUtGVWRkb1g5MnRhNmwKV3k5WWxXLzJ6RmxsQnBaNGx5em83QjArK0JmVGl5V2tEc3V5NzgzemMvS1ZKRXVLWlpzQVJxWDVQQXhHZjZSaQpRZWp3YUVObUtMT3ZKUkJXNDBEaE5jcHlQRy9HZmRJdXBWVk5BR2h5UW9aWC9VSTJNaU1IRHdpRGs5b3AyTzNyCkc1QnF3VlNsRm1zS1JaRUQwZCtOZE1ZZEFvR0FkVDNQRXJQd1FHL3RzNmtvdTBBZVRRbWVVS0EyWWZSVkNpY0sKUUdlVmFZQTg4THAxcG43Mmt1eU5mZ3ROVzFZeUlwWDZHOFYrQzJicm9UQVVKMVRvTVB1eEJYclY5dHBEUitMWQp0ZzlnWGpJd2ZvcExVUmJBQnRESFUrMlpXdWp1SC8vcDhvKzQzeUo5czhvMkp4VVFzaXB5VVFqUmNqYjcvT0owCitGU21RR1VDZ1lBdU5XcFhrbVkzbGtRSEFpU3RlblhTT2Zqc0xsbm5XWFJoTDVBYTZqRVZRT3FnUldVQnZ2YWkKK1RQRTNUTHQ5MGwveTZOdjU0dDdrT1QvSlEvREU4WmFiMERlTjdBRzRwRjMwNkxpYlpZNmswc3M1UDNXME8vbAozekJzQ0lEY3BHOWw4bDZSNkUwdHN0Z0I1c25hOE4rRzA2V3Q1R0M0UitRSVd1YTVpUmtVTXc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= type: user sub_kind: user version: v2` databaseCAYAML = `kind: cert_authority metadata: id: 1640648663670001000 name: me.localhost spec: active_keys: tls: - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURpakNDQW5LZ0F3SUJBZ0lRRU9IcEhIZkZwZ28wUndQSVJhdkdpakFOQmdrcWhraUc5dzBCQVFzRkFEQmYKTVJVd0V3WURWUVFLRXd4dFpTNXNiMk5oYkdodmMzUXhGVEFUQmdOVkJBTVRERzFsTG14dlkyRnNhRzl6ZERFdgpNQzBHQTFVRUJSTW1NakkwTkRBMk5ESTNPREkyTWpJNE5UQXdOemMzTmpVeU16STVOak15TWpNeU56VXhORFl3CkhoY05NakV4TWpJM01qTTBOREl6V2hjTk16RXhNakkxTWpNME5ESXpXakJmTVJVd0V3WURWUVFLRXd4dFpTNXMKYjJOaGJHaHZjM1F4RlRBVEJnTlZCQU1UREcxbExteHZZMkZzYUc5emRERXZNQzBHQTFVRUJSTW1NakkwTkRBMgpOREkzT0RJMk1qSTROVEF3TnpjM05qVXlNekk1TmpNeU1qTXlOelV4TkRZd2dnRWlNQTBHQ1NxR1NJYjNEUUVCCkFRVUFBNElCRHdBd2dnRUtBb0lCQVFETFRrVFkzQ0NMVStNUllkbEMwM2NUTTR6MUpiRGoxYjFQRWdING9iSmwKRjl4NWtQbzhncWNEbmp5L0x5NHdKeUR2Q2xPMkw1T0k3UnYwa1hFUXoybUVEeExnbjJYRG9ZNUh5VFNOVkZHNgpvZ3BlYmhlUFN1aWl0RUNZYUZDZVZFTGNDa1Q0ZGpqRDlwOExNTnJ4MHRPOXdQU1o1OXBLZUxCOG90RFloOHRCCkcyb2EzSGIzTWt0RGxOY0svVE94RFNzRzUrQ2ljdktTa3QrV04xaXJJQ2pvZ2hWTzJGcForRkdxWUM0Y1EwbWMKM0NRaGJwY1o2VTRkWnpGdFJZVzZPYzNucHBOSkZKWXZSSTRIS1FWY0RCM2N4VkhNTUd5Rzc3aFRzdEwvd0RuaQo4U2s5eml4VzN4S2FvUnlrV2FuWno4eC9WdHNydXJqanNzNDV4NlRoem1VWkFnTUJBQUdqUWpCQU1BNEdBMVVkCkR3RUIvd1FFQXdJQnBqQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01CMEdBMVVkRGdRV0JCUmNKK2NRamFQWjZGbEIKcVhoYzYyWXZldGRpQWpBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQVdYNmxZdUhtMmQyMU41RDN1eUJJelFOdApKVFR3b0xnU3FQd09Tbk1EU0luSDkvMjBqZDNGUk9qakh1M3BQRkNLRmE4OVp0ekZxTHZsTjVOdlh2WHFuOXNKCkdudTYzSVo0TWtEZk9sSVZpWFhQWFF4YllHSkMxRVlVU28rTDdtUTY0VnN5UkFpTXdnbmVwMUxwSGhROGYzU2MKeEZoVkNybFJDMmUrNENBai8vOVZWaDdvTEdSMkNhM0xEcFc5VHFxYnB3MEh0QitNcFVqVWxCWnFVbzNVMm5HTQpia1VhSVZKcnNuYk1rYnNsUGQ2dWtVRDlVTHFuUmxJb3A4cjQ1VTdvYVBhR3g3QVFiWndzbGlsNVVJZlppRmlRCm5USk9kYnJHampVdXlRYkM4UUpZY3RhdENjbVBjZUlXMVVWWFVnZ2JsdXl4VjF1NWsyYzlSb1k2RzhiN0FRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBeTA1RTJOd2dpMVBqRVdIWlF0TjNFek9NOVNXdzQ5VzlUeElCK0tHeVpSZmNlWkQ2ClBJS25BNTQ4dnk4dU1DY2c3d3BUdGkrVGlPMGI5SkZ4RU05cGhBOFM0SjlsdzZHT1I4azBqVlJSdXFJS1htNFgKajByb29yUkFtR2hRbmxSQzNBcEUrSFk0dy9hZkN6RGE4ZExUdmNEMG1lZmFTbml3ZktMUTJJZkxRUnRxR3R4Mgo5ekpMUTVUWEN2MHpzUTByQnVmZ29uTHlrcExmbGpkWXF5QW82SUlWVHRoYVdmaFJxbUF1SEVOSm5Od2tJVzZYCkdlbE9IV2N4YlVXRnVqbk41NmFUU1JTV0wwU09CeWtGWEF3ZDNNVlJ6REJzaHUrNFU3TFMvOEE1NHZFcFBjNHMKVnQ4U21xRWNwRm1wMmMvTWYxYmJLN3E0NDdMT09jZWs0YzVsR1FJREFRQUJBb0lCQVFDeXNLNXVkTHZkK2ZOQQpHZUtkaThQRENySS8zY3JsMWIwNFBEbWpVR3U5MHdVampEdUU1OGpuc3pMdFR3aW5waHlhUFZkcWI5S2FyTnkvClR2NHpxam14cXBZSys4Nno3ZEZpWXdSZm05YmgxUDZNRlBOOExIamdXTkhWb3dvSXYwS3NxQklLMTgzNDMxRFcKd3pBTkVDS3ZTMk14eXNqZ1g4ZXZKR092alZzbWN1SU1IWFljbVlORlo0dWpuTnc0dnVSWHhlYUtPY2ZWTGZ2ZwoxSWZMK05IYUh6UXI1YVoydkxST1NpdHVId2cvOFpZZ2hQcVFYLy92ak92M0FENzhXVGxINUZXWjExR0hoeCt1Ck9ZUXcwQ1lvTnNiV1UxeklwVTV0cHdCcDRGV0VlVy9jbTY5NXRBVVRlMzR1N3R1MlJJOVBZMEZDWVJ2ZkM2SUQKK2tDNjRFTEpBb0dCQVBSTEt5a0pjdHNGNngzN1liWXdmQm9ZK3V6YU4wV1o1d1RuRmVFaVp1ZzVVYWJUOTdqRApZTnpaYzQ0aitRbkxFUGVDak5tU1FhVHFxK2QvbUVjTnlXS244cVNFcXlOR0R6YlRXQ2tkMGtyWTVwcm9FVVJnClFqbWFwRHFNNEtOSm9jQ1RCS3lueHo2QzZ6ME9uSnhZM3lMdTdyYVBqeE9HTW5rcm9VMDhMVW56QW9HQkFOVU0KU2NsNGh1R0gxK3ZNL0RtenJoQ1NKbUZIcjE2QjhvWExaMHIyNmFKTnJVQ3AwYUFzV2FHd3JLZXZFcDkzRmg5Ygp3QlZYMHE0bXJ3cmZpTGpCYnRzdnlQM3l1ZWs5cWl6M2ZoMWIvZ0k2eWRKVFEzMEplQzB4UzUyRksvR2gwanEvCm43c2Y1bm5DbTlCRW01ZmdSeDFVUmVhOC9vL2M4cTNhbW94c0grdkRBb0dBZUVZUjU5QlpGZkJpQTQ3aVdwcWcKWHhEeGFXOCtTeXdzaTBOaWlFY3h0eCtSVGJ1S2VSTG9PNU5yeXcxMjdSVm5NeFM1Vjkwa0tKZkpMdDZwRUVKLwpaZTBlRDFXcUZHSEgxOHhSMlZ4dlRwNWZXdURxcjJsYzhaTnJTOUJVUU5CZHJMdzFUdlFEcW9rMlhBYzNuOW81CmNhK0ZJNmltWG94eGlTcXI3YVMwLzNVQ2dZQTJBWEJ1N3V1YUhocGcvc3h0UUJ2K3ZWMlhTVm11SmxpNUM4KzYKVkE3emdxZEpmZ0xTakl1SURrWW1GNTRyNkQ4bVlkYTJVbFhvcVl1endPaGlsVDRwdDlwR2JaSXRDdUdwbG05VQp0KzRTMko0eWY4TGEzbHlsY0JxUDZxTXlGR2c3VmpvQ2NGcTNRTnJJbDZ1dGV6L3JzbUlwMUh6Zk1RNGZmZ3V4ClR2Tmtpd0tCZ1FDbVhaSStvTUdQK3U4KzRVaGxjR01NYUNHZi92UVZLdVJYOHlOYVh1bUx6dk1Xajl0cVhpUzcKK1dQUlhuV01RSnd1QldYMzBTcWdFVVdDbjlzOGxWbzh2TVF4MmFtbXhhWkVEMGRoOHNMSkJDNXJoRmVqV29MbQp3cHg5MXR0S3JJODBKMDYyeW90SFpJYkRyQW1LSGZFeE85U1d4T1hUeFVMUGdvTlJVUW5qSnc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= additional_trusted_keys: {} cluster_name: me.localhost signing_alg: 3 type: db sub_kind: db version: v2` jwtCAYAML = `kind: cert_authority metadata: id: 1630515580249460000 name: me.localhost spec: active_keys: jwt: - private_key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNWNQaFordnk2N05NbDhwbDJ4SllvSDVFOTl0a1UzVlByTGhnVkhOMmpuZmoyRlhNCm9RdVRKbUJOdWVEd3FiVFpKREJmQ0d1bTU2RVczK1YrQUtsY3pFNVdFS2NBR0wxL3ZvOGU0NDNBRmwzQXRHVVEKSWRHVzRoL0o0Q3BTNG9idVVlMGt3K0JiZnlocUJ2a2lTNmxtUUpjV0hLbzd2aWZhRTJKc1hmZStESklSMS80eApvYjlrbkw1QWVoRS9XV25CY0RiWXROcHlFOHBoQzdGaDgzYmFUdldqbXNWTEZaaWEzKzJqL255aWRzUmUxNGMyCnR4czRWRXp6UVpaYTMwMlpESFRpRmM4MzJtY3lCTEFzRnMycHNsSkxiUnZLdllBS1dLTnRRcnRlSHIwUFBmamEKQjM3cmNWMXlLYTJONjNRTnZrd2FRMWlnbllqSmE3blRQYWpVSndJREFRQUJBb0lCQUY0b1ZMSUt2bVVhK0RObwpMUytHcUMwMU1ieEUreXM4Y3VjOE03WElEM2k0NXZWYnk5emZhbkVhbkIrbGI5cU1FMFJDVWwrWUJqRDhFZXkxCkZscmREUHRveXRwT0picjl4V0RwTStaYXk3SWV2Mzd0djV1c1VXSGZWeEozSmJwUlEwN3RtTmh3ays1Yk9JQWQKRHBIbEhOTXhWMDF0OGNldWV5N2djYnBjY1ZTaXJNYy9ERTlGcFNONFRRWFloMlRDSnVZcTdncFo1SGJ2MlpleApDOHFTTnZqVVlVTithTGlDZ2pBRFhZdGdmV2RUVWN0YkhldVUrL2Ftb3cyUitOVjFWMGpML3lOQm0rdmZUVXNuCmprNFMxQWx1U3FrT0FFaHJhZjM2UzRNcWZQWm8zbXhFOC96TVZTdEY5MTdvQVRKMlZrNzVZaGxINnMzM1BKN1kKMXdDeVdZa0NnWUVBOFRoT1E4M01xTjFTL1VVcUJWeGRaaS9HZFBldmQrbFUvY2FIQVhTNmJyd2RYNEpITTkrbApaczZVWFdocWJHUXFSVlRvZ1dna2VBSHp4R3I1S2QvSTlRRnhEZHE2am1vRUhmU3VNNnNiNU4yVjgyQTVSaUhHCmduS3IzMUpWQS9FdEt3Vk5sZUY3L1VUM0tzWTJzajRZNFkxRnI3amRkOS9tdTVaNWVwODFidFVDZ1lFQTg5Zm0KSE9tc0JYZUQ0M0FHVmltSFhEWWVGckI3UWRhY3hOQ3R4NjZuTk5jMFpxM0pDZGtDMjVaTG1nQytON2JEbkRjMApNZ2NPUGdyUWxVdWpRbWl1d1daeFlXcDAvWDh3STFHZ2p4RkEvRkJvNjFnd3lwR2tqbjJlS3lVM1ovWlJBRTBjCmlxaW5UTVc5TFBlV1k0MEhOUzFic3E2YmFoeXg1bTFVV0gxclRRc0NnWUF3bE1WMmRHbEdqU1NjcTZSVjVnOU4KZUV2QTNPMXkrZ1JMQkFQR3NFcW42SzBGd2tneTAxVU5pb2RvOUpHU2VPM21mcjVBNmNlR2YrWW5aZC8rcGZwawpGY0UrS0JJd2dudUh5UEtZcDFwNzBvRFR2a3Bxckh5OVl2am9oajFuQ05pdTlHZDJ5eTNjaVZvNlBDZGg2STI4ClIyYUVpSGZhSDdicGl0bTJiNEFrYlFLQmdRQ2tmUUpraEppZkEzVTdpa2tyL0Uyc1BYRmttdDQ2bG53Z0pDam0KSjRIeG1pNW1DVnN4UW11MEZ4bWVwRnVzbDZReWorYXN6S2VsNElPK0FrejZNa1dZZnZPQzVGNVExbWh4bXRHMQpVTTFHcHpOdmRvbExUSjMxNVBVNlk1dVJqTTR0WnRjWERoZjFLUHFwQjhjeUZtTkRVdnFsZVRXcmlmblQxL0pxCjB3Zjc2d0tCZ1FDN3hIK3NXbTR6cjVRb1VvMWlrYlRMSHRBaVFETWE2TlpWTTk0MHcxZHExUWk4YXpMTE1lMUcKUVduU1ZqTDVLaVlSY0toSjVYOGpoQjF5a2U1OTR1ZU1CSFJRdjFRU3RCalRkSzIxN2tnTTFTRmZEbzFFQWpRdgpMWk5pWHROYXVDeG9BMi96SHJrZ0d0NGQzQldKZk5ENkJPQ1RKK2lSRnNWc1VvcjhITzJQR2c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= public_key: LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQTVjUGhaK3Z5NjdOTWw4cGwyeEpZb0g1RTk5dGtVM1ZQckxoZ1ZITjJqbmZqMkZYTW9RdVQKSm1CTnVlRHdxYlRaSkRCZkNHdW01NkVXMytWK0FLbGN6RTVXRUtjQUdMMS92bzhlNDQzQUZsM0F0R1VRSWRHVwo0aC9KNENwUzRvYnVVZTBrdytCYmZ5aHFCdmtpUzZsbVFKY1dIS283dmlmYUUySnNYZmUrREpJUjEvNHhvYjlrCm5MNUFlaEUvV1duQmNEYll0TnB5RThwaEM3Rmg4M2JhVHZXam1zVkxGWmlhMysyai9ueWlkc1JlMTRjMnR4czQKVkV6elFaWmEzMDJaREhUaUZjODMybWN5QkxBc0ZzMnBzbEpMYlJ2S3ZZQUtXS050UXJ0ZUhyMFBQZmphQjM3cgpjVjF5S2EyTjYzUU52a3dhUTFpZ25ZakphN25UUGFqVUp3SURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K additional_trusted_keys: {} cluster_name: me.localhost jwt_key_pairs: - private_key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNWNQaFordnk2N05NbDhwbDJ4SllvSDVFOTl0a1UzVlByTGhnVkhOMmpuZmoyRlhNCm9RdVRKbUJOdWVEd3FiVFpKREJmQ0d1bTU2RVczK1YrQUtsY3pFNVdFS2NBR0wxL3ZvOGU0NDNBRmwzQXRHVVEKSWRHVzRoL0o0Q3BTNG9idVVlMGt3K0JiZnlocUJ2a2lTNmxtUUpjV0hLbzd2aWZhRTJKc1hmZStESklSMS80eApvYjlrbkw1QWVoRS9XV25CY0RiWXROcHlFOHBoQzdGaDgzYmFUdldqbXNWTEZaaWEzKzJqL255aWRzUmUxNGMyCnR4czRWRXp6UVpaYTMwMlpESFRpRmM4MzJtY3lCTEFzRnMycHNsSkxiUnZLdllBS1dLTnRRcnRlSHIwUFBmamEKQjM3cmNWMXlLYTJONjNRTnZrd2FRMWlnbllqSmE3blRQYWpVSndJREFRQUJBb0lCQUY0b1ZMSUt2bVVhK0RObwpMUytHcUMwMU1ieEUreXM4Y3VjOE03WElEM2k0NXZWYnk5emZhbkVhbkIrbGI5cU1FMFJDVWwrWUJqRDhFZXkxCkZscmREUHRveXRwT0picjl4V0RwTStaYXk3SWV2Mzd0djV1c1VXSGZWeEozSmJwUlEwN3RtTmh3ays1Yk9JQWQKRHBIbEhOTXhWMDF0OGNldWV5N2djYnBjY1ZTaXJNYy9ERTlGcFNONFRRWFloMlRDSnVZcTdncFo1SGJ2MlpleApDOHFTTnZqVVlVTithTGlDZ2pBRFhZdGdmV2RUVWN0YkhldVUrL2Ftb3cyUitOVjFWMGpML3lOQm0rdmZUVXNuCmprNFMxQWx1U3FrT0FFaHJhZjM2UzRNcWZQWm8zbXhFOC96TVZTdEY5MTdvQVRKMlZrNzVZaGxINnMzM1BKN1kKMXdDeVdZa0NnWUVBOFRoT1E4M01xTjFTL1VVcUJWeGRaaS9HZFBldmQrbFUvY2FIQVhTNmJyd2RYNEpITTkrbApaczZVWFdocWJHUXFSVlRvZ1dna2VBSHp4R3I1S2QvSTlRRnhEZHE2am1vRUhmU3VNNnNiNU4yVjgyQTVSaUhHCmduS3IzMUpWQS9FdEt3Vk5sZUY3L1VUM0tzWTJzajRZNFkxRnI3amRkOS9tdTVaNWVwODFidFVDZ1lFQTg5Zm0KSE9tc0JYZUQ0M0FHVmltSFhEWWVGckI3UWRhY3hOQ3R4NjZuTk5jMFpxM0pDZGtDMjVaTG1nQytON2JEbkRjMApNZ2NPUGdyUWxVdWpRbWl1d1daeFlXcDAvWDh3STFHZ2p4RkEvRkJvNjFnd3lwR2tqbjJlS3lVM1ovWlJBRTBjCmlxaW5UTVc5TFBlV1k0MEhOUzFic3E2YmFoeXg1bTFVV0gxclRRc0NnWUF3bE1WMmRHbEdqU1NjcTZSVjVnOU4KZUV2QTNPMXkrZ1JMQkFQR3NFcW42SzBGd2tneTAxVU5pb2RvOUpHU2VPM21mcjVBNmNlR2YrWW5aZC8rcGZwawpGY0UrS0JJd2dudUh5UEtZcDFwNzBvRFR2a3Bxckh5OVl2am9oajFuQ05pdTlHZDJ5eTNjaVZvNlBDZGg2STI4ClIyYUVpSGZhSDdicGl0bTJiNEFrYlFLQmdRQ2tmUUpraEppZkEzVTdpa2tyL0Uyc1BYRmttdDQ2bG53Z0pDam0KSjRIeG1pNW1DVnN4UW11MEZ4bWVwRnVzbDZReWorYXN6S2VsNElPK0FrejZNa1dZZnZPQzVGNVExbWh4bXRHMQpVTTFHcHpOdmRvbExUSjMxNVBVNlk1dVJqTTR0WnRjWERoZjFLUHFwQjhjeUZtTkRVdnFsZVRXcmlmblQxL0pxCjB3Zjc2d0tCZ1FDN3hIK3NXbTR6cjVRb1VvMWlrYlRMSHRBaVFETWE2TlpWTTk0MHcxZHExUWk4YXpMTE1lMUcKUVduU1ZqTDVLaVlSY0toSjVYOGpoQjF5a2U1OTR1ZU1CSFJRdjFRU3RCalRkSzIxN2tnTTFTRmZEbzFFQWpRdgpMWk5pWHROYXVDeG9BMi96SHJrZ0d0NGQzQldKZk5ENkJPQ1RKK2lSRnNWc1VvcjhITzJQR2c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= public_key: LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQTVjUGhaK3Z5NjdOTWw4cGwyeEpZb0g1RTk5dGtVM1ZQckxoZ1ZITjJqbmZqMkZYTW9RdVQKSm1CTnVlRHdxYlRaSkRCZkNHdW01NkVXMytWK0FLbGN6RTVXRUtjQUdMMS92bzhlNDQzQUZsM0F0R1VRSWRHVwo0aC9KNENwUzRvYnVVZTBrdytCYmZ5aHFCdmtpUzZsbVFKY1dIS283dmlmYUUySnNYZmUrREpJUjEvNHhvYjlrCm5MNUFlaEUvV1duQmNEYll0TnB5RThwaEM3Rmg4M2JhVHZXam1zVkxGWmlhMysyai9ueWlkc1JlMTRjMnR4czQKVkV6elFaWmEzMDJaREhUaUZjODMybWN5QkxBc0ZzMnBzbEpMYlJ2S3ZZQUtXS050UXJ0ZUhyMFBQZmphQjM3cgpjVjF5S2EyTjYzUU52a3dhUTFpZ25ZakphN25UUGFqVUp3SURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K type: jwt sub_kind: jwt version: v2` openSSHCAYAML = `kind: cert_authority metadata: id: 1630513579536527000 name: me.localhost spec: active_keys: ssh: - private_key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBeHpRdWtjUlhCR09nRDUrS3B1MGRrdVpXVjVsc294Wlp1cFRhRHdFcmYvQ1prOWFqCjRaZzJoeTBoUHl5SXRBRytIN08xRVYwZ0taWVRDWWs4REtiaFQ4ZWl3QldDNUhKaGtpYW9ZRStSUXpQTHd3STcKcElmb0t4ejR2WG5MRnNvWHVVY2hLTmhYdk1RZU9pVVJ3OVQ3L0RSREE0dDdsazlPY01jWE9ySHV3L2JxTTA0VApmTmhVeXhHVmhyTC90aFpQNDE4b04xZjZKQzZMU3grZThIM01iSEQralVGMjBVbU8wVUFLUHUwRXVQS3hkS2loCm1tbVZDZ3FaRWhBWjhrcklVYlV5R0tUemZjQyt4VFNrUEN3UE1qejg1aTJQVVBYS3hmVE00cW54QVh5VzRCSzYKeXo1Znk5UTgyalVESi9VS0JvSU92N1hUU2JhNUpDUHdiUUx4cHdJREFRQUJBb0lCQUJydU53MkYyYTNDT2pWaQpnRUFvOWtLUjJVSm1mNFZjMUN5aFN3bVVRdWs5QWNZMjBsa0JWdjNYWUJOR1ZnVGY1M0FwdjJUbGpoK1JKbW0zCm4rS2wvUGZvS1Z5R2kvZU9ieHB2RjN4TnhYbXNXdk8yTFpJRXZhSjJmRHBCYU85Znl1MUZiSG8xSlVkanpDSlkKT0pxZEJLUUgvTGRSK0JkT0NYQzl1YW81dStuS0RxZTZvaE1FZm5VUUpmeGd4QzNwMHhzRGkvNGovYWhCV3cvaApoZk02TUloaUlmZEhWNlN1MWlPQWlNbXNtcDBWSkd4OVR5SG9UZFdKWHkxYjRTNDh5L21rNjdNeThzUDJ0SXRHCksvdGxaaDFteDYwbCtscnZkTDZFVW9CVDVScXdCZDBCN0g3c1V0UkZjVVVkMVZBNXpMYVowYnhkbHVpL1dEMlcKaHVtOHJpRUNnWUVBN0hPZlN3OVRKQzIxSHFwb2kvUHRKOGZ5QjVMRXJaQTBmY21vd2VtMG55dTRyMVkxN2FSTwp6bU5qYmZtYWRrOU95RnJNaS9Ydm5pbW1wcnNmK3Z1dEVkeEJEK3JYMGJPZk52ZEl5NHNMNEFaN2JZSkdUVEk0ClVFREdPRVF2UkpUQmxhQ3hvMGVEcGVSbTZqamhjYjdmcHV6K3ZCaVB2ZFBHbExxU0JuaTczSDBDZ1lFQTE2dzYKazhzUDlCK251MFFkSVdRano0MU9wTzJlb2FKenhndFVRTnkyV0VuRi9ydk51K21ra1BtL0lWbWZvdzFOYXAwNApGL29FeHpUSWw2WkFCcUdjWUtHVDRNSjRlVS9QT2RsbWZtSE5DcThHTGs4TlRBaC9UdVFuZ0VKTDB2UE9Ta0wzCjMrWHg2N010MDhVUXNmRjlDQU8yS3BuQzhOOEw5NmhjOXp6ZTgvTUNnWUEwakptNVQ4V1ZnOGI5OHJkYmF6R28KcHFvbWZyclJLL3hPZkZQU0RNT0VvRzNpSWRISVo3elA1NHpBY3ptZDA1Qlp2THc2MnNTUExRaUpnNHJlOTdJRwpCeUk2akdHOGpDUDFUazNTVnF1ajlTelhNSjI1S0ZFVm5OK3d2NDZWdWsydm1GQUNUckYyVytWM1puN01EYlNjCjM0elpkc2Z6VXk2Ti9Vell2VnBhN1FLQmdRQ3NOZC9JSnpxajZhcmJBdlpudFRoTEFFQXR2WGNQQlZLQWJvZG0KQzFhbWhMSE9SMU50bXBCSEdzU2M4cDFmYXIzSVJhV0dyNktsRmVhZUFLZmJJNnhrRkdDcDlWNlJMMEwrcERNTQo4emJ3TXZVeWdQalRIMjNZSnFITDdpUHhXNi82NkNKWTY1a1NaVTVRYkdoNlRhTlNoUFF1YS95V3JPTTNhMzVnCkJJRGFOUUtCZ0dBSE5EOWVjcGpySElxUFgzekZUYm1uZjZORWl1SmN0aHpPN0hLZzI0Slh3V0FoRWJBaXpUYjQKRnY1VmY0d2dKc2xGSjFmNDdhVjJxSU5ncWJ5ajBMSlNjYksyOGlXVmIwZmxSbnJEZnd0amxaS2lneWRDTlZubApteUNBS2IyOTlZL1d0Y3NobjUxa29DdWtzSVA1VzJwZjk5M2pLbEw0OWNPN0l6N3VKbTEzCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== public_key: c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFESE5DNlJ4RmNFWTZBUG40cW03UjJTNWxaWG1XeWpGbG02bE5vUEFTdC84Sm1UMXFQaG1EYUhMU0UvTElpMEFiNGZzN1VSWFNBcGxoTUppVHdNcHVGUHg2TEFGWUxrY21HU0pxaGdUNUZETTh2REFqdWtoK2dySFBpOWVjc1d5aGU1UnlFbzJGZTh4QjQ2SlJIRDFQdjhORU1EaTN1V1QwNXd4eGM2c2U3RDl1b3pUaE44MkZUTEVaV0dzdisyRmsvalh5ZzNWL29rTG90TEg1N3dmY3hzY1A2TlFYYlJTWTdSUUFvKzdRUzQ4ckYwcUtHYWFaVUtDcGtTRUJueVNzaFJ0VElZcFBOOXdMN0ZOS1E4TEE4eVBQem1MWTlROWNyRjlNemlxZkVCZkpiZ0VyckxQbC9MMUR6YU5RTW45UW9HZ2c2L3RkTkp0cmtrSS9CdEF2R24K additional_trusted_keys: {} checking_keys: - c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFESE5DNlJ4RmNFWTZBUG40cW03UjJTNWxaWG1XeWpGbG02bE5vUEFTdC84Sm1UMXFQaG1EYUhMU0UvTElpMEFiNGZzN1VSWFNBcGxoTUppVHdNcHVGUHg2TEFGWUxrY21HU0pxaGdUNUZETTh2REFqdWtoK2dySFBpOWVjc1d5aGU1UnlFbzJGZTh4QjQ2SlJIRDFQdjhORU1EaTN1V1QwNXd4eGM2c2U3RDl1b3pUaE44MkZUTEVaV0dzdisyRmsvalh5ZzNWL29rTG90TEg1N3dmY3hzY1A2TlFYYlJTWTdSUUFvKzdRUzQ4ckYwcUtHYWFaVUtDcGtTRUJueVNzaFJ0VElZcFBOOXdMN0ZOS1E4TEE4eVBQem1MWTlROWNyRjlNemlxZkVCZkpiZ0VyckxQbC9MMUR6YU5RTW45UW9HZ2c2L3RkTkp0cmtrSS9CdEF2R24K cluster_name: me.localhost signing_alg: 3 signing_keys: - LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBeHpRdWtjUlhCR09nRDUrS3B1MGRrdVpXVjVsc294Wlp1cFRhRHdFcmYvQ1prOWFqCjRaZzJoeTBoUHl5SXRBRytIN08xRVYwZ0taWVRDWWs4REtiaFQ4ZWl3QldDNUhKaGtpYW9ZRStSUXpQTHd3STcKcElmb0t4ejR2WG5MRnNvWHVVY2hLTmhYdk1RZU9pVVJ3OVQ3L0RSREE0dDdsazlPY01jWE9ySHV3L2JxTTA0VApmTmhVeXhHVmhyTC90aFpQNDE4b04xZjZKQzZMU3grZThIM01iSEQralVGMjBVbU8wVUFLUHUwRXVQS3hkS2loCm1tbVZDZ3FaRWhBWjhrcklVYlV5R0tUemZjQyt4VFNrUEN3UE1qejg1aTJQVVBYS3hmVE00cW54QVh5VzRCSzYKeXo1Znk5UTgyalVESi9VS0JvSU92N1hUU2JhNUpDUHdiUUx4cHdJREFRQUJBb0lCQUJydU53MkYyYTNDT2pWaQpnRUFvOWtLUjJVSm1mNFZjMUN5aFN3bVVRdWs5QWNZMjBsa0JWdjNYWUJOR1ZnVGY1M0FwdjJUbGpoK1JKbW0zCm4rS2wvUGZvS1Z5R2kvZU9ieHB2RjN4TnhYbXNXdk8yTFpJRXZhSjJmRHBCYU85Znl1MUZiSG8xSlVkanpDSlkKT0pxZEJLUUgvTGRSK0JkT0NYQzl1YW81dStuS0RxZTZvaE1FZm5VUUpmeGd4QzNwMHhzRGkvNGovYWhCV3cvaApoZk02TUloaUlmZEhWNlN1MWlPQWlNbXNtcDBWSkd4OVR5SG9UZFdKWHkxYjRTNDh5L21rNjdNeThzUDJ0SXRHCksvdGxaaDFteDYwbCtscnZkTDZFVW9CVDVScXdCZDBCN0g3c1V0UkZjVVVkMVZBNXpMYVowYnhkbHVpL1dEMlcKaHVtOHJpRUNnWUVBN0hPZlN3OVRKQzIxSHFwb2kvUHRKOGZ5QjVMRXJaQTBmY21vd2VtMG55dTRyMVkxN2FSTwp6bU5qYmZtYWRrOU95RnJNaS9Ydm5pbW1wcnNmK3Z1dEVkeEJEK3JYMGJPZk52ZEl5NHNMNEFaN2JZSkdUVEk0ClVFREdPRVF2UkpUQmxhQ3hvMGVEcGVSbTZqamhjYjdmcHV6K3ZCaVB2ZFBHbExxU0JuaTczSDBDZ1lFQTE2dzYKazhzUDlCK251MFFkSVdRano0MU9wTzJlb2FKenhndFVRTnkyV0VuRi9ydk51K21ra1BtL0lWbWZvdzFOYXAwNApGL29FeHpUSWw2WkFCcUdjWUtHVDRNSjRlVS9QT2RsbWZtSE5DcThHTGs4TlRBaC9UdVFuZ0VKTDB2UE9Ta0wzCjMrWHg2N010MDhVUXNmRjlDQU8yS3BuQzhOOEw5NmhjOXp6ZTgvTUNnWUEwakptNVQ4V1ZnOGI5OHJkYmF6R28KcHFvbWZyclJLL3hPZkZQU0RNT0VvRzNpSWRISVo3elA1NHpBY3ptZDA1Qlp2THc2MnNTUExRaUpnNHJlOTdJRwpCeUk2akdHOGpDUDFUazNTVnF1ajlTelhNSjI1S0ZFVm5OK3d2NDZWdWsydm1GQUNUckYyVytWM1puN01EYlNjCjM0elpkc2Z6VXk2Ti9Vell2VnBhN1FLQmdRQ3NOZC9JSnpxajZhcmJBdlpudFRoTEFFQXR2WGNQQlZLQWJvZG0KQzFhbWhMSE9SMU50bXBCSEdzU2M4cDFmYXIzSVJhV0dyNktsRmVhZUFLZmJJNnhrRkdDcDlWNlJMMEwrcERNTQo4emJ3TXZVeWdQalRIMjNZSnFITDdpUHhXNi82NkNKWTY1a1NaVTVRYkdoNlRhTlNoUFF1YS95V3JPTTNhMzVnCkJJRGFOUUtCZ0dBSE5EOWVjcGpySElxUFgzekZUYm1uZjZORWl1SmN0aHpPN0hLZzI0Slh3V0FoRWJBaXpUYjQKRnY1VmY0d2dKc2xGSjFmNDdhVjJxSU5ncWJ5ajBMSlNjYksyOGlXVmIwZmxSbnJEZnd0amxaS2lneWRDTlZubApteUNBS2IyOTlZL1d0Y3NobjUxa29DdWtzSVA1VzJwZjk5M2pLbEw0OWNPN0l6N3VKbTEzCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== type: openssh sub_kind: openssh version: v2` samlCAYAML = `kind: cert_authority metadata: id: 1640648663670002000 name: me.localhost spec: active_keys: tls: - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURpakNDQW5LZ0F3SUJBZ0lRRU9IcEhIZkZwZ28wUndQSVJhdkdpakFOQmdrcWhraUc5dzBCQVFzRkFEQmYKTVJVd0V3WURWUVFLRXd4dFpTNXNiMk5oYkdodmMzUXhGVEFUQmdOVkJBTVRERzFsTG14dlkyRnNhRzl6ZERFdgpNQzBHQTFVRUJSTW1NakkwTkRBMk5ESTNPREkyTWpJNE5UQXdOemMzTmpVeU16STVOak15TWpNeU56VXhORFl3CkhoY05NakV4TWpJM01qTTBOREl6V2hjTk16RXhNakkxTWpNME5ESXpXakJmTVJVd0V3WURWUVFLRXd4dFpTNXMKYjJOaGJHaHZjM1F4RlRBVEJnTlZCQU1UREcxbExteHZZMkZzYUc5emRERXZNQzBHQTFVRUJSTW1NakkwTkRBMgpOREkzT0RJMk1qSTROVEF3TnpjM05qVXlNekk1TmpNeU1qTXlOelV4TkRZd2dnRWlNQTBHQ1NxR1NJYjNEUUVCCkFRVUFBNElCRHdBd2dnRUtBb0lCQVFETFRrVFkzQ0NMVStNUllkbEMwM2NUTTR6MUpiRGoxYjFQRWdING9iSmwKRjl4NWtQbzhncWNEbmp5L0x5NHdKeUR2Q2xPMkw1T0k3UnYwa1hFUXoybUVEeExnbjJYRG9ZNUh5VFNOVkZHNgpvZ3BlYmhlUFN1aWl0RUNZYUZDZVZFTGNDa1Q0ZGpqRDlwOExNTnJ4MHRPOXdQU1o1OXBLZUxCOG90RFloOHRCCkcyb2EzSGIzTWt0RGxOY0svVE94RFNzRzUrQ2ljdktTa3QrV04xaXJJQ2pvZ2hWTzJGcForRkdxWUM0Y1EwbWMKM0NRaGJwY1o2VTRkWnpGdFJZVzZPYzNucHBOSkZKWXZSSTRIS1FWY0RCM2N4VkhNTUd5Rzc3aFRzdEwvd0RuaQo4U2s5eml4VzN4S2FvUnlrV2FuWno4eC9WdHNydXJqanNzNDV4NlRoem1VWkFnTUJBQUdqUWpCQU1BNEdBMVVkCkR3RUIvd1FFQXdJQnBqQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01CMEdBMVVkRGdRV0JCUmNKK2NRamFQWjZGbEIKcVhoYzYyWXZldGRpQWpBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQVdYNmxZdUhtMmQyMU41RDN1eUJJelFOdApKVFR3b0xnU3FQd09Tbk1EU0luSDkvMjBqZDNGUk9qakh1M3BQRkNLRmE4OVp0ekZxTHZsTjVOdlh2WHFuOXNKCkdudTYzSVo0TWtEZk9sSVZpWFhQWFF4YllHSkMxRVlVU28rTDdtUTY0VnN5UkFpTXdnbmVwMUxwSGhROGYzU2MKeEZoVkNybFJDMmUrNENBai8vOVZWaDdvTEdSMkNhM0xEcFc5VHFxYnB3MEh0QitNcFVqVWxCWnFVbzNVMm5HTQpia1VhSVZKcnNuYk1rYnNsUGQ2dWtVRDlVTHFuUmxJb3A4cjQ1VTdvYVBhR3g3QVFiWndzbGlsNVVJZlppRmlRCm5USk9kYnJHampVdXlRYkM4UUpZY3RhdENjbVBjZUlXMVVWWFVnZ2JsdXl4VjF1NWsyYzlSb1k2RzhiN0FRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBeTA1RTJOd2dpMVBqRVdIWlF0TjNFek9NOVNXdzQ5VzlUeElCK0tHeVpSZmNlWkQ2ClBJS25BNTQ4dnk4dU1DY2c3d3BUdGkrVGlPMGI5SkZ4RU05cGhBOFM0SjlsdzZHT1I4azBqVlJSdXFJS1htNFgKajByb29yUkFtR2hRbmxSQzNBcEUrSFk0dy9hZkN6RGE4ZExUdmNEMG1lZmFTbml3ZktMUTJJZkxRUnRxR3R4Mgo5ekpMUTVUWEN2MHpzUTByQnVmZ29uTHlrcExmbGpkWXF5QW82SUlWVHRoYVdmaFJxbUF1SEVOSm5Od2tJVzZYCkdlbE9IV2N4YlVXRnVqbk41NmFUU1JTV0wwU09CeWtGWEF3ZDNNVlJ6REJzaHUrNFU3TFMvOEE1NHZFcFBjNHMKVnQ4U21xRWNwRm1wMmMvTWYxYmJLN3E0NDdMT09jZWs0YzVsR1FJREFRQUJBb0lCQVFDeXNLNXVkTHZkK2ZOQQpHZUtkaThQRENySS8zY3JsMWIwNFBEbWpVR3U5MHdVampEdUU1OGpuc3pMdFR3aW5waHlhUFZkcWI5S2FyTnkvClR2NHpxam14cXBZSys4Nno3ZEZpWXdSZm05YmgxUDZNRlBOOExIamdXTkhWb3dvSXYwS3NxQklLMTgzNDMxRFcKd3pBTkVDS3ZTMk14eXNqZ1g4ZXZKR092alZzbWN1SU1IWFljbVlORlo0dWpuTnc0dnVSWHhlYUtPY2ZWTGZ2ZwoxSWZMK05IYUh6UXI1YVoydkxST1NpdHVId2cvOFpZZ2hQcVFYLy92ak92M0FENzhXVGxINUZXWjExR0hoeCt1Ck9ZUXcwQ1lvTnNiV1UxeklwVTV0cHdCcDRGV0VlVy9jbTY5NXRBVVRlMzR1N3R1MlJJOVBZMEZDWVJ2ZkM2SUQKK2tDNjRFTEpBb0dCQVBSTEt5a0pjdHNGNngzN1liWXdmQm9ZK3V6YU4wV1o1d1RuRmVFaVp1ZzVVYWJUOTdqRApZTnpaYzQ0aitRbkxFUGVDak5tU1FhVHFxK2QvbUVjTnlXS244cVNFcXlOR0R6YlRXQ2tkMGtyWTVwcm9FVVJnClFqbWFwRHFNNEtOSm9jQ1RCS3lueHo2QzZ6ME9uSnhZM3lMdTdyYVBqeE9HTW5rcm9VMDhMVW56QW9HQkFOVU0KU2NsNGh1R0gxK3ZNL0RtenJoQ1NKbUZIcjE2QjhvWExaMHIyNmFKTnJVQ3AwYUFzV2FHd3JLZXZFcDkzRmg5Ygp3QlZYMHE0bXJ3cmZpTGpCYnRzdnlQM3l1ZWs5cWl6M2ZoMWIvZ0k2eWRKVFEzMEplQzB4UzUyRksvR2gwanEvCm43c2Y1bm5DbTlCRW01ZmdSeDFVUmVhOC9vL2M4cTNhbW94c0grdkRBb0dBZUVZUjU5QlpGZkJpQTQ3aVdwcWcKWHhEeGFXOCtTeXdzaTBOaWlFY3h0eCtSVGJ1S2VSTG9PNU5yeXcxMjdSVm5NeFM1Vjkwa0tKZkpMdDZwRUVKLwpaZTBlRDFXcUZHSEgxOHhSMlZ4dlRwNWZXdURxcjJsYzhaTnJTOUJVUU5CZHJMdzFUdlFEcW9rMlhBYzNuOW81CmNhK0ZJNmltWG94eGlTcXI3YVMwLzNVQ2dZQTJBWEJ1N3V1YUhocGcvc3h0UUJ2K3ZWMlhTVm11SmxpNUM4KzYKVkE3emdxZEpmZ0xTakl1SURrWW1GNTRyNkQ4bVlkYTJVbFhvcVl1endPaGlsVDRwdDlwR2JaSXRDdUdwbG05VQp0KzRTMko0eWY4TGEzbHlsY0JxUDZxTXlGR2c3VmpvQ2NGcTNRTnJJbDZ1dGV6L3JzbUlwMUh6Zk1RNGZmZ3V4ClR2Tmtpd0tCZ1FDbVhaSStvTUdQK3U4KzRVaGxjR01NYUNHZi92UVZLdVJYOHlOYVh1bUx6dk1Xajl0cVhpUzcKK1dQUlhuV01RSnd1QldYMzBTcWdFVVdDbjlzOGxWbzh2TVF4MmFtbXhhWkVEMGRoOHNMSkJDNXJoRmVqV29MbQp3cHg5MXR0S3JJODBKMDYyeW90SFpJYkRyQW1LSGZFeE85U1d4T1hUeFVMUGdvTlJVUW5qSnc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= additional_trusted_keys: {} cluster_name: me.localhost signing_alg: 3 type: saml_idp sub_kind: saml_idp version: v2` ) func TestInit_bootstrap(t *testing.T) { t.Parallel() hostCA := resourceFromYAML(t, hostCAYAML).(types.CertAuthority) userCA := resourceFromYAML(t, userCAYAML).(types.CertAuthority) jwtCA := resourceFromYAML(t, jwtCAYAML).(types.CertAuthority) dbCA := resourceFromYAML(t, databaseCAYAML).(types.CertAuthority) osshCA := resourceFromYAML(t, openSSHCAYAML).(types.CertAuthority) samlCA := resourceFromYAML(t, samlCAYAML).(types.CertAuthority) invalidHostCA := resourceFromYAML(t, hostCAYAML).(types.CertAuthority) invalidHostCA.(*types.CertAuthorityV2).Spec.ActiveKeys.SSH = nil invalidUserCA := resourceFromYAML(t, userCAYAML).(types.CertAuthority) invalidUserCA.(*types.CertAuthorityV2).Spec.ActiveKeys.SSH = nil invalidJWTCA := resourceFromYAML(t, jwtCAYAML).(types.CertAuthority) invalidJWTCA.(*types.CertAuthorityV2).Spec.ActiveKeys.JWT = nil invalidDBCA := resourceFromYAML(t, databaseCAYAML).(types.CertAuthority) invalidDBCA.(*types.CertAuthorityV2).Spec.ActiveKeys.TLS = nil invalidOSSHCA := resourceFromYAML(t, openSSHCAYAML).(types.CertAuthority) invalidOSSHCA.(*types.CertAuthorityV2).Spec.ActiveKeys.SSH = nil invalidSAMLCA := resourceFromYAML(t, samlCAYAML).(types.CertAuthority) invalidSAMLCA.(*types.CertAuthorityV2).Spec.ActiveKeys.TLS = nil tests := []struct { name string modifyConfig func(*InitConfig) assertError require.ErrorAssertionFunc }{ { // Issue https://github.com/gravitational/teleport/issues/7853. name: "OK bootstrap CAs", modifyConfig: func(cfg *InitConfig) { cfg.BootstrapResources = append( cfg.BootstrapResources, hostCA.Clone(), userCA.Clone(), jwtCA.Clone(), dbCA.Clone(), osshCA.Clone(), samlCA.Clone(), ) }, assertError: require.NoError, }, { name: "NOK bootstrap Host CA missing keys", modifyConfig: func(cfg *InitConfig) { cfg.BootstrapResources = append( cfg.BootstrapResources, invalidHostCA.Clone(), userCA.Clone(), jwtCA.Clone(), dbCA.Clone(), osshCA.Clone(), samlCA.Clone(), ) }, assertError: require.Error, }, { name: "NOK bootstrap User CA missing keys", modifyConfig: func(cfg *InitConfig) { cfg.BootstrapResources = append( cfg.BootstrapResources, hostCA.Clone(), invalidUserCA.Clone(), jwtCA.Clone(), dbCA.Clone(), osshCA.Clone(), samlCA.Clone(), ) }, assertError: require.Error, }, { name: "NOK bootstrap JWT CA missing keys", modifyConfig: func(cfg *InitConfig) { cfg.BootstrapResources = append( cfg.BootstrapResources, hostCA.Clone(), userCA.Clone(), invalidJWTCA.Clone(), dbCA.Clone(), osshCA.Clone(), samlCA.Clone(), ) }, assertError: require.Error, }, { name: "NOK bootstrap Database CA missing keys", modifyConfig: func(cfg *InitConfig) { cfg.BootstrapResources = append( cfg.BootstrapResources, hostCA.Clone(), userCA.Clone(), jwtCA.Clone(), invalidDBCA.Clone(), osshCA.Clone(), samlCA.Clone(), ) }, assertError: require.Error, }, { name: "NOK bootstrap OpenSSH CA missing keys", modifyConfig: func(cfg *InitConfig) { cfg.BootstrapResources = append( cfg.BootstrapResources, hostCA.Clone(), userCA.Clone(), jwtCA.Clone(), dbCA.Clone(), invalidOSSHCA.Clone(), samlCA.Clone(), ) }, assertError: require.Error, }, { name: "NOK bootstrap SAML IdP CA missing keys", modifyConfig: func(cfg *InitConfig) { cfg.BootstrapResources = append( cfg.BootstrapResources, hostCA.Clone(), userCA.Clone(), jwtCA.Clone(), dbCA.Clone(), osshCA.Clone(), invalidSAMLCA.Clone(), ) }, assertError: require.Error, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { cfg := setupConfig(t) test.modifyConfig(&cfg) _, err := Init(cfg) test.assertError(t, err) }) } } const ( userYAML = `kind: user version: v2 metadata: name: joe spec: roles: ["admin"]` tokenYAML = `kind: token version: v2 metadata: name: github-token expires: "3000-01-01T00:00:00Z" spec: roles: [Bot] join_method: github bot_name: github-demo github: allow: - repository: gravitational/example` ) func TestInit_ApplyOnStartup(t *testing.T) { t.Parallel() user := resourceFromYAML(t, userYAML).(types.User) token := resourceFromYAML(t, tokenYAML).(types.ProvisionToken) tests := []struct { name string modifyConfig func(*InitConfig) assertError require.ErrorAssertionFunc }{ { name: "Apply unsupported resource", modifyConfig: func(cfg *InitConfig) { cfg.ApplyOnStartupResources = append(cfg.ApplyOnStartupResources, user) }, assertError: require.Error, }, { name: "Apply ProvisionToken", modifyConfig: func(cfg *InitConfig) { cfg.ApplyOnStartupResources = append(cfg.ApplyOnStartupResources, token) }, assertError: require.NoError, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { cfg := setupConfig(t) test.modifyConfig(&cfg) _, err := Init(cfg) test.assertError(t, err) }) } } func resourceFromYAML(t *testing.T, value string) types.Resource { t.Helper() ur := &services.UnknownResource{} err := kyaml.NewYAMLToJSONDecoder(strings.NewReader(value)).Decode(ur) require.NoError(t, err) resource, err := services.UnmarshalResource(ur.Kind, ur.Raw) require.NoError(t, err) return resource } func resourceDiff(res1, res2 types.Resource) string { return cmp.Diff(res1, res2, cmpopts.IgnoreFields(types.Metadata{}, "ID", "Namespace"), cmpopts.EquateEmpty()) } // TestIdentityChecker verifies auth identity properly validates host // certificates when connecting to an SSH server. func TestIdentityChecker(t *testing.T) { ctx := context.Background() conf := setupConfig(t) authServer, err := Init(conf) require.NoError(t, err) t.Cleanup(func() { authServer.Close() }) lockWatcher, err := services.NewLockWatcher(ctx, services.LockWatcherConfig{ ResourceWatcherConfig: services.ResourceWatcherConfig{ Component: teleport.ComponentAuth, Client: authServer, }, }) require.NoError(t, err) authServer.SetLockWatcher(lockWatcher) clusterName, err := authServer.GetDomainName() require.NoError(t, err) ca, err := authServer.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: clusterName, }, true) require.NoError(t, err) signers, err := sshutils.GetSigners(ca) require.NoError(t, err) require.Len(t, signers, 1) realCert, err := apisshutils.MakeRealHostCert(signers[0]) require.NoError(t, err) spoofedCert, err := apisshutils.MakeSpoofedHostCert(signers[0]) require.NoError(t, err) tests := []struct { desc string cert ssh.Signer err bool }{ { desc: "should be able to connect with real cert", cert: realCert, err: false, }, { desc: "should not be able to connect with spoofed cert", cert: spoofedCert, err: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { handler := sshutils.NewChanHandlerFunc(func(_ context.Context, ccx *sshutils.ConnectionContext, nch ssh.NewChannel) { ch, _, err := nch.Accept() require.NoError(t, err) require.NoError(t, ch.Close()) }) sshServer, err := sshutils.NewServer( "test", utils.NetAddr{AddrNetwork: "tcp", Addr: "localhost:0"}, handler, []ssh.Signer{test.cert}, sshutils.AuthMethods{NoClient: true}, sshutils.SetInsecureSkipHostValidation(), ) require.NoError(t, err) t.Cleanup(func() { sshServer.Close() }) require.NoError(t, sshServer.Start()) identity, err := GenerateIdentity(authServer, IdentityID{ Role: types.RoleNode, HostUUID: uuid.New().String(), NodeName: "node-1", }, nil, nil) require.NoError(t, err) sshClientConfig, err := identity.SSHClientConfig(false) require.NoError(t, err) dialer := proxy.DialerFromEnvironment(sshServer.Addr()) sconn, err := dialer.Dial(ctx, "tcp", sshServer.Addr(), sshClientConfig) if test.err { require.Error(t, err) } else { require.NoError(t, err) require.NoError(t, sconn.Close()) } }) } } func TestInitCreatesCertsIfMissing(t *testing.T) { conf := setupConfig(t) auth, err := Init(conf) require.NoError(t, err) t.Cleanup(func() { err = auth.Close() require.NoError(t, err) }) ctx := context.Background() for _, caType := range types.CertAuthTypes { cert, err := auth.GetCertAuthorities(ctx, caType, false) require.NoError(t, err) require.Len(t, cert, 1) } } func TestMigrateDatabaseCA(t *testing.T) { conf := setupConfig(t) // Create only HostCA and UserCA. DatabaseCA should be created on Init(). hostCA := suite.NewTestCA(types.HostCA, "me.localhost") userCA := suite.NewTestCA(types.UserCA, "me.localhost") conf.Authorities = []types.CertAuthority{hostCA, userCA} // Here is where migration happens. auth, err := Init(conf) require.NoError(t, err) t.Cleanup(func() { err = auth.Close() require.NoError(t, err) }) dbCAs, err := auth.GetCertAuthorities(context.Background(), types.DatabaseCA, true) require.NoError(t, err) require.Len(t, dbCAs, 1) require.Equal(t, hostCA.Spec.ActiveKeys.TLS[0].Cert, dbCAs[0].GetActiveKeys().TLS[0].Cert) require.Equal(t, hostCA.Spec.ActiveKeys.TLS[0].Key, dbCAs[0].GetActiveKeys().TLS[0].Key) } func TestRotateDuplicatedCerts(t *testing.T) { ctx := context.Background() conf := setupConfig(t) // suite.NewTestCA() uses the same SSH key for all created keys, which in this scenario triggers extra CA rotation. keygen := keygen.New(ctx) privHost, _, err := keygen.GenerateKeyPair() require.NoError(t, err) privUser, _, err := keygen.GenerateKeyPair() require.NoError(t, err) hostCA := suite.NewTestCA(types.HostCA, "me.localhost", privHost) userCA := suite.NewTestCA(types.UserCA, "me.localhost", privUser) // Create duplicated CAs databaseCA := suite.NewTestCA(types.DatabaseCA, "me.localhost", privHost) databaseCA.Spec.ActiveKeys = hostCA.Spec.ActiveKeys.Clone() conf.Authorities = []types.CertAuthority{hostCA, userCA, databaseCA} auth, err := Init(conf) require.NoError(t, err) t.Cleanup(func() { err = auth.Close() require.NoError(t, err) }) rotationPhases := []string{ types.RotationPhaseInit, types.RotationPhaseUpdateClients, types.RotationPhaseUpdateServers, types.RotationPhaseStandby, } // Rotate CAs. for _, phase := range rotationPhases { err = auth.RotateCertAuthority(ctx, RotateRequest{ Mode: types.RotationModeManual, TargetPhase: phase, Type: types.HostCA, }) require.NoError(t, err) } newUserCA, err := auth.GetCertAuthority(ctx, types.CertAuthID{ Type: types.UserCA, DomainName: "me.localhost", }, true) require.NoError(t, err) // UserCA should be untouched, as it was not the part of the rotate request, and it's unique. require.Equal(t, userCA.Spec.ActiveKeys.TLS, newUserCA.GetActiveKeys().TLS) require.Equal(t, userCA.Spec.ActiveKeys.SSH, newUserCA.GetActiveKeys().SSH) newHostCA, err := auth.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: "me.localhost", }, true) require.NoError(t, err) // HostCA should be rotated, as we requested for it. require.NotEqual(t, hostCA.Spec.ActiveKeys.TLS, newHostCA.GetActiveKeys().TLS) require.NotEqual(t, hostCA.Spec.ActiveKeys.SSH, newHostCA.GetActiveKeys().SSH) newDatabaseCA, err := auth.GetCertAuthority(ctx, types.CertAuthID{ Type: types.DatabaseCA, DomainName: "me.localhost", }, true) require.NoError(t, err) // DatabaseCA should be rotated, as the key was the same as HostCA. require.NotEqual(t, databaseCA.Spec.ActiveKeys.TLS, newDatabaseCA.GetActiveKeys().TLS) require.NotEqual(t, databaseCA.Spec.ActiveKeys.SSH, newDatabaseCA.GetActiveKeys().SSH) // HostCA and DatabaseCA should be different now. require.NotEqual(t, newHostCA.GetActiveKeys().TLS, newDatabaseCA.GetActiveKeys().TLS) require.NotEqual(t, newHostCA.GetActiveKeys().SSH, newDatabaseCA.GetActiveKeys().SSH) }