Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI/database cg read role #12111

Merged
merged 10 commits into from Jul 20, 2021
3 changes: 3 additions & 0 deletions changelog/12111.txt
@@ -0,0 +1,3 @@
```release-note:bug
ui: Fix database role CG access
```
27 changes: 15 additions & 12 deletions ui/app/adapters/database/credential.js
@@ -1,5 +1,6 @@
import RSVP from 'rsvp';
import { allSettled } from 'rsvp';
import ApplicationAdapter from '../application';
import ControlGroupError from 'vault/lib/control-group-error';

export default ApplicationAdapter.extend({
namespace: 'v1',
Expand All @@ -20,18 +21,20 @@ export default ApplicationAdapter.extend({

fetchByQuery(store, query) {
const { backend, secret } = query;
return RSVP.allSettled([this._staticCreds(backend, secret), this._dynamicCreds(backend, secret)]).then(
if (query.roleType === 'static') {
return this._staticCreds(backend, secret);
} else if (query.roleType === 'dynamic') {
return this._dynamicCreds(backend, secret);
}
return allSettled([this._staticCreds(backend, secret), this._dynamicCreds(backend, secret)]).then(
([staticResp, dynamicResp]) => {
// If one comes back with wrapped response from control group, throw it
const accessor = staticResp.accessor || dynamicResp.accessor;
if (accessor) {
throw accessor;
}
// if neither has payload, throw reason with highest httpStatus
if (!staticResp.value && !dynamicResp.value) {
let reason = dynamicResp.reason;
if (reason?.httpStatus < staticResp.reason?.httpStatus) {
reason = staticResp.reason;
if (staticResp.state === 'rejected' && dynamicResp.state === 'rejected') {
let reason = staticResp.reason;
if (dynamicResp.reason instanceof ControlGroupError) {
throw dynamicResp.reason;
}
if (reason?.httpStatus < dynamicResp.reason?.httpStatus) {
reason = dynamicResp.reason;
}
throw reason;
}
Expand Down
87 changes: 56 additions & 31 deletions ui/app/adapters/database/role.js
@@ -1,5 +1,6 @@
import { assign } from '@ember/polyfills';
import { assert } from '@ember/debug';
import ControlGroupError from 'vault/lib/control-group-error';
import ApplicationAdapter from '../application';
import { allSettled } from 'rsvp';
import { addToArray } from 'vault/helpers/add-to-array';
Expand All @@ -24,11 +25,31 @@ export default ApplicationAdapter.extend({
},

staticRoles(backend, id) {
return this.ajax(this.urlFor(backend, id, 'static'), 'GET', this.optionsForQuery(id));
return this.ajax(this.urlFor(backend, id, 'static'), 'GET', this.optionsForQuery(id)).then(resp => {
if (id) {
return {
...resp,
type: 'static',
backend,
id,
};
}
return resp;
});
},

dynamicRoles(backend, id) {
return this.ajax(this.urlFor(backend, id), 'GET', this.optionsForQuery(id));
return this.ajax(this.urlFor(backend, id), 'GET', this.optionsForQuery(id)).then(resp => {
if (id) {
return {
...resp,
type: 'dynamic',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

smart move on adding a type.

backend,
id,
};
}
return resp;
});
},

optionsForQuery(id) {
Expand All @@ -39,39 +60,43 @@ export default ApplicationAdapter.extend({
return { data };
},

fetchByQuery(store, query) {
const { backend, id } = query;
return this.ajax(this.urlFor(backend, id), 'GET', this.optionsForQuery(id)).then(resp => {
resp.id = id;
resp.backend = backend;
return resp;
});
},

queryRecord(store, type, query) {
const { backend, id } = query;
const staticReq = this.staticRoles(backend, id);
const dynamicReq = this.dynamicRoles(backend, id);

return allSettled([staticReq, dynamicReq]).then(([staticResp, dynamicResp]) => {
if (!staticResp.value && !dynamicResp.value) {
// Throw error, both reqs failed
throw dynamicResp.reason;
if (query.type === 'static') {
return this.staticRoles(backend, id);
} else if (query?.type === 'dynamic') {
return this.dynamicRoles(backend, id);
}
// if role type is not defined, try both
return allSettled([this.staticRoles(backend, id), this.dynamicRoles(backend, id)]).then(
([staticResp, dynamicResp]) => {
if (staticResp.state === 'rejected' && dynamicResp.state === 'rejected') {
let reason = staticResp.reason;
if (dynamicResp.reason instanceof ControlGroupError) {
throw dynamicResp.reason;
}
if (reason?.httpStatus < dynamicResp.reason?.httpStatus) {
reason = dynamicResp.reason;
}
throw reason;
}
// Names are distinct across both types of role,
// so only one request should ever come back with value
let type = staticResp.value ? 'static' : 'dynamic';
let successful = staticResp.value || dynamicResp.value;
let resp = {
data: {},
backend,
id,
type,
};

resp.data = assign({}, successful.data);

return resp;
}
// Names are distinct across both types of role,
// so only one request should ever come back with value
let type = staticResp.value ? 'static' : 'dynamic';
let successful = staticResp.value || dynamicResp.value;
let resp = {
data: {},
backend,
secret: id,
};

resp.data = assign({}, resp.data, successful.data, { backend, type, secret: id });

return resp;
});
);
},

query(store, type, query) {
Expand Down
6 changes: 4 additions & 2 deletions ui/app/components/database-role-edit.js
Expand Up @@ -54,8 +54,10 @@ export default class DatabaseRoleEdit extends Component {
}

@action
generateCreds(roleId) {
this.router.transitionTo('vault.cluster.secrets.backend.credentials', roleId);
generateCreds(roleId, roleType = '') {
this.router.transitionTo('vault.cluster.secrets.backend.credentials', roleId, {
queryParams: { roleType },
});
}

@action
Expand Down
@@ -1,9 +1,11 @@
import Controller from '@ember/controller';

export default Controller.extend({
queryParams: ['action'],
queryParams: ['action', 'roleType'],
action: '',
roleType: '',
reset() {
this.set('action', '');
this.set('roleType', '');
},
});
4 changes: 3 additions & 1 deletion ui/app/controllers/vault/cluster/secrets/backend/show.js
Expand Up @@ -3,12 +3,14 @@ import BackendCrumbMixin from 'vault/mixins/backend-crumb';

export default Controller.extend(BackendCrumbMixin, {
backendController: controller('vault.cluster.secrets.backend'),
queryParams: ['tab', 'version'],
queryParams: ['tab', 'version', 'type'],
version: '',
tab: '',
type: '',
reset() {
this.set('tab', '');
this.set('version', '');
this.set('type', '');
},
actions: {
refresh: function() {
Expand Down
5 changes: 4 additions & 1 deletion ui/app/helpers/secret-query-params.js
@@ -1,9 +1,12 @@
import { helper } from '@ember/component/helper';

export function secretQueryParams([backendType]) {
export function secretQueryParams([backendType, type = '']) {
if (backendType === 'transit') {
return { tab: 'actions' };
}
if (backendType === 'database') {
return { type: type };
}
return;
}

Expand Down
2 changes: 2 additions & 0 deletions ui/app/models/database/role.js
Expand Up @@ -127,7 +127,9 @@ export default Model.extend({
staticPath: lazyCapabilities(apiPath`${'backend'}/static-roles/+`, 'backend'),
canCreateStatic: alias('staticPath.canCreate'),
credentialPath: lazyCapabilities(apiPath`${'backend'}/creds/${'id'}`, 'backend', 'id'),
staticCredentialPath: lazyCapabilities(apiPath`${'backend'}/static-creds/${'id'}`, 'backend', 'id'),
canGenerateCredentials: alias('credentialPath.canRead'),
canGetCredentials: alias('staticCredentialPath.canRead'),
databasePath: lazyCapabilities(apiPath`${'backend'}/config/${'database[0]'}`, 'backend', 'database'),
canUpdateDb: alias('databasePath.canUpdate'),
});
6 changes: 3 additions & 3 deletions ui/app/routes/vault/cluster/secrets/backend/credentials.js
Expand Up @@ -23,8 +23,8 @@ export default Route.extend({
return this.pathHelp.getNewModel(modelType, backend);
},

getDatabaseCredential(backend, secret) {
return this.store.queryRecord('database/credential', { backend, secret }).catch(error => {
getDatabaseCredential(backend, secret, roleType = '') {
return this.store.queryRecord('database/credential', { backend, secret, roleType }).catch(error => {
if (error instanceof ControlGroupError) {
throw error;
}
Expand Down Expand Up @@ -57,7 +57,7 @@ export default Route.extend({
let roleType = params.roleType;
let dbCred;
if (backendType === 'database') {
dbCred = await this.getDatabaseCredential(backendPath, role);
dbCred = await this.getDatabaseCredential(backendPath, role, roleType);
}
if (!SUPPORTED_DYNAMIC_BACKENDS.includes(backendModel.get('type'))) {
return this.transitionTo('vault.cluster.secrets.backend.list-root', backendPath);
Expand Down
Expand Up @@ -219,6 +219,7 @@ export default Route.extend(UnloadModelRoute, {
let secret = this.secretParam();
let backend = this.enginePathParam();
let modelType = this.modelType(backend, secret);
let type = params.type || '';
if (!secret) {
secret = '\u0020';
}
Expand All @@ -235,7 +236,7 @@ export default Route.extend(UnloadModelRoute, {

let capabilities = this.capabilities(secret, modelType);
try {
secretModel = await this.store.queryRecord(modelType, { id: secret, backend });
secretModel = await this.store.queryRecord(modelType, { id: secret, backend, type });
} catch (err) {
// we've failed the read request, but if it's a kv-type backend, we want to
// do additional checks of the capabilities
Expand Down
7 changes: 4 additions & 3 deletions ui/app/serializers/database/role.js
Expand Up @@ -17,7 +17,7 @@ export default RESTSerializer.extend({
return roles;
}
let path = 'roles';
if (payload.data.type === 'static') {
if (payload.type === 'static') {
path = 'static-roles';
}
let database = [];
Expand All @@ -34,9 +34,10 @@ export default RESTSerializer.extend({
revocation_statement = payload.data.revocation_statements[0];
}
return {
id: payload.secret,
name: payload.secret,
id: payload.id,
backend: payload.backend,
name: payload.id,
type: payload.type,
database,
path,
creation_statement,
Expand Down
4 changes: 2 additions & 2 deletions ui/app/templates/components/database-role-edit.hbs
Expand Up @@ -32,10 +32,10 @@
<div class="toolbar-separator" />
{{/if}}
{{#if @model.canGenerateCredentials}}
<button
<button
type="button"
class="toolbar-link"
{{on 'click' (fn this.generateCreds @model.id)}}
{{on 'click' (fn this.generateCreds @model.id @model.type)}}
data-test-database-role-generate-creds
>
{{if (eq @model.type "static") "Get credentials" "Generate credentials"}}
Expand Down
19 changes: 15 additions & 4 deletions ui/app/templates/components/secret-list/database-list-item.hbs
Expand Up @@ -4,11 +4,16 @@
class="list-item-row"
data-test-secret-link=@item.id
encode=true
queryParams=(secret-query-params @backendType)
queryParams=(secret-query-params @backendType @item.type)
}}
<div class="columns is-mobile">
<div class="column is-10">
<LinkTo @route={{concat "vault.cluster.secrets.backend.show" }} @model={{if this.keyTypeValue (concat 'role/' @item.id) @item.id}} class="has-text-black has-text-weight-semibold">
<LinkTo
@route={{concat "vault.cluster.secrets.backend.show" }}
@model={{if this.keyTypeValue (concat 'role/' @item.id) @item.id}}
@query={{secret-query-params @backendType @item.type}}
class="has-text-black has-text-weight-semibold"
>
<Icon
@glyph="user-square-outline"
class="has-text-grey-light is-pulled-left"
Expand Down Expand Up @@ -50,10 +55,16 @@
</button>
</li>
{{/if}}
{{#if @item.canGenerateCredentials}}
{{#if (and (eq @item.type "dynamic") @item.canGenerateCredentials)}}
<li class="action">
<LinkTo @route="vault.cluster.secrets.backend.credentials" @model={{@item.id}} @query={{hash roleType=this.keyTypeValue}}>
{{if (eq @item.type "static") "Get credentials" "Generate credentials"}}
Generate credentials
</LinkTo>
</li>
{{else if (and (eq @item.type "static") @item.canGetCredentials)}}
<li class="action">
<LinkTo @route="vault.cluster.secrets.backend.credentials" @model={{@item.id}} @query={{hash roleType=this.keyTypeValue}}>
Get credentials
</LinkTo>
</li>
{{/if}}
Expand Down