Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

readonly cluster configs #43422

Merged
merged 1 commit into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions api/types/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (

"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/api/utils/tlsutils"
)
Expand Down Expand Up @@ -177,6 +178,9 @@ type AuthPreference interface {

// String represents a human readable version of authentication settings.
String() string

// Clone makes a deep copy of the AuthPreference.
Clone() AuthPreference
}

// NewAuthPreference is a convenience method to to create AuthPreferenceV2.
Expand Down Expand Up @@ -766,6 +770,11 @@ func (c *AuthPreferenceV2) String() string {
return fmt.Sprintf("AuthPreference(Type=%q,SecondFactor=%q)", c.Spec.Type, c.Spec.SecondFactor)
}

// Clone returns a copy of the AuthPreference resource.
func (c *AuthPreferenceV2) Clone() AuthPreference {
return utils.CloneProtoMsg(c)
}

func (u *U2F) Check() error {
if u.AppID == "" {
return trace.BadParameter("u2f configuration missing app_id")
Expand Down
10 changes: 10 additions & 0 deletions api/types/sessionrecording.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"time"

"github.com/gravitational/trace"

"github.com/gravitational/teleport/api/utils"
)

// SessionRecordingConfig defines session recording configuration. This is
Expand All @@ -40,6 +42,9 @@ type SessionRecordingConfig interface {

// SetProxyChecksHostKeys sets if the proxy will check host keys.
SetProxyChecksHostKeys(bool)

// Clone returns a copy of the resource.
Clone() SessionRecordingConfig
}

// NewSessionRecordingConfigFromConfigFile is a convenience method to create
Expand Down Expand Up @@ -158,6 +163,11 @@ func (c *SessionRecordingConfigV2) SetProxyChecksHostKeys(t bool) {
c.Spec.ProxyChecksHostKeys = NewBoolOption(t)
}

// Clone returns a copy of the resource.
func (c *SessionRecordingConfigV2) Clone() SessionRecordingConfig {
return utils.CloneProtoMsg(c)
}

// setStaticFields sets static resource header and metadata fields.
func (c *SessionRecordingConfigV2) setStaticFields() {
c.Kind = KindSessionRecordingConfig
Expand Down
5 changes: 4 additions & 1 deletion integration/helpers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,8 +483,11 @@ func UpsertAuthPrefAndWaitForCache(
_, err := srv.UpsertAuthPreference(ctx, pref)
require.NoError(t, err)
require.EventuallyWithT(t, func(t *assert.CollectT) {
p, err := srv.GetAuthPreference(ctx)
// we need to wait for the in-memory copy of auth pref to be updated, which
// takes a bit longer than standard cache propagation.
rp, err := srv.GetReadOnlyAuthPreference(ctx)
require.NoError(t, err)
p := rp.Clone()
assert.Empty(t, cmp.Diff(&pref, &p))
}, 5*time.Second, 100*time.Millisecond)
}
55 changes: 40 additions & 15 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ import (
"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/services/readonly"
"github.com/gravitational/teleport/lib/spacelift"
"github.com/gravitational/teleport/lib/srv/db/common/role"
"github.com/gravitational/teleport/lib/sshca"
Expand Down Expand Up @@ -485,6 +486,20 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (*Server, error) {
return nil, trace.Wrap(err)
}

_, cacheEnabled := as.getCache()

// cluster config ttl cache *must* be set up after `opts` has been applied to the server because
// the Cache field starts off as a pointer to the local backend services and is only switched
// over to being a proper cache during option processing.
as.ReadOnlyCache, err = readonly.NewCache(readonly.CacheConfig{
Upstream: as.Cache,
Disabled: !cacheEnabled,
ReloadOnErr: true,
})
if err != nil {
return nil, trace.Wrap(err)
}

if as.ghaIDTokenValidator == nil {
as.ghaIDTokenValidator = githubactions.NewIDTokenValidator(
githubactions.IDTokenValidatorConfig{
Expand Down Expand Up @@ -813,6 +828,10 @@ type LoginHook func(context.Context, types.User) error
// the user has no suitable trusted device.
type CreateDeviceWebTokenFunc func(context.Context, *devicepb.DeviceWebToken) (*devicepb.DeviceWebToken, error)

// ReadOnlyCache is a type alias used to assist with embedding [readonly.Cache] in places
// where it would have a naming conflict with other types named Cache.
type ReadOnlyCache = readonly.Cache

// Server keeps the cluster together. It acts as a certificate authority (CA) for
// a cluster and:
// - generates the keypair for the node it's running on
Expand Down Expand Up @@ -864,6 +883,11 @@ type Server struct {
// method on Services instead.
authclient.Cache

// ReadOnlyCache is a specialized cache that provides read-only shared references
// in certain performance-critical paths where deserialization/cloning may be too
// expensive at scale.
*ReadOnlyCache

// privateKey is used in tests to use pre-generated private keys
privateKey []byte

Expand Down Expand Up @@ -1867,7 +1891,8 @@ func (a *Server) GenerateHostCert(ctx context.Context, hostPublicKey []byte, hos
func (a *Server) generateHostCert(
ctx context.Context, p services.HostCertParams,
) ([]byte, error) {
authPref, err := a.GetAuthPreference(ctx)

readOnlyAuthPref, err := a.GetReadOnlyAuthPreference(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
Expand All @@ -1890,7 +1915,7 @@ func (a *Server) generateHostCert(
default:
locks = []types.LockTarget{{ServerID: p.HostID}, {ServerID: HostFQDN(p.HostID, p.ClusterName)}}
}
if lockErr := a.checkLockInForce(authPref.GetLockingMode(),
if lockErr := a.checkLockInForce(readOnlyAuthPref.GetLockingMode(),
locks,
); lockErr != nil {
return nil, trace.Wrap(lockErr)
Expand Down Expand Up @@ -2052,11 +2077,11 @@ func (a *Server) GenerateOpenSSHCert(ctx context.Context, req *proto.OpenSSHCert
return nil, trace.BadParameter("public key is empty")
}
if req.TTL == 0 {
cap, err := a.GetAuthPreference(ctx)
readOnlyAuthPref, err := a.GetReadOnlyAuthPreference(ctx)
if err != nil {
return nil, trace.BadParameter("cert request does not specify a TTL and the cluster_auth_preference is not available: %v", err)
}
req.TTL = proto.Duration(cap.GetDefaultSessionTTL())
req.TTL = proto.Duration(readOnlyAuthPref.GetDefaultSessionTTL())
}
if req.TTL < 0 {
return nil, trace.BadParameter("TTL must be positive")
Expand Down Expand Up @@ -2604,13 +2629,13 @@ func (a *Server) augmentUserCertificates(
}

// Verify locks right before we re-issue any certificates.
authPref, err := a.GetAuthPreference(ctx)
readOnlyAuthPref, err := a.GetReadOnlyAuthPreference(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
if err := a.verifyLocksForUserCerts(verifyLocksForUserCertsReq{
checker: opts.checker,
defaultMode: authPref.GetLockingMode(),
defaultMode: readOnlyAuthPref.GetLockingMode(),
username: x509Identity.Username,
mfaVerified: x509Identity.MFAVerified,
activeAccessRequests: x509Identity.ActiveRequests,
Expand Down Expand Up @@ -2743,13 +2768,13 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types.
}

// Reject the cert request if there is a matching lock in force.
authPref, err := a.GetAuthPreference(ctx)
readOnlyAuthPref, err := a.GetReadOnlyAuthPreference(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
if err := a.verifyLocksForUserCerts(verifyLocksForUserCertsReq{
checker: req.checker,
defaultMode: authPref.GetLockingMode(),
defaultMode: readOnlyAuthPref.GetLockingMode(),
username: req.user.GetName(),
mfaVerified: req.mfaVerified,
activeAccessRequests: req.activeRequests.AccessRequests,
Expand Down Expand Up @@ -2778,7 +2803,7 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types.
var allowedLogins []string

if req.ttl == 0 {
req.ttl = time.Duration(authPref.GetDefaultSessionTTL())
req.ttl = time.Duration(readOnlyAuthPref.GetDefaultSessionTTL())
}

// If the role TTL is ignored, do not restrict session TTL and allowed logins.
Expand Down Expand Up @@ -2808,7 +2833,7 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types.
}

attestedKeyPolicy := keys.PrivateKeyPolicyNone
requiredKeyPolicy, err := req.checker.PrivateKeyPolicy(authPref.GetPrivateKeyPolicy())
requiredKeyPolicy, err := req.checker.PrivateKeyPolicy(readOnlyAuthPref.GetPrivateKeyPolicy())
if err != nil {
return nil, trace.Wrap(err)
}
Expand All @@ -2830,7 +2855,7 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types.
}

var validateSerialNumber bool
hksnv, err := authPref.GetHardwareKeySerialNumberValidation()
hksnv, err := readOnlyAuthPref.GetHardwareKeySerialNumberValidation()
if err == nil {
validateSerialNumber = hksnv.Enabled
}
Expand Down Expand Up @@ -3619,7 +3644,7 @@ func (a *Server) deleteMFADeviceSafely(ctx context.Context, user, deviceName str
return nil, trace.Wrap(err)
}

authPref, err := a.GetAuthPreference(ctx)
readOnlyAuthPref, err := a.GetReadOnlyAuthPreference(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down Expand Up @@ -3667,7 +3692,7 @@ func (a *Server) deleteMFADeviceSafely(ctx context.Context, user, deviceName str

// Prevent users from deleting their last device for clusters that require second factors.
const minDevices = 1
switch sf := authPref.GetSecondFactor(); sf {
switch sf := readOnlyAuthPref.GetSecondFactor(); sf {
case constants.SecondFactorOff, constants.SecondFactorOptional: // MFA is not required, allow deletion
case constants.SecondFactorOn:
if knownDevices <= minDevices {
Expand All @@ -3688,7 +3713,7 @@ func (a *Server) deleteMFADeviceSafely(ctx context.Context, user, deviceName str
// It checks whether the credential to delete is a last passkey and whether
// the user has other valid local credentials.
canDeleteLastPasskey := func() (bool, error) {
if !authPref.GetAllowPasswordless() || numResidentKeys > 1 || !isResidentKey(deviceToDelete) {
if !readOnlyAuthPref.GetAllowPasswordless() || numResidentKeys > 1 || !isResidentKey(deviceToDelete) {
return true, nil
}

Expand All @@ -3710,7 +3735,7 @@ func (a *Server) deleteMFADeviceSafely(ctx context.Context, user, deviceName str

// Whether we take TOTPs into consideration or not depends on whether it's
// enabled.
switch sf := authPref.GetSecondFactor(); sf {
switch sf := readOnlyAuthPref.GetSecondFactor(); sf {
case constants.SecondFactorOTP, constants.SecondFactorOn, constants.SecondFactorOptional:
if sfToCount[constants.SecondFactorOTP] >= 1 {
return true, nil
Expand Down
25 changes: 16 additions & 9 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -1559,7 +1559,7 @@ func (a *ServerWithRoles) GetSSHTargets(ctx context.Context, req *proto.GetSSHTa
// try to detect case-insensitive routing setting, but default to false if we can't load
// networking config (equivalent to proxy routing behavior).
var caseInsensitiveRouting bool
if cfg, err := a.authServer.GetClusterNetworkingConfig(ctx); err == nil {
if cfg, err := a.authServer.GetReadOnlyClusterNetworkingConfig(ctx); err == nil {
caseInsensitiveRouting = cfg.GetCaseInsensitiveRouting()
}

Expand Down Expand Up @@ -2952,11 +2952,11 @@ func getBotName(user types.User) string {

func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserCertsRequest, opts ...certRequestOption) (*proto.Certs, error) {
// Device trust: authorize device before issuing certificates.
authPref, err := a.authServer.GetAuthPreference(ctx)
readOnlyAuthPref, err := a.authServer.GetReadOnlyAuthPreference(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
if err := a.verifyUserDeviceForCertIssuance(req.Usage, authPref.GetDeviceTrust()); err != nil {
if err := a.verifyUserDeviceForCertIssuance(req.Usage, readOnlyAuthPref.GetDeviceTrust()); err != nil {
return nil, trace.Wrap(err)
}

Expand Down Expand Up @@ -3054,7 +3054,7 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC
if err != nil {
return nil, trace.Wrap(err)
}
sessionTTL := roleSet.AdjustSessionTTL(authPref.GetDefaultSessionTTL().Duration())
sessionTTL := roleSet.AdjustSessionTTL(readOnlyAuthPref.GetDefaultSessionTTL().Duration())
req.Expires = a.authServer.GetClock().Now().UTC().Add(sessionTTL)
} else if req.Expires.After(sessionExpires) {
// Standard user impersonation has an expiry limited to the expiry
Expand Down Expand Up @@ -4483,8 +4483,11 @@ func (a *ServerWithRoles) GetAuthPreference(ctx context.Context) (types.AuthPref
if err := a.action(apidefaults.Namespace, types.KindClusterAuthPreference, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}

return a.authServer.GetAuthPreference(ctx)
cfg, err := a.authServer.GetReadOnlyAuthPreference(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
return cfg.Clone(), nil
}

func (a *ServerWithRoles) GetUIConfig(ctx context.Context) (types.UIConfig, error) {
Expand Down Expand Up @@ -4673,7 +4676,11 @@ func (a *ServerWithRoles) GetClusterNetworkingConfig(ctx context.Context) (types
return nil, trace.Wrap(err)
}
}
return a.authServer.GetClusterNetworkingConfig(ctx)
cfg, err := a.authServer.GetReadOnlyClusterNetworkingConfig(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
return cfg.Clone(), nil
}

// SetClusterNetworkingConfig sets cluster networking configuration.
Expand Down Expand Up @@ -6772,12 +6779,12 @@ func (a *ServerWithRoles) CreateRegisterChallenge(ctx context.Context, req *prot
// enforceGlobalModeTrustedDevice is used to enforce global device trust requirements
// for key endpoints.
func (a *ServerWithRoles) enforceGlobalModeTrustedDevice(ctx context.Context) error {
authPref, err := a.GetAuthPreference(ctx)
readOnlyAuthPref, err := a.authServer.GetReadOnlyAuthPreference(ctx)
if err != nil {
return trace.Wrap(err)
}

err = dtauthz.VerifyTLSUser(authPref.GetDeviceTrust(), a.context.Identity.GetIdentity())
err = dtauthz.VerifyTLSUser(readOnlyAuthPref.GetDeviceTrust(), a.context.Identity.GetIdentity())
return trace.Wrap(err)
}

Expand Down
Loading
Loading