Skip to content

Commit

Permalink
Merge branch 'release/1.18.x' into docs/fix-build-1.18.x
Browse files Browse the repository at this point in the history
  • Loading branch information
fairclothjm authored Feb 3, 2025
2 parents 6f35b56 + a85745e commit 65d9b69
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 21 deletions.
3 changes: 3 additions & 0 deletions changelog/29416.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
ui (enterprise): Fixes token renewal to ensure capability checks are performed in the relevant namespace, resolving 'Not authorized' errors for resources that users have permission to access.
```
17 changes: 11 additions & 6 deletions ui/app/services/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,15 +246,14 @@ export default Service.extend({
// haven't set a value yet
// all of the typeof checks are necessary because the root namespace is ''
let userRootNamespace = namespace_path && namespace_path.replace(/\/$/, '');
// if we're logging in with token and there's no namespace_path, we can assume
// renew-self does not return namespace_path, so we manually setting in renew().
// so if we're logging in with token and there's no namespace_path, we can assume
// that the token belongs to the root namespace
if (backend === 'token' && !userRootNamespace) {
userRootNamespace = '';
}
if (typeof userRootNamespace === 'undefined') {
if (this.authData) {
userRootNamespace = this.authData.userRootNamespace;
}
if (typeof userRootNamespace === 'undefined' && this.authData) {
userRootNamespace = this.authData.userRootNamespace;
}
if (typeof userRootNamespace === 'undefined') {
userRootNamespace = currentNamespace;
Expand Down Expand Up @@ -374,7 +373,13 @@ export default Service.extend({
return this.renewCurrentToken().then(
(resp) => {
this.isRenewing = false;
return this.persistAuthData(tokenName, resp.data || resp.auth);
const namespacePath = this.namespaceService.path;
const response = resp.data || resp.auth;
// renew-self does not return namespace_path, so manually add it if it exists
if (!response?.namespace_path && namespacePath) {
response.namespace_path = namespacePath;
}
return this.persistAuthData(tokenName, response);
},
(e) => {
this.isRenewing = false;
Expand Down
103 changes: 92 additions & 11 deletions ui/tests/acceptance/auth-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ import { click, currentURL, visit, waitUntil, find, fillIn } from '@ember/test-h
import { setupMirage } from 'ember-cli-mirage/test-support';
import { allSupportedAuthBackends, supportedAuthBackends } from 'vault/helpers/supported-auth-backends';
import VAULT_KEYS from 'vault/tests/helpers/vault-keys';
import {
createNS,
createPolicyCmd,
mountAuthCmd,
mountEngineCmd,
runCmd,
} from 'vault/tests/helpers/commands';
import { login, loginMethod, loginNs, logout } from 'vault/tests/helpers/auth/auth-helpers';
import { AUTH_FORM } from 'vault/tests/helpers/auth/auth-form-selectors';
import { v4 as uuidv4 } from 'uuid';
import { GENERAL } from '../helpers/general-selectors';

const AUTH_FORM = {
method: '[data-test-select=auth-method]',
token: '[data-test-token]',
login: '[data-test-auth-submit]',
};
const ENT_AUTH_METHODS = ['saml'];
const { rootToken } = VAULT_KEYS;

Expand All @@ -39,10 +45,10 @@ module('Acceptance | auth', function (hooks) {

test('it clears token when changing selected auth method', async function (assert) {
await visit('/vault/auth');
await fillIn(AUTH_FORM.token, 'token');
await fillIn(AUTH_FORM.input('token'), 'token');
await fillIn(AUTH_FORM.method, 'github');
await fillIn(AUTH_FORM.method, 'token');
assert.dom(AUTH_FORM.token).hasNoValue('it clears the token value when toggling methods');
assert.dom(AUTH_FORM.input('token')).hasNoValue('it clears the token value when toggling methods');
});

module('it sends the right payload when authenticating', function (hooks) {
Expand Down Expand Up @@ -202,10 +208,85 @@ module('Acceptance | auth', function (hooks) {
() => new Error('should not call renew-self directly after logging in')
);

await visit('/vault/auth');
await fillIn(AUTH_FORM.method, 'token');
await fillIn(AUTH_FORM.token, rootToken);
await click('[data-test-auth-submit]');
await login(rootToken);
assert.strictEqual(currentURL(), '/vault/dashboard');
});

module('Enterprise', function (hooks) {
hooks.beforeEach(async function () {
const uid = uuidv4();
this.ns = `admin-${uid}`;
// log in to root to create namespace
await login();
await runCmd(createNS(this.ns), false);
// login to namespace, mount userpass, create policy and user
await loginNs(this.ns);
this.db = `database-${uid}`;
this.userpass = `userpass-${uid}`;
this.user = 'bob';
this.policyName = `policy-${this.userpass}`;
this.policy = `
path "${this.db}/" {
capabilities = ["list"]
}
path "${this.db}/roles" {
capabilities = ["read","list"]
}
`;
await runCmd([
mountAuthCmd('userpass', this.userpass),
mountEngineCmd('database', this.db),
createPolicyCmd(this.policyName, this.policy),
`write auth/${this.userpass}/users/${this.user} password=${this.user} token_policies=${this.policyName}`,
]);
return await logout();
});

hooks.afterEach(async function () {
await visit(`/vault/logout?namespace=${this.ns}`);
await fillIn(AUTH_FORM.namespaceInput, ''); // clear login form namespace input
await login();
await runCmd([`delete sys/namespaces/${this.ns}`], false);
});

// this test is specifically to cover a token renewal bug within namespaces
// namespace_path isn't returned by the renew-self response and so the auth service was
// incorrectly setting userRootNamespace to '' (which denotes 'root')
// making subsequent capability checks fail because they would not be queried with the appropriate namespace header
// if this test fails because a POST /v1/sys/capabilities-self returns a 403, then we have a problem!
test('it sets namespace when renewing token', async function (assert) {
await login();
await runCmd([
mountAuthCmd('userpass', this.userpass),
mountEngineCmd('database', this.db),
createPolicyCmd(this.policyName, this.policy),
`write auth/${this.userpass}/users/${this.user} password=${this.user} token_policies=${this.policyName}`,
]);

const options = { username: this.user, password: this.user, 'auth-form-mount-path': this.userpass };

// login as user just to get token (this is the only way to generate a token in the UI right now..)
await loginMethod('userpass', options, { toggleOptions: true, ns: this.ns });
await click('[data-test-user-menu-trigger=""]');
const token = find('[data-test-copy-button]').getAttribute('data-test-copy-button');

// login with token to reproduce bug
await loginNs(this.ns, token);
await visit(`/vault/secrets/${this.db}/overview?namespace=${this.ns}`);
assert
.dom('[data-test-overview-card="Roles"]')
.hasText('Roles Create new', 'database overview renders');
// renew token
await click('[data-test-user-menu-trigger=""]');
await click('[data-test-user-menu-item="renew token"]');
// navigate out and back to overview tab to re-request capabilities
await click(GENERAL.secretTab('Roles'));
await click(GENERAL.tab('overview'));
assert.strictEqual(
currentURL(),
`/vault/secrets/${this.db}/overview?namespace=${this.ns}`,
'it navigates to database overview'
);
});
});
});
1 change: 1 addition & 0 deletions ui/tests/helpers/auth/auth-form-selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

export const AUTH_FORM = {
method: '[data-test-select=auth-method]',
form: '[data-test-auth-form]',
login: '[data-test-auth-submit]',
tabs: (method: string) => (method ? `[data-test-auth-method="${method}"]` : '[data-test-auth-method]'),
Expand Down
25 changes: 25 additions & 0 deletions ui/tests/helpers/auth/auth-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { AUTH_FORM } from 'vault/tests/helpers/auth/auth-form-selectors';

const { rootToken } = VAULT_KEYS;

// LOGIN WITH TOKEN
export const login = async (token = rootToken) => {
// make sure we're always logged out and logged back in
await logout();
Expand All @@ -17,6 +18,30 @@ export const login = async (token = rootToken) => {
return await click(AUTH_FORM.login);
};

// LOGIN WITH NON-TOKEN methods
/*
inputValues are for filling in the form values
the key completes to the input's test selector and fills it in with the corresponding value
for example: { username: 'bob', password: 'my-password', 'auth-form-mount-path': 'userpasss1' };
*/
export const loginMethod = async (
methodType: string,
inputValues: object,
{ toggleOptions = false, ns = '' }
) => {
// make sure we're always logged out and logged back in
await logout();
await visit(`/vault/auth?with=${methodType}`);

if (ns) await fillIn(AUTH_FORM.namespaceInput, ns);
if (toggleOptions) await click(AUTH_FORM.moreOptions);

for (const [input, value] of Object.entries(inputValues)) {
await fillIn(AUTH_FORM.input(input), value);
}
return click(AUTH_FORM.login);
};

export const logout = async () => {
// make sure we're always logged out and logged back in
await visit('/vault/logout');
Expand Down
2 changes: 1 addition & 1 deletion website/content/docs/upgrading/upgrade-to-1.16.x.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,4 @@ more details, and information about opt-out.
@include 'known-issues/manual-entity-merge-does-not-persist.mdx'


@include 'known-issues/database-skip-static-role-rotation.mdx
@include 'known-issues/database-skip-static-role-rotation.mdx'
3 changes: 1 addition & 2 deletions website/content/docs/upgrading/upgrade-to-1.17.x.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -208,5 +208,4 @@ more details, and information about opt-out.

@include 'known-issues/duplicate-hsm-key.mdx'

@include 'known-issues/database-skip-static-role-rotation.mdx

@include 'known-issues/database-skip-static-role-rotation.mdx'
2 changes: 1 addition & 1 deletion website/content/docs/upgrading/upgrade-to-1.18.x.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,4 @@ more details, and information about opt-out.

@include 'known-issues/duplicate-hsm-key.mdx'

@include 'known-issues/database-skip-static-role-rotation.mdx
@include 'known-issues/database-skip-static-role-rotation.mdx'

0 comments on commit 65d9b69

Please sign in to comment.