Skip to content

Commit

Permalink
[v12] Allow setting max_session_ttl from clusterauth preferences #28130
Browse files Browse the repository at this point in the history
  (#28185)

* Allow setting max_session_ttl from clusterauth preferences

Document `default_session_ttl` in the reference.

* Set the default session ttl in generateCert

use default session ttl for `tctl auth sign`

leave tctl alone

* fix merge error

* Fix wrong CertTTL the KeyTTL if its zero in teleterm

Make use of updateClientFromPingReponse
  • Loading branch information
lxea committed Jun 28, 2023
1 parent a0604c6 commit 1165f4b
Show file tree
Hide file tree
Showing 16 changed files with 1,441 additions and 1,250 deletions.
4 changes: 4 additions & 0 deletions api/client/webclient/webclient.go
Expand Up @@ -41,6 +41,7 @@ import (
"github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/observability/tracing"
tracehttp "github.com/gravitational/teleport/api/observability/tracing/http"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/api/utils/keys"
)
Expand Down Expand Up @@ -406,6 +407,9 @@ type AuthenticationSettings struct {
HasMessageOfTheDay bool `json:"has_motd"`
// LoadAllCAs tells tsh to load CAs for all clusters when trying to ssh into a node.
LoadAllCAs bool `json:"load_all_cas,omitempty"`
// DefaultSessionTTL is the TTL requested for user certs if
// a TTL is not otherwise specified.
DefaultSessionTTL types.Duration `json:"default_session_ttl"`
}

// LocalSettings holds settings for local authentication.
Expand Down
7 changes: 7 additions & 0 deletions api/proto/teleport/legacy/types/types.proto
Expand Up @@ -1682,6 +1682,13 @@ message AuthPreferenceSpecV2 {
(gogoproto.jsontag) = "allow_headless,omitempty",
(gogoproto.customtype) = "BoolOption"
];

// DefaultSessionTTL is the TTL to use for user certs when
// an explicit TTL is not requested.
int64 DefaultSessionTTL = 16 [
(gogoproto.jsontag) = "default_session_ttl,omitempty",
(gogoproto.casttype) = "Duration"
];
}

// U2F defines settings for U2F device.
Expand Down
20 changes: 20 additions & 0 deletions api/types/authentication.go
Expand Up @@ -29,6 +29,7 @@ import (
log "github.com/sirupsen/logrus"

"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/api/utils/tlsutils"
)
Expand Down Expand Up @@ -127,6 +128,11 @@ type AuthPreference interface {
// SetSAMLIdPEnabled sets the SAML IdP to enabled.
SetSAMLIdPEnabled(bool)

// GetDefaultSessionTTL retrieves the max session ttl
GetDefaultSessionTTL() Duration
// SetDefaultSessionTTL sets the max session ttl
SetDefaultSessionTTL(Duration)

// String represents a human readable version of authentication settings.
String() string
}
Expand Down Expand Up @@ -435,6 +441,16 @@ func (c *AuthPreferenceV2) SetSAMLIdPEnabled(enabled bool) {
c.Spec.IDP.SAML.Enabled = NewBoolOption(enabled)
}

// SetDefaultSessionTTL sets the default session ttl
func (c *AuthPreferenceV2) SetDefaultSessionTTL(sessionTTL Duration) {
c.Spec.DefaultSessionTTL = sessionTTL
}

// GetDefaultSessionTTL retrieves the default session ttl
func (c *AuthPreferenceV2) GetDefaultSessionTTL() Duration {
return c.Spec.DefaultSessionTTL
}

// setStaticFields sets static resource header and metadata fields.
func (c *AuthPreferenceV2) setStaticFields() {
c.Kind = KindClusterAuthPreference
Expand Down Expand Up @@ -471,6 +487,10 @@ func (c *AuthPreferenceV2) CheckAndSetDefaults() error {
c.SetOrigin(OriginDynamic)
}

if c.Spec.DefaultSessionTTL == 0 {
c.Spec.DefaultSessionTTL = Duration(defaults.CertDuration)
}

switch c.Spec.Type {
case constants.Local, constants.OIDC, constants.SAML, constants.Github:
// Note that "type:local" and "local_auth:false" is considered a valid
Expand Down
2,509 changes: 1,271 additions & 1,238 deletions api/types/types.pb.go

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions docs/pages/includes/config-reference/auth-service.yaml
Expand Up @@ -238,6 +238,11 @@ auth_service:
# and Kubernetes connections.
mode: optional # always "off" for OSS

# Determines the default time to live for user certificates
# issued by this auth server, defaults to 12 hours. Examples:
# "14h30m", "1h" etc.
default_session_ttl: 12h

# IP and the port to bind to. Other Teleport Nodes will be connecting to
# this port (AKA "Auth API" or "Cluster API") to validate client
# certificates
Expand Down
91 changes: 91 additions & 0 deletions docs/pages/reference/resources.mdx
Expand Up @@ -122,6 +122,7 @@ Here's the list of resources currently exposed via [`tctl`](./cli.mdx#tctl):
| login_rule | A Login Rule, see the [Login Rules guide](../access-controls/login-rules.mdx) for more info. |
| device | A Teleport Trusted Device, see the [Device Trust guide](../access-controls/device-trust/guide.mdx) for more info. |
| [ui_config](#ui-config) | Configuration for the Web UI served by the Proxy Service |
| cluster_auth_preference | Configuration for the cluster's auth preferences. |

**Examples:**

Expand Down Expand Up @@ -149,6 +150,9 @@ $ tctl get devices
# Fetch a specific device:
$ tctl get devices/<asset-tag>
# Fetch the cluster auth preferences
$ tctl get cluster_auth_preference
```

<Admonition type="note">
Expand Down Expand Up @@ -216,3 +220,90 @@ Device contains information identifying a trusted device.
Global configuration options for the Web UI served by the Proxy Service. This resource is not set by default, which means a `tctl get ui` will result in an error if used before this resource has been set.

(!docs/pages/includes/ui-config-spec.mdx!)

### Cluster Auth Preferences

Global cluster configuration options for authentication.

```yaml
metadata:
name: cluster-auth-preference
spec:
# Sets the type of second factor to use.
# Possible values: "on", "off", "otp", "webauthn" and "optional".
# If "on" is set, all 2FA protocols are supported.
second_factor: "otp"

# The name of the OIDC or SAML connector. if this is not set, the first connector in the backend is used.
connector_name: ""

# webauthn is the settings for server-side Web authentication support.
webauthn:
# rp_id is the ID of the Relying Party.
# It should be set to the domain name of the Teleport installation.
#
# IMPORTANT: rp_id must never change in the lifetime of the cluster, because
# it's recorded in the registration data on the WebAuthn device. If the
# ri_id changes, all existing WebAuthn key registrations will become invalid
# and all users who use WebAuthn as the second factor will need to
# re-register.
rp_id: teleport.example.com
# Allow list of device attestation CAs in PEM format.
# If present, only devices whose attestation certificates match the
# certificates specified here may be registered (existing registrations are
# unchanged).
# If supplied in conjunction with `attestation_denied_cas`, then both
# conditions need to be true for registration to be allowed (the device
# MUST match an allowed CA and MUST NOT match a denied CA).
# By default all devices are allowed.
attestation_allowed_cas: []
# Deny list of device attestation CAs in PEM format.
# If present, only devices whose attestation certificates don't match the
# certificates specified here may be registered (existing registrations are
# unchanged).
attestation_denied_cas: []

# Enforce per-session MFA or PIV-hardware key restrictions on user login sessions.
# Possible values: true, false, "hardware_key", "hardware_key_touch"
require_mfa_type: "off"

# Sets whether connections with expired client certificates will be disconnected.
disconnect_expired_cert: false

# Sets whether headless authentication is allowed.
# Headless authentication requires WebAuthn.
# Defaults to true if webauthn is configured.
allow_headless: false

# Sets whether local auth is enabled alongside any other authentication
# type.
allow_local_auth: true

# Sets whether passwordless authentication is allowed.
# Requires Webauthn to work.
allow_passwordless: false

# Sets the message of the day for the cluster.
message_of_the_day: ""

# idp is a set of options related to accessing IdPs within Teleport. Requires Teleport Enterprise
idp:
# options related to the Teleport SAML IdP.
saml:
# enables access to the Teleport SAML IdP.
enabled: true

# locking_mode is the cluster-wide locking mode default.
# Possible values: "strict" or "best_effort"
locking_mode: best_effort

# default_session_ttl defines the default TTL (time to live) of certificates
# issued to the users on this cluster.
default_session_ttl: "12h"

# The type of authentication to use for this cluster.
# Possible values: "local", "oidc", "saml" and "github"
type: local

version: v2
```
10 changes: 7 additions & 3 deletions lib/auth/auth.go
Expand Up @@ -1874,6 +1874,10 @@ func (a *Server) generateUserCert(req certRequest) (*proto.Certs, error) {
var sessionTTL time.Duration
var allowedLogins []string

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

// If the role TTL is ignored, do not restrict session TTL and allowed logins.
// The only caller setting this parameter should be "tctl auth sign".
// Otherwise, set the session TTL to the smallest of all roles and
Expand All @@ -1887,10 +1891,10 @@ func (a *Server) generateUserCert(req certRequest) (*proto.Certs, error) {
return nil, trace.Wrap(err)
}
} else {
// Adjust session TTL to the smaller of two values: the session TTL
// requested in tsh or the session TTL for the role.
// Adjust session TTL to the smaller of two values: the session TTL requested
// in tsh (possibly using default_session_ttl) or the session TTL for the
// role.
sessionTTL = req.checker.AdjustSessionTTL(req.ttl)

// Return a list of logins that meet the session TTL limit. This means if
// the requested session TTL is larger than the max session TTL for a login,
// that login will not be included in the list of allowed logins.
Expand Down
5 changes: 5 additions & 0 deletions lib/auth/auth_with_roles_test.go
Expand Up @@ -183,6 +183,11 @@ func TestLocalUserCanReissueCerts(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
user, _, err := CreateUserAndRole(srv.Auth(), test.desc, []string{"role"})
require.NoError(t, err)
ctx := context.Background()
authPref, err := srv.Auth().GetAuthPreference(ctx)
require.NoError(t, err)
authPref.SetDefaultSessionTTL(types.Duration(test.expiresIn))
srv.Auth().SetAuthPreference(ctx, authPref)

var id TestIdentity
if test.renewable {
Expand Down
4 changes: 4 additions & 0 deletions lib/auth/tls_test.go
Expand Up @@ -2170,6 +2170,10 @@ func TestGenerateCerts(t *testing.T) {
t.Run("ImpersonateAllow", func(t *testing.T) {
// Super impersonator impersonate anyone and login as root
maxSessionTTL := 300 * time.Hour
authPref, err := srv.Auth().GetAuthPreference(ctx)
require.NoError(t, err)
authPref.SetDefaultSessionTTL(types.Duration(maxSessionTTL))
srv.Auth().SetAuthPreference(ctx, authPref)
superImpersonatorRole, err := types.NewRole("superimpersonator", types.RoleSpecV6{
Options: types.RoleOptions{
MaxSessionTTL: types.Duration(maxSessionTTL),
Expand Down
12 changes: 9 additions & 3 deletions lib/client/api.go
Expand Up @@ -984,9 +984,7 @@ func NewClient(c *Config) (tc *TeleportClient, err error) {
}
log.Infof("no host login given. defaulting to %s", c.HostLogin)
}
if c.KeyTTL == 0 {
c.KeyTTL = apidefaults.CertDuration
}

c.Namespace = types.ProcessNamespace(c.Namespace)

if c.Tracer == nil {
Expand Down Expand Up @@ -3165,6 +3163,14 @@ func (tc *TeleportClient) Login(ctx context.Context) (*Key, error) {
// Perform the ALPN test once at login.
tc.TLSRoutingConnUpgradeRequired = alpnproxy.IsALPNConnUpgradeRequired(tc.WebProxyAddr, tc.InsecureSkipVerify)

if tc.KeyTTL == 0 {
tc.KeyTTL = time.Duration(pr.Auth.DefaultSessionTTL)
}
// todo(lxea): DELETE IN v15(?) where the auth is guaranteed to send us a valid MaxSessionTTL or the auth is guaranteed to interpret 0 duration as the auth's default?
if tc.KeyTTL == 0 {
tc.KeyTTL = apidefaults.CertDuration
}

// Get the SSHLoginFunc that matches client and cluster settings.
sshLoginFunc, err := tc.getSSHLoginFunc(pr)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions lib/config/configuration_test.go
Expand Up @@ -797,6 +797,7 @@ SREzU8onbBsjMg9QDiSf5oJLKvd/Ren+zGY7
LockingMode: constants.LockingModeBestEffort,
AllowPasswordless: types.NewBoolOption(true),
AllowHeadless: types.NewBoolOption(true),
DefaultSessionTTL: types.Duration(apidefaults.CertDuration),
IDP: &types.IdPOptions{
SAML: &types.IdPSAMLOptions{
Enabled: types.NewBoolOption(true),
Expand Down
4 changes: 4 additions & 0 deletions lib/config/fileconf.go
Expand Up @@ -1169,6 +1169,9 @@ type AuthenticationConfig struct {
// DeviceTrust holds settings related to trusted device verification.
// Requires Teleport Enterprise.
DeviceTrust *DeviceTrust `yaml:"device_trust,omitempty"`

// DefaultSessionTTL is the default cluster max session ttl
DefaultSessionTTL types.Duration `yaml:"default_session_ttl"`
}

// Parse returns valid types.AuthPreference instance.
Expand Down Expand Up @@ -1211,6 +1214,7 @@ func (a *AuthenticationConfig) Parse() (types.AuthPreference, error) {
AllowPasswordless: a.Passwordless,
AllowHeadless: a.Headless,
DeviceTrust: dt,
DefaultSessionTTL: a.DefaultSessionTTL,
})
}

Expand Down
11 changes: 11 additions & 0 deletions lib/teleterm/clusters/cluster_auth.go
Expand Up @@ -27,6 +27,7 @@ import (

"github.com/gravitational/teleport/api/client/webclient"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/utils/keys"
api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1"
"github.com/gravitational/teleport/lib/auth"
Expand Down Expand Up @@ -162,6 +163,16 @@ func (c *Cluster) updateClientFromPingResponse(ctx context.Context) (*webclient.
return nil, trace.Wrap(err)
}

if c.clusterClient.KeyTTL == 0 {
c.clusterClient.KeyTTL = pingResp.Auth.DefaultSessionTTL.Duration()
}
// todo(lxea): DELETE IN v15 where the auth is guaranteed to
// send us a valid MaxSessionTTL or the auth is guaranteed to
// interpret 0 duration as the auth's default
if c.clusterClient.KeyTTL == 0 {
c.clusterClient.KeyTTL = defaults.CertDuration
}

return pingResp, nil
}

Expand Down
1 change: 1 addition & 0 deletions lib/web/apiserver.go
Expand Up @@ -1079,6 +1079,7 @@ func getAuthSettings(ctx context.Context, authClient auth.ClientI) (webclient.Au
return webclient.AuthenticationSettings{}, trace.Wrap(err)
}
as.LoadAllCAs = pingResp.LoadAllCAs
as.DefaultSessionTTL = authPreference.GetDefaultSessionTTL()

return as, nil
}
Expand Down
5 changes: 0 additions & 5 deletions tool/tsh/tsh.go
Expand Up @@ -3258,11 +3258,6 @@ func loadClientConfigFromCLIConf(cf *CLIConf, proxy string) (*client.Config, err
return nil, trace.Wrap(err)
}

// apply defaults
if cf.MinsToLive == 0 {
cf.MinsToLive = int32(apidefaults.CertDuration / time.Minute)
}

// split login & host
hostLogin := cf.NodeLogin
hostUser := cf.UserHost
Expand Down
2 changes: 1 addition & 1 deletion tool/tsh/tsh_test.go
Expand Up @@ -785,7 +785,7 @@ func TestMakeClient(t *testing.T) {
require.NoError(t, err)

require.Equal(t, localUser, tc.Config.HostLogin)
require.Equal(t, apidefaults.CertDuration, tc.Config.KeyTTL)
require.Equal(t, time.Duration(0), tc.Config.KeyTTL)

// specific configuration
conf.MinsToLive = 5
Expand Down

0 comments on commit 1165f4b

Please sign in to comment.