From 8f784a3afb9246dd124afd1922cf03a15b1ea860 Mon Sep 17 00:00:00 2001 From: claire bontempo <68122737+hellobontempo@users.noreply.github.com> Date: Fri, 5 Jan 2024 14:23:02 -0800 Subject: [PATCH 01/14] Secrets Sync UI: Small UX copy improvements (#24693) * update secret details alert banner * small copy changes * remove empty description block * update tests * use pluralize helper * change banner wording --- .../addon/components/page/secret/details.hbs | 7 ++- ui/lib/kv/package.json | 3 +- .../page/destinations/destination/sync.hbs | 2 +- .../components/secrets/page/overview.hbs | 2 +- .../kv/page/kv-page-secret-details-test.js | 56 +++++++++++++++++-- .../sync/secrets/page/overview-test.js | 2 +- 6 files changed, 61 insertions(+), 11 deletions(-) diff --git a/ui/lib/kv/addon/components/page/secret/details.hbs b/ui/lib/kv/addon/components/page/secret/details.hbs index 4a5f91ea66..2eb6bab4e8 100644 --- a/ui/lib/kv/addon/components/page/secret/details.hbs +++ b/ui/lib/kv/addon/components/page/secret/details.hbs @@ -6,10 +6,11 @@ <:syncDetails> {{#if this.syncStatus}} - + - This secret has been synced from Vault to other destinations, updates to the secret will get automatically synced - to destinations. + This secret has been synced from Vault to + {{pluralize this.syncStatus.length "destination"}}. Updates to this secret will automatically sync to its + {{if (eq this.syncStatus.length 1) "destination" "destinations"}}. {{#each this.syncStatus as |status|}} diff --git a/ui/lib/kv/package.json b/ui/lib/kv/package.json index 4942e70fea..5c76aa678d 100644 --- a/ui/lib/kv/package.json +++ b/ui/lib/kv/package.json @@ -8,7 +8,8 @@ "ember-cli-htmlbars": "*", "ember-cli-babel": "*", "ember-concurrency": "*", - "@ember/test-waiters": "*" + "@ember/test-waiters": "*", + "ember-inflector": "*" }, "ember-addon": { "paths": [ diff --git a/ui/lib/sync/addon/components/secrets/page/destinations/destination/sync.hbs b/ui/lib/sync/addon/components/secrets/page/destinations/destination/sync.hbs index be90b8acb5..20725c4c78 100644 --- a/ui/lib/sync/addon/components/secrets/page/destinations/destination/sync.hbs +++ b/ui/lib/sync/addon/components/secrets/page/destinations/destination/sync.hbs @@ -37,7 +37,7 @@

Select a KV engine mount and path to sync a secret to the {{@destination.typeDisplayName}} - destination. + destination. Selecting a previously synced secret will re-sync that secret.

diff --git a/ui/lib/sync/addon/components/secrets/page/overview.hbs b/ui/lib/sync/addon/components/secrets/page/overview.hbs index 3a8844fb5f..e114da4cf8 100644 --- a/ui/lib/sync/addon/components/secrets/page/overview.hbs +++ b/ui/lib/sync/addon/components/secrets/page/overview.hbs @@ -104,7 +104,7 @@ diff --git a/ui/tests/integration/components/kv/page/kv-page-secret-details-test.js b/ui/tests/integration/components/kv/page/kv-page-secret-details-test.js index d01abb56ab..28327e13a6 100644 --- a/ui/tests/integration/components/kv/page/kv-page-secret-details-test.js +++ b/ui/tests/integration/components/kv/page/kv-page-secret-details-test.js @@ -240,8 +240,8 @@ module('Integration | Component | kv-v2 | Page::Secret::Details', function (hook .exists('renders current version icon'); }); - test('it renders sync status page alert', async function (assert) { - assert.expect(5); // assert count important because confirms request made to fetch sync status twice + test('it renders sync status page alert and refreshes', async function (assert) { + assert.expect(6); // assert count important because confirms request made to fetch sync status twice const destinationName = 'my-destination'; this.server.create('sync-association', { type: 'aws-sm', @@ -250,7 +250,7 @@ module('Integration | Component | kv-v2 | Page::Secret::Details', function (hook secret_name: this.path, }); this.server.get(`sys/sync/associations/destinations`, (schema, req) => { - // this assertion should be hit twice, once on init and again when the 'Refresh' button is clicked + // these assertions should be hit twice, once on init and again when the 'Refresh' button is clicked assert.ok(true, 'request made to fetch sync status'); assert.propEqual( req.queryParams, @@ -281,8 +281,56 @@ module('Integration | Component | kv-v2 | Page::Secret::Details', function (hook 'Synced my-destination - last updated September', 'renders sync status alert banner' ); - + assert + .dom(PAGE.detail.syncAlert()) + .hasTextContaining( + 'This secret has been synced from Vault to 1 destination. Updates to this secret will automatically sync to its destination.', + 'renders alert header referring to singular destination' + ); // sync status refresh button await click(`${PAGE.detail.syncAlert()} button`); }); + + test('it renders sync status page alert for multiple destinations', async function (assert) { + assert.expect(3); // assert count important because confirms request made to fetch sync status twice + this.server.create('sync-association', { + type: 'aws-sm', + name: 'aws-dest', + mount: this.backend, + secret_name: this.path, + }); + this.server.create('sync-association', { + type: 'gh', + name: 'gh-dest', + mount: this.backend, + secret_name: this.path, + }); + this.server.get(`sys/sync/associations/destinations`, (schema, req) => { + return syncStatusResponse(schema, req); + }); + + await render( + hbs` + + `, + { owner: this.engine } + ); + assert + .dom(PAGE.detail.syncAlert('aws-dest')) + .hasTextContaining('Synced aws-dest - last updated September', 'renders status for aws destination'); + assert + .dom(PAGE.detail.syncAlert('gh-dest')) + .hasTextContaining('Syncing gh-dest - last updated September', 'renders status for gh destination'); + assert + .dom(PAGE.detail.syncAlert()) + .hasTextContaining( + 'This secret has been synced from Vault to 2 destinations. Updates to this secret will automatically sync to its destinations.', + 'renders alert title referring to plural destinations' + ); + }); }); diff --git a/ui/tests/integration/components/sync/secrets/page/overview-test.js b/ui/tests/integration/components/sync/secrets/page/overview-test.js index 33cdff977d..860a185a1c 100644 --- a/ui/tests/integration/components/sync/secrets/page/overview-test.js +++ b/ui/tests/integration/components/sync/secrets/page/overview-test.js @@ -105,7 +105,7 @@ module('Integration | Component | sync | Page::Overview', function (hooks) { await click(actionToggle(0)); assert.dom(action('sync')).hasText('Sync secrets', 'Sync action renders'); - assert.dom(action('details')).hasText('Details', 'Details action renders'); + assert.dom(action('details')).hasText('View synced secrets', 'View synced secrets action renders'); }); test('it should paginate secrets by destination table', async function (assert) { From 205680c0772df413ed7972f62f7bfd2874ceb277 Mon Sep 17 00:00:00 2001 From: Kyle Schochenmaier Date: Fri, 5 Jan 2024 16:55:48 -0600 Subject: [PATCH 02/14] add docs for configuring jwt validation pubkeys for vso (#24599) * add docs for configuring jwt validation pubkeys for vso and update jwt auth docs to mention key rotation Co-authored-by: Tom Proctor --- .../content/docs/auth/jwt/oidc-providers/kubernetes.mdx | 5 +++++ website/content/docs/platform/k8s/vso/examples.mdx | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/website/content/docs/auth/jwt/oidc-providers/kubernetes.mdx b/website/content/docs/auth/jwt/oidc-providers/kubernetes.mdx index 383b12592e..55220b7ecd 100644 --- a/website/content/docs/auth/jwt/oidc-providers/kubernetes.mdx +++ b/website/content/docs/auth/jwt/oidc-providers/kubernetes.mdx @@ -81,6 +81,11 @@ This method can be useful if Kubernetes' API is not reachable from Vault or if you would like a single JWT auth mount to service multiple Kubernetes clusters by chaining their public signing keys. + + Should the JWT Signing Key used by Kubernetes be rotated, + this process should be repeated with the new key. + + Kubernetes cluster requirements: * [`ServiceAccountIssuerDiscovery`][k8s-sa-issuer-discovery] feature enabled. diff --git a/website/content/docs/platform/k8s/vso/examples.mdx b/website/content/docs/platform/k8s/vso/examples.mdx index d2a02c7be0..62a02e3e3a 100644 --- a/website/content/docs/platform/k8s/vso/examples.mdx +++ b/website/content/docs/platform/k8s/vso/examples.mdx @@ -11,6 +11,13 @@ The Operator project provides the following examples: - Sample use-cases are documented [here](https://github.com/hashicorp/vault-secrets-operator#samples) - A Terraform based demo can be found [here](https://github.com/hashicorp/vault-secrets-operator/tree/main/demo) +## JWT auth for Kubernetes clusters in private networks + +Vault Secrets Operator supports using the [JWT auth method](/vault/docs/platform/k8s/vso/api-reference#vaultauthconfigjwt). +JWT auth [verifies tokens](/vault/docs/auth/jwt#jwt-verification) using the issuer's public signing key. +Vault supports fetching this public key from the Kubernetes API, but if users can't expose the Kubernetes API to Vault, the public key can be provided directly using [`jwt_validation_pubkeys`](/vault/api-docs/auth/jwt#jwt_validation_pubkeys). +To configure this please follow the steps outlined for [Using JWT validation public keys](/vault/docs/auth/jwt/oidc-providers/kubernetes#using-jwt-validation-public-keys) + ## Using VaultStaticSecrets for imagePullSecrets Vault Secret Operator supports Kubernetes' templating of Secrets based on their From 87ab7497fa96c7f113b2e8790d3fdf11120a8e82 Mon Sep 17 00:00:00 2001 From: divyaac Date: Fri, 5 Jan 2024 15:31:16 -0800 Subject: [PATCH 03/14] Docs changes (#24242) --- .../content/api-docs/system/lease-count-quotas.mdx | 2 +- website/content/api-docs/system/quotas-config.mdx | 13 +++++++++++-- .../content/api-docs/system/rate-limit-quotas.mdx | 2 +- .../content/partials/api/restricted-endpoints.mdx | 6 +++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/website/content/api-docs/system/lease-count-quotas.mdx b/website/content/api-docs/system/lease-count-quotas.mdx index 1e95b2d108..dc2c513c50 100644 --- a/website/content/api-docs/system/lease-count-quotas.mdx +++ b/website/content/api-docs/system/lease-count-quotas.mdx @@ -8,7 +8,7 @@ description: The `/sys/quotas/lease-count` endpoint is used to create, edit and @include 'alerts/enterprise-only.mdx' -@include 'alerts/restricted-root.mdx' +@include 'alerts/restricted-admin.mdx' The `/sys/quotas/lease-count` endpoint is used to create, edit and delete lease count quotas. diff --git a/website/content/api-docs/system/quotas-config.mdx b/website/content/api-docs/system/quotas-config.mdx index f1cb2700c2..d0d021ddda 100644 --- a/website/content/api-docs/system/quotas-config.mdx +++ b/website/content/api-docs/system/quotas-config.mdx @@ -6,7 +6,8 @@ description: The `/sys/quotas/config` endpoint is used to configure rate limit q # `/sys/quotas/config` -@include 'alerts/restricted-root.mdx' +@include 'alerts/restricted-admin.mdx' + The `/sys/quotas/config` endpoint is used to configure rate limit quotas. @@ -19,7 +20,15 @@ The `/sys/quotas/config` endpoint is used to configure rate limit quotas. ### Parameters - `rate_limit_exempt_paths` `([]string: [])` - Specifies the list of exempt paths - from all rate limit quotas. If empty no paths will be exempt. + from all rate limit quotas. Exempt paths are relative and apply to all + namespaces. When `rate_limit_exempt_paths` is empty, Vault applies quotas to + all relative paths. Access to exemption data is read-only from the admin namespace. + **You cannot update `rate_limit_exempt_paths` from the admin namespace** +- `global_rate_limit_exempt_paths` `([]string: [])` -Specifies the list of + exempt paths from all rate limit quotas. Global exempt paths are absolute and + do not apply across namespaces. When `global_rate_limit_exempt_paths` is empty, + Vault applies quotas to all absolute paths. You can only add, update, or delete + global paths within the scope of the calling namespace. - `enable_rate_limit_audit_logging` `(bool: false)` - If set, starts audit logging of requests that get rejected due to rate limit quota rule violations. - `enable_rate_limit_response_headers` `(bool: false)` - If set, additional rate diff --git a/website/content/api-docs/system/rate-limit-quotas.mdx b/website/content/api-docs/system/rate-limit-quotas.mdx index a85a18a757..e479781fea 100644 --- a/website/content/api-docs/system/rate-limit-quotas.mdx +++ b/website/content/api-docs/system/rate-limit-quotas.mdx @@ -6,7 +6,7 @@ description: The `/sys/quotas/rate-limit` endpoint is used to create, edit and d # `/sys/quotas/rate-limit` -@include 'alerts/restricted-root.mdx' +@include 'alerts/restricted-admin.mdx' The `/sys/quotas/rate-limit` endpoint is used to create, edit and delete rate limit quotas. diff --git a/website/content/partials/api/restricted-endpoints.mdx b/website/content/partials/api/restricted-endpoints.mdx index 6d2d63e152..1e94d400a8 100644 --- a/website/content/partials/api/restricted-endpoints.mdx +++ b/website/content/partials/api/restricted-endpoints.mdx @@ -34,9 +34,9 @@ API path | Root | Admin `sys/mfa/method/*` | YES | NO `sys/monitor` | YES | YES `sys/pprof/*` | YES | NO -`sys/quotas/config` | YES | NO -`sys/quotas/lease-count` | YES | NO -`sys/quotas/rate-limit` | YES | NO +`sys/quotas/config` | YES | YES +`sys/quotas/lease-count` | YES | YES +`sys/quotas/rate-limit` | YES | YES `sys/raw` | YES | NO `sys/rekey/*` | YES | NO `sys/rekey-recovery-key` | YES | NO From 3153673894781bdffb8562435dcc6ac8427d7205 Mon Sep 17 00:00:00 2001 From: Jordan Reimer Date: Fri, 5 Jan 2024 16:41:57 -0700 Subject: [PATCH 04/14] Sync Destinations List Name Filter Updates (#24695) * updates destination name filter to use FilterInput component * simplifies destinations list redirect condition * fixes issue with sync destination type filter and issue filtering by both name and type * unsets page query param in sync destination secrets route --- ui/app/styles/helper-classes/typography.scss | 3 ++ .../components/search-select-placeholder.hbs | 2 +- .../components/secrets/page/destinations.hbs | 23 +++++++------- .../components/secrets/page/destinations.ts | 31 +++++++++++++++---- .../page/destinations/create-and-edit.ts | 2 +- .../destinations/destination/secrets.ts | 20 +++++++++++- .../routes/secrets/destinations/index.ts | 28 +++++++++++++++-- .../sync/secrets/destinations-test.js | 9 ++++-- ui/tests/helpers/general-selectors.js | 1 + .../sync/secrets/page/destinations-test.js | 5 ++- 10 files changed, 96 insertions(+), 28 deletions(-) diff --git a/ui/app/styles/helper-classes/typography.scss b/ui/app/styles/helper-classes/typography.scss index 4cde9b2d3c..8466b0c294 100644 --- a/ui/app/styles/helper-classes/typography.scss +++ b/ui/app/styles/helper-classes/typography.scss @@ -110,6 +110,9 @@ } } +.opacity-050 { + opacity: 0.5; +} .opacity-060 { opacity: 0.6; } diff --git a/ui/lib/core/addon/components/search-select-placeholder.hbs b/ui/lib/core/addon/components/search-select-placeholder.hbs index b168b1c9ab..32b9adeea1 100644 --- a/ui/lib/core/addon/components/search-select-placeholder.hbs +++ b/ui/lib/core/addon/components/search-select-placeholder.hbs @@ -6,7 +6,7 @@

- {{or @placeholder "Search"}} + {{or @placeholder "Search"}}

diff --git a/ui/lib/sync/addon/components/secrets/page/destinations.hbs b/ui/lib/sync/addon/components/secrets/page/destinations.hbs index 40eba8842c..a1cf7ecbea 100644 --- a/ui/lib/sync/addon/components/secrets/page/destinations.hbs +++ b/ui/lib/sync/addon/components/secrets/page/destinations.hbs @@ -28,18 +28,17 @@ class="is-marginless" data-test-filter="type" /> - +
+ +
diff --git a/ui/lib/sync/addon/components/secrets/page/destinations.ts b/ui/lib/sync/addon/components/secrets/page/destinations.ts index d4aa6c08b5..77ac790f06 100644 --- a/ui/lib/sync/addon/components/secrets/page/destinations.ts +++ b/ui/lib/sync/addon/components/secrets/page/destinations.ts @@ -9,6 +9,7 @@ import { action } from '@ember/object'; import { getOwner } from '@ember/application'; import errorMessage from 'vault/utils/error-message'; import { findDestination, syncDestinations } from 'core/helpers/sync-destinations'; +import { next } from '@ember/runloop'; import type SyncDestinationModel from 'vault/vault/models/sync/destination'; import type RouterService from '@ember/routing/router-service'; @@ -16,6 +17,7 @@ import type StoreService from 'vault/services/store'; import type FlashMessageService from 'vault/services/flash-messages'; import type { EngineOwner } from 'vault/vault/app-types'; import type { SyncDestinationName, SyncDestinationType } from 'vault/vault/helpers/sync-destinations'; +import type Transition from '@ember/routing/transition'; interface Args { destinations: Array; @@ -28,15 +30,31 @@ export default class SyncSecretsDestinationsPageComponent extends Component document.getElementById('name-filter')?.focus()); + } + } + // typeFilter arg comes in as destination type but we need to pass the destination display name into the SearchSelect get typeFilterName() { return findDestination(this.args.typeFilter)?.name; } - get destinationNames() { - return this.args.destinations.map((destination) => ({ id: destination.name, name: destination.name })); - } - get destinationTypes() { return syncDestinations().map((d) => ({ id: d.name, name: d.type })); } @@ -65,9 +83,10 @@ export default class SyncSecretsDestinationsPageComponent extends Component) { + onFilterChange(key: string, value: { id: string; name: string }[] | string | undefined) { + const queryValue = Array.isArray(value) ? value[0]?.name : value; this.router.transitionTo('vault.cluster.sync.secrets.destinations', { - queryParams: { [key]: selectObject[0]?.name }, + queryParams: { [key]: queryValue }, }); } diff --git a/ui/lib/sync/addon/components/secrets/page/destinations/create-and-edit.ts b/ui/lib/sync/addon/components/secrets/page/destinations/create-and-edit.ts index 04e9331bcf..6ba75cfe0e 100644 --- a/ui/lib/sync/addon/components/secrets/page/destinations/create-and-edit.ts +++ b/ui/lib/sync/addon/components/secrets/page/destinations/create-and-edit.ts @@ -59,7 +59,7 @@ export default class DestinationsCreateForm extends Component { @waitFor *save(event: Event) { event.preventDefault(); - + this.error = ''; // clear out validation warnings this.modelValidations = null; const { destination } = this.args; diff --git a/ui/lib/sync/addon/routes/secrets/destinations/destination/secrets.ts b/ui/lib/sync/addon/routes/secrets/destinations/destination/secrets.ts index ddf1ca5dd5..9d24413acd 100644 --- a/ui/lib/sync/addon/routes/secrets/destinations/destination/secrets.ts +++ b/ui/lib/sync/addon/routes/secrets/destinations/destination/secrets.ts @@ -8,12 +8,24 @@ import { inject as service } from '@ember/service'; import { hash } from 'rsvp'; import type StoreService from 'vault/services/store'; -import SyncDestinationModel from 'vault/vault/models/sync/destination'; +import type SyncDestinationModel from 'vault/vault/models/sync/destination'; +import type SyncAssociationModel from 'vault/vault/models/sync/association'; +import type Controller from '@ember/controller'; interface SyncDestinationSecretsRouteParams { page: string; } +interface SyncDestinationSecretsRouteModel { + destination: SyncDestinationModel; + associations: SyncAssociationModel[]; +} + +interface SyncDestinationSecretsController extends Controller { + model: SyncDestinationSecretsRouteModel; + page: number | undefined; +} + export default class SyncDestinationSecretsRoute extends Route { @service declare readonly store: StoreService; @@ -35,4 +47,10 @@ export default class SyncDestinationSecretsRoute extends Route { }), }); } + + resetController(controller: SyncDestinationSecretsController, isExiting: boolean) { + if (isExiting) { + controller.set('page', undefined); + } + } } diff --git a/ui/lib/sync/addon/routes/secrets/destinations/index.ts b/ui/lib/sync/addon/routes/secrets/destinations/index.ts index 132417095f..ae8d37cbcc 100644 --- a/ui/lib/sync/addon/routes/secrets/destinations/index.ts +++ b/ui/lib/sync/addon/routes/secrets/destinations/index.ts @@ -11,6 +11,7 @@ import type StoreService from 'vault/services/store'; import type RouterService from '@ember/routing/router-service'; import type { ModelFrom } from 'vault/vault/route'; import type SyncDestinationModel from 'vault/vault/models/sync/destination'; +import type Controller from '@ember/controller'; interface SyncSecretsDestinationsIndexRouteParams { name: string; @@ -18,6 +19,19 @@ interface SyncSecretsDestinationsIndexRouteParams { page: string; } +interface SyncSecretsDestinationsRouteModel { + destinations: SyncDestinationModel[]; + nameFilter: string | undefined; + typeFilter: string | undefined; +} + +interface SyncSecretsDestinationsController extends Controller { + model: SyncSecretsDestinationsRouteModel; + page: number | undefined; + name: number | undefined; + type: number | undefined; +} + export default class SyncSecretsDestinationsIndexRoute extends Route { @service declare readonly store: StoreService; @service declare readonly router: RouterService; @@ -35,7 +49,7 @@ export default class SyncSecretsDestinationsIndexRoute extends Route { }; redirect(model: ModelFrom) { - if (model.destinations.length === 0) { + if (!model.destinations.meta.total) { this.router.transitionTo('vault.cluster.sync.secrets.overview'); } } @@ -43,7 +57,7 @@ export default class SyncSecretsDestinationsIndexRoute extends Route { filterData(dataset: Array, name: string, type: string): Array { let filteredDataset = dataset; const filter = (key: keyof SyncDestinationModel, value: string) => { - return dataset.filter((model) => { + return filteredDataset.filter((model) => { return model[key].toLowerCase().includes(value.toLowerCase()); }); }; @@ -68,4 +82,14 @@ export default class SyncSecretsDestinationsIndexRoute extends Route { typeFilter: params.type, }); } + + resetController(controller: SyncSecretsDestinationsController, isExiting: boolean) { + if (isExiting) { + controller.setProperties({ + page: undefined, + name: undefined, + type: undefined, + }); + } + } } diff --git a/ui/tests/acceptance/sync/secrets/destinations-test.js b/ui/tests/acceptance/sync/secrets/destinations-test.js index 017460621f..715d8ab1f5 100644 --- a/ui/tests/acceptance/sync/secrets/destinations-test.js +++ b/ui/tests/acceptance/sync/secrets/destinations-test.js @@ -9,7 +9,7 @@ import { setupMirage } from 'ember-cli-mirage/test-support'; import syncScenario from 'vault/mirage/scenarios/sync'; import syncHandlers from 'vault/mirage/handlers/sync'; import authPage from 'vault/tests/pages/auth'; -import { click, visit } from '@ember/test-helpers'; +import { click, visit, fillIn } from '@ember/test-helpers'; import { PAGE } from 'vault/tests/helpers/sync/sync-selectors'; const { searchSelect, filter, listItem } = PAGE; @@ -29,6 +29,11 @@ module('Acceptance | sync | destinations', function (hooks) { assert.dom(listItem).exists({ count: 6 }, 'All destinations render'); await click(`${filter('type')} .ember-basic-dropdown-trigger`); await click(searchSelect.option()); - assert.dom(listItem).exists({ count: 2 }, 'Filtered destinations render'); + assert.dom(listItem).exists({ count: 2 }, 'Destinations are filtered by type'); + await fillIn(filter('name'), 'new'); + assert.dom(listItem).exists({ count: 1 }, 'Destinations are filtered by type and name'); + await click(searchSelect.removeSelected); + await fillIn(filter('name'), 'gcp'); + assert.dom(listItem).exists({ count: 1 }, 'Destinations are filtered by name'); }); }); diff --git a/ui/tests/helpers/general-selectors.js b/ui/tests/helpers/general-selectors.js index 670fda1f4c..36ee70c5f9 100644 --- a/ui/tests/helpers/general-selectors.js +++ b/ui/tests/helpers/general-selectors.js @@ -14,6 +14,7 @@ export const SELECTORS = { icon: (name) => `[data-test-icon="${name}"]`, tab: (name) => `[data-test-tab="${name}"]`, filter: (name) => `[data-test-filter="${name}"]`, + filterInput: '[data-test-filter-input]', confirmModalInput: '[data-test-confirmation-modal-input]', confirmButton: '[data-test-confirm-button]', emptyStateTitle: '[data-test-empty-state-title]', diff --git a/ui/tests/integration/components/sync/secrets/page/destinations-test.js b/ui/tests/integration/components/sync/secrets/page/destinations-test.js index 4366bb379b..66bf310a79 100644 --- a/ui/tests/integration/components/sync/secrets/page/destinations-test.js +++ b/ui/tests/integration/components/sync/secrets/page/destinations-test.js @@ -7,7 +7,7 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { setupEngine } from 'ember-engines/test-support'; import { setupMirage } from 'ember-cli-mirage/test-support'; -import { render, click } from '@ember/test-helpers'; +import { render, click, fillIn } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs'; import sinon from 'sinon'; @@ -97,8 +97,7 @@ module('Integration | Component | sync | Page::Destinations', function (hooks) { ); // NAME FILTER - await click(`${filter('name')} .ember-basic-dropdown-trigger`); - await click(searchSelect.option(searchSelect.optionIndex('destination-aws'))); + await fillIn(filter('name'), 'destination-aws'); assert.deepEqual( this.transitionStub.lastCall.args, ['vault.cluster.sync.secrets.destinations', { queryParams: { name: 'destination-aws' } }], From ee0ccea547ae6a6c3c097e67903d5cc2b9184f2a Mon Sep 17 00:00:00 2001 From: claire bontempo <68122737+hellobontempo@users.noreply.github.com> Date: Sun, 7 Jan 2024 12:06:03 -0800 Subject: [PATCH 05/14] UI: Changes jwks_ca_pem param to a 'file' edit type (#24697) * change jwks_ca_pem to file edit type * add changelog --- changelog/24697.txt | 3 +++ ui/app/models/auth-config/jwt.js | 30 +++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 changelog/24697.txt diff --git a/changelog/24697.txt b/changelog/24697.txt new file mode 100644 index 0000000000..49492d19b2 --- /dev/null +++ b/changelog/24697.txt @@ -0,0 +1,3 @@ +```release-note:bug +ui: Fixes input for jwks_ca_pem when configuring a JWT auth method +``` \ No newline at end of file diff --git a/ui/app/models/auth-config/jwt.js b/ui/app/models/auth-config/jwt.js index e432800c30..f675c29c9f 100644 --- a/ui/app/models/auth-config/jwt.js +++ b/ui/app/models/auth-config/jwt.js @@ -24,12 +24,31 @@ export default AuthConfig.extend({ oidcClientSecret: attr('string', { label: 'OIDC client secret', }), + oidcDiscoveryCaPem: attr('string', { label: 'OIDC discovery CA PEM', editType: 'file', helpText: 'The CA certificate or chain of certificates, in PEM format, to use to validate connections to the OIDC Discovery URL. If not set, system certificates are used', }), + + jwksCaPem: attr('string', { + label: 'JWKS CA PEM', + editType: 'file', + }), + + jwksUrl: attr('string', { + label: 'JWKS URL', + }), + + oidcResponseMode: attr('string', { + label: 'OIDC response mode', + }), + + oidcResponseTypes: attr('string', { + label: 'OIDC response types', + }), + jwtValidationPubkeys: attr({ label: 'JWT validation public keys', editType: 'stringArray', @@ -38,14 +57,23 @@ export default AuthConfig.extend({ jwtSupportedAlgs: attr({ label: 'JWT supported algorithms', }), + boundIssuer: attr('string', { helpText: 'The value against which to match the iss claim in a JWT', }), + fieldGroups: computed('constructor.modelName', 'newFields', function () { const type = this.constructor.modelName.split('/')[1].toUpperCase(); let groups = [ { - default: ['oidcDiscoveryUrl', 'defaultRole'], + default: [ + 'oidcDiscoveryUrl', + 'defaultRole', + 'jwksCaPem', + 'jwksUrl', + 'oidcResponseMode', + 'oidcResponseTypes', + ], }, { [`${type} Options`]: [ From 6e537bb376d29e7054ca7eab854f02eb481808e9 Mon Sep 17 00:00:00 2001 From: Tom Proctor Date: Mon, 8 Jan 2024 12:21:13 +0000 Subject: [PATCH 06/14] Support reloading database plugins across multiple mounts (#24512) * Support reloading database plugins across multiple mounts * Add clarifying comment to MountEntry.Path field * Tests: Replace non-parallelisable t.Setenv with plugin env settings --- builtin/logical/database/backend_test.go | 271 +++++++++--------- .../logical/database/versioning_large_test.go | 7 +- builtin/plugin/backend_test.go | 5 +- changelog/24512.txt | 6 + http/plugin_test.go | 6 +- vault/external_tests/plugin/plugin_test.go | 32 +++ vault/logical_system.go | 25 +- vault/mount.go | 2 +- vault/plugin_reload.go | 47 ++- 9 files changed, 233 insertions(+), 168 deletions(-) create mode 100644 changelog/24512.txt diff --git a/builtin/logical/database/backend_test.go b/builtin/logical/database/backend_test.go index 0554365e1c..f330e5e5c2 100644 --- a/builtin/logical/database/backend_test.go +++ b/builtin/logical/database/backend_test.go @@ -6,12 +6,15 @@ package database import ( "context" "database/sql" + "encoding/json" + "errors" "fmt" "log" "net/url" "os" "reflect" "strings" + "sync" "testing" "time" @@ -35,12 +38,26 @@ import ( "github.com/mitchellh/mapstructure" ) +func getClusterPostgresDBWithFactory(t *testing.T, factory logical.Factory) (*vault.TestCluster, logical.SystemView) { + t.Helper() + cluster, sys := getClusterWithFactory(t, factory) + vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "postgresql-database-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_PostgresMultiplexed", + []string{fmt.Sprintf("%s=%s", pluginutil.PluginCACertPEMEnv, cluster.CACertPEMFile)}) + return cluster, sys +} + func getClusterPostgresDB(t *testing.T) (*vault.TestCluster, logical.SystemView) { + t.Helper() + cluster, sys := getClusterPostgresDBWithFactory(t, Factory) + return cluster, sys +} + +func getClusterWithFactory(t *testing.T, factory logical.Factory) (*vault.TestCluster, logical.SystemView) { t.Helper() pluginDir := corehelpers.MakeTestPluginDir(t) coreConfig := &vault.CoreConfig{ LogicalBackends: map[string]logical.Factory{ - "database": Factory, + "database": factory, }, BuiltinRegistry: builtinplugins.Registry, PluginDirectory: pluginDir, @@ -53,36 +70,14 @@ func getClusterPostgresDB(t *testing.T) (*vault.TestCluster, logical.SystemView) cores := cluster.Cores vault.TestWaitActive(t, cores[0].Core) - os.Setenv(pluginutil.PluginCACertPEMEnv, cluster.CACertPEMFile) - sys := vault.TestDynamicSystemView(cores[0].Core, nil) - vault.TestAddTestPlugin(t, cores[0].Core, "postgresql-database-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_PostgresMultiplexed", []string{}) return cluster, sys } func getCluster(t *testing.T) (*vault.TestCluster, logical.SystemView) { t.Helper() - pluginDir := corehelpers.MakeTestPluginDir(t) - coreConfig := &vault.CoreConfig{ - LogicalBackends: map[string]logical.Factory{ - "database": Factory, - }, - BuiltinRegistry: builtinplugins.Registry, - PluginDirectory: pluginDir, - } - - cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ - HandlerFunc: vaulthttp.Handler, - }) - cluster.Start() - cores := cluster.Cores - vault.TestWaitActive(t, cores[0].Core) - - os.Setenv(pluginutil.PluginCACertPEMEnv, cluster.CACertPEMFile) - - sys := vault.TestDynamicSystemView(cores[0].Core, nil) - + cluster, sys := getClusterWithFactory(t, Factory) return cluster, sys } @@ -515,7 +510,7 @@ func TestBackend_basic(t *testing.T) { if credsResp.Secret.TTL != 5*time.Minute { t.Fatalf("unexpected TTL of %d", credsResp.Secret.TTL) } - if !testCredsExist(t, credsResp, connURL) { + if !testCredsExist(t, credsResp.Data, connURL) { t.Fatalf("Creds should exist") } @@ -535,7 +530,7 @@ func TestBackend_basic(t *testing.T) { t.Fatalf("err:%s resp:%#v\n", err, resp) } - if testCredsExist(t, credsResp, connURL) { + if testCredsExist(t, credsResp.Data, connURL) { t.Fatalf("Creds should not exist") } } @@ -553,7 +548,7 @@ func TestBackend_basic(t *testing.T) { if err != nil || (credsResp != nil && credsResp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, credsResp) } - if !testCredsExist(t, credsResp, connURL) { + if !testCredsExist(t, credsResp.Data, connURL) { t.Fatalf("Creds should exist") } @@ -586,108 +581,118 @@ func TestBackend_basic(t *testing.T) { t.Fatalf("err:%s resp:%#v\n", err, resp) } - if testCredsExist(t, credsResp, connURL) { + if testCredsExist(t, credsResp.Data, connURL) { t.Fatalf("Creds should not exist") } } } -func TestBackend_connectionCrud(t *testing.T) { - cluster, sys := getClusterPostgresDB(t) - defer cluster.Cleanup() +// singletonDBFactory allows us to reach into the internals of a databaseBackend +// even when it's been created by a call to the sys mount. The factory method +// satisfies the logical.Factory type, and lazily creates the databaseBackend +// once the SystemView has been provided because the factory method itself is an +// input for creating the test cluster and its system view. +type singletonDBFactory struct { + once sync.Once + db *databaseBackend + + sys logical.SystemView +} + +// factory satisfies the logical.Factory type. +func (s *singletonDBFactory) factory(context.Context, *logical.BackendConfig) (logical.Backend, error) { + if s.sys == nil { + return nil, errors.New("sys is nil") + } config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} - config.System = sys + config.System = s.sys - b, err := Factory(context.Background(), config) + var err error + s.once.Do(func() { + var b logical.Backend + b, err = Factory(context.Background(), config) + s.db = b.(*databaseBackend) + }) if err != nil { - t.Fatal(err) + return nil, err } - defer b.Cleanup(context.Background()) + if s.db == nil { + return nil, errors.New("db is nil") + } + return s.db, nil +} + +func TestBackend_connectionCrud(t *testing.T) { + dbFactory := &singletonDBFactory{} + cluster, sys := getClusterPostgresDBWithFactory(t, dbFactory.factory) + defer cluster.Cleanup() + + dbFactory.sys = sys + client := cluster.Cores[0].Client.Logical() cleanup, connURL := postgreshelper.PrepareTestContainer(t, "13.4-buster") defer cleanup() + // Mount the database plugin. + resp, err := client.Write("sys/mounts/database", map[string]interface{}{ + "type": "database", + }) + if err != nil { + t.Fatalf("err:%s resp:%#v\n", err, resp) + } + // Configure a connection - data := map[string]interface{}{ + resp, err = client.Write("database/config/plugin-test", map[string]interface{}{ "connection_url": "test", "plugin_name": "postgresql-database-plugin", "verify_connection": false, - } - req := &logical.Request{ - Operation: logical.UpdateOperation, - Path: "config/plugin-test", - Storage: config.StorageView, - Data: data, - } - resp, err := b.HandleRequest(namespace.RootContext(nil), req) - if err != nil || (resp != nil && resp.IsError()) { + }) + if err != nil { t.Fatalf("err:%s resp:%#v\n", err, resp) } // Configure a second connection to confirm below it doesn't get restarted. - data = map[string]interface{}{ + resp, err = client.Write("database/config/plugin-test-hana", map[string]interface{}{ "connection_url": "test", "plugin_name": "hana-database-plugin", "verify_connection": false, - } - req = &logical.Request{ - Operation: logical.UpdateOperation, - Path: "config/plugin-test-hana", - Storage: config.StorageView, - Data: data, - } - resp, err = b.HandleRequest(namespace.RootContext(nil), req) - if err != nil || (resp != nil && resp.IsError()) { + }) + if err != nil { t.Fatalf("err:%s resp:%#v\n", err, resp) } // Create a role - data = map[string]interface{}{ + resp, err = client.Write("database/roles/plugin-role-test", map[string]interface{}{ "db_name": "plugin-test", "creation_statements": testRole, "revocation_statements": defaultRevocationSQL, "default_ttl": "5m", "max_ttl": "10m", - } - req = &logical.Request{ - Operation: logical.UpdateOperation, - Path: "roles/plugin-role-test", - Storage: config.StorageView, - Data: data, - } - resp, err = b.HandleRequest(namespace.RootContext(nil), req) - if err != nil || (resp != nil && resp.IsError()) { + }) + if err != nil { t.Fatalf("err:%s resp:%#v\n", err, resp) } // Update the connection - data = map[string]interface{}{ + resp, err = client.Write("database/config/plugin-test", map[string]interface{}{ "connection_url": connURL, "plugin_name": "postgresql-database-plugin", "allowed_roles": []string{"plugin-role-test"}, "username": "postgres", "password": "secret", "private_key": "PRIVATE_KEY", - } - req = &logical.Request{ - Operation: logical.UpdateOperation, - Path: "config/plugin-test", - Storage: config.StorageView, - Data: data, - } - resp, err = b.HandleRequest(namespace.RootContext(nil), req) - if err != nil || (resp != nil && resp.IsError()) { + }) + if err != nil { t.Fatalf("err:%s resp:%#v\n", err, resp) } if len(resp.Warnings) == 0 { t.Fatalf("expected warning about password in url %s, resp:%#v\n", connURL, resp) } - req.Operation = logical.ReadOperation - resp, err = b.HandleRequest(namespace.RootContext(nil), req) - if err != nil || (resp != nil && resp.IsError()) { + resp, err = client.Read("database/config/plugin-test") + if err != nil { t.Fatalf("err:%s resp:%#v\n", err, resp) } returnedConnectionDetails := resp.Data["connection_details"].(map[string]interface{}) @@ -703,11 +708,16 @@ func TestBackend_connectionCrud(t *testing.T) { } // Replace connection url with templated version - req.Operation = logical.UpdateOperation - connURL = strings.ReplaceAll(connURL, "postgres:secret", "{{username}}:{{password}}") - data["connection_url"] = connURL - resp, err = b.HandleRequest(namespace.RootContext(nil), req) - if err != nil || (resp != nil && resp.IsError()) { + templatedConnURL := strings.ReplaceAll(connURL, "postgres:secret", "{{username}}:{{password}}") + resp, err = client.Write("database/config/plugin-test", map[string]interface{}{ + "connection_url": templatedConnURL, + "plugin_name": "postgresql-database-plugin", + "allowed_roles": []string{"plugin-role-test"}, + "username": "postgres", + "password": "secret", + "private_key": "PRIVATE_KEY", + }) + if err != nil { t.Fatalf("err:%s resp:%#v\n", err, resp) } @@ -716,36 +726,38 @@ func TestBackend_connectionCrud(t *testing.T) { "plugin_name": "postgresql-database-plugin", "connection_details": map[string]interface{}{ "username": "postgres", - "connection_url": connURL, + "connection_url": templatedConnURL, }, - "allowed_roles": []string{"plugin-role-test"}, - "root_credentials_rotate_statements": []string(nil), + "allowed_roles": []any{"plugin-role-test"}, + "root_credentials_rotate_statements": []any{}, "password_policy": "", "plugin_version": "", } - req.Operation = logical.ReadOperation - resp, err = b.HandleRequest(namespace.RootContext(nil), req) - if err != nil || (resp != nil && resp.IsError()) { + resp, err = client.Read("database/config/plugin-test") + if err != nil { t.Fatalf("err:%s resp:%#v\n", err, resp) } delete(resp.Data["connection_details"].(map[string]interface{}), "name") if diff := deep.Equal(resp.Data, expected); diff != nil { - t.Fatal(diff) + t.Fatal(strings.Join(diff, "\n")) } // Test endpoints for reloading plugins. - for _, reloadPath := range []string{ - "reset/plugin-test", - "reload/postgresql-database-plugin", + for _, reload := range []struct { + path string + data map[string]any + checkCount bool + }{ + {"database/reset/plugin-test", nil, false}, + {"database/reload/postgresql-database-plugin", nil, true}, + {"sys/plugins/reload/backend", map[string]any{ + "plugin": "postgresql-database-plugin", + }, false}, } { getConnectionID := func(name string) string { t.Helper() - dbBackend, ok := b.(*databaseBackend) - if !ok { - t.Fatal("could not convert logical.Backend to databaseBackend") - } - dbi := dbBackend.connections.Get(name) + dbi := dbFactory.db.connections.Get(name) if dbi == nil { t.Fatal("no plugin-test dbi") } @@ -753,14 +765,8 @@ func TestBackend_connectionCrud(t *testing.T) { } initialID := getConnectionID("plugin-test") hanaID := getConnectionID("plugin-test-hana") - req = &logical.Request{ - Operation: logical.UpdateOperation, - Path: reloadPath, - Storage: config.StorageView, - Data: map[string]interface{}{}, - } - resp, err = b.HandleRequest(namespace.RootContext(nil), req) - if err != nil || (resp != nil && resp.IsError()) { + resp, err = client.Write(reload.path, reload.data) + if err != nil { t.Fatalf("err:%s resp:%#v\n", err, resp) } if initialID == getConnectionID("plugin-test") { @@ -769,54 +775,43 @@ func TestBackend_connectionCrud(t *testing.T) { if hanaID != getConnectionID("plugin-test-hana") { t.Fatal("hana plugin got restarted but shouldn't have been") } - if strings.HasPrefix(reloadPath, "reload/") { - if expected := 1; expected != resp.Data["count"] { - t.Fatalf("expected %d but got %d", expected, resp.Data["count"]) + if reload.checkCount { + actual, err := resp.Data["count"].(json.Number).Int64() + if err != nil { + t.Fatal(err) } - if expected := []string{"plugin-test"}; !reflect.DeepEqual(expected, resp.Data["connections"]) { + if expected := 1; expected != int(actual) { + t.Fatalf("expected %d but got %d", expected, resp.Data["count"].(int)) + } + if expected := []any{"plugin-test"}; !reflect.DeepEqual(expected, resp.Data["connections"]) { t.Fatalf("expected %v but got %v", expected, resp.Data["connections"]) } } } // Get creds - data = map[string]interface{}{} - req = &logical.Request{ - Operation: logical.ReadOperation, - Path: "creds/plugin-role-test", - Storage: config.StorageView, - Data: data, - } - credsResp, err := b.HandleRequest(namespace.RootContext(nil), req) - if err != nil || (credsResp != nil && credsResp.IsError()) { + credsResp, err := client.Read("database/creds/plugin-role-test") + if err != nil { t.Fatalf("err:%s resp:%#v\n", err, credsResp) } - credCheckURL := dbutil.QueryHelper(connURL, map[string]string{ + credCheckURL := dbutil.QueryHelper(templatedConnURL, map[string]string{ "username": "postgres", "password": "secret", }) - if !testCredsExist(t, credsResp, credCheckURL) { + if !testCredsExist(t, credsResp.Data, credCheckURL) { t.Fatalf("Creds should exist") } // Delete Connection - data = map[string]interface{}{} - req = &logical.Request{ - Operation: logical.DeleteOperation, - Path: "config/plugin-test", - Storage: config.StorageView, - Data: data, - } - resp, err = b.HandleRequest(namespace.RootContext(nil), req) - if err != nil || (resp != nil && resp.IsError()) { + resp, err = client.Delete("database/config/plugin-test") + if err != nil { t.Fatalf("err:%s resp:%#v\n", err, resp) } // Read connection - req.Operation = logical.ReadOperation - resp, err = b.HandleRequest(namespace.RootContext(nil), req) - if err != nil || (resp != nil && resp.IsError()) { + resp, err = client.Read("database/config/plugin-test") + if err != nil { t.Fatalf("err:%s resp:%#v\n", err, resp) } @@ -1190,7 +1185,7 @@ func TestBackend_allowedRoles(t *testing.T) { t.Fatalf("err:%s resp:%#v\n", err, credsResp) } - if !testCredsExist(t, credsResp, connURL) { + if !testCredsExist(t, credsResp.Data, connURL) { t.Fatalf("Creds should exist") } @@ -1224,7 +1219,7 @@ func TestBackend_allowedRoles(t *testing.T) { t.Fatalf("err:%s resp:%#v\n", err, credsResp) } - if !testCredsExist(t, credsResp, connURL) { + if !testCredsExist(t, credsResp.Data, connURL) { t.Fatalf("Creds should exist") } @@ -1271,7 +1266,7 @@ func TestBackend_allowedRoles(t *testing.T) { t.Fatalf("err:%s resp:%#v\n", err, credsResp) } - if !testCredsExist(t, credsResp, connURL) { + if !testCredsExist(t, credsResp.Data, connURL) { t.Fatalf("Creds should exist") } } @@ -1581,13 +1576,13 @@ func TestNewDatabaseWrapper_IgnoresBuiltinVersion(t *testing.T) { } } -func testCredsExist(t *testing.T, resp *logical.Response, connURL string) bool { +func testCredsExist(t *testing.T, data map[string]any, connURL string) bool { t.Helper() var d struct { Username string `mapstructure:"username"` Password string `mapstructure:"password"` } - if err := mapstructure.Decode(resp.Data, &d); err != nil { + if err := mapstructure.Decode(data, &d); err != nil { t.Fatal(err) } log.Printf("[TRACE] Generated credentials: %v", d) diff --git a/builtin/logical/database/versioning_large_test.go b/builtin/logical/database/versioning_large_test.go index bacb4a6a71..be936c7603 100644 --- a/builtin/logical/database/versioning_large_test.go +++ b/builtin/logical/database/versioning_large_test.go @@ -25,9 +25,10 @@ func TestPlugin_lifecycle(t *testing.T) { cluster, sys := getCluster(t) defer cluster.Cleanup() - vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v4-database-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MockV4", []string{}) - vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v5-database-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MockV5", []string{}) - vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v6-database-plugin-muxed", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MockV6Multiplexed", []string{}) + env := []string{fmt.Sprintf("%s=%s", pluginutil.PluginCACertPEMEnv, cluster.CACertPEMFile)} + vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v4-database-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MockV4", env) + vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v5-database-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MockV5", env) + vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v6-database-plugin-muxed", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MockV6Multiplexed", env) config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} diff --git a/builtin/plugin/backend_test.go b/builtin/plugin/backend_test.go index c1b7a83d15..7134440612 100644 --- a/builtin/plugin/backend_test.go +++ b/builtin/plugin/backend_test.go @@ -140,9 +140,8 @@ func testConfig(t *testing.T, pluginCmd string) (*logical.BackendConfig, func()) }, } - os.Setenv(pluginutil.PluginCACertPEMEnv, cluster.CACertPEMFile) - - vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "", pluginCmd, []string{}) + vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "", pluginCmd, + []string{fmt.Sprintf("%s=%s", pluginutil.PluginCACertPEMEnv, cluster.CACertPEMFile)}) return config, func() { cluster.Cleanup() diff --git a/changelog/24512.txt b/changelog/24512.txt new file mode 100644 index 0000000000..efed04a225 --- /dev/null +++ b/changelog/24512.txt @@ -0,0 +1,6 @@ +```release-note:change +plugins: Add a warning to the response from sys/plugins/reload/backend if no plugins were reloaded. +``` +```release-note:improvement +secrets/database: Support reloading named database plugins using the sys/plugins/reload/backend API endpoint. +``` diff --git a/http/plugin_test.go b/http/plugin_test.go index fa67187621..b215a6b1c6 100644 --- a/http/plugin_test.go +++ b/http/plugin_test.go @@ -5,6 +5,7 @@ package http import ( "encoding/json" + "fmt" "io/ioutil" "os" "reflect" @@ -55,10 +56,9 @@ func getPluginClusterAndCore(t *testing.T, logger log.Logger) (*vault.TestCluste cores := cluster.Cores core := cores[0] - os.Setenv(pluginutil.PluginCACertPEMEnv, cluster.CACertPEMFile) - vault.TestWaitActive(benchhelpers.TBtoT(t), core.Core) - vault.TestAddTestPlugin(benchhelpers.TBtoT(t), core.Core, "mock-plugin", consts.PluginTypeSecrets, "", "TestPlugin_PluginMain", []string{}) + vault.TestAddTestPlugin(benchhelpers.TBtoT(t), core.Core, "mock-plugin", consts.PluginTypeSecrets, "", "TestPlugin_PluginMain", + []string{fmt.Sprintf("%s=%s", pluginutil.PluginCACertPEMEnv, cluster.CACertPEMFile)}) // Mount the mock plugin err = core.Client.Sys().Mount("mock", &api.MountInput{ diff --git a/vault/external_tests/plugin/plugin_test.go b/vault/external_tests/plugin/plugin_test.go index e29340a17c..affe8fa6b8 100644 --- a/vault/external_tests/plugin/plugin_test.go +++ b/vault/external_tests/plugin/plugin_test.go @@ -560,6 +560,9 @@ func testSystemBackend_PluginReload(t *testing.T, reqData map[string]interface{} if resp.Data["reload_id"] == nil { t.Fatal("no reload_id in response") } + if len(resp.Warnings) != 0 { + t.Fatal(resp.Warnings) + } for i := 0; i < 2; i++ { // Ensure internal backed value is reset @@ -578,6 +581,35 @@ func testSystemBackend_PluginReload(t *testing.T, reqData map[string]interface{} } } +func TestSystemBackend_PluginReload_WarningIfNoneReloaded(t *testing.T) { + cluster := testSystemBackendMock(t, 1, 2, logical.TypeLogical, "v5") + defer cluster.Cleanup() + + core := cluster.Cores[0] + client := core.Client + + for _, backendType := range []logical.BackendType{logical.TypeLogical, logical.TypeCredential} { + t.Run(backendType.String(), func(t *testing.T) { + // Perform plugin reload + resp, err := client.Logical().Write("sys/plugins/reload/backend", map[string]any{ + "plugin": "does-not-exist", + }) + if err != nil { + t.Fatalf("err: %v", err) + } + if resp == nil { + t.Fatalf("bad: %v", resp) + } + if resp.Data["reload_id"] == nil { + t.Fatal("no reload_id in response") + } + if len(resp.Warnings) == 0 { + t.Fatal("expected warning") + } + }) + } +} + // testSystemBackendMock returns a systemBackend with the desired number // of mounted mock plugin backends. numMounts alternates between different // ways of providing the plugin_name. diff --git a/vault/logical_system.go b/vault/logical_system.go index 1ae623546e..6215954850 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -738,11 +738,24 @@ func (b *SystemBackend) handlePluginReloadUpdate(ctx context.Context, req *logic return logical.ErrorResponse("plugin or mounts must be provided"), nil } + resp := logical.Response{ + Data: map[string]interface{}{ + "reload_id": req.ID, + }, + } + if pluginName != "" { - err := b.Core.reloadMatchingPlugin(ctx, pluginName) + reloaded, err := b.Core.reloadMatchingPlugin(ctx, pluginName) if err != nil { return nil, err } + if reloaded == 0 { + if scope == globalScope { + resp.AddWarning("no plugins were reloaded locally (but they may be reloaded on other nodes)") + } else { + resp.AddWarning("no plugins were reloaded") + } + } } else if len(pluginMounts) > 0 { err := b.Core.reloadMatchingPluginMounts(ctx, pluginMounts) if err != nil { @@ -750,20 +763,14 @@ func (b *SystemBackend) handlePluginReloadUpdate(ctx context.Context, req *logic } } - r := logical.Response{ - Data: map[string]interface{}{ - "reload_id": req.ID, - }, - } - if scope == globalScope { err := handleGlobalPluginReload(ctx, b.Core, req.ID, pluginName, pluginMounts) if err != nil { return nil, err } - return logical.RespondWithStatusCode(&r, req, http.StatusAccepted) + return logical.RespondWithStatusCode(&resp, req, http.StatusAccepted) } - return &r, nil + return &resp, nil } func (b *SystemBackend) handlePluginRuntimeCatalogUpdate(ctx context.Context, _ *logical.Request, d *framework.FieldData) (*logical.Response, error) { diff --git a/vault/mount.go b/vault/mount.go index 158dbd9356..d70e819360 100644 --- a/vault/mount.go +++ b/vault/mount.go @@ -322,7 +322,7 @@ const mountStateUnmounting = "unmounting" // MountEntry is used to represent a mount table entry type MountEntry struct { Table string `json:"table"` // The table it belongs to - Path string `json:"path"` // Mount Path + Path string `json:"path"` // Mount Path, as provided in the mount API call but with a trailing slash, i.e. no auth/ or namespace prefix. Type string `json:"type"` // Logical backend Type. NB: This is the plugin name, e.g. my-vault-plugin, NOT plugin type (e.g. auth). Description string `json:"description"` // User-provided description UUID string `json:"uuid"` // Barrier view UUID diff --git a/vault/plugin_reload.go b/vault/plugin_reload.go index 7fa6e936e6..938c47eb34 100644 --- a/vault/plugin_reload.go +++ b/vault/plugin_reload.go @@ -70,10 +70,10 @@ func (c *Core) reloadMatchingPluginMounts(ctx context.Context, mounts []string) return errors } -// reloadPlugin reloads all mounted backends that are of -// plugin pluginName (name of the plugin as registered in -// the plugin catalog). -func (c *Core) reloadMatchingPlugin(ctx context.Context, pluginName string) error { +// reloadMatchingPlugin reloads all mounted backends that are named pluginName +// (name of the plugin as registered in the plugin catalog). It returns the +// number of plugins that were reloaded and an error if any. +func (c *Core) reloadMatchingPlugin(ctx context.Context, pluginName string) (reloaded int, err error) { c.mountsLock.RLock() defer c.mountsLock.RUnlock() c.authLock.RLock() @@ -81,25 +81,49 @@ func (c *Core) reloadMatchingPlugin(ctx context.Context, pluginName string) erro ns, err := namespace.FromContext(ctx) if err != nil { - return err + return reloaded, err } - // Filter mount entries that only matches the plugin name for _, entry := range c.mounts.Entries { // We dont reload mounts that are not in the same namespace if ns.ID != entry.Namespace().ID { continue } + if entry.Type == pluginName || (entry.Type == "plugin" && entry.Config.PluginName == pluginName) { err := c.reloadBackendCommon(ctx, entry, false) if err != nil { - return err + return reloaded, err + } + reloaded++ + c.logger.Info("successfully reloaded plugin", "plugin", pluginName, "namespace", entry.Namespace(), "path", entry.Path, "version", entry.Version) + } else if entry.Type == "database" { + // The combined database plugin is itself a secrets engine, but + // knowledge of whether a database plugin is in use within a particular + // mount is internal to the combined database plugin's storage, so + // we delegate the reload request with an internally routed request. + req := &logical.Request{ + Operation: logical.UpdateOperation, + Path: entry.Path + "reload/" + pluginName, + } + resp, err := c.router.Route(ctx, req) + if err != nil { + return reloaded, err + } + if resp == nil { + return reloaded, fmt.Errorf("failed to reload %q database plugin(s) mounted under %s", pluginName, entry.Path) + } + if resp.IsError() { + return reloaded, fmt.Errorf("failed to reload %q database plugin(s) mounted under %s: %s", pluginName, entry.Path, resp.Error()) + } + + if count, ok := resp.Data["count"].(int); ok && count > 0 { + c.logger.Info("successfully reloaded database plugin(s)", "plugin", pluginName, "namespace", entry.Namespace(), "path", entry.Path, "connections", resp.Data["connections"]) + reloaded += count } - c.logger.Info("successfully reloaded plugin", "plugin", pluginName, "path", entry.Path, "version", entry.Version) } } - // Filter auth mount entries that ony matches the plugin name for _, entry := range c.auth.Entries { // We dont reload mounts that are not in the same namespace if ns.ID != entry.Namespace().ID { @@ -109,13 +133,14 @@ func (c *Core) reloadMatchingPlugin(ctx context.Context, pluginName string) erro if entry.Type == pluginName || (entry.Type == "plugin" && entry.Config.PluginName == pluginName) { err := c.reloadBackendCommon(ctx, entry, true) if err != nil { - return err + return reloaded, err } + reloaded++ c.logger.Info("successfully reloaded plugin", "plugin", entry.Accessor, "path", entry.Path, "version", entry.Version) } } - return nil + return reloaded, nil } // reloadBackendCommon is a generic method to reload a backend provided a From 2047ce752724c0b3e2e6629d3ccf5f5ba9e53eb6 Mon Sep 17 00:00:00 2001 From: Kuba Wieczorek Date: Mon, 8 Jan 2024 13:57:43 +0000 Subject: [PATCH 07/14] [VAULT-22480] Add audit fallback device (#24583) Co-authored-by: Peter Wilson --- audit/types.go | 5 + builtin/audit/file/backend.go | 25 +++ builtin/audit/file/backend_test.go | 126 ++++++++++++ builtin/audit/socket/backend.go | 23 +++ builtin/audit/socket/backend_test.go | 192 ++++++++++++++++++ builtin/audit/syslog/backend.go | 25 +++ builtin/audit/syslog/backend_test.go | 125 ++++++++++++ helper/testhelpers/corehelpers/corehelpers.go | 8 +- vault/audit.go | 16 +- vault/audit_broker.go | 184 +++++++++++++++-- vault/audit_broker_test.go | 115 +++++++++++ vault/audit_test.go | 63 ++++++ 12 files changed, 888 insertions(+), 19 deletions(-) diff --git a/audit/types.go b/audit/types.go index 3434ff84d8..8d8bd158c3 100644 --- a/audit/types.go +++ b/audit/types.go @@ -279,6 +279,11 @@ type Backend interface { // nodes for node and pipeline registration. event.PipelineReader + // IsFallback can be used to determine if this audit backend device is intended to + // be used as a fallback to catch all events that are not written when only using + // filtered pipelines. + IsFallback() bool + // LogRequest is used to synchronously log a request. This is done after the // request is authorized but before the request is executed. The arguments // MUST not be modified in any way. They should be deep copied if this is diff --git a/builtin/audit/file/backend.go b/builtin/audit/file/backend.go index 2681ee244e..44fd2438e0 100644 --- a/builtin/audit/file/backend.go +++ b/builtin/audit/file/backend.go @@ -16,6 +16,7 @@ import ( "sync/atomic" "github.com/hashicorp/eventlogger" + "github.com/hashicorp/go-secure-stdlib/parseutil" "github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/internal/observability/event" "github.com/hashicorp/vault/sdk/helper/salt" @@ -36,6 +37,7 @@ var _ audit.Backend = (*Backend)(nil) // or reset the write cursor, this should be done in the future. type Backend struct { f *os.File + fallback bool fileLock sync.RWMutex formatter *audit.EntryFormatterWriter formatConfig audit.FormatterConfig @@ -60,6 +62,21 @@ func Factory(_ context.Context, conf *audit.BackendConfig, useEventLogger bool, return nil, fmt.Errorf("%s: nil salt view", op) } + // The config options 'fallback' and 'filter' are mutually exclusive, a fallback + // device catches everything, so it cannot be allowed to filter. + var fallback bool + var err error + if fallbackRaw, ok := conf.Config["fallback"]; ok { + fallback, err = parseutil.ParseBool(fallbackRaw) + if err != nil { + return nil, fmt.Errorf("%s: unable to parse 'fallback': %w", op, err) + } + } + + if _, ok := conf.Config["filter"]; ok && fallback { + return nil, fmt.Errorf("%s: cannot configure a fallback device with a filter: %w", op, event.ErrInvalidParameter) + } + // Get file path from config or fall back to the old option name ('path') for compatibility // (see commit bac4fe0799a372ba1245db642f3f6cd1f1d02669). var filePath string @@ -106,6 +123,7 @@ func Factory(_ context.Context, conf *audit.BackendConfig, useEventLogger bool, } b := &Backend{ + fallback: fallback, filePath: filePath, formatConfig: cfg, mode: mode, @@ -550,3 +568,10 @@ func (b *Backend) EventType() eventlogger.EventType { func (b *Backend) HasFiltering() bool { return len(b.nodeIDList) > 0 && b.nodeMap[b.nodeIDList[0]].Type() == eventlogger.NodeTypeFilter } + +// IsFallback can be used to determine if this audit backend device is intended to +// be used as a fallback to catch all events that are not written when only using +// filtered pipelines. +func (b *Backend) IsFallback() bool { + return b.fallback +} diff --git a/builtin/audit/file/backend_test.go b/builtin/audit/file/backend_test.go index 17ea7fd203..1e47f0f2cd 100644 --- a/builtin/audit/file/backend_test.go +++ b/builtin/audit/file/backend_test.go @@ -576,3 +576,129 @@ func TestBackend_configureFilterFormatterSink(t *testing.T) { node = b.nodeMap[id] require.Equal(t, eventlogger.NodeTypeSink, node.Type()) } + +// TestBackend_Factory_Conf is used to ensure that any configuration which is +// supplied, is validated and tested. +func TestBackend_Factory_Conf(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + tests := map[string]struct { + backendConfig *audit.BackendConfig + isErrorExpected bool + expectedErrorMessage string + }{ + "nil-salt-config": { + backendConfig: &audit.BackendConfig{ + SaltConfig: nil, + }, + isErrorExpected: true, + expectedErrorMessage: "file.Factory: nil salt config", + }, + "nil-salt-view": { + backendConfig: &audit.BackendConfig{ + SaltConfig: &salt.Config{}, + }, + isErrorExpected: true, + expectedErrorMessage: "file.Factory: nil salt view", + }, + "fallback-device-with-filter": { + backendConfig: &audit.BackendConfig{ + MountPath: "discard", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "fallback": "true", + "file_path": discard, + "filter": "mount_type == kv", + }, + }, + isErrorExpected: true, + expectedErrorMessage: "file.Factory: cannot configure a fallback device with a filter: invalid parameter", + }, + "non-fallback-device-with-filter": { + backendConfig: &audit.BackendConfig{ + MountPath: "discard", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "fallback": "false", + "file_path": discard, + "filter": "mount_type == kv", + }, + }, + isErrorExpected: false, + }, + } + + for name, tc := range tests { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + be, err := Factory(ctx, tc.backendConfig, true, nil) + + switch { + case tc.isErrorExpected: + require.Error(t, err) + require.EqualError(t, err, tc.expectedErrorMessage) + default: + require.NoError(t, err) + require.NotNil(t, be) + } + }) + } +} + +// TestBackend_IsFallback ensures that the 'fallback' config setting is parsed +// and set correctly, then exposed via the interface method IsFallback(). +func TestBackend_IsFallback(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + tests := map[string]struct { + backendConfig *audit.BackendConfig + isFallbackExpected bool + }{ + "fallback": { + backendConfig: &audit.BackendConfig{ + MountPath: "discard", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "fallback": "true", + "file_path": discard, + }, + }, + isFallbackExpected: true, + }, + "no-fallback": { + backendConfig: &audit.BackendConfig{ + MountPath: "discard", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "fallback": "false", + "file_path": discard, + }, + }, + isFallbackExpected: false, + }, + } + + for name, tc := range tests { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + be, err := Factory(ctx, tc.backendConfig, true, nil) + require.NoError(t, err) + require.NotNil(t, be) + require.Equal(t, tc.isFallbackExpected, be.IsFallback()) + }) + } +} diff --git a/builtin/audit/socket/backend.go b/builtin/audit/socket/backend.go index 09662c2ab6..84629efe31 100644 --- a/builtin/audit/socket/backend.go +++ b/builtin/audit/socket/backend.go @@ -29,6 +29,7 @@ type Backend struct { sync.Mutex address string connection net.Conn + fallback bool formatter *audit.EntryFormatterWriter formatConfig audit.FormatterConfig name string @@ -73,12 +74,27 @@ func Factory(_ context.Context, conf *audit.BackendConfig, useEventLogger bool, return nil, fmt.Errorf("%s: failed to parse 'write_timeout': %w", op, err) } + // The config options 'fallback' and 'filter' are mutually exclusive, a fallback + // device catches everything, so it cannot be allowed to filter. + var fallback bool + if fallbackRaw, ok := conf.Config["fallback"]; ok { + fallback, err = parseutil.ParseBool(fallbackRaw) + if err != nil { + return nil, fmt.Errorf("%s: unable to parse 'fallback': %w", op, err) + } + } + + if _, ok := conf.Config["filter"]; ok && fallback { + return nil, fmt.Errorf("%s: cannot configure a fallback device with a filter: %w", op, event.ErrInvalidParameter) + } + cfg, err := formatterConfig(conf.Config) if err != nil { return nil, fmt.Errorf("%s: failed to create formatter config: %w", op, err) } b := &Backend{ + fallback: fallback, address: address, formatConfig: cfg, name: conf.MountPath, @@ -443,3 +459,10 @@ func (b *Backend) EventType() eventlogger.EventType { func (b *Backend) HasFiltering() bool { return len(b.nodeIDList) > 0 && b.nodeMap[b.nodeIDList[0]].Type() == eventlogger.NodeTypeFilter } + +// IsFallback can be used to determine if this audit backend device is intended to +// be used as a fallback to catch all events that are not written when only using +// filtered pipelines. +func (b *Backend) IsFallback() bool { + return b.fallback +} diff --git a/builtin/audit/socket/backend_test.go b/builtin/audit/socket/backend_test.go index d1dfc38472..3693f8fdee 100644 --- a/builtin/audit/socket/backend_test.go +++ b/builtin/audit/socket/backend_test.go @@ -4,10 +4,13 @@ package socket import ( + "context" "testing" "github.com/hashicorp/eventlogger" "github.com/hashicorp/vault/audit" + "github.com/hashicorp/vault/sdk/helper/salt" + "github.com/hashicorp/vault/sdk/logical" "github.com/stretchr/testify/require" ) @@ -329,3 +332,192 @@ func TestBackend_configureFilterFormatterSink(t *testing.T) { node = b.nodeMap[id] require.Equal(t, eventlogger.NodeTypeSink, node.Type()) } + +// TestBackend_Factory_Conf is used to ensure that any configuration which is +// supplied, is validated and tested. +func TestBackend_Factory_Conf(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + tests := map[string]struct { + backendConfig *audit.BackendConfig + isErrorExpected bool + expectedErrorMessage string + }{ + "nil-salt-config": { + backendConfig: &audit.BackendConfig{ + SaltConfig: nil, + }, + isErrorExpected: true, + expectedErrorMessage: "socket.Factory: nil salt config", + }, + "nil-salt-view": { + backendConfig: &audit.BackendConfig{ + SaltConfig: &salt.Config{}, + }, + isErrorExpected: true, + expectedErrorMessage: "socket.Factory: nil salt view", + }, + "no-address": { + backendConfig: &audit.BackendConfig{ + MountPath: "discard", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{}, + }, + isErrorExpected: true, + expectedErrorMessage: "socket.Factory: address is required", + }, + "empty-address": { + backendConfig: &audit.BackendConfig{ + MountPath: "discard", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "address": "", + }, + }, + isErrorExpected: true, + expectedErrorMessage: "socket.Factory: error configuring sink node: socket.(Backend).configureSinkNode: address is required: invalid parameter", + }, + "whitespace-address": { + backendConfig: &audit.BackendConfig{ + MountPath: "discard", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "address": " ", + }, + }, + isErrorExpected: true, + expectedErrorMessage: "socket.Factory: error configuring sink node: socket.(Backend).configureSinkNode: address is required: invalid parameter", + }, + "write-duration-valid": { + backendConfig: &audit.BackendConfig{ + MountPath: "discard", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "address": "hashicorp.com", + "write_timeout": "5s", + }, + }, + isErrorExpected: false, + }, + "write-duration-not-valid": { + backendConfig: &audit.BackendConfig{ + MountPath: "discard", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "address": "hashicorp.com", + "write_timeout": "qwerty", + }, + }, + isErrorExpected: true, + expectedErrorMessage: "socket.Factory: failed to parse 'write_timeout': time: invalid duration \"qwerty\"", + }, + "non-fallback-device-with-filter": { + backendConfig: &audit.BackendConfig{ + MountPath: "discard", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "address": "hashicorp.com", + "write_timeout": "5s", + "fallback": "false", + "filter": "mount_type == kv", + }, + }, + isErrorExpected: false, + }, + "fallback-device-with-filter": { + backendConfig: &audit.BackendConfig{ + MountPath: "discard", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "address": "hashicorp.com", + "write_timeout": "2s", + "fallback": "true", + "filter": "mount_type == kv", + }, + }, + isErrorExpected: true, + expectedErrorMessage: "socket.Factory: cannot configure a fallback device with a filter: invalid parameter", + }, + } + + for name, tc := range tests { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + be, err := Factory(ctx, tc.backendConfig, true, nil) + + switch { + case tc.isErrorExpected: + require.Error(t, err) + require.EqualError(t, err, tc.expectedErrorMessage) + default: + require.NoError(t, err) + require.NotNil(t, be) + } + }) + } +} + +// TestBackend_IsFallback ensures that the 'fallback' config setting is parsed +// and set correctly, then exposed via the interface method IsFallback(). +func TestBackend_IsFallback(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + tests := map[string]struct { + backendConfig *audit.BackendConfig + isFallbackExpected bool + }{ + "fallback": { + backendConfig: &audit.BackendConfig{ + MountPath: "qwerty", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "fallback": "true", + "address": "hashicorp.com", + "write_timeout": "5s", + }, + }, + isFallbackExpected: true, + }, + "no-fallback": { + backendConfig: &audit.BackendConfig{ + MountPath: "qwerty", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "fallback": "false", + "address": "hashicorp.com", + "write_timeout": "5s", + }, + }, + isFallbackExpected: false, + }, + } + + for name, tc := range tests { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + be, err := Factory(ctx, tc.backendConfig, true, nil) + require.NoError(t, err) + require.NotNil(t, be) + require.Equal(t, tc.isFallbackExpected, be.IsFallback()) + }) + } +} diff --git a/builtin/audit/syslog/backend.go b/builtin/audit/syslog/backend.go index 45d6e0762d..dff6b58003 100644 --- a/builtin/audit/syslog/backend.go +++ b/builtin/audit/syslog/backend.go @@ -12,6 +12,7 @@ import ( "sync" "github.com/hashicorp/eventlogger" + "github.com/hashicorp/go-secure-stdlib/parseutil" gsyslog "github.com/hashicorp/go-syslog" "github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/internal/observability/event" @@ -23,6 +24,7 @@ var _ audit.Backend = (*Backend)(nil) // Backend is the audit backend for the syslog-based audit store. type Backend struct { + fallback bool formatter *audit.EntryFormatterWriter formatConfig audit.FormatterConfig logger gsyslog.Syslogger @@ -58,6 +60,21 @@ func Factory(_ context.Context, conf *audit.BackendConfig, useEventLogger bool, tag = "vault" } + // The config options 'fallback' and 'filter' are mutually exclusive, a fallback + // device catches everything, so it cannot be allowed to filter. + var fallback bool + var err error + if fallbackRaw, ok := conf.Config["fallback"]; ok { + fallback, err = parseutil.ParseBool(fallbackRaw) + if err != nil { + return nil, fmt.Errorf("%s: unable to parse 'fallback': %w", op, err) + } + } + + if _, ok := conf.Config["filter"]; ok && fallback { + return nil, fmt.Errorf("%s: cannot configure a fallback device with a filter: %w", op, event.ErrInvalidParameter) + } + cfg, err := formatterConfig(conf.Config) if err != nil { return nil, fmt.Errorf("%s: failed to create formatter config: %w", op, err) @@ -70,6 +87,7 @@ func Factory(_ context.Context, conf *audit.BackendConfig, useEventLogger bool, } b := &Backend{ + fallback: fallback, formatConfig: cfg, logger: logger, name: conf.MountPath, @@ -347,3 +365,10 @@ func (b *Backend) EventType() eventlogger.EventType { func (b *Backend) HasFiltering() bool { return len(b.nodeIDList) > 0 && b.nodeMap[b.nodeIDList[0]].Type() == eventlogger.NodeTypeFilter } + +// IsFallback can be used to determine if this audit backend device is intended to +// be used as a fallback to catch all events that are not written when only using +// filtered pipelines. +func (b *Backend) IsFallback() bool { + return b.fallback +} diff --git a/builtin/audit/syslog/backend_test.go b/builtin/audit/syslog/backend_test.go index 4aeaa5d0da..ba0b990803 100644 --- a/builtin/audit/syslog/backend_test.go +++ b/builtin/audit/syslog/backend_test.go @@ -4,10 +4,13 @@ package syslog import ( + "context" "testing" "github.com/hashicorp/eventlogger" "github.com/hashicorp/vault/audit" + "github.com/hashicorp/vault/sdk/helper/salt" + "github.com/hashicorp/vault/sdk/logical" "github.com/stretchr/testify/require" ) @@ -311,3 +314,125 @@ func TestBackend_configureFilterFormatterSink(t *testing.T) { node = b.nodeMap[id] require.Equal(t, eventlogger.NodeTypeSink, node.Type()) } + +// TestBackend_Factory_Conf is used to ensure that any configuration which is +// supplied, is validated and tested. +func TestBackend_Factory_Conf(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + tests := map[string]struct { + backendConfig *audit.BackendConfig + isErrorExpected bool + expectedErrorMessage string + }{ + "nil-salt-config": { + backendConfig: &audit.BackendConfig{ + SaltConfig: nil, + }, + isErrorExpected: true, + expectedErrorMessage: "syslog.Factory: nil salt config", + }, + "nil-salt-view": { + backendConfig: &audit.BackendConfig{ + SaltConfig: &salt.Config{}, + }, + isErrorExpected: true, + expectedErrorMessage: "syslog.Factory: nil salt view", + }, + "non-fallback-device-with-filter": { + backendConfig: &audit.BackendConfig{ + MountPath: "discard", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "fallback": "false", + "filter": "mount_type == kv", + }, + }, + isErrorExpected: false, + }, + "fallback-device-with-filter": { + backendConfig: &audit.BackendConfig{ + MountPath: "discard", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "fallback": "true", + "filter": "mount_type == kv", + }, + }, + isErrorExpected: true, + expectedErrorMessage: "syslog.Factory: cannot configure a fallback device with a filter: invalid parameter", + }, + } + + for name, tc := range tests { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + be, err := Factory(ctx, tc.backendConfig, true, nil) + + switch { + case tc.isErrorExpected: + require.Error(t, err) + require.EqualError(t, err, tc.expectedErrorMessage) + default: + require.NoError(t, err) + require.NotNil(t, be) + } + }) + } +} + +// TestBackend_IsFallback ensures that the 'fallback' config setting is parsed +// and set correctly, then exposed via the interface method IsFallback(). +func TestBackend_IsFallback(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + tests := map[string]struct { + backendConfig *audit.BackendConfig + isFallbackExpected bool + }{ + "fallback": { + backendConfig: &audit.BackendConfig{ + MountPath: "qwerty", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "fallback": "true", + }, + }, + isFallbackExpected: true, + }, + "no-fallback": { + backendConfig: &audit.BackendConfig{ + MountPath: "qwerty", + SaltConfig: &salt.Config{}, + SaltView: &logical.InmemStorage{}, + Config: map[string]string{ + "fallback": "false", + }, + }, + isFallbackExpected: false, + }, + } + + for name, tc := range tests { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + be, err := Factory(ctx, tc.backendConfig, true, nil) + require.NoError(t, err) + require.NotNil(t, be) + require.Equal(t, tc.isFallbackExpected, be.IsFallback()) + }) + } +} diff --git a/helper/testhelpers/corehelpers/corehelpers.go b/helper/testhelpers/corehelpers/corehelpers.go index c2d6bc8a3c..8c3d7cfb96 100644 --- a/helper/testhelpers/corehelpers/corehelpers.go +++ b/helper/testhelpers/corehelpers/corehelpers.go @@ -535,7 +535,7 @@ func (n *NoopAudit) Invalidate(_ context.Context) { // the audit.Backend interface. func (n *NoopAudit) RegisterNodesAndPipeline(broker *eventlogger.Broker, name string) error { for id, node := range n.nodeMap { - if err := broker.RegisterNode(id, node, eventlogger.WithNodeRegistrationPolicy(eventlogger.DenyOverwrite)); err != nil { + if err := broker.RegisterNode(id, node); err != nil { return err } } @@ -546,7 +546,7 @@ func (n *NoopAudit) RegisterNodesAndPipeline(broker *eventlogger.Broker, name st NodeIDs: n.nodeIDList, } - return broker.RegisterPipeline(pipeline, eventlogger.WithPipelineRegistrationPolicy(eventlogger.DenyOverwrite)) + return broker.RegisterPipeline(pipeline) } type TestLogger struct { @@ -632,3 +632,7 @@ func (n *NoopAudit) Nodes() map[eventlogger.NodeID]eventlogger.Node { func (n *NoopAudit) NodeIDs() []eventlogger.NodeID { return n.nodeIDList } + +func (n *NoopAudit) IsFallback() bool { + return false +} diff --git a/vault/audit.go b/vault/audit.go index a3d6fcfab9..7f2e5cac47 100644 --- a/vault/audit.go +++ b/vault/audit.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "os" + "strconv" "strings" "time" @@ -81,6 +82,17 @@ func (c *Core) enableAudit(ctx context.Context, entry *MountEntry, updateStorage return fmt.Errorf("backend path must be specified") } + if fallbackRaw, ok := entry.Options["fallback"]; ok { + fallback, err := parseutil.ParseBool(fallbackRaw) + if err != nil { + return fmt.Errorf("unable to enable audit device '%s', cannot parse supplied 'fallback' setting: %w", entry.Path, err) + } + + // Reassigning the fallback value means we can ensure that the formatting + // of it as a string is consistent for future comparisons. + entry.Options["fallback"] = strconv.FormatBool(fallback) + } + // Update the audit table c.auditLock.Lock() defer c.auditLock.Unlock() @@ -88,6 +100,8 @@ func (c *Core) enableAudit(ctx context.Context, entry *MountEntry, updateStorage // Look for matching name for _, ent := range c.audit.Entries { switch { + case entry.Options["fallback"] == "true" && ent.Options["fallback"] == "true": + return fmt.Errorf("unable to enable audit device '%s', a fallback device already exists '%s'", entry.Path, ent.Path) // Existing is sql/mysql/ new is sql/ or // existing is sql/ and new is sql/mysql/ case strings.HasPrefix(ent.Path, entry.Path): @@ -531,7 +545,7 @@ func (c *Core) newAuditBackend(ctx context.Context, entry *MountEntry, view logi !disableEventLogger, c.auditedHeaders) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to create new audit backend: %w", err) } if be == nil { return nil, fmt.Errorf("nil backend returned from %q factory function", entry.Type) diff --git a/vault/audit_broker.go b/vault/audit_broker.go index 514c3b5a77..fbfa0d2769 100644 --- a/vault/audit_broker.go +++ b/vault/audit_broker.go @@ -12,9 +12,9 @@ import ( "sync" "time" - "github.com/armon/go-metrics" "github.com/hashicorp/eventlogger" "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-metrics" "github.com/hashicorp/go-multierror" "github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/helper/namespace" @@ -32,32 +32,52 @@ type backendEntry struct { type AuditBroker struct { sync.RWMutex backends map[string]backendEntry - logger hclog.Logger + // broker is used to register pipelines for all devices except a fallback device. broker *eventlogger.Broker + + // fallbackBroker is used to register a pipeline to be used as a fallback + // in situations where we cannot use the eventlogger.Broker to guarantee that + // the required number of sinks were successfully written to. This situation + // occurs when all the audit devices registered with the broker use filtering. + // NOTE: there should only ever be a single device registered on the fallbackBroker. + fallbackBroker *eventlogger.Broker + + // fallbackName stores the name (path) of the audit device which has been configured + // as the fallback pipeline (its eventlogger.PipelineID). + fallbackName string + logger hclog.Logger } // NewAuditBroker creates a new audit broker func NewAuditBroker(log hclog.Logger, useEventLogger bool) (*AuditBroker, error) { var eventBroker *eventlogger.Broker + var fallbackBroker *eventlogger.Broker var err error // The reason for this check is due to 1.15.x supporting the env var: // 'VAULT_AUDIT_DISABLE_EVENTLOGGER' // When NewAuditBroker is called, it is supplied a bool to determine whether - // we initialize the broker, which are left nil otherwise. + // we initialize the broker (and fallback broker), which are left nil otherwise. // In 1.16.x this check should go away and the env var removed. if useEventLogger { eventBroker, err = eventlogger.NewBroker() if err != nil { return nil, fmt.Errorf("error creating event broker for audit events: %w", err) } + + // Set up the broker that will support a single fallback device. + fallbackBroker, err = eventlogger.NewBroker() + if err != nil { + return nil, fmt.Errorf("error creating event fallback broker for audit event: %w", err) + } } b := &AuditBroker{ - backends: make(map[string]backendEntry), - logger: log, - broker: eventBroker, + backends: make(map[string]backendEntry), + logger: log, + broker: eventBroker, + fallbackBroker: fallbackBroker, } return b, nil } @@ -74,19 +94,42 @@ func (a *AuditBroker) Register(name string, b audit.Backend, local bool) error { return fmt.Errorf("%s: name is required: %w", op, event.ErrInvalidParameter) } + // If the backend is already registered, we cannot re-register it. + if a.isRegistered(name) { + return fmt.Errorf("%s: backend already registered '%s'", op, name) + } + + // Fallback devices are singleton instances, we cannot register more than one or overwrite the existing one. + if b.IsFallback() && a.fallbackBroker.IsAnyPipelineRegistered(eventlogger.EventType(event.AuditType.String())) { + existing, err := a.existingFallbackName() + if err != nil { + return fmt.Errorf("%s: existing fallback device already registered: %w", op, err) + } + + return fmt.Errorf("%s: existing fallback device already registered: %q", op, existing) + } + // The reason for this check is due to 1.15.x supporting the env var: // 'VAULT_AUDIT_DISABLE_EVENTLOGGER' // When NewAuditBroker is called, it is supplied a bool to determine whether - // we initialize the broker, which are left nil otherwise. + // we initialize the broker (and fallback broker), which are left nil otherwise. // In 1.16.x this check should go away and the env var removed. if a.broker != nil { if name != b.Name() { return fmt.Errorf("%s: audit registration failed due to device name mismatch: %q, %q", op, name, b.Name()) } - err := a.register(name, b) - if err != nil { - return fmt.Errorf("%s: unable to register device for %q: %w", op, name, err) + switch { + case b.IsFallback(): + err := a.registerFallback(name, b) + if err != nil { + return fmt.Errorf("%s: unable to register fallback device for %q: %w", op, name, err) + } + default: + err := a.register(name, b) + if err != nil { + return fmt.Errorf("%s: unable to register device for %q: %w", op, name, err) + } } } @@ -110,6 +153,12 @@ func (a *AuditBroker) Deregister(ctx context.Context, name string) error { return fmt.Errorf("%s: name is required: %w", op, event.ErrInvalidParameter) } + // If the backend isn't actually registered, then there's nothing to do. + // We don't return any error so that Deregister can be idempotent. + if !a.isRegistered(name) { + return nil + } + // Remove the Backend from the map first, so that if an error occurs while // removing the pipeline and nodes, we can quickly exit this method with // the error. @@ -118,23 +167,37 @@ func (a *AuditBroker) Deregister(ctx context.Context, name string) error { // The reason for this check is due to 1.15.x supporting the env var: // 'VAULT_AUDIT_DISABLE_EVENTLOGGER' // When NewAuditBroker is called, it is supplied a bool to determine whether - // we initialize the broker, which are left nil otherwise. + // we initialize the broker (and fallback broker), which are left nil otherwise. // In 1.16.x this check should go away and the env var removed. if a.broker != nil { - err := a.deregister(ctx, name) - if err != nil { - return fmt.Errorf("%s: deregistration failed for audit device %q: %w", op, name, err) + switch { + case name == a.fallbackName: + err := a.deregisterFallback(ctx, name) + if err != nil { + return fmt.Errorf("%s: deregistration failed for fallback audit device %q: %w", op, name, err) + } + default: + err := a.deregister(ctx, name) + if err != nil { + return fmt.Errorf("%s: deregistration failed for audit device %q: %w", op, name, err) + } } } return nil } -// IsRegistered is used to check if a given audit backend is registered +// IsRegistered is used to check if a given audit backend is registered. func (a *AuditBroker) IsRegistered(name string) bool { a.RLock() defer a.RUnlock() + return a.isRegistered(name) +} + +// isRegistered is used to check if a given audit backend is registered. +// This method should be used within the AuditBroker to prevent locking issues. +func (a *AuditBroker) isRegistered(name string) bool { _, ok := a.backends[name] return ok } @@ -236,6 +299,9 @@ func (a *AuditBroker) LogRequest(ctx context.Context, in *logical.LogInput, head e.Data = in + // There may be cases where only the fallback device was added but no other + // normal audit devices, so check if the broker had an audit based pipeline + // registered before trying to send to it. var status eventlogger.Status if a.broker.IsAnyPipelineRegistered(eventlogger.EventType(event.AuditType.String())) { status, err = a.broker.Send(ctx, eventlogger.EventType(event.AuditType.String()), e) @@ -255,6 +321,15 @@ func (a *AuditBroker) LogRequest(ctx context.Context, in *logical.LogInput, head retErr = multierror.Append(retErr, multierror.Append(errors.New("error during audit pipeline processing"), status.Warnings...)) return retErr.ErrorOrNil() } + + // If a fallback device is registered we can rely on that to 'catch all' + // and also the broker level guarantee for completed sinks. + if a.fallbackBroker.IsAnyPipelineRegistered(eventlogger.EventType(event.AuditType.String())) { + status, err = a.fallbackBroker.Send(ctx, eventlogger.EventType(event.AuditType.String()), e) + if err != nil { + retErr = multierror.Append(retErr, multierror.Append(fmt.Errorf("auditing request to fallback device failed: %w", err), status.Warnings...)) + } + } } } @@ -349,6 +424,9 @@ func (a *AuditBroker) LogResponse(ctx context.Context, in *logical.LogInput, hea defer auditCancel() auditContext = namespace.ContextWithNamespace(auditContext, ns) + // There may be cases where only the fallback device was added but no other + // normal audit devices, so check if the broker had an audit based pipeline + // registered before trying to send to it. var status eventlogger.Status if a.broker.IsAnyPipelineRegistered(eventlogger.EventType(event.AuditType.String())) { status, err = a.broker.Send(auditContext, eventlogger.EventType(event.AuditType.String()), e) @@ -368,6 +446,15 @@ func (a *AuditBroker) LogResponse(ctx context.Context, in *logical.LogInput, hea retErr = multierror.Append(retErr, multierror.Append(errors.New("error during audit pipeline processing"), status.Warnings...)) return retErr.ErrorOrNil() } + + // If a fallback device is registered we can rely on that to 'catch all' + // and also the broker level guarantee for completed sinks. + if a.fallbackBroker.IsAnyPipelineRegistered(eventlogger.EventType(event.AuditType.String())) { + status, err = a.fallbackBroker.Send(auditContext, eventlogger.EventType(event.AuditType.String()), e) + if err != nil { + retErr = multierror.Append(retErr, multierror.Append(fmt.Errorf("auditing response to fallback device failed: %w", err), status.Warnings...)) + } + } } } @@ -391,13 +478,19 @@ func (a *AuditBroker) Invalidate(ctx context.Context, key string) { // guarantee provided by setting the threshold to 1, and must set it to 0. // If you are registering an audit device, you should first check if that backend // does not have filtering before querying the backends via requiredSuccessThresholdSinks. +// backends may also contain a fallback device, which should be ignored as it is +// handled by the fallbackBroker. func (a *AuditBroker) requiredSuccessThresholdSinks() int { threshold := 0 // We might need to check over all the existing backends to discover if any // don't use filtering. for _, be := range a.backends { - if !be.backend.HasFiltering() { + switch { + case be.backend.IsFallback(): + // Ignore fallback devices as they're handled by a separate broker. + continue + case !be.backend.HasFiltering(): threshold = 1 break } @@ -432,6 +525,65 @@ func registerNodesAndPipeline(broker *eventlogger.Broker, b audit.Backend) error return nil } +// existingFallbackName returns the name of the fallback device which is registered +// with the AuditBroker. +func (a *AuditBroker) existingFallbackName() (string, error) { + const op = "vault.(AuditBroker).existingFallbackName" + + for _, be := range a.backends { + if be.backend.IsFallback() { + return be.backend.Name(), nil + } + } + + return "", fmt.Errorf("%s: existing fallback device name is missing", op) +} + +// registerFallback can be used to register a fallback device, it will also +// configure the success threshold required for sinks. +func (a *AuditBroker) registerFallback(name string, backend audit.Backend) error { + const op = "vault.(AuditBroker).registerFallback" + + err := registerNodesAndPipeline(a.fallbackBroker, backend) + if err != nil { + return fmt.Errorf("%s: fallback device pipeline registration error: %w", op, err) + } + + // Store the name of the fallback audit device so that we can check when + // deregistering if the device is the single fallback one. + a.fallbackName = backend.Name() + + // We need to turn on the threshold for the fallback broker, so we can + // guarantee it ends up somewhere + err = a.fallbackBroker.SetSuccessThresholdSinks(eventlogger.EventType(event.AuditType.String()), 1) + if err != nil { + return fmt.Errorf("%s: unable to configure fallback sink success threshold (1) for %q: %w", op, name, err) + } + + return nil +} + +// deregisterFallback can be used to deregister a fallback audit device, it will +// also configure the success threshold required for sinks. +func (a *AuditBroker) deregisterFallback(ctx context.Context, name string) error { + const op = "vault.(AuditBroker).deregisterFallback" + + err := a.fallbackBroker.SetSuccessThresholdSinks(eventlogger.EventType(event.AuditType.String()), 0) + if err != nil { + return fmt.Errorf("%s: unable to configure fallback sink success threshold (0) for %q: %w", op, name, err) + } + + _, err = a.fallbackBroker.RemovePipelineAndNodes(ctx, eventlogger.EventType(event.AuditType.String()), eventlogger.PipelineID(name)) + if err != nil { + return fmt.Errorf("%s: unable to deregister fallback device %q: %w", op, name, err) + } + + // Clear the fallback device name now we've deregistered. + a.fallbackName = "" + + return nil +} + // register can be used to register a normal audit device, it will also calculate // and configure the success threshold required for sinks. func (a *AuditBroker) register(name string, backend audit.Backend) error { diff --git a/vault/audit_broker_test.go b/vault/audit_broker_test.go index a7fa891fcf..18efaa5601 100644 --- a/vault/audit_broker_test.go +++ b/vault/audit_broker_test.go @@ -141,3 +141,118 @@ func TestAuditBroker_Deregister_SuccessThresholdSinks(t *testing.T) { require.True(t, ok) require.Equal(t, 1, res) } + +// TestAuditBroker_Register_Fallback ensures we can register a fallback device. +func TestAuditBroker_Register_Fallback(t *testing.T) { + t.Parallel() + + l := corehelpers.NewTestLogger(t) + a, err := NewAuditBroker(l, true) + require.NoError(t, err) + require.NotNil(t, a) + + path := "juan/" + fallbackBackend := testAuditBackend(t, path, map[string]string{"fallback": "true"}) + err = a.Register(path, fallbackBackend, false) + require.NoError(t, err) + require.True(t, a.fallbackBroker.IsAnyPipelineRegistered(eventlogger.EventType(event.AuditType.String()))) + require.Equal(t, path, a.fallbackName) + threshold, found := a.fallbackBroker.SuccessThresholdSinks(eventlogger.EventType(event.AuditType.String())) + require.True(t, found) + require.Equal(t, 1, threshold) +} + +// TestAuditBroker_Register_FallbackMultiple tests that trying to register more +// than a single fallback device results in the correct error. +func TestAuditBroker_Register_FallbackMultiple(t *testing.T) { + t.Parallel() + + l := corehelpers.NewTestLogger(t) + a, err := NewAuditBroker(l, true) + require.NoError(t, err) + require.NotNil(t, a) + + path1 := "juan1/" + fallbackBackend1 := testAuditBackend(t, path1, map[string]string{"fallback": "true"}) + err = a.Register(path1, fallbackBackend1, false) + require.NoError(t, err) + require.True(t, a.fallbackBroker.IsAnyPipelineRegistered(eventlogger.EventType(event.AuditType.String()))) + require.Equal(t, path1, a.fallbackName) + + path2 := "juan2/" + fallbackBackend2 := testAuditBackend(t, path2, map[string]string{"fallback": "true"}) + err = a.Register(path1, fallbackBackend2, false) + require.Error(t, err) + require.EqualError(t, err, "vault.(AuditBroker).Register: backend already registered 'juan1/'") + require.True(t, a.fallbackBroker.IsAnyPipelineRegistered(eventlogger.EventType(event.AuditType.String()))) + require.Equal(t, path1, a.fallbackName) +} + +// TestAuditBroker_Deregister_Fallback ensures that we can deregister a fallback +// device successfully. +func TestAuditBroker_Deregister_Fallback(t *testing.T) { + t.Parallel() + + l := corehelpers.NewTestLogger(t) + a, err := NewAuditBroker(l, true) + require.NoError(t, err) + require.NotNil(t, a) + + path := "juan/" + fallbackBackend := testAuditBackend(t, path, map[string]string{"fallback": "true"}) + err = a.Register(path, fallbackBackend, false) + require.NoError(t, err) + require.True(t, a.fallbackBroker.IsAnyPipelineRegistered(eventlogger.EventType(event.AuditType.String()))) + require.Equal(t, path, a.fallbackName) + + threshold, found := a.fallbackBroker.SuccessThresholdSinks(eventlogger.EventType(event.AuditType.String())) + require.True(t, found) + require.Equal(t, 1, threshold) + + err = a.Deregister(context.Background(), path) + require.NoError(t, err) + require.False(t, a.fallbackBroker.IsAnyPipelineRegistered(eventlogger.EventType(event.AuditType.String()))) + require.Equal(t, "", a.fallbackName) + + threshold, found = a.fallbackBroker.SuccessThresholdSinks(eventlogger.EventType(event.AuditType.String())) + require.True(t, found) + require.Equal(t, 0, threshold) +} + +// TestAuditBroker_Deregister_Multiple ensures that we can call deregister multiple +// times without issue if is no matching backend registered. +func TestAuditBroker_Deregister_Multiple(t *testing.T) { + t.Parallel() + + l := corehelpers.NewTestLogger(t) + a, err := NewAuditBroker(l, true) + require.NoError(t, err) + require.NotNil(t, a) + + err = a.Deregister(context.Background(), "foo") + require.NoError(t, err) + + err = a.Deregister(context.Background(), "foo2") + require.NoError(t, err) +} + +// TestAuditBroker_Register_MultipleFails checks for failure when we try to +// re-register an audit backend. +func TestAuditBroker_Register_MultipleFails(t *testing.T) { + t.Parallel() + + l := corehelpers.NewTestLogger(t) + a, err := NewAuditBroker(l, true) + require.NoError(t, err) + require.NotNil(t, a) + + path := "b2-no-filter" + noFilterBackend := testAuditBackend(t, path, map[string]string{}) + + err = a.Register(path, noFilterBackend, false) + require.NoError(t, err) + + err = a.Register(path, noFilterBackend, false) + require.Error(t, err) + require.EqualError(t, err, "vault.(AuditBroker).Register: backend already registered 'b2-no-filter'") +} diff --git a/vault/audit_test.go b/vault/audit_test.go index afecafaea2..87f06f4a57 100644 --- a/vault/audit_test.go +++ b/vault/audit_test.go @@ -237,6 +237,69 @@ func TestCore_EnableAudit_Local(t *testing.T) { } } +// TestAudit_enableAudit_fallback_invalid ensures that supplying a bad value for +// 'fallback' in options gives us the correct error. +func TestAudit_enableAudit_fallback_invalid(t *testing.T) { + entry := &MountEntry{ + Path: "noop/", + Options: map[string]string{ + "fallback": "juan", + }, + } + + cluster := NewTestCluster(t, nil, nil) + cluster.Start() + defer cluster.Cleanup() + core := cluster.Cores[0] + core.auditBackends["noop"] = corehelpers.NoopAuditFactory(nil) + err := core.enableAudit(context.Background(), entry, false) + require.Error(t, err) + require.EqualError(t, err, "unable to enable audit device 'noop/', cannot parse supplied 'fallback' setting: cannot parse '' as bool: strconv.ParseBool: parsing \"juan\": invalid syntax") +} + +// TestAudit_enableAudit_fallback_two ensures trying to enable a second fallback +// device returns the correct error. +func TestAudit_enableAudit_fallback_two(t *testing.T) { + entry1 := &MountEntry{ + Table: auditTableType, + Path: "noop1/", + Type: "noop", + UUID: "abcd", + Accessor: "noop1-abcd", + NamespaceID: namespace.RootNamespaceID, + Options: map[string]string{ + "fallback": "TRUE", + }, + namespace: namespace.RootNamespace, + } + + entry2 := &MountEntry{ + Table: auditTableType, + Path: "noop2/", + Type: "noop", + UUID: "abcd", + Accessor: "noop2-abcd", + NamespaceID: namespace.RootNamespaceID, + Options: map[string]string{ + "fallback": "1", + }, + namespace: namespace.RootNamespace, + } + + cluster := NewTestCluster(t, nil, nil) + cluster.Start() + defer cluster.Cleanup() + core := cluster.Cores[0] + core.auditBackends["noop"] = corehelpers.NoopAuditFactory(nil) + ctx := namespace.ContextWithNamespace(context.Background(), namespace.RootNamespace) + err := core.enableAudit(ctx, entry1, false) + require.NoError(t, err) + + err = core.enableAudit(ctx, entry2, false) + require.Error(t, err) + require.EqualError(t, err, "unable to enable audit device 'noop2/', a fallback device already exists 'noop1/'") +} + func TestCore_DisableAudit(t *testing.T) { c, keys, _ := TestCoreUnsealed(t) c.auditBackends["noop"] = corehelpers.NoopAuditFactory(nil) From d13edc610706724ab6337ea45617a8df49e8a012 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 11:15:57 -0500 Subject: [PATCH 08/14] Bump golang.org/x/crypto from 0.14.0 to 0.17.0 in /sdk (#24576) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.17.0. - [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Violet Hynes --- sdk/go.mod | 8 ++++---- sdk/go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/sdk/go.mod b/sdk/go.mod index 5bd6a0a98a..862175f672 100644 --- a/sdk/go.mod +++ b/sdk/go.mod @@ -46,9 +46,9 @@ require ( github.com/ryanuber/go-glob v1.0.0 github.com/stretchr/testify v1.8.3 go.uber.org/atomic v1.9.0 - golang.org/x/crypto v0.14.0 + golang.org/x/crypto v0.17.0 golang.org/x/net v0.17.0 - golang.org/x/text v0.13.0 + golang.org/x/text v0.14.0 google.golang.org/grpc v1.57.2 google.golang.org/protobuf v1.31.0 ) @@ -104,8 +104,8 @@ require ( go.opencensus.io v0.24.0 // indirect golang.org/x/mod v0.9.0 // indirect golang.org/x/oauth2 v0.11.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.7.0 // indirect google.golang.org/api v0.134.0 // indirect diff --git a/sdk/go.sum b/sdk/go.sum index b71ecfa543..74c09ebd6e 100644 --- a/sdk/go.sum +++ b/sdk/go.sum @@ -543,8 +543,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -683,14 +683,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -702,8 +702,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 49ee9e72a74a6731388d8403f5bfbad021e24370 Mon Sep 17 00:00:00 2001 From: Matthew Irish <39469+meirish@users.noreply.github.com> Date: Mon, 8 Jan 2024 11:17:11 -0600 Subject: [PATCH 09/14] ui: add ember exam (#24669) * feature(ember-exam): add ember-exam to split ui tests and run them in parallel * update yarn.lock * update package.json scripts * test(oidc): comment out test with race condition in oidc test * chore(test): use 127.0.0.1 in testem config and add uuid to pki mount path * test(kv-workflow): make policy creation unique per-test * chore(test): use --preserve-test-name so flakey test reporting is preserved * yarn test:filter runs ember test instead of exam for easier filtering * fix kv control group tests --------- Co-authored-by: Chelsea Shaw --- ui/package.json | 8 +- ui/scripts/start-vault.js | 4 +- ui/testem.js | 3 +- ui/tests/acceptance/console-test.js | 25 +- .../oidc-config/clients-assignments-test.js | 17 +- ui/tests/acceptance/pki/pki-overview-test.js | 4 +- .../backend/kv/kv-v2-workflow-create-test.js | 29 +- .../backend/kv/kv-v2-workflow-delete-test.js | 23 +- .../kv/kv-v2-workflow-edge-cases-test.js | 8 +- .../kv/kv-v2-workflow-navigation-test.js | 21 +- ...-v2-workflow-version-history-paths-test.js | 20 +- ui/tests/acceptance/unseal-test.js | 2 +- ui/tests/helpers/control-groups.js | 50 +- .../components/info-table-row-test.js | 6 +- ui/tests/test-helper.js | 2 +- ui/yarn.lock | 571 +++++++++++++++++- 16 files changed, 684 insertions(+), 109 deletions(-) diff --git a/ui/package.json b/ui/package.json index be524e0817..5965b81085 100644 --- a/ui/package.json +++ b/ui/package.json @@ -30,9 +30,10 @@ "start:mirage": "start () { MIRAGE_DEV_HANDLER=$1 yarn run start; }; start", "test": "npm-run-all --print-name lint:js:quiet lint:hbs:quiet && node scripts/start-vault.js", "test:enos": "npm-run-all lint:js:quiet lint:hbs:quiet && node scripts/enos-test-ember.js", - "test:oss": "yarn run test -f='!enterprise'", - "test:quick": "node scripts/start-vault.js", - "test:quick-oss": "yarn test:quick -f='!enterprise'", + "test:oss": "yarn run test -f='!enterprise' --split=8 --preserve-test-name --parallel", + "test:quick": "node scripts/start-vault.js --split=8 --preserve-test-name --parallel", + "test:quick-oss": "yarn test:quick -f='!enterprise' --split=8 --preserve-test-name --parallel", + "test:filter": "node scripts/start-vault.js --server -f='!enterprise'", "types:declare": "declare () { yarn tsc $1 --declaration --allowJs --emitDeclarationOnly --experimentalDecorators --outDir $2; }; declare", "vault": "VAULT_REDIRECT_ADDR=http://127.0.0.1:8200 vault server -log-level=error -dev -dev-root-token-id=root -dev-ha -dev-transactional", "vault:cluster": "VAULT_REDIRECT_ADDR=http://127.0.0.1:8202 vault server -log-level=error -dev -dev-root-token-id=root -dev-listen-address=127.0.0.1:8202 -dev-ha -dev-transactional" @@ -145,6 +146,7 @@ "ember-d3": "^0.5.1", "ember-data": "~4.11.3", "ember-engines": "0.8.23", + "ember-exam": "^9.0.0", "ember-fetch": "^8.1.2", "ember-inflector": "4.0.2", "ember-load-initializers": "^2.1.2", diff --git a/ui/scripts/start-vault.js b/ui/scripts/start-vault.js index 44c1cae542..8ea4398da8 100755 --- a/ui/scripts/start-vault.js +++ b/ui/scripts/start-vault.js @@ -69,7 +69,9 @@ async function processLines(input, eachLine = () => {}) { } }); try { - await testHelper.run('ember', ['test', ...process.argv.slice(2)]); + // only the test:filter command specifies --server by default + const verb = process.argv[2] === '--server' ? 'test' : 'exam'; + await testHelper.run('ember', [verb, ...process.argv.slice(2)]); } catch (error) { console.log(error); process.exit(1); diff --git a/ui/testem.js b/ui/testem.js index 3888c70fc4..66c8bdbbb0 100644 --- a/ui/testem.js +++ b/ui/testem.js @@ -28,9 +28,10 @@ module.exports = { }, proxies: { '/v1': { - target: 'http://localhost:9200', + target: 'http://127.0.0.1:9200', }, }, + parallel: process.env.EMBER_EXAM_SPLIT_COUNT || 1, }; if (process.env.CI) { diff --git a/ui/tests/acceptance/console-test.js b/ui/tests/acceptance/console-test.js index 409a70a49e..527605da81 100644 --- a/ui/tests/acceptance/console-test.js +++ b/ui/tests/acceptance/console-test.js @@ -10,6 +10,7 @@ import { setupApplicationTest } from 'ember-qunit'; import enginesPage from 'vault/tests/pages/secrets/backends'; import authPage from 'vault/tests/pages/auth'; import consoleClass from 'vault/tests/pages/components/console/ui-panel'; +import { v4 as uuidv4 } from 'uuid'; const consoleComponent = create(consoleClass); @@ -21,28 +22,38 @@ module('Acceptance | console', function (hooks) { }); test("refresh reloads the current route's data", async function (assert) { + assert.expect(6); await enginesPage.visit(); await settled(); - const numEngines = enginesPage.rows.length; await consoleComponent.toggle(); await settled(); - for (const num of [1, 2, 3]) { - const inputString = `write sys/mounts/console-route-${num} type=kv`; + const ids = [uuidv4(), uuidv4(), uuidv4()]; + for (const id of ids) { + const inputString = `write sys/mounts/console-route-${id} type=kv`; await consoleComponent.runCommands(inputString); await settled(); } await consoleComponent.runCommands('refresh'); await settled(); - assert.strictEqual(enginesPage.rows.length, numEngines + 3, 'new engines were added to the page'); + for (const id of ids) { + assert.ok( + enginesPage.rows.findOneBy('path', `console-route-${id}/`), + 'new engine is shown on the page' + ); + } // Clean up - for (const num of [1, 2, 3]) { - const inputString = `delete sys/mounts/console-route-${num}`; + for (const id of ids) { + const inputString = `delete sys/mounts/console-route-${id}`; await consoleComponent.runCommands(inputString); await settled(); } await consoleComponent.runCommands('refresh'); await settled(); - assert.strictEqual(enginesPage.rows.length, numEngines, 'engines were removed from the page'); + for (const id of ids) { + assert.throws(() => { + enginesPage.rows.findOneBy('path', `console-route-${id}/`); + }, 'engine was removed'); + } }); test('fullscreen command expands the cli panel', async function (assert) { diff --git a/ui/tests/acceptance/oidc-config/clients-assignments-test.js b/ui/tests/acceptance/oidc-config/clients-assignments-test.js index c3b34996da..c5fa9d2858 100644 --- a/ui/tests/acceptance/oidc-config/clients-assignments-test.js +++ b/ui/tests/acceptance/oidc-config/clients-assignments-test.js @@ -75,7 +75,7 @@ module('Acceptance | oidc-config clients and assignments', function (hooks) { }); test('it creates an assignment inline, creates a client, updates client to limit access, deletes client', async function (assert) { - assert.expect(22); + assert.expect(21); //* clear out test state await clearRecord(this.store, 'oidc/client', 'test-app'); @@ -197,11 +197,16 @@ module('Acceptance | oidc-config clients and assignments', function (hooks) { assert.strictEqual(currentRouteName(), 'vault.cluster.access.oidc.clients.client.details'); await click(SELECTORS.clientDeleteButton); await click(SELECTORS.confirmActionButton); - assert.strictEqual( - currentRouteName(), - 'vault.cluster.access.oidc.index', - 'redirects to call to action if only existing client is deleted' - ); + + //TODO this part of the test has a race condition + //because other tests could have created clients - there is no guarantee that this will be the last + //client in the list to redirect to the call to action + //assert.strictEqual( + //currentRouteName(), + //'vault.cluster.access.oidc.index', + //'redirects to call to action if only existing client is deleted' + //); + //* clean up test state await clearRecord(this.store, 'oidc/assignment', 'assignment-inline'); }); diff --git a/ui/tests/acceptance/pki/pki-overview-test.js b/ui/tests/acceptance/pki/pki-overview-test.js index 05a09dd87d..355216a531 100644 --- a/ui/tests/acceptance/pki/pki-overview-test.js +++ b/ui/tests/acceptance/pki/pki-overview-test.js @@ -5,6 +5,8 @@ import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; +import { v4 as uuidv4 } from 'uuid'; + import authPage from 'vault/tests/pages/auth'; import logout from 'vault/tests/pages/logout'; import enablePage from 'vault/tests/pages/settings/mount-secret-backend'; @@ -19,7 +21,7 @@ module('Acceptance | pki overview', function (hooks) { this.store = this.owner.lookup('service:store'); await authPage.login(); // Setup PKI engine - const mountPath = `pki`; + const mountPath = `pki-${uuidv4()}`; await enablePage.enable('pki', mountPath); this.mountPath = mountPath; await runCommands([`write ${this.mountPath}/root/generate/internal common_name="Hashicorp Test"`]); diff --git a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-create-test.js b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-create-test.js index 49567b1f50..fc121a888c 100644 --- a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-create-test.js +++ b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-create-test.js @@ -38,7 +38,7 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook module('admin persona', function (hooks) { hooks.beforeEach(async function () { - const token = await runCmd(tokenWithPolicyCmd('admin', personas.admin(this.backend))); + const token = await runCmd(tokenWithPolicyCmd(`admin-${this.backend}`, personas.admin(this.backend))); await authPage.login(token); clearRecords(this.store); return; @@ -282,7 +282,9 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook module('data-reader persona', function (hooks) { hooks.beforeEach(async function () { - const token = await runCmd(tokenWithPolicyCmd('data-reader', personas.dataReader(this.backend))); + const token = await runCmd( + tokenWithPolicyCmd(`data-reader-${this.backend}`, personas.dataReader(this.backend)) + ); await authPage.login(token); clearRecords(this.store); return; @@ -423,7 +425,7 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook module('data-list-reader persona', function (hooks) { hooks.beforeEach(async function () { const token = await runCmd( - tokenWithPolicyCmd('data-list-reader', personas.dataListReader(this.backend)) + tokenWithPolicyCmd(`data-list-reader-${this.backend}`, personas.dataListReader(this.backend)) ); await authPage.login(token); clearRecords(this.store); @@ -568,7 +570,7 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook module('metadata-maintainer persona', function (hooks) { hooks.beforeEach(async function () { const token = await runCmd( - tokenWithPolicyCmd('data-list-reader', personas.metadataMaintainer(this.backend)) + tokenWithPolicyCmd(`data-list-reader-${this.backend}`, personas.metadataMaintainer(this.backend)) ); await authPage.login(token); clearRecords(this.store); @@ -764,7 +766,9 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook module('secret-creator persona', function (hooks) { hooks.beforeEach(async function () { - const token = await runCmd(tokenWithPolicyCmd('secret-creator', personas.secretCreator(this.backend))); + const token = await runCmd( + tokenWithPolicyCmd(`secret-creator-${this.backend}`, personas.secretCreator(this.backend)) + ); await authPage.login(token); clearRecords(this.store); return; @@ -1006,7 +1010,10 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook module('secret-nested-creator persona', function (hooks) { hooks.beforeEach(async function () { const token = await runCmd( - tokenWithPolicyCmd('secret-nested-creator', personas.secretNestedCreator(this.backend)) + tokenWithPolicyCmd( + `secret-nested-creator-${this.backend}`, + personas.secretNestedCreator(this.backend) + ) ); await authPage.login(token); clearRecords(this.store); @@ -1048,7 +1055,11 @@ path "${this.backend}/metadata/*" { capabilities = ["list", "read"] } `; - const { userToken } = await setupControlGroup({ userPolicy }); + + const { userToken } = await setupControlGroup({ + userPolicy, + backend: this.backend, + }); this.userToken = userToken; await authPage.login(userToken); clearRecords(this.store); @@ -1089,11 +1100,12 @@ path "${this.backend}/metadata/*" { 'shows control group error' ); await grantAccessForWrite({ - accessor: tokenToUnwrap.accessor, token: tokenToUnwrap.token, + accessor: tokenToUnwrap.accessor, creation_path: `${backend}/data/${secretPath}`, originUrl: `/vault/secrets/${backend}/kv/create`, userToken: this.userToken, + backend: this.backend, }); // In a real scenario the user would stay on page, but in the test // we fill in the same info and try again @@ -1161,6 +1173,7 @@ path "${this.backend}/metadata/*" { creation_path: `${backend}/data/${secretPath}`, originUrl: `/vault/secrets/${backend}/kv/${secretPath}/details/edit`, userToken: this.userToken, + backend: this.backend, }); // Remark for unwrap as if we never left the page. this.controlGroup.markTokenForUnwrap(tokenToUnwrap.accessor); diff --git a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-delete-test.js b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-delete-test.js index c96e70f061..6a5f5be9c9 100644 --- a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-delete-test.js +++ b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-delete-test.js @@ -28,6 +28,11 @@ const assertDeleteActions = (assert, expected = ['delete', 'destroy']) => { } }); }; + +const makeToken = (name, mountPath, policyGenerator) => { + return tokenWithPolicyCmd(`${name}-${mountPath}`, policyGenerator(mountPath)); +}; + /** * This test set is for testing delete, undelete, destroy flows * Letter(s) in parenthesis at the end are shorthand for the persona, @@ -58,7 +63,7 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook module('admin persona', function (hooks) { hooks.beforeEach(async function () { - const token = await runCmd(tokenWithPolicyCmd('admin', personas.admin(this.backend))); + const token = await runCmd(makeToken('admin', this.backend, personas.admin)); await authPage.login(token); clearRecords(this.store); return; @@ -159,7 +164,7 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook module('data-reader persona', function (hooks) { hooks.beforeEach(async function () { - const token = await runCmd(tokenWithPolicyCmd('data-reader', personas.dataReader(this.backend))); + const token = await runCmd(makeToken('data-reader', this.backend, personas.dataReader)); await authPage.login(token); clearRecords(this.store); return; @@ -206,9 +211,7 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook module('data-list-reader persona', function (hooks) { hooks.beforeEach(async function () { - const token = await runCmd( - tokenWithPolicyCmd('data-list-reader', personas.dataListReader(this.backend)) - ); + const token = await runCmd(makeToken('data-list-reader', this.backend, personas.dataListReader)); await authPage.login(token); clearRecords(this.store); return; @@ -266,9 +269,7 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook module('metadata-maintainer persona', function (hooks) { hooks.beforeEach(async function () { - const token = await runCmd( - tokenWithPolicyCmd('metadata-maintainer', personas.metadataMaintainer(this.backend)) - ); + const token = await runCmd(makeToken('metadata-maintainer', this.backend, personas.metadataMaintainer)); await authPage.login(token); clearRecords(this.store); return; @@ -358,7 +359,7 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook module('secret-nested-creator persona', function (hooks) { hooks.beforeEach(async function () { const token = await runCmd( - tokenWithPolicyCmd('secret-nested-creator', personas.secretNestedCreator(this.backend)) + makeToken('secret-nested-creator', this.backend, personas.secretNestedCreator) ); await authPage.login(token); clearRecords(this.store); @@ -384,7 +385,7 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook module('secret-creator persona', function (hooks) { hooks.beforeEach(async function () { - const token = await runCmd(tokenWithPolicyCmd('secret-creator', personas.secretCreator(this.backend))); + const token = await runCmd(makeToken('secret-creator', this.backend, personas.secretCreator)); await authPage.login(token); clearRecords(this.store); return; @@ -468,7 +469,7 @@ path "sys/control-group/request" { } `; - const { userToken } = await setupControlGroup({ userPolicy }); + const { userToken } = await setupControlGroup({ userPolicy, backend: this.backend }); this.userToken = userToken; await authPage.login(userToken); clearRecords(this.store); diff --git a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-edge-cases-test.js b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-edge-cases-test.js index 1c1b0d433a..87de5a20d7 100644 --- a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-edge-cases-test.js +++ b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-edge-cases-test.js @@ -59,11 +59,11 @@ module('Acceptance | kv-v2 workflow | edge cases', function (hooks) { const backend = this.backend; const token = await runCmd([ createPolicyCmd( - 'nested-secret-list-reader', + `nested-secret-list-reader-${this.backend}`, metadataPolicy({ backend, secretPath, capabilities }) + dataPolicy({ backend, secretPath, capabilities }) ), - createTokenCmd('nested-secret-list-reader'), + createTokenCmd(`nested-secret-list-reader-${this.backend}`), ]); await authPage.login(token); }); @@ -191,14 +191,14 @@ module('Acceptance | kv-v2 workflow | edge cases', function (hooks) { // user has different permissions for each secret path const token = await runCmd([ createPolicyCmd( - 'destruction-no-read', + `destruction-no-read-${this.backend}`, dataPolicy({ backend, secretPath: 'data-delete-only', capabilities: ['delete'] }) + deleteVersionsPolicy({ backend, secretPath: 'delete-version-only' }) + destroyVersionsPolicy({ backend, secretPath: 'destroy-version-only' }) + metadataPolicy({ backend, secretPath: 'destroy-metadata-only', capabilities: ['delete'] }) + metadataListPolicy(backend) ), - createTokenCmd('destruction-no-read'), + createTokenCmd(`destruction-no-read-${this.backend}`), ]); for (const secret of testSecrets) { await writeVersionedSecret(backend, secret, 'foo', 'bar', 2); diff --git a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-navigation-test.js b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-navigation-test.js index 914d8820c9..b1369e35e1 100644 --- a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-navigation-test.js +++ b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-navigation-test.js @@ -360,10 +360,10 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) { hooks.beforeEach(async function () { const token = await runCmd([ createPolicyCmd( - 'data-reader', + `data-reader-${this.backend}`, personas.dataReader(this.backend) + personas.dataReader(this.emptyBackend) ), - createTokenCmd('data-reader'), + createTokenCmd(`data-reader-${this.backend}`), ]); await authPage.login(token); clearRecords(this.store); @@ -540,10 +540,10 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) { hooks.beforeEach(async function () { const token = await runCmd([ createPolicyCmd( - 'data-reader-list', + `data-reader-list-${this.backend}`, personas.dataListReader(this.backend) + personas.dataListReader(this.emptyBackend) ), - createTokenCmd('data-reader-list'), + createTokenCmd(`data-reader-list-${this.backend}`), ]); await authPage.login(token); @@ -727,10 +727,10 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) { hooks.beforeEach(async function () { const token = await runCmd([ createPolicyCmd( - 'metadata-maintainer', + `metadata-maintainer-${this.backend}`, personas.metadataMaintainer(this.backend) + personas.metadataMaintainer(this.emptyBackend) ), - createTokenCmd('metadata-maintainer'), + createTokenCmd(`metadata-maintainer-${this.backend}`), ]); await authPage.login(token); clearRecords(this.store); @@ -945,10 +945,10 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) { hooks.beforeEach(async function () { const token = await runCmd([ createPolicyCmd( - 'secret-creator', + `secret-creator-${this.backend}`, personas.secretCreator(this.backend) + personas.secretCreator(this.emptyBackend) ), - createTokenCmd('secret-creator'), + createTokenCmd(`secret-creator-${this.backend}`), ]); await authPage.login(token); clearRecords(this.store); @@ -1163,7 +1163,7 @@ path "${this.backend}/*" { capabilities = ["list"] } `; - const { userToken } = await setupControlGroup({ userPolicy }); + const { userToken } = await setupControlGroup({ userPolicy, backend: this.backend }); this.userToken = userToken; await authPage.login(userToken); clearRecords(this.store); @@ -1204,6 +1204,7 @@ path "${this.backend}/*" { apiPath: `${backend}/data/app/nested/secret`, originUrl: `/vault/secrets/${backend}/kv/list/app/nested/`, userToken: this.userToken, + backend: this.backend, }); assert.strictEqual( currentURL(), @@ -1254,11 +1255,11 @@ path "${this.backend}/*" { await waitUntil(() => currentRouteName() === 'vault.cluster.access.control-group-accessor'), 'redirects to access control group route' ); - await grantAccess({ apiPath: `${backend}/data/${encodeURIComponent(secretPath)}`, originUrl: `/vault/secrets/${backend}/kv/list`, userToken: this.userToken, + backend: this.backend, }); assert.strictEqual( diff --git a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-version-history-paths-test.js b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-version-history-paths-test.js index eed313cde5..82d0461ed6 100644 --- a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-version-history-paths-test.js +++ b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-version-history-paths-test.js @@ -20,6 +20,9 @@ import { PAGE } from 'vault/tests/helpers/kv/kv-selectors'; import { click, currentRouteName, currentURL, visit, waitUntil } from '@ember/test-helpers'; import { grantAccess, setupControlGroup } from 'vault/tests/helpers/control-groups'; +const makeToken = (name, mountPath, policyGenerator) => { + return tokenWithPolicyCmd(`${name}-${mountPath}`, policyGenerator(mountPath)); +}; /** * This test set is for testing version history & path pages for secret. * Letter(s) in parenthesis at the end are shorthand for the persona, @@ -52,7 +55,7 @@ module('Acceptance | kv-v2 workflow | version history, paths', function (hooks) module('admin persona', function (hooks) { hooks.beforeEach(async function () { - const token = await runCmd(tokenWithPolicyCmd('admin', personas.admin(this.backend))); + const token = await runCmd(makeToken('admin', this.backend, personas.admin)); await authPage.login(token); clearRecords(this.store); }); @@ -101,7 +104,7 @@ module('Acceptance | kv-v2 workflow | version history, paths', function (hooks) module('data-reader persona', function (hooks) { hooks.beforeEach(async function () { - const token = await runCmd(tokenWithPolicyCmd('data-reader', personas.dataReader(this.backend))); + const token = await runCmd(makeToken('data-reader', this.backend, personas.dataReader)); await authPage.login(token); clearRecords(this.store); }); @@ -128,9 +131,7 @@ module('Acceptance | kv-v2 workflow | version history, paths', function (hooks) module('data-list-reader persona', function (hooks) { hooks.beforeEach(async function () { - const token = await runCmd( - tokenWithPolicyCmd('data-list-reader', personas.dataListReader(this.backend)) - ); + const token = await runCmd(makeToken('data-list-reader', this.backend, personas.dataListReader)); await authPage.login(token); clearRecords(this.store); }); @@ -157,9 +158,7 @@ module('Acceptance | kv-v2 workflow | version history, paths', function (hooks) module('metadata-maintainer persona', function (hooks) { hooks.beforeEach(async function () { - const token = await runCmd( - tokenWithPolicyCmd('metadata-maintainer', personas.metadataMaintainer(this.backend)) - ); + const token = await runCmd(makeToken('metadata-maintainer', this.backend, personas.metadataMaintainer)); await authPage.login(token); clearRecords(this.store); }); @@ -208,7 +207,7 @@ module('Acceptance | kv-v2 workflow | version history, paths', function (hooks) module('secret-creator persona', function (hooks) { hooks.beforeEach(async function () { - const token = await runCmd(tokenWithPolicyCmd('secret-creator', personas.secretCreator(this.backend))); + const token = await runCmd(makeToken('secret-creator', this.backend, personas.secretCreator)); await authPage.login(token); clearRecords(this.store); }); @@ -254,7 +253,7 @@ path "${this.backend}/*" { capabilities = ["list"] } `; - const { userToken } = await setupControlGroup({ userPolicy }); + const { userToken } = await setupControlGroup({ userPolicy, backend: this.backend }); this.userToken = userToken; await authPage.login(userToken); clearRecords(this.store); @@ -270,6 +269,7 @@ path "${this.backend}/*" { apiPath: `${this.backend}/metadata/${this.secretPath}`, originUrl: `/vault/secrets/${this.urlPath}/details`, userToken: this.userToken, + backend: this.backend, }); assert.strictEqual( currentURL(), diff --git a/ui/tests/acceptance/unseal-test.js b/ui/tests/acceptance/unseal-test.js index 26fb12d768..9385dbaac6 100644 --- a/ui/tests/acceptance/unseal-test.js +++ b/ui/tests/acceptance/unseal-test.js @@ -12,7 +12,7 @@ import { pollCluster } from 'vault/tests/helpers/poll-cluster'; const { unsealKeys } = VAULT_KEYS; -module('Acceptance | unseal', function (hooks) { +module.skip('Acceptance | unseal', function (hooks) { setupApplicationTest(hooks); hooks.beforeEach(function () { diff --git a/ui/tests/helpers/control-groups.js b/ui/tests/helpers/control-groups.js index abe42ee550..c226ba1587 100644 --- a/ui/tests/helpers/control-groups.js +++ b/ui/tests/helpers/control-groups.js @@ -5,8 +5,8 @@ import { click, visit } from '@ember/test-helpers'; import { create } from 'ember-cli-page-object'; -import { CONTROL_GROUP_PREFIX, TOKEN_SEPARATOR } from 'vault/services/control-group'; +import { CONTROL_GROUP_PREFIX, TOKEN_SEPARATOR } from 'vault/services/control-group'; import authPage from 'vault/tests/pages/auth'; import controlGroup from 'vault/tests/pages/components/control-group'; import { createPolicyCmd, createTokenCmd, mountAuthCmd, runCmd } from './commands'; @@ -16,13 +16,22 @@ const storageKey = (accessor, path) => { return `${CONTROL_GROUP_PREFIX}${accessor}${TOKEN_SEPARATOR}${path}`; }; +// This function is used to setup a control group for testing +// It will create a userpass backend, create an authorizing user, +// and create a controlled access token. The auth mount and policy +// names will be appended with the backend export const setupControlGroup = async ({ userPolicy, + backend, adminUser = 'authorizer', - adminPassword = 'password', - userpassMount = 'userpass', + adminPassword = 'testing-xyz', }) => { - const userPolicyName = 'kv-control-group'; + if (!backend || !userPolicy) { + throw new Error('missing required fields for setupControlGroup'); + } + const userpassMount = `userpass-${backend}`; + const userPolicyName = `kv-control-group-${backend}`; + const authorizerPolicyName = `authorizer-${backend}`; const authorizerPolicy = ` path "sys/control-group/authorize" { capabilities = ["update"] @@ -35,7 +44,7 @@ export const setupControlGroup = async ({ const userpassAccessor = await runCmd([ // write policies for control group + authorization createPolicyCmd(userPolicyName, userPolicy), - createPolicyCmd('authorizer', authorizerPolicy), + createPolicyCmd(authorizerPolicyName, authorizerPolicy), // enable userpass, create admin user mountAuthCmd('userpass', userpassMount), // read out mount to get the accessor @@ -50,14 +59,13 @@ export const setupControlGroup = async ({ const userToken = await runCmd([ // create alias for authorizor and add them to the managers group `write identity/alias mount_accessor=${userpassAccessor} entity_id=${authorizerEntityId} name=${adminUser}`, - `write identity/group name=managers member_entity_ids=${authorizerEntityId} policies=authorizer`, + `write identity/group name=managers member_entity_ids=${authorizerEntityId} policies=${authorizerPolicyName}`, // create a token to request access to kv/foo createTokenCmd(userPolicyName), ]); return { userToken, userPolicyName, - userPolicy, adminUser, adminPassword, userpassMount, @@ -70,10 +78,15 @@ export async function grantAccessForWrite({ creation_path, originUrl, userToken, + backend, authorizerUser = 'authorizer', - authorizerPassword = 'password', + authorizerPassword = 'testing-xyz', }) { - await authPage.loginUsername(authorizerUser, authorizerPassword); + if (!token || !accessor || !creation_path || !originUrl || !userToken || !backend) { + throw new Error('missing required fields for grantAccessForWrite'); + } + const userpassMount = `userpass-${backend}`; + await authPage.loginUsername(authorizerUser, authorizerPassword, userpassMount); await visit(`/vault/access/control-groups/${accessor}`); await controlGroupComponent.authorize(); await authPage.login(userToken); @@ -91,21 +104,26 @@ export async function grantAccessForWrite({ await visit(originUrl); } +/* + * Control group grant access flow + * Assumes start on route 'vault.cluster.access.control-group-accessor' + * and authorizer login is via userpass + */ export async function grantAccess({ apiPath, originUrl, userToken, + backend, authorizerUser = 'authorizer', - authorizerPassword = 'password', + authorizerPassword = 'testing-xyz', }) { - /* - * Control group grant access flow - * Assumes start on route 'vault.cluster.access.control-group-accessor' - * and authorizer login is via userpass - */ + if (!apiPath || !originUrl || !userToken || !backend) { + throw new Error('missing required fields for grantAccess'); + } + const userpassMount = `userpass-${backend}`; const accessor = controlGroupComponent.accessor; const controlGroupToken = controlGroupComponent.token; - await authPage.loginUsername(authorizerUser, authorizerPassword); + await authPage.loginUsername(authorizerUser, authorizerPassword, userpassMount); await visit(`/vault/access/control-groups/${accessor}`); await controlGroupComponent.authorize(); await authPage.login(userToken); diff --git a/ui/tests/integration/components/info-table-row-test.js b/ui/tests/integration/components/info-table-row-test.js index d6ba44a25e..37b53e60e3 100644 --- a/ui/tests/integration/components/info-table-row-test.js +++ b/ui/tests/integration/components/info-table-row-test.js @@ -197,10 +197,12 @@ module('Integration | Component | InfoTableRow', function (hooks) { }); test('Truncates the label if too long', async function (assert) { this.set('label', 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz'); - await render(hbs` + `); + /> +
`); assert.dom('[data-test-component="info-table-row"]').exists('Row renders'); assert.dom('[data-test-label-div].label-overflow').exists('Label has class label-overflow'); await triggerEvent('[data-test-row-label]', 'mouseenter'); diff --git a/ui/tests/test-helper.js b/ui/tests/test-helper.js index bc3ba1386c..5213d456fd 100644 --- a/ui/tests/test-helper.js +++ b/ui/tests/test-helper.js @@ -8,7 +8,7 @@ import config from 'vault/config/environment'; import * as QUnit from 'qunit'; import { setApplication } from '@ember/test-helpers'; import { setup } from 'qunit-dom'; -import { start } from 'ember-qunit'; +import start from 'ember-exam/test-support/start'; import './helpers/flash-message'; import preloadAssets from 'ember-asset-loader/test-support/preload-assets'; import { setupGlobalA11yHooks, setRunOptions } from 'ember-a11y-testing/test-support'; diff --git a/ui/yarn.lock b/ui/yarn.lock index d586f0e0e1..f8ec5a61b0 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -67,7 +67,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.22.13": +"@babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.5": version: 7.23.5 resolution: "@babel/code-frame@npm:7.23.5" dependencies: @@ -266,6 +266,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:^7.23.6": + version: 7.23.7 + resolution: "@babel/core@npm:7.23.7" + dependencies: + "@ampproject/remapping": ^2.2.0 + "@babel/code-frame": ^7.23.5 + "@babel/generator": ^7.23.6 + "@babel/helper-compilation-targets": ^7.23.6 + "@babel/helper-module-transforms": ^7.23.3 + "@babel/helpers": ^7.23.7 + "@babel/parser": ^7.23.6 + "@babel/template": ^7.22.15 + "@babel/traverse": ^7.23.7 + "@babel/types": ^7.23.6 + convert-source-map: ^2.0.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.2 + json5: ^2.2.3 + semver: ^6.3.1 + checksum: 32d5bf73372a47429afaae9adb0af39e47bcea6a831c4b5dcbb4791380cda6949cb8cb1a2fea8b60bb1ebe189209c80e333903df1fa8e9dcb04798c0ce5bf59e + languageName: node + linkType: hard + "@babel/eslint-parser@npm:^7.21.3": version: 7.22.9 resolution: "@babel/eslint-parser@npm:7.22.9" @@ -336,6 +359,18 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.23.6": + version: 7.23.6 + resolution: "@babel/generator@npm:7.23.6" + dependencies: + "@babel/types": ^7.23.6 + "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 + jsesc: ^2.5.1 + checksum: 1a1a1c4eac210f174cd108d479464d053930a812798e09fee069377de39a893422df5b5b146199ead7239ae6d3a04697b45fc9ac6e38e0f6b76374390f91fc6c + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.12.13, @babel/helper-annotate-as-pure@npm:^7.16.0": version: 7.16.0 resolution: "@babel/helper-annotate-as-pure@npm:7.16.0" @@ -485,6 +520,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-compilation-targets@npm:^7.23.6": + version: 7.23.6 + resolution: "@babel/helper-compilation-targets@npm:7.23.6" + dependencies: + "@babel/compat-data": ^7.23.5 + "@babel/helper-validator-option": ^7.23.5 + browserslist: ^4.22.2 + lru-cache: ^5.1.1 + semver: ^6.3.1 + checksum: c630b98d4527ac8fe2c58d9a06e785dfb2b73ec71b7c4f2ddf90f814b5f75b547f3c015f110a010fd31f76e3864daaf09f3adcd2f6acdbfb18a8de3a48717590 + languageName: node + linkType: hard + "@babel/helper-create-class-features-plugin@npm:^7.13.0, @babel/helper-create-class-features-plugin@npm:^7.16.0, @babel/helper-create-class-features-plugin@npm:^7.8.3": version: 7.16.0 resolution: "@babel/helper-create-class-features-plugin@npm:7.16.0" @@ -1574,6 +1622,17 @@ __metadata: languageName: node linkType: hard +"@babel/helpers@npm:^7.23.7": + version: 7.23.7 + resolution: "@babel/helpers@npm:7.23.7" + dependencies: + "@babel/template": ^7.22.15 + "@babel/traverse": ^7.23.7 + "@babel/types": ^7.23.6 + checksum: 4f3bdf35fb54ff79107c6020ba1e36a38213a15b05ca0fa06c553b65f566e185fba6339fb3344be04593ebc244ed0bbb0c6087e73effe0d053a30bcd2db3a013 + languageName: node + linkType: hard + "@babel/highlight@npm:^7.12.13, @babel/highlight@npm:^7.16.0": version: 7.16.0 resolution: "@babel/highlight@npm:7.16.0" @@ -1692,6 +1751,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.23.6": + version: 7.23.6 + resolution: "@babel/parser@npm:7.23.6" + bin: + parser: ./bin/babel-parser.js + checksum: 140801c43731a6c41fd193f5c02bc71fd647a0360ca616b23d2db8be4b9739b9f951a03fc7c2db4f9b9214f4b27c1074db0f18bc3fa653783082d5af7c8860d5 + languageName: node + linkType: hard + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.16.7": version: 7.16.7 resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.16.7" @@ -5088,6 +5156,24 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.23.7": + version: 7.23.7 + resolution: "@babel/traverse@npm:7.23.7" + dependencies: + "@babel/code-frame": ^7.23.5 + "@babel/generator": ^7.23.6 + "@babel/helper-environment-visitor": ^7.22.20 + "@babel/helper-function-name": ^7.23.0 + "@babel/helper-hoist-variables": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/parser": ^7.23.6 + "@babel/types": ^7.23.6 + debug: ^4.3.1 + globals: ^11.1.0 + checksum: d4a7afb922361f710efc97b1e25ec343fab8b2a4ddc81ca84f9a153f22d4482112cba8f263774be8d297918b6c4767c7a98988ab4e53ac73686c986711dd002e + languageName: node + linkType: hard + "@babel/types@npm:^7.1.6, @babel/types@npm:^7.7.2": version: 7.14.0 resolution: "@babel/types@npm:7.14.0" @@ -5172,6 +5258,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.23.6": + version: 7.23.6 + resolution: "@babel/types@npm:7.23.6" + dependencies: + "@babel/helper-string-parser": ^7.23.4 + "@babel/helper-validator-identifier": ^7.22.20 + to-fast-properties: ^2.0.0 + checksum: 68187dbec0d637f79bc96263ac95ec8b06d424396678e7e225492be866414ce28ebc918a75354d4c28659be6efe30020b4f0f6df81cc418a2d30645b690a8de0 + languageName: node + linkType: hard + "@babel/types@npm:^7.8.3": version: 7.21.4 resolution: "@babel/types@npm:7.21.4" @@ -5195,6 +5292,13 @@ __metadata: languageName: node linkType: hard +"@colors/colors@npm:1.5.0": + version: 1.5.0 + resolution: "@colors/colors@npm:1.5.0" + checksum: d64d5260bed1d5012ae3fc617d38d1afc0329fec05342f4e6b838f46998855ba56e0a73833f4a80fa8378c84810da254f76a8a19c39d038260dc06dc4e007425 + languageName: node + linkType: hard + "@csstools/css-parser-algorithms@npm:^2.3.0": version: 2.3.0 resolution: "@csstools/css-parser-algorithms@npm:2.3.0" @@ -6207,6 +6311,20 @@ __metadata: languageName: node linkType: hard +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: ^5.1.2 + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: ^7.0.1 + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: ^8.1.0 + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 4a473b9b32a7d4d3cfb7a614226e555091ff0c5a29a1734c28c72a182c2f6699b26fc6b5c2131dfd841e86b185aea714c72201d7c98c2fba5f17709333a67aeb + languageName: node + linkType: hard + "@jridgewell/gen-mapping@npm:^0.3.0": version: 0.3.1 resolution: "@jridgewell/gen-mapping@npm:0.3.1" @@ -6483,6 +6601,13 @@ __metadata: languageName: node linkType: hard +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 6ad6a00fc4f2f2cfc6bff76fb1d88b8ee20bc0601e18ebb01b6d4be583733a860239a521a7fbca73b612e66705078809483549d2b18f370eb346c5155c8e4a0f + languageName: node + linkType: hard + "@popperjs/core@npm:^2.9.0": version: 2.11.8 resolution: "@popperjs/core@npm:2.11.8" @@ -7844,6 +7969,15 @@ __metadata: languageName: node linkType: hard +"abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "abort-controller@npm:3.0.0" + dependencies: + event-target-shim: ^5.0.0 + checksum: 170bdba9b47b7e65906a28c8ce4f38a7a369d78e2271706f020849c1bfe0ee2067d4261df8bbb66eb84f79208fd5b710df759d64191db58cfba7ce8ef9c54b75 + languageName: node + linkType: hard + "abortcontroller-polyfill@npm:^1.7.3": version: 1.7.3 resolution: "abortcontroller-polyfill@npm:1.7.3" @@ -8216,6 +8350,13 @@ __metadata: languageName: node linkType: hard +"ansi-regex@npm:^6.0.1": + version: 6.0.1 + resolution: "ansi-regex@npm:6.0.1" + checksum: 1ff8b7667cded1de4fa2c9ae283e979fc87036864317da86a2e546725f96406746411d0d85e87a2d12fa5abd715d90006de7fa4fa0477c92321ad3b4c7d4e169 + languageName: node + linkType: hard + "ansi-styles@npm:^2.2.1": version: 2.2.1 resolution: "ansi-styles@npm:2.2.1" @@ -8241,6 +8382,13 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:^6.1.0": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: ef940f2f0ced1a6347398da88a91da7930c33ecac3c77b72c5905f8b8fe402c52e6fde304ff5347f616e27a742da3f1dc76de98f6866c69251ad0b07a66776d9 + languageName: node + linkType: hard + "ansi-styles@npm:~1.0.0": version: 1.0.0 resolution: "ansi-styles@npm:1.0.0" @@ -8320,6 +8468,16 @@ __metadata: languageName: node linkType: hard +"are-we-there-yet@npm:^4.0.0": + version: 4.0.1 + resolution: "are-we-there-yet@npm:4.0.1" + dependencies: + delegates: ^1.0.0 + readable-stream: ^4.1.0 + checksum: 16871ee259e138bfab60800ae5b53406fb1b72b5d356f98b13c1b222bb2a13d9bc4292d79f4521fb0eca10874eb3838ae0d9f721f3bb34ddd37ee8f949831800 + languageName: node + linkType: hard + "argparse@npm:^1.0.7": version: 1.0.10 resolution: "argparse@npm:1.0.10" @@ -11082,7 +11240,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.22.1": +"browserslist@npm:^4.22.1, browserslist@npm:^4.22.2": version: 4.22.2 resolution: "browserslist@npm:4.22.2" dependencies: @@ -11140,6 +11298,16 @@ __metadata: languageName: node linkType: hard +"buffer@npm:^6.0.3": + version: 6.0.3 + resolution: "buffer@npm:6.0.3" + dependencies: + base64-js: ^1.3.1 + ieee754: ^1.2.1 + checksum: 5ad23293d9a731e4318e420025800b42bf0d264004c0286c8cc010af7a270c7a0f6522e84f54b9ad65cbd6db20b8badbfd8d2ebf4f80fa03dab093b89e68c3f9 + languageName: node + linkType: hard + "builtin-status-codes@npm:^3.0.0": version: 3.0.0 resolution: "builtin-status-codes@npm:3.0.0" @@ -11432,7 +11600,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^5.2.0": +"chalk@npm:^5.2.0, chalk@npm:^5.3.0": version: 5.3.0 resolution: "chalk@npm:5.3.0" checksum: 623922e077b7d1e9dedaea6f8b9e9352921f8ae3afe739132e0e00c275971bdd331268183b2628cf4ab1727c45ea1f28d7e24ac23ce1db1eb653c414ca8a5a80 @@ -11703,6 +11871,19 @@ __metadata: languageName: node linkType: hard +"cli-table3@npm:^0.6.0": + version: 0.6.3 + resolution: "cli-table3@npm:0.6.3" + dependencies: + "@colors/colors": 1.5.0 + string-width: ^4.2.0 + dependenciesMeta: + "@colors/colors": + optional: true + checksum: 09897f68467973f827c04e7eaadf13b55f8aec49ecd6647cc276386ea660059322e2dd8020a8b6b84d422dbdd619597046fa89cbbbdc95b2cea149a2df7c096c + languageName: node + linkType: hard + "cli-table@npm:^0.3.1": version: 0.3.6 resolution: "cli-table@npm:0.3.6" @@ -12120,6 +12301,13 @@ __metadata: languageName: node linkType: hard +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 63ae9933be5a2b8d4509daca5124e20c14d023c820258e484e32dc324d34c2754e71297c94a05784064ad27615037ef677e3f0c00469fb55f409d2bb21261035 + languageName: node + linkType: hard + "cookie-signature@npm:1.0.6": version: 1.0.6 resolution: "cookie-signature@npm:1.0.6" @@ -13496,6 +13684,13 @@ __metadata: languageName: node linkType: hard +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 7d00d7cd8e49b9afa762a813faac332dee781932d6f2c848dc348939c4253f1d4564341b7af1d041853bc3f32c2ef141b58e0a4d9862c17a7f08f68df1e0f1ed + languageName: node + linkType: hard + "ecc-jsbn@npm:~0.1.1": version: 0.1.2 resolution: "ecc-jsbn@npm:0.1.2" @@ -13862,6 +14057,48 @@ __metadata: languageName: node linkType: hard +"ember-auto-import@npm:^2.7.0": + version: 2.7.2 + resolution: "ember-auto-import@npm:2.7.2" + dependencies: + "@babel/core": ^7.16.7 + "@babel/plugin-proposal-class-properties": ^7.16.7 + "@babel/plugin-proposal-decorators": ^7.16.7 + "@babel/plugin-proposal-private-methods": ^7.16.7 + "@babel/plugin-transform-class-static-block": ^7.16.7 + "@babel/preset-env": ^7.16.7 + "@embroider/macros": ^1.0.0 + "@embroider/shared-internals": ^2.0.0 + babel-loader: ^8.0.6 + babel-plugin-ember-modules-api-polyfill: ^3.5.0 + babel-plugin-ember-template-compilation: ^2.0.1 + babel-plugin-htmlbars-inline-precompile: ^5.2.1 + babel-plugin-syntax-dynamic-import: ^6.18.0 + broccoli-debug: ^0.6.4 + broccoli-funnel: ^3.0.8 + broccoli-merge-trees: ^4.2.0 + broccoli-plugin: ^4.0.0 + broccoli-source: ^3.0.0 + css-loader: ^5.2.0 + debug: ^4.3.1 + fs-extra: ^10.0.0 + fs-tree-diff: ^2.0.0 + handlebars: ^4.3.1 + js-string-escape: ^1.0.1 + lodash: ^4.17.19 + mini-css-extract-plugin: ^2.5.2 + minimatch: ^3.0.0 + parse5: ^6.0.1 + resolve: ^1.20.0 + resolve-package-path: ^4.0.3 + semver: ^7.3.4 + style-loader: ^2.0.0 + typescript-memoize: ^1.0.0-alpha.3 + walk-sync: ^3.0.0 + checksum: c998028f7330ecb9ffa5242b60b55f7e6722c3b45959b1b0910cdb9b82920abdefd36f393a80e79bdfd0ff6532641ff1a3fd5b668bfdecd883ecafb00620c15f + languageName: node + linkType: hard + "ember-basic-dropdown@npm:6.0.1": version: 6.0.1 resolution: "ember-basic-dropdown@npm:6.0.1" @@ -14923,6 +15160,31 @@ __metadata: languageName: node linkType: hard +"ember-exam@npm:^9.0.0": + version: 9.0.0 + resolution: "ember-exam@npm:9.0.0" + dependencies: + "@babel/core": ^7.23.6 + chalk: ^5.3.0 + cli-table3: ^0.6.0 + debug: ^4.2.0 + ember-auto-import: ^2.7.0 + ember-cli-babel: ^8.2.0 + execa: ^8.0.1 + fs-extra: ^11.2.0 + js-yaml: ^4.0.0 + npmlog: ^7.0.0 + rimraf: ^5.0.0 + semver: ^7.3.2 + silent-error: ^1.1.1 + peerDependencies: + ember-qunit: "*" + ember-source: ">= 4.0.0" + qunit: "*" + checksum: 5ff9dd11d89d96d6ca018e145eefad558f38b93ee95d39108bb919514c66c731713b00c68725e2377da738c1cf4854ce89b6132d2df44be93a4d99900f589a7b + languageName: node + linkType: hard + "ember-fetch@npm:^8.1.2": version: 8.1.2 resolution: "ember-fetch@npm:8.1.2" @@ -15538,6 +15800,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 8487182da74aabd810ac6d6f1994111dfc0e331b01271ae01ec1eb0ad7b5ecc2bbbbd2f053c05cb55a1ac30449527d819bbfbf0e3de1023db308cbcb47f86601 + languageName: node + linkType: hard + "emoji-regex@npm:~6.1.0": version: 6.1.3 resolution: "emoji-regex@npm:6.1.3" @@ -16328,6 +16597,13 @@ __metadata: languageName: node linkType: hard +"event-target-shim@npm:^5.0.0": + version: 5.0.1 + resolution: "event-target-shim@npm:5.0.1" + checksum: 1ffe3bb22a6d51bdeb6bf6f7cf97d2ff4a74b017ad12284cc9e6a279e727dc30a5de6bb613e5596ff4dc3e517841339ad09a7eec44266eccb1aa201a30448166 + languageName: node + linkType: hard + "eventemitter3@npm:^4.0.0": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" @@ -16342,7 +16618,7 @@ __metadata: languageName: node linkType: hard -"events@npm:^3.0.0, events@npm:^3.2.0": +"events@npm:^3.0.0, events@npm:^3.2.0, events@npm:^3.3.0": version: 3.3.0 resolution: "events@npm:3.3.0" checksum: f6f487ad2198aa41d878fa31452f1a3c00958f46e9019286ff4787c84aac329332ab45c9cdc8c445928fc6d7ded294b9e005a7fce9426488518017831b272780 @@ -16451,6 +16727,23 @@ __metadata: languageName: node linkType: hard +"execa@npm:^8.0.1": + version: 8.0.1 + resolution: "execa@npm:8.0.1" + dependencies: + cross-spawn: ^7.0.3 + get-stream: ^8.0.1 + human-signals: ^5.0.0 + is-stream: ^3.0.0 + merge-stream: ^2.0.0 + npm-run-path: ^5.1.0 + onetime: ^6.0.0 + signal-exit: ^4.1.0 + strip-final-newline: ^3.0.0 + checksum: cac1bf86589d1d9b73bdc5dda65c52012d1a9619c44c526891956745f7b366ca2603d29fe3f7460bacc2b48c6eab5d6a4f7afe0534b31473d3708d1265545e1f + languageName: node + linkType: hard + "exists-sync@npm:0.0.4": version: 0.0.4 resolution: "exists-sync@npm:0.0.4" @@ -17187,6 +17480,16 @@ __metadata: languageName: node linkType: hard +"foreground-child@npm:^3.1.0": + version: 3.1.1 + resolution: "foreground-child@npm:3.1.1" + dependencies: + cross-spawn: ^7.0.0 + signal-exit: ^4.0.1 + checksum: 139d270bc82dc9e6f8bc045fe2aae4001dc2472157044fdfad376d0a3457f77857fa883c1c8b21b491c6caade9a926a4bed3d3d2e8d3c9202b151a4cbbd0bcd5 + languageName: node + linkType: hard + "forever-agent@npm:~0.6.1": version: 0.6.1 resolution: "forever-agent@npm:0.6.1" @@ -17299,6 +17602,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.2.0": + version: 11.2.0 + resolution: "fs-extra@npm:11.2.0" + dependencies: + graceful-fs: ^4.2.0 + jsonfile: ^6.0.1 + universalify: ^2.0.0 + checksum: b12e42fa40ba47104202f57b8480dd098aa931c2724565e5e70779ab87605665594e76ee5fb00545f772ab9ace167fe06d2ab009c416dc8c842c5ae6df7aa7e8 + languageName: node + linkType: hard + "fs-extra@npm:^3.0.1": version: 3.0.1 resolution: "fs-extra@npm:3.0.1" @@ -17545,6 +17859,22 @@ __metadata: languageName: node linkType: hard +"gauge@npm:^5.0.0": + version: 5.0.1 + resolution: "gauge@npm:5.0.1" + dependencies: + aproba: ^1.0.3 || ^2.0.0 + color-support: ^1.1.3 + console-control-strings: ^1.1.0 + has-unicode: ^2.0.1 + signal-exit: ^4.0.1 + string-width: ^4.2.3 + strip-ansi: ^6.0.1 + wide-align: ^1.1.5 + checksum: 09b1eb8d8c850df7e4e2822feef27427afc845d4839fa13a08ddad74f882caf668dd1e77ac5e059d3e9a7b0cef59b706d28be40e1dc5fd326da32965e1f206a6 + languageName: node + linkType: hard + "gensync@npm:^1.0.0-beta.2": version: 1.0.0-beta.2 resolution: "gensync@npm:1.0.0-beta.2" @@ -17635,6 +17965,13 @@ __metadata: languageName: node linkType: hard +"get-stream@npm:^8.0.1": + version: 8.0.1 + resolution: "get-stream@npm:8.0.1" + checksum: 01e3d3cf29e1393f05f44d2f00445c5f9ec3d1c49e8179b31795484b9c117f4c695e5e07b88b50785d5c8248a788c85d9913a79266fc77e3ef11f78f10f1b974 + languageName: node + linkType: hard + "get-symbol-description@npm:^1.0.0": version: 1.0.0 resolution: "get-symbol-description@npm:1.0.0" @@ -17710,6 +18047,21 @@ __metadata: languageName: node linkType: hard +"glob@npm:^10.3.7": + version: 10.3.10 + resolution: "glob@npm:10.3.10" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^2.3.5 + minimatch: ^9.0.1 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + path-scurry: ^1.10.1 + bin: + glob: dist/esm/bin.mjs + checksum: 4f2fe2511e157b5a3f525a54092169a5f92405f24d2aed3142f4411df328baca13059f4182f1db1bf933e2c69c0bd89e57ae87edd8950cba8c7ccbe84f721cf3 + languageName: node + linkType: hard + "glob@npm:^5.0.10": version: 5.0.15 resolution: "glob@npm:5.0.15" @@ -18507,6 +18859,13 @@ __metadata: languageName: node linkType: hard +"human-signals@npm:^5.0.0": + version: 5.0.0 + resolution: "human-signals@npm:5.0.0" + checksum: 6504560d5ed91444f16bea3bd9dfc66110a339442084e56c3e7fa7bbdf3f406426d6563d662bdce67064b165eac31eeabfc0857ed170aaa612cf14ec9f9a464c + languageName: node + linkType: hard + "humanize-ms@npm:^1.2.1": version: 1.2.1 resolution: "humanize-ms@npm:1.2.1" @@ -18543,7 +18902,7 @@ __metadata: languageName: node linkType: hard -"ieee754@npm:^1.1.13, ieee754@npm:^1.1.4": +"ieee754@npm:^1.1.13, ieee754@npm:^1.1.4, ieee754@npm:^1.2.1": version: 1.2.1 resolution: "ieee754@npm:1.2.1" checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e @@ -19316,6 +19675,13 @@ __metadata: languageName: node linkType: hard +"is-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "is-stream@npm:3.0.0" + checksum: 172093fe99119ffd07611ab6d1bcccfe8bc4aa80d864b15f43e63e54b7abc71e779acd69afdb854c4e2a67fdc16ae710e370eda40088d1cfc956a50ed82d8f16 + languageName: node + linkType: hard + "is-string@npm:^1.0.5, is-string@npm:^1.0.7": version: 1.0.7 resolution: "is-string@npm:1.0.7" @@ -19508,6 +19874,19 @@ __metadata: languageName: node linkType: hard +"jackspeak@npm:^2.3.5": + version: 2.3.6 + resolution: "jackspeak@npm:2.3.6" + dependencies: + "@isaacs/cliui": ^8.0.2 + "@pkgjs/parseargs": ^0.11.0 + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 57d43ad11eadc98cdfe7496612f6bbb5255ea69fe51ea431162db302c2a11011642f50cfad57288bd0aea78384a0612b16e131944ad8ecd09d619041c8531b54 + languageName: node + linkType: hard + "jest-worker@npm:^27.4.5": version: 27.5.1 resolution: "jest-worker@npm:27.5.1" @@ -19566,7 +19945,7 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^4.1.0": +"js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0": version: 4.1.0 resolution: "js-yaml@npm:4.1.0" dependencies: @@ -19737,7 +20116,7 @@ __metadata: languageName: node linkType: hard -"json5@npm:^2.1.1, json5@npm:^2.2.2": +"json5@npm:^2.1.1, json5@npm:^2.2.2, json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" bin: @@ -20641,6 +21020,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^9.1.1 || ^10.0.0": + version: 10.1.0 + resolution: "lru-cache@npm:10.1.0" + checksum: 58056d33e2500fbedce92f8c542e7c11b50d7d086578f14b7074d8c241422004af0718e08a6eaae8705cee09c77e39a61c1c79e9370ba689b7010c152e6a76ab + languageName: node + linkType: hard + "magic-string@npm:^0.24.0": version: 0.24.1 resolution: "magic-string@npm:0.24.1" @@ -21327,6 +21713,13 @@ __metadata: languageName: node linkType: hard +"mimic-fn@npm:^4.0.0": + version: 4.0.0 + resolution: "mimic-fn@npm:4.0.0" + checksum: 995dcece15ee29aa16e188de6633d43a3db4611bcf93620e7e62109ec41c79c0f34277165b8ce5e361205049766e371851264c21ac64ca35499acb5421c2ba56 + languageName: node + linkType: hard + "min-indent@npm:^1.0.1": version: 1.0.1 resolution: "min-indent@npm:1.0.1" @@ -21470,6 +21863,13 @@ __metadata: languageName: node linkType: hard +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0": + version: 7.0.4 + resolution: "minipass@npm:7.0.4" + checksum: 87585e258b9488caf2e7acea242fd7856bbe9a2c84a7807643513a338d66f368c7d518200ad7b70a508664d408aa000517647b2930c259a8b1f9f0984f344a21 + languageName: node + linkType: hard + "minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": version: 2.1.2 resolution: "minizlib@npm:2.1.2" @@ -22089,6 +22489,15 @@ __metadata: languageName: node linkType: hard +"npm-run-path@npm:^5.1.0": + version: 5.2.0 + resolution: "npm-run-path@npm:5.2.0" + dependencies: + path-key: ^4.0.0 + checksum: c5325e016014e715689c4014f7e0be16cc4cbf529f32a1723e511bc4689b5f823b704d2bca61ac152ce2bda65e0205dc8b3ba0ec0f5e4c3e162d302f6f5b9efb + languageName: node + linkType: hard + "npmlog@npm:^6.0.0": version: 6.0.2 resolution: "npmlog@npm:6.0.2" @@ -22101,6 +22510,18 @@ __metadata: languageName: node linkType: hard +"npmlog@npm:^7.0.0": + version: 7.0.1 + resolution: "npmlog@npm:7.0.1" + dependencies: + are-we-there-yet: ^4.0.0 + console-control-strings: ^1.1.0 + gauge: ^5.0.0 + set-blocking: ^2.0.0 + checksum: caabeb1f557c1094ad7ed3275b968b83ccbaefc133f17366ebb9fe8eb44e1aace28c31419d6244bfc0422aede1202875d555fe6661978bf04386f6cf617f43a4 + languageName: node + linkType: hard + "nth-check@npm:^1.0.2": version: 1.0.2 resolution: "nth-check@npm:1.0.2" @@ -22320,6 +22741,15 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^6.0.0": + version: 6.0.0 + resolution: "onetime@npm:6.0.0" + dependencies: + mimic-fn: ^4.0.0 + checksum: 0846ce78e440841335d4e9182ef69d5762e9f38aa7499b19f42ea1c4cd40f0b4446094c455c713f9adac3f4ae86f613bb5e30c99e52652764d06a89f709b3788 + languageName: node + linkType: hard + "optionator@npm:^0.8.1": version: 0.8.3 resolution: "optionator@npm:0.8.3" @@ -22767,6 +23197,13 @@ __metadata: languageName: node linkType: hard +"path-key@npm:^4.0.0": + version: 4.0.0 + resolution: "path-key@npm:4.0.0" + checksum: 8e6c314ae6d16b83e93032c61020129f6f4484590a777eed709c4a01b50e498822b00f76ceaf94bc64dbd90b327df56ceadce27da3d83393790f1219e07721d7 + languageName: node + linkType: hard + "path-parse@npm:^1.0.6, path-parse@npm:^1.0.7": version: 1.0.7 resolution: "path-parse@npm:1.0.7" @@ -22797,6 +23234,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^1.10.1": + version: 1.10.1 + resolution: "path-scurry@npm:1.10.1" + dependencies: + lru-cache: ^9.1.1 || ^10.0.0 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + checksum: e2557cff3a8fb8bc07afdd6ab163a92587884f9969b05bbbaf6fe7379348bfb09af9ed292af12ed32398b15fb443e81692047b786d1eeb6d898a51eb17ed7d90 + languageName: node + linkType: hard + "path-to-regexp@npm:0.1.7": version: 0.1.7 resolution: "path-to-regexp@npm:0.1.7" @@ -23662,6 +24109,19 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^4.1.0": + version: 4.5.2 + resolution: "readable-stream@npm:4.5.2" + dependencies: + abort-controller: ^3.0.0 + buffer: ^6.0.3 + events: ^3.3.0 + process: ^0.11.10 + string_decoder: ^1.3.0 + checksum: c4030ccff010b83e4f33289c535f7830190773e274b3fcb6e2541475070bdfd69c98001c3b0cb78763fc00c8b62f514d96c2b10a8bd35d5ce45203a25fa1d33a + languageName: node + linkType: hard + "readable-stream@npm:~1.0.2": version: 1.0.34 resolution: "readable-stream@npm:1.0.34" @@ -24415,6 +24875,17 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:^5.0.0": + version: 5.0.5 + resolution: "rimraf@npm:5.0.5" + dependencies: + glob: ^10.3.7 + bin: + rimraf: dist/esm/bin.mjs + checksum: d66eef829b2e23b16445f34e73d75c7b7cf4cbc8834b04720def1c8f298eb0753c3d76df77325fad79d0a2c60470525d95f89c2475283ad985fd7441c32732d1 + languageName: node + linkType: hard + "rimraf@npm:~2.6.2": version: 2.6.3 resolution: "rimraf@npm:2.6.3" @@ -25096,6 +25567,13 @@ __metadata: languageName: node linkType: hard +"signal-exit@npm:^4.1.0": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 64c757b498cb8629ffa5f75485340594d2f8189e9b08700e69199069c8e3070fb3e255f7ab873c05dc0b3cec412aea7402e10a5990cb6a050bd33ba062a6c549 + languageName: node + linkType: hard + "silent-error@npm:^1.0.0, silent-error@npm:^1.0.1, silent-error@npm:^1.1.0, silent-error@npm:^1.1.1": version: 1.1.1 resolution: "silent-error@npm:1.1.1" @@ -25646,6 +26124,17 @@ __metadata: languageName: node linkType: hard +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0": + version: 4.2.2 + resolution: "string-width@npm:4.2.2" + dependencies: + emoji-regex: ^8.0.0 + is-fullwidth-code-point: ^3.0.0 + strip-ansi: ^6.0.0 + checksum: 343e089b0e66e0f72aab4ad1d9b6f2c9cc5255844b0c83fd9b53f2a3b3fd0421bdd6cb05be96a73117eb012db0887a6c1d64ca95aaa50c518e48980483fea0ab + languageName: node + linkType: hard + "string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" @@ -25678,14 +26167,14 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^4.1.0, string-width@npm:^4.2.0": - version: 4.2.2 - resolution: "string-width@npm:4.2.2" +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" dependencies: - emoji-regex: ^8.0.0 - is-fullwidth-code-point: ^3.0.0 - strip-ansi: ^6.0.0 - checksum: 343e089b0e66e0f72aab4ad1d9b6f2c9cc5255844b0c83fd9b53f2a3b3fd0421bdd6cb05be96a73117eb012db0887a6c1d64ca95aaa50c518e48980483fea0ab + eastasianwidth: ^0.2.0 + emoji-regex: ^9.2.2 + strip-ansi: ^7.0.1 + checksum: 7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193 languageName: node linkType: hard @@ -25806,7 +26295,7 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1": +"string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" dependencies: @@ -25835,6 +26324,15 @@ __metadata: languageName: node linkType: hard +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: ^5.0.1 + checksum: f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c + languageName: node + linkType: hard + "strip-ansi@npm:^3.0.0": version: 3.0.1 resolution: "strip-ansi@npm:3.0.1" @@ -25871,12 +26369,12 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^6.0.1": - version: 6.0.1 - resolution: "strip-ansi@npm:6.0.1" +"strip-ansi@npm:^7.0.1": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" dependencies: - ansi-regex: ^5.0.1 - checksum: f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c + ansi-regex: ^6.0.1 + checksum: 859c73fcf27869c22a4e4d8c6acfe690064659e84bef9458aa6d13719d09ca88dcfd40cbf31fd0be63518ea1a643fe070b4827d353e09533a5b0b9fd4553d64d languageName: node linkType: hard @@ -25917,6 +26415,13 @@ __metadata: languageName: node linkType: hard +"strip-final-newline@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-final-newline@npm:3.0.0" + checksum: 23ee263adfa2070cd0f23d1ac14e2ed2f000c9b44229aec9c799f1367ec001478469560abefd00c5c99ee6f0b31c137d53ec6029c53e9f32a93804e18c201050 + languageName: node + linkType: hard + "strip-indent@npm:^4.0.0": version: 4.0.0 resolution: "strip-indent@npm:4.0.0" @@ -27425,6 +27930,7 @@ __metadata: ember-d3: ^0.5.1 ember-data: ~4.11.3 ember-engines: 0.8.23 + ember-exam: ^9.0.0 ember-fetch: ^8.1.2 ember-inflector: 4.0.2 ember-load-initializers: ^2.1.2 @@ -28025,6 +28531,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b + languageName: node + linkType: hard + "wrap-ansi@npm:^5.1.0": version: 5.1.0 resolution: "wrap-ansi@npm:5.1.0" @@ -28047,14 +28564,14 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" dependencies: - ansi-styles: ^4.0.0 - string-width: ^4.1.0 - strip-ansi: ^6.0.0 - checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b + ansi-styles: ^6.1.0 + string-width: ^5.0.1 + strip-ansi: ^7.0.1 + checksum: 371733296dc2d616900ce15a0049dca0ef67597d6394c57347ba334393599e800bab03c41d4d45221b6bc967b8c453ec3ae4749eff3894202d16800fdfe0e238 languageName: node linkType: hard From 39312072447abc82b990cce99934e952caaf5741 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 12:24:19 -0500 Subject: [PATCH 10/14] Bump github.com/dvsekhvalnov/jose2go from 1.5.0 to 1.6.0 (#24639) Bumps [github.com/dvsekhvalnov/jose2go](https://github.com/dvsekhvalnov/jose2go) from 1.5.0 to 1.6.0. - [Commits](https://github.com/dvsekhvalnov/jose2go/compare/v1.5...v1.6.0) --- updated-dependencies: - dependency-name: github.com/dvsekhvalnov/jose2go dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Violet Hynes --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 03418d9890..fd2cdbf6fe 100644 --- a/go.mod +++ b/go.mod @@ -334,7 +334,7 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect - github.com/dvsekhvalnov/jose2go v1.5.0 // indirect + github.com/dvsekhvalnov/jose2go v1.6.0 // indirect github.com/emicklei/go-restful/v3 v3.10.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/envoyproxy/go-control-plane v0.11.1 // indirect diff --git a/go.sum b/go.sum index ca57987211..432f86ad06 100644 --- a/go.sum +++ b/go.sum @@ -1579,8 +1579,9 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY= +github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= From 09427955b2e51988e412a94b88b5e53f23e9dc8b Mon Sep 17 00:00:00 2001 From: Violet Hynes Date: Mon, 8 Jan 2024 13:17:35 -0500 Subject: [PATCH 11/14] Add -type -f to the -find delete call as part of test collect reports (#24711) --- .github/workflows/test-go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-go.yml b/.github/workflows/test-go.yml index 24825b5272..63e96ec14a 100644 --- a/.github/workflows/test-go.yml +++ b/.github/workflows/test-go.yml @@ -510,7 +510,7 @@ jobs: - run: | rm -rf test-results/go-test/logs ls -lhR test-results/go-test - find test-results/go-test -mindepth 1 -mtime +3 -delete + find test-results/go-test -mindepth 1 -type f -mtime +3 -delete # Prune invalid timing files find test-results/go-test -mindepth 1 -type f -name "*.json" -exec sh -c ' From 376812adaf08e1648566e62e2ed394434067c4ff Mon Sep 17 00:00:00 2001 From: Christopher Swenson Date: Mon, 8 Jan 2024 10:36:58 -0800 Subject: [PATCH 12/14] Upgrade go-msgpack to v2 2.1.1 (#23750) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And set the `time.Time` option to use the go-msgpack-1.1.5-compatible encoding in all the places, since that is the (now previous) version in `go.mod`. v2 2.1.1 was specifically designed to honor backwards compatibility with 1.1.5 and 0.5.5, and to clean up the code base to be more maintainable. There may performance lost with the 1.1.5 to 2.1.1 migration since the fastpath code was removed, but the increased safety is probably worth it. See [the release notes for go-msgkack 2.1.0](https://github.com/hashicorp/go-msgpack/releases/tag/v2.1.0) for more details. I tested this by running this code, and booting up a cluster with a node also running Vault 1.15.0 (before the upgrade). Before I made the changes to set the right `time.Time` option, the previous-version node would throw a bunch of time-decoding errors. After fixing the option, the node came up smoothly, even after changing leadership between them. This relies on - https://github.com/hashicorp/raft-boltdb/pull/38 - https://github.com/hashicorp/raft/pull/577 I did a simple pair of benchmarks (one with a final sync, one without) and ran them before and after on both my Mac (M2 Max) laptop and my Linux (AMD Threadripper 3970X) desktop. tl;dr There was no performance difference for this benchmark. ``` goos: darwin goarch: arm64 pkg: github.com/hashicorp/vault/physical/raft │ a.txt │ b.txt │ │ sec/op │ sec/op vs base │ RaftWithNetwork-10 58.65m ± 2% 58.62m ± 2% ~ (p=0.937 n=6) ``` ``` goos: linux goarch: amd64 pkg: github.com/hashicorp/vault/physical/raft cpu: AMD Ryzen Threadripper 3970X 32-Core Processor │ c.txt │ d.txt │ │ sec/op │ sec/op vs base │ RaftWithNetwork-64 5.861m ± 1% 5.837m ± 0% ~ (p=0.240 n=6) ``` --- go.mod | 19 ++++++++++--------- go.sum | 31 +++++++++++++++++-------------- physical/raft/msgpack.go | 13 ------------- physical/raft/raft.go | 16 +++++++++------- vault/cluster/cluster.go | 3 ++- 5 files changed, 38 insertions(+), 44 deletions(-) delete mode 100644 physical/raft/msgpack.go diff --git a/go.mod b/go.mod index fd2cdbf6fe..6d1466cdee 100644 --- a/go.mod +++ b/go.mod @@ -82,6 +82,7 @@ require ( github.com/hashicorp/cli v1.1.6 github.com/hashicorp/consul-template v0.36.0 github.com/hashicorp/consul/api v1.26.1 + github.com/hashicorp/consul/sdk v0.15.0 github.com/hashicorp/errwrap v1.1.0 github.com/hashicorp/eventlogger v0.2.8 github.com/hashicorp/go-bexpr v0.1.12 @@ -99,7 +100,8 @@ require ( github.com/hashicorp/go-kms-wrapping/wrappers/ocikms/v2 v2.0.7 github.com/hashicorp/go-kms-wrapping/wrappers/transit/v2 v2.0.8 github.com/hashicorp/go-memdb v1.3.4 - github.com/hashicorp/go-msgpack v1.1.5 + github.com/hashicorp/go-metrics v0.5.1 + github.com/hashicorp/go-msgpack/v2 v2.1.1 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-plugin v1.6.0 github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a @@ -120,16 +122,16 @@ require ( github.com/hashicorp/go-syslog v1.0.0 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 - github.com/hashicorp/golang-lru v0.5.4 + github.com/hashicorp/golang-lru v1.0.2 github.com/hashicorp/hcl v1.0.1-vault-5 github.com/hashicorp/hcl/v2 v2.16.2 github.com/hashicorp/hcp-link v0.1.0 github.com/hashicorp/hcp-scada-provider v0.2.1 github.com/hashicorp/hcp-sdk-go v0.23.0 github.com/hashicorp/nomad/api v0.0.0-20230519153805-2275a83cbfdf - github.com/hashicorp/raft v1.3.10 + github.com/hashicorp/raft v1.6.0 github.com/hashicorp/raft-autopilot v0.2.0 - github.com/hashicorp/raft-boltdb/v2 v2.0.0-20210421194847-a7e34179d62c + github.com/hashicorp/raft-boltdb/v2 v2.3.0 github.com/hashicorp/raft-snapshot v1.0.4 github.com/hashicorp/vault-plugin-auth-alicloud v0.16.0 github.com/hashicorp/vault-plugin-auth-azure v0.16.2 @@ -220,11 +222,11 @@ require ( golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 golang.org/x/net v0.17.0 golang.org/x/oauth2 v0.11.0 - golang.org/x/sync v0.3.0 + golang.org/x/sync v0.4.0 golang.org/x/sys v0.15.0 golang.org/x/term v0.15.0 golang.org/x/text v0.14.0 - golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 + golang.org/x/tools v0.14.0 google.golang.org/api v0.139.0 google.golang.org/grpc v1.58.3 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 @@ -301,6 +303,7 @@ require ( github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect + github.com/boltdb/bolt v1.3.1 // indirect github.com/boombuler/barcode v1.0.1 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect @@ -386,8 +389,6 @@ require ( github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect github.com/hashicorp/cronexpr v1.1.1 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-metrics v0.5.1 // indirect - github.com/hashicorp/go-msgpack/v2 v2.0.0 // indirect github.com/hashicorp/go-secure-stdlib/fileutil v0.1.0 // indirect github.com/hashicorp/go-secure-stdlib/plugincontainer v0.3.0 // indirect github.com/hashicorp/go-slug v0.12.1 // indirect @@ -515,7 +516,7 @@ require ( go.uber.org/multierr v1.7.0 // indirect go.uber.org/zap v1.19.1 // indirect golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect - golang.org/x/mod v0.12.0 // indirect + golang.org/x/mod v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 432f86ad06..7454c192a3 100644 --- a/go.sum +++ b/go.sum @@ -1232,6 +1232,7 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -2205,8 +2206,8 @@ github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4= -github.com/hashicorp/go-msgpack/v2 v2.0.0 h1:c1fiLq1LNghmLOry1ipGhvLDi+/zEoaEP2JrE1oFJ9s= -github.com/hashicorp/go-msgpack/v2 v2.0.0/go.mod h1:JIxYkkFJRDDRSoWQBSh7s9QAVThq+82iWmUpmE4jKak= +github.com/hashicorp/go-msgpack/v2 v2.1.1 h1:xQEY9yB2wnHitoSzk/B9UjXWRQ67QKu5AOm8aFp8N3I= +github.com/hashicorp/go-msgpack/v2 v2.1.1/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= @@ -2275,8 +2276,9 @@ github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= @@ -2304,16 +2306,17 @@ github.com/hashicorp/net-rpc-msgpackrpc/v2 v2.0.0/go.mod h1:6pdNz0vo0mF0GvhwDG56 github.com/hashicorp/nomad/api v0.0.0-20230519153805-2275a83cbfdf h1:cKXVf1UJqwdkGiTF3idqCOLApAql0310OSmJxeiaMWg= github.com/hashicorp/nomad/api v0.0.0-20230519153805-2275a83cbfdf/go.mod h1:rb38DqjaaIfhJRiLeCAGgIt+wV7o78rB+liyFE3mVzE= github.com/hashicorp/raft v1.0.1/go.mod h1:DVSAWItjLjTOkVbSpWQ0j0kUADIvDaCtBxIcbNAQLkI= -github.com/hashicorp/raft v1.1.0/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= github.com/hashicorp/raft v1.1.2-0.20191002163536-9c6bd3e3eb17/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= github.com/hashicorp/raft v1.2.0/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= -github.com/hashicorp/raft v1.3.10 h1:LR5QZX1VQd0DFWZfeCwWawyeKfpS/Tm1yjnJIY5X4Tw= -github.com/hashicorp/raft v1.3.10/go.mod h1:J8naEwc6XaaCfts7+28whSeRvCqTd6e20BlCU3LtEO4= +github.com/hashicorp/raft v1.6.0 h1:tkIAORZy2GbJ2Trp5eUSggLXDPOJLXC+JJLNMMqtgtM= +github.com/hashicorp/raft v1.6.0/go.mod h1:Xil5pDgeGwRWuX4uPUmwa+7Vagg4N804dz6mhNi6S7o= github.com/hashicorp/raft-autopilot v0.2.0 h1:2/R2RPgamgRKgNWGQioULZvjeKXQZmDuw5Ty+6c+H7Y= github.com/hashicorp/raft-autopilot v0.2.0/go.mod h1:q6tZ8UAZ5xio2gv2JvjgmtOlh80M6ic8xQYBe2Egkg8= github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk= -github.com/hashicorp/raft-boltdb/v2 v2.0.0-20210421194847-a7e34179d62c h1:oiKun9QlrOz5yQxMZJ3tf1kWtFYuKSJzxzEDxDPevj4= -github.com/hashicorp/raft-boltdb/v2 v2.0.0-20210421194847-a7e34179d62c/go.mod h1:kiPs9g148eLShc2TYagUAyKDnD+dH9U+CQKsXzlY9xo= +github.com/hashicorp/raft-boltdb v0.0.0-20230125174641-2a8082862702 h1:RLKEcCuKcZ+qp2VlaaZsYZfLOmIiuJNpEi48Rl8u9cQ= +github.com/hashicorp/raft-boltdb v0.0.0-20230125174641-2a8082862702/go.mod h1:nTakvJ4XYq45UXtn0DbwR4aU9ZdjlnIenpbs6Cd+FM0= +github.com/hashicorp/raft-boltdb/v2 v2.3.0 h1:fPpQR1iGEVYjZ2OELvUHX600VAK5qmdnDEv3eXOwZUA= +github.com/hashicorp/raft-boltdb/v2 v2.3.0/go.mod h1:YHukhB04ChJsLHLJEUD6vjFyLX2L3dsX3wPBZcX4tmc= github.com/hashicorp/raft-snapshot v1.0.4 h1:EuDuayAJPdiDmVk1ygTDnG2zDzrs0/6/yBuma1IYSow= github.com/hashicorp/raft-snapshot v1.0.4/go.mod h1:5sL9eUn72lH5DzsFIJ9jaysITbHksSSszImWSOTC8Ic= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= @@ -3551,8 +3554,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -3705,8 +3708,9 @@ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -3951,7 +3955,6 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190424220101-1e8e1cfdf96b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -4034,8 +4037,8 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/physical/raft/msgpack.go b/physical/raft/msgpack.go deleted file mode 100644 index b9b4f11048..0000000000 --- a/physical/raft/msgpack.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package raft - -// If we downgrade msgpack from v1.1.5 to v0.5.5, everything will still -// work, but any pre-existing raft clusters will break on upgrade. -// This file exists so that the Vault project has an explicit dependency -// on the library, which allows us to pin the version in go.mod. - -import ( - _ "github.com/hashicorp/go-msgpack/codec" -) diff --git a/physical/raft/raft.go b/physical/raft/raft.go index 0a46ff26ae..e937697777 100644 --- a/physical/raft/raft.go +++ b/physical/raft/raft.go @@ -431,8 +431,9 @@ func NewRaftBackend(conf map[string]string, logger log.Logger) (physical.Backend dbPath := filepath.Join(path, "raft.db") opts := etcdboltOptions(dbPath) raftOptions := raftboltdb.Options{ - Path: dbPath, - BoltOptions: opts, + Path: dbPath, + BoltOptions: opts, + MsgpackUseNewTimeFormat: true, } store, err := raftboltdb.New(raftOptions) if err != nil { @@ -997,11 +998,12 @@ func (b *RaftBackend) SetupCluster(ctx context.Context, opts SetupOpts) error { return err } transConfig := &raft.NetworkTransportConfig{ - Stream: streamLayer, - MaxPool: 3, - Timeout: 10 * time.Second, - ServerAddressProvider: b.serverAddressProvider, - Logger: b.logger.Named("raft-net"), + Stream: streamLayer, + MaxPool: 3, + Timeout: 10 * time.Second, + ServerAddressProvider: b.serverAddressProvider, + Logger: b.logger.Named("raft-net"), + MsgpackUseNewTimeFormat: true, } transport := raft.NewNetworkTransportWithConfig(transConfig) diff --git a/vault/cluster/cluster.go b/vault/cluster/cluster.go index 00967ac3c7..ed77acba1e 100644 --- a/vault/cluster/cluster.go +++ b/vault/cluster/cluster.go @@ -270,7 +270,8 @@ func (cl *Listener) TLSConfig(ctx context.Context) (*tls.Config, error) { } // Run starts the tcp listeners and will accept connections until stop is -// called. This function blocks so should be called in a goroutine. +// called. This function does not block and will start the listeners in +// separate goroutines. func (cl *Listener) Run(ctx context.Context) error { // Get our TLS config tlsConfig, err := cl.TLSConfig(ctx) From 7ffad66b0b43a0e42893167d275efb55347da2c5 Mon Sep 17 00:00:00 2001 From: prajnamohan1 <36304276+prajnamohan1@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:22:03 +0100 Subject: [PATCH 13/14] Fixed Oauth redirect not working on Android Chrome (#18513) * Fixed Oauth redirect not working on Android Chrome This fixes the issue described in https://github.com/hashicorp/vault/issues/16778. Navigation is blocked in Android chrome while redirecting back after OIDC authentication. The issue is explained by the lead maintainer of AppAuth(https://stackoverflow.com/a/41882732). The latest Chrome version redirects to the app only if triggered by the user and not automatically redirect. Hence, a link is added in the UI to redirect back to the app. * Update ui/app/templates/vault/cluster/oidc-provider.hbs Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com> * added requested changes * Modified requested changes and added changelog * Added requested change * Modified requested changes --------- Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com> --- changelog/18513.txt | 3 +++ ui/app/routes/vault/cluster/oidc-provider.js | 12 ++++++------ ui/app/templates/vault/cluster/oidc-provider.hbs | 4 +++- ui/tests/acceptance/oidc-provider-test.js | 9 ++++++--- 4 files changed, 18 insertions(+), 10 deletions(-) create mode 100644 changelog/18513.txt diff --git a/changelog/18513.txt b/changelog/18513.txt new file mode 100644 index 0000000000..6b3ca2fe48 --- /dev/null +++ b/changelog/18513.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: latest version of chrome does not automatically redirect back to the app after authentication unless triggered by the user, hence added a link to redirect back to the app. +``` \ No newline at end of file diff --git a/ui/app/routes/vault/cluster/oidc-provider.js b/ui/app/routes/vault/cluster/oidc-provider.js index d969c1e68e..a33a9b6ac6 100644 --- a/ui/app/routes/vault/cluster/oidc-provider.js +++ b/ui/app/routes/vault/cluster/oidc-provider.js @@ -94,17 +94,17 @@ export default class VaultClusterOidcProviderRoute extends Route { _handleSuccess(response, baseUrl, state) { const { code } = response; const redirectUrl = this._buildUrl(baseUrl, { code, state }); - if (Ember.testing) { - return { redirectUrl }; + if (!Ember.testing) { + this.win.location.replace(redirectUrl); } - this.win.location.replace(redirectUrl); + return { redirectUrl }; } _handleError(errorResp, baseUrl) { const redirectUrl = this._buildUrl(baseUrl, { ...errorResp }); - if (Ember.testing) { - return { redirectUrl }; + if (!Ember.testing) { + this.win.location.replace(redirectUrl); } - this.win.location.replace(redirectUrl); + return { redirectUrl }; } /** diff --git a/ui/app/templates/vault/cluster/oidc-provider.hbs b/ui/app/templates/vault/cluster/oidc-provider.hbs index 24f57426bd..e3fcd541a8 100644 --- a/ui/app/templates/vault/cluster/oidc-provider.hbs +++ b/ui/app/templates/vault/cluster/oidc-provider.hbs @@ -22,7 +22,9 @@ @onSuccess={{this._handleSuccess}} /> {{else if this.model.redirectUrl}} -
{{this.model.redirectUrl}}
+ +

If you are not automatically redirected, + click here to go back to app.

{{else}} {{/if}} diff --git a/ui/tests/acceptance/oidc-provider-test.js b/ui/tests/acceptance/oidc-provider-test.js index 8ef24aa24c..4446afccd9 100644 --- a/ui/tests/acceptance/oidc-provider-test.js +++ b/ui/tests/acceptance/oidc-provider-test.js @@ -163,10 +163,11 @@ module('Acceptance | oidc provider', function (hooks) { await authFormComponent.login(); await settled(); assert.strictEqual(currentURL(), url, 'URL is as expected after login'); - assert.dom('[data-test-oidc-redirect]').exists('redirect text exists'); assert .dom('[data-test-oidc-redirect]') - .hasTextContaining(`${callback}?code=`, 'Successful redirect to callback'); + .hasTextContaining(`click here to go back to app`, 'Shows link back to app'); + const link = document.querySelector('[data-test-oidc-redirect]').getAttribute('href'); + assert.ok(link.includes('/callback?code='), 'Redirects to correct url'); //* clean up test state await clearRecord(this.store, 'oidc/client', 'my-webapp'); @@ -191,7 +192,9 @@ module('Acceptance | oidc provider', function (hooks) { await settled(); assert .dom('[data-test-oidc-redirect]') - .hasTextContaining(`${callback}?code=`, 'Successful redirect to callback'); + .hasTextContaining(`click here to go back to app`, 'Shows link back to app'); + const link = document.querySelector('[data-test-oidc-redirect]').getAttribute('href'); + assert.ok(link.includes('/callback?code='), 'Redirects to correct url'); //* clean up test state await clearRecord(this.store, 'oidc/client', 'my-webapp'); From 9088f2b1b7f2b06d94950a81ae9d243d353d3830 Mon Sep 17 00:00:00 2001 From: Angel Garbarino Date: Mon, 8 Jan 2024 13:36:12 -0800 Subject: [PATCH 14/14] Prep ember data: relationships (#24712) * relationships, failing tests * remove old policies test to get tests passing * try adding back in --- ui/app/models/control-group.js | 4 ++-- ui/app/models/identity/entity-alias.js | 2 +- ui/app/models/identity/entity.js | 2 +- ui/app/models/identity/group.js | 2 +- ui/app/models/kmip/ca.js | 2 +- ui/app/models/kmip/config.js | 2 +- ui/app/models/mfa-login-enforcement.js | 6 +++--- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ui/app/models/control-group.js b/ui/app/models/control-group.js index be420d5dff..b84974feed 100644 --- a/ui/app/models/control-group.js +++ b/ui/app/models/control-group.js @@ -10,8 +10,8 @@ import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities'; export default Model.extend({ approved: attr('boolean'), requestPath: attr('string'), - requestEntity: belongsTo('identity/entity', { async: false }), - authorizations: hasMany('identity/entity', { async: false }), + requestEntity: belongsTo('identity/entity', { async: false, inverse: null }), + authorizations: hasMany('identity/entity', { async: false, inverse: null }), authorizePath: lazyCapabilities(apiPath`sys/control-group/authorize`), canAuthorize: alias('authorizePath.canUpdate'), diff --git a/ui/app/models/identity/entity-alias.js b/ui/app/models/identity/entity-alias.js index a29f160846..d6042175a7 100644 --- a/ui/app/models/identity/entity-alias.js +++ b/ui/app/models/identity/entity-alias.js @@ -14,7 +14,7 @@ export default IdentityModel.extend({ formFields: computed(function () { return ['name', 'mountAccessor']; }), - entity: belongsTo('identity/entity', { readOnly: true, async: false }), + entity: belongsTo('identity/entity', { readOnly: true, async: false, inverse: 'aliases' }), name: attr('string'), canonicalId: attr('string'), diff --git a/ui/app/models/identity/entity.js b/ui/app/models/identity/entity.js index 5622ea5a65..bb53324d81 100644 --- a/ui/app/models/identity/entity.js +++ b/ui/app/models/identity/entity.js @@ -34,7 +34,7 @@ export default IdentityModel.extend({ lastUpdateTime: attr('string', { readOnly: true, }), - aliases: hasMany('identity/entity-alias', { async: false, readOnly: true }), + aliases: hasMany('identity/entity-alias', { async: false, readOnly: true, inverse: 'entity' }), groupIds: attr({ readOnly: true, }), diff --git a/ui/app/models/identity/group.js b/ui/app/models/identity/group.js index 14a8c8c30d..d2be312b31 100644 --- a/ui/app/models/identity/group.js +++ b/ui/app/models/identity/group.js @@ -77,7 +77,7 @@ export default IdentityModel.extend({ ), policyPath: lazyCapabilities(apiPath`sys/policies`), canCreatePolicies: alias('policyPath.canCreate'), - alias: belongsTo('identity/group-alias', { async: false, readOnly: true }), + alias: belongsTo('identity/group-alias', { async: false, readOnly: true, inverse: 'group' }), updatePath: identityCapabilities(), canDelete: alias('updatePath.canDelete'), canEdit: alias('updatePath.canUpdate'), diff --git a/ui/app/models/kmip/ca.js b/ui/app/models/kmip/ca.js index aea85b4cf9..c468530c63 100644 --- a/ui/app/models/kmip/ca.js +++ b/ui/app/models/kmip/ca.js @@ -6,7 +6,7 @@ import Model, { belongsTo, attr } from '@ember-data/model'; export default Model.extend({ - config: belongsTo('kmip/config', { async: false }), + config: belongsTo('kmip/config', { async: false, inverse: 'ca' }), caPem: attr('string', { label: 'CA PEM', }), diff --git a/ui/app/models/kmip/config.js b/ui/app/models/kmip/config.js index 336cc3db7c..8a9fedbb2d 100644 --- a/ui/app/models/kmip/config.js +++ b/ui/app/models/kmip/config.js @@ -10,7 +10,7 @@ import fieldToAttrs from 'vault/utils/field-to-attrs'; export default Model.extend({ useOpenAPI: true, - ca: belongsTo('kmip/ca', { async: false }), + ca: belongsTo('kmip/ca', { async: false, inverse: 'config' }), getHelpUrl(path) { return `/v1/${path}/config?help=1`; }, diff --git a/ui/app/models/mfa-login-enforcement.js b/ui/app/models/mfa-login-enforcement.js index 69f307ea67..ac1791b7f7 100644 --- a/ui/app/models/mfa-login-enforcement.js +++ b/ui/app/models/mfa-login-enforcement.js @@ -37,12 +37,12 @@ const validations = { export default class MfaLoginEnforcementModel extends Model { @service store; @attr('string') name; - @hasMany('mfa-method') mfa_methods; + @hasMany('mfa-method', { async: true, inverse: null }) mfa_methods; @attr('string') namespace_id; @attr('array', { defaultValue: () => [] }) auth_method_accessors; // ["auth_approle_17a552c6"] @attr('array', { defaultValue: () => [] }) auth_method_types; // ["userpass"] - @hasMany('identity/entity') identity_entities; - @hasMany('identity/group') identity_groups; + @hasMany('identity/entity', { async: true, inverse: null }) identity_entities; + @hasMany('identity/group', { async: true, inverse: null }) identity_groups; get targets() { return ArrayProxy.extend(PromiseProxyMixin).create({