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

[Security Solution] Login refactor #172472

Merged
merged 7 commits into from
Dec 5, 2023
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
158 changes: 18 additions & 140 deletions x-pack/test/security_solution_cypress/cypress/tasks/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import * as yaml from 'js-yaml';
import { LoginState } from '@kbn/security-plugin/common/login_state';
import type { SecurityRoleName } from '@kbn/security-solution-plugin/common/test';
import { KNOWN_SERVERLESS_ROLE_DEFINITIONS } from '@kbn/security-solution-plugin/common/test';
Expand All @@ -18,160 +16,36 @@ import {
} from '../env_var_names_constants';
import { API_HEADERS, rootRequest } from './api_calls/common';

/**
* Credentials in the `kibana.dev.yml` config file will be used to authenticate
* with Kibana when credentials are not provided via environment variables
*/
const KIBANA_DEV_YML_PATH = '../../../config/kibana.dev.yml';

/**
* The configuration path in `kibana.dev.yml` to the username to be used when
* authenticating with Kibana.
*/
const ELASTICSEARCH_USERNAME_CONFIG_PATH = 'config.elasticsearch.username';

/**
* The configuration path in `kibana.dev.yml` to the password to be used when
* authenticating with Kibana.
*/
const ELASTICSEARCH_PASSWORD_CONFIG_PATH = 'config.elasticsearch.password';

/**
* Authenticates with Kibana using, if specified, credentials specified by
* environment variables. The credentials in `kibana.dev.yml` will be used
* for authentication when the environment variables are unset.
*
* To speed the execution of tests, prefer this non-interactive authentication,
* which is faster than authentication via Kibana's interactive login page.
*/
export const login = (role?: SecurityRoleName): void => {
if (role != null) {
loginWithRole(role);
} else if (credentialsProvidedByEnvironment()) {
loginViaEnvironmentCredentials();
} else {
loginViaConfig();
}
};

export interface User {
username: string;
password: string;
}

export const loginWithUser = (user: User): void => {
loginWithUsernameAndPassword(user.username, user.password);
};

/**
* Builds a URL with basic auth using the passed in user.
*
* @param user the user information to build the basic auth with
* @param route string route to visit
*/
export const constructUrlWithUser = (user: User, route: string): string => {
const url = Cypress.config().baseUrl;
const kibana = new URL(String(url));
const hostname = kibana.hostname;
const username = user.username;
const password = user.password;
const protocol = kibana.protocol.replace(':', '');
const port = kibana.port;

const path = `${route.startsWith('/') ? '' : '/'}${route}`;
const strUrl = `${protocol}://${username}:${password}@${hostname}:${port}${path}`;
const builtUrl = new URL(strUrl);

cy.log(`origin: ${builtUrl.href}`);
return builtUrl.href;
export const defaultUser: User = {
username: Cypress.env(ELASTICSEARCH_USERNAME),
password: Cypress.env(ELASTICSEARCH_PASSWORD),
};

/**
* Authenticates with a predefined role
*
* @param role role name
*/
const loginWithRole = (role: SecurityRoleName): void => {
export const getEnvAuth = (role: SecurityRoleName): User => {
if (
(Cypress.env(IS_SERVERLESS) || Cypress.env(CLOUD_SERVERLESS)) &&
!(role in KNOWN_SERVERLESS_ROLE_DEFINITIONS)
) {
throw new Error(`An attempt to log in with unsupported by Serverless role "${role}".`);
}

const password = 'changeme';

cy.log(`origin: ${Cypress.config().baseUrl}`);
loginWithUsernameAndPassword(role, password);
};

/**
* Returns `true` if the credentials used to login to Kibana are provided
* via environment variables
*/
const credentialsProvidedByEnvironment = (): boolean =>
Cypress.env(ELASTICSEARCH_USERNAME) != null && Cypress.env(ELASTICSEARCH_PASSWORD) != null;

/**
* Authenticates with Kibana by reading credentials from the
* `CYPRESS_ELASTICSEARCH_USERNAME` and `CYPRESS_ELASTICSEARCH_PASSWORD`
* environment variables, and POSTing the username and password directly to
* Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed).
*/
const loginViaEnvironmentCredentials = (): void => {
cy.log(
`Authenticating via environment credentials from the \`CYPRESS_${ELASTICSEARCH_USERNAME}\` and \`CYPRESS_${ELASTICSEARCH_PASSWORD}\` environment variables`
);

const username = Cypress.env(ELASTICSEARCH_USERNAME);
const password = Cypress.env(ELASTICSEARCH_PASSWORD);
loginWithUsernameAndPassword(username, password);
};

/**
* Authenticates with Kibana by reading credentials from the
* `kibana.dev.yml` file and POSTing the username and password directly to
* Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed).
*/
const loginViaConfig = (): void => {
cy.log(
`Authenticating via config credentials \`${ELASTICSEARCH_USERNAME_CONFIG_PATH}\` and \`${ELASTICSEARCH_PASSWORD_CONFIG_PATH}\` from \`${KIBANA_DEV_YML_PATH}\``
);

// read the login details from `kibana.dev.yaml`
cy.readFile(KIBANA_DEV_YML_PATH).then((kibanaDevYml) => {
const { username, password } = yaml.safeLoad(kibanaDevYml);
loginWithUsernameAndPassword(username, password);
});
};

/**
* Get the configured auth details that were used to spawn cypress
*
* @returns the default Elasticsearch username and password for this environment
*/
export const getEnvAuth = (): User => {
if (credentialsProvidedByEnvironment()) {
return {
username: Cypress.env(ELASTICSEARCH_USERNAME),
password: Cypress.env(ELASTICSEARCH_PASSWORD),
};
} else {
let user: User = { username: '', password: '' };
cy.readFile(KIBANA_DEV_YML_PATH).then((devYml) => {
const config = yaml.safeLoad(devYml);
user = { username: config.elasticsearch.username, password: config.elasticsearch.password };
});

return user;
}
const user: User = {
username: role,
password: 'changeme',
};
return user;
};

export const logout = (): void => {
cy.visit(LOGOUT_URL);
export const login = (role?: SecurityRoleName): void => {
const user = role ? getEnvAuth(role) : defaultUser;
loginWithUser(user);
};

const loginWithUsernameAndPassword = (username: string, password: string): void => {
export const loginWithUser = (user: User): void => {
const baseUrl = Cypress.config().baseUrl;
if (!baseUrl) {
throw Error(`Cypress config baseUrl not set!`);
Expand All @@ -192,9 +66,13 @@ const loginWithUsernameAndPassword = (username: string, password: string): void
providerType: basicProvider?.type,
providerName: basicProvider?.name,
currentURL: '/',
params: { username, password },
params: { username: user.username, password: user.password },
},
headers: API_HEADERS,
});
});
};

export const logout = (): void => {
cy.visit(LOGOUT_URL);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { constructUrlWithUser, getEnvAuth } from './login';
import { API_AUTH } from './api_calls/common';

interface User {
username: string;
Expand Down Expand Up @@ -183,14 +183,14 @@ const getUserInfo = (user: User): UserInfo => ({
});

export const createUsersAndRoles = (users: User[], roles: Role[]) => {
const envUser = getEnvAuth();
for (const role of roles) {
cy.log(`Creating role: ${JSON.stringify(role)}`);
cy.request({
body: role.privileges,
headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' },
method: 'PUT',
url: constructUrlWithUser(envUser, `/api/security/role/${role.name}`),
auth: API_AUTH,
url: `/api/security/role/${role.name}`,
})
.its('status')
.should('eql', 204);
Expand All @@ -209,21 +209,22 @@ export const createUsersAndRoles = (users: User[], roles: Role[]) => {
},
headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' },
method: 'POST',
url: constructUrlWithUser(envUser, `/internal/security/users/${user.username}`),
auth: API_AUTH,
url: `/internal/security/users/${user.username}`,
})
.its('status')
.should('eql', 200);
}
};

export const deleteUsersAndRoles = (users: User[], roles: Role[]) => {
const envUser = getEnvAuth();
for (const user of users) {
cy.log(`Deleting user: ${JSON.stringify(user)}`);
cy.request({
headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' },
method: 'DELETE',
url: constructUrlWithUser(envUser, `/internal/security/users/${user.username}`),
auth: API_AUTH,
url: `/internal/security/users/${user.username}`,
failOnStatusCode: false,
})
.its('status')
Expand All @@ -235,7 +236,8 @@ export const deleteUsersAndRoles = (users: User[], roles: Role[]) => {
cy.request({
headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' },
method: 'DELETE',
url: constructUrlWithUser(envUser, `/api/security/role/${role.name}`),
auth: API_AUTH,
url: `/api/security/role/${role.name}`,
failOnStatusCode: false,
})
.its('status')
Expand Down
Loading