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

Saml validate asserts issue token validate token #4421

Closed
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
52 changes: 52 additions & 0 deletions config/opensearch_dashboards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,55 @@
#data_source.encryption.wrappingKeyName: 'changeme'
#data_source.encryption.wrappingKeyNamespace: 'changeme'
#data_source.encryption.wrappingKey: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]


# timelion.ui.enabled: true
# server.name: opensearch-dashboards
# server.host: "0.0.0.0"
# opensearch.hosts: http://localhost:9200
# opensearch.ssl.verificationMode: none
# opensearch.username: kibanaserver
# opensearch.password: kibanaserver
# opensearch.requestHeadersWhitelist: ["securitytenant","Authorization"]
# # dashboards_security.multitenancy.enabled: true
# # dashboards_security.multitenancy.tenants.preferred: ["Private", "Global"]
# dashboards_security.readonly_mode.roles: ["kibana_read_only"]
# # dashboards_security.auth.type: "saml"
# server.xsrf.whitelist: [/_plugins/_security/saml/acs,/_opendistro/_security/saml/acs,/_plugins/_security/saml/acs/idpinitiated,/_opendistro/_security/saml/acs/idpinitiated,/_plugins/_security/saml/logout,/_opendistro/_security/saml/logout]




server.host: 0.0.0.0
server.port: 5601
opensearch.hosts: ["http://localhost:9200"]
opensearch.ssl.verificationMode: none
opensearch.username: "kibanaserver"
opensearch.password: "kibanaserver"
opensearch.requestHeadersWhitelist: [ authorization,securitytenant ]

dashboards_security.idp.setting: {
"basicauth_opensearch":
{
"base_redirect_url": "http://localhost:5601"},
"oidc_okta":
{
"base_redirect_url": "http://localhost:5601",
"logout_url": "http://localhost:5601/app/login",
"connect_url": "https://dev-16628832.okta.com/.well-known/openid-configuration",
"client_id": "0oa566po99gotj46m5d7",
"client_secret": "4Gy9_NxFS2Xf97t4GRzkoRlyRAsApRwFcM6Zx9WB",
"scope": "openid profile email",
"verify_hostnames": "false",
"refresh_tokens": "false"},
"oidc_google":
{
"base_redirect_url": "http://localhost:5601",
"logout_url": "http://localhost:5601/app/login",
"connect_url": "https://accounts.google.com/.well-known/openid-configuration",
"client_id": "177403260062-qsvknolof1u4qfmv3qtjti45eps7k3qs.apps.googleusercontent.com",
"client_secret": "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",
"scope": "openid profile email",
"verify_hostnames": "false",
"refresh_tokens": "false"},
}
43 changes: 43 additions & 0 deletions src/plugins/dashboards_security/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export const PLUGIN_ID = 'opensearchDashboardsSecurity';
export const PLUGIN_NAME = 'security-dashboards-plugin';

export const APP_ID_LOGIN = 'login';

export const API_PREFIX = '/api/v1';
export const CONFIGURATION_API_PREFIX = 'configuration';
export const API_ENDPOINT_AUTHINFO = API_PREFIX + '/auth/authinfo';
export const API_ENDPOINT_AUTHTYPE = API_PREFIX + '/auth/type';
export const LOGIN_PAGE_URI = '/app/' + APP_ID_LOGIN;
export const API_AUTH_LOGIN = '/auth/login';
export const API_AUTH_LOGOUT = '/auth/logout';
export const OPENID_AUTH_LOGIN = '/auth/openid/login';
export const SAML_AUTH_LOGIN = '/auth/saml/login';
export const ANONYMOUS_AUTH_LOGIN = '/auth/anonymous';
export const SAML_AUTH_LOGIN_WITH_FRAGMENT = '/auth/saml/captureUrlFragment?nextUrl=%2F';

export const OPENID_AUTH_LOGOUT = '/auth/openid/logout';
export const SAML_AUTH_LOGOUT = '/auth/saml/logout';
export const ANONYMOUS_AUTH_LOGOUT = '/auth/anonymous/logout';

export const AUTH_HEADER_NAME = 'authorization';
export const AUTH_GRANT_TYPE = 'authorization_code';
export const AUTH_RESPONSE_TYPE = 'code';

export const jwtKey = '6aff3042-1327-4f3d-82f0-40a157ac4464';

export const idpCert =
'MIIDzzCCAregAwIBAgIUKizt/svOXO4USLQ3spS2Bn507LYwDQYJKoZIhvcNAQEFBQAwQTEMMAoGA1UECgwDQVdTMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxGjAYBgNVBAMMEU9uZUxvZ2luIEFjY291bnQgMB4XDTIzMDExMzIwMTQzNVoXDTI4MDExMzIwMTQzNVowQTEMMAoGA1UECgwDQVdTMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxGjAYBgNVBAMMEU9uZUxvZ2luIEFjY291bnQgMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwTX1daRM90aJmDCWTL3Iuj4GvK2nHRNZoLP9dzscbFJNMIQEXdyREHSVnFO18KWDfwX3gOgvcuijJUk+r5XCf1oJueUNhme/Q8eSHQe1TOhOVPXuI9BxMyPupeKfmFelIylTNvUoCQo2A/dJURRN2rjz4pOoCqadOlgm2So//J8I/JiZVO6S1YleAjWY5VYOMJMq8QKBBMKkmxok+reA36lmvi2JtUZWpZVo62XVcjP9+uOONyXo7O3VEu8Vwezex2sXFyCm699G1aeRCtHQ3yKmhf0Rm0D+RgZKnG+9i6aeJFTXluBqOrz6CtXtW0SV2NKIeK36EcMH1unlG4/VMwIDAQABo4G+MIG7MAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFKsWx05elVPbItGUYA3SBXVehP7VMHwGA1UdIwR1MHOAFKsWx05elVPbItGUYA3SBXVehP7VoUWkQzBBMQwwCgYDVQQKDANBV1MxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEaMBgGA1UEAwwRT25lTG9naW4gQWNjb3VudCCCFCos7f7LzlzuFEi0N7KUtgZ+dOy2MA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQUFAAOCAQEAh0Kg8BQrOuWO30A6Qj+VL2Ke0/Y96hgdjYxk4zcIwZcIxfb5U733ftF2H0r8RKBYNrWpEmPwa4RnaTqwRaY/pahZ7kznzgMVUMhT9QZe4uNDLu5HgzAuOdhpYk2qv6+GYqcbMNtKPEtTjp0/KwMntgBkn9dPBSiydqojtwh0i2e2rhFh4gBDvuXdHZCcOWCKYm24IOoEI41Q4JIu1jAk6LM3jErcZdx+Lqa9rvSn6jdC6/jwhR1anqqLU9qGIjN99640z/JIOdK8wPei2veLpZbKIDtG/iaSNkdrFhEE1WNXTnnPImQNVgvIT9QdyOLLdzuQ25G3Qraj47JEMm0Xmw==';

export enum AuthType {
BASIC = 'basicauth',
OIDC = 'oidc',
JWT = 'jwt',
SAML = 'saml',
PROXY = 'proxy',
ANONYMOUS = 'anonymous',
}
8 changes: 8 additions & 0 deletions src/plugins/dashboards_security/opensearch_dashboards.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "dashboardsSecurity",
"version": "opensearchDashboards",
"configPath": ["dashboards_security"],
"requiredPlugins": ["navigation"],
"server": true,
"ui": true
}
44 changes: 44 additions & 0 deletions src/plugins/dashboards_security/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "dashboards-security",
"version": "3.0.0.0",
"main": "target/plugins/dashboards-security",
"opensearchDashboards": {
"version": "3.0.0",
"templateVersion": "3.0.0"
},
"license": "Apache-2.0",
"homepage": "https://github.com/opensearch-project/security-dashboards-plugin",
"scripts": {
"plugin-helpers": "node ../../scripts/plugin_helpers",
"osd": "node ../../scripts/osd",
"opensearch": "node ../../scripts/opensearch",
"build": "yarn plugin-helpers build && node build_tools/rename_zip.js",
"start": "node ../../scripts/opensearch-dashboards --dev",
"lint:es": "node ../../scripts/eslint",
"lint:style": "node ../../scripts/stylelint",
"lint": "yarn run lint:es && yarn run lint:style",
"pretest:jest_server": "node ./test/jest_integration/runIdpServer.js &",
"test:jest_server": "node ./test/run_jest_tests.js --config ./test/jest.config.server.js",
"test:jest_ui": "node ./test/run_jest_tests.js --config ./test/jest.config.ui.js"
},
"devDependencies": {
"@elastic/eslint-import-resolver-kibana": "link:../../packages/osd-eslint-import-resolver-opensearch-dashboards",
"@testing-library/react-hooks": "^7.0.2",
"@types/hapi__wreck": "^15.0.1",
"gulp-rename": "2.0.0",
"saml-idp": "^1.2.1",
"selenium-webdriver": "^4.0.0-alpha.7",
"selfsigned": "^2.0.1",
"typescript": "4.0.2",
"saml-encoder-decoder-js": "1.0.1",
"fast-xml-parser": "4.0.15"
},
"dependencies": {
"@hapi/cryptiles": "5.0.0",
"@hapi/wreck": "^17.1.0",
"html-entities": "1.3.1",
"node-saml": "^4.0.0-beta.2",
"@node-saml/passport-saml": "4.0.2",
"jsonwebtoken": "9.0.0"
}
}
25 changes: 25 additions & 0 deletions src/plugins/dashboards_security/public/apps/login/_index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright OpenSearch Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

.login-wrapper {
margin: 10% auto;
width: 350px;
padding: 1rem;
position: relative;
}

.btn-login {
width: 100%;
}
21 changes: 21 additions & 0 deletions src/plugins/dashboards_security/public/apps/login/login-app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import './_index.scss';
// @ts-ignore : Component not used
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { AppMountParameters, CoreStart } from '../../../../../core/public';
import { ClientConfigType } from '../../types';
import { LoginPage } from './login-page';

export function renderApp(
coreStart: CoreStart,
params: AppMountParameters,
config: ClientConfigType
) {
ReactDOM.render(<LoginPage http={coreStart.http} config={config} />, params.element);
return () => ReactDOM.unmountComponentAtNode(params.element);
}
184 changes: 184 additions & 0 deletions src/plugins/dashboards_security/public/apps/login/login-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState } from 'react';
import {
EuiText,
EuiFieldText,
EuiIcon,
EuiSpacer,
EuiButton,
EuiImage,
EuiListGroup,
EuiForm,
EuiFormRow,
} from '@elastic/eui';
import { AuthType } from 'src/plugins/dashboards_security/common';
import { ESMap } from 'typescript';
import { map } from 'bluebird';
import { CoreStart } from '../../../../../core/public';
import { ClientConfigType } from '../../types';
import defaultBrandImage from '../../assets/opensearch_logo_h.svg';
import { validateCurrentPassword } from '../../utils/auth_utils';

interface LoginButtonConfig {
buttonname: string;
showbrandimage: boolean;
brandimage: string;
buttonstyle: string;
}

interface LoginPageDeps {
http: CoreStart['http'];
config: ClientConfigType;
}

function redirect(serverBasePath: string) {
// navigate to nextUrl
const urlParams = new URLSearchParams(window.location.search);
let nextUrl = urlParams.get('nextUrl');
if (!nextUrl || nextUrl.toLowerCase().includes('//')) {
nextUrl = serverBasePath + '/';
}
window.location.href = nextUrl + window.location.hash;
}

export function LoginPage(props: LoginPageDeps) {
const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState('');
const [loginFailed, setloginFailed] = useState(false);
const [loginError, setloginError] = useState('');
const [usernameValidationFailed, setUsernameValidationFailed] = useState(false);
const [passwordValidationFailed, setPasswordValidationFailed] = useState(false);

let errorLabel: any = null;
if (loginFailed) {
errorLabel = (
<EuiText id="error" color="danger" textAlign="center">
<b>{loginError}</b>
</EuiText>
);
}

// @ts-ignore : Parameter 'e' implicitly has an 'any' type.
const handleSubmit = async (e) => {
e.preventDefault();

// Clear errors
setloginFailed(false);
setUsernameValidationFailed(false);
setPasswordValidationFailed(false);

// Form validation
if (username === '') {
setUsernameValidationFailed(true);
return;
}

if (password === '') {
setPasswordValidationFailed(true);
return;
}
try {
await validateCurrentPassword(props.http, username, password);
redirect(props.http.basePath.serverBasePath);
} catch (error) {
setloginFailed(true);
setloginError('Invalid username or password. Please try again.');
return;
}
};

// TODO: Get brand image from server config
return (
<EuiListGroup className="login-wrapper">
{props.config.ui.basicauth.login.showbrandimage && (
<EuiImage
size="fullWidth"
alt=""
url={props.config.ui.basicauth.login.brandimage || defaultBrandImage}
/>
)}
<EuiSpacer size="s" />
<EuiText size="m" textAlign="center">
{props.config.ui.basicauth.login.title || 'Please login to OpenSearch Dashboards'}
</EuiText>
<EuiSpacer size="s" />
<EuiText size="s" textAlign="center">
{props.config.ui.basicauth.login.subtitle ||
'If you have forgotten your username or password, please ask your system administrator'}
</EuiText>
<EuiSpacer size="s" />
<EuiForm component="form">
<EuiFormRow>
<EuiFieldText
data-test-subj="user-name"
placeholder="Username"
prepend={<EuiIcon type="user" />}
onChange={(e) => setUsername(e.target.value)}
value={username}
/>
</EuiFormRow>
<EuiFormRow>
<EuiFieldText
data-test-subj="password"
placeholder="Password"
prepend={<EuiIcon type="lock" />}
type="password"
onChange={(e) => setPassword(e.target.value)}
value={password}
/>
</EuiFormRow>
<EuiFormRow>
<EuiButton
data-test-subj="submit"
fill
size="s"
type="submit"
className={props.config.ui.basicauth.login.buttonstyle || 'btn-login'}
onClick={handleSubmit}
>
Log In
</EuiButton>
</EuiFormRow>
</EuiForm>
<EuiSpacer size="s" />
<EuiFormRow>
<EuiButton
data-test-subj="submit"
size="s"
type="prime"
className={props.config.ui.openid.login.buttonstyle || 'btn-login'}
href="/auth/oidc/okta/login"
iconType={
props.config.ui.openid.login.showbrandimage
? props.config.ui.openid.login.brandimage
: ''
}
>
Login with OKTA (OIDC)
</EuiButton>
</EuiFormRow>
<EuiSpacer size="s" />
<EuiFormRow>
<EuiButton
data-test-subj="submit"
size="s"
type="prime"
className={props.config.ui.openid.login.buttonstyle || 'btn-login'}
href="/auth/oidc/google/login"
iconType={
props.config.ui.openid.login.showbrandimage
? props.config.ui.openid.login.brandimage
: ''
}
>
Login with Google (OIDC)
</EuiButton>
</EuiFormRow>
{errorLabel}
</EuiListGroup>
);
}
Loading