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

release-20.2: auth: add autoLogin ability to OIDC configuration #56510

Merged
merged 1 commit into from
Nov 11, 2020
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
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 @@ -137,6 +137,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 @@ -147,6 +148,7 @@ func (s *oidcAuthenticationServer) GetOIDCConf() ui.OIDCUIConf {
return ui.OIDCUIConf{
ButtonText: s.conf.buttonText,
Enabled: s.enabled,
AutoLogin: s.conf.autoLogin,
}
}

Expand All @@ -170,6 +172,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 @@ -483,6 +486,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