Sync Destination Updates (#25571)

* normalizes sync destination granularity key in serializer

* adds new fields to aws and gcp sync destinations

* updates sync destination delete action from destinations list view to route to overview on success

* updates destination serializer normalize to check if options is defined
This commit is contained in:
Jordan Reimer
2024-02-21 15:49:01 -07:00
committed by GitHub
parent d1885ee558
commit 574f54ab28
7 changed files with 54 additions and 12 deletions

View File

@@ -13,13 +13,17 @@ const displayFields = [
'region',
'accessKeyId',
'secretAccessKey',
'roleArn',
'externalId',
// sync config options
'granularity',
'secretNameTemplate',
'customTags',
];
const formFieldGroups = [
{ default: ['name', 'region', 'granularity', 'secretNameTemplate', 'customTags'] },
{
default: ['name', 'region', 'roleArn', 'externalId', 'granularity', 'secretNameTemplate', 'customTags'],
},
{ Credentials: ['accessKeyId', 'secretAccessKey'] },
];
@withFormFields(displayFields, formFieldGroups)
@@ -51,4 +55,18 @@ export default class SyncDestinationsAwsSecretsManagerModel extends SyncDestinat
editType: 'kv',
})
customTags;
@attr('string', {
label: 'Role ARN',
subText:
'Specifies a role to assume when connecting to AWS. When assuming a role, Vault uses temporary STS credentials to authenticate.',
})
roleArn;
@attr('string', {
label: 'External ID',
subText:
'Optional extra protection that must match the trust policy granting access to the AWS IAM role ARN. We recommend using a different random UUID per destination.',
})
externalId;
}

View File

@@ -10,6 +10,7 @@ import { withFormFields } from 'vault/decorators/model-form-fields';
const displayFields = [
// connection details
'name',
'projectId',
'credentials',
// vault sync config options
'granularity',
@@ -17,11 +18,18 @@ const displayFields = [
'customTags',
];
const formFieldGroups = [
{ default: ['name', 'granularity', 'secretNameTemplate', 'customTags'] },
{ default: ['name', 'projectId', 'granularity', 'secretNameTemplate', 'customTags'] },
{ Credentials: ['credentials'] },
];
@withFormFields(displayFields, formFieldGroups)
export default class SyncDestinationsGoogleCloudSecretManagerModel extends SyncDestinationModel {
@attr('string', {
label: 'Project ID',
subText:
'The target project to manage secrets in. If set, overrides the project derived from the service account JSON credentials or application default credentials.',
})
projectId;
@attr('string', {
label: 'JSON credentials',
subText:

View File

@@ -68,6 +68,11 @@ export default class SyncDestinationSerializer extends ApplicationSerializer {
data.id = data.name;
delete data.connection_details;
delete data.options;
// granularity keys differ from payload to response -- normalize to payload format
if (options) {
options.granularity = options.granularity_level;
delete options.granularity_level;
}
return { data: { ...data, ...connection_details, ...options } };
}
return payload;

View File

@@ -95,11 +95,11 @@ export default class SyncSecretsDestinationsPageComponent extends Component<Args
@action
async onDelete(destination: SyncDestinationModel) {
try {
const { name, type } = destination;
const { name } = destination;
const message = `Destination ${name} has been queued for deletion.`;
await destination.destroyRecord();
this.store.clearDataset('sync/destination');
this.router.transitionTo('vault.cluster.sync.secrets.destinations.destination.secrets', type, name);
this.router.transitionTo('vault.cluster.sync.secrets.overview');
this.flashMessages.success(message);
} catch (error) {
this.flashMessages.danger(`Error deleting destination \n ${errorMessage(error)}`);

View File

@@ -13,9 +13,11 @@ export default Factory.extend({
access_key_id: '*****',
secret_access_key: '*****',
region: 'us-west-1',
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'
secret_name_template: 'vault-{{ .MountAccessor | replace "_" "-" }}-{{ .SecretPath }}',
secret_name_template: 'vault-{{ .MountAccessor }}-{{ .SecretPath }}',
custom_tags: { foo: 'bar' },
}),
['azure-kv']: trait({
@@ -30,17 +32,18 @@ export default Factory.extend({
cloud: 'Azure Public Cloud',
// options
granularity: 'secret-path',
secret_name_template: 'vault-{{ .MountAccessor | replace "_" "-" }}-{{ .SecretPath }}',
secret_name_template: 'vault-{{ .MountAccessor }}-{{ .SecretPath }}',
custom_tags: { foo: 'bar' },
}),
['gcp-sm']: trait({
type: 'gcp-sm',
name: 'destination-gcp',
project_id: 'id12345',
// connection_details
credentials: '*****',
// options
granularity: 'secret-path',
secret_name_template: 'vault-{{ .MountAccessor | replace "_" "-" }}-{{ .SecretPath }}',
secret_name_template: 'vault-{{ .MountAccessor }}-{{ .SecretPath }}',
custom_tags: { foo: 'bar' },
}),
gh: trait({
@@ -52,7 +55,7 @@ export default Factory.extend({
repository_name: 'my-repository',
// options
granularity: 'secret-path',
secret_name_template: 'vault-{{ .MountAccessor | replace "_" "-" }}-{{ .SecretPath }}',
secret_name_template: 'vault-{{ .MountAccessor }}-{{ .SecretPath }}',
}),
['vercel-project']: trait({
type: 'vercel-project',
@@ -63,6 +66,6 @@ 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
secret_name_template: 'vault-{{ .MountAccessor | replace "_" "-" }}-{{ .SecretPath }}',
secret_name_template: 'vault-{{ .MountAccessor }}-{{ .SecretPath }}',
}),
});

View File

@@ -154,7 +154,7 @@ module('Integration | Component | sync | Page::Destinations', function (hooks) {
assert.propEqual(
this.transitionStub.lastCall.args,
['vault.cluster.sync.secrets.destinations.destination.secrets', 'aws-sm', 'destination-aws'],
['vault.cluster.sync.secrets.overview'],
'Transition is triggered on delete success'
);
assert.propEqual(

View File

@@ -270,9 +270,17 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE
// if it is not a string type, add case to EXPECTED_VALUE and update
// fillInByAttr() (in sync-selectors) to interact with the form
const EDITABLE_FIELDS = {
'aws-sm': ['accessKeyId', 'secretAccessKey', 'granularity', 'secretNameTemplate', 'customTags'],
'aws-sm': [
'accessKeyId',
'secretAccessKey',
'roleArn',
'externalId',
'granularity',
'secretNameTemplate',
'customTags',
],
'azure-kv': ['clientId', 'clientSecret', 'granularity', 'secretNameTemplate', 'customTags'],
'gcp-sm': ['credentials', 'granularity', 'secretNameTemplate', 'customTags'],
'gcp-sm': ['projectId', 'credentials', 'granularity', 'secretNameTemplate', 'customTags'],
gh: ['accessToken', 'granularity', 'secretNameTemplate'],
'vercel-project': [
'accessToken',