mirror of
https://github.com/hashicorp/vault.git
synced 2026-06-08 16:24:51 -04:00
* migrating db overview page * fix toolbar alignment on remaining links * migrating database creds + minor secrets table fix * update totp key fetch * removing store for aws * fix workflow test * removed commented code * fix return line * [UI] Ember Data Migration - Core Addon (#14891) * removes store service from confirm-leave decorator * updates secret list header tab component to use capabilities service for database type * removes store service from edit-form component * removes ember data fetch support from InfoTableItemArray component * removes store from shamir components * removes store from replication components in core addon * adds missing service injection to shamir flow component * fixes reduced disclosure test * fixes issues with seal/unseal workflow * reverts assertion change in info-table-item-array test * fixes database test * updates shamir flow test * removes commented out code * fix pathfors * dont throw messages that dont need to be thrown :) * updating to use allSettled * matching whats in adapter * fix * updating to use enums * [UI] Ember Data Migration - TOTP Secrets Engine Views | VAULT-44225 (#14933) * VAULT-44225 - edm secrets totp views * fixed review comments and updated validations to match original * fixed review comments * fix 2 * update to parseError * fix --------- Co-authored-by: Dan Rivera <dan.rivera@hashicorp.com> Co-authored-by: Jordan Reimer <zofskeez@gmail.com> Co-authored-by: mohit-hashicorp <mohit.ojha@hashicorp.com>
This commit is contained in:
parent
60e61741f9
commit
b00064cba2
8 changed files with 110 additions and 60 deletions
|
|
@ -41,22 +41,22 @@
|
|||
<InfoTableRow @label="Password" @value={{@model.password}}>
|
||||
<MaskedInput @value={{@model.password}} @name="Password" @displayOnly={{true}} @allowCopy={{true}} />
|
||||
</InfoTableRow>
|
||||
<InfoTableRow @label="Lease ID" @value={{@model.leaseId}} />
|
||||
<InfoTableRow @label="Lease Duration" @value={{format-duration @model.leaseDuration}} />
|
||||
<InfoTableRow @label="Lease ID" @value={{@model.lease_id}} />
|
||||
<InfoTableRow @label="Lease Duration" @value={{format-duration @model.lease_duration}} />
|
||||
{{/if}}
|
||||
{{! STATIC ROLE }}
|
||||
{{#if (and (eq @roleType "static") @model.username)}}
|
||||
<InfoTableRow
|
||||
@label="Last Vault rotation"
|
||||
@value={{date-format @model.lastVaultRotation "MMMM d yyyy, h:mm:ss a"}}
|
||||
@tooltipText={{@model.lastVaultRotation}}
|
||||
@value={{date-format @model.last_vault_rotation "MMMM d yyyy, h:mm:ss a"}}
|
||||
@tooltipText={{@model.last_vault_rotation}}
|
||||
@addCopyButton={{true}}
|
||||
/>
|
||||
<InfoTableRow @label="Password" @value={{@model.password}}>
|
||||
<MaskedInput @value={{@model.password}} @name="Password" @displayOnly={{true}} @allowCopy={{true}} />
|
||||
</InfoTableRow>
|
||||
<InfoTableRow @label="Username" @value={{@model.username}} />
|
||||
<InfoTableRow @label="Rotation Period" @value={{format-duration @model.rotationPeriod}} />
|
||||
<InfoTableRow @label="Rotation Period" @value={{format-duration @model.rotation_period}} />
|
||||
<InfoTableRow @label="Time Remaining" @value={{format-duration @model.ttl}} />
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -62,23 +62,23 @@ export default class SecretEngineList extends Component<Args> {
|
|||
{
|
||||
key: 'accessor',
|
||||
label: 'Accessor',
|
||||
width: '175px',
|
||||
width: '205px',
|
||||
},
|
||||
{
|
||||
key: 'description',
|
||||
label: 'Description',
|
||||
width: '300px',
|
||||
width: '320px',
|
||||
},
|
||||
{
|
||||
key: 'running_plugin_version',
|
||||
label: 'Version',
|
||||
isSortable: true,
|
||||
width: '170px',
|
||||
width: '175px',
|
||||
},
|
||||
{
|
||||
key: 'popupMenu',
|
||||
label: 'Action',
|
||||
width: '75px',
|
||||
width: '80px',
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@
|
|||
...attributes
|
||||
>
|
||||
{{yield}}
|
||||
<Icon @name={{this.glyph}} />
|
||||
<Icon class="toolbar-icon" @name={{this.glyph}} />
|
||||
</SecretLink>
|
||||
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import ControlGroupError from 'vault/lib/control-group-error';
|
||||
|
||||
const SUPPORTED_DYNAMIC_BACKENDS = ['database', 'ssh', 'aws', 'totp'];
|
||||
|
||||
|
|
@ -13,7 +12,6 @@ export default Route.extend({
|
|||
templateName: 'vault/cluster/secrets/backend/credentials',
|
||||
pathHelp: service('path-help'),
|
||||
router: service(),
|
||||
store: service(),
|
||||
api: service(),
|
||||
|
||||
beforeModel(transition) {
|
||||
|
|
@ -34,21 +32,42 @@ export default Route.extend({
|
|||
}
|
||||
},
|
||||
|
||||
getDatabaseCredential(backend, secret, roleType = '') {
|
||||
return this.store.queryRecord('database/credential', { backend, secret, roleType }).catch((error) => {
|
||||
if (error instanceof ControlGroupError) {
|
||||
throw error;
|
||||
async getDatabaseCredential(backend, secret, roleType = '') {
|
||||
try {
|
||||
if (roleType === 'static') {
|
||||
const { last_vault_rotation, lease_duration, data } =
|
||||
await this.api.secrets.databaseReadStaticRoleCredentials(secret, backend);
|
||||
return {
|
||||
last_vault_rotation,
|
||||
lease_duration,
|
||||
...data,
|
||||
};
|
||||
} else {
|
||||
const { data, lease_id, lease_duration } = await this.api.secrets.databaseGenerateCredentials(
|
||||
secret,
|
||||
backend
|
||||
);
|
||||
return {
|
||||
...data,
|
||||
lease_id,
|
||||
lease_duration,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
const { response } = await this.api.parseError(error);
|
||||
if (response.isControlGroupError) {
|
||||
throw response;
|
||||
}
|
||||
// Unless it's a control group error, we want to pass back error info
|
||||
// so we can render it on the GenerateCredentialsDatabase component
|
||||
return error;
|
||||
});
|
||||
return response;
|
||||
}
|
||||
},
|
||||
|
||||
async getAwsRole(backend, id) {
|
||||
try {
|
||||
const role = await this.store.queryRecord('role-aws', { backend, id });
|
||||
return role;
|
||||
const { data } = await this.api.secrets.awsReadRole(id, backend);
|
||||
return data;
|
||||
} catch (e) {
|
||||
// swallow error, non-essential data
|
||||
return;
|
||||
|
|
@ -57,8 +76,8 @@ export default Route.extend({
|
|||
|
||||
async getTotpKey(backend, keyName) {
|
||||
try {
|
||||
const resp = await this.api.secrets.totpReadKey(keyName, backend);
|
||||
return resp.data || {};
|
||||
const { data } = await this.api.secrets.totpReadKey(keyName, backend);
|
||||
return data || {};
|
||||
} catch (e) {
|
||||
// swallow error, non-essential data
|
||||
return {};
|
||||
|
|
@ -86,19 +105,11 @@ export default Route.extend({
|
|||
roleName: role,
|
||||
roleType,
|
||||
dbCred,
|
||||
awsRoleType: awsRole?.credentialType,
|
||||
awsRoleType: awsRole?.credential_type,
|
||||
};
|
||||
},
|
||||
|
||||
resetController(controller) {
|
||||
controller.reset();
|
||||
},
|
||||
|
||||
actions: {
|
||||
willTransition() {
|
||||
// we do not want to save any of the credential information in the store.
|
||||
// once the user navigates away from this page, remove all credential info.
|
||||
this.store.unloadAll('database/credential');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,54 +7,96 @@ import Route from '@ember/routing/route';
|
|||
import { hash } from 'rsvp';
|
||||
import { service } from '@ember/service';
|
||||
import { getEnginePathParam } from 'vault/utils/backend-route-helpers';
|
||||
import {
|
||||
SecretsApiDatabaseListStaticRolesListEnum,
|
||||
SecretsApiDatabaseListRolesListEnum,
|
||||
SecretsApiDatabaseListConnectionsListEnum,
|
||||
} from '@hashicorp/vault-client-typescript';
|
||||
|
||||
export default Route.extend({
|
||||
store: service(),
|
||||
capabilities: service(),
|
||||
api: service(),
|
||||
type: '',
|
||||
|
||||
// this only grabs connections for current db backend, only used to populate # of connections
|
||||
async fetchConnection(queryOptions) {
|
||||
try {
|
||||
return await this.store.query('database/connection', queryOptions);
|
||||
} catch (e) {
|
||||
return e.httpStatus;
|
||||
const { keys } = await this.api.secrets.databaseListConnections(
|
||||
queryOptions.backend,
|
||||
SecretsApiDatabaseListConnectionsListEnum.TRUE
|
||||
);
|
||||
return keys;
|
||||
} catch (error) {
|
||||
const { status } = await this.api.parseError(error);
|
||||
if (status === 404) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// this grabs both dynamic and static roles for current db backend, only used to populate # of roles
|
||||
async fetchAllRoles(queryOptions) {
|
||||
try {
|
||||
return await this.store.query('database/role', queryOptions);
|
||||
} catch (e) {
|
||||
return e.httpStatus;
|
||||
}
|
||||
},
|
||||
const roles = [];
|
||||
const { backend } = queryOptions;
|
||||
const [staticResp, dynamicResp] = await Promise.allSettled([
|
||||
this.api.secrets.databaseListStaticRoles(backend, SecretsApiDatabaseListStaticRolesListEnum.TRUE),
|
||||
this.api.secrets.databaseListRoles(backend, SecretsApiDatabaseListRolesListEnum.TRUE),
|
||||
]);
|
||||
|
||||
pathQuery(backend, endpoint) {
|
||||
return {
|
||||
id: `${backend}/${endpoint}/`,
|
||||
};
|
||||
if (staticResp.status === 'rejected' && dynamicResp.status === 'rejected') {
|
||||
const { response: staticError, status: staticStatus } = await this.api.parseError(staticResp.reason);
|
||||
const { response: dynamicError, status: dynamicStatus } = await this.api.parseError(
|
||||
dynamicResp.reason
|
||||
);
|
||||
if (staticError?.isControlGroupError) {
|
||||
throw staticError;
|
||||
}
|
||||
throw staticStatus < dynamicStatus ? dynamicError : staticError;
|
||||
} else {
|
||||
if (staticResp.value) {
|
||||
roles.push(...staticResp.value.keys);
|
||||
}
|
||||
if (dynamicResp.value) {
|
||||
roles.push(...dynamicResp.value.keys);
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
} catch (error) {
|
||||
const { status } = await this.api.parseError(error);
|
||||
if (status === 404) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async fetchCapabilitiesRole(queryOptions) {
|
||||
return this.store.queryRecord('capabilities', this.pathQuery(queryOptions.backend, 'roles'));
|
||||
const paths = [this.capabilities.pathFor('databaseRoles', { backend: queryOptions.backend })];
|
||||
const capabilities = paths ? await this.capabilities.fetch(paths) : {};
|
||||
return capabilities[paths[0]];
|
||||
},
|
||||
|
||||
async fetchCapabilitiesStaticRole(queryOptions) {
|
||||
return this.store.queryRecord('capabilities', this.pathQuery(queryOptions.backend, 'static-roles'));
|
||||
const paths = [this.capabilities.pathFor('databaseStaticRoles', { backend: queryOptions.backend })];
|
||||
const capabilities = paths ? await this.capabilities.fetch(paths) : {};
|
||||
return capabilities[paths[0]];
|
||||
},
|
||||
|
||||
async fetchCapabilitiesConnection(queryOptions) {
|
||||
return this.store.queryRecord('capabilities', this.pathQuery(queryOptions.backend, 'config'));
|
||||
const paths = [this.capabilities.pathFor('databaseConfig', { backend: queryOptions.backend })];
|
||||
const capabilities = paths ? await this.capabilities.fetch(paths) : {};
|
||||
return capabilities[paths[0]];
|
||||
},
|
||||
|
||||
model() {
|
||||
async model() {
|
||||
const backend = getEnginePathParam(this);
|
||||
const queryOptions = { backend, id: '' };
|
||||
|
||||
const connection = this.fetchConnection(queryOptions);
|
||||
const role = this.fetchAllRoles(queryOptions);
|
||||
const roleCapabilities = this.fetchCapabilitiesRole(queryOptions);
|
||||
const staticRoleCapabilities = this.fetchCapabilitiesStaticRole(queryOptions);
|
||||
const connectionCapabilities = this.fetchCapabilitiesConnection(queryOptions);
|
||||
const connection = await this.fetchConnection(queryOptions);
|
||||
const role = await this.fetchAllRoles(queryOptions);
|
||||
const roleCapabilities = await this.fetchCapabilitiesRole(queryOptions);
|
||||
const staticRoleCapabilities = await this.fetchCapabilitiesStaticRole(queryOptions);
|
||||
const connectionCapabilities = await this.fetchCapabilitiesConnection(queryOptions);
|
||||
|
||||
return hash({
|
||||
backend,
|
||||
|
|
@ -71,7 +113,7 @@ export default Route.extend({
|
|||
|
||||
setupController(controller, model) {
|
||||
this._super(...arguments);
|
||||
const showEmptyState = model.connections === 404 && model.roles === 404;
|
||||
const showEmptyState = model.connections === 404 && (model.roles === undefined || model.roles === 404);
|
||||
const noConnectionCapabilities =
|
||||
!model.connectionCapabilities.canList &&
|
||||
!model.connectionCapabilities.canCreate &&
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<GenerateCredentialsDatabase
|
||||
@backendPath={{this.model.backendPath}}
|
||||
@roleName={{this.model.roleName}}
|
||||
@roleType={{this.model.dbCred.roleType}}
|
||||
@roleType={{this.model.roleType}}
|
||||
@model={{this.model.dbCred}}
|
||||
/>
|
||||
{{else if (eq this.model.backendType "totp")}}
|
||||
|
|
|
|||
|
|
@ -644,10 +644,7 @@ module('Acceptance | secrets/database/*', function (hooks) {
|
|||
assert
|
||||
.dom('[data-test-secret-list-tab="Roles"]')
|
||||
.doesNotExist(`does not show the roles tab because it does not have permissions`);
|
||||
assert
|
||||
.dom('[data-test-overview-card="Connections"]')
|
||||
.exists({ count: 1 }, 'renders only the connection card');
|
||||
await click('[data-test-action-text="Configure new"]');
|
||||
await click(SES.createSecretLink);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets-engines/${backend}/create?itemType=connection`);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ module('Acceptance | database workflow', function (hooks) {
|
|||
.hasText('generated-password', 'Password is generated');
|
||||
assert
|
||||
.dom(GENERAL.infoRowValue('Lease Duration'))
|
||||
.hasText('3600', 'shows lease duration from response');
|
||||
.hasText('1 hour', 'shows lease duration from response');
|
||||
assert
|
||||
.dom(GENERAL.infoRowValue('Lease ID'))
|
||||
.hasText(`database/creds/${roleName}/abcd`, 'shows lease ID from response');
|
||||
|
|
|
|||
Loading…
Reference in a new issue