mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-18 18:38:08 -05:00
SSH configuration test coverage (#28021)
* initial changes * test selector and duplicate tests clean up * check for flashDanger * rename to make it easier to parse * clean up selector names * clean up * add component test coverage * remove true
This commit is contained in:
parent
0b4d54ddde
commit
09cc71d6dc
12 changed files with 391 additions and 369 deletions
|
|
@ -4,7 +4,7 @@
|
|||
~}}
|
||||
|
||||
{{#if @configured}}
|
||||
<div class="box is-fullwidth is-sideless is-marginless">
|
||||
<div class="box is-fullwidth is-sideless is-marginless" data-test-edit-config-section>
|
||||
<div class="field">
|
||||
<label for="publicKey" class="is-label">
|
||||
Public key
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
@value={{@model.publicKey}}
|
||||
@displayOnly={{true}}
|
||||
@allowCopy={{true}}
|
||||
data-test-ssh-input="public-key"
|
||||
data-test-input="public-key"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -34,11 +34,12 @@
|
|||
@buttonColor="secondary"
|
||||
@confirmMessage="This will remove the CA certificate information."
|
||||
@onConfirmAction={{this.delete}}
|
||||
data-test-delete-public-key
|
||||
/>
|
||||
</Hds::ButtonSet>
|
||||
</div>
|
||||
{{else}}
|
||||
<form {{on "submit" this.saveConfig}} data-test-ssh-configure-form="true">
|
||||
<form {{on "submit" this.saveConfig}} data-test-configure-form>
|
||||
<div class="box is-fullwidth is-sideless is-marginless">
|
||||
<NamespaceReminder @mode="save" @noun="configuration" />
|
||||
<div class="field">
|
||||
|
|
@ -54,7 +55,7 @@
|
|||
Public key
|
||||
</label>
|
||||
<div class="control">
|
||||
<Textarea name="publicKey" id="publicKey" class="input" @value={{@model.publicKey}} />
|
||||
<Textarea name="publicKey" id="publicKey" class="input" @value={{@model.publicKey}} data-test-input="publicKey" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="b-checkbox">
|
||||
|
|
@ -64,7 +65,7 @@
|
|||
class="styled"
|
||||
@checked={{@model.generateSigningKey}}
|
||||
{{on "change" (fn (mut @model.generateSigningKey) (not @model.generateSigningKey))}}
|
||||
data-test-ssh-input="generate-signing-key-checkbox"
|
||||
data-test-input="generate-signing-key-checkbox"
|
||||
/>
|
||||
<label for="generateSigningKey" class="is-label">
|
||||
Generate signing key
|
||||
|
|
@ -81,7 +82,7 @@
|
|||
@icon={{if @loading "loading"}}
|
||||
type="submit"
|
||||
disabled={{@loading}}
|
||||
data-test-ssh-input="configure-submit"
|
||||
data-test-configure-save-button
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@
|
|||
@route="vault.cluster.secrets.backend.list-root"
|
||||
@model={{@model.id}}
|
||||
@current-when="vault.cluster.secrets.backend.list-root vault.cluster.secrets.backend.list"
|
||||
data-test-tab={{@model.id}}
|
||||
>
|
||||
{{capitalize (pluralize options.item)}}
|
||||
</LinkTo>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ module('Acceptance | aws | configuration', function (hooks) {
|
|||
await enablePage.enable('aws', path);
|
||||
await click(SES.configTab);
|
||||
await visit(`/vault/settings/secrets/configure/${path}`);
|
||||
assert.dom('[data-test-not-found]').exists('shows page-error');
|
||||
assert.dom(GENERAL.notFound).exists('shows page-error');
|
||||
// cleanup
|
||||
await runCmd(`delete sys/mounts/${path}`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { click } from '@ember/test-helpers';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { SECRET_ENGINE_SELECTORS as SES } from 'vault/tests/helpers/secret-engine/secret-engine-selectors';
|
||||
import { runCmd } from 'vault/tests/helpers/commands';
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
|
||||
import { create } from 'ember-cli-page-object';
|
||||
import fm from 'vault/tests/pages/components/flash-message';
|
||||
const flashMessage = create(fm);
|
||||
|
||||
module('Acceptance | secrets configuration | edit', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.uid = uuidv4();
|
||||
return authPage.login();
|
||||
});
|
||||
|
||||
test('it configures ssh ca', async function (assert) {
|
||||
const path = `ssh-configure-${this.uid}`;
|
||||
await enablePage.enable('ssh', path);
|
||||
await click(SES.configTab);
|
||||
await click(SES.configure);
|
||||
assert
|
||||
.dom(SES.ssh.sshInput('generate-signing-key-checkbox'))
|
||||
.isChecked('generate_signing_key defaults to true');
|
||||
await click(SES.ssh.sshInput('generate-signing-key-checkbox'));
|
||||
await click(SES.ssh.sshInput('configure-submit'));
|
||||
assert.strictEqual(
|
||||
flashMessage.latestMessage,
|
||||
'missing public_key',
|
||||
'renders warning flash message for failed save'
|
||||
);
|
||||
await click(SES.ssh.sshInput('generate-signing-key-checkbox'));
|
||||
await click(SES.ssh.sshInput('configure-submit'));
|
||||
assert.dom(SES.ssh.sshInput('public-key')).exists('renders public key after saving config');
|
||||
// cleanup
|
||||
await runCmd(`delete sys/mounts/${path}`);
|
||||
});
|
||||
});
|
||||
|
|
@ -7,6 +7,7 @@ import { click, fillIn, currentURL, waitFor, visit } from '@ember/test-helpers';
|
|||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { spy } from 'sinon';
|
||||
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
|
||||
|
|
@ -22,6 +23,10 @@ module('Acceptance | ssh | configuration', function (hooks) {
|
|||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
const flash = this.owner.lookup('service:flash-messages');
|
||||
this.flashDangerSpy = spy(flash, 'danger');
|
||||
this.store = this.owner.lookup('service:store');
|
||||
|
||||
this.uid = uuidv4();
|
||||
return authPage.login();
|
||||
});
|
||||
|
|
@ -46,12 +51,12 @@ module('Acceptance | ssh | configuration', function (hooks) {
|
|||
await enablePage.enable('ssh', sshPath);
|
||||
await click(SES.configTab);
|
||||
await visit(`/vault/settings/secrets/configure/${sshPath}`);
|
||||
assert.dom('[data-test-not-found]').exists('shows page-error');
|
||||
assert.dom(GENERAL.notFound).exists('shows page-error');
|
||||
// cleanup
|
||||
await runCmd(`delete sys/mounts/${sshPath}`);
|
||||
});
|
||||
|
||||
test('it should show a public key after saving default configuration', async function (assert) {
|
||||
test('it should show a public key after saving default configuration and allows you to delete public key', async function (assert) {
|
||||
const sshPath = `ssh-${this.uid}`;
|
||||
await enablePage.enable('ssh', sshPath);
|
||||
await click(SES.configTab);
|
||||
|
|
@ -61,32 +66,61 @@ module('Acceptance | ssh | configuration', function (hooks) {
|
|||
`/vault/secrets/${sshPath}/configuration/edit`,
|
||||
'transitions to the configuration page'
|
||||
);
|
||||
assert.dom(SES.ssh.configureForm).exists('renders ssh configuration form');
|
||||
|
||||
// default has generate CA checked so we just submit the form
|
||||
await click(SES.ssh.sshInput('configure-submit'));
|
||||
await click(SES.ssh.save);
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/secrets/${sshPath}/configuration/edit`,
|
||||
'stays on configuration form page.'
|
||||
);
|
||||
|
||||
await waitFor(SES.ssh.sshInput('public-key'));
|
||||
assert.dom(SES.ssh.sshInput('public-key')).exists('renders the public key input on form page');
|
||||
assert.dom(SES.ssh.sshInput('public-key')).hasClass('masked-input', 'public key is masked');
|
||||
|
||||
// There is a delay in the backend for the public key to be generated, wait for it to complete by checking that the public key is displayed
|
||||
await waitFor(GENERAL.inputByAttr('public-key'));
|
||||
assert.dom(GENERAL.inputByAttr('public-key')).hasText('***********', 'public key is masked');
|
||||
assert
|
||||
.dom(SES.ssh.editConfigSection)
|
||||
.exists('renders the edit configuration section of the form and not the create part');
|
||||
// delete Public key
|
||||
await click(SES.ssh.deletePublicKey);
|
||||
assert.dom(GENERAL.confirmMessage).hasText('This will remove the CA certificate information.');
|
||||
await click(GENERAL.confirmButton);
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/secrets/${sshPath}/configuration/edit`,
|
||||
'after deleting public key stays on edit page'
|
||||
);
|
||||
assert.dom(GENERAL.maskedInput('privateKey')).hasNoText('Private key is empty and reset');
|
||||
assert.dom(GENERAL.inputByAttr('publicKey')).hasNoText('Public key is empty and reset');
|
||||
assert
|
||||
.dom(GENERAL.inputByAttr('generate-signing-key-checkbox'))
|
||||
.isNotChecked('Generate signing key is unchecked');
|
||||
await click(SES.viewBackend);
|
||||
await click(SES.configTab);
|
||||
assert
|
||||
.dom(`[data-test-value-div="Public key"] [data-test-masked-input]`)
|
||||
.hasText('***********', 'value for Public key is on config details and is masked');
|
||||
assert
|
||||
.dom(GENERAL.infoRowValue('Generate signing key'))
|
||||
.hasText('Yes', 'value for Generate signing key displays default of true/yes.');
|
||||
.dom(GENERAL.emptyStateTitle)
|
||||
.hasText('SSH not configured', 'after deleting public key SSH is no longer configured');
|
||||
// cleanup
|
||||
await runCmd(`delete sys/mounts/${sshPath}`);
|
||||
});
|
||||
|
||||
test('it displays error if generate Signing key is not checked and no public and private keys', async function (assert) {
|
||||
const path = `ssh-configure-${this.uid}`;
|
||||
await enablePage.enable('ssh', path);
|
||||
await click(SES.configTab);
|
||||
await click(SES.configure);
|
||||
assert
|
||||
.dom(GENERAL.inputByAttr('generate-signing-key-checkbox'))
|
||||
.isChecked('generate_signing_key defaults to true');
|
||||
await click(GENERAL.inputByAttr('generate-signing-key-checkbox'));
|
||||
await click(SES.ssh.save);
|
||||
assert.true(this.flashDangerSpy.calledWith('missing public_key'), 'Danger flash message is displayed');
|
||||
// visit the details page and confirm the public key is not shown
|
||||
await visit(`/vault/secrets/${path}/configuration`);
|
||||
assert.dom(GENERAL.infoRowLabel('Public key')).doesNotExist('Public Key label does not exist');
|
||||
assert.dom(GENERAL.emptyStateTitle).hasText('SSH not configured', 'SSH not configured');
|
||||
// cleanup
|
||||
await runCmd(`delete sys/mounts/${path}`);
|
||||
});
|
||||
|
||||
test('it should show API error when SSH configuration read fails', async function (assert) {
|
||||
assert.expect(1);
|
||||
const path = `ssh-${this.uid}`;
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { currentRouteName, settled } from '@ember/test-helpers';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import editPage from 'vault/tests/pages/secrets/backend/ssh/edit-role';
|
||||
import showPage from 'vault/tests/pages/secrets/backend/ssh/show';
|
||||
import generatePage from 'vault/tests/pages/secrets/backend/ssh/generate-otp';
|
||||
import listPage from 'vault/tests/pages/secrets/backend/list';
|
||||
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
|
||||
module('Acceptance | secrets/ssh', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.uid = uuidv4();
|
||||
return authPage.login();
|
||||
});
|
||||
|
||||
const mountAndNav = async (uid) => {
|
||||
const path = `ssh-${uid}`;
|
||||
await enablePage.enable('ssh', path);
|
||||
await settled();
|
||||
await editPage.visitRoot({ backend: path });
|
||||
await settled();
|
||||
return path;
|
||||
};
|
||||
|
||||
test('it creates a role and redirects', async function (assert) {
|
||||
assert.expect(5);
|
||||
const path = await mountAndNav(this.uid);
|
||||
await editPage.createOTPRole('role');
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'vault.cluster.secrets.backend.show',
|
||||
'redirects to the show page'
|
||||
);
|
||||
assert.ok(showPage.generateIsPresent, 'shows the generate button');
|
||||
|
||||
await showPage.visit({ backend: path, id: 'role' });
|
||||
await settled();
|
||||
await showPage.generate();
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'vault.cluster.secrets.backend.credentials',
|
||||
'navs to the credentials page'
|
||||
);
|
||||
|
||||
await listPage.visitRoot({ backend: path });
|
||||
await settled();
|
||||
assert.strictEqual(listPage.secrets.length, 1, 'shows role in the list');
|
||||
const secret = listPage.secrets.objectAt(0);
|
||||
await secret.menuToggle();
|
||||
assert.dom('.hds-dropdown li').exists({ count: 5 }, 'Renders 5 popup menu items');
|
||||
});
|
||||
|
||||
test('it deletes a role', async function (assert) {
|
||||
assert.expect(2);
|
||||
const path = await mountAndNav(this.uid);
|
||||
await editPage.createOTPRole('role');
|
||||
await settled();
|
||||
await showPage.visit({ backend: path, id: 'role' });
|
||||
await settled();
|
||||
await showPage.deleteRole();
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'vault.cluster.secrets.backend.list-root',
|
||||
'redirects to list page'
|
||||
);
|
||||
assert.ok(listPage.backendIsEmpty, 'no roles listed');
|
||||
});
|
||||
|
||||
test('it generates an OTP', async function (assert) {
|
||||
assert.expect(6);
|
||||
const path = await mountAndNav(this.uid);
|
||||
await editPage.createOTPRole('role');
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'vault.cluster.secrets.backend.show',
|
||||
'redirects to the show page'
|
||||
);
|
||||
assert.ok(showPage.generateIsPresent, 'shows the generate button');
|
||||
|
||||
await showPage.visit({ backend: path, id: 'role' });
|
||||
await settled();
|
||||
await showPage.generate();
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'vault.cluster.secrets.backend.credentials',
|
||||
'navs to the credentials page'
|
||||
);
|
||||
|
||||
await generatePage.generateOTP();
|
||||
await settled();
|
||||
assert.ok(generatePage.warningIsPresent, 'shows warning');
|
||||
await generatePage.back();
|
||||
await settled();
|
||||
assert.ok(generatePage.userIsPresent, 'clears generate, shows user input');
|
||||
assert.ok(generatePage.ipIsPresent, 'clears generate, shows ip input');
|
||||
});
|
||||
});
|
||||
223
ui/tests/acceptance/secrets/backend/ssh/roles-test.js
Normal file
223
ui/tests/acceptance/secrets/backend/ssh/roles-test.js
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { click, fillIn, currentURL, find, settled, waitUntil, currentRouteName } from '@ember/test-helpers';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
|
||||
import listPage from 'vault/tests/pages/secrets/backend/list';
|
||||
import editPage from 'vault/tests/pages/secrets/backend/ssh/edit-role';
|
||||
import showPage from 'vault/tests/pages/secrets/backend/ssh/show';
|
||||
import generatePage from 'vault/tests/pages/secrets/backend/ssh/generate-otp';
|
||||
import { runCmd } from 'vault/tests/helpers/commands';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
import { SECRET_ENGINE_SELECTORS as SES } from 'vault/tests/helpers/secret-engine/secret-engine-selectors';
|
||||
|
||||
// There is duplication within this test suite. The duplication occurred because two test suites both testing ssh roles were merged.
|
||||
// refactoring the tests to remove the duplication would be a good next step as well as removing the tests/pages.
|
||||
|
||||
module('Acceptance | ssh | roles', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.uid = uuidv4();
|
||||
return authPage.login();
|
||||
});
|
||||
|
||||
const PUB_KEY = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCn9p5dHNr4aU4R2W7ln+efzO5N2Cdv/SXk6zbCcvhWcblWMjkXf802B0PbKvf6cJIzM/Xalb3qz1cK+UUjCSEAQWefk6YmfzbOikfc5EHaSKUqDdE+HlsGPvl42rjCr28qYfuYh031YfwEQGEAIEypo7OyAj+38NLbHAQxDxuaReee1YCOV5rqWGtEgl2VtP5kG+QEBza4ZfeglS85f/GGTvZC4Jq1GX+wgmFxIPnd6/mUXa4ecoR0QMfOAzzvPm4ajcNCQORfHLQKAcmiBYMiyQJoU+fYpi9CJGT1jWTmR99yBkrSg6yitI2qqXyrpwAbhNGrM0Fw0WpWxh66N9Xp meirish@Macintosh-3.local`;
|
||||
|
||||
const ROLES = [
|
||||
{
|
||||
type: 'ca',
|
||||
name: 'carole',
|
||||
credsRoute: 'vault.cluster.secrets.backend.sign',
|
||||
async fillInCreate() {
|
||||
await click(GENERAL.inputByAttr('allowUserCertificates'));
|
||||
},
|
||||
async fillInGenerate() {
|
||||
await fillIn(GENERAL.inputByAttr('publicKey'), PUB_KEY);
|
||||
await click('[data-test-toggle-button]');
|
||||
|
||||
await click(GENERAL.ttl.toggle('TTL'));
|
||||
await fillIn(GENERAL.selectByAttr('ttl-unit'), 'm');
|
||||
|
||||
document.querySelector(GENERAL.ttl.input('TTL')).value = 30;
|
||||
},
|
||||
assertBeforeGenerate(assert) {
|
||||
assert.dom('[data-test-form-field-from-model]').exists('renders the FormFieldFromModel');
|
||||
const value = document.querySelector('[data-test-ttl-value="TTL"]').value;
|
||||
// confirms that the actions are correctly being passed down to the FormFieldFromModel component
|
||||
assert.strictEqual(value, '30', 'renders action updateTtl');
|
||||
},
|
||||
assertAfterGenerate(assert, sshPath) {
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/secrets/${sshPath}/sign/${this.name}`,
|
||||
'ca sign url is correct'
|
||||
);
|
||||
assert.dom('[data-test-row-label="Signed key"]').exists({ count: 1 }, 'renders the signed key');
|
||||
assert
|
||||
.dom('[data-test-row-value="Signed key"]')
|
||||
.exists({ count: 1 }, "renders the signed key's value");
|
||||
assert.dom('[data-test-row-label="Serial number"]').exists({ count: 1 }, 'renders the serial');
|
||||
assert.dom('[data-test-row-value="Serial number"]').exists({ count: 1 }, 'renders the serial value');
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'otp',
|
||||
name: 'otprole',
|
||||
credsRoute: 'vault.cluster.secrets.backend.credentials',
|
||||
async fillInCreate() {
|
||||
await fillIn(GENERAL.inputByAttr('defaultUser'), 'admin');
|
||||
await click(GENERAL.toggleGroup('Options'));
|
||||
await fillIn(GENERAL.inputByAttr('cidrList'), '1.2.3.4/32');
|
||||
},
|
||||
async fillInGenerate() {
|
||||
await fillIn(GENERAL.inputByAttr('username'), 'admin');
|
||||
await fillIn(GENERAL.inputByAttr('ip'), '1.2.3.4');
|
||||
},
|
||||
assertAfterGenerate(assert, sshPath) {
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/secrets/${sshPath}/credentials/${this.name}`,
|
||||
'otp credential url is correct'
|
||||
);
|
||||
assert.dom(GENERAL.infoRowLabel('Key')).exists({ count: 1 }, 'renders the key');
|
||||
assert.dom('[data-test-masked-input]').exists({ count: 1 }, 'renders mask for key value');
|
||||
assert.dom(GENERAL.infoRowLabel('Port')).exists({ count: 1 }, 'renders the port');
|
||||
assert.dom('[data-test-row-value="Port"]').exists({ count: 1 }, "renders the port's value");
|
||||
},
|
||||
},
|
||||
];
|
||||
test('it creates roles, generates keys and deletes roles', async function (assert) {
|
||||
assert.expect(28);
|
||||
const sshPath = `ssh-${this.uid}`;
|
||||
await enablePage.enable('ssh', sshPath);
|
||||
await click(SES.configTab);
|
||||
await click(SES.configure);
|
||||
// default has generate CA checked so we just submit the form
|
||||
await click(SES.ssh.save);
|
||||
await click(SES.viewBackend);
|
||||
|
||||
for (const role of ROLES) {
|
||||
// create a role
|
||||
await click(SES.createSecret);
|
||||
assert.dom(SES.secretHeader).includesText('SSH Role', `${role.type}: renders the create page`);
|
||||
|
||||
await fillIn(GENERAL.inputByAttr('name'), role.name);
|
||||
await fillIn(GENERAL.inputByAttr('keyType'), role.type);
|
||||
await role.fillInCreate();
|
||||
await settled();
|
||||
|
||||
// save the role
|
||||
await click(SES.ssh.createRole);
|
||||
await waitUntil(() => currentURL() === `/vault/secrets/${sshPath}/show/${role.name}`); // flaky without this
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/secrets/${sshPath}/show/${role.name}`,
|
||||
`${role.type}: navigates to the show page on creation`
|
||||
);
|
||||
|
||||
// sign a key with this role
|
||||
await click(SES.generateLink);
|
||||
assert.strictEqual(currentRouteName(), role.credsRoute, 'navigates to the credentials page');
|
||||
await role.fillInGenerate();
|
||||
if (role.type === 'ca') {
|
||||
await settled();
|
||||
role.assertBeforeGenerate(assert);
|
||||
}
|
||||
|
||||
// generate creds
|
||||
await click(GENERAL.saveButton);
|
||||
await settled(); // eslint-disable-line
|
||||
role.assertAfterGenerate(assert, sshPath);
|
||||
|
||||
// click the "Back" button
|
||||
await click(SES.backButton);
|
||||
assert.dom('[data-test-secret-generate-form]').exists(`${role.type}: back takes you back to the form`);
|
||||
|
||||
await click(GENERAL.cancelButton);
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/secrets/${sshPath}/list`,
|
||||
`${role.type}: cancel takes you to ssh index`
|
||||
);
|
||||
assert.dom(SES.secretLink(role.name)).exists(`${role.type}: role shows in the list`);
|
||||
const secret = listPage.secrets.objectAt(0);
|
||||
await secret.menuToggle();
|
||||
assert.dom('.hds-dropdown li').exists({ count: 5 }, 'Renders 5 popup menu items');
|
||||
|
||||
// and delete from the popup list menu
|
||||
await waitUntil(() => find(SES.ssh.deleteRole)); // flaky without
|
||||
await click(SES.ssh.deleteRole);
|
||||
await click(GENERAL.confirmButton);
|
||||
assert.dom(SES.secretLink(role.name)).doesNotExist(`${role.type}: role is no longer in the list`);
|
||||
}
|
||||
// cleanup
|
||||
await runCmd(`delete sys/mounts/${sshPath}`);
|
||||
});
|
||||
module('Acceptance | ssh | otp role', function () {
|
||||
test('it deletes a role from list view', async function (assert) {
|
||||
assert.expect(2);
|
||||
const path = `ssh-${this.uid}`;
|
||||
await enablePage.enable('ssh', path);
|
||||
await settled();
|
||||
await editPage.visitRoot({ backend: path });
|
||||
await editPage.createOTPRole('role');
|
||||
await settled();
|
||||
await showPage.visit({ backend: path, id: 'role' });
|
||||
await settled();
|
||||
await showPage.deleteRole();
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'vault.cluster.secrets.backend.list-root',
|
||||
'redirects to list page'
|
||||
);
|
||||
assert.ok(listPage.backendIsEmpty, 'no roles listed');
|
||||
// cleanup
|
||||
await runCmd(`delete sys/mounts/${path}`);
|
||||
});
|
||||
|
||||
test('it generates an OTP', async function (assert) {
|
||||
assert.expect(6);
|
||||
const path = `ssh-${this.uid}`;
|
||||
await enablePage.enable('ssh', path);
|
||||
await settled();
|
||||
await editPage.visitRoot({ backend: path });
|
||||
await editPage.createOTPRole('role');
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'vault.cluster.secrets.backend.show',
|
||||
'redirects to the show page'
|
||||
);
|
||||
assert.ok(showPage.generateIsPresent, 'shows the generate button');
|
||||
|
||||
await showPage.visit({ backend: path, id: 'role' });
|
||||
await settled();
|
||||
await showPage.generate();
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'vault.cluster.secrets.backend.credentials',
|
||||
'navs to the credentials page'
|
||||
);
|
||||
|
||||
await generatePage.generateOTP();
|
||||
await settled();
|
||||
assert.ok(generatePage.warningIsPresent, 'shows warning');
|
||||
await generatePage.back();
|
||||
await settled();
|
||||
assert.ok(generatePage.userIsPresent, 'clears generate, shows user input');
|
||||
assert.ok(generatePage.ipIsPresent, 'clears generate, shows ip input');
|
||||
// cleanup
|
||||
await runCmd(`delete sys/mounts/${path}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import {
|
||||
click,
|
||||
fillIn,
|
||||
currentURL,
|
||||
find,
|
||||
settled,
|
||||
waitUntil,
|
||||
currentRouteName,
|
||||
waitFor,
|
||||
} from '@ember/test-helpers';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
|
||||
module('Acceptance | ssh secret backend', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.uid = uuidv4();
|
||||
return authPage.login();
|
||||
});
|
||||
|
||||
const PUB_KEY = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCn9p5dHNr4aU4R2W7ln+efzO5N2Cdv/SXk6zbCcvhWcblWMjkXf802B0PbKvf6cJIzM/Xalb3qz1cK+UUjCSEAQWefk6YmfzbOikfc5EHaSKUqDdE+HlsGPvl42rjCr28qYfuYh031YfwEQGEAIEypo7OyAj+38NLbHAQxDxuaReee1YCOV5rqWGtEgl2VtP5kG+QEBza4ZfeglS85f/GGTvZC4Jq1GX+wgmFxIPnd6/mUXa4ecoR0QMfOAzzvPm4ajcNCQORfHLQKAcmiBYMiyQJoU+fYpi9CJGT1jWTmR99yBkrSg6yitI2qqXyrpwAbhNGrM0Fw0WpWxh66N9Xp meirish@Macintosh-3.local`;
|
||||
|
||||
const ROLES = [
|
||||
{
|
||||
type: 'ca',
|
||||
name: 'carole',
|
||||
credsRoute: 'vault.cluster.secrets.backend.sign',
|
||||
async fillInCreate() {
|
||||
await click('[data-test-input="allowUserCertificates"]');
|
||||
},
|
||||
async fillInGenerate() {
|
||||
await fillIn('[data-test-input="publicKey"]', PUB_KEY);
|
||||
await click('[data-test-toggle-button]');
|
||||
|
||||
await click('[data-test-toggle-label="TTL"]');
|
||||
await fillIn('[data-test-select="ttl-unit"]', 'm');
|
||||
|
||||
document.querySelector('[data-test-ttl-value="TTL"]').value = 30;
|
||||
},
|
||||
assertBeforeGenerate(assert) {
|
||||
assert.dom('[data-test-form-field-from-model]').exists('renders the FormFieldFromModel');
|
||||
const value = document.querySelector('[data-test-ttl-value="TTL"]').value;
|
||||
// confirms that the actions are correctly being passed down to the FormFieldFromModel component
|
||||
assert.strictEqual(value, '30', 'renders action updateTtl');
|
||||
},
|
||||
assertAfterGenerate(assert, sshPath) {
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/secrets/${sshPath}/sign/${this.name}`,
|
||||
'ca sign url is correct'
|
||||
);
|
||||
assert.dom('[data-test-row-label="Signed key"]').exists({ count: 1 }, 'renders the signed key');
|
||||
assert
|
||||
.dom('[data-test-row-value="Signed key"]')
|
||||
.exists({ count: 1 }, "renders the signed key's value");
|
||||
assert.dom('[data-test-row-label="Serial number"]').exists({ count: 1 }, 'renders the serial');
|
||||
assert.dom('[data-test-row-value="Serial number"]').exists({ count: 1 }, 'renders the serial value');
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'otp',
|
||||
name: 'otprole',
|
||||
credsRoute: 'vault.cluster.secrets.backend.credentials',
|
||||
async fillInCreate() {
|
||||
await fillIn('[data-test-input="defaultUser"]', 'admin');
|
||||
await click('[data-test-toggle-group="Options"]');
|
||||
await fillIn('[data-test-input="cidrList"]', '1.2.3.4/32');
|
||||
},
|
||||
async fillInGenerate() {
|
||||
await fillIn('[data-test-input="username"]', 'admin');
|
||||
await fillIn('[data-test-input="ip"]', '1.2.3.4');
|
||||
},
|
||||
assertAfterGenerate(assert, sshPath) {
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/secrets/${sshPath}/credentials/${this.name}`,
|
||||
'otp credential url is correct'
|
||||
);
|
||||
assert.dom('[data-test-row-label="Key"]').exists({ count: 1 }, 'renders the key');
|
||||
assert.dom('[data-test-masked-input]').exists({ count: 1 }, 'renders mask for key value');
|
||||
assert.dom('[data-test-row-label="Port"]').exists({ count: 1 }, 'renders the port');
|
||||
assert.dom('[data-test-row-value="Port"]').exists({ count: 1 }, "renders the port's value");
|
||||
},
|
||||
},
|
||||
];
|
||||
test('ssh backend', async function (assert) {
|
||||
assert.expect(30);
|
||||
const sshPath = `ssh-${this.uid}`;
|
||||
|
||||
await enablePage.enable('ssh', sshPath);
|
||||
await settled();
|
||||
await click('[data-test-configuration-tab]');
|
||||
|
||||
await click('[data-test-secret-backend-configure]');
|
||||
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/secrets/${sshPath}/configuration/edit`,
|
||||
'transitions to the configuration page'
|
||||
);
|
||||
assert.dom('[data-test-ssh-configure-form]').exists('renders the empty configuration form');
|
||||
|
||||
// default has generate CA checked so we just submit the form
|
||||
await click('[data-test-ssh-input="configure-submit"]');
|
||||
|
||||
await waitFor('[data-test-ssh-input="public-key"]');
|
||||
assert.dom('[data-test-ssh-input="public-key"]').exists();
|
||||
await click('[data-test-backend-view-link]');
|
||||
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${sshPath}/list`, `redirects to ssh index`);
|
||||
|
||||
for (const role of ROLES) {
|
||||
// create a role
|
||||
await click('[data-test-secret-create]');
|
||||
|
||||
assert
|
||||
.dom('[data-test-secret-header]')
|
||||
.includesText('SSH Role', `${role.type}: renders the create page`);
|
||||
|
||||
await fillIn('[data-test-input="name"]', role.name);
|
||||
await fillIn('[data-test-input="keyType"]', role.type);
|
||||
await role.fillInCreate();
|
||||
await settled();
|
||||
|
||||
// save the role
|
||||
await click('[data-test-role-ssh-create]');
|
||||
await waitUntil(() => currentURL() === `/vault/secrets/${sshPath}/show/${role.name}`); // flaky without this
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/secrets/${sshPath}/show/${role.name}`,
|
||||
`${role.type}: navigates to the show page on creation`
|
||||
);
|
||||
|
||||
// sign a key with this role
|
||||
await click('[data-test-backend-credentials]');
|
||||
assert.strictEqual(currentRouteName(), role.credsRoute);
|
||||
await role.fillInGenerate();
|
||||
if (role.type === 'ca') {
|
||||
await settled();
|
||||
role.assertBeforeGenerate(assert);
|
||||
}
|
||||
|
||||
// generate creds
|
||||
await click(GENERAL.saveButton);
|
||||
await settled(); // eslint-disable-line
|
||||
role.assertAfterGenerate(assert, sshPath);
|
||||
|
||||
// click the "Back" button
|
||||
await click('[data-test-back-button]');
|
||||
|
||||
assert.dom('[data-test-secret-generate-form]').exists(`${role.type}: back takes you back to the form`);
|
||||
|
||||
await click(GENERAL.cancelButton);
|
||||
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/secrets/${sshPath}/list`,
|
||||
`${role.type}: cancel takes you to ssh index`
|
||||
);
|
||||
assert.dom(`[data-test-secret-link="${role.name}"]`).exists(`${role.type}: role shows in the list`);
|
||||
|
||||
//and delete
|
||||
await click(`[data-test-secret-link="${role.name}"] [data-test-popup-menu-trigger]`);
|
||||
await waitUntil(() => find('[data-test-ssh-role-delete]')); // flaky without
|
||||
await click(`[data-test-ssh-role-delete]`);
|
||||
await click(`[data-test-confirm-button]`);
|
||||
assert
|
||||
.dom(`[data-test-secret-link="${role.name}"]`)
|
||||
.doesNotExist(`${role.type}: role is no longer in the list`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -26,6 +26,7 @@ export const GENERAL = {
|
|||
filterInputExplicitSearch: '[data-test-filter-input-explicit-search]',
|
||||
confirmModalInput: '[data-test-confirmation-modal-input]',
|
||||
confirmButton: '[data-test-confirm-button]',
|
||||
confirmMessage: '[data-test-confirm-action-message]',
|
||||
confirmTrigger: '[data-test-confirm-action-trigger]',
|
||||
emptyStateTitle: '[data-test-empty-state-title]',
|
||||
emptyStateSubtitle: '[data-test-empty-state-subtitle]',
|
||||
|
|
@ -42,6 +43,7 @@ export const GENERAL = {
|
|||
inputByAttr: (attr: string) => `[data-test-input="${attr}"]`,
|
||||
selectByAttr: (attr: string) => `[data-test-select="${attr}"]`,
|
||||
toggleInput: (attr: string) => `[data-test-toggle-input="${attr}"]`,
|
||||
toggleGroup: (attr: string) => `[data-test-toggle-group="${attr}"]`,
|
||||
ttl: {
|
||||
toggle: (attr: string) => `[data-test-toggle-label="${attr}"]`,
|
||||
input: (attr: string) => `[data-test-ttl-value="${attr}"]`,
|
||||
|
|
@ -50,12 +52,14 @@ export const GENERAL = {
|
|||
validation: (attr: string) => `[data-test-field-validation=${attr}]`,
|
||||
validationWarning: (attr: string) => `[data-test-validation-warning=${attr}]`,
|
||||
messageError: '[data-test-message-error]',
|
||||
notFound: '[data-test-not-found]',
|
||||
pageError: {
|
||||
error: '[data-test-page-error]',
|
||||
errorTitle: (httpStatus: number) => `[data-test-page-error-title="${httpStatus}"]`,
|
||||
errorMessage: '[data-test-page-error-message]',
|
||||
errorDetails: '[data-test-page-error-details]',
|
||||
},
|
||||
inlineError: '[data-test-inline-error-message]',
|
||||
kvObjectEditor: {
|
||||
deleteRow: (idx = 0) => `[data-test-kv-delete-row="${idx}"]`,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const createAwsRootConfig = (store, backend) => {
|
|||
id: backend,
|
||||
modelName: 'aws/root-config',
|
||||
data: {
|
||||
backend: backend,
|
||||
backend,
|
||||
region: 'us-west-2',
|
||||
access_key: '123-key',
|
||||
iam_endpoint: 'iam-endpoint',
|
||||
|
|
@ -36,8 +36,7 @@ const createSshCaConfig = (store, backend) => {
|
|||
id: backend,
|
||||
modelName: 'ssh/ca-config',
|
||||
data: {
|
||||
backend: backend,
|
||||
public_key: 'public-key',
|
||||
backend,
|
||||
generate_signing_key: true,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,7 +26,11 @@ export const SECRET_ENGINE_SELECTORS = {
|
|||
delete: (role: string) => `[data-test-aws-role-delete="${role}"]`,
|
||||
},
|
||||
ssh: {
|
||||
configureForm: '[data-test-ssh-configure-form]',
|
||||
sshInput: (name: string) => `[data-test-ssh-input="${name}"]`,
|
||||
configureForm: '[data-test-configure-form]',
|
||||
editConfigSection: '[data-test-edit-config-section]',
|
||||
deletePublicKey: '[data-test-delete-public-key]',
|
||||
save: '[data-test-configure-save-button]',
|
||||
createRole: '[data-test-role-ssh-create]',
|
||||
deleteRole: '[data-test-ssh-role-delete]',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'vault/tests/helpers';
|
||||
import { render, click } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
import { SECRET_ENGINE_SELECTORS as SES } from 'vault/tests/helpers/secret-engine/secret-engine-selectors';
|
||||
import { createConfig } from 'vault/tests/helpers/secret-engine/secret-engine-helpers';
|
||||
import sinon from 'sinon';
|
||||
|
||||
module('Integration | Component | SecretEngine/configure-ssh', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.model = createConfig(this.store, 'ssh-test', 'ssh');
|
||||
this.saveConfig = sinon.stub();
|
||||
});
|
||||
|
||||
test('it shows create fields if not configured', async function (assert) {
|
||||
await render(hbs`
|
||||
<SecretEngine::ConfigureSsh
|
||||
@model={{this.model}}
|
||||
@configured={{false}}
|
||||
@saveConfig={{this.saveConfig}}
|
||||
@loading={{false}}
|
||||
/>
|
||||
`);
|
||||
assert.dom(GENERAL.maskedInput('privateKey')).hasNoText('Private key is empty and reset');
|
||||
assert.dom(GENERAL.inputByAttr('publicKey')).hasNoText('Public key is empty and reset');
|
||||
assert
|
||||
.dom(GENERAL.inputByAttr('generate-signing-key-checkbox'))
|
||||
.isChecked('Generate signing key is checked by default');
|
||||
});
|
||||
|
||||
test('it calls save with correct arg', async function (assert) {
|
||||
await render(hbs`
|
||||
<SecretEngine::ConfigureSsh
|
||||
@model={{this.model}}
|
||||
@configured={{false}}
|
||||
@saveConfig={{this.saveConfig}}
|
||||
@loading={{false}}
|
||||
/>
|
||||
`);
|
||||
await click(SES.ssh.save);
|
||||
assert.ok(
|
||||
this.saveConfig.withArgs({ delete: false }).calledOnce,
|
||||
'calls the saveConfig action with args delete:false'
|
||||
);
|
||||
});
|
||||
|
||||
test('it shows masked key if model is not new', async function (assert) {
|
||||
// replace model with model that has public_key
|
||||
this.model = {
|
||||
publicKey:
|
||||
'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3lCZ7W2eJZ9W9qzv7K9GJ5qJYQ2cY6C+5Kv8Jtjz8h6wqZJ9U9K1lJ9Z6zq4sX0f7Q5X2l8L4gTt2+2ZKpVv6g1KQ6JG5H4QbVrQq2r4FzZQ2B0Y8q5c7q3Y5X6q4Q6',
|
||||
};
|
||||
await render(hbs`
|
||||
<SecretEngine::ConfigureSsh
|
||||
@model={{this.model}}
|
||||
@configured={{true}}
|
||||
@saveConfig={{this.saveConfig}}
|
||||
@loading={{false}}
|
||||
/>
|
||||
`);
|
||||
assert
|
||||
.dom(SES.ssh.editConfigSection)
|
||||
.exists('renders the edit configuration section of the form and not the create part');
|
||||
assert.dom(GENERAL.inputByAttr('public-key')).hasText('***********', 'public key is masked');
|
||||
await click('[data-test-button="toggle-masked"]');
|
||||
assert
|
||||
.dom(GENERAL.inputByAttr('public-key'))
|
||||
.hasText(this.model.publicKey, 'public key is unmasked and shows the actual value');
|
||||
});
|
||||
|
||||
test('it calls delete correctly', async function (assert) {
|
||||
await render(hbs`
|
||||
<SecretEngine::ConfigureSsh
|
||||
@model={{this.model}}
|
||||
@configured={{true}}
|
||||
@saveConfig={{this.saveConfig}}
|
||||
@loading={{false}}
|
||||
/>
|
||||
`);
|
||||
// delete Public key
|
||||
await click(SES.ssh.deletePublicKey);
|
||||
assert.dom(GENERAL.confirmMessage).hasText('This will remove the CA certificate information.');
|
||||
await click(GENERAL.confirmButton);
|
||||
assert.ok(
|
||||
this.saveConfig.withArgs({ delete: true }).calledOnce,
|
||||
'calls the saveConfig action with args delete:true'
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue