From 31281ba5a03b8e44d3e0f7ad1eb28063434d2318 Mon Sep 17 00:00:00 2001 From: Andrew LeFevre Date: Thu, 13 Feb 2025 16:36:04 -0500 Subject: [PATCH] verify cluster name of TLS peer certificates (#52130) --- api/types/authority_test.go | 14 ++ integration/helpers/instance.go | 103 ++++++----- lib/auth/authclient/tls.go | 108 ++++++++--- lib/auth/client_tls_config_generator.go | 105 +++++++++-- lib/auth/init.go | 41 ++++- lib/auth/init_test.go | 83 +++------ lib/auth/middleware.go | 53 +----- lib/auth/tls_test.go | 226 ++++++++++++++++++++++++ lib/auth/trust/trustv1/service.go | 22 +++ lib/auth/trust/trustv1/service_test.go | 35 ++-- lib/auth/trustedcluster.go | 19 ++ lib/auth/trustedcluster_test.go | 15 ++ lib/service/desktop.go | 2 +- lib/services/suite/suite.go | 16 +- lib/srv/app/connections_handler.go | 2 +- lib/srv/db/access_test.go | 2 +- lib/srv/db/proxyserver.go | 4 +- lib/web/apiserver_test.go | 2 +- 18 files changed, 640 insertions(+), 212 deletions(-) diff --git a/api/types/authority_test.go b/api/types/authority_test.go index 962ba8c44175b..624f10c16ff9c 100644 --- a/api/types/authority_test.go +++ b/api/types/authority_test.go @@ -52,3 +52,17 @@ func TestRotationZero(t *testing.T) { require.Equal(t, tt.z, tt.r.IsZero(), tt.d) } } + +// Test that the spec cluster name name will be set to match the resource name +func TestCheckAndSetDefaults(t *testing.T) { + ca := CertAuthorityV2{ + Metadata: Metadata{Name: "caName"}, + Spec: CertAuthoritySpecV2{ + ClusterName: "clusterName", + Type: HostCA, + }, + } + err := ca.CheckAndSetDefaults() + require.NoError(t, err) + require.Equal(t, ca.Metadata.Name, ca.Spec.ClusterName) +} diff --git a/integration/helpers/instance.go b/integration/helpers/instance.go index 60354d01c9ef6..65afaedec25d3 100644 --- a/integration/helpers/instance.go +++ b/integration/helpers/instance.go @@ -103,11 +103,15 @@ type InstanceSecrets struct { // PrivKey is instance private key PrivKey []byte `json:"priv"` // Cert is SSH host certificate - Cert []byte `json:"cert"` - // TLSCACert is the certificate of the trusted certificate authority - TLSCACert []byte `json:"tls_ca_cert"` - // TLSCert is client TLS X509 certificate - TLSCert []byte `json:"tls_cert"` + SSHHostCert []byte `json:"cert"` + // TLSHostCACert is the certificate of the trusted host certificate authority + TLSHostCACert []byte `json:"tls_host_ca_cert"` + // TLSCert is client TLS host X509 certificate + TLSHostCert []byte `json:"tls_host_cert"` + // TLSUserCACert is the certificate of the trusted user certificate authority + TLSUserCACert []byte `json:"tls_user_ca_cert"` + // TLSUserCert is client TLS user X509 certificate + TLSUserCert []byte `json:"tls_user_cert"` // TunnelAddr is a reverse tunnel listening port, allowing // other sites to connect to i instance. Set to empty // string if i instance is not allowing incoming tunnels @@ -138,9 +142,7 @@ func (s *InstanceSecrets) GetRoles(t *testing.T) []types.Role { return roles } -// GetCAs return an array of CAs stored by the secrets object. In i -// case we always return hard-coded userCA + hostCA (and they share keys -// for simplicity) +// GetCAs return an array of CAs stored by the secrets object func (s *InstanceSecrets) GetCAs() ([]types.CertAuthority, error) { hostCA, err := types.NewCertAuthority(types.CertAuthoritySpecV2{ Type: types.HostCA, @@ -154,7 +156,7 @@ func (s *InstanceSecrets) GetCAs() ([]types.CertAuthority, error) { TLS: []*types.TLSKeyPair{{ Key: s.PrivKey, KeyType: types.PrivateKeyType_RAW, - Cert: s.TLSCACert, + Cert: s.TLSHostCACert, }}, }, }) @@ -174,7 +176,7 @@ func (s *InstanceSecrets) GetCAs() ([]types.CertAuthority, error) { TLS: []*types.TLSKeyPair{{ Key: s.PrivKey, KeyType: types.PrivateKeyType_RAW, - Cert: s.TLSCACert, + Cert: s.TLSUserCACert, }}, }, Roles: []string{services.RoleNameForCertAuthority(s.SiteName)}, @@ -190,7 +192,7 @@ func (s *InstanceSecrets) GetCAs() ([]types.CertAuthority, error) { TLS: []*types.TLSKeyPair{{ Key: s.PrivKey, KeyType: types.PrivateKeyType_RAW, - Cert: s.TLSCACert, + Cert: s.TLSHostCACert, }}, }, }) @@ -205,7 +207,7 @@ func (s *InstanceSecrets) GetCAs() ([]types.CertAuthority, error) { TLS: []*types.TLSKeyPair{{ Key: s.PrivKey, KeyType: types.PrivateKeyType_RAW, - Cert: s.TLSCACert, + Cert: s.TLSHostCACert, }}, }, }) @@ -262,9 +264,9 @@ func (s *InstanceSecrets) AsSlice() []*InstanceSecrets { func (s *InstanceSecrets) GetIdentity() *state.Identity { i, err := state.ReadIdentityFromKeyPair(s.PrivKey, &clientproto.Certs{ - SSH: s.Cert, - TLS: s.TLSCert, - TLSCACerts: [][]byte{s.TLSCACert}, + SSH: s.SSHHostCert, + TLS: s.TLSHostCert, + TLSCACerts: [][]byte{s.TLSHostCACert}, }) fatalIf(err) return i @@ -356,17 +358,11 @@ func NewInstance(t *testing.T, cfg InstanceConfig) *TeleInstance { key, err := keys.ParsePrivateKey(cfg.Priv) fatalIf(err) - tlsCACert, err := tlsca.GenerateSelfSignedCAWithSigner(key, pkix.Name{ - CommonName: cfg.ClusterName, - Organization: []string{cfg.ClusterName}, - }, nil, defaults.CATTL) - fatalIf(err) - sshSigner, err := ssh.NewSignerFromSigner(key) fatalIf(err) keygen := keygen.New(context.TODO()) - cert, err := keygen.GenerateHostCert(sshca.HostCertificateRequest{ + hostCert, err := keygen.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: sshSigner, PublicHostKey: cfg.Pub, HostID: cfg.HostID, @@ -378,23 +374,48 @@ func NewInstance(t *testing.T, cfg InstanceConfig) *TeleInstance { }, }) fatalIf(err) - tlsCA, err := tlsca.FromKeys(tlsCACert, cfg.Priv) - fatalIf(err) - cryptoPubKey, err := sshutils.CryptoPublicKey(cfg.Pub) - fatalIf(err) - identity := tlsca.Identity{ - Username: fmt.Sprintf("%v.%v", cfg.HostID, cfg.ClusterName), - Groups: []string{string(types.RoleAdmin)}, - } + clock := cfg.Clock if clock == nil { clock = clockwork.NewRealClock() } + + identity := tlsca.Identity{ + Username: fmt.Sprintf("%v.%v", cfg.HostID, cfg.ClusterName), + Groups: []string{string(types.RoleAdmin)}, + } subject, err := identity.Subject() fatalIf(err) - tlsCert, err := tlsCA.GenerateCertificate(tlsca.CertificateRequest{ + + tlsCAHostCert, err := tlsca.GenerateSelfSignedCAWithSigner(key, pkix.Name{ + CommonName: cfg.ClusterName, + Organization: []string{cfg.ClusterName}, + }, nil, defaults.CATTL) + fatalIf(err) + tlsHostCA, err := tlsca.FromKeys(tlsCAHostCert, cfg.Priv) + fatalIf(err) + hostCryptoPubKey, err := sshutils.CryptoPublicKey(cfg.Pub) + fatalIf(err) + tlsHostCert, err := tlsHostCA.GenerateCertificate(tlsca.CertificateRequest{ + Clock: clock, + PublicKey: hostCryptoPubKey, + Subject: subject, + NotAfter: clock.Now().UTC().Add(time.Hour * 24), + }) + fatalIf(err) + + tlsCAUserCert, err := tlsca.GenerateSelfSignedCAWithSigner(key, pkix.Name{ + CommonName: cfg.ClusterName, + Organization: []string{cfg.ClusterName}, + }, nil, defaults.CATTL) + fatalIf(err) + tlsUserCA, err := tlsca.FromKeys(tlsCAHostCert, cfg.Priv) + fatalIf(err) + userCryptoPubKey, err := sshutils.CryptoPublicKey(cfg.Pub) + fatalIf(err) + tlsUserCert, err := tlsUserCA.GenerateCertificate(tlsca.CertificateRequest{ Clock: clock, - PublicKey: cryptoPubKey, + PublicKey: userCryptoPubKey, Subject: subject, NotAfter: clock.Now().UTC().Add(time.Hour * 24), }) @@ -409,14 +430,16 @@ func NewInstance(t *testing.T, cfg InstanceConfig) *TeleInstance { } secrets := InstanceSecrets{ - SiteName: cfg.ClusterName, - PrivKey: cfg.Priv, - PubKey: cfg.Pub, - Cert: cert, - TLSCACert: tlsCACert, - TLSCert: tlsCert, - TunnelAddr: i.ReverseTunnel, - Users: make(map[string]*User), + SiteName: cfg.ClusterName, + PrivKey: cfg.Priv, + PubKey: cfg.Pub, + SSHHostCert: hostCert, + TLSHostCACert: tlsCAHostCert, + TLSHostCert: tlsHostCert, + TLSUserCACert: tlsCAUserCert, + TLSUserCert: tlsUserCert, + TunnelAddr: i.ReverseTunnel, + Users: make(map[string]*User), } i.Secrets = secrets diff --git a/lib/auth/authclient/tls.go b/lib/auth/authclient/tls.go index 7c607badb2f85..e2acf48484ee7 100644 --- a/lib/auth/authclient/tls.go +++ b/lib/auth/authclient/tls.go @@ -39,59 +39,113 @@ type CAGetter interface { GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool) ([]types.CertAuthority, error) } -// ClientCertPool returns trusted x509 certificate authority pool with CAs provided as caTypes. +// HostAndUserCAInfo is a map of CA raw subjects and type info for Host +// and User CAs. The key is the RawSubject of the X.509 certificate authority +// (so it's ASN.1 data, not printable). +type HostAndUserCAInfo = map[string]CATypeInfo + +// CATypeInfo indicates whether the CA is a host or user CA, or both. +type CATypeInfo struct { + IsHostCA bool + IsUserCA bool +} + +// ClientCertPool returns trusted x509 certificate authority pool with CAs provided as caType. // In addition, it returns the total length of all subjects added to the cert pool, allowing // the caller to validate that the pool doesn't exceed the maximum 2-byte length prefix before // using it. -func ClientCertPool(ctx context.Context, client CAGetter, clusterName string, caTypes ...types.CertAuthType) (*x509.CertPool, int64, error) { - if len(caTypes) == 0 { - return nil, 0, trace.BadParameter("at least one CA type is required") +func ClientCertPool(ctx context.Context, client CAGetter, clusterName string, caType types.CertAuthType) (*x509.CertPool, int64, error) { + authorities, err := getCACerts(ctx, client, clusterName, caType) + if err != nil { + return nil, 0, trace.Wrap(err) } pool := x509.NewCertPool() - var authorities []types.CertAuthority - if clusterName == "" { - for _, caType := range caTypes { - cas, err := client.GetCertAuthorities(ctx, caType, false) - if err != nil { - return nil, 0, trace.Wrap(err) - } - authorities = append(authorities, cas...) - } - } else { - for _, caType := range caTypes { - ca, err := client.GetCertAuthority( - ctx, - types.CertAuthID{Type: caType, DomainName: clusterName}, - false) + var totalSubjectsLen int64 + for _, auth := range authorities { + for _, keyPair := range auth.GetTrustedTLSKeyPairs() { + cert, err := tlsca.ParseCertificatePEM(keyPair.Cert) if err != nil { return nil, 0, trace.Wrap(err) } + pool.AddCert(cert) - authorities = append(authorities, ca) + // Each subject in the list gets a separate 2-byte length prefix. + totalSubjectsLen += 2 + totalSubjectsLen += int64(len(cert.RawSubject)) } } + return pool, totalSubjectsLen, nil +} + +// DefaultClientCertPool returns default trusted x509 certificate authority pool. +func DefaultClientCertPool(ctx context.Context, client CAGetter, clusterName string) (*x509.CertPool, HostAndUserCAInfo, int64, error) { + authorities, err := getCACerts(ctx, client, clusterName, types.HostCA, types.UserCA) + if err != nil { + return nil, nil, 0, trace.Wrap(err) + } + pool := x509.NewCertPool() + caInfos := make(HostAndUserCAInfo, len(authorities)) var totalSubjectsLen int64 for _, auth := range authorities { for _, keyPair := range auth.GetTrustedTLSKeyPairs() { cert, err := tlsca.ParseCertificatePEM(keyPair.Cert) if err != nil { - return nil, 0, trace.Wrap(err) + return nil, nil, 0, trace.Wrap(err) } pool.AddCert(cert) + caType := auth.GetType() + caInfo := caInfos[string(cert.RawSubject)] + switch caType { + case types.HostCA: + caInfo.IsHostCA = true + case types.UserCA: + caInfo.IsUserCA = true + default: + return nil, nil, 0, trace.BadParameter("unexpected CA type %q", caType) + } + caInfos[string(cert.RawSubject)] = caInfo + // Each subject in the list gets a separate 2-byte length prefix. totalSubjectsLen += 2 totalSubjectsLen += int64(len(cert.RawSubject)) } } - return pool, totalSubjectsLen, nil + + return pool, caInfos, totalSubjectsLen, nil } -// DefaultClientCertPool returns default trusted x509 certificate authority pool. -func DefaultClientCertPool(ctx context.Context, client CAGetter, clusterName string) (*x509.CertPool, int64, error) { - return ClientCertPool(ctx, client, clusterName, types.HostCA, types.UserCA) +func getCACerts(ctx context.Context, client CAGetter, clusterName string, caTypes ...types.CertAuthType) ([]types.CertAuthority, error) { + if len(caTypes) == 0 { + return nil, trace.BadParameter("at least one CA type is required") + } + + var authorities []types.CertAuthority + if clusterName == "" { + for _, caType := range caTypes { + cas, err := client.GetCertAuthorities(ctx, caType, false) + if err != nil { + return nil, trace.Wrap(err) + } + authorities = append(authorities, cas...) + } + } else { + for _, caType := range caTypes { + ca, err := client.GetCertAuthority( + ctx, + types.CertAuthID{Type: caType, DomainName: clusterName}, + false) + if err != nil { + return nil, trace.Wrap(err) + } + + authorities = append(authorities, ca) + } + } + + return authorities, nil } // WithClusterCAs returns a TLS hello callback that returns a copy of the provided @@ -110,7 +164,7 @@ func WithClusterCAs(tlsConfig *tls.Config, ap CAGetter, currentClusterName strin } } } - pool, totalSubjectsLen, err := DefaultClientCertPool(info.Context(), ap, clusterName) + pool, _, totalSubjectsLen, err := DefaultClientCertPool(info.Context(), ap, clusterName) if err != nil { log.WithError(err).Errorf("Failed to retrieve client pool for %q.", clusterName) // this falls back to the default config @@ -132,7 +186,7 @@ func WithClusterCAs(tlsConfig *tls.Config, ap CAGetter, currentClusterName strin if totalSubjectsLen >= int64(math.MaxUint16) { log.Debugf("Number of CAs in client cert pool is too large and cannot be encoded in a TLS handshake; this is due to a large number of trusted clusters; will use only the CA of the current cluster to validate.") - pool, _, err = DefaultClientCertPool(info.Context(), ap, currentClusterName) + pool, _, _, err = DefaultClientCertPool(info.Context(), ap, currentClusterName) if err != nil { log.WithError(err).Errorf("Failed to retrieve client pool for %q.", currentClusterName) // this falls back to the default config diff --git a/lib/auth/client_tls_config_generator.go b/lib/auth/client_tls_config_generator.go index 9c29a9fad57b0..9c3a8ad559bf0 100644 --- a/lib/auth/client_tls_config_generator.go +++ b/lib/auth/client_tls_config_generator.go @@ -21,17 +21,20 @@ package auth import ( "context" "crypto/tls" + "crypto/x509" "errors" "log/slog" "math" "time" "github.com/gravitational/trace" + "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/types" apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/api/utils/retryutils" "github.com/gravitational/teleport/lib/auth/authclient" + "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils/genmap" ) @@ -75,13 +78,70 @@ func (cfg *ClientTLSConfigGeneratorConfig) CheckAndSetDefaults() error { type ClientTLSConfigGenerator struct { // cfg holds the config parameters for this generator. cfg ClientTLSConfigGeneratorConfig - // clientTLSConfigs is a specialized cache that stores tls configs - // by cluster name. - clientTLSConfigs *genmap.GenMap[string, *tls.Config] + // clientTLSPools is a specialized cache that stores client CA + // certificate pools by cluster name. + clientTLSPools *genmap.GenMap[string, *HostAndUserCAPoolInfo] // cancel terminates the above close context. cancel context.CancelFunc } +// HostAndUserCAPoolInfo bundles a CA pool with a map of CA raw subjects +// to the registered types of that CA. [x509.CertPool] doesn't make it +// easy to view info on its certs so this info is stored alongside it. +type HostAndUserCAPoolInfo struct { + Pool *x509.CertPool + CATypes authclient.HostAndUserCAInfo +} + +// verifyPeerCert returns a function that checks that the client peer +// certificate's cluster name matches the cluster name of the CA +// that issued it. +func (p *HostAndUserCAPoolInfo) verifyPeerCert() func([][]byte, [][]*x509.Certificate) error { + return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 { + return nil + } + + peerCert := verifiedChains[0][0] + identity, err := tlsca.FromSubject(peerCert.Subject, peerCert.NotAfter) + if err != nil { + log.WithError(err).Warn("Failed to parse identity from client certificate subject") + return trace.Wrap(err) + } + certClusterName := identity.TeleportCluster + issuerClusterName, err := tlsca.ClusterName(peerCert.Issuer) + if err != nil { + log.WithError(err).Warn("Failed to parse client certificate") + return trace.AccessDenied(invalidCertErrMsg) + } + if certClusterName != issuerClusterName { + log.WithFields(logrus.Fields{ + "peer_cert_cluster_name": certClusterName, + "issuer_cluster_name": issuerClusterName, + }).Warn("Client peer certificate was issued by a CA from a different cluster than what the certificate claims to be from") + return trace.AccessDenied(invalidCertErrMsg) + } + + ca, ok := p.CATypes[string(peerCert.RawIssuer)] + if !ok { + log.Warn("Could not find issuer CA of client certificate") + return trace.AccessDenied(invalidCertErrMsg) + } + + // Ensure the CA that issued this client cert is of the appropriate type + systemRole := findPrimarySystemRole(identity.Groups) + if systemRole != nil && !ca.IsHostCA { + log.WithField("role", systemRole.String()).Warn("Client peer certificate has a builtin role but was not issued by a host CA") + return trace.AccessDenied(invalidCertErrMsg) + } else if systemRole == nil && !ca.IsUserCA { + log.Warn("Client peer certificate has a local role but was not issued by a user CA") + return trace.AccessDenied(invalidCertErrMsg) + } + + return nil + } +} + // NewClientTLSConfigGenerator sets up a new generator based on the supplied parameters. func NewClientTLSConfigGenerator(cfg ClientTLSConfigGeneratorConfig) (*ClientTLSConfigGenerator, error) { if err := cfg.CheckAndSetDefaults(); err != nil { @@ -104,7 +164,7 @@ func NewClientTLSConfigGenerator(cfg ClientTLSConfigGeneratorConfig) (*ClientTLS } var err error - c.clientTLSConfigs, err = genmap.New(genmap.Config[string, *tls.Config]{ + c.clientTLSPools, err = genmap.New(genmap.Config[string, *HostAndUserCAPoolInfo]{ Generator: c.generator, }) if err != nil { @@ -117,6 +177,8 @@ func NewClientTLSConfigGenerator(cfg ClientTLSConfigGeneratorConfig) (*ClientTLS return c, nil } +const invalidCertErrMsg = "access denied: invalid client certificate" + // GetConfigForClient is intended to be slotted into the GetConfigForClient field of tls.Config. func (c *ClientTLSConfigGenerator) GetConfigForClient(info *tls.ClientHelloInfo) (*tls.Config, error) { var clusterName string @@ -134,7 +196,15 @@ func (c *ClientTLSConfigGenerator) GetConfigForClient(info *tls.ClientHelloInfo) } } - cfg, err := c.clientTLSConfigs.Get(context.Background(), clusterName) + poolInfo, err := c.clientTLSPools.Get(context.Background(), clusterName) + cfg := c.cfg.TLS.Clone() + if poolInfo != nil { + cfg.ClientCAs = poolInfo.Pool + // Verify that the peer cert matches the cluster name of the + // issuer CA and that the CA type matches the cert Teleport role + cfg.VerifyPeerCertificate = poolInfo.verifyPeerCert() + } + return cfg, trace.Wrap(err) } @@ -142,19 +212,19 @@ var errNonLocalCluster = errors.New("non-local cluster specified in client hello // generator is the underlying lookup function used to resolve the tls config that should be used for a // given cluster. this method is used by the underlying genmap to load/refresh values as-needed. -func (c *ClientTLSConfigGenerator) generator(ctx context.Context, clusterName string) (*tls.Config, error) { +func (c *ClientTLSConfigGenerator) generator(ctx context.Context, clusterName string) (*HostAndUserCAPoolInfo, error) { if !c.cfg.PermitRemoteClusters && clusterName != c.cfg.ClusterName { if clusterName != "" { slog.WarnContext(ctx, "refusing to set up client cert pool for non-local cluster", "cluster_name", clusterName) return nil, trace.Wrap(errNonLocalCluster) } - // unsepcified cluster name should be treated as a request for local cluster CAs + // unspecified cluster name should be treated as a request for local cluster CAs clusterName = c.cfg.ClusterName } // update client certificate pool based on currently trusted TLS // certificate authorities. - pool, totalSubjectsLen, err := authclient.DefaultClientCertPool(ctx, c.cfg.AccessPoint, clusterName) + pool, caMap, totalSubjectsLen, err := authclient.DefaultClientCertPool(ctx, c.cfg.AccessPoint, clusterName) if err != nil { slog.ErrorContext(ctx, "failed to retrieve client cert pool for target cluster", "cluster_name", clusterName, "error", err) // this falls back to the default config @@ -175,7 +245,7 @@ func (c *ClientTLSConfigGenerator) generator(ctx context.Context, clusterName st // client will be rejected. if totalSubjectsLen >= int64(math.MaxUint16) { slog.WarnContext(ctx, "cluster subject name set too large for TLS handshake, falling back to using local cluster CAs only") - pool, _, err = authclient.DefaultClientCertPool(ctx, c.cfg.AccessPoint, c.cfg.ClusterName) + pool, caMap, _, err = authclient.DefaultClientCertPool(ctx, c.cfg.AccessPoint, c.cfg.ClusterName) if err != nil { slog.ErrorContext(ctx, "failed to retrieve client cert pool for current cluster", "cluster_name", c.cfg.ClusterName, "error", err) // this falls back to the default config @@ -183,9 +253,10 @@ func (c *ClientTLSConfigGenerator) generator(ctx context.Context, clusterName st } } - tlsCopy := c.cfg.TLS.Clone() - tlsCopy.ClientCAs = pool - return tlsCopy, nil + return &HostAndUserCAPoolInfo{ + Pool: pool, + CATypes: caMap, + }, nil } // refreshClientTLSConfigs is the top-level loop for client TLS config regen. note that it @@ -240,7 +311,7 @@ func (c *ClientTLSConfigGenerator) watchForCAChanges(ctx context.Context) error return nil } - c.clientTLSConfigs.RegenAll() + c.clientTLSPools.RegenAll() for { select { @@ -248,7 +319,7 @@ func (c *ClientTLSConfigGenerator) watchForCAChanges(ctx context.Context) error return trace.Errorf("ca watcher exited with: %v", watcher.Error()) case event := <-watcher.Events(): if event.Type == types.OpDelete { - c.clientTLSConfigs.Terminate(event.Resource.GetName()) + c.clientTLSPools.Terminate(event.Resource.GetName()) } else { if !c.cfg.PermitRemoteClusters && event.Resource.GetName() != c.cfg.ClusterName { // ignore non-local cluster CA events when we aren't configured to support them @@ -257,10 +328,10 @@ func (c *ClientTLSConfigGenerator) watchForCAChanges(ctx context.Context) error if event.Resource.GetName() == c.cfg.ClusterName { // actively regenerate on modifications associated with the local cluster - c.clientTLSConfigs.Generate(event.Resource.GetName()) + c.clientTLSPools.Generate(event.Resource.GetName()) } else { // clear extant state on modifications associated with non-local clusters - c.clientTLSConfigs.Terminate(event.Resource.GetName()) + c.clientTLSPools.Terminate(event.Resource.GetName()) } } case <-ctx.Done(): @@ -271,7 +342,7 @@ func (c *ClientTLSConfigGenerator) watchForCAChanges(ctx context.Context) error // Close terminates background ca load/refresh operations. func (c *ClientTLSConfigGenerator) Close() error { - c.clientTLSConfigs.Close() + c.clientTLSPools.Close() c.cancel() return nil } diff --git a/lib/auth/init.go b/lib/auth/init.go index afd8ce4056874..59a9a82596235 100644 --- a/lib/auth/init.go +++ b/lib/auth/init.go @@ -583,6 +583,12 @@ func initCluster(ctx context.Context, cfg InitConfig, asrv *Server) error { } span.AddEvent("completed migration legacy resources") + span.AddEvent("checking certificate authority cluster names") + if err := checkAuthorityClusterNames(ctx, asrv); err != nil { + return trace.Wrap(err) + } + span.AddEvent("completed checking certificate authority cluster names") + // Create presets - convenience and example resources. if !services.IsDashboard(*modules.GetModules().Features().ToProto()) { span.AddEvent("creating preset roles") @@ -770,6 +776,35 @@ func generateAuthority(ctx context.Context, asrv *Server, caID types.CertAuthID) return ca, nil } +func checkAuthorityClusterNames(ctx context.Context, asrv *Server) error { + for _, caType := range types.CertAuthTypes { + authorities, err := asrv.Services.GetCertAuthorities(ctx, caType, false) + if err != nil { + return trace.Wrap(err) + } + for _, ca := range authorities { + caClusterName := ca.GetClusterName() + // sanity check that the cluster name in the CA certificates + // matches the authority resource's cluster name + for _, keyPair := range ca.GetTrustedTLSKeyPairs() { + cert, err := tlsca.ParseCertificatePEM(keyPair.Cert) + if err != nil { + return trace.Wrap(err) + } + clusterName, err := tlsca.ClusterName(cert.Subject) + if err != nil { + return trace.Wrap(err) + } + if clusterName != caClusterName { + return trace.BadParameter("CA certificate of type %s has cluster name %q that does not match the cluster name %q found in the subject", ca.GetType(), caClusterName, clusterName) + } + } + } + } + + return nil +} + var secondFactorUpgradeInstructions = ` Teleport requires second factor authentication for local users. The auth_service configuration should be updated to enable it. @@ -1228,7 +1263,8 @@ func checkResourceConsistency(ctx context.Context, keyStore *keystore.Manager, c case types.CertAuthority: // check that signing CAs have expected cluster name and that // all CAs for this cluster do having signing keys. - seemsLocal := r.GetClusterName() == clusterName + caClusterName := r.GetClusterName() + seemsLocal := caClusterName == clusterName var hasKeys bool var signerErr error @@ -1257,6 +1293,9 @@ func checkResourceConsistency(ctx context.Context, keyStore *keystore.Manager, c if !seemsLocal && hasKeys { return trace.BadParameter("unexpected cluster name %q for signing ca (expected %q)", r.GetClusterName(), clusterName) } + if !seemsLocal { + continue + } case types.TrustedCluster: if r.GetName() == clusterName { return trace.BadParameter("trusted cluster has same name as local cluster (%q)", clusterName) diff --git a/lib/auth/init_test.go b/lib/auth/init_test.go index 8ecb060a409e9..8b0c27ca73a4f 100644 --- a/lib/auth/init_test.go +++ b/lib/auth/init_test.go @@ -1374,64 +1374,47 @@ func newWebauthnAuthPreferenceConfigFromFile(t *testing.T) types.AuthPreference const ( hostCAYAML = `kind: cert_authority metadata: - id: 1630515580178991000 name: me.localhost + revision: 6cc380df-816d-4c6b-8fc8-c20af75eede2 spec: active_keys: ssh: - - private_key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBMGpYWHA5SmM3amFVVThydC9NUXZyZjY3TXZoZnk3ZG5ldHoyenBKWFFNNmJQUG5PCjdQVGdIV1o1TFFndVVJTldXLzN6ci9xa2V6UHhmU0lXaUVlcDlRTy9HSytBWHVNWTdmUThNTEc2cS93ckM3UU8KeWxaY3htWGZuS2ZXSkV5RUdralA5VlZmakd3UEdxRUNGWmZtT1pJdXFMVWVadEJKVzduZTZWbkYvWnYzbUhBZwpkc1FFRHphTjdlQnN2NHJyOVhQeHI4anh4Tzg5TjZZN0lsRStoUXNiT3FNSVVsSUVvSk0xYjBOcVFldlhHLzhVClBWWXJLK21hR2dNclNqZHppTjl6VHUzRzNCKzJ3SkJNZzd3VitMQjlJRUU0Y2NHYVVDbUM0QzBuZlByM283emgKTE1UNTZpdGZLdGw0TlkrN1NjRHVSSkN2TkFra3IrNkZtVUc5NXdJREFRQUJBb0lCQVFEUUVJTVlsVnR1WFkrTApNTDFIQjFpNk8veEdneGt1cHFaQ01od0ljMGp4Mkk1SFdHdThsdFNOeFRRRG9xbFUvK3FtdTBKTTJTV2MzTmtXCkpudHZBSi8wNkhScGxxelZQcXNhUERpbmFnTiszK1lyZTFsNFpPc0haU1prQkt3czJaK1g5S0lDRHpLMzV1MDgKU2ttcDNlUCs3L1pHL3A3TTNUVC9HWWJPS2hHUzV0VXdTY291NXV3aFMxQjFJOFNQQW05UGdsMmkyTjVib2s2YgpQTHBqT0xwOUZPVmpVMlNXYUhVa0ZiUUc2TENzOGlJdVdlVzdvMEtkY3B1QndXeGQxd0p3bm5nbjlJVzJ0aUdyCnZLTGlsYzJLR2xITnJxZldiRWw4Z2N5eXBKeUNqZEIvVXkvOFFBOG9uOHBnK1hldThrcnFyRjNTZm5iZHNOaDQKVmNKV09zTUJBb0dCQVBvY1E1ZWd0enZSL1djR0ZtbUxNbTUxTEVDMytPOWxVSUMwMGZ0Q1lkYW5vK0NGUTdEbgpQd0IyLzUyZWZMeUhLUlpjc0lrd2IreXZjYXp3eG9BWlNzMnRlV1FqRUJoOXdRbGlzWlNacVJIVEVMbnY0WjdvCkFMM3JmUjNtM3FHR0tmMm9UQnQ3dEVCcEMwZEVITDlxOVRpbkJJVDVKWGVSSVNzZUcyOGFKUG9IQW9HQkFOY3AKREtjc2hVKzJnaSs2V3p4eThOU3gyMkV5Y29FdUFKbWlSdm1jcExNNE9OTW03dUlQQ2RDYWYyY0UxYzJjVUg5OAppRGw2ZzBYN1ViUjQ4NzdJeVluMi9TU2hiOG56R3ZBNmVBbGJYZk9PYlYvTkIrczUxQzduTGFVSUE1ZEkyTE1WClRJL2xRTnV4cDhieXZDNjU3UVFQZk9jVXlGMVZIVVo5VzNPbU1hVWhBb0dCQU5RREZZRDQ2Wm81M1RaeHdKbmoKTnZMUFBKMzMxWHNKUlA1MVNQSldTUjF1cWNudTdYeU42YWY1TjZGaThaWFdkUXZSc292NGxVZnJTTTh5b3ZGLwpmeHR1aTlKSXJxSTBKMmhQVXYwR2JIMEJqOUl0OS9GOTlQTUpKZHd0RWxlVnBRNnlsU0ZPOFhNUUdGRm0rWCtCCnFURkcwdHZ0WHNkR0xQbWg0ZHVDTEFvTkFvR0FLSS9aamM2TDEwbzk0c2VNR2FwRmtxTnhDekxhZVZYMTBRRFIKeG83c1Vja2dsVlg2cE8xVzJWZTIrdkhqYUo2MllrSlU0QmtqbEZiYndWMG4vbWlWN2dkOUU2SEhsRmZiVlR5QQprcXNCM0QrV2lQLzdKVEpDdVJEbC92Mnl4NXQ1RnRIR0hENkk2cUhrVWxKQ2ZjQ1pXVEdlUjJZWW05Zkc3Qm9ICjJwYVROMkVDZ1lFQXRUQ2o1M253M213RDE1ZjAyZ2pKcWNjeHBkRk1lYlJpNUltMXJ5RUFaSzhwTmNlL2xNclgKVXM1KzM2eFpHWG1CR1ByMVl0UmxIVThQL1owWFdJM3ZDOFpLRHB6SnB4dnB6QmRFV0pUZGlPZEhpV2J4TzZOOQpiYjh3ZUM4STl4ZDlwd0cyMHdYNUVnR0krekdjMlNGa1VDTWROd1JkV3NSTWNCMklMeVV0MmZzPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= - public_key: c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFEU05kZW4wbHp1TnBSVHl1Mzh4Qyt0L3JzeStGL0x0MmQ2M1BiT2tsZEF6cHM4K2M3czlPQWRabmt0Q0M1UWcxWmIvZk92K3FSN00vRjlJaGFJUjZuMUE3OFlyNEJlNHhqdDlEd3dzYnFyL0NzTHRBN0tWbHpHWmQrY3A5WWtUSVFhU00vMVZWK01iQThhb1FJVmwrWTVraTZvdFI1bTBFbGJ1ZDdwV2NYOW0vZVljQ0IyeEFRUE5vM3Q0R3kvaXV2MWMvR3Z5UEhFN3owM3Bqc2lVVDZGQ3hzNm93aFNVZ1Nna3pWdlEycEI2OWNiL3hROVZpc3I2Wm9hQXl0S04zT0kzM05PN2NiY0g3YkFrRXlEdkJYNHNIMGdRVGh4d1pwUUtZTGdMU2Q4K3ZlanZPRXN4UG5xSzE4cTJYZzFqN3RKd081RWtLODBDU1N2N29XWlFiM24K + - private_key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1DNENBUUF3QlFZREsyVndCQ0lFSU5WTzBiNUxSOXk2Nm5SRGJHN3JJUzFRZ3dBcUVpSWtMZS9WVmFrd3pJZ2oKLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo= + public_key: c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSUw2ZWtVSTg3U3VOYkFiWnhPbGxRUEJJWGdPVjFNcEt4UWVNQXB0MklpVlYK tls: - - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURlVENDQW1HZ0F3SUJBZ0lSQUlvWW1QRUhNV0wwZmp6VkZVZ2l2Ykl3RFFZSktvWklodmNOQVFFTEJRQXcKVmpFUU1BNEdBMVVFQ2hNSGVtRnljWFZ2YmpFUU1BNEdBMVVFQXhNSGVtRnljWFZ2YmpFd01DNEdBMVVFQlJNbgpNVGd6TlRZeE1UZ3dOVFkxTXprMk1qUTNNRFV4TXpRM05qa3pOREV5TmpNME56Y3dPRFkyTUI0WERUSXhNRGt3Ck1URTJOVGswTUZvWERUTXhNRGd6TURFMk5UazBNRm93VmpFUU1BNEdBMVVFQ2hNSGVtRnljWFZ2YmpFUU1BNEcKQTFVRUF4TUhlbUZ5Y1hWdmJqRXdNQzRHQTFVRUJSTW5NVGd6TlRZeE1UZ3dOVFkxTXprMk1qUTNNRFV4TXpRMwpOamt6TkRFeU5qTTBOemN3T0RZMk1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBCjlzNGgrUTlpKzBlQ0c0VWNtSThFbEg1MHBlWmFkV2JzdzZDUStCZmF0em1NQWJEa016WGpSTHlNZy8xNFdSRGsKWTU4OUpCWVgrTzRBYXBRNno5MFRhTzhHamN1RmxvcWlFcTZOci9VTjJMYnc2am9KdTYvQ0dmcWIzNXZKT1NDdApKMWp5am5Cc2ZVNTRzRGFGcWpPRG1BL2l3YjNsSlZSV1pmYUdQSGZtRTRUcHJCSzdXV2FHbmlDZktUdGQza3lHCnkzWXpGZEJzSDU0OU9Lc1BFUlJOdTVCdlpzcmZWazRDdnlKNWVxREE1NlBaU1pmSVptZEk4VUdsRmk0V3lhMHEKUHRtWmg4bURGSnpVNnNXZWY5bTRqM3grMmF1UFEra2M3cjJneFdLZ2lTR1FBT2hLU0VhaEZDUW9UU3NzemVReApoZG9xamZUK1NsUWlQQlhJbDhTbzlRSURBUUFCbzBJd1FEQU9CZ05WSFE4QkFmOEVCQU1DQXFRd0R3WURWUjBUCkFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVlSDJQTUhjRmwwTnkvQmozcE4xTExlSStPZTB3RFFZSktvWkkKaHZjTkFRRUxCUUFEZ2dFQkFMeU5aRXByOWtWbk5ORERiWWxWd0M1bnRlUEU2Z0ZQdGRjU3JjYmcyU01temtMMgppSjhCQnJ1ZVBmTllrMVE0c2xlN0FDeTRQL1dzU2xYZThqQkY0bm5KaUkxM2kvaUtQOEJzNVVIQWwwUTJ6RnlMCmp0Sk9JRlFuT1hiUmxQU1RQY21jcWdVUUcrL3lGOGhQVndHNnRwOVZoeXlCVkdlMDRlWU44eVNnbkZKTUloNGcKeDEzcytTWjE1RFkzSERnTGNCSjA5dXA4Q3NJdGV1RG05aG1jbGJ1bUc4OVEyQ3I4T25TcmYzTDNFSVhyelZBbwp3eEUwN21IWnkxSjBvNnljdWJXOWVuS2J3bTRKRHRhaHc4QzlERitvQXJlUEdaSXJXdVZpWFZYeTV6S2NOQW9LCjVXY0Q5RHdjcTFFUnVreGNFWWlrV05BV1FFNnVaMGVYZ0pKWlJ2OD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= - key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBOXM0aCtROWkrMGVDRzRVY21JOEVsSDUwcGVaYWRXYnN3NkNRK0JmYXR6bU1BYkRrCk16WGpSTHlNZy8xNFdSRGtZNTg5SkJZWCtPNEFhcFE2ejkwVGFPOEdqY3VGbG9xaUVxNk5yL1VOMkxidzZqb0oKdTYvQ0dmcWIzNXZKT1NDdEoxanlqbkJzZlU1NHNEYUZxak9EbUEvaXdiM2xKVlJXWmZhR1BIZm1FNFRwckJLNwpXV2FHbmlDZktUdGQza3lHeTNZekZkQnNINTQ5T0tzUEVSUk51NUJ2WnNyZlZrNEN2eUo1ZXFEQTU2UFpTWmZJClptZEk4VUdsRmk0V3lhMHFQdG1aaDhtREZKelU2c1dlZjltNGozeCsyYXVQUStrYzdyMmd4V0tnaVNHUUFPaEsKU0VhaEZDUW9UU3NzemVReGhkb3FqZlQrU2xRaVBCWElsOFNvOVFJREFRQUJBb0lCQUhkODZ1TzYrRS94bWVNYQorZkkrWTVoRTlOS1JDTUNJT1I2cE1TWjczZzhSRkdDSk5LSTZkN0tDbW9FWWlWaU5uaFZCTmdldmpxR2RFS1NJCjZVUlRveDhOZ2gzS0ovM3ZWbkkzQWkvck0yMzFmQVBhWDNYM3JNQ0pIVWdRRTBiT05DYTFvSkVuaXM3TDNCQnMKQlNDVzJpSVhwcy9uMFBYV3RCR2ZYZlFPbEZ4ajdKbXdwdDlqYTYyankxa3hlTVdMemlqQUZ1QzRwRTl3WUVaVApGa0ovb2FVdzR4WXM5N0pnelRNUHFJSlUzMStNYTNoNGFPQ2lPK2RzSExhK2tRUkZIN0tHOUtRaWlRYzdrTmdGCnljN2s3L2s3UVJSbG1mZmZLN3E3YUtVNkNueHVxOG05UThob2Q5QVdjQ3ZyTm9jSmVjSzcvSzFzNlhMYXhSTFgKRGFLc0xRMENnWUVBK0lBOGNRVmcveU5xbGQ5c2lHZ0NETy9DVUNwM21uZjdxZTNQeEZIWHVSdFNzaHJheE9qVQpRVXFtbHV5VWRCZTZLQnA4ZWxoVmFlYlZsdFNNd2xyWXNpUlIxaTV0SDNnRGUybXRheE5VZ0xtTGoyZUhVWGN3Cmk1UW1yQ1AyTzY4V0xQZjg4SkkwQWtOWWx3em5aWUg2VEZKbFp0MkxoU3ZRTG1qeFArWDZTT01DZ1lFQS9rREwKNndJMjVoYU1DbTBSWitrQWM1T2FJcUx0OUNWeWNTaGdzblBjK2xBQll0ZVRkclhjVW1FaklQYW1uZ1pKZUpvSgpoUDNxaHBja1JVcWwrSldhN3lXRnJocTdyTHlpTVNBb0VwaXdQMkRUMkxROWFXR0lONjdwSU9ObWtpamU2NmNNCjBsd05hQTBtL3Q5ZWZUZ2RLOUpHYnNoaTVnRFFiTFVtMW5Gd1prY0NnWUFMenJ3UWVycnpKSkdwOFdYTXpYUmIKZlFEMG9pL3dyUWJPT2ppSEVZUjRqUzNPdkt2c2MwdXlsb04zNUdIaGFrYzBKSjRKaWl6MHpUMFUzNkNZazR4OApXbkZ4QmQrMWdSUlpSdG93bmtpRG5VMWVVUU1EQWZEU2tRV05aR0FNMGZMeHpBNit0NU8xRDlJanl6OHJlWk9WCkVNMDBxQTQ3RTZ2ZXFLbmQ2V1dORlFLQmdBa3Z6aTV2cGd3cVJHVWNDOFQxWms3R3hvcjUyQjg2T3loYmpTTGwKak5aK2pZNUV1ODlPUXVlM0dzM1dHNjhhQ3cyUWcwZUs1UzUzeDVlNVdzWGdvZmlDSXBKbjVPQVk4TU5WcGgwRgo1MWhpNTBTdFBvclFPMXZIdGlTNkVycTFQMWpFY0hJcFlWS2hKd2VPaXB0N3E1SXB4dUc1MjlqenJwUSs5MmhJCk1RZUJBb0dCQUkrNnE2OGFvdW41bE5sU3NoTVg0citoZlNtbFhXYmcvdU1OQXROdWhtZndmSE0zWmR3SU9ZWlYKRTloalFNQXh6RFduZkx1ajljRkJQM2NER0s0NytoU0U2N082eHBrYk9BYzk4dUlXZVEzb3lyWEd5akQ3TGdrYwpxajFRa2ZiZE4wMU1BdEN0MDhaSlRwZzR1OUdXUUVVQWY2ckJaKzJPN2ZVdEJuUG40TXpsCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== + - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNBakNDQWFlZ0F3SUJBZ0lSQUpKYkRNWGtYN3ZOR0EreFRQeGlzYzR3Q2dZSUtvWkl6ajBFQXdJd1lERVYKTUJNR0ExVUVDaE1NYldVdWJHOWpZV3hvYjNOME1SVXdFd1lEVlFRREV3eHRaUzVzYjJOaGJHaHZjM1F4TURBdQpCZ05WQkFVVEp6RTVORFUwTURBME5UUTJOakkyTlRrMk1qazBOakkxTURNNU5UYzJNamM1TkRBNE1qYzJOakFlCkZ3MHlOREV5TXpFeE5ESTJNRFphRncwek5ERXlNamt4TkRJMk1EWmFNR0F4RlRBVEJnTlZCQW9UREcxbExteHYKWTJGc2FHOXpkREVWTUJNR0ExVUVBeE1NYldVdWJHOWpZV3hvYjNOME1UQXdMZ1lEVlFRRkV5Y3hPVFExTkRBdwpORFUwTmpZeU5qVTVOakk1TkRZeU5UQXpPVFUzTmpJM09UUXdPREkzTmpZd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxCmhrak9QUU1CQndOQ0FBUzZTZE92WEdZV2wyY2FCRHFPcXRRN3RyNW1JWkdKR0JFYXJWUnMvSFFvYXpiZExKK0IKMExMUFhHemQvOTNxZ04wNXJVWGhyNHVXb3pEeTMxR2V4ZDZmbzBJd1FEQU9CZ05WSFE4QkFmOEVCQU1DQVlZdwpEd1lEVlIwVEFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVLWmhpWVRwc3Z0NTkwTXc1OGhPY2xNcTBVYmN3CkNnWUlLb1pJemowRUF3SURTUUF3UmdJaEFLcFExNWt6MFZlQThmOEE5S3RKRS9LLytxVkkyNGFpMnM5dytmYTQKVFNFdkFpRUEwWWtNZGNFc1dqTjBFeFpzVmVsRU5EdS9hcWhtUkpua2FpakJDNjFBY0Q0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ3RTcVllSWhXeTZ3S3QxVDYKUVdVSitTVkF0SjRpNkppajhac0hmWkw2YzJXaFJBTkNBQVM2U2RPdlhHWVdsMmNhQkRxT3F0UTd0cjVtSVpHSgpHQkVhclZScy9IUW9hemJkTEorQjBMTFBYR3pkLzkzcWdOMDVyVVhocjR1V296RHkzMUdleGQ2ZgotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg== 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 + revision: b82425a2-dd10-49c6-9b8a-3f10e3ec8a41 spec: active_keys: ssh: - - private_key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBMnZ1ekRQRHZWTElYZTdicDZIc1ZiM3NHak0xZ3lFa2hkWVBUMVVzZ01Kd1JuL1VSCk5EQTZST2pya2NUSEhyRXhnL002dVlQaGN2K2UweUFOZk9JMXROWVgvQ3hGcWdCcGFpRE5YRGpRajdyV2E4RDQKWmNyNkdIVXoza3VDN0k0QTJaUlJXK2M3ajhlZDFEbTlzZ0lpOW1BU3NXL0M2RTZTblEvVGlXVk9hTEdqdEVTdwo5L0lpenc1S2pPYnJLMGRXUXZiMXY1SW8rTUpVbko1NE5jWUlMRm9pYk9WeDdQU1FzWCtDK1JwQ3BIbFIrWnNWClVmSzFrd0l4eXg1Q25BTEMzcnlkOUFaSnY2aVdoci9uczZyWmU5b21xMHd6VUpnT045VE5aQjdZTjRhUmlYSmMKNkpsZmpjdUpnZ2ZDbVZaSWNyRm9wd3kzdnp4LzBxNVVCOVRNRFFJREFRQUJBb0lCQUhVNHkyNGdBMTJwUDl6ZgoyM0t4Z0pYK20xRUFGOURmSk9RTlAzWXNFdjB5YmxUY0VPdUk3WWc1enZCbkQ5Z2tMa2RlQ28rSVEwVVdCT1VyCmdVemFvcms4NmZYNWxRa2QwMUFXWXhmODZkZ213ZVZJbFMrWWFpeHhnT1I4TTRlQnRIN0VZSkQ3eE95QWhNSTQKYm8wOWk0MnJmQll6cDNoSHAwQWdXckp2NG5zenJtVGl2d2s4b0JRS2pGbDZ5VjU2L0g4K3VxaWlSS28vRDlPTApXSU51M1JXRTd4RVpJdlNBZlFMYUFyVG1XaXVVMkhrTklTSmJqRGxrd1phMmtjQ1VQUnVkbFJsOWw2WVM0ZUpSClhNU3RaVmQrcVRpVHhFc29TcTBjRmI3dTlYcEs0a3U3TXR5dDFHY0V0MnIvQzRrcXJFNFAyK3c3RmkvalN2VjYKdUZoWW41a0NnWUVBM3VuK0JMN21hSUhsOGxNclVrcG5SUFJNenllOGFyREplbnM4RXYzTUNyVkxLR0p5NUV6cwppc2ZmVythYTJSejhGOFk0U09aZjZaTk9CS3RzOHFYbkVxL0RhVEgyY3NxMFZXUWxNOFZQNkl3SHpTemNXaThwCnVVRUFOTFg3aG9maTFmZERGNnE2cTFrcmJtdjREa085RGdnRVpxVUR6KzB1dWZhTFNWWG05TzhDZ1lFQSszeFoKTGRJdjVjbXNHWTZhL0xXVjgwRjZHMVMxYUVJVDlBdXNpTThFcnhybEZpbjRSL2dDcEMzSjJod0ZKYldzUjN0OApMVUgxdWxqdE82Mmk2enkzUUlJa1RFNDRveUo0V1RVSEpWQ0VEYXFvcldySklWWTZxMFBlWGs5VDVwa1dzRFpOCldGeDRKQzZhdDZDcEtSTEJPSHdoSm1mYU53K1daamVob251L1pzTUNnWUJpMGI2UFlnV0luTlZRYUxoU3diTW8KS1ZrSG1LajVieWZTU1dGblZlV25kWms4N08vYjc1SUpML1AvcktwR3g0ZW1EblNUTkxXZU9YUWpzODhYZnA2QwpkVEtlcHN5SE5QOWV2NGVTZk0wZzNUcjBKUWdHWHRRVFVSS0RTNDJXcFJUVkg4azVhN0ZYRnErZlF2UHpkdW9QCmwxUkVJTEVnOHhkOHp5UU9QYXVtTndLQmdRREprZXlrMW5DL3ZMcXRyV2k2bncwMmNjZmVlaklCQTkyY1lYTUUKSVBJL0s4NXN5bTBQdWxEYnFUdStEM0ZzdlVYOThaTWhiMW4yNStvV1NHRnFMVHN3Z0Y5NXJjU2x0UzVEU2thVQorUWt2THhlT0VDWndDdjV4WWErdFplWDQwY0dtc1krakFGTG5wVmNyVWFIa292eXVPb2dUa1hBTmEvZi9yQjFvCjc4a0ZJd0tCZ0Q1cHVRd3NodXRYdmNOOCs1bUZDc2d6YW5hckYxSllPdTE2MkhVU2ZPQ2k4aS9VRDNWSUpYQmwKbWpNQVN4elhRamFSa2dQZXNqTmtsTEoyekJsK3ZLaG1jb3NJelNOdzM5b1ZMcC9ObTBiRzlpZmdUdmlha1N6MwpHNFhLYUdNczdRQ3h4bjZsc2ptYTRxYlFlU3JwWmVSMXVtOXJ4TVhLaVdGSUtERXN5TUpNCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== - public_key: c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFEYSs3TU04TzlVc2hkN3R1bm9leFZ2ZXdhTXpXRElTU0YxZzlQVlN5QXduQkdmOVJFME1EcEU2T3VSeE1jZXNUR0Q4enE1ZytGeS81N1RJQTE4NGpXMDFoZjhMRVdxQUdscUlNMWNPTkNQdXRacndQaGx5dm9ZZFRQZVM0THNqZ0RabEZGYjV6dVB4NTNVT2IyeUFpTDJZQkt4YjhMb1RwS2REOU9KWlU1b3NhTzBSTEQzOGlMUERrcU01dXNyUjFaQzl2Vy9raWo0d2xTY25uZzF4Z2dzV2lKczVYSHM5SkN4ZjRMNUdrS2tlVkg1bXhWUjhyV1RBakhMSGtLY0FzTGV2SjMwQmttL3FKYUd2K2V6cXRsNzJpYXJURE5RbUE0MzFNMWtIdGczaHBHSmNsem9tVitOeTRtQ0I4S1pWa2h5c1dpbkRMZS9QSC9TcmxRSDFNd04K + - private_key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1DNENBUUF3QlFZREsyVndCQ0lFSU1tK2EyVlRqSHV2OVhrWG1UR2Roem5GSVJPK0pTaVEyWUF6eHNleEJFZGEKLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo= + public_key: c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSUtZYU1pUWZTUWgyQVpqN2lNSEFBQUpxU0FQWWJLL0gzRFJ3NWtUSXBDRUMK tls: - - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURlRENDQW1DZ0F3SUJBZ0lRVENnQUVvV3BBa2U4YWdHandmVmtkVEFOQmdrcWhraUc5dzBCQVFzRkFEQlcKTVJBd0RnWURWUVFLRXdkNllYSnhkVzl1TVJBd0RnWURWUVFERXdkNllYSnhkVzl1TVRBd0xnWURWUVFGRXljeApNREV5TWprd01qRXdNakUwTmpjM05UQXlOREF6TnpBNE5qQXpPVEV6TmpNd056Y3lNemN3SGhjTk1qRXdPVEF4Ck1UWTFPVE01V2hjTk16RXdPRE13TVRZMU9UTTVXakJXTVJBd0RnWURWUVFLRXdkNllYSnhkVzl1TVJBd0RnWUQKVlFRREV3ZDZZWEp4ZFc5dU1UQXdMZ1lEVlFRRkV5Y3hNREV5TWprd01qRXdNakUwTmpjM05UQXlOREF6TnpBNApOakF6T1RFek5qTXdOemN5TXpjd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUM5Cjg5dFE1YTB0bzQ5dkN3dmxFTUpYTnBiYkpzUVIram96QzJScFFTLzZBSHlHbytueXRKUTZSclp6UWlZeTVkb2UKUTArUmhPTGM0R1Q4R0JQdTBrRmUwejk4Zlp4TC8wb2kvZ0trVTgrM0VOaDFuN1krbjV0RVBvbHZsZTBZUm5lWAp3NWpna1lDZG1TTjFMZXVqYW96OUpyYXVUMzRPSGwzdFNoZ3pBUkV1ZlBVVDJQNXlaQXNCdW9nYzBHd055MUo4Cml1UXpDb0tNektOM2ZidFhYU2llU3pubWxQeWMyeXpIcW1UZGdQNnVDNWFrVXpnT3E5ZGFjUkpxVWJRVXlXOUUKVk80MWRQK2wrMXdzNmdSaFNuK29Sc0JLOG1TVW1JNjc4VjE2b3RxT1I2ZDl5VnZKVTZSbjEvMWIzYk5ES3I2awpYSWwwR1FYczNtajd0QUEzVHNCRkFnTUJBQUdqUWpCQU1BNEdBMVVkRHdFQi93UUVBd0lDcERBUEJnTlZIUk1CCkFmOEVCVEFEQVFIL01CMEdBMVVkRGdRV0JCVFovZGxCRDdncWpKTU9rR2laSitGL0NXbkh5akFOQmdrcWhraUcKOXcwQkFRc0ZBQU9DQVFFQXU4cVZUZTYzZnd4UkM3cFU2bzRscldSby9ldnB3VkhkV04yT1NES0t4UU5ISURhbQovRDlnamhtdnphQWFKY3E2UmdkaTNCWlRuZDhsSDYzUlZuVHk4RzljUTNTYmFBMHlSQzMyQnQ3bjh6aUYxTTNqCkFxck5zV0hIWkZydkJKWkx2UDBqbms5RmRtSmFYc3o0cGgyRndYQjFTUkdFT3lVcFAvSS9OeVUyNFovRGFvOG4KWlRSazAvbHBVUis2SGp0UGliUTdJejkxcWtxNmsxcEY0SXdlbG5xU1Vqc2FjLzBGa1lib1I0UHkvT1BUbmtIRgpVR2doRUFHRzNnZXFsaGI4Q3FUYmRwKzhLb0R6RzNBOUtRUURBWUlMNkh5Mm9nVEJmU241Uk5WY2pLdHI0QS9UClhnSnlUdjdHZ0FabkZHT1dMc3B2TDhJQVdGYmN4T2hwV2hEaDNRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= - key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdmZQYlVPV3RMYU9QYndzTDVSRENWemFXMnliRUVmbzZNd3RrYVVFditnQjhocVBwCjhyU1VPa2EyYzBJbU11WGFIa05Qa1lUaTNPQmsvQmdUN3RKQlh0TS9mSDJjUy85S0l2NENwRlBQdHhEWWRaKzIKUHArYlJENkpiNVh0R0VaM2w4T1k0SkdBblpramRTM3JvMnFNL1NhMnJrOStEaDVkN1VvWU13RVJMbnoxRTlqKwpjbVFMQWJxSUhOQnNEY3RTZklya013cUNqTXlqZDMyN1YxMG9ua3M1NXBUOG5Oc3N4NnBrM1lEK3JndVdwRk00CkRxdlhXbkVTYWxHMEZNbHZSRlR1TlhUL3BmdGNMT29FWVVwL3FFYkFTdkprbEppT3UvRmRlcUxhamtlbmZjbGIKeVZPa1o5ZjlXOTJ6UXlxK3BGeUpkQmtGN041bys3UUFOMDdBUlFJREFRQUJBb0lCQVFDWkFiTHBxUGdrU1JtaQpncTFrS0duQ3dwQWxtMFpZak0wUWpONm5BZ0ZaU2NjRTFVZi9Yb0gvcHpJVUNYYW5qUXB6VWhqbnlMak0zbHU1CnpOTlJqajlsMkpmTStZbEtsaXJyb053VDdnYmxHVWFqQ0xGT0pGWjNWRUIwaDduaDBmRkhhQ0RlMDVWY1hSeDQKcVRLa0FaSHI0S0ZLSzNJSWdXRjdZREc1OCtRWkl0N01BdDQyc1NrUlhaenNyaUVzVWxZNU9xT1dlVHlkYy93dQpDK292V1FlOUU2bnRSc0EvY25nS0M1bnp6T0pCano3SlVTMGQ2UnBGK1Zodk9SRlZ6YTZKMnpRRXJsWWtFdVVUCkJYdThsVmhhcDVOU09nd3ByZXVmb2tNZVY4eGRNdUxuVEthdHVjajIwT3JFRUlHZVFrb08yTzhTeDJtNitOS0kKbzBTT2kzU0pBb0dCQU5LVkk4R0JHQUpSUlV2NVBnVkNDQ1VHSkFBRmtxY0FMTFlaZ0NMZCtLYStBVW41L2VONwpWemR4b2VqNG1UcnFOb0RLTnk5b0N2Z1RBWTlWZ3RZZllpdlNQbW5ZZk9FbTJUR2pLRTJSYzhBd0R5UWdHWFJVCm5WOUVueHRUUHF5dU9tQTFqa0ladXBLeUU0b0drdTE4MDRVaTB2d2pJUWI3cDdML0ZGYksvaGlmQW9HQkFPYnIKclBrOHdicnBlVk9QWmVudzVzbjVhTUpjdDM3emt4dHZzejVSVWlsZnZtVkdja1lyMU41SGpmZjVVVGcwNmNWdgp1N0dGY0NLQmhncFdqSXM3dHZ4cDhUVFM1VEZaWHZqY2VhRC91ZkFiN3ZUVnR0L1hRMFUwQ28rcDdzMENKWlZKCjY2MUtUU1RCYlFtR0xYYmNqNmFYdVM3bzFJM1FRU1l4NW5LV1F5aWJBb0dCQUtBTWZocUtGVWRkb1g5MnRhNmwKV3k5WWxXLzJ6RmxsQnBaNGx5em83QjArK0JmVGl5V2tEc3V5NzgzemMvS1ZKRXVLWlpzQVJxWDVQQXhHZjZSaQpRZWp3YUVObUtMT3ZKUkJXNDBEaE5jcHlQRy9HZmRJdXBWVk5BR2h5UW9aWC9VSTJNaU1IRHdpRGs5b3AyTzNyCkc1QnF3VlNsRm1zS1JaRUQwZCtOZE1ZZEFvR0FkVDNQRXJQd1FHL3RzNmtvdTBBZVRRbWVVS0EyWWZSVkNpY0sKUUdlVmFZQTg4THAxcG43Mmt1eU5mZ3ROVzFZeUlwWDZHOFYrQzJicm9UQVVKMVRvTVB1eEJYclY5dHBEUitMWQp0ZzlnWGpJd2ZvcExVUmJBQnRESFUrMlpXdWp1SC8vcDhvKzQzeUo5czhvMkp4VVFzaXB5VVFqUmNqYjcvT0owCitGU21RR1VDZ1lBdU5XcFhrbVkzbGtRSEFpU3RlblhTT2Zqc0xsbm5XWFJoTDVBYTZqRVZRT3FnUldVQnZ2YWkKK1RQRTNUTHQ5MGwveTZOdjU0dDdrT1QvSlEvREU4WmFiMERlTjdBRzRwRjMwNkxpYlpZNmswc3M1UDNXME8vbAozekJzQ0lEY3BHOWw4bDZSNkUwdHN0Z0I1c25hOE4rRzA2V3Q1R0M0UitRSVd1YTVpUmtVTXc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= + - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNBakNDQWFlZ0F3SUJBZ0lSQUpsUkc4OFlqTURVUmZmOENYMnRpbkF3Q2dZSUtvWkl6ajBFQXdJd1lERVYKTUJNR0ExVUVDaE1NYldVdWJHOWpZV3hvYjNOME1SVXdFd1lEVlFRREV3eHRaUzVzYjJOaGJHaHZjM1F4TURBdQpCZ05WQkFVVEp6SXdNemM1TXpBeU16UXpNelV5TURFNE9URXdNRFU0TnpNek9EWXlNalkxT1RVMk1qQTVOakFlCkZ3MHlOREV5TXpFeE5ESTJNRFphRncwek5ERXlNamt4TkRJMk1EWmFNR0F4RlRBVEJnTlZCQW9UREcxbExteHYKWTJGc2FHOXpkREVWTUJNR0ExVUVBeE1NYldVdWJHOWpZV3hvYjNOME1UQXdMZ1lEVlFRRkV5Y3lNRE0zT1RNdwpNak0wTXpNMU1qQXhPRGt4TURBMU9EY3pNemcyTWpJMk5UazFOakl3T1RZd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxCmhrak9QUU1CQndOQ0FBUlN0T0tLYklVeGppSTFIZEJGU2xVQW1LQW0wUXlxejljWVZEdlZEc0RNT1BqNFRTWGsKOHp5Q0FhYXloTlgra21lZlU3R2ZIeDBlVVMxbVhBYnl6UnNHbzBJd1FEQU9CZ05WSFE4QkFmOEVCQU1DQVlZdwpEd1lEVlIwVEFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVKZi94RjBtMStZM1lIRlRubVJhcGJ1dVdqa293CkNnWUlLb1pJemowRUF3SURTUUF3UmdJaEFLZkU5bXpPdzcrcUx3bEFxdFFWUVBNODVFTGp2NlFSdmZrcUE3aWoKSEtCMUFpRUFoNjVJZmV4NFpwT0szYVVUVTZQWGFkODlDdmhVb3NFMzRzWU1FZHdxZk9zPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZzRXUVZQclNGTmR6bVFMRWcKQkMvTU5sb2JEL0dKekNGU01vOUZsV0pjZDY2aFJBTkNBQVJTdE9LS2JJVXhqaUkxSGRCRlNsVUFtS0FtMFF5cQp6OWNZVkR2VkRzRE1PUGo0VFNYazh6eUNBYWF5aE5YK2ttZWZVN0dmSHgwZVVTMW1YQWJ5elJzRwotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg== 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` - databaseClientCAYAML = ` -kind: cert_authority + databaseClientCAYAML = `kind: cert_authority metadata: - id: 1696989861240620000 name: me.localhost + revision: 797ca33f-e922-45db-b531-ab62af23963c spec: active_keys: tls: - - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURqVENDQW5XZ0F3SUJBZ0lSQU5XcUtsOWR3WGYrVTBWUmhxNGEyaTB3RFFZSktvWklodmNOQVFFTEJRQXcKWURFVk1CTUdBMVVFQ2hNTWJXVXViRzlqWVd4b2IzTjBNUlV3RXdZRFZRUURFd3h0WlM1c2IyTmhiR2h2YzNReApNREF1QmdOVkJBVVRKekk0TkRBd09URXhNams0TlRBek1qYzRPVEV3TURJNE5qUXlPREV6TmpRMk9UQTVNamt3Ck9UQWVGdzB5TXpFd01URXdNakEwTWpGYUZ3MHpNekV3TURnd01qQTBNakZhTUdBeEZUQVRCZ05WQkFvVERHMWwKTG14dlkyRnNhRzl6ZERFVk1CTUdBMVVFQXhNTWJXVXViRzlqWVd4b2IzTjBNVEF3TGdZRFZRUUZFeWN5T0RRdwpNRGt4TVRJNU9EVXdNekkzT0RreE1EQXlPRFkwTWpneE16WTBOamt3T1RJNU1Ea3dnZ0VpTUEwR0NTcUdTSWIzCkRRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRGlDNU5GVDhmUy9hSzdSSVVRVnFjVmFxaFBzMFNuU0prTmd3azEKUHZkeFV1OWZZMlNwek5NaUUzSGZlb0Y4S1h2YUU0aHJzMEFGOVRmYlpJTnM1RjNHNTNzOUg3Q2JXWHpOWVRtZApCN0gyWEVxVGp3N0xGL2pzYzkwcTN4ZnZqMkk0Z29tOUdYK3dGMXdaRldjZXVJRkJTdXdCRkV6a1Yzc1o5NEVqClBsWUIxK2lnNlJoWGhvUjdhRlJUNDFvZmtMUUovMDdBVmR4blUranp5VkVFSVk3SjUwUWU3bFc2Nk9wL3BncmwKR1FBSnkwbnowUVpVYVJjVmZrODVHK3NwMnhjcUJ6clJHbXNybmw1TmhMdGJqcUJIUkZ2cU5XS1pLa1V2M0NjUApiTytWT1krV1FmV0UzRThhekxUQ2ppcnJXYWVXeTNLR0RTZGF5YktOK0FKbVpqU3hBZ01CQUFHalFqQkFNQTRHCkExVWREd0VCL3dRRUF3SUJwakFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQjBHQTFVZERnUVdCQlFhVmMzdXlYWnoKdDBWLzFnNzE3MzMrRjFhaHNqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUF6NTVkVnVFdmVLdnJtYThzL0dWSQo2Q0t5akNNYXNjWmhwV1JIT3QxRjQ1T0pjcXg1RDBQeVhSenZXS2NTYzlZTkN0M1BzSi8yNGp3VDlLaElqK2NiClQ5Z0h5WXNkb3pWY2NzMXNZTkFjK3VFSmRSOEsydHJqa1JJN0Q5VmZvTEJJVFlHUkJGTWpSOEE1bENlUzVnTkgKRG42V09rSlpRUi9UQS9IbFFlUmttMW5teUp3VVVQOVA0aUVWVlVSS0lMRVVNTS9EdERXdTZuNnM2K0pVVXNDNwp5QmI2T3JQeVRGbkV4TFljN2RhYUM1bm5UVDZHY2xUSm4wYkJ2UmtXdUFVa1FtWXJyYkpBMnhEVjFBL0JOcmp3Ci9aU2ErU1ZlVWJxSW05ZEVESE4zQUhXcmJzbWwyVjI3YUtrMHVUK0JmeUJBZ3NSdGpMN0U2YUdJanlNcStlOW4KYmc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== - key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBNGd1VFJVL0gwdjJpdTBTRkVGYW5GV3FvVDdORXAwaVpEWU1KTlQ3M2NWTHZYMk5rCnFjelRJaE54MzNxQmZDbDcyaE9JYTdOQUJmVTMyMlNEYk9SZHh1ZDdQUit3bTFsOHpXRTVuUWV4OWx4S2s0OE8KeXhmNDdIUGRLdDhYNzQ5aU9JS0p2Umwvc0JkY0dSVm5IcmlCUVVyc0FSUk01RmQ3R2ZlQkl6NVdBZGZvb09rWQpWNGFFZTJoVVUrTmFINUMwQ2Y5T3dGWGNaMVBvODhsUkJDR095ZWRFSHU1VnV1anFmNllLNVJrQUNjdEo4OUVHClZHa1hGWDVQT1J2cktkc1hLZ2M2MFJwcks1NWVUWVM3VzQ2Z1IwUmI2alZpbVNwRkw5d25EMnp2bFRtUGxrSDEKaE54UEdzeTB3bzRxNjFtbmxzdHloZzBuV3NteWpmZ0NabVkwc1FJREFRQUJBb0lCQUQ5R01EWkJxOVRDek0rUQowWktPUHZ6K3V4aDhQT1o2cXVVZVhmQjZyTGNiR1FoaGdTY0t2N3NWS0ZYL0s4bStydjJQWkN1SnBJMUdaQmxVCm5IbFp2MnBURjZzM2VLOHpzSHlwRDRDR1MrbURVaGpWL2JVYUE4TGtkKzl0UFgwQWJPVVduVW5Dbm55RFBYT0UKQ3phTlBSa3l5TGRRb0dsMmwyM2dXMVNyT1ZZUVBEUjZncWVJZVFYa3pHYUFUQ2twZWYrOVk4US9pTkZUR05oZAppamtXSUZOdEYzQjdIODUwdnR0VFFRckE3QXQ3ZnN2bmo2YVVDUkQ3MmFYZGJmeHIwK0VQbUR6WGNhejM3U20yClg3ZkJrakRFa0pCa0gwVnBnZHdvMDh3cmtzbnBieUNpbE95alp4Z3lhUWw2NGFKcGVUN1FHbEMxcm1kUEFmTU4KSEdweFBwVUNnWUVBK21vbllBVW1oSVpTR0R6VjI0NFc0QVRnVFdGb2gxb24wKzlZZ2xxR1RUZFFBM2dXYy9ReQowSmJ6QXpOVFZnODNibWhvU3NtbUMwZ1BoSytBb3BCZXc5ZlVxMmhIOVptUzVjZE1CaTJ6cFdvZHQrWXMvNys1Ckk3d3d2bGgvY3llelQvU0ZyazVCVVB0azZFR1pLZk1KdGZlNDcvb05uUzRmc3lmYlAyWDVUWHNDZ1lFQTV4WkcKSmhiYkwwWFljL0plc0JucXBuRFZHNXhkbkh6aGx0aVB3QzdGbHRDRUpuNFdhTUNpSUtsL0o3VGUzbndqMHk0YQpSVzFTWGN6anc5dHZxY0V3aE9CQUZBUHlsd0FWWjUyOTRzdWZzMnk4SHBFcFhhMjlIRXNReW50TE9JRFZyYkVsClJCV1pEb0xhbllVRTNtWml5WnZ0ZU9TT0hTajVhUnIzRTg1UmtNTUNnWUFweUVLUG8reGNXbWtpUUN4U3VPK2EKSzFZZHN5NFV2M2M3eG9qWEh6R2ZlcVl3SGY1cEZJclNBUTNGTC9Bc3dOYzM1ZFhZL0xKbTJYdzFZRzh2TUxXUApLZGtEVEtBTkc3WEYveTN4TGZqMmxiRWx1Uk16RFJOZ0lndGtCeklremEvK25FY2Q0VkxHcDF1YjRTNGtNTGdqCkU1VlkvVGorUys3Z0hydFhaYlZtTndLQmdET3A2eTBBMXlnT2VZSVNvZERGT296VGxSR0ROL3FRZ083MG84N1gKcGgwOXFRM2lDcWlJeUxaOHJvejJCdzIrdTFPdmJ2Z3VwTWVMMHpBcWt5QmtyTEJJWW9zWEJ0bHpqMVdIRXJqdAp4VnFiNk1MOHVUN1VaUDg2V1JxcnpmbG45RjNNeVFRYndBaGFnUDNPaTNRZGQrQ1RGOWg3WUxwc09yYWc3TFJrCjRCOTVBb0dCQU9maHNVSzVSZm1RU1ZzN08wMmMrdWFVelB2dnEwaXNqNW45dWlOaFQxdjFDUGY4YStZdkkyTisKcWV5bHkwRjN3L2sxbElaUzFjWlMwRDVWMUd6bmVHTUgreWYwVFAvNmlUcElHTC94N1pTNGJEZjE3dEc5dklDdgoya1JBTno5WHpzVzFETm9CRkJmZXg5NDFmT0RzdHlvdThvYmF3dDdJWThTdU1GMHV3aDlBCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== + - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURqRENDQW5TZ0F3SUJBZ0lRWGtnVThDZFo3TnkrbVJacG9GZUwzVEFOQmdrcWhraUc5dzBCQVFzRkFEQmcKTVJVd0V3WURWUVFLRXd4dFpTNXNiMk5oYkdodmMzUXhGVEFUQmdOVkJBTVRERzFsTG14dlkyRnNhRzl6ZERFdwpNQzRHQTFVRUJSTW5NVEkxTXpJeE56QXhOalV5TnpJMk16QTBORE13TlRRM05EQTNNVEE0TXpjM05qUXpPVGszCk1CNFhEVEkwTVRJek1URTBNall3TmxvWERUTTBNVEl5T1RFME1qWXdObG93WURFVk1CTUdBMVVFQ2hNTWJXVXUKYkc5allXeG9iM04wTVJVd0V3WURWUVFERXd4dFpTNXNiMk5oYkdodmMzUXhNREF1QmdOVkJBVVRKekV5TlRNeQpNVGN3TVRZMU1qY3lOak13TkRRek1EVTBOelF3TnpFd09ETTNOelkwTXprNU56Q0NBU0l3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFMMDZCa0hXMTBpWm1NTHZ6Y01tNC9WNmk3M2EvWjNpM1hzN1IxamYKc0FDQlB5TkNJTkFCbnFoV0NHdWJPb0JWT3REemQyVUJTZ0JXYVVNcXhiUytmQjBLYllEVDVkaFN1YXd4Wk8zQwpmN0xaZWtlZk4vb1FZSjk2WlVoemVvL1g4dkZZNE5ocXBIbXJMV1VRTGpZUEZ5cVhIWGM1L3hTUkVPVm8ybnZyCjNOWXFnR2EwRUFvMTRvRUJsR20zMmNQUWNvTGRBUUhqaFJrampQYStNeXRGSmNzeFkxWTFDemM5V1hnYzZvK1AKRWp0U3dRMW9Fd3ZOVHhYbXp1UElOcTZxdVZjS1kwcDF0anBWT1R1dWpaZ1R5NGRIMkJIQ1I5d1ZxejlVRGJPWgpWVHVKWjVnRFdQQkpGcVRPNUhkbzh5QnVLOXM0RndYZDJoaC9oa1YrNitabXdJRUNBd0VBQWFOQ01FQXdEZ1lEClZSMFBBUUgvQkFRREFnR21NQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdIUVlEVlIwT0JCWUVGRTZ4R09aSmFoTzUKc3FmQ3pZK0ZxTjQ3eGpWeE1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQk41V2JLdVRzaWlvUlltS3BlcGtiTQppT2FCbGJ4ajZtOTZDVHZpczJSeXlmUU1qSXduT1dYbUtMMHpGQUtvZThDWVk0UEJFWEtuZzZBd0NNVXdzWnNPCkZPMFE2Q21JUzlwQXdVZURIZ3VrN2x2dWQyeGFvb0MrazY4OGtmUlZhRjVPTW5WaW4xajBJY1MwM1d1QlhSckwKVDVwODFza3JOaG9Gd1BtdnU0RkZmU1lHb1YrajQ4NC83Y0FOY3J2c0FFdFBFdk9EdzlUVG5vb05zdUoreVhaMAoyVW5LSG1rOTFCeGdZVDVHUE1CVkYwQWxzenhjcVN0S2hBMlhrdzBCY1pnTXVBa3BYSXJRbUZNNGh0RnIyY0NOCkpTTk1mVUpsZVlXLzJiNmJvbENZTUliTFoxeldaaDBVYTJ6QjlOME9wNE1aS25xRmhtc2JVWFFOa0xGd1FEYzAKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBdlRvR1FkYlhTSm1Zd3UvTnd5Ymo5WHFMdmRyOW5lTGRlenRIV04rd0FJRS9JMElnCjBBR2VxRllJYTVzNmdGVTYwUE4zWlFGS0FGWnBReXJGdEw1OEhRcHRnTlBsMkZLNXJERms3Y0ovc3RsNlI1ODMKK2hCZ24zcGxTSE42ajlmeThWamcyR3FrZWFzdFpSQXVOZzhYS3BjZGR6bi9GSkVRNVdqYWUrdmMxaXFBWnJRUQpDalhpZ1FHVWFiZlp3OUJ5Z3QwQkFlT0ZHU09NOXI0ekswVWx5ekZqVmpVTE56MVplQnpxajQ4U08xTEJEV2dUCkM4MVBGZWJPNDhnMnJxcTVWd3BqU25XMk9sVTVPNjZObUJQTGgwZllFY0pIM0JXclAxUU5zNWxWTzRsbm1BTlkKOEVrV3BNN2tkMmp6SUc0cjJ6Z1hCZDNhR0grR1JYN3I1bWJBZ1FJREFRQUJBb0lCQUM4MnF4a0NZZlRiWGlKRgpjekdlSW9LOWNPQ09JM21oZ1dHZUNNOUVBTVlmZVlGeW5uMUg2aTVXU1FPUVY2aHRtNTlISUNNemp5TkdiRDAyCkR0NXFLTTJXTEh4WVlxRDNBeHpUdGpzY3JJQVRnMDhiaXZ2NTJpSHdpQlRydTBqb3VOVS9OOXJId1FJYWs5a0QKa0lRc2Y3dEF1VGxtWHg3aWt6U3FWTmxXb0dOUENZek9Bb3FZcXlTaWhEc0pLZ3N1SVFOV0lPVzE1dGxmb0NsQgpTTkF0cGxlclBNbFl6T2FjaUZVWkJFbGVOWEp0VURwd0szOGU5N2RMclJrYWRLeXhSVTNVTTE4dFR5TDZrZ2hMCmVsT3JBWDE2K0RFdm1PeDhGQlgyS29tak9OL0pORVZUN2lkcndyVnh2OU1PTUFpQjRDMms0a25jbUxIVE5DTE0KelJmOHY0RUNnWUVBNVg1d0VXVlZ0WFhNek02VVZCZ1ZwMGZmNVlPTTNTV3FoOHE1OGQrLzVBS0ZZSjZ5U1BpLwpoaVJvZ2xXc2NheXV3UVhTaHVMV2ZQVHJxMmVQNFVlcGdEa0g0c3NiamFwODFTMFZnN2JJZ3ZrdGxOTjdFMkRJCnFhMS9pNzBBQW9GOFNNY20vRE00N3RiTGxxS0ZwT21GSjB3dk1zUkh5aTJGM3pRaDB1aTFTQThDZ1lFQTB4VDgKSFZISnl1VHEvMERBdi9kLy9RNEtFUW5CS3ZmaFV5K0FOd0NGNkFhRW5USnAzTTRmNkpmUkVRUytFd1ZVVFk5WAoxWkNBdTd5N2NoQmtORG5TMmZJR2ZiQnYramVGWm9SZDRkQ3RwUnB1bHJka21yNGFaZnZnaGZsanJkdVZrdEM3CmZtQjZORTl5TURMYVBWT3Z4VEZGd3ZYMHRRUFdmYVNpL0Z3NVhtOENnWUVBdUtJT201QkJjbXBCeUl4eXZXMWIKRG1nKzg3SHdoSU1uUFhTV1FNaFk0Nkk3bUU1VTlXeGEraHNVa2JkSHMzVFFhNjY1ZjVmRUpHZ1BxcWo1RXEvSwo2TVA1V2pjNkJiR2lHUWZhaFV0cTZpUjZ6WCtQUnpuWWR0cUZBUEdmcm1ScWowcmFUSkVSUHVaRWlQNWNNeDlFCjV5YmQyaVFiOWNiR0s1c1BrMVZ4YzNVQ2dZRUFybkVzUGNyRzJyKyttYjVZelF6c29DUkhHM2VWUlQ1ZjM5QmsKeEkvUkdrU3d1ZnpjMGhjaTlhVHBxWWZpMFhOWkRWUUdRYi9QTTlld2pYNlFZVHpjVFRPZ082VmhsVWJuSHljTApNMEN6RUx3OFlxQWpLMk1xQzloUjRFYVBJekpTZFdlOVc1Njl2NWRjaGdxd28zZ1N6Z04vWkxUQlRBdGs2cWJ4CjcxOEVKazhDZ1lFQXZGckFPc1VJdmh4T0FYYk5mMUxwbnlabXVORUg3ek9XZ1FNM1ZlNG9iT3hqenVHaGs2RFUKVGhFdDg0emhsMk5LeFVacHRwdEJLRkdxM3B2dGc2TElqbEtpWmNPUEovbHcrTVZPb0U0S1Y5U0F3Q1VPN3o4eApDbTlzM05ZQTV6RWZRdGg0OEt5NmdBZVhZUHlkMWZidjl2QXlwM3FQbEpxa281SG5peHNxamNvPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= additional_trusted_keys: {} cluster_name: me.localhost type: db_client @@ -1439,67 +1422,57 @@ sub_kind: db_client version: v2` databaseCAYAML = `kind: cert_authority metadata: - id: 1640648663670001000 name: me.localhost + revision: 3b019bb5-051c-461a-a7f4-e335fc031bf3 spec: active_keys: - tls: - - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURpakNDQW5LZ0F3SUJBZ0lRRU9IcEhIZkZwZ28wUndQSVJhdkdpakFOQmdrcWhraUc5dzBCQVFzRkFEQmYKTVJVd0V3WURWUVFLRXd4dFpTNXNiMk5oYkdodmMzUXhGVEFUQmdOVkJBTVRERzFsTG14dlkyRnNhRzl6ZERFdgpNQzBHQTFVRUJSTW1NakkwTkRBMk5ESTNPREkyTWpJNE5UQXdOemMzTmpVeU16STVOak15TWpNeU56VXhORFl3CkhoY05NakV4TWpJM01qTTBOREl6V2hjTk16RXhNakkxTWpNME5ESXpXakJmTVJVd0V3WURWUVFLRXd4dFpTNXMKYjJOaGJHaHZjM1F4RlRBVEJnTlZCQU1UREcxbExteHZZMkZzYUc5emRERXZNQzBHQTFVRUJSTW1NakkwTkRBMgpOREkzT0RJMk1qSTROVEF3TnpjM05qVXlNekk1TmpNeU1qTXlOelV4TkRZd2dnRWlNQTBHQ1NxR1NJYjNEUUVCCkFRVUFBNElCRHdBd2dnRUtBb0lCQVFETFRrVFkzQ0NMVStNUllkbEMwM2NUTTR6MUpiRGoxYjFQRWdING9iSmwKRjl4NWtQbzhncWNEbmp5L0x5NHdKeUR2Q2xPMkw1T0k3UnYwa1hFUXoybUVEeExnbjJYRG9ZNUh5VFNOVkZHNgpvZ3BlYmhlUFN1aWl0RUNZYUZDZVZFTGNDa1Q0ZGpqRDlwOExNTnJ4MHRPOXdQU1o1OXBLZUxCOG90RFloOHRCCkcyb2EzSGIzTWt0RGxOY0svVE94RFNzRzUrQ2ljdktTa3QrV04xaXJJQ2pvZ2hWTzJGcForRkdxWUM0Y1EwbWMKM0NRaGJwY1o2VTRkWnpGdFJZVzZPYzNucHBOSkZKWXZSSTRIS1FWY0RCM2N4VkhNTUd5Rzc3aFRzdEwvd0RuaQo4U2s5eml4VzN4S2FvUnlrV2FuWno4eC9WdHNydXJqanNzNDV4NlRoem1VWkFnTUJBQUdqUWpCQU1BNEdBMVVkCkR3RUIvd1FFQXdJQnBqQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01CMEdBMVVkRGdRV0JCUmNKK2NRamFQWjZGbEIKcVhoYzYyWXZldGRpQWpBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQVdYNmxZdUhtMmQyMU41RDN1eUJJelFOdApKVFR3b0xnU3FQd09Tbk1EU0luSDkvMjBqZDNGUk9qakh1M3BQRkNLRmE4OVp0ekZxTHZsTjVOdlh2WHFuOXNKCkdudTYzSVo0TWtEZk9sSVZpWFhQWFF4YllHSkMxRVlVU28rTDdtUTY0VnN5UkFpTXdnbmVwMUxwSGhROGYzU2MKeEZoVkNybFJDMmUrNENBai8vOVZWaDdvTEdSMkNhM0xEcFc5VHFxYnB3MEh0QitNcFVqVWxCWnFVbzNVMm5HTQpia1VhSVZKcnNuYk1rYnNsUGQ2dWtVRDlVTHFuUmxJb3A4cjQ1VTdvYVBhR3g3QVFiWndzbGlsNVVJZlppRmlRCm5USk9kYnJHampVdXlRYkM4UUpZY3RhdENjbVBjZUlXMVVWWFVnZ2JsdXl4VjF1NWsyYzlSb1k2RzhiN0FRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= - key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBeTA1RTJOd2dpMVBqRVdIWlF0TjNFek9NOVNXdzQ5VzlUeElCK0tHeVpSZmNlWkQ2ClBJS25BNTQ4dnk4dU1DY2c3d3BUdGkrVGlPMGI5SkZ4RU05cGhBOFM0SjlsdzZHT1I4azBqVlJSdXFJS1htNFgKajByb29yUkFtR2hRbmxSQzNBcEUrSFk0dy9hZkN6RGE4ZExUdmNEMG1lZmFTbml3ZktMUTJJZkxRUnRxR3R4Mgo5ekpMUTVUWEN2MHpzUTByQnVmZ29uTHlrcExmbGpkWXF5QW82SUlWVHRoYVdmaFJxbUF1SEVOSm5Od2tJVzZYCkdlbE9IV2N4YlVXRnVqbk41NmFUU1JTV0wwU09CeWtGWEF3ZDNNVlJ6REJzaHUrNFU3TFMvOEE1NHZFcFBjNHMKVnQ4U21xRWNwRm1wMmMvTWYxYmJLN3E0NDdMT09jZWs0YzVsR1FJREFRQUJBb0lCQVFDeXNLNXVkTHZkK2ZOQQpHZUtkaThQRENySS8zY3JsMWIwNFBEbWpVR3U5MHdVampEdUU1OGpuc3pMdFR3aW5waHlhUFZkcWI5S2FyTnkvClR2NHpxam14cXBZSys4Nno3ZEZpWXdSZm05YmgxUDZNRlBOOExIamdXTkhWb3dvSXYwS3NxQklLMTgzNDMxRFcKd3pBTkVDS3ZTMk14eXNqZ1g4ZXZKR092alZzbWN1SU1IWFljbVlORlo0dWpuTnc0dnVSWHhlYUtPY2ZWTGZ2ZwoxSWZMK05IYUh6UXI1YVoydkxST1NpdHVId2cvOFpZZ2hQcVFYLy92ak92M0FENzhXVGxINUZXWjExR0hoeCt1Ck9ZUXcwQ1lvTnNiV1UxeklwVTV0cHdCcDRGV0VlVy9jbTY5NXRBVVRlMzR1N3R1MlJJOVBZMEZDWVJ2ZkM2SUQKK2tDNjRFTEpBb0dCQVBSTEt5a0pjdHNGNngzN1liWXdmQm9ZK3V6YU4wV1o1d1RuRmVFaVp1ZzVVYWJUOTdqRApZTnpaYzQ0aitRbkxFUGVDak5tU1FhVHFxK2QvbUVjTnlXS244cVNFcXlOR0R6YlRXQ2tkMGtyWTVwcm9FVVJnClFqbWFwRHFNNEtOSm9jQ1RCS3lueHo2QzZ6ME9uSnhZM3lMdTdyYVBqeE9HTW5rcm9VMDhMVW56QW9HQkFOVU0KU2NsNGh1R0gxK3ZNL0RtenJoQ1NKbUZIcjE2QjhvWExaMHIyNmFKTnJVQ3AwYUFzV2FHd3JLZXZFcDkzRmg5Ygp3QlZYMHE0bXJ3cmZpTGpCYnRzdnlQM3l1ZWs5cWl6M2ZoMWIvZ0k2eWRKVFEzMEplQzB4UzUyRksvR2gwanEvCm43c2Y1bm5DbTlCRW01ZmdSeDFVUmVhOC9vL2M4cTNhbW94c0grdkRBb0dBZUVZUjU5QlpGZkJpQTQ3aVdwcWcKWHhEeGFXOCtTeXdzaTBOaWlFY3h0eCtSVGJ1S2VSTG9PNU5yeXcxMjdSVm5NeFM1Vjkwa0tKZkpMdDZwRUVKLwpaZTBlRDFXcUZHSEgxOHhSMlZ4dlRwNWZXdURxcjJsYzhaTnJTOUJVUU5CZHJMdzFUdlFEcW9rMlhBYzNuOW81CmNhK0ZJNmltWG94eGlTcXI3YVMwLzNVQ2dZQTJBWEJ1N3V1YUhocGcvc3h0UUJ2K3ZWMlhTVm11SmxpNUM4KzYKVkE3emdxZEpmZ0xTakl1SURrWW1GNTRyNkQ4bVlkYTJVbFhvcVl1endPaGlsVDRwdDlwR2JaSXRDdUdwbG05VQp0KzRTMko0eWY4TGEzbHlsY0JxUDZxTXlGR2c3VmpvQ2NGcTNRTnJJbDZ1dGV6L3JzbUlwMUh6Zk1RNGZmZ3V4ClR2Tmtpd0tCZ1FDbVhaSStvTUdQK3U4KzRVaGxjR01NYUNHZi92UVZLdVJYOHlOYVh1bUx6dk1Xajl0cVhpUzcKK1dQUlhuV01RSnd1QldYMzBTcWdFVVdDbjlzOGxWbzh2TVF4MmFtbXhhWkVEMGRoOHNMSkJDNXJoRmVqV29MbQp3cHg5MXR0S3JJODBKMDYyeW90SFpJYkRyQW1LSGZFeE85U1d4T1hUeFVMUGdvTlJVUW5qSnc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= + tls: + - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURqVENDQW5XZ0F3SUJBZ0lSQUs5OUdYVGFmVEhNNWN2cEpzK0xhTkV3RFFZSktvWklodmNOQVFFTEJRQXcKWURFVk1CTUdBMVVFQ2hNTWJXVXViRzlqWVd4b2IzTjBNUlV3RXdZRFZRUURFd3h0WlM1c2IyTmhiR2h2YzNReApNREF1QmdOVkJBVVRKekl6TXpJMk5EUTFNalk0T0RBd016RTFOekl3TWpNeE16TTVOekl3T0RZMU5UVTFORGMyCk9UQWVGdzB5TkRFeU16RXhOREkyTURaYUZ3MHpOREV5TWpreE5ESTJNRFphTUdBeEZUQVRCZ05WQkFvVERHMWwKTG14dlkyRnNhRzl6ZERFVk1CTUdBMVVFQXhNTWJXVXViRzlqWVd4b2IzTjBNVEF3TGdZRFZRUUZFeWN5TXpNeQpOalEwTlRJMk9EZ3dNRE14TlRjeU1ESXpNVE16T1RjeU1EZzJOVFUxTlRRM05qa3dnZ0VpTUEwR0NTcUdTSWIzCkRRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRE8ybGRZbjhIc3ZNWTkzZVFMSWRoMUk5WmJtWWFlZm80UHJJRTgKNXA0bUpSUnVMMDg2Y0xlbFZNZDc0K1h3RmlSOUpLNlY1ZHBYVGw1cFoxTVBEbjh1cUkvMjAwWnlvVHlad1ZWUgpISUdwSjFqTVRZbjBQcDE0TC81Uk9MbXAwVTEzL3hRVDd4a2QxZnA4QVdzVzFYYnloc1hYaEhkMUpraTRFY0djCkJtSjc0Nml3aGxyUVNZcTNvSFpXNzZjdVVaM0g2bU9QVzVZenl1cUVYR3J3SUxneExZSVZNNXlxTUpEbHdiTU8KMzlBTGE4bzl1U1ZiaGJ1QjlaeG9BT1h4ck9ERGJSN0RuVTc0cEFTQm5pV2g5aTFEMTBBK2NlMEQySVNjTFcvWAo4MXpKSzVGSXRZSldSVDQvZUo3MmNMUGUycFRlSTMreEFCNW9tZVBCQjlrZjlLanRBZ01CQUFHalFqQkFNQTRHCkExVWREd0VCL3dRRUF3SUJwakFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQjBHQTFVZERnUVdCQlFacW9pb3paYVoKMTVOcFMrVjVUV1RUNnY0S0V6QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFPZFhnYk1XWUM0TFdmMnRtNUtyZQoyM2J5MlgxWHZYQ2U4L1RGdFVsMUNhdHVrV2MwY205cXUxckxiRy84ZUxCWjA2cUZSc1VTSEUxamEyRUtkREU0CjU5Y0p3bE5DN1hCMjRTQ2s1QTcxNGtpSDZMQjAxRk00WGEzWjg4YnZCSHV2ak01Q1ErN1lSSjlGZFd6YjFKK3UKREF3czI1V0haWTFGV0ovUGt5eTVycjJNTWVvS3dWMGRNSjJJWlZVMmpGZkVhYXhLWml4Y29EbG5KclIzWThEMwovUVZRckY5d0xkQWVENkExTlpjbnd3Q2F0MWFoOVA1cWhQdGlnc2lsOVM5dkd3T0JyQUEwNE9iZXQrVFNjd0tnCkVQQWhFWVlkRWUvRzV6MVZUQXBCUCtzeXRWcHdRZXQxZDRlV2VvVW04QU8yZEVuQWhhM0FvU1pmQjFTVVgxTmEKb3c9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBenRwWFdKL0I3THpHUGQza0N5SFlkU1BXVzVtR25uNk9ENnlCUE9hZUppVVViaTlQCk9uQzNwVlRIZStQbDhCWWtmU1N1bGVYYVYwNWVhV2RURHc1L0xxaVA5dE5HY3FFOG1jRlZVUnlCcVNkWXpFMkoKOUQ2ZGVDLytVVGk1cWRGTmQvOFVFKzhaSGRYNmZBRnJGdFYyOG9iRjE0UjNkU1pJdUJIQm5BWmllK09vc0laYQowRW1LdDZCMlZ1K25MbEdkeCtwamoxdVdNOHJxaEZ4cThDQzRNUzJDRlRPY3FqQ1E1Y0d6RHQvUUMydktQYmtsClc0VzdnZldjYUFEbDhhemd3MjBldzUxTytLUUVnWjRsb2ZZdFE5ZEFQbkh0QTlpRW5DMXYxL05jeVN1UlNMV0MKVmtVK1AzaWU5bkN6M3RxVTNpTi9zUUFlYUpuandRZlpIL1NvN1FJREFRQUJBb0lCQUgxbTZ2c2taeG1SWEJHWAptcStSQmp3RnpPZGRUTHA3ZUw1UjAwdkxkK2NpSloraStNSXlJWE9PMFJ6dmphK2VqT0o5UVlaSWdiVGFJdXg5Cm9tSUhaTjB4ZlkyaWlodm1XZW5ReGx0VkQ5b3ZxMnE0TzBFaVVLN1RVYmVGenpEL1hacTR2a0JUZklPVS9MVCsKMnlCTnF6M2VyTVE2WDMxYkIwem9IdHJyRi91SWNzNHNnZXFOTWFtU2JUY2I1ODRSbHhUNDA0MUdlWWdtNlZCRQpHWnpRSjJESXlQbEExZFhGUW5lcHpaaWRqQmdJMWJKS3lTMzVsekdjWVc0aGgvbVFhUVp5ZmRuS3Z4QmlsdUlBCjBxcnAvTEQ4QVp6RWxONVNMb2JtMXVEZEIrRW1RNllKbDEvdm5iWEY1QWFXSDN6b3U1OHJDcGFudDcraVJ6OGwKNFhuWjl0VUNnWUVBN1dPajhMSDBYVnFMcDMyTnNrK2xnNmYwRSttVUZnMU93eWZGSEJPdlpsRDQ1T3ZQNWx6ZgpsLzNoaUJiZEMzNHNhWU1HdW9GZlRhMjJiVHpwTXl6eXFhZVRhdy9HS3psR2Jlc0dIRk9Xd3F2U2Ivd2tWTEUvCkZocTZGSE1vWGp0aTE4eFhjRXdJQmVQaHhVZVkrZkd3V0RTREV1c3NZdFFxQjh1ajBXaDlYUDhDZ1lFQTN4SFgKbUhpdjFWLytPMkNPZDdWUkFWaThKTS9JZ0NQTk84L0o2SWRYTkNwKy8wNW10dEh0QnpyVFN0cXBGMlBSUWJEaApjYmpuYk05TGJTTVNKTHNtYndTOEhtT3MwSGQwSzU1aXdqRmFxNUdOT0xBQmlrOGMrU2Naa0d5YitxRHlGRU9UCkMzMThFSEg5SlZ6L24zYWxHZmRGWG9xWkZ1RXJvQWJCTkFEZlBoTUNnWUJsZHkxZmQvQ201a2pDOGx0YVY4aTcKR1ZLdUlDeDNzSUIxMGMzaVRsZXVOL1hxZ3hCOXVqeW56cEJUaHRJOFUxWFFVM3pRd3ZObFZGYWhJbVBheDkrQQp2R3U2V3llczJmSk1rU1F2ZjFyMUlsUDBJYVcxdlh6bGljNzNackZlZGF1dDZWMkdWamtucTF1WTR4MXoxK1kwCkRWM28vRFFnbWViTkpqR0RGRkpoS1FLQmdEaG1VSFp5ZlRLYjFMRzZsZ3JhUXlMdUJwUGdIVGVZMWJrN3JqY20Ka1B2VmlzcU9UaFlIT2NETU5NUUdTUjVxMUd1aGh6NnptMys5WWJxMFZWQUlLWTJFU3ZQOEM2T2hzRE9mRmlVMwpTVTk3dTVNTG5UZ1ZES1JLS0lLRmsySm84d3dBa2RzajNReGpaYmZlclpycDZwQ0lIbmZxM3c0VDNHM1hoMTNZCm9wa1ZBb0dCQU5USGlsZHVuWXBCTXNFVks5TFRWSm95TlNOTVdBbGFGaWlwZllmWm82M3RyQzgxK0FtMHFDQW4KZTF1V2NwRFFkQlZzbGRnTWV2TEcwOUNBU05rRjN0ZUJqWEsranNzWTRWMllTU1Bwd0dyWWhjZHpFMFBIZEVROApiMGllSGJUMGRZZ3hjWmczbW9pN1BDZi90d3Joa2lDdllIMlBMR2hzMkduV1JzSEQzcHY1Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== 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 + revision: a3f0310c-80fe-4c43-95d5-ba3f3cf9aedc spec: active_keys: jwt: - - private_key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNWNQaFordnk2N05NbDhwbDJ4SllvSDVFOTl0a1UzVlByTGhnVkhOMmpuZmoyRlhNCm9RdVRKbUJOdWVEd3FiVFpKREJmQ0d1bTU2RVczK1YrQUtsY3pFNVdFS2NBR0wxL3ZvOGU0NDNBRmwzQXRHVVEKSWRHVzRoL0o0Q3BTNG9idVVlMGt3K0JiZnlocUJ2a2lTNmxtUUpjV0hLbzd2aWZhRTJKc1hmZStESklSMS80eApvYjlrbkw1QWVoRS9XV25CY0RiWXROcHlFOHBoQzdGaDgzYmFUdldqbXNWTEZaaWEzKzJqL255aWRzUmUxNGMyCnR4czRWRXp6UVpaYTMwMlpESFRpRmM4MzJtY3lCTEFzRnMycHNsSkxiUnZLdllBS1dLTnRRcnRlSHIwUFBmamEKQjM3cmNWMXlLYTJONjNRTnZrd2FRMWlnbllqSmE3blRQYWpVSndJREFRQUJBb0lCQUY0b1ZMSUt2bVVhK0RObwpMUytHcUMwMU1ieEUreXM4Y3VjOE03WElEM2k0NXZWYnk5emZhbkVhbkIrbGI5cU1FMFJDVWwrWUJqRDhFZXkxCkZscmREUHRveXRwT0picjl4V0RwTStaYXk3SWV2Mzd0djV1c1VXSGZWeEozSmJwUlEwN3RtTmh3ays1Yk9JQWQKRHBIbEhOTXhWMDF0OGNldWV5N2djYnBjY1ZTaXJNYy9ERTlGcFNONFRRWFloMlRDSnVZcTdncFo1SGJ2MlpleApDOHFTTnZqVVlVTithTGlDZ2pBRFhZdGdmV2RUVWN0YkhldVUrL2Ftb3cyUitOVjFWMGpML3lOQm0rdmZUVXNuCmprNFMxQWx1U3FrT0FFaHJhZjM2UzRNcWZQWm8zbXhFOC96TVZTdEY5MTdvQVRKMlZrNzVZaGxINnMzM1BKN1kKMXdDeVdZa0NnWUVBOFRoT1E4M01xTjFTL1VVcUJWeGRaaS9HZFBldmQrbFUvY2FIQVhTNmJyd2RYNEpITTkrbApaczZVWFdocWJHUXFSVlRvZ1dna2VBSHp4R3I1S2QvSTlRRnhEZHE2am1vRUhmU3VNNnNiNU4yVjgyQTVSaUhHCmduS3IzMUpWQS9FdEt3Vk5sZUY3L1VUM0tzWTJzajRZNFkxRnI3amRkOS9tdTVaNWVwODFidFVDZ1lFQTg5Zm0KSE9tc0JYZUQ0M0FHVmltSFhEWWVGckI3UWRhY3hOQ3R4NjZuTk5jMFpxM0pDZGtDMjVaTG1nQytON2JEbkRjMApNZ2NPUGdyUWxVdWpRbWl1d1daeFlXcDAvWDh3STFHZ2p4RkEvRkJvNjFnd3lwR2tqbjJlS3lVM1ovWlJBRTBjCmlxaW5UTVc5TFBlV1k0MEhOUzFic3E2YmFoeXg1bTFVV0gxclRRc0NnWUF3bE1WMmRHbEdqU1NjcTZSVjVnOU4KZUV2QTNPMXkrZ1JMQkFQR3NFcW42SzBGd2tneTAxVU5pb2RvOUpHU2VPM21mcjVBNmNlR2YrWW5aZC8rcGZwawpGY0UrS0JJd2dudUh5UEtZcDFwNzBvRFR2a3Bxckh5OVl2am9oajFuQ05pdTlHZDJ5eTNjaVZvNlBDZGg2STI4ClIyYUVpSGZhSDdicGl0bTJiNEFrYlFLQmdRQ2tmUUpraEppZkEzVTdpa2tyL0Uyc1BYRmttdDQ2bG53Z0pDam0KSjRIeG1pNW1DVnN4UW11MEZ4bWVwRnVzbDZReWorYXN6S2VsNElPK0FrejZNa1dZZnZPQzVGNVExbWh4bXRHMQpVTTFHcHpOdmRvbExUSjMxNVBVNlk1dVJqTTR0WnRjWERoZjFLUHFwQjhjeUZtTkRVdnFsZVRXcmlmblQxL0pxCjB3Zjc2d0tCZ1FDN3hIK3NXbTR6cjVRb1VvMWlrYlRMSHRBaVFETWE2TlpWTTk0MHcxZHExUWk4YXpMTE1lMUcKUVduU1ZqTDVLaVlSY0toSjVYOGpoQjF5a2U1OTR1ZU1CSFJRdjFRU3RCalRkSzIxN2tnTTFTRmZEbzFFQWpRdgpMWk5pWHROYXVDeG9BMi96SHJrZ0d0NGQzQldKZk5ENkJPQ1RKK2lSRnNWc1VvcjhITzJQR2c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= - public_key: LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQTVjUGhaK3Z5NjdOTWw4cGwyeEpZb0g1RTk5dGtVM1ZQckxoZ1ZITjJqbmZqMkZYTW9RdVQKSm1CTnVlRHdxYlRaSkRCZkNHdW01NkVXMytWK0FLbGN6RTVXRUtjQUdMMS92bzhlNDQzQUZsM0F0R1VRSWRHVwo0aC9KNENwUzRvYnVVZTBrdytCYmZ5aHFCdmtpUzZsbVFKY1dIS283dmlmYUUySnNYZmUrREpJUjEvNHhvYjlrCm5MNUFlaEUvV1duQmNEYll0TnB5RThwaEM3Rmg4M2JhVHZXam1zVkxGWmlhMysyai9ueWlkc1JlMTRjMnR4czQKVkV6elFaWmEzMDJaREhUaUZjODMybWN5QkxBc0ZzMnBzbEpMYlJ2S3ZZQUtXS050UXJ0ZUhyMFBQZmphQjM3cgpjVjF5S2EyTjYzUU52a3dhUTFpZ25ZakphN25UUGFqVUp3SURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K + - private_key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ3dFaDQ2empZTHltT0ZqNWIKSVZDNXBkMUxSbWU1clAzTmRVN3hKQ0plMDJ1aFJBTkNBQVRYQXA5MFRRTmowaDhCNHllR0dnNFJWQStsbVV4MwpRN3EzdFBLbEJaSVpiMWtXSHdSRmFXdUJmYUQ3Sis0MnUwSjRvVGxqYU5zUUg2Vm8wRXRLTkI4ZwotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg== + public_key: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFMXdLZmRFMERZOUlmQWVNbmhob09FVlFQcFpsTQpkME82dDdUeXBRV1NHVzlaRmg4RVJXbHJnWDJnK3lmdU5ydENlS0U1WTJqYkVCK2xhTkJMU2pRZklBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg== 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 + revision: 2c49b2de-7529-4e8f-9442-dec2357f2e93 spec: active_keys: ssh: - - private_key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBeHpRdWtjUlhCR09nRDUrS3B1MGRrdVpXVjVsc294Wlp1cFRhRHdFcmYvQ1prOWFqCjRaZzJoeTBoUHl5SXRBRytIN08xRVYwZ0taWVRDWWs4REtiaFQ4ZWl3QldDNUhKaGtpYW9ZRStSUXpQTHd3STcKcElmb0t4ejR2WG5MRnNvWHVVY2hLTmhYdk1RZU9pVVJ3OVQ3L0RSREE0dDdsazlPY01jWE9ySHV3L2JxTTA0VApmTmhVeXhHVmhyTC90aFpQNDE4b04xZjZKQzZMU3grZThIM01iSEQralVGMjBVbU8wVUFLUHUwRXVQS3hkS2loCm1tbVZDZ3FaRWhBWjhrcklVYlV5R0tUemZjQyt4VFNrUEN3UE1qejg1aTJQVVBYS3hmVE00cW54QVh5VzRCSzYKeXo1Znk5UTgyalVESi9VS0JvSU92N1hUU2JhNUpDUHdiUUx4cHdJREFRQUJBb0lCQUJydU53MkYyYTNDT2pWaQpnRUFvOWtLUjJVSm1mNFZjMUN5aFN3bVVRdWs5QWNZMjBsa0JWdjNYWUJOR1ZnVGY1M0FwdjJUbGpoK1JKbW0zCm4rS2wvUGZvS1Z5R2kvZU9ieHB2RjN4TnhYbXNXdk8yTFpJRXZhSjJmRHBCYU85Znl1MUZiSG8xSlVkanpDSlkKT0pxZEJLUUgvTGRSK0JkT0NYQzl1YW81dStuS0RxZTZvaE1FZm5VUUpmeGd4QzNwMHhzRGkvNGovYWhCV3cvaApoZk02TUloaUlmZEhWNlN1MWlPQWlNbXNtcDBWSkd4OVR5SG9UZFdKWHkxYjRTNDh5L21rNjdNeThzUDJ0SXRHCksvdGxaaDFteDYwbCtscnZkTDZFVW9CVDVScXdCZDBCN0g3c1V0UkZjVVVkMVZBNXpMYVowYnhkbHVpL1dEMlcKaHVtOHJpRUNnWUVBN0hPZlN3OVRKQzIxSHFwb2kvUHRKOGZ5QjVMRXJaQTBmY21vd2VtMG55dTRyMVkxN2FSTwp6bU5qYmZtYWRrOU95RnJNaS9Ydm5pbW1wcnNmK3Z1dEVkeEJEK3JYMGJPZk52ZEl5NHNMNEFaN2JZSkdUVEk0ClVFREdPRVF2UkpUQmxhQ3hvMGVEcGVSbTZqamhjYjdmcHV6K3ZCaVB2ZFBHbExxU0JuaTczSDBDZ1lFQTE2dzYKazhzUDlCK251MFFkSVdRano0MU9wTzJlb2FKenhndFVRTnkyV0VuRi9ydk51K21ra1BtL0lWbWZvdzFOYXAwNApGL29FeHpUSWw2WkFCcUdjWUtHVDRNSjRlVS9QT2RsbWZtSE5DcThHTGs4TlRBaC9UdVFuZ0VKTDB2UE9Ta0wzCjMrWHg2N010MDhVUXNmRjlDQU8yS3BuQzhOOEw5NmhjOXp6ZTgvTUNnWUEwakptNVQ4V1ZnOGI5OHJkYmF6R28KcHFvbWZyclJLL3hPZkZQU0RNT0VvRzNpSWRISVo3elA1NHpBY3ptZDA1Qlp2THc2MnNTUExRaUpnNHJlOTdJRwpCeUk2akdHOGpDUDFUazNTVnF1ajlTelhNSjI1S0ZFVm5OK3d2NDZWdWsydm1GQUNUckYyVytWM1puN01EYlNjCjM0elpkc2Z6VXk2Ti9Vell2VnBhN1FLQmdRQ3NOZC9JSnpxajZhcmJBdlpudFRoTEFFQXR2WGNQQlZLQWJvZG0KQzFhbWhMSE9SMU50bXBCSEdzU2M4cDFmYXIzSVJhV0dyNktsRmVhZUFLZmJJNnhrRkdDcDlWNlJMMEwrcERNTQo4emJ3TXZVeWdQalRIMjNZSnFITDdpUHhXNi82NkNKWTY1a1NaVTVRYkdoNlRhTlNoUFF1YS95V3JPTTNhMzVnCkJJRGFOUUtCZ0dBSE5EOWVjcGpySElxUFgzekZUYm1uZjZORWl1SmN0aHpPN0hLZzI0Slh3V0FoRWJBaXpUYjQKRnY1VmY0d2dKc2xGSjFmNDdhVjJxSU5ncWJ5ajBMSlNjYksyOGlXVmIwZmxSbnJEZnd0amxaS2lneWRDTlZubApteUNBS2IyOTlZL1d0Y3NobjUxa29DdWtzSVA1VzJwZjk5M2pLbEw0OWNPN0l6N3VKbTEzCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== - public_key: c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFESE5DNlJ4RmNFWTZBUG40cW03UjJTNWxaWG1XeWpGbG02bE5vUEFTdC84Sm1UMXFQaG1EYUhMU0UvTElpMEFiNGZzN1VSWFNBcGxoTUppVHdNcHVGUHg2TEFGWUxrY21HU0pxaGdUNUZETTh2REFqdWtoK2dySFBpOWVjc1d5aGU1UnlFbzJGZTh4QjQ2SlJIRDFQdjhORU1EaTN1V1QwNXd4eGM2c2U3RDl1b3pUaE44MkZUTEVaV0dzdisyRmsvalh5ZzNWL29rTG90TEg1N3dmY3hzY1A2TlFYYlJTWTdSUUFvKzdRUzQ4ckYwcUtHYWFaVUtDcGtTRUJueVNzaFJ0VElZcFBOOXdMN0ZOS1E4TEE4eVBQem1MWTlROWNyRjlNemlxZkVCZkpiZ0VyckxQbC9MMUR6YU5RTW45UW9HZ2c2L3RkTkp0cmtrSS9CdEF2R24K + - private_key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1DNENBUUF3QlFZREsyVndCQ0lFSUhoT0o3KzZlek4raTZ5dVVhVDBPT01rVm1NK0pIZm41Sm5ONzJFNmduWTgKLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo= + public_key: c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSVBuQ0ZocHk3TUdBZTNBSDBjTDBkMmVUK20xNVF1dmptMUU2QXlubm1uSkYK 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 + revision: 252e2c73-6805-48d4-8491-dde615ff8c47 spec: active_keys: - tls: - - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURpakNDQW5LZ0F3SUJBZ0lRRU9IcEhIZkZwZ28wUndQSVJhdkdpakFOQmdrcWhraUc5dzBCQVFzRkFEQmYKTVJVd0V3WURWUVFLRXd4dFpTNXNiMk5oYkdodmMzUXhGVEFUQmdOVkJBTVRERzFsTG14dlkyRnNhRzl6ZERFdgpNQzBHQTFVRUJSTW1NakkwTkRBMk5ESTNPREkyTWpJNE5UQXdOemMzTmpVeU16STVOak15TWpNeU56VXhORFl3CkhoY05NakV4TWpJM01qTTBOREl6V2hjTk16RXhNakkxTWpNME5ESXpXakJmTVJVd0V3WURWUVFLRXd4dFpTNXMKYjJOaGJHaHZjM1F4RlRBVEJnTlZCQU1UREcxbExteHZZMkZzYUc5emRERXZNQzBHQTFVRUJSTW1NakkwTkRBMgpOREkzT0RJMk1qSTROVEF3TnpjM05qVXlNekk1TmpNeU1qTXlOelV4TkRZd2dnRWlNQTBHQ1NxR1NJYjNEUUVCCkFRVUFBNElCRHdBd2dnRUtBb0lCQVFETFRrVFkzQ0NMVStNUllkbEMwM2NUTTR6MUpiRGoxYjFQRWdING9iSmwKRjl4NWtQbzhncWNEbmp5L0x5NHdKeUR2Q2xPMkw1T0k3UnYwa1hFUXoybUVEeExnbjJYRG9ZNUh5VFNOVkZHNgpvZ3BlYmhlUFN1aWl0RUNZYUZDZVZFTGNDa1Q0ZGpqRDlwOExNTnJ4MHRPOXdQU1o1OXBLZUxCOG90RFloOHRCCkcyb2EzSGIzTWt0RGxOY0svVE94RFNzRzUrQ2ljdktTa3QrV04xaXJJQ2pvZ2hWTzJGcForRkdxWUM0Y1EwbWMKM0NRaGJwY1o2VTRkWnpGdFJZVzZPYzNucHBOSkZKWXZSSTRIS1FWY0RCM2N4VkhNTUd5Rzc3aFRzdEwvd0RuaQo4U2s5eml4VzN4S2FvUnlrV2FuWno4eC9WdHNydXJqanNzNDV4NlRoem1VWkFnTUJBQUdqUWpCQU1BNEdBMVVkCkR3RUIvd1FFQXdJQnBqQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01CMEdBMVVkRGdRV0JCUmNKK2NRamFQWjZGbEIKcVhoYzYyWXZldGRpQWpBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQVdYNmxZdUhtMmQyMU41RDN1eUJJelFOdApKVFR3b0xnU3FQd09Tbk1EU0luSDkvMjBqZDNGUk9qakh1M3BQRkNLRmE4OVp0ekZxTHZsTjVOdlh2WHFuOXNKCkdudTYzSVo0TWtEZk9sSVZpWFhQWFF4YllHSkMxRVlVU28rTDdtUTY0VnN5UkFpTXdnbmVwMUxwSGhROGYzU2MKeEZoVkNybFJDMmUrNENBai8vOVZWaDdvTEdSMkNhM0xEcFc5VHFxYnB3MEh0QitNcFVqVWxCWnFVbzNVMm5HTQpia1VhSVZKcnNuYk1rYnNsUGQ2dWtVRDlVTHFuUmxJb3A4cjQ1VTdvYVBhR3g3QVFiWndzbGlsNVVJZlppRmlRCm5USk9kYnJHampVdXlRYkM4UUpZY3RhdENjbVBjZUlXMVVWWFVnZ2JsdXl4VjF1NWsyYzlSb1k2RzhiN0FRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= - key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBeTA1RTJOd2dpMVBqRVdIWlF0TjNFek9NOVNXdzQ5VzlUeElCK0tHeVpSZmNlWkQ2ClBJS25BNTQ4dnk4dU1DY2c3d3BUdGkrVGlPMGI5SkZ4RU05cGhBOFM0SjlsdzZHT1I4azBqVlJSdXFJS1htNFgKajByb29yUkFtR2hRbmxSQzNBcEUrSFk0dy9hZkN6RGE4ZExUdmNEMG1lZmFTbml3ZktMUTJJZkxRUnRxR3R4Mgo5ekpMUTVUWEN2MHpzUTByQnVmZ29uTHlrcExmbGpkWXF5QW82SUlWVHRoYVdmaFJxbUF1SEVOSm5Od2tJVzZYCkdlbE9IV2N4YlVXRnVqbk41NmFUU1JTV0wwU09CeWtGWEF3ZDNNVlJ6REJzaHUrNFU3TFMvOEE1NHZFcFBjNHMKVnQ4U21xRWNwRm1wMmMvTWYxYmJLN3E0NDdMT09jZWs0YzVsR1FJREFRQUJBb0lCQVFDeXNLNXVkTHZkK2ZOQQpHZUtkaThQRENySS8zY3JsMWIwNFBEbWpVR3U5MHdVampEdUU1OGpuc3pMdFR3aW5waHlhUFZkcWI5S2FyTnkvClR2NHpxam14cXBZSys4Nno3ZEZpWXdSZm05YmgxUDZNRlBOOExIamdXTkhWb3dvSXYwS3NxQklLMTgzNDMxRFcKd3pBTkVDS3ZTMk14eXNqZ1g4ZXZKR092alZzbWN1SU1IWFljbVlORlo0dWpuTnc0dnVSWHhlYUtPY2ZWTGZ2ZwoxSWZMK05IYUh6UXI1YVoydkxST1NpdHVId2cvOFpZZ2hQcVFYLy92ak92M0FENzhXVGxINUZXWjExR0hoeCt1Ck9ZUXcwQ1lvTnNiV1UxeklwVTV0cHdCcDRGV0VlVy9jbTY5NXRBVVRlMzR1N3R1MlJJOVBZMEZDWVJ2ZkM2SUQKK2tDNjRFTEpBb0dCQVBSTEt5a0pjdHNGNngzN1liWXdmQm9ZK3V6YU4wV1o1d1RuRmVFaVp1ZzVVYWJUOTdqRApZTnpaYzQ0aitRbkxFUGVDak5tU1FhVHFxK2QvbUVjTnlXS244cVNFcXlOR0R6YlRXQ2tkMGtyWTVwcm9FVVJnClFqbWFwRHFNNEtOSm9jQ1RCS3lueHo2QzZ6ME9uSnhZM3lMdTdyYVBqeE9HTW5rcm9VMDhMVW56QW9HQkFOVU0KU2NsNGh1R0gxK3ZNL0RtenJoQ1NKbUZIcjE2QjhvWExaMHIyNmFKTnJVQ3AwYUFzV2FHd3JLZXZFcDkzRmg5Ygp3QlZYMHE0bXJ3cmZpTGpCYnRzdnlQM3l1ZWs5cWl6M2ZoMWIvZ0k2eWRKVFEzMEplQzB4UzUyRksvR2gwanEvCm43c2Y1bm5DbTlCRW01ZmdSeDFVUmVhOC9vL2M4cTNhbW94c0grdkRBb0dBZUVZUjU5QlpGZkJpQTQ3aVdwcWcKWHhEeGFXOCtTeXdzaTBOaWlFY3h0eCtSVGJ1S2VSTG9PNU5yeXcxMjdSVm5NeFM1Vjkwa0tKZkpMdDZwRUVKLwpaZTBlRDFXcUZHSEgxOHhSMlZ4dlRwNWZXdURxcjJsYzhaTnJTOUJVUU5CZHJMdzFUdlFEcW9rMlhBYzNuOW81CmNhK0ZJNmltWG94eGlTcXI3YVMwLzNVQ2dZQTJBWEJ1N3V1YUhocGcvc3h0UUJ2K3ZWMlhTVm11SmxpNUM4KzYKVkE3emdxZEpmZ0xTakl1SURrWW1GNTRyNkQ4bVlkYTJVbFhvcVl1endPaGlsVDRwdDlwR2JaSXRDdUdwbG05VQp0KzRTMko0eWY4TGEzbHlsY0JxUDZxTXlGR2c3VmpvQ2NGcTNRTnJJbDZ1dGV6L3JzbUlwMUh6Zk1RNGZmZ3V4ClR2Tmtpd0tCZ1FDbVhaSStvTUdQK3U4KzRVaGxjR01NYUNHZi92UVZLdVJYOHlOYVh1bUx6dk1Xajl0cVhpUzcKK1dQUlhuV01RSnd1QldYMzBTcWdFVVdDbjlzOGxWbzh2TVF4MmFtbXhhWkVEMGRoOHNMSkJDNXJoRmVqV29MbQp3cHg5MXR0S3JJODBKMDYyeW90SFpJYkRyQW1LSGZFeE85U1d4T1hUeFVMUGdvTlJVUW5qSnc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= + tls: + - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURqVENDQW5XZ0F3SUJBZ0lSQUtDdHRGNGZ1V0ZTSzJaNm1mb3VPcmt3RFFZSktvWklodmNOQVFFTEJRQXcKWURFVk1CTUdBMVVFQ2hNTWJXVXViRzlqWVd4b2IzTjBNUlV3RXdZRFZRUURFd3h0WlM1c2IyTmhiR2h2YzNReApNREF1QmdOVkJBVVRKekl4TXpVM09EUXdORGszTXpFd056RTBORFkxTWpJMk9EVXlOelV5T0Rjek1qRTBOak0yCk1UQWVGdzB5TkRFeU16RXhOREkyTURaYUZ3MHpOREV5TWpreE5ESTJNRFphTUdBeEZUQVRCZ05WQkFvVERHMWwKTG14dlkyRnNhRzl6ZERFVk1CTUdBMVVFQXhNTWJXVXViRzlqWVd4b2IzTjBNVEF3TGdZRFZRUUZFeWN5TVRNMQpOemcwTURRNU56TXhNRGN4TkRRMk5USXlOamcxTWpjMU1qZzNNekl4TkRZek5qRXdnZ0VpTUEwR0NTcUdTSWIzCkRRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRREVBQTFtbG1jeU5uK3BiYzVtRkxVRWNFN3JSWWtxNlEydnNKdnYKd0F3dDRpbjhGSmoxdVJVenU1YUJqYUs5bERDN1cyd2JwSjI4SGZuZmtkb2ttOHN4T0xqTTh5YUlaSzBYNG9ZcwpTTDFhdFVPMUsrUkt5citJNGc0dzJBd05QUURsUE5xU0t3dlNzRXhsQzhSSXpUTllZYXVpQVUxaWdoVW5zZTUxCm5jMVRTbzJ0NStuemNoVEt2SU1qV0U5TUpmZ2lBRDRjVXlHVU5DQXV1dTRndWJEZVBFdStYZmVhUjNCcXJoNUwKaTRiWGdQanF3NnBLMTZzY1h5QlYwL1RzdGsxYXhpekh3M2h4SG5INDNFeUtsUVNneXBPNnNLTFFmR0VEVTZ6UgpjdlFIOHNOc0g0YmVNL2Zzb0RoeXFZQVZKbDVNT011UG0zTHBpdHJ1YktEOXA4S1ZBZ01CQUFHalFqQkFNQTRHCkExVWREd0VCL3dRRUF3SUJwakFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQjBHQTFVZERnUVdCQlJqNytSM3pabEoKRlpPU05pM2xsOHhUclNQNVREQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFwZmJHWXA5Q1hoNksvL1FKY3kyUQpqVjF0RDk1akZYcVVyZ0JrZ1g0cmplaEVqQjd0N3k1SnNXZlJIK25DL2UxaGRqL3dlZWMxenR1MFJNazUxRThlCmJCNWFaLzV1eVhMWXlMVGJWaVpZQzEzV0EyTlZpY200d08yWDQvK29NNzVQTzFwUmRvTm4weGRlcDFPa2o2VDcKQlA3WVM3SUhYRlMwUE00bUQxRDRsTHRIQmhqcjdJQnNlK2ZSRXBaZEJZMktqOTZ3NjZzT0c1RlNNMVY0NWRiNQoyY1lhTHFEcGR3aFVEY0xDZWVIVnYzaTB4UGk2TUJmWTFoVGd1MjRTMFpHZnhpR2F5Qk5FWEpKL1dtNk5kQkFUClhVckV2YkU3Z2tORWRkeXJPUW9sWDA5R3lYU0dNbDVXMTVtMHNVaythNk5RanlVQTVxZU1sTTNWN1Q1REVLcWgKV0E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBeEFBTlpwWm5NalovcVczT1poUzFCSEJPNjBXSkt1a05yN0NiNzhBTUxlSXAvQlNZCjlia1ZNN3VXZ1kyaXZaUXd1MXRzRzZTZHZCMzUzNUhhSkp2TE1UaTR6UE1taUdTdEYrS0dMRWk5V3JWRHRTdmsKU3NxL2lPSU9NTmdNRFQwQTVUemFraXNMMHJCTVpRdkVTTTB6V0dHcm9nRk5Zb0lWSjdIdWRaM05VMHFOcmVmcAo4M0lVeXJ5REkxaFBUQ1g0SWdBK0hGTWhsRFFnTHJydUlMbXczanhMdmwzM21rZHdhcTRlUzR1RzE0RDQ2c09xClN0ZXJIRjhnVmRQMDdMWk5Xc1lzeDhONGNSNXgrTnhNaXBVRW9NcVR1ckNpMEh4aEExT3MwWEwwQi9MRGJCK0cKM2pQMzdLQTRjcW1BRlNaZVREakxqNXR5NllyYTdteWcvYWZDbFFJREFRQUJBb0lCQUZtSU1KYnBJM0REaG1OdAozbmV4QTlOb1BoU282ZlNwQ3ZCemUzZjBRVndBVU85dXRVU2g3RFo2ZlZEbTB5MUljVTVVZjdqTTVLVFhDSnFBCjlLWCthTDR1Uy9TTEtkSHFNMHVTMVhtTExMd3Z5eU1LVHJsL2ppaklJblZiYTMzc25Pa2FlRG1HNGxxMjM5N1UKbGpBdlZFSU9NNm5JY0lJTUsvKzYvdFBKWnM2aGxXZXAzcGRuOWMwUXBUWGExbGpiZ1FVMHVNN2NwanRLRE4rcwpYRGRzb2N3VGpUV2MwNGY0aDBtS3NkdmlxeG80bVBSK0tyV3Brb1pFdFhiUGQ4THg5OVNkQjJ0aEs2enBiNEErCmZKOFlXYjhZTWp3SGpXRW5OYVpiM2FocGVuRkVyVWZFb1hlRGc4ajltak1VSGFSUURIK3ZGN0RINlI3U3lRb1QKOWo2dFBXRUNnWUVBemptNCtKamswbEd3SmVDMzAyN01Fd2lQMlNwcG0yRlNLY1MzVnM0cmE3TGhFR1pSZEpBcQpYSDNpdEVjZWViZTg4aU1RcDJXam1DMktEN1ZLOTBTN2pVUUpLbEw2eTM5WDh0RGlJRHQ4Y3l2OVd6TTJEK0p2Cm0vc3BFc1A4aURJTEVYZDJYUk1JNHhja2tXRml0TkE0SDNnODNtYWRPVWkzOEsyN0xyR3Q0b2tDZ1lFQTgwNkgKbWJOY1hKSTRML09FdTdHd2hVSk1uOGdLaWpvWStMdmZvQlN5Ym9SdzFIWkNvYnRZM2NLd0xNSnAvNkdQVG1lbwpucWlXNWs0YjJLVlpuczl0elNSRWhZa1NNTWRyZllhTis3bGZKTEozS3BhenNXMGlWbk5HaFlidzZVSnY0TmpnCjMzYnQvemh4bkp5ckhzS3B0VndiWm9lWmN2RDFkcVB5cm5kOVRLMENnWUVBdDkvYnZ6eUQrY3NBSmlYQmdmR3UKWCtJb2NGZFNwa29WK2t2OXRKWkxQTkhYdnNtY0l6UlBzUHhGWUx4d3ZkSkgxQlhUeVkza1dkRnc0aVNoWE91WgoxcEV0SXVHdDREZ0E4TzJ5VVU3NDNiQUJUSW5TMEVMemhMNWlsdXJNaFpzcEp6Kys5Nm43S0kvLytPZytIRDN6CmJJdkdxZjRRZlgwTEZMdXl4Q1dFaHhFQ2dZQVk2eG9JSzg1eHpLZmtnVlEreE53SFNkci9Ja1d5RW5Fc1NGR0cKMjVmS3FkWEViTGcyU0RHNXhJNjJodExFVTQrUndCd000OGRRbnY5TEdPUXMxNkd2T04rcnJYWW5lTVVSZmc1YwprWWVsQW9JaDRuMVUxcENGdWhpbTVFTVlJSzNFb1hHbWNVKytxOUUyOFBTMW1jbzN3TTh0bVFXbU4vZHJ4eTY3Cm41RTlvUUtCZ0R4VmptWGpCd1hkY3RhQm8ra3JHT1JWV2hJUzVWUFQrVkgwQ1F2MTUxcCtvT2EyMldQNSttbSsKUjJrcEhWNXRVTmUyUE9VZU5oMVE5U3JKQ1NLWmQ2K0JsT0U4bjQvc0kyNDJvbmRnelhrL21LRnc4NjBMQmdUbApkWTRvTC9mUU1mQ242aU4vMXBGZmFhWDZSV1lVMUplSTduK3VFZ0hDTFE4Z2x3eU1JcFdmCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== additional_trusted_keys: {} cluster_name: me.localhost - signing_alg: 3 type: saml_idp sub_kind: saml_idp version: v2` diff --git a/lib/auth/middleware.go b/lib/auth/middleware.go index 8e21c071eb4a5..6acfa4ac4ead9 100644 --- a/lib/auth/middleware.go +++ b/lib/auth/middleware.go @@ -681,7 +681,7 @@ func (a *Middleware) GetUser(connState tls.ConnectionState) (authz.IdentityGette // as the local certificate authority if certClusterName != a.ClusterName { // make sure that this user does not have system role - // the local auth server can not truste remote servers + // the local auth server can not trust remote servers // to issue certificates with system roles (e.g. Admin), // to get unrestricted access to the local cluster systemRole := findPrimarySystemRole(identity.Groups) @@ -832,57 +832,6 @@ func (a *Middleware) WrapContextWithUserFromTLSConnState(ctx context.Context, tl return ctx, nil } -// ClientCertPool returns trusted x509 certificate authority pool with CAs provided as caTypes. -// In addition, it returns the total length of all subjects added to the cert pool, allowing -// the caller to validate that the pool doesn't exceed the maximum 2-byte length prefix before -// using it. -func ClientCertPool(client authclient.AccessCache, clusterName string, caTypes ...types.CertAuthType) (*x509.CertPool, int64, error) { - if len(caTypes) == 0 { - return nil, 0, trace.BadParameter("at least one CA type is required") - } - - ctx := context.TODO() - pool := x509.NewCertPool() - var authorities []types.CertAuthority - if clusterName == "" { - for _, caType := range caTypes { - cas, err := client.GetCertAuthorities(ctx, caType, false) - if err != nil { - return nil, 0, trace.Wrap(err) - } - authorities = append(authorities, cas...) - } - } else { - for _, caType := range caTypes { - ca, err := client.GetCertAuthority( - ctx, - types.CertAuthID{Type: caType, DomainName: clusterName}, - false) - if err != nil { - return nil, 0, trace.Wrap(err) - } - - authorities = append(authorities, ca) - } - } - - var totalSubjectsLen int64 - for _, auth := range authorities { - for _, keyPair := range auth.GetTrustedTLSKeyPairs() { - cert, err := tlsca.ParseCertificatePEM(keyPair.Cert) - if err != nil { - return nil, 0, trace.Wrap(err) - } - pool.AddCert(cert) - - // Each subject in the list gets a separate 2-byte length prefix. - totalSubjectsLen += 2 - totalSubjectsLen += int64(len(cert.RawSubject)) - } - } - return pool, totalSubjectsLen, nil -} - // isProxyRole returns true if the certificate role is a proxy role. func isProxyRole(identity authz.IdentityGetter) bool { switch id := identity.(type) { diff --git a/lib/auth/tls_test.go b/lib/auth/tls_test.go index 59c6ca4a0d9c4..c0db8566be150 100644 --- a/lib/auth/tls_test.go +++ b/lib/auth/tls_test.go @@ -22,7 +22,9 @@ import ( "context" "crypto" "crypto/tls" + "crypto/x509" "encoding/base32" + "encoding/pem" "errors" "fmt" "net" @@ -5059,3 +5061,227 @@ func newTestTLSServer(t testing.TB, opts ...testTLSServerOption) *TestTLSServer return srv } + +func TestVerifyPeerCert(t *testing.T) { + t.Parallel() + const ( + localClusterName = "local" + remoteClusterName = "remote" + ) + s := newTestServices(t) + // Set up local cluster name in the backend. + cn, err := services.NewClusterNameWithRandomID(types.ClusterNameSpecV2{ + ClusterName: localClusterName, + }) + require.NoError(t, err) + require.NoError(t, s.UpsertClusterName(cn)) + + now := time.Date(2020, time.November, 5, 0, 0, 0, 0, time.UTC) + + var ( + localUserIdentity = tlsca.Identity{ + Username: "foo", + Groups: []string{"devs"}, + TeleportCluster: localClusterName, + Expires: now, + } + localSystemRole = tlsca.Identity{ + Username: "node", + Groups: []string{string(types.RoleNode)}, + TeleportCluster: localClusterName, + Expires: now, + } + remoteUserIdentity = tlsca.Identity{ + Username: "foo", + Groups: []string{"devs"}, + TeleportCluster: remoteClusterName, + Expires: now, + } + remoteSystemRole = tlsca.Identity{ + Username: "node", + Groups: []string{string(types.RoleNode)}, + TeleportCluster: remoteClusterName, + Expires: now, + } + ) + + localHostCA := suite.NewTestCA(types.HostCA, localClusterName) + remoteHostCA := suite.NewTestCA(types.HostCA, remoteClusterName) + localUserCA := suite.NewTestCA(types.UserCA, localClusterName) + remoteUserCA := suite.NewTestCA(types.UserCA, remoteClusterName) + + caPool := buildPoolInfo( + t, + localHostCA, + localUserCA, + remoteHostCA, + remoteUserCA, + ) + + tests := []struct { + desc string + peer *x509.Certificate + clusterName string + wantErr bool + }{ + { + desc: "local user issued from remote CA", + peer: generateTestCert(t, remoteUserCA, localUserIdentity, localClusterName), + clusterName: localClusterName, + wantErr: true, + }, + { + desc: "local system user issued from remote CA", + peer: generateTestCert(t, remoteHostCA, localSystemRole, localClusterName), + clusterName: localClusterName, + wantErr: true, + }, + { + desc: "local user issued from local host CA", + peer: generateTestCert(t, localHostCA, localUserIdentity, localClusterName), + clusterName: localClusterName, + wantErr: true, + }, + { + desc: "local system user issued from local user CA", + peer: generateTestCert(t, localUserCA, localSystemRole, localClusterName), + clusterName: localClusterName, + wantErr: true, + }, + { + desc: "local user with remote cluster name issued from local user CA", + peer: generateTestCert(t, localUserCA, localUserIdentity, remoteClusterName), + clusterName: localClusterName, + wantErr: true, + }, + { + desc: "local system user with remote cluster name issued from local host CA", + peer: generateTestCert(t, localHostCA, localSystemRole, remoteClusterName), + clusterName: localClusterName, + wantErr: true, + }, + { + desc: "local user issued from local user CA", + peer: generateTestCert(t, localUserCA, localUserIdentity, localClusterName), + clusterName: localClusterName, + wantErr: false, + }, + { + desc: "local system user issued from local host CA", + peer: generateTestCert(t, localHostCA, localSystemRole, localClusterName), + clusterName: localClusterName, + wantErr: false, + }, + { + desc: "remote user issued from local CA", + peer: generateTestCert(t, localUserCA, remoteUserIdentity, remoteClusterName), + clusterName: remoteClusterName, + wantErr: true, + }, + { + desc: "remote system user issued from local CA", + peer: generateTestCert(t, localHostCA, remoteSystemRole, remoteClusterName), + clusterName: remoteClusterName, + wantErr: true, + }, + { + desc: "remote user issued from remote host CA", + peer: generateTestCert(t, remoteHostCA, remoteUserIdentity, remoteClusterName), + clusterName: remoteClusterName, + wantErr: true, + }, + { + desc: "remote system user issued from user CA", + peer: generateTestCert(t, remoteUserCA, remoteSystemRole, remoteClusterName), + clusterName: remoteClusterName, + wantErr: true, + }, + { + desc: "remote user with local cluster name issued from remote user CA", + peer: generateTestCert(t, remoteUserCA, remoteUserIdentity, localClusterName), + clusterName: remoteClusterName, + wantErr: true, + }, + { + desc: "remote system user with local cluster name issued from host CA", + peer: generateTestCert(t, remoteHostCA, remoteSystemRole, localClusterName), + clusterName: remoteClusterName, + wantErr: true, + }, + { + desc: "remote user issued from remote user CA", + peer: generateTestCert(t, remoteUserCA, remoteUserIdentity, remoteClusterName), + clusterName: remoteClusterName, + wantErr: false, + }, + { + desc: "remote system user issued from host CA", + peer: generateTestCert(t, remoteHostCA, remoteSystemRole, remoteClusterName), + clusterName: remoteClusterName, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + verify := caPool.verifyPeerCert() + err := verify(nil, [][]*x509.Certificate{{tt.peer}}) + if tt.wantErr { + require.ErrorContains(t, err, "access denied: invalid client certificate") + } else { + require.NoError(t, err) + } + }) + } +} + +func buildPoolInfo(t *testing.T, ca ...types.CertAuthority) *HostAndUserCAPoolInfo { + poolInfo := HostAndUserCAPoolInfo{ + Pool: x509.NewCertPool(), + CATypes: make(authclient.HostAndUserCAInfo), + } + + for _, authority := range ca { + for _, c := range authority.GetTrustedTLSKeyPairs() { + cert, err := tlsca.ParseCertificatePEM(c.Cert) + require.NoError(t, err) + poolInfo.Pool.AddCert(cert) + + poolInfo.CATypes[string(cert.RawSubject)] = authclient.CATypeInfo{ + IsHostCA: authority.GetType() == types.HostCA, + IsUserCA: authority.GetType() == types.UserCA, + } + } + } + + return &poolInfo +} + +func generateTestCert(t *testing.T, ca types.CertAuthority, id tlsca.Identity, clusterName string) *x509.Certificate { + tlsKeyPairs := ca.GetTrustedTLSKeyPairs() + require.Len(t, tlsKeyPairs, 1) + signer, err := tlsca.FromKeys(tlsKeyPairs[0].Cert, tlsKeyPairs[0].Key) + require.NoError(t, err) + + priv, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256) + require.NoError(t, err) + + id.TeleportCluster = clusterName + subj, err := id.Subject() + require.NoError(t, err) + + pemCert, err := signer.GenerateCertificate(tlsca.CertificateRequest{ + PublicKey: priv.Public(), + Subject: subj, + NotAfter: time.Now().Add(time.Hour), + }) + require.NoError(t, err) + block, rest := pem.Decode(pemCert) + require.NotNil(t, block) + require.Empty(t, rest) + + cert, err := x509.ParseCertificate(block.Bytes) + require.NoError(t, err) + + return cert +} diff --git a/lib/auth/trust/trustv1/service.go b/lib/auth/trust/trustv1/service.go index c0dfd564ea4a7..bea71f4dae6aa 100644 --- a/lib/auth/trust/trustv1/service.go +++ b/lib/auth/trust/trustv1/service.go @@ -31,6 +31,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/authz" "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/tlsca" ) type authServer interface { @@ -311,6 +312,27 @@ func (s *Service) RotateExternalCertAuthority(ctx context.Context, req *trustpb. return nil, trace.AccessDenied("this request can be only executed by an internal Teleport service") } + // ensure that the caller is rotating a CA from the same cluster + caClusterName := req.CertAuthority.GetClusterName() + if caClusterName != authCtx.Identity.GetIdentity().TeleportCluster { + return nil, trace.BadParameter("can not rotate local certificate authority") + } + // ensure the subjects and issuers of the CA certs match what the + // cluster name of this CA is supposed to be + for _, keyPair := range req.CertAuthority.GetTrustedTLSKeyPairs() { + cert, err := tlsca.ParseCertificatePEM(keyPair.Cert) + if err != nil { + return nil, trace.Wrap(err) + } + certClusterName, err := tlsca.ClusterName(cert.Subject) + if err != nil { + return nil, trace.AccessDenied("CA certificate subject organization is invalid") + } + if certClusterName != caClusterName { + return nil, trace.AccessDenied("the subject organization of a CA certificate does not match the cluster name of the CA") + } + } + clusterName, err := s.authServer.GetClusterName() if err != nil { return nil, trace.Wrap(err) diff --git a/lib/auth/trust/trustv1/service_test.go b/lib/auth/trust/trustv1/service_test.go index 23ae66a5149b5..42d4dc49ab8c7 100644 --- a/lib/auth/trust/trustv1/service_test.go +++ b/lib/auth/trust/trustv1/service_test.go @@ -138,7 +138,7 @@ func newCertAuthority(t *testing.T, caType types.CertAuthType, domain string) ty priv, pub, err := ta.GenerateKeyPair() require.NoError(t, err) - key, cert, err := tlsca.GenerateSelfSignedCA(pkix.Name{CommonName: domain}, nil, time.Hour) + key, cert, err := tlsca.GenerateSelfSignedCA(pkix.Name{CommonName: domain, Organization: []string{domain}}, nil, time.Hour) require.NoError(t, err) ca, err := types.NewCertAuthority(types.CertAuthoritySpecV2{ @@ -859,6 +859,19 @@ func TestRotateExternalCertAuthority(t *testing.T) { }, }, } + remoteUserCtx := &authz.Context{ + UnmappedIdentity: authz.BuiltinRole{}, + Checker: &fakeChecker{ + allow: map[check]bool{ + {types.KindCertAuthority, types.VerbRotate}: true, + }, + }, + Identity: authz.RemoteUser{ + Identity: tlsca.Identity{ + TeleportCluster: "external", + }, + }, + } tests := []struct { name string @@ -878,7 +891,7 @@ func TestRotateExternalCertAuthority(t *testing.T) { }, ca: externalCA, assertError: func(tt require.TestingT, err error, i ...interface{}) { - require.True(t, trace.IsAccessDenied(err), "expected access denied error but got %v", err) + require.True(tt, trace.IsAccessDenied(err), "expected access denied error but got %v", err) }, }, { name: "NOK unauthorized service", @@ -892,44 +905,44 @@ func TestRotateExternalCertAuthority(t *testing.T) { }, ca: externalCA, assertError: func(tt require.TestingT, err error, i ...interface{}) { - require.True(t, trace.IsAccessDenied(err), "expected access denied error but got %v", err) + require.True(tt, trace.IsAccessDenied(err), "expected access denied error but got %v", err) }, }, { name: "NOK no ca", authzCtx: authorizedCtx, ca: nil, assertError: func(tt require.TestingT, err error, i ...interface{}) { - require.True(t, trace.IsBadParameter(err)) + require.True(tt, trace.IsBadParameter(err)) }, }, { name: "NOK invalid ca", authzCtx: authorizedCtx, ca: &types.CertAuthorityV2{}, assertError: func(tt require.TestingT, err error, i ...interface{}) { - require.True(t, trace.IsBadParameter(err)) + require.True(tt, trace.IsBadParameter(err)) }, }, { name: "NOK rotate local ca", - authzCtx: authorizedCtx, + authzCtx: remoteUserCtx, ca: localCA, assertError: func(tt require.TestingT, err error, i ...interface{}) { - require.True(t, trace.IsBadParameter(err)) + require.True(tt, trace.IsBadParameter(err)) }, }, { name: "NOK nonexistent ca", - authzCtx: authorizedCtx, + authzCtx: remoteUserCtx, ca: newCertAuthority(t, types.HostCA, "na").(*types.CertAuthorityV2), assertError: func(tt require.TestingT, err error, i ...interface{}) { - require.True(t, trace.IsNotFound(err)) + require.True(tt, trace.IsBadParameter(err)) }, }, { name: "OK rotate external ca", - authzCtx: authorizedCtx, + authzCtx: remoteUserCtx, ca: newCertAuthority(t, types.HostCA, "external").(*types.CertAuthorityV2), assertError: require.NoError, }, { name: "OK equivalent external ca", - authzCtx: authorizedCtx, + authzCtx: remoteUserCtx, ca: externalCA, assertError: require.NoError, }, diff --git a/lib/auth/trustedcluster.go b/lib/auth/trustedcluster.go index 8a74011692547..8336bf9979a17 100644 --- a/lib/auth/trustedcluster.go +++ b/lib/auth/trustedcluster.go @@ -567,10 +567,29 @@ func (a *Server) validateTrustedCluster(ctx context.Context, validateRequest *au remoteCA.SetRoles(nil) remoteClusterName := remoteCA.GetName() + if remoteClusterName != remoteCA.GetClusterName() { + return nil, trace.AccessDenied("CA name does not match its cluster name") + } if remoteClusterName == domainName { return nil, trace.AccessDenied("remote cluster has same name as this cluster: %v", domainName) } + // ensure the subjects of the CA certs match what the + // cluster name of this CA is supposed to be + for _, keyPair := range remoteCA.GetTrustedTLSKeyPairs() { + cert, err := tlsca.ParseCertificatePEM(keyPair.Cert) + if err != nil { + return nil, trace.Wrap(err) + } + certClusterName, err := tlsca.ClusterName(cert.Subject) + if err != nil { + return nil, trace.AccessDenied("CA certificate subject organization is invalid") + } + if certClusterName != remoteClusterName { + return nil, trace.AccessDenied("the subject organization of a CA certificate does not match the cluster name of the CA") + } + } + remoteCluster, err := types.NewRemoteCluster(remoteClusterName) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/auth/trustedcluster_test.go b/lib/auth/trustedcluster_test.go index aca02a7dc4943..a1ff2e1d78df3 100644 --- a/lib/auth/trustedcluster_test.go +++ b/lib/auth/trustedcluster_test.go @@ -392,6 +392,21 @@ func TestValidateTrustedCluster(t *testing.T) { _, err := server.ValidateTrustedCluster(ctx, req) require.True(t, trace.IsNotImplemented(err), "ValidateTrustedCluster returned an unexpected error, got = %v (%T), want trace.NotImplementedError", err, err) }) + + t.Run("CA cluster name does not match subject organization", func(t *testing.T) { + _, err = a.validateTrustedCluster(ctx, &authclient.ValidateTrustedClusterRequest{ + Token: validToken, + CAs: []types.CertAuthority{ + suite.NewTestCAWithConfig(suite.TestCAConfig{ + Type: types.HostCA, + ClusterName: "remoteCluster", + SubjectOrganization: "commonName", + }), + }, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "the subject organization of a CA certificate does not match the cluster name of the CA") + }) } func newTestAuthServer(ctx context.Context, t *testing.T, name ...string) *Server { diff --git a/lib/service/desktop.go b/lib/service/desktop.go index 943bedad32f12..2b79491f2bbd1 100644 --- a/lib/service/desktop.go +++ b/lib/service/desktop.go @@ -185,7 +185,7 @@ func (process *TeleportProcess) initWindowsDesktopServiceRegistered(logger *slog logger.DebugContext(process.ExitContext(), "Ignoring unsupported cluster name.", "cluster_name", info.ServerName) } } - pool, _, err := authclient.DefaultClientCertPool(info.Context(), accessPoint, clusterName) + pool, _, _, err := authclient.DefaultClientCertPool(info.Context(), accessPoint, clusterName) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/services/suite/suite.go b/lib/services/suite/suite.go index ad2cb4695b3f2..c22f50762497f 100644 --- a/lib/services/suite/suite.go +++ b/lib/services/suite/suite.go @@ -70,9 +70,12 @@ func NewTestCA(caType types.CertAuthType, clusterName string, privateKeys ...[]b // a test certificate authority type TestCAConfig struct { Type types.CertAuthType - ClusterName string PrivateKeys [][]byte Clock clockwork.Clock + ClusterName string + // the below string fields default to ClusterName if left empty + ResourceName string + SubjectOrganization string } // NewTestCAWithConfig generates a new certificate authority with the specified @@ -83,6 +86,13 @@ func NewTestCAWithConfig(config TestCAConfig) *types.CertAuthorityV2 { var keyPEM []byte var key *keys.PrivateKey + if config.ResourceName == "" { + config.ResourceName = config.ClusterName + } + if config.SubjectOrganization == "" { + config.SubjectOrganization = config.ClusterName + } + switch config.Type { case types.DatabaseCA, types.SAMLIDPCA, types.OIDCIdPCA: // These CAs only support RSA. @@ -127,7 +137,7 @@ func NewTestCAWithConfig(config TestCAConfig) *types.CertAuthorityV2 { SubKind: string(config.Type), Version: types.V2, Metadata: types.Metadata{ - Name: config.ClusterName, + Name: config.ResourceName, Namespace: apidefaults.Namespace, }, Spec: types.CertAuthoritySpecV2{ @@ -152,7 +162,7 @@ func NewTestCAWithConfig(config TestCAConfig) *types.CertAuthorityV2 { Signer: key.Signer, Entity: pkix.Name{ CommonName: config.ClusterName, - Organization: []string{config.ClusterName}, + Organization: []string{config.SubjectOrganization}, }, TTL: defaults.CATTL, Clock: config.Clock, diff --git a/lib/srv/app/connections_handler.go b/lib/srv/app/connections_handler.go index d310c26c53acd..22360ff4c13a2 100644 --- a/lib/srv/app/connections_handler.go +++ b/lib/srv/app/connections_handler.go @@ -788,7 +788,7 @@ func newGetConfigForClientFn(log logrus.FieldLogger, client authclient.AccessCac // Fetch list of CAs that could have signed this certificate. If clusterName // is empty, all CAs that this cluster knows about are returned. - pool, _, err := authclient.DefaultClientCertPool(info.Context(), client, clusterName) + pool, _, _, err := authclient.DefaultClientCertPool(info.Context(), client, clusterName) if err != nil { // If this request fails, return nil and fallback to the default ClientCAs. diff --git a/lib/srv/db/access_test.go b/lib/srv/db/access_test.go index 5b851ea4c38aa..f9e6af2f63dde 100644 --- a/lib/srv/db/access_test.go +++ b/lib/srv/db/access_test.go @@ -2236,7 +2236,7 @@ func (c *testContext) makeTLSConfig(t testing.TB) *tls.Config { conf := utils.TLSConfig(nil) conf.Certificates = append(conf.Certificates, cert) conf.ClientAuth = tls.VerifyClientCertIfGiven - conf.ClientCAs, _, err = authclient.DefaultClientCertPool(context.Background(), c.authServer, c.clusterName) + conf.ClientCAs, _, _, err = authclient.DefaultClientCertPool(context.Background(), c.authServer, c.clusterName) require.NoError(t, err) return conf } diff --git a/lib/srv/db/proxyserver.go b/lib/srv/db/proxyserver.go index 3a2bacd8610e1..7c8ad3af62803 100644 --- a/lib/srv/db/proxyserver.go +++ b/lib/srv/db/proxyserver.go @@ -658,7 +658,7 @@ func (s *ProxyServer) getConfigForServer(ctx context.Context, identity tlsca.Ide }, nil } -func getConfigForClient(ctx context.Context, conf *tls.Config, ap authclient.ReadDatabaseAccessPoint, log *slog.Logger, caTypes ...types.CertAuthType) func(*tls.ClientHelloInfo) (*tls.Config, error) { +func getConfigForClient(ctx context.Context, conf *tls.Config, ap authclient.ReadDatabaseAccessPoint, log *slog.Logger, caType types.CertAuthType) func(*tls.ClientHelloInfo) (*tls.Config, error) { return func(info *tls.ClientHelloInfo) (*tls.Config, error) { var clusterName string var err error @@ -668,7 +668,7 @@ func getConfigForClient(ctx context.Context, conf *tls.Config, ap authclient.Rea log.DebugContext(ctx, "Ignoring unsupported cluster name.", "cluster_name", info.ServerName) } } - pool, _, err := authclient.ClientCertPool(info.Context(), ap, clusterName, caTypes...) + pool, _, err := authclient.ClientCertPool(info.Context(), ap, clusterName, caType) if err != nil { log.ErrorContext(ctx, "Failed to retrieve client CA pool.", "error", err) return nil, nil // Fall back to the default config. diff --git a/lib/web/apiserver_test.go b/lib/web/apiserver_test.go index 5c419ce46d709..175f0488d9907 100644 --- a/lib/web/apiserver_test.go +++ b/lib/web/apiserver_test.go @@ -8422,7 +8422,7 @@ func createProxy(ctx context.Context, t *testing.T, proxyID string, node *regula // Build the client CA pool containing the cluster's user CA in // order to be able to validate certificates provided by users. var err error - tlsClone.ClientCAs, _, err = authclient.DefaultClientCertPool(info.Context(), authServer.Auth(), clustername) + tlsClone.ClientCAs, _, _, err = authclient.DefaultClientCertPool(info.Context(), authServer.Auth(), clustername) if err != nil { return nil, trace.Wrap(err) }