mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
* Clean up unused symbols Update enterprise imports * Update enterprise imports
1809 lines
64 KiB
Go
1809 lines
64 KiB
Go
/*
|
|
* Teleport
|
|
* Copyright (C) 2023 Gravitational, Inc.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package auth
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/coreos/go-semver/semver"
|
|
"github.com/gravitational/trace"
|
|
"github.com/jonboulle/clockwork"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
|
oteltrace "go.opentelemetry.io/otel/trace"
|
|
"golang.org/x/crypto/ssh"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"github.com/gravitational/teleport"
|
|
"github.com/gravitational/teleport/api/client/proto"
|
|
autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1"
|
|
clusterconfigpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1"
|
|
machineidv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1"
|
|
joiningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1"
|
|
summarizerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/summarizer/v1"
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/api/types/clusterconfig"
|
|
apievents "github.com/gravitational/teleport/api/types/events"
|
|
"github.com/gravitational/teleport/api/types/vnet"
|
|
"github.com/gravitational/teleport/api/utils/clientutils"
|
|
"github.com/gravitational/teleport/api/utils/keys"
|
|
"github.com/gravitational/teleport/lib/auth/autoupdate/autoupdatev1"
|
|
igcredentials "github.com/gravitational/teleport/lib/auth/integration/credentials"
|
|
"github.com/gravitational/teleport/lib/auth/keystore"
|
|
"github.com/gravitational/teleport/lib/auth/machineid/machineidv1"
|
|
"github.com/gravitational/teleport/lib/auth/migration"
|
|
"github.com/gravitational/teleport/lib/auth/recordingencryption"
|
|
"github.com/gravitational/teleport/lib/auth/recordingencryption/recordingencryptionv1"
|
|
"github.com/gravitational/teleport/lib/auth/recordingmetadata"
|
|
"github.com/gravitational/teleport/lib/auth/state"
|
|
"github.com/gravitational/teleport/lib/auth/summarizer"
|
|
"github.com/gravitational/teleport/lib/backend"
|
|
"github.com/gravitational/teleport/lib/cryptosuites"
|
|
"github.com/gravitational/teleport/lib/events"
|
|
"github.com/gravitational/teleport/lib/join/iamjoin"
|
|
"github.com/gravitational/teleport/lib/modules"
|
|
"github.com/gravitational/teleport/lib/service/servicecfg"
|
|
"github.com/gravitational/teleport/lib/services"
|
|
"github.com/gravitational/teleport/lib/services/local"
|
|
"github.com/gravitational/teleport/lib/srv/db/common/databaseobjectimportrule"
|
|
"github.com/gravitational/teleport/lib/sshca"
|
|
"github.com/gravitational/teleport/lib/tlsca"
|
|
usagereporter "github.com/gravitational/teleport/lib/usagereporter/teleport"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
logutils "github.com/gravitational/teleport/lib/utils/log"
|
|
)
|
|
|
|
var logger = logutils.NewPackageLogger(teleport.ComponentKey, teleport.ComponentAuth)
|
|
|
|
// VersionStorage local storage for saving the version.
|
|
type VersionStorage interface {
|
|
// GetTeleportVersion reads the last known Teleport version from storage.
|
|
GetTeleportVersion(ctx context.Context) (semver.Version, error)
|
|
// WriteTeleportVersion writes the last known Teleport version to the storage.
|
|
WriteTeleportVersion(ctx context.Context, version semver.Version) error
|
|
// DeleteTeleportVersion removes the last known Teleport version in storage.
|
|
DeleteTeleportVersion(ctx context.Context) error
|
|
}
|
|
|
|
// RecordingEncryptionManager wraps a RecordingEncryption backend service with higher level
|
|
// operations.
|
|
type RecordingEncryptionManager interface {
|
|
services.RecordingEncryption
|
|
recordingencryption.KeyUnwrapper
|
|
recordingencryptionv1.KeyRotater
|
|
SetCache(cache recordingencryption.Cache)
|
|
}
|
|
|
|
// InitConfig is auth server init config
|
|
type InitConfig struct {
|
|
// InsecureMode defines whether insecure connections are allowed.
|
|
InsecureMode bool
|
|
|
|
// Backend is auth backend to use
|
|
Backend backend.Backend
|
|
|
|
// VersionStorage is a version storage for local process
|
|
VersionStorage VersionStorage
|
|
|
|
// Authority is key generator that we use
|
|
Authority sshca.Authority
|
|
|
|
// KeyStoreConfig is the config for the KeyStore which handles private CA
|
|
// keys that may be held in an HSM.
|
|
KeyStoreConfig servicecfg.KeystoreConfig
|
|
|
|
// KeyStore handles private CA keys and encryption keys that may be held
|
|
// in an HSM.
|
|
KeyStore *keystore.Manager
|
|
|
|
// HostUUID is a UUID of this host
|
|
HostUUID string
|
|
|
|
// NodeName is the DNS name of the node
|
|
NodeName string
|
|
|
|
// ClusterName stores the FQDN of the signing CA (its certificate will have this
|
|
// name embedded). It is usually set to the GUID of the host the Auth service runs on
|
|
ClusterName types.ClusterName
|
|
|
|
// Authorities is a list of pre-configured authorities to supply on first start
|
|
Authorities []types.CertAuthority
|
|
|
|
// ApplyOnStartupResources is a set of resources that should be applied
|
|
// on each Teleport start.
|
|
ApplyOnStartupResources []types.Resource
|
|
|
|
// BootstrapResources is a list of previously backed-up resources used to
|
|
// bootstrap backend on first start.
|
|
BootstrapResources []types.Resource
|
|
|
|
// AuthServiceName is a human-readable name of this CA. If several Auth services are running
|
|
// (managing multiple teleport clusters) this field is used to tell them apart in UIs
|
|
// It usually defaults to the hostname of the machine the Auth service runs on.
|
|
AuthServiceName string
|
|
|
|
// DataDir is the full path to the directory where keys, events and logs are kept
|
|
DataDir string
|
|
|
|
// ReverseTunnels is a list of reverse tunnels statically supplied
|
|
// in configuration, so auth server will init the tunnels on the first start
|
|
ReverseTunnels []types.ReverseTunnel
|
|
|
|
// OIDCConnectors is a list of trusted OpenID Connect identity providers
|
|
// in configuration, so auth server will init the tunnels on the first start
|
|
OIDCConnectors []types.OIDCConnector
|
|
|
|
// Trust is a service that manages users and credentials
|
|
Trust services.TrustInternal
|
|
|
|
// Presence service is a discovery and heartbeat tracker
|
|
Presence services.PresenceInternal
|
|
|
|
// Provisioner is a service that keeps track of provisioning tokens
|
|
Provisioner services.Provisioner
|
|
|
|
// Identity is a service that manages users and credentials
|
|
Identity services.Identity
|
|
|
|
// Access is service controlling access to resources
|
|
Access services.Access
|
|
|
|
// DynamicAccessExt is a service that manages dynamic RBAC.
|
|
DynamicAccessExt services.DynamicAccessExt
|
|
|
|
// Events is an event service
|
|
Events types.Events
|
|
|
|
// ClusterConfiguration is a services that holds cluster wide configuration.
|
|
ClusterConfiguration services.ClusterConfigurationInternal
|
|
|
|
// AutoUpdateService is a service of autoupdate configuration and version.
|
|
AutoUpdateService services.AutoUpdateService
|
|
|
|
// Restrictions is a service to access network restrictions, etc
|
|
Restrictions services.Restrictions
|
|
|
|
// Apps is a service that manages application resources.
|
|
Apps services.Applications
|
|
|
|
// Databases is a service that manages database resources.
|
|
Databases services.Databases
|
|
|
|
// DatabaseServices is a service that manages DatabaseService resources.
|
|
DatabaseServices services.DatabaseServices
|
|
|
|
// Status is a service that manages cluster status info.
|
|
Status services.Status
|
|
|
|
// UserPreferences is a service that manages user preferences.
|
|
UserPreferences services.UserPreferences
|
|
|
|
// Roles is a set of roles to create
|
|
Roles []types.Role
|
|
|
|
// StaticTokens are pre-defined host provisioning tokens supplied via config file for
|
|
// environments where paranoid security is not needed
|
|
StaticTokens types.StaticTokens
|
|
|
|
// StaticTokens are pre-defined, scoped host provisioning tokens supplied via config file for
|
|
// environments where paranoid security is not needed
|
|
StaticScopedTokens *joiningv1.StaticScopedTokens
|
|
|
|
// AuthPreference defines the authentication type (local, oidc) and second
|
|
// factor passed in from a configuration file.
|
|
AuthPreference types.AuthPreference
|
|
|
|
// AuditLog is used for emitting events to audit log.
|
|
AuditLog events.AuditLogSessionStreamer
|
|
|
|
// ClusterAuditConfig holds cluster audit configuration.
|
|
ClusterAuditConfig types.ClusterAuditConfig
|
|
|
|
// ClusterNetworkingConfig holds cluster networking configuration.
|
|
ClusterNetworkingConfig types.ClusterNetworkingConfig
|
|
|
|
// SessionRecordingConfig holds session recording configuration.
|
|
SessionRecordingConfig types.SessionRecordingConfig
|
|
|
|
// SkipPeriodicOperations turns off periodic operations
|
|
// used in tests that don't need periodic operations.
|
|
SkipPeriodicOperations bool
|
|
|
|
// CipherSuites is a list of ciphersuites that the auth server supports.
|
|
CipherSuites []uint16
|
|
|
|
// Emitter is events emitter, used to submit discrete events
|
|
Emitter apievents.Emitter
|
|
|
|
// Streamer is events sessionstreamer, used to create continuous
|
|
// session related streams
|
|
Streamer events.Streamer
|
|
|
|
// WindowsDesktops is a service that manages Windows desktop resources.
|
|
WindowsDesktops services.WindowsDesktops
|
|
|
|
// DynamicWindowsServices is a service that manages dynamic Windows desktop resources.
|
|
DynamicWindowsDesktops services.DynamicWindowsDesktops
|
|
|
|
// SAMLIdPServiceProviders is a service that manages SAML IdP service providers.
|
|
SAMLIdPServiceProviders services.SAMLIdPServiceProviders
|
|
|
|
// UserGroups is a service that manages user groups.
|
|
UserGroups services.UserGroups
|
|
|
|
// CrownJewels is a service that manages CrownJewels.
|
|
CrownJewels services.CrownJewels
|
|
|
|
// Integrations is a service that manages Integrations.
|
|
Integrations services.Integrations
|
|
|
|
// UserTasks is a service that manages UserTasks.
|
|
UserTasks services.UserTasks
|
|
|
|
// DiscoveryConfigs is a service that manages DiscoveryConfigs.
|
|
DiscoveryConfigs services.DiscoveryConfigs
|
|
|
|
// SessionTrackerService is a service that manages trackers for all active sessions.
|
|
SessionTrackerService services.SessionTrackerService
|
|
|
|
// ConnectionsDiagnostic is a service that manages Connection Diagnostics resources.
|
|
ConnectionsDiagnostic services.ConnectionsDiagnostic
|
|
|
|
// LoadAllCAs tells tsh to load the host CAs for all clusters when trying to ssh into a node.
|
|
LoadAllCAs bool
|
|
|
|
// TraceClient is used to forward spans to the upstream telemetry collector
|
|
TraceClient otlptrace.Client
|
|
|
|
// Kubernetes is a service that manages kubernetes cluster resources.
|
|
Kubernetes services.Kubernetes
|
|
|
|
// AssertionReplayService is a service that mitigates SSO assertion replay.
|
|
*local.AssertionReplayService
|
|
|
|
// FIPS means FedRAMP/FIPS compliant configuration was requested.
|
|
FIPS bool
|
|
|
|
// UsageReporter is a service that forwards cluster usage events.
|
|
UsageReporter usagereporter.UsageReporter
|
|
|
|
// Okta is a service that manages Okta resources.
|
|
Okta services.Okta
|
|
|
|
// AccessLists is a service that manages access list resources.
|
|
AccessLists services.AccessListsInternal
|
|
|
|
// DatabaseObjectImportRule is a service that manages database object import rules.
|
|
DatabaseObjectImportRules services.DatabaseObjectImportRules
|
|
|
|
// DatabaseObject is a service that manages database objects.
|
|
DatabaseObjects services.DatabaseObjects
|
|
|
|
// UserLoginStates is a service that manages user login states.
|
|
UserLoginState services.UserLoginStates
|
|
|
|
// SecReports is a service that manages security reports.
|
|
SecReports services.SecReports
|
|
|
|
// PluginData is a service that manages plugin data.
|
|
PluginData services.PluginData
|
|
|
|
// Clock is the clock instance auth uses. Typically you'd only want to set
|
|
// this during testing.
|
|
Clock clockwork.Clock
|
|
|
|
// HTTPClientForAWSSTS overwrites the default HTTP client used for making
|
|
// STS requests. Used in test.
|
|
HTTPClientForAWSSTS utils.HTTPDoClient
|
|
|
|
// AWSOrganizationsClientGetter provides an AWS client that can call Organizations APIs.
|
|
// This is used to allow the IAM join method to validate that an AWS account belongs to a specific AWS Organization.
|
|
AWSOrganizationsClientGetter iamjoin.OrganizationsAPIGetter
|
|
|
|
// Tracer used to create spans.
|
|
Tracer oteltrace.Tracer
|
|
|
|
// AccessMonitoringEnabled is true if access monitoring is enabled.
|
|
AccessMonitoringEnabled bool
|
|
|
|
// AccessMonitoringRules is a service that manages access monitoring rules.
|
|
AccessMonitoringRules services.AccessMonitoringRules
|
|
|
|
// KubeWaitingContainers is a service that manages
|
|
// Kubernetes ephemeral containers that are waiting
|
|
// to be created until moderated session conditions are met.
|
|
KubeWaitingContainers services.KubeWaitingContainer
|
|
|
|
// Notifications is a service that manages notifications.
|
|
Notifications services.Notifications
|
|
|
|
// BotInstance is a service that manages Machine ID bot instances
|
|
BotInstance services.BotInstance
|
|
|
|
// SPIFFEFederations is a service that manages storing SPIFFE federations.
|
|
SPIFFEFederations services.SPIFFEFederations
|
|
|
|
// WorkloadIdentity is the service for storing and retrieving
|
|
// WorkloadIdentity resources.
|
|
WorkloadIdentity services.WorkloadIdentities
|
|
|
|
// WorkloadIdentityX509Revocations is the service for storing and retrieving
|
|
// WorkloadIdentityX509Revocations.
|
|
WorkloadIdentityX509Revocations services.WorkloadIdentityX509Revocations
|
|
|
|
// WorkloadIdentityX509Overrides handles the storage for workload
|
|
// identity-related X.509 certificate overrides.
|
|
WorkloadIdentityX509Overrides services.WorkloadIdentityX509Overrides
|
|
|
|
// SigstorePolicies handles the storage for Sigstore policy objects.
|
|
SigstorePolicies services.SigstorePolicies
|
|
|
|
// StaticHostUsers is a service that manages host users that should be
|
|
// created on SSH nodes.
|
|
StaticHostUsers services.StaticHostUser
|
|
|
|
// ProvisioningStates is a service that manages the storage and retrieval of
|
|
// downstream User and Access List provisioning records
|
|
ProvisioningStates services.ProvisioningStates
|
|
|
|
// Logger is the logger instance for the auth service to use.
|
|
Logger *slog.Logger
|
|
|
|
// IdentityCenter is the Identity Center state storage service to use in
|
|
// this node.
|
|
IdentityCenter services.IdentityCenter
|
|
|
|
// Plugins is a service that manages plugin resources for integrations.
|
|
Plugins *local.PluginsService
|
|
|
|
// PluginStaticCredentials handles credentials for integrations and plugins.
|
|
PluginStaticCredentials services.PluginStaticCredentials
|
|
|
|
// GitServers manages git servers.
|
|
GitServers services.GitServers
|
|
|
|
// StableUNIXUsers handles the storage for stable UNIX users.
|
|
StableUNIXUsers services.StableUNIXUsersInternal
|
|
|
|
// HealthCheckConfig manages health check config resources.
|
|
HealthCheckConfig services.HealthCheckConfig
|
|
|
|
// BackendInfo is a service of backend information.
|
|
BackendInfo services.BackendInfoService
|
|
|
|
// RecordingEncryption manages state for encrypted session recording.
|
|
RecordingEncryption RecordingEncryptionManager
|
|
|
|
// SkipVersionCheck skips version check during major version upgrade/downgrade.
|
|
SkipVersionCheck bool
|
|
|
|
// VnetConfigService manages the VNet config resource.
|
|
VnetConfigService services.VnetConfigService
|
|
|
|
// MultipartHandler handles multipart uploads.
|
|
MultipartHandler events.MultipartHandler
|
|
|
|
// RunWhileLockedRetryInterval defines the interval at which the auth server retries
|
|
// a locking operation for backend objects.
|
|
// This setting is particularly useful in test environments,
|
|
// as it can help accelerate operations such as updating the access list,
|
|
// especially when the list is also being modified concurrently by the background
|
|
// eligibility handler.
|
|
RunWhileLockedRetryInterval time.Duration
|
|
|
|
// ScopedAccess is a service that manages scoped access resources.
|
|
ScopedAccess services.ScopedAccess
|
|
|
|
// Summarizer manages summary inference configuration resources.
|
|
Summarizer services.Summarizer
|
|
|
|
// SessionSummarizerProvider is a provider of the session summarizer service.
|
|
// It allows for late initialization of the summarizer in the enterprise
|
|
// plugin. The summarizer itself summarizes session recordings.
|
|
SessionSummarizerProvider *summarizer.SessionSummarizerProvider
|
|
|
|
// RecordingMetadataProvider provides recording metadata for session recordings.
|
|
RecordingMetadataProvider *recordingmetadata.Provider
|
|
|
|
// ScopedTokenService is a service that manages scoped join token resources.
|
|
ScopedTokenService services.ScopedTokenService
|
|
|
|
// AppAuthConfig is the service for storing and retrieving
|
|
// app auth config resources.
|
|
AppAuthConfig services.AppAuthConfig
|
|
|
|
// MFAService is the service that manages backend MFA resources.
|
|
MFAService MFAService
|
|
|
|
// WorkloadClusterService is the service that manages WorkloadClusters.
|
|
WorkloadClusterService services.WorkloadClusterService
|
|
|
|
// FakePasswordHash is the password hash given to all users without a password.
|
|
// This helps eliminate timing attacks by ensuring that all authentication attempts
|
|
// with a password do a bcrypt comparison.
|
|
FakePasswordHash []byte
|
|
// FakeRecoveryCodeHash is the recovery code hash given to all users without codes.
|
|
// This helps eliminate timing attacks by ensuring that all recovery attempts
|
|
// with codes do a bcrypt comparison.
|
|
FakeRecoveryCodeHash []byte
|
|
}
|
|
|
|
// Init instantiates and configures an instance of AuthServer
|
|
func Init(ctx context.Context, cfg InitConfig, opts ...ServerOption) (*Server, error) {
|
|
ctx, span := cfg.Tracer.Start(ctx, "auth/Init")
|
|
defer span.End()
|
|
|
|
if cfg.DataDir == "" {
|
|
return nil, trace.BadParameter("DataDir: data dir can not be empty")
|
|
}
|
|
if cfg.HostUUID == "" {
|
|
return nil, trace.BadParameter("HostUUID: host UUID can not be empty")
|
|
}
|
|
|
|
asrv, err := NewServer(&cfg, opts...)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
domainName := cfg.ClusterName.GetClusterName()
|
|
if err := backend.RunWhileLocked(ctx,
|
|
backend.RunWhileLockedConfig{
|
|
LockConfiguration: backend.LockConfiguration{
|
|
Backend: cfg.Backend,
|
|
LockNameComponents: []string{domainName},
|
|
TTL: 30 * time.Second,
|
|
},
|
|
RefreshLockInterval: 20 * time.Second,
|
|
}, func(ctx context.Context) error {
|
|
return trace.Wrap(initCluster(ctx, cfg, asrv))
|
|
}); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return asrv, nil
|
|
}
|
|
|
|
// initCluster configures the cluster based on the user provided configuration. This should
|
|
// only be called when the init lock is held to prevent multiple instances of Auth from attempting
|
|
// to bootstrap the cluster at the same time.
|
|
func initCluster(ctx context.Context, cfg InitConfig, asrv *Server) error {
|
|
span := oteltrace.SpanFromContext(ctx)
|
|
domainName := cfg.ClusterName.GetClusterName()
|
|
firstStart, err := isFirstStart(ctx, asrv, cfg)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
if cfg.SkipVersionCheck {
|
|
if err := upsertTeleportVersion(ctx, cfg.VersionStorage, asrv.Services.BackendInfoService, *teleport.SemVer()); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
} else {
|
|
if err := validateAndUpdateTeleportVersion(ctx, cfg.VersionStorage, asrv.Services.BackendInfoService, *teleport.SemVer()); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
// if bootstrap resources are supplied, use them to bootstrap backend state
|
|
// on initial startup.
|
|
if len(cfg.BootstrapResources) > 0 {
|
|
if firstStart {
|
|
asrv.logger.InfoContext(ctx, "Applying bootstrap resources (first initialization)", "resource_count", len(cfg.BootstrapResources))
|
|
if err := checkResourceConsistency(ctx, asrv.keyStore, domainName, cfg.BootstrapResources...); err != nil {
|
|
return trace.Wrap(err, "refusing to bootstrap backend")
|
|
}
|
|
if err := local.CreateResources(ctx, cfg.Backend, cfg.BootstrapResources...); err != nil {
|
|
return trace.Wrap(err, "backend bootstrap failed")
|
|
}
|
|
} else {
|
|
asrv.logger.WarnContext(ctx, "Ignoring bootstrap resources (previously initialized)", "resource_count", len(cfg.BootstrapResources))
|
|
}
|
|
}
|
|
|
|
// if apply-on-startup resources are supplied, apply them
|
|
if len(cfg.ApplyOnStartupResources) > 0 {
|
|
asrv.logger.InfoContext(ctx, "Applying resources (apply-on-startup)", "resource_count", len(cfg.ApplyOnStartupResources))
|
|
|
|
if err := applyResources(ctx, asrv.Services, cfg.ApplyOnStartupResources); err != nil {
|
|
return trace.Wrap(err, "applying resources failed")
|
|
}
|
|
}
|
|
|
|
// Set the ciphersuites that this auth server supports.
|
|
asrv.cipherSuites = cfg.CipherSuites
|
|
|
|
// INTERNAL: Authorities (plus Roles) and ReverseTunnels don't follow the
|
|
// same pattern as the rest of the configuration (they are not configuration
|
|
// singletons). However, we need to keep them around while Telekube uses them.
|
|
for _, role := range cfg.Roles {
|
|
if _, err := asrv.UpsertRole(ctx, role); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
asrv.logger.InfoContext(ctx, "Created role", "role", role.GetName())
|
|
}
|
|
for i := range cfg.Authorities {
|
|
ca := cfg.Authorities[i]
|
|
|
|
// Remove private key from leaf clusters.
|
|
if domainName != ca.GetClusterName() {
|
|
ca = ca.Clone()
|
|
types.RemoveCASecrets(ca)
|
|
}
|
|
// Don't re-create CA if it already exists, otherwise
|
|
// the existing cluster configuration will be corrupted;
|
|
// this part of code is only used in tests.
|
|
if err := asrv.CreateCertAuthority(ctx, ca); err != nil {
|
|
if !trace.IsAlreadyExists(err) {
|
|
return trace.Wrap(err)
|
|
}
|
|
} else {
|
|
asrv.logger.InfoContext(ctx, "Created trusted certificate authority",
|
|
"ca_name", ca.GetName(),
|
|
"ca_type", ca.GetType(),
|
|
)
|
|
}
|
|
}
|
|
for _, tunnel := range cfg.ReverseTunnels {
|
|
if _, err := asrv.UpsertReverseTunnel(ctx, tunnel); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
asrv.logger.InfoContext(ctx, "Created reverse tunnel", "tunnel", tunnel.GetName())
|
|
}
|
|
|
|
g, gctx := errgroup.WithContext(ctx)
|
|
g.Go(func() error {
|
|
ctx, span := cfg.Tracer.Start(gctx, "auth/SetClusterAuditConfig")
|
|
defer span.End()
|
|
return trace.Wrap(asrv.SetClusterAuditConfig(ctx, cfg.ClusterAuditConfig))
|
|
})
|
|
|
|
g.Go(func() error {
|
|
ctx, span := cfg.Tracer.Start(gctx, "auth/InitializeClusterNetworkingConfig")
|
|
defer span.End()
|
|
return trace.Wrap(initializeClusterNetworkingConfig(ctx, asrv, cfg.ClusterNetworkingConfig))
|
|
})
|
|
|
|
g.Go(func() error {
|
|
ctx, span := cfg.Tracer.Start(gctx, "auth/initializeAuthPreference")
|
|
if err := initializeAuthPreference(ctx, asrv, cfg.AuthPreference); err != nil {
|
|
span.End()
|
|
return trace.Wrap(err)
|
|
}
|
|
span.End()
|
|
|
|
ctx, span = cfg.Tracer.Start(gctx, "auth/InitializeSessionRecordingConfig")
|
|
defer span.End()
|
|
return trace.Wrap(initializeSessionRecordingConfig(ctx, asrv, cfg.SessionRecordingConfig))
|
|
})
|
|
|
|
g.Go(func() error {
|
|
ctx, span := cfg.Tracer.Start(gctx, "auth/InitializeAccessGraphSettings")
|
|
defer span.End()
|
|
return trace.Wrap(initializeAccessGraphSettings(ctx, asrv))
|
|
})
|
|
|
|
g.Go(func() error {
|
|
ctx, span := cfg.Tracer.Start(gctx, "auth/InitializeVnetConfig")
|
|
defer span.End()
|
|
return trace.Wrap(initializeVnetConfig(ctx, asrv))
|
|
})
|
|
|
|
g.Go(func() error {
|
|
_, span := cfg.Tracer.Start(gctx, "auth/SetStaticTokens")
|
|
defer span.End()
|
|
asrv.logger.InfoContext(ctx, "Updating cluster configuration", "static_tokens", cfg.StaticTokens)
|
|
return trace.Wrap(asrv.SetStaticTokens(cfg.StaticTokens))
|
|
})
|
|
|
|
g.Go(func() error {
|
|
_, span := cfg.Tracer.Start(gctx, "auth/SetStaticScopedTokens")
|
|
defer span.End()
|
|
if cfg.StaticScopedTokens != nil {
|
|
return trace.Wrap(asrv.SetStaticScopedTokens(gctx, cfg.StaticScopedTokens))
|
|
}
|
|
return nil
|
|
})
|
|
|
|
var cn types.ClusterName
|
|
g.Go(func() error {
|
|
_, span := cfg.Tracer.Start(gctx, "auth/SetClusterName")
|
|
defer span.End()
|
|
|
|
// The first Auth Server that starts gets to set the name of the cluster.
|
|
// If a cluster name/ID is already stored in the backend, the attempt to set
|
|
// a new name returns an AlreadyExists error.
|
|
err := asrv.SetClusterName(cfg.ClusterName)
|
|
if err == nil {
|
|
cn = cfg.ClusterName
|
|
return nil
|
|
}
|
|
|
|
if !trace.IsAlreadyExists(err) {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// If the cluster name has already been set, log a warning if the user
|
|
// is trying to change the name.
|
|
cn, err = asrv.Services.GetClusterName(ctx)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
if cn.GetClusterName() != cfg.ClusterName.GetClusterName() {
|
|
const msg = "Cannot rename cluster: continuing with current cluster name. Teleport " +
|
|
"clusters can not be renamed once they are created. You are seeing this " +
|
|
"message for one of two reasons. Either you have not set \"cluster_name\" in " +
|
|
"Teleport configuration and changed the hostname of the auth server or you " +
|
|
"are trying to change the value of \"cluster_name\"."
|
|
asrv.logger.ErrorContext(ctx, msg,
|
|
"current_cluster_name", cn.GetClusterName(),
|
|
"configured_cluster_name", cfg.ClusterName.GetClusterName(),
|
|
)
|
|
}
|
|
|
|
asrv.logger.DebugContext(ctx, "Cluster configuration", "cluster_name", cn.GetClusterName())
|
|
return nil
|
|
})
|
|
|
|
if err := g.Wait(); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// Override user passed in cluster name with what is in the backend.
|
|
cfg.ClusterName = cn
|
|
|
|
// Apply any outstanding migrations.
|
|
if err := migration.Apply(ctx, asrv.logger, cfg.Backend); err != nil {
|
|
return trace.Wrap(err, "applying migrations")
|
|
}
|
|
|
|
// Clone UserCA into WindowsCA. Must happen before initializeAuthorities() so
|
|
// it only affects upgraded clusters.
|
|
// Added on Teleport 18.x and 19.
|
|
clusterConfiguration, err := local.NewClusterConfigurationService(cfg.Backend)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
if err := migrateWindowsCA(ctx, migrateWindowsCAParams{
|
|
Logger: asrv.logger,
|
|
ClusterConfiguration: clusterConfiguration,
|
|
Trust: local.NewCAService(cfg.Backend),
|
|
}); err != nil {
|
|
return trace.Wrap(err, "migrate WindowsCA")
|
|
}
|
|
|
|
// generate certificate authorities if they don't exist
|
|
if err := initializeAuthorities(ctx, asrv, &cfg); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
if cfg.InsecureMode {
|
|
const warningMessage = "Starting teleport in insecure mode. This is " +
|
|
"dangerous! Sensitive information will be logged to console and " +
|
|
"certificates will not be verified. Proceed with caution!"
|
|
asrv.logger.WarnContext(ctx, warningMessage)
|
|
}
|
|
|
|
span.AddEvent("migrating legacy resources")
|
|
// Migrate any legacy resources to new format.
|
|
if err := migrateLegacyResources(ctx, asrv); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
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")
|
|
if err := createPresetRoles(ctx, modules.GetModules().BuildType(), asrv); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
span.AddEvent("completed creating preset roles")
|
|
|
|
span.AddEvent("creating preset users")
|
|
if err := createPresetUsers(ctx, modules.GetModules().BuildType(), asrv); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
span.AddEvent("completed creating preset users")
|
|
|
|
span.AddEvent("creating preset database object import rules")
|
|
if err := createPresetDatabaseObjectImportRule(ctx, asrv); err != nil {
|
|
// merely raise a warning; this is not a fatal error.
|
|
asrv.logger.WarnContext(ctx, "error creating preset database object import rules", "error", err)
|
|
}
|
|
span.AddEvent("completed creating database object import rules")
|
|
} else {
|
|
asrv.logger.InfoContext(ctx, "skipping preset role and user creation")
|
|
}
|
|
|
|
if !cfg.SkipPeriodicOperations {
|
|
asrv.logger.InfoContext(ctx, "Auth server is running periodic operations")
|
|
go asrv.runPeriodicOperations()
|
|
} else {
|
|
asrv.logger.InfoContext(ctx, "Auth server is skipping periodic operations")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func initializeAuthorities(ctx context.Context, asrv *Server, cfg *InitConfig) error {
|
|
var (
|
|
mu sync.Mutex
|
|
allKeysInUse [][]byte
|
|
)
|
|
usableKeysResults := make(map[types.CertAuthType]*keystore.UsableKeysResult)
|
|
g, gctx := errgroup.WithContext(ctx)
|
|
for _, caType := range types.CertAuthTypes {
|
|
g.Go(func() error {
|
|
tctx, span := cfg.Tracer.Start(gctx, "auth/initializeAuthority", oteltrace.WithAttributes(attribute.String("type", string(caType))))
|
|
defer span.End()
|
|
|
|
caID := types.CertAuthID{Type: caType, DomainName: cfg.ClusterName.GetClusterName()}
|
|
usableKeysResult, keysInUse, err := initializeAuthority(tctx, asrv, caID)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
usableKeysResults[caType] = usableKeysResult
|
|
allKeysInUse = append(allKeysInUse, keysInUse...)
|
|
return nil
|
|
})
|
|
}
|
|
if err := g.Wait(); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
if err := asrv.syncUsableKeysAlert(ctx, usableKeysResults); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// Collect CAs from integrations to avoid deleting them.
|
|
for ig, err := range clientutils.Resources(ctx, asrv.Services.ListIntegrations) {
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
caKeySet, err := igcredentials.GetIntegrationCertAuthorities(ctx, ig, asrv.Services)
|
|
switch {
|
|
case trace.IsNotImplemented(err):
|
|
case err != nil:
|
|
// This should not happen by design. In case integration is in a
|
|
// bad state, log a warning instead of failing this initialization.
|
|
asrv.logger.WarnContext(ctx, "Failed to fetch integration CAs", "ig", ig.GetName(), "error", err)
|
|
default:
|
|
allKeysInUse = append(allKeysInUse, collectKeysInUse(*caKeySet)...)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Delete any unused keys from the keyStore. This is to avoid exhausting
|
|
// (or wasting) HSM resources.
|
|
if err := asrv.keyStore.DeleteUnusedKeys(ctx, allKeysInUse); err != nil {
|
|
// Key deletion is best-effort, log a warning if it fails and carry on.
|
|
// We don't want to prevent a CA rotation, which may be necessary in
|
|
// some cases where this would fail.
|
|
asrv.logger.WarnContext(ctx, "An attempt to clean up unused HSM or KMS CA keys has failed unexpectedly", "error", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func initializeAuthority(ctx context.Context, asrv *Server, caID types.CertAuthID) (*keystore.UsableKeysResult, [][]byte, error) {
|
|
ca, err := asrv.Services.GetCertAuthority(ctx, caID, true)
|
|
if err != nil {
|
|
if !trace.IsNotFound(err) {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
asrv.logger.InfoContext(ctx, "First start: generating certificate authority", "ca_type", caID.Type)
|
|
if ca, err = generateAuthority(ctx, asrv, caID); err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := asrv.CreateCertAuthority(ctx, ca); err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
// Add [empty] CRLs to any issuers that are missing them.
|
|
// These are valid for 10 years and regenerated on CA rotation.
|
|
// DELETE IN v20(probakowski, zmb3): by v20 all auths will have
|
|
// at least been on v19, and all versions of 19 backfill missing CRLs.
|
|
updated := false
|
|
for _, kp := range ca.GetActiveKeys().TLS {
|
|
cert, err := tlsca.ParseCertificatePEM(kp.Cert)
|
|
if err != nil {
|
|
asrv.logger.WarnContext(ctx, "Couldn't parse CA certificate", "ca_type", caID.Type, "error", err)
|
|
continue
|
|
}
|
|
|
|
needsNewCRL := len(kp.CRL) == 0
|
|
if !needsNewCRL {
|
|
// An earlier version of this code generated CRLs that may have been signed with the wrong keypair.
|
|
// To fix this, we must also validate the signature of existing CRLs.
|
|
if crl, err := x509.ParseRevocationList(kp.CRL); err != nil {
|
|
needsNewCRL = true
|
|
} else if err := crl.CheckSignatureFrom(cert); err != nil {
|
|
asrv.logger.WarnContext(ctx, "Detected CRL with invalid signature, regenerating", "ca_type", caID.Type, "ca", ca.GetName(), "error", err)
|
|
needsNewCRL = true
|
|
}
|
|
}
|
|
|
|
if !needsNewCRL {
|
|
continue
|
|
}
|
|
|
|
signer, err := asrv.keyStore.TLSSigner(ctx, kp)
|
|
if err != nil {
|
|
if !errors.Is(err, keystore.ErrUnusableKey) {
|
|
asrv.logger.WarnContext(ctx, "Couldn't get TLS signer for CA", "ca", ca.GetName(), "ca_type", caID.Type, "error", err)
|
|
}
|
|
continue
|
|
}
|
|
|
|
if cert.KeyUsage&x509.KeyUsageCRLSign == 0 {
|
|
asrv.logger.WarnContext(ctx, "Certificate authority can't sign CRLs, some Active Directory integrations will require a CA rotation", "ca_type", caID.Type)
|
|
continue
|
|
}
|
|
|
|
crl, err := keystore.GenerateCRL(cert, signer)
|
|
if err != nil {
|
|
asrv.logger.WarnContext(ctx, "Failed to generate CRL", "ca_type", caID.Type, "error", err)
|
|
continue
|
|
}
|
|
kp.CRL = crl
|
|
updated = true
|
|
}
|
|
|
|
if updated {
|
|
if ca, err = asrv.UpdateCertAuthority(ctx, ca); err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
// Make sure the keystore has usable keys. This is a bit redundant if the CA
|
|
// was just generated above, but cheap relative to generating the CA, and
|
|
// it's nice to get the usableKeysResult.
|
|
usableKeysResult, err := asrv.keyStore.HasUsableActiveKeys(ctx, ca)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
if !usableKeysResult.CAHasUsableKeys {
|
|
if ca.GetType() == types.HostCA {
|
|
// We need to sign the local Admin identity to support auth startup
|
|
// and local tctl. For this special case we add new
|
|
// AdditionalTrustedKeys without any active keys. These keys will
|
|
// sign the local Admin identity but nothing else (until a CA
|
|
// rotation). Only the Host CA is necessary to issue the Admin
|
|
// identity.
|
|
//
|
|
// We can only get here if all the active keys for this CA are in an
|
|
// HSM or KMS that this auth is not configured to use. Because the
|
|
// auth will not use PKCS#11 keys created by a different host UUID,
|
|
// for clusters using HSM keys this includes cases where a new auth is
|
|
// added to an HA cluster, or an existing auth's host UUID is reset.
|
|
if err := asrv.ensureLocalAdditionalKeys(ctx, ca); err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
ca, err = asrv.Services.GetCertAuthority(ctx, caID, true)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
usableKeysResult, err = asrv.keyStore.HasUsableActiveKeys(ctx, ca)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
} else {
|
|
const msg = "This Auth Service is configured to use key types that the CA does not contain. " +
|
|
"No new certificates can be signed with the existing keys. " +
|
|
"You must perform a CA rotation to generate new keys, or adjust your configuration to use the existing keys."
|
|
asrv.logger.WarnContext(ctx, msg,
|
|
"configured_key_type", usableKeysResult.PreferredKeyType,
|
|
"ca_type", caID.Type,
|
|
"available_key_types", usableKeysResult.CAKeyTypes,
|
|
)
|
|
}
|
|
} else if !usableKeysResult.CAHasPreferredKeyType {
|
|
const msg = "This Auth Service is configured to use key types that the CA does not contain. " +
|
|
"New certificates will continue to be signed with raw software keys but you must perform a CA rotation to begin using the new key type."
|
|
asrv.logger.WarnContext(ctx, msg,
|
|
"configured_key_type", usableKeysResult.PreferredKeyType,
|
|
"ca_type", caID.Type,
|
|
"available_key_types", usableKeysResult.CAKeyTypes,
|
|
)
|
|
}
|
|
allKeyTypes := ca.AllKeyTypes()
|
|
numKeyTypes := len(allKeyTypes)
|
|
if numKeyTypes > 1 {
|
|
const msg = "CA contains a combination of key types. If you are attempting to" +
|
|
" configure HSM or KMS key storage, make sure it is configured on all auth servers in" +
|
|
" this cluster and then perform a CA rotation: https://goteleport.com/docs/admin-guides/management/operations/ca-rotation/"
|
|
asrv.logger.WarnContext(ctx, msg,
|
|
"ca_type", caID.Type,
|
|
"key_types", []string{strings.Join(allKeyTypes[:numKeyTypes-1], ", "), allKeyTypes[numKeyTypes-1]},
|
|
)
|
|
}
|
|
ca, err = applyAuthorityConfig(ctx, asrv, ca)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
keysInUse := collectKeysInUse(ca.GetActiveKeys(), ca.GetAdditionalTrustedKeys())
|
|
return usableKeysResult, keysInUse, nil
|
|
}
|
|
|
|
func collectKeysInUse(cas ...types.CAKeySet) (keysInUse [][]byte) {
|
|
for _, keySet := range cas {
|
|
for _, sshKeyPair := range keySet.SSH {
|
|
keysInUse = append(keysInUse, sshKeyPair.PrivateKey)
|
|
}
|
|
for _, tlsKeyPair := range keySet.TLS {
|
|
keysInUse = append(keysInUse, tlsKeyPair.Key)
|
|
}
|
|
for _, jwtKeyPair := range keySet.JWT {
|
|
keysInUse = append(keysInUse, jwtKeyPair.PrivateKey)
|
|
}
|
|
}
|
|
return keysInUse
|
|
}
|
|
|
|
// applyAuthorityConfig applies the latest keystore config to active keys updating
|
|
// the stored CA if any changes occur.
|
|
func applyAuthorityConfig(ctx context.Context, asrv *Server, ca types.CertAuthority) (types.CertAuthority, error) {
|
|
activeKeys := ca.GetActiveKeys()
|
|
var (
|
|
changed bool
|
|
err error
|
|
)
|
|
|
|
apply := func(curr []byte) ([]byte, error) {
|
|
next, err := asrv.keyStore.ApplyMultiRegionConfig(ctx, curr)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if !slices.Equal(curr, next) {
|
|
changed = true
|
|
}
|
|
return next, nil
|
|
}
|
|
|
|
for _, key := range activeKeys.SSH {
|
|
key.PrivateKey, err = apply(key.PrivateKey)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
}
|
|
for _, key := range activeKeys.TLS {
|
|
key.Key, err = apply(key.Key)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
}
|
|
for _, key := range activeKeys.JWT {
|
|
key.PrivateKey, err = apply(key.PrivateKey)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
}
|
|
if !changed {
|
|
return ca, nil
|
|
}
|
|
if err := ca.SetActiveKeys(activeKeys); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
// This is only executed during cluster init while holding a lock to prevent
|
|
// other auth servers from updating CAs simulaniously.
|
|
ca, err = asrv.UpdateCertAuthority(ctx, ca)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return ca, nil
|
|
}
|
|
|
|
// generateAuthority creates a new self-signed authority of the provided type
|
|
// and returns it to the caller. It is the responsibility of callers to persist
|
|
// the authority.
|
|
func generateAuthority(ctx context.Context, asrv *Server, caID types.CertAuthID) (types.CertAuthority, error) {
|
|
keySet, err := newKeySet(ctx, asrv.keyStore, caID)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
ca, err := types.NewCertAuthority(types.CertAuthoritySpecV2{
|
|
Type: caID.Type,
|
|
ClusterName: caID.DomainName,
|
|
ActiveKeys: keySet,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
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.
|
|
|
|
auth_service:
|
|
authentication:
|
|
second_factor: on
|
|
webauthn:
|
|
rp_id: example.com
|
|
|
|
For more information:
|
|
- https://goteleport.com/docs/admin-guides/access-controls/guides/webauthn/
|
|
`
|
|
|
|
func initializeAuthPreference(ctx context.Context, asrv *Server, newAuthPref types.AuthPreference) error {
|
|
const iterationLimit = 3
|
|
for range iterationLimit {
|
|
storedAuthPref, err := asrv.Services.GetAuthPreference(ctx)
|
|
if err != nil && !trace.IsNotFound(err) {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
shouldReplace, err := shouldInitReplaceResourceWithOrigin(ctx, storedAuthPref, newAuthPref, asrv.logger)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
if !shouldReplace {
|
|
if err := modules.ValidateResource(storedAuthPref); err != nil {
|
|
if errors.Is(err, modules.ErrCannotDisableSecondFactor) {
|
|
return trace.Wrap(err, secondFactorUpgradeInstructions)
|
|
}
|
|
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
switch {
|
|
case storedAuthPref == nil:
|
|
// This is a brand new cluster with no stored auth pref, set a
|
|
// default signature algorithm suite.
|
|
newAuthPref = newAuthPref.Clone()
|
|
newAuthPref.SetDefaultSignatureAlgorithmSuite(types.SignatureAlgorithmSuiteParams{
|
|
FIPS: asrv.fips,
|
|
UsingHSMOrKMS: asrv.keyStore.UsingHSMOrKMS(),
|
|
Cloud: modules.GetModules().Features().Cloud,
|
|
})
|
|
case newAuthPref.Origin() == types.OriginDefaults:
|
|
// There is a stored auth preference which we are overwriting with a
|
|
// default auth preference. Maintain the stored signature algorithm
|
|
// suite to avoid automatically changing it on version upgrades, new
|
|
// suites should always be opt-in for existing clusters.
|
|
newAuthPref = newAuthPref.Clone()
|
|
newAuthPref.SetSignatureAlgorithmSuite(storedAuthPref.GetSignatureAlgorithmSuite())
|
|
case newAuthPref.GetSignatureAlgorithmSuite() == types.SignatureAlgorithmSuite_SIGNATURE_ALGORITHM_SUITE_UNSPECIFIED:
|
|
// There is a stored auth preference and the new auth preference is
|
|
// coming from a config file where the signature_algorithm_suite is
|
|
// unset. Maintain the stored signature algorithm suite otherwise we
|
|
// would unset the default suite after the first auth restart.
|
|
newAuthPref = newAuthPref.Clone()
|
|
newAuthPref.SetSignatureAlgorithmSuite(storedAuthPref.GetSignatureAlgorithmSuite())
|
|
}
|
|
|
|
if err := modules.ValidateResource(newAuthPref); err != nil {
|
|
if errors.Is(err, modules.ErrCannotDisableSecondFactor) {
|
|
return trace.Wrap(err, secondFactorUpgradeInstructions)
|
|
}
|
|
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
if storedAuthPref == nil {
|
|
asrv.logger.InfoContext(ctx, "Creating cluster auth preference", "auth_preference", newAuthPref)
|
|
_, err := asrv.CreateAuthPreference(ctx, newAuthPref)
|
|
if trace.IsAlreadyExists(err) {
|
|
continue
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
newAuthPref.SetRevision(storedAuthPref.GetRevision())
|
|
_, err = asrv.UpdateAuthPreference(ctx, newAuthPref)
|
|
if trace.IsCompareFailed(err) {
|
|
continue
|
|
}
|
|
if errors.Is(err, modules.ErrCannotDisableSecondFactor) {
|
|
return trace.Wrap(err, secondFactorUpgradeInstructions)
|
|
}
|
|
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return trace.LimitExceeded("failed to initialize auth preference in %v iterations", iterationLimit)
|
|
}
|
|
|
|
func initializeClusterNetworkingConfig(ctx context.Context, asrv *Server, newNetConfig types.ClusterNetworkingConfig) error {
|
|
const iterationLimit = 3
|
|
for range 3 {
|
|
storedNetConfig, err := asrv.Services.GetClusterNetworkingConfig(ctx)
|
|
if err != nil && !trace.IsNotFound(err) {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
shouldReplace, err := shouldInitReplaceResourceWithOrigin(ctx, storedNetConfig, newNetConfig, asrv.logger)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
if !shouldReplace {
|
|
return nil
|
|
}
|
|
|
|
if storedNetConfig == nil {
|
|
asrv.logger.InfoContext(ctx, "Creating cluster networking configuration", "networking_config", newNetConfig)
|
|
_, err = asrv.CreateClusterNetworkingConfig(ctx, newNetConfig)
|
|
if trace.IsAlreadyExists(err) {
|
|
continue
|
|
}
|
|
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
asrv.logger.InfoContext(ctx, "Updating cluster networking configuration", "networking_config", newNetConfig)
|
|
newNetConfig.SetRevision(storedNetConfig.GetRevision())
|
|
_, err = asrv.UpdateClusterNetworkingConfig(ctx, newNetConfig)
|
|
if trace.IsCompareFailed(err) {
|
|
continue
|
|
}
|
|
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return trace.LimitExceeded("failed to initialize cluster networking config in %v iterations", iterationLimit)
|
|
}
|
|
|
|
func initializeSessionRecordingConfig(ctx context.Context, asrv *Server, newRecConfig types.SessionRecordingConfig) error {
|
|
const iterationLimit = 3
|
|
for range iterationLimit {
|
|
storedRecConfig, err := asrv.Services.GetSessionRecordingConfig(ctx)
|
|
if err != nil && !trace.IsNotFound(err) {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
shouldReplace, err := shouldInitReplaceResourceWithOrigin(ctx, storedRecConfig, newRecConfig, asrv.logger)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
if !shouldReplace {
|
|
return nil
|
|
}
|
|
|
|
if storedRecConfig == nil {
|
|
asrv.logger.InfoContext(ctx, "Creating session recording config", "recording_config", newRecConfig)
|
|
_, err := asrv.CreateSessionRecordingConfig(ctx, newRecConfig)
|
|
if trace.IsAlreadyExists(err) {
|
|
continue
|
|
}
|
|
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
asrv.logger.InfoContext(ctx, "Updating session recording config", "recording_config", newRecConfig)
|
|
newRecConfig.SetRevision(storedRecConfig.GetRevision())
|
|
_, err = asrv.UpdateSessionRecordingConfig(ctx, newRecConfig)
|
|
if trace.IsCompareFailed(err) {
|
|
continue
|
|
}
|
|
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return trace.LimitExceeded("failed to initialize session recording config in %v iterations", iterationLimit)
|
|
}
|
|
|
|
func initializeAccessGraphSettings(ctx context.Context, asrv *Server) error {
|
|
stored, err := asrv.Services.GetAccessGraphSettings(ctx)
|
|
if err != nil && !trace.IsNotFound(err) {
|
|
return trace.Wrap(err)
|
|
}
|
|
if stored != nil {
|
|
return nil
|
|
}
|
|
|
|
stored, err = clusterconfig.NewAccessGraphSettings(&clusterconfigpb.AccessGraphSettingsSpec{
|
|
SecretsScanConfig: clusterconfigpb.AccessGraphSecretsScanConfig_ACCESS_GRAPH_SECRETS_SCAN_CONFIG_DISABLED,
|
|
})
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
asrv.logger.InfoContext(ctx, "Creating access graph settings", "settings", stored)
|
|
_, err = asrv.CreateAccessGraphSettings(ctx, stored)
|
|
if trace.IsAlreadyExists(err) {
|
|
return nil
|
|
}
|
|
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
func initializeVnetConfig(ctx context.Context, asrv *Server) error {
|
|
stored, err := asrv.Services.GetVnetConfig(ctx)
|
|
if err != nil && !trace.IsNotFound(err) {
|
|
return trace.Wrap(err)
|
|
}
|
|
if stored != nil {
|
|
return nil
|
|
}
|
|
|
|
defaultConfig, err := vnet.DefaultVnetConfig()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
asrv.logger.InfoContext(ctx, "Creating VNet config", "vnet_config", defaultConfig)
|
|
_, err = asrv.CreateVnetConfig(ctx, defaultConfig)
|
|
if trace.IsAlreadyExists(err) {
|
|
return nil
|
|
}
|
|
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// shouldInitReplaceResourceWithOrigin determines whether the candidate
|
|
// resource should be used to replace the stored resource during auth server
|
|
// initialization. Dynamically configured resources must not be overwritten
|
|
// when the corresponding file config is left unspecified (i.e., by defaults).
|
|
func shouldInitReplaceResourceWithOrigin(ctx context.Context, stored, candidate types.ResourceWithOrigin, logger *slog.Logger) (bool, error) {
|
|
if candidate == nil || (candidate.Origin() != types.OriginDefaults && candidate.Origin() != types.OriginConfigFile) {
|
|
return false, trace.BadParameter("candidate origin must be either defaults or config-file (this is a bug)")
|
|
}
|
|
|
|
// If there is no resource stored in the backend or it was not dynamically
|
|
// configured, the candidate resource should be stored in the backend.
|
|
if stored == nil || stored.Origin() != types.OriginDynamic {
|
|
return true, nil
|
|
}
|
|
|
|
// If the candidate resource is explicitly configured in the config file,
|
|
// store this config-file resource in the backend no matter what.
|
|
if candidate.Origin() == types.OriginConfigFile {
|
|
// Log a warning when about to overwrite a dynamically configured resource.
|
|
if stored.Origin() == types.OriginDynamic {
|
|
logger.WarnContext(ctx, "Stored resource that was configured dynamically is about to be discarded in favor of explicit file configuration", "resource", stored.GetKind())
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// The resource in the backend was configured dynamically, and there is no
|
|
// more authoritative file configuration to replace it. Keep the stored
|
|
// dynamic resource.
|
|
return false, nil
|
|
}
|
|
|
|
// migrationStart marks the migration as active.
|
|
// It should be called when a migration starts.
|
|
func migrationStart(ctx context.Context, migrationName string, logger *slog.Logger) {
|
|
logger.DebugContext(ctx, "Migration started", "migration_name", migrationName)
|
|
migrations.WithLabelValues(migrationName).Set(1)
|
|
}
|
|
|
|
// migrationEnd marks the migration as inactive.
|
|
// It should be called when a migration ends.
|
|
func migrationEnd(ctx context.Context, migrationName string, logger *slog.Logger) {
|
|
logger.DebugContext(ctx, "Migration ended", "migration_name", migrationName)
|
|
migrations.WithLabelValues(migrationName).Set(0)
|
|
}
|
|
|
|
func migrateLegacyResources(ctx context.Context, asrv *Server) error {
|
|
if err := migrateRemoteClusters(ctx, asrv); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PresetRoleManager contains the required Role Management methods to create a Preset Role.
|
|
type PresetRoleManager interface {
|
|
// GetRole returns role by name.
|
|
GetRole(ctx context.Context, name string) (types.Role, error)
|
|
// CreateRole creates a role.
|
|
CreateRole(ctx context.Context, role types.Role) (types.Role, error)
|
|
// UpsertRole creates or updates a role and emits a related audit event.
|
|
UpsertRole(ctx context.Context, role types.Role) (types.Role, error)
|
|
}
|
|
|
|
// GetPresetRoles returns a list of all preset roles expected to be available on
|
|
// this cluster.
|
|
func GetPresetRoles(buildType string) []types.Role {
|
|
presets := []types.Role{
|
|
services.NewPresetGroupAccessRole(buildType),
|
|
services.NewPresetEditorRole(),
|
|
services.NewPresetAccessRole(),
|
|
services.NewPresetAuditorRole(),
|
|
services.NewPresetReviewerRole(buildType),
|
|
services.NewPresetRequesterRole(buildType),
|
|
services.NewSystemAutomaticAccessApproverRole(buildType),
|
|
services.NewPresetDeviceAdminRole(buildType),
|
|
services.NewPresetDeviceEnrollRole(buildType),
|
|
services.NewPresetRequireTrustedDeviceRole(buildType),
|
|
services.NewSystemOktaAccessRole(buildType),
|
|
services.NewSystemOktaRequesterRole(buildType),
|
|
services.NewPresetTerraformProviderRole(),
|
|
services.NewSystemIdentityCenterAccessRole(buildType),
|
|
services.NewPresetWildcardWorkloadIdentityIssuerRole(),
|
|
services.NewPresetAccessPluginRole(),
|
|
services.NewPresetListAccessRequestResourcesRole(),
|
|
services.NewPresetMCPUserRole(),
|
|
}
|
|
|
|
// Certain `New$FooRole()` functions will return a nil role if the
|
|
// corresponding feature is disabled. They should be filtered out as they
|
|
// are not actually made available on the cluster.
|
|
return slices.DeleteFunc(presets, func(r types.Role) bool { return r == nil })
|
|
}
|
|
|
|
// createPresetRoles creates preset role resources
|
|
func createPresetRoles(ctx context.Context, buildType string, rm PresetRoleManager) error {
|
|
roles := GetPresetRoles(buildType)
|
|
|
|
g, gctx := errgroup.WithContext(ctx)
|
|
for _, role := range roles {
|
|
// If the role is nil, skip because it doesn't apply to this Teleport installation.
|
|
if role == nil {
|
|
continue
|
|
}
|
|
|
|
role := role
|
|
g.Go(func() error {
|
|
// Specifically skip the Okta requester role, as it will be
|
|
// modified by the Okta access list sync.
|
|
if types.IsSystemResource(role) && role.GetName() != teleport.SystemOktaRequesterRoleName {
|
|
// System resources *always* get reset on every auth startup
|
|
if _, err := rm.UpsertRole(gctx, role); err != nil {
|
|
return trace.Wrap(err, "failed upserting system role %s", role.GetName())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
if _, err := rm.CreateRole(gctx, role); err != nil {
|
|
if !trace.IsAlreadyExists(err) {
|
|
return trace.WrapWithMessage(err, "failed to create preset role %v", role.GetName())
|
|
}
|
|
|
|
currentRole, err := rm.GetRole(gctx, role.GetName())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
role, err := services.AddRoleDefaults(gctx, buildType, currentRole)
|
|
if trace.IsAlreadyExists(err) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
if _, err := rm.UpsertRole(gctx, role); err != nil {
|
|
return trace.WrapWithMessage(err, "failed to update preset role %v", role.GetName())
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
return trace.Wrap(g.Wait())
|
|
}
|
|
|
|
// PresetUsers contains the required User Management methods to
|
|
// create a preset User. Method names represent the appropriate
|
|
// subset
|
|
type PresetUsers interface {
|
|
// CreateUser creates a new user record based on the supplied `user` instance.
|
|
CreateUser(ctx context.Context, user types.User) (types.User, error)
|
|
// GetUser fetches a user from the repository by name, optionally fetching
|
|
// any associated secrets.
|
|
GetUser(ctx context.Context, username string, withSecrets bool) (types.User, error)
|
|
// UpsertUser user creates or updates a user record as needed.
|
|
UpsertUser(ctx context.Context, user types.User) (types.User, error)
|
|
}
|
|
|
|
// getPresetUsers returns a list of all preset users expected to be available on
|
|
// this cluster.
|
|
func getPresetUsers(buildType string) []types.User {
|
|
presets := []types.User{
|
|
services.NewSystemAutomaticAccessBotUser(buildType),
|
|
}
|
|
|
|
// Certain `New$FooUser()` functions will return a nil role if the
|
|
// corresponding feature is disabled. They should be filtered out as they
|
|
// are not actually made available on the cluster.
|
|
return slices.DeleteFunc(presets, func(u types.User) bool { return u == nil })
|
|
}
|
|
|
|
// createPresetUsers creates all of the required user presets. No attempt is
|
|
// made to migrate any existing users to the lastest preset.
|
|
func createPresetUsers(ctx context.Context, buildType string, um PresetUsers) error {
|
|
users := getPresetUsers(buildType)
|
|
for _, user := range users {
|
|
// Some users are only valid for enterprise Teleport, and so will be
|
|
// nil for an OSS build and can be skipped
|
|
if user == nil {
|
|
continue
|
|
}
|
|
|
|
if types.IsSystemResource(user) {
|
|
// System resources *always* get reset on every auth startup
|
|
if _, err := um.UpsertUser(ctx, user); err != nil {
|
|
return trace.Wrap(err, "failed upserting system user %s", user.GetName())
|
|
}
|
|
continue
|
|
}
|
|
|
|
if _, err := um.CreateUser(ctx, user); err != nil && !trace.IsAlreadyExists(err) {
|
|
return trace.Wrap(err, "failed creating preset user %s", user.GetName())
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createPresetDatabaseObjectImportRule will create a sample database object import rule if there are none.
|
|
func createPresetDatabaseObjectImportRule(ctx context.Context, rules services.DatabaseObjectImportRules) error {
|
|
importRules, _, err := rules.ListDatabaseObjectImportRules(ctx, 100, "")
|
|
if err != nil {
|
|
return trace.Wrap(err, "failed listing available database object import rules")
|
|
}
|
|
// If there are existing rules, don't create a preset.
|
|
if len(importRules) > 0 {
|
|
return nil
|
|
}
|
|
|
|
rule := databaseobjectimportrule.NewPresetImportAllObjectsRule()
|
|
if rule == nil {
|
|
return nil
|
|
}
|
|
|
|
_, err = rules.CreateDatabaseObjectImportRule(ctx, rule)
|
|
if err != nil {
|
|
return trace.Wrap(err, "failed to create default database object import rule")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// isFirstStart returns 'true' if the auth server is starting for the 1st time
|
|
// on this server.
|
|
func isFirstStart(ctx context.Context, authServer *Server, cfg InitConfig) (bool, error) {
|
|
// check if the CA exists?
|
|
_, err := authServer.Services.GetCertAuthority(
|
|
ctx,
|
|
types.CertAuthID{
|
|
DomainName: cfg.ClusterName.GetClusterName(),
|
|
Type: types.HostCA,
|
|
}, false)
|
|
if err != nil {
|
|
if !trace.IsNotFound(err) {
|
|
return false, trace.Wrap(err)
|
|
}
|
|
// If a CA was not found, that means this is the first start.
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// checkResourceConsistency checks far basic conflicting state issues.
|
|
func checkResourceConsistency(ctx context.Context, keyStore *keystore.Manager, clusterName string, resources ...types.Resource) error {
|
|
for _, rsc := range resources {
|
|
switch r := rsc.(type) {
|
|
case types.CertAuthority:
|
|
// check that signing CAs have expected cluster name and that
|
|
// all CAs for this cluster do having signing keys.
|
|
caClusterName := r.GetClusterName()
|
|
seemsLocal := caClusterName == clusterName
|
|
|
|
var hasKeys bool
|
|
var signerErr error
|
|
switch r.GetType() {
|
|
case types.HostCA,
|
|
types.UserCA,
|
|
types.OpenSSHCA:
|
|
_, signerErr = keyStore.GetSSHSigner(ctx, r)
|
|
case types.DatabaseCA,
|
|
types.DatabaseClientCA,
|
|
types.SAMLIDPCA,
|
|
types.SPIFFECA,
|
|
types.AWSRACA,
|
|
types.WindowsCA:
|
|
_, _, signerErr = keyStore.GetTLSCertAndSigner(ctx, r)
|
|
case types.JWTSigner,
|
|
types.OIDCIdPCA,
|
|
types.OktaCA,
|
|
types.BoundKeypairCA:
|
|
_, signerErr = keyStore.GetJWTSigner(ctx, r)
|
|
default:
|
|
return trace.BadParameter("unexpected cert_authority type %s for cluster %v", r.GetType(), clusterName)
|
|
}
|
|
switch {
|
|
case signerErr == nil:
|
|
hasKeys = true
|
|
case trace.IsNotFound(signerErr):
|
|
hasKeys = false
|
|
default:
|
|
return trace.Wrap(signerErr)
|
|
}
|
|
|
|
if seemsLocal && !hasKeys {
|
|
return trace.BadParameter("ca for local cluster %q missing signing keys", clusterName)
|
|
}
|
|
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)
|
|
}
|
|
case types.Role:
|
|
// Some options are only available with enterprise subscription
|
|
if err := checkRoleFeatureSupport(r); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
default:
|
|
// No validation checks for this resource type
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GenerateIdentity generates identity for the auth server
|
|
func GenerateIdentity(a *Server, id state.IdentityID, additionalPrincipals, dnsNames []string) (*state.Identity, error) {
|
|
key, err := cryptosuites.GenerateKey(context.Background(), cryptosuites.GetCurrentSuiteFromAuthPreference(a), cryptosuites.HostIdentity)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
sshPub, err := ssh.NewPublicKey(key.Public())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
tlsPub, err := keys.MarshalPublicKey(key.Public())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
certs, err := a.GenerateHostCerts(context.Background(), HostCertsParams{
|
|
Req: &proto.HostCertsRequest{
|
|
HostID: id.HostUUID,
|
|
NodeName: id.NodeName,
|
|
Role: id.Role,
|
|
AdditionalPrincipals: additionalPrincipals,
|
|
DNSNames: dnsNames,
|
|
PublicSSHKey: ssh.MarshalAuthorizedKey(sshPub),
|
|
PublicTLSKey: tlsPub,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
priv, err := keys.MarshalPrivateKey(key)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return state.ReadIdentityFromKeyPair(priv, certs)
|
|
}
|
|
|
|
// CertInfo returns diagnostic information about certificate
|
|
func CertInfo(cert *x509.Certificate) string {
|
|
return fmt.Sprintf("cert(%v issued by %v:%v)", cert.Subject.CommonName, cert.Issuer.CommonName, cert.Issuer.SerialNumber)
|
|
}
|
|
|
|
// TLSCertInfo returns diagnostic information about certificate
|
|
func TLSCertInfo(cert *tls.Certificate) string {
|
|
x509cert, err := x509.ParseCertificate(cert.Certificate[0])
|
|
if err != nil {
|
|
return err.Error()
|
|
}
|
|
return CertInfo(x509cert)
|
|
}
|
|
|
|
// CertAuthorityInfo returns debugging information about certificate authority
|
|
func CertAuthorityInfo(ca types.CertAuthority) string {
|
|
var out []string
|
|
for _, keyPair := range ca.GetTrustedTLSKeyPairs() {
|
|
cert, err := tlsca.ParseCertificatePEM(keyPair.Cert)
|
|
if err != nil {
|
|
out = append(out, err.Error())
|
|
} else {
|
|
out = append(out, fmt.Sprintf("trust root(%v:%v)", cert.Subject.CommonName, cert.Subject.SerialNumber))
|
|
}
|
|
}
|
|
return fmt.Sprintf("cert authority(state: %v, phase: %v, roots: %v)", ca.GetRotation().State, ca.GetRotation().Phase, strings.Join(out, ", "))
|
|
}
|
|
|
|
// DELETE IN: 2.7.0
|
|
// NOTE: Sadly, our integration tests depend on this logic
|
|
// because they create remote cluster resource. Our integration
|
|
// tests should be migrated to use trusted clusters instead of manually
|
|
// creating tunnels.
|
|
// This migration adds remote cluster resource migrating from 2.5.0
|
|
// where the presence of remote cluster was identified only by presence
|
|
// of host certificate authority with cluster name not equal local cluster name
|
|
func migrateRemoteClusters(ctx context.Context, asrv *Server) error {
|
|
migrationStart(ctx, "remote_clusters", asrv.logger)
|
|
defer migrationEnd(ctx, "remote_clusters", asrv.logger)
|
|
|
|
clusterName, err := asrv.Services.GetClusterName(ctx)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
certAuthorities, err := asrv.Services.GetCertAuthorities(ctx, types.HostCA, false)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
// loop over all roles and make sure any v3 roles have permit port
|
|
// forward and forward agent allowed
|
|
for _, certAuthority := range certAuthorities {
|
|
if certAuthority.GetName() == clusterName.GetClusterName() {
|
|
asrv.logger.DebugContext(ctx, "Migrations: skipping local cluster cert authority", "cert_authority", certAuthority.GetName())
|
|
continue
|
|
}
|
|
// remote cluster already exists
|
|
_, err = asrv.Services.GetRemoteCluster(ctx, certAuthority.GetName())
|
|
if err == nil {
|
|
asrv.logger.DebugContext(ctx, "Migrations: remote cluster already exists for cert authority", "cert_authority", certAuthority.GetName())
|
|
continue
|
|
}
|
|
if !trace.IsNotFound(err) {
|
|
return trace.Wrap(err)
|
|
}
|
|
// the cert authority is associated with trusted cluster
|
|
_, err = asrv.Services.GetTrustedCluster(ctx, certAuthority.GetName())
|
|
if err == nil {
|
|
asrv.logger.DebugContext(ctx, "Migrations: trusted cluster resource exists for cert authority", "cert_authority", certAuthority.GetName())
|
|
continue
|
|
}
|
|
if !trace.IsNotFound(err) {
|
|
return trace.Wrap(err)
|
|
}
|
|
remoteCluster, err := types.NewRemoteCluster(certAuthority.GetName())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
_, err = asrv.CreateRemoteCluster(ctx, remoteCluster)
|
|
if err != nil {
|
|
if !trace.IsAlreadyExists(err) {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
asrv.logger.InfoContext(ctx, "Migrations: added remote cluster resource for cert authority", "cert_authority", certAuthority.GetName())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ResourceApplyPriority specifies in which order the resources must be applied
|
|
// to avoid consistency issues. A lower priority means the resource is applied
|
|
// before.
|
|
var ResourceApplyPriority = map[string]int{
|
|
types.KindRole: 1,
|
|
types.KindUser: 2, // Users must be applied after Roles
|
|
types.KindToken: 3,
|
|
types.KindClusterNetworkingConfig: 3,
|
|
types.KindClusterAuthPreference: 3,
|
|
// Bots should be applied after users and roles as at the moment they are actually converted to users and roles.
|
|
// This will ensure that Bot Users/Roles are properly created regardless of the Teleport version from which the
|
|
// resources have been exported.
|
|
types.KindBot: 3,
|
|
}
|
|
|
|
// Unlike when resources are loaded via --bootstrap, we're inserting elements via their service.
|
|
// This means consistency is checked. This function support applying resources
|
|
// with dependencies (like a user referring to a role).
|
|
func applyResources(ctx context.Context, service *Services, resources []types.Resource) error {
|
|
var err error
|
|
slices.SortFunc(resources, func(a, b types.Resource) int {
|
|
priorityA := ResourceApplyPriority[a.GetKind()]
|
|
priorityB := ResourceApplyPriority[b.GetKind()]
|
|
return cmp.Compare(priorityA, priorityB)
|
|
})
|
|
for _, resource := range resources {
|
|
// DO NOT ADD EVERY RESOURCE TO THIS SWITCH
|
|
// Apply-on-startup should not be supported for every resource, this is a way to bootstrap a
|
|
// minimal cluster (bootstrap IaC user, role, bot, join token, config so you have enough
|
|
// trust to join core components without human intervention).
|
|
// Resources not required for a minimal cluster should be created via Infrastructure-as-Code
|
|
// (tctl, Terraform Provider, Kube Operator)
|
|
// You MUST also add a test case for your resource in `TestInit_ApplyOnStartup` because adding
|
|
// your resource to this switch in not enough for the --apply-on-startup flag to work, you also
|
|
// need to RegisterResourceUnmarshaler() your resource.
|
|
switch r := resource.(type) {
|
|
case types.ProvisionToken:
|
|
err = service.Provisioner.UpsertToken(ctx, r)
|
|
case types.User:
|
|
err = services.ValidateUserRoles(ctx, r, service.Access)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
_, err = service.Identity.UpsertUser(ctx, r)
|
|
case types.Role:
|
|
_, err = service.Access.UpsertRole(ctx, r)
|
|
case types.ClusterNetworkingConfig:
|
|
_, err = service.ClusterConfigurationInternal.UpsertClusterNetworkingConfig(ctx, r)
|
|
case types.AuthPreference:
|
|
_, err = service.ClusterConfigurationInternal.UpsertAuthPreference(ctx, r)
|
|
case types.Resource153UnwrapperT[*machineidv1pb.Bot]:
|
|
_, err = machineidv1.UpsertBot(ctx, service, r.UnwrapT(), time.Now(), "system")
|
|
case types.Resource153UnwrapperT[*autoupdatev1pb.AutoUpdateConfig]:
|
|
_, err = autoupdatev1.UpsertAutoUpdateConfig(ctx, service, r.UnwrapT())
|
|
case types.Resource153UnwrapperT[*autoupdatev1pb.AutoUpdateVersion]:
|
|
_, err = autoupdatev1.UpsertAutoUpdateVersion(ctx, service, r.UnwrapT())
|
|
case types.Resource153UnwrapperT[*summarizerv1.InferenceModel]:
|
|
_, err = service.Summarizer.UpsertInferenceModel(ctx, r.UnwrapT())
|
|
default:
|
|
return trace.NotImplemented("cannot apply resource of type %T", resource)
|
|
}
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|