UI: add default granularity depending on type, add optional to secret_name_template (#25611)

* add default granularity depending on type

* move default setting to helper

* add test coverage for default granularity

* update mirage

* update secret name template

* remove has-text-black class which was making help tooltip black as well
This commit is contained in:
claire bontempo
2024-02-23 13:11:07 -08:00
committed by GitHub
parent 2ab94c68ac
commit dc5070904b
7 changed files with 53 additions and 10 deletions

View File

@@ -18,13 +18,17 @@ const validations = {
@withModelValidations(validations)
export default class SyncDestinationModel extends Model {
@attr('string', { subText: 'Specifies the name for this destination.', editDisabled: true }) name;
@attr('string', { subText: 'Specifies the name for this destination.', editDisabled: true })
name;
@attr type;
@attr('string', {
subText:
'Go-template string that indicates how to format the secret name at the destination. The default template varies by destination type but is generally in the form of "vault-<accessor_id>-<secret_path>" e.g. "vault-kv-1234-my-secret-1".',
'Go-template string that indicates how to format the secret name at the destination. The default template varies by destination type but is generally in the form of "vault-{{ .MountAccessor }}-{{ .SecretPath }}" e.g. "vault-kv_9a8f68ad-my-secret-1". Optional.',
})
secretNameTemplate;
@attr('string', {
editType: 'radio',
label: 'Secret sync granularity',
@@ -42,9 +46,8 @@ export default class SyncDestinationModel extends Model {
value: 'secret-key',
},
],
defaultValue: 'secret-path',
})
granularity;
granularity; // default value depends on type and is set in create route
// only present if delete action has been initiated
@attr('string') purgeInitiatedAt;

View File

@@ -37,7 +37,7 @@
<div class="has-left-margin-xs">
<label
for="{{or val.value val}}"
class="has-left-margin-xs has-text-black is-size-7"
class="has-left-margin-xs is-size-7"
data-test-radio-label={{or val.label val.value val}}
>
{{or val.label val.value val}}

View File

@@ -8,8 +8,7 @@ import { helper as buildHelper } from '@ember/component/helper';
import type { SyncDestination, SyncDestinationType } from 'vault/vault/helpers/sync-destinations';
/*
This helper is referenced in the base sync destination model
to return static display attributes that rely on type
This helper is referenced in the base sync destination model and elsewhere to set attributes that rely on type
maskedParams: attributes for sensitive data, the API returns these values as '*****'
*/
@@ -20,6 +19,9 @@ const SYNC_DESTINATIONS: Array<SyncDestination> = [
icon: 'aws-color',
category: 'cloud',
maskedParams: ['accessKeyId', 'secretAccessKey'],
defaultValues: {
granularity: 'secret-path',
},
},
{
name: 'Azure Key Vault',
@@ -27,6 +29,9 @@ const SYNC_DESTINATIONS: Array<SyncDestination> = [
icon: 'azure-color',
category: 'cloud',
maskedParams: ['clientSecret'],
defaultValues: {
granularity: 'secret-path',
},
},
{
name: 'Google Secret Manager',
@@ -34,6 +39,9 @@ const SYNC_DESTINATIONS: Array<SyncDestination> = [
icon: 'gcp-color',
category: 'cloud',
maskedParams: ['credentials'],
defaultValues: {
granularity: 'secret-path',
},
},
{
name: 'Github Actions',
@@ -41,6 +49,9 @@ const SYNC_DESTINATIONS: Array<SyncDestination> = [
icon: 'github-color',
category: 'dev-tools',
maskedParams: ['accessToken'],
defaultValues: {
granularity: 'secret-key',
},
},
{
name: 'Vercel Project',
@@ -48,6 +59,9 @@ const SYNC_DESTINATIONS: Array<SyncDestination> = [
icon: 'vercel-color',
category: 'dev-tools',
maskedParams: ['accessToken'],
defaultValues: {
granularity: 'secret-key',
},
},
];

View File

@@ -5,11 +5,13 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';
import { findDestination } from 'core/helpers/sync-destinations';
import type StoreService from 'vault/services/store';
import type { SyncDestinationType } from 'vault/vault/helpers/sync-destinations';
interface Params {
type: string;
type: SyncDestinationType;
}
export default class SyncSecretsDestinationsCreateDestinationRoute extends Route {
@@ -17,6 +19,7 @@ export default class SyncSecretsDestinationsCreateDestinationRoute extends Route
model(params: Params) {
const { type } = params;
return this.store.createRecord(`sync/destinations/${type}`, { type });
const defaultValues = findDestination(type)?.defaultValues;
return this.store.createRecord(`sync/destinations/${type}`, { type, ...defaultValues });
}
}

View File

@@ -16,7 +16,7 @@ export default Factory.extend({
role_arn: 'test-role',
external_id: 'id12345',
// options
granularity: 'secret-path', // default option (same for all destinations) so edit test can update to 'secret-key'
granularity: 'secret-path', // default varies per destination, but setting all as secret-path so edit test loop updates each to 'secret-key'
secret_name_template: 'vault-{{ .MountAccessor }}-{{ .SecretPath }}',
custom_tags: { foo: 'bar' },
}),
@@ -66,6 +66,7 @@ export default Factory.extend({
team_id: 'team_12345',
deployment_environments: ['development', 'preview'], // 'production' is also an option, but left out for testing to assert form changes value
// options
granularity: 'secret-path',
secret_name_template: 'vault-{{ .MountAccessor }}-{{ .SecretPath }}',
}),
});

View File

@@ -11,6 +11,9 @@ import syncHandlers from 'vault/mirage/handlers/sync';
import authPage from 'vault/tests/pages/auth';
import { click, visit, fillIn, currentURL, currentRouteName } from '@ember/test-helpers';
import { PAGE as ts } from 'vault/tests/helpers/sync/sync-selectors';
import { syncDestinations } from 'vault/helpers/sync-destinations';
const SYNC_DESTINATIONS = syncDestinations();
// sync is an enterprise feature but since mirage is used the enterprise label has been intentionally omitted from the module name
module('Acceptance | sync | destinations', function (hooks) {
@@ -43,6 +46,24 @@ module('Acceptance | sync | destinations', function (hooks) {
);
});
for (const destination of SYNC_DESTINATIONS) {
const { type, defaultValues } = destination;
test(`it should render default values for destination: ${type}`, async function (assert) {
// remove destinations from mirage so cta shows when 404 is returned
this.server.db.syncDestinations.remove();
await click(ts.navLink('Secrets Sync'));
await click(ts.cta.button);
await click(ts.selectType(type));
// check default values
const attr = 'granularity';
assert
.dom(`${ts.inputByAttr(attr)} input#${defaultValues[attr]}`)
.isChecked(`${defaultValues[attr]} is checked`);
});
}
test('it should filter destinations list', async function (assert) {
await visit('vault/sync/secrets/destinations');
assert.dom(ts.listItem).exists({ count: 6 }, 'All destinations render');

View File

@@ -17,4 +17,5 @@ export interface SyncDestination {
icon: 'aws-color' | 'azure-color' | 'gcp-color' | 'github-color' | 'vercel-color';
category: 'cloud' | 'dev-tools';
maskedParams: Array<string>;
defaultValues: object;
}