Skip to content

Commit

Permalink
[kbn-test] validate isCloud by checking kbnHost (#198025)
Browse files Browse the repository at this point in the history
## Summary

Recently we had issues investigating SAML auth failures against MKI. The
issue was caused by missing `TEST_CLOUD` env var that led to `isCloud`
property to be set to false.

This PR adds `isCloud` validation by checking if `kbnHost` is pointing
to Cloud instance and throwing error about misconfiguration

**How to test:**

Try to run FTR tests against MKI without defining `TEST_CLOUD` env var

---------

Co-authored-by: Aleh Zasypkin <[email protected]>
(cherry picked from commit 413f7c1)
  • Loading branch information
dmlemeshko committed Nov 1, 2024
1 parent d0a6118 commit 2940055
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 13 deletions.
26 changes: 15 additions & 11 deletions packages/kbn-test/src/auth/saml_auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { createSAMLResponse as createMockedSAMLResponse } from '@kbn/mock-idp-utils';
import { ToolingLog } from '@kbn/tooling-log';
import axios, { AxiosResponse } from 'axios';
import util from 'util';
import * as cheerio from 'cheerio';
import { Cookie, parse as parseCookie } from 'tough-cookie';
import Url from 'url';
Expand Down Expand Up @@ -253,23 +254,26 @@ export const finishSAMLHandshake = async ({
}) => {
const encodedResponse = encodeURIComponent(samlResponse);
const url = kbnHost + '/api/security/saml/callback';
const request = {
url,
method: 'post',
data: `SAMLResponse=${encodedResponse}`,
headers: {
'content-type': 'application/x-www-form-urlencoded',
...(sid ? { Cookie: `sid=${sid}` } : {}),
},
validateStatus: () => true,
maxRedirects: 0,
};
let authResponse: AxiosResponse;

try {
authResponse = await axios.request({
url,
method: 'post',
data: `SAMLResponse=${encodedResponse}`,
headers: {
'content-type': 'application/x-www-form-urlencoded',
...(sid ? { Cookie: `sid=${sid}` } : {}),
},
validateStatus: () => true,
maxRedirects: 0,
});
authResponse = await axios.request(request);
} catch (ex) {
log.error('Failed to call SAML callback');
cleanException(url, ex);
// Logging the `Cookie: sid=xxxx` header is safe here since it’s an intermediate, non-authenticated cookie that cannot be reused if leaked.
log.error(`Request sent: ${util.inspect(request)}`);
throw ex;
}

Expand Down
29 changes: 28 additions & 1 deletion packages/kbn-test/src/auth/session_manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ describe('SamlSessionManager', () => {
describe('for cloud session', () => {
const hostOptions = {
protocol: 'https' as 'http' | 'https',
hostname: 'cloud',
hostname: 'my-test-deployment.test.elastic.cloud',
username: 'elastic',
password: 'changeme',
};
Expand Down Expand Up @@ -328,4 +328,31 @@ describe('SamlSessionManager', () => {
expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(0);
});
});

describe(`for cloud session with 'isCloud' set to false`, () => {
const hostOptions = {
protocol: 'http' as 'http' | 'https',
hostname: 'my-test-deployment.test.elastic.cloud',
username: 'elastic',
password: 'changeme',
};
const samlSessionManagerOptions = {
hostOptions,
isCloud: false,
log,
cloudUsersFilePath,
};

beforeEach(() => {
jest.resetAllMocks();
});

test('should throw an error when kbnHost points to a Cloud instance', () => {
const kbnHost = `${hostOptions.protocol}://${hostOptions.hostname}`;
expect(() => new SamlSessionManager(samlSessionManagerOptions)).toThrow(
`SamlSessionManager: 'isCloud' was set to false, but 'kbnHost' appears to be a Cloud instance: ${kbnHost}
Set env variable 'TEST_CLOUD=1' to run FTR against your Cloud deployment`
);
});
});
});
18 changes: 17 additions & 1 deletion packages/kbn-test/src/auth/session_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ export class SamlSessionManager {
private readonly cloudUsersFilePath: string;

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.isCloud = options.isCloud;
this.kbnClient = new KbnClient({
log: this.log,
url: Url.format({
Expand All @@ -73,6 +73,22 @@ export class SamlSessionManager {
this.sessionCache = new Map<Role, Session>();
this.roleToUserMap = new Map<Role, User>();
this.supportedRoles = options.supportedRoles;
this.validateCloudSetting();
}

/**
* Validates if the 'kbnHost' points to Cloud, even if 'isCloud' was set to false
*/
private validateCloudSetting() {
const cloudSubDomains = ['elastic.cloud', 'foundit.no', 'cloud.es.io', 'elastic-cloud.com'];
const isCloudHost = cloudSubDomains.some((domain) => this.kbnHost.endsWith(domain));

if (!this.isCloud && isCloudHost) {
throw new Error(
`SamlSessionManager: 'isCloud' was set to false, but 'kbnHost' appears to be a Cloud instance: ${this.kbnHost}
Set env variable 'TEST_CLOUD=1' to run FTR against your Cloud deployment`
);
}
}

/**
Expand Down

0 comments on commit 2940055

Please sign in to comment.