-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Secrets Sync #23667
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
Merged
Merged
Secrets Sync #23667
Changes from 48 commits
Commits
Show all changes
49 commits
Select commit
Hold shift + click to select a range
dc27b26
Ember Engine Setup for Secrets Sync (#23653)
zofskeez 8338397
Sync Mirage Setup (#23683)
zofskeez b0ca805
Merge branch 'main' into ui/VAULT-17968/secrets-sync
hellobontempo df35050
UI Secrets Sync: Ember data sync destinations (#23674)
hellobontempo 94fe0f4
UI Secrets Sync: Overview landing page (#23696)
hellobontempo 866b61f
UI Secrets Sync: Destinations adapter add LIST (#23716)
hellobontempo 32d2868
Merge branch 'main' into ui/VAULT-17968/secrets-sync
hellobontempo 379ceaf
Secrets Sync: Destinations create - select type (#23792)
hellobontempo e78c5f1
UI Secrets Sync: Create destination form and route (#23806)
hellobontempo 7884a13
cleanup test selectors
hellobontempo f92c811
secrets sync: refactor sync destinations helper (#23839)
hellobontempo b437785
Merge branch 'main' into ui/VAULT-17968/secrets-sync
hellobontempo e6f2087
Secrets sync UI: Destination details page (#23842)
hellobontempo ed05f84
Secrets Sync UI: Cleanup headers + tabs (#23873)
hellobontempo 4f8e1a9
Merge branch 'main' into ui/VAULT-17968/secrets-sync
zofskeez 4d838c8
Secrets Sync Destinations List View (#23949)
zofskeez 889a7cf
Sync Destinations Capabilities (#23953)
zofskeez 3a948bd
Sync Associations Ember Data Setup (#24132)
zofskeez 8a92993
Sync Destination Secrets Route and Page Component (#24155)
zofskeez 681d540
Merge branch 'main' into ui/VAULT-17968/secrets-sync
zofskeez 2af55e2
updates usage of old spacing style variable after merge
zofskeez f908a7e
Merge branch 'main' into ui/VAULT-17968/secrets-sync
hellobontempo bba33f6
use confirm action instead of contextual confirm (old) component (#24…
hellobontempo 01cd83a
UI Secrets Sync: Adds secret status to kv v2 details page (#24208)
hellobontempo 0a5ad75
Sync Secrets to Destination (#24247)
zofskeez f4b7956
Secrets Sync Landing Page Images (#24277)
zofskeez 2016eb8
UI Secrets Sync: Serialize trailing slash from destination type (#24…
hellobontempo e1d8221
Sync Overview (#24340)
zofskeez b37e040
Merge branch 'main' into ui/VAULT-17968/secrets-sync
zofskeez 7e81adb
Secrets Sync UI: Add loading and error substates (#24353)
hellobontempo 39744bb
Remove is-version Helper (#24388)
zofskeez 7eb7dff
updates sync tests to use common selectors (#24397)
zofskeez d9e7003
Merge branch 'main' into ui/VAULT-17968/secrets-sync
hellobontempo a13c780
update capitalization to consistently be titlecase, fix breadcrumb se…
hellobontempo d3bf747
Merge branch 'main' into ui/VAULT-17968/secrets-sync
hellobontempo 4d7a3cf
clears sync associations from store on destination sync page componen…
zofskeez 49eab64
KV Suggestion Input (#24447)
zofskeez bc4a067
Secrets Sync UI: Editing a destination (#24413)
hellobontempo 5494d60
Sync Success Banner (#24491)
zofskeez b0fd407
use Sync secrets everywhere (remove new) (#24494)
hellobontempo b8c1d9d
Sync Destinations List Filter Bug (#24496)
zofskeez 4b712b7
fixes Sync now action text alignment in destination secrets list
zofskeez 5f98b44
UI Secrets sync: Add purge query param to delete endpoint (#24497)
hellobontempo 341a9c1
adds updated_at to mirage set association handler
zofskeez e4b364f
adds changelog entry
zofskeez 9c6d28e
Merge branch 'ui/VAULT-17968/secrets-sync' of github.com:hashicorp/va…
zofskeez a181036
Merge branch 'main' into ui/VAULT-17968/secrets-sync
zofskeez c1e8f76
add enterprise in parenthesis for changelog
hellobontempo aaf532b
addres a11y feedback
hellobontempo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ```release-note:feature | ||
| **Secrets Sync UI (enterprise)**: Adds secret syncing for KV v2 secrets to external destinations using the UI. | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| /** | ||
| * Copyright (c) HashiCorp, Inc. | ||
| * SPDX-License-Identifier: BUSL-1.1 | ||
| */ | ||
|
|
||
| import ApplicationAdapter from 'vault/adapters/application'; | ||
| import { assert } from '@ember/debug'; | ||
| import { all } from 'rsvp'; | ||
|
|
||
| export default class SyncAssociationAdapter extends ApplicationAdapter { | ||
| namespace = 'v1/sys/sync'; | ||
|
|
||
| buildURL(modelName, id, snapshot, requestType, query = {}) { | ||
| const { destinationType, destinationName } = snapshot ? snapshot.attributes() : query; | ||
| if (!destinationType || !destinationName) { | ||
| return `${super.buildURL()}/associations`; | ||
| } | ||
| const { action } = snapshot?.adapterOptions || {}; | ||
| const uri = action ? `/${action}` : ''; | ||
| return `${super.buildURL()}/destinations/${destinationType}/${destinationName}/associations${uri}`; | ||
| } | ||
|
|
||
| query(store, { modelName }, query) { | ||
| // endpoint doesn't accept the typical list query param and we don't want to pass options from lazyPaginatedQuery | ||
| const url = this.buildURL(modelName, null, null, 'query', query); | ||
| return this.ajax(url, 'GET'); | ||
| } | ||
|
|
||
| // typically associations are queried for a specific destination which is what the standard query method does | ||
| // in specific cases we can query all associations to access total_associations and total_secrets values | ||
| queryAll() { | ||
| return this.query(this.store, { modelName: 'sync/association' }).then((response) => { | ||
| const { total_associations, total_secrets } = response.data; | ||
| return { total_associations, total_secrets }; | ||
| }); | ||
| } | ||
|
|
||
| // fetch associations for many destinations | ||
| // returns aggregated association information for each destination | ||
| // information includes total associations, total unsynced and most recent updated datetime | ||
| async fetchByDestinations(destinations) { | ||
| const promises = destinations.map(({ name: destinationName, type: destinationType }) => { | ||
| return this.query(this.store, { modelName: 'sync/association' }, { destinationName, destinationType }); | ||
| }); | ||
| const queryResponses = await all(promises); | ||
| const serializer = this.store.serializerFor('sync/association'); | ||
| return queryResponses.map((response) => serializer.normalizeFetchByDestinations(response)); | ||
| } | ||
|
|
||
| // array of association data for each destination a secret is synced to | ||
| fetchSyncStatus({ mount, secretName }) { | ||
| const url = `${this.buildURL()}/${mount}/${secretName}`; | ||
| return this.ajax(url, 'GET').then((resp) => { | ||
| const { associated_destinations } = resp.data; | ||
| const syncData = []; | ||
| for (const key in associated_destinations) { | ||
| const data = associated_destinations[key]; | ||
| // renaming keys to match query() response | ||
| syncData.push({ | ||
| destinationType: data.type, | ||
| destinationName: data.name, | ||
| syncStatus: data.sync_status, | ||
| updatedAt: data.updated_at, | ||
| }); | ||
| } | ||
| return syncData; | ||
| }); | ||
| } | ||
|
|
||
| // snapshot is needed for mount and secret_name values which are used to parse response since all associations are returned | ||
| _setOrRemove(store, { modelName }, snapshot) { | ||
| assert( | ||
| "action type of set or remove required when saving association => association.save({ adapterOptions: { action: 'set' }})", | ||
| ['set', 'remove'].includes(snapshot?.adapterOptions?.action) | ||
| ); | ||
| const url = this.buildURL(modelName, null, snapshot); | ||
| const data = snapshot.serialize(); | ||
| return this.ajax(url, 'POST', { data }).then((resp) => { | ||
| const id = `${data.mount}/${data.secret_name}`; | ||
| return { | ||
| ...resp.data.associated_secrets[id], | ||
| id, | ||
| destinationName: resp.data.store_name, | ||
| destinationType: resp.data.store_type, | ||
| }; | ||
| }); | ||
| } | ||
|
|
||
| createRecord() { | ||
| return this._setOrRemove(...arguments); | ||
| } | ||
|
|
||
| updateRecord() { | ||
| return this._setOrRemove(...arguments); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| /** | ||
| * Copyright (c) HashiCorp, Inc. | ||
| * SPDX-License-Identifier: BUSL-1.1 | ||
| */ | ||
|
|
||
| import ApplicationAdapter from 'vault/adapters/application'; | ||
| import { pluralize } from 'ember-inflector'; | ||
|
|
||
| export default class SyncDestinationAdapter extends ApplicationAdapter { | ||
| namespace = 'v1/sys'; | ||
|
|
||
| pathForType(modelName) { | ||
| return modelName === 'sync/destination' ? pluralize(modelName) : modelName; | ||
| } | ||
|
|
||
| urlForCreateRecord(modelName, snapshot) { | ||
| const { name } = snapshot.attributes(); | ||
| return `${super.urlForCreateRecord(modelName, snapshot)}/${name}`; | ||
| } | ||
|
|
||
| updateRecord(store, { modelName }, snapshot) { | ||
| const { name } = snapshot.attributes(); | ||
| return this.ajax(`${this.buildURL(modelName)}/${name}`, 'PATCH', { data: snapshot.serialize() }); | ||
| } | ||
|
|
||
| urlForDeleteRecord(id, modelName, snapshot) { | ||
| const { name, type } = snapshot.attributes(); | ||
| // the only delete option in the UI is to purge which unsyncs all secrets prior to deleting | ||
| return `${this.buildURL('sync/destinations')}/${type}/${name}?purge=true`; | ||
| } | ||
|
|
||
| query(store, { modelName }) { | ||
| return this.ajax(this.buildURL(modelName), 'GET', { data: { list: true } }); | ||
| } | ||
|
|
||
| // return normalized query response | ||
| // useful for fetching data directly without loading models into store | ||
| async normalizedQuery() { | ||
| const queryResponse = await this.query(this.store, { modelName: 'sync/destination' }); | ||
| const serializer = this.store.serializerFor('sync/destination'); | ||
| return serializer.extractLazyPaginatedData(queryResponse); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| /** | ||
| * Copyright (c) HashiCorp, Inc. | ||
| * SPDX-License-Identifier: BUSL-1.1 | ||
| */ | ||
|
|
||
| import SyncDestinationAdapter from '../destination'; | ||
|
|
||
| export default class SyncDestinationsAwsSecretsManagerAdapter extends SyncDestinationAdapter {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| /** | ||
| * Copyright (c) HashiCorp, Inc. | ||
| * SPDX-License-Identifier: BUSL-1.1 | ||
| */ | ||
|
|
||
| import SyncDestinationAdapter from '../destination'; | ||
|
|
||
| export default class SyncDestinationsAzureKeyVaultAdapter extends SyncDestinationAdapter {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| /** | ||
| * Copyright (c) HashiCorp, Inc. | ||
| * SPDX-License-Identifier: BUSL-1.1 | ||
| */ | ||
|
|
||
| import SyncDestinationAdapter from '../destination'; | ||
|
|
||
| export default class SyncDestinationGoogleCloudSecretManagerAdapter extends SyncDestinationAdapter {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| /** | ||
| * Copyright (c) HashiCorp, Inc. | ||
| * SPDX-License-Identifier: BUSL-1.1 | ||
| */ | ||
|
|
||
| import SyncDestinationAdapter from '../destination'; | ||
|
|
||
| export default class SyncDestinationsGithubAdapter extends SyncDestinationAdapter {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| /** | ||
| * Copyright (c) HashiCorp, Inc. | ||
| * SPDX-License-Identifier: BUSL-1.1 | ||
| */ | ||
|
|
||
| import SyncDestinationAdapter from '../destination'; | ||
|
|
||
| export default class SyncDestinationsVercelProjectAdapter extends SyncDestinationAdapter {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| /** | ||
| * Copyright (c) HashiCorp, Inc. | ||
| * SPDX-License-Identifier: BUSL-1.1 | ||
| */ | ||
|
|
||
| import Model, { attr } from '@ember-data/model'; | ||
| import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities'; | ||
|
|
||
| export default class SyncAssociationModel extends Model { | ||
| @attr mount; | ||
| @attr secretName; | ||
| @attr syncStatus; | ||
| @attr updatedAt; | ||
| // destination related properties that are not serialized to payload | ||
| @attr destinationName; | ||
| @attr destinationType; | ||
|
|
||
| @lazyCapabilities( | ||
| apiPath`sys/sync/destinations/${'destinationType'}/${'destinationName'}/associations/set`, | ||
| 'destinationType', | ||
| 'destinationName' | ||
| ) | ||
| setAssociationPath; | ||
|
|
||
| @lazyCapabilities( | ||
| apiPath`sys/sync/destinations/${'destinationType'}/${'destinationName'}/associations/remove`, | ||
| 'destinationType', | ||
| 'destinationName' | ||
| ) | ||
| removeAssociationPath; | ||
|
|
||
| get canSync() { | ||
| return this.setAssociationPath.get('canUpdate') !== false; | ||
| } | ||
|
|
||
| get canUnsync() { | ||
| return this.removeAssociationPath.get('canUpdate') !== false; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| /** | ||
| * Copyright (c) HashiCorp, Inc. | ||
| * SPDX-License-Identifier: BUSL-1.1 | ||
| */ | ||
|
|
||
| import Model, { attr } from '@ember-data/model'; | ||
| import { findDestination } from 'vault/helpers/sync-destinations'; | ||
| import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities'; | ||
| import { withModelValidations } from 'vault/decorators/model-validations'; | ||
|
|
||
| // Base model for all secret sync destination types | ||
| const validations = { | ||
| name: [{ type: 'presence', message: 'Name is required.' }], | ||
| }; | ||
|
|
||
| @withModelValidations(validations) | ||
| export default class SyncDestinationModel extends Model { | ||
| @attr('string', { subText: 'Specifies the name for this destination.', editDisabled: true }) name; | ||
| @attr type; | ||
|
|
||
| // findDestination returns static attributes for each destination type | ||
| get icon() { | ||
| return findDestination(this.type)?.icon; | ||
| } | ||
|
|
||
| get typeDisplayName() { | ||
| return findDestination(this.type)?.name; | ||
| } | ||
|
|
||
| get maskedParams() { | ||
| return findDestination(this.type)?.maskedParams; | ||
| } | ||
|
|
||
| @lazyCapabilities(apiPath`sys/sync/destinations/${'type'}/${'name'}`, 'type', 'name') destinationPath; | ||
| @lazyCapabilities(apiPath`sys/sync/destinations/${'type'}/${'name'}/associations/set`, 'type', 'name') | ||
| setAssociationPath; | ||
|
|
||
| get canCreate() { | ||
| return this.destinationPath.get('canCreate') !== false; | ||
| } | ||
| get canDelete() { | ||
| return this.destinationPath.get('canDelete') !== false; | ||
| } | ||
| get canEdit() { | ||
| return this.destinationPath.get('canUpdate') !== false; | ||
| } | ||
| get canRead() { | ||
| return this.destinationPath.get('canRead') !== false; | ||
| } | ||
| get canSync() { | ||
| return this.setAssociationPath.get('canUpdate') !== false; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| /** | ||
| * Copyright (c) HashiCorp, Inc. | ||
| * SPDX-License-Identifier: BUSL-1.1 | ||
| */ | ||
|
|
||
| import SyncDestinationModel from '../destination'; | ||
| import { attr } from '@ember-data/model'; | ||
| import { withFormFields } from 'vault/decorators/model-form-fields'; | ||
|
|
||
| const displayFields = ['name', 'region', 'accessKeyId', 'secretAccessKey']; | ||
| const formFieldGroups = [ | ||
| { default: ['name', 'region'] }, | ||
| { Credentials: ['accessKeyId', 'secretAccessKey'] }, | ||
| ]; | ||
| @withFormFields(displayFields, formFieldGroups) | ||
| export default class SyncDestinationsAwsSecretsManagerModel extends SyncDestinationModel { | ||
| @attr('string', { | ||
| label: 'Access key ID', | ||
| subText: | ||
| 'Access key ID to authenticate against the secrets manager. If empty, Vault will use the AWS_ACCESS_KEY_ID environment variable if configured.', | ||
| }) | ||
| accessKeyId; // obfuscated, never returned by API | ||
|
|
||
| @attr('string', { | ||
| label: 'Secret access key', | ||
| subText: | ||
| 'Secret access key to authenticate against the secrets manager. If empty, Vault will use the AWS_SECRET_ACCESS_KEY environment variable if configured.', | ||
| }) | ||
| secretAccessKey; // obfuscated, never returned by API | ||
|
|
||
| @attr('string', { | ||
| subText: | ||
| 'For AWS secrets manager, the name of the region must be supplied, something like “us-west-1.” If empty, Vault will use the AWS_REGION environment variable if configured.', | ||
| editDisabled: true, | ||
| }) | ||
| region; | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this and accessKeyId be masked inputs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They weren't in the designs 🤔 But this is a good question - we have a meeting scheduled to do a design review/walk through in the new year. I'll make a note to ask this!