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
Allow using JWT credentials to grant API keys. #172444
Changes from 1 commit
05078fb
d762cea
70fb07a
de9107b
a610e4a
e795826
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,66 @@ | ||
/* | ||
* 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 testEndpointsPlugin = resolve(__dirname, '../security_functional/plugins/test_endpoints'); | ||
const jwksPath = require.resolve('@kbn/security-api-integration-helpers/oidc/jwks.json'); | ||
|
||
return { | ||
testFiles: [require.resolve('./tests/api_keys')], | ||
servers: xPackAPITestsConfig.get('servers'), | ||
security: { disableTestUser: true }, | ||
services, | ||
junit: { | ||
reportName: 'X-Pack Security API Integration Tests (Api Keys)', | ||
}, | ||
|
||
esTestCluster: { | ||
...xPackAPITestsConfig.get('esTestCluster'), | ||
serverArgs: [ | ||
...xPackAPITestsConfig.get('esTestCluster.serverArgs'), | ||
'xpack.security.authc.token.enabled=true', | ||
|
||
// JWT WITH shared secret | ||
'xpack.security.authc.realms.jwt.jwt_with_secret.allowed_audiences=elasticsearch', | ||
`xpack.security.authc.realms.jwt.jwt_with_secret.allowed_issuer=https://kibana.elastic.co/jwt/`, | ||
`xpack.security.authc.realms.jwt.jwt_with_secret.allowed_signature_algorithms=[RS256]`, | ||
`xpack.security.authc.realms.jwt.jwt_with_secret.allowed_subjects=elastic-agent`, | ||
`xpack.security.authc.realms.jwt.jwt_with_secret.claims.principal=sub`, | ||
'xpack.security.authc.realms.jwt.jwt_with_secret.client_authentication.type=shared_secret', | ||
`xpack.security.authc.realms.jwt.jwt_with_secret.client_authentication.shared_secret=my_super_secret`, | ||
'xpack.security.authc.realms.jwt.jwt_with_secret.order=0', | ||
`xpack.security.authc.realms.jwt.jwt_with_secret.pkc_jwkset_path=${jwksPath}`, | ||
`xpack.security.authc.realms.jwt.jwt_with_secret.token_type=access_token`, | ||
|
||
// JWT WITHOUT shared secret | ||
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. 🏅 thanks for testing both with and without shared secret |
||
'xpack.security.authc.realms.jwt.jwt_without_secret.allowed_audiences=elasticsearch', | ||
`xpack.security.authc.realms.jwt.jwt_without_secret.allowed_issuer=https://kibana.elastic.co/jwt/no-secret`, | ||
`xpack.security.authc.realms.jwt.jwt_without_secret.allowed_signature_algorithms=[RS256]`, | ||
`xpack.security.authc.realms.jwt.jwt_without_secret.allowed_subjects=elastic-agent-no-secret`, | ||
`xpack.security.authc.realms.jwt.jwt_without_secret.claims.principal=sub`, | ||
'xpack.security.authc.realms.jwt.jwt_without_secret.client_authentication.type=none', | ||
'xpack.security.authc.realms.jwt.jwt_without_secret.order=1', | ||
`xpack.security.authc.realms.jwt.jwt_without_secret.pkc_jwkset_path=${jwksPath}`, | ||
`xpack.security.authc.realms.jwt.jwt_without_secret.token_type=access_token`, | ||
], | ||
}, | ||
|
||
kbnTestServer: { | ||
...xPackAPITestsConfig.get('kbnTestServer'), | ||
serverArgs: [ | ||
...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), | ||
`--plugin-path=${testEndpointsPlugin}`, | ||
], | ||
}, | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/* | ||
* 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 { adminTestUser } from '@kbn/test'; | ||
import { FtrProviderContext } from '../../ftr_provider_context'; | ||
|
||
export default function ({ getService }: FtrProviderContext) { | ||
const supertest = getService('supertestWithoutAuth'); | ||
const esSupertest = getService('esSupertest'); | ||
|
||
describe('Grant API keys', () => { | ||
async function validateApiKey(username: string, encodedApiKey: string) { | ||
const { body: user } = await supertest | ||
.get('/internal/security/me') | ||
.set('kbn-xsrf', 'xxx') | ||
.set('Authorization', `ApiKey ${encodedApiKey}`) | ||
.expect(200); | ||
|
||
expect(user.username).to.eql(username); | ||
expect(user.authentication_provider).to.eql({ name: '__http__', type: 'http' }); | ||
expect(user.authentication_type).to.eql('api_key'); | ||
} | ||
|
||
it('should properly grant API key with `Basic` credentials', async function () { | ||
const credentials = Buffer.from( | ||
`${adminTestUser.username}:${adminTestUser.password}` | ||
).toString('base64'); | ||
|
||
const { body: apiKey } = await supertest | ||
.post('/api_keys/_grant') | ||
.set('Authorization', `Basic ${credentials}`) | ||
.set('kbn-xsrf', 'xxx') | ||
.send({ name: 'my-basic-api-key' }) | ||
.expect(200); | ||
expect(apiKey.name).to.eql('my-basic-api-key'); | ||
|
||
await validateApiKey(adminTestUser.username, apiKey.encoded); | ||
}); | ||
|
||
it('should properly grant API key with `Bearer` credentials', async function () { | ||
const { body: token } = await esSupertest | ||
.post('/_security/oauth2/token') | ||
.send({ grant_type: 'password', ...adminTestUser }) | ||
.expect(200); | ||
|
||
const { body: apiKey } = await supertest | ||
.post('/api_keys/_grant') | ||
.set('Authorization', `Bearer ${token.access_token}`) | ||
.set('kbn-xsrf', 'xxx') | ||
.send({ name: 'my-bearer-api-key' }) | ||
.expect(200); | ||
expect(apiKey.name).to.eql('my-bearer-api-key'); | ||
|
||
await validateApiKey(adminTestUser.username, apiKey.encoded); | ||
}); | ||
|
||
describe('with JWT credentials', function () { | ||
// When we run tests on MKI, JWT realm is configured differently, and we cannot handcraft valid JWTs. We create | ||
// separate `describe` since `this.tags` only works on a test suite level. | ||
this.tags(['skipMKI']); | ||
|
||
it('should properly grant API key (with client authentication)', async function () { | ||
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 should we include negative tests to ensure that end-to-end error handling works correctly if we:
While this should be handled more or less automatically, we've benefited in the past from these types of "redundant" tests. 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. All these tests will fail during authentication stage, so we'll effectively be testing authentication, not |
||
const jsonWebToken = | ||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2tpYmFuYS5lbGFzdGljLmNvL2p3dC8iLCJzdWIiOiJlbGFzdGljLWFnZW50IiwiYXVkIjoiZWxhc3RpY3NlYXJjaCIsIm5hbWUiOiJFbGFzdGljIEFnZW50IiwiaWF0Ijo5NDY2ODQ4MDAsImV4cCI6NDA3MDkwODgwMH0.P7RHKZlLskS5DfVRqoVO4ivoIq9rXl2-GW6hhC9NvTSkwphYivcjpTVcyENZvxTTvJJNqcyx6rF3T-7otTTIHBOZIMhZauc5dob-sqcN_mT2htqm3BpSdlJlz60TBq6diOtlNhV212gQCEJMPZj0MNj7kZRj_GsECrTaU7FU0A3HAzkbdx15vQJMKZiFbbQCVI7-X2J0bZzQKIWfMHD-VgHFwOe6nomT-jbYIXtCBDd6fNj1zTKRl-_uzjVqNK-h8YW1h6tE4xvZmXyHQ1-9yNKZIWC7iEaPkBLaBKQulLU5MvW3AtVDUhzm6--5H1J85JH5QhRrnKYRon7ZW5q1AQ'; | ||
|
||
const { body: apiKey } = await supertest | ||
.post('/api_keys/_grant') | ||
.set('Authorization', `Bearer ${jsonWebToken}`) | ||
.set('ES-Client-Authentication', 'SharedSecret my_super_secret') | ||
.set('kbn-xsrf', 'xxx') | ||
.send({ name: 'my-jwt-secret-api-key' }) | ||
.expect(200); | ||
expect(apiKey.name).to.eql('my-jwt-secret-api-key'); | ||
|
||
await validateApiKey('elastic-agent', apiKey.encoded); | ||
}); | ||
|
||
it('should properly grant API key (without client authentication)', async function () { | ||
const jsonWebToken = | ||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2tpYmFuYS5lbGFzdGljLmNvL2p3dC9uby1zZWNyZXQiLCJzdWIiOiJlbGFzdGljLWFnZW50LW5vLXNlY3JldCIsImF1ZCI6ImVsYXN0aWNzZWFyY2giLCJuYW1lIjoiRWxhc3RpYyBBZ2VudCIsImlhdCI6OTQ2Njg0ODAwLCJleHAiOjQwNzA5MDg4MDB9.OZ_XIDqMmoWr8XqbWE9C04l1NYMsbGXG0zGPdztT-7PuZirzbSvm8z9T7SqbvsujUMn78vpeHx1HyBukrzrBXw2PKeVCa6PGPBtJ_m1fpsCffelHGAD3n2Mu3HanQmdmamHG6JbyLGUwWJ9F31M1xWFAtnMTqP0yeaDOw_9t0WVXHAedVNjvJIrz2X09GHpa9RXxSA0hDuzPotw41kzSrCOhsiBXTNUUNiv4BQ6LNmxbIS6XcXab6LxnQEKtu7XbziaokHKjdZpVAWG8GF8fu0i77GGszNE30RBonYUUPbBrBjhEueK7M8HXTwdHCalRMGsXqD8qS0-TGzii6G-4vg'; | ||
|
||
const { body: apiKey } = await supertest | ||
.post('/api_keys/_grant') | ||
.set('Authorization', `Bearer ${jsonWebToken}`) | ||
.set('kbn-xsrf', 'xxx') | ||
.send({ name: 'my-jwt-api-key' }) | ||
.expect(200); | ||
expect(apiKey.name).to.eql('my-jwt-api-key'); | ||
|
||
await validateApiKey('elastic-agent-no-secret', apiKey.encoded); | ||
}); | ||
}); | ||
}); | ||
} |
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 - Api Keys', function () { | ||
loadTestFile(require.resolve('./grant_api_key')); | ||
}); | ||
} |
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.
note: the time has come 🙂 See https://www.elastic.co/guide/en/elasticsearch/reference/8.11/jwt-auth-realm.html#jwt-realm-configuration