-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[ftr] move SAML auth to kbn-test #172678
[ftr] move SAML auth to kbn-test #172678
Changes from 7 commits
bf8c7f9
3ecba70
cdbf959
30a9fa1
b931c6c
09f7893
d217a4c
82f750b
eaf24d6
740ab77
3f46466
fac981e
7ef742e
692b618
27afd47
63537f9
568d02a
2ece8e7
93664d5
405dde8
c4ead20
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
import { REPO_ROOT } from '@kbn/repo-info'; | ||
import * as fs from 'fs'; | ||
import { resolve } from 'path'; | ||
import { User } from './session_manager'; | ||
|
||
export const getProjectType = (serverArgs: string[]) => { | ||
const svlArg = serverArgs.filter((arg) => arg.startsWith('--serverless')); | ||
if (svlArg.length === 0) { | ||
throw new Error('--serverless argument is missing in kbnTestServer.serverArgs'); | ||
} | ||
return svlArg[0].split('=')[1]; | ||
}; | ||
|
||
/** | ||
* Loads cloud users from '.ftr/role_users.json' | ||
* QAF prepares the file for CI pipelines, make sure to add it manually for local run | ||
*/ | ||
export const readCloudUsersFromFile = (): Array<[string, User]> => { | ||
const cloudRoleUsersFilePath = resolve(REPO_ROOT, '.ftr', 'role_users.json'); | ||
if (!fs.existsSync(cloudRoleUsersFilePath)) { | ||
throw new Error(`Please define user roles with email/password in ${cloudRoleUsersFilePath}`); | ||
} | ||
const data = fs.readFileSync(cloudRoleUsersFilePath, 'utf8'); | ||
if (data.length === 0) { | ||
throw new Error(`'${cloudRoleUsersFilePath}' is empty: no roles are defined`); | ||
} | ||
|
||
return Object.entries(JSON.parse(data)) as Array<[string, User]>; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
export { SAMLSessionManager } from './session_manager'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
import { ToolingLog } from '@kbn/tooling-log'; | ||
import Url from 'url'; | ||
import type { Config } from '../functional_test_runner'; | ||
import { KbnClient } from '../kbn_client'; | ||
import { getProjectType, readCloudUsersFromFile } from './helper'; | ||
import { createCloudSAMLSession, createLocalSAMLSession, Session } from './saml_auth'; | ||
|
||
export interface User { | ||
readonly email: string; | ||
readonly password: string; | ||
} | ||
|
||
export type Role = string; | ||
|
||
export class SAMLSessionManager { | ||
private readonly config: Config; | ||
private readonly log: ToolingLog; | ||
private readonly sessionCache; | ||
private readonly isCloud: boolean; | ||
private readonly kbnHost: string; | ||
private readonly kbnClient: KbnClient; | ||
private readonly roleToUserMap: Map<string, User>; | ||
private readonly svlProjectType: string; | ||
constructor(config: Config, log: ToolingLog, isCloud: boolean) { | ||
this.config = config; | ||
this.log = log; | ||
this.sessionCache = new Map<Role, Session>(); | ||
this.isCloud = isCloud; | ||
this.roleToUserMap = new Map<string, User>(); | ||
|
||
const hostOptions = { | ||
protocol: this.config.get('servers.kibana.protocol'), | ||
hostname: this.config.get('servers.kibana.hostname'), | ||
port: isCloud ? undefined : this.config.get('servers.kibana.port'), | ||
}; | ||
this.kbnHost = Url.format(hostOptions); | ||
this.kbnClient = new KbnClient({ | ||
log: this.log, | ||
url: Url.format({ | ||
...hostOptions, | ||
auth: `${this.config.get('servers.kibana.username')}:${this.config.get( | ||
'servers.kibana.password' | ||
)}`, | ||
}), | ||
}); | ||
this.svlProjectType = getProjectType(this.config.get('kbnTestServer.serverArgs')); | ||
} | ||
|
||
// we should split packages/kbn-es/src/serverless_resources/roles.yml into 3 different files | ||
|
||
// getLocalUsers = () => { | ||
// const rolesDefinitionFilePath = resolve( | ||
// REPO_ROOT, | ||
// 'packages/kbn-es/src/serverless_resources/roles.yml' | ||
// ); | ||
// const roles: string[] = Object.keys(loadYaml(fs.readFileSync(rolesDefinitionFilePath, 'utf8'))); | ||
// }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this commented out intentionally? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking to split There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: Currently folks can specify non-existing role in the test and I believe tests will pass locally/Kibana CI. But it will fail on MKI pipeline since that role does not exist in real Cloud env. |
||
|
||
private getCloudUsers = () => { | ||
// Loading cloud users on the first call | ||
if (this.roleToUserMap.size === 0) { | ||
const data = readCloudUsersFromFile(); | ||
for (const [roleName, user] of data) { | ||
this.roleToUserMap.set(roleName, user); | ||
} | ||
} | ||
|
||
return this.roleToUserMap; | ||
}; | ||
|
||
private getCloudUserByRole = (role: string) => { | ||
if (this.getCloudUsers().has(role)) { | ||
return this.getCloudUsers().get(role)!; | ||
} else { | ||
throw new Error(`User with '${role}' role is not defined for ${this.svlProjectType} project`); | ||
} | ||
}; | ||
|
||
private getSessionByRole = async (role: string) => { | ||
if (this.sessionCache.has(role)) { | ||
return this.sessionCache.get(role)!; | ||
} | ||
|
||
let session: Session; | ||
|
||
if (this.isCloud) { | ||
this.log.debug(`new SAML authentication with '${role}' role`); | ||
const kbnVersion = await this.kbnClient.version.get(); | ||
const { email, password } = this.getCloudUserByRole(role); | ||
session = await createCloudSAMLSession({ | ||
email, | ||
password, | ||
kbnHost: this.kbnHost, | ||
kbnVersion, | ||
log: this.log, | ||
}); | ||
} else { | ||
this.log.debug(`new fake SAML authentication with '${role}' role`); | ||
session = await createLocalSAMLSession({ | ||
username: `elastic_${role}`, | ||
email: `elastic_${role}@elastic.co`, | ||
fullname: `test ${role}`, | ||
role, | ||
kbnHost: this.kbnHost, | ||
log: this.log, | ||
}); | ||
} | ||
|
||
this.sessionCache.set(role, session); | ||
return session; | ||
}; | ||
|
||
async getApiCredentialsForRole(role: string) { | ||
const session = await this.getSessionByRole(role); | ||
return { Cookie: `sid=${session.getCookieValue()}` }; | ||
} | ||
|
||
async getSessionCookieForRole(role: string) { | ||
const session = await this.getSessionByRole(role); | ||
return session.getCookieValue(); | ||
} | ||
|
||
async getUserData(role: string) { | ||
const { email, fullname } = await this.getSessionByRole(role); | ||
return { email, fullname }; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
import { ToolingLog } from '@kbn/tooling-log'; | ||
|
||
export interface CloudSamlSessionParams { | ||
kbnHost: string; | ||
kbnVersion: string; | ||
email: string; | ||
password: string; | ||
log: ToolingLog; | ||
} | ||
|
||
export interface LocalSamlSessionParams { | ||
kbnHost: string; | ||
email: string; | ||
username: string; | ||
fullname: string; | ||
role: string; | ||
log: ToolingLog; | ||
} | ||
|
||
export interface CreateSamlSessionParams { | ||
hostname: string; | ||
email: string; | ||
password: string; | ||
log: ToolingLog; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,5 +32,6 @@ | |
"@kbn/babel-register", | ||
"@kbn/repo-packages", | ||
"@kbn/core-saved-objects-api-server", | ||
"@kbn/mock-idp-plugin", | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { SAMLSessionManager } from '@kbn/test'; | ||
import { FtrProviderContext } from '../../functional/ftr_provider_context'; | ||
|
||
export function SvlUserManagerProvider({ getService }: FtrProviderContext) { | ||
const config = getService('config'); | ||
const log = getService('log'); | ||
const isCloud = !!process.env.TEST_CLOUD; | ||
// Sharing the instance within FTR config run means cookies are persistent for each role between tests. | ||
const sessionManager = new SAMLSessionManager(config, log, isCloud); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if Cypress test runner has access to new SAMLSessionManager(kbnHost, auth, log, isCloud) where const kbnHost = {
protocol: config.get('servers.kibana.protocol'),
hostname: config.get('servers.kibana.hostname'),
port: isCloud ? undefined : config.get('servers.kibana.port'),
};
const auth = { username: config.get('servers.kibana.username')}, password: config.get('servers.kibana.password') } @MadameSheema @pheyos wdyt? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe the alternative is the best one for Cypress usage. Thanks :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed in 82f750b |
||
|
||
return sessionManager; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: I see this function isn't used anywhere yet, is it just a leftover or is it reserved for some future use?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remove it in fac981e. Originally I planned to split svl roles yml into 3 files and validate roles per project, but decided to move it to the follow-up PR.