Skip to content

Commit

Permalink
[8.6] [Cloud/Security] Fix the server-side import of the contract `Cl…
Browse files Browse the repository at this point in the history
…oudStart` (elastic#149203) (elastic#149251)

# Backport

This will backport the following commits from `main` to `8.6`:
- [[Cloud/Security] Fix the server-side import of the contract
`CloudStart` (elastic#149203)](elastic#149203)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Alejandro Fernández
Haro","email":"alejandro.haro@elastic.co"},"sourceCommit":{"committedDate":"2023-01-19T17:49:34Z","message":"[Cloud/Security]
Fix the server-side import of the contract `CloudStart`
(elastic#149203)\n\nCo-authored-by: Aleh Zasypkin
<aleh.zasypkin@elastic.co>\r\nResolves
https://github.com/elastic/kibana/issues/149204","sha":"f0b5db6f7013275b41bcbdfbaa8b4e0f67b7629d","branchLabelMapping":{"^v8.7.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:Core","release_note:fix","Team:Security","backport:prev-minor","ci:cloud-deploy","v8.7.0"],"number":149203,"url":"https://github.com/elastic/kibana/pull/149203","mergeCommit":{"message":"[Cloud/Security]
Fix the server-side import of the contract `CloudStart`
(elastic#149203)\n\nCo-authored-by: Aleh Zasypkin
<aleh.zasypkin@elastic.co>\r\nResolves
https://github.com/elastic/kibana/issues/149204","sha":"f0b5db6f7013275b41bcbdfbaa8b4e0f67b7629d"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.7.0","labelRegex":"^v8.7.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/149203","number":149203,"mergeCommit":{"message":"[Cloud/Security]
Fix the server-side import of the contract `CloudStart`
(elastic#149203)\n\nCo-authored-by: Aleh Zasypkin
<aleh.zasypkin@elastic.co>\r\nResolves
https://github.com/elastic/kibana/issues/149204","sha":"f0b5db6f7013275b41bcbdfbaa8b4e0f67b7629d"}}]}]
BACKPORT-->

Co-authored-by: Alejandro Fernández Haro <alejandro.haro@elastic.co>
  • Loading branch information
kibanamachine and afharo authored Jan 19, 2023
1 parent 3cfd246 commit 84833d2
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 17 deletions.
1 change: 1 addition & 0 deletions .buildkite/ftr_configs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ enabled:
- x-pack/test/security_api_integration/oidc.config.ts
- x-pack/test/security_api_integration/pki.config.ts
- x-pack/test/security_api_integration/saml.config.ts
- x-pack/test/security_api_integration/saml_cloud.config.ts
- x-pack/test/security_api_integration/session_idle.config.ts
- x-pack/test/security_api_integration/session_invalidate.config.ts
- x-pack/test/security_api_integration/session_lifespan.config.ts
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/cloud/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { PluginInitializerContext } from '@kbn/core/server';
import { CloudPlugin } from './plugin';

export type { CloudSetup } from './plugin';
export type { CloudSetup, CloudStart } from './plugin';
export { config } from './config';
export const plugin = (initializerContext: PluginInitializerContext) => {
return new CloudPlugin(initializerContext);
Expand Down
36 changes: 23 additions & 13 deletions x-pack/plugins/cloud/server/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,23 @@ const baseConfig = {
};

describe('Cloud Plugin', () => {
describe('#setup', () => {
describe('interface', () => {
const setupPlugin = () => {
const initContext = coreMock.createPluginInitializerContext({
...baseConfig,
id: 'cloudId',
cname: 'cloud.elastic.co',
});
const plugin = new CloudPlugin(initContext);
const setupPlugin = () => {
const initContext = coreMock.createPluginInitializerContext({
...baseConfig,
id: 'cloudId',
cname: 'cloud.elastic.co',
});
const plugin = new CloudPlugin(initContext);

const coreSetup = coreMock.createSetup();
const setup = plugin.setup(coreSetup, {});
const coreSetup = coreMock.createSetup();
const setup = plugin.setup(coreSetup, {});
const start = plugin.start();

return { setup };
};
return { setup, start };
};

describe('#setup', () => {
describe('interface', () => {
it('exposes isCloudEnabled', () => {
const { setup } = setupPlugin();
expect(setup.isCloudEnabled).toBe(true);
Expand All @@ -58,4 +59,13 @@ describe('Cloud Plugin', () => {
});
});
});

describe('#start', () => {
describe('interface', () => {
it('exposes isCloudEnabled', () => {
const { start } = setupPlugin();
expect(start.isCloudEnabled).toBe(true);
});
});
});
});
18 changes: 16 additions & 2 deletions x-pack/plugins/cloud/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,17 @@ export interface CloudSetup {
};
}

export class CloudPlugin implements Plugin<CloudSetup> {
/**
* Start contract
*/
export interface CloudStart {
/**
* `true` when running on Elastic Cloud.
*/
isCloudEnabled: boolean;
}

export class CloudPlugin implements Plugin<CloudSetup, CloudStart> {
private readonly config: CloudConfigType;

constructor(private readonly context: PluginInitializerContext) {
Expand Down Expand Up @@ -85,5 +95,9 @@ export class CloudPlugin implements Plugin<CloudSetup> {
};
}

public start() {}
public start() {
return {
isCloudEnabled: getIsCloudEnabled(this.config.id),
};
}
}
2 changes: 1 addition & 1 deletion x-pack/plugins/security/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import type { Subscription } from 'rxjs';
import { map } from 'rxjs/operators';

import type { CloudStart } from '@kbn/cloud-plugin/public';
import type { CloudStart } from '@kbn/cloud-plugin/server';
import type { TypeOf } from '@kbn/config-schema';
import type {
CoreSetup,
Expand Down
55 changes: 55 additions & 0 deletions x-pack/test/security_api_integration/saml_cloud.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 { resolve } from 'path';
import { FtrConfigProviderContext } from '@kbn/test';
import { services } from './services';

export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));

const kibanaPort = xPackAPITestsConfig.get('servers.kibana.port');
const idpPath = resolve(__dirname, './fixtures/saml/idp_metadata.xml');

return {
testFiles: [require.resolve('./tests/saml_cloud')],
servers: xPackAPITestsConfig.get('servers'),
security: { disableTestUser: true },
services,
junit: {
reportName: 'X-Pack Security API Integration Tests (Cloud SAML)',
},

esTestCluster: {
...xPackAPITestsConfig.get('esTestCluster'),
serverArgs: [
...xPackAPITestsConfig.get('esTestCluster.serverArgs'),
'xpack.security.authc.token.enabled=true',
'xpack.security.authc.token.timeout=15s',
'xpack.security.authc.realms.saml.cloud-saml-kibana.order=0',
`xpack.security.authc.realms.saml.cloud-saml-kibana.idp.metadata.path=${idpPath}`,
'xpack.security.authc.realms.saml.cloud-saml-kibana.idp.entity_id=http://www.elastic.co/saml1',
`xpack.security.authc.realms.saml.cloud-saml-kibana.sp.entity_id=http://localhost:${kibanaPort}`,
`xpack.security.authc.realms.saml.cloud-saml-kibana.sp.logout=http://localhost:${kibanaPort}/logout`,
`xpack.security.authc.realms.saml.cloud-saml-kibana.sp.acs=http://localhost:${kibanaPort}/api/security/saml/callback`,
'xpack.security.authc.realms.saml.cloud-saml-kibana.attributes.principal=urn:oid:0.0.7',
],
},

kbnTestServer: {
...xPackAPITestsConfig.get('kbnTestServer'),
serverArgs: [
...xPackAPITestsConfig.get('kbnTestServer.serverArgs'),
'--xpack.cloud.id=ftr_fake_cloud_id',
`--xpack.security.authc.providers=${JSON.stringify({
basic: { 'cloud-basic': { order: 0 } },
saml: { 'cloud-saml-kibana': { order: 1, realm: 'cloud-saml-kibana' } },
})}`,
],
},
};
}
14 changes: 14 additions & 0 deletions x-pack/test/security_api_integration/tests/saml_cloud/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';

export default function ({ loadTestFile }: FtrProviderContext) {
describe('security APIs - Cloud SAML', function () {
loadTestFile(require.resolve('./saml_login'));
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* 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 expect from '@kbn/expect';
import { parse as parseCookie, Cookie } from 'tough-cookie';
import { getSAMLResponse } from '../../fixtures/saml/saml_tools';
import { FtrProviderContext } from '../../ftr_provider_context';

export default function ({ getService }: FtrProviderContext) {
const randomness = getService('randomness');
const supertest = getService('supertestWithoutAuth');
const config = getService('config');

const kibanaServerConfig = config.get('servers.kibana');

function createSAMLResponse(options = {}) {
return getSAMLResponse({
destination: `http://localhost:${kibanaServerConfig.port}/api/security/saml/callback`,
sessionIndex: String(randomness.naturalNumber()),
...options,
});
}

async function checkSessionCookie(sessionCookie: Cookie, username = 'a@b.c') {
const apiResponse = await supertest
.get('/internal/security/me')
.set('kbn-xsrf', 'xxx')
.set('Cookie', sessionCookie.cookieString())
.expect(200);

expect(apiResponse.body.username).to.be(username);
expect(apiResponse.body.elastic_cloud_user).to.be(true);
expect(apiResponse.body.authentication_realm).to.eql({
type: 'saml',
name: 'cloud-saml-kibana',
});
expect(apiResponse.body.authentication_provider).to.eql({
type: 'saml',
name: 'cloud-saml-kibana',
});
expect(apiResponse.body.authentication_type).to.be('token');
}

describe('Cloud SAML authentication', () => {
let sessionCookie: Cookie;

beforeEach(async () => {
// Cloud SAML relies on IdP initiated login.
const samlAuthenticationResponse = await supertest
.post('/api/security/saml/callback')
.send({ SAMLResponse: await createSAMLResponse({ username: 'a@b.c' }) })
.expect(302);

expect(samlAuthenticationResponse.headers.location).to.be('/');

sessionCookie = parseCookie(samlAuthenticationResponse.headers['set-cookie'][0])!;
});

it('should properly set `elastic_cloud_user` user property', async () => {
// Same user, same provider - session ID hasn't changed and cookie should still be valid.
await supertest
.get('/internal/security/me')
.set('kbn-xsrf', 'xxx')
.set('Cookie', sessionCookie.cookieString())
.expect(200);

// Check that all properties of the cloud saml user are properly set.
await checkSessionCookie(sessionCookie);
});
});
}

0 comments on commit 84833d2

Please sign in to comment.