Skip to content

Commit a00a139

Browse files
authored
[v17] Teleport Connect allow SSO hostname (#48467)
* Add SSO provider URLs to ping response. * Add SSOHostname to profile. * Add SSOHostname to teleterm cluster. * Add SSO hostname to connect's proxy host allow list. * Fix lint. * Address comments. * Document use of SSOHostname in the profile. * Use sso host, not hostname. * Ping with connector. * Resolve comments.
1 parent 2ed17ad commit a00a139

File tree

15 files changed

+229
-133
lines changed

15 files changed

+229
-133
lines changed

api/client/webclient/webclient.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,8 @@ type SAMLSettings struct {
464464
Display string `json:"display"`
465465
// SingleLogoutEnabled is whether SAML SLO (single logout) is enabled for this auth connector.
466466
SingleLogoutEnabled bool `json:"singleLogoutEnabled,omitempty"`
467+
// SSO is the URL of the identity provider's SSO service.
468+
SSO string
467469
}
468470

469471
// OIDCSettings contains the Name and Display string for OIDC.
@@ -472,6 +474,8 @@ type OIDCSettings struct {
472474
Name string `json:"name"`
473475
// Display is the display name for the connector.
474476
Display string `json:"display"`
477+
// Issuer URL is the endpoint of the provider
478+
IssuerURL string
475479
}
476480

477481
// GithubSettings contains the Name and Display string for Github connector.
@@ -480,6 +484,8 @@ type GithubSettings struct {
480484
Name string `json:"name"`
481485
// Display is the connector display name
482486
Display string `json:"display"`
487+
// EndpointURL is the endpoint URL.
488+
EndpointURL string
483489
}
484490

485491
// DeviceTrustSettings holds cluster-wide device trust settings that are liable

api/profile/profile.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ type Profile struct {
119119

120120
// SSHDialTimeout is the timeout value that should be used for SSH connections.
121121
SSHDialTimeout time.Duration `yaml:"ssh_dial_timeout,omitempty"`
122+
123+
// SSOHost is the host of the SSO provider used to log in. Clients can check this value, along
124+
// with WebProxyAddr, to determine if a webpage is safe to open. Currently used by Teleport
125+
// Connect in the proxy host allow list.
126+
SSOHost string `yaml:"sso_host,omitempty"`
122127
}
123128

124129
// Copy returns a shallow copy of p, or nil if p is nil.

gen/proto/go/teleport/lib/teleterm/v1/cluster.pb.go

Lines changed: 120 additions & 109 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gen/proto/ts/teleport/lib/teleterm/v1/cluster_pb.ts

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/client/api.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,9 @@ type Config struct {
519519
// HasTouchIDCredentialsFunc allows tests to override touchid.HasCredentials.
520520
// If nil touchid.HasCredentials is used.
521521
HasTouchIDCredentialsFunc func(rpID, user string) bool
522+
523+
// SSOHost is the host of the SSO provider used to log in.
524+
SSOHost string
522525
}
523526

524527
// CachePolicy defines cache policy for local clients
@@ -850,6 +853,8 @@ func (c *Config) LoadProfile(ps ProfileStore, proxyAddr string) error {
850853
c.PIVSlot = profile.PIVSlot
851854
c.SAMLSingleLogoutEnabled = profile.SAMLSingleLogoutEnabled
852855
c.SSHDialTimeout = profile.SSHDialTimeout
856+
c.SSOHost = profile.SSOHost
857+
853858
c.AuthenticatorAttachment, err = parseMFAMode(profile.MFAMode)
854859
if err != nil {
855860
return trace.BadParameter("unable to parse mfa mode in user profile: %v.", err)
@@ -900,6 +905,7 @@ func (c *Config) Profile() *profile.Profile {
900905
PIVSlot: c.PIVSlot,
901906
SAMLSingleLogoutEnabled: c.SAMLSingleLogoutEnabled,
902907
SSHDialTimeout: c.SSHDialTimeout,
908+
SSOHost: c.SSOHost,
903909
}
904910
}
905911

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

4267-
tc.applyAuthSettings(pr.Auth)
4273+
if err := tc.applyAuthSettings(pr.Auth); err != nil {
4274+
return nil, trace.Wrap(err)
4275+
}
42684276

42694277
tc.lastPing = pr
42704278

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

45444552
// applyAuthSettings updates configuration changes based on the advertised
45454553
// authentication settings, overriding existing fields in tc.
4546-
func (tc *TeleportClient) applyAuthSettings(authSettings webclient.AuthenticationSettings) {
4554+
func (tc *TeleportClient) applyAuthSettings(authSettings webclient.AuthenticationSettings) error {
45474555
tc.LoadAllCAs = authSettings.LoadAllCAs
45484556

45494557
// If PIVSlot is not already set, default to the server setting.
@@ -4555,6 +4563,25 @@ func (tc *TeleportClient) applyAuthSettings(authSettings webclient.Authenticatio
45554563
if authSettings.PrivateKeyPolicy != "" && !authSettings.PrivateKeyPolicy.IsSatisfiedBy(tc.PrivateKeyPolicy) {
45564564
tc.PrivateKeyPolicy = authSettings.PrivateKeyPolicy
45574565
}
4566+
4567+
var ssoURL *url.URL
4568+
var err error
4569+
switch {
4570+
case authSettings.SAML != nil:
4571+
ssoURL, err = url.Parse(authSettings.SAML.SSO)
4572+
case authSettings.OIDC != nil:
4573+
ssoURL, err = url.Parse(authSettings.OIDC.IssuerURL)
4574+
case authSettings.Github != nil:
4575+
ssoURL, err = url.Parse(authSettings.Github.EndpointURL)
4576+
}
4577+
if err != nil {
4578+
return trace.Wrap(err)
4579+
}
4580+
if ssoURL != nil {
4581+
tc.SSOHost = ssoURL.Host
4582+
}
4583+
4584+
return nil
45584585
}
45594586

45604587
// AddTrustedCA adds a new CA as trusted CA for this client, used in tests

lib/client/client_store.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ func (s *Store) ReadProfileStatus(profileName string) (*ProfileStatus, error) {
202202
// Set ValidUntil to now to show that the keys are not available.
203203
ValidUntil: time.Now(),
204204
SAMLSingleLogoutEnabled: profile.SAMLSingleLogoutEnabled,
205+
SSOHost: profile.SSOHost,
205206
}, nil
206207
}
207208
return nil, trace.Wrap(err)
@@ -217,6 +218,7 @@ func (s *Store) ReadProfileStatus(profileName string) (*ProfileStatus, error) {
217218
SiteName: profile.SiteName,
218219
KubeProxyAddr: profile.KubeProxyAddr,
219220
SAMLSingleLogoutEnabled: profile.SAMLSingleLogoutEnabled,
221+
SSOHost: profile.SSOHost,
220222
IsVirtual: !onDisk,
221223
})
222224
}

lib/client/profile.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,9 @@ type ProfileStatus struct {
242242
// SAMLSingleLogoutEnabled is whether SAML SLO (single logout) is enabled, this can only be true if this is a SAML SSO session
243243
// using an auth connector with a SAML SLO URL configured.
244244
SAMLSingleLogoutEnabled bool
245+
246+
// SSOHost is the host of the SSO provider used to log in.
247+
SSOHost string
245248
}
246249

247250
// profileOptions contains fields needed to initialize a profile beyond those
@@ -255,6 +258,7 @@ type profileOptions struct {
255258
KubeProxyAddr string
256259
IsVirtual bool
257260
SAMLSingleLogoutEnabled bool
261+
SSOHost string
258262
}
259263

260264
// profileStatueFromKeyRing returns a ProfileStatus for the given key ring and options.
@@ -375,6 +379,7 @@ func profileStatusFromKeyRing(keyRing *KeyRing, opts profileOptions) (*ProfileSt
375379
IsVirtual: opts.IsVirtual,
376380
AllowedResourceIDs: allowedResourceIDs,
377381
SAMLSingleLogoutEnabled: opts.SAMLSingleLogoutEnabled,
382+
SSOHost: opts.SSOHost,
378383
}, nil
379384
}
380385

lib/teleterm/apiserver/handler/handler_clusters.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ func newAPIRootCluster(cluster *clusters.Cluster) *api.Cluster {
105105
Roles: loggedInUser.Roles,
106106
ActiveRequests: loggedInUser.ActiveRequests,
107107
},
108+
SsoHost: cluster.SSOHost,
108109
}
109110

110111
if cluster.GetProfileStatusError() != nil {

lib/teleterm/clusters/cluster.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ type Cluster struct {
6262
clusterClient *client.TeleportClient
6363
// clock is a clock for time-related operations
6464
clock clockwork.Clock
65+
// SSOHost is the host of the SSO provider used to log in.
66+
SSOHost string
6567
}
6668

6769
type ClusterWithDetails struct {

lib/teleterm/clusters/cluster_auth.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,13 @@ func (c *Cluster) LocalLogin(ctx context.Context, user, password, otpToken strin
100100

101101
// SSOLogin logs in a user to the Teleport cluster using supported SSO provider
102102
func (c *Cluster) SSOLogin(ctx context.Context, providerType, providerName string) error {
103+
// Get the ping response for the given auth connector.
104+
c.clusterClient.AuthConnector = providerName
105+
103106
if _, err := c.updateClientFromPingResponse(ctx); err != nil {
104107
return trace.Wrap(err)
105108
}
106109

107-
c.clusterClient.AuthConnector = providerName
108-
109110
if err := c.login(ctx, c.ssoLogin(providerType, providerName)); err != nil {
110111
return trace.Wrap(err)
111112
}
@@ -115,12 +116,13 @@ func (c *Cluster) SSOLogin(ctx context.Context, providerType, providerName strin
115116

116117
// PasswordlessLogin processes passwordless logins for this cluster.
117118
func (c *Cluster) PasswordlessLogin(ctx context.Context, stream api.TerminalService_LoginPasswordlessServer) error {
119+
// Get the ping response for the given auth connector.
120+
c.clusterClient.AuthConnector = constants.PasswordlessConnector
121+
118122
if _, err := c.updateClientFromPingResponse(ctx); err != nil {
119123
return trace.Wrap(err)
120124
}
121125

122-
c.clusterClient.AuthConnector = constants.PasswordlessConnector
123-
124126
if err := c.login(ctx, c.passwordlessLogin(stream)); err != nil {
125127
return trace.Wrap(err)
126128
}

lib/teleterm/clusters/storage.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ func (s *Storage) fromProfile(profileName, leafClusterName string) (*Cluster, *c
245245
}
246246
if status != nil {
247247
cluster.status = *status
248+
cluster.SSOHost = status.SSOHost
248249
}
249250

250251
return cluster, clusterClient, trace.Wrap(err)

lib/web/apiserver.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,8 +1300,9 @@ func oidcSettings(connector types.OIDCConnector, cap types.AuthPreference) webcl
13001300
return webclient.AuthenticationSettings{
13011301
Type: constants.OIDC,
13021302
OIDC: &webclient.OIDCSettings{
1303-
Name: connector.GetName(),
1304-
Display: connector.GetDisplay(),
1303+
Name: connector.GetName(),
1304+
Display: connector.GetDisplay(),
1305+
IssuerURL: connector.GetIssuerURL(),
13051306
},
13061307
// Local fallback / MFA.
13071308
SecondFactor: types.LegacySecondFactorFromSecondFactors(cap.GetSecondFactors()),
@@ -1320,6 +1321,10 @@ func samlSettings(connector types.SAMLConnector, cap types.AuthPreference) webcl
13201321
Name: connector.GetName(),
13211322
Display: connector.GetDisplay(),
13221323
SingleLogoutEnabled: connector.GetSingleLogoutURL() != "",
1324+
// Note that we get the connector's primary SSO field, not the MFA SSO field.
1325+
// These two values are often unique, but should have the same host prefix
1326+
// (e.g. https://dev-813354.oktapreview.com) in reasonable, functional setups.
1327+
SSO: connector.GetSSO(),
13231328
},
13241329
// Local fallback / MFA.
13251330
SecondFactor: types.LegacySecondFactorFromSecondFactors(cap.GetSecondFactors()),
@@ -1335,8 +1340,9 @@ func githubSettings(connector types.GithubConnector, cap types.AuthPreference) w
13351340
return webclient.AuthenticationSettings{
13361341
Type: constants.Github,
13371342
Github: &webclient.GithubSettings{
1338-
Name: connector.GetName(),
1339-
Display: connector.GetDisplay(),
1343+
Name: connector.GetName(),
1344+
Display: connector.GetDisplay(),
1345+
EndpointURL: connector.GetEndpointURL(),
13401346
},
13411347
// Local fallback / MFA.
13421348
SecondFactor: types.LegacySecondFactorFromSecondFactors(cap.GetSecondFactors()),

proto/teleport/lib/teleterm/v1/cluster.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ message Cluster {
6464
// profile_status_error is set if there was an error when reading the profile.
6565
// This allows the app to be usable, when one or more profiles cannot be read.
6666
string profile_status_error = 12;
67+
// sso_host is the host of the SSO provider used to log in.
68+
string sso_host = 13;
6769
}
6870

6971
// ShowResources tells if the cluster can show requestable resources on the resources page.

web/packages/teleterm/src/mainProcess/rootClusterProxyHostAllowList.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,22 +77,32 @@ export function manageRootClusterProxyHostAllowList({
7777

7878
allowList.clear();
7979
for (const rootCluster of rootClusters) {
80-
if (!rootCluster.proxyHost) {
81-
continue;
80+
if (rootCluster.proxyHost) {
81+
let browserProxyHost: string;
82+
try {
83+
browserProxyHost = proxyHostToBrowserProxyHost(rootCluster.proxyHost);
84+
allowList.add(browserProxyHost);
85+
} catch (error) {
86+
logger.error(
87+
'Ran into an error when converting proxy host to browser proxy host',
88+
error
89+
);
90+
}
8291
}
8392

84-
let browserProxyHost: string;
85-
try {
86-
browserProxyHost = proxyHostToBrowserProxyHost(rootCluster.proxyHost);
87-
} catch (error) {
88-
logger.error(
89-
'Ran into an error when converting proxy host to browser proxy host',
90-
error
91-
);
92-
continue;
93+
// Allow the SSO host for SSO login/mfa redirects.
94+
if (rootCluster.ssoHost) {
95+
let browserSsoHost: string;
96+
try {
97+
browserSsoHost = proxyHostToBrowserProxyHost(rootCluster.ssoHost);
98+
allowList.add(browserSsoHost);
99+
} catch (error) {
100+
logger.error(
101+
'Ran into an error when converting sso host to browser sso host',
102+
error
103+
);
104+
}
93105
}
94-
95-
allowList.add(browserProxyHost);
96106
}
97107
};
98108

web/packages/teleterm/src/services/tshd/testHelpers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export const makeRootCluster = (
9191
proxyVersion: '11.1.0',
9292
showResources: ShowResources.REQUESTABLE,
9393
profileStatusError: '',
94+
ssoHost: 'example.auth0.com',
9495
...props,
9596
});
9697

@@ -107,6 +108,7 @@ export const makeLeafCluster = (
107108
proxyVersion: '',
108109
profileStatusError: '',
109110
showResources: ShowResources.UNSPECIFIED,
111+
ssoHost: 'example.auth0.com',
110112
...props,
111113
});
112114

0 commit comments

Comments
 (0)