mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 09:42:25 +00:00
UI: glimmerize generate credentials component (#27405)
This commit is contained in:
3
changelog/27405.txt
Normal file
3
changelog/27405.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
ui: AWS credentials form sets credential_type from backing role
|
||||
```
|
||||
@@ -40,7 +40,7 @@
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
@value={{@accessKey}}
|
||||
data-test-aws-input="accessKey"
|
||||
data-test-input="accessKey"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,7 +56,7 @@
|
||||
name="secret"
|
||||
class="input"
|
||||
@value={{@secretKey}}
|
||||
data-test-aws-input="secretKey"
|
||||
data-test-input="secretKey"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -104,7 +104,7 @@
|
||||
{{/if}}
|
||||
|
||||
<div class="box is-bottomless is-fullwidth">
|
||||
<Hds::Button @text="Save" data-test-aws-input="root-save" type="submit" />
|
||||
<Hds::Button @text="Save" data-test-save type="submit" />
|
||||
</div>
|
||||
</form>
|
||||
</T.Panel>
|
||||
@@ -134,7 +134,7 @@
|
||||
@onChange={{fn this.handleTtlChange "leaseMax"}}
|
||||
/>
|
||||
<div class="box is-bottomless is-fullwidth">
|
||||
<Hds::Button @text="Save" data-test-aws-input="lease-save" type="submit" />
|
||||
<Hds::Button @text="Save" data-test-save type="submit" />
|
||||
</div>
|
||||
</form>
|
||||
</T.Panel>
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
<p.top>
|
||||
<Hds::Breadcrumb>
|
||||
<Hds::Breadcrumb::Item
|
||||
@text={{this.backendPath}}
|
||||
@text={{@backendPath}}
|
||||
@route="vault.cluster.secrets.backend"
|
||||
@model={{this.backendPath}}
|
||||
@model={{@backendPath}}
|
||||
data-test-link="role-list"
|
||||
/>
|
||||
<Hds::Breadcrumb::Item @text="Credentials" @route="vault.cluster.secrets.backend" @model={{this.backendPath}} />
|
||||
<Hds::Breadcrumb::Item @text={{this.roleName}} @route="vault.cluster.secrets.backend.show" @model={{this.roleName}} />
|
||||
<Hds::Breadcrumb::Item @text="Credentials" @route="vault.cluster.secrets.backend" @model={{@backendPath}} />
|
||||
<Hds::Breadcrumb::Item @text={{@roleName}} @route="vault.cluster.secrets.backend.show" @model={{@roleName}} />
|
||||
<Hds::Breadcrumb::Item @text={{this.options.title}} @current={{true}} />
|
||||
</Hds::Breadcrumb>
|
||||
</p.top>
|
||||
@@ -24,7 +24,7 @@
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
{{#if this.model.hasGenerated}}
|
||||
{{#if this.hasGenerated}}
|
||||
<div class="box is-fullwidth is-sideless is-paddingless is-marginless">
|
||||
<MessageError @model={{this.model}} />
|
||||
{{#unless this.model.isError}}
|
||||
@@ -35,48 +35,42 @@
|
||||
</A.Description>
|
||||
</Hds::Alert>
|
||||
{{/unless}}
|
||||
{{#each this.model.attrs as |attr|}}
|
||||
{{#if (eq attr.type "object")}}
|
||||
<InfoTableRow
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{stringify (get this.model attr.name)}}
|
||||
/>
|
||||
{{else}}
|
||||
{{#if
|
||||
(or
|
||||
(eq attr.name "key")
|
||||
(eq attr.name "secretKey")
|
||||
(eq attr.name "securityToken")
|
||||
(eq attr.name "privateKey")
|
||||
attr.options.masked
|
||||
)
|
||||
}}
|
||||
{{#if (get this.model attr.name)}}
|
||||
{{#each this.displayFields as |key|}}
|
||||
{{#let (get this.model.allByKey key) as |attr|}}
|
||||
{{#if (eq attr.type "object")}}
|
||||
<InfoTableRow
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{stringify (get this.model attr.name)}}
|
||||
/>
|
||||
{{else}}
|
||||
{{#if attr.options.masked}}
|
||||
{{#if (get this.model attr.name)}}
|
||||
<InfoTableRow
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{get this.model attr.name}}
|
||||
>
|
||||
<MaskedInput
|
||||
@value={{get this.model attr.name}}
|
||||
@name={{attr.name}}
|
||||
@displayOnly={{true}}
|
||||
@allowCopy={{true}}
|
||||
/>
|
||||
</InfoTableRow>
|
||||
{{/if}}
|
||||
{{else if (and (get this.model attr.name) (or (eq attr.name "issueDate") (eq attr.name "expiryDate")))}}
|
||||
<InfoTableRow
|
||||
data-test-table-row
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{date-format (get this.model attr.name) "MMM dd, yyyy hh:mm:ss a"}}
|
||||
/>
|
||||
{{else}}
|
||||
<InfoTableRow
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{get this.model attr.name}}
|
||||
>
|
||||
<MaskedInput
|
||||
@value={{get this.model attr.name}}
|
||||
@name={{attr.name}}
|
||||
@displayOnly={{true}}
|
||||
@allowCopy={{true}}
|
||||
/>
|
||||
</InfoTableRow>
|
||||
/>
|
||||
{{/if}}
|
||||
{{else if (and (get this.model attr.name) (or (eq attr.name "issueDate") (eq attr.name "expiryDate")))}}
|
||||
<InfoTableRow
|
||||
data-test-table-row
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{date-format (get this.model attr.name) "MMM dd, yyyy hh:mm:ss a"}}
|
||||
/>
|
||||
{{else}}
|
||||
<InfoTableRow
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{get this.model attr.name}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
@@ -106,34 +100,25 @@
|
||||
@text="Back"
|
||||
@color="secondary"
|
||||
@route="vault.cluster.secrets.backend.list-root"
|
||||
@model={{this.backendPath}}
|
||||
data-test-secret-generate-back={{true}}
|
||||
@model={{@backendPath}}
|
||||
data-test-back-button
|
||||
/>
|
||||
{{else}}
|
||||
<Hds::Button
|
||||
@text="Back"
|
||||
@color="secondary"
|
||||
{{on "click" (action "newModel")}}
|
||||
data-test-secret-generate-back="true"
|
||||
/>
|
||||
<Hds::Button @text="Back" @color="secondary" {{on "click" this.reset}} data-test-back-button />
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<form {{action "create" on="submit"}} data-test-secret-generate-form="true">
|
||||
<form {{on "submit" this.create}} data-test-secret-generate-form>
|
||||
<div class="box is-sideless no-padding-top is-fullwidth is-marginless">
|
||||
<NamespaceReminder @mode="generate" @noun="credential" />
|
||||
<MessageError @model={{this.model}} />
|
||||
{{#if this.model.helpText}}
|
||||
<p class="is-hint">{{this.model.helpText}}</p>
|
||||
{{/if}}
|
||||
{{#if this.model.fieldGroups}}
|
||||
<FormFieldGroupsLoop @model={{this.model}} @mode={{this.mode}} />
|
||||
{{else}}
|
||||
{{#each this.model.attrs as |attr|}}
|
||||
<FormField data-test-field={{true}} @attr={{attr}} @model={{this.model}} />
|
||||
{{/each}}
|
||||
{{#if this.helpText}}
|
||||
<p class="is-hint">{{this.helpText}}</p>
|
||||
{{/if}}
|
||||
{{#each this.formFields as |key|}}
|
||||
<FormField data-test-field @attr={{get this.model.allByKey key}} @model={{this.model}} />
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<Hds::ButtonSet>
|
||||
@@ -142,14 +127,14 @@
|
||||
@icon={{if this.loading "loading"}}
|
||||
type="submit"
|
||||
disabled={{this.loading}}
|
||||
data-test-secret-generate={{true}}
|
||||
data-test-save
|
||||
/>
|
||||
<Hds::Button
|
||||
@text="Cancel"
|
||||
@route="vault.cluster.secrets.backend.list-root"
|
||||
@color="secondary"
|
||||
@model={{this.backendPath}}
|
||||
data-test-secret-generate-cancel={{true}}
|
||||
@model={{@backendPath}}
|
||||
data-test-cancel
|
||||
/>
|
||||
</Hds::ButtonSet>
|
||||
</div>
|
||||
@@ -4,56 +4,49 @@
|
||||
*/
|
||||
|
||||
import { service } from '@ember/service';
|
||||
import { computed, set } from '@ember/object';
|
||||
import Component from '@ember/component';
|
||||
import { action } from '@ember/object';
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
const MODEL_TYPES = {
|
||||
'ssh-sign': {
|
||||
model: 'ssh-sign',
|
||||
},
|
||||
'ssh-creds': {
|
||||
const CREDENTIAL_TYPES = {
|
||||
ssh: {
|
||||
model: 'ssh-otp-credential',
|
||||
title: 'Generate SSH Credentials',
|
||||
formFields: ['username', 'ip'],
|
||||
displayFields: ['username', 'ip', 'key', 'keyType', 'port'],
|
||||
},
|
||||
'aws-creds': {
|
||||
aws: {
|
||||
model: 'aws-credential',
|
||||
title: 'Generate AWS Credentials',
|
||||
backIsListLink: true,
|
||||
displayFields: ['accessKey', 'secretKey', 'securityToken', 'leaseId', 'renewable', 'leaseDuration'],
|
||||
// aws form fields are dynamic
|
||||
formFields: (model) => {
|
||||
return {
|
||||
iam_user: ['credentialType'],
|
||||
assumed_role: ['credentialType', 'ttl', 'roleArn'],
|
||||
federation_token: ['credentialType', 'ttl'],
|
||||
session_token: ['credentialType', 'ttl'],
|
||||
}[model.credentialType];
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default Component.extend({
|
||||
controlGroup: service(),
|
||||
store: service(),
|
||||
router: service(),
|
||||
// set on the component
|
||||
backendType: null,
|
||||
backendPath: null,
|
||||
roleName: null,
|
||||
action: null,
|
||||
export default class GenerateCredentials extends Component {
|
||||
@service controlGroup;
|
||||
@service store;
|
||||
@service router;
|
||||
|
||||
model: null,
|
||||
loading: false,
|
||||
emptyData: '{\n}',
|
||||
@tracked model;
|
||||
@tracked loading = false;
|
||||
@tracked hasGenerated = false;
|
||||
emptyData = '{\n}';
|
||||
|
||||
modelForType() {
|
||||
const type = this.options;
|
||||
if (type) {
|
||||
return type.model;
|
||||
}
|
||||
// if we don't have a mode for that type then redirect them back to the backend list
|
||||
this.router.transitionTo('vault.cluster.secrets.backend.list-root', this.backendPath);
|
||||
},
|
||||
|
||||
options: computed('action', 'backendType', function () {
|
||||
const action = this.action || 'creds';
|
||||
return MODEL_TYPES[`${this.backendType}-${action}`];
|
||||
}),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.createOrReplaceModel();
|
||||
},
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
const modelType = this.modelForType();
|
||||
this.model = this.generateNewModel(modelType);
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
// components are torn down after store is unloaded and will cause an error if attempt to unload record
|
||||
@@ -61,20 +54,46 @@ export default Component.extend({
|
||||
if (noTeardown && !this.model.isDestroyed && !this.model.isDestroying) {
|
||||
this.model.unloadRecord();
|
||||
}
|
||||
this._super(...arguments);
|
||||
},
|
||||
super.willDestroy();
|
||||
}
|
||||
|
||||
createOrReplaceModel() {
|
||||
const modelType = this.modelForType();
|
||||
const model = this.model;
|
||||
const roleName = this.roleName;
|
||||
const backendPath = this.backendPath;
|
||||
modelForType() {
|
||||
const type = this.options;
|
||||
if (type) {
|
||||
return type.model;
|
||||
}
|
||||
// if we don't have a mode for that type then redirect them back to the backend list
|
||||
this.router.transitionTo('vault.cluster.secrets.backend.list-root', this.args.backendPath);
|
||||
}
|
||||
|
||||
get helpText() {
|
||||
if (this.options?.model === 'aws-credential') {
|
||||
return 'For Vault roles of credential type iam_user, there are no inputs, just submit the form. Choose a type to change the input options.';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
get options() {
|
||||
return CREDENTIAL_TYPES[this.args.backendType];
|
||||
}
|
||||
|
||||
get formFields() {
|
||||
const typeOpts = this.options;
|
||||
if (typeof typeOpts.formFields === 'function') {
|
||||
return typeOpts.formFields(this.model);
|
||||
}
|
||||
return typeOpts.formFields;
|
||||
}
|
||||
|
||||
get displayFields() {
|
||||
return this.options.displayFields;
|
||||
}
|
||||
|
||||
generateNewModel(modelType) {
|
||||
if (!modelType) {
|
||||
return;
|
||||
}
|
||||
if (model) {
|
||||
model.unloadRecord();
|
||||
}
|
||||
const { roleName, backendPath, awsRoleType } = this.args;
|
||||
const attrs = {
|
||||
role: {
|
||||
backend: backendPath,
|
||||
@@ -82,44 +101,60 @@ export default Component.extend({
|
||||
},
|
||||
id: `${backendPath}-${roleName}`,
|
||||
};
|
||||
const newModel = this.store.createRecord(modelType, attrs);
|
||||
this.set('model', newModel);
|
||||
},
|
||||
if (awsRoleType) {
|
||||
// this is only set from route if backendType = aws
|
||||
attrs.credentialType = awsRoleType;
|
||||
}
|
||||
return this.store.createRecord(modelType, attrs);
|
||||
}
|
||||
|
||||
actions: {
|
||||
create() {
|
||||
const model = this.model;
|
||||
this.set('loading', true);
|
||||
this.model
|
||||
.save()
|
||||
.then(() => {
|
||||
model.set('hasGenerated', true);
|
||||
})
|
||||
.catch((error) => {
|
||||
// Handle control group AdapterError
|
||||
if (error.message === 'Control Group encountered') {
|
||||
this.controlGroup.saveTokenFromError(error);
|
||||
const err = this.controlGroup.logFromError(error);
|
||||
error.errors = [err.content];
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
.finally(() => {
|
||||
this.set('loading', false);
|
||||
});
|
||||
},
|
||||
replaceModel() {
|
||||
const modelType = this.modelForType();
|
||||
if (!modelType) {
|
||||
return;
|
||||
}
|
||||
if (this.model) {
|
||||
this.model.unloadRecord();
|
||||
}
|
||||
this.model = this.generateNewModel(modelType);
|
||||
}
|
||||
|
||||
codemirrorUpdated(attr, val, codemirror) {
|
||||
codemirror.performLint();
|
||||
const hasErrors = codemirror.state.lint.marked.length > 0;
|
||||
@action
|
||||
create(evt) {
|
||||
evt.preventDefault();
|
||||
this.loading = true;
|
||||
this.model
|
||||
.save()
|
||||
.then(() => {
|
||||
this.hasGenerated = true;
|
||||
})
|
||||
.catch((error) => {
|
||||
// Handle control group AdapterError
|
||||
if (error.message === 'Control Group encountered') {
|
||||
this.controlGroup.saveTokenFromError(error);
|
||||
const err = this.controlGroup.logFromError(error);
|
||||
error.errors = [err.content];
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasErrors) {
|
||||
set(this.model, attr, JSON.parse(val));
|
||||
}
|
||||
},
|
||||
@action
|
||||
codemirrorUpdated(attr, val, codemirror) {
|
||||
codemirror.performLint();
|
||||
const hasErrors = codemirror.state.lint.marked.length > 0;
|
||||
|
||||
newModel() {
|
||||
this.createOrReplaceModel();
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!hasErrors) {
|
||||
this.model[attr] = JSON.parse(val);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
reset() {
|
||||
this.hasGenerated = false;
|
||||
this.replaceModel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
import Controller from '@ember/controller';
|
||||
|
||||
export default Controller.extend({
|
||||
queryParams: ['action', 'roleType'],
|
||||
action: '',
|
||||
queryParams: ['roleType'],
|
||||
// used for database credentials
|
||||
roleType: '',
|
||||
reset() {
|
||||
this.set('action', '');
|
||||
this.set('roleType', '');
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
|
||||
import Model, { attr } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
|
||||
import { withExpandedAttributes } from 'vault/decorators/model-expanded-attributes';
|
||||
|
||||
const CREDENTIAL_TYPES = [
|
||||
{
|
||||
value: 'iam_user',
|
||||
@@ -25,27 +25,28 @@ const CREDENTIAL_TYPES = [
|
||||
},
|
||||
];
|
||||
|
||||
const DISPLAY_FIELDS = ['accessKey', 'secretKey', 'securityToken', 'leaseId', 'renewable', 'leaseDuration'];
|
||||
export default Model.extend({
|
||||
helpText:
|
||||
'For Vault roles of credential type iam_user, there are no inputs, just submit the form. Choose a type to change the input options.',
|
||||
role: attr('object', {
|
||||
@withExpandedAttributes()
|
||||
export default class AwsCredential extends Model {
|
||||
@attr('object', {
|
||||
readOnly: true,
|
||||
}),
|
||||
})
|
||||
role;
|
||||
|
||||
credentialType: attr('string', {
|
||||
@attr('string', {
|
||||
defaultValue: 'iam_user',
|
||||
possibleValues: CREDENTIAL_TYPES,
|
||||
readOnly: true,
|
||||
}),
|
||||
})
|
||||
credentialType;
|
||||
|
||||
roleArn: attr('string', {
|
||||
@attr('string', {
|
||||
label: 'Role ARN',
|
||||
helpText:
|
||||
'The ARN of the role to assume if credential_type on the Vault role is assumed_role. Optional if the role has a single role ARN; required otherwise.',
|
||||
}),
|
||||
})
|
||||
roleArn;
|
||||
|
||||
ttl: attr({
|
||||
@attr({
|
||||
editType: 'ttl',
|
||||
defaultValue: '3600s',
|
||||
setDefault: true,
|
||||
@@ -53,29 +54,17 @@ export default Model.extend({
|
||||
label: 'TTL',
|
||||
helpText:
|
||||
'Specifies the TTL for the use of the STS token. Valid only when credential_type is assumed_role, federation_token, or session_token.',
|
||||
}),
|
||||
leaseId: attr('string'),
|
||||
renewable: attr('boolean'),
|
||||
leaseDuration: attr('number'),
|
||||
accessKey: attr('string'),
|
||||
secretKey: attr('string'),
|
||||
securityToken: attr('string'),
|
||||
})
|
||||
ttl;
|
||||
|
||||
attrs: computed('credentialType', 'accessKey', 'securityToken', function () {
|
||||
const type = this.credentialType;
|
||||
const fieldsForType = {
|
||||
iam_user: ['credentialType'],
|
||||
assumed_role: ['credentialType', 'ttl', 'roleArn'],
|
||||
federation_token: ['credentialType', 'ttl'],
|
||||
session_token: ['credentialType', 'ttl'],
|
||||
};
|
||||
if (this.accessKey || this.securityToken) {
|
||||
return expandAttributeMeta(this, DISPLAY_FIELDS.slice(0));
|
||||
}
|
||||
return expandAttributeMeta(this, fieldsForType[type].slice(0));
|
||||
}),
|
||||
@attr('string') leaseId;
|
||||
@attr('boolean') renewable;
|
||||
@attr('number') leaseDuration;
|
||||
@attr('string') accessKey;
|
||||
@attr('string', { masked: true }) secretKey;
|
||||
@attr('string', { masked: true }) securityToken;
|
||||
|
||||
toCreds: computed('accessKey', 'secretKey', 'securityToken', 'leaseId', function () {
|
||||
get toCreds() {
|
||||
const props = {
|
||||
accessKey: this.accessKey,
|
||||
secretKey: this.secretKey,
|
||||
@@ -90,5 +79,5 @@ export default Model.extend({
|
||||
return ret;
|
||||
}, {});
|
||||
return JSON.stringify(propsWithVals, null, 2);
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,27 +3,25 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { reads } from '@ember/object/computed';
|
||||
import Model, { attr } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
|
||||
const CREATE_FIELDS = ['username', 'ip'];
|
||||
import { withExpandedAttributes } from 'vault/decorators/model-expanded-attributes';
|
||||
|
||||
const DISPLAY_FIELDS = ['username', 'ip', 'key', 'keyType', 'port'];
|
||||
export default Model.extend({
|
||||
role: attr('object', {
|
||||
@withExpandedAttributes()
|
||||
export default class SshOtpCredential extends Model {
|
||||
@attr('object', {
|
||||
readOnly: true,
|
||||
}),
|
||||
ip: attr('string', {
|
||||
})
|
||||
role;
|
||||
@attr('string', {
|
||||
label: 'IP Address',
|
||||
}),
|
||||
username: attr('string'),
|
||||
key: attr('string'),
|
||||
keyType: attr('string'),
|
||||
port: attr('number'),
|
||||
attrs: computed('key', function () {
|
||||
const keys = this.key ? DISPLAY_FIELDS.slice(0) : CREATE_FIELDS.slice(0);
|
||||
return expandAttributeMeta(this, keys);
|
||||
}),
|
||||
toCreds: reads('key'),
|
||||
});
|
||||
})
|
||||
ip;
|
||||
@attr('string') username;
|
||||
@attr('string', { masked: true }) key;
|
||||
@attr('string') keyType;
|
||||
@attr('number') port;
|
||||
|
||||
get toCreds() {
|
||||
return this.key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,15 @@ export default Route.extend({
|
||||
store: service(),
|
||||
|
||||
beforeModel() {
|
||||
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
||||
if (backend != 'ssh') {
|
||||
return;
|
||||
const { id: backendPath, type: backendType } = this.modelFor('vault.cluster.secrets.backend');
|
||||
// redirect if the backend type does not support credentials
|
||||
if (!SUPPORTED_DYNAMIC_BACKENDS.includes(backendType)) {
|
||||
return this.router.transitionTo('vault.cluster.secrets.backend.list-root', backendPath);
|
||||
}
|
||||
// hydrate model if backend type is ssh
|
||||
if (backendType === 'ssh') {
|
||||
this.pathHelp.getNewModel('ssh-otp-credential', backendPath);
|
||||
}
|
||||
const modelType = 'ssh-otp-credential';
|
||||
return this.pathHelp.getNewModel(modelType, backend);
|
||||
},
|
||||
|
||||
getDatabaseCredential(backend, secret, roleType = '') {
|
||||
@@ -51,23 +54,34 @@ export default Route.extend({
|
||||
});
|
||||
},
|
||||
|
||||
async getAwsRole(backend, id) {
|
||||
try {
|
||||
const role = await this.store.queryRecord('role-aws', { backend, id });
|
||||
return role;
|
||||
} catch (e) {
|
||||
// swallow error, non-essential data
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
async model(params) {
|
||||
const role = params.secret;
|
||||
const { id: backendPath, type: backendType } = this.modelFor('vault.cluster.secrets.backend');
|
||||
const roleType = params.roleType;
|
||||
let dbCred;
|
||||
let dbCred, awsRole;
|
||||
if (backendType === 'database') {
|
||||
dbCred = await this.getDatabaseCredential(backendPath, role, roleType);
|
||||
} else if (backendType === 'aws') {
|
||||
awsRole = await this.getAwsRole(backendPath, role);
|
||||
}
|
||||
if (!SUPPORTED_DYNAMIC_BACKENDS.includes(backendType)) {
|
||||
return this.router.transitionTo('vault.cluster.secrets.backend.list-root', backendPath);
|
||||
}
|
||||
|
||||
return resolve({
|
||||
backendPath,
|
||||
backendType,
|
||||
roleName: role,
|
||||
roleType,
|
||||
dbCred,
|
||||
awsRoleType: awsRole?.credentialType,
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
</div>
|
||||
<div class="field is-grouped-split box is-fullwidth is-bottomless">
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button @text={{if (eq this.mode "create") "Create role" "Save"}} type="submit" data-test-role-aws-create />
|
||||
<Hds::Button @text={{if (eq this.mode "create") "Create role" "Save"}} type="submit" data-test-save />
|
||||
{{#if (eq this.mode "create")}}
|
||||
<Hds::Button
|
||||
@text="Cancel"
|
||||
|
||||
@@ -11,11 +11,10 @@
|
||||
@model={{this.model.dbCred}}
|
||||
/>
|
||||
{{else}}
|
||||
{{! TODO smells a little to have action off of query param requiring a conditional }}
|
||||
<GenerateCredentials
|
||||
@backendPath={{this.model.backendPath}}
|
||||
@backendType={{this.model.backendType}}
|
||||
@roleName={{this.model.roleName}}
|
||||
@action={{if this.action this.action ""}}
|
||||
@awsRoleType={{this.model.awsRoleType}}
|
||||
/>
|
||||
{{/if}}
|
||||
@@ -67,12 +67,7 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="control">
|
||||
<Hds::Button
|
||||
@text="Back"
|
||||
@color="secondary"
|
||||
{{on "click" (action "newModel")}}
|
||||
data-test-secret-generate-back={{true}}
|
||||
/>
|
||||
<Hds::Button @text="Back" @color="secondary" {{on "click" (action "newModel")}} data-test-back-button />
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
@@ -113,14 +108,14 @@
|
||||
@icon={{if this.loading "loading"}}
|
||||
type="submit"
|
||||
disabled={{this.loading}}
|
||||
data-test-secret-generate
|
||||
data-test-save
|
||||
/>
|
||||
<Hds::Button
|
||||
@text="Cancel"
|
||||
@color="secondary"
|
||||
@route="vault.cluster.secrets.backend.list-root"
|
||||
@model={{this.backend.id}}
|
||||
data-test-secret-generate-cancel
|
||||
data-test-cancel
|
||||
/>
|
||||
</Hds::ButtonSet>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { click, fillIn, currentURL, find, settled, waitUntil } from '@ember/test-helpers';
|
||||
import { click, fillIn, currentURL, find, settled, waitUntil, visit } from '@ember/test-helpers';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
@@ -13,7 +13,63 @@ import { GENERAL } from '../helpers/general-selectors';
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import { deleteEngineCmd, mountEngineCmd, runCmd } from 'vault/tests/helpers/commands';
|
||||
import { overrideResponse } from 'vault/tests/helpers/stubs';
|
||||
|
||||
const AWS_CREDS = {
|
||||
configTab: '[data-test-configuration-tab]',
|
||||
configure: '[data-test-secret-backend-configure]',
|
||||
awsForm: '[data-test-aws-root-creds-form]',
|
||||
viewBackend: '[data-test-backend-view-link]',
|
||||
createSecret: '[data-test-secret-create]',
|
||||
secretHeader: '[data-test-secret-header]',
|
||||
secretLink: (name) => (name ? `[data-test-secret-link="${name}"]` : '[data-test-secret-link]'),
|
||||
crumb: (path) => `[data-test-secret-breadcrumb="${path}"] a`,
|
||||
ttlToggle: '[data-test-ttl-toggle="TTL"]',
|
||||
warning: '[data-test-warning]',
|
||||
delete: (role) => `[data-test-aws-role-delete="${role}"]`,
|
||||
backButton: '[data-test-back-button]',
|
||||
generateLink: '[data-test-backend-credentials]',
|
||||
};
|
||||
const ROLE_TYPES = [
|
||||
{
|
||||
credentialType: 'iam_user',
|
||||
async fillOutForm(assert) {
|
||||
// nothing to fill out
|
||||
assert.dom('[data-test-field]').exists({ count: 1 });
|
||||
},
|
||||
expectedPayload: {},
|
||||
},
|
||||
{
|
||||
credentialType: 'assumed_role',
|
||||
async fillOutForm(assert) {
|
||||
await click(GENERAL.toggleInput('TTL'));
|
||||
assert.dom(GENERAL.toggleInput('TTL')).isNotChecked();
|
||||
await fillIn(GENERAL.inputByAttr('roleArn'), 'foobar');
|
||||
},
|
||||
expectedPayload: {
|
||||
role_arn: 'foobar',
|
||||
},
|
||||
},
|
||||
{
|
||||
credentialType: 'federation_token',
|
||||
async fillOutForm(assert) {
|
||||
assert.dom(GENERAL.toggleInput('TTL')).isChecked();
|
||||
await fillIn(GENERAL.ttl.input('TTL'), '3');
|
||||
},
|
||||
expectedPayload: {
|
||||
ttl: '10800s',
|
||||
},
|
||||
},
|
||||
{
|
||||
credentialType: 'session_token',
|
||||
async fillOutForm(assert) {
|
||||
await click(GENERAL.toggleInput('TTL'));
|
||||
assert.dom(GENERAL.toggleInput('TTL')).isNotChecked();
|
||||
},
|
||||
expectedPayload: null,
|
||||
},
|
||||
];
|
||||
module('Acceptance | aws secret backend', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
@@ -30,28 +86,23 @@ module('Acceptance | aws secret backend', function (hooks) {
|
||||
test('aws backend', async function (assert) {
|
||||
const path = `aws-${this.uid}`;
|
||||
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 settled();
|
||||
await click('[data-test-configuration-tab]');
|
||||
await click(AWS_CREDS.configTab);
|
||||
|
||||
await click('[data-test-secret-backend-configure]');
|
||||
await click(AWS_CREDS.configure);
|
||||
|
||||
assert.strictEqual(currentURL(), `/vault/settings/secrets/configure/${path}`);
|
||||
|
||||
assert.dom('[data-test-aws-root-creds-form]').exists();
|
||||
assert.dom(AWS_CREDS.awsForm).exists();
|
||||
assert.dom(GENERAL.tab('access-to-aws')).exists('renders the root creds tab');
|
||||
assert.dom(GENERAL.tab('lease')).exists('renders the leases config tab');
|
||||
|
||||
await fillIn('[data-test-aws-input="accessKey"]', 'foo');
|
||||
await fillIn('[data-test-aws-input="secretKey"]', 'bar');
|
||||
await fillIn(GENERAL.inputByAttr('accessKey'), 'foo');
|
||||
await fillIn(GENERAL.inputByAttr('secretKey'), 'bar');
|
||||
|
||||
await click('[data-test-aws-input="root-save"]');
|
||||
await click(GENERAL.saveButton);
|
||||
|
||||
assert.true(
|
||||
this.flashSuccessSpy.calledWith('The backend configuration saved successfully!'),
|
||||
@@ -60,53 +111,133 @@ module('Acceptance | aws secret backend', function (hooks) {
|
||||
|
||||
await click(GENERAL.tab('lease'));
|
||||
|
||||
await click('[data-test-aws-input="lease-save"]');
|
||||
await click(GENERAL.saveButton);
|
||||
|
||||
assert.true(
|
||||
this.flashSuccessSpy.calledTwice,
|
||||
'a new success flash message is rendered upon saving lease'
|
||||
);
|
||||
|
||||
await click('[data-test-backend-view-link]');
|
||||
await click(AWS_CREDS.viewBackend);
|
||||
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${path}/list`, 'navigates to the roles list');
|
||||
|
||||
await click('[data-test-secret-create]');
|
||||
await click(AWS_CREDS.createSecret);
|
||||
|
||||
assert.dom('[data-test-secret-header]').hasText('Create an AWS Role', 'aws: renders the create page');
|
||||
assert.dom(AWS_CREDS.secretHeader).hasText('Create an AWS Role', 'aws: renders the create page');
|
||||
|
||||
await fillIn('[data-test-input="name"]', roleName);
|
||||
await fillIn(GENERAL.inputByAttr('name'), roleName);
|
||||
|
||||
// save the role
|
||||
await click('[data-test-role-aws-create]');
|
||||
await click(GENERAL.saveButton);
|
||||
await waitUntil(() => currentURL() === `/vault/secrets/${path}/show/${roleName}`); // flaky without this
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/secrets/${path}/show/${roleName}`,
|
||||
'aws: navigates to the show page on creation'
|
||||
);
|
||||
await click(`[data-test-secret-breadcrumb="${path}"] a`);
|
||||
await click(AWS_CREDS.crumb(path));
|
||||
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${path}/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]');
|
||||
assert.dom(AWS_CREDS.secretLink(roleName)).exists();
|
||||
|
||||
//and delete
|
||||
await click(`[data-test-secret-link="${roleName}"] [data-test-popup-menu-trigger]`);
|
||||
await waitUntil(() => find(`[data-test-aws-role-delete="${roleName}"]`)); // flaky without
|
||||
await click(`[data-test-aws-role-delete="${roleName}"]`);
|
||||
await click(`${AWS_CREDS.secretLink(roleName)} [data-test-popup-menu-trigger]`);
|
||||
await waitUntil(() => find(AWS_CREDS.delete(roleName))); // flaky without
|
||||
await click(AWS_CREDS.delete(roleName));
|
||||
await click(GENERAL.confirmButton);
|
||||
assert.dom(`[data-test-secret-link="${roleName}"]`).doesNotExist('aws: role is no longer in the list');
|
||||
assert.dom(AWS_CREDS.secretLink(roleName)).doesNotExist('aws: role is no longer in the list');
|
||||
});
|
||||
|
||||
ROLE_TYPES.forEach((scenario) => {
|
||||
test(`aws credentials - type ${scenario.credentialType}`, async function (assert) {
|
||||
const path = `aws-cred-${this.uid}`;
|
||||
const roleName = `awsrole-${scenario.credentialType}`;
|
||||
this.server.post(`/${path}/creds/${roleName}`, (_, req) => {
|
||||
const payload = JSON.parse(req.requestBody);
|
||||
assert.deepEqual(payload, scenario.expectedPayload);
|
||||
return {
|
||||
data: {
|
||||
access_key: 'AKIA...',
|
||||
secret_key: 'xlCs...',
|
||||
security_token: 'some-token',
|
||||
arn: 'arn:aws:sts::123456789012:assumed-role/DeveloperRole/some-user-supplied-role-session-name',
|
||||
},
|
||||
};
|
||||
});
|
||||
this.server.get(`/${path}/creds/${roleName}`, () => {
|
||||
return {
|
||||
data: {
|
||||
access_key: 'AKIA...',
|
||||
secret_key: 'xlCs...',
|
||||
security_token: 'some-token',
|
||||
arn: 'arn:aws:sts::123456789012:assumed-role/DeveloperRole/some-user-supplied-role-session-name',
|
||||
},
|
||||
};
|
||||
});
|
||||
await runCmd(mountEngineCmd('aws', path));
|
||||
|
||||
await visit(`/vault/secrets/${path}/create`);
|
||||
assert.dom('h1').hasText('Create an AWS Role');
|
||||
await fillIn(GENERAL.inputByAttr('name'), roleName);
|
||||
await fillIn(GENERAL.inputByAttr('credentialType'), scenario.credentialType);
|
||||
await click(GENERAL.saveButton);
|
||||
await waitUntil(() => currentURL() === `/vault/secrets/${path}/show/${roleName}`); // flaky without this
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${path}/show/${roleName}`);
|
||||
await click(AWS_CREDS.generateLink);
|
||||
assert
|
||||
.dom(GENERAL.inputByAttr('credentialType'))
|
||||
.hasValue(scenario.credentialType, 'credentialType matches backing role');
|
||||
|
||||
// based on credentialType, fill out form
|
||||
await scenario.fillOutForm(assert);
|
||||
|
||||
await click(GENERAL.saveButton);
|
||||
assert.dom(AWS_CREDS.warning).exists('Shows access warning after generation');
|
||||
assert.dom(GENERAL.infoRowValue('Access key')).exists();
|
||||
assert.dom(GENERAL.infoRowValue('Secret key')).exists();
|
||||
assert.dom(GENERAL.infoRowValue('Security token')).exists();
|
||||
await visit('/vault/dashboard');
|
||||
|
||||
await runCmd(deleteEngineCmd(path));
|
||||
});
|
||||
});
|
||||
|
||||
test(`aws credentials without role read access`, async function (assert) {
|
||||
const path = `aws-cred-${this.uid}`;
|
||||
const roleName = `awsrole-noread`;
|
||||
this.server.post(`/${path}/creds/${roleName}`, () => {
|
||||
return {
|
||||
data: {
|
||||
access_key: 'AKIA...',
|
||||
secret_key: 'xlCs...',
|
||||
security_token: 'some-token',
|
||||
arn: 'arn:aws:sts::123456789012:assumed-role/DeveloperRole/some-user-supplied-role-session-name',
|
||||
},
|
||||
};
|
||||
});
|
||||
this.server.get(`/${path}/roles/${roleName}`, () => overrideResponse(403));
|
||||
await runCmd(mountEngineCmd('aws', path));
|
||||
await runCmd(`write ${path}/roles/${roleName} credential_type=assumed_role`);
|
||||
|
||||
await visit(`/vault/secrets/${path}/list`);
|
||||
assert.dom(AWS_CREDS.secretLink(roleName)).exists();
|
||||
await click(AWS_CREDS.secretLink(roleName));
|
||||
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${path}/credentials/${roleName}`);
|
||||
assert
|
||||
.dom(GENERAL.inputByAttr('credentialType'))
|
||||
.hasValue('iam_user', 'credentialType defaults to first in list due to no role read permissions');
|
||||
|
||||
await fillIn(GENERAL.inputByAttr('credentialType'), 'assumed_role');
|
||||
|
||||
await click(GENERAL.saveButton);
|
||||
assert.dom(AWS_CREDS.warning).exists('Shows access warning after generation');
|
||||
assert.dom(GENERAL.infoRowValue('Access key')).exists();
|
||||
assert.dom(GENERAL.infoRowValue('Secret key')).exists();
|
||||
assert.dom(GENERAL.infoRowValue('Security token')).exists();
|
||||
await visit('/vault/dashboard');
|
||||
|
||||
await runCmd(deleteEngineCmd(path));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,14 +3,23 @@
|
||||
* 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,
|
||||
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 { setRunOptions } from 'ember-a11y-testing/test-support';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
|
||||
module('Acceptance | ssh secret backend', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
@@ -26,6 +35,7 @@ module('Acceptance | ssh secret backend', function (hooks) {
|
||||
{
|
||||
type: 'ca',
|
||||
name: 'carole',
|
||||
credsRoute: 'vault.cluster.secrets.backend.sign',
|
||||
async fillInCreate() {
|
||||
await click('[data-test-input="allowUserCertificates"]');
|
||||
},
|
||||
@@ -61,6 +71,7 @@ module('Acceptance | ssh secret backend', function (hooks) {
|
||||
{
|
||||
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"]');
|
||||
@@ -84,13 +95,7 @@ module('Acceptance | ssh secret backend', function (hooks) {
|
||||
},
|
||||
];
|
||||
test('ssh backend', async function (assert) {
|
||||
// Popup menu causes flakiness
|
||||
setRunOptions({
|
||||
rules: {
|
||||
'color-contrast': { enabled: false },
|
||||
},
|
||||
});
|
||||
assert.expect(28);
|
||||
assert.expect(30);
|
||||
const sshPath = `ssh-${this.uid}`;
|
||||
|
||||
await enablePage.enable('ssh', sshPath);
|
||||
@@ -100,27 +105,24 @@ module('Acceptance | ssh secret backend', function (hooks) {
|
||||
await click('[data-test-secret-backend-configure]');
|
||||
|
||||
assert.strictEqual(currentURL(), `/vault/settings/secrets/configure/${sshPath}`);
|
||||
assert.ok(findAll('[data-test-ssh-configure-form]').length, 'renders the empty configuration form');
|
||||
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"]');
|
||||
|
||||
assert.ok(
|
||||
await waitUntil(() => findAll('[data-test-ssh-input="public-key"]').length),
|
||||
'a public key is fetched'
|
||||
);
|
||||
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]');
|
||||
await click('[data-test-secret-create]');
|
||||
|
||||
assert.ok(
|
||||
find('[data-test-secret-header]').textContent.includes('SSH Role'),
|
||||
`${role.type}: renders the create page`
|
||||
);
|
||||
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);
|
||||
@@ -138,7 +140,7 @@ module('Acceptance | ssh secret backend', function (hooks) {
|
||||
|
||||
// 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();
|
||||
@@ -146,29 +148,23 @@ module('Acceptance | ssh secret backend', function (hooks) {
|
||||
}
|
||||
|
||||
// generate creds
|
||||
await click('[data-test-secret-generate]');
|
||||
await click(GENERAL.saveButton);
|
||||
await settled(); // eslint-disable-line
|
||||
role.assertAfterGenerate(assert, sshPath);
|
||||
|
||||
// click the "Back" button
|
||||
await click('[data-test-secret-generate-back]');
|
||||
await click('[data-test-back-button]');
|
||||
|
||||
assert.ok(
|
||||
findAll('[data-test-secret-generate-form]').length,
|
||||
`${role.type}: back takes you back to the form`
|
||||
);
|
||||
assert.dom('[data-test-secret-generate-form]').exists(`${role.type}: back takes you back to the form`);
|
||||
|
||||
await click('[data-test-secret-generate-cancel]');
|
||||
await click(GENERAL.cancelButton);
|
||||
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/secrets/${sshPath}/list`,
|
||||
`${role.type}: cancel takes you to ssh index`
|
||||
);
|
||||
assert.ok(
|
||||
findAll(`[data-test-secret-link="${role.name}"]`).length,
|
||||
`${role.type}: role shows in the list`
|
||||
);
|
||||
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]`);
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { Base } from '../create';
|
||||
import { settled } from '@ember/test-helpers';
|
||||
import { clickable, visitable, create, fillable } from 'ember-cli-page-object';
|
||||
|
||||
export default create({
|
||||
...Base,
|
||||
visitEdit: visitable('/vault/secrets/:backend/edit/:id'),
|
||||
visitEditRoot: visitable('/vault/secrets/:backend/edit'),
|
||||
toggleDomain: clickable('[data-test-toggle-group="Domain Handling"]'),
|
||||
toggleOptions: clickable('[data-test-toggle-group="Options"]'),
|
||||
name: fillable('[data-test-input="name"]'),
|
||||
allowAnyName: clickable('[data-test-input="allowAnyName"]'),
|
||||
allowedDomains: fillable('[data-test-input="allowedDomains"] .input'),
|
||||
save: clickable('[data-test-role-create]'),
|
||||
|
||||
async createRole(name, allowedDomains) {
|
||||
await this.toggleDomain();
|
||||
await settled();
|
||||
await this.toggleOptions();
|
||||
await settled();
|
||||
await this.name(name);
|
||||
await settled();
|
||||
await this.allowAnyName();
|
||||
await settled();
|
||||
await this.allowedDomains(allowedDomains);
|
||||
await settled();
|
||||
await this.save();
|
||||
await settled();
|
||||
},
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { Base } from '../credentials';
|
||||
import { clickable, text, value, create, fillable, isPresent } from 'ember-cli-page-object';
|
||||
|
||||
export default create({
|
||||
...Base,
|
||||
title: text('[data-test-title]'),
|
||||
commonName: fillable('[data-test-input="commonName"]'),
|
||||
commonNameValue: value('[data-test-input="commonName"]'),
|
||||
csr: fillable('[data-test-input="csr"]'),
|
||||
submit: clickable('[data-test-secret-generate]'),
|
||||
back: clickable('[data-test-secret-generate-back]'),
|
||||
certificate: text('[data-test-row-value="Certificate"]'),
|
||||
toggleOptions: clickable('[data-test-toggle-group]'),
|
||||
enableTtl: clickable('[data-test-toggle-input]'),
|
||||
hasCert: isPresent('[data-test-row-value="Certificate"]'),
|
||||
fillInTime: fillable('[data-test-ttl-value]'),
|
||||
fillInField: fillable('[data-test-select="ttl-unit"]'),
|
||||
issueCert: async function (commonName) {
|
||||
await this.commonName(commonName).toggleOptions().enableTtl().fillInField('h').fillInTime('30').submit();
|
||||
},
|
||||
|
||||
sign: async function (commonName, csr) {
|
||||
return this.csr(csr)
|
||||
.commonName(commonName)
|
||||
.toggleOptions()
|
||||
.enableTtl()
|
||||
.fillInField('h')
|
||||
.fillInTime('30')
|
||||
.submit();
|
||||
},
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { Base } from '../show';
|
||||
import { settled } from '@ember/test-helpers';
|
||||
import { create, clickable, collection, text, isPresent } from 'ember-cli-page-object';
|
||||
|
||||
export default create({
|
||||
...Base,
|
||||
rows: collection('data-test-row-label'),
|
||||
certificate: text('[data-test-row-value="Certificate"]'),
|
||||
hasCert: isPresent('[data-test-row-value="Certificate"]'),
|
||||
edit: clickable('[data-test-edit-link]'),
|
||||
generateCert: clickable('[data-test-credentials-link]'),
|
||||
deleteBtn: clickable('[data-test-role-delete] button'),
|
||||
confirmBtn: clickable('[data-test-confirm-button]'),
|
||||
async deleteRole() {
|
||||
await this.deleteBtn();
|
||||
await settled();
|
||||
await this.confirmBtn();
|
||||
await settled();
|
||||
},
|
||||
});
|
||||
@@ -14,8 +14,8 @@ export default create({
|
||||
ip: fillable('[data-test-input="ip"]'),
|
||||
warningIsPresent: isPresent('[data-test-warning]'),
|
||||
commonNameValue: value('[data-test-input="commonName"]'),
|
||||
submit: clickable('[data-test-secret-generate]'),
|
||||
back: clickable('[data-test-secret-generate-back]'),
|
||||
submit: clickable('[data-test-save]'),
|
||||
back: clickable('[data-test-back-button]'),
|
||||
generateOTP: async function () {
|
||||
await this.user('admin').ip('192.168.1.1').submit();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user