Skip to content

Commit

Permalink
Merge pull request #56510 from dhartunian/backport20.2-55552
Browse files Browse the repository at this point in the history
release-20.2: auth: add autoLogin ability to OIDC configuration
  • Loading branch information
dhartunian committed Nov 11, 2020
2 parents da124a8 + cd8c5df commit 317e252
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 55 deletions.
3 changes: 2 additions & 1 deletion docs/generated/settings/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
<tr><td><code>server.consistency_check.max_rate</code></td><td>byte size</td><td><code>8.0 MiB</code></td><td>the rate limit (bytes/sec) to use for consistency checks; used in conjunction with server.consistency_check.interval to control the frequency of consistency checks. Note that setting this too high can negatively impact performance.</td></tr>
<tr><td><code>server.eventlog.ttl</code></td><td>duration</td><td><code>2160h0m0s</code></td><td>if nonzero, event log entries older than this duration are deleted every 10m0s. Should not be lowered below 24 hours.</td></tr>
<tr><td><code>server.host_based_authentication.configuration</code></td><td>string</td><td><code></code></td><td>host-based authentication configuration to use during connection authentication</td></tr>
<tr><td><code>server.oidc_authentication.button_text</code></td><td>string</td><td><code>Login with your OIDC provider</code></td><td>text to show on button on admin ui login page to login with your OIDC provider (only shown if OIDC is enabled) (this feature is experimental)</td></tr>
<tr><td><code>server.oidc_authentication.autologin</code></td><td>boolean</td><td><code>false</code></td><td>if true, logged-out visitors to the Admin UI will be automatically redirected to the OIDC login endpoint (this feature is experimental)</td></tr>
<tr><td><code>server.oidc_authentication.button_text</code></td><td>string</td><td><code>Login with your OIDC provider</code></td><td>text to show on button on Admin UI login page to login with your OIDC provider (only shown if OIDC is enabled) (this feature is experimental)</td></tr>
<tr><td><code>server.oidc_authentication.claim_json_key</code></td><td>string</td><td><code></code></td><td>sets JSON key of principal to extract from payload after OIDC authentication completes (usually email or sid) (this feature is experimental)</td></tr>
<tr><td><code>server.oidc_authentication.client_id</code></td><td>string</td><td><code></code></td><td>sets OIDC client id (this feature is experimental)</td></tr>
<tr><td><code>server.oidc_authentication.client_secret</code></td><td>string</td><td><code></code></td><td>sets OIDC client secret (this feature is experimental)</td></tr>
Expand Down
10 changes: 10 additions & 0 deletions pkg/ccl/oidcccl/authentication_oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ type oidcAuthenticationConf struct {
claimJSONKey string
principalRegex *regexp.Regexp
buttonText string
autoLogin bool
}

// GetUIConf is used to extract certain parts of the OIDC
Expand All @@ -142,6 +143,7 @@ func (s *oidcAuthenticationServer) GetOIDCConf() ui.OIDCUIConf {
return ui.OIDCUIConf{
ButtonText: s.conf.buttonText,
Enabled: s.enabled,
AutoLogin: s.conf.autoLogin,
}
}

Expand All @@ -165,6 +167,7 @@ func reloadConfigLocked(
// The success of this line is guaranteed by the validation of the setting
principalRegex: regexp.MustCompile(OIDCPrincipalRegex.Get(&st.SV)),
buttonText: OIDCButtonText.Get(&st.SV),
autoLogin: OIDCAutoLogin.Get(&st.SV),
}

if !server.conf.enabled && conf.enabled {
Expand Down Expand Up @@ -433,6 +436,13 @@ var ConfigureOIDC = func(
st,
)
})
OIDCAutoLogin.SetOnChange(&st.SV, func() {
reloadConfig(
ambientCtx.AnnotateCtx(context.Background()),
oidcAuthentication,
st,
)
})

return oidcAuthentication, nil
}
Expand Down
15 changes: 14 additions & 1 deletion pkg/ccl/oidcccl/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
OIDCClaimJSONKeySettingName = baseOIDCSettingName + "claim_json_key"
OIDCPrincipalRegexSettingName = baseOIDCSettingName + "principal_regex"
OIDCButtonTextSettingName = baseOIDCSettingName + "button_text"
OIDCAutoLoginSettingName = baseOIDCSettingName + "autologin"
)

// OIDCEnabled enables or disabled OIDC login for the Admin UI
Expand Down Expand Up @@ -158,9 +159,21 @@ var OIDCPrincipalRegex = func() *settings.StringSetting {
var OIDCButtonText = func() *settings.StringSetting {
s := settings.RegisterPublicStringSetting(
OIDCButtonTextSettingName,
"text to show on button on admin ui login page to login with your OIDC provider "+
"text to show on button on Admin UI login page to login with your OIDC provider "+
"(only shown if OIDC is enabled) (this feature is experimental)",
"Login with your OIDC provider",
)
return s
}()

// OIDCAutoLogin is a boolean that enables automatic redirection to OIDC auth in the Admin
// UI.
var OIDCAutoLogin = func() *settings.BoolSetting {
s := settings.RegisterPublicBoolSetting(
OIDCAutoLoginSettingName,
"if true, logged-out visitors to the Admin UI will be "+
"automatically redirected to the OIDC login endpoint (this feature is experimental)",
false,
)
return s
}()
6 changes: 3 additions & 3 deletions pkg/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1041,7 +1041,7 @@ Binary built without web UI.
expected := fmt.Sprintf(
htmlTemplate,
fmt.Sprintf(
`{"ExperimentalUseLogin":false,"LoginEnabled":false,"LoggedInUser":null,"Tag":"%s","Version":"%s","NodeID":"%d","PasswordLoginEnabled":true,"OIDCLoginEnabled":false,"OIDCButtonText":""}`,
`{"ExperimentalUseLogin":false,"LoginEnabled":false,"LoggedInUser":null,"Tag":"%s","Version":"%s","NodeID":"%d","OIDCAutoLogin":false,"OIDCLoginEnabled":false,"OIDCButtonText":""}`,
build.GetInfo().Tag,
build.VersionPrefix(),
1,
Expand Down Expand Up @@ -1076,7 +1076,7 @@ Binary built without web UI.
{
loggedInClient,
fmt.Sprintf(
`{"ExperimentalUseLogin":true,"LoginEnabled":true,"LoggedInUser":"authentic_user","Tag":"%s","Version":"%s","NodeID":"%d","PasswordLoginEnabled":true,"OIDCLoginEnabled":false,"OIDCButtonText":""}`,
`{"ExperimentalUseLogin":true,"LoginEnabled":true,"LoggedInUser":"authentic_user","Tag":"%s","Version":"%s","NodeID":"%d","OIDCAutoLogin":false,"OIDCLoginEnabled":false,"OIDCButtonText":""}`,
build.GetInfo().Tag,
build.VersionPrefix(),
1,
Expand All @@ -1085,7 +1085,7 @@ Binary built without web UI.
{
loggedOutClient,
fmt.Sprintf(
`{"ExperimentalUseLogin":true,"LoginEnabled":true,"LoggedInUser":null,"Tag":"%s","Version":"%s","NodeID":"%d","PasswordLoginEnabled":true,"OIDCLoginEnabled":false,"OIDCButtonText":""}`,
`{"ExperimentalUseLogin":true,"LoginEnabled":true,"LoggedInUser":null,"Tag":"%s","Version":"%s","NodeID":"%d","OIDCAutoLogin":false,"OIDCLoginEnabled":false,"OIDCButtonText":""}`,
build.GetInfo().Tag,
build.VersionPrefix(),
1,
Expand Down
8 changes: 4 additions & 4 deletions pkg/ui/src/redux/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,17 +150,17 @@ export interface LoginAPIState {
loggedInUser: string;
error: Error;
inProgress: boolean;
displayPasswordLogin: boolean;
displayOIDCButton: boolean;
oidcAutoLogin: boolean;
oidcLoginEnabled: boolean;
oidcButtonText: string;
}

export const emptyLoginState: LoginAPIState = {
loggedInUser: dataFromServer.LoggedInUser,
error: null,
inProgress: false,
displayPasswordLogin: dataFromServer.PasswordLoginEnabled,
displayOIDCButton: dataFromServer.OIDCLoginEnabled,
oidcAutoLogin: dataFromServer.OIDCAutoLogin,
oidcLoginEnabled: dataFromServer.OIDCLoginEnabled,
oidcButtonText: dataFromServer.OIDCButtonText,
};

Expand Down
6 changes: 3 additions & 3 deletions pkg/ui/src/util/dataFromServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export interface DataFromServer {
Tag: string;
Version: string;
NodeID: string;
PasswordLoginEnabled: boolean;
OIDCLoginEnabled: boolean;
OIDCButtonText: string;
OIDCAutoLogin: boolean;
OIDCLoginEnabled: boolean;
OIDCButtonText: string;
}

// Tell TypeScript about `window.dataFromServer`, which is set in a script
Expand Down
65 changes: 24 additions & 41 deletions pkg/ui/src/views/login/loginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import "./loginPage.styl";
import { CockroachLabsLockupIcon, Button, TextInput, PasswordInput } from "src/components";
import { Text, TextTypes } from "src/components";
import ErrorCircle from "assets/error-circle.svg";
import { OIDCLoginConnected } from "src/views/login/oidc";

export interface LoginPageProps {
loginState: LoginAPIState;
Expand All @@ -29,20 +30,6 @@ export interface LoginPageProps {

type Props = LoginPageProps & RouteComponentProps;

const OIDCLoginButton = ({loginState}: {loginState: LoginAPIState}) => {
if (loginState.displayOIDCButton) {
return (
<a href="/oidc/v1/login" >
<Button className="submit-button-oidc" disabled={loginState.inProgress} textAlign={"center"}>
{loginState.oidcButtonText}
</Button>
</a>
);
} else {
return null;
}
};

interface PasswordLoginState {
username?: string;
password?: string;
Expand Down Expand Up @@ -82,32 +69,28 @@ class PasswordLoginForm extends React.Component<LoginPageProps, PasswordLoginSta
const { username, password } = this.state;
const { loginState } = this.props;

if (loginState.displayPasswordLogin) {
return (
<form id="loginForm" onSubmit={this.handleSubmit} className="form-internal" method="post">
<TextInput
name="username"
onChange={this.handleUpdateUsername}
placeholder="Username"
label="Username"
value={username}
/>
<PasswordInput
name="password"
onChange={this.handleUpdatePassword}
placeholder="Password"
label="Password"
value={password}
/>
<Button buttonType="submit" className="submit-button" disabled={loginState.inProgress}
textAlign={"center"}>
{loginState.inProgress ? "Logging in..." : "Log in"}
</Button>
</form>
);
} else {
return null;
}
return (
<form id="loginForm" onSubmit={this.handleSubmit} className="form-internal" method="post">
<TextInput
name="username"
onChange={this.handleUpdateUsername}
placeholder="Username"
label="Username"
value={username}
/>
<PasswordInput
name="password"
onChange={this.handleUpdatePassword}
placeholder="Password"
label="Password"
value={password}
/>
<Button buttonType="submit" className="submit-button" disabled={loginState.inProgress}
textAlign={"center"}>
{loginState.inProgress ? "Logging in..." : "Log in"}
</Button>
</form>
);
}
}

Expand Down Expand Up @@ -162,7 +145,7 @@ export class LoginPage extends React.Component<Props> {
<Text textType={TextTypes.Heading2}>Log in to the Admin UI</Text>
{this.renderError()}
<PasswordLoginForm {...this.props} />
<OIDCLoginButton loginState={loginState} />
<OIDCLoginConnected loginState={loginState} />
</div>
</section>
<section className="section login-page__info">
Expand Down
40 changes: 40 additions & 0 deletions pkg/ui/src/views/login/oidc.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2020 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

import React from "react";

import { LoginAPIState } from "oss/src/redux/login";
import { Button } from "src/components";
import { RouteComponentProps, withRouter } from "react-router-dom";

const OIDC_LOGIN_PATH = "/oidc/v1/login";

const OIDCLoginButton = ({loginState}: {loginState: LoginAPIState}) => {
return (
<a href={OIDC_LOGIN_PATH} >
<Button type="secondary" className="submit-button-oidc" disabled={loginState.inProgress} textAlign={"center"}>
{loginState.oidcButtonText}
</Button>
</a>
);
};

const OIDCLogin: React.FC<{loginState: LoginAPIState} & RouteComponentProps> = (props) => {
const oidcAutoLoginQuery = new URLSearchParams(props.location.search).get("oidc_auto_login");
if (props.loginState.oidcLoginEnabled) {
if (props.loginState.oidcAutoLogin && !(oidcAutoLoginQuery === "false")) {
window.location.replace(OIDC_LOGIN_PATH);
}
return <OIDCLoginButton loginState={props.loginState} />;
}
return null;
};

export const OIDCLoginConnected = withRouter(OIDCLogin);
5 changes: 3 additions & 2 deletions pkg/ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ type indexHTMLArgs struct {
Tag string
Version string
NodeID string
PasswordLoginEnabled bool
OIDCAutoLogin bool
OIDCLoginEnabled bool
OIDCButtonText string
}
Expand All @@ -104,6 +104,7 @@ type indexHTMLArgs struct {
// since that's where all the OIDC configuration is centralized.
type OIDCUIConf struct {
ButtonText string
AutoLogin bool
Enabled bool
}

Expand Down Expand Up @@ -163,7 +164,7 @@ func Handler(cfg Config) http.Handler {
Tag: buildInfo.Tag,
Version: build.VersionPrefix(),
NodeID: cfg.NodeID.String(),
PasswordLoginEnabled: true,
OIDCAutoLogin: oidcConf.AutoLogin,
OIDCLoginEnabled: oidcConf.Enabled,
OIDCButtonText: oidcConf.ButtonText,
}); err != nil {
Expand Down

0 comments on commit 317e252

Please sign in to comment.