UI: Fix aws credential generation sending ttl value when off (#27366)

* do not send ttl if unset for aws credentials

* test coverage

* remove comment

* add changelog

* Update aws test, cancel button is secondary
This commit is contained in:
Chelsea Shaw
2024-06-05 14:56:54 -05:00
committed by GitHub
parent 74f1c4a618
commit 67fc1fab4c
8 changed files with 78 additions and 23 deletions

3
changelog/27366.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:bug
ui: Fix a bug where disabling TTL on the AWS credential form would still send TTL value
```

View File

@@ -16,7 +16,7 @@ export default ApplicationAdapter.extend({
if (roleType === 'iam_user') { if (roleType === 'iam_user') {
method = 'GET'; method = 'GET';
} else { } else {
if (ttl !== undefined) { if (ttl) {
data.ttl = ttl; data.ttl = ttl;
} }
if (roleType === 'assumed_role' && roleArn) { if (roleType === 'assumed_role' && roleArn) {

View File

@@ -49,6 +49,7 @@ export default Model.extend({
editType: 'ttl', editType: 'ttl',
defaultValue: '3600s', defaultValue: '3600s',
setDefault: true, setDefault: true,
ttlOffValue: '',
label: 'TTL', label: 'TTL',
helpText: helpText:
'Specifies the TTL for the use of the STS token. Valid only when credential_type is assumed_role, federation_token, or session_token.', 'Specifies the TTL for the use of the STS token. Valid only when credential_type is assumed_role, federation_token, or session_token.',
@@ -66,7 +67,7 @@ export default Model.extend({
iam_user: ['credentialType'], iam_user: ['credentialType'],
assumed_role: ['credentialType', 'ttl', 'roleArn'], assumed_role: ['credentialType', 'ttl', 'roleArn'],
federation_token: ['credentialType', 'ttl'], federation_token: ['credentialType', 'ttl'],
session_token: ['ttl'], session_token: ['credentialType', 'ttl'],
}; };
if (this.accessKey || this.securityToken) { if (this.accessKey || this.securityToken) {
return expandAttributeMeta(this, DISPLAY_FIELDS.slice(0)); return expandAttributeMeta(this, DISPLAY_FIELDS.slice(0));

View File

@@ -147,6 +147,7 @@
<Hds::Button <Hds::Button
@text="Cancel" @text="Cancel"
@route="vault.cluster.secrets.backend.list-root" @route="vault.cluster.secrets.backend.list-root"
@color="secondary"
@model={{this.backendPath}} @model={{this.backendPath}}
data-test-secret-generate-cancel={{true}} data-test-secret-generate-cancel={{true}}
/> />

View File

@@ -158,7 +158,13 @@ export default class FormFieldComponent extends Component {
@action @action
setAndBroadcastTtl(value) { setAndBroadcastTtl(value) {
const alwaysSendValue = this.valuePath === 'expiry' || this.valuePath === 'safetyBuffer'; const alwaysSendValue = this.valuePath === 'expiry' || this.valuePath === 'safetyBuffer';
const valueToSet = value.enabled === true || alwaysSendValue ? `${value.seconds}s` : 0; const attrOptions = this.args.attr.options || {};
let valueToSet = 0;
if (value.enabled || alwaysSendValue) {
valueToSet = `${value.seconds}s`;
} else if (Object.keys(attrOptions).includes('ttlOffValue')) {
valueToSet = attrOptions.ttlOffValue;
}
this.setAndBroadcast(`${valueToSet}`); this.setAndBroadcast(`${valueToSet}`);
} }
@action @action

View File

@@ -3,16 +3,18 @@
* SPDX-License-Identifier: BUSL-1.1 * SPDX-License-Identifier: BUSL-1.1
*/ */
import { click, fillIn, findAll, currentURL, find, settled, waitUntil } from '@ember/test-helpers'; import { click, fillIn, currentURL, find, settled, waitUntil } from '@ember/test-helpers';
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit'; import { setupApplicationTest } from 'ember-qunit';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import authPage from 'vault/tests/pages/auth'; import authPage from 'vault/tests/pages/auth';
import enablePage from 'vault/tests/pages/settings/mount-secret-backend'; import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Acceptance | aws secret backend', function (hooks) { module('Acceptance | aws secret backend', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
setupMirage(hooks);
hooks.beforeEach(function () { hooks.beforeEach(function () {
this.uid = uuidv4(); this.uid = uuidv4();
@@ -20,9 +22,13 @@ module('Acceptance | aws secret backend', function (hooks) {
}); });
test('aws backend', async function (assert) { test('aws backend', async function (assert) {
assert.expect(12);
const path = `aws-${this.uid}`; const path = `aws-${this.uid}`;
const roleName = 'awsrole'; const roleName = 'awsrole';
this.server.post(`/${path}/creds/${roleName}`, (_, req) => {
const payload = JSON.parse(req.requestBody);
assert.deepEqual(payload, { role_arn: 'foobar' }, 'does not send TTL when unchecked');
return {};
});
await enablePage.enable('aws', path); await enablePage.enable('aws', path);
await settled(); await settled();
@@ -31,28 +37,25 @@ module('Acceptance | aws secret backend', function (hooks) {
await click('[data-test-secret-backend-configure]'); await click('[data-test-secret-backend-configure]');
assert.strictEqual(currentURL(), `/vault/settings/secrets/configure/${path}`); assert.strictEqual(currentURL(), `/vault/settings/secrets/configure/${path}`);
assert.ok(findAll('[data-test-aws-root-creds-form]').length, 'renders the empty root creds form'); assert.dom('[data-test-aws-root-creds-form]').exists();
assert.ok(findAll('[data-test-aws-link="root-creds"]').length, 'renders the root creds link'); assert.dom('[data-test-aws-link="root-creds"]').exists();
assert.ok(findAll('[data-test-aws-link="leases"]').length, 'renders the leases config link'); assert.dom('[data-test-aws-link="leases"]').exists();
await fillIn('[data-test-aws-input="accessKey"]', 'foo'); await fillIn('[data-test-aws-input="accessKey"]', 'foo');
await fillIn('[data-test-aws-input="secretKey"]', 'bar'); await fillIn('[data-test-aws-input="secretKey"]', 'bar');
await click('[data-test-aws-input="root-save"]'); await click('[data-test-aws-input="root-save"]');
assert.ok( assert
find('[data-test-flash-message]').textContent.trim(), .dom('[data-test-flash-message]:last-of-type [data-test-flash-message-body]')
`The backend configuration saved successfully!` .includesText(`The backend configuration saved successfully!`);
);
await click('[data-test-aws-link="leases"]'); await click('[data-test-aws-link="leases"]');
await click('[data-test-aws-input="lease-save"]'); await click('[data-test-aws-input="lease-save"]');
assert
assert.ok( .dom('[data-test-flash-message]:last-of-type [data-test-flash-message-body]')
find('[data-test-flash-message]').textContent.trim(), .includesText(`The backend configuration saved successfully!`);
`The backend configuration saved successfully!`
);
await click('[data-test-backend-view-link]'); await click('[data-test-backend-view-link]');
@@ -60,10 +63,7 @@ module('Acceptance | aws secret backend', function (hooks) {
await click('[data-test-secret-create]'); await click('[data-test-secret-create]');
assert.ok( assert.dom('[data-test-secret-header]').includesText('AWS Role');
find('[data-test-secret-header]').textContent.includes('AWS Role'),
`aws: renders the create page`
);
await fillIn('[data-test-input="name"]', roleName); await fillIn('[data-test-input="name"]', roleName);
@@ -78,7 +78,19 @@ module('Acceptance | aws secret backend', function (hooks) {
await click(`[data-test-secret-breadcrumb="${path}"] a`); await click(`[data-test-secret-breadcrumb="${path}"] a`);
assert.strictEqual(currentURL(), `/vault/secrets/${path}/list`); assert.strictEqual(currentURL(), `/vault/secrets/${path}/list`);
assert.ok(findAll(`[data-test-secret-link="${roleName}"]`).length, `aws: role shows in the list`); assert.dom(`[data-test-secret-link="${roleName}"]`).exists();
// check that generates credentials flow is correct
await click(`[data-test-secret-link="${roleName}"]`);
assert.dom('h1').hasText('Generate AWS Credentials');
assert.dom('[data-test-input="credentialType"]').hasValue('iam_user');
await fillIn('[data-test-input="credentialType"]', 'assumed_role');
await click('[data-test-ttl-toggle="TTL"]');
assert.dom('[data-test-ttl-toggle="TTL"]').isNotChecked();
await fillIn('[data-test-input="roleArn"]', 'foobar');
await click('[data-test-secret-generate]');
assert.dom('[data-test-warning]').exists('Shows access warning after generation');
await click('[data-test-secret-generate-back]');
//and delete //and delete
await click(`[data-test-secret-link="${roleName}"] [data-test-popup-menu-trigger]`); await click(`[data-test-secret-link="${roleName}"] [data-test-popup-menu-trigger]`);

View File

@@ -127,14 +127,41 @@ module('Integration | Component | form field', function (hooks) {
}); });
test('it renders: editType ttl', async function (assert) { test('it renders: editType ttl', async function (assert) {
const [model, spy] = await setup.call(this, createAttr('foo', null, { editType: 'ttl' })); const [model, spy] = await setup.call(
this,
createAttr('foo', null, {
editType: 'ttl',
helperTextDisabled: 'TTL is disabled',
helperTextEnabled: 'TTL is enabled',
})
);
assert.ok(component.hasTTLPicker, 'renders the ttl-picker component'); assert.ok(component.hasTTLPicker, 'renders the ttl-picker component');
assert.dom('[data-test-ttl-form-subtext]').hasText('TTL is disabled');
assert.dom('[data-test-ttl-toggle]').isNotChecked();
await component.fields.objectAt(0).toggleTtl(); await component.fields.objectAt(0).toggleTtl();
await component.fields.objectAt(0).select('h').change(); await component.fields.objectAt(0).select('h').change();
await component.fields.objectAt(0).ttlTime('3'); await component.fields.objectAt(0).ttlTime('3');
const expectedSeconds = `${3 * 3600}s`; const expectedSeconds = `${3 * 3600}s`;
assert.strictEqual(model.get('foo'), expectedSeconds); assert.strictEqual(model.get('foo'), expectedSeconds);
assert.ok(spy.calledWith('foo', expectedSeconds), 'onChange called with correct args'); assert.ok(spy.calledWith('foo', expectedSeconds), 'onChange called with correct args');
await component.fields.objectAt(0).toggleTtl();
assert.ok(spy.calledWith('foo', '0'), 'onChange called with 0 when toggle off');
});
test('it renders: editType ttl with special settings', async function (assert) {
const [model, spy] = await setup.call(
this,
createAttr('foo', null, {
editType: 'ttl',
setDefault: '3600s',
ttlOffValue: '',
})
);
assert.ok(component.hasTTLPicker, 'renders the ttl-picker component');
assert.dom('[data-test-ttl-toggle]').isChecked();
await component.fields.objectAt(0).toggleTtl();
assert.strictEqual(model.get('foo'), '');
assert.ok(spy.calledWith('foo', ''), 'onChange called with correct args');
}); });
test('it renders: editType ttl without toggle', async function (assert) { test('it renders: editType ttl without toggle', async function (assert) {

View File

@@ -74,6 +74,11 @@ module('Unit | Adapter | aws credential', function (hooks) {
[storeStub, type, makeSnapshot({ credentialType: 'assumed_role' })], [storeStub, type, makeSnapshot({ credentialType: 'assumed_role' })],
'POST', 'POST',
], ],
[
'assumed_role type no arn, ttl empty',
[storeStub, type, makeSnapshot({ credentialType: 'assumed_role', ttl: '' })],
'POST',
],
[ [
'assumed_role type no arn', 'assumed_role type no arn',
[storeStub, type, makeSnapshot({ credentialType: 'assumed_role', ttl: '3h' })], [storeStub, type, makeSnapshot({ credentialType: 'assumed_role', ttl: '3h' })],