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

Teleport Connect allow SSO hostname #48035

Merged
merged 11 commits into from
Nov 5, 2024
6 changes: 6 additions & 0 deletions api/client/webclient/webclient.go
Joerger marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -516,6 +516,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 @@ -847,6 +850,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 @@ -897,6 +902,7 @@ func (c *Config) Profile() *profile.Profile {
PIVSlot: c.PIVSlot,
SAMLSingleLogoutEnabled: c.SAMLSingleLogoutEnabled,
SSHDialTimeout: c.SSHDialTimeout,
SSOHost: c.SSOHost,
}
}

Expand Down Expand Up @@ -4261,7 +4267,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 @@ -4540,7 +4548,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 @@ -4552,6 +4560,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.OIDC != nil:
ssoURL, err = url.Parse(authSettings.OIDC.IssuerURL)
case authSettings.SAML != nil:
ssoURL, err = url.Parse(authSettings.SAML.SSO)
case authSettings.Github != nil:
ssoURL, err = url.Parse(authSettings.Github.EndpointURL)
Joerger marked this conversation as resolved.
Show resolved Hide resolved
}
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
ravicious marked this conversation as resolved.
Show resolved Hide resolved
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
11 changes: 7 additions & 4 deletions lib/web/apiserver.go
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the backward compatibility story going to look like here wrt SSO MFA? Do we run into a risk of Connect v17 wanting to use SSO MFA but not being able to because the proxy is on an older version which does not send SSOHostname?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SSO MFA is being released in v17 so if we can get this in for v17.0.0 we won't have any issues.

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,7 @@ func samlSettings(connector types.SAMLConnector, cap types.AuthPreference) webcl
Name: connector.GetName(),
Display: connector.GetDisplay(),
SingleLogoutEnabled: connector.GetSingleLogoutURL() != "",
SSO: connector.GetSSO(),
},
// Local fallback / MFA.
SecondFactor: types.LegacySecondFactorFromSecondFactors(cap.GetSecondFactors()),
Expand All @@ -1335,8 +1337,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,23 @@ 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 hostname for SSO login/mfa redirects.
Joerger marked this conversation as resolved.
Show resolved Hide resolved
if (rootCluster.ssoHost) {
allowList.add(rootCluster.ssoHost);
Joerger marked this conversation as resolved.
Show resolved Hide resolved
}

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