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
3 changes: 3 additions & 0 deletions api/profile/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ type Profile struct {

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

// SSOHostname is the hostname of the SSO provider used to login..
Joerger marked this conversation as resolved.
Show resolved Hide resolved
SSOHostname string `yaml:"sso_hostname,omitempty"`
}

// Copy returns a shallow copy of p, or nil if p is nil.
Expand Down
237 changes: 124 additions & 113 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

// SSOHostname is the hostname of the SSO provider used to login..
SSOHostname 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.SSOHostname = profile.SSOHostname

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,
SSOHostname: c.SSOHostname,
}
}

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.SSOHostname = ssoURL.Hostname()
}

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,
SSOHostname: profile.SSOHostname,
}, 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,
SSOHostname: profile.SSOHostname,
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

// SSOHostname is the hostname of the SSO provider used to login..
SSOHostname string
Joerger marked this conversation as resolved.
Show resolved Hide resolved
}

// 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
SSOHostname 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,
SSOHostname: opts.SSOHostname,
}, 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,
},
SsoHostname: cluster.SSOHostname,
}

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
// SSOHostname is the hostname of the SSO provider used to login..
SSOHostname string
}

type ClusterWithDetails struct {
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.SSOHostname = status.SSOHostname
}

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_hostname is the hostname of the SSO provider used to login..
string sso_hostname = 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,24 @@ 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);
} catch (error) {
logger.error(
'Ran into an error when converting proxy host to browser proxy host',
error
);
continue;
}
allowList.add(browserProxyHost);
Joerger marked this conversation as resolved.
Show resolved Hide resolved
}

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.ssoHostname) {
allowList.add(rootCluster.ssoHostname);
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 @@ -92,6 +92,7 @@ export const makeRootCluster = (
showResources: ShowResources.REQUESTABLE,
profileStatusError: '',
...props,
ssoHostname: 'example.auth0.com',
Joerger marked this conversation as resolved.
Show resolved Hide resolved
});

export const makeLeafCluster = (
Expand All @@ -108,6 +109,7 @@ export const makeLeafCluster = (
profileStatusError: '',
showResources: ShowResources.UNSPECIFIED,
...props,
ssoHostname: 'example.auth0.com',
Joerger marked this conversation as resolved.
Show resolved Hide resolved
});

export const makeLoggedInUser = (
Expand Down
Loading