-
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 11 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,30 @@ | ||
/* | ||
* 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 * as fs from 'fs'; | ||
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]; | ||
}; | ||
|
||
export const readCloudUsersFromFile = (filePath: string): Array<[string, User]> => { | ||
if (!fs.existsSync(filePath)) { | ||
throw new Error(`Please define user roles with email/password in ${filePath}`); | ||
} | ||
const data = fs.readFileSync(filePath, 'utf8'); | ||
if (data.length === 0) { | ||
throw new Error(`'${filePath}' 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,13 @@ | ||
/* | ||
* 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, | ||
type SamlSessionManagerOptions, | ||
type HostOptions, | ||
} from './session_manager'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
/* | ||
* 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 { ToolingLog } from '@kbn/tooling-log'; | ||
import { resolve } from 'path'; | ||
import Url from 'url'; | ||
import { KbnClient } from '../kbn_client'; | ||
import { readCloudUsersFromFile } from './helper'; | ||
import { createCloudSAMLSession, createLocalSAMLSession, Session } from './saml_auth'; | ||
|
||
export interface User { | ||
readonly email: string; | ||
readonly password: string; | ||
} | ||
|
||
export type Role = string; | ||
|
||
export interface HostOptions { | ||
protocol: 'http' | 'https'; | ||
hostname: string; | ||
port?: number; | ||
username: string; | ||
password: string; | ||
} | ||
|
||
export interface SamlSessionManagerOptions { | ||
hostOptions: HostOptions; | ||
isCloud: boolean; | ||
log: ToolingLog; | ||
} | ||
|
||
/** | ||
* Manages cookies assosiated with user roles | ||
dmlemeshko marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
export class SamlSessionManager { | ||
private readonly isCloud: boolean; | ||
private readonly kbnHost: string; | ||
private readonly kbnClient: KbnClient; | ||
private readonly log: ToolingLog; | ||
private readonly roleToUserMap: Map<Role, User>; | ||
private readonly sessionCache: Map<Role, Session>; | ||
private readonly userRoleFilePath = resolve(REPO_ROOT, '.ftr', 'role_users.json'); | ||
|
||
constructor(options: SamlSessionManagerOptions) { | ||
this.isCloud = options.isCloud; | ||
this.log = options.log; | ||
const hostOptionsWithoutAuth = { | ||
protocol: options.hostOptions.protocol, | ||
hostname: options.hostOptions.hostname, | ||
port: options.hostOptions.port, | ||
}; | ||
this.kbnHost = Url.format(hostOptionsWithoutAuth); | ||
this.kbnClient = new KbnClient({ | ||
log: this.log, | ||
url: Url.format({ | ||
...hostOptionsWithoutAuth, | ||
auth: `${options.hostOptions.username}:${options.hostOptions.password}`, | ||
}), | ||
}); | ||
this.sessionCache = new Map<Role, Session>(); | ||
this.roleToUserMap = new Map<Role, User>(); | ||
} | ||
|
||
/** | ||
* Loads cloud users from '.ftr/role_users.json' | ||
* QAF prepares the file for CI pipelines, make sure to add it manually for local run | ||
*/ | ||
private getCloudUsers = () => { | ||
if (this.roleToUserMap.size === 0) { | ||
const data = readCloudUsersFromFile(this.userRoleFilePath); | ||
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`); | ||
} | ||
}; | ||
|
||
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 cloud 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,30 @@ | ||
/* | ||
* 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({ | ||
hostOptions: { | ||
protocol: config.get('servers.kibana.protocol'), | ||
hostname: config.get('servers.kibana.hostname'), | ||
port: isCloud ? undefined : config.get('servers.kibana.port'), | ||
username: config.get('servers.kibana.username'), | ||
password: config.get('servers.kibana.password'), | ||
Comment on lines
+22
to
+23
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. question: just for my understanding: what are these credentials for? 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. @azasypkin it turns out we need to create instance of KbnClient to fetch Kibana version and for cloud we pass credentials to the client. |
||
}, | ||
log, | ||
isCloud, | ||
}); | ||
|
||
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.