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

[v17] Teleport Connect allow SSO hostname #48467

Merged
merged 10 commits into from
Nov 6, 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
6 changes: 6 additions & 0 deletions api/client/webclient/webclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,8 @@ type SAMLSettings struct {
Display string `json:"display"`
// SingleLogoutEnabled is whether SAML SLO (single logout) is enabled for this auth connector.
SingleLogoutEnabled bool `json:"singleLogoutEnabled,omitempty"`
// SSO is the URL of the identity provider's SSO service.
SSO string
}

// OIDCSettings contains the Name and Display string for OIDC.
Expand All @@ -472,6 +474,8 @@ type OIDCSettings struct {
Name string `json:"name"`
// Display is the display name for the connector.
Display string `json:"display"`
// Issuer URL is the endpoint of the provider
IssuerURL string
}

// GithubSettings contains the Name and Display string for Github connector.
Expand All @@ -480,6 +484,8 @@ type GithubSettings struct {
Name string `json:"name"`
// Display is the connector display name
Display string `json:"display"`
// EndpointURL is the endpoint URL.
EndpointURL string
}

// DeviceTrustSettings holds cluster-wide device trust settings that are liable
Expand Down
5 changes: 5 additions & 0 deletions api/profile/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ type Profile struct {

// SSHDialTimeout is the timeout value that should be used for SSH connections.
SSHDialTimeout time.Duration `yaml:"ssh_dial_timeout,omitempty"`

// SSOHost is the host of the SSO provider used to log in. Clients can check this value, along
// with WebProxyAddr, to determine if a webpage is safe to open. Currently used by Teleport
// Connect in the proxy host allow list.
SSOHost string `yaml:"sso_host,omitempty"`
}

// Copy returns a shallow copy of p, or nil if p is nil.
Expand Down
229 changes: 120 additions & 109 deletions gen/proto/go/teleport/lib/teleterm/v1/cluster.pb.go

Large diffs are not rendered by default.

16 changes: 15 additions & 1 deletion gen/proto/ts/teleport/lib/teleterm/v1/cluster_pb.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 29 additions & 2 deletions lib/client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,9 @@ type Config struct {
// HasTouchIDCredentialsFunc allows tests to override touchid.HasCredentials.
// If nil touchid.HasCredentials is used.
HasTouchIDCredentialsFunc func(rpID, user string) bool

// SSOHost is the host of the SSO provider used to log in.
SSOHost string
}

// CachePolicy defines cache policy for local clients
Expand Down Expand Up @@ -850,6 +853,8 @@ func (c *Config) LoadProfile(ps ProfileStore, proxyAddr string) error {
c.PIVSlot = profile.PIVSlot
c.SAMLSingleLogoutEnabled = profile.SAMLSingleLogoutEnabled
c.SSHDialTimeout = profile.SSHDialTimeout
c.SSOHost = profile.SSOHost

c.AuthenticatorAttachment, err = parseMFAMode(profile.MFAMode)
if err != nil {
return trace.BadParameter("unable to parse mfa mode in user profile: %v.", err)
Expand Down Expand Up @@ -900,6 +905,7 @@ func (c *Config) Profile() *profile.Profile {
PIVSlot: c.PIVSlot,
SAMLSingleLogoutEnabled: c.SAMLSingleLogoutEnabled,
SSHDialTimeout: c.SSHDialTimeout,
SSOHost: c.SSOHost,
}
}

Expand Down Expand Up @@ -4264,7 +4270,9 @@ You may use the --skip-version-check flag to bypass this check.
// cached, there is no need to do this test again.
tc.TLSRoutingConnUpgradeRequired = client.IsALPNConnUpgradeRequired(ctx, tc.WebProxyAddr, tc.InsecureSkipVerify)

tc.applyAuthSettings(pr.Auth)
if err := tc.applyAuthSettings(pr.Auth); err != nil {
return nil, trace.Wrap(err)
}

tc.lastPing = pr

Expand Down Expand Up @@ -4543,7 +4551,7 @@ func (tc *TeleportClient) applyProxySettings(proxySettings webclient.ProxySettin

// applyAuthSettings updates configuration changes based on the advertised
// authentication settings, overriding existing fields in tc.
func (tc *TeleportClient) applyAuthSettings(authSettings webclient.AuthenticationSettings) {
func (tc *TeleportClient) applyAuthSettings(authSettings webclient.AuthenticationSettings) error {
tc.LoadAllCAs = authSettings.LoadAllCAs

// If PIVSlot is not already set, default to the server setting.
Expand All @@ -4555,6 +4563,25 @@ func (tc *TeleportClient) applyAuthSettings(authSettings webclient.Authenticatio
if authSettings.PrivateKeyPolicy != "" && !authSettings.PrivateKeyPolicy.IsSatisfiedBy(tc.PrivateKeyPolicy) {
tc.PrivateKeyPolicy = authSettings.PrivateKeyPolicy
}

var ssoURL *url.URL
var err error
switch {
case authSettings.SAML != nil:
ssoURL, err = url.Parse(authSettings.SAML.SSO)
case authSettings.OIDC != nil:
ssoURL, err = url.Parse(authSettings.OIDC.IssuerURL)
case authSettings.Github != nil:
ssoURL, err = url.Parse(authSettings.Github.EndpointURL)
}
if err != nil {
return trace.Wrap(err)
}
if ssoURL != nil {
tc.SSOHost = ssoURL.Host
}

return nil
}

// AddTrustedCA adds a new CA as trusted CA for this client, used in tests
Expand Down
2 changes: 2 additions & 0 deletions lib/client/client_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ func (s *Store) ReadProfileStatus(profileName string) (*ProfileStatus, error) {
// Set ValidUntil to now to show that the keys are not available.
ValidUntil: time.Now(),
SAMLSingleLogoutEnabled: profile.SAMLSingleLogoutEnabled,
SSOHost: profile.SSOHost,
}, nil
}
return nil, trace.Wrap(err)
Expand All @@ -217,6 +218,7 @@ func (s *Store) ReadProfileStatus(profileName string) (*ProfileStatus, error) {
SiteName: profile.SiteName,
KubeProxyAddr: profile.KubeProxyAddr,
SAMLSingleLogoutEnabled: profile.SAMLSingleLogoutEnabled,
SSOHost: profile.SSOHost,
IsVirtual: !onDisk,
})
}
Expand Down
5 changes: 5 additions & 0 deletions lib/client/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,9 @@ type ProfileStatus struct {
// SAMLSingleLogoutEnabled is whether SAML SLO (single logout) is enabled, this can only be true if this is a SAML SSO session
// using an auth connector with a SAML SLO URL configured.
SAMLSingleLogoutEnabled bool

// SSOHost is the host of the SSO provider used to log in.
SSOHost string
}

// profileOptions contains fields needed to initialize a profile beyond those
Expand All @@ -255,6 +258,7 @@ type profileOptions struct {
KubeProxyAddr string
IsVirtual bool
SAMLSingleLogoutEnabled bool
SSOHost string
}

// profileStatueFromKeyRing returns a ProfileStatus for the given key ring and options.
Expand Down Expand Up @@ -375,6 +379,7 @@ func profileStatusFromKeyRing(keyRing *KeyRing, opts profileOptions) (*ProfileSt
IsVirtual: opts.IsVirtual,
AllowedResourceIDs: allowedResourceIDs,
SAMLSingleLogoutEnabled: opts.SAMLSingleLogoutEnabled,
SSOHost: opts.SSOHost,
}, nil
}

Expand Down
1 change: 1 addition & 0 deletions lib/teleterm/apiserver/handler/handler_clusters.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ func newAPIRootCluster(cluster *clusters.Cluster) *api.Cluster {
Roles: loggedInUser.Roles,
ActiveRequests: loggedInUser.ActiveRequests,
},
SsoHost: cluster.SSOHost,
}

if cluster.GetProfileStatusError() != nil {
Expand Down
2 changes: 2 additions & 0 deletions lib/teleterm/clusters/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ type Cluster struct {
clusterClient *client.TeleportClient
// clock is a clock for time-related operations
clock clockwork.Clock
// SSOHost is the host of the SSO provider used to log in.
SSOHost string
}

type ClusterWithDetails struct {
Expand Down
10 changes: 6 additions & 4 deletions lib/teleterm/clusters/cluster_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,13 @@ func (c *Cluster) LocalLogin(ctx context.Context, user, password, otpToken strin

// SSOLogin logs in a user to the Teleport cluster using supported SSO provider
func (c *Cluster) SSOLogin(ctx context.Context, providerType, providerName string) error {
// Get the ping response for the given auth connector.
c.clusterClient.AuthConnector = providerName

if _, err := c.updateClientFromPingResponse(ctx); err != nil {
return trace.Wrap(err)
}

c.clusterClient.AuthConnector = providerName

if err := c.login(ctx, c.ssoLogin(providerType, providerName)); err != nil {
return trace.Wrap(err)
}
Expand All @@ -115,12 +116,13 @@ func (c *Cluster) SSOLogin(ctx context.Context, providerType, providerName strin

// PasswordlessLogin processes passwordless logins for this cluster.
func (c *Cluster) PasswordlessLogin(ctx context.Context, stream api.TerminalService_LoginPasswordlessServer) error {
// Get the ping response for the given auth connector.
c.clusterClient.AuthConnector = constants.PasswordlessConnector

if _, err := c.updateClientFromPingResponse(ctx); err != nil {
return trace.Wrap(err)
}

c.clusterClient.AuthConnector = constants.PasswordlessConnector

if err := c.login(ctx, c.passwordlessLogin(stream)); err != nil {
return trace.Wrap(err)
}
Expand Down
1 change: 1 addition & 0 deletions lib/teleterm/clusters/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ func (s *Storage) fromProfile(profileName, leafClusterName string) (*Cluster, *c
}
if status != nil {
cluster.status = *status
cluster.SSOHost = status.SSOHost
}

return cluster, clusterClient, trace.Wrap(err)
Expand Down
14 changes: 10 additions & 4 deletions lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1300,8 +1300,9 @@ func oidcSettings(connector types.OIDCConnector, cap types.AuthPreference) webcl
return webclient.AuthenticationSettings{
Type: constants.OIDC,
OIDC: &webclient.OIDCSettings{
Name: connector.GetName(),
Display: connector.GetDisplay(),
Name: connector.GetName(),
Display: connector.GetDisplay(),
IssuerURL: connector.GetIssuerURL(),
},
// Local fallback / MFA.
SecondFactor: types.LegacySecondFactorFromSecondFactors(cap.GetSecondFactors()),
Expand All @@ -1320,6 +1321,10 @@ func samlSettings(connector types.SAMLConnector, cap types.AuthPreference) webcl
Name: connector.GetName(),
Display: connector.GetDisplay(),
SingleLogoutEnabled: connector.GetSingleLogoutURL() != "",
// Note that we get the connector's primary SSO field, not the MFA SSO field.
// These two values are often unique, but should have the same host prefix
// (e.g. https://dev-813354.oktapreview.com) in reasonable, functional setups.
SSO: connector.GetSSO(),
},
// Local fallback / MFA.
SecondFactor: types.LegacySecondFactorFromSecondFactors(cap.GetSecondFactors()),
Expand All @@ -1335,8 +1340,9 @@ func githubSettings(connector types.GithubConnector, cap types.AuthPreference) w
return webclient.AuthenticationSettings{
Type: constants.Github,
Github: &webclient.GithubSettings{
Name: connector.GetName(),
Display: connector.GetDisplay(),
Name: connector.GetName(),
Display: connector.GetDisplay(),
EndpointURL: connector.GetEndpointURL(),
},
// Local fallback / MFA.
SecondFactor: types.LegacySecondFactorFromSecondFactors(cap.GetSecondFactors()),
Expand Down
2 changes: 2 additions & 0 deletions proto/teleport/lib/teleterm/v1/cluster.proto
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ message Cluster {
// profile_status_error is set if there was an error when reading the profile.
// This allows the app to be usable, when one or more profiles cannot be read.
string profile_status_error = 12;
// sso_host is the host of the SSO provider used to log in.
string sso_host = 13;
}

// ShowResources tells if the cluster can show requestable resources on the resources page.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,22 +77,32 @@ export function manageRootClusterProxyHostAllowList({

allowList.clear();
for (const rootCluster of rootClusters) {
if (!rootCluster.proxyHost) {
continue;
if (rootCluster.proxyHost) {
let browserProxyHost: string;
try {
browserProxyHost = proxyHostToBrowserProxyHost(rootCluster.proxyHost);
allowList.add(browserProxyHost);
} catch (error) {
logger.error(
'Ran into an error when converting proxy host to browser proxy host',
error
);
}
}

let browserProxyHost: string;
try {
browserProxyHost = proxyHostToBrowserProxyHost(rootCluster.proxyHost);
} catch (error) {
logger.error(
'Ran into an error when converting proxy host to browser proxy host',
error
);
continue;
// Allow the SSO host for SSO login/mfa redirects.
if (rootCluster.ssoHost) {
let browserSsoHost: string;
try {
browserSsoHost = proxyHostToBrowserProxyHost(rootCluster.ssoHost);
allowList.add(browserSsoHost);
} catch (error) {
logger.error(
'Ran into an error when converting sso host to browser sso host',
error
);
}
}

allowList.add(browserProxyHost);
}
};

Expand Down
2 changes: 2 additions & 0 deletions web/packages/teleterm/src/services/tshd/testHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export const makeRootCluster = (
proxyVersion: '11.1.0',
showResources: ShowResources.REQUESTABLE,
profileStatusError: '',
ssoHost: 'example.auth0.com',
...props,
});

Expand All @@ -107,6 +108,7 @@ export const makeLeafCluster = (
proxyVersion: '',
profileStatusError: '',
showResources: ShowResources.UNSPECIFIED,
ssoHost: 'example.auth0.com',
...props,
});

Expand Down
Loading