\\d{5})',
- encodeFormat: null,
- decodeFormats: {},
- };
- await render(hbs``);
-
- assert.dom('.box').doesNotExist('Form is hidden when not toggled');
- await click('[data-test-toggle-advanced]');
- await fillIn('[data-test-input="regex-test-val"]', '123-45-67890');
- [
- ['$1', '123'],
- ['$2', '45'],
- ['$3', '67890'],
- ['$last', '67890'],
- ].forEach(([p, v]) => {
- assert.dom(`[data-test-regex-group-position="${p}"]`).hasText(`${p}`, `Capture group ${p} renders`);
- assert.dom(`[data-test-regex-group-value="${p}"]`).hasText(v, `Capture group value ${v} renders`);
- });
- // need to simulate InputEvent
- await triggerEvent('[data-test-encode-format] input', 'input', { data: '$' });
- const options = this.element.querySelectorAll('.autocomplete-input-option');
- ['$1: 123', '$2: 45', '$3: 67890', '$last: 67890'].forEach((val, index) => {
- assert.dom(options[index]).hasText(val, 'Autocomplete option renders');
- });
-
- assert.dom('[data-test-kv-object-editor]').exists('KvObjectEditor renders for decode formats');
- assert.dom('[data-test-decode-format]').exists('AutocompleteInput renders for decode format value');
- await fillIn('[data-test-kv-key]', 'last');
- await fillIn('[data-test-decode-format] input', '$last');
- assert.deepEqual(this.model.decodeFormats, { last: '$last' }, 'Decode formats updates correctly');
- });
-});
diff --git a/ui/tests/integration/components/transform-edit-base-test.js b/ui/tests/integration/components/transform-edit-base-test.js
deleted file mode 100644
index bfa6818b2..000000000
--- a/ui/tests/integration/components/transform-edit-base-test.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { render } from '@ember/test-helpers';
-import hbs from 'htmlbars-inline-precompile';
-
-module('Integration | Component | transform-edit-base', function (hooks) {
- setupRenderingTest(hooks);
-
- test('it renders', async function (assert) {
- // Set any properties with this.set('myProperty', 'value');
- // Handle any actions with this.set('myAction', function(val) { ... });
-
- await render(hbs`{{transform-edit-base}}`);
-
- assert.dom(this.element).hasText('');
-
- // Template block usage:
- await render(hbs`
- {{#transform-edit-base}}
- template block text
- {{/transform-edit-base}}
- `);
-
- assert.dom(this.element).hasText('template block text');
- });
-});
diff --git a/ui/tests/integration/components/transform-list-item-test.js b/ui/tests/integration/components/transform-list-item-test.js
deleted file mode 100644
index b7da30c14..000000000
--- a/ui/tests/integration/components/transform-list-item-test.js
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import EmberObject from '@ember/object';
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { render, click } from '@ember/test-helpers';
-import hbs from 'htmlbars-inline-precompile';
-
-module('Integration | Component | transform-list-item', function (hooks) {
- setupRenderingTest(hooks);
-
- test('it renders un-clickable item if no read capability', async function (assert) {
- const item = EmberObject.create({
- id: 'foo',
- updatePath: {
- canRead: false,
- canDelete: true,
- canUpdate: true,
- },
- });
- this.set('itemPath', 'role/foo');
- this.set('itemType', 'role');
- this.set('item', item);
- await render(hbs``);
-
- assert.dom('[data-test-view-only-list-item]').exists('shows view only list item');
- assert.dom('[data-test-view-only-list-item]').hasText(item.id, 'has correct label');
- });
-
- test('it is clickable with details menu item if read capability', async function (assert) {
- const item = EmberObject.create({
- id: 'foo',
- updatePath: {
- canRead: true,
- canDelete: false,
- canUpdate: false,
- },
- });
- this.set('itemPath', 'template/foo');
- this.set('itemType', 'template');
- this.set('item', item);
- await render(hbs``);
-
- assert.dom('[data-test-secret-link="template/foo"]').exists('shows clickable list item');
- await click('button.popup-menu-trigger');
- assert.dom('.popup-menu-content li').exists({ count: 1 }, 'has one option');
- });
-
- test('it has details and edit menu item if read & edit capabilities', async function (assert) {
- const item = EmberObject.create({
- id: 'foo',
- updatePath: {
- canRead: true,
- canDelete: true,
- canUpdate: true,
- },
- });
- this.set('itemPath', 'alphabet/foo');
- this.set('itemType', 'alphabet');
- this.set('item', item);
- await render(hbs``);
-
- assert.dom('[data-test-secret-link="alphabet/foo"]').exists('shows clickable list item');
- await click('button.popup-menu-trigger');
- assert.dom('.popup-menu-content li').exists({ count: 2 }, 'has both options');
- });
-
- test('it is not clickable if built-in template with all capabilities', async function (assert) {
- const item = EmberObject.create({
- id: 'builtin/foo',
- updatePath: {
- canRead: true,
- canDelete: true,
- canUpdate: true,
- },
- });
- this.set('itemPath', 'template/builtin/foo');
- this.set('itemType', 'template');
- this.set('item', item);
- await render(hbs``);
-
- assert.dom('[data-test-view-only-list-item]').exists('shows view only list item');
- assert.dom('[data-test-view-only-list-item]').hasText(item.id, 'has correct label');
- });
-
- test('it is not clickable if built-in alphabet', async function (assert) {
- const item = EmberObject.create({
- id: 'builtin/foo',
- updatePath: {
- canRead: true,
- canDelete: true,
- canUpdate: true,
- },
- });
- this.set('itemPath', 'alphabet/builtin/foo');
- this.set('itemType', 'alphabet');
- this.set('item', item);
- await render(hbs``);
-
- assert.dom('[data-test-view-only-list-item]').exists('shows view only list item');
- assert.dom('[data-test-view-only-list-item]').hasText(item.id, 'has correct label');
- });
-});
diff --git a/ui/tests/integration/components/transform-role-edit-test.js b/ui/tests/integration/components/transform-role-edit-test.js
deleted file mode 100644
index b77dd454e..000000000
--- a/ui/tests/integration/components/transform-role-edit-test.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, skip } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { render } from '@ember/test-helpers';
-import hbs from 'htmlbars-inline-precompile';
-
-module('Integration | Component | transform-role-edit', function (hooks) {
- setupRenderingTest(hooks);
-
- skip('it renders', async function (assert) {
- // TODO: Fill out these tests, merging without to unblock other work
-
- await render(hbs`{{transform-role-edit}}`);
-
- assert.dom(this.element).hasText('');
-
- // Template block usage:
- await render(hbs`
- {{#transform-role-edit}}
- template block text
- {{/transform-role-edit}}
- `);
-
- assert.dom(this.element).hasText('template block text');
- });
-});
diff --git a/ui/tests/integration/components/transit-edit-test.js b/ui/tests/integration/components/transit-edit-test.js
deleted file mode 100644
index f44f2e116..000000000
--- a/ui/tests/integration/components/transit-edit-test.js
+++ /dev/null
@@ -1,110 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'vault/tests/helpers';
-import { click, fillIn, render } from '@ember/test-helpers';
-import { hbs } from 'ember-cli-htmlbars';
-
-const SELECTORS = {
- createForm: '[data-test-transit-create-form]',
- editForm: '[data-test-transit-edit-form]',
- ttlToggle: '[data-test-ttl-toggle="Auto-rotation period"]',
- ttlValue: '[data-test-ttl-value="Auto-rotation period"]',
-};
-module('Integration | Component | transit-edit', function (hooks) {
- setupRenderingTest(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.model = this.store.createRecord('transit-key');
- this.backendCrumb = {
- label: 'transit',
- text: 'transit',
- path: 'vault.cluster.secrets.backend.list-root',
- model: 'transit',
- };
- });
-
- test('it renders in create mode and updates model', async function (assert) {
- await render(hbs`
- `);
-
- assert.dom(SELECTORS.createForm).exists();
- assert.dom(SELECTORS.ttlToggle).isNotChecked();
-
- // confirm model params update when ttl changes
- assert.strictEqual(this.model.autoRotatePeriod, '0');
- await click(SELECTORS.ttlToggle);
-
- assert.dom(SELECTORS.ttlValue).hasValue('30'); // 30 days
- assert.strictEqual(this.model.autoRotatePeriod, '720h');
-
- await fillIn(SELECTORS.ttlValue, '10'); // 10 days
- assert.strictEqual(this.model.autoRotatePeriod, '240h');
- });
-
- test('it renders edit form correctly when key has autoRotatePeriod=0', async function (assert) {
- this.model.autoRotatePeriod = 0;
- this.model.keys = {
- 1: 1684882652000,
- };
- await render(hbs`
- `);
-
- assert.dom(SELECTORS.editForm).exists();
- assert.dom(SELECTORS.ttlToggle).isNotChecked();
-
- assert.strictEqual(this.model.autoRotatePeriod, 0);
-
- await click(SELECTORS.ttlToggle);
- assert.dom(SELECTORS.ttlToggle).isChecked();
- assert.dom(SELECTORS.ttlValue).hasValue('30');
- assert.strictEqual(this.model.autoRotatePeriod, '720h', 'model value changes with toggle');
-
- await click(SELECTORS.ttlToggle);
- assert.strictEqual(this.model.autoRotatePeriod, 0); // reverts to original value when toggled back on
- });
-
- test('it renders edit form correctly when key has non-zero rotation period', async function (assert) {
- this.model.autoRotatePeriod = '5h';
- this.model.keys = {
- 1: 1684882652000,
- };
- await render(hbs`
- `);
-
- assert.dom(SELECTORS.editForm).exists();
- assert.dom(SELECTORS.ttlToggle).isChecked();
-
- await click(SELECTORS.ttlToggle);
- assert.dom(SELECTORS.ttlToggle).isNotChecked();
- assert.strictEqual(this.model.autoRotatePeriod, 0, 'model value changes back to 0 when toggled off');
-
- await click(SELECTORS.ttlToggle);
- assert.strictEqual(this.model.autoRotatePeriod, '5h'); // reverts to original value when toggled back on
-
- await fillIn(SELECTORS.ttlValue, '10');
- assert.strictEqual(this.model.autoRotatePeriod, '10h');
- });
-});
diff --git a/ui/tests/integration/components/transit-key-actions-test.js b/ui/tests/integration/components/transit-key-actions-test.js
deleted file mode 100644
index 4c82345cf..000000000
--- a/ui/tests/integration/components/transit-key-actions-test.js
+++ /dev/null
@@ -1,347 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { run } from '@ember/runloop';
-import { resolve } from 'rsvp';
-import { assign } from '@ember/polyfills';
-import Service from '@ember/service';
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { render, click, find, findAll, fillIn, blur, triggerEvent } from '@ember/test-helpers';
-import hbs from 'htmlbars-inline-precompile';
-import { encodeString } from 'vault/utils/b64';
-import waitForError from 'vault/tests/helpers/wait-for-error';
-
-const storeStub = Service.extend({
- callArgs: null,
- keyActionReturnVal: null,
- rootKeyActionReturnVal: null,
- adapterFor() {
- const self = this;
- return {
- keyAction(action, { backend, id, payload }, options) {
- self.set('callArgs', { action, backend, id, payload });
- self.set('callArgsOptions', options);
- const rootResp = assign({}, self.get('rootKeyActionReturnVal'));
- const resp =
- Object.keys(rootResp).length > 0
- ? rootResp
- : {
- data: assign({}, self.get('keyActionReturnVal')),
- };
- return resolve(resp);
- },
- };
- },
-});
-
-module('Integration | Component | transit key actions', function (hooks) {
- setupRenderingTest(hooks);
-
- hooks.beforeEach(function () {
- run(() => {
- this.owner.unregister('service:store');
- this.owner.register('service:store', storeStub);
- this.storeService = this.owner.lookup('service:store');
- });
- });
-
- test('it requires `key`', async function (assert) {
- const promise = waitForError();
- render(hbs`
- {{transit-key-actions}}
-
- `);
- const err = await promise;
- assert.ok(err.message.includes('`key` is required for'), 'asserts without key');
- });
-
- test('it renders', async function (assert) {
- this.set('key', { backend: 'transit', supportedActions: ['encrypt'] });
- await render(hbs`
- {{transit-key-actions selectedAction="encrypt" key=this.key}}
-
- `);
- assert.dom('[data-test-transit-action="encrypt"]').exists({ count: 1 }, 'renders encrypt');
-
- this.set('key', { backend: 'transit', supportedActions: ['sign'] });
- await render(hbs`
- {{transit-key-actions selectedAction="sign" key=this.key}}
- `);
- assert.dom('[data-test-transit-action="sign"]').exists({ count: 1 }, 'renders sign');
- });
-
- test('it renders: signature_algorithm field', async function (assert) {
- this.set('key', { backend: 'transit', supportsSigning: true, supportedActions: ['sign', 'verify'] });
- this.set('selectedAction', 'sign');
- await render(hbs`
- {{transit-key-actions selectedAction=this.selectedAction key=this.key}}
-
- `);
- assert
- .dom('[data-test-signature-algorithm]')
- .doesNotExist('does not render signature_algorithm field on sign');
- this.set('selectedAction', 'verify');
- assert
- .dom('[data-test-signature-algorithm]')
- .doesNotExist('does not render signature_algorithm field on verify');
-
- this.set('selectedAction', 'sign');
- this.set('key', {
- type: 'rsa-2048',
- supportsSigning: true,
- backend: 'transit',
- supportedActions: ['sign', 'verify'],
- });
- assert
- .dom('[data-test-signature-algorithm]')
- .exists({ count: 1 }, 'renders signature_algorithm field on sign with rsa key');
- this.set('selectedAction', 'verify');
- assert
- .dom('[data-test-signature-algorithm]')
- .exists({ count: 1 }, 'renders signature_algorithm field on verify with rsa key');
- });
-
- test('it renders: rotate', async function (assert) {
- this.set('key', { backend: 'transit', id: 'akey', supportedActions: ['rotate'] });
- await render(hbs`
- {{transit-key-actions selectedAction="rotate" key=this.key}}
-
- `);
-
- assert.dom('*').hasText('', 'renders an empty div');
-
- this.set('key.canRotate', true);
- assert
- .dom('button')
- .hasText('Rotate encryption key', 'renders confirm-button when key.canRotate is true');
- });
-
- async function doEncrypt(assert, actions = [], keyattrs = {}) {
- const keyDefaults = { backend: 'transit', id: 'akey', supportedActions: ['encrypt'].concat(actions) };
-
- const key = assign({}, keyDefaults, keyattrs);
- this.set('key', key);
- this.set('selectedAction', 'encrypt');
- this.set('storeService.keyActionReturnVal', { ciphertext: 'secret' });
- await render(hbs`
- {{transit-key-actions selectedAction=this.selectedAction key=this.key}}
-
- `);
-
- find('#plaintext-control .CodeMirror').CodeMirror.setValue('plaintext');
- await click('button[type="submit"]');
- assert.deepEqual(
- this.storeService.callArgs,
- {
- action: 'encrypt',
- backend: 'transit',
- id: 'akey',
- payload: {
- plaintext: encodeString('plaintext'),
- },
- },
- 'passes expected args to the adapter'
- );
-
- assert.strictEqual(find('[data-test-encrypted-value="ciphertext"]').innerText, 'secret');
-
- // exit modal
- await click('[data-test-modal-background]');
- // Encrypt again, with pre-encoded value and checkbox selected
- const preEncodedValue = encodeString('plaintext');
- find('#plaintext-control .CodeMirror').CodeMirror.setValue(preEncodedValue);
- await click('input[data-test-transit-input="encodedBase64"]');
- await click('button[type="submit"]');
-
- assert.deepEqual(
- this.storeService.callArgs,
- {
- action: 'encrypt',
- backend: 'transit',
- id: 'akey',
- payload: {
- plaintext: preEncodedValue,
- },
- },
- 'passes expected args to the adapter'
- );
- }
-
- test('it encrypts', doEncrypt);
-
- test('it shows key version selection', async function (assert) {
- const keyDefaults = { backend: 'transit', id: 'akey', supportedActions: ['encrypt'].concat([]) };
- const keyattrs = { keysForEncryption: [3, 2, 1], latestVersion: 3 };
- const key = assign({}, keyDefaults, keyattrs);
- this.set('key', key);
- this.set('storeService.keyActionReturnVal', { ciphertext: 'secret' });
- await render(hbs`
- {{transit-key-actions selectedAction="encrypt" key=this.key}}
-
- `);
-
- findAll('.CodeMirror')[0].CodeMirror.setValue('plaintext');
- assert.dom('#key_version').exists({ count: 1 }, 'it renders the key version selector');
-
- await triggerEvent('#key_version', 'change');
- await click('button[type="submit"]');
- assert.deepEqual(
- this.storeService.callArgs,
- {
- action: 'encrypt',
- backend: 'transit',
- id: 'akey',
- payload: {
- plaintext: encodeString('plaintext'),
- key_version: '0',
- },
- },
- 'includes key_version in the payload'
- );
- });
-
- test('it hides key version selection', async function (assert) {
- const keyDefaults = { backend: 'transit', id: 'akey', supportedActions: ['encrypt'].concat([]) };
- const keyattrs = { keysForEncryption: [1] };
- const key = assign({}, keyDefaults, keyattrs);
- this.set('key', key);
- this.set('storeService.keyActionReturnVal', { ciphertext: 'secret' });
- await render(hbs`
- {{transit-key-actions selectedAction="encrypt" key=this.key}}
-
- `);
-
- // await fillIn('#plaintext', 'plaintext');
- find('#plaintext-control .CodeMirror').CodeMirror.setValue('plaintext');
- assert.dom('#key_version').doesNotExist('it does not render the selector when there is only one key');
- });
-
- test('it does not carry ciphertext value over to decrypt', async function (assert) {
- assert.expect(4);
- const plaintext = 'not so secret';
- await doEncrypt.call(this, assert, ['decrypt']);
-
- this.set('storeService.keyActionReturnVal', { plaintext });
- this.set('selectedAction', 'decrypt');
- assert.strictEqual(
- find('#ciphertext-control .CodeMirror').CodeMirror.getValue(),
- '',
- 'does not prefill ciphertext value'
- );
- });
-
- const setupExport = async function () {
- this.set('key', {
- backend: 'transit',
- id: 'akey',
- supportedActions: ['export'],
- exportKeyTypes: ['encryption'],
- validKeyVersions: [1],
- });
- await render(hbs`
- {{transit-key-actions key=this.key}}
-
- `);
- };
-
- test('it can export a key:default behavior', async function (assert) {
- this.set('storeService.rootKeyActionReturnVal', { wrap_info: { token: 'wrapped-token' } });
- await setupExport.call(this);
- await click('button[type="submit"]');
-
- assert.deepEqual(
- this.storeService.callArgs,
- {
- action: 'export',
- backend: 'transit',
- id: 'akey',
- payload: {
- param: ['encryption'],
- },
- },
- 'passes expected args to the adapter'
- );
- assert.strictEqual(this.storeService.callArgsOptions.wrapTTL, '30m', 'passes value for wrapTTL');
- assert.strictEqual(
- find('[data-test-encrypted-value="export"]').innerText,
- 'wrapped-token',
- 'wraps by default'
- );
- });
-
- test('it can export a key:unwrapped behavior', async function (assert) {
- const response = { keys: { a: 'key' } };
- this.set('storeService.keyActionReturnVal', response);
- await setupExport.call(this);
- await click('[data-test-toggle-label="Wrap response"]');
- await click('button[type="submit"]');
- assert.dom('.modal.is-active').exists('Modal opens after export');
- assert.deepEqual(
- find('.modal [data-test-encrypted-value="export"]').innerText,
- JSON.stringify(response, null, 2),
- 'prints json response'
- );
- });
-
- test('it can export a key: unwrapped, single version', async function (assert) {
- const response = { keys: { a: 'key' } };
- this.set('storeService.keyActionReturnVal', response);
- await setupExport.call(this);
- await click('[data-test-toggle-label="Wrap response"]');
- await click('#exportVersion');
- await triggerEvent('#exportVersion', 'change');
- await click('button[type="submit"]');
- assert.dom('.modal.is-active').exists('Modal opens after export');
- assert.deepEqual(
- find('.modal [data-test-encrypted-value="export"]').innerText,
- JSON.stringify(response, null, 2),
- 'prints json response'
- );
- assert.deepEqual(
- this.storeService.callArgs,
- {
- action: 'export',
- backend: 'transit',
- id: 'akey',
- payload: {
- param: ['encryption', 1],
- },
- },
- 'passes expected args to the adapter'
- );
- });
-
- test('it includes algorithm param for HMAC', async function (assert) {
- this.set('key', {
- backend: 'transit',
- id: 'akey',
- supportedActions: ['hmac'],
- validKeyVersions: [1],
- });
- await render(hbs`
- {{transit-key-actions key=this.key}}
-
- `);
- await fillIn('#algorithm', 'sha2-384');
- await blur('#algorithm');
- await fillIn('[data-test-component="code-mirror-modifier"] textarea', 'plaintext');
- await click('input[data-test-transit-input="encodedBase64"]');
- await click('button[type="submit"]');
- assert.deepEqual(
- this.storeService.callArgs,
- {
- action: 'hmac',
- backend: 'transit',
- id: 'akey',
- payload: {
- algorithm: 'sha2-384',
- input: 'plaintext',
- },
- },
- 'passes expected args to the adapter'
- );
- });
-});
diff --git a/ui/tests/integration/components/ttl-picker-test.js b/ui/tests/integration/components/ttl-picker-test.js
deleted file mode 100644
index 1e92cb82f..000000000
--- a/ui/tests/integration/components/ttl-picker-test.js
+++ /dev/null
@@ -1,395 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { render, click, fillIn } from '@ember/test-helpers';
-import sinon from 'sinon';
-import hbs from 'htmlbars-inline-precompile';
-import selectors from 'vault/tests/helpers/components/ttl-picker';
-
-module('Integration | Component | ttl-picker', function (hooks) {
- setupRenderingTest(hooks);
-
- hooks.beforeEach(function () {
- this.set('onChange', sinon.spy());
- this.set('label', 'Foobar');
- });
-
- module('without toggle', function (hooks) {
- hooks.beforeEach(function () {
- this.set('hideToggle', true);
- });
-
- test('it shows correct time and value when no initialValue set', async function (assert) {
- await render(hbs``);
- assert.dom(selectors.ttlFormGroup).exists('TTL Form fields exist');
- assert.dom(selectors.ttlValue).hasValue('');
- assert.dom(selectors.ttlUnit).hasValue('s');
- });
-
- test('it calls the change fn with the correct values', async function (assert) {
- const changeSpy = sinon.spy();
- this.set('onChange', changeSpy);
- await render(hbs`
-
- `);
- assert.dom(selectors.ttlUnit).hasValue('m', 'unit value shows m (minutes)');
- await fillIn(selectors.ttlValue, '10');
- await assert.ok(changeSpy.calledOnce, 'it calls the passed onChange');
- assert.ok(
- changeSpy.calledWithExactly({
- enabled: true,
- seconds: 600,
- timeString: '10m',
- goSafeTimeString: '10m',
- }),
- 'Passes the values back to onChange'
- );
- });
-
- test('it correctly shows initial time and unit', async function (assert) {
- await render(hbs`
-
- `);
-
- assert.dom(selectors.ttlUnit).hasValue('h', 'unit value initially shows as h (hours)');
- assert.dom(selectors.ttlValue).hasValue('3', 'time value initially shows as 3');
- });
-
- test('it fails gracefully when initialValue is not parseable', async function (assert) {
- const changeSpy = sinon.spy();
- this.set('onChange', changeSpy);
- await render(hbs`
-
- `);
-
- assert.dom(selectors.ttlValue).hasValue('', 'time value initially shows as empty');
- assert.dom(selectors.ttlUnit).hasValue('s', 'unit value initially shows as s (seconds)');
- assert.ok(changeSpy.notCalled, 'onChange is not called on init');
- });
-
- test('it recalculates time when unit is changed', async function (assert) {
- const changeSpy = sinon.spy();
- this.set('onChange', changeSpy);
- await render(hbs`
-
- `);
-
- assert.dom(selectors.ttlUnit).hasValue('h', 'unit value initially shows as h (hours)');
- assert.dom(selectors.ttlValue).hasValue('1', 'time value initially shows as 1');
- await fillIn(selectors.ttlUnit, 'm');
- assert.dom(selectors.ttlUnit).hasValue('m', 'unit value changed to m (minutes)');
- assert.dom(selectors.ttlValue).hasValue('60', 'time value recalculates to fit unit');
- assert.ok(
- changeSpy.calledWithExactly({
- enabled: true,
- seconds: 3600,
- timeString: '60m',
- goSafeTimeString: '60m',
- }),
- 'Passes the values back to onChange'
- );
- });
-
- test('it skips recalculating time when unit is changed if time is not whole number', async function (assert) {
- const changeSpy = sinon.spy();
- this.set('onChange', changeSpy);
- await render(hbs`
-
- `);
-
- assert.dom(selectors.ttlUnit).hasValue('s', 'unit value starts as s (seconds)');
- assert.dom(selectors.ttlValue).hasValue('30', 'time value starts as 30');
- await fillIn(selectors.ttlUnit, 'm');
- assert.dom(selectors.ttlUnit).hasValue('m', 'unit value changed to m (minutes)');
- assert.dom(selectors.ttlValue).hasValue('30', 'time value is still 30');
- assert.ok(
- changeSpy.calledWithExactly({
- enabled: true,
- seconds: 1800,
- timeString: '30m',
- goSafeTimeString: '30m',
- }),
- 'Passes the values back to onChange'
- );
- });
-
- test('it calls onChange on init when changeOnInit is true', async function (assert) {
- const changeSpy = sinon.spy();
- this.set('onChange', changeSpy);
- await render(hbs`
-
- `);
-
- assert.ok(changeSpy.calledOnce, 'it calls the passed onChange when rendered');
- assert.ok(
- changeSpy.calledWithExactly({
- enabled: true,
- seconds: 600,
- timeString: '10m',
- goSafeTimeString: '10m',
- }),
- 'Passes the values back to onChange'
- );
- });
-
- test('it shows a label when passed', async function (assert) {
- this.set('label', 'My Label');
- await render(hbs`
-
- `);
-
- assert.dom('[data-test-ttl-form-label]').hasText('My Label', 'Renders label correctly');
- assert.dom('[data-test-ttl-form-subtext]').doesNotExist('Subtext not rendered');
- assert.dom('[data-test-tooltip-trigger]').doesNotExist('Description tooltip not rendered');
- });
-
- test('it shows subtext and description when passed', async function (assert) {
- this.set('label', 'My Label');
- await render(hbs`
-
- `);
-
- assert.dom('[data-test-ttl-form-label]').hasText('My Label', 'Renders label correctly');
- assert.dom('[data-test-ttl-form-subtext]').hasText('Subtext', 'Renders subtext when present');
- assert
- .dom('[data-test-tooltip-trigger]')
- .exists({ count: 1 }, 'Description tooltip icon shows when description present');
- });
-
- test('it yields in place of label if block is present', async function (assert) {
- this.set('label', 'My Label');
- await render(hbs`
-
-
-
- `);
-
- assert.dom('[data-test-custom]').hasText('Different Label', 'custom block is rendered');
- assert.dom('[data-test-ttl-form-label]').doesNotExist('Label not rendered');
- });
- });
-
- module('with toggle', function () {
- test('it has toggle off by default', async function (assert) {
- await render(hbs`
-
- `);
- assert.dom(selectors.toggle).isNotChecked('Toggle is unchecked by default');
- assert.dom(selectors.ttlFormGroup).doesNotExist('TTL Form is not rendered');
- });
-
- test('it shows time and unit inputs when initialEnabled', async function (assert) {
- const changeSpy = sinon.spy();
- this.set('onChange', changeSpy);
- await render(hbs`
-
- `);
- assert.dom(selectors.toggle).isChecked('Toggle is checked when initialEnabled is true');
- assert.dom(selectors.ttlFormGroup).exists('TTL Form is rendered');
- assert.ok(changeSpy.notCalled, 'onChange not called because initialValue not parsed');
- });
-
- test('it sets initial value to initialValue', async function (assert) {
- await render(hbs`
-
- `);
- assert.dom(selectors.ttlValue).hasValue('2', 'time value is 2');
- assert.dom(selectors.ttlUnit).hasValue('h', 'unit is hours');
- assert.ok(
- this.onChange.notCalled,
- 'it does not call onChange after render when changeOnInit is not set'
- );
- });
-
- test('it passes the appropriate data to onChange when toggled on', async function (assert) {
- const changeSpy = sinon.spy();
- this.set('onChange', changeSpy);
- await render(hbs`
-
- `);
- await click(selectors.toggle);
- assert.ok(changeSpy.calledOnce, 'it calls the passed onChange');
- assert.ok(
- changeSpy.calledWith({
- enabled: true,
- seconds: 600,
- timeString: '10m',
- goSafeTimeString: '10m',
- }),
- 'Passes the values back to onChange'
- );
- });
-
- test('inputs reflect initial value when toggled on', async function (assert) {
- await render(hbs`
-
- `);
- assert.dom(selectors.toggle).isNotChecked('Toggle is off');
- assert.dom(selectors.ttlFormGroup).doesNotExist('TTL Form not shown on mount');
- await click(selectors.toggle);
- assert.dom(selectors.ttlValue).hasValue('100', 'time after toggle is 100');
- assert.dom(selectors.ttlUnit).hasValue('m', 'Unit is minutes after toggle');
- });
-
- test('it is enabled on init if initialEnabled is true', async function (assert) {
- await render(hbs`
-
- `);
- assert.dom(selectors.toggle).isChecked('Toggle is on');
- assert.dom(selectors.ttlFormGroup).exists();
- assert.dom(selectors.ttlValue).hasValue('100', 'time is shown on mount');
- assert.dom(selectors.ttlUnit).hasValue('m', 'Unit is shown on mount');
- await click(selectors.toggle);
- assert.dom(selectors.toggle).isNotChecked('Toggle is off');
- assert.dom(selectors.ttlFormGroup).doesNotExist('TTL Form no longer shows after toggle');
- });
-
- test('it is enabled on init if initialEnabled evals to truthy', async function (assert) {
- await render(hbs`
-
- `);
- assert.dom(selectors.toggle).isChecked('Toggle is enabled');
- assert.dom(selectors.ttlValue).hasValue('100', 'time value is shown on mount');
- assert.dom(selectors.ttlUnit).hasValue('m', 'Unit matches what is passed in');
- });
-
- test('it converts days to go safe time', async function (assert) {
- await render(hbs`
-
- `);
- await click(selectors.toggle);
- assert.ok(this.onChange.calledOnce, 'it calls the passed onChange');
- assert.ok(
- this.onChange.calledWith({
- enabled: true,
- seconds: 172800,
- timeString: '2d',
- goSafeTimeString: '48h',
- }),
- 'Converts day unit to go safe time'
- );
- });
-
- test('it converts to the largest round unit on init', async function (assert) {
- await render(hbs`
-
- `);
- assert.dom(selectors.ttlValue).hasValue('1000', 'time value is converted');
- assert.dom(selectors.ttlUnit).hasValue('m', 'unit value is m (minutes)');
- });
-
- test('it converts to the largest round unit on init when no unit provided', async function (assert) {
- await render(hbs`
-
- `);
- assert.dom(selectors.ttlValue).hasValue('1', 'time value is converted');
- assert.dom(selectors.ttlUnit).hasValue('d', 'unit value is d (days)');
- });
- });
-});
diff --git a/ui/tests/integration/components/upgrade-page-test.js b/ui/tests/integration/components/upgrade-page-test.js
deleted file mode 100644
index 7f7bc408d..000000000
--- a/ui/tests/integration/components/upgrade-page-test.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { render } from '@ember/test-helpers';
-import hbs from 'htmlbars-inline-precompile';
-
-module('Integration | Component | upgrade page', function (hooks) {
- setupRenderingTest(hooks);
-
- test('it renders with defaults', async function (assert) {
- await render(hbs`
- {{upgrade-page}}
-
- `);
-
- assert.dom('.page-header .title').hasText('Vault Enterprise', 'renders default page title');
- assert
- .dom('[data-test-empty-state-title]')
- .hasText('Upgrade to use this feature', 'renders default title');
- assert
- .dom('[data-test-empty-state-message]')
- .hasText(
- 'You will need Vault Enterprise with this feature included to use this feature.',
- 'renders default message'
- );
- assert.dom('[data-test-upgrade-link]').exists({ count: 1 }, 'renders upgrade link');
- });
-
- test('it renders with custom attributes', async function (assert) {
- await render(hbs`
- {{upgrade-page title="Test Feature Title" minimumEdition="Vault Enterprise Premium"}}
-
- `);
-
- assert.dom('.page-header .title').hasText('Test Feature Title', 'renders custom page title');
- assert
- .dom('[data-test-empty-state-title]')
- .hasText('Upgrade to use Test Feature Title', 'renders custom title');
- assert
- .dom('[data-test-empty-state-message]')
- .hasText(
- 'You will need Vault Enterprise Premium with Test Feature Title included to use this feature.',
- 'renders custom message'
- );
- });
-});
diff --git a/ui/tests/integration/components/wrap-ttl-test.js b/ui/tests/integration/components/wrap-ttl-test.js
deleted file mode 100644
index 9d0e8c509..000000000
--- a/ui/tests/integration/components/wrap-ttl-test.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import Sinon from 'sinon';
-import { setupRenderingTest } from 'ember-qunit';
-import { render, click, fillIn } from '@ember/test-helpers';
-import hbs from 'htmlbars-inline-precompile';
-import waitForError from 'vault/tests/helpers/wait-for-error';
-
-module('Integration | Component | wrap ttl', function (hooks) {
- setupRenderingTest(hooks);
-
- test('it requires `onChange`', async function (assert) {
- const promise = waitForError();
- render(hbs``);
- const err = await promise;
- assert.ok(err.message.includes('`onChange` handler is a required attr in'), 'asserts without onChange');
- });
-
- test('it renders', async function (assert) {
- const changeSpy = Sinon.spy();
- this.set('onChange', changeSpy);
- await render(hbs``);
- assert.ok(changeSpy.calledWithExactly('30m'), 'calls onChange with 30m default on render');
- assert.dom('[data-test-ttl-form-label]').hasText('Wrap response');
- });
-
- test('it nulls out value when you uncheck wrapResponse', async function (assert) {
- const changeSpy = Sinon.spy();
- this.set('onChange', changeSpy);
- await render(hbs``);
- await click('[data-test-ttl-form-label]');
- assert.ok(changeSpy.calledWithExactly(null), 'calls onChange with null');
- });
-
- test('it sends value changes to onChange handler', async function (assert) {
- const changeSpy = Sinon.spy();
- this.set('onChange', changeSpy);
- await render(hbs``);
- // for testing purposes we need to input unit first because it keeps seconds value
- await fillIn('[data-test-select="ttl-unit"]', 'h');
- assert.ok(changeSpy.calledWithExactly('30h'), 'calls onChange correctly on time input');
- await fillIn('[data-test-ttl-value]', '20');
- assert.ok(changeSpy.calledWithExactly('20h'), 'calls onChange correctly on unit change');
- });
-});
diff --git a/ui/tests/integration/helpers/add-to-array-test.js b/ui/tests/integration/helpers/add-to-array-test.js
deleted file mode 100644
index 3bce33fee..000000000
--- a/ui/tests/integration/helpers/add-to-array-test.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { addToArray } from '../../../helpers/add-to-array';
-
-module('Integration | Helper | add-to-array', function (hooks) {
- setupRenderingTest(hooks);
-
- test('it correctly adds a value to an array without mutating the original', function (assert) {
- const ARRAY = ['horse', 'cow', 'chicken'];
- const result = addToArray([ARRAY, 'pig']);
- assert.deepEqual(result, [...ARRAY, 'pig'], 'Result has additional item');
- assert.deepEqual(ARRAY, ['horse', 'cow', 'chicken'], 'original array is not mutated');
- });
-
- test('it fails if the first value is not an array', function (assert) {
- let result;
- try {
- result = addToArray(['not-array', 'string']);
- } catch (e) {
- result = e.message;
- }
- assert.strictEqual(result, 'Assertion Failed: Value provided is not an array');
- });
-
- test('it works with non-string arrays', function (assert) {
- const ARRAY = ['five', 6, '7'];
- const result = addToArray([ARRAY, 10]);
- assert.deepEqual(result, ['five', 6, '7', 10], 'added number value');
- });
-
- test('it de-dupes the result', function (assert) {
- const ARRAY = ['horse', 'cow', 'chicken'];
- const result = addToArray([ARRAY, 'horse']);
- assert.deepEqual(result, ['horse', 'cow', 'chicken']);
- });
-});
diff --git a/ui/tests/integration/helpers/changelog-url-for-test.js b/ui/tests/integration/helpers/changelog-url-for-test.js
deleted file mode 100644
index 2e43c29a7..000000000
--- a/ui/tests/integration/helpers/changelog-url-for-test.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { changelogUrlFor } from '../../../helpers/changelog-url-for';
-
-const CHANGELOG_URL = 'https://www.github.com/hashicorp/vault/blob/main/CHANGELOG.md#';
-
-module('Integration | Helper | changelog-url-for', function (hooks) {
- setupRenderingTest(hooks);
-
- test('it builds an enterprise URL', function (assert) {
- const result = changelogUrlFor(['1.5.0+prem']);
- assert.strictEqual(result, CHANGELOG_URL.concat('150'));
- });
-
- test('it builds an OSS URL', function (assert) {
- const result = changelogUrlFor(['1.4.3']);
- assert.strictEqual(result, CHANGELOG_URL.concat('143'));
- });
-
- test('it returns the base changelog URL if the version is less than 1.4.3', function (assert) {
- const result = changelogUrlFor(['1.4.0']);
- assert.strictEqual(result, CHANGELOG_URL);
- });
-
- test('it returns the base changelog URL if version cannot be found', function (assert) {
- const result = changelogUrlFor(['']);
- assert.strictEqual(result, CHANGELOG_URL);
- });
-
- test('it builds the url for double-digit versions', function (assert) {
- const result = changelogUrlFor(['1.13.0+ent']);
- assert.strictEqual(result, CHANGELOG_URL.concat('1130'));
- });
-});
diff --git a/ui/tests/integration/helpers/date-format-test.js b/ui/tests/integration/helpers/date-format-test.js
deleted file mode 100644
index 3e8896b21..000000000
--- a/ui/tests/integration/helpers/date-format-test.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import sinon from 'sinon';
-import { setupRenderingTest } from 'ember-qunit';
-import { find, render, settled } from '@ember/test-helpers';
-import hbs from 'htmlbars-inline-precompile';
-import timestamp from 'core/utils/timestamp';
-
-module('Integration | Helper | date-format', function (hooks) {
- setupRenderingTest(hooks);
-
- hooks.before(function () {
- sinon.stub(timestamp, 'now').callsFake(() => new Date('2018-04-03T14:15:30'));
- });
- hooks.after(function () {
- timestamp.now.restore();
- });
-
- test('it is able to format a date object', async function (assert) {
- const today = timestamp.now();
- this.set('today', today);
-
- await render(hbs`{{date-format this.today "yyyy"}}`);
- assert.dom(this.element).includesText('2018', 'it renders the date in the year format');
- });
-
- test('it supports date timestamps', async function (assert) {
- const today = timestamp.now().getTime();
- this.set('today', today);
-
- await render(hbs`{{date-format this.today 'hh:mm:ss'}}`);
- const formattedDate = this.element.innerText;
- assert.strictEqual(formattedDate, '02:15:30');
- });
-
- test('it supports date strings', async function (assert) {
- const todayString = timestamp.now().getFullYear().toString();
- this.set('todayString', todayString);
-
- await render(hbs`{{date-format this.todayString "yyyy"}}`);
- assert.dom(this.element).includesText(todayString, 'it renders the a date if passed in as a string');
- });
-
- test('it supports ten digit dates', async function (assert) {
- const tenDigitDate = 1621785298;
- this.set('tenDigitDate', tenDigitDate);
-
- await render(hbs`{{date-format this.tenDigitDate "MM/dd/yyyy"}}`);
- assert.dom(this.element).includesText('05/23/2021');
- });
-
- test('it supports already formatted dates', async function (assert) {
- const formattedDate = timestamp.now();
- this.set('formattedDate', formattedDate);
-
- await render(hbs`{{date-format this.formattedDate 'MMMM dd, yyyy hh:mm:ss a' isFormatted=true}}`);
- assert.dom(this.element).hasText('April 03, 2018 02:15:30 PM');
- });
-
- test('displays time zone if withTimeZone=true', async function (assert) {
- const timestampDate = '2022-12-06T11:29:15-08:00';
- this.set('withTimezone', false);
- this.set('timestampDate', timestampDate);
-
- await render(
- hbs`{{date-format this.timestampDate 'MMM d yyyy, h:mm:ss aaa' withTimeZone=this.withTimezone}}`
- );
- const result = find('[data-test-formatted]');
- assert.strictEqual(result.innerText.length, 22);
- // Compare to with timezone, which should add 4 characters
- this.set('withTimezone', true);
- await settled();
- assert.strictEqual(result.innerText.length, 26);
- });
-});
diff --git a/ui/tests/integration/helpers/date-from-now-test.js b/ui/tests/integration/helpers/date-from-now-test.js
deleted file mode 100644
index a42392ce7..000000000
--- a/ui/tests/integration/helpers/date-from-now-test.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { subMinutes } from 'date-fns';
-import { setupRenderingTest } from 'ember-qunit';
-import { dateFromNow } from '../../../helpers/date-from-now';
-import { render } from '@ember/test-helpers';
-import hbs from 'htmlbars-inline-precompile';
-
-module('Integration | Helper | date-from-now', function (hooks) {
- setupRenderingTest(hooks);
-
- test('it works', function (assert) {
- const result = dateFromNow([1481022124443]);
- assert.strictEqual(typeof result, 'string', 'it is a string');
- });
-
- test('you can include a suffix', function (assert) {
- const result = dateFromNow([1481022124443], { addSuffix: true });
- assert.ok(result.includes(' ago'));
- });
-
- test('you can pass in UTC timestamp', function (assert) {
- const result = dateFromNow(['Fri, 11 Oct 2019 18:56:08 GMT'], { addSuffix: true });
- assert.ok(result.includes(' ago'));
- });
-
- test('you can pass in ISO timestamp', function (assert) {
- const result = dateFromNow(['2019-10-11T18:56:08.984Z'], { addSuffix: true });
- assert.ok(result.includes(' ago'));
- });
-
- test('you can include a suffix using date class', function (assert) {
- const now = Date.now();
- const pastDate = subMinutes(now, 30);
- const result = dateFromNow([pastDate], { addSuffix: true });
- assert.ok(result.includes(' ago'));
- });
-
- test('you can include a suffix using ISO 8601 format', function (assert) {
- const result = dateFromNow(['2021-02-05T20:43:09+00:00'], { addSuffix: true });
- assert.ok(result.includes(' ago'));
- });
-
- test('you can include a suffix in the helper', async function (assert) {
- await render(hbs`Date: {{date-from-now 1481022124443 addSuffix=true}}
`);
- assert.dom('[data-test-date-from-now]').includesText(' years ago');
- });
-});
diff --git a/ui/tests/integration/helpers/format-duration-test.js b/ui/tests/integration/helpers/format-duration-test.js
deleted file mode 100644
index be8e0d602..000000000
--- a/ui/tests/integration/helpers/format-duration-test.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { render } from '@ember/test-helpers';
-import hbs from 'htmlbars-inline-precompile';
-
-module('Integration | Helper | format-duration', function (hooks) {
- setupRenderingTest(hooks);
-
- test('it supports strings and formats seconds', async function (assert) {
- await render(hbs`Date: {{format-duration '3606'}}
`);
-
- assert
- .dom('[data-test-format-duration]')
- .includesText('1 hour 6 seconds', 'it renders the duration in hours and seconds');
- });
-
- test('it is able to format seconds and days', async function (assert) {
- await render(hbs`Date: {{format-duration '93606000'}}
`);
-
- assert
- .dom('[data-test-format-duration]')
- .includesText(
- '2 years 11 months 18 days 9 hours 40 minutes',
- 'it renders with years months and days and hours and minutes'
- );
- });
-
- test('it is able to format numbers', async function (assert) {
- this.set('number', 60);
- await render(hbs`Date: {{format-duration this.number}}
`);
-
- assert
- .dom('[data-test-format-duration]')
- .includesText('1 minute', 'it renders duration when a number is passed in.');
- });
-
- test('it renders the input if time not found', async function (assert) {
- this.set('number', 'arg');
-
- await render(hbs`Date: {{format-duration this.number}}
`);
- assert.dom('[data-test-format-duration]').hasText('Date: arg');
- });
-
- test('it renders no value if nullable true', async function (assert) {
- this.set('number', 0);
-
- await render(hbs`Date: {{format-duration this.number nullable=true}}
`);
- assert.dom('[data-test-format-duration]').hasText('Date:');
- });
-});
diff --git a/ui/tests/integration/helpers/has-feature-test.js b/ui/tests/integration/helpers/has-feature-test.js
deleted file mode 100644
index 87af455fa..000000000
--- a/ui/tests/integration/helpers/has-feature-test.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import Service from '@ember/service';
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { render } from '@ember/test-helpers';
-import hbs from 'htmlbars-inline-precompile';
-import waitForError from 'vault/tests/helpers/wait-for-error';
-
-const versionStub = Service.extend({
- features: null,
-});
-
-module('helper:has-feature', function (hooks) {
- setupRenderingTest(hooks);
-
- hooks.beforeEach(function () {
- this.owner.register('service:version', versionStub);
- this.versionService = this.owner.lookup('service:version');
- });
-
- test('it asserts on unknown features', async function (assert) {
- const promise = waitForError();
- render(hbs`{{has-feature 'New Feature'}}`);
- const err = await promise;
- assert.ok(
- err.message.includes('New Feature is not one of the available values for Vault Enterprise features.'),
- 'asserts when an unknown feature is passed as an arg'
- );
- });
-
- test('it is true with existing features', async function (assert) {
- this.set('versionService.features', ['HSM']);
- await render(hbs`{{if (has-feature 'HSM') 'It works' null}}`);
- assert.dom(this.element).hasText('It works', 'present features evaluate to true');
- });
-
- test('it is false with missing features', async function (assert) {
- this.set('versionService.features', ['MFA']);
- await render(hbs`{{if (has-feature 'HSM') 'It works' null}}`);
- assert.dom(this.element).hasText('', 'missing features evaluate to false');
- });
-});
diff --git a/ui/tests/integration/helpers/has-permission-test.js b/ui/tests/integration/helpers/has-permission-test.js
deleted file mode 100644
index 91391e4ca..000000000
--- a/ui/tests/integration/helpers/has-permission-test.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { render } from '@ember/test-helpers';
-import { run } from '@ember/runloop';
-import hbs from 'htmlbars-inline-precompile';
-import Service from '@ember/service';
-import sinon from 'sinon';
-
-const Permissions = Service.extend({
- globPaths: null,
- hasNavPermission() {
- return this.globPaths ? true : false;
- },
-});
-
-module('Integration | Helper | has-permission', function (hooks) {
- setupRenderingTest(hooks);
-
- hooks.beforeEach(function () {
- this.owner.register('service:permissions', Permissions);
- this.permissions = this.owner.lookup('service:permissions');
- });
-
- test('it renders', async function (assert) {
- await render(hbs`{{#if (has-permission)}}Yes{{else}}No{{/if}}`);
-
- assert.dom(this.element).hasText('No');
- await run(() => {
- this.permissions.set('globPaths', { 'test/': { capabilities: ['update'] } });
- });
- assert.dom(this.element).hasText('Yes', 'the helper re-computes when globPaths changes');
- });
-
- test('it should pass args from helper to service method', async function (assert) {
- const stub = sinon.stub(this.permissions, 'hasNavPermission').returns(true);
- this.permissions.set('exactPaths', {
- 'sys/auth': {
- capabilities: ['read'],
- },
- 'identity/mfa/method': {
- capabilities: ['read'],
- },
- });
- this.routeParams = ['methods', 'mfa'];
-
- await render(hbs`
- {{#if (has-permission "access" routeParams=this.routeParams requireAll=true)}}
- Yes
- {{else}}
- No
- {{/if}}
- `);
-
- assert.true(
- stub.withArgs('access', this.routeParams, true).calledOnce,
- 'Args are passed from helper to service'
- );
- assert.dom(this.element).hasText('Yes', 'Helper returns value from service method');
- stub.restore();
- });
-});
diff --git a/ui/tests/integration/helpers/is-empty-value-test.js b/ui/tests/integration/helpers/is-empty-value-test.js
deleted file mode 100644
index 1f57f13b8..000000000
--- a/ui/tests/integration/helpers/is-empty-value-test.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { render } from '@ember/test-helpers';
-import { hbs } from 'ember-cli-htmlbars';
-
-const template = hbs`
-{{#if (is-empty-value this.inputValue hasDefault=this.defaultValue)}}
-Empty
-{{else}}
-Full
-{{/if}}
-`;
-
-const emptyObject = {};
-
-const nonEmptyObject = { thing: 0 };
-
-module('Integration | Helper | is-empty-value', function (hooks) {
- setupRenderingTest(hooks);
-
- test('it is truthy if the value evaluated is undefined and no default', async function (assert) {
- this.set('inputValue', undefined);
- this.set('defaultValue', false);
-
- await render(template);
-
- assert.dom(this.element).hasText('Empty');
- });
-
- test('it is truthy if the value evaluated is an empty string and no default', async function (assert) {
- this.set('inputValue', '');
- this.set('defaultValue', false);
-
- await render(template);
-
- assert.dom(this.element).hasText('Empty');
- });
-
- test('it is truthy if the value evaluated is an empty object and no default', async function (assert) {
- this.set('inputValue', emptyObject);
- this.set('defaultValue', false);
-
- await render(template);
-
- assert.dom(this.element).hasText('Empty');
- });
-
- test('it is falsy if the value evaluated is not an empty object and no default', async function (assert) {
- this.set('inputValue', nonEmptyObject);
- this.set('defaultValue', false);
-
- await render(template);
-
- assert.dom(this.element).hasText('Full');
- });
-
- test('it is falsy if the value evaluated is empty but a default exists', async function (assert) {
- this.set('defaultValue', 'Some default');
- this.set('inputValue', emptyObject);
-
- await render(template);
- assert.dom(this.element).hasText('Full', 'shows default when value is empty object');
-
- this.set('inputValue', '');
- await render(template);
- assert.dom(this.element).hasText('Full', 'shows default when value is empty string');
-
- this.set('inputValue', undefined);
- await render(template);
- assert.dom(this.element).hasText('Full', 'shows default when value is undefined');
- });
-});
diff --git a/ui/tests/integration/helpers/loose-equal-test.js b/ui/tests/integration/helpers/loose-equal-test.js
deleted file mode 100644
index 0a33fcb42..000000000
--- a/ui/tests/integration/helpers/loose-equal-test.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'vault/tests/helpers';
-import { render } from '@ember/test-helpers';
-import { hbs } from 'ember-cli-htmlbars';
-import { looseEqual } from 'core/helpers/loose-equal';
-
-module('Integration | Helper | loose-equal', function (hooks) {
- setupRenderingTest(hooks);
-
- test('it renders', async function (assert) {
- this.inputValue = 1234;
- await render(hbs`{{if (loose-equal "1234" 1234) "true" "false"}}`);
- assert.dom(this.element).hasText('true');
-
- this.inputValue = '4567';
- await render(hbs`{{if (loose-equal "1234" "4567") "true" "false"}}`);
- assert.dom(this.element).hasText('false');
- });
-
- test('it compares values as expected', async function (assert) {
- assert.true(looseEqual([0, '0']));
- assert.true(looseEqual([0, 0]));
- assert.true(looseEqual(['0', '0']));
- assert.true(looseEqual(['1234', 1234]));
- assert.true(looseEqual(['1234', '1234']));
- assert.true(looseEqual([1234, 1234]));
- assert.true(looseEqual(['abc', 'abc']));
- assert.true(looseEqual(['', '']));
-
- // == normally returns true for this comparison, we intercept and return false
- assert.false(looseEqual(['', 0]));
- assert.false(looseEqual([0, '']));
- });
-});
diff --git a/ui/tests/integration/helpers/remove-from-array-test.js b/ui/tests/integration/helpers/remove-from-array-test.js
deleted file mode 100644
index 3e87cd5ea..000000000
--- a/ui/tests/integration/helpers/remove-from-array-test.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { removeFromArray } from '../../../helpers/remove-from-array';
-
-module('Integration | Helper | remove-from-array', function (hooks) {
- setupRenderingTest(hooks);
-
- test('it correctly removes a value from an array without mutating the original', function (assert) {
- const ARRAY = ['horse', 'cow', 'chicken'];
- const result = removeFromArray([ARRAY, 'horse']);
- assert.deepEqual(result, ['cow', 'chicken'], 'Result does not have removed item');
- assert.deepEqual(ARRAY, ['horse', 'cow', 'chicken'], 'original array is not mutated');
- });
-
- test('it returns the same value if the item is not found', function (assert) {
- const ARRAY = ['horse', 'cow', 'chicken'];
- const result = removeFromArray([ARRAY, 'pig']);
- assert.deepEqual(result, ARRAY, 'Results are the same as original array');
- });
-
- test('it fails if the first value is not an array', function (assert) {
- let result;
- try {
- result = removeFromArray(['not-array', 'string']);
- } catch (e) {
- result = e.message;
- }
- assert.strictEqual(result, 'Assertion Failed: Value provided is not an array');
- });
-
- test('it works with non-string arrays', function (assert) {
- const ARRAY = ['five', 6, '7'];
- const result1 = removeFromArray([ARRAY, 6]);
- const result2 = removeFromArray([ARRAY, 7]);
- assert.deepEqual(result1, ['five', '7'], 'removed number value');
- assert.deepEqual(result2, ARRAY, 'did not match on different types');
- });
-
- test('it de-dupes the result', function (assert) {
- const ARRAY = ['horse', 'cow', 'chicken', 'cow'];
- const result = removeFromArray([ARRAY, 'horse']);
- assert.deepEqual(result, ['cow', 'chicken']);
- });
-});
diff --git a/ui/tests/integration/helpers/sanitized-html-test.js b/ui/tests/integration/helpers/sanitized-html-test.js
deleted file mode 100644
index a6c0addb2..000000000
--- a/ui/tests/integration/helpers/sanitized-html-test.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'vault/tests/helpers';
-import { render } from '@ember/test-helpers';
-import { hbs } from 'ember-cli-htmlbars';
-
-module('Integration | Helper | sanitized-html', function (hooks) {
- setupRenderingTest(hooks);
-
- test('it does not alter if string is safe', async function (assert) {
- this.set('inputValue', 'height: 15.33px');
-
- await render(hbs`{{sanitized-html this.inputValue}}`);
- assert.dom(this.element).hasText('height: 15.33px');
- });
-
- test('it strips unsafe HTML before rendering safe HTML', async function (assert) {
- this.set(
- 'inputValue',
- 'This is something'
- );
-
- await render(hbs`{{sanitized-html this.inputValue}}`);
- assert.dom('[data-test-thing]').hasTagName('main');
- assert.dom('[data-test-thing]').hasText('This is something', 'preserves non-problematic content');
- assert.dom('[data-test-script]').doesNotExist('Script is stripped from render');
- });
-
- test('it does not invoke functions passed as value', async function (assert) {
- this.set('inputValue', () => {
- window.alert('h4cK3d');
- });
- await render(hbs`{{sanitized-html this.inputValue}}`);
- assert.dom(this.element).hasText("() => { window.alert('h4cK3d'); }");
- });
-});
diff --git a/ui/tests/integration/services/auth-test.js b/ui/tests/integration/services/auth-test.js
deleted file mode 100644
index 33c2298f3..000000000
--- a/ui/tests/integration/services/auth-test.js
+++ /dev/null
@@ -1,355 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { run } from '@ember/runloop';
-import { copy } from 'ember-copy';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { TOKEN_SEPARATOR, TOKEN_PREFIX, ROOT_PREFIX } from 'vault/services/auth';
-import Pretender from 'pretender';
-
-function storage() {
- return {
- items: {},
- getItem(key) {
- var item = this.items[key];
- return item && JSON.parse(item);
- },
-
- setItem(key, val) {
- return (this.items[key] = JSON.stringify(val));
- },
-
- removeItem(key) {
- delete this.items[key];
- },
-
- keys() {
- return Object.keys(this.items);
- },
- };
-}
-
-const ROOT_TOKEN_RESPONSE = {
- request_id: 'e6674d7f-c96f-d51f-4463-cc95f0ad307e',
- lease_id: '',
- renewable: false,
- lease_duration: 0,
- data: {
- accessor: '1dd25306-fdb9-0f43-8169-48ad702041b0',
- creation_time: 1477671134,
- creation_ttl: 0,
- display_name: 'root',
- explicit_max_ttl: 0,
- id: '',
- meta: null,
- num_uses: 0,
- orphan: true,
- path: 'auth/token/root',
- policies: ['root'],
- ttl: 0,
- },
- wrap_info: null,
- warnings: null,
- auth: null,
-};
-
-const TOKEN_NON_ROOT_RESPONSE = function () {
- return {
- request_id: '3ca32cd9-fd40-891d-02d5-ea23138e8642',
- lease_id: '',
- renewable: false,
- lease_duration: 0,
- data: {
- accessor: '4ef32471-a94c-79ee-c290-aeba4d63bdc9',
- creation_time: Math.floor(Date.now() / 1000),
- creation_ttl: 2764800,
- display_name: 'token',
- explicit_max_ttl: 0,
- id: '6d83e912-1b21-9df9-b51a-d201b709f3d5',
- meta: null,
- num_uses: 0,
- orphan: false,
- path: 'auth/token/create',
- policies: ['default', 'userpass'],
- renewable: true,
- ttl: 2763327,
- },
- wrap_info: null,
- warnings: null,
- auth: null,
- };
-};
-
-const USERPASS_RESPONSE = {
- request_id: '7e5e8d3d-599e-6ef7-7570-f7057fc7c53d',
- lease_id: '',
- renewable: false,
- lease_duration: 0,
- data: null,
- wrap_info: null,
- warnings: null,
- auth: {
- client_token: '5313ff81-05cb-699f-29d1-b82b4e2906dc',
- accessor: '5c5303e7-56d6-ea13-72df-d85411bd9a7d',
- policies: ['default'],
- metadata: {
- username: 'matthew',
- },
- lease_duration: 2764800,
- renewable: true,
- },
-};
-
-const GITHUB_RESPONSE = {
- request_id: '4913f9cd-a95f-d1f9-5746-4c3af4e15660',
- lease_id: '',
- renewable: false,
- lease_duration: 0,
- data: null,
- wrap_info: null,
- warnings: null,
- auth: {
- client_token: '0d39b535-598e-54d9-96e3-97493492a5f7',
- accessor: 'd8cd894f-bedf-5ce3-f1b5-98f7c6cf8ab4',
- policies: ['default'],
- metadata: {
- org: 'hashicorp',
- username: 'meirish',
- },
- lease_duration: 2764800,
- renewable: true,
- },
-};
-
-module('Integration | Service | auth', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.owner.lookup('service:flash-messages').registerTypes(['warning']);
- this.store = storage();
- this.memStore = storage();
- this.server = new Pretender(function () {
- this.get('/v1/auth/token/lookup-self', function (request) {
- const resp = copy(ROOT_TOKEN_RESPONSE, true);
- resp.id = request.requestHeaders['X-Vault-Token'];
- resp.data.id = request.requestHeaders['X-Vault-Token'];
- return [200, {}, resp];
- });
- this.post('/v1/auth/userpass/login/:username', function (request) {
- const { username } = request.params;
- const resp = copy(USERPASS_RESPONSE, true);
- resp.auth.metadata.username = username;
- return [200, {}, resp];
- });
-
- this.post('/v1/auth/github/login', function () {
- const resp = copy(GITHUB_RESPONSE, true);
- return [200, {}, resp];
- });
- });
-
- this.server.prepareBody = function (body) {
- return body ? JSON.stringify(body) : '{"error": "not found"}';
- };
-
- this.server.prepareHeaders = function (headers) {
- headers['content-type'] = 'application/javascript';
- return headers;
- };
- });
-
- hooks.afterEach(function () {
- this.server.shutdown();
- });
-
- test('token authentication: root token', function (assert) {
- assert.expect(6);
- const done = assert.async();
- const self = this;
- const service = this.owner.factoryFor('service:auth').create({
- storage(tokenName) {
- if (
- tokenName &&
- tokenName.indexOf(`${TOKEN_PREFIX}${ROOT_PREFIX}`) === 0 &&
- this.environment() !== 'development'
- ) {
- return self.memStore;
- } else {
- return self.store;
- }
- },
- });
- run(() => {
- service
- .authenticate({ clusterId: '1', backend: 'token', data: { token: 'test' } })
- .then(() => {
- const clusterTokenName = service.get('currentTokenName');
- const clusterToken = service.get('currentToken');
- const authData = service.get('authData');
-
- const expectedTokenName = `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`;
- assert.strictEqual(clusterToken, 'test', 'token is saved properly');
- assert.strictEqual(
- `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`,
- clusterTokenName,
- 'token name is saved properly'
- );
- assert.strictEqual(authData.backend.type, 'token', 'backend is saved properly');
- assert.strictEqual(
- ROOT_TOKEN_RESPONSE.data.display_name,
- authData.displayName,
- 'displayName is saved properly'
- );
- assert.ok(
- this.memStore.keys().includes(expectedTokenName),
- 'root token is stored in the memory store'
- );
- assert.strictEqual(this.store.keys().length, 0, 'normal storage is empty');
- })
- .finally(() => {
- done();
- });
- });
- });
-
- test('token authentication: root token in ember development environment', async function (assert) {
- const self = this;
- const service = this.owner.factoryFor('service:auth').create({
- storage(tokenName) {
- if (
- tokenName &&
- tokenName.indexOf(`${TOKEN_PREFIX}${ROOT_PREFIX}`) === 0 &&
- this.environment() !== 'development'
- ) {
- return self.memStore;
- } else {
- return self.store;
- }
- },
- environment: () => 'development',
- });
- await service.authenticate({ clusterId: '1', backend: 'token', data: { token: 'test' } });
- const clusterTokenName = service.get('currentTokenName');
- const clusterToken = service.get('currentToken');
- const authData = service.get('authData');
-
- const expectedTokenName = `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`;
- assert.strictEqual(clusterToken, 'test', 'token is saved properly');
- assert.strictEqual(
- `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`,
- clusterTokenName,
- 'token name is saved properly'
- );
- assert.strictEqual(authData.backend.type, 'token', 'backend is saved properly');
- assert.strictEqual(
- ROOT_TOKEN_RESPONSE.data.display_name,
- authData.displayName,
- 'displayName is saved properly'
- );
- assert.ok(this.store.keys().includes(expectedTokenName), 'root token is stored in the store');
- assert.strictEqual(this.memStore.keys().length, 0, 'mem storage is empty');
- });
-
- test('github authentication', function (assert) {
- assert.expect(6);
- const done = assert.async();
- const service = this.owner.factoryFor('service:auth').create({
- storage: (type) => (type === 'memory' ? this.memStore : this.store),
- });
-
- run(() => {
- service.authenticate({ clusterId: '1', backend: 'github', data: { token: 'test' } }).then(() => {
- const clusterTokenName = service.get('currentTokenName');
- const clusterToken = service.get('currentToken');
- const authData = service.get('authData');
- const expectedTokenName = `${TOKEN_PREFIX}github${TOKEN_SEPARATOR}1`;
-
- assert.strictEqual(GITHUB_RESPONSE.auth.client_token, clusterToken, 'token is saved properly');
- assert.strictEqual(expectedTokenName, clusterTokenName, 'token name is saved properly');
- assert.strictEqual(authData.backend.type, 'github', 'backend is saved properly');
- assert.strictEqual(
- GITHUB_RESPONSE.auth.metadata.org + '/' + GITHUB_RESPONSE.auth.metadata.username,
- authData.displayName,
- 'displayName is saved properly'
- );
- assert.strictEqual(this.memStore.keys().length, 0, 'mem storage is empty');
- assert.ok(this.store.keys().includes(expectedTokenName), 'normal storage contains the token');
- done();
- });
- });
- });
-
- test('userpass authentication', function (assert) {
- assert.expect(4);
- const done = assert.async();
- const service = this.owner.factoryFor('service:auth').create({ storage: () => this.store });
- run(() => {
- service
- .authenticate({
- clusterId: '1',
- backend: 'userpass',
- data: { username: USERPASS_RESPONSE.auth.metadata.username, password: 'passoword' },
- })
- .then(() => {
- const clusterTokenName = service.get('currentTokenName');
- const clusterToken = service.get('currentToken');
- const authData = service.get('authData');
-
- assert.strictEqual(USERPASS_RESPONSE.auth.client_token, clusterToken, 'token is saved properly');
- assert.strictEqual(
- `${TOKEN_PREFIX}userpass${TOKEN_SEPARATOR}1`,
- clusterTokenName,
- 'token name is saved properly'
- );
- assert.strictEqual(authData.backend.type, 'userpass', 'backend is saved properly');
- assert.strictEqual(
- USERPASS_RESPONSE.auth.metadata.username,
- authData.displayName,
- 'displayName is saved properly'
- );
- done();
- });
- });
- });
-
- test('token auth expiry with non-root token', function (assert) {
- assert.expect(5);
- const tokenResp = TOKEN_NON_ROOT_RESPONSE();
- this.server.map(function () {
- this.get('/v1/auth/token/lookup-self', function (request) {
- const resp = copy(tokenResp, true);
- resp.id = request.requestHeaders['X-Vault-Token'];
- resp.data.id = request.requestHeaders['X-Vault-Token'];
- return [200, {}, resp];
- });
- });
-
- const done = assert.async();
- const service = this.owner.factoryFor('service:auth').create({ storage: () => this.store });
- run(() => {
- service.authenticate({ clusterId: '1', backend: 'token', data: { token: 'test' } }).then(() => {
- const clusterTokenName = service.get('currentTokenName');
- const clusterToken = service.get('currentToken');
- const authData = service.get('authData');
-
- assert.strictEqual(clusterToken, 'test', 'token is saved properly');
- assert.strictEqual(
- `${TOKEN_PREFIX}token${TOKEN_SEPARATOR}1`,
- clusterTokenName,
- 'token name is saved properly'
- );
- assert.strictEqual(authData.backend.type, 'token', 'backend is saved properly');
- assert.strictEqual(
- authData.displayName,
- tokenResp.data.display_name,
- 'displayName is saved properly'
- );
- assert.false(service.get('tokenExpired'), 'token is not expired');
- done();
- });
- });
- });
-});
diff --git a/ui/tests/integration/utils/client-count-utils-test.js b/ui/tests/integration/utils/client-count-utils-test.js
deleted file mode 100644
index 5f17fc4ec..000000000
--- a/ui/tests/integration/utils/client-count-utils-test.js
+++ /dev/null
@@ -1,930 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import {
- flattenDataset,
- formatByMonths,
- formatByNamespace,
- homogenizeClientNaming,
- sortMonthsByTimestamp,
- namespaceArrayToObject,
-} from 'core/utils/client-count-utils';
-import { parseAPITimestamp } from 'core/utils/date-formatters';
-import isBefore from 'date-fns/isBefore';
-import isAfter from 'date-fns/isAfter';
-
-// import { setupMirage } from 'ember-cli-mirage/test-support';
-// import ENV from 'vault/config/environment';
-// import { formatRFC3339 } from 'date-fns';
-
-module('Integration | Util | client count utils', function (hooks) {
- setupTest(hooks);
- // setupMirage(hooks);
-
- // TODO: wire up to stubbed API/mirage?
- // hooks.before(function () {
- // ENV['ember-cli-mirage'].handler = 'clients';
- // });
- // hooks.after(function () {
- // ENV['ember-cli-mirage'].handler = null;
- // });
-
- /* MONTHS array contains: (update when backend work done on months )
- - one month with only old client naming
- */
-
- const MONTHS = [
- {
- timestamp: '2021-05-01T00:00:00Z',
- counts: {
- distinct_entities: 25,
- non_entity_tokens: 25,
- clients: 50,
- },
- namespaces: [
- {
- namespace_id: 'root',
- namespace_path: '',
- counts: {
- distinct_entities: 13,
- non_entity_tokens: 7,
- clients: 20,
- },
- mounts: [
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 8,
- non_entity_tokens: 0,
- clients: 8,
- },
- },
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- non_entity_tokens: 7,
- clients: 7,
- },
- },
- ],
- },
- {
- namespace_id: 's07UR',
- namespace_path: 'ns1/',
- counts: {
- distinct_entities: 5,
- non_entity_tokens: 5,
- clients: 10,
- },
- mounts: [
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- non_entity_tokens: 5,
- clients: 5,
- },
- },
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 5,
- non_entity_tokens: 0,
- clients: 5,
- },
- },
- ],
- },
- ],
- new_clients: {
- counts: {
- distinct_entities: 3,
- non_entity_tokens: 2,
- clients: 5,
- },
- namespaces: [
- {
- namespace_id: 'root',
- namespace_path: '',
- counts: {
- distinct_entities: 3,
- non_entity_tokens: 2,
- clients: 5,
- },
- mounts: [
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 3,
- non_entity_tokens: 0,
- clients: 3,
- },
- },
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- non_entity_tokens: 2,
- clients: 2,
- },
- },
- ],
- },
- ],
- },
- },
- {
- timestamp: '2021-10-01T00:00:00Z',
- counts: {
- distinct_entities: 20,
- entity_clients: 20,
- non_entity_tokens: 20,
- non_entity_clients: 20,
- clients: 40,
- },
- namespaces: [
- {
- namespace_id: 'root',
- namespace_path: '',
- counts: {
- distinct_entities: 8,
- entity_clients: 8,
- non_entity_tokens: 7,
- non_entity_clients: 7,
- clients: 15,
- },
- mounts: [
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 8,
- entity_clients: 8,
- non_entity_tokens: 0,
- non_entity_clients: 0,
- clients: 8,
- },
- },
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 0,
- non_entity_tokens: 7,
- non_entity_clients: 7,
- clients: 7,
- },
- },
- ],
- },
- {
- namespace_id: 's07UR',
- namespace_path: 'ns1/',
- counts: {
- distinct_entities: 5,
- entity_clients: 5,
- non_entity_tokens: 5,
- non_entity_clients: 5,
- clients: 10,
- },
- mounts: [
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 0,
- non_entity_tokens: 5,
- non_entity_clients: 5,
- clients: 5,
- },
- },
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 5,
- entity_clients: 5,
- non_entity_tokens: 0,
- non_entity_clients: 0,
- clients: 5,
- },
- },
- ],
- },
- ],
- new_clients: {
- counts: {
- distinct_entities: 3,
- entity_clients: 3,
- non_entity_tokens: 2,
- non_entity_clients: 2,
- clients: 5,
- },
- namespaces: [
- {
- namespace_id: 'root',
- namespace_path: '',
- counts: {
- distinct_entities: 3,
- entity_clients: 3,
- non_entity_tokens: 2,
- non_entity_clients: 2,
- clients: 5,
- },
- mounts: [
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 3,
- entity_clients: 3,
- non_entity_tokens: 0,
- non_entity_clients: 0,
- clients: 3,
- },
- },
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 0,
- non_entity_tokens: 2,
- non_entity_clients: 2,
- clients: 2,
- },
- },
- ],
- },
- ],
- },
- },
- {
- timestamp: '2021-09-01T00:00:00Z',
- counts: {
- distinct_entities: 0,
- entity_clients: 17,
- non_entity_tokens: 0,
- non_entity_clients: 18,
- clients: 35,
- },
- namespaces: [
- {
- namespace_id: 'oImjk',
- namespace_path: 'ns2/',
- counts: {
- distinct_entities: 0,
- entity_clients: 5,
- non_entity_tokens: 0,
- non_entity_clients: 5,
- clients: 10,
- },
- mounts: [
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 0,
- non_entity_tokens: 0,
- non_entity_clients: 5,
- clients: 5,
- },
- },
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 0,
- entity_clients: 5,
- non_entity_tokens: 0,
- non_entity_clients: 0,
- clients: 5,
- },
- },
- ],
- },
- {
- namespace_id: 'root',
- namespace_path: '',
- counts: {
- distinct_entities: 0,
- entity_clients: 2,
- non_entity_tokens: 0,
- non_entity_clients: 3,
- clients: 5,
- },
- mounts: [
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 0,
- non_entity_tokens: 0,
- non_entity_clients: 3,
- clients: 3,
- },
- },
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 0,
- entity_clients: 2,
- non_entity_tokens: 0,
- non_entity_clients: 0,
- clients: 2,
- },
- },
- ],
- },
- {
- namespace_id: 's07UR',
- namespace_path: 'ns1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 3,
- non_entity_tokens: 0,
- non_entity_clients: 2,
- clients: 5,
- },
- mounts: [
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 0,
- entity_clients: 3,
- non_entity_tokens: 0,
- non_entity_clients: 0,
- clients: 3,
- },
- },
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 0,
- non_entity_tokens: 0,
- non_entity_clients: 2,
- clients: 2,
- },
- },
- ],
- },
- ],
- new_clients: {
- counts: {
- distinct_entities: 0,
- entity_clients: 10,
- non_entity_tokens: 0,
- non_entity_clients: 10,
- clients: 20,
- },
- namespaces: [
- {
- namespace_id: 'oImjk',
- namespace_path: 'ns2/',
- counts: {
- distinct_entities: 0,
- entity_clients: 5,
- non_entity_tokens: 0,
- non_entity_clients: 5,
- clients: 10,
- },
- mounts: [
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 0,
- non_entity_tokens: 0,
- non_entity_clients: 5,
- clients: 5,
- },
- },
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 0,
- entity_clients: 5,
- non_entity_tokens: 0,
- non_entity_clients: 0,
- clients: 5,
- },
- },
- ],
- },
- {
- namespace_id: 'root',
- namespace_path: '',
- counts: {
- distinct_entities: 0,
- entity_clients: 2,
- non_entity_tokens: 0,
- non_entity_clients: 3,
- clients: 5,
- },
- mounts: [
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 0,
- non_entity_tokens: 0,
- non_entity_clients: 3,
- clients: 3,
- },
- },
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 0,
- entity_clients: 2,
- non_entity_tokens: 0,
- non_entity_clients: 0,
- clients: 2,
- },
- },
- ],
- },
- {
- namespace_id: 's07UR',
- namespace_path: 'ns1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 3,
- non_entity_tokens: 0,
- non_entity_clients: 2,
- clients: 5,
- },
- mounts: [
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 0,
- entity_clients: 3,
- non_entity_tokens: 0,
- non_entity_clients: 0,
- clients: 3,
- },
- },
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 0,
- non_entity_tokens: 0,
- non_entity_clients: 2,
- clients: 2,
- },
- },
- ],
- },
- ],
- },
- },
- ];
-
- const BY_NAMESPACE = [
- {
- namespace_id: '96OwG',
- namespace_path: 'test-ns/',
- counts: {
- distinct_entities: 18290,
- entity_clients: 18290,
- non_entity_tokens: 18738,
- non_entity_clients: 18738,
- clients: 37028,
- },
- mounts: [
- {
- mount_path: 'path-1',
- counts: {
- distinct_entities: 6403,
- entity_clients: 6403,
- non_entity_tokens: 6300,
- non_entity_clients: 6300,
- clients: 12703,
- },
- },
- {
- mount_path: 'path-2',
- counts: {
- distinct_entities: 5699,
- entity_clients: 5699,
- non_entity_tokens: 6777,
- non_entity_clients: 6777,
- clients: 12476,
- },
- },
- {
- mount_path: 'path-3',
- counts: {
- distinct_entities: 6188,
- entity_clients: 6188,
- non_entity_tokens: 5661,
- non_entity_clients: 5661,
- clients: 11849,
- },
- },
- ],
- },
- {
- namespace_id: 'root',
- namespace_path: '',
- counts: {
- distinct_entities: 19099,
- entity_clients: 19099,
- non_entity_tokens: 17781,
- non_entity_clients: 17781,
- clients: 36880,
- },
- mounts: [
- {
- mount_path: 'path-3',
- counts: {
- distinct_entities: 6863,
- entity_clients: 6863,
- non_entity_tokens: 6801,
- non_entity_clients: 6801,
- clients: 13664,
- },
- },
- {
- mount_path: 'path-2',
- counts: {
- distinct_entities: 6047,
- entity_clients: 6047,
- non_entity_tokens: 5957,
- non_entity_clients: 5957,
- clients: 12004,
- },
- },
- {
- mount_path: 'path-1',
- counts: {
- distinct_entities: 6189,
- entity_clients: 6189,
- non_entity_tokens: 5023,
- non_entity_clients: 5023,
- clients: 11212,
- },
- },
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 0,
- entity_clients: 50,
- non_entity_tokens: 0,
- non_entity_clients: 23,
- clients: 73,
- },
- },
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 25,
- non_entity_tokens: 0,
- non_entity_clients: 15,
- clients: 40,
- },
- },
- ],
- },
- ];
-
- const EMPTY_MONTHS = [
- {
- timestamp: '2021-06-01T00:00:00Z',
- counts: null,
- namespaces: null,
- new_clients: null,
- },
- {
- timestamp: '2021-07-01T00:00:00Z',
- counts: null,
- namespaces: null,
- new_clients: null,
- },
- ];
-
- const SOME_OBJECT = { foo: 'bar' };
-
- test('formatByMonths: formats the months array', async function (assert) {
- assert.expect(103);
- const keyNameAssertions = (object, objectName) => {
- const objectKeys = Object.keys(object);
- assert.false(objectKeys.includes('counts'), `${objectName} doesn't include 'counts' key`);
- assert.true(objectKeys.includes('clients'), `${objectName} includes 'clients' key`);
- assert.true(objectKeys.includes('entity_clients'), `${objectName} includes 'entity_clients' key`);
- assert.true(
- objectKeys.includes('non_entity_clients'),
- `${objectName} includes 'non_entity_clients' key`
- );
- };
- const assertClientCounts = (object, originalObject) => {
- const newObjectKeys = ['clients', 'entity_clients', 'non_entity_clients'];
- const originalKeys = Object.keys(originalObject.counts).includes('entity_clients')
- ? newObjectKeys
- : ['clients', 'distinct_entities', 'non_entity_tokens'];
-
- newObjectKeys.forEach((key, i) => {
- assert.strictEqual(
- object[key],
- originalObject.counts[originalKeys[i]],
- `${object.month} ${key} equal original counts`
- );
- });
- };
-
- const formattedMonths = formatByMonths(MONTHS);
- assert.notEqual(formattedMonths, MONTHS, 'does not modify original array');
-
- formattedMonths.forEach((month) => {
- const originalMonth = MONTHS.find((m) => month.month === parseAPITimestamp(m.timestamp, 'M/yy'));
- // if originalMonth is found (not undefined) then the formatted month has an accurate, parsed timestamp
- assert.ok(originalMonth, `month has parsed timestamp of ${month.month}`);
- assert.ok(month.namespaces_by_key, `month includes 'namespaces_by_key' key`);
-
- keyNameAssertions(month, 'formatted month');
- assertClientCounts(month, originalMonth);
-
- assert.ok(month.new_clients.month, 'new clients key has a month key');
- keyNameAssertions(month.new_clients, 'formatted month new_clients');
- assertClientCounts(month.new_clients, originalMonth.new_clients);
-
- month.namespaces.forEach((namespace) => keyNameAssertions(namespace, 'namespace within month'));
- month.new_clients.namespaces.forEach((namespace) =>
- keyNameAssertions(namespace, 'new client namespaces within month')
- );
- });
-
- // method fails gracefully
- const expected = [
- {
- counts: null,
- month: '6/21',
- namespaces: [],
- namespaces_by_key: {},
- new_clients: {
- month: '6/21',
- namespaces: [],
- timestamp: '2021-06-01T00:00:00Z',
- },
- timestamp: '2021-06-01T00:00:00Z',
- },
- {
- counts: null,
- month: '7/21',
- namespaces: [],
- namespaces_by_key: {},
- new_clients: {
- month: '7/21',
- namespaces: [],
- timestamp: '2021-07-01T00:00:00Z',
- },
- timestamp: '2021-07-01T00:00:00Z',
- },
- ];
- assert.strictEqual(formatByMonths(SOME_OBJECT), SOME_OBJECT, 'it returns if arg is not an array');
- assert.propEqual(expected, formatByMonths(EMPTY_MONTHS), 'it does not error with null months');
- assert.ok(formatByMonths([...EMPTY_MONTHS, ...MONTHS]), 'it does not error with combined data');
- });
-
- test('formatByNamespace: formats namespace arrays with and without mounts', async function (assert) {
- assert.expect(102);
- const keyNameAssertions = (object, objectName) => {
- const objectKeys = Object.keys(object);
- assert.false(objectKeys.includes('counts'), `${objectName} doesn't include 'counts' key`);
- assert.true(objectKeys.includes('label'), `${objectName} includes 'label' key`);
- assert.true(objectKeys.includes('clients'), `${objectName} includes 'clients' key`);
- assert.true(objectKeys.includes('entity_clients'), `${objectName} includes 'entity_clients' key`);
- assert.true(
- objectKeys.includes('non_entity_clients'),
- `${objectName} includes 'non_entity_clients' key`
- );
- };
- const keyValueAssertions = (object, pathName, originalObject) => {
- const keysToAssert = ['clients', 'entity_clients', 'non_entity_clients'];
- assert.strictEqual(object.label, originalObject[pathName], `${pathName} matches label`);
-
- keysToAssert.forEach((key) => {
- assert.strictEqual(object[key], originalObject.counts[key], `number of ${key} equal original`);
- });
- };
-
- const formattedNamespaces = formatByNamespace(BY_NAMESPACE);
- assert.notEqual(formattedNamespaces, MONTHS, 'does not modify original array');
-
- formattedNamespaces.forEach((namespace) => {
- const origNamespace = BY_NAMESPACE.find((ns) => ns.namespace_path === namespace.label);
- keyNameAssertions(namespace, 'formatted namespace');
- keyValueAssertions(namespace, 'namespace_path', origNamespace);
-
- namespace.mounts.forEach((mount) => {
- const origMount = origNamespace.mounts.find((m) => m.mount_path === mount.label);
- keyNameAssertions(mount, 'formatted mount');
- keyValueAssertions(mount, 'mount_path', origMount);
- });
- });
-
- const nsWithoutMounts = {
- namespace_id: '96OwG',
- namespace_path: 'no-mounts-ns/',
- counts: {
- distinct_entities: 18290,
- entity_clients: 18290,
- non_entity_tokens: 18738,
- non_entity_clients: 18738,
- clients: 37028,
- },
- mounts: [],
- };
-
- const formattedNsWithoutMounts = formatByNamespace([nsWithoutMounts])[0];
- keyNameAssertions(formattedNsWithoutMounts, 'namespace without mounts');
- keyValueAssertions(formattedNsWithoutMounts, 'namespace_path', nsWithoutMounts);
- assert.strictEqual(formattedNsWithoutMounts.mounts.length, 0, 'formatted namespace has no mounts');
-
- assert.strictEqual(formatByNamespace(SOME_OBJECT), SOME_OBJECT, 'it returns if arg is not an array');
- });
-
- test('homogenizeClientNaming: homogenizes key names when both old and new keys exist, or just old key names', async function (assert) {
- assert.expect(168);
- const keyNameAssertions = (object, objectName) => {
- const objectKeys = Object.keys(object);
- assert.false(
- objectKeys.includes('distinct_entities'),
- `${objectName} doesn't include 'distinct_entities' key`
- );
- assert.false(
- objectKeys.includes('non_entity_tokens'),
- `${objectName} doesn't include 'non_entity_tokens' key`
- );
- assert.true(objectKeys.includes('entity_clients'), `${objectName} includes 'entity_clients' key`);
- assert.true(
- objectKeys.includes('non_entity_clients'),
- `${objectName} includes 'non_entity_clients' key`
- );
- };
-
- const transformedMonths = [...MONTHS];
- transformedMonths.forEach((month) => {
- month.counts = homogenizeClientNaming(month.counts);
- keyNameAssertions(month.counts, 'month counts');
-
- month.new_clients.counts = homogenizeClientNaming(month.new_clients.counts);
- keyNameAssertions(month.new_clients.counts, 'month new counts');
-
- month.namespaces.forEach((ns) => {
- ns.counts = homogenizeClientNaming(ns.counts);
- keyNameAssertions(ns.counts, 'namespace counts');
-
- ns.mounts.forEach((mount) => {
- mount.counts = homogenizeClientNaming(mount.counts);
- keyNameAssertions(mount.counts, 'mount counts');
- });
- });
-
- month.new_clients.namespaces.forEach((ns) => {
- ns.counts = homogenizeClientNaming(ns.counts);
- keyNameAssertions(ns.counts, 'namespace new counts');
-
- ns.mounts.forEach((mount) => {
- mount.counts = homogenizeClientNaming(mount.counts);
- keyNameAssertions(mount.counts, 'mount new counts');
- });
- });
- });
- });
-
- test('flattenDataset: removes the counts key and flattens the dataset', async function (assert) {
- assert.expect(22);
- const flattenedNamespace = flattenDataset(BY_NAMESPACE[0]);
- const flattenedMount = flattenDataset(BY_NAMESPACE[0].mounts[0]);
- const flattenedMonth = flattenDataset(MONTHS[0]);
- const flattenedNewMonthClients = flattenDataset(MONTHS[0].new_clients);
- const objectNullCounts = { counts: null, foo: 'bar' };
-
- const keyNameAssertions = (object, objectName) => {
- const objectKeys = Object.keys(object);
- assert.false(objectKeys.includes('counts'), `${objectName} doesn't include 'counts' key`);
- assert.true(objectKeys.includes('clients'), `${objectName} includes 'clients' key`);
- assert.true(objectKeys.includes('entity_clients'), `${objectName} includes 'entity_clients' key`);
- assert.true(
- objectKeys.includes('non_entity_clients'),
- `${objectName} includes 'non_entity_clients' key`
- );
- };
-
- keyNameAssertions(flattenedNamespace, 'namespace object');
- keyNameAssertions(flattenedMount, 'mount object');
- keyNameAssertions(flattenedMonth, 'month object');
- keyNameAssertions(flattenedNewMonthClients, 'month new_clients object');
-
- assert.strictEqual(
- flattenDataset(SOME_OBJECT),
- SOME_OBJECT,
- "it returns original object if counts key doesn't exist"
- );
-
- assert.strictEqual(
- flattenDataset(objectNullCounts),
- objectNullCounts,
- 'it returns original object if counts are null'
- );
-
- assert.propEqual(
- ['some array'],
- flattenDataset(['some array']),
- 'it fails gracefully if an array is passed in'
- );
- assert.strictEqual(flattenDataset(null), null, 'it fails gracefully if null is passed in');
- assert.strictEqual(
- flattenDataset('some string'),
- 'some string',
- 'it fails gracefully if a string is passed in'
- );
- assert.propEqual(
- new Object(),
- flattenDataset(new Object()),
- 'it fails gracefully if an empty object is passed in'
- );
- });
-
- test('sortMonthsByTimestamp: sorts timestamps chronologically, oldest to most recent', async function (assert) {
- assert.expect(4);
- const sortedMonths = sortMonthsByTimestamp(MONTHS);
- assert.ok(
- isBefore(parseAPITimestamp(sortedMonths[0].timestamp), parseAPITimestamp(sortedMonths[1].timestamp)),
- 'first timestamp date is earlier than second'
- );
- assert.ok(
- isAfter(parseAPITimestamp(sortedMonths[2].timestamp), parseAPITimestamp(sortedMonths[1].timestamp)),
- 'third timestamp date is later second'
- );
- assert.notEqual(sortedMonths[1], MONTHS[1], 'it does not modify original array');
- assert.strictEqual(sortedMonths[0], MONTHS[0], 'it does not modify original array');
- });
-
- test('namespaceArrayToObject: transforms data without modifying original', async function (assert) {
- assert.expect(30);
-
- const assertClientCounts = (object, originalObject) => {
- const valuesToCheck = ['clients', 'entity_clients', 'non_entity_clients'];
-
- valuesToCheck.forEach((key) => {
- assert.strictEqual(object[key], originalObject[key], `${key} equal original counts`);
- });
- };
- const totalClientsByNamespace = formatByNamespace(MONTHS[1].namespaces);
- const newClientsByNamespace = formatByNamespace(MONTHS[1].new_clients.namespaces);
-
- const byNamespaceKeyObject = namespaceArrayToObject(
- totalClientsByNamespace,
- newClientsByNamespace,
- '10/21'
- );
-
- assert.propEqual(
- totalClientsByNamespace,
- formatByNamespace(MONTHS[1].namespaces),
- 'it does not modify original array'
- );
- assert.propEqual(
- newClientsByNamespace,
- formatByNamespace(MONTHS[1].new_clients.namespaces),
- 'it does not modify original array'
- );
-
- const namespaceKeys = Object.keys(byNamespaceKeyObject);
- namespaceKeys.forEach((nsKey) => {
- const newNsObject = byNamespaceKeyObject[nsKey];
- const originalNsData = totalClientsByNamespace.find((ns) => ns.label === nsKey);
- assertClientCounts(newNsObject, originalNsData);
- const mountKeys = Object.keys(newNsObject.mounts_by_key);
- mountKeys.forEach((mKey) => {
- const mountData = originalNsData.mounts.find((m) => m.label === mKey);
- assertClientCounts(newNsObject.mounts_by_key[mKey], mountData);
- });
- });
-
- namespaceKeys.forEach((nsKey) => {
- const newNsObject = byNamespaceKeyObject[nsKey];
- const originalNsData = newClientsByNamespace.find((ns) => ns.label === nsKey);
- if (!originalNsData) return;
- assertClientCounts(newNsObject.new_clients, originalNsData);
- const mountKeys = Object.keys(newNsObject.mounts_by_key);
-
- mountKeys.forEach((mKey) => {
- const mountData = originalNsData.mounts.find((m) => m.label === mKey);
- assertClientCounts(newNsObject.mounts_by_key[mKey].new_clients, mountData);
- });
- });
-
- assert.propEqual(
- {},
- namespaceArrayToObject(null, null, '10/21'),
- 'returns an empty object when totalClientsByNamespace = null'
- );
- });
-});
diff --git a/ui/tests/integration/utils/date-formatters-test.js b/ui/tests/integration/utils/date-formatters-test.js
deleted file mode 100644
index 3d285d146..000000000
--- a/ui/tests/integration/utils/date-formatters-test.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { formatRFC3339, isSameDay, isSameMonth, isSameYear } from 'date-fns';
-import { parseAPITimestamp, formatChartDate } from 'core/utils/date-formatters';
-
-module('Integration | Util | date formatters utils', function (hooks) {
- setupTest(hooks);
-
- test('parseAPITimestamp: parses API timestamp string irrespective of timezone', async function (assert) {
- assert.expect(6);
- const DATE = new Date('2012-06-10T15:30:45');
- const API_TIMESTAMP = formatRFC3339(DATE).split('T')[0].concat('T00:00:00Z');
- const UNIX_TIME = DATE.getTime();
- assert.strictEqual(
- parseAPITimestamp(UNIX_TIME),
- undefined,
- 'it returns undefined if timestamp is not a string'
- );
-
- const parsedTimestamp = parseAPITimestamp(API_TIMESTAMP);
-
- assert.true(parsedTimestamp instanceof Date, 'parsed timestamp is a date object');
- assert.true(isSameYear(parsedTimestamp, DATE), 'parsed timestamp is correct year');
- assert.true(isSameMonth(parsedTimestamp, DATE), 'parsed timestamp is correct month');
- assert.true(isSameDay(parsedTimestamp, DATE), 'parsed timestamp is correct day');
-
- const formattedTimestamp = parseAPITimestamp(API_TIMESTAMP, 'MM yyyy');
- assert.strictEqual(formattedTimestamp, '06 2012', 'it formats the date');
- });
-
- test('formatChartDate: expand chart date to full month and year', async function (assert) {
- assert.expect(1);
- const chartDate = '03/21';
- assert.strictEqual(formatChartDate(chartDate), 'March 2021', 'it re-formats the date');
- });
-});
diff --git a/ui/tests/integration/utils/field-to-attrs-test.js b/ui/tests/integration/utils/field-to-attrs-test.js
deleted file mode 100644
index b28a5c017..000000000
--- a/ui/tests/integration/utils/field-to-attrs-test.js
+++ /dev/null
@@ -1,163 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { run } from '@ember/runloop';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import fieldToAttrs, { expandAttributeMeta } from 'vault/utils/field-to-attrs';
-
-module('Integration | Util | field to attrs', function (hooks) {
- setupTest(hooks);
-
- const PATH_ATTR = { type: 'string', name: 'path', options: {} };
- const DESCRIPTION_ATTR = { type: 'string', name: 'description', options: { editType: 'textarea' } };
- const DEFAULT_LEASE_ATTR = {
- type: undefined,
- name: 'config.defaultLeaseTtl',
- options: { label: 'Default Lease TTL', editType: 'ttl' },
- };
-
- const OTHER_DEFAULT_LEASE_ATTR = {
- type: undefined,
- name: 'otherConfig.defaultLeaseTtl',
- options: { label: 'Default Lease TTL', editType: 'ttl' },
- };
- const MAX_LEASE_ATTR = {
- type: undefined,
- name: 'config.maxLeaseTtl',
- options: { label: 'Max Lease TTL', editType: 'ttl' },
- };
- const OTHER_MAX_LEASE_ATTR = {
- type: undefined,
- name: 'otherConfig.maxLeaseTtl',
- options: { label: 'Max Lease TTL', editType: 'ttl' },
- };
-
- test('it extracts attrs', function (assert) {
- assert.expect(1);
- const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
- run(() => {
- const [attr] = expandAttributeMeta(model, ['path']);
- assert.deepEqual(attr, PATH_ATTR, 'returns attribute meta');
- });
- });
-
- test('it extracts more than one attr', function (assert) {
- assert.expect(2);
- const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
- run(() => {
- const [path, desc] = expandAttributeMeta(model, ['path', 'description']);
- assert.deepEqual(path, PATH_ATTR, 'returns attribute meta');
- assert.deepEqual(desc, DESCRIPTION_ATTR, 'returns attribute meta');
- });
- });
-
- test('it extracts fieldGroups', function (assert) {
- assert.expect(1);
- const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
- run(() => {
- const groups = fieldToAttrs(model, [{ default: ['path'] }, { Options: ['description'] }]);
- const expected = [{ default: [PATH_ATTR] }, { Options: [DESCRIPTION_ATTR] }];
- assert.deepEqual(groups, expected, 'expands all given groups');
- });
- });
-
- test('it extracts arrays as fieldGroups', function (assert) {
- assert.expect(1);
- const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
- run(() => {
- const groups = fieldToAttrs(model, [
- { default: ['path', 'description'] },
- { Options: ['description'] },
- ]);
- const expected = [{ default: [PATH_ATTR, DESCRIPTION_ATTR] }, { Options: [DESCRIPTION_ATTR] }];
- assert.deepEqual(groups, expected, 'expands all given groups');
- });
- });
-
- test('it extracts model-fragment attributes with brace expansion', function (assert) {
- assert.expect(3);
- const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
- run(() => {
- const [attr] = expandAttributeMeta(model, ['config.{defaultLeaseTtl}']);
- assert.deepEqual(attr, DEFAULT_LEASE_ATTR, 'properly extracts model fragment attr');
- });
-
- run(() => {
- const [defaultLease, maxLease] = expandAttributeMeta(model, ['config.{defaultLeaseTtl,maxLeaseTtl}']);
- assert.deepEqual(defaultLease, DEFAULT_LEASE_ATTR, 'properly extracts default lease');
- assert.deepEqual(maxLease, MAX_LEASE_ATTR, 'properly extracts max lease');
- });
- });
-
- test('it extracts model-fragment attributes with double brace expansion', function (assert) {
- assert.expect(4);
- const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
- run(() => {
- const [configDefault, configMax, otherConfigDefault, otherConfigMax] = expandAttributeMeta(model, [
- '{config,otherConfig}.{defaultLeaseTtl,maxLeaseTtl}',
- ]);
- assert.deepEqual(configDefault, DEFAULT_LEASE_ATTR, 'properly extracts config.defaultLeaseTTL');
- assert.deepEqual(
- otherConfigDefault,
- OTHER_DEFAULT_LEASE_ATTR,
- 'properly extracts otherConfig.defaultLeaseTTL'
- );
-
- assert.deepEqual(configMax, MAX_LEASE_ATTR, 'properly extracts config.maxLeaseTTL');
- assert.deepEqual(otherConfigMax, OTHER_MAX_LEASE_ATTR, 'properly extracts otherConfig.maxLeaseTTL');
- });
- });
-
- test('it extracts model-fragment attributes with dot notation', function (assert) {
- assert.expect(3);
- const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
- run(() => {
- const [attr] = expandAttributeMeta(model, ['config.defaultLeaseTtl']);
- assert.deepEqual(attr, DEFAULT_LEASE_ATTR, 'properly extracts model fragment attr');
- });
-
- run(() => {
- const [defaultLease, maxLease] = expandAttributeMeta(model, [
- 'config.defaultLeaseTtl',
- 'config.maxLeaseTtl',
- ]);
- assert.deepEqual(defaultLease, DEFAULT_LEASE_ATTR, 'properly extracts model fragment attr');
- assert.deepEqual(maxLease, MAX_LEASE_ATTR, 'properly extracts model fragment attr');
- });
- });
-
- test('it extracts fieldGroups from model-fragment attributes with brace expansion', function (assert) {
- assert.expect(1);
- const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
- const expected = [
- { default: [PATH_ATTR, DEFAULT_LEASE_ATTR, MAX_LEASE_ATTR] },
- { Options: [DESCRIPTION_ATTR] },
- ];
- run(() => {
- const groups = fieldToAttrs(model, [
- { default: ['path', 'config.{defaultLeaseTtl,maxLeaseTtl}'] },
- { Options: ['description'] },
- ]);
- assert.deepEqual(groups, expected, 'properly extracts fieldGroups with brace expansion');
- });
- });
-
- test('it extracts fieldGroups from model-fragment attributes with dot notation', function (assert) {
- assert.expect(1);
- const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
- const expected = [
- { default: [DEFAULT_LEASE_ATTR, PATH_ATTR, MAX_LEASE_ATTR] },
- { Options: [DESCRIPTION_ATTR] },
- ];
- run(() => {
- const groups = fieldToAttrs(model, [
- { default: ['config.defaultLeaseTtl', 'path', 'config.maxLeaseTtl'] },
- { Options: ['description'] },
- ]);
- assert.deepEqual(groups, expected, 'properly extracts fieldGroups with dot notation');
- });
- });
-});
diff --git a/ui/tests/integration/utils/parse-pki-cert-test.js b/ui/tests/integration/utils/parse-pki-cert-test.js
deleted file mode 100644
index 4b31833da..000000000
--- a/ui/tests/integration/utils/parse-pki-cert-test.js
+++ /dev/null
@@ -1,307 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { parseCertificate, parseExtensions, parseSubject, formatValues } from 'vault/utils/parse-pki-cert';
-import * as asn1js from 'asn1js';
-import { fromBase64, stringToArrayBuffer } from 'pvutils';
-import { Certificate } from 'pkijs';
-import { addHours, fromUnixTime, isSameDay } from 'date-fns';
-import errorMessage from 'vault/utils/error-message';
-import { OTHER_OIDs, SAN_TYPES } from 'vault/utils/parse-pki-cert-oids';
-import {
- certWithoutCN,
- loadedCert,
- pssTrueCert,
- skeletonCert,
- unsupportedOids,
- unsupportedSignatureRoot,
- unsupportedSignatureInt,
-} from 'vault/tests/helpers/pki/values';
-import { verifyCertificates } from 'vault/utils/parse-pki-cert';
-import { jsonToCertObject } from 'vault/utils/parse-pki-cert';
-import { verifySignature } from 'vault/utils/parse-pki-cert';
-
-module('Integration | Util | parse pki certificate', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.getErrorMessages = (certErrors) => certErrors.map((error) => errorMessage(error));
- this.certSchema = (cert) => {
- const cert_base64 = cert.replace(/(-----(BEGIN|END) CERTIFICATE-----|\n)/g, '');
- const cert_der = fromBase64(cert_base64);
- const cert_asn1 = asn1js.fromBER(stringToArrayBuffer(cert_der));
- return new Certificate({ schema: cert_asn1.result });
- };
- this.parsableLoadedCert = this.certSchema(loadedCert);
- this.parsableUnsupportedCert = this.certSchema(unsupportedOids);
- });
-
- test('it parses a certificate with supported values', async function (assert) {
- assert.expect(2);
- // certificate contains all allowable params
- const parsedCert = parseCertificate(loadedCert);
- assert.propEqual(
- parsedCert,
- {
- alt_names: 'altname1, altname2',
- can_parse: true,
- common_name: 'common-name.com',
- country: 'France',
- other_sans: '1.3.1.4.1.5.9.2.6;UTF8:some-utf-string',
- exclude_cn_from_sans: true,
- ip_sans: '192.158.1.38, 1234:0fd2:5621:0001:0089:0000:0000:4500',
- key_usage: 'CertSign, CRLSign',
- locality: 'Paris',
- max_path_length: 17,
- not_valid_after: 1678210083,
- not_valid_before: 1675445253,
- organization: 'Widget',
- ou: 'Finance',
- parsing_errors: [],
- permitted_dns_domains: 'dnsname1.com, dsnname2.com',
- postal_code: '123456',
- province: 'Champagne',
- subject_serial_number: 'cereal1292',
- signature_bits: '256',
- street_address: '234 sesame',
- ttl: '768h',
- uri_sans: 'testuri1, testuri2',
- use_pss: false,
- },
- 'it contains expected attrs, cn is excluded from alt_names (exclude_cn_from_sans: true) and ipV6 is compressed correctly'
- );
- assert.ok(
- isSameDay(
- addHours(fromUnixTime(parsedCert.not_valid_before), Number(parsedCert.ttl.split('h')[0])),
- fromUnixTime(parsedCert.not_valid_after),
- 'ttl value is correct'
- )
- );
- });
-
- test('it parses a certificate with use_pass=true and exclude_cn_from_sans=false', async function (assert) {
- assert.expect(2);
- const parsedPssCert = parseCertificate(pssTrueCert);
- assert.propContains(
- parsedPssCert,
- { signature_bits: '256', ttl: '768h', use_pss: true },
- 'returns signature_bits value and use_pss is true'
- );
- assert.propContains(
- parsedPssCert,
- {
- alt_names: 'common-name.com',
- can_parse: true,
- common_name: 'common-name.com',
- exclude_cn_from_sans: false,
- },
- 'common name is included in alt_names'
- );
- });
-
- test('it returns parsing_errors when certificate has unsupported values', async function (assert) {
- assert.expect(2);
- const parsedCert = parseCertificate(unsupportedOids); // contains unsupported subject and extension OIDs
- const parsingErrors = this.getErrorMessages(parsedCert.parsing_errors);
- assert.propContains(
- parsedCert,
- {
- alt_names: 'dns-NameSupported',
- common_name: 'fancy-cert-unsupported-subj-and-ext-oids',
- ip_sans: '192.158.1.38',
- parsing_errors: [{}, {}],
- uri_sans: 'uriSupported',
- },
- 'supported values are present when unsupported values exist'
- );
- assert.propEqual(
- parsingErrors,
- [
- 'certificate contains unsupported subject OIDs: 1.2.840.113549.1.9.1',
- 'certificate contains unsupported extension OIDs: 2.5.29.37',
- ],
- 'it contains expected error messages'
- );
- });
-
- test('it returns attr with a null value if nonexistent', async function (assert) {
- assert.expect(1);
- const onlyHasCommonName = parseCertificate(skeletonCert);
- assert.propContains(
- onlyHasCommonName,
- {
- alt_names: 'common-name.com',
- common_name: 'common-name.com',
- country: null,
- ip_sans: null,
- locality: null,
- max_path_length: undefined,
- organization: null,
- ou: null,
- postal_code: null,
- province: null,
- subject_serial_number: null,
- street_address: null,
- uri_sans: null,
- },
- 'it contains expected attrs'
- );
- });
-
- test('the helper parseSubject returns object with correct key/value pairs', async function (assert) {
- assert.expect(3);
- const supportedSubj = parseSubject(this.parsableLoadedCert.subject.typesAndValues);
- assert.propEqual(
- supportedSubj,
- {
- subjErrors: [],
- subjValues: {
- common_name: 'common-name.com',
- country: 'France',
- locality: 'Paris',
- organization: 'Widget',
- ou: 'Finance',
- postal_code: '123456',
- province: 'Champagne',
- subject_serial_number: 'cereal1292',
- street_address: '234 sesame',
- },
- },
- 'it returns supported subject values'
- );
-
- const unsupportedSubj = parseSubject(this.parsableUnsupportedCert.subject.typesAndValues);
- assert.propEqual(
- this.getErrorMessages(unsupportedSubj.subjErrors),
- ['certificate contains unsupported subject OIDs: 1.2.840.113549.1.9.1'],
- 'it returns subject errors'
- );
- assert.ok(
- unsupportedSubj.subjErrors.every((e) => e instanceof Error),
- 'subjErrors contain error objects'
- );
- });
-
- test('the helper parseExtensions returns object with correct key/value pairs', async function (assert) {
- assert.expect(11);
- // assert supported extensions return correct type
- const supportedExtensions = parseExtensions(this.parsableLoadedCert.extensions);
- let { extValues, extErrors } = supportedExtensions;
- for (const keyName in SAN_TYPES) {
- assert.ok(Array.isArray(extValues[keyName]), `${keyName} is an array`);
- }
- assert.ok(Array.isArray(extValues.permitted_dns_domains), 'permitted_dns_domains is an array');
- assert.ok(Number.isInteger(extValues.max_path_length), 'max_path_length is an integer');
- assert.propEqual(extValues.key_usage, ['CertSign', 'CRLSign'], 'parses key_usage');
- assert.strictEqual(extErrors.length, 0, 'no extension errors');
-
- // assert unsupported extensions return errors
- const unsupportedExt = parseExtensions(this.parsableUnsupportedCert.extensions);
- ({ extValues, extErrors } = unsupportedExt);
- assert.propEqual(
- this.getErrorMessages(extErrors),
- ['certificate contains unsupported extension OIDs: 2.5.29.37'],
- 'it returns extension errors'
- );
- assert.ok(
- extErrors.every((e) => e instanceof Error),
- 'subjErrors contain error objects'
- );
- assert.ok(Number.isInteger(extValues.max_path_length), 'max_path_length is an integer');
- });
-
- test('the helper formatValues returns object with correct types', async function (assert) {
- assert.expect(1);
- const supportedSubj = parseSubject(this.parsableLoadedCert.subject.typesAndValues);
- const supportedExtensions = parseExtensions(this.parsableLoadedCert.extensions);
- assert.propContains(
- formatValues(supportedSubj, supportedExtensions),
- {
- alt_names: 'altname1, altname2',
- ip_sans: '192.158.1.38, 1234:0fd2:5621:0001:0089:0000:0000:4500',
- permitted_dns_domains: 'dnsname1.com, dsnname2.com',
- uri_sans: 'testuri1, testuri2',
- parsing_errors: [],
- exclude_cn_from_sans: true,
- },
- `values for ${Object.keys(SAN_TYPES).join(', ')} are comma separated strings (and no longer arrays)`
- );
- });
-
- test('the helper verifyCertificates catches errors', async function (assert) {
- assert.expect(5);
- const verifiedRoot = await verifyCertificates(unsupportedSignatureRoot, unsupportedSignatureRoot);
- assert.true(verifiedRoot, 'returns true for root certificate');
- const verifiedInt = await verifyCertificates(unsupportedSignatureInt, unsupportedSignatureInt);
- assert.false(verifiedInt, 'returns false for intermediate cert');
-
- const filterExtensions = (list, oid) => list.filter((ext) => ext.extnID !== oid);
- const { subject_key_identifier, authority_key_identifier } = OTHER_OIDs;
- const testCert = jsonToCertObject(unsupportedSignatureRoot);
- const certWithoutSKID = testCert;
- certWithoutSKID.extensions = filterExtensions(testCert.extensions, subject_key_identifier);
- assert.false(
- await verifySignature(certWithoutSKID, certWithoutSKID),
- 'returns false if no subject key ID'
- );
-
- const certWithoutAKID = testCert;
- certWithoutAKID.extensions = filterExtensions(testCert.extensions, authority_key_identifier);
- assert.false(await verifySignature(certWithoutAKID, certWithoutAKID), 'returns false if no AKID');
-
- const certWithoutKeyID = testCert;
- certWithoutAKID.extensions = [];
- assert.false(
- await verifySignature(certWithoutKeyID, certWithoutKeyID),
- 'returns false if neither SKID or AKID'
- );
- });
-
- test('it fails silently when passed null', async function (assert) {
- assert.expect(3);
- const parsedCert = parseCertificate(certWithoutCN);
- assert.propEqual(
- parsedCert,
- {
- can_parse: true,
- common_name: null,
- country: null,
- exclude_cn_from_sans: false,
- key_usage: null,
- locality: null,
- max_path_length: 10,
- not_valid_after: 1989876490,
- not_valid_before: 1674516490,
- organization: null,
- ou: null,
- parsing_errors: [{}, {}],
- postal_code: null,
- province: null,
- subject_serial_number: null,
- signature_bits: '256',
- street_address: null,
- ttl: '87600h',
- use_pss: false,
- },
- 'it parses a cert without CN'
- );
- const parsingErrors = this.getErrorMessages(parsedCert.parsing_errors);
- assert.propEqual(
- parsingErrors,
- [
- 'certificate contains unsupported subject OIDs: 1.2.840.113549.1.9.1',
- 'certificate contains unsupported extension OIDs: 2.5.29.37',
- ],
- 'it returns correct errors'
- );
- assert.propEqual(
- formatValues(null, null),
- { parsing_errors: [Error('error parsing certificate')] },
- 'it returns error if unable to format values'
- );
- });
-});
diff --git a/ui/tests/pages/access/identity/aliases/add.js b/ui/tests/pages/access/identity/aliases/add.js
deleted file mode 100644
index 1a5c9d591..000000000
--- a/ui/tests/pages/access/identity/aliases/add.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, visitable } from 'ember-cli-page-object';
-import editForm from 'vault/tests/pages/components/identity/edit-form';
-
-export default create({
- visit: visitable('/vault/access/identity/:item_type/aliases/add/:id'),
- editForm,
-});
diff --git a/ui/tests/pages/access/identity/aliases/index.js b/ui/tests/pages/access/identity/aliases/index.js
deleted file mode 100644
index e43fd116d..000000000
--- a/ui/tests/pages/access/identity/aliases/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, clickable, text, visitable, collection } from 'ember-cli-page-object';
-import flashMessage from 'vault/tests/pages/components/flash-message';
-
-export default create({
- visit: visitable('/vault/access/identity/:item_type/aliases'),
- flashMessage,
- items: collection('[data-test-identity-row]', {
- menu: clickable('[data-test-popup-menu-trigger]'),
- name: text('[data-test-identity-link]'),
- }),
- delete: clickable('[data-test-item-delete]', {
- testContainer: '#ember-testing',
- }),
- confirmDelete: clickable('[data-test-confirm-button]'),
-});
diff --git a/ui/tests/pages/access/identity/aliases/show.js b/ui/tests/pages/access/identity/aliases/show.js
deleted file mode 100644
index 0710b96fb..000000000
--- a/ui/tests/pages/access/identity/aliases/show.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, clickable, collection, contains, visitable } from 'ember-cli-page-object';
-import flashMessage from 'vault/tests/pages/components/flash-message';
-import infoTableRow from 'vault/tests/pages/components/info-table-row';
-
-export default create({
- visit: visitable('/vault/access/identity/:item_type/aliases/:alias_id'),
- flashMessage,
- nameContains: contains('[data-test-alias-name]'),
- rows: collection('[data-test-component="info-table-row"]', infoTableRow),
- edit: clickable('[data-test-alias-edit-link]'),
-});
diff --git a/ui/tests/pages/access/identity/create.js b/ui/tests/pages/access/identity/create.js
deleted file mode 100644
index 34c70d780..000000000
--- a/ui/tests/pages/access/identity/create.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, visitable } from 'ember-cli-page-object';
-import editForm from 'vault/tests/pages/components/identity/edit-form';
-
-export default create({
- visit: visitable('/vault/access/identity/:item_type/create'),
- editForm,
- createItem(item_type, type) {
- if (type) {
- return this.visit({ item_type }).editForm.type(type).submit();
- }
- return this.visit({ item_type }).editForm.submit();
- },
-});
diff --git a/ui/tests/pages/access/identity/index.js b/ui/tests/pages/access/identity/index.js
deleted file mode 100644
index 73367d0db..000000000
--- a/ui/tests/pages/access/identity/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, clickable, text, visitable, collection } from 'ember-cli-page-object';
-import flashMessage from 'vault/tests/pages/components/flash-message';
-
-export default create({
- visit: visitable('/vault/access/identity/:item_type'),
- flashMessage,
- items: collection('[data-test-identity-row]', {
- menu: clickable('[data-test-popup-menu-trigger]'),
- name: text('[data-test-identity-link]'),
- }),
-
- delete: clickable('[data-test-item-delete]', {
- testContainer: '#ember-testing',
- }),
- confirmDelete: clickable('[data-test-confirm-button]'),
-});
diff --git a/ui/tests/pages/access/identity/show.js b/ui/tests/pages/access/identity/show.js
deleted file mode 100644
index 71fcf3da6..000000000
--- a/ui/tests/pages/access/identity/show.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, clickable, collection, contains, visitable } from 'ember-cli-page-object';
-import flashMessage from 'vault/tests/pages/components/flash-message';
-import infoTableRow from 'vault/tests/pages/components/info-table-row';
-
-export default create({
- visit: visitable('/vault/access/identity/:item_type/:item_id'),
- flashMessage,
- nameContains: contains('[data-test-identity-item-name]'),
- rows: collection('[data-test-component="info-table-row"]', infoTableRow),
- edit: clickable('[data-test-entity-edit-link]'),
-});
diff --git a/ui/tests/pages/access/methods.js b/ui/tests/pages/access/methods.js
deleted file mode 100644
index 92d834f20..000000000
--- a/ui/tests/pages/access/methods.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, attribute, visitable, collection, hasClass, text } from 'ember-cli-page-object';
-
-export default create({
- visit: visitable('/vault/access/'),
- methodsLink: {
- isActive: hasClass('active'),
- text: text(),
- scope: '[data-test-sidebar-nav-link="Authentication methods"]',
- },
-
- backendLinks: collection('[data-test-auth-backend-link]', {
- path: text('[data-test-path]'),
- id: attribute('data-test-id', '[data-test-path]'),
- }),
-
- findLinkById(id) {
- return this.backendLinks.filterBy('id', id)[0];
- },
-});
diff --git a/ui/tests/pages/access/namespaces/index.js b/ui/tests/pages/access/namespaces/index.js
deleted file mode 100644
index 4ca5fe393..000000000
--- a/ui/tests/pages/access/namespaces/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, visitable } from 'ember-cli-page-object';
-
-export default create({
- visit: visitable('/vault/access/namespaces'),
-});
diff --git a/ui/tests/pages/auth.js b/ui/tests/pages/auth.js
deleted file mode 100644
index 029d52673..000000000
--- a/ui/tests/pages/auth.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, visitable, fillable, clickable } from 'ember-cli-page-object';
-import { settled } from '@ember/test-helpers';
-import VAULT_KEYS from 'vault/tests/helpers/vault-keys';
-
-const { rootToken } = VAULT_KEYS;
-
-export default create({
- visit: visitable('/vault/auth'),
- logout: visitable('/vault/logout'),
- submit: clickable('[data-test-auth-submit]'),
- tokenInput: fillable('[data-test-token]'),
- usernameInput: fillable('[data-test-username]'),
- passwordInput: fillable('[data-test-password]'),
- namespaceInput: fillable('[data-test-auth-form-ns-input]'),
- optionsToggle: clickable('[data-test-auth-form-options-toggle]'),
- mountPath: fillable('[data-test-auth-form-mount-path]'),
-
- login: async function (token = rootToken) {
- // make sure we're always logged out and logged back in
- await this.logout();
- await settled();
- // clear session storage to ensure we have a clean state
- window.localStorage.clear();
- await this.visit({ with: 'token' });
- await settled();
- return this.tokenInput(token).submit();
- },
- loginUsername: async function (username, password, path) {
- // make sure we're always logged out and logged back in
- await this.logout();
- await settled();
- // clear local storage to ensure we have a clean state
- window.localStorage.clear();
- await this.visit({ with: 'userpass' });
- await settled();
- if (path) {
- await this.optionsToggle();
- await this.mountPath(path);
- }
- await this.usernameInput(username);
- return this.passwordInput(password).submit();
- },
- loginNs: async function (ns) {
- // make sure we're always logged out and logged back in
- await this.logout();
- await settled();
- // clear session storage to ensure we have a clean state
- window.localStorage.clear();
- await this.visit({ with: 'token' });
- await settled();
- await this.namespaceInput(ns);
- await settled();
- await this.tokenInput(rootToken).submit();
- return;
- },
-});
diff --git a/ui/tests/pages/components/alert-banner.js b/ui/tests/pages/components/alert-banner.js
deleted file mode 100644
index e0a91380e..000000000
--- a/ui/tests/pages/components/alert-banner.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { text } from 'ember-cli-page-object';
-
-export default {
- errorText: text('[data-test-error]'),
-};
diff --git a/ui/tests/pages/components/auth-config-form/options.js b/ui/tests/pages/components/auth-config-form/options.js
deleted file mode 100644
index ecf77082d..000000000
--- a/ui/tests/pages/components/auth-config-form/options.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { clickable, fillable } from 'ember-cli-page-object';
-
-import fields from '../form-field';
-export default {
- ...fields,
- ttlValue: fillable('[data-test-ttl-value]'),
- ttlUnit: fillable('[data-test-ttl-value]'),
- save: clickable('[data-test-save-config]'),
-};
diff --git a/ui/tests/pages/components/auth-form.js b/ui/tests/pages/components/auth-form.js
deleted file mode 100644
index 859a0e0bb..000000000
--- a/ui/tests/pages/components/auth-form.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { collection, clickable, fillable, text, value, isPresent } from 'ember-cli-page-object';
-
-export default {
- tabs: collection('[data-test-auth-method]', {
- name: text(),
- link: clickable('[data-test-auth-method-link]'),
- }),
- selectMethod: fillable('[data-test-select=auth-method]'),
- username: fillable('[data-test-username]'),
- token: fillable('[data-test-token]'),
- tokenValue: value('[data-test-token]'),
- password: fillable('[data-test-password]'),
- errorText: text('[data-test-auth-error]'),
- errorMessagePresent: isPresent('[data-test-auth-error]'),
- descriptionText: text('[data-test-description]'),
- login: clickable('[data-test-auth-submit]'),
- oidcRole: fillable('[data-test-role]'),
- oidcMoreOptions: clickable('[data-test-yield-content] button'),
- oidcMountPath: fillable('#custom-path'),
-};
diff --git a/ui/tests/pages/components/auth-jwt.js b/ui/tests/pages/components/auth-jwt.js
deleted file mode 100644
index 8e489f5fa..000000000
--- a/ui/tests/pages/components/auth-jwt.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { text, isPresent, clickable, fillable } from 'ember-cli-page-object';
-
-export default {
- jwt: fillable('[data-test-jwt]'),
- jwtPresent: isPresent('[data-test-jwt]'),
- role: fillable('[data-test-role]'),
- rolePresent: isPresent('[data-test-role]'),
- login: clickable('[data-test-auth-submit]'),
- loginButtonText: text('[data-test-auth-submit]'),
- yieldContent: text('[data-test-yield-content]'),
-};
diff --git a/ui/tests/pages/components/calendar-widget.js b/ui/tests/pages/components/calendar-widget.js
deleted file mode 100644
index 2b62d98c8..000000000
--- a/ui/tests/pages/components/calendar-widget.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { clickable, create, isPresent } from 'ember-cli-page-object';
-
-export default create({
- clickPreviousYear: clickable('[data-test-previous-year]'),
- clickCurrentMonth: clickable('[data-test-current-month]'),
- clickCurrentBillingPeriod: clickable('[data-test-current-billing-period]'),
- customEndMonthBtn: clickable('[data-test-show-calendar]'),
- menuToggle: clickable('[data-test-calendar-widget-trigger]'),
- showsCalendar: isPresent('[data-test-calendar-widget-container]'),
- dateRangeTrigger: '[data-test-show-calendar]',
- async openCalendar() {
- await this.menuToggle();
- await this.customEndMonthBtn();
- return;
- },
-});
diff --git a/ui/tests/pages/components/console/ui-panel.js b/ui/tests/pages/components/console/ui-panel.js
deleted file mode 100644
index b53083288..000000000
--- a/ui/tests/pages/components/console/ui-panel.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { text, triggerable, clickable, collection, fillable, value, isPresent } from 'ember-cli-page-object';
-import { getter } from 'ember-cli-page-object/macros';
-import { settled } from '@ember/test-helpers';
-
-import keys from 'vault/lib/keycodes';
-
-export default {
- toggle: clickable('[data-test-console-toggle]'),
- consoleInput: fillable('[data-test-component="console/command-input"] input'),
- consoleInputValue: value('[data-test-component="console/command-input"] input'),
- logOutput: text('[data-test-component="console/output-log"]'),
- logOutputItems: collection('[data-test-component="console/output-log"] > div', {
- text: text(),
- }),
- lastLogOutput: getter(function () {
- const count = this.logOutputItems.length;
- const outputItemText = this.logOutputItems.objectAt(count - 1).text;
- return outputItemText;
- }),
- logTextItems: collection('[data-test-component="console/log-text"]', {
- text: text(),
- }),
- lastTextOutput: getter(function () {
- const count = this.logTextItems.length;
- return this.logTextItems.objectAt(count - 1).text;
- }),
- logJSONItems: collection('[data-test-component="console/log-json"]', {
- text: text(),
- }),
- lastJSONOutput: getter(function () {
- const count = this.logJSONItems.length;
- return this.logJSONItems.objectAt(count - 1).text;
- }),
- up: triggerable('keyup', '[data-test-component="console/command-input"] input', {
- eventProperties: { keyCode: keys.UP },
- }),
- down: triggerable('keyup', '[data-test-component="console/command-input"] input', {
- eventProperties: { keyCode: keys.DOWN },
- }),
- enter: triggerable('keyup', '[data-test-component="console/command-input"] input', {
- eventProperties: { keyCode: keys.ENTER },
- }),
- hasInput: isPresent('[data-test-component="console/command-input"] input'),
- runCommands: async function (commands) {
- const toExecute = Array.isArray(commands) ? commands : [commands];
- for (const command of toExecute) {
- await this.consoleInput(command);
- await this.enter();
- await settled();
- }
- },
-};
diff --git a/ui/tests/pages/components/control-group-success.js b/ui/tests/pages/components/control-group-success.js
deleted file mode 100644
index cb6f09660..000000000
--- a/ui/tests/pages/components/control-group-success.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { isPresent, fillable, clickable } from 'ember-cli-page-object';
-
-export default {
- showsJsonViewer: isPresent('[data-test-json-viewer]'),
- showsNavigateMessage: isPresent('[data-test-navigate-message]'),
- showsUnwrapForm: isPresent('[data-test-unwrap-form]'),
- navigate: clickable('[data-test-navigate-button]'),
- unwrap: clickable('[data-test-unwrap-button]'),
- token: fillable('[data-test-token-input]'),
-};
diff --git a/ui/tests/pages/components/control-group.js b/ui/tests/pages/components/control-group.js
deleted file mode 100644
index 063741691..000000000
--- a/ui/tests/pages/components/control-group.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { isPresent, clickable, text } from 'ember-cli-page-object';
-
-export default {
- showsAccessorCallout: isPresent('[data-test-accessor-callout]'),
- authorizationText: text('[data-test-authorizations]'),
- bannerPrefix: text('[data-test-banner-prefix]'),
- bannerText: text('[data-test-banner-text]'),
- requestorText: text('[data-test-requestor-text]'),
- showsTokenText: isPresent('[data-test-token]'),
- refresh: clickable('[data-test-refresh-button]'),
- authorize: clickable('[data-test-authorize-button]'),
- showsSuccessComponent: isPresent('[data-test-control-group-success]'),
-
- accessor: text('[data-test-accessor-value]'),
- token: text('[data-test-token-value]'),
- showsRefresh: isPresent('[data-test-refresh-button]'),
- showsAuthorize: isPresent('[data-test-authorize-button]'),
- showsBackLink: isPresent('[data-test-back-link]'),
-};
diff --git a/ui/tests/pages/components/edit-form.js b/ui/tests/pages/components/edit-form.js
deleted file mode 100644
index c84f79f11..000000000
--- a/ui/tests/pages/components/edit-form.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { clickable, isPresent, text } from 'ember-cli-page-object';
-import fields from './form-field';
-export default {
- ...fields,
- submit: clickable('[data-test-edit-form-submit]'),
- deleteButton: clickable('[data-test-confirm-action-trigger]'),
- deleteConfirm: clickable('[data-test-confirm-button]'),
- deleteText: text('[data-test-edit-delete-text]'),
- showsDelete: isPresent('[data-test-edit-delete-text]'),
- errorText: text('[data-test-edit-form-error]'),
-};
diff --git a/ui/tests/pages/components/flash-message.js b/ui/tests/pages/components/flash-message.js
deleted file mode 100644
index 36945a3cd..000000000
--- a/ui/tests/pages/components/flash-message.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { waitFor, settled } from '@ember/test-helpers';
-import { collection, text, clickable } from 'ember-cli-page-object';
-import { getter } from 'ember-cli-page-object/macros';
-
-export default {
- latestMessage: getter(function () {
- return this.latestItem.text;
- }),
- latestItem: getter(function () {
- const count = this.messages.length;
- return this.messages.objectAt(count - 1);
- }),
- messages: collection('[data-test-flash-message-body]', {
- click: clickable(),
- text: text(),
- }),
- waitForFlash() {
- return waitFor('[data-test-flash-message-body]');
- },
- clickLast() {
- return this.latestItem.click();
- },
- async clickAll() {
- for (const message of this.messages) {
- message.click();
- }
- await settled();
- },
-};
diff --git a/ui/tests/pages/components/form-field.js b/ui/tests/pages/components/form-field.js
deleted file mode 100644
index dbf295d16..000000000
--- a/ui/tests/pages/components/form-field.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import {
- attribute,
- focusable,
- value,
- clickable,
- isPresent,
- collection,
- fillable,
- text,
- triggerable,
-} from 'ember-cli-page-object';
-
-export default {
- hasStringList: isPresent('[data-test-component=string-list]'),
- hasSearchSelect: isPresent('[data-test-component=search-select]'),
- hasTextFile: isPresent('[data-test-component=text-file]'),
- hasTTLPicker: isPresent('[data-test-toggle-input="Foo"]'),
- hasJSONEditor: isPresent('[data-test-component="code-mirror-modifier"]'),
- hasJSONClearButton: isPresent('[data-test-json-clear-button]'),
- hasSelect: isPresent('select'),
- hasInput: isPresent('input'),
- hasCheckbox: isPresent('input[type=checkbox]'),
- hasTextarea: isPresent('textarea'),
- hasMaskedInput: isPresent('[data-test-masked-input]'),
- hasTooltip: isPresent('[data-test-component=info-tooltip]'),
- tooltipTrigger: focusable('[data-test-tool-tip-trigger]'),
- tooltipContent: text('[data-test-help-text]'),
- hasRadio: isPresent('[data-test-radio-input]'),
- radioButtons: collection('input[type=radio]', {
- select: clickable(),
- id: attribute('id'),
- }),
-
- fields: collection('[data-test-field]', {
- clickLabel: clickable('label'),
- toggleTtl: clickable('[data-test-toggle-input="Foo"]'),
- for: attribute('for', 'label', { multiple: true }),
- labelText: text('label', { multiple: true }),
- input: fillable('input'),
- ttlTime: fillable('[data-test-ttl-value]'),
- select: fillable('select'),
- textarea: fillable('textarea'),
- change: triggerable('keyup', '.input'),
- inputValue: value('input'),
- textareaValue: value('textarea'),
- inputChecked: attribute('checked', 'input[type=checkbox]'),
- selectValue: value('select'),
- }),
- selectRadioInput: async function (value) {
- return this.radioButtons.filterBy('id', value)[0].select();
- },
- fillInTextarea: async function (name, value) {
- return this.fields
- .filter((field) => {
- return field.for.includes(name);
- })[0]
- .textarea(value);
- },
- fillIn: async function (name, value) {
- return this.fields
- .filter((field) => {
- return field.for.includes(name);
- })[0]
- .input(value);
- },
-};
diff --git a/ui/tests/pages/components/hover-copy-button.js b/ui/tests/pages/components/hover-copy-button.js
deleted file mode 100644
index 5cb5942df..000000000
--- a/ui/tests/pages/components/hover-copy-button.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { attribute, clickable, isVisible, focusable, text } from 'ember-cli-page-object';
-import { triggerEvent, focus } from '@ember/test-helpers';
-
-export default {
- async focusContainer() {
- await focus('.has-copy-button');
- },
- tooltipText: text('[data-test-hover-copy-tooltip-text]', {
- testContainer: '#ember-testing',
- }),
- wrapperClass: attribute('class', '[data-test-hover-copy]'),
- buttonIsVisible: isVisible('[data-test-hover-copy-button]'),
- click: clickable('[data-test-hover-copy-button]'),
- focus: focusable('[data-test-hover-copy-button]'),
-
- async mouseEnter() {
- await triggerEvent('[data-test-tooltip-trigger]', 'mouseenter');
- },
-};
diff --git a/ui/tests/pages/components/identity/edit-form.js b/ui/tests/pages/components/identity/edit-form.js
deleted file mode 100644
index 1cd3e6887..000000000
--- a/ui/tests/pages/components/identity/edit-form.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { clickable, fillable, attribute } from 'ember-cli-page-object';
-import { waitFor } from '@ember/test-helpers';
-import fields from '../form-field';
-
-export default {
- ...fields,
- cancelLinkHref: attribute('href', '[data-test-cancel-link]'),
- cancelLink: clickable('[data-test-cancel-link]'),
- name: fillable('[data-test-input="name"]'),
- disabled: clickable('[data-test-input="disabled"]'),
- metadataKey: fillable('[data-test-kv-key]'),
- metadataValue: fillable('[data-test-kv-value]'),
- type: fillable('[data-test-input="type"]'),
- submit: clickable('[data-test-identity-submit]'),
- delete: clickable('[data-test-confirm-action-trigger]'),
- confirmDelete: clickable('[data-test-confirm-button]'),
- waitForConfirm() {
- return waitFor('[data-test-confirm-button]');
- },
- waitForFlash() {
- return waitFor('[data-test-flash-message-body]');
- },
-};
diff --git a/ui/tests/pages/components/identity/item-details.js b/ui/tests/pages/components/identity/item-details.js
deleted file mode 100644
index 580ca6c71..000000000
--- a/ui/tests/pages/components/identity/item-details.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { clickable } from 'ember-cli-page-object';
-
-export default {
- enable: clickable('[data-test-enable]'),
-};
diff --git a/ui/tests/pages/components/info-table-row.js b/ui/tests/pages/components/info-table-row.js
deleted file mode 100644
index 101f847b4..000000000
--- a/ui/tests/pages/components/info-table-row.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { text, isPresent } from 'ember-cli-page-object';
-
-export default {
- hasLabel: isPresent('[data-test-row-label]'),
- rowLabel: text('[data-test-row-label]'),
- rowValue: text('[data-test-row-value]'),
-};
diff --git a/ui/tests/pages/components/json-editor.js b/ui/tests/pages/components/json-editor.js
deleted file mode 100644
index b410c1927..000000000
--- a/ui/tests/pages/components/json-editor.js
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { isPresent, notHasClass, text } from 'ember-cli-page-object';
-
-export default {
- title: text('[data-test-component=json-editor-title]'),
- hasToolbar: isPresent('[data-test-component=json-editor-toolbar]'),
- hasJSONEditor: isPresent('[data-test-component="code-mirror-modifier"]'),
- canEdit: notHasClass('readonly-codemirror'),
-};
diff --git a/ui/tests/pages/components/kv-object-editor.js b/ui/tests/pages/components/kv-object-editor.js
deleted file mode 100644
index f3efcc214..000000000
--- a/ui/tests/pages/components/kv-object-editor.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { clickable, collection, fillable, isPresent } from 'ember-cli-page-object';
-
-export default {
- showsDuplicateError: isPresent('[data-test-duplicate-error-warnings]'),
- addRow: clickable('[data-test-kv-add-row]'),
- rows: collection('[data-test-kv-row]', {
- kvKey: fillable('[data-test-kv-key]'),
- kvVal: fillable('[data-test-kv-value]'),
- deleteRow: clickable('[data-test-kv-delete-row]'),
- }),
-};
diff --git a/ui/tests/pages/components/license-info.js b/ui/tests/pages/components/license-info.js
deleted file mode 100644
index 3aefe01b7..000000000
--- a/ui/tests/pages/components/license-info.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { text, collection } from 'ember-cli-page-object';
-
-export default {
- detailRows: collection('[data-test-detail-row]', {
- rowName: text('[data-test-row-label]'),
- rowValue: text('.column.is-flex'),
- }),
- featureRows: collection('[data-test-feature-row]', {
- featureName: text('[data-test-row-label]'),
- featureStatus: text('[data-test-feature-status]'),
- }),
-};
diff --git a/ui/tests/pages/components/list-view.js b/ui/tests/pages/components/list-view.js
deleted file mode 100644
index 5be88752a..000000000
--- a/ui/tests/pages/components/list-view.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { text, isPresent, collection, clickable } from 'ember-cli-page-object';
-
-export default {
- isEmpty: isPresent('[data-test-component="empty-state"]'),
- listItemLinks: collection('[data-test-list-item-link]', {
- text: text(),
- click: clickable(),
- menuToggle: clickable('[data-test-popup-menu-trigger]'),
- }),
- listItems: collection('[data-test-list-item]', {
- text: text(),
- menuToggle: clickable('[data-test-popup-menu-trigger]'),
- }),
- menuItems: collection('.ember-basic-dropdown-content li', {
- testContainer: '#ember-testing',
- }),
- delete: clickable('[data-test-confirm-action-trigger]', {
- testContainer: '#ember-testing',
- }),
- confirmDelete: clickable('[data-test-confirm-button]', {
- testContainer: '#ember-testing',
- }),
-};
diff --git a/ui/tests/pages/components/masked-input.js b/ui/tests/pages/components/masked-input.js
deleted file mode 100644
index 2cf9664e3..000000000
--- a/ui/tests/pages/components/masked-input.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { clickable, isPresent } from 'ember-cli-page-object';
-
-export default {
- textareaIsPresent: isPresent('[data-test-textarea]'),
- copyButtonIsPresent: isPresent('[data-test-copy-button]'),
- downloadIconIsPresent: isPresent('[data-test-download-icon]'),
- downloadButtonIsPresent: isPresent('[data-test-download-button]'),
- toggleMasked: clickable('[data-test-button="toggle-masked"]'),
-};
diff --git a/ui/tests/pages/components/mount-backend-form.js b/ui/tests/pages/components/mount-backend-form.js
deleted file mode 100644
index bfacd0991..000000000
--- a/ui/tests/pages/components/mount-backend-form.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { clickable, collection, fillable, text, value, attribute } from 'ember-cli-page-object';
-import fields from './form-field';
-import errorText from './alert-banner';
-
-export default {
- ...fields,
- ...errorText,
- header: text('[data-test-mount-form-header]'),
- submit: clickable('[data-test-mount-submit]'),
- next: clickable('[data-test-mount-next]'),
- back: clickable('[data-test-mount-back]'),
- path: fillable('[data-test-input="path"]'),
- toggleOptions: clickable('[data-test-toggle-group="Method Options"]'),
- pathValue: value('[data-test-input="path"]'),
- types: collection('[data-test-mount-type-radio] input', {
- select: clickable(),
- id: attribute('id'),
- }),
- type: fillable('[name="mount-type"]'),
- async selectType(type) {
- return this.types.filterBy('id', type)[0].select();
- },
- async mount(type, path) {
- await this.selectType(type);
- if (path) {
- await this.next().path(path).submit();
- } else {
- await this.next().submit();
- }
- },
-};
diff --git a/ui/tests/pages/components/radial-progress.js b/ui/tests/pages/components/radial-progress.js
deleted file mode 100644
index 913296445..000000000
--- a/ui/tests/pages/components/radial-progress.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { attribute } from 'ember-cli-page-object';
-
-export default {
- viewBox: attribute('viewBox', '[data-test-radial-progress]'),
- height: attribute('height', '[data-test-radial-progress]'),
- width: attribute('width', '[data-test-radial-progress]'),
- cx: attribute('cx', '[data-test-path]'),
- cy: attribute('cy', '[data-test-path]'),
- r: attribute('r', '[data-test-path]'),
- strokeWidth: attribute('stroke-width', '[data-test-path]'),
- strokeDash: attribute('stroke-dasharray', '[data-test-progress]'),
- strokeDashOffset: attribute('stroke-dashoffset', '[data-test-progress]'),
-};
diff --git a/ui/tests/pages/components/search-select.js b/ui/tests/pages/components/search-select.js
deleted file mode 100644
index 133806c8f..000000000
--- a/ui/tests/pages/components/search-select.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { isPresent, collection, text, clickable } from 'ember-cli-page-object';
-
-export default {
- hasSearchSelect: isPresent('[data-test-component=search-select]'),
- hasTrigger: isPresent('.ember-power-select-trigger'),
- hasLabel: isPresent('[data-test-field-label]'),
- labelText: text('[data-test-field-label]'),
- options: collection('.ember-power-select-option'),
- selectedOptions: collection('[data-test-selected-option]'),
- deleteButtons: collection('[data-test-selected-list-button="delete"]'),
- selectedOptionText: text('[aria-current=true]'),
- selectOption: clickable('[aria-current=true]'),
- hasStringList: isPresent('[data-test-string-list-input]'),
- smallOptionIds: collection('[data-test-smaller-id]'),
-};
diff --git a/ui/tests/pages/components/wizard/features-selection.js b/ui/tests/pages/components/wizard/features-selection.js
deleted file mode 100644
index 487cc828e..000000000
--- a/ui/tests/pages/components/wizard/features-selection.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { collection, isPresent, property, clickable } from 'ember-cli-page-object';
-
-export default {
- wizardItems: collection('[data-test-select-input]', {
- hasDisabledTooltip: isPresent('[data-test-tooltip]'),
- }),
- hasDisabledStartButton: property('disabled', '[data-test-start-button]'),
- selectSecrets: clickable('[data-test-checkbox=Secrets]'),
-};
diff --git a/ui/tests/pages/init.js b/ui/tests/pages/init.js
deleted file mode 100644
index c1cdd40fc..000000000
--- a/ui/tests/pages/init.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { text, create, collection, visitable, fillable, clickable } from 'ember-cli-page-object';
-
-export default create({
- visit: visitable('/vault/init'),
- submit: clickable('[data-test-init-submit]'),
- shares: fillable('[data-test-key-shares]'),
- threshold: fillable('[data-test-key-threshold]'),
- keys: collection('[data-test-key-box]'),
- buttonText: text('[data-test-advance-button]'),
- init: async function (shares, threshold) {
- await this.visit();
- return this.shares(shares).threshold(threshold).submit();
- },
-});
diff --git a/ui/tests/pages/logout.js b/ui/tests/pages/logout.js
deleted file mode 100644
index d770d75f9..000000000
--- a/ui/tests/pages/logout.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, visitable } from 'ember-cli-page-object';
-
-export default create({
- visit: visitable('/vault/logout'),
-});
diff --git a/ui/tests/pages/policies/create.js b/ui/tests/pages/policies/create.js
deleted file mode 100644
index 89543ecd6..000000000
--- a/ui/tests/pages/policies/create.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, visitable } from 'ember-cli-page-object';
-export default create({
- visit: visitable('/vault/policies/create'),
-});
diff --git a/ui/tests/pages/policies/index.js b/ui/tests/pages/policies/index.js
deleted file mode 100644
index 79407f65b..000000000
--- a/ui/tests/pages/policies/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { text, create, collection, clickable, visitable } from 'ember-cli-page-object';
-export default create({
- visit: visitable('/vault/policies/:type'),
- policies: collection('[data-test-policy-item]', {
- name: text('[data-test-policy-name]'),
- }),
- row: collection('[data-test-policy-link]', {
- name: text(),
- menu: clickable('[data-test-popup-menu-trigger]'),
- }),
- findPolicyByName(name) {
- return this.policies.filterBy('name', name)[0];
- },
- delete: clickable('[data-test-confirm-action-trigger]', {
- testContainer: '#ember-testing',
- }),
- confirmDelete: clickable('[data-test-confirm-button]', {
- testContainer: '#ember-testing',
- }),
-});
diff --git a/ui/tests/pages/policy/edit.js b/ui/tests/pages/policy/edit.js
deleted file mode 100644
index 15be98440..000000000
--- a/ui/tests/pages/policy/edit.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { clickable, create, isPresent, visitable } from 'ember-cli-page-object';
-export default create({
- visit: visitable('/vault/policy/:type/:name/edit'),
- deleteIsPresent: isPresent('[data-test-policy-delete]'),
- toggleEdit: clickable('[data-test-policy-edit-toggle]'),
-});
diff --git a/ui/tests/pages/policy/show.js b/ui/tests/pages/policy/show.js
deleted file mode 100644
index c5f5fc5f9..000000000
--- a/ui/tests/pages/policy/show.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { clickable, create, visitable } from 'ember-cli-page-object';
-export default create({
- visit: visitable('/vault/policy/:type/:name'),
- toggleEdit: clickable('[data-test-policy-edit-toggle]'),
-});
diff --git a/ui/tests/pages/secrets/backend/configuration.js b/ui/tests/pages/secrets/backend/configuration.js
deleted file mode 100644
index fc02b0e82..000000000
--- a/ui/tests/pages/secrets/backend/configuration.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, visitable, text } from 'ember-cli-page-object';
-
-export default create({
- visit: visitable('/vault/secrets/:backend/configuration'),
- defaultTTL: text('[data-test-value-div="Default Lease TTL"]'),
- maxTTL: text('[data-test-value-div="Max Lease TTL"]'),
-});
diff --git a/ui/tests/pages/secrets/backend/create.js b/ui/tests/pages/secrets/backend/create.js
deleted file mode 100644
index 5b1576c39..000000000
--- a/ui/tests/pages/secrets/backend/create.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, visitable } from 'ember-cli-page-object';
-
-export const Base = {
- visit: visitable('/vault/secrets/:backend/create/:id'),
- visitRoot: visitable('/vault/secrets/:backend/create'),
-};
-export default create(Base);
diff --git a/ui/tests/pages/secrets/backend/credentials.js b/ui/tests/pages/secrets/backend/credentials.js
deleted file mode 100644
index ad2137bfd..000000000
--- a/ui/tests/pages/secrets/backend/credentials.js
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, visitable } from 'ember-cli-page-object';
-
-export const Base = {
- visit: visitable('/vault/secrets/:backend/credentials/:id'),
- visitRoot: visitable('/vault/secrets/:backend/credentials'),
-};
-
-export default create(Base);
diff --git a/ui/tests/pages/secrets/backend/database/connection.js b/ui/tests/pages/secrets/backend/database/connection.js
deleted file mode 100644
index 14f9de35c..000000000
--- a/ui/tests/pages/secrets/backend/database/connection.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, clickable, fillable, visitable, selectable } from 'ember-cli-page-object';
-import ListView from 'vault/tests/pages/components/list-view';
-
-export default create({
- ...ListView,
- visit: visitable('/vault/secrets/:backend/list'),
- visitShow: visitable('/vault/secrets/:backend/show/:id'),
- visitCreate: visitable('/vault/secrets/:backend/create'),
- createLink: clickable('[data-test-secret-create]'),
- dbPlugin: selectable('[data-test-input="plugin_name"]'),
- name: fillable('[data-test-input="name"]'),
- toggleVerify: clickable('[data-test-input="verify_connection"]'),
- connectionUrl: fillable('[data-test-input="connection_url"]'),
- url: fillable('[data-test-input="url"]'),
- username: fillable('[data-test-input="username"]'),
- password: fillable('[data-test-input="password"]'),
- save: clickable('[data-test-secret-save]'),
- addRole: clickable('[data-test-secret-create]'), // only from connection show
- enable: clickable('[data-test-enable-connection]'),
- edit: clickable('[data-test-edit-link]'),
- delete: clickable('[data-test-database-connection-delete]'),
-});
diff --git a/ui/tests/pages/secrets/backend/database/role.js b/ui/tests/pages/secrets/backend/database/role.js
deleted file mode 100644
index 2c12d8c51..000000000
--- a/ui/tests/pages/secrets/backend/database/role.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, clickable, fillable, visitable, selectable } from 'ember-cli-page-object';
-import ListView from 'vault/tests/pages/components/list-view';
-
-export default create({
- ...ListView,
- visit: visitable('/vault/secrets/:backend/list?itemType=role'),
- visitShow: visitable('/vault/secrets/:backend/show/role/:id'),
- visitCreate: visitable('/vault/secrets/:backend/create?itemType=role'),
- createLink: clickable('[data-test-secret-create]'),
- name: fillable('[data-test-input="name"]'),
- roleType: selectable('[data-test-input="type"'),
- save: clickable('[data-test-secret-save]'),
- edit: clickable('[data-test-edit-link]'),
-});
diff --git a/ui/tests/pages/secrets/backend/edit.js b/ui/tests/pages/secrets/backend/edit.js
deleted file mode 100644
index 589b4595b..000000000
--- a/ui/tests/pages/secrets/backend/edit.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, visitable } from 'ember-cli-page-object';
-
-export default create({
- visit: visitable('/vault/secrets/:backend/edit/:id'),
- visitRoot: visitable('/vault/secrets/:backend/edit'),
-});
diff --git a/ui/tests/pages/secrets/backend/kmip/credentials.js b/ui/tests/pages/secrets/backend/kmip/credentials.js
deleted file mode 100644
index 439bed7c8..000000000
--- a/ui/tests/pages/secrets/backend/kmip/credentials.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, clickable, visitable } from 'ember-cli-page-object';
-import ListView from 'vault/tests/pages/components/list-view';
-
-export default create({
- ...ListView,
- visit: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles/:role/credentials'),
- visitDetail: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles/:role/credentials/:serial'),
- create: clickable('[data-test-role-create]'),
- generateCredentialsLink: clickable('[data-test-kmip-link-generate-credentials]'),
- backToRoleLink: clickable('[data-test-kmip-link-back-to-role]'),
- submit: clickable('[data-test-edit-form-submit]'),
-});
diff --git a/ui/tests/pages/secrets/backend/kmip/roles.js b/ui/tests/pages/secrets/backend/kmip/roles.js
deleted file mode 100644
index aafaeba98..000000000
--- a/ui/tests/pages/secrets/backend/kmip/roles.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, clickable, fillable, visitable } from 'ember-cli-page-object';
-import ListView from 'vault/tests/pages/components/list-view';
-
-export default create({
- ...ListView,
- visit: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles'),
- visitDetail: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles/:role'),
- create: clickable('[data-test-role-create]'),
- roleName: fillable('[data-test-input="name"]'),
- submit: clickable('[data-test-edit-form-submit]'),
- detailEditLink: clickable('[data-test-kmip-link-edit-role]'),
- cancelLink: clickable('[data-test-edit-form-cancel]'),
-});
diff --git a/ui/tests/pages/secrets/backend/kmip/scopes.js b/ui/tests/pages/secrets/backend/kmip/scopes.js
deleted file mode 100644
index b72ab3e99..000000000
--- a/ui/tests/pages/secrets/backend/kmip/scopes.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, clickable, fillable, visitable } from 'ember-cli-page-object';
-import ListView from 'vault/tests/pages/components/list-view';
-
-export default create({
- ...ListView,
- visit: visitable('/vault/secrets/:backend/kmip/scopes'),
- visitCreate: visitable('/vault/secrets/:backend/kmip/scopes/create'),
- createLink: clickable('[data-test-scope-create]'),
- scopeName: fillable('[data-test-input="name"]'),
- submit: clickable('[data-test-edit-form-submit]'),
- configurationLink: clickable('[data-test-kmip-link-config]'),
- configureLink: clickable('[data-test-kmip-link-configure]'),
- scopesLink: clickable('[data-test-kmip-link-scopes]'),
-});
diff --git a/ui/tests/pages/secrets/backend/kv/edit-secret.js b/ui/tests/pages/secrets/backend/kv/edit-secret.js
deleted file mode 100644
index 68f71e878..000000000
--- a/ui/tests/pages/secrets/backend/kv/edit-secret.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { Base } from '../create';
-import { isPresent, clickable, visitable, create, fillable } from 'ember-cli-page-object';
-
-export default create({
- ...Base,
- path: fillable('[data-test-secret-path="true"]'),
- secretKey: fillable('[data-test-secret-key]'),
- secretValue: fillable('[data-test-secret-value] textarea'),
- save: clickable('[data-test-secret-save]'),
- deleteBtn: clickable('[data-test-secret-delete] button'),
- confirmBtn: clickable('[data-test-confirm-button]'),
- visitEdit: visitable('/vault/secrets/:backend/edit/:id'),
- visitEditRoot: visitable('/vault/secrets/:backend/edit'),
- toggleJSON: clickable('[data-test-toggle-input="json"]'),
- toggleMetadata: clickable('[data-test-show-metadata-toggle]'),
- metadataTab: clickable('[data-test-secret-metadata-tab]'),
- hasMetadataFields: isPresent('[data-test-metadata-fields]'),
- maxVersion: fillable('[data-test-input="maxVersions"]'),
- startCreateSecret: clickable('[data-test-secret-create]'),
- deleteSecret() {
- return this.deleteBtn().confirmBtn();
- },
- createSecret: async function (path, key, value) {
- return this.path(path).secretKey(key).secretValue(value).save();
- },
- createSecretDontSave: async function (path, key, value) {
- return this.path(path).secretKey(key).secretValue(value);
- },
- createSecretWithMetadata: async function (path, key, value, maxVersion) {
- return this.path(path).secretKey(key).secretValue(value).toggleMetadata().maxVersion(maxVersion).save();
- },
- editSecret: async function (key, value) {
- return this.secretKey(key).secretValue(value).save();
- },
-});
diff --git a/ui/tests/pages/secrets/backend/kv/show.js b/ui/tests/pages/secrets/backend/kv/show.js
deleted file mode 100644
index 8e927e81c..000000000
--- a/ui/tests/pages/secrets/backend/kv/show.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { Base } from '../show';
-import { create, clickable, collection, isPresent, text } from 'ember-cli-page-object';
-
-export default create({
- ...Base,
- breadcrumbs: collection('[data-test-secret-breadcrumb]', {
- text: text(),
- }),
- deleteBtn: clickable('[data-test-secret-delete] button'),
- deleteBtnV1: clickable('[data-test-secret-v1-delete="true"] button'),
- deleteBtnV2: clickable('[data-test-secret-v2-delete="true"] button'),
- confirmBtn: clickable('[data-test-confirm-button]'),
- rows: collection('data-test-row-label'),
- toggleJSON: clickable('[data-test-secret-json-toggle]'),
- toggleIsPresent: isPresent('[data-test-secret-json-toggle]'),
- edit: clickable('[data-test-secret-edit]'),
- editIsPresent: isPresent('[data-test-secret-edit]'),
- noReadIsPresent: isPresent('[data-test-write-without-read-empty-message]'),
- noReadMessage: text('data-test-empty-state-message'),
-
- deleteSecret() {
- return this.deleteBtn().confirmBtn();
- },
- deleteSecretV1() {
- return this.deleteBtnV1().confirmBtn();
- },
- deleteSecretV2() {
- return this.deleteBtnV2().confirmBtn();
- },
-});
diff --git a/ui/tests/pages/secrets/backend/list.js b/ui/tests/pages/secrets/backend/list.js
deleted file mode 100644
index 78088d0a2..000000000
--- a/ui/tests/pages/secrets/backend/list.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import {
- create,
- collection,
- fillable,
- text,
- visitable,
- value,
- clickable,
- isPresent,
-} from 'ember-cli-page-object';
-import { getter } from 'ember-cli-page-object/macros';
-
-export default create({
- visit: visitable('/vault/secrets/:backend/list/:id'),
- visitRoot: visitable('/vault/secrets/:backend/list'),
- create: clickable('[data-test-secret-create]'),
- createIsPresent: isPresent('[data-test-secret-create]'),
- configure: clickable('[data-test-secret-backend-configure]'),
- configureIsPresent: isPresent('[data-test-secret-backend-configure]'),
- tabs: collection('[data-test-secret-list-tab]'),
- filterInput: fillable('[data-test-nav-input] input'),
- filterInputValue: value('[data-test-nav-input] input'),
- secrets: collection('[data-test-secret-link]', {
- menuToggle: clickable('[data-test-popup-menu-trigger]'),
- id: text(),
- click: clickable(),
- }),
- menuItems: collection('.ember-basic-dropdown-content li', {
- testContainer: '#ember-testing',
- }),
- delete: clickable('[data-test-confirm-action-trigger]', {
- testContainer: '#ember-testing',
- }),
- confirmDelete: clickable('[data-test-confirm-button]', {
- testContainer: '#ember-testing',
- }),
- backendIsEmpty: getter(function () {
- return this.secrets.length === 0;
- }),
-});
diff --git a/ui/tests/pages/secrets/backend/pki/edit-role.js b/ui/tests/pages/secrets/backend/pki/edit-role.js
deleted file mode 100644
index 9da0f8d2d..000000000
--- a/ui/tests/pages/secrets/backend/pki/edit-role.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { Base } from '../create';
-import { settled } from '@ember/test-helpers';
-import { clickable, visitable, create, fillable } from 'ember-cli-page-object';
-
-export default create({
- ...Base,
- visitEdit: visitable('/vault/secrets/:backend/edit/:id'),
- visitEditRoot: visitable('/vault/secrets/:backend/edit'),
- toggleDomain: clickable('[data-test-toggle-group="Domain Handling"]'),
- toggleOptions: clickable('[data-test-toggle-group="Options"]'),
- name: fillable('[data-test-input="name"]'),
- allowAnyName: clickable('[data-test-input="allowAnyName"]'),
- allowedDomains: fillable('[data-test-input="allowedDomains"] .input'),
- save: clickable('[data-test-role-create]'),
-
- async createRole(name, allowedDomains) {
- await this.toggleDomain();
- await settled();
- await this.toggleOptions();
- await settled();
- await this.name(name);
- await settled();
- await this.allowAnyName();
- await settled();
- await this.allowedDomains(allowedDomains);
- await settled();
- await this.save();
- await settled();
- },
-});
diff --git a/ui/tests/pages/secrets/backend/pki/generate-cert.js b/ui/tests/pages/secrets/backend/pki/generate-cert.js
deleted file mode 100644
index 77268e347..000000000
--- a/ui/tests/pages/secrets/backend/pki/generate-cert.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { Base } from '../credentials';
-import { clickable, text, value, create, fillable, isPresent } from 'ember-cli-page-object';
-
-export default create({
- ...Base,
- title: text('[data-test-title]'),
- commonName: fillable('[data-test-input="commonName"]'),
- commonNameValue: value('[data-test-input="commonName"]'),
- csr: fillable('[data-test-input="csr"]'),
- submit: clickable('[data-test-secret-generate]'),
- back: clickable('[data-test-secret-generate-back]'),
- certificate: text('[data-test-row-value="Certificate"]'),
- toggleOptions: clickable('[data-test-toggle-group]'),
- enableTtl: clickable('[data-test-toggle-input]'),
- hasCert: isPresent('[data-test-row-value="Certificate"]'),
- fillInTime: fillable('[data-test-ttl-value]'),
- fillInField: fillable('[data-test-select="ttl-unit"]'),
- issueCert: async function (commonName) {
- await this.commonName(commonName).toggleOptions().enableTtl().fillInField('h').fillInTime('30').submit();
- },
-
- sign: async function (commonName, csr) {
- return this.csr(csr)
- .commonName(commonName)
- .toggleOptions()
- .enableTtl()
- .fillInField('h')
- .fillInTime('30')
- .submit();
- },
-});
diff --git a/ui/tests/pages/secrets/backend/pki/show.js b/ui/tests/pages/secrets/backend/pki/show.js
deleted file mode 100644
index f1a1a0922..000000000
--- a/ui/tests/pages/secrets/backend/pki/show.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { Base } from '../show';
-import { settled } from '@ember/test-helpers';
-import { create, clickable, collection, text, isPresent } from 'ember-cli-page-object';
-
-export default create({
- ...Base,
- rows: collection('data-test-row-label'),
- certificate: text('[data-test-row-value="Certificate"]'),
- hasCert: isPresent('[data-test-row-value="Certificate"]'),
- edit: clickable('[data-test-edit-link]'),
- generateCert: clickable('[data-test-credentials-link]'),
- deleteBtn: clickable('[data-test-role-delete] button'),
- confirmBtn: clickable('[data-test-confirm-button]'),
- async deleteRole() {
- await this.deleteBtn();
- await settled();
- await this.confirmBtn();
- await settled();
- },
-});
diff --git a/ui/tests/pages/secrets/backend/show.js b/ui/tests/pages/secrets/backend/show.js
deleted file mode 100644
index 27f9fe548..000000000
--- a/ui/tests/pages/secrets/backend/show.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, visitable } from 'ember-cli-page-object';
-
-export const Base = {
- visit: visitable('/vault/secrets/:backend/show/:id'),
- visitRoot: visitable('/vault/secrets/:backend/show'),
-};
-export default create(Base);
diff --git a/ui/tests/pages/secrets/backend/ssh/edit-role.js b/ui/tests/pages/secrets/backend/ssh/edit-role.js
deleted file mode 100644
index 3c7df3755..000000000
--- a/ui/tests/pages/secrets/backend/ssh/edit-role.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { Base } from '../create';
-import { clickable, visitable, create, fillable } from 'ember-cli-page-object';
-
-export default create({
- ...Base,
- visitEdit: visitable('/vault/secrets/:backend/edit/:id'),
- visitEditRoot: visitable('/vault/secrets/:backend/edit'),
- keyType: fillable('[data-test-input="keyType"]'),
- defaultUser: fillable('[data-test-input="defaultUser"]'),
- toggleMore: clickable('[data-test-toggle-group="Options"]'),
- name: fillable('[data-test-input="name"]'),
- CIDR: fillable('[data-test-input="cidrList"]'),
- save: clickable('[data-test-role-ssh-create]'),
-
- async createOTPRole(name) {
- await this.name(name);
- await this.toggleMore().keyType('otp').defaultUser('admin').CIDR('0.0.0.0/0').save();
- },
-});
diff --git a/ui/tests/pages/secrets/backend/ssh/generate-otp.js b/ui/tests/pages/secrets/backend/ssh/generate-otp.js
deleted file mode 100644
index d4ed260a4..000000000
--- a/ui/tests/pages/secrets/backend/ssh/generate-otp.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { Base } from '../credentials';
-import { clickable, value, create, fillable, isPresent } from 'ember-cli-page-object';
-
-export default create({
- ...Base,
- userIsPresent: isPresent('[data-test-input="username"]'),
- ipIsPresent: isPresent('[data-test-input="ip"]'),
- user: fillable('[data-test-input="username"]'),
- ip: fillable('[data-test-input="ip"]'),
- warningIsPresent: isPresent('[data-test-warning]'),
- commonNameValue: value('[data-test-input="commonName"]'),
- submit: clickable('[data-test-secret-generate]'),
- back: clickable('[data-test-secret-generate-back]'),
- generateOTP: async function () {
- await this.user('admin').ip('192.168.1.1').submit();
- },
-});
diff --git a/ui/tests/pages/secrets/backend/ssh/show.js b/ui/tests/pages/secrets/backend/ssh/show.js
deleted file mode 100644
index e4e23839f..000000000
--- a/ui/tests/pages/secrets/backend/ssh/show.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { Base } from '../show';
-import { create, clickable, collection, isPresent } from 'ember-cli-page-object';
-
-export default create({
- ...Base,
- rows: collection('data-test-row-label'),
- edit: clickable('[data-test-edit-link]'),
- editIsPresent: isPresent('[data-test-edit-link]'),
- generate: clickable('[data-test-backend-credentials]'),
- generateIsPresent: isPresent('[data-test-backend-credentials]'),
- deleteBtn: clickable('[data-test-confirm-action-trigger]'),
- confirmBtn: clickable('[data-test-confirm-button]'),
- deleteRole() {
- return this.deleteBtn().confirmBtn();
- },
-});
diff --git a/ui/tests/pages/secrets/backend/transform/alphabets.js b/ui/tests/pages/secrets/backend/transform/alphabets.js
deleted file mode 100644
index bcfbf8710..000000000
--- a/ui/tests/pages/secrets/backend/transform/alphabets.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, clickable, fillable, visitable } from 'ember-cli-page-object';
-import ListView from 'vault/tests/pages/components/list-view';
-
-export default create({
- ...ListView,
- visit: visitable('/vault/secrets/:backend/list?tab=alphabet'),
- visitCreate: visitable('/vault/secrets/:backend/create?itemType=alphabet'),
- createLink: clickable('[data-test-secret-create]'),
- editLink: clickable('[data-test-edit-link]'),
- name: fillable('[data-test-input="name"]'),
- alphabet: fillable('[data-test-input="alphabet"'),
- submit: clickable('[data-test-alphabet-transform-create]'),
-});
diff --git a/ui/tests/pages/secrets/backend/transform/roles.js b/ui/tests/pages/secrets/backend/transform/roles.js
deleted file mode 100644
index 3ec92f568..000000000
--- a/ui/tests/pages/secrets/backend/transform/roles.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, clickable, fillable, visitable } from 'ember-cli-page-object';
-import ListView from 'vault/tests/pages/components/list-view';
-
-export default create({
- ...ListView,
- visit: visitable('/vault/secrets/:backend/list?tab=roles'),
- visitCreate: visitable('/vault/secrets/:backend/create?itemType=role'),
- createLink: clickable('[data-test-secret-create]'),
- name: fillable('[data-test-input="name"]'),
- transformations: fillable('[data-test-input="transformations"'),
- submit: clickable('[data-test-role-transform-create]'),
- modalConfirm: clickable('[data-test-edit-confirm-button]'),
-});
diff --git a/ui/tests/pages/secrets/backend/transform/templates.js b/ui/tests/pages/secrets/backend/transform/templates.js
deleted file mode 100644
index cbede093b..000000000
--- a/ui/tests/pages/secrets/backend/transform/templates.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, clickable, fillable, visitable } from 'ember-cli-page-object';
-import ListView from 'vault/tests/pages/components/list-view';
-
-export default create({
- ...ListView,
- visit: visitable('/vault/secrets/:backend/list?tab=templates'),
- visitCreate: visitable('/vault/secrets/:backend/create?itemType=template'),
- createLink: clickable('[data-test-secret-create]'),
- editLink: clickable('[data-test-edit-link]'),
- deleteLink: clickable('[data-test-transformation-template-delete]'),
- name: fillable('[data-test-input="name"]'),
- pattern: fillable('[data-test-input="pattern"'),
- alphabet: fillable('[data-test-input="alphabet"'),
- submit: clickable('[data-test-template-transform-create]'),
- removeAlphabet: clickable('#alphabet [data-test-selected-list-button="delete"]'),
-});
diff --git a/ui/tests/pages/secrets/backend/transform/transformations.js b/ui/tests/pages/secrets/backend/transform/transformations.js
deleted file mode 100644
index 531658035..000000000
--- a/ui/tests/pages/secrets/backend/transform/transformations.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, clickable, fillable, visitable } from 'ember-cli-page-object';
-import ListView from 'vault/tests/pages/components/list-view';
-
-export default create({
- ...ListView,
- visit: visitable('/vault/secrets/:backend/list'),
- visitShow: visitable('/vault/secrets/:backend/show/:id'),
- visitCreate: visitable('/vault/secrets/:backend/create'),
- createLink: clickable('[data-test-secret-create]'),
- name: fillable('[data-test-input="name"]'),
- submit: clickable('[data-test-transform-create]'),
- type: fillable('[data-test-input="type"'),
- tweakSource: fillable('[data-test-input="tweak_source"'),
- maskingChar: fillable('[data-test-input="masking_character"'),
- save: clickable('[data-test-transformation-save-button]'),
-});
diff --git a/ui/tests/pages/secrets/backends.js b/ui/tests/pages/secrets/backends.js
deleted file mode 100644
index 135e638d1..000000000
--- a/ui/tests/pages/secrets/backends.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, visitable, collection, clickable, text } from 'ember-cli-page-object';
-import uiPanel from 'vault/tests/pages/components/console/ui-panel';
-
-export default create({
- consoleToggle: clickable('[data-test-console-toggle]'),
- visit: visitable('/vault/secrets'),
- rows: collection('[data-test-auth-backend-link]', {
- path: text('[data-test-secret-path]'),
- menu: clickable('[data-test-popup-menu-trigger]'),
- }),
- configLink: clickable('[data-test-engine-config]', {
- testContainer: '#ember-testing',
- }),
- disableButton: clickable('[data-test-confirm-action-trigger]', {
- testContainer: '#ember-testing',
- }),
- confirmDisable: clickable('[data-test-confirm-button]', {
- testContainer: '#ember-testing',
- }),
- console: uiPanel,
-});
diff --git a/ui/tests/pages/settings/auth/configure/index.js b/ui/tests/pages/settings/auth/configure/index.js
deleted file mode 100644
index 018a3504e..000000000
--- a/ui/tests/pages/settings/auth/configure/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, visitable } from 'ember-cli-page-object';
-
-export default create({
- visit: visitable('/vault/settings/auth/configure/:path'),
-});
diff --git a/ui/tests/pages/settings/auth/configure/section.js b/ui/tests/pages/settings/auth/configure/section.js
deleted file mode 100644
index 1232a78c2..000000000
--- a/ui/tests/pages/settings/auth/configure/section.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, clickable, visitable, collection } from 'ember-cli-page-object';
-import fields from '../../../components/form-field';
-import flashMessage from '../../../components/flash-message';
-
-export default create({
- ...fields,
- tabs: collection('[data-test-auth-section-tab]'),
- visit: visitable('/vault/settings/auth/configure/:path/:section'),
- flash: flashMessage,
- save: clickable('[data-test-save-config]'),
-});
diff --git a/ui/tests/pages/settings/auth/enable.js b/ui/tests/pages/settings/auth/enable.js
deleted file mode 100644
index 34893885a..000000000
--- a/ui/tests/pages/settings/auth/enable.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, visitable } from 'ember-cli-page-object';
-import backendForm from '../../components/mount-backend-form';
-import flashMessages from '../../components/flash-message';
-
-export default create({
- visit: visitable('/vault/settings/auth/enable'),
- ...backendForm,
- flash: flashMessages,
- enable: async function (type, path) {
- await this.visit();
- await this.mount(type, path);
- },
-});
diff --git a/ui/tests/pages/settings/mount-secret-backend.js b/ui/tests/pages/settings/mount-secret-backend.js
deleted file mode 100644
index de011247e..000000000
--- a/ui/tests/pages/settings/mount-secret-backend.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { create, visitable, fillable, clickable } from 'ember-cli-page-object';
-import { settled } from '@ember/test-helpers';
-import mountForm from 'vault/tests/pages/components/mount-backend-form';
-
-export default create({
- visit: visitable('/vault/settings/mount-secret-backend'),
- ...mountForm,
- version: fillable('[data-test-input="version"]'),
- setMaxVersion: fillable('[data-test-input="maxVersions"]'),
- enableMaxTtl: clickable('[data-test-toggle-input="Max Lease TTL"]'),
- maxTTLVal: fillable('[data-test-ttl-value="Max Lease TTL"]'),
- maxTTLUnit: fillable('[data-test-ttl-unit="Max Lease TTL"] [data-test-select="ttl-unit"]'),
- enableDefaultTtl: clickable('[data-test-toggle-input="Default Lease TTL"]'),
- enableEngine: clickable('[data-test-enable-engine]'),
- secretList: clickable('[data-test-sidebar-nav-link="Secrets engines"]'),
- defaultTTLVal: fillable('input[data-test-ttl-value="Default Lease TTL"]'),
- defaultTTLUnit: fillable('[data-test-ttl-unit="Default Lease TTL"] [data-test-select="ttl-unit"]'),
- enable: async function (type, path) {
- await this.visit();
- await settled();
- await this.mount(type, path);
- await settled();
- },
-});
diff --git a/ui/tests/test-helper.js b/ui/tests/test-helper.js
deleted file mode 100644
index 03bffa357..000000000
--- a/ui/tests/test-helper.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import Application from 'vault/app';
-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 './helpers/flash-message';
-import preloadAssets from 'ember-asset-loader/test-support/preload-assets';
-import manifest from 'vault/config/asset-manifest';
-
-preloadAssets(manifest).then(() => {
- setApplication(Application.create(config.APP));
- // TODO CBS: Check what this is, upgrade added it
- setup(QUnit.assert);
- start({
- setupTestIsolationValidation: true,
- });
-});
diff --git a/ui/tests/unit/.gitkeep b/ui/tests/unit/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/ui/tests/unit/adapters/aws-credential-test.js b/ui/tests/unit/adapters/aws-credential-test.js
deleted file mode 100644
index 84e241f43..000000000
--- a/ui/tests/unit/adapters/aws-credential-test.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import apiStub from 'vault/tests/helpers/noop-all-api-requests';
-
-module('Unit | Adapter | aws credential', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.server = apiStub();
- });
-
- hooks.afterEach(function () {
- this.server.shutdown();
- });
-
- const storeStub = {
- pushPayload() {},
- serializerFor() {
- return {
- serializeIntoHash() {},
- };
- },
- };
-
- const makeSnapshot = (obj) => {
- obj.role = {
- backend: 'aws',
- name: 'foo',
- };
- obj.attr = (attr) => obj[attr];
- return obj;
- };
-
- const type = {
- modelName: 'aws-credential',
- };
-
- const cases = [
- ['iam_user type', [storeStub, type, makeSnapshot({ credentialType: 'iam_user', ttl: '3h' })], 'GET'],
- [
- 'federation_token type with ttl',
- [storeStub, type, makeSnapshot({ credentialType: 'federation_token', ttl: '3h', roleArn: 'arn' })],
- 'POST',
- { ttl: '3h' },
- ],
- [
- 'federation_token type no ttl',
- [storeStub, type, makeSnapshot({ credentialType: 'federation_token', roleArn: 'arn' })],
- 'POST',
- ],
- [
- 'assumed_role type no arn, no ttl',
- [storeStub, type, makeSnapshot({ credentialType: 'assumed_role' })],
- 'POST',
- ],
- [
- 'assumed_role type no arn',
- [storeStub, type, makeSnapshot({ credentialType: 'assumed_role', ttl: '3h' })],
- 'POST',
- { ttl: '3h' },
- ],
- [
- 'assumed_role type',
- [storeStub, type, makeSnapshot({ credentialType: 'assumed_role', roleArn: 'arn', ttl: '3h' })],
- 'POST',
- { ttl: '3h', role_arn: 'arn' },
- ],
- ];
- cases.forEach(([description, args, expectedMethod, expectedRequestBody]) => {
- test(`aws-credential: ${description}`, function (assert) {
- assert.expect(3);
- const adapter = this.owner.lookup('adapter:aws-credential');
- adapter.createRecord(...args);
- const { method, url, requestBody } = this.server.handledRequests[0];
- assert.strictEqual(url, '/v1/aws/creds/foo', `calls the correct url`);
- assert.strictEqual(
- method,
- expectedMethod,
- `${description} uses the correct http verb: ${expectedMethod}`
- );
- assert.strictEqual(requestBody, expectedRequestBody ? JSON.stringify(expectedRequestBody) : null);
- });
- });
-});
diff --git a/ui/tests/unit/adapters/capabilities-test.js b/ui/tests/unit/adapters/capabilities-test.js
deleted file mode 100644
index bcd1f055b..000000000
--- a/ui/tests/unit/adapters/capabilities-test.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { resolve } from 'rsvp';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Adapter | capabilities', function (hooks) {
- setupTest(hooks);
-
- test('calls the correct url', function (assert) {
- let url, method, options;
- const adapter = this.owner.factoryFor('adapter:capabilities').create({
- ajax: (...args) => {
- [url, method, options] = args;
- return resolve();
- },
- });
-
- adapter.findRecord(null, 'capabilities', 'foo');
- assert.strictEqual(url, '/v1/sys/capabilities-self', 'calls the correct URL');
- assert.deepEqual({ paths: ['foo'] }, options.data, 'data params OK');
- assert.strictEqual(method, 'POST', 'method OK');
- });
-});
diff --git a/ui/tests/unit/adapters/clients-activity-test.js b/ui/tests/unit/adapters/clients-activity-test.js
deleted file mode 100644
index ab6e4ffeb..000000000
--- a/ui/tests/unit/adapters/clients-activity-test.js
+++ /dev/null
@@ -1,142 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import sinon from 'sinon';
-import { setupTest } from 'ember-qunit';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-import { subMonths, fromUnixTime, addMonths } from 'date-fns';
-import { parseAPITimestamp } from 'core/utils/date-formatters';
-import timestamp from 'core/utils/timestamp';
-
-module('Unit | Adapter | clients activity', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.before(function () {
- sinon.stub(timestamp, 'now').callsFake(() => new Date('2023-01-13T09:30:15'));
- });
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.modelName = 'clients/activity';
- this.startDate = subMonths(timestamp.now(), 6);
- this.endDate = timestamp.now();
- this.readableUnix = (unix) => parseAPITimestamp(fromUnixTime(unix).toISOString(), 'MMMM dd yyyy');
- });
- hooks.after(function () {
- timestamp.now.restore();
- });
-
- test('it does not format if both params are timestamp strings', async function (assert) {
- assert.expect(1);
- const queryParams = {
- start_time: { timestamp: this.startDate.toISOString() },
- end_time: { timestamp: this.endDate.toISOString() },
- };
- this.server.get('sys/internal/counters/activity', (schema, req) => {
- assert.propEqual(req.queryParams, {
- start_time: this.startDate.toISOString(),
- end_time: this.endDate.toISOString(),
- });
- });
-
- this.store.queryRecord(this.modelName, queryParams);
- });
-
- test('it formats start_time if only end_time is a timestamp string', async function (assert) {
- assert.expect(2);
- const twoMonthsAhead = addMonths(this.startDate, 2);
- const month = twoMonthsAhead.getMonth();
- const year = twoMonthsAhead.getFullYear();
- const queryParams = {
- start_time: {
- monthIdx: month,
- year,
- },
- end_time: {
- timestamp: this.endDate.toISOString(),
- },
- };
-
- this.server.get('sys/internal/counters/activity', (schema, req) => {
- const { start_time, end_time } = req.queryParams;
- const readableStart = this.readableUnix(start_time);
- assert.strictEqual(
- readableStart,
- `September 01 2022`,
- `formatted unix start time is the first of the month: ${readableStart}`
- );
- assert.strictEqual(end_time, this.endDate.toISOString(), 'end time is a timestamp string');
- });
- this.store.queryRecord(this.modelName, queryParams);
- });
-
- test('it formats end_time only if only start_time is a timestamp string', async function (assert) {
- assert.expect(2);
- const twoMothsAgo = subMonths(this.endDate, 2);
- const endMonth = twoMothsAgo.getMonth();
- const year = twoMothsAgo.getFullYear();
- const queryParams = {
- start_time: {
- timestamp: this.startDate.toISOString(),
- },
- end_time: {
- monthIdx: endMonth,
- year,
- },
- };
-
- this.server.get('sys/internal/counters/activity', (schema, req) => {
- const { start_time, end_time } = req.queryParams;
- const readableEnd = this.readableUnix(end_time);
- assert.strictEqual(start_time, this.startDate.toISOString(), 'start time is a timestamp string');
- assert.strictEqual(
- readableEnd,
- `November 30 2022`,
- `formatted unix end time is the last day of the month: ${readableEnd}`
- );
- });
-
- this.store.queryRecord(this.modelName, queryParams);
- });
-
- test('it formats both params if neither are a timestamp', async function (assert) {
- assert.expect(2);
- const startDate = subMonths(this.startDate, 2);
- const endDate = addMonths(this.endDate, 2);
- const startMonth = startDate.getMonth();
- const startYear = startDate.getFullYear();
- const endMonth = endDate.getMonth();
- const endYear = endDate.getFullYear();
- const queryParams = {
- start_time: {
- monthIdx: startMonth,
- year: startYear,
- },
- end_time: {
- monthIdx: endMonth,
- year: endYear,
- },
- };
-
- this.server.get('sys/internal/counters/activity', (schema, req) => {
- const { start_time, end_time } = req.queryParams;
- const readableEnd = this.readableUnix(end_time);
- const readableStart = this.readableUnix(start_time);
- assert.strictEqual(
- readableStart,
- `May 01 2022`,
- `formatted unix start time is the first of the month: ${readableStart}`
- );
- assert.strictEqual(
- readableEnd,
- `March 31 2023`,
- `formatted unix end time is the last day of the month: ${readableEnd}`
- );
- });
-
- this.store.queryRecord(this.modelName, queryParams);
- });
-});
diff --git a/ui/tests/unit/adapters/cluster-test.js b/ui/tests/unit/adapters/cluster-test.js
deleted file mode 100644
index 543c7f56d..000000000
--- a/ui/tests/unit/adapters/cluster-test.js
+++ /dev/null
@@ -1,253 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { resolve } from 'rsvp';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Adapter | cluster', function (hooks) {
- setupTest(hooks);
-
- test('cluster api urls', function (assert) {
- let url, method, options;
- const adapter = this.owner.factoryFor('adapter:cluster').create({
- ajax: (...args) => {
- [url, method, options] = args;
- return resolve();
- },
- });
- adapter.health();
- assert.strictEqual(url, '/v1/sys/health', 'health url OK');
- assert.deepEqual(
- {
- standbycode: 200,
- sealedcode: 200,
- uninitcode: 200,
- drsecondarycode: 200,
- performancestandbycode: 200,
- },
- options.data,
- 'health data params OK'
- );
- assert.strictEqual(method, 'GET', 'health method OK');
-
- adapter.sealStatus();
- assert.strictEqual(url, '/v1/sys/seal-status', 'health url OK');
- assert.strictEqual(method, 'GET', 'seal-status method OK');
-
- let data = { someData: 1 };
- adapter.unseal(data);
- assert.strictEqual(url, '/v1/sys/unseal', 'unseal url OK');
- assert.strictEqual(method, 'PUT', 'unseal method OK');
- assert.deepEqual({ data, unauthenticated: true }, options, 'unseal options OK');
-
- adapter.initCluster(data);
- assert.strictEqual(url, '/v1/sys/init', 'init url OK');
- assert.strictEqual(method, 'PUT', 'init method OK');
- assert.deepEqual({ data, unauthenticated: true }, options, 'init options OK');
-
- data = { token: 'token', password: 'password', username: 'username' };
-
- adapter.authenticate({ backend: 'token', data });
- assert.strictEqual(url, '/v1/auth/token/lookup-self', 'auth:token url OK');
- assert.strictEqual(method, 'GET', 'auth:token method OK');
- assert.deepEqual(
- { headers: { 'X-Vault-Token': 'token' }, unauthenticated: true },
- options,
- 'auth:token options OK'
- );
-
- adapter.authenticate({ backend: 'github', data });
- assert.strictEqual(url, '/v1/auth/github/login', 'auth:github url OK');
- assert.strictEqual(method, 'POST', 'auth:github method OK');
- assert.deepEqual(
- { data: { password: 'password', token: 'token' }, unauthenticated: true },
- options,
- 'auth:github options OK'
- );
-
- data = { jwt: 'token', role: 'test' };
- adapter.authenticate({ backend: 'jwt', data });
- assert.strictEqual(url, '/v1/auth/jwt/login', 'auth:jwt url OK');
- assert.strictEqual(method, 'POST', 'auth:jwt method OK');
- assert.deepEqual(
- { data: { jwt: 'token', role: 'test' }, unauthenticated: true },
- options,
- 'auth:jwt options OK'
- );
-
- data = { jwt: 'token', role: 'test', path: 'oidc' };
- adapter.authenticate({ backend: 'jwt', data });
- assert.strictEqual(url, '/v1/auth/oidc/login', 'auth:jwt custom mount path, url OK');
-
- data = { token: 'token', password: 'password', username: 'username', path: 'path' };
-
- adapter.authenticate({ backend: 'token', data });
- assert.strictEqual(url, '/v1/auth/token/lookup-self', 'auth:token url with path OK');
-
- adapter.authenticate({ backend: 'github', data });
- assert.strictEqual(url, '/v1/auth/path/login', 'auth:github with path url OK');
-
- data = { password: 'password', username: 'username' };
-
- adapter.authenticate({ backend: 'userpass', data });
- assert.strictEqual(url, '/v1/auth/userpass/login/username', 'auth:userpass url OK');
- assert.strictEqual(method, 'POST', 'auth:userpass method OK');
- assert.deepEqual(
- { data: { password: 'password' }, unauthenticated: true },
- options,
- 'auth:userpass options OK'
- );
-
- adapter.authenticate({ backend: 'radius', data });
- assert.strictEqual(url, '/v1/auth/radius/login/username', 'auth:RADIUS url OK');
- assert.strictEqual(method, 'POST', 'auth:RADIUS method OK');
- assert.deepEqual(
- { data: { password: 'password' }, unauthenticated: true },
- options,
- 'auth:RADIUS options OK'
- );
-
- adapter.authenticate({ backend: 'LDAP', data });
- assert.strictEqual(url, '/v1/auth/ldap/login/username', 'ldap:userpass url OK');
- assert.strictEqual(method, 'POST', 'ldap:userpass method OK');
- assert.deepEqual(
- { data: { password: 'password' }, unauthenticated: true },
- options,
- 'ldap:userpass options OK'
- );
-
- data = { password: 'password', username: 'username', nonce: 'uuid' };
- adapter.authenticate({ backend: 'okta', data });
- assert.strictEqual(url, '/v1/auth/okta/login/username', 'okta:userpass url OK');
- assert.strictEqual(method, 'POST', 'ldap:userpass method OK');
- assert.deepEqual(
- { data: { password: 'password', nonce: 'uuid' }, unauthenticated: true },
- options,
- 'okta:userpass options OK'
- );
-
- // use a custom mount path
- data = { password: 'password', username: 'username', path: 'path' };
-
- adapter.authenticate({ backend: 'userpass', data });
- assert.strictEqual(url, '/v1/auth/path/login/username', 'auth:userpass with path url OK');
-
- adapter.authenticate({ backend: 'LDAP', data });
- assert.strictEqual(url, '/v1/auth/path/login/username', 'auth:LDAP with path url OK');
-
- data = { password: 'password', username: 'username', path: 'path', nonce: 'uuid' };
- adapter.authenticate({ backend: 'Okta', data });
- assert.strictEqual(url, '/v1/auth/path/login/username', 'auth:Okta with path url OK');
- });
-
- test('cluster replication api urls', function (assert) {
- let url, method, options;
- const adapter = this.owner.factoryFor('adapter:cluster').create({
- ajax: (...args) => {
- [url, method, options] = args;
- return resolve();
- },
- });
-
- adapter.replicationStatus();
- assert.strictEqual(url, '/v1/sys/replication/status', 'replication:status url OK');
- assert.strictEqual(method, 'GET', 'replication:status method OK');
- assert.deepEqual({ unauthenticated: true }, options, 'replication:status options OK');
-
- adapter.replicationAction('recover', 'dr');
- assert.strictEqual(url, '/v1/sys/replication/recover', 'replication: recover url OK');
- assert.strictEqual(method, 'POST', 'replication:recover method OK');
-
- adapter.replicationAction('reindex', 'dr');
- assert.strictEqual(url, '/v1/sys/replication/reindex', 'replication: reindex url OK');
- assert.strictEqual(method, 'POST', 'replication:reindex method OK');
-
- adapter.replicationAction('enable', 'dr', 'primary');
- assert.strictEqual(url, '/v1/sys/replication/dr/primary/enable', 'replication:dr primary:enable url OK');
- assert.strictEqual(method, 'POST', 'replication:primary:enable method OK');
- adapter.replicationAction('enable', 'performance', 'primary');
- assert.strictEqual(
- url,
- '/v1/sys/replication/performance/primary/enable',
- 'replication:performance primary:enable url OK'
- );
-
- adapter.replicationAction('enable', 'dr', 'secondary');
- assert.strictEqual(
- url,
- '/v1/sys/replication/dr/secondary/enable',
- 'replication:dr secondary:enable url OK'
- );
- assert.strictEqual(method, 'POST', 'replication:secondary:enable method OK');
- adapter.replicationAction('enable', 'performance', 'secondary');
- assert.strictEqual(
- url,
- '/v1/sys/replication/performance/secondary/enable',
- 'replication:performance secondary:enable url OK'
- );
-
- adapter.replicationAction('disable', 'dr', 'primary');
- assert.strictEqual(
- url,
- '/v1/sys/replication/dr/primary/disable',
- 'replication:dr primary:disable url OK'
- );
- assert.strictEqual(method, 'POST', 'replication:primary:disable method OK');
- adapter.replicationAction('disable', 'performance', 'primary');
- assert.strictEqual(
- url,
- '/v1/sys/replication/performance/primary/disable',
- 'replication:performance primary:disable url OK'
- );
-
- adapter.replicationAction('disable', 'dr', 'secondary');
- assert.strictEqual(
- url,
- '/v1/sys/replication/dr/secondary/disable',
- 'replication: drsecondary:disable url OK'
- );
- assert.strictEqual(method, 'POST', 'replication:secondary:disable method OK');
- adapter.replicationAction('disable', 'performance', 'secondary');
- assert.strictEqual(
- url,
- '/v1/sys/replication/performance/secondary/disable',
- 'replication: performance:disable url OK'
- );
-
- adapter.replicationAction('demote', 'dr', 'primary');
- assert.strictEqual(url, '/v1/sys/replication/dr/primary/demote', 'replication: dr primary:demote url OK');
- assert.strictEqual(method, 'POST', 'replication:primary:demote method OK');
- adapter.replicationAction('demote', 'performance', 'primary');
- assert.strictEqual(
- url,
- '/v1/sys/replication/performance/primary/demote',
- 'replication: performance primary:demote url OK'
- );
-
- adapter.replicationAction('promote', 'performance', 'secondary');
- assert.strictEqual(method, 'POST', 'replication:secondary:promote method OK');
- assert.strictEqual(
- url,
- '/v1/sys/replication/performance/secondary/promote',
- 'replication:performance secondary:promote url OK'
- );
-
- adapter.replicationDrPromote();
- assert.strictEqual(
- url,
- '/v1/sys/replication/dr/secondary/promote',
- 'replication:dr secondary:promote url OK'
- );
- assert.strictEqual(method, 'PUT', 'replication:dr secondary:promote method OK');
- adapter.replicationDrPromote({}, { checkStatus: true });
- assert.strictEqual(
- url,
- '/v1/sys/replication/dr/secondary/promote',
- 'replication:dr secondary:promote url OK'
- );
- assert.strictEqual(method, 'GET', 'replication:dr secondary:promote method OK');
- });
-});
diff --git a/ui/tests/unit/adapters/console-test.js b/ui/tests/unit/adapters/console-test.js
deleted file mode 100644
index 3aec9f976..000000000
--- a/ui/tests/unit/adapters/console-test.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Adapter | console', function (hooks) {
- setupTest(hooks);
-
- test('it builds the correct URL', function (assert) {
- const adapter = this.owner.lookup('adapter:console');
- const sysPath = 'sys/health';
- const awsPath = 'aws/roles/my-other-role';
- assert.strictEqual(adapter.buildURL(sysPath), '/v1/sys/health');
- assert.strictEqual(adapter.buildURL(awsPath), '/v1/aws/roles/my-other-role');
- });
-});
diff --git a/ui/tests/unit/adapters/identity/_test-cases.js b/ui/tests/unit/adapters/identity/_test-cases.js
deleted file mode 100644
index 2ea5d9d51..000000000
--- a/ui/tests/unit/adapters/identity/_test-cases.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-export const storeMVP = {
- serializerFor() {
- return {
- serializeIntoHash() {},
- };
- },
-};
-
-export default function (modelName) {
- return [
- {
- adapterMethod: 'findRecord',
- args: [null, { modelName }, 'foo'],
- url: `/v1/${modelName}/id/foo`,
- method: 'GET',
- },
-
- {
- adapterMethod: 'createRecord',
- args: [storeMVP, { modelName }],
- url: `/v1/${modelName}`,
- method: 'POST',
- },
- {
- adapterMethod: 'updateRecord',
- args: [storeMVP, { modelName }, { id: 'foo', modelName }],
- url: `/v1/${modelName}/id/foo`,
- method: 'PUT',
- },
- {
- adapterMethod: 'deleteRecord',
- args: [storeMVP, { modelName }, { id: 'foo', modelName }],
- url: `/v1/${modelName}/id/foo`,
- method: 'DELETE',
- },
- {
- adapterMethod: 'query',
- args: [null, { modelName }, {}],
- url: `/v1/${modelName}/id?list=true`,
- method: 'GET',
- },
- ];
-}
diff --git a/ui/tests/unit/adapters/identity/entity-alias-test.js b/ui/tests/unit/adapters/identity/entity-alias-test.js
deleted file mode 100644
index e66051401..000000000
--- a/ui/tests/unit/adapters/identity/entity-alias-test.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import testCases from './_test-cases';
-import apiStub from 'vault/tests/helpers/noop-all-api-requests';
-
-module('Unit | Adapter | identity/entity-alias', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.server = apiStub();
- });
-
- hooks.afterEach(function () {
- this.server.shutdown();
- });
-
- const cases = testCases('identity/entity-alias');
-
- cases.forEach((testCase) => {
- test(`entity-alias#${testCase.adapterMethod}`, function (assert) {
- assert.expect(2);
- const adapter = this.owner.lookup('adapter:identity/entity-alias');
- adapter[testCase.adapterMethod](...testCase.args);
- const { url, method } = this.server.handledRequests[0];
- assert.strictEqual(
- url,
- testCase.url,
- `${testCase.adapterMethod} calls the correct url: ${testCase.url}`
- );
- assert.strictEqual(
- method,
- testCase.method,
- `${testCase.adapterMethod} uses the correct http verb: ${testCase.method}`
- );
- });
- });
-});
diff --git a/ui/tests/unit/adapters/identity/entity-merge-test.js b/ui/tests/unit/adapters/identity/entity-merge-test.js
deleted file mode 100644
index 538520e94..000000000
--- a/ui/tests/unit/adapters/identity/entity-merge-test.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import Pretender from 'pretender';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { storeMVP } from './_test-cases';
-
-module('Unit | Adapter | identity/entity-merge', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.server = new Pretender(function () {
- this.post('/v1/**', (response) => {
- return [response, { 'Content-Type': 'application/json' }, JSON.stringify({})];
- });
- });
- });
-
- hooks.afterEach(function () {
- this.server.shutdown();
- });
-
- test(`entity-merge#createRecord`, function (assert) {
- assert.expect(2);
- const adapter = this.owner.lookup('adapter:identity/entity-merge');
- adapter.createRecord(storeMVP, { modelName: 'identity/entity-merge' }, { attr: (x) => x });
- const { url, method } = this.server.handledRequests[0];
- assert.strictEqual(url, `/v1/identity/entity/merge`, ` calls the correct url`);
- assert.strictEqual(method, 'POST', `uses the correct http verb: POST`);
- });
-});
diff --git a/ui/tests/unit/adapters/identity/entity-test.js b/ui/tests/unit/adapters/identity/entity-test.js
deleted file mode 100644
index a6211ec8b..000000000
--- a/ui/tests/unit/adapters/identity/entity-test.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import testCases from './_test-cases';
-import apiStub from 'vault/tests/helpers/noop-all-api-requests';
-
-module('Unit | Adapter | identity/entity', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.server = apiStub();
- });
-
- hooks.afterEach(function () {
- this.server.shutdown();
- });
-
- const cases = testCases('identit/entity');
-
- cases.forEach((testCase) => {
- test(`entity#${testCase.adapterMethod}`, function (assert) {
- assert.expect(2);
- const adapter = this.owner.lookup('adapter:identity/entity');
- adapter[testCase.adapterMethod](...testCase.args);
- const { url, method } = this.server.handledRequests[0];
- assert.strictEqual(
- url,
- testCase.url,
- `${testCase.adapterMethod} calls the correct url: ${testCase.url}`
- );
- assert.strictEqual(
- method,
- testCase.method,
- `${testCase.adapterMethod} uses the correct http verb: ${testCase.method}`
- );
- });
- });
-});
diff --git a/ui/tests/unit/adapters/identity/group-alias-test.js b/ui/tests/unit/adapters/identity/group-alias-test.js
deleted file mode 100644
index a6d92478f..000000000
--- a/ui/tests/unit/adapters/identity/group-alias-test.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import testCases from './_test-cases';
-import apiStub from 'vault/tests/helpers/noop-all-api-requests';
-
-module('Unit | Adapter | identity/group-alias', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.server = apiStub();
- });
-
- hooks.afterEach(function () {
- this.server.shutdown();
- });
-
- const cases = testCases('identity/group-alias');
-
- cases.forEach((testCase) => {
- test(`group-alias#${testCase.adapterMethod}`, function (assert) {
- assert.expect(2);
- const adapter = this.owner.lookup('adapter:identity/group-alias');
- adapter[testCase.adapterMethod](...testCase.args);
- const { url, method } = this.server.handledRequests[0];
- assert.strictEqual(
- url,
- testCase.url,
- `${testCase.adapterMethod} calls the correct url: ${testCase.url}`
- );
- assert.strictEqual(
- method,
- testCase.method,
- `${testCase.adapterMethod} uses the correct http verb: ${testCase.method}`
- );
- });
- });
-});
diff --git a/ui/tests/unit/adapters/identity/group-test.js b/ui/tests/unit/adapters/identity/group-test.js
deleted file mode 100644
index b314cb3b8..000000000
--- a/ui/tests/unit/adapters/identity/group-test.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import testCases from './_test-cases';
-import apiStub from 'vault/tests/helpers/noop-all-api-requests';
-
-module('Unit | Adapter | identity/group', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.server = apiStub();
- });
-
- hooks.afterEach(function () {
- this.server.shutdown();
- });
-
- const cases = testCases('identit/entity');
-
- cases.forEach((testCase) => {
- test(`group#${testCase.adapterMethod}`, function (assert) {
- assert.expect(2);
- const adapter = this.owner.lookup('adapter:identity/group');
- adapter[testCase.adapterMethod](...testCase.args);
- const { url, method } = this.server.handledRequests[0];
- assert.strictEqual(
- url,
- testCase.url,
- `${testCase.adapterMethod} calls the correct url: ${testCase.url}`
- );
- assert.strictEqual(
- method,
- testCase.method,
- `${testCase.adapterMethod} uses the correct http verb: ${testCase.method}`
- );
- });
- });
-});
diff --git a/ui/tests/unit/adapters/kmip/role-test.js b/ui/tests/unit/adapters/kmip/role-test.js
deleted file mode 100644
index a9d20d900..000000000
--- a/ui/tests/unit/adapters/kmip/role-test.js
+++ /dev/null
@@ -1,88 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Adapter | kmip/role', function (hooks) {
- setupTest(hooks);
-
- const serializeTests = [
- [
- 'operation_all is the only operation item present after serialization',
- {
- serialize() {
- return { operation_all: true, operation_get: true, operation_create: true, tls_ttl: '10s' };
- },
- record: {
- nonOperationFields: ['tlsTtl'],
- },
- },
- {
- operation_all: true,
- tls_ttl: '10s',
- },
- ],
- [
- 'serialize does not include nonOperationFields values if they are not set',
- {
- serialize() {
- return { operation_all: true, operation_get: true, operation_create: true };
- },
- record: {
- nonOperationFields: ['tlsTtl'],
- },
- },
- {
- operation_all: true,
- },
- ],
- [
- 'operation_none is the only operation item present after serialization',
- {
- serialize() {
- return { operation_none: true, operation_get: true, operation_add_attribute: true, tls_ttl: '10s' };
- },
- record: {
- nonOperationFields: ['tlsTtl'],
- },
- },
- {
- operation_none: true,
- tls_ttl: '10s',
- },
- ],
- [
- 'operation_all and operation_none are removed if not truthy',
- {
- serialize() {
- return {
- operation_all: false,
- operation_none: false,
- operation_get: true,
- operation_add_attribute: true,
- operation_destroy: true,
- };
- },
- record: {
- nonOperationFields: ['tlsTtl'],
- },
- },
- {
- operation_get: true,
- operation_add_attribute: true,
- operation_destroy: true,
- },
- ],
- ];
- for (const testCase of serializeTests) {
- const [name, snapshotStub, expected] = testCase;
- test(`adapter serialize: ${name}`, function (assert) {
- const adapter = this.owner.lookup('adapter:kmip/role');
- const result = adapter.serialize(snapshotStub);
- assert.deepEqual(result, expected, 'output matches expected');
- });
- }
-});
diff --git a/ui/tests/unit/adapters/kubernetes/config-test.js b/ui/tests/unit/adapters/kubernetes/config-test.js
deleted file mode 100644
index edc73f766..000000000
--- a/ui/tests/unit/adapters/kubernetes/config-test.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-
-module('Unit | Adapter | kubernetes/config', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.store.unloadAll('kubernetes/config');
- });
-
- test('it should make request to correct endpoint when querying record', async function (assert) {
- assert.expect(1);
- this.server.get('/kubernetes-test/config', () => {
- assert.ok('GET request made to correct endpoint when querying record');
- });
- await this.store.queryRecord('kubernetes/config', { backend: 'kubernetes-test' });
- });
-
- test('it should make request to correct endpoint when creating new record', async function (assert) {
- assert.expect(1);
- this.server.post('/kubernetes-test/config', () => {
- assert.ok('POST request made to correct endpoint when creating new record');
- });
- const record = this.store.createRecord('kubernetes/config', { backend: 'kubernetes-test' });
- await record.save();
- });
-
- test('it should make request to correct endpoint when updating record', async function (assert) {
- assert.expect(1);
- this.server.post('/kubernetes-test/config', () => {
- assert.ok('POST request made to correct endpoint when updating record');
- });
- this.store.pushPayload('kubernetes/config', {
- modelName: 'kubernetes/config',
- backend: 'kubernetes-test',
- });
- const record = this.store.peekRecord('kubernetes/config', 'kubernetes-test');
- await record.save();
- });
-
- test('it should make request to correct endpoint when deleting record', async function (assert) {
- assert.expect(1);
- this.server.delete('/kubernetes-test/config', () => {
- assert.ok('DELETE request made to correct endpoint when deleting record');
- });
- this.store.pushPayload('kubernetes/config', {
- modelName: 'kubernetes/config',
- backend: 'kubernetes-test',
- });
- const record = this.store.peekRecord('kubernetes/config', 'kubernetes-test');
- await record.destroyRecord();
- });
-});
diff --git a/ui/tests/unit/adapters/kubernetes/role-test.js b/ui/tests/unit/adapters/kubernetes/role-test.js
deleted file mode 100644
index 0db589e6d..000000000
--- a/ui/tests/unit/adapters/kubernetes/role-test.js
+++ /dev/null
@@ -1,76 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-
-module('Unit | Adapter | kubernetes/role', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.store.unloadAll('kubernetes/role');
- });
-
- test('it should make request to correct endpoint when listing records', async function (assert) {
- assert.expect(1);
- this.server.get('/kubernetes-test/roles', (schema, req) => {
- assert.ok(req.queryParams.list, 'GET request made to correct endpoint when listing records');
- return { data: { keys: ['test-role'] } };
- });
- await this.store.query('kubernetes/role', { backend: 'kubernetes-test' });
- });
-
- test('it should make request to correct endpoint when querying record', async function (assert) {
- assert.expect(1);
- this.server.get('/kubernetes-test/roles/test-role', () => {
- assert.ok('GET request made to correct endpoint when querying record');
- return { data: {} };
- });
- await this.store.queryRecord('kubernetes/role', { backend: 'kubernetes-test', name: 'test-role' });
- });
-
- test('it should make request to correct endpoint when creating new record', async function (assert) {
- assert.expect(1);
- this.server.post('/kubernetes-test/roles/test-role', () => {
- assert.ok('POST request made to correct endpoint when creating new record');
- });
- const record = this.store.createRecord('kubernetes/role', {
- backend: 'kubernetes-test',
- name: 'test-role',
- });
- await record.save();
- });
-
- test('it should make request to correct endpoint when updating record', async function (assert) {
- assert.expect(1);
- this.server.post('/kubernetes-test/roles/test-role', () => {
- assert.ok('POST request made to correct endpoint when updating record');
- });
- this.store.pushPayload('kubernetes/role', {
- modelName: 'kubernetes/role',
- backend: 'kubernetes-test',
- name: 'test-role',
- });
- const record = this.store.peekRecord('kubernetes/role', 'test-role');
- await record.save();
- });
-
- test('it should make request to correct endpoint when deleting record', async function (assert) {
- assert.expect(1);
- this.server.delete('/kubernetes-test/roles/test-role', () => {
- assert.ok('DELETE request made to correct endpoint when deleting record');
- });
- this.store.pushPayload('kubernetes/role', {
- modelName: 'kubernetes/role',
- backend: 'kubernetes-test',
- name: 'test-role',
- });
- const record = this.store.peekRecord('kubernetes/role', 'test-role');
- await record.destroyRecord();
- });
-});
diff --git a/ui/tests/unit/adapters/oidc/assignment-test.js b/ui/tests/unit/adapters/oidc/assignment-test.js
deleted file mode 100644
index d898ac55c..000000000
--- a/ui/tests/unit/adapters/oidc/assignment-test.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-import testHelper from './test-helper';
-
-module('Unit | Adapter | oidc/assignment', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.modelName = 'oidc/assignment';
- this.data = {
- name: 'foo-assignment',
- entity_ids: ['my-entity'],
- group_ids: ['my-group'],
- };
- this.path = '/identity/oidc/assignment/foo-assignment';
- });
-
- testHelper(test);
-});
diff --git a/ui/tests/unit/adapters/oidc/client-test.js b/ui/tests/unit/adapters/oidc/client-test.js
deleted file mode 100644
index 98b2c077d..000000000
--- a/ui/tests/unit/adapters/oidc/client-test.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-import testHelper from './test-helper';
-
-module('Unit | Adapter | oidc/client', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.modelName = 'oidc/client';
- this.data = {
- name: 'client-1',
- key: 'test-key',
- access_token_ttl: '30m',
- id_token_ttl: '1h',
- };
- this.path = '/identity/oidc/client/client-1';
- });
-
- testHelper(test);
-});
diff --git a/ui/tests/unit/adapters/oidc/key-test.js b/ui/tests/unit/adapters/oidc/key-test.js
deleted file mode 100644
index c472a7e9b..000000000
--- a/ui/tests/unit/adapters/oidc/key-test.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-import testHelper from './test-helper';
-
-module('Unit | Adapter | oidc/key', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.modelName = 'oidc/key';
- this.data = {
- name: 'foo-key',
- rotation_period: '12h',
- verification_ttl: 43200,
- };
- this.path = '/identity/oidc/key/foo-key';
- });
-
- testHelper(test);
-
- test('it should make request to correct endpoint on rotate', async function (assert) {
- assert.expect(1);
-
- this.server.post(`${this.path}/rotate`, (schema, req) => {
- const json = JSON.parse(req.requestBody);
- assert.strictEqual(json.verification_ttl, '30m', 'request made to correct endpoint on rotate');
- });
-
- await this.store.adapterFor('oidc/key').rotate(this.data.name, '30m');
- });
-});
diff --git a/ui/tests/unit/adapters/oidc/provider-test.js b/ui/tests/unit/adapters/oidc/provider-test.js
deleted file mode 100644
index 8f447a668..000000000
--- a/ui/tests/unit/adapters/oidc/provider-test.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-import testHelper from './test-helper';
-
-module('Unit | Adapter | oidc/provider', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.modelName = 'oidc/provider';
- this.data = {
- name: 'foo-provider',
- allowed_client_ids: ['*'],
- scopes_supported: [],
- };
- this.path = '/identity/oidc/provider/foo-provider';
- });
-
- testHelper(test);
-});
diff --git a/ui/tests/unit/adapters/oidc/scope-test.js b/ui/tests/unit/adapters/oidc/scope-test.js
deleted file mode 100644
index ecd25bc20..000000000
--- a/ui/tests/unit/adapters/oidc/scope-test.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-import testHelper from './test-helper';
-
-module('Unit | Adapter | oidc/key', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.modelName = 'oidc/scope';
- this.data = {
- name: 'foo-scope',
- template: '{ "groups": {{identity.entity.groups.names}} }',
- description: 'A simple scope example.',
- };
- this.path = '/identity/oidc/scope/foo-scope';
- });
-
- testHelper(test);
-});
diff --git a/ui/tests/unit/adapters/oidc/test-helper.js b/ui/tests/unit/adapters/oidc/test-helper.js
deleted file mode 100644
index ebc47a958..000000000
--- a/ui/tests/unit/adapters/oidc/test-helper.js
+++ /dev/null
@@ -1,174 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-export default (test) => {
- test('it should make request to correct endpoint on save', async function (assert) {
- assert.expect(1);
-
- this.server.post(this.path, () => {
- assert.ok(true, 'request made to correct endpoint on save');
- });
-
- const model = this.store.createRecord(this.modelName, this.data);
- await model.save();
- });
-
- test('it should throw error if attempting to createRecord with an existing name', async function (assert) {
- const { modelName, data } = this;
- this.store.pushPayload(modelName, { modelName, name: data.name });
-
- const model = this.store.createRecord(modelName, data);
- assert.rejects(model.save(), `Error: A record already exists with the name: ${data.name}`);
- });
-
- test('it should make request to correct endpoint on find', async function (assert) {
- assert.expect(1);
-
- this.server.get(this.path, () => {
- assert.ok(true, 'request is made to correct endpoint on find');
- return { data: this.data };
- });
-
- this.store.findRecord(this.modelName, this.data.name);
- });
-
- test('it should make request to correct endpoint on query', async function (assert) {
- const keyInfoModels = ['client', 'provider']; // these models have key_info on the LIST response
- const { name, ...otherAttrs } = this.data; // excludes name from key_info data
- const key_info = { [name]: { ...otherAttrs } };
-
- this.server.get(`/identity/${this.modelName}`, (schema, req) => {
- assert.strictEqual(req.queryParams.list, 'true', 'request is made to correct endpoint on query');
- if (keyInfoModels.some((model) => this.modelName.includes(model))) {
- return { data: { keys: [name], key_info } };
- } else {
- return { data: { keys: [name] } };
- }
- });
-
- this.store.query(this.modelName, {});
- });
-
- test('it should filter query when passed filterFor and paramKey', async function (assert) {
- const keyInfoModels = ['client', 'provider']; // these models have key_info on the LIST response
- const keys = ['model-1', 'model-2', 'model-3'];
- const key_info = {
- 'model-1': {
- model_id: 'a123',
- key: 'test-key',
- access_token_ttl: '30m',
- id_token_ttl: '1h',
- },
- 'model-2': {
- model_id: 'b123',
- key: 'test-key',
- access_token_ttl: '30m',
- id_token_ttl: '1h',
- },
- 'model-3': {
- model_id: 'c123',
- key: 'test-key',
- access_token_ttl: '30m',
- id_token_ttl: '1h',
- },
- };
-
- this.server.get(`/identity/${this.modelName}`, () => {
- if (keyInfoModels.some((model) => this.modelName.includes(model))) {
- return { data: { keys, key_info } };
- } else {
- return { data: { keys: [this.data.name] } };
- }
- });
-
- // test passing 'paramKey' and 'filterFor' to query and filterListResponse in adapters/named-path.js works as expected
- if (keyInfoModels.some((model) => this.modelName.includes(model))) {
- let testQuery = ['*', 'a123'];
- await this.store
- .query(this.modelName, { paramKey: 'model_id', filterFor: testQuery })
- .then((resp) =>
- assert.strictEqual(resp.content.length, 3, 'returns all models when ids include glob (*)')
- );
-
- testQuery = ['*'];
- await this.store
- .query(this.modelName, { paramKey: 'model_id', filterFor: testQuery })
- .then((resp) =>
- assert.strictEqual(resp.content.length, 3, 'returns all models when glob (*) is only id')
- );
-
- testQuery = ['b123'];
- await this.store.query(this.modelName, { paramKey: 'model_id', filterFor: testQuery }).then((resp) => {
- assert.strictEqual(resp.content.length, 1, 'filters response and returns only matching id');
-
- assert.strictEqual(resp.firstObject.name, 'model-2', 'response contains correct model');
- });
-
- testQuery = ['b123', 'c123'];
- await this.store.query(this.modelName, { paramKey: 'model_id', filterFor: testQuery }).then((resp) => {
- assert.strictEqual(resp.content.length, 2, 'filters response when passed multiple ids');
- resp.content.forEach((m) =>
- assert.ok(['model-2', 'model-3'].includes(m.id), `it filters correctly and included: ${m.id}`)
- );
- });
-
- await this.store
- .query(this.modelName, { paramKey: 'nonexistent_key', filterFor: testQuery })
- .then((resp) => assert.ok(resp.isLoaded, 'does not error when paramKey does not exist'));
-
- assert.rejects(
- this.store.query(this.modelName, { paramKey: 'model_id', filterFor: 'some-string' }),
- 'throws assertion when filterFor is not an array'
- );
- } else {
- const testQuery = ['b123', 'c123'];
- await this.store
- .query(this.modelName, { paramKey: 'model_id', filterFor: testQuery })
- .then((resp) => assert.ok(resp.isLoaded, 'does not error when key_info does not exist'));
- }
- });
-
- test('it passes allowed_client_id only when the param exists', async function (assert) {
- const keyInfoModels = ['client', 'provider']; // these models have key_info on the LIST response
- const { name, ...otherAttrs } = this.data; // excludes name from key_info data
- const key_info = { [name]: { ...otherAttrs } };
-
- this.server.get(`/identity/${this.modelName}`, (schema, req) => {
- if (this.modelName === 'oidc/provider') {
- assert.propEqual(
- req.queryParams,
- { list: 'true', allowed_client_id: 'a123' },
- 'request has allowed_client_id as query param'
- );
- } else {
- assert.propEqual(req.queryParams, { list: 'true' }, 'request only has `list` param');
- }
- if (keyInfoModels.some((model) => this.modelName.includes(model))) {
- return { data: { keys: [name], key_info } };
- } else {
- return { data: { keys: [name] } };
- }
- });
-
- // only /provider accepts an allowed_client_id
- if (this.modelName === 'oidc/provider') {
- this.store.query(this.modelName, { allowed_client_id: 'a123' });
- } else {
- this.store.query(this.modelName, {});
- }
- });
-
- test('it should make request to correct endpoint on delete', async function (assert) {
- assert.expect(1);
-
- this.server.get(this.path, () => ({ data: this.data }));
- this.server.delete(this.path, () => {
- assert.ok(true, 'request made to correct endpoint on delete');
- });
-
- const model = await this.store.findRecord(this.modelName, this.data.name);
- await model.destroyRecord();
- });
-};
diff --git a/ui/tests/unit/adapters/pki/action-test.js b/ui/tests/unit/adapters/pki/action-test.js
deleted file mode 100644
index f08f13ff1..000000000
--- a/ui/tests/unit/adapters/pki/action-test.js
+++ /dev/null
@@ -1,260 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'vault/tests/helpers';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs';
-import { rootPem } from 'vault/tests/helpers/pki/values';
-
-module('Unit | Adapter | pki/action', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.secretMountPath = this.owner.lookup('service:secret-mount-path');
- this.backend = 'pki-test';
- this.secretMountPath.currentPath = this.backend;
- this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub());
- this.emptyResponse = {
- // Action adapter uses request_id as ember data id for response
- request_id: '123',
- data: {},
- };
- });
-
- test('it exists', function (assert) {
- const adapter = this.owner.lookup('adapter:pki/action');
- assert.ok(adapter);
- });
-
- module('actionType import', function (hooks) {
- hooks.beforeEach(function () {
- this.payload = {
- pem_bundle: rootPem,
- };
- });
-
- test('it calls the correct endpoint when useIssuer = false', async function (assert) {
- assert.expect(1);
-
- this.server.post(`${this.backend}/config/ca`, () => {
- assert.ok(true, 'request made to correct endpoint on create');
- return this.emptyResponse;
- });
-
- await this.store
- .createRecord('pki/action', this.payload)
- .save({ adapterOptions: { actionType: 'import', useIssuer: false } });
- });
-
- test('it calls the correct endpoint when useIssuer = true', async function (assert) {
- assert.expect(1);
- this.server.post(`${this.backend}/issuers/import/bundle`, () => {
- assert.ok(true, 'request made to correct endpoint on create');
- return this.emptyResponse;
- });
-
- await this.store
- .createRecord('pki/action', this.payload)
- .save({ adapterOptions: { actionType: 'import', useIssuer: true } });
- });
- });
-
- module('actionType generate-root', function () {
- test('it calls the correct endpoint when useIssuer = false', async function (assert) {
- assert.expect(4);
- const adapterOptions = { adapterOptions: { actionType: 'generate-root', useIssuer: false } };
- this.server.post(`${this.backend}/root/generate/internal`, () => {
- assert.ok(true, 'request made correctly when type = internal');
- return this.emptyResponse;
- });
- this.server.post(`${this.backend}/root/generate/exported`, () => {
- assert.ok(true, 'request made correctly when type = exported');
- return this.emptyResponse;
- });
- this.server.post(`${this.backend}/root/generate/existing`, () => {
- assert.ok(true, 'request made correctly when type = exising');
- return this.emptyResponse;
- });
- this.server.post(`${this.backend}/root/generate/kms`, () => {
- assert.ok(true, 'request made correctly when type = kms');
- return this.emptyResponse;
- });
-
- await this.store
- .createRecord('pki/action', {
- type: 'internal',
- })
- .save(adapterOptions);
- await this.store
- .createRecord('pki/action', {
- type: 'exported',
- })
- .save(adapterOptions);
- await this.store
- .createRecord('pki/action', {
- type: 'existing',
- })
- .save(adapterOptions);
- await this.store
- .createRecord('pki/action', {
- type: 'kms',
- })
- .save(adapterOptions);
- });
-
- test('it calls the correct endpoint when useIssuer = true', async function (assert) {
- assert.expect(4);
- const adapterOptions = { adapterOptions: { actionType: 'generate-root', useIssuer: true } };
- this.server.post(`${this.backend}/issuers/generate/root/internal`, () => {
- assert.ok(true, 'request made correctly when type = internal');
- return this.emptyResponse;
- });
- this.server.post(`${this.backend}/issuers/generate/root/exported`, () => {
- assert.ok(true, 'request made correctly when type = exported');
- return this.emptyResponse;
- });
- this.server.post(`${this.backend}/issuers/generate/root/existing`, () => {
- assert.ok(true, 'request made correctly when type = exising');
- return this.emptyResponse;
- });
- this.server.post(`${this.backend}/issuers/generate/root/kms`, () => {
- assert.ok(true, 'request made correctly when type = kms');
- return this.emptyResponse;
- });
-
- await this.store
- .createRecord('pki/action', {
- type: 'internal',
- })
- .save(adapterOptions);
- await this.store
- .createRecord('pki/action', {
- type: 'exported',
- })
- .save(adapterOptions);
- await this.store
- .createRecord('pki/action', {
- type: 'existing',
- })
- .save(adapterOptions);
- await this.store
- .createRecord('pki/action', {
- type: 'kms',
- })
- .save(adapterOptions);
- });
- });
-
- module('actionType generate-csr', function () {
- test('it calls the correct endpoint when useIssuer = false', async function (assert) {
- assert.expect(4);
- const adapterOptions = { adapterOptions: { actionType: 'generate-csr', useIssuer: false } };
- this.server.post(`${this.backend}/intermediate/generate/internal`, () => {
- assert.ok(true, 'request made correctly when type = internal');
- return this.emptyResponse;
- });
- this.server.post(`${this.backend}/intermediate/generate/exported`, () => {
- assert.ok(true, 'request made correctly when type = exported');
- return this.emptyResponse;
- });
- this.server.post(`${this.backend}/intermediate/generate/existing`, () => {
- assert.ok(true, 'request made correctly when type = exising');
- return this.emptyResponse;
- });
- this.server.post(`${this.backend}/intermediate/generate/kms`, () => {
- assert.ok(true, 'request made correctly when type = kms');
- return this.emptyResponse;
- });
-
- await this.store
- .createRecord('pki/action', {
- type: 'internal',
- })
- .save(adapterOptions);
- await this.store
- .createRecord('pki/action', {
- type: 'exported',
- })
- .save(adapterOptions);
- await this.store
- .createRecord('pki/action', {
- type: 'existing',
- })
- .save(adapterOptions);
- await this.store
- .createRecord('pki/action', {
- type: 'kms',
- })
- .save(adapterOptions);
- });
-
- test('it calls the correct endpoint when useIssuer = true', async function (assert) {
- assert.expect(4);
- const adapterOptions = { adapterOptions: { actionType: 'generate-csr', useIssuer: true } };
- this.server.post(`${this.backend}/issuers/generate/intermediate/internal`, () => {
- assert.ok(true, 'request made correctly when type = internal');
- return this.emptyResponse;
- });
- this.server.post(`${this.backend}/issuers/generate/intermediate/exported`, () => {
- assert.ok(true, 'request made correctly when type = exported');
- return this.emptyResponse;
- });
- this.server.post(`${this.backend}/issuers/generate/intermediate/existing`, () => {
- assert.ok(true, 'request made correctly when type = exising');
- return this.emptyResponse;
- });
- this.server.post(`${this.backend}/issuers/generate/intermediate/kms`, () => {
- assert.ok(true, 'request made correctly when type = kms');
- return this.emptyResponse;
- });
-
- await this.store
- .createRecord('pki/action', {
- type: 'internal',
- })
- .save(adapterOptions);
- await this.store
- .createRecord('pki/action', {
- type: 'exported',
- })
- .save(adapterOptions);
- await this.store
- .createRecord('pki/action', {
- type: 'existing',
- })
- .save(adapterOptions);
- await this.store
- .createRecord('pki/action', {
- type: 'kms',
- })
- .save(adapterOptions);
- });
- });
-
- module('actionType sign-intermediate', function () {
- test('it overrides backend when adapter options specify a mount', async function (assert) {
- assert.expect(1);
- const mount = 'foo';
- const issuerRef = 'ref';
- const adapterOptions = {
- adapterOptions: { actionType: 'sign-intermediate', mount, issuerRef },
- };
-
- this.server.post(`${mount}/issuer/${issuerRef}/sign-intermediate`, () => {
- assert.ok(true, 'request made to correct mount');
- return this.emptyResponse;
- });
-
- await this.store
- .createRecord('pki/action', {
- csr: '---BEGIN REQUEST---',
- })
- .save(adapterOptions);
- });
- });
-});
diff --git a/ui/tests/unit/adapters/pki/certificate/base-test.js b/ui/tests/unit/adapters/pki/certificate/base-test.js
deleted file mode 100644
index c5edba373..000000000
--- a/ui/tests/unit/adapters/pki/certificate/base-test.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'vault/tests/helpers';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-
-module('Unit | Adapter | pki/certificate/base', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.owner.lookup('service:secretMountPath').update('pki-test');
- });
-
- test('it should make request to correct endpoint on queryRecord', async function (assert) {
- assert.expect(1);
-
- this.server.get('/pki-test/cert/1234', () => {
- assert.ok(true, 'Request made to correct endpoint on queryRecord');
- return { data: {} };
- });
-
- await this.store.queryRecord('pki/certificate/base', { backend: 'pki-test', id: '1234' });
- });
-
- test('it should make request to correct endpoint on query', async function (assert) {
- assert.expect(1);
-
- this.server.get('/pki-test/certs', (schema, req) => {
- assert.strictEqual(req.queryParams.list, 'true', 'Request made to correct endpoint on query');
- return { data: { keys: [] } };
- });
-
- await this.store.query('pki/certificate/base', { backend: 'pki-test' });
- });
-
- test('it should make request to correct endpoint on update', async function (assert) {
- assert.expect(1);
-
- this.store.pushPayload('pki/certificate/base', {
- modelName: 'pki/certificate/base',
- data: {
- serial_number: '1234',
- },
- });
-
- this.server.post('pki-test/revoke', (schema, req) => {
- assert.deepEqual(
- JSON.parse(req.requestBody),
- { serial_number: '1234' },
- 'Request made to correct endpoint on update'
- );
- return { data: {} };
- });
-
- await this.store.peekRecord('pki/certificate/base', '1234').save();
- });
-});
diff --git a/ui/tests/unit/adapters/pki/certificate/generate-test.js b/ui/tests/unit/adapters/pki/certificate/generate-test.js
deleted file mode 100644
index 7fa97bee4..000000000
--- a/ui/tests/unit/adapters/pki/certificate/generate-test.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'vault/tests/helpers';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-
-module('Unit | Adapter | pki/certificate/generate', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.secretMountPath = this.owner.lookup('service:secret-mount-path');
- this.backend = 'pki-test';
- this.secretMountPath.currentPath = this.backend;
- this.data = {
- serial_number: 'my-serial-number',
- certificate: 'some-cert',
- };
- });
-
- test('it should make request to correct endpoint on create', async function (assert) {
- assert.expect(1);
- const generateData = {
- role: 'my-role',
- common_name: 'example.com',
- };
- this.server.post(`${this.backend}/issue/${generateData.role}`, () => {
- assert.ok(true, 'request made to correct endpoint on create');
- return {
- data: {
- serial_number: 'this-serial-number',
- },
- };
- });
-
- const model = await this.store.createRecord('pki/certificate/generate', generateData);
- await model.save();
- });
-});
diff --git a/ui/tests/unit/adapters/pki/certificate/sign-test.js b/ui/tests/unit/adapters/pki/certificate/sign-test.js
deleted file mode 100644
index 02cbf0ca5..000000000
--- a/ui/tests/unit/adapters/pki/certificate/sign-test.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'vault/tests/helpers';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-import { csr2 } from 'vault/tests/helpers/pki/values';
-
-module('Unit | Adapter | pki/certificate/sign', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.secretMountPath = this.owner.lookup('service:secret-mount-path');
- this.backend = 'pki-test';
- this.secretMountPath.currentPath = this.backend;
- this.data = {
- serial_number: 'my-serial-number',
- certificate: 'some-cert',
- };
- });
-
- test('it should make request to correct endpoint on create', async function (assert) {
- assert.expect(1);
- const generateData = {
- role: 'my-role',
- csr: csr2,
- };
- this.server.post(`${this.backend}/sign/${generateData.role}`, () => {
- assert.ok(true, 'request made to correct endpoint on create');
- return {
- data: this.data,
- };
- });
-
- await this.store.createRecord('pki/certificate/sign', generateData).save();
- });
-});
diff --git a/ui/tests/unit/adapters/pki/config-test.js b/ui/tests/unit/adapters/pki/config-test.js
deleted file mode 100644
index 9be9dfdcc..000000000
--- a/ui/tests/unit/adapters/pki/config-test.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'vault/tests/helpers';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-
-module('Unit | Adapter | pki/config', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(async function () {
- this.store = this.owner.lookup('service:store');
- this.backend = 'pki-engine';
- });
-
- const testHelper = (test) => {
- test('it should make request to correct endpoint on update', async function (assert) {
- assert.expect(1);
-
- this.server.post(`/${this.backend}/config/${this.endpoint}`, () => {
- assert.ok(true, `request made to POST config/${this.endpoint} endpoint on update`);
- });
-
- this.store.pushPayload(`pki/config/${this.endpoint}`, {
- modelName: `pki/config/${this.endpoint}`,
- id: this.backend,
- });
-
- const model = this.store.peekRecord(`pki/config/${this.endpoint}`, this.backend);
- await model.save();
- });
-
- test('it should make request to correct endpoint on find', async function (assert) {
- assert.expect(1);
-
- this.server.get(`/${this.backend}/config/${this.endpoint}`, () => {
- assert.ok(true, `request is made to GET /config/${this.endpoint} endpoint on find`);
- return { data: { id: this.backend } };
- });
-
- this.store.findRecord(`pki/config/${this.endpoint}`, this.backend);
- });
- };
-
- module('cluster', function (hooks) {
- hooks.beforeEach(async function () {
- this.endpoint = 'cluster';
- });
- testHelper(test);
- });
-
- module('urls', function (hooks) {
- hooks.beforeEach(async function () {
- this.endpoint = 'urls';
- });
- testHelper(test);
- });
-
- module('crl', function (hooks) {
- hooks.beforeEach(async function () {
- this.endpoint = 'crl';
- });
- testHelper(test);
- });
-});
diff --git a/ui/tests/unit/adapters/pki/key-test.js b/ui/tests/unit/adapters/pki/key-test.js
deleted file mode 100644
index 7fd5ae73d..000000000
--- a/ui/tests/unit/adapters/pki/key-test.js
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-
-module('Unit | Adapter | pki/key', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.secretMountPath = this.owner.lookup('service:secret-mount-path');
- this.backend = 'pki-test';
- this.secretMountPath.currentPath = this.backend;
- this.data = {
- key_id: '724862ff-6438-bad0-b598-77a6c7f4e934',
- key_type: 'ec',
- key_name: 'test-key',
- key_bits: '256',
- };
- });
-
- hooks.afterEach(function () {
- this.server.shutdown();
- });
-
- test('it should make request to correct endpoint on query', async function (assert) {
- assert.expect(1);
- const { key_id, ...otherAttrs } = this.data; // excludes key_id from key_info data
- const key_info = { [key_id]: { ...otherAttrs } };
- this.server.get(`${this.backend}/keys`, (schema, req) => {
- assert.strictEqual(req.queryParams.list, 'true', 'request is made to correct endpoint on query');
- return { data: { keys: [key_id], key_info } };
- });
-
- this.store.query('pki/key', { backend: this.backend });
- });
-
- test('it should make request to correct endpoint on queryRecord', async function (assert) {
- assert.expect(1);
-
- this.server.get(`${this.backend}/key/${this.data.key_id}`, () => {
- assert.ok(true, 'request is made to correct endpoint on query record');
- return { data: this.data };
- });
-
- this.store.queryRecord('pki/key', { backend: this.backend, id: this.data.key_id });
- });
-
- test('it should make request to correct endpoint on delete', async function (assert) {
- assert.expect(1);
- this.store.pushPayload('pki/key', { modelName: 'pki/key', ...this.data });
- this.server.get(`${this.backend}/key/${this.data.key_id}`, () => ({ data: this.data }));
- this.server.delete(`${this.backend}/key/${this.data.key_id}`, () => {
- assert.ok(true, 'request made to correct endpoint on delete');
- });
-
- const model = await this.store.queryRecord('pki/key', { backend: this.backend, id: this.data.key_id });
- await model.destroyRecord();
- });
-});
diff --git a/ui/tests/unit/adapters/pki/role-test.js b/ui/tests/unit/adapters/pki/role-test.js
deleted file mode 100644
index 3781ee42c..000000000
--- a/ui/tests/unit/adapters/pki/role-test.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-
-module('Unit | Adapter | pki/role', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.store.unloadAll('pki/role');
- });
-
- test('it should make request to correct endpoint when updating a record', async function (assert) {
- assert.expect(1);
- this.server.post('/pki-not-hardcoded/roles/pki-test', () => {
- assert.ok(true, 'POST request made to correct endpoint when updating a record');
- });
-
- this.store.pushPayload('pki/role', {
- modelName: 'pki/role',
- backend: 'pki-not-hardcoded',
- id: 'pki-test',
- name: 'pki-test',
- });
- const record = this.store.peekRecord('pki/role', 'pki-test');
- await record.save();
- });
-});
diff --git a/ui/tests/unit/adapters/pki/sign-intermediate-test.js b/ui/tests/unit/adapters/pki/sign-intermediate-test.js
deleted file mode 100644
index 6eefa3ad5..000000000
--- a/ui/tests/unit/adapters/pki/sign-intermediate-test.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'vault/tests/helpers';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-
-module('Unit | Adapter | pki/sign-intermediate', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.secretMountPath = this.owner.lookup('service:secret-mount-path');
- this.backend = 'pki-test';
- this.secretMountPath.currentPath = this.backend;
- this.payload = {
- issuerRef: 'my-issuer-id',
- };
- });
-
- test('it exists', function (assert) {
- const adapter = this.owner.lookup('adapter:pki/sign-intermediate');
- assert.ok(adapter);
- });
-
- test('it calls the correct endpoint on save', async function (assert) {
- assert.expect(2);
-
- this.server.post(`${this.backend}/issuer/my-issuer-id/sign-intermediate`, () => {
- assert.ok(true, 'correct endpoint called');
- return {
- request_id: 'unique-request-id',
- data: {
- foo: 'bar',
- },
- };
- });
-
- const result = await this.store.createRecord('pki/sign-intermediate', this.payload).save();
- assert.strictEqual(result.id, 'unique-request-id', 'Resulting model has ID matching request ID');
- });
-});
diff --git a/ui/tests/unit/adapters/pki/tidy-test.js b/ui/tests/unit/adapters/pki/tidy-test.js
deleted file mode 100644
index 0cba31f1d..000000000
--- a/ui/tests/unit/adapters/pki/tidy-test.js
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'vault/tests/helpers';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs';
-
-module('Unit | Adapter | pki/tidy', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.secretMountPath = this.owner.lookup('service:secret-mount-path');
- this.backend = 'pki-test';
- this.secretMountPath.currentPath = this.backend;
- this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub());
- });
-
- test('it exists', function (assert) {
- const adapter = this.owner.lookup('adapter:pki/tidy');
- assert.ok(adapter);
- });
-
- test('it calls the correct endpoint when tidyType = manual', async function (assert) {
- assert.expect(1);
-
- this.server.post(`${this.backend}/tidy`, () => {
- assert.ok(true, 'request made to correct endpoint on create');
- return {};
- });
- this.payload = {
- tidy_cert_store: true,
- tidy_revocation_queue: false,
- safetyBuffer: '120h',
- backend: this.backend,
- };
- await this.store.createRecord('pki/tidy', this.payload).save({ adapterOptions: { tidyType: 'manual' } });
- });
-
- test('it should make a request to correct endpoint for findRecord', async function (assert) {
- assert.expect(1);
- this.server.get(`${this.backend}/config/auto-tidy`, () => {
- assert.ok(true, 'request made to correct endpoint on create');
- return {
- request_id: '2a4a1f36-20df-e71c-02d6-be15a09656f9',
- lease_id: '',
- renewable: false,
- lease_duration: 0,
- data: {
- acme_account_safety_buffer: 2592000,
- enabled: false,
- interval_duration: 43200,
- issuer_safety_buffer: 31536000,
- maintain_stored_certificate_counts: false,
- pause_duration: '0s',
- publish_stored_certificate_count_metrics: false,
- revocation_queue_safety_buffer: 172800,
- safety_buffer: 259200,
- tidy_acme: false,
- tidy_cert_store: false,
- tidy_cross_cluster_revoked_certs: false,
- tidy_expired_issuers: false,
- tidy_move_legacy_ca_bundle: false,
- tidy_revocation_queue: false,
- tidy_revoked_cert_issuer_associations: false,
- tidy_revoked_certs: false,
- },
- wrap_info: null,
- warnings: null,
- auth: null,
- };
- });
-
- this.store.findRecord('pki/tidy', this.backend);
- });
-});
diff --git a/ui/tests/unit/adapters/secret-engine-test.js b/ui/tests/unit/adapters/secret-engine-test.js
deleted file mode 100644
index 38288d584..000000000
--- a/ui/tests/unit/adapters/secret-engine-test.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import apiStub from 'vault/tests/helpers/noop-all-api-requests';
-
-module('Unit | Adapter | secret engine', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.server = apiStub();
- });
-
- hooks.afterEach(function () {
- this.server.shutdown();
- });
-
- const storeStub = {
- serializerFor() {
- return {
- serializeIntoHash() {},
- };
- },
- };
- const type = {
- modelName: 'secret-engine',
- };
-
- const cases = [
- {
- description: 'Empty query',
- adapterMethod: 'query',
- args: [storeStub, type, {}],
- url: '/v1/sys/internal/ui/mounts',
- method: 'GET',
- },
- {
- description: 'Query with a path',
- adapterMethod: 'query',
- args: [storeStub, type, { path: 'foo' }],
- url: '/v1/sys/internal/ui/mounts/foo',
- method: 'GET',
- },
-
- {
- description: 'Query with nested path',
- adapterMethod: 'query',
- args: [storeStub, type, { path: 'foo/bar/baz' }],
- url: '/v1/sys/internal/ui/mounts/foo/bar/baz',
- method: 'GET',
- },
- ];
- cases.forEach((testCase) => {
- test(`secret-engine: ${testCase.description}`, function (assert) {
- assert.expect(2);
- const adapter = this.owner.lookup('adapter:secret-engine');
- adapter[testCase.adapterMethod](...testCase.args);
- const { url, method } = this.server.handledRequests[0];
- assert.strictEqual(
- url,
- testCase.url,
- `${testCase.adapterMethod} calls the correct url: ${testCase.url}`
- );
- assert.strictEqual(
- method,
- testCase.method,
- `${testCase.adapterMethod} uses the correct http verb: ${testCase.method}`
- );
- });
- });
-});
diff --git a/ui/tests/unit/adapters/secret-test.js b/ui/tests/unit/adapters/secret-test.js
deleted file mode 100644
index fa6ec0c39..000000000
--- a/ui/tests/unit/adapters/secret-test.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { resolve } from 'rsvp';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Adapter | secret', function (hooks) {
- setupTest(hooks);
-
- test('secret api urls', function (assert) {
- let url, method, options;
- const adapter = this.owner.factoryFor('adapter:secret').create({
- ajax: (...args) => {
- [url, method, options] = args;
- return resolve({});
- },
- });
-
- adapter.query({}, 'secret', { id: '', backend: 'secret' });
- assert.strictEqual(url, '/v1/secret/', 'query generic url OK');
- assert.strictEqual(method, 'GET', 'query generic method OK');
- assert.deepEqual(options, { data: { list: true } }, 'query generic url OK');
-
- adapter.queryRecord({}, 'secret', { id: 'foo', backend: 'secret' });
- assert.strictEqual(url, '/v1/secret/foo', 'queryRecord generic url OK');
- assert.strictEqual(method, 'GET', 'queryRecord generic method OK');
- });
-});
diff --git a/ui/tests/unit/adapters/secret-v2-test.js b/ui/tests/unit/adapters/secret-v2-test.js
deleted file mode 100644
index 7cbef24d2..000000000
--- a/ui/tests/unit/adapters/secret-v2-test.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import apiStub from 'vault/tests/helpers/noop-all-api-requests';
-
-module('Unit | Adapter | secret-v2', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.server = apiStub();
- });
-
- hooks.afterEach(function () {
- this.server.shutdown();
- });
-
- [
- ['query', null, {}, { id: '', backend: 'secret' }, 'GET', '/v1/secret/metadata/?list=true'],
- ['queryRecord', null, {}, { id: 'foo', backend: 'secret' }, 'GET', '/v1/secret/metadata/foo'],
- [
- 'updateRecord',
- {
- serializerFor() {
- return {
- serializeIntoHash() {},
- };
- },
- },
- {},
- {
- id: 'foo',
- belongsTo() {
- return 'secret';
- },
- },
- 'PUT',
- '/v1/secret/metadata/foo',
- ],
- [
- 'deleteRecord',
- {
- serializerFor() {
- return {
- serializeIntoHash() {},
- };
- },
- },
- {},
- {
- id: 'foo',
- belongsTo() {
- return 'secret';
- },
- },
- 'DELETE',
- '/v1/secret/metadata/foo',
- ],
- ].forEach(([adapterMethod, store, type, queryOrSnapshot, expectedHttpVerb, expectedURL]) => {
- test(`secret-v2: ${adapterMethod}`, function (assert) {
- const adapter = this.owner.lookup('adapter:secret-v2');
- adapter[adapterMethod](store, type, queryOrSnapshot);
- const { url, method } = this.server.handledRequests[0];
- assert.strictEqual(url, expectedURL, `${adapterMethod} calls the correct url: ${expectedURL}`);
- assert.strictEqual(
- method,
- expectedHttpVerb,
- `${adapterMethod} uses the correct http verb: ${expectedHttpVerb}`
- );
- });
- });
-});
diff --git a/ui/tests/unit/adapters/secret-v2-version-test.js b/ui/tests/unit/adapters/secret-v2-version-test.js
deleted file mode 100644
index 1a7ab3d89..000000000
--- a/ui/tests/unit/adapters/secret-v2-version-test.js
+++ /dev/null
@@ -1,103 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-/* eslint qunit/no-conditional-assertions: "warn" */
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import apiStub from 'vault/tests/helpers/noop-all-api-requests';
-
-module('Unit | Adapter | secret-v2-version', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.server = apiStub();
- });
-
- hooks.afterEach(function () {
- this.server.shutdown();
- });
-
- const fakeStore = {
- peekRecord() {
- return {
- rollbackAttributes() {},
- reload() {},
- };
- },
- };
- [
- [
- 'findRecord with version',
- 'findRecord',
- [null, {}, JSON.stringify(['secret', 'foo', '2']), {}],
- 'GET',
- '/v1/secret/data/foo?version=2',
- null,
- 2,
- ],
- [
- 'v2DeleteOperation with delete',
- 'v2DeleteOperation',
- [fakeStore, JSON.stringify(['secret', 'foo', '2']), 'delete'],
- 'POST',
- '/v1/secret/delete/foo',
- { versions: ['2'] },
- 3,
- ],
- [
- 'v2DeleteOperation with destroy',
- 'v2DeleteOperation',
- [fakeStore, JSON.stringify(['secret', 'foo', '2']), 'destroy'],
- 'POST',
- '/v1/secret/destroy/foo',
- { versions: ['2'] },
- 3,
- ],
- [
- 'v2DeleteOperation with destroy',
- 'v2DeleteOperation',
- [fakeStore, JSON.stringify(['secret', 'foo', '2']), 'undelete'],
- 'POST',
- '/v1/secret/undelete/foo',
- { versions: ['2'] },
- 3,
- ],
- [
- 'updateRecord makes calls to correct url',
- 'updateRecord',
- [
- {
- serializerFor() {
- return { serializeIntoHash() {} };
- },
- },
- {},
- { id: JSON.stringify(['secret', 'foo', '2']) },
- ],
- 'PUT',
- '/v1/secret/data/foo',
- null,
- 2,
- ],
- ].forEach(
- ([testName, adapterMethod, args, expectedHttpVerb, expectedURL, exptectedRequestBody, assertCount]) => {
- test(`${testName}`, function (assert) {
- assert.expect(assertCount);
- const adapter = this.owner.lookup('adapter:secret-v2-version');
- adapter[adapterMethod](...args);
- const { url, method, requestBody } = this.server.handledRequests[0];
- assert.strictEqual(url, expectedURL, `${adapterMethod} calls the correct url: ${expectedURL}`);
- assert.strictEqual(
- method,
- expectedHttpVerb,
- `${adapterMethod} uses the correct http verb: ${expectedHttpVerb}`
- );
- if (exptectedRequestBody) {
- assert.deepEqual(JSON.parse(requestBody), exptectedRequestBody);
- }
- });
- }
- );
-});
diff --git a/ui/tests/unit/adapters/tools-test.js b/ui/tests/unit/adapters/tools-test.js
deleted file mode 100644
index 9a7281026..000000000
--- a/ui/tests/unit/adapters/tools-test.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { resolve } from 'rsvp';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Adapter | tools', function (hooks) {
- setupTest(hooks);
-
- test('wrapping api urls', function (assert) {
- let url, method, options;
- const adapter = this.owner.factoryFor('adapter:tools').create({
- ajax: (...args) => {
- [url, method, options] = args;
- return resolve();
- },
- });
-
- let clientToken;
- let data = { foo: 'bar' };
- adapter.toolAction('wrap', data, { wrapTTL: '30m' });
- assert.strictEqual(url, '/v1/sys/wrapping/wrap', 'wrapping:wrap url OK');
- assert.strictEqual(method, 'POST', 'wrapping:wrap method OK');
- assert.deepEqual({ data: data, wrapTTL: '30m', clientToken }, options, 'wrapping:wrap options OK');
-
- data = { token: 'token' };
- adapter.toolAction('lookup', data);
- assert.strictEqual(url, '/v1/sys/wrapping/lookup', 'wrapping:lookup url OK');
- assert.strictEqual(method, 'POST', 'wrapping:lookup method OK');
- assert.deepEqual({ data, clientToken }, options, 'wrapping:lookup options OK');
-
- adapter.toolAction('unwrap', data);
- assert.strictEqual(url, '/v1/sys/wrapping/unwrap', 'wrapping:unwrap url OK');
- assert.strictEqual(method, 'POST', 'wrapping:unwrap method OK');
- assert.deepEqual({ data, clientToken }, options, 'wrapping:unwrap options OK');
-
- adapter.toolAction('rewrap', data);
- assert.strictEqual(url, '/v1/sys/wrapping/rewrap', 'wrapping:rewrap url OK');
- assert.strictEqual(method, 'POST', 'wrapping:rewrap method OK');
- assert.deepEqual({ data, clientToken }, options, 'wrapping:rewrap options OK');
- });
-
- test('tools api urls', function (assert) {
- let url, method;
- const adapter = this.owner.factoryFor('adapter:tools').create({
- ajax: (...args) => {
- [url, method] = args;
- return resolve();
- },
- });
-
- adapter.toolAction('hash', { input: 'someBase64' });
- assert.strictEqual(url, '/v1/sys/tools/hash', 'sys tools hash: url OK');
- assert.strictEqual(method, 'POST', 'sys tools hash: method OK');
-
- adapter.toolAction('random', { bytes: '32' });
- assert.strictEqual(url, '/v1/sys/tools/random', 'sys tools random: url OK');
- assert.strictEqual(method, 'POST', 'sys tools random: method OK');
- });
-});
diff --git a/ui/tests/unit/adapters/transit-key-test.js b/ui/tests/unit/adapters/transit-key-test.js
deleted file mode 100644
index 719b4993e..000000000
--- a/ui/tests/unit/adapters/transit-key-test.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { resolve } from 'rsvp';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Adapter | transit key', function (hooks) {
- setupTest(hooks);
-
- test('transit api urls', function (assert) {
- let url, method, options;
- const adapter = this.owner.factoryFor('adapter:transit-key').create({
- ajax: (...args) => {
- [url, method, options] = args;
- return resolve({});
- },
- });
-
- adapter.query({}, 'transit-key', { id: '', backend: 'transit' });
- assert.strictEqual(url, '/v1/transit/keys/', 'query list url OK');
- assert.strictEqual(method, 'GET', 'query list method OK');
- assert.deepEqual(options, { data: { list: true } }, 'query generic url OK');
-
- adapter.queryRecord({}, 'transit-key', { id: 'foo', backend: 'transit' });
- assert.strictEqual(url, '/v1/transit/keys/foo', 'queryRecord generic url OK');
- assert.strictEqual(method, 'GET', 'queryRecord generic method OK');
-
- adapter.keyAction('rotate', { backend: 'transit', id: 'foo', payload: {} });
- assert.strictEqual(url, '/v1/transit/keys/foo/rotate', 'keyAction:rotate url OK');
-
- adapter.keyAction('encrypt', { backend: 'transit', id: 'foo', payload: {} });
- assert.strictEqual(url, '/v1/transit/encrypt/foo', 'keyAction:encrypt url OK');
-
- adapter.keyAction('datakey', { backend: 'transit', id: 'foo', payload: { param: 'plaintext' } });
- assert.strictEqual(url, '/v1/transit/datakey/plaintext/foo', 'keyAction:datakey url OK');
-
- adapter.keyAction('export', { backend: 'transit', id: 'foo', payload: { param: ['hmac'] } });
- assert.strictEqual(url, '/v1/transit/export/hmac-key/foo', 'transitAction:export, no version url OK');
-
- adapter.keyAction('export', { backend: 'transit', id: 'foo', payload: { param: ['hmac', 10] } });
- assert.strictEqual(
- url,
- '/v1/transit/export/hmac-key/foo/10',
- 'transitAction:export, with version url OK'
- );
- });
-});
diff --git a/ui/tests/unit/components/auth-form-test.js b/ui/tests/unit/components/auth-form-test.js
deleted file mode 100644
index 81113101a..000000000
--- a/ui/tests/unit/components/auth-form-test.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { settled } from '@ember/test-helpers';
-
-module('Unit | Component | auth-form', function (hooks) {
- setupTest(hooks);
-
- test('it should use token for oidc and jwt auth method types when processing form submit', async function (assert) {
- assert.expect(4);
-
- const component = this.owner.lookup('component:auth-form');
- component.reopen({
- methods: [], // eslint-disable-line
- // eslint-disable-next-line
- authenticate: {
- unlinked() {
- return {
- perform(type, data) {
- assert.deepEqual(
- type,
- 'token',
- `Token type correctly passed to authenticate method for ${component.providerName}`
- );
- assert.deepEqual(
- data,
- { token: component.token },
- `Token passed to authenticate method for ${component.providerName}`
- );
- },
- };
- },
- },
- });
-
- const event = new Event('submit');
-
- for (const type of ['oidc', 'jwt']) {
- component.set('selectedAuth', type);
- await settled();
- await component.actions.doSubmit.apply(component, [undefined, event, 'foo-bar']);
- }
- });
-});
diff --git a/ui/tests/unit/components/auth-jwt-test.js b/ui/tests/unit/components/auth-jwt-test.js
deleted file mode 100644
index dd414653c..000000000
--- a/ui/tests/unit/components/auth-jwt-test.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import EmberObject from '@ember/object';
-import Evented from '@ember/object/evented';
-import sinon from 'sinon';
-import { _cancelTimers as cancelTimers } from '@ember/runloop';
-
-const mockWindow = EmberObject.extend(Evented, {
- origin: 'http://localhost:4200',
- close: () => {},
-});
-
-module('Unit | Component | auth-jwt', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.component = this.owner.lookup('component:auth-jwt');
- this.component.set('window', mockWindow.create());
- this.errorSpy = sinon.spy(this.component, 'handleOIDCError');
- });
-
- test('it should ignore messages from cross origin windows while waiting for oidc callback', async function (assert) {
- assert.expect(2);
- this.component.prepareForOIDC.perform(mockWindow.create());
- this.component.window.trigger('message', { origin: 'http://anotherdomain.com', isTrusted: true });
-
- assert.ok(this.errorSpy.notCalled, 'Error handler not triggered while waiting for oidc callback message');
- assert.strictEqual(this.component.exchangeOIDC.performCount, 0, 'exchangeOIDC method not fired');
- cancelTimers();
- });
-
- test('it should ignore untrusted messages while waiting for oidc callback', async function (assert) {
- assert.expect(2);
- this.component.prepareForOIDC.perform(mockWindow.create());
- this.component.window.trigger('message', { origin: 'http://localhost:4200', isTrusted: false });
- assert.ok(this.errorSpy.notCalled, 'Error handler not triggered while waiting for oidc callback message');
- assert.strictEqual(this.component.exchangeOIDC.performCount, 0, 'exchangeOIDC method not fired');
- cancelTimers();
- });
-
- // test case for https://github.com/hashicorp/vault/issues/12436
- test('it should ignore messages sent from outside the app while waiting for oidc callback', async function (assert) {
- assert.expect(2);
- this.component.prepareForOIDC.perform(mockWindow.create());
- const message = {
- origin: 'http://localhost:4200',
- isTrusted: true,
- data: {
- namespace: 'foobar',
- path: '/foo/bar',
- state: 'authorized',
- code: 204,
- },
- };
-
- this.component.window.trigger('message', message);
- message.data.source = 'foo-bar';
- this.component.window.trigger('message', message);
- message.data.source = 'oidc-callback';
- this.component.window.trigger('message', message);
-
- assert.ok(this.errorSpy.notCalled, 'Error handler not triggered while waiting for oidc callback message');
- assert.strictEqual(
- this.component.exchangeOIDC.performCount,
- 1,
- 'exchangeOIDC method fires when oidc callback message is received'
- );
- cancelTimers();
- });
-});
diff --git a/ui/tests/unit/components/identity/edit-form-test.js b/ui/tests/unit/components/identity/edit-form-test.js
deleted file mode 100644
index 58457ccfd..000000000
--- a/ui/tests/unit/components/identity/edit-form-test.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import EmberObject from '@ember/object';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import sinon from 'sinon';
-
-module('Unit | Component | identity/edit-form', function (hooks) {
- setupTest(hooks);
-
- const testCases = [
- {
- identityType: 'entity',
- mode: 'create',
- expected: 'vault.cluster.access.identity',
- },
- {
- identityType: 'entity',
- mode: 'edit',
- expected: 'vault.cluster.access.identity.show',
- },
- {
- identityType: 'entity-merge',
- mode: 'merge',
- expected: 'vault.cluster.access.identity',
- },
- {
- identityType: 'entity-alias',
- mode: 'create',
- expected: 'vault.cluster.access.identity.aliases',
- },
- {
- identityType: 'entity-alias',
- mode: 'edit',
- expected: 'vault.cluster.access.identity.aliases.show',
- },
- {
- identityType: 'group',
- mode: 'create',
- expected: 'vault.cluster.access.identity',
- },
- {
- identityType: 'group',
- mode: 'edit',
- expected: 'vault.cluster.access.identity.show',
- },
- {
- identityType: 'group-alias',
- mode: 'create',
- expected: 'vault.cluster.access.identity.aliases',
- },
- {
- identityType: 'group-alias',
- mode: 'edit',
- expected: 'vault.cluster.access.identity.aliases.show',
- },
- ];
- testCases.forEach(function (testCase) {
- const model = EmberObject.create({
- identityType: testCase.identityType,
- rollbackAttributes: sinon.spy(),
- });
- test(`it computes cancelLink properly: ${testCase.identityType} ${testCase.mode}`, function (assert) {
- const component = this.owner.lookup('component:identity/edit-form');
-
- component.set('mode', testCase.mode);
- component.set('model', model);
- assert.strictEqual(component.get('cancelLink'), testCase.expected, 'cancel link is correct');
- });
- });
-});
diff --git a/ui/tests/unit/decorators/model-expanded-attributes-test.js b/ui/tests/unit/decorators/model-expanded-attributes-test.js
deleted file mode 100644
index 26c077fe9..000000000
--- a/ui/tests/unit/decorators/model-expanded-attributes-test.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import sinon from 'sinon';
-import Model, { attr } from '@ember-data/model';
-import { withExpandedAttributes } from 'vault/decorators/model-expanded-attributes';
-
-// create class using decorator
-const createClass = () => {
- @withExpandedAttributes()
- class Foo extends Model {
- @attr('string', {
- label: 'Foo',
- subText: 'A form field',
- })
- foo;
- @attr('boolean', {
- label: 'Bar',
- subText: 'Maybe a checkbox',
- })
- bar;
- @attr('number', {
- label: 'Baz',
- subText: 'A number field',
- })
- baz;
-
- get fieldGroups() {
- return [{ default: ['baz'] }, { 'Other options': ['foo', 'bar'] }];
- }
- }
- return new Foo();
-};
-
-module('Unit | Decorators | model-expanded-attributes', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.spy = sinon.spy(console, 'error');
- this.fooField = {
- name: 'foo',
- options: { label: 'Foo', subText: 'A form field' },
- type: 'string',
- };
- this.barField = {
- name: 'bar',
- options: { label: 'Bar', subText: 'Maybe a checkbox' },
- type: 'boolean',
- };
- this.bazField = {
- name: 'baz',
- options: { label: 'Baz', subText: 'A number field' },
- type: 'number',
- };
- });
- hooks.afterEach(function () {
- this.spy.restore();
- });
-
- test('it should warn when applying decorator to class that does not extend Model', function (assert) {
- @withExpandedAttributes()
- class Foo {} // eslint-disable-line
- const message =
- 'withExpandedAttributes decorator must be used on instance of ember-data Model class. Decorator not applied to returned class';
- assert.ok(this.spy.calledWith(message), 'Error is printed to console');
- });
-
- test('it adds allByKey value to model', function (assert) {
- assert.expect(1);
- const model = createClass();
- assert.deepEqual(
- { foo: this.fooField, bar: this.barField, baz: this.bazField },
- model.allByKey,
- 'allByKey set on Model class'
- );
- });
-
- test('_expandGroups helper works correctly', function (assert) {
- const model = createClass();
- const result = model._expandGroups(model.fieldGroups);
- assert.deepEqual(result, [
- { default: [this.bazField] },
- { 'Other options': [this.fooField, this.barField] },
- ]);
- });
-
- test('_expandGroups throws assertion when incorrect inputs', function (assert) {
- assert.expect(1);
- const model = createClass();
- try {
- model._expandGroups({ foo: ['bar'] });
- } catch (e) {
- assert.strictEqual(e.message, '_expandGroups expects an array of objects');
- }
- });
-});
diff --git a/ui/tests/unit/decorators/model-form-fields-test.js b/ui/tests/unit/decorators/model-form-fields-test.js
deleted file mode 100644
index 330736432..000000000
--- a/ui/tests/unit/decorators/model-form-fields-test.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { withFormFields } from 'vault/decorators/model-form-fields';
-import sinon from 'sinon';
-import Model, { attr } from '@ember-data/model';
-
-// create class using decorator
-const createClass = (propertyNames, groups) => {
- @withFormFields(propertyNames, groups)
- class Foo extends Model {
- @attr('string', {
- label: 'Foo',
- subText: 'A form field',
- })
- foo;
- @attr('boolean', {
- label: 'Bar',
- subText: 'Maybe a checkbox',
- })
- bar;
- @attr('number', {
- label: 'Baz',
- subText: 'A number field',
- })
- baz;
- }
- return new Foo();
-};
-
-module('Unit | Decorators | ModelFormFields', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.spy = sinon.spy(console, 'error');
- this.fooField = {
- name: 'foo',
- options: { label: 'Foo', subText: 'A form field' },
- type: 'string',
- };
- this.barField = {
- name: 'bar',
- options: { label: 'Bar', subText: 'Maybe a checkbox' },
- type: 'boolean',
- };
- this.bazField = {
- name: 'baz',
- options: { label: 'Baz', subText: 'A number field' },
- type: 'number',
- };
- });
- hooks.afterEach(function () {
- this.spy.restore();
- });
-
- test('it should warn when applying decorator to class that does not extend Model', function (assert) {
- @withFormFields()
- class Foo {} // eslint-disable-line
- const message =
- 'withFormFields decorator must be used on instance of ember-data Model class. Decorator not applied to returned class';
- assert.ok(this.spy.calledWith(message), 'Error is printed to console');
- });
-
- test('it return allFields when arguments not provided', function (assert) {
- assert.expect(1);
- const model = createClass();
- assert.deepEqual(
- [this.fooField, this.barField, this.bazField],
- model.allFields,
- 'allFields set on Model class'
- );
- });
-
- test('it should set formFields prop on Model class', function (assert) {
- const model = createClass(['foo']);
- assert.deepEqual([this.fooField], model.formFields, 'formFields set on Model class');
- });
-
- test('it should set formFieldGroups on Model class', function (assert) {
- const groups = [{ default: ['foo'] }, { subgroup: ['bar'] }];
- const model = createClass(null, groups);
- const fieldGroups = [{ default: [this.fooField] }, { subgroup: [this.barField] }];
- assert.deepEqual(fieldGroups, model.formFieldGroups, 'formFieldGroups set on Model class');
- });
-});
diff --git a/ui/tests/unit/decorators/model-validations-test.js b/ui/tests/unit/decorators/model-validations-test.js
deleted file mode 100644
index 94ca73b1b..000000000
--- a/ui/tests/unit/decorators/model-validations-test.js
+++ /dev/null
@@ -1,136 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { withModelValidations } from 'vault/decorators/model-validations';
-import validators from 'vault/utils/validators';
-import sinon from 'sinon';
-import Model from '@ember-data/model';
-
-// create class using decorator
-const createClass = (validations) => {
- @withModelValidations(validations)
- class Foo extends Model {}
- const foo = Foo.extend({
- modelName: 'bar',
- foo: null,
- integer: null,
- });
- return new foo();
-};
-
-module('Unit | Decorators | ModelValidations', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.spy = sinon.spy(console, 'error');
- });
- hooks.afterEach(function () {
- this.spy.restore();
- });
-
- test('it should throw error when validations object is not provided', function (assert) {
- assert.expect(1);
-
- try {
- createClass();
- } catch (e) {
- assert.strictEqual(e.message, 'Validations object must be provided to constructor for setup');
- }
- });
-
- test('it should log error to console when validations are not passed as array', function (assert) {
- const validations = {
- foo: { type: 'presence', message: 'Foo is required' },
- };
- const fooClass = createClass(validations);
- fooClass.validate();
- const message = 'Must provide validations as an array for property "foo" on bar model';
- assert.ok(this.spy.calledWith(message));
- });
-
- test('it should log error for incorrect validator type', function (assert) {
- const validations = {
- foo: [{ type: 'bar', message: 'Foo is bar' }],
- };
- const fooClass = createClass(validations);
- fooClass.validate();
- const message = `Validator type: "bar" not found. Available validators: ${Object.keys(validators).join(
- ', '
- )}`;
- assert.ok(this.spy.calledWith(message));
- });
-
- test('it should validate', function (assert) {
- const message = 'This field is required';
- const validations = {
- foo: [{ type: 'presence', message }],
- };
- const fooClass = createClass(validations);
- const v1 = fooClass.validate();
- assert.false(v1.isValid, 'isValid state is correct when errors exist');
- assert.deepEqual(
- v1.state,
- { foo: { isValid: false, errors: [message], warnings: [] } },
- 'Correct state returned when property is invalid'
- );
-
- fooClass.foo = true;
- const v2 = fooClass.validate();
- assert.true(v2.isValid, 'isValid state is correct when no errors exist');
- assert.deepEqual(
- v2.state,
- { foo: { isValid: true, errors: [], warnings: [] } },
- 'Correct state returned when property is valid'
- );
- });
-
- test('invalid form message has correct error count', function (assert) {
- const message = 'This field is required';
- const messageII = 'This field must be a number';
- const validations = {
- foo: [{ type: 'presence', message }],
- integer: [{ type: 'number', messageII }],
- };
- const fooClass = createClass(validations);
- const v1 = fooClass.validate();
- assert.strictEqual(
- v1.invalidFormMessage,
- 'There are 2 errors with this form.',
- 'error message says form as 2 errors'
- );
-
- fooClass.integer = 9;
- const v2 = fooClass.validate();
- assert.strictEqual(
- v2.invalidFormMessage,
- 'There is an error with this form.',
- 'error message says form has an error'
- );
-
- fooClass.foo = true;
- const v3 = fooClass.validate();
- assert.strictEqual(v3.invalidFormMessage, null, 'invalidFormMessage is null when form is valid');
- });
-
- test('it should validate warnings', function (assert) {
- const message = 'Value contains whitespace.';
- const validations = {
- foo: [
- {
- type: 'containsWhiteSpace',
- message,
- level: 'warn',
- },
- ],
- };
- const fooClass = createClass(validations);
- fooClass.foo = 'foo bar';
- const { state, isValid } = fooClass.validate();
- assert.true(isValid, 'Model is considered valid when there are only warnings');
- assert.strictEqual(state.foo.warnings.join(' '), message, 'Warnings are returned');
- });
-});
diff --git a/ui/tests/unit/helpers/await-test.js b/ui/tests/unit/helpers/await-test.js
deleted file mode 100644
index 5cfda0cf7..000000000
--- a/ui/tests/unit/helpers/await-test.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import AwaitHelper from 'vault/helpers/await';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { waitUntil } from '@ember/test-helpers';
-import { Promise } from 'rsvp';
-import { later } from '@ember/runloop';
-import sinon from 'sinon';
-
-// recompute triggers a rerender which isn't going to work for unit tests
-// override method to trigger compute instead
-class AwaitHelperForTesting extends AwaitHelper {
- compute([promise]) {
- // cache original promise arg to simulate rerender when calling recompute
- this._promiseArg = promise;
- return super.compute(...arguments);
- }
- recompute() {
- this.compute([this._promiseArg]);
- }
-}
-
-module('Unit | Helpers | await', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.helper = new AwaitHelperForTesting();
- this.spy = sinon.spy(this.helper, 'compute');
- });
-
- test('it returns value when input is not a promise', async function (assert) {
- this.helper.compute(['foo']);
- assert.strictEqual(this.spy.returnValues[0], 'foo', 'Input value returned when not promise');
- });
-
- test('it returns null default value and then resolved value', async function (assert) {
- const promise = new Promise((resolve) => resolve('foo'));
- this.helper.compute([promise]);
- await waitUntil(() => this.spy.returnValues[1]);
- assert.strictEqual(this.spy.returnValues[0], null, 'Default value returned while promise resolves');
- assert.strictEqual(this.spy.returnValues[1], 'foo', 'Resolved value is returned');
- });
-
- test('it returns rejected value', async function (assert) {
- const promise = new Promise((resolve, reject) => reject('bar'));
- this.helper.compute([promise]);
- await waitUntil(() => this.spy.returnValues[1]);
- assert.strictEqual(this.spy.returnValues[1], 'bar', 'Rejected value is returned');
- });
-
- test('it returns then value', async function (assert) {
- const promise = new Promise((resolve) => resolve('foo')).then(() => 'new resolve value');
- this.helper.compute([promise]);
- await waitUntil(() => this.spy.returnValues[1]);
- assert.strictEqual(this.spy.returnValues[1], 'new resolve value', 'Value from then is returned');
- });
-
- test('it returns catch value', async function (assert) {
- const promise = new Promise((resolve, reject) => reject('bar')).catch(() => 'new reject value');
- this.helper.compute([promise]);
- await waitUntil(() => this.spy.returnValues[1]);
- assert.strictEqual(this.spy.returnValues[1], 'new reject value', 'Value from catch is returned');
- });
-
- test('it always returns value from latest promise', async function (assert) {
- const promise1 = new Promise((resolve) => later(() => resolve('foo'), 500));
- const promise2 = new Promise((resolve) => resolve('bar'));
- this.helper.compute([promise1]);
- this.helper.compute([promise2]);
- // allow first promise time to resolve
- await waitUntil(() => later(() => true, 500));
- assert.strictEqual(this.spy.returnValues[2], 'bar', 'Latest promise value is returned');
- });
-});
diff --git a/ui/tests/unit/helpers/filter-wildcard-test.js b/ui/tests/unit/helpers/filter-wildcard-test.js
deleted file mode 100644
index 216587822..000000000
--- a/ui/tests/unit/helpers/filter-wildcard-test.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { filterWildcard } from 'vault/helpers/filter-wildcard';
-import { module, test } from 'qunit';
-
-module('Unit | Helpers | filter-wildcard', function () {
- test('it returns a count if array contains a wildcard', function (assert) {
- const string = { id: 'foo*' };
- const array = ['foobar', 'foozar', 'boo', 'oof'];
- const result = filterWildcard([string, array]);
- assert.strictEqual(result, 2);
- });
-
- test('it returns zero if no wildcard is string', function (assert) {
- const string = { id: 'foo#' };
- const array = ['foobar', 'foozar', 'boo', 'oof'];
- const result = filterWildcard([string, array]);
- assert.strictEqual(result, 0);
- });
-
- test('it escapes function and does not error if no id is in string', function (assert) {
- const string = '*bar*';
- const array = ['foobar', 'foozar', 'boobarboo', 'oof'];
- const result = filterWildcard([string, array]);
- assert.strictEqual(result, 2);
- });
-});
diff --git a/ui/tests/unit/helpers/is-wildcard-string-test.js b/ui/tests/unit/helpers/is-wildcard-string-test.js
deleted file mode 100644
index b6012cd2e..000000000
--- a/ui/tests/unit/helpers/is-wildcard-string-test.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { isWildcardString } from 'vault/helpers/is-wildcard-string';
-import { module, test } from 'qunit';
-
-module('Unit | Helpers | is-wildcard-string', function () {
- test('it returns true if regular string with wildcard', function (assert) {
- const string = 'foom#*eep';
- const result = isWildcardString([string]);
- assert.true(result);
- });
-
- test('it returns false if no wildcard', function (assert) {
- const string = 'foo.bar';
- const result = isWildcardString([string]);
- assert.false(result);
- });
-
- test('it returns true if string with id as in searchSelect selected has wildcard', function (assert) {
- const string = { id: 'foo.bar*baz' };
- const result = isWildcardString([string]);
- assert.true(result);
- });
-
- test('it returns true if string object has name and no id', function (assert) {
- const string = { name: 'foo.bar*baz' };
- const result = isWildcardString([string]);
- assert.true(result);
- });
-
- test('it returns true if string object has name and id with at least one wildcard', function (assert) {
- const string = { id: '7*', name: 'seven' };
- const result = isWildcardString([string]);
- assert.true(result);
- });
-
- test('it returns true if string object has name and id with wildcard in name not id', function (assert) {
- const string = { id: '7', name: 'sev*n' };
- const result = isWildcardString([string]);
- assert.true(result);
- });
-});
diff --git a/ui/tests/unit/lib/attach-capabilities-test.js b/ui/tests/unit/lib/attach-capabilities-test.js
deleted file mode 100644
index 3a5568d1f..000000000
--- a/ui/tests/unit/lib/attach-capabilities-test.js
+++ /dev/null
@@ -1,168 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import Model from '@ember-data/model';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import attachCapabilities from 'vault/lib/attach-capabilities';
-import apiPath from 'vault/utils/api-path';
-
-const MODEL_TYPE = 'test-form-model';
-
-const makeModelClass = () => {
- return Model.extend();
-};
-
-module('Unit | lib | attach capabilities', function (hooks) {
- setupTest(hooks);
-
- test('it attaches passed capabilities', function (assert) {
- let mc = makeModelClass();
- mc = attachCapabilities(mc, {
- updatePath: apiPath`update/{'id'}`,
- deletePath: apiPath`delete/{'id'}`,
- });
- let relationship = mc.relationshipsByName.get('updatePath');
-
- assert.strictEqual(relationship.key, 'updatePath', 'has updatePath relationship');
- assert.strictEqual(relationship.kind, 'belongsTo', 'kind of relationship is belongsTo');
- assert.strictEqual(relationship.type, 'capabilities', 'updatePath is a related capabilities model');
-
- relationship = mc.relationshipsByName.get('deletePath');
- assert.strictEqual(relationship.key, 'deletePath', 'has deletePath relationship');
- assert.strictEqual(relationship.kind, 'belongsTo', 'kind of relationship is belongsTo');
- assert.strictEqual(relationship.type, 'capabilities', 'deletePath is a related capabilities model');
- });
-
- test('it adds a static method to the model class', function (assert) {
- let mc = makeModelClass();
- mc = attachCapabilities(mc, {
- updatePath: apiPath`update/{'id'}`,
- deletePath: apiPath`delete/{'id'}`,
- });
- const relatedCapabilities = !!mc.relatedCapabilities && typeof mc.relatedCapabilities === 'function';
- assert.true(relatedCapabilities, 'model class now has a relatedCapabilities static function');
- });
-
- test('calling static method with single response JSON-API document adds expected relationships', function (assert) {
- let mc = makeModelClass();
- mc = attachCapabilities(mc, {
- updatePath: apiPath`update/${'id'}`,
- deletePath: apiPath`delete/${'id'}`,
- });
- const jsonAPIDocSingle = {
- data: {
- id: 'test',
- type: MODEL_TYPE,
- attributes: {},
- relationships: {},
- },
- included: [],
- };
-
- const expected = {
- data: {
- id: 'test',
- type: MODEL_TYPE,
- attributes: {},
- relationships: {
- updatePath: {
- data: {
- type: 'capabilities',
- id: 'update/test',
- },
- },
- deletePath: {
- data: {
- type: 'capabilities',
- id: 'delete/test',
- },
- },
- },
- },
- included: [],
- };
-
- mc.relatedCapabilities(jsonAPIDocSingle);
-
- assert.strictEqual(
- Object.keys(jsonAPIDocSingle.data.relationships).length,
- 2,
- 'document now has 2 relationships'
- );
- assert.deepEqual(jsonAPIDocSingle, expected, 'has the exected new document structure');
- });
-
- test('calling static method with an arrary response JSON-API document adds expected relationships', function (assert) {
- let mc = makeModelClass();
- mc = attachCapabilities(mc, {
- updatePath: apiPath`update/${'id'}`,
- deletePath: apiPath`delete/${'id'}`,
- });
- const jsonAPIDocSingle = {
- data: [
- {
- id: 'test',
- type: MODEL_TYPE,
- attributes: {},
- relationships: {},
- },
- {
- id: 'foo',
- type: MODEL_TYPE,
- attributes: {},
- relationships: {},
- },
- ],
- included: [],
- };
-
- const expected = {
- data: [
- {
- id: 'test',
- type: MODEL_TYPE,
- attributes: {},
- relationships: {
- updatePath: {
- data: {
- type: 'capabilities',
- id: 'update/test',
- },
- },
- deletePath: {
- data: {
- type: 'capabilities',
- id: 'delete/test',
- },
- },
- },
- },
- {
- id: 'foo',
- type: MODEL_TYPE,
- attributes: {},
- relationships: {
- updatePath: {
- data: {
- type: 'capabilities',
- id: 'update/foo',
- },
- },
- deletePath: {
- data: {
- type: 'capabilities',
- id: 'delete/foo',
- },
- },
- },
- },
- ],
- included: [],
- };
- mc.relatedCapabilities(jsonAPIDocSingle);
- assert.deepEqual(jsonAPIDocSingle, expected, 'has the exected new document structure');
- });
-});
diff --git a/ui/tests/unit/lib/console-helpers-test.js b/ui/tests/unit/lib/console-helpers-test.js
deleted file mode 100644
index b94588514..000000000
--- a/ui/tests/unit/lib/console-helpers-test.js
+++ /dev/null
@@ -1,509 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import {
- parseCommand,
- logFromResponse,
- logFromError,
- formattedErrorFromInput,
- extractFlagsFromStrings,
- extractDataFromStrings,
-} from 'vault/lib/console-helpers';
-
-module('Unit | Lib | console helpers', function () {
- const testCommands = [
- {
- name: 'write with data',
- command: `vault write aws/config/root \
- access_key=AKIAJWVN5Z4FOFT7NLNA \
- secret_key=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 \
- region=us-east-1`,
- expected: {
- method: 'write',
- flagArray: [],
- path: 'aws/config/root',
- dataArray: [
- 'access_key=AKIAJWVN5Z4FOFT7NLNA',
- 'secret_key=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',
- 'region=us-east-1',
- ],
- },
- },
- {
- name: 'write with space in a value',
- command: `vault write \
- auth/ldap/config \
- url=ldap://ldap.example.com:3268 \
- binddn="CN=ServiceViewDev,OU=Service Accounts,DC=example,DC=com" \
- bindpass="xxxxxxxxxxxxxxxxxxxxxxxxxx" \
- userdn="DC=example,DC=com" \
- groupdn="DC=example,DC=com" \
- insecure_tls=true \
- starttls=false
- `,
- expected: {
- method: 'write',
- flagArray: [],
- path: 'auth/ldap/config',
- dataArray: [
- 'url=ldap://ldap.example.com:3268',
- 'binddn=CN=ServiceViewDev,OU=Service Accounts,DC=example,DC=com',
- 'bindpass=xxxxxxxxxxxxxxxxxxxxxxxxxx',
- 'userdn=DC=example,DC=com',
- 'groupdn=DC=example,DC=com',
- 'insecure_tls=true',
- 'starttls=false',
- ],
- },
- },
- {
- name: 'write with double quotes',
- command: `vault write \
- auth/token/create \
- policies="foo"
- `,
- expected: { method: 'write', flagArray: [], path: 'auth/token/create', dataArray: ['policies=foo'] },
- },
- {
- name: 'write with single quotes',
- command: `vault write \
- auth/token/create \
- policies='foo'
- `,
- expected: { method: 'write', flagArray: [], path: 'auth/token/create', dataArray: ['policies=foo'] },
- },
- {
- name: 'write with unmatched quotes',
- command: `vault write \
- auth/token/create \
- policies="'foo"
- `,
- expected: { method: 'write', flagArray: [], path: 'auth/token/create', dataArray: ["policies='foo"] },
- },
- {
- name: 'write with shell characters',
- /* eslint-disable no-useless-escape */
- command: `vault write database/roles/api-prod db_name=apiprod creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" default_ttl=1h max_ttl=24h
- `,
- expected: {
- method: 'write',
- flagArray: [],
- path: 'database/roles/api-prod',
- dataArray: [
- 'db_name=apiprod',
- `creation_statements=CREATE ROLE {{name}} WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO {{name}};`,
- 'default_ttl=1h',
- 'max_ttl=24h',
- ],
- },
- },
-
- {
- name: 'read with field',
- command: `vault read -field=access_key aws/creds/my-role`,
- expected: {
- method: 'read',
- flagArray: ['-field=access_key'],
- path: 'aws/creds/my-role',
- dataArray: [],
- },
- },
- ];
-
- testCommands.forEach(function (testCase) {
- test(`#parseCommand: ${testCase.name}`, function (assert) {
- const result = parseCommand(testCase.command);
- assert.deepEqual(result, testCase.expected);
- });
- });
-
- test('#parseCommand: invalid commands', function (assert) {
- assert.expect(1);
- const command = 'vault kv get foo';
- assert.throws(
- () => {
- parseCommand(command);
- },
- /invalid command/,
- 'throws on invalid command'
- );
- });
-
- const testExtractCases = [
- {
- method: 'read',
- name: 'data fields',
- dataInput: [
- 'access_key=AKIAJWVN5Z4FOFT7NLNA',
- 'secret_key=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',
- 'region=us-east-1',
- ],
- flagInput: [],
- expected: {
- data: {
- access_key: 'AKIAJWVN5Z4FOFT7NLNA',
- secret_key: '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',
- region: 'us-east-1',
- },
- flags: {},
- },
- },
- {
- method: 'read',
- name: 'repeated data and a flag',
- dataInput: ['allowed_domains=example.com', 'allowed_domains=foo.example.com'],
- flagInput: ['-wrap-ttl=2h'],
- expected: {
- data: {
- allowed_domains: ['example.com', 'foo.example.com'],
- },
- flags: {
- wrapTTL: '2h',
- },
- },
- },
- {
- method: 'read',
- name: 'triple data',
- dataInput: [
- 'allowed_domains=example.com',
- 'allowed_domains=foo.example.com',
- 'allowed_domains=dev.example.com',
- ],
- flagInput: [],
- expected: {
- data: {
- allowed_domains: ['example.com', 'foo.example.com', 'dev.example.com'],
- },
- flags: {},
- },
- },
- {
- method: 'read',
- name: 'data with more than one equals sign',
- dataInput: ['foo=bar=baz', 'foo=baz=bop', 'some=value=val'],
- flagInput: [],
- expected: {
- data: {
- foo: ['bar=baz', 'baz=bop'],
- some: 'value=val',
- },
- flags: {},
- },
- },
- {
- method: 'read',
- name: 'data with empty values',
- dataInput: [`foo=`, 'some=thing'],
- flagInput: [],
- expected: {
- data: {
- foo: '',
- some: 'thing',
- },
- flags: {},
- },
- },
- {
- method: 'write',
- name: 'write with force flag',
- dataInput: [],
- flagInput: ['-force'],
- expected: {
- data: {},
- flags: {
- force: true,
- },
- },
- },
- {
- method: 'write',
- name: 'write with force short flag',
- dataInput: [],
- flagInput: ['-f'],
- expected: {
- data: {},
- flags: {
- force: true,
- },
- },
- },
- {
- method: 'write',
- name: 'write with GNU style force flag',
- dataInput: [],
- flagInput: ['--force'],
- expected: {
- data: {},
- flags: {
- force: true,
- },
- },
- },
- ];
-
- testExtractCases.forEach(function (testCase) {
- test(`#extractDataFromStrings: ${testCase.name}`, function (assert) {
- const data = extractDataFromStrings(testCase.dataInput);
- assert.deepEqual(data, testCase.expected.data, 'has expected data');
- });
- test(`#extractFlagsFromStrings: ${testCase.name}`, function (assert) {
- const flags = extractFlagsFromStrings(testCase.flagInput, testCase.method);
- assert.deepEqual(flags, testCase.expected.flags, 'has expected flags');
- });
- });
-
- const testResponseCases = [
- {
- name: 'write response, no content',
- args: [null, 'foo/bar', 'write', {}],
- expectedData: {
- type: 'success',
- content: 'Success! Data written to: foo/bar',
- },
- },
- {
- name: 'delete response, no content',
- args: [null, 'foo/bar', 'delete', {}],
- expectedData: {
- type: 'success',
- content: 'Success! Data deleted (if it existed) at: foo/bar',
- },
- },
- {
- name: 'read, no data, auth, wrap_info',
- args: [{ foo: 'bar', one: 'two' }, 'foo/bar', 'read', {}],
- expectedData: {
- type: 'object',
- content: { foo: 'bar', one: 'two' },
- },
- },
- {
- name: 'read with -format=json flag, no data, auth, wrap_info',
- args: [{ foo: 'bar', one: 'two' }, 'foo/bar', 'read', { format: 'json' }],
- expectedData: {
- type: 'json',
- content: { foo: 'bar', one: 'two' },
- },
- },
- {
- name: 'read with -field flag, no data, auth, wrap_info',
- args: [{ foo: 'bar', one: 'two' }, 'foo/bar', 'read', { field: 'one' }],
- expectedData: {
- type: 'text',
- content: 'two',
- },
- },
- {
- name: 'write, with content',
- args: [{ data: { one: 'two' } }, 'foo/bar', 'write', {}],
- expectedData: {
- type: 'object',
- content: { one: 'two' },
- },
- },
- {
- name: 'with wrap-ttl flag',
- args: [{ wrap_info: { one: 'two' } }, 'foo/bar', 'read', { wrapTTL: '1h' }],
- expectedData: {
- type: 'object',
- content: { one: 'two' },
- },
- },
- {
- name: 'with -format=json flag and wrap-ttl flag',
- args: [{ foo: 'bar', wrap_info: { one: 'two' } }, 'foo/bar', 'read', { format: 'json', wrapTTL: '1h' }],
- expectedData: {
- type: 'json',
- content: { foo: 'bar', wrap_info: { one: 'two' } },
- },
- },
- {
- name: 'with -format=json and -field flags',
- args: [{ foo: 'bar', data: { one: 'two' } }, 'foo/bar', 'read', { format: 'json', field: 'one' }],
- expectedData: {
- type: 'json',
- content: 'two',
- },
- },
- {
- name: 'with -format=json and -field, and -wrap-ttl flags',
- args: [
- { foo: 'bar', wrap_info: { one: 'two' } },
- 'foo/bar',
- 'read',
- { format: 'json', wrapTTL: '1h', field: 'one' },
- ],
- expectedData: {
- type: 'json',
- content: 'two',
- },
- },
- {
- name: 'with string field flag and wrap-ttl flag',
- args: [{ foo: 'bar', wrap_info: { one: 'two' } }, 'foo/bar', 'read', { field: 'one', wrapTTL: '1h' }],
- expectedData: {
- type: 'text',
- content: 'two',
- },
- },
- {
- name: 'with object field flag and wrap-ttl flag',
- args: [
- { foo: 'bar', wrap_info: { one: { two: 'three' } } },
- 'foo/bar',
- 'read',
- { field: 'one', wrapTTL: '1h' },
- ],
- expectedData: {
- type: 'object',
- content: { two: 'three' },
- },
- },
- {
- name: 'with response data and string field flag',
- args: [{ foo: 'bar', data: { one: 'two' } }, 'foo/bar', 'read', { field: 'one', wrapTTL: '1h' }],
- expectedData: {
- type: 'text',
- content: 'two',
- },
- },
- {
- name: 'with response data and object field flag ',
- args: [
- { foo: 'bar', data: { one: { two: 'three' } } },
- 'foo/bar',
- 'read',
- { field: 'one', wrapTTL: '1h' },
- ],
- expectedData: {
- type: 'object',
- content: { two: 'three' },
- },
- },
- {
- name: 'response with data',
- args: [{ foo: 'bar', data: { one: 'two' } }, 'foo/bar', 'read', {}],
- expectedData: {
- type: 'object',
- content: { one: 'two' },
- },
- },
- {
- name: 'with response data, field flag, and field missing',
- args: [{ foo: 'bar', data: { one: 'two' } }, 'foo/bar', 'read', { field: 'foo' }],
- expectedData: {
- type: 'error',
- content: 'Field "foo" not present in secret',
- },
- },
- {
- name: 'with response data and auth block',
- args: [{ data: { one: 'two' }, auth: { three: 'four' } }, 'auth/token/create', 'write', {}],
- expectedData: {
- type: 'object',
- content: { three: 'four' },
- },
- },
- {
- name: 'with -field and -format with an object field',
- args: [{ data: { one: { three: 'two' } } }, 'sys/mounts', 'read', { field: 'one', format: 'json' }],
- expectedData: {
- type: 'json',
- content: { three: 'two' },
- },
- },
- {
- name: 'with -field and -format with a string field',
- args: [{ data: { one: 'two' } }, 'sys/mounts', 'read', { field: 'one', format: 'json' }],
- expectedData: {
- type: 'json',
- content: 'two',
- },
- },
- ];
-
- testResponseCases.forEach(function (testCase) {
- test(`#logFromResponse: ${testCase.name}`, function (assert) {
- const data = logFromResponse(...testCase.args);
- assert.deepEqual(data, testCase.expectedData);
- });
- });
-
- const testErrorCases = [
- {
- name: 'AdapterError write',
- args: [{ httpStatus: 404, path: 'v1/sys/foo', errors: [{}] }, 'sys/foo', 'write'],
- expectedContent: 'Error writing to: sys/foo.\nURL: v1/sys/foo\nCode: 404',
- },
- {
- name: 'AdapterError read',
- args: [{ httpStatus: 404, path: 'v1/sys/foo', errors: [{}] }, 'sys/foo', 'read'],
- expectedContent: 'Error reading from: sys/foo.\nURL: v1/sys/foo\nCode: 404',
- },
- {
- name: 'AdapterError list',
- args: [{ httpStatus: 404, path: 'v1/sys/foo', errors: [{}] }, 'sys/foo', 'list'],
- expectedContent: 'Error listing: sys/foo.\nURL: v1/sys/foo\nCode: 404',
- },
- {
- name: 'AdapterError delete',
- args: [{ httpStatus: 404, path: 'v1/sys/foo', errors: [{}] }, 'sys/foo', 'delete'],
- expectedContent: 'Error deleting at: sys/foo.\nURL: v1/sys/foo\nCode: 404',
- },
- {
- name: 'VaultError single error',
- args: [{ httpStatus: 404, path: 'v1/sys/foo', errors: ['no client token'] }, 'sys/foo', 'delete'],
- expectedContent: 'Error deleting at: sys/foo.\nURL: v1/sys/foo\nCode: 404\nErrors:\n no client token',
- },
- {
- name: 'VaultErrors multiple errors',
- args: [
- { httpStatus: 404, path: 'v1/sys/foo', errors: ['no client token', 'this is an error'] },
- 'sys/foo',
- 'delete',
- ],
- expectedContent:
- 'Error deleting at: sys/foo.\nURL: v1/sys/foo\nCode: 404\nErrors:\n no client token\n this is an error',
- },
- ];
-
- testErrorCases.forEach(function (testCase) {
- test(`#logFromError: ${testCase.name}`, function (assert) {
- const data = logFromError(...testCase.args);
- assert.deepEqual(
- data,
- { type: 'error', content: testCase.expectedContent },
- 'returns the expected data'
- );
- });
- });
-
- const testCommandCases = [
- {
- name: 'errors when command does not include a path',
- args: [],
- expectedContent: 'A path is required to make a request.',
- },
- {
- name: 'errors when write command does not include data and does not have force tag',
- args: ['foo/bar', 'write', {}, []],
- expectedContent: 'Must supply data or use -force',
- },
- ];
-
- testCommandCases.forEach(function (testCase) {
- test(`#formattedErrorFromInput: ${testCase.name}`, function (assert) {
- const data = formattedErrorFromInput(...testCase.args);
-
- assert.deepEqual(
- data,
- { type: 'error', content: testCase.expectedContent },
- 'returns the pcorrect data'
- );
- });
- });
-});
diff --git a/ui/tests/unit/lib/kv-object-test.js b/ui/tests/unit/lib/kv-object-test.js
deleted file mode 100644
index e3b15a0e5..000000000
--- a/ui/tests/unit/lib/kv-object-test.js
+++ /dev/null
@@ -1,107 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import KVObject from 'vault/lib/kv-object';
-
-module('Unit | Lib | kv object', function () {
- const fromJSONTests = [
- [
- 'types',
- { string: 'string', false: false, zero: 0, number: 1, null: null, true: true, object: { one: 'two' } },
- [
- { name: 'false', value: false },
- { name: 'null', value: null },
- { name: 'number', value: 1 },
- { name: 'object', value: { one: 'two' } },
- { name: 'string', value: 'string' },
- { name: 'true', value: true },
- { name: 'zero', value: 0 },
- ],
- ],
- [
- 'ordering',
- { b: 'b', 1: '1', z: 'z', A: 'A', a: 'a' },
- [
- { name: '1', value: '1' },
- { name: 'a', value: 'a' },
- { name: 'A', value: 'A' },
- { name: 'b', value: 'b' },
- { name: 'z', value: 'z' },
- ],
- ],
- ];
-
- fromJSONTests.forEach(function ([name, input, content]) {
- test(`fromJSON: ${name}`, function (assert) {
- const data = KVObject.create({ content: [] }).fromJSON(input);
- assert.deepEqual(data.get('content'), content, 'has expected content');
- });
- });
-
- test(`fromJSON: non-object input`, function (assert) {
- const input = [{ foo: 'bar' }];
- assert.throws(
- () => {
- KVObject.create({ content: [] }).fromJSON(input);
- },
- /Vault expects data to be formatted as an JSON object/,
- 'throws when non-object input is used to construct the KVObject'
- );
- });
-
- fromJSONTests.forEach(function ([name, input, content]) {
- test(`fromJSONString: ${name}`, function (assert) {
- const inputString = JSON.stringify(input, null, 2);
- const data = KVObject.create({ content: [] }).fromJSONString(inputString);
- assert.deepEqual(data.get('content'), content, 'has expected content');
- });
- });
-
- const toJSONTests = [
- [
- 'types',
- false,
- { string: 'string', false: false, zero: 0, number: 1, null: null, true: true, object: { one: 'two' } },
- { false: false, null: null, number: 1, object: { one: 'two' }, string: 'string', true: true, zero: 0 },
- ],
- ['include blanks = true', true, { string: 'string', '': '' }, { string: 'string', '': '' }],
- ['include blanks = false', false, { string: 'string', '': '' }, { string: 'string' }],
- ];
-
- toJSONTests.forEach(function ([name, includeBlanks, input, output]) {
- test(`toJSON: ${name}`, function (assert) {
- const data = KVObject.create({ content: [] }).fromJSON(input);
- const result = data.toJSON(includeBlanks);
- assert.deepEqual(result, output, 'has expected output');
- });
- });
-
- toJSONTests.forEach(function ([name, includeBlanks, input, output]) {
- test(`toJSONString: ${name}`, function (assert) {
- const expected = JSON.stringify(output, null, 2);
- const data = KVObject.create({ content: [] }).fromJSON(input);
- const result = data.toJSONString(includeBlanks);
- assert.strictEqual(result, expected, 'has expected output string');
- });
- });
-
- const isAdvancedTests = [
- [
- 'advanced',
- { string: 'string', false: false, zero: 0, number: 1, null: null, true: true, object: { one: 'two' } },
- true,
- ],
- ['string-only', { string: 'string', one: 'two' }, false],
- ];
-
- isAdvancedTests.forEach(function ([name, input, expected]) {
- test(`isAdvanced: ${name}`, function (assert) {
- const data = KVObject.create({ content: [] }).fromJSON(input);
-
- assert.strictEqual(data.isAdvanced(), expected, 'calculates isAdvanced correctly');
- });
- });
-});
diff --git a/ui/tests/unit/lib/local-storage-test.js b/ui/tests/unit/lib/local-storage-test.js
deleted file mode 100644
index 1b518ae5f..000000000
--- a/ui/tests/unit/lib/local-storage-test.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import LocalStorage from 'vault/lib/local-storage';
-
-module('Unit | lib | local-storage', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- window.localStorage.clear();
- });
-
- test('it does not error if nothing is in local storage', async function (assert) {
- assert.expect(1);
- assert.strictEqual(
- LocalStorage.cleanupStorage('something', 'something-key'),
- undefined,
- 'returns undefined and does not throw an error when method is called and nothing exist in localStorage.'
- );
- });
-
- test('it does not remove anything in localStorage that does not start with the string or we have specified to keep.', async function (assert) {
- assert.expect(3);
- LocalStorage.setItem('string-key-remove', 'string-key-remove-value');
- LocalStorage.setItem('beep-boop-bop-key', 'beep-boop-bop-value');
- LocalStorage.setItem('string-key', 'string-key-value');
- const storageLengthBefore = window.localStorage.length;
- LocalStorage.cleanupStorage('string', 'string-key');
- const storageLengthAfter = window.localStorage.length;
- assert.strictEqual(
- storageLengthBefore - storageLengthAfter,
- 1,
- 'the method should only remove one key from localStorage.'
- );
- assert.strictEqual(
- LocalStorage.getItem('string-key'),
- 'string-key-value',
- 'the key we asked to keep still exists in localStorage.'
- );
- assert.strictEqual(
- LocalStorage.getItem('string-key-remove'),
- null,
- 'the key we did not specify to keep was removed from localStorage.'
- );
- // clear storage
- window.localStorage.clear();
- });
-});
diff --git a/ui/tests/unit/lib/path-to-tree-test.js b/ui/tests/unit/lib/path-to-tree-test.js
deleted file mode 100644
index 40fa948c7..000000000
--- a/ui/tests/unit/lib/path-to-tree-test.js
+++ /dev/null
@@ -1,113 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import pathToTree from 'vault/lib/path-to-tree';
-
-module('Unit | Lib | path to tree', function () {
- const tests = [
- [
- 'basic',
- ['one', 'one/two', 'one/two/three/four/five'],
- {
- one: {
- two: {
- three: {
- four: {
- five: null,
- },
- },
- },
- },
- },
- ],
- [
- 'multiple leaves on a level',
- ['one', 'two', 'three/four/five', 'three/four/six', 'three/four/six/one'],
- {
- one: null,
- three: {
- four: {
- five: null,
- six: {
- one: null,
- },
- },
- },
- two: null,
- },
- ],
- [
- 'leaves with shared prefix',
- ['ns1', 'ns1a', 'ns1a/ns2/ns3', 'ns1a/ns2a/ns3'],
- {
- ns1: null,
- ns1a: {
- ns2: {
- ns3: null,
- },
- ns2a: {
- ns3: null,
- },
- },
- },
- ],
- [
- 'leaves with nested number and shared prefix',
- ['ns1', 'ns1a', 'ns1a/99999/five9s', 'ns1a/999/ns3', 'ns1a/9999/ns3'],
- {
- ns1: null,
- ns1a: {
- 999: {
- ns3: null,
- },
- 9999: {
- ns3: null,
- },
- 99999: {
- five9s: null,
- },
- },
- },
- ],
- [
- 'sorting lexicographically',
- [
- '99',
- 'bat',
- 'bat/bird',
- 'animal/flying/birds',
- 'animal/walking/dogs',
- 'animal/walking/cats',
- '1/thing',
- ],
- {
- 1: {
- thing: null,
- },
- 99: null,
- animal: {
- flying: {
- birds: null,
- },
- walking: {
- cats: null,
- dogs: null,
- },
- },
- bat: {
- bird: null,
- },
- },
- ],
- ];
-
- tests.forEach(function ([name, input, expected]) {
- test(`pathToTree: ${name}`, function (assert) {
- const output = pathToTree(input);
- assert.deepEqual(output, expected, 'has expected data');
- });
- });
-});
diff --git a/ui/tests/unit/machines/auth-machine-test.js b/ui/tests/unit/machines/auth-machine-test.js
deleted file mode 100644
index c5bf6531a..000000000
--- a/ui/tests/unit/machines/auth-machine-test.js
+++ /dev/null
@@ -1,90 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { Machine } from 'xstate';
-import AuthMachineConfig from 'vault/machines/auth-machine';
-
-module('Unit | Machine | auth-machine', function () {
- const authMachine = Machine(AuthMachineConfig);
-
- const testCases = [
- {
- currentState: authMachine.initialState,
- event: 'CONTINUE',
- params: null,
- expectedResults: {
- value: 'enable',
- actions: [
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- { component: 'wizard/auth-enable', level: 'step', type: 'render' },
- ],
- },
- },
- {
- currentState: 'enable',
- event: 'CONTINUE',
- params: null,
- expectedResults: {
- value: 'config',
- actions: [
- { type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
- { type: 'render', level: 'step', component: 'wizard/auth-config' },
- ],
- },
- },
- {
- currentState: 'config',
- event: 'CONTINUE',
- expectedResults: {
- value: 'details',
- actions: [
- { component: 'wizard/auth-details', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'details',
- event: 'CONTINUE',
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- {
- currentState: 'details',
- event: 'RESET',
- params: null,
- expectedResults: {
- value: 'idle',
- actions: [
- {
- params: ['vault.cluster.settings.auth.enable'],
- type: 'routeTransition',
- },
- {
- component: 'wizard/mounts-wizard',
- level: 'feature',
- type: 'render',
- },
- {
- component: 'wizard/auth-idle',
- level: 'step',
- type: 'render',
- },
- ],
- },
- },
- ];
-
- testCases.forEach((testCase) => {
- test(`transition: ${testCase.event} for currentState ${testCase.currentState} and componentState ${testCase.params}`, function (assert) {
- const result = authMachine.transition(testCase.currentState, testCase.event, testCase.params);
- assert.strictEqual(result.value, testCase.expectedResults.value);
- assert.deepEqual(result.actions, testCase.expectedResults.actions);
- });
- });
-});
diff --git a/ui/tests/unit/machines/policies-machine-test.js b/ui/tests/unit/machines/policies-machine-test.js
deleted file mode 100644
index cfffea3e3..000000000
--- a/ui/tests/unit/machines/policies-machine-test.js
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { Machine } from 'xstate';
-import PoliciesMachineConfig from 'vault/machines/policies-machine';
-
-module('Unit | Machine | policies-machine', function () {
- const policiesMachine = Machine(PoliciesMachineConfig);
-
- const testCases = [
- {
- currentState: policiesMachine.initialState,
- event: 'CONTINUE',
- params: null,
- expectedResults: {
- value: 'create',
- actions: [{ component: 'wizard/policies-create', level: 'feature', type: 'render' }],
- },
- },
- {
- currentState: 'create',
- event: 'CONTINUE',
- params: null,
- expectedResults: {
- value: 'details',
- actions: [{ component: 'wizard/policies-details', level: 'feature', type: 'render' }],
- },
- },
- {
- currentState: 'details',
- event: 'CONTINUE',
- expectedResults: {
- value: 'delete',
- actions: [{ component: 'wizard/policies-delete', level: 'feature', type: 'render' }],
- },
- },
- {
- currentState: 'delete',
- event: 'CONTINUE',
- expectedResults: {
- value: 'others',
- actions: [{ component: 'wizard/policies-others', level: 'feature', type: 'render' }],
- },
- },
- {
- currentState: 'others',
- event: 'CONTINUE',
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- ];
-
- testCases.forEach((testCase) => {
- test(`transition: ${testCase.event} for currentState ${testCase.currentState} and componentState ${testCase.params}`, function (assert) {
- const result = policiesMachine.transition(testCase.currentState, testCase.event, testCase.params);
- assert.strictEqual(result.value, testCase.expectedResults.value);
- assert.deepEqual(result.actions, testCase.expectedResults.actions);
- });
- });
-});
diff --git a/ui/tests/unit/machines/replication-machine-test.js b/ui/tests/unit/machines/replication-machine-test.js
deleted file mode 100644
index 48bb8c27d..000000000
--- a/ui/tests/unit/machines/replication-machine-test.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { Machine } from 'xstate';
-import ReplicationMachineConfig from 'vault/machines/replication-machine';
-
-module('Unit | Machine | replication-machine', function () {
- const replicationMachine = Machine(ReplicationMachineConfig);
-
- const testCases = [
- {
- currentState: replicationMachine.initialState,
- event: 'ENABLEREPLICATION',
- params: null,
- expectedResults: {
- value: 'details',
- actions: [{ type: 'render', level: 'feature', component: 'wizard/replication-details' }],
- },
- },
- {
- currentState: 'details',
- event: 'CONTINUE',
- params: null,
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- ];
-
- testCases.forEach((testCase) => {
- test(`transition: ${testCase.event} for currentState ${testCase.currentState} and componentState ${testCase.params}`, function (assert) {
- const result = replicationMachine.transition(testCase.currentState, testCase.event, testCase.params);
- assert.strictEqual(result.value, testCase.expectedResults.value);
- assert.deepEqual(result.actions, testCase.expectedResults.actions);
- });
- });
-});
diff --git a/ui/tests/unit/machines/secrets-machine-test.js b/ui/tests/unit/machines/secrets-machine-test.js
deleted file mode 100644
index 5bf2358a7..000000000
--- a/ui/tests/unit/machines/secrets-machine-test.js
+++ /dev/null
@@ -1,1075 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { Machine } from 'xstate';
-import SecretsMachineConfig from 'vault/machines/secrets-machine';
-
-module('Unit | Machine | secrets-machine', function () {
- const secretsMachine = Machine(SecretsMachineConfig);
-
- const testCases = [
- {
- currentState: secretsMachine.initialState,
- event: 'CONTINUE',
- params: null,
- expectedResults: {
- value: 'enable',
- actions: [
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- { component: 'wizard/secrets-enable', level: 'step', type: 'render' },
- ],
- },
- },
- {
- currentState: 'enable',
- event: 'CONTINUE',
- params: 'aws',
- expectedResults: {
- value: 'details',
- actions: [
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- { component: 'wizard/secrets-details', level: 'step', type: 'render' },
- ],
- },
- },
- {
- currentState: 'details',
- event: 'CONTINUE',
- params: 'aws',
- expectedResults: {
- value: 'role',
- actions: [
- { component: 'wizard/secrets-role', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'role',
- event: 'CONTINUE',
- params: 'aws',
- expectedResults: {
- value: 'displayRole',
- actions: [
- { component: 'wizard/secrets-display-role', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'displayRole',
- event: 'CONTINUE',
- params: 'aws',
- expectedResults: {
- value: 'credentials',
- actions: [
- { component: 'wizard/secrets-credentials', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'credentials',
- event: 'CONTINUE',
- params: 'aws',
- expectedResults: {
- value: 'display',
- actions: [
- { component: 'wizard/secrets-display', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'REPEAT',
- params: 'aws',
- expectedResults: {
- value: 'role',
- actions: [
- {
- params: ['vault.cluster.secrets.backend.create-root'],
- type: 'routeTransition',
- },
- { component: 'wizard/secrets-role', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'RESET',
- params: 'aws',
- expectedResults: {
- value: 'idle',
- actions: [
- {
- params: ['vault.cluster.settings.mount-secret-backend'],
- type: 'routeTransition',
- },
- {
- component: 'wizard/mounts-wizard',
- level: 'feature',
- type: 'render',
- },
- {
- component: 'wizard/secrets-idle',
- level: 'step',
- type: 'render',
- },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'DONE',
- params: 'aws',
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- {
- currentState: 'display',
- event: 'ERROR',
- params: 'aws',
- expectedResults: {
- value: 'error',
- actions: [
- { component: 'wizard/tutorial-error', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'enable',
- event: 'CONTINUE',
- params: 'pki',
- expectedResults: {
- value: 'details',
- actions: [
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- { component: 'wizard/secrets-details', level: 'step', type: 'render' },
- ],
- },
- },
- {
- currentState: 'details',
- event: 'CONTINUE',
- params: 'pki',
- expectedResults: {
- value: 'role',
- actions: [
- { component: 'wizard/secrets-role', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'role',
- event: 'CONTINUE',
- params: 'pki',
- expectedResults: {
- value: 'displayRole',
- actions: [
- { component: 'wizard/secrets-display-role', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'displayRole',
- event: 'CONTINUE',
- params: 'pki',
- expectedResults: {
- value: 'credentials',
- actions: [
- { component: 'wizard/secrets-credentials', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'credentials',
- event: 'CONTINUE',
- params: 'pki',
- expectedResults: {
- value: 'display',
- actions: [
- { component: 'wizard/secrets-display', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'REPEAT',
- params: 'pki',
- expectedResults: {
- value: 'role',
- actions: [
- {
- params: ['vault.cluster.secrets.backend.create-root'],
- type: 'routeTransition',
- },
- { component: 'wizard/secrets-role', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'RESET',
- params: 'pki',
- expectedResults: {
- value: 'idle',
- actions: [
- {
- params: ['vault.cluster.settings.mount-secret-backend'],
- type: 'routeTransition',
- },
- {
- component: 'wizard/mounts-wizard',
- level: 'feature',
- type: 'render',
- },
- {
- component: 'wizard/secrets-idle',
- level: 'step',
- type: 'render',
- },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'DONE',
- params: 'pki',
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- {
- currentState: 'display',
- event: 'ERROR',
- params: 'pki',
- expectedResults: {
- value: 'error',
- actions: [
- { component: 'wizard/tutorial-error', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'enable',
- event: 'CONTINUE',
- params: 'ssh',
- expectedResults: {
- value: 'details',
- actions: [
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- { component: 'wizard/secrets-details', level: 'step', type: 'render' },
- ],
- },
- },
- {
- currentState: 'details',
- event: 'CONTINUE',
- params: 'ssh',
- expectedResults: {
- value: 'role',
- actions: [
- { component: 'wizard/secrets-role', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'role',
- event: 'CONTINUE',
- params: 'ssh',
- expectedResults: {
- value: 'displayRole',
- actions: [
- { component: 'wizard/secrets-display-role', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'displayRole',
- event: 'CONTINUE',
- params: 'ssh',
- expectedResults: {
- value: 'credentials',
- actions: [
- { component: 'wizard/secrets-credentials', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'credentials',
- event: 'CONTINUE',
- params: 'ssh',
- expectedResults: {
- value: 'display',
- actions: [
- { component: 'wizard/secrets-display', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'REPEAT',
- params: 'ssh',
- expectedResults: {
- value: 'role',
- actions: [
- {
- params: ['vault.cluster.secrets.backend.create-root'],
- type: 'routeTransition',
- },
- { component: 'wizard/secrets-role', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'RESET',
- params: 'ssh',
- expectedResults: {
- value: 'idle',
- actions: [
- {
- params: ['vault.cluster.settings.mount-secret-backend'],
- type: 'routeTransition',
- },
- {
- component: 'wizard/mounts-wizard',
- level: 'feature',
- type: 'render',
- },
- {
- component: 'wizard/secrets-idle',
- level: 'step',
- type: 'render',
- },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'DONE',
- params: 'ssh',
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- {
- currentState: 'display',
- event: 'ERROR',
- params: 'ssh',
- expectedResults: {
- value: 'error',
- actions: [
- { component: 'wizard/tutorial-error', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'enable',
- event: 'CONTINUE',
- params: 'ad',
- expectedResults: {
- value: 'list',
- actions: [
- { type: 'render', level: 'step', component: 'wizard/secrets-list' },
- { type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
- ],
- },
- },
- {
- currentState: 'list',
- event: 'CONTINUE',
- params: 'ad',
- expectedResults: {
- value: 'display',
- actions: [
- { component: 'wizard/secrets-display', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'RESET',
- params: 'ad',
- expectedResults: {
- value: 'idle',
- actions: [
- {
- params: ['vault.cluster.settings.mount-secret-backend'],
- type: 'routeTransition',
- },
- {
- component: 'wizard/mounts-wizard',
- level: 'feature',
- type: 'render',
- },
- {
- component: 'wizard/secrets-idle',
- level: 'step',
- type: 'render',
- },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'DONE',
- params: 'ad',
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- {
- currentState: 'display',
- event: 'ERROR',
- params: 'ad',
- expectedResults: {
- value: 'error',
- actions: [
- { component: 'wizard/tutorial-error', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'enable',
- event: 'CONTINUE',
- params: 'consul',
- expectedResults: {
- value: 'list',
- actions: [
- { type: 'render', level: 'step', component: 'wizard/secrets-list' },
- { type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
- ],
- },
- },
- {
- currentState: 'list',
- event: 'CONTINUE',
- params: 'consul',
- expectedResults: {
- value: 'display',
- actions: [
- { component: 'wizard/secrets-display', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'RESET',
- params: 'consul',
- expectedResults: {
- value: 'idle',
- actions: [
- {
- params: ['vault.cluster.settings.mount-secret-backend'],
- type: 'routeTransition',
- },
- {
- component: 'wizard/mounts-wizard',
- level: 'feature',
- type: 'render',
- },
- {
- component: 'wizard/secrets-idle',
- level: 'step',
- type: 'render',
- },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'DONE',
- params: 'consul',
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- {
- currentState: 'display',
- event: 'ERROR',
- params: 'consul',
- expectedResults: {
- value: 'error',
- actions: [
- { component: 'wizard/tutorial-error', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'enable',
- event: 'CONTINUE',
- params: 'database',
- expectedResults: {
- value: 'details',
- actions: [
- { type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
- { type: 'render', level: 'step', component: 'wizard/secrets-details' },
- ],
- },
- },
- {
- currentState: 'list',
- event: 'CONTINUE',
- params: 'database',
- expectedResults: {
- value: 'display',
- actions: [
- { component: 'wizard/secrets-display', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'RESET',
- params: 'database',
- expectedResults: {
- value: 'idle',
- actions: [
- {
- params: ['vault.cluster.settings.mount-secret-backend'],
- type: 'routeTransition',
- },
- {
- component: 'wizard/mounts-wizard',
- level: 'feature',
- type: 'render',
- },
- {
- component: 'wizard/secrets-idle',
- level: 'step',
- type: 'render',
- },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'DONE',
- params: 'database',
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- {
- currentState: 'display',
- event: 'ERROR',
- params: 'database',
- expectedResults: {
- value: 'error',
- actions: [
- { component: 'wizard/tutorial-error', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'enable',
- event: 'CONTINUE',
- params: 'gcp',
- expectedResults: {
- value: 'list',
- actions: [
- { type: 'render', level: 'step', component: 'wizard/secrets-list' },
- { type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
- ],
- },
- },
- {
- currentState: 'list',
- event: 'CONTINUE',
- params: 'gcp',
- expectedResults: {
- value: 'display',
- actions: [
- { component: 'wizard/secrets-display', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'RESET',
- params: 'gcp',
- expectedResults: {
- value: 'idle',
- actions: [
- {
- params: ['vault.cluster.settings.mount-secret-backend'],
- type: 'routeTransition',
- },
- {
- component: 'wizard/mounts-wizard',
- level: 'feature',
- type: 'render',
- },
- {
- component: 'wizard/secrets-idle',
- level: 'step',
- type: 'render',
- },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'DONE',
- params: 'gcp',
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- {
- currentState: 'display',
- event: 'ERROR',
- params: 'gcp',
- expectedResults: {
- value: 'error',
- actions: [
- { component: 'wizard/tutorial-error', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'enable',
- event: 'CONTINUE',
- params: 'nomad',
- expectedResults: {
- value: 'list',
- actions: [
- { type: 'render', level: 'step', component: 'wizard/secrets-list' },
- { type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
- ],
- },
- },
- {
- currentState: 'list',
- event: 'CONTINUE',
- params: 'nomad',
- expectedResults: {
- value: 'display',
- actions: [
- { component: 'wizard/secrets-display', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'RESET',
- params: 'nomad',
- expectedResults: {
- value: 'idle',
- actions: [
- {
- params: ['vault.cluster.settings.mount-secret-backend'],
- type: 'routeTransition',
- },
- {
- component: 'wizard/mounts-wizard',
- level: 'feature',
- type: 'render',
- },
- {
- component: 'wizard/secrets-idle',
- level: 'step',
- type: 'render',
- },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'DONE',
- params: 'nomad',
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- {
- currentState: 'display',
- event: 'ERROR',
- params: 'nomad',
- expectedResults: {
- value: 'error',
- actions: [
- { component: 'wizard/tutorial-error', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'enable',
- event: 'CONTINUE',
- params: 'rabbitmq',
- expectedResults: {
- value: 'list',
- actions: [
- { type: 'render', level: 'step', component: 'wizard/secrets-list' },
- { type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
- ],
- },
- },
- {
- currentState: 'list',
- event: 'CONTINUE',
- params: 'rabbitmq',
- expectedResults: {
- value: 'display',
- actions: [
- { component: 'wizard/secrets-display', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'RESET',
- params: 'rabbitmq',
- expectedResults: {
- value: 'idle',
- actions: [
- {
- params: ['vault.cluster.settings.mount-secret-backend'],
- type: 'routeTransition',
- },
- {
- component: 'wizard/mounts-wizard',
- level: 'feature',
- type: 'render',
- },
- {
- component: 'wizard/secrets-idle',
- level: 'step',
- type: 'render',
- },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'DONE',
- params: 'rabbitmq',
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- {
- currentState: 'display',
- event: 'ERROR',
- params: 'rabbitmq',
- expectedResults: {
- value: 'error',
- actions: [
- { component: 'wizard/tutorial-error', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'enable',
- event: 'CONTINUE',
- params: 'totp',
- expectedResults: {
- value: 'list',
- actions: [
- { type: 'render', level: 'step', component: 'wizard/secrets-list' },
- { type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
- ],
- },
- },
- {
- currentState: 'list',
- event: 'CONTINUE',
- params: 'totp',
- expectedResults: {
- value: 'display',
- actions: [
- { component: 'wizard/secrets-display', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'RESET',
- params: 'totp',
- expectedResults: {
- value: 'idle',
- actions: [
- {
- params: ['vault.cluster.settings.mount-secret-backend'],
- type: 'routeTransition',
- },
- {
- component: 'wizard/mounts-wizard',
- level: 'feature',
- type: 'render',
- },
- {
- component: 'wizard/secrets-idle',
- level: 'step',
- type: 'render',
- },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'DONE',
- params: 'totp',
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- {
- currentState: 'display',
- event: 'ERROR',
- params: 'totp',
- expectedResults: {
- value: 'error',
- actions: [
- { component: 'wizard/tutorial-error', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'enable',
- event: 'CONTINUE',
- params: 'kv',
- expectedResults: {
- value: 'details',
- actions: [
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- { component: 'wizard/secrets-details', level: 'step', type: 'render' },
- ],
- },
- },
- {
- currentState: 'details',
- event: 'CONTINUE',
- params: 'kv',
- expectedResults: {
- value: 'secret',
- actions: [
- { component: 'wizard/secrets-secret', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'secret',
- event: 'CONTINUE',
- params: 'kv',
- expectedResults: {
- value: 'display',
- actions: [
- { component: 'wizard/secrets-display', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'REPEAT',
- params: 'kv',
- expectedResults: {
- value: 'secret',
- actions: [
- {
- params: ['vault.cluster.secrets.backend.create-root'],
- type: 'routeTransition',
- },
- { component: 'wizard/secrets-secret', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'RESET',
- params: 'kv',
- expectedResults: {
- value: 'idle',
- actions: [
- {
- params: ['vault.cluster.settings.mount-secret-backend'],
- type: 'routeTransition',
- },
- {
- component: 'wizard/mounts-wizard',
- level: 'feature',
- type: 'render',
- },
- {
- component: 'wizard/secrets-idle',
- level: 'step',
- type: 'render',
- },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'DONE',
- params: 'kv',
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- {
- currentState: 'display',
- event: 'ERROR',
- params: 'kv',
- expectedResults: {
- value: 'error',
- actions: [
- { component: 'wizard/tutorial-error', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'enable',
- event: 'CONTINUE',
- params: 'transit',
- expectedResults: {
- value: 'details',
- actions: [
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- { component: 'wizard/secrets-details', level: 'step', type: 'render' },
- ],
- },
- },
- {
- currentState: 'details',
- event: 'CONTINUE',
- params: 'transit',
- expectedResults: {
- value: 'encryption',
- actions: [
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- { component: 'wizard/secrets-encryption', level: 'step', type: 'render' },
- ],
- },
- },
- {
- currentState: 'encryption',
- event: 'CONTINUE',
- params: 'transit',
- expectedResults: {
- value: 'display',
- actions: [
- { component: 'wizard/secrets-display', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'REPEAT',
- params: 'transit',
- expectedResults: {
- value: 'encryption',
- actions: [
- {
- params: ['vault.cluster.secrets.backend.create-root'],
- type: 'routeTransition',
- },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- { component: 'wizard/secrets-encryption', level: 'step', type: 'render' },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'RESET',
- params: 'transit',
- expectedResults: {
- value: 'idle',
- actions: [
- {
- params: ['vault.cluster.settings.mount-secret-backend'],
- type: 'routeTransition',
- },
- {
- component: 'wizard/mounts-wizard',
- level: 'feature',
- type: 'render',
- },
- {
- component: 'wizard/secrets-idle',
- level: 'step',
- type: 'render',
- },
- ],
- },
- },
- {
- currentState: 'display',
- event: 'DONE',
- params: 'transit',
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- {
- currentState: 'display',
- event: 'ERROR',
- params: 'transit',
- expectedResults: {
- value: 'error',
- actions: [
- { component: 'wizard/tutorial-error', level: 'step', type: 'render' },
- { component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
- ],
- },
- },
- ];
-
- testCases.forEach((testCase) => {
- test(`transition: ${testCase.event} for currentState ${testCase.currentState} and componentState ${testCase.params}`, function (assert) {
- const result = secretsMachine.transition(testCase.currentState, testCase.event, testCase.params);
- assert.strictEqual(result.value, testCase.expectedResults.value);
- assert.deepEqual(result.actions, testCase.expectedResults.actions);
- });
- });
-});
diff --git a/ui/tests/unit/machines/tools-machine-test.js b/ui/tests/unit/machines/tools-machine-test.js
deleted file mode 100644
index 607e46e27..000000000
--- a/ui/tests/unit/machines/tools-machine-test.js
+++ /dev/null
@@ -1,94 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { Machine } from 'xstate';
-import ToolsMachineConfig from 'vault/machines/tools-machine';
-
-module('Unit | Machine | tools-machine', function () {
- const toolsMachine = Machine(ToolsMachineConfig);
-
- const testCases = [
- {
- currentState: toolsMachine.initialState,
- event: 'CONTINUE',
- params: null,
- expectedResults: {
- value: 'wrapped',
- actions: [{ type: 'render', level: 'feature', component: 'wizard/tools-wrapped' }],
- },
- },
- {
- currentState: 'wrapped',
- event: 'LOOKUP',
- params: null,
- expectedResults: {
- value: 'lookup',
- actions: [{ type: 'render', level: 'feature', component: 'wizard/tools-lookup' }],
- },
- },
- {
- currentState: 'lookup',
- event: 'CONTINUE',
- params: null,
- expectedResults: {
- value: 'info',
- actions: [{ type: 'render', level: 'feature', component: 'wizard/tools-info' }],
- },
- },
- {
- currentState: 'info',
- event: 'REWRAP',
- params: null,
- expectedResults: {
- value: 'rewrap',
- actions: [{ type: 'render', level: 'feature', component: 'wizard/tools-rewrap' }],
- },
- },
- {
- currentState: 'rewrap',
- event: 'CONTINUE',
- params: null,
- expectedResults: {
- value: 'rewrapped',
- actions: [{ type: 'render', level: 'feature', component: 'wizard/tools-rewrapped' }],
- },
- },
- {
- currentState: 'rewrapped',
- event: 'UNWRAP',
- params: null,
- expectedResults: {
- value: 'unwrap',
- actions: [{ type: 'render', level: 'feature', component: 'wizard/tools-unwrap' }],
- },
- },
- {
- currentState: 'unwrap',
- event: 'CONTINUE',
- params: null,
- expectedResults: {
- value: 'unwrapped',
- actions: [{ type: 'render', level: 'feature', component: 'wizard/tools-unwrapped' }],
- },
- },
- {
- currentState: 'unwrapped',
- event: 'CONTINUE',
- expectedResults: {
- value: 'complete',
- actions: ['completeFeature'],
- },
- },
- ];
-
- testCases.forEach((testCase) => {
- test(`transition: ${testCase.event} for currentState ${testCase.currentState} and componentState ${testCase.params}`, function (assert) {
- const result = toolsMachine.transition(testCase.currentState, testCase.event, testCase.params);
- assert.strictEqual(result.value, testCase.expectedResults.value);
- assert.deepEqual(result.actions, testCase.expectedResults.actions);
- });
- });
-});
diff --git a/ui/tests/unit/machines/tutorial-machine-test.js b/ui/tests/unit/machines/tutorial-machine-test.js
deleted file mode 100644
index eae055344..000000000
--- a/ui/tests/unit/machines/tutorial-machine-test.js
+++ /dev/null
@@ -1,251 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { Machine } from 'xstate';
-import TutorialMachineConfig from 'vault/machines/tutorial-machine';
-
-module('Unit | Machine | tutorial-machine', function () {
- const tutorialMachine = Machine(TutorialMachineConfig);
-
- const testCases = [
- {
- currentState: 'init',
- event: 'START',
- params: null,
- expectedResults: {
- value: {
- init: {
- active: 'setup',
- },
- },
- actions: [
- { type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' },
- { type: 'render', level: 'feature', component: 'wizard/init-setup' },
- ],
- },
- },
- {
- currentState: 'init.active.setup',
- event: 'TOSAVE',
- params: null,
- expectedResults: {
- value: {
- init: {
- active: 'save',
- },
- },
- actions: [{ type: 'render', level: 'feature', component: 'wizard/init-save-keys' }],
- },
- },
- {
- currentState: 'init',
- event: 'SAVE',
- params: null,
- expectedResults: {
- value: {
- init: {
- active: 'save',
- },
- },
- actions: [
- { type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' },
- { type: 'render', level: 'feature', component: 'wizard/init-save-keys' },
- ],
- },
- },
- {
- currentState: 'init.active.save',
- event: 'TOUNSEAL',
- params: null,
- expectedResults: {
- value: {
- init: {
- active: 'unseal',
- },
- },
- actions: [{ type: 'render', level: 'feature', component: 'wizard/init-unseal' }],
- },
- },
- {
- currentState: 'init',
- event: 'UNSEAL',
- params: null,
- expectedResults: {
- value: {
- init: {
- active: 'unseal',
- },
- },
- actions: [
- { type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' },
- { type: 'render', level: 'feature', component: 'wizard/init-unseal' },
- ],
- },
- },
- {
- currentState: 'init.active.unseal',
- event: 'TOLOGIN',
- params: null,
- expectedResults: {
- value: {
- init: {
- active: 'login',
- },
- },
- actions: [{ type: 'render', level: 'feature', component: 'wizard/init-login' }],
- },
- },
- {
- currentState: 'init',
- event: 'LOGIN',
- params: null,
- expectedResults: {
- value: {
- init: {
- active: 'login',
- },
- },
- actions: [
- { type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' },
- { type: 'render', level: 'feature', component: 'wizard/init-login' },
- ],
- },
- },
- {
- currentState: 'init.active.login',
- event: 'INITDONE',
- params: null,
- expectedResults: {
- value: {
- active: 'select',
- },
- actions: [
- 'showTutorialWhenAuthenticated',
- 'clearFeatureData',
- { type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' },
- { type: 'render', level: 'feature', component: 'wizard/features-selection' },
- ],
- },
- },
- {
- currentState: 'active.select',
- event: 'CONTINUE',
- params: null,
- expectedResults: {
- value: {
- active: 'feature',
- },
- actions: [],
- },
- },
- {
- currentState: 'active.feature',
- event: 'DISMISS',
- params: null,
- expectedResults: {
- value: 'dismissed',
- actions: [
- { type: 'render', level: 'tutorial', component: null },
- { type: 'render', level: 'feature', component: null },
- { type: 'render', level: 'step', component: null },
- { type: 'render', level: 'detail', component: null },
- 'handleDismissed',
- ],
- },
- },
- {
- currentState: 'active.feature',
- event: 'DONE',
- params: null,
- expectedResults: {
- value: 'complete',
- actions: [
- { type: 'render', level: 'feature', component: null },
- { type: 'render', level: 'step', component: null },
- { type: 'render', level: 'detail', component: null },
- { type: 'render', level: 'tutorial', component: 'wizard/tutorial-complete' },
- ],
- },
- },
- {
- currentState: 'active.feature',
- event: 'PAUSE',
- params: null,
- expectedResults: {
- value: 'paused',
- actions: [
- { type: 'render', level: 'feature', component: null },
- { type: 'render', level: 'step', component: null },
- { type: 'render', level: 'detail', component: null },
- { type: 'render', level: 'tutorial', component: 'wizard/tutorial-paused' },
- 'handlePaused',
- ],
- },
- },
- {
- currentState: 'paused',
- event: 'CONTINUE',
- params: null,
- expectedResults: {
- value: {
- active: 'feature',
- },
- actions: ['handleResume', { type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' }],
- },
- },
- {
- currentState: 'idle',
- event: 'INIT',
- params: null,
- expectedResults: {
- value: {
- init: 'idle',
- },
- actions: [
- 'showTutorialAlways',
- { type: 'render', level: 'tutorial', component: 'wizard/tutorial-idle' },
- { type: 'render', level: 'feature', component: null },
- ],
- },
- },
- {
- currentState: 'idle',
- event: 'AUTH',
- params: null,
- expectedResults: {
- value: {
- active: 'select',
- },
- actions: [
- { type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' },
- { type: 'render', level: 'feature', component: 'wizard/features-selection' },
- ],
- },
- },
- {
- currentState: 'idle',
- event: 'CONTINUE',
- params: null,
- expectedResults: {
- value: {
- active: 'select',
- },
- actions: [
- { type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' },
- { type: 'render', level: 'feature', component: 'wizard/features-selection' },
- ],
- },
- },
- ];
-
- testCases.forEach((testCase) => {
- test(`transition: ${testCase.event} for currentState ${testCase.currentState} and componentState ${testCase.params}`, function (assert) {
- const result = tutorialMachine.transition(testCase.currentState, testCase.event, testCase.params);
- assert.deepEqual(result.value, testCase.expectedResults.value);
- assert.deepEqual(result.actions, testCase.expectedResults.actions);
- });
- });
-});
diff --git a/ui/tests/unit/mixins/cluster-route-test.js b/ui/tests/unit/mixins/cluster-route-test.js
deleted file mode 100644
index 3129e46d9..000000000
--- a/ui/tests/unit/mixins/cluster-route-test.js
+++ /dev/null
@@ -1,162 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { assign } from '@ember/polyfills';
-import EmberObject from '@ember/object';
-import ClusterRouteMixin from 'vault/mixins/cluster-route';
-import {
- INIT,
- UNSEAL,
- AUTH,
- CLUSTER,
- CLUSTER_INDEX,
- DR_REPLICATION_SECONDARY,
- REDIRECT,
-} from 'vault/lib/route-paths';
-import { module, test } from 'qunit';
-import sinon from 'sinon';
-
-module('Unit | Mixin | cluster route', function () {
- function createClusterRoute(
- clusterModel = {},
- methods = { router: {}, hasKeyData: () => false, authToken: () => null, transitionTo: () => {} }
- ) {
- const ClusterRouteObject = EmberObject.extend(
- ClusterRouteMixin,
- assign(methods, { clusterModel: () => clusterModel })
- );
- return ClusterRouteObject.create();
- }
-
- test('#targetRouteName init', function (assert) {
- let subject = createClusterRoute({ needsInit: true });
- subject.routeName = CLUSTER;
- assert.strictEqual(subject.targetRouteName(), INIT, 'forwards to INIT when cluster needs init');
-
- subject = createClusterRoute({ needsInit: false, sealed: true });
- subject.routeName = CLUSTER;
- assert.strictEqual(subject.targetRouteName(), UNSEAL, 'forwards to UNSEAL if sealed and initialized');
-
- subject = createClusterRoute({ needsInit: false });
- subject.routeName = CLUSTER;
- assert.strictEqual(subject.targetRouteName(), AUTH, 'forwards to AUTH if unsealed and initialized');
-
- subject = createClusterRoute({ dr: { isSecondary: true } });
- subject.routeName = CLUSTER;
- assert.strictEqual(
- subject.targetRouteName(),
- DR_REPLICATION_SECONDARY,
- 'forwards to DR_REPLICATION_SECONDARY if is a dr secondary'
- );
- });
-
- test('#targetRouteName when #hasDataKey is true', function (assert) {
- let subject = createClusterRoute(
- { needsInit: false, sealed: true },
- { hasKeyData: () => true, authToken: () => null }
- );
-
- subject.routeName = CLUSTER;
- assert.strictEqual(
- subject.targetRouteName(),
- INIT,
- 'still land on INIT if there are keys on the controller'
- );
-
- subject.routeName = UNSEAL;
- assert.strictEqual(subject.targetRouteName(), UNSEAL, 'allowed to proceed to unseal');
-
- subject = createClusterRoute(
- { needsInit: false, sealed: false },
- { hasKeyData: () => true, authToken: () => null }
- );
-
- subject.routeName = AUTH;
- assert.strictEqual(subject.targetRouteName(), AUTH, 'allowed to proceed to auth');
- });
-
- test('#targetRouteName happy path forwards to CLUSTER route', function (assert) {
- const subject = createClusterRoute(
- { needsInit: false, sealed: false, dr: { isSecondary: false } },
- { hasKeyData: () => false, authToken: () => 'a token' }
- );
- subject.routeName = INIT;
- assert.strictEqual(subject.targetRouteName(), CLUSTER, 'forwards when inited and navigating to INIT');
-
- subject.routeName = UNSEAL;
- assert.strictEqual(subject.targetRouteName(), CLUSTER, 'forwards when unsealed and navigating to UNSEAL');
-
- subject.routeName = AUTH;
- assert.strictEqual(
- subject.targetRouteName(),
- REDIRECT,
- 'forwards when authenticated and navigating to AUTH'
- );
-
- subject.routeName = DR_REPLICATION_SECONDARY;
- assert.strictEqual(
- subject.targetRouteName(),
- CLUSTER,
- 'forwards when not a DR secondary and navigating to DR_REPLICATION_SECONDARY'
- );
- });
-
- test('#targetRouteName happy path when not authed forwards to AUTH', function (assert) {
- const subject = createClusterRoute(
- { needsInit: false, sealed: false, dr: { isSecondary: false } },
- { hasKeyData: () => false, authToken: () => null }
- );
- subject.routeName = INIT;
- assert.strictEqual(subject.targetRouteName(), AUTH, 'forwards when inited and navigating to INIT');
-
- subject.routeName = UNSEAL;
- assert.strictEqual(subject.targetRouteName(), AUTH, 'forwards when unsealed and navigating to UNSEAL');
-
- subject.routeName = AUTH;
- assert.strictEqual(
- subject.targetRouteName(),
- AUTH,
- 'forwards when non-authenticated and navigating to AUTH'
- );
-
- subject.routeName = DR_REPLICATION_SECONDARY;
- assert.strictEqual(
- subject.targetRouteName(),
- AUTH,
- 'forwards when not a DR secondary and navigating to DR_REPLICATION_SECONDARY'
- );
- });
-
- test('#transitionToTargetRoute', function (assert) {
- const redirectRouteURL = '/vault/secrets/secret/create';
- const subject = createClusterRoute({ needsInit: false, sealed: false });
- subject.router.currentURL = redirectRouteURL;
- const spy = sinon.spy(subject, 'transitionTo');
- subject.transitionToTargetRoute();
- assert.ok(
- spy.calledWithExactly(AUTH, { queryParams: { redirect_to: redirectRouteURL } }),
- 'calls transitionTo with the expected args'
- );
-
- spy.restore();
- });
-
- test('#transitionToTargetRoute with auth as a target', function (assert) {
- const subject = createClusterRoute({ needsInit: false, sealed: false });
- const spy = sinon.spy(subject, 'transitionTo');
- // in this case it's already transitioning to the AUTH route so we don't need to call transitionTo again
- subject.transitionToTargetRoute({ targetName: AUTH });
- assert.ok(spy.notCalled, 'transitionTo is not called');
- spy.restore();
- });
-
- test('#transitionToTargetRoute with auth target, coming from cluster route', function (assert) {
- const subject = createClusterRoute({ needsInit: false, sealed: false });
- const spy = sinon.spy(subject, 'transitionTo');
- subject.transitionToTargetRoute({ targetName: CLUSTER_INDEX });
- assert.ok(spy.calledWithExactly(AUTH), 'calls transitionTo without redirect_to');
- spy.restore();
- });
-});
diff --git a/ui/tests/unit/models/capabilities-test.js b/ui/tests/unit/models/capabilities-test.js
deleted file mode 100644
index 99a5654be..000000000
--- a/ui/tests/unit/models/capabilities-test.js
+++ /dev/null
@@ -1,131 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { SUDO_PATHS, SUDO_PATH_PREFIXES } from 'vault/models/capabilities';
-
-import { run } from '@ember/runloop';
-
-module('Unit | Model | capabilities', function (hooks) {
- setupTest(hooks);
-
- test('it exists', function (assert) {
- const model = run(() => this.owner.lookup('service:store').createRecord('capabilities'));
- assert.ok(!!model);
- });
-
- test('it reads capabilities', function (assert) {
- const model = run(() =>
- this.owner.lookup('service:store').createRecord('capabilities', {
- path: 'foo',
- capabilities: ['list', 'read'],
- })
- );
-
- assert.ok(model.get('canRead'));
- assert.ok(model.get('canList'));
- assert.notOk(model.get('canUpdate'));
- assert.notOk(model.get('canDelete'));
- });
-
- test('it allows everything if root is present', function (assert) {
- const model = run(() =>
- this.owner.lookup('service:store').createRecord('capabilities', {
- path: 'foo',
- capabilities: ['root', 'deny', 'read'],
- })
- );
- assert.ok(model.get('canRead'));
- assert.ok(model.get('canCreate'));
- assert.ok(model.get('canUpdate'));
- assert.ok(model.get('canDelete'));
- assert.ok(model.get('canList'));
- });
-
- test('it denies everything if deny is present', function (assert) {
- const model = run(() =>
- this.owner.lookup('service:store').createRecord('capabilities', {
- path: 'foo',
- capabilities: ['sudo', 'deny', 'read'],
- })
- );
- assert.notOk(model.get('canRead'));
- assert.notOk(model.get('canCreate'));
- assert.notOk(model.get('canUpdate'));
- assert.notOk(model.get('canDelete'));
- assert.notOk(model.get('canList'));
- });
-
- test('it requires sudo on sudo paths', function (assert) {
- const model = run(() =>
- this.owner.lookup('service:store').createRecord('capabilities', {
- path: SUDO_PATHS[0],
- capabilities: ['sudo', 'read'],
- })
- );
- assert.ok(model.get('canRead'));
- assert.notOk(model.get('canCreate'), 'sudo requires the capability to be set as well');
- assert.notOk(model.get('canUpdate'));
- assert.notOk(model.get('canDelete'));
- assert.notOk(model.get('canList'));
- });
-
- test('it requires sudo on sudo paths prefixes', function (assert) {
- const model = run(() =>
- this.owner.lookup('service:store').createRecord('capabilities', {
- path: SUDO_PATH_PREFIXES[0] + '/foo',
- capabilities: ['sudo', 'read'],
- })
- );
- assert.ok(model.get('canRead'));
- assert.notOk(model.get('canCreate'), 'sudo requires the capability to be set as well');
- assert.notOk(model.get('canUpdate'));
- assert.notOk(model.get('canDelete'));
- assert.notOk(model.get('canList'));
- });
-
- test('it does not require sudo on sys/leases/revoke if update capability is present and path is not fully a sudo prefix', function (assert) {
- const model = run(() =>
- this.owner.lookup('service:store').createRecord('capabilities', {
- path: 'sys/leases/revoke',
- capabilities: ['update', 'read'],
- })
- );
- assert.ok(model.get('canRead'));
- assert.notOk(model.get('canCreate'), 'sudo requires the capability to be set as well');
- assert.ok(model.get('canUpdate'), 'should not require sudo if it has update');
- assert.notOk(model.get('canDelete'));
- assert.notOk(model.get('canList'));
- });
-
- test('it requires sudo on prefix path even if capability is present', function (assert) {
- const model = run(() =>
- this.owner.lookup('service:store').createRecord('capabilities', {
- path: SUDO_PATH_PREFIXES[0] + '/aws',
- capabilities: ['update', 'read'],
- })
- );
- assert.notOk(model.get('canRead'));
- assert.notOk(model.get('canCreate'));
- assert.notOk(model.get('canUpdate'), 'should still require sudo');
- assert.notOk(model.get('canDelete'));
- assert.notOk(model.get('canList'));
- });
-
- test('it does not require sudo on prefix path if both update and sudo capabilities are present', function (assert) {
- const model = run(() =>
- this.owner.lookup('service:store').createRecord('capabilities', {
- path: SUDO_PATH_PREFIXES[0] + '/aws',
- capabilities: ['sudo', 'update', 'read'],
- })
- );
- assert.ok(model.get('canRead'));
- assert.notOk(model.get('canCreate'));
- assert.ok(model.get('canUpdate'), 'should not require sudo');
- assert.notOk(model.get('canDelete'));
- assert.notOk(model.get('canList'));
- });
-});
diff --git a/ui/tests/unit/models/role-jwt-test.js b/ui/tests/unit/models/role-jwt-test.js
deleted file mode 100644
index 9bb8f9a27..000000000
--- a/ui/tests/unit/models/role-jwt-test.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-/* eslint qunit/no-conditional-assertions: "warn" */
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { DOMAIN_STRINGS, PROVIDER_WITH_LOGO } from 'vault/models/role-jwt';
-
-module('Unit | Model | role-jwt', function (hooks) {
- setupTest(hooks);
-
- test('it exists', function (assert) {
- const model = this.owner.lookup('service:store').createRecord('role-jwt');
- assert.ok(!!model);
- assert.strictEqual(model.providerName, null, 'no providerName');
- assert.strictEqual(model.providerButtonComponent, null, 'no providerButtonComponent');
- });
-
- test('it computes providerName when known provider url match fails', function (assert) {
- const model = this.owner.lookup('service:store').createRecord('role-jwt', {
- authUrl: 'http://example.com',
- });
-
- assert.strictEqual(model.providerName, null, 'no providerName');
- assert.strictEqual(model.providerButtonComponent, null, 'no providerButtonComponent');
- });
-
- test('it provides a providerName for listed known providers', function (assert) {
- assert.expect(12);
- Object.keys(DOMAIN_STRINGS).forEach((domain) => {
- const model = this.owner.lookup('service:store').createRecord('role-jwt', {
- authUrl: `http://provider-${domain}`,
- });
-
- const expectedName = DOMAIN_STRINGS[domain];
- assert.strictEqual(model.providerName, expectedName, `computes providerName: ${expectedName}`);
- if (PROVIDER_WITH_LOGO.includes(expectedName)) {
- assert.strictEqual(
- model.providerButtonComponent,
- `auth-button-${expectedName.toLowerCase()}`,
- `computes providerButtonComponent: ${domain}`
- );
- } else {
- assert.strictEqual(
- model.providerButtonComponent,
- null,
- `computes providerButtonComponent: ${domain}`
- );
- }
- });
- });
-
- test('it does not return provider unless domain matches completely', function (assert) {
- assert.expect(2);
- const model = this.owner.lookup('service:store').createRecord('role-jwt', {
- authUrl: `http://custom-auth0-provider.com`,
- });
- assert.strictEqual(model.providerName, null, `no providerName for custom URL`);
- assert.strictEqual(model.providerButtonComponent, null, 'no providerButtonComponent for custom URL');
- });
-});
diff --git a/ui/tests/unit/models/secret-engine-test.js b/ui/tests/unit/models/secret-engine-test.js
deleted file mode 100644
index afe4d7d3d..000000000
--- a/ui/tests/unit/models/secret-engine-test.js
+++ /dev/null
@@ -1,480 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import sinon from 'sinon';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Model | secret-engine', function (hooks) {
- setupTest(hooks);
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- });
-
- module('modelTypeForKV', function () {
- test('is secret by default', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine');
- assert.strictEqual(model.get('modelTypeForKV'), 'secret');
- });
-
- test('is secret-v2 for kv v2', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- version: 2,
- type: 'kv',
- });
- assert.strictEqual(model.get('modelTypeForKV'), 'secret-v2');
- });
-
- test('is secret-v2 for generic v2', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- version: 2,
- type: 'kv',
- });
-
- assert.strictEqual(model.get('modelTypeForKV'), 'secret-v2');
- });
-
- test('is secret when v2 if not kv or generic', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- version: 2,
- type: 'ssh',
- });
-
- assert.strictEqual(model.get('modelTypeForKV'), 'secret');
- });
- });
-
- module('formFields', function () {
- test('it returns correct fields by default', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: '',
- });
-
- assert.deepEqual(model.get('formFields'), [
- 'type',
- 'path',
- 'description',
- 'accessor',
- 'local',
- 'sealWrap',
- 'config.defaultLeaseTtl',
- 'config.maxLeaseTtl',
- 'config.allowedManagedKeys',
- 'config.auditNonHmacRequestKeys',
- 'config.auditNonHmacResponseKeys',
- 'config.passthroughRequestHeaders',
- 'config.allowedResponseHeaders',
- ]);
- });
-
- test('it returns correct fields for KV v1', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'kv',
- });
-
- assert.deepEqual(model.get('formFields'), [
- 'type',
- 'path',
- 'description',
- 'accessor',
- 'local',
- 'sealWrap',
- 'config.defaultLeaseTtl',
- 'config.maxLeaseTtl',
- 'config.allowedManagedKeys',
- 'config.auditNonHmacRequestKeys',
- 'config.auditNonHmacResponseKeys',
- 'config.passthroughRequestHeaders',
- 'config.allowedResponseHeaders',
- 'version',
- ]);
- });
-
- test('it returns correct fields for KV v2', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'kv',
- version: '2',
- });
-
- assert.deepEqual(model.get('formFields'), [
- 'type',
- 'path',
- 'description',
- 'accessor',
- 'local',
- 'sealWrap',
- 'config.defaultLeaseTtl',
- 'config.maxLeaseTtl',
- 'config.allowedManagedKeys',
- 'config.auditNonHmacRequestKeys',
- 'config.auditNonHmacResponseKeys',
- 'config.passthroughRequestHeaders',
- 'config.allowedResponseHeaders',
- 'version',
- 'casRequired',
- 'deleteVersionAfter',
- 'maxVersions',
- ]);
- });
-
- test('it returns correct fields for keymgmt', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'keymgmt',
- });
-
- assert.deepEqual(model.get('formFields'), [
- 'type',
- 'path',
- 'description',
- 'accessor',
- 'local',
- 'sealWrap',
- 'config.allowedManagedKeys',
- 'config.auditNonHmacRequestKeys',
- 'config.auditNonHmacResponseKeys',
- 'config.passthroughRequestHeaders',
- 'config.allowedResponseHeaders',
- ]);
- });
- });
-
- module('formFieldGroups', function () {
- test('returns correct values by default', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'aws',
- });
-
- assert.deepEqual(model.get('formFieldGroups'), [
- { default: ['path'] },
- {
- 'Method Options': [
- 'description',
- 'config.listingVisibility',
- 'local',
- 'sealWrap',
- 'config.defaultLeaseTtl',
- 'config.maxLeaseTtl',
- 'config.allowedManagedKeys',
- 'config.auditNonHmacRequestKeys',
- 'config.auditNonHmacResponseKeys',
- 'config.passthroughRequestHeaders',
- 'config.allowedResponseHeaders',
- ],
- },
- ]);
- });
- test('returns correct values for KV', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'kv',
- });
-
- assert.deepEqual(model.get('formFieldGroups'), [
- { default: ['path', 'maxVersions', 'casRequired', 'deleteVersionAfter'] },
- {
- 'Method Options': [
- 'version',
- 'description',
- 'config.listingVisibility',
- 'local',
- 'sealWrap',
- 'config.defaultLeaseTtl',
- 'config.maxLeaseTtl',
- 'config.allowedManagedKeys',
- 'config.auditNonHmacRequestKeys',
- 'config.auditNonHmacResponseKeys',
- 'config.passthroughRequestHeaders',
- 'config.allowedResponseHeaders',
- ],
- },
- ]);
- });
-
- test('returns correct values for generic', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'generic',
- });
-
- assert.deepEqual(model.get('formFieldGroups'), [
- { default: ['path'] },
- {
- 'Method Options': [
- 'version',
- 'description',
- 'config.listingVisibility',
- 'local',
- 'sealWrap',
- 'config.defaultLeaseTtl',
- 'config.maxLeaseTtl',
- 'config.allowedManagedKeys',
- 'config.auditNonHmacRequestKeys',
- 'config.auditNonHmacResponseKeys',
- 'config.passthroughRequestHeaders',
- 'config.allowedResponseHeaders',
- ],
- },
- ]);
- });
-
- test('returns correct values for database', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'database',
- });
-
- assert.deepEqual(model.get('formFieldGroups'), [
- { default: ['path', 'config.defaultLeaseTtl', 'config.maxLeaseTtl'] },
- {
- 'Method Options': [
- 'description',
- 'config.listingVisibility',
- 'local',
- 'sealWrap',
- 'config.allowedManagedKeys',
- 'config.auditNonHmacRequestKeys',
- 'config.auditNonHmacResponseKeys',
- 'config.passthroughRequestHeaders',
- 'config.allowedResponseHeaders',
- ],
- },
- ]);
- });
-
- test('returns correct values for pki', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'pki',
- });
-
- assert.deepEqual(model.get('formFieldGroups'), [
- { default: ['path', 'config.defaultLeaseTtl', 'config.maxLeaseTtl', 'config.allowedManagedKeys'] },
- {
- 'Method Options': [
- 'description',
- 'config.listingVisibility',
- 'local',
- 'sealWrap',
- 'config.auditNonHmacRequestKeys',
- 'config.auditNonHmacResponseKeys',
- 'config.passthroughRequestHeaders',
- 'config.allowedResponseHeaders',
- ],
- },
- ]);
- });
-
- test('returns correct values for keymgmt', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'keymgmt',
- });
-
- assert.deepEqual(model.get('formFieldGroups'), [
- { default: ['path'] },
- {
- 'Method Options': [
- 'description',
- 'config.listingVisibility',
- 'local',
- 'sealWrap',
- 'config.allowedManagedKeys',
- 'config.auditNonHmacRequestKeys',
- 'config.auditNonHmacResponseKeys',
- 'config.passthroughRequestHeaders',
- 'config.allowedResponseHeaders',
- ],
- },
- ]);
- });
- });
-
- module('engineType', function () {
- test('strips leading ns_ from type', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- // eg. ns_cubbyhole, ns_identity, ns_system
- type: 'ns_identity',
- });
- assert.strictEqual(model.engineType, 'identity');
- });
- test('returns type by default', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'zebras',
- });
- assert.strictEqual(model.engineType, 'zebras');
- });
- });
-
- module('icon', function () {
- test('returns secrets if no engineType', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: '',
- });
- assert.strictEqual(model.icon, 'secrets');
- });
- test('returns secrets if kmip', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'kmip',
- });
- assert.strictEqual(model.icon, 'secrets');
- });
- test('returns key if keymgmt', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'keymgmt',
- });
- assert.strictEqual(model.icon, 'key');
- });
- test('returns engineType by default', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'ducks',
- });
- assert.strictEqual(model.icon, 'ducks');
- });
- });
-
- module('shouldIncludeInList', function () {
- test('returns false if excludeList includes type', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'system',
- });
- assert.false(model.shouldIncludeInList);
- });
- test('returns true if excludeList does not include type', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'hippos',
- });
- assert.true(model.shouldIncludeInList);
- });
- });
-
- module('localDisplay', function () {
- test('returns local if local', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- local: true,
- });
- assert.strictEqual(model.localDisplay, 'local');
- });
- test('returns replicated if !local', function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- local: false,
- });
- assert.strictEqual(model.localDisplay, 'replicated');
- });
- });
-
- module('saveCA', function () {
- test('does not call endpoint if type != ssh', async function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {
- type: 'not-ssh',
- });
- const saveSpy = sinon.spy(model, 'save');
- await model.saveCA({});
- assert.ok(saveSpy.notCalled, 'save not called');
- });
- test('calls save with correct params', async function (assert) {
- assert.expect(4);
- const model = this.store.createRecord('secret-engine', {
- type: 'ssh',
- privateKey: 'private-key',
- publicKey: 'public-key',
- generateSigningKey: true,
- });
- const saveStub = sinon.stub(model, 'save').callsFake((params) => {
- assert.deepEqual(
- params,
- {
- adapterOptions: {
- options: {},
- apiPath: 'config/ca',
- attrsToSend: ['privateKey', 'publicKey', 'generateSigningKey'],
- },
- },
- 'send correct params to save'
- );
- return;
- });
-
- await model.saveCA({});
- assert.strictEqual(model.privateKey, 'private-key', 'value exists before save');
- assert.strictEqual(model.publicKey, 'public-key', 'value exists before save');
- assert.true(model.generateSigningKey, 'value true before save');
-
- saveStub.restore();
- });
- test('sets properties when isDelete', async function (assert) {
- assert.expect(7);
- const model = this.store.createRecord('secret-engine', {
- type: 'ssh',
- privateKey: 'private-key',
- publicKey: 'public-key',
- generateSigningKey: true,
- });
- const saveStub = sinon.stub(model, 'save').callsFake((params) => {
- assert.deepEqual(
- params,
- {
- adapterOptions: {
- options: { isDelete: true },
- apiPath: 'config/ca',
- attrsToSend: ['privateKey', 'publicKey', 'generateSigningKey'],
- },
- },
- 'send correct params to save'
- );
- return;
- });
- assert.strictEqual(model.privateKey, 'private-key', 'value exists before save');
- assert.strictEqual(model.publicKey, 'public-key', 'value exists before save');
- assert.true(model.generateSigningKey, 'value true before save');
-
- await model.saveCA({ isDelete: true });
- assert.strictEqual(model.privateKey, null, 'value null after save');
- assert.strictEqual(model.publicKey, null, 'value null after save');
- assert.false(model.generateSigningKey, 'value false after save');
- saveStub.restore();
- });
- });
-
- module('saveZeroAddressConfig', function () {
- test('calls save with correct params', async function (assert) {
- assert.expect(1);
- const model = this.store.createRecord('secret-engine', {});
- const saveStub = sinon.stub(model, 'save').callsFake((params) => {
- assert.deepEqual(
- params,
- {
- adapterOptions: {
- adapterMethod: 'saveZeroAddressConfig',
- },
- },
- 'send correct params to save'
- );
- return;
- });
- await model.saveZeroAddressConfig();
- saveStub.restore();
- });
- });
-});
diff --git a/ui/tests/unit/models/secret-v2-version-test.js b/ui/tests/unit/models/secret-v2-version-test.js
deleted file mode 100644
index 6e896a142..000000000
--- a/ui/tests/unit/models/secret-v2-version-test.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { run } from '@ember/runloop';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Model | secret-v2-version', function (hooks) {
- setupTest(hooks);
-
- test('deleted is true for a past deletionTime', function (assert) {
- assert.expect(1);
- let model;
- run(() => {
- model = run(() =>
- this.owner.lookup('service:store').createRecord('secret-v2-version', {
- deletionTime: '2000-10-14T00:00:00.000000Z',
- })
- );
- assert.true(model.get('deleted'));
- });
- });
-
- test('deleted is false for a future deletionTime', function (assert) {
- assert.expect(1);
- let model;
- run(() => {
- model = run(() =>
- this.owner.lookup('service:store').createRecord('secret-v2-version', {
- deletionTime: '2999-10-14T00:00:00.000000Z',
- })
- );
- assert.false(model.get('deleted'));
- });
- });
-});
diff --git a/ui/tests/unit/models/transit-key-test.js b/ui/tests/unit/models/transit-key-test.js
deleted file mode 100644
index 223403982..000000000
--- a/ui/tests/unit/models/transit-key-test.js
+++ /dev/null
@@ -1,76 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { run } from '@ember/runloop';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Model | transit key', function (hooks) {
- setupTest(hooks);
-
- test('it exists', function (assert) {
- const model = run(() => this.owner.lookup('service:store').createRecord('transit-key'));
- assert.ok(!!model);
- });
-
- test('supported actions', function (assert) {
- const model = run(() =>
- this.owner.lookup('service:store').createRecord('transit-key', {
- supportsEncryption: true,
- supportsDecryption: true,
- supportsSigning: false,
- })
- );
-
- const supportedActions = model.get('supportedActions').map((k) => k.name);
- assert.deepEqual(['encrypt', 'decrypt', 'datakey', 'rewrap', 'hmac', 'verify'], supportedActions);
- });
-
- test('encryption key versions', function (assert) {
- assert.expect(2);
- const done = assert.async();
- const model = run(() =>
- this.owner.lookup('service:store').createRecord('transit-key', {
- keys: { 1: 123, 2: 456, 3: 789, 4: 101112, 5: 131415 },
- minDecryptionVersion: 1,
- latestVersion: 5,
- })
- );
- assert.deepEqual([5, 4, 3, 2, 1], model.get('encryptionKeyVersions'), 'lists all available versions');
- run(() => {
- model.set('minDecryptionVersion', 3);
- assert.deepEqual(
- [5, 4, 3],
- model.get('encryptionKeyVersions'),
- 'adjusts to a change in minDecryptionVersion'
- );
- done();
- });
- });
-
- test('keys for encryption', function (assert) {
- assert.expect(2);
- const done = assert.async();
- const model = run(() =>
- this.owner.lookup('service:store').createRecord('transit-key', {
- keys: { 1: 123, 2: 456, 3: 789, 4: 101112, 5: 131415 },
- minDecryptionVersion: 1,
- latestVersion: 5,
- })
- );
-
- assert.deepEqual(
- [5, 4, 3, 2, 1],
- model.get('keysForEncryption'),
- 'lists all available versions when no min is set'
- );
-
- run(() => {
- model.set('minEncryptionVersion', 4);
- assert.deepEqual([5, 4], model.get('keysForEncryption'), 'calculates using minEncryptionVersion');
- done();
- });
- });
-});
diff --git a/ui/tests/unit/routes/vault/cluster/oidc-callback-test.js b/ui/tests/unit/routes/vault/cluster/oidc-callback-test.js
deleted file mode 100644
index 99ce56d6a..000000000
--- a/ui/tests/unit/routes/vault/cluster/oidc-callback-test.js
+++ /dev/null
@@ -1,184 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import sinon from 'sinon';
-import { getParamsForCallback } from 'vault/routes/vault/cluster/oidc-callback';
-
-module('Unit | Route | vault/cluster/oidc-callback', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.originalOpener = window.opener;
- window.opener = {
- postMessage: () => {},
- };
- this.route = this.owner.lookup('route:vault/cluster/oidc-callback');
- this.windowStub = sinon.stub(window.opener, 'postMessage');
- this.state = 'st_yOarDguU848w5YZuotLs';
- this.path = 'oidc';
- this.code = 'lTazRXEwKfyGKBUCo5TyLJzdIt39YniBJOXPABiRMkL0T';
- this.route.paramsFor = (path) => {
- if (path === 'vault.cluster') return { namespaceQueryParam: '' };
- return {
- auth_path: this.path,
- code: this.code,
- };
- };
- this.callbackUrlQueryParams = (stateParam) => {
- switch (stateParam) {
- case '':
- window.history.pushState({}, '');
- break;
- case 'stateless':
- window.history.pushState({}, '', '?' + `code=${this.code}`);
- break;
- default:
- window.history.pushState({}, '', '?' + `code=${this.code}&state=${stateParam}`);
- break;
- }
- };
- });
-
- hooks.afterEach(function () {
- this.windowStub.restore();
- window.opener = this.originalOpener;
- this.callbackUrlQueryParams('');
- });
-
- test('it calls route', function (assert) {
- assert.ok(this.route);
- });
-
- module('getParamsForCallback helper fn', function () {
- test('it parses params correctly with regular inputs and no namespace', function (assert) {
- const qp = {
- state: 'my-state',
- code: 'my-code',
- path: 'oidc-path',
- };
- const searchString = `?code=my-code&state=my-state`;
- const results = getParamsForCallback(qp, searchString);
- assert.deepEqual(results, { source: 'oidc-callback', ...qp });
- });
-
- test('it parses params correctly regular inputs and namespace param', function (assert) {
- const params = {
- state: 'my-state',
- code: 'my-code',
- path: 'oidc-path',
- namespace: 'my-namespace',
- };
- const results = getParamsForCallback(params, '?code=my-code&state=my-state&namespace=my-namespace');
- assert.deepEqual(results, { source: 'oidc-callback', ...params });
- });
-
- /*
- If authenticating to a namespace, most SSO providers return a callback url
- with a 'state' query param that includes a URI encoded namespace, example:
- '?code=BZBDVPMz0By2JTqulEMWX5-6rflW3A20UAusJYHEeFygJ&state=sst_yOarDguU848w5YZuotLs%2Cns%3Dadmin'
-
- Active Directory Federation Service (AD FS), instead, decodes the namespace portion:
- '?code=BZBDVPMz0By2JTqulEMWX5-6rflW3A20UAusJYHEeFygJ&state=st_yOarDguU848w5YZuotLs,ns=admin'
-
- 'ns' isn't recognized as a separate param because there is no ampersand, so using this.paramsFor() returns
- a namespace-less state and authentication fails
- { state: 'st_yOarDguU848w5YZuotLs,ns' }
- */
- test('it parses params correctly with regular inputs and namespace in state (unencoded)', function (assert) {
- const searchString = '?code=my-code&state=my-state,ns=foo/bar';
- const params = {
- state: 'my-state,ns', // Ember parses the QP incorrectly if unencoded
- code: 'my-code',
- path: 'oidc-path',
- };
- const results = getParamsForCallback(params, searchString);
- assert.deepEqual(results, {
- source: 'oidc-callback',
- ...params,
- state: 'my-state',
- namespace: 'foo/bar',
- });
- });
-
- test('it parses params correctly with regular inputs and namespace in state (encoded)', function (assert) {
- const qp = {
- state: 'my-state,ns=foo/bar', // Ember parses the QP correctly when encoded
- code: 'my-code',
- path: 'oidc-path',
- };
- const searchString = `?code=my-code&state=${encodeURIComponent(qp.state)}`;
- const results = getParamsForCallback(qp, searchString);
- assert.deepEqual(results, { source: 'oidc-callback', ...qp, state: 'my-state', namespace: 'foo/bar' });
- });
-
- test('namespace in state takes precedence over namespace in route (encoded)', function (assert) {
- const qp = {
- state: 'my-state,ns=foo/bar',
- code: 'my-code',
- path: 'oidc-path',
- namespace: 'other/ns',
- };
- const searchString = `?code=my-code&state=${encodeURIComponent(
- qp.state
- )}&namespace=${encodeURIComponent(qp.namespace)}`;
- const results = getParamsForCallback(qp, searchString);
- assert.deepEqual(results, {
- source: 'oidc-callback',
- ...qp,
- state: 'my-state',
- namespace: 'foo/bar',
- });
- });
-
- test('namespace in state takes precedence over namespace in route (unencoded)', function (assert) {
- const qp = {
- state: 'my-state,ns',
- code: 'my-code',
- path: 'oidc-path',
- namespace: 'other/ns',
- };
- const searchString = `?code=${qp.code}&state=my-state,ns=foo/bar&namespace=${qp.namespace}`;
- const results = getParamsForCallback(qp, searchString);
- assert.deepEqual(results, {
- source: 'oidc-callback',
- ...qp,
- state: 'my-state',
- namespace: 'foo/bar',
- });
- });
-
- test('it parses params correctly when window.location.search is empty (HCP scenario)', function (assert) {
- const params = {
- state: 'some-state,ns=admin/child-ns',
- code: 'my-code',
- namespace: 'admin',
- path: 'oidc-path',
- };
- const results = getParamsForCallback(params, '');
- assert.deepEqual(results, {
- source: 'oidc-callback',
- code: 'my-code',
- path: 'oidc-path',
- state: 'some-state',
- namespace: 'admin/child-ns',
- });
- });
-
- test('it defaults to reasonable values if query params are missing', function (assert) {
- const params = {
- path: 'oidc-path',
- };
- const results = getParamsForCallback(params, '');
- assert.deepEqual(results, {
- source: 'oidc-callback',
- code: '',
- path: 'oidc-path',
- state: '',
- });
- });
- });
-});
diff --git a/ui/tests/unit/routes/vault/cluster/redirect-test.js b/ui/tests/unit/routes/vault/cluster/redirect-test.js
deleted file mode 100644
index 96f1b7010..000000000
--- a/ui/tests/unit/routes/vault/cluster/redirect-test.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import sinon from 'sinon';
-
-module('Unit | Route | vault/cluster/redirect', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.router = this.owner.lookup('service:router');
- this.route = this.owner.lookup('route:vault/cluster/redirect');
- this.auth = this.owner.lookup('service:auth');
- this.originalTransition = this.router.replaceWith;
- this.router.replaceWith = sinon.stub().returns({
- followRedirects: function () {
- return {
- then: function (callback) {
- callback();
- },
- };
- },
- });
- this.setCurrentToken = (token) => {
- this.auth.setCluster(token);
- if (token) {
- this.auth.setTokenData(token, { token: 'foo' });
- this.auth.tokens = [token];
- }
- };
- });
-
- hooks.afterEach(function () {
- this.router.replaceWith = this.originalTransition;
- });
-
- test('it calls route', function (assert) {
- assert.ok(this.route);
- });
-
- test('it redirects to auth when unauthenticated', function (assert) {
- const originalToken = this.auth.currentToken;
- this.setCurrentToken(null);
-
- this.route.beforeModel({
- to: { queryParams: { redirect_to: 'vault/cluster/tools', namespace: 'admin' } },
- });
-
- assert.true(
- this.router.replaceWith.calledWithExactly('vault.cluster.auth', {
- queryParams: { namespace: 'admin' },
- }),
- 'transitions to auth when not authenticated'
- );
- this.setCurrentToken(originalToken);
- });
-
- test('it redirects to cluster when authenticated without redirect param', function (assert) {
- const originalToken = this.auth.currentToken;
- this.setCurrentToken('s.xxxxxxxxx');
-
- this.route.beforeModel({ to: { queryParams: { foo: 'bar' } } });
- assert.true(
- this.router.replaceWith.calledWithExactly('vault.cluster', { queryParams: { foo: 'bar' } }),
- 'transitions to cluster when authenticated but no redirect param'
- );
- this.setCurrentToken(originalToken);
- });
-
- test('it redirects to desired path when authenticated with redirect param', function (assert) {
- const originalToken = this.auth.currentToken;
- this.setCurrentToken('s.xxxxxxxxx');
-
- this.route.beforeModel({
- to: {
- queryParams: { redirect_to: 'vault/cluster/tools?namespace=admin', namespace: 'ns1', foo: 'bar' },
- },
- });
-
- assert.true(
- this.router.replaceWith.calledWithExactly('vault/cluster/tools?namespace=admin'),
- 'transitions to redirect_to path when authenticated and removes other params'
- );
- this.setCurrentToken(originalToken);
- });
-});
diff --git a/ui/tests/unit/serializers/cluster-test.js b/ui/tests/unit/serializers/cluster-test.js
deleted file mode 100644
index b4e038895..000000000
--- a/ui/tests/unit/serializers/cluster-test.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { validate } from 'uuid';
-
-module('Unit | Serializer | cluster', function (hooks) {
- setupTest(hooks);
-
- test('it should generate ids for replication attributes', async function (assert) {
- const serializer = this.owner.lookup('serializer:cluster');
- const data = {};
- serializer.setReplicationId(data);
- assert.true(validate(data.id), 'UUID is generated for replication attribute');
- });
-});
diff --git a/ui/tests/unit/serializers/database/connection-test.js b/ui/tests/unit/serializers/database/connection-test.js
deleted file mode 100644
index 13b73871a..000000000
--- a/ui/tests/unit/serializers/database/connection-test.js
+++ /dev/null
@@ -1,101 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { v4 as uuidv4 } from 'uuid';
-
-module('Unit | Serializer | database/connection', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.uid = uuidv4();
- this.store = this.owner.lookup('service:store');
- });
- test('it should serialize only keys that are valid for the database type (elasticsearch)', function (assert) {
- const backend = `db-serializer-test-${this.uid}`;
- const name = `elastic-test-${this.uid}`;
- const record = this.store.createRecord('database/connection', {
- plugin_name: 'elasticsearch-database-plugin',
- backend,
- name,
- allowed_roles: ['readonly'],
- connection_url: 'http://localhost:9200',
- url: 'http://localhost:9200',
- username: 'elastic',
- password: 'changeme',
- tls_ca: 'some-value',
- ca_cert: undefined, // does not send undefined values
- });
- const expectedResult = {
- plugin_name: 'elasticsearch-database-plugin',
- backend,
- name,
- verify_connection: true,
- allowed_roles: ['readonly'],
- url: 'http://localhost:9200',
- username: 'elastic',
- password: 'changeme',
- insecure: false,
- };
-
- const serializedRecord = record.serialize();
- assert.deepEqual(
- serializedRecord,
- expectedResult,
- 'invalid elasticsearch options were not added to the payload'
- );
- });
-
- test('it should normalize values for the database type (elasticsearch)', function (assert) {
- const serializer = this.owner.lookup('serializer:database/connection');
- const normalized = serializer.normalizeSecrets({
- request_id: 'request-id',
- lease_id: '',
- renewable: false,
- lease_duration: 0,
- data: {
- allowed_roles: ['readonly'],
- connection_details: {
- backend: 'database',
- insecure: false,
- url: 'https://localhost:9200',
- username: 'root',
- },
- password_policy: '',
- plugin_name: 'elasticsearch-database-plugin',
- plugin_version: '',
- root_credentials_rotate_statements: [],
- },
- wrap_info: null,
- warnings: null,
- auth: null,
- mount_type: 'database',
- backend: 'database',
- id: 'elastic-test',
- });
- const expectedResult = {
- allowed_roles: ['readonly'],
- backend: 'database',
- connection_details: {
- backend: 'database',
- insecure: false,
- url: 'https://localhost:9200',
- username: 'root',
- },
- id: 'elastic-test',
- insecure: false,
- name: 'elastic-test',
- password_policy: '',
- plugin_name: 'elasticsearch-database-plugin',
- plugin_version: '',
- root_credentials_rotate_statements: [],
- root_rotation_statements: [],
- url: 'https://localhost:9200',
- username: 'root',
- };
- assert.deepEqual(normalized, expectedResult, `Normalizes and flattens database response`);
- });
-});
diff --git a/ui/tests/unit/serializers/mfa-login-enforcement-test.js b/ui/tests/unit/serializers/mfa-login-enforcement-test.js
deleted file mode 100644
index dd10c172a..000000000
--- a/ui/tests/unit/serializers/mfa-login-enforcement-test.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Serializer | mfa-login-enforcement', function (hooks) {
- setupTest(hooks);
-
- test('it should transform property names for hasMany relationships', function (assert) {
- const serverData = {
- name: 'foo',
- mfa_method_ids: ['1'],
- auth_method_types: ['userpass'],
- auth_method_accessors: ['auth_approle_17a552c6'],
- identity_entity_ids: ['2', '3'],
- identity_group_ids: ['4', '5', '6'],
- };
- const tranformedData = {
- name: 'foo',
- mfa_methods: ['1'],
- auth_method_types: ['userpass'],
- auth_method_accessors: ['auth_approle_17a552c6'],
- identity_entities: ['2', '3'],
- identity_groups: ['4', '5', '6'],
- };
- const mutableData = { ...serverData };
- const serializer = this.owner.lookup('serializer:mfa-login-enforcement');
-
- serializer.transformHasManyKeys(mutableData, 'model');
- assert.deepEqual(mutableData, tranformedData, 'hasMany property names are transformed for model');
-
- serializer.transformHasManyKeys(mutableData, 'server');
- assert.deepEqual(mutableData, serverData, 'hasMany property names are transformed for server');
- });
-});
diff --git a/ui/tests/unit/serializers/pki/action-test.js b/ui/tests/unit/serializers/pki/action-test.js
deleted file mode 100644
index 6fa31c284..000000000
--- a/ui/tests/unit/serializers/pki/action-test.js
+++ /dev/null
@@ -1,179 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'vault/tests/helpers';
-import { rootPem } from 'vault/tests/helpers/pki/values';
-
-module('Unit | Serializer | pki/action', function (hooks) {
- setupTest(hooks);
-
- test('it exists', function (assert) {
- const store = this.owner.lookup('service:store');
- const serializer = store.serializerFor('pki/action');
-
- assert.ok(serializer);
- });
-
- module('actionType import', function (hooks) {
- hooks.beforeEach(function () {
- this.actionType = 'import';
- this.pemBundle = rootPem;
- });
-
- test('it serializes only valid params', function (assert) {
- const store = this.owner.lookup('service:store');
- const record = store.createRecord('pki/action', {
- pemBundle: this.pemBundle,
- issuerRef: 'do-not-send',
- keyType: 'do-not-send',
- });
- const expectedResult = {
- pem_bundle: this.pemBundle,
- };
-
- const serializedRecord = record.serialize(this.actionType);
- assert.deepEqual(
- serializedRecord,
- expectedResult,
- 'Serializes only parameters valid for import action'
- );
- });
- });
-
- module('actionType generate-root', function (hooks) {
- hooks.beforeEach(function () {
- this.actionType = 'generate-root';
- this.allKeyFields = {
- keyName: 'key name',
- keyType: 'rsa',
- keyBits: '0',
- keyRef: 'key ref',
- managedKeyName: 'managed name',
- managedKeyId: 'managed id',
- };
- this.withDefaults = {
- exclude_cn_from_sans: false,
- format: 'pem',
- max_path_length: -1,
- not_before_duration: '30s',
- private_key_format: 'der',
- };
- });
-
- test('it serializes only params with values', function (assert) {
- const store = this.owner.lookup('service:store');
- const record = store.createRecord('pki/action', {
- excludeCnFromSans: false,
- format: 'pem',
- maxPathLength: -1,
- notBeforeDuration: '30s',
- privateKeyFormat: 'der',
- type: 'external', // only used for endpoint in adapter
- customTtl: '40m', // UI-only value
- issuerName: 'my issuer',
- commonName: undefined,
- foo: 'bar',
- });
- const expectedResult = {
- ...this.withDefaults,
- key_bits: '0',
- key_ref: 'default',
- key_type: 'rsa',
- issuer_name: 'my issuer',
- };
-
- // without passing `actionType` it will not compare against an allowlist
- const serializedRecord = record.serialize();
- assert.deepEqual(serializedRecord, expectedResult);
- });
-
- test('it serializes only valid params for type = external', function (assert) {
- const store = this.owner.lookup('service:store');
- const record = store.createRecord('pki/action', {
- ...this.allKeyFields,
- type: 'external',
- customTtl: '40m',
- issuerName: 'my issuer',
- commonName: 'my common name',
- });
- const expectedResult = {
- ...this.withDefaults,
- issuer_name: 'my issuer',
- common_name: 'my common name',
- key_name: 'key name',
- key_type: 'rsa',
- key_bits: '0',
- };
-
- const serializedRecord = record.serialize(this.actionType);
- assert.deepEqual(serializedRecord, expectedResult);
- });
-
- test('it serializes only valid params for type = internal', function (assert) {
- const store = this.owner.lookup('service:store');
- const record = store.createRecord('pki/action', {
- ...this.allKeyFields,
- type: 'internal',
- customTtl: '40m',
- issuerName: 'my issuer',
- commonName: 'my common name',
- });
- const expectedResult = {
- ...this.withDefaults,
- issuer_name: 'my issuer',
- common_name: 'my common name',
- key_name: 'key name',
- key_type: 'rsa',
- key_bits: '0',
- };
-
- const serializedRecord = record.serialize(this.actionType);
- assert.deepEqual(serializedRecord, expectedResult);
- });
-
- test('it serializes only valid params for type = existing', function (assert) {
- const store = this.owner.lookup('service:store');
- const record = store.createRecord('pki/action', {
- ...this.allKeyFields,
- type: 'existing',
- customTtl: '40m',
- issuerName: 'my issuer',
- commonName: 'my common name',
- });
- const expectedResult = {
- ...this.withDefaults,
- issuer_name: 'my issuer',
- common_name: 'my common name',
- key_ref: 'key ref',
- };
-
- const serializedRecord = record.serialize(this.actionType);
- assert.deepEqual(serializedRecord, expectedResult);
- });
-
- test('it serializes only valid params for type = kms', function (assert) {
- const store = this.owner.lookup('service:store');
- const record = store.createRecord('pki/action', {
- ...this.allKeyFields,
- type: 'kms',
- customTtl: '40m',
- issuerName: 'my issuer',
- commonName: 'my common name',
- });
- const expectedResult = {
- ...this.withDefaults,
- issuer_name: 'my issuer',
- common_name: 'my common name',
- key_name: 'key name',
- managed_key_name: 'managed name',
- managed_key_id: 'managed id',
- };
-
- const serializedRecord = record.serialize(this.actionType);
- assert.deepEqual(serializedRecord, expectedResult);
- });
- });
-});
diff --git a/ui/tests/unit/serializers/policy-test.js b/ui/tests/unit/serializers/policy-test.js
deleted file mode 100644
index a6e172023..000000000
--- a/ui/tests/unit/serializers/policy-test.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Serializer | policy', function (hooks) {
- setupTest(hooks);
-
- const POLICY_LIST_RESPONSE = {
- keys: ['default', 'root'],
- policies: ['default', 'root'],
- request_id: '3a6a3d67-dc3b-a086-2fc7-902bdc4dec3a',
- lease_id: '',
- renewable: false,
- lease_duration: 0,
- data: {
- keys: ['default', 'root'],
- policies: ['default', 'root'],
- },
- wrap_info: null,
- warnings: null,
- auth: null,
- };
-
- const EMBER_DATA_EXPECTS_FOR_POLICY_LIST = [{ name: 'default' }, { name: 'root' }];
-
- const POLICY_SHOW_RESPONSE = {
- name: 'default',
- rules:
- '\n# Allow tokens to look up their own properties\npath "auth/token/lookup-self" {\n capabilities = ["read"]\n}\n\n# Allow tokens to renew themselves\npath "auth/token/renew-self" {\n capabilities = ["update"]\n}\n\n# Allow tokens to revoke themselves\npath "auth/token/revoke-self" {\n capabilities = ["update"]\n}\n\n# Allow a token to look up its own capabilities on a path\npath "sys/capabilities-self" {\n capabilities = ["update"]\n}\n\n# Allow a token to renew a lease via lease_id in the request body\npath "sys/renew" {\n capabilities = ["update"]\n}\n\n# Allow a token to manage its own cubbyhole\npath "cubbyhole/*" {\n capabilities = ["create", "read", "update", "delete", "list"]\n}\n\n# Allow a token to list its cubbyhole (not covered by the splat above)\npath "cubbyhole" {\n capabilities = ["list"]\n}\n\n# Allow a token to wrap arbitrary values in a response-wrapping token\npath "sys/wrapping/wrap" {\n capabilities = ["update"]\n}\n\n# Allow a token to look up the creation time and TTL of a given\n# response-wrapping token\npath "sys/wrapping/lookup" {\n capabilities = ["update"]\n}\n\n# Allow a token to unwrap a response-wrapping token. This is a convenience to\n# avoid client token swapping since this is also part of the response wrapping\n# policy.\npath "sys/wrapping/unwrap" {\n capabilities = ["update"]\n}\n',
- request_id: '890eabf8-d418-07af-f978-928d328a7e64',
- lease_id: '',
- renewable: false,
- lease_duration: 0,
- data: {
- name: 'default',
- rules:
- '\n# Allow tokens to look up their own properties\npath "auth/token/lookup-self" {\n capabilities = ["read"]\n}\n\n# Allow tokens to renew themselves\npath "auth/token/renew-self" {\n capabilities = ["update"]\n}\n\n# Allow tokens to revoke themselves\npath "auth/token/revoke-self" {\n capabilities = ["update"]\n}\n\n# Allow a token to look up its own capabilities on a path\npath "sys/capabilities-self" {\n capabilities = ["update"]\n}\n\n# Allow a token to renew a lease via lease_id in the request body\npath "sys/renew" {\n capabilities = ["update"]\n}\n\n# Allow a token to manage its own cubbyhole\npath "cubbyhole/*" {\n capabilities = ["create", "read", "update", "delete", "list"]\n}\n\n# Allow a token to list its cubbyhole (not covered by the splat above)\npath "cubbyhole" {\n capabilities = ["list"]\n}\n\n# Allow a token to wrap arbitrary values in a response-wrapping token\npath "sys/wrapping/wrap" {\n capabilities = ["update"]\n}\n\n# Allow a token to look up the creation time and TTL of a given\n# response-wrapping token\npath "sys/wrapping/lookup" {\n capabilities = ["update"]\n}\n\n# Allow a token to unwrap a response-wrapping token. This is a convenience to\n# avoid client token swapping since this is also part of the response wrapping\n# policy.\npath "sys/wrapping/unwrap" {\n capabilities = ["update"]\n}\n',
- },
- wrap_info: null,
- warnings: null,
- auth: null,
- };
-
- const EMBER_DATA_EXPECTS_FOR_POLICY_SHOW = {
- name: 'default',
- rules:
- '\n# Allow tokens to look up their own properties\npath "auth/token/lookup-self" {\n capabilities = ["read"]\n}\n\n# Allow tokens to renew themselves\npath "auth/token/renew-self" {\n capabilities = ["update"]\n}\n\n# Allow tokens to revoke themselves\npath "auth/token/revoke-self" {\n capabilities = ["update"]\n}\n\n# Allow a token to look up its own capabilities on a path\npath "sys/capabilities-self" {\n capabilities = ["update"]\n}\n\n# Allow a token to renew a lease via lease_id in the request body\npath "sys/renew" {\n capabilities = ["update"]\n}\n\n# Allow a token to manage its own cubbyhole\npath "cubbyhole/*" {\n capabilities = ["create", "read", "update", "delete", "list"]\n}\n\n# Allow a token to list its cubbyhole (not covered by the splat above)\npath "cubbyhole" {\n capabilities = ["list"]\n}\n\n# Allow a token to wrap arbitrary values in a response-wrapping token\npath "sys/wrapping/wrap" {\n capabilities = ["update"]\n}\n\n# Allow a token to look up the creation time and TTL of a given\n# response-wrapping token\npath "sys/wrapping/lookup" {\n capabilities = ["update"]\n}\n\n# Allow a token to unwrap a response-wrapping token. This is a convenience to\n# avoid client token swapping since this is also part of the response wrapping\n# policy.\npath "sys/wrapping/unwrap" {\n capabilities = ["update"]\n}\n',
- };
-
- test('it transforms a list request payload', function (assert) {
- const serializer = this.owner.lookup('serializer:policy');
-
- const transformedPayload = serializer.normalizePolicies(POLICY_LIST_RESPONSE);
-
- assert.deepEqual(
- transformedPayload,
- EMBER_DATA_EXPECTS_FOR_POLICY_LIST,
- 'transformed payload matches the expected payload'
- );
- });
-
- test('it transforms another list request payload', function (assert) {
- const serializer = this.owner.lookup('serializer:policy');
-
- const transformedPayload = serializer.normalizePolicies(POLICY_SHOW_RESPONSE);
-
- assert.deepEqual(
- transformedPayload,
- EMBER_DATA_EXPECTS_FOR_POLICY_SHOW,
- 'transformed payload matches the expected payload'
- );
- });
-});
diff --git a/ui/tests/unit/serializers/transit-key-test.js b/ui/tests/unit/serializers/transit-key-test.js
deleted file mode 100644
index 2812373ac..000000000
--- a/ui/tests/unit/serializers/transit-key-test.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-const CHACHA = {
- request_id: 'a5695685-584c-6b25-fada-35304d3d583d',
- lease_id: '',
- renewable: false,
- lease_duration: 0,
- data: {
- allow_plaintext_backup: false,
- deletion_allowed: false,
- derived: false,
- exportable: false,
- keys: {
- 1: 1559598610,
- },
- latest_version: 1,
- min_available_version: 0,
- min_decryption_version: 1,
- min_encryption_version: 0,
- name: 'anewone',
- supports_decryption: true,
- supports_derivation: true,
- supports_encryption: true,
- supports_signing: false,
- type: 'chacha20-poly1305',
- },
- wrap_info: null,
- warnings: null,
- auth: null,
- backend: 'its-a-transit',
-};
-
-const AES = {
- request_id: '90c327a8-9a68-6fab-13a1-f51b68cb24d7',
- lease_id: '',
- renewable: false,
- lease_duration: 0,
- data: {
- allow_plaintext_backup: false,
- deletion_allowed: false,
- derived: false,
- exportable: false,
- keys: {
- 1: 1559577523,
- },
- latest_version: 1,
- min_available_version: 0,
- min_decryption_version: 1,
- min_encryption_version: 0,
- name: 'new',
- supports_decryption: true,
- supports_derivation: true,
- supports_encryption: true,
- supports_signing: false,
- type: 'aes256-gcm96',
- },
- wrap_info: null,
- warnings: null,
- auth: null,
- backend: 'its-a-transit',
-};
-
-module('Unit | Serializer | transit-key', function (hooks) {
- setupTest(hooks);
- test('it expands the timestamp for aes and chacha-poly keys', function (assert) {
- const serializer = this.owner.lookup('serializer:transit-key');
- const aesExpected = AES.data.keys[1] * 1000;
- const chachaExpected = CHACHA.data.keys[1] * 1000;
- const aesData = serializer.normalizeSecrets({ ...AES });
- assert.strictEqual(aesData.firstObject.keys[1], aesExpected, 'converts seconds to millis for aes keys');
-
- const chachaData = serializer.normalizeSecrets({ ...CHACHA });
- assert.strictEqual(
- chachaData.firstObject.keys[1],
- chachaExpected,
- 'converts seconds to millis for chacha keys'
- );
- });
-
- test('it includes backend from the payload on the normalized data', function (assert) {
- const serializer = this.owner.lookup('serializer:transit-key');
- const data = serializer.normalizeSecrets({ ...AES });
- assert.strictEqual(
- data.firstObject.backend,
- 'its-a-transit',
- 'pulls backend from the payload onto the data'
- );
- });
-});
diff --git a/ui/tests/unit/services/auth-test.js b/ui/tests/unit/services/auth-test.js
deleted file mode 100644
index fb6f41dcc..000000000
--- a/ui/tests/unit/services/auth-test.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Service | auth', function (hooks) {
- setupTest(hooks);
-
- [
- ['#calculateExpiration w/ttl', { ttl: 30 }, 30],
- ['#calculateExpiration w/lease_duration', { ttl: 15 }, 15],
- ].forEach(([testName, response, ttlValue]) => {
- test(testName, function (assert) {
- const now = Date.now();
- const service = this.owner.factoryFor('service:auth').create({
- now() {
- return now;
- },
- });
-
- const resp = service.calculateExpiration(response);
-
- assert.strictEqual(resp.ttl, ttlValue, 'returns the ttl');
- assert.strictEqual(
- resp.tokenExpirationEpoch,
- now + ttlValue * 1e3,
- 'calculates expiration from ttl as epoch timestamp'
- );
- });
- });
-});
diff --git a/ui/tests/unit/services/console-test.js b/ui/tests/unit/services/console-test.js
deleted file mode 100644
index 0f88f35f0..000000000
--- a/ui/tests/unit/services/console-test.js
+++ /dev/null
@@ -1,159 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { sanitizePath, ensureTrailingSlash } from 'vault/services/console';
-import sinon from 'sinon';
-
-module('Unit | Service | console', function (hooks) {
- setupTest(hooks);
- hooks.beforeEach(function () {});
- hooks.afterEach(function () {});
-
- test('#sanitizePath', function (assert) {
- assert.strictEqual(
- sanitizePath(' /foo/bar/baz/ '),
- 'foo/bar/baz',
- 'removes spaces and slashs on either side'
- );
- assert.strictEqual(sanitizePath('//foo/bar/baz/'), 'foo/bar/baz', 'removes more than one slash');
- });
-
- test('#ensureTrailingSlash', function (assert) {
- assert.strictEqual(ensureTrailingSlash('foo/bar'), 'foo/bar/', 'adds trailing slash');
- assert.strictEqual(ensureTrailingSlash('baz/'), 'baz/', 'keeps trailing slash if there is one');
- });
-
- const testCases = [
- {
- method: 'read',
- args: ['/sys/health', {}],
- expectedURL: 'sys/health',
- expectedVerb: 'GET',
- expectedOptions: { data: undefined, wrapTTL: undefined },
- },
-
- {
- method: 'read',
- args: ['/secrets/foo/bar', {}, { wrapTTL: '30m' }],
- expectedURL: 'secrets/foo/bar',
- expectedVerb: 'GET',
- expectedOptions: { data: undefined, wrapTTL: '30m' },
- },
-
- {
- method: 'write',
- args: ['aws/roles/my-other-role', { arn: 'arn=arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess' }],
- expectedURL: 'aws/roles/my-other-role',
- expectedVerb: 'POST',
- expectedOptions: {
- data: { arn: 'arn=arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess' },
- wrapTTL: undefined,
- },
- },
-
- {
- method: 'list',
- args: ['secret/mounts', {}],
- expectedURL: 'secret/mounts/',
- expectedVerb: 'GET',
- expectedOptions: { data: { list: true }, wrapTTL: undefined },
- },
-
- {
- method: 'list',
- args: ['secret/mounts', {}, { wrapTTL: '1h' }],
- expectedURL: 'secret/mounts/',
- expectedVerb: 'GET',
- expectedOptions: { data: { list: true }, wrapTTL: '1h' },
- },
-
- {
- method: 'delete',
- args: ['secret/secrets/kv'],
- expectedURL: 'secret/secrets/kv',
- expectedVerb: 'DELETE',
- expectedOptions: { data: undefined, wrapTTL: undefined },
- },
- ];
-
- test('it reads, writes, lists, deletes', function (assert) {
- assert.expect(18);
- const ajax = sinon.stub();
- const uiConsole = this.owner.factoryFor('service:console').create({
- adapter() {
- return {
- buildURL(url) {
- return url;
- },
- ajax,
- };
- },
- });
-
- testCases.forEach((testCase) => {
- uiConsole[testCase.method](...testCase.args);
- const [url, verb, options] = ajax.lastCall.args;
- assert.strictEqual(url, testCase.expectedURL, `${testCase.method}: uses trimmed passed url`);
- assert.strictEqual(verb, testCase.expectedVerb, `${testCase.method}: uses the correct verb`);
- assert.deepEqual(options, testCase.expectedOptions, `${testCase.method}: uses the correct options`);
- });
- });
-
- const kvTestCases = [
- {
- method: 'kvGet',
- args: ['kv/foo'],
- expectedURL: 'kv/data/foo',
- expectedVerb: 'GET',
- expectedOptions: { data: undefined, wrapTTL: undefined },
- },
- {
- method: 'kvGet',
- args: ['kv/foo', {}, { metadata: true }],
- expectedURL: 'kv/metadata/foo',
- expectedVerb: 'GET',
- expectedOptions: { data: undefined, wrapTTL: undefined },
- },
- {
- method: 'kvGet',
- args: ['kv/foo', {}, { wrapTTL: '10m' }],
- expectedURL: 'kv/data/foo',
- expectedVerb: 'GET',
- expectedOptions: { data: undefined, wrapTTL: '10m' },
- },
- {
- method: 'kvGet',
- args: ['kv/foo', {}, { metadata: true, wrapTTL: '10m' }],
- expectedURL: 'kv/metadata/foo',
- expectedVerb: 'GET',
- expectedOptions: { data: undefined, wrapTTL: '10m' },
- },
- ];
-
- test('it reads kv secret and metadata', function (assert) {
- assert.expect(12);
- const ajax = sinon.stub();
- const uiConsole = this.owner.factoryFor('service:console').create({
- adapter() {
- return {
- buildURL(url) {
- return url;
- },
- ajax,
- };
- },
- });
-
- kvTestCases.forEach((testCase) => {
- uiConsole[testCase.method](...testCase.args);
- const [url, verb, options] = ajax.lastCall.args;
- assert.strictEqual(url, testCase.expectedURL, `${testCase.method}: uses correct url`);
- assert.strictEqual(verb, testCase.expectedVerb, `${testCase.method}: uses the correct verb`);
- assert.deepEqual(options, testCase.expectedOptions, `${testCase.method}: uses the correct options`);
- });
- });
-});
diff --git a/ui/tests/unit/services/control-group-test.js b/ui/tests/unit/services/control-group-test.js
deleted file mode 100644
index 6a4778467..000000000
--- a/ui/tests/unit/services/control-group-test.js
+++ /dev/null
@@ -1,255 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { set } from '@ember/object';
-import Service from '@ember/service';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import sinon from 'sinon';
-
-import { storageKey, CONTROL_GROUP_PREFIX, TOKEN_SEPARATOR } from 'vault/services/control-group';
-
-const versionStub = Service.extend();
-
-function storage() {
- return {
- items: {},
- getItem(key) {
- var item = this.items[key];
- return item && JSON.parse(item);
- },
-
- setItem(key, val) {
- return (this.items[key] = JSON.stringify(val));
- },
-
- removeItem(key) {
- delete this.items[key];
- },
-
- keys() {
- return Object.keys(this.items);
- },
- };
-}
-
-module('Unit | Service | control group', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.owner.register('service:version', versionStub);
- this.version = this.owner.lookup('service:version');
- this.router = this.owner.lookup('service:router');
- this.router.reopen({
- transitionTo: sinon.stub(),
- urlFor: sinon.stub().returns('/ui/vault/foo'),
- currentURL: '/vault/secrets/kv/show/foo',
- });
- });
-
- hooks.afterEach(function () {});
-
- const isOSS = (context) => set(context, 'version.isOSS', true);
- const isEnt = (context) => set(context, 'version.isOSS', false);
- const resolvesArgs = (assert, result, expectedArgs) => {
- return result.then((...args) => {
- return assert.deepEqual(args, expectedArgs, 'resolves with the passed args');
- });
- };
-
- [
- [
- 'it resolves isOSS:true, wrapTTL: true, response: has wrap_info',
- isOSS,
- [[{ one: 'two', three: 'four' }], { wrap_info: { token: 'foo', accessor: 'bar' } }, true],
- (assert, result) => resolvesArgs(assert, result, [{ one: 'two', three: 'four' }]),
- ],
- [
- 'it resolves isOSS:true, wrapTTL: false, response: has no wrap_info',
- isOSS,
- [[{ one: 'two', three: 'four' }], { wrap_info: null }, false],
- (assert, result) => resolvesArgs(assert, result, [{ one: 'two', three: 'four' }]),
- ],
- [
- 'it resolves isOSS: false and wrapTTL:true response: has wrap_info',
- isEnt,
- [[{ one: 'two', three: 'four' }], { wrap_info: { token: 'foo', accessor: 'bar' } }, true],
- (assert, result) => resolvesArgs(assert, result, [{ one: 'two', three: 'four' }]),
- ],
- [
- 'it resolves isOSS: false and wrapTTL:false response: has no wrap_info',
- isEnt,
- [[{ one: 'two', three: 'four' }], { wrap_info: null }, false],
- (assert, result) => resolvesArgs(assert, result, [{ one: 'two', three: 'four' }]),
- ],
- [
- 'it rejects isOSS: false, wrapTTL:false, response: has wrap_info',
- isEnt,
- [
- [{ one: 'two', three: 'four' }],
- { foo: 'bar', wrap_info: { token: 'secret', accessor: 'lookup' } },
- false,
- ],
- (assert, result) => {
- // ensure failure if we ever don't reject
- assert.expect(2);
-
- return result.then(
- () => {},
- (err) => {
- assert.strictEqual(err.token, 'secret');
- assert.strictEqual(err.accessor, 'lookup');
- }
- );
- },
- ],
- ].forEach(function ([name, setup, args, expectation]) {
- test(`checkForControlGroup: ${name}`, function (assert) {
- const assertCount = name === 'it rejects isOSS: false, wrapTTL:false, response: has wrap_info' ? 2 : 1;
- assert.expect(assertCount);
- if (setup) {
- setup(this);
- }
- const service = this.owner.lookup('service:control-group');
- const result = service.checkForControlGroup(...args);
- return expectation(assert, result);
- });
- });
-
- test(`handleError: transitions to accessor and stores control group token`, function (assert) {
- const error = {
- accessor: '12345',
- token: 'token',
- creation_path: 'kv/',
- creation_time: '2022-03-17T20:00:25.594Z',
- ttl: 400,
- };
- const expected = { ...error, uiParams: { url: '/vault/secrets/kv/show/foo' } };
- const service = this.owner.factoryFor('service:control-group').create({
- storeControlGroupToken: sinon.spy(),
- });
- service.handleError(error);
- assert.ok(service.storeControlGroupToken.calledWith(expected), 'calls storeControlGroupToken');
- assert.ok(
- this.router.transitionTo.calledWith('vault.cluster.access.control-group-accessor', '12345'),
- 'calls router transitionTo'
- );
- });
-
- test(`logFromError: returns correct content string`, function (assert) {
- const error = {
- accessor: '12345',
- token: 'token',
- creation_path: 'kv/',
- creation_time: '2022-03-17T20:00:25.594Z',
- ttl: 400,
- };
- const service = this.owner.factoryFor('service:control-group').create({
- storeControlGroupToken: sinon.spy(),
- });
- const contentString = service.logFromError(error);
- assert.ok(
- this.router.urlFor.calledWith('vault.cluster.access.control-group-accessor', '12345'),
- 'calls urlFor with accessor'
- );
- assert.ok(service.storeControlGroupToken.calledWith(error), 'calls storeControlGroupToken');
- assert.ok(contentString.content.includes('12345'), 'contains accessor');
- assert.ok(contentString.content.includes('kv/'), 'contains creation path');
- assert.ok(contentString.content.includes('token'), 'contains token');
- });
-
- test('storageKey', function (assert) {
- const accessor = '12345';
- const path = 'kv/foo/bar';
- const expectedKey = `${CONTROL_GROUP_PREFIX}${accessor}${TOKEN_SEPARATOR}${path}`;
- assert.strictEqual(storageKey(accessor, path), expectedKey, 'uses expected key');
- });
-
- test('keyFromAccessor', function (assert) {
- const store = storage();
- const accessor = '12345';
- const path = 'kv/foo/bar';
- const data = { foo: 'bar' };
- const expectedKey = `${CONTROL_GROUP_PREFIX}${accessor}${TOKEN_SEPARATOR}${path}`;
- const subject = this.owner.factoryFor('service:control-group').create({
- storage() {
- return store;
- },
- });
-
- store.setItem(expectedKey, data);
- store.setItem(`${CONTROL_GROUP_PREFIX}2345${TOKEN_SEPARATOR}${path}`, 'ok');
-
- assert.strictEqual(subject.keyFromAccessor(accessor), expectedKey, 'finds key given the accessor');
- assert.strictEqual(subject.keyFromAccessor('foo'), null, 'returns null if no key was found');
- });
-
- test('storeControlGroupToken', function (assert) {
- const store = storage();
- const subject = this.owner.factoryFor('service:control-group').create({
- storage() {
- return store;
- },
- });
- const info = {
- accessor: '12345',
- creation_path: 'foo/',
- creation_time: '2022-03-17T20:00:25.594Z',
- ttl: 300,
- };
- const key = `${CONTROL_GROUP_PREFIX}${info.accessor}${TOKEN_SEPARATOR}${info.creation_path}`;
-
- subject.storeControlGroupToken(info);
- assert.deepEqual(store.items[key], JSON.stringify(info), 'stores the whole info object');
- });
-
- test('deleteControlGroupToken', function (assert) {
- const store = storage();
- const subject = this.owner.factoryFor('service:control-group').create({
- storage() {
- return store;
- },
- });
- const accessor = 'foo';
- const path = 'kv/one';
-
- const expectedKey = `${CONTROL_GROUP_PREFIX}${accessor}${TOKEN_SEPARATOR}${path}`;
- store.setItem(expectedKey, { one: '2' });
- subject.deleteControlGroupToken(accessor);
- assert.strictEqual(Object.keys(store.items).length, 0, 'there are no keys stored in storage');
- });
-
- test('deleteTokens', function (assert) {
- const store = storage();
- const subject = this.owner.factoryFor('service:control-group').create({
- storage() {
- return store;
- },
- });
-
- const keyOne = `${CONTROL_GROUP_PREFIX}foo`;
- const keyTwo = `${CONTROL_GROUP_PREFIX}bar`;
- store.setItem(keyOne, { one: '2' });
- store.setItem(keyTwo, { two: '2' });
- store.setItem('value', 'one');
- assert.strictEqual(Object.keys(store.items).length, 3, 'stores 3 values');
- subject.deleteTokens();
- assert.strictEqual(Object.keys(store.items).length, 1, 'removes tokens with control group prefix');
- assert.strictEqual(store.getItem('value'), 'one', 'keeps the non-prefixed value');
- });
-
- test('wrapInfoForAccessor', function (assert) {
- const store = storage();
- const subject = this.owner.factoryFor('service:control-group').create({
- storage() {
- return store;
- },
- });
-
- const keyOne = `${CONTROL_GROUP_PREFIX}foo`;
- store.setItem(keyOne, { one: '2' });
- assert.deepEqual(subject.wrapInfoForAccessor('foo'), { one: '2' });
- });
-});
diff --git a/ui/tests/unit/services/feature-flag-test.js b/ui/tests/unit/services/feature-flag-test.js
deleted file mode 100644
index cf1b3fdf5..000000000
--- a/ui/tests/unit/services/feature-flag-test.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Service | feature-flag', function (hooks) {
- setupTest(hooks);
-
- test('it exists', function (assert) {
- const service = this.owner.lookup('service:feature-flag');
- assert.ok(service);
- });
-
- test('it returns the namespace root when flag is present', function (assert) {
- const service = this.owner.lookup('service:feature-flag');
- assert.strictEqual(service.managedNamespaceRoot, null, 'Managed namespace root is null by default');
- service.setFeatureFlags(['VAULT_CLOUD_ADMIN_NAMESPACE']);
- assert.strictEqual(service.managedNamespaceRoot, 'admin', 'Managed namespace is admin when flag present');
- service.setFeatureFlags(['SOMETHING_ELSE']);
- assert.strictEqual(
- service.managedNamespaceRoot,
- null,
- 'Flags were overwritten and root namespace is null again'
- );
- });
-});
diff --git a/ui/tests/unit/services/path-helper-test.js b/ui/tests/unit/services/path-helper-test.js
deleted file mode 100644
index 5612e9e3a..000000000
--- a/ui/tests/unit/services/path-helper-test.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-
-const openapiStub = {
- openapi: {
- components: {
- schemas: {
- UsersRequest: {
- type: 'object',
- properties: {
- password: {
- description: 'Password for the user',
- type: 'string',
- 'x-vault-displayAttrs': { sensitive: true },
- },
- },
- },
- },
- },
- paths: {
- '/users/{username}': {
- post: {
- requestBody: {
- content: {
- 'application/json': {
- schema: { $ref: '#/components/schemas/UsersRequest' },
- },
- },
- },
- },
- parameters: [
- {
- description: 'Username for this user.',
- in: 'path',
- name: 'username',
- required: true,
- schema: { type: 'string' },
- },
- ],
- 'x-vault-displayAttrs': { itemType: 'User', action: 'Create' },
- },
- },
- },
-};
-
-module('Unit | Service | path-help', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.pathHelp = this.owner.lookup('service:path-help');
- this.store = this.owner.lookup('service:store');
- });
-
- test('it should generate model with mutableId', async function (assert) {
- assert.expect(2);
-
- this.server.get('/auth/userpass/', () => openapiStub);
- this.server.get('/auth/userpass/users/example', () => openapiStub);
- this.server.post('/auth/userpass/users/test', () => {
- assert.ok(true, 'POST request made to correct endpoint');
- return;
- });
-
- const modelType = 'generated-user-userpass';
- await this.pathHelp.getNewModel(modelType, 'userpass', 'auth/userpass/', 'user');
- const model = this.store.createRecord(modelType);
- model.set('mutableId', 'test');
- await model.save();
- assert.strictEqual(model.get('id'), 'test', 'model id is set to mutableId value on save success');
- });
-});
diff --git a/ui/tests/unit/services/permissions-test.js b/ui/tests/unit/services/permissions-test.js
deleted file mode 100644
index ceb7cf894..000000000
--- a/ui/tests/unit/services/permissions-test.js
+++ /dev/null
@@ -1,224 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import Pretender from 'pretender';
-import Service from '@ember/service';
-
-const PERMISSIONS_RESPONSE = {
- data: {
- exact_paths: {
- foo: {
- capabilities: ['read'],
- },
- 'bar/bee': {
- capabilities: ['create', 'list'],
- },
- boo: {
- capabilities: ['deny'],
- },
- },
- glob_paths: {
- 'baz/biz': {
- capabilities: ['read'],
- },
- 'ends/in/slash/': {
- capabilities: ['list'],
- },
- },
- },
-};
-
-module('Unit | Service | permissions', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.server = new Pretender();
- this.server.get('/v1/sys/internal/ui/resultant-acl', () => {
- return [200, { 'Content-Type': 'application/json' }, JSON.stringify(PERMISSIONS_RESPONSE)];
- });
- this.service = this.owner.lookup('service:permissions');
- });
-
- hooks.afterEach(function () {
- this.server.shutdown();
- });
-
- test('sets paths properly', async function (assert) {
- await this.service.getPaths.perform();
- assert.deepEqual(this.service.get('exactPaths'), PERMISSIONS_RESPONSE.data.exact_paths);
- assert.deepEqual(this.service.get('globPaths'), PERMISSIONS_RESPONSE.data.glob_paths);
- });
-
- test('returns true if a policy includes access to an exact path', function (assert) {
- this.service.set('exactPaths', PERMISSIONS_RESPONSE.data.exact_paths);
- assert.true(this.service.hasPermission('foo'));
- });
-
- test('returns true if a paths prefix is included in the policys exact paths', function (assert) {
- this.service.set('exactPaths', PERMISSIONS_RESPONSE.data.exact_paths);
- assert.true(this.service.hasPermission('bar'));
- });
-
- test('it returns true if a policy includes access to a glob path', function (assert) {
- this.service.set('globPaths', PERMISSIONS_RESPONSE.data.glob_paths);
- assert.true(this.service.hasPermission('baz/biz/hi'));
- });
-
- test('it returns true if a policy includes access to the * glob path', function (assert) {
- const splatPath = { '': {} };
- this.service.set('globPaths', splatPath);
- assert.true(this.service.hasPermission('hi'));
- });
-
- test('it returns false if the matched path includes the deny capability', function (assert) {
- this.service.set('globPaths', PERMISSIONS_RESPONSE.data.glob_paths);
- assert.false(this.service.hasPermission('boo'));
- });
-
- test('it returns true if passed path does not end in a slash but globPath does', function (assert) {
- this.service.set('globPaths', PERMISSIONS_RESPONSE.data.glob_paths);
- assert.true(this.service.hasPermission('ends/in/slash'), 'matches without slash');
- assert.true(this.service.hasPermission('ends/in/slash/'), 'matches with slash');
- });
-
- test('it returns false if a policy does not includes access to a path', function (assert) {
- assert.false(this.service.hasPermission('danger'));
- });
-
- test('sets the root token', function (assert) {
- this.service.setPaths({ data: { root: true } });
- assert.true(this.service.canViewAll);
- });
-
- test('returns true with the root token', function (assert) {
- this.service.set('canViewAll', true);
- assert.true(this.service.hasPermission('hi'));
- });
-
- test('it returns true if a policy has the specified capabilities on a path', function (assert) {
- this.service.set('exactPaths', PERMISSIONS_RESPONSE.data.exact_paths);
- this.service.set('globPaths', PERMISSIONS_RESPONSE.data.glob_paths);
- assert.true(this.service.hasPermission('bar/bee', ['create', 'list']));
- assert.true(this.service.hasPermission('baz/biz', ['read']));
- });
-
- test('it returns false if a policy does not have the specified capabilities on a path', function (assert) {
- this.service.set('exactPaths', PERMISSIONS_RESPONSE.data.exact_paths);
- this.service.set('globPaths', PERMISSIONS_RESPONSE.data.glob_paths);
- assert.false(this.service.hasPermission('bar/bee', ['create', 'delete']));
- assert.false(this.service.hasPermission('foo', ['create']));
- });
-
- test('defaults to show all items when policy cannot be found', async function (assert) {
- this.server.get('/v1/sys/internal/ui/resultant-acl', () => {
- return [403, { 'Content-Type': 'application/json' }];
- });
- await this.service.getPaths.perform();
- assert.true(this.service.canViewAll);
- });
-
- test('returns the first allowed nav route for policies', function (assert) {
- const policyPaths = {
- 'sys/policies/acl': {
- capabilities: ['deny'],
- },
- 'sys/policies/rgp': {
- capabilities: ['read'],
- },
- };
- this.service.set('exactPaths', policyPaths);
- assert.strictEqual(this.service.navPathParams('policies').models[0], 'rgp');
- });
-
- test('returns the first allowed nav route for access', function (assert) {
- const accessPaths = {
- 'sys/auth': {
- capabilities: ['deny'],
- },
- 'identity/entity/id': {
- capabilities: ['read'],
- },
- };
- const expected = { route: 'vault.cluster.access.identity', models: ['entities'] };
- this.service.set('exactPaths', accessPaths);
- assert.deepEqual(this.service.navPathParams('access'), expected);
- });
-
- test('hasNavPermission returns true if a policy includes the required capabilities for at least one path', function (assert) {
- const accessPaths = {
- 'sys/auth': {
- capabilities: ['deny'],
- },
- 'identity/group/id': {
- capabilities: ['list', 'read'],
- },
- };
- this.service.set('exactPaths', accessPaths);
- assert.true(this.service.hasNavPermission('access', 'groups'));
- });
-
- test('hasNavPermission returns false if a policy does not include the required capabilities for at least one path', function (assert) {
- const accessPaths = {
- 'sys/auth': {
- capabilities: ['deny'],
- },
- 'identity/group/id': {
- capabilities: ['read'],
- },
- };
- this.service.set('exactPaths', accessPaths);
- assert.false(this.service.hasNavPermission('access', 'groups'));
- });
-
- test('hasNavPermission should handle routeParams as array', function (assert) {
- const getPaths = (override) => ({
- 'sys/auth': {
- capabilities: [override || 'read'],
- },
- 'identity/mfa/method': {
- capabilities: [override || 'read'],
- },
- 'identity/oidc/client': {
- capabilities: [override || 'deny'],
- },
- });
-
- this.service.set('exactPaths', getPaths());
- assert.true(
- this.service.hasNavPermission('access', ['methods', 'mfa', 'oidc']),
- 'hasNavPermission returns true for array of route params when any route is permitted'
- );
- assert.false(
- this.service.hasNavPermission('access', ['methods', 'mfa', 'oidc'], true),
- 'hasNavPermission returns false for array of route params when any route is not permitted and requireAll is passed'
- );
-
- this.service.set('exactPaths', getPaths('read'));
- assert.true(
- this.service.hasNavPermission('access', ['methods', 'mfa', 'oidc'], true),
- 'hasNavPermission returns true for array of route params when all routes are permitted and requireAll is passed'
- );
-
- this.service.set('exactPaths', getPaths('deny'));
- assert.false(
- this.service.hasNavPermission('access', ['methods', 'mfa', 'oidc']),
- 'hasNavPermission returns false for array of route params when no routes are permitted'
- );
- assert.false(
- this.service.hasNavPermission('access', ['methods', 'mfa', 'oidc'], true),
- 'hasNavPermission returns false for array of route params when no routes are permitted and requireAll is passed'
- );
- });
-
- test('appends the namespace to the path if there is one', function (assert) {
- const namespaceService = Service.extend({
- path: 'marketing',
- });
- this.owner.register('service:namespace', namespaceService);
- assert.strictEqual(this.service.pathNameWithNamespace('sys/auth'), 'marketing/sys/auth');
- });
-});
diff --git a/ui/tests/unit/services/store-test.js b/ui/tests/unit/services/store-test.js
deleted file mode 100644
index 117ab25b8..000000000
--- a/ui/tests/unit/services/store-test.js
+++ /dev/null
@@ -1,254 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { resolve } from 'rsvp';
-import { run } from '@ember/runloop';
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { normalizeModelName, keyForCache } from 'vault/services/store';
-import clamp from 'vault/utils/clamp';
-import config from 'vault/config/environment';
-
-const { DEFAULT_PAGE_SIZE } = config.APP;
-
-module('Unit | Service | store', function (hooks) {
- setupTest(hooks);
-
- test('normalizeModelName', function (assert) {
- assert.strictEqual(normalizeModelName('oneThing'), 'one-thing', 'dasherizes modelName');
- });
-
- test('keyForCache', function (assert) {
- const query = { id: 1 };
- const queryWithSize = { id: 1, size: 1 };
- assert.deepEqual(keyForCache(query), JSON.stringify(query), 'generated the correct cache key');
- assert.deepEqual(keyForCache(queryWithSize), JSON.stringify(query), 'excludes size from query cache');
- });
-
- test('clamp', function (assert) {
- assert.strictEqual(clamp('foo', 0, 100), 0, 'returns the min if passed a non-number');
- assert.strictEqual(clamp(0, 1, 100), 1, 'returns the min when passed number is less than the min');
- assert.strictEqual(clamp(200, 1, 100), 100, 'returns the max passed number is greater than the max');
- assert.strictEqual(clamp(50, 1, 100), 50, 'returns the passed number when it is in range');
- });
-
- test('store.storeDataset', function (assert) {
- const arr = ['one', 'two'];
- const store = this.owner.lookup('service:store');
- const query = { id: 1 };
- store.storeDataset('data', query, {}, arr);
-
- assert.deepEqual(store.getDataset('data', query).dataset, arr, 'it stores the array as .dataset');
- assert.deepEqual(store.getDataset('data', query).response, {}, 'it stores the response as .response');
- assert.ok(store.get('lazyCaches').has('data'), 'it stores model map');
- assert.ok(store.get('lazyCaches').get('data').has(keyForCache(query)), 'it stores data on the model map');
- });
-
- test('store.clearDataset with a prefix', function (assert) {
- const store = this.owner.lookup('service:store');
- const arr = ['one', 'two'];
- const arr2 = ['one', 'two', 'three', 'four'];
- store.storeDataset('data', { id: 1 }, {}, arr);
- store.storeDataset('transit-key', { id: 2 }, {}, arr2);
- assert.strictEqual(store.get('lazyCaches').size, 2, 'it stores both keys');
-
- store.clearDataset('transit-key');
- assert.strictEqual(store.get('lazyCaches').size, 1, 'deletes one key');
- assert.notOk(store.get('lazyCaches').has(), 'cache is no longer stored');
- });
-
- test('store.clearAllDatasets', function (assert) {
- const store = this.owner.lookup('service:store');
- const arr = ['one', 'two'];
- const arr2 = ['one', 'two', 'three', 'four'];
- store.storeDataset('data', { id: 1 }, {}, arr);
- store.storeDataset('transit-key', { id: 2 }, {}, arr2);
- assert.strictEqual(store.get('lazyCaches').size, 2, 'it stores both keys');
-
- store.clearAllDatasets();
- assert.strictEqual(store.get('lazyCaches').size, 0, 'deletes all of the keys');
- assert.notOk(store.get('lazyCaches').has('transit-key'), 'first cache key is no longer stored');
- assert.notOk(store.get('lazyCaches').has('data'), 'second cache key is no longer stored');
- });
-
- test('store.getDataset', function (assert) {
- const arr = ['one', 'two'];
- const store = this.owner.lookup('service:store');
- store.storeDataset('data', { id: 1 }, {}, arr);
-
- assert.deepEqual(store.getDataset('data', { id: 1 }), { response: {}, dataset: arr });
- });
-
- test('store.constructResponse', function (assert) {
- const arr = ['one', 'two', 'three', 'fifteen', 'twelve'];
- const store = this.owner.lookup('service:store');
- store.storeDataset('data', { id: 1 }, {}, arr);
-
- assert.deepEqual(
- store.constructResponse('data', { id: 1, pageFilter: 't', page: 1, size: 3, responsePath: 'data' }),
- {
- data: ['two', 'three', 'fifteen'],
- meta: {
- currentPage: 1,
- lastPage: 2,
- nextPage: 2,
- prevPage: 1,
- total: 5,
- filteredTotal: 4,
- pageSize: 3,
- },
- },
- 'it returns filtered results'
- );
- });
-
- test('store.fetchPage', function (assert) {
- const done = assert.async(4);
- const keys = ['zero', 'one', 'two', 'three', 'four', 'five', 'six'];
- const data = {
- data: {
- keys,
- },
- };
- const store = this.owner.lookup('service:store');
- const pageSize = 2;
- const query = {
- size: pageSize,
- page: 1,
- responsePath: 'data.keys',
- };
- store.storeDataset('transit-key', query, data, keys);
-
- let result;
- run(() => {
- store.fetchPage('transit-key', query).then((r) => {
- result = r;
- done();
- });
- });
-
- assert.strictEqual(result.get('length'), pageSize, 'returns the correct number of items');
- assert.deepEqual(result.mapBy('id'), keys.slice(0, pageSize), 'returns the first page of items');
- assert.deepEqual(
- result.get('meta'),
- {
- nextPage: 2,
- prevPage: 1,
- currentPage: 1,
- lastPage: 4,
- total: 7,
- filteredTotal: 7,
- pageSize: 2,
- },
- 'returns correct meta values'
- );
-
- run(() => {
- store
- .fetchPage('transit-key', {
- size: pageSize,
- page: 3,
- responsePath: 'data.keys',
- })
- .then((r) => {
- result = r;
- done();
- });
- });
-
- const pageThreeEnd = 3 * pageSize;
- const pageThreeStart = pageThreeEnd - pageSize;
- assert.deepEqual(
- result.mapBy('id'),
- keys.slice(pageThreeStart, pageThreeEnd),
- 'returns the third page of items'
- );
-
- run(() => {
- store
- .fetchPage('transit-key', {
- size: pageSize,
- page: 99,
- responsePath: 'data.keys',
- })
- .then((r) => {
- result = r;
- done();
- });
- });
-
- assert.deepEqual(
- result.mapBy('id'),
- keys.slice(keys.length - 1),
- 'returns the last page when the page value is beyond the of bounds'
- );
-
- run(() => {
- store
- .fetchPage('transit-key', {
- size: pageSize,
- page: 0,
- responsePath: 'data.keys',
- })
- .then((r) => {
- result = r;
- done();
- });
- });
- assert.deepEqual(
- result.mapBy('id'),
- keys.slice(0, pageSize),
- 'returns the first page when page value is under the bounds'
- );
- });
-
- test('store.lazyPaginatedQuery', function (assert) {
- const response = {
- data: ['foo'],
- };
- let queryArgs;
- const store = this.owner.factoryFor('service:store').create({
- adapterFor() {
- return {
- query(store, modelName, query) {
- queryArgs = query;
- return resolve(response);
- },
- };
- },
- fetchPage() {},
- });
-
- const query = { page: 1, size: 1, responsePath: 'data' };
- run(function () {
- store.lazyPaginatedQuery('transit-key', query);
- });
- assert.deepEqual(
- store.getDataset('transit-key', query),
- { response: { data: null }, dataset: ['foo'] },
- 'stores returned dataset'
- );
-
- run(function () {
- store.lazyPaginatedQuery('secret', { page: 1, responsePath: 'data' });
- });
- assert.strictEqual(queryArgs.size, DEFAULT_PAGE_SIZE, 'calls query with DEFAULT_PAGE_SIZE');
-
- assert.throws(
- () => {
- store.lazyPaginatedQuery('transit-key', {});
- },
- /responsePath is required/,
- 'requires responsePath'
- );
- assert.throws(
- () => {
- store.lazyPaginatedQuery('transit-key', { responsePath: 'foo' });
- },
- /page is required/,
- 'requires page'
- );
- });
-});
diff --git a/ui/tests/unit/services/version-test.js b/ui/tests/unit/services/version-test.js
deleted file mode 100644
index 1bc1ca4d7..000000000
--- a/ui/tests/unit/services/version-test.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Service | version', function (hooks) {
- setupTest(hooks);
-
- test('setting version computes isOSS properly', function (assert) {
- const service = this.owner.lookup('service:version');
- service.version = '0.9.5';
- assert.true(service.isOSS);
- assert.false(service.isEnterprise);
- });
-
- test('setting version computes isEnterprise properly', function (assert) {
- const service = this.owner.lookup('service:version');
- service.version = '0.9.5+ent';
- assert.false(service.isOSS);
- assert.true(service.isEnterprise);
- });
-
- test('setting version with hsm ending computes isEnterprise properly', function (assert) {
- const service = this.owner.lookup('service:version');
- service.version = '0.9.5+ent.hsm';
- assert.false(service.isOSS);
- assert.true(service.isEnterprise);
- });
-
- test('hasPerfReplication', function (assert) {
- const service = this.owner.lookup('service:version');
- assert.false(service.hasPerfReplication);
- service.features = ['Performance Replication'];
- assert.true(service.hasPerfReplication);
- });
-
- test('hasDRReplication', function (assert) {
- const service = this.owner.lookup('service:version');
- assert.false(service.hasDRReplication);
- service.features = ['DR Replication'];
- assert.true(service.hasDRReplication);
- });
-});
diff --git a/ui/tests/unit/services/wizard-test.js b/ui/tests/unit/services/wizard-test.js
deleted file mode 100644
index 08dd5b210..000000000
--- a/ui/tests/unit/services/wizard-test.js
+++ /dev/null
@@ -1,374 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-/* eslint qunit/no-conditional-assertions: "warn" */
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import sinon from 'sinon';
-import { STORAGE_KEYS, DEFAULTS } from 'vault/helpers/wizard-constants';
-
-module('Unit | Service | wizard', function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.router = this.owner.lookup('service:router');
- this.router.reopen({
- transitionTo: sinon.stub().returns({
- followRedirects: function () {
- return {
- then: function (callback) {
- callback();
- },
- };
- },
- }),
- urlFor: sinon.stub().returns('/ui/vault/foo'),
- });
- });
-
- function storage() {
- return {
- items: {},
- getItem(key) {
- var item = this.items[key];
- return item && JSON.parse(item);
- },
-
- setItem(key, val) {
- return (this.items[key] = JSON.stringify(val));
- },
-
- removeItem(key) {
- delete this.items[key];
- },
-
- keys() {
- return Object.keys(this.items);
- },
- };
- }
-
- const testCases = [
- {
- method: 'getExtState',
- args: [STORAGE_KEYS.TUTORIAL_STATE],
- expectedResults: {
- storage: [{ key: STORAGE_KEYS.TUTORIAL_STATE, value: 'idle' }],
- },
- assertCount: 1,
- },
- {
- method: 'saveExtState',
- args: [STORAGE_KEYS.TUTORIAL_STATE, 'test'],
- expectedResults: {
- storage: [{ key: STORAGE_KEYS.TUTORIAL_STATE, value: 'test' }],
- },
- assertCount: 1,
- },
- {
- method: 'storageHasKey',
- args: ['fake-key'],
- expectedResults: { value: false },
- assertCount: 1,
- },
- {
- method: 'storageHasKey',
- args: [STORAGE_KEYS.TUTORIAL_STATE],
- expectedResults: { value: true },
- assertCount: 1,
- },
- {
- method: 'handleDismissed',
- args: [],
- expectedResults: {
- storage: [
- { key: STORAGE_KEYS.FEATURE_STATE, value: undefined },
- { key: STORAGE_KEYS.FEATURE_LIST, value: undefined },
- { key: STORAGE_KEYS.COMPONENT_STATE, value: undefined },
- ],
- },
- assertCount: 3,
- },
- {
- method: 'handlePaused',
- args: [],
- properties: {
- expectedURL: 'this/is/a/url',
- expectedRouteName: 'this.is.a.route',
- },
- expectedResults: {
- storage: [
- { key: STORAGE_KEYS.RESUME_URL, value: 'this/is/a/url' },
- { key: STORAGE_KEYS.RESUME_ROUTE, value: 'this.is.a.route' },
- ],
- },
- assertCount: 2,
- },
- {
- method: 'handlePaused',
- args: [],
- expectedResults: {
- storage: [
- { key: STORAGE_KEYS.RESUME_URL, value: undefined },
- { key: STORAGE_KEYS.RESUME_ROUTE, value: undefined },
- ],
- },
- assertCount: 2,
- },
- {
- method: 'handleResume',
- storage: [
- { key: STORAGE_KEYS.RESUME_URL, value: 'this/is/a/url' },
- { key: STORAGE_KEYS.RESUME_ROUTE, value: 'this.is.a.route' },
- ],
- args: [],
- expectedResults: {
- props: [
- { prop: 'expectedURL', value: 'this/is/a/url' },
- { prop: 'expectedRouteName', value: 'this.is.a.route' },
- ],
- storage: [
- { key: STORAGE_KEYS.RESUME_URL, value: undefined },
- { key: STORAGE_KEYS.RESUME_ROUTE, value: 'this.is.a.route' },
- ],
- },
- assertCount: 4,
- },
- {
- method: 'handleResume',
- args: [],
- expectedResults: {
- storage: [
- { key: STORAGE_KEYS.RESUME_URL, value: undefined },
- { key: STORAGE_KEYS.RESUME_ROUTE, value: undefined },
- ],
- },
- assertCount: 2,
- },
- {
- method: 'restartGuide',
- args: [],
- expectedResults: {
- props: [
- { prop: 'currentState', value: 'active.select' },
- { prop: 'featureComponent', value: 'wizard/features-selection' },
- { prop: 'tutorialComponent', value: 'wizard/tutorial-active' },
- ],
- storage: [
- { key: STORAGE_KEYS.FEATURE_STATE, value: undefined },
- { key: STORAGE_KEYS.FEATURE_STATE_HISTORY, value: undefined },
- { key: STORAGE_KEYS.FEATURE_LIST, value: undefined },
- { key: STORAGE_KEYS.COMPONENT_STATE, value: undefined },
- { key: STORAGE_KEYS.TUTORIAL_STATE, value: 'active.select' },
- { key: STORAGE_KEYS.COMPLETED_FEATURES, value: undefined },
- { key: STORAGE_KEYS.RESUME_URL, value: undefined },
- { key: STORAGE_KEYS.RESUME_ROUTE, value: undefined },
- ],
- },
- assertCount: 11,
- },
- {
- method: 'clearFeatureData',
- args: [],
- expectedResults: {
- props: [
- { prop: 'currentMachine', value: null },
- { prop: 'featureMachineHistory', value: null },
- ],
- storage: [
- { key: STORAGE_KEYS.FEATURE_STATE, value: undefined },
- { key: STORAGE_KEYS.FEATURE_STATE_HISTORY, value: undefined },
- { key: STORAGE_KEYS.FEATURE_LIST, value: undefined },
- { key: STORAGE_KEYS.COMPONENT_STATE, value: undefined },
- ],
- },
- assertCount: 6,
- },
- {
- method: 'saveState',
- args: [
- 'currentState',
- {
- value: {
- init: {
- active: 'login',
- },
- },
- actions: [{ type: 'render', level: 'feature', component: 'wizard/init-login' }],
- },
- ],
- expectedResults: {
- props: [{ prop: 'currentState', value: 'init.active.login' }],
- },
- assertCount: 1,
- },
- {
- method: 'saveState',
- args: [
- 'currentState',
- {
- value: {
- active: 'login',
- },
- actions: [{ type: 'render', level: 'feature', component: 'wizard/init-login' }],
- },
- ],
- expectedResults: {
- props: [{ prop: 'currentState', value: 'active.login' }],
- },
- assertCount: 1,
- },
- {
- method: 'saveState',
- args: ['currentState', 'login'],
- expectedResults: {
- props: [{ prop: 'currentState', value: 'login' }],
- },
- assertCount: 1,
- },
- {
- method: 'saveFeatureHistory',
- args: ['idle'],
- properties: { featureList: ['policies', 'tools'] },
- storage: [{ key: STORAGE_KEYS.COMPLETED_FEATURES, value: ['secrets'] }],
- expectedResults: {
- props: [{ prop: 'featureMachineHistory', value: null }],
- },
- assertCount: 1,
- },
- {
- method: 'saveFeatureHistory',
- args: ['idle'],
- properties: { featureList: ['policies', 'tools'] },
- storage: [],
- expectedResults: {
- props: [{ prop: 'featureMachineHistory', value: ['idle'] }],
- },
- assertCount: 1,
- },
- {
- method: 'saveFeatureHistory',
- args: ['idle'],
- properties: { featureList: ['policies', 'tools'] },
- storage: [],
- expectedResults: {
- props: [{ prop: 'featureMachineHistory', value: ['idle'] }],
- },
- assertCount: 1,
- },
- {
- method: 'saveFeatureHistory',
- args: ['idle'],
- properties: { featureMachineHistory: [], featureList: ['policies', 'tools'] },
- storage: [{ key: STORAGE_KEYS.COMPLETED_FEATURES, value: ['secrets'] }],
- expectedResults: {
- props: [{ prop: 'featureMachineHistory', value: ['idle'] }],
- storage: [{ key: STORAGE_KEYS.FEATURE_STATE_HISTORY, value: ['idle'] }],
- },
- assertCount: 2,
- },
- {
- method: 'saveFeatureHistory',
- args: ['idle'],
- properties: { featureMachineHistory: null, featureList: ['policies', 'tools'] },
- storage: [{ key: STORAGE_KEYS.COMPLETED_FEATURES, value: ['secrets'] }],
- expectedResults: {
- props: [{ prop: 'featureMachineHistory', value: null }],
- },
- assertCount: 1,
- },
- {
- method: 'saveFeatureHistory',
- args: ['create'],
- properties: { featureMachineHistory: ['idle'], featureList: ['policies', 'tools'] },
- storage: [{ key: STORAGE_KEYS.COMPLETED_FEATURES, value: ['secrets'] }],
- expectedResults: {
- props: [{ prop: 'featureMachineHistory', value: ['idle', 'create'] }],
- storage: [{ key: STORAGE_KEYS.FEATURE_STATE_HISTORY, value: ['idle', 'create'] }],
- },
- assertCount: 2,
- },
- {
- method: 'saveFeatureHistory',
- args: ['create'],
- properties: { featureMachineHistory: ['idle'], featureList: ['policies', 'tools'] },
- storage: [
- { key: STORAGE_KEYS.COMPLETED_FEATURES, value: ['secrets'] },
- { key: STORAGE_KEYS.FEATURE_STATE_HISTORY, value: ['idle', 'create'] },
- ],
- expectedResults: {
- props: [{ prop: 'featureMachineHistory', value: ['idle', 'create'] }],
- storage: [{ key: STORAGE_KEYS.FEATURE_STATE_HISTORY, value: ['idle', 'create'] }],
- },
- assertCount: 2,
- },
- {
- method: 'startFeature',
- args: [],
- properties: { featureList: ['secrets', 'tools'] },
- expectedResults: {
- props: [
- { prop: 'featureState', value: 'idle' },
- { prop: 'currentMachine', value: 'secrets' },
- ],
- },
- assertCount: 2,
- },
- {
- method: 'saveFeatures',
- args: [['secrets', 'tools']],
- expectedResults: {
- props: [{ prop: 'featureList', value: ['secrets', 'tools'] }],
- storage: [{ key: STORAGE_KEYS.FEATURE_LIST, value: ['secrets', 'tools'] }],
- },
- assertCount: 2,
- },
- ];
-
- testCases.forEach((testCase) => {
- const store = storage();
- test(`${testCase.method}`, function (assert) {
- assert.expect(testCase.assertCount);
- const wizard = this.owner.factoryFor('service:wizard').create({
- storage() {
- return store;
- },
- });
-
- if (testCase.properties) {
- wizard.setProperties(testCase.properties);
- } else {
- wizard.setProperties(DEFAULTS);
- }
-
- if (testCase.storage) {
- testCase.storage.forEach((item) => wizard.storage().setItem(item.key, item.value));
- }
-
- const result = wizard[testCase.method](...testCase.args);
- if (testCase.expectedResults.props) {
- testCase.expectedResults.props.forEach((property) => {
- assert.deepEqual(
- wizard.get(property.prop),
- property.value,
- `${testCase.method} creates correct value for ${property.prop}`
- );
- });
- }
- if (testCase.expectedResults.storage) {
- testCase.expectedResults.storage.forEach((item) => {
- assert.deepEqual(
- wizard.storage().getItem(item.key),
- item.value,
- `${testCase.method} creates correct storage state for ${item.key}`
- );
- });
- }
- if (testCase.expectedResults.value !== null && testCase.expectedResults.value !== undefined) {
- assert.strictEqual(result, testCase.expectedResults.value, `${testCase.method} gives correct value`);
- }
- });
- });
-});
diff --git a/ui/tests/unit/utils/api-path-test.js b/ui/tests/unit/utils/api-path-test.js
deleted file mode 100644
index 8c723286e..000000000
--- a/ui/tests/unit/utils/api-path-test.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import apiPath from 'vault/utils/api-path';
-import { module, test } from 'qunit';
-
-module('Unit | Util | api path', function () {
- test('it returns a function', function (assert) {
- const ret = apiPath`foo`;
- assert.strictEqual(typeof ret, 'function');
- });
-
- test('it iterpolates strings from passed context object', function (assert) {
- const ret = apiPath`foo/${'one'}/${'two'}`;
- const result = ret({ one: 1, two: 2 });
-
- assert.strictEqual(result, 'foo/1/2', 'returns the expected string');
- });
-
- test('it throws when the key is not found in the context', function (assert) {
- const ret = apiPath`foo/${'one'}/${'two'}`;
- assert.throws(() => {
- ret({ one: 1 });
- }, /Error: Assertion Failed: Expected 2 keys in apiPath context, only recieved one/);
- });
-});
diff --git a/ui/tests/unit/utils/chart-helpers-test.js b/ui/tests/unit/utils/chart-helpers-test.js
deleted file mode 100644
index 881a286d1..000000000
--- a/ui/tests/unit/utils/chart-helpers-test.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { formatNumbers, formatTooltipNumber, calculateAverage } from 'vault/utils/chart-helpers';
-import { module, test } from 'qunit';
-
-const SMALL_NUMBERS = [0, 7, 27, 103, 999];
-const LARGE_NUMBERS = {
- 1001: '1k',
- 33777: '34k',
- 532543: '530k',
- 2100100: '2.1M',
- 54500200100: '55B',
-};
-
-module('Unit | Utility | chart-helpers', function () {
- test('formatNumbers renders number correctly', function (assert) {
- assert.expect(11);
- const method = formatNumbers();
- assert.ok(method);
- SMALL_NUMBERS.forEach(function (num) {
- assert.strictEqual(formatNumbers(num), num, `Does not format small number ${num}`);
- });
- Object.keys(LARGE_NUMBERS).forEach(function (num) {
- const expected = LARGE_NUMBERS[num];
- assert.strictEqual(formatNumbers(num), expected, `Formats ${num} as ${expected}`);
- });
- });
-
- test('formatTooltipNumber renders number correctly', function (assert) {
- const formatted = formatTooltipNumber(120300200100);
- assert.strictEqual(formatted.length, 15, 'adds punctuation at proper place for large numbers');
- });
-
- test('calculateAverage is accurate', function (assert) {
- const testArray1 = [
- { label: 'foo', value: 10 },
- { label: 'bar', value: 22 },
- ];
- const testArray2 = [
- { label: 'foo', value: undefined },
- { label: 'bar', value: 22 },
- ];
- const testArray3 = [{ label: 'foo' }, { label: 'bar' }];
- const getAverage = (array) => array.reduce((a, b) => a + b, 0) / array.length;
- assert.strictEqual(calculateAverage(null), null, 'returns null if dataset it null');
- assert.strictEqual(calculateAverage([]), null, 'returns null if dataset it empty array');
- assert.strictEqual(
- calculateAverage(testArray1, 'value'),
- getAverage([10, 22]),
- `returns correct average for array of objects`
- );
- assert.strictEqual(
- calculateAverage(testArray2, 'value'),
- getAverage([0, 22]),
- `returns correct average for array of objects containing undefined values`
- );
- assert.strictEqual(
- calculateAverage(testArray3, 'value'),
- null,
- 'returns null when object key does not exist at all'
- );
- });
-});
diff --git a/ui/tests/unit/utils/common-prefix-test.js b/ui/tests/unit/utils/common-prefix-test.js
deleted file mode 100644
index 93d75dbf0..000000000
--- a/ui/tests/unit/utils/common-prefix-test.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import commonPrefix from 'core/utils/common-prefix';
-import { module, test } from 'qunit';
-
-module('Unit | Util | common prefix', function () {
- test('it returns empty string if called with no args or an empty array', function (assert) {
- let returned = commonPrefix();
- assert.strictEqual(returned, '', 'returns an empty string');
- returned = commonPrefix([]);
- assert.strictEqual(returned, '', 'returns an empty string for an empty array');
- });
-
- test('it returns empty string if there are no common prefixes', function (assert) {
- const secrets = ['asecret', 'secret2', 'secret3'].map((s) => ({ id: s }));
- const returned = commonPrefix(secrets);
- assert.strictEqual(returned, '', 'returns an empty string');
- });
-
- test('it returns the longest prefix', function (assert) {
- const secrets = ['secret1', 'secret2', 'secret3'].map((s) => ({ id: s }));
- let returned = commonPrefix(secrets);
- assert.strictEqual(returned, 'secret', 'finds secret prefix');
- const greetings = ['hello-there', 'hello-hi', 'hello-howdy'].map((s) => ({ id: s }));
- returned = commonPrefix(greetings);
- assert.strictEqual(returned, 'hello-', 'finds hello- prefix');
- });
-
- test('it can compare an attribute that is not "id" to calculate the longest prefix', function (assert) {
- const secrets = ['secret1', 'secret2', 'secret3'].map((s) => ({ name: s }));
- const returned = commonPrefix(secrets, 'name');
- assert.strictEqual(returned, 'secret', 'finds secret prefix from name attribute');
- });
-});
diff --git a/ui/tests/unit/utils/decode-config-from-jwt-test.js b/ui/tests/unit/utils/decode-config-from-jwt-test.js
deleted file mode 100644
index 32e23c378..000000000
--- a/ui/tests/unit/utils/decode-config-from-jwt-test.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import decodeConfigFromJWT from 'replication/utils/decode-config-from-jwt';
-import { module, test } from 'qunit';
-
-module('Unit | Util | decode config from jwt', function () {
- const PADDING_STRIPPED_TOKEN =
- 'eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJhZGRyIjoiaHR0cDovLzE5Mi4xNjguNTAuMTUwOjgyMDAiLCJleHAiOjE1MTczNjkwNzUsImlhdCI6MTUxNzM2NzI3NSwianRpIjoiN2IxZDZkZGUtZmViZC00ZGU1LTc0MWUtZDU2ZTg0ZTNjZDk2IiwidHlwZSI6IndyYXBwaW5nIn0.MIGIAkIB6s2zbohbxLimwhM6cg16OISK2DgoTgy1vHbTjPT8uG4hsrJndZp5COB8dX-djWjx78ZFMk-3a6Ij51su_By9xsoCQgFXV8y3DzH_YzYvdL9x38dMSWaVHpR_lpoKWsQnMvAukSchJp1FfHZQ8JcSkPu5IAVZdfwlG5esJ_ZOMxA3KIQFnA';
- const NO_PADDING_TOKEN =
- 'eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJhZGRyIjoiaHR0cDovLzEyNy4wLjAuMTo4MjAwIiwiZXhwIjoxNTE3NDM0NDA2LCJpYXQiOjE1MTc0MzI2MDYsImp0aSI6IjBiYmI1ZWMyLWM0ODgtMzRjYi0wMzY5LTkxZmJiMjVkZTFiYSIsInR5cGUiOiJ3cmFwcGluZyJ9.MIGHAkIBAGzB5EW6PolAi2rYOzZNvfJnR902WxprtRqnSF2E2I2ye9XLGX--L7npSBjBhnd27ocQ4ZO9VhfDIFqMzu1TNiwCQT52O6xAoz9ElRrq76PjkEHO4ns5_ZgjSKXuKaqdGysHYSlry8KEjWLGQECvZWg9LQeIf35jwqeQUfyJUfmwl5r_';
- const INVALID_JSON_TOKEN = `foo.${btoa({ addr: 'http://127.0.0.1' })}.bar`;
-
- test('it decodes token with no padding', function (assert) {
- const config = decodeConfigFromJWT(NO_PADDING_TOKEN);
-
- assert.ok(!!config, 'config was decoded');
- assert.ok(!!config.addr, 'config.addr is present');
- });
-
- test('it decodes token with stripped padding', function (assert) {
- const config = decodeConfigFromJWT(PADDING_STRIPPED_TOKEN);
-
- assert.ok(!!config, 'config was decoded');
- assert.ok(!!config.addr, 'config.addr is present');
- });
-
- test('it returns nothing if the config is invalid JSON', function (assert) {
- const config = decodeConfigFromJWT(INVALID_JSON_TOKEN);
-
- assert.notOk(config, 'config is not present');
- });
-});
diff --git a/ui/tests/unit/utils/openapi-to-attrs-test.js b/ui/tests/unit/utils/openapi-to-attrs-test.js
deleted file mode 100644
index 3d4cd5708..000000000
--- a/ui/tests/unit/utils/openapi-to-attrs-test.js
+++ /dev/null
@@ -1,295 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { attr } from '@ember-data/model';
-import { expandOpenApiProps, combineAttributes, combineFieldGroups } from 'vault/utils/openapi-to-attrs';
-import { module, test } from 'qunit';
-import { camelize } from '@ember/string';
-
-module('Unit | Util | OpenAPI Data Utilities', function () {
- const OPENAPI_RESPONSE_PROPS = {
- ttl: {
- type: 'string',
- format: 'seconds',
- description: 'this is a TTL!',
- 'x-vault-displayAttrs': {
- name: 'TTL',
- },
- },
- 'awesome-people': {
- type: 'array',
- items: {
- type: 'string',
- },
- 'x-vault-displayAttrs': {
- value: 'Grace Hopper,Lady Ada',
- },
- },
- 'favorite-ice-cream': {
- type: 'string',
- enum: ['vanilla', 'chocolate', 'strawberry'],
- },
- 'default-value': {
- default: 30,
- 'x-vault-displayAttrs': {
- value: 300,
- },
- type: 'integer',
- },
- default: {
- 'x-vault-displayAttrs': {
- value: 30,
- },
- type: 'integer',
- },
- 'super-secret': {
- type: 'string',
- 'x-vault-displayAttrs': {
- sensitive: true,
- },
- description: 'A really secret thing',
- },
- };
- const EXPANDED_PROPS = {
- ttl: {
- helpText: 'this is a TTL!',
- editType: 'ttl',
- label: 'TTL',
- fieldGroup: 'default',
- },
- awesomePeople: {
- editType: 'stringArray',
- defaultValue: 'Grace Hopper,Lady Ada',
- fieldGroup: 'default',
- },
- favoriteIceCream: {
- editType: 'string',
- type: 'string',
- possibleValues: ['vanilla', 'chocolate', 'strawberry'],
- fieldGroup: 'default',
- },
- defaultValue: {
- editType: 'number',
- type: 'number',
- defaultValue: 300,
- fieldGroup: 'default',
- },
- default: {
- editType: 'number',
- type: 'number',
- defaultValue: 30,
- fieldGroup: 'default',
- },
- superSecret: {
- type: 'string',
- editType: 'string',
- sensitive: true,
- helpText: 'A really secret thing',
- fieldGroup: 'default',
- },
- };
-
- const EXISTING_MODEL_ATTRS = [
- {
- key: 'name',
- value: {
- isAttribute: true,
- name: 'name',
- options: {
- editType: 'string',
- label: 'Role name',
- },
- },
- },
- {
- key: 'awesomePeople',
- value: {
- isAttribute: true,
- name: 'awesomePeople',
- options: {
- label: 'People Who Are Awesome',
- },
- },
- },
- ];
-
- const COMBINED_ATTRS = {
- name: attr('string', {
- editType: 'string',
- type: 'string',
- label: 'Role name',
- }),
- ttl: attr('string', {
- editType: 'ttl',
- label: 'TTL',
- helpText: 'this is a TTL!',
- }),
- awesomePeople: attr({
- label: 'People Who Are Awesome',
- editType: 'stringArray',
- defaultValue: 'Grace Hopper,Lady Ada',
- }),
- favoriteIceCream: attr('string', {
- type: 'string',
- editType: 'string',
- possibleValues: ['vanilla', 'chocolate', 'strawberry'],
- }),
- superSecret: attr('string', {
- type: 'string',
- editType: 'string',
- sensitive: true,
- description: 'A really secret thing',
- }),
- };
-
- const NEW_FIELDS = ['one', 'two', 'three'];
-
- const OPENAPI_DESCRIPTIONS = {
- token_bound_cidrs: {
- type: 'array',
- description:
- 'Comma separated string or JSON list of CIDR blocks. If set, specifies the blocks of IP addresses which are allowed to use the generated token.',
- items: {
- type: 'string',
- },
- 'x-vault-displayAttrs': {
- description:
- 'List of CIDR blocks. If set, specifies the blocks of IP addresses which are allowed to use the generated token.',
- name: "Generated Token's Bound CIDRs",
- group: 'Tokens',
- },
- },
- blah_blah: {
- type: 'array',
- description: 'Comma-separated list of policies',
- items: {
- type: 'string',
- },
- 'x-vault-displayAttrs': {
- name: "Generated Token's Policies",
- group: 'Tokens',
- },
- },
- only_display_description: {
- type: 'array',
- items: {
- type: 'string',
- },
- 'x-vault-displayAttrs': {
- description: 'Hello there, you look nice today',
- },
- },
- };
-
- const STRING_ARRAY_DESCRIPTIONS = {
- token_bound_cidrs: {
- helpText:
- 'List of CIDR blocks. If set, specifies the blocks of IP addresses which are allowed to use the generated token.',
- },
- blah_blah: {
- helpText: 'Comma-separated list of policies',
- },
- only_display_description: {
- helpText: 'Hello there, you look nice today',
- },
- };
-
- test('it creates objects from OpenAPI schema props', function (assert) {
- assert.expect(6);
- const generatedProps = expandOpenApiProps(OPENAPI_RESPONSE_PROPS);
- for (const propName in EXPANDED_PROPS) {
- assert.deepEqual(EXPANDED_PROPS[propName], generatedProps[propName], `correctly expands ${propName}`);
- }
- });
-
- test('it combines OpenAPI props with existing model attrs', function (assert) {
- assert.expect(3);
- const combined = combineAttributes(EXISTING_MODEL_ATTRS, EXPANDED_PROPS);
- for (const propName in EXISTING_MODEL_ATTRS) {
- assert.deepEqual(COMBINED_ATTRS[propName], combined[propName]);
- }
- });
-
- test('it adds new fields from OpenAPI to fieldGroups except for exclusions', function (assert) {
- assert.expect(3);
- const modelFieldGroups = [
- { default: ['name', 'awesomePeople'] },
- {
- Options: ['ttl'],
- },
- ];
- const excludedFields = ['two'];
- const expectedGroups = [
- { default: ['name', 'awesomePeople', 'one', 'three'] },
- {
- Options: ['ttl'],
- },
- ];
- const newFieldGroups = combineFieldGroups(modelFieldGroups, NEW_FIELDS, excludedFields);
- for (const groupName in modelFieldGroups) {
- assert.deepEqual(
- newFieldGroups[groupName],
- expectedGroups[groupName],
- 'it incorporates all new fields except for those excluded'
- );
- }
- });
- test('it adds all new fields from OpenAPI to fieldGroups when excludedFields is empty', function (assert) {
- assert.expect(3);
- const modelFieldGroups = [
- { default: ['name', 'awesomePeople'] },
- {
- Options: ['ttl'],
- },
- ];
- const excludedFields = [];
- const expectedGroups = [
- { default: ['name', 'awesomePeople', 'one', 'two', 'three'] },
- {
- Options: ['ttl'],
- },
- ];
- const nonExcludedFieldGroups = combineFieldGroups(modelFieldGroups, NEW_FIELDS, excludedFields);
- for (const groupName in modelFieldGroups) {
- assert.deepEqual(
- nonExcludedFieldGroups[groupName],
- expectedGroups[groupName],
- 'it incorporates all new fields'
- );
- }
- });
- test('it keeps fields the same when there are no brand new fields from OpenAPI', function (assert) {
- assert.expect(3);
- const modelFieldGroups = [
- { default: ['name', 'awesomePeople', 'two', 'one', 'three'] },
- {
- Options: ['ttl'],
- },
- ];
- const excludedFields = [];
- const expectedGroups = [
- { default: ['name', 'awesomePeople', 'two', 'one', 'three'] },
- {
- Options: ['ttl'],
- },
- ];
- const fieldGroups = combineFieldGroups(modelFieldGroups, NEW_FIELDS, excludedFields);
- for (const groupName in modelFieldGroups) {
- assert.deepEqual(fieldGroups[groupName], expectedGroups[groupName], 'it incorporates all new fields');
- }
- });
-
- test('it uses the description from the display attrs block if it exists', async function (assert) {
- assert.expect(3);
- const generatedProps = expandOpenApiProps(OPENAPI_DESCRIPTIONS);
- for (const propName in STRING_ARRAY_DESCRIPTIONS) {
- assert.strictEqual(
- generatedProps[camelize(propName)].helpText,
- STRING_ARRAY_DESCRIPTIONS[propName].helpText,
- `correctly updates helpText for ${propName}`
- );
- }
- });
-});
diff --git a/ui/tests/unit/utils/sort-objects-test.js b/ui/tests/unit/utils/sort-objects-test.js
deleted file mode 100644
index 40ccfb8f1..000000000
--- a/ui/tests/unit/utils/sort-objects-test.js
+++ /dev/null
@@ -1,91 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import sortObjects from 'vault/utils/sort-objects';
-import { module, test } from 'qunit';
-
-module('Unit | Utility | sort-objects', function () {
- test('it sorts array of objects', function (assert) {
- const originalArray = [
- { foo: 'grape', bar: 'third' },
- { foo: 'banana', bar: 'second' },
- { foo: 'lemon', bar: 'fourth' },
- { foo: 'apple', bar: 'first' },
- ];
- const expectedArray = [
- { bar: 'first', foo: 'apple' },
- { bar: 'second', foo: 'banana' },
- { bar: 'third', foo: 'grape' },
- { bar: 'fourth', foo: 'lemon' },
- ];
-
- assert.propEqual(sortObjects(originalArray, 'foo'), expectedArray, 'it sorts array of objects');
-
- const originalWithNumbers = [
- { foo: 'Z', bar: 'fourth' },
- { foo: '1', bar: 'first' },
- { foo: '2', bar: 'second' },
- { foo: 'A', bar: 'third' },
- ];
- const expectedWithNumbers = [
- { bar: 'first', foo: '1' },
- { bar: 'second', foo: '2' },
- { bar: 'third', foo: 'A' },
- { bar: 'fourth', foo: 'Z' },
- ];
- assert.propEqual(
- sortObjects(originalWithNumbers, 'foo'),
- expectedWithNumbers,
- 'it sorts strings with numbers and letters'
- );
- });
-
- test('it disregards capitalization', function (assert) {
- // sort() arranges capitalized values before lowercase, the helper removes case by making all strings toUppercase()
- const originalArray = [
- { foo: 'something-a', bar: 'third' },
- { foo: 'D-something', bar: 'second' },
- { foo: 'SOMETHING-b', bar: 'fourth' },
- { foo: 'a-something', bar: 'first' },
- ];
- const expectedArray = [
- { bar: 'first', foo: 'a-something' },
- { bar: 'second', foo: 'D-something' },
- { bar: 'third', foo: 'something-a' },
- { bar: 'fourth', foo: 'SOMETHING-b' },
- ];
-
- assert.propEqual(
- sortObjects(originalArray, 'foo'),
- expectedArray,
- 'it sorts array of objects regardless of capitalization'
- );
- });
-
- test('it fails gracefully', function (assert) {
- const originalArray = [
- { foo: 'b', bar: 'two' },
- { foo: 'a', bar: 'one' },
- ];
- assert.propEqual(
- sortObjects(originalArray, 'someKey'),
- originalArray,
- 'it returns original array if key does not exist'
- );
- assert.deepEqual(sortObjects('not an array'), 'not an array', 'it returns original arg if not an array');
-
- const notStrings = [
- { foo: '1', bar: 'third' },
- { foo: 'Z', bar: 'second' },
- { foo: 1, bar: 'fourth' },
- { foo: 2, bar: 'first' },
- ];
- assert.propEqual(
- sortObjects(notStrings, 'foo'),
- notStrings,
- 'it returns original array if values are not all strings'
- );
- });
-});
diff --git a/ui/tests/unit/utils/timestamp-test.js b/ui/tests/unit/utils/timestamp-test.js
deleted file mode 100644
index 89196fbca..000000000
--- a/ui/tests/unit/utils/timestamp-test.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import timestamp from 'core/utils/timestamp';
-import sinon from 'sinon';
-import { module, test } from 'qunit';
-
-/*
- This test coverage is more for an example than actually covering the utility
-*/
-module('Unit | Utility | timestamp', function () {
- test('it can be overridden', function (assert) {
- const stub = sinon.stub(timestamp, 'now').callsFake(() => new Date('2030-03-03T03:30:03'));
- const result = timestamp.now();
- assert.strictEqual(result.toISOString(), new Date('2030-03-03T03:30:03').toISOString());
- // Always make sure to restore the stub
- stub.restore(); // timestamp.now.restore(); also works
- });
-});
diff --git a/ui/tests/unit/utils/trim-right-test.js b/ui/tests/unit/utils/trim-right-test.js
deleted file mode 100644
index 761596466..000000000
--- a/ui/tests/unit/utils/trim-right-test.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import trimRight from 'vault/utils/trim-right';
-import { module, test } from 'qunit';
-
-module('Unit | Util | trim right', function () {
- test('it trims extension array from end of string', function (assert) {
- const trimmedName = trimRight('my-file-name-is-cool.json', ['.json', '.txt', '.hcl', '.policy']);
-
- assert.strictEqual(trimmedName, 'my-file-name-is-cool');
- });
-
- test('it only trims extension array from the very end of string', function (assert) {
- const trimmedName = trimRight('my-file-name.json-is-cool.json', ['.json', '.txt', '.hcl', '.policy']);
-
- assert.strictEqual(trimmedName, 'my-file-name.json-is-cool');
- });
-
- test('it returns string as is if trim array is empty', function (assert) {
- const trimmedName = trimRight('my-file-name-is-cool.json', []);
-
- assert.strictEqual(trimmedName, 'my-file-name-is-cool.json');
- });
-
- test('it returns string as is if trim array is not passed in', function (assert) {
- const trimmedName = trimRight('my-file-name-is-cool.json');
-
- assert.strictEqual(trimmedName, 'my-file-name-is-cool.json');
- });
-
- test('it allows the last extension to also be part of the file name', function (assert) {
- const trimmedName = trimRight('my-policy.hcl', ['.json', '.txt', '.hcl', '.policy']);
-
- assert.strictEqual(trimmedName, 'my-policy');
- });
-
- test('it allows the last extension to also be part of the file name and the extenstion', function (assert) {
- const trimmedName = trimRight('my-policy.policy', ['.json', '.txt', '.hcl', '.policy']);
-
- assert.strictEqual(trimmedName, 'my-policy');
- });
-
- test('it passes endings into the regex unescaped when passing false as the third arg', function (assert) {
- const trimmedName = trimRight('my-policypolicy', ['.json', '.txt', '.hcl', '.policy'], false);
-
- // the . gets interpreted as regex wildcard so it also trims the y character
- assert.strictEqual(trimmedName, 'my-polic');
- });
-});
diff --git a/ui/tests/unit/utils/validators-test.js b/ui/tests/unit/utils/validators-test.js
deleted file mode 100644
index 918539855..000000000
--- a/ui/tests/unit/utils/validators-test.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import validators from 'vault/utils/validators';
-
-module('Unit | Util | validators', function (hooks) {
- setupTest(hooks);
-
- test('it should validate presence', function (assert) {
- let isValid;
- const check = (value) => (isValid = validators.presence(value));
- check(null);
- assert.false(isValid, 'Invalid when value is null');
- check('');
- assert.false(isValid, 'Invalid when value is empty string');
- check(true);
- assert.true(isValid, 'Valid when value is true');
- check(0);
- assert.true(isValid, 'Valid when value is 0 as integer');
- check('0');
- assert.true(isValid, 'Valid when value is 0 as string');
- });
-
- test('it should validate length', function (assert) {
- let isValid;
- const options = { nullable: true, min: 3, max: 5 };
- const check = (prop) => (isValid = validators.length(prop, options));
- check(null);
- assert.true(isValid, 'Valid when nullable is true');
- options.nullable = false;
- check(null);
- assert.false(isValid, 'Invalid when nullable is false');
- check('12');
- assert.false(isValid, 'Invalid when string not min length');
- check('123456');
- assert.false(isValid, 'Invalid when string over max length');
- check('1234');
- assert.true(isValid, 'Valid when string between min and max length');
- check(12);
- assert.false(isValid, 'Invalid when integer not min length');
- check(123456);
- assert.false(isValid, 'Invalid when integer over max length');
- check(1234);
- assert.true(isValid, 'Valid when integer between min and max length');
- options.min = 1;
- check(0);
- assert.true(isValid, 'Valid when integer is 0 and min is 1');
- check('0');
- assert.true(isValid, 'Valid when string is 0 and min is 1');
- });
-
- test('it should validate number', function (assert) {
- let isValid;
- const options = { nullable: true };
- const check = (prop) => (isValid = validators.number(prop, options));
- check(null);
- assert.true(isValid, 'Valid when nullable is true');
- options.nullable = false;
- check(null);
- assert.false(isValid, 'Invalid when nullable is false');
- check(9);
- assert.true(isValid, 'Valid for number');
- check('9');
- assert.true(isValid, 'Valid for number as string');
- check('foo');
- assert.false(isValid, 'Invalid for string that is not a number');
- check('12foo');
- assert.false(isValid, 'Invalid for string that contains a number');
- check(0);
- assert.true(isValid, 'Valid for 0 as an integer');
- check('0');
- assert.true(isValid, 'Valid for 0 as a string');
- });
-
- test('it should validate white space', function (assert) {
- let isValid;
- const check = (prop) => (isValid = validators.containsWhiteSpace(prop));
- check('validText');
- assert.true(isValid, 'Valid when text contains no spaces');
- check('valid-text');
- assert.true(isValid, 'Valid when text contains no spaces and hyphen');
- check('some space');
- assert.false(isValid, 'Invalid when text contains single space');
- check('text with spaces');
- assert.false(isValid, 'Invalid when text contains multiple spaces');
- check(' leadingSpace');
- assert.false(isValid, 'Invalid when text has leading whitespace');
- check('trailingSpace ');
- assert.false(isValid, 'Invalid when text has trailing whitespace');
- });
-});
diff --git a/vault/acl_test.go b/vault/acl_test.go
deleted file mode 100644
index 467a8c35c..000000000
--- a/vault/acl_test.go
+++ /dev/null
@@ -1,1334 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "fmt"
- "reflect"
- "sync"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestACL_NewACL(t *testing.T) {
- t.Run("root-ns", func(t *testing.T) {
- t.Parallel()
- testNewACL(t, namespace.RootNamespace)
- })
-}
-
-func testNewACL(t *testing.T, ns *namespace.Namespace) {
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- policy := []*Policy{{Name: "root"}}
- _, err := NewACL(ctx, policy)
- switch ns.ID {
- case namespace.RootNamespaceID:
- if err != nil {
- t.Fatal(err)
- }
- default:
- if err == nil {
- t.Fatal("expected an error")
- }
- }
-}
-
-func TestACL_MFAMethods(t *testing.T) {
- t.Run("root-ns", func(t *testing.T) {
- t.Parallel()
- testACLMFAMethods(t, namespace.RootNamespace)
- })
-}
-
-func testACLMFAMethods(t *testing.T, ns *namespace.Namespace) {
- mfaRules := `
-path "secret/foo/*" {
- mfa_methods = ["mfa_method_1", "mfa_method_2", "mfa_method_3"]
-}
-path "secret/exact/path" {
- mfa_methods = ["mfa_method_4", "mfa_method_5"]
-}
-path "secret/split/definition" {
- mfa_methods = ["mfa_method_6", "mfa_method_7"]
-}
-path "secret/split/definition" {
- mfa_methods = ["mfa_method_7", "mfa_method_8", "mfa_method_9"]
-}
- `
-
- policy, err := ParseACLPolicy(ns, mfaRules)
- if err != nil {
- t.Fatal(err)
- }
-
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- acl, err := NewACL(ctx, []*Policy{policy})
- if err != nil {
- t.Fatal(err)
- }
-
- request := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/foo/testing/glob/pattern",
- }
-
- actual := acl.AllowOperation(ctx, request, false).MFAMethods
- expected := []string{"mfa_method_1", "mfa_method_2", "mfa_method_3"}
-
- if !reflect.DeepEqual(expected, actual) {
- t.Fatalf("bad: MFA methods; expected: %#v\n actual: %#v\n", expected, actual)
- }
-
- request.Path = "secret/exact/path"
- actual = acl.AllowOperation(ctx, request, false).MFAMethods
- expected = []string{"mfa_method_4", "mfa_method_5"}
-
- if !reflect.DeepEqual(expected, actual) {
- t.Fatalf("bad: MFA methods; expected: %#v\n actual: %#v\n", expected, actual)
- }
-
- request.Path = "secret/split/definition"
- actual = acl.AllowOperation(ctx, request, false).MFAMethods
- expected = []string{"mfa_method_6", "mfa_method_7", "mfa_method_8", "mfa_method_9"}
-
- if !reflect.DeepEqual(expected, actual) {
- t.Fatalf("bad: MFA methods; expected: %#v\n actual: %#v\n", expected, actual)
- }
-}
-
-func TestACL_Capabilities(t *testing.T) {
- t.Run("root-ns", func(t *testing.T) {
- t.Parallel()
- policy := []*Policy{{Name: "root"}}
- ctx := namespace.RootContext(context.Background())
- acl, err := NewACL(ctx, policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- actual := acl.Capabilities(ctx, "any/path")
- expected := []string{"root"}
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
- }
- testACLCapabilities(t, namespace.RootNamespace)
- })
-}
-
-func testACLCapabilities(t *testing.T, ns *namespace.Namespace) {
- // Create the root policy ACL
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- policy, err := ParseACLPolicy(ns, aclPolicy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- acl, err := NewACL(ctx, []*Policy{policy})
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- actual := acl.Capabilities(ctx, "dev")
- expected := []string{"deny"}
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("bad: path: %s\ngot\n%#v\nexpected\n%#v\n", "deny", actual, expected)
- }
-
- actual = acl.Capabilities(ctx, "dev/")
- expected = []string{"sudo", "read", "list", "update", "delete", "create"}
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("bad: path: %s\ngot\n%#v\nexpected\n%#v\n", "dev/", actual, expected)
- }
-
- actual = acl.Capabilities(ctx, "stage/aws/test")
- expected = []string{"sudo", "read", "list", "update"}
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("bad: path: %s\ngot\n%#v\nexpected\n%#v\n", "stage/aws/test", actual, expected)
- }
-}
-
-func TestACL_Root(t *testing.T) {
- t.Run("root-ns", func(t *testing.T) {
- t.Parallel()
- testACLRoot(t, namespace.RootNamespace)
- })
-}
-
-func testACLRoot(t *testing.T, ns *namespace.Namespace) {
- // Create the root policy ACL. Always create on root namespace regardless of
- // which namespace to ACL check on.
- policy := []*Policy{{Name: "root"}}
- acl, err := NewACL(namespace.RootContext(context.Background()), policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- request := new(logical.Request)
- request.Operation = logical.UpdateOperation
- request.Path = "sys/mount/foo"
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
-
- authResults := acl.AllowOperation(ctx, request, false)
- if !authResults.RootPrivs {
- t.Fatalf("expected root")
- }
- if !authResults.Allowed {
- t.Fatalf("expected permissions")
- }
-}
-
-func TestACL_Single(t *testing.T) {
- t.Run("root-ns", func(t *testing.T) {
- t.Parallel()
- testACLSingle(t, namespace.RootNamespace)
- })
-}
-
-func testACLSingle(t *testing.T, ns *namespace.Namespace) {
- policy, err := ParseACLPolicy(ns, aclPolicy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- acl, err := NewACL(ctx, []*Policy{policy})
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Type of operation is not important here as we only care about checking
- // sudo/root
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- request := new(logical.Request)
- request.Operation = logical.ReadOperation
- request.Path = "sys/mount/foo"
-
- authResults := acl.AllowOperation(ctx, request, false)
- if authResults.RootPrivs {
- t.Fatalf("unexpected root")
- }
-
- type tcase struct {
- op logical.Operation
- path string
- allowed bool
- rootPrivs bool
- }
- tcases := []tcase{
- {logical.ReadOperation, "root", false, false},
- {logical.HelpOperation, "root", true, false},
-
- {logical.ReadOperation, "dev/foo", true, true},
- {logical.UpdateOperation, "dev/foo", true, true},
-
- {logical.DeleteOperation, "stage/foo", true, false},
- {logical.ListOperation, "stage/aws/foo", true, true},
- {logical.UpdateOperation, "stage/aws/foo", true, true},
- {logical.UpdateOperation, "stage/aws/policy/foo", true, true},
-
- {logical.DeleteOperation, "prod/foo", false, false},
- {logical.UpdateOperation, "prod/foo", false, false},
- {logical.ReadOperation, "prod/foo", true, false},
- {logical.ListOperation, "prod/foo", true, false},
- {logical.ReadOperation, "prod/aws/foo", false, false},
-
- {logical.ReadOperation, "foo/bar", true, true},
- {logical.ListOperation, "foo/bar", false, true},
- {logical.UpdateOperation, "foo/bar", false, true},
- {logical.CreateOperation, "foo/bar", true, true},
-
- {logical.ReadOperation, "baz/quux", true, false},
- {logical.CreateOperation, "baz/quux", true, false},
- {logical.PatchOperation, "baz/quux", true, false},
- {logical.ListOperation, "baz/quux", false, false},
- {logical.UpdateOperation, "baz/quux", false, false},
-
- // Path segment wildcards
- {logical.ReadOperation, "test/foo/bar/segment", false, false},
- {logical.ReadOperation, "test/foo/segment", true, false},
- {logical.ReadOperation, "test/bar/segment", true, false},
- {logical.ReadOperation, "test/segment/at/frond", false, false},
- {logical.ReadOperation, "test/segment/at/front", true, false},
- {logical.ReadOperation, "test/segment/at/end/foo", true, false},
- {logical.ReadOperation, "test/segment/at/end/foo/", false, false},
- {logical.ReadOperation, "test/segment/at/end/v2/foo/", true, false},
- {logical.ReadOperation, "test/segment/wildcard/at/foo/", true, false},
- {logical.ReadOperation, "test/segment/wildcard/at/end", true, false},
- {logical.ReadOperation, "test/segment/wildcard/at/end/", true, false},
-
- // Path segment wildcards vs glob
- {logical.ReadOperation, "1/2/3/4", false, false},
- {logical.ReadOperation, "1/2/3", true, false},
- {logical.UpdateOperation, "1/2/3", false, false},
- {logical.UpdateOperation, "1/2/3/4", true, false},
- {logical.CreateOperation, "1/2/3/4/5", true, false},
- }
-
- for _, tc := range tcases {
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- request := new(logical.Request)
- request.Operation = tc.op
- request.Path = tc.path
-
- authResults := acl.AllowOperation(ctx, request, false)
- if authResults.Allowed != tc.allowed {
- t.Fatalf("bad: case %#v: %v, %v", tc, authResults.Allowed, authResults.RootPrivs)
- }
- if authResults.RootPrivs != tc.rootPrivs {
- t.Fatalf("bad: case %#v: %v, %v", tc, authResults.Allowed, authResults.RootPrivs)
- }
- }
-}
-
-func TestACL_Layered(t *testing.T) {
- t.Run("root-ns", func(t *testing.T) {
- t.Parallel()
- policy1, err := ParseACLPolicy(namespace.RootNamespace, aclPolicy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- policy2, err := ParseACLPolicy(namespace.RootNamespace, aclPolicy2)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- acl, err := NewACL(namespace.RootContext(context.Background()), []*Policy{policy1, policy2})
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- testLayeredACL(t, acl, namespace.RootNamespace)
- })
-}
-
-func testLayeredACL(t *testing.T, acl *ACL, ns *namespace.Namespace) {
- // Type of operation is not important here as we only care about checking
- // sudo/root
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- request := new(logical.Request)
- request.Operation = logical.ReadOperation
- request.Path = "sys/mount/foo"
-
- authResults := acl.AllowOperation(ctx, request, false)
- if authResults.RootPrivs {
- t.Fatalf("unexpected root")
- }
-
- type tcase struct {
- op logical.Operation
- path string
- allowed bool
- rootPrivs bool
- }
- tcases := []tcase{
- {logical.ReadOperation, "root", false, false},
- {logical.HelpOperation, "root", true, false},
-
- {logical.ReadOperation, "dev/foo", true, true},
- {logical.UpdateOperation, "dev/foo", true, true},
- {logical.ReadOperation, "dev/hide/foo", false, false},
- {logical.UpdateOperation, "dev/hide/foo", false, false},
-
- {logical.DeleteOperation, "stage/foo", true, false},
- {logical.ListOperation, "stage/aws/foo", true, true},
- {logical.UpdateOperation, "stage/aws/foo", true, true},
- {logical.UpdateOperation, "stage/aws/policy/foo", false, false},
-
- {logical.DeleteOperation, "prod/foo", true, false},
- {logical.UpdateOperation, "prod/foo", true, false},
- {logical.ReadOperation, "prod/foo", true, false},
- {logical.ListOperation, "prod/foo", true, false},
- {logical.ReadOperation, "prod/aws/foo", false, false},
-
- {logical.ReadOperation, "sys/status", false, false},
- {logical.UpdateOperation, "sys/seal", true, true},
-
- {logical.ReadOperation, "foo/bar", false, false},
- {logical.ListOperation, "foo/bar", false, false},
- {logical.UpdateOperation, "foo/bar", false, false},
- {logical.CreateOperation, "foo/bar", false, false},
-
- {logical.ReadOperation, "baz/quux", false, false},
- {logical.ListOperation, "baz/quux", false, false},
- {logical.UpdateOperation, "baz/quux", false, false},
- {logical.CreateOperation, "baz/quux", false, false},
- {logical.PatchOperation, "baz/quux", false, false},
- }
-
- for _, tc := range tcases {
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- request := new(logical.Request)
- request.Operation = tc.op
- request.Path = tc.path
-
- authResults := acl.AllowOperation(ctx, request, false)
- if authResults.Allowed != tc.allowed {
- t.Fatalf("bad: case %#v: %v, %v", tc, authResults.Allowed, authResults.RootPrivs)
- }
- if authResults.RootPrivs != tc.rootPrivs {
- t.Fatalf("bad: case %#v: %v, %v", tc, authResults.Allowed, authResults.RootPrivs)
- }
- }
-}
-
-func TestACL_ParseMalformedPolicy(t *testing.T) {
- _, err := ParseACLPolicy(namespace.RootNamespace, `name{}`)
- if err == nil {
- t.Fatalf("expected error")
- }
-}
-
-func TestACL_PolicyMerge(t *testing.T) {
- t.Run("root-ns", func(t *testing.T) {
- t.Parallel()
- testACLPolicyMerge(t, namespace.RootNamespace)
- })
-}
-
-func testACLPolicyMerge(t *testing.T, ns *namespace.Namespace) {
- policy, err := ParseACLPolicy(ns, mergingPolicies)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- acl, err := NewACL(ctx, []*Policy{policy})
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- type tcase struct {
- path string
- minWrappingTTL *time.Duration
- maxWrappingTTL *time.Duration
- allowed map[string][]interface{}
- denied map[string][]interface{}
- required []string
- }
-
- createDuration := func(seconds int) *time.Duration {
- ret := time.Duration(seconds) * time.Second
- return &ret
- }
-
- tcases := []tcase{
- {"foo/bar", nil, nil, nil, map[string][]interface{}{"zip": {}, "baz": {}}, []string{"baz"}},
- {"hello/universe", createDuration(50), createDuration(200), map[string][]interface{}{"foo": {}, "bar": {}}, nil, []string{"foo", "bar"}},
- {"allow/all", nil, nil, map[string][]interface{}{"*": {}, "test": {}, "test1": {"foo"}}, nil, nil},
- {"allow/all1", nil, nil, map[string][]interface{}{"*": {}, "test": {}, "test1": {"foo"}}, nil, nil},
- {"deny/all", nil, nil, nil, map[string][]interface{}{"*": {}, "test": {}}, nil},
- {"deny/all1", nil, nil, nil, map[string][]interface{}{"*": {}, "test": {}}, nil},
- {"value/merge", nil, nil, map[string][]interface{}{"test": {3, 4, 1, 2}}, map[string][]interface{}{"test": {3, 4, 1, 2}}, nil},
- {"value/empty", nil, nil, map[string][]interface{}{"empty": {}}, map[string][]interface{}{"empty": {}}, nil},
- }
-
- for _, tc := range tcases {
- policyPath := ns.Path + tc.path
- raw, ok := acl.exactRules.Get(policyPath)
- if !ok {
- t.Fatalf("Could not find acl entry for path %s", policyPath)
- }
-
- p := raw.(*ACLPermissions)
- if !reflect.DeepEqual(tc.allowed, p.AllowedParameters) {
- t.Fatalf("Allowed parameters did not match, Expected: %#v, Got: %#v", tc.allowed, p.AllowedParameters)
- }
- if !reflect.DeepEqual(tc.denied, p.DeniedParameters) {
- t.Fatalf("Denied parameters did not match, Expected: %#v, Got: %#v", tc.denied, p.DeniedParameters)
- }
- if !reflect.DeepEqual(tc.required, p.RequiredParameters) {
- t.Fatalf("Required parameters did not match, Expected: %#v, Got: %#v", tc.required, p.RequiredParameters)
- }
- if tc.minWrappingTTL != nil && *tc.minWrappingTTL != p.MinWrappingTTL {
- t.Fatalf("Min wrapping TTL did not match, Expected: %#v, Got: %#v", tc.minWrappingTTL, p.MinWrappingTTL)
- }
- if tc.minWrappingTTL != nil && *tc.maxWrappingTTL != p.MaxWrappingTTL {
- t.Fatalf("Max wrapping TTL did not match, Expected: %#v, Got: %#v", tc.maxWrappingTTL, p.MaxWrappingTTL)
- }
- }
-}
-
-func TestACL_AllowOperation(t *testing.T) {
- t.Run("root-ns", func(t *testing.T) {
- t.Parallel()
- testACLAllowOperation(t, namespace.RootNamespace)
- })
-}
-
-func testACLAllowOperation(t *testing.T, ns *namespace.Namespace) {
- policy, err := ParseACLPolicy(ns, permissionsPolicy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- acl, err := NewACL(ctx, []*Policy{policy})
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- toperations := []logical.Operation{
- logical.UpdateOperation,
- logical.CreateOperation,
- }
- type tcase struct {
- path string
- wrappingTTL *time.Duration
- parameters []string
- allowed bool
- }
-
- createDuration := func(seconds int) *time.Duration {
- ret := time.Duration(seconds) * time.Second
- return &ret
- }
-
- tcases := []tcase{
- {"dev/ops", nil, []string{"zip"}, true},
- {"foo/bar", nil, []string{"zap"}, false},
- {"foo/bar", nil, []string{"zip"}, false},
- {"foo/bar", createDuration(50), []string{"zip"}, false},
- {"foo/bar", createDuration(450), []string{"zip"}, false},
- {"foo/bar", createDuration(350), []string{"zip"}, true},
- {"foo/baz", nil, []string{"hello"}, false},
- {"foo/baz", createDuration(50), []string{"hello"}, false},
- {"foo/baz", createDuration(450), []string{"hello"}, true},
- {"foo/baz", nil, []string{"zap"}, false},
- {"broken/phone", nil, []string{"steve"}, false},
- {"working/phone", nil, []string{""}, false},
- {"working/phone", createDuration(450), []string{""}, false},
- {"working/phone", createDuration(350), []string{""}, true},
- {"hello/world", nil, []string{"one"}, false},
- {"tree/fort", nil, []string{"one"}, true},
- {"tree/fort", nil, []string{"foo"}, false},
- {"fruit/apple", nil, []string{"pear"}, false},
- {"fruit/apple", nil, []string{"one"}, false},
- {"cold/weather", nil, []string{"four"}, true},
- {"var/aws", nil, []string{"cold", "warm", "kitty"}, false},
- {"var/req", nil, []string{"cold", "warm", "kitty"}, false},
- {"var/req", nil, []string{"cold", "warm", "kitty", "foo"}, true},
- }
-
- for _, tc := range tcases {
- request := &logical.Request{
- Path: tc.path,
- Data: make(map[string]interface{}),
- }
-
- for _, parameter := range tc.parameters {
- request.Data[parameter] = ""
- }
- if tc.wrappingTTL != nil {
- request.WrapInfo = &logical.RequestWrapInfo{
- TTL: *tc.wrappingTTL,
- }
- }
- for _, op := range toperations {
- request.Operation = op
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- authResults := acl.AllowOperation(ctx, request, false)
- if authResults.Allowed != tc.allowed {
- t.Fatalf("bad: case %#v: %v", tc, authResults.Allowed)
- }
- }
- }
-}
-
-func TestACL_ValuePermissions(t *testing.T) {
- t.Run("root-ns", func(t *testing.T) {
- t.Parallel()
- testACLValuePermissions(t, namespace.RootNamespace)
- })
-}
-
-func testACLValuePermissions(t *testing.T, ns *namespace.Namespace) {
- policy, err := ParseACLPolicy(ns, valuePermissionsPolicy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- acl, err := NewACL(ctx, []*Policy{policy})
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- toperations := []logical.Operation{
- logical.UpdateOperation,
- logical.CreateOperation,
- }
- type tcase struct {
- path string
- parameters []string
- values []interface{}
- allowed bool
- }
-
- tcases := []tcase{
- {"dev/ops", []string{"allow"}, []interface{}{"good"}, true},
- {"dev/ops", []string{"allow"}, []interface{}{"bad"}, false},
- {"foo/bar", []string{"deny"}, []interface{}{"bad"}, false},
- {"foo/bar", []string{"deny"}, []interface{}{"bad glob"}, false},
- {"foo/bar", []string{"deny"}, []interface{}{"good"}, true},
- {"foo/bar", []string{"allow"}, []interface{}{"good"}, true},
- {"foo/bar", []string{"deny"}, []interface{}{nil}, true},
- {"foo/bar", []string{"allow"}, []interface{}{nil}, true},
- {"foo/baz", []string{"aLLow"}, []interface{}{"good"}, true},
- {"foo/baz", []string{"deny"}, []interface{}{"bad"}, false},
- {"foo/baz", []string{"deny"}, []interface{}{"good"}, false},
- {"foo/baz", []string{"allow", "deny"}, []interface{}{"good", "bad"}, false},
- {"foo/baz", []string{"deny", "allow"}, []interface{}{"good", "bad"}, false},
- {"foo/baz", []string{"deNy", "allow"}, []interface{}{"bad", "good"}, false},
- {"foo/baz", []string{"aLLow"}, []interface{}{"bad"}, false},
- {"foo/baz", []string{"Neither"}, []interface{}{"bad"}, false},
- {"foo/baz", []string{"allow"}, []interface{}{nil}, false},
- {"fizz/buzz", []string{"allow_multi"}, []interface{}{"good"}, true},
- {"fizz/buzz", []string{"allow_multi"}, []interface{}{"good1"}, true},
- {"fizz/buzz", []string{"allow_multi"}, []interface{}{"good2"}, true},
- {"fizz/buzz", []string{"allow_multi"}, []interface{}{"glob good2"}, false},
- {"fizz/buzz", []string{"allow_multi"}, []interface{}{"glob good3"}, true},
- {"fizz/buzz", []string{"allow_multi"}, []interface{}{"bad"}, false},
- {"fizz/buzz", []string{"allow_multi"}, []interface{}{"bad"}, false},
- {"fizz/buzz", []string{"allow_multi", "allow"}, []interface{}{"good1", "good"}, true},
- {"fizz/buzz", []string{"deny_multi"}, []interface{}{"bad2"}, false},
- {"fizz/buzz", []string{"deny_multi", "allow_multi"}, []interface{}{"good", "good2"}, false},
- // {"test/types", []string{"array"}, []interface{}{[1]string{"good"}}, true},
- {"test/types", []string{"map"}, []interface{}{map[string]interface{}{"good": "one"}}, true},
- {"test/types", []string{"map"}, []interface{}{map[string]interface{}{"bad": "one"}}, false},
- {"test/types", []string{"int"}, []interface{}{1}, true},
- {"test/types", []string{"int"}, []interface{}{3}, false},
- {"test/types", []string{"bool"}, []interface{}{false}, true},
- {"test/types", []string{"bool"}, []interface{}{true}, false},
- {"test/star", []string{"anything"}, []interface{}{true}, true},
- {"test/star", []string{"foo"}, []interface{}{true}, true},
- {"test/star", []string{"bar"}, []interface{}{false}, true},
- {"test/star", []string{"bar"}, []interface{}{true}, false},
- }
-
- for _, tc := range tcases {
- request := &logical.Request{
- Path: tc.path,
- Data: make(map[string]interface{}),
- }
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
-
- for i, parameter := range tc.parameters {
- request.Data[parameter] = tc.values[i]
- }
- for _, op := range toperations {
- request.Operation = op
- authResults := acl.AllowOperation(ctx, request, false)
- if authResults.Allowed != tc.allowed {
- t.Fatalf("bad: case %#v: %v", tc, authResults.Allowed)
- }
- }
- }
-}
-
-func TestACL_SegmentWildcardPriority(t *testing.T) {
- ns := namespace.RootNamespace
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- type poltest struct {
- policy string
- path string
- }
-
- // These test cases should each have a read rule and an update rule, where
- // the update rule wins out due to being more specific.
- poltests := []poltest{
- {
- // Verify edge conditions. Here '*' is more specific both because
- // of first wildcard position (0 vs -1/infinity) and #wildcards.
- `
-path "+/*" { capabilities = ["read"] }
-path "*" { capabilities = ["update"] }
-`,
- "foo/bar/bar/baz",
- },
- {
- // Verify edge conditions. Here '+/*' is less specific because of
- // first wildcard position.
- `
-path "+/*" { capabilities = ["read"] }
-path "foo/+/*" { capabilities = ["update"] }
-`,
- "foo/bar/bar/baz",
- },
- {
- // Verify that more wildcard segments is lower priority.
- `
-path "foo/+/+/*" { capabilities = ["read"] }
-path "foo/+/bar/baz" { capabilities = ["update"] }
-`,
- "foo/bar/bar/baz",
- },
- {
- // Verify that more wildcard segments is lower priority.
- `
-path "foo/+/+/baz" { capabilities = ["read"] }
-path "foo/+/bar/baz" { capabilities = ["update"] }
-`,
- "foo/bar/bar/baz",
- },
- {
- // Verify that first wildcard position is lower priority.
- // '(' is used here because it is lexicographically smaller than "+"
- `
-path "foo/+/(ar/baz" { capabilities = ["read"] }
-path "foo/(ar/+/baz" { capabilities = ["update"] }
-`,
- "foo/(ar/(ar/baz",
- },
-
- {
- // Verify that a glob has lower priority, even if the prefix is the
- // same otherwise.
- `
-path "foo/bar/+/baz*" { capabilities = ["read"] }
-path "foo/bar/+/baz" { capabilities = ["update"] }
-`,
- "foo/bar/bar/baz",
- },
- {
- // Verify that a shorter prefix has lower priority.
- `
-path "foo/bar/+/b*" { capabilities = ["read"] }
-path "foo/bar/+/ba*" { capabilities = ["update"] }
-`,
- "foo/bar/bar/baz",
- },
- }
-
- for i, pt := range poltests {
- policy, err := ParseACLPolicy(ns, pt.policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- acl, err := NewACL(ctx, []*Policy{policy})
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- request := new(logical.Request)
- request.Path = pt.path
-
- request.Operation = logical.UpdateOperation
- authResults := acl.AllowOperation(ctx, request, false)
- if !authResults.Allowed {
- t.Fatalf("bad: case %d %#v: %v", i, pt, authResults.Allowed)
- }
-
- request.Operation = logical.ReadOperation
- authResults = acl.AllowOperation(ctx, request, false)
- if authResults.Allowed {
- t.Fatalf("bad: case %d %#v: %v", i, pt, authResults.Allowed)
- }
- }
-}
-
-func TestACL_SegmentWildcardPriority_BareMount(t *testing.T) {
- ns := namespace.RootNamespace
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- type poltest struct {
- policy string
- mountpath string
- hasperms bool
- }
- // These test cases should have one or more rules and a mount prefix.
- // hasperms should be true if there are non-deny perms that apply
- // to the mount prefix or something below it.
- poltests := []poltest{
- {
- `path "+" { capabilities = ["read"] }`,
- "foo/",
- true,
- },
- {
- `path "+/*" { capabilities = ["read"] }`,
- "foo/",
- true,
- },
- {
- `path "foo/+/+/*" { capabilities = ["read"] }`,
- "foo/",
- true,
- },
- {
- `path "foo/+/+/*" { capabilities = ["read"] }`,
- "foo/bar/",
- true,
- },
- {
- `path "foo/+/+/*" { capabilities = ["read"] }`,
- "foo/bar/bar/",
- true,
- },
- {
- `path "foo/+/+/*" { capabilities = ["read"] }`,
- "foo/bar/bar/baz/",
- true,
- },
- {
- `path "foo/+/+/baz" { capabilities = ["read"] }`,
- "foo/bar/bar/baz/",
- true,
- },
- {
- `path "foo/+/bar/baz" { capabilities = ["read"] }`,
- "foo/bar/bar/baz/",
- true,
- },
- {
- `path "foo/bar/+/baz*" { capabilities = ["read"] }`,
- "foo/bar/bar/baz/",
- true,
- },
- {
- `path "foo/bar/+/b*" { capabilities = ["read"] }`,
- "foo/bar/bar/baz/",
- true,
- },
- {
- `path "foo/+" { capabilities = ["read"] }`,
- "foo/",
- true,
- },
- }
-
- for i, pt := range poltests {
- policy, err := ParseACLPolicy(ns, pt.policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- acl, err := NewACL(ctx, []*Policy{policy})
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- hasperms := nil != acl.CheckAllowedFromNonExactPaths(pt.mountpath, true)
- if hasperms != pt.hasperms {
- t.Fatalf("bad: case %d: %#v", i, pt)
- }
-
- }
-}
-
-// NOTE: this test doesn't catch any races ATM
-func TestACL_CreationRace(t *testing.T) {
- policy, err := ParseACLPolicy(namespace.RootNamespace, valuePermissionsPolicy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- var wg sync.WaitGroup
- errs := make(chan error)
- stopTime := time.Now().Add(20 * time.Second)
-
- for i := 0; i < 50; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- for {
- if time.Now().After(stopTime) {
- return
- }
- _, err := NewACL(namespace.RootContext(context.Background()), []*Policy{policy})
- if err != nil {
- errs <- fmt.Errorf("goroutine %d: %w", i, err)
- }
- }
- }(i)
- }
-
- go func() {
- wg.Wait()
- close(errs)
- }()
-
- for err := range errs {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestACLGrantingPolicies(t *testing.T) {
- ns := namespace.RootNamespace
- policy, err := ParseACLPolicy(ns, grantingTestPolicy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- merged, err := ParseACLPolicy(ns, grantingTestPolicyMerged)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
-
- type tcase struct {
- path string
- op logical.Operation
- policies []*Policy
- expected []logical.PolicyInfo
- allowed bool
- }
-
- policyInfo := logical.PolicyInfo{
- Name: "granting_policy",
- NamespaceId: "root",
- NamespacePath: "",
- Type: "acl",
- }
- mergedInfo := logical.PolicyInfo{
- Name: "granting_policy_merged",
- NamespaceId: "root",
- NamespacePath: "",
- Type: "acl",
- }
-
- tcases := []tcase{
- {"kv/foo", logical.ReadOperation, []*Policy{policy}, []logical.PolicyInfo{policyInfo}, true},
- {"kv/foo", logical.UpdateOperation, []*Policy{policy}, []logical.PolicyInfo{policyInfo}, true},
- {"kv/bad", logical.ReadOperation, []*Policy{policy}, nil, false},
- {"kv/deny", logical.ReadOperation, []*Policy{policy}, nil, false},
- {"kv/path/foo", logical.ReadOperation, []*Policy{policy}, []logical.PolicyInfo{policyInfo}, true},
- {"kv/path/longer", logical.ReadOperation, []*Policy{policy}, []logical.PolicyInfo{policyInfo}, true},
- {"kv/foo", logical.ReadOperation, []*Policy{policy, merged}, []logical.PolicyInfo{policyInfo, mergedInfo}, true},
- {"kv/path/longer3", logical.ReadOperation, []*Policy{policy, merged}, []logical.PolicyInfo{mergedInfo}, true},
- {"kv/bar", logical.ReadOperation, []*Policy{policy, merged}, []logical.PolicyInfo{mergedInfo}, true},
- {"kv/deny", logical.ReadOperation, []*Policy{policy, merged}, nil, false},
- {"kv/path/longer", logical.UpdateOperation, []*Policy{policy, merged}, []logical.PolicyInfo{policyInfo}, true},
- {"kv/path/foo", logical.ReadOperation, []*Policy{policy, merged}, []logical.PolicyInfo{policyInfo, mergedInfo}, true},
- }
-
- for _, tc := range tcases {
- request := &logical.Request{
- Path: tc.path,
- Operation: tc.op,
- }
-
- acl, err := NewACL(ctx, tc.policies)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- authResults := acl.AllowOperation(ctx, request, false)
- if authResults.Allowed != tc.allowed {
- t.Fatalf("bad: case %#v: %v", tc, authResults.Allowed)
- }
- if !reflect.DeepEqual(authResults.GrantingPolicies, tc.expected) {
- t.Fatalf("bad: case %#v: got\n%#v\nexpected\n%#v\n", tc, authResults.GrantingPolicies, tc.expected)
- }
- }
-}
-
-var grantingTestPolicy = `
-name = "granting_policy"
-path "kv/foo" {
- capabilities = ["update", "read"]
-}
-
-path "kv/path/*" {
- capabilities = ["read"]
-}
-
-path "kv/path/longer" {
- capabilities = ["update", "read"]
-}
-
-path "kv/path/longer2" {
- capabilities = ["update"]
-}
-
-path "kv/deny" {
- capabilities = ["deny"]
-}
-
-path "ns1/kv/foo" {
- capabilities = ["update", "read"]
-}
-`
-
-var grantingTestPolicyMerged = `
-name = "granting_policy_merged"
-path "kv/foo" {
- capabilities = ["update", "read"]
-}
-
-path "kv/bar" {
- capabilities = ["update", "read"]
-}
-
-path "kv/path/*" {
- capabilities = ["read"]
-}
-
-path "kv/path/longer" {
- capabilities = ["read"]
-}
-
-path "kv/path/longer3" {
- capabilities = ["read"]
-}
-
-path "kv/deny" {
- capabilities = ["update"]
-}
-`
-
-var tokenCreationPolicy = `
-name = "tokenCreation"
-path "auth/token/create*" {
- capabilities = ["update", "create", "sudo"]
-}
-`
-
-var aclPolicy = `
-name = "DeV"
-path "dev/*" {
- policy = "sudo"
-}
-path "stage/*" {
- policy = "write"
-}
-path "stage/aws/*" {
- policy = "read"
- capabilities = ["update", "sudo"]
-}
-path "stage/aws/policy/*" {
- policy = "sudo"
-}
-path "prod/*" {
- policy = "read"
-}
-path "prod/aws/*" {
- policy = "deny"
-}
-path "sys/*" {
- policy = "deny"
-}
-path "foo/bar" {
- capabilities = ["read", "create", "sudo"]
-}
-path "baz/quux" {
- capabilities = ["read", "create", "patch"]
-}
-path "test/+/segment" {
- capabilities = ["read"]
-}
-path "+/segment/at/front" {
- capabilities = ["read"]
-}
-path "test/segment/at/end/+" {
- capabilities = ["read"]
-}
-path "test/segment/at/end/v2/+/" {
- capabilities = ["read"]
-}
-path "test/+/wildcard/+/*" {
- capabilities = ["read"]
-}
-path "test/+/wildcardglob/+/end*" {
- capabilities = ["read"]
-}
-path "1/2/*" {
- capabilities = ["create"]
-}
-path "1/2/+" {
- capabilities = ["read"]
-}
-path "1/2/+/+" {
- capabilities = ["update"]
-}
-`
-
-var aclPolicy2 = `
-name = "OpS"
-path "dev/hide/*" {
- policy = "deny"
-}
-path "stage/aws/policy/*" {
- policy = "deny"
- # This should have no effect
- capabilities = ["read", "update", "sudo"]
-}
-path "prod/*" {
- policy = "write"
-}
-path "sys/seal" {
- policy = "sudo"
-}
-path "foo/bar" {
- capabilities = ["deny"]
-}
-path "baz/quux" {
- capabilities = ["deny"]
-}
-`
-
-// test merging
-var mergingPolicies = `
-name = "ops"
-path "foo/bar" {
- policy = "write"
- denied_parameters = {
- "baz" = []
- }
- required_parameters = ["baz"]
-}
-path "foo/bar" {
- policy = "write"
- denied_parameters = {
- "zip" = []
- }
-}
-path "hello/universe" {
- policy = "write"
- allowed_parameters = {
- "foo" = []
- }
- required_parameters = ["foo"]
- max_wrapping_ttl = 300
- min_wrapping_ttl = 100
-}
-path "hello/universe" {
- policy = "write"
- allowed_parameters = {
- "bar" = []
- }
- required_parameters = ["bar"]
- max_wrapping_ttl = 200
- min_wrapping_ttl = 50
-}
-path "allow/all" {
- policy = "write"
- allowed_parameters = {
- "test" = []
- "test1" = ["foo"]
- }
-}
-path "allow/all" {
- policy = "write"
- allowed_parameters = {
- "*" = []
- }
-}
-path "allow/all1" {
- policy = "write"
- allowed_parameters = {
- "*" = []
- }
-}
-path "allow/all1" {
- policy = "write"
- allowed_parameters = {
- "test" = []
- "test1" = ["foo"]
- }
-}
-path "deny/all" {
- policy = "write"
- denied_parameters = {
- "test" = []
- }
-}
-path "deny/all" {
- policy = "write"
- denied_parameters = {
- "*" = []
- }
-}
-path "deny/all1" {
- policy = "write"
- denied_parameters = {
- "*" = []
- }
-}
-path "deny/all1" {
- policy = "write"
- denied_parameters = {
- "test" = []
- }
-}
-path "value/merge" {
- policy = "write"
- allowed_parameters = {
- "test" = [1, 2]
- }
- denied_parameters = {
- "test" = [1, 2]
- }
-}
-path "value/merge" {
- policy = "write"
- allowed_parameters = {
- "test" = [3, 4]
- }
- denied_parameters = {
- "test" = [3, 4]
- }
-}
-path "value/empty" {
- policy = "write"
- allowed_parameters = {
- "empty" = []
- }
- denied_parameters = {
- "empty" = [1]
- }
-}
-path "value/empty" {
- policy = "write"
- allowed_parameters = {
- "empty" = [1]
- }
- denied_parameters = {
- "empty" = []
- }
-}
-`
-
-// allow operation testing
-var permissionsPolicy = `
-name = "dev"
-path "dev/*" {
- policy = "write"
- allowed_parameters = {
- "zip" = []
- }
-}
-path "foo/bar" {
- policy = "write"
- denied_parameters = {
- "zap" = []
- }
- min_wrapping_ttl = 300
- max_wrapping_ttl = 400
-}
-path "foo/baz" {
- policy = "write"
- allowed_parameters = {
- "hello" = []
- }
- denied_parameters = {
- "zap" = []
- }
- min_wrapping_ttl = 300
-}
-path "working/phone" {
- policy = "write"
- max_wrapping_ttl = 400
-}
-path "broken/phone" {
- policy = "write"
- allowed_parameters = {
- "steve" = []
- }
- denied_parameters = {
- "steve" = []
- }
-}
-path "hello/world" {
- policy = "write"
- allowed_parameters = {
- "*" = []
- }
- denied_parameters = {
- "*" = []
- }
-}
-path "tree/fort" {
- policy = "write"
- allowed_parameters = {
- "*" = []
- }
- denied_parameters = {
- "foo" = []
- }
-}
-path "fruit/apple" {
- policy = "write"
- allowed_parameters = {
- "pear" = []
- }
- denied_parameters = {
- "*" = []
- }
-}
-path "cold/weather" {
- policy = "write"
- allowed_parameters = {}
- denied_parameters = {}
-}
-path "var/aws" {
- policy = "write"
- allowed_parameters = {
- "*" = []
- }
- denied_parameters = {
- "soft" = []
- "warm" = []
- "kitty" = []
- }
-}
-path "var/req" {
- policy = "write"
- required_parameters = ["foo"]
-}
-`
-
-// allow operation testing
-var valuePermissionsPolicy = `
-name = "op"
-path "dev/*" {
- policy = "write"
- allowed_parameters = {
- "allow" = ["good"]
- }
-}
-path "foo/bar" {
- policy = "write"
- denied_parameters = {
- "deny" = ["bad*"]
- }
-}
-path "foo/baz" {
- policy = "write"
- allowed_parameters = {
- "ALLOW" = ["good"]
- }
- denied_parameters = {
- "dEny" = ["bad"]
- }
-}
-path "fizz/buzz" {
- policy = "write"
- allowed_parameters = {
- "allow_multi" = ["good", "good1", "good2", "*good3"]
- "allow" = ["good"]
- }
- denied_parameters = {
- "deny_multi" = ["bad", "bad1", "bad2"]
- }
-}
-path "test/types" {
- policy = "write"
- allowed_parameters = {
- "map" = [{"good" = "one"}]
- "int" = [1, 2]
- "bool" = [false]
- }
- denied_parameters = {
- }
-}
-path "test/star" {
- policy = "write"
- allowed_parameters = {
- "*" = []
- "foo" = []
- "bar" = [false]
- }
- denied_parameters = {
- }
-}
-`
diff --git a/vault/activity/query_test.go b/vault/activity/query_test.go
deleted file mode 100644
index 1c5a83231..000000000
--- a/vault/activity/query_test.go
+++ /dev/null
@@ -1,294 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package activity
-
-import (
- "context"
- "reflect"
- "sort"
- "testing"
- "time"
-
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/helper/timeutil"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func NewTestQueryStore(t *testing.T) *PrecomputedQueryStore {
- t.Helper()
-
- logger := logging.NewVaultLogger(log.Trace)
- view := &logical.InmemStorage{}
- return NewPrecomputedQueryStore(logger, view, 12)
-}
-
-func TestQueryStore_Inventory(t *testing.T) {
- startTimes := []time.Time{
- time.Date(2020, 1, 15, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 3, 1, 0, 0, 0, 0, time.UTC),
- }
-
- endTimes := []time.Time{
- timeutil.EndOfMonth(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
- timeutil.EndOfMonth(time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC)),
- timeutil.EndOfMonth(time.Date(2020, 3, 1, 0, 0, 0, 0, time.UTC)),
- timeutil.EndOfMonth(time.Date(2020, 4, 1, 0, 0, 0, 0, time.UTC)),
- timeutil.EndOfMonth(time.Date(2020, 5, 1, 0, 0, 0, 0, time.UTC)),
- }
-
- qs := NewTestQueryStore(t)
- ctx := context.Background()
-
- for _, s := range startTimes {
- for _, e := range endTimes {
- if e.Before(s) {
- continue
- }
- qs.Put(ctx, &PrecomputedQuery{
- StartTime: s,
- EndTime: e,
- Namespaces: []*NamespaceRecord{},
- })
- }
- }
-
- storedStartTimes, err := qs.listStartTimes(ctx)
- if err != nil {
- t.Fatal(err)
- }
- if len(storedStartTimes) != len(startTimes) {
- t.Fatalf("bad length, expected %v got %v", len(startTimes), storedStartTimes)
- }
- sort.Slice(storedStartTimes, func(i, j int) bool {
- return storedStartTimes[i].Before(storedStartTimes[j])
- })
- if !reflect.DeepEqual(storedStartTimes, startTimes) {
- t.Fatalf("start time mismatch, expected %v got %v", startTimes, storedStartTimes)
- }
-
- storedEndTimes, err := qs.listEndTimes(ctx, startTimes[1])
- if err != nil {
- t.Fatal(err)
- }
- expected := endTimes[1:]
- if len(storedEndTimes) != len(expected) {
- t.Fatalf("bad length, expected %v got %v", len(expected), storedEndTimes)
- }
- sort.Slice(storedEndTimes, func(i, j int) bool {
- return storedEndTimes[i].Before(storedEndTimes[j])
- })
- if !reflect.DeepEqual(storedEndTimes, expected) {
- t.Fatalf("end time mismatch, expected %v got %v", expected, storedEndTimes)
- }
-}
-
-func TestQueryStore_MarshalDemarshal(t *testing.T) {
- tsStart := time.Date(2020, 1, 15, 0, 0, 0, 0, time.UTC)
- tsEnd := timeutil.EndOfMonth(tsStart)
-
- p := &PrecomputedQuery{
- StartTime: tsStart,
- EndTime: tsEnd,
- Namespaces: []*NamespaceRecord{
- {
- NamespaceID: "root",
- Entities: 20,
- NonEntityTokens: 42,
- },
- {
- NamespaceID: "yzABC",
- Entities: 15,
- NonEntityTokens: 31,
- },
- },
- }
-
- qs := NewTestQueryStore(t)
- ctx := context.Background()
- qs.Put(ctx, p)
- result, err := qs.Get(ctx, tsStart, tsEnd)
- if err != nil {
- t.Fatal(err)
- }
- if result == nil {
- t.Fatal("nil response from Get")
- }
- if !reflect.DeepEqual(result, p) {
- t.Fatalf("unequal query objects, expected %v got %v", p, result)
- }
-}
-
-func TestQueryStore_TimeRanges(t *testing.T) {
- qs := NewTestQueryStore(t)
- ctx := context.Background()
-
- // Scenario ranges: Jan 15 - Jan 31 (one month)
- // Feb 2 - Mar 31 (two months, but not contiguous)
- // April and May are skipped
- // June 1 - September 30 (4 months)
- periods := []struct {
- Begin time.Time
- Ends []time.Time
- }{
- {
- time.Date(2020, 1, 15, 12, 45, 53, 0, time.UTC),
- []time.Time{
- timeutil.EndOfMonth(time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC)),
- },
- },
- {
- time.Date(2020, 2, 2, 0, 0, 0, 0, time.UTC),
- []time.Time{
- timeutil.EndOfMonth(time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC)),
- timeutil.EndOfMonth(time.Date(2020, 3, 1, 0, 0, 0, 0, time.UTC)),
- },
- },
- {
- time.Date(2020, 6, 1, 0, 0, 0, 0, time.UTC),
- []time.Time{
- timeutil.EndOfMonth(time.Date(2020, 6, 1, 0, 0, 0, 0, time.UTC)),
- timeutil.EndOfMonth(time.Date(2020, 7, 1, 0, 0, 0, 0, time.UTC)),
- timeutil.EndOfMonth(time.Date(2020, 8, 1, 0, 0, 0, 0, time.UTC)),
- timeutil.EndOfMonth(time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC)),
- },
- },
- }
-
- for _, period := range periods {
- for _, e := range period.Ends {
- qs.Put(ctx, &PrecomputedQuery{
- StartTime: period.Begin,
- EndTime: e,
- Namespaces: []*NamespaceRecord{
- {
- NamespaceID: "root",
- Entities: 17,
- NonEntityTokens: 31,
- },
- },
- })
- }
- }
-
- testCases := []struct {
- Name string
- StartTime time.Time
- EndTime time.Time
- Empty bool
- ExpectedStart time.Time
- ExpectedEnd time.Time
- }{
- {
- "year query in October",
- time.Date(2019, 10, 12, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 10, 12, 0, 0, 0, 0, time.UTC),
- false,
- // June - Sept
- periods[2].Begin,
- periods[2].Ends[3],
- },
- {
- "one day in January",
- time.Date(2020, 1, 4, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 1, 5, 0, 0, 0, 0, time.UTC),
- false,
- // January, even though this is outside the range specified
- periods[0].Begin,
- periods[0].Ends[0],
- },
- {
- "one day in February",
- time.Date(2020, 2, 4, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 2, 5, 0, 0, 0, 0, time.UTC),
- false,
- // February only
- periods[1].Begin,
- periods[1].Ends[0],
- },
- {
- "January through March",
- time.Date(2020, 1, 4, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 3, 5, 0, 0, 0, 0, time.UTC),
- false,
- // February and March only
- // Fails due to bug in library function, TODO
- periods[1].Begin,
- periods[1].Ends[1],
- },
- {
- "the month of May",
- time.Date(2020, 5, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 5, 31, 0, 0, 0, 0, time.UTC),
- true, // no data
- time.Time{},
- time.Time{},
- },
- {
- "May through June",
- time.Date(2020, 5, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 6, 1, 0, 0, 0, 0, time.UTC),
- false,
- // June only
- periods[2].Begin,
- periods[2].Ends[0],
- },
- {
- "September",
- time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC),
- true, // We have June through September,
- // but not anything starting in September
- // (which does not match a real scenario)
- time.Time{},
- time.Time{},
- },
- {
- "December",
- time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC),
- true, // no data
- time.Time{},
- time.Time{},
- },
- {
- "June through December",
- time.Date(2020, 6, 1, 12, 0, 0, 0, time.UTC),
- time.Date(2020, 12, 31, 12, 0, 0, 0, time.UTC),
- false,
- // June through September
- periods[2].Begin,
- periods[2].Ends[3],
- },
- }
-
- for _, tc := range testCases {
- tc := tc // capture range variable
- t.Run(tc.Name, func(t *testing.T) {
- t.Parallel()
- result, err := qs.Get(ctx, tc.StartTime, tc.EndTime)
- if err != nil {
- t.Fatal(err)
- }
- if result == nil {
- if tc.Empty {
- return
- } else {
- t.Fatal("unexpected empty result")
- }
- } else {
- if tc.Empty {
- t.Fatal("expected empty result")
- }
- }
- if !result.StartTime.Equal(tc.ExpectedStart) {
- t.Errorf("start time mismatch: %v, expected %v", result.StartTime, tc.ExpectedStart)
- }
- if !result.EndTime.Equal(tc.ExpectedEnd) {
- t.Errorf("end time mismatch: %v, expected %v", result.EndTime, tc.ExpectedEnd)
- }
- })
- }
-}
diff --git a/vault/activity/test_fixtures/aug.csv b/vault/activity/test_fixtures/aug.csv
deleted file mode 100644
index 575a8953e..000000000
--- a/vault/activity/test_fixtures/aug.csv
+++ /dev/null
@@ -1,21 +0,0 @@
-client_id,namespace_id,timestamp,non_entity,mount_accessor
-111122222-3333-4444-5555-000000000000,root,1,false,auth_1
-111122222-3333-4444-5555-000000000001,root,1,false,auth_1
-111122222-3333-4444-5555-000000000002,root,1,false,auth_1
-111122222-3333-4444-5555-000000000003,root,1,false,auth_1
-111122222-3333-4444-5555-000000000004,root,1,false,auth_1
-111122222-3333-4444-5555-000000000005,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000006,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000007,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000008,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000009,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000010,bbbbb,1,false,auth_3
-111122222-3333-4444-5555-000000000011,bbbbb,1,false,auth_3
-111122222-3333-4444-5555-000000000012,bbbbb,1,false,auth_3
-111122222-3333-4444-5555-000000000013,bbbbb,2,false,auth_3
-111122222-3333-4444-5555-000000000014,bbbbb,2,false,auth_3
-111122222-3333-4444-5555-000000000015,root,2,false,auth_4
-111122222-3333-4444-5555-000000000016,root,2,false,auth_4
-111122222-3333-4444-5555-000000000017,root,2,false,auth_4
-111122222-3333-4444-5555-000000000018,root,2,false,auth_4
-111122222-3333-4444-5555-000000000019,root,2,false,auth_4
diff --git a/vault/activity/test_fixtures/aug.json b/vault/activity/test_fixtures/aug.json
deleted file mode 100644
index 17f117ab2..000000000
--- a/vault/activity/test_fixtures/aug.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{"client_id":"111122222-3333-4444-5555-000000000000","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000001","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000002","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000003","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000004","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000005","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000006","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000007","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000008","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000009","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000010","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000011","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000012","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000013","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000014","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000015","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000016","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000017","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000018","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000019","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
diff --git a/vault/activity/test_fixtures/aug_oct.csv b/vault/activity/test_fixtures/aug_oct.csv
deleted file mode 100644
index d7a3848b1..000000000
--- a/vault/activity/test_fixtures/aug_oct.csv
+++ /dev/null
@@ -1,41 +0,0 @@
-client_id,namespace_id,timestamp,non_entity,mount_accessor
-111122222-3333-4444-5555-000000000000,root,1,false,auth_1
-111122222-3333-4444-5555-000000000001,root,1,false,auth_1
-111122222-3333-4444-5555-000000000002,root,1,false,auth_1
-111122222-3333-4444-5555-000000000003,root,1,false,auth_1
-111122222-3333-4444-5555-000000000004,root,1,false,auth_1
-111122222-3333-4444-5555-000000000005,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000006,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000007,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000008,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000009,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000010,bbbbb,1,false,auth_3
-111122222-3333-4444-5555-000000000011,bbbbb,1,false,auth_3
-111122222-3333-4444-5555-000000000012,bbbbb,1,false,auth_3
-111122222-3333-4444-5555-000000000013,bbbbb,2,false,auth_3
-111122222-3333-4444-5555-000000000014,bbbbb,2,false,auth_3
-111122222-3333-4444-5555-000000000015,root,2,false,auth_4
-111122222-3333-4444-5555-000000000016,root,2,false,auth_4
-111122222-3333-4444-5555-000000000017,root,2,false,auth_4
-111122222-3333-4444-5555-000000000018,root,2,false,auth_4
-111122222-3333-4444-5555-000000000019,root,2,false,auth_4
-111122222-3333-4444-5555-000000000020,root,3,false,auth_5
-111122222-3333-4444-5555-000000000021,root,3,false,auth_5
-111122222-3333-4444-5555-000000000022,root,3,false,auth_5
-111122222-3333-4444-5555-000000000023,root,3,false,auth_5
-111122222-3333-4444-5555-000000000024,root,3,false,auth_5
-111122222-3333-4444-5555-000000000025,ccccc,3,false,auth_6
-111122222-3333-4444-5555-000000000026,ccccc,3,false,auth_6
-111122222-3333-4444-5555-000000000027,ccccc,3,false,auth_6
-111122222-3333-4444-5555-000000000028,ccccc,3,false,auth_6
-111122222-3333-4444-5555-000000000029,ccccc,3,false,auth_6
-111122222-3333-4444-5555-000000000030,root,4,false,auth_7
-111122222-3333-4444-5555-000000000031,root,4,false,auth_7
-111122222-3333-4444-5555-000000000032,root,4,false,auth_7
-111122222-3333-4444-5555-000000000033,root,4,false,auth_7
-111122222-3333-4444-5555-000000000034,root,4,false,auth_7
-111122222-3333-4444-5555-000000000035,bbbbb,4,false,auth_8
-111122222-3333-4444-5555-000000000036,bbbbb,4,false,auth_8
-111122222-3333-4444-5555-000000000037,bbbbb,4,false,auth_8
-111122222-3333-4444-5555-000000000038,bbbbb,4,false,auth_8
-111122222-3333-4444-5555-000000000039,bbbbb,4,false,auth_8
diff --git a/vault/activity/test_fixtures/aug_oct.json b/vault/activity/test_fixtures/aug_oct.json
deleted file mode 100644
index 011b9f6d8..000000000
--- a/vault/activity/test_fixtures/aug_oct.json
+++ /dev/null
@@ -1,40 +0,0 @@
-{"client_id":"111122222-3333-4444-5555-000000000000","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000001","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000002","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000003","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000004","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000005","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000006","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000007","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000008","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000009","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000010","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000011","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000012","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000013","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000014","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000015","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000016","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000017","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000018","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000019","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000020","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"}
-{"client_id":"111122222-3333-4444-5555-000000000021","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"}
-{"client_id":"111122222-3333-4444-5555-000000000022","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"}
-{"client_id":"111122222-3333-4444-5555-000000000023","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"}
-{"client_id":"111122222-3333-4444-5555-000000000024","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"}
-{"client_id":"111122222-3333-4444-5555-000000000025","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"}
-{"client_id":"111122222-3333-4444-5555-000000000026","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"}
-{"client_id":"111122222-3333-4444-5555-000000000027","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"}
-{"client_id":"111122222-3333-4444-5555-000000000028","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"}
-{"client_id":"111122222-3333-4444-5555-000000000029","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"}
-{"client_id":"111122222-3333-4444-5555-000000000030","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"}
-{"client_id":"111122222-3333-4444-5555-000000000031","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"}
-{"client_id":"111122222-3333-4444-5555-000000000032","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"}
-{"client_id":"111122222-3333-4444-5555-000000000033","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"}
-{"client_id":"111122222-3333-4444-5555-000000000034","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"}
-{"client_id":"111122222-3333-4444-5555-000000000035","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"}
-{"client_id":"111122222-3333-4444-5555-000000000036","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"}
-{"client_id":"111122222-3333-4444-5555-000000000037","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"}
-{"client_id":"111122222-3333-4444-5555-000000000038","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"}
-{"client_id":"111122222-3333-4444-5555-000000000039","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"}
diff --git a/vault/activity/test_fixtures/aug_sep.csv b/vault/activity/test_fixtures/aug_sep.csv
deleted file mode 100644
index 34549f8ed..000000000
--- a/vault/activity/test_fixtures/aug_sep.csv
+++ /dev/null
@@ -1,31 +0,0 @@
-client_id,namespace_id,timestamp,non_entity,mount_accessor
-111122222-3333-4444-5555-000000000000,root,1,false,auth_1
-111122222-3333-4444-5555-000000000001,root,1,false,auth_1
-111122222-3333-4444-5555-000000000002,root,1,false,auth_1
-111122222-3333-4444-5555-000000000003,root,1,false,auth_1
-111122222-3333-4444-5555-000000000004,root,1,false,auth_1
-111122222-3333-4444-5555-000000000005,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000006,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000007,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000008,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000009,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000010,bbbbb,1,false,auth_3
-111122222-3333-4444-5555-000000000011,bbbbb,1,false,auth_3
-111122222-3333-4444-5555-000000000012,bbbbb,1,false,auth_3
-111122222-3333-4444-5555-000000000013,bbbbb,2,false,auth_3
-111122222-3333-4444-5555-000000000014,bbbbb,2,false,auth_3
-111122222-3333-4444-5555-000000000015,root,2,false,auth_4
-111122222-3333-4444-5555-000000000016,root,2,false,auth_4
-111122222-3333-4444-5555-000000000017,root,2,false,auth_4
-111122222-3333-4444-5555-000000000018,root,2,false,auth_4
-111122222-3333-4444-5555-000000000019,root,2,false,auth_4
-111122222-3333-4444-5555-000000000020,root,3,false,auth_5
-111122222-3333-4444-5555-000000000021,root,3,false,auth_5
-111122222-3333-4444-5555-000000000022,root,3,false,auth_5
-111122222-3333-4444-5555-000000000023,root,3,false,auth_5
-111122222-3333-4444-5555-000000000024,root,3,false,auth_5
-111122222-3333-4444-5555-000000000025,ccccc,3,false,auth_6
-111122222-3333-4444-5555-000000000026,ccccc,3,false,auth_6
-111122222-3333-4444-5555-000000000027,ccccc,3,false,auth_6
-111122222-3333-4444-5555-000000000028,ccccc,3,false,auth_6
-111122222-3333-4444-5555-000000000029,ccccc,3,false,auth_6
diff --git a/vault/activity/test_fixtures/aug_sep.json b/vault/activity/test_fixtures/aug_sep.json
deleted file mode 100644
index fb3a52193..000000000
--- a/vault/activity/test_fixtures/aug_sep.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{"client_id":"111122222-3333-4444-5555-000000000000","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000001","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000002","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000003","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000004","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000005","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000006","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000007","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000008","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000009","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000010","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000011","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000012","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000013","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000014","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000015","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000016","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000017","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000018","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000019","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000020","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"}
-{"client_id":"111122222-3333-4444-5555-000000000021","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"}
-{"client_id":"111122222-3333-4444-5555-000000000022","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"}
-{"client_id":"111122222-3333-4444-5555-000000000023","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"}
-{"client_id":"111122222-3333-4444-5555-000000000024","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"}
-{"client_id":"111122222-3333-4444-5555-000000000025","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"}
-{"client_id":"111122222-3333-4444-5555-000000000026","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"}
-{"client_id":"111122222-3333-4444-5555-000000000027","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"}
-{"client_id":"111122222-3333-4444-5555-000000000028","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"}
-{"client_id":"111122222-3333-4444-5555-000000000029","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"}
diff --git a/vault/activity/test_fixtures/full_history.csv b/vault/activity/test_fixtures/full_history.csv
deleted file mode 100644
index 7a37ed236..000000000
--- a/vault/activity/test_fixtures/full_history.csv
+++ /dev/null
@@ -1,46 +0,0 @@
-client_id,namespace_id,timestamp,non_entity,mount_accessor
-111122222-3333-4444-5555-000000000040,rrrrr,0,false,auth_9
-111122222-3333-4444-5555-000000000041,rrrrr,0,false,auth_9
-111122222-3333-4444-5555-000000000042,rrrrr,0,false,auth_9
-111122222-3333-4444-5555-000000000043,rrrrr,0,false,auth_9
-111122222-3333-4444-5555-000000000044,rrrrr,0,false,auth_9
-111122222-3333-4444-5555-000000000000,root,1,false,auth_1
-111122222-3333-4444-5555-000000000001,root,1,false,auth_1
-111122222-3333-4444-5555-000000000002,root,1,false,auth_1
-111122222-3333-4444-5555-000000000003,root,1,false,auth_1
-111122222-3333-4444-5555-000000000004,root,1,false,auth_1
-111122222-3333-4444-5555-000000000005,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000006,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000007,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000008,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000009,aaaaa,1,false,auth_2
-111122222-3333-4444-5555-000000000010,bbbbb,1,false,auth_3
-111122222-3333-4444-5555-000000000011,bbbbb,1,false,auth_3
-111122222-3333-4444-5555-000000000012,bbbbb,1,false,auth_3
-111122222-3333-4444-5555-000000000013,bbbbb,2,false,auth_3
-111122222-3333-4444-5555-000000000014,bbbbb,2,false,auth_3
-111122222-3333-4444-5555-000000000015,root,2,false,auth_4
-111122222-3333-4444-5555-000000000016,root,2,false,auth_4
-111122222-3333-4444-5555-000000000017,root,2,false,auth_4
-111122222-3333-4444-5555-000000000018,root,2,false,auth_4
-111122222-3333-4444-5555-000000000019,root,2,false,auth_4
-111122222-3333-4444-5555-000000000020,root,3,false,auth_5
-111122222-3333-4444-5555-000000000021,root,3,false,auth_5
-111122222-3333-4444-5555-000000000022,root,3,false,auth_5
-111122222-3333-4444-5555-000000000023,root,3,false,auth_5
-111122222-3333-4444-5555-000000000024,root,3,false,auth_5
-111122222-3333-4444-5555-000000000025,ccccc,3,false,auth_6
-111122222-3333-4444-5555-000000000026,ccccc,3,false,auth_6
-111122222-3333-4444-5555-000000000027,ccccc,3,false,auth_6
-111122222-3333-4444-5555-000000000028,ccccc,3,false,auth_6
-111122222-3333-4444-5555-000000000029,ccccc,3,false,auth_6
-111122222-3333-4444-5555-000000000030,root,4,false,auth_7
-111122222-3333-4444-5555-000000000031,root,4,false,auth_7
-111122222-3333-4444-5555-000000000032,root,4,false,auth_7
-111122222-3333-4444-5555-000000000033,root,4,false,auth_7
-111122222-3333-4444-5555-000000000034,root,4,false,auth_7
-111122222-3333-4444-5555-000000000035,bbbbb,4,false,auth_8
-111122222-3333-4444-5555-000000000036,bbbbb,4,false,auth_8
-111122222-3333-4444-5555-000000000037,bbbbb,4,false,auth_8
-111122222-3333-4444-5555-000000000038,bbbbb,4,false,auth_8
-111122222-3333-4444-5555-000000000039,bbbbb,4,false,auth_8
diff --git a/vault/activity/test_fixtures/full_history.json b/vault/activity/test_fixtures/full_history.json
deleted file mode 100644
index 7516adfad..000000000
--- a/vault/activity/test_fixtures/full_history.json
+++ /dev/null
@@ -1,45 +0,0 @@
-{"client_id":"111122222-3333-4444-5555-000000000040","namespace_id":"rrrrr","mount_accessor":"auth_9"}
-{"client_id":"111122222-3333-4444-5555-000000000041","namespace_id":"rrrrr","mount_accessor":"auth_9"}
-{"client_id":"111122222-3333-4444-5555-000000000042","namespace_id":"rrrrr","mount_accessor":"auth_9"}
-{"client_id":"111122222-3333-4444-5555-000000000043","namespace_id":"rrrrr","mount_accessor":"auth_9"}
-{"client_id":"111122222-3333-4444-5555-000000000044","namespace_id":"rrrrr","mount_accessor":"auth_9"}
-{"client_id":"111122222-3333-4444-5555-000000000000","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000001","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000002","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000003","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000004","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"}
-{"client_id":"111122222-3333-4444-5555-000000000005","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000006","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000007","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000008","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000009","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"}
-{"client_id":"111122222-3333-4444-5555-000000000010","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000011","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000012","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000013","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000014","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"}
-{"client_id":"111122222-3333-4444-5555-000000000015","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000016","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000017","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000018","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000019","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"}
-{"client_id":"111122222-3333-4444-5555-000000000020","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"}
-{"client_id":"111122222-3333-4444-5555-000000000021","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"}
-{"client_id":"111122222-3333-4444-5555-000000000022","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"}
-{"client_id":"111122222-3333-4444-5555-000000000023","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"}
-{"client_id":"111122222-3333-4444-5555-000000000024","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"}
-{"client_id":"111122222-3333-4444-5555-000000000025","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"}
-{"client_id":"111122222-3333-4444-5555-000000000026","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"}
-{"client_id":"111122222-3333-4444-5555-000000000027","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"}
-{"client_id":"111122222-3333-4444-5555-000000000028","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"}
-{"client_id":"111122222-3333-4444-5555-000000000029","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"}
-{"client_id":"111122222-3333-4444-5555-000000000030","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"}
-{"client_id":"111122222-3333-4444-5555-000000000031","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"}
-{"client_id":"111122222-3333-4444-5555-000000000032","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"}
-{"client_id":"111122222-3333-4444-5555-000000000033","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"}
-{"client_id":"111122222-3333-4444-5555-000000000034","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"}
-{"client_id":"111122222-3333-4444-5555-000000000035","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"}
-{"client_id":"111122222-3333-4444-5555-000000000036","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"}
-{"client_id":"111122222-3333-4444-5555-000000000037","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"}
-{"client_id":"111122222-3333-4444-5555-000000000038","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"}
-{"client_id":"111122222-3333-4444-5555-000000000039","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"}
diff --git a/vault/activity_log_test.go b/vault/activity_log_test.go
deleted file mode 100644
index 1d9f4eb6b..000000000
--- a/vault/activity_log_test.go
+++ /dev/null
@@ -1,4726 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "os"
- "path/filepath"
- "reflect"
- "sort"
- "strconv"
- "strings"
- "sync"
- "testing"
- "time"
-
- "github.com/stretchr/testify/require"
-
- "github.com/hashicorp/go-uuid"
-
- "github.com/axiomhq/hyperloglog"
- "github.com/go-test/deep"
- "github.com/golang/protobuf/proto"
- "github.com/hashicorp/vault/helper/constants"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/helper/timeutil"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault/activity"
- "github.com/mitchellh/mapstructure"
-)
-
-// TestActivityLog_Creation calls AddEntityToFragment and verifies that it appears correctly in a.fragment.
-func TestActivityLog_Creation(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
-
- a := core.activityLog
- a.SetEnable(true)
-
- if a == nil {
- t.Fatal("no activity log found")
- }
- if a.logger == nil || a.view == nil {
- t.Fatal("activity log not initialized")
- }
- if a.fragment != nil {
- t.Fatal("activity log already has fragment")
- }
-
- const entity_id = "entity_id_75432"
- const namespace_id = "ns123"
- ts := time.Now()
-
- a.AddEntityToFragment(entity_id, namespace_id, ts.Unix())
- if a.fragment == nil {
- t.Fatal("no fragment created")
- }
-
- if a.fragment.OriginatingNode != a.nodeID {
- t.Errorf("mismatched node ID, %q vs %q", a.fragment.OriginatingNode, a.nodeID)
- }
-
- if a.fragment.Clients == nil {
- t.Fatal("no fragment entity slice")
- }
-
- if a.fragment.NonEntityTokens == nil {
- t.Fatal("no fragment token map")
- }
-
- if len(a.fragment.Clients) != 1 {
- t.Fatalf("wrong number of entities %v", len(a.fragment.Clients))
- }
-
- er := a.fragment.Clients[0]
- if er.ClientID != entity_id {
- t.Errorf("mimatched entity ID, %q vs %q", er.ClientID, entity_id)
- }
- if er.NamespaceID != namespace_id {
- t.Errorf("mimatched namespace ID, %q vs %q", er.NamespaceID, namespace_id)
- }
- if er.Timestamp != ts.Unix() {
- t.Errorf("mimatched timestamp, %v vs %v", er.Timestamp, ts.Unix())
- }
-
- // Reset and test the other code path
- a.fragment = nil
- a.AddTokenToFragment(namespace_id)
-
- if a.fragment == nil {
- t.Fatal("no fragment created")
- }
-
- if a.fragment.NonEntityTokens == nil {
- t.Fatal("no fragment token map")
- }
-
- actual := a.fragment.NonEntityTokens[namespace_id]
- if actual != 1 {
- t.Errorf("mismatched number of tokens, %v vs %v", actual, 1)
- }
-}
-
-// TestActivityLog_Creation_WrappingTokens calls HandleTokenUsage for two wrapping tokens, and verifies that this
-// doesn't create a fragment.
-func TestActivityLog_Creation_WrappingTokens(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
-
- a := core.activityLog
- a.SetEnable(true)
-
- if a == nil {
- t.Fatal("no activity log found")
- }
- if a.logger == nil || a.view == nil {
- t.Fatal("activity log not initialized")
- }
- a.fragmentLock.Lock()
- if a.fragment != nil {
- t.Fatal("activity log already has fragment")
- }
- a.fragmentLock.Unlock()
- const namespace_id = "ns123"
-
- te := &logical.TokenEntry{
- Path: "test",
- Policies: []string{responseWrappingPolicyName},
- CreationTime: time.Now().Unix(),
- TTL: 3600,
- NamespaceID: namespace_id,
- }
-
- id, isTWE := te.CreateClientID()
- err := a.HandleTokenUsage(context.Background(), te, id, isTWE)
- if err != nil {
- t.Fatal(err)
- }
-
- a.fragmentLock.Lock()
- if a.fragment != nil {
- t.Fatal("fragment created")
- }
- a.fragmentLock.Unlock()
-
- teNew := &logical.TokenEntry{
- Path: "test",
- Policies: []string{controlGroupPolicyName},
- CreationTime: time.Now().Unix(),
- TTL: 3600,
- NamespaceID: namespace_id,
- }
-
- id, isTWE = teNew.CreateClientID()
- err = a.HandleTokenUsage(context.Background(), teNew, id, isTWE)
- if err != nil {
- t.Fatal(err)
- }
-
- a.fragmentLock.Lock()
- if a.fragment != nil {
- t.Fatal("fragment created")
- }
- a.fragmentLock.Unlock()
-}
-
-func checkExpectedEntitiesInMap(t *testing.T, a *ActivityLog, entityIDs []string) {
- t.Helper()
-
- activeClients := a.core.GetActiveClients()
- if len(activeClients) != len(entityIDs) {
- t.Fatalf("mismatched number of entities, expected %v got %v", len(entityIDs), activeClients)
- }
- for _, e := range entityIDs {
- if _, present := activeClients[e]; !present {
- t.Errorf("entity ID %q is missing", e)
- }
- }
-}
-
-// TestActivityLog_UniqueEntities calls AddEntityToFragment 4 times with 2 different clients, then verifies that there
-// are only 2 clients in the fragment and that they have the earlier timestamps.
-func TestActivityLog_UniqueEntities(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
- a.SetEnable(true)
-
- id1 := "11111111-1111-1111-1111-111111111111"
- t1 := time.Now()
-
- id2 := "22222222-2222-2222-2222-222222222222"
- t2 := time.Now()
- t3 := t2.Add(60 * time.Second)
-
- a.AddEntityToFragment(id1, "root", t1.Unix())
- a.AddEntityToFragment(id2, "root", t2.Unix())
- a.AddEntityToFragment(id2, "root", t3.Unix())
- a.AddEntityToFragment(id1, "root", t3.Unix())
-
- if a.fragment == nil {
- t.Fatal("no current fragment")
- }
-
- if len(a.fragment.Clients) != 2 {
- t.Fatalf("number of entities is %v", len(a.fragment.Clients))
- }
-
- for i, e := range a.fragment.Clients {
- expectedID := id1
- expectedTime := t1.Unix()
- expectedNS := "root"
- if i == 1 {
- expectedID = id2
- expectedTime = t2.Unix()
- }
- if e.ClientID != expectedID {
- t.Errorf("%v: expected %q, got %q", i, expectedID, e.ClientID)
- }
- if e.NamespaceID != expectedNS {
- t.Errorf("%v: expected %q, got %q", i, expectedNS, e.NamespaceID)
- }
- if e.Timestamp != expectedTime {
- t.Errorf("%v: expected %v, got %v", i, expectedTime, e.Timestamp)
- }
- }
-
- checkExpectedEntitiesInMap(t, a, []string{id1, id2})
-}
-
-func readSegmentFromStorageNil(t *testing.T, c *Core, path string) {
- t.Helper()
- logSegment, err := c.barrier.Get(context.Background(), path)
- if err != nil {
- t.Fatal(err)
- }
- if logSegment != nil {
- t.Fatalf("expected non-nil log segment at %q", path)
- }
-}
-
-func readSegmentFromStorage(t *testing.T, c *Core, path string) *logical.StorageEntry {
- t.Helper()
- logSegment, err := c.barrier.Get(context.Background(), path)
- if err != nil {
- t.Fatal(err)
- }
- if logSegment == nil {
- t.Fatalf("expected non-nil log segment at %q", path)
- }
-
- return logSegment
-}
-
-func expectMissingSegment(t *testing.T, c *Core, path string) {
- t.Helper()
- logSegment, err := c.barrier.Get(context.Background(), path)
- if err != nil {
- t.Fatal(err)
- }
- if logSegment != nil {
- t.Fatalf("expected nil log segment at %q", path)
- }
-}
-
-func expectedEntityIDs(t *testing.T, out *activity.EntityActivityLog, ids []string) {
- t.Helper()
-
- if len(out.Clients) != len(ids) {
- t.Fatalf("entity log expected length %v, actual %v", len(ids), len(out.Clients))
- }
-
- // Double loop, OK for small cases
- for _, id := range ids {
- found := false
- for _, e := range out.Clients {
- if e.ClientID == id {
- found = true
- break
- }
- }
- if !found {
- t.Errorf("did not find entity ID %v", id)
- }
- }
-}
-
-// TestActivityLog_SaveTokensToStorage calls AddTokenToFragment with duplicate namespaces and then saves the segment to
-// storage. The test then reads and unmarshals the segment, and verifies that the results have the correct counts by
-// namespace.
-func TestActivityLog_SaveTokensToStorage(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- ctx := context.Background()
-
- a := core.activityLog
- a.SetStandbyEnable(ctx, true)
- a.SetStartTimestamp(time.Now().Unix()) // set a nonzero segment
-
- nsIDs := [...]string{"ns1_id", "ns2_id", "ns3_id"}
- path := fmt.Sprintf("%sdirecttokens/%d/0", ActivityLogPrefix, a.GetStartTimestamp())
-
- for i := 0; i < 3; i++ {
- a.AddTokenToFragment(nsIDs[0])
- }
- a.AddTokenToFragment(nsIDs[1])
- err := a.saveCurrentSegmentToStorage(ctx, false)
- if err != nil {
- t.Fatalf("got error writing tokens to storage: %v", err)
- }
- if a.fragment != nil {
- t.Errorf("fragment was not reset after write to storage")
- }
-
- out := &activity.TokenCount{}
- protoSegment := readSegmentFromStorage(t, core, path)
- err = proto.Unmarshal(protoSegment.Value, out)
- if err != nil {
- t.Fatalf("could not unmarshal protobuf: %v", err)
- }
-
- if len(out.CountByNamespaceID) != 2 {
- t.Fatalf("unexpected token length. Expected %d, got %d", 2, len(out.CountByNamespaceID))
- }
- for i := 0; i < 2; i++ {
- if _, ok := out.CountByNamespaceID[nsIDs[i]]; !ok {
- t.Fatalf("namespace ID %s missing from token counts", nsIDs[i])
- }
- }
- if out.CountByNamespaceID[nsIDs[0]] != 3 {
- t.Errorf("namespace ID %s has %d count, expected %d", nsIDs[0], out.CountByNamespaceID[nsIDs[0]], 3)
- }
- if out.CountByNamespaceID[nsIDs[1]] != 1 {
- t.Errorf("namespace ID %s has %d count, expected %d", nsIDs[1], out.CountByNamespaceID[nsIDs[1]], 1)
- }
-
- a.AddTokenToFragment(nsIDs[0])
- a.AddTokenToFragment(nsIDs[2])
- err = a.saveCurrentSegmentToStorage(ctx, false)
- if err != nil {
- t.Fatalf("got error writing tokens to storage: %v", err)
- }
- if a.fragment != nil {
- t.Errorf("fragment was not reset after write to storage")
- }
-
- protoSegment = readSegmentFromStorage(t, core, path)
- out = &activity.TokenCount{}
- err = proto.Unmarshal(protoSegment.Value, out)
- if err != nil {
- t.Fatalf("could not unmarshal protobuf: %v", err)
- }
-
- if len(out.CountByNamespaceID) != 3 {
- t.Fatalf("unexpected token length. Expected %d, got %d", 3, len(out.CountByNamespaceID))
- }
- for i := 0; i < 3; i++ {
- if _, ok := out.CountByNamespaceID[nsIDs[i]]; !ok {
- t.Fatalf("namespace ID %s missing from token counts", nsIDs[i])
- }
- }
- if out.CountByNamespaceID[nsIDs[0]] != 4 {
- t.Errorf("namespace ID %s has %d count, expected %d", nsIDs[0], out.CountByNamespaceID[nsIDs[0]], 4)
- }
- if out.CountByNamespaceID[nsIDs[1]] != 1 {
- t.Errorf("namespace ID %s has %d count, expected %d", nsIDs[1], out.CountByNamespaceID[nsIDs[1]], 1)
- }
- if out.CountByNamespaceID[nsIDs[2]] != 1 {
- t.Errorf("namespace ID %s has %d count, expected %d", nsIDs[2], out.CountByNamespaceID[nsIDs[2]], 1)
- }
-}
-
-// TestActivityLog_SaveTokensToStorageDoesNotUpdateTokenCount ensures that
-// a new fragment with nonEntityTokens will not update the currentSegment's
-// tokenCount, as this field will not be used going forward.
-func TestActivityLog_SaveTokensToStorageDoesNotUpdateTokenCount(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- ctx := context.Background()
-
- a := core.activityLog
- a.SetStandbyEnable(ctx, true)
- a.SetStartTimestamp(time.Now().Unix()) // set a nonzero segment
-
- tokenPath := fmt.Sprintf("%sdirecttokens/%d/0", ActivityLogPrefix, a.GetStartTimestamp())
- clientPath := fmt.Sprintf("sys/counters/activity/log/entity/%d/0", a.GetStartTimestamp())
- // Create some entries without entityIDs
- tokenEntryOne := logical.TokenEntry{NamespaceID: namespace.RootNamespaceID, Policies: []string{"hi"}}
- entityEntry := logical.TokenEntry{EntityID: "foo", NamespaceID: namespace.RootNamespaceID, Policies: []string{"hi"}}
-
- idNonEntity, isTWE := tokenEntryOne.CreateClientID()
-
- for i := 0; i < 3; i++ {
- err := a.HandleTokenUsage(ctx, &tokenEntryOne, idNonEntity, isTWE)
- if err != nil {
- t.Fatal(err)
- }
- }
-
- idEntity, isTWE := entityEntry.CreateClientID()
- for i := 0; i < 2; i++ {
- err := a.HandleTokenUsage(ctx, &entityEntry, idEntity, isTWE)
- if err != nil {
- t.Fatal(err)
- }
- }
- err := a.saveCurrentSegmentToStorage(ctx, false)
- if err != nil {
- t.Fatalf("got error writing TWEs to storage: %v", err)
- }
-
- // Assert that new elements have been written to the fragment
- if a.fragment != nil {
- t.Errorf("fragment was not reset after write to storage")
- }
-
- // Assert that no tokens have been written to the fragment
- readSegmentFromStorageNil(t, core, tokenPath)
-
- e := readSegmentFromStorage(t, core, clientPath)
- out := &activity.EntityActivityLog{}
- err = proto.Unmarshal(e.Value, out)
- if err != nil {
- t.Fatalf("could not unmarshal protobuf: %v", err)
- }
- if len(out.Clients) != 2 {
- t.Fatalf("added 3 distinct TWEs and 2 distinct entity tokens that should all result in the same ID, got: %d", len(out.Clients))
- }
- nonEntityTokenFlag := false
- entityTokenFlag := false
- for _, client := range out.Clients {
- if client.NonEntity == true {
- nonEntityTokenFlag = true
- if client.ClientID != idNonEntity {
- t.Fatalf("expected a client ID of %s, but saved instead %s", idNonEntity, client.ClientID)
- }
- }
- if client.NonEntity == false {
- entityTokenFlag = true
- if client.ClientID != idEntity {
- t.Fatalf("expected a client ID of %s, but saved instead %s", idEntity, client.ClientID)
- }
- }
- }
-
- if !nonEntityTokenFlag || !entityTokenFlag {
- t.Fatalf("Saved clients missing TWE: %v; saved clients missing entity token: %v", nonEntityTokenFlag, entityTokenFlag)
- }
-}
-
-// TestActivityLog_SaveEntitiesToStorage calls AddEntityToFragment with clients with different namespaces and then
-// writes the segment to storage. Read back from storage, and verify that client IDs exist in storage.
-func TestActivityLog_SaveEntitiesToStorage(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- ctx := context.Background()
-
- a := core.activityLog
- a.SetStandbyEnable(ctx, true)
- a.SetStartTimestamp(time.Now().Unix()) // set a nonzero segment
-
- now := time.Now()
- ids := []string{"11111111-1111-1111-1111-111111111111", "22222222-2222-2222-2222-222222222222", "33333333-2222-2222-2222-222222222222"}
- times := [...]int64{
- now.Unix(),
- now.Add(1 * time.Second).Unix(),
- now.Add(2 * time.Second).Unix(),
- }
- path := fmt.Sprintf("%sentity/%d/0", ActivityLogPrefix, a.GetStartTimestamp())
-
- a.AddEntityToFragment(ids[0], "root", times[0])
- a.AddEntityToFragment(ids[1], "root2", times[1])
- err := a.saveCurrentSegmentToStorage(ctx, false)
- if err != nil {
- t.Fatalf("got error writing entities to storage: %v", err)
- }
- if a.fragment != nil {
- t.Errorf("fragment was not reset after write to storage")
- }
-
- protoSegment := readSegmentFromStorage(t, core, path)
- out := &activity.EntityActivityLog{}
- err = proto.Unmarshal(protoSegment.Value, out)
- if err != nil {
- t.Fatalf("could not unmarshal protobuf: %v", err)
- }
- expectedEntityIDs(t, out, ids[:2])
-
- a.AddEntityToFragment(ids[0], "root", times[2])
- a.AddEntityToFragment(ids[2], "root", times[2])
- err = a.saveCurrentSegmentToStorage(ctx, false)
- if err != nil {
- t.Fatalf("got error writing segments to storage: %v", err)
- }
-
- protoSegment = readSegmentFromStorage(t, core, path)
- out = &activity.EntityActivityLog{}
- err = proto.Unmarshal(protoSegment.Value, out)
- if err != nil {
- t.Fatalf("could not unmarshal protobuf: %v", err)
- }
- expectedEntityIDs(t, out, ids)
-}
-
-// TestActivityLog_StoreAndReadHyperloglog inserts into a hyperloglog, stores it and then reads it back. The test
-// verifies the estimate count is correct.
-func TestActivityLog_StoreAndReadHyperloglog(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- ctx := context.Background()
-
- a := core.activityLog
- a.SetStandbyEnable(ctx, true)
- a.SetStartTimestamp(time.Now().Unix()) // set a nonzero segment
- currentMonth := timeutil.StartOfMonth(time.Now())
- currentMonthHll := hyperloglog.New()
- currentMonthHll.Insert([]byte("a"))
- currentMonthHll.Insert([]byte("a"))
- currentMonthHll.Insert([]byte("b"))
- currentMonthHll.Insert([]byte("c"))
- currentMonthHll.Insert([]byte("d"))
- currentMonthHll.Insert([]byte("d"))
-
- err := a.StoreHyperlogLog(ctx, currentMonth, currentMonthHll)
- if err != nil {
- t.Fatalf("error storing hyperloglog in storage: %v", err)
- }
- fetchedHll, err := a.CreateOrFetchHyperlogLog(ctx, currentMonth)
- // check the distinct count stored from hll
- if fetchedHll.Estimate() != 4 {
- t.Fatalf("wrong number of distinct elements: expected: 5 actual: %v", fetchedHll.Estimate())
- }
-}
-
-// TestModifyResponseMonthsNilAppend calls modifyResponseMonths for a range of 5 months ago to now. It verifies that the
-// 5 months in the range are correct.
-func TestModifyResponseMonthsNilAppend(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
- end := time.Now().UTC()
- start := timeutil.StartOfMonth(end).AddDate(0, -5, 0)
- responseMonthTimestamp := timeutil.StartOfMonth(end).AddDate(0, -3, 0).Format(time.RFC3339)
- responseMonths := []*ResponseMonth{{Timestamp: responseMonthTimestamp}}
- months := a.modifyResponseMonths(responseMonths, start, end)
- if len(months) != 5 {
- t.Fatal("wrong number of months padded")
- }
- for _, m := range months {
- ts, err := time.Parse(time.RFC3339, m.Timestamp)
- if err != nil {
- t.Fatal(err)
- }
- if !ts.Equal(start) {
- t.Fatalf("incorrect time in month sequence timestamps: expected %+v, got %+v", start, ts)
- }
- start = timeutil.StartOfMonth(start).AddDate(0, 1, 0)
- }
- // The following is a redundant check, but for posterity and readability I've
- // made it explicit.
- lastMonth, err := time.Parse(time.RFC3339, months[4].Timestamp)
- if err != nil {
- t.Fatal(err)
- }
- if timeutil.IsCurrentMonth(lastMonth, time.Now().UTC()) {
- t.Fatalf("do not include current month timestamp in nil padding for months")
- }
-}
-
-// TestActivityLog_ReceivedFragment calls receivedFragment with a fragment and verifies it gets added to
-// standbyFragmentsReceived. Send the same fragment again and then verify that it doesn't change the entity map but does
-// get added to standbyFragmentsReceived.
-func TestActivityLog_ReceivedFragment(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
- a.SetEnable(true)
-
- ids := []string{
- "11111111-1111-1111-1111-111111111111",
- "22222222-2222-2222-2222-222222222222",
- }
-
- entityRecords := []*activity.EntityRecord{
- {
- ClientID: ids[0],
- NamespaceID: "root",
- Timestamp: time.Now().Unix(),
- },
- {
- ClientID: ids[1],
- NamespaceID: "root",
- Timestamp: time.Now().Unix(),
- },
- }
-
- fragment := &activity.LogFragment{
- OriginatingNode: "test-123",
- Clients: entityRecords,
- NonEntityTokens: make(map[string]uint64),
- }
-
- if len(a.standbyFragmentsReceived) != 0 {
- t.Fatalf("fragment already received")
- }
-
- a.receivedFragment(fragment)
-
- checkExpectedEntitiesInMap(t, a, ids)
-
- if len(a.standbyFragmentsReceived) != 1 {
- t.Fatalf("fragment count is %v, expected 1", len(a.standbyFragmentsReceived))
- }
-
- // Send a duplicate, should be stored but not change entity map
- a.receivedFragment(fragment)
-
- checkExpectedEntitiesInMap(t, a, ids)
-
- if len(a.standbyFragmentsReceived) != 2 {
- t.Fatalf("fragment count is %v, expected 2", len(a.standbyFragmentsReceived))
- }
-}
-
-// TestActivityLog_availableLogsEmptyDirectory verifies that availableLogs returns an empty slice when the log directory
-// is empty.
-func TestActivityLog_availableLogsEmptyDirectory(t *testing.T) {
- // verify that directory is empty, and nothing goes wrong
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
- times, err := a.availableLogs(context.Background())
- if err != nil {
- t.Fatalf("error getting start_time(s) for empty activity log")
- }
- if len(times) != 0 {
- t.Fatalf("invalid number of start_times returned. expected 0, got %d", len(times))
- }
-}
-
-// TestActivityLog_availableLogs writes to the direct token paths and entity paths and verifies that the correct start
-// times are returned.
-func TestActivityLog_availableLogs(t *testing.T) {
- // set up a few files in storage
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
- paths := [...]string{"entity/1111/1", "directtokens/1111/1", "directtokens/1000000/1", "entity/992/3", "directtokens/992/1"}
- expectedTimes := [...]time.Time{time.Unix(1000000, 0), time.Unix(1111, 0), time.Unix(992, 0)}
-
- for _, path := range paths {
- WriteToStorage(t, core, ActivityLogPrefix+path, []byte("test"))
- }
-
- // verify above files are there, and dates in correct order
- times, err := a.availableLogs(context.Background())
- if err != nil {
- t.Fatalf("error getting start_time(s) for activity log")
- }
-
- if len(times) != len(expectedTimes) {
- t.Fatalf("invalid number of start_times returned. expected %d, got %d", len(expectedTimes), len(times))
- }
- for i := range times {
- if !times[i].Equal(expectedTimes[i]) {
- t.Errorf("invalid time. expected %v, got %v", expectedTimes[i], times[i])
- }
- }
-}
-
-// TestActivityLog_MultipleFragmentsAndSegments adds 4000 clients to a fragment
-// and saves it and reads it. The test then adds 4000 more clients and calls
-// receivedFragment with 200 more entities. The current segment is saved to
-// storage and read back. The test verifies that there are ActivitySegmentClientCapacity clients in the
-// first and second segment index, then the rest in the third index.
-func TestActivityLog_MultipleFragmentsAndSegments(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
-
- // enabled check is now inside AddClientToFragment
- a.SetEnable(true)
- a.SetStartTimestamp(time.Now().Unix()) // set a nonzero segment
-
- // Stop timers for test purposes
- close(a.doneCh)
- defer func() {
- a.l.Lock()
- a.doneCh = make(chan struct{}, 1)
- a.l.Unlock()
- }()
-
- startTimestamp := a.GetStartTimestamp()
- path0 := fmt.Sprintf("sys/counters/activity/log/entity/%d/0", startTimestamp)
- path1 := fmt.Sprintf("sys/counters/activity/log/entity/%d/1", startTimestamp)
- path2 := fmt.Sprintf("sys/counters/activity/log/entity/%d/2", startTimestamp)
- tokenPath := fmt.Sprintf("sys/counters/activity/log/directtokens/%d/0", startTimestamp)
-
- genID := func(i int) string {
- return fmt.Sprintf("11111111-1111-1111-1111-%012d", i)
- }
- ts := time.Now().Unix()
-
- // First ActivitySegmentClientCapacity should fit in one segment
- for i := 0; i < 4000; i++ {
- a.AddEntityToFragment(genID(i), "root", ts)
- }
-
- // Consume new fragment notification.
- // The worker may have gotten it first, before processing
- // the close!
- select {
- case <-a.newFragmentCh:
- default:
- }
-
- // Save segment
- err := a.saveCurrentSegmentToStorage(context.Background(), false)
- if err != nil {
- t.Fatalf("got error writing entities to storage: %v", err)
- }
-
- protoSegment0 := readSegmentFromStorage(t, core, path0)
- entityLog0 := activity.EntityActivityLog{}
- err = proto.Unmarshal(protoSegment0.Value, &entityLog0)
- if err != nil {
- t.Fatalf("could not unmarshal protobuf: %v", err)
- }
- if len(entityLog0.Clients) != ActivitySegmentClientCapacity {
- t.Fatalf("unexpected entity length. Expected %d, got %d", ActivitySegmentClientCapacity, len(entityLog0.Clients))
- }
-
- // 4000 more local entities
- for i := 4000; i < 8000; i++ {
- a.AddEntityToFragment(genID(i), "root", ts)
- }
-
- // Simulated remote fragment with 100 duplicate entities
- tokens1 := map[string]uint64{
- "root": 3,
- "aaaaa": 4,
- "bbbbb": 5,
- }
- fragment1 := &activity.LogFragment{
- OriginatingNode: "test-123",
- Clients: make([]*activity.EntityRecord, 0, 100),
- NonEntityTokens: tokens1,
- }
- for i := 4000; i < 4100; i++ {
- fragment1.Clients = append(fragment1.Clients, &activity.EntityRecord{
- ClientID: genID(i),
- NamespaceID: "root",
- Timestamp: ts,
- })
- }
-
- // Simulated remote fragment with 100 new entities
- tokens2 := map[string]uint64{
- "root": 6,
- "aaaaa": 7,
- "bbbbb": 8,
- }
- fragment2 := &activity.LogFragment{
- OriginatingNode: "test-123",
- Clients: make([]*activity.EntityRecord, 0, 100),
- NonEntityTokens: tokens2,
- }
- for i := 8000; i < 8100; i++ {
- fragment2.Clients = append(fragment2.Clients, &activity.EntityRecord{
- ClientID: genID(i),
- NamespaceID: "root",
- Timestamp: ts,
- })
- }
- a.receivedFragment(fragment1)
- a.receivedFragment(fragment2)
-
- select {
- case <-a.newFragmentCh:
- case <-time.After(time.Minute):
- t.Fatal("timed out waiting for new fragment")
- }
-
- err = a.saveCurrentSegmentToStorage(context.Background(), false)
- if err != nil {
- t.Fatalf("got error writing entities to storage: %v", err)
- }
-
- seqNum := a.GetEntitySequenceNumber()
- if seqNum != 2 {
- t.Fatalf("expected sequence number 2, got %v", seqNum)
- }
-
- protoSegment0 = readSegmentFromStorage(t, core, path0)
- err = proto.Unmarshal(protoSegment0.Value, &entityLog0)
- if err != nil {
- t.Fatalf("could not unmarshal protobuf: %v", err)
- }
- if len(entityLog0.Clients) != ActivitySegmentClientCapacity {
- t.Fatalf("unexpected client length. Expected %d, got %d", ActivitySegmentClientCapacity,
- len(entityLog0.Clients))
- }
-
- protoSegment1 := readSegmentFromStorage(t, core, path1)
- entityLog1 := activity.EntityActivityLog{}
- err = proto.Unmarshal(protoSegment1.Value, &entityLog1)
- if err != nil {
- t.Fatalf("could not unmarshal protobuf: %v", err)
- }
- if len(entityLog1.Clients) != ActivitySegmentClientCapacity {
- t.Fatalf("unexpected entity length. Expected %d, got %d", ActivitySegmentClientCapacity,
- len(entityLog1.Clients))
- }
-
- protoSegment2 := readSegmentFromStorage(t, core, path2)
- entityLog2 := activity.EntityActivityLog{}
- err = proto.Unmarshal(protoSegment2.Value, &entityLog2)
- if err != nil {
- t.Fatalf("could not unmarshal protobuf: %v", err)
- }
- expectedCount := 8100 - (ActivitySegmentClientCapacity * 2)
- if len(entityLog2.Clients) != expectedCount {
- t.Fatalf("unexpected entity length. Expected %d, got %d", expectedCount,
- len(entityLog1.Clients))
- }
-
- entityPresent := make(map[string]struct{})
- for _, e := range entityLog0.Clients {
- entityPresent[e.ClientID] = struct{}{}
- }
- for _, e := range entityLog1.Clients {
- entityPresent[e.ClientID] = struct{}{}
- }
- for _, e := range entityLog2.Clients {
- entityPresent[e.ClientID] = struct{}{}
- }
- for i := 0; i < 8100; i++ {
- expectedID := genID(i)
- if _, present := entityPresent[expectedID]; !present {
- t.Fatalf("entity ID %v = %v not present", i, expectedID)
- }
- }
- expectedNSCounts := map[string]uint64{
- "root": 9,
- "aaaaa": 11,
- "bbbbb": 13,
- }
- tokenSegment := readSegmentFromStorage(t, core, tokenPath)
- tokenCount := activity.TokenCount{}
- err = proto.Unmarshal(tokenSegment.Value, &tokenCount)
- if err != nil {
- t.Fatalf("could not unmarshal protobuf: %v", err)
- }
-
- if !reflect.DeepEqual(expectedNSCounts, tokenCount.CountByNamespaceID) {
- t.Fatalf("token counts are not equal, expected %v got %v", expectedNSCounts, tokenCount.CountByNamespaceID)
- }
-}
-
-// TestActivityLog_API_ConfigCRUD_Census performs various CRUD operations on internal/counters/config
-// depending on license reporting
-func TestActivityLog_API_ConfigCRUD_Census(t *testing.T) {
- core, b, _ := testCoreSystemBackend(t)
- view := core.systemBarrierView
-
- req := logical.TestRequest(t, logical.UpdateOperation, "internal/counters/config")
- req.Storage = view
- req.Data["retention_months"] = 2
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if core.ManualLicenseReportingEnabled() {
- if err == nil {
- t.Fatal("expected error")
- }
- if resp.Data["error"] != `retention_months must be at least 48 while Reporting is enabled` {
- t.Fatalf("bad: %v", resp)
- }
- } else {
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "internal/counters/config")
- req.Storage = view
- req.Data["retention_months"] = 56
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "internal/counters/config")
- req.Storage = view
- req.Data["enabled"] = "disable"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if core.ManualLicenseReportingEnabled() {
- if err == nil {
- t.Fatal("expected error")
- }
- if resp.Data["error"] != `cannot disable the activity log while Reporting is enabled` {
- t.Fatalf("bad: %v", resp)
- }
- } else {
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "internal/counters/config")
- req.Storage = view
- req.Data["enabled"] = "enable"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "internal/counters/config")
- req.Storage = view
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- expected := map[string]interface{}{
- "default_report_months": 12,
- "retention_months": 56,
- "enabled": "enable",
- "queries_available": false,
- "reporting_enabled": core.AutomatedLicenseReportingEnabled(),
- "billing_start_timestamp": core.BillingStart(),
- "minimum_retention_months": core.activityLog.configOverrides.MinimumRetentionMonths,
- }
-
- if diff := deep.Equal(resp.Data, expected); len(diff) > 0 {
- t.Fatalf("diff: %v", diff)
- }
-}
-
-// TestActivityLog_parseSegmentNumberFromPath verifies that the segment number is extracted correctly from a path.
-func TestActivityLog_parseSegmentNumberFromPath(t *testing.T) {
- testCases := []struct {
- input string
- expected int
- expectExists bool
- }{
- {
- input: "path/to/log/5",
- expected: 5,
- expectExists: true,
- },
- {
- input: "/path/to/log/5",
- expected: 5,
- expectExists: true,
- },
- {
- input: "path/to/log/",
- expected: 0,
- expectExists: false,
- },
- {
- input: "path/to/log/foo",
- expected: 0,
- expectExists: false,
- },
- {
- input: "",
- expected: 0,
- expectExists: false,
- },
- {
- input: "5",
- expected: 5,
- expectExists: true,
- },
- }
-
- for _, tc := range testCases {
- result, ok := parseSegmentNumberFromPath(tc.input)
- if result != tc.expected {
- t.Errorf("expected: %d, got: %d for input %q", tc.expected, result, tc.input)
- }
- if ok != tc.expectExists {
- t.Errorf("unexpected value presence. expected exists: %t, got: %t for input %q", tc.expectExists, ok, tc.input)
- }
- }
-}
-
-// TestActivityLog_getLastEntitySegmentNumber verifies that the last segment number is correctly returned.
-func TestActivityLog_getLastEntitySegmentNumber(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
- paths := [...]string{"entity/992/0", "entity/1000/-1", "entity/1001/foo", "entity/1111/0", "entity/1111/1"}
- for _, path := range paths {
- WriteToStorage(t, core, ActivityLogPrefix+path, []byte("test"))
- }
-
- testCases := []struct {
- input int64
- expectedVal uint64
- expectExists bool
- }{
- {
- input: 992,
- expectedVal: 0,
- expectExists: true,
- },
- {
- input: 1000,
- expectedVal: 0,
- expectExists: false,
- },
- {
- input: 1001,
- expectedVal: 0,
- expectExists: false,
- },
- {
- input: 1111,
- expectedVal: 1,
- expectExists: true,
- },
- {
- input: 2222,
- expectedVal: 0,
- expectExists: false,
- },
- }
-
- ctx := context.Background()
- for _, tc := range testCases {
- result, exists, err := a.getLastEntitySegmentNumber(ctx, time.Unix(tc.input, 0))
- if err != nil {
- t.Fatalf("unexpected error for input %d: %v", tc.input, err)
- }
- if exists != tc.expectExists {
- t.Errorf("expected result exists: %t, got: %t for input: %d", tc.expectExists, exists, tc.input)
- }
- if result != tc.expectedVal {
- t.Errorf("expected: %d got: %d for input: %d", tc.expectedVal, result, tc.input)
- }
- }
-}
-
-// TestActivityLog_tokenCountExists writes to the direct tokens segment path and verifies that segment count exists
-// returns true for the segments at these paths.
-func TestActivityLog_tokenCountExists(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
- paths := [...]string{"directtokens/992/0", "directtokens/1001/foo", "directtokens/1111/0", "directtokens/2222/1"}
- for _, path := range paths {
- WriteToStorage(t, core, ActivityLogPrefix+path, []byte("test"))
- }
-
- testCases := []struct {
- input int64
- expectExists bool
- }{
- {
- input: 992,
- expectExists: true,
- },
- {
- input: 1001,
- expectExists: false,
- },
- {
- input: 1111,
- expectExists: true,
- },
- {
- input: 2222,
- expectExists: false,
- },
- }
-
- ctx := context.Background()
- for _, tc := range testCases {
- exists, err := a.tokenCountExists(ctx, time.Unix(tc.input, 0))
- if err != nil {
- t.Fatalf("unexpected error for input %d: %v", tc.input, err)
- }
- if exists != tc.expectExists {
- t.Errorf("expected segment to exist: %t but got: %t for input: %d", tc.expectExists, exists, tc.input)
- }
- }
-}
-
-// entityRecordsEqual compares the parts we care about from two activity entity record slices
-// note: this makes a copy of the []*activity.EntityRecord so that misordered slices won't fail the comparison,
-// but the function won't modify the order of the slices to compare
-func entityRecordsEqual(t *testing.T, record1, record2 []*activity.EntityRecord) bool {
- t.Helper()
-
- if record1 == nil {
- return record2 == nil
- }
- if record2 == nil {
- return record1 == nil
- }
-
- if len(record1) != len(record2) {
- return false
- }
-
- // sort first on namespace, then on ID, then on timestamp
- entityLessFn := func(e []*activity.EntityRecord, i, j int) bool {
- ei := e[i]
- ej := e[j]
-
- nsComp := strings.Compare(ei.NamespaceID, ej.NamespaceID)
- if nsComp == -1 {
- return true
- }
- if nsComp == 1 {
- return false
- }
-
- idComp := strings.Compare(ei.ClientID, ej.ClientID)
- if idComp == -1 {
- return true
- }
- if idComp == 1 {
- return false
- }
-
- return ei.Timestamp < ej.Timestamp
- }
-
- entitiesCopy1 := make([]*activity.EntityRecord, len(record1))
- entitiesCopy2 := make([]*activity.EntityRecord, len(record2))
- copy(entitiesCopy1, record1)
- copy(entitiesCopy2, record2)
-
- sort.Slice(entitiesCopy1, func(i, j int) bool {
- return entityLessFn(entitiesCopy1, i, j)
- })
- sort.Slice(entitiesCopy2, func(i, j int) bool {
- return entityLessFn(entitiesCopy2, i, j)
- })
-
- for i, a := range entitiesCopy1 {
- b := entitiesCopy2[i]
- if a.ClientID != b.ClientID || a.NamespaceID != b.NamespaceID || a.Timestamp != b.Timestamp {
- return false
- }
- }
-
- return true
-}
-
-func (a *ActivityLog) resetEntitiesInMemory(t *testing.T) {
- t.Helper()
-
- a.l.Lock()
- defer a.l.Unlock()
- a.fragmentLock.Lock()
- defer a.fragmentLock.Unlock()
- a.currentSegment = segmentInfo{
- startTimestamp: time.Time{}.Unix(),
- currentClients: &activity.EntityActivityLog{
- Clients: make([]*activity.EntityRecord, 0),
- },
- tokenCount: a.currentSegment.tokenCount,
- clientSequenceNumber: 0,
- }
-
- a.partialMonthClientTracker = make(map[string]*activity.EntityRecord)
-}
-
-// TestActivityLog_loadCurrentClientSegment writes entity segments and calls loadCurrentClientSegment, then verifies
-// that the correct values are returned when querying the current segment.
-func TestActivityLog_loadCurrentClientSegment(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
- // we must verify that loadCurrentClientSegment doesn't overwrite the in-memory token count
- tokenRecords := make(map[string]uint64)
- tokenRecords["test"] = 1
- tokenCount := &activity.TokenCount{
- CountByNamespaceID: tokenRecords,
- }
- a.l.Lock()
- a.currentSegment.tokenCount = tokenCount
- a.l.Unlock()
-
- // setup in-storage data to load for testing
- entityRecords := []*activity.EntityRecord{
- {
- ClientID: "11111111-1111-1111-1111-111111111111",
- NamespaceID: "root",
- Timestamp: time.Now().Unix(),
- },
- {
- ClientID: "22222222-2222-2222-2222-222222222222",
- NamespaceID: "root",
- Timestamp: time.Now().Unix(),
- },
- }
- testEntities1 := &activity.EntityActivityLog{
- Clients: entityRecords[:1],
- }
- testEntities2 := &activity.EntityActivityLog{
- Clients: entityRecords[1:2],
- }
- testEntities3 := &activity.EntityActivityLog{
- Clients: entityRecords[:2],
- }
-
- time1 := time.Date(2020, 4, 1, 0, 0, 0, 0, time.UTC).Unix()
- time2 := time.Date(2020, 5, 1, 0, 0, 0, 0, time.UTC).Unix()
- testCases := []struct {
- time int64
- seqNum uint64
- path string
- entities *activity.EntityActivityLog
- }{
- {
- time: time1,
- seqNum: 0,
- path: "entity/" + fmt.Sprint(time1) + "/0",
- entities: testEntities1,
- },
- {
- // we want to verify that data from segment 0 hasn't been loaded
- time: time1,
- seqNum: 1,
- path: "entity/" + fmt.Sprint(time1) + "/1",
- entities: testEntities2,
- },
- {
- time: time2,
- seqNum: 0,
- path: "entity/" + fmt.Sprint(time2) + "/0",
- entities: testEntities3,
- },
- }
-
- for _, tc := range testCases {
- data, err := proto.Marshal(tc.entities)
- if err != nil {
- t.Fatalf(err.Error())
- }
- WriteToStorage(t, core, ActivityLogPrefix+tc.path, data)
- }
-
- ctx := context.Background()
- for _, tc := range testCases {
- a.l.Lock()
- a.fragmentLock.Lock()
- // loadCurrentClientSegment requires us to grab the fragment lock and the
- // activityLog lock, as per the comment in the loadCurrentClientSegment
- // function
- err := a.loadCurrentClientSegment(ctx, time.Unix(tc.time, 0), tc.seqNum)
- a.fragmentLock.Unlock()
- a.l.Unlock()
-
- if err != nil {
- t.Fatalf("got error loading data for %q: %v", tc.path, err)
- }
- if !reflect.DeepEqual(a.GetStoredTokenCountByNamespaceID(), tokenCount.CountByNamespaceID) {
- t.Errorf("this function should not wipe out the in-memory token count")
- }
-
- // verify accurate data in in-memory current segment
- startTimestamp := a.GetStartTimestamp()
- if startTimestamp != tc.time {
- t.Errorf("bad timestamp loaded. expected: %v, got: %v for path %q", tc.time, startTimestamp, tc.path)
- }
-
- seqNum := a.GetEntitySequenceNumber()
- if seqNum != tc.seqNum {
- t.Errorf("bad sequence number loaded. expected: %v, got: %v for path %q", tc.seqNum, seqNum, tc.path)
- }
-
- currentEntities := a.GetCurrentEntities()
- if !entityRecordsEqual(t, currentEntities.Clients, tc.entities.Clients) {
- t.Errorf("bad data loaded. expected: %v, got: %v for path %q", tc.entities.Clients, currentEntities, tc.path)
- }
-
- activeClients := core.GetActiveClients()
- if !ActiveEntitiesEqual(activeClients, tc.entities.Clients) {
- t.Errorf("bad data loaded into active entities. expected only set of EntityID from %v in %v for path %q", tc.entities.Clients, activeClients, tc.path)
- }
-
- a.resetEntitiesInMemory(t)
- }
-}
-
-// TestActivityLog_loadPriorEntitySegment writes entities to two months and calls loadPriorEntitySegment for each month,
-// verifying that the active clients are correct.
-func TestActivityLog_loadPriorEntitySegment(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
- a.SetEnable(true)
-
- // setup in-storage data to load for testing
- entityRecords := []*activity.EntityRecord{
- {
- ClientID: "11111111-1111-1111-1111-111111111111",
- NamespaceID: "root",
- Timestamp: time.Now().Unix(),
- },
- {
- ClientID: "22222222-2222-2222-2222-222222222222",
- NamespaceID: "root",
- Timestamp: time.Now().Unix(),
- },
- }
- testEntities1 := &activity.EntityActivityLog{
- Clients: entityRecords[:1],
- }
- testEntities2 := &activity.EntityActivityLog{
- Clients: entityRecords[:2],
- }
-
- time1 := time.Date(2020, 4, 1, 0, 0, 0, 0, time.UTC).Unix()
- time2 := time.Date(2020, 5, 1, 0, 0, 0, 0, time.UTC).Unix()
- testCases := []struct {
- time int64
- seqNum uint64
- path string
- entities *activity.EntityActivityLog
- // set true if the in-memory active entities should be wiped because the next test case is a new month
- // this also means that currentSegment.startTimestamp must be updated with :time:
- refresh bool
- }{
- {
- time: time1,
- seqNum: 0,
- path: "entity/" + fmt.Sprint(time1) + "/0",
- entities: testEntities1,
- refresh: true,
- },
- {
- // verify that we don't have a duplicate (shouldn't be possible with the current implementation)
- time: time1,
- seqNum: 1,
- path: "entity/" + fmt.Sprint(time1) + "/1",
- entities: testEntities2,
- refresh: true,
- },
- {
- time: time2,
- seqNum: 0,
- path: "entity/" + fmt.Sprint(time2) + "/0",
- entities: testEntities2,
- refresh: true,
- },
- }
-
- for _, tc := range testCases {
- data, err := proto.Marshal(tc.entities)
- if err != nil {
- t.Fatalf(err.Error())
- }
- WriteToStorage(t, core, ActivityLogPrefix+tc.path, data)
- }
-
- ctx := context.Background()
- for _, tc := range testCases {
- if tc.refresh {
- a.l.Lock()
- a.fragmentLock.Lock()
- a.partialMonthClientTracker = make(map[string]*activity.EntityRecord)
- a.currentSegment.startTimestamp = tc.time
- a.fragmentLock.Unlock()
- a.l.Unlock()
- }
-
- err := a.loadPriorEntitySegment(ctx, time.Unix(tc.time, 0), tc.seqNum)
- if err != nil {
- t.Fatalf("got error loading data for %q: %v", tc.path, err)
- }
-
- activeClients := core.GetActiveClients()
- if !ActiveEntitiesEqual(activeClients, tc.entities.Clients) {
- t.Errorf("bad data loaded into active entities. expected only set of EntityID from %v in %v for path %q", tc.entities.Clients, activeClients, tc.path)
- }
- }
-}
-
-// TestActivityLog_loadTokenCount ensures that previous segments with tokenCounts
-// can still be read from storage, even when TWE's have distinct, tracked clientIDs.
-func TestActivityLog_loadTokenCount(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
-
- // setup in-storage data to load for testing
- tokenRecords := make(map[string]uint64)
- for i := 1; i < 4; i++ {
- nsID := "ns" + strconv.Itoa(i)
- tokenRecords[nsID] = uint64(i)
- }
- tokenCount := &activity.TokenCount{
- CountByNamespaceID: tokenRecords,
- }
-
- data, err := proto.Marshal(tokenCount)
- if err != nil {
- t.Fatalf(err.Error())
- }
-
- testCases := []struct {
- time int64
- path string
- }{
- {
- time: 1111,
- path: "directtokens/1111/0",
- },
- {
- time: 2222,
- path: "directtokens/2222/0",
- },
- }
-
- ctx := context.Background()
- for _, tc := range testCases {
- WriteToStorage(t, core, ActivityLogPrefix+tc.path, data)
- }
-
- for _, tc := range testCases {
- err := a.loadTokenCount(ctx, time.Unix(tc.time, 0))
- if err != nil {
- t.Fatalf("got error loading data for %q: %v", tc.path, err)
- }
-
- nsCount := a.GetStoredTokenCountByNamespaceID()
- if !reflect.DeepEqual(nsCount, tokenRecords) {
- t.Errorf("bad token count loaded. expected: %v got: %v for path %q", tokenRecords, nsCount, tc.path)
- }
- }
-}
-
-// TestActivityLog_StopAndRestart disables the activity log, waits for deletes to complete, and then enables the
-// activity log. The activity log is then stopped and started again, to simulate a seal and unseal. The test then
-// verifies that there's no error adding an entity, direct token, and when writing a segment to storage.
-func TestActivityLog_StopAndRestart(t *testing.T) {
- core, b, _ := testCoreSystemBackend(t)
- sysView := core.systemBarrierView
-
- a := core.activityLog
- ctx := namespace.RootContext(nil)
-
- // Disable, then enable, to exercise newly-enabled code
- a.SetConfig(ctx, activityConfig{
- Enabled: "disable",
- RetentionMonths: 12,
- DefaultReportMonths: 12,
- })
-
- // On enterprise, a segment will be created, and
- // disabling it will trigger deletion, so wait
- // for that deletion to finish.
- // (Alternatively, we could ensure that the next segment
- // uses a different timestamp by waiting 1 second.)
- a.WaitForDeletion()
-
- // Go through request to ensure config is persisted
- req := logical.TestRequest(t, logical.UpdateOperation, "internal/counters/config")
- req.Storage = sysView
- req.Data["enabled"] = "enable"
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Simulate seal/unseal cycle
- core.stopActivityLog()
- var wg sync.WaitGroup
- core.setupActivityLog(ctx, &wg)
- wg.Wait()
-
- a = core.activityLog
- if a.GetStoredTokenCountByNamespaceID() == nil {
- t.Fatalf("nil token count map")
- }
-
- a.AddEntityToFragment("1111-1111", "root", time.Now().Unix())
- a.AddTokenToFragment("root")
-
- err = a.saveCurrentSegmentToStorage(ctx, false)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-// :base: is the timestamp to start from for the setup logic (use to simulate newest log from past or future)
-// entity records returned include [0] data from a previous month and [1:] data from the current month
-// token counts returned are from the current month
-func setupActivityRecordsInStorage(t *testing.T, base time.Time, includeEntities, includeTokens bool) (*ActivityLog, []*activity.EntityRecord, map[string]uint64) {
- t.Helper()
-
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
- monthsAgo := base.AddDate(0, -3, 0)
-
- var entityRecords []*activity.EntityRecord
- if includeEntities {
- entityRecords = []*activity.EntityRecord{
- {
- ClientID: "11111111-1111-1111-1111-111111111111",
- NamespaceID: namespace.RootNamespaceID,
- Timestamp: time.Now().Unix(),
- },
- {
- ClientID: "22222222-2222-2222-2222-222222222222",
- NamespaceID: namespace.RootNamespaceID,
- Timestamp: time.Now().Unix(),
- },
- {
- ClientID: "33333333-2222-2222-2222-222222222222",
- NamespaceID: namespace.RootNamespaceID,
- Timestamp: time.Now().Unix(),
- },
- }
- if constants.IsEnterprise {
- entityRecords = append(entityRecords, []*activity.EntityRecord{
- {
- ClientID: "44444444-1111-1111-1111-111111111111",
- NamespaceID: "ns1",
- Timestamp: time.Now().Unix(),
- },
- }...)
- }
- for i, entityRecord := range entityRecords {
- entityData, err := proto.Marshal(&activity.EntityActivityLog{
- Clients: []*activity.EntityRecord{entityRecord},
- })
- if err != nil {
- t.Fatalf(err.Error())
- }
- if i == 0 {
- WriteToStorage(t, core, ActivityLogPrefix+"entity/"+fmt.Sprint(monthsAgo.Unix())+"/0", entityData)
- } else {
- WriteToStorage(t, core, ActivityLogPrefix+"entity/"+fmt.Sprint(base.Unix())+"/"+strconv.Itoa(i-1), entityData)
- }
- }
- }
-
- var tokenRecords map[string]uint64
- if includeTokens {
- tokenRecords = make(map[string]uint64)
- tokenRecords[namespace.RootNamespaceID] = uint64(1)
- if constants.IsEnterprise {
- for i := 1; i < 4; i++ {
- nsID := "ns" + strconv.Itoa(i)
- tokenRecords[nsID] = uint64(i)
- }
- }
- tokenCount := &activity.TokenCount{
- CountByNamespaceID: tokenRecords,
- }
-
- tokenData, err := proto.Marshal(tokenCount)
- if err != nil {
- t.Fatalf(err.Error())
- }
-
- WriteToStorage(t, core, ActivityLogPrefix+"directtokens/"+fmt.Sprint(base.Unix())+"/0", tokenData)
- }
-
- return a, entityRecords, tokenRecords
-}
-
-// TestActivityLog_refreshFromStoredLog writes records for 3 months ago and this month, then calls refreshFromStoredLog.
-// The test verifies that current entities and current tokens are correct.
-func TestActivityLog_refreshFromStoredLog(t *testing.T) {
- a, expectedClientRecords, expectedTokenCounts := setupActivityRecordsInStorage(t, time.Now().UTC(), true, true)
- a.SetEnable(true)
-
- var wg sync.WaitGroup
- err := a.refreshFromStoredLog(context.Background(), &wg, time.Now().UTC())
- if err != nil {
- t.Fatalf("got error loading stored activity logs: %v", err)
- }
- wg.Wait()
-
- expectedActive := &activity.EntityActivityLog{
- Clients: expectedClientRecords[1:],
- }
- expectedCurrent := &activity.EntityActivityLog{
- Clients: expectedClientRecords[len(expectedClientRecords)-1:],
- }
-
- currentEntities := a.GetCurrentEntities()
- if !entityRecordsEqual(t, currentEntities.Clients, expectedCurrent.Clients) {
- // we only expect the newest entity segment to be loaded (for the current month)
- t.Errorf("bad activity entity logs loaded. expected: %v got: %v", expectedCurrent, currentEntities)
- }
-
- nsCount := a.GetStoredTokenCountByNamespaceID()
- if !reflect.DeepEqual(nsCount, expectedTokenCounts) {
- // we expect all token counts to be loaded
- t.Errorf("bad activity token counts loaded. expected: %v got: %v", expectedTokenCounts, nsCount)
- }
-
- activeClients := a.core.GetActiveClients()
- if !ActiveEntitiesEqual(activeClients, expectedActive.Clients) {
- // we expect activeClients to be loaded for the entire month
- t.Errorf("bad data loaded into active entities. expected only set of EntityID from %v in %v", expectedActive.Clients, activeClients)
- }
-}
-
-// TestActivityLog_refreshFromStoredLogWithBackgroundLoadingCancelled writes data from 3 months ago to this month. The
-// test closes a.doneCh and calls refreshFromStoredLog, which will not do any processing because the doneCh is closed.
-// The test verifies that the current data is not loaded.
-func TestActivityLog_refreshFromStoredLogWithBackgroundLoadingCancelled(t *testing.T) {
- a, expectedClientRecords, expectedTokenCounts := setupActivityRecordsInStorage(t, time.Now().UTC(), true, true)
- a.SetEnable(true)
-
- var wg sync.WaitGroup
- close(a.doneCh)
- defer func() {
- a.l.Lock()
- a.doneCh = make(chan struct{}, 1)
- a.l.Unlock()
- }()
-
- err := a.refreshFromStoredLog(context.Background(), &wg, time.Now().UTC())
- if err != nil {
- t.Fatalf("got error loading stored activity logs: %v", err)
- }
- wg.Wait()
-
- expected := &activity.EntityActivityLog{
- Clients: expectedClientRecords[len(expectedClientRecords)-1:],
- }
-
- currentEntities := a.GetCurrentEntities()
- if !entityRecordsEqual(t, currentEntities.Clients, expected.Clients) {
- // we only expect the newest entity segment to be loaded (for the current month)
- t.Errorf("bad activity entity logs loaded. expected: %v got: %v", expected, currentEntities)
- }
-
- nsCount := a.GetStoredTokenCountByNamespaceID()
- if !reflect.DeepEqual(nsCount, expectedTokenCounts) {
- // we expect all token counts to be loaded
- t.Errorf("bad activity token counts loaded. expected: %v got: %v", expectedTokenCounts, nsCount)
- }
-
- activeClients := a.core.GetActiveClients()
- if !ActiveEntitiesEqual(activeClients, expected.Clients) {
- // we only expect activeClients to be loaded for the newest segment (for the current month)
- t.Errorf("bad data loaded into active entities. expected only set of EntityID from %v in %v", expected.Clients, activeClients)
- }
-}
-
-// TestActivityLog_refreshFromStoredLogContextCancelled writes data from 3 months ago to this month and calls
-// refreshFromStoredLog with a canceled context, verifying that the function errors because of the canceled context.
-func TestActivityLog_refreshFromStoredLogContextCancelled(t *testing.T) {
- a, _, _ := setupActivityRecordsInStorage(t, time.Now().UTC(), true, true)
-
- var wg sync.WaitGroup
- ctx, cancelFn := context.WithCancel(context.Background())
- cancelFn()
-
- err := a.refreshFromStoredLog(ctx, &wg, time.Now().UTC())
- if !errors.Is(err, context.Canceled) {
- t.Fatalf("expected context cancelled error, got: %v", err)
- }
-}
-
-// TestActivityLog_refreshFromStoredLogNoTokens writes only entities from 3 months ago to today, then calls
-// refreshFromStoredLog. It verifies that there are no tokens loaded.
-func TestActivityLog_refreshFromStoredLogNoTokens(t *testing.T) {
- a, expectedClientRecords, _ := setupActivityRecordsInStorage(t, time.Now().UTC(), true, false)
- a.SetEnable(true)
-
- var wg sync.WaitGroup
- err := a.refreshFromStoredLog(context.Background(), &wg, time.Now().UTC())
- if err != nil {
- t.Fatalf("got error loading stored activity logs: %v", err)
- }
- wg.Wait()
-
- expectedActive := &activity.EntityActivityLog{
- Clients: expectedClientRecords[1:],
- }
- expectedCurrent := &activity.EntityActivityLog{
- Clients: expectedClientRecords[len(expectedClientRecords)-1:],
- }
-
- currentEntities := a.GetCurrentEntities()
- if !entityRecordsEqual(t, currentEntities.Clients, expectedCurrent.Clients) {
- // we expect all segments for the current month to be loaded
- t.Errorf("bad activity entity logs loaded. expected: %v got: %v", expectedCurrent, currentEntities)
- }
- activeClients := a.core.GetActiveClients()
- if !ActiveEntitiesEqual(activeClients, expectedActive.Clients) {
- t.Errorf("bad data loaded into active entities. expected only set of EntityID from %v in %v", expectedActive.Clients, activeClients)
- }
-
- // we expect no tokens
- nsCount := a.GetStoredTokenCountByNamespaceID()
- if len(nsCount) > 0 {
- t.Errorf("expected no token counts to be loaded. got: %v", nsCount)
- }
-}
-
-// TestActivityLog_refreshFromStoredLogNoEntities writes only direct tokens from 3 months ago to today, and runs
-// refreshFromStoredLog. It verifies that there are no entities or clients loaded.
-func TestActivityLog_refreshFromStoredLogNoEntities(t *testing.T) {
- a, _, expectedTokenCounts := setupActivityRecordsInStorage(t, time.Now().UTC(), false, true)
- a.SetEnable(true)
-
- var wg sync.WaitGroup
- err := a.refreshFromStoredLog(context.Background(), &wg, time.Now().UTC())
- if err != nil {
- t.Fatalf("got error loading stored activity logs: %v", err)
- }
- wg.Wait()
-
- nsCount := a.GetStoredTokenCountByNamespaceID()
- if !reflect.DeepEqual(nsCount, expectedTokenCounts) {
- // we expect all token counts to be loaded
- t.Errorf("bad activity token counts loaded. expected: %v got: %v", expectedTokenCounts, nsCount)
- }
-
- currentEntities := a.GetCurrentEntities()
- if len(currentEntities.Clients) > 0 {
- t.Errorf("expected no current entity segment to be loaded. got: %v", currentEntities)
- }
- activeClients := a.core.GetActiveClients()
- if len(activeClients) > 0 {
- t.Errorf("expected no active entity segment to be loaded. got: %v", activeClients)
- }
-}
-
-// TestActivityLog_refreshFromStoredLogNoData writes nothing and calls refreshFromStoredLog, and verifies that the
-// current segment counts are zero.
-func TestActivityLog_refreshFromStoredLogNoData(t *testing.T) {
- now := time.Now().UTC()
- a, _, _ := setupActivityRecordsInStorage(t, now, false, false)
- a.SetEnable(true)
-
- var wg sync.WaitGroup
- err := a.refreshFromStoredLog(context.Background(), &wg, now)
- if err != nil {
- t.Fatalf("got error loading stored activity logs: %v", err)
- }
- wg.Wait()
-
- a.ExpectCurrentSegmentRefreshed(t, now.Unix(), false)
-}
-
-// TestActivityLog_refreshFromStoredLogTwoMonthsPrevious creates segment data from 5 months ago to 2 months ago and
-// calls refreshFromStoredLog, then verifies that the current segment counts are zero.
-func TestActivityLog_refreshFromStoredLogTwoMonthsPrevious(t *testing.T) {
- // test what happens when the most recent data is from month M-2 (or earlier - same effect)
- now := time.Now().UTC()
- twoMonthsAgoStart := timeutil.StartOfPreviousMonth(timeutil.StartOfPreviousMonth(now))
- a, _, _ := setupActivityRecordsInStorage(t, twoMonthsAgoStart, true, true)
- a.SetEnable(true)
-
- var wg sync.WaitGroup
- err := a.refreshFromStoredLog(context.Background(), &wg, now)
- if err != nil {
- t.Fatalf("got error loading stored activity logs: %v", err)
- }
- wg.Wait()
-
- a.ExpectCurrentSegmentRefreshed(t, now.Unix(), false)
-}
-
-// TestActivityLog_refreshFromStoredLogPreviousMonth creates segment data from 4 months ago to 1 month ago, then calls
-// refreshFromStoredLog, then verifies that these clients are included in the current segment.
-func TestActivityLog_refreshFromStoredLogPreviousMonth(t *testing.T) {
- // test what happens when most recent data is from month M-1
- // we expect to load the data from the previous month so that the activeFragmentWorker
- // can handle end of month rotations
- monthStart := timeutil.StartOfMonth(time.Now().UTC())
- oneMonthAgoStart := timeutil.StartOfPreviousMonth(monthStart)
- a, expectedClientRecords, expectedTokenCounts := setupActivityRecordsInStorage(t, oneMonthAgoStart, true, true)
- a.SetEnable(true)
-
- var wg sync.WaitGroup
- err := a.refreshFromStoredLog(context.Background(), &wg, time.Now().UTC())
- if err != nil {
- t.Fatalf("got error loading stored activity logs: %v", err)
- }
- wg.Wait()
-
- expectedActive := &activity.EntityActivityLog{
- Clients: expectedClientRecords[1:],
- }
- expectedCurrent := &activity.EntityActivityLog{
- Clients: expectedClientRecords[len(expectedClientRecords)-1:],
- }
-
- currentEntities := a.GetCurrentEntities()
- if !entityRecordsEqual(t, currentEntities.Clients, expectedCurrent.Clients) {
- // we only expect the newest entity segment to be loaded (for the current month)
- t.Errorf("bad activity entity logs loaded. expected: %v got: %v", expectedCurrent, currentEntities)
- }
-
- nsCount := a.GetStoredTokenCountByNamespaceID()
- if !reflect.DeepEqual(nsCount, expectedTokenCounts) {
- // we expect all token counts to be loaded
- t.Errorf("bad activity token counts loaded. expected: %v got: %v", expectedTokenCounts, nsCount)
- }
-
- activeClients := a.core.GetActiveClients()
- if !ActiveEntitiesEqual(activeClients, expectedActive.Clients) {
- // we expect activeClients to be loaded for the entire month
- t.Errorf("bad data loaded into active entities. expected only set of EntityID from %v in %v", expectedActive.Clients, activeClients)
- }
-}
-
-// TestActivityLog_Export writes overlapping client for 5 months with various mounts and namespaces. It performs an
-// export for various month ranges in the range, and verifies that the outputs are correct.
-func TestActivityLog_Export(t *testing.T) {
- timeutil.SkipAtEndOfMonth(t)
-
- january := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
- august := time.Date(2020, 8, 15, 12, 0, 0, 0, time.UTC)
- september := timeutil.StartOfMonth(time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC))
- october := timeutil.StartOfMonth(time.Date(2020, 10, 1, 0, 0, 0, 0, time.UTC))
- november := timeutil.StartOfMonth(time.Date(2020, 11, 1, 0, 0, 0, 0, time.UTC))
-
- core, _, _ := TestCoreUnsealedWithConfig(t, &CoreConfig{
- ActivityLogConfig: ActivityLogCoreConfig{
- DisableTimers: true,
- ForceEnable: true,
- },
- })
- a := core.activityLog
- ctx := namespace.RootContext(nil)
-
- // Generate overlapping sets of entity IDs from this list.
- // january: 40-44 RRRRR
- // first month: 0-19 RRRRRAAAAABBBBBRRRRR
- // second month: 10-29 BBBBBRRRRRRRRRRCCCCC
- // third month: 15-39 RRRRRRRRRRCCCCCRRRRRBBBBB
-
- entityRecords := make([]*activity.EntityRecord, 45)
- entityNamespaces := []string{"root", "aaaaa", "bbbbb", "root", "root", "ccccc", "root", "bbbbb", "rrrrr"}
- authMethods := []string{"auth_1", "auth_2", "auth_3", "auth_4", "auth_5", "auth_6", "auth_7", "auth_8", "auth_9"}
-
- for i := range entityRecords {
- entityRecords[i] = &activity.EntityRecord{
- ClientID: fmt.Sprintf("111122222-3333-4444-5555-%012v", i),
- NamespaceID: entityNamespaces[i/5],
- MountAccessor: authMethods[i/5],
- }
- }
-
- toInsert := []struct {
- StartTime int64
- Segment uint64
- Clients []*activity.EntityRecord
- }{
- // January, should not be included
- {
- january.Unix(),
- 0,
- entityRecords[40:45],
- },
- // Artifically split August and October
- { // 1
- august.Unix(),
- 0,
- entityRecords[:13],
- },
- { // 2
- august.Unix(),
- 1,
- entityRecords[13:20],
- },
- { // 3
- september.Unix(),
- 0,
- entityRecords[10:30],
- },
- { // 4
- october.Unix(),
- 0,
- entityRecords[15:40],
- },
- {
- october.Unix(),
- 1,
- entityRecords[15:40],
- },
- {
- october.Unix(),
- 2,
- entityRecords[17:23],
- },
- }
-
- for i, segment := range toInsert {
- eal := &activity.EntityActivityLog{
- Clients: segment.Clients,
- }
-
- // Mimic a lower time stamp for earlier clients
- for _, c := range eal.Clients {
- c.Timestamp = int64(i)
- }
-
- data, err := proto.Marshal(eal)
- if err != nil {
- t.Fatal(err)
- }
- path := fmt.Sprintf("%ventity/%v/%v", ActivityLogPrefix, segment.StartTime, segment.Segment)
- WriteToStorage(t, core, path, data)
- }
-
- tCases := []struct {
- format string
- startTime time.Time
- endTime time.Time
- expected string
- }{
- {
- format: "json",
- startTime: august,
- endTime: timeutil.EndOfMonth(september),
- expected: "aug_sep.json",
- },
- {
- format: "csv",
- startTime: august,
- endTime: timeutil.EndOfMonth(september),
- expected: "aug_sep.csv",
- },
- {
- format: "json",
- startTime: january,
- endTime: timeutil.EndOfMonth(november),
- expected: "full_history.json",
- },
- {
- format: "csv",
- startTime: january,
- endTime: timeutil.EndOfMonth(november),
- expected: "full_history.csv",
- },
- {
- format: "json",
- startTime: august,
- endTime: timeutil.EndOfMonth(october),
- expected: "aug_oct.json",
- },
- {
- format: "csv",
- startTime: august,
- endTime: timeutil.EndOfMonth(october),
- expected: "aug_oct.csv",
- },
- {
- format: "json",
- startTime: august,
- endTime: timeutil.EndOfMonth(august),
- expected: "aug.json",
- },
- {
- format: "csv",
- startTime: august,
- endTime: timeutil.EndOfMonth(august),
- expected: "aug.csv",
- },
- }
-
- for _, tCase := range tCases {
- rw := &fakeResponseWriter{
- buffer: &bytes.Buffer{},
- headers: http.Header{},
- }
- if err := a.writeExport(ctx, rw, tCase.format, tCase.startTime, tCase.endTime); err != nil {
- t.Fatal(err)
- }
-
- expected, err := os.ReadFile(filepath.Join("activity", "test_fixtures", tCase.expected))
- if err != nil {
- t.Fatal(err)
- }
-
- if !bytes.Equal(rw.buffer.Bytes(), expected) {
- t.Fatal(rw.buffer.String())
- }
- }
-}
-
-type fakeResponseWriter struct {
- buffer *bytes.Buffer
- headers http.Header
-}
-
-func (f *fakeResponseWriter) Write(b []byte) (int, error) {
- return f.buffer.Write(b)
-}
-
-func (f *fakeResponseWriter) Header() http.Header {
- return f.headers
-}
-
-func (f *fakeResponseWriter) WriteHeader(statusCode int) {
- panic("unimplmeneted")
-}
-
-// TestActivityLog_IncludeNamespace verifies that includeInResponse returns true for namespaces that are children of
-// their parents.
-func TestActivityLog_IncludeNamespace(t *testing.T) {
- root := namespace.RootNamespace
- a := &ActivityLog{}
-
- nsA := &namespace.Namespace{
- ID: "aaaaa",
- Path: "a/",
- }
- nsC := &namespace.Namespace{
- ID: "ccccc",
- Path: "c/",
- }
- nsAB := &namespace.Namespace{
- ID: "bbbbb",
- Path: "a/b/",
- }
-
- testCases := []struct {
- QueryNS *namespace.Namespace
- RecordNS *namespace.Namespace
- Expected bool
- }{
- {root, nil, true},
- {root, root, true},
- {root, nsA, true},
- {root, nsAB, true},
- {nsA, nsA, true},
- {nsA, nsAB, true},
- {nsAB, nsAB, true},
-
- {nsA, root, false},
- {nsA, nil, false},
- {nsAB, root, false},
- {nsAB, nil, false},
- {nsAB, nsA, false},
- {nsC, nsA, false},
- {nsC, nsAB, false},
- }
-
- for _, tc := range testCases {
- if a.includeInResponse(tc.QueryNS, tc.RecordNS) != tc.Expected {
- t.Errorf("bad response for query %v record %v, expected %v",
- tc.QueryNS, tc.RecordNS, tc.Expected)
- }
- }
-}
-
-// TestActivityLog_DeleteWorker writes segments for entities and direct tokens for 2 different timestamps, then runs the
-// deleteLogWorker for one of the timestamps. The test verifies that the correct segment is deleted, and the other remains.
-func TestActivityLog_DeleteWorker(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
-
- paths := []string{
- "entity/1111/1",
- "entity/1111/2",
- "entity/1111/3",
- "entity/1112/1",
- "directtokens/1111/1",
- "directtokens/1112/1",
- }
- for _, path := range paths {
- WriteToStorage(t, core, ActivityLogPrefix+path, []byte("test"))
- }
-
- doneCh := make(chan struct{})
- timeout := time.After(20 * time.Second)
-
- go a.deleteLogWorker(namespace.RootContext(nil), 1111, doneCh)
- select {
- case <-doneCh:
- break
- case <-timeout:
- t.Fatalf("timed out")
- }
-
- // Check segments still present
- readSegmentFromStorage(t, core, ActivityLogPrefix+"entity/1112/1")
- readSegmentFromStorage(t, core, ActivityLogPrefix+"directtokens/1112/1")
-
- // Check other segments not present
- expectMissingSegment(t, core, ActivityLogPrefix+"entity/1111/1")
- expectMissingSegment(t, core, ActivityLogPrefix+"entity/1111/2")
- expectMissingSegment(t, core, ActivityLogPrefix+"entity/1111/3")
- expectMissingSegment(t, core, ActivityLogPrefix+"directtokens/1111/1")
-}
-
-// checkAPIWarnings ensures there is a warning if switching from enabled -> disabled,
-// and no response otherwise
-func checkAPIWarnings(t *testing.T, originalEnabled, newEnabled bool, resp *logical.Response) {
- t.Helper()
-
- expectWarning := originalEnabled == true && newEnabled == false
-
- switch {
- case !expectWarning && resp != nil:
- t.Fatalf("got unexpected response: %#v", resp)
- case expectWarning && resp == nil:
- t.Fatal("expected response (containing warning) when switching from enabled to disabled")
- case expectWarning && len(resp.Warnings) == 0:
- t.Fatal("expected warning when switching from enabled to disabled")
- }
-}
-
-// TestActivityLog_EnableDisable writes a segment, adds an entity to the in-memory fragment, then disables the activity
-// log. The test verifies that activity log cannot be disabled if manual reporting is enabled and no segment data is lost.
-// If manual reporting is not enabled(OSS), The test verifies that the segment doesn't exist. The activity log is enabled, then verified that an empty
-// segment is written and new clients can be added and written to segments.
-func TestActivityLog_EnableDisable(t *testing.T) {
- timeutil.SkipAtEndOfMonth(t)
-
- core, b, _ := testCoreSystemBackend(t)
- a := core.activityLog
- view := core.systemBarrierView
- ctx := namespace.RootContext(nil)
-
- enableRequest := func() {
- t.Helper()
- originalEnabled := a.GetEnabled()
-
- req := logical.TestRequest(t, logical.UpdateOperation, "internal/counters/config")
- req.Storage = view
- req.Data["enabled"] = "enable"
- resp, err := b.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- // don't really need originalEnabled, but might as well be correct
- checkAPIWarnings(t, originalEnabled, true, resp)
- }
- disableRequest := func() {
- t.Helper()
- originalEnabled := a.GetEnabled()
-
- req := logical.TestRequest(t, logical.UpdateOperation, "internal/counters/config")
- req.Storage = view
- req.Data["enabled"] = "disable"
- resp, err := b.HandleRequest(ctx, req)
- if a.core.ManualLicenseReportingEnabled() {
- if err == nil {
- t.Fatal("expected error")
- }
- if resp.Data["error"] != `cannot disable the activity log while Reporting is enabled` {
- t.Fatalf("bad: %v", resp)
- }
- } else {
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- checkAPIWarnings(t, originalEnabled, false, resp)
- }
- }
-
- // enable (if not already) and write a segment
- enableRequest()
-
- id1 := "11111111-1111-1111-1111-111111111111"
- id2 := "22222222-2222-2222-2222-222222222222"
- id3 := "33333333-3333-3333-3333-333333333333"
- a.AddEntityToFragment(id1, "root", time.Now().Unix())
- a.AddEntityToFragment(id2, "root", time.Now().Unix())
-
- a.SetStartTimestamp(a.GetStartTimestamp() - 10)
- seg1 := a.GetStartTimestamp()
- err := a.saveCurrentSegmentToStorage(ctx, false)
- if err != nil {
- t.Fatal(err)
- }
-
- // verify segment exists
- path := fmt.Sprintf("%ventity/%v/0", ActivityLogPrefix, seg1)
- readSegmentFromStorage(t, core, path)
-
- // Add in-memory fragment
- a.AddEntityToFragment(id3, "root", time.Now().Unix())
-
- // disable and verify segment exists
- disableRequest()
-
- if !a.core.ManualLicenseReportingEnabled() {
- timeout := time.After(20 * time.Second)
- select {
- case <-a.deleteDone:
- break
- case <-timeout:
- t.Fatalf("timed out")
- }
-
- expectMissingSegment(t, core, path)
- a.ExpectCurrentSegmentRefreshed(t, 0, false)
-
- // enable (if not already) which force-writes an empty segment
- enableRequest()
-
- seg2 := a.GetStartTimestamp()
- if seg1 >= seg2 {
- t.Errorf("bad second segment timestamp, %v >= %v", seg1, seg2)
- }
-
- // Verify empty segments are present
- path = fmt.Sprintf("%ventity/%v/0", ActivityLogPrefix, seg2)
- readSegmentFromStorage(t, core, path)
-
- path = fmt.Sprintf("%vdirecttokens/%v/0", ActivityLogPrefix, seg2)
- }
- readSegmentFromStorage(t, core, path)
-}
-
-func TestActivityLog_EndOfMonth(t *testing.T) {
- // We only want *fake* end of months, *real* ones are too scary.
- timeutil.SkipAtEndOfMonth(t)
-
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
- ctx := namespace.RootContext(nil)
-
- // Make sure we're enabled.
- a.SetConfig(ctx, activityConfig{
- Enabled: "enable",
- RetentionMonths: 12,
- DefaultReportMonths: 12,
- })
-
- id1 := "11111111-1111-1111-1111-111111111111"
- id2 := "22222222-2222-2222-2222-222222222222"
- id3 := "33333333-3333-3333-3333-333333333333"
- a.AddEntityToFragment(id1, "root", time.Now().Unix())
-
- month0 := time.Now().UTC()
- segment0 := a.GetStartTimestamp()
- month1 := timeutil.StartOfNextMonth(month0)
- month2 := timeutil.StartOfNextMonth(month1)
-
- // Trigger end-of-month
- a.HandleEndOfMonth(ctx, month1)
-
- // Check segment is present, with 1 entity
- path := fmt.Sprintf("%ventity/%v/0", ActivityLogPrefix, segment0)
- protoSegment := readSegmentFromStorage(t, core, path)
- out := &activity.EntityActivityLog{}
- err := proto.Unmarshal(protoSegment.Value, out)
- if err != nil {
- t.Fatal(err)
- }
-
- segment1 := a.GetStartTimestamp()
- expectedTimestamp := timeutil.StartOfMonth(month1).Unix()
- if segment1 != expectedTimestamp {
- t.Errorf("expected segment timestamp %v got %v", expectedTimestamp, segment1)
- }
-
- // Check intent log is present
- intentRaw, err := core.barrier.Get(ctx, "sys/counters/activity/endofmonth")
- if err != nil {
- t.Fatal(err)
- }
- if intentRaw == nil {
- t.Fatal("no intent log present")
- }
-
- var intent ActivityIntentLog
- err = intentRaw.DecodeJSON(&intent)
- if err != nil {
- t.Fatal(err)
- }
-
- if intent.PreviousMonth != segment0 {
- t.Errorf("expected previous month %v got %v", segment0, intent.PreviousMonth)
- }
-
- if intent.NextMonth != segment1 {
- t.Errorf("expected previous month %v got %v", segment1, intent.NextMonth)
- }
-
- a.AddEntityToFragment(id2, "root", time.Now().Unix())
-
- a.HandleEndOfMonth(ctx, month2)
- segment2 := a.GetStartTimestamp()
-
- a.AddEntityToFragment(id3, "root", time.Now().Unix())
-
- err = a.saveCurrentSegmentToStorage(ctx, false)
- if err != nil {
- t.Fatal(err)
- }
-
- // Check all three segments still present, with correct entities
- testCases := []struct {
- SegmentTimestamp int64
- ExpectedEntityIDs []string
- }{
- {segment0, []string{id1}},
- {segment1, []string{id2}},
- {segment2, []string{id3}},
- }
-
- for i, tc := range testCases {
- t.Logf("checking segment %v timestamp %v", i, tc.SegmentTimestamp)
- path := fmt.Sprintf("%ventity/%v/0", ActivityLogPrefix, tc.SegmentTimestamp)
- protoSegment := readSegmentFromStorage(t, core, path)
- out := &activity.EntityActivityLog{}
- err = proto.Unmarshal(protoSegment.Value, out)
- if err != nil {
- t.Fatalf("could not unmarshal protobuf: %v", err)
- }
- expectedEntityIDs(t, out, tc.ExpectedEntityIDs)
- }
-}
-
-// TestActivityLog_CalculatePrecomputedQueriesWithMixedTWEs tests that precomputed
-// queries work when new months have tokens without entities saved in the TokenCount,
-// as clients, or both.
-func TestActivityLog_CalculatePrecomputedQueriesWithMixedTWEs(t *testing.T) {
- timeutil.SkipAtEndOfMonth(t)
-
- // root namespace will have TWEs with clientIDs and untracked TWEs
- // ns1 namespace will only have TWEs with clientIDs
- // aaaa, bbbb, and cccc namespace will only have untracked TWEs
- // 1. January tests clientIDs from a segment don't roll over into another month's
- // client counts in same segment.
- // 2. August tests that client counts work when split across segment.
- // 3. September tests that an entire segment in a month yields correct cc.
- // 4. October tests that a month with only a segment rolled over from previous
- // month yields correct client count.
-
- january := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
- august := time.Date(2020, 8, 15, 12, 0, 0, 0, time.UTC)
- september := timeutil.StartOfMonth(time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC))
- october := timeutil.StartOfMonth(time.Date(2020, 10, 1, 0, 0, 0, 0, time.UTC))
- november := timeutil.StartOfMonth(time.Date(2020, 11, 1, 0, 0, 0, 0, time.UTC))
-
- conf := &CoreConfig{
- ActivityLogConfig: ActivityLogCoreConfig{
- ForceEnable: true,
- DisableTimers: true,
- },
- }
- sink := SetupMetrics(conf)
- core, _, _ := TestCoreUnsealedWithConfig(t, conf)
- a := core.activityLog
- <-a.computationWorkerDone
- ctx := namespace.RootContext(nil)
-
- // Generate overlapping sets of entity IDs from this list.
-
- clientRecords := make([]*activity.EntityRecord, 45)
- clientNamespaces := []string{"root", "aaaaa", "bbbbb", "root", "root", "ccccc", "root", "bbbbb", "rrrrr"}
-
- for i := range clientRecords {
- clientRecords[i] = &activity.EntityRecord{
- ClientID: fmt.Sprintf("111122222-3333-4444-5555-%012v", i),
- NamespaceID: clientNamespaces[i/5],
- Timestamp: time.Now().Unix(),
- NonEntity: true,
- }
- }
-
- toInsert := []struct {
- StartTime int64
- Segment uint64
- Clients []*activity.EntityRecord
- }{
- // January, should not be included
- {
- january.Unix(),
- 0,
- clientRecords[40:45],
- },
- {
- august.Unix(),
- 0,
- clientRecords[:13],
- },
- {
- august.Unix(),
- 1,
- clientRecords[13:20],
- },
- {
- september.Unix(),
- 1,
- clientRecords[10:30],
- },
- {
- september.Unix(),
- 2,
- clientRecords[15:40],
- },
- {
- september.Unix(),
- 3,
- clientRecords[15:40],
- },
- {
- october.Unix(),
- 3,
- clientRecords[17:23],
- },
- }
-
- // Insert token counts for all 3 segments
- toInsertTokenCount := []struct {
- StartTime int64
- Segment uint64
- CountByNamespaceID map[string]uint64
- }{
- {
- january.Unix(),
- 0,
- map[string]uint64{"root": 3, "ns1": 5},
- },
- {
- august.Unix(),
- 0,
- map[string]uint64{"root": 40, "ns1": 50},
- },
- {
- august.Unix(),
- 1,
- map[string]uint64{"root": 60, "ns1": 70},
- },
- {
- september.Unix(),
- 1,
- map[string]uint64{"root": 400, "ns1": 500},
- },
- {
- september.Unix(),
- 2,
- map[string]uint64{"root": 700, "ns1": 800},
- },
- {
- september.Unix(),
- 3,
- map[string]uint64{"root": 0, "ns1": 0},
- },
- {
- october.Unix(),
- 3,
- map[string]uint64{"root": 0, "ns1": 0},
- },
- }
- doInsertTokens := func(i int) {
- segment := toInsertTokenCount[i]
- tc := &activity.TokenCount{
- CountByNamespaceID: segment.CountByNamespaceID,
- }
- data, err := proto.Marshal(tc)
- if err != nil {
- t.Fatal(err)
- }
- tokenPath := fmt.Sprintf("%vdirecttokens/%v/%v", ActivityLogPrefix, segment.StartTime, segment.Segment)
- WriteToStorage(t, core, tokenPath, data)
- }
-
- // Note that precomputedQuery worker doesn't filter
- // for times <= the one it was asked to do. Is that a problem?
- // Here, it means that we can't insert everything *first* and do multiple
- // test cases, we have to write logs incrementally.
- doInsert := func(i int) {
- segment := toInsert[i]
- eal := &activity.EntityActivityLog{
- Clients: segment.Clients,
- }
- data, err := proto.Marshal(eal)
- if err != nil {
- t.Fatal(err)
- }
- path := fmt.Sprintf("%ventity/%v/%v", ActivityLogPrefix, segment.StartTime, segment.Segment)
- WriteToStorage(t, core, path, data)
- }
- expectedCounts := []struct {
- StartTime time.Time
- EndTime time.Time
- ByNamespace map[string]int
- }{
- // First test case
- {
- august,
- timeutil.EndOfMonth(august),
- map[string]int{
- "root": 110, // 10 TWEs + 50 + 60 direct tokens
- "ns1": 120, // 60 + 70 direct tokens
- "aaaaa": 5,
- "bbbbb": 5,
- },
- },
- // Second test case
- {
- august,
- timeutil.EndOfMonth(september),
- map[string]int{
- "root": 1220, // 110 from august + 10 non-overlapping TWEs in September, + 400 + 700 direct tokens in september
- "ns1": 1420, // 120 from August + 500 + 800 direct tokens in september
- "aaaaa": 5,
- "bbbbb": 10,
- "ccccc": 5,
- },
- },
- {
- september,
- timeutil.EndOfMonth(september),
- map[string]int{
- "root": 1115, // 15 TWEs in September, + 400 + 700 direct tokens
- "ns1": 1300, // 500 direct tokens in september
- "bbbbb": 10,
- "ccccc": 5,
- },
- },
- // Third test case
- {
- august,
- timeutil.EndOfMonth(october),
- map[string]int{
- "root": 1220, // 1220 from Aug to Sept
- "ns1": 1420, // 1420 from Aug to Sept
- "aaaaa": 5,
- "bbbbb": 10,
- "ccccc": 5,
- },
- },
- {
- september,
- timeutil.EndOfMonth(october),
- map[string]int{
- "root": 1115, // 1115 in Sept
- "ns1": 1300, // 1300 in Sept
- "bbbbb": 10,
- "ccccc": 5,
- },
- },
- {
- october,
- timeutil.EndOfMonth(october),
- map[string]int{
- "root": 6, // 6 overlapping TWEs in October
- "ns1": 0, // No new direct tokens in october
- },
- },
- }
-
- checkPrecomputedQuery := func(i int) {
- t.Helper()
- pq, err := a.queryStore.Get(ctx, expectedCounts[i].StartTime, expectedCounts[i].EndTime)
- if err != nil {
- t.Fatal(err)
- }
- if pq == nil {
- t.Errorf("empty result for %v -- %v", expectedCounts[i].StartTime, expectedCounts[i].EndTime)
- }
- if len(pq.Namespaces) != len(expectedCounts[i].ByNamespace) {
- t.Errorf("mismatched number of namespaces, expected %v got %v",
- len(expectedCounts[i].ByNamespace), len(pq.Namespaces))
- }
- for _, nsRecord := range pq.Namespaces {
- val, ok := expectedCounts[i].ByNamespace[nsRecord.NamespaceID]
- if !ok {
- t.Errorf("unexpected namespace %v", nsRecord.NamespaceID)
- continue
- }
- if uint64(val) != nsRecord.NonEntityTokens {
- t.Errorf("wrong number of entities in %v: expected %v, got %v",
- nsRecord.NamespaceID, val, nsRecord.NonEntityTokens)
- }
- }
- if !pq.StartTime.Equal(expectedCounts[i].StartTime) {
- t.Errorf("mismatched start time: expected %v got %v",
- expectedCounts[i].StartTime, pq.StartTime)
- }
- if !pq.EndTime.Equal(expectedCounts[i].EndTime) {
- t.Errorf("mismatched end time: expected %v got %v",
- expectedCounts[i].EndTime, pq.EndTime)
- }
- }
-
- testCases := []struct {
- InsertUpTo int // index in the toInsert array
- PrevMonth int64
- NextMonth int64
- ExpectedUpTo int // index in the expectedCounts array
- }{
- {
- 2, // jan-august
- august.Unix(),
- september.Unix(),
- 0, // august-august
- },
- {
- 5, // jan-sept
- september.Unix(),
- october.Unix(),
- 2, // august-september
- },
- {
- 6, // jan-oct
- october.Unix(),
- november.Unix(),
- 5, // august-october
- },
- }
-
- inserted := -1
- for _, tc := range testCases {
- t.Logf("tc %+v", tc)
-
- // Persists across loops
- for inserted < tc.InsertUpTo {
- inserted += 1
- t.Logf("inserting segment %v", inserted)
- doInsert(inserted)
- doInsertTokens(inserted)
- }
-
- intent := &ActivityIntentLog{
- PreviousMonth: tc.PrevMonth,
- NextMonth: tc.NextMonth,
- }
- data, err := json.Marshal(intent)
- if err != nil {
- t.Fatal(err)
- }
- WriteToStorage(t, core, "sys/counters/activity/endofmonth", data)
-
- // Pretend we've successfully rolled over to the following month
- a.SetStartTimestamp(tc.NextMonth)
-
- err = a.precomputedQueryWorker(ctx)
- if err != nil {
- t.Fatal(err)
- }
-
- expectMissingSegment(t, core, "sys/counters/activity/endofmonth")
-
- for i := 0; i <= tc.ExpectedUpTo; i++ {
- checkPrecomputedQuery(i)
- }
- }
-
- // Check metrics on the last precomputed query
- // (otherwise we need a way to reset the in-memory metrics between test cases.)
-
- intervals := sink.Data()
- // Test crossed an interval boundary, don't try to deal with it.
- if len(intervals) > 1 {
- t.Skip("Detected interval crossing.")
- }
- expectedGauges := []struct {
- Name string
- NamespaceLabel string
- Value float32
- }{
- // october values
- {
- "identity.nonentity.active.monthly",
- "root",
- 6.0,
- },
- {
- "identity.nonentity.active.monthly",
- "deleted-bbbbb", // No namespace entry for this fake ID
- 10.0,
- },
- {
- "identity.nonentity.active.monthly",
- "deleted-ccccc",
- 5.0,
- },
- // january-september values
- {
- "identity.nonentity.active.reporting_period",
- "root",
- 1223.0,
- },
- {
- "identity.nonentity.active.reporting_period",
- "deleted-aaaaa",
- 5.0,
- },
- {
- "identity.nonentity.active.reporting_period",
- "deleted-bbbbb",
- 10.0,
- },
- {
- "identity.nonentity.active.reporting_period",
- "deleted-ccccc",
- 5.0,
- },
- }
- for _, g := range expectedGauges {
- found := false
- for _, actual := range intervals[0].Gauges {
- actualNamespaceLabel := ""
- for _, l := range actual.Labels {
- if l.Name == "namespace" {
- actualNamespaceLabel = l.Value
- break
- }
- }
- if actual.Name == g.Name && actualNamespaceLabel == g.NamespaceLabel {
- found = true
- if actual.Value != g.Value {
- t.Errorf("Mismatched value for %v %v %v != %v",
- g.Name, g.NamespaceLabel, actual.Value, g.Value)
- }
- break
- }
- }
- if !found {
- t.Errorf("No gauge found for %v %v",
- g.Name, g.NamespaceLabel)
- }
- }
-}
-
-func TestActivityLog_SaveAfterDisable(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- a := core.activityLog
- a.SetConfig(ctx, activityConfig{
- Enabled: "enable",
- RetentionMonths: 12,
- DefaultReportMonths: 12,
- })
-
- a.AddEntityToFragment("1111-1111-11111111", "root", time.Now().Unix())
- startTimestamp := a.GetStartTimestamp()
-
- // This kicks off an asynchronous delete
- a.SetConfig(ctx, activityConfig{
- Enabled: "disable",
- RetentionMonths: 12,
- DefaultReportMonths: 12,
- })
-
- timer := time.After(10 * time.Second)
- select {
- case <-timer:
- t.Fatal("timeout waiting for delete to finish")
- case <-a.deleteDone:
- break
- }
-
- // Segment should not be written even with force
- err := a.saveCurrentSegmentToStorage(context.Background(), true)
- if err != nil {
- t.Fatal(err)
- }
-
- path := ActivityLogPrefix + "entity/0/0"
- expectMissingSegment(t, core, path)
-
- path = fmt.Sprintf("%ventity/%v/0", ActivityLogPrefix, startTimestamp)
- expectMissingSegment(t, core, path)
-}
-
-// TestActivityLog_Precompute creates segments over a range of 11 months, with overlapping clients and namespaces.
-// Create intent logs and run precomputedQueryWorker for various month ranges. Verify that the precomputed queries have
-// the correct counts, including per namespace.
-func TestActivityLog_Precompute(t *testing.T) {
- timeutil.SkipAtEndOfMonth(t)
-
- january := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
- august := time.Date(2020, 8, 15, 12, 0, 0, 0, time.UTC)
- september := timeutil.StartOfMonth(time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC))
- october := timeutil.StartOfMonth(time.Date(2020, 10, 1, 0, 0, 0, 0, time.UTC))
- november := timeutil.StartOfMonth(time.Date(2020, 11, 1, 0, 0, 0, 0, time.UTC))
-
- conf := &CoreConfig{
- ActivityLogConfig: ActivityLogCoreConfig{
- ForceEnable: true,
- DisableTimers: true,
- },
- }
- sink := SetupMetrics(conf)
- core, _, _ := TestCoreUnsealedWithConfig(t, conf)
- a := core.activityLog
- ctx := namespace.RootContext(nil)
-
- // Generate overlapping sets of entity IDs from this list.
- // january: 40-44 RRRRR
- // first month: 0-19 RRRRRAAAAABBBBBRRRRR
- // second month: 10-29 BBBBBRRRRRRRRRRCCCCC
- // third month: 15-39 RRRRRRRRRRCCCCCRRRRRBBBBB
-
- entityRecords := make([]*activity.EntityRecord, 45)
- entityNamespaces := []string{"root", "aaaaa", "bbbbb", "root", "root", "ccccc", "root", "bbbbb", "rrrrr"}
-
- for i := range entityRecords {
- entityRecords[i] = &activity.EntityRecord{
- ClientID: fmt.Sprintf("111122222-3333-4444-5555-%012v", i),
- NamespaceID: entityNamespaces[i/5],
- Timestamp: time.Now().Unix(),
- }
- }
-
- toInsert := []struct {
- StartTime int64
- Segment uint64
- Clients []*activity.EntityRecord
- }{
- // January, should not be included
- {
- january.Unix(),
- 0,
- entityRecords[40:45],
- },
- // Artifically split August and October
- { // 1
- august.Unix(),
- 0,
- entityRecords[:13],
- },
- { // 2
- august.Unix(),
- 1,
- entityRecords[13:20],
- },
- { // 3
- september.Unix(),
- 0,
- entityRecords[10:30],
- },
- { // 4
- october.Unix(),
- 0,
- entityRecords[15:40],
- },
- {
- october.Unix(),
- 1,
- entityRecords[15:40],
- },
- {
- october.Unix(),
- 2,
- entityRecords[17:23],
- },
- }
-
- // Note that precomputedQuery worker doesn't filter
- // for times <= the one it was asked to do. Is that a problem?
- // Here, it means that we can't insert everything *first* and do multiple
- // test cases, we have to write logs incrementally.
- doInsert := func(i int) {
- segment := toInsert[i]
- eal := &activity.EntityActivityLog{
- Clients: segment.Clients,
- }
- data, err := proto.Marshal(eal)
- if err != nil {
- t.Fatal(err)
- }
- path := fmt.Sprintf("%ventity/%v/%v", ActivityLogPrefix, segment.StartTime, segment.Segment)
- WriteToStorage(t, core, path, data)
- }
-
- expectedCounts := []struct {
- StartTime time.Time
- EndTime time.Time
- ByNamespace map[string]int
- }{
- // First test case
- {
- august,
- timeutil.EndOfMonth(august),
- map[string]int{
- "root": 10,
- "aaaaa": 5,
- "bbbbb": 5,
- },
- },
- // Second test case
- {
- august,
- timeutil.EndOfMonth(september),
- map[string]int{
- "root": 15,
- "aaaaa": 5,
- "bbbbb": 5,
- "ccccc": 5,
- },
- },
- {
- september,
- timeutil.EndOfMonth(september),
- map[string]int{
- "root": 10,
- "bbbbb": 5,
- "ccccc": 5,
- },
- },
- // Third test case
- {
- august,
- timeutil.EndOfMonth(october),
- map[string]int{
- "root": 20,
- "aaaaa": 5,
- "bbbbb": 10,
- "ccccc": 5,
- },
- },
- {
- september,
- timeutil.EndOfMonth(october),
- map[string]int{
- "root": 15,
- "bbbbb": 10,
- "ccccc": 5,
- },
- },
- {
- october,
- timeutil.EndOfMonth(october),
- map[string]int{
- "root": 15,
- "bbbbb": 5,
- "ccccc": 5,
- },
- },
- }
-
- checkPrecomputedQuery := func(i int) {
- t.Helper()
- pq, err := a.queryStore.Get(ctx, expectedCounts[i].StartTime, expectedCounts[i].EndTime)
- if err != nil {
- t.Fatal(err)
- }
- if pq == nil {
- t.Errorf("empty result for %v -- %v", expectedCounts[i].StartTime, expectedCounts[i].EndTime)
- }
- if len(pq.Namespaces) != len(expectedCounts[i].ByNamespace) {
- t.Errorf("mismatched number of namespaces, expected %v got %v",
- len(expectedCounts[i].ByNamespace), len(pq.Namespaces))
- }
- for _, nsRecord := range pq.Namespaces {
- val, ok := expectedCounts[i].ByNamespace[nsRecord.NamespaceID]
- if !ok {
- t.Errorf("unexpected namespace %v", nsRecord.NamespaceID)
- continue
- }
- if uint64(val) != nsRecord.Entities {
- t.Errorf("wrong number of entities in %v: expected %v, got %v",
- nsRecord.NamespaceID, val, nsRecord.Entities)
- }
- }
- if !pq.StartTime.Equal(expectedCounts[i].StartTime) {
- t.Errorf("mismatched start time: expected %v got %v",
- expectedCounts[i].StartTime, pq.StartTime)
- }
- if !pq.EndTime.Equal(expectedCounts[i].EndTime) {
- t.Errorf("mismatched end time: expected %v got %v",
- expectedCounts[i].EndTime, pq.EndTime)
- }
- }
-
- testCases := []struct {
- InsertUpTo int // index in the toInsert array
- PrevMonth int64
- NextMonth int64
- ExpectedUpTo int // index in the expectedCounts array
- }{
- {
- 2, // jan-august
- august.Unix(),
- september.Unix(),
- 0, // august-august
- },
- {
- 3, // jan-sept
- september.Unix(),
- october.Unix(),
- 2, // august-september
- },
- {
- 6, // jan-oct
- october.Unix(),
- november.Unix(),
- 5, // august-september
- },
- }
-
- inserted := -1
- for _, tc := range testCases {
- t.Logf("tc %+v", tc)
-
- // Persists across loops
- for inserted < tc.InsertUpTo {
- inserted += 1
- t.Logf("inserting segment %v", inserted)
- doInsert(inserted)
- }
-
- intent := &ActivityIntentLog{
- PreviousMonth: tc.PrevMonth,
- NextMonth: tc.NextMonth,
- }
- data, err := json.Marshal(intent)
- if err != nil {
- t.Fatal(err)
- }
- WriteToStorage(t, core, "sys/counters/activity/endofmonth", data)
-
- // Pretend we've successfully rolled over to the following month
- a.SetStartTimestamp(tc.NextMonth)
-
- err = a.precomputedQueryWorker(ctx)
- if err != nil {
- t.Fatal(err)
- }
-
- expectMissingSegment(t, core, "sys/counters/activity/endofmonth")
-
- for i := 0; i <= tc.ExpectedUpTo; i++ {
- checkPrecomputedQuery(i)
- }
- }
-
- // Check metrics on the last precomputed query
- // (otherwise we need a way to reset the in-memory metrics between test cases.)
-
- intervals := sink.Data()
- // Test crossed an interval boundary, don't try to deal with it.
- if len(intervals) > 1 {
- t.Skip("Detected interval crossing.")
- }
- expectedGauges := []struct {
- Name string
- NamespaceLabel string
- Value float32
- }{
- // october values
- {
- "identity.entity.active.monthly",
- "root",
- 15.0,
- },
- {
- "identity.entity.active.monthly",
- "deleted-bbbbb", // No namespace entry for this fake ID
- 5.0,
- },
- {
- "identity.entity.active.monthly",
- "deleted-ccccc",
- 5.0,
- },
- // august-september values
- {
- "identity.entity.active.reporting_period",
- "root",
- 20.0,
- },
- {
- "identity.entity.active.reporting_period",
- "deleted-aaaaa",
- 5.0,
- },
- {
- "identity.entity.active.reporting_period",
- "deleted-bbbbb",
- 10.0,
- },
- {
- "identity.entity.active.reporting_period",
- "deleted-ccccc",
- 5.0,
- },
- }
- for _, g := range expectedGauges {
- found := false
- for _, actual := range intervals[0].Gauges {
- actualNamespaceLabel := ""
- for _, l := range actual.Labels {
- if l.Name == "namespace" {
- actualNamespaceLabel = l.Value
- break
- }
- }
- if actual.Name == g.Name && actualNamespaceLabel == g.NamespaceLabel {
- found = true
- if actual.Value != g.Value {
- t.Errorf("Mismatched value for %v %v %v != %v",
- g.Name, g.NamespaceLabel, actual.Value, g.Value)
- }
- break
- }
- }
- if !found {
- t.Errorf("No guage found for %v %v",
- g.Name, g.NamespaceLabel)
- }
- }
-}
-
-// TestActivityLog_Precompute_SkipMonth will put two non-contiguous chunks of
-// data in the activity log, and then run precomputedQueryWorker. Finally it
-// will perform a query get over the skip month and expect a query for the entire
-// time segment (non-contiguous)
-func TestActivityLog_Precompute_SkipMonth(t *testing.T) {
- timeutil.SkipAtEndOfMonth(t)
-
- august := time.Date(2020, 8, 15, 12, 0, 0, 0, time.UTC)
- september := timeutil.StartOfMonth(time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC))
- october := timeutil.StartOfMonth(time.Date(2020, 10, 1, 0, 0, 0, 0, time.UTC))
- november := timeutil.StartOfMonth(time.Date(2020, 11, 1, 0, 0, 0, 0, time.UTC))
- december := timeutil.StartOfMonth(time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC))
-
- core, _, _ := TestCoreUnsealedWithConfig(t, &CoreConfig{
- ActivityLogConfig: ActivityLogCoreConfig{
- ForceEnable: true,
- DisableTimers: true,
- },
- })
- a := core.activityLog
- ctx := namespace.RootContext(nil)
-
- entityRecords := make([]*activity.EntityRecord, 45)
-
- for i := range entityRecords {
- entityRecords[i] = &activity.EntityRecord{
- ClientID: fmt.Sprintf("111122222-3333-4444-5555-%012v", i),
- NamespaceID: "root",
- Timestamp: time.Now().Unix(),
- }
- }
-
- toInsert := []struct {
- StartTime int64
- Segment uint64
- Clients []*activity.EntityRecord
- }{
- {
- august.Unix(),
- 0,
- entityRecords[:20],
- },
- {
- september.Unix(),
- 0,
- entityRecords[20:30],
- },
- {
- november.Unix(),
- 0,
- entityRecords[30:45],
- },
- }
-
- // Note that precomputedQuery worker doesn't filter
- // for times <= the one it was asked to do. Is that a problem?
- // Here, it means that we can't insert everything *first* and do multiple
- // test cases, we have to write logs incrementally.
- doInsert := func(i int) {
- t.Helper()
- segment := toInsert[i]
- eal := &activity.EntityActivityLog{
- Clients: segment.Clients,
- }
- data, err := proto.Marshal(eal)
- if err != nil {
- t.Fatal(err)
- }
- path := fmt.Sprintf("%ventity/%v/%v", ActivityLogPrefix, segment.StartTime, segment.Segment)
- WriteToStorage(t, core, path, data)
- }
-
- expectedCounts := []struct {
- StartTime time.Time
- EndTime time.Time
- ByNamespace map[string]int
- }{
- // First test case
- {
- august,
- timeutil.EndOfMonth(september),
- map[string]int{
- "root": 30,
- },
- },
- // Second test case
- {
- august,
- timeutil.EndOfMonth(november),
- map[string]int{
- "root": 45,
- },
- },
- }
-
- checkPrecomputedQuery := func(i int) {
- t.Helper()
- pq, err := a.queryStore.Get(ctx, expectedCounts[i].StartTime, expectedCounts[i].EndTime)
- if err != nil {
- t.Fatal(err)
- }
- if pq == nil {
- t.Errorf("empty result for %v -- %v", expectedCounts[i].StartTime, expectedCounts[i].EndTime)
- }
- if len(pq.Namespaces) != len(expectedCounts[i].ByNamespace) {
- t.Errorf("mismatched number of namespaces, expected %v got %v",
- len(expectedCounts[i].ByNamespace), len(pq.Namespaces))
- }
- for _, nsRecord := range pq.Namespaces {
- val, ok := expectedCounts[i].ByNamespace[nsRecord.NamespaceID]
- if !ok {
- t.Errorf("unexpected namespace %v", nsRecord.NamespaceID)
- continue
- }
- if uint64(val) != nsRecord.Entities {
- t.Errorf("wrong number of entities in %v: expected %v, got %v",
- nsRecord.NamespaceID, val, nsRecord.Entities)
- }
- }
- if !pq.StartTime.Equal(expectedCounts[i].StartTime) {
- t.Errorf("mismatched start time: expected %v got %v",
- expectedCounts[i].StartTime, pq.StartTime)
- }
- if !pq.EndTime.Equal(expectedCounts[i].EndTime) {
- t.Errorf("mismatched end time: expected %v got %v",
- expectedCounts[i].EndTime, pq.EndTime)
- }
- }
-
- testCases := []struct {
- InsertUpTo int // index in the toInsert array
- PrevMonth int64
- NextMonth int64
- ExpectedUpTo int // index in the expectedCounts array
- }{
- {
- 1,
- september.Unix(),
- october.Unix(),
- 0,
- },
- {
- 2,
- november.Unix(),
- december.Unix(),
- 1,
- },
- }
-
- inserted := -1
- for _, tc := range testCases {
- t.Logf("tc %+v", tc)
-
- // Persists across loops
- for inserted < tc.InsertUpTo {
- inserted += 1
- t.Logf("inserting segment %v", inserted)
- doInsert(inserted)
- }
-
- intent := &ActivityIntentLog{
- PreviousMonth: tc.PrevMonth,
- NextMonth: tc.NextMonth,
- }
- data, err := json.Marshal(intent)
- if err != nil {
- t.Fatal(err)
- }
- WriteToStorage(t, core, "sys/counters/activity/endofmonth", data)
-
- // Pretend we've successfully rolled over to the following month
- a.SetStartTimestamp(tc.NextMonth)
-
- err = a.precomputedQueryWorker(ctx)
- if err != nil {
- t.Fatal(err)
- }
-
- expectMissingSegment(t, core, "sys/counters/activity/endofmonth")
-
- for i := 0; i <= tc.ExpectedUpTo; i++ {
- checkPrecomputedQuery(i)
- }
- }
-}
-
-// TestActivityLog_PrecomputeNonEntityTokensWithID is the same test as
-// TestActivityLog_Precompute, except all the clients are tokens without
-// entities. This ensures the deduplication logic and separation logic between
-// entities and TWE clients is correct.
-func TestActivityLog_PrecomputeNonEntityTokensWithID(t *testing.T) {
- timeutil.SkipAtEndOfMonth(t)
-
- january := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
- august := time.Date(2020, 8, 15, 12, 0, 0, 0, time.UTC)
- september := timeutil.StartOfMonth(time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC))
- october := timeutil.StartOfMonth(time.Date(2020, 10, 1, 0, 0, 0, 0, time.UTC))
- november := timeutil.StartOfMonth(time.Date(2020, 11, 1, 0, 0, 0, 0, time.UTC))
-
- conf := &CoreConfig{
- ActivityLogConfig: ActivityLogCoreConfig{
- ForceEnable: true,
- DisableTimers: true,
- },
- }
- sink := SetupMetrics(conf)
- core, _, _ := TestCoreUnsealedWithConfig(t, conf)
- a := core.activityLog
- ctx := namespace.RootContext(nil)
-
- // Generate overlapping sets of entity IDs from this list.
- // january: 40-44 RRRRR
- // first month: 0-19 RRRRRAAAAABBBBBRRRRR
- // second month: 10-29 BBBBBRRRRRRRRRRCCCCC
- // third month: 15-39 RRRRRRRRRRCCCCCRRRRRBBBBB
-
- clientRecords := make([]*activity.EntityRecord, 45)
- clientNamespaces := []string{"root", "aaaaa", "bbbbb", "root", "root", "ccccc", "root", "bbbbb", "rrrrr"}
-
- for i := range clientRecords {
- clientRecords[i] = &activity.EntityRecord{
- ClientID: fmt.Sprintf("111122222-3333-4444-5555-%012v", i),
- NamespaceID: clientNamespaces[i/5],
- Timestamp: time.Now().Unix(),
- NonEntity: true,
- }
- }
-
- toInsert := []struct {
- StartTime int64
- Segment uint64
- Clients []*activity.EntityRecord
- }{
- // January, should not be included
- {
- january.Unix(),
- 0,
- clientRecords[40:45],
- },
- // Artifically split August and October
- { // 1
- august.Unix(),
- 0,
- clientRecords[:13],
- },
- { // 2
- august.Unix(),
- 1,
- clientRecords[13:20],
- },
- { // 3
- september.Unix(),
- 0,
- clientRecords[10:30],
- },
- { // 4
- october.Unix(),
- 0,
- clientRecords[15:40],
- },
- {
- october.Unix(),
- 1,
- clientRecords[15:40],
- },
- {
- october.Unix(),
- 2,
- clientRecords[17:23],
- },
- }
-
- // Note that precomputedQuery worker doesn't filter
- // for times <= the one it was asked to do. Is that a problem?
- // Here, it means that we can't insert everything *first* and do multiple
- // test cases, we have to write logs incrementally.
- doInsert := func(i int) {
- segment := toInsert[i]
- eal := &activity.EntityActivityLog{
- Clients: segment.Clients,
- }
- data, err := proto.Marshal(eal)
- if err != nil {
- t.Fatal(err)
- }
- path := fmt.Sprintf("%ventity/%v/%v", ActivityLogPrefix, segment.StartTime, segment.Segment)
- WriteToStorage(t, core, path, data)
- }
-
- expectedCounts := []struct {
- StartTime time.Time
- EndTime time.Time
- ByNamespace map[string]int
- }{
- // First test case
- {
- august,
- timeutil.EndOfMonth(august),
- map[string]int{
- "root": 10,
- "aaaaa": 5,
- "bbbbb": 5,
- },
- },
- // Second test case
- {
- august,
- timeutil.EndOfMonth(september),
- map[string]int{
- "root": 15,
- "aaaaa": 5,
- "bbbbb": 5,
- "ccccc": 5,
- },
- },
- {
- september,
- timeutil.EndOfMonth(september),
- map[string]int{
- "root": 10,
- "bbbbb": 5,
- "ccccc": 5,
- },
- },
- // Third test case
- {
- august,
- timeutil.EndOfMonth(october),
- map[string]int{
- "root": 20,
- "aaaaa": 5,
- "bbbbb": 10,
- "ccccc": 5,
- },
- },
- {
- september,
- timeutil.EndOfMonth(october),
- map[string]int{
- "root": 15,
- "bbbbb": 10,
- "ccccc": 5,
- },
- },
- {
- october,
- timeutil.EndOfMonth(october),
- map[string]int{
- "root": 15,
- "bbbbb": 5,
- "ccccc": 5,
- },
- },
- }
-
- checkPrecomputedQuery := func(i int) {
- t.Helper()
- pq, err := a.queryStore.Get(ctx, expectedCounts[i].StartTime, expectedCounts[i].EndTime)
- if err != nil {
- t.Fatal(err)
- }
- if pq == nil {
- t.Errorf("empty result for %v -- %v", expectedCounts[i].StartTime, expectedCounts[i].EndTime)
- }
- if len(pq.Namespaces) != len(expectedCounts[i].ByNamespace) {
- t.Errorf("mismatched number of namespaces, expected %v got %v",
- len(expectedCounts[i].ByNamespace), len(pq.Namespaces))
- }
- for _, nsRecord := range pq.Namespaces {
- val, ok := expectedCounts[i].ByNamespace[nsRecord.NamespaceID]
- if !ok {
- t.Errorf("unexpected namespace %v", nsRecord.NamespaceID)
- continue
- }
- if uint64(val) != nsRecord.NonEntityTokens {
- t.Errorf("wrong number of entities in %v: expected %v, got %v",
- nsRecord.NamespaceID, val, nsRecord.NonEntityTokens)
- }
- }
- if !pq.StartTime.Equal(expectedCounts[i].StartTime) {
- t.Errorf("mismatched start time: expected %v got %v",
- expectedCounts[i].StartTime, pq.StartTime)
- }
- if !pq.EndTime.Equal(expectedCounts[i].EndTime) {
- t.Errorf("mismatched end time: expected %v got %v",
- expectedCounts[i].EndTime, pq.EndTime)
- }
- }
-
- testCases := []struct {
- InsertUpTo int // index in the toInsert array
- PrevMonth int64
- NextMonth int64
- ExpectedUpTo int // index in the expectedCounts array
- }{
- {
- 2, // jan-august
- august.Unix(),
- september.Unix(),
- 0, // august-august
- },
- {
- 3, // jan-sept
- september.Unix(),
- october.Unix(),
- 2, // august-september
- },
- {
- 6, // jan-oct
- october.Unix(),
- november.Unix(),
- 5, // august-september
- },
- }
-
- inserted := -1
- for _, tc := range testCases {
- t.Logf("tc %+v", tc)
-
- // Persists across loops
- for inserted < tc.InsertUpTo {
- inserted += 1
- t.Logf("inserting segment %v", inserted)
- doInsert(inserted)
- }
-
- intent := &ActivityIntentLog{
- PreviousMonth: tc.PrevMonth,
- NextMonth: tc.NextMonth,
- }
- data, err := json.Marshal(intent)
- if err != nil {
- t.Fatal(err)
- }
- WriteToStorage(t, core, "sys/counters/activity/endofmonth", data)
-
- // Pretend we've successfully rolled over to the following month
- a.SetStartTimestamp(tc.NextMonth)
-
- err = a.precomputedQueryWorker(ctx)
- if err != nil {
- t.Fatal(err)
- }
-
- expectMissingSegment(t, core, "sys/counters/activity/endofmonth")
-
- for i := 0; i <= tc.ExpectedUpTo; i++ {
- checkPrecomputedQuery(i)
- }
- }
-
- // Check metrics on the last precomputed query
- // (otherwise we need a way to reset the in-memory metrics between test cases.)
-
- intervals := sink.Data()
- // Test crossed an interval boundary, don't try to deal with it.
- if len(intervals) > 1 {
- t.Skip("Detected interval crossing.")
- }
- expectedGauges := []struct {
- Name string
- NamespaceLabel string
- Value float32
- }{
- // october values
- {
- "identity.nonentity.active.monthly",
- "root",
- 15.0,
- },
- {
- "identity.nonentity.active.monthly",
- "deleted-bbbbb", // No namespace entry for this fake ID
- 5.0,
- },
- {
- "identity.nonentity.active.monthly",
- "deleted-ccccc",
- 5.0,
- },
- // august-september values
- {
- "identity.nonentity.active.reporting_period",
- "root",
- 20.0,
- },
- {
- "identity.nonentity.active.reporting_period",
- "deleted-aaaaa",
- 5.0,
- },
- {
- "identity.nonentity.active.reporting_period",
- "deleted-bbbbb",
- 10.0,
- },
- {
- "identity.nonentity.active.reporting_period",
- "deleted-ccccc",
- 5.0,
- },
- }
- for _, g := range expectedGauges {
- found := false
- for _, actual := range intervals[0].Gauges {
- actualNamespaceLabel := ""
- for _, l := range actual.Labels {
- if l.Name == "namespace" {
- actualNamespaceLabel = l.Value
- break
- }
- }
- if actual.Name == g.Name && actualNamespaceLabel == g.NamespaceLabel {
- found = true
- if actual.Value != g.Value {
- t.Errorf("Mismatched value for %v %v %v != %v",
- g.Name, g.NamespaceLabel, actual.Value, g.Value)
- }
- break
- }
- }
- if !found {
- t.Errorf("No guage found for %v %v",
- g.Name, g.NamespaceLabel)
- }
- }
-}
-
-type BlockingInmemStorage struct{}
-
-func (b *BlockingInmemStorage) List(ctx context.Context, prefix string) ([]string, error) {
- <-ctx.Done()
- return nil, errors.New("fake implementation")
-}
-
-func (b *BlockingInmemStorage) Get(ctx context.Context, key string) (*logical.StorageEntry, error) {
- <-ctx.Done()
- return nil, errors.New("fake implementation")
-}
-
-func (b *BlockingInmemStorage) Put(ctx context.Context, entry *logical.StorageEntry) error {
- <-ctx.Done()
- return errors.New("fake implementation")
-}
-
-func (b *BlockingInmemStorage) Delete(ctx context.Context, key string) error {
- <-ctx.Done()
- return errors.New("fake implementation")
-}
-
-// TestActivityLog_PrecomputeCancel stops the activity log before running the precomputedQueryWorker, and verifies that
-// the context used to query storage has been canceled.
-func TestActivityLog_PrecomputeCancel(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
-
- // Substitute in a new view
- a.view = NewBarrierView(&BlockingInmemStorage{}, "test")
-
- core.stopActivityLog()
-
- done := make(chan struct{})
-
- // This will block if the shutdown didn't work.
- go func() {
- // We expect this to error because of BlockingInmemStorage
- _ = a.precomputedQueryWorker(namespace.RootContext(nil))
- close(done)
- }()
-
- timeout := time.After(5 * time.Second)
-
- select {
- case <-done:
- break
- case <-timeout:
- t.Fatalf("timeout waiting for worker to finish")
- }
-}
-
-// TestActivityLog_NextMonthStart sets the activity log start timestamp, then verifies that StartOfNextMonth returns the
-// correct value.
-func TestActivityLog_NextMonthStart(t *testing.T) {
- timeutil.SkipAtEndOfMonth(t)
-
- now := time.Now().UTC()
- year, month, _ := now.Date()
- computedStart := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC).AddDate(0, 1, 0)
-
- testCases := []struct {
- SegmentStart int64
- ExpectedTime time.Time
- }{
- {
- 0,
- computedStart,
- },
- {
- time.Date(2021, 2, 12, 13, 14, 15, 0, time.UTC).Unix(),
- time.Date(2021, 3, 1, 0, 0, 0, 0, time.UTC),
- },
- {
- time.Date(2021, 3, 1, 0, 0, 0, 0, time.UTC).Unix(),
- time.Date(2021, 4, 1, 0, 0, 0, 0, time.UTC),
- },
- }
-
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
-
- for _, tc := range testCases {
- t.Logf("segmentStart=%v", tc.SegmentStart)
- a.SetStartTimestamp(tc.SegmentStart)
-
- actual := a.StartOfNextMonth()
- if !actual.Equal(tc.ExpectedTime) {
- t.Errorf("expected %v, got %v", tc.ExpectedTime, actual)
- }
- }
-}
-
-// The retention worker is called on unseal; wait for it to finish before
-// proceeding with the test.
-func waitForRetentionWorkerToFinish(t *testing.T, a *ActivityLog) {
- t.Helper()
- timeout := time.After(30 * time.Second)
- select {
- case <-a.retentionDone:
- return
- case <-timeout:
- t.Fatal("timeout waiting for retention worker to finish")
- }
-}
-
-// TestActivityLog_Deletion writes entity, direct tokens, and queries for dates ranging over 20 months. Then the test
-// calls the retentionWorker with decreasing retention values, and verifies that the correct paths are being deleted.
-func TestActivityLog_Deletion(t *testing.T) {
- timeutil.SkipAtEndOfMonth(t)
-
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
- waitForRetentionWorkerToFinish(t, a)
-
- times := []time.Time{
- time.Date(2019, 1, 15, 1, 2, 3, 0, time.UTC), // 0
- time.Date(2019, 3, 15, 1, 2, 3, 0, time.UTC),
- time.Date(2019, 4, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2019, 5, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2019, 6, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2019, 7, 1, 0, 0, 0, 0, time.UTC), // 5
- time.Date(2019, 8, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2019, 11, 1, 0, 0, 0, 0, time.UTC), // <-- 12 months starts here
- time.Date(2019, 12, 1, 0, 0, 0, 0, time.UTC), // 10
- time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 3, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 4, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 5, 1, 0, 0, 0, 0, time.UTC), // 15
- time.Date(2020, 6, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 7, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 8, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2020, 10, 1, 0, 0, 0, 0, time.UTC), // 20
- time.Date(2020, 11, 1, 0, 0, 0, 0, time.UTC),
- }
-
- novIndex := len(times) - 1
- paths := make([][]string, len(times))
-
- for i, start := range times {
- // no entities in some months, just for fun
- for j := 0; j < (i+3)%5; j++ {
- entityPath := fmt.Sprintf("%ventity/%v/%v", ActivityLogPrefix, start.Unix(), j)
- paths[i] = append(paths[i], entityPath)
- WriteToStorage(t, core, entityPath, []byte("test"))
- }
- tokenPath := fmt.Sprintf("%vdirecttokens/%v/0", ActivityLogPrefix, start.Unix())
- paths[i] = append(paths[i], tokenPath)
- WriteToStorage(t, core, tokenPath, []byte("test"))
-
- // No queries for November yet
- if i < novIndex {
- for _, endTime := range times[i+1 : novIndex] {
- queryPath := fmt.Sprintf("sys/counters/activity/queries/%v/%v", start.Unix(), endTime.Unix())
- paths[i] = append(paths[i], queryPath)
- WriteToStorage(t, core, queryPath, []byte("test"))
- }
- }
- }
-
- checkPresent := func(i int) {
- t.Helper()
- for _, p := range paths[i] {
- readSegmentFromStorage(t, core, p)
- }
- }
-
- checkAbsent := func(i int) {
- t.Helper()
- for _, p := range paths[i] {
- expectMissingSegment(t, core, p)
- }
- }
-
- ctx := namespace.RootContext(nil)
- t.Log("24 months")
- now := times[len(times)-1]
- err := a.retentionWorker(ctx, now, 24)
- if err != nil {
- t.Fatal(err)
- }
- for i := range times {
- checkPresent(i)
- }
-
- t.Log("12 months")
- err = a.retentionWorker(ctx, now, 12)
- if err != nil {
- t.Fatal(err)
- }
- for i := 0; i <= 8; i++ {
- checkAbsent(i)
- }
- for i := 9; i <= 21; i++ {
- checkPresent(i)
- }
-
- t.Log("1 month")
- err = a.retentionWorker(ctx, now, 1)
- if err != nil {
- t.Fatal(err)
- }
- for i := 0; i <= 19; i++ {
- checkAbsent(i)
- }
- checkPresent(20)
- checkPresent(21)
-
- t.Log("0 months")
- err = a.retentionWorker(ctx, now, 0)
- if err != nil {
- t.Fatal(err)
- }
- for i := 0; i <= 20; i++ {
- checkAbsent(i)
- }
- checkPresent(21)
-}
-
-// TestActivityLog_partialMonthClientCount writes segment data for the curren month and runs refreshFromStoredLog and
-// then partialMonthClientCount. The test verifies that the values returned by partialMonthClientCount are correct.
-func TestActivityLog_partialMonthClientCount(t *testing.T) {
- timeutil.SkipAtEndOfMonth(t)
-
- ctx := namespace.RootContext(nil)
- now := time.Now().UTC()
- a, clients, _ := setupActivityRecordsInStorage(t, timeutil.StartOfMonth(now), true, true)
-
- // clients[0] belongs to previous month
- clients = clients[1:]
-
- clientCounts := make(map[string]uint64)
- for _, client := range clients {
- clientCounts[client.NamespaceID] += 1
- }
-
- a.SetEnable(true)
- var wg sync.WaitGroup
- err := a.refreshFromStoredLog(ctx, &wg, now)
- if err != nil {
- t.Fatalf("error loading clients: %v", err)
- }
- wg.Wait()
-
- results, err := a.partialMonthClientCount(ctx)
- if err != nil {
- t.Fatal(err)
- }
- if results == nil {
- t.Fatal("no results to test")
- }
-
- byNamespace, ok := results["by_namespace"]
- if !ok {
- t.Fatalf("malformed results. got %v", results)
- }
-
- clientCountResponse := make([]*ResponseNamespace, 0)
- err = mapstructure.Decode(byNamespace, &clientCountResponse)
- if err != nil {
- t.Fatal(err)
- }
-
- for _, clientCount := range clientCountResponse {
- if int(clientCounts[clientCount.NamespaceID]) != clientCount.Counts.DistinctEntities {
- t.Errorf("bad entity count for namespace %s . expected %d, got %d", clientCount.NamespaceID, int(clientCounts[clientCount.NamespaceID]), clientCount.Counts.DistinctEntities)
- }
- totalCount := int(clientCounts[clientCount.NamespaceID])
- if totalCount != clientCount.Counts.Clients {
- t.Errorf("bad client count for namespace %s . expected %d, got %d", clientCount.NamespaceID, totalCount, clientCount.Counts.Clients)
- }
- }
-
- distinctEntities, ok := results["distinct_entities"]
- if !ok {
- t.Fatalf("malformed results. got %v", results)
- }
- if distinctEntities != len(clients) {
- t.Errorf("bad entity count. expected %d, got %d", len(clients), distinctEntities)
- }
-
- clientCount, ok := results["clients"]
- if !ok {
- t.Fatalf("malformed results. got %v", results)
- }
- if clientCount != len(clients) {
- t.Errorf("bad client count. expected %d, got %d", len(clients), clientCount)
- }
-}
-
-// TestActivityLog_partialMonthClientCountUsingHandleQuery writes segments for the current month and calls
-// refreshFromStoredLog, then handleQuery. The test verifies that the results from handleQuery are correct.
-func TestActivityLog_partialMonthClientCountUsingHandleQuery(t *testing.T) {
- timeutil.SkipAtEndOfMonth(t)
-
- ctx := namespace.RootContext(nil)
- now := time.Now().UTC()
- a, clients, _ := setupActivityRecordsInStorage(t, timeutil.StartOfMonth(now), true, true)
-
- // clients[0] belongs to previous month
- clients = clients[1:]
-
- clientCounts := make(map[string]uint64)
- for _, client := range clients {
- clientCounts[client.NamespaceID] += 1
- }
-
- a.SetEnable(true)
- var wg sync.WaitGroup
- err := a.refreshFromStoredLog(ctx, &wg, now)
- if err != nil {
- t.Fatalf("error loading clients: %v", err)
- }
- wg.Wait()
-
- results, err := a.handleQuery(ctx, time.Now().UTC(), time.Now().UTC(), 0)
- if err != nil {
- t.Fatal(err)
- }
- if results == nil {
- t.Fatal("no results to test")
- }
- if err != nil {
- t.Fatal(err)
- }
- if results == nil {
- t.Fatal("no results to test")
- }
-
- byNamespace, ok := results["by_namespace"]
- if !ok {
- t.Fatalf("malformed results. got %v", results)
- }
-
- clientCountResponse := make([]*ResponseNamespace, 0)
- err = mapstructure.Decode(byNamespace, &clientCountResponse)
- if err != nil {
- t.Fatal(err)
- }
-
- for _, clientCount := range clientCountResponse {
- if int(clientCounts[clientCount.NamespaceID]) != clientCount.Counts.DistinctEntities {
- t.Errorf("bad entity count for namespace %s . expected %d, got %d", clientCount.NamespaceID, int(clientCounts[clientCount.NamespaceID]), clientCount.Counts.DistinctEntities)
- }
- totalCount := int(clientCounts[clientCount.NamespaceID])
- if totalCount != clientCount.Counts.Clients {
- t.Errorf("bad client count for namespace %s . expected %d, got %d", clientCount.NamespaceID, totalCount, clientCount.Counts.Clients)
- }
- }
-
- totals, ok := results["total"]
- if !ok {
- t.Fatalf("malformed results. got %v", results)
- }
- totalCounts := ResponseCounts{}
- err = mapstructure.Decode(totals, &totalCounts)
- distinctEntities := totalCounts.DistinctEntities
- if distinctEntities != len(clients) {
- t.Errorf("bad entity count. expected %d, got %d", len(clients), distinctEntities)
- }
-
- clientCount := totalCounts.Clients
- if clientCount != len(clients) {
- t.Errorf("bad client count. expected %d, got %d", len(clients), clientCount)
- }
- // Ensure that the month response is the same as the totals, because all clients
- // are new clients and there will be no approximation in the single month partial
- // case
- monthsRaw, ok := results["months"]
- if !ok {
- t.Fatalf("malformed results. got %v", results)
- }
- monthsResponse := make([]ResponseMonth, 0)
- err = mapstructure.Decode(monthsRaw, &monthsResponse)
- if len(monthsResponse) != 1 {
- t.Fatalf("wrong number of months returned. got %v", monthsResponse)
- }
- if monthsResponse[0].Counts.Clients != totalCounts.Clients {
- t.Fatalf("wrong client count. got %v, expected %v", monthsResponse[0].Counts.Clients, totalCounts.Clients)
- }
- if monthsResponse[0].Counts.EntityClients != totalCounts.EntityClients {
- t.Fatalf("wrong entity client count. got %v, expected %v", monthsResponse[0].Counts.EntityClients, totalCounts.EntityClients)
- }
- if monthsResponse[0].Counts.NonEntityClients != totalCounts.NonEntityClients {
- t.Fatalf("wrong non-entity client count. got %v, expected %v", monthsResponse[0].Counts.NonEntityClients, totalCounts.NonEntityClients)
- }
- if monthsResponse[0].Counts.NonEntityTokens != totalCounts.NonEntityTokens {
- t.Fatalf("wrong non-entity client count. got %v, expected %v", monthsResponse[0].Counts.NonEntityTokens, totalCounts.NonEntityTokens)
- }
- if monthsResponse[0].Counts.Clients != monthsResponse[0].NewClients.Counts.Clients {
- t.Fatalf("wrong client count. got %v, expected %v", monthsResponse[0].Counts.Clients, monthsResponse[0].NewClients.Counts.Clients)
- }
- if monthsResponse[0].Counts.DistinctEntities != monthsResponse[0].NewClients.Counts.DistinctEntities {
- t.Fatalf("wrong distinct entities count. got %v, expected %v", monthsResponse[0].Counts.DistinctEntities, monthsResponse[0].NewClients.Counts.DistinctEntities)
- }
- if monthsResponse[0].Counts.EntityClients != monthsResponse[0].NewClients.Counts.EntityClients {
- t.Fatalf("wrong entity client count. got %v, expected %v", monthsResponse[0].Counts.EntityClients, monthsResponse[0].NewClients.Counts.EntityClients)
- }
- if monthsResponse[0].Counts.NonEntityClients != monthsResponse[0].NewClients.Counts.NonEntityClients {
- t.Fatalf("wrong non-entity client count. got %v, expected %v", monthsResponse[0].Counts.NonEntityClients, monthsResponse[0].NewClients.Counts.NonEntityClients)
- }
- if monthsResponse[0].Counts.NonEntityTokens != monthsResponse[0].NewClients.Counts.NonEntityTokens {
- t.Fatalf("wrong non-entity token count. got %v, expected %v", monthsResponse[0].Counts.NonEntityTokens, monthsResponse[0].NewClients.Counts.NonEntityTokens)
- }
-
- namespaceResponseMonth := monthsResponse[0].Namespaces
-
- for _, clientCount := range namespaceResponseMonth {
- if int(clientCounts[clientCount.NamespaceID]) != clientCount.Counts.EntityClients {
- t.Errorf("bad entity count for namespace %s . expected %d, got %d", clientCount.NamespaceID, int(clientCounts[clientCount.NamespaceID]), clientCount.Counts.DistinctEntities)
- }
- totalCount := int(clientCounts[clientCount.NamespaceID])
- if totalCount != clientCount.Counts.Clients {
- t.Errorf("bad client count for namespace %s . expected %d, got %d", clientCount.NamespaceID, totalCount, clientCount.Counts.Clients)
- }
- }
-}
-
-// TestActivityLog_handleQuery_normalizedMountPaths ensures that the mount paths returned by the activity log always have a trailing slash and client accounting is done correctly when there's no trailing slash.
-// Two clients that have the same mount path, but one has a trailing slash, should be considered part of the same mount path.
-func TestActivityLog_handleQuery_normalizedMountPaths(t *testing.T) {
- timeutil.SkipAtEndOfMonth(t)
-
- core, _, _ := TestCoreUnsealed(t)
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "auth/")
- ctx := namespace.RootContext(nil)
- now := time.Now().UTC()
- a := core.activityLog
- a.SetEnable(true)
-
- uuid1, err := uuid.GenerateUUID()
- require.NoError(t, err)
- uuid2, err := uuid.GenerateUUID()
- require.NoError(t, err)
- accessor1 := "accessor1"
- accessor2 := "accessor2"
- pathWithSlash := "auth/foo/"
- pathWithoutSlash := "auth/foo"
-
- // create two mounts of the same name. One has a trailing slash, the other doesn't
- err = core.router.Mount(&NoopBackend{}, "auth/foo", &MountEntry{UUID: uuid1, Accessor: accessor1, NamespaceID: namespace.RootNamespaceID, namespace: namespace.RootNamespace, Path: pathWithSlash}, view)
- require.NoError(t, err)
- err = core.router.Mount(&NoopBackend{}, "auth/bar", &MountEntry{UUID: uuid2, Accessor: accessor2, NamespaceID: namespace.RootNamespaceID, namespace: namespace.RootNamespace, Path: pathWithoutSlash}, view)
- require.NoError(t, err)
-
- // handle token usage for each of the mount paths
- a.HandleTokenUsage(ctx, &logical.TokenEntry{Path: pathWithSlash, NamespaceID: namespace.RootNamespaceID}, "id1", false)
- a.HandleTokenUsage(ctx, &logical.TokenEntry{Path: pathWithoutSlash, NamespaceID: namespace.RootNamespaceID}, "id2", false)
- // and have client 2 use both mount paths
- a.HandleTokenUsage(ctx, &logical.TokenEntry{Path: pathWithSlash, NamespaceID: namespace.RootNamespaceID}, "id2", false)
-
- // query the data for the month
- results, err := a.handleQuery(ctx, timeutil.StartOfMonth(now), timeutil.EndOfMonth(now), 0)
- require.NoError(t, err)
-
- byNamespace := results["by_namespace"].([]*ResponseNamespace)
- require.Len(t, byNamespace, 1)
- byMount := byNamespace[0].Mounts
- require.Len(t, byMount, 1)
- mountPath := byMount[0].MountPath
-
- // verify that both clients are recorded for the mount path with the slash
- require.Equal(t, mountPath, pathWithSlash)
- require.Equal(t, byMount[0].Counts.Clients, 2)
-}
-
-// TestActivityLog_partialMonthClientCountWithMultipleMountPaths verifies that logic in refreshFromStoredLog includes all mount paths
-// in its mount data. In this test we create 3 entity records with different mount accessors: one is empty, one is
-// valid, one can't be found (so it's assumed the mount is deleted). These records are written to storage, then this data is
-// refreshed in refreshFromStoredLog, and finally we verify the results returned with partialMonthClientCount.
-func TestActivityLog_partialMonthClientCountWithMultipleMountPaths(t *testing.T) {
- timeutil.SkipAtEndOfMonth(t)
-
- core, _, _ := TestCoreUnsealed(t)
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "auth/")
-
- ctx := namespace.RootContext(nil)
- now := time.Now().UTC()
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
-
- a := core.activityLog
- path := "auth/foo/bar/"
- accessor := "authfooaccessor"
-
- // we mount a path using the accessor 'authfooaccessor' which has mount path "auth/foo/bar"
- // when an entity record references this accessor, activity log will be able to find it on its mounts and translate the mount accessor
- // into a mount path
- err = core.router.Mount(&NoopBackend{}, "auth/foo/", &MountEntry{UUID: meUUID, Accessor: accessor, NamespaceID: namespace.RootNamespaceID, namespace: namespace.RootNamespace, Path: path}, view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- entityRecords := []*activity.EntityRecord{
- {
- // this record has no mount accessor, so it'll get recorded as a pre-1.10 upgrade
- ClientID: "11111111-1111-1111-1111-111111111111",
- NamespaceID: namespace.RootNamespaceID,
- Timestamp: time.Now().Unix(),
- },
- {
- // this record's mount path won't be able to be found, because there's no mount with the accessor 'deleted'
- // the code in mountAccessorToMountPath assumes that if the mount accessor isn't empty but the mount path
- // can't be found, then the mount must have been deleted
- ClientID: "22222222-2222-2222-2222-222222222222",
- NamespaceID: namespace.RootNamespaceID,
- Timestamp: time.Now().Unix(),
- MountAccessor: "deleted",
- },
- {
- // this record will have mount path 'auth/foo/bar', because we set up the mount above
- ClientID: "33333333-2222-2222-2222-222222222222",
- NamespaceID: namespace.RootNamespaceID,
- Timestamp: time.Now().Unix(),
- MountAccessor: "authfooaccessor",
- },
- }
- for i, entityRecord := range entityRecords {
- entityData, err := proto.Marshal(&activity.EntityActivityLog{
- Clients: []*activity.EntityRecord{entityRecord},
- })
- if err != nil {
- t.Fatalf(err.Error())
- }
- storagePath := fmt.Sprintf("%sentity/%d/%d", ActivityLogPrefix, timeutil.StartOfMonth(now).Unix(), i)
- WriteToStorage(t, core, storagePath, entityData)
- }
-
- a.SetEnable(true)
- var wg sync.WaitGroup
- err = a.refreshFromStoredLog(ctx, &wg, now)
- if err != nil {
- t.Fatalf("error loading clients: %v", err)
- }
- wg.Wait()
-
- results, err := a.partialMonthClientCount(ctx)
- if err != nil {
- t.Fatal(err)
- }
- if results == nil {
- t.Fatal("no results to test")
- }
-
- byNamespace, ok := results["by_namespace"]
- if !ok {
- t.Fatalf("malformed results. got %v", results)
- }
-
- clientCountResponse := make([]*ResponseNamespace, 0)
- err = mapstructure.Decode(byNamespace, &clientCountResponse)
- if err != nil {
- t.Fatal(err)
- }
- if len(clientCountResponse) != 1 {
- t.Fatalf("incorrect client count responses, expected 1 but got %d", len(clientCountResponse))
- }
- if len(clientCountResponse[0].Mounts) != len(entityRecords) {
- t.Fatalf("incorrect client mounts, expected %d but got %d", len(entityRecords), len(clientCountResponse[0].Mounts))
- }
- byPath := make(map[string]int, len(clientCountResponse[0].Mounts))
- for _, mount := range clientCountResponse[0].Mounts {
- byPath[mount.MountPath] = byPath[mount.MountPath] + mount.Counts.Clients
- }
-
- // these are the paths that are expected and correspond with the entity records created above
- expectedPaths := []string{
- noMountAccessor,
- fmt.Sprintf(deletedMountFmt, "deleted"),
- path,
- }
- for _, expectedPath := range expectedPaths {
- count, ok := byPath[expectedPath]
- if !ok {
- t.Fatalf("path %s not found", expectedPath)
- }
- if count != 1 {
- t.Fatalf("incorrect count value %d for path %s", count, expectedPath)
- }
- }
-}
-
-// TestActivityLog_processNewClients_delete ensures that the correct clients are deleted from a processNewClients struct
-func TestActivityLog_processNewClients_delete(t *testing.T) {
- mount := "mount"
- namespace := "namespace"
- clientID := "client-id"
- run := func(t *testing.T, isNonEntity bool) {
- t.Helper()
- record := &activity.EntityRecord{
- MountAccessor: mount,
- NamespaceID: namespace,
- ClientID: clientID,
- NonEntity: isNonEntity,
- }
- newClients := newProcessNewClients()
- newClients.add(record)
-
- require.True(t, newClients.Counts.contains(record))
- require.True(t, newClients.Namespaces[namespace].Counts.contains(record))
- require.True(t, newClients.Namespaces[namespace].Mounts[mount].Counts.contains(record))
-
- newClients.delete(record)
-
- byNS := newClients.Namespaces
- counts := newClients.Counts
- require.NotContains(t, counts.NonEntities, clientID)
- require.NotContains(t, counts.Entities, clientID)
-
- require.NotContains(t, counts.NonEntities, clientID)
- require.NotContains(t, counts.Entities, clientID)
-
- require.NotContains(t, byNS[namespace].Mounts[mount].Counts.NonEntities, clientID)
- require.NotContains(t, byNS[namespace].Counts.NonEntities, clientID)
-
- require.NotContains(t, byNS[namespace].Mounts[mount].Counts.Entities, clientID)
- require.NotContains(t, byNS[namespace].Counts.Entities, clientID)
- }
- t.Run("entity", func(t *testing.T) {
- run(t, false)
- })
- t.Run("non-entity", func(t *testing.T) {
- run(t, true)
- })
-}
-
-// TestActivityLog_processClientRecord calls processClientRecord for an entity and a non-entity record and verifies that
-// the record is present in the namespace and month maps
-func TestActivityLog_processClientRecord(t *testing.T) {
- startTime := time.Now()
- mount := "mount"
- namespace := "namespace"
- clientID := "client-id"
- run := func(t *testing.T, isNonEntity bool) {
- t.Helper()
- record := &activity.EntityRecord{
- MountAccessor: mount,
- NamespaceID: namespace,
- ClientID: clientID,
- NonEntity: isNonEntity,
- }
- byNS := make(summaryByNamespace)
- byMonth := make(summaryByMonth)
- processClientRecord(record, byNS, byMonth, startTime)
- require.Contains(t, byNS, namespace)
- require.Contains(t, byNS[namespace].Mounts, mount)
- monthIndex := timeutil.StartOfMonth(startTime).UTC().Unix()
- require.Contains(t, byMonth, monthIndex)
- require.Equal(t, byMonth[monthIndex].Namespaces, byNS)
- require.Equal(t, byMonth[monthIndex].NewClients.Namespaces, byNS)
-
- if isNonEntity {
- require.Contains(t, byMonth[monthIndex].Counts.NonEntities, clientID)
- require.NotContains(t, byMonth[monthIndex].Counts.Entities, clientID)
-
- require.Contains(t, byMonth[monthIndex].NewClients.Counts.NonEntities, clientID)
- require.NotContains(t, byMonth[monthIndex].NewClients.Counts.Entities, clientID)
-
- require.Contains(t, byNS[namespace].Mounts[mount].Counts.NonEntities, clientID)
- require.Contains(t, byNS[namespace].Counts.NonEntities, clientID)
-
- require.NotContains(t, byNS[namespace].Mounts[mount].Counts.Entities, clientID)
- require.NotContains(t, byNS[namespace].Counts.Entities, clientID)
- } else {
- require.Contains(t, byMonth[monthIndex].Counts.Entities, clientID)
- require.NotContains(t, byMonth[monthIndex].Counts.NonEntities, clientID)
-
- require.Contains(t, byMonth[monthIndex].NewClients.Counts.Entities, clientID)
- require.NotContains(t, byMonth[monthIndex].NewClients.Counts.NonEntities, clientID)
-
- require.Contains(t, byNS[namespace].Mounts[mount].Counts.Entities, clientID)
- require.Contains(t, byNS[namespace].Counts.Entities, clientID)
-
- require.NotContains(t, byNS[namespace].Mounts[mount].Counts.NonEntities, clientID)
- require.NotContains(t, byNS[namespace].Counts.NonEntities, clientID)
- }
- }
- t.Run("non entity", func(t *testing.T) {
- run(t, true)
- })
- t.Run("entity", func(t *testing.T) {
- run(t, false)
- })
-}
-
-func verifyByNamespaceContains(t *testing.T, s summaryByNamespace, clients ...*activity.EntityRecord) {
- t.Helper()
- for _, c := range clients {
- require.Contains(t, s, c.NamespaceID)
- counts := s[c.NamespaceID].Counts
- require.True(t, counts.contains(c))
- mounts := s[c.NamespaceID].Mounts
- require.Contains(t, mounts, c.MountAccessor)
- require.True(t, mounts[c.MountAccessor].Counts.contains(c))
- }
-}
-
-func (s summaryByMonth) firstSeen(t *testing.T, client *activity.EntityRecord) time.Time {
- t.Helper()
- var seen int64
- for month, data := range s {
- present := data.NewClients.Counts.contains(client)
- if present {
- if seen != 0 {
- require.Fail(t, "client seen more than once", client.ClientID, s)
- }
- seen = month
- }
- }
- return time.Unix(seen, 0).UTC()
-}
-
-// TestActivityLog_handleEntitySegment verifies that the by namespace and by month summaries are correctly filled in a
-// variety of scenarios
-func TestActivityLog_handleEntitySegment(t *testing.T) {
- finalTime := timeutil.StartOfMonth(time.Date(2022, 12, 1, 0, 0, 0, 0, time.UTC))
- addMonths := func(i int) time.Time {
- return timeutil.StartOfMonth(finalTime.AddDate(0, i, 0))
- }
- currentSegmentClients := make([]*activity.EntityRecord, 0, 3)
- for i := 0; i < 3; i++ {
- currentSegmentClients = append(currentSegmentClients, &activity.EntityRecord{
- ClientID: fmt.Sprintf("id-%d", i),
- NamespaceID: fmt.Sprintf("ns-%d", i),
- MountAccessor: fmt.Sprintf("mnt-%d", i),
- NonEntity: i == 0,
- })
- }
- a := &ActivityLog{}
- t.Run("older segment empty", func(t *testing.T) {
- hll := hyperloglog.New()
- byNS := make(summaryByNamespace)
- byMonth := make(summaryByMonth)
- segmentTime := addMonths(-3)
- // our 3 clients were seen 3 months ago, with no other clients having been seen
- err := a.handleEntitySegment(&activity.EntityActivityLog{Clients: currentSegmentClients}, segmentTime, hll, pqOptions{
- byNamespace: byNS,
- byMonth: byMonth,
- endTime: timeutil.EndOfMonth(segmentTime),
- activePeriodStart: addMonths(-12),
- activePeriodEnd: addMonths(12),
- })
- require.NoError(t, err)
- require.Len(t, byNS, 3)
- verifyByNamespaceContains(t, byNS, currentSegmentClients...)
- require.Len(t, byMonth, 1)
- // they should all be registered as having first been seen 3 months ago
- require.Equal(t, byMonth.firstSeen(t, currentSegmentClients[0]), segmentTime)
- require.Equal(t, byMonth.firstSeen(t, currentSegmentClients[1]), segmentTime)
- require.Equal(t, byMonth.firstSeen(t, currentSegmentClients[2]), segmentTime)
- // and all 3 should be in the hyperloglog
- require.Equal(t, hll.Estimate(), uint64(3))
- })
- t.Run("older segment clients seen earlier", func(t *testing.T) {
- hll := hyperloglog.New()
- byNS := make(summaryByNamespace)
- byNS.add(currentSegmentClients[0])
- byNS.add(currentSegmentClients[1])
- byMonth := make(summaryByMonth)
- segmentTime := addMonths(-3)
- seenBefore2Months := addMonths(-2)
- seenBefore1Month := addMonths(-1)
-
- // client 0 was seen 2 months ago
- byMonth.add(currentSegmentClients[0], seenBefore2Months)
- // client 1 was seen 1 month ago
- byMonth.add(currentSegmentClients[1], seenBefore1Month)
-
- // handle clients 0, 1, and 2 as having been seen 3 months ago
- err := a.handleEntitySegment(&activity.EntityActivityLog{Clients: currentSegmentClients}, segmentTime, hll, pqOptions{
- byNamespace: byNS,
- byMonth: byMonth,
- endTime: timeutil.EndOfMonth(segmentTime),
- activePeriodStart: addMonths(-12),
- activePeriodEnd: addMonths(12),
- })
- require.NoError(t, err)
- require.Len(t, byNS, 3)
- verifyByNamespaceContains(t, byNS, currentSegmentClients...)
- // we expect that they will only be registered as new 3 months ago, because that's when they were first seen
- require.Equal(t, byMonth.firstSeen(t, currentSegmentClients[0]), segmentTime)
- require.Equal(t, byMonth.firstSeen(t, currentSegmentClients[1]), segmentTime)
- require.Equal(t, byMonth.firstSeen(t, currentSegmentClients[2]), segmentTime)
-
- require.Equal(t, hll.Estimate(), uint64(3))
- })
- t.Run("disjoint set of clients", func(t *testing.T) {
- hll := hyperloglog.New()
- byNS := make(summaryByNamespace)
- byNS.add(currentSegmentClients[0])
- byNS.add(currentSegmentClients[1])
- byMonth := make(summaryByMonth)
- segmentTime := addMonths(-3)
- seenBefore2Months := addMonths(-2)
- seenBefore1Month := addMonths(-1)
-
- // client 0 was seen 2 months ago
- byMonth.add(currentSegmentClients[0], seenBefore2Months)
- // client 1 was seen 1 month ago
- byMonth.add(currentSegmentClients[1], seenBefore1Month)
-
- // handle client 2 as having been seen 3 months ago
- err := a.handleEntitySegment(&activity.EntityActivityLog{Clients: currentSegmentClients[2:]}, segmentTime, hll, pqOptions{
- byNamespace: byNS,
- byMonth: byMonth,
- endTime: timeutil.EndOfMonth(segmentTime),
- activePeriodStart: addMonths(-12),
- activePeriodEnd: addMonths(12),
- })
- require.NoError(t, err)
- require.Len(t, byNS, 3)
- verifyByNamespaceContains(t, byNS, currentSegmentClients...)
- // client 2 should be added to the map, and the other clients should stay where they were
- require.Equal(t, byMonth.firstSeen(t, currentSegmentClients[0]), seenBefore2Months)
- require.Equal(t, byMonth.firstSeen(t, currentSegmentClients[1]), seenBefore1Month)
- require.Equal(t, byMonth.firstSeen(t, currentSegmentClients[2]), segmentTime)
- // the hyperloglog will have 1 element, because there was only 1 client in the segment
- require.Equal(t, hll.Estimate(), uint64(1))
- })
- t.Run("new clients same namespaces", func(t *testing.T) {
- hll := hyperloglog.New()
- byNS := make(summaryByNamespace)
- byNS.add(currentSegmentClients[0])
- byNS.add(currentSegmentClients[1])
- byNS.add(currentSegmentClients[2])
- byMonth := make(summaryByMonth)
- segmentTime := addMonths(-3)
- seenBefore2Months := addMonths(-2)
- seenBefore1Month := addMonths(-1)
-
- // client 0 and 2 were seen 2 months ago
- byMonth.add(currentSegmentClients[0], seenBefore2Months)
- byMonth.add(currentSegmentClients[2], seenBefore2Months)
- // client 1 was seen 1 month ago
- byMonth.add(currentSegmentClients[1], seenBefore1Month)
-
- // create 3 additional clients
- // these have ns-1, ns-2, ns-3 and mnt-1, mnt-2, mnt-3
- moreSegmentClients := make([]*activity.EntityRecord, 0, 3)
- for i := 0; i < 3; i++ {
- moreSegmentClients = append(moreSegmentClients, &activity.EntityRecord{
- ClientID: fmt.Sprintf("id-%d", i+3),
- NamespaceID: fmt.Sprintf("ns-%d", i),
- MountAccessor: fmt.Sprintf("ns-%d", i),
- NonEntity: i == 1,
- })
- }
- // 3 new clients have been seen 3 months ago
- err := a.handleEntitySegment(&activity.EntityActivityLog{Clients: moreSegmentClients}, segmentTime, hll, pqOptions{
- byNamespace: byNS,
- byMonth: byMonth,
- endTime: timeutil.EndOfMonth(segmentTime),
- activePeriodStart: addMonths(-12),
- activePeriodEnd: addMonths(12),
- })
- require.NoError(t, err)
- // there are only 3 namespaces, since both currentSegmentClients and moreSegmentClients use the same namespaces
- require.Len(t, byNS, 3)
- verifyByNamespaceContains(t, byNS, currentSegmentClients...)
- verifyByNamespaceContains(t, byNS, moreSegmentClients...)
- // The segment clients that have already been seen have their same first seen dates
- require.Equal(t, byMonth.firstSeen(t, currentSegmentClients[0]), seenBefore2Months)
- require.Equal(t, byMonth.firstSeen(t, currentSegmentClients[1]), seenBefore1Month)
- require.Equal(t, byMonth.firstSeen(t, currentSegmentClients[2]), seenBefore2Months)
- // and the new clients should be first seen at segmentTime
- require.Equal(t, byMonth.firstSeen(t, moreSegmentClients[0]), segmentTime)
- require.Equal(t, byMonth.firstSeen(t, moreSegmentClients[1]), segmentTime)
- require.Equal(t, byMonth.firstSeen(t, moreSegmentClients[2]), segmentTime)
- // the hyperloglog will have 3 elements, because there were the 3 new elements in moreSegmentClients seen
- require.Equal(t, hll.Estimate(), uint64(3))
- })
-}
-
-// TestActivityLog_breakdownTokenSegment verifies that tokens are correctly added to a map that tracks counts per namespace
-func TestActivityLog_breakdownTokenSegment(t *testing.T) {
- toAdd := map[string]uint64{
- "a": 1,
- "b": 2,
- "c": 3,
- }
- a := &ActivityLog{}
- testCases := []struct {
- name string
- existingNamespaceCounts map[string]uint64
- wantCounts map[string]uint64
- }{
- {
- name: "empty",
- wantCounts: toAdd,
- },
- {
- name: "some overlap",
- existingNamespaceCounts: map[string]uint64{
- "a": 2,
- "z": 1,
- },
- wantCounts: map[string]uint64{
- "a": 3,
- "b": 2,
- "c": 3,
- "z": 1,
- },
- },
- {
- name: "disjoint sets",
- existingNamespaceCounts: map[string]uint64{
- "z": 5,
- "y": 3,
- "x": 2,
- },
- wantCounts: map[string]uint64{
- "a": 1,
- "b": 2,
- "c": 3,
- "z": 5,
- "y": 3,
- "x": 2,
- },
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- byNamespace := make(map[string]*processByNamespace)
- for k, v := range tc.existingNamespaceCounts {
- byNamespace[k] = newByNamespace()
- byNamespace[k].Counts.Tokens = v
- }
- a.breakdownTokenSegment(&activity.TokenCount{CountByNamespaceID: toAdd}, byNamespace)
- got := make(map[string]uint64)
- for k, v := range byNamespace {
- got[k] = v.Counts.Tokens
- }
- require.Equal(t, tc.wantCounts, got)
- })
- }
-}
-
-// TestActivityLog_writePrecomputedQuery calls writePrecomputedQuery for a segment with 1 non entity and 1 entity client,
-// which have different namespaces and mounts. The precomputed query is then retrieved from storage and we verify that
-// the data structure is filled correctly
-func TestActivityLog_writePrecomputedQuery(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
-
- a := core.activityLog
- a.SetEnable(true)
-
- byMonth := make(summaryByMonth)
- byNS := make(summaryByNamespace)
- clientEntity := &activity.EntityRecord{
- ClientID: "id-1",
- NamespaceID: "ns-1",
- MountAccessor: "mnt-1",
- }
- clientNonEntity := &activity.EntityRecord{
- ClientID: "id-2",
- NamespaceID: "ns-2",
- MountAccessor: "mnt-2",
- NonEntity: true,
- }
- now := time.Now()
-
- // add the 2 clients to the namespace and month summaries
- processClientRecord(clientEntity, byNS, byMonth, now)
- processClientRecord(clientNonEntity, byNS, byMonth, now)
-
- endTime := timeutil.EndOfMonth(now)
- opts := pqOptions{
- byNamespace: byNS,
- byMonth: byMonth,
- endTime: endTime,
- }
-
- err := a.writePrecomputedQuery(context.Background(), now, opts)
- require.NoError(t, err)
-
- // read the query back from storage
- val, err := a.queryStore.Get(context.Background(), now, endTime)
- require.NoError(t, err)
- require.Equal(t, now.UTC().Unix(), val.StartTime.UTC().Unix())
- require.Equal(t, endTime.UTC().Unix(), val.EndTime.UTC().Unix())
-
- // ns-1 and ns-2 should both be present in the results
- require.Len(t, val.Namespaces, 2)
- require.Len(t, val.Months, 1)
- resultByNS := make(map[string]*activity.NamespaceRecord)
- for _, ns := range val.Namespaces {
- resultByNS[ns.NamespaceID] = ns
- }
- ns1 := resultByNS["ns-1"]
- ns2 := resultByNS["ns-2"]
-
- require.Equal(t, ns1.Entities, uint64(1))
- require.Equal(t, ns1.NonEntityTokens, uint64(0))
- require.Equal(t, ns2.Entities, uint64(0))
- require.Equal(t, ns2.NonEntityTokens, uint64(1))
-
- require.Len(t, ns1.Mounts, 1)
- require.Len(t, ns2.Mounts, 1)
- // ns-1 needs to have mnt-1
- require.Contains(t, ns1.Mounts[0].MountPath, "mnt-1")
- // ns-2 needs to have mnt-2
- require.Contains(t, ns2.Mounts[0].MountPath, "mnt-2")
-
- require.Equal(t, 1, ns1.Mounts[0].Counts.EntityClients)
- require.Equal(t, 0, ns1.Mounts[0].Counts.NonEntityClients)
- require.Equal(t, 0, ns2.Mounts[0].Counts.EntityClients)
- require.Equal(t, 1, ns2.Mounts[0].Counts.NonEntityClients)
-
- monthRecord := val.Months[0]
- // there should only be one month present, since the clients were added with the same timestamp
- require.Equal(t, monthRecord.Timestamp, timeutil.StartOfMonth(now).UTC().Unix())
- require.Equal(t, 1, monthRecord.Counts.NonEntityClients)
- require.Equal(t, 1, monthRecord.Counts.EntityClients)
- require.Len(t, monthRecord.Namespaces, 2)
- require.Len(t, monthRecord.NewClients.Namespaces, 2)
- require.Equal(t, 1, monthRecord.NewClients.Counts.EntityClients)
- require.Equal(t, 1, monthRecord.NewClients.Counts.NonEntityClients)
-}
-
-type mockTimeNowClock struct {
- timeutil.DefaultClock
- start time.Time
- created time.Time
-}
-
-func newMockTimeNowClock(startAt time.Time) timeutil.Clock {
- return &mockTimeNowClock{start: startAt, created: time.Now()}
-}
-
-// NewTimer returns a timer with a channel that will return the correct time,
-// relative to the starting time. This is used when testing the
-// activeFragmentWorker, as that function uses the returned value from timer.C
-// to perform additional functionality
-func (m mockTimeNowClock) NewTimer(d time.Duration) *time.Timer {
- timerStarted := m.Now()
- t := time.NewTimer(d)
- readCh := t.C
- writeCh := make(chan time.Time, 1)
- go func() {
- <-readCh
- writeCh <- timerStarted.Add(d)
- }()
- t.C = writeCh
- return t
-}
-
-func (m mockTimeNowClock) Now() time.Time {
- return m.start.Add(time.Since(m.created))
-}
-
-// TestActivityLog_HandleEndOfMonth runs the activity log with a mock clock.
-// The current time is set to be 3 seconds before the end of a month. The test
-// verifies that the precomputedQueryWorker runs and writes precomputed queries
-// with the proper start and end times when the end of the month is triggered
-func TestActivityLog_HandleEndOfMonth(t *testing.T) {
- // 3 seconds until a new month
- now := time.Date(2021, 1, 31, 23, 59, 57, 0, time.UTC)
- core, _, _ := TestCoreUnsealedWithConfig(t, &CoreConfig{ActivityLogConfig: ActivityLogCoreConfig{Clock: newMockTimeNowClock(now)}})
- done := make(chan struct{})
- go func() {
- defer close(done)
- <-core.activityLog.precomputedQueryWritten
- }()
- core.activityLog.SetEnable(true)
- core.activityLog.SetStartTimestamp(now.Unix())
- core.activityLog.AddClientToFragment("id", "ns", now.Unix(), false, "mount")
-
- // wait for the end of month to be triggered
- select {
- case <-done:
- case <-time.After(10 * time.Second):
- t.Fatal("timeout waiting for precomputed query")
- }
-
- // verify that a precomputed query was written
- exists, err := core.activityLog.queryStore.QueriesAvailable(context.Background())
- require.NoError(t, err)
- require.True(t, exists)
-
- // verify that the timestamp is correct
- pq, err := core.activityLog.queryStore.Get(context.Background(), now, now.Add(24*time.Hour))
- require.NoError(t, err)
- require.Equal(t, now, pq.StartTime)
- require.Equal(t, timeutil.EndOfMonth(now), pq.EndTime)
-}
diff --git a/vault/activity_log_testing_util.go b/vault/activity_log_testing_util.go
deleted file mode 100644
index 3c1c27c3d..000000000
--- a/vault/activity_log_testing_util.go
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "fmt"
- "math/rand"
- "testing"
-
- "github.com/hashicorp/vault/helper/constants"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault/activity"
-)
-
-// InjectActivityLogDataThisMonth populates the in-memory client store
-// with some entities and tokens, overriding what was already there
-// It is currently used for API integration tests
-func (c *Core) InjectActivityLogDataThisMonth(t *testing.T) map[string]*activity.EntityRecord {
- t.Helper()
-
- c.activityLog.l.Lock()
- defer c.activityLog.l.Unlock()
- c.activityLog.fragmentLock.Lock()
- defer c.activityLog.fragmentLock.Unlock()
-
- for i := 0; i < 3; i++ {
- er := &activity.EntityRecord{
- ClientID: fmt.Sprintf("testclientid-%d", i),
- NamespaceID: "root",
- MountAccessor: fmt.Sprintf("testmountaccessor-%d", i),
- Timestamp: c.activityLog.clock.Now().Unix(),
- NonEntity: i%2 == 0,
- }
- c.activityLog.partialMonthClientTracker[er.ClientID] = er
- }
-
- if constants.IsEnterprise {
- for j := 0; j < 2; j++ {
- for i := 0; i < 2; i++ {
- er := &activity.EntityRecord{
- ClientID: fmt.Sprintf("ns-%d-testclientid-%d", j, i),
- NamespaceID: fmt.Sprintf("ns-%d", j),
- MountAccessor: fmt.Sprintf("ns-%d-testmountaccessor-%d", j, i),
- Timestamp: c.activityLog.clock.Now().Unix(),
- NonEntity: i%2 == 0,
- }
- c.activityLog.partialMonthClientTracker[er.ClientID] = er
- }
- }
- }
-
- return c.activityLog.partialMonthClientTracker
-}
-
-// GetActiveClients returns the in-memory partialMonthClientTracker from an
-// activity log.
-func (c *Core) GetActiveClients() map[string]*activity.EntityRecord {
- out := make(map[string]*activity.EntityRecord)
-
- c.stateLock.RLock()
- c.activityLog.fragmentLock.RLock()
- for k, v := range c.activityLog.partialMonthClientTracker {
- out[k] = v
- }
- c.activityLog.fragmentLock.RUnlock()
- c.stateLock.RUnlock()
-
- return out
-}
-
-// GetCurrentEntities returns the current entity activity log
-func (a *ActivityLog) GetCurrentEntities() *activity.EntityActivityLog {
- a.l.RLock()
- defer a.l.RUnlock()
- return a.currentSegment.currentClients
-}
-
-// WriteToStorage is used to put entity data in storage
-// `path` should be the complete path (not relative to the view)
-func WriteToStorage(t *testing.T, c *Core, path string, data []byte) {
- t.Helper()
- err := c.barrier.Put(context.Background(), &logical.StorageEntry{
- Key: path,
- Value: data,
- })
- if err != nil {
- t.Fatalf("Failed to write %s\nto %s\nerror: %v", data, path, err)
- }
-}
-
-// SetStandbyEnable sets enabled on a performance standby (using config)
-func (a *ActivityLog) SetStandbyEnable(ctx context.Context, enabled bool) {
- var enableStr string
- if enabled {
- enableStr = "enable"
- } else {
- enableStr = "disable"
- }
-
- // TODO only patch enabled?
- a.SetConfigStandby(ctx, activityConfig{
- DefaultReportMonths: 12,
- RetentionMonths: ActivityLogMinimumRetentionMonths,
- Enabled: enableStr,
- })
-}
-
-// NOTE: AddTokenToFragment is deprecated and can no longer be used, except for
-// testing backward compatibility. Please use AddClientToFragment instead.
-func (a *ActivityLog) AddTokenToFragment(namespaceID string) {
- a.fragmentLock.Lock()
- defer a.fragmentLock.Unlock()
-
- if !a.enabled {
- return
- }
-
- a.createCurrentFragment()
-
- a.fragment.NonEntityTokens[namespaceID] += 1
-}
-
-func RandStringBytes(n int) string {
- b := make([]byte, n)
- for i := range b {
- b[i] = byte(rand.Intn(26)) + 'a'
- }
- return string(b)
-}
-
-// ExpectCurrentSegmentRefreshed verifies that the current segment has been refreshed
-// non-nil empty components and updated with the `expectedStart` timestamp
-// Note: if `verifyTimeNotZero` is true, ignore `expectedStart` and just make sure the timestamp isn't 0
-func (a *ActivityLog) ExpectCurrentSegmentRefreshed(t *testing.T, expectedStart int64, verifyTimeNotZero bool) {
- t.Helper()
-
- a.l.RLock()
- defer a.l.RUnlock()
- a.fragmentLock.RLock()
- defer a.fragmentLock.RUnlock()
- if a.currentSegment.currentClients == nil {
- t.Fatalf("expected non-nil currentSegment.currentClients")
- }
- if a.currentSegment.currentClients.Clients == nil {
- t.Errorf("expected non-nil currentSegment.currentClients.Entities")
- }
- if a.currentSegment.tokenCount == nil {
- t.Fatalf("expected non-nil currentSegment.tokenCount")
- }
- if a.currentSegment.tokenCount.CountByNamespaceID == nil {
- t.Errorf("expected non-nil currentSegment.tokenCount.CountByNamespaceID")
- }
- if a.partialMonthClientTracker == nil {
- t.Errorf("expected non-nil partialMonthClientTracker")
- }
- if len(a.currentSegment.currentClients.Clients) > 0 {
- t.Errorf("expected no current entity segment to be loaded. got: %v", a.currentSegment.currentClients)
- }
- if len(a.currentSegment.tokenCount.CountByNamespaceID) > 0 {
- t.Errorf("expected no token counts to be loaded. got: %v", a.currentSegment.tokenCount.CountByNamespaceID)
- }
- if len(a.partialMonthClientTracker) > 0 {
- t.Errorf("expected no active entity segment to be loaded. got: %v", a.partialMonthClientTracker)
- }
-
- if verifyTimeNotZero {
- if a.currentSegment.startTimestamp == 0 {
- t.Error("bad start timestamp. expected no reset but timestamp was reset")
- }
- } else if a.currentSegment.startTimestamp != expectedStart {
- t.Errorf("bad start timestamp. expected: %v got: %v", expectedStart, a.currentSegment.startTimestamp)
- }
-}
-
-// ActiveEntitiesEqual checks that only the set of `test` exists in `active`
-func ActiveEntitiesEqual(active map[string]*activity.EntityRecord, test []*activity.EntityRecord) bool {
- if len(active) != len(test) {
- return false
- }
-
- for _, ent := range test {
- if _, ok := active[ent.ClientID]; !ok {
- return false
- }
- }
-
- return true
-}
-
-// GetStartTimestamp returns the start timestamp on an activity log
-func (a *ActivityLog) GetStartTimestamp() int64 {
- a.l.RLock()
- defer a.l.RUnlock()
- return a.currentSegment.startTimestamp
-}
-
-// SetStartTimestamp sets the start timestamp on an activity log
-func (a *ActivityLog) SetStartTimestamp(timestamp int64) {
- a.l.Lock()
- defer a.l.Unlock()
- a.currentSegment.startTimestamp = timestamp
-}
-
-// GetStoredTokenCountByNamespaceID returns the count of tokens by namespace ID
-func (a *ActivityLog) GetStoredTokenCountByNamespaceID() map[string]uint64 {
- a.l.RLock()
- defer a.l.RUnlock()
- return a.currentSegment.tokenCount.CountByNamespaceID
-}
-
-// GetEntitySequenceNumber returns the current entity sequence number
-func (a *ActivityLog) GetEntitySequenceNumber() uint64 {
- a.l.RLock()
- defer a.l.RUnlock()
- return a.currentSegment.clientSequenceNumber
-}
-
-// SetEnable sets the enabled flag on the activity log
-func (a *ActivityLog) SetEnable(enabled bool) {
- a.l.Lock()
- defer a.l.Unlock()
- a.fragmentLock.Lock()
- defer a.fragmentLock.Unlock()
- a.enabled = enabled
-}
-
-// GetEnabled returns the enabled flag on an activity log
-func (a *ActivityLog) GetEnabled() bool {
- a.fragmentLock.RLock()
- defer a.fragmentLock.RUnlock()
- return a.enabled
-}
-
-// GetActivityLog returns a pointer to the (private) activity log on a core
-// Note: you must do the usual locking scheme when modifying the ActivityLog
-func (c *Core) GetActivityLog() *ActivityLog {
- return c.activityLog
-}
diff --git a/vault/activity_log_util_common_test.go b/vault/activity_log_util_common_test.go
deleted file mode 100644
index f8d975097..000000000
--- a/vault/activity_log_util_common_test.go
+++ /dev/null
@@ -1,343 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "errors"
- "fmt"
- "io"
- "testing"
- "time"
-
- "github.com/axiomhq/hyperloglog"
- "github.com/hashicorp/vault/helper/timeutil"
- "github.com/hashicorp/vault/vault/activity"
- "github.com/stretchr/testify/require"
- "google.golang.org/protobuf/proto"
-)
-
-// Test_ActivityLog_ComputeCurrentMonthForBillingPeriodInternal creates 3 months of hyperloglogs and fills them with
-// overlapping clients. The test calls computeCurrentMonthForBillingPeriodInternal with the current month map having
-// some overlap with the previous months. The test then verifies that the results have the correct number of entity and
-// non-entity clients. The test also calls computeCurrentMonthForBillingPeriodInternal with an empty current month map,
-// and verifies that the results are all 0.
-func Test_ActivityLog_ComputeCurrentMonthForBillingPeriodInternal(t *testing.T) {
- // populate the first month with clients 1-10
- monthOneHLL := hyperloglog.New()
- // populate the second month with clients 5-15
- monthTwoHLL := hyperloglog.New()
- // populate the third month with clients 10-20
- monthThreeHLL := hyperloglog.New()
-
- for i := 0; i < 20; i++ {
- clientID := []byte(fmt.Sprintf("client_%d", i))
- if i < 10 {
- monthOneHLL.Insert(clientID)
- }
- if 5 <= i && i < 15 {
- monthTwoHLL.Insert(clientID)
- }
- if 10 <= i && i < 20 {
- monthThreeHLL.Insert(clientID)
- }
- }
- mockHLLGetFunc := func(ctx context.Context, startTime time.Time) (*hyperloglog.Sketch, error) {
- currMonthStart := timeutil.StartOfMonth(time.Now())
- if startTime.Equal(timeutil.MonthsPreviousTo(3, currMonthStart)) {
- return monthThreeHLL, nil
- }
- if startTime.Equal(timeutil.MonthsPreviousTo(2, currMonthStart)) {
- return monthTwoHLL, nil
- }
- if startTime.Equal(timeutil.MonthsPreviousTo(1, currMonthStart)) {
- return monthOneHLL, nil
- }
- return nil, fmt.Errorf("bad start time")
- }
-
- // Let's add 2 entities exclusive to month 1 (clients 0,1),
- // 2 entities shared by month 1 and 2 (clients 5,6),
- // 2 entities shared by month 2 and 3 (clients 10,11), and
- // 2 entities exclusive to month 3 (15,16). Furthermore, we can add
- // 3 new entities (clients 20,21, and 22).
- entitiesStruct := make(map[string]struct{}, 0)
- entitiesStruct["client_0"] = struct{}{}
- entitiesStruct["client_1"] = struct{}{}
- entitiesStruct["client_5"] = struct{}{}
- entitiesStruct["client_6"] = struct{}{}
- entitiesStruct["client_10"] = struct{}{}
- entitiesStruct["client_11"] = struct{}{}
- entitiesStruct["client_15"] = struct{}{}
- entitiesStruct["client_16"] = struct{}{}
- entitiesStruct["client_20"] = struct{}{}
- entitiesStruct["client_21"] = struct{}{}
- entitiesStruct["client_22"] = struct{}{}
-
- // We will add 3 nonentity clients from month 1 (clients 2,3,4),
- // 3 shared by months 1 and 2 (7,8,9),
- // 3 shared by months 2 and 3 (12,13,14), and
- // 3 exclusive to month 3 (17,18,19). We will also
- // add 4 new nonentity clients.
- nonEntitiesStruct := make(map[string]struct{}, 0)
- nonEntitiesStruct["client_2"] = struct{}{}
- nonEntitiesStruct["client_3"] = struct{}{}
- nonEntitiesStruct["client_4"] = struct{}{}
- nonEntitiesStruct["client_7"] = struct{}{}
- nonEntitiesStruct["client_8"] = struct{}{}
- nonEntitiesStruct["client_9"] = struct{}{}
- nonEntitiesStruct["client_12"] = struct{}{}
- nonEntitiesStruct["client_13"] = struct{}{}
- nonEntitiesStruct["client_14"] = struct{}{}
- nonEntitiesStruct["client_17"] = struct{}{}
- nonEntitiesStruct["client_18"] = struct{}{}
- nonEntitiesStruct["client_19"] = struct{}{}
- nonEntitiesStruct["client_23"] = struct{}{}
- nonEntitiesStruct["client_24"] = struct{}{}
- nonEntitiesStruct["client_25"] = struct{}{}
- nonEntitiesStruct["client_26"] = struct{}{}
-
- counts := &processCounts{
- Entities: entitiesStruct,
- NonEntities: nonEntitiesStruct,
- }
-
- currentMonthClientsMap := make(map[int64]*processMonth, 1)
- currentMonthClients := &processMonth{
- Counts: counts,
- NewClients: &processNewClients{Counts: counts},
- }
- // Technially I think currentMonthClientsMap should have the keys as
- // unix timestamps, but for the purposes of the unit test it doesn't
- // matter what the values actually are.
- currentMonthClientsMap[0] = currentMonthClients
-
- core, _, _ := TestCoreUnsealed(t)
- a := core.activityLog
-
- endTime := timeutil.StartOfMonth(time.Now())
- startTime := timeutil.MonthsPreviousTo(3, endTime)
-
- monthRecord, err := a.computeCurrentMonthForBillingPeriodInternal(context.Background(), currentMonthClientsMap, mockHLLGetFunc, startTime, endTime)
- if err != nil {
- t.Fatal(err)
- }
-
- // We should have 11 entity clients and 16 nonentity clients, and 3 new entity clients
- // and 4 new nonentity clients
- if monthRecord.Counts.EntityClients != 11 {
- t.Fatalf("wrong number of entity clients. Expected 11, got %d", monthRecord.Counts.EntityClients)
- }
- if monthRecord.Counts.NonEntityClients != 16 {
- t.Fatalf("wrong number of non entity clients. Expected 16, got %d", monthRecord.Counts.NonEntityClients)
- }
- if monthRecord.NewClients.Counts.EntityClients != 3 {
- t.Fatalf("wrong number of new entity clients. Expected 3, got %d", monthRecord.NewClients.Counts.EntityClients)
- }
- if monthRecord.NewClients.Counts.NonEntityClients != 4 {
- t.Fatalf("wrong number of new non entity clients. Expected 4, got %d", monthRecord.NewClients.Counts.NonEntityClients)
- }
-
- // Attempt to compute current month when no records exist
- endTime = time.Now().UTC()
- startTime = timeutil.StartOfMonth(endTime)
- emptyClientsMap := make(map[int64]*processMonth, 0)
- monthRecord, err = a.computeCurrentMonthForBillingPeriodInternal(context.Background(), emptyClientsMap, mockHLLGetFunc, startTime, endTime)
- if err != nil {
- t.Fatalf("failed to compute empty current month, err: %v", err)
- }
-
- // We should have 0 entity clients, nonentity clients,new entity clients
- // and new nonentity clients
- if monthRecord.Counts.EntityClients != 0 {
- t.Fatalf("wrong number of entity clients. Expected 0, got %d", monthRecord.Counts.EntityClients)
- }
- if monthRecord.Counts.NonEntityClients != 0 {
- t.Fatalf("wrong number of non entity clients. Expected 0, got %d", monthRecord.Counts.NonEntityClients)
- }
- if monthRecord.NewClients.Counts.EntityClients != 0 {
- t.Fatalf("wrong number of new entity clients. Expected 0, got %d", monthRecord.NewClients.Counts.EntityClients)
- }
- if monthRecord.NewClients.Counts.NonEntityClients != 0 {
- t.Fatalf("wrong number of new non entity clients. Expected 0, got %d", monthRecord.NewClients.Counts.NonEntityClients)
- }
-}
-
-// writeEntitySegment writes a single segment file with the given time and index for an entity
-func writeEntitySegment(t *testing.T, core *Core, ts time.Time, index int, item *activity.EntityActivityLog) {
- t.Helper()
- protoItem, err := proto.Marshal(item)
- require.NoError(t, err)
- WriteToStorage(t, core, makeSegmentPath(t, activityEntityBasePath, ts, index), protoItem)
-}
-
-// writeTokenSegment writes a single segment file with the given time and index for a token
-func writeTokenSegment(t *testing.T, core *Core, ts time.Time, index int, item *activity.TokenCount) {
- t.Helper()
- protoItem, err := proto.Marshal(item)
- require.NoError(t, err)
- WriteToStorage(t, core, makeSegmentPath(t, activityTokenBasePath, ts, index), protoItem)
-}
-
-// makeSegmentPath formats the path for a segment at a particular time and index
-func makeSegmentPath(t *testing.T, typ string, ts time.Time, index int) string {
- t.Helper()
- return fmt.Sprintf("%s%s%d/%d", ActivityPrefix, typ, ts.Unix(), index)
-}
-
-// TestSegmentFileReader_BadData verifies that the reader returns errors when the data is unable to be parsed
-// However, the next time that Read*() is called, the reader should still progress and be able to then return any
-// valid data without errors
-func TestSegmentFileReader_BadData(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- now := time.Now()
-
- // write bad data that won't be able to be unmarshaled at index 0
- WriteToStorage(t, core, makeSegmentPath(t, activityTokenBasePath, now, 0), []byte("fake data"))
- WriteToStorage(t, core, makeSegmentPath(t, activityEntityBasePath, now, 0), []byte("fake data"))
-
- // write entity at index 1
- entity := &activity.EntityActivityLog{Clients: []*activity.EntityRecord{
- {
- ClientID: "id",
- },
- }}
- writeEntitySegment(t, core, now, 1, entity)
-
- // write token at index 1
- token := &activity.TokenCount{CountByNamespaceID: map[string]uint64{
- "ns": 1,
- }}
- writeTokenSegment(t, core, now, 1, token)
- reader, err := core.activityLog.NewSegmentFileReader(context.Background(), now)
- require.NoError(t, err)
-
- // first the bad entity is read, which returns an error
- _, err = reader.ReadEntity(context.Background())
- require.Error(t, err)
- // then, the reader can read the good entity at index 1
- gotEntity, err := reader.ReadEntity(context.Background())
- require.True(t, proto.Equal(gotEntity, entity))
- require.Nil(t, err)
-
- // the bad token causes an error
- _, err = reader.ReadToken(context.Background())
- require.Error(t, err)
- // but the good token is able to be read
- gotToken, err := reader.ReadToken(context.Background())
- require.True(t, proto.Equal(gotToken, token))
- require.Nil(t, err)
-}
-
-// TestSegmentFileReader_MissingData verifies that the segment file reader will skip over missing segment paths without
-// errorring until it is able to find a valid segment path
-func TestSegmentFileReader_MissingData(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- now := time.Now()
- // write entities and tokens at indexes 0, 1, 2
- for i := 0; i < 3; i++ {
- WriteToStorage(t, core, makeSegmentPath(t, activityTokenBasePath, now, i), []byte("fake data"))
- WriteToStorage(t, core, makeSegmentPath(t, activityEntityBasePath, now, i), []byte("fake data"))
-
- }
- // write entity at index 3
- entity := &activity.EntityActivityLog{Clients: []*activity.EntityRecord{
- {
- ClientID: "id",
- },
- }}
- writeEntitySegment(t, core, now, 3, entity)
- // write token at index 3
- token := &activity.TokenCount{CountByNamespaceID: map[string]uint64{
- "ns": 1,
- }}
- writeTokenSegment(t, core, now, 3, token)
- reader, err := core.activityLog.NewSegmentFileReader(context.Background(), now)
- require.NoError(t, err)
-
- // delete the indexes 0, 1, 2
- for i := 0; i < 3; i++ {
- require.NoError(t, core.barrier.Delete(context.Background(), makeSegmentPath(t, activityTokenBasePath, now, i)))
- require.NoError(t, core.barrier.Delete(context.Background(), makeSegmentPath(t, activityEntityBasePath, now, i)))
- }
-
- // we expect the reader to only return the data at index 3, and then be done
- gotEntity, err := reader.ReadEntity(context.Background())
- require.NoError(t, err)
- require.True(t, proto.Equal(gotEntity, entity))
- _, err = reader.ReadEntity(context.Background())
- require.Equal(t, err, io.EOF)
-
- gotToken, err := reader.ReadToken(context.Background())
- require.NoError(t, err)
- require.True(t, proto.Equal(gotToken, token))
- _, err = reader.ReadToken(context.Background())
- require.Equal(t, err, io.EOF)
-}
-
-// TestSegmentFileReader_NoData verifies that the reader return io.EOF when there is no data
-func TestSegmentFileReader_NoData(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- now := time.Now()
- reader, err := core.activityLog.NewSegmentFileReader(context.Background(), now)
- require.NoError(t, err)
- entity, err := reader.ReadEntity(context.Background())
- require.Nil(t, entity)
- require.Equal(t, err, io.EOF)
- token, err := reader.ReadToken(context.Background())
- require.Nil(t, token)
- require.Equal(t, err, io.EOF)
-}
-
-// TestSegmentFileReader verifies that the reader iterates through all segments paths in ascending order and returns
-// io.EOF when it's done
-func TestSegmentFileReader(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- now := time.Now()
- entities := make([]*activity.EntityActivityLog, 0, 3)
- tokens := make([]*activity.TokenCount, 0, 3)
-
- // write 3 entity segment pieces and 3 token segment pieces
- for i := 0; i < 3; i++ {
- entity := &activity.EntityActivityLog{Clients: []*activity.EntityRecord{
- {
- ClientID: fmt.Sprintf("id-%d", i),
- },
- }}
- token := &activity.TokenCount{CountByNamespaceID: map[string]uint64{
- fmt.Sprintf("ns-%d", i): uint64(i),
- }}
- writeEntitySegment(t, core, now, i, entity)
- writeTokenSegment(t, core, now, i, token)
- entities = append(entities, entity)
- tokens = append(tokens, token)
- }
-
- reader, err := core.activityLog.NewSegmentFileReader(context.Background(), now)
- require.NoError(t, err)
-
- gotEntities := make([]*activity.EntityActivityLog, 0, 3)
- gotTokens := make([]*activity.TokenCount, 0, 3)
-
- // read the entities from the reader
- for entity, err := reader.ReadEntity(context.Background()); !errors.Is(err, io.EOF); entity, err = reader.ReadEntity(context.Background()) {
- require.NoError(t, err)
- gotEntities = append(gotEntities, entity)
- }
-
- // read the tokens from the reader
- for token, err := reader.ReadToken(context.Background()); !errors.Is(err, io.EOF); token, err = reader.ReadToken(context.Background()) {
- require.NoError(t, err)
- gotTokens = append(gotTokens, token)
- }
- require.Len(t, gotEntities, 3)
- require.Len(t, gotTokens, 3)
-
- // verify that the entities and tokens we got from the reader are correct
- // we can't use require.Equals() here because there are protobuf differences in unexported fields
- for i := 0; i < 3; i++ {
- require.True(t, proto.Equal(gotEntities[i], entities[i]))
- require.True(t, proto.Equal(gotTokens[i], tokens[i]))
- }
-}
diff --git a/vault/audit_test.go b/vault/audit_test.go
deleted file mode 100644
index 573dceaf3..000000000
--- a/vault/audit_test.go
+++ /dev/null
@@ -1,615 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "errors"
- "fmt"
- "reflect"
- "strings"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/helper/testhelpers/corehelpers"
-
- "github.com/hashicorp/errwrap"
- log "github.com/hashicorp/go-hclog"
- uuid "github.com/hashicorp/go-uuid"
- "github.com/hashicorp/vault/audit"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/helper/jsonutil"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/mitchellh/copystructure"
-)
-
-func TestAudit_ReadOnlyViewDuringMount(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
- err := config.SaltView.Put(ctx, &logical.StorageEntry{
- Key: "bar",
- Value: []byte("baz"),
- })
- if err == nil || !strings.Contains(err.Error(), logical.ErrSetupReadOnly.Error()) {
- t.Fatalf("expected a read-only error")
- }
- factory := corehelpers.NoopAuditFactory(nil)
- return factory(ctx, config)
- }
-
- me := &MountEntry{
- Table: auditTableType,
- Path: "foo",
- Type: "noop",
- }
- err := c.enableAudit(namespace.RootContext(nil), me, true)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestCore_EnableAudit(t *testing.T) {
- c, keys, _ := TestCoreUnsealed(t)
- c.auditBackends["noop"] = corehelpers.NoopAuditFactory(nil)
-
- me := &MountEntry{
- Table: auditTableType,
- Path: "foo",
- Type: "noop",
- }
- err := c.enableAudit(namespace.RootContext(nil), me, true)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if !c.auditBroker.IsRegistered("foo/") {
- t.Fatalf("missing audit backend")
- }
-
- conf := &CoreConfig{
- Physical: c.physical,
- AuditBackends: make(map[string]audit.Factory),
- DisableMlock: true,
- }
- conf.AuditBackends["noop"] = corehelpers.NoopAuditFactory(nil)
- c2, err := NewCore(conf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer c2.Shutdown()
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c2, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("should be unsealed")
- }
- }
-
- // Verify matching audit tables
- if !reflect.DeepEqual(c.audit, c2.audit) {
- t.Fatalf("mismatch: %v %v", c.audit, c2.audit)
- }
-
- // Check for registration
- if !c2.auditBroker.IsRegistered("foo/") {
- t.Fatalf("missing audit backend")
- }
-}
-
-func TestCore_EnableAudit_MixedFailures(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- c.auditBackends["noop"] = corehelpers.NoopAuditFactory(nil)
- c.auditBackends["fail"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
- return nil, fmt.Errorf("failing enabling")
- }
-
- c.audit = &MountTable{
- Type: auditTableType,
- Entries: []*MountEntry{
- {
- Table: auditTableType,
- Path: "noop/",
- Type: "noop",
- UUID: "abcd",
- },
- {
- Table: auditTableType,
- Path: "noop2/",
- Type: "noop",
- UUID: "bcde",
- },
- },
- }
-
- // Both should set up successfully
- err := c.setupAudits(context.Background())
- if err != nil {
- t.Fatal(err)
- }
-
- // We expect this to work because the other entry is still valid
- c.audit.Entries[0].Type = "fail"
- err = c.setupAudits(context.Background())
- if err != nil {
- t.Fatal(err)
- }
-
- // No audit backend set up successfully, so expect error
- c.audit.Entries[1].Type = "fail"
- err = c.setupAudits(context.Background())
- if err == nil {
- t.Fatal("expected error")
- }
-}
-
-// Test that the local table actually gets populated as expected with local
-// entries, and that upon reading the entries from both are recombined
-// correctly
-func TestCore_EnableAudit_Local(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- c.auditBackends["noop"] = corehelpers.NoopAuditFactory(nil)
- c.auditBackends["fail"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
- return nil, fmt.Errorf("failing enabling")
- }
-
- c.audit = &MountTable{
- Type: auditTableType,
- Entries: []*MountEntry{
- {
- Table: auditTableType,
- Path: "noop/",
- Type: "noop",
- UUID: "abcd",
- Accessor: "noop-abcd",
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- },
- {
- Table: auditTableType,
- Path: "noop2/",
- Type: "noop",
- UUID: "bcde",
- Accessor: "noop-bcde",
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- },
- },
- }
-
- // Both should set up successfully
- err := c.setupAudits(context.Background())
- if err != nil {
- t.Fatal(err)
- }
-
- rawLocal, err := c.barrier.Get(context.Background(), coreLocalAuditConfigPath)
- if err != nil {
- t.Fatal(err)
- }
- if rawLocal == nil {
- t.Fatal("expected non-nil local audit")
- }
- localAuditTable := &MountTable{}
- if err := jsonutil.DecodeJSON(rawLocal.Value, localAuditTable); err != nil {
- t.Fatal(err)
- }
- if len(localAuditTable.Entries) > 0 {
- t.Fatalf("expected no entries in local audit table, got %#v", localAuditTable)
- }
-
- c.audit.Entries[1].Local = true
- if err := c.persistAudit(context.Background(), c.audit, false); err != nil {
- t.Fatal(err)
- }
-
- rawLocal, err = c.barrier.Get(context.Background(), coreLocalAuditConfigPath)
- if err != nil {
- t.Fatal(err)
- }
- if rawLocal == nil {
- t.Fatal("expected non-nil local audit")
- }
- localAuditTable = &MountTable{}
- if err := jsonutil.DecodeJSON(rawLocal.Value, localAuditTable); err != nil {
- t.Fatal(err)
- }
- if len(localAuditTable.Entries) != 1 {
- t.Fatalf("expected one entry in local audit table, got %#v", localAuditTable)
- }
-
- oldAudit := c.audit
- if err := c.loadAudits(context.Background()); err != nil {
- t.Fatal(err)
- }
-
- if !reflect.DeepEqual(oldAudit, c.audit) {
- t.Fatalf("expected\n%#v\ngot\n%#v\n", oldAudit, c.audit)
- }
-
- if len(c.audit.Entries) != 2 {
- t.Fatalf("expected two audit entries, got %#v", localAuditTable)
- }
-}
-
-func TestCore_DisableAudit(t *testing.T) {
- c, keys, _ := TestCoreUnsealed(t)
- c.auditBackends["noop"] = corehelpers.NoopAuditFactory(nil)
-
- existed, err := c.disableAudit(namespace.RootContext(nil), "foo", true)
- if existed && err != nil {
- t.Fatalf("existed: %v; err: %v", existed, err)
- }
-
- me := &MountEntry{
- Table: auditTableType,
- Path: "foo",
- Type: "noop",
- }
- err = c.enableAudit(namespace.RootContext(nil), me, true)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- existed, err = c.disableAudit(namespace.RootContext(nil), "foo", true)
- if !existed || err != nil {
- t.Fatalf("existed: %v; err: %v", existed, err)
- }
-
- // Check for registration
- if c.auditBroker.IsRegistered("foo") {
- t.Fatalf("audit backend present")
- }
-
- conf := &CoreConfig{
- Physical: c.physical,
- DisableMlock: true,
- }
- c2, err := NewCore(conf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer c2.Shutdown()
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c2, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("should be unsealed")
- }
- }
-
- // Verify matching mount tables
- if !reflect.DeepEqual(c.audit, c2.audit) {
- t.Fatalf("mismatch:\n%#v\n%#v", c.audit, c2.audit)
- }
-}
-
-func TestCore_DefaultAuditTable(t *testing.T) {
- c, keys, _ := TestCoreUnsealed(t)
- verifyDefaultAuditTable(t, c.audit)
-
- // Verify we have an audit broker
- if c.auditBroker == nil {
- t.Fatalf("missing audit broker")
- }
-
- // Start a second core with same physical
- conf := &CoreConfig{
- Physical: c.physical,
- DisableMlock: true,
- }
- c2, err := NewCore(conf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer c2.Shutdown()
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c2, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("should be unsealed")
- }
- }
-
- // Verify matching mount tables
- if !reflect.DeepEqual(c.audit, c2.audit) {
- t.Fatalf("mismatch: %v %v", c.audit, c2.audit)
- }
-}
-
-func TestDefaultAuditTable(t *testing.T) {
- table := defaultAuditTable()
- verifyDefaultAuditTable(t, table)
-}
-
-func verifyDefaultAuditTable(t *testing.T, table *MountTable) {
- if len(table.Entries) != 0 {
- t.Fatalf("bad: %v", table.Entries)
- }
- if table.Type != auditTableType {
- t.Fatalf("bad: %v", *table)
- }
-}
-
-func TestAuditBroker_LogRequest(t *testing.T) {
- l := logging.NewVaultLogger(log.Trace)
- b := NewAuditBroker(l)
- a1 := corehelpers.TestNoopAudit(t, nil)
- a2 := corehelpers.TestNoopAudit(t, nil)
- b.Register("foo", a1, nil, false)
- b.Register("bar", a2, nil, false)
-
- auth := &logical.Auth{
- ClientToken: "foo",
- Policies: []string{"dev", "ops"},
- Metadata: map[string]string{
- "user": "armon",
- "source": "github",
- },
- }
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "sys/mounts",
- }
-
- // Copy so we can verify nothing changed
- authCopyRaw, err := copystructure.Copy(auth)
- if err != nil {
- t.Fatal(err)
- }
- authCopy := authCopyRaw.(*logical.Auth)
-
- reqCopyRaw, err := copystructure.Copy(req)
- if err != nil {
- t.Fatal(err)
- }
- reqCopy := reqCopyRaw.(*logical.Request)
-
- // Create an identifier for the request to verify against
- req.ID, err = uuid.GenerateUUID()
- if err != nil {
- t.Fatalf("failed to generate identifier for the request: path%s err: %v", req.Path, err)
- }
- reqCopy.ID = req.ID
-
- reqErrs := errors.New("errs")
-
- headersConf := &AuditedHeadersConfig{
- Headers: make(map[string]*auditedHeaderSettings),
- }
-
- logInput := &logical.LogInput{
- Auth: authCopy,
- Request: reqCopy,
- OuterErr: reqErrs,
- }
- ctx := namespace.RootContext(context.Background())
- err = b.LogRequest(ctx, logInput, headersConf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- for _, a := range []*corehelpers.NoopAudit{a1, a2} {
- if !reflect.DeepEqual(a.ReqAuth[0], auth) {
- t.Fatalf("Bad: %#v", a.ReqAuth[0])
- }
- if !reflect.DeepEqual(a.Req[0], req) {
- t.Fatalf("Bad: %#v\n wanted %#v", a.Req[0], req)
- }
- if !reflect.DeepEqual(a.ReqErrs[0], reqErrs) {
- t.Fatalf("Bad: %#v", a.ReqErrs[0])
- }
- }
-
- // Should still work with one failing backend
- a1.ReqErr = fmt.Errorf("failed")
- logInput = &logical.LogInput{
- Auth: auth,
- Request: req,
- }
- if err := b.LogRequest(ctx, logInput, headersConf); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should FAIL work with both failing backends
- a2.ReqErr = fmt.Errorf("failed")
- if err := b.LogRequest(ctx, logInput, headersConf); !errwrap.Contains(err, "no audit backend succeeded in logging the request") {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestAuditBroker_LogResponse(t *testing.T) {
- l := logging.NewVaultLogger(log.Trace)
- b := NewAuditBroker(l)
- a1 := corehelpers.TestNoopAudit(t, nil)
- a2 := corehelpers.TestNoopAudit(t, nil)
- b.Register("foo", a1, nil, false)
- b.Register("bar", a2, nil, false)
-
- auth := &logical.Auth{
- NumUses: 10,
- ClientToken: "foo",
- Policies: []string{"dev", "ops"},
- Metadata: map[string]string{
- "user": "armon",
- "source": "github",
- },
- }
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "sys/mounts",
- }
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: 1 * time.Hour,
- },
- },
- Data: map[string]interface{}{
- "user": "root",
- "password": "password",
- },
- }
- respErr := fmt.Errorf("permission denied")
-
- // Copy so we can verify nothing changed
- authCopyRaw, err := copystructure.Copy(auth)
- if err != nil {
- t.Fatal(err)
- }
- authCopy := authCopyRaw.(*logical.Auth)
-
- reqCopyRaw, err := copystructure.Copy(req)
- if err != nil {
- t.Fatal(err)
- }
- reqCopy := reqCopyRaw.(*logical.Request)
-
- respCopyRaw, err := copystructure.Copy(resp)
- if err != nil {
- t.Fatal(err)
- }
- respCopy := respCopyRaw.(*logical.Response)
-
- headersConf := &AuditedHeadersConfig{
- Headers: make(map[string]*auditedHeaderSettings),
- }
-
- logInput := &logical.LogInput{
- Auth: authCopy,
- Request: reqCopy,
- Response: respCopy,
- OuterErr: respErr,
- }
- ctx := namespace.RootContext(context.Background())
- err = b.LogResponse(ctx, logInput, headersConf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- for _, a := range []*corehelpers.NoopAudit{a1, a2} {
- if !reflect.DeepEqual(a.RespAuth[0], auth) {
- t.Fatalf("Bad: %#v", a.ReqAuth[0])
- }
- if !reflect.DeepEqual(a.RespReq[0], req) {
- t.Fatalf("Bad: %#v", a.Req[0])
- }
- if !reflect.DeepEqual(a.Resp[0], resp) {
- t.Fatalf("Bad: %#v", a.Resp[0])
- }
- if !reflect.DeepEqual(a.RespErrs[0], respErr) {
- t.Fatalf("Expected\n%v\nGot\n%#v", respErr, a.RespErrs[0])
- }
- }
-
- // Should still work with one failing backend
- a1.RespErr = fmt.Errorf("failed")
- logInput = &logical.LogInput{
- Auth: auth,
- Request: req,
- Response: resp,
- OuterErr: respErr,
- }
- err = b.LogResponse(ctx, logInput, headersConf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should FAIL work with both failing backends
- a2.RespErr = fmt.Errorf("failed")
- err = b.LogResponse(ctx, logInput, headersConf)
- if !strings.Contains(err.Error(), "no audit backend succeeded in logging the response") {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestAuditBroker_AuditHeaders(t *testing.T) {
- logger := logging.NewVaultLogger(log.Trace)
- b := NewAuditBroker(logger)
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "headers/")
- a1 := corehelpers.TestNoopAudit(t, nil)
- a2 := corehelpers.TestNoopAudit(t, nil)
- b.Register("foo", a1, nil, false)
- b.Register("bar", a2, nil, false)
-
- auth := &logical.Auth{
- ClientToken: "foo",
- Policies: []string{"dev", "ops"},
- Metadata: map[string]string{
- "user": "armon",
- "source": "github",
- },
- }
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "sys/mounts",
- Headers: map[string][]string{
- "X-Test-Header": {"foo"},
- "X-Vault-Header": {"bar"},
- "Content-Type": {"baz"},
- },
- }
- respErr := fmt.Errorf("permission denied")
-
- // Copy so we can verify nothing changed
- reqCopyRaw, err := copystructure.Copy(req)
- if err != nil {
- t.Fatal(err)
- }
- reqCopy := reqCopyRaw.(*logical.Request)
-
- headersConf := &AuditedHeadersConfig{
- view: view,
- }
- headersConf.add(context.Background(), "X-Test-Header", false)
- headersConf.add(context.Background(), "X-Vault-Header", false)
-
- logInput := &logical.LogInput{
- Auth: auth,
- Request: reqCopy,
- OuterErr: respErr,
- }
- ctx := namespace.RootContext(context.Background())
- err = b.LogRequest(ctx, logInput, headersConf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- expected := map[string][]string{
- "x-test-header": {"foo"},
- "x-vault-header": {"bar"},
- }
-
- for _, a := range []*corehelpers.NoopAudit{a1, a2} {
- if !reflect.DeepEqual(a.ReqHeaders[0], expected) {
- t.Fatalf("Bad audited headers: %#v", a.Req[0].Headers)
- }
- }
-
- // Should still work with one failing backend
- a1.ReqErr = fmt.Errorf("failed")
- logInput = &logical.LogInput{
- Auth: auth,
- Request: req,
- OuterErr: respErr,
- }
- err = b.LogRequest(ctx, logInput, headersConf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should FAIL work with both failing backends
- a2.ReqErr = fmt.Errorf("failed")
- err = b.LogRequest(ctx, logInput, headersConf)
- if !errwrap.Contains(err, "no audit backend succeeded in logging the request") {
- t.Fatalf("err: %v", err)
- }
-}
diff --git a/vault/audited_headers_test.go b/vault/audited_headers_test.go
deleted file mode 100644
index e9c3e9c17..000000000
--- a/vault/audited_headers_test.go
+++ /dev/null
@@ -1,241 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "reflect"
- "testing"
-
- "github.com/hashicorp/vault/sdk/helper/salt"
-)
-
-func mockAuditedHeadersConfig(t *testing.T) *AuditedHeadersConfig {
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "foo/")
- return &AuditedHeadersConfig{
- Headers: make(map[string]*auditedHeaderSettings),
- view: view,
- }
-}
-
-func TestAuditedHeadersConfig_CRUD(t *testing.T) {
- conf := mockAuditedHeadersConfig(t)
-
- testAuditedHeadersConfig_Add(t, conf)
- testAuditedHeadersConfig_Remove(t, conf)
-}
-
-func testAuditedHeadersConfig_Add(t *testing.T, conf *AuditedHeadersConfig) {
- err := conf.add(context.Background(), "X-Test-Header", false)
- if err != nil {
- t.Fatalf("Error when adding header to config: %s", err)
- }
-
- settings, ok := conf.Headers["x-test-header"]
- if !ok {
- t.Fatal("Expected header to be found in config")
- }
-
- if settings.HMAC {
- t.Fatal("Expected HMAC to be set to false, got true")
- }
-
- out, err := conf.view.Get(context.Background(), auditedHeadersEntry)
- if err != nil {
- t.Fatalf("Could not retrieve headers entry from config: %s", err)
- }
- if out == nil {
- t.Fatal("nil value")
- }
-
- headers := make(map[string]*auditedHeaderSettings)
- err = out.DecodeJSON(&headers)
- if err != nil {
- t.Fatalf("Error decoding header view: %s", err)
- }
-
- expected := map[string]*auditedHeaderSettings{
- "x-test-header": {
- HMAC: false,
- },
- }
-
- if !reflect.DeepEqual(headers, expected) {
- t.Fatalf("Expected config didn't match actual. Expected: %#v, Got: %#v", expected, headers)
- }
-
- err = conf.add(context.Background(), "X-Vault-Header", true)
- if err != nil {
- t.Fatalf("Error when adding header to config: %s", err)
- }
-
- settings, ok = conf.Headers["x-vault-header"]
- if !ok {
- t.Fatal("Expected header to be found in config")
- }
-
- if !settings.HMAC {
- t.Fatal("Expected HMAC to be set to true, got false")
- }
-
- out, err = conf.view.Get(context.Background(), auditedHeadersEntry)
- if err != nil {
- t.Fatalf("Could not retrieve headers entry from config: %s", err)
- }
- if out == nil {
- t.Fatal("nil value")
- }
-
- headers = make(map[string]*auditedHeaderSettings)
- err = out.DecodeJSON(&headers)
- if err != nil {
- t.Fatalf("Error decoding header view: %s", err)
- }
-
- expected["x-vault-header"] = &auditedHeaderSettings{
- HMAC: true,
- }
-
- if !reflect.DeepEqual(headers, expected) {
- t.Fatalf("Expected config didn't match actual. Expected: %#v, Got: %#v", expected, headers)
- }
-}
-
-func testAuditedHeadersConfig_Remove(t *testing.T, conf *AuditedHeadersConfig) {
- err := conf.remove(context.Background(), "X-Test-Header")
- if err != nil {
- t.Fatalf("Error when adding header to config: %s", err)
- }
-
- _, ok := conf.Headers["x-Test-HeAder"]
- if ok {
- t.Fatal("Expected header to not be found in config")
- }
-
- out, err := conf.view.Get(context.Background(), auditedHeadersEntry)
- if err != nil {
- t.Fatalf("Could not retrieve headers entry from config: %s", err)
- }
- if out == nil {
- t.Fatal("nil value")
- }
-
- headers := make(map[string]*auditedHeaderSettings)
- err = out.DecodeJSON(&headers)
- if err != nil {
- t.Fatalf("Error decoding header view: %s", err)
- }
-
- expected := map[string]*auditedHeaderSettings{
- "x-vault-header": {
- HMAC: true,
- },
- }
-
- if !reflect.DeepEqual(headers, expected) {
- t.Fatalf("Expected config didn't match actual. Expected: %#v, Got: %#v", expected, headers)
- }
-
- err = conf.remove(context.Background(), "x-VaulT-Header")
- if err != nil {
- t.Fatalf("Error when adding header to config: %s", err)
- }
-
- _, ok = conf.Headers["x-vault-header"]
- if ok {
- t.Fatal("Expected header to not be found in config")
- }
-
- out, err = conf.view.Get(context.Background(), auditedHeadersEntry)
- if err != nil {
- t.Fatalf("Could not retrieve headers entry from config: %s", err)
- }
- if out == nil {
- t.Fatal("nil value")
- }
-
- headers = make(map[string]*auditedHeaderSettings)
- err = out.DecodeJSON(&headers)
- if err != nil {
- t.Fatalf("Error decoding header view: %s", err)
- }
-
- expected = make(map[string]*auditedHeaderSettings)
-
- if !reflect.DeepEqual(headers, expected) {
- t.Fatalf("Expected config didn't match actual. Expected: %#v, Got: %#v", expected, headers)
- }
-}
-
-func TestAuditedHeadersConfig_ApplyConfig(t *testing.T) {
- conf := mockAuditedHeadersConfig(t)
-
- conf.add(context.Background(), "X-TesT-Header", false)
- conf.add(context.Background(), "X-Vault-HeAdEr", true)
-
- reqHeaders := map[string][]string{
- "X-Test-Header": {"foo"},
- "X-Vault-Header": {"bar", "bar"},
- "Content-Type": {"json"},
- }
-
- hashFunc := func(ctx context.Context, s string) (string, error) { return "hashed", nil }
-
- result, err := conf.ApplyConfig(context.Background(), reqHeaders, hashFunc)
- if err != nil {
- t.Fatal(err)
- }
-
- expected := map[string][]string{
- "x-test-header": {"foo"},
- "x-vault-header": {"hashed", "hashed"},
- }
-
- if !reflect.DeepEqual(result, expected) {
- t.Fatalf("Expected headers did not match actual: Expected %#v\n Got %#v\n", expected, result)
- }
-
- // Make sure we didn't edit the reqHeaders map
- reqHeadersCopy := map[string][]string{
- "X-Test-Header": {"foo"},
- "X-Vault-Header": {"bar", "bar"},
- "Content-Type": {"json"},
- }
-
- if !reflect.DeepEqual(reqHeaders, reqHeadersCopy) {
- t.Fatalf("Req headers were changed, expected %#v\n got %#v", reqHeadersCopy, reqHeaders)
- }
-}
-
-func BenchmarkAuditedHeaderConfig_ApplyConfig(b *testing.B) {
- conf := &AuditedHeadersConfig{
- Headers: make(map[string]*auditedHeaderSettings),
- view: nil,
- }
-
- conf.Headers = map[string]*auditedHeaderSettings{
- "X-Test-Header": {false},
- "X-Vault-Header": {true},
- }
-
- reqHeaders := map[string][]string{
- "X-Test-Header": {"foo"},
- "X-Vault-Header": {"bar", "bar"},
- "Content-Type": {"json"},
- }
-
- salter, err := salt.NewSalt(context.Background(), nil, nil)
- if err != nil {
- b.Fatal(err)
- }
-
- hashFunc := func(ctx context.Context, s string) (string, error) { return salter.GetIdentifiedHMAC(s), nil }
-
- // Reset the timer since we did a lot above
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- conf.ApplyConfig(context.Background(), reqHeaders, hashFunc)
- }
-}
diff --git a/vault/auth_test.go b/vault/auth_test.go
deleted file mode 100644
index d07c83c20..000000000
--- a/vault/auth_test.go
+++ /dev/null
@@ -1,842 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "reflect"
- "strings"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/helper/testhelpers/corehelpers"
-
- "github.com/armon/go-metrics"
- "github.com/hashicorp/vault/helper/metricsutil"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/helper/versions"
- "github.com/hashicorp/vault/sdk/helper/consts"
- "github.com/hashicorp/vault/sdk/helper/jsonutil"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestAuth_ReadOnlyViewDuringMount(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- c.credentialBackends["noop"] = func(ctx context.Context, config *logical.BackendConfig) (logical.Backend, error) {
- err := config.StorageView.Put(ctx, &logical.StorageEntry{
- Key: "bar",
- Value: []byte("baz"),
- })
- if err == nil || !strings.Contains(err.Error(), logical.ErrSetupReadOnly.Error()) {
- t.Fatalf("expected a read-only error")
- }
- return &NoopBackend{
- BackendType: logical.TypeCredential,
- }, nil
- }
-
- me := &MountEntry{
- Table: credentialTableType,
- Path: "foo",
- Type: "noop",
- }
- err := c.enableCredential(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestAuthMountMetrics(t *testing.T) {
- c, _, _, _ := TestCoreUnsealedWithMetrics(t)
- c.credentialBackends["noop"] = func(ctx context.Context, config *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{
- BackendType: logical.TypeCredential,
- }, nil
- }
- mountKeyName := "core.mount_table.num_entries.type|auth||local|false||"
- mountMetrics := &c.metricsHelper.LoopMetrics.Metrics
- loadMetric, ok := mountMetrics.Load(mountKeyName)
- var numEntriesMetric metricsutil.GaugeMetric = loadMetric.(metricsutil.GaugeMetric)
-
- // 1 default nonlocal auth backend
- if !ok || numEntriesMetric.Value != 1 {
- t.Fatalf("Auth values should be: %+v", numEntriesMetric)
- }
-
- me := &MountEntry{
- Table: credentialTableType,
- Path: "foo",
- Type: "noop",
- }
- err := c.enableCredential(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- mountMetrics = &c.metricsHelper.LoopMetrics.Metrics
- loadMetric, ok = mountMetrics.Load(mountKeyName)
- numEntriesMetric = loadMetric.(metricsutil.GaugeMetric)
- if !ok || numEntriesMetric.Value != 2 {
- t.Fatalf("mount metrics for num entries do not match true values")
- }
- if len(numEntriesMetric.Key) != 3 ||
- numEntriesMetric.Key[0] != "core" ||
- numEntriesMetric.Key[1] != "mount_table" ||
- numEntriesMetric.Key[2] != "num_entries" {
- t.Fatalf("mount metrics for num entries have wrong key")
- }
- if len(numEntriesMetric.Labels) != 2 ||
- numEntriesMetric.Labels[0].Name != "type" ||
- numEntriesMetric.Labels[0].Value != "auth" ||
- numEntriesMetric.Labels[1].Name != "local" ||
- numEntriesMetric.Labels[1].Value != "false" {
- t.Fatalf("mount metrics for num entries have wrong labels")
- }
- mountSizeKeyName := "core.mount_table.size.type|auth||local|false||"
- loadMetric, ok = mountMetrics.Load(mountSizeKeyName)
- sizeMetric := loadMetric.(metricsutil.GaugeMetric)
-
- if !ok {
- t.Fatalf("mount metrics for size do not match exist")
- }
- if len(sizeMetric.Key) != 3 ||
- sizeMetric.Key[0] != "core" ||
- sizeMetric.Key[1] != "mount_table" ||
- sizeMetric.Key[2] != "size" {
- t.Fatalf("mount metrics for size have wrong key")
- }
- if len(sizeMetric.Labels) != 2 ||
- sizeMetric.Labels[0].Name != "type" ||
- sizeMetric.Labels[0].Value != "auth" ||
- sizeMetric.Labels[1].Name != "local" ||
- sizeMetric.Labels[1].Value != "false" {
- t.Fatalf("mount metrics for size have wrong labels")
- }
-}
-
-func TestCore_DefaultAuthTable(t *testing.T) {
- c, keys, _ := TestCoreUnsealed(t)
- verifyDefaultAuthTable(t, c.auth)
-
- // Start a second core with same physical
- inmemSink := metrics.NewInmemSink(1000000*time.Hour, 2000000*time.Hour)
- conf := &CoreConfig{
- Physical: c.physical,
- DisableMlock: true,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- MetricSink: metricsutil.NewClusterMetricSink("test-cluster", inmemSink),
- MetricsHelper: metricsutil.NewMetricsHelper(inmemSink, false),
- }
- c2, err := NewCore(conf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer c2.Shutdown()
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c2, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("should be unsealed")
- }
- }
-
- // Verify matching mount tables
- if !reflect.DeepEqual(c.auth, c2.auth) {
- t.Fatalf("mismatch: %v %v", c.auth, c2.auth)
- }
-}
-
-func TestCore_BuiltinRegistry(t *testing.T) {
- conf := &CoreConfig{
- // set PluginDirectory and ensure that vault doesn't expect approle to
- // be there when we are mounting the builtin approle
- PluginDirectory: "/Users/foo",
-
- DisableMlock: true,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- }
- c, _, _ := TestCoreUnsealedWithConfig(t, conf)
-
- for _, me := range []*MountEntry{
- {
- Table: credentialTableType,
- Path: "approle/",
- Type: "approle",
- },
- {
- Table: credentialTableType,
- Path: "approle2/",
- Type: "approle",
- Version: versions.GetBuiltinVersion(consts.PluginTypeCredential, "approle"),
- },
- } {
- err := c.enableCredential(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- }
-}
-
-func TestCore_EnableCredential(t *testing.T) {
- c, keys, _ := TestCoreUnsealed(t)
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{
- BackendType: logical.TypeCredential,
- }, nil
- }
-
- me := &MountEntry{
- Table: credentialTableType,
- Path: "foo",
- Type: "noop",
- }
- err := c.enableCredential(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- match := c.router.MatchingMount(namespace.RootContext(nil), "auth/foo/bar")
- if match != "auth/foo/" {
- t.Fatalf("missing mount, match: %q", match)
- }
-
- inmemSink := metrics.NewInmemSink(1000000*time.Hour, 2000000*time.Hour)
- conf := &CoreConfig{
- Physical: c.physical,
- DisableMlock: true,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- MetricSink: metricsutil.NewClusterMetricSink("test-cluster", inmemSink),
- MetricsHelper: metricsutil.NewMetricsHelper(inmemSink, false),
- }
- c2, err := NewCore(conf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer c2.Shutdown()
- c2.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{
- BackendType: logical.TypeCredential,
- }, nil
- }
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c2, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("should be unsealed")
- }
- }
-
- // Verify matching auth tables
- if !reflect.DeepEqual(c.auth, c2.auth) {
- t.Fatalf("mismatch: %v %v", c.auth, c2.auth)
- }
-}
-
-// TestCore_EnableCredential_aws_ec2 tests that we can successfully mount aws
-// auth using the alias "aws-ec2"
-func TestCore_EnableCredential_aws_ec2(t *testing.T) {
- c, keys, _ := TestCoreUnsealed(t)
- c.credentialBackends["aws"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{
- BackendType: logical.TypeCredential,
- }, nil
- }
-
- me := &MountEntry{
- Table: credentialTableType,
- Path: "foo",
- Type: "aws-ec2",
- }
- err := c.enableCredential(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- match := c.router.MatchingMount(namespace.RootContext(nil), "auth/foo/bar")
- if match != "auth/foo/" {
- t.Fatalf("missing mount, match: %q", match)
- }
-
- inmemSink := metrics.NewInmemSink(1000000*time.Hour, 2000000*time.Hour)
- conf := &CoreConfig{
- Physical: c.physical,
- DisableMlock: true,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- MetricSink: metricsutil.NewClusterMetricSink("test-cluster", inmemSink),
- MetricsHelper: metricsutil.NewMetricsHelper(inmemSink, false),
- }
- c2, err := NewCore(conf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer c2.Shutdown()
- c2.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{
- BackendType: logical.TypeCredential,
- }, nil
- }
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c2, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("should be unsealed")
- }
- }
-
- // Verify matching auth tables
- if !reflect.DeepEqual(c.auth, c2.auth) {
- t.Fatalf("mismatch: %v %v", c.auth, c2.auth)
- }
-}
-
-// Test that the local table actually gets populated as expected with local
-// entries, and that upon reading the entries from both are recombined
-// correctly
-func TestCore_EnableCredential_Local(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{
- BackendType: logical.TypeCredential,
- }, nil
- }
-
- c.auth = &MountTable{
- Type: credentialTableType,
- Entries: []*MountEntry{
- {
- Table: credentialTableType,
- Path: "noop/",
- Type: "noop",
- UUID: "abcd",
- Accessor: "noop-abcd",
- BackendAwareUUID: "abcde",
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- },
- {
- Table: credentialTableType,
- Path: "noop2/",
- Type: "noop",
- UUID: "bcde",
- Accessor: "noop-bcde",
- BackendAwareUUID: "bcdea",
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- },
- },
- }
-
- // Both should set up successfully
- err := c.setupCredentials(context.Background())
- if err != nil {
- t.Fatal(err)
- }
-
- rawLocal, err := c.barrier.Get(context.Background(), coreLocalAuthConfigPath)
- if err != nil {
- t.Fatal(err)
- }
- if rawLocal == nil {
- t.Fatal("expected non-nil local credential")
- }
- localCredentialTable := &MountTable{}
- if err := jsonutil.DecodeJSON(rawLocal.Value, localCredentialTable); err != nil {
- t.Fatal(err)
- }
- if len(localCredentialTable.Entries) > 0 {
- t.Fatalf("expected no entries in local credential table, got %#v", localCredentialTable)
- }
-
- c.auth.Entries[1].Local = true
- if err := c.persistAuth(context.Background(), c.auth, nil); err != nil {
- t.Fatal(err)
- }
-
- rawLocal, err = c.barrier.Get(context.Background(), coreLocalAuthConfigPath)
- if err != nil {
- t.Fatal(err)
- }
- if rawLocal == nil {
- t.Fatal("expected non-nil local credential")
- }
- localCredentialTable = &MountTable{}
- if err := jsonutil.DecodeJSON(rawLocal.Value, localCredentialTable); err != nil {
- t.Fatal(err)
- }
- if len(localCredentialTable.Entries) != 1 {
- t.Fatalf("expected one entry in local credential table, got %#v", localCredentialTable)
- }
-
- oldCredential := c.auth
- if err := c.loadCredentials(context.Background()); err != nil {
- t.Fatal(err)
- }
-
- if !reflect.DeepEqual(oldCredential, c.auth) {
- t.Fatalf("expected\n%#v\ngot\n%#v\n", oldCredential, c.auth)
- }
-
- if len(c.auth.Entries) != 2 {
- t.Fatalf("expected two credential entries, got %#v", localCredentialTable)
- }
-}
-
-func TestCore_EnableCredential_twice_409(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{
- BackendType: logical.TypeCredential,
- }, nil
- }
-
- me := &MountEntry{
- Table: credentialTableType,
- Path: "foo",
- Type: "noop",
- }
- err := c.enableCredential(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // 2nd should be a 409 error
- err2 := c.enableCredential(namespace.RootContext(nil), me)
- switch err2.(type) {
- case logical.HTTPCodedError:
- if err2.(logical.HTTPCodedError).Code() != 409 {
- t.Fatalf("invalid code given")
- }
- default:
- t.Fatalf("expected a different error type")
- }
-}
-
-func TestCore_EnableCredential_Token(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- me := &MountEntry{
- Table: credentialTableType,
- Path: "foo",
- Type: "token",
- }
- err := c.enableCredential(namespace.RootContext(nil), me)
- if err.Error() != "token credential backend cannot be instantiated" {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestCore_DisableCredential(t *testing.T) {
- c, keys, _ := TestCoreUnsealed(t)
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{
- BackendType: logical.TypeCredential,
- }, nil
- }
-
- err := c.disableCredential(namespace.RootContext(nil), "foo")
- if err != nil && !strings.HasPrefix(err.Error(), "no matching mount") {
- t.Fatal(err)
- }
-
- me := &MountEntry{
- Table: credentialTableType,
- Path: "foo",
- Type: "noop",
- }
- err = c.enableCredential(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- err = c.disableCredential(namespace.RootContext(nil), "foo")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- match := c.router.MatchingMount(namespace.RootContext(nil), "auth/foo/bar")
- if match != "" {
- t.Fatalf("backend present")
- }
-
- inmemSink := metrics.NewInmemSink(1000000*time.Hour, 2000000*time.Hour)
- conf := &CoreConfig{
- Physical: c.physical,
- DisableMlock: true,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- MetricSink: metricsutil.NewClusterMetricSink("test-cluster", inmemSink),
- MetricsHelper: metricsutil.NewMetricsHelper(inmemSink, false),
- }
- c2, err := NewCore(conf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer c2.Shutdown()
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c2, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("should be unsealed")
- }
- }
-
- // Verify matching mount tables
- if !reflect.DeepEqual(c.auth, c2.auth) {
- t.Fatalf("mismatch: %v %v", c.auth, c2.auth)
- }
-}
-
-func TestCore_DisableCredential_Protected(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- err := c.disableCredential(namespace.RootContext(nil), "token")
- if err.Error() != "token credential backend cannot be disabled" {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestCore_DisableCredential_Cleanup(t *testing.T) {
- noop := &NoopBackend{
- Login: []string{"login"},
- BackendType: logical.TypeCredential,
- }
- c, _, _ := TestCoreUnsealed(t)
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return noop, nil
- }
-
- me := &MountEntry{
- Table: credentialTableType,
- Path: "foo",
- Type: "noop",
- }
- err := c.enableCredential(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Store the view
- view := c.router.MatchingStorageByAPIPath(namespace.RootContext(nil), "auth/foo/")
-
- // Inject data
- se := &logical.StorageEntry{
- Key: "plstodelete",
- Value: []byte("test"),
- }
- if err := view.Put(context.Background(), se); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Generate a new token auth
- noop.Response = &logical.Response{
- Auth: &logical.Auth{
- Policies: []string{"foo"},
- },
- }
- r := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "auth/foo/login",
- }
- resp, err := c.HandleRequest(namespace.RootContext(nil), r)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Disable should cleanup
- err = c.disableCredential(namespace.RootContext(nil), "foo")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Token should be revoked
- te, err := c.tokenStore.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if te != nil {
- t.Fatalf("bad: %#v", te)
- }
-
- // View should be empty
- out, err := logical.CollectKeys(context.Background(), view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(out) != 0 {
- t.Fatalf("bad: %#v", out)
- }
-}
-
-func TestDefaultAuthTable(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- table := c.defaultAuthTable()
- verifyDefaultAuthTable(t, table)
-}
-
-func verifyDefaultAuthTable(t *testing.T, table *MountTable) {
- if len(table.Entries) != 1 {
- t.Fatalf("bad: %v", table.Entries)
- }
- if table.Type != credentialTableType {
- t.Fatalf("bad: %v", *table)
- }
- for idx, entry := range table.Entries {
- switch idx {
- case 0:
- if entry.Path != "token/" {
- t.Fatalf("bad: %v", entry)
- }
- if entry.Type != "token" {
- t.Fatalf("bad: %v", entry)
- }
- }
- if entry.Description == "" {
- t.Fatalf("bad: %v", entry)
- }
- if entry.UUID == "" {
- t.Fatalf("bad: %v", entry)
- }
- }
-}
-
-func TestCore_CredentialInitialize(t *testing.T) {
- {
- backend := &InitializableBackend{
- &NoopBackend{
- BackendType: logical.TypeCredential,
- }, false,
- }
-
- c, _, _ := TestCoreUnsealed(t)
- c.credentialBackends["initable"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return backend, nil
- }
-
- me := &MountEntry{
- Table: credentialTableType,
- Path: "foo/",
- Type: "initable",
- }
- err := c.enableCredential(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if !backend.isInitialized {
- t.Fatal("backend is not initialized")
- }
- }
- {
- backend := &InitializableBackend{
- &NoopBackend{
- BackendType: logical.TypeCredential,
- }, false,
- }
-
- c, _, _ := TestCoreUnsealed(t)
- c.credentialBackends["initable"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return backend, nil
- }
-
- c.auth = &MountTable{
- Type: credentialTableType,
- Entries: []*MountEntry{
- {
- Table: credentialTableType,
- Path: "foo/",
- Type: "initable",
- UUID: "abcd",
- Accessor: "initable-abcd",
- BackendAwareUUID: "abcde",
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- },
- },
- }
-
- err := c.setupCredentials(context.Background())
- if err != nil {
- t.Fatal(err)
- }
-
- // run the postUnseal funcs, so that the backend will be inited
- for _, f := range c.postUnsealFuncs {
- f()
- }
-
- if !backend.isInitialized {
- t.Fatal("backend is not initialized")
- }
- }
-}
-
-func remountCredentialFromRoot(c *Core, src, dst string, updateStorage bool) error {
- srcPathDetails := c.splitNamespaceAndMountFromPath("", src)
- dstPathDetails := c.splitNamespaceAndMountFromPath("", dst)
- return c.remountCredential(namespace.RootContext(nil), srcPathDetails, dstPathDetails, updateStorage)
-}
-
-func TestCore_RemountCredential(t *testing.T) {
- c, keys, _ := TestCoreUnsealed(t)
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{
- BackendType: logical.TypeCredential,
- }, nil
- }
-
- me := &MountEntry{
- Table: credentialTableType,
- Path: "foo",
- Type: "noop",
- }
- err := c.enableCredential(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- match := c.router.MatchingMount(namespace.RootContext(nil), "auth/foo/bar")
- if match != "auth/foo/" {
- t.Fatalf("missing mount, match: %q", match)
- }
-
- err = remountCredentialFromRoot(c, "auth/foo", "auth/bar", true)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- match = c.router.MatchingMount(namespace.RootContext(nil), "auth/bar/baz")
- if match != "auth/bar/" {
- t.Fatalf("auth method not at new location, match: %q", match)
- }
-
- c.sealInternal()
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("should be unsealed")
- }
- }
-
- match = c.router.MatchingMount(namespace.RootContext(nil), "auth/bar/baz")
- if match != "auth/bar/" {
- t.Fatalf("auth method not at new location after unseal, match: %q", match)
- }
-}
-
-func TestCore_RemountCredential_Cleanup(t *testing.T) {
- noop := &NoopBackend{
- Login: []string{"login"},
- BackendType: logical.TypeCredential,
- }
- c, _, _ := TestCoreUnsealed(t)
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return noop, nil
- }
-
- me := &MountEntry{
- Table: credentialTableType,
- Path: "foo",
- Type: "noop",
- }
- err := c.enableCredential(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Store the view
- view := c.router.MatchingStorageByAPIPath(namespace.RootContext(nil), "auth/foo/")
-
- // Inject data
- se := &logical.StorageEntry{
- Key: "plstodelete",
- Value: []byte("test"),
- }
- if err := view.Put(context.Background(), se); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Generate a new token auth
- noop.Response = &logical.Response{
- Auth: &logical.Auth{
- Policies: []string{"foo"},
- },
- }
- r := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "auth/foo/login",
- }
- resp, err := c.HandleRequest(namespace.RootContext(nil), r)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Disable should cleanup
- err = remountCredentialFromRoot(c, "auth/foo", "auth/bar", true)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Token should be revoked
- te, err := c.tokenStore.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if te != nil {
- t.Fatalf("bad: %#v", te)
- }
-
- // View should be empty
- out, err := logical.CollectKeys(context.Background(), view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(out) != 1 && out[0] != "plstokeep" {
- t.Fatalf("bad: %#v", out)
- }
-}
-
-func TestCore_RemountCredential_InvalidSource(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- err := remountCredentialFromRoot(c, "foo", "auth/bar", true)
- if err.Error() != `cannot remount non-auth mount "foo/"` {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestCore_RemountCredential_InvalidDestination(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- err := remountCredentialFromRoot(c, "auth/foo", "bar", true)
- if err.Error() != `cannot remount auth mount to non-auth mount "bar/"` {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestCore_RemountCredential_ProtectedSource(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- err := remountCredentialFromRoot(c, "auth/token", "auth/bar", true)
- if err.Error() != `cannot remount "auth/token/"` {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestCore_RemountCredential_ProtectedDestination(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- err := remountCredentialFromRoot(c, "auth/foo", "auth/token", true)
- if err.Error() != `cannot remount to "auth/token/"` {
- t.Fatalf("err: %v", err)
- }
-}
diff --git a/vault/barrier_aes_gcm_test.go b/vault/barrier_aes_gcm_test.go
deleted file mode 100644
index 9ed7d6fcb..000000000
--- a/vault/barrier_aes_gcm_test.go
+++ /dev/null
@@ -1,774 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "bytes"
- "context"
- "crypto/rand"
- "encoding/json"
- "testing"
- "time"
-
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/helper/testhelpers/corehelpers"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/sdk/physical"
- "github.com/hashicorp/vault/sdk/physical/inmem"
- "github.com/stretchr/testify/require"
-)
-
-var logger = logging.NewVaultLogger(log.Trace)
-
-// mockBarrier returns a physical backend, security barrier, and master key
-func mockBarrier(t testing.TB) (physical.Backend, SecurityBarrier, []byte) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Initialize and unseal
- key, _ := b.GenerateKey(rand.Reader)
- b.Initialize(context.Background(), key, nil, rand.Reader)
- b.Unseal(context.Background(), key)
- return inm, b, key
-}
-
-func TestAESGCMBarrier_Basic(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- testBarrier(t, b)
-}
-
-func TestAESGCMBarrier_Rotate(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- testBarrier_Rotate(t, b)
-}
-
-func TestAESGCMBarrier_MissingRotateConfig(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Initialize and unseal
- key, _ := b.GenerateKey(rand.Reader)
- b.Initialize(context.Background(), key, nil, rand.Reader)
- b.Unseal(context.Background(), key)
-
- // Write a keyring which lacks rotation config settings
- oldKeyring := b.keyring.Clone()
- oldKeyring.rotationConfig = KeyRotationConfig{}
- b.persistKeyring(context.Background(), oldKeyring)
-
- b.ReloadKeyring(context.Background())
-
- // At this point, the rotation config should match the default
- if !defaultRotationConfig.Equals(b.keyring.rotationConfig) {
- t.Fatalf("expected empty rotation config to recover as default config")
- }
-}
-
-func TestAESGCMBarrier_Upgrade(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b1, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b2, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- testBarrier_Upgrade(t, b1, b2)
-}
-
-func TestAESGCMBarrier_Upgrade_Rekey(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b1, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b2, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- testBarrier_Upgrade_Rekey(t, b1, b2)
-}
-
-func TestAESGCMBarrier_Rekey(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- testBarrier_Rekey(t, b)
-}
-
-// Test an upgrade from the old (0.1) barrier/init to the new
-// core/keyring style
-func TestAESGCMBarrier_BackwardsCompatible(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Generate a barrier/init entry
- encrypt, _ := b.GenerateKey(rand.Reader)
- init := &barrierInit{
- Version: 1,
- Key: encrypt,
- }
- buf, _ := json.Marshal(init)
-
- // Protect with master key
- master, _ := b.GenerateKey(rand.Reader)
- gcm, _ := b.aeadFromKey(master)
- value, err := b.encrypt(barrierInitPath, initialKeyTerm, gcm, buf)
- if err != nil {
- t.Fatal(err)
- }
-
- // Write to the physical backend
- pe := &physical.Entry{
- Key: barrierInitPath,
- Value: value,
- }
- inm.Put(context.Background(), pe)
-
- // Create a fake key
- gcm, _ = b.aeadFromKey(encrypt)
- value, err = b.encrypt("test/foo", initialKeyTerm, gcm, []byte("test"))
- if err != nil {
- t.Fatal(err)
- }
- pe = &physical.Entry{
- Key: "test/foo",
- Value: value,
- }
- inm.Put(context.Background(), pe)
-
- // Should still be initialized
- isInit, err := b.Initialized(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !isInit {
- t.Fatalf("should be initialized")
- }
-
- // Unseal should work and migrate online
- err = b.Unseal(context.Background(), master)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Check for migration
- out, err := inm.Get(context.Background(), barrierInitPath)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("should delete old barrier init")
- }
-
- // Should have keyring
- out, err = inm.Get(context.Background(), keyringPath)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out == nil {
- t.Fatalf("should have keyring file")
- }
-
- // Attempt to read encrypted key
- entry, err := b.Get(context.Background(), "test/foo")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if string(entry.Value) != "test" {
- t.Fatalf("bad: %#v", entry)
- }
-}
-
-// Verify data sent through is encrypted
-func TestAESGCMBarrier_Confidential(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Initialize and unseal
- key, _ := b.GenerateKey(rand.Reader)
- b.Initialize(context.Background(), key, nil, rand.Reader)
- b.Unseal(context.Background(), key)
-
- // Put a logical entry
- entry := &logical.StorageEntry{Key: "test", Value: []byte("test")}
- err = b.Put(context.Background(), entry)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Check the physical entry
- pe, err := inm.Get(context.Background(), "test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if pe == nil {
- t.Fatalf("missing physical entry")
- }
-
- if pe.Key != "test" {
- t.Fatalf("bad: %#v", pe)
- }
- if bytes.Equal(pe.Value, entry.Value) {
- t.Fatalf("bad: %#v", pe)
- }
-}
-
-// Verify data sent through cannot be tampered with
-func TestAESGCMBarrier_Integrity(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Initialize and unseal
- key, _ := b.GenerateKey(rand.Reader)
- b.Initialize(context.Background(), key, nil, rand.Reader)
- b.Unseal(context.Background(), key)
-
- // Put a logical entry
- entry := &logical.StorageEntry{Key: "test", Value: []byte("test")}
- err = b.Put(context.Background(), entry)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Change a byte in the underlying physical entry
- pe, _ := inm.Get(context.Background(), "test")
- pe.Value[15]++
- err = inm.Put(context.Background(), pe)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Read from the barrier
- _, err = b.Get(context.Background(), "test")
- if err == nil {
- t.Fatalf("should fail!")
- }
-}
-
-// Verify data sent through cannot be moved
-func TestAESGCMBarrier_MoveIntegrityV1(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b.currentAESGCMVersionByte = AESGCMVersion1
-
- // Initialize and unseal
- key, _ := b.GenerateKey(rand.Reader)
- err = b.Initialize(context.Background(), key, nil, rand.Reader)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- err = b.Unseal(context.Background(), key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Put a logical entry
- entry := &logical.StorageEntry{Key: "test", Value: []byte("test")}
- err = b.Put(context.Background(), entry)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Change the location of the underlying physical entry
- pe, _ := inm.Get(context.Background(), "test")
- pe.Key = "moved"
- err = inm.Put(context.Background(), pe)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Read from the barrier
- _, err = b.Get(context.Background(), "moved")
- if err != nil {
- t.Fatalf("should succeed with version 1!")
- }
-}
-
-func TestAESGCMBarrier_MoveIntegrityV2(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b.currentAESGCMVersionByte = AESGCMVersion2
-
- // Initialize and unseal
- key, _ := b.GenerateKey(rand.Reader)
- err = b.Initialize(context.Background(), key, nil, rand.Reader)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- err = b.Unseal(context.Background(), key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Put a logical entry
- entry := &logical.StorageEntry{Key: "test", Value: []byte("test")}
- err = b.Put(context.Background(), entry)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Change the location of the underlying physical entry
- pe, _ := inm.Get(context.Background(), "test")
- pe.Key = "moved"
- err = inm.Put(context.Background(), pe)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Read from the barrier
- _, err = b.Get(context.Background(), "moved")
- if err == nil {
- t.Fatalf("should fail with version 2!")
- }
-}
-
-func TestAESGCMBarrier_UpgradeV1toV2(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b.currentAESGCMVersionByte = AESGCMVersion1
-
- // Initialize and unseal
- key, _ := b.GenerateKey(rand.Reader)
- err = b.Initialize(context.Background(), key, nil, rand.Reader)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- err = b.Unseal(context.Background(), key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Put a logical entry
- entry := &logical.StorageEntry{Key: "test", Value: []byte("test")}
- err = b.Put(context.Background(), entry)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Seal
- err = b.Seal()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Open again as version 2
- b, err = NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b.currentAESGCMVersionByte = AESGCMVersion2
-
- // Unseal
- err = b.Unseal(context.Background(), key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Check successful decryption
- _, err = b.Get(context.Background(), "test")
- if err != nil {
- t.Fatalf("Upgrade unsuccessful")
- }
-}
-
-func TestEncrypt_Unique(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- key, _ := b.GenerateKey(rand.Reader)
- b.Initialize(context.Background(), key, nil, rand.Reader)
- b.Unseal(context.Background(), key)
-
- if b.keyring == nil {
- t.Fatalf("barrier is sealed")
- }
-
- entry := &logical.StorageEntry{Key: "test", Value: []byte("test")}
- term := b.keyring.ActiveTerm()
- primary, _ := b.aeadForTerm(term)
-
- first, err := b.encrypt("test", term, primary, entry.Value)
- if err != nil {
- t.Fatal(err)
- }
- second, err := b.encrypt("test", term, primary, entry.Value)
- if err != nil {
- t.Fatal(err)
- }
-
- if bytes.Equal(first, second) {
- t.Fatalf("improper random seeding detected")
- }
-}
-
-func TestInitialize_KeyLength(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- long := []byte("ThisKeyDoesNotHaveTheRightLength!")
- middle := []byte("ThisIsASecretKeyAndMore")
- short := []byte("Key")
-
- err = b.Initialize(context.Background(), long, nil, rand.Reader)
-
- if err == nil {
- t.Fatalf("key length protection failed")
- }
-
- err = b.Initialize(context.Background(), middle, nil, rand.Reader)
-
- if err == nil {
- t.Fatalf("key length protection failed")
- }
-
- err = b.Initialize(context.Background(), short, nil, rand.Reader)
-
- if err == nil {
- t.Fatalf("key length protection failed")
- }
-}
-
-func TestEncrypt_BarrierEncryptor(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Initialize and unseal
- key, err := b.GenerateKey(rand.Reader)
- if err != nil {
- t.Fatalf("err generating key: %v", err)
- }
- ctx := context.Background()
- b.Initialize(ctx, key, nil, rand.Reader)
- b.Unseal(ctx, key)
-
- cipher, err := b.Encrypt(ctx, "foo", []byte("quick brown fox"))
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- plain, err := b.Decrypt(ctx, "foo", cipher)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if string(plain) != "quick brown fox" {
- t.Fatalf("bad: %s", plain)
- }
-}
-
-// Ensure Decrypt returns an error (rather than panic) when given a ciphertext
-// that is nil or too short
-func TestDecrypt_InvalidCipherLength(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- key, err := b.GenerateKey(rand.Reader)
- if err != nil {
- t.Fatalf("err generating key: %v", err)
- }
- ctx := context.Background()
- b.Initialize(ctx, key, nil, rand.Reader)
- b.Unseal(ctx, key)
-
- var nilCipher []byte
- if _, err = b.Decrypt(ctx, "", nilCipher); err == nil {
- t.Fatal("expected error when given nil cipher")
- }
- emptyCipher := []byte{}
- if _, err = b.Decrypt(ctx, "", emptyCipher); err == nil {
- t.Fatal("expected error when given empty cipher")
- }
-
- badTermLengthCipher := make([]byte, 3, 3)
- if _, err = b.Decrypt(ctx, "", badTermLengthCipher); err == nil {
- t.Fatal("expected error when given cipher with too short term")
- }
-}
-
-func TestAESGCMBarrier_ReloadKeyring(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Initialize and unseal
- key, _ := b.GenerateKey(rand.Reader)
- b.Initialize(context.Background(), key, nil, rand.Reader)
- b.Unseal(context.Background(), key)
-
- keyringRaw, err := inm.Get(context.Background(), keyringPath)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Encrypt something to test cache invalidation
- _, err = b.Encrypt(context.Background(), "foo", []byte("quick brown fox"))
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- {
- // Create a second barrier and rotate the keyring
- b2, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b2.Unseal(context.Background(), key)
- _, err = b2.Rotate(context.Background(), rand.Reader)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- }
-
- // Reload the keyring on the first
- err = b.ReloadKeyring(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if b.keyring.ActiveTerm() != 2 {
- t.Fatal("failed to reload keyring")
- }
- if len(b.cache) != 0 {
- t.Fatal("failed to clear cache")
- }
-
- // Encrypt something to test cache invalidation
- _, err = b.Encrypt(context.Background(), "foo", []byte("quick brown fox"))
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Restore old keyring to test rolling back
- err = inm.Put(context.Background(), keyringRaw)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Reload the keyring on the first
- err = b.ReloadKeyring(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if b.keyring.ActiveTerm() != 1 {
- t.Fatal("failed to reload keyring")
- }
- if len(b.cache) != 0 {
- t.Fatal("failed to clear cache")
- }
-}
-
-func TestBarrier_LegacyRotate(t *testing.T) {
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- b1, err := NewAESGCMBarrier(inm)
- if err != nil {
- t.Fatalf("err: %v", err)
- } // Initialize the barrier
- key, _ := b1.GenerateKey(rand.Reader)
- b1.Initialize(context.Background(), key, nil, rand.Reader)
- err = b1.Unseal(context.Background(), key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- k1 := b1.keyring.TermKey(1)
- k1.Encryptions = 0
- k1.InstallTime = time.Now().Add(-24 * 366 * time.Hour)
- b1.persistKeyring(context.Background(), b1.keyring)
- b1.Seal()
-
- err = b1.Unseal(context.Background(), key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- reason, err := b1.CheckBarrierAutoRotate(context.Background())
- if err != nil || reason != legacyRotateReason {
- t.Fail()
- }
-}
-
-// TestBarrier_persistKeyring_Context checks that we get the right errors if
-// the context is cancelled or times-out before the first part of persistKeyring
-// is able to persist the keyring itself (i.e. we don't go on to try and persist
-// the root key).
-func TestBarrier_persistKeyring_Context(t *testing.T) {
- t.Parallel()
-
- tests := map[string]struct {
- shouldCancel bool
- isErrorExpected bool
- expectedErrorMessage string
- contextTimeout time.Duration
- testTimeout time.Duration
- }{
- "cancelled": {
- shouldCancel: true,
- isErrorExpected: true,
- expectedErrorMessage: "failed to persist keyring: context canceled",
- contextTimeout: 8 * time.Second,
- testTimeout: 10 * time.Second,
- },
- "timeout-before-keyring": {
- isErrorExpected: true,
- expectedErrorMessage: "failed to persist keyring: context deadline exceeded",
- contextTimeout: 1 * time.Nanosecond,
- testTimeout: 5 * time.Second,
- },
- }
-
- for name, tc := range tests {
- name := name
- tc := tc
- t.Run(name, func(t *testing.T) {
- t.Parallel()
-
- // Set up barrier
- backend, err := inmem.NewInmem(nil, corehelpers.NewTestLogger(t))
- require.NoError(t, err)
- barrier, err := NewAESGCMBarrier(backend)
- require.NoError(t, err)
- key, err := barrier.GenerateKey(rand.Reader)
- require.NoError(t, err)
- err = barrier.Initialize(context.Background(), key, nil, rand.Reader)
- require.NoError(t, err)
- err = barrier.Unseal(context.Background(), key)
- require.NoError(t, err)
- k := barrier.keyring.TermKey(1)
- k.Encryptions = 0
- k.InstallTime = time.Now().Add(-24 * 366 * time.Hour)
-
- // Persist the keyring
- ctx, cancel := context.WithTimeout(context.Background(), tc.contextTimeout)
- persistChan := make(chan error)
- go func() {
- if tc.shouldCancel {
- cancel()
- }
- persistChan <- barrier.persistKeyring(ctx, barrier.keyring)
- }()
-
- select {
- case err := <-persistChan:
- switch {
- case tc.isErrorExpected:
- require.Error(t, err)
- require.EqualError(t, err, tc.expectedErrorMessage)
- default:
- require.NoError(t, err)
- }
- case <-time.After(tc.testTimeout):
- t.Fatal("timeout reached")
- }
- })
- }
-}
diff --git a/vault/barrier_test.go b/vault/barrier_test.go
deleted file mode 100644
index 97ec591e2..000000000
--- a/vault/barrier_test.go
+++ /dev/null
@@ -1,546 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "crypto/rand"
- "reflect"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func testBarrier(t *testing.T, b SecurityBarrier) {
- err, e, key := testInitAndUnseal(t, b)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Operations should work
- out, err := b.Get(context.Background(), "test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("bad: %v", out)
- }
-
- // List should have only "core/"
- keys, err := b.List(context.Background(), "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(keys) != 1 || keys[0] != "core/" {
- t.Fatalf("bad: %v", keys)
- }
-
- // Try to write
- if err := b.Put(context.Background(), e); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should be equal
- out, err = b.Get(context.Background(), "test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !reflect.DeepEqual(out, e) {
- t.Fatalf("bad: %v exp: %v", out, e)
- }
-
- // List should show the items
- keys, err = b.List(context.Background(), "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(keys) != 2 {
- t.Fatalf("bad: %v", keys)
- }
- if keys[0] != "core/" || keys[1] != "test" {
- t.Fatalf("bad: %v", keys)
- }
-
- // Delete should clear
- err = b.Delete(context.Background(), "test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Double Delete is fine
- err = b.Delete(context.Background(), "test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should be nil
- out, err = b.Get(context.Background(), "test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("bad: %v", out)
- }
-
- // List should have nothing
- keys, err = b.List(context.Background(), "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(keys) != 1 || keys[0] != "core/" {
- t.Fatalf("bad: %v", keys)
- }
-
- // Add the item back
- if err := b.Put(context.Background(), e); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Reseal should prevent any updates
- if err := b.Seal(); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // No access allowed
- if _, err := b.Get(context.Background(), "test"); err != ErrBarrierSealed {
- t.Fatalf("err: %v", err)
- }
-
- // Unseal should work
- if err := b.Unseal(context.Background(), key); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should be equal
- out, err = b.Get(context.Background(), "test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !reflect.DeepEqual(out, e) {
- t.Fatalf("bad: %v exp: %v", out, e)
- }
-
- // Final cleanup
- err = b.Delete(context.Background(), "test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Reseal should prevent any updates
- if err := b.Seal(); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Modify the key
- key[0]++
-
- // Unseal should fail
- if err := b.Unseal(context.Background(), key); err != ErrBarrierInvalidKey {
- t.Fatalf("err: %v", err)
- }
-}
-
-func testInitAndUnseal(t *testing.T, b SecurityBarrier) (error, *logical.StorageEntry, []byte) {
- // Should not be initialized
- init, err := b.Initialized(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if init {
- t.Fatalf("should not be initialized")
- }
-
- // Should start sealed
- sealed, err := b.Sealed()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !sealed {
- t.Fatalf("should be sealed")
- }
-
- // Sealing should be a no-op
- if err := b.Seal(); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // All operations should fail
- e := &logical.StorageEntry{Key: "test", Value: []byte("test")}
- if err := b.Put(context.Background(), e); err != ErrBarrierSealed {
- t.Fatalf("err: %v", err)
- }
- if _, err := b.Get(context.Background(), "test"); err != ErrBarrierSealed {
- t.Fatalf("err: %v", err)
- }
- if err := b.Delete(context.Background(), "test"); err != ErrBarrierSealed {
- t.Fatalf("err: %v", err)
- }
- if _, err := b.List(context.Background(), ""); err != ErrBarrierSealed {
- t.Fatalf("err: %v", err)
- }
-
- // Get a new key
- key, err := b.GenerateKey(rand.Reader)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Validate minimum key length
- min, max := b.KeyLength()
- if min < 16 {
- t.Fatalf("minimum key size too small: %d", min)
- }
- if max < min {
- t.Fatalf("maximum key size smaller than min")
- }
-
- // Unseal should not work
- if err := b.Unseal(context.Background(), key); err != ErrBarrierNotInit {
- t.Fatalf("err: %v", err)
- }
-
- // Initialize the vault
- if err := b.Initialize(context.Background(), key, nil, rand.Reader); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Double Initialize should fail
- if err := b.Initialize(context.Background(), key, nil, rand.Reader); err != ErrBarrierAlreadyInit {
- t.Fatalf("err: %v", err)
- }
-
- // Should be initialized
- init, err = b.Initialized(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !init {
- t.Fatalf("should be initialized")
- }
-
- // Should still be sealed
- sealed, err = b.Sealed()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !sealed {
- t.Fatalf("should sealed")
- }
-
- // Unseal should work
- if err := b.Unseal(context.Background(), key); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Unseal should no-op when done twice
- if err := b.Unseal(context.Background(), key); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should no longer be sealed
- sealed, err = b.Sealed()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if sealed {
- t.Fatalf("should be unsealed")
- }
-
- // Verify the root key
- if err := b.VerifyRoot(key); err != nil {
- t.Fatalf("err: %v", err)
- }
- return err, e, key
-}
-
-func testBarrier_Rotate(t *testing.T, b SecurityBarrier) {
- // Initialize the barrier
- key, _ := b.GenerateKey(rand.Reader)
- b.Initialize(context.Background(), key, nil, rand.Reader)
- err := b.Unseal(context.Background(), key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Check the key info
- info, err := b.ActiveKeyInfo()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if info.Term != 1 {
- t.Fatalf("Bad term: %d", info.Term)
- }
- if time.Since(info.InstallTime) > time.Second {
- t.Fatalf("Bad install: %v", info.InstallTime)
- }
- first := info.InstallTime
-
- // Write a key
- e1 := &logical.StorageEntry{Key: "test", Value: []byte("test")}
- if err := b.Put(context.Background(), e1); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Rotate the encryption key
- newTerm, err := b.Rotate(context.Background(), rand.Reader)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if newTerm != 2 {
- t.Fatalf("bad: %v", newTerm)
- }
-
- // Check the key info
- info, err = b.ActiveKeyInfo()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if info.Term != 2 {
- t.Fatalf("Bad term: %d", info.Term)
- }
- if !info.InstallTime.After(first) {
- t.Fatalf("Bad install: %v", info.InstallTime)
- }
-
- // Write another key
- e2 := &logical.StorageEntry{Key: "foo", Value: []byte("test")}
- if err := b.Put(context.Background(), e2); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Reading both should work
- out, err := b.Get(context.Background(), e1.Key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out == nil {
- t.Fatalf("bad: %v", out)
- }
-
- out, err = b.Get(context.Background(), e2.Key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out == nil {
- t.Fatalf("bad: %v", out)
- }
-
- // Seal and unseal
- err = b.Seal()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- err = b.Unseal(context.Background(), key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Reading both should work
- out, err = b.Get(context.Background(), e1.Key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out == nil {
- t.Fatalf("bad: %v", out)
- }
-
- out, err = b.Get(context.Background(), e2.Key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out == nil {
- t.Fatalf("bad: %v", out)
- }
-
- // Should be fine to reload keyring
- err = b.ReloadKeyring(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-}
-
-func testBarrier_Rekey(t *testing.T, b SecurityBarrier) {
- // Initialize the barrier
- key, _ := b.GenerateKey(rand.Reader)
- b.Initialize(context.Background(), key, nil, rand.Reader)
- err := b.Unseal(context.Background(), key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Write a key
- e1 := &logical.StorageEntry{Key: "test", Value: []byte("test")}
- if err := b.Put(context.Background(), e1); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Verify the master key
- if err := b.VerifyRoot(key); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Rekey to a new key
- newKey, _ := b.GenerateKey(rand.Reader)
- err = b.Rekey(context.Background(), newKey)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Verify the old master key
- if err := b.VerifyRoot(key); err != ErrBarrierInvalidKey {
- t.Fatalf("err: %v", err)
- }
-
- // Verify the new master key
- if err := b.VerifyRoot(newKey); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Reading should work
- out, err := b.Get(context.Background(), e1.Key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out == nil {
- t.Fatalf("bad: %v", out)
- }
-
- // Seal
- err = b.Seal()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Unseal with old key should fail
- err = b.Unseal(context.Background(), key)
- if err == nil {
- t.Fatalf("unseal should fail")
- }
-
- // Unseal with new keys should work
- err = b.Unseal(context.Background(), newKey)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Reading should work
- out, err = b.Get(context.Background(), e1.Key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out == nil {
- t.Fatalf("bad: %v", out)
- }
-
- // Should be fine to reload keyring
- err = b.ReloadKeyring(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-}
-
-func testBarrier_Upgrade(t *testing.T, b1, b2 SecurityBarrier) {
- // Initialize the barrier
- key, _ := b1.GenerateKey(rand.Reader)
- b1.Initialize(context.Background(), key, nil, rand.Reader)
- err := b1.Unseal(context.Background(), key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- err = b2.Unseal(context.Background(), key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Rotate the encryption key
- newTerm, err := b1.Rotate(context.Background(), rand.Reader)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Create upgrade path
- err = b1.CreateUpgrade(context.Background(), newTerm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Check for an upgrade
- did, updated, err := b2.CheckUpgrade(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !did || updated != newTerm {
- t.Fatalf("failed to upgrade")
- }
-
- // Should have no upgrades pending
- did, updated, err = b2.CheckUpgrade(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if did {
- t.Fatalf("should not have upgrade")
- }
-
- // Rotate the encryption key
- newTerm, err = b1.Rotate(context.Background(), rand.Reader)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Create upgrade path
- err = b1.CreateUpgrade(context.Background(), newTerm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Destroy upgrade path
- err = b1.DestroyUpgrade(context.Background(), newTerm)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should have no upgrades pending
- did, updated, err = b2.CheckUpgrade(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if did {
- t.Fatalf("should not have upgrade")
- }
-}
-
-func testBarrier_Upgrade_Rekey(t *testing.T, b1, b2 SecurityBarrier) {
- // Initialize the barrier
- key, _ := b1.GenerateKey(rand.Reader)
- b1.Initialize(context.Background(), key, nil, rand.Reader)
- err := b1.Unseal(context.Background(), key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- err = b2.Unseal(context.Background(), key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Rekey to a new key
- newKey, _ := b1.GenerateKey(rand.Reader)
- err = b1.Rekey(context.Background(), newKey)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Reload the master key
- err = b2.ReloadRootKey(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Reload the keyring
- err = b2.ReloadKeyring(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-}
diff --git a/vault/barrier_view_test.go b/vault/barrier_view_test.go
deleted file mode 100644
index 705d3340c..000000000
--- a/vault/barrier_view_test.go
+++ /dev/null
@@ -1,321 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "reflect"
- "sort"
- "testing"
-
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestBarrierView_impl(t *testing.T) {
- var _ logical.Storage = new(BarrierView)
-}
-
-func TestBarrierView_spec(t *testing.T) {
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "foo/")
- logical.TestStorage(t, view)
-}
-
-func TestBarrierView_BadKeysKeys(t *testing.T) {
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "foo/")
-
- _, err := view.List(context.Background(), "../")
- if err == nil {
- t.Fatalf("expected error")
- }
-
- _, err = view.Get(context.Background(), "../")
- if err == nil {
- t.Fatalf("expected error")
- }
-
- err = view.Delete(context.Background(), "../foo")
- if err == nil {
- t.Fatalf("expected error")
- }
-
- le := &logical.StorageEntry{
- Key: "../foo",
- Value: []byte("test"),
- }
- err = view.Put(context.Background(), le)
- if err == nil {
- t.Fatalf("expected error")
- }
-}
-
-func TestBarrierView(t *testing.T) {
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "foo/")
-
- // Write a key outside of foo/
- entry := &logical.StorageEntry{Key: "test", Value: []byte("test")}
- if err := barrier.Put(context.Background(), entry); err != nil {
- t.Fatalf("bad: %v", err)
- }
-
- // List should have no visibility
- keys, err := view.List(context.Background(), "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(keys) != 0 {
- t.Fatalf("bad: %v", err)
- }
-
- // Get should have no visibility
- out, err := view.Get(context.Background(), "test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("bad: %v", out)
- }
-
- // Try to put the same entry via the view
- if err := view.Put(context.Background(), entry); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Check it is nested
- entry, err = barrier.Get(context.Background(), "foo/test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if entry == nil {
- t.Fatalf("missing nested foo/test")
- }
-
- // Delete nested
- if err := view.Delete(context.Background(), "test"); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Check the nested key
- entry, err = barrier.Get(context.Background(), "foo/test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if entry != nil {
- t.Fatalf("nested foo/test should be gone")
- }
-
- // Check the non-nested key
- entry, err = barrier.Get(context.Background(), "test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if entry == nil {
- t.Fatalf("root test missing")
- }
-}
-
-func TestBarrierView_SubView(t *testing.T) {
- _, barrier, _ := mockBarrier(t)
- root := NewBarrierView(barrier, "foo/")
- view := root.SubView("bar/")
-
- // List should have no visibility
- keys, err := view.List(context.Background(), "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(keys) != 0 {
- t.Fatalf("bad: %v", err)
- }
-
- // Get should have no visibility
- out, err := view.Get(context.Background(), "test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("bad: %v", out)
- }
-
- // Try to put the same entry via the view
- entry := &logical.StorageEntry{Key: "test", Value: []byte("test")}
- if err := view.Put(context.Background(), entry); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Check it is nested
- bout, err := barrier.Get(context.Background(), "foo/bar/test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if bout == nil {
- t.Fatalf("missing nested foo/bar/test")
- }
-
- // Check for visibility in root
- out, err = root.Get(context.Background(), "bar/test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out == nil {
- t.Fatalf("missing nested bar/test")
- }
-
- // Delete nested
- if err := view.Delete(context.Background(), "test"); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Check the nested key
- bout, err = barrier.Get(context.Background(), "foo/bar/test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if bout != nil {
- t.Fatalf("nested foo/bar/test should be gone")
- }
-}
-
-func TestBarrierView_Scan(t *testing.T) {
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "view/")
-
- expect := []string{}
- ent := []*logical.StorageEntry{
- {Key: "foo", Value: []byte("test")},
- {Key: "zip", Value: []byte("test")},
- {Key: "foo/bar", Value: []byte("test")},
- {Key: "foo/zap", Value: []byte("test")},
- {Key: "foo/bar/baz", Value: []byte("test")},
- {Key: "foo/bar/zoo", Value: []byte("test")},
- }
-
- for _, e := range ent {
- expect = append(expect, e.Key)
- if err := view.Put(context.Background(), e); err != nil {
- t.Fatalf("err: %v", err)
- }
- }
-
- var out []string
- cb := func(path string) {
- out = append(out, path)
- }
-
- // Collect the keys
- if err := logical.ScanView(context.Background(), view, cb); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- sort.Strings(out)
- sort.Strings(expect)
- if !reflect.DeepEqual(out, expect) {
- t.Fatalf("out: %v expect: %v", out, expect)
- }
-}
-
-func TestBarrierView_CollectKeys(t *testing.T) {
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "view/")
-
- expect := []string{}
- ent := []*logical.StorageEntry{
- {Key: "foo", Value: []byte("test")},
- {Key: "zip", Value: []byte("test")},
- {Key: "foo/bar", Value: []byte("test")},
- {Key: "foo/zap", Value: []byte("test")},
- {Key: "foo/bar/baz", Value: []byte("test")},
- {Key: "foo/bar/zoo", Value: []byte("test")},
- }
-
- for _, e := range ent {
- expect = append(expect, e.Key)
- if err := view.Put(context.Background(), e); err != nil {
- t.Fatalf("err: %v", err)
- }
- }
-
- // Collect the keys
- out, err := logical.CollectKeys(context.Background(), view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- sort.Strings(out)
- sort.Strings(expect)
- if !reflect.DeepEqual(out, expect) {
- t.Fatalf("out: %v expect: %v", out, expect)
- }
-}
-
-func TestBarrierView_ClearView(t *testing.T) {
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "view/")
-
- expect := []string{}
- ent := []*logical.StorageEntry{
- {Key: "foo", Value: []byte("test")},
- {Key: "zip", Value: []byte("test")},
- {Key: "foo/bar", Value: []byte("test")},
- {Key: "foo/zap", Value: []byte("test")},
- {Key: "foo/bar/baz", Value: []byte("test")},
- {Key: "foo/bar/zoo", Value: []byte("test")},
- }
-
- for _, e := range ent {
- expect = append(expect, e.Key)
- if err := view.Put(context.Background(), e); err != nil {
- t.Fatalf("err: %v", err)
- }
- }
-
- // Clear the keys
- if err := logical.ClearView(context.Background(), view); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Collect the keys
- out, err := logical.CollectKeys(context.Background(), view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(out) != 0 {
- t.Fatalf("have keys: %#v", out)
- }
-}
-
-func TestBarrierView_Readonly(t *testing.T) {
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "foo/")
-
- // Add a key before enabling read-only
- entry := &logical.StorageEntry{Key: "test", Value: []byte("test")}
- if err := view.Put(context.Background(), entry); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Enable read only mode
- view.readOnlyErr = logical.ErrReadOnly
-
- // Put should fail in readonly mode
- if err := view.Put(context.Background(), entry); err != logical.ErrReadOnly {
- t.Fatalf("err: %v", err)
- }
-
- // Delete nested
- if err := view.Delete(context.Background(), "test"); err != logical.ErrReadOnly {
- t.Fatalf("err: %v", err)
- }
-
- // Check the non-nested key
- e, err := view.Get(context.Background(), "test")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if e == nil {
- t.Fatalf("key test missing")
- }
-}
diff --git a/vault/capabilities_test.go b/vault/capabilities_test.go
deleted file mode 100644
index d0bd92c60..000000000
--- a/vault/capabilities_test.go
+++ /dev/null
@@ -1,231 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "fmt"
- "reflect"
- "sort"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestCapabilities_DerivedPolicies(t *testing.T) {
- var resp *logical.Response
- var err error
-
- ctx := namespace.RootContext(nil)
- i, _, c := testIdentityStoreWithGithubAuth(ctx, t)
-
- policy1 := `
-name = "policy1"
-path "secret/sample" {
- capabilities = ["update", "create", "sudo"]
-}
-`
- policy2 := `
-name = "policy2"
-path "secret/sample" {
- capabilities = ["read", "delete"]
-}
-`
-
- policy3 := `
-name = "policy3"
-path "secret/sample" {
- capabilities = ["list", "list"]
-}
-`
- // Create the above policies
- policy, _ := ParseACLPolicy(namespace.RootNamespace, policy1)
- err = c.policyStore.SetPolicy(ctx, policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- policy, _ = ParseACLPolicy(namespace.RootNamespace, policy2)
- err = c.policyStore.SetPolicy(ctx, policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- policy, _ = ParseACLPolicy(namespace.RootNamespace, policy3)
- err = c.policyStore.SetPolicy(ctx, policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Create an entity and assign policy1 to it
- entityReq := &logical.Request{
- Path: "entity",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "policies": "policy1",
- },
- }
- resp, err = i.HandleRequest(ctx, entityReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %#v\n", resp, err)
- }
- entityID := resp.Data["id"].(string)
-
- // Create a token for the entity and assign policy2 on the token
- ent := &logical.TokenEntry{
- ID: "capabilitiestoken",
- Path: "secret/sample",
- Policies: []string{"policy2"},
- EntityID: entityID,
- TTL: time.Hour,
- }
- testMakeTokenDirectly(t, c.tokenStore, ent)
-
- actual, err := c.Capabilities(ctx, "capabilitiestoken", "secret/sample")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- expected := []string{"create", "read", "sudo", "delete", "update"}
- sort.Strings(actual)
- sort.Strings(expected)
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
- }
-
- // Create a group and add the above created entity to it
- groupReq := &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "member_entity_ids": []string{entityID},
- "policies": "policy3",
- },
- }
- resp, err = i.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %#v\n", resp, err)
- }
-
- actual, err = c.Capabilities(namespace.RootContext(nil), "capabilitiestoken", "secret/sample")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- expected = []string{"create", "read", "sudo", "delete", "update", "list"}
- sort.Strings(actual)
- sort.Strings(expected)
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
- }
-}
-
-func TestCapabilities_TemplatedPolicies(t *testing.T) {
- var resp *logical.Response
- var err error
- i, _, c := testIdentityStoreWithGithubAuth(namespace.RootContext(nil), t)
- // Create an entity and assign policy1 to it
- entityReq := &logical.Request{
- Path: "entity",
- Operation: logical.UpdateOperation,
- }
- resp, err = i.HandleRequest(namespace.RootContext(nil), entityReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %#v\n", resp, err)
- }
- entityID := resp.Data["id"].(string)
-
- // Create a token for the entity and assign policy2 on the token
- ent := &logical.TokenEntry{
- ID: "capabilitiestoken",
- Path: "auth/token/create",
- Policies: []string{"testpolicy"},
- EntityID: entityID,
- TTL: time.Hour,
- }
- testMakeTokenDirectly(t, c.tokenStore, ent)
-
- tCases := []struct {
- policy string
- path string
- expected []string
- }{
- {
- `name = "testpolicy"
- path "secret/{{identity.entity.id}}/sample" {
- capabilities = ["update", "create"]
- }
- `,
- fmt.Sprintf("secret/%s/sample", entityID),
- []string{"update", "create"},
- },
- {
- `{"name": "testpolicy", "path": {"secret/{{identity.entity.id}}/sample": {"capabilities": ["read", "create"]}}}`,
- fmt.Sprintf("secret/%s/sample", entityID),
- []string{"read", "create"},
- },
- {
- `{"name": "testpolicy", "path": {"secret/sample": {"capabilities": ["read"]}}}`,
- "secret/sample",
- []string{"read"},
- },
- }
- for _, tCase := range tCases {
- // Create the above policies
- policy, err := ParseACLPolicy(namespace.RootNamespace, tCase.policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- err = c.policyStore.SetPolicy(namespace.RootContext(nil), policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- actual, err := c.Capabilities(namespace.RootContext(nil), "capabilitiestoken", tCase.path)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- sort.Strings(actual)
- sort.Strings(tCase.expected)
- if !reflect.DeepEqual(actual, tCase.expected) {
- t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, tCase.expected)
- }
- }
-}
-
-func TestCapabilities(t *testing.T) {
- c, _, token := TestCoreUnsealed(t)
-
- actual, err := c.Capabilities(namespace.RootContext(nil), token, "path")
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- expected := []string{"root"}
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
- }
-
- // Create a policy
- policy, _ := ParseACLPolicy(namespace.RootNamespace, aclPolicy)
- err = c.policyStore.SetPolicy(namespace.RootContext(nil), policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Create a token for the policy
- ent := &logical.TokenEntry{
- ID: "capabilitiestoken",
- Path: "testpath",
- Policies: []string{"dev"},
- TTL: time.Hour,
- }
- testMakeTokenDirectly(t, c.tokenStore, ent)
-
- actual, err = c.Capabilities(namespace.RootContext(nil), "capabilitiestoken", "foo/bar")
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- expected = []string{"create", "read", "sudo"}
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
- }
-}
diff --git a/vault/cluster/inmem_layer_test.go b/vault/cluster/inmem_layer_test.go
deleted file mode 100644
index dcdb89730..000000000
--- a/vault/cluster/inmem_layer_test.go
+++ /dev/null
@@ -1,264 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package cluster
-
-import (
- "sync"
- "testing"
- "time"
-
- log "github.com/hashicorp/go-hclog"
- "go.uber.org/atomic"
-)
-
-func TestInmemCluster_Connect(t *testing.T) {
- cluster, err := NewInmemLayerCluster("c1", 3, log.New(&log.LoggerOptions{
- Mutex: &sync.Mutex{},
- Level: log.Trace,
- Name: "inmem-cluster",
- }))
- if err != nil {
- t.Fatal(err)
- }
-
- server := cluster.layers[0]
-
- listener := server.Listeners()[0]
- var accepted int
- stopCh := make(chan struct{})
- var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- defer wg.Done()
- for {
- select {
- case <-stopCh:
- return
- default:
- }
-
- listener.SetDeadline(time.Now().Add(5 * time.Second))
-
- _, err := listener.Accept()
- if err != nil {
- return
- }
-
- accepted++
-
- }
- }()
-
- // Make sure two nodes can connect in
- conn, err := cluster.layers[1].Dial(server.addr, 0, nil)
- if err != nil {
- t.Fatal(err)
- }
-
- if conn == nil {
- t.Fatal("nil conn")
- }
-
- conn, err = cluster.layers[2].Dial(server.addr, 0, nil)
- if err != nil {
- t.Fatal(err)
- }
-
- if conn == nil {
- t.Fatal("nil conn")
- }
-
- close(stopCh)
- wg.Wait()
-
- if accepted != 2 {
- t.Fatalf("expected 2 connections to be accepted, got %d", accepted)
- }
-}
-
-func TestInmemCluster_Disconnect(t *testing.T) {
- cluster, err := NewInmemLayerCluster("c1", 3, log.New(&log.LoggerOptions{
- Mutex: &sync.Mutex{},
- Level: log.Trace,
- Name: "inmem-cluster",
- }))
- if err != nil {
- t.Fatal(err)
- }
-
- server := cluster.layers[0]
- server.Disconnect(cluster.layers[1].addr)
-
- listener := server.Listeners()[0]
- var accepted int
- stopCh := make(chan struct{})
- var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- defer wg.Done()
- for {
- select {
- case <-stopCh:
- return
- default:
- }
-
- listener.SetDeadline(time.Now().Add(5 * time.Second))
-
- _, err := listener.Accept()
- if err != nil {
- return
- }
-
- accepted++
-
- }
- }()
-
- // Make sure node1 cannot connect in
- conn, err := cluster.layers[1].Dial(server.addr, 0, nil)
- if err == nil {
- t.Fatal("expected error")
- }
-
- if conn != nil {
- t.Fatal("expected nil conn")
- }
-
- // Node2 should be able to connect
- conn, err = cluster.layers[2].Dial(server.addr, 0, nil)
- if err != nil {
- t.Fatal(err)
- }
-
- if conn == nil {
- t.Fatal("nil conn")
- }
-
- close(stopCh)
- wg.Wait()
-
- if accepted != 1 {
- t.Fatalf("expected 1 connections to be accepted, got %d", accepted)
- }
-}
-
-func TestInmemCluster_DisconnectAll(t *testing.T) {
- cluster, err := NewInmemLayerCluster("c1", 3, log.New(&log.LoggerOptions{
- Mutex: &sync.Mutex{},
- Level: log.Trace,
- Name: "inmem-cluster",
- }))
- if err != nil {
- t.Fatal(err)
- }
-
- server := cluster.layers[0]
- server.DisconnectAll()
-
- // Make sure nodes cannot connect in
- conn, err := cluster.layers[1].Dial(server.addr, 0, nil)
- if err == nil {
- t.Fatal("expected error")
- }
-
- if conn != nil {
- t.Fatal("expected nil conn")
- }
-
- conn, err = cluster.layers[2].Dial(server.addr, 0, nil)
- if err == nil {
- t.Fatal("expected error")
- }
-
- if conn != nil {
- t.Fatal("expected nil conn")
- }
-}
-
-func TestInmemCluster_ConnectCluster(t *testing.T) {
- cluster, err := NewInmemLayerCluster("c1", 3, log.New(&log.LoggerOptions{
- Mutex: &sync.Mutex{},
- Level: log.Trace,
- Name: "inmem-cluster",
- }))
- if err != nil {
- t.Fatal(err)
- }
- cluster2, err := NewInmemLayerCluster("c2", 3, log.New(&log.LoggerOptions{
- Mutex: &sync.Mutex{},
- Level: log.Trace,
- Name: "inmem-cluster",
- }))
- if err != nil {
- t.Fatal(err)
- }
-
- cluster.ConnectCluster(cluster2)
-
- var accepted atomic.Int32
- stopCh := make(chan struct{})
- var wg sync.WaitGroup
- acceptConns := func(listener NetworkListener) {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for {
- select {
- case <-stopCh:
- return
- default:
- }
-
- listener.SetDeadline(time.Now().Add(5 * time.Second))
-
- _, err := listener.Accept()
- if err != nil {
- return
- }
-
- accepted.Add(1)
-
- }
- }()
- }
-
- // Start a listener on each node.
- for _, node := range cluster.layers {
- acceptConns(node.Listeners()[0])
- }
- for _, node := range cluster2.layers {
- acceptConns(node.Listeners()[0])
- }
-
- // Make sure each node can connect to each other
- for _, node1 := range cluster.layers {
- for _, node2 := range cluster2.layers {
- conn, err := node1.Dial(node2.addr, 0, nil)
- if err != nil {
- t.Fatal(err)
- }
-
- if conn == nil {
- t.Fatal("nil conn")
- }
-
- conn, err = node2.Dial(node1.addr, 0, nil)
- if err != nil {
- t.Fatal(err)
- }
-
- if conn == nil {
- t.Fatal("nil conn")
- }
- }
- }
-
- close(stopCh)
- wg.Wait()
-
- if accepted.Load() != 18 {
- t.Fatalf("expected 18 connections to be accepted, got %d", accepted.Load())
- }
-}
diff --git a/vault/cluster_test.go b/vault/cluster_test.go
deleted file mode 100644
index 8e56909af..000000000
--- a/vault/cluster_test.go
+++ /dev/null
@@ -1,374 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "bytes"
- "context"
- "crypto/tls"
- "fmt"
- "net/http"
- "sync"
- "testing"
- "time"
-
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/helper/testhelpers/corehelpers"
- "github.com/hashicorp/vault/sdk/helper/consts"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/sdk/physical"
- "github.com/hashicorp/vault/sdk/physical/inmem"
- "github.com/hashicorp/vault/vault/cluster"
-)
-
-var clusterTestPausePeriod = 2 * time.Second
-
-func TestClusterFetching(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
-
- err := c.setupCluster(context.Background())
- if err != nil {
- t.Fatal(err)
- }
-
- cluster, err := c.Cluster(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- // Test whether expected values are found
- if cluster == nil || cluster.Name == "" || cluster.ID == "" {
- t.Fatalf("cluster information missing: cluster: %#v", cluster)
- }
-}
-
-func TestClusterHAFetching(t *testing.T) {
- logger := logging.NewVaultLogger(log.Trace)
-
- redirect := "http://127.0.0.1:8200"
-
- inm, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- inmha, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- c, err := NewCore(&CoreConfig{
- Physical: inm,
- HAPhysical: inmha.(physical.HABackend),
- RedirectAddr: redirect,
- DisableMlock: true,
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer c.Shutdown()
- keys, _ := TestCoreInit(t, c)
- for _, key := range keys {
- if _, err := TestCoreUnseal(c, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- // Verify unsealed
- if c.Sealed() {
- t.Fatal("should not be sealed")
- }
-
- // Wait for core to become active
- TestWaitActive(t, c)
-
- cluster, err := c.Cluster(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- // Test whether expected values are found
- if cluster == nil || cluster.Name == "" || cluster.ID == "" {
- t.Fatalf("cluster information missing: cluster:%#v", cluster)
- }
-}
-
-func TestCluster_ListenForRequests(t *testing.T) {
- // Make this nicer for tests
- manualStepDownSleepPeriod = 5 * time.Second
-
- cluster := NewTestCluster(t, nil, &TestClusterOptions{
- KeepStandbysSealed: true,
- })
- cluster.Start()
- defer cluster.Cleanup()
- cores := cluster.Cores
-
- // Wait for core to become active
- TestWaitActive(t, cores[0].Core)
-
- clusterListener := cores[0].getClusterListener()
- clusterListener.AddClient(consts.RequestForwardingALPN, &requestForwardingClusterClient{cores[0].Core})
- addrs := cores[0].getClusterListener().Addrs()
-
- // Use this to have a valid config after sealing since ClusterTLSConfig returns nil
- checkListenersFunc := func(expectFail bool) {
- dialer := clusterListener.GetDialerFunc(context.Background(), consts.RequestForwardingALPN)
- for i := range cores[0].Listeners {
-
- clnAddr := addrs[i]
- netConn, err := dialer(clnAddr.String(), 0)
- if err != nil {
- if expectFail {
- t.Logf("testing %s unsuccessful as expected", clnAddr)
- continue
- }
- t.Fatalf("error: %v\ncluster listener is %s", err, clnAddr)
- }
- if expectFail {
- t.Fatalf("testing %s not unsuccessful as expected", clnAddr)
- }
- conn := netConn.(*tls.Conn)
- err = conn.Handshake()
- if err != nil {
- t.Fatal(err)
- }
- connState := conn.ConnectionState()
- switch {
- case connState.Version != tls.VersionTLS12 && connState.Version != tls.VersionTLS13:
- t.Fatal("version mismatch")
- case connState.NegotiatedProtocol != consts.RequestForwardingALPN || !connState.NegotiatedProtocolIsMutual:
- t.Fatal("bad protocol negotiation")
- }
- t.Logf("testing %s successful", clnAddr)
- }
- }
-
- time.Sleep(clusterTestPausePeriod)
- checkListenersFunc(false)
-
- err := cores[0].StepDown(context.Background(), &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "sys/step-down",
- ClientToken: cluster.RootToken,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // StepDown doesn't wait during actual preSeal so give time for listeners
- // to close
- time.Sleep(clusterTestPausePeriod)
- checkListenersFunc(true)
-
- // After this period it should be active again
- TestWaitActive(t, cores[0].Core)
- cores[0].getClusterListener().AddClient(consts.RequestForwardingALPN, &requestForwardingClusterClient{cores[0].Core})
- checkListenersFunc(false)
-
- err = cores[0].Core.Seal(cluster.RootToken)
- if err != nil {
- t.Fatal(err)
- }
- time.Sleep(clusterTestPausePeriod)
- // After sealing it should be inactive again
- checkListenersFunc(true)
-}
-
-func TestCluster_ForwardRequests(t *testing.T) {
- // Make this nicer for tests
- manualStepDownSleepPeriod = 5 * time.Second
-
- t.Run("tcpLayer", func(t *testing.T) {
- testCluster_ForwardRequestsCommon(t, nil)
- })
-
- t.Run("inmemLayer", func(t *testing.T) {
- // Run again with in-memory network
- inmemCluster, err := cluster.NewInmemLayerCluster("inmem-cluster", 3, log.New(&log.LoggerOptions{
- Mutex: &sync.Mutex{},
- Level: log.Trace,
- Name: "inmem-cluster",
- }))
- if err != nil {
- t.Fatal(err)
- }
-
- testCluster_ForwardRequestsCommon(t, &TestClusterOptions{
- ClusterLayers: inmemCluster,
- })
- })
-}
-
-func testCluster_ForwardRequestsCommon(t *testing.T, clusterOpts *TestClusterOptions) {
- cluster := NewTestCluster(t, nil, clusterOpts)
- cores := cluster.Cores
- cores[0].Handler.(*http.ServeMux).HandleFunc("/core1", func(w http.ResponseWriter, req *http.Request) {
- w.Header().Add("Content-Type", "application/json")
- w.WriteHeader(201)
- w.Write([]byte("core1"))
- })
- cores[1].Handler.(*http.ServeMux).HandleFunc("/core2", func(w http.ResponseWriter, req *http.Request) {
- w.Header().Add("Content-Type", "application/json")
- w.WriteHeader(202)
- w.Write([]byte("core2"))
- })
- cores[2].Handler.(*http.ServeMux).HandleFunc("/core3", func(w http.ResponseWriter, req *http.Request) {
- w.Header().Add("Content-Type", "application/json")
- w.WriteHeader(203)
- w.Write([]byte("core3"))
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- root := cluster.RootToken
-
- // Wait for core to become active
- TestWaitActiveForwardingReady(t, cores[0].Core)
-
- // Test forwarding a request. Since we're going directly from core to core
- // with no fallback we know that if it worked, request handling is working
- testCluster_ForwardRequests(t, cores[1], root, "core1")
- testCluster_ForwardRequests(t, cores[2], root, "core1")
-
- //
- // Now we do a bunch of round-robining. The point is to make sure that as
- // nodes come and go, we can always successfully forward to the active
- // node.
- //
-
- // Ensure active core is cores[1] and test
- testCluster_Forwarding(t, cluster, 0, 1, root, "core2")
-
- // Ensure active core is cores[2] and test
- testCluster_Forwarding(t, cluster, 1, 2, root, "core3")
-
- // Ensure active core is cores[0] and test
- testCluster_Forwarding(t, cluster, 2, 0, root, "core1")
-
- // Ensure active core is cores[1] and test
- testCluster_Forwarding(t, cluster, 0, 1, root, "core2")
-
- // Ensure active core is cores[2] and test
- testCluster_Forwarding(t, cluster, 1, 2, root, "core3")
-}
-
-func testCluster_Forwarding(t *testing.T, cluster *TestCluster, oldLeaderCoreIdx, newLeaderCoreIdx int, rootToken, remoteCoreID string) {
- t.Logf("new leaderidx will be %d, stepping down other cores to make it so", newLeaderCoreIdx)
- err := cluster.Cores[oldLeaderCoreIdx].StepDown(context.Background(), &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "sys/step-down",
- ClientToken: rootToken,
- })
- if err != nil {
- t.Fatal(err)
- }
- time.Sleep(clusterTestPausePeriod)
-
- for i := 0; i < 3; i++ {
- if i != oldLeaderCoreIdx && i != newLeaderCoreIdx {
- _ = cluster.Cores[i].StepDown(context.Background(), &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "sys/step-down",
- ClientToken: rootToken,
- })
- time.Sleep(clusterTestPausePeriod)
- }
- }
-
- TestWaitActiveForwardingReady(t, cluster.Cores[newLeaderCoreIdx].Core)
-
- deadline := time.Now().Add(5 * time.Second)
- var ready int
- for time.Now().Before(deadline) {
- for i := 0; i < 3; i++ {
- if i != newLeaderCoreIdx {
- leaderParams := cluster.Cores[i].clusterLeaderParams.Load().(*ClusterLeaderParams)
- if leaderParams != nil && leaderParams.LeaderClusterAddr == cluster.Cores[newLeaderCoreIdx].ClusterAddr() {
- ready++
- }
- }
- }
- if ready == 2 {
- break
- }
- ready = 0
-
- time.Sleep(100 * time.Millisecond)
- }
- if ready != 2 {
- t.Fatal("standbys have not discovered the new active node in time")
- }
-
- for i := 0; i < 3; i++ {
- if i != newLeaderCoreIdx {
- testCluster_ForwardRequests(t, cluster.Cores[i], rootToken, remoteCoreID)
- }
- }
-}
-
-func testCluster_ForwardRequests(t *testing.T, c *TestClusterCore, rootToken, remoteCoreID string) {
- t.Helper()
-
- standby, err := c.Standby()
- if err != nil {
- t.Fatal(err)
- }
- if !standby {
- t.Fatal("expected core to be standby")
- }
-
- // We need to call Leader as that refreshes the connection info
- isLeader, _, _, err := c.Leader()
- if err != nil {
- t.Fatal(err)
- }
- if isLeader {
- t.Fatal("core should not be leader")
- }
- corehelpers.RetryUntil(t, 5*time.Second, func() error {
- state := c.ActiveNodeReplicationState()
- if state == 0 {
- return fmt.Errorf("heartbeats have not yet returned a valid active node replication state: %d", state)
- }
- return nil
- })
-
- bodBuf := bytes.NewReader([]byte(`{ "foo": "bar", "zip": "zap" }`))
- req, err := http.NewRequest("PUT", "https://pushit.real.good:9281/"+remoteCoreID, bodBuf)
- if err != nil {
- t.Fatal(err)
- }
- req.Header.Add(consts.AuthHeaderName, rootToken)
- req = req.WithContext(context.WithValue(req.Context(), "original_request_path", req.URL.Path))
-
- statusCode, header, respBytes, err := c.ForwardRequest(req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if header == nil {
- t.Fatal("err: expected at least a content-type header")
- }
- if header.Get("Content-Type") != "application/json" {
- t.Fatalf("bad content-type: %s", header.Get("Content-Type"))
- }
-
- body := string(respBytes)
-
- if body != remoteCoreID {
- t.Fatalf("expected %s, got %s", remoteCoreID, body)
- }
- switch body {
- case "core1":
- if statusCode != 201 {
- t.Fatal("bad response")
- }
- case "core2":
- if statusCode != 202 {
- t.Fatal("bad response")
- }
- case "core3":
- if statusCode != 203 {
- t.Fatal("bad response")
- }
- }
-}
diff --git a/vault/core_metrics_test.go b/vault/core_metrics_test.go
deleted file mode 100644
index eede072fb..000000000
--- a/vault/core_metrics_test.go
+++ /dev/null
@@ -1,352 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "errors"
- "sort"
- "strings"
- "testing"
- "time"
-
- "github.com/armon/go-metrics"
- logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestCoreMetrics_KvSecretGauge(t *testing.T) {
- // Use the real KV implementation instead of Passthrough
- AddTestLogicalBackend("kv", logicalKv.Factory)
- // Clean up for the next test-- is there a better way?
- defer func() {
- delete(testLogicalBackends, "kv")
- }()
- core, _, root := TestCoreUnsealed(t)
-
- testMounts := []struct {
- Path string
- Type string
- Version string
- ExpectedCount int
- }{
- {"secret/", "kv", "2", 0},
- {"secret1/", "kv", "1", 3},
- {"secret2/", "kv", "1", 0},
- {"secret3/", "kv", "2", 4},
- {"prefix/secret3/", "kv", "2", 0},
- {"prefix/secret4/", "kv", "2", 5},
- {"generic/", "generic", "1", 3},
- }
- ctx := namespace.RootContext(nil)
-
- // skip 0, secret/ is already mounted
- for _, tm := range testMounts[1:] {
- me := &MountEntry{
- Table: mountTableType,
- Path: sanitizePath(tm.Path),
- Type: tm.Type,
- Options: map[string]string{"version": tm.Version},
- }
- err := core.mount(ctx, me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- }
-
- v1secrets := []string{
- "secret1/a", // 3
- "secret1/b",
- "secret1/c/d",
- "generic/a",
- "generic/b",
- "generic/c",
- }
- v2secrets := []string{
- "secret3/data/a", // 4
- "secret3/data/b",
- "secret3/data/c/d",
- "secret3/data/c/e",
- "prefix/secret4/data/a/secret", // 5
- "prefix/secret4/data/a/secret2",
- "prefix/secret4/data/a/b/c/secret",
- "prefix/secret4/data/a/b/c/secret2",
- "prefix/secret4/data/a/b/c/d/secret3",
- }
- for _, p := range v1secrets {
- req := logical.TestRequest(t, logical.CreateOperation, p)
- req.Data["foo"] = "bar"
- req.ClientToken = root
- resp, err := core.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
- }
- for _, p := range v2secrets {
- for i := 0; i < 50; i++ {
- req := logical.TestRequest(t, logical.CreateOperation, p)
- req.Data["data"] = map[string]interface{}{"foo": "bar"}
- req.ClientToken = root
- resp, err := core.HandleRequest(ctx, req)
- if err != nil {
- if errors.Is(err, logical.ErrInvalidRequest) {
- // Handle scenario where KVv2 upgrade is ongoing
- time.Sleep(100 * time.Millisecond)
- continue
- }
- t.Fatalf("err: %v", err)
- }
- if resp.Error() != nil {
- t.Fatalf("bad: %#v", resp)
- }
- break
- }
- }
-
- values, err := core.kvSecretGaugeCollector(ctx)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(values) != len(testMounts) {
- t.Errorf("Got %v values but expected %v mounts", len(values), len(testMounts))
- }
-
- for _, glv := range values {
- mountPoint := ""
- for _, l := range glv.Labels {
- if l.Name == "mount_point" {
- mountPoint = l.Value
- } else if l.Name == "namespace" {
- if l.Value != "root" {
- t.Errorf("Namespace is %v, not root", l.Value)
- }
- } else {
- t.Errorf("Unexpected label %v", l.Name)
- }
- }
- if mountPoint == "" {
- t.Errorf("No mount point in labels %v", glv.Labels)
- continue
- }
- found := false
- for _, tm := range testMounts {
- if tm.Path == mountPoint {
- found = true
- if glv.Value != float32(tm.ExpectedCount) {
- t.Errorf("Mount %v reported %v, not %v",
- tm.Path, glv.Value, tm.ExpectedCount)
- }
- break
- }
- }
- if !found {
- t.Errorf("Unexpected mount point %v", mountPoint)
- }
- }
-}
-
-func TestCoreMetrics_KvSecretGauge_BadPath(t *testing.T) {
- // Use the real KV implementation instead of Passthrough
- AddTestLogicalBackend("kv", logicalKv.Factory)
- // Clean up for the next test.
- defer func() {
- delete(testLogicalBackends, "kv")
- }()
- core, _, _ := TestCoreUnsealed(t)
-
- me := &MountEntry{
- Table: mountTableType,
- Path: sanitizePath("kv1"),
- Type: "kv",
- Options: map[string]string{"version": "1"},
- }
- ctx := namespace.RootContext(nil)
- err := core.mount(ctx, me)
- if err != nil {
- t.Fatalf("mount error: %v", err)
- }
-
- // I don't think there's any remaining way to create a zero-length
- // key via the API, so we'll fake it by talking to the storage layer directly.
- fake_entry := &logical.StorageEntry{
- Key: "logical/" + me.UUID + "/foo/",
- Value: []byte{1},
- }
- err = core.barrier.Put(ctx, fake_entry)
- if err != nil {
- t.Fatalf("put error: %v", err)
- }
-
- values, err := core.kvSecretGaugeCollector(ctx)
- if err != nil {
- t.Fatalf("collector error: %v", err)
- }
- t.Logf("Values: %v", values)
- found := false
- var count float32 = -1
- for _, glv := range values {
- for _, l := range glv.Labels {
- if l.Name == "mount_point" && l.Value == "kv1/" {
- found = true
- count = glv.Value
- break
- }
- }
- }
- if found {
- if count != 1.0 {
- t.Errorf("bad secret count for kv1/")
- }
- } else {
- t.Errorf("no secret count for kv1/")
- }
-}
-
-func TestCoreMetrics_KvSecretGaugeError(t *testing.T) {
- core, _, _, sink := TestCoreUnsealedWithMetrics(t)
- ctx := namespace.RootContext(nil)
-
- badKvMount := &kvMount{
- Namespace: namespace.RootNamespace,
- MountPoint: "bad/path",
- Version: "1",
- NumSecrets: 0,
- }
-
- core.walkKvMountSecrets(ctx, badKvMount)
-
- intervals := sink.Data()
- // Test crossed an interval boundary, don't try to deal with it.
- if len(intervals) > 1 {
- t.Skip("Detected interval crossing.")
- }
-
- // Should be an error
- keyPrefix := "metrics.collection.error"
- var counter *metrics.SampledValue = nil
-
- for _, c := range intervals[0].Counters {
- if strings.HasPrefix(c.Name, keyPrefix) {
- counter = &c
- break
- }
- }
- if counter == nil {
- t.Fatal("No metrics.collection.error counter found.")
- }
- if counter.Count != 1 {
- t.Errorf("Counter number of samples %v is not 1.", counter.Count)
- }
-}
-
-func metricLabelsMatch(t *testing.T, actual []metrics.Label, expected map[string]string) {
- t.Helper()
-
- if len(actual) != len(expected) {
- t.Errorf("Expected %v labels, got %v: %v", len(expected), len(actual), actual)
- }
-
- for _, l := range actual {
- if v, ok := expected[l.Name]; ok {
- if v != l.Value {
- t.Errorf("Mismatched value %v=%v, expected %v", l.Name, l.Value, v)
- }
- } else {
- t.Errorf("Unexpected label %v", l.Name)
- }
- }
-}
-
-func TestCoreMetrics_EntityGauges(t *testing.T) {
- ctx := namespace.RootContext(nil)
- is, ghAccessor, upAccessor, core := testIdentityStoreWithGithubUserpassAuth(ctx, t)
-
- // Create an entity
- alias1 := &logical.Alias{
- MountType: "github",
- MountAccessor: ghAccessor,
- Name: "githubuser",
- }
-
- entity, _, err := is.CreateOrFetchEntity(ctx, alias1)
- if err != nil {
- t.Fatal(err)
- }
-
- // Create a second alias for the same entity
- registerReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity-alias",
- Data: map[string]interface{}{
- "name": "userpassuser",
- "canonical_id": entity.ID,
- "mount_accessor": upAccessor,
- },
- }
- resp, err := is.HandleRequest(ctx, registerReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- glv, err := core.entityGaugeCollector(ctx)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if len(glv) != 1 {
- t.Fatalf("Wrong number of gauges %v, expected %v", len(glv), 1)
- }
-
- if glv[0].Value != 1.0 {
- t.Errorf("Entity count %v, expected %v", glv[0].Value, 1.0)
- }
-
- metricLabelsMatch(t, glv[0].Labels,
- map[string]string{
- "namespace": "root",
- })
-
- glv, err = core.entityGaugeCollectorByMount(ctx)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if len(glv) != 2 {
- t.Fatalf("Wrong number of gauges %v, expected %v", len(glv), 1)
- }
-
- if glv[0].Value != 1.0 {
- t.Errorf("Alias count %v, expected %v", glv[0].Value, 1.0)
- }
-
- if glv[1].Value != 1.0 {
- t.Errorf("Alias count %v, expected %v", glv[0].Value, 1.0)
- }
-
- // Sort both metrics.Label slices by Name, causing the Label
- // with Name auth_method to be first in both arrays
- sort.Slice(glv[0].Labels, func(i, j int) bool { return glv[0].Labels[i].Name < glv[0].Labels[j].Name })
- sort.Slice(glv[1].Labels, func(i, j int) bool { return glv[1].Labels[i].Name < glv[1].Labels[j].Name })
-
- // Sort the GaugeLabelValues slice by the Value of the first metric,
- // in this case auth_method, in each metrics.Label slice
- sort.Slice(glv, func(i, j int) bool { return glv[i].Labels[0].Value < glv[j].Labels[0].Value })
-
- metricLabelsMatch(t, glv[0].Labels,
- map[string]string{
- "namespace": "root",
- "auth_method": "github",
- "mount_point": "auth/github/",
- })
-
- metricLabelsMatch(t, glv[1].Labels,
- map[string]string{
- "namespace": "root",
- "auth_method": "userpass",
- "mount_point": "auth/userpass/",
- })
-}
diff --git a/vault/core_test.go b/vault/core_test.go
deleted file mode 100644
index 8bca07971..000000000
--- a/vault/core_test.go
+++ /dev/null
@@ -1,3372 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "fmt"
- "reflect"
- "strings"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/command/server"
-
- logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
- logicalDb "github.com/hashicorp/vault/builtin/logical/database"
-
- "github.com/hashicorp/vault/builtin/plugin"
-
- "github.com/hashicorp/vault/builtin/audit/syslog"
-
- "github.com/hashicorp/vault/builtin/audit/file"
- "github.com/hashicorp/vault/builtin/audit/socket"
- "github.com/stretchr/testify/require"
-
- "github.com/go-test/deep"
- "github.com/hashicorp/errwrap"
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/go-secure-stdlib/strutil"
- "github.com/hashicorp/go-uuid"
- "github.com/hashicorp/vault/audit"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/helper/testhelpers/corehelpers"
- "github.com/hashicorp/vault/internalshared/configutil"
- "github.com/hashicorp/vault/sdk/helper/consts"
- "github.com/hashicorp/vault/sdk/helper/jsonutil"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/sdk/physical"
- "github.com/hashicorp/vault/sdk/physical/inmem"
- "github.com/hashicorp/vault/version"
- "github.com/sasha-s/go-deadlock"
-)
-
-// invalidKey is used to test Unseal
-var invalidKey = []byte("abcdefghijklmnopqrstuvwxyz")[:17]
-
-// TestNewCore_configureAuditBackends ensures that we are able to configure the
-// supplied audit backends when getting a NewCore.
-func TestNewCore_configureAuditBackends(t *testing.T) {
- t.Parallel()
-
- tests := map[string]struct {
- backends map[string]audit.Factory
- }{
- "none": {
- backends: nil,
- },
- "file": {
- backends: map[string]audit.Factory{
- "file": file.Factory,
- },
- },
- "socket": {
- backends: map[string]audit.Factory{
- "socket": socket.Factory,
- },
- },
- "syslog": {
- backends: map[string]audit.Factory{
- "syslog": syslog.Factory,
- },
- },
- "all": {
- backends: map[string]audit.Factory{
- "file": file.Factory,
- "socket": socket.Factory,
- "syslog": syslog.Factory,
- },
- },
- }
-
- for name, tc := range tests {
- name := name
- tc := tc
- t.Run(name, func(t *testing.T) {
- t.Parallel()
-
- core := &Core{}
- require.Len(t, core.auditBackends, 0)
- core.configureAuditBackends(tc.backends)
- require.Len(t, core.auditBackends, len(tc.backends))
- for k := range tc.backends {
- require.Contains(t, core.auditBackends, k)
- }
- })
- }
-}
-
-// TestNewCore_configureCredentialsBackends ensures that we are able to configure the
-// supplied credential backends, in addition to defaults, when getting a NewCore.
-func TestNewCore_configureCredentialsBackends(t *testing.T) {
- t.Parallel()
-
- tests := map[string]struct {
- backends map[string]logical.Factory
- }{
- "none": {
- backends: nil,
- },
- "plugin": {
- backends: map[string]logical.Factory{
- "plugin": plugin.Factory,
- },
- },
- }
-
- for name, tc := range tests {
- name := name
- tc := tc
- t.Run(name, func(t *testing.T) {
- t.Parallel()
-
- core := &Core{}
- require.Len(t, core.credentialBackends, 0)
- core.configureCredentialsBackends(tc.backends, corehelpers.NewTestLogger(t))
- require.GreaterOrEqual(t, len(core.credentialBackends), len(tc.backends)+1) // token + ent
- for k := range tc.backends {
- require.Contains(t, core.credentialBackends, k)
- }
- })
- }
-}
-
-// TestNewCore_configureLogicalBackends ensures that we are able to configure the
-// supplied logical backends, in addition to defaults, when getting a NewCore.
-func TestNewCore_configureLogicalBackends(t *testing.T) {
- t.Parallel()
-
- // configureLogicalBackends will add some default backends for us:
- // cubbyhole
- // identity
- // kv
- // system
- // In addition Enterprise versions of Vault may add additional engines.
-
- tests := map[string]struct {
- backends map[string]logical.Factory
- adminNamespacePath string
- expectedNonEntBackends int
- }{
- "none": {
- backends: nil,
- expectedNonEntBackends: 0,
- },
- "database": {
- backends: map[string]logical.Factory{
- "database": logicalDb.Factory,
- },
- adminNamespacePath: "foo",
- expectedNonEntBackends: 5, // database + defaults
- },
- "kv": {
- backends: map[string]logical.Factory{
- "kv": logicalKv.Factory,
- },
- adminNamespacePath: "foo",
- expectedNonEntBackends: 4, // kv + defaults (kv is a default)
- },
- "plugin": {
- backends: map[string]logical.Factory{
- "plugin": plugin.Factory,
- },
- adminNamespacePath: "foo",
- expectedNonEntBackends: 5, // plugin + defaults
- },
- "all": {
- backends: map[string]logical.Factory{
- "database": logicalDb.Factory,
- "kv": logicalKv.Factory,
- "plugin": plugin.Factory,
- },
- adminNamespacePath: "foo",
- expectedNonEntBackends: 6, // database, plugin + defaults
- },
- }
-
- for name, tc := range tests {
- name := name
- tc := tc
- t.Run(name, func(t *testing.T) {
- t.Parallel()
-
- core := &Core{}
- require.Len(t, core.logicalBackends, 0)
- core.configureLogicalBackends(tc.backends, corehelpers.NewTestLogger(t), tc.adminNamespacePath)
- require.GreaterOrEqual(t, len(core.logicalBackends), tc.expectedNonEntBackends)
- require.Contains(t, core.logicalBackends, mountTypeKV)
- require.Contains(t, core.logicalBackends, mountTypeCubbyhole)
- require.Contains(t, core.logicalBackends, mountTypeSystem)
- require.Contains(t, core.logicalBackends, mountTypeIdentity)
- for k := range tc.backends {
- require.Contains(t, core.logicalBackends, k)
- }
- })
- }
-}
-
-// TestNewCore_configureLogRequestLevel ensures that we are able to configure the
-// supplied logging level when getting a NewCore.
-func TestNewCore_configureLogRequestLevel(t *testing.T) {
- t.Parallel()
-
- tests := map[string]struct {
- level string
- expectedLevel log.Level
- }{
- "none": {
- level: "",
- expectedLevel: log.NoLevel,
- },
- "trace": {
- level: "trace",
- expectedLevel: log.Trace,
- },
- "debug": {
- level: "debug",
- expectedLevel: log.Debug,
- },
- "info": {
- level: "info",
- expectedLevel: log.Info,
- },
- "warn": {
- level: "warn",
- expectedLevel: log.Warn,
- },
- "error": {
- level: "error",
- expectedLevel: log.Error,
- },
- "bad": {
- level: "foo",
- expectedLevel: log.NoLevel,
- },
- }
-
- for name, tc := range tests {
- name := name
- tc := tc
- t.Run(name, func(t *testing.T) {
- t.Parallel()
-
- // We need to supply a logger, as configureLogRequestsLevel emits
- // warnings to the logs in certain circumstances.
- core := &Core{
- logger: corehelpers.NewTestLogger(t),
- }
- core.configureLogRequestsLevel(tc.level)
- require.Equal(t, tc.expectedLevel, log.Level(core.logRequestsLevel.Load()))
- })
- }
-}
-
-// TestNewCore_configureListeners tests that we are able to configure listeners
-// on a NewCore via config.
-func TestNewCore_configureListeners(t *testing.T) {
- // We would usually expect CoreConfig to come from server.NewConfig().
- // However, we want to fiddle to give us some granular control over the config.
- tests := map[string]struct {
- config *CoreConfig
- expectedListeners []*ListenerCustomHeaders
- }{
- "nil-listeners": {
- config: &CoreConfig{
- RawConfig: &server.Config{
- SharedConfig: &configutil.SharedConfig{},
- },
- },
- expectedListeners: nil,
- },
- "listeners-empty": {
- config: &CoreConfig{
- RawConfig: &server.Config{
- SharedConfig: &configutil.SharedConfig{
- Listeners: []*configutil.Listener{},
- },
- },
- },
- expectedListeners: nil,
- },
- "listeners-some": {
- config: &CoreConfig{
- RawConfig: &server.Config{
- SharedConfig: &configutil.SharedConfig{
- Listeners: []*configutil.Listener{
- {Address: "foo"},
- },
- },
- },
- },
- expectedListeners: []*ListenerCustomHeaders{
- {Address: "foo"},
- },
- },
- }
-
- for name, tc := range tests {
- name := name
- tc := tc
- t.Run(name, func(t *testing.T) {
- t.Parallel()
-
- // We need to init some values ourselves, usually CreateCore does this for us.
- logger := corehelpers.NewTestLogger(t)
- backend, err := inmem.NewInmem(nil, logger)
- require.NoError(t, err)
- storage := &logical.InmemStorage{}
- core := &Core{
- clusterListener: new(atomic.Value),
- customListenerHeader: new(atomic.Value),
- uiConfig: NewUIConfig(false, backend, storage),
- }
-
- err = core.configureListeners(tc.config)
- require.NoError(t, err)
- switch tc.expectedListeners {
- case nil:
- require.Nil(t, core.customListenerHeader.Load())
- default:
- for i, v := range core.customListenerHeader.Load().([]*ListenerCustomHeaders) {
- require.Equal(t, v.Address, tc.config.RawConfig.Listeners[i].Address)
- }
- }
- })
- }
-}
-
-func TestNewCore_badRedirectAddr(t *testing.T) {
- logger = logging.NewVaultLogger(log.Trace)
-
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
-
- conf := &CoreConfig{
- RedirectAddr: "127.0.0.1:8200",
- Physical: inm,
- DisableMlock: true,
- }
- _, err = NewCore(conf)
- if err == nil {
- t.Fatal("should error")
- }
-}
-
-func TestSealConfig_Invalid(t *testing.T) {
- s := &SealConfig{
- SecretShares: 2,
- SecretThreshold: 1,
- }
- err := s.Validate()
- if err == nil {
- t.Fatalf("expected err")
- }
-}
-
-// TestCore_HasVaultVersion checks that versionHistory is correct and initialized
-// after a core has been unsealed.
-func TestCore_HasVaultVersion(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- if c.versionHistory == nil {
- t.Fatalf("Version timestamps for core were not initialized for a new core")
- }
- versionEntry, ok := c.versionHistory[version.Version]
- if !ok {
- t.Fatalf("%s upgrade time not found", version.Version)
- }
-
- upgradeTime := versionEntry.TimestampInstalled
-
- if upgradeTime.After(time.Now()) || upgradeTime.Before(time.Now().Add(-1*time.Hour)) {
- t.Fatalf("upgrade time isn't within reasonable bounds of new core initialization. " +
- fmt.Sprintf("time is: %+v, upgrade time is %+v", time.Now(), upgradeTime))
- }
-}
-
-func TestCore_Unseal_MultiShare(t *testing.T) {
- c := TestCore(t)
-
- _, err := TestCoreUnseal(c, invalidKey)
- if err != ErrNotInit {
- t.Fatalf("err: %v", err)
- }
-
- sealConf := &SealConfig{
- SecretShares: 5,
- SecretThreshold: 3,
- }
- res, err := c.Initialize(namespace.RootContext(nil), &InitParams{
- BarrierConfig: sealConf,
- RecoveryConfig: nil,
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if !c.Sealed() {
- t.Fatalf("should be sealed")
- }
-
- if prog, _ := c.SecretProgress(true); prog != 0 {
- t.Fatalf("bad progress: %d", prog)
- }
-
- for i := 0; i < 5; i++ {
- unseal, err := TestCoreUnseal(c, res.SecretShares[i])
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Ignore redundant
- _, err = TestCoreUnseal(c, res.SecretShares[i])
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i >= 2 {
- if !unseal {
- t.Fatalf("should be unsealed")
- }
- if prog, _ := c.SecretProgress(true); prog != 0 {
- t.Fatalf("bad progress: %d", prog)
- }
- } else {
- if unseal {
- t.Fatalf("should not be unsealed")
- }
- if prog, _ := c.SecretProgress(true); prog != i+1 {
- t.Fatalf("bad progress: %d", prog)
- }
- }
- }
-
- if c.Sealed() {
- t.Fatalf("should not be sealed")
- }
-
- err = c.Seal(res.RootToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Ignore redundant
- err = c.Seal(res.RootToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if !c.Sealed() {
- t.Fatalf("should be sealed")
- }
-}
-
-// TestCore_UseSSCTokenToggleOn will check that the root SSC
-// token can be used even when disableSSCTokens is toggled on
-func TestCore_UseSSCTokenToggleOn(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- c.disableSSCTokens = true
- req := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/test",
- Data: map[string]interface{}{
- "foo": "bar",
- "lease": "1h",
- },
- ClientToken: root,
- }
- ctx := namespace.RootContext(nil)
- resp, err := c.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read the key
- req.Operation = logical.ReadOperation
- req.Data = nil
- err = c.PopulateTokenEntry(ctx, req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = c.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Data == nil {
- t.Fatalf("bad: %#v", resp)
- }
- if resp.Secret.TTL != time.Hour {
- t.Fatalf("bad: %#v", resp.Secret)
- }
- if resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp.Secret)
- }
- if resp.Data["foo"] != "bar" {
- t.Fatalf("bad: %#v", resp.Data)
- }
-}
-
-// TestCore_UseNonSSCTokenToggleOff will check that the root
-// non-SSC token can be used even when disableSSCTokens is toggled
-// off.
-func TestCore_UseNonSSCTokenToggleOff(t *testing.T) {
- coreConfig := &CoreConfig{
- DisableSSCTokens: true,
- }
- c, _, root := TestCoreUnsealedWithConfig(t, coreConfig)
- if len(root) > TokenLength+OldTokenPrefixLength || !strings.HasPrefix(root, consts.LegacyServiceTokenPrefix) {
- t.Fatalf("token is not an old token type: %s, %d", root, len(root))
- }
- c.disableSSCTokens = false
- req := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/test",
- Data: map[string]interface{}{
- "foo": "bar",
- "lease": "1h",
- },
- ClientToken: root,
- }
- ctx := namespace.RootContext(nil)
- resp, err := c.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read the key
- req.Operation = logical.ReadOperation
- req.Data = nil
- err = c.PopulateTokenEntry(ctx, req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = c.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Data == nil {
- t.Fatalf("bad: %#v", resp)
- }
- if resp.Secret.TTL != time.Hour {
- t.Fatalf("bad: %#v", resp.Secret)
- }
- if resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp.Secret)
- }
- if resp.Data["foo"] != "bar" {
- t.Fatalf("bad: %#v", resp.Data)
- }
-}
-
-func TestCore_Unseal_Single(t *testing.T) {
- c := TestCore(t)
-
- _, err := TestCoreUnseal(c, invalidKey)
- if err != ErrNotInit {
- t.Fatalf("err: %v", err)
- }
-
- sealConf := &SealConfig{
- SecretShares: 1,
- SecretThreshold: 1,
- }
- res, err := c.Initialize(namespace.RootContext(nil), &InitParams{
- BarrierConfig: sealConf,
- RecoveryConfig: nil,
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if !c.Sealed() {
- t.Fatalf("should be sealed")
- }
-
- if prog, _ := c.SecretProgress(true); prog != 0 {
- t.Fatalf("bad progress: %d", prog)
- }
-
- unseal, err := TestCoreUnseal(c, res.SecretShares[0])
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if !unseal {
- t.Fatalf("should be unsealed")
- }
- if prog, _ := c.SecretProgress(true); prog != 0 {
- t.Fatalf("bad progress: %d", prog)
- }
-
- if c.Sealed() {
- t.Fatalf("should not be sealed")
- }
-}
-
-func TestCore_Route_Sealed(t *testing.T) {
- c := TestCore(t)
- sealConf := &SealConfig{
- SecretShares: 1,
- SecretThreshold: 1,
- }
-
- ctx := namespace.RootContext(nil)
-
- // Should not route anything
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "sys/mounts",
- }
- _, err := c.HandleRequest(ctx, req)
- if err != consts.ErrSealed {
- t.Fatalf("err: %v", err)
- }
-
- res, err := c.Initialize(ctx, &InitParams{
- BarrierConfig: sealConf,
- RecoveryConfig: nil,
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- unseal, err := TestCoreUnseal(c, res.SecretShares[0])
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !unseal {
- t.Fatalf("should be unsealed")
- }
-
- // Should not error after unseal
- req.ClientToken = res.RootToken
- _, err = c.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-}
-
-// Attempt to unseal after doing a first seal
-func TestCore_SealUnseal(t *testing.T) {
- c, keys, root := TestCoreUnsealed(t)
- if err := c.Seal(root); err != nil {
- t.Fatalf("err: %v", err)
- }
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("err: should be unsealed")
- }
- }
-}
-
-// TestCore_RunLockedUserUpdatesForStaleEntry tests that stale locked user entries
-// get deleted upon unseal
-func TestCore_RunLockedUserUpdatesForStaleEntry(t *testing.T) {
- core, keys, root := TestCoreUnsealed(t)
- storageUserLockoutPath := fmt.Sprintf(coreLockedUsersPath + "ns1/mountAccessor1/aliasName1")
-
- // cleanup
- defer core.barrier.Delete(context.Background(), storageUserLockoutPath)
-
- // create invalid entry in storage to test stale entries get deleted on unseal
- // last failed login time for this path is 1970-01-01 00:00:00 +0000 UTC
- // since user lockout configurations are not configured, lockout duration will
- // be set to default (15m) internally
- compressedBytes, err := jsonutil.EncodeJSONAndCompress(int(time.Unix(0, 0).Unix()), nil)
- if err != nil {
- t.Fatal(err)
- }
-
- // Create an entry
- entry := &logical.StorageEntry{
- Key: storageUserLockoutPath,
- Value: compressedBytes,
- }
-
- // Write to the physical backend
- err = core.barrier.Put(context.Background(), entry)
- if err != nil {
- t.Fatalf("failed to write invalid locked user entry, err: %v", err)
- }
-
- // seal and unseal vault
- if err := core.Seal(root); err != nil {
- t.Fatalf("err: %v", err)
- }
- for i, key := range keys {
- unseal, err := TestCoreUnseal(core, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("err: should be unsealed")
- }
- }
-
- // locked user entry must be deleted upon unseal as it is stale
- lastFailedLoginRaw, err := core.barrier.Get(context.Background(), storageUserLockoutPath)
- if err != nil {
- t.Fatal(err)
- }
- if lastFailedLoginRaw != nil {
- t.Fatal("err: stale locked user entry exists")
- }
-}
-
-// TestCore_RunLockedUserUpdatesForValidEntry tests that valid locked user entries
-// do not get removed on unseal
-// Also tests that the userFailedLoginInfo map gets updated with correct information
-func TestCore_RunLockedUserUpdatesForValidEntry(t *testing.T) {
- core, keys, root := TestCoreUnsealed(t)
- storageUserLockoutPath := fmt.Sprintf(coreLockedUsersPath + "ns1/mountAccessor1/aliasName1")
-
- // cleanup
- defer core.barrier.Delete(context.Background(), storageUserLockoutPath)
-
- // create valid storage entry for locked user
- lastFailedLoginTime := int(time.Now().Unix())
-
- compressedBytes, err := jsonutil.EncodeJSONAndCompress(lastFailedLoginTime, nil)
- if err != nil {
- t.Fatal(err)
- }
-
- // Create an entry
- entry := &logical.StorageEntry{
- Key: storageUserLockoutPath,
- Value: compressedBytes,
- }
-
- // Write to the physical backend
- err = core.barrier.Put(context.Background(), entry)
- if err != nil {
- t.Fatalf("failed to write invalid locked user entry, err: %v", err)
- }
-
- // seal and unseal vault
- if err := core.Seal(root); err != nil {
- t.Fatalf("err: %v", err)
- }
- for i, key := range keys {
- unseal, err := TestCoreUnseal(core, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("err: should be unsealed")
- }
- }
-
- // locked user entry must exist as it is still valid
- existingEntry, err := core.barrier.Get(context.Background(), storageUserLockoutPath)
- if err != nil {
- t.Fatal(err)
- }
- if existingEntry == nil {
- t.Fatalf("err: entry must exist for locked user in storage")
- }
-
- // userFailedLoginInfo map should have the correct information for locked user
- loginUserInfoKey := FailedLoginUser{
- aliasName: "aliasName1",
- mountAccessor: "mountAccessor1",
- }
-
- failedLoginInfoFromMap := core.LocalGetUserFailedLoginInfo(context.Background(), loginUserInfoKey)
- if failedLoginInfoFromMap == nil {
- t.Fatalf("err: entry must exist for locked user in userFailedLoginInfo map")
- }
- if failedLoginInfoFromMap.lastFailedLoginTime != lastFailedLoginTime {
- t.Fatalf("err: incorrect failed login time information for locked user updated in userFailedLoginInfo map")
- }
- if int(failedLoginInfoFromMap.count) != configutil.UserLockoutThresholdDefault {
- t.Fatalf("err: incorrect failed login count information for locked user updated in userFailedLoginInfo map")
- }
-}
-
-// Attempt to shutdown after unseal
-func TestCore_Shutdown(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- if err := c.Shutdown(); err != nil {
- t.Fatalf("err: %v", err)
- }
- if !c.Sealed() {
- t.Fatal("wasn't sealed")
- }
-}
-
-// verify the channel returned by ShutdownDone is closed after Finalize
-func TestCore_ShutdownDone(t *testing.T) {
- c := TestCoreWithSealAndUINoCleanup(t, &CoreConfig{})
- testCoreUnsealed(t, c)
- doneCh := c.ShutdownDone()
- go func() {
- time.Sleep(100 * time.Millisecond)
- err := c.Shutdown()
- if err != nil {
- t.Fatal(err)
- }
- }()
-
- select {
- case <-doneCh:
- if !c.Sealed() {
- t.Fatalf("shutdown done called prematurely!")
- }
- case <-time.After(5 * time.Second):
- t.Fatalf("shutdown notification not received")
- }
-}
-
-// Attempt to seal bad token
-func TestCore_Seal_BadToken(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- if err := c.Seal("foo"); err == nil {
- t.Fatalf("err: %v", err)
- }
- if c.Sealed() {
- t.Fatal("was sealed")
- }
-}
-
-func TestCore_PreOneTen_BatchTokens(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
-
- // load up some versions and ensure that 1.9 is the most recent one by timestamp (even though this isn't realistic)
- upgradeTimePlusEpsilon := time.Now().UTC()
-
- versionEntries := []VaultVersion{
- {Version: "1.10.1", TimestampInstalled: upgradeTimePlusEpsilon.Add(-4 * time.Hour)},
- {Version: "1.9.2", TimestampInstalled: upgradeTimePlusEpsilon.Add(2 * time.Hour)},
- }
-
- for _, entry := range versionEntries {
- _, err := c.storeVersionEntry(context.Background(), &entry, false)
- if err != nil {
- t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error())
- }
- }
-
- err := c.loadVersionHistory(c.activeContext)
- if err != nil {
- t.Fatalf("failed to populate version history cache, err: %s", err.Error())
- }
-
- // double check that we're working with 1.9
- v, _, err := c.FindNewestVersionTimestamp()
- if err != nil {
- t.Fatal(err)
- }
- if v != "1.9.2" {
- t.Fatalf("expected 1.9.2, found: %s", v)
- }
-
- // generate a batch token
- te := &logical.TokenEntry{
- NumUses: 1,
- Policies: []string{"root"},
- NamespaceID: namespace.RootNamespaceID,
- Type: logical.TokenTypeBatch,
- }
- err = c.tokenStore.create(namespace.RootContext(nil), te)
- if err != nil {
- t.Fatal(err)
- }
-
- // verify it uses the legacy prefix
- if !strings.HasPrefix(te.ID, consts.LegacyBatchTokenPrefix) {
- t.Fatalf("expected 1.9 batch token IDs to start with b. but it didn't: %s", te.ID)
- }
-}
-
-func TestCore_OneTenPlus_BatchTokens(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
-
- // load up some versions and ensure that 1.10 is the most recent version
- upgradeTimePlusEpsilon := time.Now().UTC()
-
- versionEntries := []VaultVersion{
- {Version: "1.9.2", TimestampInstalled: upgradeTimePlusEpsilon.Add(-4 * time.Hour)},
- {Version: "1.10.1", TimestampInstalled: upgradeTimePlusEpsilon.Add(2 * time.Hour)},
- }
-
- for _, entry := range versionEntries {
- _, err := c.storeVersionEntry(context.Background(), &entry, false)
- if err != nil {
- t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error())
- }
- }
-
- err := c.loadVersionHistory(c.activeContext)
- if err != nil {
- t.Fatalf("failed to populate version history cache, err: %s", err.Error())
- }
-
- // double check that we're working with 1.10
- v, _, err := c.FindNewestVersionTimestamp()
- if err != nil {
- t.Fatal(err)
- }
- if v != "1.10.1" {
- t.Fatalf("expected 1.10.1, found: %s", v)
- }
-
- // generate a batch token
- te := &logical.TokenEntry{
- NumUses: 1,
- Policies: []string{"root"},
- NamespaceID: namespace.RootNamespaceID,
- Type: logical.TokenTypeBatch,
- }
- err = c.tokenStore.create(namespace.RootContext(nil), te)
- if err != nil {
- t.Fatal(err)
- }
-
- // verify it uses the legacy prefix
- if !strings.HasPrefix(te.ID, consts.BatchTokenPrefix) {
- t.Fatalf("expected 1.10 batch token IDs to start with hvb. but it didn't: %s", te.ID)
- }
-}
-
-// GH-3497
-func TestCore_Seal_SingleUse(t *testing.T) {
- c, keys, _ := TestCoreUnsealed(t)
- c.tokenStore.create(namespace.RootContext(nil), &logical.TokenEntry{
- ID: "foo",
- NumUses: 1,
- Policies: []string{"root"},
- NamespaceID: namespace.RootNamespaceID,
- })
- if err := c.Seal("foo"); err != nil {
- t.Fatalf("err: %v", err)
- }
- if !c.Sealed() {
- t.Fatal("not sealed")
- }
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("err: should be unsealed")
- }
- }
- if err := c.Seal("foo"); err == nil {
- t.Fatal("expected error from revoked token")
- }
- te, err := c.tokenStore.Lookup(namespace.RootContext(nil), "foo")
- if err != nil {
- t.Fatal(err)
- }
- if te != nil {
- t.Fatalf("expected nil token entry, got %#v", *te)
- }
-}
-
-// Ensure we get a LeaseID
-func TestCore_HandleRequest_Lease(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
-
- req := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/test",
- Data: map[string]interface{}{
- "foo": "bar",
- "lease": "1h",
- },
- ClientToken: root,
- }
- ctx := namespace.RootContext(nil)
- resp, err := c.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read the key
- req.Operation = logical.ReadOperation
- req.Data = nil
- err = c.PopulateTokenEntry(ctx, req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = c.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Data == nil {
- t.Fatalf("bad: %#v", resp)
- }
- if resp.Secret.TTL != time.Hour {
- t.Fatalf("bad: %#v", resp.Secret)
- }
- if resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp.Secret)
- }
- if resp.Data["foo"] != "bar" {
- t.Fatalf("bad: %#v", resp.Data)
- }
-}
-
-func TestCore_HandleRequest_Lease_MaxLength(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
-
- req := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/test",
- Data: map[string]interface{}{
- "foo": "bar",
- "lease": "1000h",
- },
- ClientToken: root,
- }
- ctx := namespace.RootContext(nil)
- resp, err := c.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read the key
- req.Operation = logical.ReadOperation
- req.Data = nil
- err = c.PopulateTokenEntry(ctx, req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = c.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Data == nil {
- t.Fatalf("bad: %#v", resp)
- }
- if resp.Secret.TTL != c.maxLeaseTTL {
- t.Fatalf("bad: %#v, %d", resp.Secret, c.maxLeaseTTL)
- }
- if resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp.Secret)
- }
- if resp.Data["foo"] != "bar" {
- t.Fatalf("bad: %#v", resp.Data)
- }
-}
-
-func TestCore_HandleRequest_Lease_DefaultLength(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
-
- req := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/test",
- Data: map[string]interface{}{
- "foo": "bar",
- "lease": "0h",
- },
- ClientToken: root,
- }
- ctx := namespace.RootContext(nil)
- resp, err := c.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read the key
- req.Operation = logical.ReadOperation
- req.Data = nil
- err = c.PopulateTokenEntry(ctx, req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = c.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Data == nil {
- t.Fatalf("bad: %#v", resp)
- }
- if resp.Secret.TTL != c.defaultLeaseTTL {
- t.Fatalf("bad: %d, %d", resp.Secret.TTL/time.Second, c.defaultLeaseTTL/time.Second)
- }
- if resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp.Secret)
- }
- if resp.Data["foo"] != "bar" {
- t.Fatalf("bad: %#v", resp.Data)
- }
-}
-
-func TestCore_HandleRequest_MissingToken(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
-
- req := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/test",
- Data: map[string]interface{}{
- "foo": "bar",
- "lease": "1h",
- },
- }
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || !errwrap.Contains(err, logical.ErrPermissionDenied.Error()) {
- t.Fatalf("err: %v", err)
- }
- if resp.Data["error"] != logical.ErrPermissionDenied.Error() {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestCore_HandleRequest_InvalidToken(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
-
- req := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/test",
- Data: map[string]interface{}{
- "foo": "bar",
- "lease": "1h",
- },
- ClientToken: "foobarbaz",
- }
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || !errwrap.Contains(err, logical.ErrPermissionDenied.Error()) {
- t.Fatalf("err: %v", err)
- }
- if resp.Data["error"] != "permission denied" {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-// Check that standard permissions work
-func TestCore_HandleRequest_NoSlash(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
-
- req := &logical.Request{
- Operation: logical.HelpOperation,
- Path: "secret",
- ClientToken: root,
- }
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v, resp: %v", err, resp)
- }
- if _, ok := resp.Data["help"]; !ok {
- t.Fatalf("resp: %v", resp)
- }
-}
-
-// Test a root path is denied if non-root
-func TestCore_HandleRequest_RootPath(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- testMakeServiceTokenViaCore(t, c, root, "child", "", []string{"test"})
-
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "sys/policy", // root protected!
- ClientToken: "child",
- }
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || !errwrap.Contains(err, logical.ErrPermissionDenied.Error()) {
- t.Fatalf("err: %v, resp: %v", err, resp)
- }
-}
-
-// Test a root path is allowed if non-root but with sudo
-func TestCore_HandleRequest_RootPath_WithSudo(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
-
- // Set the 'test' policy object to permit access to sys/policy
- req := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "sys/policy/test", // root protected!
- Data: map[string]interface{}{
- "rules": `path "sys/policy" { policy = "sudo" }`,
- },
- ClientToken: root,
- }
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil && (resp.IsError() || len(resp.Data) > 0) {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Child token (non-root) but with 'test' policy should have access
- testMakeServiceTokenViaCore(t, c, root, "child", "", []string{"test"})
- req = &logical.Request{
- Operation: logical.ReadOperation,
- Path: "sys/policy", // root protected!
- ClientToken: "child",
- }
- resp, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-// Check that standard permissions work
-func TestCore_HandleRequest_PermissionDenied(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- testMakeServiceTokenViaCore(t, c, root, "child", "", []string{"test"})
-
- req := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/test",
- Data: map[string]interface{}{
- "foo": "bar",
- "lease": "1h",
- },
- ClientToken: "child",
- }
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || !errwrap.Contains(err, logical.ErrPermissionDenied.Error()) {
- t.Fatalf("err: %v, resp: %v", err, resp)
- }
-}
-
-// Check that standard permissions work
-func TestCore_HandleRequest_PermissionAllowed(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- testMakeServiceTokenViaCore(t, c, root, "child", "", []string{"test"})
-
- // Set the 'test' policy object to permit access to secret/
- req := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "sys/policy/test",
- Data: map[string]interface{}{
- "rules": `path "secret/*" { policy = "write" }`,
- },
- ClientToken: root,
- }
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil && (resp.IsError() || len(resp.Data) > 0) {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Write should work now
- req = &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/test",
- Data: map[string]interface{}{
- "foo": "bar",
- "lease": "1h",
- },
- ClientToken: "child",
- }
- resp, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestCore_HandleRequest_NoClientToken(t *testing.T) {
- noop := &NoopBackend{
- Response: &logical.Response{},
- }
- c, _, root := TestCoreUnsealed(t)
- c.logicalBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return noop, nil
- }
-
- // Enable the logical backend
- req := logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo")
- req.Data["type"] = "noop"
- req.Data["description"] = "foo"
- req.ClientToken = root
- _, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Attempt to request with connection data
- req = &logical.Request{
- Path: "foo/login",
- }
- req.ClientToken = root
- if _, err := c.HandleRequest(namespace.RootContext(nil), req); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- ct := noop.Requests[0].ClientToken
- if ct == "" || ct == root {
- t.Fatalf("bad: %#v", noop.Requests)
- }
-}
-
-func TestCore_HandleRequest_ConnOnLogin(t *testing.T) {
- noop := &NoopBackend{
- Login: []string{"login"},
- Response: &logical.Response{},
- BackendType: logical.TypeCredential,
- }
- c, _, root := TestCoreUnsealed(t)
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return noop, nil
- }
-
- // Enable the credential backend
- req := logical.TestRequest(t, logical.UpdateOperation, "sys/auth/foo")
- req.Data["type"] = "noop"
- req.ClientToken = root
- _, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Attempt to request with connection data
- req = &logical.Request{
- Path: "auth/foo/login",
- Connection: &logical.Connection{},
- }
- if _, err := c.HandleRequest(namespace.RootContext(nil), req); err != nil {
- t.Fatalf("err: %v", err)
- }
- if noop.Requests[0].Connection == nil {
- t.Fatalf("bad: %#v", noop.Requests)
- }
-}
-
-// Ensure we get a client token
-func TestCore_HandleLogin_Token(t *testing.T) {
- noop := &NoopBackend{
- Login: []string{"login"},
- Response: &logical.Response{
- Auth: &logical.Auth{
- Policies: []string{"foo", "bar"},
- Metadata: map[string]string{
- "user": "armon",
- },
- DisplayName: "armon",
- },
- },
- BackendType: logical.TypeCredential,
- }
- c, _, root := TestCoreUnsealed(t)
- c.credentialBackends["noop"] = func(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
- return noop, nil
- }
-
- // Enable the credential backend
- req := logical.TestRequest(t, logical.UpdateOperation, "sys/auth/foo")
- req.Data["type"] = "noop"
- req.ClientToken = root
- _, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Attempt to login
- lreq := &logical.Request{
- Path: "auth/foo/login",
- }
- lresp, err := c.HandleRequest(namespace.RootContext(nil), lreq)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Ensure we got a client token back
- clientToken := lresp.Auth.ClientToken
- if clientToken == "" {
- t.Fatalf("bad: %#v", lresp)
- }
-
- // Check the policy and metadata
- innerToken, _ := c.DecodeSSCToken(clientToken)
- te, err := c.tokenStore.Lookup(namespace.RootContext(nil), innerToken)
- if err != nil || te == nil {
- t.Fatalf("tok: %s, err: %v", clientToken, err)
- }
-
- expectedID, _ := c.DecodeSSCToken(clientToken)
- expect := &logical.TokenEntry{
- ID: expectedID,
- Accessor: te.Accessor,
- Parent: "",
- Policies: []string{"bar", "default", "foo"},
- Path: "auth/foo/login",
- Meta: map[string]string{
- "user": "armon",
- },
- DisplayName: "foo-armon",
- TTL: time.Hour * 24,
- CreationTime: te.CreationTime,
- NamespaceID: namespace.RootNamespaceID,
- CubbyholeID: te.CubbyholeID,
- Type: logical.TokenTypeService,
- }
-
- if diff := deep.Equal(te, expect); diff != nil {
- t.Fatal(diff)
- }
-
- // Check that we have a lease with default duration
- if lresp.Auth.TTL != noop.System().DefaultLeaseTTL() {
- t.Fatalf("bad: %#v, defaultLeaseTTL: %#v", lresp.Auth, c.defaultLeaseTTL)
- }
-}
-
-func TestCore_HandleRequest_AuditTrail(t *testing.T) {
- // Create a noop audit backend
- noop := &corehelpers.NoopAudit{}
- c, _, root := TestCoreUnsealed(t)
- c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
- noop = &corehelpers.NoopAudit{
- Config: config,
- }
- return noop, nil
- }
-
- // Enable the audit backend
- req := logical.TestRequest(t, logical.UpdateOperation, "sys/audit/noop")
- req.Data["type"] = "noop"
- req.ClientToken = root
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Make a request
- req = &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/test",
- Data: map[string]interface{}{
- "foo": "bar",
- "lease": "1h",
- },
- ClientToken: root,
- }
- req.ClientToken = root
- if _, err := c.HandleRequest(namespace.RootContext(nil), req); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Check the audit trail on request and response
- if len(noop.ReqAuth) != 1 {
- t.Fatalf("bad: %#v", noop)
- }
- auth := noop.ReqAuth[0]
- if auth.ClientToken != root {
- t.Fatalf("bad client token: %#v", auth)
- }
- if len(auth.Policies) != 1 || auth.Policies[0] != "root" {
- t.Fatalf("bad: %#v", auth)
- }
- if len(noop.Req) != 1 || !reflect.DeepEqual(noop.Req[0], req) {
- t.Fatalf("Bad: %#v", noop.Req[0])
- }
-
- if len(noop.RespAuth) != 2 {
- t.Fatalf("bad: %#v", noop)
- }
- if !reflect.DeepEqual(noop.RespAuth[1], auth) {
- t.Fatalf("bad: %#v, vs %#v", auth, noop.RespAuth)
- }
- if len(noop.RespReq) != 2 || !reflect.DeepEqual(noop.RespReq[1], req) {
- t.Fatalf("Bad: %#v", noop.RespReq[1])
- }
- if len(noop.Resp) != 2 || !reflect.DeepEqual(noop.Resp[1], resp) {
- t.Fatalf("Bad: %#v", noop.Resp[1])
- }
-}
-
-func TestCore_HandleRequest_AuditTrail_noHMACKeys(t *testing.T) {
- // Create a noop audit backend
- var noop *corehelpers.NoopAudit
- c, _, root := TestCoreUnsealed(t)
- c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
- noop = &corehelpers.NoopAudit{
- Config: config,
- }
- return noop, nil
- }
-
- // Specify some keys to not HMAC
- req := logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/secret/tune")
- req.Data["audit_non_hmac_request_keys"] = "foo"
- req.ClientToken = root
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/secret/tune")
- req.Data["audit_non_hmac_response_keys"] = "baz"
- req.ClientToken = root
- resp, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Enable the audit backend
- req = logical.TestRequest(t, logical.UpdateOperation, "sys/audit/noop")
- req.Data["type"] = "noop"
- req.ClientToken = root
- resp, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Make a request
- req = &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/test",
- Data: map[string]interface{}{
- "foo": "bar",
- },
- ClientToken: root,
- }
- req.ClientToken = root
- if _, err := c.HandleRequest(namespace.RootContext(nil), req); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Check the audit trail on request and response
- if len(noop.ReqAuth) != 1 {
- t.Fatalf("bad: %#v", noop)
- }
- auth := noop.ReqAuth[0]
- if auth.ClientToken != root {
- t.Fatalf("bad client token: %#v", auth)
- }
- if len(auth.Policies) != 1 || auth.Policies[0] != "root" {
- t.Fatalf("bad: %#v", auth)
- }
- if len(noop.Req) != 1 || !reflect.DeepEqual(noop.Req[0], req) {
- t.Fatalf("Bad: %#v", noop.Req[0])
- }
- if len(noop.ReqNonHMACKeys) != 1 || noop.ReqNonHMACKeys[0] != "foo" {
- t.Fatalf("Bad: %#v", noop.ReqNonHMACKeys)
- }
- if len(noop.RespAuth) != 2 {
- t.Fatalf("bad: %#v", noop)
- }
- if !reflect.DeepEqual(noop.RespAuth[1], auth) {
- t.Fatalf("bad: %#v", auth)
- }
- if len(noop.RespReq) != 2 || !reflect.DeepEqual(noop.RespReq[1], req) {
- t.Fatalf("Bad: %#v", noop.RespReq[1])
- }
- if len(noop.Resp) != 2 || !reflect.DeepEqual(noop.Resp[1], resp) {
- t.Fatalf("Bad: %#v", noop.Resp[1])
- }
-
- // Test for response keys
- // Make a request
- req = &logical.Request{
- Operation: logical.ReadOperation,
- Path: "secret/test",
- ClientToken: root,
- }
- req.ClientToken = root
- err = c.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if _, err := c.HandleRequest(namespace.RootContext(nil), req); err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(noop.RespNonHMACKeys) != 1 || !strutil.EquivalentSlices(noop.RespNonHMACKeys[0], []string{"baz"}) {
- t.Fatalf("Bad: %#v", noop.RespNonHMACKeys)
- }
- if len(noop.RespReqNonHMACKeys) != 1 || !strutil.EquivalentSlices(noop.RespReqNonHMACKeys[0], []string{"foo"}) {
- t.Fatalf("Bad: %#v", noop.RespReqNonHMACKeys)
- }
-}
-
-func TestCore_HandleLogin_AuditTrail(t *testing.T) {
- // Create a badass credential backend that always logs in as armon
- noop := &corehelpers.NoopAudit{}
- noopBack := &NoopBackend{
- Login: []string{"login"},
- Response: &logical.Response{
- Auth: &logical.Auth{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- Policies: []string{"foo", "bar"},
- Metadata: map[string]string{
- "user": "armon",
- },
- },
- },
- BackendType: logical.TypeCredential,
- }
- c, _, root := TestCoreUnsealed(t)
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return noopBack, nil
- }
- c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
- noop = &corehelpers.NoopAudit{
- Config: config,
- }
- return noop, nil
- }
-
- // Enable the credential backend
- req := logical.TestRequest(t, logical.UpdateOperation, "sys/auth/foo")
- req.Data["type"] = "noop"
- req.ClientToken = root
- _, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Enable the audit backend
- req = logical.TestRequest(t, logical.UpdateOperation, "sys/audit/noop")
- req.Data["type"] = "noop"
- req.ClientToken = root
- _, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Attempt to login
- lreq := &logical.Request{
- Path: "auth/foo/login",
- }
- lresp, err := c.HandleRequest(namespace.RootContext(nil), lreq)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Ensure we got a client token back
- clientToken := lresp.Auth.ClientToken
- if clientToken == "" {
- t.Fatalf("bad: %#v", lresp)
- }
-
- // Check the audit trail on request and response
- if len(noop.ReqAuth) != 1 {
- t.Fatalf("bad: %#v", noop)
- }
- if len(noop.Req) != 1 || !reflect.DeepEqual(noop.Req[0], lreq) {
- t.Fatalf("Bad: %#v %#v", noop.Req[0], lreq)
- }
-
- if len(noop.RespAuth) != 2 {
- t.Fatalf("bad: %#v", noop)
- }
- auth := noop.RespAuth[1]
- if auth.ClientToken != clientToken {
- t.Fatalf("bad client token: %#v", auth)
- }
- if len(auth.Policies) != 3 || auth.Policies[0] != "bar" || auth.Policies[1] != "default" || auth.Policies[2] != "foo" {
- t.Fatalf("bad: %#v", auth)
- }
- if len(noop.RespReq) != 2 || !reflect.DeepEqual(noop.RespReq[1], lreq) {
- t.Fatalf("Bad: %#v", noop.RespReq[1])
- }
- if len(noop.Resp) != 2 || !reflect.DeepEqual(noop.Resp[1], lresp) {
- t.Fatalf("Bad: %#v %#v", noop.Resp[1], lresp)
- }
-}
-
-// Check that we register a lease for new tokens
-func TestCore_HandleRequest_CreateToken_Lease(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
-
- // Create a new credential
- req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/create")
- req.ClientToken = root
- req.Data["policies"] = []string{"foo"}
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Ensure we got a new client token back
- if resp.IsError() {
- t.Fatalf("err: %v %v", err, *resp)
- }
- clientToken := resp.Auth.ClientToken
- if clientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Check the policy and metadata
- te, err := c.tokenStore.Lookup(namespace.RootContext(nil), clientToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- expectedID, _ := c.DecodeSSCToken(clientToken)
- expectedRootID, _ := c.DecodeSSCToken(root)
-
- expect := &logical.TokenEntry{
- ID: expectedID,
- Accessor: te.Accessor,
- Parent: expectedRootID,
- Policies: []string{"default", "foo"},
- Path: "auth/token/create",
- DisplayName: "token",
- CreationTime: te.CreationTime,
- TTL: time.Hour * 24 * 32,
- NamespaceID: namespace.RootNamespaceID,
- CubbyholeID: te.CubbyholeID,
- Type: logical.TokenTypeService,
- }
- if diff := deep.Equal(te, expect); diff != nil {
- t.Fatal(diff)
- }
-
- // Check that we have a lease with default duration
- if resp.Auth.TTL != c.defaultLeaseTTL {
- t.Fatalf("bad: %#v", resp.Auth)
- }
-}
-
-// Check that we handle excluding the default policy
-func TestCore_HandleRequest_CreateToken_NoDefaultPolicy(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
-
- // Create a new credential
- req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/create")
- req.ClientToken = root
- req.Data["policies"] = []string{"foo"}
- req.Data["no_default_policy"] = true
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Ensure we got a new client token back
- clientToken := resp.Auth.ClientToken
- if clientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Check the policy and metadata
- te, err := c.tokenStore.Lookup(namespace.RootContext(nil), clientToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- expectedID, _ := c.DecodeSSCToken(clientToken)
- expectedRootID, _ := c.DecodeSSCToken(root)
-
- expect := &logical.TokenEntry{
- ID: expectedID,
- Accessor: te.Accessor,
- Parent: expectedRootID,
- Policies: []string{"foo"},
- Path: "auth/token/create",
- DisplayName: "token",
- CreationTime: te.CreationTime,
- TTL: time.Hour * 24 * 32,
- NamespaceID: namespace.RootNamespaceID,
- CubbyholeID: te.CubbyholeID,
- Type: logical.TokenTypeService,
- }
- if diff := deep.Equal(te, expect); diff != nil {
- t.Fatal(diff)
- }
-}
-
-func TestCore_LimitedUseToken(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
-
- // Create a new credential
- req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/create")
- req.ClientToken = root
- req.Data["num_uses"] = "1"
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Put a secret
- req = &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/foo",
- Data: map[string]interface{}{
- "foo": "bar",
- },
- ClientToken: resp.Auth.ClientToken,
- }
- _, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Second operation should fail
- _, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || !errwrap.Contains(err, logical.ErrPermissionDenied.Error()) {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestCore_Standby_Seal(t *testing.T) {
- // Create the first core and initialize it
- logger = logging.NewVaultLogger(log.Trace)
-
- inm, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- inmha, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
-
- redirectOriginal := "http://127.0.0.1:8200"
- core, err := NewCore(&CoreConfig{
- Physical: inm,
- HAPhysical: inmha.(physical.HABackend),
- RedirectAddr: redirectOriginal,
- DisableMlock: true,
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer core.Shutdown()
- keys, root := TestCoreInit(t, core)
- for _, key := range keys {
- if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- // Verify unsealed
- if core.Sealed() {
- t.Fatal("should not be sealed")
- }
-
- // Wait for core to become active
- TestWaitActive(t, core)
-
- // Check the leader is local
- isLeader, advertise, _, err := core.Leader()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !isLeader {
- t.Fatalf("should be leader")
- }
- if advertise != redirectOriginal {
- t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
- }
-
- // Create the second core and initialize it
- redirectOriginal2 := "http://127.0.0.1:8500"
- core2, err := NewCore(&CoreConfig{
- Physical: inm,
- HAPhysical: inmha.(physical.HABackend),
- RedirectAddr: redirectOriginal2,
- DisableMlock: true,
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer core2.Shutdown()
- for _, key := range keys {
- if _, err := TestCoreUnseal(core2, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- // Verify unsealed
- if core2.Sealed() {
- t.Fatal("should not be sealed")
- }
-
- // Core2 should be in standby
- standby, err := core2.Standby()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !standby {
- t.Fatalf("should be standby")
- }
-
- // Check the leader is not local
- isLeader, advertise, _, err = core2.Leader()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if isLeader {
- t.Fatalf("should not be leader")
- }
- if advertise != redirectOriginal {
- t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
- }
-
- // Seal the standby core with the correct token. Shouldn't go down
- err = core2.Seal(root)
- if err == nil {
- t.Fatal("should not be sealed")
- }
-
- keyUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- // Seal the standby core with an invalid token. Shouldn't go down
- err = core2.Seal(keyUUID)
- if err == nil {
- t.Fatal("should not be sealed")
- }
-}
-
-func TestCore_StepDown(t *testing.T) {
- // Create the first core and initialize it
- logger = logging.NewVaultLogger(log.Trace).Named(t.Name())
-
- inm, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- inmha, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
-
- redirectOriginal := "http://127.0.0.1:8200"
- core, err := NewCore(&CoreConfig{
- Physical: inm,
- HAPhysical: inmha.(physical.HABackend),
- RedirectAddr: redirectOriginal,
- DisableMlock: true,
- Logger: logger.Named("core1"),
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer core.Shutdown()
- keys, root := TestCoreInit(t, core)
- for _, key := range keys {
- if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- // Verify unsealed
- if core.Sealed() {
- t.Fatal("should not be sealed")
- }
-
- // Wait for core to become active
- TestWaitActive(t, core)
-
- // Check the leader is local
- isLeader, advertise, _, err := core.Leader()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !isLeader {
- t.Fatalf("should be leader")
- }
- if advertise != redirectOriginal {
- t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
- }
-
- // Create the second core and initialize it
- redirectOriginal2 := "http://127.0.0.1:8500"
- core2, err := NewCore(&CoreConfig{
- Physical: inm,
- HAPhysical: inmha.(physical.HABackend),
- RedirectAddr: redirectOriginal2,
- DisableMlock: true,
- Logger: logger.Named("core2"),
- })
- defer core2.Shutdown()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- for _, key := range keys {
- if _, err := TestCoreUnseal(core2, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- // Verify unsealed
- if core2.Sealed() {
- t.Fatal("should not be sealed")
- }
-
- // Core2 should be in standby
- standby, err := core2.Standby()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !standby {
- t.Fatalf("should be standby")
- }
-
- // Check the leader is not local
- isLeader, advertise, _, err = core2.Leader()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if isLeader {
- t.Fatalf("should not be leader")
- }
- if advertise != redirectOriginal {
- t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
- }
-
- req := &logical.Request{
- ClientToken: root,
- Path: "sys/step-down",
- }
-
- // Create an identifier for the request
- req.ID, err = uuid.GenerateUUID()
- if err != nil {
- t.Fatalf("failed to generate identifier for the request: path: %s err: %v", req.Path, err)
- }
-
- // Step down core
- err = core.StepDown(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal("error stepping down core 1")
- }
-
- // Give time to switch leaders
- time.Sleep(5 * time.Second)
-
- // Core1 should be in standby
- standby, err = core.Standby()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !standby {
- t.Fatalf("should be standby")
- }
-
- // Check the leader is core2
- isLeader, advertise, _, err = core2.Leader()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !isLeader {
- t.Fatalf("should be leader")
- }
- if advertise != redirectOriginal2 {
- t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal2)
- }
-
- // Check the leader is not local
- isLeader, advertise, _, err = core.Leader()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if isLeader {
- t.Fatalf("should not be leader")
- }
- if advertise != redirectOriginal2 {
- t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal2)
- }
-
- // Step down core2
- err = core2.StepDown(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal("error stepping down core 1")
- }
-
- // Give time to switch leaders -- core 1 will still be waiting on its
- // cooling off period so give it a full 10 seconds to recover
- time.Sleep(10 * time.Second)
-
- // Core2 should be in standby
- standby, err = core2.Standby()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !standby {
- t.Fatalf("should be standby")
- }
-
- // Check the leader is core1
- isLeader, advertise, _, err = core.Leader()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !isLeader {
- t.Fatalf("should be leader")
- }
- if advertise != redirectOriginal {
- t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
- }
-
- // Check the leader is not local
- isLeader, advertise, _, err = core2.Leader()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if isLeader {
- t.Fatalf("should not be leader")
- }
- if advertise != redirectOriginal {
- t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
- }
-}
-
-func TestCore_CleanLeaderPrefix(t *testing.T) {
- // Create the first core and initialize it
- logger = logging.NewVaultLogger(log.Trace)
-
- inm, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- inmha, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
-
- redirectOriginal := "http://127.0.0.1:8200"
- core, err := NewCore(&CoreConfig{
- Physical: inm,
- HAPhysical: inmha.(physical.HABackend),
- RedirectAddr: redirectOriginal,
- DisableMlock: true,
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer core.Shutdown()
- keys, root := TestCoreInit(t, core)
- for _, key := range keys {
- if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- // Verify unsealed
- if core.Sealed() {
- t.Fatal("should not be sealed")
- }
-
- // Wait for core to become active
- TestWaitActive(t, core)
-
- // Ensure that the original clean function has stopped running
- time.Sleep(2 * time.Second)
-
- // Put several random entries
- for i := 0; i < 5; i++ {
- keyUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- valueUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- core.barrier.Put(namespace.RootContext(nil), &logical.StorageEntry{
- Key: coreLeaderPrefix + keyUUID,
- Value: []byte(valueUUID),
- })
- }
-
- entries, err := core.barrier.List(namespace.RootContext(nil), coreLeaderPrefix)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(entries) != 6 {
- t.Fatalf("wrong number of core leader prefix entries, got %d", len(entries))
- }
-
- // Check the leader is local
- isLeader, advertise, _, err := core.Leader()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !isLeader {
- t.Fatalf("should be leader")
- }
- if advertise != redirectOriginal {
- t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
- }
-
- // Create a second core, attached to same in-memory store
- redirectOriginal2 := "http://127.0.0.1:8500"
- core2, err := NewCore(&CoreConfig{
- Physical: inm,
- HAPhysical: inmha.(physical.HABackend),
- RedirectAddr: redirectOriginal2,
- DisableMlock: true,
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer core2.Shutdown()
- for _, key := range keys {
- if _, err := TestCoreUnseal(core2, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- // Verify unsealed
- if core2.Sealed() {
- t.Fatal("should not be sealed")
- }
-
- // Core2 should be in standby
- standby, err := core2.Standby()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !standby {
- t.Fatalf("should be standby")
- }
-
- // Check the leader is not local
- isLeader, advertise, _, err = core2.Leader()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if isLeader {
- t.Fatalf("should not be leader")
- }
- if advertise != redirectOriginal {
- t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
- }
-
- // Seal the first core, should step down
- err = core.Seal(root)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Core should be in standby
- standby, err = core.Standby()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !standby {
- t.Fatalf("should be standby")
- }
-
- // Wait for core2 to become active
- TestWaitActive(t, core2)
-
- // Check the leader is local
- isLeader, advertise, _, err = core2.Leader()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !isLeader {
- t.Fatalf("should be leader")
- }
- if advertise != redirectOriginal2 {
- t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal2)
- }
-
- // Give time for the entries to clear out; it is conservative at 1/second
- time.Sleep(10 * leaderPrefixCleanDelay)
-
- entries, err = core2.barrier.List(namespace.RootContext(nil), coreLeaderPrefix)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(entries) != 1 {
- t.Fatalf("wrong number of core leader prefix entries, got %d", len(entries))
- }
-}
-
-func TestCore_Standby(t *testing.T) {
- logger = logging.NewVaultLogger(log.Trace)
-
- inmha, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
-
- testCore_Standby_Common(t, inmha, inmha.(physical.HABackend))
-}
-
-func TestCore_Standby_SeparateHA(t *testing.T) {
- logger = logging.NewVaultLogger(log.Trace)
-
- inmha, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- inmha2, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
-
- testCore_Standby_Common(t, inmha, inmha2.(physical.HABackend))
-}
-
-func testCore_Standby_Common(t *testing.T, inm physical.Backend, inmha physical.HABackend) {
- // Create the first core and initialize it
- redirectOriginal := "http://127.0.0.1:8200"
- core, err := NewCore(&CoreConfig{
- Physical: inm,
- HAPhysical: inmha,
- RedirectAddr: redirectOriginal,
- DisableMlock: true,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer core.Shutdown()
- keys, root := TestCoreInit(t, core)
- for _, key := range keys {
- if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- // Verify unsealed
- if core.Sealed() {
- t.Fatal("should not be sealed")
- }
-
- // Wait for core to become active
- TestWaitActive(t, core)
-
- testCoreAddSecretMount(t, core, root)
-
- // Put a secret
- req := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/foo",
- Data: map[string]interface{}{
- "foo": "bar",
- },
- ClientToken: root,
- }
- _, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Check the leader is local
- isLeader, advertise, _, err := core.Leader()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !isLeader {
- t.Fatalf("should be leader")
- }
- if advertise != redirectOriginal {
- t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
- }
-
- // Create a second core, attached to same in-memory store
- redirectOriginal2 := "http://127.0.0.1:8500"
- core2, err := NewCore(&CoreConfig{
- Physical: inm,
- HAPhysical: inmha,
- RedirectAddr: redirectOriginal2,
- DisableMlock: true,
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer core2.Shutdown()
- for _, key := range keys {
- if _, err := TestCoreUnseal(core2, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- // Verify unsealed
- if core2.Sealed() {
- t.Fatal("should not be sealed")
- }
-
- // Core2 should be in standby
- standby, err := core2.Standby()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !standby {
- t.Fatalf("should be standby")
- }
-
- // Request should fail in standby mode
- _, err = core2.HandleRequest(namespace.RootContext(nil), req)
- if err != consts.ErrStandby {
- t.Fatalf("err: %v", err)
- }
-
- // Check the leader is not local
- isLeader, advertise, _, err = core2.Leader()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if isLeader {
- t.Fatalf("should not be leader")
- }
- if advertise != redirectOriginal {
- t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
- }
-
- // Seal the first core, should step down
- err = core.Seal(root)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Core should be in standby
- standby, err = core.Standby()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !standby {
- t.Fatalf("should be standby")
- }
-
- // Wait for core2 to become active
- TestWaitActive(t, core2)
-
- // Read the secret
- req = &logical.Request{
- Operation: logical.ReadOperation,
- Path: "secret/foo",
- ClientToken: root,
- }
- resp, err := core2.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Verify the response
- if resp.Data["foo"] != "bar" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Check the leader is local
- isLeader, advertise, _, err = core2.Leader()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !isLeader {
- t.Fatalf("should be leader")
- }
- if advertise != redirectOriginal2 {
- t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal2)
- }
-
- if inm.(*inmem.InmemHABackend) == inmha.(*inmem.InmemHABackend) {
- lockSize := inm.(*inmem.InmemHABackend).LockMapSize()
- if lockSize == 0 {
- t.Fatalf("locks not used with only one HA backend")
- }
- } else {
- lockSize := inmha.(*inmem.InmemHABackend).LockMapSize()
- if lockSize == 0 {
- t.Fatalf("locks not used with expected HA backend")
- }
-
- lockSize = inm.(*inmem.InmemHABackend).LockMapSize()
- if lockSize != 0 {
- t.Fatalf("locks used with unexpected HA backend")
- }
- }
-}
-
-// Ensure that InternalData is never returned
-func TestCore_HandleRequest_Login_InternalData(t *testing.T) {
- noop := &NoopBackend{
- Login: []string{"login"},
- Response: &logical.Response{
- Auth: &logical.Auth{
- Policies: []string{"foo", "bar"},
- InternalData: map[string]interface{}{
- "foo": "bar",
- },
- },
- },
- BackendType: logical.TypeCredential,
- }
-
- c, _, root := TestCoreUnsealed(t)
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return noop, nil
- }
-
- // Enable the credential backend
- req := logical.TestRequest(t, logical.UpdateOperation, "sys/auth/foo")
- req.Data["type"] = "noop"
- req.ClientToken = root
- _, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Attempt to login
- lreq := &logical.Request{
- Path: "auth/foo/login",
- }
- lresp, err := c.HandleRequest(namespace.RootContext(nil), lreq)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Ensure we do not get the internal data
- if lresp.Auth.InternalData != nil {
- t.Fatalf("bad: %#v", lresp)
- }
-}
-
-// Ensure that InternalData is never returned
-func TestCore_HandleRequest_InternalData(t *testing.T) {
- noop := &NoopBackend{
- Response: &logical.Response{
- Secret: &logical.Secret{
- InternalData: map[string]interface{}{
- "foo": "bar",
- },
- },
- Data: map[string]interface{}{
- "foo": "bar",
- },
- },
- }
-
- c, _, root := TestCoreUnsealed(t)
- c.logicalBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return noop, nil
- }
-
- // Enable the credential backend
- req := logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo")
- req.Data["type"] = "noop"
- req.ClientToken = root
- _, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Attempt to read
- lreq := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "foo/test",
- ClientToken: root,
- }
- lreq.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
- lresp, err := c.HandleRequest(namespace.RootContext(nil), lreq)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Ensure we do not get the internal data
- if lresp.Secret.InternalData != nil {
- t.Fatalf("bad: %#v", lresp)
- }
-}
-
-// Ensure login does not return a secret
-func TestCore_HandleLogin_ReturnSecret(t *testing.T) {
- // Create a badass credential backend that always logs in as armon
- noopBack := &NoopBackend{
- Login: []string{"login"},
- Response: &logical.Response{
- Secret: &logical.Secret{},
- Auth: &logical.Auth{
- Policies: []string{"foo", "bar"},
- },
- },
- BackendType: logical.TypeCredential,
- }
- c, _, root := TestCoreUnsealed(t)
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return noopBack, nil
- }
-
- // Enable the credential backend
- req := logical.TestRequest(t, logical.UpdateOperation, "sys/auth/foo")
- req.Data["type"] = "noop"
- req.ClientToken = root
- _, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Attempt to login
- lreq := &logical.Request{
- Path: "auth/foo/login",
- }
- _, err = c.HandleRequest(namespace.RootContext(nil), lreq)
- if err != ErrInternalError {
- t.Fatalf("err: %v", err)
- }
-}
-
-// Renew should return the same lease back
-func TestCore_RenewSameLease(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
-
- // Create a leasable secret
- req := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/test",
- Data: map[string]interface{}{
- "foo": "bar",
- "lease": "1h",
- },
- ClientToken: root,
- }
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read the key
- req.Operation = logical.ReadOperation
- req.Data = nil
- err = c.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp.Secret)
- }
- original := resp.Secret.LeaseID
-
- // Renew the lease
- req = logical.TestRequest(t, logical.UpdateOperation, "sys/renew/"+resp.Secret.LeaseID)
- req.ClientToken = root
- resp, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Verify the lease did not change
- if resp.Secret.LeaseID != original {
- t.Fatalf("lease id changed: %s %s", original, resp.Secret.LeaseID)
- }
-
- // Renew the lease (alternate path)
- req = logical.TestRequest(t, logical.UpdateOperation, "sys/leases/renew/"+resp.Secret.LeaseID)
- req.ClientToken = root
- resp, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Verify the lease did not change
- if resp.Secret.LeaseID != original {
- t.Fatalf("lease id changed: %s %s", original, resp.Secret.LeaseID)
- }
-}
-
-// Renew of a token should not create a new lease
-func TestCore_RenewToken_SingleRegister(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
-
- // Create a new token
- req := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "auth/token/create",
- Data: map[string]interface{}{
- "lease": "1h",
- },
- ClientToken: root,
- }
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- newClient := resp.Auth.ClientToken
-
- // Renew the token
- req = logical.TestRequest(t, logical.UpdateOperation, "auth/token/renew")
- req.ClientToken = newClient
- req.Data = map[string]interface{}{
- "token": newClient,
- }
- resp, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Revoke using the renew prefix
- req = logical.TestRequest(t, logical.UpdateOperation, "sys/revoke-prefix/auth/token/renew/")
- req.ClientToken = root
- resp, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Verify our token is still valid (e.g. we did not get invalidated by the revoke)
- req = logical.TestRequest(t, logical.UpdateOperation, "auth/token/lookup")
- req.Data = map[string]interface{}{
- "token": newClient,
- }
- req.ClientToken = newClient
- resp, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Verify the token exists
- if newClient != resp.Data["id"].(string) {
- t.Fatalf("bad: return IDs: expected %v, got %v",
- resp.Data["id"], newClient)
- }
-}
-
-// Based on bug GH-203, attempt to disable a credential backend with leased secrets
-func TestCore_EnableDisableCred_WithLease(t *testing.T) {
- noopBack := &NoopBackend{
- Login: []string{"login"},
- Response: &logical.Response{
- Auth: &logical.Auth{
- Policies: []string{"root"},
- },
- },
- BackendType: logical.TypeCredential,
- }
-
- c, _, root := TestCoreUnsealed(t)
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return noopBack, nil
- }
-
- secretWritingPolicy := `
-name = "admins"
-path "secret/*" {
- capabilities = ["update", "create", "read"]
-}
-`
-
- ps := c.policyStore
- policy, _ := ParseACLPolicy(namespace.RootNamespace, secretWritingPolicy)
- if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
- t.Fatal(err)
- }
-
- // Enable the credential backend
- req := logical.TestRequest(t, logical.UpdateOperation, "sys/auth/foo")
- req.Data["type"] = "noop"
- req.ClientToken = root
- _, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Attempt to login -- should fail because we don't allow root to be returned
- lreq := &logical.Request{
- Path: "auth/foo/login",
- }
- lresp, err := c.HandleRequest(namespace.RootContext(nil), lreq)
- if err == nil || lresp == nil || !lresp.IsError() {
- t.Fatalf("expected error trying to auth and receive root policy")
- }
-
- // Fix and try again
- noopBack.Response.Auth.Policies = []string{"admins"}
- lreq = &logical.Request{
- Path: "auth/foo/login",
- }
- lresp, err = c.HandleRequest(namespace.RootContext(nil), lreq)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Create a leasable secret
- req = &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "secret/test",
- Data: map[string]interface{}{
- "foo": "bar",
- "lease": "1h",
- },
- ClientToken: lresp.Auth.ClientToken,
- }
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read the key
- req.Operation = logical.ReadOperation
- req.Data = nil
- err = c.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp.Secret)
- }
-
- // Renew the lease
- req = logical.TestRequest(t, logical.UpdateOperation, "sys/leases/renew")
- req.Data = map[string]interface{}{
- "lease_id": resp.Secret.LeaseID,
- }
- req.ClientToken = lresp.Auth.ClientToken
- _, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Disable the credential backend
- req = logical.TestRequest(t, logical.DeleteOperation, "sys/auth/foo")
- req.ClientToken = root
- resp, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v %#v", err, resp)
- }
-}
-
-func TestCore_HandleRequest_MountPointType(t *testing.T) {
- noop := &NoopBackend{
- Response: &logical.Response{},
- }
- c, _, root := TestCoreUnsealed(t)
- c.logicalBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return noop, nil
- }
-
- // Enable the logical backend
- req := logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo")
- req.Data["type"] = "noop"
- req.Data["description"] = "foo"
- req.ClientToken = root
- _, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Attempt to request
- req = &logical.Request{
- Operation: logical.ReadOperation,
- Path: "foo/test",
- Connection: &logical.Connection{},
- }
- req.ClientToken = root
- if _, err := c.HandleRequest(namespace.RootContext(nil), req); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Verify Path, MountPoint, and MountType
- if noop.Requests[0].Path != "test" {
- t.Fatalf("bad: %#v", noop.Requests)
- }
- if noop.Requests[0].MountPoint != "foo/" {
- t.Fatalf("bad: %#v", noop.Requests)
- }
- if noop.Requests[0].MountType != "noop" {
- t.Fatalf("bad: %#v", noop.Requests)
- }
-}
-
-func TestCore_Standby_Rotate(t *testing.T) {
- // Create the first core and initialize it
- logger = logging.NewVaultLogger(log.Trace)
-
- inm, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- inmha, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
-
- redirectOriginal := "http://127.0.0.1:8200"
- core, err := NewCore(&CoreConfig{
- Physical: inm,
- HAPhysical: inmha.(physical.HABackend),
- RedirectAddr: redirectOriginal,
- DisableMlock: true,
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer core.Shutdown()
- keys, root := TestCoreInit(t, core)
- for _, key := range keys {
- if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- // Wait for core to become active
- TestWaitActive(t, core)
-
- // Create a second core, attached to same in-memory store
- redirectOriginal2 := "http://127.0.0.1:8500"
- core2, err := NewCore(&CoreConfig{
- Physical: inm,
- HAPhysical: inmha.(physical.HABackend),
- RedirectAddr: redirectOriginal2,
- DisableMlock: true,
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer core2.Shutdown()
- for _, key := range keys {
- if _, err := TestCoreUnseal(core2, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- // Rotate the encryption key
- req := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "sys/rotate",
- ClientToken: root,
- }
- _, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Seal the first core, should step down
- err = core.Seal(root)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Wait for core2 to become active
- TestWaitActive(t, core2)
-
- // Read the key status
- req = &logical.Request{
- Operation: logical.ReadOperation,
- Path: "sys/key-status",
- ClientToken: root,
- }
- resp, err := core2.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Verify the response
- if resp.Data["term"] != 2 {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestCore_HandleRequest_Headers(t *testing.T) {
- noop := &NoopBackend{
- Response: &logical.Response{
- Data: map[string]interface{}{},
- },
- }
-
- c, _, root := TestCoreUnsealed(t)
- c.logicalBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return noop, nil
- }
-
- // Enable the backend
- req := logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo")
- req.Data["type"] = "noop"
- req.ClientToken = root
- _, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Mount tune
- req = logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo/tune")
- req.Data["passthrough_request_headers"] = []string{"Should-Passthrough", "should-passthrough-case-insensitive"}
- req.ClientToken = root
- _, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Attempt to read
- lreq := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "foo/test",
- ClientToken: root,
- Headers: map[string][]string{
- "Should-Passthrough": {"foo"},
- "Should-Passthrough-Case-Insensitive": {"baz"},
- "Should-Not-Passthrough": {"bar"},
- consts.AuthHeaderName: {"nope"},
- },
- }
- _, err = c.HandleRequest(namespace.RootContext(nil), lreq)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Check the headers
- headers := noop.Requests[0].Headers
-
- // Test passthrough values
- if val, ok := headers["Should-Passthrough"]; ok {
- expected := []string{"foo"}
- if !reflect.DeepEqual(val, expected) {
- t.Fatalf("expected: %v, got: %v", expected, val)
- }
- } else {
- t.Fatalf("expected 'Should-Passthrough' to be present in the headers map")
- }
-
- if val, ok := headers["Should-Passthrough-Case-Insensitive"]; ok {
- expected := []string{"baz"}
- if !reflect.DeepEqual(val, expected) {
- t.Fatalf("expected: %v, got: %v", expected, val)
- }
- } else {
- t.Fatal("expected 'Should-Passthrough-Case-Insensitive' to be present in the headers map")
- }
-
- if _, ok := headers["Should-Not-Passthrough"]; ok {
- t.Fatal("did not expect 'Should-Not-Passthrough' to be in the headers map")
- }
-
- if _, ok := headers[consts.AuthHeaderName]; ok {
- t.Fatalf("did not expect %q to be in the headers map", consts.AuthHeaderName)
- }
-}
-
-func TestCore_HandleRequest_Headers_denyList(t *testing.T) {
- noop := &NoopBackend{
- Response: &logical.Response{
- Data: map[string]interface{}{},
- },
- }
-
- c, _, root := TestCoreUnsealed(t)
- c.logicalBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return noop, nil
- }
-
- // Enable the backend
- req := logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo")
- req.Data["type"] = "noop"
- req.ClientToken = root
- _, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Mount tune
- req = logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo/tune")
- req.Data["passthrough_request_headers"] = []string{"Authorization", consts.AuthHeaderName}
- req.ClientToken = root
- _, err = c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Attempt to read
- lreq := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "foo/test",
- ClientToken: root,
- Headers: map[string][]string{
- consts.AuthHeaderName: {"foo"},
- },
- }
- _, err = c.HandleRequest(namespace.RootContext(nil), lreq)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Check the headers
- headers := noop.Requests[0].Headers
-
- // Test passthrough values, they should not be present in the backend
- if _, ok := headers[consts.AuthHeaderName]; ok {
- t.Fatalf("did not expect %q to be in the headers map", consts.AuthHeaderName)
- }
-}
-
-func TestCore_HandleRequest_TokenCreate_RegisterAuthFailure(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
-
- // Create a root token and use that for subsequent requests
- req := logical.TestRequest(t, logical.CreateOperation, "auth/token/create")
- req.Data = map[string]interface{}{
- "policies": []string{"root"},
- }
- req.ClientToken = root
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || resp.Auth == nil || resp.Auth.ClientToken == "" {
- t.Fatalf("expected a response from token creation, got: %#v", resp)
- }
- tokenWithRootPolicy := resp.Auth.ClientToken
-
- // Use new token to create yet a new token, this should succeed
- req = logical.TestRequest(t, logical.CreateOperation, "auth/token/create")
- req.ClientToken = tokenWithRootPolicy
- _, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
-
- // Try again but force failure on RegisterAuth to simulate a network failure
- // when registering the lease (e.g. a storage failure). This should trigger
- // an expiration manager cleanup on the newly created token
- core.expiration.testRegisterAuthFailure.Store(true)
- req = logical.TestRequest(t, logical.CreateOperation, "auth/token/create")
- req.ClientToken = tokenWithRootPolicy
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatalf("expected error, got a response: %#v", resp)
- }
- core.expiration.testRegisterAuthFailure.Store(false)
-
- // Do a lookup against the client token that we used for the failed request.
- // It should still be present
- req = logical.TestRequest(t, logical.UpdateOperation, "auth/token/lookup")
- req.Data = map[string]interface{}{
- "token": tokenWithRootPolicy,
- }
- req.ClientToken = root
- _, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
-
- // Do a token creation request with the token to ensure that it's still
- // valid, should succeed.
- req = logical.TestRequest(t, logical.CreateOperation, "auth/token/create")
- req.ClientToken = tokenWithRootPolicy
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-// mockServiceRegistration helps test whether standalone ServiceRegistration works
-type mockServiceRegistration struct {
- notifyActiveCount int
- notifySealedCount int
- notifyPerfCount int
- notifyInitCount int
- runDiscoveryCount int
-}
-
-func (m *mockServiceRegistration) Run(shutdownCh <-chan struct{}, wait *sync.WaitGroup, redirectAddr string) error {
- m.runDiscoveryCount++
- return nil
-}
-
-func (m *mockServiceRegistration) NotifyActiveStateChange(isActive bool) error {
- m.notifyActiveCount++
- return nil
-}
-
-func (m *mockServiceRegistration) NotifySealedStateChange(isSealed bool) error {
- m.notifySealedCount++
- return nil
-}
-
-func (m *mockServiceRegistration) NotifyPerformanceStandbyStateChange(isStandby bool) error {
- m.notifyPerfCount++
- return nil
-}
-
-func (m *mockServiceRegistration) NotifyInitializedStateChange(isInitialized bool) error {
- m.notifyInitCount++
- return nil
-}
-
-// TestCore_ServiceRegistration tests whether standalone ServiceRegistration works
-func TestCore_ServiceRegistration(t *testing.T) {
- // Make a mock service discovery
- sr := &mockServiceRegistration{}
-
- // Create the core
- logger = logging.NewVaultLogger(log.Trace)
- inm, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- inmha, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- const redirectAddr = "http://127.0.0.1:8200"
- core, err := NewCore(&CoreConfig{
- ServiceRegistration: sr,
- Physical: inm,
- HAPhysical: inmha.(physical.HABackend),
- RedirectAddr: redirectAddr,
- DisableMlock: true,
- })
- if err != nil {
- t.Fatal(err)
- }
- defer core.Shutdown()
-
- // Vault should not yet be registered
- if diff := deep.Equal(sr, &mockServiceRegistration{}); diff != nil {
- t.Fatal(diff)
- }
-
- // Vault should be registered
- if diff := deep.Equal(sr, &mockServiceRegistration{
- runDiscoveryCount: 1,
- }); diff != nil {
- t.Fatal(diff)
- }
-
- // Initialize and unseal the core
- keys, _ := TestCoreInit(t, core)
- for _, key := range keys {
- if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
- if core.Sealed() {
- t.Fatal("should not be sealed")
- }
-
- // Wait for core to become active
- TestWaitActive(t, core)
-
- // Vault should be registered, unsealed, and active
- if diff := deep.Equal(sr, &mockServiceRegistration{
- runDiscoveryCount: 1,
- notifyActiveCount: 1,
- notifySealedCount: 1,
- notifyInitCount: 1,
- }); diff != nil {
- t.Fatal(diff)
- }
-}
-
-func TestDetectedDeadlock(t *testing.T) {
- testCore, _, _ := TestCoreUnsealedWithConfig(t, &CoreConfig{DetectDeadlocks: "statelock"})
- InduceDeadlock(t, testCore, 1)
-}
-
-func TestDefaultDeadlock(t *testing.T) {
- testCore, _, _ := TestCoreUnsealed(t)
- InduceDeadlock(t, testCore, 0)
-}
-
-func RestoreDeadlockOpts() func() {
- opts := deadlock.Opts
- return func() {
- deadlock.Opts = opts
- }
-}
-
-func InduceDeadlock(t *testing.T, vaultcore *Core, expected uint32) {
- defer RestoreDeadlockOpts()()
- var deadlocks uint32
- deadlock.Opts.OnPotentialDeadlock = func() {
- atomic.AddUint32(&deadlocks, 1)
- }
- var mtx deadlock.Mutex
- var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- defer wg.Done()
- vaultcore.expiration.coreStateLock.Lock()
- mtx.Lock()
- mtx.Unlock()
- vaultcore.expiration.coreStateLock.Unlock()
- }()
- wg.Wait()
- wg.Add(1)
- go func() {
- defer wg.Done()
- mtx.Lock()
- vaultcore.expiration.coreStateLock.RLock()
- vaultcore.expiration.coreStateLock.RUnlock()
- mtx.Unlock()
- }()
- wg.Wait()
- if atomic.LoadUint32(&deadlocks) != expected {
- t.Fatalf("expected 1 deadlock, detected %d", deadlocks)
- }
-}
-
-func TestExpiration_DeadlockDetection(t *testing.T) {
- testCore := TestCore(t)
- testCoreUnsealed(t, testCore)
-
- if testCore.expiration.DetectDeadlocks() {
- t.Fatal("expiration has deadlock detection enabled, it shouldn't")
- }
-
- testCore = TestCoreWithDeadlockDetection(t, nil, false)
- testCoreUnsealed(t, testCore)
-
- if !testCore.expiration.DetectDeadlocks() {
- t.Fatal("expiration doesn't have deadlock detection enabled, it should")
- }
-}
-
-func TestQuotas_DeadlockDetection(t *testing.T) {
- testCore := TestCore(t)
- testCoreUnsealed(t, testCore)
-
- if testCore.quotaManager.DetectDeadlocks() {
- t.Fatal("quotas has deadlock detection enabled, it shouldn't")
- }
-
- testCore = TestCoreWithDeadlockDetection(t, nil, false)
- testCoreUnsealed(t, testCore)
-
- if !testCore.quotaManager.DetectDeadlocks() {
- t.Fatal("quotas doesn't have deadlock detection enabled, it should")
- }
-}
-
-func TestStatelock_DeadlockDetection(t *testing.T) {
- testCore := TestCore(t)
- testCoreUnsealed(t, testCore)
-
- if testCore.DetectStateLockDeadlocks() {
- t.Fatal("statelock has deadlock detection enabled, it shouldn't")
- }
-
- testCore = TestCoreWithDeadlockDetection(t, nil, false)
- testCoreUnsealed(t, testCore)
-
- if !testCore.DetectStateLockDeadlocks() {
- t.Fatal("statelock doesn't have deadlock detection enabled, it should")
- }
-}
diff --git a/vault/counters_test.go b/vault/counters_test.go
deleted file mode 100644
index 06f17d689..000000000
--- a/vault/counters_test.go
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "testing"
- "time"
-
- "github.com/go-test/deep"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-// noinspection SpellCheckingInspection
-func testParseTime(t *testing.T, format, timeval string) time.Time {
- t.Helper()
- tm, err := time.Parse(format, timeval)
- if err != nil {
- t.Fatalf("Error parsing time %q: %v", timeval, err)
- }
- return tm
-}
-
-func testCountActiveTokens(t *testing.T, c *Core, root string) int {
- t.Helper()
-
- rootCtx := namespace.RootContext(nil)
- req := &logical.Request{
- ClientToken: root,
- Operation: logical.ReadOperation,
- Path: "sys/internal/counters/tokens",
- }
- resp, err := c.HandleRequest(rootCtx, req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %v", resp, err)
- }
- schema.ValidateResponse(
- t,
- // we remove the `sys/` prefix b/c Core removes it before routing to the 'sys' backend
- schema.GetResponseSchema(t, c.systemBackend.Route("internal/counters/tokens"), req.Operation),
- resp,
- true,
- )
-
- activeTokens := resp.Data["counters"].(*ActiveTokens)
- return activeTokens.ServiceTokens.Total
-}
-
-func TestTokenStore_CountActiveTokens(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- rootCtx := namespace.RootContext(nil)
-
- // Count the root token
- count := testCountActiveTokens(t, c, root)
- if count != 1 {
- t.Fatalf("expected %d tokens, not %d", 1, count)
- }
-
- tokens := make([]string, 10)
- for i := 0; i < 10; i++ {
-
- // Create some service tokens
- req := &logical.Request{
- ClientToken: root,
- Operation: logical.UpdateOperation,
- Path: "auth/token/create",
- Data: map[string]interface{}{
- "ttl": "1h",
- },
- }
-
- resp, err := c.HandleRequest(rootCtx, req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %v", resp, err)
- }
-
- tokens[i] = resp.Auth.ClientToken
-
- count = testCountActiveTokens(t, c, root)
- if count != i+2 {
- t.Fatalf("expected %d tokens, not %d", i+2, count)
- }
- }
-
- // Revoke the service tokens
- for i := 0; i < 10; i++ {
-
- req := &logical.Request{
- ClientToken: root,
- Operation: logical.UpdateOperation,
- Path: "auth/token/revoke",
- Data: map[string]interface{}{
- "token": tokens[i],
- },
- }
-
- resp, err := c.HandleRequest(rootCtx, req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %v", resp, err)
- }
- }
-
- // We should now have only 1 token (the root token). However, because
- // token deletion works by setting the TTL of the token to 0 and waiting
- // for it to get cleaned up by the expiration manager, occasionally we will
- // have to wait briefly for all the tokens to actually get deleted.
- for i := 0; i < 10; i++ {
- count = testCountActiveTokens(t, c, root)
- if count == 1 {
- return
- }
- time.Sleep(time.Second)
- }
- t.Fatalf("expected %d tokens, not %d", 1, count)
-}
-
-func testCountActiveEntities(t *testing.T, c *Core, root string, expectedEntities int) {
- t.Helper()
-
- rootCtx := namespace.RootContext(nil)
- resp, err := c.HandleRequest(rootCtx, &logical.Request{
- ClientToken: root,
- Operation: logical.ReadOperation,
- Path: "sys/internal/counters/entities",
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %v", resp, err)
- }
-
- if diff := deep.Equal(resp.Data, map[string]interface{}{
- "counters": &ActiveEntities{
- Entities: EntityCounter{
- Total: expectedEntities,
- },
- },
- }); diff != nil {
- t.Fatal(diff)
- }
-}
-
-func TestIdentityStore_CountActiveEntities(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- rootCtx := namespace.RootContext(nil)
-
- // Count the root token
- testCountActiveEntities(t, c, root, 0)
-
- // Create some entities
- req := &logical.Request{
- ClientToken: root,
- Operation: logical.UpdateOperation,
- Path: "entity",
- }
- ids := make([]string, 10)
- for i := 0; i < 10; i++ {
- resp, err := c.identityStore.HandleRequest(rootCtx, req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %v", resp, err)
- }
- ids[i] = resp.Data["id"].(string)
-
- testCountActiveEntities(t, c, root, i+1)
- }
-
- req.Operation = logical.DeleteOperation
- for i := 0; i < 10; i++ {
- req.Path = "entity/id/" + ids[i]
- resp, err := c.identityStore.HandleRequest(rootCtx, req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %v", resp, err)
- }
-
- testCountActiveEntities(t, c, root, 9-i)
- }
-}
diff --git a/vault/custom_response_headers_test.go b/vault/custom_response_headers_test.go
deleted file mode 100644
index 6ce32a68c..000000000
--- a/vault/custom_response_headers_test.go
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "fmt"
- "net/http/httptest"
- "strings"
- "testing"
-
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/internalshared/configutil"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/sdk/physical/inmem"
-)
-
-var defaultCustomHeaders = map[string]string{
- "Strict-Transport-Security": "max-age=1; domains",
- "Content-Security-Policy": "default-src 'others'",
- "X-Vault-Ignored": "ignored",
- "X-Custom-Header": "Custom header value default",
- "X-Frame-Options": "Deny",
- "X-Content-Type-Options": "nosniff",
- "Content-Type": "text/plain; charset=utf-8",
- "X-XSS-Protection": "1; mode=block",
-}
-
-var customHeaders307 = map[string]string{
- "X-Custom-Header": "Custom header value 307",
-}
-
-var customHeader3xx = map[string]string{
- "X-Vault-Ignored-3xx": "Ignored 3xx",
- "X-Custom-Header": "Custom header value 3xx",
-}
-
-var customHeaders200 = map[string]string{
- "Someheader-200": "200",
- "X-Custom-Header": "Custom header value 200",
-}
-
-var customHeader2xx = map[string]string{
- "X-Custom-Header": "Custom header value 2xx",
-}
-
-var customHeader400 = map[string]string{
- "Someheader-400": "400",
-}
-
-func TestConfigCustomHeaders(t *testing.T) {
- logger := logging.NewVaultLogger(log.Trace)
- phys, err := inmem.NewTransactionalInmem(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- logl := &logical.InmemStorage{}
- uiConfig := NewUIConfig(true, phys, logl)
-
- rawListenerConfig := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- CustomResponseHeaders: map[string]map[string]string{
- "default": defaultCustomHeaders,
- "307": customHeaders307,
- "3xx": customHeader3xx,
- "200": customHeaders200,
- "2xx": customHeader2xx,
- "400": customHeader400,
- },
- },
- }
-
- uiHeaders, err := uiConfig.Headers(context.Background())
- if err != nil {
- t.Fatal(err)
- }
-
- listenerCustomHeaders := NewListenerCustomHeader(rawListenerConfig, logger, uiHeaders)
- if listenerCustomHeaders == nil || len(listenerCustomHeaders) != 1 {
- t.Fatalf("failed to get custom header configuration")
- }
-
- lch := listenerCustomHeaders[0]
-
- if lch.ExistCustomResponseHeader("X-Vault-Ignored-307") {
- t.Fatalf("header name with X-Vault prefix is not valid")
- }
- if lch.ExistCustomResponseHeader("X-Vault-Ignored-3xx") {
- t.Fatalf("header name with X-Vault prefix is not valid")
- }
-
- if !lch.ExistCustomResponseHeader("X-Custom-Header") {
- t.Fatalf("header name with X-Vault prefix is not valid")
- }
-}
-
-func TestCustomResponseHeadersConfigInteractUiConfig(t *testing.T) {
- b := testSystemBackend(t)
- paths := b.(*SystemBackend).configPaths()
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "")
- b.(*SystemBackend).Core.systemBarrierView = view
-
- logger := logging.NewVaultLogger(log.Trace)
- rawListenerConfig := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- CustomResponseHeaders: map[string]map[string]string{
- "default": defaultCustomHeaders,
- "307": customHeaders307,
- "3xx": customHeader3xx,
- "200": customHeaders200,
- "2xx": customHeader2xx,
- "400": customHeader400,
- },
- },
- }
- uiHeaders, err := b.(*SystemBackend).Core.uiConfig.Headers(context.Background())
- if err != nil {
- t.Fatalf("failed to get headers from ui config")
- }
- customListenerHeader := NewListenerCustomHeader(rawListenerConfig, logger, uiHeaders)
- if customListenerHeader == nil {
- t.Fatalf("custom header config should be configured")
- }
- b.(*SystemBackend).Core.customListenerHeader.Store(customListenerHeader)
- clh := b.(*SystemBackend).Core.customListenerHeader
- if clh == nil {
- t.Fatalf("custom header config should be configured in core")
- }
-
- w := httptest.NewRecorder()
- hw := logical.NewHTTPResponseWriter(w)
-
- // setting a header that already exist in custom headers
- req := logical.TestRequest(t, logical.UpdateOperation, "config/ui/headers/X-Custom-Header")
- req.Data["values"] = []string{"UI Custom Header"}
- req.ResponseWriter = hw
-
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatal("request did not fail on setting a header that is present in custom response headers")
- }
- schema.ValidateResponse(
- t,
- schema.FindResponseSchema(t, paths, 3, req.Operation),
- resp,
- true,
- )
-
- if !strings.Contains(resp.Data["error"].(string), fmt.Sprintf("This header already exists in the server configuration and cannot be set in the UI.")) {
- t.Fatalf("failed to get the expected error")
- }
-
- // setting a header that already exist in custom headers
- req = logical.TestRequest(t, logical.UpdateOperation, "config/ui/headers/Someheader-400")
- req.Data["values"] = []string{"400"}
- req.ResponseWriter = hw
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatal("request did not fail on setting a header that is present in custom response headers")
- }
- schema.ValidateResponse(
- t,
- schema.FindResponseSchema(t, paths, 3, req.Operation),
- resp,
- true,
- )
-
- h, err := b.(*SystemBackend).Core.uiConfig.Headers(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- if h.Get("Someheader-400") == "400" {
- t.Fatalf("should not be able to set a header that is in custom response headers")
- }
-
- // setting an ui specific header
- req = logical.TestRequest(t, logical.UpdateOperation, "config/ui/headers/X-CustomUiHeader")
- req.Data["values"] = []string{"Ui header value"}
- req.ResponseWriter = hw
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal("request failed on setting a header that is not present in custom response headers.", "error:", err)
- }
- schema.ValidateResponse(
- t,
- schema.FindResponseSchema(t, paths, 3, req.Operation),
- resp,
- true,
- )
-
- h, err = b.(*SystemBackend).Core.uiConfig.Headers(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- if h.Get("X-CustomUiHeader") != "Ui header value" {
- t.Fatalf("failed to set a header that is not in custom response headers")
- }
-}
diff --git a/vault/diagnose/file_checks_test.go b/vault/diagnose/file_checks_test.go
deleted file mode 100644
index 5bd01f6e9..000000000
--- a/vault/diagnose/file_checks_test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package diagnose
-
-import (
- "context"
- "os"
- "strings"
- "testing"
-)
-
-func TestRaftFolderPerms(t *testing.T) {
- // Make sure overpermissive permissions are caught
- err := os.Mkdir("diagnose", 0o777)
- if err != nil {
- t.Fatal(err)
- }
-
- info, _ := os.Stat("diagnose")
-
- if !IsDir(info) {
- t.Fatal("directory was reported to not be a directory")
- }
-
- // Create a boltDB formatted file and make sure isDB returns true
- fullDBPath := "diagnose/" + DatabaseFilename
- _, err = os.Create(fullDBPath)
- if err != nil {
- t.Fatal(err)
- }
- if !HasDB(fullDBPath) {
- t.Fatal("well-formatted database path is not accepted by DB check function")
- }
-
- hasOnlyOwnerRW, errs := CheckFilePerms(info)
- if hasOnlyOwnerRW {
- t.Fatal("folder has more than owner rw")
- }
- if len(errs) != 1 && !strings.Contains(errs[0], FileTooPermissiveWarning) {
- t.Fatalf("wrong error or number of errors or wrong error returned: %v", errs)
- }
-
- // Make sure underpermissiveness is caught
- err = os.Chmod("diagnose", 0o100)
- if err != nil {
- t.Fatal(err)
- }
- info, _ = os.Stat("diagnose")
- hasOnlyOwnerRW, errs = CheckFilePerms(info)
- if hasOnlyOwnerRW {
- t.Fatal("folder should not have owner write")
- }
- if len(errs) != 1 || !strings.Contains(errs[0], FilePermissionsMissingWarning) {
- t.Fatalf("wrong error or number of errors returned: %v", errs)
- }
-
- // Make sure actually setting owner rw returns properly
- err = os.Chmod("diagnose", 0o600)
- if err != nil {
- t.Fatal(err)
- }
- info, _ = os.Stat("diagnose")
- hasOnlyOwnerRW, errs = CheckFilePerms(info)
- if errs != nil || !hasOnlyOwnerRW {
- t.Fatal("folder with correct perms returns error")
- }
-
- // Make sure we can clean up the diagnose folder
- os.Chmod("diagnose", 0o777)
-
- // Clean up test diagnose folder
- err = os.RemoveAll("diagnose")
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestRaftStorageQuorum(t *testing.T) {
- m := mockStorageBackend{}
- m.raftServerQuorumType = 0
- twoVoterCluster := RaftStorageQuorum(context.Background(), m)
-
- if !strings.Contains(twoVoterCluster, "Please ensure that Vault has access to an odd number of voter nodes.") {
- t.Fatalf("two voter cluster yielded wrong error: %+s", twoVoterCluster)
- }
-
- m.raftServerQuorumType = 1
- threeVoterCluster := RaftStorageQuorum(context.Background(), m)
- if !strings.Contains(threeVoterCluster, "Voter quorum exists") {
- t.Fatalf("three voter cluster yielded incorrect error: %s", threeVoterCluster)
- }
-
- m.raftServerQuorumType = 2
- threeNodeTwoVoterCluster := RaftStorageQuorum(context.Background(), m)
- if !strings.Contains(threeNodeTwoVoterCluster, "Please ensure that Vault has access to an odd number of voter nodes.") {
- t.Fatalf("two voter cluster yielded wrong error: %+s", threeNodeTwoVoterCluster)
- }
-
- m.raftServerQuorumType = 3
- errClusterInfo := RaftStorageQuorum(context.Background(), m)
- if !strings.Contains(errClusterInfo, "error") {
- t.Fatalf("two voter cluster yielded wrong error: %+s", errClusterInfo)
- }
-}
diff --git a/vault/diagnose/helpers_test.go b/vault/diagnose/helpers_test.go
deleted file mode 100644
index a5055c292..000000000
--- a/vault/diagnose/helpers_test.go
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package diagnose
-
-import (
- "context"
- "errors"
- "os"
- "reflect"
- "strings"
- "testing"
-
- "github.com/go-test/deep"
-)
-
-const getMoreCoffee = "You'll find more coffee in the freezer door, or consider buying more for the office."
-
-func TestDiagnoseOtelResults(t *testing.T) {
- expected := &Result{
- Name: "make-coffee",
- Status: ErrorStatus,
- Warnings: []string{
- "coffee getting low",
- },
- Advice: getMoreCoffee,
- Children: []*Result{
- {
- Name: "prepare-kitchen",
- Status: ErrorStatus,
- Children: []*Result{
- {
- Name: "build-microwave",
- Status: ErrorStatus,
- Children: []*Result{
- {
- Name: "buy-parts",
- Status: ErrorStatus,
- Message: "no stores sell microwave parts, please buy a microwave instead.",
- Warnings: []string{
- "warning: you are about to try to build a microwave from scratch.",
- },
- },
- },
- },
- },
- },
- {
- Name: "warm-milk",
- Status: OkStatus,
- },
- {
- Name: "brew-coffee",
- Status: OkStatus,
- },
- {
- Name: "pick-scone",
- Status: ErrorStatus,
- Message: "no scones",
- },
- {
- Name: "dispose-grounds",
- Status: SkippedStatus,
- Message: "skipped as requested",
- },
- },
- }
- sess := New(os.Stdout)
- sess.SkipFilters = []string{"dispose-grounds"}
- ctx := Context(context.Background(), sess)
-
- func() {
- ctx, span := StartSpan(ctx, "make-coffee")
- defer span.End()
-
- makeCoffee(ctx)
- }()
-
- results := sess.Finalize(ctx)
- results.ZeroTimes()
-
- if !reflect.DeepEqual(results, expected) {
- t.Fatalf("results mismatch: %s", strings.Join(deep.Equal(results, expected), "\n"))
- }
- results.Write(os.Stdout, 0)
-}
-
-const coffeeLeft = 3
-
-func makeCoffee(ctx context.Context) error {
- if coffeeLeft < 5 {
- Warn(ctx, "coffee getting low")
- Advise(ctx, getMoreCoffee)
- }
-
- // To mimic listener TLS checks, we'll see if we can nest a Test and add errors in the function
- Test(ctx, "prepare-kitchen", func(ctx context.Context) error {
- return Test(ctx, "build-microwave", func(ctx context.Context) error {
- buildMicrowave(ctx)
- return nil
- })
- })
-
- Test(ctx, "warm-milk", func(ctx context.Context) error {
- return warmMilk(ctx)
- })
-
- brewCoffee(ctx)
-
- SpotCheck(ctx, "pick-scone", pickScone)
-
- Test(ctx, "dispose-grounds", disposeGrounds)
- return nil
-}
-
-// buildMicrowave will throw an error in the function itself to fail the span,
-// but will return nil so the caller test doesn't necessarily throw an error.
-// The intended behavior is that the superspan will detect the failed subspan
-// and fail regardless. This happens when Fail is used to fail the span, but not
-// when Error is used. See the comment in the function itself.
-func buildMicrowave(ctx context.Context) error {
- ctx, span := StartSpan(ctx, "buy-parts")
-
- Fail(ctx, "no stores sell microwave parts, please buy a microwave instead.")
-
- // The error line here does not actually yield an error in the output.
- // TODO: Debug this. In the meantime, always use Fail over Error.
- // Error(ctx, errors.New("no stores sell microwave parts, please buy a microwave instead."))
-
- Warn(ctx, "warning: you are about to try to build a microwave from scratch.")
- span.End()
- return nil
-}
-
-func warmMilk(ctx context.Context) error {
- // Always succeeds
- return nil
-}
-
-func brewCoffee(ctx context.Context) error {
- ctx, span := StartSpan(ctx, "brew-coffee")
- defer span.End()
-
- // Brewing happens here, successfully
- return nil
-}
-
-func pickScone() error {
- return errors.New("no scones")
-}
-
-func disposeGrounds(_ context.Context) error {
- // Done!
- return nil
-}
-
-func TestCapitalizeFirstLetter(t *testing.T) {
- s := "this is a test."
- if CapitalizeFirstLetter(s) != "This is a test." {
- t.Fatalf("first word of string was not capitalized: got %s", CapitalizeFirstLetter(s))
- }
- s = "this"
- if CapitalizeFirstLetter(s) != "This" {
- t.Fatalf("first word of string was not capitalized: got %s", CapitalizeFirstLetter(s))
- }
- s = "."
- if CapitalizeFirstLetter(s) != "." {
- t.Fatalf("String without letters was not unchanged: got %s", CapitalizeFirstLetter(s))
- }
-}
diff --git a/vault/diagnose/storage_checks_test.go b/vault/diagnose/storage_checks_test.go
deleted file mode 100644
index 75f19e18e..000000000
--- a/vault/diagnose/storage_checks_test.go
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package diagnose
-
-import (
- "context"
- "strings"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/sdk/physical"
-)
-
-func TestStorageTimeout(t *testing.T) {
- testCases := []struct {
- errSubString string
- mb physical.Backend
- }{
- {
- errSubString: LatencyWarning,
- mb: mockStorageBackend{callType: timeoutCallWrite},
- },
- {
- errSubString: LatencyWarning,
- mb: mockStorageBackend{callType: timeoutCallRead},
- },
- {
- errSubString: LatencyWarning,
- mb: mockStorageBackend{callType: timeoutCallDelete},
- },
- {
- errSubString: storageErrStringWrite,
- mb: mockStorageBackend{callType: errCallWrite},
- },
- {
- errSubString: storageErrStringDelete,
- mb: mockStorageBackend{callType: errCallDelete},
- },
- {
- errSubString: storageErrStringRead,
- mb: mockStorageBackend{callType: errCallRead},
- },
- {
- errSubString: wrongRWValsPrefix,
- mb: mockStorageBackend{callType: badReadCall},
- },
- }
-
- for _, tc := range testCases {
- var outErr error
- var dur time.Duration
- uuid := "foo"
- backendCallType := tc.mb.(mockStorageBackend).callType
- if callTypeToOp(backendCallType) == readOp {
- dur, outErr = EndToEndLatencyCheckRead(context.Background(), uuid, tc.mb)
- }
- if callTypeToOp(backendCallType) == writeOp {
- dur, outErr = EndToEndLatencyCheckWrite(context.Background(), uuid, tc.mb)
- }
- if callTypeToOp(backendCallType) == deleteOp {
- dur, outErr = EndToEndLatencyCheckDelete(context.Background(), uuid, tc.mb)
- }
-
- if tc.errSubString == "" && outErr == nil {
- // this is the success case where the Storage Latency check passes
- continue
- }
- if tc.errSubString == LatencyWarning && dur > time.Duration(0) {
- // this is the success case where the Storage Latency check successfully returns nonzero duration
- continue
- }
- if !strings.Contains(outErr.Error(), tc.errSubString) {
- t.Errorf("wrong error: expected %s to be contained in %s", tc.errSubString, outErr)
- }
- }
-}
diff --git a/vault/diagnose/test-fixtures/chain.crt.pem b/vault/diagnose/test-fixtures/chain.crt.pem
deleted file mode 100644
index 35e82d5b0..000000000
--- a/vault/diagnose/test-fixtures/chain.crt.pem
+++ /dev/null
@@ -1,64 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIFkjCCA3qgAwIBAgICEAAwDQYJKoZIhvcNAQENBQAwWDELMAkGA1UEBhMCQ0Ex
-CzAJBgNVBAgMAk9OMRAwDgYDVQQHDAdUb3JvbnRvMRQwEgYDVQQKDAtleGFtcGxl
-LmNvbTEUMBIGA1UEAwwLZXhhbXBsZS5jb20wHhcNMjEwNjE3MTUyOTA4WhcNMzEw
-NjE1MTUyOTA4WjBYMQswCQYDVQQGEwJDQTELMAkGA1UECAwCT04xFDASBgNVBAoM
-C2V4YW1wbGUuY29tMQwwCgYDVQQLDANpbnQxGDAWBgNVBAMMD2ludC5leGFtcGxl
-LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPcI9yYyT4jxvo7g
-23mrjdAJC4tNeg2+eyujaVpxNWkwqecFiyCeN1qVNSzjY95wKk95Pd51oUsup3Ja
-zQ/Prdt6eabPr3g/7eZOw87AW1g/fe2soh46+IIZA0JJxKDjJNY4JasRNvusHjYX
-7hSZR+PnHcMuTx1h2arVnJX+ln6DmRRMNvPs0In+TG1z4wJbIRFq3m/uLgnEd7fv
-/fvX4jphz7dk/I/I1b7NedHyl/wO52mBRQ6IdT8/hR7uT90CDxd66to2r5nnttLu
-oVFjZkHrG4BJh9QO5Fp55QOy8wmAhlaDewbRUui0OpvFnxamarNd2vql4Kt645cc
-R4+fpYHWTzJPM/8HqocrnI63NVaa7wSfGgCHBhNo1GYF2QL+5zAEHdKkSDPLszoc
-dXAOZBDVZssgRZt5nWIAp7Kkm4gmsqPdUrO0OB+0v9+q2UjOT5/dhCDKdRsZ4K30
-gU5BEJyfO31oM2QRRKlqjM4yN7/TRm5RlbFcVXobuVKWYsfkfhANa/hbWII3bB+v
-G/V+HOqd0zwfhaTYNSRiUKfAcZBUxDW2XCWfxIV2bygu1Ev9POjrWteWqYTga3le
-y+rnIq6mcAkjNKPIiRmM3guyl0XPqzfqHf2TMGRNf/Y6GlxkBDLcEF7cB420/FWz
-zXM2DqpMKTvPnBm+I4nvO2lkZuNxAgMBAAGjZjBkMB0GA1UdDgQWBBSqAVbbvHD5
-V/Cc0NjmmFyHBmX08zAfBgNVHSMEGDAWgBQDJwHpS6RzHvUmYhKXj3u++kF/sjAS
-BgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQ0F
-AAOCAgEAl7BViBti9cGf/bEvOv1DmMDPQRy9GBwnzyBGeF8zYw79yGhOezruicg8
-UzjgKL2zzNEdCQfetz+iEiuFAu/cpy2gpOkjuYa3KSNQJzmKwRlTAPNo3yHisSxM
-YSvCD1csthZJVxBbM4XwiLg3zD1dOfVByasCPZ41PA2Ott5Xs0xwIYbYytypJ5WL
-2bwMrt8vVkBz+SicoiUIbfNXAYP2NoW0JTg6gbVY7RbBi4suJDzjT4w75UbYMKzA
-GudSvWi/8jC3AtXMu1usMVRc3Mpjo8NTnpTRcigHbRIHOO+s3UyXaDxyXXu1ML+u
-8+DMq7PL+2EKWGIy8edj2VG2niRIV3WO15w5/LeGiAIfBEMbxDq3Y4m2VsObTEjh
-3R4AHnyvfLjxk1wlFUfApcl/TQ3d/KI8e4FS3cB6XVO6oJSTfrm4QcWxHgZ9xJ6i
-iVX3RahohR/5QcRsCPFJ19Iik9WvwZBQ/vh1BwDgGGOIUy/chzPIbJDg/OcBMfFk
-xXA5Db8ykpMhpLD0FGdzTCIzCMitv46mRLSff3+4PyqEiZ7GOrKnduiBF6tkI34i
-etQ4LvFmwloKfwPgAbRXtwp0XCnk+C/adT8fT16JGviHc4Q8fglUKZLVYJiqgb+x
-CQZJrtWk7AgmiaeWQkfPcPbRGJjwXaJ7OvoFhXmZyDRWwOcCunQ=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFjjCCA3agAwIBAgIBADANBgkqhkiG9w0BAQ0FADBYMQswCQYDVQQGEwJDQTEL
-MAkGA1UECAwCT04xEDAOBgNVBAcMB1Rvcm9udG8xFDASBgNVBAoMC2V4YW1wbGUu
-Y29tMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0yMTA2MTcxNTEyMDZaFw0zMTA2
-MTUxNTEyMDZaMFgxCzAJBgNVBAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwH
-VG9yb250bzEUMBIGA1UECgwLZXhhbXBsZS5jb20xFDASBgNVBAMMC2V4YW1wbGUu
-Y29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqhfgT3MSg+QfWFCp
-RQ/z9GdrV3AGH/NMnlzpWFo39pUul8bGTSLiSNJZnfn4ZR6nL01gBVC14lD2W4a/
-az6tkAR1s0Rfn9EfZFW8dWesYs0t98ENvsW7kZnJTtZX822XquE65hYOxI7ul87L
-XsbMQBoAuQk44kla2gJ0VIJosvA+LyZIHZXcA4UYL70mcqWn72D3hLkIzUpapG8f
-czoGhLW51anScQnJ3MStnOsvTpk1QVsrHLACNala7Pe+OIXHrg9D4eZorX3TZRIo
-EVOcHyJVx40BkTnGhjgTAFuhTvc1NqHW7IpcAQawNV583JIZ8RvTXmCQGsJEgiDO
-1HP1wVc8uUyCsZ0J/qbUohdWdFpgAwM5OkOV+0QOkvhD4ZOvwK2NxBlIwOxDdhTl
-BmiKoLutvTXIc1dT+bU3g5T4024ulfsG3ZISJAGWB8b0KKPFl8oT3DGruMAA7dLn
-pa9a35FF4zyERm5PTYVNRg59+bbpYOeZbUS3SoJdenjcL/pV8z9bmeSsWTZf4GtD
-5sfvlnamUcqVveBSGNJQ/4TkTrWqpyEbQTpiAndqetJEDb8Ta7K2IaFzWwvYYFkB
-o2G1jvr7qB19yChedu3DREtsDz4jaBEVSw+oyLHdN37iaDksP7cgmnjsmTnO5SdR
-zjHt24CuwdpdPPf3m3XxZfWgb9MCAwEAAaNjMGEwHQYDVR0OBBYEFAMnAelLpHMe
-9SZiEpePe776QX+yMB8GA1UdIwQYMBaAFAMnAelLpHMe9SZiEpePe776QX+yMA8G
-A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBDQUAA4IC
-AQAkyoX6Wzl7xWPa6rqSj3gxTDs8rKMCw6mmjLIRsKU+vRQ/a8uDYunJXQrDQ21e
-AKJfCPRt+d/gTllJ/OOuDW55zRvJX486aIQzQnU4TGgj58HUYaA9z8pQEpCN36Dp
-X0UWarGKDGBJ/fb3GJf71TXQgCLgd9IWgtN0Y59MMOQyiUYjulBcSD9eTTsSezcQ
-OYTSybma8mZxERMmuEMWZUJl91PLBogAwelwoFoOoCDFyZXcnXD5yDL4uhzMG+px
-0A2zWM8dYYq+Hn1zfzu5cyxeiyGSLYtaKxQCxZJ9FunSLwgLfYRH9YZx92ZcVuMG
-FylUhQVEkH3d+Pjgys8q/avWGkj0LBzK8/KkZGzO/SMlIJRHtqQgzKi4hbc57vRQ
-F32H8/zGTfwDwdjZSy4UvvATNLGbYa/nXYgGzqzQiQd5SIIBMzyG89ZeEsYb3EKD
-rZPqDfhSzXs0gZj+JDgFCkro7203k1mRjXa/txfmjjGKEggPk80J2joeHHbsQOj2
-vLl/GE8XiLpT0rZQY1VvlrM0NdG/Ncfs9K3BZWh4yisspJgq6blURRhGV8rWHUMH
-bnXMYXDb9bhmsbho0e+KYdN8EzdzZP0kX0Ro5uj6JlR+DtTX0MGQwawwdtrAbjGV
-EDFilaw7tcZUQFXRmn5YTs8k7kowY8v5acOaeTJBoOSqBg==
------END CERTIFICATE-----
diff --git a/vault/diagnose/test-fixtures/ecdsa.crt b/vault/diagnose/test-fixtures/ecdsa.crt
deleted file mode 100644
index 816f23191..000000000
--- a/vault/diagnose/test-fixtures/ecdsa.crt
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN CERTIFICATE-----
-MIICSTCCAdACFEjzHOj+v+Do1IeND8+HQQpWDtcNMAoGCCqGSM49BAMCMIGHMQsw
-CQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlBIMRIwEAYDVQQKDAlI
-YXNoaWNvcnAxDjAMBgNVBAsMBVZhdWx0MRgwFgYDVQQDDA9lY2RzYS52YXVsdC5j
-b20xIDAeBgkqhkiG9w0BCQEWEXJveUBoYXNoaWNvcnAuY29tMCAXDTIzMDMzMDE4
-MDE0MVoYDzIwNzMwMzE3MTgwMTQxWjCBhzELMAkGA1UEBhMCVVMxCzAJBgNVBAgM
-AkNBMQswCQYDVQQHDAJQSDESMBAGA1UECgwJSGFzaGljb3JwMQ4wDAYDVQQLDAVW
-YXVsdDEYMBYGA1UEAwwPZWNkc2EudmF1bHQuY29tMSAwHgYJKoZIhvcNAQkBFhFy
-b3lAaGFzaGljb3JwLmNvbTB2MBAGByqGSM49AgEGBSuBBAAiA2IABEB6/cKO/C1X
-dsv6VPxGZEPiQ1Qz1dq5kSGDGg0qwaSsqSbIju6fr3O5ogmTv5V53gGYhccF+RQx
-pQSFEkJxppB5aSmNHIT+qIDSpJ6AowRYBRtEat8GAIB4P+nSekjZRjAKBggqhkjO
-PQQDAgNnADBkAjBpUEBCoOFTDAKvnmNORxnQidtV0niSbS3Kn3BA2Kl/pZ9yiXZy
-o+PBIcQfR3gXBvICMC8JVQzPMyoPPaAMqZmFnFn2God2CekEjRJRHdZFptey4ozX
-q9s+Rj3XNfdr8KUmPQ==
------END CERTIFICATE-----
diff --git a/vault/diagnose/test-fixtures/ecdsa.key b/vault/diagnose/test-fixtures/ecdsa.key
deleted file mode 100644
index b9552a91e..000000000
--- a/vault/diagnose/test-fixtures/ecdsa.key
+++ /dev/null
@@ -1,6 +0,0 @@
------BEGIN EC PRIVATE KEY-----
-MIGkAgEBBDB8mhOjdT0FUMchJl+EFtVs+Hwbh0vN8ArITefyBQZoIZ8KLd8qZmhK
-wjuF0eZx+lqgBwYFK4EEACKhZANiAARAev3CjvwtV3bL+lT8RmRD4kNUM9XauZEh
-gxoNKsGkrKkmyI7un69zuaIJk7+Ved4BmIXHBfkUMaUEhRJCcaaQeWkpjRyE/qiA
-0qSegKMEWAUbRGrfBgCAeD/p0npI2UY=
------END EC PRIVATE KEY-----
diff --git a/vault/diagnose/test-fixtures/expiredcert.pem b/vault/diagnose/test-fixtures/expiredcert.pem
deleted file mode 100644
index 65b8ab339..000000000
--- a/vault/diagnose/test-fixtures/expiredcert.pem
+++ /dev/null
@@ -1,22 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDkDCCAngCCQCRJCLDizZwvjANBgkqhkiG9w0BAQsFADCBiTELMAkGA1UEBhMC
-VVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJQSDESMBAGA1UECgwJSGFzaGljb3Jw
-MQ4wDAYDVQQLDAVWYXVsdDEaMBgGA1UEAwwRZXhwaXJlZC52YXVsdC5jb20xIDAe
-BgkqhkiG9w0BCQEWEXJveUBoYXNoaWNvcnAuY29tMB4XDTIxMDQwNzE0MzczNloX
-DTIxMDQwODE0MzczNlowgYkxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkG
-A1UEBwwCUEgxEjAQBgNVBAoMCUhhc2hpY29ycDEOMAwGA1UECwwFVmF1bHQxGjAY
-BgNVBAMMEWV4cGlyZWQudmF1bHQuY29tMSAwHgYJKoZIhvcNAQkBFhFyb3lAaGFz
-aGljb3JwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALCAzh3U
-w1y8d3lvW5IND5Zc4ncugbJj29YzHicVodSW8B43YWWdN85VuRvniGOYBDjUlsT9
-j0b22U1ql/CG2ZwJGWN4o44sdaIDxKNcOqySbKCx/cvytSv/sel1xB1bM+eG5UFm
-BIIBZzTbF5sqiSqW1gKIRYn8UYx6QB6pORAww8OfI4pywGasbInTf+7l1753gsR2
-fa2rDqZfD+puHiHfF1l85YFnvY1KxS2JkbUepDBqMlYoGSDXGlmgF9WJ2UEuDBDT
-aAw3c3hyfpZt9Fr0Ok8KIehBrkirXTzkpYOKN3imiUbVSNBrlYyDPZhYAshBzdGZ
-zY+/7dIIfmGYgdECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAIrR+y0Mf7Wm+25pZ
-SdJKYoleuKnzSJZBPavCMaL4AeZDI4N3q3/SYT5OsuY3Q6NyZQ9oMBT+1UowMPrB
-CBQzCeuuBj4/W8Vl0bC46mF+Uzw4b93I+6Q1ymQ0pHMIP15MJz/y7r4I0AyvBkDJ
-vS2qp+rtrkPa4sP+e3cOl/DEn+frfkzUZqwWqKBWvKb01eAcU0K9TMEC0tNWDT9b
-yY1EzqbVk+J2TLZjpdsrVE9UdbMPujWGKDG0JGp7YHobjr4XZslx4XVxppzBU4EA
-+SdqJckoMYWwN5MwPxnEET8Ng2qEvvbd/hmlQ7Mwb2/047vY4jblCGOpecxcvgrY
-SzlpFQ==
------END CERTIFICATE-----
diff --git a/vault/diagnose/test-fixtures/expiredprivatekey.pem b/vault/diagnose/test-fixtures/expiredprivatekey.pem
deleted file mode 100644
index 1a1f14440..000000000
--- a/vault/diagnose/test-fixtures/expiredprivatekey.pem
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpAIBAAKCAQEAsIDOHdTDXLx3eW9bkg0Pllzidy6BsmPb1jMeJxWh1JbwHjdh
-ZZ03zlW5G+eIY5gEONSWxP2PRvbZTWqX8IbZnAkZY3ijjix1ogPEo1w6rJJsoLH9
-y/K1K/+x6XXEHVsz54blQWYEggFnNNsXmyqJKpbWAohFifxRjHpAHqk5EDDDw58j
-inLAZqxsidN/7uXXvneCxHZ9rasOpl8P6m4eId8XWXzlgWe9jUrFLYmRtR6kMGoy
-VigZINcaWaAX1YnZQS4MENNoDDdzeHJ+lm30WvQ6Twoh6EGuSKtdPOSlg4o3eKaJ
-RtVI0GuVjIM9mFgCyEHN0ZnNj7/t0gh+YZiB0QIDAQABAoIBADTkNF/+HRkSJR9P
-vnoSzeP6K6cZWaZShFM4+EUwF1G0dXookFg597ouOA1joZxutn0tP40CisfyOwYW
-9jcOwfEfTOthAGofapTFWky126NRMZoqHGrcbWwk8HSMZNuDNBeiddrWLm2i7AGL
-0hQeeAI6NyGFIomKAgn9rG9DuKTf6DjT5zdiShb2A5R7R1kxtvggcsdMN4j6AM9d
-ZFTUoQKYTN3jxS2McSeqphjSAgI+spJxCa0MXVE7RbTu3EoQpyW/MesooNaED7Fb
-mPSIvbaa8h1Ffnw+IoYLrOaZlO0qhb4GfTSWHzVKQg1rmpEJGnMOhnJ3TT/MlD7i
-QyDELj0CgYEA59xrO5GBSTe5plM0wy36yaq4xuEWcFIxwn7kItHd7f3jHmkmnBKk
-NlOhfLEoFsWkzU/U560fx5+ch2X/oWCr2FFQngxtgKsVj1KFtY4exSb+48wbTB92
-4QOdNTZzfYwkFYASTwCfORhiOWxHySa7PRTNZR+3brYtxIvKtIOiUb8CgYEAwuD7
-uTYLymkSQq5Mg1Be5b9+F9r+Wtl98Nmrf+V90sq9mwe2zJpEeiZBGF3TH8NrpXVs
-13QLJGvHL7zHq4LXfwxUXjoR+5G3WeCcz0mlaNqAakHQUpnEDXdXxG6WcXZvXOV6
-s5e6xr3y0UCNG6pH9VfHIyotWmqklFptKJts8G8CgYBreD54AOylLGAv2Pdm1KQe
-37P/8wr8iwynczt5bD9q9bhVOzX7N6THzaHXcyH61CecRrmPnS5S0Ae0trFzcqSU
-grRUXbxP3H0EzqJNUg+vIHEa01t/wEHQ8GTo6lFDyzZahN93oPksdMHqjecENKCr
-Ij5F9hqHBYhXRthxLWaKbwKBgQCVZGOUWBox0Npuw69j+vjEp0fCgd77Rj+Fo1gV
-c5hBP6qabg90Sc601R6Rz1tJvkWHUb1ebVcdVIId1lo64NLSUmFa2qlucQZdoBKV
-1Z84AkjvLATHTZk9uX9O1nf+Awzb6g9mHarRMEw0nYeO7bq8Gc5O5sZdyeLg13FW
-789TgQKBgQCsS2xVrflx9oxi+3pPsVOMR2+TFIIFG7xxMxF5+1Zwypgu87Lef6wc
-AIumQFNaASoYJUE//DlviACfEMgY+R2EojegbOW8gVBMvJ2z6lPBopiKvyTyRtNS
-HHThuJO+qgpOe1tlJSzVqYXeuf3hA3GUqx/DxccDQIJCPXvTJ1SWJQ==
------END RSA PRIVATE KEY-----
diff --git a/vault/diagnose/test-fixtures/fakecert.pem b/vault/diagnose/test-fixtures/fakecert.pem
deleted file mode 100644
index 413ec9491..000000000
--- a/vault/diagnose/test-fixtures/fakecert.pem
+++ /dev/null
@@ -1 +0,0 @@
-This is a fake cert!
\ No newline at end of file
diff --git a/vault/diagnose/test-fixtures/goodcertbadroot.pem b/vault/diagnose/test-fixtures/goodcertbadroot.pem
deleted file mode 100644
index ea3eb3963..000000000
--- a/vault/diagnose/test-fixtures/goodcertbadroot.pem
+++ /dev/null
@@ -1,43 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDtTCCAp2gAwIBAgIUf+jhKTFBnqSs34II0WS1L4QsbbAwDQYJKoZIhvcNAQEL
-BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzQxWhcNMjUw
-MTA1MTAyODExWjAbMRkwFwYDVQQDExBjZXJ0LmV4YW1wbGUuY29tMIIBIjANBgkq
-hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsZx0Svr82YJpFpIy4fJNW5fKA6B8mhxS
-TRAVnygAftetT8puHflY0ss7Y6X2OXjsU0PRn+1PswtivhKi+eLtgWkUF9cFYFGn
-SgMld6ZWRhNheZhA6ZfQmeM/BF2pa5HK2SDF36ljgjL9T+nWrru2Uv0BCoHzLAmi
-YYMiIWplidMmMO5NTRG3k+3AN0TkfakB6JVzjLGhTcXdOcVEMXkeQVqJMAuGouU5
-donyqtnaHuIJGuUdy54YDnX86txhOQhAv6r7dHXzZxS4pmLvw8UI1rsSf/GLcUVG
-B+5+AAGF5iuHC3N2DTl4xz3FcN4Cb4w9pbaQ7+mCzz+anqiJfyr2nwIDAQABo4H1
-MIHyMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUm++e
-HpyM3p708bgZJuRYEdX1o+UwHwYDVR0jBBgwFoAUncSzT/6HMexyuiU9/7EgHu+o
-k5swOwYIKwYBBQUHAQEELzAtMCsGCCsGAQUFBzAChh9odHRwOi8vMTI3LjAuMC4x
-OjgyMDAvdjEvcGtpL2NhMCEGA1UdEQQaMBiCEGNlcnQuZXhhbXBsZS5jb22HBH8A
-AAEwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovLzEyNy4wLjAuMTo4MjAwL3YxL3Br
-aS9jcmwwDQYJKoZIhvcNAQELBQADggEBABsuvmPSNjjKTVN6itWzdQy+SgMIrwfs
-X1Yb9Lefkkwmp9ovKFNQxa4DucuCuzXcQrbKwWTfHGgR8ct4rf30xCRoA7dbQWq4
-aYqNKFWrRaBRAaaYZ/O1ApRTOrXqRx9Eqr0H1BXLsoAq+mWassL8sf6siae+CpwA
-KqBko5G0dNXq5T4i2LQbmoQSVetIrCJEeMrU+idkuqfV2h1BQKgSEhFDABjFdTCN
-QDAHsEHsi2M4/jRW9fqEuhHSDfl2n7tkFUI8wTHUUCl7gXwweJ4qtaSXIwKXYzNj
-xqKHA8Purc1Yfybz4iE1JCROi9fInKlzr5xABq8nb9Qc/J9DIQM+Xmk=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
-RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
-VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
-DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
-ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
-VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
-mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
-IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
-mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
-XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
-dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
-jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
-BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
-DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
-9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
-jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
-Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
-ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
-R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
------END CERTIFICATE-----
\ No newline at end of file
diff --git a/vault/diagnose/test-fixtures/goodcertwithroot.pem b/vault/diagnose/test-fixtures/goodcertwithroot.pem
deleted file mode 100644
index 6e4baf613..000000000
--- a/vault/diagnose/test-fixtures/goodcertwithroot.pem
+++ /dev/null
@@ -1,42 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDtTCCAp2gAwIBAgIUf+jhKTFBnqSs34II0WS1L4QsbbAwDQYJKoZIhvcNAQEL
-BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzQxWhcNMjUw
-MTA1MTAyODExWjAbMRkwFwYDVQQDExBjZXJ0LmV4YW1wbGUuY29tMIIBIjANBgkq
-hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsZx0Svr82YJpFpIy4fJNW5fKA6B8mhxS
-TRAVnygAftetT8puHflY0ss7Y6X2OXjsU0PRn+1PswtivhKi+eLtgWkUF9cFYFGn
-SgMld6ZWRhNheZhA6ZfQmeM/BF2pa5HK2SDF36ljgjL9T+nWrru2Uv0BCoHzLAmi
-YYMiIWplidMmMO5NTRG3k+3AN0TkfakB6JVzjLGhTcXdOcVEMXkeQVqJMAuGouU5
-donyqtnaHuIJGuUdy54YDnX86txhOQhAv6r7dHXzZxS4pmLvw8UI1rsSf/GLcUVG
-B+5+AAGF5iuHC3N2DTl4xz3FcN4Cb4w9pbaQ7+mCzz+anqiJfyr2nwIDAQABo4H1
-MIHyMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUm++e
-HpyM3p708bgZJuRYEdX1o+UwHwYDVR0jBBgwFoAUncSzT/6HMexyuiU9/7EgHu+o
-k5swOwYIKwYBBQUHAQEELzAtMCsGCCsGAQUFBzAChh9odHRwOi8vMTI3LjAuMC4x
-OjgyMDAvdjEvcGtpL2NhMCEGA1UdEQQaMBiCEGNlcnQuZXhhbXBsZS5jb22HBH8A
-AAEwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovLzEyNy4wLjAuMTo4MjAwL3YxL3Br
-aS9jcmwwDQYJKoZIhvcNAQELBQADggEBABsuvmPSNjjKTVN6itWzdQy+SgMIrwfs
-X1Yb9Lefkkwmp9ovKFNQxa4DucuCuzXcQrbKwWTfHGgR8ct4rf30xCRoA7dbQWq4
-aYqNKFWrRaBRAaaYZ/O1ApRTOrXqRx9Eqr0H1BXLsoAq+mWassL8sf6siae+CpwA
-KqBko5G0dNXq5T4i2LQbmoQSVetIrCJEeMrU+idkuqfV2h1BQKgSEhFDABjFdTCN
-QDAHsEHsi2M4/jRW9fqEuhHSDfl2n7tkFUI8wTHUUCl7gXwweJ4qtaSXIwKXYzNj
-xqKHA8Purc1Yfybz4iE1JCROi9fInKlzr5xABq8nb9Qc/J9DIQM+Xmk=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDPDCCAiSgAwIBAgIUb5id+GcaMeMnYBv3MvdTGWigyJ0wDQYJKoZIhvcNAQEL
-BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzI5WhcNMjYw
-MjI2MDIyNzU5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
-AQEBBQADggEPADCCAQoCggEBAOxTMvhTuIRc2YhxZpmPwegP86cgnqfT1mXxi1A7
-Q7qax24Nqbf00I3oDMQtAJlj2RB3hvRSCb0/lkF7i1Bub+TGxuM7NtZqp2F8FgG0
-z2md+W6adwW26rlxbQKjmRvMn66G9YPTkoJmPmxt2Tccb9+apmwW7lslL5j8H48x
-AHJTMb+PMP9kbOHV5Abr3PT4jXUPUr/mWBvBiKiHG0Xd/HEmlyOEPeAThxK+I5tb
-6m+eB+7cL9BsvQpy135+2bRAxUphvFi5NhryJ2vlAvoJ8UqigsNK3E28ut60FAoH
-SWRfFUFFYtfPgTDS1yOKU/z/XMU2giQv2HrleWt0mp4jqBUCAwEAAaOBgTB/MA4G
-A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdxLNP/ocx
-7HK6JT3/sSAe76iTmzAfBgNVHSMEGDAWgBSdxLNP/ocx7HK6JT3/sSAe76iTmzAc
-BgNVHREEFTATggtleGFtcGxlLmNvbYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
-wHThDRsXJunKbAapxmQ6bDxSvTvkLA6m97TXlsFgL+Q3Jrg9HoJCNowJ0pUTwhP2
-U946dCnSCkZck0fqkwVi4vJ5EQnkvyEbfN4W5qVsQKOFaFVzep6Qid4rZT6owWPa
-cNNzNcXAee3/j6hgr6OQ/i3J6fYR4YouYxYkjojYyg+CMdn6q8BoV0BTsHdnw1/N
-ScbnBHQIvIZMBDAmQueQZolgJcdOuBLYHe/kRy167z8nGg+PUFKIYOL8NaOU1+CJ
-t2YaEibVq5MRqCbRgnd9a2vG0jr5a3Mn4CUUYv+5qIjP3hUusYenW1/EWtn1s/gk
-zehNe5dFTjFpylg1o6b8Ow==
------END CERTIFICATE-----
diff --git a/vault/diagnose/test-fixtures/goodkey.pem b/vault/diagnose/test-fixtures/goodkey.pem
deleted file mode 100644
index add982002..000000000
--- a/vault/diagnose/test-fixtures/goodkey.pem
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEogIBAAKCAQEAsZx0Svr82YJpFpIy4fJNW5fKA6B8mhxSTRAVnygAftetT8pu
-HflY0ss7Y6X2OXjsU0PRn+1PswtivhKi+eLtgWkUF9cFYFGnSgMld6ZWRhNheZhA
-6ZfQmeM/BF2pa5HK2SDF36ljgjL9T+nWrru2Uv0BCoHzLAmiYYMiIWplidMmMO5N
-TRG3k+3AN0TkfakB6JVzjLGhTcXdOcVEMXkeQVqJMAuGouU5donyqtnaHuIJGuUd
-y54YDnX86txhOQhAv6r7dHXzZxS4pmLvw8UI1rsSf/GLcUVGB+5+AAGF5iuHC3N2
-DTl4xz3FcN4Cb4w9pbaQ7+mCzz+anqiJfyr2nwIDAQABAoIBAHR7fFV0eAGaopsX
-9OD0TUGlsephBXb43g0GYHfJ/1Ew18w9oaxszJEqkl+PB4W3xZ3yG3e8ZomxDOhF
-RreF2WgG5xOfhDogMwu6NodbArfgnAvoC6JnW3qha8HMP4F500RFVyCRcd6A3Frd
-rFtaZn/UyCsBAN8/zkwPeYHayo7xX6d9kzgRl9HluEX5PXI5+3uiBDUiM085gkLI
-5Cmadh9fMdjfhDXI4x2JYmILpp/9Nlc/krB15s5n1MPNtn3yL0TI0tWp0WlwDCV7
-oUm1SfIM0F1fXGFyFDcqwoIr6JCQgXk6XtTg31YhH1xgUIclUVdtHqmAwAbLdIhQ
-GAiHn2kCgYEAwD4pZ8HfpiOG/EHNoWsMATc/5yC7O8F9WbvcHZQIymLY4v/7HKZb
-VyOR6UQ5/O2cztSGIuKSF6+OK1C34lOyCuTSOTFrjlgEYtLIXjdGLfFdtOO8GRQR
-akVXdwuzNAjTBaH5eXbG+NKcjmCvZL48dQVlfDTVulzFGbcsVTHIMQUCgYEA7IQI
-FVsKnY3KqpyGqXq92LMcsT3XgW6X1BIIV+YhJ5AFUFkFrjrbXs94/8XyLfi0xBQy
-efK+8g5sMs7koF8LyZEcAXWZJQduaKB71hoLlRaU4VQkL/dl2B6VFmAII/CsRCYh
-r9RmDN2PF/mp98Ih9dpC1VqcCDRGoTYsd7jLalMCgYAMgH5k1wDaZxkSMp1S0AlZ
-0uP+/evvOOgT+9mWutfPgZolOQx1koQCKLgGeX9j6Xf3I28NubpSfAI84uTyfQrp
-FnRtb79U5Hh0jMynA+U2e6niZ6UF5H41cQj9Hu+qhKBkj2IP+h96cwfnYnZFkPGR
-kqZE65KyqfHPeFATwkcImQKBgCdrfhlpGiTWXCABhKQ8s+WpPLAB2ahV8XJEKyXT
-UlVQuMIChGLcpnFv7P/cUxf8asx/fUY8Aj0/0CLLvulHziQjTmKj4gl86pb/oIQ3
-xRRtNhU0O+/OsSfLORgIm3K6C0w0esregL/GMbJSR1TnA1gBr7/1oSnw5JC8Ab9W
-injHAoGAJT1MGAiQrhlt9GCGe6Ajw4omdbY0wS9NXefnFhf7EwL0es52ezZ28zpU
-2LXqSFbtann5CHgpSLxiMYPDIf+er4xgg9Bz34tz1if1rDfP2Qrxdrpr4jDnrGT3
-gYC2qCpvVD9RRUMKFfnJTfl5gMQdBW/LINkHtJ82snAeLl3gjQ4=
------END RSA PRIVATE KEY-----
diff --git a/vault/diagnose/test-fixtures/intermediateCert.pem b/vault/diagnose/test-fixtures/intermediateCert.pem
deleted file mode 100644
index 024baaa60..000000000
--- a/vault/diagnose/test-fixtures/intermediateCert.pem
+++ /dev/null
@@ -1,32 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIFkjCCA3qgAwIBAgICEAAwDQYJKoZIhvcNAQENBQAwWDELMAkGA1UEBhMCQ0Ex
-CzAJBgNVBAgMAk9OMRAwDgYDVQQHDAdUb3JvbnRvMRQwEgYDVQQKDAtleGFtcGxl
-LmNvbTEUMBIGA1UEAwwLZXhhbXBsZS5jb20wHhcNMjEwNjE3MTUyOTA4WhcNMzEw
-NjE1MTUyOTA4WjBYMQswCQYDVQQGEwJDQTELMAkGA1UECAwCT04xFDASBgNVBAoM
-C2V4YW1wbGUuY29tMQwwCgYDVQQLDANpbnQxGDAWBgNVBAMMD2ludC5leGFtcGxl
-LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPcI9yYyT4jxvo7g
-23mrjdAJC4tNeg2+eyujaVpxNWkwqecFiyCeN1qVNSzjY95wKk95Pd51oUsup3Ja
-zQ/Prdt6eabPr3g/7eZOw87AW1g/fe2soh46+IIZA0JJxKDjJNY4JasRNvusHjYX
-7hSZR+PnHcMuTx1h2arVnJX+ln6DmRRMNvPs0In+TG1z4wJbIRFq3m/uLgnEd7fv
-/fvX4jphz7dk/I/I1b7NedHyl/wO52mBRQ6IdT8/hR7uT90CDxd66to2r5nnttLu
-oVFjZkHrG4BJh9QO5Fp55QOy8wmAhlaDewbRUui0OpvFnxamarNd2vql4Kt645cc
-R4+fpYHWTzJPM/8HqocrnI63NVaa7wSfGgCHBhNo1GYF2QL+5zAEHdKkSDPLszoc
-dXAOZBDVZssgRZt5nWIAp7Kkm4gmsqPdUrO0OB+0v9+q2UjOT5/dhCDKdRsZ4K30
-gU5BEJyfO31oM2QRRKlqjM4yN7/TRm5RlbFcVXobuVKWYsfkfhANa/hbWII3bB+v
-G/V+HOqd0zwfhaTYNSRiUKfAcZBUxDW2XCWfxIV2bygu1Ev9POjrWteWqYTga3le
-y+rnIq6mcAkjNKPIiRmM3guyl0XPqzfqHf2TMGRNf/Y6GlxkBDLcEF7cB420/FWz
-zXM2DqpMKTvPnBm+I4nvO2lkZuNxAgMBAAGjZjBkMB0GA1UdDgQWBBSqAVbbvHD5
-V/Cc0NjmmFyHBmX08zAfBgNVHSMEGDAWgBQDJwHpS6RzHvUmYhKXj3u++kF/sjAS
-BgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQ0F
-AAOCAgEAl7BViBti9cGf/bEvOv1DmMDPQRy9GBwnzyBGeF8zYw79yGhOezruicg8
-UzjgKL2zzNEdCQfetz+iEiuFAu/cpy2gpOkjuYa3KSNQJzmKwRlTAPNo3yHisSxM
-YSvCD1csthZJVxBbM4XwiLg3zD1dOfVByasCPZ41PA2Ott5Xs0xwIYbYytypJ5WL
-2bwMrt8vVkBz+SicoiUIbfNXAYP2NoW0JTg6gbVY7RbBi4suJDzjT4w75UbYMKzA
-GudSvWi/8jC3AtXMu1usMVRc3Mpjo8NTnpTRcigHbRIHOO+s3UyXaDxyXXu1ML+u
-8+DMq7PL+2EKWGIy8edj2VG2niRIV3WO15w5/LeGiAIfBEMbxDq3Y4m2VsObTEjh
-3R4AHnyvfLjxk1wlFUfApcl/TQ3d/KI8e4FS3cB6XVO6oJSTfrm4QcWxHgZ9xJ6i
-iVX3RahohR/5QcRsCPFJ19Iik9WvwZBQ/vh1BwDgGGOIUy/chzPIbJDg/OcBMfFk
-xXA5Db8ykpMhpLD0FGdzTCIzCMitv46mRLSff3+4PyqEiZ7GOrKnduiBF6tkI34i
-etQ4LvFmwloKfwPgAbRXtwp0XCnk+C/adT8fT16JGviHc4Q8fglUKZLVYJiqgb+x
-CQZJrtWk7AgmiaeWQkfPcPbRGJjwXaJ7OvoFhXmZyDRWwOcCunQ=
------END CERTIFICATE-----
diff --git a/vault/diagnose/test-fixtures/selfSignedCert.pem b/vault/diagnose/test-fixtures/selfSignedCert.pem
deleted file mode 100644
index 1ae49f844..000000000
--- a/vault/diagnose/test-fixtures/selfSignedCert.pem
+++ /dev/null
@@ -1,28 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIEujCCAqICCQC/sjLTDP0XszANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRn
-aXRodWIuaGFzaGljb3JwLmNvbTAeFw0yMTA2MTYyMDUwNDRaFw0yMjA2MTYyMDUw
-NDRaMB8xHTAbBgNVBAMMFGdpdGh1Yi5oYXNoaWNvcnAuY29tMIICIjANBgkqhkiG
-9w0BAQEFAAOCAg8AMIICCgKCAgEA7a2YsR6fPZPYGpUDQVsSFJSaX7ZqOecQiuTa
-gIbbiPNVWgznMwPtHVdmZjjZQspMM49ox6wXiT2WqBGIgraFB+VW00I8XMwM64ge
-Pcr7NK8INgwphvlI9cbrHQYce6wWexLO/S7EJ0UFSns6LGRn8FctXIalu1HgkeT3
-jLpo3Fnr2UnwQAxljb8vW+5otpBDCRRXfgSq5dvZrPh+hxrfBNFZsd0gJGSJEzd1
-9485+e5cvSKs7kyaeQBlxEEBP/ai7zroPhzEyBTm7/2qnr8sWmpFUHIgC0zO8qf3
-TnJ6HDCGGwKXLEjHB+vrRBB4iAgDmUsYlW6G8A+U2hAAjTi3k2JHna5QBL7CEeza
-U4GzyS2Kf3MQLLMyqntRIlotXzKBaoIQl8PSZZddyxWnd3qrwawYC4lDEFo1KnE5
-m+jUojXx84ioKwqj2vZ7+LT6kCErrT/Di47ii89Y7KibF26ZA3IcpUtYNeypHmt1
-A4eCxLIddf6rKtBwVb8cH/dvde/CErSQKsmA9LVvvm5V91y+oAyf6czpknU70F3h
-YuCjFN347ipYSUYvDqvuIMnFo/gAqkonqDdYc7W7l1GMfvxr9c9oymqMml5W0FNM
-77wnL6kUNJhlgaddVFHVWiUjeuX+G4uv2EemdzPmwB8BtC1X6ePEKUlWXVVg+OLf
-hFdvor0CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAjtrZzh9qx5RJ2vBLKugaLK80
-aFIxIOqIZ/oJOMeTvqK1NLUCrRHP7mjhvkB0sYs0OS1ypOjhDOM9ccfkh2q9hwzz
-m/gCqeUUOL+tpvA3VQyxxX5OnFCLdXVMPGd74BBWrYyFoZj28yNqEOAIUU5seJL5
-A7bY/1rUG8YH1p82lPr8DOLg5OfPCIoaoOZE7i7/rL5j97RkFukYQX1pJiiPLfJi
-s0cUOySSnnkqvo0u3UZIOexOlCLxfO32/UA1VW6/LBVitTVgzOSiORbkkXv+vxOZ
-C/qr2XpEENj9vjP/1phPhyYDpVnrzXWPgJ30gl81IU+qTafONUzGftF7ELX0rNvK
-5bFnEFJY7GeBdLLdUOxKlZ9L9Qd1d2Q+iQV25kwzf714dh/t4Oz4K0ZJ5mxpujcA
-bbrEKMvqq7vIAfigHpI8W79BDEC7AP+QPSD1G3QzcxJRG2ctyfODXqTvw5zL/Fy7
-ZYLVWhJuNnHE1pHNmUDRiT8sYl1xGdrUofvSQrnhju9g3FYX/OARRDzFYRq4Dwq/
-ktZEcBcC6QN3fhVaefpsA+QdpMOdDwA+5NA7r7W3qqgAa9qoBdGtzKAzA9J1I67b
-dGgdRrEjr6eP7enrOR+iymTWxun53H2Y4dseqrn18LmyrVV82dgLWH9xkWch5W0/
-kQXne4cSrE727hmkw4k=
------END CERTIFICATE-----
diff --git a/vault/diagnose/test-fixtures/selfSignedCertKey.pem b/vault/diagnose/test-fixtures/selfSignedCertKey.pem
deleted file mode 100644
index 1051bb305..000000000
--- a/vault/diagnose/test-fixtures/selfSignedCertKey.pem
+++ /dev/null
@@ -1,52 +0,0 @@
------BEGIN PRIVATE KEY-----
-MIIJRQIBADANBgkqhkiG9w0BAQEFAASCCS8wggkrAgEAAoICAQDtrZixHp89k9ga
-lQNBWxIUlJpftmo55xCK5NqAhtuI81VaDOczA+0dV2ZmONlCykwzj2jHrBeJPZao
-EYiCtoUH5VbTQjxczAzriB49yvs0rwg2DCmG+Uj1xusdBhx7rBZ7Es79LsQnRQVK
-ezosZGfwVy1chqW7UeCR5PeMumjcWevZSfBADGWNvy9b7mi2kEMJFFd+BKrl29ms
-+H6HGt8E0Vmx3SAkZIkTN3X3jzn57ly9IqzuTJp5AGXEQQE/9qLvOug+HMTIFObv
-/aqevyxaakVQciALTM7yp/dOcnocMIYbApcsSMcH6+tEEHiICAOZSxiVbobwD5Ta
-EACNOLeTYkedrlAEvsIR7NpTgbPJLYp/cxAsszKqe1EiWi1fMoFqghCXw9Jll13L
-Fad3eqvBrBgLiUMQWjUqcTmb6NSiNfHziKgrCqPa9nv4tPqQISutP8OLjuKLz1js
-qJsXbpkDchylS1g17Kkea3UDh4LEsh11/qsq0HBVvxwf929178IStJAqyYD0tW++
-blX3XL6gDJ/pzOmSdTvQXeFi4KMU3fjuKlhJRi8Oq+4gycWj+ACqSieoN1hztbuX
-UYx+/Gv1z2jKaoyaXlbQU0zvvCcvqRQ0mGWBp11UUdVaJSN65f4bi6/YR6Z3M+bA
-HwG0LVfp48QpSVZdVWD44t+EV2+ivQIDAQABAoICAQCUOjeH/rkBBjs4GMa2870K
-6MJ9/p2xDtHaTW+XyIMRnfAVAQcPYdt2+RL7nWihpthvL3kBTeo/xRE4L/cazgmZ
-KwZDKoPKu9cy7OkvUG/qI17TljIv4zgFT9FBgJYy6tf6WXiNnaTneLwb/04AcX4A
-/d1kXvTtJdsQIePg+EB9a/cSxHH4/8I17I30n3LeqImmF/GYvgB26e2PWkpOqAt+
-TbHKo0VwbOKwAV6ozcIyhN2BdyayV0PfQsg05PWKlp525B4C3p46yg5cja7i4gcf
-PDeOPB6P7Y8C9o3ddreA7SI1ph/xllHKNu+6uyrwa08TQypJx2yQOqdyd5hgeobB
-SDlEkrzJh08/f4BOHkubfQ1o40IwRuxnbcZ36OqYiJSMfkG8KR2E/Wu+t5A43rX5
-+b3r+9+EIuyJFiPbIVkQxmpN+NqtXlHZUaZs70mjUChGr+k/8CCQKC6HqO/7oKjq
-gAzbkcntYTJrpl2YZO/NbqjoA1wLmpnHEhJ2F2Q5NAgAosFs/89joyaFYvIka7As
-aS8y6v8RXPpVZ0iKcydThwEhHJvUnqEI8B9mF4oLFrHJgdYSAEc3tiGrV3lrvS2U
-Uza9rJGw1UnRpFkp3MoS5dI60GyQ0hvQ7X5XJQEUOnUyQd+3pzkW+jKIeczDdYSu
-4b8YyQDsNMD2Ewru5lY9AQKCAQEA/VN+EnjvJgtK6s0r8VpqkOvIkFcLDWjS8sQ5
-tWXu5yEyCRJMO+J2aRvoFFph8kMs2oGhJRZ1/4V6D2Zzm0mkhdjMWyBo5n4nDL/4
-Q5CJX3ri2NtORJjPp8BAVSwXj4HijLQKQqr4oatX5LKhTKUS/4X1vGzzi0w1u9cI
-o3U7kENc8+f5O1Xq9+0xlV+hjE/btWUZfqlUSSqF6SIdcA00f0IMFjXyAwlh0R1G
-iBKNmhLrKGbU0KFyzcAqxfWf+wehTsggpiX0vtodyi1EvG7HiZn9QCgRARQGgq5O
-7MKlOATHB9Iv2Kj4fFw5onE8jQYyZqaWMUUpBCPCHahj6z6XNQKCAQEA8C/SXiYt
-eOQINLYh6RQ1uEYWPH8vUe3UXOt59a/t2CWDRApn7JfOis01z/OjpRPLfOgBTeeN
-9l3WjCKFHqp+0Y0GSZYmgCZbQCvzer49tqT482jRDI1g52qs4WQMJM/ONmL4ixKP
-83RZlmKV7NjedBFfArsw3WJy0Q1rpcnrpv20IcCyn/qcylLxziRWh39NRsWWERtv
-wN02zcBQChqrhFDpzzSFslnKNejQsVasIlH17UsMe/PvPBqulHqKSDxihxKAjtcB
-LRMglWHr5rEkoOxoVVZvmwh+6A+DqHe55dXGNxarv07hjwtvgpgzdy4QCe5B0Zrq
-7JcwiC2LPoLmaQKCAQEA6E9c2gvVJApPFaw5lAfamjPfpZ5tIEr0yHRyh4uG3qZu
-gCsrhe9Tr2hMF/4avFQmGeuun5hNdZouKVlGwy1xlt0N6rN5/4XIwcR6I1u03r6O
-sVfMGtQX+jovxOu+X3g5Ddc9YY3wnDHJVI0LpoHrPjDW/Yjcfu3QiQXVgjDMAqwD
-3hjpUiSkaeA3DEi6mTXSwjKIgsM97Cr2yqjiXhN+BQXIl8W4vlgoP+CdAcQh3x1i
-UZabqwejhFOp5gguQcLphpm4dyVvoGXd075XvoXIrsNsnx0fGuIGZmj7L9wAL7MR
-4nY6MnIiDcl1gSZe5OS966zxJxXJW2Z/aTs3BlBL0QKCAQEAqHjStSlQQfio7NhI
-BuYfHCdFF6Aaf/wzNg4RmMyTJ0aAwWwPIzwEKwXv1fJOec7dr7pIl+1wfTuq7taT
-y0PJ+pBRtbH1RXQiE2wAt7rTLNagrJN79rMAIrKHmv0DK5r7SNi4/0vA3wJgiISU
-JvKjboR0wUSt7MtOP+aK+Foeyh4wiHBSmrY93gi6BV8ltpsLiDW1okA9bel8tGtN
-eRjl78SVi7qKgORMWu333DwwN06IEq7Oje83glAw3oLpleuNLLNEq2ySLZy6AS4T
-OthMGfhY4mrjk7os0fd34OZB5b3B8Agd5e2ddymNSOwbRWBw7ZZKYoyoddVCvHI9
-tlY46QKCAQEAtP4/Qkxuz8u4ZLKwHzGF9T1PgopsCBHpT/bP7CpXcA4dmo3CELgK
-B993iwtTpPTR6LABATnQ10wv6sJPp5aysggcWPkgjrVEOh7bdrhejcbauzGUotf/
-uwI8csEjJ1PUzmojlO1dlZ2BjEKIkreTBx1hKgNi8VjpR+/2lGXAp4iAm7gpJrsk
-mDnqmwBJ6gwRVrB9+8qmicpqNloopj43PeJTWrREUEcpdTpx2drIylAc/o71l3Cd
-WBbQKYf3YXzmsthICWmCXL6U5Hv9quKXUs3hSAmPMay+sopulxFnNck+ppf759H8
-xOLGonW/TvDl4gkmCzyFJcFxz77EF6N7hw==
------END PRIVATE KEY-----
diff --git a/vault/diagnose/test-fixtures/trailingdatacert.pem b/vault/diagnose/test-fixtures/trailingdatacert.pem
deleted file mode 100644
index 0aeeffe79..000000000
--- a/vault/diagnose/test-fixtures/trailingdatacert.pem
+++ /dev/null
@@ -1,20 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDTDCCAjQCFBVkElzevM5tqOIoJxMSJnzPvc1MMA0GCSqGSIb3DQEBCwUAMGMx
-CzAJBgNVBAYTAkZSMQ4wDAYDVQQIDAVQYXJpczEOMAwGA1UEBwwFUGFyaXMxDjAM
-BgNVBAoMBVVidWR1MQ4wDAYDVQQLDAVVYnVkdTEUMBIGA1UEAwwLU0RBIFJPT1Qg
-Q0EwHhcNMjAwNjExMTUzNTIyWhcNMzAwNjA5MTUzNTIyWjBiMQswCQYDVQQGEwJG
-UjEOMAwGA1UECAwFUGFyaXMxDjAMBgNVBAcMBVBhcmlzMQ4wDAYDVQQKDAVVQnVk
-dTEOMAwGA1UECwwFVWJ1ZHUxEzARBgNVBAMMClNEQSBDTElFTlQwggEiMA0GCSqG
-SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCmDo5b/iLyNfColCwB15pvUDUkYzp5ungr
-BCPb5H9Sw7ymcfsiTeBtgADXHyRli+YtfAPjC/55wkkgfBgKRKS8xG8zas0XXt/f
-Wpnl2d8oYGXhGBZPdfaPP/fTANSTWIKVF7+My9LKrulJ+CTKNSnTWKsXFzZivN4H
-a9aaBdP3s5CjVtJ456Egu5g2EfStBtNJwXzCjPSZyD69v1G0yWx6M8zhvY6+EV+T
-uqycAsNfvcf9K0nR83RXkZnNnSNy6ptNg0fMpa5JrCHYirtGU5O7CSBCpA52fy9R
-c12NYxl7qFSCTMC/SzZXjGUpIohxNmTzwCttT292A0yc3Bi8aPl/AgMBAAEwDQYJ
-KoZIhvcNAQELBQADggEBAIvLrCOMedGTg4oSCYI+HdWB6E4uhG5UFxNnv3QEib0p
-GV71S+wrU443ZAfZvxlHFKjkvZDoV8hXT8VIuWOxA9TvaLksoIHeOKdDiGntjVfL
-aDMBnYXPXSDEqFDtAXjtUuIua74fkJ/guCJiX+t7fPctWZPo6J89XtgRZrZcnFIF
-1RQZg6cMDoVb3aMUWKvKo6YDsoxiP6rM8xNHqxW788oLTyDeu9ceJkv31jIUYIgJ
-t4i3bkqf54FAIMdKn/mXmprrqW0b4tvR0fCThD4pgXxVw/Egm3tro/z6/y+qLJ9U
-oZa28h/VYGyoJnEOSl09nZhcfOwWUaXxl94n93dBwXIwDDAKBggrBgEFBQcDAg==
------END CERTIFICATE-----
\ No newline at end of file
diff --git a/vault/diagnose/test-fixtures/twoRootCA.pem b/vault/diagnose/test-fixtures/twoRootCA.pem
deleted file mode 100644
index 75074dac0..000000000
--- a/vault/diagnose/test-fixtures/twoRootCA.pem
+++ /dev/null
@@ -1,52 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDPDCCAiSgAwIBAgIUb5id+GcaMeMnYBv3MvdTGWigyJ0wDQYJKoZIhvcNAQEL
-BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzI5WhcNMjYw
-MjI2MDIyNzU5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
-AQEBBQADggEPADCCAQoCggEBAOxTMvhTuIRc2YhxZpmPwegP86cgnqfT1mXxi1A7
-Q7qax24Nqbf00I3oDMQtAJlj2RB3hvRSCb0/lkF7i1Bub+TGxuM7NtZqp2F8FgG0
-z2md+W6adwW26rlxbQKjmRvMn66G9YPTkoJmPmxt2Tccb9+apmwW7lslL5j8H48x
-AHJTMb+PMP9kbOHV5Abr3PT4jXUPUr/mWBvBiKiHG0Xd/HEmlyOEPeAThxK+I5tb
-6m+eB+7cL9BsvQpy135+2bRAxUphvFi5NhryJ2vlAvoJ8UqigsNK3E28ut60FAoH
-SWRfFUFFYtfPgTDS1yOKU/z/XMU2giQv2HrleWt0mp4jqBUCAwEAAaOBgTB/MA4G
-A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdxLNP/ocx
-7HK6JT3/sSAe76iTmzAfBgNVHSMEGDAWgBSdxLNP/ocx7HK6JT3/sSAe76iTmzAc
-BgNVHREEFTATggtleGFtcGxlLmNvbYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
-wHThDRsXJunKbAapxmQ6bDxSvTvkLA6m97TXlsFgL+Q3Jrg9HoJCNowJ0pUTwhP2
-U946dCnSCkZck0fqkwVi4vJ5EQnkvyEbfN4W5qVsQKOFaFVzep6Qid4rZT6owWPa
-cNNzNcXAee3/j6hgr6OQ/i3J6fYR4YouYxYkjojYyg+CMdn6q8BoV0BTsHdnw1/N
-ScbnBHQIvIZMBDAmQueQZolgJcdOuBLYHe/kRy167z8nGg+PUFKIYOL8NaOU1+CJ
-t2YaEibVq5MRqCbRgnd9a2vG0jr5a3Mn4CUUYv+5qIjP3hUusYenW1/EWtn1s/gk
-zehNe5dFTjFpylg1o6b8Ow==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFjjCCA3agAwIBAgIBADANBgkqhkiG9w0BAQ0FADBYMQswCQYDVQQGEwJDQTEL
-MAkGA1UECAwCT04xEDAOBgNVBAcMB1Rvcm9udG8xFDASBgNVBAoMC2V4YW1wbGUu
-Y29tMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0yMTA2MTcxNTEyMDZaFw0zMTA2
-MTUxNTEyMDZaMFgxCzAJBgNVBAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwH
-VG9yb250bzEUMBIGA1UECgwLZXhhbXBsZS5jb20xFDASBgNVBAMMC2V4YW1wbGUu
-Y29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqhfgT3MSg+QfWFCp
-RQ/z9GdrV3AGH/NMnlzpWFo39pUul8bGTSLiSNJZnfn4ZR6nL01gBVC14lD2W4a/
-az6tkAR1s0Rfn9EfZFW8dWesYs0t98ENvsW7kZnJTtZX822XquE65hYOxI7ul87L
-XsbMQBoAuQk44kla2gJ0VIJosvA+LyZIHZXcA4UYL70mcqWn72D3hLkIzUpapG8f
-czoGhLW51anScQnJ3MStnOsvTpk1QVsrHLACNala7Pe+OIXHrg9D4eZorX3TZRIo
-EVOcHyJVx40BkTnGhjgTAFuhTvc1NqHW7IpcAQawNV583JIZ8RvTXmCQGsJEgiDO
-1HP1wVc8uUyCsZ0J/qbUohdWdFpgAwM5OkOV+0QOkvhD4ZOvwK2NxBlIwOxDdhTl
-BmiKoLutvTXIc1dT+bU3g5T4024ulfsG3ZISJAGWB8b0KKPFl8oT3DGruMAA7dLn
-pa9a35FF4zyERm5PTYVNRg59+bbpYOeZbUS3SoJdenjcL/pV8z9bmeSsWTZf4GtD
-5sfvlnamUcqVveBSGNJQ/4TkTrWqpyEbQTpiAndqetJEDb8Ta7K2IaFzWwvYYFkB
-o2G1jvr7qB19yChedu3DREtsDz4jaBEVSw+oyLHdN37iaDksP7cgmnjsmTnO5SdR
-zjHt24CuwdpdPPf3m3XxZfWgb9MCAwEAAaNjMGEwHQYDVR0OBBYEFAMnAelLpHMe
-9SZiEpePe776QX+yMB8GA1UdIwQYMBaAFAMnAelLpHMe9SZiEpePe776QX+yMA8G
-A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBDQUAA4IC
-AQAkyoX6Wzl7xWPa6rqSj3gxTDs8rKMCw6mmjLIRsKU+vRQ/a8uDYunJXQrDQ21e
-AKJfCPRt+d/gTllJ/OOuDW55zRvJX486aIQzQnU4TGgj58HUYaA9z8pQEpCN36Dp
-X0UWarGKDGBJ/fb3GJf71TXQgCLgd9IWgtN0Y59MMOQyiUYjulBcSD9eTTsSezcQ
-OYTSybma8mZxERMmuEMWZUJl91PLBogAwelwoFoOoCDFyZXcnXD5yDL4uhzMG+px
-0A2zWM8dYYq+Hn1zfzu5cyxeiyGSLYtaKxQCxZJ9FunSLwgLfYRH9YZx92ZcVuMG
-FylUhQVEkH3d+Pjgys8q/avWGkj0LBzK8/KkZGzO/SMlIJRHtqQgzKi4hbc57vRQ
-F32H8/zGTfwDwdjZSy4UvvATNLGbYa/nXYgGzqzQiQd5SIIBMzyG89ZeEsYb3EKD
-rZPqDfhSzXs0gZj+JDgFCkro7203k1mRjXa/txfmjjGKEggPk80J2joeHHbsQOj2
-vLl/GE8XiLpT0rZQY1VvlrM0NdG/Ncfs9K3BZWh4yisspJgq6blURRhGV8rWHUMH
-bnXMYXDb9bhmsbho0e+KYdN8EzdzZP0kX0Ro5uj6JlR+DtTX0MGQwawwdtrAbjGV
-EDFilaw7tcZUQFXRmn5YTs8k7kowY8v5acOaeTJBoOSqBg==
------END CERTIFICATE-----
diff --git a/vault/diagnose/tls_verification_test.go b/vault/diagnose/tls_verification_test.go
deleted file mode 100644
index 70f506c93..000000000
--- a/vault/diagnose/tls_verification_test.go
+++ /dev/null
@@ -1,543 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package diagnose
-
-import (
- "context"
- "fmt"
- "strings"
- "testing"
-
- "github.com/hashicorp/vault/internalshared/configutil"
-)
-
-// TestTLSValidCert is the positive test case to show that specifying a valid cert and key
-// passes all checks.
-func TestTLSValidCert(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./test-fixtures/goodcertwithroot.pem",
- TLSKeyFile: "./test-fixtures/goodkey.pem",
- TLSMinVersion: "tls10",
- TLSDisableClientCerts: true,
- },
- }
- warnings, errs := ListenerChecks(context.Background(), listeners)
- if errs != nil {
- // The test failed -- we can just return one of the errors
- t.Fatalf(errs[0].Error())
- }
- if warnings != nil {
- t.Fatalf("warnings returned from good listener")
- }
-}
-
-// TestTLSFakeCert simply ensures that the certificate file must contain PEM data.
-func TestTLSFakeCert(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./test-fixtures/fakecert.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSMinVersion: "tls10",
- TLSDisableClientCerts: true,
- },
- }
- _, errs := ListenerChecks(context.Background(), listeners)
- if errs == nil {
- t.Fatalf("TLS Config check on fake certificate should fail")
- }
- if len(errs) != 1 {
- t.Fatalf("more than one error returned: %+v", errs)
- }
- if !strings.Contains(errs[0].Error(), "Could not decode certificate") {
- t.Fatalf("Bad error message: %s", errs[0])
- }
-}
-
-// TestTLSTrailingData uses a certificate from:
-// https://github.com/golang/go/issues/40545 that contains
-// an extra DER sequence, and makes sure a trailing data error
-// is returned.
-func TestTLSTrailingData(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./test-fixtures/trailingdatacert.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSMinVersion: "tls10",
- TLSDisableClientCerts: true,
- },
- }
- _, errs := ListenerChecks(context.Background(), listeners)
- if errs == nil || len(errs) != 1 {
- t.Fatalf("TLS Config check on fake certificate should fail")
- }
- if !strings.Contains(errs[0].Error(), "x509: trailing data") {
- t.Fatalf("Bad error message: %s", errs[0])
- }
-}
-
-// TestTLSExpiredCert checks that an expired certificate fails TLS checks
-// with an appropriate error.
-func TestTLSExpiredCert(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./test-fixtures/expiredcert.pem",
- TLSKeyFile: "./test-fixtures/expiredprivatekey.pem",
- TLSMinVersion: "tls10",
- TLSDisableClientCerts: true,
- },
- }
- warnings, errs := ListenerChecks(context.Background(), listeners)
- if errs == nil || len(errs) != 1 {
- t.Fatalf("TLS Config check on fake certificate should fail")
- }
- if !strings.Contains(errs[0].Error(), "certificate has expired or is not yet valid") {
- t.Fatalf("Bad error message: %s", errs[0])
- }
- if warnings == nil || len(warnings) != 1 {
- t.Fatalf("TLS Config check on fake certificate should warn")
- }
- if !strings.Contains(warnings[0], "expired or near expiry") {
- t.Fatalf("Bad warning: %s", warnings[0])
- }
-}
-
-// TestTLSMismatchedCryptographicInfo verifies that a cert and key of differing cryptographic
-// types, when specified together, is met with a unique error message.
-func TestTLSMismatchedCryptographicInfo(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSKeyFile: "./test-fixtures/ecdsa.key",
- TLSMinVersion: "tls10",
- TLSDisableClientCerts: true,
- },
- }
- _, errs := ListenerChecks(context.Background(), listeners)
- if errs == nil || len(errs) != 1 {
- t.Fatalf("TLS Config check on fake certificate should fail")
- }
- if !strings.Contains(errs[0].Error(), "tls: private key type does not match public key type") {
- t.Fatalf("Bad error message: %s", errs[0])
- }
-
- listeners = []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./test-fixtures/ecdsa.crt",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSClientCAFile: "./../../api/test-fixtures/root/rootcacert.pem",
- TLSMinVersion: "tls10",
- TLSDisableClientCerts: true,
- },
- }
- _, errs = ListenerChecks(context.Background(), listeners)
- if errs == nil || len(errs) != 1 {
- t.Fatalf("TLS Config check on fake certificate should fail")
- }
- if !strings.Contains(errs[0].Error(), "tls: private key type does not match public key type") {
- t.Fatalf("Bad error message: %s", errs[0])
- }
-}
-
-// TestTLSMultiKeys verifies that a unique error message is thrown when a key is specified twice.
-func TestTLSMultiKeys(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/key.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSClientCAFile: "./../../api/test-fixtures/root/rootcacert.pem",
- TLSMinVersion: "tls10",
- TLSDisableClientCerts: true,
- },
- }
- _, errs := ListenerChecks(context.Background(), listeners)
- if errs == nil || len(errs) != 1 {
- t.Fatalf("TLS Config check on fake certificate should fail")
- }
- if !strings.Contains(errs[0].Error(), "PEM block does not parse to a certificate") {
- t.Fatalf("Bad error message: %s", errs[0])
- }
-}
-
-// TestTLSCertAsKey verifies that a unique error message is thrown when a cert is specified twice.
-func TestTLSCertAsKey(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSMinVersion: "tls10",
- TLSDisableClientCerts: true,
- },
- }
- _, errs := ListenerChecks(context.Background(), listeners)
- if errs == nil || len(errs) != 1 {
- t.Fatalf("TLS Config check on fake certificate should fail")
- }
- if !strings.Contains(errs[0].Error(), "found a certificate rather than a key in the PEM for the private key") {
- t.Fatalf("Bad error message: %s", errs[0])
- }
-}
-
-// TestTLSInvalidRoot makes sure that the Verify call in tls.go checks the authority of
-// the root. The root certificate used in this test is the Baltimore Cyber Trust root
-// certificate, downloaded from: https://www.digicert.com/kb/digicert-root-certificates.htm
-func TestTLSInvalidRoot(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./test-fixtures/goodcertbadroot.pem",
- TLSKeyFile: "./test-fixtures/goodkey.pem",
- TLSMinVersion: "tls10",
- TLSDisableClientCerts: true,
- },
- }
- _, errs := ListenerChecks(context.Background(), listeners)
- if errs == nil || len(errs) != 1 {
- t.Fatalf("TLS Config check on fake certificate should fail")
- }
- if !strings.Contains(errs[0].Error(), "certificate signed by unknown authority") {
- t.Fatalf("Bad error message: %s", errs[0])
- }
-}
-
-// TestTLSNoRoot ensures that a server certificate that is passed in without a root
-// is still accepted by diagnose as valid. This is an acceptable, though less secure,
-// server configuration.
-func TestTLSNoRoot(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSKeyFile: "./test-fixtures/goodkey.pem",
- TLSMinVersion: "tls10",
- TLSDisableClientCerts: true,
- },
- }
- _, errs := ListenerChecks(context.Background(), listeners)
-
- if errs != nil {
- t.Fatalf("server certificate without root certificate is insecure, but still valid")
- }
-}
-
-// TestTLSInvalidMinVersion checks that a listener with an invalid minimum configured
-// version errors appropriately.
-func TestTLSInvalidMinVersion(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSClientCAFile: "./../../api/test-fixtures/root/rootcacert.pem",
- TLSMinVersion: "0",
- TLSDisableClientCerts: true,
- },
- }
- _, errs := ListenerChecks(context.Background(), listeners)
- if errs == nil || len(errs) != 1 {
- t.Fatalf("TLS Config check on invalid 'tls_min_version' should fail")
- }
- if !strings.Contains(errs[0].Error(), fmt.Errorf(minVersionError, "0").Error()) {
- t.Fatalf("Bad error message: %s", errs[0])
- }
-}
-
-// TestTLSInvalidMaxVersion checks that a listener with an invalid maximum configured
-// version errors appropriately.
-func TestTLSInvalidMaxVersion(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSClientCAFile: "./../../api/test-fixtures/root/rootcacert.pem",
- TLSMaxVersion: "0",
- TLSDisableClientCerts: true,
- },
- }
- _, errs := ListenerChecks(context.Background(), listeners)
- if errs == nil || len(errs) != 1 {
- t.Fatalf("TLS Config check on invalid 'tls_max_version' should fail")
- }
- if !strings.Contains(errs[0].Error(), fmt.Errorf(maxVersionError, "0").Error()) {
- t.Fatalf("Bad error message: %s", errs[0])
- }
-}
-
-// TestDisabledClientCertsAndDisabledTLSClientCAVerfiy checks that a listener works properly when both
-// TLSRequireAndVerifyClientCert and TLSDisableClientCerts are false
-func TestDisabledClientCertsAndDisabledTLSClientCAVerfiy(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSClientCAFile: "./../../api/test-fixtures/root/rootcacert.pem",
- TLSMaxVersion: "tls10",
- TLSRequireAndVerifyClientCert: false,
- TLSDisableClientCerts: false,
- },
- }
- status, _ := TLSMutualExclusionCertCheck(listeners[0])
- if status != 0 {
- t.Fatalf("TLS config failed when both TLSRequireAndVerifyClientCert and TLSDisableClientCerts are false")
- }
-}
-
-// TestTLSClientCAVerfiy checks that a listener which has TLS client certs checks enabled works as expected
-func TestTLSClientCAVerfiy(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSClientCAFile: "./../../api/test-fixtures/root/rootcacert.pem",
- TLSMaxVersion: "tls10",
- TLSRequireAndVerifyClientCert: true,
- TLSDisableClientCerts: false,
- },
- }
- status, err := TLSMutualExclusionCertCheck(listeners[0])
- if status != 0 {
- t.Fatalf("TLS config check failed with %s", err)
- }
-}
-
-// TestTLSClientCAVerfiySkip checks that TLS client cert checks are skipped if TLSDisableClientCerts is true
-// regardless of the value for TLSRequireAndVerifyClientCert
-func TestTLSClientCAVerfiySkip(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSClientCAFile: "./../../api/test-fixtures/root/rootcacert.pem",
- TLSMaxVersion: "tls10",
- TLSRequireAndVerifyClientCert: false,
- TLSDisableClientCerts: true,
- },
- }
- status, err := TLSMutualExclusionCertCheck(listeners[0])
- if status != 0 {
- t.Fatalf("TLS config check did not skip verification and failed with %s", err)
- }
-}
-
-// TestTLSClientCAVerfiyMutualExclusion checks that TLS client cert checks are skipped if TLSDisableClientCerts is true
-// regardless of the value for TLSRequireAndVerifyClientCert
-func TestTLSClientCAVerfiyMutualExclusion(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSClientCAFile: "./../../api/test-fixtures/root/rootcacert.pem",
- TLSMaxVersion: "tls10",
- TLSRequireAndVerifyClientCert: true,
- TLSDisableClientCerts: true,
- },
- }
- status, err := TLSMutualExclusionCertCheck(listeners[0])
- if status == 0 {
- t.Fatalf("TLS config check should have failed when both 'tls_disable_client_certs' and 'tls_require_and_verify_client_cert' are true")
- }
- if !strings.Contains(err, "The tls_disable_client_certs and tls_require_and_verify_client_cert fields in the "+
- "listener stanza of the Vault server configuration are mutually exclusive fields") {
- t.Fatalf("Bad error message: %s", err)
- }
-}
-
-// TestTLSClientCAVerfiy checks that a listener which has TLS client certs checks enabled works as expected
-func TestTLSClientCAFileCheck(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSClientCAFile: "./../../api/test-fixtures/root/rootcacert.pem",
- TLSMaxVersion: "tls10",
- TLSRequireAndVerifyClientCert: true,
- TLSDisableClientCerts: false,
- },
- }
- warnings, errs := ListenerChecks(context.Background(), listeners)
- if errs != nil {
- t.Fatalf("TLS config check failed while a good ClientCAFile was used")
- }
- if warnings != nil {
- t.Fatalf("TLS config check return warning while a good ClientCAFile was used")
- }
-}
-
-// TestTLSLeafCertInClientCAFile checks if a leafCert exist in TLSClientCAFile
-func TestTLSLeafCertInClientCAFile(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSClientCAFile: "./test-fixtures/goodcertbadroot.pem",
- TLSMaxVersion: "tls10",
- TLSRequireAndVerifyClientCert: true,
- TLSDisableClientCerts: false,
- },
- }
- warnings, errs := ListenerChecks(context.Background(), listeners)
- fmt.Println(warnings)
- if errs == nil || len(errs) != 1 {
- t.Fatalf("TLS Config check on bad ClientCAFile certificate should fail once")
- }
- if warnings == nil || len(warnings) != 1 {
- t.Fatalf("TLS Config check on bad ClientCAFile certificate should warn once")
- }
- if !strings.Contains(warnings[0], "Found at least one leaf certificate in the CA certificate file.") {
- t.Fatalf("Bad error message: %s", warnings[0])
- }
- if !strings.Contains(errs[0].Error(), "signed by unknown authority") {
- t.Fatalf("Bad error message: %s", errs[0])
- }
-}
-
-// TestTLSNoRootInClientCAFile checks if no Root cert exist in TLSClientCAFile
-func TestTLSNoRootInClientCAFile(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSClientCAFile: "./test-fixtures/intermediateCert.pem",
- TLSMaxVersion: "tls10",
- TLSRequireAndVerifyClientCert: true,
- TLSDisableClientCerts: false,
- },
- }
- _, errs := ListenerChecks(context.Background(), listeners)
- if errs == nil {
- t.Fatalf("TLS Config check on bad ClientCAFile certificate should fail")
- }
- if !strings.Contains(errs[0].Error(), " No root certificate found") {
- t.Fatalf("Bad error message: %s", errs[0])
- }
-}
-
-// TestTLSIntermediateCertInClientCAFile checks if an intermediate cert is included in TLSClientCAFile
-func TestTLSIntermediateCertInClientCAFile(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSClientCAFile: "./test-fixtures/chain.crt.pem",
- TLSMaxVersion: "tls10",
- TLSRequireAndVerifyClientCert: true,
- TLSDisableClientCerts: false,
- },
- }
- warnings, _ := ListenerChecks(context.Background(), listeners)
- if warnings == nil || len(warnings) != 1 {
- t.Fatalf("TLS Config check on bad ClientCAFile certificate should fail")
- }
- if !strings.Contains(warnings[0], "Found at least one intermediate certificate in the CA certificate file.") {
- t.Fatalf("Bad error message: %s", warnings[0])
- }
-}
-
-// TestTLSMultipleRootInClietCACert checks if multiple roots included in TLSClientCAFile
-func TestTLSMultipleRootInClietCACert(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSClientCAFile: "./test-fixtures/twoRootCA.pem",
- TLSMinVersion: "tls10",
- TLSRequireAndVerifyClientCert: true,
- TLSDisableClientCerts: false,
- },
- }
- warnings, errs := ListenerChecks(context.Background(), listeners)
- if errs != nil {
- t.Fatalf("TLS Config check on valid certificate should not fail")
- }
- if warnings == nil {
- t.Fatalf("TLS Config check on valid but bad certificate should warn")
- }
- if !strings.Contains(warnings[0], "Found multiple root certificates in CA Certificate file instead of just one.") {
- t.Fatalf("Bad warning: %s", warnings[0])
- }
-}
-
-// TestTLSSelfSignedCerts tests invalid self-signed cert as TLSClientCAFile
-func TestTLSSelfSignedCert(t *testing.T) {
- listeners := []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1:443",
- ClusterAddress: "127.0.0.1:8201",
- TLSCertFile: "./../../api/test-fixtures/keys/cert.pem",
- TLSKeyFile: "./../../api/test-fixtures/keys/key.pem",
- TLSClientCAFile: "test-fixtures/selfSignedCert.pem",
- TLSMinVersion: "tls10",
- TLSRequireAndVerifyClientCert: true,
- TLSDisableClientCerts: false,
- },
- }
- _, errs := ListenerChecks(context.Background(), listeners)
- if errs == nil {
- t.Fatalf("Self-signed certificate is insecure")
- }
- if !strings.Contains(errs[0].Error(), "No root certificate found") {
- t.Fatalf("Bad error message: %s", errs[0])
- }
-}
diff --git a/vault/dynamic_system_view_test.go b/vault/dynamic_system_view_test.go
deleted file mode 100644
index ab360c773..000000000
--- a/vault/dynamic_system_view_test.go
+++ /dev/null
@@ -1,323 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "encoding/base64"
- "fmt"
- "reflect"
- "sort"
- "testing"
- "time"
-
- log "github.com/hashicorp/go-hclog"
- ldapcred "github.com/hashicorp/vault/builtin/credential/ldap"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/framework"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-var (
- testPolicyName = "testpolicy"
- rawTestPasswordPolicy = `
-length = 20
-rule "charset" {
- charset = "abcdefghijklmnopqrstuvwxyz"
- min_chars = 1
-}
-rule "charset" {
- charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- min_chars = 1
-}
-rule "charset" {
- charset = "0123456789"
- min_chars = 1
-}`
-)
-
-func TestIdentity_BackendTemplating(t *testing.T) {
- var err error
- coreConfig := &CoreConfig{
- DisableMlock: true,
- DisableCache: true,
- Logger: log.NewNullLogger(),
- CredentialBackends: map[string]logical.Factory{
- "ldap": ldapcred.Factory,
- },
- }
-
- cluster := NewTestCluster(t, coreConfig, &TestClusterOptions{})
-
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
-
- TestWaitActive(t, core)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "sys/auth/ldap")
- req.ClientToken = cluster.RootToken
- req.Data["type"] = "ldap"
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "sys/auth")
- req.ClientToken = cluster.RootToken
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- accessor := resp.Data["ldap/"].(map[string]interface{})["accessor"].(string)
-
- // Create an entity
- req = logical.TestRequest(t, logical.UpdateOperation, "identity/entity")
- req.ClientToken = cluster.RootToken
- req.Data["name"] = "entity1"
- req.Data["metadata"] = map[string]string{
- "organization": "hashicorp",
- "team": "vault",
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
-
- entityID := resp.Data["id"].(string)
-
- // Create an alias
- req = logical.TestRequest(t, logical.UpdateOperation, "identity/entity-alias")
- req.ClientToken = cluster.RootToken
- req.Data["name"] = "alias1"
- req.Data["canonical_id"] = entityID
- req.Data["mount_accessor"] = accessor
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
-
- aliasID := resp.Data["id"].(string)
-
- // Create a group
- req = logical.TestRequest(t, logical.UpdateOperation, "identity/group")
- req.ClientToken = cluster.RootToken
- req.Data["name"] = "group1"
- req.Data["member_entity_ids"] = []string{entityID}
- req.Data["metadata"] = map[string]string{
- "group": "vault",
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
-
- groupID := resp.Data["id"].(string)
-
- // Get the ldap mount
- sysView := core.router.MatchingSystemView(namespace.RootContext(nil), "auth/ldap/")
-
- tCases := []struct {
- tpl string
- expected string
- }{
- {
- tpl: "{{identity.entity.id}}",
- expected: entityID,
- },
- {
- tpl: "{{identity.entity.name}}",
- expected: "entity1",
- },
- {
- tpl: "{{identity.entity.metadata.organization}}",
- expected: "hashicorp",
- },
- {
- tpl: "{{identity.entity.aliases." + accessor + ".id}}",
- expected: aliasID,
- },
- {
- tpl: "{{identity.entity.aliases." + accessor + ".name}}",
- expected: "alias1",
- },
- {
- tpl: "{{identity.groups.ids." + groupID + ".name}}",
- expected: "group1",
- },
- {
- tpl: "{{identity.groups.names.group1.id}}",
- expected: groupID,
- },
- {
- tpl: "{{identity.groups.names.group1.metadata.group}}",
- expected: "vault",
- },
- {
- tpl: "{{identity.groups.ids." + groupID + ".metadata.group}}",
- expected: "vault",
- },
- }
-
- for _, tCase := range tCases {
- out, err := framework.PopulateIdentityTemplate(tCase.tpl, entityID, sysView)
- if err != nil {
- t.Fatal(err)
- }
-
- if out != tCase.expected {
- t.Fatalf("got %q, expected %q", out, tCase.expected)
- }
- }
-}
-
-func TestDynamicSystemView_GeneratePasswordFromPolicy_successful(t *testing.T) {
- var err error
- coreConfig := &CoreConfig{
- DisableMlock: true,
- DisableCache: true,
- Logger: log.NewNullLogger(),
- CredentialBackends: map[string]logical.Factory{},
- }
-
- cluster := NewTestCluster(t, coreConfig, &TestClusterOptions{})
-
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- TestWaitActive(t, core)
-
- b64Policy := base64.StdEncoding.EncodeToString([]byte(rawTestPasswordPolicy))
-
- path := fmt.Sprintf("sys/policies/password/%s", testPolicyName)
- req := logical.TestRequest(t, logical.CreateOperation, path)
- req.ClientToken = cluster.RootToken
- req.Data["policy"] = b64Policy
-
- _, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
- defer cancel()
-
- ctx = namespace.RootContext(ctx)
- dsv := TestDynamicSystemView(cluster.Cores[0].Core, nil)
-
- runeset := map[rune]bool{}
- runesFound := []rune{}
-
- for i := 0; i < 100; i++ {
- actual, err := dsv.GeneratePasswordFromPolicy(ctx, testPolicyName)
- if err != nil {
- t.Fatalf("no error expected, but got: %s", err)
- }
- for _, r := range actual {
- if runeset[r] {
- continue
- }
- runeset[r] = true
- runesFound = append(runesFound, r)
- }
- }
-
- sort.Sort(runes(runesFound))
-
- expectedRunes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
- sort.Sort(runes(expectedRunes)) // Sort it so they can be compared
-
- if !reflect.DeepEqual(runesFound, expectedRunes) {
- t.Fatalf("Didn't find all characters from the charset\nActual : [%s]\nExpected: [%s]", string(runesFound), string(expectedRunes))
- }
-}
-
-func TestDynamicSystemView_GeneratePasswordFromPolicy_failed(t *testing.T) {
- type testCase struct {
- policyName string
- getEntry *logical.StorageEntry
- getErr error
- }
-
- tests := map[string]testCase{
- "no policy name": {
- policyName: "",
- },
- "no policy found": {
- policyName: "testpolicy",
- getEntry: nil,
- getErr: nil,
- },
- "error retrieving policy": {
- policyName: "testpolicy",
- getEntry: nil,
- getErr: fmt.Errorf("a test error"),
- },
- "saved policy is malformed": {
- policyName: "testpolicy",
- getEntry: &logical.StorageEntry{
- Key: getPasswordPolicyKey("testpolicy"),
- Value: []byte(`{"policy":"asdfahsdfasdf"}`),
- },
- getErr: nil,
- },
- }
-
- for name, test := range tests {
- t.Run(name, func(t *testing.T) {
- testStorage := fakeBarrier{
- getEntry: test.getEntry,
- getErr: test.getErr,
- }
-
- core := &Core{
- systemBarrierView: NewBarrierView(testStorage, "sys/"),
- }
- dsv := TestDynamicSystemView(core, nil)
-
- ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
- defer cancel()
- actualPassword, err := dsv.GeneratePasswordFromPolicy(ctx, test.policyName)
- if err == nil {
- t.Fatalf("err expected, got nil")
- }
- if actualPassword != "" {
- t.Fatalf("no password expected, got %s", actualPassword)
- }
- })
- }
-}
-
-type runes []rune
-
-func (r runes) Len() int { return len(r) }
-func (r runes) Less(i, j int) bool { return r[i] < r[j] }
-func (r runes) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
-
-type fakeBarrier struct {
- getEntry *logical.StorageEntry
- getErr error
-}
-
-func (b fakeBarrier) Get(context.Context, string) (*logical.StorageEntry, error) {
- return b.getEntry, b.getErr
-}
-
-func (b fakeBarrier) List(context.Context, string) ([]string, error) {
- return nil, fmt.Errorf("not implemented")
-}
-
-func (b fakeBarrier) Put(context.Context, *logical.StorageEntry) error {
- return fmt.Errorf("not implemented")
-}
-
-func (b fakeBarrier) Delete(context.Context, string) error {
- return fmt.Errorf("not implemented")
-}
diff --git a/vault/eventbus/bus_test.go b/vault/eventbus/bus_test.go
deleted file mode 100644
index 8d0afbe66..000000000
--- a/vault/eventbus/bus_test.go
+++ /dev/null
@@ -1,420 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package eventbus
-
-import (
- "context"
- "fmt"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/hashicorp/eventlogger"
- "github.com/hashicorp/go-secure-stdlib/strutil"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-// TestBusBasics tests that basic event sending and subscribing function.
-func TestBusBasics(t *testing.T) {
- bus, err := NewEventBus(nil)
- if err != nil {
- t.Fatal(err)
- }
- ctx := context.Background()
-
- eventType := logical.EventType("someType")
-
- event, err := logical.NewEvent()
- if err != nil {
- t.Fatal(err)
- }
-
- err = bus.SendInternal(ctx, namespace.RootNamespace, nil, eventType, event)
- if err != ErrNotStarted {
- t.Errorf("Expected not started error but got: %v", err)
- }
-
- bus.Start()
-
- err = bus.SendInternal(ctx, namespace.RootNamespace, nil, eventType, event)
- if err != nil {
- t.Errorf("Expected no error sending: %v", err)
- }
-
- ch, cancel, err := bus.Subscribe(ctx, namespace.RootNamespace, string(eventType))
- if err != nil {
- t.Fatal(err)
- }
- defer cancel()
-
- event, err = logical.NewEvent()
- if err != nil {
- t.Fatal(err)
- }
-
- err = bus.SendInternal(ctx, namespace.RootNamespace, nil, eventType, event)
- if err != nil {
- t.Error(err)
- }
-
- timeout := time.After(1 * time.Second)
- select {
- case message := <-ch:
- if message.Payload.(*logical.EventReceived).Event.Id != event.Id {
- t.Errorf("Got unexpected message: %+v", message)
- }
- case <-timeout:
- t.Error("Timeout waiting for message")
- }
-}
-
-// TestBusIgnoresSendContext tests that the context is ignored when sending to an event,
-// so that we do not give up too quickly.
-func TestBusIgnoresSendContext(t *testing.T) {
- bus, err := NewEventBus(nil)
- if err != nil {
- t.Fatal(err)
- }
- eventType := logical.EventType("someType")
-
- event, err := logical.NewEvent()
- if err != nil {
- t.Fatal(err)
- }
-
- bus.Start()
-
- ch, subCancel, err := bus.Subscribe(context.Background(), namespace.RootNamespace, string(eventType))
- if err != nil {
- t.Fatal(err)
- }
- defer subCancel()
-
- ctx, cancel := context.WithCancel(context.Background())
- cancel() // cancel immediately
-
- err = bus.SendInternal(ctx, namespace.RootNamespace, nil, eventType, event)
- if err != nil {
- t.Errorf("Expected no error sending: %v", err)
- }
-
- timeout := time.After(1 * time.Second)
- select {
- case message := <-ch:
- if message.Payload.(*logical.EventReceived).Event.Id != event.Id {
- t.Errorf("Got unexpected message: %+v", message)
- }
- case <-timeout:
- t.Error("Timeout waiting for message")
- }
-}
-
-// TestNamespaceFiltering verifies that events for other namespaces are filtered out by the bus.
-func TestNamespaceFiltering(t *testing.T) {
- bus, err := NewEventBus(nil)
- if err != nil {
- t.Fatal(err)
- }
- bus.Start()
- ctx := context.Background()
-
- eventType := logical.EventType("someType")
-
- event, err := logical.NewEvent()
- if err != nil {
- t.Fatal(err)
- }
-
- ch, cancel, err := bus.Subscribe(ctx, namespace.RootNamespace, string(eventType))
- if err != nil {
- t.Fatal(err)
- }
- defer cancel()
-
- event, err = logical.NewEvent()
- if err != nil {
- t.Fatal(err)
- }
-
- err = bus.SendInternal(ctx, &namespace.Namespace{
- ID: "abc",
- Path: "/abc",
- }, nil, eventType, event)
- if err != nil {
- t.Error(err)
- }
-
- timeout := time.After(100 * time.Millisecond)
- select {
- case <-ch:
- t.Errorf("Got abc namespace message when root namespace was specified")
- case <-timeout:
- // okay
- }
-
- err = bus.SendInternal(ctx, namespace.RootNamespace, nil, eventType, event)
- if err != nil {
- t.Error(err)
- }
-
- timeout = time.After(1 * time.Second)
- select {
- case message := <-ch:
- if message.Payload.(*logical.EventReceived).Event.Id != event.Id {
- t.Errorf("Got unexpected message %+v but was waiting for %+v", message, event)
- }
-
- case <-timeout:
- t.Error("Timed out waiting for message")
- }
-}
-
-// TestBus2Subscriptions verifies that events of different types are successfully routed to the correct subscribers.
-func TestBus2Subscriptions(t *testing.T) {
- bus, err := NewEventBus(nil)
- if err != nil {
- t.Fatal(err)
- }
- ctx := context.Background()
-
- eventType1 := logical.EventType("someType1")
- eventType2 := logical.EventType("someType2")
- bus.Start()
-
- ch1, cancel1, err := bus.Subscribe(ctx, namespace.RootNamespace, string(eventType1))
- if err != nil {
- t.Fatal(err)
- }
- defer cancel1()
-
- ch2, cancel2, err := bus.Subscribe(ctx, namespace.RootNamespace, string(eventType2))
- if err != nil {
- t.Fatal(err)
- }
- defer cancel2()
-
- event1, err := logical.NewEvent()
- if err != nil {
- t.Fatal(err)
- }
- event2, err := logical.NewEvent()
- if err != nil {
- t.Fatal(err)
- }
-
- err = bus.SendInternal(ctx, namespace.RootNamespace, nil, eventType2, event2)
- if err != nil {
- t.Error(err)
- }
- err = bus.SendInternal(ctx, namespace.RootNamespace, nil, eventType1, event1)
- if err != nil {
- t.Error(err)
- }
-
- timeout := time.After(1 * time.Second)
- select {
- case message := <-ch1:
- if message.Payload.(*logical.EventReceived).Event.Id != event1.Id {
- t.Errorf("Got unexpected message: %v", message)
- }
- case <-timeout:
- t.Error("Timeout waiting for event1")
- }
- select {
- case message := <-ch2:
- if message.Payload.(*logical.EventReceived).Event.Id != event2.Id {
- t.Errorf("Got unexpected message: %v", message)
- }
- case <-timeout:
- t.Error("Timeout waiting for event2")
- }
-}
-
-// TestBusSubscriptionsCancel verifies that canceled subscriptions are cleaned up.
-func TestBusSubscriptionsCancel(t *testing.T) {
- testCases := []struct {
- cancel bool
- }{
- {cancel: true},
- {cancel: false},
- }
-
- for _, tc := range testCases {
- t.Run(fmt.Sprintf("cancel=%v", tc.cancel), func(t *testing.T) {
- subscriptions.Store(0)
- bus, err := NewEventBus(nil)
- if err != nil {
- t.Fatal(err)
- }
- ctx := context.Background()
- if !tc.cancel {
- // set the timeout very short to make the test faster if we aren't canceling explicitly
- bus.SetSendTimeout(100 * time.Millisecond)
- }
- bus.Start()
-
- // create and stop a bunch of subscriptions
- const create = 100
- const stop = 50
-
- eventType := logical.EventType("someType")
-
- var channels []<-chan *eventlogger.Event
- var cancels []context.CancelFunc
- stopped := atomic.Int32{}
-
- received := atomic.Int32{}
-
- for i := 0; i < create; i++ {
- ch, cancelFunc, err := bus.Subscribe(ctx, namespace.RootNamespace, string(eventType))
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(cancelFunc)
- channels = append(channels, ch)
- cancels = append(cancels, cancelFunc)
-
- go func(i int32) {
- <-ch // always receive one message
- received.Add(1)
- // continue receiving messages as long as are not stopped
- for i < int32(stop) {
- <-ch
- received.Add(1)
- }
- if tc.cancel {
- cancelFunc() // stop explicitly to unsubscribe
- }
- stopped.Add(1)
- }(int32(i))
- }
-
- // check that all channels receive a message
- event, err := logical.NewEvent()
- if err != nil {
- t.Fatal(err)
- }
- err = bus.SendInternal(ctx, namespace.RootNamespace, nil, eventType, event)
- if err != nil {
- t.Error(err)
- }
- waitFor(t, 1*time.Second, func() bool { return received.Load() == int32(create) })
- waitFor(t, 1*time.Second, func() bool { return stopped.Load() == int32(stop) })
-
- // send another message, but half should stop receiving
- event, err = logical.NewEvent()
- if err != nil {
- t.Fatal(err)
- }
- err = bus.SendInternal(ctx, namespace.RootNamespace, nil, eventType, event)
- if err != nil {
- t.Error(err)
- }
- waitFor(t, 1*time.Second, func() bool { return received.Load() == int32(create*2-stop) })
- // the sends should time out and the subscriptions should drop when cancelFunc is called or the context cancels
- waitFor(t, 1*time.Second, func() bool { return subscriptions.Load() == int64(create-stop) })
- })
- }
-}
-
-// waitFor waits for a condition to be true, up to the maximum timeout.
-// It waits with a capped exponential backoff starting at 1ms.
-// It is guaranteed to try f() at least once.
-func waitFor(t *testing.T, maxWait time.Duration, f func() bool) {
- t.Helper()
- start := time.Now()
-
- if f() {
- return
- }
- sleepAmount := 1 * time.Millisecond
- for time.Now().Sub(start) <= maxWait {
- left := time.Now().Sub(start)
- sleepAmount = sleepAmount * 2
- if sleepAmount > left {
- sleepAmount = left
- }
- time.Sleep(sleepAmount)
- if f() {
- return
- }
- }
- t.Error("Timeout waiting for condition")
-}
-
-// TestBusWildcardSubscriptions tests that a single subscription can receive
-// multiple event types using * for glob patterns.
-func TestBusWildcardSubscriptions(t *testing.T) {
- bus, err := NewEventBus(nil)
- if err != nil {
- t.Fatal(err)
- }
- ctx := context.Background()
-
- fooEventType := logical.EventType("kv/foo")
- barEventType := logical.EventType("kv/bar")
- bus.Start()
-
- ch1, cancel1, err := bus.Subscribe(ctx, namespace.RootNamespace, "kv/*")
- if err != nil {
- t.Fatal(err)
- }
- defer cancel1()
-
- ch2, cancel2, err := bus.Subscribe(ctx, namespace.RootNamespace, "*/bar")
- if err != nil {
- t.Fatal(err)
- }
- defer cancel2()
-
- event1, err := logical.NewEvent()
- if err != nil {
- t.Fatal(err)
- }
- event2, err := logical.NewEvent()
- if err != nil {
- t.Fatal(err)
- }
-
- err = bus.SendInternal(ctx, namespace.RootNamespace, nil, barEventType, event2)
- if err != nil {
- t.Error(err)
- }
- err = bus.SendInternal(ctx, namespace.RootNamespace, nil, fooEventType, event1)
- if err != nil {
- t.Error(err)
- }
-
- timeout := time.After(1 * time.Second)
- // Expect to receive both events on ch1, which subscribed to kv/*
- var ch1Seen []string
- for i := 0; i < 2; i++ {
- select {
- case message := <-ch1:
- ch1Seen = append(ch1Seen, message.Payload.(*logical.EventReceived).Event.Id)
- case <-timeout:
- t.Error("Timeout waiting for event1")
- }
- }
- if len(ch1Seen) != 2 {
- t.Errorf("Expected 2 events but got: %v", ch1Seen)
- } else {
- if !strutil.StrListContains(ch1Seen, event1.Id) {
- t.Errorf("Did not find %s event1 ID in ch1seen", event1.Id)
- }
- if !strutil.StrListContains(ch1Seen, event2.Id) {
- t.Errorf("Did not find %s event2 ID in ch1seen", event2.Id)
- }
- }
- // Expect to receive just kv/bar on ch2, which subscribed to */bar
- select {
- case message := <-ch2:
- if message.Payload.(*logical.EventReceived).Event.Id != event2.Id {
- t.Errorf("Got unexpected message: %v", message)
- }
- case <-timeout:
- t.Error("Timeout waiting for event2")
- }
-}
diff --git a/vault/events_test.go b/vault/events_test.go
deleted file mode 100644
index 12610ca4f..000000000
--- a/vault/events_test.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "testing"
- "time"
-
- "github.com/hashicorp/go-uuid"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestCanSendEventsFromBuiltinPlugin(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
-
- ctx := namespace.RootContext(nil)
-
- // subscribe to an event type
- eventType, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- ch, cancel, err := c.events.Subscribe(ctx, namespace.RootNamespace, eventType)
- if err != nil {
- t.Fatal(err)
- }
- defer cancel()
-
- // generate the event in a plugin
- event, err := logical.NewEvent()
- if err != nil {
- t.Fatal(err)
- }
- err = c.cubbyholeBackend.SendEvent(ctx, logical.EventType(eventType), event)
- if err != nil {
- t.Fatal(err)
- }
-
- // check that the event is routed to the subscription
- select {
- case receivedEvent := <-ch:
- received := receivedEvent.Payload.(*logical.EventReceived)
- if event.Id != received.Event.Id {
- t.Errorf("Got wrong event: %+v, expected %+v", received, event)
- }
- if received.PluginInfo == nil {
- t.Error("Expected plugin info but got nil")
- } else {
- if received.PluginInfo.Plugin != "cubbyhole" {
- t.Errorf("Expected cubbyhole plugin but got %s", received.PluginInfo.Plugin)
- }
- }
-
- case <-time.After(1 * time.Second):
- t.Error("timeout waiting for event")
- }
-}
diff --git a/vault/expiration_integ_test.go b/vault/expiration_integ_test.go
deleted file mode 100644
index 75f077b14..000000000
--- a/vault/expiration_integ_test.go
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault_test
-
-import (
- "encoding/json"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/builtin/credential/approle"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-func TestExpiration_RenewToken_TestCluster(t *testing.T) {
- // Use a TestCluster and the approle backend to test renewal
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "approle": approle.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // Mount the auth backend
- err := client.Sys().EnableAuthWithOptions("approle", &api.EnableAuthOptions{
- Type: "approle",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Tune the mount
- err = client.Sys().TuneMount("auth/approle", api.MountConfigInput{
- DefaultLeaseTTL: "5s",
- MaxLeaseTTL: "5s",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create role
- resp, err := client.Logical().Write("auth/approle/role/role-period", map[string]interface{}{
- "period": "5s",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Get role_id
- resp, err = client.Logical().Read("auth/approle/role/role-period/role-id")
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for fetching the role-id")
- }
- roleID := resp.Data["role_id"]
-
- // Get secret_id
- resp, err = client.Logical().Write("auth/approle/role/role-period/secret-id", map[string]interface{}{})
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for fetching the secret-id")
- }
- secretID := resp.Data["secret_id"]
-
- // Login
- resp, err = client.Logical().Write("auth/approle/login", map[string]interface{}{
- "role_id": roleID,
- "secret_id": secretID,
- })
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for login")
- }
- if resp.Auth == nil {
- t.Fatal("expected auth object from response")
- }
- if resp.Auth.ClientToken == "" {
- t.Fatal("expected a client token")
- }
-
- roleToken := resp.Auth.ClientToken
- // Wait 3 seconds
- time.Sleep(3 * time.Second)
-
- // Renew
- resp, err = client.Logical().Write("auth/token/renew", map[string]interface{}{
- "token": roleToken,
- })
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for renew")
- }
-
- // Perform token lookup and verify TTL
- resp, err = client.Auth().Token().Lookup(roleToken)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for token lookup")
- }
-
- ttlRaw, ok := resp.Data["ttl"].(json.Number)
- if !ok {
- t.Fatal("no ttl value found in data object")
- }
- ttlInt, err := ttlRaw.Int64()
- if err != nil {
- t.Fatalf("unable to convert ttl to int: %s", err)
- }
- ttl := time.Duration(ttlInt) * time.Second
- if ttl < 4*time.Second {
- t.Fatal("expected ttl value to be around 5s")
- }
-
- // Wait 3 seconds
- time.Sleep(3 * time.Second)
-
- // Do a second renewal to ensure that period can be renewed past sys/mount max_ttl
- resp, err = client.Logical().Write("auth/token/renew", map[string]interface{}{
- "token": roleToken,
- })
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for renew")
- }
-
- // Perform token lookup and verify TTL
- resp, err = client.Auth().Token().Lookup(roleToken)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for token lookup")
- }
-
- ttlRaw, ok = resp.Data["ttl"].(json.Number)
- if !ok {
- t.Fatal("no ttl value found in data object")
- }
- ttlInt, err = ttlRaw.Int64()
- if err != nil {
- t.Fatalf("unable to convert ttl to int: %s", err)
- }
- ttl = time.Duration(ttlInt) * time.Second
- if ttl < 4*time.Second {
- t.Fatal("expected ttl value to be around 5s")
- }
-}
diff --git a/vault/expiration_test.go b/vault/expiration_test.go
deleted file mode 100644
index a790f3f34..000000000
--- a/vault/expiration_test.go
+++ /dev/null
@@ -1,3502 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "bytes"
- "context"
- "errors"
- "fmt"
- "os"
- "reflect"
- "sort"
- "strings"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/armon/go-metrics"
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/go-uuid"
- "github.com/hashicorp/vault/helper/benchhelpers"
- "github.com/hashicorp/vault/helper/fairshare"
- "github.com/hashicorp/vault/helper/metricsutil"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/framework"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/sdk/physical"
- "github.com/hashicorp/vault/sdk/physical/inmem"
-)
-
-var testImagePull sync.Once
-
-// mockExpiration returns a mock expiration manager
-func mockExpiration(t testing.TB) *ExpirationManager {
- c, _, _ := TestCoreUnsealed(benchhelpers.TBtoT(t))
-
- // Wait until the expiration manager is out of restore mode.
- // This was added to prevent sporadic failures of TestExpiration_unrecoverableErrorMakesIrrevocable.
- timeout := time.Now().Add(time.Second * 10)
- for c.expiration.inRestoreMode() {
- if time.Now().After(timeout) {
- t.Fatal("ExpirationManager is still in restore mode after 10 seconds")
- }
- time.Sleep(50 * time.Millisecond)
- }
-
- return c.expiration
-}
-
-func mockBackendExpiration(t testing.TB, backend physical.Backend) (*Core, *ExpirationManager) {
- c, _, _ := TestCoreUnsealedBackend(benchhelpers.TBtoT(t), backend)
- return c, c.expiration
-}
-
-func TestExpiration_Metrics(t *testing.T) {
- var err error
-
- testCore := TestCore(t)
- testCore.baseLogger = logger
- testCore.logger = logger.Named("core")
- testCoreUnsealed(t, testCore)
-
- exp := testCore.expiration
-
- if err := exp.Restore(nil); err != nil {
- t.Fatal(err)
- }
-
- // Set up a count function to calculate number of leases
- count := 0
- countFunc := func(_ string) {
- count++
- }
-
- // Scan the storage with the count func set
- if err = logical.ScanView(namespace.RootContext(nil), exp.idView, countFunc); err != nil {
- t.Fatal(err)
- }
-
- // Check that there are no leases to begin with
- if count != 0 {
- t.Fatalf("bad: lease count; expected:0 actual:%d", count)
- }
-
- for i := 0; i < 50; i++ {
- le := &leaseEntry{
- LeaseID: "lease" + fmt.Sprintf("%d", i),
- Path: "foo/bar/" + fmt.Sprintf("%d", i),
- namespace: namespace.RootNamespace,
- IssueTime: time.Now(),
- ExpireTime: time.Now().Add(time.Hour),
- }
-
- otherNS := &namespace.Namespace{
- ID: "nsid",
- Path: "foo/bar",
- }
-
- otherNSle := &leaseEntry{
- LeaseID: "lease" + fmt.Sprintf("%d", i) + "/blah.nsid",
- Path: "foo/bar/" + fmt.Sprintf("%d", i) + "/blah.nsid",
- namespace: otherNS,
- IssueTime: time.Now(),
- ExpireTime: time.Now().Add(time.Hour),
- }
-
- exp.pendingLock.Lock()
- if err := exp.persistEntry(namespace.RootContext(nil), le); err != nil {
- exp.pendingLock.Unlock()
- t.Fatalf("error persisting entry: %v", err)
- }
- exp.updatePendingInternal(le)
-
- if err := exp.persistEntry(namespace.RootContext(nil), otherNSle); err != nil {
- exp.pendingLock.Unlock()
- t.Fatalf("error persisting entry: %v", err)
- }
- exp.updatePendingInternal(otherNSle)
- exp.pendingLock.Unlock()
- }
-
- for i := 50; i < 250; i++ {
- le := &leaseEntry{
- LeaseID: "lease" + fmt.Sprintf("%d", i+1),
- Path: "foo/bar/" + fmt.Sprintf("%d", i+1),
- namespace: namespace.RootNamespace,
- IssueTime: time.Now(),
- ExpireTime: time.Now().Add(2 * time.Hour),
- }
-
- exp.pendingLock.Lock()
- if err := exp.persistEntry(namespace.RootContext(nil), le); err != nil {
- exp.pendingLock.Unlock()
- t.Fatalf("error persisting entry: %v", err)
- }
- exp.updatePendingInternal(le)
- exp.pendingLock.Unlock()
- }
-
- count = 0
- if err = logical.ScanView(context.Background(), exp.idView, countFunc); err != nil {
- t.Fatal(err)
- }
-
- var conf metricsutil.TelemetryConstConfig = metricsutil.TelemetryConstConfig{
- LeaseMetricsEpsilon: time.Hour,
- NumLeaseMetricsTimeBuckets: 2,
- LeaseMetricsNameSpaceLabels: true,
- }
-
- flattenedResults, err := exp.leaseAggregationMetrics(context.Background(), conf)
- if err != nil {
- t.Fatal(err)
- }
- if flattenedResults == nil {
- t.Fatal("lease aggregation returns nil metrics")
- }
-
- labelOneHour := metrics.Label{"expiring", time.Now().Add(time.Hour).Round(time.Hour).String()}
- labelTwoHours := metrics.Label{"expiring", time.Now().Add(2 * time.Hour).Round(time.Hour).String()}
- nsLabel := metrics.Label{"namespace", "root"}
- nsLabelNonRoot := metrics.Label{"namespace", "nsid"}
-
- foundLabelOne := false
- foundLabelTwo := false
- foundLabelThree := false
-
- for _, labelVal := range flattenedResults {
- retNsLabel := labelVal.Labels[1]
- retTimeLabel := labelVal.Labels[0]
- if nsLabel == retNsLabel {
- if labelVal.Value == 50 {
- if retTimeLabel == labelOneHour {
- foundLabelOne = true
- }
- }
- if labelVal.Value == 200 {
- if retTimeLabel == labelTwoHours {
- foundLabelTwo = true
- }
- }
- } else if retNsLabel == nsLabelNonRoot {
- if labelVal.Value == 50 {
- if retTimeLabel == labelOneHour {
- foundLabelThree = true
- }
- }
- }
- }
-
- if !foundLabelOne || !foundLabelTwo || !foundLabelThree {
- t.Errorf("One of the labels is missing. one: %t, two: %t, three: %t", foundLabelOne, foundLabelTwo, foundLabelThree)
- }
-
- // test the same leases while ignoring namespaces so the 2 different namespaces get aggregated
- conf = metricsutil.TelemetryConstConfig{
- LeaseMetricsEpsilon: time.Hour,
- NumLeaseMetricsTimeBuckets: 2,
- LeaseMetricsNameSpaceLabels: false,
- }
-
- flattenedResults, err = exp.leaseAggregationMetrics(context.Background(), conf)
- if err != nil {
- t.Fatal(err)
- }
- if flattenedResults == nil {
- t.Fatal("lease aggregation returns nil metrics")
- }
-
- foundLabelOne = false
- foundLabelTwo = false
-
- for _, labelVal := range flattenedResults {
- if len(labelVal.Labels) != 1 {
- t.Errorf("Namespace label is returned when explicitly not requested.")
- }
- retTimeLabel := labelVal.Labels[0]
- if labelVal.Value == 100 {
- if retTimeLabel == labelOneHour {
- foundLabelOne = true
- }
- }
- if labelVal.Value == 200 {
- if retTimeLabel == labelTwoHours {
- foundLabelTwo = true
- }
- }
- }
- if !foundLabelOne || !foundLabelTwo {
- t.Errorf("One of the labels is missing")
- }
-}
-
-func TestExpiration_TotalLeaseCount(t *testing.T) {
- // Quotas and internal lease count tracker are coupled, so this is a proxy
- // for testing the total lease count quota
- c, _, _ := TestCoreUnsealed(t)
- exp := c.expiration
-
- expectedCount := 0
- otherNS := &namespace.Namespace{
- ID: "nsid",
- Path: "foo/bar",
- }
- for i := 0; i < 50; i++ {
- le := &leaseEntry{
- LeaseID: "lease" + fmt.Sprintf("%d", i),
- Path: "foo/bar/" + fmt.Sprintf("%d", i),
- namespace: namespace.RootNamespace,
- IssueTime: time.Now(),
- ExpireTime: time.Now().Add(time.Hour),
- }
-
- otherNSle := &leaseEntry{
- LeaseID: "lease" + fmt.Sprintf("%d", i) + "/blah.nsid",
- Path: "foo/bar/" + fmt.Sprintf("%d", i) + "/blah.nsid",
- namespace: otherNS,
- IssueTime: time.Now(),
- ExpireTime: time.Now().Add(time.Hour),
- }
-
- exp.pendingLock.Lock()
- if err := exp.persistEntry(namespace.RootContext(nil), le); err != nil {
- exp.pendingLock.Unlock()
- t.Fatalf("error persisting irrevocable entry: %v", err)
- }
- exp.updatePendingInternal(le)
- expectedCount++
-
- if err := exp.persistEntry(namespace.RootContext(nil), otherNSle); err != nil {
- exp.pendingLock.Unlock()
- t.Fatalf("error persisting irrevocable entry: %v", err)
- }
- exp.updatePendingInternal(otherNSle)
- expectedCount++
- exp.pendingLock.Unlock()
- }
-
- // add some irrevocable leases to each count to ensure they are counted too
- // note: irrevocable leases almost certainly have an expire time set in the
- // past, but for this exercise it should be fine to set it to whatever
- for i := 50; i < 60; i++ {
- le := &leaseEntry{
- LeaseID: "lease" + fmt.Sprintf("%d", i+1),
- Path: "foo/bar/" + fmt.Sprintf("%d", i+1),
- namespace: namespace.RootNamespace,
- IssueTime: time.Now(),
- ExpireTime: time.Now(),
- RevokeErr: "some err message",
- }
-
- otherNSle := &leaseEntry{
- LeaseID: "lease" + fmt.Sprintf("%d", i+1) + "/blah.nsid",
- Path: "foo/bar/" + fmt.Sprintf("%d", i+1) + "/blah.nsid",
- namespace: otherNS,
- IssueTime: time.Now(),
- ExpireTime: time.Now(),
- RevokeErr: "some err message",
- }
-
- exp.pendingLock.Lock()
- if err := exp.persistEntry(namespace.RootContext(nil), le); err != nil {
- exp.pendingLock.Unlock()
- t.Fatalf("error persisting irrevocable entry: %v", err)
- }
- exp.updatePendingInternal(le)
- expectedCount++
-
- if err := exp.persistEntry(namespace.RootContext(nil), otherNSle); err != nil {
- exp.pendingLock.Unlock()
- t.Fatalf("error persisting irrevocable entry: %v", err)
- }
- exp.updatePendingInternal(otherNSle)
- expectedCount++
- exp.pendingLock.Unlock()
- }
-
- exp.pendingLock.RLock()
- count := exp.leaseCount
- exp.pendingLock.RUnlock()
-
- if count != expectedCount {
- t.Errorf("bad lease count. expected %d, got %d", expectedCount, count)
- }
-}
-
-func TestExpiration_TotalLeaseCount_WithRoles(t *testing.T) {
- // Quotas and internal lease count tracker are coupled, so this is a proxy
- // for testing the total lease count quota
- c, _, _ := TestCoreUnsealed(t)
- exp := c.expiration
-
- expectedCount := 0
- otherNS := &namespace.Namespace{
- ID: "nsid",
- Path: "foo/bar",
- }
- for i := 0; i < 50; i++ {
- le := &leaseEntry{
- LeaseID: "lease" + fmt.Sprintf("%d", i),
- Path: "foo/bar/" + fmt.Sprintf("%d", i),
- LoginRole: "loginRole" + fmt.Sprintf("%d", i),
- namespace: namespace.RootNamespace,
- IssueTime: time.Now(),
- ExpireTime: time.Now().Add(time.Hour),
- }
-
- otherNSle := &leaseEntry{
- LeaseID: "lease" + fmt.Sprintf("%d", i) + "/blah.nsid",
- Path: "foo/bar/" + fmt.Sprintf("%d", i) + "/blah.nsid",
- LoginRole: "loginRole" + fmt.Sprintf("%d", i),
- namespace: otherNS,
- IssueTime: time.Now(),
- ExpireTime: time.Now().Add(time.Hour),
- }
-
- exp.pendingLock.Lock()
- if err := exp.persistEntry(namespace.RootContext(nil), le); err != nil {
- exp.pendingLock.Unlock()
- t.Fatalf("error persisting irrevocable entry: %v", err)
- }
- exp.updatePendingInternal(le)
- expectedCount++
-
- if err := exp.persistEntry(namespace.RootContext(nil), otherNSle); err != nil {
- exp.pendingLock.Unlock()
- t.Fatalf("error persisting irrevocable entry: %v", err)
- }
- exp.updatePendingInternal(otherNSle)
- expectedCount++
- exp.pendingLock.Unlock()
- }
-
- // add some irrevocable leases to each count to ensure they are counted too
- // note: irrevocable leases almost certainly have an expire time set in the
- // past, but for this exercise it should be fine to set it to whatever
- for i := 50; i < 60; i++ {
- le := &leaseEntry{
- LeaseID: "lease" + fmt.Sprintf("%d", i+1),
- Path: "foo/bar/" + fmt.Sprintf("%d", i+1),
- LoginRole: "loginRole" + fmt.Sprintf("%d", i),
- namespace: namespace.RootNamespace,
- IssueTime: time.Now(),
- ExpireTime: time.Now(),
- RevokeErr: "some err message",
- }
-
- otherNSle := &leaseEntry{
- LeaseID: "lease" + fmt.Sprintf("%d", i+1) + "/blah.nsid",
- Path: "foo/bar/" + fmt.Sprintf("%d", i+1) + "/blah.nsid",
- LoginRole: "loginRole" + fmt.Sprintf("%d", i),
- namespace: otherNS,
- IssueTime: time.Now(),
- ExpireTime: time.Now(),
- RevokeErr: "some err message",
- }
-
- exp.pendingLock.Lock()
- if err := exp.persistEntry(namespace.RootContext(nil), le); err != nil {
- exp.pendingLock.Unlock()
- t.Fatalf("error persisting irrevocable entry: %v", err)
- }
- exp.updatePendingInternal(le)
- expectedCount++
-
- if err := exp.persistEntry(namespace.RootContext(nil), otherNSle); err != nil {
- exp.pendingLock.Unlock()
- t.Fatalf("error persisting irrevocable entry: %v", err)
- }
- exp.updatePendingInternal(otherNSle)
- expectedCount++
- exp.pendingLock.Unlock()
- }
-
- exp.pendingLock.RLock()
- count := exp.leaseCount
- exp.pendingLock.RUnlock()
-
- if count != expectedCount {
- t.Errorf("bad lease count. expected %d, got %d", expectedCount, count)
- }
-}
-
-func TestExpiration_Tidy(t *testing.T) {
- var err error
-
- // We use this later for tidy testing where we need to check the output
- logOut := new(bytes.Buffer)
- logger := log.New(&log.LoggerOptions{
- Output: logOut,
- })
-
- testCore := TestCore(t)
- testCore.baseLogger = logger
- testCore.logger = logger.Named("core")
- testCoreUnsealed(t, testCore)
-
- exp := testCore.expiration
-
- if err := exp.Restore(nil); err != nil {
- t.Fatal(err)
- }
-
- // Set up a count function to calculate number of leases
- count := 0
- countFunc := func(leaseID string) {
- count++
- }
-
- // Scan the storage with the count func set
- if err = logical.ScanView(namespace.RootContext(nil), exp.idView, countFunc); err != nil {
- t.Fatal(err)
- }
-
- // Check that there are no leases to begin with
- if count != 0 {
- t.Fatalf("bad: lease count; expected:0 actual:%d", count)
- }
-
- // Create a lease entry without a client token in it
- le := &leaseEntry{
- LeaseID: "lease/with/no/client/token",
- Path: "foo/bar",
- namespace: namespace.RootNamespace,
- }
-
- // Persist the invalid lease entry
- if err = exp.persistEntry(namespace.RootContext(nil), le); err != nil {
- t.Fatalf("error persisting entry: %v", err)
- }
-
- count = 0
- if err = logical.ScanView(context.Background(), exp.idView, countFunc); err != nil {
- t.Fatal(err)
- }
-
- // Check that the storage was successful and that the count of leases is
- // now 1
- if count != 1 {
- t.Fatalf("bad: lease count; expected:1 actual:%d", count)
- }
-
- // Run the tidy operation
- err = exp.Tidy(namespace.RootContext(nil))
- if err != nil {
- t.Fatal(err)
- }
-
- count = 0
- if err := logical.ScanView(context.Background(), exp.idView, countFunc); err != nil {
- t.Fatal(err)
- }
-
- // Post the tidy operation, the invalid lease entry should have been gone
- if count != 0 {
- t.Fatalf("bad: lease count; expected:0 actual:%d", count)
- }
-
- // Set a revoked/invalid token in the lease entry
- le.ClientToken = "invalidtoken"
-
- // Persist the invalid lease entry
- if err = exp.persistEntry(namespace.RootContext(nil), le); err != nil {
- t.Fatalf("error persisting entry: %v", err)
- }
-
- count = 0
- if err = logical.ScanView(context.Background(), exp.idView, countFunc); err != nil {
- t.Fatal(err)
- }
-
- // Check that the storage was successful and that the count of leases is
- // now 1
- if count != 1 {
- t.Fatalf("bad: lease count; expected:1 actual:%d", count)
- }
-
- // Run the tidy operation
- err = exp.Tidy(namespace.RootContext(nil))
- if err != nil {
- t.Fatal(err)
- }
-
- count = 0
- if err = logical.ScanView(context.Background(), exp.idView, countFunc); err != nil {
- t.Fatal(err)
- }
-
- // Post the tidy operation, the invalid lease entry should have been gone
- if count != 0 {
- t.Fatalf("bad: lease count; expected:0 actual:%d", count)
- }
-
- // Attach an invalid token with 2 leases
- if err = exp.persistEntry(namespace.RootContext(nil), le); err != nil {
- t.Fatalf("error persisting entry: %v", err)
- }
-
- le.LeaseID = "another/invalid/lease"
- if err = exp.persistEntry(context.Background(), le); err != nil {
- t.Fatalf("error persisting entry: %v", err)
- }
-
- // Run the tidy operation
- err = exp.Tidy(namespace.RootContext(nil))
- if err != nil {
- t.Fatal(err)
- }
-
- count = 0
- if err = logical.ScanView(context.Background(), exp.idView, countFunc); err != nil {
- t.Fatal(err)
- }
-
- // Post the tidy operation, the invalid lease entry should have been gone
- if count != 0 {
- t.Fatalf("bad: lease count; expected:0 actual:%d", count)
- }
-
- for i := 0; i < 1000; i++ {
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "invalid/lease/" + fmt.Sprintf("%d", i+1),
- ClientToken: "invalidtoken",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "invalidtoken", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: 100 * time.Millisecond,
- },
- },
- Data: map[string]interface{}{
- "test_key": "test_value",
- },
- }
- _, err := exp.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- }
-
- count = 0
- if err = logical.ScanView(context.Background(), exp.idView, countFunc); err != nil {
- t.Fatal(err)
- }
-
- // Check that there are 1000 leases now
- if count != 1000 {
- t.Fatalf("bad: lease count; expected:1000 actual:%d", count)
- }
-
- errCh1 := make(chan error)
- errCh2 := make(chan error)
-
- // Initiate tidy of the above 1000 invalid leases in quick succession. Only
- // one tidy operation can be in flight at any time. One of these requests
- // should error out.
- go func() {
- errCh1 <- exp.Tidy(namespace.RootContext(nil))
- }()
-
- go func() {
- errCh2 <- exp.Tidy(namespace.RootContext(nil))
- }()
-
- var err1, err2 error
-
- for i := 0; i < 2; i++ {
- select {
- case err1 = <-errCh1:
- case err2 = <-errCh2:
- }
- }
-
- if err1 != nil || err2 != nil {
- t.Fatalf("got an error: err1: %v; err2: %v", err1, err2)
- }
- if !strings.Contains(logOut.String(), "tidy operation on leases is already in progress") {
- t.Fatalf("expected to see a warning saying operation in progress, output is %s", logOut.String())
- }
-
- root, err := exp.tokenStore.rootToken(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- le.ClientToken = root.ID
-
- // Attach a valid token with the leases
- if err = exp.persistEntry(namespace.RootContext(nil), le); err != nil {
- t.Fatalf("error persisting entry: %v", err)
- }
-
- // Run the tidy operation
- err = exp.Tidy(namespace.RootContext(nil))
- if err != nil {
- t.Fatal(err)
- }
-
- count = 0
- if err = logical.ScanView(context.Background(), exp.idView, countFunc); err != nil {
- t.Fatal(err)
- }
-
- // Post the tidy operation, the valid lease entry should not get affected
- if count != 1 {
- t.Fatalf("bad: lease count; expected:1 actual:%d", count)
- }
-}
-
-// To avoid pulling in deps for all users of the package, don't leave these
-// uncommented in the public tree
-/*
-func BenchmarkExpiration_Restore_Etcd(b *testing.B) {
- addr := os.Getenv("PHYSICAL_BACKEND_BENCHMARK_ADDR")
- randPath := fmt.Sprintf("vault-%d/", time.Now().Unix())
-
- logger := logging.NewVaultLogger(log.Trace)
- physicalBackend, err := physEtcd.NewEtcdBackend(map[string]string{
- "address": addr,
- "path": randPath,
- "max_parallel": "256",
- }, logger)
- if err != nil {
- b.Fatalf("err: %s", err)
- }
-
- benchmarkExpirationBackend(b, physicalBackend, 10000) // 10,000 leases
-}
-
-func BenchmarkExpiration_Restore_Consul(b *testing.B) {
- addr := os.Getenv("PHYSICAL_BACKEND_BENCHMARK_ADDR")
- randPath := fmt.Sprintf("vault-%d/", time.Now().Unix())
-
- logger := logging.NewVaultLogger(log.Trace)
- physicalBackend, err := physConsul.NewConsulBackend(map[string]string{
- "address": addr,
- "path": randPath,
- "max_parallel": "256",
- }, logger)
- if err != nil {
- b.Fatalf("err: %s", err)
- }
-
- benchmarkExpirationBackend(b, physicalBackend, 10000) // 10,000 leases
-}
-*/
-
-func BenchmarkExpiration_Restore_InMem(b *testing.B) {
- logger := logging.NewVaultLogger(log.Trace)
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- b.Fatal(err)
- }
- benchmarkExpirationBackend(b, inm, 100000) // 100,000 Leases
-}
-
-func benchmarkExpirationBackend(b *testing.B, physicalBackend physical.Backend, numLeases int) {
- c, _, _ := TestCoreUnsealedBackend(benchhelpers.TBtoT(b), physicalBackend)
- exp := c.expiration
- noop := &NoopBackend{}
- view := NewBarrierView(c.barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- b.Fatal(err)
- }
- err = exp.router.Mount(noop, "prod/aws/", &MountEntry{Path: "prod/aws/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- b.Fatal(err)
- }
-
- // Register fake leases
- for i := 0; i < numLeases; i++ {
- pathUUID, err := uuid.GenerateUUID()
- if err != nil {
- b.Fatal(err)
- }
-
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "prod/aws/" + pathUUID,
- ClientToken: "root",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "root", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: 400 * time.Second,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
- _, err = exp.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- b.Fatalf("err: %v", err)
- }
- }
-
- // Stop everything
- err = exp.Stop()
- if err != nil {
- b.Fatalf("err: %v", err)
- }
- // Avoid panic due to calling exp.Stop multiple times
- c.expiration = nil
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- err = exp.Restore(nil)
- // Restore
- if err != nil {
- b.Fatalf("err: %v", err)
- }
- }
- b.StopTimer()
-}
-
-func BenchmarkExpiration_Create_Leases(b *testing.B) {
- logger := logging.NewVaultLogger(log.Trace)
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- b.Fatal(err)
- }
-
- c, _, _ := TestCoreUnsealedBackend(benchhelpers.TBtoT(b), inm)
- exp := c.expiration
- noop := &NoopBackend{}
- view := NewBarrierView(c.barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- b.Fatal(err)
- }
- err = exp.router.Mount(noop, "prod/aws/", &MountEntry{Path: "prod/aws/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- b.Fatal(err)
- }
- req := &logical.Request{
- Operation: logical.ReadOperation,
- ClientToken: "root",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "root", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: 400 * time.Second,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- req.Path = fmt.Sprintf("prod/aws/%d", i)
- _, err = exp.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- b.Fatalf("err: %v", err)
- }
- }
-}
-
-func TestExpiration_Restore(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- exp := c.expiration
- noop := &NoopBackend{}
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "prod/aws/", &MountEntry{Path: "prod/aws/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- paths := []string{
- "prod/aws/foo",
- "prod/aws/sub/bar",
- "prod/aws/zip",
- }
- for _, path := range paths {
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: path,
- ClientToken: "foobar",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Second,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
- _, err := exp.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- }
-
- if exp.leaseCount != len(paths) {
- t.Fatalf("expected %v leases, got %v", len(paths), exp.leaseCount)
- }
-
- // Stop everything
- err = c.stopExpiration()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if exp.leaseCount != 0 {
- t.Fatalf("expected %v leases, got %v", 0, exp.leaseCount)
- }
-
- // Restore
- err = exp.Restore(nil)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Would like to test here, but this is a race with the expiration of the leases.
-
- // Ensure all are reaped
- start := time.Now()
- for time.Now().Sub(start) < time.Second {
- noop.Lock()
- less := len(noop.Requests) < 3
- noop.Unlock()
-
- if less {
- time.Sleep(5 * time.Millisecond)
- continue
- }
- break
- }
- for _, req := range noop.Requests {
- if req.Operation != logical.RevokeOperation {
- t.Fatalf("Bad: %v", req)
- }
- }
-
- if exp.leaseCount != 0 {
- t.Fatalf("expected %v leases, got %v", 0, exp.leaseCount)
- }
-}
-
-func TestExpiration_Register(t *testing.T) {
- exp := mockExpiration(t)
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "prod/aws/foo",
- ClientToken: "foobar",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
-
- id, err := exp.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if !strings.HasPrefix(id, req.Path) {
- t.Fatalf("bad: %s", id)
- }
-
- if len(id) <= len(req.Path) {
- t.Fatalf("bad: %s", id)
- }
-}
-
-func TestExpiration_Register_Role(t *testing.T) {
- exp := mockExpiration(t)
- role := "role1"
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "prod/aws/foo",
- ClientToken: "foobar",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
-
- id, err := exp.Register(namespace.RootContext(nil), req, resp, role)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if !strings.HasPrefix(id, req.Path) {
- t.Fatalf("bad: %s", id)
- }
-
- if len(id) <= len(req.Path) {
- t.Fatalf("bad: %s", id)
- }
-
- le, err := exp.loadEntry(exp.quitContext, id)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if le.LoginRole != role {
- t.Fatalf("Login role incorrect. Expected %s, received %s", role, le.LoginRole)
- }
-}
-
-func TestExpiration_Register_BatchToken(t *testing.T) {
- c, _, rootToken := TestCoreUnsealed(t)
- exp := c.expiration
- noop := &NoopBackend{
- RequestHandler: func(ctx context.Context, req *logical.Request) (*logical.Response, error) {
- resp := &logical.Response{Secret: req.Secret}
- resp.Secret.TTL = time.Hour
- return resp, nil
- },
- }
- {
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "prod/aws/", &MountEntry{Path: "prod/aws/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
- }
-
- te := &logical.TokenEntry{
- Type: logical.TokenTypeBatch,
- TTL: 1 * time.Second,
- NamespaceID: "root",
- CreationTime: time.Now().Unix(),
- Parent: rootToken,
- }
-
- err := exp.tokenStore.create(context.Background(), te)
- if err != nil {
- t.Fatal(err)
- }
-
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "prod/aws/foo",
- ClientToken: te.ID,
- }
- req.SetTokenEntry(te)
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
-
- leaseID, err := exp.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- _, err = exp.Renew(namespace.RootContext(nil), leaseID, time.Minute)
- if err != nil {
- t.Fatal(err)
- }
-
- start := time.Now()
- reqID := 0
- for {
- if time.Now().Sub(start) > 10*time.Second {
- t.Fatal("didn't revoke lease")
- }
- req = nil
-
- noop.Lock()
- if len(noop.Requests) > reqID {
- req = noop.Requests[reqID]
- reqID++
- }
- noop.Unlock()
- if req == nil || req.Operation == logical.RenewOperation {
- time.Sleep(5 * time.Millisecond)
- continue
- }
- if req.Operation != logical.RevokeOperation {
- t.Fatalf("Bad: %v", req)
- }
-
- break
- }
-
- deadline := time.Now().Add(5 * time.Second)
- var idEnts []string
- for time.Now().Before(deadline) {
- idEnts, err = exp.tokenView.List(context.Background(), "")
- if err != nil {
- t.Fatal(err)
- }
- if len(idEnts) == 0 {
- return
- }
- time.Sleep(time.Second)
- }
- t.Fatalf("expected no entries in sys/expire/token, got: %v", idEnts)
-}
-
-func TestExpiration_RegisterAuth(t *testing.T) {
- exp := mockExpiration(t)
-
- root, err := exp.tokenStore.rootToken(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- auth := &logical.Auth{
- ClientToken: root.ID,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- }
-
- te := &logical.TokenEntry{
- Path: "auth/github/login",
- NamespaceID: namespace.RootNamespaceID,
- }
- err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- te = &logical.TokenEntry{
- Path: "auth/github/../login",
- NamespaceID: namespace.RootNamespaceID,
- }
- err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
- if err == nil {
- t.Fatal("expected error")
- }
-}
-
-func TestExpiration_RegisterAuth_Role(t *testing.T) {
- exp := mockExpiration(t)
- role := "role1"
- root, err := exp.tokenStore.rootToken(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- auth := &logical.Auth{
- ClientToken: root.ID,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- }
-
- te := &logical.TokenEntry{
- Path: "auth/github/login",
- NamespaceID: namespace.RootNamespaceID,
- }
- err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, role)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- te = &logical.TokenEntry{
- Path: "auth/github/../login",
- NamespaceID: namespace.RootNamespaceID,
- }
- err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, role)
- if err == nil {
- t.Fatal("expected error")
- }
-}
-
-func TestExpiration_RegisterAuth_NoLease(t *testing.T) {
- exp := mockExpiration(t)
- root, err := exp.tokenStore.rootToken(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- auth := &logical.Auth{
- ClientToken: root.ID,
- Policies: []string{"root"},
- }
-
- te := &logical.TokenEntry{
- ID: root.ID,
- Path: "auth/github/login",
- Policies: []string{"root"},
- NamespaceID: namespace.RootNamespaceID,
- }
- err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should not be able to renew, no expiration
- te = &logical.TokenEntry{
- ID: root.ID,
- Path: "auth/github/login",
- NamespaceID: namespace.RootNamespaceID,
- }
- resp, err := exp.RenewToken(namespace.RootContext(nil), &logical.Request{}, te, 0)
- if err != nil && (err != logical.ErrInvalidRequest || (resp != nil && resp.IsError() && resp.Error().Error() != "lease is not renewable")) {
- t.Fatalf("bad: err:%v resp:%#v", err, resp)
- }
- if resp == nil {
- t.Fatal("expected a response")
- }
-
- // Wait and check token is not invalidated
- time.Sleep(20 * time.Millisecond)
-
- // Verify token does not get revoked
- out, err := exp.tokenStore.Lookup(namespace.RootContext(nil), root.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out == nil {
- t.Fatalf("missing token")
- }
-}
-
-// Tests both the expiration function and the core function
-func TestExpiration_RegisterAuth_NoTTL(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- exp := c.expiration
- ctx := namespace.RootContext(nil)
-
- root, err := exp.tokenStore.rootToken(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- auth := &logical.Auth{
- ClientToken: root.ID,
- TokenPolicies: []string{"root"},
- }
-
- // First on core
- err = c.RegisterAuth(ctx, 0, "auth/github/login", auth, "")
- if err != nil {
- t.Fatal(err)
- }
-
- auth.TokenPolicies[0] = "default"
- err = c.RegisterAuth(ctx, 0, "auth/github/login", auth, "")
- if err == nil {
- t.Fatal("expected error")
- }
-
- // Now expiration
- // Should work, root token with zero TTL
- te := &logical.TokenEntry{
- ID: root.ID,
- Path: "auth/github/login",
- Policies: []string{"root"},
- NamespaceID: namespace.RootNamespaceID,
- }
- err = exp.RegisterAuth(ctx, te, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Test non-root token with zero TTL
- te.Policies = []string{"default"}
- err = exp.RegisterAuth(ctx, te, auth, "")
- if err == nil {
- t.Fatal("expected error")
- }
-}
-
-func TestExpiration_Revoke(t *testing.T) {
- exp := mockExpiration(t)
- noop := &NoopBackend{}
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "prod/aws/", &MountEntry{Path: "prod/aws/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "prod/aws/foo",
- ClientToken: "foobar",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
-
- id, err := exp.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if err := exp.Revoke(namespace.RootContext(nil), id); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req = noop.Requests[0]
- if req.Operation != logical.RevokeOperation {
- t.Fatalf("Bad: %v", req)
- }
-}
-
-func TestExpiration_RevokeOnExpire(t *testing.T) {
- exp := mockExpiration(t)
- noop := &NoopBackend{}
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "prod/aws/", &MountEntry{Path: "prod/aws/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "prod/aws/foo",
- ClientToken: "foobar",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: 20 * time.Millisecond,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
-
- _, err = exp.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- start := time.Now()
- for time.Now().Sub(start) < time.Second {
- req = nil
-
- noop.Lock()
- if len(noop.Requests) > 0 {
- req = noop.Requests[0]
- }
- noop.Unlock()
- if req == nil {
- time.Sleep(5 * time.Millisecond)
- continue
- }
- if req.Operation != logical.RevokeOperation {
- t.Fatalf("Bad: %v", req)
- }
-
- break
- }
-}
-
-func TestExpiration_RevokePrefix(t *testing.T) {
- exp := mockExpiration(t)
- noop := &NoopBackend{}
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "prod/aws/", &MountEntry{Path: "prod/aws/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- paths := []string{
- "prod/aws/foo",
- "prod/aws/sub/bar",
- "prod/aws/zip",
- }
- for _, path := range paths {
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: path,
- ClientToken: "foobar",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Second,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
- _, err := exp.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- }
-
- // Should nuke all the keys
- if err := exp.RevokePrefix(namespace.RootContext(nil), "prod/aws/", true); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if len(noop.Requests) != 3 {
- t.Fatalf("Bad: %v", noop.Requests)
- }
- for _, req := range noop.Requests {
- if req.Operation != logical.RevokeOperation {
- t.Fatalf("Bad: %v", req)
- }
- }
-
- expect := []string{
- "foo",
- "sub/bar",
- "zip",
- }
- sort.Strings(noop.Paths)
- sort.Strings(expect)
- if !reflect.DeepEqual(noop.Paths, expect) {
- t.Fatalf("bad: %v", noop.Paths)
- }
-}
-
-func TestExpiration_RevokeByToken(t *testing.T) {
- exp := mockExpiration(t)
- noop := &NoopBackend{}
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "prod/aws/", &MountEntry{Path: "prod/aws/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- paths := []string{
- "prod/aws/foo",
- "prod/aws/sub/bar",
- "prod/aws/zip",
- }
- for _, path := range paths {
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: path,
- ClientToken: "foobarbaz",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "foobarbaz", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Second,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
- _, err := exp.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- }
-
- // Should nuke all the keys
- te := &logical.TokenEntry{
- ID: "foobarbaz",
- NamespaceID: namespace.RootNamespaceID,
- }
- if err := exp.RevokeByToken(namespace.RootContext(nil), te); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- limit := time.Now().Add(3 * time.Second)
- for time.Now().Before(limit) {
- time.Sleep(50 * time.Millisecond)
-
- noop.Lock()
- currentRequests := len(noop.Requests)
- noop.Unlock()
-
- if currentRequests == 3 {
- break
- }
- }
-
- noop.Lock()
- defer noop.Unlock()
- if len(noop.Requests) != 3 {
- t.Errorf("Noop revocation requests less than expected, expected 3, found %d", len(noop.Requests))
- }
- for _, req := range noop.Requests {
- if req.Operation != logical.RevokeOperation {
- t.Fatalf("Bad: %v", req)
- }
- }
-
- expect := []string{
- "foo",
- "sub/bar",
- "zip",
- }
- sort.Strings(noop.Paths)
- sort.Strings(expect)
- if !reflect.DeepEqual(noop.Paths, expect) {
- t.Fatalf("bad: %v", noop.Paths)
- }
-}
-
-func TestExpiration_RevokeByToken_Blocking(t *testing.T) {
- exp := mockExpiration(t)
- noop := &NoopBackend{}
- // Request handle with a timeout context that simulates blocking lease revocation.
- noop.RequestHandler = func(ctx context.Context, req *logical.Request) (*logical.Response, error) {
- ctx, cancel := context.WithTimeout(ctx, 200*time.Millisecond)
- defer cancel()
-
- select {
- case <-ctx.Done():
- return noop.Response, nil
- }
- }
-
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "prod/aws/", &MountEntry{Path: "prod/aws/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- paths := []string{
- "prod/aws/foo",
- "prod/aws/sub/bar",
- "prod/aws/zip",
- }
- for _, path := range paths {
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: path,
- ClientToken: "foobarbaz",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "foobarbaz", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: 1 * time.Minute,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
- _, err := exp.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- }
-
- // Should nuke all the keys
- te := &logical.TokenEntry{
- ID: "foobarbaz",
- NamespaceID: namespace.RootNamespaceID,
- }
- if err := exp.RevokeByToken(namespace.RootContext(nil), te); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Lock and check that no requests has gone through yet
- noop.Lock()
- if len(noop.Requests) != 0 {
- t.Fatalf("Bad: %v", noop.Requests)
- }
- noop.Unlock()
-
- // Wait for a bit for timeouts to trigger and pending revocations to go
- // through and then we relock
- time.Sleep(300 * time.Millisecond)
-
- noop.Lock()
- defer noop.Unlock()
-
- // Now make sure that all requests have gone through
- if len(noop.Requests) != 3 {
- t.Fatalf("Bad: %v", noop.Requests)
- }
- for _, req := range noop.Requests {
- if req.Operation != logical.RevokeOperation {
- t.Fatalf("Bad: %v", req)
- }
- }
-
- expect := []string{
- "foo",
- "sub/bar",
- "zip",
- }
- sort.Strings(noop.Paths)
- sort.Strings(expect)
- if !reflect.DeepEqual(noop.Paths, expect) {
- t.Fatalf("bad: %v", noop.Paths)
- }
-}
-
-func TestExpiration_RenewToken(t *testing.T) {
- exp := mockExpiration(t)
- root, err := exp.tokenStore.rootToken(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Register a token
- auth := &logical.Auth{
- ClientToken: root.ID,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- Renewable: true,
- },
- }
-
- te := &logical.TokenEntry{
- ID: root.ID,
- Path: "auth/token/login",
- NamespaceID: namespace.RootNamespaceID,
- }
- err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Renew the token
- te = &logical.TokenEntry{
- ID: root.ID,
- Path: "auth/token/login",
- NamespaceID: namespace.RootNamespaceID,
- }
- out, err := exp.RenewToken(namespace.RootContext(nil), &logical.Request{}, te, 0)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if auth.ClientToken != out.Auth.ClientToken {
- t.Fatalf("bad: %#v", out)
- }
-}
-
-func TestExpiration_RenewToken_period(t *testing.T) {
- exp := mockExpiration(t)
- root := &logical.TokenEntry{
- Policies: []string{"root"},
- Path: "auth/token/root",
- DisplayName: "root",
- CreationTime: time.Now().Unix(),
- Period: time.Minute,
- NamespaceID: namespace.RootNamespaceID,
- }
- if err := exp.tokenStore.create(namespace.RootContext(nil), root); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Register a token
- auth := &logical.Auth{
- ClientToken: root.ID,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- Renewable: true,
- },
- Period: time.Minute,
- }
- te := &logical.TokenEntry{
- ID: root.ID,
- Path: "auth/token/login",
- NamespaceID: namespace.RootNamespaceID,
- }
- err := exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if exp.leaseCount != 1 {
- t.Fatalf("expected %v leases, got %v", 1, exp.leaseCount)
- }
-
- // Renew the token
- te = &logical.TokenEntry{
- ID: root.ID,
- Path: "auth/token/login",
- NamespaceID: namespace.RootNamespaceID,
- }
- out, err := exp.RenewToken(namespace.RootContext(nil), &logical.Request{}, te, 0)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if auth.ClientToken != out.Auth.ClientToken {
- t.Fatalf("bad: %#v", out)
- }
-
- if out.Auth.TTL > time.Minute {
- t.Fatalf("expected TTL to be less than 1 minute, got: %s", out.Auth.TTL)
- }
-
- if exp.leaseCount != 1 {
- t.Fatalf("expected %v leases, got %v", 1, exp.leaseCount)
- }
-}
-
-func TestExpiration_RenewToken_period_backend(t *testing.T) {
- exp := mockExpiration(t)
- root, err := exp.tokenStore.rootToken(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Mount a noop backend
- noop := &NoopBackend{
- Response: &logical.Response{
- Auth: &logical.Auth{
- LeaseOptions: logical.LeaseOptions{
- TTL: 10 * time.Second,
- Renewable: true,
- },
- Period: 5 * time.Second,
- },
- },
- DefaultLeaseTTL: 5 * time.Second,
- MaxLeaseTTL: 5 * time.Second,
- }
-
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, credentialBarrierPrefix)
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "auth/foo/", &MountEntry{Path: "auth/foo/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- // Register a token
- auth := &logical.Auth{
- ClientToken: root.ID,
- LeaseOptions: logical.LeaseOptions{
- TTL: 10 * time.Second,
- Renewable: true,
- },
- Period: 5 * time.Second,
- }
- te := &logical.TokenEntry{
- ID: root.ID,
- Path: "auth/foo/login",
- NamespaceID: namespace.RootNamespaceID,
- }
-
- err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Wait 3 seconds
- time.Sleep(3 * time.Second)
- te = &logical.TokenEntry{
- ID: root.ID,
- Path: "auth/foo/login",
- NamespaceID: namespace.RootNamespaceID,
- }
- resp, err := exp.RenewToken(namespace.RootContext(nil), &logical.Request{}, te, 0)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatal("expected a response")
- }
- if resp.Auth.TTL == 0 || resp.Auth.TTL > 5*time.Second {
- t.Fatalf("expected TTL to be greater than zero and less than or equal to period, got: %s", resp.Auth.TTL)
- }
-
- // Wait another 3 seconds. If period works correctly, this should not fail
- time.Sleep(3 * time.Second)
- resp, err = exp.RenewToken(namespace.RootContext(nil), &logical.Request{}, te, 0)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatal("expected a response")
- }
- if resp.Auth.TTL < 4*time.Second || resp.Auth.TTL > 5*time.Second {
- t.Fatalf("expected TTL to be around period's value, got: %s", resp.Auth.TTL)
- }
-}
-
-func TestExpiration_RenewToken_NotRenewable(t *testing.T) {
- exp := mockExpiration(t)
- root, err := exp.tokenStore.rootToken(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Register a token
- auth := &logical.Auth{
- ClientToken: root.ID,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- Renewable: false,
- },
- }
- te := &logical.TokenEntry{
- ID: root.ID,
- Path: "auth/foo/login",
- NamespaceID: namespace.RootNamespaceID,
- }
- err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Attempt to renew the token
- te = &logical.TokenEntry{
- ID: root.ID,
- Path: "auth/github/login",
- NamespaceID: namespace.RootNamespaceID,
- }
- resp, err := exp.RenewToken(namespace.RootContext(nil), &logical.Request{}, te, 0)
- if err != nil && (err != logical.ErrInvalidRequest || (resp != nil && resp.IsError() && resp.Error().Error() != "invalid lease ID")) {
- t.Fatalf("bad: err:%v resp:%#v", err, resp)
- }
- if resp == nil {
- t.Fatal("expected a response")
- }
-}
-
-func TestExpiration_Renew(t *testing.T) {
- exp := mockExpiration(t)
- noop := &NoopBackend{}
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "prod/aws/", &MountEntry{Path: "prod/aws/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "prod/aws/foo",
- ClientToken: "foobar",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Second,
- Renewable: true,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
-
- id, err := exp.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- noop.Response = &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Second,
- },
- },
- Data: map[string]interface{}{
- "access_key": "123",
- "secret_key": "abcd",
- },
- }
-
- out, err := exp.Renew(namespace.RootContext(nil), id, 0)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- noop.Lock()
- defer noop.Unlock()
-
- if !reflect.DeepEqual(out, noop.Response) {
- t.Fatalf("Bad: %#v", out)
- }
-
- if len(noop.Requests) != 1 {
- t.Fatalf("Bad: %#v", noop.Requests)
- }
- req = noop.Requests[0]
- if req.Operation != logical.RenewOperation {
- t.Fatalf("Bad: %v", req)
- }
-}
-
-func TestExpiration_Renew_NotRenewable(t *testing.T) {
- exp := mockExpiration(t)
- noop := &NoopBackend{}
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "prod/aws/", &MountEntry{Path: "prod/aws/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "prod/aws/foo",
- ClientToken: "foobar",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Second,
- Renewable: false,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
-
- id, err := exp.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- _, err = exp.Renew(namespace.RootContext(nil), id, 0)
- if err.Error() != "lease is not renewable" {
- t.Fatalf("err: %v", err)
- }
-
- noop.Lock()
- defer noop.Unlock()
-
- if len(noop.Requests) != 0 {
- t.Fatalf("Bad: %#v", noop.Requests)
- }
-}
-
-func TestExpiration_Renew_RevokeOnExpire(t *testing.T) {
- exp := mockExpiration(t)
- noop := &NoopBackend{}
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "prod/aws/", &MountEntry{Path: "prod/aws/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "prod/aws/foo",
- ClientToken: "foobar",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: 20 * time.Millisecond,
- Renewable: true,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
-
- id, err := exp.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- noop.Response = &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: 20 * time.Millisecond,
- },
- },
- Data: map[string]interface{}{
- "access_key": "123",
- "secret_key": "abcd",
- },
- }
-
- _, err = exp.Renew(namespace.RootContext(nil), id, 0)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- start := time.Now()
- for time.Now().Sub(start) < time.Second {
- req = nil
-
- noop.Lock()
- if len(noop.Requests) >= 2 {
- req = noop.Requests[1]
- }
- noop.Unlock()
-
- if req == nil {
- time.Sleep(5 * time.Millisecond)
- continue
- }
- if req.Operation != logical.RevokeOperation {
- t.Fatalf("Bad: %v", req)
- }
- break
- }
-}
-
-func TestExpiration_Renew_FinalSecond(t *testing.T) {
- exp := mockExpiration(t)
- noop := &NoopBackend{}
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "prod/aws/", &MountEntry{Path: "prod/aws/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "prod/aws/foo",
- ClientToken: "foobar",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: 2 * time.Second,
- Renewable: true,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
-
- ctx := namespace.RootContext(nil)
- id, err := exp.Register(ctx, req, resp, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- le, err := exp.loadEntry(ctx, id)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- // Give it an auth section to emulate the real world bug
- le.Auth = &logical.Auth{
- LeaseOptions: logical.LeaseOptions{
- Renewable: true,
- },
- }
- exp.persistEntry(ctx, le)
-
- noop.Response = &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: 2 * time.Second,
- MaxTTL: 2 * time.Second,
- },
- },
- Data: map[string]interface{}{
- "access_key": "123",
- "secret_key": "abcd",
- },
- }
-
- time.Sleep(1000 * time.Millisecond)
- _, err = exp.Renew(ctx, id, 0)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if _, ok := exp.nonexpiring.Load(id); ok {
- t.Fatalf("expirable lease became nonexpiring")
- }
-}
-
-func TestExpiration_Renew_FinalSecond_Lease(t *testing.T) {
- exp := mockExpiration(t)
- noop := &NoopBackend{}
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "prod/aws/", &MountEntry{Path: "prod/aws/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "prod/aws/foo",
- ClientToken: "foobar",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: 2 * time.Second,
- Renewable: true,
- },
- LeaseID: "abcde",
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
-
- ctx := namespace.RootContext(nil)
- id, err := exp.Register(ctx, req, resp, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- le, err := exp.loadEntry(ctx, id)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- // Give it an auth section to emulate the real world bug
- le.Auth = &logical.Auth{
- LeaseOptions: logical.LeaseOptions{
- Renewable: true,
- },
- }
- exp.persistEntry(ctx, le)
-
- time.Sleep(1000 * time.Millisecond)
- _, err = exp.Renew(ctx, id, 0)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if _, ok := exp.nonexpiring.Load(id); ok {
- t.Fatalf("expirable lease became nonexpiring")
- }
-}
-
-func TestExpiration_revokeEntry(t *testing.T) {
- exp := mockExpiration(t)
-
- noop := &NoopBackend{}
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "foo/bar/", &MountEntry{Path: "foo/bar/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- le := &leaseEntry{
- LeaseID: "foo/bar/1234",
- Path: "foo/bar",
- Data: map[string]interface{}{
- "testing": true,
- },
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Minute,
- },
- },
- IssueTime: time.Now(),
- ExpireTime: time.Now(),
- namespace: namespace.RootNamespace,
- }
-
- err = exp.revokeEntry(namespace.RootContext(nil), le)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- noop.Lock()
- defer noop.Unlock()
-
- req := noop.Requests[0]
- if req.Operation != logical.RevokeOperation {
- t.Fatalf("bad: operation; req: %#v", req)
- }
- if !reflect.DeepEqual(req.Data, le.Data) {
- t.Fatalf("bad: data; req: %#v\n le: %#v\n", req, le)
- }
-}
-
-func TestExpiration_revokeEntry_token(t *testing.T) {
- exp := mockExpiration(t)
- root, err := exp.tokenStore.rootToken(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // N.B.: Vault doesn't allow both a secret and auth to be returned, but the
- // reason for both is that auth needs to be included in order to use the
- // token store as it's the only mounted backend, *but* RegisterAuth doesn't
- // actually create the index by token, only Register (for a Secret) does.
- // So without the Secret we don't do anything when removing the index which
- // (at the time of writing) now fails because a bug causing every token
- // expiration to do an extra delete to a non-existent key has been fixed,
- // and this test relies on this nonstandard behavior.
- le := &leaseEntry{
- LeaseID: "foo/bar/1234",
- Auth: &logical.Auth{
- ClientToken: root.ID,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Minute,
- },
- },
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Minute,
- },
- },
- ClientToken: root.ID,
- Path: "foo/bar",
- IssueTime: time.Now(),
- ExpireTime: time.Now().Add(time.Minute),
- namespace: namespace.RootNamespace,
- }
-
- if err := exp.persistEntry(namespace.RootContext(nil), le); err != nil {
- t.Fatalf("error persisting entry: %v", err)
- }
- if err := exp.createIndexByToken(namespace.RootContext(nil), le, le.ClientToken); err != nil {
- t.Fatalf("error creating secondary index: %v", err)
- }
- exp.updatePending(le)
-
- indexEntry, err := exp.indexByToken(namespace.RootContext(nil), le)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if indexEntry == nil {
- t.Fatalf("err: should have found a secondary index entry")
- }
-
- err = exp.revokeEntry(namespace.RootContext(nil), le)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- limit := time.Now().Add(10 * time.Second)
- for time.Now().Before(limit) {
- indexEntry, err = exp.indexByToken(namespace.RootContext(nil), le)
- if err != nil {
- t.Fatalf("token index lookup error: %v", err)
- }
- if indexEntry == nil {
- break
- }
-
- time.Sleep(50 * time.Millisecond)
- }
-
- if indexEntry != nil {
- t.Fatalf("should not have found a secondary index entry after revocation")
- }
-
- out, err := exp.tokenStore.Lookup(namespace.RootContext(nil), le.ClientToken)
- if err != nil {
- t.Fatalf("error looking up client token after revocation: %v", err)
- }
- if out != nil {
- t.Fatalf("should not have found revoked token in tokenstore: %v", out)
- }
-}
-
-func TestExpiration_renewEntry(t *testing.T) {
- exp := mockExpiration(t)
-
- noop := &NoopBackend{
- Response: &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- Renewable: true,
- TTL: time.Hour,
- },
- },
- Data: map[string]interface{}{
- "testing": false,
- },
- },
- }
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "foo/bar/", &MountEntry{Path: "foo/bar/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- le := &leaseEntry{
- LeaseID: "foo/bar/1234",
- Path: "foo/bar",
- Data: map[string]interface{}{
- "testing": true,
- },
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Minute,
- },
- },
- IssueTime: time.Now(),
- ExpireTime: time.Now(),
- namespace: namespace.RootNamespace,
- }
-
- resp, err := exp.renewEntry(namespace.RootContext(nil), le, 0)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- noop.Lock()
- defer noop.Unlock()
-
- if !reflect.DeepEqual(resp, noop.Response) {
- t.Fatalf("bad: %#v", resp)
- }
-
- req := noop.Requests[0]
- if req.Operation != logical.RenewOperation {
- t.Fatalf("Bad: %v", req)
- }
- if !reflect.DeepEqual(req.Data, le.Data) {
- t.Fatalf("Bad: %v", req)
- }
-}
-
-func TestExpiration_revokeEntry_rejected_fairsharing(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- exp := core.expiration
-
- rejected := new(uint32)
-
- noop := &NoopBackend{
- RequestHandler: func(ctx context.Context, req *logical.Request) (*logical.Response, error) {
- if req.Operation == logical.RevokeOperation {
- if atomic.CompareAndSwapUint32(rejected, 0, 1) {
- t.Logf("denying revocation")
- return nil, errors.New("nope")
- }
- t.Logf("allowing revocation")
- }
- return nil, nil
- },
- }
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "foo/bar/", &MountEntry{Path: "foo/bar/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- le := &leaseEntry{
- LeaseID: "foo/bar/1234",
- Path: "foo/bar",
- Data: map[string]interface{}{
- "testing": true,
- },
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Minute,
- },
- },
- IssueTime: time.Now(),
- ExpireTime: time.Now().Add(time.Minute),
- namespace: namespace.RootNamespace,
- }
-
- err = exp.persistEntry(namespace.RootContext(nil), le)
- if err != nil {
- t.Fatal(err)
- }
-
- err = exp.LazyRevoke(namespace.RootContext(nil), le.LeaseID)
- if err != nil {
- t.Fatal(err)
- }
-
- // Give time to let the request be handled
- time.Sleep(1 * time.Second)
-
- if atomic.LoadUint32(rejected) != 1 {
- t.Fatal("unexpected val for rejected")
- }
-
- err = exp.Stop()
- if err != nil {
- t.Fatal(err)
- }
-
- err = core.setupExpiration(expireLeaseStrategyFairsharing)
- if err != nil {
- t.Fatal(err)
- }
- exp = core.expiration
-
- for {
- if !exp.inRestoreMode() {
- break
- }
- time.Sleep(100 * time.Millisecond)
- }
-
- // Now let the revocation actually process
- time.Sleep(1 * time.Second)
-
- le, err = exp.FetchLeaseTimes(namespace.RootContext(nil), le.LeaseID)
- if err != nil {
- t.Fatal(err)
- }
- if le != nil {
- t.Fatal("lease entry not nil")
- }
-}
-
-func TestExpiration_renewAuthEntry(t *testing.T) {
- exp := mockExpiration(t)
-
- noop := &NoopBackend{
- Response: &logical.Response{
- Auth: &logical.Auth{
- LeaseOptions: logical.LeaseOptions{
- Renewable: true,
- TTL: time.Hour,
- },
- },
- },
- }
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "auth/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "auth/foo/", &MountEntry{Path: "auth/foo/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- le := &leaseEntry{
- LeaseID: "auth/foo/1234",
- Path: "auth/foo/login",
- Auth: &logical.Auth{
- LeaseOptions: logical.LeaseOptions{
- Renewable: true,
- TTL: time.Minute,
- },
- InternalData: map[string]interface{}{
- "MySecret": "secret",
- },
- },
- IssueTime: time.Now(),
- ExpireTime: time.Now().Add(time.Minute),
- namespace: namespace.RootNamespace,
- }
-
- resp, err := exp.renewAuthEntry(namespace.RootContext(nil), &logical.Request{}, le, 0)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- noop.Lock()
- defer noop.Unlock()
-
- if !reflect.DeepEqual(resp, noop.Response) {
- t.Fatalf("bad: %#v", resp)
- }
-
- req := noop.Requests[0]
- if req.Operation != logical.RenewOperation {
- t.Fatalf("Bad: %v", req)
- }
- if req.Path != "login" {
- t.Fatalf("Bad: %v", req)
- }
- if req.Auth.InternalData["MySecret"] != "secret" {
- t.Fatalf("Bad: %v", req)
- }
-}
-
-func TestExpiration_PersistLoadDelete(t *testing.T) {
- exp := mockExpiration(t)
- lastTime := time.Now()
- le := &leaseEntry{
- LeaseID: "foo/bar/1234",
- Path: "foo/bar",
- Data: map[string]interface{}{
- "testing": true,
- },
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Minute,
- },
- },
- IssueTime: lastTime,
- ExpireTime: lastTime,
- LastRenewalTime: lastTime,
- namespace: namespace.RootNamespace,
- }
- if err := exp.persistEntry(namespace.RootContext(nil), le); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- out, err := exp.loadEntry(namespace.RootContext(nil), "foo/bar/1234")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !le.LastRenewalTime.Equal(out.LastRenewalTime) ||
- !le.IssueTime.Equal(out.IssueTime) ||
- !le.ExpireTime.Equal(out.ExpireTime) {
- t.Fatalf("bad: expected:\n%#v\nactual:\n%#v", le, out)
- }
- le.LastRenewalTime = out.LastRenewalTime
- le.IssueTime = out.IssueTime
- le.ExpireTime = out.ExpireTime
- if !reflect.DeepEqual(out, le) {
- t.Fatalf("bad: expected:\n%#v\nactual:\n%#v", le, out)
- }
-
- err = exp.deleteEntry(namespace.RootContext(nil), le)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- out, err = exp.loadEntry(namespace.RootContext(nil), "foo/bar/1234")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("out: %#v", out)
- }
-}
-
-func TestLeaseEntry(t *testing.T) {
- le := &leaseEntry{
- LeaseID: "foo/bar/1234",
- Path: "foo/bar",
- Data: map[string]interface{}{
- "testing": true,
- },
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Minute,
- Renewable: true,
- },
- },
- IssueTime: time.Now(),
- ExpireTime: time.Now(),
- }
-
- enc, err := le.encode()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- out, err := decodeLeaseEntry(enc)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if !reflect.DeepEqual(out.Data, le.Data) {
- t.Fatalf("got: %#v, expect %#v", out, le)
- }
-
- // Test renewability
- le.ExpireTime = time.Time{}
- if r, _ := le.renewable(); r {
- t.Fatal("lease with zero expire time is not renewable")
- }
- le.ExpireTime = time.Now().Add(-1 * time.Hour)
- if r, _ := le.renewable(); r {
- t.Fatal("lease with expire time in the past is not renewable")
- }
- le.ExpireTime = time.Now().Add(1 * time.Hour)
- if r, err := le.renewable(); !r {
- t.Fatalf("lease with future expire time is renewable, err: %v", err)
- }
- le.Secret.LeaseOptions.Renewable = false
- if r, _ := le.renewable(); r {
- t.Fatal("secret is set to not be renewable but returns as renewable")
- }
- le.Secret = nil
- le.Auth = &logical.Auth{
- LeaseOptions: logical.LeaseOptions{
- Renewable: true,
- },
- }
- if r, err := le.renewable(); !r {
- t.Fatalf("auth is renewable but is set to not be, err: %v", err)
- }
- le.Auth.LeaseOptions.Renewable = false
- if r, _ := le.renewable(); r {
- t.Fatal("auth is set to not be renewable but returns as renewable")
- }
-}
-
-func TestExpiration_RevokeForce(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
-
- core.logicalBackends["badrenew"] = badRenewFactory
- me := &MountEntry{
- Table: mountTableType,
- Path: "badrenew/",
- Type: "badrenew",
- Accessor: "badrenewaccessor",
- }
-
- err := core.mount(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatal(err)
- }
-
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "badrenew/creds",
- ClientToken: root,
- }
- err = core.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("response was nil")
- }
- if resp.Secret == nil {
- t.Fatalf("response secret was nil, response was %#v", *resp)
- }
-
- req.Operation = logical.UpdateOperation
- req.Path = "sys/revoke-prefix/badrenew/creds"
-
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatal("expected error")
- }
-
- req.Path = "sys/revoke-force/badrenew/creds"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("got error: %s", err)
- }
-}
-
-func TestExpiration_RevokeForceSingle(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
-
- core.logicalBackends["badrenew"] = badRenewFactory
- me := &MountEntry{
- Table: mountTableType,
- Path: "badrenew/",
- Type: "badrenew",
- Accessor: "badrenewaccessor",
- }
-
- err := core.mount(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatal(err)
- }
-
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "badrenew/creds",
- ClientToken: root,
- }
- err = core.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("response was nil")
- }
- if resp.Secret == nil {
- t.Fatalf("response secret was nil, response was %#v", *resp)
- }
- leaseID := resp.Secret.LeaseID
-
- req.Operation = logical.UpdateOperation
- req.Path = "sys/leases/lookup"
- req.Data = map[string]interface{}{"lease_id": leaseID}
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("nil response")
- }
- if resp.Data["id"].(string) != leaseID {
- t.Fatalf("expected id %q, got %q", leaseID, resp.Data["id"].(string))
- }
-
- req.Path = "sys/revoke-prefix/" + leaseID
-
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatal("expected error")
- }
-
- req.Path = "sys/revoke-force/" + leaseID
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("got error: %s", err)
- }
-
- req.Path = "sys/leases/lookup"
- req.Data = map[string]interface{}{"lease_id": leaseID}
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatal("expected error")
- }
- if !strings.Contains(err.Error(), "invalid request") {
- t.Fatalf("bad error: %v", err)
- }
-}
-
-func badRenewFactory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
- be := &framework.Backend{
- Paths: []*framework.Path{
- {
- Pattern: "creds",
- Callbacks: map[logical.Operation]framework.OperationFunc{
- logical.ReadOperation: func(context.Context, *logical.Request, *framework.FieldData) (*logical.Response, error) {
- resp := &logical.Response{
- Secret: &logical.Secret{
- InternalData: map[string]interface{}{
- "secret_type": "badRenewBackend",
- },
- },
- }
- resp.Secret.TTL = time.Second * 30
- return resp, nil
- },
- },
- },
- },
-
- Secrets: []*framework.Secret{
- {
- Type: "badRenewBackend",
- Revoke: func(context.Context, *logical.Request, *framework.FieldData) (*logical.Response, error) {
- return nil, fmt.Errorf("always errors")
- },
- },
- },
- BackendType: logical.TypeLogical,
- }
-
- err := be.Setup(namespace.RootContext(nil), conf)
- if err != nil {
- return nil, err
- }
-
- return be, nil
-}
-
-func sampleToken(t *testing.T, exp *ExpirationManager, path string, expiring bool, policy string) *logical.TokenEntry {
- t.Helper()
-
- root, err := exp.tokenStore.rootToken(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- auth := &logical.Auth{
- ClientToken: root.ID,
- Policies: []string{policy},
- }
- if expiring {
- auth.LeaseOptions = logical.LeaseOptions{
- TTL: time.Hour,
- }
- }
-
- te := &logical.TokenEntry{
- ID: root.ID,
- Path: path,
- NamespaceID: namespace.RootNamespaceID,
- Policies: auth.Policies,
- }
-
- err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- return te
-}
-
-func findMatchingPath(path string, tokenEntries []*logical.TokenEntry) bool {
- for _, te := range tokenEntries {
- if path == te.Path {
- return true
- }
- }
- return false
-}
-
-func findMatchingPolicy(policy string, tokenEntries []*logical.TokenEntry) bool {
- for _, te := range tokenEntries {
- for _, p := range te.Policies {
- if policy == p {
- return true
- }
- }
- }
- return false
-}
-
-func TestExpiration_WalkTokens(t *testing.T) {
- exp := mockExpiration(t)
-
- tokenEntries := []*logical.TokenEntry{
- sampleToken(t, exp, "auth/userpass/login", true, "default"),
- sampleToken(t, exp, "auth/userpass/login", true, "policy23457"),
- sampleToken(t, exp, "auth/token/create", false, "root"),
- sampleToken(t, exp, "auth/github/login", true, "root"),
- sampleToken(t, exp, "auth/github/login", false, "root"),
- }
-
- waitForRestore(t, exp)
-
- for true {
- // Count before and after each revocation
- t.Logf("Counting %d tokens.", len(tokenEntries))
- count := 0
- exp.WalkTokens(func(leaseId string, auth *logical.Auth, path string) bool {
- count += 1
- t.Logf("Lease ID %d: %q\n", count, leaseId)
- if !findMatchingPath(path, tokenEntries) {
- t.Errorf("Mismatched Path: %v", path)
- }
- if len(auth.Policies) < 1 || !findMatchingPolicy(auth.Policies[0], tokenEntries) {
- t.Errorf("Mismatched Policies: %v", auth.Policies)
- }
- return true
- })
- if count != len(tokenEntries) {
- t.Errorf("Mismatched number of tokens: %v", count)
- }
-
- if len(tokenEntries) == 0 {
- break
- }
-
- // Revoke last token
- toRevoke := len(tokenEntries) - 1
- leaseId, err := exp.CreateOrFetchRevocationLeaseByToken(namespace.RootContext(nil), tokenEntries[toRevoke])
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- t.Logf("revocation lease ID: %q", leaseId)
- err = exp.Revoke(namespace.RootContext(nil), leaseId)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- tokenEntries = tokenEntries[:len(tokenEntries)-1]
-
- }
-}
-
-func waitForRestore(t *testing.T, exp *ExpirationManager) {
- t.Helper()
-
- timeout := time.After(200 * time.Millisecond)
- ticker := time.Tick(5 * time.Millisecond)
-
- for exp.inRestoreMode() {
- select {
- case <-timeout:
- t.Fatalf("Timeout waiting for expiration manager to recover.")
- case <-ticker:
- continue
- }
- }
-}
-
-func TestExpiration_CachedPolicyIsShared(t *testing.T) {
- exp := mockExpiration(t)
-
- tokenEntries := []*logical.TokenEntry{
- sampleToken(t, exp, "auth/userpass/login", true, "policy23457"),
- sampleToken(t, exp, "auth/github/login", true, strings.Join([]string{"policy", "23457"}, "")),
- sampleToken(t, exp, "auth/token/create", true, "policy23457"),
- }
-
- var policies [][]string
-
- waitForRestore(t, exp)
- exp.WalkTokens(func(leaseId string, auth *logical.Auth, path string) bool {
- policies = append(policies, auth.Policies)
- return true
- })
- if len(policies) != len(tokenEntries) {
- t.Fatalf("Mismatched number of tokens: %v", len(policies))
- }
- ptrs := make([]*string, len(policies))
- for i := range ptrs {
- ptrs[i] = &((policies[0])[0])
- }
- for i := 1; i < len(ptrs); i++ {
- if ptrs[i-1] != ptrs[i] {
- t.Errorf("Mismatched pointers: %v and %v", ptrs[i-1], ptrs[i])
- }
- }
-}
-
-func TestExpiration_FairsharingEnvVar(t *testing.T) {
- testCases := []struct {
- set string
- expected int
- }{
- {
- set: "15",
- expected: 15,
- },
- {
- set: "0",
- expected: numExpirationWorkersTest,
- },
- {
- set: "10001",
- expected: numExpirationWorkersTest,
- },
- }
-
- defer os.Unsetenv(fairshareWorkersOverrideVar)
- for _, tc := range testCases {
- os.Setenv(fairshareWorkersOverrideVar, tc.set)
- exp := mockExpiration(t)
-
- if fairshare.GetNumWorkers(exp.jobManager) != tc.expected {
- t.Errorf("bad worker pool size. expected %d, got %d", tc.expected, fairshare.GetNumWorkers(exp.jobManager))
- }
- }
-}
-
-// register one lease ID and return the leaseID
-func registerOneLease(t *testing.T, ctx context.Context, exp *ExpirationManager) string {
- t.Helper()
-
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "irrevocable/lease",
- ClientToken: "sometoken",
- }
- req.SetTokenEntry(&logical.TokenEntry{ID: "sometoken", NamespaceID: "root"})
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: 10 * time.Hour,
- },
- },
- }
-
- leaseID, err := exp.Register(ctx, req, resp, "")
- if err != nil {
- t.Fatal(err)
- }
-
- return leaseID
-}
-
-func TestExpiration_MarkIrrevocable(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- exp := c.expiration
- ctx := namespace.RootContext(nil)
-
- leaseID := registerOneLease(t, ctx, exp)
- loadedLE, err := exp.loadEntry(ctx, leaseID)
- if err != nil {
- t.Fatalf("error loading non irrevocable lease: %v", err)
- }
-
- if loadedLE.isIrrevocable() {
- t.Fatalf("lease is irrevocable and shouldn't be")
- }
- if _, ok := exp.irrevocable.Load(leaseID); ok {
- t.Fatalf("lease included in irrevocable map")
- }
- if _, ok := exp.pending.Load(leaseID); !ok {
- t.Fatalf("lease not included in pending map")
- }
-
- irrevocableErr := fmt.Errorf("test irrevocable error")
-
- exp.pendingLock.Lock()
- exp.markLeaseIrrevocable(ctx, loadedLE, irrevocableErr)
- exp.pendingLock.Unlock()
-
- if !loadedLE.isIrrevocable() {
- t.Fatalf("irrevocable lease is not irrevocable and should be")
- }
- if loadedLE.RevokeErr != irrevocableErr.Error() {
- t.Errorf("irrevocable lease has wrong error message. expected %s, got %s", irrevocableErr.Error(), loadedLE.RevokeErr)
- }
- if _, ok := exp.irrevocable.Load(leaseID); !ok {
- t.Fatalf("irrevocable lease not included in irrevocable map")
- }
-
- exp.pendingLock.RLock()
- irrevocableLeaseCount := exp.irrevocableLeaseCount
- exp.pendingLock.RUnlock()
-
- if irrevocableLeaseCount != 1 {
- t.Fatalf("expected 1 irrevocable lease, found %d", irrevocableLeaseCount)
- }
- if _, ok := exp.pending.Load(leaseID); ok {
- t.Fatalf("irrevocable lease included in pending map")
- }
- if _, ok := exp.nonexpiring.Load(leaseID); ok {
- t.Fatalf("irrevocable lease included in nonexpiring map")
- }
-
- // stop and restore to verify that irrevocable leases are properly loaded from storage
- err = c.stopExpiration()
- if err != nil {
- t.Fatalf("error stopping expiration manager: %v", err)
- }
-
- err = exp.Restore(nil)
- if err != nil {
- t.Fatalf("error restoring expiration manager: %v", err)
- }
-
- loadedLE, err = exp.loadEntry(ctx, leaseID)
- if err != nil {
- t.Fatalf("error loading non irrevocable lease after restore: %v", err)
- }
- exp.updatePending(loadedLE)
-
- if !loadedLE.isIrrevocable() {
- t.Fatalf("irrevocable lease is not irrevocable and should be")
- }
- if loadedLE.RevokeErr != irrevocableErr.Error() {
- t.Errorf("irrevocable lease has wrong error message. expected %s, got %s", irrevocableErr.Error(), loadedLE.RevokeErr)
- }
- if _, ok := exp.irrevocable.Load(leaseID); !ok {
- t.Fatalf("irrevocable lease not included in irrevocable map")
- }
- if _, ok := exp.pending.Load(leaseID); ok {
- t.Fatalf("irrevocable lease included in pending map")
- }
- if _, ok := exp.nonexpiring.Load(leaseID); ok {
- t.Fatalf("irrevocable lease included in nonexpiring map")
- }
-}
-
-func TestExpiration_FetchLeaseTimesIrrevocable(t *testing.T) {
- exp := mockExpiration(t)
- ctx := namespace.RootContext(nil)
-
- leaseID := registerOneLease(t, ctx, exp)
- expectedLeaseTimes, err := exp.FetchLeaseTimes(ctx, leaseID)
- if err != nil {
- t.Fatalf("error getting lease times: %v", err)
- }
- if expectedLeaseTimes == nil {
- t.Fatal("got nil lease")
- }
-
- le, err := exp.loadEntry(ctx, leaseID)
- if err != nil {
- t.Fatalf("error loading lease: %v", err)
- }
- exp.pendingLock.Lock()
- exp.markLeaseIrrevocable(ctx, le, fmt.Errorf("test irrevocable error"))
- exp.pendingLock.Unlock()
-
- irrevocableLeaseTimes, err := exp.FetchLeaseTimes(ctx, leaseID)
- if err != nil {
- t.Fatalf("error getting irrevocable lease times: %v", err)
- }
- if irrevocableLeaseTimes == nil {
- t.Fatal("got nil irrevocable lease")
- }
-
- // strip monotonic clock reading
- expectedLeaseTimes.IssueTime = expectedLeaseTimes.IssueTime.Round(0)
- expectedLeaseTimes.ExpireTime = expectedLeaseTimes.ExpireTime.Round(0)
- expectedLeaseTimes.LastRenewalTime = expectedLeaseTimes.LastRenewalTime.Round(0)
-
- if !irrevocableLeaseTimes.IssueTime.Equal(expectedLeaseTimes.IssueTime) {
- t.Errorf("bad issue time. expected %v, got %v", expectedLeaseTimes.IssueTime, irrevocableLeaseTimes.IssueTime)
- }
- if !irrevocableLeaseTimes.ExpireTime.Equal(expectedLeaseTimes.ExpireTime) {
- t.Errorf("bad expire time. expected %v, got %v", expectedLeaseTimes.ExpireTime, irrevocableLeaseTimes.ExpireTime)
- }
- if !irrevocableLeaseTimes.LastRenewalTime.Equal(expectedLeaseTimes.LastRenewalTime) {
- t.Errorf("bad last renew time. expected %v, got %v", expectedLeaseTimes.LastRenewalTime, irrevocableLeaseTimes.LastRenewalTime)
- }
-}
-
-func TestExpiration_StopClearsIrrevocableCache(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- exp := c.expiration
- ctx := namespace.RootContext(nil)
-
- leaseID := registerOneLease(t, ctx, exp)
- le, err := exp.loadEntry(ctx, leaseID)
- if err != nil {
- t.Fatalf("error loading non irrevocable lease: %v", err)
- }
-
- exp.pendingLock.Lock()
- exp.markLeaseIrrevocable(ctx, le, fmt.Errorf("test irrevocable error"))
- exp.pendingLock.Unlock()
-
- err = c.stopExpiration()
- if err != nil {
- t.Fatalf("error stopping expiration manager: %v", err)
- }
-
- if _, ok := exp.irrevocable.Load(leaseID); ok {
- t.Error("expiration manager irrevocable cache should be cleared on stop")
- }
-
- exp.pendingLock.RLock()
- irrevocableLeaseCount := exp.irrevocableLeaseCount
- exp.pendingLock.RUnlock()
-
- if irrevocableLeaseCount != 0 {
- t.Errorf("expected 0 leases, found %d", irrevocableLeaseCount)
- }
-}
-
-func TestExpiration_errorIsUnrecoverable(t *testing.T) {
- testCases := []struct {
- err error
- isUnrecoverable bool
- }{
- {
- err: logical.ErrUnrecoverable,
- isUnrecoverable: true,
- },
- {
- err: logical.ErrUnsupportedOperation,
- isUnrecoverable: true,
- },
- {
- err: logical.ErrUnsupportedPath,
- isUnrecoverable: true,
- },
- {
- err: logical.ErrInvalidRequest,
- isUnrecoverable: true,
- },
- {
- err: logical.ErrPermissionDenied,
- isUnrecoverable: false,
- },
- {
- err: logical.ErrMultiAuthzPending,
- isUnrecoverable: false,
- },
- {
- err: fmt.Errorf("some other error"),
- isUnrecoverable: false,
- },
- }
-
- for _, tc := range testCases {
- out := errIsUnrecoverable(tc.err)
- if out != tc.isUnrecoverable {
- t.Errorf("wrong answer: expected %t, got %t", tc.isUnrecoverable, out)
- }
- }
-}
-
-func TestExpiration_unrecoverableErrorMakesIrrevocable(t *testing.T) {
- exp := mockExpiration(t)
- ctx := namespace.RootContext(nil)
-
- makeJob := func() *revocationJob {
- leaseID := registerOneLease(t, ctx, exp)
-
- job, err := newRevocationJob(ctx, leaseID, namespace.RootNamespace, exp)
- if err != nil {
- t.Fatalf("err making revocation job: %v", err)
- }
-
- return job
- }
-
- testCases := []struct {
- err error
- job *revocationJob
- shouldBeIrrevocable bool
- }{
- {
- err: logical.ErrUnrecoverable,
- job: makeJob(),
- shouldBeIrrevocable: true,
- },
- {
- err: logical.ErrInvalidRequest,
- job: makeJob(),
- shouldBeIrrevocable: true,
- },
- {
- err: logical.ErrPermissionDenied,
- job: makeJob(),
- shouldBeIrrevocable: false,
- },
- {
- err: logical.ErrRateLimitQuotaExceeded,
- job: makeJob(),
- shouldBeIrrevocable: false,
- },
- {
- err: fmt.Errorf("some random recoverable error"),
- job: makeJob(),
- shouldBeIrrevocable: false,
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.err.Error(), func(t *testing.T) {
- tc.job.OnFailure(tc.err)
-
- le, err := exp.loadEntry(ctx, tc.job.leaseID)
- if err != nil {
- t.Fatalf("could not load leaseID %q: %v", tc.job.leaseID, err)
- }
- if le == nil {
- t.Fatalf("nil lease for leaseID: %q", tc.job.leaseID)
- }
-
- isIrrevocable := le.isIrrevocable()
- if isIrrevocable != tc.shouldBeIrrevocable {
- t.Errorf("expected irrevocable: %t, got irrevocable: %t", tc.shouldBeIrrevocable, isIrrevocable)
- }
- })
- }
-}
-
-func TestExpiration_getIrrevocableLeaseCounts(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
-
- backends := []*backend{
- {
- path: "foo/bar/1/",
- ns: namespace.RootNamespace,
- },
- {
- path: "foo/bar/2/",
- ns: namespace.RootNamespace,
- },
- {
- path: "foo/bar/3/",
- ns: namespace.RootNamespace,
- },
- }
- pathToMount, err := mountNoopBackends(c, backends)
- if err != nil {
- t.Fatal(err)
- }
-
- exp := c.expiration
-
- expectedPerMount := 10
- for i := 0; i < expectedPerMount; i++ {
- for _, backend := range backends {
- if _, err := c.AddIrrevocableLease(namespace.RootContext(nil), backend.path); err != nil {
- t.Fatal(err)
- }
- }
- }
-
- out, err := exp.getIrrevocableLeaseCounts(namespace.RootContext(nil), false)
- if err != nil {
- t.Fatalf("error getting irrevocable lease counts: %v", err)
- }
-
- exp.pendingLock.RLock()
- irrevocableLeaseCount := exp.irrevocableLeaseCount
- exp.pendingLock.RUnlock()
-
- if irrevocableLeaseCount != len(backends)*expectedPerMount {
- t.Fatalf("incorrect lease counts. expected %d got %d", len(backends)*expectedPerMount, irrevocableLeaseCount)
- }
- countRaw, ok := out["lease_count"]
- if !ok {
- t.Fatal("no lease count")
- }
-
- countPerMountRaw, ok := out["counts"]
- if !ok {
- t.Fatal("no count per mount")
- }
-
- count := countRaw.(int)
- countPerMount := countPerMountRaw.(map[string]int)
-
- expectedCount := len(backends) * expectedPerMount
- if count != expectedCount {
- t.Errorf("bad count. expected %d, got %d", expectedCount, count)
- }
-
- if len(countPerMount) != len(backends) {
- t.Fatalf("bad mounts. got %#v, expected %#v", countPerMount, backends)
- }
-
- for _, backend := range backends {
- mountCount := countPerMount[pathToMount[backend.path]]
- if mountCount != expectedPerMount {
- t.Errorf("bad count for prefix %q. expected %d, got %d", backend.path, expectedPerMount, mountCount)
- }
- }
-}
-
-func TestExpiration_listIrrevocableLeases(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
-
- backends := []*backend{
- {
- path: "foo/bar/1/",
- ns: namespace.RootNamespace,
- },
- {
- path: "foo/bar/2/",
- ns: namespace.RootNamespace,
- },
- {
- path: "foo/bar/3/",
- ns: namespace.RootNamespace,
- },
- }
- pathToMount, err := mountNoopBackends(c, backends)
- if err != nil {
- t.Fatal(err)
- }
-
- exp := c.expiration
-
- expectedLeases := make([]*basicLeaseTestInfo, 0)
- expectedPerMount := 10
- for i := 0; i < expectedPerMount; i++ {
- for _, backend := range backends {
- le, err := c.AddIrrevocableLease(namespace.RootContext(nil), backend.path)
- if err != nil {
- t.Fatal(err)
- }
- expectedLeases = append(expectedLeases, &basicLeaseTestInfo{
- id: le.id,
- mount: pathToMount[backend.path],
- expire: le.expire,
- })
- }
- }
-
- out, warn, err := exp.listIrrevocableLeases(namespace.RootContext(nil), false, false, MaxIrrevocableLeasesToReturn)
- if err != nil {
- t.Fatalf("error listing irrevocable leases: %v", err)
- }
- if warn != "" {
- t.Errorf("expected no warning, got %q", warn)
- }
-
- countRaw, ok := out["lease_count"]
- if !ok {
- t.Fatal("no lease count")
- }
-
- leasesRaw, ok := out["leases"]
- if !ok {
- t.Fatal("no leases")
- }
-
- count := countRaw.(int)
- leases := leasesRaw.([]*leaseResponse)
-
- expectedCount := len(backends) * expectedPerMount
- if count != expectedCount {
- t.Errorf("bad count. expected %d, got %d", expectedCount, count)
- }
- if len(leases) != len(expectedLeases) {
- t.Errorf("bad lease results. expected %d, got %d with values %v", len(expectedLeases), len(leases), leases)
- }
-
- // `leases` is already sorted by lease ID
- sort.Slice(expectedLeases, func(i, j int) bool {
- return expectedLeases[i].id < expectedLeases[j].id
- })
- sort.SliceStable(expectedLeases, func(i, j int) bool {
- return expectedLeases[i].expire.Before(expectedLeases[j].expire)
- })
-
- for i, lease := range expectedLeases {
- if lease.id != leases[i].LeaseID {
- t.Errorf("bad lease id. expected %q, got %q", lease.id, leases[i].LeaseID)
- }
- if lease.mount != leases[i].MountID {
- t.Errorf("bad mount id. expected %q, got %q", lease.mount, leases[i].MountID)
- }
- }
-}
-
-func TestExpiration_listIrrevocableLeases_includeAll(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- exp := c.expiration
-
- expectedNumLeases := MaxIrrevocableLeasesToReturn + 10
- for i := 0; i < expectedNumLeases; i++ {
- if _, err := c.AddIrrevocableLease(namespace.RootContext(nil), "foo/"); err != nil {
- t.Fatal(err)
- }
- }
-
- dataRaw, warn, err := exp.listIrrevocableLeases(namespace.RootContext(nil), false, false, MaxIrrevocableLeasesToReturn)
- if err != nil {
- t.Fatalf("expected no error, got: %v", err)
- }
- if warn != MaxIrrevocableLeasesWarning {
- t.Errorf("expected warning %q, got %q", MaxIrrevocableLeasesWarning, warn)
- }
- if dataRaw == nil {
- t.Fatal("expected partial data, got nil")
- }
-
- leaseListLength := len(dataRaw["leases"].([]*leaseResponse))
- if leaseListLength != MaxIrrevocableLeasesToReturn {
- t.Fatalf("expected %d results, got %d", MaxIrrevocableLeasesToReturn, leaseListLength)
- }
-
- dataRaw, warn, err = exp.listIrrevocableLeases(namespace.RootContext(nil), false, true, 0)
- if err != nil {
- t.Fatalf("got error when using limit=none: %v", err)
- }
- if warn != "" {
- t.Errorf("expected no warning, got %q", warn)
- }
- if dataRaw == nil {
- t.Fatalf("got nil data when using limit=none")
- }
-
- leaseListLength = len(dataRaw["leases"].([]*leaseResponse))
- if leaseListLength != expectedNumLeases {
- t.Fatalf("expected %d results, got %d", MaxIrrevocableLeasesToReturn, expectedNumLeases)
- }
-
- numLeasesRaw, ok := dataRaw["lease_count"]
- if !ok {
- t.Fatalf("lease count data not present")
- }
- if numLeasesRaw == nil {
- t.Fatalf("nil lease count")
- }
-
- numLeases := numLeasesRaw.(int)
- if numLeases != expectedNumLeases {
- t.Errorf("bad lease count. expected %d, got %d", expectedNumLeases, numLeases)
- }
-}
diff --git a/vault/expiration_testing_util_common.go b/vault/expiration_testing_util_common.go
deleted file mode 100644
index 57b1718e5..000000000
--- a/vault/expiration_testing_util_common.go
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "fmt"
- "math/rand"
- "path"
- "time"
-
- uuid "github.com/hashicorp/go-uuid"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-type basicLeaseTestInfo struct {
- id string
- mount string
- expire time.Time
-}
-
-// add an irrevocable lease for test purposes
-// returns the lease ID and expire time
-func (c *Core) AddIrrevocableLease(ctx context.Context, pathPrefix string) (*basicLeaseTestInfo, error) {
- exp := c.expiration
-
- uuid, err := uuid.GenerateUUID()
- if err != nil {
- return nil, fmt.Errorf("error generating uuid: %w", err)
- }
-
- ns, err := namespace.FromContext(ctx)
- if err != nil {
- return nil, fmt.Errorf("error getting namespace from context: %w", err)
- }
- if ns == nil {
- ns = namespace.RootNamespace
- }
-
- leaseID := path.Join(pathPrefix, "lease"+uuid)
-
- if ns != namespace.RootNamespace {
- leaseID = fmt.Sprintf("%s.%s", leaseID, ns.ID)
- }
-
- randomTimeDelta := time.Duration(rand.Int31n(24))
- le := &leaseEntry{
- LeaseID: leaseID,
- Path: pathPrefix,
- namespace: ns,
- IssueTime: time.Now(),
- ExpireTime: time.Now().Add(randomTimeDelta * time.Hour),
- RevokeErr: "some error message",
- }
-
- exp.pendingLock.Lock()
- defer exp.pendingLock.Unlock()
-
- if err := exp.persistEntry(context.Background(), le); err != nil {
- return nil, fmt.Errorf("error persisting irrevocable lease: %w", err)
- }
-
- exp.updatePendingInternal(le)
-
- return &basicLeaseTestInfo{
- id: le.LeaseID,
- expire: le.ExpireTime,
- }, nil
-}
-
-// InjectIrrevocableLeases injects `count` irrevocable leases (currently to a
-// single mount).
-// It returns a map of the mount accessor to the number of leases stored there
-func (c *Core) InjectIrrevocableLeases(ctx context.Context, count int) (map[string]int, error) {
- out := make(map[string]int)
- for i := 0; i < count; i++ {
- le, err := c.AddIrrevocableLease(ctx, "foo/")
- if err != nil {
- return nil, err
- }
-
- mountAccessor := c.expiration.getLeaseMountAccessor(ctx, le.id)
- if _, ok := out[mountAccessor]; !ok {
- out[mountAccessor] = 0
- }
-
- out[mountAccessor]++
- }
-
- return out, nil
-}
-
-type backend struct {
- path string
- ns *namespace.Namespace
-}
-
-// set up multiple mounts, and return a mapping of the path to the mount accessor
-func mountNoopBackends(c *Core, backends []*backend) (map[string]string, error) {
- // enable the noop backend
- c.logicalBackends["noop"] = func(ctx context.Context, config *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{}, nil
- }
-
- pathToMount := make(map[string]string)
- for _, backend := range backends {
- me := &MountEntry{
- Table: mountTableType,
- Path: backend.path,
- Type: "noop",
- }
-
- nsCtx := namespace.ContextWithNamespace(context.Background(), backend.ns)
- if err := c.mount(nsCtx, me); err != nil {
- return nil, fmt.Errorf("error mounting backend %s: %w", backend.path, err)
- }
-
- mount := c.router.MatchingMountEntry(nsCtx, backend.path)
- if mount == nil {
- return nil, fmt.Errorf("couldn't find mount for path %s", backend.path)
- }
- pathToMount[backend.path] = mount.Accessor
- }
-
- return pathToMount, nil
-}
-
-func (c *Core) FetchLeaseCountToRevoke() int {
- c.expiration.pendingLock.RLock()
- defer c.expiration.pendingLock.RUnlock()
- return c.expiration.leaseCount
-}
diff --git a/vault/external_plugin_test.go b/vault/external_plugin_test.go
deleted file mode 100644
index 4f29b2e8f..000000000
--- a/vault/external_plugin_test.go
+++ /dev/null
@@ -1,992 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "encoding/hex"
- "errors"
- "fmt"
- "os"
- "path"
- "path/filepath"
- "strings"
- "testing"
-
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/helper/testhelpers/corehelpers"
- "github.com/hashicorp/vault/helper/testhelpers/pluginhelpers"
- "github.com/hashicorp/vault/sdk/framework"
- "github.com/hashicorp/vault/sdk/helper/consts"
- "github.com/hashicorp/vault/sdk/helper/pluginutil"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/sdk/plugin"
- "github.com/hashicorp/vault/sdk/plugin/mock"
- "github.com/hashicorp/vault/version"
-)
-
-const vaultTestingMockPluginEnv = "VAULT_TESTING_MOCK_PLUGIN"
-
-// version is used to override the plugin's self-reported version
-func testCoreWithPlugins(t *testing.T, typ consts.PluginType, versions ...string) (*Core, []pluginhelpers.TestPlugin) {
- t.Helper()
- pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
- t.Cleanup(func() { cleanup(t) })
-
- var plugins []pluginhelpers.TestPlugin
- for _, version := range versions {
- plugins = append(plugins, pluginhelpers.CompilePlugin(t, typ, version, pluginDir))
- }
- conf := &CoreConfig{
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- PluginDirectory: pluginDir,
- }
- core := TestCoreWithSealAndUI(t, conf)
- core, _, _ = testCoreUnsealed(t, core)
- return core, plugins
-}
-
-func TestCore_EnableExternalPlugin(t *testing.T) {
- for name, tc := range map[string]struct {
- pluginType consts.PluginType
- routerPath string
- expectedMatch string
- }{
- "enable external credential plugin": {
- pluginType: consts.PluginTypeCredential,
- routerPath: "auth/foo/bar",
- expectedMatch: "auth/foo/",
- },
- "enable external secrets plugin": {
- pluginType: consts.PluginTypeSecrets,
- routerPath: "foo/bar",
- expectedMatch: "foo/",
- },
- } {
- t.Run(name, func(t *testing.T) {
- coreConfig := &CoreConfig{
- DisableMlock: true,
- DisableCache: true,
- Logger: log.NewNullLogger(),
- CredentialBackends: map[string]logical.Factory{},
- }
-
- cluster := NewTestCluster(t, coreConfig, &TestClusterOptions{
- Plugins: &TestPluginConfig{
- Typ: tc.pluginType,
- Versions: []string{""},
- },
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- c := cluster.Cores[0].Core
- TestWaitActive(t, c)
-
- plugins := cluster.Plugins
-
- registerPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType.String(), "1.0.0", plugins[0].Sha256, plugins[0].FileName)
-
- mountPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType, "v1.0.0", "")
-
- match := c.router.MatchingMount(namespace.RootContext(nil), tc.routerPath)
- if match != tc.expectedMatch {
- t.Fatalf("missing mount, match: %q", match)
- }
- })
- }
-}
-
-func TestCore_EnableExternalPlugin_MultipleVersions(t *testing.T) {
- for name, tc := range map[string]struct {
- pluginType consts.PluginType
- registerVersions []string
- mountVersion string
- expectedVersion string
- routerPath string
- expectedMatch string
- }{
- "enable external credential plugin, multiple versions available": {
- pluginType: consts.PluginTypeCredential,
- registerVersions: []string{"v1.0.0", "v1.0.1"},
- mountVersion: "v1.0.0",
- expectedVersion: "v1.0.0",
- routerPath: "auth/foo/bar",
- expectedMatch: "auth/foo/",
- },
- "enable external secrets plugin, multiple versions available": {
- pluginType: consts.PluginTypeSecrets,
- registerVersions: []string{"v1.0.0", "v1.0.1"},
- mountVersion: "v1.0.0",
- expectedVersion: "v1.0.0",
- routerPath: "foo/bar",
- expectedMatch: "foo/",
- },
- "enable external credential plugin, multiple versions available, select other version": {
- pluginType: consts.PluginTypeCredential,
- registerVersions: []string{"v1.0.0", "v1.0.1"},
- mountVersion: "v1.0.1",
- expectedVersion: "v1.0.1",
- routerPath: "auth/foo/bar",
- expectedMatch: "auth/foo/",
- },
- "enable external secrets plugin, multiple versions available, select other version": {
- pluginType: consts.PluginTypeSecrets,
- registerVersions: []string{"v1.0.0", "v1.0.1"},
- mountVersion: "v1.0.1",
- expectedVersion: "v1.0.1",
- routerPath: "foo/bar",
- expectedMatch: "foo/",
- },
- "enable external credential plugin, selects latest when version not specified": {
- pluginType: consts.PluginTypeCredential,
- registerVersions: []string{"v1.0.0", "v1.0.1"},
- mountVersion: "",
- expectedVersion: "v1.0.1",
- routerPath: "auth/foo/bar",
- expectedMatch: "auth/foo/",
- },
- "enable external secrets plugin, selects latest when version not specified": {
- pluginType: consts.PluginTypeSecrets,
- registerVersions: []string{"v1.0.0", "v1.0.1"},
- mountVersion: "",
- expectedVersion: "v1.0.1",
- routerPath: "foo/bar",
- expectedMatch: "foo/",
- },
- } {
- t.Run(name, func(t *testing.T) {
- c, plugins := testCoreWithPlugins(t, tc.pluginType, "")
- for _, version := range tc.registerVersions {
- registerPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType.String(), version, plugins[0].Sha256, plugins[0].FileName)
- }
-
- mountPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType, tc.mountVersion, "")
-
- match := c.router.MatchingMount(namespace.RootContext(nil), tc.routerPath)
- if match != tc.expectedMatch {
- t.Fatalf("missing mount, match: %q", match)
- }
-
- raw, _ := c.router.root.Get(match)
- if raw.(*routeEntry).mountEntry.Version != tc.expectedVersion {
- t.Errorf("Expected mount to be version %s but got %s", tc.expectedVersion, raw.(*routeEntry).mountEntry.Version)
- }
-
- if raw.(*routeEntry).mountEntry.RunningVersion != tc.expectedVersion {
- t.Errorf("Expected mount running version to be %s but got %s", tc.expectedVersion, raw.(*routeEntry).mountEntry.RunningVersion)
- }
-
- if raw.(*routeEntry).mountEntry.RunningSha256 == "" {
- t.Errorf("Expected RunningSha256 to be present: %+v", raw.(*routeEntry).mountEntry.RunningSha256)
- }
- })
- }
-}
-
-func TestCore_EnableExternalPlugin_Deregister_SealUnseal(t *testing.T) {
- pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
- t.Cleanup(func() { cleanup(t) })
-
- // create an external plugin to shadow the builtin "pending-removal-test-plugin"
- pluginName := "therug"
- plugin := pluginhelpers.CompilePlugin(t, consts.PluginTypeCredential, "", pluginDir)
- err := os.Link(path.Join(pluginDir, plugin.FileName), path.Join(pluginDir, pluginName))
- if err != nil {
- t.Fatal(err)
- }
- conf := &CoreConfig{
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- PluginDirectory: pluginDir,
- }
-
- c := TestCoreWithSealAndUI(t, conf)
- c, keys, root := testCoreUnsealed(t, c)
-
- // Register a plugin
- registerPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "", plugin.Sha256, plugin.FileName)
- mountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
- plugct := len(c.pluginCatalog.externalPlugins)
- if plugct != 1 {
- t.Fatalf("expected a single external plugin entry after registering, got: %d", plugct)
- }
-
- // Now pull the rug out from underneath us
- deregisterPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "", "", "")
-
- if err := c.Seal(root); err != nil {
- t.Fatalf("err: %v", err)
- }
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("err: should be unsealed")
- }
- }
-
- plugct = len(c.pluginCatalog.externalPlugins)
- if plugct != 0 {
- t.Fatalf("expected no plugin entries after unseal, got: %d", plugct)
- }
-
- found := false
- mounts, err := c.ListAuths()
- if err != nil {
- t.Fatal(err)
- }
- for _, mount := range mounts {
- if mount.Type == pluginName {
- found = true
- break
- }
- }
-
- if !found {
- t.Fatalf("expected to find %s mount, but got none", pluginName)
- }
-}
-
-// TestCore_Unseal_isMajorVersionFirstMount_PendingRemoval_Plugin tests the
-// behavior of deprecated builtins when attempting to unseal Vault after a major
-// version upgrade. It simulates this behavior by instantiating a Vault cluster,
-// registering a shadow plugin to mount a builtin, and deregistering the shadow
-// plugin. The first unseal should work. Before sealing and unsealing again, the
-// version store is cleared. Vault sees the next unseal as a major upgrade and
-// should immediately shut down.
-func TestCore_Unseal_isMajorVersionFirstMount_PendingRemoval_Plugin(t *testing.T) {
- pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
- t.Cleanup(func() { cleanup(t) })
-
- // create an external plugin to shadow the builtin "pending-removal-test-plugin"
- pluginName := "pending-removal-test-plugin"
- plugin := pluginhelpers.CompilePlugin(t, consts.PluginTypeCredential, "", pluginDir)
- err := os.Link(path.Join(pluginDir, plugin.FileName), path.Join(pluginDir, pluginName))
- if err != nil {
- t.Fatal(err)
- }
- conf := &CoreConfig{
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- PluginDirectory: pluginDir,
- }
- c := TestCoreWithSealAndUI(t, conf)
- c, keys, root := testCoreUnsealed(t, c)
-
- // Register a shadow plugin
- registerPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "", plugin.Sha256, plugin.FileName)
- mountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
-
- // Deregister shadow plugin
- deregisterPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "", plugin.Sha256, plugin.FileName)
-
- // Make sure this isn't the first mount for the current major version.
- if c.isMajorVersionFirstMount(context.Background()) {
- t.Fatalf("expected major version to register as mounted")
- }
-
- if err := c.Seal(root); err != nil {
- t.Fatalf("err: %v", err)
- }
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("err: should be unsealed")
- }
- }
-
- // Now remove version history and try again
- vaultVersionPath := "core/versions/"
- key := vaultVersionPath + version.Version
- if err := c.barrier.Delete(context.Background(), key); err != nil {
- t.Fatal(err)
- }
-
- // loadVersionHistory doesn't care about invalidating old entries, since
- // they shouldn't really be deleted from the version store. It just updates
- // the map, so we need to manually delete the current entry.
- delete(c.versionHistory, version.Version)
-
- // Make sure this appears to be the first mount for the current major
- // version.
- if !c.isMajorVersionFirstMount(context.Background()) {
- t.Fatalf("expected major version first mount")
- }
-
- // Seal again and check for unseal failure.
- if err := c.Seal(root); err != nil {
- t.Fatalf("err: %v", err)
- }
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c, key)
- if i+1 == len(keys) {
- if !errors.Is(err, errLoadAuthFailed) {
- t.Fatalf("expected error: %q, got: %q", errLoadAuthFailed, err)
- }
-
- if unseal {
- t.Fatalf("err: should not be unsealed")
- }
- }
- }
-}
-
-func TestCore_EnableExternalPlugin_PendingRemoval(t *testing.T) {
- pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
- t.Cleanup(func() { cleanup(t) })
-
- // create an external plugin to shadow the builtin "pending-removal-test-plugin"
- pluginName := "pending-removal-test-plugin"
- plugin := pluginhelpers.CompilePlugin(t, consts.PluginTypeCredential, "v1.2.3", pluginDir)
- err := os.Link(path.Join(pluginDir, plugin.FileName), path.Join(pluginDir, pluginName))
- if err != nil {
- t.Fatal(err)
- }
- conf := &CoreConfig{
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- PluginDirectory: pluginDir,
- }
-
- c := TestCoreWithSealAndUI(t, conf)
- c, _, _ = testCoreUnsealed(t, c)
-
- pendingRemovalString := "pending removal"
-
- // Create a new auth method with builtin pending-removal-test-plugin
- resp, err := mountPluginWithResponse(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
- if err == nil {
- t.Fatalf("expected error when mounting deprecated backend")
- }
- if resp == nil || resp.Data == nil || !strings.Contains(resp.Data["error"].(string), pendingRemovalString) {
- t.Fatalf("expected error response to contain %q but got %+v", pendingRemovalString, resp)
- }
-
- // Register a shadow plugin
- registerPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "v1.2.3", plugin.Sha256, plugin.FileName)
- mountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
-}
-
-func TestCore_EnableExternalPlugin_ShadowBuiltin(t *testing.T) {
- pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
- t.Cleanup(func() { cleanup(t) })
-
- // create an external plugin to shadow the builtin "approle"
- plugin := pluginhelpers.CompilePlugin(t, consts.PluginTypeCredential, "v1.2.3", pluginDir)
- err := os.Link(path.Join(pluginDir, plugin.FileName), path.Join(pluginDir, "approle"))
- if err != nil {
- t.Fatal(err)
- }
- pluginName := "approle"
- conf := &CoreConfig{
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- PluginDirectory: pluginDir,
- }
- c := TestCoreWithSealAndUI(t, conf)
- c, _, _ = testCoreUnsealed(t, c)
-
- verifyAuthListDeprecationStatus := func(authName string, checkExists bool) error {
- req := logical.TestRequest(t, logical.ReadOperation, mountTable(consts.PluginTypeCredential))
- req.Data = map[string]interface{}{
- "type": authName,
- }
- resp, err := c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- return err
- }
- status := resp.Data["deprecation_status"]
- if checkExists && status == nil {
- return fmt.Errorf("expected deprecation status but found none")
- } else if !checkExists && status != nil {
- return fmt.Errorf("expected nil deprecation status but found %q", status)
- }
- return nil
- }
-
- // Create a new auth method with builtin approle
- mountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
-
- // Read the auth table to verify deprecation status
- if err := verifyAuthListDeprecationStatus(pluginName, true); err != nil {
- t.Fatal(err)
- }
-
- // Register a shadow plugin
- registerPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "v1.2.3", plugin.Sha256, plugin.FileName)
-
- // Verify auth table hasn't changed
- if err := verifyAuthListDeprecationStatus(pluginName, true); err != nil {
- t.Fatal(err)
- }
-
- // Remount auth method using registered shadow plugin
- unmountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
- mountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
-
- // Verify auth table has changed
- if err := verifyAuthListDeprecationStatus(pluginName, false); err != nil {
- t.Fatal(err)
- }
-
- // Deregister shadow plugin
- deregisterPlugin(t, c.systemBackend, pluginName, consts.PluginTypeSecrets.String(), "v1.2.3", plugin.Sha256, plugin.FileName)
-
- // Verify auth table hasn't changed
- if err := verifyAuthListDeprecationStatus(pluginName, false); err != nil {
- t.Fatal(err)
- }
-
- // Remount auth method
- unmountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
- mountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
-
- // Verify auth table has changed
- if err := verifyAuthListDeprecationStatus(pluginName, false); err != nil {
- t.Fatal(err)
- }
-}
-
-func TestCore_EnableExternalKv_MultipleVersions(t *testing.T) {
- pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
- t.Cleanup(func() { cleanup(t) })
-
- // new kv plugin can be registered but not mounted
- plugin := pluginhelpers.CompilePlugin(t, consts.PluginTypeSecrets, "v1.2.3", pluginDir)
- err := os.Link(path.Join(pluginDir, plugin.FileName), path.Join(pluginDir, "kv"))
- if err != nil {
- t.Fatal(err)
- }
- pluginName := "kv"
- conf := &CoreConfig{
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- PluginDirectory: pluginDir,
- }
- c := TestCoreWithSealAndUI(t, conf)
- c, _, _ = testCoreUnsealed(t, c)
-
- registerPlugin(t, c.systemBackend, pluginName, consts.PluginTypeSecrets.String(), "v1.2.3", plugin.Sha256, plugin.FileName)
- req := logical.TestRequest(t, logical.ReadOperation, "plugins/catalog")
- resp, err := c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp.Error() != nil {
- t.Fatalf("%#v", resp)
- }
- found := false
- for _, plugin := range resp.Data["detailed"].([]map[string]any) {
- if plugin["name"] == pluginName && plugin["version"] == "v1.2.3" {
- found = true
- break
- }
- }
- if !found {
- t.Fatal("Expected to find v1.2.3 kv plugin but did not")
- }
- req = logical.TestRequest(t, logical.UpdateOperation, mountTable(consts.PluginTypeSecrets))
- req.Data = map[string]interface{}{
- "type": pluginName,
- }
- req.Data["config"] = map[string]interface{}{
- "plugin_version": "v1.2.3",
- }
- resp, err = c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp.Error() == nil {
- t.Fatal("Expected resp error but got successful response")
- }
-}
-
-func TestCore_EnableExternalNoop_MultipleVersions(t *testing.T) {
- pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
- t.Cleanup(func() { cleanup(t) })
-
- // new noop plugin can be registered but not mounted
- plugin := pluginhelpers.CompilePlugin(t, consts.PluginTypeCredential, "v1.2.3", pluginDir)
- err := os.Link(path.Join(pluginDir, plugin.FileName), path.Join(pluginDir, "noop"))
- if err != nil {
- t.Fatal(err)
- }
- pluginName := "noop"
- conf := &CoreConfig{
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- PluginDirectory: pluginDir,
- }
- c := TestCoreWithSealAndUI(t, conf)
- c, _, _ = testCoreUnsealed(t, c)
-
- registerPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "v1.2.3", plugin.Sha256, plugin.FileName)
- req := logical.TestRequest(t, logical.ReadOperation, "plugins/catalog")
- resp, err := c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp.Error() != nil {
- t.Fatalf("%#v", resp)
- }
- found := false
- for _, plugin := range resp.Data["detailed"].([]map[string]any) {
- if plugin["name"] == "noop" && plugin["version"] == "v1.2.3" {
- found = true
- break
- }
- }
- if !found {
- t.Fatal("Expected to find v1.2.3 noop plugin but did not")
- }
- req = logical.TestRequest(t, logical.UpdateOperation, mountTable(consts.PluginTypeCredential))
- req.Data = map[string]interface{}{
- "type": pluginName,
- }
- req.Data["config"] = map[string]interface{}{
- "plugin_version": "v1.2.3",
- }
- resp, err = c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp.Error() == nil {
- t.Fatal("Expected resp error but got successful response")
- }
-}
-
-func TestCore_EnableExternalPlugin_NoVersionsOkay(t *testing.T) {
- for name, tc := range map[string]struct {
- pluginType consts.PluginType
- routerPath string
- expectedMatch string
- }{
- "enable external credential plugin with no version": {
- pluginType: consts.PluginTypeCredential,
- routerPath: "auth/foo/bar",
- expectedMatch: "auth/foo/",
- },
- "enable external secrets plugin with no version": {
- pluginType: consts.PluginTypeSecrets,
- routerPath: "foo/bar",
- expectedMatch: "foo/",
- },
- } {
- t.Run(name, func(t *testing.T) {
- c, plugins := testCoreWithPlugins(t, tc.pluginType, "")
- // When an unversioned plugin is registered, mounting a plugin with no
- // version specified should mount the unversioned plugin even if there
- // are versioned plugins available.
- for _, version := range []string{"", "v1.0.0"} {
- registerPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType.String(), version, plugins[0].Sha256, plugins[0].FileName)
- }
-
- mountPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType, "", "")
-
- match := c.router.MatchingMount(namespace.RootContext(nil), tc.routerPath)
- if match != tc.expectedMatch {
- t.Fatalf("missing mount, match: %q", match)
- }
-
- raw, _ := c.router.root.Get(match)
- if raw.(*routeEntry).mountEntry.Version != "" {
- t.Errorf("Expected mount to be empty version but got %s", raw.(*routeEntry).mountEntry.Version)
- }
- })
- }
-}
-
-func TestCore_EnableExternalCredentialPlugin_NoVersionOnRegister(t *testing.T) {
- for name, tc := range map[string]struct {
- pluginType consts.PluginType
- routerPath string
- expectedMatch string
- }{
- "enable external credential plugin with version, but no version was provided on registration": {
- pluginType: consts.PluginTypeCredential,
- routerPath: "auth/foo/bar",
- expectedMatch: "auth/foo/",
- },
- "enable external secrets plugin with version, but no version was provided on registration": {
- pluginType: consts.PluginTypeSecrets,
- routerPath: "foo/bar",
- expectedMatch: "foo/",
- },
- } {
- t.Run(name, func(t *testing.T) {
- c, plugins := testCoreWithPlugins(t, tc.pluginType, "")
- registerPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType.String(), "", plugins[0].Sha256, plugins[0].FileName)
-
- req := logical.TestRequest(t, logical.UpdateOperation, mountTable(tc.pluginType))
- req.Data = map[string]interface{}{
- "type": plugins[0].Name,
- "config": map[string]interface{}{
- "plugin_version": "v1.0.0",
- },
- }
- resp, _ := c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
- if resp == nil || !resp.IsError() || !strings.Contains(resp.Error().Error(), ErrPluginNotFound.Error()) {
- t.Fatalf("Expected to get plugin not found but got: %v", resp.Error())
- }
- })
- }
-}
-
-func TestCore_EnableExternalCredentialPlugin_InvalidName(t *testing.T) {
- for name, tc := range map[string]struct {
- pluginType consts.PluginType
- }{
- "enable external credential plugin with the wrong name": {
- pluginType: consts.PluginTypeCredential,
- },
- "enable external secrets plugin with the wrong name": {
- pluginType: consts.PluginTypeSecrets,
- },
- } {
- t.Run(name, func(t *testing.T) {
- c, plugins := testCoreWithPlugins(t, tc.pluginType, "")
- d := &framework.FieldData{
- Raw: map[string]interface{}{
- "name": plugins[0].Name,
- "sha256": plugins[0].Sha256,
- "version": "v1.0.0",
- "command": plugins[0].Name + "xyz",
- },
- Schema: c.systemBackend.pluginsCatalogCRUDPath().Fields,
- }
- _, err := c.systemBackend.handlePluginCatalogUpdate(context.Background(), nil, d)
- if err == nil || !strings.Contains(err.Error(), "no such file or directory") {
- t.Fatalf("should have gotten a no such file or directory error inserting the plugin: %v", err)
- }
- })
- }
-}
-
-func TestExternalPlugin_getBackendTypeVersion(t *testing.T) {
- for name, tc := range map[string]struct {
- pluginType consts.PluginType
- setRunningVersion string
- }{
- "external credential plugin": {
- pluginType: consts.PluginTypeCredential,
- setRunningVersion: "v1.2.3",
- },
- "external secrets plugin": {
- pluginType: consts.PluginTypeSecrets,
- setRunningVersion: "v1.2.3",
- },
- "external database plugin": {
- pluginType: consts.PluginTypeDatabase,
- setRunningVersion: "v1.2.3",
- },
- } {
- t.Run(name, func(t *testing.T) {
- c, plugins := testCoreWithPlugins(t, tc.pluginType, tc.setRunningVersion)
- registerPlugin(t, c.systemBackend, plugins[0].Name, tc.pluginType.String(), tc.setRunningVersion, plugins[0].Sha256, plugins[0].FileName)
-
- shaBytes, _ := hex.DecodeString(plugins[0].Sha256)
- commandFull := filepath.Join(c.pluginCatalog.directory, plugins[0].FileName)
- entry := &pluginutil.PluginRunner{
- Name: plugins[0].Name,
- Command: commandFull,
- Args: nil,
- Sha256: shaBytes,
- Builtin: false,
- }
-
- var version logical.PluginVersion
- var err error
- if tc.pluginType == consts.PluginTypeDatabase {
- version, err = c.pluginCatalog.getDatabaseRunningVersion(context.Background(), entry)
- } else {
- version, err = c.pluginCatalog.getBackendRunningVersion(context.Background(), entry)
- }
- if err != nil {
- t.Fatal(err)
- }
- if version.Version != tc.setRunningVersion {
- t.Errorf("Expected to get version %v but got %v", tc.setRunningVersion, version.Version)
- }
- })
- }
-}
-
-func TestExternalPlugin_CheckFilePermissions(t *testing.T) {
- // Turn on the check.
- if err := os.Setenv(consts.VaultEnableFilePermissionsCheckEnv, "true"); err != nil {
- t.Fatal(err)
- }
- defer func() {
- if err := os.Unsetenv(consts.VaultEnableFilePermissionsCheckEnv); err != nil {
- t.Fatal(err)
- }
- }()
-
- for name, tc := range map[string]struct {
- pluginNameFmt string
- pluginType consts.PluginType
- pluginVersion string
- }{
- "plugin name and file name match": {
- pluginNameFmt: "%s",
- pluginType: consts.PluginTypeCredential,
- },
- "plugin name and file name mismatch": {
- pluginNameFmt: "%s-foo",
- pluginType: consts.PluginTypeSecrets,
- },
- "plugin name has slash": {
- pluginNameFmt: "%s/foo",
- pluginType: consts.PluginTypeCredential,
- },
- "plugin with version": {
- pluginNameFmt: "%s/foo",
- pluginType: consts.PluginTypeCredential,
- pluginVersion: "v1.2.3",
- },
- } {
- t.Run(name, func(t *testing.T) {
- c, plugins := testCoreWithPlugins(t, tc.pluginType, tc.pluginVersion)
- registeredPluginName := fmt.Sprintf(tc.pluginNameFmt, plugins[0].Name)
-
- // Permissions will be checked once during registration.
- req := logical.TestRequest(t, logical.UpdateOperation, fmt.Sprintf("plugins/catalog/%s/%s", tc.pluginType.String(), registeredPluginName))
- req.Data = map[string]interface{}{
- "command": plugins[0].FileName,
- "sha256": plugins[0].Sha256,
- "version": tc.pluginVersion,
- }
- resp, err := c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp.Error() != nil {
- t.Fatal(resp.Error())
- }
-
- // Now attempt to mount the plugin, which should trigger checking the permissions again.
- req = logical.TestRequest(t, logical.UpdateOperation, mountTable(tc.pluginType))
- req.Data = map[string]interface{}{
- "type": registeredPluginName,
- }
- if tc.pluginVersion != "" {
- req.Data["config"] = map[string]interface{}{
- "plugin_version": tc.pluginVersion,
- }
- }
- resp, err = c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp.Error() != nil {
- t.Fatal(resp.Error())
- }
- })
- }
-}
-
-func TestExternalPlugin_DifferentVersionsAndArgs_AreNotMultiplexed(t *testing.T) {
- env := []string{fmt.Sprintf("%s=yes", vaultTestingMockPluginEnv)}
- core, _, _ := TestCoreUnsealed(t)
-
- for i, tc := range []struct {
- version string
- testName string
- }{
- {"v1.2.3", "TestBackend_PluginMain_Multiplexed_Logical_v123"},
- {"v1.2.4", "TestBackend_PluginMain_Multiplexed_Logical_v124"},
- } {
- // Register and mount plugins.
- TestAddTestPlugin(t, core, "mux-secret", consts.PluginTypeSecrets, tc.version, tc.testName, env, "")
- mountPlugin(t, core.systemBackend, "mux-secret", consts.PluginTypeSecrets, tc.version, fmt.Sprintf("foo%d", i))
- }
-
- if len(core.pluginCatalog.externalPlugins) != 2 {
- t.Fatalf("expected 2 external plugins, but got %d", len(core.pluginCatalog.externalPlugins))
- }
-}
-
-func TestExternalPlugin_DifferentTypes_AreNotMultiplexed(t *testing.T) {
- const version = "v1.2.3"
- env := []string{fmt.Sprintf("%s=yes", vaultTestingMockPluginEnv)}
- core, _, _ := TestCoreUnsealed(t)
-
- // Register and mount plugins.
- TestAddTestPlugin(t, core, "mux-aws", consts.PluginTypeSecrets, version, "TestBackend_PluginMain_Multiplexed_Logical_v123", env, "")
- TestAddTestPlugin(t, core, "mux-aws", consts.PluginTypeCredential, version, "TestBackend_PluginMain_Multiplexed_Credential_v123", env, "")
-
- mountPlugin(t, core.systemBackend, "mux-aws", consts.PluginTypeSecrets, version, "")
- mountPlugin(t, core.systemBackend, "mux-aws", consts.PluginTypeCredential, version, "")
-
- if len(core.pluginCatalog.externalPlugins) != 2 {
- t.Fatalf("expected 2 external plugins, but got %d", len(core.pluginCatalog.externalPlugins))
- }
-}
-
-func TestExternalPlugin_DifferentEnv_AreNotMultiplexed(t *testing.T) {
- const version = "v1.2.3"
- baseEnv := []string{
- fmt.Sprintf("%s=yes", vaultTestingMockPluginEnv),
- }
- alteredEnv := []string{
- fmt.Sprintf("%s=yes", vaultTestingMockPluginEnv),
- "FOO=BAR",
- }
-
- core, _, _ := TestCoreUnsealed(t)
-
- // Register and mount plugins.
- for i, env := range [][]string{baseEnv, alteredEnv} {
- TestAddTestPlugin(t, core, "mux-secret", consts.PluginTypeSecrets, version, "TestBackend_PluginMain_Multiplexed_Logical_v123", env, "")
- mountPlugin(t, core.systemBackend, "mux-secret", consts.PluginTypeSecrets, version, fmt.Sprintf("foo%d", i))
- }
-
- if len(core.pluginCatalog.externalPlugins) != 2 {
- t.Fatalf("expected 2 external plugins, but got %d", len(core.pluginCatalog.externalPlugins))
- }
-}
-
-// Used to run a mock multiplexed secrets plugin
-func TestBackend_PluginMain_Multiplexed_Logical_v123(t *testing.T) {
- if os.Getenv(vaultTestingMockPluginEnv) == "" {
- return
- }
-
- os.Setenv(mock.MockPluginVersionEnv, "v1.2.3")
-
- err := plugin.ServeMultiplex(&plugin.ServeOpts{
- BackendFactoryFunc: mock.FactoryType(logical.TypeLogical),
- })
- if err != nil {
- t.Fatal(err)
- }
-}
-
-// Used to run a mock multiplexed secrets plugin
-func TestBackend_PluginMain_Multiplexed_Logical_v124(t *testing.T) {
- if os.Getenv(vaultTestingMockPluginEnv) == "" {
- return
- }
-
- os.Setenv(mock.MockPluginVersionEnv, "v1.2.4")
-
- err := plugin.ServeMultiplex(&plugin.ServeOpts{
- BackendFactoryFunc: mock.FactoryType(logical.TypeLogical),
- })
- if err != nil {
- t.Fatal(err)
- }
-}
-
-// Used to run a mock multiplexed auth plugin
-func TestBackend_PluginMain_Multiplexed_Credential_v123(t *testing.T) {
- if os.Getenv(vaultTestingMockPluginEnv) == "" {
- return
- }
-
- os.Setenv(mock.MockPluginVersionEnv, "v1.2.3")
-
- err := plugin.ServeMultiplex(&plugin.ServeOpts{
- BackendFactoryFunc: mock.FactoryType(logical.TypeCredential),
- })
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func registerPlugin(t *testing.T, sys *SystemBackend, pluginName, pluginType, version, sha, command string) {
- t.Helper()
- req := logical.TestRequest(t, logical.UpdateOperation, fmt.Sprintf("plugins/catalog/%s/%s", pluginType, pluginName))
- req.Data = map[string]interface{}{
- "command": command,
- "sha256": sha,
- "version": version,
- }
- resp, err := sys.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-}
-
-func mountPluginWithResponse(t *testing.T, sys *SystemBackend, pluginName string, pluginType consts.PluginType, version, path string) (*logical.Response, error) {
- t.Helper()
- var mountPath string
- if path == "" {
- mountPath = mountTable(pluginType)
- } else {
- mountPath = mountTableWithPath(consts.PluginTypeSecrets, path)
- }
- req := logical.TestRequest(t, logical.UpdateOperation, mountPath)
- req.Data = map[string]interface{}{
- "type": pluginName,
- }
- if version != "" {
- req.Data["config"] = map[string]interface{}{
- "plugin_version": version,
- }
- }
- return sys.HandleRequest(namespace.RootContext(nil), req)
-}
-
-func mountPlugin(t *testing.T, sys *SystemBackend, pluginName string, pluginType consts.PluginType, version, path string) {
- t.Helper()
- resp, err := mountPluginWithResponse(t, sys, pluginName, pluginType, version, path)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-}
-
-func unmountPlugin(t *testing.T, sys *SystemBackend, pluginName string, pluginType consts.PluginType, version, path string) {
- t.Helper()
- var mountPath string
- if path == "" {
- mountPath = mountTable(pluginType)
- } else {
- mountPath = mountTableWithPath(consts.PluginTypeSecrets, path)
- }
- req := logical.TestRequest(t, logical.DeleteOperation, mountPath)
- req.Data = map[string]interface{}{
- "type": pluginName,
- }
- if version != "" {
- req.Data["config"] = map[string]interface{}{
- "plugin_version": version,
- }
- }
- resp, err := sys.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-}
-
-func deregisterPlugin(t *testing.T, sys *SystemBackend, pluginName, pluginType, version, sha, command string) {
- t.Helper()
- req := logical.TestRequest(t, logical.DeleteOperation, fmt.Sprintf("plugins/catalog/%s/%s", pluginType, pluginName))
- req.Data = map[string]interface{}{
- "command": command,
- "sha256": sha,
- "version": version,
- }
- resp, err := sys.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-}
-
-func mountTable(pluginType consts.PluginType) string {
- return mountTableWithPath(pluginType, "foo")
-}
-
-func mountTableWithPath(pluginType consts.PluginType, path string) string {
- switch pluginType {
- case consts.PluginTypeCredential:
- return "auth/" + path
- case consts.PluginTypeSecrets:
- return "mounts/" + path
- default:
- panic("test does not support mounting plugin type yet: " + pluginType.String())
- }
-}
diff --git a/vault/external_tests/api/api_integration_test.go b/vault/external_tests/api/api_integration_test.go
deleted file mode 100644
index e756f0b95..000000000
--- a/vault/external_tests/api/api_integration_test.go
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package api
-
-import (
- "encoding/base64"
- "testing"
-
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/audit"
- auditFile "github.com/hashicorp/vault/builtin/audit/file"
- credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
- "github.com/hashicorp/vault/builtin/logical/database"
- "github.com/hashicorp/vault/builtin/logical/pki"
- "github.com/hashicorp/vault/builtin/logical/transit"
- "github.com/hashicorp/vault/helper/benchhelpers"
- "github.com/hashicorp/vault/helper/builtinplugins"
- "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-// testVaultServer creates a test vault cluster and returns a configured API
-// client and closer function.
-func testVaultServer(t testing.TB) (*api.Client, func()) {
- t.Helper()
-
- client, _, closer := testVaultServerUnseal(t)
- return client, closer
-}
-
-// testVaultServerUnseal creates a test vault cluster and returns a configured
-// API client, list of unseal keys (as strings), and a closer function.
-func testVaultServerUnseal(t testing.TB) (*api.Client, []string, func()) {
- t.Helper()
-
- return testVaultServerCoreConfig(t, &vault.CoreConfig{
- DisableMlock: true,
- DisableCache: true,
- Logger: log.NewNullLogger(),
- CredentialBackends: map[string]logical.Factory{
- "userpass": credUserpass.Factory,
- },
- AuditBackends: map[string]audit.Factory{
- "file": auditFile.Factory,
- },
- LogicalBackends: map[string]logical.Factory{
- "database": database.Factory,
- "generic-leased": vault.LeasedPassthroughBackendFactory,
- "pki": pki.Factory,
- "transit": transit.Factory,
- },
- BuiltinRegistry: builtinplugins.Registry,
- })
-}
-
-// testVaultServerCoreConfig creates a new vault cluster with the given core
-// configuration. This is a lower-level test helper.
-func testVaultServerCoreConfig(t testing.TB, coreConfig *vault.CoreConfig) (*api.Client, []string, func()) {
- t.Helper()
-
- cluster := vault.NewTestCluster(benchhelpers.TBtoT(t), coreConfig, &vault.TestClusterOptions{
- HandlerFunc: http.Handler,
- NumCores: 1,
- })
- cluster.Start()
-
- // Make it easy to get access to the active
- core := cluster.Cores[0].Core
- vault.TestWaitActive(benchhelpers.TBtoT(t), core)
-
- // Get the client already setup for us!
- client := cluster.Cores[0].Client
- client.SetToken(cluster.RootToken)
-
- // Convert the unseal keys to base64 encoded, since these are how the user
- // will get them.
- unsealKeys := make([]string, len(cluster.BarrierKeys))
- for i := range unsealKeys {
- unsealKeys[i] = base64.StdEncoding.EncodeToString(cluster.BarrierKeys[i])
- }
-
- return client, unsealKeys, func() { defer cluster.Cleanup() }
-}
diff --git a/vault/external_tests/api/feature_flag_ext_test.go b/vault/external_tests/api/feature_flag_ext_test.go
deleted file mode 100644
index 4f8cc8138..000000000
--- a/vault/external_tests/api/feature_flag_ext_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package api
-
-import (
- "encoding/json"
- "io/ioutil"
- "net/http"
- "os"
- "testing"
-
- "github.com/hashicorp/go-cleanhttp"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
- "github.com/hashicorp/vault/vault"
- "golang.org/x/net/http2"
-)
-
-func TestFeatureFlags(t *testing.T) {
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- RequestResponseCallback: schema.ResponseValidatingCallback(t),
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- // Wait for core to start
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // Create a raw http connection copying the configuration
- // created by NewTestCluster
- transport := cleanhttp.DefaultPooledTransport()
- transport.TLSClientConfig = cluster.Cores[0].TLSConfig()
- if err := http2.ConfigureTransport(transport); err != nil {
- t.Fatal(err)
- }
- httpClient := &http.Client{
- Transport: transport,
- }
-
- callApi := func() map[string]interface{} {
- // Use the normal API client to construct the URL
- req := client.NewRequest("GET", "/v1/sys/internal/ui/feature-flags")
- httpReq, err := req.ToHTTP()
- if err != nil {
- t.Fatal(err)
- }
- resp, err := httpClient.Do(httpReq)
- if err != nil {
- t.Fatal(err)
- }
- defer resp.Body.Close()
-
- httpRespBody, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Fatal(err)
- }
-
- httpResp := make(map[string]interface{})
- err = json.Unmarshal(httpRespBody, &httpResp)
- if err != nil {
- t.Fatal(err)
- }
- return httpResp
- }
-
- // First try with no environment variable set
- httpResp := callApi()
- featureFlags, ok := httpResp["feature_flags"]
- if !ok {
- t.Fatal("Missing 'feature_flags' in response")
- }
- if featureFlags != nil {
- t.Fatal("Nonempty 'feature_flags'")
- }
-
- // Now try with the environment variable temporarily set
- envVar := "VAULT_CLOUD_ADMIN_NAMESPACE"
- os.Setenv(envVar, "1")
- defer os.Unsetenv(envVar)
-
- httpResp = callApi()
- featureFlags, ok = httpResp["feature_flags"]
- if !ok {
- t.Fatal("Missing 'feature_flags' in response")
- }
- flagList := featureFlags.([]interface{})
- if len(flagList) != 1 {
- t.Fatalf("Bad length for 'feature_flags': %v", flagList)
- }
- flag := flagList[0].(string)
- if flag != envVar {
- t.Fatalf("Bad environment variable in `feature_flags`: %q", flag)
- }
-}
diff --git a/vault/external_tests/api/kv_helpers_test.go b/vault/external_tests/api/kv_helpers_test.go
deleted file mode 100644
index d8c91a4ef..000000000
--- a/vault/external_tests/api/kv_helpers_test.go
+++ /dev/null
@@ -1,627 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package api
-
-import (
- "context"
- "errors"
- "reflect"
- "testing"
- "time"
-
- logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
- "github.com/hashicorp/vault/api"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-const (
- v1MountPath = "secret"
- v2MountPath = "secret-v2"
- secretPath = "my-secret"
-)
-
-var (
- client *api.Client
- secretData = map[string]interface{}{
- "foo": "bar",
- }
-)
-
-// setupKVv2Test creates the secret that will be used in each KV v2 subtest. It
-// returns a function (that should be deferred whenever setupKVv2Test is called)
-// which will perform the cleanup of all existing versions of the secret, as
-// well as the secret that was written for comparison.
-func setupKVv2Test(t *testing.T) (func(t *testing.T), *api.KVSecret) {
- writtenSecret, err := client.KVv2(v2MountPath).Put(context.Background(), secretPath, secretData)
- if err != nil {
- t.Fatal(err)
- }
- if writtenSecret == nil || writtenSecret.VersionMetadata == nil {
- t.Fatal("secret created during kv v2 subtest setup did not have expected contents")
- }
-
- return func(t *testing.T) {
- err := client.KVv2(v2MountPath).DeleteMetadata(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
- }, writtenSecret
-}
-
-func TestKVHelpers(t *testing.T) {
- t.Parallel()
-
- // initialize test cluster
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "kv": logicalKv.Factory,
- "kv-v2": logicalKv.VersionedKVFactory,
- },
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- cores := cluster.Cores
- core := cores[0].Core
- client = cluster.Cores[0].Client
- vault.TestWaitActive(t, core)
-
- // mount the KVv2 backend
- // (the test cluster has already mounted the KVv1 backend at "secret")
- err := client.Sys().MountWithContext(context.Background(), "secret-v2", &api.MountInput{
- Type: "kv-v2",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- //// v1 ////
- t.Run("kv v1: put, get, and delete data", func(t *testing.T) {
- if err := client.KVv1(v1MountPath).Put(context.Background(), secretPath, secretData); err != nil {
- t.Fatal(err)
- }
-
- secret, err := client.KVv1(v1MountPath).Get(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
-
- if secret.Data["foo"] != "bar" {
- t.Fatalf("kv v1 secret did not contain expected value")
- }
-
- if err := client.KVv1(v1MountPath).Delete(context.Background(), secretPath); err != nil {
- t.Fatal(err)
- }
-
- _, err = client.KVv1(v1MountPath).Get(context.Background(), secretPath)
- if !errors.Is(err, api.ErrSecretNotFound) {
- t.Fatalf("KVv1.Get is expected to return an api.ErrSecretNotFound wrapped error after secret had been deleted; got %v", err)
- }
- })
-
- t.Run("kv v1: get secret that does not exist", func(t *testing.T) {
- _, err = client.KVv1(v1MountPath).Get(context.Background(), "does/not/exist")
- if err == nil {
- t.Fatalf("KVv1.Get is expected to return an error for a missing secret")
- }
- if !errors.Is(err, api.ErrSecretNotFound) {
- t.Fatalf("KVv1.Get is expected to return an api.ErrSecretNotFound wrapped error for a missing secret; got %v", err)
- }
- })
-
- //// v2 ////
- t.Run("kv v2: get data and full metadata", func(t *testing.T) {
- teardownTest, originalSecret := setupKVv2Test(t)
- defer teardownTest(t)
-
- secret, err := client.KVv2(v2MountPath).Get(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
- if secret.Data["foo"] != "bar" {
- t.Fatal("kv v2 secret did not contain expected value")
- }
- if secret.VersionMetadata.CreatedTime != originalSecret.VersionMetadata.CreatedTime {
- t.Fatal("the created_time on the secret did not match the response from when it was created")
- }
-
- // get its full metadata
- fullMetadata, err := client.KVv2(v2MountPath).GetMetadata(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(secret.CustomMetadata, fullMetadata.CustomMetadata) {
- t.Fatalf("custom metadata on the secret does not match the custom metadata in the full metadata")
- }
- })
-
- t.Run("kv v2: get secret that does not exist", func(t *testing.T) {
- teardownTest, _ := setupKVv2Test(t)
- defer teardownTest(t)
-
- _, err = client.KVv2(v2MountPath).Get(context.Background(), "does/not/exist")
- if !errors.Is(err, api.ErrSecretNotFound) {
- t.Fatalf("KVv2.Get is expected to return an api.ErrSecretNotFound wrapped error for a missing secret; got %v", err)
- }
-
- _, err = client.KVv2(v2MountPath).GetMetadata(context.Background(), "does/not/exist")
- if !errors.Is(err, api.ErrSecretNotFound) {
- t.Fatalf("KVv2.GetMetadata is expected to return an api.ErrSecretNotFound wrapped error for a missing secret; got %v", err)
- }
-
- _, err = client.KVv2(v2MountPath).GetVersion(context.Background(), secretPath, 99)
- if !errors.Is(err, api.ErrSecretNotFound) {
- t.Fatalf("KVv2.GetVersion is expected to return an api.ErrSecretNotFound wrapped error for a missing secret version; got %v", err)
- }
- })
-
- t.Run("kv v2: multiple versions", func(t *testing.T) {
- teardownTest, _ := setupKVv2Test(t)
- defer teardownTest(t)
-
- // create a second version
- _, err = client.KVv2(v2MountPath).Put(context.Background(), secretPath, map[string]interface{}{
- "foo": "baz",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- s2, err := client.KVv2(v2MountPath).Get(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
- if s2.Data["foo"] != "baz" {
- t.Fatalf("second version of secret did not have expected contents")
- }
- if s2.VersionMetadata.Version != 2 {
- t.Fatalf("wrong version of kv v2 secret was read, expected 2 but got %d", s2.VersionMetadata.Version)
- }
- })
-
- t.Run("kv v2: delete and undelete", func(t *testing.T) {
- teardownTest, _ := setupKVv2Test(t)
- defer teardownTest(t)
-
- // create a second version
- _, err = client.KVv2(v2MountPath).Put(context.Background(), secretPath, map[string]interface{}{
- "foo": "baz",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // get a specific past version
- s1, err := client.KVv2(v2MountPath).GetVersion(context.Background(), secretPath, 1)
- if err != nil {
- t.Fatal(err)
- }
- if s1.VersionMetadata.Version != 1 {
- t.Fatalf("wrong version of kv v2 secret was read, expected 1 but got %d", s1.VersionMetadata.Version)
- }
-
- // delete that version
- if err = client.KVv2(v2MountPath).DeleteVersions(context.Background(), secretPath, []int{1}); err != nil {
- t.Fatal(err)
- }
-
- s1AfterDelete, err := client.KVv2(v2MountPath).GetVersion(context.Background(), secretPath, 1)
- if err != nil {
- t.Fatal(err)
- }
-
- if s1AfterDelete.VersionMetadata.DeletionTime.IsZero() {
- t.Fatalf("the deletion_time in the first version of the secret was not updated")
- }
-
- if s1AfterDelete.Data != nil {
- t.Fatalf("data still exists on the first version of the secret despite this version being deleted")
- }
-
- // undelete it
- err = client.KVv2(v2MountPath).Undelete(context.Background(), secretPath, []int{1})
- if err != nil {
- t.Fatal(err)
- }
-
- s1AfterUndelete, err := client.KVv2(v2MountPath).GetVersion(context.Background(), secretPath, 1)
- if err != nil {
- t.Fatal(err)
- }
-
- if s1AfterUndelete.Data == nil {
- t.Fatalf("data is empty for the first version of the secret despite this version being undeleted")
- }
- })
-
- t.Run("kv v2: destroy", func(t *testing.T) {
- teardownTest, _ := setupKVv2Test(t)
- defer teardownTest(t)
-
- err = client.KVv2(v2MountPath).Destroy(context.Background(), secretPath, []int{1})
- if err != nil {
- t.Fatal(err)
- }
-
- destroyedSecret, err := client.KVv2(v2MountPath).Get(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
-
- if !destroyedSecret.VersionMetadata.Destroyed {
- t.Fatalf("expected secret to be destroyed but it wasn't")
- }
- })
-
- t.Run("kv v2: use named functional options and generic WithOption", func(t *testing.T) {
- teardownTest, _ := setupKVv2Test(t)
- defer teardownTest(t)
-
- // check that KVOption works
- // WithCheckAndSet
- _, err = client.KVv2(v2MountPath).Put(context.Background(), secretPath, map[string]interface{}{
- "meow": "woof",
- }, api.WithCheckAndSet(99))
- // should fail
- if err == nil {
- t.Fatalf("expected error from trying to update different version from check-and-set value using WithCheckAndSet")
- }
-
- // WithOption (generic)
- _, err = client.KVv2(v2MountPath).Put(context.Background(), secretPath, map[string]interface{}{
- "bow": "wow",
- }, api.WithOption("cas", 99))
- // should fail
- if err == nil {
- t.Fatalf("expected error from trying to update different version from check-and-set value using generic WithOption")
- }
- })
-
- t.Run("kv v2: patch", func(t *testing.T) {
- teardownTest, _ := setupKVv2Test(t)
- defer teardownTest(t)
-
- // WithMergeMethod Patch (implicit)
- patch, err := client.KVv2(v2MountPath).Patch(context.Background(), secretPath, map[string]interface{}{
- "dog": "cat",
- })
- if err != nil {
- t.Fatal(err)
- }
- if patch.VersionMetadata.Version != 2 {
- t.Fatalf("incorrect version %d, expected 2", patch.VersionMetadata.Version)
- }
-
- // WithMergeMethod Patch (explicit)
- patchExp, err := client.KVv2(v2MountPath).Patch(context.Background(), secretPath, map[string]interface{}{
- "rat": "mouse",
- }, api.WithMergeMethod(api.KVMergeMethodPatch))
- if err != nil {
- t.Fatal(err)
- }
- if patchExp.VersionMetadata.Version != 3 {
- t.Fatalf("incorrect version %d, expected 3", patchExp.VersionMetadata.Version)
- }
-
- // WithMergeMethod RW
- patchRW, err := client.KVv2(v2MountPath).Patch(context.Background(), secretPath, map[string]interface{}{
- "bird": "tweet",
- }, api.WithMergeMethod(api.KVMergeMethodReadWrite))
- if err != nil {
- t.Fatal(err)
- }
- if patchRW.VersionMetadata.Version != 4 {
- t.Fatalf("incorrect version %d, expected 4", patchRW.VersionMetadata.Version)
- }
-
- secretAfterPatches, err := client.KVv2(v2MountPath).Get(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
- _, ok := secretAfterPatches.Data["dog"]
- if !ok {
- t.Fatalf("secret did not contain data patched with implicit Patch method")
- }
- _, ok = secretAfterPatches.Data["rat"]
- if !ok {
- t.Fatalf("secret did not contain data patched with explicit Patch method")
- }
- _, ok = secretAfterPatches.Data["bird"]
- if !ok {
- t.Fatalf("secret did not contain data patched with RW method")
- }
- value, ok := secretAfterPatches.Data["foo"]
- if !ok || value != "bar" {
- t.Fatalf("secret did not keep original data after patch")
- }
-
- // patch an existing field
- _, err = client.KVv2(v2MountPath).Patch(context.Background(), secretPath, map[string]interface{}{
- "dog": "pug",
- })
- if err != nil {
- t.Fatal(err)
- }
- patchedFieldKV, err := client.KVv2(v2MountPath).Get(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
- v, ok := patchedFieldKV.Data["dog"]
- if !ok || v != "pug" {
- t.Fatalf("secret's data was not replaced by patch")
- }
-
- // delete a key in a secret via patch
- _, err = client.KVv2(v2MountPath).Patch(context.Background(), secretPath, map[string]interface{}{
- "dog": nil,
- })
- if err != nil {
- t.Fatal(err)
- }
- deletedFieldKV, err := client.KVv2(v2MountPath).Get(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
- _, ok = deletedFieldKV.Data["dog"]
- if ok {
- t.Fatalf("secret key \"dog\" should have been removed by nil patch")
- }
-
- // set a key to an empty string via patch
- _, err = client.KVv2(v2MountPath).Patch(context.Background(), secretPath, map[string]interface{}{
- "dog": "",
- })
- if err != nil {
- t.Fatal(err)
- }
- emptyValueKV, err := client.KVv2(v2MountPath).Get(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
- v, ok = emptyValueKV.Data["dog"]
- if !ok || v != "" {
- t.Fatalf("secret key \"dog\" should have an empty string value")
- }
- })
-
- t.Run("kv v2: patch a secret that does not exist", func(t *testing.T) {
- for _, method := range [][]api.KVOption{
- {},
- {api.WithMergeMethod(api.KVMergeMethodPatch)},
- {api.WithMergeMethod(api.KVMergeMethodReadWrite)},
- } {
- _, err = client.KVv2(v2MountPath).Patch(
- context.Background(),
- "does/not/exist",
- map[string]interface{}{"no": "nope"},
- method...,
- )
- if !errors.Is(err, api.ErrSecretNotFound) {
- t.Fatalf("expected an api.ErrSecretNotFound wrapped error from trying to patch something that doesn't exist for %v method; got: %v", method, err)
- }
- }
- })
-
- t.Run("kv v2: roll back to an old version", func(t *testing.T) {
- teardownTest, _ := setupKVv2Test(t)
- defer teardownTest(t)
-
- // create a second version
- _, err = client.KVv2(v2MountPath).Put(context.Background(), secretPath, map[string]interface{}{
- "color": "yellow",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // get versions as list
- versions, err := client.KVv2(v2MountPath).GetVersionsAsList(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
-
- expectedLength := 2
- if len(versions) != expectedLength {
- t.Fatalf("expected there to be %d versions of the secret but got %d", expectedLength, len(versions))
- }
-
- if versions[0].Version != 1 || versions[len(versions)-1].Version != expectedLength {
- t.Fatalf("versions list is not ordered as expected")
- }
-
- // roll back to version 1
- rb, err := client.KVv2(v2MountPath).Rollback(context.Background(), secretPath, 1)
- if err != nil {
- t.Fatal(err)
- }
- if rb.VersionMetadata.Version != 3 {
- t.Fatalf("expected returned secret's version %d to be the latest version, which should be 3", rb.VersionMetadata.Version)
- }
-
- // destroy version 1
- err = client.KVv2(v2MountPath).Destroy(context.Background(), secretPath, []int{1})
- if err != nil {
- t.Fatal(err)
- }
-
- // roll back but fail
- _, err = client.KVv2(v2MountPath).Rollback(context.Background(), secretPath, 1)
- if err == nil {
- t.Fatalf("expected error from trying to rollback to destroyed version")
- }
- })
-
- t.Run("kv v2: delete all versions of a secret", func(t *testing.T) {
- teardownTest, _ := setupKVv2Test(t)
- defer teardownTest(t)
-
- // create a second version
- _, err = client.KVv2(v2MountPath).Put(context.Background(), secretPath, map[string]interface{}{
- "color": "yellow",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // delete it all
- err = client.KVv2(v2MountPath).DeleteMetadata(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
-
- versions, err := client.KVv2(v2MountPath).GetVersionsAsList(context.Background(), secretPath)
- if err == nil {
- t.Fatalf("expected to be unable to get list of versions since all metadata was destroyed")
- }
- if len(versions) > 0 {
- t.Fatalf("expected no versions of secret after deleting all metadata")
- }
- })
-
- t.Run("kv v2: create a secret with metadata but no data", func(t *testing.T) {
- // put and patch metadata
- ////
- noDataSecretPath := "empty"
-
- // create a secret with metadata but no data
- err = client.KVv2(v2MountPath).PutMetadata(context.Background(), noDataSecretPath, api.KVMetadataPutInput{
- DeleteVersionAfter: 5 * time.Hour,
- MaxVersions: 5,
- CustomMetadata: map[string]interface{}{"ape": "gorilla"},
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // get its metadata to make sure it was created successfully
- md, err := client.KVv2(v2MountPath).GetMetadata(context.Background(), noDataSecretPath)
- if err != nil {
- t.Fatal(err)
- }
- if md.CreatedTime.IsZero() {
- t.Fatalf("secret metadata was not populated as expected: %v", err)
- }
- if len(md.Versions) > 0 {
- t.Fatalf("no secret versions should have been created since only metadata was populated")
- }
- })
-
- t.Run("kv v2: put and patch metadata", func(t *testing.T) {
- teardownTest, _ := setupKVv2Test(t)
- defer teardownTest(t)
-
- md, err := client.KVv2(v2MountPath).GetMetadata(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
-
- // replace all modifiable metadata fields
- err = client.KVv2(v2MountPath).PutMetadata(context.Background(), secretPath, api.KVMetadataPutInput{
- CASRequired: true,
- DeleteVersionAfter: 6 * time.Hour,
- MaxVersions: 6,
- CustomMetadata: map[string]interface{}{"foo": "fwah", "cat": "tabby"},
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // check that metadata was replaced
- md2, err := client.KVv2(v2MountPath).GetMetadata(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
- if md.CASRequired == md2.CASRequired || md.MaxVersions == md2.MaxVersions || md.DeleteVersionAfter == md2.DeleteVersionAfter || reflect.DeepEqual(md.CustomMetadata, md2.CustomMetadata) {
- t.Fatalf("metadata fields should have been updated by PutMetadata")
- }
-
- // now let's try a patch
- maxVersions := 7
- err = client.KVv2(v2MountPath).PatchMetadata(context.Background(), secretPath, api.KVMetadataPatchInput{
- MaxVersions: &maxVersions,
- CustomMetadata: map[string]interface{}{"foo": nil, "rat": "brown"},
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // check that the metadata was only partially replaced
- md3, err := client.KVv2(v2MountPath).GetMetadata(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
- if md2.CASRequired != md3.CASRequired || md2.DeleteVersionAfter != md3.DeleteVersionAfter {
- t.Fatalf("expected fields to remain unchanged but they were updated")
- }
- if md3.MaxVersions == 0 {
- t.Fatalf("field was reset to its zero value when it should not have been")
- }
- if md2.MaxVersions == md3.MaxVersions {
- t.Fatalf("expected field to be updated but it remained unchanged")
- }
-
- // let's check the custom metadata was updated correctly
- if r, ok := md3.CustomMetadata["rat"]; ok {
- if r != "brown" {
- t.Fatalf("expected value to be \"brown\"")
- }
- } else {
- t.Fatalf("expected there to be a new \"rat\" key")
- }
-
- if _, ok := md3.CustomMetadata["foo"]; ok {
- t.Fatalf("expected \"foo\" key to be removed")
- }
-
- if _, ok := md3.CustomMetadata["cat"]; !ok {
- t.Fatalf("did not expect \"cat\" key to be removed")
- }
- })
-
- t.Run("kv v2: patch with explicit zero values", func(t *testing.T) {
- teardownTest, _ := setupKVv2Test(t)
- defer teardownTest(t)
-
- // now let's do another patch to test the "explicit zero value" use case
- var (
- explicitFalse bool
- explicitZero int
- explicitTimeZero time.Duration
- )
- err = client.KVv2(v2MountPath).PatchMetadata(context.Background(), secretPath, api.KVMetadataPatchInput{
- CASRequired: &explicitFalse,
- MaxVersions: &explicitZero,
- DeleteVersionAfter: &explicitTimeZero,
- CustomMetadata: map[string]interface{}{},
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // check that those fields were reset to their zero value
- md4, err := client.KVv2(v2MountPath).GetMetadata(context.Background(), secretPath)
- if err != nil {
- t.Fatal(err)
- }
- if len(md4.CustomMetadata) > 0 {
- t.Fatalf("expected empty map to cause deletion of all custom metadata")
- }
-
- if md4.MaxVersions != 0 || md4.CASRequired != false {
- t.Fatalf("expected fields to be reset to their zero values but they were %d and %t instead", md4.MaxVersions, md4.CASRequired)
- }
-
- if md4.DeleteVersionAfter.String() != "0s" {
- t.Fatalf("expected delete-version-after to be reset to its zero value but instead it was %s", md4.DeleteVersionAfter.String())
- }
- })
-}
diff --git a/vault/external_tests/api/renewer_integration_test.go b/vault/external_tests/api/renewer_integration_test.go
deleted file mode 100644
index 24abed1ca..000000000
--- a/vault/external_tests/api/renewer_integration_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package api
-
-import (
- "testing"
- "time"
-
- "github.com/hashicorp/vault/api"
-)
-
-func TestRenewer_Renew(t *testing.T) {
- t.Parallel()
-
- client, vaultDone := testVaultServer(t)
- defer vaultDone()
-
- t.Run("group", func(t *testing.T) {
- t.Run("kv", func(t *testing.T) {
- t.Parallel()
-
- if _, err := client.Logical().Write("secret/value", map[string]interface{}{
- "foo": "bar",
- }); err != nil {
- t.Fatal(err)
- }
-
- secret, err := client.Logical().Read("secret/value")
- if err != nil {
- t.Fatal(err)
- }
-
- v, err := client.NewLifetimeWatcher(&api.RenewerInput{
- Secret: secret,
- })
- if err != nil {
- t.Fatal(err)
- }
- go v.Renew()
- defer v.Stop()
-
- select {
- case err := <-v.DoneCh():
- if err != api.ErrLifetimeWatcherNotRenewable {
- t.Fatal(err)
- }
- case renew := <-v.RenewCh():
- t.Errorf("received renew, but should have been nil: %#v", renew)
- case <-time.After(500 * time.Millisecond):
- t.Error("should have been non-renewable")
- }
- })
-
- t.Run("transit", func(t *testing.T) {
- t.Parallel()
-
- if err := client.Sys().Mount("transit", &api.MountInput{
- Type: "transit",
- }); err != nil {
- t.Fatal(err)
- }
-
- secret, err := client.Logical().Write("transit/encrypt/my-app", map[string]interface{}{
- "plaintext": "Zm9vCg==",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- v, err := client.NewLifetimeWatcher(&api.RenewerInput{
- Secret: secret,
- })
- if err != nil {
- t.Fatal(err)
- }
- go v.Renew()
- defer v.Stop()
-
- select {
- case err := <-v.DoneCh():
- if err != api.ErrLifetimeWatcherNotRenewable {
- t.Fatal(err)
- }
- case renew := <-v.RenewCh():
- t.Errorf("received renew, but should have been nil: %#v", renew)
- case <-time.After(500 * time.Millisecond):
- t.Error("should have been non-renewable")
- }
- })
-
- t.Run("auth", func(t *testing.T) {
- t.Parallel()
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- TTL: "5s",
- ExplicitMaxTTL: "10s",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- v, err := client.NewLifetimeWatcher(&api.RenewerInput{
- Secret: secret,
- })
- if err != nil {
- t.Fatal(err)
- }
- go v.Renew()
- defer v.Stop()
-
- renewed, done := false, false
- timeout := time.After(10 * time.Second)
- for {
- if done {
- break
- }
- select {
- case err := <-v.DoneCh():
- if renewed {
- // If we renewed but there's an error, we fail
- if err != nil {
- t.Fatalf("renewal failed with an error: %v", err)
- }
- // We can break out early here
- done = true
- } else {
- t.Errorf("should have renewed once before returning: %s", err)
- }
- done = true
- case renew := <-v.RenewCh():
- if renew == nil {
- t.Fatal("renew is nil")
- }
- if renew.Secret.Auth == nil {
- t.Fatal("renew auth is nil")
- }
- if !renew.Secret.Auth.Renewable {
- t.Errorf("expected lease to be renewable: %#v", renew)
- }
- if renew.Secret.Auth.LeaseDuration > 5 {
- t.Errorf("expected lease to <= 5s: %#v", renew)
- }
- if renew.Secret.Auth.ClientToken == "" {
- t.Error("expected a client token")
- }
- if renew.Secret.Auth.Accessor == "" {
- t.Error("expected an accessor")
- }
- renewed = true
- case <-timeout:
- if !renewed {
- t.Errorf("no renewal")
- }
- done = true
- }
- }
- })
- })
-}
diff --git a/vault/external_tests/api/secret_test.go b/vault/external_tests/api/secret_test.go
deleted file mode 100644
index 20eb2af97..000000000
--- a/vault/external_tests/api/secret_test.go
+++ /dev/null
@@ -1,2026 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package api
-
-import (
- "encoding/json"
- "reflect"
- "strings"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/api"
-)
-
-func TestParseSecret(t *testing.T) {
- t.Parallel()
-
- raw := strings.TrimSpace(`
-{
- "lease_id": "foo",
- "renewable": true,
- "lease_duration": 10,
- "data": {
- "key": "value"
- },
- "warnings": [
- "a warning!"
- ],
- "wrap_info": {
- "token": "token",
- "accessor": "accessor",
- "ttl": 60,
- "creation_time": "2016-06-07T15:52:10-04:00",
- "wrapped_accessor": "abcd1234"
- }
-}`)
-
- rawTime, _ := time.Parse(time.RFC3339, "2016-06-07T15:52:10-04:00")
-
- secret, err := api.ParseSecret(strings.NewReader(raw))
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- expected := &api.Secret{
- LeaseID: "foo",
- Renewable: true,
- LeaseDuration: 10,
- Data: map[string]interface{}{
- "key": "value",
- },
- Warnings: []string{
- "a warning!",
- },
- WrapInfo: &api.SecretWrapInfo{
- Token: "token",
- Accessor: "accessor",
- TTL: 60,
- CreationTime: rawTime,
- WrappedAccessor: "abcd1234",
- },
- }
- if !reflect.DeepEqual(secret, expected) {
- t.Fatalf("bad:\ngot\n%#v\nexpected\n%#v\n", secret, expected)
- }
-}
-
-func TestSecret_TokenID(t *testing.T) {
- t.Parallel()
-
- cases := []struct {
- name string
- secret *api.Secret
- exp string
- err bool
- }{
- {
- "nil",
- nil,
- "",
- false,
- },
- {
- "nil_auth",
- &api.Secret{
- Auth: nil,
- },
- "",
- false,
- },
- {
- "empty_auth_client_token",
- &api.Secret{
- Auth: &api.SecretAuth{
- ClientToken: "",
- },
- },
- "",
- false,
- },
- {
- "real_auth_client_token",
- &api.Secret{
- Auth: &api.SecretAuth{
- ClientToken: "my-token",
- },
- },
- "my-token",
- false,
- },
- {
- "nil_data",
- &api.Secret{
- Data: nil,
- },
- "",
- false,
- },
- {
- "empty_data",
- &api.Secret{
- Data: map[string]interface{}{},
- },
- "",
- false,
- },
- {
- "data_not_string",
- &api.Secret{
- Data: map[string]interface{}{
- "id": 123,
- },
- },
- "",
- true,
- },
- {
- "data_string",
- &api.Secret{
- Data: map[string]interface{}{
- "id": "my-token",
- },
- },
- "my-token",
- false,
- },
- }
-
- for _, tc := range cases {
- tc := tc
-
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
-
- act, err := tc.secret.TokenID()
- if err != nil && !tc.err {
- t.Fatal(err)
- }
- if act != tc.exp {
- t.Errorf("expected %q to be %q", act, tc.exp)
- }
- })
- }
-
- t.Run("auth", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil {
- t.Fatal(err)
- }
- if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{
- "password": "test",
- "policies": "default",
- }); err != nil {
- t.Fatal(err)
- }
-
- secret, err := client.Logical().Write("auth/userpass/login/test", map[string]interface{}{
- "password": "test",
- })
- if err != nil || secret == nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- tokenID, err := secret.TokenID()
- if err != nil {
- t.Fatal(err)
- }
- if tokenID != token {
- t.Errorf("expected %q to be %q", tokenID, token)
- }
- })
-
- t.Run("token create", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- tokenID, err := secret.TokenID()
- if err != nil {
- t.Fatal(err)
- }
- if tokenID != token {
- t.Errorf("expected %q to be %q", tokenID, token)
- }
- })
-
- t.Run("token lookup", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- secret, err = client.Auth().Token().Lookup(token)
- if err != nil {
- t.Fatal(err)
- }
-
- tokenID, err := secret.TokenID()
- if err != nil {
- t.Fatal(err)
- }
- if tokenID != token {
- t.Errorf("expected %q to be %q", tokenID, token)
- }
- })
-
- t.Run("token lookup-self", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- client.SetToken(token)
- secret, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
-
- tokenID, err := secret.TokenID()
- if err != nil {
- t.Fatal(err)
- }
- if tokenID != token {
- t.Errorf("expected %q to be %q", tokenID, token)
- }
- })
-
- t.Run("token renew", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- secret, err = client.Auth().Token().Renew(token, 0)
- if err != nil {
- t.Fatal(err)
- }
-
- tokenID, err := secret.TokenID()
- if err != nil {
- t.Fatal(err)
- }
- if tokenID != token {
- t.Errorf("expected %q to be %q", tokenID, token)
- }
- })
-
- t.Run("token renew-self", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- client.SetToken(token)
- secret, err = client.Auth().Token().RenewSelf(0)
- if err != nil {
- t.Fatal(err)
- }
-
- tokenID, err := secret.TokenID()
- if err != nil {
- t.Fatal(err)
- }
- if tokenID != token {
- t.Errorf("expected %q to be %q", tokenID, token)
- }
- })
-}
-
-func TestSecret_TokenAccessor(t *testing.T) {
- t.Parallel()
-
- cases := []struct {
- name string
- secret *api.Secret
- exp string
- err bool
- }{
- {
- "nil",
- nil,
- "",
- false,
- },
- {
- "nil_auth",
- &api.Secret{
- Auth: nil,
- },
- "",
- false,
- },
- {
- "empty_auth_accessor",
- &api.Secret{
- Auth: &api.SecretAuth{
- Accessor: "",
- },
- },
- "",
- false,
- },
- {
- "real_auth_accessor",
- &api.Secret{
- Auth: &api.SecretAuth{
- Accessor: "my-accessor",
- },
- },
- "my-accessor",
- false,
- },
- {
- "nil_data",
- &api.Secret{
- Data: nil,
- },
- "",
- false,
- },
- {
- "empty_data",
- &api.Secret{
- Data: map[string]interface{}{},
- },
- "",
- false,
- },
- {
- "data_not_string",
- &api.Secret{
- Data: map[string]interface{}{
- "accessor": 123,
- },
- },
- "",
- true,
- },
- {
- "data_string",
- &api.Secret{
- Data: map[string]interface{}{
- "accessor": "my-accessor",
- },
- },
- "my-accessor",
- false,
- },
- }
-
- for _, tc := range cases {
- tc := tc
-
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
-
- act, err := tc.secret.TokenAccessor()
- if err != nil && !tc.err {
- t.Fatal(err)
- }
- if act != tc.exp {
- t.Errorf("expected %q to be %q", act, tc.exp)
- }
- })
- }
-
- t.Run("auth", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil {
- t.Fatal(err)
- }
- if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{
- "password": "test",
- "policies": "default",
- }); err != nil {
- t.Fatal(err)
- }
-
- secret, err := client.Logical().Write("auth/userpass/login/test", map[string]interface{}{
- "password": "test",
- })
- if err != nil || secret == nil {
- t.Fatal(err)
- }
- _, accessor := secret.Auth.ClientToken, secret.Auth.Accessor
-
- newAccessor, err := secret.TokenAccessor()
- if err != nil {
- t.Fatal(err)
- }
- if newAccessor != accessor {
- t.Errorf("expected %q to be %q", newAccessor, accessor)
- }
- })
-
- t.Run("token create", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- _, accessor := secret.Auth.ClientToken, secret.Auth.Accessor
-
- newAccessor, err := secret.TokenAccessor()
- if err != nil {
- t.Fatal(err)
- }
- if newAccessor != accessor {
- t.Errorf("expected %q to be %q", newAccessor, accessor)
- }
- })
-
- t.Run("token lookup", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- token, accessor := secret.Auth.ClientToken, secret.Auth.Accessor
-
- secret, err = client.Auth().Token().Lookup(token)
- if err != nil {
- t.Fatal(err)
- }
-
- newAccessor, err := secret.TokenAccessor()
- if err != nil {
- t.Fatal(err)
- }
- if newAccessor != accessor {
- t.Errorf("expected %q to be %q", newAccessor, accessor)
- }
- })
-
- t.Run("token lookup-self", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- token, accessor := secret.Auth.ClientToken, secret.Auth.Accessor
-
- client.SetToken(token)
- secret, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
-
- newAccessor, err := secret.TokenAccessor()
- if err != nil {
- t.Fatal(err)
- }
- if newAccessor != accessor {
- t.Errorf("expected %q to be %q", newAccessor, accessor)
- }
- })
-
- t.Run("token renew", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- token, accessor := secret.Auth.ClientToken, secret.Auth.Accessor
-
- secret, err = client.Auth().Token().Renew(token, 0)
- if err != nil {
- t.Fatal(err)
- }
-
- newAccessor, err := secret.TokenAccessor()
- if err != nil {
- t.Fatal(err)
- }
- if newAccessor != accessor {
- t.Errorf("expected %q to be %q", newAccessor, accessor)
- }
- })
-
- t.Run("token renew-self", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- token, accessor := secret.Auth.ClientToken, secret.Auth.Accessor
-
- client.SetToken(token)
- secret, err = client.Auth().Token().RenewSelf(0)
- if err != nil {
- t.Fatal(err)
- }
-
- newAccessor, err := secret.TokenAccessor()
- if err != nil {
- t.Fatal(err)
- }
- if newAccessor != accessor {
- t.Errorf("expected %q to be %q", newAccessor, accessor)
- }
- })
-}
-
-func TestSecret_TokenRemainingUses(t *testing.T) {
- t.Parallel()
-
- cases := []struct {
- name string
- secret *api.Secret
- exp int
- }{
- {
- "nil",
- nil,
- -1,
- },
- {
- "nil_data",
- &api.Secret{
- Data: nil,
- },
- -1,
- },
- {
- "empty_data",
- &api.Secret{
- Data: map[string]interface{}{},
- },
- -1,
- },
- {
- "data_not_json_number",
- &api.Secret{
- Data: map[string]interface{}{
- "num_uses": 123,
- },
- },
- 123,
- },
- {
- "data_json_number",
- &api.Secret{
- Data: map[string]interface{}{
- "num_uses": json.Number("123"),
- },
- },
- 123,
- },
- }
-
- for _, tc := range cases {
- tc := tc
-
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
-
- act, err := tc.secret.TokenRemainingUses()
- if tc.exp != -1 && err != nil {
- t.Fatal(err)
- }
- if act != tc.exp {
- t.Errorf("expected %d to be %d", act, tc.exp)
- }
- })
- }
-
- t.Run("auth", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- uses := 5
-
- if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil {
- t.Fatal(err)
- }
- if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{
- "password": "test",
- "policies": "default",
- "num_uses": uses,
- }); err != nil {
- t.Fatal(err)
- }
-
- secret, err := client.Logical().Write("auth/userpass/login/test", map[string]interface{}{
- "password": "test",
- })
- if err != nil || secret == nil {
- t.Fatal(err)
- }
-
- // Remaining uses is not returned from this API
- uses = -1
- remaining, err := secret.TokenRemainingUses()
- if err != nil {
- t.Fatal(err)
- }
- if remaining != uses {
- t.Errorf("expected %d to be %d", remaining, uses)
- }
- })
-
- t.Run("token create", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- uses := 5
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- NumUses: uses,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // /auth/token/create does not return the number of uses
- uses = -1
- remaining, err := secret.TokenRemainingUses()
- if err != nil {
- t.Fatal(err)
- }
- if remaining != uses {
- t.Errorf("expected %d to be %d", remaining, uses)
- }
- })
-
- t.Run("token lookup", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- uses := 5
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- NumUses: uses,
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- secret, err = client.Auth().Token().Lookup(token)
- if err != nil {
- t.Fatal(err)
- }
-
- remaining, err := secret.TokenRemainingUses()
- if err != nil {
- t.Fatal(err)
- }
- if remaining != uses {
- t.Errorf("expected %d to be %d", remaining, uses)
- }
- })
-
- t.Run("token lookup-self", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- uses := 5
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- NumUses: uses,
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- client.SetToken(token)
- secret, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
-
- uses = uses - 1 // we just used it
- remaining, err := secret.TokenRemainingUses()
- if err != nil {
- t.Fatal(err)
- }
- if remaining != uses {
- t.Errorf("expected %d to be %d", remaining, uses)
- }
- })
-
- t.Run("token renew", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- uses := 5
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- NumUses: uses,
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- secret, err = client.Auth().Token().Renew(token, 0)
- if err != nil {
- t.Fatal(err)
- }
-
- // /auth/token/renew does not return the number of uses
- uses = -1
- remaining, err := secret.TokenRemainingUses()
- if err != nil {
- t.Fatal(err)
- }
- if remaining != uses {
- t.Errorf("expected %d to be %d", remaining, uses)
- }
- })
-
- t.Run("token renew-self", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- uses := 5
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- NumUses: uses,
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- client.SetToken(token)
- secret, err = client.Auth().Token().RenewSelf(0)
- if err != nil {
- t.Fatal(err)
- }
-
- // /auth/token/renew-self does not return the number of uses
- uses = -1
- remaining, err := secret.TokenRemainingUses()
- if err != nil {
- t.Fatal(err)
- }
- if remaining != uses {
- t.Errorf("expected %d to be %d", remaining, uses)
- }
- })
-}
-
-func TestSecret_TokenPolicies(t *testing.T) {
- t.Parallel()
-
- cases := []struct {
- name string
- secret *api.Secret
- exp []string
- err bool
- }{
- {
- "nil",
- nil,
- nil,
- false,
- },
- {
- "nil_auth",
- &api.Secret{
- Auth: nil,
- },
- nil,
- false,
- },
- {
- "nil_auth_policies",
- &api.Secret{
- Auth: &api.SecretAuth{
- Policies: nil,
- },
- },
- nil,
- false,
- },
- {
- "empty_auth_policies",
- &api.Secret{
- Auth: &api.SecretAuth{
- Policies: []string{},
- },
- },
- nil,
- false,
- },
- {
- "real_auth_policies",
- &api.Secret{
- Auth: &api.SecretAuth{
- Policies: []string{"foo"},
- },
- },
- []string{"foo"},
- false,
- },
- {
- "nil_data",
- &api.Secret{
- Data: nil,
- },
- nil,
- false,
- },
- {
- "empty_data",
- &api.Secret{
- Data: map[string]interface{}{},
- },
- nil,
- false,
- },
- {
- "data_not_slice",
- &api.Secret{
- Data: map[string]interface{}{
- "policies": 123,
- },
- },
- nil,
- true,
- },
- {
- "data_slice",
- &api.Secret{
- Data: map[string]interface{}{
- "policies": []interface{}{"foo"},
- },
- },
- []string{"foo"},
- false,
- },
- }
-
- for _, tc := range cases {
- tc := tc
-
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
-
- act, err := tc.secret.TokenPolicies()
- if err != nil && !tc.err {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(act, tc.exp) {
- t.Errorf("expected %#v to be %#v", act, tc.exp)
- }
- })
- }
-
- t.Run("auth", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- policies := []string{"bar", "default", "foo"}
-
- if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil {
- t.Fatal(err)
- }
- if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{
- "password": "test",
- "policies": strings.Join(policies, ","),
- }); err != nil {
- t.Fatal(err)
- }
-
- secret, err := client.Logical().Write("auth/userpass/login/test", map[string]interface{}{
- "password": "test",
- })
- if err != nil || secret == nil {
- t.Fatal(err)
- }
-
- tPol, err := secret.TokenPolicies()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(tPol, policies) {
- t.Errorf("expected %#v to be %#v", tPol, policies)
- }
- })
-
- t.Run("token create", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- policies := []string{"bar", "default", "foo"}
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: policies,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- tPol, err := secret.TokenPolicies()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(tPol, policies) {
- t.Errorf("expected %#v to be %#v", tPol, policies)
- }
- })
-
- t.Run("token lookup", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- policies := []string{"bar", "default", "foo"}
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: policies,
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- secret, err = client.Auth().Token().Lookup(token)
- if err != nil {
- t.Fatal(err)
- }
-
- tPol, err := secret.TokenPolicies()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(tPol, policies) {
- t.Errorf("expected %#v to be %#v", tPol, policies)
- }
- })
-
- t.Run("token lookup-self", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- policies := []string{"bar", "default", "foo"}
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: policies,
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- client.SetToken(token)
- secret, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
-
- tPol, err := secret.TokenPolicies()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(tPol, policies) {
- t.Errorf("expected %#v to be %#v", tPol, policies)
- }
- })
-
- t.Run("token renew", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- policies := []string{"bar", "default", "foo"}
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: policies,
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- secret, err = client.Auth().Token().Renew(token, 0)
- if err != nil {
- t.Fatal(err)
- }
-
- tPol, err := secret.TokenPolicies()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(tPol, policies) {
- t.Errorf("expected %#v to be %#v", tPol, policies)
- }
- })
-
- t.Run("token renew-self", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- policies := []string{"bar", "default", "foo"}
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: policies,
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- client.SetToken(token)
- secret, err = client.Auth().Token().RenewSelf(0)
- if err != nil {
- t.Fatal(err)
- }
-
- tPol, err := secret.TokenPolicies()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(tPol, policies) {
- t.Errorf("expected %#v to be %#v", tPol, policies)
- }
- })
-}
-
-func TestSecret_TokenMetadata(t *testing.T) {
- t.Parallel()
-
- cases := []struct {
- name string
- secret *api.Secret
- exp map[string]string
- err bool
- }{
- {
- "nil",
- nil,
- nil,
- false,
- },
- {
- "nil_auth",
- &api.Secret{
- Auth: nil,
- },
- nil,
- false,
- },
- {
- "nil_auth_metadata",
- &api.Secret{
- Auth: &api.SecretAuth{
- Metadata: nil,
- },
- },
- nil,
- false,
- },
- {
- "empty_auth_metadata",
- &api.Secret{
- Auth: &api.SecretAuth{
- Metadata: map[string]string{},
- },
- },
- nil,
- false,
- },
- {
- "real_auth_metadata",
- &api.Secret{
- Auth: &api.SecretAuth{
- Metadata: map[string]string{"foo": "bar"},
- },
- },
- map[string]string{"foo": "bar"},
- false,
- },
- {
- "nil_data",
- &api.Secret{
- Data: nil,
- },
- nil,
- false,
- },
- {
- "empty_data",
- &api.Secret{
- Data: map[string]interface{}{},
- },
- nil,
- false,
- },
- {
- "data_not_map",
- &api.Secret{
- Data: map[string]interface{}{
- "metadata": 123,
- },
- },
- nil,
- true,
- },
- {
- "data_map",
- &api.Secret{
- Data: map[string]interface{}{
- "metadata": map[string]interface{}{"foo": "bar"},
- },
- },
- map[string]string{"foo": "bar"},
- false,
- },
- {
- "data_map_bad_type",
- &api.Secret{
- Data: map[string]interface{}{
- "metadata": map[string]interface{}{"foo": 123},
- },
- },
- nil,
- true,
- },
- }
-
- for _, tc := range cases {
- tc := tc
-
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
-
- act, err := tc.secret.TokenMetadata()
- if err != nil && !tc.err {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(act, tc.exp) {
- t.Errorf("expected %#v to be %#v", act, tc.exp)
- }
- })
- }
-
- t.Run("auth", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- metadata := map[string]string{"username": "test"}
-
- if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil {
- t.Fatal(err)
- }
- if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{
- "password": "test",
- "policies": "default",
- }); err != nil {
- t.Fatal(err)
- }
-
- secret, err := client.Logical().Write("auth/userpass/login/test", map[string]interface{}{
- "password": "test",
- })
- if err != nil || secret == nil {
- t.Fatal(err)
- }
-
- tMeta, err := secret.TokenMetadata()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(tMeta, metadata) {
- t.Errorf("expected %#v to be %#v", tMeta, metadata)
- }
- })
-
- t.Run("token create", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- metadata := map[string]string{"username": "test"}
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Metadata: metadata,
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
-
- tMeta, err := secret.TokenMetadata()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(tMeta, metadata) {
- t.Errorf("expected %#v to be %#v", tMeta, metadata)
- }
- })
-
- t.Run("token lookup", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- metadata := map[string]string{"username": "test"}
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Metadata: metadata,
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- secret, err = client.Auth().Token().Lookup(token)
- if err != nil {
- t.Fatal(err)
- }
-
- tMeta, err := secret.TokenMetadata()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(tMeta, metadata) {
- t.Errorf("expected %#v to be %#v", tMeta, metadata)
- }
- })
-
- t.Run("token lookup-self", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- metadata := map[string]string{"username": "test"}
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Metadata: metadata,
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- client.SetToken(token)
- secret, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
-
- tMeta, err := secret.TokenMetadata()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(tMeta, metadata) {
- t.Errorf("expected %#v to be %#v", tMeta, metadata)
- }
- })
-
- t.Run("token renew", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- metadata := map[string]string{"username": "test"}
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Metadata: metadata,
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- secret, err = client.Auth().Token().Renew(token, 0)
- if err != nil {
- t.Fatal(err)
- }
-
- tMeta, err := secret.TokenMetadata()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(tMeta, metadata) {
- t.Errorf("expected %#v to be %#v", tMeta, metadata)
- }
- })
-
- t.Run("token renew-self", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- metadata := map[string]string{"username": "test"}
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Metadata: metadata,
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- client.SetToken(token)
- secret, err = client.Auth().Token().RenewSelf(0)
- if err != nil {
- t.Fatal(err)
- }
-
- tMeta, err := secret.TokenMetadata()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(tMeta, metadata) {
- t.Errorf("expected %#v to be %#v", tMeta, metadata)
- }
- })
-}
-
-func TestSecret_TokenIsRenewable(t *testing.T) {
- t.Parallel()
-
- cases := []struct {
- name string
- secret *api.Secret
- exp bool
- }{
- {
- "nil",
- nil,
- false,
- },
- {
- "nil_auth",
- &api.Secret{
- Auth: nil,
- },
- false,
- },
- {
- "auth_renewable_false",
- &api.Secret{
- Auth: &api.SecretAuth{
- Renewable: false,
- },
- },
- false,
- },
- {
- "auth_renewable_true",
- &api.Secret{
- Auth: &api.SecretAuth{
- Renewable: true,
- },
- },
- true,
- },
- {
- "nil_data",
- &api.Secret{
- Data: nil,
- },
- false,
- },
- {
- "empty_data",
- &api.Secret{
- Data: map[string]interface{}{},
- },
- false,
- },
- {
- "data_not_bool",
- &api.Secret{
- Data: map[string]interface{}{
- "renewable": 123,
- },
- },
- true,
- },
- {
- "data_bool_string",
- &api.Secret{
- Data: map[string]interface{}{
- "renewable": "true",
- },
- },
- true,
- },
- {
- "data_bool_true",
- &api.Secret{
- Data: map[string]interface{}{
- "renewable": true,
- },
- },
- true,
- },
- {
- "data_bool_false",
- &api.Secret{
- Data: map[string]interface{}{
- "renewable": true,
- },
- },
- true,
- },
- }
-
- for _, tc := range cases {
- tc := tc
-
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
-
- act, err := tc.secret.TokenIsRenewable()
- if err != nil {
- t.Fatal(err)
- }
- if act != tc.exp {
- t.Errorf("expected %t to be %t", act, tc.exp)
- }
- })
- }
-
- t.Run("auth", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- renewable := true
-
- if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil {
- t.Fatal(err)
- }
- if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{
- "password": "test",
- "policies": "default",
- }); err != nil {
- t.Fatal(err)
- }
-
- secret, err := client.Logical().Write("auth/userpass/login/test", map[string]interface{}{
- "password": "test",
- })
- if err != nil || secret == nil {
- t.Fatal(err)
- }
-
- tRenew, err := secret.TokenIsRenewable()
- if err != nil {
- t.Fatal(err)
- }
- if tRenew != renewable {
- t.Errorf("expected %t to be %t", tRenew, renewable)
- }
- })
-
- t.Run("token create", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- renewable := true
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- Renewable: &renewable,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- tRenew, err := secret.TokenIsRenewable()
- if err != nil {
- t.Fatal(err)
- }
- if tRenew != renewable {
- t.Errorf("expected %t to be %t", tRenew, renewable)
- }
- })
-
- t.Run("token lookup", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- renewable := true
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- Renewable: &renewable,
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- secret, err = client.Auth().Token().Lookup(token)
- if err != nil {
- t.Fatal(err)
- }
-
- tRenew, err := secret.TokenIsRenewable()
- if err != nil {
- t.Fatal(err)
- }
- if tRenew != renewable {
- t.Errorf("expected %t to be %t", tRenew, renewable)
- }
- })
-
- t.Run("token lookup-self", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- renewable := true
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- Renewable: &renewable,
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- client.SetToken(token)
- secret, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
-
- tRenew, err := secret.TokenIsRenewable()
- if err != nil {
- t.Fatal(err)
- }
- if tRenew != renewable {
- t.Errorf("expected %t to be %t", tRenew, renewable)
- }
- })
-
- t.Run("token renew", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- renewable := true
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- Renewable: &renewable,
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- secret, err = client.Auth().Token().Renew(token, 0)
- if err != nil {
- t.Fatal(err)
- }
-
- tRenew, err := secret.TokenIsRenewable()
- if err != nil {
- t.Fatal(err)
- }
- if tRenew != renewable {
- t.Errorf("expected %t to be %t", tRenew, renewable)
- }
- })
-
- t.Run("token renew-self", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- renewable := true
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- Renewable: &renewable,
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- client.SetToken(token)
- secret, err = client.Auth().Token().RenewSelf(0)
- if err != nil {
- t.Fatal(err)
- }
-
- tRenew, err := secret.TokenIsRenewable()
- if err != nil {
- t.Fatal(err)
- }
- if tRenew != renewable {
- t.Errorf("expected %t to be %t", tRenew, renewable)
- }
- })
-}
-
-func TestSecret_TokenTTL(t *testing.T) {
- t.Parallel()
-
- cases := []struct {
- name string
- secret *api.Secret
- exp time.Duration
- }{
- {
- "nil",
- nil,
- 0,
- },
- {
- "nil_auth",
- &api.Secret{
- Auth: nil,
- },
- 0,
- },
- {
- "nil_auth_lease_duration",
- &api.Secret{
- Auth: &api.SecretAuth{
- LeaseDuration: 0,
- },
- },
- 0,
- },
- {
- "real_auth_lease_duration",
- &api.Secret{
- Auth: &api.SecretAuth{
- LeaseDuration: 3600,
- },
- },
- 1 * time.Hour,
- },
- {
- "nil_data",
- &api.Secret{
- Data: nil,
- },
- 0,
- },
- {
- "empty_data",
- &api.Secret{
- Data: map[string]interface{}{},
- },
- 0,
- },
- {
- "data_not_json_number",
- &api.Secret{
- Data: map[string]interface{}{
- "ttl": 123,
- },
- },
- 123 * time.Second,
- },
- {
- "data_json_number",
- &api.Secret{
- Data: map[string]interface{}{
- "ttl": json.Number("3600"),
- },
- },
- 1 * time.Hour,
- },
- }
-
- for _, tc := range cases {
- tc := tc
-
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
-
- act, err := tc.secret.TokenTTL()
- if err != nil {
- t.Fatal(err)
- }
- if act != tc.exp {
- t.Errorf("expected %q to be %q", act, tc.exp)
- }
- })
- }
-
- t.Run("auth", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- ttl := 30 * time.Minute
-
- if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil {
- t.Fatal(err)
- }
- if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{
- "password": "test",
- "policies": "default",
- "ttl": ttl.String(),
- "explicit_max_ttl": ttl.String(),
- }); err != nil {
- t.Fatal(err)
- }
-
- secret, err := client.Logical().Write("auth/userpass/login/test", map[string]interface{}{
- "password": "test",
- })
- if err != nil || secret == nil {
- t.Fatal(err)
- }
-
- tokenTTL, err := secret.TokenTTL()
- if err != nil {
- t.Fatal(err)
- }
- if tokenTTL == 0 || tokenTTL > ttl {
- t.Errorf("expected %q to non-zero and less than %q", tokenTTL, ttl)
- }
- })
-
- t.Run("token create", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- ttl := 30 * time.Minute
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- TTL: ttl.String(),
- ExplicitMaxTTL: ttl.String(),
- })
- if err != nil {
- t.Fatal(err)
- }
-
- tokenTTL, err := secret.TokenTTL()
- if err != nil {
- t.Fatal(err)
- }
- if tokenTTL == 0 || tokenTTL > ttl {
- t.Errorf("expected %q to non-zero and less than %q", tokenTTL, ttl)
- }
- })
-
- t.Run("token lookup", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- ttl := 30 * time.Minute
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- TTL: ttl.String(),
- ExplicitMaxTTL: ttl.String(),
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- secret, err = client.Auth().Token().Lookup(token)
- if err != nil {
- t.Fatal(err)
- }
-
- tokenTTL, err := secret.TokenTTL()
- if err != nil {
- t.Fatal(err)
- }
- if tokenTTL == 0 || tokenTTL > ttl {
- t.Errorf("expected %q to non-zero and less than %q", tokenTTL, ttl)
- }
- })
-
- t.Run("token lookup-self", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- ttl := 30 * time.Minute
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- TTL: ttl.String(),
- ExplicitMaxTTL: ttl.String(),
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- client.SetToken(token)
- secret, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
-
- tokenTTL, err := secret.TokenTTL()
- if err != nil {
- t.Fatal(err)
- }
- if tokenTTL == 0 || tokenTTL > ttl {
- t.Errorf("expected %q to non-zero and less than %q", tokenTTL, ttl)
- }
- })
-
- t.Run("token renew", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- ttl := 30 * time.Minute
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- TTL: ttl.String(),
- ExplicitMaxTTL: ttl.String(),
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- secret, err = client.Auth().Token().Renew(token, 0)
- if err != nil {
- t.Fatal(err)
- }
-
- tokenTTL, err := secret.TokenTTL()
- if err != nil {
- t.Fatal(err)
- }
- if tokenTTL == 0 || tokenTTL > ttl {
- t.Errorf("expected %q to non-zero and less than %q", tokenTTL, ttl)
- }
- })
-
- t.Run("token renew-self", func(t *testing.T) {
- t.Parallel()
-
- client, closer := testVaultServer(t)
- defer closer()
-
- ttl := 30 * time.Minute
-
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- TTL: ttl.String(),
- ExplicitMaxTTL: ttl.String(),
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- client.SetToken(token)
- secret, err = client.Auth().Token().RenewSelf(0)
- if err != nil {
- t.Fatal(err)
- }
-
- tokenTTL, err := secret.TokenTTL()
- if err != nil {
- t.Fatal(err)
- }
- if tokenTTL == 0 || tokenTTL > ttl {
- t.Errorf("expected %q to non-zero and less than %q", tokenTTL, ttl)
- }
- })
-}
diff --git a/vault/external_tests/api/sudo_paths_test.go b/vault/external_tests/api/sudo_paths_test.go
deleted file mode 100644
index 45a1288ac..000000000
--- a/vault/external_tests/api/sudo_paths_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package api
-
-import (
- "fmt"
- "testing"
-
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/audit"
- auditFile "github.com/hashicorp/vault/builtin/audit/file"
- credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
- "github.com/hashicorp/vault/builtin/logical/database"
- "github.com/hashicorp/vault/builtin/logical/pki"
- "github.com/hashicorp/vault/builtin/logical/transit"
- "github.com/hashicorp/vault/helper/builtinplugins"
- "github.com/hashicorp/vault/sdk/helper/jsonutil"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-const sudoKey = "x-vault-sudo"
-
-// Tests that the static list of sudo paths in the api package matches what's in the current OpenAPI spec.
-func TestSudoPaths(t *testing.T) {
- t.Parallel()
-
- coreConfig := &vault.CoreConfig{
- DisableMlock: true,
- DisableCache: true,
- EnableRaw: true,
- EnableIntrospection: true,
- Logger: log.NewNullLogger(),
- CredentialBackends: map[string]logical.Factory{
- "userpass": credUserpass.Factory,
- },
- AuditBackends: map[string]audit.Factory{
- "file": auditFile.Factory,
- },
- LogicalBackends: map[string]logical.Factory{
- "database": database.Factory,
- "generic-leased": vault.LeasedPassthroughBackendFactory,
- "pki": pki.Factory,
- "transit": transit.Factory,
- },
- BuiltinRegistry: builtinplugins.Registry,
- }
- client, _, closer := testVaultServerCoreConfig(t, coreConfig)
- defer closer()
-
- for credBackendName := range coreConfig.CredentialBackends {
- err := client.Sys().EnableAuthWithOptions(credBackendName, &api.EnableAuthOptions{
- Type: credBackendName,
- })
- if err != nil {
- t.Fatalf("error enabling auth backend for test: %v", err)
- }
- }
-
- for logicalBackendName := range coreConfig.LogicalBackends {
- err := client.Sys().Mount(logicalBackendName, &api.MountInput{
- Type: logicalBackendName,
- })
- if err != nil {
- t.Fatalf("error enabling logical backend for test: %v", err)
- }
- }
-
- sudoPathsFromSpec, err := getSudoPathsFromSpec(client)
- if err != nil {
- t.Fatalf("error getting list of paths that require sudo from OpenAPI endpoint: %v", err)
- }
-
- sudoPathsInCode := api.SudoPaths()
-
- // check for missing paths
- for path := range sudoPathsFromSpec {
- if _, ok := sudoPathsInCode[path]; !ok {
- t.Fatalf(
- "A path in the OpenAPI spec is missing from the static list of "+
- "sudo paths in the api module (%s). Please reconcile the two "+
- "accordingly.", path)
- }
- }
-}
-
-func getSudoPathsFromSpec(client *api.Client) (map[string]struct{}, error) {
- r := client.NewRequest("GET", "/v1/sys/internal/specs/openapi")
- resp, err := client.RawRequest(r)
- if err != nil {
- return nil, fmt.Errorf("unable to retrieve sudo endpoints: %v", err)
- }
- if resp != nil {
- defer resp.Body.Close()
- }
-
- oasInfo := make(map[string]interface{})
- if err := jsonutil.DecodeJSONFromReader(resp.Body, &oasInfo); err != nil {
- return nil, fmt.Errorf("unable to decode JSON from OpenAPI response: %v", err)
- }
-
- paths, ok := oasInfo["paths"]
- if !ok {
- return nil, fmt.Errorf("OpenAPI response did not include paths")
- }
-
- pathsMap, ok := paths.(map[string]interface{})
- if !ok {
- return nil, fmt.Errorf("OpenAPI response did not return valid paths")
- }
-
- sudoPaths := make(map[string]struct{})
- for pathName, pathInfo := range pathsMap {
- pathInfoMap, ok := pathInfo.(map[string]interface{})
- if !ok {
- continue
- }
-
- if sudo, ok := pathInfoMap[sudoKey]; ok {
- if sudo == true {
- sudoPaths[pathName] = struct{}{}
- }
- }
- }
-
- return sudoPaths, nil
-}
diff --git a/vault/external_tests/api/sys_rekey_ext_test.go b/vault/external_tests/api/sys_rekey_ext_test.go
deleted file mode 100644
index f1a63ed83..000000000
--- a/vault/external_tests/api/sys_rekey_ext_test.go
+++ /dev/null
@@ -1,300 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package api
-
-import (
- "encoding/base64"
- "fmt"
- "testing"
-
- "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/helper/testhelpers"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/physical/inmem"
- "github.com/hashicorp/vault/vault"
- "github.com/hashicorp/vault/vault/seal"
-)
-
-func TestSysRekey_Verification(t *testing.T) {
- testcases := []struct {
- recovery bool
- legacyShamir bool
- }{
- {recovery: true, legacyShamir: false},
- {recovery: false, legacyShamir: false},
- {recovery: false, legacyShamir: true},
- }
-
- for _, tc := range testcases {
- recovery, legacy := tc.recovery, tc.legacyShamir
- t.Run(fmt.Sprintf("recovery=%v,legacyShamir=%v", recovery, legacy), func(t *testing.T) {
- t.Parallel()
- testSysRekey_Verification(t, recovery, legacy)
- })
- }
-}
-
-func testSysRekey_Verification(t *testing.T, recovery bool, legacyShamir bool) {
- opts := &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- }
- switch {
- case recovery:
- if legacyShamir {
- panic("invalid case")
- }
- opts.SealFunc = func() vault.Seal {
- return vault.NewTestSeal(t, &seal.TestSealOpts{
- StoredKeys: seal.StoredKeysSupportedGeneric,
- })
- }
- case legacyShamir:
- opts.SealFunc = func() vault.Seal {
- return vault.NewTestSeal(t, &seal.TestSealOpts{
- StoredKeys: seal.StoredKeysNotSupported,
- })
- }
- }
- inm, err := inmem.NewInmemHA(nil, logging.NewVaultLogger(hclog.Debug))
- if err != nil {
- t.Fatal(err)
- }
- conf := vault.CoreConfig{
- Physical: inm,
- }
- cluster := vault.NewTestCluster(t, &conf, opts)
- cluster.Start()
- defer cluster.Cleanup()
-
- vault.TestWaitActive(t, cluster.Cores[0].Core)
- client := cluster.Cores[0].Client
- client.SetMaxRetries(0)
-
- initFunc := client.Sys().RekeyInit
- updateFunc := client.Sys().RekeyUpdate
- verificationUpdateFunc := client.Sys().RekeyVerificationUpdate
- verificationStatusFunc := client.Sys().RekeyVerificationStatus
- verificationCancelFunc := client.Sys().RekeyVerificationCancel
- if recovery {
- initFunc = client.Sys().RekeyRecoveryKeyInit
- updateFunc = client.Sys().RekeyRecoveryKeyUpdate
- verificationUpdateFunc = client.Sys().RekeyRecoveryKeyVerificationUpdate
- verificationStatusFunc = client.Sys().RekeyRecoveryKeyVerificationStatus
- verificationCancelFunc = client.Sys().RekeyRecoveryKeyVerificationCancel
- }
-
- var verificationNonce string
- var newKeys []string
- doRekeyInitialSteps := func() {
- status, err := initFunc(&api.RekeyInitRequest{
- SecretShares: 5,
- SecretThreshold: 3,
- RequireVerification: true,
- })
- if err != nil {
- t.Fatal(err)
- }
- if status == nil {
- t.Fatal("nil status")
- }
- if !status.VerificationRequired {
- t.Fatal("expected verification required")
- }
-
- keys := cluster.BarrierKeys
- if recovery {
- keys = cluster.RecoveryKeys
- }
- var resp *api.RekeyUpdateResponse
- for i := 0; i < 3; i++ {
- resp, err = updateFunc(base64.StdEncoding.EncodeToString(keys[i]), status.Nonce)
- if err != nil {
- t.Fatal(err)
- }
- }
- switch {
- case !resp.Complete:
- t.Fatal("expected completion")
- case !resp.VerificationRequired:
- t.Fatal("expected verification required")
- case resp.VerificationNonce == "":
- t.Fatal("verification nonce expected")
- }
- verificationNonce = resp.VerificationNonce
- newKeys = resp.KeysB64
- t.Logf("verification nonce: %q", verificationNonce)
- }
-
- doRekeyInitialSteps()
-
- // We are still going, so should not be able to init again
- _, err = initFunc(&api.RekeyInitRequest{
- SecretShares: 5,
- SecretThreshold: 3,
- RequireVerification: true,
- })
- if err == nil {
- t.Fatal("expected error")
- }
-
- // Sealing should clear state, so after this we should be able to perform
- // the above again
- cluster.EnsureCoresSealed(t)
- if err := cluster.UnsealCoresWithError(recovery); err != nil {
- t.Fatal(err)
- }
- doRekeyInitialSteps()
-
- doStartVerify := func() {
- // Start the process
- for i := 0; i < 2; i++ {
- status, err := verificationUpdateFunc(newKeys[i], verificationNonce)
- if err != nil {
- t.Fatal(err)
- }
- switch {
- case status.Nonce != verificationNonce:
- t.Fatalf("unexpected nonce, expected %q, got %q", verificationNonce, status.Nonce)
- case status.Complete:
- t.Fatal("unexpected completion")
- }
- }
-
- // Check status
- vStatus, err := verificationStatusFunc()
- if err != nil {
- t.Fatal(err)
- }
- switch {
- case vStatus.Nonce != verificationNonce:
- t.Fatalf("unexpected nonce, expected %q, got %q", verificationNonce, vStatus.Nonce)
- case vStatus.T != 3:
- t.Fatal("unexpected threshold")
- case vStatus.N != 5:
- t.Fatal("unexpected number of new keys")
- case vStatus.Progress != 2:
- t.Fatal("unexpected progress")
- }
- }
-
- doStartVerify()
-
- // Cancel; this should still keep the rekey process going but just cancel
- // the verification operation
- err = verificationCancelFunc()
- if err != nil {
- t.Fatal(err)
- }
- // Verify cannot init again
- _, err = initFunc(&api.RekeyInitRequest{
- SecretShares: 5,
- SecretThreshold: 3,
- RequireVerification: true,
- })
- if err == nil {
- t.Fatal("expected error")
- }
- vStatus, err := verificationStatusFunc()
- if err != nil {
- t.Fatal(err)
- }
- switch {
- case vStatus.Nonce == verificationNonce:
- t.Fatalf("unexpected nonce, expected not-%q but got it", verificationNonce)
- case vStatus.T != 3:
- t.Fatal("unexpected threshold")
- case vStatus.N != 5:
- t.Fatal("unexpected number of new keys")
- case vStatus.Progress != 0:
- t.Fatal("unexpected progress")
- }
-
- verificationNonce = vStatus.Nonce
- doStartVerify()
-
- if !recovery {
- // Sealing should clear state, but we never actually finished, so it should
- // still be the old keys (which are still currently set)
- cluster.EnsureCoresSealed(t)
- cluster.UnsealCores(t)
- vault.TestWaitActive(t, cluster.Cores[0].Core)
-
- // Should be able to init again and get back to where we were
- doRekeyInitialSteps()
- doStartVerify()
- } else {
- // We haven't finished, so generating a root token should still be the
- // old keys (which are still currently set)
- testhelpers.GenerateRoot(t, cluster, testhelpers.GenerateRootRegular)
- }
-
- // Provide the final new key
- vuStatus, err := verificationUpdateFunc(newKeys[2], verificationNonce)
- if err != nil {
- t.Fatal(err)
- }
- switch {
- case vuStatus.Nonce != verificationNonce:
- t.Fatalf("unexpected nonce, expected %q, got %q", verificationNonce, vuStatus.Nonce)
- case !vuStatus.Complete:
- t.Fatal("expected completion")
- }
-
- if !recovery {
- // Seal and unseal -- it should fail to unseal because the key has now been
- // rotated
- cluster.EnsureCoresSealed(t)
-
- // Simulate restarting Vault rather than just a seal/unseal, because
- // the standbys may not have had time to learn about the new key before
- // we sealed them. We could sleep, but that's unreliable.
- oldKeys := cluster.BarrierKeys
- opts.SkipInit = true
- opts.SealFunc = nil // post rekey we should use the barrier config on disk
- cluster = vault.NewTestCluster(t, &conf, opts)
- cluster.BarrierKeys = oldKeys
- cluster.Start()
- defer cluster.Cleanup()
-
- if err := cluster.UnsealCoresWithError(false); err == nil {
- t.Fatal("expected error")
- }
-
- // Swap out the keys with our new ones and try again
- var newKeyBytes [][]byte
- for _, key := range newKeys {
- val, err := base64.StdEncoding.DecodeString(key)
- if err != nil {
- t.Fatal(err)
- }
- newKeyBytes = append(newKeyBytes, val)
- }
- cluster.BarrierKeys = newKeyBytes
- if err := cluster.UnsealCoresWithError(false); err != nil {
- t.Fatal(err)
- }
- } else {
- // The old keys should no longer work
- _, err := testhelpers.GenerateRootWithError(t, cluster, testhelpers.GenerateRootRegular)
- if err == nil {
- t.Fatal("expected error")
- }
-
- // Put the new keys in place and run again
- cluster.RecoveryKeys = nil
- for _, key := range newKeys {
- dec, err := base64.StdEncoding.DecodeString(key)
- if err != nil {
- t.Fatal(err)
- }
- cluster.RecoveryKeys = append(cluster.RecoveryKeys, dec)
- }
- if err := client.Sys().GenerateRootCancel(); err != nil {
- t.Fatal(err)
- }
- testhelpers.GenerateRoot(t, cluster, testhelpers.GenerateRootRegular)
- }
-}
diff --git a/vault/external_tests/approle/wrapped_secretid_test.go b/vault/external_tests/approle/wrapped_secretid_test.go
deleted file mode 100644
index e7376635a..000000000
--- a/vault/external_tests/approle/wrapped_secretid_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package approle
-
-import (
- "testing"
-
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/api"
- credAppRole "github.com/hashicorp/vault/builtin/credential/approle"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
- "github.com/stretchr/testify/require"
-)
-
-func TestApproleSecretId_Wrapped(t *testing.T) {
- var err error
- coreConfig := &vault.CoreConfig{
- DisableMlock: true,
- DisableCache: true,
- Logger: log.NewNullLogger(),
- CredentialBackends: map[string]logical.Factory{
- "approle": credAppRole.Factory,
- },
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- cores := cluster.Cores
-
- vault.TestWaitActive(t, cores[0].Core)
-
- client := cores[0].Client
- client.SetToken(cluster.RootToken)
-
- err = client.Sys().EnableAuthWithOptions("approle", &api.EnableAuthOptions{
- Type: "approle",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("auth/approle/role/test-role-1", map[string]interface{}{
- "name": "test-role-1",
- })
- require.NoError(t, err)
-
- client.SetWrappingLookupFunc(func(operation, path string) string {
- return "5m"
- })
-
- resp, err := client.Logical().Write("/auth/approle/role/test-role-1/secret-id", map[string]interface{}{})
- require.NoError(t, err)
-
- wrappedAccessor := resp.WrapInfo.WrappedAccessor
- wrappingToken := resp.WrapInfo.Token
-
- client.SetWrappingLookupFunc(func(operation, path string) string {
- return api.DefaultWrappingLookupFunc(operation, path)
- })
-
- unwrappedSecretid, err := client.Logical().Unwrap(wrappingToken)
- require.NoError(t, err)
- unwrappedAccessor := unwrappedSecretid.Data["secret_id_accessor"].(string)
-
- if wrappedAccessor != unwrappedAccessor {
- t.Fatalf("Expected wrappedAccessor (%v) to match wrapped secret_id_accessor (%v)", wrappedAccessor, unwrappedAccessor)
- }
-}
-
-func TestApproleSecretId_NotWrapped(t *testing.T) {
- var err error
- coreConfig := &vault.CoreConfig{
- DisableMlock: true,
- DisableCache: true,
- Logger: log.NewNullLogger(),
- CredentialBackends: map[string]logical.Factory{
- "approle": credAppRole.Factory,
- },
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- cores := cluster.Cores
-
- vault.TestWaitActive(t, cores[0].Core)
-
- client := cores[0].Client
- client.SetToken(cluster.RootToken)
-
- err = client.Sys().EnableAuthWithOptions("approle", &api.EnableAuthOptions{
- Type: "approle",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("auth/approle/role/test-role-1", map[string]interface{}{
- "name": "test-role-1",
- })
- require.NoError(t, err)
-
- resp, err := client.Logical().Write("/auth/approle/role/test-role-1/secret-id", map[string]interface{}{})
- require.NoError(t, err)
-
- if resp.WrapInfo != nil && resp.WrapInfo.WrappedAccessor != "" {
- t.Fatalf("WrappedAccessor unexpectedly set")
- }
-}
diff --git a/vault/external_tests/consul_fencing_binary/consul_fencing_test.go b/vault/external_tests/consul_fencing_binary/consul_fencing_test.go
deleted file mode 100644
index bf69a8714..000000000
--- a/vault/external_tests/consul_fencing_binary/consul_fencing_test.go
+++ /dev/null
@@ -1,295 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package consul_fencing
-
-import (
- "context"
- "fmt"
- "sort"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/helper/testhelpers/consul"
- "github.com/hashicorp/vault/sdk/helper/testcluster"
- "github.com/hashicorp/vault/sdk/helper/testcluster/docker"
- "github.com/stretchr/testify/require"
-)
-
-// TestConsulFencing_PartitionedLeaderCantWrite attempts to create an active
-// node split-brain when using Consul storage to ensure the old leader doesn't
-// continue to write data potentially corrupting storage. It is naturally
-// non-deterministic since it relies heavily on timing of the different
-// container processes, however it pretty reliably failed before the fencing fix
-// (and Consul lock improvements) and should _never_ fail now we correctly fence
-// writes.
-func TestConsulFencing_PartitionedLeaderCantWrite(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
- defer cancel()
-
- consulStorage := consul.NewClusterStorage()
-
- // Create cluster logger that will dump cluster logs to stdout for debugging.
- logger := hclog.NewInterceptLogger(hclog.DefaultOptions)
- logger.SetLevel(hclog.Trace)
-
- clusterOpts := docker.DefaultOptions(t)
- clusterOpts.ImageRepo = "hashicorp/vault-enterprise"
- clusterOpts.ClusterOptions.Logger = logger
-
- clusterOpts.Storage = consulStorage
-
- logger.Info("==> starting cluster")
- c, err := docker.NewDockerCluster(ctx, clusterOpts)
- require.NoError(t, err)
- logger.Info(" ✅ done.", "root_token", c.GetRootToken(),
- "consul_token", consulStorage.Config().Token)
-
- logger.Info("==> waiting for leader")
- leaderIdx, err := testcluster.WaitForActiveNode(ctx, c)
- require.NoError(t, err)
-
- leader := c.Nodes()[leaderIdx]
- leaderClient := leader.APIClient()
-
- notLeader := c.Nodes()[1] // Assumes it's usually zero and correct below
- if leaderIdx == 1 {
- notLeader = c.Nodes()[0]
- }
-
- // Mount a KV v2 backend
- logger.Info("==> mounting KV")
- err = leaderClient.Sys().Mount("/test", &api.MountInput{
- Type: "kv-v2",
- })
- require.NoError(t, err)
-
- // Start two background workers that will cause writes to Consul in the
- // background. KV v2 relies on a single active node for correctness.
- // Specifically its patch operation does a read-modify-write under a
- // key-specific lock which is correct for concurrent writes to one process,
- // but which by nature of our storage API is not going to be atomic if another
- // active node is also writing the same KV. It's made worse because the cache
- // backend means the active node will not actually read from Consul after the
- // initial read and will be modifying its own in-memory version and writing
- // that back. So we should be able to detect multiple active nodes writing
- // reliably with the following setup:
- // 1. Two separate "client" goroutines each connected to different Vault
- // servers.
- // 2. Both write to the same kv-v2 key, each one modifies only its own set
- // of subkeys c1-X or c2-X.
- // 3. Each request adds the next sequential X value for that client. We use a
- // Patch operation so we don't need to read the version or use CAS. On an
- // error each client will retry the same key until it gets a success.
- // 4. In a correct system with a single active node despite a partition, we
- // expect a complete set of consecutive X values for both clients (i.e.
- // no writes lost). If an old leader is still allowed to write to Consul
- // then it will continue to patch against its own last-known version from
- // cache and so will overwrite any concurrent updates from the other
- // client and we should see that as lost updates at the end.
- var wg sync.WaitGroup
- errCh := make(chan error, 10)
- var writeCount uint64
-
- // Initialise the key once
- kv := leaderClient.KVv2("/test")
- _, err = kv.Put(ctx, "data", map[string]interface{}{
- "c0-00000000": 1, // value don't matter here only keys in this set.
- "c1-00000000": 1,
- })
- require.NoError(t, err)
-
- const interval = 500 * time.Millisecond
-
- runWriter := func(i int, targetServer testcluster.VaultClusterNode, ctr *uint64) {
- wg.Add(1)
- defer wg.Done()
- kv := targetServer.APIClient().KVv2("/test")
-
- for {
- key := fmt.Sprintf("c%d-%08d", i, atomic.LoadUint64(ctr))
-
- // Use a short timeout. If we don't then the one goroutine writing to the
- // partitioned active node can get stuck here until the 60 second request
- // timeout kicks in without issuing another request.
- reqCtx, cancel := context.WithTimeout(ctx, interval)
- logger.Debug("sending patch", "client", i, "key", key)
- _, err = kv.Patch(reqCtx, "data", map[string]interface{}{
- key: 1,
- })
- cancel()
- // Deliver errors to test, don't block if we get too many before context
- // is cancelled otherwise client 0 can end up blocked as it has so many
- // errors during the partition it doesn't actually start writing again
- // ever and so the test never sees split-brain writes.
- if err != nil {
- select {
- case <-ctx.Done():
- return
- case errCh <- fmt.Errorf("client %d error: %w", i, err):
- default:
- // errCh is blocked, carry on anyway
- }
- } else {
- // Only increment our set counter here now we've had an ack that the
- // update was successful.
- atomic.AddUint64(ctr, 1)
- atomic.AddUint64(&writeCount, 1)
- }
- select {
- case <-ctx.Done():
- return
- case <-time.After(interval):
- }
- }
- }
-
- logger.Info("==> starting writers")
- client0Ctr, client1Ctr := uint64(1), uint64(1)
- go runWriter(0, leader, &client0Ctr)
- go runWriter(1, notLeader, &client1Ctr)
-
- // Wait for some writes to have started
- var writesBeforePartition uint64
- logger.Info("==> waiting for writes")
- for {
- time.Sleep(1 * time.Millisecond)
- writesBeforePartition = atomic.LoadUint64(&writeCount)
- if writesBeforePartition >= 5 {
- break
- }
- // Also check for any write errors
- select {
- case err := <-errCh:
- require.NoError(t, err)
- default:
- }
- require.NoError(t, ctx.Err())
- }
-
- val, err := kv.Get(ctx, "data")
- require.NoError(t, err)
-
- logger.Info("==> partitioning leader")
- // Now partition the leader from everything else (including Consul)
- err = leader.(*docker.DockerClusterNode).PartitionFromCluster(ctx)
- require.NoError(t, err)
-
- // Reload this incase more writes occurred before the partition completed.
- writesBeforePartition = atomic.LoadUint64(&writeCount)
-
- // Wait for some more writes to have happened (the client writing to leader
- // will probably have sent one and hung waiting for a response but the other
- // one should eventually start committing again when new active node is
- // elected).
-
- logger.Info("==> waiting for writes to new leader")
- for {
- time.Sleep(1 * time.Millisecond)
- writesAfterPartition := atomic.LoadUint64(&writeCount)
- if (writesAfterPartition - writesBeforePartition) >= 20 {
- break
- }
- // Also check for any write errors or timeouts
- select {
- case err := <-errCh:
- // Don't fail here because we expect writes to the old leader to fail
- // eventually or if they need a new connection etc.
- logger.Info("failed write", "write_count", writesAfterPartition, "err", err)
- default:
- }
- require.NoError(t, ctx.Err())
- }
-
- // Heal partition
- logger.Info("==> healing partition")
- err = leader.(*docker.DockerClusterNode).UnpartitionFromCluster(ctx)
- require.NoError(t, err)
-
- // Wait for old leader to rejoin as a standby and get healthy.
- logger.Info("==> wait for old leader to rejoin")
-
- require.NoError(t, waitUntilNotLeader(ctx, leaderClient, logger))
-
- // Stop the writers and wait for them to shut down nicely
- logger.Info("==> stopping writers")
- cancel()
- wg.Wait()
-
- // Now verify that all Consul data is consistent with only one leader writing.
- // Use a new context since we just cancelled the general one
- reqCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
- val, err = kv.Get(reqCtx, "data")
- require.NoError(t, err)
-
- // Ensure we have every consecutive key for both client
- sets := [][]int{make([]int, 0, 128), make([]int, 0, 128)}
- for k := range val.Data {
- var cNum, x int
- _, err := fmt.Sscanf(k, "c%d-%08d", &cNum, &x)
- require.NoError(t, err)
- sets[cNum] = append(sets[cNum], x)
- }
-
- // Sort both sets
- sort.Ints(sets[0])
- sort.Ints(sets[1])
-
- // Ensure they are both complete by creating an expected set and comparing to
- // get nice output to debug. Note that make set is an exclusive bound since we
- // don't know it the current counter value write completed or not yet so we'll
- // only create sets up to one less than that value that we know for sure
- // should be present.
- c0Writes := int(atomic.LoadUint64(&client0Ctr))
- c1Writes := int(atomic.LoadUint64(&client1Ctr))
- expect0 := makeSet(c0Writes)
- expect1 := makeSet(c1Writes)
-
- // Trim the sets to only the writes we know completed since that's all the
- // expected arrays contain. But only if they are longer so we don't change the
- // slice length if they are shorter than the expected number.
- if len(sets[0]) > c0Writes {
- sets[0] = sets[0][0:c0Writes]
- }
- if len(sets[1]) > c1Writes {
- sets[1] = sets[1][0:c1Writes]
- }
- require.Equal(t, expect0, sets[0], "Client 0 writes lost")
- require.Equal(t, expect1, sets[1], "Client 1 writes lost")
-}
-
-func makeSet(n int) []int {
- a := make([]int, n)
- for i := 0; i < n; i++ {
- a[i] = i
- }
- return a
-}
-
-func waitUntilNotLeader(ctx context.Context, oldLeaderClient *api.Client, logger hclog.Logger) error {
- for {
- // Wait for the original leader to acknowledge it's not active any more.
- resp, err := oldLeaderClient.Sys().LeaderWithContext(ctx)
- // Tolerate errors as the old leader is in a difficult place right now.
- if err == nil {
- if !resp.IsSelf {
- // We are not leader!
- return nil
- }
- logger.Info("old leader not ready yet", "IsSelf", resp.IsSelf)
- } else {
- logger.Info("failed to read old leader status", "err", err)
- }
- select {
- case <-ctx.Done():
- return ctx.Err()
- case <-time.After(time.Second):
- // Loop again
- }
- }
-}
diff --git a/vault/external_tests/expiration/expiration_test.go b/vault/external_tests/expiration/expiration_test.go
deleted file mode 100644
index aa8f1a013..000000000
--- a/vault/external_tests/expiration/expiration_test.go
+++ /dev/null
@@ -1,305 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package expiration
-
-import (
- "encoding/json"
- "reflect"
- "testing"
-
- "github.com/hashicorp/vault/helper/namespace"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/vault"
-)
-
-func TestExpiration_irrevocableLeaseCountsAPI(t *testing.T) {
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- NumCores: 1,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- client := cluster.Cores[0].Client
- core := cluster.Cores[0].Core
-
- params := make(map[string][]string)
- params["type"] = []string{"irrevocable"}
- resp, err := client.Logical().ReadWithData("sys/leases/count", params)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("response is nil")
- }
-
- if len(resp.Warnings) > 0 {
- t.Errorf("expected no warnings, got: %v", resp.Warnings)
- }
-
- totalLeaseCountRaw, ok := resp.Data["lease_count"]
- if !ok {
- t.Fatalf("expected 'lease_count' response, got: %#v", resp.Data)
- }
-
- totalLeaseCount, err := totalLeaseCountRaw.(json.Number).Int64()
- if err != nil {
- t.Fatalf("error extracting lease count: %v", err)
- }
- if totalLeaseCount != 0 {
- t.Errorf("expected no leases, got %d", totalLeaseCount)
- }
-
- countPerMountRaw, ok := resp.Data["counts"]
- if !ok {
- t.Fatalf("expected 'counts' response, got %#v", resp.Data)
- }
- countPerMount := countPerMountRaw.(map[string]interface{})
- if len(countPerMount) != 0 {
- t.Errorf("expected no mounts with counts, got %#v", countPerMount)
- }
-
- expectedNumLeases := 50
- expectedCountPerMount, err := core.InjectIrrevocableLeases(namespace.RootContext(nil), expectedNumLeases)
- if err != nil {
- t.Fatal(err)
- }
-
- resp, err = client.Logical().ReadWithData("sys/leases/count", params)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("response is nil")
- }
-
- if len(resp.Warnings) > 0 {
- t.Errorf("expected no warnings, got: %v", resp.Warnings)
- }
-
- totalLeaseCountRaw, ok = resp.Data["lease_count"]
- if !ok {
- t.Fatalf("expected 'lease_count' response, got: %#v", resp.Data)
- }
-
- totalLeaseCount, err = totalLeaseCountRaw.(json.Number).Int64()
- if err != nil {
- t.Fatalf("error extracting lease count: %v", err)
- }
- if totalLeaseCount != int64(expectedNumLeases) {
- t.Errorf("expected %d leases, got %d", expectedNumLeases, totalLeaseCount)
- }
-
- countPerMountRaw, ok = resp.Data["counts"]
- if !ok {
- t.Fatalf("expected 'counts' response, got %#v", resp.Data)
- }
-
- countPerMount = countPerMountRaw.(map[string]interface{})
- if len(countPerMount) != len(expectedCountPerMount) {
- t.Fatalf("expected %d mounts, got %d: %#v", len(expectedCountPerMount), len(countPerMount), countPerMount)
- }
-
- for mount, expectedCount := range expectedCountPerMount {
- gotCountRaw, ok := countPerMount[mount]
- if !ok {
- t.Errorf("missing mount %q", mount)
- continue
- }
-
- gotCount, err := gotCountRaw.(json.Number).Int64()
- if err != nil {
- t.Errorf("error extracting lease count for mount %q: %v", mount, err)
- continue
- }
- if gotCount != int64(expectedCount) {
- t.Errorf("bad count for mount %q: expected: %d, got: %d", mount, expectedCount, gotCount)
- }
- }
-}
-
-func TestExpiration_irrevocableLeaseListAPI(t *testing.T) {
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- NumCores: 1,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- client := cluster.Cores[0].Client
- core := cluster.Cores[0].Core
-
- params := make(map[string][]string)
- params["type"] = []string{"irrevocable"}
- resp, err := client.Logical().ReadWithData("sys/leases", params)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("response is nil")
- }
-
- if len(resp.Warnings) > 0 {
- t.Errorf("expected no warnings, got: %v", resp.Warnings)
- }
-
- totalLeaseCountRaw, ok := resp.Data["lease_count"]
- if !ok {
- t.Fatalf("expected 'lease_count' response, got: %#v", resp.Data)
- }
-
- totalLeaseCount, err := totalLeaseCountRaw.(json.Number).Int64()
- if err != nil {
- t.Fatalf("error extracting lease count: %v", err)
- }
- if totalLeaseCount != 0 {
- t.Errorf("expected no leases, got %d", totalLeaseCount)
- }
-
- leasesRaw, ok := resp.Data["leases"]
- if !ok {
- t.Fatalf("expected 'leases' response, got %#v", resp.Data)
- }
- leases := leasesRaw.([]interface{})
- if len(leases) != 0 {
- t.Errorf("expected no mounts with leases, got %#v", leases)
- }
-
- // test with a low enough number to not give an error without limit set to none
- expectedNumLeases := 50
- expectedCountPerMount, err := core.InjectIrrevocableLeases(namespace.RootContext(nil), expectedNumLeases)
- if err != nil {
- t.Fatal(err)
- }
-
- resp, err = client.Logical().ReadWithData("sys/leases", params)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("response is nil")
- }
-
- if len(resp.Warnings) > 0 {
- t.Errorf("expected no warnings, got: %v", resp.Warnings)
- }
-
- totalLeaseCountRaw, ok = resp.Data["lease_count"]
- if !ok {
- t.Fatalf("expected 'lease_count' response, got: %#v", resp.Data)
- }
-
- totalLeaseCount, err = totalLeaseCountRaw.(json.Number).Int64()
- if err != nil {
- t.Fatalf("error extracting lease count: %v", err)
- }
- if totalLeaseCount != int64(expectedNumLeases) {
- t.Errorf("expected %d leases, got %d", expectedNumLeases, totalLeaseCount)
- }
-
- leasesRaw, ok = resp.Data["leases"]
- if !ok {
- t.Fatalf("expected 'leases' response, got %#v", resp.Data)
- }
-
- leases = leasesRaw.([]interface{})
- countPerMount := make(map[string]int)
- for _, leaseRaw := range leases {
- lease := leaseRaw.(map[string]interface{})
- mount := lease["mount_id"].(string)
-
- if _, ok := countPerMount[mount]; !ok {
- countPerMount[mount] = 0
- }
-
- countPerMount[mount]++
- }
-
- if !reflect.DeepEqual(countPerMount, expectedCountPerMount) {
- t.Errorf("bad mount count. expected %v, got %v", expectedCountPerMount, countPerMount)
- }
-}
-
-func TestExpiration_irrevocableLeaseListAPI_includeAll(t *testing.T) {
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- NumCores: 1,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- client := cluster.Cores[0].Client
- core := cluster.Cores[0].Core
-
- // test with a low enough number to not give an error with the default limit
- expectedNumLeases := vault.MaxIrrevocableLeasesToReturn + 50
- expectedCountPerMount, err := core.InjectIrrevocableLeases(namespace.RootContext(nil), expectedNumLeases)
- if err != nil {
- t.Fatal(err)
- }
-
- params := make(map[string][]string)
- params["type"] = []string{"irrevocable"}
-
- resp, err := client.Logical().ReadWithData("sys/leases", params)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if resp == nil {
- t.Fatal("unexpected nil response")
- }
-
- if len(resp.Warnings) != 1 {
- t.Errorf("expected one warning (%q), got: %v", vault.MaxIrrevocableLeasesWarning, resp.Warnings)
- }
-
- // now try it with the no limit on return size - we expect no errors and many results
- params["limit"] = []string{"none"}
- resp, err = client.Logical().ReadWithData("sys/leases", params)
- if err != nil {
- t.Fatalf("unexpected error when using limit=none: %v", err)
- }
- if resp == nil {
- t.Fatal("response is nil")
- }
-
- if len(resp.Warnings) > 0 {
- t.Errorf("expected no warnings, got: %v", resp.Warnings)
- }
-
- totalLeaseCountRaw, ok := resp.Data["lease_count"]
- if !ok {
- t.Fatalf("expected 'lease_count' response, got: %#v", resp.Data)
- }
-
- totalLeaseCount, err := totalLeaseCountRaw.(json.Number).Int64()
- if err != nil {
- t.Fatalf("error extracting lease count: %v", err)
- }
- if totalLeaseCount != int64(expectedNumLeases) {
- t.Errorf("expected %d leases, got %d", expectedNumLeases, totalLeaseCount)
- }
-
- leasesRaw, ok := resp.Data["leases"]
- if !ok {
- t.Fatalf("expected 'leases' response, got %#v", resp.Data)
- }
-
- leases := leasesRaw.([]interface{})
- countPerMount := make(map[string]int)
- for _, leaseRaw := range leases {
- lease := leaseRaw.(map[string]interface{})
- mount := lease["mount_id"].(string)
-
- if _, ok := countPerMount[mount]; !ok {
- countPerMount[mount] = 0
- }
-
- countPerMount[mount]++
- }
-
- if !reflect.DeepEqual(countPerMount, expectedCountPerMount) {
- t.Errorf("bad mount count. expected %v, got %v", expectedCountPerMount, countPerMount)
- }
-}
diff --git a/vault/external_tests/hcp_link/hcp_link_test.go b/vault/external_tests/hcp_link/hcp_link_test.go
deleted file mode 100644
index c2e264991..000000000
--- a/vault/external_tests/hcp_link/hcp_link_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package hcp_link
-
-import (
- "testing"
-
- scada "github.com/hashicorp/hcp-scada-provider"
- "github.com/hashicorp/vault/vault"
-)
-
-func TestHCPLinkConnected(t *testing.T) {
- cluster := getTestCluster(t, 2)
- defer cluster.Cleanup()
-
- vaultHCPLink, _ := TestClusterWithHCPLinkEnabled(t, cluster, false, false)
- defer vaultHCPLink.Cleanup()
-
- for _, core := range cluster.Cores {
- checkLinkStatus(core.Client, scada.SessionStatusConnected, t)
- }
-}
-
-func TestHCPLinkNotConfigured(t *testing.T) {
- t.Parallel()
- cluster := getTestCluster(t, 2)
- defer cluster.Cleanup()
-
- cluster.Start()
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
-
- for _, core := range cluster.Cores {
- checkLinkStatus(core.Client, "", t)
- }
-}
diff --git a/vault/external_tests/hcp_link/test_helpers.go b/vault/external_tests/hcp_link/test_helpers.go
deleted file mode 100644
index be06fb0c3..000000000
--- a/vault/external_tests/hcp_link/test_helpers.go
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package hcp_link
-
-import (
- "os"
- "testing"
- "time"
-
- sdkResource "github.com/hashicorp/hcp-sdk-go/resource"
- "github.com/hashicorp/vault/api"
- credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/internalshared/configutil"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
- "github.com/hashicorp/vault/vault/hcp_link"
-)
-
-type VaultHCPLinkInstances struct {
- instances []*hcp_link.HCPLinkVault
-}
-
-func NewVaultHCPLinkInstances() *VaultHCPLinkInstances {
- i := &VaultHCPLinkInstances{
- instances: make([]*hcp_link.HCPLinkVault, 0),
- }
-
- return i
-}
-
-func (v *VaultHCPLinkInstances) Cleanup() {
- for _, inst := range v.instances {
- inst.Shutdown()
- }
-}
-
-func getHCPConfig(t *testing.T, clientID, clientSecret string) *configutil.HCPLinkConfig {
- resourceIDRaw, ok := os.LookupEnv("HCP_RESOURCE_ID")
- if !ok {
- t.Skip("failed to find the HCP resource ID")
- }
- res, err := sdkResource.FromString(resourceIDRaw)
- if err != nil {
- t.Fatalf("failed to parse the resource ID, %v", err.Error())
- }
- return &configutil.HCPLinkConfig{
- ResourceIDRaw: resourceIDRaw,
- Resource: &res,
- ClientID: clientID,
- ClientSecret: clientSecret,
- }
-}
-
-func getTestCluster(t *testing.T, numCores int) *vault.TestCluster {
- t.Helper()
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": credUserpass.Factory,
- },
- }
-
- if numCores <= 0 {
- numCores = 1
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- NumCores: numCores,
- })
-
- return cluster
-}
-
-func TestClusterWithHCPLinkEnabled(t *testing.T, cluster *vault.TestCluster, enableAPICap, enablePassthroughCap bool) (*VaultHCPLinkInstances, *configutil.HCPLinkConfig) {
- t.Helper()
- clientID, ok := os.LookupEnv("HCP_CLIENT_ID")
- if !ok {
- t.Skip("HCP client ID not found in env")
- }
- clientSecret, ok := os.LookupEnv("HCP_CLIENT_SECRET")
- if !ok {
- t.Skip("HCP client secret not found in env")
- }
-
- if _, ok := os.LookupEnv("HCP_API_ADDRESS"); !ok {
- t.Skip("failed to find HCP_API_ADDRESS in the environment")
- }
- if _, ok := os.LookupEnv("HCP_SCADA_ADDRESS"); !ok {
- t.Skip("failed to find HCP_SCADA_ADDRESS in the environment")
- }
- if _, ok := os.LookupEnv("HCP_AUTH_URL"); !ok {
- t.Skip("failed to find HCP_AUTH_URL in the environment")
- }
-
- hcpConfig := getHCPConfig(t, clientID, clientSecret)
- if enableAPICap {
- hcpConfig.EnableAPICapability = true
- }
- if enablePassthroughCap {
- hcpConfig.EnablePassThroughCapability = true
- }
- hcpLinkIns := NewVaultHCPLinkInstances()
-
- cluster.Start()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
-
- for _, c := range cluster.Cores {
- logger := c.Logger().Named("hcpLink")
- vaultHCPLink, err := hcp_link.NewHCPLink(hcpConfig, c.Core, logger)
- if err != nil {
- t.Fatalf("failed to start HCP link, %v", err)
- }
- hcpLinkIns.instances = append(hcpLinkIns.instances, vaultHCPLink)
- }
-
- return hcpLinkIns, hcpConfig
-}
-
-func checkLinkStatus(client *api.Client, expectedStatus string, t *testing.T) {
- deadline := time.Now().Add(10 * time.Second)
- var status *api.SealStatusResponse
- var err error
- for time.Now().Before(deadline) {
- status, err = client.Sys().SealStatus()
- if err != nil {
- t.Fatal(err)
- }
- if status.HCPLinkStatus == expectedStatus {
- break
- }
- time.Sleep(500 * time.Millisecond)
- }
-
- if status.HCPLinkStatus != expectedStatus {
- t.Fatalf("HCP link did not behave as expected. expected status %v, actual status %v", expectedStatus, status.HCPLinkStatus)
- }
-}
diff --git a/vault/external_tests/identity/aliases_test.go b/vault/external_tests/identity/aliases_test.go
deleted file mode 100644
index d71e06e68..000000000
--- a/vault/external_tests/identity/aliases_test.go
+++ /dev/null
@@ -1,954 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package identity
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "strings"
- "testing"
-
- "github.com/hashicorp/vault/api"
- auth "github.com/hashicorp/vault/api/auth/userpass"
- "github.com/hashicorp/vault/builtin/credential/github"
- "github.com/hashicorp/vault/builtin/credential/userpass"
- "github.com/hashicorp/vault/helper/testhelpers"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-func TestIdentityStore_ListAlias(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "github": github.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- err := client.Sys().EnableAuthWithOptions("github", &api.EnableAuthOptions{
- Type: "github",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- mounts, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
- var githubAccessor string
- for k, v := range mounts {
- t.Logf("key: %v\nmount: %#v", k, *v)
- if k == "github/" {
- githubAccessor = v.Accessor
- break
- }
- }
- if githubAccessor == "" {
- t.Fatal("did not find github accessor")
- }
-
- resp, err := client.Logical().Write("identity/entity", nil)
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- if resp == nil {
- t.Fatalf("expected a non-nil response")
- }
-
- entityID := resp.Data["id"].(string)
-
- // Create an alias
- resp, err = client.Logical().Write("identity/entity-alias", map[string]interface{}{
- "name": "testaliasname",
- "mount_accessor": githubAccessor,
- })
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- testAliasCanonicalID := resp.Data["canonical_id"].(string)
- testAliasAliasID := resp.Data["id"].(string)
-
- resp, err = client.Logical().Write("identity/entity-alias", map[string]interface{}{
- "name": "entityalias",
- "mount_accessor": githubAccessor,
- "canonical_id": entityID,
- })
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- entityAliasAliasID := resp.Data["id"].(string)
-
- resp, err = client.Logical().List("identity/entity-alias/id")
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- keys := resp.Data["keys"].([]interface{})
- if len(keys) != 2 {
- t.Fatalf("bad: length of alias IDs listed; expected: 2, actual: %d", len(keys))
- }
-
- // Do some due diligence on the key info
- aliasInfoRaw, ok := resp.Data["key_info"]
- if !ok {
- t.Fatal("expected key_info map in response")
- }
- aliasInfo := aliasInfoRaw.(map[string]interface{})
- for _, keyRaw := range keys {
- key := keyRaw.(string)
- infoRaw, ok := aliasInfo[key]
- if !ok {
- t.Fatal("expected key info")
- }
- info := infoRaw.(map[string]interface{})
- currName := "entityalias"
- if info["canonical_id"].(string) == testAliasCanonicalID {
- currName = "testaliasname"
- }
- t.Logf("alias info: %#v", info)
- switch {
- case info["name"].(string) != currName:
- t.Fatalf("bad name: %v", info["name"].(string))
- case info["mount_accessor"].(string) != githubAccessor:
- t.Fatalf("bad mount_path: %v", info["mount_accessor"].(string))
- }
- }
-
- // Now do the same with entity info
- resp, err = client.Logical().List("identity/entity/id")
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- keys = resp.Data["keys"].([]interface{})
- if len(keys) != 2 {
- t.Fatalf("bad: length of entity IDs listed; expected: 2, actual: %d", len(keys))
- }
-
- entityInfoRaw, ok := resp.Data["key_info"]
- if !ok {
- t.Fatal("expected key_info map in response")
- }
-
- // This is basically verifying that the entity has the alias in key_info
- // that we expect to be tied to it, plus tests a value further down in it
- // for fun
- entityInfo := entityInfoRaw.(map[string]interface{})
- for _, keyRaw := range keys {
- key := keyRaw.(string)
- infoRaw, ok := entityInfo[key]
- if !ok {
- t.Fatal("expected key info")
- }
- info := infoRaw.(map[string]interface{})
- t.Logf("entity info: %#v", info)
- currAliasID := entityAliasAliasID
- if key == testAliasCanonicalID {
- currAliasID = testAliasAliasID
- }
- currAliases := info["aliases"].([]interface{})
- if len(currAliases) != 1 {
- t.Fatal("bad aliases length")
- }
- for _, v := range currAliases {
- curr := v.(map[string]interface{})
- switch {
- case curr["id"].(string) != currAliasID:
- t.Fatalf("bad alias id: %v", curr["id"])
- case curr["mount_accessor"].(string) != githubAccessor:
- t.Fatalf("bad mount accessor: %v", curr["mount_accessor"])
- case curr["mount_path"].(string) != "auth/github/":
- t.Fatalf("bad mount path: %v", curr["mount_path"])
- case curr["mount_type"].(string) != "github":
- t.Fatalf("bad mount type: %v", curr["mount_type"])
- }
- }
- }
-}
-
-// TestIdentityStore_RenameAlias_CannotMergeEntity verifies that an error is
-// returned on an attempt to rename an alias to match another alias with the
-// same mount accessor. This used to result in a merge entity.
-func TestIdentityStore_RenameAlias_CannotMergeEntity(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- client := cluster.Cores[0].Client
-
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("auth/userpass/users/bsmith", map[string]interface{}{
- "password": "training",
- })
- if err != nil {
- t.Fatal(err)
- }
- _, err = client.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{
- "password": "training",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- mounts, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
-
- var mountAccessor string
- for k, v := range mounts {
- if k == "userpass/" {
- mountAccessor = v.Accessor
- break
- }
- }
- if mountAccessor == "" {
- t.Fatal("did not find userpass accessor")
- }
-
- // Now create a new unrelated entity and alias
- entityResp, err := client.Logical().Write("identity/entity", map[string]interface{}{
- "name": "bob-smith",
- })
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, entityResp)
- }
- if entityResp == nil {
- t.Fatalf("expected a non-nil response")
- }
-
- aliasResp, err := client.Logical().Write("identity/entity-alias", map[string]interface{}{
- "name": "bob",
- "mount_accessor": mountAccessor,
- })
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, aliasResp)
- }
- aliasID2 := aliasResp.Data["id"].(string)
-
- // Rename this new alias to have the same name as the one implicitly created by our login as bsmith
- _, err = client.Logical().Write("identity/entity-alias/id/"+aliasID2, map[string]interface{}{
- "name": "bsmith",
- })
- if err == nil {
- t.Fatal("expected rename over existing entity to fail")
- }
-}
-
-func TestIdentityStore_MergeEntities_FailsDueToClash(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- client := cluster.Cores[0].Client
-
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("auth/userpass/users/bob", map[string]interface{}{
- "password": "training",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- mounts, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
-
- var mountAccessor string
- for k, v := range mounts {
- if k == "userpass/" {
- mountAccessor = v.Accessor
- break
- }
- }
- if mountAccessor == "" {
- t.Fatal("did not find userpass accessor")
- }
-
- _, entityIdBob, aliasIdBob := testhelpers.CreateEntityAndAlias(t, client, mountAccessor, "bob-smith", "bob")
-
- // Create userpass login for alice
- _, err = client.Logical().Write("auth/userpass/users/alice", map[string]interface{}{
- "password": "training",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, entityIdAlice, aliasIdAlice := testhelpers.CreateEntityAndAlias(t, client, mountAccessor, "alice-smith", "alice")
-
- // Perform entity merge
- mergeResp, err := client.Logical().Write("identity/entity/merge", map[string]interface{}{
- "to_entity_id": entityIdBob,
- "from_entity_ids": entityIdAlice,
- })
- if err == nil {
- t.Fatalf("Expected error upon merge. Resp:%#v", mergeResp)
- }
- if !strings.Contains(err.Error(), "toEntity and at least one fromEntity have aliases with the same mount accessor") {
- t.Fatalf("Error was not due to conflicting alias mount accessors. Error: %v", err)
- }
- if !strings.Contains(err.Error(), entityIdAlice) {
- t.Fatalf("Did not identify alice's entity (%s) as conflicting. Error: %v", entityIdAlice, err)
- }
- if !strings.Contains(err.Error(), entityIdBob) {
- t.Fatalf("Did not identify bob's entity (%s) as conflicting. Error: %v", entityIdBob, err)
- }
- if !strings.Contains(err.Error(), aliasIdAlice) {
- t.Fatalf("Did not identify alice's alias (%s) as conflicting. Error: %v", aliasIdAlice, err)
- }
- if !strings.Contains(err.Error(), aliasIdBob) {
- t.Fatalf("Did not identify bob's alias (%s) as conflicting. Error: %v", aliasIdBob, err)
- }
- if !strings.Contains(err.Error(), mountAccessor) {
- t.Fatalf("Did not identify mount accessor %s as being reason for conflict. Error: %v", mountAccessor, err)
- }
-}
-
-func TestIdentityStore_MergeEntities_FailsDueToClashInFromEntities(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- "github": github.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- client := cluster.Cores[0].Client
-
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- err = client.Sys().EnableAuthWithOptions("github", &api.EnableAuthOptions{
- Type: "github",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("auth/userpass/users/bob", map[string]interface{}{
- "password": "training",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- mounts, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
-
- var mountAccessor string
- for k, v := range mounts {
- if k == "userpass/" {
- mountAccessor = v.Accessor
- break
- }
- }
- if mountAccessor == "" {
- t.Fatal("did not find userpass accessor")
- }
-
- var mountAccessorGitHub string
- for k, v := range mounts {
- if k == "github/" {
- mountAccessorGitHub = v.Accessor
- break
- }
- }
- if mountAccessorGitHub == "" {
- t.Fatal("did not find github accessor")
- }
-
- _, entityIdBob, _ := testhelpers.CreateEntityAndAlias(t, client, mountAccessor, "bob-smith", "bob")
- _, entityIdAlice, _ := testhelpers.CreateEntityAndAlias(t, client, mountAccessorGitHub, "alice-smith", "alice")
- _, entityIdClara, _ := testhelpers.CreateEntityAndAlias(t, client, mountAccessorGitHub, "clara-smith", "clara")
-
- // Perform entity merge
- mergeResp, err := client.Logical().Write("identity/entity/merge", map[string]interface{}{
- "to_entity_id": entityIdBob,
- "from_entity_ids": []string{entityIdAlice, entityIdClara},
- })
- if err == nil {
- t.Fatalf("Expected error upon merge. Resp:%#v", mergeResp)
- }
- if !strings.Contains(err.Error(), fmt.Sprintf("mount accessor %s found in multiple fromEntities, merge should be done with one fromEntity at a time", mountAccessorGitHub)) {
- t.Fatalf("Error was not due to conflicting alias mount accessors in fromEntities. Error: %v", err)
- }
-}
-
-func TestIdentityStore_MergeEntities_FailsDueToDoubleClash(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- "github": github.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- client := cluster.Cores[0].Client
-
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- err = client.Sys().EnableAuthWithOptions("github", &api.EnableAuthOptions{
- Type: "github",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("auth/userpass/users/bob", map[string]interface{}{
- "password": "training",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("auth/userpass/users/bob-github", map[string]interface{}{
- "password": "training",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- mounts, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
-
- var mountAccessor string
- for k, v := range mounts {
- if k == "userpass/" {
- mountAccessor = v.Accessor
- break
- }
- }
- if mountAccessor == "" {
- t.Fatal("did not find userpass accessor")
- }
-
- var mountAccessorGitHub string
- for k, v := range mounts {
- if k == "github/" {
- mountAccessorGitHub = v.Accessor
- break
- }
- }
- if mountAccessorGitHub == "" {
- t.Fatal("did not find github accessor")
- }
-
- _, entityIdBob, aliasIdBob := testhelpers.CreateEntityAndAlias(t, client, mountAccessor, "bob-smith", "bob")
-
- aliasResp, err := client.Logical().Write("identity/entity-alias", map[string]interface{}{
- "name": "bob-github",
- "canonical_id": entityIdBob,
- "mount_accessor": mountAccessorGitHub,
- })
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, aliasResp)
- }
-
- aliasIdBobGitHub := aliasResp.Data["id"].(string)
- if aliasIdBobGitHub == "" {
- t.Fatal("Alias ID not present in response")
- }
-
- // Create userpass login for alice
- _, err = client.Logical().Write("auth/userpass/users/alice", map[string]interface{}{
- "password": "training",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, entityIdAlice, aliasIdAlice := testhelpers.CreateEntityAndAlias(t, client, mountAccessor, "alice-smith", "alice")
- _, entityIdClara, aliasIdClara := testhelpers.CreateEntityAndAlias(t, client, mountAccessorGitHub, "clara-smith", "clara")
-
- // Perform entity merge
- mergeResp, err := client.Logical().Write("identity/entity/merge", map[string]interface{}{
- "to_entity_id": entityIdBob,
- "from_entity_ids": []string{entityIdAlice, entityIdClara},
- })
- if err == nil {
- t.Fatalf("Expected error upon merge. Resp:%#v", mergeResp)
- }
- if mergeResp != nil {
- t.Fatalf("Response was non-nil. Resp:%#v", mergeResp)
- }
- if !strings.Contains(err.Error(), "toEntity and at least one fromEntity have aliases with the same mount accessor") {
- t.Fatalf("Error was not due to conflicting alias mount accessors. Error: %v", err)
- }
- if !strings.Contains(err.Error(), entityIdAlice) {
- t.Fatalf("Did not identify alice's entity (%s) as conflicting. Error: %v", entityIdAlice, err)
- }
- if !strings.Contains(err.Error(), entityIdBob) {
- t.Fatalf("Did not identify bob's entity (%s) as conflicting. Error: %v", entityIdBob, err)
- }
- if !strings.Contains(err.Error(), entityIdClara) {
- t.Fatalf("Did not identify clara's alias (%s) as conflicting. Error: %v", entityIdClara, err)
- }
- if !strings.Contains(err.Error(), aliasIdAlice) {
- t.Fatalf("Did not identify alice's alias (%s) as conflicting. Error: %v", aliasIdAlice, err)
- }
- if !strings.Contains(err.Error(), aliasIdBob) {
- t.Fatalf("Did not identify bob's alias (%s) as conflicting. Error: %v", aliasIdBob, err)
- }
- if !strings.Contains(err.Error(), aliasIdClara) {
- t.Fatalf("Did not identify bob's alias (%s) as conflicting. Error: %v", aliasIdClara, err)
- }
- if !strings.Contains(err.Error(), mountAccessor) {
- t.Fatalf("Did not identify mount accessor %s as being reason for conflict. Error: %v", mountAccessor, err)
- }
- if !strings.Contains(err.Error(), mountAccessorGitHub) {
- t.Fatalf("Did not identify mount accessor %s as being reason for conflict. Error: %v", mountAccessorGitHub, err)
- }
-}
-
-func TestIdentityStore_MergeEntities_FailsDueToClashInFromEntities_CheckRawRequest(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- client := cluster.Cores[0].Client
-
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("auth/userpass/users/bob", map[string]interface{}{
- "password": "training",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- mounts, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
-
- var mountAccessor string
- for k, v := range mounts {
- if k == "userpass/" {
- mountAccessor = v.Accessor
- break
- }
- }
- if mountAccessor == "" {
- t.Fatal("did not find userpass accessor")
- }
-
- _, entityIdBob, _ := testhelpers.CreateEntityAndAlias(t, client, mountAccessor, "bob-smith", "bob")
-
- // Create userpass login for alice
- _, err = client.Logical().Write("auth/userpass/users/alice", map[string]interface{}{
- "password": "training",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, entityIdAlice, _ := testhelpers.CreateEntityAndAlias(t, client, mountAccessor, "alice-smith", "alice")
-
- // Perform entity merge as a Raw Request so we can investigate the response body
- req := client.NewRequest("POST", "/v1/identity/entity/merge")
- req.SetJSONBody(map[string]interface{}{
- "to_entity_id": entityIdBob,
- "from_entity_ids": []string{entityIdAlice},
- })
-
- resp, err := client.RawRequest(req)
- if err == nil {
- t.Fatalf("Expected error but did not get one. Response: %v", resp)
- }
-
- bodyBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- t.Fatal(err)
- }
-
- bodyString := string(bodyBytes)
-
- if resp.StatusCode != 400 {
- t.Fatal("Incorrect status code for response")
- }
-
- var mapOutput map[string]interface{}
- if err = json.Unmarshal([]byte(bodyString), &mapOutput); err != nil {
- t.Fatal(err)
- }
-
- errorStrings, ok := mapOutput["errors"].([]interface{})
- if !ok {
- t.Fatalf("error not present in response - full response: %s", bodyString)
- }
-
- if len(errorStrings) != 1 {
- t.Fatalf("Incorrect number of errors in response - full response: %s", bodyString)
- }
-
- errorString, ok := errorStrings[0].(string)
- if !ok {
- t.Fatalf("error not present in response - full response: %s", bodyString)
- }
-
- if !strings.Contains(errorString, "toEntity and at least one fromEntity have aliases with the same mount accessor") {
- t.Fatalf("Error was not due to conflicting alias mount accessors. Error: %s", errorString)
- }
-
- dataArray, ok := mapOutput["data"].([]interface{})
- if !ok {
- t.Fatalf("data not present in response - full response: %s", bodyString)
- }
-
- if len(dataArray) != 2 {
- t.Fatalf("Incorrect amount of clash data in response - full response: %s", bodyString)
- }
-
- for _, data := range dataArray {
- dataMap, ok := data.(map[string]interface{})
- if !ok {
- t.Fatalf("data could not be understood - full response: %s", bodyString)
- }
-
- entityId, ok := dataMap["entity_id"].(string)
- if !ok {
- t.Fatalf("entity_id not present in data - full response: %s", bodyString)
- }
-
- if entityId != entityIdBob && entityId != entityIdAlice {
- t.Fatalf("entityId not bob or alice - full response: %s", bodyString)
- }
-
- entity, ok := dataMap["entity"].(string)
- if !ok {
- t.Fatalf("entity not present in data - full response: %s", bodyString)
- }
-
- if entity != "bob-smith" && entity != "alice-smith" {
- t.Fatalf("entity not bob or alice - full response: %s", bodyString)
- }
-
- alias, ok := dataMap["alias"].(string)
- if !ok {
- t.Fatalf("alias not present in data - full response: %s", bodyString)
- }
-
- if alias != "bob" && alias != "alice" {
- t.Fatalf("alias not bob or alice - full response: %s", bodyString)
- }
-
- mountPath, ok := dataMap["mount_path"].(string)
- if !ok {
- t.Fatalf("mountPath not present in data - full response: %s", bodyString)
- }
-
- if mountPath != "auth/userpass/" {
- t.Fatalf("mountPath not auth/userpass/ - full response: %s", bodyString)
- }
-
- mount, ok := dataMap["mount"].(string)
- if !ok {
- t.Fatalf("mount not present in data - full response: %s", bodyString)
- }
-
- if mount != "userpass" {
- t.Fatalf("mount not userpass - full response: %s", bodyString)
- }
- }
-}
-
-func TestIdentityStore_MergeEntities_SameMountAccessor_ThenUseAlias(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- client := cluster.Cores[0].Client
-
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("auth/userpass/users/bob", map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatal(err)
- }
- _, err = client.Logical().Write("auth/userpass/login/bob", map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- mounts, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
-
- var mountAccessor string
- for k, v := range mounts {
- if k == "userpass/" {
- mountAccessor = v.Accessor
- break
- }
- }
- if mountAccessor == "" {
- t.Fatal("did not find userpass accessor")
- }
-
- _, entityIdBob, aliasIdBob := testhelpers.CreateEntityAndAlias(t, client, mountAccessor, "bob-smith", "bob")
-
- // Create userpass login for alice
- _, err = client.Logical().Write("auth/userpass/users/alice", map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatal(err)
- }
- _, err = client.Logical().Write("auth/userpass/login/alice", map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, entityIdAlice, _ := testhelpers.CreateEntityAndAlias(t, client, mountAccessor, "alice-smith", "alice")
-
- // Try and login with alias 2 (alice) pre-merge
- userpassAuth, err := auth.NewUserpassAuth("alice", &auth.Password{FromString: "testpassword"})
- if err != nil {
- t.Fatal(err)
- }
- loginResp, err := client.Logical().Write("auth/userpass/login/alice", map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, loginResp)
- }
- if loginResp.Auth == nil {
- t.Fatalf("Request auth is nil, something has gone wrong - resp:%#v", loginResp)
- }
- loginEntityId := loginResp.Auth.EntityID
- if loginEntityId != entityIdAlice {
- t.Fatalf("Login entity ID is not Alice. loginEntityId:%s aliceEntityId:%s", loginEntityId, entityIdAlice)
- }
-
- // Perform entity merge
- mergeResp, err := client.Logical().Write("identity/entity/merge", map[string]interface{}{
- "to_entity_id": entityIdBob,
- "from_entity_ids": entityIdAlice,
- "conflicting_alias_ids_to_keep": aliasIdBob,
- })
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, mergeResp)
- }
-
- // Delete entity id 1 (bob)
- deleteResp, err := client.Logical().Delete(fmt.Sprintf("identity/entity/id/%s", entityIdBob))
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, deleteResp)
- }
-
- // Try and login with alias 2 (alice) post-merge
- // Notably, this login method sets the client token, which is why we didn't use it above
- loginResp, err = client.Auth().Login(context.Background(), userpassAuth)
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, loginResp)
- }
- if loginResp.Auth == nil {
- t.Fatalf("Request auth is nil, something has gone wrong - resp:%#v", loginResp)
- }
- if loginEntityId != entityIdAlice {
- t.Fatalf("Login entity ID is not Alice. loginEntityId:%s aliceEntityId:%s", loginEntityId, entityIdAlice)
- }
-}
-
-func TestIdentityStore_MergeEntities_FailsDueToMultipleClashMergesAttempted(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- "github": github.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- client := cluster.Cores[0].Client
-
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- err = client.Sys().EnableAuthWithOptions("github", &api.EnableAuthOptions{
- Type: "github",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("auth/userpass/users/bob", map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("auth/userpass/users/bob-github", map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- mounts, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
-
- var mountAccessor string
- for k, v := range mounts {
- if k == "userpass/" {
- mountAccessor = v.Accessor
- break
- }
- }
- if mountAccessor == "" {
- t.Fatal("did not find userpass accessor")
- }
-
- var mountAccessorGitHub string
- for k, v := range mounts {
- if k == "github/" {
- mountAccessorGitHub = v.Accessor
- break
- }
- }
- if mountAccessorGitHub == "" {
- t.Fatal("did not find github accessor")
- }
-
- _, entityIdBob, _ := testhelpers.CreateEntityAndAlias(t, client, mountAccessor, "bob-smith", "bob")
- aliasResp, err := client.Logical().Write("identity/entity-alias", map[string]interface{}{
- "name": "bob-github",
- "canonical_id": entityIdBob,
- "mount_accessor": mountAccessorGitHub,
- })
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, aliasResp)
- }
-
- aliasIdBobGitHub := aliasResp.Data["id"].(string)
- if aliasIdBobGitHub == "" {
- t.Fatal("Alias ID not present in response")
- }
-
- // Create userpass login for alice
- _, err = client.Logical().Write("auth/userpass/users/alice", map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, entityIdAlice, aliasIdAlice := testhelpers.CreateEntityAndAlias(t, client, mountAccessor, "alice-smith", "alice")
- _, entityIdClara, aliasIdClara := testhelpers.CreateEntityAndAlias(t, client, mountAccessorGitHub, "clara-smith", "alice")
-
- // Perform entity merge
- mergeResp, err := client.Logical().Write("identity/entity/merge", map[string]interface{}{
- "to_entity_id": entityIdBob,
- "from_entity_ids": []string{entityIdAlice, entityIdClara},
- "conflicting_alias_ids_to_keep": []string{aliasIdAlice, aliasIdClara},
- })
- if err == nil {
- t.Fatalf("Expected error upon merge. Resp:%#v", mergeResp)
- }
- if !strings.Contains(err.Error(), "merge one entity at a time") {
- t.Fatalf("did not error for the right reason. Error: %v", err)
- }
-}
diff --git a/vault/external_tests/identity/entities_test.go b/vault/external_tests/identity/entities_test.go
deleted file mode 100644
index 8e69705ba..000000000
--- a/vault/external_tests/identity/entities_test.go
+++ /dev/null
@@ -1,381 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package identity
-
-import (
- "strings"
- "testing"
-
- "github.com/hashicorp/go-secure-stdlib/strutil"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/builtin/credential/approle"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-func TestIdentityStore_EntityDisabled(t *testing.T) {
- // Use a TestCluster and the approle backend to get a token and entity for testing
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "approle": approle.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // Mount the auth backend
- err := client.Sys().EnableAuthWithOptions("approle", &api.EnableAuthOptions{
- Type: "approle",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Tune the mount
- err = client.Sys().TuneMount("auth/approle", api.MountConfigInput{
- DefaultLeaseTTL: "5m",
- MaxLeaseTTL: "5m",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create role
- resp, err := client.Logical().Write("auth/approle/role/role-period", map[string]interface{}{
- "period": "5m",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Get role_id
- resp, err = client.Logical().Read("auth/approle/role/role-period/role-id")
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for fetching the role-id")
- }
- roleID := resp.Data["role_id"]
-
- // Get secret_id
- resp, err = client.Logical().Write("auth/approle/role/role-period/secret-id", map[string]interface{}{})
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for fetching the secret-id")
- }
- secretID := resp.Data["secret_id"]
-
- // Login
- resp, err = client.Logical().Write("auth/approle/login", map[string]interface{}{
- "role_id": roleID,
- "secret_id": secretID,
- })
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for login")
- }
- if resp.Auth == nil {
- t.Fatal("expected auth object from response")
- }
- if resp.Auth.ClientToken == "" {
- t.Fatal("expected a client token")
- }
-
- roleToken := resp.Auth.ClientToken
-
- client.SetToken(roleToken)
- resp, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for token lookup")
- }
- entityIDRaw, ok := resp.Data["entity_id"]
- if !ok {
- t.Fatal("expected an entity ID")
- }
- entityID, ok := entityIDRaw.(string)
- if !ok {
- t.Fatal("entity_id not a string")
- }
-
- client.SetToken(cluster.RootToken)
- resp, err = client.Logical().Write("identity/entity/id/"+entityID, map[string]interface{}{
- "disabled": true,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // This call should now fail
- client.SetToken(roleToken)
- resp, err = client.Auth().Token().LookupSelf()
- if err == nil {
- t.Fatalf("expected error, got %#v", *resp)
- }
- if !strings.Contains(err.Error(), logical.ErrPermissionDenied.Error()) {
- t.Fatalf("expected to see entity disabled error, got %v", err)
- }
-
- // Attempting to get a new token should also now fail
- client.SetToken("")
- resp, err = client.Logical().Write("auth/approle/login", map[string]interface{}{
- "role_id": roleID,
- "secret_id": secretID,
- })
- if err == nil {
- t.Fatalf("expected error, got %#v", *resp)
- }
- if !strings.Contains(err.Error(), logical.ErrPermissionDenied.Error()) {
- t.Fatalf("expected to see entity disabled error, got %v", err)
- }
-
- client.SetToken(cluster.RootToken)
- resp, err = client.Logical().Write("identity/entity/id/"+entityID, map[string]interface{}{
- "disabled": false,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- client.SetToken(roleToken)
- resp, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
-
- // Getting a new token should now work again too
- client.SetToken("")
- resp, err = client.Logical().Write("auth/approle/login", map[string]interface{}{
- "role_id": roleID,
- "secret_id": secretID,
- })
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for login")
- }
- if resp.Auth == nil {
- t.Fatal("expected auth object from response")
- }
- if resp.Auth.ClientToken == "" {
- t.Fatal("expected a client token")
- }
-}
-
-func TestIdentityStore_EntityPoliciesInInitialAuth(t *testing.T) {
- // Use a TestCluster and the approle backend to get a token and entity for testing
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "approle": approle.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // Mount the auth backend
- err := client.Sys().EnableAuthWithOptions("approle", &api.EnableAuthOptions{
- Type: "approle",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Tune the mount
- err = client.Sys().TuneMount("auth/approle", api.MountConfigInput{
- DefaultLeaseTTL: "5m",
- MaxLeaseTTL: "5m",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create role
- resp, err := client.Logical().Write("auth/approle/role/role-period", map[string]interface{}{
- "period": "5m",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Get role_id
- resp, err = client.Logical().Read("auth/approle/role/role-period/role-id")
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for fetching the role-id")
- }
- roleID := resp.Data["role_id"]
-
- // Get secret_id
- resp, err = client.Logical().Write("auth/approle/role/role-period/secret-id", map[string]interface{}{})
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for fetching the secret-id")
- }
- secretID := resp.Data["secret_id"]
-
- // Login
- resp, err = client.Logical().Write("auth/approle/login", map[string]interface{}{
- "role_id": roleID,
- "secret_id": secretID,
- })
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for login")
- }
- if resp.Auth == nil {
- t.Fatal("expected auth object from response")
- }
- if resp.Auth.ClientToken == "" {
- t.Fatal("expected a client token")
- }
- if !strutil.EquivalentSlices(resp.Auth.TokenPolicies, []string{"default"}) {
- t.Fatalf("policy mismatch, got token policies: %v", resp.Auth.TokenPolicies)
- }
- if len(resp.Auth.IdentityPolicies) > 0 {
- t.Fatalf("policy mismatch, got identity policies: %v", resp.Auth.IdentityPolicies)
- }
- if !strutil.EquivalentSlices(resp.Auth.Policies, []string{"default"}) {
- t.Fatalf("policy mismatch, got policies: %v", resp.Auth.Policies)
- }
-
- // Check policies
- client.SetToken(resp.Auth.ClientToken)
- resp, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for token lookup")
- }
- entityIDRaw, ok := resp.Data["entity_id"]
- if !ok {
- t.Fatal("expected an entity ID")
- }
- entityID, ok := entityIDRaw.(string)
- if !ok {
- t.Fatal("entity_id not a string")
- }
- policiesRaw := resp.Data["policies"]
- if policiesRaw == nil {
- t.Fatal("expected policies, got nil")
- }
- var policies []string
- for _, v := range policiesRaw.([]interface{}) {
- policies = append(policies, v.(string))
- }
- policiesRaw = resp.Data["identity_policies"]
- if policiesRaw != nil {
- t.Fatalf("expected nil policies, got %#v", policiesRaw)
- }
- if !strutil.EquivalentSlices(policies, []string{"default"}) {
- t.Fatalf("policy mismatch, got policies: %v", resp.Auth.Policies)
- }
-
- // Write more policies into the entity
- client.SetToken(cluster.RootToken)
- resp, err = client.Logical().Write("identity/entity/id/"+entityID, map[string]interface{}{
- "policies": []string{"foo", "bar"},
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Reauthenticate to get a token with updated policies
- client.SetToken("")
- resp, err = client.Logical().Write("auth/approle/login", map[string]interface{}{
- "role_id": roleID,
- "secret_id": secretID,
- })
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for login")
- }
- if resp.Auth == nil {
- t.Fatal("expected auth object from response")
- }
- if resp.Auth.ClientToken == "" {
- t.Fatal("expected a client token")
- }
- if !strutil.EquivalentSlices(resp.Auth.TokenPolicies, []string{"default"}) {
- t.Fatalf("policy mismatch, got token policies: %v", resp.Auth.TokenPolicies)
- }
- if !strutil.EquivalentSlices(resp.Auth.IdentityPolicies, []string{"foo", "bar"}) {
- t.Fatalf("policy mismatch, got identity policies: %v", resp.Auth.IdentityPolicies)
- }
- if !strutil.EquivalentSlices(resp.Auth.Policies, []string{"default", "foo", "bar"}) {
- t.Fatalf("policy mismatch, got policies: %v", resp.Auth.Policies)
- }
-
- // Validate the policies on lookup again -- this ensures that the right
- // policies were encoded on the token but all were looked up successfully
- client.SetToken(resp.Auth.ClientToken)
- resp, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for token lookup")
- }
- entityIDRaw, ok = resp.Data["entity_id"]
- if !ok {
- t.Fatal("expected an entity ID")
- }
- entityID, ok = entityIDRaw.(string)
- if !ok {
- t.Fatal("entity_id not a string")
- }
- policies = nil
- policiesRaw = resp.Data["policies"]
- if policiesRaw == nil {
- t.Fatal("expected policies, got nil")
- }
- for _, v := range policiesRaw.([]interface{}) {
- policies = append(policies, v.(string))
- }
- if !strutil.EquivalentSlices(policies, []string{"default"}) {
- t.Fatalf("policy mismatch, got policies: %v", policies)
- }
- policies = nil
- policiesRaw = resp.Data["identity_policies"]
- if policiesRaw == nil {
- t.Fatal("expected policies, got nil")
- }
- for _, v := range policiesRaw.([]interface{}) {
- policies = append(policies, v.(string))
- }
- if !strutil.EquivalentSlices(policies, []string{"foo", "bar"}) {
- t.Fatalf("policy mismatch, got policies: %v", policies)
- }
-}
diff --git a/vault/external_tests/identity/group_aliases_test.go b/vault/external_tests/identity/group_aliases_test.go
deleted file mode 100644
index b2a7670d5..000000000
--- a/vault/external_tests/identity/group_aliases_test.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package identity
-
-import (
- "testing"
-
- "github.com/hashicorp/vault/api"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-
- credLdap "github.com/hashicorp/vault/builtin/credential/ldap"
-)
-
-func TestIdentityStore_GroupAliasLocalMount(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "ldap": credLdap.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // Create a local auth mount
- err := client.Sys().EnableAuthWithOptions("ldap", &api.EnableAuthOptions{
- Type: "ldap",
- Local: true,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Extract out the mount accessor for LDAP auth
- auths, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
- ldapMountAccessor := auths["ldap/"].Accessor
-
- // Create an external group
- secret, err := client.Logical().Write("identity/group", map[string]interface{}{
- "type": "external",
- })
- if err != nil {
- t.Fatal(err)
- }
- groupID := secret.Data["id"].(string)
-
- // Attempt to create a group alias against a local mount should fail
- secret, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
- "name": "testuser",
- "mount_accessor": ldapMountAccessor,
- "canonical_id": groupID,
- })
- if err == nil {
- t.Fatalf("expected error since mount is local")
- }
-}
diff --git a/vault/external_tests/identity/groups_test.go b/vault/external_tests/identity/groups_test.go
deleted file mode 100644
index 5beaf93ae..000000000
--- a/vault/external_tests/identity/groups_test.go
+++ /dev/null
@@ -1,386 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package identity
-
-import (
- "testing"
-
- "github.com/hashicorp/vault/api"
- ldaphelper "github.com/hashicorp/vault/helper/testhelpers/ldap"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-
- "github.com/hashicorp/vault/builtin/credential/github"
- credLdap "github.com/hashicorp/vault/builtin/credential/ldap"
-)
-
-func TestIdentityStore_ListGroupAlias(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "github": github.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- err := client.Sys().EnableAuthWithOptions("github", &api.EnableAuthOptions{
- Type: "github",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- mounts, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
- var githubAccessor string
- for k, v := range mounts {
- t.Logf("key: %v\nmount: %#v", k, *v)
- if k == "github/" {
- githubAccessor = v.Accessor
- break
- }
- }
- if githubAccessor == "" {
- t.Fatal("did not find github accessor")
- }
-
- resp, err := client.Logical().Write("identity/group", map[string]interface{}{
- "type": "external",
- })
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- groupID := resp.Data["id"].(string)
-
- resp, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
- "name": "groupalias",
- "mount_accessor": githubAccessor,
- "canonical_id": groupID,
- })
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- aliasID := resp.Data["id"].(string)
-
- resp, err = client.Logical().List("identity/group-alias/id")
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- keys := resp.Data["keys"].([]interface{})
- if len(keys) != 1 {
- t.Fatalf("bad: length of alias IDs listed; expected: 1, actual: %d", len(keys))
- }
-
- // Do some due diligence on the key info
- aliasInfoRaw, ok := resp.Data["key_info"]
- if !ok {
- t.Fatal("expected key_info map in response")
- }
- aliasInfo := aliasInfoRaw.(map[string]interface{})
- if len(aliasInfo) != 1 {
- t.Fatalf("bad: length of alias ID key info; expected: 1, actual: %d", len(aliasInfo))
- }
-
- infoRaw, ok := aliasInfo[aliasID]
- if !ok {
- t.Fatal("expected to find alias ID in key info map")
- }
- info := infoRaw.(map[string]interface{})
- t.Logf("alias info: %#v", info)
- switch {
- case info["name"].(string) != "groupalias":
- t.Fatalf("bad name: %v", info["name"].(string))
- case info["mount_accessor"].(string) != githubAccessor:
- t.Fatalf("bad mount_accessor: %v", info["mount_accessor"].(string))
- }
-
- // Now do the same with group info
- resp, err = client.Logical().List("identity/group/id")
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- keys = resp.Data["keys"].([]interface{})
- if len(keys) != 1 {
- t.Fatalf("bad: length of group IDs listed; expected: 1, actual: %d", len(keys))
- }
-
- groupInfoRaw, ok := resp.Data["key_info"]
- if !ok {
- t.Fatal("expected key_info map in response")
- }
-
- // This is basically verifying that the group has the alias in key_info
- // that we expect to be tied to it, plus tests a value further down in it
- // for fun
- groupInfo := groupInfoRaw.(map[string]interface{})
- if len(groupInfo) != 1 {
- t.Fatalf("bad: length of group ID key info; expected: 1, actual: %d", len(groupInfo))
- }
-
- infoRaw, ok = groupInfo[groupID]
- if !ok {
- t.Fatal("expected key info")
- }
- info = infoRaw.(map[string]interface{})
- t.Logf("group info: %#v", info)
- alias := info["alias"].(map[string]interface{})
- switch {
- case alias["id"].(string) != aliasID:
- t.Fatalf("bad alias id: %v", alias["id"])
- case alias["mount_accessor"].(string) != githubAccessor:
- t.Fatalf("bad mount accessor: %v", alias["mount_accessor"])
- case alias["mount_path"].(string) != "auth/github/":
- t.Fatalf("bad mount path: %v", alias["mount_path"])
- case alias["mount_type"].(string) != "github":
- t.Fatalf("bad mount type: %v", alias["mount_type"])
- }
-}
-
-// Testing the fix for GH-4351
-func TestIdentityStore_ExternalGroupMembershipsAcrossMounts(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "ldap": credLdap.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // Enable the first LDAP auth
- err := client.Sys().EnableAuthWithOptions("ldap", &api.EnableAuthOptions{
- Type: "ldap",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Extract out the mount accessor for LDAP auth
- auths, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
- ldapMountAccessor1 := auths["ldap/"].Accessor
-
- cleanup, cfg := ldaphelper.PrepareTestContainer(t, "latest")
- defer cleanup()
-
- // Configure LDAP auth
- secret, err := client.Logical().Write("auth/ldap/config", map[string]interface{}{
- "url": cfg.Url,
- "userattr": cfg.UserAttr,
- "userdn": cfg.UserDN,
- "groupdn": cfg.GroupDN,
- "groupattr": cfg.GroupAttr,
- "binddn": cfg.BindDN,
- "bindpass": cfg.BindPassword,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create a group in LDAP auth
- _, err = client.Logical().Write("auth/ldap/groups/testgroup1", map[string]interface{}{
- "policies": "testgroup1-policy",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Tie the group to a user
- _, err = client.Logical().Write("auth/ldap/users/hermes conrad", map[string]interface{}{
- "policies": "default",
- "groups": "testgroup1",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create an external group
- secret, err = client.Logical().Write("identity/group", map[string]interface{}{
- "type": "external",
- })
- if err != nil {
- t.Fatal(err)
- }
- ldapExtGroupID1 := secret.Data["id"].(string)
-
- // Associate a group from LDAP auth as a group-alias in the external group
- _, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
- "name": "testgroup1",
- "mount_accessor": ldapMountAccessor1,
- "canonical_id": ldapExtGroupID1,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Login using LDAP
- secret, err = client.Logical().Write("auth/ldap/login/hermes conrad", map[string]interface{}{
- "password": "hermes",
- })
- if err != nil {
- t.Fatal(err)
- }
- ldapClientToken := secret.Auth.ClientToken
-
- //
- // By now, the entity ID of the token should be automatically added as a
- // member in the external group.
- //
-
- // Extract the entity ID of the token
- secret, err = client.Logical().Write("auth/token/lookup", map[string]interface{}{
- "token": ldapClientToken,
- })
- if err != nil {
- t.Fatal(err)
- }
- entityID := secret.Data["entity_id"].(string)
-
- // Enable another LDAP auth mount
- err = client.Sys().EnableAuthWithOptions("ldap2", &api.EnableAuthOptions{
- Type: "ldap",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Extract the mount accessor
- auths, err = client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
- ldapMountAccessor2 := auths["ldap2/"].Accessor
-
- // Create an entity-alias asserting that the user "hermes conrad" from the first
- // and second LDAP mounts as the same.
- _, err = client.Logical().Write("identity/entity-alias", map[string]interface{}{
- "name": "hermes conrad",
- "mount_accessor": ldapMountAccessor2,
- "canonical_id": entityID,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- cleanup2, cfg2 := ldaphelper.PrepareTestContainer(t, "latest")
- defer cleanup2()
-
- // Configure LDAP auth
- secret, err = client.Logical().Write("auth/ldap2/config", map[string]interface{}{
- "url": cfg2.Url,
- "userattr": cfg2.UserAttr,
- "userdn": cfg2.UserDN,
- "groupdn": cfg2.GroupDN,
- "groupattr": cfg2.GroupAttr,
- "binddn": cfg2.BindDN,
- "bindpass": cfg2.BindPassword,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create a group in second LDAP auth
- _, err = client.Logical().Write("auth/ldap2/groups/testgroup2", map[string]interface{}{
- "policies": "testgroup2-policy",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create a user in second LDAP auth
- _, err = client.Logical().Write("auth/ldap2/users/hermes conrad", map[string]interface{}{
- "policies": "default",
- "groups": "testgroup2",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create another external group
- secret, err = client.Logical().Write("identity/group", map[string]interface{}{
- "type": "external",
- })
- if err != nil {
- t.Fatal(err)
- }
- ldapExtGroupID2 := secret.Data["id"].(string)
-
- // Create a group-alias tying the external group to "testgroup2" group in second LDAP
- _, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
- "name": "testgroup2",
- "mount_accessor": ldapMountAccessor2,
- "canonical_id": ldapExtGroupID2,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Login using second LDAP
- _, err = client.Logical().Write("auth/ldap2/login/hermes conrad", map[string]interface{}{
- "password": "hermes",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- //
- // By now the same entity ID of the token from first LDAP should have been
- // added as a member of the second external group.
- //
-
- // Check that entityID is present in both the external groups
- secret, err = client.Logical().Read("identity/group/id/" + ldapExtGroupID1)
- if err != nil {
- t.Fatal(err)
- }
- extGroup1Entities := secret.Data["member_entity_ids"].([]interface{})
-
- found := false
- for _, item := range extGroup1Entities {
- if item.(string) == entityID {
- found = true
- break
- }
- }
- if !found {
- t.Fatalf("missing entity ID %q first external group with ID %q", entityID, ldapExtGroupID1)
- }
-
- secret, err = client.Logical().Read("identity/group/id/" + ldapExtGroupID2)
- if err != nil {
- t.Fatal(err)
- }
- extGroup2Entities := secret.Data["member_entity_ids"].([]interface{})
- found = false
- for _, item := range extGroup2Entities {
- if item.(string) == entityID {
- found = true
- break
- }
- }
- if !found {
- t.Fatalf("missing entity ID %q first external group with ID %q", entityID, ldapExtGroupID2)
- }
-}
diff --git a/vault/external_tests/identity/identity_test.go b/vault/external_tests/identity/identity_test.go
deleted file mode 100644
index 292b2c30a..000000000
--- a/vault/external_tests/identity/identity_test.go
+++ /dev/null
@@ -1,699 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package identity
-
-import (
- "fmt"
- "testing"
-
- "github.com/hashicorp/go-secure-stdlib/strutil"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/sdk/helper/ldaputil"
- "github.com/hashicorp/vault/sdk/logical"
-
- "github.com/stretchr/testify/require"
-
- "github.com/hashicorp/vault/helper/testhelpers/teststorage"
-
- "github.com/go-ldap/ldap/v3"
- log "github.com/hashicorp/go-hclog"
- ldapcred "github.com/hashicorp/vault/builtin/credential/ldap"
- "github.com/hashicorp/vault/helper/namespace"
- ldaphelper "github.com/hashicorp/vault/helper/testhelpers/ldap"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/vault"
-)
-
-func TestIdentityStore_ExternalGroupMemberships_DifferentMounts(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "ldap": ldapcred.Factory,
- },
- }
- conf, opts := teststorage.ClusterSetup(coreConfig, nil, nil)
- cluster := vault.NewTestCluster(t, conf, opts)
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- client := cluster.Cores[0].Client
- vault.TestWaitActive(t, core)
-
- // Create a entity
- secret, err := client.Logical().Write("identity/entity", map[string]interface{}{
- "name": "testentityname",
- })
- require.NoError(t, err)
- entityID := secret.Data["id"].(string)
-
- cleanup, config1 := ldaphelper.PrepareTestContainer(t, "latest")
- defer cleanup()
-
- cleanup2, config2 := ldaphelper.PrepareTestContainer(t, "latest")
- defer cleanup2()
-
- setupFunc := func(path string, cfg *ldaputil.ConfigEntry) string {
- // Create an external group
- resp, err := client.Logical().Write("identity/group", map[string]interface{}{
- "type": "external",
- "name": path + "ldap_admin_staff",
- "policies": []string{"admin-policy"},
- })
- require.NoError(t, err)
- require.NotNil(t, resp)
- require.NotNil(t, resp.Data)
- groupID := resp.Data["id"].(string)
-
- // Enable LDAP mount in Vault
- err = client.Sys().EnableAuthWithOptions(path, &api.EnableAuthOptions{
- Type: "ldap",
- })
- require.NoError(t, err)
-
- // Take out its accessor
- auth, err := client.Sys().ListAuth()
- require.NoError(t, err)
- accessor := auth[path+"/"].Accessor
- require.NotEmpty(t, accessor)
-
- // Create an external group alias
- resp, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
- "name": "admin_staff",
- "canonical_id": groupID,
- "mount_accessor": accessor,
- })
- require.NoError(t, err)
-
- // Create a user in Vault
- _, err = client.Logical().Write("auth/"+path+"/users/hermes conrad", map[string]interface{}{
- "password": "hermes",
- })
- require.NoError(t, err)
-
- // Create an entity alias
- client.Logical().Write("identity/entity-alias", map[string]interface{}{
- "name": "hermes conrad",
- "canonical_id": entityID,
- "mount_accessor": accessor,
- })
-
- // Configure LDAP auth
- secret, err = client.Logical().Write("auth/"+path+"/config", map[string]interface{}{
- "url": cfg.Url,
- "userattr": cfg.UserAttr,
- "userdn": cfg.UserDN,
- "groupdn": cfg.GroupDN,
- "groupattr": cfg.GroupAttr,
- "binddn": cfg.BindDN,
- "bindpass": cfg.BindPassword,
- })
- require.NoError(t, err)
-
- secret, err = client.Logical().Write("auth/"+path+"/login/hermes conrad", map[string]interface{}{
- "password": "hermes",
- })
- require.NoError(t, err)
-
- policies, err := secret.TokenPolicies()
- require.NoError(t, err)
- require.Contains(t, policies, "admin-policy")
-
- secret, err = client.Logical().Read("identity/group/id/" + groupID)
- require.NoError(t, err)
- require.Contains(t, secret.Data["member_entity_ids"], entityID)
-
- return groupID
- }
- groupID1 := setupFunc("ldap", config1)
- groupID2 := setupFunc("ldap2", config2)
-
- // Remove hermes conrad from admin_staff group
- removeLdapGroupMember(t, config1, "admin_staff", "hermes conrad")
- secret, err = client.Logical().Write("auth/ldap/login/hermes conrad", map[string]interface{}{
- "password": "hermes",
- })
- require.NoError(t, err)
-
- secret, err = client.Logical().Read("identity/group/id/" + groupID1)
- require.NoError(t, err)
- require.NotContains(t, secret.Data["member_entity_ids"], entityID)
-
- secret, err = client.Logical().Read("identity/group/id/" + groupID2)
- require.NoError(t, err)
- require.Contains(t, secret.Data["member_entity_ids"], entityID)
-}
-
-func TestIdentityStore_Integ_GroupAliases(t *testing.T) {
- t.Parallel()
-
- var err error
- coreConfig := &vault.CoreConfig{
- DisableMlock: true,
- DisableCache: true,
- Logger: log.NewNullLogger(),
- CredentialBackends: map[string]logical.Factory{
- "ldap": ldapcred.Factory,
- },
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- cores := cluster.Cores
-
- vault.TestWaitActive(t, cores[0].Core)
-
- client := cores[0].Client
-
- err = client.Sys().EnableAuthWithOptions("ldap", &api.EnableAuthOptions{
- Type: "ldap",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- auth, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
-
- accessor := auth["ldap/"].Accessor
-
- secret, err := client.Logical().Write("identity/group", map[string]interface{}{
- "type": "external",
- "name": "ldap_ship_crew",
- })
- if err != nil {
- t.Fatal(err)
- }
- shipCrewGroupID := secret.Data["id"].(string)
-
- secret, err = client.Logical().Write("identity/group", map[string]interface{}{
- "type": "external",
- "name": "ldap_admin_staff",
- })
- if err != nil {
- t.Fatal(err)
- }
- adminStaffGroupID := secret.Data["id"].(string)
-
- secret, err = client.Logical().Write("identity/group", map[string]interface{}{
- "type": "external",
- "name": "ldap_devops",
- })
- if err != nil {
- t.Fatal(err)
- }
- devopsGroupID := secret.Data["id"].(string)
-
- secret, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
- "name": "ship_crew",
- "canonical_id": shipCrewGroupID,
- "mount_accessor": accessor,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- secret, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
- "name": "admin_staff",
- "canonical_id": adminStaffGroupID,
- "mount_accessor": accessor,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- secret, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
- "name": "devops",
- "canonical_id": devopsGroupID,
- "mount_accessor": accessor,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- secret, err = client.Logical().Read("identity/group/id/" + shipCrewGroupID)
- if err != nil {
- t.Fatal(err)
- }
- aliasMap := secret.Data["alias"].(map[string]interface{})
- if aliasMap["canonical_id"] != shipCrewGroupID ||
- aliasMap["name"] != "ship_crew" ||
- aliasMap["mount_accessor"] != accessor {
- t.Fatalf("bad: group alias: %#v\n", aliasMap)
- }
-
- secret, err = client.Logical().Read("identity/group/id/" + adminStaffGroupID)
- if err != nil {
- t.Fatal(err)
- }
- aliasMap = secret.Data["alias"].(map[string]interface{})
- if aliasMap["canonical_id"] != adminStaffGroupID ||
- aliasMap["name"] != "admin_staff" ||
- aliasMap["mount_accessor"] != accessor {
- t.Fatalf("bad: group alias: %#v\n", aliasMap)
- }
-
- cleanup, cfg := ldaphelper.PrepareTestContainer(t, "latest")
- defer cleanup()
-
- // Configure LDAP auth
- secret, err = client.Logical().Write("auth/ldap/config", map[string]interface{}{
- "url": cfg.Url,
- "userattr": cfg.UserAttr,
- "userdn": cfg.UserDN,
- "groupdn": cfg.GroupDN,
- "groupattr": cfg.GroupAttr,
- "binddn": cfg.BindDN,
- "bindpass": cfg.BindPassword,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create a local group in LDAP backend
- secret, err = client.Logical().Write("auth/ldap/groups/devops", map[string]interface{}{
- "policies": "default",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create a local group in LDAP backend
- secret, err = client.Logical().Write("auth/ldap/groups/engineers", map[string]interface{}{
- "policies": "default",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create a local user in LDAP
- secret, err = client.Logical().Write("auth/ldap/users/hermes conrad", map[string]interface{}{
- "policies": "default",
- "groups": "engineers,devops",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Login with LDAP and create a token
- secret, err = client.Logical().Write("auth/ldap/login/hermes conrad", map[string]interface{}{
- "password": "hermes",
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- // Lookup the token to get the entity ID
- secret, err = client.Auth().Token().Lookup(token)
- if err != nil {
- t.Fatal(err)
- }
- entityID := secret.Data["entity_id"].(string)
-
- // Re-read the admin_staff, ship_crew and devops group. This entity ID should have
- // been added to admin_staff but not ship_crew.
- assertMember(t, client, entityID, "ship_crew", shipCrewGroupID, false)
- assertMember(t, client, entityID, "admin_staff", adminStaffGroupID, true)
- assertMember(t, client, entityID, "devops", devopsGroupID, true)
- assertMember(t, client, entityID, "engineer", devopsGroupID, true)
-
- // Now add Hermes to ship_crew
- addLdapGroupMember(t, cfg, "ship_crew", "hermes conrad")
-
- // Re-login with LDAP
- secret, err = client.Logical().Write("auth/ldap/login/hermes conrad", map[string]interface{}{
- "password": "hermes",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Hermes should now be in ship_crew external group
- assertMember(t, client, entityID, "ship_crew", shipCrewGroupID, true)
- assertMember(t, client, entityID, "admin_staff", adminStaffGroupID, true)
- assertMember(t, client, entityID, "devops", devopsGroupID, true)
- assertMember(t, client, entityID, "engineer", devopsGroupID, true)
-
- identityStore := cores[0].IdentityStore()
-
- group, err := identityStore.MemDBGroupByID(shipCrewGroupID, true)
- if err != nil {
- t.Fatal(err)
- }
-
- // Remove its member entities
- group.MemberEntityIDs = nil
-
- ctx := namespace.RootContext(nil)
-
- err = identityStore.UpsertGroup(ctx, group, true)
- if err != nil {
- t.Fatal(err)
- }
-
- group, err = identityStore.MemDBGroupByID(shipCrewGroupID, true)
- if err != nil {
- t.Fatal(err)
- }
- if group.MemberEntityIDs != nil {
- t.Fatalf("failed to remove entity ID from the group")
- }
-
- group, err = identityStore.MemDBGroupByID(adminStaffGroupID, true)
- if err != nil {
- t.Fatal(err)
- }
-
- // Remove its member entities
- group.MemberEntityIDs = nil
-
- err = identityStore.UpsertGroup(ctx, group, true)
- if err != nil {
- t.Fatal(err)
- }
-
- group, err = identityStore.MemDBGroupByID(adminStaffGroupID, true)
- if err != nil {
- t.Fatal(err)
- }
- if group.MemberEntityIDs != nil {
- t.Fatalf("failed to remove entity ID from the group")
- }
-
- group, err = identityStore.MemDBGroupByID(devopsGroupID, true)
- if err != nil {
- t.Fatal(err)
- }
-
- // Remove its member entities
- group.MemberEntityIDs = nil
-
- err = identityStore.UpsertGroup(ctx, group, true)
- if err != nil {
- t.Fatal(err)
- }
-
- group, err = identityStore.MemDBGroupByID(devopsGroupID, true)
- if err != nil {
- t.Fatal(err)
- }
- if group.MemberEntityIDs != nil {
- t.Fatalf("failed to remove entity ID from the group")
- }
-
- _, err = client.Auth().Token().Renew(token, 0)
- if err != nil {
- t.Fatal(err)
- }
-
- assertMember(t, client, entityID, "ship_crew", shipCrewGroupID, true)
- assertMember(t, client, entityID, "admin_staff", adminStaffGroupID, true)
- assertMember(t, client, entityID, "devops", devopsGroupID, true)
- assertMember(t, client, entityID, "engineer", devopsGroupID, true)
-
- // Remove user hermes conrad from the devops group in LDAP backend
- secret, err = client.Logical().Write("auth/ldap/users/hermes conrad", map[string]interface{}{
- "policies": "default",
- "groups": "engineers",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Renewing the token now should remove its entity ID from the devops
- // group
- _, err = client.Auth().Token().Renew(token, 0)
- if err != nil {
- t.Fatal(err)
- }
-
- group, err = identityStore.MemDBGroupByID(devopsGroupID, true)
- if err != nil {
- t.Fatal(err)
- }
- if group.MemberEntityIDs != nil {
- t.Fatalf("failed to remove entity ID from the group")
- }
-}
-
-func TestIdentityStore_Integ_RemoveFromExternalGroup(t *testing.T) {
- t.Parallel()
- var err error
- coreConfig := &vault.CoreConfig{
- DisableMlock: true,
- DisableCache: true,
- Logger: log.NewNullLogger(),
- CredentialBackends: map[string]logical.Factory{
- "ldap": ldapcred.Factory,
- },
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- cores := cluster.Cores
- client := cores[0].Client
-
- err = client.Sys().EnableAuthWithOptions("ldap", &api.EnableAuthOptions{
- Type: "ldap",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- auth, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
-
- accessor := auth["ldap/"].Accessor
-
- adminPolicy := "admin_policy"
- secret, err := client.Logical().Write("identity/group", map[string]interface{}{
- "type": "external",
- "name": "ldap_admin_staff",
- "policies": []string{adminPolicy},
- })
- if err != nil {
- t.Fatal(err)
- }
- adminStaffGroupID := secret.Data["id"].(string)
- adminGroupName := "admin_staff"
-
- secret, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
- "name": adminGroupName,
- "canonical_id": adminStaffGroupID,
- "mount_accessor": accessor,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- secret, err = client.Logical().Read("identity/group/id/" + adminStaffGroupID)
- if err != nil {
- t.Fatal(err)
- }
- aliasMap := secret.Data["alias"].(map[string]interface{})
- if aliasMap["canonical_id"] != adminStaffGroupID ||
- aliasMap["name"] != adminGroupName ||
- aliasMap["mount_accessor"] != accessor {
- t.Fatalf("bad: group alias: %#v\n", aliasMap)
- }
-
- cleanup, cfg := ldaphelper.PrepareTestContainer(t, "latest")
- defer cleanup()
-
- // Configure LDAP auth
- secret, err = client.Logical().Write("auth/ldap/config", map[string]interface{}{
- "url": cfg.Url,
- "userattr": cfg.UserAttr,
- "userdn": cfg.UserDN,
- "groupdn": cfg.GroupDN,
- "groupattr": cfg.GroupAttr,
- "binddn": cfg.BindDN,
- "bindpass": cfg.BindPassword,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create a local user in LDAP
- secret, err = client.Logical().Write("auth/ldap/users/hermes conrad", map[string]interface{}{
- "policies": "default",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Login with LDAP and create a token
- secret, err = client.Logical().Write("auth/ldap/login/hermes conrad", map[string]interface{}{
- "password": "hermes",
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
- tokenPolicies, err := secret.TokenPolicies()
- if err != nil {
- t.Fatal(err)
- }
- if !strutil.StrListContains(tokenPolicies, adminPolicy) {
- t.Fatalf("expected token policies to contain %s, got: %v", adminPolicy, tokenPolicies)
- }
-
- // Lookup the token to get the entity ID
- secret, err = client.Auth().Token().Lookup(token)
- if err != nil {
- t.Fatal(err)
- }
- entityID := secret.Data["entity_id"].(string)
-
- assertMember(t, client, entityID, adminGroupName, adminStaffGroupID, true)
-
- // Now remove Hermes from admin_staff
- removeLdapGroupMember(t, cfg, adminGroupName, "hermes conrad")
-
- // Re-login with LDAP
- secret, err = client.Logical().Write("auth/ldap/login/hermes conrad", map[string]interface{}{
- "password": "hermes",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Hermes should now be out of admin_staff group
- assertMember(t, client, entityID, adminGroupName, adminStaffGroupID, false)
- tokenPolicies, err = secret.TokenPolicies()
- if err != nil {
- t.Fatal(err)
- }
- if strutil.StrListContains(tokenPolicies, adminPolicy) {
- t.Fatalf("expected token policies to not contain %s, got: %v", adminPolicy, tokenPolicies)
- }
-
- // Add Hermes back to admin_staff
- addLdapGroupMember(t, cfg, adminGroupName, "hermes conrad")
-
- // Re-login with LDAP
- secret, err = client.Logical().Write("auth/ldap/login/hermes conrad", map[string]interface{}{
- "password": "hermes",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Hermes should now be back in admin_staff group
- assertMember(t, client, entityID, adminGroupName, adminStaffGroupID, true)
- tokenPolicies, err = secret.TokenPolicies()
- if err != nil {
- t.Fatal(err)
- }
- if !strutil.StrListContains(tokenPolicies, adminPolicy) {
- t.Fatalf("expected token policies to contain %s, got: %v", adminPolicy, tokenPolicies)
- }
-
- // Remove Hermes from admin_staff once again
- removeLdapGroupMember(t, cfg, adminGroupName, "hermes conrad")
-
- oldToken := client.Token()
- client.SetToken(secret.Auth.ClientToken)
- secret, err = client.Auth().Token().RenewSelf(1)
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(oldToken)
- assertMember(t, client, entityID, adminGroupName, adminStaffGroupID, false)
- tokenPolicies, err = secret.TokenPolicies()
- if err != nil {
- t.Fatal(err)
- }
- if strutil.StrListContains(tokenPolicies, adminPolicy) {
- t.Fatalf("expected token policies to not contain %s, got: %v", adminPolicy, tokenPolicies)
- }
-}
-
-func assertMember(t *testing.T, client *api.Client, entityID, groupName, groupID string, expectFound bool) {
- t.Helper()
- secret, err := client.Logical().Read("identity/group/id/" + groupID)
- if err != nil {
- t.Fatal(err)
- }
- groupMap := secret.Data
-
- groupEntityMembers, ok := groupMap["member_entity_ids"].([]interface{})
- if !ok && expectFound {
- t.Fatalf("expected member_entity_ids not to be nil")
- }
-
- // if type assertion fails and expectFound is false, groupEntityMembers
- // is nil, then let's just return, nothing to be done!
- if !ok && !expectFound {
- return
- }
-
- found := false
- for _, entityIDRaw := range groupEntityMembers {
- if entityIDRaw.(string) == entityID {
- found = true
- }
- }
- if found != expectFound {
- negation := ""
- if !expectFound {
- negation = "not "
- }
- t.Fatalf("expected entity ID %q to %sbe part of %q group", entityID, negation, groupName)
- }
-}
-
-func removeLdapGroupMember(t *testing.T, cfg *ldaputil.ConfigEntry, groupCN, userCN string) {
- userDN := fmt.Sprintf("cn=%s,ou=people,dc=planetexpress,dc=com", userCN)
- groupDN := fmt.Sprintf("cn=%s,ou=people,dc=planetexpress,dc=com", groupCN)
- ldapreq := ldap.ModifyRequest{DN: groupDN}
- ldapreq.Delete("member", []string{userDN})
- addRemoveLdapGroupMember(t, cfg, userCN, &ldapreq)
-}
-
-func addLdapGroupMember(t *testing.T, cfg *ldaputil.ConfigEntry, groupCN, userCN string) {
- userDN := fmt.Sprintf("cn=%s,ou=people,dc=planetexpress,dc=com", userCN)
- groupDN := fmt.Sprintf("cn=%s,ou=people,dc=planetexpress,dc=com", groupCN)
- ldapreq := ldap.ModifyRequest{DN: groupDN}
- ldapreq.Add("member", []string{userDN})
- addRemoveLdapGroupMember(t, cfg, userCN, &ldapreq)
-}
-
-func addRemoveLdapGroupMember(t *testing.T, cfg *ldaputil.ConfigEntry, userCN string, req *ldap.ModifyRequest) {
- logger := log.New(nil)
- ldapClient := ldaputil.Client{LDAP: ldaputil.NewLDAP(), Logger: logger}
- // LDAP server won't accept changes unless we connect with TLS. This
- // isn't the default config returned by PrepareTestContainer because
- // the Vault LDAP backend won't work with it, even with InsecureTLS,
- // because the ServerName should be planetexpress.com and not localhost.
- conn, err := ldapClient.DialLDAP(cfg)
- if err != nil {
- t.Fatal(err)
- }
- defer conn.Close()
-
- err = conn.Bind(cfg.BindDN, cfg.BindPassword)
- if err != nil {
- t.Fatal(err)
- }
-
- err = conn.Modify(req)
- if err != nil {
- t.Fatal(err)
- }
-}
diff --git a/vault/external_tests/identity/login_mfa_totp_test.go b/vault/external_tests/identity/login_mfa_totp_test.go
deleted file mode 100644
index 4fc22e627..000000000
--- a/vault/external_tests/identity/login_mfa_totp_test.go
+++ /dev/null
@@ -1,330 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package identity
-
-import (
- "context"
- "encoding/base64"
- "fmt"
- "strings"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/api"
- upAuth "github.com/hashicorp/vault/api/auth/userpass"
- "github.com/hashicorp/vault/audit"
- "github.com/hashicorp/vault/builtin/credential/userpass"
- "github.com/hashicorp/vault/builtin/logical/totp"
- "github.com/hashicorp/vault/helper/testhelpers"
- "github.com/hashicorp/vault/helper/testhelpers/corehelpers"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-func doTwoPhaseLogin(t *testing.T, client *api.Client, totpCodePath, methodID, username string) {
- t.Helper()
- totpPasscode := testhelpers.GetTOTPCodeFromEngine(t, client, totpCodePath)
-
- upMethod, err := upAuth.NewUserpassAuth(username, &upAuth.Password{FromString: "testpassword"})
-
- mfaSecret, err := client.Auth().MFALogin(context.Background(), upMethod)
- if err != nil {
- t.Fatalf("failed to login with userpass auth method: %v", err)
- }
-
- secret, err := client.Auth().MFAValidate(
- context.Background(),
- mfaSecret,
- map[string]interface{}{
- methodID: []string{totpPasscode},
- },
- )
- if err != nil {
- t.Fatalf("MFA validation failed: %v", err)
- }
-
- if secret == nil || secret.Auth == nil || secret.Auth.ClientToken == "" {
- t.Fatalf("MFA validation failed to return a ClientToken in secret: %v", secret)
- }
-}
-
-func TestLoginMfaGenerateTOTPTestAuditIncluded(t *testing.T) {
- noop := corehelpers.TestNoopAudit(t, nil)
-
- cluster := vault.NewTestCluster(t, &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- },
- LogicalBackends: map[string]logical.Factory{
- "totp": totp.Factory,
- },
- AuditBackends: map[string]audit.Factory{
- "noop": func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
- return noop, nil
- },
- },
- },
- &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- client := cluster.Cores[0].Client
-
- // Enable the audit backend
- if err := client.Sys().EnableAuditWithOptions("noop", &api.EnableAuditOptions{Type: "noop"}); err != nil {
- t.Fatal(err)
- }
-
- testhelpers.SetupTOTPMount(t, client)
- mountAccessor := testhelpers.SetupUserpassMountAccessor(t, client)
-
- // Get a random set of chars to seed our entity and alias names
- userseed := base64.StdEncoding.EncodeToString([]byte("couple of test users"))
- entity1 := userseed[0:3]
- testuser1 := userseed[3:6]
- entity2 := userseed[6:9]
- testuser2 := userseed[9:12]
-
- // Creating two users in the userpass auth mount
- userClient1, entityID1, _ := testhelpers.CreateEntityAndAlias(t, client, mountAccessor, entity1, testuser1)
- userClient2, entityID2, _ := testhelpers.CreateEntityAndAlias(t, client, mountAccessor, entity2, testuser2)
- waitPeriod := 5
- totpConfig := map[string]interface{}{
- "issuer": "yCorp",
- "period": waitPeriod,
- "algorithm": "SHA1",
- "digits": 6,
- "skew": 1,
- "key_size": 10,
- "qr_size": 100,
- "max_validation_attempts": 3,
- "method_name": "foo",
- }
-
- methodID := testhelpers.SetupTOTPMethod(t, client, totpConfig)
-
- // registering EntityIDs in the TOTP secret Engine for MethodID
- enginePath1 := testhelpers.RegisterEntityInTOTPEngine(t, client, entityID1, methodID)
- enginePath2 := testhelpers.RegisterEntityInTOTPEngine(t, client, entityID2, methodID)
-
- // Configure a default login enforcement
- enforcementConfig := map[string]interface{}{
- "auth_method_types": []string{"userpass"},
- "name": methodID[0:4],
- "mfa_method_ids": []string{methodID},
- }
-
- testhelpers.SetupMFALoginEnforcement(t, client, enforcementConfig)
-
- userpassPath := fmt.Sprintf("auth/userpass/login/%s", testuser1)
-
- // MFA single-phase login
- verifyLoginRequest := func(secret *api.Secret) {
- userpassToken := secret.Auth.ClientToken
- userClient1.SetToken(client.Token())
- secret, err := userClient1.Logical().WriteWithContext(context.Background(), "auth/token/lookup", map[string]interface{}{
- "token": userpassToken,
- })
- if err != nil {
- t.Fatalf("failed to lookup userpass authenticated token: %v", err)
- }
-
- entityIDCheck := secret.Data["entity_id"].(string)
- if entityIDCheck != entityID1 {
- t.Fatalf("different entityID assigned")
- }
- }
-
- // helper function to clear the MFA request header
- clearMFARequestHeaders := func(c *api.Client) {
- headers := c.Headers()
- headers.Del("X-Vault-MFA")
- c.SetHeaders(headers)
- }
-
- var secret *api.Secret
- var err error
- var methodIdentifier string
-
- singlePhaseLoginFunc := func() error {
- totpPasscode := testhelpers.GetTOTPCodeFromEngine(t, client, enginePath1)
- userClient1.AddHeader("X-Vault-MFA", fmt.Sprintf("%s:%s", methodIdentifier, totpPasscode))
- defer clearMFARequestHeaders(userClient1)
- secret, err = userClient1.Logical().WriteWithContext(context.Background(), userpassPath, map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- return fmt.Errorf("MFA failed for identifier %s: %v", methodIdentifier, err)
- }
- return nil
- }
-
- // single phase login for both method name and method ID
- methodIdentifier = totpConfig["method_name"].(string)
- testhelpers.RetryUntilAtCadence(t, 20*time.Second, 100*time.Millisecond, singlePhaseLoginFunc)
- verifyLoginRequest(secret)
-
- methodIdentifier = methodID
- // Need to wait a bit longer to avoid hitting maximum allowed consecutive
- // failed TOTP validation
- testhelpers.RetryUntilAtCadence(t, 20*time.Second, time.Duration(waitPeriod)*time.Second, singlePhaseLoginFunc)
- verifyLoginRequest(secret)
-
- // Two-phase login
- secret, err = userClient1.Logical().WriteWithContext(context.Background(), userpassPath, map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatalf("MFA failed: %v", err)
- }
-
- if len(secret.Warnings) == 0 || !strings.Contains(strings.Join(secret.Warnings, ""), "A login request was issued that is subject to MFA validation") {
- t.Fatalf("first phase of login did not have a warning")
- }
-
- if secret.Auth == nil || secret.Auth.MFARequirement == nil {
- t.Fatalf("two phase login returned nil MFARequirement")
- }
- if secret.Auth.MFARequirement.MFARequestID == "" {
- t.Fatalf("MFARequirement contains empty MFARequestID")
- }
- if secret.Auth.MFARequirement.MFAConstraints == nil || len(secret.Auth.MFARequirement.MFAConstraints) == 0 {
- t.Fatalf("MFAConstraints is nil or empty")
- }
- mfaConstraints, ok := secret.Auth.MFARequirement.MFAConstraints[methodID[0:4]]
- if !ok {
- t.Fatalf("failed to find the mfaConstrains")
- }
- if mfaConstraints.Any == nil || len(mfaConstraints.Any) == 0 {
- t.Fatalf("expected to see the methodID is enforced in MFAConstaint.Any")
- }
- for _, mfaAny := range mfaConstraints.Any {
- if mfaAny.ID != methodID || mfaAny.Type != "totp" || !mfaAny.UsesPasscode {
- t.Fatalf("Invalid mfa constraints")
- }
- }
-
- // validation
- var mfaReqID string
- var totpPasscode1 string
- mfaValidateFunc := func() error {
- totpPasscode1 = testhelpers.GetTOTPCodeFromEngine(t, client, enginePath1)
- secret, err = userClient1.Logical().WriteWithContext(context.Background(), "sys/mfa/validate", map[string]interface{}{
- "mfa_request_id": mfaReqID,
- "mfa_payload": map[string][]string{
- methodIdentifier: {totpPasscode1},
- },
- })
- if err != nil {
- return fmt.Errorf("MFA failed: %v", err)
- }
- if secret.Auth == nil || secret.Auth.ClientToken == "" {
- t.Fatalf("successful mfa validation did not return a client token")
- }
-
- return nil
- }
-
- methodIdentifier = methodID
- mfaReqID = secret.Auth.MFARequirement.MFARequestID
- testhelpers.RetryUntilAtCadence(t, 20*time.Second, time.Duration(waitPeriod)*time.Second, mfaValidateFunc)
-
- // two phase login with method name
- secret, err = userClient1.Logical().WriteWithContext(context.Background(), userpassPath, map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatalf("MFA failed: %v", err)
- }
-
- methodIdentifier = totpConfig["method_name"].(string)
- mfaReqID = secret.Auth.MFARequirement.MFARequestID
- testhelpers.RetryUntilAtCadence(t, 20*time.Second, time.Duration(waitPeriod)*time.Second, mfaValidateFunc)
-
- // checking audit log
- if noop.Req == nil {
- t.Fatalf("no request was logged in audit log")
- }
- var found bool
- for _, req := range noop.Req {
- if req.Path == "sys/mfa/validate" {
- found = true
- break
- }
- }
- if !found {
- t.Fatalf("mfa/validate was not logged in audit log")
- }
-
- // check for login request expiration
- secret, err = userClient1.Logical().WriteWithContext(context.Background(), userpassPath, map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatalf("MFA failed: %v", err)
- }
-
- if secret.Auth == nil || secret.Auth.MFARequirement == nil {
- t.Fatalf("two phase login returned nil MFARequirement")
- }
-
- _, err = userClient1.Logical().WriteWithContext(context.Background(), "sys/mfa/validate", map[string]interface{}{
- "mfa_request_id": secret.Auth.MFARequirement.MFARequestID,
- "mfa_payload": map[string][]string{
- methodID: {totpPasscode1},
- },
- })
- if err == nil {
- t.Fatalf("MFA succeeded with an already used passcode")
- }
- if !strings.Contains(err.Error(), "code already used") {
- t.Fatalf("got: %+v, expected: code already used", err.Error())
- }
-
- // check for reaching max failed validation requests
- secret, err = userClient1.Logical().WriteWithContext(context.Background(), userpassPath, map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatalf("MFA failed: %v", err)
- }
-
- var maxErr error
- maxAttempts := 6
- i := 0
- for i = 0; i < maxAttempts; i++ {
- _, maxErr = userClient1.Logical().WriteWithContext(context.Background(), "sys/mfa/validate", map[string]interface{}{
- "mfa_request_id": secret.Auth.MFARequirement.MFARequestID,
- "mfa_payload": map[string][]string{
- methodID: {fmt.Sprintf("%d", i)},
- },
- })
- if maxErr == nil {
- t.Fatalf("MFA succeeded with an invalid passcode")
- }
- }
- if !strings.Contains(maxErr.Error(), "maximum TOTP validation attempts") {
- t.Fatalf("unexpected error message when exceeding max failed validation attempts: %s", maxErr.Error())
- }
-
- // let's make sure the configID is not blocked for other users
- doTwoPhaseLogin(t, userClient2, enginePath2, methodID, testuser2)
-
- // let's see if user1 is able to login after 5 seconds
- time.Sleep(5 * time.Second)
- doTwoPhaseLogin(t, userClient1, enginePath1, methodID, testuser1)
-
- // Destroy the secret so that the token can self generate
- _, err = client.Logical().WriteWithContext(context.Background(), fmt.Sprintf("identity/mfa/method/totp/admin-destroy"), map[string]interface{}{
- "entity_id": entityID1,
- "method_id": methodID,
- })
- if err != nil {
- t.Fatalf("failed to destroy the MFA secret: %s", err)
- }
-}
diff --git a/vault/external_tests/identity/oidc_provider_test.go b/vault/external_tests/identity/oidc_provider_test.go
deleted file mode 100644
index a360a5f09..000000000
--- a/vault/external_tests/identity/oidc_provider_test.go
+++ /dev/null
@@ -1,988 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package identity
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "net/http"
- "net/url"
- "strconv"
- "strings"
- "testing"
- "time"
-
- "github.com/hashicorp/cap/oidc"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/builtin/credential/userpass"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
- "github.com/stretchr/testify/require"
-)
-
-const (
- testPassword = "testpassword"
- testRedirectURI = "https://127.0.0.1:8251/callback"
- testGroupScopeTemplate = `
- {
- "groups": {{identity.entity.groups.names}}
- }
- `
- testUserScopeTemplate = `
- {
- "username": {{identity.entity.aliases.%s.name}},
- "contact": {
- "email": {{identity.entity.metadata.email}},
- "phone_number": {{identity.entity.metadata.phone_number}}
- }
- }
- `
-)
-
-// TestOIDC_Auth_Code_Flow_Default_Resources tests the authorization
-// code flow using the default OIDC provider, default key, and allow_all
-// assignment. This ensures that the resources are created and usable with
-// an initial setup of Vault.
-func TestOIDC_Auth_Code_Flow_Default_Resources(t *testing.T) {
- cluster := setupOIDCTestCluster(t, 2)
- defer cluster.Cleanup()
- active := cluster.Cores[0].Client
- standby := cluster.Cores[1].Client
-
- // Enable userpass auth and create a user
- err := active.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- require.NoError(t, err)
- _, err = active.Logical().Write("auth/userpass/users/end-user", map[string]interface{}{
- "password": testPassword,
- })
- require.NoError(t, err)
-
- // Create a confidential client
- _, err = active.Logical().Write("identity/oidc/client/confidential", map[string]interface{}{
- "redirect_uris": []string{testRedirectURI},
- "assignments": []string{"allow_all"},
- "id_token_ttl": "1h",
- "access_token_ttl": "30m",
- })
- require.NoError(t, err)
-
- // Read the client ID and secret in order to configure the OIDC client
- resp, err := active.Logical().Read("identity/oidc/client/confidential")
- require.NoError(t, err)
- clientID := resp.Data["client_id"].(string)
- clientSecret := resp.Data["client_secret"].(string)
-
- // We aren't going to open up a browser to facilitate the login and redirect
- // from this test, so we'll log in via userpass and set the client's token as
- // the token that results from the authentication.
- resp, err = active.Logical().Write("auth/userpass/login/end-user", map[string]interface{}{
- "password": testPassword,
- })
- require.NoError(t, err)
- clientToken := resp.Auth.ClientToken
- entityID := resp.Auth.EntityID
-
- // Look up the token to get its creation time. This will be used for test
- // cases that make assertions on the max_age parameter and auth_time claim.
- resp, err = active.Logical().Write("auth/token/lookup", map[string]interface{}{
- "token": clientToken,
- })
- require.NoError(t, err)
- expectedAuthTime, err := strconv.Atoi(string(resp.Data["creation_time"].(json.Number)))
- require.NoError(t, err)
-
- // Read the issuer from the OIDC provider's discovery document
- var discovery struct {
- Issuer string `json:"issuer"`
- }
- decodeRawRequest(t, active, http.MethodGet,
- "/v1/identity/oidc/provider/default/.well-known/openid-configuration",
- nil, &discovery)
-
- // Create the client-side OIDC provider config
- pc, err := oidc.NewConfig(discovery.Issuer, clientID,
- oidc.ClientSecret(clientSecret), []oidc.Alg{oidc.RS256},
- []string{testRedirectURI}, oidc.WithProviderCA(string(cluster.CACertPEM)))
- require.NoError(t, err)
-
- // Create the client-side OIDC provider
- p, err := oidc.NewProvider(pc)
- require.NoError(t, err)
- defer p.Done()
-
- // Create the client-side PKCE code verifier
- v, err := oidc.NewCodeVerifier()
- require.NoError(t, err)
-
- type args struct {
- useStandby bool
- options []oidc.Option
- }
- tests := []struct {
- name string
- args args
- expected string
- }{
- {
- name: "active: authorization code flow",
- args: args{
- options: []oidc.Option{
- oidc.WithScopes("openid"),
- },
- },
- expected: fmt.Sprintf(`{
- "iss": "%s",
- "aud": "%s",
- "sub": "%s",
- "namespace": "root"
- }`, discovery.Issuer, clientID, entityID),
- },
- {
- name: "active: authorization code flow with max_age parameter",
- args: args{
- options: []oidc.Option{
- oidc.WithScopes("openid"),
- oidc.WithMaxAge(60),
- },
- },
- expected: fmt.Sprintf(`{
- "iss": "%s",
- "aud": "%s",
- "sub": "%s",
- "namespace": "root",
- "auth_time": %d
- }`, discovery.Issuer, clientID, entityID, expectedAuthTime),
- },
- {
- name: "active: authorization code flow with Proof Key for Code Exchange (PKCE)",
- args: args{
- options: []oidc.Option{
- oidc.WithScopes("openid"),
- oidc.WithPKCE(v),
- },
- },
- expected: fmt.Sprintf(`{
- "iss": "%s",
- "aud": "%s",
- "sub": "%s",
- "namespace": "root"
- }`, discovery.Issuer, clientID, entityID),
- },
- {
- name: "standby: authorization code flow",
- args: args{
- useStandby: true,
- options: []oidc.Option{
- oidc.WithScopes("openid"),
- },
- },
- expected: fmt.Sprintf(`{
- "iss": "%s",
- "aud": "%s",
- "sub": "%s",
- "namespace": "root"
- }`, discovery.Issuer, clientID, entityID),
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- client := active
- if tt.args.useStandby {
- client = standby
- }
- client.SetToken(clientToken)
-
- // Create the client-side OIDC request state
- oidcRequest, err := oidc.NewRequest(10*time.Minute, testRedirectURI, tt.args.options...)
- require.NoError(t, err)
-
- // Get the URL for the authorization endpoint from the OIDC client
- authURL, err := p.AuthURL(context.Background(), oidcRequest)
- require.NoError(t, err)
- parsedAuthURL, err := url.Parse(authURL)
- require.NoError(t, err)
-
- // This replace only occurs because we're not using the browser in this test
- authURLPath := strings.Replace(parsedAuthURL.Path, "/ui/vault/", "/v1/", 1)
-
- // Kick off the authorization code flow
- var authResp struct {
- Code string `json:"code"`
- State string `json:"state"`
- }
- decodeRawRequest(t, client, http.MethodGet, authURLPath, parsedAuthURL.Query(), &authResp)
-
- // The returned state must match the OIDC client state
- require.Equal(t, oidcRequest.State(), authResp.State)
-
- // Exchange the authorization code for an ID token and access token.
- // The ID token signature is verified using the provider's public keys after
- // the exchange takes place. The ID token is also validated according to the
- // client-side requirements of the OIDC spec. See the validation code at:
- // - https://github.com/hashicorp/cap/blob/main/oidc/provider.go#L240
- // - https://github.com/hashicorp/cap/blob/main/oidc/provider.go#L441
- token, err := p.Exchange(context.Background(), oidcRequest, authResp.State, authResp.Code)
- require.NoError(t, err)
- require.NotNil(t, token)
- idToken := token.IDToken()
- accessToken := token.StaticTokenSource()
-
- // Get the ID token claims
- allClaims := make(map[string]interface{})
- require.NoError(t, idToken.Claims(&allClaims))
-
- // Get the sub claim for userinfo validation
- require.NotEmpty(t, allClaims["sub"])
- subject := allClaims["sub"].(string)
-
- // Request userinfo using the access token
- err = p.UserInfo(context.Background(), accessToken, subject, &allClaims)
- require.NoError(t, err)
-
- // Assert that claims computed during the flow (i.e., not known
- // ahead of time in this test) are present as top-level keys
- for _, claim := range []string{"iat", "exp", "nonce", "at_hash", "c_hash"} {
- _, ok := allClaims[claim]
- require.True(t, ok)
- }
-
- // Assert that all other expected claims are populated
- expectedClaims := make(map[string]interface{})
- require.NoError(t, json.Unmarshal([]byte(tt.expected), &expectedClaims))
- for k, expectedVal := range expectedClaims {
- actualVal, ok := allClaims[k]
- require.True(t, ok)
- require.EqualValues(t, expectedVal, actualVal)
- }
- })
- }
-}
-
-// TestOIDC_Auth_Code_Flow_Confidential_CAP_Client tests the authorization code
-// flow using a Vault OIDC provider. The test uses the CAP OIDC client to verify
-// that the Vault OIDC provider's responses pass the various client-side validation
-// requirements of the OIDC spec. This test uses a confidential client which has
-// a client secret and authenticates to the token endpoint.
-func TestOIDC_Auth_Code_Flow_Confidential_CAP_Client(t *testing.T) {
- cluster := setupOIDCTestCluster(t, 2)
- defer cluster.Cleanup()
- active := cluster.Cores[0].Client
- standby := cluster.Cores[1].Client
-
- // Create an entity with some metadata
- resp, err := active.Logical().Write("identity/entity", map[string]interface{}{
- "name": "test-entity",
- "metadata": map[string]string{
- "email": "test@hashicorp.com",
- "phone_number": "123-456-7890",
- },
- })
- require.NoError(t, err)
- entityID := resp.Data["id"].(string)
-
- // Create a group
- resp, err = active.Logical().Write("identity/group", map[string]interface{}{
- "name": "engineering",
- "member_entity_ids": []string{entityID},
- })
- require.NoError(t, err)
- groupID := resp.Data["id"].(string)
-
- // Create a policy that allows updating the provider
- err = active.Sys().PutPolicy("test-policy", `
- path "identity/oidc/provider/test-provider" {
- capabilities = ["update"]
- }
- `)
- require.NoError(t, err)
-
- // Enable userpass auth and create a user
- err = active.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- require.NoError(t, err)
- _, err = active.Logical().Write("auth/userpass/users/end-user", map[string]interface{}{
- "password": testPassword,
- "token_policies": "test-policy",
- })
- require.NoError(t, err)
-
- // Get the userpass mount accessor
- mounts, err := active.Sys().ListAuth()
- require.NoError(t, err)
- var mountAccessor string
- for k, v := range mounts {
- if k == "userpass/" {
- mountAccessor = v.Accessor
- break
- }
- }
- require.NotEmpty(t, mountAccessor)
-
- // Create an entity alias
- _, err = active.Logical().Write("identity/entity-alias", map[string]interface{}{
- "name": "end-user",
- "canonical_id": entityID,
- "mount_accessor": mountAccessor,
- })
- require.NoError(t, err)
-
- // Create some custom scopes
- _, err = active.Logical().Write("identity/oidc/scope/groups", map[string]interface{}{
- "template": testGroupScopeTemplate,
- })
- require.NoError(t, err)
- _, err = active.Logical().Write("identity/oidc/scope/user", map[string]interface{}{
- "template": fmt.Sprintf(testUserScopeTemplate, mountAccessor),
- })
- require.NoError(t, err)
-
- // Create a key
- _, err = active.Logical().Write("identity/oidc/key/test-key", map[string]interface{}{
- "allowed_client_ids": []string{"*"},
- "algorithm": "RS256",
- })
- require.NoError(t, err)
-
- // Create an assignment
- _, err = active.Logical().Write("identity/oidc/assignment/test-assignment", map[string]interface{}{
- "entity_ids": []string{entityID},
- "group_ids": []string{groupID},
- })
- require.NoError(t, err)
-
- // Create a confidential client
- _, err = active.Logical().Write("identity/oidc/client/confidential", map[string]interface{}{
- "key": "test-key",
- "redirect_uris": []string{testRedirectURI},
- "assignments": []string{"test-assignment"},
- "id_token_ttl": "1h",
- "access_token_ttl": "30m",
- })
- require.NoError(t, err)
-
- // Read the client ID and secret in order to configure the OIDC client
- resp, err = active.Logical().Read("identity/oidc/client/confidential")
- require.NoError(t, err)
- clientID := resp.Data["client_id"].(string)
- clientSecret := resp.Data["client_secret"].(string)
-
- // Create the OIDC provider
- _, err = active.Logical().Write("identity/oidc/provider/test-provider", map[string]interface{}{
- "allowed_client_ids": []string{clientID},
- "scopes_supported": []string{"user", "groups"},
- })
- require.NoError(t, err)
-
- // We aren't going to open up a browser to facilitate the login and redirect
- // from this test, so we'll log in via userpass and set the client's token as
- // the token that results from the authentication.
- resp, err = active.Logical().Write("auth/userpass/login/end-user", map[string]interface{}{
- "password": testPassword,
- })
- require.NoError(t, err)
- clientToken := resp.Auth.ClientToken
-
- // Look up the token to get its creation time. This will be used for test
- // cases that make assertions on the max_age parameter and auth_time claim.
- resp, err = active.Logical().Write("auth/token/lookup", map[string]interface{}{
- "token": clientToken,
- })
- require.NoError(t, err)
- expectedAuthTime, err := strconv.Atoi(string(resp.Data["creation_time"].(json.Number)))
- require.NoError(t, err)
-
- // Read the issuer from the OIDC provider's discovery document
- var discovery struct {
- Issuer string `json:"issuer"`
- }
- decodeRawRequest(t, active, http.MethodGet,
- "/v1/identity/oidc/provider/test-provider/.well-known/openid-configuration",
- nil, &discovery)
-
- // Create the client-side OIDC provider config
- pc, err := oidc.NewConfig(discovery.Issuer, clientID,
- oidc.ClientSecret(clientSecret), []oidc.Alg{oidc.RS256},
- []string{testRedirectURI}, oidc.WithProviderCA(string(cluster.CACertPEM)))
- require.NoError(t, err)
-
- // Create the client-side OIDC provider
- p, err := oidc.NewProvider(pc)
- require.NoError(t, err)
- defer p.Done()
-
- // Create the client-side PKCE code verifier
- v, err := oidc.NewCodeVerifier()
- require.NoError(t, err)
-
- type args struct {
- useStandby bool
- options []oidc.Option
- }
- tests := []struct {
- name string
- args args
- expected string
- }{
- {
- name: "active: authorization code flow",
- args: args{
- options: []oidc.Option{
- oidc.WithScopes("openid user"),
- },
- },
- expected: fmt.Sprintf(`{
- "iss": "%s",
- "aud": "%s",
- "sub": "%s",
- "namespace": "root",
- "username": "end-user",
- "contact": {
- "email": "test@hashicorp.com",
- "phone_number": "123-456-7890"
- }
- }`, discovery.Issuer, clientID, entityID),
- },
- {
- name: "active: authorization code flow with additional scopes",
- args: args{
- options: []oidc.Option{
- oidc.WithScopes("openid user groups"),
- },
- },
- expected: fmt.Sprintf(`{
- "iss": "%s",
- "aud": "%s",
- "sub": "%s",
- "namespace": "root",
- "username": "end-user",
- "contact": {
- "email": "test@hashicorp.com",
- "phone_number": "123-456-7890"
- },
- "groups": ["engineering"]
- }`, discovery.Issuer, clientID, entityID),
- },
- {
- name: "active: authorization code flow with max_age parameter",
- args: args{
- options: []oidc.Option{
- oidc.WithScopes("openid"),
- oidc.WithMaxAge(60),
- },
- },
- expected: fmt.Sprintf(`{
- "iss": "%s",
- "aud": "%s",
- "sub": "%s",
- "namespace": "root",
- "auth_time": %d
- }`, discovery.Issuer, clientID, entityID, expectedAuthTime),
- },
- {
- name: "active: authorization code flow with Proof Key for Code Exchange (PKCE)",
- args: args{
- options: []oidc.Option{
- oidc.WithScopes("openid"),
- oidc.WithPKCE(v),
- },
- },
- expected: fmt.Sprintf(`{
- "iss": "%s",
- "aud": "%s",
- "sub": "%s",
- "namespace": "root"
- }`, discovery.Issuer, clientID, entityID),
- },
- {
- name: "standby: authorization code flow with additional scopes",
- args: args{
- useStandby: true,
- options: []oidc.Option{
- oidc.WithScopes("openid user groups"),
- },
- },
- expected: fmt.Sprintf(`{
- "iss": "%s",
- "aud": "%s",
- "sub": "%s",
- "namespace": "root",
- "username": "end-user",
- "contact": {
- "email": "test@hashicorp.com",
- "phone_number": "123-456-7890"
- },
- "groups": ["engineering"]
- }`, discovery.Issuer, clientID, entityID),
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- client := active
- if tt.args.useStandby {
- client = standby
- }
- client.SetToken(clientToken)
-
- // Update allowed client IDs before the authentication flow
- _, err = client.Logical().Write("identity/oidc/provider/test-provider", map[string]interface{}{
- "allowed_client_ids": []string{clientID},
- })
- require.NoError(t, err)
-
- // Create the client-side OIDC request state
- oidcRequest, err := oidc.NewRequest(10*time.Minute, testRedirectURI, tt.args.options...)
- require.NoError(t, err)
-
- // Get the URL for the authorization endpoint from the OIDC client
- authURL, err := p.AuthURL(context.Background(), oidcRequest)
- require.NoError(t, err)
- parsedAuthURL, err := url.Parse(authURL)
- require.NoError(t, err)
-
- // This replace only occurs because we're not using the browser in this test
- authURLPath := strings.Replace(parsedAuthURL.Path, "/ui/vault/", "/v1/", 1)
-
- // Kick off the authorization code flow
- var authResp struct {
- Code string `json:"code"`
- State string `json:"state"`
- }
- decodeRawRequest(t, client, http.MethodGet, authURLPath, parsedAuthURL.Query(), &authResp)
-
- // The returned state must match the OIDC client state
- require.Equal(t, oidcRequest.State(), authResp.State)
-
- // Exchange the authorization code for an ID token and access token.
- // The ID token signature is verified using the provider's public keys after
- // the exchange takes place. The ID token is also validated according to the
- // client-side requirements of the OIDC spec. See the validation code at:
- // - https://github.com/hashicorp/cap/blob/main/oidc/provider.go#L240
- // - https://github.com/hashicorp/cap/blob/main/oidc/provider.go#L441
- token, err := p.Exchange(context.Background(), oidcRequest, authResp.State, authResp.Code)
- require.NoError(t, err)
- require.NotNil(t, token)
- idToken := token.IDToken()
- accessToken := token.StaticTokenSource()
-
- // Get the ID token claims
- allClaims := make(map[string]interface{})
- require.NoError(t, idToken.Claims(&allClaims))
-
- // Get the sub claim for userinfo validation
- require.NotEmpty(t, allClaims["sub"])
- subject := allClaims["sub"].(string)
-
- // Request userinfo using the access token
- err = p.UserInfo(context.Background(), accessToken, subject, &allClaims)
- require.NoError(t, err)
-
- // Assert that claims computed during the flow (i.e., not known
- // ahead of time in this test) are present as top-level keys
- for _, claim := range []string{"iat", "exp", "nonce", "at_hash", "c_hash"} {
- _, ok := allClaims[claim]
- require.True(t, ok)
- }
-
- // Assert that all other expected claims are populated
- expectedClaims := make(map[string]interface{})
- require.NoError(t, json.Unmarshal([]byte(tt.expected), &expectedClaims))
- for k, expectedVal := range expectedClaims {
- actualVal, ok := allClaims[k]
- require.True(t, ok)
- require.EqualValues(t, expectedVal, actualVal)
- }
-
- // Assert that the access token is no longer able to obtain user info
- // after removing the client from the provider's allowed client ids
- _, err = client.Logical().Write("identity/oidc/provider/test-provider", map[string]interface{}{
- "allowed_client_ids": []string{},
- })
- require.NoError(t, err)
- err = p.UserInfo(context.Background(), accessToken, subject, &allClaims)
- require.Error(t, err)
- require.Equal(t, `Provider.UserInfo: provider UserInfo request failed: 403 Forbidden: {"error":"access_denied","error_description":"client is not authorized to use the provider"}`,
- err.Error())
- })
- }
-}
-
-// TestOIDC_Auth_Code_Flow_Public_CAP_Client tests the authorization code flow using
-// a Vault OIDC provider. The test uses the CAP OIDC client to verify that the Vault
-// OIDC provider's responses pass the various client-side validation requirements of
-// the OIDC spec. This test uses a public client which does not have a client secret
-// and always uses proof key for code exchange (PKCE).
-func TestOIDC_Auth_Code_Flow_Public_CAP_Client(t *testing.T) {
- cluster := setupOIDCTestCluster(t, 2)
- defer cluster.Cleanup()
- active := cluster.Cores[0].Client
- standby := cluster.Cores[1].Client
-
- // Create an entity with some metadata
- resp, err := active.Logical().Write("identity/entity", map[string]interface{}{
- "name": "test-entity",
- "metadata": map[string]string{
- "email": "test@hashicorp.com",
- "phone_number": "123-456-7890",
- },
- })
- require.NoError(t, err)
- entityID := resp.Data["id"].(string)
-
- // Create a group
- resp, err = active.Logical().Write("identity/group", map[string]interface{}{
- "name": "engineering",
- "member_entity_ids": []string{entityID},
- })
- require.NoError(t, err)
- groupID := resp.Data["id"].(string)
-
- // Create a policy that allows updating the provider
- err = active.Sys().PutPolicy("test-policy", `
- path "identity/oidc/provider/test-provider" {
- capabilities = ["update"]
- }
- `)
- require.NoError(t, err)
-
- // Enable userpass auth and create a user
- err = active.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- require.NoError(t, err)
- _, err = active.Logical().Write("auth/userpass/users/end-user", map[string]interface{}{
- "password": testPassword,
- "token_policies": "test-policy",
- })
- require.NoError(t, err)
-
- // Get the userpass mount accessor
- mounts, err := active.Sys().ListAuth()
- require.NoError(t, err)
- var mountAccessor string
- for k, v := range mounts {
- if k == "userpass/" {
- mountAccessor = v.Accessor
- break
- }
- }
- require.NotEmpty(t, mountAccessor)
-
- // Create an entity alias
- _, err = active.Logical().Write("identity/entity-alias", map[string]interface{}{
- "name": "end-user",
- "canonical_id": entityID,
- "mount_accessor": mountAccessor,
- })
- require.NoError(t, err)
-
- // Create some custom scopes
- _, err = active.Logical().Write("identity/oidc/scope/groups", map[string]interface{}{
- "template": testGroupScopeTemplate,
- })
- require.NoError(t, err)
- _, err = active.Logical().Write("identity/oidc/scope/user", map[string]interface{}{
- "template": fmt.Sprintf(testUserScopeTemplate, mountAccessor),
- })
- require.NoError(t, err)
-
- // Create a key
- _, err = active.Logical().Write("identity/oidc/key/test-key", map[string]interface{}{
- "allowed_client_ids": []string{"*"},
- "algorithm": "RS256",
- })
- require.NoError(t, err)
-
- // Create an assignment
- _, err = active.Logical().Write("identity/oidc/assignment/test-assignment", map[string]interface{}{
- "entity_ids": []string{entityID},
- "group_ids": []string{groupID},
- })
- require.NoError(t, err)
-
- // Create a public client
- _, err = active.Logical().Write("identity/oidc/client/public", map[string]interface{}{
- "key": "test-key",
- "redirect_uris": []string{testRedirectURI},
- "assignments": []string{"test-assignment"},
- "id_token_ttl": "1h",
- "access_token_ttl": "30m",
- "client_type": "public",
- })
- require.NoError(t, err)
-
- // Read the client ID in order to configure the OIDC client
- resp, err = active.Logical().Read("identity/oidc/client/public")
- require.NoError(t, err)
- clientID := resp.Data["client_id"].(string)
-
- // Create the OIDC provider
- _, err = active.Logical().Write("identity/oidc/provider/test-provider", map[string]interface{}{
- "allowed_client_ids": []string{clientID},
- "scopes_supported": []string{"user", "groups"},
- })
- require.NoError(t, err)
-
- // We aren't going to open up a browser to facilitate the login and redirect
- // from this test, so we'll log in via userpass and set the client's token as
- // the token that results from the authentication.
- resp, err = active.Logical().Write("auth/userpass/login/end-user", map[string]interface{}{
- "password": testPassword,
- })
- require.NoError(t, err)
- clientToken := resp.Auth.ClientToken
-
- // Look up the token to get its creation time. This will be used for test
- // cases that make assertions on the max_age parameter and auth_time claim.
- resp, err = active.Logical().Write("auth/token/lookup", map[string]interface{}{
- "token": clientToken,
- })
- require.NoError(t, err)
- expectedAuthTime, err := strconv.Atoi(string(resp.Data["creation_time"].(json.Number)))
- require.NoError(t, err)
-
- // Read the issuer from the OIDC provider's discovery document
- var discovery struct {
- Issuer string `json:"issuer"`
- }
- decodeRawRequest(t, active, http.MethodGet,
- "/v1/identity/oidc/provider/test-provider/.well-known/openid-configuration",
- nil, &discovery)
-
- // Create the client-side OIDC provider config with client secret intentionally empty
- clientSecret := oidc.ClientSecret("")
- pc, err := oidc.NewConfig(discovery.Issuer, clientID, clientSecret, []oidc.Alg{oidc.RS256},
- []string{testRedirectURI}, oidc.WithProviderCA(string(cluster.CACertPEM)))
- require.NoError(t, err)
-
- // Create the client-side OIDC provider
- p, err := oidc.NewProvider(pc)
- require.NoError(t, err)
- defer p.Done()
-
- type args struct {
- useStandby bool
- options []oidc.Option
- }
- tests := []struct {
- name string
- args args
- expected string
- }{
- {
- name: "active: authorization code flow",
- args: args{
- options: []oidc.Option{
- oidc.WithScopes("openid user"),
- },
- },
- expected: fmt.Sprintf(`{
- "iss": "%s",
- "aud": "%s",
- "sub": "%s",
- "namespace": "root",
- "username": "end-user",
- "contact": {
- "email": "test@hashicorp.com",
- "phone_number": "123-456-7890"
- }
- }`, discovery.Issuer, clientID, entityID),
- },
- {
- name: "active: authorization code flow with additional scopes",
- args: args{
- options: []oidc.Option{
- oidc.WithScopes("openid user groups"),
- },
- },
- expected: fmt.Sprintf(`{
- "iss": "%s",
- "aud": "%s",
- "sub": "%s",
- "namespace": "root",
- "username": "end-user",
- "contact": {
- "email": "test@hashicorp.com",
- "phone_number": "123-456-7890"
- },
- "groups": ["engineering"]
- }`, discovery.Issuer, clientID, entityID),
- },
- {
- name: "active: authorization code flow with max_age parameter",
- args: args{
- options: []oidc.Option{
- oidc.WithScopes("openid"),
- oidc.WithMaxAge(60),
- },
- },
- expected: fmt.Sprintf(`{
- "iss": "%s",
- "aud": "%s",
- "sub": "%s",
- "namespace": "root",
- "auth_time": %d
- }`, discovery.Issuer, clientID, entityID, expectedAuthTime),
- },
- {
- name: "standby: authorization code flow with additional scopes",
- args: args{
- useStandby: true,
- options: []oidc.Option{
- oidc.WithScopes("openid user groups"),
- },
- },
- expected: fmt.Sprintf(`{
- "iss": "%s",
- "aud": "%s",
- "sub": "%s",
- "namespace": "root",
- "username": "end-user",
- "contact": {
- "email": "test@hashicorp.com",
- "phone_number": "123-456-7890"
- },
- "groups": ["engineering"]
- }`, discovery.Issuer, clientID, entityID),
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- client := active
- if tt.args.useStandby {
- client = standby
- }
- client.SetToken(clientToken)
-
- // Update allowed client IDs before the authentication flow
- _, err = client.Logical().Write("identity/oidc/provider/test-provider", map[string]interface{}{
- "allowed_client_ids": []string{clientID},
- })
- require.NoError(t, err)
-
- // Create the required client-side PKCE code verifier.
- v, err := oidc.NewCodeVerifier()
- require.NoError(t, err)
- options := append([]oidc.Option{oidc.WithPKCE(v)}, tt.args.options...)
-
- // Create the client-side OIDC request state
- oidcRequest, err := oidc.NewRequest(10*time.Minute, testRedirectURI, options...)
- require.NoError(t, err)
-
- // Get the URL for the authorization endpoint from the OIDC client
- authURL, err := p.AuthURL(context.Background(), oidcRequest)
- require.NoError(t, err)
- parsedAuthURL, err := url.Parse(authURL)
- require.NoError(t, err)
-
- // This replace only occurs because we're not using the browser in this test
- authURLPath := strings.Replace(parsedAuthURL.Path, "/ui/vault/", "/v1/", 1)
-
- // Kick off the authorization code flow
- var authResp struct {
- Code string `json:"code"`
- State string `json:"state"`
- }
- decodeRawRequest(t, client, http.MethodGet, authURLPath, parsedAuthURL.Query(), &authResp)
-
- // The returned state must match the OIDC client state
- require.Equal(t, oidcRequest.State(), authResp.State)
-
- // Exchange the authorization code for an ID token and access token.
- // The ID token signature is verified using the provider's public keys after
- // the exchange takes place. The ID token is also validated according to the
- // client-side requirements of the OIDC spec. See the validation code at:
- // - https://github.com/hashicorp/cap/blob/main/oidc/provider.go#L240
- // - https://github.com/hashicorp/cap/blob/main/oidc/provider.go#L441
- token, err := p.Exchange(context.Background(), oidcRequest, authResp.State, authResp.Code)
- require.NoError(t, err)
- require.NotNil(t, token)
- idToken := token.IDToken()
- accessToken := token.StaticTokenSource()
-
- // Get the ID token claims
- allClaims := make(map[string]interface{})
- require.NoError(t, idToken.Claims(&allClaims))
-
- // Get the sub claim for userinfo validation
- require.NotEmpty(t, allClaims["sub"])
- subject := allClaims["sub"].(string)
-
- // Request userinfo using the access token
- err = p.UserInfo(context.Background(), accessToken, subject, &allClaims)
- require.NoError(t, err)
-
- // Assert that claims computed during the flow (i.e., not known
- // ahead of time in this test) are present as top-level keys
- for _, claim := range []string{"iat", "exp", "nonce", "at_hash", "c_hash"} {
- _, ok := allClaims[claim]
- require.True(t, ok)
- }
-
- // Assert that all other expected claims are populated
- expectedClaims := make(map[string]interface{})
- require.NoError(t, json.Unmarshal([]byte(tt.expected), &expectedClaims))
- for k, expectedVal := range expectedClaims {
- actualVal, ok := allClaims[k]
- require.True(t, ok)
- require.EqualValues(t, expectedVal, actualVal)
- }
-
- // Assert that the access token is no longer able to obtain user info
- // after removing the client from the provider's allowed client ids
- _, err = client.Logical().Write("identity/oidc/provider/test-provider", map[string]interface{}{
- "allowed_client_ids": []string{},
- })
- require.NoError(t, err)
- err = p.UserInfo(context.Background(), accessToken, subject, &allClaims)
- require.Error(t, err)
- require.Equal(t, `Provider.UserInfo: provider UserInfo request failed: 403 Forbidden: {"error":"access_denied","error_description":"client is not authorized to use the provider"}`,
- err.Error())
- })
- }
-}
-
-func setupOIDCTestCluster(t *testing.T, numCores int) *vault.TestCluster {
- t.Helper()
-
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- },
- }
- clusterOptions := &vault.TestClusterOptions{
- NumCores: numCores,
- HandlerFunc: vaulthttp.Handler,
- }
- cluster := vault.NewTestCluster(t, coreConfig, clusterOptions)
- cluster.Start()
- vault.TestWaitActive(t, cluster.Cores[0].Core)
-
- return cluster
-}
-
-func decodeRawRequest(t *testing.T, client *api.Client, method, path string, params url.Values, v interface{}) {
- t.Helper()
-
- // Create the request and add query params if provided
- req := client.NewRequest(method, path)
- req.Params = params
-
- // Send the raw request
- r, err := client.RawRequest(req)
- require.NoError(t, err)
- require.NotNil(t, r)
- require.Equal(t, http.StatusOK, r.StatusCode)
- defer r.Body.Close()
-
- // Decode the body into v
- require.NoError(t, json.NewDecoder(r.Body).Decode(v))
-}
diff --git a/vault/external_tests/identity/userlockouts_test.go b/vault/external_tests/identity/userlockouts_test.go
deleted file mode 100644
index 2a7b69913..000000000
--- a/vault/external_tests/identity/userlockouts_test.go
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package identity
-
-import (
- "os"
- "strings"
- "testing"
-
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/builtin/credential/userpass"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-const (
- UserLockoutThresholdDefault = 5
-)
-
-// TestIdentityStore_DisableUserLockoutTest tests that user login will
-// fail when supplied with wrong credentials. If the user is locked,
-// it returns permission denied. Otherwise, it returns invalid user
-// credentials error if the user lockout feature is disabled.
-// It tests disabling the feature using env variable VAULT_DISABLE_USER_LOCKOUT
-// and also using auth tune. Also, tests that env var has more precedence over
-// settings in auth tune.
-func TestIdentityStore_DisableUserLockoutTest(t *testing.T) {
- // reset to false before exiting
- defer os.Unsetenv("VAULT_DISABLE_USER_LOCKOUT")
-
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- // standby client
- client := cluster.Cores[1].Client
-
- // enable userpass
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // create a userpass user
- _, err = client.Logical().Write("auth/userpass/users/bsmith", map[string]interface{}{
- "password": "training",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // get mount accessor for userpass mount
- secret, err := client.Logical().Read("sys/auth/userpass")
- if err != nil || secret == nil {
- t.Fatal(err)
- }
- mountAccessor := secret.Data["accessor"].(string)
-
- // variables for auth tune
- disableLockout := true
- enableLockout := false
-
- tests := []struct {
- name string
- setDisableUserLockoutEnvVar string
- // default is false
- setDisableLockoutAuthTune bool
- expectedUserLocked bool
- }{
- {
- name: "Both unset, uses default behaviour i.e; user lockout feature enabled",
- setDisableUserLockoutEnvVar: "",
- setDisableLockoutAuthTune: false,
- expectedUserLocked: true,
- },
- {
- name: "User lockout feature is disabled using auth tune",
- setDisableUserLockoutEnvVar: "",
- setDisableLockoutAuthTune: true,
- expectedUserLocked: false,
- },
- {
- name: "User Lockout feature is disabled using env var VAULT_DISABLE_USER_LOCKOUT",
- setDisableUserLockoutEnvVar: "true",
- setDisableLockoutAuthTune: false,
- expectedUserLocked: false,
- },
- {
- name: "User lockout feature is enabled using env variable, disabled using auth tune",
- setDisableUserLockoutEnvVar: "false",
- setDisableLockoutAuthTune: true,
- expectedUserLocked: true,
- },
- {
- name: "User lockout feature is disabled using auth tune and env variable",
- setDisableUserLockoutEnvVar: "true",
- setDisableLockoutAuthTune: true,
- expectedUserLocked: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if tt.setDisableUserLockoutEnvVar != "" {
- os.Setenv("VAULT_DISABLE_USER_LOCKOUT", tt.setDisableUserLockoutEnvVar)
- } else {
- os.Unsetenv("VAULT_DISABLE_USER_LOCKOUT")
- }
-
- var disableLockoutAuthTune *bool
-
- // default for disable lockout is false
- disableLockoutAuthTune = &enableLockout
-
- if tt.setDisableLockoutAuthTune == true {
- disableLockoutAuthTune = &disableLockout
- }
-
- // tune auth mount
- userlockoutConfig := &api.UserLockoutConfigInput{
- DisableLockout: disableLockoutAuthTune,
- }
- err := client.Sys().TuneMount("auth/userpass", api.MountConfigInput{
- UserLockoutConfig: userlockoutConfig,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // login for default lockout threshold times with wrong credentials
- for i := 0; i < UserLockoutThresholdDefault; i++ {
- _, err = client.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{
- "password": "wrongPassword",
- })
- if err == nil {
- t.Fatal("expected login to fail due to wrong credentials")
- }
- if !strings.Contains(err.Error(), "invalid username or password") {
- t.Fatal(err)
- }
- }
-
- // login to check if user locked
- _, err = client.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{
- "password": "wrongPassword",
- })
- if err == nil {
- t.Fatal("expected login to fail due to wrong credentials")
- }
-
- switch tt.expectedUserLocked {
- case true:
- if !strings.Contains(err.Error(), logical.ErrPermissionDenied.Error()) {
- t.Fatalf("expected user to get locked but got %v", err)
- }
- // user locked, unlock user to perform next test iteration
- if _, err = client.Logical().Write("sys/locked-users/"+mountAccessor+"/unlock/bsmith", nil); err != nil {
- t.Fatal(err)
- }
-
- default:
- if !strings.Contains(err.Error(), "invalid username or password") {
- t.Fatalf("expected user to be unlocked but locked, got %v", err)
- }
- }
- })
- }
-}
diff --git a/vault/external_tests/kv/kv_patch_test.go b/vault/external_tests/kv/kv_patch_test.go
deleted file mode 100644
index a4bbdae7a..000000000
--- a/vault/external_tests/kv/kv_patch_test.go
+++ /dev/null
@@ -1,297 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package kv
-
-import (
- "context"
- "encoding/json"
- "io/ioutil"
- "net/http"
- "strings"
- "testing"
- "time"
-
- logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/audit"
- auditFile "github.com/hashicorp/vault/builtin/audit/file"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-func TestKV_Patch_BadContentTypeHeader(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "kv": logicalKv.VersionedKVFactory,
- },
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- cores := cluster.Cores
-
- core := cores[0].Core
- c := cluster.Cores[0].Client
- vault.TestWaitActive(t, core)
-
- // Mount a KVv2 backend
- err := c.Sys().Mount("kv", &api.MountInput{
- Type: "kv-v2",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- kvData := map[string]interface{}{
- "data": map[string]interface{}{
- "bar": "a",
- },
- }
-
- secretRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
- return c.Logical().Write("kv/data/foo", kvData)
- })
- if err != nil {
- t.Fatalf("write failed - err :%#v, resp: %#v\n", err, secretRaw)
- }
-
- secretRaw, err = kvRequestWithRetry(t, func() (interface{}, error) {
- return c.Logical().Read("kv/data/foo")
- })
- if err != nil {
- t.Fatalf("read failed - err :%#v, resp: %#v\n", err, secretRaw)
- }
-
- apiRespRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
- req := c.NewRequest("PATCH", "/v1/kv/data/foo")
- req.Headers = http.Header{
- "Content-Type": []string{"application/json"},
- }
-
- if err := req.SetJSONBody(kvData); err != nil {
- t.Fatal(err)
- }
-
- return c.RawRequestWithContext(context.Background(), req)
- })
-
- apiResp, ok := apiRespRaw.(*api.Response)
- if !ok {
- t.Fatalf("response not an api.Response, actual: %#v", apiRespRaw)
- }
-
- if err == nil || apiResp.StatusCode != http.StatusUnsupportedMediaType {
- t.Fatalf("expected PATCH request to fail with %d status code - err :%#v, resp: %#v\n", http.StatusUnsupportedMediaType, err, apiResp)
- }
-}
-
-func kvRequestWithRetry(t *testing.T, req func() (interface{}, error)) (interface{}, error) {
- t.Helper()
-
- var err error
- var resp interface{}
-
- // Loop until return message does not indicate upgrade, or timeout.
- timeout := time.After(20 * time.Second)
- ticker := time.Tick(time.Second)
-
- for {
- select {
- case <-timeout:
- t.Error("timeout expired waiting for upgrade")
- case <-ticker:
- resp, err = req()
-
- if err == nil {
- return resp, nil
- }
-
- responseError := err.(*api.ResponseError)
- if !strings.Contains(responseError.Error(), "Upgrading from non-versioned to versioned data") {
- return resp, err
- }
- }
- }
-}
-
-func TestKV_Patch_Audit(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "kv": logicalKv.VersionedKVFactory,
- },
- AuditBackends: map[string]audit.Factory{
- "file": auditFile.Factory,
- },
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- cores := cluster.Cores
-
- core := cores[0].Core
- c := cluster.Cores[0].Client
- vault.TestWaitActive(t, core)
-
- if err := c.Sys().Mount("kv/", &api.MountInput{
- Type: "kv-v2",
- }); err != nil {
- t.Fatalf("kv-v2 mount attempt failed - err: %#v\n", err)
- }
-
- auditLogFile, err := ioutil.TempFile("", "httppatch")
- if err != nil {
- t.Fatal(err)
- }
-
- err = c.Sys().EnableAuditWithOptions("file", &api.EnableAuditOptions{
- Type: "file",
- Options: map[string]string{
- "file_path": auditLogFile.Name(),
- },
- })
- if err != nil {
- t.Fatal(err)
- }
-
- writeData := map[string]interface{}{
- "data": map[string]interface{}{
- "bar": "a",
- },
- }
-
- resp, err := kvRequestWithRetry(t, func() (interface{}, error) {
- return c.Logical().Write("kv/data/foo", writeData)
- })
- if err != nil {
- t.Fatalf("write request failed, err: %#v, resp: %#v\n", err, resp)
- }
-
- patchData := map[string]interface{}{
- "data": map[string]interface{}{
- "baz": "b",
- },
- }
-
- resp, err = kvRequestWithRetry(t, func() (interface{}, error) {
- return c.Logical().JSONMergePatch(context.Background(), "kv/data/foo", patchData)
- })
- if err != nil {
- t.Fatalf("patch request failed, err: %#v, resp: %#v\n", err, resp)
- }
-
- patchRequestLogCount := 0
- patchResponseLogCount := 0
- decoder := json.NewDecoder(auditLogFile)
-
- var auditRecord map[string]interface{}
- for decoder.Decode(&auditRecord) == nil {
- auditRequest := map[string]interface{}{}
-
- if req, ok := auditRecord["request"]; ok {
- auditRequest = req.(map[string]interface{})
- }
-
- if auditRequest["operation"] == "patch" && auditRecord["type"] == "request" {
- patchRequestLogCount += 1
- } else if auditRequest["operation"] == "patch" && auditRecord["type"] == "response" {
- patchResponseLogCount += 1
- }
- }
-
- if patchRequestLogCount != 1 {
- t.Fatalf("expected 1 patch request audit log record, saw %d\n", patchRequestLogCount)
- }
-
- if patchResponseLogCount != 1 {
- t.Fatalf("expected 1 patch response audit log record, saw %d\n", patchResponseLogCount)
- }
-}
-
-// Verifies that patching works by default with the root token
-func TestKV_Patch_RootToken(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "kv": logicalKv.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0]
- client := core.Client
-
- // make sure this client is using the root token
- client.SetToken(cluster.RootToken)
-
- // Enable KVv2
- err := client.Sys().Mount("kv", &api.MountInput{
- Type: "kv-v2",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Write a kv value and patch it
- _, err = kvRequestWithRetry(t, func() (interface{}, error) {
- data := map[string]interface{}{
- "data": map[string]interface{}{
- "bar": "baz",
- "foo": "qux",
- },
- }
-
- return client.Logical().Write("kv/data/foo", data)
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = kvRequestWithRetry(t, func() (interface{}, error) {
- data := map[string]interface{}{
- "data": map[string]interface{}{
- "bar": "quux",
- "foo": nil,
- },
- }
- return client.Logical().JSONMergePatch(context.Background(), "kv/data/foo", data)
- })
- if err != nil {
- t.Fatal(err)
- }
-
- secretRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
- return client.Logical().Read("kv/data/foo")
- })
- if err != nil {
- t.Fatal(err)
- }
-
- secret, ok := secretRaw.(*api.Secret)
- if !ok {
- t.Fatalf("response not an api.Secret, actual: %#v", secretRaw)
- }
-
- bar := secret.Data["data"].(map[string]interface{})["bar"]
- if bar != "quux" {
- t.Fatalf("expected bar to be quux but it was %q", bar)
- }
-
- if _, ok := secret.Data["data"].(map[string]interface{})["foo"]; ok {
- t.Fatalf("expected data not to include foo")
- }
-}
diff --git a/vault/external_tests/kv/kv_subkeys_test.go b/vault/external_tests/kv/kv_subkeys_test.go
deleted file mode 100644
index f085ebfd8..000000000
--- a/vault/external_tests/kv/kv_subkeys_test.go
+++ /dev/null
@@ -1,379 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package kv
-
-import (
- "context"
- "net/http"
- "testing"
-
- "github.com/go-test/deep"
- logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
- "github.com/hashicorp/vault/api"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-// TestKV_Subkeys_NotFound issues a read to the subkeys endpoint for a path
-// that does not exist. A 400 status should be returned.
-func TestKV_Subkeys_NotFound(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "kv": logicalKv.VersionedKVFactory,
- },
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- cores := cluster.Cores
-
- core := cores[0].Core
- c := cluster.Cores[0].Client
- vault.TestWaitActive(t, core)
-
- // Mount a KVv2 backend
- err := c.Sys().Mount("kv", &api.MountInput{
- Type: "kv-v2",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- apiRespRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
- req := c.NewRequest("GET", "/v1/kv/subkeys/foo")
- return c.RawRequestWithContext(context.Background(), req)
- })
-
- apiResp, ok := apiRespRaw.(*api.Response)
- if !ok {
- t.Fatalf("response not an api.Response, actual: %#v", apiRespRaw)
- }
-
- if err == nil || apiResp == nil {
- t.Fatalf("expected subkeys request to fail, err :%v, resp: %#v", err, apiResp)
- }
-
- if apiResp.StatusCode != http.StatusNotFound {
- t.Fatalf("expected subkeys request to fail with %d status code, resp: %#v", http.StatusNotFound, apiResp)
- }
-}
-
-// TestKV_Subkeys_Deleted writes a single version of a secret to the KVv2
-// secret engine. The secret is subsequently deleted. A read to the subkeys
-// endpoint should return a 400 status with a nil "subkeys" value and the
-// "deletion_time" key in the "metadata" key should be not be empty.
-func TestKV_Subkeys_Deleted(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "kv": logicalKv.VersionedKVFactory,
- },
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- cores := cluster.Cores
-
- core := cores[0].Core
- c := cluster.Cores[0].Client
- vault.TestWaitActive(t, core)
-
- // Mount a KVv2 backend
- err := c.Sys().Mount("kv", &api.MountInput{
- Type: "kv-v2",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- kvData := map[string]interface{}{
- "data": map[string]interface{}{
- "bar": "a",
- },
- }
-
- resp, err := kvRequestWithRetry(t, func() (interface{}, error) {
- return c.Logical().Write("kv/data/foo", kvData)
- })
- if err != nil {
- t.Fatalf("write failed, err :%v, resp: %#v", err, resp)
- }
-
- secretRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
- return c.Logical().Delete("kv/data/foo")
- })
- if err != nil {
- t.Fatalf("delete failed, err :%v, resp: %#v", err, secretRaw)
- }
-
- apiRespRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
- req := c.NewRequest("GET", "/v1/kv/subkeys/foo")
- return c.RawRequestWithContext(context.Background(), req)
- })
-
- apiResp, ok := apiRespRaw.(*api.Response)
- if !ok {
- t.Fatalf("response not a api.Response, actual: %#v", apiRespRaw)
- }
-
- if apiResp != nil {
- defer apiResp.Body.Close()
- }
-
- if err == nil || apiResp == nil {
- t.Fatalf("expected subkeys request to fail, err :%v, resp: %#v", err, apiResp)
- }
-
- if apiResp.StatusCode != http.StatusNotFound {
- t.Fatalf("expected subkeys request to fail with %d status code, resp: %#v", http.StatusNotFound, apiResp)
- }
-
- secret, err := api.ParseSecret(apiResp.Body)
- if err != nil {
- t.Fatalf("failed to parse resp body, err: %v", err)
- }
-
- subkeys, ok := secret.Data["subkeys"]
- if !ok {
- t.Fatalf("key \"subkeys\" not found in response")
- }
-
- if subkeys != nil {
- t.Fatalf("expected nil subkeys, actual: %#v", subkeys)
- }
-
- metadata, ok := secret.Data["metadata"].(map[string]interface{})
-
- if !ok {
- t.Fatalf("metadata not present in response or invalid, metadata: %#v", secret.Data["metadata"])
- }
-
- if deletionTime, ok := metadata["deletion_time"].(string); !ok || deletionTime == "" {
- t.Fatalf("metadata does not contain deletion time, metadata: %#v", metadata)
- }
-}
-
-// TestKV_Subkeys_Destroyed writes a single version of a secret to the KVv2
-// secret engine. The secret is subsequently destroyed. A read to the subkeys
-// endpoint should return a 400 status with a nil "subkeys" value and the
-// "destroyed" key in the "metadata" key should be set to true.
-func TestKV_Subkeys_Destroyed(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "kv": logicalKv.VersionedKVFactory,
- },
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- cores := cluster.Cores
-
- core := cores[0].Core
- c := cluster.Cores[0].Client
- vault.TestWaitActive(t, core)
-
- // Mount a KVv2 backend
- err := c.Sys().Mount("kv", &api.MountInput{
- Type: "kv-v2",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- kvData := map[string]interface{}{
- "data": map[string]interface{}{
- "bar": "a",
- },
- }
-
- secretRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
- return c.Logical().Write("kv/data/foo", kvData)
- })
- if err != nil {
- t.Fatalf("write failed, err :%v, resp: %#v", err, secretRaw)
- }
-
- destroyVersions := map[string]interface{}{
- "versions": []int{1},
- }
-
- secretRaw, err = kvRequestWithRetry(t, func() (interface{}, error) {
- return c.Logical().Write("kv/destroy/foo", destroyVersions)
- })
- if err != nil {
- t.Fatalf("destroy failed, err :%v, resp: %#v", err, secretRaw)
- }
-
- secret, ok := secretRaw.(*api.Secret)
- if !ok {
- t.Fatalf("response not an api.Secret, actual: %#v", secretRaw)
- }
-
- apiRespRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
- req := c.NewRequest("GET", "/v1/kv/subkeys/foo")
- return c.RawRequestWithContext(context.Background(), req)
- })
-
- apiResp, ok := apiRespRaw.(*api.Response)
- if !ok {
- t.Fatalf("response not a api.Response, actual: %#v", apiRespRaw)
- }
-
- if apiResp != nil {
- defer apiResp.Body.Close()
- }
-
- if err == nil || apiResp == nil {
- t.Fatalf("expected subkeys request to fail, err :%v, resp: %#v", err, apiResp)
- }
-
- if apiResp.StatusCode != http.StatusNotFound {
- t.Fatalf("expected subkeys request to fail with %d status code, resp: %#v", http.StatusNotFound, apiResp)
- }
-
- secret, err = api.ParseSecret(apiResp.Body)
- if err != nil {
- t.Fatalf("failed to parse resp body, err: %v", err)
- }
-
- subkeys, ok := secret.Data["subkeys"]
- if !ok {
- t.Fatalf("key \"subkeys\" not found in response")
- }
-
- if subkeys != nil {
- t.Fatalf("expected nil subkeys, actual: %#v", subkeys)
- }
-
- metadata, ok := secret.Data["metadata"].(map[string]interface{})
-
- if !ok {
- t.Fatalf("metadata not present in response or invalid, metadata: %#v", secret.Data["metadata"])
- }
-
- if destroyed, ok := metadata["destroyed"].(bool); !ok || !destroyed {
- t.Fatalf("expected destroyed to be true, metadata: %#v", metadata)
- }
-}
-
-// TestKV_Subkeys_CurrentVersion writes multiples versions of a secret to the
-// KVv2 secret engine. It ensures that the subkeys endpoint returns a 200 status
-// and current version of the secret.
-func TestKV_Subkeys_CurrentVersion(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "kv": logicalKv.VersionedKVFactory,
- },
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- cores := cluster.Cores
-
- core := cores[0].Core
- c := cluster.Cores[0].Client
- vault.TestWaitActive(t, core)
-
- // Mount a KVv2 backend
- err := c.Sys().Mount("kv", &api.MountInput{
- Type: "kv-v2",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- kvData := map[string]interface{}{
- "data": map[string]interface{}{
- "foo": "does-not-matter",
- "bar": map[string]interface{}{
- "a": map[string]interface{}{
- "c": "does-not-matter",
- },
- "b": map[string]interface{}{},
- },
- },
- }
-
- secretRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
- return c.Logical().Write("kv/data/foo", kvData)
- })
- if err != nil {
- t.Fatalf("write failed, err :%v, resp: %#v", err, secretRaw)
- }
-
- kvData = map[string]interface{}{
- "data": map[string]interface{}{
- "baz": "does-not-matter",
- },
- }
-
- secretRaw, err = kvRequestWithRetry(t, func() (interface{}, error) {
- return c.Logical().JSONMergePatch(context.Background(), "kv/data/foo", kvData)
- })
- if err != nil {
- t.Fatalf("patch failed, err :%v, resp: %#v", err, secretRaw)
- }
-
- apiRespRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
- req := c.NewRequest("GET", "/v1/kv/subkeys/foo")
- return c.RawRequestWithContext(context.Background(), req)
- })
-
- apiResp, ok := apiRespRaw.(*api.Response)
- if !ok {
- t.Fatalf("response not a api.Response, actual: %#v", apiRespRaw)
- }
-
- if apiResp != nil {
- defer apiResp.Body.Close()
- }
-
- if err != nil || apiResp == nil {
- t.Fatalf("subkeys request failed, err :%v, resp: %#v", err, apiResp)
- }
-
- if apiResp.StatusCode != http.StatusOK {
- t.Fatalf("expected subkeys request to succeed with %d status code, resp: %#v", http.StatusOK, apiResp)
- }
-
- secret, err := api.ParseSecret(apiResp.Body)
- if err != nil {
- t.Fatalf("failed to parse resp body, err: %v", err)
- }
-
- expectedSubkeys := map[string]interface{}{
- "foo": nil,
- "bar": map[string]interface{}{
- "a": map[string]interface{}{
- "c": nil,
- },
- "b": nil,
- },
- "baz": nil,
- }
-
- if diff := deep.Equal(secret.Data["subkeys"], expectedSubkeys); len(diff) > 0 {
- t.Fatalf("resp and expected data mismatch, diff: %#v", diff)
- }
-}
diff --git a/vault/external_tests/kv/kvv2_upgrade_test.go b/vault/external_tests/kv/kvv2_upgrade_test.go
deleted file mode 100644
index 0e3d6c0bc..000000000
--- a/vault/external_tests/kv/kvv2_upgrade_test.go
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package kv
-
-import (
- "bytes"
- "context"
- "strings"
- "sync"
- "testing"
- "time"
-
- "github.com/hashicorp/go-hclog"
- logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/helper/testhelpers"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/sdk/physical"
- "github.com/hashicorp/vault/vault"
- "github.com/kr/pretty"
-)
-
-// Tests the regression in
-// https://github.com/hashicorp/vault-plugin-secrets-kv/pull/31
-func TestKVv2_UpgradePaths(t *testing.T) {
- m := new(sync.Mutex)
- logOut := new(bytes.Buffer)
-
- logger := hclog.New(&hclog.LoggerOptions{
- Output: logOut,
- Mutex: m,
- })
-
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "kv": logicalKv.Factory,
- },
- EnableRaw: true,
- Logger: logger,
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0]
- vault.TestWaitActive(t, core.Core)
- client := core.Client
-
- // Enable KVv2
- err := client.Sys().Mount("kv", &api.MountInput{
- Type: "kv-v2",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- cluster.EnsureCoresSealed(t)
-
- ctx := context.Background()
-
- // Delete the policy from storage, to trigger the clean slate necessary for
- // the error
- mounts, err := core.UnderlyingStorage.List(ctx, "logical/")
- if err != nil {
- t.Fatal(err)
- }
- kvMount := mounts[0]
- basePaths, err := core.UnderlyingStorage.List(ctx, "logical/"+kvMount)
- if err != nil {
- t.Fatal(err)
- }
- basePath := basePaths[0]
-
- beforeList, err := core.UnderlyingStorage.List(ctx, "logical/"+kvMount+basePath)
- if err != nil {
- t.Fatal(err)
- }
- t.Log(pretty.Sprint(beforeList))
-
- // Delete policy/archive
- if err = logical.ClearView(ctx, physical.NewView(core.UnderlyingStorage, "logical/"+kvMount+basePath+"policy/")); err != nil {
- t.Fatal(err)
- }
- if err = logical.ClearView(ctx, physical.NewView(core.UnderlyingStorage, "logical/"+kvMount+basePath+"archive/")); err != nil {
- t.Fatal(err)
- }
-
- afterList, err := core.UnderlyingStorage.List(ctx, "logical/"+kvMount+basePath)
- if err != nil {
- t.Fatal(err)
- }
- t.Log(pretty.Sprint(afterList))
-
- testhelpers.EnsureCoresUnsealed(t, cluster)
-
- // Need to give it time to actually set up
- time.Sleep(10 * time.Second)
-
- m.Lock()
- defer m.Unlock()
- if strings.Contains(logOut.String(), "cannot write to storage during setup") {
- t.Fatal("got a cannot write to storage during setup error")
- }
-}
diff --git a/vault/external_tests/metrics/core_metrics_int_test.go b/vault/external_tests/metrics/core_metrics_int_test.go
deleted file mode 100644
index d70e10e89..000000000
--- a/vault/external_tests/metrics/core_metrics_int_test.go
+++ /dev/null
@@ -1,217 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package metrics
-
-import (
- "context"
- "encoding/json"
- "errors"
- "io/ioutil"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/helper/testhelpers/corehelpers"
-
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/helper/testhelpers"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/vault"
-)
-
-func TestMountTableMetrics(t *testing.T) {
- clusterName := "mycluster"
- conf := &vault.CoreConfig{
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- ClusterName: clusterName,
- }
- cluster := vault.NewTestCluster(t, conf, &vault.TestClusterOptions{
- KeepStandbysSealed: false,
- HandlerFunc: vaulthttp.Handler,
- NumCores: 2,
- CoreMetricSinkProvider: testhelpers.TestMetricSinkProvider(time.Minute),
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- // Wait for core to become active
- cores := cluster.Cores
- vault.TestWaitActive(t, cores[0].Core)
-
- client := cores[0].Client
-
- // Verify that the nonlocal logical mount table has 3 entries -- cubbyhole, identity, and kv
-
- data, err := testhelpers.SysMetricsReq(client, cluster, false)
- if err != nil {
- t.Fatal(err)
- }
-
- nonlocalLogicalMountsize, err := gaugeSearchHelper(data, 3)
- if err != nil {
- t.Errorf(err.Error())
- }
-
- // Mount new kv
- if err = client.Sys().Mount("kv", &api.MountInput{
- Type: "kv",
- Options: map[string]string{
- "version": "2",
- },
- }); err != nil {
- t.Fatal(err)
- }
-
- data, err = testhelpers.SysMetricsReq(client, cluster, false)
- if err != nil {
- t.Fatal(err)
- }
-
- nonlocalLogicalMountsizeAfterMount, err := gaugeSearchHelper(data, 4)
- if err != nil {
- t.Errorf(err.Error())
- }
-
- if nonlocalLogicalMountsizeAfterMount <= nonlocalLogicalMountsize {
- t.Errorf("Mount size does not change after new mount is mounted")
- }
-}
-
-func gaugeSearchHelper(data *testhelpers.SysMetricsJSON, expectedValue int) (int, error) {
- foundFlag := false
- tablesize := int(^uint(0) >> 1)
- for _, gauge := range data.Gauges {
- labels := gauge.Labels
- if loc, ok := labels["local"]; ok && loc.(string) == "false" {
- if tp, ok := labels["type"]; ok && tp.(string) == "logical" {
- if gauge.Name == "core.mount_table.num_entries" {
- foundFlag = true
- if err := gaugeConditionCheck("eq", expectedValue, gauge.Value); err != nil {
- return int(^uint(0) >> 1), err
- }
- } else if gauge.Name == "core.mount_table.size" {
- tablesize = gauge.Value
- }
- }
- }
- }
- if !foundFlag {
- return int(^uint(0) >> 1), errors.New("No metrics reported for mount sizes")
- }
- return tablesize, nil
-}
-
-func gaugeConditionCheck(comparator string, compareVal int, compareToVal int) error {
- if comparator == "eq" && compareVal != compareToVal {
- return errors.New("equality gauge check for comparison failed")
- }
- return nil
-}
-
-func TestLeaderReElectionMetrics(t *testing.T) {
- clusterName := "mycluster"
- conf := &vault.CoreConfig{
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- ClusterName: clusterName,
- }
- cluster := vault.NewTestCluster(t, conf, &vault.TestClusterOptions{
- KeepStandbysSealed: false,
- HandlerFunc: vaulthttp.Handler,
- NumCores: 2,
- CoreMetricSinkProvider: testhelpers.TestMetricSinkProvider(time.Minute),
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- // Wait for core to become active
- cores := cluster.Cores
- vault.TestWaitActive(t, cores[0].Core)
-
- client := cores[0].Client
- standbyClient := cores[1].Client
-
- r := client.NewRequest("GET", "/v1/sys/metrics")
- r2 := standbyClient.NewRequest("GET", "/v1/sys/metrics")
- r.Headers.Set("X-Vault-Token", cluster.RootToken)
- r2.Headers.Set("X-Vault-Token", cluster.RootToken)
- respo, err := client.RawRequestWithContext(context.Background(), r)
- if err != nil {
- t.Fatal(err)
- }
- bodyBytes, err := ioutil.ReadAll(respo.Response.Body)
- if err != nil {
- t.Fatal(err)
- }
- if respo != nil {
- defer respo.Body.Close()
- }
- var data testhelpers.SysMetricsJSON
- var coreLeaderMetric bool = false
- var coreUnsealMetric bool = false
- if err := json.Unmarshal(bodyBytes, &data); err != nil {
- t.Fatal("failed to unmarshal:", err)
- }
- for _, gauge := range data.Gauges {
- if gauge.Name == "core.active" {
- coreLeaderMetric = true
- if gauge.Value != 1 {
- t.Errorf("metric incorrectly reports active status")
- }
- }
- if gauge.Name == "core.unsealed" {
- coreUnsealMetric = true
- if gauge.Value != 1 {
- t.Errorf("metric incorrectly reports unseal status of leader")
- }
- }
- }
- if !coreLeaderMetric || !coreUnsealMetric {
- t.Errorf("unseal metric or leader metric are missing")
- }
-
- err = client.Sys().StepDown()
- if err != nil {
- t.Fatal(err)
- }
- // Wait for core to become active
- vault.TestWaitActive(t, cores[1].Core)
-
- r = standbyClient.NewRequest("GET", "/v1/sys/metrics")
- r.Headers.Set("X-Vault-Token", cluster.RootToken)
- respo, err = standbyClient.RawRequestWithContext(context.Background(), r)
- if err != nil {
- t.Fatal(err)
- }
- bodyBytes, err = ioutil.ReadAll(respo.Response.Body)
- if err != nil {
- t.Fatal(err)
- }
- if err := json.Unmarshal(bodyBytes, &data); err != nil {
- t.Fatal("failed to unmarshal:", err)
- } else {
- coreLeaderMetric = false
- coreUnsealMetric = false
- for _, gauge := range data.Gauges {
- if gauge.Name == "core.active" {
- coreLeaderMetric = true
- if gauge.Value != 1 {
- t.Errorf("metric incorrectly reports active status")
- }
- }
- if gauge.Name == "core.unsealed" {
- coreUnsealMetric = true
- if gauge.Value != 1 {
- t.Errorf("metric incorrectly reports unseal status of leader")
- }
- }
- }
- if !coreLeaderMetric || !coreUnsealMetric {
- t.Errorf("unseal metric or leader metric are missing")
- }
- }
- if respo != nil {
- defer respo.Body.Close()
- }
-}
diff --git a/vault/external_tests/mfa/login_mfa_test.go b/vault/external_tests/mfa/login_mfa_test.go
deleted file mode 100644
index ae745564e..000000000
--- a/vault/external_tests/mfa/login_mfa_test.go
+++ /dev/null
@@ -1,708 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package mfa
-
-import (
- "fmt"
- "strings"
- "testing"
-
- "github.com/hashicorp/go-secure-stdlib/strutil"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/builtin/credential/userpass"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-// TestLoginMFA_Method_CRUD tests creating/reading/updating/deleting a method config for all the MFA providers
-func TestLoginMFA_Method_CRUD(t *testing.T) {
- cluster := vault.NewTestCluster(t, &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- },
- }, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // Enable userpass authentication
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatalf("failed to enable userpass auth: %v", err)
- }
-
- auths, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
- mountAccessor := auths["userpass/"].Accessor
-
- testCases := []struct {
- methodName string
- invalidType string
- configData map[string]interface{}
- keyToUpdate string
- valueToUpdate string
- keyToCheck string
- updatedValue string
- }{
- {
- "totp",
- "duo",
- map[string]interface{}{
- "issuer": "yCorp",
- "period": 10,
- "algorithm": "SHA1",
- "digits": 6,
- "skew": 1,
- "key_size": uint(10),
- "qr_size": 100,
- "max_validation_attempts": 1,
- },
- "issuer",
- "zCorp",
- "",
- "",
- },
- {
- "duo",
- "totp",
- map[string]interface{}{
- "mount_accessor": mountAccessor,
- "secret_key": "lol-secret",
- "integration_key": "integration-key",
- "api_hostname": "some-hostname",
- },
- "api_hostname",
- "api-updated.duosecurity.com",
- "",
- "",
- },
- {
- "okta",
- "pingid",
- map[string]interface{}{
- "mount_accessor": mountAccessor,
- "base_url": "example.com",
- "org_name": "my-org",
- "api_token": "lol-token",
- },
- "org_name",
- "dev-62954466-updated",
- "",
- "",
- },
- {
- "pingid",
- "okta",
- map[string]interface{}{
- "mount_accessor": mountAccessor,
- "settings_file_base64": "I0F1dG8tR2VuZXJhdGVkIGZyb20gUGluZ09uZSwgZG93bmxvYWRlZCBieSBpZD1bU1NPXSBlbWFpbD1baGFtaWRAaGFzaGljb3JwLmNvbV0KI1dlZCBEZWMgMTUgMTM6MDg6NDQgTVNUIDIwMjEKdXNlX2Jhc2U2NF9rZXk9YlhrdGMyVmpjbVYwTFd0bGVRPT0KdXNlX3NpZ25hdHVyZT10cnVlCnRva2VuPWxvbC10b2tlbgppZHBfdXJsPWh0dHBzOi8vaWRweG55bDNtLnBpbmdpZGVudGl0eS5jb20vcGluZ2lkCm9yZ19hbGlhcz1sb2wtb3JnLWFsaWFzCmFkbWluX3VybD1odHRwczovL2lkcHhueWwzbS5waW5naWRlbnRpdHkuY29tL3BpbmdpZAphdXRoZW50aWNhdG9yX3VybD1odHRwczovL2F1dGhlbnRpY2F0b3IucGluZ29uZS5jb20vcGluZ2lkL3BwbQ==",
- },
- "settings_file_base64",
- "I0F1dG8tR2VuZXJhdGVkIGZyb20gUGluZ09uZSwgZG93bmxvYWRlZCBieSBpZD1bU1NPXSBlbWFpbD1baGFtaWRAaGFzaGljb3JwLmNvbV0KI1dlZCBEZWMgMTUgMTM6MDg6NDQgTVNUIDIwMjEKdXNlX2Jhc2U2NF9rZXk9YlhrdGMyVmpjbVYwTFd0bGVRPT0KdXNlX3NpZ25hdHVyZT10cnVlCnRva2VuPWxvbC10b2tlbgppZHBfdXJsPWh0dHBzOi8vaWRweG55bDNtLnBpbmdpZGVudGl0eS5jb20vcGluZ2lkL3VwZGF0ZWQKb3JnX2FsaWFzPWxvbC1vcmctYWxpYXMKYWRtaW5fdXJsPWh0dHBzOi8vaWRweG55bDNtLnBpbmdpZGVudGl0eS5jb20vcGluZ2lkCmF1dGhlbnRpY2F0b3JfdXJsPWh0dHBzOi8vYXV0aGVudGljYXRvci5waW5nb25lLmNvbS9waW5naWQvcHBt",
- "idp_url",
- "https://idpxnyl3m.pingidentity.com/pingid/updated",
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.methodName, func(t *testing.T) {
- // create a new method config
- myPath := fmt.Sprintf("identity/mfa/method/%s", tc.methodName)
- resp, err := client.Logical().Write(myPath, tc.configData)
- if err != nil {
- t.Fatal(err)
- }
-
- methodId := resp.Data["method_id"]
- if methodId == "" {
- t.Fatal("method id is empty")
- }
-
- myNewPath := fmt.Sprintf("%s/%s", myPath, methodId)
-
- // read it back
- resp, err = client.Logical().Read(myNewPath)
- if err != nil {
- t.Fatal(err)
- }
-
- if resp.Data["id"] != methodId {
- t.Fatal("expected response id to match existing method id but it didn't")
- }
-
- if resp.Data["namespace_id"] != "root" {
- t.Fatalf("namespace id was not root, it was %s", resp.Data["namespace_id"])
- }
-
- if resp.Data["namespace_path"] != "" {
- t.Fatalf("namespace path was not empty, it was %s", resp.Data["namespace_path"])
- }
-
- // listing should show it
- resp, err = client.Logical().List(myPath)
- if err != nil {
- t.Fatal(err)
- }
- if resp.Data["keys"].([]interface{})[0] != methodId {
- t.Fatalf("expected %q in the list of method ids but it wasn't there", methodId)
- }
-
- // update it
- tc.configData[tc.keyToUpdate] = tc.valueToUpdate
- _, err = client.Logical().Write(myNewPath, tc.configData)
- if err != nil {
- t.Fatal(err)
- }
-
- resp, err = client.Logical().Read(myNewPath)
- if err != nil {
- t.Fatal(err)
- }
-
- // these shenanigans are to work around the arcane way that pingid does updates
- if tc.keyToCheck != "" && tc.updatedValue != "" {
- if resp.Data[tc.keyToCheck] != tc.updatedValue {
- t.Fatalf("expected config to update but it didn't: %v != %v", resp.Data[tc.keyToCheck], tc.updatedValue)
- }
- } else {
- if resp.Data[tc.keyToUpdate] != tc.valueToUpdate {
- t.Fatalf("expected config to update but it didn't: %v != %v", resp.Data[tc.keyToUpdate], tc.valueToUpdate)
- }
- }
-
- // read the id on another MFA type endpoint should fail
- invalidPath := fmt.Sprintf("identity/mfa/method/%s/%s", tc.invalidType, methodId)
- resp, err = client.Logical().Read(invalidPath)
- if err == nil {
- t.Fatal(err)
- }
-
- // read the id globally should succeed
- globalPath := fmt.Sprintf("identity/mfa/method/%s", methodId)
- resp, err = client.Logical().Read(globalPath)
- if err != nil {
- t.Fatal(err)
- }
- if resp.Data["id"] != methodId {
- t.Fatal("expected response id to match existing method id but it didn't")
- }
-
- // delete with invalid path should fail
- _, err = client.Logical().Delete(invalidPath)
- if err == nil {
- t.Fatal("expected deleting an MFA method ID with invalid path to fail")
- }
-
- // delete it
- _, err = client.Logical().Delete(myNewPath)
- if err != nil {
- t.Fatal(err)
- }
-
- // try to read it again - should 404
- resp, err = client.Logical().Read(myNewPath)
- if !(resp == nil && err == nil) {
- t.Fatal("expected a 404 but didn't get one")
- }
- })
- }
-}
-
-func TestLoginMFAMethodName(t *testing.T) {
- cluster := vault.NewTestCluster(t, &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- },
- }, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // Enable userpass authentication
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatalf("failed to enable userpass auth: %v", err)
- }
-
- auths, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
- mountAccessor := auths["userpass/"].Accessor
-
- testCases := []struct {
- methodType string
- configData map[string]interface{}
- }{
- {
- "totp",
- map[string]interface{}{
- "issuer": "yCorp",
- "method_name": "totp-method",
- },
- },
- {
- "duo",
- map[string]interface{}{
- "mount_accessor": mountAccessor,
- "secret_key": "lol-secret",
- "integration_key": "integration-key",
- "api_hostname": "some-hostname",
- "method_name": "duo-method",
- },
- },
- {
- "okta",
- map[string]interface{}{
- "mount_accessor": mountAccessor,
- "base_url": "example.com",
- "org_name": "my-org",
- "api_token": "lol-token",
- "method_name": "okta-method",
- },
- },
- {
- "pingid",
- map[string]interface{}{
- "mount_accessor": mountAccessor,
- "settings_file_base64": "I0F1dG8tR2VuZXJhdGVkIGZyb20gUGluZ09uZSwgZG93bmxvYWRlZCBieSBpZD1bU1NPXSBlbWFpbD1baGFtaWRAaGFzaGljb3JwLmNvbV0KI1dlZCBEZWMgMTUgMTM6MDg6NDQgTVNUIDIwMjEKdXNlX2Jhc2U2NF9rZXk9YlhrdGMyVmpjbVYwTFd0bGVRPT0KdXNlX3NpZ25hdHVyZT10cnVlCnRva2VuPWxvbC10b2tlbgppZHBfdXJsPWh0dHBzOi8vaWRweG55bDNtLnBpbmdpZGVudGl0eS5jb20vcGluZ2lkCm9yZ19hbGlhcz1sb2wtb3JnLWFsaWFzCmFkbWluX3VybD1odHRwczovL2lkcHhueWwzbS5waW5naWRlbnRpdHkuY29tL3BpbmdpZAphdXRoZW50aWNhdG9yX3VybD1odHRwczovL2F1dGhlbnRpY2F0b3IucGluZ29uZS5jb20vcGluZ2lkL3BwbQ==",
- "method_name": "pingid-method",
- },
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.methodType, func(t *testing.T) {
- // create a new method config
- myPath := fmt.Sprintf("identity/mfa/method/%s", tc.methodType)
- resp, err := client.Logical().Write(myPath, tc.configData)
- if err != nil {
- t.Fatal(err)
- }
-
- methodId := resp.Data["method_id"]
- if methodId == "" {
- t.Fatal("method id is empty")
- }
-
- // creating an MFA config with the same name should not return a new method ID
- resp, err = client.Logical().Write(myPath, tc.configData)
- if err != nil {
- t.Fatal(err)
- }
- if methodId != resp.Data["method_id"] {
- t.Fatal("trying to create a new MFA config with the same name should not result in a new MFA config")
- }
-
- originalName := tc.configData["method_name"]
-
- // create a new MFA config name
- tc.configData["method_name"] = "newName"
- resp, err = client.Logical().Write(myPath, tc.configData)
- if err != nil {
- t.Fatal(err)
- }
-
- myNewPath := fmt.Sprintf("%s/%s", myPath, methodId)
-
- // Updating an existing MFA config with another config's name
- resp, err = client.Logical().Write(myNewPath, tc.configData)
- if err == nil {
- t.Fatalf("expected a failure for configuring an MFA method with an existing MFA method name, %v", err)
- }
-
- // Create a method with a / in the name
- tc.configData["method_name"] = fmt.Sprintf("ns1/%s", originalName)
- _, err = client.Logical().Write(myNewPath, tc.configData)
- if err != nil {
- t.Fatal(err)
- }
- })
- }
-}
-
-// TestLoginMFA_ListAllMFAConfigs tests listing all configs globally
-func TestLoginMFA_ListAllMFAConfigsGlobally(t *testing.T) {
- cluster := vault.NewTestCluster(t, &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- },
- }, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // Enable userpass authentication
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatalf("failed to enable userpass auth: %v", err)
- }
-
- auths, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
- mountAccessor := auths["userpass/"].Accessor
-
- mfaConfigs := []struct {
- methodType string
- configData map[string]interface{}
- }{
- {
- "totp",
- map[string]interface{}{
- "issuer": "yCorp",
- "period": 10,
- "algorithm": "SHA1",
- "digits": 6,
- "skew": 1,
- "key_size": uint(10),
- "qr_size": 100,
- "max_validation_attempts": 1,
- },
- },
- {
- "duo",
- map[string]interface{}{
- "mount_accessor": mountAccessor,
- "secret_key": "lol-secret",
- "integration_key": "integration-key",
- "api_hostname": "some-hostname",
- },
- },
- {
- "okta",
- map[string]interface{}{
- "mount_accessor": mountAccessor,
- "base_url": "example.com",
- "org_name": "my-org",
- "api_token": "lol-token",
- },
- },
- {
- "pingid",
- map[string]interface{}{
- "mount_accessor": mountAccessor,
- "settings_file_base64": "I0F1dG8tR2VuZXJhdGVkIGZyb20gUGluZ09uZSwgZG93bmxvYWRlZCBieSBpZD1bU1NPXSBlbWFpbD1baGFtaWRAaGFzaGljb3JwLmNvbV0KI1dlZCBEZWMgMTUgMTM6MDg6NDQgTVNUIDIwMjEKdXNlX2Jhc2U2NF9rZXk9YlhrdGMyVmpjbVYwTFd0bGVRPT0KdXNlX3NpZ25hdHVyZT10cnVlCnRva2VuPWxvbC10b2tlbgppZHBfdXJsPWh0dHBzOi8vaWRweG55bDNtLnBpbmdpZGVudGl0eS5jb20vcGluZ2lkCm9yZ19hbGlhcz1sb2wtb3JnLWFsaWFzCmFkbWluX3VybD1odHRwczovL2lkcHhueWwzbS5waW5naWRlbnRpdHkuY29tL3BpbmdpZAphdXRoZW50aWNhdG9yX3VybD1odHRwczovL2F1dGhlbnRpY2F0b3IucGluZ29uZS5jb20vcGluZ2lkL3BwbQ==",
- },
- },
- }
-
- var methodIDs []interface{}
- for _, method := range mfaConfigs {
- // create a new method config
- myPath := fmt.Sprintf("identity/mfa/method/%s", method.methodType)
- resp, err := client.Logical().Write(myPath, method.configData)
- if err != nil {
- t.Fatal(err)
- }
-
- methodId := resp.Data["method_id"]
- if methodId == "" {
- t.Fatal("method id is empty")
- }
- methodIDs = append(methodIDs, methodId)
- }
- // listing should show it
- resp, err := client.Logical().List("identity/mfa/method")
- if err != nil || resp == nil {
- t.Fatal(err)
- }
-
- if len(resp.Data["keys"].([]interface{})) != len(methodIDs) {
- t.Fatalf("global list request did not return all MFA method IDs")
- }
- if len(resp.Data["key_info"].(map[string]interface{})) != len(methodIDs) {
- t.Fatal("global list request did not return all MFA method configurations")
- }
-}
-
-// TestLoginMFA_LoginEnforcement_CRUD tests creating/reading/updating/deleting a login enforcement config
-func TestLoginMFA_LoginEnforcement_CRUD(t *testing.T) {
- cluster := vault.NewTestCluster(t, &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- },
- }, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // first create a few configs
- configIDs := make([]string, 0)
-
- for i := 0; i < 2; i++ {
- resp, err := client.Logical().Write("identity/mfa/method/totp", map[string]interface{}{
- "issuer": fmt.Sprintf("fooCorp%d", i),
- "period": 10,
- "algorithm": "SHA1",
- "digits": 6,
- "skew": 1,
- "key_size": uint(10),
- "qr_size": 100 + i,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- configIDs = append(configIDs, resp.Data["method_id"].(string))
- }
-
- // enable userpass auth
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- auths, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
-
- var mountAccessor string
- if auths != nil && auths["userpass/"] != nil {
- mountAccessor = auths["userpass/"].Accessor
- }
-
- // create a few entities
- resp, err := client.Logical().Write("identity/entity", map[string]interface{}{"name": "bob"})
- if err != nil {
- t.Fatal(err)
- }
- bobId := resp.Data["id"].(string)
- resp, err = client.Logical().Write("identity/entity", map[string]interface{}{"name": "alice"})
- if err != nil {
- t.Fatal(err)
- }
- aliceId := resp.Data["id"].(string)
-
- // create a few groups
- resp, err = client.Logical().Write("identity/group", map[string]interface{}{
- "metadata": map[string]interface{}{"rad": true},
- "member_entity_ids": []string{aliceId},
- })
- if err != nil {
- t.Fatal(err)
- }
- radGroupId := resp.Data["id"].(string)
-
- resp, err = client.Logical().Write("identity/group", map[string]interface{}{
- "metadata": map[string]interface{}{"sad": true},
- "member_entity_ids": []string{bobId},
- })
- if err != nil {
- t.Fatal(err)
- }
- sadGroupId := resp.Data["id"].(string)
-
- myPath := "identity/mfa/login-enforcement/foo"
- data := map[string]interface{}{
- "mfa_method_ids": []string{configIDs[0], configIDs[1]},
- "auth_method_accessors": []string{mountAccessor},
- }
-
- // create a login enforcement config
- _, err = client.Logical().Write(myPath, data)
- if err != nil {
- t.Fatal(err)
- }
-
- // read it back
- resp, err = client.Logical().Read(myPath)
- if err != nil {
- t.Fatal(err)
- }
-
- equal := strutil.EquivalentSlices(data["mfa_method_ids"].([]string), stringSliceFromInterfaceSlice(resp.Data["mfa_method_ids"].([]interface{})))
- if !equal {
- t.Fatal("expected input mfa method ids to equal output mfa method ids")
- }
- equal = strutil.EquivalentSlices(data["auth_method_accessors"].([]string), stringSliceFromInterfaceSlice(resp.Data["auth_method_accessors"].([]interface{})))
- if !equal {
- t.Fatal("expected input auth method accessors to equal output auth method accessors")
- }
-
- // listing should show it
- resp, err = client.Logical().List("identity/mfa/login-enforcement")
- if err != nil {
- t.Fatal(err)
- }
- if resp.Data["keys"].([]interface{})[0] != "foo" {
- t.Fatal("expected foo in the list of enforcement names but it wasn't there")
- }
-
- // update it
- data["identity_group_ids"] = []string{radGroupId, sadGroupId}
- data["identity_entity_ids"] = []string{bobId, aliceId}
- _, err = client.Logical().Write(myPath, data)
- if err != nil {
- t.Fatal(err)
- }
-
- // read it back
- resp, err = client.Logical().Read(myPath)
- if err != nil {
- t.Fatal(err)
- }
-
- equal = strutil.EquivalentSlices(data["identity_group_ids"].([]string), stringSliceFromInterfaceSlice(resp.Data["identity_group_ids"].([]interface{})))
- if !equal {
- t.Fatal("expected input identity group ids to equal output identity group ids")
- }
- equal = strutil.EquivalentSlices(data["identity_entity_ids"].([]string), stringSliceFromInterfaceSlice(resp.Data["identity_entity_ids"].([]interface{})))
- if !equal {
- t.Fatal("expected input identity entity ids to equal output identity entity ids")
- }
-
- // delete it
- _, err = client.Logical().Delete(myPath)
- if err != nil {
- t.Fatal(err)
- }
-
- // try to read it back again - should 404
- resp, err = client.Logical().Read(myPath)
-
- // when both the response and the error are nil on a read request, that gets translated into a 404
- if !(resp == nil && err == nil) {
- t.Fatal("expected the read to 404 but it didn't")
- }
-}
-
-// TestLoginMFA_LoginEnforcement_MethodIdsIsRequired ensures that login enforcements have method ids attached
-func TestLoginMFA_LoginEnforcement_MethodIdsIsRequired(t *testing.T) {
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // create a login enforcement config, which should fail
- _, err := client.Logical().Write("identity/mfa/login-enforcement/foo", map[string]interface{}{})
- if err == nil {
- t.Fatal("expected an error but didn't get one")
- }
-
- if !strings.Contains(err.Error(), "missing method ids") {
- t.Fatal("should have received an error about missing method ids but didn't")
- }
-}
-
-// TestLoginMFA_LoginEnforcement_RequiredParameters validates that all of the required fields must be present
-func TestLoginMFA_LoginEnforcement_RequiredParameters(t *testing.T) {
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // first create a few configs
- configIDs := make([]string, 0)
-
- for i := 0; i < 2; i++ {
- resp, err := client.Logical().Write("identity/mfa/method/totp", map[string]interface{}{
- "issuer": fmt.Sprintf("fooCorp%d", i),
- "period": 10,
- "algorithm": "SHA1",
- "digits": 6,
- "skew": 1,
- "key_size": uint(10),
- "qr_size": 100 + i,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- configIDs = append(configIDs, resp.Data["method_id"].(string))
- }
-
- // create a login enforcement config, which should fail
- _, err := client.Logical().Write("identity/mfa/login-enforcement/foo", map[string]interface{}{
- "mfa_method_ids": []string{configIDs[0], configIDs[1]},
- })
- if err == nil {
- t.Fatal("expected an error but didn't get one")
- }
- if !strings.Contains(err.Error(), "One of auth_method_accessors, auth_method_types, identity_group_ids, identity_entity_ids must be specified") {
- t.Fatal("expected an error about required fields but didn't get one")
- }
-}
-
-func TestLoginMFA_UpdateNonExistentConfig(t *testing.T) {
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- _, err := client.Logical().Write("mfa/method/totp/a51884c6-51f2-bdc3-f4c5-0da64fe4d061", map[string]interface{}{
- "issuer": "yCorp",
- "period": 10,
- "algorithm": "SHA1",
- "digits": 6,
- "skew": 1,
- "key_size": uint(10),
- "qr_size": 100,
- })
- if err == nil {
- t.Fatal("expected to get an error but didn't")
- }
- if !strings.Contains(err.Error(), "Code: 404") {
- t.Fatal("expected to get a 404 but didn't")
- }
-}
-
-// This is for converting []interface{} that you know holds all strings into []string
-func stringSliceFromInterfaceSlice(input []interface{}) []string {
- result := make([]string, 0, len(input))
- for _, x := range input {
- if val, ok := x.(string); ok {
- result = append(result, val)
- }
- }
- return result
-}
diff --git a/vault/external_tests/misc/misc_binary/recovery_test.go b/vault/external_tests/misc/misc_binary/recovery_test.go
deleted file mode 100644
index 6aaecffca..000000000
--- a/vault/external_tests/misc/misc_binary/recovery_test.go
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package misc
-
-import (
- "context"
- "os"
- "path"
- "testing"
-
- "github.com/mitchellh/mapstructure"
-
- "github.com/go-test/deep"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/sdk/helper/testcluster"
- "github.com/hashicorp/vault/sdk/helper/testcluster/docker"
-)
-
-// TestRecovery_Docker exercises recovery mode. It starts a single node raft
-// cluster, writes some data, then restarts it and makes sure that we can read
-// the data (that's mostly to make sure that our framework is properly handling
-// a volume that persists across runs.) It then starts the node in recovery mode
-// and deletes the data via sys/raw, and finally restarts it in normal mode and
-// makes sure the data has been deleted.
-func TestRecovery_Docker(t *testing.T) {
- ctx := context.TODO()
-
- t.Parallel()
- binary := os.Getenv("VAULT_BINARY")
- if binary == "" {
- t.Skip("only running docker test when $VAULT_BINARY present")
- }
- opts := &docker.DockerClusterOptions{
- ImageRepo: "hashicorp/vault",
- // We're replacing the binary anyway, so we're not too particular about
- // the docker image version tag.
- ImageTag: "latest",
- VaultBinary: binary,
- ClusterOptions: testcluster.ClusterOptions{
- NumCores: 1,
- VaultNodeConfig: &testcluster.VaultNodeConfig{
- LogLevel: "TRACE",
- // If you want the test to run faster locally, you could
- // uncomment this performance_multiplier change.
- //StorageOptions: map[string]string{
- // "performance_multiplier": "1",
- //},
- },
- },
- }
-
- cluster := docker.NewTestDockerCluster(t, opts)
- defer cluster.Cleanup()
-
- var secretUUID string
- {
- client := cluster.Nodes()[0].APIClient()
- if err := client.Sys().Mount("secret/", &api.MountInput{
- Type: "kv-v1",
- }); err != nil {
- t.Fatal(err)
- }
-
- fooVal := map[string]interface{}{"bar": 1.0}
- _, err := client.Logical().Write("secret/foo", fooVal)
- if err != nil {
- t.Fatal(err)
- }
- secret, err := client.Logical().List("secret/")
- if err != nil {
- t.Fatal(err)
- }
- if diff := deep.Equal(secret.Data["keys"], []interface{}{"foo"}); len(diff) > 0 {
- t.Fatalf("got=%v, want=%v, diff: %v", secret.Data["keys"], []string{"foo"}, diff)
- }
- mounts, err := client.Sys().ListMounts()
- if err != nil {
- t.Fatal(err)
- }
- secretMount := mounts["secret/"]
- if secretMount == nil {
- t.Fatalf("secret mount not found, mounts: %v", mounts)
- }
- secretUUID = secretMount.UUID
- }
-
- listSecrets := func() []string {
- client := cluster.Nodes()[0].APIClient()
- secret, err := client.Logical().List("secret/")
- if err != nil {
- t.Fatal(err)
- }
- if secret == nil {
- return nil
- }
- var result []string
- err = mapstructure.Decode(secret.Data["keys"], &result)
- return result
- }
-
- restart := func() {
- cluster.Nodes()[0].(*docker.DockerClusterNode).Stop()
-
- err := cluster.Nodes()[0].(*docker.DockerClusterNode).Start(ctx, opts)
- if err != nil {
- t.Fatalf("node restart post-recovery failed: %v", err)
- }
-
- err = testcluster.UnsealAllNodes(ctx, cluster)
- if err != nil {
- t.Fatalf("node unseal post-recovery failed: %v", err)
- }
-
- _, err = testcluster.WaitForActiveNode(ctx, cluster)
- if err != nil {
- t.Fatalf("node didn't become active: %v", err)
- }
- }
-
- restart()
- if len(listSecrets()) == 0 {
- t.Fatal("expected secret to still be there")
- }
-
- // Now bring it up in recovery mode.
- {
- cluster.Nodes()[0].(*docker.DockerClusterNode).Stop()
-
- newOpts := *opts
- opts := &newOpts
- opts.Args = []string{"-recovery"}
- opts.StartProbe = func(client *api.Client) error {
- // In recovery mode almost no paths are supported, and pretty much
- // the only ones that don't require a recovery token are the ones used
- // to generate a recovery token.
- _, err := client.Sys().GenerateRecoveryOperationTokenStatusWithContext(ctx)
- return err
- }
- err := cluster.Nodes()[0].(*docker.DockerClusterNode).Start(ctx, opts)
- if err != nil {
- t.Fatalf("node restart with -recovery failed: %v", err)
- }
- client := cluster.Nodes()[0].APIClient()
-
- recoveryToken, err := testcluster.GenerateRoot(cluster, testcluster.GenerateRecovery)
- if err != nil {
- t.Fatalf("recovery token generation failed: %v", err)
- }
- _, err = testcluster.GenerateRoot(cluster, testcluster.GenerateRecovery)
- if err == nil {
- t.Fatal("expected second generate-root to fail")
- }
- client.SetToken(recoveryToken)
-
- secret, err := client.Logical().List(path.Join("sys/raw/logical", secretUUID))
- if err != nil {
- t.Fatal(err)
- }
- if diff := deep.Equal(secret.Data["keys"], []interface{}{"foo"}); len(diff) > 0 {
- t.Fatalf("got=%v, want=%v, diff: %v", secret.Data, []string{"foo"}, diff)
- }
-
- _, err = client.Logical().Delete(path.Join("sys/raw/logical", secretUUID, "foo"))
- if err != nil {
- t.Fatal(err)
- }
- }
-
- // Now go back to regular mode and verify that our changes are present
- restart()
- if len(listSecrets()) != 0 {
- t.Fatal("expected secret to still be gone")
- }
-}
diff --git a/vault/external_tests/misc/recover_from_panic_test.go b/vault/external_tests/misc/recover_from_panic_test.go
deleted file mode 100644
index 8ddf5cfdc..000000000
--- a/vault/external_tests/misc/recover_from_panic_test.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package misc
-
-import (
- "testing"
-
- "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/api"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-// Tests the regression in
-// https://github.com/hashicorp/vault/pull/6920
-func TestRecoverFromPanic(t *testing.T) {
- logger := hclog.New(nil)
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "noop": vault.NoopBackendFactory,
- },
- EnableRaw: true,
- Logger: logger,
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0]
- vault.TestWaitActive(t, core.Core)
- client := core.Client
-
- err := client.Sys().Mount("noop", &api.MountInput{
- Type: "noop",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Read("noop/panic")
- if err == nil {
- t.Fatal("expected error")
- }
-
- // This will deadlock the test if we hit the condition
- cluster.EnsureCoresSealed(t)
-}
diff --git a/vault/external_tests/misc/recovery_test.go b/vault/external_tests/misc/recovery_test.go
deleted file mode 100644
index ea685fc82..000000000
--- a/vault/external_tests/misc/recovery_test.go
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package misc
-
-import (
- "path"
- "testing"
-
- "github.com/go-test/deep"
- "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/helper/testhelpers"
- "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/physical/inmem"
- "github.com/hashicorp/vault/vault"
- "go.uber.org/atomic"
-)
-
-func TestRecovery(t *testing.T) {
- logger := logging.NewVaultLogger(hclog.Debug).Named(t.Name())
- inm, err := inmem.NewTransactionalInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
-
- var keys [][]byte
- var secretUUID string
- var rootToken string
- {
- conf := vault.CoreConfig{
- Physical: inm,
- Logger: logger,
- }
- opts := vault.TestClusterOptions{
- HandlerFunc: http.Handler,
- NumCores: 1,
- }
-
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.Start()
- defer cluster.Cleanup()
-
- client := cluster.Cores[0].Client
- rootToken = client.Token()
- fooVal := map[string]interface{}{"bar": 1.0}
- _, err = client.Logical().Write("secret/foo", fooVal)
- if err != nil {
- t.Fatal(err)
- }
- secret, err := client.Logical().List("secret/")
- if err != nil {
- t.Fatal(err)
- }
- if diff := deep.Equal(secret.Data["keys"], []interface{}{"foo"}); len(diff) > 0 {
- t.Fatalf("got=%v, want=%v, diff: %v", secret.Data["keys"], []string{"foo"}, diff)
- }
- mounts, err := cluster.Cores[0].Client.Sys().ListMounts()
- if err != nil {
- t.Fatal(err)
- }
- secretMount := mounts["secret/"]
- if secretMount == nil {
- t.Fatalf("secret mount not found, mounts: %v", mounts)
- }
- secretUUID = secretMount.UUID
- cluster.EnsureCoresSealed(t)
- keys = cluster.BarrierKeys
- }
-
- {
- // Now bring it up in recovery mode.
- var tokenRef atomic.String
- conf := vault.CoreConfig{
- Physical: inm,
- Logger: logger,
- RecoveryMode: true,
- }
- opts := vault.TestClusterOptions{
- HandlerFunc: http.Handler,
- NumCores: 1,
- SkipInit: true,
- DefaultHandlerProperties: vault.HandlerProperties{
- RecoveryMode: true,
- RecoveryToken: &tokenRef,
- },
- }
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.BarrierKeys = keys
- cluster.Start()
- defer cluster.Cleanup()
-
- client := cluster.Cores[0].Client
- recoveryToken := testhelpers.GenerateRoot(t, cluster, testhelpers.GenerateRecovery)
- _, err = testhelpers.GenerateRootWithError(t, cluster, testhelpers.GenerateRecovery)
- if err == nil {
- t.Fatal("expected second generate-root to fail")
- }
- client.SetToken(recoveryToken)
-
- secret, err := client.Logical().List(path.Join("sys/raw/logical", secretUUID))
- if err != nil {
- t.Fatal(err)
- }
- if diff := deep.Equal(secret.Data["keys"], []interface{}{"foo"}); len(diff) > 0 {
- t.Fatalf("got=%v, want=%v, diff: %v", secret.Data, []string{"foo"}, diff)
- }
-
- _, err = client.Logical().Delete(path.Join("sys/raw/logical", secretUUID, "foo"))
- if err != nil {
- t.Fatal(err)
- }
- cluster.EnsureCoresSealed(t)
- }
-
- {
- // Now go back to regular mode and verify that our changes are present
- conf := vault.CoreConfig{
- Physical: inm,
- Logger: logger,
- }
- opts := vault.TestClusterOptions{
- HandlerFunc: http.Handler,
- NumCores: 1,
- SkipInit: true,
- }
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.BarrierKeys = keys
- cluster.Start()
- defer cluster.Cleanup()
-
- testhelpers.EnsureCoresUnsealed(t, cluster)
- vault.TestWaitActive(t, cluster.Cores[0].Core)
-
- client := cluster.Cores[0].Client
- client.SetToken(rootToken)
- secret, err := client.Logical().List("secret/")
- if err != nil {
- t.Fatal(err)
- }
- if secret != nil {
- t.Fatal("expected no data in secret mount")
- }
- }
-}
diff --git a/vault/external_tests/plugin/external_plugin_test.go b/vault/external_tests/plugin/external_plugin_test.go
deleted file mode 100644
index d3de1afc3..000000000
--- a/vault/external_tests/plugin/external_plugin_test.go
+++ /dev/null
@@ -1,1022 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package plugin_test
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "os"
- "path/filepath"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/audit"
- auditFile "github.com/hashicorp/vault/builtin/audit/file"
-
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/api/auth/approle"
- "github.com/hashicorp/vault/builtin/logical/database"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/helper/testhelpers/consul"
- "github.com/hashicorp/vault/helper/testhelpers/corehelpers"
- "github.com/hashicorp/vault/helper/testhelpers/pluginhelpers"
- postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/helper/consts"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-
- _ "github.com/jackc/pgx/v4/stdlib"
-)
-
-func getClusterWithFileAuditBackend(t *testing.T, typ consts.PluginType, numCores int) *vault.TestCluster {
- pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
- t.Cleanup(func() { cleanup(t) })
- coreConfig := &vault.CoreConfig{
- PluginDirectory: pluginDir,
- LogicalBackends: map[string]logical.Factory{
- "database": database.Factory,
- },
- AuditBackends: map[string]audit.Factory{
- "file": auditFile.Factory,
- },
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- TempDir: pluginDir,
- NumCores: numCores,
- Plugins: &vault.TestPluginConfig{
- Typ: typ,
- Versions: []string{""},
- },
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- vault.TestWaitActive(t, cluster.Cores[0].Core)
-
- return cluster
-}
-
-func getCluster(t *testing.T, typ consts.PluginType, numCores int) *vault.TestCluster {
- pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
- t.Cleanup(func() { cleanup(t) })
- coreConfig := &vault.CoreConfig{
- PluginDirectory: pluginDir,
- LogicalBackends: map[string]logical.Factory{
- "database": database.Factory,
- },
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- TempDir: pluginDir,
- NumCores: numCores,
- Plugins: &vault.TestPluginConfig{
- Typ: typ,
- Versions: []string{""},
- },
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- vault.TestWaitActive(t, cluster.Cores[0].Core)
-
- return cluster
-}
-
-// TestExternalPlugin_RollbackAndReload ensures that we can successfully
-// rollback and reload a plugin without triggering race conditions by the go
-// race detector
-func TestExternalPlugin_RollbackAndReload(t *testing.T) {
- pluginDir, cleanup := corehelpers.MakeTestPluginDir(t)
- t.Cleanup(func() { cleanup(t) })
- coreConfig := &vault.CoreConfig{
- // set rollback period to a short interval to make conditions more "racy"
- RollbackPeriod: 1 * time.Second,
- PluginDirectory: pluginDir,
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- TempDir: pluginDir,
- NumCores: 1,
- Plugins: &vault.TestPluginConfig{
- Typ: consts.PluginTypeSecrets,
- Versions: []string{""},
- },
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- vault.TestWaitActive(t, cluster.Cores[0].Core)
-
- core := cluster.Cores[0]
- plugin := cluster.Plugins[0]
- client := core.Client
- client.SetToken(cluster.RootToken)
-
- testRegisterAndEnable(t, client, plugin)
- if _, err := client.Sys().ReloadPlugin(&api.ReloadPluginInput{
- Plugin: plugin.Name,
- }); err != nil {
- t.Fatal(err)
- }
-}
-
-func testRegisterAndEnable(t *testing.T, client *api.Client, plugin pluginhelpers.TestPlugin) {
- t.Helper()
- if err := client.Sys().RegisterPlugin(&api.RegisterPluginInput{
- Name: plugin.Name,
- Type: api.PluginType(plugin.Typ),
- Command: plugin.Name,
- SHA256: plugin.Sha256,
- Version: plugin.Version,
- }); err != nil {
- t.Fatal(err)
- }
-
- switch plugin.Typ {
- case consts.PluginTypeSecrets:
- if err := client.Sys().Mount(plugin.Name, &api.MountInput{
- Type: plugin.Name,
- }); err != nil {
- t.Fatal(err)
- }
- case consts.PluginTypeCredential:
- if err := client.Sys().EnableAuthWithOptions(plugin.Name, &api.EnableAuthOptions{
- Type: plugin.Name,
- }); err != nil {
- t.Fatal(err)
- }
- }
-}
-
-// TestExternalPlugin_ContinueOnError tests that vault can recover from a
-// sha256 mismatch or missing plugin binary scenario
-func TestExternalPlugin_ContinueOnError(t *testing.T) {
- t.Run("secret", func(t *testing.T) {
- t.Parallel()
- t.Run("sha256_mismatch", func(t *testing.T) {
- t.Parallel()
- testExternalPlugin_ContinueOnError(t, true, consts.PluginTypeSecrets)
- })
-
- t.Run("missing_plugin", func(t *testing.T) {
- t.Parallel()
- testExternalPlugin_ContinueOnError(t, false, consts.PluginTypeSecrets)
- })
- })
-
- t.Run("auth", func(t *testing.T) {
- t.Parallel()
- t.Run("sha256_mismatch", func(t *testing.T) {
- t.Parallel()
- testExternalPlugin_ContinueOnError(t, true, consts.PluginTypeCredential)
- })
-
- t.Run("missing_plugin", func(t *testing.T) {
- t.Parallel()
- testExternalPlugin_ContinueOnError(t, false, consts.PluginTypeCredential)
- })
- })
-}
-
-func testExternalPlugin_ContinueOnError(t *testing.T, mismatch bool, pluginType consts.PluginType) {
- cluster := getCluster(t, pluginType, 1)
- defer cluster.Cleanup()
-
- core := cluster.Cores[0]
- plugin := cluster.Plugins[0]
- client := core.Client
- client.SetToken(cluster.RootToken)
-
- testRegisterAndEnable(t, client, plugin)
- pluginPath := fmt.Sprintf("sys/plugins/catalog/%s/%s", pluginType, plugin.Name)
- // Get the registered plugin
- req := logical.TestRequest(t, logical.ReadOperation, pluginPath)
- req.ClientToken = core.Client.Token()
- resp, err := core.HandleRequest(namespace.RootContext(testCtx), req)
- if err != nil || resp == nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- command, ok := resp.Data["command"].(string)
- if !ok || command == "" {
- t.Fatal("invalid command")
- }
-
- // Trigger a sha256 mismatch or missing plugin error
- if mismatch {
- req = logical.TestRequest(t, logical.UpdateOperation, pluginPath)
- req.Data = map[string]interface{}{
- "sha256": "d17bd7334758e53e6fbab15745d2520765c06e296f2ce8e25b7919effa0ac216",
- "command": filepath.Base(command),
- }
- req.ClientToken = core.Client.Token()
- resp, err = core.HandleRequest(namespace.RootContext(testCtx), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- } else {
- err := os.Remove(filepath.Join(cluster.TempDir, filepath.Base(command)))
- if err != nil {
- t.Fatal(err)
- }
- }
-
- // Seal the cluster
- cluster.EnsureCoresSealed(t)
-
- // Unseal the cluster
- barrierKeys := cluster.BarrierKeys
- for _, core := range cluster.Cores {
- for _, key := range barrierKeys {
- _, err := core.Unseal(vault.TestKeyCopy(key))
- if err != nil {
- t.Fatal(err)
- }
- }
- if core.Sealed() {
- t.Fatal("should not be sealed")
- }
- }
-
- // Wait for active so post-unseal takes place
- // If it fails, it means unseal process failed
- vault.TestWaitActive(t, core.Core)
-
- // unmount
- switch pluginType {
- case consts.PluginTypeSecrets:
- if err := client.Sys().Unmount(plugin.Name); err != nil {
- t.Fatal(err)
- }
- case consts.PluginTypeCredential:
- if err := client.Sys().DisableAuth(plugin.Name); err != nil {
- t.Fatal(err)
- }
- }
-
- // Re-compile plugin
- var plugins []pluginhelpers.TestPlugin
- plugins = append(plugins, pluginhelpers.CompilePlugin(t, pluginType, "", core.CoreConfig.PluginDirectory))
- cluster.Plugins = plugins
-
- // Re-add the plugin to the catalog
- testRegisterAndEnable(t, client, plugin)
-
- // Reload the plugin
- req = logical.TestRequest(t, logical.UpdateOperation, "sys/plugins/reload/backend")
- req.Data = map[string]interface{}{
- "plugin": plugin.Name,
- }
- req.ClientToken = core.Client.Token()
- resp, err = core.HandleRequest(namespace.RootContext(testCtx), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, pluginPath)
- req.ClientToken = core.Client.Token()
- resp, err = core.HandleRequest(namespace.RootContext(testCtx), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatalf("bad: response should not be nil")
- }
-}
-
-// TestExternalPlugin_AuthMethod tests that we can build, register and use an
-// external auth method
-func TestExternalPlugin_AuthMethod(t *testing.T) {
- cluster := getCluster(t, consts.PluginTypeCredential, 5)
- defer cluster.Cleanup()
-
- plugin := cluster.Plugins[0]
- client := cluster.Cores[0].Client
- client.SetToken(cluster.RootToken)
-
- // Register
- if err := client.Sys().RegisterPlugin(&api.RegisterPluginInput{
- Name: plugin.Name,
- Type: api.PluginType(plugin.Typ),
- Command: plugin.Name,
- SHA256: plugin.Sha256,
- Version: plugin.Version,
- }); err != nil {
- t.Fatal(err)
- }
-
- // define a group of parallel tests so we wait for their execution before
- // continuing on to cleanup
- // see: https://go.dev/blog/subtests
- t.Run("parallel execution group", func(t *testing.T) {
- // loop to mount 5 auth methods that will each share a single
- // plugin process
- for i := 0; i < 5; i++ {
- i := i
- pluginPath := fmt.Sprintf("%s-%d", plugin.Name, i)
- client := cluster.Cores[i].Client
- t.Run(pluginPath, func(t *testing.T) {
- t.Parallel()
- client.SetToken(cluster.RootToken)
- // Enable
- if err := client.Sys().EnableAuthWithOptions(pluginPath, &api.EnableAuthOptions{
- Type: plugin.Name,
- }); err != nil {
- t.Fatal(err)
- }
-
- // Configure
- _, err := client.Logical().Write("auth/"+pluginPath+"/role/role1", map[string]interface{}{
- "bind_secret_id": "true",
- "period": "300",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- secret, err := client.Logical().Write("auth/"+pluginPath+"/role/role1/secret-id", nil)
- if err != nil {
- t.Fatal(err)
- }
- secretID := secret.Data["secret_id"].(string)
-
- secret, err = client.Logical().Read("auth/" + pluginPath + "/role/role1/role-id")
- if err != nil {
- t.Fatal(err)
- }
- roleID := secret.Data["role_id"].(string)
-
- // Login - expect SUCCESS
- authMethod, err := approle.NewAppRoleAuth(
- roleID,
- &approle.SecretID{FromString: secretID},
- approle.WithMountPath(pluginPath),
- )
- if err != nil {
- t.Fatal(err)
- }
- _, err = client.Auth().Login(context.Background(), authMethod)
- if err != nil {
- t.Fatal(err)
- }
-
- // Renew
- resp, err := client.Auth().Token().RenewSelf(30)
- if err != nil {
- t.Fatal(err)
- }
-
- // Login - expect SUCCESS
- resp, err = client.Auth().Login(context.Background(), authMethod)
- if err != nil {
- t.Fatal(err)
- }
-
- revokeToken := resp.Auth.ClientToken
- // Revoke
- if err = client.Auth().Token().RevokeSelf(revokeToken); err != nil {
- t.Fatal(err)
- }
-
- // Reset root token
- client.SetToken(cluster.RootToken)
-
- // Lookup - expect FAILURE
- resp, err = client.Auth().Token().Lookup(revokeToken)
- if err == nil {
- t.Fatalf("expected error, got nil")
- }
-
- // Reset root token
- client.SetToken(cluster.RootToken)
- })
- }
- })
-
- // Deregister
- if err := client.Sys().DeregisterPlugin(&api.DeregisterPluginInput{
- Name: plugin.Name,
- Type: api.PluginType(plugin.Typ),
- Version: plugin.Version,
- }); err != nil {
- t.Fatal(err)
- }
-}
-
-// TestExternalPlugin_AuthMethodReload tests that we can use an external auth
-// method after reload
-func TestExternalPlugin_AuthMethodReload(t *testing.T) {
- cluster := getCluster(t, consts.PluginTypeCredential, 1)
- defer cluster.Cleanup()
-
- plugin := cluster.Plugins[0]
- client := cluster.Cores[0].Client
- client.SetToken(cluster.RootToken)
-
- testRegisterAndEnable(t, client, plugin)
-
- // Configure
- _, err := client.Logical().Write("auth/"+plugin.Name+"/role/role1", map[string]interface{}{
- "bind_secret_id": "true",
- "period": "300",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- secret, err := client.Logical().Write("auth/"+plugin.Name+"/role/role1/secret-id", nil)
- if err != nil {
- t.Fatal(err)
- }
- secretID := secret.Data["secret_id"].(string)
-
- secret, err = client.Logical().Read("auth/" + plugin.Name + "/role/role1/role-id")
- if err != nil {
- t.Fatal(err)
- }
- roleID := secret.Data["role_id"].(string)
-
- // Login - expect SUCCESS
- authMethod, err := approle.NewAppRoleAuth(
- roleID,
- &approle.SecretID{FromString: secretID},
- approle.WithMountPath(plugin.Name),
- )
- if err != nil {
- t.Fatal(err)
- }
- _, err = client.Auth().Login(context.Background(), authMethod)
- if err != nil {
- t.Fatal(err)
- }
-
- // Reset root token
- client.SetToken(cluster.RootToken)
-
- // Reload plugin
- if _, err := client.Sys().ReloadPlugin(&api.ReloadPluginInput{
- Plugin: plugin.Name,
- }); err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Auth().Login(context.Background(), authMethod)
- if err != nil {
- t.Fatal(err)
- }
-
- // Reset root token
- client.SetToken(cluster.RootToken)
-
- // Deregister
- if err := client.Sys().DeregisterPlugin(&api.DeregisterPluginInput{
- Name: plugin.Name,
- Type: api.PluginType(plugin.Typ),
- Version: plugin.Version,
- }); err != nil {
- t.Fatal(err)
- }
-}
-
-// TestExternalPlugin_SecretsEngine tests that we can build, register and use an
-// external secrets engine
-func TestExternalPlugin_SecretsEngine(t *testing.T) {
- cluster := getCluster(t, consts.PluginTypeSecrets, 1)
- defer cluster.Cleanup()
-
- plugin := cluster.Plugins[0]
- client := cluster.Cores[0].Client
- client.SetToken(cluster.RootToken)
-
- // Register
- if err := client.Sys().RegisterPlugin(&api.RegisterPluginInput{
- Name: plugin.Name,
- Type: api.PluginType(plugin.Typ),
- Command: plugin.Name,
- SHA256: plugin.Sha256,
- Version: plugin.Version,
- }); err != nil {
- t.Fatal(err)
- }
-
- // define a group of parallel tests so we wait for their execution before
- // continuing on to cleanup
- // see: https://go.dev/blog/subtests
- t.Run("parallel execution group", func(t *testing.T) {
- // loop to mount 5 secrets engines that will each share a single
- // plugin process
- for i := 0; i < 5; i++ {
- pluginPath := fmt.Sprintf("%s-%d", plugin.Name, i)
- t.Run(pluginPath, func(t *testing.T) {
- t.Parallel()
- // Enable
- if err := client.Sys().Mount(pluginPath, &api.MountInput{
- Type: plugin.Name,
- }); err != nil {
- t.Fatal(err)
- }
-
- // Configure
- cleanupConsul, consulConfig := consul.PrepareTestContainer(t, "", false, true)
- defer cleanupConsul()
-
- _, err := client.Logical().Write(pluginPath+"/config/access", map[string]interface{}{
- "address": consulConfig.Address(),
- "token": consulConfig.Token,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write(pluginPath+"/roles/test", map[string]interface{}{
- "consul_policies": []string{"test"},
- "ttl": "6h",
- "local": false,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- resp, err := client.Logical().Read(pluginPath + "/creds/test")
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("read creds response is nil")
- }
- })
- }
- })
-
- // Deregister
- if err := client.Sys().DeregisterPlugin(&api.DeregisterPluginInput{
- Name: plugin.Name,
- Type: api.PluginType(plugin.Typ),
- Version: plugin.Version,
- }); err != nil {
- t.Fatal(err)
- }
-}
-
-// TestExternalPlugin_SecretsEngineReload tests that we can use an external
-// secrets engine after reload
-func TestExternalPlugin_SecretsEngineReload(t *testing.T) {
- cluster := getCluster(t, consts.PluginTypeSecrets, 1)
- defer cluster.Cleanup()
-
- plugin := cluster.Plugins[0]
- client := cluster.Cores[0].Client
- client.SetToken(cluster.RootToken)
-
- testRegisterAndEnable(t, client, plugin)
-
- // Configure
- cleanupConsul, consulConfig := consul.PrepareTestContainer(t, "", false, true)
- defer cleanupConsul()
-
- _, err := client.Logical().Write(plugin.Name+"/config/access", map[string]interface{}{
- "address": consulConfig.Address(),
- "token": consulConfig.Token,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write(plugin.Name+"/roles/test", map[string]interface{}{
- "consul_policies": []string{"test"},
- "ttl": "6h",
- "local": false,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- resp, err := client.Logical().Read(plugin.Name + "/creds/test")
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("read creds response is nil")
- }
-
- // Reload plugin
- if _, err := client.Sys().ReloadPlugin(&api.ReloadPluginInput{
- Plugin: plugin.Name,
- }); err != nil {
- t.Fatal(err)
- }
-
- resp, err = client.Logical().Read(plugin.Name + "/creds/test")
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("read creds response is nil")
- }
-
- // Deregister
- if err := client.Sys().DeregisterPlugin(&api.DeregisterPluginInput{
- Name: plugin.Name,
- Type: api.PluginType(plugin.Typ),
- Version: plugin.Version,
- }); err != nil {
- t.Fatal(err)
- }
-}
-
-// TestExternalPlugin_Database tests that we can build, register and use an
-// external database secrets engine
-func TestExternalPlugin_Database(t *testing.T) {
- cluster := getCluster(t, consts.PluginTypeDatabase, 1)
- defer cluster.Cleanup()
-
- plugin := cluster.Plugins[0]
- client := cluster.Cores[0].Client
- client.SetToken(cluster.RootToken)
-
- // Register
- if err := client.Sys().RegisterPlugin(&api.RegisterPluginInput{
- Name: plugin.Name,
- Type: api.PluginType(consts.PluginTypeDatabase),
- Command: plugin.Name,
- SHA256: plugin.Sha256,
- Version: plugin.Version,
- }); err != nil {
- t.Fatal(err)
- }
-
- // Enable
- if err := client.Sys().Mount(consts.PluginTypeDatabase.String(), &api.MountInput{
- Type: consts.PluginTypeDatabase.String(),
- }); err != nil {
- t.Fatal(err)
- }
-
- // define a group of parallel tests so we wait for their execution before
- // continuing on to cleanup
- // see: https://go.dev/blog/subtests
- t.Run("parallel execution group", func(t *testing.T) {
- // loop to mount 5 database connections that will each share a single
- // plugin process
- for i := 0; i < 5; i++ {
- dbName := fmt.Sprintf("%s-%d", plugin.Name, i)
- t.Run(dbName, func(t *testing.T) {
- t.Parallel()
- roleName := "test-role-" + dbName
-
- cleanupContainer, connURL := postgreshelper.PrepareTestContainerWithVaultUser(t, context.Background(), "13.4-buster")
- defer cleanupContainer()
-
- _, err := client.Logical().Write("database/config/"+dbName, map[string]interface{}{
- "connection_url": connURL,
- "plugin_name": plugin.Name,
- "allowed_roles": []string{roleName},
- "username": "vaultadmin",
- "password": "vaultpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("database/rotate-root/"+dbName, map[string]interface{}{})
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("database/roles/"+roleName, map[string]interface{}{
- "db_name": dbName,
- "creation_statements": testRole,
- "max_ttl": "10m",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Generate credentials
- resp, err := client.Logical().Read("database/creds/" + roleName)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("read creds response is nil")
- }
-
- _, err = client.Logical().Write("database/reset/"+dbName, map[string]interface{}{})
- if err != nil {
- t.Fatal(err)
- }
-
- // Generate credentials
- resp, err = client.Logical().Read("database/creds/" + roleName)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("read creds response is nil")
- }
-
- resp, err = client.Logical().Read("database/creds/" + roleName)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("read creds response is nil")
- }
-
- revokeLease := resp.LeaseID
- // Lookup - expect SUCCESS
- resp, err = client.Sys().Lookup(revokeLease)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatalf("lease lookup response is nil")
- }
-
- // Revoke
- if err = client.Sys().Revoke(revokeLease); err != nil {
- t.Fatal(err)
- }
-
- // Reset root token
- client.SetToken(cluster.RootToken)
-
- // Lookup - expect FAILURE
- resp, err = client.Sys().Lookup(revokeLease)
- if err == nil {
- t.Fatalf("expected error, got nil")
- }
- })
- }
- })
-
- // Deregister
- if err := client.Sys().DeregisterPlugin(&api.DeregisterPluginInput{
- Name: plugin.Name,
- Type: api.PluginType(plugin.Typ),
- Version: plugin.Version,
- }); err != nil {
- t.Fatal(err)
- }
-}
-
-// TestExternalPlugin_DatabaseReload tests that we can use an external database
-// secrets engine after reload
-func TestExternalPlugin_DatabaseReload(t *testing.T) {
- cluster := getCluster(t, consts.PluginTypeDatabase, 1)
- defer cluster.Cleanup()
-
- plugin := cluster.Plugins[0]
- client := cluster.Cores[0].Client
- client.SetToken(cluster.RootToken)
-
- // Register
- if err := client.Sys().RegisterPlugin(&api.RegisterPluginInput{
- Name: plugin.Name,
- Type: api.PluginType(consts.PluginTypeDatabase),
- Command: plugin.Name,
- SHA256: plugin.Sha256,
- Version: plugin.Version,
- }); err != nil {
- t.Fatal(err)
- }
-
- // Enable
- if err := client.Sys().Mount(consts.PluginTypeDatabase.String(), &api.MountInput{
- Type: consts.PluginTypeDatabase.String(),
- }); err != nil {
- t.Fatal(err)
- }
-
- dbName := fmt.Sprintf("%s-%d", plugin.Name, 0)
- roleName := "test-role-" + dbName
-
- cleanupContainer, connURL := postgreshelper.PrepareTestContainerWithVaultUser(t, context.Background(), "13.4-buster")
- defer cleanupContainer()
-
- _, err := client.Logical().Write("database/config/"+dbName, map[string]interface{}{
- "connection_url": connURL,
- "plugin_name": plugin.Name,
- "allowed_roles": []string{roleName},
- "username": "vaultadmin",
- "password": "vaultpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("database/roles/"+roleName, map[string]interface{}{
- "db_name": dbName,
- "creation_statements": testRole,
- "max_ttl": "10m",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- resp, err := client.Logical().Read("database/creds/" + roleName)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("read creds response is nil")
- }
-
- // Reload plugin
- if _, err := client.Sys().ReloadPlugin(&api.ReloadPluginInput{
- Plugin: plugin.Name,
- }); err != nil {
- t.Fatal(err)
- }
-
- // Generate credentials after reload
- resp, err = client.Logical().Read("database/creds/" + roleName)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("read creds response is nil")
- }
-
- // Deregister
- if err := client.Sys().DeregisterPlugin(&api.DeregisterPluginInput{
- Name: plugin.Name,
- Type: api.PluginType(plugin.Typ),
- Version: plugin.Version,
- }); err != nil {
- t.Fatal(err)
- }
-}
-
-const testRole = `
-CREATE ROLE "{{name}}" WITH
- LOGIN
- PASSWORD '{{password}}'
- VALID UNTIL '{{expiration}}';
-GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}";
-`
-
-func testExternalPluginMetadataAuditLog(t *testing.T, log map[string]interface{}, expectedMountClass string) {
- if mountClass, ok := log["mount_class"].(string); !ok {
- t.Fatalf("mount_class should be a string, not %T", log["mount_class"])
- } else if mountClass != expectedMountClass {
- t.Fatalf("bad: mount_class should be %s, not %s", expectedMountClass, mountClass)
- }
-
- if mountIsExternalPlugin, ok := log["mount_is_external_plugin"].(bool); !ok {
- t.Fatalf("mount_is_external_plugin should be a bool, not %T", log["mount_is_external_plugin"])
- } else if !mountIsExternalPlugin {
- t.Fatalf("bad: mount_is_external_plugin should be true, not %t", mountIsExternalPlugin)
- }
-
- if _, ok := log["mount_running_sha256"].(string); !ok {
- t.Fatalf("mount_running_sha256 should be a string, not %T", log["mount_running_sha256"])
- }
-}
-
-// TestExternalPlugin_AuditEnabled_ShouldLogPluginMetadata_Auth tests that we have plugin metadata of an auth plugin
-// in audit log when it is enabled
-func TestExternalPlugin_AuditEnabled_ShouldLogPluginMetadata_Auth(t *testing.T) {
- cluster := getClusterWithFileAuditBackend(t, consts.PluginTypeCredential, 1)
- defer cluster.Cleanup()
-
- plugin := cluster.Plugins[0]
- client := cluster.Cores[0].Client
- client.SetToken(cluster.RootToken)
-
- testRegisterAndEnable(t, client, plugin)
-
- // Enable the audit backend
- tempDir := t.TempDir()
- auditLogFile, err := os.CreateTemp(tempDir, "")
- if err != nil {
- t.Fatal(err)
- }
-
- err = client.Sys().EnableAuditWithOptions("file", &api.EnableAuditOptions{
- Type: "file",
- Options: map[string]string{
- "file_path": auditLogFile.Name(),
- },
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("auth/"+plugin.Name+"/role/role1", map[string]interface{}{
- "bind_secret_id": "true",
- "period": "300",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Check the audit trail on request and response
- decoder := json.NewDecoder(auditLogFile)
- var auditRecord map[string]interface{}
- for decoder.Decode(&auditRecord) == nil {
- auditRequest := map[string]interface{}{}
- if req, ok := auditRecord["request"]; ok {
- auditRequest = req.(map[string]interface{})
- if auditRequest["path"] != "auth/"+plugin.Name+"/role/role1" {
- continue
- }
- }
- testExternalPluginMetadataAuditLog(t, auditRequest, consts.PluginTypeCredential.String())
-
- auditResponse := map[string]interface{}{}
- if req, ok := auditRecord["response"]; ok {
- auditRequest = req.(map[string]interface{})
- if auditResponse["path"] != "auth/"+plugin.Name+"/role/role1" {
- continue
- }
- }
- testExternalPluginMetadataAuditLog(t, auditResponse, consts.PluginTypeCredential.String())
- }
-
- // Deregister
- if err := client.Sys().DeregisterPlugin(&api.DeregisterPluginInput{
- Name: plugin.Name,
- Type: api.PluginType(plugin.Typ),
- Version: plugin.Version,
- }); err != nil {
- t.Fatal(err)
- }
-}
-
-// TestExternalPlugin_AuditEnabled_ShouldLogPluginMetadata_Secret tests that we have plugin metadata of a secret plugin
-// in audit log when it is enabled
-func TestExternalPlugin_AuditEnabled_ShouldLogPluginMetadata_Secret(t *testing.T) {
- cluster := getClusterWithFileAuditBackend(t, consts.PluginTypeSecrets, 1)
- defer cluster.Cleanup()
-
- plugin := cluster.Plugins[0]
- client := cluster.Cores[0].Client
- client.SetToken(cluster.RootToken)
-
- testRegisterAndEnable(t, client, plugin)
-
- // Enable the audit backend
- tempDir := t.TempDir()
- auditLogFile, err := os.CreateTemp(tempDir, "")
- if err != nil {
- t.Fatal(err)
- }
-
- err = client.Sys().EnableAuditWithOptions("file", &api.EnableAuditOptions{
- Type: "file",
- Options: map[string]string{
- "file_path": auditLogFile.Name(),
- },
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Configure
- cleanupConsul, consulConfig := consul.PrepareTestContainer(t, "", false, true)
- defer cleanupConsul()
- _, err = client.Logical().Write(plugin.Name+"/config/access", map[string]interface{}{
- "address": consulConfig.Address(),
- "token": consulConfig.Token,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Check the audit trail on request and response
- decoder := json.NewDecoder(auditLogFile)
- var auditRecord map[string]interface{}
- for decoder.Decode(&auditRecord) == nil {
- auditRequest := map[string]interface{}{}
- if req, ok := auditRecord["request"]; ok {
- auditRequest = req.(map[string]interface{})
- if auditRequest["path"] != plugin.Name+"/config/access" {
- continue
- }
- }
- testExternalPluginMetadataAuditLog(t, auditRequest, consts.PluginTypeSecrets.String())
-
- auditResponse := map[string]interface{}{}
- if req, ok := auditRecord["response"]; ok {
- auditRequest = req.(map[string]interface{})
- if auditResponse["path"] != plugin.Name+"/config/access" {
- continue
- }
- }
- testExternalPluginMetadataAuditLog(t, auditResponse, consts.PluginTypeSecrets.String())
- }
-
- // Deregister
- if err := client.Sys().DeregisterPlugin(&api.DeregisterPluginInput{
- Name: plugin.Name,
- Type: api.PluginType(plugin.Typ),
- Version: plugin.Version,
- }); err != nil {
- t.Fatal(err)
- }
-}
diff --git a/vault/external_tests/plugin/plugin_test.go b/vault/external_tests/plugin/plugin_test.go
deleted file mode 100644
index 80486f862..000000000
--- a/vault/external_tests/plugin/plugin_test.go
+++ /dev/null
@@ -1,918 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package plugin_test
-
-import (
- "context"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/builtin/plugin"
- "github.com/hashicorp/vault/helper/namespace"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/helper/consts"
- "github.com/hashicorp/vault/sdk/helper/pluginutil"
- "github.com/hashicorp/vault/sdk/logical"
- lplugin "github.com/hashicorp/vault/sdk/plugin"
- "github.com/hashicorp/vault/sdk/plugin/mock"
- "github.com/hashicorp/vault/vault"
-)
-
-const (
- expectedEnvKey = "FOO"
- expectedEnvValue = "BAR"
-)
-
-// logicalVersionMap is a map of version to test plugin
-var logicalVersionMap = map[string]string{
- "v4": "TestBackend_PluginMain_V4_Logical",
- "v5": "TestBackend_PluginMainLogical",
- "v5_multiplexed": "TestBackend_PluginMain_Multiplexed_Logical",
-}
-
-// credentialVersionMap is a map of version to test plugin
-var credentialVersionMap = map[string]string{
- "v4": "TestBackend_PluginMain_V4_Credentials",
- "v5": "TestBackend_PluginMainCredentials",
- "v5_multiplexed": "TestBackend_PluginMain_Multiplexed_Credentials",
-}
-
-var testCtx = context.TODO()
-
-func TestSystemBackend_Plugin_secret(t *testing.T) {
- t.Parallel()
- testCases := []struct {
- pluginVersion string
- }{
- {
- pluginVersion: "v5_multiplexed",
- },
- {
- pluginVersion: "v5",
- },
- {
- pluginVersion: "v4",
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.pluginVersion, func(t *testing.T) {
- t.Parallel()
- cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical, tc.pluginVersion)
- defer cluster.Cleanup()
-
- core := cluster.Cores[0]
-
- // Make a request to lazy load the plugin
- req := logical.TestRequest(t, logical.ReadOperation, "mock-0/internal")
- req.ClientToken = core.Client.Token()
- resp, err := core.HandleRequest(namespace.RootContext(testCtx), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatalf("bad: response should not be nil")
- }
-
- // Seal the cluster
- cluster.EnsureCoresSealed(t)
-
- // Unseal the cluster
- barrierKeys := cluster.BarrierKeys
- for _, core := range cluster.Cores {
- for _, key := range barrierKeys {
- _, err := core.Unseal(vault.TestKeyCopy(key))
- if err != nil {
- t.Fatal(err)
- }
- }
- if core.Sealed() {
- t.Fatal("should not be sealed")
- }
- // Wait for active so post-unseal takes place
- // If it fails, it means unseal process failed
- vault.TestWaitActive(t, core.Core)
- }
- })
- }
-}
-
-func TestSystemBackend_Plugin_auth(t *testing.T) {
- t.Parallel()
- testCases := []struct {
- pluginVersion string
- }{
- {
- pluginVersion: "v5_multiplexed",
- },
- {
- pluginVersion: "v5",
- },
- {
- pluginVersion: "v4",
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.pluginVersion, func(t *testing.T) {
- t.Parallel()
- cluster := testSystemBackendMock(t, 1, 1, logical.TypeCredential, tc.pluginVersion)
- defer cluster.Cleanup()
-
- core := cluster.Cores[0]
-
- // Make a request to lazy load the plugin
- req := logical.TestRequest(t, logical.ReadOperation, "auth/mock-0/internal")
- req.ClientToken = core.Client.Token()
- resp, err := core.HandleRequest(namespace.RootContext(testCtx), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatalf("bad: response should not be nil")
- }
-
- // Seal the cluster
- cluster.EnsureCoresSealed(t)
-
- // Unseal the cluster
- barrierKeys := cluster.BarrierKeys
- for _, core := range cluster.Cores {
- for _, key := range barrierKeys {
- _, err := core.Unseal(vault.TestKeyCopy(key))
- if err != nil {
- t.Fatal(err)
- }
- }
- if core.Sealed() {
- t.Fatal("should not be sealed")
- }
- // Wait for active so post-unseal takes place
- // If it fails, it means unseal process failed
- vault.TestWaitActive(t, core.Core)
- }
- })
- }
-}
-
-func TestSystemBackend_Plugin_MissingBinary(t *testing.T) {
- t.Parallel()
- testCases := []struct {
- pluginVersion string
- }{
- {
- pluginVersion: "v5_multiplexed",
- },
- {
- pluginVersion: "v5",
- },
- {
- pluginVersion: "v4",
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.pluginVersion, func(t *testing.T) {
- t.Parallel()
- cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical, tc.pluginVersion)
- defer cluster.Cleanup()
-
- core := cluster.Cores[0]
-
- // Make a request to lazy load the plugin
- req := logical.TestRequest(t, logical.ReadOperation, "mock-0/internal")
- req.ClientToken = core.Client.Token()
- resp, err := core.HandleRequest(namespace.RootContext(testCtx), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatalf("bad: response should not be nil")
- }
-
- // Seal the cluster
- cluster.EnsureCoresSealed(t)
-
- // Simulate removal of the plugin binary. Use os.Args to determine file name
- // since that's how we create the file for catalog registration in the test
- // helper.
- pluginFileName := filepath.Base(os.Args[0])
- err = os.Remove(filepath.Join(cluster.TempDir, pluginFileName))
- if err != nil {
- t.Fatal(err)
- }
-
- // Unseal the cluster
- cluster.UnsealCores(t)
-
- // Make a request against on tune after it is removed
- req = logical.TestRequest(t, logical.ReadOperation, "sys/mounts/mock-0/tune")
- req.ClientToken = core.Client.Token()
- _, err = core.HandleRequest(namespace.RootContext(testCtx), req)
- if err == nil {
- t.Fatalf("expected error")
- }
- })
- }
-}
-
-func TestSystemBackend_Plugin_MismatchType(t *testing.T) {
- t.Parallel()
- testCases := []struct {
- pluginVersion string
- }{
- {
- pluginVersion: "v5_multiplexed",
- },
- {
- pluginVersion: "v5",
- },
- {
- pluginVersion: "v4",
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.pluginVersion, func(t *testing.T) {
- cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical, tc.pluginVersion)
- defer cluster.Cleanup()
-
- core := cluster.Cores[0]
-
- // Add a credential backend with the same name
- vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, "", "TestBackend_PluginMainCredentials", []string{}, "")
-
- // Make a request to lazy load the now-credential plugin
- // and expect an error
- req := logical.TestRequest(t, logical.ReadOperation, "mock-0/internal")
- req.ClientToken = core.Client.Token()
- _, err := core.HandleRequest(namespace.RootContext(testCtx), req)
- if err != nil {
- t.Fatalf("adding a same-named plugin of a different type should be no problem: %s", err)
- }
-
- // Sleep a bit before cleanup is called
- time.Sleep(1 * time.Second)
- })
- }
-}
-
-func TestSystemBackend_Plugin_CatalogRemoved(t *testing.T) {
- t.Run("secret", func(t *testing.T) {
- t.Parallel()
- testPlugin_CatalogRemoved(t, logical.TypeLogical, false, logicalVersionMap)
- })
-
- t.Run("auth", func(t *testing.T) {
- t.Parallel()
- testPlugin_CatalogRemoved(t, logical.TypeCredential, false, credentialVersionMap)
- })
-
- t.Run("secret-mount-existing", func(t *testing.T) {
- t.Parallel()
- testPlugin_CatalogRemoved(t, logical.TypeLogical, true, logicalVersionMap)
- })
-
- t.Run("auth-mount-existing", func(t *testing.T) {
- t.Parallel()
- testPlugin_CatalogRemoved(t, logical.TypeCredential, true, credentialVersionMap)
- })
-}
-
-func testPlugin_CatalogRemoved(t *testing.T, btype logical.BackendType, testMount bool, versionMap map[string]string) {
- testCases := []struct {
- pluginVersion string
- }{
- {
- pluginVersion: "v5_multiplexed",
- },
- {
- pluginVersion: "v5",
- },
- {
- pluginVersion: "v4",
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.pluginVersion, func(t *testing.T) {
- t.Parallel()
- cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical, tc.pluginVersion)
- defer cluster.Cleanup()
-
- core := cluster.Cores[0]
-
- // Remove the plugin from the catalog
- req := logical.TestRequest(t, logical.DeleteOperation, "sys/plugins/catalog/database/mock-plugin")
- req.ClientToken = core.Client.Token()
- resp, err := core.HandleRequest(namespace.RootContext(testCtx), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- // Seal the cluster
- cluster.EnsureCoresSealed(t)
-
- // Unseal the cluster
- barrierKeys := cluster.BarrierKeys
- for _, core := range cluster.Cores {
- for _, key := range barrierKeys {
- _, err := core.Unseal(vault.TestKeyCopy(key))
- if err != nil {
- t.Fatal(err)
- }
- }
- if core.Sealed() {
- t.Fatal("should not be sealed")
- }
- }
-
- // Wait for active so post-unseal takes place
- // If it fails, it means unseal process failed
- vault.TestWaitActive(t, core.Core)
-
- if testMount {
- // Mount the plugin at the same path after plugin is re-added to the catalog
- // and expect an error due to existing path.
- var err error
- switch btype {
- case logical.TypeLogical:
- // Add plugin back to the catalog
- vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "", logicalVersionMap[tc.pluginVersion], []string{}, "")
- _, err = core.Client.Logical().Write("sys/mounts/mock-0", map[string]interface{}{
- "type": "test",
- })
- case logical.TypeCredential:
- // Add plugin back to the catalog
- vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, "", credentialVersionMap[tc.pluginVersion], []string{}, "")
- _, err = core.Client.Logical().Write("sys/auth/mock-0", map[string]interface{}{
- "type": "test",
- })
- }
- if err == nil {
- t.Fatal("expected error when mounting on existing path")
- }
- }
- })
- }
-}
-
-func TestSystemBackend_Plugin_autoReload(t *testing.T) {
- t.Parallel()
- testCases := []struct {
- pluginVersion string
- }{
- {
- pluginVersion: "v5_multiplexed",
- },
- {
- pluginVersion: "v5",
- },
- {
- pluginVersion: "v4",
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.pluginVersion, func(t *testing.T) {
- t.Parallel()
- cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical, tc.pluginVersion)
- defer cluster.Cleanup()
-
- core := cluster.Cores[0]
-
- // Update internal value
- req := logical.TestRequest(t, logical.UpdateOperation, "mock-0/internal")
- req.ClientToken = core.Client.Token()
- req.Data["value"] = "baz"
- resp, err := core.HandleRequest(namespace.RootContext(testCtx), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- // Call errors/rpc endpoint to trigger reload
- req = logical.TestRequest(t, logical.ReadOperation, "mock-0/errors/rpc")
- req.ClientToken = core.Client.Token()
- _, err = core.HandleRequest(namespace.RootContext(testCtx), req)
- if err == nil {
- t.Fatalf("expected error from error/rpc request")
- }
-
- // Check internal value to make sure it's reset
- req = logical.TestRequest(t, logical.ReadOperation, "mock-0/internal")
- req.ClientToken = core.Client.Token()
- resp, err = core.HandleRequest(namespace.RootContext(testCtx), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatalf("bad: response should not be nil")
- }
- if resp.Data["value"].(string) == "baz" {
- t.Fatal("did not expect backend internal value to be 'baz'")
- }
- })
- }
-}
-
-func TestSystemBackend_Plugin_SealUnseal(t *testing.T) {
- testCases := []struct {
- pluginVersion string
- }{
- {
- pluginVersion: "v5_multiplexed",
- },
- {
- pluginVersion: "v5",
- },
- {
- pluginVersion: "v4",
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.pluginVersion, func(t *testing.T) {
- t.Parallel()
- cluster := testSystemBackendMock(t, 1, 1, logical.TypeLogical, tc.pluginVersion)
- defer cluster.Cleanup()
-
- // Seal the cluster
- cluster.EnsureCoresSealed(t)
-
- // Unseal the cluster
- barrierKeys := cluster.BarrierKeys
- for _, core := range cluster.Cores {
- for _, key := range barrierKeys {
- _, err := core.Unseal(vault.TestKeyCopy(key))
- if err != nil {
- t.Fatal(err)
- }
- }
- if core.Sealed() {
- t.Fatal("should not be sealed")
- }
- }
-
- // Wait for active so post-unseal takes place
- // If it fails, it means unseal process failed
- vault.TestWaitActive(t, cluster.Cores[0].Core)
- })
- }
-}
-
-func TestSystemBackend_Plugin_reload(t *testing.T) {
- testCases := []struct {
- name string
- backendType logical.BackendType
- data map[string]interface{}
- }{
- {
- name: "test plugin reload for type credential",
- backendType: logical.TypeCredential,
- data: map[string]interface{}{
- "plugin": "mock-plugin",
- },
- },
- {
- name: "test mount reload for type credential",
- backendType: logical.TypeCredential,
- data: map[string]interface{}{
- "mounts": "sys/auth/mock-0/,auth/mock-1/",
- },
- },
- {
- name: "test plugin reload for type secret",
- backendType: logical.TypeLogical,
- data: map[string]interface{}{
- "plugin": "mock-plugin",
- },
- },
- {
- name: "test mount reload for type secret",
- backendType: logical.TypeLogical,
- data: map[string]interface{}{
- "mounts": "mock-0/,mock-1",
- },
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
- testSystemBackend_PluginReload(t, tc.data, tc.backendType)
- })
- }
-}
-
-// Helper func to test different reload methods on plugin reload endpoint
-func testSystemBackend_PluginReload(t *testing.T, reqData map[string]interface{}, backendType logical.BackendType) {
- testCases := []struct {
- pluginVersion string
- }{
- {
- pluginVersion: "v5_multiplexed",
- },
- {
- pluginVersion: "v5",
- },
- {
- pluginVersion: "v4",
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.pluginVersion, func(t *testing.T) {
- cluster := testSystemBackendMock(t, 1, 2, backendType, tc.pluginVersion)
- defer cluster.Cleanup()
-
- core := cluster.Cores[0]
- client := core.Client
-
- pathPrefix := "mock-"
- if backendType == logical.TypeCredential {
- pathPrefix = "auth/" + pathPrefix
- }
- for i := 0; i < 2; i++ {
- // Update internal value in the backend
- resp, err := client.Logical().Write(fmt.Sprintf("%s%d/internal", pathPrefix, i), map[string]interface{}{
- "value": "baz",
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
- }
-
- // Perform plugin reload
- resp, err := client.Logical().Write("sys/plugins/reload/backend", reqData)
- 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")
- }
-
- for i := 0; i < 2; i++ {
- // Ensure internal backed value is reset
- resp, err := client.Logical().Read(fmt.Sprintf("%s%d/internal", pathPrefix, i))
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatalf("bad: response should not be nil")
- }
- if resp.Data["value"].(string) == "baz" {
- t.Fatal("did not expect backend internal value to be 'baz'")
- }
- }
- })
- }
-}
-
-// testSystemBackendMock returns a systemBackend with the desired number
-// of mounted mock plugin backends. numMounts alternates between different
-// ways of providing the plugin_name.
-//
-// The mounts are mounted at sys/mounts/mock-[numMounts] or sys/auth/mock-[numMounts]
-func testSystemBackendMock(t *testing.T, numCores, numMounts int, backendType logical.BackendType, pluginVersion string) *vault.TestCluster {
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "plugin": plugin.Factory,
- },
- CredentialBackends: map[string]logical.Factory{
- "plugin": plugin.Factory,
- },
- }
-
- // Create a tempdir, cluster.Cleanup will clean up this directory
- tempDir, err := ioutil.TempDir("", "vault-test-cluster")
- if err != nil {
- t.Fatal(err)
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- KeepStandbysSealed: true,
- NumCores: numCores,
- TempDir: tempDir,
- })
- cluster.Start()
-
- core := cluster.Cores[0]
- vault.TestWaitActive(t, core.Core)
- client := core.Client
-
- env := []string{pluginutil.PluginCACertPEMEnv + "=" + cluster.CACertPEMFile}
-
- switch backendType {
- case logical.TypeLogical:
- plugin := logicalVersionMap[pluginVersion]
- vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "", plugin, env, tempDir)
- for i := 0; i < numMounts; i++ {
- // Alternate input styles for plugin_name on every other mount
- options := map[string]interface{}{
- "type": "mock-plugin",
- }
- resp, err := client.Logical().Write(fmt.Sprintf("sys/mounts/mock-%d", i), options)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
- }
- case logical.TypeCredential:
- plugin := credentialVersionMap[pluginVersion]
- vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeCredential, "", plugin, env, tempDir)
- for i := 0; i < numMounts; i++ {
- // Alternate input styles for plugin_name on every other mount
- options := map[string]interface{}{
- "type": "mock-plugin",
- }
- resp, err := client.Logical().Write(fmt.Sprintf("sys/auth/mock-%d", i), options)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
- }
- default:
- t.Fatal("unknown backend type provided")
- }
-
- return cluster
-}
-
-func TestSystemBackend_Plugin_Env(t *testing.T) {
- kvPair := fmt.Sprintf("%s=%s", expectedEnvKey, expectedEnvValue)
- cluster := testSystemBackend_SingleCluster_Env(t, []string{kvPair})
- defer cluster.Cleanup()
-}
-
-// testSystemBackend_SingleCluster_Env is a helper func that returns a single
-// cluster and a single mounted plugin logical backend.
-func testSystemBackend_SingleCluster_Env(t *testing.T, env []string) *vault.TestCluster {
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "test": plugin.Factory,
- },
- }
- // Create a tempdir, cluster.Cleanup will clean up this directory
- tempDir, err := ioutil.TempDir("", "vault-test-cluster")
- if err != nil {
- t.Fatal(err)
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- KeepStandbysSealed: true,
- NumCores: 1,
- TempDir: tempDir,
- })
- cluster.Start()
-
- core := cluster.Cores[0]
- vault.TestWaitActive(t, core.Core)
- client := core.Client
-
- env = append([]string{pluginutil.PluginCACertPEMEnv + "=" + cluster.CACertPEMFile}, env...)
- vault.TestAddTestPlugin(t, core.Core, "mock-plugin", consts.PluginTypeSecrets, "", "TestBackend_PluginMainEnv", env, tempDir)
- options := map[string]interface{}{
- "type": "mock-plugin",
- }
-
- resp, err := client.Logical().Write("sys/mounts/mock", options)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- return cluster
-}
-
-func TestBackend_PluginMain_V4_Logical(t *testing.T) {
- args := []string{}
- // don't run as a standalone unit test
- if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
- return
- }
-
- // don't run as a V5 plugin
- if os.Getenv(pluginutil.PluginAutoMTLSEnv) == "true" {
- return
- }
-
- caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
- if caPEM == "" {
- t.Fatal("CA cert not passed in")
- }
- args = append(args, fmt.Sprintf("--ca-cert=%s", caPEM))
-
- apiClientMeta := &api.PluginAPIClientMeta{}
- flags := apiClientMeta.FlagSet()
- flags.Parse(args)
-
- // V4 does not support AutoMTLS so we set a TLSConfig via TLSProviderFunc
- tlsConfig := apiClientMeta.GetTLSConfig()
- tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig)
-
- factoryFunc := mock.FactoryType(logical.TypeLogical)
-
- err := lplugin.Serve(&lplugin.ServeOpts{
- BackendFactoryFunc: factoryFunc,
- TLSProviderFunc: tlsProviderFunc,
- })
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestBackend_PluginMain_Multiplexed_Logical(t *testing.T) {
- args := []string{}
- if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
- return
- }
-
- caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
- if caPEM == "" {
- t.Fatal("CA cert not passed in")
- }
- args = append(args, fmt.Sprintf("--ca-cert=%s", caPEM))
-
- apiClientMeta := &api.PluginAPIClientMeta{}
- flags := apiClientMeta.FlagSet()
- flags.Parse(args)
-
- factoryFunc := mock.FactoryType(logical.TypeLogical)
-
- err := lplugin.ServeMultiplex(&lplugin.ServeOpts{
- BackendFactoryFunc: factoryFunc,
- })
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestBackend_PluginMainLogical(t *testing.T) {
- args := []string{}
- if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
- return
- }
-
- caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
- if caPEM == "" {
- t.Fatal("CA cert not passed in")
- }
- args = append(args, fmt.Sprintf("--ca-cert=%s", caPEM))
-
- apiClientMeta := &api.PluginAPIClientMeta{}
- flags := apiClientMeta.FlagSet()
- flags.Parse(args)
-
- factoryFunc := mock.FactoryType(logical.TypeLogical)
-
- err := lplugin.Serve(&lplugin.ServeOpts{
- BackendFactoryFunc: factoryFunc,
- })
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestBackend_PluginMain_V4_Credentials(t *testing.T) {
- args := []string{}
- // don't run as a standalone unit test
- if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
- return
- }
-
- // don't run as a V5 plugin
- if os.Getenv(pluginutil.PluginAutoMTLSEnv) == "true" {
- return
- }
-
- caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
- if caPEM == "" {
- t.Fatal("CA cert not passed in")
- }
- args = append(args, fmt.Sprintf("--ca-cert=%s", caPEM))
-
- apiClientMeta := &api.PluginAPIClientMeta{}
- flags := apiClientMeta.FlagSet()
- flags.Parse(args)
-
- // V4 does not support AutoMTLS so we set a TLSConfig via TLSProviderFunc
- tlsConfig := apiClientMeta.GetTLSConfig()
- tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig)
-
- factoryFunc := mock.FactoryType(logical.TypeCredential)
-
- err := lplugin.Serve(&lplugin.ServeOpts{
- BackendFactoryFunc: factoryFunc,
- TLSProviderFunc: tlsProviderFunc,
- })
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestBackend_PluginMain_Multiplexed_Credentials(t *testing.T) {
- args := []string{}
- if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
- return
- }
-
- caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
- if caPEM == "" {
- t.Fatal("CA cert not passed in")
- }
- args = append(args, fmt.Sprintf("--ca-cert=%s", caPEM))
-
- apiClientMeta := &api.PluginAPIClientMeta{}
- flags := apiClientMeta.FlagSet()
- flags.Parse(args)
-
- factoryFunc := mock.FactoryType(logical.TypeCredential)
-
- err := lplugin.ServeMultiplex(&lplugin.ServeOpts{
- BackendFactoryFunc: factoryFunc,
- })
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestBackend_PluginMainCredentials(t *testing.T) {
- args := []string{}
- if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
- return
- }
-
- caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
- if caPEM == "" {
- t.Fatal("CA cert not passed in")
- }
- args = append(args, fmt.Sprintf("--ca-cert=%s", caPEM))
-
- apiClientMeta := &api.PluginAPIClientMeta{}
- flags := apiClientMeta.FlagSet()
- flags.Parse(args)
-
- factoryFunc := mock.FactoryType(logical.TypeCredential)
-
- err := lplugin.Serve(&lplugin.ServeOpts{
- BackendFactoryFunc: factoryFunc,
- })
- if err != nil {
- t.Fatal(err)
- }
-}
-
-// TestBackend_PluginMainEnv is a mock plugin that simply checks for the existence of FOO env var.
-func TestBackend_PluginMainEnv(t *testing.T) {
- args := []string{}
- if os.Getenv(pluginutil.PluginUnwrapTokenEnv) == "" && os.Getenv(pluginutil.PluginMetadataModeEnv) != "true" {
- return
- }
-
- // Check on actual vs expected env var
- actual := os.Getenv(expectedEnvKey)
- if actual != expectedEnvValue {
- t.Fatalf("expected: %q, got: %q", expectedEnvValue, actual)
- }
-
- caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
- if caPEM == "" {
- t.Fatal("CA cert not passed in")
- }
- args = append(args, fmt.Sprintf("--ca-cert=%s", caPEM))
-
- apiClientMeta := &api.PluginAPIClientMeta{}
- flags := apiClientMeta.FlagSet()
- flags.Parse(args)
-
- factoryFunc := mock.FactoryType(logical.TypeLogical)
-
- err := lplugin.Serve(&lplugin.ServeOpts{
- BackendFactoryFunc: factoryFunc,
- })
- if err != nil {
- t.Fatal(err)
- }
-}
diff --git a/vault/external_tests/policy/acl_templating_test.go b/vault/external_tests/policy/acl_templating_test.go
deleted file mode 100644
index a0af24e48..000000000
--- a/vault/external_tests/policy/acl_templating_test.go
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package policy
-
-import (
- "fmt"
- "testing"
-
- "github.com/hashicorp/vault/api"
- credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-func TestPolicyTemplating(t *testing.T) {
- goodPolicy1 := `
-path "secret/{{ identity.entity.name}}/*" {
- capabilities = ["read", "create", "update"]
-
-}
-
-path "secret/{{ identity.entity.aliases.%s.name}}/*" {
- capabilities = ["read", "create", "update"]
-
-}
-`
-
- goodPolicy2 := `
-path "secret/{{ identity.groups.ids.%s.name}}/*" {
- capabilities = ["read", "create", "update"]
-
-}
-
-path "secret/{{ identity.groups.names.group_name.id}}/*" {
- capabilities = ["read", "create", "update"]
-
-}
-`
-
- badPolicy1 := `
-path "secret/{{ identity.groups.names.foobar.name}}/*" {
- capabilities = ["read", "create", "update"]
-
-}
-`
-
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": credUserpass.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- resp, err := client.Logical().Write("identity/entity", map[string]interface{}{
- "name": "entity_name",
- "policies": []string{
- "goodPolicy1",
- "badPolicy1",
- },
- })
- if err != nil {
- t.Fatal(err)
- }
- entityID := resp.Data["id"].(string)
-
- resp, err = client.Logical().Write("identity/group", map[string]interface{}{
- "policies": []string{
- "goodPolicy2",
- },
- "member_entity_ids": []string{
- entityID,
- },
- "name": "group_name",
- })
- if err != nil {
- t.Fatal(err)
- }
- groupID := resp.Data["id"]
-
- resp, err = client.Logical().Write("identity/group", map[string]interface{}{
- "name": "foobar",
- })
- if err != nil {
- t.Fatal(err)
- }
- foobarGroupID := resp.Data["id"]
-
- // Enable userpass auth
- err = client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create an external group and renew the token. This should add external
- // group policies to the token.
- auths, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
- userpassAccessor := auths["userpass/"].Accessor
-
- // Create an alias
- resp, err = client.Logical().Write("identity/entity-alias", map[string]interface{}{
- "name": "testuser",
- "mount_accessor": userpassAccessor,
- "canonical_id": entityID,
- })
- if err != nil {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- // Add a user to userpass backend
- _, err = client.Logical().Write("auth/userpass/users/testuser", map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Write in policies
- goodPolicy1 = fmt.Sprintf(goodPolicy1, userpassAccessor)
- goodPolicy2 = fmt.Sprintf(goodPolicy2, groupID)
- err = client.Sys().PutPolicy("goodPolicy1", goodPolicy1)
- if err != nil {
- t.Fatal(err)
- }
- err = client.Sys().PutPolicy("goodPolicy2", goodPolicy2)
- if err != nil {
- t.Fatal(err)
- }
-
- // Authenticate
- secret, err := client.Logical().Write("auth/userpass/login/testuser", map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatal(err)
- }
- clientToken := secret.Auth.ClientToken
-
- tests := []struct {
- name string
- path string
- fail bool
- }{
- {
- name: "entity name",
- path: "secret/entity_name/foo",
- },
- {
- name: "bad entity name",
- path: "secret/entityname/foo",
- fail: true,
- },
- {
- name: "group name",
- path: "secret/group_name/foo",
- },
- {
- name: "group id",
- path: fmt.Sprintf("secret/%s/foo", groupID),
- },
- {
- name: "alias name",
- path: "secret/testuser/foo",
- },
- {
- name: "bad group name",
- path: "secret/foobar/foo",
- },
- }
-
- runTests := func(failGroupName bool) {
- for _, test := range tests {
- resp, err := client.Logical().Write(test.path, map[string]interface{}{"zip": "zap"})
- fail := test.fail
- if test.name == "bad group name" {
- fail = failGroupName
- }
- if err != nil && !fail {
- if resp.Data["error"].(string) != "permission denied" {
- t.Fatalf("unexpected status %v", resp.Data["error"])
- }
- t.Fatalf("%s: got unexpected error: %v", test.name, err)
- }
- if err == nil && fail {
- t.Fatalf("%s: expected error", test.name)
- }
- }
- }
-
- rootToken := client.Token()
- client.SetToken(clientToken)
- runTests(true)
-
- client.SetToken(rootToken)
- // Test that a policy with bad group membership doesn't kill the other paths
- err = client.Sys().PutPolicy("badPolicy1", badPolicy1)
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(clientToken)
- runTests(true)
-
- // Test that adding group membership now allows access
- client.SetToken(rootToken)
- resp, err = client.Logical().Write("identity/group", map[string]interface{}{
- "id": foobarGroupID,
- "member_entity_ids": []string{
- entityID,
- },
- })
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(clientToken)
- runTests(false)
-}
diff --git a/vault/external_tests/policy/policy_test.go b/vault/external_tests/policy/policy_test.go
deleted file mode 100644
index a478307a2..000000000
--- a/vault/external_tests/policy/policy_test.go
+++ /dev/null
@@ -1,329 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package policy
-
-import (
- "testing"
- "time"
-
- "github.com/go-test/deep"
- "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/go-secure-stdlib/strutil"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/builtin/credential/ldap"
- credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
- ldaphelper "github.com/hashicorp/vault/helper/testhelpers/ldap"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-func TestPolicy_NoDefaultPolicy(t *testing.T) {
- var err error
- coreConfig := &vault.CoreConfig{
- DisableMlock: true,
- DisableCache: true,
- Logger: hclog.NewNullLogger(),
- CredentialBackends: map[string]logical.Factory{
- "ldap": ldap.Factory,
- },
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- cores := cluster.Cores
-
- vault.TestWaitActive(t, cores[0].Core)
-
- client := cores[0].Client
-
- err = client.Sys().EnableAuthWithOptions("ldap", &api.EnableAuthOptions{
- Type: "ldap",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Configure LDAP auth backend
- cleanup, cfg := ldaphelper.PrepareTestContainer(t, "latest")
- defer cleanup()
-
- _, err = client.Logical().Write("auth/ldap/config", map[string]interface{}{
- "url": cfg.Url,
- "userattr": cfg.UserAttr,
- "userdn": cfg.UserDN,
- "groupdn": cfg.GroupDN,
- "groupattr": cfg.GroupAttr,
- "binddn": cfg.BindDN,
- "bindpass": cfg.BindPassword,
- "token_no_default_policy": true,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create a local user in LDAP
- secret, err := client.Logical().Write("auth/ldap/users/hermes conrad", map[string]interface{}{
- "policies": "foo",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Login with LDAP and create a token
- secret, err = client.Logical().Write("auth/ldap/login/hermes conrad", map[string]interface{}{
- "password": "hermes",
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- // Lookup the token to get the entity ID
- secret, err = client.Auth().Token().Lookup(token)
- if err != nil {
- t.Fatal(err)
- }
-
- if diff := deep.Equal(secret.Data["policies"], []interface{}{"foo"}); diff != nil {
- t.Fatal(diff)
- }
-}
-
-func TestPolicy_NoConfiguredPolicy(t *testing.T) {
- var err error
- coreConfig := &vault.CoreConfig{
- DisableMlock: true,
- DisableCache: true,
- Logger: hclog.NewNullLogger(),
- CredentialBackends: map[string]logical.Factory{
- "ldap": ldap.Factory,
- },
- }
-
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
-
- cluster.Start()
- defer cluster.Cleanup()
-
- cores := cluster.Cores
-
- vault.TestWaitActive(t, cores[0].Core)
-
- client := cores[0].Client
-
- err = client.Sys().EnableAuthWithOptions("ldap", &api.EnableAuthOptions{
- Type: "ldap",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Configure LDAP auth backend
- cleanup, cfg := ldaphelper.PrepareTestContainer(t, "latest")
- defer cleanup()
-
- _, err = client.Logical().Write("auth/ldap/config", map[string]interface{}{
- "url": cfg.Url,
- "userattr": cfg.UserAttr,
- "userdn": cfg.UserDN,
- "groupdn": cfg.GroupDN,
- "groupattr": cfg.GroupAttr,
- "binddn": cfg.BindDN,
- "bindpass": cfg.BindPassword,
- "token_ttl": "24h",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create a local user in LDAP without any policies configured
- secret, err := client.Logical().Write("auth/ldap/users/hermes conrad", map[string]interface{}{})
- if err != nil {
- t.Fatal(err)
- }
-
- // Login with LDAP and create a token
- secret, err = client.Logical().Write("auth/ldap/login/hermes conrad", map[string]interface{}{
- "password": "hermes",
- })
- if err != nil {
- t.Fatal(err)
- }
- token := secret.Auth.ClientToken
-
- // Lookup the token to get the entity ID
- secret, err = client.Auth().Token().Lookup(token)
- if err != nil {
- t.Fatal(err)
- }
-
- if diff := deep.Equal(secret.Data["policies"], []interface{}{"default"}); diff != nil {
- t.Fatal(diff)
- }
-
- // Renew the token with an increment of 2 hours to ensure that lease renewal
- // occurred and can be checked against the default lease duration with a
- // big enough delta.
- secret, err = client.Logical().Write("auth/token/renew", map[string]interface{}{
- "token": token,
- "increment": "2h",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Verify that the lease renewal extended the duration properly.
- if float64(secret.Auth.LeaseDuration) < (1 * time.Hour).Seconds() {
- t.Fatalf("failed to renew lease, got: %v", secret.Auth.LeaseDuration)
- }
-}
-
-func TestPolicy_TokenRenewal(t *testing.T) {
- cases := []struct {
- name string
- tokenPolicies []string
- identityPolicies []string
- }{
- {
- "default only",
- nil,
- nil,
- },
- {
- "with token policies",
- []string{"token-policy"},
- nil,
- },
- {
- "with identity policies",
- nil,
- []string{"identity-policy"},
- },
- {
- "with token and identity policies",
- []string{"token-policy"},
- []string{"identity-policy"},
- },
- }
-
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": credUserpass.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // Enable userpass auth
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Add a user to userpass backend
- data := map[string]interface{}{
- "password": "testpassword",
- }
- if len(tc.tokenPolicies) > 0 {
- data["token_policies"] = tc.tokenPolicies
- }
- _, err = client.Logical().Write("auth/userpass/users/testuser", data)
- if err != nil {
- t.Fatal(err)
- }
-
- // Set up entity if we're testing against an identity_policies
- if len(tc.identityPolicies) > 0 {
- auths, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
- userpassAccessor := auths["userpass/"].Accessor
-
- resp, err := client.Logical().Write("identity/entity", map[string]interface{}{
- "name": "test-entity",
- "policies": tc.identityPolicies,
- })
- if err != nil {
- t.Fatal(err)
- }
- entityID := resp.Data["id"].(string)
-
- // Create an alias
- resp, err = client.Logical().Write("identity/entity-alias", map[string]interface{}{
- "name": "testuser",
- "mount_accessor": userpassAccessor,
- "canonical_id": entityID,
- })
- if err != nil {
- t.Fatal(err)
- }
- }
-
- // Authenticate
- secret, err := client.Logical().Write("auth/userpass/login/testuser", map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatal(err)
- }
- clientToken := secret.Auth.ClientToken
-
- // Verify the policies exist in the login response
- expectedTokenPolicies := append([]string{"default"}, tc.tokenPolicies...)
- if !strutil.EquivalentSlices(secret.Auth.TokenPolicies, expectedTokenPolicies) {
- t.Fatalf("token policy mismatch:\nexpected: %v\ngot: %v", expectedTokenPolicies, secret.Auth.TokenPolicies)
- }
-
- if !strutil.EquivalentSlices(secret.Auth.IdentityPolicies, tc.identityPolicies) {
- t.Fatalf("identity policy mismatch:\nexpected: %v\ngot: %v", tc.identityPolicies, secret.Auth.IdentityPolicies)
- }
-
- expectedPolicies := append(expectedTokenPolicies, tc.identityPolicies...)
- if !strutil.EquivalentSlices(secret.Auth.Policies, expectedPolicies) {
- t.Fatalf("policy mismatch:\nexpected: %v\ngot: %v", expectedPolicies, secret.Auth.Policies)
- }
-
- // Renew token
- secret, err = client.Logical().Write("auth/token/renew", map[string]interface{}{
- "token": clientToken,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Verify the policies exist in the renewal response
- if !strutil.EquivalentSlices(secret.Auth.TokenPolicies, expectedTokenPolicies) {
- t.Fatalf("policy mismatch:\nexpected: %v\ngot: %v", expectedTokenPolicies, secret.Auth.TokenPolicies)
- }
-
- if !strutil.EquivalentSlices(secret.Auth.IdentityPolicies, tc.identityPolicies) {
- t.Fatalf("identity policy mismatch:\nexpected: %v\ngot: %v", tc.identityPolicies, secret.Auth.IdentityPolicies)
- }
-
- if !strutil.EquivalentSlices(secret.Auth.Policies, expectedPolicies) {
- t.Fatalf("policy mismatch:\nexpected: %v\ngot: %v", expectedPolicies, secret.Auth.Policies)
- }
- })
- }
-}
diff --git a/vault/external_tests/pprof/pprof.go b/vault/external_tests/pprof/pprof.go
deleted file mode 100644
index 91169e0ab..000000000
--- a/vault/external_tests/pprof/pprof.go
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package pprof
-
-import (
- "context"
- "encoding/json"
- "io/ioutil"
- "net/http"
- "testing"
-
- "github.com/hashicorp/go-cleanhttp"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/sdk/helper/testcluster"
- "github.com/stretchr/testify/require"
- "golang.org/x/net/http2"
-)
-
-func SysPprof_Test(t *testing.T, cluster testcluster.VaultCluster) {
- nodes := cluster.Nodes()
- client := nodes[0].APIClient()
-
- transport := cleanhttp.DefaultPooledTransport()
- transport.TLSClientConfig = nodes[0].TLSConfig()
- if err := http2.ConfigureTransport(transport); err != nil {
- t.Fatal(err)
- }
- httpClient := &http.Client{
- Transport: transport,
- }
-
- cases := []struct {
- name string
- path string
- seconds string
- }{
- {
- "index",
- "/v1/sys/pprof/",
- "",
- },
- {
- "cmdline",
- "/v1/sys/pprof/cmdline",
- "",
- },
- {
- "goroutine",
- "/v1/sys/pprof/goroutine",
- "",
- },
- {
- "heap",
- "/v1/sys/pprof/heap",
- "",
- },
- {
- "profile",
- "/v1/sys/pprof/profile",
- "1",
- },
- {
- "symbol",
- "/v1/sys/pprof/symbol",
- "",
- },
- {
- "trace",
- "/v1/sys/pprof/trace",
- "1",
- },
- }
-
- pprofRequest := func(t *testing.T, path string, seconds string) {
- req := client.NewRequest("GET", path)
- if seconds != "" {
- req.Params.Set("seconds", seconds)
- }
- httpReq, err := req.ToHTTP()
- if err != nil {
- t.Fatal(err)
- }
- resp, err := httpClient.Do(httpReq)
- if err != nil {
- t.Fatal(err)
- }
- defer resp.Body.Close()
-
- httpRespBody, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Fatal(err)
- }
-
- httpResp := make(map[string]interface{})
-
- // Skip this error check since some endpoints return binary blobs, we
- // only care about the ok check right after as an existence check.
- _ = json.Unmarshal(httpRespBody, &httpResp)
-
- // Make sure that we don't get a error response
- if _, ok := httpResp["errors"]; ok {
- t.Fatalf("unexpected error response: %v", httpResp["errors"])
- }
-
- if len(httpRespBody) == 0 {
- t.Fatal("no pprof index returned")
- }
- }
-
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- pprofRequest(t, tc.path, tc.seconds)
- })
- }
-}
-
-func SysPprof_Standby_Test(t *testing.T, cluster testcluster.VaultCluster) {
- pprof := func(client *api.Client) (string, error) {
- req := client.NewRequest("GET", "/v1/sys/pprof/cmdline")
- resp, err := client.RawRequestWithContext(context.Background(), req)
- if err != nil {
- return "", err
- }
- defer resp.Body.Close()
-
- data, err := ioutil.ReadAll(resp.Body)
- return string(data), err
- }
-
- cmdline, err := pprof(cluster.Nodes()[0].APIClient())
- require.Nil(t, err)
- require.NotEmpty(t, cmdline)
- t.Log(cmdline)
-
- cmdline, err = pprof(cluster.Nodes()[1].APIClient())
- require.Nil(t, err)
- require.NotEmpty(t, cmdline)
- t.Log(cmdline)
-}
diff --git a/vault/external_tests/pprof/pprof_binary/pprof_test.go b/vault/external_tests/pprof/pprof_binary/pprof_test.go
deleted file mode 100644
index 7e050eded..000000000
--- a/vault/external_tests/pprof/pprof_binary/pprof_test.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package pprof_binary
-
-import (
- "os"
- "testing"
-
- "github.com/hashicorp/vault/sdk/helper/testcluster"
- "github.com/hashicorp/vault/vault/external_tests/pprof"
-)
-
-// TestSysPprof_Exec is the same as TestSysPprof, but using a Vault binary
-// running as -dev instead of a fake single node TestCluster. There's no
-// particular reason why TestSysPprof was chosen to validate that mechanism,
-// other than that it was fast and simple.
-func TestSysPprof_Exec(t *testing.T) {
- t.Parallel()
- binary := os.Getenv("VAULT_BINARY")
- if binary == "" {
- t.Skip("only running exec test when $VAULT_BINARY present")
- }
- cluster := testcluster.NewTestExecDevCluster(t, &testcluster.ExecDevClusterOptions{
- ClusterOptions: testcluster.ClusterOptions{
- NumCores: 1,
- },
- BinaryPath: binary,
- BaseListenAddress: "127.0.0.1:8208",
- })
- defer cluster.Cleanup()
-
- pprof.SysPprof_Test(t, cluster)
-}
-
-// TestSysPprof_Standby_Exec is the same as TestSysPprof_Standby, but using a Vault binary
-// running as -dev-three-node instead of a fake single node TestCluster. There's
-// no particular reason why TestSysPprof was chosen to validate that mechanism,
-// other than that it was fast and simple.
-func TestSysPprof_Standby_Exec(t *testing.T) {
- t.Parallel()
- binary := os.Getenv("VAULT_BINARY")
- if binary == "" {
- t.Skip("only running exec test when $VAULT_BINARY present")
- }
- cluster := testcluster.NewTestExecDevCluster(t, &testcluster.ExecDevClusterOptions{
- ClusterOptions: testcluster.ClusterOptions{
- VaultNodeConfig: &testcluster.VaultNodeConfig{
- DisablePerformanceStandby: true,
- },
- },
- BinaryPath: binary,
- BaseListenAddress: "127.0.0.1:8210",
- })
- defer cluster.Cleanup()
-
- pprof.SysPprof_Standby_Test(t, cluster)
-}
diff --git a/vault/external_tests/pprof/pprof_test.go b/vault/external_tests/pprof/pprof_test.go
deleted file mode 100644
index 3edbcb107..000000000
--- a/vault/external_tests/pprof/pprof_test.go
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package pprof
-
-import (
- "encoding/json"
- "io/ioutil"
- "net/http"
- "strconv"
- "strings"
- "testing"
-
- "github.com/hashicorp/go-cleanhttp"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/internalshared/configutil"
- "github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
- "github.com/hashicorp/vault/vault"
- "golang.org/x/net/http2"
-)
-
-func TestSysPprof(t *testing.T) {
- t.Parallel()
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- RequestResponseCallback: schema.ResponseValidatingCallback(t),
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- SysPprof_Test(t, cluster)
-}
-
-func TestSysPprof_MaxRequestDuration(t *testing.T) {
- t.Parallel()
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
- client := cluster.Cores[0].Client
-
- transport := cleanhttp.DefaultPooledTransport()
- transport.TLSClientConfig = cluster.Cores[0].TLSConfig()
- if err := http2.ConfigureTransport(transport); err != nil {
- t.Fatal(err)
- }
- httpClient := &http.Client{
- Transport: transport,
- }
-
- sec := strconv.Itoa(int(vault.DefaultMaxRequestDuration.Seconds()) + 1)
-
- req := client.NewRequest("GET", "/v1/sys/pprof/profile")
- req.Params.Set("seconds", sec)
- httpReq, err := req.ToHTTP()
- if err != nil {
- t.Fatal(err)
- }
- resp, err := httpClient.Do(httpReq)
- if err != nil {
- t.Fatal(err)
- }
- defer resp.Body.Close()
-
- httpRespBody, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Fatal(err)
- }
-
- httpResp := make(map[string]interface{})
-
- // If we error here, it means that profiling likely happened, which is not
- // what we're checking for in this case.
- if err := json.Unmarshal(httpRespBody, &httpResp); err != nil {
- t.Fatalf("expected valid error response, got: %v", err)
- }
-
- errs, ok := httpResp["errors"].([]interface{})
- if !ok {
- t.Fatalf("expected error response, got: %v", httpResp)
- }
- if len(errs) == 0 || !strings.Contains(errs[0].(string), "exceeds max request duration") {
- t.Fatalf("unexpected error returned: %v", errs)
- }
-}
-
-func TestSysPprof_Standby(t *testing.T) {
- t.Parallel()
- cluster := vault.NewTestCluster(t, &vault.CoreConfig{
- DisablePerformanceStandby: true,
- }, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- DefaultHandlerProperties: vault.HandlerProperties{
- ListenerConfig: &configutil.Listener{
- Profiling: configutil.ListenerProfiling{
- UnauthenticatedPProfAccess: true,
- },
- },
- },
- })
- defer cluster.Cleanup()
-
- SysPprof_Standby_Test(t, cluster)
-}
diff --git a/vault/external_tests/quotas/quotas_test.go b/vault/external_tests/quotas/quotas_test.go
deleted file mode 100644
index 93ca71a79..000000000
--- a/vault/external_tests/quotas/quotas_test.go
+++ /dev/null
@@ -1,544 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package quotas
-
-import (
- "fmt"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/stretchr/testify/require"
-
- "github.com/hashicorp/vault/builtin/credential/userpass"
- "github.com/hashicorp/vault/builtin/logical/pki"
- "github.com/hashicorp/vault/helper/testhelpers/teststorage"
- "github.com/hashicorp/vault/vault"
- "go.uber.org/atomic"
-)
-
-const (
- testLookupOnlyPolicy = `
-path "/auth/token/lookup" {
- capabilities = [ "create", "update"]
-}
-`
-)
-
-var coreConfig = &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "pki": pki.Factory,
- },
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- },
-}
-
-func setupMounts(t *testing.T, client *api.Client) {
- t.Helper()
-
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("auth/userpass/users/foo", map[string]interface{}{
- "password": "bar",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- err = client.Sys().Mount("pki", &api.MountInput{
- Type: "pki",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("pki/root/generate/internal", map[string]interface{}{
- "common_name": "testvault.com",
- "ttl": "200h",
- "ip_sans": "127.0.0.1",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("pki/roles/test", map[string]interface{}{
- "require_cn": false,
- "allowed_domains": "testvault.com",
- "allow_subdomains": true,
- "max_ttl": "2h",
- "generate_lease": true,
- })
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func teardownMounts(t *testing.T, client *api.Client) {
- t.Helper()
- if err := client.Sys().Unmount("pki"); err != nil {
- t.Fatal(err)
- }
- if err := client.Sys().DisableAuth("userpass"); err != nil {
- t.Fatal(err)
- }
- if err := client.Sys().DisableAuth("approle"); err != nil {
- t.Fatal(err)
- }
-}
-
-func testRPS(reqFunc func(numSuccess, numFail *atomic.Int32), d time.Duration) (int32, int32, time.Duration) {
- numSuccess := atomic.NewInt32(0)
- numFail := atomic.NewInt32(0)
-
- start := time.Now()
- end := start.Add(d)
- for time.Now().Before(end) {
- reqFunc(numSuccess, numFail)
- }
-
- return numSuccess.Load(), numFail.Load(), time.Since(start)
-}
-
-func waitForRemovalOrTimeout(c *api.Client, path string, tick, to time.Duration) error {
- ticker := time.Tick(tick)
- timeout := time.After(to)
-
- // wait for the resource to be removed
- for {
- select {
- case <-timeout:
- return fmt.Errorf("timeout exceeding waiting for resource to be deleted: %s", path)
-
- case <-ticker:
- resp, err := c.Logical().Read(path)
- if err != nil {
- return err
- }
-
- if resp == nil {
- return nil
- }
- }
- }
-}
-
-func TestQuotas_RateLimit_DupName(t *testing.T) {
- conf, opts := teststorage.ClusterSetup(coreConfig, nil, nil)
- opts.NoDefaultQuotas = true
- opts.RequestResponseCallback = schema.ResponseValidatingCallback(t)
- cluster := vault.NewTestCluster(t, conf, opts)
- cluster.Start()
- defer cluster.Cleanup()
- core := cluster.Cores[0].Core
- client := cluster.Cores[0].Client
- vault.TestWaitActive(t, core)
-
- // create a rate limit quota w/ 'secret' path
- _, err := client.Logical().Write("sys/quotas/rate-limit/secret-rlq", map[string]interface{}{
- "rate": 7.7,
- "path": "secret",
- })
- require.NoError(t, err)
-
- s, err := client.Logical().Read("sys/quotas/rate-limit/secret-rlq")
- require.NoError(t, err)
- require.NotEmpty(t, s.Data)
-
- // create a rate limit quota w/ empty path (same name)
- _, err = client.Logical().Write("sys/quotas/rate-limit/secret-rlq", map[string]interface{}{
- "rate": 7.7,
- "path": "",
- })
- require.NoError(t, err)
-
- // list again and verify that only 1 item is returned
- s, err = client.Logical().List("sys/quotas/rate-limit")
- require.NoError(t, err)
-
- require.Len(t, s.Data, 1, "incorrect number of quotas")
-}
-
-func TestQuotas_RateLimit_DupPath(t *testing.T) {
- conf, opts := teststorage.ClusterSetup(coreConfig, nil, nil)
- opts.NoDefaultQuotas = true
- opts.RequestResponseCallback = schema.ResponseValidatingCallback(t)
- cluster := vault.NewTestCluster(t, conf, opts)
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- client := cluster.Cores[0].Client
- vault.TestWaitActive(t, core)
- // create a global rate limit quota
- _, err := client.Logical().Write("sys/quotas/rate-limit/global-rlq", map[string]interface{}{
- "rate": 10,
- "path": "",
- })
- require.NoError(t, err)
-
- // create a rate limit quota w/ 'secret' path
- _, err = client.Logical().Write("sys/quotas/rate-limit/secret-rlq", map[string]interface{}{
- "rate": 7.7,
- "path": "secret",
- })
- require.NoError(t, err)
-
- s, err := client.Logical().Read("sys/quotas/rate-limit/secret-rlq")
- require.NoError(t, err)
- require.NotEmpty(t, s.Data)
-
- // create a rate limit quota w/ empty path (same name)
- _, err = client.Logical().Write("sys/quotas/rate-limit/secret-rlq", map[string]interface{}{
- "rate": 7.7,
- "path": "",
- })
-
- if err == nil {
- t.Fatal("Duplicated paths were accepted")
- }
-}
-
-func TestQuotas_RateLimitQuota_ExemptPaths(t *testing.T) {
- conf, opts := teststorage.ClusterSetup(coreConfig, nil, nil)
- opts.NoDefaultQuotas = true
- opts.RequestResponseCallback = schema.ResponseValidatingCallback(t)
- cluster := vault.NewTestCluster(t, conf, opts)
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- client := cluster.Cores[0].Client
- vault.TestWaitActive(t, core)
-
- _, err := client.Logical().Write("sys/quotas/rate-limit/rlq", map[string]interface{}{
- "rate": 7.7,
- })
- require.NoError(t, err)
-
- // ensure exempt paths are not empty by default
- resp, err := client.Logical().Read("sys/quotas/config")
- require.NoError(t, err)
- require.NotEmpty(t, resp.Data["rate_limit_exempt_paths"].([]interface{}), "expected no exempt paths by default")
-
- reqFunc := func(numSuccess, numFail *atomic.Int32) {
- _, err := client.Logical().Read("sys/quotas/rate-limit/rlq")
-
- if err != nil {
- numFail.Add(1)
- } else {
- numSuccess.Add(1)
- }
- }
-
- numSuccess, numFail, elapsed := testRPS(reqFunc, 5*time.Second)
- ideal := 8 + (7.7 * float64(elapsed) / float64(time.Second))
- want := int32(ideal + 1)
- require.NotZerof(t, numFail, "expected some requests to fail; numSuccess: %d, elapsed: %d", numSuccess, elapsed)
- require.LessOrEqualf(t, numSuccess, want, "too many successful requests;numSuccess: %d, numFail: %d, elapsed: %d", numSuccess, numFail, elapsed)
-
- // allow time (1s) for rate limit to refill before updating the quota config
- time.Sleep(time.Second)
-
- _, err = client.Logical().Write("sys/quotas/config", map[string]interface{}{
- "rate_limit_exempt_paths": []string{"sys/quotas/rate-limit"},
- })
- require.NoError(t, err)
-
- // all requests should success
- numSuccess, numFail, _ = testRPS(reqFunc, 5*time.Second)
- require.NotZero(t, numSuccess)
- require.Zero(t, numFail)
-}
-
-func TestQuotas_RateLimitQuota_DefaultExemptPaths(t *testing.T) {
- conf, opts := teststorage.ClusterSetup(coreConfig, nil, nil)
- opts.NoDefaultQuotas = true
- opts.RequestResponseCallback = schema.ResponseValidatingCallback(t)
- cluster := vault.NewTestCluster(t, conf, opts)
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- client := cluster.Cores[0].Client
- vault.TestWaitActive(t, core)
-
- _, err := client.Logical().Write("sys/quotas/rate-limit/rlq", map[string]interface{}{
- "rate": 1,
- })
- require.NoError(t, err)
-
- resp, err := client.Logical().Read("sys/health")
- require.NoError(t, err)
- require.NotNil(t, resp)
- require.NotNil(t, resp.Data)
-
- // The second sys/health call should not fail as /v1/sys/health is
- // part of the default exempt paths
- resp, err = client.Logical().Read("sys/health")
- require.NoError(t, err)
- // If the response is nil, then we are being rate limited
- require.NotNil(t, resp)
- require.NotNil(t, resp.Data)
-}
-
-func TestQuotas_RateLimitQuota_Mount(t *testing.T) {
- conf, opts := teststorage.ClusterSetup(coreConfig, nil, nil)
- cluster := vault.NewTestCluster(t, conf, opts)
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- client := cluster.Cores[0].Client
- vault.TestWaitActive(t, core)
-
- err := client.Sys().Mount("pki", &api.MountInput{
- Type: "pki",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("pki/root/generate/internal", map[string]interface{}{
- "common_name": "testvault.com",
- "ttl": "200h",
- "ip_sans": "127.0.0.1",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("pki/roles/test", map[string]interface{}{
- "require_cn": false,
- "allowed_domains": "testvault.com",
- "allow_subdomains": true,
- "max_ttl": "2h",
- "generate_lease": true,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- reqFunc := func(numSuccess, numFail *atomic.Int32) {
- _, err := client.Logical().Read("pki/cert/ca_chain")
-
- if err != nil {
- numFail.Add(1)
- } else {
- numSuccess.Add(1)
- }
- }
-
- // Create a rate limit quota with a low RPS of 7.7, which means we can process
- // ⌈7.7⌉*2 requests in the span of roughly a second -- 8 initially, followed
- // by a refill rate of 7.7 per-second.
- _, err = client.Logical().Write("sys/quotas/rate-limit/rlq", map[string]interface{}{
- "rate": 7.7,
- "path": "pki/",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- numSuccess, numFail, elapsed := testRPS(reqFunc, 5*time.Second)
-
- // evaluate the ideal RPS as (ceil(RPS) + (RPS * totalSeconds))
- ideal := 8 + (7.7 * float64(elapsed) / float64(time.Second))
-
- // ensure there were some failed requests
- if numFail == 0 {
- t.Fatalf("expected some requests to fail; numSuccess: %d, numFail: %d, elapsed: %d", numSuccess, numFail, elapsed)
- }
-
- // ensure that we should never get more requests than allowed
- if want := int32(ideal + 1); numSuccess > want {
- t.Fatalf("too many successful requests; want: %d, numSuccess: %d, numFail: %d, elapsed: %d", want, numSuccess, numFail, elapsed)
- }
-
- // update the rate limit quota with a high RPS such that no requests should fail
- _, err = client.Logical().Write("sys/quotas/rate-limit/rlq", map[string]interface{}{
- "rate": 10000.0,
- "path": "pki/",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, numFail, _ = testRPS(reqFunc, 5*time.Second)
- if numFail > 0 {
- t.Fatalf("unexpected number of failed requests: %d", numFail)
- }
-}
-
-func TestQuotas_RateLimitQuota_MountPrecedence(t *testing.T) {
- conf, opts := teststorage.ClusterSetup(coreConfig, nil, nil)
- opts.NoDefaultQuotas = true
- cluster := vault.NewTestCluster(t, conf, opts)
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- client := cluster.Cores[0].Client
-
- vault.TestWaitActive(t, core)
-
- // create PKI mount
- err := client.Sys().Mount("pki", &api.MountInput{
- Type: "pki",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("pki/root/generate/internal", map[string]interface{}{
- "common_name": "testvault.com",
- "ttl": "200h",
- "ip_sans": "127.0.0.1",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("pki/roles/test", map[string]interface{}{
- "require_cn": false,
- "allowed_domains": "testvault.com",
- "allow_subdomains": true,
- "max_ttl": "2h",
- "generate_lease": true,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // create a root rate limit quota
- _, err = client.Logical().Write("sys/quotas/rate-limit/root-rlq", map[string]interface{}{
- "name": "root-rlq",
- "rate": 14.7,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // create a mount rate limit quota with a lower RPS than the root rate limit quota
- _, err = client.Logical().Write("sys/quotas/rate-limit/mount-rlq", map[string]interface{}{
- "name": "mount-rlq",
- "rate": 7.7,
- "path": "pki/",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // ensure mount rate limit quota takes precedence over root rate limit quota
- reqFunc := func(numSuccess, numFail *atomic.Int32) {
- _, err := client.Logical().Read("pki/cert/ca_chain")
-
- if err != nil {
- numFail.Add(1)
- } else {
- numSuccess.Add(1)
- }
- }
-
- // ensure mount rate limit quota takes precedence over root rate limit quota
- numSuccess, numFail, elapsed := testRPS(reqFunc, 5*time.Second)
-
- // evaluate the ideal RPS as (ceil(RPS) + (RPS * totalSeconds))
- ideal := 8 + (7.7 * float64(elapsed) / float64(time.Second))
-
- // ensure there were some failed requests
- if numFail == 0 {
- t.Fatalf("expected some requests to fail; numSuccess: %d, numFail: %d, elapsed: %d", numSuccess, numFail, elapsed)
- }
-
- // ensure that we should never get more requests than allowed
- if want := int32(ideal + 1); numSuccess > want {
- t.Fatalf("too many successful requests; want: %d, numSuccess: %d, numFail: %d, elapsed: %d", want, numSuccess, numFail, elapsed)
- }
-}
-
-func TestQuotas_RateLimitQuota(t *testing.T) {
- conf, opts := teststorage.ClusterSetup(coreConfig, nil, nil)
- opts.NoDefaultQuotas = true
- cluster := vault.NewTestCluster(t, conf, opts)
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- client := cluster.Cores[0].Client
-
- vault.TestWaitActive(t, core)
-
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("auth/userpass/users/foo", map[string]interface{}{
- "password": "bar",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create a rate limit quota with a low RPS of 7.7, which means we can process
- // ⌈7.7⌉*2 requests in the span of roughly a second -- 8 initially, followed
- // by a refill rate of 7.7 per-second.
- _, err = client.Logical().Write("sys/quotas/rate-limit/rlq", map[string]interface{}{
- "rate": 7.7,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- reqFunc := func(numSuccess, numFail *atomic.Int32) {
- _, err := client.Logical().Read("sys/quotas/rate-limit/rlq")
-
- if err != nil {
- numFail.Add(1)
- } else {
- numSuccess.Add(1)
- }
- }
-
- numSuccess, numFail, elapsed := testRPS(reqFunc, 5*time.Second)
-
- // evaluate the ideal RPS as (ceil(RPS) + (RPS * totalSeconds))
- ideal := 8 + (7.7 * float64(elapsed) / float64(time.Second))
-
- // ensure there were some failed requests
- if numFail == 0 {
- t.Fatalf("expected some requests to fail; numSuccess: %d, numFail: %d, elapsed: %d", numSuccess, numFail, elapsed)
- }
-
- // ensure that we should never get more requests than allowed
- if want := int32(ideal + 1); numSuccess > want {
- t.Fatalf("too many successful requests; want: %d, numSuccess: %d, numFail: %d, elapsed: %d", want, numSuccess, numFail, elapsed)
- }
-
- // allow time (1s) for rate limit to refill before updating the quota
- time.Sleep(time.Second)
-
- // update the rate limit quota with a high RPS such that no requests should fail
- _, err = client.Logical().Write("sys/quotas/rate-limit/rlq", map[string]interface{}{
- "rate": 10000.0,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, numFail, _ = testRPS(reqFunc, 5*time.Second)
- if numFail > 0 {
- t.Fatalf("unexpected number of failed requests: %d", numFail)
- }
-}
diff --git a/vault/external_tests/raft/raft.go b/vault/external_tests/raft/raft.go
deleted file mode 100644
index f3744c4ae..000000000
--- a/vault/external_tests/raft/raft.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package rafttests
-
-import (
- "fmt"
- "testing"
-
- "github.com/go-test/deep"
- "github.com/hashicorp/vault/sdk/helper/testcluster"
-)
-
-func Raft_Configuration_Test(t *testing.T, cluster testcluster.VaultCluster) {
- client := cluster.Nodes()[0].APIClient()
- secret, err := client.Logical().Read("sys/storage/raft/configuration")
- if err != nil {
- t.Fatal(err)
- }
- servers := secret.Data["config"].(map[string]interface{})["servers"].([]interface{})
- found := make(map[string]struct{})
- for _, s := range servers {
- server := s.(map[string]interface{})
- nodeID := server["node_id"].(string)
- leader := server["leader"].(bool)
- switch nodeID {
- case "core-0":
- if !leader {
- t.Fatalf("expected server to be leader: %#v", server)
- }
- default:
- if leader {
- t.Fatalf("expected server to not be leader: %#v", server)
- }
- }
-
- found[nodeID] = struct{}{}
- }
- expected := map[string]struct{}{}
- for i := range cluster.Nodes() {
- expected[fmt.Sprintf("core-%d", i)] = struct{}{}
- }
- if diff := deep.Equal(expected, found); len(diff) > 0 {
- t.Fatalf("configuration mismatch, diff: %v", diff)
- }
-}
diff --git a/vault/external_tests/raft/raft_autopilot_test.go b/vault/external_tests/raft/raft_autopilot_test.go
deleted file mode 100644
index 215addf59..000000000
--- a/vault/external_tests/raft/raft_autopilot_test.go
+++ /dev/null
@@ -1,635 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package rafttests
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "math"
- "reflect"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/go-secure-stdlib/strutil"
- autopilot "github.com/hashicorp/raft-autopilot"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/helper/testhelpers"
- "github.com/hashicorp/vault/helper/testhelpers/teststorage"
- "github.com/hashicorp/vault/physical/raft"
- "github.com/hashicorp/vault/vault"
- "github.com/hashicorp/vault/version"
- "github.com/kr/pretty"
- testingintf "github.com/mitchellh/go-testing-interface"
- "github.com/stretchr/testify/require"
-)
-
-func TestRaft_Autopilot_Disable(t *testing.T) {
- cluster, _ := raftCluster(t, &RaftClusterOpts{
- DisableFollowerJoins: true,
- InmemCluster: true,
- // Not setting EnableAutopilot here.
- })
- defer cluster.Cleanup()
-
- cli := cluster.Cores[0].Client
- state, err := cli.Sys().RaftAutopilotState()
- require.NoError(t, err)
- require.Nil(t, nil, state)
-}
-
-func TestRaft_Autopilot_Stabilization_And_State(t *testing.T) {
- cluster, _ := raftCluster(t, &RaftClusterOpts{
- DisableFollowerJoins: true,
- InmemCluster: true,
- EnableAutopilot: true,
- PhysicalFactoryConfig: map[string]interface{}{
- "performance_multiplier": "5",
- },
- })
- defer cluster.Cleanup()
-
- // Check that autopilot execution state is running
- client := cluster.Cores[0].Client
- state, err := client.Sys().RaftAutopilotState()
- require.NoError(t, err)
- require.Equal(t, true, state.Healthy)
- require.Len(t, state.Servers, 1)
- require.Equal(t, "core-0", state.Servers["core-0"].ID)
- require.Equal(t, "alive", state.Servers["core-0"].NodeStatus)
- require.Equal(t, "leader", state.Servers["core-0"].Status)
-
- writeConfig := func(config map[string]interface{}, expectError bool) {
- resp, err := client.Logical().Write("sys/storage/raft/autopilot/configuration", config)
- if expectError {
- require.Error(t, err)
- return
- }
- require.NoError(t, err)
- require.Nil(t, resp)
- }
-
- writableConfig := map[string]interface{}{
- "last_contact_threshold": "5s",
- "max_trailing_logs": 100,
- "server_stabilization_time": "10s",
- }
- writeConfig(writableConfig, false)
-
- config, err := client.Sys().RaftAutopilotConfiguration()
- require.NoError(t, err)
-
- // Wait for 110% of the stabilization time to add nodes
- stabilizationKickOffWaitDuration := time.Duration(math.Ceil(1.1 * float64(config.ServerStabilizationTime)))
- time.Sleep(stabilizationKickOffWaitDuration)
-
- joinAndStabilizeAndPromote(t, cluster.Cores[1], client, cluster, config, "core-1", 2)
- joinAndStabilizeAndPromote(t, cluster.Cores[2], client, cluster, config, "core-2", 3)
- state, err = client.Sys().RaftAutopilotState()
- require.NoError(t, err)
- require.Equal(t, []string{"core-0", "core-1", "core-2"}, state.Voters)
-
- // Now make sure that after we seal and unseal a node, the current leader
- // remains leader, and that the cluster becomes healthy again.
- leader := state.Leader
- testhelpers.EnsureCoreSealed(t, cluster.Cores[1])
- time.Sleep(10 * time.Second)
- testhelpers.EnsureCoreUnsealed(t, cluster, cluster.Cores[1])
-
- deadline := time.Now().Add(2 * time.Minute)
- for time.Now().Before(deadline) {
- state, err = client.Sys().RaftAutopilotState()
- require.NoError(t, err)
- if state.Healthy && state.Leader == leader {
- break
- }
- time.Sleep(time.Second)
- }
- require.Equal(t, true, state.Healthy)
- require.Equal(t, leader, state.Leader)
-}
-
-func TestRaft_Autopilot_Configuration(t *testing.T) {
- cluster, _ := raftCluster(t, &RaftClusterOpts{
- DisableFollowerJoins: true,
- InmemCluster: true,
- EnableAutopilot: true,
- })
- defer cluster.Cleanup()
-
- client := cluster.Cores[0].Client
- configCheckFunc := func(config *api.AutopilotConfig) {
- conf, err := client.Sys().RaftAutopilotConfiguration()
- require.NoError(t, err)
- require.Equal(t, config, conf)
- }
-
- writeConfigFunc := func(config map[string]interface{}, expectError bool) {
- resp, err := client.Logical().Write("sys/storage/raft/autopilot/configuration", config)
- if expectError {
- require.Error(t, err)
- return
- }
- require.NoError(t, err)
- require.Nil(t, resp)
- }
-
- // Ensure autopilot's default config has taken effect
- config := &api.AutopilotConfig{
- CleanupDeadServers: false,
- DeadServerLastContactThreshold: 24 * time.Hour,
- LastContactThreshold: 10 * time.Second,
- MaxTrailingLogs: 1000,
- ServerStabilizationTime: 10 * time.Second,
- }
- configCheckFunc(config)
-
- // Update config
- writableConfig := map[string]interface{}{
- "cleanup_dead_servers": true,
- "dead_server_last_contact_threshold": "100h",
- "last_contact_threshold": "100s",
- "max_trailing_logs": 100,
- "min_quorum": 100,
- "server_stabilization_time": "100s",
- }
- writeConfigFunc(writableConfig, false)
-
- // Ensure update has taken effect
- config.CleanupDeadServers = true
- config.DeadServerLastContactThreshold = 100 * time.Hour
- config.LastContactThreshold = 100 * time.Second
- config.MaxTrailingLogs = 100
- config.MinQuorum = 100
- config.ServerStabilizationTime = 100 * time.Second
- configCheckFunc(config)
-
- // Update some fields and leave the rest as it is.
- writableConfig = map[string]interface{}{
- "dead_server_last_contact_threshold": "50h",
- "max_trailing_logs": 50,
- "server_stabilization_time": "50s",
- }
- writeConfigFunc(writableConfig, false)
-
- // Check update
- config.DeadServerLastContactThreshold = 50 * time.Hour
- config.MaxTrailingLogs = 50
- config.ServerStabilizationTime = 50 * time.Second
- configCheckFunc(config)
-
- // Check error case
- writableConfig = map[string]interface{}{
- "min_quorum": 2,
- "dead_server_last_contact_threshold": "48h",
- }
- writeConfigFunc(writableConfig, true)
- configCheckFunc(config)
-
- // Check dead server last contact threshold minimum
- writableConfig = map[string]interface{}{
- "cleanup_dead_servers": true,
- "dead_server_last_contact_threshold": "5s",
- }
- writeConfigFunc(writableConfig, true)
- configCheckFunc(config)
-
- // Ensure that the configuration stays across reboots
- leaderCore := cluster.Cores[0]
- testhelpers.EnsureCoreSealed(t, cluster.Cores[0])
- cluster.UnsealCore(t, leaderCore)
- vault.TestWaitActive(t, leaderCore.Core)
- configCheckFunc(config)
-}
-
-// TestRaft_Autopilot_Stabilization_Delay verifies that if a node takes a long
-// time to become ready, it doesn't get promoted to voter until then.
-func TestRaft_Autopilot_Stabilization_Delay(t *testing.T) {
- conf, opts := teststorage.ClusterSetup(nil, nil, teststorage.RaftBackendSetup)
- conf.DisableAutopilot = false
- opts.InmemClusterLayers = true
- opts.KeepStandbysSealed = true
- opts.SetupFunc = nil
- timeToHealthyCore2 := 5 * time.Second
- opts.PhysicalFactory = func(t testingintf.T, coreIdx int, logger hclog.Logger, conf map[string]interface{}) *vault.PhysicalBackendBundle {
- config := map[string]interface{}{
- "snapshot_threshold": "50",
- "trailing_logs": "100",
- "autopilot_reconcile_interval": "1s",
- "autopilot_update_interval": "500ms",
- "snapshot_interval": "1s",
- }
- if coreIdx == 2 {
- config["snapshot_delay"] = timeToHealthyCore2.String()
- }
- return teststorage.MakeRaftBackend(t, coreIdx, logger, config)
- }
-
- cluster := vault.NewTestCluster(t, conf, opts)
- cluster.Start()
- defer cluster.Cleanup()
- testhelpers.WaitForActiveNode(t, cluster)
-
- // Check that autopilot execution state is running
- client := cluster.Cores[0].Client
- state, err := client.Sys().RaftAutopilotState()
- require.NotNil(t, state)
- require.NoError(t, err)
- require.Equal(t, true, state.Healthy)
- require.Len(t, state.Servers, 1)
- require.Equal(t, "core-0", state.Servers["core-0"].ID)
- require.Equal(t, "alive", state.Servers["core-0"].NodeStatus)
- require.Equal(t, "leader", state.Servers["core-0"].Status)
-
- _, err = client.Logical().Write("sys/storage/raft/autopilot/configuration", map[string]interface{}{
- "server_stabilization_time": "5s",
- })
- require.NoError(t, err)
-
- config, err := client.Sys().RaftAutopilotConfiguration()
- require.NoError(t, err)
-
- // Wait for 110% of the stabilization time to add nodes
- stabilizationKickOffWaitDuration := time.Duration(math.Ceil(1.1 * float64(config.ServerStabilizationTime)))
- time.Sleep(stabilizationKickOffWaitDuration)
-
- cli := cluster.Cores[0].Client
- // Write more keys than snapshot_threshold
- for i := 0; i < 250; i++ {
- _, err := cli.Logical().Write(fmt.Sprintf("secret/%d", i), map[string]interface{}{
- "test": "data",
- })
- if err != nil {
- t.Fatal(err)
- }
- }
-
- joinAndUnseal(t, cluster.Cores[1], cluster, false, false)
- joinAndUnseal(t, cluster.Cores[2], cluster, false, false)
-
- core2shouldBeHealthyAt := time.Now().Add(timeToHealthyCore2)
-
- stabilizationWaitDuration := time.Duration(1.25 * float64(config.ServerStabilizationTime))
- deadline := time.Now().Add(stabilizationWaitDuration)
- var core1healthy, core2healthy bool
- for time.Now().Before(deadline) {
- state, err := client.Sys().RaftAutopilotState()
- require.NoError(t, err)
- core1healthy = state.Servers["core-1"] != nil && state.Servers["core-1"].Healthy
- core2healthy = state.Servers["core-2"] != nil && state.Servers["core-2"].Healthy
- time.Sleep(1 * time.Second)
- }
- if !core1healthy || core2healthy {
- t.Fatalf("expected health: core1=true and core2=false, got: core1=%v, core2=%v", core1healthy, core2healthy)
- }
-
- time.Sleep(2 * time.Second) // wait for reconciliation
- state, err = client.Sys().RaftAutopilotState()
- require.NoError(t, err)
- require.Equal(t, []string{"core-0", "core-1"}, state.Voters)
-
- for time.Now().Before(core2shouldBeHealthyAt) {
- state, err := client.Sys().RaftAutopilotState()
- require.NoError(t, err)
- core2healthy = state.Servers["core-2"].Healthy
- time.Sleep(1 * time.Second)
- t.Log(core2healthy)
- }
-
- deadline = time.Now().Add(10 * time.Second)
- for time.Now().Before(deadline) {
- state, err = client.Sys().RaftAutopilotState()
- if err != nil {
- t.Fatal(err)
- }
- if strutil.EquivalentSlices(state.Voters, []string{"core-0", "core-1", "core-2"}) {
- break
- }
- }
- require.Equal(t, []string{"core-0", "core-1", "core-2"}, state.Voters)
-}
-
-func TestRaft_AutoPilot_Peersets_Equivalent(t *testing.T) {
- cluster, _ := raftCluster(t, &RaftClusterOpts{
- InmemCluster: true,
- EnableAutopilot: true,
- DisableFollowerJoins: true,
- })
- defer cluster.Cleanup()
- testhelpers.WaitForActiveNode(t, cluster)
-
- // Create a very large stabilization time so we can test the state between
- // joining and promotions
- client := cluster.Cores[0].Client
- _, err := client.Logical().Write("sys/storage/raft/autopilot/configuration", map[string]interface{}{
- "server_stabilization_time": "1h",
- })
- require.NoError(t, err)
-
- joinAsVoterAndUnseal(t, cluster.Cores[1], cluster)
- joinAsVoterAndUnseal(t, cluster.Cores[2], cluster)
-
- deadline := time.Now().Add(10 * time.Second)
- var core0Peers, core1Peers, core2Peers []raft.Peer
- for time.Now().Before(deadline) {
- // Make sure all nodes have an equivalent configuration
- core0Peers, err = cluster.Cores[0].UnderlyingRawStorage.(*raft.RaftBackend).Peers(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- core1Peers, err = cluster.Cores[1].UnderlyingRawStorage.(*raft.RaftBackend).Peers(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- core2Peers, err = cluster.Cores[2].UnderlyingRawStorage.(*raft.RaftBackend).Peers(context.Background())
- if err != nil {
- t.Fatal(err)
- }
-
- if len(core0Peers) == 3 && reflect.DeepEqual(core0Peers, core1Peers) && reflect.DeepEqual(core1Peers, core2Peers) {
- break
- }
- time.Sleep(time.Second)
- }
- require.Equal(t, core0Peers, core1Peers)
- require.Equal(t, core1Peers, core2Peers)
-}
-
-// TestRaft_VotersStayVoters ensures that autopilot doesn't demote a node just
-// because it hasn't been heard from in some time.
-func TestRaft_VotersStayVoters(t *testing.T) {
- cluster, _ := raftCluster(t, &RaftClusterOpts{
- DisableFollowerJoins: true,
- InmemCluster: true,
- EnableAutopilot: true,
- PhysicalFactoryConfig: map[string]interface{}{
- "performance_multiplier": "5",
- "autopilot_reconcile_interval": "300ms",
- "autopilot_update_interval": "100ms",
- },
- VersionMap: map[int]string{
- 0: version.Version,
- 1: version.Version,
- 2: version.Version,
- },
- })
- defer cluster.Cleanup()
- testhelpers.WaitForActiveNode(t, cluster)
-
- client := cluster.Cores[0].Client
-
- config, err := client.Sys().RaftAutopilotConfiguration()
- require.NoError(t, err)
- joinAndStabilizeAndPromote(t, cluster.Cores[1], client, cluster, config, "core-1", 2)
- joinAndStabilizeAndPromote(t, cluster.Cores[2], client, cluster, config, "core-2", 3)
-
- errIfNonVotersExist := func() error {
- t.Helper()
- resp, err := client.Sys().RaftAutopilotState()
- if err != nil {
- t.Fatal(err)
- }
- for k, v := range resp.Servers {
- if v.Status == "non-voter" {
- return fmt.Errorf("node %q is a non-voter", k)
- }
- }
- return nil
- }
- testhelpers.RetryUntil(t, 10*time.Second, errIfNonVotersExist)
-
- // Core0 is the leader, sealing it will both cause an election - and the
- // new leader won't have seen any heartbeats initially - and create a "down"
- // node that won't be sending heartbeats.
- testhelpers.EnsureCoreSealed(t, cluster.Cores[0])
- time.Sleep(config.ServerStabilizationTime + 2*time.Second)
- client = cluster.Cores[1].Client
- err = errIfNonVotersExist()
- require.NoError(t, err)
-}
-
-// TestRaft_Autopilot_DeadServerCleanup tests that dead servers are correctly
-// removed by Vault and autopilot when a node stops and a replacement node joins.
-// The expected behavior is that removing a node from a 3 node cluster wouldn't
-// remove it from Raft until a replacement voter had joined and stabilized/been promoted.
-func TestRaft_Autopilot_DeadServerCleanup(t *testing.T) {
- conf, opts := teststorage.ClusterSetup(nil, nil, teststorage.RaftBackendSetup)
- conf.DisableAutopilot = false
- opts.NumCores = 4
- opts.SetupFunc = nil
- opts.PhysicalFactoryConfig = map[string]interface{}{
- "autopilot_reconcile_interval": "300ms",
- "autopilot_update_interval": "100ms",
- }
-
- cluster := vault.NewTestCluster(t, conf, opts)
- cluster.Start()
- defer cluster.Cleanup()
- leader, addressProvider := setupLeaderAndUnseal(t, cluster)
-
- // Join 2 extra nodes manually, store the 3rd for later
- core1 := cluster.Cores[1]
- core2 := cluster.Cores[2]
- core3 := cluster.Cores[3]
- core1.UnderlyingRawStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
- core2.UnderlyingRawStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
- core3.UnderlyingRawStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
- joinAsVoterAndUnseal(t, core1, cluster)
- joinAsVoterAndUnseal(t, core2, cluster)
- // Do not join node 3
- testhelpers.WaitForNodesExcludingSelectedStandbys(t, cluster, 3)
-
- config, err := leader.Client.Sys().RaftAutopilotConfiguration()
- require.NoError(t, err)
- require.True(t, isHealthyAfterStabilization(t, leader, config.ServerStabilizationTime))
-
- // Ensure Autopilot has the aggressive settings
- config.CleanupDeadServers = true
- config.ServerStabilizationTime = 5 * time.Second
- config.DeadServerLastContactThreshold = 1 * time.Minute
- config.MaxTrailingLogs = 10
- config.LastContactThreshold = 10 * time.Second
- config.MinQuorum = 3
-
- // We can't use Client.Sys().PutRaftAutopilotConfiguration(config) in OSS as disable_upgrade_migration isn't in OSS
- b, err := json.Marshal(&config)
- require.NoError(t, err)
- var m map[string]interface{}
- err = json.Unmarshal(b, &m)
- require.NoError(t, err)
- delete(m, "disable_upgrade_migration")
- _, err = leader.Client.Logical().Write("sys/storage/raft/autopilot/configuration", m)
- require.NoError(t, err)
-
- // Observe for healthy state
- state, err := leader.Client.Sys().RaftAutopilotState()
- require.NoError(t, err)
- require.True(t, state.Healthy)
-
- // Kill a node (core-2)
- cluster.StopCore(t, 2)
- // Wait for just over the dead server threshold to ensure the core is classed as 'dead'
- time.Sleep(config.DeadServerLastContactThreshold + 2*time.Second)
-
- // Observe for an unhealthy state (but we still have 3 voters according to Raft)
- state, err = leader.Client.Sys().RaftAutopilotState()
- require.NoError(t, err)
- require.False(t, state.Healthy)
- require.Len(t, state.Voters, 3)
-
- // Join node 3 now
- joinAsVoterAndUnseal(t, core3, cluster)
-
- // Stabilization time
- require.True(t, isHealthyAfterStabilization(t, leader, config.ServerStabilizationTime))
-
- // Observe for healthy and contains 3 correct voters
- state, err = leader.Client.Sys().RaftAutopilotState()
- require.NoError(t, err)
- require.True(t, state.Healthy)
- require.Len(t, state.Voters, 3)
- require.Contains(t, state.Voters, "core-0")
- require.Contains(t, state.Voters, "core-1")
- require.NotContains(t, state.Voters, "core-2")
- require.Contains(t, state.Voters, "core-3")
-}
-
-func joinAndStabilizeAndPromote(t *testing.T, core *vault.TestClusterCore, client *api.Client, cluster *vault.TestCluster, config *api.AutopilotConfig, nodeID string, numServers int) {
- joinAndStabilize(t, core, client, cluster, config, nodeID, numServers)
-
- // Now that the server is stable, wait for autopilot to reconcile and
- // promotion to happen. Reconcile interval is 10 seconds. Bound it by
- // doubling.
- deadline := time.Now().Add(2 * autopilot.DefaultReconcileInterval)
- failed := true
- var err error
- var state *api.AutopilotState
- for time.Now().Before(deadline) {
- state, err = client.Sys().RaftAutopilotState()
- require.NoError(t, err)
- if state.Servers[nodeID].Status == "voter" {
- failed = false
- break
- }
- time.Sleep(1 * time.Second)
- }
-
- if failed {
- t.Fatalf("autopilot failed to promote node: id: %#v: state:%# v\n", nodeID, pretty.Formatter(state))
- }
-}
-
-func joinAndStabilize(t *testing.T, core *vault.TestClusterCore, client *api.Client, cluster *vault.TestCluster, config *api.AutopilotConfig, nodeID string, numServers int) {
- t.Helper()
- joinAndUnseal(t, core, cluster, false, false)
- time.Sleep(2 * time.Second)
-
- state, err := client.Sys().RaftAutopilotState()
- require.NoError(t, err)
- require.Equal(t, false, state.Healthy)
- require.Len(t, state.Servers, numServers)
- require.Equal(t, false, state.Servers[nodeID].Healthy)
- require.Equal(t, "alive", state.Servers[nodeID].NodeStatus)
- require.Equal(t, "non-voter", state.Servers[nodeID].Status)
-
- // Wait till the stabilization period is over
- deadline := time.Now().Add(config.ServerStabilizationTime)
- healthy := false
- for time.Now().Before(deadline) {
- state, err := client.Sys().RaftAutopilotState()
- require.NoError(t, err)
- if state.Healthy {
- healthy = true
- }
- time.Sleep(1 * time.Second)
- }
- if !healthy {
- t.Fatalf("cluster failed to stabilize")
- }
-}
-
-// joinAsVoterAndUnseal joins the specified core to the specified cluster as a voter and unseals it.
-// It will wait (up to a timeout) for the core to be fully unsealed before returning
-func joinAsVoterAndUnseal(t *testing.T, core *vault.TestClusterCore, cluster *vault.TestCluster) {
- joinAndUnseal(t, core, cluster, false, true)
-}
-
-// joinAndUnseal joins the specified core to the specified cluster and unseals it.
-// You can specify if the core should be joined as a voter/non-voter,
-// and whether to wait (up to a timeout) for the core to be unsealed before returning.
-func joinAndUnseal(t *testing.T, core *vault.TestClusterCore, cluster *vault.TestCluster, nonVoter bool, waitForUnseal bool) {
- leader, leaderAddr := clusterLeader(t, cluster)
- _, err := core.JoinRaftCluster(namespace.RootContext(context.Background()), []*raft.LeaderJoinInfo{
- {
- LeaderAPIAddr: leaderAddr,
- TLSConfig: leader.TLSConfig(),
- Retry: true,
- },
- }, nonVoter)
- require.NoError(t, err)
-
- time.Sleep(1 * time.Second)
- cluster.UnsealCore(t, core)
- if waitForUnseal {
- waitForCoreUnseal(t, core)
- }
-}
-
-// clusterLeader gets the leader node and its address from the specified cluster
-func clusterLeader(t *testing.T, cluster *vault.TestCluster) (*vault.TestClusterCore, string) {
- for _, core := range cluster.Cores {
- isLeader, addr, _, err := core.Leader()
- require.NoError(t, err)
- if isLeader {
- return core, addr
- }
- }
-
- t.Fatal("unable to find leader")
- return nil, ""
-}
-
-// setupLeaderAndUnseal configures and unseals the leader node.
-// It will wait until the node is active before returning the core and the address of the leader.
-func setupLeaderAndUnseal(t *testing.T, cluster *vault.TestCluster) (*vault.TestClusterCore, *testhelpers.TestRaftServerAddressProvider) {
- leader, _ := clusterLeader(t, cluster)
-
- // Lots of tests seem to do this when they deal with a TestRaftServerAddressProvider, it makes the test work rather than error out.
- atomic.StoreUint32(&vault.TestingUpdateClusterAddr, 1)
-
- addressProvider := &testhelpers.TestRaftServerAddressProvider{Cluster: cluster}
- testhelpers.EnsureCoreSealed(t, leader)
- leader.UnderlyingRawStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
- cluster.UnsealCore(t, leader)
- vault.TestWaitActive(t, leader.Core)
-
- return leader, addressProvider
-}
-
-// waitForCoreUnseal waits until the specified core is unsealed.
-// It fails the calling test if the deadline has elapsed and the core is still sealed.
-func waitForCoreUnseal(t *testing.T, core *vault.TestClusterCore) {
- deadline := time.Now().Add(30 * time.Second)
- for time.Now().Before(deadline) {
- if !core.Sealed() {
- return
- }
- time.Sleep(time.Second)
- }
- t.Fatalf("expected core %v to unseal before deadline but it has not", core.NodeID)
-}
-
-// isHealthyAfterStabilization will use the supplied leader core to query the
-// health of Raft Autopilot just after the specified deadline.
-func isHealthyAfterStabilization(t *testing.T, leaderCore *vault.TestClusterCore, stabilizationTime time.Duration) bool {
- timeoutGrace := 2 * time.Second
- time.Sleep(stabilizationTime + timeoutGrace)
- state, err := leaderCore.Client.Sys().RaftAutopilotState()
- require.NoError(t, err)
- require.NotNil(t, state)
- return state.Healthy
-}
diff --git a/vault/external_tests/raft/raft_binary/raft_test.go b/vault/external_tests/raft/raft_binary/raft_test.go
deleted file mode 100644
index 851961c4e..000000000
--- a/vault/external_tests/raft/raft_binary/raft_test.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package raft_binary
-
-import (
- "context"
- "os"
- "testing"
-
- "github.com/hashicorp/vault/sdk/helper/testcluster"
- "github.com/hashicorp/vault/sdk/helper/testcluster/docker"
- rafttest "github.com/hashicorp/vault/vault/external_tests/raft"
-)
-
-// TestRaft_Configuration_Docker is a variant of TestRaft_Configuration that
-// uses docker containers for the vault nodes.
-func TestRaft_Configuration_Docker(t *testing.T) {
- t.Parallel()
- binary := os.Getenv("VAULT_BINARY")
- if binary == "" {
- t.Skip("only running docker test when $VAULT_BINARY present")
- }
- opts := &docker.DockerClusterOptions{
- ImageRepo: "hashicorp/vault",
- // We're replacing the binary anyway, so we're not too particular about
- // the docker image version tag.
- ImageTag: "latest",
- VaultBinary: binary,
- ClusterOptions: testcluster.ClusterOptions{
- VaultNodeConfig: &testcluster.VaultNodeConfig{
- LogLevel: "TRACE",
- // If you want the test to run faster locally, you could
- // uncomment this performance_multiplier change.
- //StorageOptions: map[string]string{
- // "performance_multiplier": "1",
- //},
- },
- },
- }
- cluster := docker.NewTestDockerCluster(t, opts)
- defer cluster.Cleanup()
- rafttest.Raft_Configuration_Test(t, cluster)
-
- if err := cluster.AddNode(context.TODO(), opts); err != nil {
- t.Fatal(err)
- }
- rafttest.Raft_Configuration_Test(t, cluster)
-}
diff --git a/vault/external_tests/raft/raft_test.go b/vault/external_tests/raft/raft_test.go
deleted file mode 100644
index e4defdc10..000000000
--- a/vault/external_tests/raft/raft_test.go
+++ /dev/null
@@ -1,1231 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package rafttests
-
-import (
- "bytes"
- "context"
- "crypto/md5"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "strings"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/hashicorp/go-cleanhttp"
- "github.com/hashicorp/go-uuid"
- "github.com/hashicorp/vault/api"
- credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
- "github.com/hashicorp/vault/helper/benchhelpers"
- "github.com/hashicorp/vault/helper/constants"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/helper/testhelpers"
- "github.com/hashicorp/vault/helper/testhelpers/corehelpers"
- "github.com/hashicorp/vault/helper/testhelpers/teststorage"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/internalshared/configutil"
- "github.com/hashicorp/vault/physical/raft"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
- vaultseal "github.com/hashicorp/vault/vault/seal"
- "github.com/stretchr/testify/require"
- "golang.org/x/net/http2"
-)
-
-type RaftClusterOpts struct {
- DisableFollowerJoins bool
- InmemCluster bool
- EnableAutopilot bool
- PhysicalFactoryConfig map[string]interface{}
- DisablePerfStandby bool
- EnableResponseHeaderRaftNodeID bool
- NumCores int
- Seal vault.Seal
- VersionMap map[int]string
- RedundancyZoneMap map[int]string
- EffectiveSDKVersionMap map[int]string
-}
-
-func raftCluster(t testing.TB, ropts *RaftClusterOpts) (*vault.TestCluster, *vault.TestClusterOptions) {
- if ropts == nil {
- ropts = &RaftClusterOpts{}
- }
-
- conf := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": credUserpass.Factory,
- },
- DisableAutopilot: !ropts.EnableAutopilot,
- EnableResponseHeaderRaftNodeID: ropts.EnableResponseHeaderRaftNodeID,
- Seal: ropts.Seal,
- }
-
- opts := vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- }
- opts.InmemClusterLayers = ropts.InmemCluster
- opts.PhysicalFactoryConfig = ropts.PhysicalFactoryConfig
- conf.DisablePerformanceStandby = ropts.DisablePerfStandby
- opts.NumCores = ropts.NumCores
- opts.VersionMap = ropts.VersionMap
- opts.RedundancyZoneMap = ropts.RedundancyZoneMap
- opts.EffectiveSDKVersionMap = ropts.EffectiveSDKVersionMap
-
- teststorage.RaftBackendSetup(conf, &opts)
-
- if ropts.DisableFollowerJoins {
- opts.SetupFunc = nil
- }
-
- cluster := vault.NewTestCluster(benchhelpers.TBtoT(t), conf, &opts)
- cluster.Start()
- vault.TestWaitActive(benchhelpers.TBtoT(t), cluster.Cores[0].Core)
- return cluster, &opts
-}
-
-func TestRaft_BoltDBMetrics(t *testing.T) {
- t.Parallel()
- conf := vault.CoreConfig{}
- opts := vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- NumCores: 1,
- CoreMetricSinkProvider: testhelpers.TestMetricSinkProvider(time.Minute),
- DefaultHandlerProperties: vault.HandlerProperties{
- ListenerConfig: &configutil.Listener{
- Telemetry: configutil.ListenerTelemetry{
- UnauthenticatedMetricsAccess: true,
- },
- },
- },
- }
-
- teststorage.RaftBackendSetup(&conf, &opts)
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.Start()
- defer cluster.Cleanup()
-
- vault.TestWaitActive(t, cluster.Cores[0].Core)
- leaderClient := cluster.Cores[0].Client
-
- // Write a few keys
- for i := 0; i < 50; i++ {
- _, err := leaderClient.Logical().Write(fmt.Sprintf("secret/%d", i), map[string]interface{}{
- fmt.Sprintf("foo%d", i): fmt.Sprintf("bar%d", i),
- })
- if err != nil {
- t.Fatal(err)
- }
- }
-
- // Even though there is a long delay between when we start the node and when we check for these metrics,
- // the core metrics loop isn't started until postUnseal, which happens after said delay. This means we
- // need a small artificial delay here as well, otherwise we won't see any metrics emitted.
- time.Sleep(5 * time.Second)
- data, err := testhelpers.SysMetricsReq(leaderClient, cluster, true)
- if err != nil {
- t.Fatal(err)
- }
-
- noBoltDBMetrics := true
- for _, g := range data.Gauges {
- if strings.HasPrefix(g.Name, "raft_storage.bolt.") {
- noBoltDBMetrics = false
- break
- }
- }
-
- if noBoltDBMetrics {
- t.Fatal("expected to find boltdb metrics being emitted from the raft backend, but there were none")
- }
-}
-
-func TestRaft_RetryAutoJoin(t *testing.T) {
- t.Parallel()
-
- var (
- conf vault.CoreConfig
-
- opts = vault.TestClusterOptions{HandlerFunc: vaulthttp.Handler}
- )
-
- teststorage.RaftBackendSetup(&conf, &opts)
-
- opts.SetupFunc = nil
- cluster := vault.NewTestCluster(t, &conf, &opts)
-
- cluster.Start()
- defer cluster.Cleanup()
-
- addressProvider := &testhelpers.TestRaftServerAddressProvider{Cluster: cluster}
- leaderCore := cluster.Cores[0]
- atomic.StoreUint32(&vault.TestingUpdateClusterAddr, 1)
-
- {
- testhelpers.EnsureCoreSealed(t, leaderCore)
- leaderCore.UnderlyingRawStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
- cluster.UnsealCore(t, leaderCore)
- vault.TestWaitActive(t, leaderCore.Core)
- }
-
- leaderInfos := []*raft.LeaderJoinInfo{
- {
- AutoJoin: "provider=aws region=eu-west-1 tag_key=consul tag_value=tag access_key_id=a secret_access_key=a",
- TLSConfig: leaderCore.TLSConfig(),
- Retry: true,
- },
- }
-
- {
- // expected to pass but not join as we're not actually discovering leader addresses
- core := cluster.Cores[1]
- core.UnderlyingRawStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
-
- _, err := core.JoinRaftCluster(namespace.RootContext(context.Background()), leaderInfos, false)
- require.NoError(t, err)
- }
-
- err := testhelpers.VerifyRaftPeers(t, cluster.Cores[0].Client, map[string]bool{
- "core-0": true,
- })
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestRaft_Retry_Join(t *testing.T) {
- t.Parallel()
- var conf vault.CoreConfig
- opts := vault.TestClusterOptions{HandlerFunc: vaulthttp.Handler}
- teststorage.RaftBackendSetup(&conf, &opts)
- opts.SetupFunc = nil
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.Start()
- defer cluster.Cleanup()
-
- addressProvider := &testhelpers.TestRaftServerAddressProvider{Cluster: cluster}
-
- leaderCore := cluster.Cores[0]
- leaderAPI := leaderCore.Client.Address()
- atomic.StoreUint32(&vault.TestingUpdateClusterAddr, 1)
-
- {
- testhelpers.EnsureCoreSealed(t, leaderCore)
- leaderCore.UnderlyingRawStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
- }
-
- leaderInfos := []*raft.LeaderJoinInfo{
- {
- LeaderAPIAddr: leaderAPI,
- TLSConfig: leaderCore.TLSConfig(),
- Retry: true,
- },
- }
-
- var wg sync.WaitGroup
- for _, clusterCore := range cluster.Cores[1:] {
- wg.Add(1)
- go func(t *testing.T, core *vault.TestClusterCore) {
- t.Helper()
- defer wg.Done()
- core.UnderlyingRawStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
- _, err := core.JoinRaftCluster(namespace.RootContext(context.Background()), leaderInfos, false)
- if err != nil {
- t.Error(err)
- }
-
- // Handle potential racy behavior with unseals. Retry the unseal until it succeeds.
- corehelpers.RetryUntil(t, 10*time.Second, func() error {
- return cluster.AttemptUnsealCore(core)
- })
- }(t, clusterCore)
- }
-
- // Unseal the leader and wait for the other cores to unseal
- cluster.UnsealCore(t, leaderCore)
- wg.Wait()
-
- vault.TestWaitActive(t, leaderCore.Core)
-
- corehelpers.RetryUntil(t, 10*time.Second, func() error {
- return testhelpers.VerifyRaftPeers(t, cluster.Cores[0].Client, map[string]bool{
- "core-0": true,
- "core-1": true,
- "core-2": true,
- })
- })
-}
-
-func TestRaft_Join(t *testing.T) {
- t.Parallel()
- var conf vault.CoreConfig
- opts := vault.TestClusterOptions{HandlerFunc: vaulthttp.Handler}
- teststorage.RaftBackendSetup(&conf, &opts)
- opts.SetupFunc = nil
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.Start()
- defer cluster.Cleanup()
-
- addressProvider := &testhelpers.TestRaftServerAddressProvider{Cluster: cluster}
-
- leaderCore := cluster.Cores[0]
- leaderAPI := leaderCore.Client.Address()
- atomic.StoreUint32(&vault.TestingUpdateClusterAddr, 1)
-
- // Seal the leader so we can install an address provider
- {
- testhelpers.EnsureCoreSealed(t, leaderCore)
- leaderCore.UnderlyingRawStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
- cluster.UnsealCore(t, leaderCore)
- vault.TestWaitActive(t, leaderCore.Core)
- }
-
- joinFunc := func(client *api.Client, addClientCerts bool) {
- req := &api.RaftJoinRequest{
- LeaderAPIAddr: leaderAPI,
- LeaderCACert: string(cluster.CACertPEM),
- }
- if addClientCerts {
- req.LeaderClientCert = string(cluster.CACertPEM)
- req.LeaderClientKey = string(cluster.CAKeyPEM)
- }
- resp, err := client.Sys().RaftJoin(req)
- if err != nil {
- t.Fatal(err)
- }
- if !resp.Joined {
- t.Fatalf("failed to join raft cluster")
- }
- }
-
- joinFunc(cluster.Cores[1].Client, false)
- joinFunc(cluster.Cores[2].Client, false)
-
- _, err := cluster.Cores[0].Client.Logical().Write("sys/storage/raft/remove-peer", map[string]interface{}{
- "server_id": "core-1",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = cluster.Cores[0].Client.Logical().Write("sys/storage/raft/remove-peer", map[string]interface{}{
- "server_id": "core-2",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- joinFunc(cluster.Cores[1].Client, true)
- joinFunc(cluster.Cores[2].Client, true)
-}
-
-func TestRaft_RemovePeer(t *testing.T) {
- t.Parallel()
- cluster, _ := raftCluster(t, nil)
- defer cluster.Cleanup()
-
- for i, c := range cluster.Cores {
- if c.Core.Sealed() {
- t.Fatalf("failed to unseal core %d", i)
- }
- }
-
- client := cluster.Cores[0].Client
-
- err := testhelpers.VerifyRaftPeers(t, client, map[string]bool{
- "core-0": true,
- "core-1": true,
- "core-2": true,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("sys/storage/raft/remove-peer", map[string]interface{}{
- "server_id": "core-2",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- err = testhelpers.VerifyRaftPeers(t, client, map[string]bool{
- "core-0": true,
- "core-1": true,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("sys/storage/raft/remove-peer", map[string]interface{}{
- "server_id": "core-1",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- err = testhelpers.VerifyRaftPeers(t, client, map[string]bool{
- "core-0": true,
- })
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestRaft_NodeIDHeader(t *testing.T) {
- t.Parallel()
- testCases := []struct {
- description string
- ropts *RaftClusterOpts
- headerPresent bool
- }{
- {
- description: "with no header configured",
- ropts: nil,
- headerPresent: false,
- },
- {
- description: "with header configured",
- ropts: &RaftClusterOpts{
- EnableResponseHeaderRaftNodeID: true,
- },
- headerPresent: true,
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.description, func(t *testing.T) {
- cluster, _ := raftCluster(t, tc.ropts)
- defer cluster.Cleanup()
-
- for i, c := range cluster.Cores {
- if c.Core.Sealed() {
- t.Fatalf("failed to unseal core %d", i)
- }
-
- client := c.Client
- req := client.NewRequest("GET", "/v1/sys/seal-status")
- resp, err := client.RawRequest(req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if resp == nil {
- t.Fatalf("nil response")
- }
-
- rniHeader := resp.Header.Get("X-Vault-Raft-Node-ID")
- nodeID := c.Core.GetRaftNodeID()
-
- if tc.headerPresent && rniHeader == "" {
- t.Fatal("missing 'X-Vault-Raft-Node-ID' header entry in response")
- }
- if tc.headerPresent && rniHeader != nodeID {
- t.Fatalf("got the wrong raft node id. expected %s to equal %s", rniHeader, nodeID)
- }
- if !tc.headerPresent && rniHeader != "" {
- t.Fatal("didn't expect 'X-Vault-Raft-Node-ID' header but it was present anyway")
- }
- }
- })
- }
-}
-
-func TestRaft_Configuration(t *testing.T) {
- t.Parallel()
- cluster, _ := raftCluster(t, nil)
- defer cluster.Cleanup()
- Raft_Configuration_Test(t, cluster)
-}
-
-func TestRaft_ShamirUnseal(t *testing.T) {
- t.Parallel()
- cluster, _ := raftCluster(t, nil)
- defer cluster.Cleanup()
-
- for i, c := range cluster.Cores {
- if c.Core.Sealed() {
- t.Fatalf("failed to unseal core %d", i)
- }
- }
-}
-
-func TestRaft_SnapshotAPI(t *testing.T) {
- t.Parallel()
- cluster, _ := raftCluster(t, nil)
- defer cluster.Cleanup()
-
- leaderClient := cluster.Cores[0].Client
-
- // Write a few keys
- for i := 0; i < 10; i++ {
- _, err := leaderClient.Logical().Write(fmt.Sprintf("secret/%d", i), map[string]interface{}{
- "test": "data",
- })
- if err != nil {
- t.Fatal(err)
- }
- }
-
- // Take a snapshot
- buf := new(bytes.Buffer)
- err := leaderClient.Sys().RaftSnapshot(buf)
- if err != nil {
- t.Fatal(err)
- }
- snap, err := io.ReadAll(buf)
- if err != nil {
- t.Fatal(err)
- }
- if len(snap) == 0 {
- t.Fatal("no snapshot returned")
- }
-
- // Write a few more keys
- for i := 10; i < 20; i++ {
- _, err := leaderClient.Logical().Write(fmt.Sprintf("secret/%d", i), map[string]interface{}{
- "test": "data",
- })
- if err != nil {
- t.Fatal(err)
- }
- }
- // Restore snapshot
- err = leaderClient.Sys().RaftSnapshotRestore(bytes.NewReader(snap), false)
- if err != nil {
- t.Fatal(err)
- }
-
- // List kv to make sure we removed the extra keys
- secret, err := leaderClient.Logical().List("secret/")
- if err != nil {
- t.Fatal(err)
- }
-
- if len(secret.Data["keys"].([]interface{})) != 10 {
- t.Fatal("snapshot didn't apply correctly")
- }
-}
-
-func TestRaft_SnapshotAPI_MidstreamFailure(t *testing.T) {
- // defer goleak.VerifyNone(t)
- t.Parallel()
-
- seal, setErr := vaultseal.NewToggleableTestSeal(nil)
- autoSeal, err := vault.NewAutoSeal(seal)
- if err != nil {
- t.Fatal(err)
- }
- cluster, _ := raftCluster(t, &RaftClusterOpts{
- NumCores: 1,
- Seal: autoSeal,
- })
- defer cluster.Cleanup()
-
- leaderClient := cluster.Cores[0].Client
-
- // Write a bunch of keys; if too few, the detection code in api.RaftSnapshot
- // will never make it into the tar part, it'll fail merely when trying to
- // decompress the stream.
- for i := 0; i < 1000; i++ {
- _, err := leaderClient.Logical().Write(fmt.Sprintf("secret/%d", i), map[string]interface{}{
- "test": "data",
- })
- if err != nil {
- t.Fatal(err)
- }
- }
-
- r, w := io.Pipe()
- var snap []byte
- var wg sync.WaitGroup
- wg.Add(1)
-
- var readErr error
- go func() {
- snap, readErr = ioutil.ReadAll(r)
- wg.Done()
- }()
-
- setErr(errors.New("seal failure"))
- // Take a snapshot
- err = leaderClient.Sys().RaftSnapshot(w)
- w.Close()
- if err == nil || err != api.ErrIncompleteSnapshot {
- t.Fatalf("expected err=%v, got: %v", api.ErrIncompleteSnapshot, err)
- }
- wg.Wait()
- if len(snap) == 0 && readErr == nil {
- readErr = errors.New("no bytes read")
- }
- if readErr != nil {
- t.Fatal(readErr)
- }
-}
-
-func TestRaft_SnapshotAPI_RekeyRotate_Backward(t *testing.T) {
- type testCase struct {
- Name string
- Rekey bool
- Rotate bool
- DisablePerfStandby bool
- }
-
- tCases := []testCase{
- {
- Name: "rekey",
- Rekey: true,
- Rotate: false,
- DisablePerfStandby: true,
- },
- {
- Name: "rotate",
- Rekey: false,
- Rotate: true,
- DisablePerfStandby: true,
- },
- {
- Name: "both",
- Rekey: true,
- Rotate: true,
- DisablePerfStandby: true,
- },
- }
-
- if constants.IsEnterprise {
- tCases = append(tCases, []testCase{
- {
- Name: "rekey-with-perf-standby",
- Rekey: true,
- Rotate: false,
- DisablePerfStandby: false,
- },
- {
- Name: "rotate-with-perf-standby",
- Rekey: false,
- Rotate: true,
- DisablePerfStandby: false,
- },
- {
- Name: "both-with-perf-standby",
- Rekey: true,
- Rotate: true,
- DisablePerfStandby: false,
- },
- }...)
- }
-
- for _, tCase := range tCases {
- t.Run(tCase.Name, func(t *testing.T) {
- // bind locally
- tCaseLocal := tCase
- t.Parallel()
-
- cluster, _ := raftCluster(t, &RaftClusterOpts{DisablePerfStandby: tCaseLocal.DisablePerfStandby})
- defer cluster.Cleanup()
-
- leaderClient := cluster.Cores[0].Client
-
- // Write a few keys
- for i := 0; i < 10; i++ {
- _, err := leaderClient.Logical().Write(fmt.Sprintf("secret/%d", i), map[string]interface{}{
- "test": "data",
- })
- if err != nil {
- t.Fatal(err)
- }
- }
-
- transport := cleanhttp.DefaultPooledTransport()
- transport.TLSClientConfig = cluster.Cores[0].TLSConfig()
- if err := http2.ConfigureTransport(transport); err != nil {
- t.Fatal(err)
- }
- client := &http.Client{
- Transport: transport,
- }
-
- // Take a snapshot
- req := leaderClient.NewRequest("GET", "/v1/sys/storage/raft/snapshot")
- httpReq, err := req.ToHTTP()
- if err != nil {
- t.Fatal(err)
- }
- resp, err := client.Do(httpReq)
- if err != nil {
- t.Fatal(err)
- }
- defer resp.Body.Close()
-
- snap, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Fatal(err)
- }
- if len(snap) == 0 {
- t.Fatal("no snapshot returned")
- }
-
- // cache the original barrier keys
- barrierKeys := cluster.BarrierKeys
-
- if tCaseLocal.Rotate {
- // Rotate
- err = leaderClient.Sys().Rotate()
- if err != nil {
- t.Fatal(err)
- }
-
- testhelpers.EnsureStableActiveNode(t, cluster)
- testhelpers.WaitForActiveNodeAndStandbys(t, cluster)
- }
-
- if tCaseLocal.Rekey {
- // Rekey
- cluster.BarrierKeys = testhelpers.RekeyCluster(t, cluster, false)
-
- testhelpers.EnsureStableActiveNode(t, cluster)
- testhelpers.WaitForActiveNodeAndStandbys(t, cluster)
- }
-
- if tCaseLocal.Rekey {
- // Restore snapshot, should fail.
- req = leaderClient.NewRequest("POST", "/v1/sys/storage/raft/snapshot")
- req.Body = bytes.NewBuffer(snap)
- httpReq, err = req.ToHTTP()
- if err != nil {
- t.Fatal(err)
- }
- resp, err = client.Do(httpReq)
- if err != nil {
- t.Fatal(err)
- }
- // Parse Response
- apiResp := api.Response{Response: resp}
- if !strings.Contains(apiResp.Error().Error(), "could not verify hash file, possibly the snapshot is using a different set of unseal keys") {
- t.Fatal(apiResp.Error())
- }
- }
-
- // Restore snapshot force
- req = leaderClient.NewRequest("POST", "/v1/sys/storage/raft/snapshot-force")
- req.Body = bytes.NewBuffer(snap)
- httpReq, err = req.ToHTTP()
- if err != nil {
- t.Fatal(err)
- }
- resp, err = client.Do(httpReq)
- if err != nil {
- t.Fatal(err)
- }
-
- testhelpers.EnsureStableActiveNode(t, cluster)
- testhelpers.WaitForActiveNodeAndStandbys(t, cluster)
-
- // Write some data so we can make sure we can read it later. This is testing
- // that we correctly reload the keyring
- _, err = leaderClient.Logical().Write("secret/foo", map[string]interface{}{
- "test": "data",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- testhelpers.EnsureCoresSealed(t, cluster)
-
- cluster.BarrierKeys = barrierKeys
- testhelpers.EnsureCoresUnsealed(t, cluster)
- testhelpers.WaitForActiveNode(t, cluster)
- activeCore := testhelpers.DeriveStableActiveCore(t, cluster)
-
- // Read the value.
- data, err := activeCore.Client.Logical().Read("secret/foo")
- if err != nil {
- t.Fatal(err)
- }
- if data.Data["test"].(string) != "data" {
- t.Fatal(data)
- }
- })
- }
-}
-
-func TestRaft_SnapshotAPI_RekeyRotate_Forward(t *testing.T) {
- type testCase struct {
- Name string
- Rekey bool
- Rotate bool
- ShouldSeal bool
- DisablePerfStandby bool
- }
-
- tCases := []testCase{
- {
- Name: "rekey",
- Rekey: true,
- Rotate: false,
- ShouldSeal: false,
- DisablePerfStandby: true,
- },
- {
- Name: "rotate",
- Rekey: false,
- Rotate: true,
- // Rotate writes a new master key upgrade using the new term, which
- // we can no longer decrypt. We must seal here.
- ShouldSeal: true,
- DisablePerfStandby: true,
- },
- {
- Name: "both",
- Rekey: true,
- Rotate: true,
- // If we are moving forward and we have rekeyed and rotated there
- // isn't any way to restore the latest keys so expect to seal.
- ShouldSeal: true,
- DisablePerfStandby: true,
- },
- }
-
- if constants.IsEnterprise {
- tCases = append(tCases, []testCase{
- {
- Name: "rekey-with-perf-standby",
- Rekey: true,
- Rotate: false,
- ShouldSeal: false,
- DisablePerfStandby: false,
- },
- {
- Name: "rotate-with-perf-standby",
- Rekey: false,
- Rotate: true,
- // Rotate writes a new master key upgrade using the new term, which
- // we can no longer decrypt. We must seal here.
- ShouldSeal: true,
- DisablePerfStandby: false,
- },
- {
- Name: "both-with-perf-standby",
- Rekey: true,
- Rotate: true,
- // If we are moving forward and we have rekeyed and rotated there
- // isn't any way to restore the latest keys so expect to seal.
- ShouldSeal: true,
- DisablePerfStandby: false,
- },
- }...)
- }
-
- for _, tCase := range tCases {
- t.Run(tCase.Name, func(t *testing.T) {
- // bind locally
- tCaseLocal := tCase
- t.Parallel()
-
- cluster, _ := raftCluster(t, &RaftClusterOpts{DisablePerfStandby: tCaseLocal.DisablePerfStandby})
- defer cluster.Cleanup()
-
- leaderClient := cluster.Cores[0].Client
-
- // Write a few keys
- for i := 0; i < 10; i++ {
- _, err := leaderClient.Logical().Write(fmt.Sprintf("secret/%d", i), map[string]interface{}{
- "test": "data",
- })
- if err != nil {
- t.Fatal(err)
- }
- }
-
- transport := cleanhttp.DefaultPooledTransport()
- transport.TLSClientConfig = cluster.Cores[0].TLSConfig()
- if err := http2.ConfigureTransport(transport); err != nil {
- t.Fatal(err)
- }
- client := &http.Client{
- Transport: transport,
- }
-
- // Take a snapshot
- req := leaderClient.NewRequest("GET", "/v1/sys/storage/raft/snapshot")
- httpReq, err := req.ToHTTP()
- if err != nil {
- t.Fatal(err)
- }
- resp, err := client.Do(httpReq)
- if err != nil {
- t.Fatal(err)
- }
-
- snap, err := ioutil.ReadAll(resp.Body)
- resp.Body.Close()
- if err != nil {
- t.Fatal(err)
- }
- if len(snap) == 0 {
- t.Fatal("no snapshot returned")
- }
-
- if tCaseLocal.Rekey {
- // Rekey
- cluster.BarrierKeys = testhelpers.RekeyCluster(t, cluster, false)
-
- testhelpers.EnsureStableActiveNode(t, cluster)
- testhelpers.WaitForActiveNodeAndStandbys(t, cluster)
- }
- if tCaseLocal.Rotate {
- // Set the key clean up to 0 so it's cleaned immediately. This
- // will simulate that there are no ways to upgrade to the latest
- // term.
- for _, c := range cluster.Cores {
- c.Core.SetKeyRotateGracePeriod(0)
- }
-
- // Rotate
- err = leaderClient.Sys().Rotate()
- if err != nil {
- t.Fatal(err)
- }
-
- if !tCaseLocal.DisablePerfStandby {
- // Without the key upgrade the perf standby nodes will seal and
- // raft will get into a failure state. Make sure we get the
- // cluster back into a healthy state before moving forward.
- testhelpers.WaitForNCoresSealed(t, cluster, 2)
- testhelpers.EnsureCoresUnsealed(t, cluster)
- testhelpers.WaitForActiveNodeAndStandbys(t, cluster)
-
- active := testhelpers.DeriveActiveCore(t, cluster)
- leaderClient = active.Client
- }
- }
-
- // cache the new barrier keys
- newBarrierKeys := cluster.BarrierKeys
-
- // Take another snapshot for later use in "jumping" forward
- req = leaderClient.NewRequest("GET", "/v1/sys/storage/raft/snapshot")
- httpReq, err = req.ToHTTP()
- if err != nil {
- t.Fatal(err)
- }
- resp, err = client.Do(httpReq)
- if err != nil {
- t.Fatal(err)
- }
-
- snap2, err := ioutil.ReadAll(resp.Body)
- resp.Body.Close()
- if err != nil {
- t.Fatal(err)
- }
- if len(snap2) == 0 {
- t.Fatal("no snapshot returned")
- }
-
- // Restore snapshot to move us back in time so we can test going
- // forward
- req = leaderClient.NewRequest("POST", "/v1/sys/storage/raft/snapshot-force")
- req.Body = bytes.NewBuffer(snap)
- httpReq, err = req.ToHTTP()
- if err != nil {
- t.Fatal(err)
- }
- resp, err = client.Do(httpReq)
- if err != nil {
- t.Fatal(err)
- }
-
- testhelpers.EnsureStableActiveNode(t, cluster)
- testhelpers.WaitForActiveNodeAndStandbys(t, cluster)
- if tCaseLocal.Rekey {
- // Restore snapshot, should fail.
- req = leaderClient.NewRequest("POST", "/v1/sys/storage/raft/snapshot")
- req.Body = bytes.NewBuffer(snap2)
- httpReq, err = req.ToHTTP()
- if err != nil {
- t.Fatal(err)
- }
- resp, err = client.Do(httpReq)
- if err != nil {
- t.Fatal(err)
- }
- // Parse Response
- apiResp := api.Response{Response: resp}
- if apiResp.Error() == nil || !strings.Contains(apiResp.Error().Error(), "could not verify hash file, possibly the snapshot is using a different set of unseal keys") {
- t.Fatalf("expected error verifying hash file, got %v", apiResp.Error())
- }
-
- }
-
- // Restore snapshot force
- req = leaderClient.NewRequest("POST", "/v1/sys/storage/raft/snapshot-force")
- req.Body = bytes.NewBuffer(snap2)
- httpReq, err = req.ToHTTP()
- if err != nil {
- t.Fatal(err)
- }
- resp, err = client.Do(httpReq)
- if err != nil {
- t.Fatal(err)
- }
-
- switch tCaseLocal.ShouldSeal {
- case true:
- testhelpers.WaitForNCoresSealed(t, cluster, 3)
-
- case false:
- testhelpers.EnsureStableActiveNode(t, cluster)
- testhelpers.WaitForActiveNodeAndStandbys(t, cluster)
-
- // Write some data so we can make sure we can read it later. This is testing
- // that we correctly reload the keyring
- _, err = leaderClient.Logical().Write("secret/foo", map[string]interface{}{
- "test": "data",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- testhelpers.EnsureCoresSealed(t, cluster)
-
- cluster.BarrierKeys = newBarrierKeys
- testhelpers.EnsureCoresUnsealed(t, cluster)
- testhelpers.WaitForActiveNode(t, cluster)
- activeCore := testhelpers.DeriveStableActiveCore(t, cluster)
-
- // Read the value.
- data, err := activeCore.Client.Logical().Read("secret/foo")
- if err != nil {
- t.Fatal(err)
- }
- if data.Data["test"].(string) != "data" {
- t.Fatal(data)
- }
- }
- })
- }
-}
-
-func TestRaft_SnapshotAPI_DifferentCluster(t *testing.T) {
- t.Parallel()
- cluster, _ := raftCluster(t, nil)
- defer cluster.Cleanup()
-
- leaderClient := cluster.Cores[0].Client
-
- // Write a few keys
- for i := 0; i < 10; i++ {
- _, err := leaderClient.Logical().Write(fmt.Sprintf("secret/%d", i), map[string]interface{}{
- "test": "data",
- })
- if err != nil {
- t.Fatal(err)
- }
- }
-
- transport := cleanhttp.DefaultPooledTransport()
- transport.TLSClientConfig = cluster.Cores[0].TLSConfig()
- if err := http2.ConfigureTransport(transport); err != nil {
- t.Fatal(err)
- }
- client := &http.Client{
- Transport: transport,
- }
-
- // Take a snapshot
- req := leaderClient.NewRequest("GET", "/v1/sys/storage/raft/snapshot")
- httpReq, err := req.ToHTTP()
- if err != nil {
- t.Fatal(err)
- }
- resp, err := client.Do(httpReq)
- if err != nil {
- t.Fatal(err)
- }
-
- snap, err := ioutil.ReadAll(resp.Body)
- resp.Body.Close()
- if err != nil {
- t.Fatal(err)
- }
- if len(snap) == 0 {
- t.Fatal("no snapshot returned")
- }
-
- // Cluster 2
- {
- cluster2, _ := raftCluster(t, nil)
- defer cluster2.Cleanup()
-
- leaderClient := cluster2.Cores[0].Client
-
- transport := cleanhttp.DefaultPooledTransport()
- transport.TLSClientConfig = cluster2.Cores[0].TLSConfig()
- if err := http2.ConfigureTransport(transport); err != nil {
- t.Fatal(err)
- }
- client := &http.Client{
- Transport: transport,
- }
- // Restore snapshot, should fail.
- req = leaderClient.NewRequest("POST", "/v1/sys/storage/raft/snapshot")
- req.Body = bytes.NewBuffer(snap)
- httpReq, err = req.ToHTTP()
- if err != nil {
- t.Fatal(err)
- }
- resp, err = client.Do(httpReq)
- if err != nil {
- t.Fatal(err)
- }
- // Parse Response
- apiResp := api.Response{Response: resp}
- if !strings.Contains(apiResp.Error().Error(), "could not verify hash file, possibly the snapshot is using a different set of unseal keys") {
- t.Fatal(apiResp.Error())
- }
-
- // Restore snapshot force
- req = leaderClient.NewRequest("POST", "/v1/sys/storage/raft/snapshot-force")
- req.Body = bytes.NewBuffer(snap)
- httpReq, err = req.ToHTTP()
- if err != nil {
- t.Fatal(err)
- }
- resp, err = client.Do(httpReq)
- if err != nil {
- t.Fatal(err)
- }
-
- testhelpers.WaitForNCoresSealed(t, cluster2, 3)
- }
-}
-
-func BenchmarkRaft_SingleNode(b *testing.B) {
- cluster, _ := raftCluster(b, nil)
- defer cluster.Cleanup()
-
- leaderClient := cluster.Cores[0].Client
-
- bench := func(b *testing.B, dataSize int) {
- data, err := uuid.GenerateRandomBytes(dataSize)
- if err != nil {
- b.Fatal(err)
- }
-
- testName := b.Name()
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- key := fmt.Sprintf("secret/%x", md5.Sum([]byte(fmt.Sprintf("%s-%d", testName, i))))
- _, err := leaderClient.Logical().Write(key, map[string]interface{}{
- "test": data,
- })
- if err != nil {
- b.Fatal(err)
- }
- }
- }
-
- b.Run("256b", func(b *testing.B) { bench(b, 25) })
-}
-
-func TestRaft_Join_InitStatus(t *testing.T) {
- t.Parallel()
- var conf vault.CoreConfig
- opts := vault.TestClusterOptions{HandlerFunc: vaulthttp.Handler}
- teststorage.RaftBackendSetup(&conf, &opts)
- opts.SetupFunc = nil
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.Start()
- defer cluster.Cleanup()
-
- addressProvider := &testhelpers.TestRaftServerAddressProvider{Cluster: cluster}
-
- leaderCore := cluster.Cores[0]
- leaderAPI := leaderCore.Client.Address()
- atomic.StoreUint32(&vault.TestingUpdateClusterAddr, 1)
-
- // Seal the leader so we can install an address provider
- {
- testhelpers.EnsureCoreSealed(t, leaderCore)
- leaderCore.UnderlyingRawStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
- cluster.UnsealCore(t, leaderCore)
- vault.TestWaitActive(t, leaderCore.Core)
- }
-
- joinFunc := func(client *api.Client) {
- req := &api.RaftJoinRequest{
- LeaderAPIAddr: leaderAPI,
- LeaderCACert: string(cluster.CACertPEM),
- }
- resp, err := client.Sys().RaftJoin(req)
- if err != nil {
- t.Fatal(err)
- }
- if !resp.Joined {
- t.Fatalf("failed to join raft cluster")
- }
- }
-
- verifyInitStatus := func(coreIdx int, expected bool) {
- t.Helper()
- client := cluster.Cores[coreIdx].Client
-
- initialized, err := client.Sys().InitStatus()
- if err != nil {
- t.Fatal(err)
- }
-
- if initialized != expected {
- t.Errorf("core %d: expected init=%v, sys/init returned %v", coreIdx, expected, initialized)
- }
-
- status, err := client.Sys().SealStatus()
- if err != nil {
- t.Fatal(err)
- }
-
- if status.Initialized != expected {
- t.Errorf("core %d: expected init=%v, sys/seal-status returned %v", coreIdx, expected, status.Initialized)
- }
-
- health, err := client.Sys().Health()
- if err != nil {
- t.Fatal(err)
- }
- if health.Initialized != expected {
- t.Errorf("core %d: expected init=%v, sys/health returned %v", coreIdx, expected, health.Initialized)
- }
- }
-
- for i := range cluster.Cores {
- verifyInitStatus(i, i < 1)
- }
-
- joinFunc(cluster.Cores[1].Client)
- for i, core := range cluster.Cores {
- verifyInitStatus(i, i < 2)
- if i == 1 {
- cluster.UnsealCore(t, core)
- verifyInitStatus(i, true)
- }
- }
-
- joinFunc(cluster.Cores[2].Client)
- for i, core := range cluster.Cores {
- verifyInitStatus(i, true)
- if i == 2 {
- cluster.UnsealCore(t, core)
- verifyInitStatus(i, true)
- }
- }
-
- testhelpers.WaitForActiveNodeAndStandbys(t, cluster)
- for i := range cluster.Cores {
- verifyInitStatus(i, true)
- }
-}
diff --git a/vault/external_tests/raftha/raft_ha_test.go b/vault/external_tests/raftha/raft_ha_test.go
deleted file mode 100644
index 1cf00329f..000000000
--- a/vault/external_tests/raftha/raft_ha_test.go
+++ /dev/null
@@ -1,256 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package raftha
-
-import (
- "sync/atomic"
- "testing"
-
- "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/helper/testhelpers"
- "github.com/hashicorp/vault/helper/testhelpers/teststorage"
- consulstorage "github.com/hashicorp/vault/helper/testhelpers/teststorage/consul"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/physical/raft"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/vault"
-)
-
-func TestRaft_HA_NewCluster(t *testing.T) {
- t.Run("file", func(t *testing.T) {
- t.Parallel()
-
- t.Run("no_client_certs", func(t *testing.T) {
- testRaftHANewCluster(t, teststorage.MakeFileBackend, false)
- })
-
- t.Run("with_client_certs", func(t *testing.T) {
- testRaftHANewCluster(t, teststorage.MakeFileBackend, true)
- })
- })
-
- t.Run("inmem", func(t *testing.T) {
- t.Parallel()
-
- t.Run("no_client_certs", func(t *testing.T) {
- testRaftHANewCluster(t, teststorage.MakeInmemBackend, false)
- })
-
- t.Run("with_client_certs", func(t *testing.T) {
- testRaftHANewCluster(t, teststorage.MakeInmemBackend, true)
- })
- })
-
- t.Run("consul", func(t *testing.T) {
- t.Parallel()
-
- t.Run("no_client_certs", func(t *testing.T) {
- testRaftHANewCluster(t, consulstorage.MakeConsulBackend, false)
- })
-
- t.Run("with_client_certs", func(t *testing.T) {
- testRaftHANewCluster(t, consulstorage.MakeConsulBackend, true)
- })
- })
-}
-
-func testRaftHANewCluster(t *testing.T, bundler teststorage.PhysicalBackendBundler, addClientCerts bool) {
- var conf vault.CoreConfig
- opts := vault.TestClusterOptions{HandlerFunc: vaulthttp.Handler}
-
- teststorage.RaftHASetup(&conf, &opts, bundler)
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.Start()
- defer cluster.Cleanup()
-
- addressProvider := &testhelpers.TestRaftServerAddressProvider{Cluster: cluster}
-
- leaderCore := cluster.Cores[0]
- atomic.StoreUint32(&vault.TestingUpdateClusterAddr, 1)
-
- // Seal the leader so we can install an address provider
- {
- testhelpers.EnsureCoreSealed(t, leaderCore)
- leaderCore.UnderlyingHAStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
- cluster.UnsealCore(t, leaderCore)
- vault.TestWaitActive(t, leaderCore.Core)
- }
-
- // Now unseal core for join commands to work
- testhelpers.EnsureCoresUnsealed(t, cluster)
-
- joinFunc := func(client *api.Client, addClientCerts bool) {
- req := &api.RaftJoinRequest{
- LeaderCACert: string(cluster.CACertPEM),
- }
- if addClientCerts {
- req.LeaderClientCert = string(cluster.CACertPEM)
- req.LeaderClientKey = string(cluster.CAKeyPEM)
- }
- resp, err := client.Sys().RaftJoin(req)
- if err != nil {
- t.Fatal(err)
- }
- if !resp.Joined {
- t.Fatalf("failed to join raft cluster")
- }
- }
-
- joinFunc(cluster.Cores[1].Client, addClientCerts)
- joinFunc(cluster.Cores[2].Client, addClientCerts)
-
- // Ensure peers are added
- leaderClient := cluster.Cores[0].Client
- err := testhelpers.VerifyRaftPeers(t, leaderClient, map[string]bool{
- "core-0": true,
- "core-1": true,
- "core-2": true,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Test remove peers
- _, err = leaderClient.Logical().Write("sys/storage/raft/remove-peer", map[string]interface{}{
- "server_id": "core-1",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = leaderClient.Logical().Write("sys/storage/raft/remove-peer", map[string]interface{}{
- "server_id": "core-2",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Ensure peers are removed
- err = testhelpers.VerifyRaftPeers(t, leaderClient, map[string]bool{
- "core-0": true,
- })
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestRaft_HA_ExistingCluster(t *testing.T) {
- t.Parallel()
- conf := vault.CoreConfig{
- DisablePerformanceStandby: true,
- }
- opts := vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- NumCores: vault.DefaultNumCores,
- KeepStandbysSealed: true,
- }
- logger := logging.NewVaultLogger(hclog.Debug).Named(t.Name())
-
- physBundle := teststorage.MakeInmemBackend(t, logger)
- physBundle.HABackend = nil
-
- storage, cleanup := teststorage.MakeReusableStorage(t, logger, physBundle)
- defer cleanup()
-
- var (
- clusterBarrierKeys [][]byte
- clusterRootToken string
- )
- createCluster := func(t *testing.T) {
- t.Log("simulating cluster creation without raft as HABackend")
-
- storage.Setup(&conf, &opts)
-
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.Start()
- defer func() {
- cluster.Cleanup()
- storage.Cleanup(t, cluster)
- }()
-
- clusterBarrierKeys = cluster.BarrierKeys
- clusterRootToken = cluster.RootToken
- }
-
- createCluster(t)
-
- haStorage, haCleanup := teststorage.MakeReusableRaftHAStorage(t, logger, opts.NumCores, physBundle)
- defer haCleanup()
-
- updateCluster := func(t *testing.T) {
- t.Log("simulating cluster update with raft as HABackend")
-
- opts.SkipInit = true
- haStorage.Setup(&conf, &opts)
-
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.Start()
- defer func() {
- cluster.Cleanup()
- haStorage.Cleanup(t, cluster)
- }()
-
- // Set cluster values
- cluster.BarrierKeys = clusterBarrierKeys
- cluster.RootToken = clusterRootToken
-
- addressProvider := &testhelpers.TestRaftServerAddressProvider{Cluster: cluster}
- atomic.StoreUint32(&vault.TestingUpdateClusterAddr, 1)
-
- // Seal the leader so we can install an address provider
- leaderCore := cluster.Cores[0]
- {
- testhelpers.EnsureCoreSealed(t, leaderCore)
- leaderCore.UnderlyingHAStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
- testhelpers.EnsureCoreUnsealed(t, cluster, leaderCore)
- }
-
- // Call the bootstrap on the leader and then ensure that it becomes active
- leaderClient := cluster.Cores[0].Client
- leaderClient.SetToken(clusterRootToken)
- {
- _, err := leaderClient.Logical().Write("sys/storage/raft/bootstrap", nil)
- if err != nil {
- t.Fatal(err)
- }
- vault.TestWaitActive(t, leaderCore.Core)
- }
-
- // Set address provider
- cluster.Cores[1].UnderlyingHAStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
- cluster.Cores[2].UnderlyingHAStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
-
- // Now unseal core for join commands to work
- testhelpers.EnsureCoresUnsealed(t, cluster)
-
- joinFunc := func(client *api.Client) {
- req := &api.RaftJoinRequest{
- LeaderCACert: string(cluster.CACertPEM),
- }
- resp, err := client.Sys().RaftJoin(req)
- if err != nil {
- t.Fatal(err)
- }
- if !resp.Joined {
- t.Fatalf("failed to join raft cluster")
- }
- }
-
- joinFunc(cluster.Cores[1].Client)
- joinFunc(cluster.Cores[2].Client)
-
- // Ensure peers are added
- err := testhelpers.VerifyRaftPeers(t, leaderClient, map[string]bool{
- "core-0": true,
- "core-1": true,
- "core-2": true,
- })
- if err != nil {
- t.Fatal(err)
- }
- }
-
- updateCluster(t)
-}
diff --git a/vault/external_tests/replication_binary/replication_test.go b/vault/external_tests/replication_binary/replication_test.go
deleted file mode 100644
index 9f8a88d09..000000000
--- a/vault/external_tests/replication_binary/replication_test.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package replication_binary
-
-/*
-Example of how to use docker.NewReplicationSetDocker(t), assuming
-you point VAULT_BINARY to an Enterprise Vault binary:
-
-import (
- "context"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/sdk/helper/testcluster/docker"
-)
-
-// TestStandardPerfReplication_Docker tests that we can create two 3-node
-// clusters of docker containers and connect them using perf replication.
-func TestStandardPerfReplication_Docker(t *testing.T) {
- r, err := docker.NewReplicationSetDocker(t)
- if err != nil {
- t.Fatal(err)
- }
- defer r.Cleanup()
-
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
- defer cancel()
- err = r.StandardPerfReplication(ctx)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-*/
diff --git a/vault/external_tests/response/allowed_response_headers_test.go b/vault/external_tests/response/allowed_response_headers_test.go
deleted file mode 100644
index d9dc2e4f2..000000000
--- a/vault/external_tests/response/allowed_response_headers_test.go
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package identity
-
-import (
- "context"
- "testing"
-
- "github.com/hashicorp/vault/api"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/framework"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
- "github.com/kr/pretty"
-)
-
-func TestIdentityStore_EntityDisabled(t *testing.T) {
- be := &framework.Backend{
- PathsSpecial: &logical.Paths{
- Unauthenticated: []string{
- "login*",
- },
- },
- Paths: []*framework.Path{
- {
- Pattern: "login",
- Callbacks: map[logical.Operation]framework.OperationFunc{
- logical.ReadOperation: func(context.Context, *logical.Request, *framework.FieldData) (*logical.Response, error) {
- return &logical.Response{
- Headers: map[string][]string{
- "www-authenticate": {"Negotiate"},
- },
- }, logical.CodedError(401, "authentication required")
- },
- },
- },
- {
- Pattern: "loginnoerror",
- Callbacks: map[logical.Operation]framework.OperationFunc{
- logical.ReadOperation: func(context.Context, *logical.Request, *framework.FieldData) (*logical.Response, error) {
- return &logical.Response{
- Auth: &logical.Auth{},
- Headers: map[string][]string{
- "www-authenticate": {"Negotiate"},
- },
- }, nil
- },
- },
- },
- },
- BackendType: logical.TypeCredential,
- }
-
- // Use a TestCluster and the approle backend to get a token and entity for testing
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "headtest": func(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
- err := be.Setup(ctx, conf)
- if err != nil {
- return nil, err
- }
- return be, nil
- },
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // Mount the auth backend
- err := client.Sys().EnableAuthWithOptions("headtest", &api.EnableAuthOptions{
- Type: "headtest",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Here, should succeed but we should not see the header since it's
- // not in the allowed list
- req := client.NewRequest("GET", "/v1/auth/headtest/loginnoerror")
- resp, err := client.RawRequest(req)
- if err != nil {
- t.Fatal(err)
- }
- if resp.StatusCode != 200 {
- t.Fatalf("expected code 200, got %d", resp.StatusCode)
- }
- if resp.Header.Get("www-authenticate") != "" {
- t.Fatal("expected header to not be allowed")
- }
-
- // Should fail but we should not see the header since it's
- // not in the allowed list
- req = client.NewRequest("GET", "/v1/auth/headtest/login")
- resp, err = client.RawRequest(req)
- if err == nil {
- t.Fatal("expected error")
- }
- if resp.StatusCode != 401 {
- t.Fatalf("expected code 401, got %d", resp.StatusCode)
- }
- if resp.Header.Get("www-authenticate") != "" {
- t.Fatal("expected header to not be allowed")
- }
-
- // Tune the mount
- err = client.Sys().TuneMount("auth/headtest", api.MountConfigInput{
- AllowedResponseHeaders: []string{"WwW-AuthenTicate"},
- })
- if err != nil {
- t.Fatal(err)
- }
-
- req = client.NewRequest("GET", "/v1/auth/headtest/loginnoerror")
- resp, err = client.RawRequest(req)
- if err != nil {
- t.Fatal(err)
- }
- if resp.StatusCode != 200 {
- t.Fatalf("expected code 200, got %d", resp.StatusCode)
- }
- if resp.Header.Get("www-authenticate") != "Negotiate" {
- t.Fatalf("expected negotiate header; headers:\n%s", pretty.Sprint(resp.Header))
- }
-
- req = client.NewRequest("GET", "/v1/auth/headtest/login")
- resp, err = client.RawRequest(req)
- if err == nil {
- t.Fatal("expected error")
- }
- if resp.StatusCode != 401 {
- t.Fatalf("expected code 401, got %d", resp.StatusCode)
- }
- if resp.Header.Get("www-authenticate") != "Negotiate" {
- t.Fatalf("expected negotiate header; headers:\n%s", pretty.Sprint(resp.Header))
- }
-}
diff --git a/vault/external_tests/router/router_ext_test.go b/vault/external_tests/router/router_ext_test.go
deleted file mode 100644
index 3cb6dbcca..000000000
--- a/vault/external_tests/router/router_ext_test.go
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package router
-
-import (
- "testing"
-
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/builtin/credential/userpass"
- "github.com/hashicorp/vault/builtin/logical/pki"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-func TestRouter_MountSubpath_Checks(t *testing.T) {
- testRouter_MountSubpath(t, []string{"a/abcd/123", "abcd/123"})
- testRouter_MountSubpath(t, []string{"abcd/123", "a/abcd/123"})
- testRouter_MountSubpath(t, []string{"a/abcd/123", "abcd/123"})
-}
-
-func testRouter_MountSubpath(t *testing.T, mountPoints []string) {
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "pki": pki.Factory,
- },
- CredentialBackends: map[string]logical.Factory{
- "userpass": userpass.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- vault.TestWaitActive(t, cluster.Cores[0].Core)
- client := cluster.Cores[0].Client
-
- // Test auth
- authInput := &api.EnableAuthOptions{
- Type: "userpass",
- }
- for _, mp := range mountPoints {
- t.Logf("mounting %s", "auth/"+mp)
- var err error
- err = client.Sys().EnableAuthWithOptions("auth/"+mp, authInput)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- }
-
- // Test secrets
- mountInput := &api.MountInput{
- Type: "pki",
- }
- for _, mp := range mountPoints {
- t.Logf("mounting %s", "s/"+mp)
- var err error
- err = client.Sys().Mount("s/"+mp, mountInput)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- }
-
- cluster.EnsureCoresSealed(t)
- cluster.UnsealCores(t)
- t.Logf("Done: %#v", mountPoints)
-}
-
-func TestRouter_UnmountRollbackIsntFatal(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "noop": vault.NoopBackendRollbackErrFactory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- vault.TestWaitActive(t, cluster.Cores[0].Core)
- client := cluster.Cores[0].Client
-
- if err := client.Sys().Mount("noop", &api.MountInput{
- Type: "noop",
- }); err != nil {
- t.Fatalf("failed to mount PKI: %v", err)
- }
-
- if _, err := client.Logical().Write("sys/plugins/reload/backend", map[string]interface{}{
- "mounts": "noop",
- }); err != nil {
- t.Fatalf("expected reload of noop with broken periodic func to succeed; got err=%v", err)
- }
-
- if _, err := client.Logical().Write("sys/remount", map[string]interface{}{
- "from": "noop",
- "to": "noop-to",
- }); err != nil {
- t.Fatalf("expected remount of noop with broken periodic func to succeed; got err=%v", err)
- }
-
- cluster.EnsureCoresSealed(t)
- cluster.UnsealCores(t)
-}
diff --git a/vault/external_tests/sealmigration/seal_migration_pre14_test.go b/vault/external_tests/sealmigration/seal_migration_pre14_test.go
deleted file mode 100644
index 2b5d136e5..000000000
--- a/vault/external_tests/sealmigration/seal_migration_pre14_test.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package sealmigration
-
-import (
- "testing"
-)
-
-// TestSealMigration_TransitToShamir_Pre14 tests transit-to-shamir seal
-// migration, using the pre-1.4 method of bring down the whole cluster to do
-// the migration.
-func TestSealMigration_TransitToShamir_Pre14(t *testing.T) {
- t.Parallel()
- // Note that we do not test integrated raft storage since this is
- // a pre-1.4 test.
- testVariousBackends(t, ParamTestSealMigrationTransitToShamir_Pre14, BasePort_TransitToShamir_Pre14, false)
-}
-
-// TestSealMigration_ShamirToTransit_Pre14 tests shamir-to-transit seal
-// migration, using the pre-1.4 method of bring down the whole cluster to do
-// the migration.
-func TestSealMigration_ShamirToTransit_Pre14(t *testing.T) {
- t.Parallel()
- // Note that we do not test integrated raft storage since this is
- // a pre-1.4 test.
- testVariousBackends(t, ParamTestSealMigrationShamirToTransit_Pre14, BasePort_ShamirToTransit_Pre14, false)
-}
diff --git a/vault/external_tests/sealmigration/seal_migration_test.go b/vault/external_tests/sealmigration/seal_migration_test.go
deleted file mode 100644
index 30ee36ce2..000000000
--- a/vault/external_tests/sealmigration/seal_migration_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package sealmigration
-
-import (
- "sync/atomic"
- "testing"
-
- "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/helper/testhelpers"
- "github.com/hashicorp/vault/helper/testhelpers/teststorage"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/vault"
-)
-
-type testFunc func(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage, basePort int)
-
-func testVariousBackends(t *testing.T, tf testFunc, basePort int, includeRaft bool) {
- logger := logging.NewVaultLogger(hclog.Trace).Named(t.Name())
-
- t.Run("inmem", func(t *testing.T) {
- t.Parallel()
-
- logger := logger.Named("inmem")
- storage, cleanup := teststorage.MakeReusableStorage(
- t, logger, teststorage.MakeInmemBackend(t, logger))
- defer cleanup()
- tf(t, logger, storage, basePort+100)
- })
-
- if includeRaft {
- t.Run("raft", func(t *testing.T) {
- t.Parallel()
-
- logger := logger.Named("raft")
- raftBasePort := basePort + 400
-
- atomic.StoreUint32(&vault.TestingUpdateClusterAddr, 1)
- addressProvider := testhelpers.NewHardcodedServerAddressProvider(numTestCores, raftBasePort+10)
-
- storage, cleanup := teststorage.MakeReusableRaftStorage(t, logger, numTestCores, addressProvider)
- defer cleanup()
- tf(t, logger, storage, raftBasePort)
- })
- }
-}
-
-// TestSealMigration_ShamirToTransit_Post14 tests shamir-to-transit seal
-// migration, using the post-1.4 method of bring individual nodes in the cluster
-// to do the migration.
-func TestSealMigration_ShamirToTransit_Post14(t *testing.T) {
- t.Parallel()
- testVariousBackends(t, ParamTestSealMigrationShamirToTransit_Post14, BasePort_ShamirToTransit_Post14, true)
-}
-
-// TestSealMigration_TransitToShamir_Post14 tests transit-to-shamir seal
-// migration, using the post-1.4 method of bring individual nodes in the
-// cluster to do the migration.
-func TestSealMigration_TransitToShamir_Post14(t *testing.T) {
- t.Parallel()
- testVariousBackends(t, ParamTestSealMigrationTransitToShamir_Post14, BasePort_TransitToShamir_Post14, true)
-}
-
-// TestSealMigration_TransitToTransit tests transit-to-shamir seal
-// migration, using the post-1.4 method of bring individual nodes in the
-// cluster to do the migration.
-func TestSealMigration_TransitToTransit(t *testing.T) {
- testVariousBackends(t, ParamTestSealMigration_TransitToTransit, BasePort_TransitToTransit, true)
-}
diff --git a/vault/external_tests/sealmigration/testshared.go b/vault/external_tests/sealmigration/testshared.go
deleted file mode 100644
index a50acced1..000000000
--- a/vault/external_tests/sealmigration/testshared.go
+++ /dev/null
@@ -1,887 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package sealmigration
-
-import (
- "context"
- "encoding/base64"
- "fmt"
- "testing"
- "time"
-
- "github.com/go-test/deep"
- "github.com/hashicorp/go-hclog"
- wrapping "github.com/hashicorp/go-kms-wrapping/v2"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/helper/testhelpers"
- sealhelper "github.com/hashicorp/vault/helper/testhelpers/seal"
- "github.com/hashicorp/vault/helper/testhelpers/teststorage"
- "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/physical/raft"
- "github.com/hashicorp/vault/vault"
-)
-
-const (
- numTestCores = 3
- keyShares = 3
- keyThreshold = 3
-
- BasePort_ShamirToTransit_Pre14 = 20000
- BasePort_TransitToShamir_Pre14 = 21000
- BasePort_ShamirToTransit_Post14 = 22000
- BasePort_TransitToShamir_Post14 = 23000
- BasePort_TransitToTransit = 24000
-)
-
-func ParamTestSealMigrationTransitToShamir_Pre14(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage, basePort int) {
- // Create the transit server.
- tss := sealhelper.NewTransitSealServer(t, 0)
- defer func() {
- if tss != nil {
- tss.Cleanup()
- }
- }()
- sealKeyName := "transit-seal-key"
- tss.MakeKey(t, sealKeyName)
-
- // Initialize the backend with transit.
- cluster, opts := InitializeTransit(t, logger, storage, basePort, tss, sealKeyName)
- rootToken, recoveryKeys := cluster.RootToken, cluster.RecoveryKeys
- cluster.EnsureCoresSealed(t)
- cluster.Cleanup()
- storage.Cleanup(t, cluster)
-
- // Migrate the backend from transit to shamir
- migrateFromTransitToShamir_Pre14(t, logger, storage, basePort, tss, opts.SealFunc, rootToken, recoveryKeys)
-
- // Now that migration is done, we can nuke the transit server, since we
- // can unseal without it.
- tss.Cleanup()
- tss = nil
-
- // Run the backend with shamir. Note that the recovery keys are now the
- // barrier keys.
- runShamir(t, logger, storage, basePort, rootToken, recoveryKeys)
-}
-
-func ParamTestSealMigrationShamirToTransit_Pre14(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage, basePort int) {
- // Initialize the backend using shamir
- cluster, _ := initializeShamir(t, logger, storage, basePort)
- rootToken, barrierKeys := cluster.RootToken, cluster.BarrierKeys
- cluster.Cleanup()
- storage.Cleanup(t, cluster)
-
- // Create the transit server.
- tss := sealhelper.NewTransitSealServer(t, 0)
- defer func() {
- tss.EnsureCoresSealed(t)
- tss.Cleanup()
- }()
- tss.MakeKey(t, "transit-seal-key-1")
-
- // Migrate the backend from shamir to transit. Note that the barrier keys
- // are now the recovery keys.
- sealFunc := migrateFromShamirToTransit_Pre14(t, logger, storage, basePort, tss, rootToken, barrierKeys)
-
- // Run the backend with transit.
- runAutoseal(t, logger, storage, basePort, rootToken, sealFunc)
-}
-
-func ParamTestSealMigrationTransitToShamir_Post14(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage, basePort int) {
- // Create the transit server.
- tss := sealhelper.NewTransitSealServer(t, 0)
- defer func() {
- if tss != nil {
- tss.Cleanup()
- }
- }()
- sealKeyName := "transit-seal-key-1"
- tss.MakeKey(t, sealKeyName)
-
- // Initialize the backend with transit.
- cluster, opts := InitializeTransit(t, logger, storage, basePort, tss, sealKeyName)
- rootToken, recoveryKeys := cluster.RootToken, cluster.RecoveryKeys
-
- // Migrate the backend from transit to shamir
- opts.UnwrapSealFunc = opts.SealFunc
- opts.SealFunc = func() vault.Seal { return nil }
- leaderIdx := migratePost14(t, storage, cluster, opts, cluster.RecoveryKeys)
- validateMigration(t, storage, cluster, leaderIdx, verifySealConfigShamir)
-
- cluster.Cleanup()
- storage.Cleanup(t, cluster)
-
- // Now that migration is done, we can nuke the transit server, since we
- // can unseal without it.
- tss.Cleanup()
- tss = nil
-
- // Run the backend with shamir. Note that the recovery keys are now the
- // barrier keys.
- runShamir(t, logger, storage, basePort, rootToken, recoveryKeys)
-}
-
-func ParamTestSealMigrationShamirToTransit_Post14(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage, basePort int) {
- // Initialize the backend using shamir
- cluster, opts := initializeShamir(t, logger, storage, basePort)
-
- // Create the transit server.
- tss := sealhelper.NewTransitSealServer(t, 0)
- defer tss.Cleanup()
- sealKeyName := "transit-seal-key-1"
- tss.MakeKey(t, sealKeyName)
-
- // Migrate the backend from shamir to transit.
- opts.SealFunc = func() vault.Seal {
- seal, err := tss.MakeSeal(t, sealKeyName)
- if err != nil {
- t.Fatal(err)
- }
- return seal
- }
-
- // Restart each follower with the new config, and migrate to Transit.
- // Note that the barrier keys are being used as recovery keys.
- leaderIdx := migratePost14(t, storage, cluster, opts, cluster.BarrierKeys)
- validateMigration(t, storage, cluster, leaderIdx, verifySealConfigTransit)
- cluster.Cleanup()
- storage.Cleanup(t, cluster)
-
- // Run the backend with transit.
- runAutoseal(t, logger, storage, basePort, cluster.RootToken, opts.SealFunc)
-}
-
-func ParamTestSealMigration_TransitToTransit(t *testing.T, logger hclog.Logger,
- storage teststorage.ReusableStorage, basePort int,
-) {
- // Create the transit server.
- tss1 := sealhelper.NewTransitSealServer(t, 0)
- defer func() {
- if tss1 != nil {
- tss1.Cleanup()
- }
- }()
- sealKeyName := "transit-seal-key-1"
- tss1.MakeKey(t, sealKeyName)
-
- // Initialize the backend with transit.
- cluster, opts := InitializeTransit(t, logger, storage, basePort, tss1, sealKeyName)
- rootToken := cluster.RootToken
-
- // Create the transit server.
- tss2 := sealhelper.NewTransitSealServer(t, 1)
- defer func() {
- tss2.Cleanup()
- }()
- tss2.MakeKey(t, "transit-seal-key-2")
-
- // Migrate the backend from transit to transit.
- opts.UnwrapSealFunc = opts.SealFunc
- opts.SealFunc = func() vault.Seal {
- seal, err := tss2.MakeSeal(t, "transit-seal-key-2")
- if err != nil {
- t.Fatal(err)
- }
- return seal
- }
- leaderIdx := migratePost14(t, storage, cluster, opts, cluster.RecoveryKeys)
- validateMigration(t, storage, cluster, leaderIdx, verifySealConfigTransit)
- cluster.Cleanup()
- storage.Cleanup(t, cluster)
-
- // Now that migration is done, we can nuke the transit server, since we
- // can unseal without it.
- tss1.Cleanup()
- tss1 = nil
-
- // Run the backend with transit.
- runAutoseal(t, logger, storage, basePort, rootToken, opts.SealFunc)
-}
-
-func migrateFromTransitToShamir_Pre14(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage, basePort int,
- tss *sealhelper.TransitSealServer, sealFunc func() vault.Seal, rootToken string, recoveryKeys [][]byte,
-) {
- baseClusterPort := basePort + 10
-
- var conf vault.CoreConfig
- opts := vault.TestClusterOptions{
- Logger: logger.Named("migrateFromTransitToShamir"),
- HandlerFunc: http.Handler,
- NumCores: numTestCores,
- BaseListenAddress: fmt.Sprintf("127.0.0.1:%d", basePort),
- BaseClusterListenPort: baseClusterPort,
- SkipInit: true,
- UnwrapSealFunc: sealFunc,
- }
- storage.Setup(&conf, &opts)
- conf.DisableAutopilot = true
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.Start()
- defer func() {
- cluster.Cleanup()
- storage.Cleanup(t, cluster)
- }()
-
- leader := cluster.Cores[0]
- client := leader.Client
- client.SetToken(rootToken)
-
- // Attempt to unseal while the transit server is unreachable. Although
- // we're unsealing using the recovery keys, this is still an
- // autounseal, so it should fail.
- tss.EnsureCoresSealed(t)
- unsealMigrate(t, client, recoveryKeys, false)
- tss.UnsealCores(t)
- testhelpers.WaitForActiveNode(t, tss.TestCluster)
-
- // Unseal and migrate to Shamir. Although we're unsealing using the
- // recovery keys, this is still an autounseal.
- unsealMigrate(t, client, recoveryKeys, true)
- testhelpers.WaitForActiveNode(t, cluster)
-
- // Wait for migration to finish. Sadly there is no callback, and the
- // test will fail later on if we don't do this.
- time.Sleep(10 * time.Second)
-
- // Read the secret
- secret, err := client.Logical().Read("kv-wrapped/foo")
- if err != nil {
- t.Fatal(err)
- }
- if diff := deep.Equal(secret.Data, map[string]interface{}{"zork": "quux"}); len(diff) > 0 {
- t.Fatal(diff)
- }
-
- // Write a new secret
- _, err = leader.Client.Logical().Write("kv-wrapped/test", map[string]interface{}{
- "zork": "quux",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Make sure the seal configs were updated correctly.
- b, r, err := cluster.Cores[0].Core.PhysicalSealConfigs(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- verifyBarrierConfig(t, b, wrapping.WrapperTypeShamir.String(), keyShares, keyThreshold, 1)
- if r != nil {
- t.Fatalf("expected nil recovery config, got: %#v", r)
- }
-
- cluster.EnsureCoresSealed(t)
-}
-
-func migrateFromShamirToTransit_Pre14(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage, basePort int, tss *sealhelper.TransitSealServer, rootToken string, recoveryKeys [][]byte) func() vault.Seal {
- baseClusterPort := basePort + 10
-
- conf := vault.CoreConfig{
- DisableAutopilot: true,
- }
- opts := vault.TestClusterOptions{
- Logger: logger.Named("migrateFromShamirToTransit"),
- HandlerFunc: http.Handler,
- NumCores: numTestCores,
- BaseListenAddress: fmt.Sprintf("127.0.0.1:%d", basePort),
- BaseClusterListenPort: baseClusterPort,
- SkipInit: true,
- // N.B. Providing a transit seal puts us in migration mode.
- SealFunc: func() vault.Seal {
- seal, err := tss.MakeSeal(t, "transit-seal-key")
- if err != nil {
- t.Fatal(err)
- }
- return seal
- },
- }
- storage.Setup(&conf, &opts)
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.Start()
- defer func() {
- cluster.Cleanup()
- storage.Cleanup(t, cluster)
- }()
-
- leader := cluster.Cores[0]
- leader.Client.SetToken(rootToken)
-
- // Unseal and migrate to Transit.
- unsealMigrate(t, leader.Client, recoveryKeys, true)
-
- // Wait for migration to finish.
- awaitMigration(t, leader.Client)
-
- verifySealConfigTransit(t, leader)
-
- // Read the secrets
- secret, err := leader.Client.Logical().Read("kv-wrapped/foo")
- if err != nil {
- t.Fatal(err)
- }
- if diff := deep.Equal(secret.Data, map[string]interface{}{"zork": "quux"}); len(diff) > 0 {
- t.Fatal(diff)
- }
-
- // Write a new secret
- _, err = leader.Client.Logical().Write("kv-wrapped/test", map[string]interface{}{
- "zork": "quux",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- return opts.SealFunc
-}
-
-func validateMigration(t *testing.T, storage teststorage.ReusableStorage,
- cluster *vault.TestCluster, leaderIdx int, f func(t *testing.T, core *vault.TestClusterCore),
-) {
- t.Helper()
-
- leader := cluster.Cores[leaderIdx]
-
- secret, err := leader.Client.Logical().Read("kv-wrapped/foo")
- if err != nil {
- t.Fatal(err)
- }
- if diff := deep.Equal(secret.Data, map[string]interface{}{"zork": "quux"}); len(diff) > 0 {
- t.Fatal(diff)
- }
-
- var appliedIndex uint64
- if storage.IsRaft {
- appliedIndex = testhelpers.RaftAppliedIndex(leader)
- }
-
- for _, core := range cluster.Cores {
- if storage.IsRaft {
- testhelpers.WaitForRaftApply(t, core, appliedIndex)
- }
-
- f(t, core)
- }
-}
-
-func migratePost14(t *testing.T, storage teststorage.ReusableStorage, cluster *vault.TestCluster,
- opts *vault.TestClusterOptions, unsealKeys [][]byte,
-) int {
- cluster.Logger = cluster.Logger.Named("migration")
- // Restart each follower with the new config, and migrate.
- for i := 1; i < len(cluster.Cores); i++ {
- cluster.StopCore(t, i)
- if storage.IsRaft {
- teststorage.CloseRaftStorage(t, cluster, i)
- }
- cluster.StartCore(t, i, opts)
-
- unsealMigrate(t, cluster.Cores[i].Client, unsealKeys, true)
- }
- testhelpers.WaitForActiveNodeAndStandbys(t, cluster)
-
- // Step down the active node which will kick off the migration on one of the
- // other nodes.
- err := cluster.Cores[0].Client.Sys().StepDown()
- if err != nil {
- t.Fatal(err)
- }
-
- // Wait for the followers to establish a new leader
- var leaderIdx int
- for i := 0; i < 30; i++ {
- leaderIdx, err = testhelpers.AwaitLeader(t, cluster)
- if err != nil {
- t.Fatal(err)
- }
- if leaderIdx != 0 {
- break
- }
- time.Sleep(1 * time.Second)
- }
- if leaderIdx == 0 {
- t.Fatalf("Core 0 cannot be the leader right now")
- }
- leader := cluster.Cores[leaderIdx]
-
- // Wait for migration to occur on the leader
- awaitMigration(t, leader.Client)
-
- var appliedIndex uint64
- if storage.IsRaft {
- appliedIndex = testhelpers.RaftAppliedIndex(leader)
- testhelpers.WaitForRaftApply(t, cluster.Cores[0], appliedIndex)
- }
-
- // Bring down the leader
- cluster.StopCore(t, 0)
- if storage.IsRaft {
- teststorage.CloseRaftStorage(t, cluster, 0)
- }
-
- // Bring core 0 back up; we still have the seal migration config in place,
- // but now that migration has been performed we should be able to unseal
- // with the new seal and without using the `migrate` unseal option.
- cluster.StartCore(t, 0, opts)
- unseal(t, cluster.Cores[0].Client, unsealKeys)
-
- // Write a new secret
- _, err = leader.Client.Logical().Write("kv-wrapped/test", map[string]interface{}{
- "zork": "quux",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- return leaderIdx
-}
-
-func unsealMigrate(t *testing.T, client *api.Client, keys [][]byte, transitServerAvailable bool) {
- t.Helper()
- if err := attemptUnseal(client, keys); err == nil {
- t.Fatal("expected error due to lack of migrate parameter")
- }
- if err := attemptUnsealMigrate(client, keys, transitServerAvailable); err != nil {
- t.Fatal(err)
- }
-}
-
-func attemptUnsealMigrate(client *api.Client, keys [][]byte, transitServerAvailable bool) error {
- for i, key := range keys {
- resp, err := client.Sys().UnsealWithOptions(&api.UnsealOpts{
- Key: base64.StdEncoding.EncodeToString(key),
- Migrate: true,
- })
-
- if i < keyThreshold-1 {
- // Not enough keys have been provided yet.
- if err != nil {
- return err
- }
- } else {
- if transitServerAvailable {
- // The transit server is running.
- if err != nil {
- return err
- }
- if resp == nil || resp.Sealed {
- return fmt.Errorf("expected unsealed state; got %#v", resp)
- }
- } else {
- // The transit server is stopped.
- if err == nil {
- return fmt.Errorf("expected error due to transit server being stopped.")
- }
- }
- break
- }
- }
- return nil
-}
-
-// awaitMigration waits for migration to finish.
-func awaitMigration(t *testing.T, client *api.Client) {
- timeout := time.Now().Add(60 * time.Second)
- for {
- if time.Now().After(timeout) {
- break
- }
-
- resp, err := client.Sys().SealStatus()
- if err != nil {
- t.Fatal(err)
- }
- if !resp.Migration {
- return
- }
-
- time.Sleep(time.Second)
- }
-
- t.Fatalf("migration did not complete.")
-}
-
-func unseal(t *testing.T, client *api.Client, keys [][]byte) {
- t.Helper()
- if err := attemptUnseal(client, keys); err != nil {
- t.Fatal(err)
- }
-}
-
-func attemptUnseal(client *api.Client, keys [][]byte) error {
- for i, key := range keys {
-
- resp, err := client.Sys().UnsealWithOptions(&api.UnsealOpts{
- Key: base64.StdEncoding.EncodeToString(key),
- })
- if i < keyThreshold-1 {
- // Not enough keys have been provided yet.
- if err != nil {
- return err
- }
- } else {
- if err != nil {
- return err
- }
- if resp == nil || resp.Sealed {
- return fmt.Errorf("expected unsealed state; got %#v", resp)
- }
- break
- }
- }
- return nil
-}
-
-func verifySealConfigShamir(t *testing.T, core *vault.TestClusterCore) {
- t.Helper()
- b, r, err := core.PhysicalSealConfigs(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- verifyBarrierConfig(t, b, wrapping.WrapperTypeShamir.String(), keyShares, keyThreshold, 1)
- if r != nil {
- t.Fatal("should not have recovery config for shamir")
- }
-}
-
-func verifySealConfigTransit(t *testing.T, core *vault.TestClusterCore) {
- t.Helper()
- b, r, err := core.PhysicalSealConfigs(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- verifyBarrierConfig(t, b, wrapping.WrapperTypeTransit.String(), 1, 1, 1)
- verifyBarrierConfig(t, r, wrapping.WrapperTypeShamir.String(), keyShares, keyThreshold, 0)
-}
-
-// verifyBarrierConfig verifies that a barrier configuration is correct.
-func verifyBarrierConfig(t *testing.T, cfg *vault.SealConfig, sealType string, shares, threshold, stored int) {
- t.Helper()
- if cfg.Type != sealType {
- t.Fatalf("bad seal config: %#v, expected type=%q", cfg, sealType)
- }
- if cfg.SecretShares != shares {
- t.Fatalf("bad seal config: %#v, expected SecretShares=%d", cfg, shares)
- }
- if cfg.SecretThreshold != threshold {
- t.Fatalf("bad seal config: %#v, expected SecretThreshold=%d", cfg, threshold)
- }
- if cfg.StoredShares != stored {
- t.Fatalf("bad seal config: %#v, expected StoredShares=%d", cfg, stored)
- }
-}
-
-// initializeShamir initializes a brand new backend storage with Shamir.
-func initializeShamir(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage, basePort int) (*vault.TestCluster, *vault.TestClusterOptions) {
- t.Helper()
-
- baseClusterPort := basePort + 10
-
- // Start the cluster
- conf := vault.CoreConfig{
- DisableAutopilot: true,
- }
- opts := vault.TestClusterOptions{
- Logger: logger.Named("initializeShamir"),
- HandlerFunc: http.Handler,
- NumCores: numTestCores,
- BaseListenAddress: fmt.Sprintf("127.0.0.1:%d", basePort),
- BaseClusterListenPort: baseClusterPort,
- }
- storage.Setup(&conf, &opts)
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.Start()
-
- leader := cluster.Cores[0]
- client := leader.Client
-
- // Unseal
- if storage.IsRaft {
- joinRaftFollowers(t, cluster, false)
- if err := testhelpers.VerifyRaftConfiguration(leader, len(cluster.Cores)); err != nil {
- t.Fatal(err)
- }
- } else {
- cluster.UnsealCores(t)
- }
- testhelpers.WaitForActiveNodeAndStandbys(t, cluster)
-
- err := client.Sys().Mount("kv-wrapped", &api.MountInput{
- SealWrap: true,
- Type: "kv",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Write a secret that we will read back out later.
- _, err = client.Logical().Write("kv-wrapped/foo", map[string]interface{}{
- "zork": "quux",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- return cluster, &opts
-}
-
-// runShamir uses a pre-populated backend storage with Shamir.
-func runShamir(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage, basePort int, rootToken string, barrierKeys [][]byte) {
- t.Helper()
- baseClusterPort := basePort + 10
-
- // Start the cluster
- conf := vault.CoreConfig{
- DisableAutopilot: true,
- }
- opts := vault.TestClusterOptions{
- Logger: logger.Named("runShamir"),
- HandlerFunc: http.Handler,
- NumCores: numTestCores,
- BaseListenAddress: fmt.Sprintf("127.0.0.1:%d", basePort),
- BaseClusterListenPort: baseClusterPort,
- SkipInit: true,
- }
- storage.Setup(&conf, &opts)
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.Start()
- defer func() {
- cluster.Cleanup()
- storage.Cleanup(t, cluster)
- }()
-
- leader := cluster.Cores[0]
-
- // Unseal
- cluster.BarrierKeys = barrierKeys
- if storage.IsRaft {
- for _, core := range cluster.Cores {
- cluster.UnsealCore(t, core)
- }
- // This is apparently necessary for the raft cluster to get itself
- // situated.
- time.Sleep(15 * time.Second)
- if err := testhelpers.VerifyRaftConfiguration(leader, len(cluster.Cores)); err != nil {
- t.Fatal(err)
- }
- } else {
- cluster.UnsealCores(t)
- }
- testhelpers.WaitForNCoresUnsealed(t, cluster, len(cluster.Cores))
-
- // Ensure that we always use the leader's client for this read check
- leaderIdx, err := testhelpers.AwaitLeader(t, cluster)
- if err != nil {
- t.Fatal(err)
- }
- client := cluster.Cores[leaderIdx].Client
- client.SetToken(rootToken)
-
- // Read the secrets
- secret, err := client.Logical().Read("kv-wrapped/foo")
- if err != nil {
- t.Fatal(err)
- }
- if diff := deep.Equal(secret.Data, map[string]interface{}{"zork": "quux"}); len(diff) > 0 {
- t.Fatal(diff)
- }
- secret, err = client.Logical().Read("kv-wrapped/test")
- if err != nil {
- t.Fatal(err)
- }
- if diff := deep.Equal(secret.Data, map[string]interface{}{"zork": "quux"}); len(diff) > 0 {
- t.Fatal(diff)
- }
-}
-
-// initializeTransit initializes a brand new backend storage with Transit.
-func InitializeTransit(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage, basePort int,
- tss *sealhelper.TransitSealServer, sealKeyName string,
-) (*vault.TestCluster, *vault.TestClusterOptions) {
- t.Helper()
-
- baseClusterPort := basePort + 10
-
- // Start the cluster
- conf := vault.CoreConfig{
- DisableAutopilot: true,
- }
- opts := vault.TestClusterOptions{
- Logger: logger.Named("initializeTransit"),
- HandlerFunc: http.Handler,
- NumCores: numTestCores,
- BaseListenAddress: fmt.Sprintf("127.0.0.1:%d", basePort),
- BaseClusterListenPort: baseClusterPort,
- SealFunc: func() vault.Seal {
- seal, err := tss.MakeSeal(t, sealKeyName)
- if err != nil {
- t.Fatal(err)
- }
- return seal
- },
- }
- storage.Setup(&conf, &opts)
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.Start()
-
- leader := cluster.Cores[0]
- client := leader.Client
-
- // Join raft
- if storage.IsRaft {
- joinRaftFollowers(t, cluster, true)
-
- if err := testhelpers.VerifyRaftConfiguration(leader, len(cluster.Cores)); err != nil {
- t.Fatal(err)
- }
- }
- testhelpers.WaitForActiveNodeAndStandbys(t, cluster)
-
- err := client.Sys().Mount("kv-wrapped", &api.MountInput{
- SealWrap: true,
- Type: "kv",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Write a secret that we will read back out later.
- _, err = client.Logical().Write("kv-wrapped/foo", map[string]interface{}{
- "zork": "quux",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- return cluster, &opts
-}
-
-func runAutoseal(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage,
- basePort int, rootToken string, sealFunc func() vault.Seal,
-) {
- baseClusterPort := basePort + 10
-
- // Start the cluster
- conf := vault.CoreConfig{
- DisableAutopilot: true,
- }
- opts := vault.TestClusterOptions{
- Logger: logger.Named("runTransit"),
- HandlerFunc: http.Handler,
- NumCores: numTestCores,
- BaseListenAddress: fmt.Sprintf("127.0.0.1:%d", basePort),
- BaseClusterListenPort: baseClusterPort,
- SkipInit: true,
- SealFunc: sealFunc,
- }
- storage.Setup(&conf, &opts)
- cluster := vault.NewTestCluster(t, &conf, &opts)
- cluster.Start()
- defer func() {
- cluster.Cleanup()
- storage.Cleanup(t, cluster)
- }()
-
- for _, c := range cluster.Cores {
- c.Client.SetToken(rootToken)
- }
-
- // Unseal. Even though we are using autounseal, we have to unseal
- // explicitly because we are using SkipInit.
- if storage.IsRaft {
- for _, core := range cluster.Cores {
- cluster.UnsealCoreWithStoredKeys(t, core)
- }
- // This is apparently necessary for the raft cluster to get itself
- // situated.
- time.Sleep(15 * time.Second)
- // We're taking the first core, but we're not assuming it's the leader here.
- if err := testhelpers.VerifyRaftConfiguration(cluster.Cores[0], len(cluster.Cores)); err != nil {
- t.Fatal(err)
- }
- } else {
- if err := cluster.UnsealCoresWithError(true); err != nil {
- t.Fatal(err)
- }
- }
- testhelpers.WaitForNCoresUnsealed(t, cluster, len(cluster.Cores))
-
- // Preceding code may have stepped down the leader, so we're not sure who it is
- // at this point.
- leaderCore := testhelpers.DeriveActiveCore(t, cluster)
- client := leaderCore.Client
-
- // Read the secrets
- secret, err := client.Logical().Read("kv-wrapped/foo")
- if err != nil {
- t.Fatal(err)
- }
- if diff := deep.Equal(secret.Data, map[string]interface{}{"zork": "quux"}); len(diff) > 0 {
- t.Fatal(diff)
- }
- secret, err = client.Logical().Read("kv-wrapped/test")
- if err != nil {
- t.Fatal(err)
- }
- if secret == nil {
- t.Fatal("secret is nil")
- }
- if diff := deep.Equal(secret.Data, map[string]interface{}{"zork": "quux"}); len(diff) > 0 {
- t.Fatal(diff)
- }
-}
-
-// joinRaftFollowers unseals the leader, and then joins-and-unseals the
-// followers one at a time. We assume that the ServerAddressProvider has
-// already been installed on all the nodes.
-func joinRaftFollowers(t *testing.T, cluster *vault.TestCluster, useStoredKeys bool) {
- leader := cluster.Cores[0]
-
- cluster.UnsealCore(t, leader)
- vault.TestWaitActive(t, leader.Core)
-
- leaderInfos := []*raft.LeaderJoinInfo{
- {
- LeaderAPIAddr: leader.Client.Address(),
- TLSConfig: leader.TLSConfig(),
- },
- }
-
- // Join followers
- for i := 1; i < len(cluster.Cores); i++ {
- core := cluster.Cores[i]
- _, err := core.JoinRaftCluster(namespace.RootContext(context.Background()), leaderInfos, false)
- if err != nil {
- t.Fatal(err)
- }
-
- if useStoredKeys {
- // For autounseal, the raft backend is not initialized right away
- // after the join. We need to wait briefly before we can unseal.
- awaitUnsealWithStoredKeys(t, core)
- } else {
- cluster.UnsealCore(t, core)
- }
- }
-
- testhelpers.WaitForNCoresUnsealed(t, cluster, len(cluster.Cores))
-}
-
-func awaitUnsealWithStoredKeys(t *testing.T, core *vault.TestClusterCore) {
- timeout := time.Now().Add(30 * time.Second)
- for {
- if time.Now().After(timeout) {
- t.Fatal("raft join: timeout waiting for core to unseal")
- }
- // Its actually ok for an error to happen here the first couple of
- // times -- it means the raft join hasn't gotten around to initializing
- // the backend yet.
- err := core.UnsealWithStoredKeys(context.Background())
- if err == nil {
- return
- }
- core.Logger().Warn("raft join: failed to unseal core", "error", err)
- time.Sleep(time.Second)
- }
-}
diff --git a/vault/external_tests/sealmigrationext/seal_migration_pre14_test.go b/vault/external_tests/sealmigrationext/seal_migration_pre14_test.go
deleted file mode 100644
index ae45f74f8..000000000
--- a/vault/external_tests/sealmigrationext/seal_migration_pre14_test.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package sealmigrationext
-
-import (
- "testing"
-
- "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/helper/testhelpers/teststorage"
- "github.com/hashicorp/vault/helper/testhelpers/teststorage/consul"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/vault/external_tests/sealmigration"
-)
-
-func TestSealMigration_ShamirToTransit_Pre14(t *testing.T) {
- t.Parallel()
- logger := logging.NewVaultLogger(hclog.Debug).Named(t.Name())
- storage, cleanup := teststorage.MakeReusableStorage(t, logger,
- consul.MakeConsulBackend(t, logger))
- defer cleanup()
- sealmigration.ParamTestSealMigrationShamirToTransit_Pre14(t, logger, storage,
- sealmigration.BasePort_ShamirToTransit_Pre14+300)
-}
-
-func TestSealMigration_TransitToShamir_Pre14(t *testing.T) {
- t.Parallel()
- logger := logging.NewVaultLogger(hclog.Debug).Named(t.Name())
- storage, cleanup := teststorage.MakeReusableStorage(t, logger,
- consul.MakeConsulBackend(t, logger))
- defer cleanup()
- sealmigration.ParamTestSealMigrationTransitToShamir_Pre14(t, logger, storage,
- sealmigration.BasePort_TransitToShamir_Pre14+300)
-}
-
-func TestSealMigration_ShamirToTransit_Post14(t *testing.T) {
- t.Parallel()
- logger := logging.NewVaultLogger(hclog.Debug).Named(t.Name())
- storage, cleanup := teststorage.MakeReusableStorage(t, logger,
- consul.MakeConsulBackend(t, logger))
- defer cleanup()
- sealmigration.ParamTestSealMigrationTransitToShamir_Post14(t, logger, storage,
- sealmigration.BasePort_ShamirToTransit_Post14+300)
-}
-
-func TestSealMigration_TransitToShamir_Post14(t *testing.T) {
- t.Parallel()
- logger := logging.NewVaultLogger(hclog.Debug).Named(t.Name())
- storage, cleanup := teststorage.MakeReusableStorage(t, logger,
- consul.MakeConsulBackend(t, logger))
- defer cleanup()
- sealmigration.ParamTestSealMigrationTransitToShamir_Post14(t, logger, storage,
- sealmigration.BasePort_TransitToShamir_Post14+300)
-}
-
-func TestSealMigration_TransitToTransit_Post14(t *testing.T) {
- t.Parallel()
- logger := logging.NewVaultLogger(hclog.Debug).Named(t.Name())
- storage, cleanup := teststorage.MakeReusableStorage(t, logger,
- consul.MakeConsulBackend(t, logger))
- defer cleanup()
- sealmigration.ParamTestSealMigration_TransitToTransit(t, logger, storage,
- sealmigration.BasePort_TransitToTransit+300)
-}
diff --git a/vault/external_tests/token/batch_token_test.go b/vault/external_tests/token/batch_token_test.go
deleted file mode 100644
index 2be265007..000000000
--- a/vault/external_tests/token/batch_token_test.go
+++ /dev/null
@@ -1,528 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package token
-
-import (
- "strings"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/builtin/credential/approle"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/helper/consts"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-func TestBatchTokens(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "kv": vault.LeasedPassthroughBackendFactory,
- },
- CredentialBackends: map[string]logical.Factory{
- "approle": approle.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
- rootToken := client.Token()
- var err error
-
- // Set up a KV path
- err = client.Sys().Mount("kv", &api.MountInput{
- Type: "kv",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("kv/foo", map[string]interface{}{
- "foo": "bar",
- "ttl": "5m",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Write the test policy
- err = client.Sys().PutPolicy("test", `
-path "kv/*" {
- capabilities = ["read"]
-}`)
- if err != nil {
- t.Fatal(err)
- }
-
- // Mount the auth backend
- err = client.Sys().EnableAuthWithOptions("approle", &api.EnableAuthOptions{
- Type: "approle",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Tune the mount
- if err = client.Sys().TuneMount("auth/approle", api.MountConfigInput{
- DefaultLeaseTTL: "5s",
- MaxLeaseTTL: "5s",
- }); err != nil {
- t.Fatal(err)
- }
-
- // Create role
- resp, err := client.Logical().Write("auth/approle/role/test", map[string]interface{}{
- "policies": "test",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Get role_id
- resp, err = client.Logical().Read("auth/approle/role/test/role-id")
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for fetching the role-id")
- }
- roleID := resp.Data["role_id"]
-
- // Get secret_id
- resp, err = client.Logical().Write("auth/approle/role/test/secret-id", map[string]interface{}{})
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for fetching the secret-id")
- }
- secretID := resp.Data["secret_id"]
-
- // Login
- testLogin := func(mountTuneType, roleType string, batch bool) string {
- t.Helper()
- if err = client.Sys().TuneMount("auth/approle", api.MountConfigInput{
- TokenType: mountTuneType,
- }); err != nil {
- t.Fatal(err)
- }
- _, err = client.Logical().Write("auth/approle/role/test", map[string]interface{}{
- "token_type": roleType,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- resp, err = client.Logical().Write("auth/approle/login", map[string]interface{}{
- "role_id": roleID,
- "secret_id": secretID,
- })
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected a response for login")
- }
- if resp.Auth == nil {
- t.Fatal("expected auth object from response")
- }
- if resp.Auth.ClientToken == "" {
- t.Fatal("expected a client token")
- }
- if batch && !strings.HasPrefix(resp.Auth.ClientToken, consts.BatchTokenPrefix) {
- t.Fatal("expected a batch token")
- }
- if !batch && strings.HasPrefix(resp.Auth.ClientToken, consts.BatchTokenPrefix) {
- t.Fatal("expected a non-batch token")
- }
- return resp.Auth.ClientToken
- }
- testLogin("service", "default", false)
- testLogin("service", "batch", false)
- testLogin("service", "service", false)
- testLogin("batch", "default", true)
- testLogin("batch", "batch", true)
- testLogin("batch", "service", true)
- testLogin("default-service", "default", false)
- testLogin("default-service", "batch", true)
- testLogin("default-service", "service", false)
- testLogin("default-batch", "default", true)
- testLogin("default-batch", "batch", true)
- testLogin("default-batch", "service", false)
-
- finalToken := testLogin("batch", "batch", true)
-
- client.SetToken(finalToken)
- resp, err = client.Logical().Read("kv/foo")
- if err != nil {
- t.Fatal(err)
- }
- if resp.Data["foo"].(string) != "bar" {
- t.Fatal("bad")
- }
- if resp.LeaseID == "" {
- t.Fatal("expected lease")
- }
- if !resp.Renewable {
- t.Fatal("expected renewable")
- }
- if resp.LeaseDuration > 5 {
- t.Fatalf("lease duration too big: %d", resp.LeaseDuration)
- }
- leaseID := resp.LeaseID
-
- lastDuration := resp.LeaseDuration
- for i := 0; i < 3; i++ {
- time.Sleep(time.Second)
- resp, err = client.Sys().Renew(leaseID, 0)
- if err != nil {
- t.Fatal(err)
- }
- if resp.LeaseDuration >= lastDuration {
- t.Fatal("expected duration to go down")
- }
- lastDuration = resp.LeaseDuration
- }
-
- client.SetToken(rootToken)
- time.Sleep(2 * time.Second)
- resp, err = client.Logical().Write("sys/leases/lookup", map[string]interface{}{
- "lease_id": leaseID,
- })
- if err == nil {
- t.Fatal("expected error")
- }
-}
-
-func TestBatchToken_ParentLeaseRevoke(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- LogicalBackends: map[string]logical.Factory{
- "kv": vault.LeasedPassthroughBackendFactory,
- },
- CredentialBackends: map[string]logical.Factory{
- "approle": approle.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
- rootToken := client.Token()
- var err error
-
- // Set up a KV path
- err = client.Sys().Mount("kv", &api.MountInput{
- Type: "kv",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = client.Logical().Write("kv/foo", map[string]interface{}{
- "foo": "bar",
- "ttl": "5m",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Write the test policy
- err = client.Sys().PutPolicy("test", `
-path "kv/*" {
- capabilities = ["read"]
-}`)
- if err != nil {
- t.Fatal(err)
- }
-
- // Create a second root token
- secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"root"},
- })
- if err != nil {
- t.Fatal(err)
- }
- rootToken2 := secret.Auth.ClientToken
-
- // Use this new token to create a batch token
- client.SetToken(rootToken2)
- secret, err = client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"test"},
- Type: "batch",
- })
- if err != nil {
- t.Fatal(err)
- }
- batchToken := secret.Auth.ClientToken
- client.SetToken(batchToken)
- _, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
- if secret.Auth.ClientToken[0:vault.TokenPrefixLength] != consts.BatchTokenPrefix {
- t.Fatal(secret.Auth.ClientToken)
- }
-
- // Get a lease with the batch token
- resp, err := client.Logical().Read("kv/foo")
- if err != nil {
- t.Fatal(err)
- }
- if resp.Data["foo"].(string) != "bar" {
- t.Fatal("bad")
- }
- if resp.LeaseID == "" {
- t.Fatal("expected lease")
- }
- leaseID := resp.LeaseID
-
- // Check the lease
- resp, err = client.Logical().Write("sys/leases/lookup", map[string]interface{}{
- "lease_id": leaseID,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Revoke the parent
- client.SetToken(rootToken2)
- err = client.Auth().Token().RevokeSelf("")
- if err != nil {
- t.Fatal(err)
- }
-
- time.Sleep(1 * time.Second)
-
- // Verify the batch token is not usable anymore
- client.SetToken(rootToken)
- _, err = client.Auth().Token().Lookup(batchToken)
- if err == nil {
- t.Fatal("expected error")
- }
-
- // Verify the lease has been revoked
- resp, err = client.Logical().Write("sys/leases/lookup", map[string]interface{}{
- "lease_id": leaseID,
- })
- if err == nil {
- t.Fatal("expected error")
- }
-}
-
-func TestTokenStore_Roles_Batch(t *testing.T) {
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
- rootToken := client.Token()
-
- var err error
- var secret *api.Secret
-
- // Test service
- {
- _, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{
- "bound_cidrs": []string{},
- "token_type": "service",
- })
- if err != nil {
- t.Fatal(err)
- }
- secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{
- Policies: []string{"default"},
- Type: "batch",
- }, "testrole")
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(secret.Auth.ClientToken)
- _, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
- if secret.Auth.ClientToken[0:vault.TokenPrefixLength] != consts.ServiceTokenPrefix {
- t.Fatal(secret.Auth.ClientToken)
- }
- }
-
- // Test batch
- {
- client.SetToken(rootToken)
- _, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{
- "token_type": "batch",
- })
- // Orphan not set so we should error
- if err == nil {
- t.Fatal("expected error")
- }
- _, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{
- "token_type": "batch",
- "orphan": true,
- })
- // Renewable set so we should error
- if err == nil {
- t.Fatal("expected error")
- }
- _, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{
- "token_type": "batch",
- "orphan": true,
- "renewable": false,
- })
- if err != nil {
- t.Fatal(err)
- }
- secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{
- Policies: []string{"default"},
- Type: "service",
- }, "testrole")
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(secret.Auth.ClientToken)
- _, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
- if secret.Auth.ClientToken[0:vault.TokenPrefixLength] != consts.BatchTokenPrefix {
- t.Fatal(secret.Auth.ClientToken)
- }
- }
-
- // Test default-service
- {
- client.SetToken(rootToken)
- _, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{
- "token_type": "default-service",
- })
- if err != nil {
- t.Fatal(err)
- }
- // Client specifies batch
- secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{
- Policies: []string{"default"},
- Type: "batch",
- }, "testrole")
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(secret.Auth.ClientToken)
- _, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
- if secret.Auth.ClientToken[0:vault.TokenPrefixLength] != consts.BatchTokenPrefix {
- t.Fatal(secret.Auth.ClientToken)
- }
- // Client specifies service
- client.SetToken(rootToken)
- secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{
- Policies: []string{"default"},
- Type: "service",
- }, "testrole")
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(secret.Auth.ClientToken)
- _, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
- if secret.Auth.ClientToken[0:vault.TokenPrefixLength] != consts.ServiceTokenPrefix {
- t.Fatal(secret.Auth.ClientToken)
- }
- // Client doesn't specify
- client.SetToken(rootToken)
- secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{
- Policies: []string{"default"},
- }, "testrole")
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(secret.Auth.ClientToken)
- _, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
- if secret.Auth.ClientToken[0:vault.TokenPrefixLength] != consts.ServiceTokenPrefix {
- t.Fatal(secret.Auth.ClientToken)
- }
- }
-
- // Test default-batch
- {
- client.SetToken(rootToken)
- _, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{
- "token_type": "default-batch",
- })
- if err != nil {
- t.Fatal(err)
- }
- // Client specifies batch
- secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{
- Policies: []string{"default"},
- Type: "batch",
- }, "testrole")
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(secret.Auth.ClientToken)
- _, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
- if secret.Auth.ClientToken[0:vault.TokenPrefixLength] != consts.BatchTokenPrefix {
- t.Fatal(secret.Auth.ClientToken)
- }
- // Client specifies service
- client.SetToken(rootToken)
- secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{
- Policies: []string{"default"},
- Type: "service",
- }, "testrole")
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(secret.Auth.ClientToken)
- _, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
- if secret.Auth.ClientToken[0:vault.TokenPrefixLength] != consts.ServiceTokenPrefix {
- t.Fatal(secret.Auth.ClientToken)
- }
- // Client doesn't specify
- client.SetToken(rootToken)
- secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{
- Policies: []string{"default"},
- }, "testrole")
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(secret.Auth.ClientToken)
- _, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
- if secret.Auth.ClientToken[0:vault.TokenPrefixLength] != consts.BatchTokenPrefix {
- t.Fatal(secret.Auth.ClientToken)
- }
- }
-}
diff --git a/vault/external_tests/token/token_test.go b/vault/external_tests/token/token_test.go
deleted file mode 100644
index f8adeac55..000000000
--- a/vault/external_tests/token/token_test.go
+++ /dev/null
@@ -1,681 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package token
-
-import (
- "encoding/base64"
- "reflect"
- "sort"
- "strings"
- "testing"
- "time"
-
- "github.com/go-test/deep"
- "github.com/hashicorp/vault/api"
- credLdap "github.com/hashicorp/vault/builtin/credential/ldap"
- credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
- "github.com/hashicorp/vault/helper/testhelpers/ldap"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/helper/jsonutil"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault"
-)
-
-func TestTokenStore_CreateOrphanResponse(t *testing.T) {
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- secret, err := client.Auth().Token().CreateOrphan(&api.TokenCreateRequest{
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- if !secret.Auth.Orphan {
- t.Fatalf("failed to set orphan as true, got: %#v", secret.Auth)
- }
-}
-
-func TestTokenStore_TokenInvalidEntityID(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "userpass": credUserpass.Factory,
- },
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // Enable userpass auth
- err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
- Type: "userpass",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Add a user to userpass backend
- _, err = client.Logical().Write("auth/userpass/users/testuser", map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- secret, err := client.Logical().Write("auth/userpass/login/testuser", map[string]interface{}{
- "password": "testpassword",
- })
- if err != nil {
- t.Fatal(err)
- }
- clientToken := secret.Auth.ClientToken
-
- secret, err = client.Logical().Write("auth/token/lookup", map[string]interface{}{
- "token": clientToken,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- entityID := secret.Data["entity_id"].(string)
-
- _, err = client.Logical().Delete("identity/entity/id/" + entityID)
- if err != nil {
- t.Fatal(err)
- }
-
- client.SetToken(clientToken)
-
- secret, err = client.Logical().Write("auth/token/lookup-self", nil)
- if err == nil {
- t.Fatalf("expected error due to token being invalid when its entity is invalid")
- }
-}
-
-func TestTokenStore_IdentityPolicies(t *testing.T) {
- coreConfig := &vault.CoreConfig{
- CredentialBackends: map[string]logical.Factory{
- "ldap": credLdap.Factory,
- },
- EnableRaw: true,
- }
- cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
-
- // Enable LDAP auth
- err := client.Sys().EnableAuthWithOptions("ldap", &api.EnableAuthOptions{
- Type: "ldap",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- cleanup, cfg := ldap.PrepareTestContainer(t, "latest")
- defer cleanup()
-
- // Configure LDAP auth
- _, err = client.Logical().Write("auth/ldap/config", map[string]interface{}{
- "url": cfg.Url,
- "userattr": cfg.UserAttr,
- "userdn": cfg.UserDN,
- "groupdn": cfg.GroupDN,
- "groupattr": cfg.GroupAttr,
- "binddn": cfg.BindDN,
- "bindpass": cfg.BindPassword,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create group in LDAP auth
- _, err = client.Logical().Write("auth/ldap/groups/testgroup1", map[string]interface{}{
- "policies": "testgroup1-policy",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Create user in LDAP auth. We add two groups, but we should filter out
- // the ones that don't match aliases later (we will check for this)
- _, err = client.Logical().Write("auth/ldap/users/hermes conrad", map[string]interface{}{
- "policies": "default",
- "groups": "testgroup1,testgroup2",
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Login using LDAP
- secret, err := client.Logical().Write("auth/ldap/login/hermes conrad", map[string]interface{}{
- "password": "hermes",
- })
- if err != nil {
- t.Fatal(err)
- }
- ldapClientToken := secret.Auth.ClientToken
-
- expectedPolicies := []string{
- "default",
- "testgroup1-policy",
- }
- if !reflect.DeepEqual(expectedPolicies, secret.Auth.Policies) {
- t.Fatalf("bad: identity policies; expected: %#v\nactual: %#v", expectedPolicies, secret.Auth.Policies)
- }
-
- // At this point there shouldn't be any identity policy on the token
- secret, err = client.Logical().Write("auth/token/lookup", map[string]interface{}{
- "token": ldapClientToken,
- })
- if err != nil {
- t.Fatal(err)
- }
- _, ok := secret.Data["identity_policies"]
- if ok {
- t.Fatalf("identity_policies should not have been set")
- }
-
- // Extract the entity ID of the token and set some policies on the entity
- entityID := secret.Data["entity_id"].(string)
- _, err = client.Logical().Write("identity/entity/id/"+entityID, map[string]interface{}{
- "policies": []string{
- "entity_policy_1",
- "entity_policy_2",
- },
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Lookup the token and expect entity policies on the token
- secret, err = client.Logical().Write("auth/token/lookup", map[string]interface{}{
- "token": ldapClientToken,
- })
- if err != nil {
- t.Fatal(err)
- }
- identityPolicies := secret.Data["identity_policies"].([]interface{})
- var actualPolicies []string
- for _, item := range identityPolicies {
- actualPolicies = append(actualPolicies, item.(string))
- }
- sort.Strings(actualPolicies)
-
- expectedPolicies = []string{
- "entity_policy_1",
- "entity_policy_2",
- }
- sort.Strings(expectedPolicies)
- if !reflect.DeepEqual(expectedPolicies, actualPolicies) {
- t.Fatalf("bad: identity policies; expected: %#v\nactual: %#v", expectedPolicies, actualPolicies)
- }
-
- // Create identity group and add entity as its member
- secret, err = client.Logical().Write("identity/group", map[string]interface{}{
- "policies": []string{
- "group_policy_1",
- "group_policy_2",
- },
- "member_entity_ids": []string{
- entityID,
- },
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Lookup token and expect both entity and group policies on the token
- secret, err = client.Logical().Write("auth/token/lookup", map[string]interface{}{
- "token": ldapClientToken,
- })
- if err != nil {
- t.Fatal(err)
- }
- identityPolicies = secret.Data["identity_policies"].([]interface{})
- actualPolicies = nil
- for _, item := range identityPolicies {
- actualPolicies = append(actualPolicies, item.(string))
- }
- sort.Strings(actualPolicies)
-
- expectedPolicies = []string{
- "entity_policy_1",
- "entity_policy_2",
- "group_policy_1",
- "group_policy_2",
- }
- sort.Strings(expectedPolicies)
- if !reflect.DeepEqual(expectedPolicies, actualPolicies) {
- t.Fatalf("bad: identity policies; expected: %#v\nactual: %#v", expectedPolicies, actualPolicies)
- }
-
- // Create an external group and renew the token. This should add external
- // group policies to the token.
- auths, err := client.Sys().ListAuth()
- if err != nil {
- t.Fatal(err)
- }
- ldapMountAccessor1 := auths["ldap/"].Accessor
-
- // Create an external group
- secret, err = client.Logical().Write("identity/group", map[string]interface{}{
- "type": "external",
- "policies": []string{
- "external_group_policy_1",
- "external_group_policy_2",
- },
- })
- if err != nil {
- t.Fatal(err)
- }
- ldapExtGroupID1 := secret.Data["id"].(string)
-
- // Associate a group from LDAP auth as a group-alias in the external group
- _, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
- "name": "testgroup1",
- "mount_accessor": ldapMountAccessor1,
- "canonical_id": ldapExtGroupID1,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Renew token to refresh external group memberships
- secret, err = client.Auth().Token().Renew(ldapClientToken, 10)
- if err != nil {
- t.Fatal(err)
- }
-
- // Lookup token and expect entity, group and external group policies on the
- // token
- secret, err = client.Logical().Write("auth/token/lookup", map[string]interface{}{
- "token": ldapClientToken,
- })
- if err != nil {
- t.Fatal(err)
- }
- identityPolicies = secret.Data["identity_policies"].([]interface{})
- actualPolicies = nil
- for _, item := range identityPolicies {
- actualPolicies = append(actualPolicies, item.(string))
- }
- sort.Strings(actualPolicies)
-
- expectedPolicies = []string{
- "entity_policy_1",
- "entity_policy_2",
- "group_policy_1",
- "group_policy_2",
- "external_group_policy_1",
- "external_group_policy_2",
- }
- sort.Strings(expectedPolicies)
- if !reflect.DeepEqual(expectedPolicies, actualPolicies) {
- t.Fatalf("bad: identity policies; expected: %#v\nactual: %#v", expectedPolicies, actualPolicies)
- }
-
- // Log in and get a new token, then renew it. See issue #4829. The logic is
- // continued after the next block.
- secret, err = client.Logical().Write("auth/ldap/login/hermes conrad", map[string]interface{}{
- "password": "hermes",
- })
- if err != nil {
- t.Fatal(err)
- }
- token4829 := secret.Auth.ClientToken
-
- // Check that the lease for the token contains only the single group; this
- // should be true for both as one was fresh and the other was a renew
- // (which is why we do the renew check on the 4839 token after this block)
- secret, err = client.Logical().List("sys/raw/sys/expire/id/auth/ldap/login/hermes conrad/")
- if err != nil {
- t.Fatal(err)
- }
- for _, key := range secret.Data["keys"].([]interface{}) {
- secret, err := client.Logical().Read("sys/raw/sys/expire/id/auth/ldap/login/hermes conrad/" + key.(string))
- if err != nil {
- t.Fatal(err)
- }
- // t.Logf("%#v", *secret)
- var resp logical.Response
- if err := jsonutil.DecodeJSON([]byte(secret.Data["value"].(string)), &resp); err != nil {
- t.Fatal(err)
- }
- if len(resp.Auth.GroupAliases) != 1 || resp.Auth.GroupAliases[0].Name != "testgroup1" {
- t.Fatalf("bad: %#v", resp.Auth.GroupAliases)
- }
- }
-
- secret, err = client.Auth().Token().Renew(token4829, 10)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestTokenStore_CIDRBlocks(t *testing.T) {
- testPolicy := `
-path "auth/token/create" {
- capabilities = ["update"]
-}
-`
-
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
- rootToken := client.Token()
-
- var err error
- var secret *api.Secret
-
- _, err = client.Logical().Write("sys/policies/acl/test", map[string]interface{}{
- "policy": testPolicy,
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Test normally
- _, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{
- "bound_cidrs": []string{},
- })
- if err != nil {
- t.Fatal(err)
- }
- secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{
- Policies: []string{"default"},
- }, "testrole")
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(secret.Auth.ClientToken)
- _, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
-
- // CIDR blocks, containing localhost
- client.SetToken(rootToken)
- _, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{
- "bound_cidrs": []string{"127.0.0.1/32", "1.2.3.4/8", "5.6.7.8/24"},
- "allowed_policies": "test",
- })
- if err != nil {
- t.Fatal(err)
- }
- secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{
- Policies: []string{"test", "default"},
- }, "testrole")
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(secret.Auth.ClientToken)
- _, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
-
- // Before moving on, validate that a child token created from this token
- // inherits the bound cidr blocks
- client.SetToken(secret.Auth.ClientToken)
- childSecret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(childSecret.Auth.ClientToken)
- childInfo, err := client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
- if diff := deep.Equal(childInfo.Data["bound_cidrs"], []interface{}{"127.0.0.1", "1.2.3.4/8", "5.6.7.8/24"}); diff != nil {
- t.Fatal(diff)
- }
-
- // CIDR blocks, not containing localhost (should fail)
- client.SetToken(rootToken)
- _, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{
- "bound_cidrs": []string{"1.2.3.4/8", "5.6.7.8/24"},
- })
- if err != nil {
- t.Fatal(err)
- }
- secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{
- Policies: []string{"default"},
- }, "testrole")
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(secret.Auth.ClientToken)
- _, err = client.Auth().Token().LookupSelf()
- if err == nil {
- t.Fatal("expected error")
- }
- if !strings.Contains(err.Error(), "permission denied") {
- t.Fatalf("unexpected error: %v", err)
- }
-
- // Root token, no ttl, should work
- client.SetToken(rootToken)
- _, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{
- "bound_cidrs": []string{"1.2.3.4/8", "5.6.7.8/24"},
- "allowed_policies": "",
- })
- if err != nil {
- t.Fatal(err)
- }
- secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{}, "testrole")
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(secret.Auth.ClientToken)
- _, err = client.Auth().Token().LookupSelf()
- if err != nil {
- t.Fatal(err)
- }
-
- // Root token, ttl, should not work
- client.SetToken(rootToken)
- _, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{
- "bound_cidrs": []string{"1.2.3.4/8", "5.6.7.8/24"},
- "period": 3600,
- })
- if err != nil {
- t.Fatal(err)
- }
- secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{}, "testrole")
- if err != nil {
- t.Fatal(err)
- }
- client.SetToken(secret.Auth.ClientToken)
- _, err = client.Auth().Token().LookupSelf()
- if err == nil {
- t.Fatal("expected error")
- }
- if !strings.Contains(err.Error(), "permission denied") {
- t.Fatalf("unexpected error: %v", err)
- }
-}
-
-func TestTokenStore_RevocationOnStartup(t *testing.T) {
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- NumCores: 1,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- core := cluster.Cores[0].Core
- vault.TestWaitActive(t, core)
- client := cluster.Cores[0].Client
- rootToken := client.Token()
-
- type leaseEntry struct {
- LeaseID string `json:"lease_id"`
- ClientToken string `json:"client_token"`
- Path string `json:"path"`
- Data map[string]interface{} `json:"data"`
- Secret *logical.Secret `json:"secret"`
- Auth *logical.Auth `json:"auth"`
- IssueTime time.Time `json:"issue_time"`
- ExpireTime time.Time `json:"expire_time"`
- LastRenewalTime time.Time `json:"last_renewal_time"`
- }
-
- var secret *api.Secret
- var err error
- var tokens []string
- // Create tokens
- for i := 0; i < 500; i++ {
- secret, err = client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- tokens = append(tokens, secret.Auth.ClientToken)
- }
-
- const tokenPath string = "sys/raw/sys/token/id/"
- secret, err = client.Logical().List(tokenPath)
- if err != nil {
- t.Fatal(err)
- }
- totalTokens := len(secret.Data["keys"].([]interface{}))
-
- // Get the list of leases
- const leasePath string = "sys/raw/sys/expire/id/auth/token/create/"
- secret, err = client.Logical().List(leasePath)
- if err != nil {
- t.Fatal(err)
- }
- leases := secret.Data["keys"].([]interface{})
- if len(leases) != 500 {
- t.Fatalf("unexpected number of leases: %d", len(leases))
- }
-
- // Holds non-root leases
- var validLeases []string
- // Fake times in the past
- for _, lease := range leases {
- secret, err = client.Logical().Read(leasePath + lease.(string))
- if err != nil {
- t.Fatal(err)
- }
- var entry leaseEntry
- if err := jsonutil.DecodeJSON([]byte(secret.Data["value"].(string)), &entry); err != nil {
- t.Fatal(err)
- }
- if entry.ExpireTime.IsZero() {
- continue
- }
- validLeases = append(validLeases, lease.(string))
- entry.IssueTime = entry.IssueTime.Add(-1 * time.Hour * 24 * 365)
- entry.ExpireTime = entry.ExpireTime.Add(-1 * time.Hour * 24 * 365)
- jsonEntry, err := jsonutil.EncodeJSON(&entry)
- if err != nil {
- t.Fatal(err)
- }
- if _, err := client.Logical().Write(leasePath+lease.(string), map[string]interface{}{
- "value": string(jsonEntry),
- }); err != nil {
- t.Fatal(err)
- }
- }
-
- if err := client.Sys().Seal(); err != nil {
- t.Fatal(err)
- }
-
- var status *api.SealStatusResponse
- for i := 0; i < len(cluster.BarrierKeys); i++ {
- status, err = client.Sys().Unseal(string(base64.StdEncoding.EncodeToString(cluster.BarrierKeys[i])))
- if err != nil {
- t.Fatal(err)
- }
- if !status.Sealed {
- break
- }
- }
- if status.Sealed {
- t.Fatal("did not unseal properly")
- }
-
- // Give lease loading some time to process
- time.Sleep(5 * time.Second)
-
- for i, token := range tokens {
- client.SetToken(token)
- _, err := client.Logical().Write("cubbyhole/foo", map[string]interface{}{
- "value": "bar",
- })
- if err == nil {
- t.Errorf("expected error but did not get one, token num %d", i)
- }
- }
-
- expectedLeases := len(leases) - len(validLeases)
-
- client.SetToken(rootToken)
- secret, err = client.Logical().List(leasePath)
- if err != nil {
- t.Fatal(err)
- }
-
- switch {
- case secret == nil:
- if expectedLeases != 0 {
- t.Fatalf("nil secret back but expected %d leases", expectedLeases)
- }
-
- case secret.Data == nil:
- if expectedLeases != 0 {
- t.Fatalf("nil secret data back but expected %d leases, secret is %#v", expectedLeases, *secret)
- }
-
- default:
- leasesLeft := len(secret.Data["keys"].([]interface{}))
- if leasesLeft != expectedLeases {
- t.Fatalf("found %d leases left, expected %d", leasesLeft, expectedLeases)
- }
- }
-
- expectedTokens := totalTokens - len(validLeases)
- secret, err = client.Logical().List(tokenPath)
- if err != nil {
- t.Fatal(err)
- }
- tokensLeft := len(secret.Data["keys"].([]interface{}))
- if tokensLeft != expectedTokens {
- t.Fatalf("found %d tokens left, expected %d", tokensLeft, expectedTokens)
- }
-}
diff --git a/vault/generate_root_test.go b/vault/generate_root_test.go
deleted file mode 100644
index 8f5ddf512..000000000
--- a/vault/generate_root_test.go
+++ /dev/null
@@ -1,319 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "encoding/base64"
- "testing"
-
- "github.com/hashicorp/go-secure-stdlib/base62"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/helper/pgpkeys"
- "github.com/hashicorp/vault/sdk/helper/xor"
-)
-
-func TestCore_GenerateRoot_Lifecycle(t *testing.T) {
- c, masterKeys, _ := TestCoreUnsealed(t)
- testCore_GenerateRoot_Lifecycle_Common(t, c, masterKeys)
-}
-
-func testCore_GenerateRoot_Lifecycle_Common(t *testing.T, c *Core, keys [][]byte) {
- // Verify update not allowed
- if _, err := c.GenerateRootUpdate(namespace.RootContext(nil), keys[0], "", GenerateStandardRootTokenStrategy); err == nil {
- t.Fatalf("no root generation in progress")
- }
-
- // Should be no progress
- num, err := c.GenerateRootProgress()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if num != 0 {
- t.Fatalf("bad: %d", num)
- }
-
- // Should be no config
- conf, err := c.GenerateRootConfiguration()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if conf != nil {
- t.Fatalf("bad: %v", conf)
- }
-
- // Cancel should be idempotent
- err = c.GenerateRootCancel()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- otp, err := base62.Random(TokenPrefixLength + TokenLength)
- if err != nil {
- t.Fatal(err)
- }
-
- // Start a root generation
- err = c.GenerateRootInit(otp, "", GenerateStandardRootTokenStrategy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should get config
- conf, err = c.GenerateRootConfiguration()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Cancel should be clear
- err = c.GenerateRootCancel()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should be no config
- conf, err = c.GenerateRootConfiguration()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if conf != nil {
- t.Fatalf("bad: %v", conf)
- }
-}
-
-func TestCore_GenerateRoot_Init(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- testCore_GenerateRoot_Init_Common(t, c)
-
- bc := &SealConfig{SecretShares: 5, SecretThreshold: 3, StoredShares: 1}
- rc := &SealConfig{SecretShares: 5, SecretThreshold: 3}
- c, _, _, _ = TestCoreUnsealedWithConfigs(t, bc, rc)
- testCore_GenerateRoot_Init_Common(t, c)
-}
-
-func testCore_GenerateRoot_Init_Common(t *testing.T, c *Core) {
- otp, err := base62.Random(TokenPrefixLength + TokenLength)
- if err != nil {
- t.Fatal(err)
- }
-
- err = c.GenerateRootInit(otp, "", GenerateStandardRootTokenStrategy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Second should fail
- err = c.GenerateRootInit("", pgpkeys.TestPubKey1, GenerateStandardRootTokenStrategy)
- if err == nil {
- t.Fatalf("should fail")
- }
-}
-
-func TestCore_GenerateRoot_InvalidMasterNonce(t *testing.T) {
- c, masterKeys, _ := TestCoreUnsealed(t)
- // Pass in master keys as they'll be invalid
- masterKeys[0][0]++
- testCore_GenerateRoot_InvalidMasterNonce_Common(t, c, masterKeys)
-}
-
-func testCore_GenerateRoot_InvalidMasterNonce_Common(t *testing.T, c *Core, keys [][]byte) {
- otp, err := base62.Random(TokenPrefixLength + TokenLength)
- if err != nil {
- t.Fatal(err)
- }
-
- err = c.GenerateRootInit(otp, "", GenerateStandardRootTokenStrategy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Fetch new config with generated nonce
- rgconf, err := c.GenerateRootConfiguration()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if rgconf == nil {
- t.Fatalf("bad: no rekey config received")
- }
-
- // Provide the nonce (invalid)
- _, err = c.GenerateRootUpdate(namespace.RootContext(nil), keys[0], "abcd", GenerateStandardRootTokenStrategy)
- if err == nil {
- t.Fatalf("expected error")
- }
-
- // Provide the master (invalid)
- for _, key := range keys {
- _, err = c.GenerateRootUpdate(namespace.RootContext(nil), key, rgconf.Nonce, GenerateStandardRootTokenStrategy)
- }
- if err == nil {
- t.Fatalf("expected error")
- }
-}
-
-func TestCore_GenerateRoot_Update_OTP(t *testing.T) {
- c, masterKeys, _ := TestCoreUnsealed(t)
- testCore_GenerateRoot_Update_OTP_Common(t, c, masterKeys)
-}
-
-func testCore_GenerateRoot_Update_OTP_Common(t *testing.T, c *Core, keys [][]byte) {
- otp, err := base62.Random(TokenPrefixLength + TokenLength)
- if err != nil {
- t.Fatal(err)
- }
-
- // Start a root generation
- err = c.GenerateRootInit(otp, "", GenerateStandardRootTokenStrategy)
- if err != nil {
- t.Fatal(err)
- }
-
- // Fetch new config with generated nonce
- rkconf, err := c.GenerateRootConfiguration()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if rkconf == nil {
- t.Fatalf("bad: no root generation config received")
- }
-
- // Provide the keys
- var result *GenerateRootResult
- for _, key := range keys {
- result, err = c.GenerateRootUpdate(namespace.RootContext(nil), key, rkconf.Nonce, GenerateStandardRootTokenStrategy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if result.EncodedToken != "" {
- break
- }
- }
- if result == nil {
- t.Fatalf("Bad, result is nil")
- }
-
- encodedToken := result.EncodedToken
-
- // Should be no progress
- num, err := c.GenerateRootProgress()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if num != 0 {
- t.Fatalf("bad: %d", num)
- }
-
- // Should be no config
- conf, err := c.GenerateRootConfiguration()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if conf != nil {
- t.Fatalf("bad: %v", conf)
- }
-
- tokenBytes, err := base64.RawStdEncoding.DecodeString(encodedToken)
- if err != nil {
- t.Fatal(err)
- }
-
- tokenBytes, err = xor.XORBytes(tokenBytes, []byte(otp))
- if err != nil {
- t.Fatal(err)
- }
-
- token := string(tokenBytes)
-
- // Ensure that the token is a root token
- te, err := c.tokenStore.Lookup(namespace.RootContext(nil), token)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if te == nil {
- t.Fatalf("token was nil")
- }
- if te.ID != token || te.Parent != "" ||
- len(te.Policies) != 1 || te.Policies[0] != "root" {
- t.Fatalf("bad: %#v", *te)
- }
-}
-
-func TestCore_GenerateRoot_Update_PGP(t *testing.T) {
- c, masterKeys, _ := TestCoreUnsealed(t)
- testCore_GenerateRoot_Update_PGP_Common(t, c, masterKeys)
-}
-
-func testCore_GenerateRoot_Update_PGP_Common(t *testing.T, c *Core, keys [][]byte) {
- // Start a root generation
- err := c.GenerateRootInit("", pgpkeys.TestPubKey1, GenerateStandardRootTokenStrategy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Fetch new config with generated nonce
- rkconf, err := c.GenerateRootConfiguration()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if rkconf == nil {
- t.Fatalf("bad: no root generation config received")
- }
-
- // Provide the keys
- var result *GenerateRootResult
- for _, key := range keys {
- result, err = c.GenerateRootUpdate(namespace.RootContext(nil), key, rkconf.Nonce, GenerateStandardRootTokenStrategy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if result.EncodedToken != "" {
- break
- }
- }
- if result == nil {
- t.Fatalf("Bad, result is nil")
- }
-
- encodedToken := result.EncodedToken
-
- // Should be no progress
- num, err := c.GenerateRootProgress()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if num != 0 {
- t.Fatalf("bad: %d", num)
- }
-
- // Should be no config
- conf, err := c.GenerateRootConfiguration()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if conf != nil {
- t.Fatalf("bad: %v", conf)
- }
-
- ptBuf, err := pgpkeys.DecryptBytes(encodedToken, pgpkeys.TestPrivKey1)
- if err != nil {
- t.Fatal(err)
- }
- if ptBuf == nil {
- t.Fatal("Got nil plaintext key")
- }
-
- token := ptBuf.String()
-
- // Ensure that the token is a root token
- te, err := c.tokenStore.Lookup(namespace.RootContext(nil), token)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if te == nil {
- t.Fatalf("token was nil")
- }
- if te.ID != token || te.Parent != "" ||
- len(te.Policies) != 1 || te.Policies[0] != "root" {
- t.Fatalf("bad: %#v", *te)
- }
-}
diff --git a/vault/ha_test.go b/vault/ha_test.go
deleted file mode 100644
index cf3142d76..000000000
--- a/vault/ha_test.go
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "fmt"
- "math/rand"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-)
-
-// TestGrabLockOrStopped is a non-deterministic test to detect deadlocks in the
-// grabLockOrStopped function. This test starts a bunch of workers which
-// continually lock/unlock and rlock/runlock the same RWMutex. Each worker also
-// starts a goroutine which closes the stop channel 1/2 the time, which races
-// with acquisition of the lock.
-func TestGrabLockOrStop(t *testing.T) {
- // Stop the test early if we deadlock.
- const (
- workers = 100
- testDuration = time.Second
- testTimeout = 10 * testDuration
- )
- done := make(chan struct{})
- defer close(done)
- var lockCount int64
- go func() {
- select {
- case <-done:
- case <-time.After(testTimeout):
- panic(fmt.Sprintf("deadlock after %d lock count",
- atomic.LoadInt64(&lockCount)))
- }
- }()
-
- // lock is locked/unlocked and rlocked/runlocked concurrently.
- var lock sync.RWMutex
- start := time.Now()
-
- // workerWg is used to wait until all workers exit.
- var workerWg sync.WaitGroup
- workerWg.Add(workers)
-
- // Start a bunch of worker goroutines.
- for g := 0; g < workers; g++ {
- g := g
- go func() {
- defer workerWg.Done()
- for time.Now().Sub(start) < testDuration {
- stop := make(chan struct{})
-
- // closerWg waits until the closer goroutine exits before we do
- // another iteration. This makes sure goroutines don't pile up.
- var closerWg sync.WaitGroup
- closerWg.Add(1)
- go func() {
- defer closerWg.Done()
- // Close the stop channel half the time.
- if rand.Int()%2 == 0 {
- close(stop)
- }
- }()
-
- // Half the goroutines lock/unlock and the other half rlock/runlock.
- if g%2 == 0 {
- if !grabLockOrStop(lock.Lock, lock.Unlock, stop) {
- lock.Unlock()
- }
- } else {
- if !grabLockOrStop(lock.RLock, lock.RUnlock, stop) {
- lock.RUnlock()
- }
- }
-
- closerWg.Wait()
-
- // This lets us know how many lock/unlock and rlock/runlock have
- // happened if there's a deadlock.
- atomic.AddInt64(&lockCount, 1)
- }
- }()
- }
- workerWg.Wait()
-}
diff --git a/vault/hcp_link/capabilities/api_capability/token_manager_test.go b/vault/hcp_link/capabilities/api_capability/token_manager_test.go
deleted file mode 100644
index c0f688609..000000000
--- a/vault/hcp_link/capabilities/api_capability/token_manager_test.go
+++ /dev/null
@@ -1,318 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package api_capability
-
-import (
- "context"
- "os"
- "testing"
- "time"
-
- "github.com/hashicorp/go-hclog"
- scada "github.com/hashicorp/hcp-scada-provider"
- sdkResource "github.com/hashicorp/hcp-sdk-go/resource"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/internalshared/configutil"
- "github.com/hashicorp/vault/vault"
- "github.com/hashicorp/vault/vault/hcp_link/internal"
-)
-
-func getHCPConfig(t *testing.T, clientID, clientSecret string) *configutil.HCPLinkConfig {
- resourceIDRaw, ok := os.LookupEnv("HCP_RESOURCE_ID")
- if !ok {
- t.Skip("failed to find the HCP resource ID")
- }
- res, err := sdkResource.FromString(resourceIDRaw)
- if err != nil {
- t.Fatalf("failed to parse the resource ID, %v", err.Error())
- }
- return &configutil.HCPLinkConfig{
- ResourceIDRaw: resourceIDRaw,
- Resource: &res,
- ClientID: clientID,
- ClientSecret: clientSecret,
- }
-}
-
-func getTestScadaConfig(t *testing.T) *scada.Config {
- clientID, ok := os.LookupEnv("HCP_CLIENT_ID")
- if !ok {
- t.Skip("HCP client ID not found in env")
- }
- clientSecret, ok := os.LookupEnv("HCP_CLIENT_SECRET")
- if !ok {
- t.Skip("HCP client secret not found in env")
- }
-
- if _, ok := os.LookupEnv("HCP_API_ADDRESS"); !ok {
- t.Skip("failed to find HCP_API_ADDRESS in the environment")
- }
- if _, ok := os.LookupEnv("HCP_SCADA_ADDRESS"); !ok {
- t.Skip("failed to find HCP_SCADA_ADDRESS in the environment")
- }
- if _, ok := os.LookupEnv("HCP_AUTH_URL"); !ok {
- t.Skip("failed to find HCP_AUTH_URL in the environment")
- }
-
- hcpConfig := getHCPConfig(t, clientID, clientSecret)
-
- scadaConfig, err := internal.NewScadaConfig(hcpConfig, hclog.New(nil))
- if err != nil {
- t.Fatalf("failed to initialize Scada config")
- }
- return scadaConfig
-}
-
-func TestCreateTokenCoreSealedUnSealed(t *testing.T) {
- t.Parallel()
- core := vault.TestCore(t)
- logger := hclog.New(nil)
-
- scadaConfig := getTestScadaConfig(t)
-
- tm, err := NewHCPLinkTokenManager(scadaConfig, core, logger)
- if err != nil {
- t.Fatalf("failed to instantiate token manager")
- }
-
- if tm.latestToken != "" {
- t.Fatalf("unexpected latest token")
- }
-
- ctx := context.Background()
- tm.createToken(ctx)
-
- if tm.latestToken != "" {
- t.Fatalf("unexpected latest token while core is sealed")
- }
-
- // unsealing core
- vault.TestInitUnsealCore(t, core)
-
- tm.createToken(ctx)
- if tm.latestToken == "" {
- t.Fatalf("latestToken should not be empty")
- }
- latestTokenOld := tm.latestToken
-
- // running update token again should not change the token
- tm.createToken(ctx)
-
- if tm.latestToken == latestTokenOld {
- t.Fatalf("latestToken should have been refreshed")
- }
-}
-
-func TestShutdownTokenManagerForgetsTokenPolicy(t *testing.T) {
- t.Parallel()
- core := vault.TestCore(t)
- logger := hclog.New(nil)
-
- scadaConfig := getTestScadaConfig(t)
-
- tm, err := NewHCPLinkTokenManager(scadaConfig, core, logger)
- if err != nil {
- t.Fatalf("failed to instantiate token manager")
- }
-
- // unsealing core
- vault.TestInitUnsealCore(t, core)
-
- tm.HandleTokenPolicy(context.Background(), true)
- if tm.GetLatestToken() == "" {
- t.Fatalf("token manager did not update both token and policy")
- }
-
- tm.Shutdown()
-
- if tm.GetLatestToken() != "" || tm.policy != "" {
- t.Fatalf("shutting down TM did not forget both token and policy")
- }
-}
-
-func TestSealVaultTokenManagerForgetsTokenPolicy(t *testing.T) {
- t.Parallel()
- core := vault.TestCore(t)
- logger := hclog.New(nil)
-
- scadaConfig := getTestScadaConfig(t)
-
- tm, err := NewHCPLinkTokenManager(scadaConfig, core, logger)
- if err != nil {
- t.Fatalf("failed to instantiate token manager")
- }
-
- // unsealing core
- vault.TestInitUnsealCore(t, core)
-
- ctx := context.Background()
- tm.HandleTokenPolicy(ctx, true)
-
- if tm.GetLatestToken() == "" {
- t.Fatalf("token manager did not update both token and policy")
- }
-
- // seal core
- err = vault.TestCoreSeal(core)
- if err != nil {
- t.Fatalf("failed to seal core")
- }
-
- tm.HandleTokenPolicy(ctx, true)
- if tm.GetLatestToken() != "" || tm.GetPolicy() != "" {
- t.Fatalf("vault is seal, TM did not forget both token and policy")
- }
-}
-
-func TestCreateTokenWithTTL(t *testing.T) {
- t.Parallel()
- core := vault.TestCore(t)
- logger := hclog.New(nil)
-
- scadaConfig := getTestScadaConfig(t)
-
- tm, err := NewHCPLinkTokenManager(scadaConfig, core, logger)
- if err != nil {
- t.Fatalf("failed to instantiate token manager")
- }
-
- // unsealing core
- vault.TestInitUnsealCore(t, core)
-
- ttl := 7 * time.Second
- err = tm.SetTokenTTL(ttl)
- if err != nil {
- t.Fatalf("failed to set token TTL")
- }
-
- ctx := context.Background()
- tm.createToken(ctx)
-
- latestToken := tm.GetLatestToken()
- if latestToken == "" {
- t.Fatalf("latestToken should not be empty")
- }
-
- te, err := core.LookupToken(namespace.RootContext(nil), latestToken)
- if err != nil {
- t.Fatalf("failed to look up token")
- }
- if te.TTL != ttl {
- t.Fatalf("ttl is not as expected")
- }
-
- // sleep until the token is expired
- deadline := time.Now().Add(8 * time.Second)
- for time.Now().Before(deadline) {
- te, err = core.LookupToken(namespace.RootContext(nil), latestToken)
- if err == nil && te == nil {
- break
- }
- time.Sleep(500 * time.Millisecond)
- }
- if err != nil && te != nil {
- t.Fatalf("token did not expire as expected")
- }
-}
-
-func TestHandleTokenPolicyWithTokenTTL(t *testing.T) {
- t.Parallel()
- core := vault.TestCore(t)
- logger := hclog.New(nil)
-
- scadaConfig := getTestScadaConfig(t)
-
- tm, err := NewHCPLinkTokenManager(scadaConfig, core, logger)
- if err != nil {
- t.Fatalf("failed to instantiate token manager")
- }
-
- // unsealing core
- vault.TestInitUnsealCore(t, core)
-
- ttl := 7 * time.Second
- err = tm.SetTokenTTL(ttl)
- if err != nil {
- t.Fatalf("failed to set token TTL")
- }
-
- ctx := context.Background()
- tm.createToken(ctx)
-
- latestToken := tm.GetLatestToken()
- if latestToken == "" {
- t.Fatalf("latestToken should not be empty")
- }
-
- // waiting until TTL - 5 seconds is past
- time.Sleep(ttl - tokenExpiryOffset + 1)
-
- newToken := tm.HandleTokenPolicy(ctx, true)
- if newToken == "" || newToken == latestToken {
- t.Fatalf("token did not refreshed after expiry")
- }
-}
-
-func TestHandleTokenPolicySealedUnsealed(t *testing.T) {
- t.Parallel()
- core := vault.TestCore(t)
- logger := hclog.New(nil)
-
- scadaConfig := getTestScadaConfig(t)
-
- tm, err := NewHCPLinkTokenManager(scadaConfig, core, logger)
- if err != nil {
- t.Fatalf("failed to instantiate token manager")
- }
-
- tm.latestToken = "non empty"
- tm.policy = "non empty"
-
- ctx := context.Background()
- tm.HandleTokenPolicy(ctx, true)
- if tm.GetLatestToken() != "" || tm.GetPolicy() != "" {
- t.Fatalf("on sealed Vault, token and policy was not deleted")
- }
-
- tm.latestToken = "non empty"
- tm.policy = "non empty"
-
- // unsealing core
- vault.TestInitUnsealCore(t, core)
-
- tm.HandleTokenPolicy(ctx, true)
-
- if tm.GetLatestToken() == "non empty" {
- t.Fatalf("latestToken and policy should have been updated")
- }
-}
-
-func TestForgetTokenPolicySealedUnsealed(t *testing.T) {
- t.Parallel()
- core := vault.TestCore(t)
- logger := hclog.New(nil)
-
- scadaConfig := getTestScadaConfig(t)
-
- tm, err := NewHCPLinkTokenManager(scadaConfig, core, logger)
- if err != nil {
- t.Fatalf("failed to instantiate token manager")
- }
-
- // unsealing core
- vault.TestInitUnsealCore(t, core)
-
- ctx := context.Background()
- tm.HandleTokenPolicy(ctx, true)
-
- if tm.GetLatestToken() == "" {
- t.Fatalf("on sealed Vault, token and policy was not refreshed")
- }
-
- tm.ForgetTokenPolicy()
-
- if tm.GetLatestToken() != "" || tm.GetPolicy() != "" {
- t.Fatalf("on sealed Vault, token and policy was not deleted")
- }
-}
diff --git a/vault/identity_lookup_test.go b/vault/identity_lookup_test.go
deleted file mode 100644
index 368381aca..000000000
--- a/vault/identity_lookup_test.go
+++ /dev/null
@@ -1,336 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "testing"
-
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestIdentityStore_Lookup_Entity(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- entityReq := &logical.Request{
- Path: "entity",
- Operation: logical.UpdateOperation,
- }
- resp, err = i.HandleRequest(ctx, entityReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %#v\nresp: %v", err, resp)
- }
- entityID := resp.Data["id"].(string)
-
- aliasReq := &logical.Request{
- Path: "entity-alias",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testaliasname",
- "mount_accessor": accessor,
- "entity_id": entityID,
- },
- }
-
- resp, err = i.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %#v\nresp: %v", err, resp)
- }
- aliasID := resp.Data["id"].(string)
-
- entity, err := i.MemDBEntityByID(entityID, false)
- if err != nil {
- t.Fatal(err)
- }
-
- lookupReq := &logical.Request{
- Path: "lookup/entity",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "id": entityID,
- },
- }
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %#v\nresp: %v", err, resp)
- }
-
- if resp.Data["id"].(string) != entityID {
- t.Fatalf("bad: entity: %#v", resp.Data)
- }
-
- lookupReq.Data = map[string]interface{}{
- "name": entity.Name,
- }
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %#v\nresp: %v", err, resp)
- }
-
- if resp.Data["id"].(string) != entityID {
- t.Fatalf("bad: entity: %#v", resp.Data)
- }
-
- lookupReq.Data = map[string]interface{}{
- "alias_id": aliasID,
- }
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %#v\nresp: %v", err, resp)
- }
-
- if resp.Data["id"].(string) != entityID {
- t.Fatalf("bad: entity: %#v", resp.Data)
- }
-
- lookupReq.Data = map[string]interface{}{
- "alias_name": "testaliasname",
- "alias_mount_accessor": accessor,
- }
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %#v\nresp: %v", err, resp)
- }
-
- if resp.Data["id"].(string) != entityID {
- t.Fatalf("bad: entity: %#v", resp.Data)
- }
-
- // Supply 2 query criteria
- lookupReq.Data = map[string]interface{}{
- "id": entityID,
- "name": entity.Name,
- }
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected an error")
- }
-
- // Supply alias name and skip accessor
- lookupReq.Data = map[string]interface{}{
- "alias_name": "testaliasname",
- }
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected an error")
- }
-
- // Supply alias accessor and skip name
- lookupReq.Data = map[string]interface{}{
- "alias_mount_accessor": accessor,
- }
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected an error")
- }
-
- // Don't supply any criteria
- lookupReq.Data = nil
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected an error")
- }
-
- // Delete the alias in the entity
- aliasReq.Path = "entity-alias/id/" + aliasID
- aliasReq.Operation = logical.DeleteOperation
- resp, err = i.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %#v\nresp: %v", err, resp)
- }
-
- lookupReq.Data = map[string]interface{}{
- "alias_id": aliasID,
- }
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %#v\nresp: %v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-}
-
-func TestIdentityStore_Lookup_Group(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- groupReq := &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- }
- resp, err = i.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
- }
- groupID := resp.Data["id"].(string)
- groupName := resp.Data["name"].(string)
-
- lookupReq := &logical.Request{
- Path: "lookup/group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "id": groupID,
- },
- }
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
- }
- if resp.Data["id"].(string) != groupID {
- t.Fatalf("failed to lookup group")
- }
-
- lookupReq.Data = map[string]interface{}{
- "name": groupName,
- }
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
- }
- if resp.Data["id"].(string) != groupID {
- t.Fatalf("failed to lookup group")
- }
-
- // Query using an invalid alias_id
- lookupReq.Data = map[string]interface{}{
- "alias_id": "invalidaliasid",
- }
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- groupReq.Data = map[string]interface{}{
- "type": "external",
- }
- resp, err = i.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
- }
- groupID = resp.Data["id"].(string)
-
- aliasReq := &logical.Request{
- Path: "group-alias",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "canonical_id": groupID,
- "name": "testgroupalias",
- "mount_accessor": accessor,
- },
- }
- resp, err = i.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
- }
- aliasID := resp.Data["id"].(string)
-
- lookupReq.Data = map[string]interface{}{
- "alias_id": aliasID,
- }
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
- }
- if resp.Data["id"].(string) != groupID {
- t.Fatalf("failed to lookup group")
- }
-
- lookupReq.Data = map[string]interface{}{
- "alias_name": "testgroupalias",
- "alias_mount_accessor": accessor,
- }
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
- }
- if resp.Data["id"].(string) != groupID {
- t.Fatalf("failed to lookup group")
- }
-
- // Supply 2 query criteria
- lookupReq.Data = map[string]interface{}{
- "id": groupID,
- "name": groupName,
- }
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected an error")
- }
-
- // Supply alias name and skip accessor
- lookupReq.Data = map[string]interface{}{
- "alias_name": "testgroupalias",
- }
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected an error")
- }
-
- // Supply alias accessor and skip name
- lookupReq.Data = map[string]interface{}{
- "alias_mount_accessor": accessor,
- }
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected an error")
- }
-
- // Don't supply any criteria
- lookupReq.Data = nil
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected an error")
- }
-}
diff --git a/vault/identity_store_aliases_test.go b/vault/identity_store_aliases_test.go
deleted file mode 100644
index bf54c2db4..000000000
--- a/vault/identity_store_aliases_test.go
+++ /dev/null
@@ -1,943 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "reflect"
- "strings"
- "testing"
-
- "github.com/hashicorp/vault/helper/identity"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-// Issue 5729
-func TestIdentityStore_DuplicateAliases(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
-
- resp, err := c.systemBackend.HandleRequest(namespace.RootContext(nil), &logical.Request{
- Path: "auth",
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- tokenMountAccessor := resp.Data["token/"].(map[string]interface{})["accessor"].(string)
-
- // Create an entity and attach an alias to it
- resp, err = c.identityStore.HandleRequest(namespace.RootContext(nil), &logical.Request{
- Path: "entity-alias",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "mount_accessor": tokenMountAccessor,
- "name": "testaliasname",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- aliasID := resp.Data["id"].(string)
-
- // Create another entity without an alias
- resp, err = c.identityStore.HandleRequest(namespace.RootContext(nil), &logical.Request{
- Path: "entity",
- Operation: logical.UpdateOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- entityID2 := resp.Data["id"].(string)
-
- // Set the second entity ID as the canonical ID for the previous alias,
- // initiating an alias transfer
- resp, err = c.identityStore.HandleRequest(namespace.RootContext(nil), &logical.Request{
- Path: "entity-alias/id/" + aliasID,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "canonical_id": entityID2,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- // Read the new entity
- resp, err = c.identityStore.HandleRequest(namespace.RootContext(nil), &logical.Request{
- Path: "entity/id/" + entityID2,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- // Ensure that there is only one alias
- aliases := resp.Data["aliases"].([]interface{})
- if len(aliases) != 1 {
- t.Fatalf("bad: length of aliases; expected: %d, actual: %d", 1, len(aliases))
- }
-
- // Ensure that no merging activity has taken place
- if len(aliases[0].(map[string]interface{})["merged_from_canonical_ids"].([]string)) != 0 {
- t.Fatalf("expected no merging to take place")
- }
-}
-
-func TestIdentityStore_CaseInsensitiveEntityAliasName(t *testing.T) {
- ctx := namespace.RootContext(nil)
- i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- // Create an entity
- resp, err := i.HandleRequest(ctx, &logical.Request{
- Path: "entity",
- Operation: logical.UpdateOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- entityID := resp.Data["id"].(string)
-
- testAliasName := "testAliasName"
- // Create a case sensitive alias name
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity-alias",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "mount_accessor": accessor,
- "canonical_id": entityID,
- "name": testAliasName,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- aliasID := resp.Data["id"].(string)
-
- // Ensure that reading the alias returns case sensitive alias name
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity-alias/id/" + aliasID,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- aliasName := resp.Data["name"].(string)
- if aliasName != testAliasName {
- t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName)
- }
-
- // Overwrite the alias using lower cased alias name. This shouldn't error.
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity-alias/id/" + aliasID,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "mount_accessor": accessor,
- "canonical_id": entityID,
- "name": strings.ToLower(testAliasName),
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
-
- // Ensure that reading the alias returns lower cased alias name
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity-alias/id/" + aliasID,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- aliasName = resp.Data["name"].(string)
- if aliasName != strings.ToLower(testAliasName) {
- t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName)
- }
-
- // Ensure that there is one entity alias
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity-alias/id",
- Operation: logical.ListOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- if len(resp.Data["keys"].([]string)) != 1 {
- t.Fatalf("bad length of entity aliases; expected: 1, actual: %d", len(resp.Data["keys"].([]string)))
- }
-}
-
-// This test is required because MemDB does not take care of ensuring
-// uniqueness of indexes that are marked unique.
-func TestIdentityStore_AliasSameAliasNames(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- aliasData := map[string]interface{}{
- "name": "testaliasname",
- "mount_accessor": githubAccessor,
- }
-
- aliasReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity-alias",
- Data: aliasData,
- }
-
- // Register an alias
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- // Register another alias with same name
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp != nil {
- t.Fatalf("expected no response since this modification should be idempotent")
- }
-}
-
-func TestIdentityStore_MemDBAliasIndexes(t *testing.T) {
- var err error
-
- ctx := namespace.RootContext(nil)
- is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
- if is == nil {
- t.Fatal("failed to create test identity store")
- }
-
- validateMountResp := is.router.ValidateMountByAccessor(githubAccessor)
- if validateMountResp == nil {
- t.Fatal("failed to validate github auth mount")
- }
-
- entity := &identity.Entity{
- ID: "testentityid",
- Name: "testentityname",
- }
-
- entity.BucketKey = is.entityPacker.BucketKey(entity.ID)
-
- txn := is.db.Txn(true)
- defer txn.Abort()
- err = is.MemDBUpsertEntityInTxn(txn, entity)
- if err != nil {
- t.Fatal(err)
- }
- txn.Commit()
-
- alias := &identity.Alias{
- CanonicalID: entity.ID,
- ID: "testaliasid",
- MountAccessor: githubAccessor,
- MountType: validateMountResp.MountType,
- Name: "testaliasname",
- Metadata: map[string]string{
- "testkey1": "testmetadatavalue1",
- "testkey2": "testmetadatavalue2",
- },
- LocalBucketKey: is.localAliasPacker.BucketKey(entity.ID),
- }
-
- txn = is.db.Txn(true)
- defer txn.Abort()
- err = is.MemDBUpsertAliasInTxn(txn, alias, false)
- if err != nil {
- t.Fatal(err)
- }
- txn.Commit()
-
- aliasFetched, err := is.MemDBAliasByID("testaliasid", false, false)
- if err != nil {
- t.Fatal(err)
- }
-
- if !reflect.DeepEqual(alias, aliasFetched) {
- t.Fatalf("bad: mismatched aliases; expected: %#v\n actual: %#v\n", alias, aliasFetched)
- }
-
- aliasFetched, err = is.MemDBAliasByFactors(validateMountResp.MountAccessor, "testaliasname", false, false)
- if err != nil {
- t.Fatal(err)
- }
-
- if !reflect.DeepEqual(alias, aliasFetched) {
- t.Fatalf("bad: mismatched aliases; expected: %#v\n actual: %#v\n", alias, aliasFetched)
- }
-
- alias2 := &identity.Alias{
- CanonicalID: entity.ID,
- ID: "testaliasid2",
- MountAccessor: validateMountResp.MountAccessor,
- MountType: validateMountResp.MountType,
- Name: "testaliasname2",
- Metadata: map[string]string{
- "testkey1": "testmetadatavalue1",
- "testkey3": "testmetadatavalue3",
- },
- LocalBucketKey: is.localAliasPacker.BucketKey(entity.ID),
- }
-
- txn = is.db.Txn(true)
- defer txn.Abort()
- err = is.MemDBUpsertAliasInTxn(txn, alias2, false)
- if err != nil {
- t.Fatal(err)
- }
- err = is.MemDBDeleteAliasByIDInTxn(txn, "testaliasid", false)
- if err != nil {
- t.Fatal(err)
- }
- txn.Commit()
-
- aliasFetched, err = is.MemDBAliasByID("testaliasid", false, false)
- if err != nil {
- t.Fatal(err)
- }
-
- if aliasFetched != nil {
- t.Fatalf("expected a nil alias")
- }
-}
-
-func TestIdentityStore_AliasRegister(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- if is == nil {
- t.Fatal("failed to create test alias store")
- }
-
- aliasData := map[string]interface{}{
- "name": "testaliasname",
- "mount_accessor": githubAccessor,
- "metadata": []string{"organization=hashicorp", "team=vault"},
- }
-
- aliasReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity-alias",
- Data: aliasData,
- }
-
- // Register the alias
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- idRaw, ok := resp.Data["id"]
- if !ok {
- t.Fatalf("alias id not present in alias register response")
- }
-
- id := idRaw.(string)
- if id == "" {
- t.Fatalf("invalid alias id in alias register response")
- }
-
- entityIDRaw, ok := resp.Data["canonical_id"]
- if !ok {
- t.Fatalf("entity id not present in alias register response")
- }
-
- entityID := entityIDRaw.(string)
- if entityID == "" {
- t.Fatalf("invalid entity id in alias register response")
- }
-}
-
-func TestIdentityStore_AliasUpdate(t *testing.T) {
- ctx := namespace.RootContext(nil)
- is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- tests := []struct {
- name string
- createData map[string]interface{}
- updateData map[string]interface{}
- }{
- {
- name: "noop",
- createData: map[string]interface{}{
- "name": "noop",
- "mount_accessor": githubAccessor,
- "custom_metadata": map[string]string{
- "foo": "baz",
- "bar": "qux",
- },
- },
- updateData: map[string]interface{}{
- "name": "noop",
- "mount_accessor": githubAccessor,
- "custom_metadata": map[string]string{
- "foo": "baz",
- "bar": "qux",
- },
- },
- },
- {
- name: "no-custom-metadata",
- createData: map[string]interface{}{
- "name": "no-custom-metadata",
- "mount_accessor": githubAccessor,
- },
- updateData: map[string]interface{}{
- "name": "no-custom-metadata",
- "mount_accessor": githubAccessor,
- },
- },
- {
- name: "update-name-custom-metadata",
- createData: map[string]interface{}{
- "name": "update-name-custom-metadata",
- "mount_accessor": githubAccessor,
- "custom_metadata": make(map[string]string),
- },
- updateData: map[string]interface{}{
- "name": "update-name-custom-metadata-2",
- "mount_accessor": githubAccessor,
- "custom_metadata": map[string]string{
- "bar": "qux",
- },
- },
- },
- {
- name: "update-name",
- createData: map[string]interface{}{
- "name": "update-name",
- "mount_accessor": githubAccessor,
- "custom_metadata": map[string]string{
- "foo": "baz",
- },
- },
- updateData: map[string]interface{}{
- "name": "update-name-2",
- "mount_accessor": githubAccessor,
- "custom_metadata": map[string]string{
- "foo": "baz",
- },
- },
- },
- {
- name: "update-metadata",
- createData: map[string]interface{}{
- "name": "update-metadata",
- "mount_accessor": githubAccessor,
- "custom_metadata": map[string]string{
- "foo": "bar",
- },
- },
- updateData: map[string]interface{}{
- "name": "update-metadata",
- "mount_accessor": githubAccessor,
- "custom_metadata": map[string]string{
- "foo": "baz",
- "bar": "qux",
- },
- },
- },
- {
- name: "clear-metadata",
- createData: map[string]interface{}{
- "name": "clear",
- "mount_accessor": githubAccessor,
- "custom_metadata": map[string]string{
- "foo": "bar",
- },
- },
- updateData: map[string]interface{}{
- "name": "clear",
- "mount_accessor": githubAccessor,
- "custom_metadata": map[string]string{},
- },
- },
- {
- name: "only-metadata",
- createData: map[string]interface{}{
- "name": "only",
- "mount_accessor": githubAccessor,
- "custom_metadata": map[string]string{
- "foo": "bar",
- },
- },
- updateData: map[string]interface{}{
- "custom_metadata": map[string]string{
- "bar": "baz",
- },
- },
- },
- {
- name: "only-metadata-clear",
- createData: map[string]interface{}{
- "name": "only-clear",
- "mount_accessor": githubAccessor,
- "custom_metadata": map[string]string{
- "foo": "bar",
- },
- },
- updateData: map[string]interface{}{
- "custom_metadata": map[string]string{},
- },
- },
- {
- name: "only-metadata-none-before",
- createData: map[string]interface{}{
- "name": "no-metadata",
- "mount_accessor": githubAccessor,
- },
- updateData: map[string]interface{}{
- "custom_metadata": map[string]string{
- "foo": "bar",
- },
- },
- },
- }
-
- handleRequest := func(t *testing.T, req *logical.Request) *logical.Response {
- t.Helper()
- resp, err := is.HandleRequest(ctx, req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- return resp
- }
-
- respDefaults := map[string]interface{}{
- "custom_metadata": map[string]string{},
- }
-
- checkValues := func(t *testing.T, reqData, respData map[string]interface{}) {
- t.Helper()
- expected := make(map[string]interface{})
- for k := range reqData {
- expected[k] = reqData[k]
- }
-
- for k := range respDefaults {
- if _, ok := expected[k]; !ok {
- expected[k] = respDefaults[k]
- }
- }
-
- actual := make(map[string]interface{}, len(expected))
- for k := range expected {
- actual[k] = respData[k]
- }
-
- if !reflect.DeepEqual(expected, actual) {
- t.Fatalf("expected data %#v, actual %#v", expected, actual)
- }
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- resp := handleRequest(t, &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity-alias",
- Data: tt.createData,
- })
-
- readReq := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "entity-alias/id/" + resp.Data["id"].(string),
- }
-
- // check create
- resp = handleRequest(t, readReq)
- checkValues(t, tt.createData, resp.Data)
-
- // check update
- resp = handleRequest(t, &logical.Request{
- Operation: logical.UpdateOperation,
- Path: readReq.Path,
- Data: tt.updateData,
- })
- resp = handleRequest(t, readReq)
- checkValues(t, tt.updateData, resp.Data)
- })
- }
-}
-
-// Test to check that the alias cannot be updated with a new entity
-// which already has an alias for the mount on the alias to be updated
-func TestIdentityStore_AliasMove_DuplicateAccessor(t *testing.T) {
- var err error
- var resp *logical.Response
- ctx := namespace.RootContext(nil)
- is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- // Create 2 entities and 1 alias on each, against the same github mount
- resp, err = is.HandleRequest(ctx, &logical.Request{
- Path: "entity",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testentity1",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- entity1ID := resp.Data["id"].(string)
-
- alias1Data := map[string]interface{}{
- "name": "testaliasname1",
- "mount_accessor": githubAccessor,
- "canonical_id": entity1ID,
- }
-
- aliasReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity-alias",
- Data: alias1Data,
- }
-
- // This will create an alias against the requested entity
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- resp, err = is.HandleRequest(ctx, &logical.Request{
- Path: "entity",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testentity2",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- entity2ID := resp.Data["id"].(string)
-
- alias2Data := map[string]interface{}{
- "name": "testaliasname2",
- "mount_accessor": githubAccessor,
- "canonical_id": entity2ID,
- }
-
- aliasReq.Data = alias2Data
-
- // This will create an alias against the requested entity
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- alias2ID := resp.Data["id"].(string)
-
- // Attempt to update the second alias to point to the first entity
- updateData := map[string]interface{}{
- "canonical_id": entity1ID,
- }
-
- aliasReq.Data = updateData
- aliasReq.Path = "entity-alias/id/" + alias2ID
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil {
- t.Fatal(err)
- }
-
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected an error as alias on the github accessor exists for testentity1")
- }
-}
-
-// Test that the alias cannot be changed to a mount for which
-// the entity already has an alias
-func TestIdentityStore_AliasUpdate_DuplicateAccessor(t *testing.T) {
- var err error
- var resp *logical.Response
- ctx := namespace.RootContext(nil)
-
- is, ghAccessor, upAccessor, _ := testIdentityStoreWithGithubUserpassAuth(ctx, t)
-
- // Create 1 entity and 2 aliases on it, one for each mount
- resp, err = is.HandleRequest(ctx, &logical.Request{
- Path: "entity",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testentity",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- entityID := resp.Data["id"].(string)
-
- alias1Data := map[string]interface{}{
- "name": "testaliasname1",
- "mount_accessor": ghAccessor,
- "canonical_id": entityID,
- }
-
- aliasReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity-alias",
- Data: alias1Data,
- }
-
- // This will create an alias against the requested entity
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- alias2Data := map[string]interface{}{
- "name": "testaliasname2",
- "mount_accessor": upAccessor,
- "canonical_id": entityID,
- }
-
- aliasReq.Data = alias2Data
-
- // This will create an alias against the requested entity
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- alias2ID := resp.Data["id"].(string)
-
- // Attempt to update the userpass mount to point to the github mount
- updateData := map[string]interface{}{
- "mount_accessor": ghAccessor,
- }
-
- aliasReq.Data = updateData
- aliasReq.Path = "entity-alias/id/" + alias2ID
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil {
- t.Fatal(err)
- }
-
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected an error as an alias on the github accessor already exists for testentity")
- }
-}
-
-// Test that alias creation fails if an alias for the specified mount
-// and entity has already been created
-func TestIdentityStore_AliasCreate_DuplicateAccessor(t *testing.T) {
- var err error
- var resp *logical.Response
- ctx := namespace.RootContext(nil)
- is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- resp, err = is.HandleRequest(ctx, &logical.Request{
- Path: "entity",
- Operation: logical.UpdateOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- entityID := resp.Data["id"].(string)
-
- aliasData := map[string]interface{}{
- "name": "testaliasname",
- "mount_accessor": githubAccessor,
- "canonical_id": entityID,
- }
-
- aliasReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity-alias",
- Data: aliasData,
- }
-
- // This will create an alias against the requested entity
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- aliasData["name"] = "testaliasname2"
- aliasReq = &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity-alias",
- Data: aliasData,
- }
-
- // This will try to create a new alias with the same accessor and entity
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected an error as alias already exists for this accessor and entity")
- }
-}
-
-func TestIdentityStore_AliasUpdate_ByID(t *testing.T) {
- var err error
- var resp *logical.Response
- ctx := namespace.RootContext(nil)
- is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- updateData := map[string]interface{}{
- "name": "updatedaliasname",
- "mount_accessor": githubAccessor,
- }
-
- updateReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity-alias/id/invalidaliasid",
- Data: updateData,
- }
-
- // Try to update an non-existent alias
- resp, err = is.HandleRequest(ctx, updateReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected an error due to invalid alias id")
- }
-
- customMetadata := make(map[string]string)
- customMetadata["foo"] = "abc"
- registerData := map[string]interface{}{
- "name": "testaliasname",
- "mount_accessor": githubAccessor,
- "custom_metadata": customMetadata,
- }
-
- registerReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity-alias",
- Data: registerData,
- }
-
- resp, err = is.HandleRequest(ctx, registerReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- idRaw, ok := resp.Data["id"]
- if !ok {
- t.Fatalf("alias id not present in response")
- }
- id := idRaw.(string)
- if id == "" {
- t.Fatalf("invalid alias id")
- }
-
- updateReq.Path = "entity-alias/id/" + id
- resp, err = is.HandleRequest(ctx, updateReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- readReq := &logical.Request{
- Operation: logical.ReadOperation,
- Path: updateReq.Path,
- }
- resp, err = is.HandleRequest(ctx, readReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- if resp.Data["name"] != "updatedaliasname" {
- t.Fatalf("failed to update alias information; \n response data: %#v\n", resp.Data)
- }
- if !reflect.DeepEqual(resp.Data["custom_metadata"], customMetadata) {
- t.Fatalf("failed to update alias information; \n response data: %#v\n", resp.Data)
- }
-
- delete(registerReq.Data, "name")
-
- resp, err = is.HandleRequest(ctx, registerReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected error due to missing alias name")
- }
-
- registerReq.Data["name"] = "testaliasname"
- delete(registerReq.Data, "mount_accessor")
-
- resp, err = is.HandleRequest(ctx, registerReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected error due to missing mount accessor")
- }
-}
-
-func TestIdentityStore_AliasReadDelete(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- customMetadata := make(map[string]string)
- customMetadata["foo"] = "abc"
- registerData := map[string]interface{}{
- "name": "testaliasname",
- "mount_accessor": githubAccessor,
- "metadata": []string{"organization=hashicorp", "team=vault"},
- "custom_metadata": customMetadata,
- }
-
- registerReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity-alias",
- Data: registerData,
- }
-
- resp, err = is.HandleRequest(ctx, registerReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- idRaw, ok := resp.Data["id"]
- if !ok {
- t.Fatalf("alias id not present in response")
- }
- id := idRaw.(string)
- if id == "" {
- t.Fatalf("invalid alias id")
- }
-
- // Read it back using alias id
- aliasReq := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "entity-alias/id/" + id,
- }
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- if resp.Data["id"].(string) == "" ||
- resp.Data["canonical_id"].(string) == "" ||
- resp.Data["name"].(string) != registerData["name"] ||
- resp.Data["mount_type"].(string) != "github" || !reflect.DeepEqual(resp.Data["custom_metadata"], customMetadata) {
- t.Fatalf("bad: alias read response; \nexpected: %#v \nactual: %#v\n", registerData, resp.Data)
- }
-
- aliasReq.Operation = logical.DeleteOperation
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- aliasReq.Operation = logical.ReadOperation
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("bad: alias read response; expected: nil, actual: %#v\n", resp)
- }
-}
diff --git a/vault/identity_store_entities_test.go b/vault/identity_store_entities_test.go
deleted file mode 100644
index 11b8024f4..000000000
--- a/vault/identity_store_entities_test.go
+++ /dev/null
@@ -1,1313 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "fmt"
- "reflect"
- "sort"
- "strings"
- "testing"
-
- "github.com/hashicorp/go-uuid"
- credGithub "github.com/hashicorp/vault/builtin/credential/github"
- "github.com/hashicorp/vault/helper/identity"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/helper/strutil"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestIdentityStore_EntityDeleteGroupMembershipUpdate(t *testing.T) {
- i, _, _ := testIdentityStoreWithGithubAuth(namespace.RootContext(nil), t)
-
- // Create an entity
- resp, err := i.HandleRequest(namespace.RootContext(nil), &logical.Request{
- Path: "entity",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testentity",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- entityID := resp.Data["id"].(string)
-
- // Create a group
- resp, err = i.HandleRequest(namespace.RootContext(nil), &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testgroup",
- "member_entity_ids": []string{entityID},
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
-
- // Ensure that the group has entity ID as its member
- resp, err = i.HandleRequest(namespace.RootContext(nil), &logical.Request{
- Path: "group/name/testgroup",
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- expected := []string{entityID}
- actual := resp.Data["member_entity_ids"].([]string)
- if !reflect.DeepEqual(expected, actual) {
- t.Fatalf("bad: member entity ids; expected: %#v\nactual: %#v", expected, actual)
- }
-
- // Delete the entity
- resp, err = i.HandleRequest(namespace.RootContext(nil), &logical.Request{
- Path: "entity/name/testentity",
- Operation: logical.DeleteOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
-
- // Ensure that the group does not have entity ID as it's member anymore
- resp, err = i.HandleRequest(namespace.RootContext(nil), &logical.Request{
- Path: "group/name/testgroup",
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- expected = []string{}
- actual = resp.Data["member_entity_ids"].([]string)
- if !reflect.DeepEqual(expected, actual) {
- t.Fatalf("bad: member entity ids; expected: %#v\nactual: %#v", expected, actual)
- }
-}
-
-func TestIdentityStore_CaseInsensitiveEntityName(t *testing.T) {
- ctx := namespace.RootContext(nil)
- i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- testEntityName := "testEntityName"
-
- // Create an entity with case sensitive name
- resp, err := i.HandleRequest(ctx, &logical.Request{
- Path: "entity",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": testEntityName,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- entityID := resp.Data["id"].(string)
-
- // Lookup the entity by ID and check that name returned is case sensitive
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity/id/" + entityID,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- entityName := resp.Data["name"].(string)
- if entityName != testEntityName {
- t.Fatalf("bad entity name; expected: %q, actual: %q", testEntityName, entityName)
- }
-
- // Lookup the entity by case sensitive name
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity/name/" + testEntityName,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
- }
- entityName = resp.Data["name"].(string)
- if entityName != testEntityName {
- t.Fatalf("bad entity name; expected: %q, actual: %q", testEntityName, entityName)
- }
-
- // Lookup the entity by case insensitive name
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity/name/" + strings.ToLower(testEntityName),
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
- }
- entityName = resp.Data["name"].(string)
- if entityName != testEntityName {
- t.Fatalf("bad entity name; expected: %q, actual: %q", testEntityName, entityName)
- }
-
- // Ensure that there is only one entity
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity/name",
- Operation: logical.ListOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
- }
- if len(resp.Data["keys"].([]string)) != 1 {
- t.Fatalf("bad length of entities; expected: 1, actual: %d", len(resp.Data["keys"].([]string)))
- }
-}
-
-func TestIdentityStore_EntityByName(t *testing.T) {
- ctx := namespace.RootContext(nil)
- i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- // Create an entity using the "name" endpoint
- resp, err := i.HandleRequest(ctx, &logical.Request{
- Path: "entity/name/testentityname",
- Operation: logical.UpdateOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp == nil {
- t.Fatalf("expected a non-nil response")
- }
-
- // Test the read by name endpoint
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity/name/testentityname",
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp == nil || resp.Data["name"].(string) != "testentityname" {
- t.Fatalf("bad entity response: %#v", resp)
- }
-
- // Update entity metadata using the name endpoint
- entityMetadata := map[string]string{
- "foo": "bar",
- }
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity/name/testentityname",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "metadata": entityMetadata,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- // Check the updated result
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity/name/testentityname",
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp == nil || !reflect.DeepEqual(resp.Data["metadata"].(map[string]string), entityMetadata) {
- t.Fatalf("bad entity response: %#v", resp)
- }
-
- // Delete the entity using the name endpoint
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity/name/testentityname",
- Operation: logical.DeleteOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- // Check if deletion was successful
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity/name/testentityname",
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- // Create 2 entities
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity/name/testentityname",
- Operation: logical.UpdateOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp == nil {
- t.Fatalf("expected a non-nil response")
- }
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity/name/testentityname2",
- Operation: logical.UpdateOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp == nil {
- t.Fatalf("expected a non-nil response")
- }
-
- // List the entities by name
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity/name",
- Operation: logical.ListOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- expected := []string{"testentityname2", "testentityname"}
- sort.Strings(expected)
- actual := resp.Data["keys"].([]string)
- sort.Strings(actual)
- if !reflect.DeepEqual(expected, actual) {
- t.Fatalf("bad: entity list response; expected: %#v\nactual: %#v", expected, actual)
- }
-}
-
-func TestIdentityStore_EntityReadGroupIDs(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- entityReq := &logical.Request{
- Path: "entity",
- Operation: logical.UpdateOperation,
- }
-
- resp, err = i.HandleRequest(ctx, entityReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- entityID := resp.Data["id"].(string)
-
- groupReq := &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "member_entity_ids": []string{
- entityID,
- },
- },
- }
-
- resp, err = i.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- groupID := resp.Data["id"].(string)
-
- // Create another group with the above created group as its subgroup
-
- groupReq.Data = map[string]interface{}{
- "member_group_ids": []string{groupID},
- }
- resp, err = i.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- inheritedGroupID := resp.Data["id"].(string)
-
- lookupReq := &logical.Request{
- Path: "lookup/entity",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "type": "id",
- "id": entityID,
- },
- }
-
- resp, err = i.HandleRequest(ctx, lookupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- expected := []string{groupID, inheritedGroupID}
- actual := resp.Data["group_ids"].([]string)
- if !reflect.DeepEqual(expected, actual) {
- t.Fatalf("bad: group_ids; expected: %#v\nactual: %#v\n", expected, actual)
- }
-
- expected = []string{groupID}
- actual = resp.Data["direct_group_ids"].([]string)
- if !reflect.DeepEqual(expected, actual) {
- t.Fatalf("bad: direct_group_ids; expected: %#v\nactual: %#v\n", expected, actual)
- }
-
- expected = []string{inheritedGroupID}
- actual = resp.Data["inherited_group_ids"].([]string)
- if !reflect.DeepEqual(expected, actual) {
- t.Fatalf("bad: inherited_group_ids; expected: %#v\nactual: %#v\n", expected, actual)
- }
-}
-
-func TestIdentityStore_EntityCreateUpdate(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- entityData := map[string]interface{}{
- "name": "testentityname",
- "metadata": []string{"someusefulkey=someusefulvalue"},
- "policies": []string{"testpolicy1", "testpolicy2"},
- }
-
- entityReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity",
- Data: entityData,
- }
-
- // Create the entity
- resp, err = is.HandleRequest(ctx, entityReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- entityID := resp.Data["id"].(string)
-
- updateData := map[string]interface{}{
- // Set the entity ID here
- "id": entityID,
- "name": "updatedentityname",
- "metadata": []string{"updatedkey=updatedvalue"},
- "policies": []string{"updatedpolicy1", "updatedpolicy2"},
- }
- entityReq.Data = updateData
-
- // Update the entity
- resp, err = is.HandleRequest(ctx, entityReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- entityReq.Path = "entity/id/" + entityID
- entityReq.Operation = logical.ReadOperation
-
- // Read the entity
- resp, err = is.HandleRequest(ctx, entityReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- if resp.Data["id"] != entityID ||
- resp.Data["name"] != updateData["name"] ||
- !reflect.DeepEqual(resp.Data["policies"], updateData["policies"]) {
- t.Fatalf("bad: entity response after update; resp: %#v\n updateData: %#v\n", resp.Data, updateData)
- }
-}
-
-func TestIdentityStore_BatchDelete(t *testing.T) {
- ctx := namespace.RootContext(nil)
- is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- ids := make([]string, 10000)
- for i := 0; i < 10000; i++ {
- entityData := map[string]interface{}{
- "name": fmt.Sprintf("entity-%d", i),
- }
-
- entityReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity",
- Data: entityData,
- }
-
- // Create the entity
- resp, err := is.HandleRequest(ctx, entityReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- ids[i] = resp.Data["id"].(string)
- }
-
- deleteReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity/batch-delete",
- Data: map[string]interface{}{
- "entity_ids": ids,
- },
- }
-
- resp, err := is.HandleRequest(ctx, deleteReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- for _, entityID := range ids {
- // Read the entity
- resp, err := is.HandleRequest(ctx, &logical.Request{
- Operation: logical.ReadOperation,
- Path: "entity/id/" + entityID,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- if resp != nil {
- t.Fatal(resp)
- }
- }
-}
-
-func TestIdentityStore_CloneImmutability(t *testing.T) {
- alias := &identity.Alias{
- ID: "testaliasid",
- Name: "testaliasname",
- MergedFromCanonicalIDs: []string{"entityid1"},
- }
-
- entity := &identity.Entity{
- ID: "testentityid",
- Name: "testentityname",
- Aliases: []*identity.Alias{
- alias,
- },
- }
-
- clonedEntity, err := entity.Clone()
- if err != nil {
- t.Fatal(err)
- }
-
- // Modify entity
- entity.Aliases[0].ID = "invalidid"
-
- if clonedEntity.Aliases[0].ID == "invalidid" {
- t.Fatalf("cloned entity is mutated")
- }
-
- clonedAlias, err := alias.Clone()
- if err != nil {
- t.Fatal(err)
- }
-
- alias.MergedFromCanonicalIDs[0] = "invalidid"
-
- if clonedAlias.MergedFromCanonicalIDs[0] == "invalidid" {
- t.Fatalf("cloned alias is mutated")
- }
-}
-
-func TestIdentityStore_MemDBImmutability(t *testing.T) {
- var err error
- ctx := namespace.RootContext(nil)
- is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- validateMountResp := is.router.ValidateMountByAccessor(githubAccessor)
- if validateMountResp == nil {
- t.Fatal("failed to validate github auth mount")
- }
-
- alias1 := &identity.Alias{
- CanonicalID: "testentityid",
- ID: "testaliasid",
- MountAccessor: githubAccessor,
- MountType: validateMountResp.MountType,
- Name: "testaliasname",
- Metadata: map[string]string{
- "testkey1": "testmetadatavalue1",
- "testkey2": "testmetadatavalue2",
- },
- }
-
- entity := &identity.Entity{
- ID: "testentityid",
- Name: "testentityname",
- Metadata: map[string]string{
- "someusefulkey": "someusefulvalue",
- },
- Aliases: []*identity.Alias{
- alias1,
- },
- }
-
- entity.BucketKey = is.entityPacker.BucketKey(entity.ID)
-
- txn := is.db.Txn(true)
- defer txn.Abort()
-
- err = is.MemDBUpsertEntityInTxn(txn, entity)
- if err != nil {
- t.Fatal(err)
- }
-
- txn.Commit()
-
- entityFetched, err := is.MemDBEntityByID(entity.ID, true)
- if err != nil {
- t.Fatal(err)
- }
-
- // Modify the fetched entity outside of a transaction
- entityFetched.Aliases[0].ID = "invalidaliasid"
-
- entityFetched, err = is.MemDBEntityByID(entity.ID, false)
- if err != nil {
- t.Fatal(err)
- }
-
- if entityFetched.Aliases[0].ID == "invalidaliasid" {
- t.Fatal("memdb item is mutable outside of transaction")
- }
-}
-
-func TestIdentityStore_ContextCancel(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx, cancelFunc := context.WithCancel(namespace.RootContext(nil))
- is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- entityReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity",
- }
-
- expected := []string{}
- for i := 0; i < 10; i++ {
- resp, err = is.HandleRequest(ctx, entityReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- expected = append(expected, resp.Data["id"].(string))
- }
-
- listReq := &logical.Request{
- Operation: logical.ListOperation,
- Path: "entity/id",
- }
-
- cancelFunc()
- resp, err = is.HandleRequest(ctx, listReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- if resp.Warnings == nil || len(resp.Warnings) == 0 {
- t.Fatalf("expected warning for cancelled context. resp:%#v", resp)
- }
-}
-
-func TestIdentityStore_ListEntities(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- entityReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity",
- }
-
- expected := []string{}
- for i := 0; i < 10; i++ {
- resp, err = is.HandleRequest(ctx, entityReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- expected = append(expected, resp.Data["id"].(string))
- }
-
- listReq := &logical.Request{
- Operation: logical.ListOperation,
- Path: "entity/id",
- }
-
- resp, err = is.HandleRequest(ctx, listReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- actual := resp.Data["keys"].([]string)
-
- // Sort the operands for DeepEqual to work
- sort.Strings(actual)
- sort.Strings(expected)
- if !reflect.DeepEqual(expected, actual) {
- t.Fatalf("bad: listed entity IDs; expected: %#v\n actual: %#v\n", expected, actual)
- }
-}
-
-func TestIdentityStore_LoadingEntities(t *testing.T) {
- var resp *logical.Response
- // Add github credential factory to core config
- err := AddTestCredentialBackend("github", credGithub.Factory)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- c := TestCore(t)
- unsealKeys, token := TestCoreInit(t, c)
- for _, key := range unsealKeys {
- if _, err := TestCoreUnseal(c, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- if c.Sealed() {
- t.Fatal("should not be sealed")
- }
-
- meGH := &MountEntry{
- Table: credentialTableType,
- Path: "github/",
- Type: "github",
- Description: "github auth",
- namespace: namespace.RootNamespace,
- }
-
- // Mount UUID for github auth
- meGHUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- meGH.UUID = meGHUUID
-
- // Mount accessor for github auth
- githubAccessor, err := c.generateMountAccessor("github")
- if err != nil {
- panic(fmt.Sprintf("could not generate github accessor: %v", err))
- }
- meGH.Accessor = githubAccessor
-
- // Storage view for github auth
- ghView := NewBarrierView(c.barrier, credentialBarrierPrefix+meGH.UUID+"/")
-
- // Sysview for github auth
- ghSysview := c.mountEntrySysView(meGH)
-
- // Create new github auth credential backend
- ghAuth, _, err := c.newCredentialBackend(context.Background(), meGH, ghSysview, ghView)
- if err != nil {
- t.Fatal(err)
- }
-
- // Mount github auth
- err = c.router.Mount(ghAuth, "auth/github", meGH, ghView)
- if err != nil {
- t.Fatal(err)
- }
-
- // Identity store will be mounted by now, just fetch it from router
- identitystore := c.router.MatchingBackend(namespace.RootContext(nil), "identity/")
- if identitystore == nil {
- t.Fatalf("failed to fetch identity store from router")
- }
-
- is := identitystore.(*IdentityStore)
-
- registerData := map[string]interface{}{
- "name": "testentityname",
- "metadata": []string{"someusefulkey=someusefulvalue"},
- "policies": []string{"testpolicy1", "testpolicy2"},
- }
-
- registerReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity",
- Data: registerData,
- }
-
- ctx := namespace.RootContext(nil)
-
- // Register the entity
- resp, err = is.HandleRequest(ctx, registerReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- entityID := resp.Data["id"].(string)
-
- readReq := &logical.Request{
- Path: "entity/id/" + entityID,
- Operation: logical.ReadOperation,
- }
-
- // Ensure that entity is created
- resp, err = is.HandleRequest(ctx, readReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- if resp.Data["id"] != entityID {
- t.Fatalf("failed to read the created entity")
- }
-
- // Perform a seal/unseal cycle
- err = c.Seal(token)
- if err != nil {
- t.Fatalf("failed to seal core: %v", err)
- }
-
- if !c.Sealed() {
- t.Fatal("should be sealed")
- }
-
- for _, key := range unsealKeys {
- if _, err := TestCoreUnseal(c, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- if c.Sealed() {
- t.Fatal("should not be sealed")
- }
-
- // Check if the entity is restored
- resp, err = is.HandleRequest(ctx, readReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- if resp.Data["id"] != entityID {
- t.Fatalf("failed to read the created entity after a seal/unseal cycle")
- }
-}
-
-func TestIdentityStore_MemDBEntityIndexes(t *testing.T) {
- var err error
-
- ctx := namespace.RootContext(nil)
- is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- validateMountResp := is.router.ValidateMountByAccessor(githubAccessor)
- if validateMountResp == nil {
- t.Fatal("failed to validate github auth mount")
- }
-
- alias1 := &identity.Alias{
- CanonicalID: "testentityid",
- ID: "testaliasid",
- MountAccessor: githubAccessor,
- MountType: validateMountResp.MountType,
- Name: "testaliasname",
- Metadata: map[string]string{
- "testkey1": "testmetadatavalue1",
- "testkey2": "testmetadatavalue2",
- },
- }
-
- alias2 := &identity.Alias{
- CanonicalID: "testentityid",
- ID: "testaliasid2",
- MountAccessor: validateMountResp.MountAccessor,
- MountType: validateMountResp.MountType,
- Name: "testaliasname2",
- Metadata: map[string]string{
- "testkey2": "testmetadatavalue2",
- "testkey3": "testmetadatavalue3",
- },
- }
-
- entity := &identity.Entity{
- ID: "testentityid",
- Name: "testentityname",
- Metadata: map[string]string{
- "someusefulkey": "someusefulvalue",
- },
- Aliases: []*identity.Alias{
- alias1,
- alias2,
- },
- }
-
- entity.BucketKey = is.entityPacker.BucketKey(entity.ID)
-
- txn := is.db.Txn(true)
- defer txn.Abort()
- err = is.MemDBUpsertEntityInTxn(txn, entity)
- if err != nil {
- t.Fatal(err)
- }
- txn.Commit()
-
- // Fetch the entity using its ID
- entityFetched, err := is.MemDBEntityByID(entity.ID, false)
- if err != nil {
- t.Fatal(err)
- }
-
- if !reflect.DeepEqual(entity, entityFetched) {
- t.Fatalf("bad: mismatched entities; expected: %#v\n actual: %#v\n", entity, entityFetched)
- }
-
- // Fetch the entity using its name
- entityFetched, err = is.MemDBEntityByName(namespace.RootContext(nil), entity.Name, false)
- if err != nil {
- t.Fatal(err)
- }
-
- if !reflect.DeepEqual(entity, entityFetched) {
- t.Fatalf("entity mismatched entities; expected: %#v\n actual: %#v\n", entity, entityFetched)
- }
-
- txn = is.db.Txn(false)
- entitiesFetched, err := is.MemDBEntitiesByBucketKeyInTxn(txn, entity.BucketKey)
- if err != nil {
- t.Fatal(err)
- }
-
- if len(entitiesFetched) != 1 {
- t.Fatalf("bad: length of entities; expected: 1, actual: %d", len(entitiesFetched))
- }
-
- err = is.MemDBDeleteEntityByID(entity.ID)
- if err != nil {
- t.Fatal(err)
- }
-
- entityFetched, err = is.MemDBEntityByID(entity.ID, false)
- if err != nil {
- t.Fatal(err)
- }
-
- if entityFetched != nil {
- t.Fatalf("bad: entity; expected: nil, actual: %#v\n", entityFetched)
- }
-
- entityFetched, err = is.MemDBEntityByName(namespace.RootContext(nil), entity.Name, false)
- if err != nil {
- t.Fatal(err)
- }
-
- if entityFetched != nil {
- t.Fatalf("bad: entity; expected: nil, actual: %#v\n", entityFetched)
- }
-}
-
-func TestIdentityStore_EntityCRUD(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- registerData := map[string]interface{}{
- "name": "testentityname",
- "metadata": []string{"someusefulkey=someusefulvalue"},
- "policies": []string{"testpolicy1", "testpolicy1", "testpolicy2", "testpolicy2"},
- }
-
- registerReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity",
- Data: registerData,
- }
-
- // Register the entity
- resp, err = is.HandleRequest(ctx, registerReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- idRaw, ok := resp.Data["id"]
- if !ok {
- t.Fatalf("entity id not present in response")
- }
- id := idRaw.(string)
- if id == "" {
- t.Fatalf("invalid entity id")
- }
-
- readReq := &logical.Request{
- Path: "entity/id/" + id,
- Operation: logical.ReadOperation,
- }
-
- resp, err = is.HandleRequest(ctx, readReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- if resp.Data["id"] != id ||
- resp.Data["name"] != registerData["name"] ||
- !reflect.DeepEqual(resp.Data["policies"], strutil.RemoveDuplicates(registerData["policies"].([]string), false)) {
- t.Fatalf("bad: entity response")
- }
-
- updateData := map[string]interface{}{
- "name": "updatedentityname",
- "metadata": []string{"updatedkey=updatedvalue"},
- "policies": []string{"updatedpolicy1", "updatedpolicy2"},
- }
-
- updateReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity/id/" + id,
- Data: updateData,
- }
-
- resp, err = is.HandleRequest(ctx, updateReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- resp, err = is.HandleRequest(ctx, readReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- if resp.Data["id"] != id ||
- resp.Data["name"] != updateData["name"] ||
- !reflect.DeepEqual(resp.Data["policies"], updateData["policies"]) {
- t.Fatalf("bad: entity response after update; resp: %#v\n updateData: %#v\n", resp.Data, updateData)
- }
-
- deleteReq := &logical.Request{
- Path: "entity/id/" + id,
- Operation: logical.DeleteOperation,
- }
-
- resp, err = is.HandleRequest(ctx, deleteReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- resp, err = is.HandleRequest(ctx, readReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response; actual: %#v\n", resp)
- }
-}
-
-func TestIdentityStore_MergeEntitiesByID(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- is, githubAccessor, upAccessor, _ := testIdentityStoreWithGithubUserpassAuth(ctx, t)
-
- registerData := map[string]interface{}{
- "name": "testentityname2",
- "metadata": []string{"someusefulkey=someusefulvalue"},
- }
-
- registerData2 := map[string]interface{}{
- "name": "testentityname",
- "metadata": []string{"someusefulkey=someusefulvalue"},
- }
-
- aliasRegisterData1 := map[string]interface{}{
- "name": "testaliasname1",
- "mount_accessor": githubAccessor,
- "metadata": []string{"organization=hashicorp", "team=vault"},
- }
-
- aliasRegisterData2 := map[string]interface{}{
- "name": "testaliasname2",
- "mount_accessor": upAccessor,
- "metadata": []string{"organization=hashicorp", "team=vault"},
- }
-
- registerReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity",
- Data: registerData,
- }
-
- // Register the entity
- resp, err = is.HandleRequest(ctx, registerReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- entityID1 := resp.Data["id"].(string)
-
- // Set entity ID in alias registration data and register alias
- aliasRegisterData1["entity_id"] = entityID1
-
- aliasReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "alias",
- Data: aliasRegisterData1,
- }
-
- // Register the alias
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- entity1, err := is.MemDBEntityByID(entityID1, false)
- if err != nil {
- t.Fatal(err)
- }
- if entity1 == nil {
- t.Fatalf("failed to create entity: %v", err)
- }
- if len(entity1.Aliases) != 1 {
- t.Fatalf("bad: number of aliases in entity; expected: 1, actual: %d", len(entity1.Aliases))
- }
-
- entity1GroupReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "group",
- Data: map[string]interface{}{
- "member_entity_ids": entityID1,
- },
- }
- resp, err = is.HandleRequest(ctx, entity1GroupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- entity1GroupID := resp.Data["id"].(string)
-
- registerReq.Data = registerData2
- // Register another entity
- resp, err = is.HandleRequest(ctx, registerReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- entityID2 := resp.Data["id"].(string)
-
- aliasRegisterData2["entity_id"] = entityID2
-
- aliasReq.Data = aliasRegisterData2
-
- // Register the alias
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- entity2, err := is.MemDBEntityByID(entityID2, false)
- if err != nil {
- t.Fatal(err)
- }
- if entity2 == nil {
- t.Fatalf("failed to create entity: %v", err)
- }
-
- if len(entity2.Aliases) != 1 {
- t.Fatalf("bad: number of aliases in entity; expected: 1, actual: %d", len(entity2.Aliases))
- }
-
- entity2GroupReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "group",
- Data: map[string]interface{}{
- "member_entity_ids": entityID2,
- },
- }
- resp, err = is.HandleRequest(ctx, entity2GroupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- entity2GroupID := resp.Data["id"].(string)
-
- mergeData := map[string]interface{}{
- "to_entity_id": entityID1,
- "from_entity_ids": []string{entityID2},
- }
- mergeReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity/merge",
- Data: mergeData,
- }
-
- resp, err = is.HandleRequest(ctx, mergeReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- entityReq := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "entity/id/" + entityID2,
- }
- resp, err = is.HandleRequest(ctx, entityReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("entity should have been deleted")
- }
-
- entityReq.Path = "entity/id/" + entityID1
- resp, err = is.HandleRequest(ctx, entityReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- entity1Aliases := resp.Data["aliases"].([]interface{})
- if len(entity1Aliases) != 2 {
- t.Fatalf("bad: number of aliases in entity; expected: 2, actual: %d", len(entity1Aliases))
- }
-
- githubAliases := 0
- for _, aliasRaw := range entity1Aliases {
- alias := aliasRaw.(map[string]interface{})
- aliasLookedUp, err := is.MemDBAliasByID(alias["id"].(string), false, false)
- if err != nil {
- t.Fatal(err)
- }
- if aliasLookedUp == nil {
- t.Fatalf("index for alias id %q is not updated", alias["id"].(string))
- }
- if aliasLookedUp.MountAccessor == githubAccessor {
- githubAliases += 1
- }
- }
-
- // Test that only 1 alias for the githubAccessor is present in the merged entity,
- // as the github alias on entity2 should've been skipped in the merge
- if githubAliases != 1 {
- t.Fatalf("Unexcepted number of github aliases in merged entity; expected: 1, actual: %d", githubAliases)
- }
-
- entity1Groups := resp.Data["direct_group_ids"].([]string)
- if len(entity1Groups) != 2 {
- t.Fatalf("bad: number of groups in entity; expected: 2, actual: %d", len(entity1Groups))
- }
-
- for _, group := range []string{entity1GroupID, entity2GroupID} {
- if !strutil.StrListContains(entity1Groups, group) {
- t.Fatalf("group id %q not found in merged entity direct groups %q", group, entity1Groups)
- }
-
- groupLookedUp, err := is.MemDBGroupByID(group, false)
- if err != nil {
- t.Fatal(err)
- }
- expectedEntityIDs := []string{entity1.ID}
- if !strutil.EquivalentSlices(groupLookedUp.MemberEntityIDs, expectedEntityIDs) {
- t.Fatalf("group id %q should contain %q but contains %q", group, expectedEntityIDs, groupLookedUp.MemberEntityIDs)
- }
- }
-}
-
-func TestIdentityStore_MergeEntitiesByID_DuplicateFromEntityIDs(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- // Register the entity
- registerReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity",
- Data: map[string]interface{}{
- "name": "testentityname2",
- "metadata": []string{"someusefulkey=someusefulvalue"},
- "policies": []string{"testPolicy1", "testPolicy1", "testPolicy2"},
- },
- }
-
- resp, err = is.HandleRequest(ctx, registerReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- entityID1 := resp.Data["id"].(string)
- entity1, err := is.MemDBEntityByID(entityID1, false)
- if err != nil {
- t.Fatal(err)
- }
- if entity1 == nil {
- t.Fatalf("failed to create entity: %v", err)
- }
-
- // Register another entity
- registerReq.Data = map[string]interface{}{
- "name": "testentityname",
- "metadata": []string{"someusefulkey=someusefulvalue"},
- }
-
- resp, err = is.HandleRequest(ctx, registerReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- entityID2 := resp.Data["id"].(string)
-
- aliasReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "alias",
- Data: map[string]interface{}{
- "name": "testaliasname1",
- "mount_accessor": githubAccessor,
- "metadata": []string{"organization=hashicorp", "team=vault"},
- "entity_id": entityID2,
- "policies": []string{"testPolicy1", "testPolicy1", "testPolicy2"},
- },
- }
-
- // Register the alias
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- entity2, err := is.MemDBEntityByID(entityID2, false)
- if err != nil {
- t.Fatal(err)
- }
- if entity2 == nil {
- t.Fatalf("failed to create entity: %v", err)
- }
- if len(entity2.Aliases) != 1 {
- t.Fatalf("bad: number of aliases in entity; expected: 1, actual: %d", len(entity2.Aliases))
- }
-
- mergeReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity/merge",
- Data: map[string]interface{}{
- "to_entity_id": entityID1,
- "from_entity_ids": []string{entityID2, entityID2},
- },
- }
-
- resp, err = is.HandleRequest(ctx, mergeReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- entityReq := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "entity/id/" + entityID2,
- }
- resp, err = is.HandleRequest(ctx, entityReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("entity should have been deleted")
- }
-
- entityReq.Path = "entity/id/" + entityID1
- resp, err = is.HandleRequest(ctx, entityReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- entity1Lookup, err := is.MemDBEntityByID(entityID1, false)
- if err != nil {
- t.Fatal(err)
- }
- if entity1Lookup == nil {
- t.Fatalf("failed to create entity: %v", err)
- }
-
- if len(entity1Lookup.Aliases) != 1 {
- t.Fatalf("bad: number of aliases in entity; expected: 1, actual: %d", len(entity1Lookup.Aliases))
- }
-
- if len(entity1Lookup.Policies) != 2 {
- t.Fatalf("invalid number of entity policies; expected: 2, actualL: %d", len(entity1Lookup.Policies))
- }
-}
diff --git a/vault/identity_store_group_aliases_test.go b/vault/identity_store_group_aliases_test.go
deleted file mode 100644
index 300d69141..000000000
--- a/vault/identity_store_group_aliases_test.go
+++ /dev/null
@@ -1,588 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "strings"
- "testing"
-
- credLdap "github.com/hashicorp/vault/builtin/credential/ldap"
- credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
- "github.com/hashicorp/vault/helper/identity"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/kr/pretty"
-)
-
-func TestIdentityStore_CaseInsensitiveGroupAliasName(t *testing.T) {
- ctx := namespace.RootContext(nil)
- i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- // Create a group
- resp, err := i.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "type": "external",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
- }
- groupID := resp.Data["id"].(string)
-
- testAliasName := "testAliasName"
-
- // Create a case sensitive alias name
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group-alias",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "mount_accessor": accessor,
- "canonical_id": groupID,
- "name": testAliasName,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- aliasID := resp.Data["id"].(string)
-
- // Ensure that reading the alias returns case sensitive alias name
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group-alias/id/" + aliasID,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- aliasName := resp.Data["name"].(string)
- if aliasName != testAliasName {
- t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName)
- }
-
- // Overwrite the alias using lower cased alias name. This shouldn't error.
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group-alias/id/" + aliasID,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "mount_accessor": accessor,
- "canonical_id": groupID,
- "name": strings.ToLower(testAliasName),
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
-
- // Ensure that reading the alias returns lower cased alias name
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group-alias/id/" + aliasID,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- aliasName = resp.Data["name"].(string)
- if aliasName != strings.ToLower(testAliasName) {
- t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName)
- }
-}
-
-func TestIdentityStore_EnsureNoDanglingGroupAlias(t *testing.T) {
- err := AddTestCredentialBackend("userpass", credUserpass.Factory)
- if err != nil {
- t.Fatal(err)
- }
-
- err = AddTestCredentialBackend("ldap", credLdap.Factory)
- if err != nil {
- t.Fatal(err)
- }
-
- c, _, _ := TestCoreUnsealed(t)
-
- ctx := namespace.RootContext(nil)
-
- userpassMe := &MountEntry{
- Table: credentialTableType,
- Path: "userpass/",
- Type: "userpass",
- Description: "userpass",
- }
- err = c.enableCredential(ctx, userpassMe)
- if err != nil {
- t.Fatal(err)
- }
-
- ldapMe := &MountEntry{
- Table: credentialTableType,
- Path: "ldap/",
- Type: "ldap",
- Description: "ldap",
- }
- err = c.enableCredential(ctx, ldapMe)
- if err != nil {
- t.Fatal(err)
- }
-
- // Create a group
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "type": "external",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
- }
- groupID := resp.Data["id"].(string)
-
- // Add an alias to the group from the userpass auth method
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group-alias",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testgroupalias",
- "mount_accessor": userpassMe.Accessor,
- "canonical_id": groupID,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
- }
- userpassGroupAliasID := resp.Data["id"].(string)
-
- // Ensure that the alias is readable
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group-alias/id/" + userpassGroupAliasID,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
- }
- if resp == nil || resp.Data["id"].(string) != userpassGroupAliasID {
- t.Fatalf("failed to read userpass group alias")
- }
-
- // Attach a different alias to the same group, overriding the previous one
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group-alias",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testgroupalias",
- "mount_accessor": ldapMe.Accessor,
- "canonical_id": groupID,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
- }
- ldapGroupAliasID := resp.Data["id"].(string)
-
- // Ensure that the new alias is readable
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group-alias/id/" + ldapGroupAliasID,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
- }
- if resp == nil || resp.Data["id"].(string) != ldapGroupAliasID {
- t.Fatalf("failed to read ldap group alias")
- }
-
- // Ensure previous alias is gone
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group-alias/id/" + userpassGroupAliasID,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-}
-
-func TestIdentityStore_GroupAliasDeletionOnGroupDeletion(t *testing.T) {
- var resp *logical.Response
- var err error
-
- ctx := namespace.RootContext(nil)
- i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "type": "external",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
- }
- groupID := resp.Data["id"].(string)
-
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group-alias",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testgroupalias",
- "mount_accessor": accessor,
- "canonical_id": groupID,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
- }
- groupAliasID := resp.Data["id"].(string)
-
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/id/" + groupID,
- Operation: logical.DeleteOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group-alias/id/" + groupAliasID,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-}
-
-func TestIdentityStore_GroupAliases_CRUD(t *testing.T) {
- var resp *logical.Response
- var err error
- ctx := namespace.RootContext(nil)
- i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- groupReq := &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "type": "external",
- },
- }
- resp, err = i.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
- }
- groupID := resp.Data["id"].(string)
-
- groupAliasReq := &logical.Request{
- Path: "group-alias",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testgroupalias",
- "mount_accessor": accessor,
- "canonical_id": groupID,
- "mount_type": "ldap",
- },
- }
- resp, err = i.HandleRequest(ctx, groupAliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
- }
- groupAliasID := resp.Data["id"].(string)
-
- groupAliasReq.Path = "group-alias/id/" + groupAliasID
- groupAliasReq.Operation = logical.ReadOperation
- resp, err = i.HandleRequest(ctx, groupAliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
- }
-
- if resp.Data["id"].(string) != groupAliasID {
- t.Fatalf("bad: group alias: %#v\n", resp.Data)
- }
-
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group-alias/id/" + groupAliasID,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testupdatedgroupaliasname",
- "mount_accessor": accessor,
- "canonical_id": groupID,
- "mount_type": "ldap",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v; resp: %#v", err, resp)
- }
- if resp.Data["id"].(string) != groupAliasID {
- t.Fatalf("bad: group alias: %#v\n", resp.Data)
- }
-
- groupAliasReq.Operation = logical.DeleteOperation
- resp, err = i.HandleRequest(ctx, groupAliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
- }
-
- groupAliasReq.Operation = logical.ReadOperation
- resp, err = i.HandleRequest(ctx, groupAliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
- }
-
- if resp != nil {
- t.Fatalf("failed to delete group alias")
- }
-}
-
-func TestIdentityStore_GroupAliases_MemDBIndexes(t *testing.T) {
- var err error
- ctx := namespace.RootContext(nil)
- i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- group := &identity.Group{
- ID: "testgroupid",
- Name: "testgroupname",
- Metadata: map[string]string{
- "testmetadatakey1": "testmetadatavalue1",
- "testmetadatakey2": "testmetadatavalue2",
- },
- Alias: &identity.Alias{
- ID: "testgroupaliasid",
- Name: "testalias",
- MountAccessor: accessor,
- CanonicalID: "testgroupid",
- MountType: "ldap",
- },
- ParentGroupIDs: []string{"testparentgroupid1", "testparentgroupid2"},
- MemberEntityIDs: []string{"testentityid1", "testentityid2"},
- Policies: []string{"testpolicy1", "testpolicy2"},
- BucketKey: i.groupPacker.BucketKey("testgroupid"),
- }
-
- txn := i.db.Txn(true)
- defer txn.Abort()
- err = i.MemDBUpsertAliasInTxn(txn, group.Alias, true)
- if err != nil {
- t.Fatal(err)
- }
- err = i.MemDBUpsertGroupInTxn(txn, group)
- if err != nil {
- t.Fatal(err)
- }
- txn.Commit()
-
- alias, err := i.MemDBAliasByID("testgroupaliasid", false, true)
- if err != nil {
- t.Fatal(err)
- }
- if alias.ID != "testgroupaliasid" {
- t.Fatalf("bad: group alias: %#v\n", alias)
- }
-
- group, err = i.MemDBGroupByAliasID("testgroupaliasid", false)
- if err != nil {
- t.Fatal(err)
- }
- if group.ID != "testgroupid" {
- t.Fatalf("bad: group: %#v\n", group)
- }
-
- aliasByFactors, err := i.MemDBAliasByFactors(group.Alias.MountAccessor, group.Alias.Name, false, true)
- if err != nil {
- t.Fatal(err)
- }
- if aliasByFactors.ID != "testgroupaliasid" {
- t.Fatalf("bad: group alias: %#v\n", aliasByFactors)
- }
-}
-
-func TestIdentityStore_GroupAliases_AliasOnInternalGroup(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- groupReq := &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- }
- resp, err = i.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v; err: %v", resp, err)
- }
- groupID := resp.Data["id"].(string)
-
- aliasReq := &logical.Request{
- Path: "group-alias",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testname",
- "mount_accessor": accessor,
- "canonical_id": groupID,
- },
- }
- resp, err = i.HandleRequest(ctx, aliasReq)
- if err != nil {
- t.Fatal(err)
- }
- if !resp.IsError() {
- t.Fatalf("expected an error")
- }
-}
-
-func TestIdentityStore_GroupAliasesUpdate(t *testing.T) {
- ctx := namespace.RootContext(nil)
- i, accessor1, c := testIdentityStoreWithGithubAuth(ctx, t)
-
- ghme2 := &MountEntry{
- Table: credentialTableType,
- Path: "github2/",
- Type: "github",
- Description: "github auth",
- }
-
- err := c.enableCredential(ctx, ghme2)
- if err != nil {
- t.Fatal(err)
- }
- accessor2 := ghme2.Accessor
-
- // Create two groups
- resp, err := i.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "type": "external",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
- }
- groupID1 := resp.Data["id"].(string)
-
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "type": "external",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
- }
- groupID2 := resp.Data["id"].(string)
-
- // Create an alias
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group-alias",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "mount_accessor": accessor1,
- "canonical_id": groupID1,
- "name": "testalias",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- aliasID := resp.Data["id"].(string)
-
- // Ensure that reading the alias returns the right group
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group-alias/id/" + aliasID,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- aliasName := resp.Data["name"].(string)
- if aliasName != "testalias" {
- t.Fatalf("bad alias name; expected: testalias, actual: %q", aliasName)
- }
- mountAccessor := resp.Data["mount_accessor"].(string)
- if mountAccessor != accessor1 {
- t.Fatalf("bad mount accessor; expected: %q, actual: %q", accessor1, mountAccessor)
- }
- canonicalID := resp.Data["canonical_id"].(string)
- if canonicalID != groupID1 {
- t.Fatalf("bad canonical name; expected: %q, actual: %q", groupID1, canonicalID)
- }
- // Ensure that reading the group returns the alias
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/id/" + groupID1,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- aliasName = resp.Data["alias"].(map[string]interface{})["name"].(string)
- if aliasName != "testalias" {
- t.Fatalf("bad alias name; expected: testalias, actual: %q", aliasName)
- }
-
- // Overwrite the alias
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group-alias/id/" + aliasID,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "mount_accessor": accessor2,
- "canonical_id": groupID2,
- "name": "testalias2",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
-
- // Ensure that reading the alias returns the right group
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group-alias/id/" + aliasID,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- aliasName = resp.Data["name"].(string)
- if aliasName != "testalias2" {
- t.Fatalf("bad alias name; expected: testalias2, actual: %q", aliasName)
- }
- mountAccessor = resp.Data["mount_accessor"].(string)
- if mountAccessor != accessor2 {
- t.Fatalf("bad mount accessor; expected: %q, actual: %q", accessor2, mountAccessor)
- }
- canonicalID = resp.Data["canonical_id"].(string)
- if canonicalID != groupID2 {
- t.Fatalf("bad canonical name; expected: %q, actual: %q", groupID2, canonicalID)
- }
- // Ensure that reading the group returns the alias
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/id/" + groupID2,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- aliasName = resp.Data["alias"].(map[string]interface{})["name"].(string)
- if aliasName != "testalias2" {
- t.Fatalf("bad alias name; expected: testalias, actual: %q", aliasName)
- }
- // Ensure the old group does not
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/id/" + groupID1,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- aliasInfo := resp.Data["alias"].(map[string]interface{})
- if len(aliasInfo) > 0 {
- t.Fatalf("still found alias with old group: %s", pretty.Sprint(resp.Data))
- }
-}
diff --git a/vault/identity_store_groups_test.go b/vault/identity_store_groups_test.go
deleted file mode 100644
index 2aadbef55..000000000
--- a/vault/identity_store_groups_test.go
+++ /dev/null
@@ -1,1486 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "fmt"
- "reflect"
- "sort"
- "strings"
- "testing"
-
- "github.com/go-test/deep"
- "github.com/hashicorp/vault/helper/identity"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestIdentityStore_Groups_AddByNameEntityUpdate(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
-
- // Create an entity and get its ID
- entityRegisterReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity",
- }
- resp, err := c.identityStore.HandleRequest(ctx, entityRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- entityID := resp.Data["id"].(string)
-
- // Create a group containing the entity
- groupName := "group-name"
- expectedMemberEntityIDs := []string{entityID}
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": groupName,
- "member_entity_ids": expectedMemberEntityIDs,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
- }
-
- // Remove the entity from the group
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": groupName,
- "member_entity_ids": []string{},
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
- }
-
- // Make sure the member no longer thinks it's in the group
- entityIDReq := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "entity/id/" + entityID,
- }
- resp, err = c.identityStore.HandleRequest(ctx, entityIDReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- expectedGroupIDs := []string{}
- actualGroupIDs := resp.Data["direct_group_ids"]
- if !reflect.DeepEqual(expectedGroupIDs, actualGroupIDs) {
- t.Fatalf("bad: direct_group_ids:\nexpected: %#v\nactual: %#v", expectedGroupIDs, actualGroupIDs)
- }
-}
-
-func TestIdentityStore_FixOverwrittenMemberGroupIDs(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
-
- // Create a group
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "group1",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
- }
-
- groupID := resp.Data["id"].(string)
-
- expectedMemberGroupIDs := []string{groupID}
-
- // Create another group and add the above created group as its member
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "group2",
- "policies": "default",
- "member_group_ids": expectedMemberGroupIDs,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
- }
-
- // Create another group and add the above created group as its member
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group/name/group2",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "policies": "default,another-policy",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
- }
-
- // Read the group and check if the member_group_ids is intact
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group/name/group2",
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
- }
-
- if !reflect.DeepEqual(resp.Data["member_group_ids"], expectedMemberGroupIDs) {
- t.Fatalf("bad: member_group_ids; expected: %#v\n, actual: %#v", expectedMemberGroupIDs, resp.Data["member_group_ids"])
- }
-}
-
-func TestIdentityStore_GroupEntityMembershipUpgrade(t *testing.T) {
- c, keys, rootToken := TestCoreUnsealed(t)
-
- // Create a group
- resp, err := c.identityStore.HandleRequest(namespace.RootContext(nil), &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testgroup",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
-
- // Create a memdb transaction
- txn := c.identityStore.db.Txn(true)
- defer txn.Abort()
-
- // Fetch the above created group
- group, err := c.identityStore.MemDBGroupByNameInTxn(namespace.RootContext(nil), txn, "testgroup", true)
- if err != nil {
- t.Fatal(err)
- }
-
- // Manually add an invalid entity as the group's member
- group.MemberEntityIDs = []string{"invalidentityid"}
-
- ctx := namespace.RootContext(nil)
-
- // Persist the group
- err = c.identityStore.UpsertGroupInTxn(ctx, txn, group, true)
- if err != nil {
- t.Fatal(err)
- }
-
- txn.Commit()
-
- // Perform seal and unseal forcing an upgrade
- err = c.Seal(rootToken)
- if err != nil {
- t.Fatal(err)
- }
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c, key)
- if err != nil {
- t.Fatal(err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("failed to unseal")
- }
- }
-
- // Read the group and ensure that invalid entity id is cleaned up
- group, err = c.identityStore.MemDBGroupByName(namespace.RootContext(nil), "testgroup", false)
- if err != nil {
- t.Fatal(err)
- }
-
- if len(group.MemberEntityIDs) != 0 {
- t.Fatalf("bad: member entity IDs; expected: none, actual: %#v", group.MemberEntityIDs)
- }
-}
-
-func TestIdentityStore_MemberGroupIDDelete(t *testing.T) {
- ctx := namespace.RootContext(nil)
- i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- // Create a child group
- resp, err := i.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "child",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- childGroupID := resp.Data["id"].(string)
-
- // Create a parent group with the above group ID as its child
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "parent",
- "member_group_ids": []string{childGroupID},
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- // Ensure that member group ID is properly updated
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/name/parent",
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- memberGroupIDs := resp.Data["member_group_ids"].([]string)
- if len(memberGroupIDs) != 1 && memberGroupIDs[0] != childGroupID {
- t.Fatalf("bad: member group ids; expected: %#v, actual: %#v", []string{childGroupID}, memberGroupIDs)
- }
-
- // Clear the member group IDs from the parent group
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/name/parent",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "member_group_ids": []string{},
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- // Ensure that member group ID is properly deleted
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/name/parent",
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- memberGroupIDs = resp.Data["member_group_ids"].([]string)
- if len(memberGroupIDs) != 0 {
- t.Fatalf("bad: length of member group ids; expected: %d, actual: %d", 0, len(memberGroupIDs))
- }
-}
-
-func TestIdentityStore_CaseInsensitiveGroupName(t *testing.T) {
- ctx := namespace.RootContext(nil)
- i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- testGroupName := "testGroupName"
-
- // Create an group with case sensitive name
- resp, err := i.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": testGroupName,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- groupID := resp.Data["id"].(string)
-
- // Lookup the group by ID and check that name returned is case sensitive
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/id/" + groupID,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
- }
- groupName := resp.Data["name"].(string)
- if groupName != testGroupName {
- t.Fatalf("bad group name; expected: %q, actual: %q", testGroupName, groupName)
- }
-
- // Lookup the group by case sensitive name
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/name/" + testGroupName,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
- }
- groupName = resp.Data["name"].(string)
- if groupName != testGroupName {
- t.Fatalf("bad group name; expected: %q, actual: %q", testGroupName, groupName)
- }
-
- // Lookup the group by case insensitive name
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/name/" + strings.ToLower(testGroupName),
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
- }
- groupName = resp.Data["name"].(string)
- if groupName != testGroupName {
- t.Fatalf("bad group name; expected: %q, actual: %q", testGroupName, groupName)
- }
-
- // Ensure that there is only one group
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/name",
- Operation: logical.ListOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
- }
- if len(resp.Data["keys"].([]string)) != 1 {
- t.Fatalf("bad length of groups; expected: 1, actual: %d", len(resp.Data["keys"].([]string)))
- }
-}
-
-func TestIdentityStore_GroupByName(t *testing.T) {
- ctx := namespace.RootContext(nil)
- i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- // Create an entity using the "name" endpoint
- resp, err := i.HandleRequest(ctx, &logical.Request{
- Path: "group/name/testgroupname",
- Operation: logical.UpdateOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp == nil {
- t.Fatalf("expected a non-nil response")
- }
-
- // Test the read by name endpoint
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/name/testgroupname",
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp == nil || resp.Data["name"].(string) != "testgroupname" {
- t.Fatalf("bad entity response: %#v", resp)
- }
-
- // Update group metadata using the name endpoint
- groupMetadata := map[string]string{
- "foo": "bar",
- }
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/name/testgroupname",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "metadata": groupMetadata,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- // Check the updated result
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/name/testgroupname",
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp == nil || !reflect.DeepEqual(resp.Data["metadata"].(map[string]string), groupMetadata) {
- t.Fatalf("bad group response: %#v", resp)
- }
-
- // Delete the group using the name endpoint
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/name/testgroupname",
- Operation: logical.DeleteOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- // Check if deletion was successful
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/name/testgroupname",
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- // Create 2 entities
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/name/testgroupname",
- Operation: logical.UpdateOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp == nil {
- t.Fatalf("expected a non-nil response")
- }
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/name/testgroupname2",
- Operation: logical.UpdateOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp == nil {
- t.Fatalf("expected a non-nil response")
- }
-
- // List the entities by name
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "group/name",
- Operation: logical.ListOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- expected := []string{"testgroupname2", "testgroupname"}
- sort.Strings(expected)
- actual := resp.Data["keys"].([]string)
- sort.Strings(actual)
- if !reflect.DeepEqual(expected, actual) {
- t.Fatalf("bad: group list response; expected: %#v\nactual: %#v", expected, actual)
- }
-}
-
-func TestIdentityStore_Groups_TypeMembershipAdditions(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
- groupReq := &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "type": "external",
- "member_entity_ids": "sampleentityid",
- },
- }
-
- resp, err = i.HandleRequest(ctx, groupReq)
- if err != nil {
- t.Fatal(err)
- }
- if !resp.IsError() {
- t.Fatalf("expected an error")
- }
-
- groupReq.Data = map[string]interface{}{
- "type": "external",
- "member_group_ids": "samplegroupid",
- }
-
- resp, err = i.HandleRequest(ctx, groupReq)
- if err != nil {
- t.Fatal(err)
- }
- if !resp.IsError() {
- t.Fatalf("expected an error")
- }
-}
-
-func TestIdentityStore_Groups_TypeImmutability(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
- groupReq := &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- }
-
- resp, err = i.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- internalGroupID := resp.Data["id"].(string)
-
- groupReq.Data = map[string]interface{}{
- "type": "external",
- }
- resp, err = i.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- externalGroupID := resp.Data["id"].(string)
-
- // Try to mark internal group as external
- groupReq.Data = map[string]interface{}{
- "type": "external",
- }
- groupReq.Path = "group/id/" + internalGroupID
- resp, err = i.HandleRequest(ctx, groupReq)
- if err != nil {
- t.Fatal(err)
- }
- if !resp.IsError() {
- t.Fatalf("expected an error")
- }
-
- // Try to mark internal group as external
- groupReq.Data = map[string]interface{}{
- "type": "internal",
- }
- groupReq.Path = "group/id/" + externalGroupID
- resp, err = i.HandleRequest(ctx, groupReq)
- if err != nil {
- t.Fatal(err)
- }
- if !resp.IsError() {
- t.Fatalf("expected an error")
- }
-}
-
-func TestIdentityStore_MemDBGroupIndexes(t *testing.T) {
- var err error
- ctx := namespace.RootContext(nil)
- i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- // Create a dummy group
- group := &identity.Group{
- ID: "testgroupid",
- Name: "testgroupname",
- Metadata: map[string]string{
- "testmetadatakey1": "testmetadatavalue1",
- "testmetadatakey2": "testmetadatavalue2",
- },
- ParentGroupIDs: []string{"testparentgroupid1", "testparentgroupid2"},
- MemberEntityIDs: []string{"testentityid1", "testentityid2"},
- Policies: []string{"testpolicy1", "testpolicy2"},
- BucketKey: i.groupPacker.BucketKey("testgroupid"),
- }
-
- // Insert it into memdb
- txn := i.db.Txn(true)
- defer txn.Abort()
- err = i.MemDBUpsertGroupInTxn(txn, group)
- if err != nil {
- t.Fatal(err)
- }
- txn.Commit()
-
- // Insert another dummy group
- group = &identity.Group{
- ID: "testgroupid2",
- Name: "testgroupname2",
- Metadata: map[string]string{
- "testmetadatakey2": "testmetadatavalue2",
- "testmetadatakey3": "testmetadatavalue3",
- },
- ParentGroupIDs: []string{"testparentgroupid2", "testparentgroupid3"},
- MemberEntityIDs: []string{"testentityid2", "testentityid3"},
- Policies: []string{"testpolicy2", "testpolicy3"},
- BucketKey: i.groupPacker.BucketKey("testgroupid2"),
- }
-
- // Insert it into memdb
-
- txn = i.db.Txn(true)
- defer txn.Abort()
- err = i.MemDBUpsertGroupInTxn(txn, group)
- if err != nil {
- t.Fatal(err)
- }
- txn.Commit()
-
- var fetchedGroup *identity.Group
-
- // Fetch group given the name
- fetchedGroup, err = i.MemDBGroupByName(namespace.RootContext(nil), "testgroupname", false)
- if err != nil {
- t.Fatal(err)
- }
- if fetchedGroup == nil || fetchedGroup.Name != "testgroupname" {
- t.Fatalf("failed to fetch an indexed group")
- }
-
- // Fetch group given the ID
- fetchedGroup, err = i.MemDBGroupByID("testgroupid", false)
- if err != nil {
- t.Fatal(err)
- }
- if fetchedGroup == nil || fetchedGroup.Name != "testgroupname" {
- t.Fatalf("failed to fetch an indexed group")
- }
-
- var fetchedGroups []*identity.Group
- // Fetch the subgroups of a given group ID
- fetchedGroups, err = i.MemDBGroupsByParentGroupID("testparentgroupid1", false)
- if err != nil {
- t.Fatal(err)
- }
- if len(fetchedGroups) != 1 || fetchedGroups[0].Name != "testgroupname" {
- t.Fatalf("failed to fetch an indexed group")
- }
-
- fetchedGroups, err = i.MemDBGroupsByParentGroupID("testparentgroupid2", false)
- if err != nil {
- t.Fatal(err)
- }
- if len(fetchedGroups) != 2 {
- t.Fatalf("failed to fetch a indexed groups")
- }
-
- // Fetch groups based on member entity ID
- fetchedGroups, err = i.MemDBGroupsByMemberEntityID("testentityid1", false, false)
- if err != nil {
- t.Fatal(err)
- }
- if len(fetchedGroups) != 1 || fetchedGroups[0].Name != "testgroupname" {
- t.Fatalf("failed to fetch an indexed group")
- }
-
- fetchedGroups, err = i.MemDBGroupsByMemberEntityID("testentityid2", false, false)
- if err != nil {
- t.Fatal(err)
- }
-
- if len(fetchedGroups) != 2 {
- t.Fatalf("failed to fetch groups by entity ID")
- }
-}
-
-func TestIdentityStore_GroupsCreateUpdate(t *testing.T) {
- var resp *logical.Response
- var err error
-
- ctx := namespace.RootContext(nil)
- is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- // Create an entity and get its ID
- entityRegisterReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity",
- }
- resp, err = is.HandleRequest(ctx, entityRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- entityID1 := resp.Data["id"].(string)
-
- // Create another entity and get its ID
- resp, err = is.HandleRequest(ctx, entityRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- entityID2 := resp.Data["id"].(string)
-
- // Create a group with the above created 2 entities as its members
- groupData := map[string]interface{}{
- "policies": "testpolicy1,testPolicy1 , testpolicy2",
- "metadata": []string{"testkey1=testvalue1", "testkey2=testvalue2"},
- "member_entity_ids": []string{entityID1, entityID2},
- }
-
- // Create a group and get its ID
- groupReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "group",
- Data: groupData,
- }
- resp, err = is.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- memberGroupID1 := resp.Data["id"].(string)
-
- // Create another group and get its ID
- resp, err = is.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- memberGroupID2 := resp.Data["id"].(string)
-
- // Create a group with the above 2 groups as its members
- groupData["member_group_ids"] = []string{memberGroupID1, memberGroupID2}
- resp, err = is.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- groupID := resp.Data["id"].(string)
-
- // Read the group using its iD and check if all the fields are properly
- // set
- groupReq = &logical.Request{
- Operation: logical.ReadOperation,
- Path: "group/id/" + groupID,
- }
- resp, err = is.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- expectedData := map[string]interface{}{
- "policies": []string{"testpolicy1", "testpolicy2"},
- "metadata": map[string]string{
- "testkey1": "testvalue1",
- "testkey2": "testvalue2",
- },
- "parent_group_ids": []string(nil),
- }
- expectedData["id"] = resp.Data["id"]
- expectedData["type"] = resp.Data["type"]
- expectedData["name"] = resp.Data["name"]
- expectedData["member_group_ids"] = resp.Data["member_group_ids"]
- expectedData["member_entity_ids"] = resp.Data["member_entity_ids"]
- expectedData["creation_time"] = resp.Data["creation_time"]
- expectedData["last_update_time"] = resp.Data["last_update_time"]
- expectedData["modify_index"] = resp.Data["modify_index"]
- expectedData["alias"] = resp.Data["alias"]
- expectedData["namespace_id"] = "root"
-
- if diff := deep.Equal(expectedData, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Update the policies and metadata in the group
- groupReq.Operation = logical.UpdateOperation
- groupReq.Data = groupData
-
- // Update by setting ID in the param
- groupData["id"] = groupID
- groupData["policies"] = "updatedpolicy1,updatedpolicy2"
- groupData["metadata"] = []string{"updatedkey=updatedvalue"}
- resp, err = is.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- // Check if updates are reflected
- groupReq.Operation = logical.ReadOperation
- resp, err = is.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- expectedData["policies"] = []string{"updatedpolicy1", "updatedpolicy2"}
- expectedData["metadata"] = map[string]string{
- "updatedkey": "updatedvalue",
- }
- expectedData["last_update_time"] = resp.Data["last_update_time"]
- expectedData["modify_index"] = resp.Data["modify_index"]
- if !reflect.DeepEqual(expectedData, resp.Data) {
- t.Fatalf("bad: group data; expected: %#v\n actual: %#v\n", expectedData, resp.Data)
- }
-}
-
-func TestIdentityStore_GroupsCreateUpdateDuplicatePolicy(t *testing.T) {
- var resp *logical.Response
- var err error
-
- ctx := namespace.RootContext(nil)
- is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- // Create a group with the above created 2 entities as its members
- groupData := map[string]interface{}{
- "policies": []string{"testpolicy1", "testpolicy2"},
- "metadata": []string{"testkey1=testvalue1", "testkey2=testvalue2"},
- }
-
- // Create a group and get its ID
- groupReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "group",
- Data: groupData,
- }
-
- // Create a group with the above 2 groups as its members
- resp, err = is.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- groupID := resp.Data["id"].(string)
-
- // Read the group using its iD and check if all the fields are properly
- // set
- groupReq = &logical.Request{
- Operation: logical.ReadOperation,
- Path: "group/id/" + groupID,
- }
- resp, err = is.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- expectedData := map[string]interface{}{
- "policies": []string{"testpolicy1", "testpolicy2"},
- "metadata": map[string]string{
- "testkey1": "testvalue1",
- "testkey2": "testvalue2",
- },
- "parent_group_ids": []string(nil),
- }
- expectedData["id"] = resp.Data["id"]
- expectedData["type"] = resp.Data["type"]
- expectedData["name"] = resp.Data["name"]
- expectedData["creation_time"] = resp.Data["creation_time"]
- expectedData["last_update_time"] = resp.Data["last_update_time"]
- expectedData["modify_index"] = resp.Data["modify_index"]
- expectedData["alias"] = resp.Data["alias"]
- expectedData["namespace_id"] = "root"
- expectedData["member_group_ids"] = resp.Data["member_group_ids"]
- expectedData["member_entity_ids"] = resp.Data["member_entity_ids"]
-
- if diff := deep.Equal(expectedData, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Update the policies and metadata in the group
- groupReq.Operation = logical.UpdateOperation
- groupReq.Data = groupData
-
- // Update by setting ID in the param
- groupData["id"] = groupID
- groupData["policies"] = []string{"updatedpolicy1", "updatedpolicy2", "updatedpolicy2"}
- resp, err = is.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- // Check if updates are reflected
- groupReq.Operation = logical.ReadOperation
- resp, err = is.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- expectedData["policies"] = []string{"updatedpolicy1", "updatedpolicy2"}
- expectedData["last_update_time"] = resp.Data["last_update_time"]
- expectedData["modify_index"] = resp.Data["modify_index"]
- if !reflect.DeepEqual(expectedData, resp.Data) {
- t.Fatalf("bad: group data; expected: %#v\n actual: %#v\n", expectedData, resp.Data)
- }
-}
-
-func TestIdentityStore_GroupsCRUD_ByID(t *testing.T) {
- var resp *logical.Response
- var err error
- ctx := namespace.RootContext(nil)
- is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- // Create an entity and get its ID
- entityRegisterReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity",
- }
- resp, err = is.HandleRequest(ctx, entityRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- entityID1 := resp.Data["id"].(string)
-
- // Create another entity and get its ID
- resp, err = is.HandleRequest(ctx, entityRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- entityID2 := resp.Data["id"].(string)
-
- // Create a group with the above created 2 entities as its members
- groupData := map[string]interface{}{
- "policies": "testpolicy1,testpolicy2",
- "metadata": []string{"testkey1=testvalue1", "testkey2=testvalue2"},
- "member_entity_ids": []string{entityID1, entityID2},
- }
-
- // Create a group and get its ID
- groupRegisterReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "group",
- Data: groupData,
- }
- resp, err = is.HandleRequest(ctx, groupRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- memberGroupID1 := resp.Data["id"].(string)
-
- // Create another group and get its ID
- resp, err = is.HandleRequest(ctx, groupRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- memberGroupID2 := resp.Data["id"].(string)
-
- // Create a group with the above 2 groups as its members
- groupData["member_group_ids"] = []string{memberGroupID1, memberGroupID2}
- resp, err = is.HandleRequest(ctx, groupRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- groupID := resp.Data["id"].(string)
-
- // Read the group using its name and check if all the fields are properly
- // set
- groupReq := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "group/id/" + groupID,
- }
- resp, err = is.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- expectedData := map[string]interface{}{
- "policies": []string{"testpolicy1", "testpolicy2"},
- "metadata": map[string]string{
- "testkey1": "testvalue1",
- "testkey2": "testvalue2",
- },
- "parent_group_ids": []string(nil),
- }
- expectedData["id"] = resp.Data["id"]
- expectedData["type"] = resp.Data["type"]
- expectedData["name"] = resp.Data["name"]
- expectedData["member_group_ids"] = resp.Data["member_group_ids"]
- expectedData["member_entity_ids"] = resp.Data["member_entity_ids"]
- expectedData["creation_time"] = resp.Data["creation_time"]
- expectedData["last_update_time"] = resp.Data["last_update_time"]
- expectedData["modify_index"] = resp.Data["modify_index"]
- expectedData["alias"] = resp.Data["alias"]
- expectedData["namespace_id"] = "root"
-
- if diff := deep.Equal(expectedData, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Update the policies and metadata in the group
- groupReq.Operation = logical.UpdateOperation
- groupReq.Data = groupData
- groupData["policies"] = "updatedpolicy1,updatedpolicy2"
- groupData["metadata"] = []string{"updatedkey=updatedvalue"}
- resp, err = is.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- // Check if updates are reflected
- groupReq.Operation = logical.ReadOperation
- resp, err = is.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- expectedData["policies"] = []string{"updatedpolicy1", "updatedpolicy2"}
- expectedData["metadata"] = map[string]string{
- "updatedkey": "updatedvalue",
- }
- expectedData["last_update_time"] = resp.Data["last_update_time"]
- expectedData["modify_index"] = resp.Data["modify_index"]
- if !reflect.DeepEqual(expectedData, resp.Data) {
- t.Fatalf("bad: group data; expected: %#v\n actual: %#v\n", expectedData, resp.Data)
- }
-
- // Check if delete is working properly
- groupReq.Operation = logical.DeleteOperation
- resp, err = is.HandleRequest(ctx, groupReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- groupReq.Operation = logical.ReadOperation
- resp, err = is.HandleRequest(ctx, groupReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-}
-
-func TestIdentityStore_GroupMultiCase(t *testing.T) {
- var resp *logical.Response
- var err error
- ctx := namespace.RootContext(nil)
- is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
- groupRegisterReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "group",
- }
-
- // Create 'build' group
- buildGroupData := map[string]interface{}{
- "name": "build",
- "policies": "buildpolicy",
- }
- groupRegisterReq.Data = buildGroupData
- resp, err = is.HandleRequest(ctx, groupRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- buildGroupID := resp.Data["id"].(string)
-
- // Create 'deploy' group
- deployGroupData := map[string]interface{}{
- "name": "deploy",
- "policies": "deploypolicy",
- }
- groupRegisterReq.Data = deployGroupData
- resp, err = is.HandleRequest(ctx, groupRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- deployGroupID := resp.Data["id"].(string)
-
- // Create an entity ID
- entityRegisterReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity",
- }
- resp, err = is.HandleRequest(ctx, entityRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- entityID1 := resp.Data["id"].(string)
-
- // Add the entity as a member of 'build' group
- entityIDReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "group/id/" + buildGroupID,
- Data: map[string]interface{}{
- "member_entity_ids": []string{entityID1},
- },
- }
- resp, err = is.HandleRequest(ctx, entityIDReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- // Add the entity as a member of the 'deploy` group
- entityIDReq.Path = "group/id/" + deployGroupID
- resp, err = is.HandleRequest(ctx, entityIDReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- policiesResult, err := is.groupPoliciesByEntityID(entityID1)
- if err != nil {
- t.Fatal(err)
- }
-
- policies := []string{}
- for _, nsPolicies := range policiesResult {
- policies = append(policies, nsPolicies...)
- }
- sort.Strings(policies)
- expected := []string{"deploypolicy", "buildpolicy"}
- sort.Strings(expected)
- if !reflect.DeepEqual(expected, policies) {
- t.Fatalf("bad: policies; expected: %#v\nactual:%#v", expected, policies)
- }
-}
-
-/*
-Test groups hierarchy:
-
- ------- eng(entityID3) -------
- | |
- ----- vault ----- -- ops(entityID2) --
- | | | |
- kube(entityID1) identity build deploy
-*/
-func TestIdentityStore_GroupHierarchyCases(t *testing.T) {
- var resp *logical.Response
- var err error
- ctx := namespace.RootContext(nil)
- is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
- groupRegisterReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "group",
- }
-
- // Create 'kube' group
- kubeGroupData := map[string]interface{}{
- "name": "kube",
- "policies": "kubepolicy",
- }
- groupRegisterReq.Data = kubeGroupData
- resp, err = is.HandleRequest(ctx, groupRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- kubeGroupID := resp.Data["id"].(string)
-
- // Create 'identity' group
- identityGroupData := map[string]interface{}{
- "name": "identity",
- "policies": "identitypolicy",
- }
- groupRegisterReq.Data = identityGroupData
- resp, err = is.HandleRequest(ctx, groupRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- identityGroupID := resp.Data["id"].(string)
-
- // Create 'build' group
- buildGroupData := map[string]interface{}{
- "name": "build",
- "policies": "buildpolicy",
- }
- groupRegisterReq.Data = buildGroupData
- resp, err = is.HandleRequest(ctx, groupRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- buildGroupID := resp.Data["id"].(string)
-
- // Create 'deploy' group
- deployGroupData := map[string]interface{}{
- "name": "deploy",
- "policies": "deploypolicy",
- }
- groupRegisterReq.Data = deployGroupData
- resp, err = is.HandleRequest(ctx, groupRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- deployGroupID := resp.Data["id"].(string)
-
- // Create 'vault' with 'kube' and 'identity' as member groups
- vaultMemberGroupIDs := []string{kubeGroupID, identityGroupID}
- vaultGroupData := map[string]interface{}{
- "name": "vault",
- "policies": "vaultpolicy",
- "member_group_ids": vaultMemberGroupIDs,
- }
- groupRegisterReq.Data = vaultGroupData
- resp, err = is.HandleRequest(ctx, groupRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- vaultGroupID := resp.Data["id"].(string)
-
- // Create 'ops' group with 'build' and 'deploy' as member groups
- opsMemberGroupIDs := []string{buildGroupID, deployGroupID}
- opsGroupData := map[string]interface{}{
- "name": "ops",
- "policies": "opspolicy",
- "member_group_ids": opsMemberGroupIDs,
- }
- groupRegisterReq.Data = opsGroupData
- resp, err = is.HandleRequest(ctx, groupRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- opsGroupID := resp.Data["id"].(string)
-
- // Create 'eng' group with 'vault' and 'ops' as member groups
- engMemberGroupIDs := []string{vaultGroupID, opsGroupID}
- engGroupData := map[string]interface{}{
- "name": "eng",
- "policies": "engpolicy",
- "member_group_ids": engMemberGroupIDs,
- }
-
- groupRegisterReq.Data = engGroupData
- resp, err = is.HandleRequest(ctx, groupRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- engGroupID := resp.Data["id"].(string)
-
- /*
- fmt.Printf("engGroupID: %#v\n", engGroupID)
- fmt.Printf("vaultGroupID: %#v\n", vaultGroupID)
- fmt.Printf("opsGroupID: %#v\n", opsGroupID)
- fmt.Printf("kubeGroupID: %#v\n", kubeGroupID)
- fmt.Printf("identityGroupID: %#v\n", identityGroupID)
- fmt.Printf("buildGroupID: %#v\n", buildGroupID)
- fmt.Printf("deployGroupID: %#v\n", deployGroupID)
- */
-
- var memberGroupIDs []string
- // Fetch 'eng' group
- engGroup, err := is.MemDBGroupByID(engGroupID, false)
- if err != nil {
- t.Fatal(err)
- }
- memberGroupIDs, err = is.memberGroupIDsByID(engGroup.ID)
- if err != nil {
- t.Fatal(err)
- }
- sort.Strings(memberGroupIDs)
- sort.Strings(engMemberGroupIDs)
- if !reflect.DeepEqual(engMemberGroupIDs, memberGroupIDs) {
- t.Fatalf("bad: group membership IDs; expected: %#v\n actual: %#v\n", engMemberGroupIDs, memberGroupIDs)
- }
-
- vaultGroup, err := is.MemDBGroupByID(vaultGroupID, false)
- if err != nil {
- t.Fatal(err)
- }
- memberGroupIDs, err = is.memberGroupIDsByID(vaultGroup.ID)
- if err != nil {
- t.Fatal(err)
- }
- sort.Strings(memberGroupIDs)
- sort.Strings(vaultMemberGroupIDs)
- if !reflect.DeepEqual(vaultMemberGroupIDs, memberGroupIDs) {
- t.Fatalf("bad: group membership IDs; expected: %#v\n actual: %#v\n", vaultMemberGroupIDs, memberGroupIDs)
- }
-
- opsGroup, err := is.MemDBGroupByID(opsGroupID, false)
- if err != nil {
- t.Fatal(err)
- }
- memberGroupIDs, err = is.memberGroupIDsByID(opsGroup.ID)
- if err != nil {
- t.Fatal(err)
- }
- sort.Strings(memberGroupIDs)
- sort.Strings(opsMemberGroupIDs)
- if !reflect.DeepEqual(opsMemberGroupIDs, memberGroupIDs) {
- t.Fatalf("bad: group membership IDs; expected: %#v\n actual: %#v\n", opsMemberGroupIDs, memberGroupIDs)
- }
-
- groupUpdateReq := &logical.Request{
- Operation: logical.UpdateOperation,
- }
-
- // Adding 'engGroupID' under 'kubeGroupID' should fail
- groupUpdateReq.Path = "group/name/kube"
- groupUpdateReq.Data = kubeGroupData
- kubeGroupData["member_group_ids"] = []string{engGroupID}
- resp, err = is.HandleRequest(ctx, groupUpdateReq)
- if err != nil || resp == nil || !resp.IsError() {
- t.Fatalf("expected an error response")
- }
-
- // Create an entity ID
- entityRegisterReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity",
- }
- resp, err = is.HandleRequest(ctx, entityRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- entityID1 := resp.Data["id"].(string)
-
- // Add the entity as a member of 'kube' group
- entityIDReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "group/id/" + kubeGroupID,
- Data: map[string]interface{}{
- "member_entity_ids": []string{entityID1},
- },
- }
- resp, err = is.HandleRequest(ctx, entityIDReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- // Create a second entity ID
- resp, err = is.HandleRequest(ctx, entityRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- entityID2 := resp.Data["id"].(string)
-
- // Add the entity as a member of 'ops' group
- entityIDReq.Path = "group/id/" + opsGroupID
- entityIDReq.Data = map[string]interface{}{
- "member_entity_ids": []string{entityID2},
- }
- resp, err = is.HandleRequest(ctx, entityIDReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- // Create a third entity ID
- resp, err = is.HandleRequest(ctx, entityRegisterReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
- entityID3 := resp.Data["id"].(string)
-
- // Add the entity as a member of 'eng' group
- entityIDReq.Path = "group/id/" + engGroupID
- entityIDReq.Data = map[string]interface{}{
- "member_entity_ids": []string{entityID3},
- "member_group_ids": engMemberGroupIDs,
- }
- resp, err = is.HandleRequest(ctx, entityIDReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- policiesResult, err := is.groupPoliciesByEntityID(entityID1)
- if err != nil {
- t.Fatal(err)
- }
- var policies []string
- for _, nsPolicies := range policiesResult {
- policies = append(policies, nsPolicies...)
- }
- sort.Strings(policies)
- expected := []string{"kubepolicy", "vaultpolicy", "engpolicy"}
- sort.Strings(expected)
- if !reflect.DeepEqual(expected, policies) {
- t.Fatalf("bad: policies; expected: %#v\nactual:%#v", expected, policies)
- }
-
- policiesResult, err = is.groupPoliciesByEntityID(entityID2)
- if err != nil {
- t.Fatal(err)
- }
- policies = nil
- for _, nsPolicies := range policiesResult {
- policies = append(policies, nsPolicies...)
- }
- sort.Strings(policies)
- expected = []string{"opspolicy", "engpolicy"}
- sort.Strings(expected)
- if !reflect.DeepEqual(expected, policies) {
- t.Fatalf("bad: policies; expected: %#v\nactual:%#v", expected, policies)
- }
-
- policiesResult, err = is.groupPoliciesByEntityID(entityID3)
- if err != nil {
- t.Fatal(err)
- }
- policies = nil
- for _, nsPolicies := range policiesResult {
- policies = append(policies, nsPolicies...)
- }
-
- if len(policies) != 1 && policies[0] != "engpolicy" {
- t.Fatalf("bad: policies; expected: 'engpolicy'\nactual:%#v", policies)
- }
-
- groups, inheritedGroups, err := is.groupsByEntityID(entityID1)
- if err != nil {
- t.Fatal(err)
- }
- if len(groups) != 1 {
- t.Fatalf("bad: length of groups; expected: 1, actual: %d", len(groups))
- }
- if len(inheritedGroups) != 2 {
- t.Fatalf("bad: length of inheritedGroups; expected: 2, actual: %d", len(inheritedGroups))
- }
-
- groups, inheritedGroups, err = is.groupsByEntityID(entityID2)
- if err != nil {
- t.Fatal(err)
- }
- if len(groups) != 1 {
- t.Fatalf("bad: length of groups; expected: 1, actual: %d", len(groups))
- }
- if len(inheritedGroups) != 1 {
- t.Fatalf("bad: length of inheritedGroups; expected: 1, actual: %d", len(inheritedGroups))
- }
-
- groups, inheritedGroups, err = is.groupsByEntityID(entityID3)
- if err != nil {
- t.Fatal(err)
- }
- if len(groups) != 1 {
- t.Fatalf("bad: length of groups; expected: 1, actual: %d", len(groups))
- }
- if len(inheritedGroups) != 0 {
- t.Fatalf("bad: length of inheritedGroups; expected: 0, actual: %d", len(inheritedGroups))
- }
-}
-
-func TestIdentityStore_GroupCycleDetection(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
-
- group1Name := "group1"
- group2Name := "group2"
- group3Name := "group3"
-
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": group1Name,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("failed to create group %q, err: %v, resp: %#v", group1Name, err, resp)
- }
-
- group1Id := resp.Data["id"].(string)
-
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": group2Name,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("failed to create group %q, err: %v, resp: %#v", group2Name, err, resp)
- }
-
- group2Id := resp.Data["id"].(string)
-
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": group3Name,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("failed to create group %q, err: %v, resp: %#v", group3Name, err, resp)
- }
-
- group3Id := resp.Data["id"].(string)
-
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": group1Name,
- "member_group_ids": []string{},
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("failed to update group %q, err: %v, resp: %#v", group1Name, err, resp)
- }
-
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": group2Name,
- "member_group_ids": []string{group3Id},
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("failed to update group %q, err: %v, resp: %#v", group2Name, err, resp)
- }
-
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": group3Name,
- "member_group_ids": []string{group1Id, group2Id},
- },
- })
-
- if err != nil || resp == nil {
- t.Fatalf("unexpected group update error for group %q, err: %v, resp: %#v", group3Name, err, resp)
- }
- if !resp.IsError() || resp.Error().Error() != fmt.Sprintf("%s %q", errCycleDetectedPrefix, group2Id) {
- t.Fatalf("expected update to group %q to fail due to cycle, resp: %#v", group3Id, resp)
- }
-}
diff --git a/vault/identity_store_oidc_provider_test.go b/vault/identity_store_oidc_provider_test.go
deleted file mode 100644
index 7fcc8fa48..000000000
--- a/vault/identity_store_oidc_provider_test.go
+++ /dev/null
@@ -1,3725 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "encoding/base64"
- "encoding/json"
- "fmt"
- "net/http"
- "sort"
- "strings"
- "testing"
- "time"
-
- "github.com/go-test/deep"
- "github.com/hashicorp/go-secure-stdlib/parseutil"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/framework"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/stretchr/testify/require"
-)
-
-/*
- Tests for the Vault OIDC provider configuration and OIDC-related
- endpoints. Additional tests for the Vault OIDC provider exist
- in: vault/external_tests/identity/oidc_provider_test.go
-*/
-
-const (
- authCodeRegex = "[a-zA-Z0-9]{32}"
-)
-
-// Tests that an authorization code issued by one provider cannot be exchanged
-// for a token using a different provider that the client is allowed to use.
-func TestOIDC_Path_OIDC_Cross_Provider_Exchange(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- s := new(logical.InmemStorage)
-
- // Create the common OIDC configuration
- entityID, _, _, clientID, clientSecret := setupOIDCCommon(t, c, s)
-
- // Create a second provider
- providerPath := "oidc/provider/test-provider-2"
- req := testProviderReq(s, clientID)
- req.Path = providerPath
- resp, err := c.identityStore.HandleRequest(ctx, req)
- require.NoError(t, err)
-
- // Obtain an authorization code from the first provider
- var authRes struct {
- Code string `json:"code"`
- State string `json:"state"`
- }
- req = testAuthorizeReq(s, clientID)
- req.EntityID = entityID
- resp, err = c.identityStore.HandleRequest(ctx, req)
- require.NoError(t, err)
- require.NoError(t, json.Unmarshal(resp.Data["http_raw_body"].([]byte), &authRes))
- require.Regexp(t, authCodeRegex, authRes.Code)
- require.Equal(t, req.Data["state"], authRes.State)
-
- // Assert that the authorization code cannot be exchanged using the second provider
- var tokenRes struct {
- Error string `json:"error"`
- ErrorDescription string `json:"error_description"`
- }
- req = testTokenReq(s, authRes.Code, clientID, clientSecret)
- req.Path = providerPath + "/token"
- resp, err = c.identityStore.HandleRequest(ctx, req)
- require.NoError(t, err)
- require.NoError(t, json.Unmarshal(resp.Data["http_raw_body"].([]byte), &tokenRes))
- require.Equal(t, ErrTokenInvalidGrant, tokenRes.Error)
- require.Equal(t, "authorization code was not issued by the provider", tokenRes.ErrorDescription)
-}
-
-func TestOIDC_Path_OIDC_Token(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- s := new(logical.InmemStorage)
-
- entityID, groupID, _, clientID, clientSecret := setupOIDCCommon(t, c, s)
-
- type args struct {
- clientReq *logical.Request
- providerReq *logical.Request
- assignmentReq *logical.Request
- authorizeReq *logical.Request
- tokenReq *logical.Request
- vaultTokenCreationTime func() time.Time
- }
- tests := []struct {
- name string
- args args
- wantErr string
- }{
- {
- name: "invalid token request with provider not found",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- tokenReq: func() *logical.Request {
- req := testTokenReq(s, "", clientID, clientSecret)
- req.Path = "oidc/provider/non-existent-provider/token"
- return req
- }(),
- },
- wantErr: ErrTokenInvalidRequest,
- },
- {
- name: "invalid token request with missing basic auth header",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- tokenReq: func() *logical.Request {
- req := testTokenReq(s, "", clientID, clientSecret)
- req.Headers = nil
- return req
- }(),
- },
- wantErr: ErrTokenInvalidRequest,
- },
- {
- name: "invalid token request with client ID not found",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- tokenReq: testTokenReq(s, "", "non-existent-client-id", clientSecret),
- },
- wantErr: ErrTokenInvalidClient,
- },
- {
- name: "invalid token request with client secret mismatch",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- tokenReq: testTokenReq(s, "", clientID, "wrong-client-secret"),
- },
- wantErr: ErrTokenInvalidClient,
- },
- {
- name: "invalid token request with client_id not allowed by provider",
- args: args{
- clientReq: testClientReq(s),
- providerReq: func() *logical.Request {
- req := testProviderReq(s, clientID)
- req.Data["allowed_client_ids"] = []string{"not-client-id"}
- return req
- }(),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- tokenReq: testTokenReq(s, "", clientID, clientSecret),
- },
- wantErr: ErrTokenInvalidClient,
- },
- {
- name: "invalid token request with empty grant_type",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- tokenReq: func() *logical.Request {
- req := testTokenReq(s, "", clientID, clientSecret)
- req.Data["grant_type"] = ""
- return req
- }(),
- },
- wantErr: ErrTokenInvalidRequest,
- },
- {
- name: "invalid token request with unsupported grant_type",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- tokenReq: func() *logical.Request {
- req := testTokenReq(s, "", clientID, clientSecret)
- req.Data["grant_type"] = "not-supported-grant-type"
- return req
- }(),
- },
- wantErr: ErrTokenUnsupportedGrantType,
- },
- {
- name: "invalid token request with invalid code",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- tokenReq: func() *logical.Request {
- req := testTokenReq(s, "", clientID, clientSecret)
- req.Data["code"] = "invalid-code"
- return req
- }(),
- },
- wantErr: ErrTokenInvalidGrant,
- },
- {
- name: "invalid token request with missing redirect_uri",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- tokenReq: func() *logical.Request {
- req := testTokenReq(s, "", clientID, clientSecret)
- req.Data["redirect_uri"] = ""
- return req
- }(),
- },
- wantErr: ErrTokenInvalidRequest,
- },
- {
- name: "invalid token request with entity not found in client assignment",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, "not-entity-id", ""),
- authorizeReq: testAuthorizeReq(s, clientID),
- tokenReq: testTokenReq(s, "", clientID, clientSecret),
- },
- wantErr: ErrTokenInvalidRequest,
- },
- {
- name: "invalid token request with redirect_uri mismatch",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- tokenReq: func() *logical.Request {
- req := testTokenReq(s, "", clientID, clientSecret)
- req.Data["redirect_uri"] = "https://not.original.redirect.uri:8251/callback"
- return req
- }(),
- },
- wantErr: ErrTokenInvalidGrant,
- },
- {
- name: "invalid token request with group not found in client assignment",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, "", "not-group-id"),
- authorizeReq: testAuthorizeReq(s, clientID),
- tokenReq: testTokenReq(s, "", clientID, clientSecret),
- },
- wantErr: ErrTokenInvalidRequest,
- },
- {
- name: "invalid token request with scopes claim conflict",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["scope"] = "openid test-scope conflict"
- return req
- }(),
- tokenReq: testTokenReq(s, "", clientID, clientSecret),
- },
- wantErr: ErrTokenInvalidRequest,
- },
- {
- name: "invalid token request with empty code_verifier",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["code_challenge_method"] = "plain"
- req.Data["code_challenge"] = "43_char_min_abcdefghijklmnopqrstuvwxyzabcde"
- return req
- }(),
- tokenReq: func() *logical.Request {
- req := testTokenReq(s, "", clientID, clientSecret)
- req.Data["code_verifier"] = ""
- return req
- }(),
- },
- wantErr: ErrTokenInvalidRequest,
- },
- {
- name: "invalid token request with code_verifier provided for non-PKCE flow",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- tokenReq: func() *logical.Request {
- req := testTokenReq(s, "", clientID, clientSecret)
- req.Data["code_verifier"] = "pkce_not_used_in_authorize_request"
- return req
- }(),
- },
- wantErr: ErrTokenInvalidRequest,
- },
- {
- name: "invalid token request with incorrect plain code_verifier",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["code_challenge_method"] = "plain"
- req.Data["code_challenge"] = "43_char_min_abcdefghijklmnopqrstuvwxyzabcde"
- return req
- }(),
- tokenReq: func() *logical.Request {
- req := testTokenReq(s, "", clientID, clientSecret)
- req.Data["code_verifier"] = "wont_match_challenge"
- return req
- }(),
- },
- wantErr: ErrTokenInvalidGrant,
- },
- {
- name: "invalid token request with incorrect S256 code_verifier",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["code_challenge_method"] = "S256"
- req.Data["code_challenge"] = "43_char_min_abcdefghijklmnopqrstuvwxyzabcde"
- return req
- }(),
- tokenReq: func() *logical.Request {
- req := testTokenReq(s, "", clientID, clientSecret)
- req.Data["code_verifier"] = "wont_hash_to_challenge"
- return req
- }(),
- },
- wantErr: ErrTokenInvalidGrant,
- },
- {
- name: "valid token request with plain code_challenge_method",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["code_challenge_method"] = "plain"
- req.Data["code_challenge"] = "43_char_min_abcdefghijklmnopqrstuvwxyzabcde"
- return req
- }(),
- tokenReq: func() *logical.Request {
- req := testTokenReq(s, "", clientID, clientSecret)
- req.Data["code_verifier"] = "43_char_min_abcdefghijklmnopqrstuvwxyzabcde"
- return req
- }(),
- },
- },
- {
- name: "valid token request with default plain code_challenge_method",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- // code_challenge_method intentionally not provided
- req := testAuthorizeReq(s, clientID)
- req.Data["code_challenge"] = "43_char_min_abcdefghijklmnopqrstuvwxyzabcde"
- return req
- }(),
- tokenReq: func() *logical.Request {
- req := testTokenReq(s, "", clientID, clientSecret)
- req.Data["code_verifier"] = "43_char_min_abcdefghijklmnopqrstuvwxyzabcde"
- return req
- }(),
- },
- },
- {
- name: "valid token request with S256 code_challenge_method",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["code_challenge_method"] = "S256"
- req.Data["code_challenge"] = "hMn-5TBH-t3uN00FEaGsQtYPhyC4Otbx-9vDcPTYHmc"
- return req
- }(),
- tokenReq: func() *logical.Request {
- req := testTokenReq(s, "", clientID, clientSecret)
- req.Data["code_verifier"] = "43_char_min_abcdefghijklmnopqrstuvwxyzabcde"
- return req
- }(),
- },
- },
- {
- name: "valid token request with max_age and auth_time claim",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["max_age"] = "30"
- return req
- }(),
- tokenReq: testTokenReq(s, "", clientID, clientSecret),
- vaultTokenCreationTime: func() time.Time {
- return time.Now()
- },
- },
- },
- {
- name: "valid token request with empty nonce in authorize request",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- delete(req.Data, "nonce")
- return req
- }(),
- tokenReq: testTokenReq(s, "", clientID, clientSecret),
- },
- },
- {
- name: "valid token request with client_secret_post client authentication method",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- tokenReq: func() *logical.Request {
- req := testTokenReq(s, "", clientID, clientSecret)
- req.Headers = nil
- req.Data["client_id"] = clientID
- req.Data["client_secret"] = clientSecret
- return req
- }(),
- },
- },
- {
- name: "valid token request",
- args: args{
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- tokenReq: testTokenReq(s, "", clientID, clientSecret),
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- // Create a token entry to associate with the authorize request
- creationTime := time.Now()
- if tt.args.vaultTokenCreationTime != nil {
- creationTime = tt.args.vaultTokenCreationTime()
- }
- te := &logical.TokenEntry{
- Path: "test",
- Policies: []string{"default"},
- TTL: time.Hour * 24,
- CreationTime: creationTime.Unix(),
- }
- testMakeTokenDirectly(t, c.tokenStore, te)
- require.NotEmpty(t, te.ID)
-
- // Reset any configuration modifications
- resetCommonOIDCConfig(t, s, c, entityID, groupID, clientID)
-
- // Send the request to the OIDC authorize endpoint
- tt.args.authorizeReq.EntityID = entityID
- tt.args.authorizeReq.ClientToken = te.ID
- resp, err := c.identityStore.HandleRequest(ctx, tt.args.authorizeReq)
- expectSuccess(t, resp, err)
-
- // Parse the authorize response
- var authRes struct {
- Code string `json:"code"`
- State string `json:"state"`
- }
- require.NoError(t, json.Unmarshal(resp.Data["http_raw_body"].([]byte), &authRes))
- require.Regexp(t, authCodeRegex, authRes.Code)
- require.Equal(t, tt.args.authorizeReq.Data["state"], authRes.State)
-
- // Update the assignment
- tt.args.assignmentReq.Operation = logical.UpdateOperation
- resp, err = c.identityStore.HandleRequest(ctx, tt.args.assignmentReq)
- expectSuccess(t, resp, err)
-
- // Update the client
- tt.args.clientReq.Operation = logical.UpdateOperation
- resp, err = c.identityStore.HandleRequest(ctx, tt.args.clientReq)
- expectSuccess(t, resp, err)
-
- // Update the provider
- tt.args.providerReq.Operation = logical.UpdateOperation
- resp, err = c.identityStore.HandleRequest(ctx, tt.args.providerReq)
- expectSuccess(t, resp, err)
-
- // Update the code if provided by test arguments
- authCode := authRes.Code
- if tt.args.tokenReq.Data["code"] != "" {
- authCode = tt.args.tokenReq.Data["code"].(string)
- }
-
- // Send the request to the OIDC token endpoint
- tt.args.tokenReq.Data["code"] = authCode
- resp, err = c.identityStore.HandleRequest(ctx, tt.args.tokenReq)
- expectSuccess(t, resp, err)
-
- // Parse the token response
- var tokenRes struct {
- TokenType string `json:"token_type"`
- AccessToken string `json:"access_token"`
- IDToken string `json:"id_token"`
- ExpiresIn int64 `json:"expires_in"`
- Error string `json:"error"`
- ErrorDescription string `json:"error_description"`
- }
- require.NotNil(t, resp)
- require.NotNil(t, resp.Data[logical.HTTPRawBody])
- require.NotNil(t, resp.Data[logical.HTTPStatusCode])
- require.NotNil(t, resp.Data[logical.HTTPContentType])
- require.NotNil(t, resp.Data[logical.HTTPPragmaHeader])
- require.NotNil(t, resp.Data[logical.HTTPCacheControlHeader])
- require.Equal(t, "no-cache", resp.Data[logical.HTTPPragmaHeader])
- require.Equal(t, "no-store", resp.Data[logical.HTTPCacheControlHeader])
- require.Equal(t, "application/json", resp.Data[logical.HTTPContentType].(string))
- require.NoError(t, json.Unmarshal(resp.Data["http_raw_body"].([]byte), &tokenRes))
-
- if tt.wantErr != "" {
- // Assert that we receive the expected error code and description
- require.Equal(t, tt.wantErr, tokenRes.Error)
- require.NotEmpty(t, tokenRes.ErrorDescription)
-
- // Assert that we receive the expected status code
- statusCode := resp.Data[logical.HTTPStatusCode].(int)
- switch tokenRes.Error {
- case ErrTokenInvalidClient:
- require.Equal(t, http.StatusUnauthorized, statusCode)
- require.Equal(t, "Basic", resp.Data[logical.HTTPWWWAuthenticateHeader])
- case ErrTokenServerError:
- require.Equal(t, http.StatusInternalServerError, statusCode)
- default:
- require.Equal(t, http.StatusBadRequest, statusCode)
- }
- return
- }
-
- // Assert that we receive the expected token response
- expectSuccess(t, resp, err)
- require.Equal(t, http.StatusOK, resp.Data[logical.HTTPStatusCode].(int))
- require.Equal(t, "Bearer", tokenRes.TokenType)
- require.NotEmpty(t, tokenRes.AccessToken)
- require.NotEmpty(t, tokenRes.IDToken)
- require.Equal(t, int64(86400), tokenRes.ExpiresIn)
- require.Empty(t, tokenRes.Error)
- require.Empty(t, tokenRes.ErrorDescription)
-
- // Parse the claims from the ID token payload
- parts := strings.Split(tokenRes.IDToken, ".")
- require.Equal(t, 3, len(parts))
- payload, err := base64.RawURLEncoding.DecodeString(parts[1])
- require.NoError(t, err)
- claims := make(map[string]interface{})
- require.NoError(t, json.Unmarshal(payload, &claims))
-
- // Assert that reserved claims are present in the ID token.
- // Optional reserved claims are asserted on conditionally.
- for _, c := range reservedClaims {
- switch c {
- case "nonce":
- // nonce must equal the nonce provided in the authorize request (including empty)
- require.EqualValues(t, tt.args.authorizeReq.Data[c], claims[c])
-
- case "auth_time":
- // auth_time must exist if max_age provided in the authorize request
- if _, ok := tt.args.authorizeReq.Data["max_age"]; ok {
- require.EqualValues(t, creationTime.Unix(), claims[c])
- } else {
- require.Empty(t, claims[c])
- }
-
- default:
- // other reserved claims must be present in all cases
- require.NotEmpty(t, claims[c])
- }
- }
- })
- }
-}
-
-func TestOIDC_Path_OIDC_Authorize(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- s := new(logical.InmemStorage)
-
- entityID, groupID, parentGroupID, clientID, _ := setupOIDCCommon(t, c, s)
-
- type args struct {
- entityID string
- clientReq *logical.Request
- providerReq *logical.Request
- assignmentReq *logical.Request
- authorizeReq *logical.Request
- vaultTokenCreationTime func() time.Time
- }
- tests := []struct {
- name string
- args args
- wantErr string
- }{
- {
- name: "invalid authorize request with provider not found",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Path = "oidc/provider/non-existent-provider/authorize"
- return req
- }(),
- },
- wantErr: ErrAuthInvalidRequest,
- },
- {
- name: "invalid authorize request with empty scope",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["scope"] = ""
- return req
- }(),
- },
- wantErr: ErrAuthInvalidRequest,
- },
- {
- name: "invalid authorize request with missing openid scope",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["scope"] = "groups email profile"
- return req
- }(),
- },
- wantErr: ErrAuthInvalidRequest,
- },
- {
- name: "invalid authorize request with missing response_type",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["response_type"] = ""
- return req
- }(),
- },
- wantErr: ErrAuthInvalidRequest,
- },
- {
- name: "invalid authorize request with unsupported response_type",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["response_type"] = "id_token"
- return req
- }(),
- },
- wantErr: ErrAuthUnsupportedResponseType,
- },
- {
- name: "invalid authorize request with client_id not found",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- return testAuthorizeReq(s, "non-existent-client-id")
- }(),
- },
- wantErr: ErrAuthInvalidClientID,
- },
- {
- name: "invalid authorize request with client_id not allowed by provider",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: func() *logical.Request {
- req := testProviderReq(s, clientID)
- req.Data["allowed_client_ids"] = []string{"not-client-id"}
- return req
- }(),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- },
- wantErr: ErrAuthUnauthorizedClient,
- },
- {
- name: "invalid authorize request with missing redirect_uri",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["redirect_uri"] = ""
- return req
- }(),
- },
- wantErr: ErrAuthInvalidRequest,
- },
- {
- name: "invalid authorize request with redirect_uri not allowed by client",
- args: args{
- entityID: entityID,
- clientReq: func() *logical.Request {
- req := testClientReq(s)
- req.Data["redirect_uris"] = []string{"https://not.redirect.uri:8251/callback"}
- return req
- }(),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- },
- wantErr: ErrAuthInvalidRedirectURI,
- },
- {
- name: "invalid authorize request with request parameter provided",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["request"] = "header.payload.signature"
- return req
- }(),
- },
- wantErr: ErrAuthRequestNotSupported,
- },
- {
- name: "invalid authorize request with request_uri parameter provided",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["request_uri"] = "https://client.example.org/request.jwt"
- return req
- }(),
- },
- wantErr: ErrAuthRequestURINotSupported,
- },
- {
- name: "invalid authorize request with identity entity not associated with the request",
- args: args{
- entityID: "",
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- },
- wantErr: ErrAuthAccessDenied,
- },
- {
- name: "invalid authorize request with identity entity ID not found",
- args: args{
- entityID: "non-existent-entity",
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- },
- wantErr: ErrAuthAccessDenied,
- },
- {
- name: "invalid authorize request with entity not found in client assignment",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, "not-entity-id", ""),
- authorizeReq: testAuthorizeReq(s, clientID),
- },
- wantErr: ErrAuthAccessDenied,
- },
- {
- name: "invalid authorize request with group not found in client assignment",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, "", "not-group-id"),
- authorizeReq: testAuthorizeReq(s, clientID),
- },
- wantErr: ErrAuthAccessDenied,
- },
- {
- name: "invalid authorize request with negative max_age",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["max_age"] = "-1"
- return req
- }(),
- },
- wantErr: ErrAuthInvalidRequest,
- },
- {
- name: "invalid authorize request with invalid code_challenge_method",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["code_challenge_method"] = "S512"
- req.Data["code_challenge"] = "43_char_min_abcdefghijklmnopqrstuvwxyzabcde"
- return req
- }(),
- },
- wantErr: ErrAuthInvalidRequest,
- },
- {
- name: "invalid authorize request with code_challenge length < 43 characters",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["code_challenge_method"] = "S256"
- req.Data["code_challenge"] = ""
- return req
- }(),
- },
- wantErr: ErrAuthInvalidRequest,
- },
- {
- name: "invalid authorize request with code_challenge length > 128 characters",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["code_challenge_method"] = "S256"
- req.Data["code_challenge"] = `
- 129_char_abcdefghijklmnopqrstuvwxyzabcd
- 129_char_abcdefghijklmnopqrstuvwxyzabcd
- 129_char_abcdefghijklmnopqrstuvwxyzabcd
- `
- return req
- }(),
- },
- wantErr: ErrAuthInvalidRequest,
- },
- {
- name: "valid authorize request with empty nonce",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- delete(req.Data, "nonce")
- return req
- }(),
- },
- },
- {
- name: "valid authorize request with empty state",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["state"] = ""
- return req
- }(),
- },
- },
- {
- name: "active re-authentication required with token creation time exceeding max_age requirement",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["max_age"] = "30"
- return req
- }(),
- vaultTokenCreationTime: func() time.Time {
- return time.Now().Add(-time.Minute)
- },
- },
- wantErr: ErrAuthMaxAgeReAuthenticate,
- },
- {
- name: "valid authorize request with token creation time within max_age requirement",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["max_age"] = "30"
- return req
- }(),
- vaultTokenCreationTime: func() time.Time {
- return time.Now()
- },
- },
- },
- {
- name: "valid authorize request using update operation (HTTP POST)",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- },
- },
- {
- name: "valid authorize request using read operation (HTTP GET)",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Operation = logical.ReadOperation
- return req
- }(),
- },
- },
- {
- name: "valid authorize request using client assignment with only entity membership",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, ""),
- authorizeReq: testAuthorizeReq(s, clientID),
- },
- },
- {
- name: "valid authorize request using client assignment with only group membership",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, "", groupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- },
- },
- {
- name: "valid authorize request using client assignment with inherited group membership",
- args: args{
- entityID: entityID,
- clientReq: testClientReq(s),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, "", parentGroupID),
- authorizeReq: testAuthorizeReq(s, clientID),
- },
- },
- {
- name: "valid authorize request with port-agnostic loopback redirect_uri 127.0.0.1",
- args: args{
- entityID: entityID,
- clientReq: func() *logical.Request {
- req := testClientReq(s)
- req.Data["redirect_uris"] = []string{"http://127.0.0.1/callback"}
- return req
- }(),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["redirect_uri"] = "http://127.0.0.1:51004/callback"
- return req
- }(),
- },
- },
- {
- name: "valid authorize request with port-agnostic loopback redirect_uri 127.0.0.1 with port",
- args: args{
- entityID: entityID,
- clientReq: func() *logical.Request {
- req := testClientReq(s)
- req.Data["redirect_uris"] = []string{"http://127.0.0.1:8251/callback"}
- return req
- }(),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["redirect_uri"] = "http://127.0.0.1:51005/callback"
- return req
- }(),
- },
- },
- {
- name: "valid authorize request with port-agnostic loopback redirect_uri localhost",
- args: args{
- entityID: entityID,
- clientReq: func() *logical.Request {
- req := testClientReq(s)
- req.Data["redirect_uris"] = []string{"http://localhost:8251/callback"}
- return req
- }(),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["redirect_uri"] = "http://localhost:51006/callback"
- return req
- }(),
- },
- },
- {
- name: "valid authorize request with port-agnostic loopback redirect_uri [::1]",
- args: args{
- entityID: entityID,
- clientReq: func() *logical.Request {
- req := testClientReq(s)
- req.Data["redirect_uris"] = []string{"http://[::1]:8251/callback"}
- return req
- }(),
- providerReq: testProviderReq(s, clientID),
- assignmentReq: testAssignmentReq(s, entityID, groupID),
- authorizeReq: func() *logical.Request {
- req := testAuthorizeReq(s, clientID)
- req.Data["redirect_uri"] = "http://[::1]:51007/callback"
- return req
- }(),
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- // Create a token entry to associate with the authorize request
- creationTime := time.Now()
- if tt.args.vaultTokenCreationTime != nil {
- creationTime = tt.args.vaultTokenCreationTime()
- }
- te := &logical.TokenEntry{
- Path: "test",
- Policies: []string{"default"},
- TTL: time.Hour * 24,
- CreationTime: creationTime.Unix(),
- }
- testMakeTokenDirectly(t, c.tokenStore, te)
- require.NotEmpty(t, te.ID)
-
- // Update the assignment
- tt.args.assignmentReq.Operation = logical.UpdateOperation
- resp, err := c.identityStore.HandleRequest(ctx, tt.args.assignmentReq)
- expectSuccess(t, resp, err)
-
- // Update the client
- tt.args.clientReq.Operation = logical.UpdateOperation
- resp, err = c.identityStore.HandleRequest(ctx, tt.args.clientReq)
- expectSuccess(t, resp, err)
-
- // Update the provider
- tt.args.providerReq.Operation = logical.UpdateOperation
- resp, err = c.identityStore.HandleRequest(ctx, tt.args.providerReq)
- expectSuccess(t, resp, err)
-
- // Send the request to the OIDC authorize endpoint
- tt.args.authorizeReq.EntityID = tt.args.entityID
- tt.args.authorizeReq.ClientToken = te.ID
- resp, err = c.identityStore.HandleRequest(ctx, tt.args.authorizeReq)
-
- // Parse the response
- var authRes struct {
- Code string `json:"code"`
- State string `json:"state"`
- Error string `json:"error"`
- ErrorDescription string `json:"error_description"`
- }
- require.NotNil(t, resp)
- require.NotNil(t, resp.Data[logical.HTTPRawBody])
- require.NotNil(t, resp.Data[logical.HTTPStatusCode])
- require.NotNil(t, resp.Data[logical.HTTPContentType])
- require.Equal(t, "application/json", resp.Data[logical.HTTPContentType].(string))
- require.NoError(t, json.Unmarshal(resp.Data["http_raw_body"].([]byte), &authRes))
-
- if tt.wantErr != "" {
- // Assert that we receive the expected error code and description
- require.Equal(t, tt.wantErr, authRes.Error)
- require.NotEmpty(t, authRes.ErrorDescription)
-
- // Assert that we receive the expected status code
- statusCode := resp.Data[logical.HTTPStatusCode].(int)
- switch authRes.Error {
- case ErrAuthServerError:
- require.Equal(t, http.StatusInternalServerError, statusCode)
- default:
- require.Equal(t, http.StatusBadRequest, statusCode)
- }
- return
- }
-
- // Assert that we receive an authorization code (base62) and state
- expectSuccess(t, resp, err)
- require.Equal(t, http.StatusOK, resp.Data[logical.HTTPStatusCode].(int))
- require.Regexp(t, authCodeRegex, authRes.Code)
- require.Equal(t, tt.args.authorizeReq.Data["state"], authRes.State)
- require.Empty(t, authRes.Error)
- require.Empty(t, authRes.ErrorDescription)
- })
- }
-}
-
-// setupOIDCCommon creates all of the resources needed to test a Vault OIDC provider.
-// Returns the entity ID, group ID, client ID, client secret to be used in tests.
-func setupOIDCCommon(t *testing.T, c *Core, s logical.Storage) (string, string, string, string, string) {
- t.Helper()
- ctx := namespace.RootContext(nil)
-
- // Create a key
- resp, err := c.identityStore.HandleRequest(ctx, testKeyReq(s, []string{"*"}, "RS256"))
- expectSuccess(t, resp, err)
-
- // Create an entity
- resp, err = c.identityStore.HandleRequest(ctx, testEntityReq(s))
- expectSuccess(t, resp, err)
- require.NotNil(t, resp.Data["id"])
- entityID := resp.Data["id"].(string)
-
- // Create a group
- resp, err = c.identityStore.HandleRequest(ctx, testGroupReq(s, "test-group",
- []string{entityID}, nil))
- expectSuccess(t, resp, err)
- require.NotNil(t, resp.Data["id"])
- groupID := resp.Data["id"].(string)
-
- // Create a parent group
- resp, err = c.identityStore.HandleRequest(ctx, testGroupReq(s, "test-parent-group",
- nil, []string{groupID}))
- expectSuccess(t, resp, err)
- require.NotNil(t, resp.Data["id"])
- parentGroupID := resp.Data["id"].(string)
-
- // Create an assignment
- resp, err = c.identityStore.HandleRequest(ctx, testAssignmentReq(s, entityID, groupID))
- expectSuccess(t, resp, err)
-
- // Create a client
- resp, err = c.identityStore.HandleRequest(ctx, testClientReq(s))
- expectSuccess(t, resp, err)
-
- // Read the client ID and secret
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Storage: s,
- Path: "oidc/client/test-client",
- Operation: logical.ReadOperation,
- })
- expectSuccess(t, resp, err)
- require.NotNil(t, resp.Data["client_id"])
- require.NotNil(t, resp.Data["client_secret"])
- clientID := resp.Data["client_id"].(string)
- clientSecret := resp.Data["client_secret"].(string)
-
- // Create a custom scope
- template := `{
- "name": {{identity.entity.name}},
- "contact": {
- "email": {{identity.entity.metadata.email}},
- "phone_number": {{identity.entity.metadata.phone_number}}
- },
- "groups": {{identity.entity.groups.names}}
- }`
- resp, err = c.identityStore.HandleRequest(ctx, testScopeReq(s, "test-scope", template))
- expectSuccess(t, resp, err)
-
- // Create a custom scope that has a conflicting claim
- template = `{
- "username": {{identity.entity.name}},
- "contact": {
- "user_email": {{identity.entity.metadata.email}},
- "phone_number": {{identity.entity.metadata.phone_number}}
- }
- }`
- resp, err = c.identityStore.HandleRequest(ctx, testScopeReq(s, "conflict", template))
- expectSuccess(t, resp, err)
-
- // Create a provider
- resp, err = c.identityStore.HandleRequest(ctx, testProviderReq(s, clientID))
- expectSuccess(t, resp, err)
-
- return entityID, groupID, parentGroupID, clientID, clientSecret
-}
-
-// resetCommonOIDCConfig resets the state of common configuration resources
-// (i.e., created by setupOIDCCommon) that are modified during tests. This
-// enables the tests to continue operating using the same underlying storage
-// throughout many test cases that modify the configuration resources.
-func resetCommonOIDCConfig(t *testing.T, s logical.Storage, c *Core, entityID, groupID, clientID string) {
- ctx := namespace.RootContext(nil)
-
- req := testAssignmentReq(s, entityID, groupID)
- req.Operation = logical.UpdateOperation
- resp, err := c.identityStore.HandleRequest(ctx, req)
- expectSuccess(t, resp, err)
-
- req = testClientReq(s)
- req.Operation = logical.UpdateOperation
- resp, err = c.identityStore.HandleRequest(ctx, req)
- expectSuccess(t, resp, err)
-
- req = testProviderReq(s, clientID)
- req.Operation = logical.UpdateOperation
- resp, err = c.identityStore.HandleRequest(ctx, req)
- expectSuccess(t, resp, err)
-}
-
-func testTokenReq(s logical.Storage, code, clientID, clientSecret string) *logical.Request {
- return &logical.Request{
- Storage: s,
- Path: "oidc/provider/test-provider/token",
- Operation: logical.UpdateOperation,
- Headers: map[string][]string{
- "Authorization": {basicAuthHeader(clientID, clientSecret)},
- },
- Data: map[string]interface{}{
- // The code is unknown until returned from the authorization endpoint
- "code": code,
- "grant_type": "authorization_code",
- "redirect_uri": "https://localhost:8251/callback",
- },
- }
-}
-
-func testAuthorizeReq(s logical.Storage, clientID string) *logical.Request {
- return &logical.Request{
- Storage: s,
- Path: "oidc/provider/test-provider/authorize",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "client_id": clientID,
- "scope": "openid",
- "redirect_uri": "https://localhost:8251/callback",
- "response_type": "code",
- "state": "abcdefg",
- "nonce": "hijklmn",
- },
- }
-}
-
-func testAssignmentReq(s logical.Storage, entityID, groupID string) *logical.Request {
- return &logical.Request{
- Storage: s,
- Path: "oidc/assignment/test-assignment",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "entity_ids": []string{entityID},
- "group_ids": []string{groupID},
- },
- }
-}
-
-func testClientReq(s logical.Storage) *logical.Request {
- return &logical.Request{
- Storage: s,
- Path: "oidc/client/test-client",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- "redirect_uris": []string{"https://localhost:8251/callback"},
- "assignments": []string{"test-assignment"},
- "id_token_ttl": "24h",
- "access_token_ttl": "24h",
- },
- }
-}
-
-func testProviderReq(s logical.Storage, clientID string) *logical.Request {
- return &logical.Request{
- Storage: s,
- Path: "oidc/provider/test-provider",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "allowed_client_ids": []string{clientID},
- "scopes_supported": []string{"test-scope", "conflict"},
- },
- }
-}
-
-func testEntityReq(s logical.Storage) *logical.Request {
- return &logical.Request{
- Storage: s,
- Path: "entity",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "test-entity",
- "metadata": map[string]string{
- "email": "test@hashicorp.com",
- "phone_number": "123-456-7890",
- },
- },
- }
-}
-
-func testKeyReq(s logical.Storage, allowedClientIDs []string, alg string) *logical.Request {
- return &logical.Request{
- Storage: s,
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "allowed_client_ids": allowedClientIDs,
- "algorithm": alg,
- },
- }
-}
-
-func testGroupReq(s logical.Storage, name string, entityIDs, groupIDs []string) *logical.Request {
- return &logical.Request{
- Storage: s,
- Path: "group",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": name,
- "member_entity_ids": entityIDs,
- "member_group_ids": groupIDs,
- },
- }
-}
-
-func testScopeReq(s logical.Storage, name, template string) *logical.Request {
- return &logical.Request{
- Storage: s,
- Path: fmt.Sprintf("oidc/scope/%s", name),
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "template": template,
- },
- }
-}
-
-func basicAuthHeader(username, password string) string {
- auth := fmt.Sprintf("%s:%s", username, password)
- encoded := base64.StdEncoding.EncodeToString([]byte(auth))
- return fmt.Sprintf("Basic %s", encoded)
-}
-
-// TestOIDC_Path_OIDC_ProviderReadPublicKey_ProviderDoesNotExist tests that the
-// path can handle the read operation when the provider does not exist
-func TestOIDC_Path_OIDC_ProviderReadPublicKey_ProviderDoesNotExist(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Read "test-provider" .well-known keys
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider/.well-known/keys",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectedResp := &logical.Response{}
- if resp != expectedResp && err != nil {
- t.Fatalf("expected empty response but got success; error:\n%v\nresp: %#v", err, resp)
- }
-}
-
-// TestOIDC_Path_OIDC_ProviderReadPublicKey tests the provider .well-known
-// keys endpoint read operations
-func TestOIDC_Path_OIDC_ProviderReadPublicKey(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test key "test-key-1"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key-1",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "verification_ttl": "2m",
- "rotation_period": "2m",
- },
- Storage: storage,
- })
-
- // Create a test client "test-client-1"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client-1",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "key": "test-key-1",
- "id_token_ttl": "1m",
- },
- })
-
- // Create a test client "test-client-2" that also uses "test-key-1"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client-2",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "key": "test-key-1",
- "id_token_ttl": "1m",
- },
- })
-
- // get the clientID for "test-client-1"
- resp, _ := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client-1",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- clientID := resp.Data["client_id"].(string)
-
- // Create a test provider "test-provider" and allow all client IDs -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "issuer": "https://example.com:8200",
- "allowed_client_ids": []string{"*"},
- },
- })
- expectSuccess(t, resp, err)
-
- // Read "test-provider" .well-known keys
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider/.well-known/keys",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // at this point only 2 public keys are expected since both clients use
- // the same key "test-key-1"
- assertRespPublicKeyCount(t, resp, 2)
-
- // Create a test key "test-key-2"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key-2",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "verification_ttl": "2m",
- "rotation_period": "2m",
- },
- Storage: storage,
- })
-
- // Create a test client "test-client-2"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client-2",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "key": "test-key-2",
- "id_token_ttl": "1m",
- },
- })
-
- // Read "test-provider" .well-known keys
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider/.well-known/keys",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- assertRespPublicKeyCount(t, resp, 4)
-
- // Update the test provider "test-provider" to only allow test-client-1 -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.UpdateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "allowed_client_ids": []string{clientID},
- },
- })
- expectSuccess(t, resp, err)
-
- // Read "test-provider" .well-known keys
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider/.well-known/keys",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- assertRespPublicKeyCount(t, resp, 2)
-}
-
-func TestOIDC_Path_OIDC_Client_Type(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- tests := []struct {
- name string
- createClientType clientType
- updateClientType clientType
- wantCreateErr bool
- wantUpdateErr bool
- }{
- {
- name: "create confidential client and update to public client",
- createClientType: confidential,
- updateClientType: public,
- wantUpdateErr: true,
- },
- {
- name: "create confidential client and update to confidential client",
- createClientType: confidential,
- updateClientType: confidential,
- },
- {
- name: "create public client and update to confidential client",
- createClientType: public,
- updateClientType: confidential,
- wantUpdateErr: true,
- },
- {
- name: "create public client and update to public client",
- createClientType: public,
- updateClientType: public,
- },
- {
- name: "create an invalid client type",
- createClientType: clientType(300),
- wantCreateErr: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- // Create a client with the given client type
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "key": "test-key",
- "client_type": tt.createClientType.String(),
- },
- })
- if tt.wantCreateErr {
- expectError(t, resp, err)
- return
- }
- expectSuccess(t, resp, err)
-
- // Read the client
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Assert that the client type is properly set
- clientType := resp.Data["client_type"].(string)
- require.Equal(t, tt.createClientType.String(), clientType)
-
- // Assert that all client types have a client ID
- clientID := resp.Data["client_id"].(string)
- require.Len(t, clientID, clientIDLength)
-
- // Assert that confidential clients have a client secret
- if tt.createClientType == confidential {
- clientSecret := resp.Data["client_secret"].(string)
- require.Contains(t, clientSecret, clientSecretPrefix)
- }
-
- // Assert that public clients do not have a client secret
- if tt.createClientType == public {
- _, ok := resp.Data["client_secret"]
- require.False(t, ok)
- }
-
- // Update the client and expect error if the type is different
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.UpdateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "key": "test-key",
- "client_type": tt.updateClientType.String(),
- },
- })
- if tt.wantUpdateErr {
- expectError(t, resp, err)
- } else {
- expectSuccess(t, resp, err)
- }
-
- // Delete the client
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- })
- }
-}
-
-// TestOIDC_Path_OIDC_ProviderClient_DefaultKey tests that a
-// client uses the default key if none provided at creation time.
-func TestOIDC_Path_OIDC_ProviderClient_DefaultKey(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- require.NoError(t, c.identityStore.storeOIDCDefaultResources(ctx, c.identityStore.view))
-
- // Create a test client "test-client" without a key param
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.CreateOperation,
- Storage: c.identityStore.view,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-client" to validate it uses the default key
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.ReadOperation,
- Storage: c.identityStore.view,
- })
- expectSuccess(t, resp, err)
-
- // Assert that the client uses the default key
- require.Equal(t, defaultKeyName, resp.Data["key"].(string))
-}
-
-// TestOIDC_Path_OIDC_ProviderClient_NilKeyEntry tests that a client cannot be
-// created when a key parameter is provided but the key does not exist
-func TestOIDC_Path_OIDC_ProviderClient_NilKeyEntry(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test client "test-client1" with a non-existent key -- should fail
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client1",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- },
- Storage: storage,
- })
- expectError(t, resp, err)
- // validate error message
- expectedStrings := map[string]interface{}{
- "key \"test-key\" does not exist": true,
- }
- expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
-}
-
-// TestOIDC_Path_OIDC_ProviderClient_InvalidTokenTTL tests the TokenTTL validation
-func TestOIDC_Path_OIDC_ProviderClient_InvalidTokenTTL(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test key "test-key"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "verification_ttl": int64(60),
- },
- Storage: storage,
- })
-
- // Create a test client "test-client" with an id_token_ttl longer than the
- // verification_ttl -- should fail with error
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- "id_token_ttl": int64(3600),
- },
- Storage: storage,
- })
- expectError(t, resp, err)
- // validate error message
- expectedStrings := map[string]interface{}{
- "a client's id_token_ttl cannot be greater than the verification_ttl of the key it references": true,
- }
- expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
-
- // Read "test-client"
- respReadTestClient, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- // Ensure that "test-client" was not created
- expectSuccess(t, respReadTestClient, err)
- if respReadTestClient != nil {
- t.Fatalf("Expected a nil response but instead got:\n%#v", respReadTestClient)
- }
-}
-
-// TestOIDC_Path_OIDC_ProviderClient_UpdateKey tests that a client
-// does not allow key modification on Update operations
-func TestOIDC_Path_OIDC_ProviderClient_UpdateKey(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test key "test-key1"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key1",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "verification_ttl": "2m",
- "rotation_period": "2m",
- },
- Storage: storage,
- })
-
- // Create a test key "test-key2"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key2",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "verification_ttl": "2m",
- "rotation_period": "2m",
- },
- Storage: storage,
- })
-
- // Create a test client "test-client" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "key": "test-key1",
- "id_token_ttl": "1m",
- },
- })
- expectSuccess(t, resp, err)
-
- // Update the test client "test-client" -- should fail
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.UpdateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "key": "test-key2",
- "id_token_ttl": "1m",
- },
- })
- expectError(t, resp, err)
- // validate error message
- expectedStrings := map[string]interface{}{
- "key modification is not allowed": true,
- }
- expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
-}
-
-// TestOIDC_Path_OIDC_ProviderClient_AssignmentDoesNotExist tests that a client
-// cannot be created with assignments that do not exist
-func TestOIDC_Path_OIDC_ProviderClient_AssignmentDoesNotExist(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test key "test-key"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "verification_ttl": "2m",
- "rotation_period": "2m",
- },
- Storage: storage,
- })
-
- // Create a test client "test-client" -- should fail
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "key": "test-key",
- "assignments": "my-assignment",
- },
- })
- expectError(t, resp, err)
- // validate error message
- expectedStrings := map[string]interface{}{
- "assignment \"my-assignment\" does not exist": true,
- }
- expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
-}
-
-// TestOIDC_Path_OIDC_ProviderClient tests CRUD operations for clients
-func TestOIDC_Path_OIDC_ProviderClient(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test key "test-key"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "verification_ttl": "2m",
- "rotation_period": "2m",
- },
- Storage: storage,
- })
-
- // Create a test client "test-client" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "key": "test-key",
- "id_token_ttl": "1m",
- },
- })
- expectSuccess(t, resp, err)
-
- // Read "test-client" and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected := map[string]interface{}{
- "redirect_uris": []string{},
- "assignments": []string{},
- "key": "test-key",
- "id_token_ttl": int64(60),
- "access_token_ttl": int64(86400),
- "client_id": resp.Data["client_id"],
- "client_secret": resp.Data["client_secret"],
- "client_type": confidential.String(),
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
- clientID := resp.Data["client_id"].(string)
- if len(clientID) != clientIDLength {
- t.Fatalf("client_id format is incorrect: %#v", clientID)
- }
- clientSecret := resp.Data["client_secret"].(string)
- if !strings.HasPrefix(clientSecret, clientSecretPrefix) {
- t.Fatalf("client_secret format is incorrect: %#v", clientSecret)
- }
- if len(clientSecret) != clientSecretLength+len(clientSecretPrefix) {
- t.Fatalf("client_secret format is incorrect: %#v", clientSecret)
- }
-
- // Create a test assignment "my-assignment" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/my-assignment",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Update "test-client" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "redirect_uris": "http://localhost:3456/callback",
- "assignments": "my-assignment",
- "key": "test-key",
- "id_token_ttl": "90s",
- "access_token_ttl": "1m",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-client" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected = map[string]interface{}{
- "redirect_uris": []string{"http://localhost:3456/callback"},
- "assignments": []string{"my-assignment"},
- "key": "test-key",
- "id_token_ttl": int64(90),
- "access_token_ttl": int64(60),
- "client_id": resp.Data["client_id"],
- "client_secret": resp.Data["client_secret"],
- "client_type": confidential.String(),
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Delete test-client -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-client" again and validate
- resp, _ = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- if resp != nil {
- t.Fatalf("expected nil but got resp: %#v", resp)
- }
-}
-
-// TestOIDC_Path_OIDC_ProviderClient_DeDuplication tests that a
-// client doesn't have duplicate redirect URIs or Assignments
-func TestOIDC_Path_OIDC_ProviderClient_Deduplication(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test key "test-key"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "verification_ttl": "2m",
- "rotation_period": "2m",
- },
- Storage: storage,
- })
-
- // Create a test assignment "test-assignment1" -- should succeed
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment1",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
-
- // Create a test client "test-client" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "key": "test-key",
- "id_token_ttl": "1m",
- "assignments": []string{"test-assignment1", "test-assignment1"},
- "redirect_uris": []string{"http://example.com", "http://notduplicate.com", "http://example.com"},
- "client_type": public.String(),
- },
- })
- expectSuccess(t, resp, err)
-
- // Read "test-client" and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected := map[string]interface{}{
- "redirect_uris": []string{"http://example.com", "http://notduplicate.com"},
- "assignments": []string{"test-assignment1"},
- "key": "test-key",
- "id_token_ttl": int64(60),
- "access_token_ttl": int64(86400),
- "client_id": resp.Data["client_id"],
- "client_type": public.String(),
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-}
-
-// TestOIDC_Path_OIDC_ProviderClient_Update tests Update operations for clients
-func TestOIDC_Path_OIDC_ProviderClient_Update(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test key "test-key"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "verification_ttl": "2m",
- "rotation_period": "2m",
- },
- Storage: storage,
- })
-
- // Create a test assignment "my-assignment" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/my-assignment",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Create a test client "test-client" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "redirect_uris": "http://localhost:3456/callback",
- "assignments": "my-assignment",
- "key": "test-key",
- "id_token_ttl": "2m",
- "access_token_ttl": "1h",
- },
- })
- expectSuccess(t, resp, err)
-
- // Read "test-client" and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected := map[string]interface{}{
- "redirect_uris": []string{"http://localhost:3456/callback"},
- "assignments": []string{"my-assignment"},
- "key": "test-key",
- "id_token_ttl": int64(120),
- "access_token_ttl": int64(3600),
- "client_id": resp.Data["client_id"],
- "client_secret": resp.Data["client_secret"],
- "client_type": confidential.String(),
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Update "test-client" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "redirect_uris": "http://localhost:3456/callback2",
- "id_token_ttl": "30",
- "access_token_ttl": "1m",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-client" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected = map[string]interface{}{
- "redirect_uris": []string{"http://localhost:3456/callback2"},
- "assignments": []string{"my-assignment"},
- "key": "test-key",
- "id_token_ttl": int64(30),
- "access_token_ttl": int64(60),
- "client_id": resp.Data["client_id"],
- "client_secret": resp.Data["client_secret"],
- "client_type": confidential.String(),
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-}
-
-// TestOIDC_Path_OIDC_ProviderClient_List tests the List operation for clients
-func TestOIDC_Path_OIDC_ProviderClient_List(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := c.identityStore.view
-
- // Prepare two clients, test-client1 and test-client2
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client1",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "id_token_ttl": "1m",
- },
- })
-
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client2",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "id_token_ttl": "1m",
- },
- })
-
- // list clients
- respListClients, listErr := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client",
- Operation: logical.ListOperation,
- Storage: storage,
- })
- expectSuccess(t, respListClients, listErr)
-
- // validate list response
- expectedStrings := map[string]interface{}{"test-client1": true, "test-client2": true}
- expectStrings(t, respListClients.Data["keys"].([]string), expectedStrings)
-
- // delete test-client2
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client2",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
-
- // list clients again and validate response
- respListClientAfterDelete, listErrAfterDelete := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client",
- Operation: logical.ListOperation,
- Storage: storage,
- })
- expectSuccess(t, respListClientAfterDelete, listErrAfterDelete)
-
- // validate list response
- delete(expectedStrings, "test-client2")
- expectStrings(t, respListClientAfterDelete.Data["keys"].([]string), expectedStrings)
-}
-
-func TestOIDC_Path_OIDC_Client_List_KeyInfo(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
-
- // Create clients with different parameters
- clients := map[string]interface{}{
- "c1": map[string]interface{}{
- "id_token_ttl": "5m",
- "access_token_ttl": "10m",
- "assignments": []string{},
- "redirect_uris": []string{"http://127.0.0.1:8250"},
- "client_type": "confidential",
- "key": "default",
- },
- "c2": map[string]interface{}{
- "id_token_ttl": "24h",
- "access_token_ttl": "5m",
- "assignments": []string{allowAllAssignmentName},
- "redirect_uris": []string{"https://localhost:9702/auth/oidc-callback"},
- "client_type": "public",
- "key": "default",
- },
- }
- for name, client := range clients {
- input := client.(map[string]interface{})
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/" + name,
- Operation: logical.CreateOperation,
- Storage: c.identityStore.view,
- Data: input,
- })
- expectSuccess(t, resp, err)
- }
-
- // List clients
- req := &logical.Request{
- Path: "oidc/client",
- Operation: logical.ListOperation,
- Storage: c.identityStore.view,
- Data: make(map[string]interface{}),
- }
- resp, err := c.identityStore.HandleRequest(ctx, req)
- expectSuccess(t, resp, err)
- require.NotNil(t, resp.Data["key_info"])
- require.NotNil(t, resp.Data["keys"])
- keys := resp.Data["keys"].([]string)
- keyInfo := resp.Data["key_info"].(map[string]interface{})
- require.Equal(t, len(keys), len(keyInfo))
-
- // Assert the clients returned have additional key info
- for name, details := range keyInfo {
- actual, _ := details.(map[string]interface{})
- require.NotNil(t, clients[name])
- expected := clients[name].(map[string]interface{})
- require.Contains(t, keys, name)
-
- idTokenTTL, _ := parseutil.ParseDurationSecond(expected["id_token_ttl"].(string))
- accessTokenTTL, _ := parseutil.ParseDurationSecond(expected["access_token_ttl"].(string))
- require.EqualValues(t, idTokenTTL.Seconds(), actual["id_token_ttl"])
- require.EqualValues(t, accessTokenTTL.Seconds(), actual["access_token_ttl"])
- require.Equal(t, expected["redirect_uris"], actual["redirect_uris"])
- require.Equal(t, expected["assignments"], actual["assignments"])
- require.Equal(t, expected["key"], actual["key"])
- require.Equal(t, expected["client_type"], actual["client_type"])
- require.NotEmpty(t, actual["client_id"])
- require.Empty(t, actual["client_secret"])
- }
-}
-
-// TestOIDC_pathOIDCClientExistenceCheck tests pathOIDCClientExistenceCheck
-func TestOIDC_pathOIDCClientExistenceCheck(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- clientName := "test"
-
- // Expect nil with empty storage
- exists, err := c.identityStore.pathOIDCClientExistenceCheck(
- ctx,
- &logical.Request{
- Storage: storage,
- },
- &framework.FieldData{
- Raw: map[string]interface{}{"name": clientName},
- Schema: map[string]*framework.FieldSchema{
- "name": {
- Type: framework.TypeString,
- },
- },
- },
- )
- if err != nil {
- t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
- }
- if exists {
- t.Fatalf("Expected existence check to return false but instead returned: %t", exists)
- }
-
- // Populte storage with a client
- client := &client{}
- entry, _ := logical.StorageEntryJSON(clientPath+clientName, client)
- if err := storage.Put(ctx, entry); err != nil {
- t.Fatalf("writing to in mem storage failed")
- }
-
- // Expect true with a populated storage
- exists, err = c.identityStore.pathOIDCClientExistenceCheck(
- ctx,
- &logical.Request{
- Storage: storage,
- },
- &framework.FieldData{
- Raw: map[string]interface{}{"name": clientName},
- Schema: map[string]*framework.FieldSchema{
- "name": {
- Type: framework.TypeString,
- },
- },
- },
- )
- if err != nil {
- t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
- }
- if !exists {
- t.Fatalf("Expected existence check to return true but instead returned: %t", exists)
- }
-}
-
-// TestOIDC_Path_OIDC_ProviderScope_ReservedName tests that the reserved name
-// "openid" cannot be used when creating a scope
-func TestOIDC_Path_OIDC_ProviderScope_ReservedName(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test scope "test-scope" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/openid",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
- expectError(t, resp, err)
- // validate error message
- expectedStrings := map[string]interface{}{
- "the \"openid\" scope name is reserved": true,
- }
- expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
-}
-
-// TestOIDC_Path_OIDC_ProviderScope_TemplateValidation tests that the template
-// validation does not allow restricted claims
-func TestOIDC_Path_OIDC_ProviderScope_TemplateValidation(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- testCases := []struct {
- templ string
- restrictedKey string
- }{
- {
- templ: `{"aud": "client-12345", "other": "test"}`,
- restrictedKey: "aud",
- },
- {
- templ: `{"exp": 1311280970, "other": "test"}`,
- restrictedKey: "exp",
- },
- {
- templ: `{"iat": 1311280970, "other": "test"}`,
- restrictedKey: "iat",
- },
- {
- templ: `{"iss": "https://openid.c2id.com", "other": "test"}`,
- restrictedKey: "iss",
- },
- {
- templ: `{"namespace": "n-0S6_WzA2Mj", "other": "test"}`,
- restrictedKey: "namespace",
- },
- {
- templ: `{"sub": "alice", "other": "test"}`,
- restrictedKey: "sub",
- },
- {
- templ: `{"auth_time": 123456, "other": "test"}`,
- restrictedKey: "auth_time",
- },
- {
- templ: `{"at_hash": "abcdefg", "other": "test"}`,
- restrictedKey: "at_hash",
- },
- {
- templ: `{"c_hash": "hijklmn", "other": "test"}`,
- restrictedKey: "c_hash",
- },
- }
- for _, tc := range testCases {
- encodedTempl := base64.StdEncoding.EncodeToString([]byte(tc.templ))
- // Create a test scope "test-scope" -- should fail
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "template": encodedTempl,
- "description": "my-description",
- },
- })
- expectError(t, resp, err)
- errString := fmt.Sprintf(
- "top level key %q not allowed. Restricted keys: iat, aud, exp, iss, sub, namespace, nonce, auth_time, at_hash, c_hash",
- tc.restrictedKey,
- )
- // validate error message
- expectedStrings := map[string]interface{}{
- errString: true,
- }
- expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
- }
-}
-
-// TestOIDC_Path_OIDC_ProviderScope tests CRUD operations for scopes
-func TestOIDC_Path_OIDC_ProviderScope(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test scope "test-scope" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-scope" and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected := map[string]interface{}{
- "template": "",
- "description": "",
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- templ := `{ "groups": {{identity.entity.groups.names}} }`
- encodedTempl := base64.StdEncoding.EncodeToString([]byte(templ))
- // Update "test-scope" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "template": encodedTempl,
- "description": "my-description",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-scope" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected = map[string]interface{}{
- "template": templ,
- "description": "my-description",
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Delete test-scope -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-scope" again and validate
- resp, _ = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- if resp != nil {
- t.Fatalf("expected nil but got resp: %#v", resp)
- }
-}
-
-// TestOIDC_Path_OIDC_ProviderScope_Update tests Update operations for scopes
-func TestOIDC_Path_OIDC_ProviderScope_Update(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- templ := `{ "groups": {{identity.entity.groups.names}} }`
- encodedTempl := base64.StdEncoding.EncodeToString([]byte(templ))
- // Create a test scope "test-scope" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "template": encodedTempl,
- "description": "my-description",
- },
- })
- expectSuccess(t, resp, err)
-
- // Read "test-scope" and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected := map[string]interface{}{
- "template": templ,
- "description": "my-description",
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Update "test-scope" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "template": encodedTempl,
- "description": "my-description-2",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-scope" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected = map[string]interface{}{
- "template": "{ \"groups\": {{identity.entity.groups.names}} }",
- "description": "my-description-2",
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-}
-
-// TestOIDC_Path_OIDC_ProviderScope_List tests the List operation for scopes
-func TestOIDC_Path_OIDC_ProviderScope_List(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Prepare two scopes, test-scope1 and test-scope2
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope1",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
-
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope2",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
-
- // list scopes
- respListScopes, listErr := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope",
- Operation: logical.ListOperation,
- Storage: storage,
- })
- expectSuccess(t, respListScopes, listErr)
-
- // validate list response
- expectedStrings := map[string]interface{}{"test-scope1": true, "test-scope2": true}
- expectStrings(t, respListScopes.Data["keys"].([]string), expectedStrings)
-
- // delete test-scope2
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope2",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
-
- // list scopes again and validate response
- respListScopeAfterDelete, listErrAfterDelete := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope",
- Operation: logical.ListOperation,
- Storage: storage,
- })
- expectSuccess(t, respListScopeAfterDelete, listErrAfterDelete)
-
- // validate list response
- delete(expectedStrings, "test-scope2")
- expectStrings(t, respListScopeAfterDelete.Data["keys"].([]string), expectedStrings)
-}
-
-// TestOIDC_pathOIDCScopeExistenceCheck tests pathOIDCScopeExistenceCheck
-func TestOIDC_pathOIDCScopeExistenceCheck(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- scopeName := "test"
-
- // Expect nil with empty storage
- exists, err := c.identityStore.pathOIDCScopeExistenceCheck(
- ctx,
- &logical.Request{
- Storage: storage,
- },
- &framework.FieldData{
- Raw: map[string]interface{}{"name": scopeName},
- Schema: map[string]*framework.FieldSchema{
- "name": {
- Type: framework.TypeString,
- },
- },
- },
- )
- if err != nil {
- t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
- }
- if exists {
- t.Fatalf("Expected existence check to return false but instead returned: %t", exists)
- }
-
- // Populte storage with a scope
- scope := &scope{}
- entry, _ := logical.StorageEntryJSON(scopePath+scopeName, scope)
- if err := storage.Put(ctx, entry); err != nil {
- t.Fatalf("writing to in mem storage failed")
- }
-
- // Expect true with a populated storage
- exists, err = c.identityStore.pathOIDCScopeExistenceCheck(
- ctx,
- &logical.Request{
- Storage: storage,
- },
- &framework.FieldData{
- Raw: map[string]interface{}{"name": scopeName},
- Schema: map[string]*framework.FieldSchema{
- "name": {
- Type: framework.TypeString,
- },
- },
- },
- )
- if err != nil {
- t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
- }
- if !exists {
- t.Fatalf("Expected existence check to return true but instead returned: %t", exists)
- }
-}
-
-// TestOIDC_Path_OIDC_ProviderScope_DeleteWithExistingProvider tests that a
-// Scope cannot be deleted when it is referenced by a provider
-func TestOIDC_Path_OIDC_ProviderScope_DeleteWithExistingProvider(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test scope "test-scope" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "template": `{"groups": "{{identity.entity.groups.names}}"}`,
- "description": "my-description",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Create a test provider "test-provider"
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "scopes_supported": []string{"test-scope"},
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Delete test-scope -- should fail
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
- expectError(t, resp, err)
- // validate error message
- expectedStrings := map[string]interface{}{
- "unable to delete scope \"test-scope\" because it is currently referenced by these providers: test-provider": true,
- }
- expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
-
- // Read "test-scope" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-}
-
-// TestOIDC_Path_OIDC_ProviderAssignment tests CRUD operations for assignments
-func TestOIDC_Path_OIDC_ProviderAssignment(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test assignment "test-assignment" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-assignment" and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected := map[string]interface{}{
- "group_ids": []string{},
- "entity_ids": []string{},
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Update "test-assignment" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "group_ids": "my-group",
- "entity_ids": "my-entity",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-assignment" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected = map[string]interface{}{
- "group_ids": []string{"my-group"},
- "entity_ids": []string{"my-entity"},
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Delete test-assignment -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-assignment" again and validate
- resp, _ = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- if resp != nil {
- t.Fatalf("expected nil but got resp: %#v", resp)
- }
-}
-
-// TestOIDC_Path_OIDC_ProviderAssignment_DeleteWithExistingClient tests that an
-// assignment cannot be deleted when it is referenced by a client
-func TestOIDC_Path_OIDC_ProviderAssignment_DeleteWithExistingClient(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test assignment "test-assignment" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Create a test key "test-key"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "verification_ttl": "2m",
- "rotation_period": "2m",
- },
- Storage: storage,
- })
-
- // Create a test client "test-client" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "key": "test-key",
- "assignments": []string{"test-assignment"},
- "id_token_ttl": "1m",
- },
- })
- expectSuccess(t, resp, err)
-
- // Delete test-assignment -- should fail
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
- expectError(t, resp, err)
- // validate error message
- expectedStrings := map[string]interface{}{
- "unable to delete assignment \"test-assignment\" because it is currently referenced by these clients: test-client": true,
- }
- expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
-
- // Read "test-assignment" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected := map[string]interface{}{
- "group_ids": []string{},
- "entity_ids": []string{},
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-}
-
-// TestOIDC_Path_OIDC_ProviderAssignment_Update tests Update operations for assignments
-func TestOIDC_Path_OIDC_ProviderAssignment_Update(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test assignment "test-assignment" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "group_ids": "my-group",
- "entity_ids": "my-entity",
- },
- })
- expectSuccess(t, resp, err)
-
- // Read "test-assignment" and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected := map[string]interface{}{
- "group_ids": []string{"my-group"},
- "entity_ids": []string{"my-entity"},
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Update "test-assignment" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "group_ids": "my-group2",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-assignment" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected = map[string]interface{}{
- "group_ids": []string{"my-group2"},
- "entity_ids": []string{"my-entity"},
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-}
-
-// TestOIDC_Path_OIDC_ProviderAssignment_List tests the List operation for assignments
-func TestOIDC_Path_OIDC_ProviderAssignment_List(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Prepare two assignments, test-assignment1 and test-assignment2
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment1",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
-
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment2",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
-
- // list assignments
- respListAssignments, listErr := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment",
- Operation: logical.ListOperation,
- Storage: storage,
- })
- expectSuccess(t, respListAssignments, listErr)
-
- // validate list response
- expectedStrings := map[string]interface{}{"test-assignment1": true, "test-assignment2": true}
- expectStrings(t, respListAssignments.Data["keys"].([]string), expectedStrings)
-
- // delete test-assignment2
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment/test-assignment2",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
-
- // list assignments again and validate response
- respListAssignmentAfterDelete, listErrAfterDelete := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/assignment",
- Operation: logical.ListOperation,
- Storage: storage,
- })
- expectSuccess(t, respListAssignmentAfterDelete, listErrAfterDelete)
-
- // validate list response
- delete(expectedStrings, "test-assignment2")
- expectStrings(t, respListAssignmentAfterDelete.Data["keys"].([]string), expectedStrings)
-}
-
-// TestOIDC_pathOIDCAssignmentExistenceCheck tests pathOIDCAssignmentExistenceCheck
-func TestOIDC_pathOIDCAssignmentExistenceCheck(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- assignmentName := "test"
-
- // Expect nil with empty storage
- exists, err := c.identityStore.pathOIDCAssignmentExistenceCheck(
- ctx,
- &logical.Request{
- Storage: storage,
- },
- &framework.FieldData{
- Raw: map[string]interface{}{"name": assignmentName},
- Schema: map[string]*framework.FieldSchema{
- "name": {
- Type: framework.TypeString,
- },
- },
- },
- )
- if err != nil {
- t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
- }
- if exists {
- t.Fatalf("Expected existence check to return false but instead returned: %t", exists)
- }
-
- // Populate storage with a assignment
- assignment := &assignment{}
- entry, _ := logical.StorageEntryJSON(assignmentPath+assignmentName, assignment)
- if err := storage.Put(ctx, entry); err != nil {
- t.Fatalf("writing to in mem storage failed")
- }
-
- // Expect true with a populated storage
- exists, err = c.identityStore.pathOIDCAssignmentExistenceCheck(
- ctx,
- &logical.Request{
- Storage: storage,
- },
- &framework.FieldData{
- Raw: map[string]interface{}{"name": assignmentName},
- Schema: map[string]*framework.FieldSchema{
- "name": {
- Type: framework.TypeString,
- },
- },
- },
- )
- if err != nil {
- t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
- }
- if !exists {
- t.Fatalf("Expected existence check to return true but instead returned: %t", exists)
- }
-}
-
-// TestOIDC_Path_OIDCProvider tests CRUD operations for providers
-func TestOIDC_Path_OIDCProvider(t *testing.T) {
- redirectAddr := "http://localhost:8200"
- conf := &CoreConfig{
- RedirectAddr: redirectAddr,
- }
- c, _, _ := TestCoreUnsealedWithConfig(t, conf)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test provider "test-provider" with non-existing scope
- // Should fail
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "scopes_supported": []string{"test-scope"},
- },
- Storage: storage,
- })
- expectError(t, resp, err)
- // validate error message
- expectedStrings := map[string]interface{}{
- "scope \"test-scope\" does not exist": true,
- }
- expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
-
- // Create a test provider "test-provider" with no scopes -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-provider" and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected := map[string]interface{}{
- "issuer": redirectAddr + "/v1/identity/oidc/provider/test-provider",
- "allowed_client_ids": []string{},
- "scopes_supported": []string{},
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Create a test scope "test-scope" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "template": `{"groups": {{identity.entity.groups.names}} }`,
- "description": "my-description",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Update "test-provider" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "allowed_client_ids": []string{"test-client-id"},
- "scopes_supported": []string{"test-scope"},
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-provider" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected = map[string]interface{}{
- "issuer": redirectAddr + "/v1/identity/oidc/provider/test-provider",
- "allowed_client_ids": []string{"test-client-id"},
- "scopes_supported": []string{"test-scope"},
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Update "test-provider" -- should fail issuer validation
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "issuer": "test-issuer",
- },
- Storage: storage,
- })
- expectError(t, resp, err)
- // validate error message
- expectedStrings = map[string]interface{}{
- "invalid issuer, which must include only a scheme, host, and optional port (e.g. https://example.com:8200)": true,
- }
- expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
-
- // Update "test-provider" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "issuer": "https://example.com:8200",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-provider" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected = map[string]interface{}{
- "issuer": "https://example.com:8200/v1/identity/oidc/provider/test-provider",
- "allowed_client_ids": []string{"test-client-id"},
- "scopes_supported": []string{"test-scope"},
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Delete test-provider -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-provider" again and validate
- resp, _ = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- if resp != nil {
- t.Fatalf("expected nil but got resp: %#v", resp)
- }
-}
-
-// TestOIDC_Path_OIDCProvider_DuplicateTempalteKeys tests that no two
-// scopes have the same top-level keys when creating a provider
-func TestOIDC_Path_OIDCProvider_DuplicateTemplateKeys(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test scope "test-scope1" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope1",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "template": `{"groups": {{identity.entity.groups.names}} }`,
- "description": "desc1",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Create another test scope "test-scope2" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope2",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "template": `{"groups": {{identity.entity.groups.names}} }`,
- "description": "desc2",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Create a test provider "test-provider" with scopes that have same top-level keys
- // Should fail
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "scopes_supported": []string{"test-scope1", "test-scope2"},
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- if resp.Warnings[0] != "Found scope templates with conflicting top-level keys: conflict \"groups\" in scopes \"test-scope2\", \"test-scope1\". This may result in an error if the scopes are requested in an OIDC Authentication Request." {
- t.Fatalf("expected a warning for conflicting keys, got %s", resp.Warnings[0])
- }
-
- // // Update "test-scope1" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope1",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "template": `{"roles": {{identity.entity.groups.names}} }`,
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Create a test provider "test-provider" with updated scopes
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "scopes_supported": []string{"test-scope1", "test-scope2"},
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-}
-
-// TestOIDC_Path_OIDCProvider_DeDuplication tests that a
-// provider doesn't have duplicate scopes or client IDs
-func TestOIDC_Path_OIDCProvider_Deduplication(t *testing.T) {
- redirectAddr := "http://localhost:8200"
- conf := &CoreConfig{
- RedirectAddr: redirectAddr,
- }
- c, _, _ := TestCoreUnsealedWithConfig(t, conf)
-
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test scope "test-scope1" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope1",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "template": `{"groups": {{identity.entity.groups.names}} }`,
- "description": "desc1",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Create a test provider "test-provider" with duplicates
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "scopes_supported": []string{"test-scope1", "test-scope1"},
- "allowed_client_ids": []string{"test-id1", "test-id2", "test-id1"},
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-provider" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected := map[string]interface{}{
- "issuer": redirectAddr + "/v1/identity/oidc/provider/test-provider",
- "allowed_client_ids": []string{"test-id1", "test-id2"},
- "scopes_supported": []string{"test-scope1"},
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-}
-
-// TestOIDC_Path_OIDCProvider_Update tests Update operations for providers
-func TestOIDC_Path_OIDCProvider_Update(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test provider "test-provider" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "issuer": "https://example.com:8200",
- "allowed_client_ids": []string{"test-client-id"},
- },
- })
- expectSuccess(t, resp, err)
-
- // Read "test-provider" and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected := map[string]interface{}{
- "issuer": "https://example.com:8200/v1/identity/oidc/provider/test-provider",
- "allowed_client_ids": []string{"test-client-id"},
- "scopes_supported": []string{},
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Update "test-provider" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "issuer": "https://changedurl.com",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-provider" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected = map[string]interface{}{
- "issuer": "https://changedurl.com/v1/identity/oidc/provider/test-provider",
- "allowed_client_ids": []string{"test-client-id"},
- "scopes_supported": []string{},
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-}
-
-// TestOIDC_Path_OIDC_Provider_List tests the List operation for providers
-func TestOIDC_Path_OIDC_Provider_List(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- // Use the identity store's storage view so that the default provider will
- // show up in the test
- storage := c.identityStore.view
-
- // Prepare two providers, test-provider1 and test-provider2
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider1",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
-
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider2",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
-
- // list providers
- respListProviders, listErr := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider",
- Operation: logical.ListOperation,
- Storage: storage,
- })
- expectSuccess(t, respListProviders, listErr)
-
- // validate list response
- expectedStrings := map[string]interface{}{"default": true, "test-provider1": true, "test-provider2": true}
- expectStrings(t, respListProviders.Data["keys"].([]string), expectedStrings)
-
- // delete test-provider2
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider2",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
-
- // list providers again and validate response
- respListProvidersAfterDelete, listErrAfterDelete := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider",
- Operation: logical.ListOperation,
- Storage: storage,
- })
- expectSuccess(t, respListProvidersAfterDelete, listErrAfterDelete)
-
- // validate list response
- delete(expectedStrings, "test-provider2")
- expectStrings(t, respListProvidersAfterDelete.Data["keys"].([]string), expectedStrings)
-}
-
-func TestOIDC_Path_OIDC_Provider_List_KeyInfo(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
-
- // Create a custom scope
- template := `{
- "groups": {{identity.entity.groups.names}}
- }`
- resp, err := c.identityStore.HandleRequest(ctx, testScopeReq(c.identityStore.view,
- "groups", template))
- expectSuccess(t, resp, err)
-
- // Create providers with different parameters
- providers := map[string]interface{}{
- "default": map[string]interface{}{
- "allowed_client_ids": []string{"*"},
- "scopes_supported": []string{},
- "issuer": "http://127.0.0.1:8200",
- },
- "p0": map[string]interface{}{
- "allowed_client_ids": []string{"abc", "def"},
- "scopes_supported": []string{},
- "issuer": "http://10.0.0.1:8200",
- },
- "p1": map[string]interface{}{
- "allowed_client_ids": []string{"xyz"},
- "scopes_supported": []string{"groups"},
- "issuer": "https://myvault.com:8200",
- },
- }
- for name, p := range providers {
- input := p.(map[string]interface{})
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/" + name,
- Operation: logical.CreateOperation,
- Storage: c.identityStore.view,
- Data: input,
- })
- expectSuccess(t, resp, err)
- }
-
- // List providers
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider",
- Operation: logical.ListOperation,
- Storage: c.identityStore.view,
- Data: make(map[string]interface{}),
- })
- expectSuccess(t, resp, err)
- require.NotNil(t, resp.Data["key_info"])
- require.NotNil(t, resp.Data["keys"])
- keys := resp.Data["keys"].([]string)
- keyInfo := resp.Data["key_info"].(map[string]interface{})
- require.Equal(t, len(keys), len(keyInfo))
-
- // Assert the providers returned have additional key info
- for name, details := range keyInfo {
- actual, _ := details.(map[string]interface{})
- require.NotNil(t, providers[name])
- expected := providers[name].(map[string]interface{})
- require.Contains(t, keys, name)
-
- expectedIssuer := fmt.Sprintf("%s%s%s", expected["issuer"],
- "/v1/identity/oidc/provider/", name)
- require.Equal(t, expectedIssuer, actual["issuer"])
- require.Equal(t, expected["allowed_client_ids"], actual["allowed_client_ids"])
- require.Equal(t, expected["scopes_supported"], actual["scopes_supported"])
- }
-}
-
-func TestOIDC_Path_OIDC_Provider_List_Filter(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
-
- // Create providers with different allowed_client_ids values
- providers := []struct {
- name string
- allowedClientIDs []string
- }{
- {name: "p0", allowedClientIDs: []string{"*"}},
- {name: "p1", allowedClientIDs: []string{"abc"}},
- {name: "p2", allowedClientIDs: []string{"abc", "def"}},
- {name: "p3", allowedClientIDs: []string{"abc", "def", "ghi"}},
- {name: "p4", allowedClientIDs: []string{"ghi"}},
- {name: "p5", allowedClientIDs: []string{"jkl"}},
- }
- for _, p := range providers {
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/" + p.name,
- Operation: logical.CreateOperation,
- Storage: c.identityStore.view,
- Data: map[string]interface{}{
- "allowed_client_ids": p.allowedClientIDs,
- },
- })
- expectSuccess(t, resp, err)
- }
-
- tests := []struct {
- name string
- clientIDFilter string
- expectedProviders []string
- }{
- {
- name: "list providers with client_id filter subset 1",
- clientIDFilter: "abc",
- expectedProviders: []string{"default", "p0", "p1", "p2", "p3"},
- },
- {
- name: "list providers with client_id filter subset 2",
- clientIDFilter: "def",
- expectedProviders: []string{"default", "p0", "p2", "p3"},
- },
- {
- name: "list providers with client_id filter subset 3",
- clientIDFilter: "ghi",
- expectedProviders: []string{"default", "p0", "p3", "p4"},
- },
- {
- name: "list providers with client_id filter subset 4",
- clientIDFilter: "jkl",
- expectedProviders: []string{"default", "p0", "p5"},
- },
- {
- name: "list providers with client_id filter only matching glob",
- clientIDFilter: "globmatch_only",
- expectedProviders: []string{"default", "p0"},
- },
- {
- name: "list providers with empty client_id filter returns all",
- clientIDFilter: "",
- expectedProviders: []string{"default", "p0", "p1", "p2", "p3", "p4", "p5"},
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- // List providers with the allowed_client_id query parameter
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider",
- Operation: logical.ListOperation,
- Storage: c.identityStore.view,
- Data: map[string]interface{}{
- "allowed_client_id": tc.clientIDFilter,
- },
- })
- expectSuccess(t, resp, err)
-
- // Assert the filtered set of providers is returned
- sort.Strings(tc.expectedProviders)
- sort.Strings(resp.Data["keys"].([]string))
- require.Equal(t, tc.expectedProviders, resp.Data["keys"].([]string))
- })
- }
-}
-
-// TestOIDC_Path_OpenIDProviderConfig tests read operations for the
-// openid-configuration path
-func TestOIDC_Path_OpenIDProviderConfig(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test scope "test-scope-1" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope-1",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "template": `{"groups": "{{identity.entity.groups.names}}"}`,
- "description": "my-description",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Create a test provider "test-provider"
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "scopes_supported": []string{"test-scope-1"},
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Expect defaults from .well-known/openid-configuration
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider/.well-known/openid-configuration",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- basePath := "/v1/identity/oidc/provider/test-provider"
- expected := &providerDiscovery{
- Issuer: basePath,
- Keys: basePath + "/.well-known/keys",
- ResponseTypes: []string{"code"},
- Scopes: []string{"test-scope-1", "openid"},
- Claims: []string{},
- Subjects: []string{"public"},
- IDTokenAlgs: supportedAlgs,
- AuthorizationEndpoint: "/ui/vault/identity/oidc/provider/test-provider/authorize",
- TokenEndpoint: basePath + "/token",
- UserinfoEndpoint: basePath + "/userinfo",
- GrantTypes: []string{"authorization_code"},
- AuthMethods: []string{"none", "client_secret_basic", "client_secret_post"},
- RequestParameter: false,
- RequestURIParameter: false,
- CodeChallengeMethods: []string{codeChallengeMethodPlain, codeChallengeMethodS256},
- }
- discoveryResp := &providerDiscovery{}
- json.Unmarshal(resp.Data["http_raw_body"].([]byte), discoveryResp)
- if diff := deep.Equal(expected, discoveryResp); diff != nil {
- t.Fatal(diff)
- }
-
- // Create a test scope "test-scope-2" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/scope/test-scope-2",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "template": `{"groups": "{{identity.entity.groups.names}}"}`,
- "description": "my-description",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Update provider issuer config
- testIssuer := "https://example.com:1234"
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider",
- Operation: logical.UpdateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "issuer": testIssuer,
- "scopes_supported": []string{"test-scope-2"},
- },
- })
- expectSuccess(t, resp, err)
-
- // Expect updates from .well-known/openid-configuration
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider/.well-known/openid-configuration",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- // Validate
- basePath = testIssuer + basePath
- expected = &providerDiscovery{
- Issuer: basePath,
- Keys: basePath + "/.well-known/keys",
- ResponseTypes: []string{"code"},
- Scopes: []string{"test-scope-2", "openid"},
- Claims: []string{},
- Subjects: []string{"public"},
- IDTokenAlgs: supportedAlgs,
- AuthorizationEndpoint: testIssuer + "/ui/vault/identity/oidc/provider/test-provider/authorize",
- TokenEndpoint: basePath + "/token",
- UserinfoEndpoint: basePath + "/userinfo",
- GrantTypes: []string{"authorization_code"},
- AuthMethods: []string{"none", "client_secret_basic", "client_secret_post"},
- RequestParameter: false,
- RequestURIParameter: false,
- CodeChallengeMethods: []string{codeChallengeMethodPlain, codeChallengeMethodS256},
- }
- discoveryResp = &providerDiscovery{}
- json.Unmarshal(resp.Data["http_raw_body"].([]byte), discoveryResp)
- if diff := deep.Equal(expected, discoveryResp); diff != nil {
- t.Fatal(diff)
- }
-}
-
-// TestOIDC_Path_OpenIDProviderConfig_ProviderDoesNotExist tests read
-// operations for the openid-configuration path when the provider does not
-// exist
-func TestOIDC_Path_OpenIDProviderConfig_ProviderDoesNotExist(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Expect defaults from .well-known/openid-configuration
- // test-provider does not exist
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/provider/test-provider/.well-known/openid-configuration",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectedResp := &logical.Response{}
- if resp != expectedResp && err != nil {
- t.Fatalf("expected empty response but got success; error:\n%v\nresp: %#v", err, resp)
- }
-}
diff --git a/vault/identity_store_oidc_test.go b/vault/identity_store_oidc_test.go
deleted file mode 100644
index 3eed9a056..000000000
--- a/vault/identity_store_oidc_test.go
+++ /dev/null
@@ -1,1711 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "encoding/json"
- "strconv"
- "strings"
- "testing"
- "time"
-
- "github.com/go-jose/go-jose/v3"
- "github.com/go-jose/go-jose/v3/jwt"
- "github.com/go-test/deep"
- "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/helper/identity"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/framework"
- "github.com/hashicorp/vault/sdk/logical"
- gocache "github.com/patrickmn/go-cache"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-// TestOIDC_Path_OIDC_RoleNoKeyParameter tests that a role cannot be created
-// without a key parameter
-func TestOIDC_Path_OIDC_RoleNoKeyParameter(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test role "test-role1" without a key param -- should fail
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
- expectError(t, resp, err)
- // validate error message
- expectedStrings := map[string]interface{}{
- "the key parameter is required": true,
- }
- expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
-}
-
-// TestOIDC_Path_OIDC_RoleNilKeyEntry tests that a role cannot be created when
-// a key parameter is provided but the key does not exist
-func TestOIDC_Path_OIDC_RoleNilKeyEntry(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test role "test-role1" with a non-existent key -- should fail
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- },
- Storage: storage,
- })
- expectError(t, resp, err)
- // validate error message
- expectedStrings := map[string]interface{}{
- "cannot find key \"test-key\"": true,
- }
- expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
-}
-
-// TestOIDC_Path_OIDCRole_UpdateNoKey test that we cannot update a role without
-// prividing a key param
-func TestOIDC_Path_OIDCRole_UpdateNoKey(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test key "test-key"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "verification_ttl": "2m",
- "rotation_period": "2m",
- },
- Storage: storage,
- })
-
- // Create a test role "test-role1" with a valid key -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- "ttl": "1m",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Update "test-role1" without prividing a key param -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "ttl": "2m",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-role1" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected := map[string]interface{}{
- "key": "test-key",
- "ttl": int64(120),
- "template": "",
- "client_id": resp.Data["client_id"],
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-}
-
-// TestOIDC_Path_OIDCRole_UpdateEmptyKey test that we cannot update a role with an
-// empty key
-func TestOIDC_Path_OIDCRole_UpdateEmptyKey(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test key "test-key"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
-
- // Create a test role "test-role1" with a valid key -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Update "test-role1" with valid parameters -- should fail
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "key": "",
- },
- Storage: storage,
- })
- expectError(t, resp, err)
-
- // Read "test-role1" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected := map[string]interface{}{
- "key": "test-key",
- "ttl": int64(86400),
- "template": "",
- "client_id": resp.Data["client_id"],
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-}
-
-// TestOIDC_Path_OIDCRoleRole tests CRUD operations for roles
-func TestOIDC_Path_OIDCRoleRole(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test key "test-key"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
-
- // Create a test role "test-role1" with a valid key -- should succeed with warning
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-role1" and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected := map[string]interface{}{
- "key": "test-key",
- "ttl": int64(86400),
- "template": "",
- "client_id": resp.Data["client_id"],
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Update "test-role1" with valid parameters -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "template": "{\"some-key\":\"some-value\"}",
- "ttl": "2h",
- "client_id": "my_custom_id",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-role1" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected = map[string]interface{}{
- "key": "test-key",
- "ttl": int64(7200),
- "template": "{\"some-key\":\"some-value\"}",
- "client_id": "my_custom_id",
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Delete "test-role1"
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-role1"
- respReadTestRole1AfterDelete, err3 := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- // Ensure that "test-role1" has been deleted
- expectSuccess(t, respReadTestRole1AfterDelete, err3)
- if respReadTestRole1AfterDelete != nil {
- t.Fatalf("Expected a nil response but instead got:\n%#v", respReadTestRole1AfterDelete)
- }
- if respReadTestRole1AfterDelete != nil {
- t.Fatalf("Expected role to have been deleted but read response was:\n%#v", respReadTestRole1AfterDelete)
- }
-}
-
-// TestOIDC_Path_OIDCRole_InvalidTokenTTL tests the TokenTTL validation
-func TestOIDC_Path_OIDCRole_InvalidTokenTTL(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test key "test-key"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "verification_ttl": int64(60),
- },
- Storage: storage,
- })
-
- // Create a test role "test-role1" with a ttl longer than the
- // verification_ttl -- should fail with error
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- "ttl": int64(3600),
- },
- Storage: storage,
- })
- expectError(t, resp, err)
-
- // Read "test-role1"
- respReadTestRole1, err3 := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- // Ensure that "test-role1" was not created
- expectSuccess(t, respReadTestRole1, err3)
- if respReadTestRole1 != nil {
- t.Fatalf("Expected a nil response but instead got:\n%#v", respReadTestRole1)
- }
-}
-
-// TestOIDC_Path_OIDCRole tests the List operation for roles
-func TestOIDC_Path_OIDCRole(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Prepare two roles, test-role1 and test-role2
- // Create a test key "test-key"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "allowed_client_ids": "test-role1,test-role2",
- },
- Storage: storage,
- })
-
- // Create "test-role1"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role1",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- },
- Storage: storage,
- })
-
- // Create "test-role2"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role2",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- },
- Storage: storage,
- })
-
- // list roles
- respListRole, listErr := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role",
- Operation: logical.ListOperation,
- Storage: storage,
- })
- expectSuccess(t, respListRole, listErr)
-
- // validate list response
- expectedStrings := map[string]interface{}{"test-role1": true, "test-role2": true}
- expectStrings(t, respListRole.Data["keys"].([]string), expectedStrings)
-
- // delete test-role2
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role2",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
-
- // list roles again and validate response
- respListRoleAfterDelete, listErrAfterDelete := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role",
- Operation: logical.ListOperation,
- Storage: storage,
- })
- expectSuccess(t, respListRoleAfterDelete, listErrAfterDelete)
-
- // validate list response
- delete(expectedStrings, "test-role2")
- expectStrings(t, respListRoleAfterDelete.Data["keys"].([]string), expectedStrings)
-}
-
-// TestOIDC_Path_CRUDKey tests CRUD operations for keys
-func TestOIDC_Path_CRUDKey(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test key "test-key" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-key" and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected := map[string]interface{}{
- "rotation_period": int64(86400),
- "verification_ttl": int64(86400),
- "algorithm": "RS256",
- "allowed_client_ids": []string{},
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Update "test-key" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "rotation_period": "10m",
- "verification_ttl": "1h",
- "allowed_client_ids": "allowed-test-role",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Read "test-key" again and validate
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- expected = map[string]interface{}{
- "rotation_period": int64(600),
- "verification_ttl": int64(3600),
- "algorithm": "RS256",
- "allowed_client_ids": []string{"allowed-test-role"},
- }
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Create a role that depends on test key
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/allowed-test-role",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Delete test-key -- should fail because test-role depends on test-key
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
- expectError(t, resp, err)
- // validate error message
- expectedStrings := map[string]interface{}{
- "unable to delete key \"test-key\" because it is currently referenced by these roles: allowed-test-role": true,
- }
- expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
-
- // Delete allowed-test-role
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/allowed-test-role",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
-
- // Delete test-key -- should succeed this time because no roles depend on test-key
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-}
-
-// TestOIDC_Path_OIDCKey_InvalidTokenTTL tests the TokenTTL validation
-func TestOIDC_Path_OIDCKey_InvalidTokenTTL(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test key "test-key" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "verification_ttl": "4m",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Create a role that depends on test key
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/allowed-test-role",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- "ttl": "4m",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Update "test-key" -- should fail since allowed-test-role ttl is less than 2m
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "rotation_period": "10m",
- "verification_ttl": "2m",
- "allowed_client_ids": "allowed-test-role",
- },
- Storage: storage,
- })
- expectError(t, resp, err)
-
- // Create a client that depends on test key
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- "id_token_ttl": "4m",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Update test key "test-key" -- should fail since id_token_ttl is greater than 2m
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "verification_ttl": "2m",
- },
- Storage: storage,
- })
- expectError(t, resp, err)
-}
-
-// TestOIDC_Path_ListKey tests the List operation for keys
-func TestOIDC_Path_ListKey(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Prepare two keys, test-key1 and test-key2
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key1",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
-
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key2",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
-
- // list keys
- respListKey, listErr := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key",
- Operation: logical.ListOperation,
- Storage: storage,
- })
- expectSuccess(t, respListKey, listErr)
-
- // validate list response
- expectedStrings := map[string]interface{}{"test-key1": true, "test-key2": true}
- expectStrings(t, respListKey.Data["keys"].([]string), expectedStrings)
-
- // delete test-key2
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key2",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
-
- // list keyes again and validate response
- respListKeyAfterDelete, listErrAfterDelete := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key",
- Operation: logical.ListOperation,
- Storage: storage,
- })
- expectSuccess(t, respListKeyAfterDelete, listErrAfterDelete)
-
- // validate list response
- delete(expectedStrings, "test-key2")
- expectStrings(t, respListKeyAfterDelete.Data["keys"].([]string), expectedStrings)
-}
-
-// TestOIDC_Path_OIDCKey_DeleteWithExistingClient tests that a key cannot be
-// deleted if it is referenced by an existing client
-func TestOIDC_Path_OIDCKey_DeleteWithExistingClient(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Prepare test key test-key
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
-
- // Create a test client "test-client" -- should succeed
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/client/test-client",
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "key": "test-key",
- },
- })
- expectSuccess(t, resp, err)
-
- // Delete test key "test-key" -- should fail
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
- expectError(t, resp, err)
-}
-
-// TestOIDC_PublicKeys_NoRole tests that public keys are not returned by the
-// oidc/.well-known/keys endpoint when they are not associated with a role
-func TestOIDC_PublicKeys_NoRole(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- s := &logical.InmemStorage{}
-
- // Create a test key "test-key"
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Storage: s,
- })
- expectSuccess(t, resp, err)
-
- // .well-known/keys should contain 0 public keys
- assertPublicKeyCount(t, ctx, s, c, 0)
-}
-
-func assertPublicKeyCount(t *testing.T, ctx context.Context, s logical.Storage, c *Core, keyCount int) {
- t.Helper()
-
- // .well-known/keys should contain keyCount public keys
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/.well-known/keys",
- Operation: logical.ReadOperation,
- Storage: s,
- })
- expectSuccess(t, resp, err)
-
- assertRespPublicKeyCount(t, resp, keyCount)
-}
-
-func assertRespPublicKeyCount(t *testing.T, resp *logical.Response, keyCount int) {
- t.Helper()
-
- // parse response
- responseJWKS := &jose.JSONWebKeySet{}
- json.Unmarshal(resp.Data["http_raw_body"].([]byte), responseJWKS)
- if len(responseJWKS.Keys) != keyCount {
- t.Fatalf("expected %d public keys but instead got %d", keyCount, len(responseJWKS.Keys))
- }
-}
-
-// TestOIDC_PublicKeys tests that public keys are updated by
-// key creation, rotation, and deletion
-func TestOIDC_PublicKeys(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test key "test-key"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
-
- // Create a test role "test-role"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- },
- Storage: storage,
- })
-
- // .well-known/keys should contain 2 public keys
- assertPublicKeyCount(t, ctx, storage, c, 2)
-
- // rotate test-key a few times, each rotate should increase the length of public keys returned
- // by the .well-known endpoint
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key/rotate",
- Operation: logical.UpdateOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key/rotate",
- Operation: logical.UpdateOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // .well-known/keys should contain 4 public keys
- assertPublicKeyCount(t, ctx, storage, c, 4)
-
- // create another named key "test-key2"
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key2",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Create a test role "test-role2"
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role2",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key2",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- // .well-known/keys should contain 6 public keys
- assertPublicKeyCount(t, ctx, storage, c, 6)
-
- // delete test role that references "test-key"
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- // delete test key
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.DeleteOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // .well-known/keys should contain 2 public keys, all of the public keys
- // from named key "test-key" should have been deleted
- assertPublicKeyCount(t, ctx, storage, c, 2)
-}
-
-// TestOIDC_PublicKeys tests that public keys are updated by
-// key creation, rotation, and deletion
-func TestOIDC_SharedPublicKeysByRoles(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create a test key "test-key"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Storage: storage,
- })
-
- // Create a test role "test-role"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- },
- Storage: storage,
- })
-
- // Create a test role "test-role2"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role2",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- },
- Storage: storage,
- })
-
- // Create a test role "test-role3"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role3",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- },
- Storage: storage,
- })
-
- // .well-known/keys should contain 2 public keys
- assertPublicKeyCount(t, ctx, storage, c, 2)
-}
-
-// TestOIDC_SignIDToken tests acquiring a signed token and verifying the public portion
-// of the signing key
-func TestOIDC_SignIDToken(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Create and load an entity, an entity is required to generate an ID token
- testEntity := &identity.Entity{
- Name: "test-entity-name",
- ID: "test-entity-id",
- BucketKey: "test-entity-bucket-key",
- }
-
- txn := c.identityStore.db.Txn(true)
- defer txn.Abort()
- err := c.identityStore.upsertEntityInTxn(ctx, txn, testEntity, nil, true)
- if err != nil {
- t.Fatal(err)
- }
- txn.Commit()
-
- // Create a test key "test-key"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "allowed_client_ids": "*",
- },
- Storage: storage,
- })
-
- // Create a test role "test-role" -- expect no warning
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- if resp != nil {
- t.Fatalf("was expecting a nil response but instead got: %#v", resp)
- }
-
- // Determine test-role's client_id
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role",
- Operation: logical.ReadOperation,
- Data: map[string]interface{}{
- "allowed_client_ids": "",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- clientID := resp.Data["client_id"].(string)
-
- // remove test-role as an allowed role from test-key
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "allowed_client_ids": "",
- },
- Storage: storage,
- })
-
- // Generate a token against the role "test-role" -- should fail
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/token/test-role",
- Operation: logical.ReadOperation,
- Storage: storage,
- EntityID: "test-entity-id",
- })
- expectError(t, resp, err)
- // validate error message
- expectedStrings := map[string]interface{}{
- "the key \"test-key\" does not list the client ID of the role \"test-role\" as an allowed client ID": true,
- }
- expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
-
- // add test-role as an allowed role from test-key
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/test-key",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "allowed_client_ids": clientID,
- },
- Storage: storage,
- })
-
- // Generate a token against the role "test-role" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/token/test-role",
- Operation: logical.ReadOperation,
- Storage: storage,
- EntityID: "test-entity-id",
- })
- expectSuccess(t, resp, err)
- parsedToken, err := jwt.ParseSigned(resp.Data["token"].(string))
- if err != nil {
- t.Fatalf("error parsing token: %s", err.Error())
- }
-
- // Acquire the public parts of the key that signed parsedToken
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/.well-known/keys",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- if err != nil {
- t.Fatal(err)
- }
- responseJWKS := &jose.JSONWebKeySet{}
- json.Unmarshal(resp.Data["http_raw_body"].([]byte), responseJWKS)
-
- keyCount := len(responseJWKS.Keys)
- errorCount := 0
- for _, key := range responseJWKS.Keys {
- // Validate the signature
- claims := &jwt.Claims{}
- if err := parsedToken.Claims(key, claims); err != nil {
- t.Logf("unable to validate signed token, err:\n%#v", err)
- errorCount += 1
- }
- }
- if errorCount == keyCount {
- t.Fatalf("unable to validate signed token with any of the .well-known keys")
- }
-}
-
-// TestOIDC_SignIDToken_NilSigningKey tests that an error is returned when
-// attempting to sign an ID token with a nil signing key
-func TestOIDC_SignIDToken_NilSigningKey(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
-
- // Create and load an entity, an entity is required to generate an ID token
- testEntity := &identity.Entity{
- Name: "test-entity-name",
- ID: "test-entity-id",
- BucketKey: "test-entity-bucket-key",
- }
-
- txn := c.identityStore.db.Txn(true)
- defer txn.Abort()
- err := c.identityStore.upsertEntityInTxn(ctx, txn, testEntity, nil, true)
- if err != nil {
- t.Fatal(err)
- }
- txn.Commit()
-
- // Create a test key "test-key" with a nil SigningKey
- namedKey := &namedKey{
- name: "test-key",
- AllowedClientIDs: []string{"*"},
- Algorithm: "RS256",
- VerificationTTL: 60 * time.Second,
- RotationPeriod: 60 * time.Second,
- KeyRing: nil,
- SigningKey: nil,
- NextSigningKey: nil,
- NextRotation: time.Now(),
- }
- s := c.router.MatchingStorageByAPIPath(ctx, "identity/oidc")
- if err := namedKey.generateAndSetNextKey(ctx, hclog.NewNullLogger(), s); err != nil {
- t.Fatalf("failed to set next signing key")
- }
- // Store namedKey
- entry, _ := logical.StorageEntryJSON(namedKeyConfigPath+namedKey.name, namedKey)
- if err := s.Put(ctx, entry); err != nil {
- t.Fatalf("writing to in mem storage failed")
- }
-
- // Create a test role "test-role" -- expect no warning
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/test-role",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": "test-key",
- "ttl": "1m",
- },
- Storage: s,
- })
- expectSuccess(t, resp, err)
- if resp != nil {
- t.Fatalf("was expecting a nil response but instead got: %#v", resp)
- }
-
- // Generate a token against the role "test-role" -- should fail
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/token/test-role",
- Operation: logical.ReadOperation,
- Storage: s,
- EntityID: "test-entity-id",
- })
- expectError(t, resp, err)
- // validate error message
- expectedStrings := map[string]interface{}{
- "error signing OIDC token: signing key is nil; rotate the key and try again": true,
- }
- expectStrings(t, []string{err.Error()}, expectedStrings)
-}
-
-func testNamedKey(name string) *namedKey {
- return &namedKey{
- name: name,
- Algorithm: "RS256",
- VerificationTTL: 1 * time.Second,
- RotationPeriod: 2 * time.Second,
- KeyRing: nil,
- SigningKey: nil,
- NextSigningKey: nil,
- NextRotation: time.Now(),
- }
-}
-
-// TestOIDC_PeriodicFunc tests timing logic for running key
-// rotations and expiration actions.
-func TestOIDC_PeriodicFunc(t *testing.T) {
- type testCase struct {
- minKeyRingLen int
- maxKeyRingLen int
- }
- testSets := []struct {
- namedKey *namedKey
- setSigningKey bool
- setNextSigningKey bool
- testCases []testCase
- }{
- {
- namedKey: testNamedKey("test-key"),
- setSigningKey: true,
- setNextSigningKey: true,
- testCases: []testCase{
- // we must always have at least 2 keys and at most 3 through rotation cycles, since
- // each rotation cycle results in a key going in/out of its verification_ttl period.
- {2, 3},
- {2, 3},
- {2, 3},
- {2, 3},
- },
- },
- {
- // don't set SigningKey to ensure its non-existence can be handled
- namedKey: testNamedKey("test-key-nil-signing-key"),
- setSigningKey: false,
- setNextSigningKey: true,
- testCases: []testCase{
- {1, 1},
-
- // key counts jump from 1 to 2 because the next signing key becomes
- // the signing key, and no key is in its verification_ttl period
- {2, 2},
- },
- },
- {
- // don't set NextSigningKey to ensure its non-existence can be handled
- namedKey: testNamedKey("test-key-nil-next-signing-key"),
- setSigningKey: true,
- setNextSigningKey: false,
- testCases: []testCase{
- {1, 1},
-
- // max key counts jump from 1 to 3 because the original signing
- // key could still be within its verification_ttl period
- {2, 3},
- },
- },
- {
- // don't set keys to ensure non-existence can be handled
- namedKey: testNamedKey("test-key-nil-signing-and-next-signing-key"),
- setSigningKey: false,
- setNextSigningKey: false,
- testCases: []testCase{
- {0, 0},
-
- // First rotation populates both current/next signing keys
- {2, 2},
- },
- },
- }
-
- for _, testSet := range testSets {
- testSet := testSet
- t.Run(testSet.namedKey.name, func(t *testing.T) {
- t.Parallel()
-
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := c.router.MatchingStorageByAPIPath(ctx, "identity/oidc")
-
- // Stop the core's rollback manager so that periodic function testing
- // doesn't race with the rollback manager.
- c.rollback.StopTicker()
-
- // Generate current and next keys as needed by the test. This ensures
- // we can rotate a key when either are unset.
- if testSet.setSigningKey {
- require.NoError(t, testSet.namedKey.generateAndSetKey(ctx, hclog.NewNullLogger(), storage))
- }
- if testSet.setNextSigningKey {
- require.NoError(t, testSet.namedKey.generateAndSetNextKey(ctx, hclog.NewNullLogger(), storage))
- }
- testSet.namedKey.NextRotation = time.Now().Add(testSet.namedKey.RotationPeriod)
-
- // Store the named key so it can be rotated by the periodic func
- entry, _ := logical.StorageEntryJSON(namedKeyConfigPath+testSet.namedKey.name, testSet.namedKey)
- require.NoError(t, storage.Put(ctx, entry))
- t.Cleanup(func() {
- require.NoError(t, storage.Delete(ctx, namedKeyConfigPath+testSet.namedKey.name))
- })
-
- // Manually execute the periodic func to rotate keys and collect
- // both the key ring and public keys.
- namedKeySamples := make([]*logical.StorageEntry, len(testSet.testCases))
- publicKeysSamples := make([][]string, len(testSet.testCases))
- for i := range testSet.testCases {
- c.identityStore.oidcPeriodicFunc(ctx)
- namedKeyEntry, _ := storage.Get(ctx, namedKeyConfigPath+testSet.namedKey.name)
- publicKeysEntry, _ := storage.List(ctx, publicKeysConfigPath)
- namedKeySamples[i] = namedKeyEntry
- publicKeysSamples[i] = publicKeysEntry
-
- // sleep until we are in the next cycle - where a next run will happen
- v, _, _ := c.identityStore.oidcCache.Get(noNamespace, "nextRun")
- nextRun := v.(time.Time)
- now := time.Now()
- diff := nextRun.Sub(now)
- if now.Before(nextRun) {
- time.Sleep(diff + 100*time.Millisecond)
- }
- }
-
- // Assert that the key lengths through each rotation match expectations
- for i, tc := range testSet.testCases {
- require.NoError(t, namedKeySamples[i].DecodeJSON(&testSet.namedKey))
-
- actualKeyRingLen := len(testSet.namedKey.KeyRing)
- assert.LessOrEqual(t, actualKeyRingLen, tc.maxKeyRingLen,
- "test case index %d: key ring length must be at most %d", i, tc.maxKeyRingLen)
- assert.GreaterOrEqual(t, actualKeyRingLen, tc.minKeyRingLen,
- "test case index %d: key ring length must be at least %d", i, tc.minKeyRingLen)
-
- actualPubKeysLen := len(publicKeysSamples[i])
- assert.LessOrEqual(t, actualPubKeysLen, tc.maxKeyRingLen,
- "test case index %d: public key ring length must be at most %d", i, tc.maxKeyRingLen)
- assert.GreaterOrEqual(t, actualPubKeysLen, tc.minKeyRingLen,
- "test case index %d: public key ring length must be at least %d", i, tc.minKeyRingLen)
- }
- })
- }
-}
-
-// TestOIDC_Config tests CRUD operations for configuring the OIDC backend
-func TestOIDC_Config(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- testIssuer := "https://example.com:1234"
-
- // Read Config - expect defaults
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/config",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- // issuer should not be set
- if resp.Data["issuer"].(string) != "" {
- t.Fatalf("Expected issuer to not be set but found %q instead", resp.Data["issuer"].(string))
- }
-
- // Update Config
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/config",
- Operation: logical.UpdateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "issuer": testIssuer,
- },
- })
- expectSuccess(t, resp, err)
-
- // Read Config - expect updated issuer value
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/config",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- // issuer should be set
- if resp.Data["issuer"].(string) != testIssuer {
- t.Fatalf("Expected issuer to be %q but found %q instead", testIssuer, resp.Data["issuer"].(string))
- }
-
- // Test bad issuers
- for _, iss := range []string{"asldfk", "ftp://a.com", "a.com", "http://a.com/", "https://a.com/foo", "http:://a.com"} {
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/config",
- Operation: logical.UpdateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "issuer": iss,
- },
- })
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || !resp.IsError() {
- t.Fatalf("Expected issuer %q to fail but it succeeded.", iss)
- }
-
- }
-}
-
-// TestOIDC_pathOIDCKeyExistenceCheck tests pathOIDCKeyExistenceCheck
-func TestOIDC_pathOIDCKeyExistenceCheck(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- keyName := "test"
-
- // Expect nil with empty storage
- exists, err := c.identityStore.pathOIDCKeyExistenceCheck(
- ctx,
- &logical.Request{
- Storage: storage,
- },
- &framework.FieldData{
- Raw: map[string]interface{}{"name": keyName},
- Schema: map[string]*framework.FieldSchema{
- "name": {
- Type: framework.TypeString,
- },
- },
- },
- )
- if err != nil {
- t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
- }
- if exists {
- t.Fatalf("Expected existence check to return false but instead returned: %t", exists)
- }
-
- // Populate storage with a namedKey
- namedKey := &namedKey{}
- entry, _ := logical.StorageEntryJSON(namedKeyConfigPath+keyName, namedKey)
- if err := storage.Put(ctx, entry); err != nil {
- t.Fatalf("writing to in mem storage failed")
- }
-
- // Expect true with a populated storage
- exists, err = c.identityStore.pathOIDCKeyExistenceCheck(
- ctx,
- &logical.Request{
- Storage: storage,
- },
- &framework.FieldData{
- Raw: map[string]interface{}{"name": keyName},
- Schema: map[string]*framework.FieldSchema{
- "name": {
- Type: framework.TypeString,
- },
- },
- },
- )
- if err != nil {
- t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
- }
- if !exists {
- t.Fatalf("Expected existence check to return true but instead returned: %t", exists)
- }
-}
-
-// TestOIDC_pathOIDCRoleExistenceCheck tests pathOIDCRoleExistenceCheck
-func TestOIDC_pathOIDCRoleExistenceCheck(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- roleName := "test"
-
- // Expect nil with empty storage
- exists, err := c.identityStore.pathOIDCRoleExistenceCheck(
- ctx,
- &logical.Request{
- Storage: storage,
- },
- &framework.FieldData{
- Raw: map[string]interface{}{"name": roleName},
- Schema: map[string]*framework.FieldSchema{
- "name": {
- Type: framework.TypeString,
- },
- },
- },
- )
- if err != nil {
- t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
- }
- if exists {
- t.Fatalf("Expected existence check to return false but instead returned: %t", exists)
- }
-
- // Populate storage with a role
- role := &role{}
- entry, _ := logical.StorageEntryJSON(roleConfigPath+roleName, role)
- if err := storage.Put(ctx, entry); err != nil {
- t.Fatalf("writing to in mem storage failed")
- }
-
- // Expect true with a populated storage
- exists, err = c.identityStore.pathOIDCRoleExistenceCheck(
- ctx,
- &logical.Request{
- Storage: storage,
- },
- &framework.FieldData{
- Raw: map[string]interface{}{"name": roleName},
- Schema: map[string]*framework.FieldSchema{
- "name": {
- Type: framework.TypeString,
- },
- },
- },
- )
- if err != nil {
- t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
- }
- if !exists {
- t.Fatalf("Expected existence check to return true but instead returned: %t", exists)
- }
-}
-
-// TestOIDC_Path_OpenIDConfig tests read operations for the openid-configuration path
-func TestOIDC_Path_OpenIDConfig(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Expect defaults from .well-known/openid-configuration
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/.well-known/openid-configuration",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- // Validate configurable parts - for now just issuer
- discoveryResp := &discovery{}
- json.Unmarshal(resp.Data["http_raw_body"].([]byte), discoveryResp)
- expected := "/v1/identity/oidc"
- if discoveryResp.Issuer != expected {
- t.Fatalf("Expected Issuer path to be %q but found %q instead", expected, discoveryResp.Issuer)
- }
-
- // Update issuer config
- testIssuer := "https://example.com:1234"
- c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/config",
- Operation: logical.UpdateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "issuer": testIssuer,
- },
- })
-
- // Expect updates from .well-known/openid-configuration
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/.well-known/openid-configuration",
- Operation: logical.ReadOperation,
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- // Validate configurable parts - for now just issuer
- json.Unmarshal(resp.Data["http_raw_body"].([]byte), discoveryResp)
- expected = "https://example.com:1234/v1/identity/oidc"
- if discoveryResp.Issuer != expected {
- t.Fatalf("Expected Issuer path to be %q but found %q instead", expected, discoveryResp.Issuer)
- }
-}
-
-// TestOIDC_Path_Introspect tests update operations on the introspect path
-func TestOIDC_Path_Introspect(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- storage := &logical.InmemStorage{}
-
- // Expect active false and an error from a malformed token
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/introspect/",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "token": "not-a-valid-token",
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- type introspectResponse struct {
- Active bool `json:"active"`
- Error string `json:"error"`
- }
- iresp := &introspectResponse{}
- json.Unmarshal(resp.Data["http_raw_body"].([]byte), iresp)
- if iresp.Active {
- t.Fatalf("expected active state of a malformed token to be false but what was found to be: %t", iresp.Active)
- }
- if iresp.Error == "" {
- t.Fatalf("expected a malformed token to return an error message but instead returned %q", iresp.Error)
- }
-
- // Populate backend with a valid token ---
- // Create and load an entity, an entity is required to generate an ID token
- testEntity := &identity.Entity{
- Name: "test-entity-name",
- ID: "test-entity-id",
- BucketKey: "test-entity-bucket-key",
- }
-
- txn := c.identityStore.db.Txn(true)
- defer txn.Abort()
- err = c.identityStore.upsertEntityInTxn(ctx, txn, testEntity, nil, true)
- if err != nil {
- t.Fatal(err)
- }
- txn.Commit()
-
- for _, alg := range []string{"RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"} {
- key := "test-key-" + alg
- role := "test-role-" + alg
-
- // Create a test key "test-key"
- resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/key/" + key,
- Operation: logical.CreateOperation,
- Storage: storage,
- Data: map[string]interface{}{
- "algorithm": alg,
- "allowed_client_ids": "*",
- },
- })
- expectSuccess(t, resp, err)
-
- // Create a test role "test-role"
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/role/" + role,
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "key": key,
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
-
- // Generate a token against the role "test-role" -- should succeed
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/token/" + role,
- Operation: logical.ReadOperation,
- Storage: storage,
- EntityID: "test-entity-id",
- })
- expectSuccess(t, resp, err)
-
- validToken := resp.Data["token"].(string)
-
- // Expect active true and no error from a valid token
- resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
- Path: "oidc/introspect/",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "token": validToken,
- },
- Storage: storage,
- })
- expectSuccess(t, resp, err)
- iresp2 := &introspectResponse{}
- json.Unmarshal(resp.Data["http_raw_body"].([]byte), iresp2)
- if !iresp2.Active {
- t.Fatalf("expected active state of a valid token to be true but what was found to be: %t", iresp2.Active)
- }
- if iresp2.Error != "" {
- t.Fatalf("expected a valid token to return an empty error message but instead got %q", iresp.Error)
- }
- }
-}
-
-func TestOIDC_isTargetNamespacedKey(t *testing.T) {
- tests := []struct {
- nsTargets []string
- nskey string
- expected bool
- }{
- {[]string{"nsid"}, "v0:nsid:key", true},
- {[]string{"nsid"}, "v0:nsid:", true},
- {[]string{"nsid"}, "v0:nsid", false},
- {[]string{"nsid"}, "v0:", false},
- {[]string{"nsid"}, "v0", false},
- {[]string{"nsid"}, "", false},
- {[]string{"nsid1"}, "v0:nsid2:key", false},
- {[]string{"nsid1"}, "nsid1:nsid2:nsid1", false},
- {[]string{"nsid1"}, "nsid1:nsid1:nsid1", true},
- {[]string{"nsid"}, "nsid:nsid:nsid:nsid:nsid:nsid", true},
- {[]string{"nsid"}, ":::", false},
- {[]string{""}, ":::", true}, // "" is a valid key for cache.Set/Get
- {[]string{"nsid1"}, "nsid0:nsid1:nsid0:nsid1:nsid0:nsid1", true},
- {[]string{"nsid0"}, "nsid0:nsid1:nsid0:nsid1:nsid0:nsid1", false},
- {[]string{"nsid0", "nsid1"}, "v0:nsid2:key", false},
- {[]string{"nsid0", "nsid1", "nsid2", "nsid3", "nsid4"}, "v0:nsid3:key", true},
- {[]string{"nsid0", "nsid1", "nsid2", "nsid3", "nsid4"}, "nsid0:nsid1:nsid2:nsid3:nsid4:nsid5", true},
- {[]string{"nsid0", "nsid1", "nsid2", "nsid3", "nsid4"}, "nsid4:nsid5:nsid6:nsid7:nsid8:nsid9", false},
- {[]string{"nsid0", "nsid0", "nsid0", "nsid0", "nsid0"}, "nsid0:nsid0:nsid0:nsid0:nsid0:nsid0", true},
- {[]string{"nsid1", "nsid1", "nsid2", "nsid2"}, "nsid0:nsid0:nsid0:nsid0:nsid0:nsid0", false},
- {[]string{"nsid1", "nsid1", "nsid2", "nsid2"}, "nsid0:nsid0:nsid0:nsid0:nsid0:nsid0", false},
- }
-
- for _, test := range tests {
- actual := isTargetNamespacedKey(test.nskey, test.nsTargets)
- if test.expected != actual {
- t.Fatalf("expected %t but got %t for nstargets: %q and nskey: %q", test.expected, actual, test.nsTargets, test.nskey)
- }
- }
-}
-
-func TestOIDC_Flush(t *testing.T) {
- c := newOIDCCache(gocache.NoExpiration, gocache.NoExpiration)
- ns := []*namespace.Namespace{
- noNamespace, // ns[0] is nilNamespace
- {ID: "ns1"},
- {ID: "ns2"},
- }
-
- // populateNs populates cache by ns with some data
- populateNs := func() {
- for i := range ns {
- for _, val := range []string{"keyA", "keyB", "keyC"} {
- if err := c.SetDefault(ns[i], val, struct{}{}); err != nil {
- t.Fatal(err)
- }
- }
- }
- }
-
- // validate verifies that cache items exist or do not exist based on their namespaced key
- verify := func(items map[string]gocache.Item, expect, doNotExpect []*namespace.Namespace) {
- for _, expectNs := range expect {
- found := false
- for i := range items {
- if isTargetNamespacedKey(i, []string{expectNs.ID}) {
- found = true
- break
- }
- }
- if !found {
- t.Fatalf("Expected cache to contain an entry with a namespaced key for namespace: %q but did not find one", expectNs.ID)
- }
- }
-
- for _, doNotExpectNs := range doNotExpect {
- for i := range items {
- if isTargetNamespacedKey(i, []string{doNotExpectNs.ID}) {
- t.Fatalf("Did not expect cache to contain an entry with a namespaced key for namespace: %q but found the key: %q", doNotExpectNs.ID, i)
- }
- }
- }
- }
-
- // flushing ns1 should flush ns1 and nilNamespace but not ns2
- populateNs()
- if err := c.Flush(ns[1]); err != nil {
- t.Fatal(err)
- }
- items := c.c.Items()
- verify(items, []*namespace.Namespace{ns[2]}, []*namespace.Namespace{ns[0], ns[1]})
-
- // flushing nilNamespace should flush nilNamespace but not ns1 or ns2
- populateNs()
- if err := c.Flush(ns[0]); err != nil {
- t.Fatal(err)
- }
- items = c.c.Items()
- verify(items, []*namespace.Namespace{ns[1], ns[2]}, []*namespace.Namespace{ns[0]})
-}
-
-func TestOIDC_CacheNamespaceNilCheck(t *testing.T) {
- cache := newOIDCCache(gocache.NoExpiration, gocache.NoExpiration)
-
- if _, _, err := cache.Get(nil, "foo"); err == nil {
- t.Fatal("expected error, got nil")
- }
-
- if err := cache.SetDefault(nil, "foo", 42); err == nil {
- t.Fatal("expected error, got nil")
- }
-
- if err := cache.Flush(nil); err == nil {
- t.Fatal("expected error, got nil")
- }
-}
-
-func TestOIDC_GetKeysCacheControlHeader(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
-
- // get default value
- header, err := c.identityStore.getKeysCacheControlHeader()
- if err != nil {
- t.Fatalf("expected success, got error:\n%v", err)
- }
-
- expectedHeader := ""
- if header != expectedHeader {
- t.Fatalf("expected %s, got %s", expectedHeader, header)
- }
-
- // set nextRun
- nextRun := time.Now().Add(24 * time.Hour)
- if err = c.identityStore.oidcCache.SetDefault(noNamespace, "nextRun", nextRun); err != nil {
- t.Fatal(err)
- }
-
- header, err = c.identityStore.getKeysCacheControlHeader()
- if err != nil {
- t.Fatalf("expected success, got error:\n%v", err)
- }
-
- expectedNextRun := "max-age=86400"
- if header != expectedNextRun {
- t.Fatalf("expected %s, got %s", expectedNextRun, header)
- }
-
- // set jwksCacheControlMaxAge
- durationSeconds := 60
- jwksCacheControlMaxAge := time.Duration(durationSeconds) * time.Second
- if err = c.identityStore.oidcCache.SetDefault(noNamespace, "jwksCacheControlMaxAge", jwksCacheControlMaxAge); err != nil {
- t.Fatal(err)
- }
-
- header, err = c.identityStore.getKeysCacheControlHeader()
- if err != nil {
- t.Fatalf("expected success, got error:\n%v", err)
- }
-
- if header == "" {
- t.Fatalf("expected header to be set, got %s", header)
- }
-
- maxAgeValue := strings.Split(header, "=")[1]
- headerVal, err := strconv.Atoi(maxAgeValue)
- if err != nil {
- t.Fatal(err)
- }
- // headerVal will be a random value between 0 and jwksCacheControlMaxAge (in seconds)
- if headerVal > durationSeconds {
- t.Logf("jwksCacheControlMaxAge: %d", int(jwksCacheControlMaxAge))
- t.Fatalf("unexpected header value, got %d expected less than %d", headerVal, durationSeconds)
- }
-}
-
-// some helpers
-func expectSuccess(t *testing.T, resp *logical.Response, err error) {
- t.Helper()
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("expected success but got error:\n%v\nresp: %#v", err, resp)
- }
-}
-
-func expectError(t *testing.T, resp *logical.Response, err error) {
- t.Helper()
- if err == nil {
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected error but got success; error:\n%v\nresp: %#v", err, resp)
- }
- }
-}
-
-// expectString fails unless every string in actualStrings is also included in expectedStrings and
-// the length of actualStrings and expectedStrings are the same
-func expectStrings(t *testing.T, actualStrings []string, expectedStrings map[string]interface{}) {
- t.Helper()
- if len(actualStrings) != len(expectedStrings) {
- t.Fatalf("expectStrings mismatch:\nactual strings:\n%#v\nexpected strings:\n%#v\n", actualStrings, expectedStrings)
- }
- for _, actualString := range actualStrings {
- _, ok := expectedStrings[actualString]
- if !ok {
- t.Fatalf("the string %q was not expected", actualString)
- }
- }
-}
diff --git a/vault/identity_store_test.go b/vault/identity_store_test.go
deleted file mode 100644
index bfd966e18..000000000
--- a/vault/identity_store_test.go
+++ /dev/null
@@ -1,915 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "strings"
- "testing"
- "time"
-
- "github.com/stretchr/testify/require"
-
- "github.com/armon/go-metrics"
- "github.com/go-test/deep"
- "github.com/golang/protobuf/ptypes"
- uuid "github.com/hashicorp/go-uuid"
- credGithub "github.com/hashicorp/vault/builtin/credential/github"
- credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
- "github.com/hashicorp/vault/helper/identity"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/helper/storagepacker"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestIdentityStore_DeleteEntityAlias(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- txn := c.identityStore.db.Txn(true)
- defer txn.Abort()
-
- alias := &identity.Alias{
- ID: "testAliasID1",
- CanonicalID: "testEntityID",
- MountType: "testMountType",
- MountAccessor: "testMountAccessor",
- Name: "testAliasName",
- LocalBucketKey: c.identityStore.localAliasPacker.BucketKey("testEntityID"),
- }
- alias2 := &identity.Alias{
- ID: "testAliasID2",
- CanonicalID: "testEntityID",
- MountType: "testMountType",
- MountAccessor: "testMountAccessor2",
- Name: "testAliasName2",
- LocalBucketKey: c.identityStore.localAliasPacker.BucketKey("testEntityID"),
- }
- entity := &identity.Entity{
- ID: "testEntityID",
- Name: "testEntityName",
- Policies: []string{"foo", "bar"},
- Aliases: []*identity.Alias{
- alias,
- alias2,
- },
- NamespaceID: namespace.RootNamespaceID,
- BucketKey: c.identityStore.entityPacker.BucketKey("testEntityID"),
- }
-
- err := c.identityStore.upsertEntityInTxn(context.Background(), txn, entity, nil, false)
- require.NoError(t, err)
-
- err = c.identityStore.deleteAliasesInEntityInTxn(txn, entity, []*identity.Alias{alias, alias2})
- require.NoError(t, err)
-
- txn.Commit()
-
- alias, err = c.identityStore.MemDBAliasByID("testAliasID1", false, false)
- require.NoError(t, err)
- require.Nil(t, alias)
-
- alias, err = c.identityStore.MemDBAliasByID("testAliasID2", false, false)
- require.NoError(t, err)
- require.Nil(t, alias)
-
- entity, err = c.identityStore.MemDBEntityByID("testEntityID", false)
- require.NoError(t, err)
-
- require.Len(t, entity.Aliases, 0)
-}
-
-func TestIdentityStore_UnsealingWhenConflictingAliasNames(t *testing.T) {
- err := AddTestCredentialBackend("github", credGithub.Factory)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- c, unsealKey, root := TestCoreUnsealed(t)
-
- meGH := &MountEntry{
- Table: credentialTableType,
- Path: "github/",
- Type: "github",
- Description: "github auth",
- }
-
- err = c.enableCredential(namespace.RootContext(nil), meGH)
- if err != nil {
- t.Fatal(err)
- }
-
- alias := &identity.Alias{
- ID: "alias1",
- CanonicalID: "entity1",
- MountType: "github",
- MountAccessor: meGH.Accessor,
- Name: "githubuser",
- LocalBucketKey: c.identityStore.localAliasPacker.BucketKey("entity1"),
- }
- entity := &identity.Entity{
- ID: "entity1",
- Name: "name1",
- Policies: []string{"foo", "bar"},
- Aliases: []*identity.Alias{
- alias,
- },
- NamespaceID: namespace.RootNamespaceID,
- BucketKey: c.identityStore.entityPacker.BucketKey("entity1"),
- }
-
- err = c.identityStore.upsertEntity(namespace.RootContext(nil), entity, nil, true)
- if err != nil {
- t.Fatal(err)
- }
-
- alias2 := &identity.Alias{
- ID: "alias2",
- CanonicalID: "entity2",
- MountType: "github",
- MountAccessor: meGH.Accessor,
- Name: "GITHUBUSER",
- LocalBucketKey: c.identityStore.localAliasPacker.BucketKey("entity2"),
- }
- entity2 := &identity.Entity{
- ID: "entity2",
- Name: "name2",
- Policies: []string{"foo", "bar"},
- Aliases: []*identity.Alias{
- alias2,
- },
- NamespaceID: namespace.RootNamespaceID,
- BucketKey: c.identityStore.entityPacker.BucketKey("entity2"),
- }
-
- // Persist the second entity directly without the regular flow. This will skip
- // merging of these enties.
- entity2Any, err := ptypes.MarshalAny(entity2)
- if err != nil {
- t.Fatal(err)
- }
- item := &storagepacker.Item{
- ID: entity2.ID,
- Message: entity2Any,
- }
-
- ctx := namespace.RootContext(nil)
- if err = c.identityStore.entityPacker.PutItem(ctx, item); err != nil {
- t.Fatal(err)
- }
-
- // Seal and ensure that unseal works
- if err = c.Seal(root); err != nil {
- t.Fatal(err)
- }
-
- var unsealed bool
- for i := 0; i < 3; i++ {
- unsealed, err = c.Unseal(unsealKey[i])
- if err != nil {
- t.Fatal(err)
- }
- }
- if !unsealed {
- t.Fatal("still sealed")
- }
-}
-
-func TestIdentityStore_EntityIDPassthrough(t *testing.T) {
- // Enable GitHub auth and initialize
- ctx := namespace.RootContext(nil)
- is, ghAccessor, core := testIdentityStoreWithGithubAuth(ctx, t)
- alias := &logical.Alias{
- MountType: "github",
- MountAccessor: ghAccessor,
- Name: "githubuser",
- }
-
- // Create an entity with GitHub alias
- entity, _, err := is.CreateOrFetchEntity(ctx, alias)
- if err != nil {
- t.Fatal(err)
- }
- if entity == nil {
- t.Fatalf("expected a non-nil entity")
- }
-
- // Create a token with the above created entity set on it
- ent := &logical.TokenEntry{
- ID: "testtokenid",
- Path: "test",
- Policies: []string{"root"},
- CreationTime: time.Now().Unix(),
- EntityID: entity.ID,
- NamespaceID: namespace.RootNamespaceID,
- }
- if err := core.tokenStore.create(ctx, ent); err != nil {
- t.Fatalf("err: %s", err)
- }
-
- // Set a request handler to the noop backend which responds with the entity
- // ID received in the request object
- requestHandler := func(ctx context.Context, req *logical.Request) (*logical.Response, error) {
- return &logical.Response{
- Data: map[string]interface{}{
- "entity_id": req.EntityID,
- },
- }, nil
- }
-
- noop := &NoopBackend{
- RequestHandler: requestHandler,
- }
-
- // Mount the noop backend
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = core.router.Mount(noop, "test/backend/", &MountEntry{Path: "test/backend/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- // Make the request with the above created token
- resp, err := core.HandleRequest(ctx, &logical.Request{
- ClientToken: "testtokenid",
- Operation: logical.ReadOperation,
- Path: "test/backend/foo",
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %v", resp, err)
- }
-
- // Expected entity ID to be in the response
- if resp.Data["entity_id"] != entity.ID {
- t.Fatalf("expected entity ID to be passed through to the backend")
- }
-}
-
-func TestIdentityStore_CreateOrFetchEntity(t *testing.T) {
- ctx := namespace.RootContext(nil)
- is, ghAccessor, upAccessor, _ := testIdentityStoreWithGithubUserpassAuth(ctx, t)
-
- alias := &logical.Alias{
- MountType: "github",
- MountAccessor: ghAccessor,
- Name: "githubuser",
- Metadata: map[string]string{
- "foo": "a",
- },
- }
-
- entity, _, err := is.CreateOrFetchEntity(ctx, alias)
- if err != nil {
- t.Fatal(err)
- }
- if entity == nil {
- t.Fatalf("expected a non-nil entity")
- }
-
- if len(entity.Aliases) != 1 {
- t.Fatalf("bad: length of aliases; expected: 1, actual: %d", len(entity.Aliases))
- }
-
- if entity.Aliases[0].Name != alias.Name {
- t.Fatalf("bad: alias name; expected: %q, actual: %q", alias.Name, entity.Aliases[0].Name)
- }
-
- entity, _, err = is.CreateOrFetchEntity(ctx, alias)
- if err != nil {
- t.Fatal(err)
- }
- if entity == nil {
- t.Fatalf("expected a non-nil entity")
- }
-
- if len(entity.Aliases) != 1 {
- t.Fatalf("bad: length of aliases; expected: 1, actual: %d", len(entity.Aliases))
- }
-
- if entity.Aliases[0].Name != alias.Name {
- t.Fatalf("bad: alias name; expected: %q, actual: %q", alias.Name, entity.Aliases[0].Name)
- }
- if diff := deep.Equal(entity.Aliases[0].Metadata, map[string]string{"foo": "a"}); diff != nil {
- t.Fatal(diff)
- }
-
- // Add a new alias to the entity and verify its existence
- registerReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity-alias",
- Data: map[string]interface{}{
- "name": "githubuser2",
- "canonical_id": entity.ID,
- "mount_accessor": upAccessor,
- },
- }
-
- resp, err := is.HandleRequest(ctx, registerReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- entity, _, err = is.CreateOrFetchEntity(ctx, alias)
- if err != nil {
- t.Fatal(err)
- }
- if entity == nil {
- t.Fatalf("expected a non-nil entity")
- }
-
- if len(entity.Aliases) != 2 {
- t.Fatalf("bad: length of aliases; expected: 2, actual: %d", len(entity.Aliases))
- }
-
- if entity.Aliases[1].Name != "githubuser2" {
- t.Fatalf("bad: alias name; expected: %q, actual: %q", alias.Name, "githubuser2")
- }
-
- if diff := deep.Equal(entity.Aliases[1].Metadata, map[string]string(nil)); diff != nil {
- t.Fatal(diff)
- }
-
- // Change the metadata of an existing alias and verify that
- // a the change takes effect only for the target alias.
- alias.Metadata = map[string]string{
- "foo": "zzzz",
- }
-
- entity, _, err = is.CreateOrFetchEntity(ctx, alias)
- if err != nil {
- t.Fatal(err)
- }
- if entity == nil {
- t.Fatalf("expected a non-nil entity")
- }
-
- if len(entity.Aliases) != 2 {
- t.Fatalf("bad: length of aliases; expected: 2, actual: %d", len(entity.Aliases))
- }
-
- if diff := deep.Equal(entity.Aliases[0].Metadata, map[string]string{"foo": "zzzz"}); diff != nil {
- t.Fatal(diff)
- }
-
- if diff := deep.Equal(entity.Aliases[1].Metadata, map[string]string(nil)); diff != nil {
- t.Fatal(diff)
- }
-}
-
-func TestIdentityStore_EntityByAliasFactors(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- is, ghAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
-
- registerData := map[string]interface{}{
- "name": "testentityname",
- "metadata": []string{"someusefulkey=someusefulvalue"},
- "policies": []string{"testpolicy1", "testpolicy2"},
- }
-
- registerReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity",
- Data: registerData,
- }
-
- // Register the entity
- resp, err = is.HandleRequest(ctx, registerReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- idRaw, ok := resp.Data["id"]
- if !ok {
- t.Fatalf("entity id not present in response")
- }
- entityID := idRaw.(string)
- if entityID == "" {
- t.Fatalf("invalid entity id")
- }
-
- aliasData := map[string]interface{}{
- "entity_id": entityID,
- "name": "alias_name",
- "mount_accessor": ghAccessor,
- }
- aliasReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "alias",
- Data: aliasData,
- }
-
- resp, err = is.HandleRequest(ctx, aliasReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
- if resp == nil {
- t.Fatalf("expected a non-nil response")
- }
-
- entity, err := is.entityByAliasFactors(ghAccessor, "alias_name", false)
- if err != nil {
- t.Fatal(err)
- }
- if entity == nil {
- t.Fatalf("expected a non-nil entity")
- }
- if entity.ID != entityID {
- t.Fatalf("bad: entity ID; expected: %q actual: %q", entityID, entity.ID)
- }
-}
-
-func TestIdentityStore_WrapInfoInheritance(t *testing.T) {
- var err error
- var resp *logical.Response
-
- ctx := namespace.RootContext(nil)
- core, is, ts, _ := testCoreWithIdentityTokenGithub(ctx, t)
-
- registerData := map[string]interface{}{
- "name": "testentityname",
- "metadata": []string{"someusefulkey=someusefulvalue"},
- "policies": []string{"testpolicy1", "testpolicy2"},
- }
-
- registerReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "entity",
- Data: registerData,
- }
-
- // Register the entity
- resp, err = is.HandleRequest(ctx, registerReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%#v", err, resp)
- }
-
- idRaw, ok := resp.Data["id"]
- if !ok {
- t.Fatalf("entity id not present in response")
- }
- entityID := idRaw.(string)
- if entityID == "" {
- t.Fatalf("invalid entity id")
- }
-
- // Create a token which has EntityID set and has update permissions to
- // sys/wrapping/wrap
- te := &logical.TokenEntry{
- Path: "test",
- Policies: []string{"default", responseWrappingPolicyName},
- EntityID: entityID,
- TTL: time.Hour,
- }
- testMakeTokenDirectly(t, ts, te)
-
- wrapReq := &logical.Request{
- Path: "sys/wrapping/wrap",
- ClientToken: te.ID,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "foo": "bar",
- },
- WrapInfo: &logical.RequestWrapInfo{
- TTL: time.Duration(5 * time.Second),
- },
- }
-
- resp, err = core.HandleRequest(ctx, wrapReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v, err: %v", resp, err)
- }
-
- if resp.WrapInfo == nil {
- t.Fatalf("expected a non-nil WrapInfo")
- }
-
- if resp.WrapInfo.WrappedEntityID != entityID {
- t.Fatalf("bad: WrapInfo in response not having proper entity ID set; expected: %q, actual:%q", entityID, resp.WrapInfo.WrappedEntityID)
- }
-}
-
-func TestIdentityStore_TokenEntityInheritance(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- // Create a token which has EntityID set
- te := &logical.TokenEntry{
- Path: "test",
- Policies: []string{"dev", "prod"},
- EntityID: "testentityid",
- TTL: time.Hour,
- }
- testMakeTokenDirectly(t, ts, te)
-
- // Create a child token; this should inherit the EntityID
- tokenReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "create",
- ClientToken: te.ID,
- }
-
- ctx := namespace.RootContext(nil)
- resp, err := ts.HandleRequest(ctx, tokenReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v err: %v", err, resp)
- }
-
- if resp.Auth.EntityID != te.EntityID {
- t.Fatalf("bad: entity ID; expected: %v, actual: %v", te.EntityID, resp.Auth.EntityID)
- }
-
- // Create an orphan token; this should not inherit the EntityID
- tokenReq.Path = "create-orphan"
- resp, err = ts.HandleRequest(ctx, tokenReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v err: %v", err, resp)
- }
-
- if resp.Auth.EntityID != "" {
- t.Fatalf("expected entity ID to be not set")
- }
-}
-
-func TestIdentityStore_MergeConflictingAliases(t *testing.T) {
- err := AddTestCredentialBackend("github", credGithub.Factory)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- c, _, _ := TestCoreUnsealed(t)
-
- meGH := &MountEntry{
- Table: credentialTableType,
- Path: "github/",
- Type: "github",
- Description: "github auth",
- }
-
- err = c.enableCredential(namespace.RootContext(nil), meGH)
- if err != nil {
- t.Fatal(err)
- }
-
- alias := &identity.Alias{
- ID: "alias1",
- CanonicalID: "entity1",
- MountType: "github",
- MountAccessor: meGH.Accessor,
- Name: "githubuser",
- LocalBucketKey: c.identityStore.localAliasPacker.BucketKey("entity1"),
- }
- entity := &identity.Entity{
- ID: "entity1",
- Name: "name1",
- Policies: []string{"foo", "bar"},
- Aliases: []*identity.Alias{
- alias,
- },
- NamespaceID: namespace.RootNamespaceID,
- BucketKey: c.identityStore.entityPacker.BucketKey("entity1"),
- }
- err = c.identityStore.upsertEntity(namespace.RootContext(nil), entity, nil, true)
- if err != nil {
- t.Fatal(err)
- }
-
- alias2 := &identity.Alias{
- ID: "alias2",
- CanonicalID: "entity2",
- MountType: "github",
- MountAccessor: meGH.Accessor,
- Name: "githubuser",
- LocalBucketKey: c.identityStore.localAliasPacker.BucketKey("entity2"),
- }
- entity2 := &identity.Entity{
- ID: "entity2",
- Name: "name2",
- Policies: []string{"bar", "baz"},
- Aliases: []*identity.Alias{
- alias2,
- },
- NamespaceID: namespace.RootNamespaceID,
- BucketKey: c.identityStore.entityPacker.BucketKey("entity2"),
- }
-
- err = c.identityStore.upsertEntity(namespace.RootContext(nil), entity2, nil, true)
- if err != nil {
- t.Fatal(err)
- }
-
- newEntity, _, err := c.identityStore.CreateOrFetchEntity(namespace.RootContext(nil), &logical.Alias{
- MountAccessor: meGH.Accessor,
- MountType: "github",
- Name: "githubuser",
- })
- if err != nil {
- t.Fatal(err)
- }
- if newEntity == nil {
- t.Fatal("nil new entity")
- }
-
- entityToUse := "entity1"
- if newEntity.ID == "entity1" {
- entityToUse = "entity2"
- }
- if len(newEntity.MergedEntityIDs) != 1 || newEntity.MergedEntityIDs[0] != entityToUse {
- t.Fatalf("bad merged entity ids: %v", newEntity.MergedEntityIDs)
- }
- if diff := deep.Equal(newEntity.Policies, []string{"bar", "baz", "foo"}); diff != nil {
- t.Fatal(diff)
- }
-
- newEntity, err = c.identityStore.MemDBEntityByID(entityToUse, false)
- if err != nil {
- t.Fatal(err)
- }
- if newEntity != nil {
- t.Fatal("got a non-nil entity")
- }
-}
-
-func testCoreWithIdentityTokenGithub(ctx context.Context, t *testing.T) (*Core, *IdentityStore, *TokenStore, string) {
- is, ghAccessor, core := testIdentityStoreWithGithubAuth(ctx, t)
- return core, is, core.tokenStore, ghAccessor
-}
-
-func testCoreWithIdentityTokenGithubRoot(ctx context.Context, t *testing.T) (*Core, *IdentityStore, *TokenStore, string, string) {
- is, ghAccessor, core, root := testIdentityStoreWithGithubAuthRoot(ctx, t)
- return core, is, core.tokenStore, ghAccessor, root
-}
-
-func testIdentityStoreWithGithubAuth(ctx context.Context, t *testing.T) (*IdentityStore, string, *Core) {
- is, ghA, c, _ := testIdentityStoreWithGithubAuthRoot(ctx, t)
- return is, ghA, c
-}
-
-// testIdentityStoreWithGithubAuthRoot returns an instance of identity store
-// which is mounted by default. This function also enables the github auth
-// backend to assist with testing aliases and entities that require an valid
-// mount accessor of an auth backend.
-func testIdentityStoreWithGithubAuthRoot(ctx context.Context, t *testing.T) (*IdentityStore, string, *Core, string) {
- // Add github credential factory to core config
- err := AddTestCredentialBackend("github", credGithub.Factory)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- c, _, root := TestCoreUnsealed(t)
-
- meGH := &MountEntry{
- Table: credentialTableType,
- Path: "github/",
- Type: "github",
- Description: "github auth",
- }
-
- err = c.enableCredential(ctx, meGH)
- if err != nil {
- t.Fatal(err)
- }
-
- return c.identityStore, meGH.Accessor, c, root
-}
-
-func testIdentityStoreWithGithubUserpassAuth(ctx context.Context, t *testing.T) (*IdentityStore, string, string, *Core) {
- // Setup 2 auth backends, github and userpass
- err := AddTestCredentialBackend("github", credGithub.Factory)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- err = AddTestCredentialBackend("userpass", credUserpass.Factory)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- c, _, _ := TestCoreUnsealed(t)
-
- githubMe := &MountEntry{
- Table: credentialTableType,
- Path: "github/",
- Type: "github",
- Description: "github auth",
- }
-
- err = c.enableCredential(ctx, githubMe)
- if err != nil {
- t.Fatal(err)
- }
-
- userpassMe := &MountEntry{
- Table: credentialTableType,
- Path: "userpass/",
- Type: "userpass",
- Description: "userpass",
- }
-
- err = c.enableCredential(ctx, userpassMe)
- if err != nil {
- t.Fatal(err)
- }
-
- return c.identityStore, githubMe.Accessor, userpassMe.Accessor, c
-}
-
-func TestIdentityStore_MetadataKeyRegex(t *testing.T) {
- key := "validVALID012_-=+/"
-
- if !metaKeyFormatRegEx(key) {
- t.Fatal("failed to accept valid metadata key")
- }
-
- key = "a:b"
- if metaKeyFormatRegEx(key) {
- t.Fatal("accepted invalid metadata key")
- }
-}
-
-func expectSingleCount(t *testing.T, sink *metrics.InmemSink, keyPrefix string) {
- t.Helper()
-
- intervals := sink.Data()
- // Test crossed an interval boundary, don't try to deal with it.
- if len(intervals) > 1 {
- t.Skip("Detected interval crossing.")
- }
-
- var counter *metrics.SampledValue = nil
-
- for _, c := range intervals[0].Counters {
- if strings.HasPrefix(c.Name, keyPrefix) {
- counter = &c
- break
- }
- }
- if counter == nil {
- t.Fatalf("No %q counter found.", keyPrefix)
- }
-
- if counter.Count != 1 {
- t.Errorf("Counter number of samples %v is not 1.", counter.Count)
- }
-
- if counter.Sum != 1.0 {
- t.Errorf("Counter sum %v is not 1.", counter.Sum)
- }
-}
-
-func TestIdentityStore_NewEntityCounter(t *testing.T) {
- // Add github credential factory to core config
- err := AddTestCredentialBackend("github", credGithub.Factory)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- c, _, _, sink := TestCoreUnsealedWithMetrics(t)
-
- meGH := &MountEntry{
- Table: credentialTableType,
- Path: "github/",
- Type: "github",
- Description: "github auth",
- }
-
- ctx := namespace.RootContext(nil)
- err = c.enableCredential(ctx, meGH)
- if err != nil {
- t.Fatal(err)
- }
-
- is := c.identityStore
- ghAccessor := meGH.Accessor
-
- alias := &logical.Alias{
- MountType: "github",
- MountAccessor: ghAccessor,
- Name: "githubuser",
- Metadata: map[string]string{
- "foo": "a",
- },
- }
-
- _, _, err = is.CreateOrFetchEntity(ctx, alias)
- if err != nil {
- t.Fatal(err)
- }
-
- expectSingleCount(t, sink, "identity.entity.creation")
-
- _, _, err = is.CreateOrFetchEntity(ctx, alias)
- if err != nil {
- t.Fatal(err)
- }
-
- expectSingleCount(t, sink, "identity.entity.creation")
-}
-
-func TestIdentityStore_UpdateAliasMetadataPerAccessor(t *testing.T) {
- entity := &identity.Entity{
- ID: "testEntityID",
- Name: "testEntityName",
- Policies: []string{"foo", "bar"},
- Aliases: []*identity.Alias{
- {
- ID: "testAliasID1",
- CanonicalID: "testEntityID",
- MountType: "testMountType",
- MountAccessor: "testMountAccessor",
- Name: "sameAliasName",
- },
- {
- ID: "testAliasID2",
- CanonicalID: "testEntityID",
- MountType: "testMountType",
- MountAccessor: "testMountAccessor2",
- Name: "sameAliasName",
- },
- },
- NamespaceID: namespace.RootNamespaceID,
- }
-
- login := &logical.Alias{
- MountType: "testMountType",
- MountAccessor: "testMountAccessor",
- Name: "sameAliasName",
- ID: "testAliasID",
- Metadata: map[string]string{"foo": "bar"},
- }
-
- if i := changedAliasIndex(entity, login); i != 0 {
- t.Fatalf("wrong alias index changed. Expected 0, got %d", i)
- }
-
- login2 := &logical.Alias{
- MountType: "testMountType",
- MountAccessor: "testMountAccessor2",
- Name: "sameAliasName",
- ID: "testAliasID2",
- Metadata: map[string]string{"bar": "foo"},
- }
-
- if i := changedAliasIndex(entity, login2); i != 1 {
- t.Fatalf("wrong alias index changed. Expected 1, got %d", i)
- }
-}
-
-// TestIdentityStore_DeleteCaseSensitivityKey tests that
-// casesensitivity key gets removed from storage if it exists upon
-// initializing identity store.
-func TestIdentityStore_DeleteCaseSensitivityKey(t *testing.T) {
- c, unsealKey, root := TestCoreUnsealed(t)
- ctx := context.Background()
-
- // add caseSensitivityKey to storage
- entry, err := logical.StorageEntryJSON(caseSensitivityKey, &casesensitivity{
- DisableLowerCasedNames: true,
- })
- if err != nil {
- t.Fatal(err)
- }
- err = c.identityStore.view.Put(ctx, entry)
- if err != nil {
- t.Fatal(err)
- }
-
- // check if the value is stored in storage
- storageEntry, err := c.identityStore.view.Get(ctx, caseSensitivityKey)
- if err != nil {
- t.Fatal(err)
- }
-
- if storageEntry == nil {
- t.Fatalf("bad: expected a non-nil entry for casesensitivity key")
- }
-
- // Seal and unseal to trigger identityStore initialize
- if err = c.Seal(root); err != nil {
- t.Fatal(err)
- }
-
- var unsealed bool
- for i := 0; i < len(unsealKey); i++ {
- unsealed, err = c.Unseal(unsealKey[i])
- if err != nil {
- t.Fatal(err)
- }
- }
- if !unsealed {
- t.Fatal("still sealed")
- }
-
- // check if caseSensitivityKey exists after initialize
- storageEntry, err = c.identityStore.view.Get(ctx, caseSensitivityKey)
- if err != nil {
- t.Fatal(err)
- }
-
- if storageEntry != nil {
- t.Fatalf("bad: expected no entry for casesensitivity key")
- }
-}
diff --git a/vault/init_test.go b/vault/init_test.go
deleted file mode 100644
index c6ecd6168..000000000
--- a/vault/init_test.go
+++ /dev/null
@@ -1,183 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "reflect"
- "testing"
-
- log "github.com/hashicorp/go-hclog"
- wrapping "github.com/hashicorp/go-kms-wrapping/v2"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/sdk/physical/inmem"
-)
-
-func TestCore_Init(t *testing.T) {
- c, conf := testCore_NewTestCore(t, nil)
- testCore_Init_Common(t, c, conf, &SealConfig{SecretShares: 5, SecretThreshold: 3}, nil)
-}
-
-func testCore_NewTestCore(t *testing.T, seal Seal) (*Core, *CoreConfig) {
- return testCore_NewTestCoreLicensing(t, seal, nil)
-}
-
-func testCore_NewTestCoreLicensing(t *testing.T, seal Seal, licensingConfig *LicensingConfig) (*Core, *CoreConfig) {
- logger := logging.NewVaultLogger(log.Trace)
-
- inm, err := inmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- conf := &CoreConfig{
- Physical: inm,
- DisableMlock: true,
- LogicalBackends: map[string]logical.Factory{
- "kv": LeasedPassthroughBackendFactory,
- },
- Seal: seal,
- LicensingConfig: licensingConfig,
- }
- c, err := NewCore(conf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- t.Cleanup(func() {
- defer func() {
- if r := recover(); r != nil {
- t.Log("panic closing core during cleanup", "panic", r)
- }
- }()
- c.Shutdown()
- })
- return c, conf
-}
-
-func testCore_Init_Common(t *testing.T, c *Core, conf *CoreConfig, barrierConf, recoveryConf *SealConfig) {
- init, err := c.Initialized(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if init {
- t.Fatalf("should not be init")
- }
-
- // Check the seal configuration
- outConf, err := c.seal.BarrierConfig(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if outConf != nil {
- t.Fatalf("bad: %v", outConf)
- }
- if recoveryConf != nil {
- outConf, err := c.seal.RecoveryConfig(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if outConf != nil {
- t.Fatalf("bad: %v", outConf)
- }
- }
-
- res, err := c.Initialize(context.Background(), &InitParams{
- BarrierConfig: barrierConf,
- RecoveryConfig: recoveryConf,
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if c.seal.BarrierType() == wrapping.WrapperTypeShamir && len(res.SecretShares) != barrierConf.SecretShares {
- t.Fatalf("Bad: got\n%#v\nexpected conf matching\n%#v\n", *res, *barrierConf)
- }
- if recoveryConf != nil {
- if len(res.RecoveryShares) != recoveryConf.SecretShares {
- t.Fatalf("Bad: got\n%#v\nexpected conf matching\n%#v\n", *res, *recoveryConf)
- }
- }
-
- if res.RootToken == "" {
- t.Fatalf("Bad: %#v", res)
- }
-
- _, err = c.Initialize(context.Background(), &InitParams{
- BarrierConfig: barrierConf,
- RecoveryConfig: recoveryConf,
- })
- if err != ErrAlreadyInit {
- t.Fatalf("err: %v", err)
- }
-
- init, err = c.Initialized(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if !init {
- t.Fatalf("should be init")
- }
-
- // Check the seal configuration
- outConf, err = c.seal.BarrierConfig(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !reflect.DeepEqual(outConf, barrierConf) {
- t.Fatalf("bad: %v expect: %v", outConf, barrierConf)
- }
- if recoveryConf != nil {
- outConf, err = c.seal.RecoveryConfig(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !reflect.DeepEqual(outConf, recoveryConf) {
- t.Fatalf("bad: %v expect: %v", outConf, recoveryConf)
- }
- }
-
- // New Core, same backend
- c2, err := NewCore(conf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- _, err = c2.Initialize(context.Background(), &InitParams{
- BarrierConfig: barrierConf,
- RecoveryConfig: recoveryConf,
- })
- if err != ErrAlreadyInit {
- t.Fatalf("err: %v", err)
- }
-
- init, err = c2.Initialized(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if !init {
- t.Fatalf("should be init")
- }
-
- // Check the seal configuration
- outConf, err = c2.seal.BarrierConfig(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !reflect.DeepEqual(outConf, barrierConf) {
- t.Fatalf("bad: %v expect: %v", outConf, barrierConf)
- }
- if recoveryConf != nil {
- outConf, err = c2.seal.RecoveryConfig(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !reflect.DeepEqual(outConf, recoveryConf) {
- t.Fatalf("bad: %v expect: %v", outConf, recoveryConf)
- }
- }
-}
diff --git a/vault/inspectable_test.go b/vault/inspectable_test.go
deleted file mode 100644
index e6fe58f02..000000000
--- a/vault/inspectable_test.go
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "strings"
- "testing"
-
- "github.com/hashicorp/vault/command/server"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestInspectRouter(t *testing.T) {
- // Verify that all the expected tables exist when we inspect the router
- coreConfig := &CoreConfig{
- EnableIntrospection: true,
- }
- c, _, root := TestCoreUnsealedWithConfig(t, coreConfig)
-
- rootCtx := namespace.RootContext(nil)
- subTrees := map[string][]string{
- "routeEntry": {"root", "storage"},
- "mountEntry": {"uuid", "accessor"},
- }
- subTreeFields := map[string][]string{
- "routeEntry": {"tainted", "storage_prefix", "accessor", "mount_namespace", "mount_path", "mount_type", "uuid"},
- "mountEntry": {"accessor", "mount_namespace", "mount_path", "mount_type", "uuid"},
- }
- for subTreeType, subTreeArray := range subTrees {
- for _, tag := range subTreeArray {
- resp, err := c.HandleRequest(rootCtx, &logical.Request{
- ClientToken: root,
- Operation: logical.ReadOperation,
- Path: "sys/internal/inspect/router/" + tag,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\n err: %v", resp, err)
- }
- // Verify that data exists
- data, ok := resp.Data[tag].([]map[string]interface{})
- if !ok {
- t.Fatalf("Router data is malformed")
- }
- for _, entry := range data {
- for _, field := range subTreeFields[subTreeType] {
- if _, ok := entry[field]; !ok {
- t.Fatalf("Field %s not found in %s", field, tag)
- }
- }
- }
-
- }
- }
-}
-
-func TestInvalidInspectRouterPath(t *testing.T) {
- // Verify that attempting to inspect an invalid tree in the router fails
- coreConfig := &CoreConfig{
- EnableIntrospection: true,
- }
- core, _, rootToken := TestCoreUnsealedWithConfig(t, coreConfig)
- rootCtx := namespace.RootContext(nil)
- _, err := core.HandleRequest(rootCtx, &logical.Request{
- ClientToken: rootToken,
- Operation: logical.ReadOperation,
- Path: "sys/internal/inspect/router/random",
- })
- if !strings.Contains(err.Error(), logical.ErrUnsupportedPath.Error()) {
- t.Fatal("expected unsupported path error")
- }
-}
-
-func TestInspectAPIDisabled(t *testing.T) {
- // Verify that the Inspect API is turned off by default
- core, _, rootToken := testCoreSystemBackend(t)
- rootCtx := namespace.RootContext(nil)
- resp, err := core.HandleRequest(rootCtx, &logical.Request{
- ClientToken: rootToken,
- Operation: logical.ReadOperation,
- Path: "sys/internal/inspect/router/root",
- })
- if err != nil {
- t.Fatal(err)
- }
- if !resp.IsError() || !strings.Contains(resp.Error().Error(), ErrIntrospectionNotEnabled.Error()) {
- t.Fatal("expected invalid configuration error")
- }
-}
-
-func TestInspectAPISudoProtect(t *testing.T) {
- // Verify that the Inspect API path is sudo protected
- core, _, rootToken := testCoreSystemBackend(t)
- testMakeServiceTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"secret"})
- rootCtx := namespace.RootContext(nil)
- _, err := core.HandleRequest(rootCtx, &logical.Request{
- ClientToken: "tokenid",
- Operation: logical.ReadOperation,
- Path: "sys/internal/inspect/router/root",
- })
- if !strings.Contains(err.Error(), logical.ErrPermissionDenied.Error()) {
- t.Fatal("expected permission denied error")
- }
-}
-
-func TestInspectAPIReload(t *testing.T) {
- // Verify that the Inspect API is turned off by default
- core, _, rootToken := testCoreSystemBackend(t)
- rootCtx := namespace.RootContext(nil)
- resp, err := core.HandleRequest(rootCtx, &logical.Request{
- ClientToken: rootToken,
- Operation: logical.ReadOperation,
- Path: "sys/internal/inspect/router/root",
- })
- if err != nil {
- t.Fatal("Unexpected error")
- }
- if !resp.IsError() {
- t.Fatal("expected invalid configuration error")
- }
- if !strings.Contains(resp.Error().Error(), ErrIntrospectionNotEnabled.Error()) {
- t.Fatalf("expected invalid configuration error but recieved: %s", resp.Error())
- }
-
- originalConfig := core.rawConfig.Load().(*server.Config)
- newConfig := originalConfig
- newConfig.EnableIntrospectionEndpointRaw = true
- newConfig.EnableIntrospectionEndpoint = true
-
- // Reload Endpoint
- core.SetConfig(newConfig)
- core.ReloadIntrospectionEndpointEnabled()
-
- resp, err = core.HandleRequest(rootCtx, &logical.Request{
- ClientToken: rootToken,
- Operation: logical.ReadOperation,
- Path: "sys/internal/inspect/router/root",
- })
- if err != nil || resp.IsError() {
- t.Fatal("Unexpected error after reload")
- }
-}
diff --git a/vault/keyring_test.go b/vault/keyring_test.go
deleted file mode 100644
index 57b06c6a7..000000000
--- a/vault/keyring_test.go
+++ /dev/null
@@ -1,211 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "bytes"
- "reflect"
- "testing"
- "time"
-)
-
-func TestKeyring(t *testing.T) {
- k := NewKeyring()
-
- // Term should be 0
- if term := k.ActiveTerm(); term != 0 {
- t.Fatalf("bad: %d", term)
- }
-
- // Should have no key
- if key := k.ActiveKey(); key != nil {
- t.Fatalf("bad: %v", key)
- }
-
- // Add a key
- testKey := []byte("testing")
- key1 := &Key{Term: 1, Version: 1, Value: testKey, InstallTime: time.Now()}
- k, err := k.AddKey(key1)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Term should be 1
- if term := k.ActiveTerm(); term != 1 {
- t.Fatalf("bad: %d", term)
- }
-
- // Should have key
- key := k.ActiveKey()
- if key == nil {
- t.Fatalf("bad: %v", key)
- }
- if !bytes.Equal(key.Value, testKey) {
- t.Fatalf("bad: %v", key)
- }
- if tKey := k.TermKey(1); tKey != key {
- t.Fatalf("bad: %v", tKey)
- }
-
- // Should handle idempotent set
- k, err = k.AddKey(key1)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should not allow conflicting set
- testConflict := []byte("nope")
- key1Conf := &Key{Term: 1, Version: 1, Value: testConflict, InstallTime: time.Now()}
- _, err = k.AddKey(key1Conf)
- if err == nil {
- t.Fatalf("err: %v", err)
- }
-
- // Add a new key
- testSecond := []byte("second")
- key2 := &Key{Term: 2, Version: 1, Value: testSecond, InstallTime: time.Now()}
- k, err = k.AddKey(key2)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Term should be 2
- if term := k.ActiveTerm(); term != 2 {
- t.Fatalf("bad: %d", term)
- }
-
- // Should have key
- newKey := k.ActiveKey()
- if newKey == nil {
- t.Fatalf("bad: %v", key)
- }
- if !bytes.Equal(newKey.Value, testSecond) {
- t.Fatalf("bad: %v", key)
- }
- if tKey := k.TermKey(2); tKey != newKey {
- t.Fatalf("bad: %v", tKey)
- }
-
- // Read of old key should work
- if tKey := k.TermKey(1); tKey != key {
- t.Fatalf("bad: %v", tKey)
- }
-
- // Remove the old key
- k, err = k.RemoveKey(1)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Read of old key should not work
- if tKey := k.TermKey(1); tKey != nil {
- t.Fatalf("bad: %v", tKey)
- }
-
- // Remove the active key should fail
- k, err = k.RemoveKey(2)
- if err == nil {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestKeyring_MasterKey(t *testing.T) {
- k := NewKeyring()
- master := []byte("test")
- master2 := []byte("test2")
-
- // Check no master
- out := k.RootKey()
- if out != nil {
- t.Fatalf("bad: %v", out)
- }
-
- // Set master
- k = k.SetRootKey(master)
- out = k.RootKey()
- if !bytes.Equal(out, master) {
- t.Fatalf("bad: %v", out)
- }
-
- // Update master
- k = k.SetRootKey(master2)
- out = k.RootKey()
- if !bytes.Equal(out, master2) {
- t.Fatalf("bad: %v", out)
- }
-}
-
-func TestKeyring_Serialize(t *testing.T) {
- k := NewKeyring()
- master := []byte("test")
- k = k.SetRootKey(master)
-
- now := time.Now()
- testKey := []byte("testing")
- testSecond := []byte("second")
- k, _ = k.AddKey(&Key{Term: 1, Version: 1, Value: testKey, InstallTime: now})
- k, _ = k.AddKey(&Key{Term: 2, Version: 1, Value: testSecond, InstallTime: now})
-
- buf, err := k.Serialize()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- k2, err := DeserializeKeyring(buf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- out := k2.RootKey()
- if !bytes.Equal(out, master) {
- t.Fatalf("bad: %v", out)
- }
-
- if k2.ActiveTerm() != k.ActiveTerm() {
- t.Fatalf("Term mismatch")
- }
-
- var i uint32
- for i = 1; i < k.ActiveTerm(); i++ {
- key1 := k2.TermKey(i)
- key2 := k.TermKey(i)
- // Work around timezone bug due to DeepEqual using == for comparison
- if !key1.InstallTime.Equal(key2.InstallTime) {
- t.Fatalf("bad: key 1:\n%#v\nkey 2:\n%#v", key1, key2)
- }
- key1.InstallTime = key2.InstallTime
- if !reflect.DeepEqual(key1, key2) {
- t.Fatalf("bad: key 1:\n%#v\nkey 2:\n%#v", key1, key2)
- }
- }
-}
-
-func TestKey_Serialize(t *testing.T) {
- k := &Key{
- Term: 10,
- Version: 1,
- Value: []byte("foobarbaz"),
- InstallTime: time.Now(),
- }
-
- buf, err := k.Serialize()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- out, err := DeserializeKey(buf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Work around timezone bug due to DeepEqual using == for comparison
- if !k.InstallTime.Equal(out.InstallTime) {
- t.Fatalf("bad: expected:\n%#v\nactual:\n%#v", k, out)
- }
- k.InstallTime = out.InstallTime
-
- if !reflect.DeepEqual(k, out) {
- t.Fatalf("bad: %#v", out)
- }
-}
diff --git a/vault/logical_cubbyhole_test.go b/vault/logical_cubbyhole_test.go
deleted file mode 100644
index bc2d469e0..000000000
--- a/vault/logical_cubbyhole_test.go
+++ /dev/null
@@ -1,270 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "reflect"
- "sort"
- "testing"
- "time"
-
- uuid "github.com/hashicorp/go-uuid"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestCubbyholeBackend_Write(t *testing.T) {
- b := testCubbyholeBackend()
- req := logical.TestRequest(t, logical.UpdateOperation, "foo")
- clientToken, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- req.ClientToken = clientToken
- storage := req.Storage
- req.Data["raw"] = "test"
-
- resp, err := b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "foo")
- req.Storage = storage
- req.ClientToken = clientToken
- _, err = b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestCubbyholeBackend_Read(t *testing.T) {
- b := testCubbyholeBackend()
- req := logical.TestRequest(t, logical.UpdateOperation, "foo")
- req.Data["raw"] = "test"
- storage := req.Storage
- clientToken, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- req.ClientToken = clientToken
-
- if _, err := b.HandleRequest(context.Background(), req); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "foo")
- req.Storage = storage
- req.ClientToken = clientToken
-
- resp, err := b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- expected := &logical.Response{
- Data: map[string]interface{}{
- "raw": "test",
- },
- }
-
- if !reflect.DeepEqual(resp, expected) {
- t.Fatalf("bad response.\n\nexpected: %#v\n\nGot: %#v", expected, resp)
- }
-}
-
-func TestCubbyholeBackend_Delete(t *testing.T) {
- b := testCubbyholeBackend()
- req := logical.TestRequest(t, logical.UpdateOperation, "foo")
- req.Data["raw"] = "test"
- storage := req.Storage
- clientToken, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- req.ClientToken = clientToken
-
- if _, err := b.HandleRequest(context.Background(), req); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req = logical.TestRequest(t, logical.DeleteOperation, "foo")
- req.Storage = storage
- req.ClientToken = clientToken
- resp, err := b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "foo")
- req.Storage = storage
- req.ClientToken = clientToken
- resp, err = b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-}
-
-func TestCubbyholeBackend_List(t *testing.T) {
- b := testCubbyholeBackend()
- req := logical.TestRequest(t, logical.UpdateOperation, "foo")
- clientToken, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- req.Data["raw"] = "test"
- req.ClientToken = clientToken
- storage := req.Storage
-
- if _, err := b.HandleRequest(context.Background(), req); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "bar")
- req.Data["raw"] = "baz"
- req.ClientToken = clientToken
- req.Storage = storage
-
- if _, err := b.HandleRequest(context.Background(), req); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req = logical.TestRequest(t, logical.ListOperation, "")
- req.Storage = storage
- req.ClientToken = clientToken
- resp, err := b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- expKeys := []string{"foo", "bar"}
- respKeys := resp.Data["keys"].([]string)
- sort.Strings(expKeys)
- sort.Strings(respKeys)
- if !reflect.DeepEqual(respKeys, expKeys) {
- t.Fatalf("bad response.\n\nexpected: %#v\n\nGot: %#v", expKeys, respKeys)
- }
-}
-
-func TestCubbyholeIsolation(t *testing.T) {
- b := testCubbyholeBackend()
-
- clientTokenA, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- clientTokenB, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- var storageA logical.Storage
- var storageB logical.Storage
-
- // Populate and test A entries
- req := logical.TestRequest(t, logical.UpdateOperation, "foo")
- req.ClientToken = clientTokenA
- storageA = req.Storage
- req.Data["raw"] = "test"
-
- resp, err := b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "foo")
- req.Storage = storageA
- req.ClientToken = clientTokenA
- resp, err = b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- expected := &logical.Response{
- Data: map[string]interface{}{
- "raw": "test",
- },
- }
-
- if !reflect.DeepEqual(resp, expected) {
- t.Fatalf("bad response.\n\nexpected: %#v\n\nGot: %#v", expected, resp)
- }
-
- // Populate and test B entries
- req = logical.TestRequest(t, logical.UpdateOperation, "bar")
- req.ClientToken = clientTokenB
- storageB = req.Storage
- req.Data["raw"] = "baz"
-
- resp, err = b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "bar")
- req.Storage = storageB
- req.ClientToken = clientTokenB
- resp, err = b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- expected = &logical.Response{
- Data: map[string]interface{}{
- "raw": "baz",
- },
- }
-
- if !reflect.DeepEqual(resp, expected) {
- t.Fatalf("bad response.\n\nexpected: %#v\n\nGot: %#v", expected, resp)
- }
-
- // We shouldn't be able to read A from B and vice versa
- req = logical.TestRequest(t, logical.ReadOperation, "foo")
- req.Storage = storageB
- req.ClientToken = clientTokenB
- resp, err = b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("err: was able to read from other user's cubbyhole")
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "bar")
- req.Storage = storageA
- req.ClientToken = clientTokenA
- resp, err = b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("err: was able to read from other user's cubbyhole")
- }
-}
-
-func testCubbyholeBackend() logical.Backend {
- b, _ := CubbyholeBackendFactory(context.Background(), &logical.BackendConfig{
- Logger: nil,
- System: logical.StaticSystemView{
- DefaultLeaseTTLVal: time.Hour * 24,
- MaxLeaseTTLVal: time.Hour * 24 * 32,
- },
- })
- return b
-}
diff --git a/vault/logical_passthrough_test.go b/vault/logical_passthrough_test.go
deleted file mode 100644
index a67b203c9..000000000
--- a/vault/logical_passthrough_test.go
+++ /dev/null
@@ -1,244 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "encoding/json"
- "reflect"
- "testing"
- "time"
-
- "github.com/hashicorp/go-secure-stdlib/parseutil"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestPassthroughBackend_RootPaths(t *testing.T) {
- b := testPassthroughBackend()
- test := func(b logical.Backend) {
- root := b.SpecialPaths()
- if len(root.Root) != 0 {
- t.Fatalf("unexpected: %v", root)
- }
- }
- test(b)
- b = testPassthroughLeasedBackend()
- test(b)
-}
-
-func TestPassthroughBackend_Write(t *testing.T) {
- test := func(b logical.Backend) {
- req := logical.TestRequest(t, logical.UpdateOperation, "foo")
- req.Data["raw"] = "test"
-
- resp, err := b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- out, err := req.Storage.Get(context.Background(), "foo")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out == nil {
- t.Fatalf("failed to write to view")
- }
- }
- b := testPassthroughBackend()
- test(b)
- b = testPassthroughLeasedBackend()
- test(b)
-}
-
-func TestPassthroughBackend_Read(t *testing.T) {
- test := func(b logical.Backend, ttlType string, ttl interface{}, leased bool) {
- req := logical.TestRequest(t, logical.UpdateOperation, "foo")
- req.Data["raw"] = "test"
- var reqTTL interface{}
- switch ttl.(type) {
- case int64:
- reqTTL = ttl.(int64)
- case string:
- reqTTL = ttl.(string)
- default:
- t.Fatal("unknown ttl type")
- }
- req.Data[ttlType] = reqTTL
- storage := req.Storage
-
- if _, err := b.HandleRequest(context.Background(), req); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "foo")
- req.Storage = storage
-
- resp, err := b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- expectedTTL, err := parseutil.ParseDurationSecond(ttl)
- if err != nil {
- t.Fatal(err)
- }
-
- // What comes back if an int is passed in is a json.Number which is
- // actually aliased as a string so to make the deep equal happy if it's
- // actually a number we set it to an int64
- var respTTL interface{} = resp.Data[ttlType]
- _, ok := respTTL.(json.Number)
- if ok {
- respTTL, err = respTTL.(json.Number).Int64()
- if err != nil {
- t.Fatal(err)
- }
- resp.Data[ttlType] = respTTL
- }
-
- expected := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- Renewable: true,
- TTL: expectedTTL,
- },
- },
- Data: map[string]interface{}{
- "raw": "test",
- ttlType: reqTTL,
- },
- }
-
- if !leased {
- expected.Secret.Renewable = false
- }
- resp.Secret.InternalData = nil
- resp.Secret.LeaseID = ""
- if !reflect.DeepEqual(resp, expected) {
- t.Fatalf("bad response.\n\nexpected:\n%#v\n\nGot:\n%#v", expected, resp)
- }
- }
- b := testPassthroughLeasedBackend()
- test(b, "lease", "1h", true)
- test(b, "ttl", "5", true)
- b = testPassthroughBackend()
- test(b, "lease", int64(10), false)
- test(b, "ttl", "40s", false)
-}
-
-func TestPassthroughBackend_Delete(t *testing.T) {
- test := func(b logical.Backend) {
- req := logical.TestRequest(t, logical.UpdateOperation, "foo")
- req.Data["raw"] = "test"
- storage := req.Storage
-
- if _, err := b.HandleRequest(context.Background(), req); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req = logical.TestRequest(t, logical.DeleteOperation, "foo")
- req.Storage = storage
- resp, err := b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "foo")
- req.Storage = storage
- resp, err = b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
- }
- b := testPassthroughBackend()
- test(b)
- b = testPassthroughLeasedBackend()
- test(b)
-}
-
-func TestPassthroughBackend_List(t *testing.T) {
- test := func(b logical.Backend) {
- req := logical.TestRequest(t, logical.UpdateOperation, "foo")
- req.Data["raw"] = "test"
- storage := req.Storage
-
- if _, err := b.HandleRequest(context.Background(), req); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req = logical.TestRequest(t, logical.ListOperation, "")
- req.Storage = storage
- resp, err := b.HandleRequest(context.Background(), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- expected := &logical.Response{
- Data: map[string]interface{}{
- "keys": []string{"foo"},
- },
- }
-
- if !reflect.DeepEqual(resp, expected) {
- t.Fatalf("bad response.\n\nexpected: %#v\n\nGot: %#v", expected, resp)
- }
- }
- b := testPassthroughBackend()
- test(b)
- b = testPassthroughLeasedBackend()
- test(b)
-}
-
-func TestPassthroughBackend_Revoke(t *testing.T) {
- test := func(t *testing.T, b logical.Backend) {
- t.Helper()
- req := logical.TestRequest(t, logical.RevokeOperation, "kv")
- req.Secret = &logical.Secret{
- InternalData: map[string]interface{}{
- "secret_type": "kv",
- },
- }
-
- if _, err := b.HandleRequest(context.Background(), req); err != nil {
- t.Fatalf("err: %v", err)
- }
- }
- t.Run("passthrough", func(t *testing.T) {
- test(t, testPassthroughBackend())
- })
- t.Run("passthrough-leased", func(t *testing.T) {
- test(t, testPassthroughLeasedBackend())
- })
-}
-
-func testPassthroughBackend() logical.Backend {
- b, _ := PassthroughBackendFactory(context.Background(), &logical.BackendConfig{
- Logger: nil,
- System: logical.StaticSystemView{
- DefaultLeaseTTLVal: time.Hour * 24,
- MaxLeaseTTLVal: time.Hour * 24 * 32,
- },
- })
- return b
-}
-
-func testPassthroughLeasedBackend() logical.Backend {
- b, _ := LeasedPassthroughBackendFactory(context.Background(), &logical.BackendConfig{
- Logger: nil,
- System: logical.StaticSystemView{
- DefaultLeaseTTLVal: time.Hour * 24,
- MaxLeaseTTLVal: time.Hour * 24 * 32,
- },
- })
- return b
-}
diff --git a/vault/logical_system_activity_write_testonly.go b/vault/logical_system_activity_write_testonly.go
deleted file mode 100644
index f663c105b..000000000
--- a/vault/logical_system_activity_write_testonly.go
+++ /dev/null
@@ -1,371 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-//go:build testonly
-
-package vault
-
-import (
- "context"
- "fmt"
- "sync"
- "time"
-
- "github.com/hashicorp/go-uuid"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/helper/timeutil"
- "github.com/hashicorp/vault/sdk/framework"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault/activity"
- "github.com/hashicorp/vault/vault/activity/generation"
- "google.golang.org/protobuf/encoding/protojson"
-)
-
-const helpText = "Create activity log data for testing purposes"
-
-func (b *SystemBackend) activityWritePath() *framework.Path {
- return &framework.Path{
- Pattern: "internal/counters/activity/write$",
- HelpDescription: helpText,
- HelpSynopsis: helpText,
- Fields: map[string]*framework.FieldSchema{
- "input": {
- Type: framework.TypeString,
- Description: "JSON input for generating mock data",
- },
- },
- Operations: map[logical.Operation]framework.OperationHandler{
- logical.CreateOperation: &framework.PathOperation{
- Callback: b.handleActivityWriteData,
- Summary: "Write activity log data",
- },
- },
- }
-}
-
-func (b *SystemBackend) handleActivityWriteData(ctx context.Context, request *logical.Request, data *framework.FieldData) (*logical.Response, error) {
- json := data.Get("input")
- input := &generation.ActivityLogMockInput{}
- err := protojson.Unmarshal([]byte(json.(string)), input)
- if err != nil {
- return logical.ErrorResponse("Invalid input data: %s", err), logical.ErrInvalidRequest
- }
- if len(input.Write) == 0 {
- return logical.ErrorResponse("Missing required \"write\" values"), logical.ErrInvalidRequest
- }
- if len(input.Data) == 0 {
- return logical.ErrorResponse("Missing required \"data\" values"), logical.ErrInvalidRequest
- }
-
- numMonths := 0
- for _, month := range input.Data {
- if int(month.GetMonthsAgo()) > numMonths {
- numMonths = int(month.GetMonthsAgo())
- }
- }
- generated := newMultipleMonthsActivityClients(numMonths + 1)
- for _, month := range input.Data {
- err := generated.processMonth(ctx, b.Core, month)
- if err != nil {
- return logical.ErrorResponse("failed to process data for month %d", month.GetMonthsAgo()), err
- }
- }
-
- opts := make(map[generation.WriteOptions]struct{}, len(input.Write))
- for _, opt := range input.Write {
- opts[opt] = struct{}{}
- }
- paths, err := generated.write(ctx, opts, b.Core.activityLog)
- if err != nil {
- return logical.ErrorResponse("failed to write data"), err
- }
- return &logical.Response{
- Data: map[string]interface{}{
- "paths": paths,
- },
- }, nil
-}
-
-// singleMonthActivityClients holds a single month's client IDs, in the order they were seen
-type singleMonthActivityClients struct {
- // clients are indexed by ID
- clients []*activity.EntityRecord
- // predefinedSegments map from the segment number to the client's index in
- // the clients slice
- predefinedSegments map[int][]int
- // generationParameters holds the generation request
- generationParameters *generation.Data
-}
-
-// multipleMonthsActivityClients holds multiple month's data
-type multipleMonthsActivityClients struct {
- // months are in order, with month 0 being the current month and index 1 being 1 month ago
- months []*singleMonthActivityClients
-}
-
-func (s *singleMonthActivityClients) addEntityRecord(record *activity.EntityRecord, segmentIndex *int) {
- s.clients = append(s.clients, record)
- if segmentIndex != nil {
- index := len(s.clients) - 1
- s.predefinedSegments[*segmentIndex] = append(s.predefinedSegments[*segmentIndex], index)
- }
-}
-
-// populateSegments converts a month of clients into a segmented map. The map's
-// keys are the segment index, and the value are the clients that were seen in
-// that index. If the value is an empty slice, then it's an empty index. If the
-// value is nil, then it's a skipped index
-func (s *singleMonthActivityClients) populateSegments() (map[int][]*activity.EntityRecord, error) {
- segments := make(map[int][]*activity.EntityRecord)
- ignoreIndexes := make(map[int]struct{})
- skipIndexes := s.generationParameters.SkipSegmentIndexes
- emptyIndexes := s.generationParameters.EmptySegmentIndexes
-
- for _, i := range skipIndexes {
- segments[int(i)] = nil
- ignoreIndexes[int(i)] = struct{}{}
- }
- for _, i := range emptyIndexes {
- segments[int(i)] = make([]*activity.EntityRecord, 0, 0)
- ignoreIndexes[int(i)] = struct{}{}
- }
-
- // if we have predefined segments, then we can construct the map using those
- if len(s.predefinedSegments) > 0 {
- for segment, clientIndexes := range s.predefinedSegments {
- clientsInSegment := make([]*activity.EntityRecord, 0, len(clientIndexes))
- for _, idx := range clientIndexes {
- clientsInSegment = append(clientsInSegment, s.clients[idx])
- }
- segments[segment] = clientsInSegment
- }
- return segments, nil
- }
-
- totalSegmentCount := 1
- if s.generationParameters.GetNumSegments() > 0 {
- totalSegmentCount = int(s.generationParameters.GetNumSegments())
- }
- numNonUsable := len(skipIndexes) + len(emptyIndexes)
- usableSegmentCount := totalSegmentCount - numNonUsable
- if usableSegmentCount <= 0 {
- return nil, fmt.Errorf("num segments %d is too low, it must be greater than %d (%d skipped indexes + %d empty indexes)", totalSegmentCount, numNonUsable, len(skipIndexes), len(emptyIndexes))
- }
-
- // determine how many clients should be in each segment
- segmentSizes := len(s.clients) / usableSegmentCount
- if len(s.clients)%usableSegmentCount != 0 {
- segmentSizes++
- }
-
- clientIndex := 0
- for i := 0; i < totalSegmentCount; i++ {
- if clientIndex >= len(s.clients) {
- break
- }
- if _, ok := ignoreIndexes[i]; ok {
- continue
- }
- for len(segments[i]) < segmentSizes && clientIndex < len(s.clients) {
- segments[i] = append(segments[i], s.clients[clientIndex])
- clientIndex++
- }
- }
- return segments, nil
-}
-
-// addNewClients generates clients according to the given parameters, and adds them to the month
-// the client will always have the mountAccessor as its mount accessor
-func (s *singleMonthActivityClients) addNewClients(c *generation.Client, mountAccessor string, segmentIndex *int) error {
- count := 1
- if c.Count > 1 {
- count = int(c.Count)
- }
- clientType := entityActivityType
- if c.NonEntity {
- clientType = nonEntityTokenActivityType
- }
- for i := 0; i < count; i++ {
- record := &activity.EntityRecord{
- ClientID: c.Id,
- NamespaceID: c.Namespace,
- NonEntity: c.NonEntity,
- MountAccessor: mountAccessor,
- ClientType: clientType,
- }
- if record.ClientID == "" {
- var err error
- record.ClientID, err = uuid.GenerateUUID()
- if err != nil {
- return err
- }
- }
- s.addEntityRecord(record, segmentIndex)
- }
- return nil
-}
-
-// processMonth populates a month of client data
-func (m *multipleMonthsActivityClients) processMonth(ctx context.Context, core *Core, month *generation.Data) error {
- // default to using the root namespace and the first mount on the root namespace
- mounts, err := core.ListMounts()
- if err != nil {
- return err
- }
- defaultMountAccessorRootNS := ""
- for _, mount := range mounts {
- if mount.NamespaceID == namespace.RootNamespaceID {
- defaultMountAccessorRootNS = mount.Accessor
- break
- }
- }
- m.months[month.GetMonthsAgo()].generationParameters = month
- add := func(c []*generation.Client, segmentIndex *int) error {
- for _, clients := range c {
-
- if clients.Namespace == "" {
- clients.Namespace = namespace.RootNamespaceID
- }
-
- // verify that the namespace exists
- ns, err := core.NamespaceByID(ctx, clients.Namespace)
- if err != nil {
- return err
- }
-
- // verify that the mount exists
- if clients.Mount != "" {
- nctx := namespace.ContextWithNamespace(ctx, ns)
- mountEntry := core.router.MatchingMountEntry(nctx, clients.Mount)
- if mountEntry == nil {
- return fmt.Errorf("unable to find matching mount in namespace %s", clients.Namespace)
- }
- }
-
- mountAccessor := defaultMountAccessorRootNS
- if clients.Namespace != namespace.RootNamespaceID && clients.Mount == "" {
- // if we're not using the root namespace, find a mount on the namespace that we are using
- found := false
- for _, mount := range mounts {
- if mount.NamespaceID == clients.Namespace {
- mountAccessor = mount.Accessor
- found = true
- break
- }
- }
- if !found {
- return fmt.Errorf("unable to find matching mount in namespace %s", clients.Namespace)
- }
- }
-
- err = m.addClientToMonth(month.GetMonthsAgo(), clients, mountAccessor, segmentIndex)
- if err != nil {
- return err
- }
- }
- return nil
- }
-
- if month.GetAll() != nil {
- return add(month.GetAll().GetClients(), nil)
- }
- predefinedSegments := month.GetSegments()
- for i, segment := range predefinedSegments.GetSegments() {
- index := i
- if segment.SegmentIndex != nil {
- index = int(*segment.SegmentIndex)
- }
- err = add(segment.GetClients().GetClients(), &index)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (m *multipleMonthsActivityClients) addClientToMonth(monthsAgo int32, c *generation.Client, mountAccessor string, segmentIndex *int) error {
- if c.Repeated || c.RepeatedFromMonth > 0 {
- return m.addRepeatedClients(monthsAgo, c, mountAccessor, segmentIndex)
- }
- return m.months[monthsAgo].addNewClients(c, mountAccessor, segmentIndex)
-}
-
-func (m *multipleMonthsActivityClients) addRepeatedClients(monthsAgo int32, c *generation.Client, mountAccessor string, segmentIndex *int) error {
- addingTo := m.months[monthsAgo]
- repeatedFromMonth := monthsAgo + 1
- if c.RepeatedFromMonth > 0 {
- repeatedFromMonth = c.RepeatedFromMonth
- }
- repeatedFrom := m.months[repeatedFromMonth]
- numClients := 1
- if c.Count > 0 {
- numClients = int(c.Count)
- }
- for _, client := range repeatedFrom.clients {
- if c.NonEntity == client.NonEntity && mountAccessor == client.MountAccessor && c.Namespace == client.NamespaceID {
- addingTo.addEntityRecord(client, segmentIndex)
- numClients--
- if numClients == 0 {
- break
- }
- }
- }
- if numClients > 0 {
- return fmt.Errorf("missing repeated %d clients matching given parameters", numClients)
- }
- return nil
-}
-
-func (m *multipleMonthsActivityClients) write(ctx context.Context, opts map[generation.WriteOptions]struct{}, activityLog *ActivityLog) ([]string, error) {
- now := timeutil.StartOfMonth(time.Now().UTC())
- paths := []string{}
- for i, month := range m.months {
- var timestamp time.Time
- if i > 0 {
- timestamp = timeutil.StartOfMonth(timeutil.MonthsPreviousTo(i, now))
- } else {
- timestamp = now
- }
- segments, err := month.populateSegments()
- if err != nil {
- return nil, err
- }
- for segmentIndex, segment := range segments {
- if _, ok := opts[generation.WriteOptions_WRITE_ENTITIES]; ok {
- if segment == nil {
- // skip the index
- continue
- }
- entityPath, err := activityLog.saveSegmentEntitiesInternal(ctx, segmentInfo{
- startTimestamp: timestamp.Unix(),
- currentClients: &activity.EntityActivityLog{Clients: segment},
- clientSequenceNumber: uint64(segmentIndex),
- tokenCount: &activity.TokenCount{},
- }, true)
- if err != nil {
- return nil, err
- }
- paths = append(paths, entityPath)
- }
- }
- }
- wg := sync.WaitGroup{}
- err := activityLog.refreshFromStoredLog(ctx, &wg, now)
- if err != nil {
- return nil, err
- }
- return paths, nil
-}
-
-func newMultipleMonthsActivityClients(numberOfMonths int) *multipleMonthsActivityClients {
- m := &multipleMonthsActivityClients{
- months: make([]*singleMonthActivityClients, numberOfMonths),
- }
- for i := 0; i < numberOfMonths; i++ {
- m.months[i] = &singleMonthActivityClients{
- predefinedSegments: make(map[int][]int),
- }
- }
- return m
-}
diff --git a/vault/logical_system_activity_write_testonly_test.go b/vault/logical_system_activity_write_testonly_test.go
deleted file mode 100644
index f1df04353..000000000
--- a/vault/logical_system_activity_write_testonly_test.go
+++ /dev/null
@@ -1,576 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-//go:build testonly
-
-package vault
-
-import (
- "context"
- "sort"
- "testing"
-
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/vault/activity"
- "github.com/hashicorp/vault/vault/activity/generation"
- "github.com/stretchr/testify/require"
- "google.golang.org/protobuf/encoding/protojson"
- "google.golang.org/protobuf/proto"
-)
-
-// TestSystemBackend_handleActivityWriteData calls the activity log write endpoint and confirms that the inputs are
-// correctly validated
-func TestSystemBackend_handleActivityWriteData(t *testing.T) {
- testCases := []struct {
- name string
- operation logical.Operation
- input map[string]interface{}
- wantError error
- wantPaths int
- }{
- {
- name: "read fails",
- operation: logical.ReadOperation,
- wantError: logical.ErrUnsupportedOperation,
- },
- {
- name: "empty write fails",
- operation: logical.CreateOperation,
- wantError: logical.ErrInvalidRequest,
- },
- {
- name: "wrong key fails",
- operation: logical.CreateOperation,
- input: map[string]interface{}{"other": "data"},
- wantError: logical.ErrInvalidRequest,
- },
- {
- name: "incorrectly formatted data fails",
- operation: logical.CreateOperation,
- input: map[string]interface{}{"input": "data"},
- wantError: logical.ErrInvalidRequest,
- },
- {
- name: "incorrect json data fails",
- operation: logical.CreateOperation,
- input: map[string]interface{}{"input": `{"other":"json"}`},
- wantError: logical.ErrInvalidRequest,
- },
- {
- name: "empty write value fails",
- operation: logical.CreateOperation,
- input: map[string]interface{}{"input": `{"write":[],"data":[]}`},
- wantError: logical.ErrInvalidRequest,
- },
- {
- name: "empty data value fails",
- operation: logical.CreateOperation,
- input: map[string]interface{}{"input": `{"write":["WRITE_PRECOMPUTED_QUERIES"],"data":[]}`},
- wantError: logical.ErrInvalidRequest,
- },
- {
- name: "correctly formatted data succeeds",
- operation: logical.CreateOperation,
- input: map[string]interface{}{"input": `{"write":["WRITE_PRECOMPUTED_QUERIES"],"data":[{"current_month":true,"all":{"clients":[{"count":5}]}}]}`},
- },
- {
- name: "entities with multiple segments",
- operation: logical.CreateOperation,
- input: map[string]interface{}{"input": `{"write":["WRITE_ENTITIES"],"data":[{"current_month":true,"num_segments":3,"all":{"clients":[{"count":5}]}}]}`},
- wantPaths: 3,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- b := testSystemBackend(t)
- req := logical.TestRequest(t, tc.operation, "internal/counters/activity/write")
- req.Data = tc.input
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if tc.wantError != nil {
- require.Equal(t, tc.wantError, err, resp.Error())
- } else {
- require.NoError(t, err)
- paths := resp.Data["paths"].([]string)
- require.Len(t, paths, tc.wantPaths)
- }
- })
- }
-}
-
-// Test_singleMonthActivityClients_addNewClients verifies that new clients are
-// created correctly, adhering to the requested parameters. The clients should
-// use the inputted mount and a generated ID if one is not supplied. The new
-// client should be added to the month's `clients` slice and segment map, if
-// a segment index is supplied
-func Test_singleMonthActivityClients_addNewClients(t *testing.T) {
- segmentIndex := 0
- tests := []struct {
- name string
- mount string
- clients *generation.Client
- wantNamespace string
- wantMount string
- wantID string
- segmentIndex *int
- }{
- {
- name: "default mount is used",
- mount: "default_mount",
- wantMount: "default_mount",
- clients: &generation.Client{},
- },
- {
- name: "record namespace is used, default mount is used",
- mount: "default_mount",
- wantNamespace: "ns",
- wantMount: "default_mount",
- clients: &generation.Client{
- Namespace: "ns",
- Mount: "mount",
- },
- },
- {
- name: "predefined ID is used",
- clients: &generation.Client{
- Id: "client_id",
- },
- wantID: "client_id",
- },
- {
- name: "non zero count",
- clients: &generation.Client{
- Count: 5,
- },
- },
- {
- name: "non entity client",
- clients: &generation.Client{
- NonEntity: true,
- },
- },
- {
- name: "added to segment",
- clients: &generation.Client{},
- segmentIndex: &segmentIndex,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- m := &singleMonthActivityClients{
- predefinedSegments: make(map[int][]int),
- }
- err := m.addNewClients(tt.clients, tt.mount, tt.segmentIndex)
- require.NoError(t, err)
- numNew := tt.clients.Count
- if numNew == 0 {
- numNew = 1
- }
- require.Len(t, m.clients, int(numNew))
- for i, rec := range m.clients {
- require.NotNil(t, rec)
- require.Equal(t, tt.wantNamespace, rec.NamespaceID)
- require.Equal(t, tt.wantMount, rec.MountAccessor)
- require.Equal(t, tt.clients.NonEntity, rec.NonEntity)
- if tt.wantID != "" {
- require.Equal(t, tt.wantID, rec.ClientID)
- } else {
- require.NotEqual(t, "", rec.ClientID)
- }
- if tt.segmentIndex != nil {
- require.Contains(t, m.predefinedSegments[*tt.segmentIndex], i)
- }
- }
- })
- }
-}
-
-// Test_multipleMonthsActivityClients_processMonth verifies that a month of data
-// is added correctly. The test checks that default values are handled correctly
-// for mounts and namespaces.
-func Test_multipleMonthsActivityClients_processMonth(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- tests := []struct {
- name string
- clients *generation.Data
- wantError bool
- numMonths int
- }{
- {
- name: "specified namespace and mount exist",
- clients: &generation.Data{
- Clients: &generation.Data_All{All: &generation.Clients{Clients: []*generation.Client{{
- Namespace: namespace.RootNamespaceID,
- Mount: "identity/",
- }}}},
- },
- numMonths: 1,
- },
- {
- name: "specified namespace exists, mount empty",
- clients: &generation.Data{
- Clients: &generation.Data_All{All: &generation.Clients{Clients: []*generation.Client{{
- Namespace: namespace.RootNamespaceID,
- }}}},
- },
- numMonths: 1,
- },
- {
- name: "empty namespace and mount",
- clients: &generation.Data{
- Clients: &generation.Data_All{All: &generation.Clients{Clients: []*generation.Client{{}}}},
- },
- numMonths: 1,
- },
- {
- name: "namespace doesn't exist",
- clients: &generation.Data{
- Clients: &generation.Data_All{All: &generation.Clients{Clients: []*generation.Client{{
- Namespace: "abcd",
- }}}},
- },
- wantError: true,
- numMonths: 1,
- },
- {
- name: "namespace exists, mount doesn't exist",
- clients: &generation.Data{
- Clients: &generation.Data_All{All: &generation.Clients{Clients: []*generation.Client{{
- Namespace: namespace.RootNamespaceID,
- Mount: "mount",
- }}}},
- },
- wantError: true,
- numMonths: 1,
- },
- {
- name: "older month",
- clients: &generation.Data{
- Month: &generation.Data_MonthsAgo{MonthsAgo: 4},
- Clients: &generation.Data_All{All: &generation.Clients{Clients: []*generation.Client{{}}}},
- },
- numMonths: 5,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- m := newMultipleMonthsActivityClients(tt.numMonths)
- err := m.processMonth(context.Background(), core, tt.clients)
- if tt.wantError {
- require.Error(t, err)
- } else {
- require.NoError(t, err)
- require.Len(t, m.months[tt.clients.GetMonthsAgo()].clients, len(tt.clients.GetAll().Clients))
- for _, month := range m.months {
- for _, c := range month.clients {
- require.NotEmpty(t, c.NamespaceID)
- require.NotEmpty(t, c.MountAccessor)
- }
- }
- }
- })
- }
-}
-
-// Test_multipleMonthsActivityClients_processMonth_segmented verifies that segments
-// are filled correctly when a month is processed with segmented data. The clients
-// should be in the clients array, and should also be in the predefinedSegments map
-// at the correct segment index
-func Test_multipleMonthsActivityClients_processMonth_segmented(t *testing.T) {
- index7 := int32(7)
- data := &generation.Data{
- Clients: &generation.Data_Segments{
- Segments: &generation.Segments{
- Segments: []*generation.Segment{
- {
- Clients: &generation.Clients{Clients: []*generation.Client{
- {},
- }},
- },
- {
- Clients: &generation.Clients{Clients: []*generation.Client{{}}},
- },
- {
- SegmentIndex: &index7,
- Clients: &generation.Clients{Clients: []*generation.Client{{}}},
- },
- },
- },
- },
- }
- m := newMultipleMonthsActivityClients(1)
- core, _, _ := TestCoreUnsealed(t)
- require.NoError(t, m.processMonth(context.Background(), core, data))
- require.Len(t, m.months[0].predefinedSegments, 3)
- require.Len(t, m.months[0].clients, 3)
-
- // segment indexes are correct
- require.Contains(t, m.months[0].predefinedSegments, 0)
- require.Contains(t, m.months[0].predefinedSegments, 1)
- require.Contains(t, m.months[0].predefinedSegments, 7)
-
- // the data in each segment is correct
- require.Contains(t, m.months[0].predefinedSegments[0], 0)
- require.Contains(t, m.months[0].predefinedSegments[1], 1)
- require.Contains(t, m.months[0].predefinedSegments[7], 2)
-}
-
-// Test_multipleMonthsActivityClients_addRepeatedClients adds repeated clients
-// from 1 month ago and 2 months ago, and verifies that the correct clients are
-// added based on namespace, mount, and non-entity attributes
-func Test_multipleMonthsActivityClients_addRepeatedClients(t *testing.T) {
- m := newMultipleMonthsActivityClients(3)
- defaultMount := "default"
-
- require.NoError(t, m.addClientToMonth(2, &generation.Client{Count: 2}, "identity", nil))
- require.NoError(t, m.addClientToMonth(2, &generation.Client{Count: 2, Namespace: "other_ns"}, defaultMount, nil))
- require.NoError(t, m.addClientToMonth(1, &generation.Client{Count: 2}, defaultMount, nil))
- require.NoError(t, m.addClientToMonth(1, &generation.Client{Count: 2, NonEntity: true}, defaultMount, nil))
-
- month2Clients := m.months[2].clients
- month1Clients := m.months[1].clients
-
- thisMonth := m.months[0]
- // this will match the first client in month 1
- require.NoError(t, m.addRepeatedClients(0, &generation.Client{Count: 1, Repeated: true}, defaultMount, nil))
- require.Contains(t, month1Clients, thisMonth.clients[0])
-
- // this will match the 3rd client in month 1
- require.NoError(t, m.addRepeatedClients(0, &generation.Client{Count: 1, Repeated: true, NonEntity: true}, defaultMount, nil))
- require.Equal(t, month1Clients[2], thisMonth.clients[1])
-
- // this will match the first two clients in month 1
- require.NoError(t, m.addRepeatedClients(0, &generation.Client{Count: 2, Repeated: true}, defaultMount, nil))
- require.Equal(t, month1Clients[0:2], thisMonth.clients[2:4])
-
- // this will match the first client in month 2
- require.NoError(t, m.addRepeatedClients(0, &generation.Client{Count: 1, RepeatedFromMonth: 2}, "identity", nil))
- require.Equal(t, month2Clients[0], thisMonth.clients[4])
-
- // this will match the 3rd client in month 2
- require.NoError(t, m.addRepeatedClients(0, &generation.Client{Count: 1, RepeatedFromMonth: 2, Namespace: "other_ns"}, defaultMount, nil))
- require.Equal(t, month2Clients[2], thisMonth.clients[5])
-
- require.Error(t, m.addRepeatedClients(0, &generation.Client{Count: 1, RepeatedFromMonth: 2, Namespace: "other_ns"}, "other_mount", nil))
-}
-
-// Test_singleMonthActivityClients_populateSegments calls populateSegments for a
-// collection of 5 clients, segmented in various ways. The test ensures that the
-// resulting map has the correct clients for each segment index
-func Test_singleMonthActivityClients_populateSegments(t *testing.T) {
- clients := []*activity.EntityRecord{
- {ClientID: "a"},
- {ClientID: "b"},
- {ClientID: "c"},
- {ClientID: "d"},
- {ClientID: "e"},
- }
- cases := []struct {
- name string
- segments map[int][]int
- numSegments int
- emptyIndexes []int32
- skipIndexes []int32
- wantSegments map[int][]*activity.EntityRecord
- }{
- {
- name: "segmented",
- segments: map[int][]int{
- 0: {0, 1},
- 1: {2, 3},
- 2: {4},
- },
- wantSegments: map[int][]*activity.EntityRecord{
- 0: {{ClientID: "a"}, {ClientID: "b"}},
- 1: {{ClientID: "c"}, {ClientID: "d"}},
- 2: {{ClientID: "e"}},
- },
- },
- {
- name: "segmented with skip and empty",
- segments: map[int][]int{
- 0: {0, 1},
- 2: {0, 1},
- },
- emptyIndexes: []int32{1, 4},
- skipIndexes: []int32{3},
- wantSegments: map[int][]*activity.EntityRecord{
- 0: {{ClientID: "a"}, {ClientID: "b"}},
- 1: {},
- 2: {{ClientID: "a"}, {ClientID: "b"}},
- 3: nil,
- 4: {},
- },
- },
- {
- name: "all clients",
- numSegments: 0,
- wantSegments: map[int][]*activity.EntityRecord{
- 0: {{ClientID: "a"}, {ClientID: "b"}, {ClientID: "c"}, {ClientID: "d"}, {ClientID: "e"}},
- },
- },
- {
- name: "all clients split",
- numSegments: 2,
- wantSegments: map[int][]*activity.EntityRecord{
- 0: {{ClientID: "a"}, {ClientID: "b"}, {ClientID: "c"}},
- 1: {{ClientID: "d"}, {ClientID: "e"}},
- },
- },
- {
- name: "all clients with skip and empty",
- numSegments: 5,
- skipIndexes: []int32{0, 3},
- emptyIndexes: []int32{2},
- wantSegments: map[int][]*activity.EntityRecord{
- 0: nil,
- 1: {{ClientID: "a"}, {ClientID: "b"}, {ClientID: "c"}},
- 2: {},
- 3: nil,
- 4: {{ClientID: "d"}, {ClientID: "e"}},
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- s := singleMonthActivityClients{predefinedSegments: tc.segments, clients: clients, generationParameters: &generation.Data{EmptySegmentIndexes: tc.emptyIndexes, SkipSegmentIndexes: tc.skipIndexes, NumSegments: int32(tc.numSegments)}}
- gotSegments, err := s.populateSegments()
- require.NoError(t, err)
- require.Equal(t, tc.wantSegments, gotSegments)
- })
- }
-}
-
-// Test_multipleMonthsActivityClients_write_entities writes 4 months of data
-// splitting some months across segments and using empty segments and skipped
-// segments. Entities are written and then storage is queried. The test verifies
-// that the correct timestamps are present in the activity log and that the correct
-// segment numbers for each month contain the correct number of clients
-func Test_multipleMonthsActivityClients_write_entities(t *testing.T) {
- index5 := int32(5)
- index4 := int32(4)
- data := &generation.ActivityLogMockInput{
- Write: []generation.WriteOptions{
- generation.WriteOptions_WRITE_ENTITIES,
- },
- Data: []*generation.Data{
- {
- // segments: 0:[x,y], 1:[z]
- Month: &generation.Data_MonthsAgo{MonthsAgo: 3},
- Clients: &generation.Data_All{All: &generation.Clients{Clients: []*generation.Client{{Count: 3}}}},
- NumSegments: 2,
- },
- {
- // segments: 1:[a,b,c], 2:[d,e]
- Month: &generation.Data_MonthsAgo{MonthsAgo: 2},
- Clients: &generation.Data_All{All: &generation.Clients{Clients: []*generation.Client{{Count: 5}}}},
- NumSegments: 3,
- SkipSegmentIndexes: []int32{0},
- },
- {
- // segments: 5:[f,g]
- Month: &generation.Data_MonthsAgo{MonthsAgo: 1},
- Clients: &generation.Data_Segments{
- Segments: &generation.Segments{Segments: []*generation.Segment{{
- SegmentIndex: &index5,
- Clients: &generation.Clients{Clients: []*generation.Client{{Count: 2}}},
- }}},
- },
- },
- {
- // segments: 1:[], 2:[], 4:[n], 5:[o]
- Month: &generation.Data_CurrentMonth{},
- EmptySegmentIndexes: []int32{1, 2},
- Clients: &generation.Data_Segments{
- Segments: &generation.Segments{Segments: []*generation.Segment{
- {
- SegmentIndex: &index5,
- Clients: &generation.Clients{Clients: []*generation.Client{{Count: 1}}},
- },
- {
- SegmentIndex: &index4,
- Clients: &generation.Clients{Clients: []*generation.Client{{Count: 1}}},
- },
- }},
- },
- },
- },
- }
-
- core, _, _ := TestCoreUnsealed(t)
- marshaled, err := protojson.Marshal(data)
- require.NoError(t, err)
- req := logical.TestRequest(t, logical.CreateOperation, "internal/counters/activity/write")
- req.Data = map[string]interface{}{"input": string(marshaled)}
- resp, err := core.systemBackend.HandleRequest(namespace.RootContext(nil), req)
- require.NoError(t, err)
- paths := resp.Data["paths"].([]string)
- require.Len(t, paths, 9)
-
- times, err := core.activityLog.availableLogs(context.Background())
- require.NoError(t, err)
- require.Len(t, times, 4)
-
- sortPaths := func(monthPaths []string) {
- sort.Slice(monthPaths, func(i, j int) bool {
- iVal, _ := parseSegmentNumberFromPath(monthPaths[i])
- jVal, _ := parseSegmentNumberFromPath(monthPaths[j])
- return iVal < jVal
- })
- }
-
- month0Paths := paths[0:4]
- month1Paths := paths[4:5]
- month2Paths := paths[5:7]
- month3Paths := paths[7:9]
- sortPaths(month0Paths)
- sortPaths(month1Paths)
- sortPaths(month2Paths)
- sortPaths(month3Paths)
- entities := func(paths []string) map[int][]*activity.EntityRecord {
- segments := make(map[int][]*activity.EntityRecord)
- for _, path := range paths {
- segmentNum, _ := parseSegmentNumberFromPath(path)
- entry, err := core.activityLog.view.Get(context.Background(), path)
- require.NoError(t, err)
- if entry == nil {
- segments[segmentNum] = []*activity.EntityRecord{}
- continue
- }
- activities := &activity.EntityActivityLog{}
- err = proto.Unmarshal(entry.Value, activities)
- require.NoError(t, err)
- segments[segmentNum] = activities.Clients
- }
- return segments
- }
- month0Entities := entities(month0Paths)
- require.Len(t, month0Entities, 4)
- require.Contains(t, month0Entities, 1)
- require.Contains(t, month0Entities, 2)
- require.Contains(t, month0Entities, 4)
- require.Contains(t, month0Entities, 5)
- require.Len(t, month0Entities[1], 0)
- require.Len(t, month0Entities[2], 0)
- require.Len(t, month0Entities[4], 1)
- require.Len(t, month0Entities[5], 1)
-
- month1Entities := entities(month1Paths)
- require.Len(t, month1Entities, 1)
- require.Contains(t, month1Entities, 5)
- require.Len(t, month1Entities[5], 2)
-
- month2Entities := entities(month2Paths)
- require.Len(t, month2Entities, 2)
- require.Contains(t, month2Entities, 1)
- require.Contains(t, month2Entities, 2)
- require.Len(t, month2Entities[1], 3)
- require.Len(t, month2Entities[2], 2)
-
- month3Entities := entities(month3Paths)
- require.Len(t, month3Entities, 2)
- require.Contains(t, month3Entities, 0)
- require.Contains(t, month3Entities, 1)
- require.Len(t, month3Entities[0], 2)
- require.Len(t, month3Entities[1], 1)
-}
diff --git a/vault/logical_system_integ_test.go b/vault/logical_system_integ_test.go
deleted file mode 100644
index d5231614c..000000000
--- a/vault/logical_system_integ_test.go
+++ /dev/null
@@ -1,258 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault_test
-
-import (
- "fmt"
- "testing"
- "time"
-
- "github.com/go-test/deep"
- "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/helper/testhelpers/corehelpers"
- vaulthttp "github.com/hashicorp/vault/http"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/physical"
- "github.com/hashicorp/vault/sdk/physical/inmem"
- "github.com/hashicorp/vault/vault"
- "github.com/hashicorp/vault/version"
-)
-
-func TestSystemBackend_InternalUIResultantACL(t *testing.T) {
- t.Parallel()
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- NumCores: 1,
- })
- cluster.Start()
- defer cluster.Cleanup()
- client := cluster.Cores[0].Client
-
- resp, err := client.Auth().Token().Create(&api.TokenCreateRequest{
- Policies: []string{"default"},
- })
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("nil response")
- }
- if resp.Auth == nil {
- t.Fatal("nil auth")
- }
- if resp.Auth.ClientToken == "" {
- t.Fatal("empty client token")
- }
-
- client.SetToken(resp.Auth.ClientToken)
-
- resp, err = client.Logical().Read("sys/internal/ui/resultant-acl")
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("nil response")
- }
- if resp.Data == nil {
- t.Fatal("nil data")
- }
-
- exp := map[string]interface{}{
- "exact_paths": map[string]interface{}{
- "auth/token/lookup-self": map[string]interface{}{
- "capabilities": []interface{}{
- "read",
- },
- },
- "auth/token/renew-self": map[string]interface{}{
- "capabilities": []interface{}{
- "update",
- },
- },
- "auth/token/revoke-self": map[string]interface{}{
- "capabilities": []interface{}{
- "update",
- },
- },
- "sys/capabilities-self": map[string]interface{}{
- "capabilities": []interface{}{
- "update",
- },
- },
- "sys/control-group/request": map[string]interface{}{
- "capabilities": []interface{}{
- "update",
- },
- },
- "sys/internal/ui/resultant-acl": map[string]interface{}{
- "capabilities": []interface{}{
- "read",
- },
- },
- "sys/leases/lookup": map[string]interface{}{
- "capabilities": []interface{}{
- "update",
- },
- },
- "sys/leases/renew": map[string]interface{}{
- "capabilities": []interface{}{
- "update",
- },
- },
- "sys/renew": map[string]interface{}{
- "capabilities": []interface{}{
- "update",
- },
- },
- "sys/tools/hash": map[string]interface{}{
- "capabilities": []interface{}{
- "update",
- },
- },
- "sys/wrapping/lookup": map[string]interface{}{
- "capabilities": []interface{}{
- "update",
- },
- },
- "sys/wrapping/unwrap": map[string]interface{}{
- "capabilities": []interface{}{
- "update",
- },
- },
- "sys/wrapping/wrap": map[string]interface{}{
- "capabilities": []interface{}{
- "update",
- },
- },
- },
- "glob_paths": map[string]interface{}{
- "cubbyhole/": map[string]interface{}{
- "capabilities": []interface{}{
- "create",
- "delete",
- "list",
- "read",
- "update",
- },
- },
- "sys/tools/hash/": map[string]interface{}{
- "capabilities": []interface{}{
- "update",
- },
- },
- },
- "root": false,
- }
-
- if diff := deep.Equal(resp.Data, exp); diff != nil {
- t.Fatal(diff)
- }
-}
-
-func TestSystemBackend_HAStatus(t *testing.T) {
- logger := logging.NewVaultLogger(hclog.Trace)
- inm, err := inmem.NewTransactionalInmem(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- inmha, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
-
- conf := &vault.CoreConfig{
- Physical: inm,
- HAPhysical: inmha.(physical.HABackend),
- }
- opts := &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- }
- cluster := vault.NewTestCluster(t, conf, opts)
- cluster.Start()
- defer cluster.Cleanup()
-
- corehelpers.RetryUntil(t, 15*time.Second, func() error {
- // Use standby deliberately to make sure it forwards
- client := cluster.Cores[1].Client
- resp, err := client.Sys().HAStatus()
- if err != nil {
- t.Fatal(err)
- }
-
- if len(resp.Nodes) != len(cluster.Cores) {
- return fmt.Errorf("expected %d nodes, got %d", len(cluster.Cores), len(resp.Nodes))
- }
- return nil
- })
-}
-
-// TestSystemBackend_VersionHistory_unauthenticated tests the sys/version-history
-// endpoint without providing a token. Requests to the endpoint must be
-// authenticated and thus a 403 response is expected.
-func TestSystemBackend_VersionHistory_unauthenticated(t *testing.T) {
- t.Parallel()
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- NumCores: 1,
- })
- cluster.Start()
- defer cluster.Cleanup()
- client := cluster.Cores[0].Client
-
- client.SetToken("")
- resp, err := client.Logical().List("sys/version-history")
-
- if resp != nil {
- t.Fatalf("expected nil response, resp: %#v", resp)
- }
-
- respErr, ok := err.(*api.ResponseError)
- if !ok {
- t.Fatalf("unexpected error type: err: %#v", err)
- }
-
- if respErr.StatusCode != 403 {
- t.Fatalf("expected response status to be 403, actual: %d", respErr.StatusCode)
- }
-}
-
-// TestSystemBackend_VersionHistory_authenticated tests the sys/version-history
-// endpoint with authentication. Without synthetically altering the underlying
-// core/versions storage entries, a single version entry should exist.
-func TestSystemBackend_VersionHistory_authenticated(t *testing.T) {
- t.Parallel()
- cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
- HandlerFunc: vaulthttp.Handler,
- NumCores: 1,
- })
- cluster.Start()
- defer cluster.Cleanup()
- client := cluster.Cores[0].Client
-
- resp, err := client.Logical().List("sys/version-history")
- if err != nil || resp == nil {
- t.Fatalf("request failed, err: %v, resp: %#v", err, resp)
- }
-
- var ok bool
- var keys []interface{}
- var keyInfo map[string]interface{}
-
- if keys, ok = resp.Data["keys"].([]interface{}); !ok {
- t.Fatalf("expected keys to be array, actual: %#v", resp.Data["keys"])
- }
-
- if keyInfo, ok = resp.Data["key_info"].(map[string]interface{}); !ok {
- t.Fatalf("expected key_info to be map, actual: %#v", resp.Data["key_info"])
- }
-
- if len(keys) != 1 {
- t.Fatalf("expected single version history entry for %q", version.Version)
- }
-
- if keyInfo[version.Version] == nil {
- t.Fatalf("expected version %s to be present in key_info, actual: %#v", version.Version, keyInfo)
- }
-}
diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go
deleted file mode 100644
index efd1a11a5..000000000
--- a/vault/logical_system_test.go
+++ /dev/null
@@ -1,5977 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "encoding/base64"
- "encoding/hex"
- "fmt"
- "io/ioutil"
- "net/http"
- "os"
- "path/filepath"
- "reflect"
- "strings"
- "testing"
- "time"
-
- "github.com/fatih/structs"
- "github.com/go-test/deep"
- "github.com/hashicorp/go-hclog"
- semver "github.com/hashicorp/go-version"
- credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
- "github.com/hashicorp/vault/helper/builtinplugins"
- "github.com/hashicorp/vault/helper/experiments"
- "github.com/hashicorp/vault/helper/identity"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/helper/random"
- "github.com/hashicorp/vault/helper/testhelpers/corehelpers"
- "github.com/hashicorp/vault/helper/versions"
- "github.com/hashicorp/vault/sdk/framework"
- "github.com/hashicorp/vault/sdk/helper/compressutil"
- "github.com/hashicorp/vault/sdk/helper/consts"
- "github.com/hashicorp/vault/sdk/helper/jsonutil"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/helper/pluginutil"
- "github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/version"
- "github.com/mitchellh/mapstructure"
-)
-
-func TestSystemBackend_RootPaths(t *testing.T) {
- expected := []string{
- "auth/*",
- "remount",
- "audit",
- "audit/*",
- "raw",
- "raw/*",
- "replication/primary/secondary-token",
- "replication/performance/primary/secondary-token",
- "replication/dr/primary/secondary-token",
- "replication/reindex",
- "replication/dr/reindex",
- "replication/performance/reindex",
- "rotate",
- "config/cors",
- "config/auditing/*",
- "config/ui/headers/*",
- "plugins/catalog/*",
- "revoke-prefix/*",
- "revoke-force/*",
- "leases/revoke-prefix/*",
- "leases/revoke-force/*",
- "leases/lookup/*",
- "storage/raft/snapshot-auto/config/*",
- "leases",
- "internal/inspect/*",
- }
-
- b := testSystemBackend(t)
- actual := b.SpecialPaths().Root
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("bad: mismatch\nexpected:\n%#v\ngot:\n%#v", expected, actual)
- }
-}
-
-func TestSystemConfigCORS(t *testing.T) {
- b := testSystemBackend(t)
- paths := b.(*SystemBackend).configPaths()
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "")
- b.(*SystemBackend).Core.systemBarrierView = view
-
- req := logical.TestRequest(t, logical.UpdateOperation, "config/cors")
- req.Data["allowed_origins"] = "http://www.example.com"
- req.Data["allowed_headers"] = "X-Custom-Header"
- _, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
-
- expected := &logical.Response{
- Data: map[string]interface{}{
- "enabled": true,
- "allowed_origins": []string{"http://www.example.com"},
- "allowed_headers": append(StdAllowedHeaders, "X-Custom-Header"),
- },
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "config/cors")
- actual, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("bad: %#v", actual)
- }
- schema.ValidateResponse(
- t,
- schema.FindResponseSchema(t, paths, 0, req.Operation),
- actual,
- true,
- )
-
- // Do it again. Bug #6182
- req = logical.TestRequest(t, logical.UpdateOperation, "config/cors")
- req.Data["allowed_origins"] = "http://www.example.com"
- req.Data["allowed_headers"] = "X-Custom-Header"
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- schema.ValidateResponse(
- t,
- schema.FindResponseSchema(t, paths, 0, req.Operation),
- resp,
- true,
- )
-
- req = logical.TestRequest(t, logical.ReadOperation, "config/cors")
- actual, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- schema.ValidateResponse(
- t,
- schema.FindResponseSchema(t, paths, 0, req.Operation),
- actual,
- true,
- )
-
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("bad: %#v", actual)
- }
-
- req = logical.TestRequest(t, logical.DeleteOperation, "config/cors")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- schema.ValidateResponse(
- t,
- schema.FindResponseSchema(t, paths, 0, req.Operation),
- resp,
- true,
- )
-
- req = logical.TestRequest(t, logical.ReadOperation, "config/cors")
- actual, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- schema.ValidateResponse(
- t,
- schema.FindResponseSchema(t, paths, 0, req.Operation),
- actual,
- true,
- )
-
- expected = &logical.Response{
- Data: map[string]interface{}{
- "enabled": false,
- },
- }
-
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("DELETE FAILED -- bad: %#v", actual)
- }
-}
-
-func TestSystemBackend_mounts(t *testing.T) {
- b := testSystemBackend(t)
- req := logical.TestRequest(t, logical.ReadOperation, "mounts")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // We can't know the pointer address ahead of time so simply
- // copy what's given
- exp := map[string]interface{}{
- "secret/": map[string]interface{}{
- "type": "kv",
- "external_entropy_access": false,
- "description": "key/value secret storage",
- "accessor": resp.Data["secret/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["secret/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": resp.Data["secret/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
- "max_lease_ttl": resp.Data["secret/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
- "force_no_cache": false,
- },
- "local": false,
- "seal_wrap": false,
- "options": map[string]string{
- "version": "1",
- },
- "plugin_version": "",
- "running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
- "running_sha256": "",
- },
- "sys/": map[string]interface{}{
- "type": "system",
- "external_entropy_access": false,
- "description": "system endpoints used for control, policy and debugging",
- "accessor": resp.Data["sys/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["sys/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": resp.Data["sys/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
- "max_lease_ttl": resp.Data["sys/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
- "force_no_cache": false,
- "passthrough_request_headers": []string{"Accept"},
- },
- "local": false,
- "seal_wrap": true,
- "options": map[string]string(nil),
- "plugin_version": "",
- "running_plugin_version": versions.DefaultBuiltinVersion,
- "running_sha256": "",
- },
- "cubbyhole/": map[string]interface{}{
- "description": "per-token private secret storage",
- "type": "cubbyhole",
- "external_entropy_access": false,
- "accessor": resp.Data["cubbyhole/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["cubbyhole/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": resp.Data["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
- "max_lease_ttl": resp.Data["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
- "force_no_cache": false,
- },
- "local": true,
- "seal_wrap": false,
- "options": map[string]string(nil),
- "plugin_version": "",
- "running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
- "running_sha256": "",
- },
- "identity/": map[string]interface{}{
- "description": "identity store",
- "type": "identity",
- "external_entropy_access": false,
- "accessor": resp.Data["identity/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["identity/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": resp.Data["identity/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
- "max_lease_ttl": resp.Data["identity/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
- "force_no_cache": false,
- "passthrough_request_headers": []string{"Authorization"},
- },
- "local": false,
- "seal_wrap": false,
- "options": map[string]string(nil),
- "plugin_version": "",
- "running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
- "running_sha256": "",
- },
- }
- if diff := deep.Equal(resp.Data, exp); len(diff) > 0 {
- t.Fatalf("bad, diff: %#v", diff)
- }
-
- for name, conf := range exp {
- req := logical.TestRequest(t, logical.ReadOperation, "mounts/"+name)
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if diff := deep.Equal(resp.Data, conf); len(diff) > 0 {
- t.Fatalf("bad, diff: %#v", diff)
- }
-
- // validate the response structure for mount named read
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
- }
-}
-
-func TestSystemBackend_mount(t *testing.T) {
- b := testSystemBackend(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "mounts/prod/secret/")
- req.Data["type"] = "kv"
- req.Data["config"] = map[string]interface{}{
- "default_lease_ttl": "35m",
- "max_lease_ttl": "45m",
- }
- req.Data["local"] = true
- req.Data["seal_wrap"] = true
- req.Data["options"] = map[string]string{
- "version": "1",
- }
-
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // validate the response structure for mount named update
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- req = logical.TestRequest(t, logical.ReadOperation, "mounts")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // We can't know the pointer address ahead of time so simply
- // copy what's given
- exp := map[string]interface{}{
- "secret/": map[string]interface{}{
- "type": "kv",
- "external_entropy_access": false,
- "description": "key/value secret storage",
- "accessor": resp.Data["secret/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["secret/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": resp.Data["secret/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
- "max_lease_ttl": resp.Data["secret/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
- "force_no_cache": false,
- },
- "local": false,
- "seal_wrap": false,
- "options": map[string]string{
- "version": "1",
- },
- "plugin_version": "",
- "running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
- "running_sha256": "",
- },
- "sys/": map[string]interface{}{
- "type": "system",
- "external_entropy_access": false,
- "description": "system endpoints used for control, policy and debugging",
- "accessor": resp.Data["sys/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["sys/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": resp.Data["sys/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
- "max_lease_ttl": resp.Data["sys/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
- "force_no_cache": false,
- "passthrough_request_headers": []string{"Accept"},
- },
- "local": false,
- "seal_wrap": true,
- "options": map[string]string(nil),
- "plugin_version": "",
- "running_plugin_version": versions.DefaultBuiltinVersion,
- "running_sha256": "",
- },
- "cubbyhole/": map[string]interface{}{
- "description": "per-token private secret storage",
- "type": "cubbyhole",
- "external_entropy_access": false,
- "accessor": resp.Data["cubbyhole/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["cubbyhole/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": resp.Data["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
- "max_lease_ttl": resp.Data["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
- "force_no_cache": false,
- },
- "local": true,
- "seal_wrap": false,
- "options": map[string]string(nil),
- "plugin_version": "",
- "running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
- "running_sha256": "",
- },
- "identity/": map[string]interface{}{
- "description": "identity store",
- "type": "identity",
- "external_entropy_access": false,
- "accessor": resp.Data["identity/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["identity/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": resp.Data["identity/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
- "max_lease_ttl": resp.Data["identity/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
- "force_no_cache": false,
- "passthrough_request_headers": []string{"Authorization"},
- },
- "local": false,
- "seal_wrap": false,
- "options": map[string]string(nil),
- "plugin_version": "",
- "running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
- "running_sha256": "",
- },
- "prod/secret/": map[string]interface{}{
- "description": "",
- "type": "kv",
- "external_entropy_access": false,
- "accessor": resp.Data["prod/secret/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["prod/secret/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": int64(2100),
- "max_lease_ttl": int64(2700),
- "force_no_cache": false,
- },
- "local": true,
- "seal_wrap": true,
- "options": map[string]string{
- "version": "1",
- },
- "plugin_version": "",
- "running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
- "running_sha256": "",
- },
- }
- if diff := deep.Equal(resp.Data, exp); len(diff) > 0 {
- t.Fatalf("bad: diff: %#v", diff)
- }
-}
-
-func TestSystemBackend_mount_force_no_cache(t *testing.T) {
- core, b, _ := testCoreSystemBackend(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "mounts/prod/secret/")
- req.Data["type"] = "kv"
- req.Data["config"] = map[string]interface{}{
- "force_no_cache": true,
- }
-
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- mountEntry := core.router.MatchingMountEntry(namespace.RootContext(nil), "prod/secret/")
- if mountEntry == nil {
- t.Fatalf("missing mount entry")
- }
- if !mountEntry.Config.ForceNoCache {
- t.Fatalf("bad config %#v", mountEntry)
- }
-}
-
-func TestSystemBackend_mount_invalid(t *testing.T) {
- b := testSystemBackend(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "mounts/prod/secret/")
- req.Data["type"] = "nope"
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if resp.Data["error"] != `plugin not found in the catalog: nope` {
- t.Fatalf("bad: %v", resp)
- }
-}
-
-func TestSystemBackend_unmount(t *testing.T) {
- b := testSystemBackend(t)
-
- req := logical.TestRequest(t, logical.DeleteOperation, "mounts/secret/")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // validate the response structure for mount named delete
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-}
-
-var capabilitiesPolicy = `
-name = "test"
-path "foo/bar*" {
- capabilities = ["create", "sudo", "update"]
-}
-path "sys/capabilities*" {
- capabilities = ["update"]
-}
-path "bar/baz" {
- capabilities = ["read", "update"]
-}
-path "bar/baz" {
- capabilities = ["delete"]
-}
-`
-
-func TestSystemBackend_PathCapabilities(t *testing.T) {
- var resp *logical.Response
- var err error
-
- core, b, rootToken := testCoreSystemBackend(t)
-
- policy, _ := ParseACLPolicy(namespace.RootNamespace, capabilitiesPolicy)
- err = core.policyStore.SetPolicy(namespace.RootContext(nil), policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- path1 := "foo/bar"
- path2 := "foo/bar/sample"
- path3 := "sys/capabilities"
- path4 := "bar/baz"
-
- rootCheckFunc := func(t *testing.T, resp *logical.Response) {
- // All the paths should have "root" as the capability
- expectedRoot := []string{"root"}
- if !reflect.DeepEqual(resp.Data[path1], expectedRoot) ||
- !reflect.DeepEqual(resp.Data[path2], expectedRoot) ||
- !reflect.DeepEqual(resp.Data[path3], expectedRoot) ||
- !reflect.DeepEqual(resp.Data[path4], expectedRoot) {
- t.Fatalf("bad: capabilities; expected: %#v, actual: %#v", expectedRoot, resp.Data)
- }
- }
-
- // Check the capabilities using the root token
- req := &logical.Request{
- Path: "capabilities",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "paths": []string{path1, path2, path3, path4},
- "token": rootToken,
- },
- }
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
- rootCheckFunc(t, resp)
-
- // Check the capabilities using capabilities-self
- req = &logical.Request{
- ClientToken: rootToken,
- Path: "capabilities-self",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "paths": []string{path1, path2, path3, path4},
- },
- }
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
- rootCheckFunc(t, resp)
-
- // Lookup the accessor of the root token
- te, err := core.tokenStore.Lookup(namespace.RootContext(nil), rootToken)
- if err != nil {
- t.Fatal(err)
- }
-
- // Check the capabilities using capabilities-accessor endpoint
- req = &logical.Request{
- Path: "capabilities-accessor",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "paths": []string{path1, path2, path3, path4},
- "accessor": te.Accessor,
- },
- }
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
- rootCheckFunc(t, resp)
-
- // Create a non-root token
- testMakeServiceTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"test"})
-
- nonRootCheckFunc := func(t *testing.T, resp *logical.Response) {
- expected1 := []string{"create", "sudo", "update"}
- expected2 := expected1
- expected3 := []string{"update"}
- expected4 := []string{"delete", "read", "update"}
-
- if !reflect.DeepEqual(resp.Data[path1], expected1) ||
- !reflect.DeepEqual(resp.Data[path2], expected2) ||
- !reflect.DeepEqual(resp.Data[path3], expected3) ||
- !reflect.DeepEqual(resp.Data[path4], expected4) {
- t.Fatalf("bad: capabilities; actual: %#v", resp.Data)
- }
- }
-
- // Check the capabilities using a non-root token
- req = &logical.Request{
- Path: "capabilities",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "paths": []string{path1, path2, path3, path4},
- "token": "tokenid",
- },
- }
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
- nonRootCheckFunc(t, resp)
-
- // Check the capabilities of a non-root token using capabilities-self
- // endpoint
- req = &logical.Request{
- ClientToken: "tokenid",
- Path: "capabilities-self",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "paths": []string{path1, path2, path3, path4},
- },
- }
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
- nonRootCheckFunc(t, resp)
-
- // Lookup the accessor of the non-root token
- te, err = core.tokenStore.Lookup(namespace.RootContext(nil), "tokenid")
- if err != nil {
- t.Fatal(err)
- }
-
- // Check the capabilities using a non-root token using
- // capabilities-accessor endpoint
- req = &logical.Request{
- Path: "capabilities-accessor",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "paths": []string{path1, path2, path3, path4},
- "accessor": te.Accessor,
- },
- }
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
- nonRootCheckFunc(t, resp)
-}
-
-func TestSystemBackend_Capabilities_BC(t *testing.T) {
- testCapabilities(t, "capabilities")
- testCapabilities(t, "capabilities-self")
-}
-
-func testCapabilities(t *testing.T, endpoint string) {
- core, b, rootToken := testCoreSystemBackend(t)
- req := logical.TestRequest(t, logical.UpdateOperation, endpoint)
- if endpoint == "capabilities-self" {
- req.ClientToken = rootToken
- } else {
- req.Data["token"] = rootToken
- }
- req.Data["path"] = "any_path"
-
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatalf("bad: %v", resp)
- }
-
- actual := resp.Data["capabilities"]
- expected := []string{"root"}
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
- }
-
- policy, _ := ParseACLPolicy(namespace.RootNamespace, capabilitiesPolicy)
- err = core.policyStore.SetPolicy(namespace.RootContext(nil), policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- testMakeServiceTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"test"})
- req = logical.TestRequest(t, logical.UpdateOperation, endpoint)
- if endpoint == "capabilities-self" {
- req.ClientToken = "tokenid"
- } else {
- req.Data["token"] = "tokenid"
- }
- req.Data["path"] = "foo/bar"
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatalf("bad: %v", resp)
- }
-
- actual = resp.Data["capabilities"]
- expected = []string{"create", "sudo", "update"}
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
- }
-}
-
-func TestSystemBackend_CapabilitiesAccessor_BC(t *testing.T) {
- core, b, rootToken := testCoreSystemBackend(t)
- te, err := core.tokenStore.Lookup(namespace.RootContext(nil), rootToken)
- if err != nil {
- t.Fatal(err)
- }
-
- req := logical.TestRequest(t, logical.UpdateOperation, "capabilities-accessor")
- // Accessor of root token
- req.Data["accessor"] = te.Accessor
- req.Data["path"] = "any_path"
-
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatalf("bad: %v", resp)
- }
-
- actual := resp.Data["capabilities"]
- expected := []string{"root"}
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
- }
-
- policy, _ := ParseACLPolicy(namespace.RootNamespace, capabilitiesPolicy)
- err = core.policyStore.SetPolicy(namespace.RootContext(nil), policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- testMakeServiceTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"test"})
-
- te, err = core.tokenStore.Lookup(namespace.RootContext(nil), "tokenid")
- if err != nil {
- t.Fatal(err)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "capabilities-accessor")
- req.Data["accessor"] = te.Accessor
- req.Data["path"] = "foo/bar"
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatalf("bad: %v", resp)
- }
-
- actual = resp.Data["capabilities"]
- expected = []string{"create", "sudo", "update"}
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
- }
-}
-
-func TestSystemBackend_remount_auth(t *testing.T) {
- err := AddTestCredentialBackend("userpass", credUserpass.Factory)
- if err != nil {
- t.Fatal(err)
- }
-
- c, b, _ := testCoreSystemBackend(t)
-
- userpassMe := &MountEntry{
- Table: credentialTableType,
- Path: "userpass1/",
- Type: "userpass",
- Description: "userpass",
- }
- err = c.enableCredential(namespace.RootContext(nil), userpassMe)
- if err != nil {
- t.Fatal(err)
- }
-
- req := logical.TestRequest(t, logical.UpdateOperation, "remount")
- req.Data["from"] = "auth/userpass1"
- req.Data["to"] = "auth/userpass2"
- req.Data["config"] = structs.Map(MountConfig{})
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
-
- // validate the response structure for remount named read
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- corehelpers.RetryUntil(t, 5*time.Second, func() error {
- req = logical.TestRequest(t, logical.ReadOperation, fmt.Sprintf("remount/status/%s", resp.Data["migration_id"]))
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // validate the response structure for remount status read
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- migrationInfo := resp.Data["migration_info"].(*MountMigrationInfo)
- if migrationInfo.MigrationStatus != MigrationSuccessStatus.String() {
- return fmt.Errorf("Expected migration status to be successful, got %q", migrationInfo.MigrationStatus)
- }
- return nil
- })
-}
-
-func TestSystemBackend_remount_auth_invalid(t *testing.T) {
- b := testSystemBackend(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "remount")
- req.Data["from"] = "auth/unknown"
- req.Data["to"] = "auth/foo"
- req.Data["config"] = structs.Map(MountConfig{})
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
-
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if !strings.Contains(resp.Data["error"].(string), "no matching mount at \"auth/unknown/\"") {
- t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
- }
-
- req.Data["to"] = "foo"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
-
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if !strings.Contains(resp.Data["error"].(string), "cannot remount auth mount to non-auth mount \"foo/\"") {
- t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
- }
-}
-
-func TestSystemBackend_remount_auth_protected(t *testing.T) {
- b := testSystemBackend(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "remount")
- req.Data["from"] = "auth/token"
- req.Data["to"] = "auth/foo"
- req.Data["config"] = structs.Map(MountConfig{})
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
-
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if !strings.Contains(resp.Data["error"].(string), "cannot remount \"auth/token/\"") {
- t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
- }
-
- req.Data["from"] = "auth/foo"
- req.Data["to"] = "auth/token"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
-
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if !strings.Contains(resp.Data["error"].(string), "cannot remount to destination \"auth/token/\"") {
- t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
- }
-}
-
-func TestSystemBackend_remount_auth_destinationInUse(t *testing.T) {
- err := AddTestCredentialBackend("userpass", credUserpass.Factory)
- if err != nil {
- t.Fatal(err)
- }
-
- c, b, _ := testCoreSystemBackend(t)
-
- userpassMe := &MountEntry{
- Table: credentialTableType,
- Path: "userpass1/",
- Type: "userpass",
- Description: "userpass",
- }
- err = c.enableCredential(namespace.RootContext(nil), userpassMe)
- if err != nil {
- t.Fatal(err)
- }
-
- userpassMe2 := &MountEntry{
- Table: credentialTableType,
- Path: "userpass2/",
- Type: "userpass",
- Description: "userpass",
- }
- err = c.enableCredential(namespace.RootContext(nil), userpassMe2)
- if err != nil {
- t.Fatal(err)
- }
-
- req := logical.TestRequest(t, logical.UpdateOperation, "remount")
- req.Data["from"] = "auth/userpass1"
- req.Data["to"] = "auth/userpass2"
- req.Data["config"] = structs.Map(MountConfig{})
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
-
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if !strings.Contains(resp.Data["error"].(string), "path already in use at \"auth/userpass2/\"") {
- t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
- }
-
- req.Data["to"] = "auth/userpass2/mypass"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
-
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if !strings.Contains(resp.Data["error"].(string), "path already in use at \"auth/userpass2/\"") {
- t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
- }
-
- userpassMe3 := &MountEntry{
- Table: credentialTableType,
- Path: "userpass3/mypass/",
- Type: "userpass",
- Description: "userpass",
- }
- err = c.enableCredential(namespace.RootContext(nil), userpassMe3)
- if err != nil {
- t.Fatal(err)
- }
-
- req.Data["to"] = "auth/userpass3/"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
-
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if !strings.Contains(resp.Data["error"].(string), "path already in use at \"auth/userpass3/mypass/\"") {
- t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
- }
-}
-
-func TestSystemBackend_remount(t *testing.T) {
- b := testSystemBackend(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "remount")
- req.Data["from"] = "secret"
- req.Data["to"] = "foo"
- req.Data["config"] = structs.Map(MountConfig{})
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- corehelpers.RetryUntil(t, 5*time.Second, func() error {
- req = logical.TestRequest(t, logical.ReadOperation, fmt.Sprintf("remount/status/%s", resp.Data["migration_id"]))
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- migrationInfo := resp.Data["migration_info"].(*MountMigrationInfo)
- if migrationInfo.MigrationStatus != MigrationSuccessStatus.String() {
- return fmt.Errorf("Expected migration status to be successful, got %q", migrationInfo.MigrationStatus)
- }
- return nil
- })
-}
-
-func TestSystemBackend_remount_destinationInUse(t *testing.T) {
- c, b, _ := testCoreSystemBackend(t)
-
- me := &MountEntry{
- Table: mountTableType,
- Path: "foo/",
- Type: "generic",
- }
- err := c.mount(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req := logical.TestRequest(t, logical.UpdateOperation, "remount")
- req.Data["from"] = "secret"
- req.Data["to"] = "foo"
- req.Data["config"] = structs.Map(MountConfig{})
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
-
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if !strings.Contains(resp.Data["error"].(string), "path already in use at \"foo/\"") {
- t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
- }
-
- req.Data["to"] = "foo/foo2"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
-
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if !strings.Contains(resp.Data["error"].(string), "path already in use at \"foo/\"") {
- t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
- }
-
- me2 := &MountEntry{
- Table: mountTableType,
- Path: "foo2/foo3/",
- Type: "generic",
- }
- err = c.mount(namespace.RootContext(nil), me2)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req.Data["to"] = "foo2/"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
-
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if !strings.Contains(resp.Data["error"].(string), "path already in use at \"foo2/foo3/\"") {
- t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
- }
-}
-
-func TestSystemBackend_remount_invalid(t *testing.T) {
- b := testSystemBackend(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "remount")
- req.Data["from"] = "unknown"
- req.Data["to"] = "foo"
- req.Data["config"] = structs.Map(MountConfig{})
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if !strings.Contains(resp.Data["error"].(string), "no matching mount at \"unknown/\"") {
- t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
- }
-}
-
-func TestSystemBackend_remount_system(t *testing.T) {
- b := testSystemBackend(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "remount")
- req.Data["from"] = "sys"
- req.Data["to"] = "foo"
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if !strings.Contains(resp.Data["error"].(string), "cannot remount \"sys/\"") {
- t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
- }
-}
-
-func TestSystemBackend_remount_clean(t *testing.T) {
- b := testSystemBackend(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "remount")
- req.Data["from"] = "foo"
- req.Data["to"] = "foo//bar"
- req.Data["config"] = structs.Map(MountConfig{})
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if resp.Data["error"] != `invalid destination mount: path 'foo//bar/' does not match cleaned path 'foo/bar/'` {
- t.Fatalf("bad: %v", resp)
- }
-}
-
-func TestSystemBackend_remount_nonPrintable(t *testing.T) {
- b := testSystemBackend(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "remount")
- req.Data["from"] = "foo"
- req.Data["to"] = "foo\nbar"
- req.Data["config"] = structs.Map(MountConfig{})
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if resp.Data["error"] != `invalid destination mount: path cannot contain non-printable characters` {
- t.Fatalf("bad: %v", resp)
- }
-}
-
-// TestSystemBackend_remount_trailingSpacesInFromPath ensures we error when
-// there are trailing spaces in the 'from' path during a remount.
-func TestSystemBackend_remount_trailingSpacesInFromPath(t *testing.T) {
- b := testSystemBackend(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "remount")
- req.Data["from"] = " foo/ "
- req.Data["to"] = "bar"
- req.Data["config"] = structs.Map(MountConfig{})
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if resp.Data["error"] != `'from' path cannot contain trailing whitespace` {
- t.Fatalf("bad: %v", resp)
- }
-}
-
-// TestSystemBackend_remount_trailingSpacesInToPath ensures we error when
-// there are trailing spaces in the 'to' path during a remount.
-func TestSystemBackend_remount_trailingSpacesInToPath(t *testing.T) {
- b := testSystemBackend(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "remount")
- req.Data["from"] = "foo"
- req.Data["to"] = " bar/ "
- req.Data["config"] = structs.Map(MountConfig{})
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if resp.Data["error"] != `'to' path cannot contain trailing whitespace` {
- t.Fatalf("bad: %v", resp)
- }
-}
-
-func TestSystemBackend_leases(t *testing.T) {
- core, b, root := testCoreSystemBackend(t)
-
- // Create a key with a lease
- req := logical.TestRequest(t, logical.UpdateOperation, "secret/foo")
- req.Data["foo"] = "bar"
- req.ClientToken = root
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read a key with a LeaseID
- req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
- req.ClientToken = root
- err = core.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read lease
- req = logical.TestRequest(t, logical.UpdateOperation, "leases/lookup")
- req.Data["lease_id"] = resp.Secret.LeaseID
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // validate the response structure for Update
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- if resp.Data["renewable"] == nil || resp.Data["renewable"].(bool) {
- t.Fatal("kv leases are not renewable")
- }
-
- // Invalid lease
- req = logical.TestRequest(t, logical.UpdateOperation, "leases/lookup")
- req.Data["lease_id"] = "invalid"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("expected invalid request, got err: %v", err)
- }
-}
-
-func TestSystemBackend_leases_list(t *testing.T) {
- core, b, root := testCoreSystemBackend(t)
-
- // Create a key with a lease
- req := logical.TestRequest(t, logical.UpdateOperation, "secret/foo")
- req.Data["foo"] = "bar"
- req.ClientToken = root
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read a key with a LeaseID
- req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
- req.ClientToken = root
- err = core.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // List top level
- req = logical.TestRequest(t, logical.ListOperation, "leases/lookup/")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // validate the response body for list
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
- var keys []string
- if err := mapstructure.WeakDecode(resp.Data["keys"], &keys); err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(keys) != 1 {
- t.Fatalf("Expected 1 subkey lease, got %d: %#v", len(keys), keys)
- }
- if keys[0] != "secret/" {
- t.Fatal("Expected only secret subkey")
- }
-
- // List lease
- req = logical.TestRequest(t, logical.ListOperation, "leases/lookup/secret/foo")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Data == nil {
- t.Fatalf("bad: %#v", resp)
- }
- keys = []string{}
- if err := mapstructure.WeakDecode(resp.Data["keys"], &keys); err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(keys) != 1 {
- t.Fatalf("Expected 1 secret lease, got %d: %#v", len(keys), keys)
- }
-
- // Generate multiple leases
- req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
- req.ClientToken = root
- err = core.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
- req.ClientToken = root
- err = core.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- req = logical.TestRequest(t, logical.ListOperation, "leases/lookup/secret/foo")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Data == nil {
- t.Fatalf("bad: %#v", resp)
- }
- keys = []string{}
- if err := mapstructure.WeakDecode(resp.Data["keys"], &keys); err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(keys) != 3 {
- t.Fatalf("Expected 3 secret lease, got %d: %#v", len(keys), keys)
- }
-
- // Listing subkeys
- req = logical.TestRequest(t, logical.UpdateOperation, "secret/bar")
- req.Data["foo"] = "bar"
- req.ClientToken = root
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read a key with a LeaseID
- req = logical.TestRequest(t, logical.ReadOperation, "secret/bar")
- req.ClientToken = root
- err = core.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- req = logical.TestRequest(t, logical.ListOperation, "leases/lookup/secret")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Data == nil {
- t.Fatalf("bad: %#v", resp)
- }
- keys = []string{}
- if err := mapstructure.WeakDecode(resp.Data["keys"], &keys); err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(keys) != 2 {
- t.Fatalf("Expected 2 secret lease, got %d: %#v", len(keys), keys)
- }
- expected := []string{"bar/", "foo/"}
- if !reflect.DeepEqual(expected, keys) {
- t.Fatalf("exp: %#v, act: %#v", expected, keys)
- }
-}
-
-func TestSystemBackend_renew(t *testing.T) {
- core, b, root := testCoreSystemBackend(t)
-
- // Create a key with a lease
- req := logical.TestRequest(t, logical.UpdateOperation, "secret/foo")
- req.Data["foo"] = "bar"
- req.ClientToken = root
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read a key with a LeaseID
- req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
- req.ClientToken = root
- err = core.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Attempt renew
- req2 := logical.TestRequest(t, logical.UpdateOperation, "leases/renew/"+resp.Secret.LeaseID)
- resp2, err := b.HandleRequest(namespace.RootContext(nil), req2)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
-
- // Validate lease renewal response structure
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req2.Path), req2.Operation),
- resp,
- true,
- )
-
- // Should get error about non-renewability
- if resp2.Data["error"] != "lease is not renewable" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Add a TTL to the lease
- req = logical.TestRequest(t, logical.UpdateOperation, "secret/foo")
- req.Data["foo"] = "bar"
- req.Data["ttl"] = "180s"
- req.ClientToken = root
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read a key with a LeaseID
- req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
- req.ClientToken = root
- err = core.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Attempt renew
- req2 = logical.TestRequest(t, logical.UpdateOperation, "leases/renew/"+resp.Secret.LeaseID)
- resp2, err = b.HandleRequest(namespace.RootContext(nil), req2)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp2.IsError() {
- t.Fatalf("got an error")
- }
- if resp2.Data == nil {
- t.Fatal("nil data")
- }
- if resp.Secret.TTL != 180*time.Second {
- t.Fatalf("bad lease duration: %v", resp.Secret.TTL)
- }
-
- // Test the other route path
- req2 = logical.TestRequest(t, logical.UpdateOperation, "leases/renew")
- req2.Data["lease_id"] = resp.Secret.LeaseID
- resp2, err = b.HandleRequest(namespace.RootContext(nil), req2)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp2.IsError() {
- t.Fatalf("got an error")
- }
- if resp2.Data == nil {
- t.Fatal("nil data")
- }
- if resp.Secret.TTL != 180*time.Second {
- t.Fatalf("bad lease duration: %v", resp.Secret.TTL)
- }
-
- // Test orig path
- req2 = logical.TestRequest(t, logical.UpdateOperation, "renew")
- req2.Data["lease_id"] = resp.Secret.LeaseID
- resp2, err = b.HandleRequest(namespace.RootContext(nil), req2)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp2.IsError() {
- t.Fatalf("got an error")
- }
- if resp2.Data == nil {
- t.Fatal("nil data")
- }
- if resp.Secret.TTL != time.Second*180 {
- t.Fatalf("bad lease duration: %v", resp.Secret.TTL)
- }
-}
-
-func TestSystemBackend_renew_invalidID(t *testing.T) {
- b := testSystemBackend(t)
-
- // Attempt renew
- req := logical.TestRequest(t, logical.UpdateOperation, "leases/renew/foobarbaz")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if resp.Data["error"] != "lease not found" {
- t.Fatalf("bad: %v", resp)
- }
-
- // Attempt renew with other method
- req = logical.TestRequest(t, logical.UpdateOperation, "leases/renew")
- req.Data["lease_id"] = "foobarbaz"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if resp.Data["error"] != "lease not found" {
- t.Fatalf("bad: %v", resp)
- }
-}
-
-func TestSystemBackend_renew_invalidID_origUrl(t *testing.T) {
- b := testSystemBackend(t)
-
- // Attempt renew
- req := logical.TestRequest(t, logical.UpdateOperation, "renew/foobarbaz")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if resp.Data["error"] != "lease not found" {
- t.Fatalf("bad: %v", resp)
- }
-
- // Attempt renew with other method
- req = logical.TestRequest(t, logical.UpdateOperation, "renew")
- req.Data["lease_id"] = "foobarbaz"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if resp.Data["error"] != "lease not found" {
- t.Fatalf("bad: %v", resp)
- }
-}
-
-func TestSystemBackend_revoke(t *testing.T) {
- core, b, root := testCoreSystemBackend(t)
-
- // Create a key with a lease
- req := logical.TestRequest(t, logical.UpdateOperation, "secret/foo")
- req.Data["foo"] = "bar"
- req.Data["lease"] = "1h"
- req.ClientToken = root
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read a key with a LeaseID
- req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
- req.ClientToken = root
- err = core.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Attempt revoke
- req2 := logical.TestRequest(t, logical.UpdateOperation, "revoke/"+resp.Secret.LeaseID)
- resp2, err := b.HandleRequest(namespace.RootContext(nil), req2)
- if err != nil {
- t.Fatalf("err: %v %#v", err, resp2)
- }
- if resp2 != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Attempt renew
- req3 := logical.TestRequest(t, logical.UpdateOperation, "renew/"+resp.Secret.LeaseID)
- resp3, err := b.HandleRequest(namespace.RootContext(nil), req3)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if resp3.Data["error"] != "lease not found" {
- t.Fatalf("bad: %v", *resp3)
- }
-
- // Read a key with a LeaseID
- req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
- req.ClientToken = root
- err = core.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Test the other route path
- req2 = logical.TestRequest(t, logical.UpdateOperation, "revoke")
- req2.Data["lease_id"] = resp.Secret.LeaseID
- resp2, err = b.HandleRequest(namespace.RootContext(nil), req2)
- if err != nil {
- t.Fatalf("err: %v %#v", err, resp2)
- }
- if resp2 != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read a key with a LeaseID
- req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
- req.ClientToken = root
- err = core.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Test the other route path
- req2 = logical.TestRequest(t, logical.UpdateOperation, "leases/revoke")
- req2.Data["lease_id"] = resp.Secret.LeaseID
- resp2, err = b.HandleRequest(namespace.RootContext(nil), req2)
- if err != nil {
- t.Fatalf("err: %v %#v", err, resp2)
- }
- if resp2 != nil {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestSystemBackend_revoke_invalidID(t *testing.T) {
- b := testSystemBackend(t)
-
- // Attempt revoke
- req := logical.TestRequest(t, logical.UpdateOperation, "leases/revoke/foobarbaz")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- // Attempt revoke with other method
- req = logical.TestRequest(t, logical.UpdateOperation, "leases/revoke")
- req.Data["lease_id"] = "foobarbaz"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // validate the response structure for lease revoke
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-}
-
-func TestSystemBackend_revoke_invalidID_origUrl(t *testing.T) {
- b := testSystemBackend(t)
-
- // Attempt revoke
- req := logical.TestRequest(t, logical.UpdateOperation, "revoke/foobarbaz")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- // Attempt revoke with other method
- req = logical.TestRequest(t, logical.UpdateOperation, "revoke")
- req.Data["lease_id"] = "foobarbaz"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-}
-
-func TestSystemBackend_revokePrefix(t *testing.T) {
- core, b, root := testCoreSystemBackend(t)
-
- // Create a key with a lease
- req := logical.TestRequest(t, logical.UpdateOperation, "secret/foo")
- req.Data["foo"] = "bar"
- req.Data["lease"] = "1h"
- req.ClientToken = root
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read a key with a LeaseID
- req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
- req.ClientToken = root
- err = core.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Attempt revoke
- req2 := logical.TestRequest(t, logical.UpdateOperation, "leases/revoke-prefix/secret/")
- resp2, err := b.HandleRequest(namespace.RootContext(nil), req2)
- if err != nil {
- t.Fatalf("err: %v %#v", err, resp2)
- }
-
- // validate the response structure for lease revoke-prefix
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req2.Path), req2.Operation),
- resp,
- true,
- )
-
- // Attempt renew
- req3 := logical.TestRequest(t, logical.UpdateOperation, "leases/renew/"+resp.Secret.LeaseID)
- resp3, err := b.HandleRequest(namespace.RootContext(nil), req3)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if resp3.Data["error"] != "lease not found" {
- t.Fatalf("bad: %v", *resp3)
- }
-}
-
-func TestSystemBackend_revokePrefix_origUrl(t *testing.T) {
- core, b, root := testCoreSystemBackend(t)
-
- // Create a key with a lease
- req := logical.TestRequest(t, logical.UpdateOperation, "secret/foo")
- req.Data["foo"] = "bar"
- req.Data["lease"] = "1h"
- req.ClientToken = root
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read a key with a LeaseID
- req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
- req.ClientToken = root
- err = core.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Attempt revoke
- req2 := logical.TestRequest(t, logical.UpdateOperation, "revoke-prefix/secret/")
- resp2, err := b.HandleRequest(namespace.RootContext(nil), req2)
- if err != nil {
- t.Fatalf("err: %v %#v", err, resp2)
- }
- if resp2 != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Attempt renew
- req3 := logical.TestRequest(t, logical.UpdateOperation, "renew/"+resp.Secret.LeaseID)
- resp3, err := b.HandleRequest(namespace.RootContext(nil), req3)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if resp3.Data["error"] != "lease not found" {
- t.Fatalf("bad: %#v", *resp3)
- }
-}
-
-func TestSystemBackend_revokePrefixAuth_newUrl(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
-
- ts := core.tokenStore
- bc := &logical.BackendConfig{
- Logger: core.logger,
- System: logical.StaticSystemView{
- DefaultLeaseTTLVal: time.Hour * 24,
- MaxLeaseTTLVal: time.Hour * 24 * 32,
- },
- }
- b := NewSystemBackend(core, hclog.New(&hclog.LoggerOptions{}))
- err := b.Backend.Setup(namespace.RootContext(nil), bc)
- if err != nil {
- t.Fatal(err)
- }
-
- exp := ts.expiration
-
- te := &logical.TokenEntry{
- ID: "foo",
- Path: "auth/github/login/bar",
- TTL: time.Hour,
- NamespaceID: namespace.RootNamespaceID,
- }
- testMakeTokenDirectly(t, ts, te)
-
- te, err = ts.Lookup(namespace.RootContext(nil), "foo")
- if err != nil {
- t.Fatal(err)
- }
- if te == nil {
- t.Fatal("token entry was nil")
- }
-
- // Create a new token
- auth := &logical.Auth{
- ClientToken: te.ID,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- }
- err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req := logical.TestRequest(t, logical.UpdateOperation, "leases/revoke-prefix/auth/github/")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v %v", err, resp)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- te, err = ts.Lookup(namespace.RootContext(nil), te.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if te != nil {
- t.Fatalf("bad: %v", te)
- }
-}
-
-func TestSystemBackend_revokePrefixAuth_origUrl(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- ts := core.tokenStore
- bc := &logical.BackendConfig{
- Logger: core.logger,
- System: logical.StaticSystemView{
- DefaultLeaseTTLVal: time.Hour * 24,
- MaxLeaseTTLVal: time.Hour * 24 * 32,
- },
- }
- b := NewSystemBackend(core, hclog.New(&hclog.LoggerOptions{}))
- err := b.Backend.Setup(namespace.RootContext(nil), bc)
- if err != nil {
- t.Fatal(err)
- }
-
- exp := ts.expiration
-
- te := &logical.TokenEntry{
- ID: "foo",
- Path: "auth/github/login/bar",
- TTL: time.Hour,
- NamespaceID: namespace.RootNamespaceID,
- }
- testMakeTokenDirectly(t, ts, te)
-
- te, err = ts.Lookup(namespace.RootContext(nil), "foo")
- if err != nil {
- t.Fatal(err)
- }
- if te == nil {
- t.Fatal("token entry was nil")
- }
-
- // Create a new token
- auth := &logical.Auth{
- ClientToken: te.ID,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- }
- err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req := logical.TestRequest(t, logical.UpdateOperation, "revoke-prefix/auth/github/")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v %v", err, resp)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- te, err = ts.Lookup(namespace.RootContext(nil), te.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if te != nil {
- t.Fatalf("bad: %v", te)
- }
-}
-
-func TestSystemBackend_authTable(t *testing.T) {
- b := testSystemBackend(t)
- req := logical.TestRequest(t, logical.ReadOperation, "auth")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- exp := map[string]interface{}{
- "token/": map[string]interface{}{
- "type": "token",
- "external_entropy_access": false,
- "description": "token based credentials",
- "accessor": resp.Data["token/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["token/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": int64(0),
- "max_lease_ttl": int64(0),
- "force_no_cache": false,
- "token_type": "default-service",
- },
- "local": false,
- "seal_wrap": false,
- "options": map[string]string(nil),
- "plugin_version": "",
- "running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeCredential, "token"),
- "running_sha256": "",
- },
- }
- if diff := deep.Equal(resp.Data, exp); diff != nil {
- t.Fatal(diff)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "auth/token")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- if diff := deep.Equal(resp.Data, exp["token/"]); diff != nil {
- t.Fatal(diff)
- }
-}
-
-func TestSystemBackend_enableAuth(t *testing.T) {
- c, b, _ := testCoreSystemBackend(t)
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{BackendType: logical.TypeCredential}, nil
- }
-
- req := logical.TestRequest(t, logical.UpdateOperation, "auth/foo")
- req.Data["type"] = "noop"
- req.Data["config"] = map[string]interface{}{
- "default_lease_ttl": "35m",
- "max_lease_ttl": "45m",
- }
- req.Data["local"] = true
- req.Data["seal_wrap"] = true
-
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- req = logical.TestRequest(t, logical.ReadOperation, "auth")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatal("resp is nil")
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- exp := map[string]interface{}{
- "foo/": map[string]interface{}{
- "type": "noop",
- "external_entropy_access": false,
- "description": "",
- "accessor": resp.Data["foo/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["foo/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": int64(2100),
- "max_lease_ttl": int64(2700),
- "force_no_cache": false,
- "token_type": "default-service",
- },
- "local": true,
- "seal_wrap": true,
- "options": map[string]string{},
- "plugin_version": "",
- "running_plugin_version": versions.DefaultBuiltinVersion,
- "running_sha256": "",
- },
- "token/": map[string]interface{}{
- "type": "token",
- "external_entropy_access": false,
- "description": "token based credentials",
- "accessor": resp.Data["token/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["token/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": int64(0),
- "max_lease_ttl": int64(0),
- "force_no_cache": false,
- "token_type": "default-service",
- },
- "local": false,
- "seal_wrap": false,
- "options": map[string]string(nil),
- "plugin_version": "",
- "running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeCredential, "token"),
- "running_sha256": "",
- },
- }
- if diff := deep.Equal(resp.Data, exp); diff != nil {
- t.Fatal(diff)
- }
-}
-
-func TestSystemBackend_enableAuth_invalid(t *testing.T) {
- b := testSystemBackend(t)
- req := logical.TestRequest(t, logical.UpdateOperation, "auth/foo")
- req.Data["type"] = "nope"
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if resp.Data["error"] != `plugin not found in the catalog: nope` {
- t.Fatalf("bad: %v", resp)
- }
-}
-
-func TestSystemBackend_disableAuth(t *testing.T) {
- c, b, _ := testCoreSystemBackend(t)
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{}, nil
- }
-
- // Register the backend
- req := logical.TestRequest(t, logical.UpdateOperation, "auth/foo")
- req.Data["type"] = "noop"
- b.HandleRequest(namespace.RootContext(nil), req)
-
- // Deregister it
- req = logical.TestRequest(t, logical.DeleteOperation, "auth/foo")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-}
-
-func TestSystemBackend_tuneAuth(t *testing.T) {
- c, b, _ := testCoreSystemBackend(t)
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{BackendType: logical.TypeCredential}, nil
- }
-
- req := logical.TestRequest(t, logical.ReadOperation, "auth/token/tune")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatal("resp is nil")
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- exp := map[string]interface{}{
- "description": "token based credentials",
- "default_lease_ttl": int(2764800),
- "max_lease_ttl": int(2764800),
- "force_no_cache": false,
- "token_type": "default-service",
- }
-
- if diff := deep.Equal(resp.Data, exp); diff != nil {
- t.Fatal(diff)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "auth/token/tune")
- req.Data["description"] = ""
- req.Data["plugin_version"] = "v1.0.0"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || resp == nil || !resp.IsError() || !strings.Contains(resp.Error().Error(), ErrPluginNotFound.Error()) {
- t.Fatalf("expected tune request to fail, but got resp: %#v, err: %s", resp, err)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- // Register the plugin in the catalog, and then try the same request again.
- {
- tempDir, err := filepath.EvalSymlinks(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- c.pluginCatalog.directory = tempDir
- file, err := os.Create(filepath.Join(tempDir, "foo"))
- if err != nil {
- t.Fatal(err)
- }
- if err := file.Close(); err != nil {
- t.Fatal(err)
- }
- err = c.pluginCatalog.Set(context.Background(), "token", consts.PluginTypeCredential, "v1.0.0", "foo", []string{}, []string{}, []byte{})
- if err != nil {
- t.Fatal(err)
- }
- }
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(resp, err)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "auth/token/tune")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatal("resp is nil")
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- if resp.Data["description"] != "" {
- t.Fatalf("got: %#v expect: %#v", resp.Data["description"], "")
- }
- if resp.Data["plugin_version"] != "v1.0.0" {
- t.Fatalf("got: %#v, expected: %v", resp.Data["version"], "v1.0.0")
- }
-}
-
-func TestSystemBackend_policyList(t *testing.T) {
- b := testSystemBackend(t)
- req := logical.TestRequest(t, logical.ReadOperation, "policy")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // validate the response structure for policy read
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- exp := map[string]interface{}{
- "keys": []string{"default", "root"},
- "policies": []string{"default", "root"},
- }
- if !reflect.DeepEqual(resp.Data, exp) {
- t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
- }
-}
-
-func TestSystemBackend_policyCRUD(t *testing.T) {
- b := testSystemBackend(t)
-
- // Create the policy
- rules := `path "foo/" { policy = "read" }`
- req := logical.TestRequest(t, logical.UpdateOperation, "policy/Foo")
- req.Data["rules"] = rules
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v %#v", err, resp)
- }
- if resp != nil && (resp.IsError() || len(resp.Data) > 0) {
- t.Fatalf("bad: %#v", resp)
- }
-
- // validate the response structure for policy named Update
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- // Read the policy
- req = logical.TestRequest(t, logical.ReadOperation, "policy/foo")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // validate the response structure for policy named read
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- exp := map[string]interface{}{
- "name": "foo",
- "rules": rules,
- }
- if !reflect.DeepEqual(resp.Data, exp) {
- t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
- }
-
- // Read, and make sure that case has been normalized
- req = logical.TestRequest(t, logical.ReadOperation, "policy/Foo")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- exp = map[string]interface{}{
- "name": "foo",
- "rules": rules,
- }
- if !reflect.DeepEqual(resp.Data, exp) {
- t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
- }
-
- // List the policies
- req = logical.TestRequest(t, logical.ReadOperation, "policy")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- exp = map[string]interface{}{
- "keys": []string{"default", "foo", "root"},
- "policies": []string{"default", "foo", "root"},
- }
- if !reflect.DeepEqual(resp.Data, exp) {
- t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
- }
-
- // Delete the policy
- req = logical.TestRequest(t, logical.DeleteOperation, "policy/foo")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // validate the response structure for policy named delete
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- // Read the policy (deleted)
- req = logical.TestRequest(t, logical.ReadOperation, "policy/foo")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // List the policies (deleted)
- req = logical.TestRequest(t, logical.ReadOperation, "policy")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- exp = map[string]interface{}{
- "keys": []string{"default", "root"},
- "policies": []string{"default", "root"},
- }
- if !reflect.DeepEqual(resp.Data, exp) {
- t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
- }
-}
-
-func TestSystemBackend_enableAudit(t *testing.T) {
- c, b, _ := testCoreSystemBackend(t)
- c.auditBackends["noop"] = corehelpers.NoopAuditFactory(nil)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "audit/foo")
- req.Data["type"] = "noop"
-
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-}
-
-// TestSystemBackend_decodeToken ensures the correct decoding of the encoded token.
-// It also ensures that the API fails if there is some payload missing.
-func TestSystemBackend_decodeToken(t *testing.T) {
- encodedToken := "Bxg9JQQqOCNKBRICNwMIRzo2J3cWCBRi"
- otp := "3JhHkONiyiaNYj14nnD9xZQS"
- tokenExpected := "4RUmoevJ3lsLni9sTXcNnRE1"
-
- _, b, _ := testCoreSystemBackend(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "decode-token")
- req.Data["encoded_token"] = encodedToken
- req.Data["otp"] = otp
-
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- token, ok := resp.Data["token"]
- if !ok {
- t.Fatalf("did not get token back in response, response was %#v", resp.Data)
- }
-
- if token.(string) != tokenExpected {
- t.Fatalf("bad token back: %s", token.(string))
- }
-
- datas := []map[string]interface{}{
- nil,
- {"encoded_token": encodedToken},
- {"otp": otp},
- }
- for _, data := range datas {
- req.Data = data
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatalf("no error despite missing payload")
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
- }
-}
-
-func TestSystemBackend_auditHash(t *testing.T) {
- c, b, _ := testCoreSystemBackend(t)
- c.auditBackends["noop"] = corehelpers.NoopAuditFactory(nil)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "audit/foo")
- req.Data["type"] = "noop"
-
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- req = logical.TestRequest(t, logical.UpdateOperation, "audit-hash/foo")
- req.Data["input"] = "bar"
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Data == nil {
- t.Fatalf("response or its data was nil")
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- hash, ok := resp.Data["hash"]
- if !ok {
- t.Fatalf("did not get hash back in response, response was %#v", resp.Data)
- }
- if hash.(string) != "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317" {
- t.Fatalf("bad hash back: %s", hash.(string))
- }
-}
-
-func TestSystemBackend_enableAudit_invalid(t *testing.T) {
- b := testSystemBackend(t)
- req := logical.TestRequest(t, logical.UpdateOperation, "audit/foo")
- req.Data["type"] = "nope"
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
- if resp.Data["error"] != `unknown backend type: "nope"` {
- t.Fatalf("bad: %v", resp)
- }
-}
-
-func TestSystemBackend_auditTable(t *testing.T) {
- c, b, _ := testCoreSystemBackend(t)
- c.auditBackends["noop"] = corehelpers.NoopAuditFactory(nil)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "audit/foo")
- req.Data["type"] = "noop"
- req.Data["description"] = "testing"
- req.Data["options"] = map[string]interface{}{
- "foo": "bar",
- }
- req.Data["local"] = true
- b.HandleRequest(namespace.RootContext(nil), req)
-
- req = logical.TestRequest(t, logical.ReadOperation, "audit")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- exp := map[string]interface{}{
- "foo/": map[string]interface{}{
- "path": "foo/",
- "type": "noop",
- "description": "testing",
- "options": map[string]string{
- "foo": "bar",
- },
- "local": true,
- },
- }
- if !reflect.DeepEqual(resp.Data, exp) {
- t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
- }
-}
-
-func TestSystemBackend_disableAudit(t *testing.T) {
- c, b, _ := testCoreSystemBackend(t)
- c.auditBackends["noop"] = corehelpers.NoopAuditFactory(nil)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "audit/foo")
- req.Data["type"] = "noop"
- req.Data["description"] = "testing"
- req.Data["options"] = map[string]interface{}{
- "foo": "bar",
- }
- b.HandleRequest(namespace.RootContext(nil), req)
-
- // Deregister it
- req = logical.TestRequest(t, logical.DeleteOperation, "audit/foo")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-}
-
-func TestSystemBackend_rawRead_Compressed(t *testing.T) {
- t.Run("basic", func(t *testing.T) {
- b := testSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.ReadOperation, "raw/core/mounts")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- if !strings.HasPrefix(resp.Data["value"].(string), `{"type":"mounts"`) {
- t.Fatalf("bad: %v", resp)
- }
- })
-
- t.Run("base64", func(t *testing.T) {
- b := testSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.ReadOperation, "raw/core/mounts")
- req.Data = map[string]interface{}{
- "encoding": "base64",
- }
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if _, ok := resp.Data["value"].([]byte); !ok {
- t.Fatalf("value is a not an array of bytes, it is %T", resp.Data["value"])
- }
-
- if !strings.HasPrefix(string(resp.Data["value"].([]byte)), `{"type":"mounts"`) {
- t.Fatalf("bad: %v", resp)
- }
- })
-
- t.Run("invalid_encoding", func(t *testing.T) {
- b := testSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.ReadOperation, "raw/core/mounts")
- req.Data = map[string]interface{}{
- "encoding": "invalid_encoding",
- }
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
-
- if !resp.IsError() {
- t.Fatalf("bad: %v", resp)
- }
- })
-
- t.Run("compressed_false", func(t *testing.T) {
- b := testSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.ReadOperation, "raw/core/mounts")
- req.Data = map[string]interface{}{
- "compressed": false,
- }
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if _, ok := resp.Data["value"].(string); !ok {
- t.Fatalf("value is a not a string, it is %T", resp.Data["value"])
- }
-
- if !strings.HasPrefix(string(resp.Data["value"].(string)), string(compressutil.CompressionCanaryGzip)) {
- t.Fatalf("bad: %v", resp)
- }
- })
-
- t.Run("compressed_false_base64", func(t *testing.T) {
- b := testSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.ReadOperation, "raw/core/mounts")
- req.Data = map[string]interface{}{
- "compressed": false,
- "encoding": "base64",
- }
-
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if _, ok := resp.Data["value"].([]byte); !ok {
- t.Fatalf("value is a not an array of bytes, it is %T", resp.Data["value"])
- }
-
- if !strings.HasPrefix(string(resp.Data["value"].([]byte)), string(compressutil.CompressionCanaryGzip)) {
- t.Fatalf("bad: %v", resp)
- }
- })
-
- t.Run("uncompressed_entry_with_prefix_byte", func(t *testing.T) {
- b := testSystemBackendRaw(t)
- req := logical.TestRequest(t, logical.CreateOperation, "raw/test_raw")
- req.Data = map[string]interface{}{
- "value": "414c1e7f-0a9a-49e0-9fc4-61af329d0724",
- }
-
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- req = logical.TestRequest(t, logical.ReadOperation, "raw/test_raw")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatalf("expected error if trying to read uncompressed entry with prefix byte")
- }
- if !resp.IsError() {
- t.Fatalf("bad: %v", resp)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "raw/test_raw")
- req.Data = map[string]interface{}{
- "compressed": false,
- }
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp.IsError() {
- t.Fatalf("bad: %v", resp)
- }
- if resp.Data["value"].(string) != "414c1e7f-0a9a-49e0-9fc4-61af329d0724" {
- t.Fatalf("bad: %v", resp)
- }
- })
-}
-
-func TestSystemBackend_rawRead_Protected(t *testing.T) {
- b := testSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.ReadOperation, "raw/"+keyringPath)
- _, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestSystemBackend_rawWrite_Protected(t *testing.T) {
- b := testSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "raw/"+keyringPath)
- _, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestSystemBackend_rawReadWrite(t *testing.T) {
- _, b, _ := testCoreSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.CreateOperation, "raw/sys/policy/test")
- req.Data["value"] = `path "secret/" { policy = "read" }`
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- // Read via raw API
- req = logical.TestRequest(t, logical.ReadOperation, "raw/sys/policy/test")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !strings.HasPrefix(resp.Data["value"].(string), "path") {
- t.Fatalf("bad: %v", resp)
- }
-
- // Note: since the upgrade code is gone that upgraded from 0.1, we can't
- // simply parse this out directly via GetPolicy, so the test now ends here.
-}
-
-func TestSystemBackend_rawWrite_ExistanceCheck(t *testing.T) {
- b := testSystemBackendRaw(t)
- req := logical.TestRequest(t, logical.CreateOperation, "raw/core/mounts")
- _, exist, err := b.HandleExistenceCheck(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: #{err}")
- }
- if !exist {
- t.Fatalf("raw existence check failed for actual key")
- }
-
- req = logical.TestRequest(t, logical.CreateOperation, "raw/non_existent")
- _, exist, err = b.HandleExistenceCheck(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: #{err}")
- }
- if exist {
- t.Fatalf("raw existence check failed for non-existent key")
- }
-}
-
-func TestSystemBackend_rawReadWrite_base64(t *testing.T) {
- t.Run("basic", func(t *testing.T) {
- _, b, _ := testCoreSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.CreateOperation, "raw/sys/policy/test")
- req.Data = map[string]interface{}{
- "value": base64.StdEncoding.EncodeToString([]byte(`path "secret/" { policy = "read"[ }`)),
- "encoding": "base64",
- }
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- // Read via raw API
- req = logical.TestRequest(t, logical.ReadOperation, "raw/sys/policy/test")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !strings.HasPrefix(resp.Data["value"].(string), "path") {
- t.Fatalf("bad: %v", resp)
- }
- })
-
- t.Run("invalid_value", func(t *testing.T) {
- _, b, _ := testCoreSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.CreateOperation, "raw/sys/policy/test")
- req.Data = map[string]interface{}{
- "value": "invalid base64",
- "encoding": "base64",
- }
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatalf("no error")
- }
-
- if err != logical.ErrInvalidRequest {
- t.Fatalf("unexpected error: %v", err)
- }
-
- if !resp.IsError() {
- t.Fatalf("response is not error: %v", resp)
- }
- })
-
- t.Run("invalid_encoding", func(t *testing.T) {
- _, b, _ := testCoreSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.CreateOperation, "raw/sys/policy/test")
- req.Data = map[string]interface{}{
- "value": "text",
- "encoding": "invalid_encoding",
- }
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatalf("no error")
- }
-
- if err != logical.ErrInvalidRequest {
- t.Fatalf("unexpected error: %v", err)
- }
-
- if !resp.IsError() {
- t.Fatalf("response is not error: %v", resp)
- }
- })
-}
-
-func TestSystemBackend_rawReadWrite_Compressed(t *testing.T) {
- t.Run("use_existing_compression", func(t *testing.T) {
- b := testSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.ReadOperation, "raw/core/mounts")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- mounts := resp.Data["value"].(string)
- req = logical.TestRequest(t, logical.UpdateOperation, "raw/core/mounts")
- req.Data = map[string]interface{}{
- "value": mounts,
- "compression_type": compressutil.CompressionTypeGzip,
- }
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- // Read back and check gzip was applied by looking for prefix byte
- req = logical.TestRequest(t, logical.ReadOperation, "raw/core/mounts")
- req.Data = map[string]interface{}{
- "compressed": false,
- "encoding": "base64",
- }
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if _, ok := resp.Data["value"].([]byte); !ok {
- t.Fatalf("value is a not an array of bytes, it is %T", resp.Data["value"])
- }
-
- if !strings.HasPrefix(string(resp.Data["value"].([]byte)), string(compressutil.CompressionCanaryGzip)) {
- t.Fatalf("bad: %v", resp)
- }
- })
-
- t.Run("compression_type_matches_existing_compression", func(t *testing.T) {
- b := testSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.ReadOperation, "raw/core/mounts")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- mounts := resp.Data["value"].(string)
- req = logical.TestRequest(t, logical.UpdateOperation, "raw/core/mounts")
- req.Data = map[string]interface{}{
- "value": mounts,
- }
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Read back and check gzip was applied by looking for prefix byte
- req = logical.TestRequest(t, logical.ReadOperation, "raw/core/mounts")
- req.Data = map[string]interface{}{
- "compressed": false,
- "encoding": "base64",
- }
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if _, ok := resp.Data["value"].([]byte); !ok {
- t.Fatalf("value is a not an array of bytes, it is %T", resp.Data["value"])
- }
-
- if !strings.HasPrefix(string(resp.Data["value"].([]byte)), string(compressutil.CompressionCanaryGzip)) {
- t.Fatalf("bad: %v", resp)
- }
- })
-
- t.Run("write_uncompressed_over_existing_compressed", func(t *testing.T) {
- b := testSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.ReadOperation, "raw/core/mounts")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- mounts := resp.Data["value"].(string)
- req = logical.TestRequest(t, logical.UpdateOperation, "raw/core/mounts")
- req.Data = map[string]interface{}{
- "value": mounts,
- "compression_type": "",
- }
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Read back and check gzip was not applied by looking for prefix byte
- req = logical.TestRequest(t, logical.ReadOperation, "raw/core/mounts")
- req.Data = map[string]interface{}{
- "compressed": false,
- "encoding": "base64",
- }
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if _, ok := resp.Data["value"].([]byte); !ok {
- t.Fatalf("value is a not an array of bytes, it is %T", resp.Data["value"])
- }
-
- if !strings.HasPrefix(string(resp.Data["value"].([]byte)), `{"type":"mounts"`) {
- t.Fatalf("bad: %v", resp)
- }
- })
-
- t.Run("invalid_compression_type", func(t *testing.T) {
- b := testSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.ReadOperation, "raw/core/mounts")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- mounts := resp.Data["value"].(string)
- req = logical.TestRequest(t, logical.UpdateOperation, "raw/core/mounts")
- req.Data = map[string]interface{}{
- "value": mounts,
- "compression_type": "invalid_type",
- }
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("unexpected error: %v", err)
- }
-
- if !resp.IsError() {
- t.Fatalf("response is not error: %v", resp)
- }
- })
-
- t.Run("update_non_existent_entry", func(t *testing.T) {
- b := testSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "raw/non_existent")
- req.Data = map[string]interface{}{
- "value": "{}",
- }
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("unexpected error: %v", err)
- }
-
- if !resp.IsError() {
- t.Fatalf("response is not error: %v", resp)
- }
- })
-
- t.Run("invalid_compression_over_existing_uncompressed_data", func(t *testing.T) {
- b := testSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.CreateOperation, "raw/test")
- req.Data = map[string]interface{}{
- "value": "{}",
- }
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
-
- if resp.IsError() {
- t.Fatalf("response is an error: %v", resp)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "raw/test")
- req.Data = map[string]interface{}{
- "value": "{}",
- "compression_type": compressutil.CompressionTypeGzip,
- }
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("unexpected error: %v", err)
- }
-
- if !resp.IsError() {
- t.Fatalf("response is not error: %v", resp)
- }
- })
-
- t.Run("wrong_compression_type_over_existing_compressed_data", func(t *testing.T) {
- b := testSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.CreateOperation, "raw/test")
- req.Data = map[string]interface{}{
- "value": "{}",
- "compression_type": compressutil.CompressionTypeGzip,
- }
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
-
- if resp.IsError() {
- t.Fatalf("response is an error: %v", resp)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "raw/test")
- req.Data = map[string]interface{}{
- "value": "{}",
- "compression_type": compressutil.CompressionTypeSnappy,
- }
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("unexpected error: %v", err)
- }
-
- if !resp.IsError() {
- t.Fatalf("response is not error: %v", resp)
- }
- })
-}
-
-func TestSystemBackend_rawDelete_Protected(t *testing.T) {
- b := testSystemBackendRaw(t)
-
- req := logical.TestRequest(t, logical.DeleteOperation, "raw/"+keyringPath)
- _, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestSystemBackend_rawDelete(t *testing.T) {
- c, b, _ := testCoreSystemBackendRaw(t)
-
- // set the policy!
- p := &Policy{
- Name: "test",
- Type: PolicyTypeACL,
- namespace: namespace.RootNamespace,
- }
- err := c.policyStore.SetPolicy(namespace.RootContext(nil), p)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Delete the policy
- req := logical.TestRequest(t, logical.DeleteOperation, "raw/sys/policy/test")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- // Policy should be gone
- c.policyStore.tokenPoliciesLRU.Purge()
- out, err := c.policyStore.GetPolicy(namespace.RootContext(nil), "test", PolicyTypeToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("policy should be gone")
- }
-}
-
-func TestSystemBackend_keyStatus(t *testing.T) {
- b := testSystemBackend(t)
- req := logical.TestRequest(t, logical.ReadOperation, "key-status")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- exp := map[string]interface{}{
- "term": 1,
- }
- delete(resp.Data, "install_time")
- delete(resp.Data, "encryptions")
- if !reflect.DeepEqual(resp.Data, exp) {
- t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
- }
-}
-
-func TestSystemBackend_rotateConfig(t *testing.T) {
- b := testSystemBackend(t)
- req := logical.TestRequest(t, logical.ReadOperation, "rotate/config")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- exp := map[string]interface{}{
- "max_operations": absoluteOperationMaximum,
- "interval": 0,
- "enabled": true,
- }
- if !reflect.DeepEqual(resp.Data, exp) {
- t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
- }
-
- req2 := logical.TestRequest(t, logical.UpdateOperation, "rotate/config")
- req2.Data["max_operations"] = int64(3221225472)
- req2.Data["interval"] = "5432h0m0s"
- req2.Data["enabled"] = false
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req2)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req2.Path), req2.Operation),
- resp,
- true,
- )
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), resp,
- true,
- )
-
- exp = map[string]interface{}{
- "max_operations": int64(3221225472),
- "interval": "5432h0m0s",
- "enabled": false,
- }
-
- if !reflect.DeepEqual(resp.Data, exp) {
- t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
- }
-}
-
-func TestSystemBackend_rotate(t *testing.T) {
- b := testSystemBackend(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "rotate")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "key-status")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- exp := map[string]interface{}{
- "term": 2,
- }
- delete(resp.Data, "install_time")
- delete(resp.Data, "encryptions")
- if !reflect.DeepEqual(resp.Data, exp) {
- t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
- }
-}
-
-func testSystemBackend(t *testing.T) logical.Backend {
- t.Helper()
- c, _, _ := TestCoreUnsealed(t)
- return c.systemBackend
-}
-
-func testSystemBackendRaw(t *testing.T) logical.Backend {
- t.Helper()
- c, _, _ := TestCoreUnsealedRaw(t)
- return c.systemBackend
-}
-
-func testCoreSystemBackend(t *testing.T) (*Core, logical.Backend, string) {
- t.Helper()
- c, _, root := TestCoreUnsealed(t)
- return c, c.systemBackend, root
-}
-
-func testCoreSystemBackendRaw(t *testing.T) (*Core, logical.Backend, string) {
- t.Helper()
- c, _, root := TestCoreUnsealedRaw(t)
- return c, c.systemBackend, root
-}
-
-func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) {
- c, b, _ := testCoreSystemBackend(t)
- // Bootstrap the pluginCatalog
- sym, err := filepath.EvalSymlinks(os.TempDir())
- if err != nil {
- t.Fatalf("error: %v", err)
- }
- c.pluginCatalog.directory = sym
-
- req := logical.TestRequest(t, logical.ListOperation, "plugins/catalog/database")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- if len(resp.Data["keys"].([]string)) != len(c.builtinRegistry.Keys(consts.PluginTypeDatabase)) {
- t.Fatalf("Wrong number of plugins, got %d, expected %d", len(resp.Data["keys"].([]string)), len(builtinplugins.Registry.Keys(consts.PluginTypeDatabase)))
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "plugins/catalog/database/mysql-database-plugin")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- // Get deprecation status directly from the registry so we can compare it to the API response
- deprecationStatus, _ := c.builtinRegistry.DeprecationStatus("mysql-database-plugin", consts.PluginTypeDatabase)
-
- actualRespData := resp.Data
- expectedRespData := map[string]interface{}{
- "name": "mysql-database-plugin",
- "command": "",
- "args": []string(nil),
- "sha256": "",
- "builtin": true,
- "version": versions.GetBuiltinVersion(consts.PluginTypeDatabase, "mysql-database-plugin"),
- "deprecation_status": deprecationStatus.String(),
- }
- if !reflect.DeepEqual(actualRespData, expectedRespData) {
- t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", actualRespData, expectedRespData)
- }
-
- // Set a plugin
- file, err := ioutil.TempFile(os.TempDir(), "temp")
- if err != nil {
- t.Fatal(err)
- }
- defer file.Close()
-
- // Check we can only specify args in one of command or args.
- command := fmt.Sprintf("%s --test", filepath.Base(file.Name()))
- req = logical.TestRequest(t, logical.UpdateOperation, "plugins/catalog/database/test-plugin")
- req.Data["args"] = []string{"--foo"}
- req.Data["sha_256"] = hex.EncodeToString([]byte{'1'})
- req.Data["command"] = command
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp.Error().Error() != "must not specify args in command and args field" {
- t.Fatalf("err: %v", resp.Error())
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- delete(req.Data, "args")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || resp.Error() != nil {
- t.Fatalf("err: %v %v", err, resp.Error())
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "plugins/catalog/database/test-plugin")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- actual := resp.Data
- expected := map[string]interface{}{
- "name": "test-plugin",
- "command": filepath.Base(file.Name()),
- "args": []string{"--test"},
- "sha256": "31",
- "builtin": false,
- "version": "",
- }
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", actual, expected)
- }
-
- // Delete plugin
- req = logical.TestRequest(t, logical.DeleteOperation, "plugins/catalog/database/test-plugin")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- req = logical.TestRequest(t, logical.ReadOperation, "plugins/catalog/database/test-plugin")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if resp != nil || err != nil {
- t.Fatalf("expected nil response, plugin not deleted correctly got resp: %v, err: %v", resp, err)
- }
-
- // Add a versioned plugin, and check we get the version back in the right form when we read.
- req = logical.TestRequest(t, logical.UpdateOperation, "plugins/catalog/database/test-plugin")
- req.Data["version"] = "v0.1.0"
- req.Data["sha_256"] = hex.EncodeToString([]byte{'1'})
- req.Data["command"] = command
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || resp.Error() != nil {
- t.Fatalf("err: %v %v", err, resp.Error())
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "plugins/catalog/database/test-plugin")
- req.Data["version"] = "v0.1.0"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- actual = resp.Data
- expected = map[string]interface{}{
- "name": "test-plugin",
- "command": filepath.Base(file.Name()),
- "args": []string{"--test"},
- "sha256": "31",
- "builtin": false,
- "version": "v0.1.0",
- }
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", actual, expected)
- }
-
- // Delete versioned plugin
- req = logical.TestRequest(t, logical.DeleteOperation, "plugins/catalog/database/test-plugin")
- req.Data["version"] = "0.1.0"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "plugins/catalog/database/test-plugin")
- req.Data["version"] = "0.1.0"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if resp != nil || err != nil {
- t.Fatalf("expected nil response, plugin not deleted correctly got resp: %v, err: %v", resp, err)
- }
-}
-
-func TestSystemBackend_PluginCatalog_ListPlugins_SucceedsWithAuditLogEnabled(t *testing.T) {
- core, b, root := testCoreSystemBackend(t)
-
- tempDir := t.TempDir()
- f, err := os.CreateTemp(tempDir, "")
- if err != nil {
- t.Fatal(err)
- }
-
- // Enable audit logging.
- req := logical.TestRequest(t, logical.UpdateOperation, "audit/file")
- req.Data = map[string]any{
- "type": "file",
- "options": map[string]any{
- "file_path": f.Name(),
- },
- }
- ctx := namespace.RootContext(nil)
- resp, err := b.HandleRequest(ctx, req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("resp: %#v, err: %v", resp, err)
- }
-
- // List plugins
- req = logical.TestRequest(t, logical.ReadOperation, "sys/plugins/catalog")
- req.ClientToken = root
- resp, err = core.HandleRequest(ctx, req)
- if err != nil || resp == nil || resp.IsError() {
- t.Fatalf("resp: %#v, err: %v", resp, err)
- }
-}
-
-func TestSystemBackend_PluginCatalog_CannotRegisterBuiltinPlugins(t *testing.T) {
- c, b, _ := testCoreSystemBackend(t)
- // Bootstrap the pluginCatalog
- sym, err := filepath.EvalSymlinks(os.TempDir())
- if err != nil {
- t.Fatalf("error: %v", err)
- }
- c.pluginCatalog.directory = sym
-
- // Set a plugin
- req := logical.TestRequest(t, logical.UpdateOperation, "plugins/catalog/database/test-plugin")
- req.Data["sha256"] = hex.EncodeToString([]byte{'1'})
- req.Data["command"] = "foo"
- req.Data["version"] = "v1.2.3+special.builtin"
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !strings.Contains(resp.Error().Error(), "reserved metadata") {
- t.Fatalf("err: %v", resp.Error())
- }
-}
-
-func TestSystemBackend_ToolsHash(t *testing.T) {
- b := testSystemBackend(t)
- req := logical.TestRequest(t, logical.UpdateOperation, "tools/hash")
- req.Data = map[string]interface{}{
- "input": "dGhlIHF1aWNrIGJyb3duIGZveA==",
- }
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- doRequest := func(req *logical.Request, errExpected bool, expected string) {
- t.Helper()
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil && !errExpected {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected non-nil response")
- }
-
- if errExpected {
- if !resp.IsError() {
- t.Fatalf("bad: got error response: %#v", *resp)
- }
- return
- } else {
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
- }
-
- if resp.IsError() {
- t.Fatalf("bad: got error response: %#v", *resp)
- }
- sum, ok := resp.Data["sum"]
- if !ok {
- t.Fatal("no sum key found in returned data")
- }
- if sum.(string) != expected {
- t.Fatalf("mismatched hashes: got: %s, expect: %s", sum.(string), expected)
- }
- }
-
- // Test defaults -- sha2-256
- doRequest(req, false, "9ecb36561341d18eb65484e833efea61edc74b84cf5e6ae1b81c63533e25fc8f")
-
- // Test algorithm selection in the path
- req.Path = "tools/hash/sha2-224"
- doRequest(req, false, "ea074a96cabc5a61f8298a2c470f019074642631a49e1c5e2f560865")
-
- // Reset and test algorithm selection in the data
- req.Path = "tools/hash"
- req.Data["algorithm"] = "sha2-224"
- doRequest(req, false, "ea074a96cabc5a61f8298a2c470f019074642631a49e1c5e2f560865")
-
- req.Data["algorithm"] = "sha2-384"
- doRequest(req, false, "15af9ec8be783f25c583626e9491dbf129dd6dd620466fdf05b3a1d0bb8381d30f4d3ec29f923ff1e09a0f6b337365a6")
-
- req.Data["algorithm"] = "sha2-512"
- doRequest(req, false, "d9d380f29b97ad6a1d92e987d83fa5a02653301e1006dd2bcd51afa59a9147e9caedaf89521abc0f0b682adcd47fb512b8343c834a32f326fe9bef00542ce887")
-
- // Test returning as base64
- req.Data["format"] = "base64"
- doRequest(req, false, "2dOA8puXrWodkumH2D+loCZTMB4QBt0rzVGvpZqRR+nK7a+JUhq8DwtoKtzUf7USuDQ8g0oy8yb+m+8AVCzohw==")
-
- // Test SHA-3
- req.Data["format"] = "hex"
- req.Data["algorithm"] = "sha3-224"
- doRequest(req, false, "ced91e69d89c837e87cff960bd64fd9b9f92325fb9add8988d33d007")
-
- req.Data["algorithm"] = "sha3-256"
- doRequest(req, false, "e4bd866ec3fa52df3b7842aa97b448bc859a7606cefcdad1715847f4b82a6c93")
-
- req.Data["algorithm"] = "sha3-384"
- doRequest(req, false, "715cd38cbf8d0bab426b6a084d649760be555dd64b34de6db148a3fbf2cd2aa5d8b03eb6eda73a3e9a8769c00b4c2113")
-
- req.Data["algorithm"] = "sha3-512"
- doRequest(req, false, "f7cac5ad830422a5408b36a60a60620687be180765a3e2895bc3bdbd857c9e08246c83064d4e3612f0cb927f3ead208413ab98624bf7b0617af0f03f62080976")
-
- // Test bad input/format/algorithm
- req.Data["format"] = "base92"
- doRequest(req, true, "")
-
- req.Data["format"] = "hex"
- req.Data["algorithm"] = "foobar"
- doRequest(req, true, "")
-
- req.Data["algorithm"] = "sha2-256"
- req.Data["input"] = "foobar"
- doRequest(req, true, "")
-}
-
-func TestSystemBackend_ToolsRandom(t *testing.T) {
- b := testSystemBackend(t)
- req := logical.TestRequest(t, logical.UpdateOperation, "tools/random")
-
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- doRequest := func(req *logical.Request, errExpected bool, format string, numBytes int) {
- t.Helper()
- getResponse := func() []byte {
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil && !errExpected {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected non-nil response")
- }
- if errExpected {
- if !resp.IsError() {
- t.Fatalf("bad: got error response: %#v", *resp)
- }
- return nil
- } else {
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
- }
-
- if resp.IsError() {
- t.Fatalf("bad: got error response: %#v", *resp)
- }
- if _, ok := resp.Data["random_bytes"]; !ok {
- t.Fatal("no random_bytes found in response")
- }
-
- outputStr := resp.Data["random_bytes"].(string)
- var outputBytes []byte
- switch format {
- case "base64":
- outputBytes, err = base64.StdEncoding.DecodeString(outputStr)
- case "hex":
- outputBytes, err = hex.DecodeString(outputStr)
- default:
- t.Fatal("unknown format")
- }
- if err != nil {
- t.Fatal(err)
- }
-
- return outputBytes
- }
-
- rand1 := getResponse()
- // Expected error
- if rand1 == nil {
- return
- }
- rand2 := getResponse()
- if len(rand1) != numBytes || len(rand2) != numBytes {
- t.Fatal("length of output random bytes not what is expected")
- }
- if reflect.DeepEqual(rand1, rand2) {
- t.Fatal("found identical ouputs")
- }
- }
-
- // Test defaults
- doRequest(req, false, "base64", 32)
-
- // Test size selection in the path
- req.Path = "tools/random/24"
- req.Data["format"] = "hex"
- doRequest(req, false, "hex", 24)
-
- // Test bad input/format
- req.Path = "tools/random"
- req.Data["format"] = "base92"
- doRequest(req, true, "", 0)
-
- req.Data["format"] = "hex"
- req.Data["bytes"] = -1
- doRequest(req, true, "", 0)
-
- req.Data["format"] = "hex"
- req.Data["bytes"] = maxBytes + 1
- doRequest(req, true, "", 0)
-}
-
-func TestSystemBackend_InternalUIMounts(t *testing.T) {
- _, b, rootToken := testCoreSystemBackend(t)
- systemBackend := b.(*SystemBackend)
-
- // Ensure no entries are in the endpoint as a starting point
- req := logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, systemBackend.Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- exp := map[string]interface{}{
- "secret": map[string]interface{}{},
- "auth": map[string]interface{}{},
- }
- if !reflect.DeepEqual(resp.Data, exp) {
- t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts")
- req.ClientToken = rootToken
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, systemBackend.Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- exp = map[string]interface{}{
- "secret": map[string]interface{}{
- "secret/": map[string]interface{}{
- "type": "kv",
- "external_entropy_access": false,
- "description": "key/value secret storage",
- "accessor": resp.Data["secret"].(map[string]interface{})["secret/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["secret"].(map[string]interface{})["secret/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": resp.Data["secret"].(map[string]interface{})["secret/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
- "max_lease_ttl": resp.Data["secret"].(map[string]interface{})["secret/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
- "force_no_cache": false,
- },
- "local": false,
- "seal_wrap": false,
- "options": map[string]string{
- "version": "1",
- },
- "plugin_version": "",
- "running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
- "running_sha256": "",
- },
- "sys/": map[string]interface{}{
- "type": "system",
- "external_entropy_access": false,
- "description": "system endpoints used for control, policy and debugging",
- "accessor": resp.Data["secret"].(map[string]interface{})["sys/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["secret"].(map[string]interface{})["sys/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": resp.Data["secret"].(map[string]interface{})["sys/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
- "max_lease_ttl": resp.Data["secret"].(map[string]interface{})["sys/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
- "force_no_cache": false,
- "passthrough_request_headers": []string{"Accept"},
- },
- "local": false,
- "seal_wrap": true,
- "options": map[string]string(nil),
- "plugin_version": "",
- "running_plugin_version": versions.DefaultBuiltinVersion,
- "running_sha256": "",
- },
- "cubbyhole/": map[string]interface{}{
- "description": "per-token private secret storage",
- "type": "cubbyhole",
- "external_entropy_access": false,
- "accessor": resp.Data["secret"].(map[string]interface{})["cubbyhole/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["secret"].(map[string]interface{})["cubbyhole/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": resp.Data["secret"].(map[string]interface{})["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
- "max_lease_ttl": resp.Data["secret"].(map[string]interface{})["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
- "force_no_cache": false,
- },
- "local": true,
- "seal_wrap": false,
- "options": map[string]string(nil),
- "plugin_version": "",
- "running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
- "running_sha256": "",
- },
- "identity/": map[string]interface{}{
- "description": "identity store",
- "type": "identity",
- "external_entropy_access": false,
- "accessor": resp.Data["secret"].(map[string]interface{})["identity/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["secret"].(map[string]interface{})["identity/"].(map[string]interface{})["uuid"],
- "config": map[string]interface{}{
- "default_lease_ttl": resp.Data["secret"].(map[string]interface{})["identity/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
- "max_lease_ttl": resp.Data["secret"].(map[string]interface{})["identity/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
- "force_no_cache": false,
- "passthrough_request_headers": []string{"Authorization"},
- },
- "local": false,
- "seal_wrap": false,
- "options": map[string]string(nil),
- "plugin_version": "",
- "running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
- "running_sha256": "",
- },
- },
- "auth": map[string]interface{}{
- "token/": map[string]interface{}{
- "options": map[string]string(nil),
- "config": map[string]interface{}{
- "default_lease_ttl": int64(0),
- "max_lease_ttl": int64(0),
- "force_no_cache": false,
- "token_type": "default-service",
- },
- "type": "token",
- "external_entropy_access": false,
- "description": "token based credentials",
- "accessor": resp.Data["auth"].(map[string]interface{})["token/"].(map[string]interface{})["accessor"],
- "uuid": resp.Data["auth"].(map[string]interface{})["token/"].(map[string]interface{})["uuid"],
- "local": false,
- "seal_wrap": false,
- "plugin_version": "",
- "running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeCredential, "token"),
- "running_sha256": "",
- },
- },
- }
- if diff := deep.Equal(resp.Data, exp); diff != nil {
- t.Fatal(diff)
- }
-
- // Mount-tune an auth mount
- req = logical.TestRequest(t, logical.UpdateOperation, "auth/token/tune")
- req.Data["listing_visibility"] = "unauth"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if resp.IsError() || err != nil {
- t.Fatalf("resp.Error: %v, err:%v", resp.Error(), err)
- }
-
- // Mount-tune a secret mount
- req = logical.TestRequest(t, logical.UpdateOperation, "mounts/secret/tune")
- req.Data["listing_visibility"] = "unauth"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if resp.IsError() || err != nil {
- t.Fatalf("resp.Error: %v, err:%v", resp.Error(), err)
- }
-
- // validate the response structure for mount update
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts")
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, systemBackend.Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- exp = map[string]interface{}{
- "secret": map[string]interface{}{
- "secret/": map[string]interface{}{
- "type": "kv",
- "description": "key/value secret storage",
- "options": map[string]string{"version": "1"},
- },
- },
- "auth": map[string]interface{}{
- "token/": map[string]interface{}{
- "type": "token",
- "description": "token based credentials",
- "options": map[string]string(nil),
- },
- },
- }
- if !reflect.DeepEqual(resp.Data, exp) {
- t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
- }
-}
-
-func TestSystemBackend_InternalUIMount(t *testing.T) {
- core, b, rootToken := testCoreSystemBackend(t)
- systemBackend := b.(*SystemBackend)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "policy/secret")
- req.ClientToken = rootToken
- req.Data = map[string]interface{}{
- "rules": `path "secret/foo/*" {
- capabilities = ["create", "read", "update", "delete", "list"]
-}`,
- }
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("Bad %#v %#v", err, resp)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "mounts/kv")
- req.ClientToken = rootToken
- req.Data = map[string]interface{}{
- "type": "kv",
- }
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("Bad %#v %#v", err, resp)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts/kv/bar")
- req.ClientToken = rootToken
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("Bad %#v %#v", err, resp)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, systemBackend.Route(req.Path), req.Operation),
- resp,
- true,
- )
- if resp.Data["type"] != "kv" {
- t.Fatalf("Bad Response: %#v", resp)
- }
-
- testMakeServiceTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"secret"})
-
- req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts/kv")
- req.ClientToken = "tokenid"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrPermissionDenied {
- t.Fatal("expected permission denied error")
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts/secret")
- req.ClientToken = "tokenid"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("Bad %#v %#v", err, resp)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, systemBackend.Route(req.Path), req.Operation),
- resp,
- true,
- )
- if resp.Data["type"] != "kv" {
- t.Fatalf("Bad Response: %#v", resp)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts/sys")
- req.ClientToken = "tokenid"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("Bad %#v %#v", err, resp)
- }
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, systemBackend.Route(req.Path), req.Operation),
- resp,
- true,
- )
- if resp.Data["type"] != "system" {
- t.Fatalf("Bad Response: %#v", resp)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts/non-existent")
- req.ClientToken = "tokenid"
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrPermissionDenied {
- t.Fatal("expected permission denied error")
- }
-}
-
-func TestSystemBackend_OpenAPI(t *testing.T) {
- _, b, rootToken := testCoreSystemBackend(t)
-
- // Ensure no paths are reported if there is no token
- {
- req := logical.TestRequest(t, logical.ReadOperation, "internal/specs/openapi")
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- body := resp.Data["http_raw_body"].([]byte)
- var oapi map[string]interface{}
- err = jsonutil.DecodeJSON(body, &oapi)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- exp := map[string]interface{}{
- "openapi": framework.OASVersion,
- "info": map[string]interface{}{
- "title": "HashiCorp Vault API",
- "description": "HTTP API that gives you full access to Vault. All API routes are prefixed with `/v1/`.",
- "version": version.GetVersion().Version,
- "license": map[string]interface{}{
- "name": "Mozilla Public License 2.0",
- "url": "https://www.mozilla.org/en-US/MPL/2.0",
- },
- },
- "paths": map[string]interface{}{},
- "components": map[string]interface{}{
- "schemas": map[string]interface{}{},
- },
- }
-
- if diff := deep.Equal(oapi, exp); diff != nil {
- t.Fatal(diff)
- }
- }
-
- // Check that default paths are present with a root token (with and without generic_mount_paths)
- for _, genericMountPaths := range []bool{false, true} {
- req := logical.TestRequest(t, logical.ReadOperation, "internal/specs/openapi")
- if genericMountPaths {
- req.Data["generic_mount_paths"] = true
- }
- req.ClientToken = rootToken
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- body := resp.Data["http_raw_body"].([]byte)
- var oapi map[string]interface{}
- err = jsonutil.DecodeJSON(body, &oapi)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- doc, err := framework.NewOASDocumentFromMap(oapi)
- if err != nil {
- t.Fatal(err)
- }
-
- expectedSecretPrefix := "/secret/"
- if genericMountPaths {
- expectedSecretPrefix = "/{secret_mount_path}/"
- }
-
- pathSamples := []struct {
- path string
- tag string
- unpublished bool
- }{
- {path: "/auth/token/lookup", tag: "auth"},
- {path: "/cubbyhole/{path}", tag: "secrets"},
- {path: "/identity/group/id", tag: "identity"},
- {path: expectedSecretPrefix + "^.*$", unpublished: true},
- {path: "/sys/policy", tag: "system"},
- }
-
- for _, path := range pathSamples {
- if doc.Paths[path.path] == nil {
- t.Fatalf("didn't find expected path %q.", path.path)
- }
- getOperation := doc.Paths[path.path].Get
- if getOperation == nil && !path.unpublished {
- t.Fatalf("path: %s; expected a get operation, but it was absent", path.path)
- }
- if getOperation != nil && path.unpublished {
- t.Fatalf("path: %s; expected absent get operation, but it was present", path.path)
- }
- if !path.unpublished {
- tag := getOperation.Tags[0]
- if tag != path.tag {
- t.Fatalf("path: %s; expected tag: %s, actual: %s", path.path, tag, path.tag)
- }
- }
- }
-
- // Simple check of response size (which is much larger than most
- // Vault responses), mainly to catch mass omission of expected path data.
- const minLen = 70000
- if len(body) < minLen {
- t.Fatalf("response size too small; expected: min %d, actual: %d", minLen, len(body))
- }
- }
-
- // Test path-help response
- {
- req := logical.TestRequest(t, logical.HelpOperation, "rotate")
- req.ClientToken = rootToken
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- doc := resp.Data["openapi"].(*framework.OASDocument)
- if len(doc.Paths) != 1 {
- t.Fatalf("expected 1 path, actual: %d", len(doc.Paths))
- }
-
- if doc.Paths["/rotate"] == nil {
- t.Fatalf("expected to find path '/rotate'")
- }
- }
-}
-
-func TestSystemBackend_PathWildcardPreflight(t *testing.T) {
- core, b, _ := testCoreSystemBackend(t)
-
- ctx := namespace.RootContext(nil)
-
- // Add another mount
- me := &MountEntry{
- Table: mountTableType,
- Path: sanitizePath("kv-v1"),
- Type: "kv",
- Options: map[string]string{"version": "1"},
- }
- if err := core.mount(ctx, me); err != nil {
- t.Fatal(err)
- }
-
- // Create the policy, designed to fail
- rules := `path "foo" { capabilities = ["read"] }`
- req := logical.TestRequest(t, logical.UpdateOperation, "policy/foo")
- req.Data["rules"] = rules
- resp, err := b.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v %#v", err, resp)
- }
- if resp != nil && (resp.IsError() || len(resp.Data) > 0) {
- t.Fatalf("bad: %#v", resp)
- }
-
- if err := core.identityStore.upsertEntity(ctx, &identity.Entity{
- ID: "abcd",
- Name: "abcd",
- BucketKey: "abcd",
- }, nil, false); err != nil {
- t.Fatal(err)
- }
-
- te := &logical.TokenEntry{
- TTL: 300 * time.Second,
- EntityID: "abcd",
- Policies: []string{"default", "foo"},
- NamespaceID: namespace.RootNamespaceID,
- }
- if err := core.tokenStore.create(ctx, te); err != nil {
- t.Fatal(err)
- }
- t.Logf("token id: %s", te.ID)
-
- if err := core.expiration.RegisterAuth(ctx, te, &logical.Auth{
- LeaseOptions: logical.LeaseOptions{
- TTL: te.TTL,
- },
- ClientToken: te.ID,
- Accessor: te.Accessor,
- Orphan: true,
- }, ""); err != nil {
- t.Fatal(err)
- }
-
- // Check the mount access func
- req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts/kv-v1/baz")
- req.ClientToken = te.ID
- resp, err = b.HandleRequest(ctx, req)
- if err == nil || !strings.Contains(err.Error(), "permission denied") {
- t.Fatalf("expected 403, got err: %v", err)
- }
-
- // Modify policy to pass
- rules = `path "kv-v1/+" { capabilities = ["read"] }`
- req = logical.TestRequest(t, logical.UpdateOperation, "policy/foo")
- req.Data["rules"] = rules
- resp, err = b.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v %#v", err, resp)
- }
- if resp != nil && (resp.IsError() || len(resp.Data) > 0) {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Check the mount access func again
- req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts/kv-v1/baz")
- req.ClientToken = te.ID
- resp, err = b.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestHandlePoliciesPasswordSet(t *testing.T) {
- type testCase struct {
- inputData *framework.FieldData
-
- storage *logical.InmemStorage
-
- expectedResp *logical.Response
- expectErr bool
- expectedStore map[string]*logical.StorageEntry
- }
-
- tests := map[string]testCase{
- "missing policy name": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "policy": `length = 20
- rule "charset" {
- charset="abcdefghij"
- }`,
- }),
-
- storage: new(logical.InmemStorage),
-
- expectedResp: nil,
- expectErr: true,
- expectedStore: map[string]*logical.StorageEntry{},
- },
- "missing policy": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- }),
-
- storage: new(logical.InmemStorage),
-
- expectedResp: nil,
- expectErr: true,
- expectedStore: map[string]*logical.StorageEntry{},
- },
- "garbage policy": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- "policy": "hasdukfhiuashdfoiasjdf",
- }),
-
- storage: new(logical.InmemStorage),
-
- expectedResp: nil,
- expectErr: true,
- expectedStore: map[string]*logical.StorageEntry{},
- },
- "storage failure": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- "policy": "length = 20\n" +
- "rule \"charset\" {\n" +
- " charset=\"abcdefghij\"\n" +
- "}",
- }),
-
- storage: new(logical.InmemStorage).FailPut(true),
-
- expectedResp: nil,
- expectErr: true,
- expectedStore: map[string]*logical.StorageEntry{},
- },
- "impossible policy": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- "policy": "length = 20\n" +
- "rule \"charset\" {\n" +
- " charset=\"a\"\n" +
- " min-chars = 30\n" +
- "}",
- }),
-
- storage: new(logical.InmemStorage),
-
- expectedResp: nil,
- expectErr: true,
- expectedStore: map[string]*logical.StorageEntry{},
- },
- "not base64 encoded": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- "policy": "length = 20\n" +
- "rule \"charset\" {\n" +
- " charset=\"abcdefghij\"\n" +
- "}",
- }),
-
- storage: new(logical.InmemStorage),
-
- expectedResp: &logical.Response{
- Data: map[string]interface{}{
- logical.HTTPContentType: "application/json",
- logical.HTTPStatusCode: http.StatusNoContent,
- },
- },
- expectErr: false,
- expectedStore: makeStorageMap(storageEntry(t, "testpolicy", "length = 20\n"+
- "rule \"charset\" {\n"+
- " charset=\"abcdefghij\"\n"+
- "}")),
- },
- "base64 encoded": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- "policy": base64Encode(
- "length = 20\n" +
- "rule \"charset\" {\n" +
- " charset=\"abcdefghij\"\n" +
- "}"),
- }),
-
- storage: new(logical.InmemStorage),
-
- expectedResp: &logical.Response{
- Data: map[string]interface{}{
- logical.HTTPContentType: "application/json",
- logical.HTTPStatusCode: http.StatusNoContent,
- },
- },
- expectErr: false,
- expectedStore: makeStorageMap(storageEntry(t, "testpolicy",
- "length = 20\n"+
- "rule \"charset\" {\n"+
- " charset=\"abcdefghij\"\n"+
- "}")),
- },
- }
-
- for name, test := range tests {
- t.Run(name, func(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
- defer cancel()
-
- req := &logical.Request{
- Storage: test.storage,
- }
-
- b := &SystemBackend{}
-
- actualResp, err := b.handlePoliciesPasswordSet(ctx, req, test.inputData)
- if test.expectErr && err == nil {
- t.Fatalf("err expected, got nil")
- }
- if !test.expectErr && err != nil {
- t.Fatalf("no error expected, got: %s", err)
- }
- if !reflect.DeepEqual(actualResp, test.expectedResp) {
- t.Fatalf("Actual response: %#v\nExpected response: %#v", actualResp, test.expectedResp)
- }
-
- actualStore := LogicalToMap(t, ctx, test.storage)
- if !reflect.DeepEqual(actualStore, test.expectedStore) {
- t.Fatalf("Actual: %#v\nActual: %#v", dereferenceMap(actualStore), dereferenceMap(test.expectedStore))
- }
- })
- }
-}
-
-func TestHandlePoliciesPasswordGet(t *testing.T) {
- type testCase struct {
- inputData *framework.FieldData
-
- storage *logical.InmemStorage
-
- expectedResp *logical.Response
- expectErr bool
- expectedStore map[string]*logical.StorageEntry
- }
-
- tests := map[string]testCase{
- "missing policy name": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{}),
-
- storage: new(logical.InmemStorage),
-
- expectedResp: nil,
- expectErr: true,
- expectedStore: map[string]*logical.StorageEntry{},
- },
- "storage error": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- }),
-
- storage: new(logical.InmemStorage).FailGet(true),
-
- expectedResp: nil,
- expectErr: true,
- expectedStore: map[string]*logical.StorageEntry{},
- },
- "missing value": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- }),
-
- storage: new(logical.InmemStorage),
-
- expectedResp: nil,
- expectErr: true,
- expectedStore: map[string]*logical.StorageEntry{},
- },
- "good value": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- }),
-
- storage: makeStorage(t, storageEntry(t, "testpolicy",
- "length = 20\n"+
- "rule \"charset\" {\n"+
- " charset=\"abcdefghij\"\n"+
- "}")),
-
- expectedResp: &logical.Response{
- Data: map[string]interface{}{
- "policy": "length = 20\n" +
- "rule \"charset\" {\n" +
- " charset=\"abcdefghij\"\n" +
- "}",
- },
- },
- expectErr: false,
- expectedStore: makeStorageMap(storageEntry(t, "testpolicy",
- "length = 20\n"+
- "rule \"charset\" {\n"+
- " charset=\"abcdefghij\"\n"+
- "}")),
- },
- }
-
- for name, test := range tests {
- t.Run(name, func(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
- defer cancel()
-
- req := &logical.Request{
- Storage: test.storage,
- }
-
- b := &SystemBackend{}
-
- actualResp, err := b.handlePoliciesPasswordGet(ctx, req, test.inputData)
- if test.expectErr && err == nil {
- t.Fatalf("err expected, got nil")
- }
- if !test.expectErr && err != nil {
- t.Fatalf("no error expected, got: %s", err)
- }
- if !reflect.DeepEqual(actualResp, test.expectedResp) {
- t.Fatalf("Actual response: %#v\nExpected response: %#v", actualResp, test.expectedResp)
- }
-
- actualStore := LogicalToMap(t, ctx, test.storage)
- if !reflect.DeepEqual(actualStore, test.expectedStore) {
- t.Fatalf("Actual: %#v\nActual: %#v", dereferenceMap(actualStore), dereferenceMap(test.expectedStore))
- }
- })
- }
-}
-
-func TestHandlePoliciesPasswordDelete(t *testing.T) {
- type testCase struct {
- inputData *framework.FieldData
-
- storage logical.Storage
-
- expectedResp *logical.Response
- expectErr bool
- expectedStore map[string]*logical.StorageEntry
- }
-
- tests := map[string]testCase{
- "missing policy name": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{}),
-
- storage: new(logical.InmemStorage),
-
- expectedResp: nil,
- expectErr: true,
- expectedStore: map[string]*logical.StorageEntry{},
- },
- "storage failure": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- }),
-
- storage: new(logical.InmemStorage).FailDelete(true),
-
- expectedResp: nil,
- expectErr: true,
- expectedStore: map[string]*logical.StorageEntry{},
- },
- "successful delete": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- }),
-
- storage: makeStorage(t,
- &logical.StorageEntry{
- Key: getPasswordPolicyKey("testpolicy"),
- Value: toJson(t,
- passwordPolicyConfig{
- HCLPolicy: "length = 18\n" +
- "rule \"charset\" {\n" +
- " charset=\"ABCDEFGHIJ\"\n" +
- "}",
- }),
- },
- &logical.StorageEntry{
- Key: getPasswordPolicyKey("unrelated_policy"),
- Value: toJson(t,
- passwordPolicyConfig{
- HCLPolicy: "length = 20\n" +
- "rule \"charset\" {\n" +
- " charset=\"abcdefghij\"\n" +
- "}",
- }),
- },
- ),
-
- expectedResp: nil,
- expectErr: false,
- expectedStore: makeStorageMap(storageEntry(t, "unrelated_policy",
- "length = 20\n"+
- "rule \"charset\" {\n"+
- " charset=\"abcdefghij\"\n"+
- "}")),
- },
- }
-
- for name, test := range tests {
- t.Run(name, func(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
- defer cancel()
-
- req := &logical.Request{
- Storage: test.storage,
- }
-
- b := &SystemBackend{}
-
- actualResp, err := b.handlePoliciesPasswordDelete(ctx, req, test.inputData)
- if test.expectErr && err == nil {
- t.Fatalf("err expected, got nil")
- }
- if !test.expectErr && err != nil {
- t.Fatalf("no error expected, got: %s", err)
- }
- if !reflect.DeepEqual(actualResp, test.expectedResp) {
- t.Fatalf("Actual response: %#v\nExpected response: %#v", actualResp, test.expectedResp)
- }
-
- actualStore := LogicalToMap(t, ctx, test.storage)
- if !reflect.DeepEqual(actualStore, test.expectedStore) {
- t.Fatalf("Actual: %#v\nExpected: %#v", dereferenceMap(actualStore), dereferenceMap(test.expectedStore))
- }
- })
- }
-}
-
-func TestHandlePoliciesPasswordList(t *testing.T) {
- type testCase struct {
- storage logical.Storage
-
- expectErr bool
- expectedResp *logical.Response
- }
-
- tests := map[string]testCase{
- "no policies": {
- storage: new(logical.InmemStorage),
-
- expectedResp: &logical.Response{
- Data: map[string]interface{}{},
- },
- },
- "one policy": {
- storage: makeStorage(t,
- &logical.StorageEntry{
- Key: getPasswordPolicyKey("testpolicy"),
- Value: toJson(t,
- passwordPolicyConfig{
- HCLPolicy: "length = 18\n" +
- "rule \"charset\" {\n" +
- " charset=\"ABCDEFGHIJ\"\n" +
- "}",
- }),
- },
- ),
-
- expectedResp: &logical.Response{
- Data: map[string]interface{}{
- "keys": []string{"testpolicy"},
- },
- },
- },
- "two policies": {
- storage: makeStorage(t,
- &logical.StorageEntry{
- Key: getPasswordPolicyKey("testpolicy"),
- Value: toJson(t,
- passwordPolicyConfig{
- HCLPolicy: "length = 18\n" +
- "rule \"charset\" {\n" +
- " charset=\"ABCDEFGHIJ\"\n" +
- "}",
- }),
- },
- &logical.StorageEntry{
- Key: getPasswordPolicyKey("unrelated_policy"),
- Value: toJson(t,
- passwordPolicyConfig{
- HCLPolicy: "length = 20\n" +
- "rule \"charset\" {\n" +
- " charset=\"abcdefghij\"\n" +
- "}",
- }),
- },
- ),
-
- expectedResp: &logical.Response{
- Data: map[string]interface{}{
- "keys": []string{
- "testpolicy",
- "unrelated_policy",
- },
- },
- },
- },
- "policy with /": {
- storage: makeStorage(t,
- &logical.StorageEntry{
- Key: getPasswordPolicyKey("testpolicy/testpolicy1"),
- Value: toJson(t,
- passwordPolicyConfig{
- HCLPolicy: "length = 18\n" +
- "rule \"charset\" {\n" +
- " charset=\"ABCDEFGHIJ\"\n" +
- "}",
- }),
- },
- ),
-
- expectedResp: &logical.Response{
- Data: map[string]interface{}{
- "keys": []string{"testpolicy/testpolicy1"},
- },
- },
- },
- "list path/to/policy": {
- storage: makeStorage(t,
- &logical.StorageEntry{
- Key: getPasswordPolicyKey("path/to/policy"),
- Value: toJson(t,
- passwordPolicyConfig{
- HCLPolicy: "length = 18\n" +
- "rule \"charset\" {\n" +
- " charset=\"ABCDEFGHIJ\"\n" +
- "}",
- }),
- },
- ),
-
- expectedResp: &logical.Response{
- Data: map[string]interface{}{
- "keys": []string{"path/to/policy"},
- },
- },
- },
- "policy ending with /": {
- storage: makeStorage(t,
- &logical.StorageEntry{
- Key: getPasswordPolicyKey("path/to/policy/"),
- Value: toJson(t,
- passwordPolicyConfig{
- HCLPolicy: "length = 18\n" +
- "rule \"charset\" {\n" +
- " charset=\"ABCDEFGHIJ\"\n" +
- "}",
- }),
- },
- ),
-
- expectedResp: &logical.Response{
- Data: map[string]interface{}{
- "keys": []string{"path/to/policy/"},
- },
- },
- },
- "storage failure": {
- storage: new(logical.InmemStorage).FailList(true),
-
- expectErr: true,
- },
- }
-
- for name, test := range tests {
- t.Run(name, func(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
- defer cancel()
-
- req := &logical.Request{
- Storage: test.storage,
- }
-
- b := &SystemBackend{}
-
- actualResp, err := b.handlePoliciesPasswordList(ctx, req, nil)
- if test.expectErr && err == nil {
- t.Fatalf("err expected, got nil")
- }
- if !test.expectErr && err != nil {
- t.Fatalf("no error expected, got: %s", err)
- }
- if !reflect.DeepEqual(actualResp, test.expectedResp) {
- t.Fatalf("Actual response: %#v\nExpected response: %#v", actualResp, test.expectedResp)
- }
- })
- }
-}
-
-func TestHandlePoliciesPasswordGenerate(t *testing.T) {
- t.Run("errors", func(t *testing.T) {
- type testCase struct {
- timeout time.Duration
- inputData *framework.FieldData
-
- storage *logical.InmemStorage
-
- expectedResp *logical.Response
- expectErr bool
- }
-
- tests := map[string]testCase{
- "missing policy name": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{}),
-
- storage: new(logical.InmemStorage),
-
- expectedResp: nil,
- expectErr: true,
- },
- "storage failure": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- }),
-
- storage: new(logical.InmemStorage).FailGet(true),
-
- expectedResp: nil,
- expectErr: true,
- },
- "policy does not exist": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- }),
-
- storage: new(logical.InmemStorage),
-
- expectedResp: nil,
- expectErr: true,
- },
- "policy improperly saved": {
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- }),
-
- storage: makeStorage(t, storageEntry(t, "testpolicy", "badpolicy")),
-
- expectedResp: nil,
- expectErr: true,
- },
- "failed to generate": {
- timeout: 0 * time.Second, // Timeout immediately
- inputData: passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- }),
-
- storage: makeStorage(t, storageEntry(t, "testpolicy",
- "length = 20\n"+
- "rule \"charset\" {\n"+
- " charset=\"abcdefghij\"\n"+
- "}")),
-
- expectedResp: nil,
- expectErr: true,
- },
- }
-
- for name, test := range tests {
- t.Run(name, func(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), test.timeout)
- defer cancel()
-
- req := &logical.Request{
- Storage: test.storage,
- }
-
- b := &SystemBackend{}
-
- actualResp, err := b.handlePoliciesPasswordGenerate(ctx, req, test.inputData)
- if test.expectErr && err == nil {
- t.Fatalf("err expected, got nil")
- }
- if !test.expectErr && err != nil {
- t.Fatalf("no error expected, got: %s", err)
- }
- if !reflect.DeepEqual(actualResp, test.expectedResp) {
- t.Fatalf("Actual response: %#v\nExpected response: %#v", actualResp, test.expectedResp)
- }
- })
- }
- })
-
- t.Run("success", func(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
-
- policyEntry := storageEntry(t, "testpolicy",
- "length = 20\n"+
- "rule \"charset\" {\n"+
- " charset=\"abcdefghij\"\n"+
- "}")
- storage := makeStorage(t, policyEntry)
-
- inputData := passwordPoliciesFieldData(map[string]interface{}{
- "name": "testpolicy",
- })
-
- expectedResp := &logical.Response{
- Data: map[string]interface{}{
- // Doesn't include the password as that's pulled out and compared separately
- },
- }
-
- // Password assertions
- expectedPassLen := 20
- rules := []random.Rule{
- random.CharsetRule{
- Charset: []rune("abcdefghij"),
- MinChars: expectedPassLen,
- },
- }
-
- // Run the test a bunch of times to help ensure we don't have flaky behavior
- for i := 0; i < 1000; i++ {
- req := &logical.Request{
- Storage: storage,
- }
-
- b := &SystemBackend{}
-
- actualResp, err := b.handlePoliciesPasswordGenerate(ctx, req, inputData)
- if err != nil {
- t.Fatalf("no error expected, got: %s", err)
- }
-
- assertTrue(t, actualResp != nil, "response is nil")
- assertTrue(t, actualResp.Data != nil, "expected data, got nil")
- assertHasKey(t, actualResp.Data, "password", "password key not found in data")
- assertIsString(t, actualResp.Data["password"], "password key should have a string value")
- password := actualResp.Data["password"].(string)
-
- // Delete the password so the rest of the response can be compared
- delete(actualResp.Data, "password")
- assertTrue(t, reflect.DeepEqual(actualResp, expectedResp), "Actual response: %#v\nExpected response: %#v", actualResp, expectedResp)
-
- // Check to make sure the password is correctly formatted
- passwordLength := len([]rune(password))
- if passwordLength != expectedPassLen {
- t.Fatalf("password is %d characters but should be %d", passwordLength, expectedPassLen)
- }
-
- for _, rule := range rules {
- if !rule.Pass([]rune(password)) {
- t.Fatalf("password %s does not have the correct characters", password)
- }
- }
- }
- })
-}
-
-func assertTrue(t *testing.T, pass bool, f string, vals ...interface{}) {
- t.Helper()
- if !pass {
- t.Fatalf(f, vals...)
- }
-}
-
-func assertHasKey(t *testing.T, m map[string]interface{}, key string, f string, vals ...interface{}) {
- t.Helper()
- _, exists := m[key]
- if !exists {
- t.Fatalf(f, vals...)
- }
-}
-
-func assertIsString(t *testing.T, val interface{}, f string, vals ...interface{}) {
- t.Helper()
- _, ok := val.(string)
- if !ok {
- t.Fatalf(f, vals...)
- }
-}
-
-func passwordPoliciesFieldData(raw map[string]interface{}) *framework.FieldData {
- return &framework.FieldData{
- Raw: raw,
- Schema: map[string]*framework.FieldSchema{
- "name": {
- Type: framework.TypeString,
- Description: "The name of the password policy.",
- },
- "policy": {
- Type: framework.TypeString,
- Description: "The password policy",
- },
- },
- }
-}
-
-func base64Encode(data string) string {
- return base64.StdEncoding.EncodeToString([]byte(data))
-}
-
-func toJson(t *testing.T, val interface{}) []byte {
- t.Helper()
-
- b, err := jsonutil.EncodeJSON(val)
- if err != nil {
- t.Fatalf("Unable to marshal to JSON: %s", err)
- }
- return b
-}
-
-func storageEntry(t *testing.T, key string, policy string) *logical.StorageEntry {
- return &logical.StorageEntry{
- Key: getPasswordPolicyKey(key),
- Value: toJson(t, passwordPolicyConfig{
- HCLPolicy: policy,
- }),
- }
-}
-
-func makeStorageMap(entries ...*logical.StorageEntry) map[string]*logical.StorageEntry {
- m := map[string]*logical.StorageEntry{}
- for _, entry := range entries {
- m[entry.Key] = entry
- }
- return m
-}
-
-func dereferenceMap(store map[string]*logical.StorageEntry) map[string]interface{} {
- m := map[string]interface{}{}
-
- for k, v := range store {
- m[k] = map[string]string{
- "Key": v.Key,
- "Value": string(v.Value),
- }
- }
- return m
-}
-
-type walkFunc func(*logical.StorageEntry) error
-
-// WalkLogicalStorage applies the provided walkFunc against each entry in the logical storage.
-// This operates as a breadth first search.
-// TODO: Figure out a place for this to live permanently. This is generic and should be in a helper package somewhere.
-// At the time of writing, none of these locations work due to import cycles:
-// - vault/helper/testhelpers
-// - vault/helper/testhelpers/logical
-// - vault/helper/testhelpers/teststorage
-func WalkLogicalStorage(ctx context.Context, store logical.Storage, walker walkFunc) (err error) {
- if store == nil {
- return fmt.Errorf("no storage provided")
- }
- if walker == nil {
- return fmt.Errorf("no walk function provided")
- }
-
- keys, err := store.List(ctx, "")
- if err != nil {
- return fmt.Errorf("unable to list root keys: %w", err)
- }
-
- // Non-recursive breadth-first search through all keys
- for i := 0; i < len(keys); i++ {
- key := keys[i]
-
- entry, err := store.Get(ctx, key)
- if err != nil {
- return fmt.Errorf("unable to retrieve key at [%s]: %w", key, err)
- }
- if entry != nil {
- err = walker(entry)
- if err != nil {
- return err
- }
- }
-
- if strings.HasSuffix(key, "/") {
- // Directory
- subkeys, err := store.List(ctx, key)
- if err != nil {
- return fmt.Errorf("unable to list keys at [%s]: %w", key, err)
- }
-
- // Append the sub-keys to the keys slice so it searches into the sub-directory
- for _, subkey := range subkeys {
- // Avoids infinite loop if the subkey is empty which then repeats indefinitely
- if subkey == "" {
- continue
- }
- subkey = fmt.Sprintf("%s%s", key, subkey)
- keys = append(keys, subkey)
- }
- }
- }
- return nil
-}
-
-// LogicalToMap retrieves all entries in the store and returns them as a map of key -> StorageEntry
-func LogicalToMap(t *testing.T, ctx context.Context, store logical.Storage) (data map[string]*logical.StorageEntry) {
- data = map[string]*logical.StorageEntry{}
- f := func(entry *logical.StorageEntry) error {
- data[entry.Key] = entry
- return nil
- }
-
- err := WalkLogicalStorage(ctx, store, f)
- if err != nil {
- t.Fatalf("Unable to walk the storage: %s", err)
- }
- return data
-}
-
-// Ensure the WalkLogicalStorage function works
-func TestWalkLogicalStorage(t *testing.T) {
- type testCase struct {
- entries []*logical.StorageEntry
- }
-
- tests := map[string]testCase{
- "no entries": {
- entries: []*logical.StorageEntry{},
- },
- "one entry": {
- entries: []*logical.StorageEntry{
- {
- Key: "root",
- },
- },
- },
- "many entries": {
- entries: []*logical.StorageEntry{
- // Alphabetical, breadth-first
- {Key: "bar"},
- {Key: "foo"},
- {Key: "bar/sub-bar1"},
- {Key: "bar/sub-bar2"},
- {Key: "foo/sub-foo1"},
- {Key: "foo/sub-foo2"},
- {Key: "foo/sub-foo3"},
- {Key: "bar/sub-bar1/sub-sub-bar1"},
- {Key: "bar/sub-bar1/sub-sub-bar2"},
- {Key: "bar/sub-bar2/sub-sub-bar1"},
- {Key: "foo/sub-foo1/sub-sub-foo1"},
- {Key: "foo/sub-foo2/sub-sub-foo1"},
- {Key: "foo/sub-foo3/sub-sub-foo1"},
- {Key: "foo/sub-foo3/sub-sub-foo2"},
- },
- },
- "sub key without root key": {
- entries: []*logical.StorageEntry{
- {Key: "foo/bar/baz"},
- },
- },
- "key with trailing slash": {
- entries: []*logical.StorageEntry{
- {Key: "foo/"},
- },
- },
- "double slash": {
- entries: []*logical.StorageEntry{
- {Key: "foo//"},
- {Key: "foo//bar"},
- },
- },
- }
-
- for name, test := range tests {
- t.Run(name, func(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- store := makeStorage(t, test.entries...)
-
- actualEntries := []*logical.StorageEntry{}
- f := func(entry *logical.StorageEntry) error {
- actualEntries = append(actualEntries, entry)
- return nil
- }
-
- err := WalkLogicalStorage(ctx, store, f)
- if err != nil {
- t.Fatalf("Failed to walk storage: %s", err)
- }
-
- if !reflect.DeepEqual(actualEntries, test.entries) {
- t.Fatalf("Actual: %#v\nExpected: %#v", actualEntries, test.entries)
- }
- })
- }
-}
-
-func makeStorage(t *testing.T, entries ...*logical.StorageEntry) *logical.InmemStorage {
- t.Helper()
-
- ctx := context.Background()
-
- store := new(logical.InmemStorage)
-
- for _, entry := range entries {
- err := store.Put(ctx, entry)
- if err != nil {
- t.Fatalf("Unable to load test storage: %s", err)
- }
- }
-
- return store
-}
-
-func leaseLimitFieldData(limit string) *framework.FieldData {
- raw := make(map[string]interface{})
- raw["limit"] = limit
- return &framework.FieldData{
- Raw: raw,
- Schema: map[string]*framework.FieldSchema{
- "limit": {
- Type: framework.TypeString,
- Default: "",
- Description: "limit return results",
- },
- },
- }
-}
-
-func TestProcessLimit(t *testing.T) {
- testCases := []struct {
- d *framework.FieldData
- expectReturnAll bool
- expectLimit int
- expectErr bool
- }{
- {
- d: leaseLimitFieldData("500"),
- expectReturnAll: false,
- expectLimit: 500,
- expectErr: false,
- },
- {
- d: leaseLimitFieldData(""),
- expectReturnAll: false,
- expectLimit: MaxIrrevocableLeasesToReturn,
- expectErr: false,
- },
- {
- d: leaseLimitFieldData("none"),
- expectReturnAll: true,
- expectLimit: 10000,
- expectErr: false,
- },
- {
- d: leaseLimitFieldData("NoNe"),
- expectReturnAll: true,
- expectLimit: 10000,
- expectErr: false,
- },
- {
- d: leaseLimitFieldData("hello_world"),
- expectReturnAll: false,
- expectLimit: 0,
- expectErr: true,
- },
- {
- d: leaseLimitFieldData("0"),
- expectReturnAll: false,
- expectLimit: 0,
- expectErr: true,
- },
- {
- d: leaseLimitFieldData("-1"),
- expectReturnAll: false,
- expectLimit: 0,
- expectErr: true,
- },
- }
-
- for i, tc := range testCases {
- returnAll, limit, err := processLimit(tc.d)
-
- if returnAll != tc.expectReturnAll {
- t.Errorf("bad return all for test case %d. expected %t, got %t", i, tc.expectReturnAll, returnAll)
- }
- if limit != tc.expectLimit {
- t.Errorf("bad limit for test case %d. expected %d, got %d", i, tc.expectLimit, limit)
- }
-
- haveErr := err != nil
- if haveErr != tc.expectErr {
- t.Errorf("bad error status for test case %d. expected error: %t, got error: %t", i, tc.expectErr, haveErr)
- if err != nil {
- t.Errorf("error was: %v", err)
- }
- }
- }
-}
-
-func TestSystemBackend_Loggers(t *testing.T) {
- testCases := []struct {
- level string
- expectedLevel string
- expectError bool
- }{
- {
- "trace",
- "trace",
- false,
- },
- {
- "debug",
- "debug",
- false,
- },
- {
- "notice",
- "info",
- false,
- },
- {
- "info",
- "info",
- false,
- },
- {
- "warn",
- "warn",
- false,
- },
- {
- "warning",
- "warn",
- false,
- },
- {
- "err",
- "error",
- false,
- },
- {
- "error",
- "error",
- false,
- },
- {
- "",
- "info",
- true,
- },
- {
- "invalid",
- "",
- true,
- },
- }
-
- for _, tc := range testCases {
- tc := tc
-
- t.Run(fmt.Sprintf("all-loggers-%s", tc.level), func(t *testing.T) {
- t.Parallel()
-
- core, b, _ := testCoreSystemBackend(t)
- // Test core overrides logging level outside of config,
- // an initial delete will ensure that we an initial read
- // to get expected values is based off of config and not
- // the test override that is hidden from this test
- req := &logical.Request{
- Path: "loggers",
- Operation: logical.DeleteOperation,
- }
-
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- req = &logical.Request{
- Path: "loggers",
- Operation: logical.ReadOperation,
- }
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- initialLoggers := resp.Data
-
- req = &logical.Request{
- Path: "loggers",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "level": tc.level,
- },
- }
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- respIsError := resp != nil && resp.IsError()
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- if err != nil || (!tc.expectError && respIsError) {
- t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
- }
-
- if tc.expectError && !respIsError {
- t.Fatalf("expected response error, resp: %#v", resp)
- }
-
- if !tc.expectError {
- req = &logical.Request{
- Path: "loggers",
- Operation: logical.ReadOperation,
- }
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- for _, logger := range core.allLoggers {
- loggerName := logger.Name()
- levelRaw, ok := resp.Data[loggerName]
-
- if !ok {
- t.Errorf("logger %q not found in response", loggerName)
- }
-
- if levelStr := levelRaw.(string); levelStr != tc.expectedLevel {
- t.Errorf("unexpected level of logger %q, expected: %s, actual: %s", loggerName, tc.expectedLevel, levelStr)
- }
- }
- }
-
- req = &logical.Request{
- Path: "loggers",
- Operation: logical.DeleteOperation,
- }
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- req = &logical.Request{
- Path: "loggers",
- Operation: logical.ReadOperation,
- }
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
- }
-
- schema.ValidateResponse(
- t,
- schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
- resp,
- true,
- )
-
- for _, logger := range core.allLoggers {
- loggerName := logger.Name()
- levelRaw, currentOk := resp.Data[loggerName]
- initialLevelRaw, initialOk := initialLoggers[loggerName]
-
- if !currentOk || !initialOk {
- t.Errorf("logger %q not found", loggerName)
- }
-
- levelStr := levelRaw.(string)
- initialLevelStr := initialLevelRaw.(string)
- if levelStr != initialLevelStr {
- t.Errorf("expected level of logger %q to match original config, expected: %s, actual: %s", loggerName, initialLevelStr, levelStr)
- }
- }
- })
- }
-}
-
-func TestSystemBackend_LoggersByName(t *testing.T) {
- testCases := []struct {
- logger string
- level string
- expectedLevel string
- expectWriteError bool
- expectDeleteError bool
- }{
- {
- "core",
- "trace",
- "trace",
- false,
- false,
- },
- {
- "token",
- "debug",
- "debug",
- false,
- false,
- },
- {
- "audit",
- "notice",
- "info",
- false,
- false,
- },
- {
- "expiration",
- "info",
- "info",
- false,
- false,
- },
- {
- "policy",
- "warn",
- "warn",
- false,
- false,
- },
- {
- "activity",
- "warning",
- "warn",
- false,
- false,
- },
- {
- "identity",
- "err",
- "error",
- false,
- false,
- },
- {
- "rollback",
- "error",
- "error",
- false,
- false,
- },
- {
- "system",
- "",
- "does-not-matter",
- true,
- false,
- },
- {
- "quotas",
- "invalid",
- "does-not-matter",
- true,
- false,
- },
- {
- "",
- "info",
- "does-not-matter",
- true,
- true,
- },
- {
- "does_not_exist",
- "error",
- "does-not-matter",
- true,
- true,
- },
- }
-
- for _, tc := range testCases {
- tc := tc
-
- t.Run(fmt.Sprintf("loggers-by-name-%s", tc.logger), func(t *testing.T) {
- t.Parallel()
-
- core, _, _ := TestCoreUnsealedWithConfig(t, &CoreConfig{
- Logger: logging.NewVaultLogger(hclog.Trace),
- })
- b := core.systemBackend
-
- // Test core overrides logging level outside of config,
- // an initial delete will ensure that we an initial read
- // to get expected values is based off of config and not
- // the test override that is hidden from this test
- req := &logical.Request{
- Path: "loggers",
- Operation: logical.DeleteOperation,
- }
-
- resp, err := b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
- }
-
- req = &logical.Request{
- Path: "loggers",
- Operation: logical.ReadOperation,
- }
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
- }
-
- initialLoggers := resp.Data
-
- req = &logical.Request{
- Path: fmt.Sprintf("loggers/%s", tc.logger),
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "level": tc.level,
- },
- }
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- respIsError := resp != nil && resp.IsError()
-
- if err != nil || (!tc.expectWriteError && respIsError) {
- t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
- }
-
- if tc.expectWriteError && !respIsError {
- t.Fatalf("expected response error, resp: %#v", resp)
- }
-
- if !tc.expectWriteError {
- req = &logical.Request{
- Path: "loggers",
- Operation: logical.ReadOperation,
- }
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
- }
-
- for _, logger := range core.allLoggers {
- loggerName := logger.Name()
- levelRaw, currentOk := resp.Data[loggerName]
- initialLevelRaw, initialOk := initialLoggers[loggerName]
-
- if !currentOk || !initialOk {
- t.Errorf("logger %q not found", loggerName)
- }
-
- levelStr := levelRaw.(string)
- initialLevelStr := initialLevelRaw.(string)
-
- if loggerName == tc.logger && levelStr != tc.expectedLevel {
- t.Fatalf("expected logger %q to be %q, actual: %s", loggerName, tc.expectedLevel, levelStr)
- }
-
- if loggerName != tc.logger && levelStr != initialLevelStr {
- t.Errorf("expected level of logger %q to be unchanged, exepcted: %s, actual: %s", loggerName, initialLevelStr, levelStr)
- }
- }
- }
-
- req = &logical.Request{
- Path: fmt.Sprintf("loggers/%s", tc.logger),
- Operation: logical.DeleteOperation,
- }
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- respIsError = resp != nil && resp.IsError()
-
- if err != nil || (!tc.expectDeleteError && respIsError) {
- t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
- }
-
- if tc.expectDeleteError && !respIsError {
- t.Fatalf("expected response error, resp: %#v", resp)
- }
-
- if !tc.expectDeleteError {
- req = &logical.Request{
- Path: fmt.Sprintf("loggers/%s", tc.logger),
- Operation: logical.ReadOperation,
- }
-
- resp, err = b.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
- }
-
- currentLevel, ok := resp.Data[tc.logger].(string)
- if !ok {
- t.Fatalf("expected resp to include %q, resp: %#v", tc.logger, resp)
- }
-
- initialLevel, ok := initialLoggers[tc.logger].(string)
- if !ok {
- t.Fatalf("expected initial loggers to include %q, resp: %#v", tc.logger, initialLoggers)
- }
-
- if currentLevel != initialLevel {
- t.Errorf("expected level of logger %q to match original config, expected: %s, actual: %s", tc.logger, initialLevel, currentLevel)
- }
- }
- })
- }
-}
-
-func TestSortVersionedPlugins(t *testing.T) {
- versionedPlugin := func(typ consts.PluginType, name string, version string, builtin bool) pluginutil.VersionedPlugin {
- return pluginutil.VersionedPlugin{
- Type: typ.String(),
- Name: name,
- Version: version,
- SHA256: "",
- Builtin: builtin,
- SemanticVersion: func() *semver.Version {
- if version != "" {
- return semver.Must(semver.NewVersion(version))
- }
-
- return semver.Must(semver.NewVersion("0.0.0"))
- }(),
- }
- }
-
- differingTypes := []pluginutil.VersionedPlugin{
- versionedPlugin(consts.PluginTypeSecrets, "c", "1.0.0", false),
- versionedPlugin(consts.PluginTypeDatabase, "c", "1.0.0", false),
- versionedPlugin(consts.PluginTypeCredential, "c", "1.0.0", false),
- }
- differingNames := []pluginutil.VersionedPlugin{
- versionedPlugin(consts.PluginTypeCredential, "c", "1.0.0", false),
- versionedPlugin(consts.PluginTypeCredential, "b", "1.0.0", false),
- versionedPlugin(consts.PluginTypeCredential, "a", "1.0.0", false),
- }
- differingVersions := []pluginutil.VersionedPlugin{
- versionedPlugin(consts.PluginTypeCredential, "c", "10.0.0", false),
- versionedPlugin(consts.PluginTypeCredential, "c", "2.0.1", false),
- versionedPlugin(consts.PluginTypeCredential, "c", "2.1.0", false),
- }
- versionedUnversionedAndBuiltin := []pluginutil.VersionedPlugin{
- versionedPlugin(consts.PluginTypeCredential, "c", "1.0.0", false),
- versionedPlugin(consts.PluginTypeCredential, "c", "", false),
- versionedPlugin(consts.PluginTypeCredential, "c", "1.0.0", true),
- }
-
- for name, tc := range map[string][]pluginutil.VersionedPlugin{
- "ascending types": differingTypes,
- "ascending names": differingNames,
- "ascending versions": differingVersions,
- // Include differing versions twice so we can test out equality too.
- "differing types, names and versions": append(differingTypes,
- append(differingNames,
- append(differingVersions, differingVersions...)...)...),
- "mix of unversioned, versioned, and builtin": versionedUnversionedAndBuiltin,
- } {
- t.Run(name, func(t *testing.T) {
- sortVersionedPlugins(tc)
- for i := 1; i < len(tc); i++ {
- previous := tc[i-1]
- current := tc[i]
- if current.Type > previous.Type {
- continue
- }
- if current.Name > previous.Name {
- continue
- }
- if current.SemanticVersion.GreaterThan(previous.SemanticVersion) {
- continue
- }
- if current.Type == previous.Type && current.Name == previous.Name && current.SemanticVersion.Equal(previous.SemanticVersion) {
- continue
- }
-
- t.Fatalf("versioned plugins at index %d and %d were not properly sorted: %+v, %+v", i-1, i, previous, current)
- }
- })
- }
-}
-
-func TestValidateVersion(t *testing.T) {
- b := testSystemBackend(t).(*SystemBackend)
- k8sAuthBuiltin := versions.GetBuiltinVersion(consts.PluginTypeCredential, "kubernetes")
-
- for name, tc := range map[string]struct {
- pluginName string
- pluginVersion string
- pluginType consts.PluginType
- expectLogicalError string
- expectedVersion string
- }{
- "default, nothing in nothing out": {"kubernetes", "", consts.PluginTypeCredential, "", ""},
- "builtin specified, empty out": {"kubernetes", k8sAuthBuiltin, consts.PluginTypeCredential, "", ""},
- "not canonical is ok": {"kubernetes", "1.0.0", consts.PluginTypeCredential, "", "v1.0.0"},
- "not a semantic version, error": {"kubernetes", "not-a-version", consts.PluginTypeCredential, "not a valid semantic version", ""},
- "can't select non-builtin token": {"token", "v1.0.0", consts.PluginTypeCredential, "cannot select non-builtin version", ""},
- "can't select non-builtin identity": {"identity", "v1.0.0", consts.PluginTypeSecrets, "cannot select non-builtin version", ""},
- } {
- t.Run(name, func(t *testing.T) {
- version, resp, err := b.validateVersion(context.Background(), tc.pluginVersion, tc.pluginName, tc.pluginType)
- if err != nil {
- t.Fatal(err)
- }
- if tc.expectLogicalError != "" {
- if resp == nil || !resp.IsError() || resp.Error() == nil {
- t.Errorf("expected logical error but got none, resp: %#v", resp)
- }
- if !strings.Contains(resp.Error().Error(), tc.expectLogicalError) {
- t.Errorf("expected logical error to contain %q, but got: %s", tc.expectLogicalError, resp.Error())
- }
- } else if version != tc.expectedVersion {
- t.Errorf("expected version %q but got %q", tc.expectedVersion, version)
- }
- })
- }
-}
-
-func TestValidateVersion_HelpfulErrorWhenBuiltinOverridden(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- tempDir, err := filepath.EvalSymlinks(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- core.pluginCatalog.directory = tempDir
- b := core.systemBackend
-
- // Shadow a builtin and test getting a helpful error back.
- file, err := ioutil.TempFile(tempDir, "temp")
- if err != nil {
- t.Fatal(err)
- }
- defer file.Close()
-
- command := filepath.Base(file.Name())
- err = core.pluginCatalog.Set(context.Background(), "kubernetes", consts.PluginTypeCredential, "", command, nil, nil, nil)
- if err != nil {
- t.Fatal(err)
- }
-
- // When we validate the version now, we should get a special error message
- // about why the builtin isn't there.
- k8sAuthBuiltin := versions.GetBuiltinVersion(consts.PluginTypeCredential, "kubernetes")
- _, resp, err := b.validateVersion(context.Background(), k8sAuthBuiltin, "kubernetes", consts.PluginTypeCredential)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || !resp.IsError() || resp.Error() == nil {
- t.Errorf("expected logical error but got none, resp: %#v", resp)
- }
- if !strings.Contains(resp.Error().Error(), "overridden by an unversioned plugin of the same name") {
- t.Errorf("expected logical error to contain overridden message, but got: %s", resp.Error())
- }
-}
-
-func TestCanUnseal_WithNonExistentBuiltinPluginVersion_InMountStorage(t *testing.T) {
- core, keys, _ := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- testCases := []struct {
- pluginName string
- pluginType consts.PluginType
- mountTable string
- }{
- {"consul", consts.PluginTypeSecrets, "mounts"},
- {"approle", consts.PluginTypeCredential, "auth"},
- }
- readMountConfig := func(pluginName, mountTable string) map[string]interface{} {
- t.Helper()
- req := logical.TestRequest(t, logical.ReadOperation, mountTable+"/"+pluginName)
- resp, err := core.systemBackend.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- return resp.Data
- }
-
- for _, tc := range testCases {
- req := logical.TestRequest(t, logical.UpdateOperation, tc.mountTable+"/"+tc.pluginName)
- req.Data["type"] = tc.pluginName
- req.Data["config"] = map[string]interface{}{
- "default_lease_ttl": "35m",
- "max_lease_ttl": "45m",
- "plugin_version": versions.GetBuiltinVersion(tc.pluginType, tc.pluginName),
- }
-
- resp, err := core.systemBackend.HandleRequest(ctx, req)
- if err != nil {
- t.Fatalf("err: %v, resp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- config := readMountConfig(tc.pluginName, tc.mountTable)
- pluginVersion, ok := config["plugin_version"]
- if !ok || pluginVersion != "" {
- t.Fatalf("expected empty plugin version in config: %#v", config)
- }
-
- // Directly store plugin version in mount entry, so we can then simulate
- // an upgrade from 1.12.1 to 1.12.2 by sealing and unsealing.
- const nonExistentBuiltinVersion = "v1.0.0+builtin"
- var mountEntry *MountEntry
- if tc.mountTable == "mounts" {
- mountEntry, err = core.mounts.find(ctx, tc.pluginName+"/")
- } else {
- mountEntry, err = core.auth.find(ctx, tc.pluginName+"/")
- }
- if err != nil {
- t.Fatal(err)
- }
- if mountEntry == nil {
- t.Fatal()
- }
- mountEntry.Version = nonExistentBuiltinVersion
- err = core.persistMounts(ctx, core.mounts, &mountEntry.Local)
- if err != nil {
- t.Fatal(err)
- }
-
- config = readMountConfig(tc.pluginName, tc.mountTable)
- pluginVersion, ok = config["plugin_version"]
- if !ok || pluginVersion != nonExistentBuiltinVersion {
- t.Fatalf("expected plugin version %s but was %s, config: %#v", nonExistentBuiltinVersion, pluginVersion, config)
- }
- }
-
- err := TestCoreSeal(core)
- if err != nil {
- t.Fatal(err)
- }
- for _, key := range keys {
- if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- for _, tc := range testCases {
- // Storage should have been upgraded during the unseal, so plugin version
- // should be empty again.
- config := readMountConfig(tc.pluginName, tc.mountTable)
- pluginVersion, ok := config["plugin_version"]
- if !ok || pluginVersion != "" {
- t.Errorf("expected empty plugin version in config: %#v", config)
- }
- }
-}
-
-func TestSystemBackend_ReadExperiments(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
-
- for name, tc := range map[string][]string{
- "no experiments enabled": {},
- "one experiment enabled": {experiments.VaultExperimentEventsAlpha1},
- } {
- t.Run(name, func(t *testing.T) {
- // Set the enabled experiments.
- c.experiments = tc
-
- req := logical.TestRequest(t, logical.ReadOperation, "experiments")
- resp, err := c.systemBackend.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatal("Expected a response")
- }
- if !reflect.DeepEqual(experiments.ValidExperiments(), resp.Data["available"]) {
- t.Fatalf("Expected %v but got %v", experiments.ValidExperiments(), resp.Data["available"])
- }
- if !reflect.DeepEqual(tc, resp.Data["enabled"]) {
- t.Fatal("No experiments should be enabled by default")
- }
- })
- }
-}
diff --git a/vault/login_mfa_test.go b/vault/login_mfa_test.go
deleted file mode 100644
index 1b4244003..000000000
--- a/vault/login_mfa_test.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "strings"
- "testing"
-)
-
-func TestParseFactors(t *testing.T) {
- testcases := []struct {
- name string
- invalidMFAHeaderVal []string
- expectedError string
- }{
- {
- "two headers with passcode",
- []string{"passcode", "foo"},
- "found multiple passcodes for the same MFA method",
- },
- {
- "single header with passcode=",
- []string{"passcode="},
- "invalid passcode",
- },
- {
- "single invalid header",
- []string{"foo="},
- "found an invalid MFA cred",
- },
- {
- "single header equal char",
- []string{"=="},
- "found an invalid MFA cred",
- },
- {
- "two headers with passcode=",
- []string{"passcode=foo", "foo"},
- "found multiple passcodes for the same MFA method",
- },
- {
- "two headers invalid name",
- []string{"passcode=foo", "passcode=bar"},
- "found multiple passcodes for the same MFA method",
- },
- {
- "two headers, two invalid",
- []string{"foo", "bar"},
- "found multiple passcodes for the same MFA method",
- },
- }
- for _, tc := range testcases {
- t.Run(tc.name, func(t *testing.T) {
- _, err := parseMfaFactors(tc.invalidMFAHeaderVal)
- if err == nil {
- t.Fatal("nil error returned")
- }
- if !strings.Contains(err.Error(), tc.expectedError) {
- t.Fatalf("expected %s, got %v", tc.expectedError, err)
- }
- })
- }
-}
diff --git a/vault/mfa_auth_resp_priority_queue_test.go b/vault/mfa_auth_resp_priority_queue_test.go
deleted file mode 100644
index 61274aecc..000000000
--- a/vault/mfa_auth_resp_priority_queue_test.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "testing"
- "time"
-
- "github.com/hashicorp/go-uuid"
- "github.com/hashicorp/vault/sdk/queue"
-)
-
-// some tests rely on the ordering of items from this method
-func testCases() (tc []*MFACachedAuthResponse) {
- // create a slice of items with times offest by these seconds
- for _, m := range []time.Duration{
- 5,
- 183600, // 51 hours
- 15, // 15 seconds
- 45, // 45 seconds
- 900, // 15 minutes
- 360, // 6 minutes
- 7200, // 2 hours
- 183600, // 51 hours
- 7201, // 2 hours, 1 second
- 115200, // 32 hours
- 1209600, // 2 weeks
- } {
- n := time.Now()
- ft := n.Add(time.Second * m)
- uid, err := uuid.GenerateUUID()
- if err != nil {
- continue
- }
- tc = append(tc, &MFACachedAuthResponse{
- TimeOfStorage: ft,
- RequestID: uid,
- })
- }
- return
-}
-
-func TestLoginMFAPriorityQueue_PushPopByKey(t *testing.T) {
- pq := NewLoginMFAPriorityQueue()
-
- if pq.Len() != 0 {
- t.Fatalf("expected new queue to have zero size, got (%d)", pq.Len())
- }
-
- tc := testCases()
- tcl := len(tc)
- for _, i := range tc {
- if err := pq.Push(i); err != nil {
- t.Fatal(err)
- }
- }
-
- if pq.Len() != tcl {
- t.Fatalf("error adding items, expected (%d) items, got (%d)", tcl, pq.Len())
- }
-
- item, err := pq.PopByKey(tc[0].RequestID)
- if err != nil {
- t.Fatalf("error popping item: %s", err)
- }
- if tc[0].TimeOfStorage != item.TimeOfStorage {
- t.Fatalf("expected tc[0] and popped item to match, got (%v) and (%v)", tc[0].TimeOfStorage, item.TimeOfStorage)
- }
-
- // push item with duplicate key
- dErr := pq.Push(tc[1])
- if dErr != queue.ErrDuplicateItem {
- t.Fatal(err)
- }
- // push item with no key
- tc[2].RequestID = ""
- kErr := pq.Push(tc[2])
- if kErr != nil && kErr.Error() != "error adding item: Item Key is required" {
- t.Fatal(kErr)
- }
-
- // check nil,nil error for not found
- i, err := pq.PopByKey("empty")
- if err != nil && i != nil {
- t.Fatalf("expected nil error for PopByKey of non-existing key, got: %s", err)
- }
-}
-
-func TestLoginMFARemoveStaleEntries(t *testing.T) {
- pq := NewLoginMFAPriorityQueue()
-
- tc := testCases()
- for _, i := range tc {
- if err := pq.Push(i); err != nil {
- t.Fatal(err)
- }
- }
-
- cutoffTime := time.Now().Add(371 * time.Second)
- timeout := time.Now().Add(5 * time.Second)
- for {
- if time.Now().After(timeout) {
- break
- }
- pq.RemoveExpiredMfaAuthResponse(defaultMFAAuthResponseTTL, cutoffTime)
- }
-
- if pq.Len() != 8 {
- t.Fatalf("failed to remove %d stale entries", pq.Len())
- }
-}
diff --git a/vault/mount_test.go b/vault/mount_test.go
deleted file mode 100644
index 45856d6f3..000000000
--- a/vault/mount_test.go
+++ /dev/null
@@ -1,1028 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "encoding/json"
- "reflect"
- "strings"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/helper/testhelpers/corehelpers"
-
- "github.com/armon/go-metrics"
- "github.com/go-test/deep"
- "github.com/hashicorp/vault/audit"
- "github.com/hashicorp/vault/helper/metricsutil"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/helper/versions"
- "github.com/hashicorp/vault/sdk/helper/compressutil"
- "github.com/hashicorp/vault/sdk/helper/jsonutil"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestMount_ReadOnlyViewDuringMount(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- c.logicalBackends["noop"] = func(ctx context.Context, config *logical.BackendConfig) (logical.Backend, error) {
- err := config.StorageView.Put(ctx, &logical.StorageEntry{
- Key: "bar",
- Value: []byte("baz"),
- })
- if err == nil || !strings.Contains(err.Error(), logical.ErrSetupReadOnly.Error()) {
- t.Fatalf("expected a read-only error")
- }
- return &NoopBackend{}, nil
- }
-
- me := &MountEntry{
- Table: mountTableType,
- Path: "foo",
- Type: "noop",
- }
- err := c.mount(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestLogicalMountMetrics(t *testing.T) {
- c, _, _, _ := TestCoreUnsealedWithMetrics(t)
- c.logicalBackends["noop"] = func(ctx context.Context, config *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{
- BackendType: logical.TypeLogical,
- }, nil
- }
- mountKeyName := "core.mount_table.num_entries.type|logical||local|false||"
- mountMetrics := &c.metricsHelper.LoopMetrics.Metrics
- loadMetric, ok := mountMetrics.Load(mountKeyName)
- var numEntriesMetric metricsutil.GaugeMetric = loadMetric.(metricsutil.GaugeMetric)
-
- // 3 default nonlocal logical backends
- if !ok || numEntriesMetric.Value != 3 {
- t.Fatalf("Auth values should be: %+v", numEntriesMetric)
- }
- me := &MountEntry{
- Table: mountTableType,
- Path: "foo",
- Type: "noop",
- }
- err := c.mount(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- mountMetrics = &c.metricsHelper.LoopMetrics.Metrics
- loadMetric, ok = mountMetrics.Load(mountKeyName)
- numEntriesMetric = loadMetric.(metricsutil.GaugeMetric)
- if !ok || numEntriesMetric.Value != 4 {
- t.Fatalf("mount metrics for num entries do not match true values")
- }
- if len(numEntriesMetric.Key) != 3 ||
- numEntriesMetric.Key[0] != "core" ||
- numEntriesMetric.Key[1] != "mount_table" ||
- numEntriesMetric.Key[2] != "num_entries" {
- t.Fatalf("mount metrics for num entries have wrong key")
- }
- if len(numEntriesMetric.Labels) != 2 ||
- numEntriesMetric.Labels[0].Name != "type" ||
- numEntriesMetric.Labels[0].Value != "logical" ||
- numEntriesMetric.Labels[1].Name != "local" ||
- numEntriesMetric.Labels[1].Value != "false" {
- t.Fatalf("mount metrics for num entries have wrong labels")
- }
- mountSizeKeyName := "core.mount_table.size.type|logical||local|false||"
- loadMetric, ok = mountMetrics.Load(mountSizeKeyName)
- sizeMetric := loadMetric.(metricsutil.GaugeMetric)
-
- if !ok {
- t.Fatalf("mount metrics for size do not match exist")
- }
- if len(sizeMetric.Key) != 3 ||
- sizeMetric.Key[0] != "core" ||
- sizeMetric.Key[1] != "mount_table" ||
- sizeMetric.Key[2] != "size" {
- t.Fatalf("mount metrics for size have wrong key")
- }
- if len(sizeMetric.Labels) != 2 ||
- sizeMetric.Labels[0].Name != "type" ||
- sizeMetric.Labels[0].Value != "logical" ||
- sizeMetric.Labels[1].Name != "local" ||
- sizeMetric.Labels[1].Value != "false" {
- t.Fatalf("mount metrics for size have wrong labels")
- }
-}
-
-func TestCore_DefaultMountTable(t *testing.T) {
- c, keys, _ := TestCoreUnsealed(t)
- verifyDefaultTable(t, c.mounts, 4)
-
- // Start a second core with same physical
- inmemSink := metrics.NewInmemSink(1000000*time.Hour, 2000000*time.Hour)
- conf := &CoreConfig{
- Physical: c.physical,
- DisableMlock: true,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- MetricSink: metricsutil.NewClusterMetricSink("test-cluster", inmemSink),
- MetricsHelper: metricsutil.NewMetricsHelper(inmemSink, false),
- }
- c2, err := NewCore(conf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer c2.Shutdown()
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c2, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("should be unsealed")
- }
- }
-
- if diff := deep.Equal(c.mounts.sortEntriesByPath(), c2.mounts.sortEntriesByPath()); len(diff) > 0 {
- t.Fatalf("mismatch: %v", diff)
- }
-}
-
-func TestCore_Mount(t *testing.T) {
- c, keys, _ := TestCoreUnsealed(t)
- me := &MountEntry{
- Table: mountTableType,
- Path: "foo",
- Type: "kv",
- }
- err := c.mount(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- match := c.router.MatchingMount(namespace.RootContext(nil), "foo/bar")
- if match != "foo/" {
- t.Fatalf("missing mount")
- }
-
- inmemSink := metrics.NewInmemSink(1000000*time.Hour, 2000000*time.Hour)
- conf := &CoreConfig{
- Physical: c.physical,
- DisableMlock: true,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- MetricSink: metricsutil.NewClusterMetricSink("test-cluster", inmemSink),
- MetricsHelper: metricsutil.NewMetricsHelper(inmemSink, false),
- }
- c2, err := NewCore(conf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer c2.Shutdown()
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c2, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("should be unsealed")
- }
- }
-
- // Verify matching mount tables
- if diff := deep.Equal(c.mounts.sortEntriesByPath(), c2.mounts.sortEntriesByPath()); len(diff) > 0 {
- t.Fatalf("mismatch: %v", diff)
- }
-}
-
-func TestCore_Mount_secrets_builtin_RunningVersion(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- me := &MountEntry{
- Table: mountTableType,
- Path: "foo",
- Type: "generic",
- }
- err := c.mount(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- match := c.router.MatchingMount(namespace.RootContext(nil), "foo/bar")
- if match != "foo/" {
- t.Fatalf("missing mount")
- }
-
- raw, _ := c.router.root.Get(match)
- // we override the running version of builtins
- if !versions.IsBuiltinVersion(raw.(*routeEntry).mountEntry.RunningVersion) {
- t.Errorf("Expected mount to have builtin version but got %s", raw.(*routeEntry).mountEntry.RunningVersion)
- }
-}
-
-// TestCore_Mount_kv_generic tests that we can successfully mount kv using the
-// kv alias "generic"
-func TestCore_Mount_kv_generic(t *testing.T) {
- c, keys, _ := TestCoreUnsealed(t)
- me := &MountEntry{
- Table: mountTableType,
- Path: "foo",
- Type: "generic",
- }
- err := c.mount(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- match := c.router.MatchingMount(namespace.RootContext(nil), "foo/bar")
- if match != "foo/" {
- t.Fatalf("missing mount")
- }
-
- inmemSink := metrics.NewInmemSink(1000000*time.Hour, 2000000*time.Hour)
- conf := &CoreConfig{
- Physical: c.physical,
- DisableMlock: true,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- MetricSink: metricsutil.NewClusterMetricSink("test-cluster", inmemSink),
- MetricsHelper: metricsutil.NewMetricsHelper(inmemSink, false),
- }
- c2, err := NewCore(conf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer c2.Shutdown()
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c2, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("should be unsealed")
- }
- }
-
- // Verify matching mount tables
- if diff := deep.Equal(c.mounts.sortEntriesByPath(), c2.mounts.sortEntriesByPath()); len(diff) > 0 {
- t.Fatalf("mismatch: %v", diff)
- }
-}
-
-// Test that the local table actually gets populated as expected with local
-// entries, and that upon reading the entries from both are recombined
-// correctly
-func TestCore_Mount_Local(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
-
- c.mounts = &MountTable{
- Type: mountTableType,
- Entries: []*MountEntry{
- {
- Table: mountTableType,
- Path: "noop/",
- Type: "kv",
- UUID: "abcd",
- Accessor: "kv-abcd",
- BackendAwareUUID: "abcde",
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- },
- {
- Table: mountTableType,
- Path: "noop2/",
- Type: "kv",
- UUID: "bcde",
- Accessor: "kv-bcde",
- BackendAwareUUID: "bcdea",
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- },
- },
- }
-
- // Both should set up successfully
- err := c.setupMounts(namespace.RootContext(nil))
- if err != nil {
- t.Fatal(err)
- }
- if len(c.mounts.Entries) != 2 {
- t.Fatalf("expected two entries, got %d", len(c.mounts.Entries))
- }
-
- rawLocal, err := c.barrier.Get(context.Background(), coreLocalMountConfigPath)
- if err != nil {
- t.Fatal(err)
- }
- if rawLocal == nil {
- t.Fatal("expected non-nil local mounts")
- }
- localMountsTable := &MountTable{}
- if err := jsonutil.DecodeJSON(rawLocal.Value, localMountsTable); err != nil {
- t.Fatal(err)
- }
- if len(localMountsTable.Entries) != 1 || localMountsTable.Entries[0].Type != "cubbyhole" {
- t.Fatalf("expected only cubbyhole entry in local mount table, got %#v", localMountsTable)
- }
-
- c.mounts.Entries[1].Local = true
- if err := c.persistMounts(context.Background(), c.mounts, nil); err != nil {
- t.Fatal(err)
- }
-
- rawLocal, err = c.barrier.Get(context.Background(), coreLocalMountConfigPath)
- if err != nil {
- t.Fatal(err)
- }
- if rawLocal == nil {
- t.Fatal("expected non-nil local mount")
- }
- localMountsTable = &MountTable{}
- if err := jsonutil.DecodeJSON(rawLocal.Value, localMountsTable); err != nil {
- t.Fatal(err)
- }
- // This requires some explanation: because we're directly munging the mount
- // table, the table initially when core unseals contains cubbyhole as per
- // above, but then we overwrite it with our own table with one local entry,
- // so we should now only expect the noop2 entry
- if len(localMountsTable.Entries) != 1 || localMountsTable.Entries[0].Path != "noop2/" {
- t.Fatalf("expected one entry in local mount table, got %#v", localMountsTable)
- }
-
- oldMounts := c.mounts
- if err := c.loadMounts(context.Background()); err != nil {
- t.Fatal(err)
- }
- compEntries := c.mounts.Entries[:0]
- // Filter out required mounts
- for _, v := range c.mounts.Entries {
- if v.Type == "kv" {
- compEntries = append(compEntries, v)
- }
- }
- c.mounts.Entries = compEntries
-
- if !reflect.DeepEqual(oldMounts, c.mounts) {
- t.Fatalf("expected\n%#v\ngot\n%#v\n", oldMounts, c.mounts)
- }
-
- if len(c.mounts.Entries) != 2 {
- t.Fatalf("expected two mount entries, got %#v", localMountsTable)
- }
-}
-
-func TestCore_FindOps(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- uuid1 := "80DA741F-3997-4179-B531-EBD7371DFA86"
- uuid2 := "0178594D-F267-445A-89A3-5B5DFC4A4C0F"
- path1 := "kv1"
- path2 := "kv2"
-
- c.mounts = &MountTable{
- Type: mountTableType,
- Entries: []*MountEntry{
- {
- Table: mountTableType,
- Path: path1,
- Type: "kv",
- UUID: "abcd",
- Accessor: "kv-abcd",
- BackendAwareUUID: uuid1,
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- },
- {
- Table: mountTableType,
- Path: path2,
- Type: "kv",
- UUID: "bcde",
- Accessor: "kv-bcde",
- BackendAwareUUID: uuid2,
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- },
- },
- }
-
- // Both should set up successfully
- if err := c.setupMounts(namespace.RootContext(nil)); err != nil {
- t.Fatal(err)
- }
-
- if len(c.mounts.Entries) != 2 {
- t.Fatalf("expected two entries, got %d", len(c.mounts.Entries))
- }
-
- // Unknown uuids/paths should return nil, nil
- entry, err := c.mounts.findByBackendUUID(namespace.RootContext(nil), "unknown")
- if err != nil || entry != nil {
- t.Fatalf("expected no errors nor matches got, error: %#v entry: %#v", err, entry)
- }
- entry, err = c.mounts.find(namespace.RootContext(nil), "unknown")
- if err != nil || entry != nil {
- t.Fatalf("expected no errors nor matches got, error: %#v entry: %#v", err, entry)
- }
-
- // Find our entry by its uuid
- entry, err = c.mounts.findByBackendUUID(namespace.RootContext(nil), uuid1)
- if err != nil || entry == nil {
- t.Fatalf("failed finding entry by uuid error: %#v entry: %#v", err, entry)
- }
- if entry.Path != path1 {
- t.Fatalf("found incorrect entry by uuid, entry should had a path of '%s': %#v", path1, entry)
- }
-
- // Find another entry by its path
- entry, err = c.mounts.find(namespace.RootContext(nil), path2)
- if err != nil || entry == nil {
- t.Fatalf("failed finding entry by path error: %#v entry: %#v", err, entry)
- }
- if entry.BackendAwareUUID != uuid2 {
- t.Fatalf("found incorrect entry by path, entry should had a uuid of '%s': %#v", uuid2, entry)
- }
-}
-
-func TestCore_Unmount(t *testing.T) {
- c, keys, _ := TestCoreUnsealed(t)
- err := c.unmount(namespace.RootContext(nil), "secret")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- match := c.router.MatchingMount(namespace.RootContext(nil), "secret/foo")
- if match != "" {
- t.Fatalf("backend present")
- }
-
- inmemSink := metrics.NewInmemSink(1000000*time.Hour, 2000000*time.Hour)
- conf := &CoreConfig{
- Physical: c.physical,
- DisableMlock: true,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- MetricSink: metricsutil.NewClusterMetricSink("test-cluster", inmemSink),
- MetricsHelper: metricsutil.NewMetricsHelper(inmemSink, false),
- }
- c2, err := NewCore(conf)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer c2.Shutdown()
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c2, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("should be unsealed")
- }
- }
-
- // Verify matching mount tables
- if diff := deep.Equal(c.mounts, c2.mounts); len(diff) > 0 {
- t.Fatalf("mismatch: %v", diff)
- }
-}
-
-func TestCore_Unmount_Cleanup(t *testing.T) {
- testCore_Unmount_Cleanup(t, false)
- testCore_Unmount_Cleanup(t, true)
-}
-
-func testCore_Unmount_Cleanup(t *testing.T, causeFailure bool) {
- noop := &NoopBackend{}
- c, _, root := TestCoreUnsealed(t)
- c.logicalBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return noop, nil
- }
-
- // Mount the noop backend
- me := &MountEntry{
- Table: mountTableType,
- Path: "test/",
- Type: "noop",
- }
- if err := c.mount(namespace.RootContext(nil), me); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Store the view
- view := c.router.MatchingStorageByAPIPath(namespace.RootContext(nil), "test/")
-
- // Inject data
- se := &logical.StorageEntry{
- Key: "plstodelete",
- Value: []byte("test"),
- }
- if err := view.Put(context.Background(), se); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Setup response
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- },
- Data: map[string]interface{}{
- "foo": "bar",
- },
- }
- noop.Response = resp
-
- // Generate leased secret
- r := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "test/foo",
- ClientToken: root,
- }
- r.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
- resp, err := c.HandleRequest(namespace.RootContext(nil), r)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- if causeFailure {
- view.(*BarrierView).setReadOnlyErr(logical.ErrSetupReadOnly)
- }
-
- // Unmount, this should cleanup
- err = c.unmount(namespace.RootContext(nil), "test/")
- switch {
- case err != nil && causeFailure:
- case err == nil && causeFailure:
- t.Fatal("expected error")
- case err != nil:
- t.Fatalf("err: %v", err)
- }
-
- // Rollback should be invoked
- if noop.Requests[1].Operation != logical.RollbackOperation {
- t.Fatalf("bad: %#v", noop.Requests)
- }
-
- // Revoke should be invoked
- if noop.Requests[2].Operation != logical.RevokeOperation {
- t.Fatalf("bad: %#v", noop.Requests)
- }
- if noop.Requests[2].Path != "foo" {
- t.Fatalf("bad: %#v", noop.Requests)
- }
-
- // View should be empty
- out, err := logical.CollectKeys(context.Background(), view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- switch {
- case len(out) == 1 && causeFailure:
- case len(out) == 0 && causeFailure:
- t.Fatal("expected a value")
- case len(out) != 0:
- t.Fatalf("bad: %#v", out)
- case !causeFailure:
- return
- }
-
- // At this point just in the failure case, check mounting
- if err := c.mount(namespace.RootContext(nil), me); err == nil {
- t.Fatal("expected error")
- } else {
- if !strings.Contains(err.Error(), "path is already in use at") {
- t.Fatalf("expected a path is already in use error, got %v", err)
- }
- }
-}
-
-func TestCore_Remount(t *testing.T) {
- c, keys, _ := TestCoreUnsealed(t)
- err := c.remountSecretsEngineCurrentNamespace(namespace.RootContext(nil), "secret", "foo", true)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- match := c.router.MatchingMount(namespace.RootContext(nil), "foo/bar")
- if match != "foo/" {
- t.Fatalf("failed remount")
- }
-
- c.sealInternal()
- for i, key := range keys {
- unseal, err := TestCoreUnseal(c, key)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if i+1 == len(keys) && !unseal {
- t.Fatalf("should be unsealed")
- }
- }
-
- match = c.router.MatchingMount(namespace.RootContext(nil), "foo/bar")
- if match != "foo/" {
- t.Fatalf("failed remount")
- }
-}
-
-func TestCore_Remount_Cleanup(t *testing.T) {
- noop := &NoopBackend{}
- c, _, root := TestCoreUnsealed(t)
- c.logicalBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return noop, nil
- }
-
- // Mount the noop backend
- me := &MountEntry{
- Table: mountTableType,
- Path: "test/",
- Type: "noop",
- }
- if err := c.mount(namespace.RootContext(nil), me); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Store the view
- view := c.router.MatchingStorageByAPIPath(namespace.RootContext(nil), "test/")
-
- // Inject data
- se := &logical.StorageEntry{
- Key: "plstokeep",
- Value: []byte("test"),
- }
- if err := view.Put(context.Background(), se); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Setup response
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- },
- Data: map[string]interface{}{
- "foo": "bar",
- },
- }
- noop.Response = resp
-
- // Generate leased secret
- r := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "test/foo",
- ClientToken: root,
- }
- r.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
- resp, err := c.HandleRequest(namespace.RootContext(nil), r)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Remount, this should cleanup
- if err := c.remountSecretsEngineCurrentNamespace(namespace.RootContext(nil), "test/", "new/", true); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Rollback should be invoked
- if noop.Requests[1].Operation != logical.RollbackOperation {
- t.Fatalf("bad: %#v", noop.Requests)
- }
-
- // Revoke should be invoked
- if noop.Requests[2].Operation != logical.RevokeOperation {
- t.Fatalf("bad: %#v", noop.Requests)
- }
- if noop.Requests[2].Path != "foo" {
- t.Fatalf("bad: %#v", noop.Requests)
- }
-
- // View should not be empty
- out, err := logical.CollectKeys(context.Background(), view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(out) != 1 && out[0] != "plstokeep" {
- t.Fatalf("bad: %#v", out)
- }
-}
-
-func TestCore_Remount_Protected(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- err := c.remountSecretsEngineCurrentNamespace(namespace.RootContext(nil), "sys", "foo", true)
- if err.Error() != `cannot remount "sys/"` {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestDefaultMountTable(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- table := c.defaultMountTable()
- verifyDefaultTable(t, table, 3)
-}
-
-func TestCore_MountTable_UpgradeToTyped(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
-
- c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
- return &corehelpers.NoopAudit{
- Config: config,
- }, nil
- }
-
- me := &MountEntry{
- Table: auditTableType,
- Path: "foo",
- Type: "noop",
- }
- err := c.enableAudit(namespace.RootContext(nil), me, true)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{
- BackendType: logical.TypeCredential,
- }, nil
- }
-
- me = &MountEntry{
- Table: credentialTableType,
- Path: "foo",
- Type: "noop",
- }
- err = c.enableCredential(namespace.RootContext(nil), me)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- testCore_MountTable_UpgradeToTyped_Common(t, c, "mounts")
- testCore_MountTable_UpgradeToTyped_Common(t, c, "audits")
- testCore_MountTable_UpgradeToTyped_Common(t, c, "credentials")
-}
-
-func testCore_MountTable_UpgradeToTyped_Common(
- t *testing.T,
- c *Core,
- testType string,
-) {
- var path string
- var mt *MountTable
- switch testType {
- case "mounts":
- path = coreMountConfigPath
- mt = c.mounts
- case "audits":
- path = coreAuditConfigPath
- mt = c.audit
- case "credentials":
- path = coreAuthConfigPath
- mt = c.auth
- }
-
- // We filter out local entries here since the logic is rather dumb
- // (straight JSON comparison) and doesn't seal well with the separate
- // locations
- newEntries := mt.Entries[:0]
- for _, entry := range mt.Entries {
- if !entry.Local {
- newEntries = append(newEntries, entry)
- }
- }
- mt.Entries = newEntries
-
- // Save the expected table
- goodJson, err := json.Marshal(mt)
- if err != nil {
- t.Fatal(err)
- }
-
- // Create a pre-typed version
- mt.Type = ""
- for _, entry := range mt.Entries {
- entry.Table = ""
- }
-
- raw, err := json.Marshal(mt)
- if err != nil {
- t.Fatal(err)
- }
-
- if reflect.DeepEqual(raw, goodJson) {
- t.Fatalf("bad: values here should be different")
- }
-
- entry := &logical.StorageEntry{
- Key: path,
- Value: raw,
- }
- if err := c.barrier.Put(context.Background(), entry); err != nil {
- t.Fatal(err)
- }
-
- var persistFunc func(context.Context, *MountTable, *bool) error
-
- // It should load successfully and be upgraded and persisted
- switch testType {
- case "mounts":
- err = c.loadMounts(context.Background())
- persistFunc = c.persistMounts
- mt = c.mounts
- case "credentials":
- err = c.loadCredentials(context.Background())
- persistFunc = c.persistAuth
- mt = c.auth
- case "audits":
- err = c.loadAudits(context.Background())
- persistFunc = func(ctx context.Context, mt *MountTable, b *bool) error {
- if b == nil {
- b = new(bool)
- *b = false
- }
- return c.persistAudit(ctx, mt, *b)
- }
- mt = c.audit
- }
- if err != nil {
- t.Fatal(err)
- }
-
- entry, err = c.barrier.Get(context.Background(), path)
- if err != nil {
- t.Fatal(err)
- }
- if entry == nil {
- t.Fatal("nil value")
- }
-
- decompressedBytes, uncompressed, err := compressutil.Decompress(entry.Value)
- if err != nil {
- t.Fatal(err)
- }
-
- actual := decompressedBytes
- if uncompressed {
- actual = entry.Value
- }
-
- if strings.TrimSpace(string(actual)) != strings.TrimSpace(string(goodJson)) {
- t.Fatalf("bad: expected\n%s\nactual\n%s\n", string(goodJson), string(actual))
- }
-
- // Now try saving invalid versions
- origTableType := mt.Type
- mt.Type = "foo"
- if err := persistFunc(context.Background(), mt, nil); err == nil {
- t.Fatal("expected error")
- }
-
- if len(mt.Entries) > 0 {
- mt.Type = origTableType
- mt.Entries[0].Table = "bar"
- if err := persistFunc(context.Background(), mt, nil); err == nil {
- t.Fatal("expected error")
- }
-
- mt.Entries[0].Table = mt.Type
- if err := persistFunc(context.Background(), mt, nil); err != nil {
- t.Fatal(err)
- }
- }
-}
-
-func verifyDefaultTable(t *testing.T, table *MountTable, expected int) {
- if len(table.Entries) != expected {
- t.Fatalf("bad: %v", table.Entries)
- }
- table.sortEntriesByPath()
- for _, entry := range table.Entries {
- switch entry.Path {
- case "cubbyhole/":
- if entry.Type != "cubbyhole" {
- t.Fatalf("bad: %v", entry)
- }
- case "secret/":
- if entry.Type != "kv" {
- t.Fatalf("bad: %v", entry)
- }
- case "sys/":
- if entry.Type != "system" {
- t.Fatalf("bad: %v", entry)
- }
- if !entry.SealWrap {
- t.Fatalf("expected SealWrap to be enabled: %v", entry)
- }
- case "identity/":
- if entry.Type != "identity" {
- t.Fatalf("bad: %v", entry)
- }
- }
- if entry.Table != mountTableType {
- t.Fatalf("bad: %v", entry)
- }
- if entry.Description == "" {
- t.Fatalf("bad: %v", entry)
- }
- if entry.UUID == "" {
- t.Fatalf("bad: %v", entry)
- }
- }
-}
-
-func TestSingletonMountTableFunc(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
-
- mounts, auth := c.singletonMountTables()
-
- if len(mounts.Entries) != 2 {
- t.Fatalf("length of mounts is wrong; expected 2, got %d", len(mounts.Entries))
- }
-
- for _, entry := range mounts.Entries {
- switch entry.Type {
- case "system":
- case "identity":
- default:
- t.Fatalf("unknown type %s", entry.Type)
- }
- }
-
- if len(auth.Entries) != 1 {
- t.Fatal("length of auth is wrong")
- }
-
- if auth.Entries[0].Type != "token" {
- t.Fatal("unexpected entry type for auth")
- }
-}
-
-func TestCore_MountInitialize(t *testing.T) {
- {
- backend := &InitializableBackend{
- &NoopBackend{
- BackendType: logical.TypeLogical,
- }, false,
- }
-
- c, _, _ := TestCoreUnsealed(t)
- c.logicalBackends["initable"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return backend, nil
- }
-
- // Mount the noop backend
- me := &MountEntry{
- Table: mountTableType,
- Path: "foo/",
- Type: "initable",
- }
- if err := c.mount(namespace.RootContext(nil), me); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if !backend.isInitialized {
- t.Fatal("backend is not initialized")
- }
- }
- {
- backend := &InitializableBackend{
- &NoopBackend{
- BackendType: logical.TypeLogical,
- }, false,
- }
-
- c, _, _ := TestCoreUnsealed(t)
- c.logicalBackends["initable"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
- return backend, nil
- }
-
- c.mounts = &MountTable{
- Type: mountTableType,
- Entries: []*MountEntry{
- {
- Table: mountTableType,
- Path: "foo/",
- Type: "initable",
- UUID: "abcd",
- Accessor: "initable-abcd",
- BackendAwareUUID: "abcde",
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- },
- },
- }
-
- err := c.setupMounts(namespace.RootContext(nil))
- if err != nil {
- t.Fatal(err)
- }
-
- // run the postUnseal funcs, so that the backend will be inited
- for _, f := range c.postUnsealFuncs {
- f()
- }
-
- if !backend.isInitialized {
- t.Fatal("backend is not initialized")
- }
- }
-}
diff --git a/vault/plugin_catalog_test.go b/vault/plugin_catalog_test.go
deleted file mode 100644
index cf4cf0d38..000000000
--- a/vault/plugin_catalog_test.go
+++ /dev/null
@@ -1,692 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "crypto/sha256"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "reflect"
- "sort"
- "testing"
-
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/builtin/credential/userpass"
- "github.com/hashicorp/vault/helper/versions"
- "github.com/hashicorp/vault/plugins/database/postgresql"
- v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
- "github.com/hashicorp/vault/sdk/helper/consts"
- "github.com/hashicorp/vault/sdk/helper/pluginutil"
- backendplugin "github.com/hashicorp/vault/sdk/plugin"
-
- "github.com/hashicorp/vault/helper/builtinplugins"
-)
-
-func TestPluginCatalog_CRUD(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- tempDir, err := filepath.EvalSymlinks(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- core.pluginCatalog.directory = tempDir
-
- const pluginName = "mysql-database-plugin"
-
- // Get builtin plugin
- p, err := core.pluginCatalog.Get(context.Background(), pluginName, consts.PluginTypeDatabase, "")
- if err != nil {
- t.Fatalf("unexpected error %v", err)
- }
-
- // Get it again, explicitly specifying builtin version
- builtinVersion := versions.GetBuiltinVersion(consts.PluginTypeDatabase, pluginName)
- p2, err := core.pluginCatalog.Get(context.Background(), pluginName, consts.PluginTypeDatabase, builtinVersion)
- if err != nil {
- t.Fatalf("unexpected error %v", err)
- }
-
- expectedBuiltin := &pluginutil.PluginRunner{
- Name: pluginName,
- Type: consts.PluginTypeDatabase,
- Builtin: true,
- Version: builtinVersion,
- }
- expectedBuiltin.BuiltinFactory, _ = builtinplugins.Registry.Get(pluginName, consts.PluginTypeDatabase)
-
- if &(p.BuiltinFactory) == &(expectedBuiltin.BuiltinFactory) {
- t.Fatal("expected BuiltinFactory did not match actual")
- }
- expectedBuiltin.BuiltinFactory = nil
- p.BuiltinFactory = nil
- p2.BuiltinFactory = nil
- if !reflect.DeepEqual(p, expectedBuiltin) {
- t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", p, expectedBuiltin)
- }
- if !reflect.DeepEqual(p2, expectedBuiltin) {
- t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", p2, expectedBuiltin)
- }
-
- // Set a plugin, test overwriting a builtin plugin
- file, err := ioutil.TempFile(tempDir, "temp")
- if err != nil {
- t.Fatal(err)
- }
- defer file.Close()
-
- command := filepath.Base(file.Name())
- err = core.pluginCatalog.Set(context.Background(), pluginName, consts.PluginTypeDatabase, "", command, []string{"--test"}, []string{"FOO=BAR"}, []byte{'1'})
- if err != nil {
- t.Fatal(err)
- }
-
- // Get the plugin
- p, err = core.pluginCatalog.Get(context.Background(), pluginName, consts.PluginTypeDatabase, "")
- if err != nil {
- t.Fatalf("unexpected error %v", err)
- }
-
- // Get it again, explicitly specifying builtin version.
- // This time it should fail because it was overwritten.
- p2, err = core.pluginCatalog.Get(context.Background(), pluginName, consts.PluginTypeDatabase, builtinVersion)
- if err != nil {
- t.Fatalf("unexpected error %v", err)
- }
- if p2 != nil {
- t.Fatalf("expected no result, got: %#v", p2)
- }
-
- expected := &pluginutil.PluginRunner{
- Name: pluginName,
- Type: consts.PluginTypeDatabase,
- Command: filepath.Join(tempDir, filepath.Base(file.Name())),
- Args: []string{"--test"},
- Env: []string{"FOO=BAR"},
- Sha256: []byte{'1'},
- Builtin: false,
- Version: "",
- }
-
- if !reflect.DeepEqual(p, expected) {
- t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", p, expected)
- }
-
- // Delete the plugin
- err = core.pluginCatalog.Delete(context.Background(), pluginName, consts.PluginTypeDatabase, "")
- if err != nil {
- t.Fatalf("unexpected err: %v", err)
- }
-
- // Get builtin plugin
- p, err = core.pluginCatalog.Get(context.Background(), pluginName, consts.PluginTypeDatabase, "")
- if err != nil {
- t.Fatalf("unexpected error %v", err)
- }
-
- expectedBuiltin = &pluginutil.PluginRunner{
- Name: pluginName,
- Type: consts.PluginTypeDatabase,
- Builtin: true,
- Version: versions.GetBuiltinVersion(consts.PluginTypeDatabase, pluginName),
- }
- expectedBuiltin.BuiltinFactory, _ = builtinplugins.Registry.Get(pluginName, consts.PluginTypeDatabase)
-
- if &(p.BuiltinFactory) == &(expectedBuiltin.BuiltinFactory) {
- t.Fatal("expected BuiltinFactory did not match actual")
- }
- expectedBuiltin.BuiltinFactory = nil
- p.BuiltinFactory = nil
- if !reflect.DeepEqual(p, expectedBuiltin) {
- t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", p, expectedBuiltin)
- }
-}
-
-func TestPluginCatalog_VersionedCRUD(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- tempDir, err := filepath.EvalSymlinks(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- core.pluginCatalog.directory = tempDir
-
- // Set a versioned plugin.
- file, err := ioutil.TempFile(tempDir, "temp")
- if err != nil {
- t.Fatal(err)
- }
- defer file.Close()
-
- const name = "mysql-database-plugin"
- const version = "1.0.0"
- command := fmt.Sprintf("%s", filepath.Base(file.Name()))
- err = core.pluginCatalog.Set(context.Background(), name, consts.PluginTypeDatabase, version, command, []string{"--test"}, []string{"FOO=BAR"}, []byte{'1'})
- if err != nil {
- t.Fatal(err)
- }
-
- // Get the plugin
- plugin, err := core.pluginCatalog.Get(context.Background(), name, consts.PluginTypeDatabase, version)
- if err != nil {
- t.Fatalf("unexpected error %v", err)
- }
-
- expected := &pluginutil.PluginRunner{
- Name: name,
- Type: consts.PluginTypeDatabase,
- Version: version,
- Command: filepath.Join(tempDir, filepath.Base(file.Name())),
- Args: []string{"--test"},
- Env: []string{"FOO=BAR"},
- Sha256: []byte{'1'},
- Builtin: false,
- }
-
- if !reflect.DeepEqual(plugin, expected) {
- t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", plugin, expected)
- }
-
- // Also get the builtin version to check we can still access that.
- builtinVersion := versions.GetBuiltinVersion(consts.PluginTypeDatabase, name)
- plugin, err = core.pluginCatalog.Get(context.Background(), name, consts.PluginTypeDatabase, builtinVersion)
- if err != nil {
- t.Fatalf("unexpected error %v", err)
- }
-
- expected = &pluginutil.PluginRunner{
- Name: name,
- Type: consts.PluginTypeDatabase,
- Version: builtinVersion,
- Builtin: true,
- }
-
- // Check by marshalling to JSON to avoid messing with BuiltinFactory function field.
- expectedBytes, err := json.Marshal(expected)
- if err != nil {
- t.Fatal(err)
- }
- actualBytes, err := json.Marshal(plugin)
- if err != nil {
- t.Fatal(err)
- }
- if string(expectedBytes) != string(actualBytes) {
- t.Fatalf("expected %s, got %s", string(expectedBytes), string(actualBytes))
- }
- if !plugin.Builtin {
- t.Fatal("expected builtin true but got false")
- }
-
- // Delete the plugin
- err = core.pluginCatalog.Delete(context.Background(), name, consts.PluginTypeDatabase, version)
- if err != nil {
- t.Fatalf("unexpected err: %v", err)
- }
-
- // Get plugin - should fail
- plugin, err = core.pluginCatalog.Get(context.Background(), name, consts.PluginTypeDatabase, version)
- if err != nil {
- t.Fatal(err)
- }
- if plugin != nil {
- t.Fatalf("expected no plugin with this version to be in the catalog, but found %+v", plugin)
- }
-}
-
-func TestPluginCatalog_List(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- tempDir, err := filepath.EvalSymlinks(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- core.pluginCatalog.directory = tempDir
-
- // Get builtin plugins and sort them
- builtinKeys := builtinplugins.Registry.Keys(consts.PluginTypeDatabase)
- sort.Strings(builtinKeys)
-
- // List only builtin plugins
- plugins, err := core.pluginCatalog.List(context.Background(), consts.PluginTypeDatabase)
- if err != nil {
- t.Fatalf("unexpected error %v", err)
- }
- sort.Strings(plugins)
-
- if len(plugins) != len(builtinKeys) {
- t.Fatalf("unexpected length of plugin list, expected %d, got %d", len(builtinKeys), len(plugins))
- }
-
- if !reflect.DeepEqual(plugins, builtinKeys) {
- t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", plugins, builtinKeys)
- }
-
- // Set a plugin, test overwriting a builtin plugin
- file, err := ioutil.TempFile(tempDir, "temp")
- if err != nil {
- t.Fatal(err)
- }
- defer file.Close()
-
- command := filepath.Base(file.Name())
- err = core.pluginCatalog.Set(context.Background(), "mysql-database-plugin", consts.PluginTypeDatabase, "", command, []string{"--test"}, []string{}, []byte{'1'})
- if err != nil {
- t.Fatal(err)
- }
-
- // Set another plugin
- err = core.pluginCatalog.Set(context.Background(), "aaaaaaa", consts.PluginTypeDatabase, "", command, []string{"--test"}, []string{}, []byte{'1'})
- if err != nil {
- t.Fatal(err)
- }
-
- // List the plugins
- plugins, err = core.pluginCatalog.List(context.Background(), consts.PluginTypeDatabase)
- if err != nil {
- t.Fatalf("unexpected error %v", err)
- }
- sort.Strings(plugins)
-
- // plugins has a test-added plugin called "aaaaaaa" that is not built in
- if len(plugins) != len(builtinKeys)+1 {
- t.Fatalf("unexpected length of plugin list, expected %d, got %d", len(builtinKeys)+1, len(plugins))
- }
-
- // verify the first plugin is the one we just created.
- if !reflect.DeepEqual(plugins[0], "aaaaaaa") {
- t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", plugins[0], "aaaaaaa")
- }
-
- // verify the builtin plugins are correct
- if !reflect.DeepEqual(plugins[1:], builtinKeys) {
- t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", plugins[1:], builtinKeys)
- }
-}
-
-func TestPluginCatalog_ListVersionedPlugins(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- tempDir, err := filepath.EvalSymlinks(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- core.pluginCatalog.directory = tempDir
-
- // Get builtin plugins and sort them
- builtinKeys := builtinplugins.Registry.Keys(consts.PluginTypeDatabase)
- sort.Strings(builtinKeys)
-
- // List only builtin plugins
- plugins, err := core.pluginCatalog.ListVersionedPlugins(context.Background(), consts.PluginTypeDatabase)
- if err != nil {
- t.Fatalf("unexpected error %v", err)
- }
- sortVersionedPlugins(plugins)
-
- if len(plugins) != len(builtinKeys) {
- t.Fatalf("unexpected length of plugin list, expected %d, got %d", len(builtinKeys), len(plugins))
- }
-
- for i, plugin := range plugins {
- if plugin.Name != builtinKeys[i] {
- t.Fatalf("expected plugin list with names %v but got %+v", builtinKeys, plugins)
- }
- }
-
- // Set a plugin, test overwriting a builtin plugin
- file, err := ioutil.TempFile(tempDir, "temp")
- if err != nil {
- t.Fatal(err)
- }
- defer file.Close()
-
- command := filepath.Base(file.Name())
- err = core.pluginCatalog.Set(
- context.Background(),
- "mysql-database-plugin",
- consts.PluginTypeDatabase,
- "",
- command,
- []string{"--test"},
- []string{},
- []byte{'1'},
- )
- if err != nil {
- t.Fatal(err)
- }
-
- // Set another plugin, with version information
- err = core.pluginCatalog.Set(
- context.Background(),
- "aaaaaaa",
- consts.PluginTypeDatabase,
- "1.1.0",
- command,
- []string{"--test"},
- []string{},
- []byte{'1'},
- )
- if err != nil {
- t.Fatal(err)
- }
-
- // List the plugins
- plugins, err = core.pluginCatalog.ListVersionedPlugins(context.Background(), consts.PluginTypeDatabase)
- if err != nil {
- t.Fatalf("unexpected error %v", err)
- }
- sortVersionedPlugins(plugins)
-
- // plugins has a test-added plugin called "aaaaaaa" that is not built in
- if len(plugins) != len(builtinKeys)+1 {
- t.Fatalf("unexpected length of plugin list, expected %d, got %d", len(builtinKeys)+1, len(plugins))
- }
-
- // verify the first plugin is the one we just created.
- if !reflect.DeepEqual(plugins[0].Name, "aaaaaaa") {
- t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", plugins[0], "aaaaaaa")
- }
- if plugins[0].SemanticVersion == nil {
- t.Fatalf("expected non-nil semantic version for %v", plugins[0].Name)
- }
-
- // verify the builtin plugins are correct
- for i, plugin := range plugins[1:] {
- if plugin.Name != builtinKeys[i] {
- t.Fatalf("expected plugin list with names %v but got %+v", builtinKeys, plugins)
- }
- switch plugin.Name {
- case "mysql-database-plugin":
- if plugin.Builtin {
- t.Fatalf("expected %v plugin to be an unversioned external plugin", plugin)
- }
- if plugin.Version != "" {
- t.Fatalf("expected no version information for %v but got %s", plugin, plugin.Version)
- }
- default:
- if !plugin.Builtin {
- t.Fatalf("expected %v plugin to be builtin", plugin)
- }
- if !versions.IsBuiltinVersion(plugin.Version) {
- t.Fatalf("expected +builtin metadata but got %s", plugin.Version)
- }
- }
-
- if plugin.SemanticVersion == nil {
- t.Fatalf("expected non-nil semantic version for %v", plugin)
- }
- }
-}
-
-func TestPluginCatalog_ListHandlesPluginNamesWithSlashes(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- tempDir, err := filepath.EvalSymlinks(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- core.pluginCatalog.directory = tempDir
-
- file, err := ioutil.TempFile(tempDir, "temp")
- if err != nil {
- t.Fatal(err)
- }
- defer file.Close()
- command := filepath.Base(file.Name())
- ctx := context.Background()
-
- pluginsToRegister := []pluginutil.PluginRunner{
- {
- Name: "unversioned-plugin",
- },
- {
- Name: "unversioned-plugin/with-slash",
- },
- {
- Name: "unversioned-plugin/with-two/slashes",
- },
- {
- Name: "versioned-plugin",
- Version: "v1.0.0",
- },
- {
- Name: "versioned-plugin/with-slash",
- Version: "v1.0.0",
- },
- {
- Name: "versioned-plugin/with-two/slashes",
- Version: "v1.0.0",
- },
- }
- for _, entry := range pluginsToRegister {
- err = core.pluginCatalog.Set(ctx, entry.Name, consts.PluginTypeCredential, entry.Version, command, nil, nil, nil)
- if err != nil {
- t.Fatal(err)
- }
- }
-
- plugins, err := core.pluginCatalog.ListVersionedPlugins(ctx, consts.PluginTypeCredential)
- if err != nil {
- t.Fatal(err)
- }
-
- for _, expected := range pluginsToRegister {
- found := false
- for _, plugin := range plugins {
- if expected.Name == plugin.Name && expected.Version == plugin.Version {
- found = true
- break
- }
- }
-
- if !found {
- t.Errorf("Did not find %#v in %#v", expected, plugins)
- }
- }
-}
-
-func TestPluginCatalog_NewPluginClient(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- tempDir, err := filepath.EvalSymlinks(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- core.pluginCatalog.directory = tempDir
-
- if extPlugins := len(core.pluginCatalog.externalPlugins); extPlugins != 0 {
- t.Fatalf("expected externalPlugins map to be of len 0 but got %d", extPlugins)
- }
-
- // register plugins
- TestAddTestPlugin(t, core, "mux-postgres", consts.PluginTypeUnknown, "", "TestPluginCatalog_PluginMain_PostgresMultiplexed", []string{}, "")
- TestAddTestPlugin(t, core, "single-postgres-1", consts.PluginTypeUnknown, "", "TestPluginCatalog_PluginMain_Postgres", []string{}, "")
- TestAddTestPlugin(t, core, "single-postgres-2", consts.PluginTypeUnknown, "", "TestPluginCatalog_PluginMain_Postgres", []string{}, "")
-
- TestAddTestPlugin(t, core, "mux-userpass", consts.PluginTypeUnknown, "", "TestPluginCatalog_PluginMain_UserpassMultiplexed", []string{}, "")
- TestAddTestPlugin(t, core, "single-userpass-1", consts.PluginTypeUnknown, "", "TestPluginCatalog_PluginMain_Userpass", []string{}, "")
- TestAddTestPlugin(t, core, "single-userpass-2", consts.PluginTypeUnknown, "", "TestPluginCatalog_PluginMain_Userpass", []string{}, "")
-
- getKey := func(pluginName string, pluginType consts.PluginType) externalPluginsKey {
- t.Helper()
- ctx := context.Background()
- plugin, err := core.pluginCatalog.Get(ctx, pluginName, pluginType, "")
- if err != nil {
- t.Fatal(err)
- }
- if plugin == nil {
- t.Fatal("did not find " + pluginName)
- }
- key, err := makeExternalPluginsKey(plugin)
- if err != nil {
- t.Fatal(err)
- }
- return key
- }
-
- var pluginClients []*pluginClient
- // run plugins
- // run "mux-postgres" twice which will start a single plugin for 2
- // distinct connections
- c := TestRunTestPlugin(t, core, consts.PluginTypeDatabase, "mux-postgres")
- pluginClients = append(pluginClients, c)
- c = TestRunTestPlugin(t, core, consts.PluginTypeDatabase, "mux-postgres")
- pluginClients = append(pluginClients, c)
- c = TestRunTestPlugin(t, core, consts.PluginTypeDatabase, "single-postgres-1")
- pluginClients = append(pluginClients, c)
- c = TestRunTestPlugin(t, core, consts.PluginTypeDatabase, "single-postgres-2")
- pluginClients = append(pluginClients, c)
-
- // run "mux-userpass" twice which will start a single plugin for 2
- // distinct connections
- c = TestRunTestPlugin(t, core, consts.PluginTypeCredential, "mux-userpass")
- pluginClients = append(pluginClients, c)
- c = TestRunTestPlugin(t, core, consts.PluginTypeCredential, "mux-userpass")
- pluginClients = append(pluginClients, c)
- c = TestRunTestPlugin(t, core, consts.PluginTypeCredential, "single-userpass-1")
- pluginClients = append(pluginClients, c)
- c = TestRunTestPlugin(t, core, consts.PluginTypeCredential, "single-userpass-2")
- pluginClients = append(pluginClients, c)
-
- externalPlugins := core.pluginCatalog.externalPlugins
- if len(externalPlugins) != 6 {
- t.Fatalf("expected externalPlugins map to be of len 6 but got %d", len(externalPlugins))
- }
-
- // check connections map
- expectConnectionLen(t, 2, externalPlugins[getKey("mux-postgres", consts.PluginTypeDatabase)].connections)
- expectConnectionLen(t, 1, externalPlugins[getKey("single-postgres-1", consts.PluginTypeDatabase)].connections)
- expectConnectionLen(t, 1, externalPlugins[getKey("single-postgres-2", consts.PluginTypeDatabase)].connections)
- expectConnectionLen(t, 2, externalPlugins[getKey("mux-userpass", consts.PluginTypeCredential)].connections)
- expectConnectionLen(t, 1, externalPlugins[getKey("single-userpass-1", consts.PluginTypeCredential)].connections)
- expectConnectionLen(t, 1, externalPlugins[getKey("single-userpass-2", consts.PluginTypeCredential)].connections)
-
- // check multiplexing support
- expectMultiplexingSupport(t, true, externalPlugins[getKey("mux-postgres", consts.PluginTypeDatabase)].multiplexingSupport)
- expectMultiplexingSupport(t, false, externalPlugins[getKey("single-postgres-1", consts.PluginTypeDatabase)].multiplexingSupport)
- expectMultiplexingSupport(t, false, externalPlugins[getKey("single-postgres-2", consts.PluginTypeDatabase)].multiplexingSupport)
- expectMultiplexingSupport(t, true, externalPlugins[getKey("mux-userpass", consts.PluginTypeCredential)].multiplexingSupport)
- expectMultiplexingSupport(t, false, externalPlugins[getKey("single-userpass-1", consts.PluginTypeCredential)].multiplexingSupport)
- expectMultiplexingSupport(t, false, externalPlugins[getKey("single-userpass-2", consts.PluginTypeCredential)].multiplexingSupport)
-
- // cleanup all of the external plugin processes
- for _, client := range pluginClients {
- client.Close()
- }
-
- // check that externalPlugins map is cleaned up
- if len(externalPlugins) != 0 {
- t.Fatalf("expected external plugin map to be of len 0 but got %d", len(externalPlugins))
- }
-}
-
-func TestPluginCatalog_MakeExternalPluginsKey_Comparable(t *testing.T) {
- var plugins []pluginutil.PluginRunner
- hasher := sha256.New()
- hasher.Write([]byte("Some random input"))
-
- for i := 0; i < 2; i++ {
- plugins = append(plugins, pluginutil.PluginRunner{
- Name: "Name",
- Type: consts.PluginTypeDatabase,
- Version: "Version",
- Command: "Command",
- Args: []string{"Some", "Args"},
- Env: []string{"Env=foo", "bar=", "baz=foo"},
- Sha256: hasher.Sum(nil),
- Builtin: true,
- })
- }
-
- var keys []externalPluginsKey
- for _, plugin := range plugins {
- key, err := makeExternalPluginsKey(&plugin)
- if err != nil {
- t.Fatal(err)
- }
- keys = append(keys, key)
- }
-
- if keys[0] != keys[1] {
- t.Fatal("expected equality")
- }
-}
-
-func TestPluginCatalog_PluginMain_Userpass(t *testing.T) {
- if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
- return
- }
-
- apiClientMeta := &api.PluginAPIClientMeta{}
- flags := apiClientMeta.FlagSet()
- flags.Parse(os.Args[1:])
-
- tlsConfig := apiClientMeta.GetTLSConfig()
- tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig)
-
- err := backendplugin.Serve(
- &backendplugin.ServeOpts{
- BackendFactoryFunc: userpass.Factory,
- TLSProviderFunc: tlsProviderFunc,
- },
- )
- if err != nil {
- t.Fatalf("Failed to initialize userpass: %s", err)
- }
-}
-
-func TestPluginCatalog_PluginMain_UserpassMultiplexed(t *testing.T) {
- if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
- return
- }
-
- apiClientMeta := &api.PluginAPIClientMeta{}
- flags := apiClientMeta.FlagSet()
- flags.Parse(os.Args[1:])
-
- tlsConfig := apiClientMeta.GetTLSConfig()
- tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig)
-
- err := backendplugin.ServeMultiplex(
- &backendplugin.ServeOpts{
- BackendFactoryFunc: userpass.Factory,
- TLSProviderFunc: tlsProviderFunc,
- },
- )
- if err != nil {
- t.Fatalf("Failed to initialize userpass: %s", err)
- }
-}
-
-func TestPluginCatalog_PluginMain_Postgres(t *testing.T) {
- if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
- return
- }
-
- dbType, err := postgresql.New()
- if err != nil {
- t.Fatalf("Failed to initialize postgres: %s", err)
- }
-
- v5.Serve(dbType.(v5.Database))
-}
-
-func TestPluginCatalog_PluginMain_PostgresMultiplexed(_ *testing.T) {
- if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
- return
- }
-
- v5.ServeMultiplex(postgresql.New)
-}
-
-// expectConnectionLen asserts that the PluginCatalog's externalPlugin
-// connections map has a length of expectedLen
-func expectConnectionLen(t *testing.T, expectedLen int, connections map[string]*pluginClient) {
- if len(connections) != expectedLen {
- t.Fatalf("expected external plugin's connections map to be of len %d but got %d", expectedLen, len(connections))
- }
-}
-
-func expectMultiplexingSupport(t *testing.T, expected, actual bool) {
- if expected != actual {
- t.Fatalf("expected external plugin multiplexing support to be %t", expected)
- }
-}
diff --git a/vault/policy_store_test.go b/vault/policy_store_test.go
deleted file mode 100644
index 72a5af3d5..000000000
--- a/vault/policy_store_test.go
+++ /dev/null
@@ -1,405 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "reflect"
- "testing"
-
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/stretchr/testify/require"
-)
-
-func mockPolicyWithCore(t *testing.T, disableCache bool) (*Core, *PolicyStore) {
- conf := &CoreConfig{
- DisableCache: disableCache,
- }
- core, _, _ := TestCoreUnsealedWithConfig(t, conf)
- ps := core.policyStore
-
- return core, ps
-}
-
-func TestPolicyStore_Root(t *testing.T) {
- t.Run("root", func(t *testing.T) {
- t.Parallel()
-
- core, _, _ := TestCoreUnsealed(t)
- ps := core.policyStore
- testPolicyRoot(t, ps, namespace.RootNamespace, true)
- })
-}
-
-func testPolicyRoot(t *testing.T, ps *PolicyStore, ns *namespace.Namespace, expectFound bool) {
- // Get should return a special policy
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- p, err := ps.GetPolicy(ctx, "root", PolicyTypeToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Handle whether a root token is expected
- if expectFound {
- if p == nil {
- t.Fatalf("bad: %v", p)
- }
- if p.Name != "root" {
- t.Fatalf("bad: %v", p)
- }
- } else {
- if p != nil {
- t.Fatal("expected nil root policy")
- }
- // Create root policy for subsequent modification and deletion failure
- // tests
- p = &Policy{
- Name: "root",
- }
- }
-
- // Set should fail
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- err = ps.SetPolicy(ctx, p)
- if err.Error() != `cannot update "root" policy` {
- t.Fatalf("err: %v", err)
- }
-
- // Delete should fail
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- err = ps.DeletePolicy(ctx, "root", PolicyTypeACL)
- if err.Error() != `cannot delete "root" policy` {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestPolicyStore_CRUD(t *testing.T) {
- t.Run("root-ns", func(t *testing.T) {
- t.Run("cached", func(t *testing.T) {
- _, ps := mockPolicyWithCore(t, false)
- testPolicyStoreCRUD(t, ps, namespace.RootNamespace)
- })
-
- t.Run("no-cache", func(t *testing.T) {
- _, ps := mockPolicyWithCore(t, true)
- testPolicyStoreCRUD(t, ps, namespace.RootNamespace)
- })
- })
-}
-
-func testPolicyStoreCRUD(t *testing.T, ps *PolicyStore, ns *namespace.Namespace) {
- // Get should return nothing
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- p, err := ps.GetPolicy(ctx, "Dev", PolicyTypeToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if p != nil {
- t.Fatalf("bad: %v", p)
- }
-
- // Delete should be no-op
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- err = ps.DeletePolicy(ctx, "deV", PolicyTypeACL)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // List should be blank
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- out, err := ps.ListPolicies(ctx, PolicyTypeACL)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(out) != 1 {
- t.Fatalf("bad: %v", out)
- }
-
- // Set should work
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- policy, _ := ParseACLPolicy(ns, aclPolicy)
- err = ps.SetPolicy(ctx, policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Get should work
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- p, err = ps.GetPolicy(ctx, "dEv", PolicyTypeToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !reflect.DeepEqual(p, policy) {
- t.Fatalf("bad: %v", p)
- }
-
- // List should contain two elements
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- out, err = ps.ListPolicies(ctx, PolicyTypeACL)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(out) != 2 {
- t.Fatalf("bad: %v", out)
- }
-
- expected := []string{"default", "dev"}
- if !reflect.DeepEqual(expected, out) {
- t.Fatalf("expected: %v\ngot: %v", expected, out)
- }
-
- // Delete should be clear the entry
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- err = ps.DeletePolicy(ctx, "Dev", PolicyTypeACL)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // List should contain one element
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- out, err = ps.ListPolicies(ctx, PolicyTypeACL)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(out) != 1 || out[0] != "default" {
- t.Fatalf("bad: %v", out)
- }
-
- // Get should fail
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- p, err = ps.GetPolicy(ctx, "deV", PolicyTypeToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if p != nil {
- t.Fatalf("bad: %v", p)
- }
-}
-
-func TestPolicyStore_Predefined(t *testing.T) {
- t.Run("root-ns", func(t *testing.T) {
- _, ps := mockPolicyWithCore(t, false)
- testPolicyStorePredefined(t, ps, namespace.RootNamespace)
- })
-}
-
-// Test predefined policy handling
-func testPolicyStorePredefined(t *testing.T, ps *PolicyStore, ns *namespace.Namespace) {
- // List should be two elements
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- out, err := ps.ListPolicies(ctx, PolicyTypeACL)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- // This shouldn't contain response-wrapping since it's non-assignable
- if len(out) != 1 || out[0] != "default" {
- t.Fatalf("bad: %v", out)
- }
-
- // Response-wrapping policy checks
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- pCubby, err := ps.GetPolicy(ctx, "response-wrapping", PolicyTypeToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if pCubby == nil {
- t.Fatal("nil cubby policy")
- }
- if pCubby.Raw != responseWrappingPolicy {
- t.Fatalf("bad: expected\n%s\ngot\n%s\n", responseWrappingPolicy, pCubby.Raw)
- }
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- err = ps.SetPolicy(ctx, pCubby)
- if err == nil {
- t.Fatalf("expected err setting %s", pCubby.Name)
- }
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- err = ps.DeletePolicy(ctx, pCubby.Name, PolicyTypeACL)
- if err == nil {
- t.Fatalf("expected err deleting %s", pCubby.Name)
- }
-
- // Root policy checks, behavior depending on namespace
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- pRoot, err := ps.GetPolicy(ctx, "root", PolicyTypeToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if ns == namespace.RootNamespace {
- if pRoot == nil {
- t.Fatal("nil root policy")
- }
- } else {
- if pRoot != nil {
- t.Fatal("expected nil root policy")
- }
- pRoot = &Policy{
- Name: "root",
- }
- }
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- err = ps.SetPolicy(ctx, pRoot)
- if err == nil {
- t.Fatalf("expected err setting %s", pRoot.Name)
- }
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- err = ps.DeletePolicy(ctx, pRoot.Name, PolicyTypeACL)
- if err == nil {
- t.Fatalf("expected err deleting %s", pRoot.Name)
- }
-}
-
-func TestPolicyStore_ACL(t *testing.T) {
- t.Run("root-ns", func(t *testing.T) {
- _, ps := mockPolicyWithCore(t, false)
- testPolicyStoreACL(t, ps, namespace.RootNamespace)
- })
-}
-
-func testPolicyStoreACL(t *testing.T, ps *PolicyStore, ns *namespace.Namespace) {
- ctx := namespace.ContextWithNamespace(context.Background(), ns)
- policy, _ := ParseACLPolicy(ns, aclPolicy)
- err := ps.SetPolicy(ctx, policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- policy, _ = ParseACLPolicy(ns, aclPolicy2)
- err = ps.SetPolicy(ctx, policy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- ctx = namespace.ContextWithNamespace(context.Background(), ns)
- acl, err := ps.ACL(ctx, nil, map[string][]string{ns.ID: {"dev", "ops"}})
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- testLayeredACL(t, acl, ns)
-}
-
-func TestDefaultPolicy(t *testing.T) {
- ctx := namespace.ContextWithNamespace(context.Background(), namespace.RootNamespace)
-
- policy, err := ParseACLPolicy(namespace.RootNamespace, defaultPolicy)
- if err != nil {
- t.Fatal(err)
- }
- acl, err := NewACL(ctx, []*Policy{policy})
- if err != nil {
- t.Fatal(err)
- }
-
- for name, tc := range map[string]struct {
- op logical.Operation
- path string
- expectAllowed bool
- }{
- "lookup self": {logical.ReadOperation, "auth/token/lookup-self", true},
- "renew self": {logical.UpdateOperation, "auth/token/renew-self", true},
- "revoke self": {logical.UpdateOperation, "auth/token/revoke-self", true},
- "check own capabilities": {logical.UpdateOperation, "sys/capabilities-self", true},
-
- "read arbitrary path": {logical.ReadOperation, "foo/bar", false},
- "login at arbitrary path": {logical.UpdateOperation, "auth/foo", false},
- } {
- t.Run(name, func(t *testing.T) {
- request := new(logical.Request)
- request.Operation = tc.op
- request.Path = tc.path
-
- result := acl.AllowOperation(ctx, request, false)
- if result.RootPrivs {
- t.Fatal("unexpected root")
- }
- if tc.expectAllowed != result.Allowed {
- t.Fatalf("Expected %v, got %v", tc.expectAllowed, result.Allowed)
- }
- })
- }
-}
-
-// TestPolicyStore_GetNonEGPPolicyType has five test cases:
-// - happy-acl and happy-rgp: we store a policy in the policy type map and
-// then look up its type successfully.
-// - not-in-map-acl and not-in-map-rgp: ensure that GetNonEGPPolicyType fails
-// returning a nil and an error when the policy doesn't exist in the map.
-// - unknown-policy-type: ensures that GetNonEGPPolicyType fails returning a nil
-// and an error when the policy type in the type map is a value that
-// does not map to a PolicyType.
-func TestPolicyStore_GetNonEGPPolicyType(t *testing.T) {
- t.Parallel()
- tests := map[string]struct {
- policyStoreKey string
- policyStoreValue any
- paramNamespace string
- paramPolicyName string
- paramPolicyType PolicyType
- isErrorExpected bool
- expectedErrorMessage string
- }{
- "happy-acl": {
- policyStoreKey: "1AbcD/policy1",
- policyStoreValue: PolicyTypeACL,
- paramNamespace: "1AbcD",
- paramPolicyName: "policy1",
- paramPolicyType: PolicyTypeACL,
- },
- "happy-rgp": {
- policyStoreKey: "1AbcD/policy1",
- policyStoreValue: PolicyTypeRGP,
- paramNamespace: "1AbcD",
- paramPolicyName: "policy1",
- paramPolicyType: PolicyTypeRGP,
- },
- "not-in-map-acl": {
- policyStoreKey: "2WxyZ/policy2",
- policyStoreValue: PolicyTypeACL,
- paramNamespace: "1AbcD",
- paramPolicyName: "policy1",
- isErrorExpected: true,
- expectedErrorMessage: "policy does not exist in type map",
- },
- "not-in-map-rgp": {
- policyStoreKey: "2WxyZ/policy2",
- policyStoreValue: PolicyTypeRGP,
- paramNamespace: "1AbcD",
- paramPolicyName: "policy1",
- isErrorExpected: true,
- expectedErrorMessage: "policy does not exist in type map",
- },
- "unknown-policy-type": {
- policyStoreKey: "1AbcD/policy1",
- policyStoreValue: 7,
- paramNamespace: "1AbcD",
- paramPolicyName: "policy1",
- isErrorExpected: true,
- expectedErrorMessage: "unknown policy type for: 1AbcD/policy1",
- },
- }
-
- for name, tc := range tests {
- name := name
- tc := tc
- t.Run(name, func(t *testing.T) {
- t.Parallel()
-
- _, ps := mockPolicyWithCore(t, false)
- ps.policyTypeMap.Store(tc.policyStoreKey, tc.policyStoreValue)
- got, err := ps.GetNonEGPPolicyType(tc.paramNamespace, tc.paramPolicyName)
- if tc.isErrorExpected {
- require.Error(t, err)
- require.Nil(t, got)
- require.EqualError(t, err, tc.expectedErrorMessage)
-
- }
- if !tc.isErrorExpected {
- require.NoError(t, err)
- require.NotNil(t, got)
- require.Equal(t, tc.paramPolicyType, *got)
- }
- })
- }
-}
diff --git a/vault/policy_test.go b/vault/policy_test.go
deleted file mode 100644
index 97a3a1b3a..000000000
--- a/vault/policy_test.go
+++ /dev/null
@@ -1,499 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "strings"
- "testing"
- "time"
-
- "github.com/go-test/deep"
- "github.com/hashicorp/vault/helper/namespace"
-)
-
-var rawPolicy = strings.TrimSpace(`
-# Developer policy
-name = "dev"
-# Deny all paths by default
-path "*" {
- policy = "deny"
-}
-# Allow full access to staging
-path "stage/*" {
- policy = "sudo"
-}
-# Limited read privilege to production
-path "prod/version" {
- policy = "read"
-}
-# Read access to foobar
-# Also tests stripping of leading slash and parsing of min/max as string and
-# integer
-path "/foo/bar" {
- policy = "read"
- min_wrapping_ttl = 300
- max_wrapping_ttl = "1h"
-}
-# Add capabilities for creation and sudo to foobar
-# This will be separate; they are combined when compiled into an ACL
-# Also tests reverse string/int handling to the above
-path "foo/bar" {
- capabilities = ["create", "sudo"]
- min_wrapping_ttl = "300s"
- max_wrapping_ttl = 3600
-}
-# Check that only allowed_parameters are being added to foobar
-path "foo/bar" {
- capabilities = ["create", "sudo"]
- allowed_parameters = {
- "zip" = []
- "zap" = []
- }
-}
-# Check that only denied_parameters are being added to bazbar
-path "baz/bar" {
- capabilities = ["create", "sudo"]
- denied_parameters = {
- "zip" = []
- "zap" = []
- }
-}
-# Check that both allowed and denied parameters are being added to bizbar
-path "biz/bar" {
- capabilities = ["create", "sudo"]
- allowed_parameters = {
- "zim" = []
- "zam" = []
- }
- denied_parameters = {
- "zip" = []
- "zap" = []
- }
-}
-path "test/types" {
- capabilities = ["create", "sudo"]
- allowed_parameters = {
- "map" = [{"good" = "one"}]
- "int" = [1, 2]
- }
- denied_parameters = {
- "string" = ["test"]
- "bool" = [false]
- }
-}
-path "test/req" {
- capabilities = ["create", "sudo"]
- required_parameters = ["foo"]
-}
-path "test/patch" {
- capabilities = ["patch"]
-}
-path "test/mfa" {
- capabilities = ["create", "sudo"]
- mfa_methods = ["my_totp", "my_totp2"]
-}
-path "test/+/segment" {
- capabilities = ["create", "sudo"]
-}
-path "test/segment/at/end/+" {
- capabilities = ["create", "sudo"]
-}
-path "test/segment/at/end/v2/+/" {
- capabilities = ["create", "sudo"]
-}
-path "test/+/wildcard/+/*" {
- capabilities = ["create", "sudo"]
-}
-path "test/+/wildcard/+/end*" {
- capabilities = ["create", "sudo"]
-}
-`)
-
-func TestPolicy_Parse(t *testing.T) {
- p, err := ParseACLPolicy(namespace.RootNamespace, rawPolicy)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if p.Name != "dev" {
- t.Fatalf("bad name: %q", p.Name)
- }
-
- expect := []*PathRules{
- {
- Path: "",
- Policy: "deny",
- Capabilities: []string{
- "deny",
- },
- Permissions: &ACLPermissions{CapabilitiesBitmap: DenyCapabilityInt},
- IsPrefix: true,
- },
- {
- Path: "stage/",
- Policy: "sudo",
- Capabilities: []string{
- "create",
- "read",
- "update",
- "delete",
- "list",
- "sudo",
- },
- Permissions: &ACLPermissions{
- CapabilitiesBitmap: (CreateCapabilityInt | ReadCapabilityInt | UpdateCapabilityInt | DeleteCapabilityInt | ListCapabilityInt | SudoCapabilityInt),
- },
- IsPrefix: true,
- },
- {
- Path: "prod/version",
- Policy: "read",
- Capabilities: []string{
- "read",
- "list",
- },
- Permissions: &ACLPermissions{CapabilitiesBitmap: (ReadCapabilityInt | ListCapabilityInt)},
- },
- {
- Path: "foo/bar",
- Policy: "read",
- Capabilities: []string{
- "read",
- "list",
- },
- MinWrappingTTLHCL: 300,
- MaxWrappingTTLHCL: "1h",
- Permissions: &ACLPermissions{
- CapabilitiesBitmap: (ReadCapabilityInt | ListCapabilityInt),
- MinWrappingTTL: 300 * time.Second,
- MaxWrappingTTL: 3600 * time.Second,
- },
- },
- {
- Path: "foo/bar",
- Capabilities: []string{
- "create",
- "sudo",
- },
- MinWrappingTTLHCL: "300s",
- MaxWrappingTTLHCL: 3600,
- Permissions: &ACLPermissions{
- CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
- MinWrappingTTL: 300 * time.Second,
- MaxWrappingTTL: 3600 * time.Second,
- },
- },
- {
- Path: "foo/bar",
- Capabilities: []string{
- "create",
- "sudo",
- },
- AllowedParametersHCL: map[string][]interface{}{"zip": {}, "zap": {}},
- Permissions: &ACLPermissions{
- CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
- AllowedParameters: map[string][]interface{}{"zip": {}, "zap": {}},
- },
- },
- {
- Path: "baz/bar",
- Capabilities: []string{
- "create",
- "sudo",
- },
- DeniedParametersHCL: map[string][]interface{}{"zip": {}, "zap": {}},
- Permissions: &ACLPermissions{
- CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
- DeniedParameters: map[string][]interface{}{"zip": {}, "zap": {}},
- },
- },
- {
- Path: "biz/bar",
- Capabilities: []string{
- "create",
- "sudo",
- },
- AllowedParametersHCL: map[string][]interface{}{"zim": {}, "zam": {}},
- DeniedParametersHCL: map[string][]interface{}{"zip": {}, "zap": {}},
- Permissions: &ACLPermissions{
- CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
- AllowedParameters: map[string][]interface{}{"zim": {}, "zam": {}},
- DeniedParameters: map[string][]interface{}{"zip": {}, "zap": {}},
- },
- },
- {
- Path: "test/types",
- Policy: "",
- Capabilities: []string{
- "create",
- "sudo",
- },
- AllowedParametersHCL: map[string][]interface{}{"map": {map[string]interface{}{"good": "one"}}, "int": {1, 2}},
- DeniedParametersHCL: map[string][]interface{}{"string": {"test"}, "bool": {false}},
- Permissions: &ACLPermissions{
- CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
- AllowedParameters: map[string][]interface{}{"map": {map[string]interface{}{"good": "one"}}, "int": {1, 2}},
- DeniedParameters: map[string][]interface{}{"string": {"test"}, "bool": {false}},
- },
- IsPrefix: false,
- },
- {
- Path: "test/req",
- Capabilities: []string{
- "create",
- "sudo",
- },
- RequiredParametersHCL: []string{"foo"},
- Permissions: &ACLPermissions{
- CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
- RequiredParameters: []string{"foo"},
- },
- },
- {
- Path: "test/patch",
- Capabilities: []string{"patch"},
- Permissions: &ACLPermissions{
- CapabilitiesBitmap: (PatchCapabilityInt),
- },
- },
- {
- Path: "test/mfa",
- Capabilities: []string{
- "create",
- "sudo",
- },
- MFAMethodsHCL: []string{
- "my_totp",
- "my_totp2",
- },
- Permissions: &ACLPermissions{
- CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
- MFAMethods: []string{
- "my_totp",
- "my_totp2",
- },
- },
- },
- {
- Path: "test/+/segment",
- Capabilities: []string{
- "create",
- "sudo",
- },
- Permissions: &ACLPermissions{
- CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
- },
- HasSegmentWildcards: true,
- },
- {
- Path: "test/segment/at/end/+",
- Capabilities: []string{
- "create",
- "sudo",
- },
- Permissions: &ACLPermissions{
- CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
- },
- HasSegmentWildcards: true,
- },
- {
- Path: "test/segment/at/end/v2/+/",
- Capabilities: []string{
- "create",
- "sudo",
- },
- Permissions: &ACLPermissions{
- CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
- },
- HasSegmentWildcards: true,
- },
- {
- Path: "test/+/wildcard/+/*",
- Capabilities: []string{
- "create",
- "sudo",
- },
- Permissions: &ACLPermissions{
- CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
- },
- HasSegmentWildcards: true,
- },
- {
- Path: "test/+/wildcard/+/end*",
- Capabilities: []string{
- "create",
- "sudo",
- },
- Permissions: &ACLPermissions{
- CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
- },
- HasSegmentWildcards: true,
- },
- }
-
- if diff := deep.Equal(p.Paths, expect); diff != nil {
- t.Error(diff)
- }
-}
-
-func TestPolicy_ParseBadRoot(t *testing.T) {
- _, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
-name = "test"
-bad = "foo"
-nope = "yes"
-`))
- if err == nil {
- t.Fatalf("expected error")
- }
-
- if !strings.Contains(err.Error(), `invalid key "bad" on line 2`) {
- t.Errorf("bad error: %q", err)
- }
-
- if !strings.Contains(err.Error(), `invalid key "nope" on line 3`) {
- t.Errorf("bad error: %q", err)
- }
-}
-
-// TestPolicy_ParseControlGroupWrongCaps makes sure an appropriate error is
-// thrown when a factor's controlled_capabilities are not a subset of
-// the path capabilities.
-func TestPolicy_ParseControlGroupWrongCaps(t *testing.T) {
- _, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
- name = "controlgroups"
- path "secret/*" {
- capabilities = ["create", "read"]
- control_group = {
- max_ttl = "1h"
- factor "ops_manager" {
- controlled_capabilities = ["read", "write"]
- identity {
- group_names = ["blah"]
- approvals = 1
- }
- }
- }
- }
- `))
- if err == nil {
- t.Fatalf("Bad policy was successfully parsed")
- }
- if !strings.Contains(err.Error(), ControlledCapabilityPolicySubsetError) {
- t.Fatalf("Wrong error returned when control group's controlled capabilities are not a subset of the path capabilities: error was %s", err.Error())
- }
-}
-
-func TestPolicy_ParseControlGroup(t *testing.T) {
- pol, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
- name = "controlgroups"
- path "secret/*" {
- capabilities = ["create", "read"]
- control_group = {
- max_ttl = "1h"
- factor "ops_manager" {
- controlled_capabilities = ["create"]
- identity {
- group_names = ["blah"]
- approvals = 1
- }
- }
- }
- }
- `))
- if err != nil {
- t.Fatalf("Policy could not be parsed")
- }
-
- // At this point paths haven't been merged yet. We must simply make sure
- // that each factor has the correct associated permissions.
-
- permFactors := pol.Paths[0].Permissions.ControlGroup.Factors
-
- if len(permFactors) != 1 {
- t.Fatalf("Expected 1 control group factor: got %d", len(permFactors))
- }
-
- if len(permFactors[0].ControlledCapabilities) != 1 && permFactors[0].ControlledCapabilities[0] != "create" {
- t.Fatalf("controlled_capabilities on the first factor was not correct: %+v", permFactors[0].ControlledCapabilities)
- }
-}
-
-func TestPolicy_ParseBadPath(t *testing.T) {
- // The wrong spelling is intended here
- _, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
-path "/" {
- capabilities = ["read"]
- capabilites = ["read"]
-}
-`))
- if err == nil {
- t.Fatalf("expected error")
- }
-
- if !strings.Contains(err.Error(), `invalid key "capabilites" on line 3`) {
- t.Errorf("bad error: %s", err)
- }
-}
-
-func TestPolicy_ParseBadPolicy(t *testing.T) {
- _, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
-path "/" {
- policy = "banana"
-}
-`))
- if err == nil {
- t.Fatalf("expected error")
- }
-
- if !strings.Contains(err.Error(), `path "/": invalid policy "banana"`) {
- t.Errorf("bad error: %s", err)
- }
-}
-
-func TestPolicy_ParseBadWrapping(t *testing.T) {
- _, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
-path "/" {
- policy = "read"
- min_wrapping_ttl = 400
- max_wrapping_ttl = 200
-}
-`))
- if err == nil {
- t.Fatalf("expected error")
- }
-
- if !strings.Contains(err.Error(), `max_wrapping_ttl cannot be less than min_wrapping_ttl`) {
- t.Errorf("bad error: %s", err)
- }
-}
-
-func TestPolicy_ParseBadCapabilities(t *testing.T) {
- _, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
-path "/" {
- capabilities = ["read", "banana"]
-}
-`))
- if err == nil {
- t.Fatalf("expected error")
- }
-
- if !strings.Contains(err.Error(), `path "/": invalid capability "banana"`) {
- t.Errorf("bad error: %s", err)
- }
-}
-
-func TestPolicy_ParseBadSegmentWildcard(t *testing.T) {
- _, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
-path "foo/+*" {
- capabilities = ["read"]
-}
-`))
- if err == nil {
- t.Fatalf("expected error")
- }
-
- if !strings.Contains(err.Error(), `path "foo/+*": invalid use of wildcards ('+*' is forbidden)`) {
- t.Errorf("bad error: %s", err)
- }
-}
diff --git a/vault/quotas/quotas_rate_limit_test.go b/vault/quotas/quotas_rate_limit_test.go
deleted file mode 100644
index f745cb568..000000000
--- a/vault/quotas/quotas_rate_limit_test.go
+++ /dev/null
@@ -1,229 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package quotas
-
-import (
- "context"
- "fmt"
- "math"
- "sync"
- "testing"
- "time"
-
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/helper/metricsutil"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/stretchr/testify/require"
- "go.uber.org/atomic"
- "go.uber.org/goleak"
-)
-
-type clientResult struct {
- atomicNumAllow *atomic.Int32
- atomicNumFail *atomic.Int32
-}
-
-func TestNewRateLimitQuota(t *testing.T) {
- testCases := []struct {
- name string
- rlq *RateLimitQuota
- expectErr bool
- }{
- {"valid rate", NewRateLimitQuota("test-rate-limiter", "qa", "/foo/bar", "", "", 16.7, time.Second, 0), false},
- }
-
- for _, tc := range testCases {
- tc := tc
-
- t.Run(tc.name, func(t *testing.T) {
- err := tc.rlq.initialize(logging.NewVaultLogger(log.Trace), metricsutil.BlackholeSink())
- require.Equal(t, tc.expectErr, err != nil, err)
- if err == nil {
- require.Nil(t, tc.rlq.close(context.Background()))
- }
- })
- }
-}
-
-func TestRateLimitQuota_Close(t *testing.T) {
- rlq := NewRateLimitQuota("test-rate-limiter", "qa", "/foo/bar", "", "", 16.7, time.Second, time.Minute)
- require.NoError(t, rlq.initialize(logging.NewVaultLogger(log.Trace), metricsutil.BlackholeSink()))
- require.NoError(t, rlq.close(context.Background()))
-
- time.Sleep(time.Second) // allow enough time for purgeClientsLoop to receive on closeCh
- require.False(t, rlq.getPurgeBlocked(), "expected blocked client purging to be disabled after explicit close")
-}
-
-func TestRateLimitQuota_Allow(t *testing.T) {
- rlq := &RateLimitQuota{
- Name: "test-rate-limiter",
- Type: TypeRateLimit,
- NamespacePath: "qa",
- MountPath: "/foo/bar",
- Rate: 16.7,
-
- // override values to lower durations for testing purposes
- purgeInterval: 10 * time.Second,
- staleAge: 10 * time.Second,
- }
-
- require.NoError(t, rlq.initialize(logging.NewVaultLogger(log.Trace), metricsutil.BlackholeSink()))
- defer rlq.close(context.Background())
-
- var wg sync.WaitGroup
-
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
-
- reqFunc := func(addr string, atomicNumAllow, atomicNumFail *atomic.Int32) {
- defer wg.Done()
-
- for ctx.Err() == nil {
- resp, err := rlq.allow(context.Background(), &Request{ClientAddress: addr})
- if err != nil {
- return
- }
-
- if resp.Allowed {
- atomicNumAllow.Add(1)
- } else {
- atomicNumFail.Add(1)
- }
- time.Sleep(2 * time.Millisecond)
- }
- }
-
- results := make(map[string]*clientResult)
-
- start := time.Now()
-
- for i := 0; i < 5; i++ {
- wg.Add(1)
-
- addr := fmt.Sprintf("127.0.0.%d", i)
- cr, ok := results[addr]
- if !ok {
- results[addr] = &clientResult{atomicNumAllow: atomic.NewInt32(0), atomicNumFail: atomic.NewInt32(0)}
- cr = results[addr]
- }
-
- go reqFunc(addr, cr.atomicNumAllow, cr.atomicNumFail)
- }
-
- wg.Wait()
-
- // evaluate the ideal RPS as (ceil(RPS) + (RPS * totalSeconds))
- elapsed := time.Since(start)
- ideal := math.Ceil(rlq.Rate) + (rlq.Rate * float64(elapsed) / float64(time.Second))
-
- for addr, cr := range results {
- numAllow := cr.atomicNumAllow.Load()
- numFail := cr.atomicNumFail.Load()
-
- // ensure there were some failed requests for the namespace
- require.NotZerof(t, numFail, "expected some requests to fail; addr: %s, numSuccess: %d, numFail: %d, elapsed: %s", addr, numAllow, numFail, elapsed)
-
- // ensure that we should never get more requests than allowed for the namespace
- want := int32(ideal + 1)
- require.Falsef(t, numAllow > want, "too many successful requests; addr: %s, want: %d, numSuccess: %d, numFail: %d, elapsed: %s", addr, want, numAllow, numFail, elapsed)
- }
-}
-
-func TestRateLimitQuota_Allow_WithBlock(t *testing.T) {
- rlq := &RateLimitQuota{
- Name: "test-rate-limiter",
- Type: TypeRateLimit,
- NamespacePath: "qa",
- MountPath: "/foo/bar",
- Rate: 16.7,
- Interval: 5 * time.Second,
- BlockInterval: 10 * time.Second,
-
- // override values to lower durations for testing purposes
- purgeInterval: 10 * time.Second,
- staleAge: 10 * time.Second,
- }
-
- require.NoError(t, rlq.initialize(logging.NewVaultLogger(log.Trace), metricsutil.BlackholeSink()))
- defer rlq.close(context.Background())
- require.True(t, rlq.getPurgeBlocked())
-
- var wg sync.WaitGroup
-
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
-
- reqFunc := func(addr string, atomicNumAllow, atomicNumFail *atomic.Int32) {
- defer wg.Done()
-
- for ctx.Err() == nil {
- resp, err := rlq.allow(ctx, &Request{ClientAddress: addr})
- if err != nil {
- return
- }
-
- if resp.Allowed {
- atomicNumAllow.Add(1)
- } else {
- atomicNumFail.Add(1)
- }
- time.Sleep(2 * time.Millisecond)
- }
- }
-
- results := make(map[string]*clientResult)
-
- for i := 0; i < 5; i++ {
- wg.Add(1)
-
- addr := fmt.Sprintf("127.0.0.%d", i)
- cr, ok := results[addr]
- if !ok {
- results[addr] = &clientResult{atomicNumAllow: atomic.NewInt32(0), atomicNumFail: atomic.NewInt32(0)}
- cr = results[addr]
- }
-
- go reqFunc(addr, cr.atomicNumAllow, cr.atomicNumFail)
- }
-
- wg.Wait()
-
- for _, cr := range results {
- numAllow := cr.atomicNumAllow.Load()
- numFail := cr.atomicNumFail.Load()
-
- // Since blocking is enabled, each client should only have 'rate' successful
- // requests, whereas all subsequent requests fail.
- require.Equal(t, int32(17), numAllow, "Expected 17 got %d allows with %d failures", numAllow, numFail)
- require.NotZero(t, numFail)
- }
-
- func() {
- timeout := time.After(rlq.purgeInterval * 2)
- ticker := time.Tick(time.Second)
- for {
- select {
- case <-timeout:
- require.Failf(t, "timeout exceeded waiting for blocked clients to be purged", "num blocked: %d", rlq.numBlockedClients())
-
- case <-ticker:
- if rlq.numBlockedClients() == 0 {
- return
- }
- }
- }
- }()
-}
-
-func TestRateLimitQuota_Update(t *testing.T) {
- defer goleak.VerifyNone(t)
- qm, err := NewManager(logging.NewVaultLogger(log.Trace), nil, metricsutil.BlackholeSink(), true)
- require.NoError(t, err)
-
- quota := NewRateLimitQuota("quota1", "", "", "", "", 10, time.Second, 0)
- require.NoError(t, qm.SetQuota(context.Background(), TypeRateLimit.String(), quota, true))
- require.NoError(t, qm.SetQuota(context.Background(), TypeRateLimit.String(), quota, true))
-
- require.Nil(t, quota.close(context.Background()))
-}
diff --git a/vault/quotas/quotas_test.go b/vault/quotas/quotas_test.go
deleted file mode 100644
index ab0645471..000000000
--- a/vault/quotas/quotas_test.go
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package quotas
-
-import (
- "context"
- "testing"
- "time"
-
- "github.com/go-test/deep"
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/helper/metricsutil"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/stretchr/testify/require"
-)
-
-func TestQuotas_MountPathOverwrite(t *testing.T) {
- qm, err := NewManager(logging.NewVaultLogger(log.Trace), nil, metricsutil.BlackholeSink(), true)
- require.NoError(t, err)
-
- quota := NewRateLimitQuota("tq", "", "kv1/", "", "", 10, time.Second, 0)
- require.NoError(t, qm.SetQuota(context.Background(), TypeRateLimit.String(), quota, false))
- quota = quota.Clone().(*RateLimitQuota)
- quota.MountPath = "kv2/"
- require.NoError(t, qm.SetQuota(context.Background(), TypeRateLimit.String(), quota, false))
-
- q, err := qm.QueryQuota(&Request{
- Type: TypeRateLimit,
- MountPath: "kv1/",
- })
- require.NoError(t, err)
- require.Nil(t, q)
-
- require.NoError(t, qm.DeleteQuota(context.Background(), TypeRateLimit.String(), "tq"))
-
- q, err = qm.QueryQuota(&Request{
- Type: TypeRateLimit,
- MountPath: "kv1/",
- })
- require.NoError(t, err)
- require.Nil(t, q)
-}
-
-func TestQuotas_Precedence(t *testing.T) {
- qm, err := NewManager(logging.NewVaultLogger(log.Trace), nil, metricsutil.BlackholeSink(), true)
- require.NoError(t, err)
-
- setQuotaFunc := func(t *testing.T, name, nsPath, mountPath, pathSuffix, role string) Quota {
- t.Helper()
- quota := NewRateLimitQuota(name, nsPath, mountPath, pathSuffix, role, 10, time.Second, 0)
- require.NoError(t, qm.SetQuota(context.Background(), TypeRateLimit.String(), quota, true))
- return quota
- }
-
- checkQuotaFunc := func(t *testing.T, nsPath, mountPath, pathSuffix, role string, expected Quota) {
- t.Helper()
- quota, err := qm.QueryQuota(&Request{
- Type: TypeRateLimit,
- NamespacePath: nsPath,
- MountPath: mountPath,
- Role: role,
- Path: nsPath + mountPath + pathSuffix,
- })
- require.NoError(t, err)
-
- if diff := deep.Equal(expected, quota); len(diff) > 0 {
- t.Fatal(diff)
- }
- }
-
- // No quota present. Expect nil.
- checkQuotaFunc(t, "", "", "", "", nil)
-
- // Define global quota and expect that to be returned.
- rateLimitGlobalQuota := setQuotaFunc(t, "rateLimitGlobalQuota", "", "", "", "")
- checkQuotaFunc(t, "", "", "", "", rateLimitGlobalQuota)
-
- // Define a global mount specific quota and expect that to be returned.
- rateLimitGlobalMountQuota := setQuotaFunc(t, "rateLimitGlobalMountQuota", "", "testmount/", "", "")
- checkQuotaFunc(t, "", "testmount/", "", "", rateLimitGlobalMountQuota)
-
- // Define a global mount + path specific quota and expect that to be returned.
- rateLimitGlobalMountPathQuota := setQuotaFunc(t, "rateLimitGlobalMountPathQuota", "", "testmount/", "testpath", "")
- checkQuotaFunc(t, "", "testmount/", "testpath", "", rateLimitGlobalMountPathQuota)
-
- // Define a namespace quota and expect that to be returned.
- rateLimitNSQuota := setQuotaFunc(t, "rateLimitNSQuota", "testns/", "", "", "")
- checkQuotaFunc(t, "testns/", "", "", "", rateLimitNSQuota)
-
- // Define a namespace mount specific quota and expect that to be returned.
- rateLimitNSMountQuota := setQuotaFunc(t, "rateLimitNSMountQuota", "testns/", "testmount/", "", "")
- checkQuotaFunc(t, "testns/", "testmount/", "testpath", "", rateLimitNSMountQuota)
-
- // Define a namespace mount + glob and expect that to be returned.
- rateLimitNSMountGlob := setQuotaFunc(t, "rateLimitNSMountGlob", "testns/", "testmount/", "*", "")
- checkQuotaFunc(t, "testns/", "testmount/", "testpath", "", rateLimitNSMountGlob)
-
- // Define a namespace mount + path specific quota with a glob and expect that to be returned.
- rateLimitNSMountPathSuffixGlob := setQuotaFunc(t, "rateLimitNSMountPathSuffixGlob", "testns/", "testmount/", "test*", "")
- checkQuotaFunc(t, "testns/", "testmount/", "testpath", "", rateLimitNSMountPathSuffixGlob)
-
- // Define a namespace mount + path specific quota with a glob at the end of the path and expect that to be returned.
- rateLimitNSMountPathSuffixGlobAfterPath := setQuotaFunc(t, "rateLimitNSMountPathSuffixGlobAfterPath", "testns/", "testmount/", "testpath*", "")
- checkQuotaFunc(t, "testns/", "testmount/", "testpath", "", rateLimitNSMountPathSuffixGlobAfterPath)
-
- // Define a namespace mount + path specific quota and expect that to be returned.
- rateLimitNSMountPathQuota := setQuotaFunc(t, "rateLimitNSMountPathQuota", "testns/", "testmount/", "testpath", "")
- checkQuotaFunc(t, "testns/", "testmount/", "testpath", "", rateLimitNSMountPathQuota)
-
- // Define a namespace mount + role specific quota and expect that to be returned.
- rateLimitNSMountRoleQuota := setQuotaFunc(t, "rateLimitNSMountPathQuota", "testns/", "testmount/", "", "role")
- checkQuotaFunc(t, "testns/", "testmount/", "", "role", rateLimitNSMountRoleQuota)
-
- // Now that many quota types are defined, verify that the most specific
- // matches are returned per namespace.
- checkQuotaFunc(t, "", "", "", "", rateLimitGlobalQuota)
- checkQuotaFunc(t, "testns/", "", "", "", rateLimitNSQuota)
-}
-
-// TestQuotas_QueryRoleQuotas checks to see if quota creation on a mount
-// requires a call to ResolveRoleOperation.
-func TestQuotas_QueryResolveRole_RateLimitQuotas(t *testing.T) {
- leaseWalkFunc := func(context.Context, func(request *Request) bool) error {
- return nil
- }
- qm, err := NewManager(logging.NewVaultLogger(log.Trace), leaseWalkFunc, metricsutil.BlackholeSink(), true)
- require.NoError(t, err)
-
- rlqReq := &Request{
- Type: TypeRateLimit,
- Path: "",
- MountPath: "mount1/",
- NamespacePath: "",
- ClientAddress: "127.0.0.1",
- }
- // Check that we have no quotas requiring role resolution on mount1/
- required, err := qm.QueryResolveRoleQuotas(rlqReq)
- require.NoError(t, err)
- require.False(t, required)
-
- // Create a non-role-based RLQ on mount1/ and make sure it doesn't require role resolution
- rlq := NewRateLimitQuota("tq", rlqReq.NamespacePath, rlqReq.MountPath, rlqReq.Path, rlqReq.Role, 10, 1*time.Minute, 10*time.Second)
- require.NoError(t, qm.SetQuota(context.Background(), TypeRateLimit.String(), rlq, false))
-
- required, err = qm.QueryResolveRoleQuotas(rlqReq)
- require.NoError(t, err)
- require.False(t, required)
-
- // Create a role-based RLQ on mount1/ and make sure it requires role resolution
- rlqReq.Role = "test"
- rlq = NewRateLimitQuota("tq", rlqReq.NamespacePath, rlqReq.MountPath, rlqReq.Path, rlqReq.Role, 10, 1*time.Minute, 10*time.Second)
- require.NoError(t, qm.SetQuota(context.Background(), TypeRateLimit.String(), rlq, false))
-
- required, err = qm.QueryResolveRoleQuotas(rlqReq)
- require.NoError(t, err)
- require.True(t, required)
-
- // Check that we have no quotas requiring role resolution on mount2/
- rlqReq.MountPath = "mount2/"
- required, err = qm.QueryResolveRoleQuotas(rlqReq)
- require.NoError(t, err)
- require.False(t, required)
-}
diff --git a/vault/rekey_test.go b/vault/rekey_test.go
deleted file mode 100644
index b2b084bfb..000000000
--- a/vault/rekey_test.go
+++ /dev/null
@@ -1,540 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "fmt"
- "reflect"
- "strings"
- "testing"
-
- log "github.com/hashicorp/go-hclog"
-
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/physical"
- "github.com/hashicorp/vault/sdk/physical/inmem"
- "github.com/hashicorp/vault/vault/seal"
-)
-
-func TestCore_Rekey_Lifecycle(t *testing.T) {
- bc := &SealConfig{
- SecretShares: 1,
- SecretThreshold: 1,
- StoredShares: 1,
- }
- c, masterKeys, _, _ := TestCoreUnsealedWithConfigs(t, bc, nil)
- if len(masterKeys) != 1 {
- t.Fatalf("expected %d keys, got %d", bc.SecretShares-bc.StoredShares, len(masterKeys))
- }
- testCore_Rekey_Lifecycle_Common(t, c, false)
-}
-
-func testCore_Rekey_Lifecycle_Common(t *testing.T, c *Core, recovery bool) {
- min, _ := c.barrier.KeyLength()
- // Verify update not allowed
- _, err := c.RekeyUpdate(context.Background(), make([]byte, min), "", recovery)
- expected := "no barrier rekey in progress"
- if recovery {
- expected = "no recovery rekey in progress"
- }
- if err == nil || !strings.Contains(err.Error(), expected) {
- t.Fatalf("no rekey should be in progress, err: %v", err)
- }
-
- // Should be no progress
- if _, _, err := c.RekeyProgress(recovery, false); err == nil {
- t.Fatal("expected error from RekeyProgress")
- }
-
- // Should be no config
- conf, err := c.RekeyConfig(recovery)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if conf != nil {
- t.Fatalf("bad: %v", conf)
- }
-
- // Cancel should be idempotent
- err = c.RekeyCancel(false)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Start a rekey
- newConf := &SealConfig{
- SecretThreshold: 3,
- SecretShares: 5,
- }
- err = c.RekeyInit(newConf, recovery)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should get config
- conf, err = c.RekeyConfig(recovery)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- newConf.Nonce = conf.Nonce
- if !reflect.DeepEqual(conf, newConf) {
- t.Fatalf("bad: %v", conf)
- }
-
- // Cancel should be clear
- err = c.RekeyCancel(recovery)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should be no config
- conf, err = c.RekeyConfig(recovery)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if conf != nil {
- t.Fatalf("bad: %v", conf)
- }
-}
-
-func TestCore_Rekey_Init(t *testing.T) {
- t.Run("barrier-rekey-init", func(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- testCore_Rekey_Init_Common(t, c, false)
- })
-}
-
-func testCore_Rekey_Init_Common(t *testing.T, c *Core, recovery bool) {
- // Try an invalid config
- badConf := &SealConfig{
- SecretThreshold: 5,
- SecretShares: 1,
- }
- err := c.RekeyInit(badConf, recovery)
- if err == nil {
- t.Fatalf("should fail")
- }
-
- // Start a rekey
- newConf := &SealConfig{
- SecretThreshold: 3,
- SecretShares: 5,
- }
-
- // If recovery key is supported, set newConf
- // to be a recovery seal config
- if c.seal.RecoveryKeySupported() {
- newConf.Type = c.seal.RecoveryType()
- }
-
- err = c.RekeyInit(newConf, recovery)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Second should fail
- err = c.RekeyInit(newConf, recovery)
- if err == nil {
- t.Fatalf("should fail")
- }
-}
-
-func TestCore_Rekey_Update(t *testing.T) {
- bc := &SealConfig{
- SecretShares: 1,
- SecretThreshold: 1,
- }
- c, masterKeys, _, root := TestCoreUnsealedWithConfigs(t, bc, nil)
- testCore_Rekey_Update_Common(t, c, masterKeys, root, false)
-}
-
-func testCore_Rekey_Update_Common(t *testing.T, c *Core, keys [][]byte, root string, recovery bool) {
- var err error
- // Start a rekey
- var expType string
- if recovery {
- expType = c.seal.RecoveryType()
- } else {
- expType = c.seal.BarrierType().String()
- }
-
- newConf := &SealConfig{
- Type: expType,
- SecretThreshold: 3,
- SecretShares: 5,
- }
- hErr := c.RekeyInit(newConf, recovery)
- if hErr != nil {
- t.Fatalf("err: %v", hErr)
- }
-
- // Fetch new config with generated nonce
- rkconf, hErr := c.RekeyConfig(recovery)
- if hErr != nil {
- t.Fatalf("err: %v", hErr)
- }
- if rkconf == nil {
- t.Fatalf("bad: no rekey config received")
- }
-
- // Provide the master/recovery keys
- var result *RekeyResult
- for _, key := range keys {
- result, err = c.RekeyUpdate(context.Background(), key, rkconf.Nonce, recovery)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if result != nil {
- break
- }
- }
- if result == nil {
- t.Fatal("nil result after update")
- }
-
- // Should be no progress
- if _, _, err := c.RekeyProgress(recovery, false); err == nil {
- t.Fatal("expected error from RekeyProgress")
- }
-
- // Should be no config
- conf, hErr := c.RekeyConfig(recovery)
- if hErr != nil {
- t.Fatalf("rekey config error: %v", hErr)
- }
- if conf != nil {
- t.Fatalf("rekey config should be nil, got: %v", conf)
- }
-
- // SealConfig should update
- var sealConf *SealConfig
- if recovery {
- sealConf, err = c.seal.RecoveryConfig(context.Background())
- } else {
- sealConf, err = c.seal.BarrierConfig(context.Background())
- }
- if err != nil {
- t.Fatalf("seal config retrieval error: %v", err)
- }
- if sealConf == nil {
- t.Fatal("seal configuration is nil")
- }
-
- newConf.Nonce = rkconf.Nonce
- if !reflect.DeepEqual(sealConf, newConf) {
- t.Fatalf("\nexpected: %#v\nactual: %#v\nexpType: %s\nrecovery: %t", newConf, sealConf, expType, recovery)
- }
-
- // At this point bail if we are rekeying the barrier key with recovery
- // keys, since a new rekey should still be using the same set of recovery
- // keys and we haven't been returned key shares in this mode.
- if !recovery && c.seal.RecoveryKeySupported() {
- return
- }
-
- // Attempt unseal if this was not recovery mode
- if !recovery {
- err = c.Seal(root)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- for i := 0; i < newConf.SecretThreshold; i++ {
- _, err = TestCoreUnseal(c, TestKeyCopy(result.SecretShares[i]))
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- }
- if c.Sealed() {
- t.Fatalf("should be unsealed")
- }
- }
-
- // Start another rekey, this time we require a quorum!
-
- newConf = &SealConfig{
- Type: expType,
- SecretThreshold: 1,
- SecretShares: 1,
- }
- err = c.RekeyInit(newConf, recovery)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Fetch new config with generated nonce
- rkconf, err = c.RekeyConfig(recovery)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if rkconf == nil {
- t.Fatalf("bad: no rekey config received")
- }
-
- // Provide the parts master
- oldResult := result
- for i := 0; i < 3; i++ {
- result, err = c.RekeyUpdate(context.Background(), TestKeyCopy(oldResult.SecretShares[i]), rkconf.Nonce, recovery)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should be progress
- if i < 2 {
- _, num, err := c.RekeyProgress(recovery, false)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if num != i+1 {
- t.Fatalf("bad: %d", num)
- }
- }
- }
- if result == nil || len(result.SecretShares) != 1 {
- t.Fatalf("Bad: %#v", result)
- }
-
- // Attempt unseal if this was not recovery mode
- if !recovery {
- err = c.Seal(root)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- unseal, err := TestCoreUnseal(c, result.SecretShares[0])
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if !unseal {
- t.Fatalf("should be unsealed")
- }
- }
-
- // SealConfig should update
- if recovery {
- sealConf, err = c.seal.RecoveryConfig(context.Background())
- } else {
- sealConf, err = c.seal.BarrierConfig(context.Background())
- }
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- newConf.Nonce = rkconf.Nonce
- if !reflect.DeepEqual(sealConf, newConf) {
- t.Fatalf("bad: %#v", sealConf)
- }
-}
-
-func TestCore_Rekey_Legacy(t *testing.T) {
- bc := &SealConfig{
- SecretShares: 1,
- SecretThreshold: 1,
- }
- c, masterKeys, _, root := TestCoreUnsealedWithConfigSealOpts(t, bc, nil,
- &seal.TestSealOpts{StoredKeys: seal.StoredKeysNotSupported})
- testCore_Rekey_Update_Common(t, c, masterKeys, root, false)
-}
-
-func TestCore_Rekey_Invalid(t *testing.T) {
- bc := &SealConfig{
- SecretShares: 5,
- SecretThreshold: 3,
- }
- bc.StoredShares = 0
- bc.SecretShares = 1
- bc.SecretThreshold = 1
- c, masterKeys, _, _ := TestCoreUnsealedWithConfigs(t, bc, nil)
- testCore_Rekey_Invalid_Common(t, c, masterKeys, false)
-}
-
-func testCore_Rekey_Invalid_Common(t *testing.T, c *Core, keys [][]byte, recovery bool) {
- // Start a rekey
- newConf := &SealConfig{
- SecretThreshold: 3,
- SecretShares: 5,
- }
- err := c.RekeyInit(newConf, recovery)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Fetch new config with generated nonce
- rkconf, err := c.RekeyConfig(recovery)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if rkconf == nil {
- t.Fatalf("bad: no rekey config received")
- }
-
- // Provide the nonce (invalid)
- _, err = c.RekeyUpdate(context.Background(), keys[0], "abcd", recovery)
- if err == nil {
- t.Fatalf("expected error")
- }
-
- // Provide the key (invalid)
- key := keys[0]
- oldkeystr := fmt.Sprintf("%#v", key)
- key[0]++
- newkeystr := fmt.Sprintf("%#v", key)
- ret, err := c.RekeyUpdate(context.Background(), key, rkconf.Nonce, recovery)
- if err == nil {
- t.Fatalf("expected error, ret is %#v\noldkeystr: %s\nnewkeystr: %s", *ret, oldkeystr, newkeystr)
- }
-
- // Check progress has been reset
- _, num, err := c.RekeyProgress(recovery, false)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if num != 0 {
- t.Fatalf("rekey progress should be 0, got: %d", num)
- }
-}
-
-func TestCore_Rekey_Standby(t *testing.T) {
- // Create the first core and initialize it
- logger := logging.NewVaultLogger(log.Trace)
-
- inm, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- inmha, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
-
- redirectOriginal := "http://127.0.0.1:8200"
- core, err := NewCore(&CoreConfig{
- Physical: inm,
- HAPhysical: inmha.(physical.HABackend),
- RedirectAddr: redirectOriginal,
- DisableMlock: true,
- DisableCache: true,
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer core.Shutdown()
- keys, root := TestCoreInit(t, core)
- for _, key := range keys {
- if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- // Wait for core to become active
- TestWaitActive(t, core)
-
- // Create a second core, attached to same in-memory store
- redirectOriginal2 := "http://127.0.0.1:8500"
- core2, err := NewCore(&CoreConfig{
- Physical: inm,
- HAPhysical: inmha.(physical.HABackend),
- RedirectAddr: redirectOriginal2,
- DisableMlock: true,
- DisableCache: true,
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defer core2.Shutdown()
- for _, key := range keys {
- if _, err := TestCoreUnseal(core2, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- // Rekey the master key
- newConf := &SealConfig{
- SecretShares: 1,
- SecretThreshold: 1,
- }
- err = core.RekeyInit(newConf, false)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- // Fetch new config with generated nonce
- rkconf, err := core.RekeyConfig(false)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if rkconf == nil {
- t.Fatalf("bad: no rekey config received")
- }
- var rekeyResult *RekeyResult
- for _, key := range keys {
- rekeyResult, err = core.RekeyUpdate(context.Background(), key, rkconf.Nonce, false)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- }
- if rekeyResult == nil {
- t.Fatalf("rekey failed")
- }
-
- // Seal the first core, should step down
- err = core.Seal(root)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Wait for core2 to become active
- TestWaitActive(t, core2)
-
- // Rekey the master key again
- err = core2.RekeyInit(newConf, false)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- // Fetch new config with generated nonce
- rkconf, err = core2.RekeyConfig(false)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if rkconf == nil {
- t.Fatalf("bad: no rekey config received")
- }
- var rekeyResult2 *RekeyResult
- for _, key := range rekeyResult.SecretShares {
- rekeyResult2, err = core2.RekeyUpdate(context.Background(), key, rkconf.Nonce, false)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- }
- if rekeyResult2 == nil {
- t.Fatalf("rekey failed")
- }
-
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if rekeyResult2 == nil {
- t.Fatalf("rekey failed")
- }
-}
-
-// verifies that if we are using recovery keys to force a
-// rekey of a stored-shares barrier that verification is not allowed since
-// the keys aren't returned
-func TestSysRekey_Verification_Invalid(t *testing.T) {
- core, _, _, _ := TestCoreUnsealedWithConfigSealOpts(t,
- &SealConfig{StoredShares: 1, SecretShares: 1, SecretThreshold: 1},
- &SealConfig{StoredShares: 1, SecretShares: 1, SecretThreshold: 1},
- &seal.TestSealOpts{StoredKeys: seal.StoredKeysSupportedGeneric})
-
- err := core.BarrierRekeyInit(&SealConfig{
- VerificationRequired: true,
- StoredShares: 1,
- })
-
- if err == nil {
- t.Fatal("expected error")
- }
- if !strings.Contains(err.Error(), "requiring verification not supported") {
- t.Fatalf("unexpected error: %v", err)
- }
-}
diff --git a/vault/request_handling_test.go b/vault/request_handling_test.go
deleted file mode 100644
index 70df607a8..000000000
--- a/vault/request_handling_test.go
+++ /dev/null
@@ -1,475 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "strings"
- "testing"
- "time"
-
- "github.com/armon/go-metrics"
- "github.com/go-test/deep"
- uuid "github.com/hashicorp/go-uuid"
- "github.com/hashicorp/vault/builtin/credential/approle"
- credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestRequestHandling_Wrapping(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
-
- core.logicalBackends["kv"] = PassthroughBackendFactory
-
- meUUID, _ := uuid.GenerateUUID()
- err := core.mount(namespace.RootContext(nil), &MountEntry{
- Table: mountTableType,
- UUID: meUUID,
- Path: "wraptest",
- Type: "kv",
- })
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // No duration specified
- req := &logical.Request{
- Path: "wraptest/foo",
- ClientToken: root,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "zip": "zap",
- },
- }
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- req = &logical.Request{
- Path: "wraptest/foo",
- ClientToken: root,
- Operation: logical.ReadOperation,
- WrapInfo: &logical.RequestWrapInfo{
- TTL: time.Duration(15 * time.Second),
- },
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatalf("bad: %v", resp)
- }
- if resp.WrapInfo == nil || resp.WrapInfo.TTL != time.Duration(15*time.Second) {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestRequestHandling_LoginWrapping(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
-
- if err := core.loadMounts(namespace.RootContext(nil)); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- core.credentialBackends["userpass"] = credUserpass.Factory
-
- // No duration specified
- req := &logical.Request{
- Path: "sys/auth/userpass",
- ClientToken: root,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "type": "userpass",
- },
- Connection: &logical.Connection{},
- }
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- req.Path = "auth/userpass/users/test"
- req.Data = map[string]interface{}{
- "password": "foo",
- "policies": "default",
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- req = &logical.Request{
- Path: "auth/userpass/login/test",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "password": "foo",
- },
- Connection: &logical.Connection{},
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatalf("bad: %v", resp)
- }
- if resp.WrapInfo != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- req = &logical.Request{
- Path: "auth/userpass/login/test",
- Operation: logical.UpdateOperation,
- WrapInfo: &logical.RequestWrapInfo{
- TTL: time.Duration(15 * time.Second),
- },
- Data: map[string]interface{}{
- "password": "foo",
- },
- Connection: &logical.Connection{},
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatalf("bad: %v", resp)
- }
- if resp.WrapInfo == nil || resp.WrapInfo.TTL != time.Duration(15*time.Second) {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestRequestHandling_Login_PeriodicToken(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
-
- if err := core.loadMounts(namespace.RootContext(nil)); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- core.credentialBackends["approle"] = approle.Factory
-
- // Enable approle
- req := &logical.Request{
- Path: "sys/auth/approle",
- ClientToken: root,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "type": "approle",
- },
- Connection: &logical.Connection{},
- }
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Create role
- req.Path = "auth/approle/role/role-period"
- req.Data = map[string]interface{}{
- "period": "5s",
- }
- _, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Get role ID
- req.Path = "auth/approle/role/role-period/role-id"
- req.Operation = logical.ReadOperation
- req.Data = nil
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Data == nil {
- t.Fatalf("bad: %#v", resp)
- }
- roleID := resp.Data["role_id"]
-
- // Get secret ID
- req.Path = "auth/approle/role/role-period/secret-id"
- req.Operation = logical.UpdateOperation
- req.Data = nil
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Data == nil {
- t.Fatalf("bad: %#v", resp)
- }
- secretID := resp.Data["secret_id"]
-
- // Perform login
- req = &logical.Request{
- Path: "auth/approle/login",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "role_id": roleID,
- "secret_id": secretID,
- },
- Connection: &logical.Connection{},
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Auth == nil {
- t.Fatalf("bad: %v", resp)
- }
- loginToken := resp.Auth.ClientToken
- entityID := resp.Auth.EntityID
- accessor := resp.Auth.Accessor
-
- // Perform token lookup on the generated token
- req = &logical.Request{
- Path: "auth/token/lookup",
- Operation: logical.UpdateOperation,
- ClientToken: root,
- Data: map[string]interface{}{
- "token": loginToken,
- },
- Connection: &logical.Connection{},
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatalf("bad: %v", resp)
- }
- if resp.Data == nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- if resp.Data["creation_time"].(int64) == 0 {
- t.Fatal("creation time was zero")
- }
-
- // Depending on timing of the test this may have ticked down, so reset it
- // back to the original value as long as it's not expired.
- if resp.Data["ttl"].(int64) > 0 && resp.Data["ttl"].(int64) < 5 {
- resp.Data["ttl"] = int64(5)
- }
-
- exp := map[string]interface{}{
- "accessor": accessor,
- "creation_time": resp.Data["creation_time"].(int64),
- "creation_ttl": int64(5),
- "display_name": "approle",
- "entity_id": entityID,
- "expire_time": resp.Data["expire_time"].(time.Time),
- "explicit_max_ttl": int64(0),
- "id": loginToken,
- "issue_time": resp.Data["issue_time"].(time.Time),
- "meta": map[string]string{"role_name": "role-period"},
- "num_uses": 0,
- "orphan": true,
- "path": "auth/approle/login",
- "period": int64(5),
- "policies": []string{"default"},
- "renewable": true,
- "ttl": int64(5),
- "type": "service",
- }
-
- if diff := deep.Equal(resp.Data, exp); diff != nil {
- t.Fatal(diff)
- }
-}
-
-func labelsMatch(actual, expected map[string]string) bool {
- for expected_label, expected_val := range expected {
- if v, ok := actual[expected_label]; ok {
- if v != expected_val {
- return false
- }
- } else {
- return false
- }
- }
- return true
-}
-
-func checkCounter(t *testing.T, inmemSink *metrics.InmemSink, keyPrefix string, expectedLabels map[string]string) {
- t.Helper()
-
- intervals := inmemSink.Data()
- if len(intervals) > 1 {
- t.Skip("Detected interval crossing.")
- }
-
- var counter *metrics.SampledValue = nil
- var labels map[string]string
- for _, c := range intervals[0].Counters {
- if !strings.HasPrefix(c.Name, keyPrefix) {
- continue
- }
- counter = &c
-
- labels = make(map[string]string)
- for _, l := range counter.Labels {
- labels[l.Name] = l.Value
- }
-
- // Distinguish between different label sets
- if labelsMatch(labels, expectedLabels) {
- break
- }
- }
- if counter == nil {
- t.Fatalf("No %q counter found with matching labels", keyPrefix)
- }
-
- if !labelsMatch(labels, expectedLabels) {
- t.Errorf("No matching label set, found %v", labels)
- }
-
- if counter.Count != 1 {
- t.Errorf("Counter number of samples %v is not 1.", counter.Count)
- }
-
- if counter.Sum != 1.0 {
- t.Errorf("Counter sum %v is not 1.", counter.Sum)
- }
-}
-
-func TestRequestHandling_LoginMetric(t *testing.T) {
- core, _, root, sink := TestCoreUnsealedWithMetrics(t)
-
- if err := core.loadMounts(namespace.RootContext(nil)); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- core.credentialBackends["userpass"] = credUserpass.Factory
-
- // Setup mount
- req := &logical.Request{
- Path: "sys/auth/userpass",
- ClientToken: root,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "type": "userpass",
- },
- Connection: &logical.Connection{},
- }
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Create user
- req.Path = "auth/userpass/users/test"
- req.Data = map[string]interface{}{
- "password": "foo",
- "policies": "default",
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Login with response wrapping
- req = &logical.Request{
- Path: "auth/userpass/login/test",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "password": "foo",
- },
- WrapInfo: &logical.RequestWrapInfo{
- TTL: time.Duration(15 * time.Second),
- },
- Connection: &logical.Connection{},
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil {
- t.Fatalf("bad: %v", resp)
- }
-
- // There should be two counters
- checkCounter(t, sink, "token.creation",
- map[string]string{
- "cluster": "test-cluster",
- "namespace": "root",
- "auth_method": "userpass",
- "mount_point": "auth/userpass/",
- "creation_ttl": "+Inf",
- "token_type": "service",
- },
- )
- checkCounter(t, sink, "token.creation",
- map[string]string{
- "cluster": "test-cluster",
- "namespace": "root",
- "auth_method": "response_wrapping",
- "mount_point": "auth/userpass/",
- "creation_ttl": "1m",
- "token_type": "service",
- },
- )
-}
-
-func TestRequestHandling_SecretLeaseMetric(t *testing.T) {
- core, _, root, sink := TestCoreUnsealedWithMetrics(t)
-
- // Create a key with a lease
- req := logical.TestRequest(t, logical.UpdateOperation, "secret/foo")
- req.Data["foo"] = "bar"
- req.ClientToken = root
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Read a key with a LeaseID
- req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
- req.ClientToken = root
- err = core.PopulateTokenEntry(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- checkCounter(t, sink, "secret.lease.creation",
- map[string]string{
- "cluster": "test-cluster",
- "namespace": "root",
- "secret_engine": "kv",
- "mount_point": "secret/",
- "creation_ttl": "+Inf",
- },
- )
-}
diff --git a/vault/rollback_test.go b/vault/rollback_test.go
deleted file mode 100644
index 163e70064..000000000
--- a/vault/rollback_test.go
+++ /dev/null
@@ -1,366 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "fmt"
- "sync"
- "testing"
- "time"
-
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/go-uuid"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/stretchr/testify/require"
-)
-
-// mockRollback returns a mock rollback manager
-func mockRollback(t *testing.T) (*RollbackManager, *NoopBackend) {
- backend := new(NoopBackend)
- mounts := new(MountTable)
- router := NewRouter()
- core, _, _ := TestCoreUnsealed(t)
-
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
-
- mounts.Entries = []*MountEntry{
- {
- Path: "foo",
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- },
- }
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
-
- if err := router.Mount(backend, "foo", &MountEntry{UUID: meUUID, Accessor: "noopaccessor", NamespaceID: namespace.RootNamespaceID, namespace: namespace.RootNamespace}, view); err != nil {
- t.Fatalf("err: %s", err)
- }
-
- mountsFunc := func() []*MountEntry {
- return mounts.Entries
- }
-
- logger := logging.NewVaultLogger(log.Trace)
-
- rb := NewRollbackManager(context.Background(), logger, mountsFunc, router, core)
- rb.period = 10 * time.Millisecond
- return rb, backend
-}
-
-func TestRollbackManager(t *testing.T) {
- m, backend := mockRollback(t)
- if len(backend.Paths) > 0 {
- t.Fatalf("bad: %#v", backend)
- }
-
- m.Start()
- time.Sleep(50 * time.Millisecond)
- m.Stop()
-
- count := len(backend.Paths)
- if count == 0 {
- t.Fatalf("bad: %#v", backend)
- }
- if backend.Paths[0] != "" {
- t.Fatalf("bad: %#v", backend)
- }
-
- time.Sleep(50 * time.Millisecond)
-
- if count != len(backend.Paths) {
- t.Fatalf("should stop requests: %#v", backend)
- }
-}
-
-// TestRollbackManager_ManyWorkers adds 10 backends that require a rollback
-// operation, with 20 workers. The test verifies that the 10
-// work items will run in parallel
-func TestRollbackManager_ManyWorkers(t *testing.T) {
- core := TestCoreWithConfig(t, &CoreConfig{NumRollbackWorkers: 20, RollbackPeriod: time.Millisecond * 10})
- view := NewBarrierView(core.barrier, "logical/")
-
- ran := make(chan string)
- release := make(chan struct{})
- core, _, _ = testCoreUnsealed(t, core)
-
- // create 10 backends
- // when a rollback happens, each backend will try to write to an unbuffered
- // channel, then wait to be released
- for i := 0; i < 10; i++ {
- b := &NoopBackend{}
- b.RequestHandler = func(ctx context.Context, request *logical.Request) (*logical.Response, error) {
- if request.Operation == logical.RollbackOperation {
- ran <- request.Path
- <-release
- }
- return nil, nil
- }
- b.Root = []string{fmt.Sprintf("foo/%d", i)}
- meUUID, err := uuid.GenerateUUID()
- require.NoError(t, err)
- mountEntry := &MountEntry{
- Table: mountTableType,
- UUID: meUUID,
- Accessor: fmt.Sprintf("accessor-%d", i),
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- Path: fmt.Sprintf("logical/foo/%d", i),
- }
- func() {
- core.mountsLock.Lock()
- defer core.mountsLock.Unlock()
- newTable := core.mounts.shallowClone()
- newTable.Entries = append(newTable.Entries, mountEntry)
- core.mounts = newTable
- err = core.router.Mount(b, "logical", mountEntry, view)
- require.NoError(t, core.persistMounts(context.Background(), newTable, &mountEntry.Local))
- }()
- }
-
- timeout, cancel := context.WithTimeout(context.Background(), 20*time.Second)
- defer cancel()
- got := make(map[string]bool)
- hasMore := true
- for hasMore {
- // we're not bounding the number of workers, so we would expect to see
- // all 10 writes to the channel from each of the backends. Once that
- // happens, close the release channel so that the functions can exit
- select {
- case <-timeout.Done():
- require.Fail(t, "test timed out")
- case i := <-ran:
- got[i] = true
- if len(got) == 10 {
- close(release)
- hasMore = false
- }
- }
- }
- done := make(chan struct{})
-
- // start a goroutine to consume the remaining items from the queued work
- go func() {
- for {
- select {
- case <-ran:
- case <-done:
- return
- }
- }
- }()
- // stop the rollback worker, which will wait for all inflight rollbacks to
- // complete
- core.rollback.Stop()
- close(done)
-}
-
-// TestRollbackManager_WorkerPool adds 10 backends that require a rollback
-// operation, with 5 workers. The test verifies that the 5 work items can occur
-// concurrently, and that the remainder of the work is queued and run when
-// workers are available
-func TestRollbackManager_WorkerPool(t *testing.T) {
- core := TestCoreWithConfig(t, &CoreConfig{NumRollbackWorkers: 5, RollbackPeriod: time.Millisecond * 10})
- view := NewBarrierView(core.barrier, "logical/")
-
- ran := make(chan string)
- release := make(chan struct{})
- core, _, _ = testCoreUnsealed(t, core)
-
- // create 10 backends
- // when a rollback happens, each backend will try to write to an unbuffered
- // channel, then wait to be released
- for i := 0; i < 10; i++ {
- b := &NoopBackend{}
- b.RequestHandler = func(ctx context.Context, request *logical.Request) (*logical.Response, error) {
- if request.Operation == logical.RollbackOperation {
- ran <- request.Path
- <-release
- }
- return nil, nil
- }
- b.Root = []string{fmt.Sprintf("foo/%d", i)}
- meUUID, err := uuid.GenerateUUID()
- require.NoError(t, err)
- mountEntry := &MountEntry{
- Table: mountTableType,
- UUID: meUUID,
- Accessor: fmt.Sprintf("accessor-%d", i),
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- Path: fmt.Sprintf("logical/foo/%d", i),
- }
- func() {
- core.mountsLock.Lock()
- defer core.mountsLock.Unlock()
- newTable := core.mounts.shallowClone()
- newTable.Entries = append(newTable.Entries, mountEntry)
- core.mounts = newTable
- err = core.router.Mount(b, "logical", mountEntry, view)
- require.NoError(t, core.persistMounts(context.Background(), newTable, &mountEntry.Local))
- }()
- }
-
- timeout, cancel := context.WithTimeout(context.Background(), 20*time.Second)
- defer cancel()
- got := make(map[string]bool)
- hasMore := true
- for hasMore {
- // we're using 5 workers, so we would expect to see 5 writes to the
- // channel. Once that happens, close the release channel so that the
- // functions can exit and new rollback operations can run
- select {
- case <-timeout.Done():
- require.Fail(t, "test timed out")
- case i := <-ran:
- got[i] = true
- numGot := len(got)
- if numGot == 5 {
- close(release)
- hasMore = false
- }
- }
- }
- done := make(chan struct{})
- defer close(done)
-
- // start a goroutine to consume the remaining items from the queued work
- gotAllPaths := make(chan struct{})
- go func() {
- channelClosed := false
- for {
- select {
- case i := <-ran:
- got[i] = true
-
- // keep this goroutine running even after there are 10 paths.
- // More rollback operations might get queued before Stop() is
- // called, and we don't want them to block on writing the to the
- // ran channel
- if len(got) == 10 && !channelClosed {
- close(gotAllPaths)
- channelClosed = true
- }
- case <-timeout.Done():
- require.Fail(t, "test timed out")
- case <-done:
- return
- }
- }
- }()
-
- // wait until all 10 backends have each ran at least once
- <-gotAllPaths
- // stop the rollback worker, which will wait for any inflight rollbacks to
- // complete
- core.rollback.Stop()
-}
-
-// TestRollbackManager_numRollbackWorkers verifies that the number of rollback
-// workers is parsed from the configuration, but can be overridden by an
-// environment variable. This test cannot be run in parallel because of the
-// environment variable
-func TestRollbackManager_numRollbackWorkers(t *testing.T) {
- testCases := []struct {
- name string
- configWorkers int
- setEnvVar bool
- envVar string
- wantWorkers int
- }{
- {
- name: "default in config",
- configWorkers: RollbackDefaultNumWorkers,
- wantWorkers: RollbackDefaultNumWorkers,
- },
- {
- name: "invalid envvar",
- configWorkers: RollbackDefaultNumWorkers,
- wantWorkers: RollbackDefaultNumWorkers,
- setEnvVar: true,
- envVar: "invalid",
- },
- {
- name: "envvar overrides config",
- configWorkers: RollbackDefaultNumWorkers,
- wantWorkers: 20,
- setEnvVar: true,
- envVar: "20",
- },
- {
- name: "envvar negative",
- configWorkers: RollbackDefaultNumWorkers,
- wantWorkers: RollbackDefaultNumWorkers,
- setEnvVar: true,
- envVar: "-1",
- },
- {
- name: "envvar zero",
- configWorkers: RollbackDefaultNumWorkers,
- wantWorkers: RollbackDefaultNumWorkers,
- setEnvVar: true,
- envVar: "0",
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if tc.setEnvVar {
- t.Setenv(RollbackWorkersEnvVar, tc.envVar)
- }
- core := &Core{numRollbackWorkers: tc.configWorkers}
- r := &RollbackManager{logger: logger.Named("test"), core: core}
- require.Equal(t, tc.wantWorkers, r.numRollbackWorkers())
- })
- }
-}
-
-func TestRollbackManager_Join(t *testing.T) {
- m, backend := mockRollback(t)
- if len(backend.Paths) > 0 {
- t.Fatalf("bad: %#v", backend)
- }
-
- m.Start()
- defer m.Stop()
-
- wg := &sync.WaitGroup{}
- wg.Add(3)
-
- errCh := make(chan error, 3)
- go func() {
- defer wg.Done()
- err := m.Rollback(namespace.RootContext(nil), "foo")
- if err != nil {
- errCh <- err
- }
- }()
-
- go func() {
- defer wg.Done()
- err := m.Rollback(namespace.RootContext(nil), "foo")
- if err != nil {
- errCh <- err
- }
- }()
-
- go func() {
- defer wg.Done()
- err := m.Rollback(namespace.RootContext(nil), "foo")
- if err != nil {
- errCh <- err
- }
- }()
- wg.Wait()
- close(errCh)
- err := <-errCh
- if err != nil {
- t.Fatalf("Error on rollback:%v", err)
- }
-}
diff --git a/vault/router_test.go b/vault/router_test.go
deleted file mode 100644
index d023b35a3..000000000
--- a/vault/router_test.go
+++ /dev/null
@@ -1,633 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "reflect"
- "strings"
- "testing"
-
- "github.com/hashicorp/go-uuid"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-func TestRouter_Mount(t *testing.T) {
- r := NewRouter()
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
-
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
-
- mountEntry := &MountEntry{
- Path: "prod/aws/",
- UUID: meUUID,
- Accessor: "awsaccessor",
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- }
-
- n := &NoopBackend{}
- err = r.Mount(n, "prod/aws/", mountEntry, view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- meUUID, err = uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
-
- err = r.Mount(n, "prod/aws/", &MountEntry{UUID: meUUID, NamespaceID: namespace.RootNamespaceID, namespace: namespace.RootNamespace}, view)
- if !strings.Contains(err.Error(), "cannot mount under existing mount") {
- t.Fatalf("err: %v", err)
- }
-
- meUUID, err = uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
-
- if path := r.MatchingMount(namespace.RootContext(nil), "prod/aws/foo"); path != "prod/aws/" {
- t.Fatalf("bad: %s", path)
- }
-
- if v := r.MatchingStorageByAPIPath(namespace.RootContext(nil), "prod/aws/foo"); v.(*BarrierView) != view {
- t.Fatalf("bad: %v", v)
- }
-
- if path := r.MatchingMount(namespace.RootContext(nil), "stage/aws/foo"); path != "" {
- t.Fatalf("bad: %s", path)
- }
-
- if v := r.MatchingStorageByAPIPath(namespace.RootContext(nil), "stage/aws/foo"); v != nil {
- t.Fatalf("bad: %v", v)
- }
-
- mountEntryFetched := r.MatchingMountByUUID(mountEntry.UUID)
- if mountEntryFetched == nil || !reflect.DeepEqual(mountEntry, mountEntryFetched) {
- t.Fatalf("failed to fetch mount entry using its ID; expected: %#v\n actual: %#v\n", mountEntry, mountEntryFetched)
- }
-
- _, mount, prefix, ok := r.MatchingAPIPrefixByStoragePath(namespace.RootContext(nil), "logical/foo")
- if !ok {
- t.Fatalf("missing storage prefix")
- }
- if mount != "prod/aws/" || prefix != "logical/" {
- t.Fatalf("Bad: %v - %v", mount, prefix)
- }
-
- req := &logical.Request{
- Path: "prod/aws/foo",
- }
- req.SetTokenEntry(&logical.TokenEntry{
- ID: "foo",
- })
- resp, err := r.Route(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
- if req.TokenEntry() == nil || req.TokenEntry().ID != "foo" {
- t.Fatalf("unexpected value for token entry: %v", req.TokenEntry())
- }
-
- // Verify the path
- if len(n.Paths) != 1 || n.Paths[0] != "foo" {
- t.Fatalf("bad: %v", n.Paths)
- }
-
- subMountEntry := &MountEntry{
- Path: "prod/",
- UUID: meUUID,
- Accessor: "prodaccessor",
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- }
-
- if r.MountConflict(namespace.RootContext(nil), "prod/aws/") == "" {
- t.Fatalf("bad: prod/aws/")
- }
-
- // No error is shown here because MountConflict is checked before Mount
- err = r.Mount(n, "prod/", subMountEntry, view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if r.MountConflict(namespace.RootContext(nil), "prod/test") == "" {
- t.Fatalf("bad: prod/test/")
- }
-}
-
-func TestRouter_MountCredential(t *testing.T) {
- r := NewRouter()
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, credentialBarrierPrefix)
-
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
-
- mountEntry := &MountEntry{
- Path: "aws",
- UUID: meUUID,
- Accessor: "awsaccessor",
- NamespaceID: namespace.RootNamespaceID,
- namespace: namespace.RootNamespace,
- }
-
- n := &NoopBackend{}
- err = r.Mount(n, "auth/aws/", mountEntry, view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- meUUID, err = uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
-
- err = r.Mount(n, "auth/aws/", &MountEntry{UUID: meUUID, NamespaceID: namespace.RootNamespaceID, namespace: namespace.RootNamespace}, view)
- if !strings.Contains(err.Error(), "cannot mount under existing mount") {
- t.Fatalf("err: %v", err)
- }
-
- if path := r.MatchingMount(namespace.RootContext(nil), "auth/aws/foo"); path != "auth/aws/" {
- t.Fatalf("bad: %s", path)
- }
-
- if v := r.MatchingStorageByAPIPath(namespace.RootContext(nil), "auth/aws/foo"); v.(*BarrierView) != view {
- t.Fatalf("bad: %v", v)
- }
-
- if path := r.MatchingMount(namespace.RootContext(nil), "auth/stage/aws/foo"); path != "" {
- t.Fatalf("bad: %s", path)
- }
-
- if v := r.MatchingStorageByAPIPath(namespace.RootContext(nil), "auth/stage/aws/foo"); v != nil {
- t.Fatalf("bad: %v", v)
- }
-
- mountEntryFetched := r.MatchingMountByUUID(mountEntry.UUID)
- if mountEntryFetched == nil || !reflect.DeepEqual(mountEntry, mountEntryFetched) {
- t.Fatalf("failed to fetch mount entry using its ID; expected: %#v\n actual: %#v\n", mountEntry, mountEntryFetched)
- }
-
- _, mount, prefix, ok := r.MatchingAPIPrefixByStoragePath(namespace.RootContext(nil), "auth/foo")
- if !ok {
- t.Fatalf("missing storage prefix")
- }
- if mount != "auth/aws" || prefix != credentialBarrierPrefix {
- t.Fatalf("Bad: %v - %v", mount, prefix)
- }
-
- req := &logical.Request{
- Path: "auth/aws/foo",
- }
- resp, err := r.Route(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if resp != nil {
- t.Fatalf("bad: %v", resp)
- }
-
- // Verify the path
- if len(n.Paths) != 1 || n.Paths[0] != "foo" {
- t.Fatalf("bad: %v", n.Paths)
- }
-}
-
-func TestRouter_Unmount(t *testing.T) {
- r := NewRouter()
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
-
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- n := &NoopBackend{}
- err = r.Mount(n, "prod/aws/", &MountEntry{Path: "prod/aws/", UUID: meUUID, Accessor: "awsaccessor", NamespaceID: namespace.RootNamespaceID, namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- err = r.Unmount(namespace.RootContext(nil), "prod/aws/")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req := &logical.Request{
- Path: "prod/aws/foo",
- }
- _, err = r.Route(namespace.RootContext(nil), req)
- if !strings.Contains(err.Error(), "unsupported path") {
- t.Fatalf("err: %v", err)
- }
-
- if _, _, _, ok := r.MatchingAPIPrefixByStoragePath(namespace.RootContext(nil), "logical/foo"); ok {
- t.Fatalf("should not have matching storage prefix")
- }
-}
-
-func TestRouter_Remount(t *testing.T) {
- r := NewRouter()
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
-
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- n := &NoopBackend{}
- me := &MountEntry{Path: "prod/aws/", UUID: meUUID, Accessor: "awsaccessor", NamespaceID: namespace.RootNamespaceID, namespace: namespace.RootNamespace}
- err = r.Mount(n, "prod/aws/", me, view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- me.Path = "stage/aws/"
- err = r.Remount(namespace.RootContext(nil), "prod/aws/", "stage/aws/")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- err = r.Remount(namespace.RootContext(nil), "prod/aws/", "stage/aws/")
- if !strings.Contains(err.Error(), "no mount at") {
- t.Fatalf("err: %v", err)
- }
-
- req := &logical.Request{
- Path: "prod/aws/foo",
- }
- _, err = r.Route(namespace.RootContext(nil), req)
- if !strings.Contains(err.Error(), "unsupported path") {
- t.Fatalf("err: %v", err)
- }
-
- req = &logical.Request{
- Path: "stage/aws/foo",
- }
- _, err = r.Route(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Verify the path
- if len(n.Paths) != 1 || n.Paths[0] != "foo" {
- t.Fatalf("bad: %v", n.Paths)
- }
-
- // Check the resolve from storage still works
- _, mount, prefix, _ := r.MatchingAPIPrefixByStoragePath(namespace.RootContext(nil), "logical/foobar")
- if mount != "stage/aws/" {
- t.Fatalf("bad mount: %s", mount)
- }
- if prefix != "logical/" {
- t.Fatalf("Bad prefix: %s", prefix)
- }
-}
-
-func TestRouter_RootPath(t *testing.T) {
- r := NewRouter()
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
-
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- n := &NoopBackend{
- Root: []string{
- "root",
- "policy/*",
- },
- }
- err = r.Mount(n, "prod/aws/", &MountEntry{UUID: meUUID, Accessor: "awsaccessor", NamespaceID: namespace.RootNamespaceID, namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- type tcase struct {
- path string
- expect bool
- }
- tcases := []tcase{
- {"random", false},
- {"prod/aws/foo", false},
- {"prod/aws/root", true},
- {"prod/aws/root-more", false},
- {"prod/aws/policy", false},
- {"prod/aws/policy/", true},
- {"prod/aws/policy/ops", true},
- }
-
- for _, tc := range tcases {
- out := r.RootPath(namespace.RootContext(nil), tc.path)
- if out != tc.expect {
- t.Fatalf("bad: path: %s expect: %v got %v", tc.path, tc.expect, out)
- }
- }
-}
-
-func TestRouter_LoginPath(t *testing.T) {
- r := NewRouter()
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "auth/")
-
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- n := &NoopBackend{
- Login: []string{
- "login",
- "oauth/*",
- "glob1*",
- "+/wildcard/glob2*",
- "end1/+",
- "end2/+/",
- "end3/+/*",
- "middle1/+/bar",
- "middle2/+/+/bar",
- "+/begin",
- "+/around/+/",
- },
- }
- err = r.Mount(n, "auth/foo/", &MountEntry{UUID: meUUID, Accessor: "authfooaccessor", NamespaceID: namespace.RootNamespaceID, namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- type tcase struct {
- path string
- expect bool
- }
- tcases := []tcase{
- {"random", false},
- {"auth/foo/bar", false},
- {"auth/foo/login", true},
- {"auth/foo/login/", false},
- {"auth/invalid/login", false},
- {"auth/foo/oauth", false},
- {"auth/foo/oauth/", true},
- {"auth/foo/oauth/redirect", true},
- {"auth/foo/oauth/redirect/", true},
- {"auth/foo/oauth/redirect/bar", true},
- {"auth/foo/glob1", true},
- {"auth/foo/glob1/", true},
- {"auth/foo/glob1/redirect", true},
-
- // Wildcard cases
-
- // "+/wildcard/glob2*"
- {"auth/foo/bar/wildcard/glo", false},
- {"auth/foo/bar/wildcard/glob2", true},
- {"auth/foo/bar/wildcard/glob2222", true},
- {"auth/foo/bar/wildcard/glob2/", true},
- {"auth/foo/bar/wildcard/glob2/baz", true},
-
- // "end1/+"
- {"auth/foo/end1", false},
- {"auth/foo/end1/", true},
- {"auth/foo/end1/bar", true},
- {"auth/foo/end1/bar/", false},
- {"auth/foo/end1/bar/baz", false},
- // "end2/+/"
- {"auth/foo/end2", false},
- {"auth/foo/end2/", false},
- {"auth/foo/end2/bar", false},
- {"auth/foo/end2/bar/", true},
- {"auth/foo/end2/bar/baz", false},
- // "end3/+/*"
- {"auth/foo/end3", false},
- {"auth/foo/end3/", false},
- {"auth/foo/end3/bar", false},
- {"auth/foo/end3/bar/", true},
- {"auth/foo/end3/bar/baz", true},
- {"auth/foo/end3/bar/baz/", true},
- {"auth/foo/end3/bar/baz/qux", true},
- {"auth/foo/end3/bar/baz/qux/qoo", true},
- {"auth/foo/end3/bar/baz/qux/qoo/qaa", true},
- // "middle1/+/bar",
- {"auth/foo/middle1/bar", false},
- {"auth/foo/middle1/bar/", false},
- {"auth/foo/middle1/bar/qux", false},
- {"auth/foo/middle1/bar/bar", true},
- {"auth/foo/middle1/bar/bar/", false},
- // "middle2/+/+/bar",
- {"auth/foo/middle2/bar", false},
- {"auth/foo/middle2/bar/", false},
- {"auth/foo/middle2/bar/baz", false},
- {"auth/foo/middle2/bar/baz/", false},
- {"auth/foo/middle2/bar/baz/bar", true},
- {"auth/foo/middle2/bar/baz/bar/", false},
- // "+/begin"
- {"auth/foo/bar/begin", true},
- {"auth/foo/bar/begin/", false},
- {"auth/foo/begin", false},
- // "+/around/+/"
- {"auth/foo/bar/around", false},
- {"auth/foo/bar/around/", false},
- {"auth/foo/bar/around/baz", false},
- {"auth/foo/bar/around/baz/", true},
- {"auth/foo/bar/around/baz/qux", false},
- }
-
- for _, tc := range tcases {
- out := r.LoginPath(namespace.RootContext(nil), tc.path)
- if out != tc.expect {
- t.Fatalf("bad: path: %s expect: %v got %v", tc.path, tc.expect, out)
- }
- }
-}
-
-func TestRouter_Taint(t *testing.T) {
- r := NewRouter()
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
-
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- n := &NoopBackend{}
- err = r.Mount(n, "prod/aws/", &MountEntry{UUID: meUUID, Accessor: "awsaccessor", NamespaceID: namespace.RootNamespaceID, namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- err = r.Taint(namespace.RootContext(nil), "prod/aws/")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "prod/aws/foo",
- }
- _, err = r.Route(namespace.RootContext(nil), req)
- if err.Error() != "unsupported path" {
- t.Fatalf("err: %v", err)
- }
-
- // Rollback and Revoke should work
- req.Operation = logical.RollbackOperation
- _, err = r.Route(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req.Operation = logical.RevokeOperation
- _, err = r.Route(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestRouter_Untaint(t *testing.T) {
- r := NewRouter()
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
-
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- n := &NoopBackend{}
- err = r.Mount(n, "prod/aws/", &MountEntry{UUID: meUUID, Accessor: "awsaccessor", NamespaceID: namespace.RootNamespaceID, namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- err = r.Taint(namespace.RootContext(nil), "prod/aws/")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- err = r.Untaint(namespace.RootContext(nil), "prod/aws/")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "prod/aws/foo",
- }
- _, err = r.Route(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-}
-
-func TestPathsToRadix(t *testing.T) {
- // Provide real paths
- paths := []string{
- "foo",
- "foo/*",
- "sub/bar*",
- }
- r := pathsToRadix(paths)
-
- raw, ok := r.Get("foo")
- if !ok || raw.(bool) != false {
- t.Fatalf("bad: %v (foo)", raw)
- }
-
- raw, ok = r.Get("foo/")
- if !ok || raw.(bool) != true {
- t.Fatalf("bad: %v (foo/)", raw)
- }
-
- raw, ok = r.Get("sub/bar")
- if !ok || raw.(bool) != true {
- t.Fatalf("bad: %v (sub/bar)", raw)
- }
-}
-
-func TestParseUnauthenticatedPaths(t *testing.T) {
- // inputs
- paths := []string{
- "foo",
- "foo/*",
- "sub/bar*",
- }
- wildcardPaths := []string{
- "end/+",
- "+/begin/*",
- "middle/+/bar*",
- }
- allPaths := append(paths, wildcardPaths...)
-
- p, err := parseUnauthenticatedPaths(allPaths)
- if err != nil {
- t.Fatal(err)
- }
-
- // outputs
- wildcardPathsEntry := []wildcardPath{
- {segments: []string{"end", "+"}, isPrefix: false},
- {segments: []string{"+", "begin", ""}, isPrefix: true},
- {segments: []string{"middle", "+", "bar"}, isPrefix: true},
- }
- expected := &loginPathsEntry{
- paths: pathsToRadix(paths),
- wildcardPaths: wildcardPathsEntry,
- }
-
- if !reflect.DeepEqual(expected, p) {
- t.Fatalf("expected: %#v\n actual: %#v\n", expected, p)
- }
-}
-
-func TestParseUnauthenticatedPaths_Error(t *testing.T) {
- type tcase struct {
- paths []string
- err string
- }
- tcases := []tcase{
- {
- []string{"/foo/+*"},
- "path \"/foo/+*\": invalid use of wildcards ('+*' is forbidden)",
- },
- {
- []string{"/foo/*/*"},
- "path \"/foo/*/*\": invalid use of wildcards (multiple '*' is forbidden)",
- },
- {
- []string{"*/foo/*"},
- "path \"*/foo/*\": invalid use of wildcards (multiple '*' is forbidden)",
- },
- {
- []string{"*/foo/"},
- "path \"*/foo/\": invalid use of wildcards ('*' is only allowed at the end of a path)",
- },
- {
- []string{"/foo+"},
- "path \"/foo+\": invalid use of wildcards ('+' is not allowed next to a non-slash)",
- },
- {
- []string{"/+foo"},
- "path \"/+foo\": invalid use of wildcards ('+' is not allowed next to a non-slash)",
- },
- {
- []string{"/++"},
- "path \"/++\": invalid use of wildcards ('+' is not allowed next to a non-slash)",
- },
- }
-
- for _, tc := range tcases {
- _, err := parseUnauthenticatedPaths(tc.paths)
- if err == nil || err != nil && !strings.Contains(err.Error(), tc.err) {
- t.Fatalf("bad: path: %s expect: %v got %v", tc.paths, tc.err, err)
- }
- }
-}
diff --git a/vault/router_testing.go b/vault/router_testing.go
deleted file mode 100644
index d8a41e00a..000000000
--- a/vault/router_testing.go
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "errors"
- "fmt"
- "sync"
- "time"
-
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/sdk/logical"
-)
-
-type RouterTestHandlerFunc func(context.Context, *logical.Request) (*logical.Response, error)
-
-type NoopBackend struct {
- sync.Mutex
-
- Root []string
- Login []string
- Paths []string
- Requests []*logical.Request
- Response *logical.Response
- RequestHandler RouterTestHandlerFunc
- Invalidations []string
- DefaultLeaseTTL time.Duration
- MaxLeaseTTL time.Duration
- BackendType logical.BackendType
-
- RollbackErrs bool
-}
-
-func NoopBackendFactory(_ context.Context, _ *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{}, nil
-}
-
-func NoopBackendRollbackErrFactory(_ context.Context, _ *logical.BackendConfig) (logical.Backend, error) {
- return &NoopBackend{RollbackErrs: true}, nil
-}
-
-func (n *NoopBackend) HandleRequest(ctx context.Context, req *logical.Request) (*logical.Response, error) {
- if req.TokenEntry() != nil {
- panic("got a non-nil TokenEntry")
- }
-
- if n.RollbackErrs && req.Operation == "rollback" {
- return nil, fmt.Errorf("no-op backend rollback has erred out")
- }
-
- var err error
- resp := n.Response
- if n.RequestHandler != nil {
- resp, err = n.RequestHandler(ctx, req)
- }
-
- n.Lock()
- defer n.Unlock()
-
- requestCopy := *req
- n.Paths = append(n.Paths, req.Path)
- n.Requests = append(n.Requests, &requestCopy)
- if req.Storage == nil {
- return nil, fmt.Errorf("missing view")
- }
-
- if req.Path == "panic" {
- panic("as you command")
- }
-
- return resp, err
-}
-
-func (n *NoopBackend) HandleExistenceCheck(ctx context.Context, req *logical.Request) (bool, bool, error) {
- return false, false, nil
-}
-
-func (n *NoopBackend) SpecialPaths() *logical.Paths {
- return &logical.Paths{
- Root: n.Root,
- Unauthenticated: n.Login,
- }
-}
-
-func (n *NoopBackend) System() logical.SystemView {
- defaultLeaseTTLVal := time.Hour * 24
- maxLeaseTTLVal := time.Hour * 24 * 32
- if n.DefaultLeaseTTL > 0 {
- defaultLeaseTTLVal = n.DefaultLeaseTTL
- }
-
- if n.MaxLeaseTTL > 0 {
- maxLeaseTTLVal = n.MaxLeaseTTL
- }
-
- return logical.StaticSystemView{
- DefaultLeaseTTLVal: defaultLeaseTTLVal,
- MaxLeaseTTLVal: maxLeaseTTLVal,
- }
-}
-
-func (n *NoopBackend) Cleanup(ctx context.Context) {
- // noop
-}
-
-func (n *NoopBackend) InvalidateKey(ctx context.Context, k string) {
- n.Invalidations = append(n.Invalidations, k)
-}
-
-func (n *NoopBackend) Setup(ctx context.Context, config *logical.BackendConfig) error {
- return nil
-}
-
-func (n *NoopBackend) Logger() log.Logger {
- return log.NewNullLogger()
-}
-
-func (n *NoopBackend) Initialize(ctx context.Context, req *logical.InitializationRequest) error {
- return nil
-}
-
-func (n *NoopBackend) Type() logical.BackendType {
- if n.BackendType == logical.TypeUnknown {
- return logical.TypeLogical
- }
- return n.BackendType
-}
-
-// InitializableBackend is a backend that knows whether it has been initialized
-// properly.
-type InitializableBackend struct {
- *NoopBackend
- isInitialized bool
-}
-
-func (b *InitializableBackend) Initialize(ctx context.Context, req *logical.InitializationRequest) error {
- if b.isInitialized {
- return errors.New("already initialized")
- }
-
- // do a dummy write, to prove that the storage is not readonly
- entry := &logical.StorageEntry{
- Key: "initialize/zork",
- Value: []byte("quux"),
- }
- err := req.Storage.Put(ctx, entry)
- if err != nil {
- return err
- }
-
- b.isInitialized = true
- return nil
-}
diff --git a/vault/seal/envelope_test.go b/vault/seal/envelope_test.go
deleted file mode 100644
index 98368ba62..000000000
--- a/vault/seal/envelope_test.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package seal
-
-import (
- "bytes"
- "testing"
-)
-
-func TestEnvelope(t *testing.T) {
- input := []byte("test")
- env, err := NewEnvelope().Encrypt(input, nil)
- if err != nil {
- t.Fatal(err)
- }
-
- output, err := NewEnvelope().Decrypt(env, nil)
- if err != nil {
- t.Fatal(err)
- }
-
- if !bytes.Equal(input, output) {
- t.Fatalf("expected the same text: expected %s, got %s", string(input), string(output))
- }
-}
diff --git a/vault/seal/seal_testing.go b/vault/seal/seal_testing.go
deleted file mode 100644
index b902156b8..000000000
--- a/vault/seal/seal_testing.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package seal
-
-import (
- "context"
- "sync"
-
- "github.com/hashicorp/go-hclog"
- wrapping "github.com/hashicorp/go-kms-wrapping/v2"
-)
-
-type TestSealOpts struct {
- Logger hclog.Logger
- StoredKeys StoredKeysSupport
- Secret []byte
- Name wrapping.WrapperType
-}
-
-func NewTestSeal(opts *TestSealOpts) (Access, *ToggleableWrapper) {
- if opts == nil {
- opts = new(TestSealOpts)
- }
-
- w := &ToggleableWrapper{Wrapper: wrapping.NewTestWrapper(opts.Secret)}
- if opts.Name != "" {
- w.wrapperType = &opts.Name
- }
- return NewAccess(w), w
-}
-
-func NewToggleableTestSeal(opts *TestSealOpts) (Access, func(error)) {
- if opts == nil {
- opts = new(TestSealOpts)
- }
-
- w := &ToggleableWrapper{Wrapper: wrapping.NewTestWrapper(opts.Secret)}
- return NewAccess(w), w.SetError
-}
-
-type ToggleableWrapper struct {
- wrapping.Wrapper
- wrapperType *wrapping.WrapperType
- error error
- l sync.RWMutex
-}
-
-func (t *ToggleableWrapper) Encrypt(ctx context.Context, bytes []byte, opts ...wrapping.Option) (*wrapping.BlobInfo, error) {
- t.l.RLock()
- defer t.l.RUnlock()
- if t.error != nil {
- return nil, t.error
- }
- return t.Wrapper.Encrypt(ctx, bytes, opts...)
-}
-
-func (t *ToggleableWrapper) Decrypt(ctx context.Context, info *wrapping.BlobInfo, opts ...wrapping.Option) ([]byte, error) {
- t.l.RLock()
- defer t.l.RUnlock()
- if t.error != nil {
- return nil, t.error
- }
- return t.Wrapper.Decrypt(ctx, info, opts...)
-}
-
-func (t *ToggleableWrapper) SetError(err error) {
- t.l.Lock()
- defer t.l.Unlock()
- t.error = err
-}
-
-func (t *ToggleableWrapper) Type(ctx context.Context) (wrapping.WrapperType, error) {
- if t.wrapperType != nil {
- return *t.wrapperType, nil
- }
- return t.Wrapper.Type(ctx)
-}
-
-var _ wrapping.Wrapper = &ToggleableWrapper{}
diff --git a/vault/seal_autoseal_test.go b/vault/seal_autoseal_test.go
deleted file mode 100644
index dec856606..000000000
--- a/vault/seal_autoseal_test.go
+++ /dev/null
@@ -1,238 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "bytes"
- "context"
- "errors"
- "reflect"
- "strings"
- "testing"
- "time"
-
- "github.com/armon/go-metrics"
- "github.com/hashicorp/vault/helper/metricsutil"
-
- proto "github.com/golang/protobuf/proto"
- wrapping "github.com/hashicorp/go-kms-wrapping/v2"
- "github.com/hashicorp/vault/sdk/physical"
- "github.com/hashicorp/vault/vault/seal"
-)
-
-// phy implements physical.Backend. It maps keys to a slice of entries.
-// Each call to Put appends the entry to the slice of entries for that
-// key. No deduplication is done. This allows the test for UpgradeKeys to
-// verify entries are only being updated when the underlying encryption key
-// has been updated.
-type phy struct {
- t *testing.T
- entries map[string][]*physical.Entry
-}
-
-var _ physical.Backend = (*phy)(nil)
-
-func newTestBackend(t *testing.T) *phy {
- return &phy{
- t: t,
- entries: make(map[string][]*physical.Entry),
- }
-}
-
-func (p *phy) Put(_ context.Context, entry *physical.Entry) error {
- p.entries[entry.Key] = append(p.entries[entry.Key], entry)
- return nil
-}
-
-func (p *phy) Get(_ context.Context, key string) (*physical.Entry, error) {
- entries := p.entries[key]
- if entries == nil {
- return nil, nil
- }
- return entries[len(entries)-1], nil
-}
-
-func (p *phy) Delete(_ context.Context, key string) error {
- p.t.Errorf("Delete called on phy: key: %v", key)
- return nil
-}
-
-func (p *phy) List(_ context.Context, prefix string) ([]string, error) {
- p.t.Errorf("List called on phy: prefix: %v", prefix)
- return []string{}, nil
-}
-
-func (p *phy) Len() int {
- return len(p.entries)
-}
-
-func TestAutoSeal_UpgradeKeys(t *testing.T) {
- core, _, _ := TestCoreUnsealed(t)
- testSeal, toggleableWrapper := seal.NewTestSeal(nil)
-
- var encKeys []string
- changeKey := func(key string) {
- encKeys = append(encKeys, key)
- toggleableWrapper.Wrapper.(*wrapping.TestWrapper).SetKeyId(key)
- }
-
- // Set initial encryption key.
- changeKey("kaz")
-
- autoSeal, err := NewAutoSeal(testSeal)
- if err != nil {
- t.Fatal(err)
- }
-
- autoSeal.SetCore(core)
- pBackend := newTestBackend(t)
- core.physical = pBackend
-
- ctx := context.Background()
-
- inkeys := [][]byte{[]byte("grist"), []byte("house")}
- if err := autoSeal.SetStoredKeys(ctx, inkeys); err != nil {
- t.Fatalf("SetStoredKeys: want no error, got %v", err)
- }
-
- inRecoveryKey := []byte("falernum")
- if err := autoSeal.SetRecoveryKey(ctx, inRecoveryKey); err != nil {
- t.Fatalf("SetRecoveryKey: want no error, got %v", err)
- }
-
- check := func() {
- // The values of the stored keys should never change.
- outkeys, err := autoSeal.GetStoredKeys(ctx)
- if err != nil {
- t.Fatalf("GetStoredKeys: want no error, got %v", err)
- }
- if !reflect.DeepEqual(inkeys, outkeys) {
- t.Errorf("incorrect stored keys: want %v, got %v", inkeys, outkeys)
- }
-
- // The value of the recovery key should also never change.
- outRecoveryKey, err := autoSeal.RecoveryKey(ctx)
- if err != nil {
- t.Fatalf("RecoveryKey: want no error, got %v", err)
- }
- if !bytes.Equal(inRecoveryKey, outRecoveryKey) {
- t.Errorf("incorrect recovery key: want %q, got %q", inRecoveryKey, outRecoveryKey)
- }
-
- // There should only be 2 entries in the physical backend. One for
- // the stored keys and one for the recovery key.
- if want, got := 2, pBackend.Len(); want != got {
- t.Errorf("backend unexpected Len: want %d, got %d", want, got)
- }
-
- for phyKey, phyEntries := range pBackend.entries {
- // Calling UpgradeKeys should only add an entry if the key has
- // changed.
- if keyCount, entryCount := len(encKeys), len(phyEntries); keyCount != entryCount {
- t.Errorf("phyKey = %s: encryption key count not equal to entry count: keys=%d, entries=%d", phyKey, keyCount, entryCount)
- }
-
- // Each phyEntry should correspond to a key at the same index
- // in encKeys. Iterate over each phyEntry and verify it was
- // encrypted with its corresponding key in encKeys.
- for i, phyEntry := range phyEntries {
- blobInfo := &wrapping.BlobInfo{}
- if err := proto.Unmarshal(phyEntry.Value, blobInfo); err != nil {
- t.Errorf("phyKey = %s: failed to proto decode stored keys: %s", phyKey, err)
- }
- if blobInfo.KeyInfo == nil {
- t.Errorf("phyKey = %s: KeyInfo missing: %+v", phyKey, blobInfo)
- }
- if want, got := encKeys[i], blobInfo.KeyInfo.KeyId; want != got {
- t.Errorf("phyKey = %s: Incorrect encryption key: want %s, got %s", phyKey, want, got)
- }
- }
- }
- }
-
- // Verify the current state is correct before calling UpgradeKeys.
- check()
-
- // Call UpgradeKeys before changing the encryption key and verify
- // nothing has changed.
- if err := autoSeal.UpgradeKeys(ctx); err != nil {
- t.Fatalf("UpgradeKeys: want no error, got %v", err)
- }
- check()
-
- // Change the encryption key, call UpgradeKeys, then verify the stored
- // keys and recovery key has been re-encrypted with the new encryption
- // key.
- changeKey("primanti")
- if err := autoSeal.UpgradeKeys(ctx); err != nil {
- t.Fatalf("UpgradeKeys: want no error, got %v", err)
- }
- check()
-}
-
-func TestAutoSeal_HealthCheck(t *testing.T) {
- inmemSink := metrics.NewInmemSink(
- 1000000*time.Hour,
- 2000000*time.Hour)
-
- metricsConf := metrics.DefaultConfig("")
- metricsConf.EnableHostname = false
- metricsConf.EnableHostnameLabel = false
- metricsConf.EnableServiceLabel = false
- metricsConf.EnableTypePrefix = false
-
- metrics.NewGlobal(metricsConf, inmemSink)
-
- pBackend := newTestBackend(t)
- testSealAccess, setErr := seal.NewToggleableTestSeal(nil)
- core, _, _ := TestCoreUnsealedWithConfig(t, &CoreConfig{
- MetricSink: metricsutil.NewClusterMetricSink("", inmemSink),
- Physical: pBackend,
- })
- sealHealthTestIntervalNominal = 10 * time.Millisecond
- sealHealthTestIntervalUnhealthy = 10 * time.Millisecond
- autoSeal, err := NewAutoSeal(testSealAccess)
- if err != nil {
- t.Fatal(err)
- }
-
- autoSeal.SetCore(core)
- core.seal = autoSeal
- autoSeal.StartHealthCheck()
- defer autoSeal.StopHealthCheck()
- setErr(errors.New("disconnected"))
-
- asu := strings.Join(autoSealUnavailableDuration, ".") + ";cluster=" + core.clusterName
- tries := 10
- for tries = 10; tries > 0; tries-- {
- intervals := inmemSink.Data()
- if len(intervals) == 1 {
- interval := inmemSink.Data()[0]
-
- if _, ok := interval.Gauges[asu]; ok {
- if interval.Gauges[asu].Value > 0 {
- break
- }
- }
- }
- time.Sleep(100 * time.Millisecond)
- }
- if tries == 0 {
- t.Fatalf("Expected value metric %s to be non-zero", asu)
- }
-
- setErr(nil)
- time.Sleep(50 * time.Millisecond)
- intervals := inmemSink.Data()
- if len(intervals) == 1 {
- interval := inmemSink.Data()[0]
-
- if _, ok := interval.Gauges[asu]; !ok {
- t.Fatalf("Expected metrics to include a value for gauge %s", asu)
- }
- if interval.Gauges[asu].Value != 0 {
- t.Fatalf("Expected value metric %s to be zero", asu)
- }
- }
-}
diff --git a/vault/seal_test.go b/vault/seal_test.go
deleted file mode 100644
index 48aba5ec3..000000000
--- a/vault/seal_test.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "reflect"
- "testing"
-)
-
-// TestDefaultSeal_Config exercises Shamir SetBarrierConfig and BarrierConfig.
-// Note that this is a little questionable, because we're doing an init and
-// unseal, then changing the barrier config using an internal function instead
-// of an API. In other words if your change break this test, it might be more
-// the test's fault than your changes.
-func TestDefaultSeal_Config(t *testing.T) {
- bc := &SealConfig{
- SecretShares: 4,
- SecretThreshold: 2,
- }
- core, _, _ := TestCoreUnsealed(t)
-
- defSeal := NewDefaultSeal(nil)
- defSeal.SetCore(core)
- err := defSeal.SetBarrierConfig(context.Background(), bc)
- if err != nil {
- t.Fatal(err)
- }
-
- newBc, err := defSeal.BarrierConfig(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(*bc, *newBc) {
- t.Fatal("config mismatch")
- }
-
- // Now, test without the benefit of the cached value in the seal
- defSeal = NewDefaultSeal(nil)
- defSeal.SetCore(core)
- newBc, err = defSeal.BarrierConfig(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(*bc, *newBc) {
- t.Fatal("config mismatch")
- }
-}
diff --git a/vault/seal_testing.go b/vault/seal_testing.go
deleted file mode 100644
index 87ba97061..000000000
--- a/vault/seal_testing.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
-
- "github.com/hashicorp/vault/vault/seal"
- vaultseal "github.com/hashicorp/vault/vault/seal"
- testing "github.com/mitchellh/go-testing-interface"
-)
-
-func TestCoreUnsealedWithConfigs(t testing.T, barrierConf, recoveryConf *SealConfig) (*Core, [][]byte, [][]byte, string) {
- t.Helper()
- opts := &seal.TestSealOpts{}
- if recoveryConf == nil {
- opts.StoredKeys = seal.StoredKeysSupportedShamirRoot
- }
- return TestCoreUnsealedWithConfigSealOpts(t, barrierConf, recoveryConf, opts)
-}
-
-func TestCoreUnsealedWithConfigSealOpts(t testing.T, barrierConf, recoveryConf *SealConfig, sealOpts *seal.TestSealOpts) (*Core, [][]byte, [][]byte, string) {
- t.Helper()
- seal := NewTestSeal(t, sealOpts)
- core := TestCoreWithSeal(t, seal, false)
- result, err := core.Initialize(context.Background(), &InitParams{
- BarrierConfig: barrierConf,
- RecoveryConfig: recoveryConf,
- LegacyShamirSeal: sealOpts.StoredKeys == vaultseal.StoredKeysNotSupported,
- })
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- err = core.UnsealWithStoredKeys(context.Background())
- if err != nil && IsFatalError(err) {
- t.Fatalf("err: %s", err)
- }
- if core.Sealed() {
- for _, key := range result.SecretShares {
- if _, err := core.Unseal(TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- if core.Sealed() {
- t.Fatal("should not be sealed")
- }
- }
-
- return core, result.SecretShares, result.RecoveryShares, result.RootToken
-}
diff --git a/vault/seal_testing_util.go b/vault/seal_testing_util.go
deleted file mode 100644
index cfea228fc..000000000
--- a/vault/seal_testing_util.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "github.com/hashicorp/go-hclog"
- aeadwrapper "github.com/hashicorp/go-kms-wrapping/wrappers/aead/v2"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/vault/seal"
- testing "github.com/mitchellh/go-testing-interface"
-)
-
-func NewTestSeal(t testing.T, opts *seal.TestSealOpts) Seal {
- t.Helper()
- if opts == nil {
- opts = &seal.TestSealOpts{}
- }
- if opts.Logger == nil {
- opts.Logger = logging.NewVaultLogger(hclog.Debug)
- }
-
- switch opts.StoredKeys {
- case seal.StoredKeysSupportedShamirRoot:
- newSeal := NewDefaultSeal(seal.NewAccess(aeadwrapper.NewShamirWrapper()))
- // Need StoredShares set or this will look like a legacy shamir seal.
- newSeal.SetCachedBarrierConfig(&SealConfig{
- StoredShares: 1,
- SecretThreshold: 1,
- SecretShares: 1,
- })
- return newSeal
- case seal.StoredKeysNotSupported:
- newSeal := NewDefaultSeal(seal.NewAccess(aeadwrapper.NewShamirWrapper()))
- newSeal.SetCachedBarrierConfig(&SealConfig{
- StoredShares: 0,
- SecretThreshold: 1,
- SecretShares: 1,
- })
- return newSeal
- default:
- access, _ := seal.NewTestSeal(opts)
- seal, err := NewAutoSeal(access)
- if err != nil {
- t.Fatal(err)
- }
- return seal
- }
-}
diff --git a/vault/sealunwrapper_test.go b/vault/sealunwrapper_test.go
deleted file mode 100644
index e984250d0..000000000
--- a/vault/sealunwrapper_test.go
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-//go:build !enterprise
-
-package vault
-
-import (
- "bytes"
- "context"
- "sync"
- "testing"
-
- proto "github.com/golang/protobuf/proto"
- log "github.com/hashicorp/go-hclog"
- wrapping "github.com/hashicorp/go-kms-wrapping/v2"
- "github.com/hashicorp/vault/sdk/physical"
- "github.com/hashicorp/vault/sdk/physical/inmem"
-)
-
-func TestSealUnwrapper(t *testing.T) {
- logger := log.New(&log.LoggerOptions{
- Mutex: &sync.Mutex{},
- })
-
- // Test without transactions
- phys, err := inmem.NewInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- performTestSealUnwrapper(t, phys, logger)
-
- // Test with transactions
- tPhys, err := inmem.NewTransactionalInmemHA(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- performTestSealUnwrapper(t, tPhys, logger)
-}
-
-func performTestSealUnwrapper(t *testing.T, phys physical.Backend, logger log.Logger) {
- ctx := context.Background()
- base := &CoreConfig{
- Physical: phys,
- }
- cluster := NewTestCluster(t, base, &TestClusterOptions{
- Logger: logger,
- })
- cluster.Start()
- defer cluster.Cleanup()
-
- // Read a value and then save it back in a proto message
- entry, err := phys.Get(ctx, "core/master")
- if err != nil {
- t.Fatal(err)
- }
- if len(entry.Value) == 0 {
- t.Fatal("got no value for master")
- }
- // Save the original for comparison later
- origBytes := make([]byte, len(entry.Value))
- copy(origBytes, entry.Value)
- se := &wrapping.BlobInfo{
- Ciphertext: entry.Value,
- }
- seb, err := proto.Marshal(se)
- if err != nil {
- t.Fatal(err)
- }
- // Write the canary
- entry.Value = append(seb, 's')
- // Save the protobuf value for comparison later
- pBytes := make([]byte, len(entry.Value))
- copy(pBytes, entry.Value)
- if err = phys.Put(ctx, entry); err != nil {
- t.Fatal(err)
- }
-
- // At this point we should be able to read through the standby cores,
- // successfully decode it, but be able to unmarshal it when read back from
- // the underlying physical store. When we read from active, it should both
- // successfully decode it and persist it back.
- checkValue := func(core *Core, wrapped bool) {
- entry, err := core.physical.Get(ctx, "core/master")
- if err != nil {
- t.Fatal(err)
- }
- if entry == nil {
- t.Fatal("nil entry")
- }
- if !bytes.Equal(entry.Value, origBytes) {
- t.Fatalf("mismatched original bytes and unwrapped entry bytes:\ngot:\n%v\nexpected:\n%v", entry.Value, origBytes)
- }
- underlyingEntry, err := phys.Get(ctx, "core/master")
- if err != nil {
- t.Fatal(err)
- }
- switch wrapped {
- case true:
- if !bytes.Equal(underlyingEntry.Value, pBytes) {
- t.Fatalf("mismatched original bytes and proto entry bytes:\ngot:\n%v\nexpected:\n%v", underlyingEntry.Value, pBytes)
- }
- default:
- if !bytes.Equal(underlyingEntry.Value, origBytes) {
- t.Fatalf("mismatched original bytes and unwrapped entry bytes:\ngot:\n%v\nexpected:\n%v", underlyingEntry.Value, origBytes)
- }
- }
- }
-
- TestWaitActive(t, cluster.Cores[0].Core)
- checkValue(cluster.Cores[2].Core, true)
- checkValue(cluster.Cores[1].Core, true)
- checkValue(cluster.Cores[0].Core, false)
-}
diff --git a/vault/test_cluster_detect_deadlock.go b/vault/test_cluster_detect_deadlock.go
deleted file mode 100644
index a6a9eef96..000000000
--- a/vault/test_cluster_detect_deadlock.go
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-//go:build deadlock
-
-package vault
-
-const TestDeadlockDetection = "statelock"
diff --git a/vault/test_cluster_do_not_detect_deadlock.go b/vault/test_cluster_do_not_detect_deadlock.go
deleted file mode 100644
index 27db3ae05..000000000
--- a/vault/test_cluster_do_not_detect_deadlock.go
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-//go:build !deadlock
-
-package vault
-
-const TestDeadlockDetection = ""
diff --git a/vault/testing.go b/vault/testing.go
index f4f712db0..dcc16b355 100644
--- a/vault/testing.go
+++ b/vault/testing.go
@@ -4,2317 +4,12 @@
package vault
import (
- "bytes"
- "context"
- "crypto/ecdsa"
- "crypto/elliptic"
- "crypto/rand"
- "crypto/sha256"
- "crypto/tls"
- "crypto/x509"
- "crypto/x509/pkix"
- "encoding/base64"
- "encoding/pem"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "math/big"
- mathrand "math/rand"
- "net"
"net/http"
- "os"
- "path/filepath"
- "reflect"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/armon/go-metrics"
- "github.com/hashicorp/go-cleanhttp"
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/go-secure-stdlib/reloadutil"
- raftlib "github.com/hashicorp/raft"
- "github.com/hashicorp/vault/api"
- "github.com/hashicorp/vault/audit"
- auditFile "github.com/hashicorp/vault/builtin/audit/file"
- "github.com/hashicorp/vault/command/server"
- "github.com/hashicorp/vault/helper/constants"
- "github.com/hashicorp/vault/helper/experiments"
- "github.com/hashicorp/vault/helper/metricsutil"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/helper/testhelpers/corehelpers"
- "github.com/hashicorp/vault/helper/testhelpers/pluginhelpers"
- "github.com/hashicorp/vault/internalshared/configutil"
- v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
- "github.com/hashicorp/vault/sdk/framework"
- "github.com/hashicorp/vault/sdk/helper/consts"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/helper/pluginutil"
- "github.com/hashicorp/vault/sdk/helper/testcluster"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/hashicorp/vault/sdk/physical"
- physInmem "github.com/hashicorp/vault/sdk/physical/inmem"
- backendplugin "github.com/hashicorp/vault/sdk/plugin"
- "github.com/hashicorp/vault/vault/cluster"
- "github.com/hashicorp/vault/vault/seal"
- "github.com/mitchellh/copystructure"
- "github.com/mitchellh/go-testing-interface"
- "golang.org/x/crypto/ed25519"
- "golang.org/x/net/http2"
)
// This file contains a number of methods that are useful for unit
// tests within other packages.
-const (
- testSharedPublicKey = `
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9i+hFxZHGo6KblVme4zrAcJstR6I0PTJozW286X4WyvPnkMYDQ5mnhEYC7UWCvjoTWbPEXPX7NjhRtwQTGD67bV+lrxgfyzK1JZbUXK4PwgKJvQD+XyyWYMzDgGSQY61KUSqCxymSm/9NZkPU3ElaQ9xQuTzPpztM4ROfb8f2Yv6/ZESZsTo0MTAkp8Pcy+WkioI/uJ1H7zqs0EA4OMY4aDJRu0UtP4rTVeYNEAuRXdX+eH4aW3KMvhzpFTjMbaJHJXlEeUm2SaX5TNQyTOvghCeQILfYIL/Ca2ij8iwCmulwdV6eQGfd4VDu40PvSnmfoaE38o6HaPnX0kUcnKiT
-`
- testSharedPrivateKey = `
------BEGIN RSA PRIVATE KEY-----
-MIIEogIBAAKCAQEAvYvoRcWRxqOim5VZnuM6wHCbLUeiND0yaM1tvOl+Fsrz55DG
-A0OZp4RGAu1Fgr46E1mzxFz1+zY4UbcEExg+u21fpa8YH8sytSWW1FyuD8ICib0A
-/l8slmDMw4BkkGOtSlEqgscpkpv/TWZD1NxJWkPcULk8z6c7TOETn2/H9mL+v2RE
-mbE6NDEwJKfD3MvlpIqCP7idR+86rNBAODjGOGgyUbtFLT+K01XmDRALkV3V/nh+
-GltyjL4c6RU4zG2iRyV5RHlJtkml+UzUMkzr4IQnkCC32CC/wmtoo/IsAprpcHVe
-nkBn3eFQ7uND70p5n6GhN/KOh2j519JFHJyokwIDAQABAoIBAHX7VOvBC3kCN9/x
-+aPdup84OE7Z7MvpX6w+WlUhXVugnmsAAVDczhKoUc/WktLLx2huCGhsmKvyVuH+
-MioUiE+vx75gm3qGx5xbtmOfALVMRLopjCnJYf6EaFA0ZeQ+NwowNW7Lu0PHmAU8
-Z3JiX8IwxTz14DU82buDyewO7v+cEr97AnERe3PUcSTDoUXNaoNxjNpEJkKREY6h
-4hAY676RT/GsRcQ8tqe/rnCqPHNd7JGqL+207FK4tJw7daoBjQyijWuB7K5chSal
-oPInylM6b13ASXuOAOT/2uSUBWmFVCZPDCmnZxy2SdnJGbsJAMl7Ma3MUlaGvVI+
-Tfh1aQkCgYEA4JlNOabTb3z42wz6mz+Nz3JRwbawD+PJXOk5JsSnV7DtPtfgkK9y
-6FTQdhnozGWShAvJvc+C4QAihs9AlHXoaBY5bEU7R/8UK/pSqwzam+MmxmhVDV7G
-IMQPV0FteoXTaJSikhZ88mETTegI2mik+zleBpVxvfdhE5TR+lq8Br0CgYEA2AwJ
-CUD5CYUSj09PluR0HHqamWOrJkKPFPwa+5eiTTCzfBBxImYZh7nXnWuoviXC0sg2
-AuvCW+uZ48ygv/D8gcz3j1JfbErKZJuV+TotK9rRtNIF5Ub7qysP7UjyI7zCssVM
-kuDd9LfRXaB/qGAHNkcDA8NxmHW3gpln4CFdSY8CgYANs4xwfercHEWaJ1qKagAe
-rZyrMpffAEhicJ/Z65lB0jtG4CiE6w8ZeUMWUVJQVcnwYD+4YpZbX4S7sJ0B8Ydy
-AhkSr86D/92dKTIt2STk6aCN7gNyQ1vW198PtaAWH1/cO2UHgHOy3ZUt5X/Uwxl9
-cex4flln+1Viumts2GgsCQKBgCJH7psgSyPekK5auFdKEr5+Gc/jB8I/Z3K9+g4X
-5nH3G1PBTCJYLw7hRzw8W/8oALzvddqKzEFHphiGXK94Lqjt/A4q1OdbCrhiE68D
-My21P/dAKB1UYRSs9Y8CNyHCjuZM9jSMJ8vv6vG/SOJPsnVDWVAckAbQDvlTHC9t
-O98zAoGAcbW6uFDkrv0XMCpB9Su3KaNXOR0wzag+WIFQRXCcoTvxVi9iYfUReQPi
-oOyBJU/HMVvBfv4g+OVFLVgSwwm6owwsouZ0+D/LasbuHqYyqYqdyPJQYzWA2Y+F
-+B6f4RoPdSXj24JHPg/ioRxjaj094UXJxua2yfkcecGNEuBQHSs=
------END RSA PRIVATE KEY-----
-`
-)
-
-// TestCore returns a pure in-memory, uninitialized core for testing.
-func TestCore(t testing.T) *Core {
- return TestCoreWithSeal(t, nil, false)
-}
-
-// TestCoreRaw returns a pure in-memory, uninitialized core for testing. The raw
-// storage endpoints are enabled with this core.
-func TestCoreRaw(t testing.T) *Core {
- return TestCoreWithSeal(t, nil, true)
-}
-
-// TestCoreNewSeal returns a pure in-memory, uninitialized core with
-// the new seal configuration.
-func TestCoreNewSeal(t testing.T) *Core {
- seal := NewTestSeal(t, nil)
- return TestCoreWithSeal(t, seal, false)
-}
-
-// TestCoreWithConfig returns a pure in-memory, uninitialized core with the
-// specified core configurations overridden for testing.
-func TestCoreWithConfig(t testing.T, conf *CoreConfig) *Core {
- return TestCoreWithSealAndUI(t, conf)
-}
-
-// TestCoreWithSeal returns a pure in-memory, uninitialized core with the
-// specified seal for testing.
-func TestCoreWithSeal(t testing.T, testSeal Seal, enableRaw bool) *Core {
- conf := &CoreConfig{
- Seal: testSeal,
- EnableUI: false,
- EnableRaw: enableRaw,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- AuditBackends: map[string]audit.Factory{
- "file": auditFile.Factory,
- },
- }
- return TestCoreWithSealAndUI(t, conf)
-}
-
-func TestCoreWithDeadlockDetection(t testing.T, testSeal Seal, enableRaw bool) *Core {
- conf := &CoreConfig{
- Seal: testSeal,
- EnableUI: false,
- EnableRaw: enableRaw,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- AuditBackends: map[string]audit.Factory{
- "file": auditFile.Factory,
- },
- DetectDeadlocks: "expiration,quotas,statelock",
- }
- return TestCoreWithSealAndUI(t, conf)
-}
-
-func TestCoreWithCustomResponseHeaderAndUI(t testing.T, CustomResponseHeaders map[string]map[string]string, enableUI bool) (*Core, [][]byte, string) {
- confRaw := &server.Config{
- SharedConfig: &configutil.SharedConfig{
- Listeners: []*configutil.Listener{
- {
- Type: "tcp",
- Address: "127.0.0.1",
- CustomResponseHeaders: CustomResponseHeaders,
- },
- },
- DisableMlock: true,
- },
- }
- conf := &CoreConfig{
- RawConfig: confRaw,
- EnableUI: enableUI,
- EnableRaw: true,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- }
- core := TestCoreWithSealAndUI(t, conf)
- return testCoreUnsealed(t, core)
-}
-
-func TestCoreUI(t testing.T, enableUI bool) *Core {
- conf := &CoreConfig{
- EnableUI: enableUI,
- EnableRaw: true,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- }
- return TestCoreWithSealAndUI(t, conf)
-}
-
-func TestCoreWithSealAndUI(t testing.T, opts *CoreConfig) *Core {
- c := TestCoreWithSealAndUINoCleanup(t, opts)
-
- t.Cleanup(func() {
- defer func() {
- if r := recover(); r != nil {
- t.Log("panic closing core during cleanup", "panic", r)
- }
- }()
- err := c.ShutdownWait()
- if err != nil {
- t.Logf("shutdown returned error: %v", err)
- }
- })
- return c
-}
-
-func TestCoreWithSealAndUINoCleanup(t testing.T, opts *CoreConfig) *Core {
- logger := corehelpers.NewTestLogger(t)
- physicalBackend, err := physInmem.NewInmem(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
-
- errInjector := physical.NewErrorInjector(physicalBackend, 0, logger)
-
- // Start off with base test core config
- conf := testCoreConfig(t, errInjector, logger)
-
- // Override config values with ones that gets passed in
- conf.EnableUI = opts.EnableUI
- conf.EnableRaw = opts.EnableRaw
- conf.EnableIntrospection = opts.EnableIntrospection
- conf.Seal = opts.Seal
- conf.LicensingConfig = opts.LicensingConfig
- conf.DisableKeyEncodingChecks = opts.DisableKeyEncodingChecks
- conf.MetricsHelper = opts.MetricsHelper
- conf.MetricSink = opts.MetricSink
- conf.NumExpirationWorkers = numExpirationWorkersTest
- conf.RawConfig = opts.RawConfig
- conf.EnableResponseHeaderHostname = opts.EnableResponseHeaderHostname
- conf.DisableSSCTokens = opts.DisableSSCTokens
- conf.PluginDirectory = opts.PluginDirectory
- conf.DetectDeadlocks = opts.DetectDeadlocks
- conf.Experiments = []string{experiments.VaultExperimentEventsAlpha1}
- conf.AdministrativeNamespacePath = opts.AdministrativeNamespacePath
- conf.ImpreciseLeaseRoleTracking = opts.ImpreciseLeaseRoleTracking
-
- if opts.Logger != nil {
- conf.Logger = opts.Logger
- }
-
- if opts.RedirectAddr != "" {
- conf.RedirectAddr = opts.RedirectAddr
- }
-
- for k, v := range opts.LogicalBackends {
- conf.LogicalBackends[k] = v
- }
- for k, v := range opts.CredentialBackends {
- conf.CredentialBackends[k] = v
- }
-
- for k, v := range opts.AuditBackends {
- conf.AuditBackends[k] = v
- }
- if opts.RollbackPeriod != time.Duration(0) {
- conf.RollbackPeriod = opts.RollbackPeriod
- }
- if opts.NumRollbackWorkers != 0 {
- conf.NumRollbackWorkers = opts.NumRollbackWorkers
- }
-
- conf.ActivityLogConfig = opts.ActivityLogConfig
- testApplyEntBaseConfig(conf, opts)
-
- c, err := NewCore(conf)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- return c
-}
-
-func testCoreConfig(t testing.T, physicalBackend physical.Backend, logger log.Logger) *CoreConfig {
- t.Helper()
- noopAudits := map[string]audit.Factory{
- "noop": corehelpers.NoopAuditFactory(nil),
- }
-
- noopBackends := make(map[string]logical.Factory)
- noopBackends["noop"] = func(ctx context.Context, config *logical.BackendConfig) (logical.Backend, error) {
- b := new(framework.Backend)
- b.Setup(ctx, config)
- b.BackendType = logical.TypeCredential
- return b, nil
- }
- noopBackends["http"] = func(ctx context.Context, config *logical.BackendConfig) (logical.Backend, error) {
- return new(rawHTTP), nil
- }
-
- credentialBackends := make(map[string]logical.Factory)
- for backendName, backendFactory := range noopBackends {
- credentialBackends[backendName] = backendFactory
- }
- for backendName, backendFactory := range testCredentialBackends {
- credentialBackends[backendName] = backendFactory
- }
-
- logicalBackends := make(map[string]logical.Factory)
- for backendName, backendFactory := range noopBackends {
- logicalBackends[backendName] = backendFactory
- }
-
- logicalBackends["kv"] = LeasedPassthroughBackendFactory
- for backendName, backendFactory := range testLogicalBackends {
- logicalBackends[backendName] = backendFactory
- }
-
- conf := &CoreConfig{
- Physical: physicalBackend,
- AuditBackends: noopAudits,
- LogicalBackends: logicalBackends,
- CredentialBackends: credentialBackends,
- DisableMlock: true,
- Logger: logger,
- NumRollbackWorkers: 10,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- }
-
- return conf
-}
-
-// TestCoreInit initializes the core with a single key, and returns
-// the key that must be used to unseal the core and a root token.
-func TestCoreInit(t testing.T, core *Core) ([][]byte, string) {
- t.Helper()
- secretShares, _, root := TestCoreInitClusterWrapperSetup(t, core, nil)
- return secretShares, root
-}
-
-func TestCoreInitClusterWrapperSetup(t testing.T, core *Core, handler http.Handler) ([][]byte, [][]byte, string) {
- t.Helper()
- core.SetClusterHandler(handler)
-
- barrierConfig := &SealConfig{
- SecretShares: 3,
- SecretThreshold: 3,
- }
-
- switch core.seal.StoredKeysSupported() {
- case seal.StoredKeysNotSupported:
- barrierConfig.StoredShares = 0
- default:
- barrierConfig.StoredShares = 1
- }
-
- recoveryConfig := &SealConfig{
- SecretShares: 3,
- SecretThreshold: 3,
- }
-
- initParams := &InitParams{
- BarrierConfig: barrierConfig,
- RecoveryConfig: recoveryConfig,
- }
- if core.seal.StoredKeysSupported() == seal.StoredKeysNotSupported {
- initParams.LegacyShamirSeal = true
- }
- result, err := core.Initialize(context.Background(), initParams)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- innerToken, err := core.DecodeSSCToken(result.RootToken)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- return result.SecretShares, result.RecoveryShares, innerToken
-}
-
-func TestCoreUnseal(core *Core, key []byte) (bool, error) {
- return core.Unseal(key)
-}
-
-func TestCoreSeal(core *Core) error {
- return core.sealInternal()
-}
-
-// TestCoreUnsealed returns a pure in-memory core that is already
-// initialized and unsealed.
-func TestCoreUnsealed(t testing.T) (*Core, [][]byte, string) {
- t.Helper()
- core := TestCore(t)
- return testCoreUnsealed(t, core)
-}
-
-func SetupMetrics(conf *CoreConfig) *metrics.InmemSink {
- inmemSink := metrics.NewInmemSink(1000000*time.Hour, 2000000*time.Hour)
- conf.MetricSink = metricsutil.NewClusterMetricSink("test-cluster", inmemSink)
- conf.MetricsHelper = metricsutil.NewMetricsHelper(inmemSink, false)
- return inmemSink
-}
-
-func TestCoreUnsealedWithMetrics(t testing.T) (*Core, [][]byte, string, *metrics.InmemSink) {
- t.Helper()
- conf := &CoreConfig{
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- }
- sink := SetupMetrics(conf)
- core, keys, root := testCoreUnsealed(t, TestCoreWithSealAndUI(t, conf))
- return core, keys, root, sink
-}
-
-// TestCoreUnsealedRaw returns a pure in-memory core that is already
-// initialized, unsealed, and with raw endpoints enabled.
-func TestCoreUnsealedRaw(t testing.T) (*Core, [][]byte, string) {
- t.Helper()
- core := TestCoreRaw(t)
- return testCoreUnsealed(t, core)
-}
-
-// TestCoreUnsealedWithConfig returns a pure in-memory core that is already
-// initialized, unsealed, with the any provided core config values overridden.
-func TestCoreUnsealedWithConfig(t testing.T, conf *CoreConfig) (*Core, [][]byte, string) {
- t.Helper()
- core := TestCoreWithConfig(t, conf)
- return testCoreUnsealed(t, core)
-}
-
-func testCoreUnsealed(t testing.T, core *Core) (*Core, [][]byte, string) {
- t.Helper()
- token, keys := TestInitUnsealCore(t, core)
-
- testCoreAddSecretMount(t, core, token)
- return core, keys, token
-}
-
-func TestInitUnsealCore(t testing.T, core *Core) (string, [][]byte) {
- keys, token := TestCoreInit(t, core)
- for _, key := range keys {
- if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
- if core.Sealed() {
- t.Fatal("should not be sealed")
- }
-
- return token, keys
-}
-
-func testCoreAddSecretMount(t testing.T, core *Core, token string) {
- kvReq := &logical.Request{
- Operation: logical.UpdateOperation,
- ClientToken: token,
- Path: "sys/mounts/secret",
- Data: map[string]interface{}{
- "type": "kv",
- "path": "secret/",
- "description": "key/value secret storage",
- "options": map[string]string{
- "version": "1",
- },
- },
- }
- resp, err := core.HandleRequest(namespace.RootContext(nil), kvReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp.IsError() {
- t.Fatal(err)
- }
-}
-
-func TestCoreUnsealedBackend(t testing.T, backend physical.Backend) (*Core, [][]byte, string) {
- t.Helper()
- logger := logging.NewVaultLogger(log.Trace)
- conf := testCoreConfig(t, backend, logger)
- conf.Seal = NewTestSeal(t, nil)
- conf.NumExpirationWorkers = numExpirationWorkersTest
-
- core, err := NewCore(conf)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- keys, token := TestCoreInit(t, core)
- for _, key := range keys {
- if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- if err := core.UnsealWithStoredKeys(context.Background()); err != nil {
- t.Fatal(err)
- }
-
- if core.Sealed() {
- t.Fatal("should not be sealed")
- }
-
- t.Cleanup(func() {
- defer func() {
- if r := recover(); r != nil {
- t.Log("panic closing core during cleanup", "panic", r)
- }
- }()
- err := core.ShutdownWait()
- if err != nil {
- t.Logf("shutdown returned error: %v", err)
- }
- })
-
- return core, keys, token
-}
-
-// TestKeyCopy is a silly little function to just copy the key so that
-// it can be used with Unseal easily.
-func TestKeyCopy(key []byte) []byte {
- result := make([]byte, len(key))
- copy(result, key)
- return result
-}
-
-func TestDynamicSystemView(c *Core, ns *namespace.Namespace) *dynamicSystemView {
- me := &MountEntry{
- Config: MountConfig{
- DefaultLeaseTTL: 24 * time.Hour,
- MaxLeaseTTL: 2 * 24 * time.Hour,
- },
- NamespaceID: namespace.RootNamespace.ID,
- namespace: namespace.RootNamespace,
- }
-
- if ns != nil {
- me.NamespaceID = ns.ID
- me.namespace = ns
- }
-
- return &dynamicSystemView{c, me, c.perfStandby}
-}
-
-// TestAddTestPlugin registers the testFunc as part of the plugin command to the
-// plugin catalog. If provided, uses tmpDir as the plugin directory.
-// NB: The test func you pass in MUST be in the same package as the parent test,
-// or the test func won't be compiled into the test binary being run and the output
-// will be something like:
-// stderr (ignored by go-plugin): "testing: warning: no tests to run"
-// stdout: "PASS"
-func TestAddTestPlugin(t testing.T, c *Core, name string, pluginType consts.PluginType, version string, testFunc string, env []string, tempDir string) {
- file, err := os.Open(os.Args[0])
- if err != nil {
- t.Fatal(err)
- }
- defer file.Close()
-
- dirPath := filepath.Dir(os.Args[0])
- fileName := filepath.Base(os.Args[0])
-
- if tempDir != "" {
- fi, err := file.Stat()
- if err != nil {
- t.Fatal(err)
- }
-
- // Copy over the file to the temp dir
- dst := filepath.Join(tempDir, fileName)
-
- // delete the file first to avoid notary failures in macOS
- _ = os.Remove(dst) // ignore error
- out, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode())
- if err != nil {
- t.Fatal(err)
- }
- defer out.Close()
-
- if _, err = io.Copy(out, file); err != nil {
- t.Fatal(err)
- }
- err = out.Sync()
- if err != nil {
- t.Fatal(err)
- }
- // Ensure that the file is closed and written. This seems to be
- // necessary on Linux systems.
- out.Close()
-
- dirPath = tempDir
- }
-
- // Determine plugin directory full path, evaluating potential symlink path
- fullPath, err := filepath.EvalSymlinks(dirPath)
- if err != nil {
- t.Fatal(err)
- }
-
- reader, err := os.Open(filepath.Join(fullPath, fileName))
- if err != nil {
- t.Fatal(err)
- }
- defer reader.Close()
-
- // Find out the sha256
- hash := sha256.New()
-
- _, err = io.Copy(hash, reader)
- if err != nil {
- t.Fatal(err)
- }
-
- sum := hash.Sum(nil)
-
- // Set core's plugin directory and plugin catalog directory
- c.pluginDirectory = fullPath
- c.pluginCatalog.directory = fullPath
-
- args := []string{fmt.Sprintf("--test.run=%s", testFunc)}
- err = c.pluginCatalog.Set(context.Background(), name, pluginType, version, fileName, args, env, sum)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-// TestRunTestPlugin runs the testFunc which has already been registered to the
-// plugin catalog and returns a pluginClient. This can be called after calling
-// TestAddTestPlugin.
-func TestRunTestPlugin(t testing.T, c *Core, pluginType consts.PluginType, pluginName string) *pluginClient {
- t.Helper()
- config := TestPluginClientConfig(c, pluginType, pluginName)
- client, err := c.pluginCatalog.NewPluginClient(context.Background(), config)
- if err != nil {
- t.Fatal(err)
- }
-
- return client
-}
-
-func TestPluginClientConfig(c *Core, pluginType consts.PluginType, pluginName string) pluginutil.PluginClientConfig {
- dsv := TestDynamicSystemView(c, nil)
- switch pluginType {
- case consts.PluginTypeCredential, consts.PluginTypeSecrets:
- return pluginutil.PluginClientConfig{
- Name: pluginName,
- PluginType: pluginType,
- PluginSets: backendplugin.PluginSet,
- HandshakeConfig: backendplugin.HandshakeConfig,
- Logger: log.NewNullLogger(),
- AutoMTLS: true,
- IsMetadataMode: false,
- Wrapper: dsv,
- }
- case consts.PluginTypeDatabase:
- return pluginutil.PluginClientConfig{
- Name: pluginName,
- PluginType: pluginType,
- PluginSets: v5.PluginSets,
- HandshakeConfig: v5.HandshakeConfig,
- Logger: log.NewNullLogger(),
- AutoMTLS: true,
- IsMetadataMode: false,
- Wrapper: dsv,
- }
- }
- return pluginutil.PluginClientConfig{}
-}
-
-var (
- testLogicalBackends = map[string]logical.Factory{}
- testCredentialBackends = map[string]logical.Factory{}
-)
-
-// This adds a credential backend for the test core. This needs to be
-// invoked before the test core is created.
-func AddTestCredentialBackend(name string, factory logical.Factory) error {
- if name == "" {
- return fmt.Errorf("missing backend name")
- }
- if factory == nil {
- return fmt.Errorf("missing backend factory function")
- }
- testCredentialBackends[name] = factory
- return nil
-}
-
-// This adds a logical backend for the test core. This needs to be
-// invoked before the test core is created.
-func AddTestLogicalBackend(name string, factory logical.Factory) error {
- if name == "" {
- return fmt.Errorf("missing backend name")
- }
- if factory == nil {
- return fmt.Errorf("missing backend factory function")
- }
- testLogicalBackends[name] = factory
- return nil
-}
-
-type rawHTTP struct{}
-
-func (n *rawHTTP) HandleRequest(ctx context.Context, req *logical.Request) (*logical.Response, error) {
- return &logical.Response{
- Data: map[string]interface{}{
- logical.HTTPStatusCode: 200,
- logical.HTTPContentType: "plain/text",
- logical.HTTPRawBody: []byte("hello world"),
- },
- }, nil
-}
-
-func (n *rawHTTP) HandleExistenceCheck(ctx context.Context, req *logical.Request) (bool, bool, error) {
- return false, false, nil
-}
-
-func (n *rawHTTP) SpecialPaths() *logical.Paths {
- return &logical.Paths{Unauthenticated: []string{"*"}}
-}
-
-func (n *rawHTTP) System() logical.SystemView {
- return logical.StaticSystemView{
- DefaultLeaseTTLVal: time.Hour * 24,
- MaxLeaseTTLVal: time.Hour * 24 * 32,
- }
-}
-
-func (n *rawHTTP) Logger() log.Logger {
- return logging.NewVaultLogger(log.Trace)
-}
-
-func (n *rawHTTP) Cleanup(ctx context.Context) {
- // noop
-}
-
-func (n *rawHTTP) Initialize(ctx context.Context, req *logical.InitializationRequest) error {
- return nil
-}
-
-func (n *rawHTTP) InvalidateKey(context.Context, string) {
- // noop
-}
-
-func (n *rawHTTP) Setup(ctx context.Context, config *logical.BackendConfig) error {
- // noop
- return nil
-}
-
-func (n *rawHTTP) Type() logical.BackendType {
- return logical.TypeLogical
-}
-
-func GenerateRandBytes(length int) ([]byte, error) {
- if length < 0 {
- return nil, fmt.Errorf("length must be >= 0")
- }
-
- buf := make([]byte, length)
- if length == 0 {
- return buf, nil
- }
-
- n, err := rand.Read(buf)
- if err != nil {
- return nil, err
- }
- if n != length {
- return nil, fmt.Errorf("unable to read %d bytes; only read %d", length, n)
- }
-
- return buf, nil
-}
-
-func TestWaitActive(t testing.T, core *Core) {
- t.Helper()
- if err := TestWaitActiveWithError(core); err != nil {
- t.Fatal(err)
- }
-}
-
-func TestWaitActiveForwardingReady(t testing.T, core *Core) {
- TestWaitActive(t, core)
-
- deadline := time.Now().Add(2 * time.Second)
- for time.Now().Before(deadline) {
- if _, ok := core.getClusterListener().Handler(consts.RequestForwardingALPN); ok {
- return
- }
- time.Sleep(100 * time.Millisecond)
- }
- t.Fatal("timed out waiting for request forwarding handler to be registered")
-}
-
-func TestWaitActiveWithError(core *Core) error {
- start := time.Now()
- var standby bool
- var err error
- for time.Now().Sub(start) < 30*time.Second {
- standby, err = core.Standby()
- if err != nil {
- return err
- }
- if !standby {
- break
- }
- }
- if standby {
- return errors.New("should not be in standby mode")
- }
- return nil
-}
-
-type TestCluster struct {
- BarrierKeys [][]byte
- RecoveryKeys [][]byte
- CACert *x509.Certificate
- CACertBytes []byte
- CACertPEM []byte
- CACertPEMFile string
- CAKey *ecdsa.PrivateKey
- CAKeyPEM []byte
- Cores []*TestClusterCore
- ID string
- Plugins []pluginhelpers.TestPlugin
- RootToken string
- RootCAs *x509.CertPool
- TempDir string
- ClientAuthRequired bool
- Logger log.Logger
- CleanupFunc func()
- SetupFunc func()
-
- cleanupFuncs []func()
- base *CoreConfig
- LicensePublicKey ed25519.PublicKey
- LicensePrivateKey ed25519.PrivateKey
- opts *TestClusterOptions
-}
-
-func (c *TestCluster) SetRootToken(token string) {
- c.RootToken = token
-}
-
-func (c *TestCluster) Start() {
-}
-
-func (c *TestCluster) start(t testing.T) {
- t.Helper()
- for i, core := range c.Cores {
- if core.Server != nil {
- for _, ln := range core.Listeners {
- c.Logger.Info("starting listener for test core", "core", i, "port", ln.Address.Port)
- go core.Server.Serve(ln)
- }
- }
- }
- if c.SetupFunc != nil {
- c.SetupFunc()
- }
-
- if c.opts != nil && c.opts.SkipInit {
- // SkipInit implies that vault may not be ready to service requests, or that
- // we're restarting a cluster from an existing storage.
- return
- }
-
- activeCore := -1
-WAITACTIVE:
- for i := 0; i < 60; i++ {
- for i, core := range c.Cores {
- if standby, _ := core.Core.Standby(); !standby {
- activeCore = i
- break WAITACTIVE
- }
- }
-
- time.Sleep(time.Second)
- }
- if activeCore == -1 {
- t.Fatalf("no core became active")
- }
-
- switch {
- case c.opts == nil:
- case c.opts.NoDefaultQuotas:
- case c.opts.HandlerFunc == nil:
- // If no HandlerFunc is provided that means that we can't actually do
- // regular vault requests.
- case reflect.TypeOf(c.opts.HandlerFunc).PkgPath() != "github.com/hashicorp/vault/http":
- case reflect.TypeOf(c.opts.HandlerFunc).Name() != "Handler":
- default:
- cli := c.Cores[activeCore].Client
- _, err := cli.Logical().Write("sys/quotas/rate-limit/rl-NewTestCluster", map[string]interface{}{
- "rate": 1000000,
- })
- if err != nil {
- t.Fatalf("error setting up global rate limit quota: %v", err)
- }
- if constants.IsEnterprise {
- _, err = cli.Logical().Write("sys/quotas/lease-count/lc-NewTestCluster", map[string]interface{}{
- "max_leases": 1000000,
- })
- if err != nil {
- t.Fatalf("error setting up global lease count quota: %v", err)
- }
- }
- }
-}
-
-// UnsealCores uses the cluster barrier keys to unseal the test cluster cores
-func (c *TestCluster) UnsealCores(t testing.T) {
- t.Helper()
- if err := c.UnsealCoresWithError(false); err != nil {
- t.Fatal(err)
- }
-}
-
-func (c *TestCluster) UnsealCoresWithError(useStoredKeys bool) error {
- unseal := func(core *Core) error {
- for _, key := range c.BarrierKeys {
- if _, err := core.Unseal(TestKeyCopy(key)); err != nil {
- return err
- }
- }
- return nil
- }
- if useStoredKeys {
- unseal = func(core *Core) error {
- return core.UnsealWithStoredKeys(context.Background())
- }
- }
-
- // Unseal first core
- if err := unseal(c.Cores[0].Core); err != nil {
- return fmt.Errorf("unseal core %d err: %s", 0, err)
- }
-
- // Verify unsealed
- if c.Cores[0].Sealed() {
- return fmt.Errorf("should not be sealed")
- }
-
- if err := TestWaitActiveWithError(c.Cores[0].Core); err != nil {
- return err
- }
-
- // Unseal other cores
- for i := 1; i < len(c.Cores); i++ {
- if err := unseal(c.Cores[i].Core); err != nil {
- return fmt.Errorf("unseal core %d err: %s", i, err)
- }
- }
-
- // Let them come fully up to standby
- time.Sleep(2 * time.Second)
-
- // Ensure cluster connection info is populated.
- // Other cores should not come up as leaders.
- for i := 1; i < len(c.Cores); i++ {
- isLeader, _, _, err := c.Cores[i].Leader()
- if err != nil {
- return err
- }
- if isLeader {
- return fmt.Errorf("core[%d] should not be leader", i)
- }
- }
-
- return nil
-}
-
-func (c *TestCluster) UnsealCore(t testing.T, core *TestClusterCore) {
- err := c.AttemptUnsealCore(core)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func (c *TestCluster) AttemptUnsealCore(core *TestClusterCore) error {
- var keys [][]byte
- if core.seal.RecoveryKeySupported() {
- keys = c.RecoveryKeys
- } else {
- keys = c.BarrierKeys
- }
- for _, key := range keys {
- if _, err := core.Core.Unseal(TestKeyCopy(key)); err != nil {
- return fmt.Errorf("unseal err: %w", err)
- }
- }
- return nil
-}
-
-func (c *TestCluster) UnsealCoreWithStoredKeys(t testing.T, core *TestClusterCore) {
- t.Helper()
- if err := core.UnsealWithStoredKeys(context.Background()); err != nil {
- t.Fatal(err)
- }
-}
-
-func (c *TestCluster) EnsureCoresSealed(t testing.T) {
- t.Helper()
- if err := c.ensureCoresSealed(); err != nil {
- t.Fatal(err)
- }
-}
-
-func (c *TestClusterCore) Seal(t testing.T) {
- t.Helper()
- if err := c.Core.sealInternal(); err != nil {
- t.Fatal(err)
- }
-}
-
-func (c *TestClusterCore) LogicalStorage() logical.Storage {
- return c.barrier
-}
-
-func (c *TestClusterCore) stop() error {
- c.Logger().Info("stopping vault test core")
-
- if c.Listeners != nil {
- for _, ln := range c.Listeners {
- ln.Close()
- }
- c.Logger().Info("listeners successfully shut down")
- }
- if c.licensingStopCh != nil {
- close(c.licensingStopCh)
- c.licensingStopCh = nil
- }
-
- if err := c.Shutdown(); err != nil {
- return err
- }
- timeout := time.Now().Add(60 * time.Second)
- for {
- if time.Now().After(timeout) {
- return errors.New("timeout waiting for core to seal")
- }
- if c.Sealed() {
- break
- }
- time.Sleep(250 * time.Millisecond)
- }
-
- c.Logger().Info("vault test core stopped")
- return nil
-}
-
-func (c *TestClusterCore) StopAutomaticRollbacks() {
- c.rollback.StopTicker()
-}
-
-func (c *TestClusterCore) GrabRollbackLock() {
- // Ensure we don't hold this lock while there are in flight rollbacks.
- c.rollback.inflightAll.Wait()
- c.rollback.inflightLock.Lock()
-}
-
-func (c *TestClusterCore) ReleaseRollbackLock() {
- c.rollback.inflightLock.Unlock()
-}
-
-func (c *TestClusterCore) TriggerRollbacks() {
- c.rollback.triggerRollbacks()
-}
-
-func (c *TestClusterCore) TLSConfig() *tls.Config {
- return c.tlsConfig.Clone()
-}
-
-func (c *TestClusterCore) ClusterListener() *cluster.Listener {
- return c.getClusterListener()
-}
-
-func (c *TestCluster) Cleanup() {
- c.Logger.Info("cleaning up vault cluster")
- if tl, ok := c.Logger.(*corehelpers.TestLogger); ok {
- tl.StopLogging()
- }
-
- wg := &sync.WaitGroup{}
- for _, core := range c.Cores {
- wg.Add(1)
- lc := core
-
- go func() {
- defer wg.Done()
- if err := lc.stop(); err != nil {
- // Note that this log won't be seen if using TestLogger, due to
- // the above call to StopLogging.
- lc.Logger().Error("error during cleanup", "error", err)
- }
- }()
- }
-
- wg.Wait()
-
- // Remove any temp dir that exists
- if c.TempDir != "" {
- os.RemoveAll(c.TempDir)
- }
-
- // Give time to actually shut down/clean up before the next test
- time.Sleep(time.Second)
- if c.CleanupFunc != nil {
- c.CleanupFunc()
- }
-}
-
-func (c *TestCluster) ensureCoresSealed() error {
- for _, core := range c.Cores {
- if err := core.Shutdown(); err != nil {
- return err
- }
- timeout := time.Now().Add(60 * time.Second)
- for {
- if time.Now().After(timeout) {
- return fmt.Errorf("timeout waiting for core to seal")
- }
- if core.Sealed() {
- break
- }
- time.Sleep(250 * time.Millisecond)
- }
- }
- return nil
-}
-
-func SetReplicationFailureMode(core *TestClusterCore, mode uint32) {
- atomic.StoreUint32(core.Core.replicationFailure, mode)
-}
-
-type TestListener struct {
- net.Listener
- Address *net.TCPAddr
-}
-
-type TestClusterCore struct {
- *Core
- CoreConfig *CoreConfig
- Client *api.Client
- Handler http.Handler
- Address *net.TCPAddr
- Listeners []*TestListener
- ReloadFuncs *map[string][]reloadutil.ReloadFunc
- ReloadFuncsLock *sync.RWMutex
- Server *http.Server
- ServerCert *x509.Certificate
- ServerCertBytes []byte
- ServerCertPEM []byte
- ServerKey *ecdsa.PrivateKey
- ServerKeyPEM []byte
- tlsConfig *tls.Config
- UnderlyingStorage physical.Backend
- UnderlyingRawStorage physical.Backend
- UnderlyingHAStorage physical.HABackend
- Barrier SecurityBarrier
- NodeID string
-}
-
-type PhysicalBackendBundle struct {
- Backend physical.Backend
- HABackend physical.HABackend
- Cleanup func()
-}
-
type HandlerHandler interface {
Handler(*HandlerProperties) http.Handler
}
-
-type TestClusterOptions struct {
- KeepStandbysSealed bool
- SkipInit bool
- HandlerFunc HandlerHandler
- DefaultHandlerProperties HandlerProperties
-
- // BaseListenAddress is used to explicitly assign ports in sequence to the
- // listener of each core. It should be a string of the form
- // "127.0.0.1:20000"
- //
- // WARNING: Using an explicitly assigned port above 30000 may clash with
- // ephemeral ports that have been assigned by the OS in other tests. The
- // use of explicitly assigned ports below 30000 is strongly recommended.
- // In addition, you should be careful to use explicitly assigned ports that
- // do not clash with any other explicitly assigned ports in other tests.
- BaseListenAddress string
-
- // BaseClusterListenPort is used to explicitly assign ports in sequence to
- // the cluster listener of each core. If BaseClusterListenPort is
- // specified, then BaseListenAddress must also be specified. Each cluster
- // listener will use the same host as the one specified in
- // BaseListenAddress.
- //
- // WARNING: Using an explicitly assigned port above 30000 may clash with
- // ephemeral ports that have been assigned by the OS in other tests. The
- // use of explicitly assigned ports below 30000 is strongly recommended.
- // In addition, you should be careful to use explicitly assigned ports that
- // do not clash with any other explicitly assigned ports in other tests.
- BaseClusterListenPort int
-
- NumCores int
- SealFunc func() Seal
- UnwrapSealFunc func() Seal
- Logger log.Logger
- TempDir string
- CACert []byte
- CAKey *ecdsa.PrivateKey
- // PhysicalFactory is used to create backends.
- // The int argument is the index of the core within the cluster, i.e. first
- // core in cluster will have 0, second 1, etc.
- // If the backend is shared across the cluster (i.e. is not Raft) then it
- // should return nil when coreIdx != 0.
- PhysicalFactory func(t testing.T, coreIdx int, logger log.Logger, conf map[string]interface{}) *PhysicalBackendBundle
- // FirstCoreNumber is used to assign a unique number to each core within
- // a multi-cluster setup.
- FirstCoreNumber int
- RequireClientAuth bool
- // SetupFunc is called after the cluster is started.
- SetupFunc func(t testing.T, c *TestCluster)
- PR1103Disabled bool
-
- // ClusterLayers are used to override the default cluster connection layer
- ClusterLayers cluster.NetworkLayerSet
- // InmemClusterLayers is a shorthand way of asking for ClusterLayers to be
- // built using the inmem implementation.
- InmemClusterLayers bool
-
- // RaftAddressProvider is used to set the raft ServerAddressProvider on
- // each core.
- //
- // If SkipInit is true, then RaftAddressProvider has no effect.
- // RaftAddressProvider should only be specified if the underlying physical
- // storage is Raft.
- RaftAddressProvider raftlib.ServerAddressProvider
-
- CoreMetricSinkProvider func(clusterName string) (*metricsutil.ClusterMetricSink, *metricsutil.MetricsHelper)
-
- PhysicalFactoryConfig map[string]interface{}
- LicensePublicKey ed25519.PublicKey
- LicensePrivateKey ed25519.PrivateKey
-
- // this stores the vault version that should be used for each core config
- VersionMap map[int]string
- RedundancyZoneMap map[int]string
- KVVersion string
- EffectiveSDKVersionMap map[int]string
-
- NoDefaultQuotas bool
-
- Plugins *TestPluginConfig
-
- // if populated, the callback is called for every request
- RequestResponseCallback func(logical.Backend, *logical.Request, *logical.Response)
-
- // ABCDLoggerNames names the loggers according to our ABCD convention when generating 4 clusters
- ABCDLoggerNames bool
-}
-
-type TestPluginConfig struct {
- Typ consts.PluginType
- Versions []string
-}
-
-var DefaultNumCores = 3
-
-type certInfo struct {
- cert *x509.Certificate
- certPEM []byte
- certBytes []byte
- key *ecdsa.PrivateKey
- keyPEM []byte
-}
-
-// NewTestCluster creates a new test cluster based on the provided core config
-// and test cluster options.
-//
-// N.B. Even though a single base CoreConfig is provided, NewTestCluster will instantiate a
-// core config for each core it creates. If separate seal per core is desired, opts.SealFunc
-// can be provided to generate a seal for each one. Otherwise, the provided base.Seal will be
-// shared among cores. NewCore's default behavior is to generate a new DefaultSeal if the
-// provided Seal in coreConfig (i.e. base.Seal) is nil.
-//
-// If opts.Logger is provided, it takes precedence and will be used as the cluster
-// logger and will be the basis for each core's logger. If no opts.Logger is
-// given, one will be generated based on t.Name() for the cluster logger, and if
-// no base.Logger is given will also be used as the basis for each core's logger.
-func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *TestCluster {
- var err error
-
- var numCores int
- if opts == nil || opts.NumCores == 0 {
- numCores = DefaultNumCores
- } else {
- numCores = opts.NumCores
- }
-
- certIPs := []net.IP{
- net.IPv6loopback,
- net.ParseIP("127.0.0.1"),
- }
-
- baseAddr, certIPs := GenerateListenerAddr(t, opts, certIPs)
- var testCluster TestCluster
- testCluster.base = base
-
- switch {
- case opts != nil && opts.Logger != nil:
- testCluster.Logger = opts.Logger
- default:
- testCluster.Logger = corehelpers.NewTestLogger(t)
- }
-
- if opts != nil && opts.TempDir != "" {
- if _, err := os.Stat(opts.TempDir); os.IsNotExist(err) {
- if err := os.MkdirAll(opts.TempDir, 0o700); err != nil {
- t.Fatal(err)
- }
- }
- testCluster.TempDir = opts.TempDir
- } else {
- tempDir, err := ioutil.TempDir("", "vault-test-cluster-")
- if err != nil {
- t.Fatal(err)
- }
- testCluster.TempDir = tempDir
- }
-
- var caKey *ecdsa.PrivateKey
- if opts != nil && opts.CAKey != nil {
- caKey = opts.CAKey
- } else {
- caKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
- if err != nil {
- t.Fatal(err)
- }
- }
- testCluster.CAKey = caKey
- var caBytes []byte
- if opts != nil && len(opts.CACert) > 0 {
- caBytes = opts.CACert
- } else {
- caCertTemplate := &x509.Certificate{
- Subject: pkix.Name{
- CommonName: "localhost",
- },
- DNSNames: []string{"localhost"},
- IPAddresses: certIPs,
- KeyUsage: x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign),
- SerialNumber: big.NewInt(mathrand.Int63()),
- NotBefore: time.Now().Add(-30 * time.Second),
- NotAfter: time.Now().Add(262980 * time.Hour),
- BasicConstraintsValid: true,
- IsCA: true,
- }
- caBytes, err = x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, caKey.Public(), caKey)
- if err != nil {
- t.Fatal(err)
- }
- }
- caCert, err := x509.ParseCertificate(caBytes)
- if err != nil {
- t.Fatal(err)
- }
- testCluster.CACert = caCert
- testCluster.CACertBytes = caBytes
- testCluster.RootCAs = x509.NewCertPool()
- testCluster.RootCAs.AddCert(caCert)
- caCertPEMBlock := &pem.Block{
- Type: "CERTIFICATE",
- Bytes: caBytes,
- }
- testCluster.CACertPEM = pem.EncodeToMemory(caCertPEMBlock)
- testCluster.CACertPEMFile = filepath.Join(testCluster.TempDir, "ca_cert.pem")
- err = ioutil.WriteFile(testCluster.CACertPEMFile, testCluster.CACertPEM, 0o755)
- if err != nil {
- t.Fatal(err)
- }
- marshaledCAKey, err := x509.MarshalECPrivateKey(caKey)
- if err != nil {
- t.Fatal(err)
- }
- caKeyPEMBlock := &pem.Block{
- Type: "EC PRIVATE KEY",
- Bytes: marshaledCAKey,
- }
- testCluster.CAKeyPEM = pem.EncodeToMemory(caKeyPEMBlock)
- err = ioutil.WriteFile(filepath.Join(testCluster.TempDir, "ca_key.pem"), testCluster.CAKeyPEM, 0o755)
- if err != nil {
- t.Fatal(err)
- }
-
- var certInfoSlice []*certInfo
-
- //
- // Certs generation
- //
- for i := 0; i < numCores; i++ {
- key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
- if err != nil {
- t.Fatal(err)
- }
- certTemplate := &x509.Certificate{
- Subject: pkix.Name{
- CommonName: "localhost",
- },
- // Include host.docker.internal for the sake of benchmark-vault running on MacOS/Windows.
- // This allows Prometheus running in docker to scrape the cluster for metrics.
- DNSNames: []string{"localhost", "host.docker.internal"},
- IPAddresses: certIPs,
- ExtKeyUsage: []x509.ExtKeyUsage{
- x509.ExtKeyUsageServerAuth,
- x509.ExtKeyUsageClientAuth,
- },
- KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
- SerialNumber: big.NewInt(mathrand.Int63()),
- NotBefore: time.Now().Add(-30 * time.Second),
- NotAfter: time.Now().Add(262980 * time.Hour),
- }
- certBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, caCert, key.Public(), caKey)
- if err != nil {
- t.Fatal(err)
- }
- cert, err := x509.ParseCertificate(certBytes)
- if err != nil {
- t.Fatal(err)
- }
- certPEMBlock := &pem.Block{
- Type: "CERTIFICATE",
- Bytes: certBytes,
- }
- certPEM := pem.EncodeToMemory(certPEMBlock)
- marshaledKey, err := x509.MarshalECPrivateKey(key)
- if err != nil {
- t.Fatal(err)
- }
- keyPEMBlock := &pem.Block{
- Type: "EC PRIVATE KEY",
- Bytes: marshaledKey,
- }
- keyPEM := pem.EncodeToMemory(keyPEMBlock)
-
- certInfoSlice = append(certInfoSlice, &certInfo{
- cert: cert,
- certPEM: certPEM,
- certBytes: certBytes,
- key: key,
- keyPEM: keyPEM,
- })
- }
-
- //
- // Listener setup
- //
- addresses := []*net.TCPAddr{}
- listeners := [][]*TestListener{}
- servers := []*http.Server{}
- handlers := []http.Handler{}
- tlsConfigs := []*tls.Config{}
- certGetters := []*reloadutil.CertificateGetter{}
- for i := 0; i < numCores; i++ {
- addr := &net.TCPAddr{
- IP: baseAddr.IP,
- Port: 0,
- }
- if baseAddr.Port != 0 {
- addr.Port = baseAddr.Port + i
- }
-
- ln, err := net.ListenTCP("tcp", addr)
- if err != nil {
- t.Fatal(err)
- }
- addresses = append(addresses, addr)
-
- certFile := filepath.Join(testCluster.TempDir, fmt.Sprintf("node%d_port_%d_cert.pem", i+1, ln.Addr().(*net.TCPAddr).Port))
- keyFile := filepath.Join(testCluster.TempDir, fmt.Sprintf("node%d_port_%d_key.pem", i+1, ln.Addr().(*net.TCPAddr).Port))
- err = ioutil.WriteFile(certFile, certInfoSlice[i].certPEM, 0o755)
- if err != nil {
- t.Fatal(err)
- }
- err = ioutil.WriteFile(keyFile, certInfoSlice[i].keyPEM, 0o755)
- if err != nil {
- t.Fatal(err)
- }
- tlsCert, err := tls.X509KeyPair(certInfoSlice[i].certPEM, certInfoSlice[i].keyPEM)
- if err != nil {
- t.Fatal(err)
- }
- certGetter := reloadutil.NewCertificateGetter(certFile, keyFile, "")
- certGetters = append(certGetters, certGetter)
- certGetter.Reload()
- tlsConfig := &tls.Config{
- Certificates: []tls.Certificate{tlsCert},
- RootCAs: testCluster.RootCAs,
- ClientCAs: testCluster.RootCAs,
- ClientAuth: tls.RequestClientCert,
- NextProtos: []string{"h2", "http/1.1"},
- GetCertificate: certGetter.GetCertificate,
- }
- if opts != nil && opts.RequireClientAuth {
- tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
- testCluster.ClientAuthRequired = true
- }
- tlsConfigs = append(tlsConfigs, tlsConfig)
- lns := []*TestListener{
- {
- Listener: tls.NewListener(ln, tlsConfig),
- Address: ln.Addr().(*net.TCPAddr),
- },
- }
- listeners = append(listeners, lns)
- var handler http.Handler = http.NewServeMux()
- handlers = append(handlers, handler)
- server := &http.Server{
- Handler: handler,
- ErrorLog: testCluster.Logger.StandardLogger(nil),
- }
- servers = append(servers, server)
- }
-
- // Create three cores with the same physical and different redirect/cluster
- // addrs.
- // N.B.: On OSX, instead of random ports, it assigns new ports to new
- // listeners sequentially. Aside from being a bad idea in a security sense,
- // it also broke tests that assumed it was OK to just use the port above
- // the redirect addr. This has now been changed to 105 ports above, but if
- // we ever do more than three nodes in a cluster it may need to be bumped.
- // Note: it's 105 so that we don't conflict with a running Consul by
- // default.
- coreConfig := &CoreConfig{
- LogicalBackends: make(map[string]logical.Factory),
- CredentialBackends: make(map[string]logical.Factory),
- AuditBackends: make(map[string]audit.Factory),
- RedirectAddr: fmt.Sprintf("https://127.0.0.1:%d", listeners[0][0].Address.Port),
- ClusterAddr: "https://127.0.0.1:0",
- DisableMlock: true,
- EnableUI: true,
- EnableRaw: true,
- BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
- }
-
- if base != nil {
- coreConfig.DetectDeadlocks = TestDeadlockDetection
- coreConfig.RawConfig = base.RawConfig
- coreConfig.DisableCache = base.DisableCache
- coreConfig.EnableUI = base.EnableUI
- coreConfig.DefaultLeaseTTL = base.DefaultLeaseTTL
- coreConfig.MaxLeaseTTL = base.MaxLeaseTTL
- coreConfig.CacheSize = base.CacheSize
- coreConfig.PluginDirectory = base.PluginDirectory
- coreConfig.Seal = base.Seal
- coreConfig.UnwrapSeal = base.UnwrapSeal
- coreConfig.DevToken = base.DevToken
- coreConfig.EnableRaw = base.EnableRaw
- coreConfig.DisableSealWrap = base.DisableSealWrap
- coreConfig.DisableCache = base.DisableCache
- coreConfig.LicensingConfig = base.LicensingConfig
- coreConfig.License = base.License
- coreConfig.LicensePath = base.LicensePath
- coreConfig.DisablePerformanceStandby = base.DisablePerformanceStandby
- coreConfig.MetricsHelper = base.MetricsHelper
- coreConfig.MetricSink = base.MetricSink
- coreConfig.SecureRandomReader = base.SecureRandomReader
- coreConfig.DisableSentinelTrace = base.DisableSentinelTrace
- coreConfig.ClusterName = base.ClusterName
- coreConfig.DisableAutopilot = base.DisableAutopilot
- coreConfig.AdministrativeNamespacePath = base.AdministrativeNamespacePath
- coreConfig.ServiceRegistration = base.ServiceRegistration
- coreConfig.ImpreciseLeaseRoleTracking = base.ImpreciseLeaseRoleTracking
-
- if base.BuiltinRegistry != nil {
- coreConfig.BuiltinRegistry = base.BuiltinRegistry
- }
-
- if !coreConfig.DisableMlock {
- base.DisableMlock = false
- }
-
- if base.Physical != nil {
- coreConfig.Physical = base.Physical
- }
-
- if base.HAPhysical != nil {
- coreConfig.HAPhysical = base.HAPhysical
- }
-
- // Used to set something non-working to test fallback
- switch base.ClusterAddr {
- case "empty":
- coreConfig.ClusterAddr = ""
- case "":
- default:
- coreConfig.ClusterAddr = base.ClusterAddr
- }
-
- if base.LogicalBackends != nil {
- for k, v := range base.LogicalBackends {
- coreConfig.LogicalBackends[k] = v
- }
- }
- if base.CredentialBackends != nil {
- for k, v := range base.CredentialBackends {
- coreConfig.CredentialBackends[k] = v
- }
- }
- if base.AuditBackends != nil {
- for k, v := range base.AuditBackends {
- coreConfig.AuditBackends[k] = v
- }
- }
- if base.Logger != nil {
- coreConfig.Logger = base.Logger
- }
-
- coreConfig.ClusterCipherSuites = base.ClusterCipherSuites
- coreConfig.DisableCache = base.DisableCache
- coreConfig.DevToken = base.DevToken
- coreConfig.RecoveryMode = base.RecoveryMode
- coreConfig.ActivityLogConfig = base.ActivityLogConfig
- coreConfig.EnableResponseHeaderHostname = base.EnableResponseHeaderHostname
- coreConfig.EnableResponseHeaderRaftNodeID = base.EnableResponseHeaderRaftNodeID
- coreConfig.RollbackPeriod = base.RollbackPeriod
- coreConfig.PendingRemovalMountsAllowed = base.PendingRemovalMountsAllowed
- coreConfig.ExpirationRevokeRetryBase = base.ExpirationRevokeRetryBase
- testApplyEntBaseConfig(coreConfig, base)
- }
- if coreConfig.ClusterName == "" {
- coreConfig.ClusterName = t.Name()
- }
-
- if coreConfig.ClusterName == "" {
- coreConfig.ClusterName = t.Name()
- }
-
- if coreConfig.ClusterHeartbeatInterval == 0 {
- // Set this lower so that state populates quickly to standby nodes
- coreConfig.ClusterHeartbeatInterval = 2 * time.Second
- }
-
- if coreConfig.RawConfig == nil {
- c := new(server.Config)
- c.SharedConfig = &configutil.SharedConfig{LogFormat: logging.UnspecifiedFormat.String()}
- coreConfig.RawConfig = c
- }
-
- addAuditBackend := len(coreConfig.AuditBackends) == 0
- if addAuditBackend {
- coreConfig.AuditBackends["noop"] = corehelpers.NoopAuditFactory(nil)
- }
-
- if coreConfig.Physical == nil && (opts == nil || opts.PhysicalFactory == nil) {
- coreConfig.Physical, err = physInmem.NewInmem(nil, testCluster.Logger)
- if err != nil {
- t.Fatal(err)
- }
- }
- if coreConfig.HAPhysical == nil && (opts == nil || opts.PhysicalFactory == nil) {
- haPhys, err := physInmem.NewInmemHA(nil, testCluster.Logger)
- if err != nil {
- t.Fatal(err)
- }
- coreConfig.HAPhysical = haPhys.(physical.HABackend)
- }
-
- if testCluster.LicensePublicKey == nil {
- pubKey, priKey, err := GenerateTestLicenseKeys()
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- testCluster.LicensePublicKey = pubKey
- testCluster.LicensePrivateKey = priKey
- }
-
- if opts != nil && opts.InmemClusterLayers {
- if opts.ClusterLayers != nil {
- t.Fatalf("cannot specify ClusterLayers when InmemClusterLayers is true")
- }
- inmemCluster, err := cluster.NewInmemLayerCluster("inmem-cluster", numCores, testCluster.Logger.Named("inmem-cluster"))
- if err != nil {
- t.Fatal(err)
- }
- opts.ClusterLayers = inmemCluster
- }
-
- if opts != nil && opts.Plugins != nil {
- var pluginDir string
- var cleanup func(t testing.T)
-
- if coreConfig.PluginDirectory == "" {
- pluginDir, cleanup = corehelpers.MakeTestPluginDir(t)
- coreConfig.PluginDirectory = pluginDir
- t.Cleanup(func() { cleanup(t) })
- }
-
- var plugins []pluginhelpers.TestPlugin
- for _, version := range opts.Plugins.Versions {
- plugins = append(plugins, pluginhelpers.CompilePlugin(t, opts.Plugins.Typ, version, coreConfig.PluginDirectory))
- }
- testCluster.Plugins = plugins
- }
-
- // Create cores
- testCluster.cleanupFuncs = []func(){}
- cores := []*Core{}
- coreConfigs := []*CoreConfig{}
-
- for i := 0; i < numCores; i++ {
- cleanup, c, localConfig, handler := testCluster.newCore(t, i, coreConfig, opts, listeners[i], testCluster.LicensePublicKey)
-
- testCluster.cleanupFuncs = append(testCluster.cleanupFuncs, cleanup)
- cores = append(cores, c)
- coreConfigs = append(coreConfigs, &localConfig)
-
- if handler != nil {
- handlers[i] = handler
- servers[i].Handler = handlers[i]
- }
- }
-
- // Clustering setup
- for i := 0; i < numCores; i++ {
- testCluster.setupClusterListener(t, i, cores[i], coreConfigs[i], opts, listeners[i], handlers[i])
- }
-
- // Create TestClusterCores
- var ret []*TestClusterCore
- for i := 0; i < numCores; i++ {
- tcc := &TestClusterCore{
- Core: cores[i],
- CoreConfig: coreConfigs[i],
- ServerKey: certInfoSlice[i].key,
- ServerKeyPEM: certInfoSlice[i].keyPEM,
- ServerCert: certInfoSlice[i].cert,
- ServerCertBytes: certInfoSlice[i].certBytes,
- ServerCertPEM: certInfoSlice[i].certPEM,
- Address: addresses[i],
- Listeners: listeners[i],
- Handler: handlers[i],
- Server: servers[i],
- tlsConfig: tlsConfigs[i],
- Barrier: cores[i].barrier,
- NodeID: fmt.Sprintf("core-%d", i),
- UnderlyingRawStorage: coreConfigs[i].Physical,
- UnderlyingHAStorage: coreConfigs[i].HAPhysical,
- }
- tcc.ReloadFuncs = &cores[i].reloadFuncs
- tcc.ReloadFuncsLock = &cores[i].reloadFuncsLock
- tcc.ReloadFuncsLock.Lock()
- (*tcc.ReloadFuncs)["listener|tcp"] = []reloadutil.ReloadFunc{certGetters[i].Reload}
- tcc.ReloadFuncsLock.Unlock()
-
- testAdjustUnderlyingStorage(tcc)
-
- ret = append(ret, tcc)
- }
- testCluster.Cores = ret
-
- // Initialize cores
- if opts == nil || !opts.SkipInit {
- testCluster.initCores(t, opts, addAuditBackend)
- }
-
- // Assign clients
- for i := 0; i < numCores; i++ {
- testCluster.Cores[i].Client = testCluster.getAPIClient(t, opts, listeners[i][0].Address.Port, tlsConfigs[i])
- }
-
- // Extra Setup
- for _, tcc := range testCluster.Cores {
- testExtraTestCoreSetup(t, testCluster.LicensePrivateKey, tcc)
- }
-
- // Cleanup
- testCluster.CleanupFunc = func() {
- for _, c := range testCluster.cleanupFuncs {
- c()
- }
- if l, ok := testCluster.Logger.(*corehelpers.TestLogger); ok {
- if t.Failed() {
- _ = l.File.Close()
- } else {
- _ = os.Remove(l.Path)
- }
- }
- }
-
- // Setup
- if opts != nil {
- if opts.SetupFunc != nil {
- testCluster.SetupFunc = func() {
- opts.SetupFunc(t, &testCluster)
- }
- }
- }
-
- testCluster.opts = opts
- testCluster.start(t)
- return &testCluster
-}
-
-// StopCore performs an orderly shutdown of a core.
-func (cluster *TestCluster) StopCore(t testing.T, idx int) {
- t.Helper()
-
- if idx < 0 || idx > len(cluster.Cores) {
- t.Fatalf("invalid core index %d", idx)
- }
- tcc := cluster.Cores[idx]
- tcc.Logger().Info("stopping core", "core", idx)
-
- // Stop listeners and call Finalize()
- if err := tcc.stop(); err != nil {
- t.Fatal(err)
- }
-
- // Run cleanup
- cluster.cleanupFuncs[idx]()
-}
-
-func GenerateListenerAddr(t testing.T, opts *TestClusterOptions, certIPs []net.IP) (*net.TCPAddr, []net.IP) {
- var baseAddr *net.TCPAddr
- var err error
-
- if opts != nil && opts.BaseListenAddress != "" {
- baseAddr, err = net.ResolveTCPAddr("tcp", opts.BaseListenAddress)
- if err != nil {
- t.Fatal("could not parse given base IP")
- }
- certIPs = append(certIPs, baseAddr.IP)
- } else {
- baseAddr = &net.TCPAddr{
- IP: net.ParseIP("127.0.0.1"),
- Port: 0,
- }
- }
-
- return baseAddr, certIPs
-}
-
-// StartCore restarts a TestClusterCore that was stopped, by replacing the
-// underlying Core.
-func (cluster *TestCluster) StartCore(t testing.T, idx int, opts *TestClusterOptions) {
- t.Helper()
-
- if idx < 0 || idx > len(cluster.Cores) {
- t.Fatalf("invalid core index %d", idx)
- }
- tcc := cluster.Cores[idx]
- tcc.Logger().Info("restarting core", "core", idx)
-
- // Set up listeners
- ln, err := net.ListenTCP("tcp", tcc.Address)
- if err != nil {
- t.Fatal(err)
- }
- tcc.Listeners = []*TestListener{
- {
- Listener: tls.NewListener(ln, tcc.tlsConfig),
- Address: ln.Addr().(*net.TCPAddr),
- },
- }
-
- tcc.Handler = http.NewServeMux()
- tcc.Server = &http.Server{
- Handler: tcc.Handler,
- ErrorLog: cluster.Logger.StandardLogger(nil),
- }
-
- // Create a new Core
- cleanup, newCore, localConfig, coreHandler := cluster.newCore(t, idx, tcc.CoreConfig, opts, tcc.Listeners, cluster.LicensePublicKey)
- if coreHandler != nil {
- tcc.Handler = coreHandler
- tcc.Server.Handler = coreHandler
- }
-
- cluster.cleanupFuncs[idx] = cleanup
- tcc.Core = newCore
- tcc.CoreConfig = &localConfig
- tcc.UnderlyingRawStorage = localConfig.Physical
-
- cluster.setupClusterListener(
- t, idx, newCore, tcc.CoreConfig,
- opts, tcc.Listeners, tcc.Handler)
-
- tcc.Client = cluster.getAPIClient(t, opts, tcc.Listeners[0].Address.Port, tcc.tlsConfig)
-
- testAdjustUnderlyingStorage(tcc)
- testExtraTestCoreSetup(t, cluster.LicensePrivateKey, tcc)
-
- // Start listeners
- for _, ln := range tcc.Listeners {
- tcc.Logger().Info("starting listener for core", "port", ln.Address.Port)
- go tcc.Server.Serve(ln)
- }
-
- tcc.Logger().Info("restarted test core", "core", idx)
-}
-
-func (testCluster *TestCluster) newCore(t testing.T, idx int, coreConfig *CoreConfig, opts *TestClusterOptions, listeners []*TestListener, pubKey ed25519.PublicKey) (func(), *Core, CoreConfig, http.Handler) {
- localConfig := *coreConfig
- cleanupFunc := func() {}
- var handler http.Handler
-
- var disablePR1103 bool
- if opts != nil && opts.PR1103Disabled {
- disablePR1103 = true
- }
-
- var firstCoreNumber int
- if opts != nil {
- firstCoreNumber = opts.FirstCoreNumber
- }
-
- localConfig.RedirectAddr = fmt.Sprintf("https://127.0.0.1:%d", listeners[0].Address.Port)
-
- // if opts.SealFunc is provided, use that to generate a seal for the config instead
- if opts != nil && opts.SealFunc != nil {
- localConfig.Seal = opts.SealFunc()
- }
- if opts != nil && opts.UnwrapSealFunc != nil {
- localConfig.UnwrapSeal = opts.UnwrapSealFunc()
- }
-
- if coreConfig.Logger == nil || (opts != nil && opts.Logger != nil) {
- localConfig.Logger = testCluster.Logger.Named(fmt.Sprintf("core%d", idx))
- }
-
- if opts != nil && opts.EffectiveSDKVersionMap != nil {
- localConfig.EffectiveSDKVersion = opts.EffectiveSDKVersionMap[idx]
- }
-
- if opts != nil && opts.PhysicalFactory != nil {
- pfc := opts.PhysicalFactoryConfig
- if pfc == nil {
- pfc = make(map[string]interface{})
- }
- if len(opts.VersionMap) > 0 {
- pfc["autopilot_upgrade_version"] = opts.VersionMap[idx]
- }
- if len(opts.RedundancyZoneMap) > 0 {
- pfc["autopilot_redundancy_zone"] = opts.RedundancyZoneMap[idx]
- }
- physBundle := opts.PhysicalFactory(t, idx, localConfig.Logger, pfc)
- switch {
- case physBundle == nil && coreConfig.Physical != nil:
- case physBundle == nil && coreConfig.Physical == nil:
- t.Fatal("PhysicalFactory produced no physical and none in CoreConfig")
- case physBundle != nil:
- // Storage backend setup
- if physBundle.Backend != nil {
- testCluster.Logger.Info("created physical backend", "instance", idx)
- coreConfig.Physical = physBundle.Backend
- localConfig.Physical = physBundle.Backend
- }
-
- // HA Backend setup
- haBackend := physBundle.HABackend
- if haBackend == nil {
- if ha, ok := physBundle.Backend.(physical.HABackend); ok {
- haBackend = ha
- }
- }
- coreConfig.HAPhysical = haBackend
- localConfig.HAPhysical = haBackend
-
- // Cleanup setup
- if physBundle.Cleanup != nil {
- cleanupFunc = physBundle.Cleanup
- }
- }
- }
-
- if opts != nil && opts.ClusterLayers != nil {
- localConfig.ClusterNetworkLayer = opts.ClusterLayers.Layers()[idx]
- localConfig.ClusterAddr = "https://" + localConfig.ClusterNetworkLayer.Listeners()[0].Addr().String()
- }
-
- switch {
- case localConfig.LicensingConfig != nil:
- if pubKey != nil {
- localConfig.LicensingConfig.AdditionalPublicKeys = append(localConfig.LicensingConfig.AdditionalPublicKeys, pubKey)
- }
- default:
- localConfig.LicensingConfig = testGetLicensingConfig(pubKey)
- }
-
- if localConfig.MetricsHelper == nil {
- inm := metrics.NewInmemSink(10*time.Second, time.Minute)
- metrics.DefaultInmemSignal(inm)
- localConfig.MetricsHelper = metricsutil.NewMetricsHelper(inm, false)
- }
- if opts != nil && opts.CoreMetricSinkProvider != nil {
- localConfig.MetricSink, localConfig.MetricsHelper = opts.CoreMetricSinkProvider(localConfig.ClusterName)
- }
-
- if opts != nil && opts.CoreMetricSinkProvider != nil {
- localConfig.MetricSink, localConfig.MetricsHelper = opts.CoreMetricSinkProvider(localConfig.ClusterName)
- }
-
- localConfig.NumExpirationWorkers = numExpirationWorkersTest
-
- c, err := NewCore(&localConfig)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- c.coreNumber = firstCoreNumber + idx
- c.PR1103disabled = disablePR1103
- if opts != nil && opts.HandlerFunc != nil {
- props := opts.DefaultHandlerProperties
- props.Core = c
- if props.ListenerConfig != nil && props.ListenerConfig.MaxRequestDuration == 0 {
- props.ListenerConfig.MaxRequestDuration = DefaultMaxRequestDuration
- }
- handler = opts.HandlerFunc.Handler(&props)
- }
-
- if opts != nil && opts.RequestResponseCallback != nil {
- c.requestResponseCallback = opts.RequestResponseCallback
- }
-
- // Set this in case the Seal was manually set before the core was
- // created
- if localConfig.Seal != nil {
- localConfig.Seal.SetCore(c)
- }
-
- return cleanupFunc, c, localConfig, handler
-}
-
-func (testCluster *TestCluster) setupClusterListener(
- t testing.T, idx int, core *Core, coreConfig *CoreConfig,
- opts *TestClusterOptions, listeners []*TestListener, handler http.Handler,
-) {
- if coreConfig.ClusterAddr == "" {
- return
- }
-
- clusterAddrGen := func(lns []*TestListener, port int) []*net.TCPAddr {
- ret := make([]*net.TCPAddr, len(lns))
- for i, ln := range lns {
- ret[i] = &net.TCPAddr{
- IP: ln.Address.IP,
- Port: port,
- }
- }
- return ret
- }
-
- baseClusterListenPort := 0
- if opts != nil && opts.BaseClusterListenPort != 0 {
- if opts.BaseListenAddress == "" {
- t.Fatal("BaseListenAddress is not specified")
- }
- baseClusterListenPort = opts.BaseClusterListenPort
- }
-
- port := 0
- if baseClusterListenPort != 0 {
- port = baseClusterListenPort + idx
- }
- core.Logger().Info("assigning cluster listener for test core", "core", idx, "port", port)
- core.SetClusterListenerAddrs(clusterAddrGen(listeners, port))
- core.SetClusterHandler(handler)
-}
-
-func (tc *TestCluster) InitCores(t testing.T, opts *TestClusterOptions, addAuditBackend bool) {
- tc.initCores(t, opts, addAuditBackend)
-}
-
-func (tc *TestCluster) initCores(t testing.T, opts *TestClusterOptions, addAuditBackend bool) {
- leader := tc.Cores[0]
-
- bKeys, rKeys, root := TestCoreInitClusterWrapperSetup(t, leader.Core, leader.Handler)
- barrierKeys, _ := copystructure.Copy(bKeys)
- tc.BarrierKeys = barrierKeys.([][]byte)
- recoveryKeys, _ := copystructure.Copy(rKeys)
- tc.RecoveryKeys = recoveryKeys.([][]byte)
- tc.RootToken = root
-
- // Write root token and barrier keys
- err := ioutil.WriteFile(filepath.Join(tc.TempDir, "root_token"), []byte(root), 0o755)
- if err != nil {
- t.Fatal(err)
- }
- var buf bytes.Buffer
- for i, key := range tc.BarrierKeys {
- buf.Write([]byte(base64.StdEncoding.EncodeToString(key)))
- if i < len(tc.BarrierKeys)-1 {
- buf.WriteRune('\n')
- }
- }
- err = ioutil.WriteFile(filepath.Join(tc.TempDir, "barrier_keys"), buf.Bytes(), 0o755)
- if err != nil {
- t.Fatal(err)
- }
- for i, key := range tc.RecoveryKeys {
- buf.Write([]byte(base64.StdEncoding.EncodeToString(key)))
- if i < len(tc.RecoveryKeys)-1 {
- buf.WriteRune('\n')
- }
- }
- err = ioutil.WriteFile(filepath.Join(tc.TempDir, "recovery_keys"), buf.Bytes(), 0o755)
- if err != nil {
- t.Fatal(err)
- }
-
- // Unseal first core
- for _, key := range bKeys {
- if _, err := leader.Core.Unseal(TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- ctx := context.Background()
-
- // If stored keys is supported, the above will no no-op, so trigger auto-unseal
- // using stored keys to try to unseal
- if err := leader.Core.UnsealWithStoredKeys(ctx); err != nil {
- t.Fatal(err)
- }
-
- // Verify unsealed
- if leader.Core.Sealed() {
- t.Fatal("should not be sealed")
- }
-
- TestWaitActive(t, leader.Core)
-
- kvVersion := "1"
- if opts != nil {
- kvVersion = opts.KVVersion
- }
-
- // Existing tests rely on this; we can make a toggle to disable it
- // later if we want
- kvReq := &logical.Request{
- Operation: logical.UpdateOperation,
- ClientToken: tc.RootToken,
- Path: "sys/mounts/secret",
- Data: map[string]interface{}{
- "type": "kv",
- "path": "secret/",
- "description": "key/value secret storage",
- "options": map[string]string{
- "version": kvVersion,
- },
- },
- }
- resp, err := leader.Core.HandleRequest(namespace.RootContext(ctx), kvReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp.IsError() {
- t.Fatal(err)
- }
-
- cfg, err := leader.Core.seal.BarrierConfig(ctx)
- if err != nil {
- t.Fatal(err)
- }
-
- // Unseal other cores unless otherwise specified
- numCores := len(tc.Cores)
- if (opts == nil || !opts.KeepStandbysSealed) && numCores > 1 {
- for i := 1; i < numCores; i++ {
- tc.Cores[i].Core.seal.SetCachedBarrierConfig(cfg)
- for _, key := range bKeys {
- if _, err := tc.Cores[i].Core.Unseal(TestKeyCopy(key)); err != nil {
- t.Fatalf("unseal err: %s", err)
- }
- }
-
- // If stored keys is supported, the above will no no-op, so trigger auto-unseal
- // using stored keys
- if err := tc.Cores[i].Core.UnsealWithStoredKeys(ctx); err != nil {
- t.Fatal(err)
- }
- }
-
- // Let them come fully up to standby
- time.Sleep(2 * time.Second)
-
- // Ensure cluster connection info is populated.
- // Other cores should not come up as leaders.
- for i := 1; i < numCores; i++ {
- isLeader, _, _, err := tc.Cores[i].Core.Leader()
- if err != nil {
- t.Fatal(err)
- }
- if isLeader {
- t.Fatalf("core[%d] should not be leader", i)
- }
- }
- }
-
- //
- // Set test cluster core(s) and test cluster
- //
- cluster, err := leader.Core.Cluster(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- tc.ID = cluster.ID
-
- if addAuditBackend {
- // Enable auditing.
- auditReq := &logical.Request{
- Operation: logical.UpdateOperation,
- ClientToken: tc.RootToken,
- Path: "sys/audit/noop",
- Data: map[string]interface{}{
- "type": "noop",
- },
- }
- resp, err = leader.Core.HandleRequest(namespace.RootContext(ctx), auditReq)
- if err != nil {
- t.Fatal(err)
- }
-
- if resp.IsError() {
- t.Fatal(err)
- }
- }
-}
-
-func (testCluster *TestCluster) getAPIClient(
- t testing.T, opts *TestClusterOptions,
- port int, tlsConfig *tls.Config,
-) *api.Client {
- transport := cleanhttp.DefaultPooledTransport()
- transport.TLSClientConfig = tlsConfig.Clone()
- if err := http2.ConfigureTransport(transport); err != nil {
- t.Fatal(err)
- }
- client := &http.Client{
- Transport: transport,
- CheckRedirect: func(*http.Request, []*http.Request) error {
- // This can of course be overridden per-test by using its own client
- return fmt.Errorf("redirects not allowed in these tests")
- },
- }
- config := api.DefaultConfig()
- if config.Error != nil {
- t.Fatal(config.Error)
- }
- config.Address = fmt.Sprintf("https://127.0.0.1:%d", port)
- config.HttpClient = client
- config.MaxRetries = 0
- apiClient, err := api.NewClient(config)
- if err != nil {
- t.Fatal(err)
- }
- if opts == nil || !opts.SkipInit {
- apiClient.SetToken(testCluster.RootToken)
- }
- return apiClient
-}
-
-func (c *TestCluster) GetBarrierOrRecoveryKeys() [][]byte {
- if c.Cores[0].SealAccess().RecoveryKeySupported() {
- return c.GetRecoveryKeys()
- } else {
- return c.GetBarrierKeys()
- }
-}
-
-func (c *TestCluster) GetCACertPEMFile() string {
- return c.CACertPEMFile
-}
-
-func (c *TestCluster) ClusterID() string {
- return c.ID
-}
-
-func (c *TestCluster) Nodes() []testcluster.VaultClusterNode {
- ret := make([]testcluster.VaultClusterNode, len(c.Cores))
- for i, core := range c.Cores {
- ret[i] = core
- }
- return ret
-}
-
-func (c *TestCluster) SetBarrierKeys(keys [][]byte) {
- c.BarrierKeys = make([][]byte, len(keys))
- for i, k := range keys {
- c.BarrierKeys[i] = TestKeyCopy(k)
- }
-}
-
-func (c *TestCluster) SetRecoveryKeys(keys [][]byte) {
- c.RecoveryKeys = make([][]byte, len(keys))
- for i, k := range keys {
- c.RecoveryKeys[i] = TestKeyCopy(k)
- }
-}
-
-func (c *TestCluster) GetBarrierKeys() [][]byte {
- ret := make([][]byte, len(c.BarrierKeys))
- for i, k := range c.BarrierKeys {
- ret[i] = TestKeyCopy(k)
- }
- return ret
-}
-
-func (c *TestCluster) GetRecoveryKeys() [][]byte {
- ret := make([][]byte, len(c.RecoveryKeys))
- for i, k := range c.RecoveryKeys {
- ret[i] = TestKeyCopy(k)
- }
- return ret
-}
-
-func (c *TestCluster) NamedLogger(name string) log.Logger {
- return c.Logger.Named(name)
-}
-
-func (c *TestCluster) GetRootToken() string {
- return c.RootToken
-}
-
-func (c *TestClusterCore) Name() string {
- return c.NodeID
-}
-
-func (c *TestClusterCore) APIClient() *api.Client {
- return c.Client
-}
-
-var (
- _ testcluster.VaultCluster = &TestCluster{}
- _ testcluster.VaultClusterNode = &TestClusterCore{}
-)
diff --git a/vault/testing_util.go b/vault/testing_util.go
deleted file mode 100644
index 0cbd1a4fb..000000000
--- a/vault/testing_util.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-//go:build !enterprise
-
-package vault
-
-import (
- "crypto/ed25519"
-
- testing "github.com/mitchellh/go-testing-interface"
-)
-
-func GenerateTestLicenseKeys() (ed25519.PublicKey, ed25519.PrivateKey, error) { return nil, nil, nil }
-func testGetLicensingConfig(key ed25519.PublicKey) *LicensingConfig { return &LicensingConfig{} }
-func testExtraTestCoreSetup(testing.T, ed25519.PrivateKey, *TestClusterCore) {}
-func testAdjustUnderlyingStorage(tcc *TestClusterCore) {
- tcc.UnderlyingStorage = tcc.physical
-}
-func testApplyEntBaseConfig(coreConfig, base *CoreConfig) {}
diff --git a/vault/token_store_test.go b/vault/token_store_test.go
deleted file mode 100644
index 17d497701..000000000
--- a/vault/token_store_test.go
+++ /dev/null
@@ -1,6181 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "path"
- "reflect"
- "sort"
- "strings"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/go-test/deep"
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/hashicorp/errwrap"
- "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/go-secure-stdlib/parseutil"
- "github.com/hashicorp/go-sockaddr"
- "github.com/hashicorp/go-uuid"
- "github.com/hashicorp/vault/helper/benchhelpers"
- "github.com/hashicorp/vault/helper/identity"
- "github.com/hashicorp/vault/helper/metricsutil"
- "github.com/hashicorp/vault/helper/namespace"
- "github.com/hashicorp/vault/sdk/helper/consts"
- "github.com/hashicorp/vault/sdk/helper/locksutil"
- "github.com/hashicorp/vault/sdk/helper/tokenutil"
- "github.com/hashicorp/vault/sdk/logical"
- "github.com/mitchellh/mapstructure"
-)
-
-func TestTokenStore_CreateOrphanResponse(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- resp, err := c.HandleRequest(namespace.RootContext(nil), &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "auth/token/create-orphan",
- ClientToken: root,
- Data: map[string]interface{}{
- "policies": "default",
- },
- })
- if err != nil && (resp != nil && resp.IsError()) {
- t.Fatalf("bad: err: %v, resp: %#v", err, resp)
- }
- if !resp.Auth.Orphan {
- t.Fatalf("failed to set orphan as true in the response")
- }
-}
-
-// TestTokenStore_CubbyholeDeletion tests that a token's cubbyhole
-// can be used and that the cubbyhole is removed after the token is revoked.
-func TestTokenStore_CubbyholeDeletion(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- testTokenStore_CubbyholeDeletion(t, c, root)
-}
-
-// TestTokenStore_CubbyholeDeletionSSCTokensDisabled tests that a legacy token's
-// cubbyhole can be used, and that the cubbyhole is removed after the token is revoked.
-func TestTokenStore_CubbyholeDeletionSSCTokensDisabled(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- c.disableSSCTokens = true
- testTokenStore_CubbyholeDeletion(t, c, root)
-}
-
-func testTokenStore_CubbyholeDeletion(t *testing.T, c *Core, root string) {
- ts := c.tokenStore
-
- for i := 0; i < 10; i++ {
- // Create a token
- tokenReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "create",
- ClientToken: root,
- Data: map[string]interface{}{
- "ttl": "600s",
- },
- }
- // Supplying token ID forces SHA1 hashing to be used
- if i%2 == 0 {
- tokenReq.Data = map[string]interface{}{
- "id": "testroot",
- "ttl": "600s",
- }
- }
- resp := testMakeTokenViaRequest(t, ts, tokenReq)
- token := resp.Auth.ClientToken
-
- // Write data in the token's cubbyhole
- resp, err := c.HandleRequest(namespace.RootContext(nil), &logical.Request{
- ClientToken: token,
- Operation: logical.UpdateOperation,
- Path: "cubbyhole/sample/data",
- Data: map[string]interface{}{
- "foo": "bar",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- // Revoke the token
- resp, err = ts.HandleRequest(namespace.RootContext(nil), &logical.Request{
- ClientToken: token,
- Path: "revoke-self",
- Operation: logical.UpdateOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- }
-
- // List the cubbyhole keys
- cubbyholeKeys, err := ts.cubbyholeBackend.storageView.List(namespace.RootContext(nil), "")
- if err != nil {
- t.Fatal(err)
- }
-
- // There should be no entries
- if len(cubbyholeKeys) != 0 {
- t.Fatalf("bad: len(cubbyholeKeys); expected: 0, actual: %d", len(cubbyholeKeys))
- }
-}
-
-func TestTokenStore_CubbyholeTidy(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- testTokenStore_CubbyholeTidy(t, c, root, namespace.RootContext(nil))
-}
-
-func testTokenStore_CubbyholeTidy(t *testing.T, c *Core, root string, nsCtx context.Context) {
- ts := c.tokenStore
-
- backend := c.router.MatchingBackend(nsCtx, mountPathCubbyhole)
- view := c.router.MatchingStorageByAPIPath(nsCtx, mountPathCubbyhole)
-
- for i := 1; i <= 20; i++ {
- // Create 20 tokens
- tokenReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "create",
- ClientToken: root,
- Data: map[string]interface{}{
- "ttl": "600s",
- },
- }
-
- resp := testMakeTokenViaRequestContext(t, nsCtx, ts, tokenReq)
- token := resp.Auth.ClientToken
-
- // Supplying token ID forces SHA1 hashing to be used
- if i%3 == 0 {
- tokenReq.Data = map[string]interface{}{
- "id": "testroot",
- "ttl": "600s",
- }
- }
-
- // Create 4 junk cubbyhole entries
- if i%5 == 0 {
- invalidToken, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
-
- resp, err := backend.HandleRequest(nsCtx, &logical.Request{
- ClientToken: invalidToken,
- Operation: logical.UpdateOperation,
- Path: "cubbyhole/sample/data",
- Data: map[string]interface{}{
- "foo": "bar",
- },
- Storage: view,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- }
-
- // Write into cubbyholes of 10 tokens
- if i%2 == 0 {
- continue
- }
- resp, err := c.HandleRequest(nsCtx, &logical.Request{
- ClientToken: token,
- Operation: logical.UpdateOperation,
- Path: "cubbyhole/sample/data",
- Data: map[string]interface{}{
- "foo": "bar",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- }
-
- // List all the cubbyhole storage keys
- cubbyholeKeys, err := view.List(nsCtx, "")
- if err != nil {
- t.Fatal(err)
- }
-
- if len(cubbyholeKeys) != 14 {
- t.Fatalf("bad: len(cubbyholeKeys); expected: 14, actual: %d", len(cubbyholeKeys))
- }
-
- // Tidy cubbyhole storage
- resp, err := ts.HandleRequest(nsCtx, &logical.Request{
- Path: "tidy",
- Operation: logical.UpdateOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- // Wait for tidy operation to complete
- time.Sleep(2 * time.Second)
-
- // List all the cubbyhole storage keys
- cubbyholeKeys, err = view.List(nsCtx, "")
- if err != nil {
- t.Fatal(err)
- }
-
- // The junk entries must have been cleaned up
- if len(cubbyholeKeys) != 10 {
- t.Fatalf("bad: len(cubbyholeKeys); expected: 10, actual: %d", len(cubbyholeKeys))
- }
-}
-
-func TestTokenStore_Salting(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- saltedID, err := ts.SaltID(namespace.RootContext(nil), "foo")
- if err != nil {
- t.Fatal(err)
- }
- if strings.HasPrefix(saltedID, "h") {
- t.Fatalf("expected sha1 hash; got sha2-256 hmac")
- }
-
- saltedID, err = ts.SaltID(namespace.RootContext(nil), "hvs.foo")
- if err != nil {
- t.Fatal(err)
- }
- if !strings.HasPrefix(saltedID, "h") {
- t.Fatalf("expected sha2-256 hmac; got sha1 hash")
- }
-
- nsCtx := namespace.ContextWithNamespace(context.Background(), &namespace.Namespace{ID: "testid", Path: "ns1"})
- saltedID, err = ts.SaltID(nsCtx, "foo")
- if err != nil {
- t.Fatal(err)
- }
- if !strings.HasPrefix(saltedID, "h") {
- t.Fatalf("expected sha2-256 hmac; got sha1 hash")
- }
-
- saltedID, err = ts.SaltID(nsCtx, "hvs.foo")
- if err != nil {
- t.Fatal(err)
- }
- if !strings.HasPrefix(saltedID, "h") {
- t.Fatalf("expected sha2-256 hmac; got sha1 hash")
- }
-}
-
-type TokenEntryOld struct {
- ID string
- Accessor string
- Parent string
- Policies []string
- Path string
- Meta map[string]string
- DisplayName string
- NumUses int
- CreationTime int64
- TTL time.Duration
- ExplicitMaxTTL time.Duration
- Role string
- Period time.Duration
-}
-
-func TestTokenStore_TokenEntryUpgrade(t *testing.T) {
- var err error
- c, _, _ := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- // Use a struct that does not have struct tags to store the items and
- // check if the lookup code handles them properly while reading back
- entry := &TokenEntryOld{
- DisplayName: "test-display-name",
- Path: "test",
- Policies: []string{"dev", "ops"},
- CreationTime: time.Now().Unix(),
- ExplicitMaxTTL: 100,
- NumUses: 10,
- }
- entry.ID, err = uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
-
- enc, err := json.Marshal(entry)
- if err != nil {
- t.Fatal(err)
- }
-
- saltedID, err := ts.SaltID(namespace.RootContext(nil), entry.ID)
- if err != nil {
- t.Fatal(err)
- }
- le := &logical.StorageEntry{
- Key: saltedID,
- Value: enc,
- }
-
- if err := ts.idView(namespace.RootNamespace).Put(namespace.RootContext(nil), le); err != nil {
- t.Fatal(err)
- }
-
- // Register with exp manager so lookup works
- auth := &logical.Auth{
- DisplayName: entry.DisplayName,
- CreationPath: entry.Path,
- Policies: entry.Policies,
- ExplicitMaxTTL: entry.ExplicitMaxTTL,
- NumUses: entry.NumUses,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- ClientToken: entry.ID,
- }
- // Same as entry from TokenEntryOld, but used for RegisterAuth
- registryEntry := &logical.TokenEntry{
- DisplayName: entry.DisplayName,
- Path: entry.Path,
- Policies: entry.Policies,
- CreationTime: entry.CreationTime,
- ExplicitMaxTTL: entry.ExplicitMaxTTL,
- NumUses: entry.NumUses,
- NamespaceID: namespace.RootNamespaceID,
- }
-
- if err := ts.expiration.RegisterAuth(namespace.RootContext(nil), registryEntry, auth, ""); err != nil {
- t.Fatal(err)
- }
-
- out, err := ts.Lookup(namespace.RootContext(nil), entry.ID)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if out.DisplayName != "test-display-name" {
- t.Fatalf("bad: display_name: expected: test-display-name, actual: %s", out.DisplayName)
- }
- if out.CreationTime == 0 {
- t.Fatal("bad: expected a non-zero creation time")
- }
- if out.ExplicitMaxTTL != 100 {
- t.Fatalf("bad: explicit_max_ttl: expected: 100, actual: %d", out.ExplicitMaxTTL)
- }
- if out.NumUses != 10 {
- t.Fatalf("bad: num_uses: expected: 10, actual: %d", out.NumUses)
- }
-
- // Test the default case to ensure there are no regressions
- ent := &logical.TokenEntry{
- DisplayName: "test-display-name",
- Path: "test",
- Policies: []string{"dev", "ops"},
- CreationTime: time.Now().Unix(),
- ExplicitMaxTTL: 100,
- NumUses: 10,
- NamespaceID: namespace.RootNamespaceID,
- }
- if err := ts.create(namespace.RootContext(nil), ent); err != nil {
- t.Fatalf("err: %s", err)
- }
- auth = &logical.Auth{
- DisplayName: ent.DisplayName,
- CreationPath: ent.Path,
- Policies: ent.Policies,
- ExplicitMaxTTL: ent.ExplicitMaxTTL,
- NumUses: ent.NumUses,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- ClientToken: ent.ID,
- }
- if err := ts.expiration.RegisterAuth(namespace.RootContext(nil), ent, auth, ""); err != nil {
- t.Fatal(err)
- }
-
- out, err = ts.Lookup(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if out.DisplayName != "test-display-name" {
- t.Fatalf("bad: display_name: expected: test-display-name, actual: %s", out.DisplayName)
- }
- if out.CreationTime == 0 {
- t.Fatal("bad: expected a non-zero creation time")
- }
- if out.ExplicitMaxTTL != 100 {
- t.Fatalf("bad: explicit_max_ttl: expected: 100, actual: %d", out.ExplicitMaxTTL)
- }
- if out.NumUses != 10 {
- t.Fatalf("bad: num_uses: expected: 10, actual: %d", out.NumUses)
- }
-
- // Fill in the deprecated fields and read out from proper fields
- ent = &logical.TokenEntry{
- Path: "test",
- Policies: []string{"dev", "ops"},
- DisplayNameDeprecated: "test-display-name",
- CreationTimeDeprecated: time.Now().Unix(),
- ExplicitMaxTTLDeprecated: 100,
- NumUsesDeprecated: 10,
- NamespaceID: namespace.RootNamespaceID,
- }
- if err := ts.create(namespace.RootContext(nil), ent); err != nil {
- t.Fatalf("err: %s", err)
- }
- auth = &logical.Auth{
- DisplayName: ent.DisplayName,
- CreationPath: ent.Path,
- Policies: ent.Policies,
- ExplicitMaxTTL: ent.ExplicitMaxTTL,
- NumUses: ent.NumUses,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- ClientToken: ent.ID,
- }
- if err := ts.expiration.RegisterAuth(namespace.RootContext(nil), ent, auth, ""); err != nil {
- t.Fatal(err)
- }
-
- out, err = ts.Lookup(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if out.DisplayName != "test-display-name" {
- t.Fatalf("bad: display_name: expected: test-display-name, actual: %s", out.DisplayName)
- }
- if out.CreationTime == 0 {
- t.Fatal("bad: expected a non-zero creation time")
- }
- if out.ExplicitMaxTTL != 100 {
- t.Fatalf("bad: explicit_max_ttl: expected: 100, actual: %d", out.ExplicitMaxTTL)
- }
- if out.NumUses != 10 {
- t.Fatalf("bad: num_uses: expected: 10, actual: %d", out.NumUses)
- }
-
- // Check if NumUses picks up a lower value
- ent = &logical.TokenEntry{
- Path: "test",
- NumUses: 5,
- NumUsesDeprecated: 10,
- NamespaceID: namespace.RootNamespaceID,
- }
- if err := ts.create(namespace.RootContext(nil), ent); err != nil {
- t.Fatalf("err: %s", err)
- }
- auth = &logical.Auth{
- DisplayName: ent.DisplayName,
- CreationPath: ent.Path,
- Policies: ent.Policies,
- ExplicitMaxTTL: ent.ExplicitMaxTTL,
- NumUses: ent.NumUses,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- ClientToken: ent.ID,
- }
- if err := ts.expiration.RegisterAuth(namespace.RootContext(nil), ent, auth, ""); err != nil {
- t.Fatal(err)
- }
-
- out, err = ts.Lookup(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if out.NumUses != 5 {
- t.Fatalf("bad: num_uses: expected: 5, actual: %d", out.NumUses)
- }
-
- // Switch the values from deprecated and proper field and check if the
- // lower value is still getting picked up
- ent = &logical.TokenEntry{
- Path: "test",
- NumUses: 10,
- NumUsesDeprecated: 5,
- NamespaceID: namespace.RootNamespaceID,
- }
- if err := ts.create(namespace.RootContext(nil), ent); err != nil {
- t.Fatalf("err: %s", err)
- }
- auth = &logical.Auth{
- DisplayName: ent.DisplayName,
- CreationPath: ent.Path,
- Policies: ent.Policies,
- ExplicitMaxTTL: ent.ExplicitMaxTTL,
- NumUses: ent.NumUses,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- ClientToken: ent.ID,
- }
- if err := ts.expiration.RegisterAuth(namespace.RootContext(nil), ent, auth, ""); err != nil {
- t.Fatal(err)
- }
-
- out, err = ts.Lookup(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if out.NumUses != 5 {
- t.Fatalf("bad: num_uses: expected: 5, actual: %d", out.NumUses)
- }
-}
-
-func getBackendConfig(c *Core) *logical.BackendConfig {
- return &logical.BackendConfig{
- Logger: c.logger,
- System: logical.StaticSystemView{
- DefaultLeaseTTLVal: time.Hour * 24,
- MaxLeaseTTLVal: time.Hour * 24 * 32,
- },
- }
-}
-
-func testMakeServiceTokenViaBackend(t testing.TB, ts *TokenStore, root, client, ttl string, policy []string) {
- t.Helper()
- testMakeTokenViaBackend(t, ts, root, client, ttl, policy, false)
-}
-
-func testMakeTokenViaBackend(t testing.TB, ts *TokenStore, root, client, ttl string, policy []string, batch bool) {
- t.Helper()
- req := logical.TestRequest(benchhelpers.TBtoT(t), logical.UpdateOperation, "create")
- req.ClientToken = root
- if batch {
- req.Data["type"] = "batch"
- } else {
- req.Data["id"] = client
- }
- req.Data["policies"] = policy
- req.Data["ttl"] = ttl
- resp := testMakeTokenViaRequest(t, ts, req)
-
- if resp.Auth.ClientToken != client {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func testMakeTokenViaRequest(t testing.TB, ts *TokenStore, req *logical.Request) *logical.Response {
- t.Helper()
- return testMakeTokenViaRequestContext(t, namespace.RootContext(nil), ts, req)
-}
-
-func testMakeTokenViaRequestContext(t testing.TB, ctx context.Context, ts *TokenStore, req *logical.Request) *logical.Response {
- t.Helper()
- resp, err := ts.HandleRequest(ctx, req)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatalf("got nil token from create call")
- }
- // Let the caller handle the error
- if resp.IsError() {
- return resp
- }
-
- ns, err := namespace.FromContext(ctx)
- if err != nil {
- t.Fatal(err)
- }
-
- te := &logical.TokenEntry{
- Path: resp.Auth.CreationPath,
- NamespaceID: ns.ID,
- }
-
- if resp.Auth.TokenType != logical.TokenTypeBatch {
- if err := ts.expiration.RegisterAuth(ctx, te, resp.Auth, ""); err != nil {
- t.Fatal(err)
- }
- }
-
- te, err = ts.Lookup(ctx, resp.Auth.ClientToken)
- if err != nil {
- t.Fatal(err)
- }
- if te == nil {
- t.Fatal("token entry was nil")
- }
-
- return resp
-}
-
-func testMakeTokenDirectly(t testing.TB, ts *TokenStore, te *logical.TokenEntry) {
- if te.NamespaceID == "" {
- te.NamespaceID = namespace.RootNamespaceID
- }
- if te.CreationTime == 0 {
- te.CreationTime = time.Now().Unix()
- }
- if err := ts.create(namespace.RootContext(nil), te); err != nil {
- t.Fatal(err)
- }
- if te.Type == logical.TokenTypeDefault {
- te.Type = logical.TokenTypeService
- }
- auth := &logical.Auth{
- NumUses: te.NumUses,
- DisplayName: te.DisplayName,
- Policies: te.Policies,
- Metadata: te.Meta,
- LeaseOptions: logical.LeaseOptions{
- TTL: te.TTL,
- Renewable: te.TTL > 0,
- },
- ClientToken: te.ID,
- Accessor: te.Accessor,
- EntityID: te.EntityID,
- Period: te.Period,
- ExplicitMaxTTL: te.ExplicitMaxTTL,
- CreationPath: te.Path,
- TokenType: te.Type,
- }
- err := ts.expiration.RegisterAuth(namespace.RootContext(nil), te, auth, "")
- switch err {
- case nil:
- if te.Type == logical.TokenTypeBatch {
- t.Fatal("expected error from trying to register auth with batch token")
- }
- default:
- if te.Type != logical.TokenTypeBatch {
- t.Fatal(err)
- }
- }
-}
-
-func testMakeServiceTokenViaCore(t testing.TB, c *Core, root, client, ttl string, policy []string) {
- testMakeTokenViaCore(t, c, root, client, ttl, "", policy, false, nil)
-}
-
-func testMakeTokenViaCore(t testing.TB, c *Core, root, client, ttl, period string, policy []string, batch bool, outAuth *logical.Auth) {
- req := logical.TestRequest(benchhelpers.TBtoT(t), logical.UpdateOperation, "auth/token/create")
- req.ClientToken = root
- if batch {
- req.Data["type"] = "batch"
- } else {
- req.Data["id"] = client
- }
- req.Data["policies"] = policy
- req.Data["ttl"] = ttl
-
- if len(period) != 0 {
- req.Data["period"] = period
- }
-
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if !batch {
- if resp.Auth.ClientToken != client {
- t.Fatalf("bad: %#v", *resp)
- }
- }
- if outAuth != nil && resp != nil && resp.Auth != nil {
- *outAuth = *resp.Auth
- }
-}
-
-func TestTokenStore_AccessorIndex(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- ent := &logical.TokenEntry{
- Path: "test",
- Policies: []string{"dev", "ops"},
- TTL: time.Hour,
- NamespaceID: namespace.RootNamespaceID,
- }
- testMakeTokenDirectly(t, ts, ent)
-
- out, err := ts.Lookup(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- // Ensure that accessor is created
- if out == nil || out.Accessor == "" {
- t.Fatalf("bad: %#v", out)
- }
-
- aEntry, err := ts.lookupByAccessor(namespace.RootContext(nil), out.Accessor, false, false)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- // Verify that the value returned from the index matches the token ID
- if aEntry.TokenID != ent.ID {
- t.Fatalf("bad: got\n%s\nexpected\n%s\n", aEntry.TokenID, ent.ID)
- }
-
- // Make sure a batch token doesn't get an accessor
- ent.Type = logical.TokenTypeBatch
- testMakeTokenDirectly(t, ts, ent)
-
- out, err = ts.Lookup(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- // Ensure that accessor is created
- if out == nil || out.Accessor != "" {
- t.Fatalf("bad: %#v", out)
- }
-}
-
-func TestTokenStore_HandleRequest_LookupAccessor(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- testMakeServiceTokenViaBackend(t, ts, root, "tokenid", "60s", []string{"foo"})
- out, err := ts.Lookup(namespace.RootContext(nil), "tokenid")
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if out == nil {
- t.Fatalf("err: %s", err)
- }
-
- req := logical.TestRequest(t, logical.UpdateOperation, "lookup-accessor")
- req.Data = map[string]interface{}{
- "accessor": out.Accessor,
- }
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if resp.Data == nil {
- t.Fatalf("response should contain data")
- }
-
- if resp.Data["accessor"].(string) == "" {
- t.Fatalf("accessor should not be empty")
- }
-
- // Verify that the lookup-accessor operation does not return the token ID
- if resp.Data["id"].(string) != "" {
- t.Fatalf("token ID should not be returned")
- }
-}
-
-func TestTokenStore_HandleRequest_ListAccessors(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- testKeys := []string{"token1", "token2", "token3", "token4"}
- for _, key := range testKeys {
- testMakeServiceTokenViaBackend(t, ts, root, key, "60s", []string{"foo"})
- }
-
- // Revoke root to make the number of accessors match
- internalRoot, _ := c.DecodeSSCToken(root)
- salted, err := ts.SaltID(namespace.RootContext(nil), internalRoot)
- if err != nil {
- t.Fatal(err)
- }
- ts.revokeInternal(namespace.RootContext(nil), salted, false)
-
- req := logical.TestRequest(t, logical.ListOperation, "accessors/")
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if resp.Data == nil {
- t.Fatalf("response should contain data")
- }
- if resp.Data["keys"] == nil {
- t.Fatalf("keys should not be empty")
- }
- keys := resp.Data["keys"].([]string)
- if len(keys) != len(testKeys) {
- t.Fatalf("wrong number of accessors found")
- }
- if len(resp.Warnings) != 0 {
- t.Fatalf("got warnings:\n%#v", resp.Warnings)
- }
-
- // Test upgrade from old struct method of accessor storage (of token id)
- for _, accessor := range keys {
- aEntry, err := ts.lookupByAccessor(namespace.RootContext(nil), accessor, false, false)
- if err != nil {
- t.Fatal(err)
- }
- if aEntry.TokenID == "" || aEntry.AccessorID == "" {
- t.Fatalf("error, accessor entry looked up is empty, but no error thrown")
- }
- saltID, err := ts.SaltID(namespace.RootContext(nil), accessor)
- if err != nil {
- t.Fatal(err)
- }
- le := &logical.StorageEntry{Key: saltID, Value: []byte(aEntry.TokenID)}
- if err := ts.accessorView(namespace.RootNamespace).Put(namespace.RootContext(nil), le); err != nil {
- t.Fatalf("failed to persist accessor index entry: %v", err)
- }
- }
-
- // Do the lookup again, should get same result
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if resp.Data == nil {
- t.Fatalf("response should contain data")
- }
- if resp.Data["keys"] == nil {
- t.Fatalf("keys should not be empty")
- }
- keys2 := resp.Data["keys"].([]string)
- if len(keys) != len(testKeys) {
- t.Fatalf("wrong number of accessors found")
- }
- if len(resp.Warnings) != 0 {
- t.Fatalf("got warnings:\n%#v", resp.Warnings)
- }
-
- for _, accessor := range keys2 {
- aEntry, err := ts.lookupByAccessor(namespace.RootContext(nil), accessor, false, false)
- if err != nil {
- t.Fatal(err)
- }
- if aEntry.TokenID == "" || aEntry.AccessorID == "" {
- t.Fatalf("error, accessor entry looked up is empty, but no error thrown")
- }
- }
-}
-
-func TestTokenStore_HandleRequest_Renew_Revoke_Accessor(t *testing.T) {
- exp := mockExpiration(t)
- ts := exp.tokenStore
-
- rootToken, err := ts.rootToken(namespace.RootContext(nil))
- if err != nil {
- t.Fatal(err)
- }
- root := rootToken.ID
-
- testMakeServiceTokenViaBackend(t, ts, root, "tokenid", "", []string{"foo"})
-
- auth := &logical.Auth{
- ClientToken: "tokenid",
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- Renewable: true,
- },
- }
-
- te, err := ts.Lookup(namespace.RootContext(nil), "tokenid")
- if err != nil {
- t.Fatal(err)
- }
- if te == nil {
- t.Fatal("token entry was nil")
- }
-
- err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- out, err := ts.Lookup(namespace.RootContext(nil), "tokenid")
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if out == nil {
- t.Fatalf("err: %s", err)
- }
-
- lookupAccessor := func() int64 {
- req := logical.TestRequest(t, logical.UpdateOperation, "lookup-accessor")
- req.Data = map[string]interface{}{
- "accessor": out.Accessor,
- }
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if resp.Data == nil {
- t.Fatal("response should contain data")
- }
- if resp.Data["accessor"].(string) == "" {
- t.Fatal("accessor should not be empty")
- }
- ttl := resp.Data["ttl"].(int64)
- if ttl == 0 {
- t.Fatal("ttl was zero")
- }
- return ttl
- }
-
- firstTTL := lookupAccessor()
-
- // Sleep so we can verify renew behavior
- time.Sleep(10 * time.Second)
-
- secondTTL := lookupAccessor()
- if secondTTL >= firstTTL {
- t.Fatalf("second TTL %d was greater than first %d", secondTTL, firstTTL)
- }
-
- req := logical.TestRequest(t, logical.UpdateOperation, "renew-accessor")
- req.Data = map[string]interface{}{
- "accessor": out.Accessor,
- }
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if resp.Auth == nil {
- t.Fatal("resp auth is nil")
- }
- if resp.Auth.ClientToken != "" {
- t.Fatal("client token found in response")
- }
-
- thirdTTL := lookupAccessor()
- if thirdTTL <= secondTTL {
- t.Fatalf("third TTL %d was not greater than second %d", thirdTTL, secondTTL)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "revoke-accessor")
- req.Data = map[string]interface{}{
- "accessor": out.Accessor,
- }
-
- _, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- // Revoking the token using the accessor should be idempotent since
- // auth/token/revoke is
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if len(resp.Warnings) != 1 {
- t.Fatalf("Was expecting 1 warning, got %d", len(resp.Warnings))
- }
-
- time.Sleep(200 * time.Millisecond)
-
- out, err = ts.Lookup(namespace.RootContext(nil), "tokenid")
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- if out != nil {
- t.Fatalf("bad:\ngot %#v\nexpected: nil\n", out)
- }
-
- // Now test without registering the token through the expiration manager
- testMakeServiceTokenViaBackend(t, ts, root, "tokenid", "", []string{"foo"})
- out, err = ts.Lookup(namespace.RootContext(nil), "tokenid")
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if out == nil {
- t.Fatalf("err: %s", err)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "revoke-accessor")
- req.Data = map[string]interface{}{
- "accessor": out.Accessor,
- }
-
- _, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- time.Sleep(200 * time.Millisecond)
-
- out, err = ts.Lookup(namespace.RootContext(nil), "tokenid")
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- if out != nil {
- t.Fatalf("bad:\ngot %#v\nexpected: nil\n", out)
- }
-}
-
-func TestTokenStore_RootToken(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- te, err := ts.rootToken(namespace.RootContext(nil))
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if te.ID == "" {
- t.Fatalf("missing ID")
- }
-
- out, err := ts.Lookup(namespace.RootContext(nil), te.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- deepEqualTokenEntries(t, out, te)
-}
-
-func TestTokenStore_NoRootBatch(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/create")
- req.ClientToken = root
- req.Data["type"] = "batch"
- req.Data["policies"] = "root"
- req.Data["ttl"] = "5m"
-
- resp, err := c.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("expected response")
- }
- if !resp.IsError() {
- t.Fatalf("expected error, got %#v", *resp)
- }
-}
-
-func TestTokenStore_CreateLookup(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- ent := &logical.TokenEntry{
- NamespaceID: namespace.RootNamespaceID,
- Path: "test",
- Policies: []string{"dev", "ops"},
- TTL: time.Hour,
- }
- testMakeTokenDirectly(t, ts, ent)
- if ent.ID == "" {
- t.Fatalf("missing ID")
- }
-
- out, err := ts.Lookup(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- deepEqualTokenEntries(t, out, ent)
-
- // New store should share the salt
- ts2, err := NewTokenStore(namespace.RootContext(nil), hclog.New(&hclog.LoggerOptions{}), c, getBackendConfig(c))
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ts2.SetExpirationManager(c.expiration)
-
- // Should still match
- out, err = ts2.Lookup(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- deepEqualTokenEntries(t, out, ent)
-}
-
-func TestTokenStore_CreateLookup_ProvidedID(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- ent := &logical.TokenEntry{
- ID: "foobarbaz",
- NamespaceID: namespace.RootNamespaceID,
- Path: "test",
- Policies: []string{"dev", "ops"},
- TTL: time.Hour,
- }
- testMakeTokenDirectly(t, ts, ent)
- if ent.ID != "foobarbaz" {
- t.Fatalf("bad: ent.ID: expected:\"foobarbaz\"\n actual:%s", ent.ID)
- }
- if err := ts.create(namespace.RootContext(nil), ent); err == nil {
- t.Fatal("expected error creating token with the same ID")
- }
-
- out, err := ts.Lookup(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- deepEqualTokenEntries(t, out, ent)
-
- // New store should share the salt
- ts2, err := NewTokenStore(namespace.RootContext(nil), hclog.New(&hclog.LoggerOptions{}), c, getBackendConfig(c))
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ts2.SetExpirationManager(c.expiration)
-
- // Should still match
- out, err = ts2.Lookup(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- deepEqualTokenEntries(t, out, ent)
-}
-
-func TestTokenStore_CreateLookup_ExpirationInRestoreMode(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- ent := &logical.TokenEntry{
- NamespaceID: namespace.RootNamespaceID,
- Path: "test",
- Policies: []string{"dev", "ops"},
- }
- if err := ts.create(namespace.RootContext(nil), ent); err != nil {
- t.Fatalf("err: %v", err)
- }
- if ent.ID == "" {
- t.Fatalf("missing ID")
- }
-
- // Replace the lease with a lease with an expire time in the past
- saltedID, err := ts.SaltID(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Create a lease entry
- leaseID := path.Join(ent.Path, saltedID)
- le := &leaseEntry{
- LeaseID: leaseID,
- ClientToken: ent.ID,
- Path: ent.Path,
- IssueTime: time.Now(),
- ExpireTime: time.Now().Add(1 * time.Hour),
- namespace: namespace.RootNamespace,
- }
- if err := ts.expiration.persistEntry(namespace.RootContext(nil), le); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- out, err := ts.Lookup(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- deepEqualTokenEntries(t, out, ent)
-
- // Set to expired lease time
- le.ExpireTime = time.Now().Add(-1 * time.Hour)
- if err := ts.expiration.persistEntry(namespace.RootContext(nil), le); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- err = c.stopExpiration()
- if err != nil {
- t.Fatal(err)
- }
-
- // Reset expiration manager to restore mode
- ts.expiration.restoreModeLock.Lock()
- atomic.StoreInt32(ts.expiration.restoreMode, 1)
- ts.expiration.restoreLocks = locksutil.CreateLocks()
- ts.expiration.restoreModeLock.Unlock()
-
- // Test that the token lookup does not return the token entry due to the
- // expired lease
- out, err = ts.Lookup(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("lease expired, no token expected: %#v", out)
- }
-}
-
-func TestTokenStore_UseToken(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- // Lookup the root token
- ent, err := ts.Lookup(namespace.RootContext(nil), root)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Root is an unlimited use token, should be a no-op
- te, err := ts.UseToken(namespace.RootContext(nil), ent)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if te == nil {
- t.Fatalf("token entry after use was nil")
- }
-
- // Lookup the root token again
- ent2, err := ts.Lookup(namespace.RootContext(nil), root)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if !reflect.DeepEqual(ent, ent2) {
- t.Fatalf("bad: ent:%#v ent2:%#v", ent, ent2)
- }
-
- // Create a restricted token
- ent = &logical.TokenEntry{
- Path: "test",
- Policies: []string{"dev", "ops"},
- NumUses: 2,
- TTL: time.Hour,
- NamespaceID: namespace.RootNamespaceID,
- }
- testMakeTokenDirectly(t, ts, ent)
-
- // Use the token
- te, err = ts.UseToken(namespace.RootContext(nil), ent)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if te == nil {
- t.Fatalf("token entry for use #1 was nil")
- }
-
- // Lookup the token
- ent2, err = ts.Lookup(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should be reduced
- if ent2.NumUses != 1 {
- t.Fatalf("bad: %#v", ent2)
- }
-
- // Use the token
- te, err = ts.UseToken(namespace.RootContext(nil), ent)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if te == nil {
- t.Fatalf("token entry for use #2 was nil")
- }
- if te.NumUses != tokenRevocationPending {
- t.Fatalf("token entry after use #2 did not have revoke flag")
- }
- ts.revokeOrphan(namespace.RootContext(nil), te.ID)
-
- // Lookup the token
- ent2, err = ts.Lookup(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Should be revoked
- if ent2 != nil {
- t.Fatalf("bad: %#v", ent2)
- }
-}
-
-func TestTokenStore_Revoke(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- ent := &logical.TokenEntry{Path: "test", Policies: []string{"dev", "ops"}, NamespaceID: namespace.RootNamespaceID}
- if err := ts.create(namespace.RootContext(nil), ent); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- err := ts.revokeOrphan(namespace.RootContext(nil), "")
- if err.Error() != "cannot revoke blank token" {
- t.Fatalf("err: %v", err)
- }
- err = ts.revokeOrphan(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- out, err := ts.Lookup(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("bad: %#v", out)
- }
-}
-
-func TestTokenStore_Revoke_Leases(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- view := NewBarrierView(c.barrier, "noop/")
-
- // Mount a noop backend
- noop := &NoopBackend{}
- err := ts.expiration.router.Mount(noop, "noop/", &MountEntry{UUID: "noopuuid", Accessor: "noopaccessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- ent := &logical.TokenEntry{Path: "test", Policies: []string{"dev", "ops"}, NamespaceID: namespace.RootNamespaceID}
- if err := ts.create(namespace.RootContext(nil), ent); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Register a lease
- req := &logical.Request{
- Operation: logical.ReadOperation,
- Path: "noop/foo",
- ClientToken: ent.ID,
- }
- req.SetTokenEntry(ent)
- resp := &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: 20 * time.Millisecond,
- },
- },
- Data: map[string]interface{}{
- "access_key": "xyz",
- "secret_key": "abcd",
- },
- }
- leaseID, err := ts.expiration.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Revoke the token
- err = ts.revokeOrphan(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- time.Sleep(200 * time.Millisecond)
-
- // Verify the lease is gone
- out, err := ts.expiration.loadEntry(namespace.RootContext(nil), leaseID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("bad: %#v", out)
- }
-}
-
-func TestTokenStore_Revoke_Orphan(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- ent := &logical.TokenEntry{
- NamespaceID: namespace.RootNamespaceID,
- Path: "test",
- Policies: []string{"dev", "ops"},
- TTL: time.Hour,
- }
- testMakeTokenDirectly(t, ts, ent)
-
- ent2 := &logical.TokenEntry{
- NamespaceID: namespace.RootNamespaceID,
- Parent: ent.ID,
- TTL: time.Hour,
- }
- testMakeTokenDirectly(t, ts, ent2)
-
- err := ts.revokeOrphan(namespace.RootContext(nil), ent.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- out, err := ts.Lookup(namespace.RootContext(nil), ent2.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Unset the expected token parent's ID
- ent2.Parent = ""
-
- deepEqualTokenEntries(t, out, ent2)
-}
-
-// This was the original function name, and now it just calls
-// the non recursive version for a variety of depths.
-func TestTokenStore_RevokeTree(t *testing.T) {
- testTokenStore_RevokeTree_NonRecursive(t, 1, false)
- testTokenStore_RevokeTree_NonRecursive(t, 2, false)
- testTokenStore_RevokeTree_NonRecursive(t, 10, false)
-
- // corrupted trees with cycles
- testTokenStore_RevokeTree_NonRecursive(t, 1, true)
- testTokenStore_RevokeTree_NonRecursive(t, 10, true)
-}
-
-// Revokes a given Token Store tree non recursively.
-// The second parameter refers to the depth of the tree.
-func testTokenStore_RevokeTree_NonRecursive(t testing.TB, depth uint64, injectCycles bool) {
- c, _, _ := TestCoreUnsealed(benchhelpers.TBtoT(t))
- ts := c.tokenStore
- root, children := buildTokenTree(t, ts, depth)
-
- var cyclePaths []string
- if injectCycles {
- // Make the root the parent of itself
- saltedRoot, _ := ts.SaltID(namespace.RootContext(nil), root.ID)
- key := fmt.Sprintf("%s/%s", saltedRoot, saltedRoot)
- cyclePaths = append(cyclePaths, key)
- le := &logical.StorageEntry{Key: key}
-
- if err := ts.parentView(namespace.RootNamespace).Put(namespace.RootContext(nil), le); err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Make a deep child the parent of a shallow child
- shallow, _ := ts.SaltID(namespace.RootContext(nil), children[0].ID)
- deep, _ := ts.SaltID(namespace.RootContext(nil), children[len(children)-1].ID)
- key = fmt.Sprintf("%s/%s", deep, shallow)
- cyclePaths = append(cyclePaths, key)
- le = &logical.StorageEntry{Key: key}
-
- if err := ts.parentView(namespace.RootNamespace).Put(namespace.RootContext(nil), le); err != nil {
- t.Fatalf("err: %v", err)
- }
- }
-
- err := ts.revokeTree(namespace.RootContext(nil), &leaseEntry{})
- if err.Error() != "cannot tree-revoke blank token" {
- t.Fatal(err)
- }
-
- saltCtx := namespace.RootContext(nil)
- saltedID, err := c.tokenStore.SaltID(saltCtx, root.ID)
- if err != nil {
- t.Fatal(err)
- }
- tokenLeaseID := path.Join(root.Path, saltedID)
-
- tokenLease, err := ts.expiration.loadEntry(namespace.RootContext(nil), tokenLeaseID)
- if err != nil || tokenLease == nil {
- t.Fatalf("err: %v, tokenLease: %#v", err, tokenLease)
- }
-
- // Nuke tree non recursively.
- err = ts.revokeTree(namespace.RootContext(nil), tokenLease)
- if err != nil {
- t.Fatal(err)
- }
- // Append the root to ensure it was successfully
- // deleted.
- children = append(children, root)
- for _, entry := range children {
- out, err := ts.Lookup(namespace.RootContext(nil), entry.ID)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("bad: %#v", out)
- }
- }
-
- for _, path := range cyclePaths {
- entry, err := ts.parentView(namespace.RootNamespace).Get(namespace.RootContext(nil), path)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if entry != nil {
- t.Fatalf("expected reference to be deleted: %v", entry)
- }
- }
-}
-
-// A benchmark function that tests testTokenStore_RevokeTree_NonRecursive
-// for a variety of different depths.
-func BenchmarkTokenStore_RevokeTree(b *testing.B) {
- benchmarks := []uint64{0, 1, 2, 4, 8, 16, 20}
- for _, depth := range benchmarks {
- b.Run(fmt.Sprintf("Tree of Depth %d", depth), func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- testTokenStore_RevokeTree_NonRecursive(b, depth, false)
- }
- })
- }
-}
-
-// Builds a TokenTree of a specified depth, so that
-// we may run revoke tests on it.
-func buildTokenTree(t testing.TB, ts *TokenStore, depth uint64) (root *logical.TokenEntry, children []*logical.TokenEntry) {
- root = &logical.TokenEntry{
- NamespaceID: namespace.RootNamespaceID,
- TTL: time.Hour,
- }
- testMakeTokenDirectly(t, ts, root)
-
- frontier := []*logical.TokenEntry{root}
- current := uint64(0)
- for current < depth {
- next := make([]*logical.TokenEntry, 0, 2*len(frontier))
- for _, node := range frontier {
- left := &logical.TokenEntry{
- Parent: node.ID,
- TTL: time.Hour,
- NamespaceID: namespace.RootNamespaceID,
- }
- testMakeTokenDirectly(t, ts, left)
-
- right := &logical.TokenEntry{
- Parent: node.ID,
- TTL: time.Hour,
- NamespaceID: namespace.RootNamespaceID,
- }
- testMakeTokenDirectly(t, ts, right)
-
- children = append(children, left, right)
- next = append(next, left, right)
- }
- frontier = next
- current++
- }
-
- return root, children
-}
-
-func TestTokenStore_RevokeSelf(t *testing.T) {
- exp := mockExpiration(t)
- ts := exp.tokenStore
-
- ent1 := &logical.TokenEntry{
- TTL: time.Hour,
- NamespaceID: namespace.RootNamespaceID,
- }
- testMakeTokenDirectly(t, ts, ent1)
-
- ent2 := &logical.TokenEntry{
- Parent: ent1.ID,
- TTL: time.Hour,
- NamespaceID: namespace.RootNamespaceID,
- }
- testMakeTokenDirectly(t, ts, ent2)
-
- ent3 := &logical.TokenEntry{
- Parent: ent2.ID,
- TTL: time.Hour,
- NamespaceID: namespace.RootNamespaceID,
- }
- testMakeTokenDirectly(t, ts, ent3)
-
- ent4 := &logical.TokenEntry{
- Parent: ent2.ID,
- TTL: time.Hour,
- NamespaceID: namespace.RootNamespaceID,
- }
- testMakeTokenDirectly(t, ts, ent4)
-
- req := logical.TestRequest(t, logical.UpdateOperation, "revoke-self")
- req.ClientToken = ent1.ID
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- lookup := []string{ent1.ID, ent2.ID, ent3.ID, ent4.ID}
- var out *logical.TokenEntry
- for _, id := range lookup {
- var found bool
- for i := 0; i < 10; i++ {
- out, err = ts.Lookup(namespace.RootContext(nil), id)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out == nil {
- found = true
- break
- }
- time.Sleep(1000 * time.Millisecond)
- }
- if !found {
- t.Fatalf("bad: %#v", out)
- }
- }
-}
-
-func TestTokenStore_HandleRequest_NonAssignable(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data["policies"] = []string{"default", "foo"}
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- req.Data["policies"] = []string{"default", "foo", responseWrappingPolicyName}
-
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("got a nil response")
- }
- if !resp.IsError() {
- t.Fatalf("expected error; response is %#v", *resp)
- }
-
- // Batch tokens too
- req.Data["type"] = "batch"
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("got a nil response")
- }
- if !resp.IsError() {
- t.Fatalf("expected error; response is %#v", *resp)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_DisplayName(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data["display_name"] = "foo_bar.baz!"
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- expected := &logical.TokenEntry{
- ID: resp.Auth.ClientToken,
- NamespaceID: namespace.RootNamespaceID,
- Accessor: resp.Auth.Accessor,
- Parent: root,
- Policies: []string{"root"},
- Path: "auth/token/create",
- DisplayName: "token-foo-bar-baz",
- TTL: 0,
- Type: logical.TokenTypeService,
- }
- out, err := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- expected.CreationTime = out.CreationTime
- expected.CubbyholeID = out.CubbyholeID
- if !reflect.DeepEqual(out, expected) {
- t.Fatalf("bad: expected:%#v\nactual:%#v", expected, out)
- }
-}
-
-func deepEqualTokenEntries(t *testing.T, a *logical.TokenEntry, b *logical.TokenEntry) {
- if diff := cmp.Diff(a, b, cmpopts.IgnoreFields(logical.TokenEntry{}, "ExternalID")); diff != "" {
- t.Fatalf("bad diff in token entries: %s", diff)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_NumUses(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data["num_uses"] = "1"
-
- // Make sure batch tokens can't do limited use counts
- req.Data["type"] = "batch"
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("error handling request: %v", err)
- }
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected response error: resp: %#v ", resp)
- }
-
- delete(req.Data, "type")
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- expected := &logical.TokenEntry{
- ID: resp.Auth.ClientToken,
- NamespaceID: namespace.RootNamespaceID,
- Accessor: resp.Auth.Accessor,
- Parent: root,
- Policies: []string{"root"},
- Path: "auth/token/create",
- DisplayName: "token",
- NumUses: 1,
- TTL: 0,
- Type: logical.TokenTypeService,
- }
- out, err := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- expected.CreationTime = out.CreationTime
- expected.CubbyholeID = out.CubbyholeID
- if !reflect.DeepEqual(out, expected) {
- t.Fatalf("bad: expected:%#v\nactual:%#v", expected, out)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_NumUses_Invalid(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data["num_uses"] = "-1"
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v resp: %#v", err, resp)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_NumUses_Restricted(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data["num_uses"] = "1"
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- // We should NOT be able to use the restricted token to create a new token
- req.ClientToken = resp.Auth.ClientToken
- _, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v resp: %#v", err, resp)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_NoPolicy(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
-
- // Make sure batch tokens won't automatically assign root
- req.Data["type"] = "batch"
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("error handling request: %v", err)
- }
- if resp == nil || !resp.IsError() {
- t.Fatalf("expected response error: resp: %#v", resp)
- }
-
- delete(req.Data, "type")
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- expected := &logical.TokenEntry{
- ID: resp.Auth.ClientToken,
- NamespaceID: namespace.RootNamespaceID,
- Accessor: resp.Auth.Accessor,
- Parent: root,
- Policies: []string{"root"},
- Path: "auth/token/create",
- DisplayName: "token",
- TTL: 0,
- Type: logical.TokenTypeService,
- }
- out, err := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- expected.CreationTime = out.CreationTime
- expected.CubbyholeID = out.CubbyholeID
- if !reflect.DeepEqual(out, expected) {
- t.Fatalf("bad: expected:%#v\nactual:%#v", expected, out)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_BadParent(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = "random"
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v resp: %#v", err, resp)
- }
- if resp.Data["error"] != "parent token lookup failed: no parent found" {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data["policies"] = []string{"foo"}
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_RootID(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data["id"] = "foobar"
- req.Data["policies"] = []string{"foo"}
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp.Auth.ClientToken != "foobar" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Retry with batch; batch should not actually accept a custom ID
- req.Data["type"] = "batch"
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- out, _ := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
- if out.ID == "foobar" {
- t.Fatalf("bad: %#v", out)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_NonRootID(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
- testMakeServiceTokenViaBackend(t, ts, root, "client", "", []string{"foo"})
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = "client"
- req.Data["id"] = "foobar"
- req.Data["policies"] = []string{"foo"}
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v resp: %#v", err, resp)
- }
- if resp.Data["error"] != "root or sudo privileges required to specify token id" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Retry with batch
- req.Data["type"] = "batch"
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v resp: %#v", err, resp)
- }
- if resp.Data["error"] != "root or sudo privileges required to specify token id" {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_NonRoot_Subset(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
- testMakeServiceTokenViaBackend(t, ts, root, "client", "", []string{"foo", "bar"})
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = "client"
- req.Data["policies"] = []string{"foo"}
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- ent := &logical.TokenEntry{
- NamespaceID: namespace.RootNamespaceID,
- Path: "test",
- Policies: []string{"foo", "bar"},
- TTL: time.Hour,
- }
- testMakeTokenDirectly(t, ts, ent)
- req.ClientToken = ent.ID
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_NonRoot_InvalidSubset(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
- testMakeServiceTokenViaBackend(t, ts, root, "client", "", []string{"foo", "bar"})
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = "client"
- req.Data["policies"] = []string{"foo", "bar", "baz"}
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v resp: %#v", err, resp)
- }
- if resp.Data["error"] != "child policies must be subset of parent" {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_NonRoot_RootChild(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
- ts := core.tokenStore
- ps := core.policyStore
-
- policy, _ := ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
- policy.Name = "test1"
- if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
- t.Fatal(err)
- }
-
- testMakeServiceTokenViaBackend(t, ts, root, "sudoClient", "", []string{"test1"})
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = "sudoClient"
- req.MountPoint = "auth/token/"
- req.Data["policies"] = []string{"root"}
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v; resp: %#v", err, resp)
- }
- if resp == nil || resp.Data == nil {
- t.Fatalf("expected a response")
- }
- if resp.Data["error"].(string) != "root tokens may not be created without parent token being root" {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_Root_RootChild_NoExpiry_Expiry(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "ttl": "5m",
- "policies": "root",
- }
- resp := testMakeTokenViaRequest(t, ts, req)
-
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"root"}) {
- t.Fatalf("bad: policies: expected: root; actual: %s", resp.Auth.Policies)
- }
- if resp.Auth.TTL.Seconds() != 300 {
- t.Fatalf("bad: expected 300 second ttl, got %v", resp.Auth.TTL.Seconds())
- }
-
- req.ClientToken = resp.Auth.ClientToken
- req.Data = map[string]interface{}{
- "ttl": "0",
- }
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatalf("expected error")
- }
- expectedErr := "expiring root tokens cannot create non-expiring root tokens"
- if resp.Data["error"].(string) != expectedErr {
- t.Fatalf("got err=%v, expected=%v", resp.Data["error"], expectedErr)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_Root_RootChild(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v; resp: %#v", err, resp)
- }
- if resp == nil || resp.Auth == nil {
- t.Fatalf("failed to create a root token using another root token")
- }
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"root"}) {
- t.Fatalf("bad: policies: expected: root; actual: %s", resp.Auth.Policies)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_NonRoot_NoParent(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
- testMakeServiceTokenViaBackend(t, ts, root, "client", "", []string{"foo"})
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = "client"
- req.Data["no_parent"] = true
- req.Data["policies"] = []string{"foo"}
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("err: %v resp: %#v", err, resp)
- }
- if resp.Data["error"] != "root or sudo privileges required to create orphan token" {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_Root_NoParent(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data["no_parent"] = true
- req.Data["policies"] = []string{"foo"}
-
- resp := testMakeTokenViaRequest(t, ts, req)
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- out, _ := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
- if out.Parent != "" {
- t.Fatalf("bad: %#v", out)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_PathBased_NoParent(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create-orphan")
- req.ClientToken = root
- req.Data["policies"] = []string{"foo"}
-
- resp := testMakeTokenViaRequest(t, ts, req)
-
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- out, _ := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
- if out.Parent != "" {
- t.Fatalf("bad: %#v", out)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_Metadata(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data["policies"] = []string{"foo"}
- meta := map[string]string{
- "user": "armon",
- "source": "github",
- }
- req.Data["meta"] = meta
-
- resp := testMakeTokenViaRequest(t, ts, req)
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- out, _ := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
- if !reflect.DeepEqual(out.Meta, meta) {
- t.Fatalf("bad: expected:%#v\nactual:%#v", meta, out.Meta)
- }
-
- // Test with batch tokens
- req.Data["type"] = "batch"
- resp = testMakeTokenViaRequest(t, ts, req)
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- out, _ = ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
- if !reflect.DeepEqual(out.Meta, meta) {
- t.Fatalf("bad: expected:%#v\nactual:%#v", meta, out.Meta)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_Lease(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data["policies"] = []string{"foo"}
- req.Data["lease"] = "1h"
-
- resp := testMakeTokenViaRequest(t, ts, req)
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
- if resp.Auth.TTL != time.Hour {
- t.Fatalf("bad: %#v", resp)
- }
- if !resp.Auth.Renewable {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_TTL(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data["policies"] = []string{"foo"}
- req.Data["ttl"] = "1h"
-
- resp := testMakeTokenViaRequest(t, ts, req)
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
- if resp.Auth.TTL != time.Hour {
- t.Fatalf("bad: %#v", resp)
- }
- if !resp.Auth.Renewable {
- t.Fatalf("bad: %#v", resp)
- }
-
- // Test batch tokens
- req.Data["type"] = "batch"
- resp = testMakeTokenViaRequest(t, ts, req)
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
- if resp.Auth.TTL != time.Hour {
- t.Fatalf("bad: %#v", resp)
- }
- if resp.Auth.Renewable {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_Metric(t *testing.T) {
- c, _, root, sink := TestCoreUnsealedWithMetrics(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data["ttl"] = "3h"
- req.Data["policies"] = []string{"foo"}
- req.MountPoint = "test/mount"
-
- resp := testMakeTokenViaRequest(t, ts, req)
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- expectSingleCount(t, sink, "token.creation")
-}
-
-func TestTokenStore_HandleRequest_Revoke(t *testing.T) {
- exp := mockExpiration(t)
- ts := exp.tokenStore
-
- rootToken, err := ts.rootToken(namespace.RootContext(nil))
- if err != nil {
- t.Fatal(err)
- }
- root := rootToken.ID
-
- testMakeServiceTokenViaBackend(t, ts, root, "child", "60s", []string{"root", "foo"})
-
- te, err := ts.Lookup(namespace.RootContext(nil), "child")
- if err != nil {
- t.Fatal(err)
- }
- if te == nil {
- t.Fatal("token entry was nil")
- }
-
- auth := &logical.Auth{
- ClientToken: "child",
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- Renewable: true,
- },
- }
- err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- testMakeServiceTokenViaBackend(t, ts, "child", "sub-child", "50s", []string{"foo"})
-
- te, err = ts.Lookup(namespace.RootContext(nil), "sub-child")
- if err != nil {
- t.Fatal(err)
- }
- if te == nil {
- t.Fatal("token entry was nil")
- }
-
- auth = &logical.Auth{
- ClientToken: "sub-child",
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- Renewable: true,
- },
- }
- err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- req := logical.TestRequest(t, logical.UpdateOperation, "revoke")
- req.Data = map[string]interface{}{
- "token": "child",
- }
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- time.Sleep(200 * time.Millisecond)
-
- out, err := ts.Lookup(namespace.RootContext(nil), "child")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("bad: %#v", out)
- }
-
- // Sub-child should not exist
- out, err = ts.Lookup(namespace.RootContext(nil), "sub-child")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("bad: %v", out)
- }
-
- // Now test without registering the tokens through the expiration manager
- testMakeServiceTokenViaBackend(t, ts, root, "child", "60s", []string{"root", "foo"})
- testMakeServiceTokenViaBackend(t, ts, "child", "sub-child", "50s", []string{"foo"})
-
- req = logical.TestRequest(t, logical.UpdateOperation, "revoke")
- req.Data = map[string]interface{}{
- "token": "child",
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- time.Sleep(200 * time.Millisecond)
-
- out, err = ts.Lookup(namespace.RootContext(nil), "child")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("bad: %#v", out)
- }
-
- // Sub-child should not exist
- out, err = ts.Lookup(namespace.RootContext(nil), "sub-child")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("bad: %v", out)
- }
-}
-
-func TestTokenStore_HandleRequest_RevokeOrphan(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
- testMakeServiceTokenViaBackend(t, ts, root, "child", "60s", []string{"root", "foo"})
- testMakeServiceTokenViaBackend(t, ts, "child", "sub-child", "50s", []string{"foo"})
-
- req := logical.TestRequest(t, logical.UpdateOperation, "revoke-orphan")
- req.Data = map[string]interface{}{
- "token": "child",
- }
- req.ClientToken = root
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- time.Sleep(200 * time.Millisecond)
-
- out, err := ts.Lookup(namespace.RootContext(nil), "child")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out != nil {
- t.Fatalf("bad: %v", out)
- }
-
- // Check that the parent entry is properly cleaned up
- saltedID, err := ts.SaltID(namespace.RootContext(nil), "child")
- if err != nil {
- t.Fatal(err)
- }
- children, err := ts.idView(namespace.RootNamespace).List(namespace.RootContext(nil), parentPrefix+saltedID+"/")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(children) != 0 {
- t.Fatalf("bad: %v", children)
- }
-
- // Sub-child should exist!
- out, err = ts.Lookup(namespace.RootContext(nil), "sub-child")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out == nil {
- t.Fatalf("bad: %v", out)
- }
-}
-
-func TestTokenStore_HandleRequest_RevokeOrphan_NonRoot(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
- testMakeServiceTokenViaBackend(t, ts, root, "child", "60s", []string{"foo"})
-
- out, err := ts.Lookup(namespace.RootContext(nil), "child")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out == nil {
- t.Fatalf("bad: %v", out)
- }
-
- req := logical.TestRequest(t, logical.UpdateOperation, "revoke-orphan")
- req.Data = map[string]interface{}{
- "token": "child",
- }
- req.ClientToken = "child"
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != logical.ErrInvalidRequest {
- t.Fatalf("did not get error when non-root revoking itself with orphan flag; resp is %#v", resp)
- }
-
- time.Sleep(200 * time.Millisecond)
-
- // Should still exist
- out, err = ts.Lookup(namespace.RootContext(nil), "child")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if out == nil {
- t.Fatalf("bad: %v", out)
- }
-}
-
-func TestTokenStore_HandleRequest_Lookup(t *testing.T) {
- t.Run("service_token", func(t *testing.T) {
- testTokenStoreHandleRequestLookup(t, false, false)
- })
-
- t.Run("service_token_periodic", func(t *testing.T) {
- testTokenStoreHandleRequestLookup(t, false, true)
- })
-
- t.Run("batch_token", func(t *testing.T) {
- testTokenStoreHandleRequestLookup(t, true, false)
- })
-}
-
-func testTokenStoreHandleRequestLookup(t *testing.T, batch, periodic bool) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
- req := logical.TestRequest(t, logical.UpdateOperation, "lookup")
- req.Data = map[string]interface{}{
- "token": root,
- }
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp == nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- internalRoot, _ := c.DecodeSSCToken(root)
-
- exp := map[string]interface{}{
- "id": internalRoot,
- "accessor": resp.Data["accessor"].(string),
- "policies": []string{"root"},
- "path": "auth/token/root",
- "meta": map[string]string(nil),
- "display_name": "root",
- "orphan": true,
- "num_uses": 0,
- "creation_ttl": int64(0),
- "ttl": int64(0),
- "explicit_max_ttl": int64(0),
- "expire_time": nil,
- "entity_id": "",
- "type": "service",
- }
-
- if resp.Data["creation_time"].(int64) == 0 {
- t.Fatalf("creation time was zero")
- }
- delete(resp.Data, "creation_time")
-
- if !reflect.DeepEqual(resp.Data, exp) {
- t.Fatalf("bad: expected:%#v\nactual:%#v", exp, resp.Data)
- }
-
- period := ""
- if periodic {
- period = "3600s"
- }
-
- outAuth := new(logical.Auth)
- testMakeTokenViaCore(t, c, root, "client", "3600s", period, []string{"foo"}, batch, outAuth)
-
- tokenType := "service"
- expID := "client"
- if batch {
- tokenType = "batch"
- expID = outAuth.ClientToken
- }
-
- // Test via POST
- req = logical.TestRequest(t, logical.UpdateOperation, "lookup")
- req.Data = map[string]interface{}{
- "token": expID,
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp == nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- exp = map[string]interface{}{
- "id": expID,
- "accessor": resp.Data["accessor"],
- "policies": []string{"default", "foo"},
- "path": "auth/token/create",
- "meta": map[string]string(nil),
- "display_name": "token",
- "orphan": false,
- "num_uses": 0,
- "creation_ttl": int64(3600),
- "ttl": int64(3600),
- "explicit_max_ttl": int64(0),
- "renewable": !batch,
- "entity_id": "",
- "type": tokenType,
- }
- if periodic {
- exp["period"] = int64(3600)
- }
-
- if resp.Data["creation_time"].(int64) == 0 {
- t.Fatalf("creation time was zero")
- }
- delete(resp.Data, "creation_time")
- if resp.Data["issue_time"].(time.Time).IsZero() {
- t.Fatal("issue time is default time")
- }
- delete(resp.Data, "issue_time")
- if resp.Data["expire_time"].(time.Time).IsZero() {
- t.Fatal("expire time is default time")
- }
- delete(resp.Data, "expire_time")
-
- // Depending on timing of the test this may have ticked down, so accept 3599
- if resp.Data["ttl"].(int64) == 3599 {
- resp.Data["ttl"] = int64(3600)
- }
-
- if diff := deep.Equal(resp.Data, exp); diff != nil {
- t.Fatal(diff)
- }
-
- // Test last_renewal_time functionality
- req = logical.TestRequest(t, logical.UpdateOperation, "renew")
- req.Data = map[string]interface{}{
- "token": expID,
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatalf("bad: %#v", resp)
- }
- if batch && !resp.IsError() || !batch && resp.IsError() {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- req.Path = "lookup"
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp == nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- if !batch {
- if resp.Data["last_renewal_time"].(int64) == 0 {
- t.Fatalf("last_renewal_time was zero")
- }
- } else if _, ok := resp.Data["last_renewal_time"]; ok {
- t.Fatal("expected zero last renewal time")
- }
-}
-
-func TestTokenStore_HandleRequest_LookupSelf(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
- testMakeServiceTokenViaCore(t, c, root, "client", "3600s", []string{"foo"})
-
- req := logical.TestRequest(t, logical.ReadOperation, "lookup-self")
- req.ClientToken = "client"
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp == nil {
- t.Fatalf("bad: %#v", resp)
- }
-
- exp := map[string]interface{}{
- "id": "client",
- "accessor": resp.Data["accessor"],
- "policies": []string{"default", "foo"},
- "path": "auth/token/create",
- "meta": map[string]string(nil),
- "display_name": "token",
- "orphan": false,
- "renewable": true,
- "num_uses": 0,
- "creation_ttl": int64(3600),
- "ttl": int64(3600),
- "explicit_max_ttl": int64(0),
- "entity_id": "",
- "type": "service",
- }
-
- if resp.Data["creation_time"].(int64) == 0 {
- t.Fatalf("creation time was zero")
- }
- delete(resp.Data, "creation_time")
- if resp.Data["issue_time"].(time.Time).IsZero() {
- t.Fatalf("creation time was zero")
- }
- delete(resp.Data, "issue_time")
- if resp.Data["expire_time"].(time.Time).IsZero() {
- t.Fatalf("expire time was zero")
- }
- delete(resp.Data, "expire_time")
-
- // Depending on timing of the test this may have ticked down, so accept 3599
- if resp.Data["ttl"].(int64) == 3599 {
- resp.Data["ttl"] = int64(3600)
- }
-
- if !reflect.DeepEqual(resp.Data, exp) {
- t.Fatalf("bad: expected:%#v\nactual:%#v", exp, resp.Data)
- }
-}
-
-func TestTokenStore_HandleRequest_Renew(t *testing.T) {
- exp := mockExpiration(t)
- ts := exp.tokenStore
-
- // Create new token
- root, err := ts.rootToken(namespace.RootContext(nil))
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Create a new token
- auth := &logical.Auth{
- ClientToken: root.ID,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- Renewable: true,
- },
- }
- err = exp.RegisterAuth(namespace.RootContext(nil), root, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Get the original expire time to compare
- originalExpire := auth.ExpirationTime()
-
- beforeRenew := time.Now()
- req := logical.TestRequest(t, logical.UpdateOperation, "renew")
- req.Data = map[string]interface{}{
- "token": root.ID,
- "increment": "3600s",
- }
-
- req.Data["increment"] = "3600s"
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- // Get the new expire time
- newExpire := resp.Auth.ExpirationTime()
- if newExpire.Before(originalExpire) {
- t.Fatalf("should expire later: %s %s", newExpire, originalExpire)
- }
- if newExpire.Before(beforeRenew.Add(time.Hour)) {
- t.Fatalf("should have at least an hour: %s %s", newExpire, beforeRenew)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_ExistingEntityAlias(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
- i := core.identityStore
- ctx := namespace.RootContext(nil)
- testPolicyName := "testpolicy"
- entityAliasName := "testentityalias"
- testRoleName := "test"
-
- // Create manually an entity
- resp, err := i.HandleRequest(ctx, &logical.Request{
- Path: "entity",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testentity",
- "policies": []string{testPolicyName},
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- entityID := resp.Data["id"].(string)
-
- // Find mount accessor
- resp, err = core.systemBackend.HandleRequest(namespace.RootContext(nil), &logical.Request{
- Path: "auth",
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- tokenMountAccessor := resp.Data["token/"].(map[string]interface{})["accessor"].(string)
-
- // Create manually an entity alias
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity-alias",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": entityAliasName,
- "canonical_id": entityID,
- "mount_accessor": tokenMountAccessor,
- },
- })
- if err != nil {
- t.Fatalf("error handling request: %v", err)
- }
-
- // Create token role
- resp, err = core.HandleRequest(ctx, &logical.Request{
- Path: "auth/token/roles/" + testRoleName,
- ClientToken: root,
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "orphan": true,
- "period": "72h",
- "path_suffix": "happenin",
- "bound_cidrs": []string{"0.0.0.0/0"},
- "allowed_entity_aliases": []string{"test1", "test2", entityAliasName},
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- resp, err = core.HandleRequest(ctx, &logical.Request{
- Path: "auth/token/create/" + testRoleName,
- Operation: logical.UpdateOperation,
- ClientToken: root,
- Data: map[string]interface{}{
- "entity_alias": entityAliasName,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp == nil {
- t.Fatal("expected a response")
- }
- if resp.Auth.EntityID != entityID {
- t.Fatalf("expected %q got %q", entityID, resp.Auth.EntityID)
- }
-
- policyFound := false
- for _, policy := range resp.Auth.IdentityPolicies {
- if policy == testPolicyName {
- policyFound = true
- }
- }
- if !policyFound {
- t.Fatalf("Policy %q not derived by entity but should be. Auth %#v", testPolicyName, resp.Auth)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_ExistingEntityAliasMixedCase(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
- i := core.identityStore
- ctx := namespace.RootContext(nil)
- testPolicyName := "testpolicy"
- entityAliasName := "tEStEntityAliaS"
- entityAliasNameLower := "testentityalias"
- testRoleName := "test"
-
- // Create manually an entity
- resp, err := i.HandleRequest(ctx, &logical.Request{
- Path: "entity",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testentity",
- "policies": []string{testPolicyName},
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- entityID := resp.Data["id"].(string)
-
- // Find mount accessor
- resp, err = core.systemBackend.HandleRequest(namespace.RootContext(nil), &logical.Request{
- Path: "auth",
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- tokenMountAccessor := resp.Data["token/"].(map[string]interface{})["accessor"].(string)
-
- // Create manually an entity alias
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity-alias",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": entityAliasName,
- "canonical_id": entityID,
- "mount_accessor": tokenMountAccessor,
- },
- })
- if err != nil {
- t.Fatalf("error handling request: %v", err)
- }
-
- // Create token role
- resp, err = core.HandleRequest(ctx, &logical.Request{
- Path: "auth/token/roles/" + testRoleName,
- ClientToken: root,
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "orphan": true,
- "period": "72h",
- "path_suffix": "happenin",
- "bound_cidrs": []string{"0.0.0.0/0"},
- "allowed_entity_aliases": []string{"test1", "test2", entityAliasName},
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- respMixedCase, err := core.HandleRequest(ctx, &logical.Request{
- Path: "auth/token/create/" + testRoleName,
- Operation: logical.UpdateOperation,
- ClientToken: root,
- Data: map[string]interface{}{
- "entity_alias": entityAliasName,
- },
- })
- if err != nil {
- t.Fatalf("error handling request: %v", err)
- }
- if respMixedCase.Auth.EntityID != entityID {
- t.Fatalf("expected %q got %q", entityID, respMixedCase.Auth.EntityID)
- }
-
- // lowercase entity alias should match a mixed case alias
- respLowerCase, err := core.HandleRequest(ctx, &logical.Request{
- Path: "auth/token/create/" + testRoleName,
- Operation: logical.UpdateOperation,
- ClientToken: root,
- Data: map[string]interface{}{
- "entity_alias": entityAliasNameLower,
- },
- })
- if err != nil {
- t.Fatalf("error handling request: %v", err)
- }
-
- // A token created with the mixed case alias should return the same entity
- // id as the normal case response.
- if respLowerCase.Auth.EntityID != entityID {
- t.Fatalf("expected %q got %q", entityID, respLowerCase.Auth.EntityID)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_NonExistingEntityAlias(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
- i := core.identityStore
- ctx := namespace.RootContext(nil)
- entityAliasName := "testentityalias"
- testRoleName := "test"
-
- // Create token role
- resp, err := core.HandleRequest(ctx, &logical.Request{
- Path: "auth/token/roles/" + testRoleName,
- ClientToken: root,
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "period": "72h",
- "path_suffix": "happenin",
- "bound_cidrs": []string{"0.0.0.0/0"},
- "allowed_entity_aliases": []string{"test1", "test2", entityAliasName},
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- // Create token with non existing entity alias
- resp, err = core.HandleRequest(ctx, &logical.Request{
- Path: "auth/token/create/" + testRoleName,
- Operation: logical.UpdateOperation,
- ClientToken: root,
- Data: map[string]interface{}{
- "entity_alias": entityAliasName,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp == nil {
- t.Fatal("expected a response")
- }
-
- // Read the new entity
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity/id/" + resp.Auth.EntityID,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- // Get the attached alias information
- aliases := resp.Data["aliases"].([]interface{})
- if len(aliases) != 1 {
- t.Fatalf("expected only one alias but got %d; Aliases: %#v", len(aliases), aliases)
- }
- alias := &identity.Alias{}
- if err := mapstructure.Decode(aliases[0], alias); err != nil {
- t.Fatal(err)
- }
-
- // Validate
- if alias.Name != entityAliasName {
- t.Fatalf("alias name should be %q but is %q", entityAliasName, alias.Name)
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_GlobPatternWildcardEntityAlias(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
- i := core.identityStore
- ctx := namespace.RootContext(nil)
- testRoleName := "test"
-
- tests := []struct {
- name string
- globPattern string
- aliasName string
- }{
- {
- name: "prefix-asterisk",
- globPattern: "*-web",
- aliasName: "department-web",
- },
- {
- name: "suffix-asterisk",
- globPattern: "web-*",
- aliasName: "web-department",
- },
- {
- name: "middle-asterisk",
- globPattern: "web-*-web",
- aliasName: "web-department-web",
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- // Create token role
- resp, err := core.HandleRequest(ctx, &logical.Request{
- Path: "auth/token/roles/" + testRoleName,
- ClientToken: root,
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "period": "72h",
- "path_suffix": "happening",
- "bound_cidrs": []string{"0.0.0.0/0"},
- "allowed_entity_aliases": []string{"test1", "test2", test.globPattern},
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- // Create token with non existing entity alias
- resp, err = core.HandleRequest(ctx, &logical.Request{
- Path: "auth/token/create/" + testRoleName,
- Operation: logical.UpdateOperation,
- ClientToken: root,
- Data: map[string]interface{}{
- "entity_alias": test.aliasName,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if resp == nil {
- t.Fatal("expected a response")
- }
-
- // Read the new entity
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity/id/" + resp.Auth.EntityID,
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- // Get the attached alias information
- aliases := resp.Data["aliases"].([]interface{})
- if len(aliases) != 1 {
- t.Fatalf("expected only one alias but got %d; Aliases: %#v", len(aliases), aliases)
- }
- alias := &identity.Alias{}
- if err := mapstructure.Decode(aliases[0], alias); err != nil {
- t.Fatal(err)
- }
-
- // Validate
- if alias.Name != test.aliasName {
- t.Fatalf("alias name should be %q but is %q", test.aliasName, alias.Name)
- }
- })
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_NotAllowedEntityAlias(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
- i := core.identityStore
- ctx := namespace.RootContext(nil)
- testPolicyName := "testpolicy"
- entityAliasName := "testentityalias"
- testRoleName := "test"
-
- // Create manually an entity
- resp, err := i.HandleRequest(ctx, &logical.Request{
- Path: "entity",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": "testentity",
- "policies": []string{testPolicyName},
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- entityID := resp.Data["id"].(string)
-
- // Find mount accessor
- resp, err = core.systemBackend.HandleRequest(ctx, &logical.Request{
- Path: "auth",
- Operation: logical.ReadOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- tokenMountAccessor := resp.Data["token/"].(map[string]interface{})["accessor"].(string)
-
- // Create manually an entity alias
- resp, err = i.HandleRequest(ctx, &logical.Request{
- Path: "entity-alias",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "name": entityAliasName,
- "canonical_id": entityID,
- "mount_accessor": tokenMountAccessor,
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- // Create token role
- resp, err = core.HandleRequest(ctx, &logical.Request{
- Path: "auth/token/roles/" + testRoleName,
- ClientToken: root,
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "period": "72h",
- "allowed_entity_aliases": []string{"test1", "test2", "testentityaliasn"},
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- resp, _ = core.HandleRequest(ctx, &logical.Request{
- Path: "auth/token/create/" + testRoleName,
- Operation: logical.UpdateOperation,
- ClientToken: root,
- Data: map[string]interface{}{
- "entity_alias": entityAliasName,
- },
- })
- if resp == nil || resp.Data == nil {
- t.Fatal("expected a response")
- }
- if resp.Data["error"] != "invalid 'entity_alias' value" {
- t.Fatalf("wrong error returned. Err: %s", resp.Data["error"])
- }
-}
-
-func TestTokenStore_HandleRequest_CreateToken_NoRoleEntityAlias(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
- ctx := namespace.RootContext(nil)
- entityAliasName := "testentityalias"
-
- resp, _ := core.HandleRequest(ctx, &logical.Request{
- Path: "auth/token/create",
- Operation: logical.UpdateOperation,
- ClientToken: root,
- Data: map[string]interface{}{
- "entity_alias": entityAliasName,
- },
- })
- if resp == nil || resp.Data == nil {
- t.Fatal("expected a response")
- }
- if resp.Data["error"] != "'entity_alias' is only allowed in combination with token role" {
- t.Fatalf("wrong error returned. Err: %s", resp.Data["error"])
- }
-}
-
-func TestTokenStore_HandleRequest_RenewSelf(t *testing.T) {
- exp := mockExpiration(t)
- ts := exp.tokenStore
-
- // Create new token
- root, err := ts.rootToken(namespace.RootContext(nil))
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Create a new token
- auth := &logical.Auth{
- ClientToken: root.ID,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- Renewable: true,
- },
- }
- err = exp.RegisterAuth(namespace.RootContext(nil), root, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Get the original expire time to compare
- originalExpire := auth.ExpirationTime()
-
- beforeRenew := time.Now()
- req := logical.TestRequest(t, logical.UpdateOperation, "renew-self")
- req.ClientToken = auth.ClientToken
- req.Data["increment"] = "3600s"
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- // Get the new expire time
- newExpire := resp.Auth.ExpirationTime()
- if newExpire.Before(originalExpire) {
- t.Fatalf("should expire later: %s %s", newExpire, originalExpire)
- }
- if newExpire.Before(beforeRenew.Add(time.Hour)) {
- t.Fatalf("should have at least an hour: %s %s", newExpire, beforeRenew)
- }
-}
-
-func TestTokenStore_RoleCRUD(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
-
- req := logical.TestRequest(t, logical.ReadOperation, "auth/token/roles/test")
- req.ClientToken = root
-
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("should not see a role")
- }
-
- // First test creation
- req.Operation = logical.CreateOperation
- req.Data = map[string]interface{}{
- "orphan": true,
- "period": "72h",
- "allowed_policies": "test1,test2",
- "path_suffix": "happenin",
- "bound_cidrs": []string{"0.0.0.0/0"},
- "explicit_max_ttl": "2h",
- "token_num_uses": 123,
- }
-
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- req.Operation = logical.ReadOperation
- req.Data = map[string]interface{}{}
-
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- if resp == nil {
- t.Fatalf("got a nil response")
- }
-
- expected := map[string]interface{}{
- "name": "test",
- "orphan": true,
- "token_period": int64(259200),
- "period": int64(259200),
- "allowed_policies": []string{"test1", "test2"},
- "disallowed_policies": []string{},
- "allowed_policies_glob": []string{},
- "disallowed_policies_glob": []string{},
- "path_suffix": "happenin",
- "explicit_max_ttl": int64(7200),
- "token_explicit_max_ttl": int64(7200),
- "renewable": true,
- "token_type": "default-service",
- "token_num_uses": 123,
- "allowed_entity_aliases": []string(nil),
- "token_no_default_policy": false,
- }
-
- if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" {
- t.Fatal("unexpected bound cidrs")
- }
- delete(resp.Data, "bound_cidrs")
- if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" {
- t.Fatal("unexpected bound cidrs")
- }
- delete(resp.Data, "token_bound_cidrs")
-
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Now test updating; this should be set to an UpdateOperation
- // automatically due to the existence check
- req.Operation = logical.CreateOperation
- req.Data = map[string]interface{}{
- "period": "79h",
- "allowed_policies": "test3",
- "path_suffix": "happenin",
- "renewable": false,
- "explicit_max_ttl": "80h",
- "token_num_uses": 0,
- "token_no_default_policy": true,
- }
-
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- req.Operation = logical.ReadOperation
- req.Data = map[string]interface{}{}
-
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp == nil {
- t.Fatalf("got a nil response")
- }
-
- expected = map[string]interface{}{
- "name": "test",
- "orphan": true,
- "period": int64(284400),
- "token_period": int64(284400),
- "allowed_policies": []string{"test3"},
- "disallowed_policies": []string{},
- "allowed_policies_glob": []string{},
- "disallowed_policies_glob": []string{},
- "path_suffix": "happenin",
- "token_explicit_max_ttl": int64(288000),
- "explicit_max_ttl": int64(288000),
- "renewable": false,
- "token_type": "default-service",
- "allowed_entity_aliases": []string(nil),
- "token_no_default_policy": true,
- }
-
- if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" {
- t.Fatal("unexpected bound cidrs")
- }
- delete(resp.Data, "bound_cidrs")
- if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" {
- t.Fatal("unexpected bound cidrs")
- }
- delete(resp.Data, "token_bound_cidrs")
-
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Now set explicit max ttl and clear the period
- req.Operation = logical.CreateOperation
- req.Data = map[string]interface{}{
- "explicit_max_ttl": "5",
- "period": "0s",
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- req.Operation = logical.ReadOperation
- req.Data = map[string]interface{}{}
-
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp == nil {
- t.Fatalf("got a nil response")
- }
-
- expected = map[string]interface{}{
- "name": "test",
- "orphan": true,
- "explicit_max_ttl": int64(5),
- "token_explicit_max_ttl": int64(5),
- "allowed_policies": []string{"test3"},
- "disallowed_policies": []string{},
- "allowed_policies_glob": []string{},
- "disallowed_policies_glob": []string{},
- "path_suffix": "happenin",
- "period": int64(0),
- "token_period": int64(0),
- "renewable": false,
- "token_type": "default-service",
- "allowed_entity_aliases": []string(nil),
- "token_no_default_policy": true,
- }
-
- if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" {
- t.Fatal("unexpected bound cidrs")
- }
- delete(resp.Data, "bound_cidrs")
- if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" {
- t.Fatal("unexpected bound cidrs")
- }
- delete(resp.Data, "token_bound_cidrs")
-
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- // Update path_suffix and bound_cidrs with empty values
- req.Operation = logical.CreateOperation
- req.Data = map[string]interface{}{
- "path_suffix": "",
- "bound_cidrs": []string{},
- "token_no_default_policy": false,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- req.Operation = logical.ReadOperation
- req.Data = map[string]interface{}{}
-
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp == nil {
- t.Fatalf("got a nil response")
- }
-
- expected = map[string]interface{}{
- "name": "test",
- "orphan": true,
- "token_explicit_max_ttl": int64(5),
- "explicit_max_ttl": int64(5),
- "allowed_policies": []string{"test3"},
- "disallowed_policies": []string{},
- "allowed_policies_glob": []string{},
- "disallowed_policies_glob": []string{},
- "path_suffix": "",
- "period": int64(0),
- "token_period": int64(0),
- "renewable": false,
- "token_type": "default-service",
- "allowed_entity_aliases": []string(nil),
- "token_no_default_policy": false,
- }
-
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
-
- req.Operation = logical.ListOperation
- req.Path = "auth/token/roles"
- req.Data = map[string]interface{}{}
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp == nil {
- t.Fatalf("got a nil response")
- }
- keysInt, ok := resp.Data["keys"]
- if !ok {
- t.Fatalf("did not find keys in response")
- }
- keys, ok := keysInt.([]string)
- if !ok {
- t.Fatalf("could not convert keys interface to key list")
- }
- if len(keys) != 1 {
- t.Fatalf("unexpected number of keys: %d", len(keys))
- }
- if keys[0] != "test" {
- t.Fatalf("expected \"test\", got %q", keys[0])
- }
-
- req.Operation = logical.DeleteOperation
- req.Path = "auth/token/roles/test"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- req.Operation = logical.ReadOperation
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-}
-
-func TestTokenStore_RoleDisallowedPoliciesWithRoot(t *testing.T) {
- var resp *logical.Response
- var err error
-
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- // Don't set disallowed_policies. Verify that a read on the role does return a non-nil value.
- roleReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "roles/role1",
- Data: map[string]interface{}{
- "disallowed_policies": "root,testpolicy",
- },
- ClientToken: root,
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), roleReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- roleReq.Operation = logical.ReadOperation
- resp, err = ts.HandleRequest(namespace.RootContext(nil), roleReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- expected := []string{"root", "testpolicy"}
- if !reflect.DeepEqual(resp.Data["disallowed_policies"], expected) {
- t.Fatalf("bad: expected: %#v, actual: %#v", expected, resp.Data["disallowed_policies"])
- }
-}
-
-func TestTokenStore_RoleDisallowedPolicies(t *testing.T) {
- var req *logical.Request
- var resp *logical.Response
- var err error
-
- core, _, root := TestCoreUnsealed(t)
- ts := core.tokenStore
- ps := core.policyStore
-
- // Create 3 different policies
- policy, _ := ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
- policy.Name = "test1"
- if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
- t.Fatal(err)
- }
-
- policy, _ = ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
- policy.Name = "test2"
- if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
- t.Fatal(err)
- }
-
- policy, _ = ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
- policy.Name = "test3"
- if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
- t.Fatal(err)
- }
-
- // Create roles with different disallowed_policies configuration
- req = logical.TestRequest(t, logical.UpdateOperation, "roles/test1")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "disallowed_policies": "test1",
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "roles/test23")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "disallowed_policies": "test2,test3",
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "roles/test123")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "disallowed_policies": "test1,test2,test3",
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- // policy containing a glob character in the non-glob disallowed_policies field
- req = logical.TestRequest(t, logical.UpdateOperation, "roles/testglobdisabled")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "disallowed_policies": "test*",
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- // Create a token that has all the policies defined above
- req = logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data["policies"] = []string{"test1", "test2", "test3"}
- resp = testMakeTokenViaRequest(t, ts, req)
- if resp == nil || resp.Auth == nil {
- t.Fatal("got nil response")
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: ClientToken; resp:%#v", resp)
- }
- parentToken := resp.Auth.ClientToken
-
- // Test that the parent token's policies are rejected by disallowed_policies
- req = logical.TestRequest(t, logical.UpdateOperation, "create/test1")
- req.ClientToken = parentToken
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || resp != nil && !resp.IsError() {
- t.Fatalf("expected an error response, got %#v", resp)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "create/test23")
- req.ClientToken = parentToken
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || resp != nil && !resp.IsError() {
- t.Fatalf("expected an error response, got %#v", resp)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "create/test123")
- req.ClientToken = parentToken
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || resp != nil && !resp.IsError() {
- t.Fatalf("expected an error response, got %#v", resp)
- }
-
- // Disallowed should act as a blacklist so make sure we can still make
- // something with other policies in the request
- req = logical.TestRequest(t, logical.UpdateOperation, "create/test123")
- req.Data["policies"] = []string{"foo", "bar"}
- req.ClientToken = parentToken
- testMakeTokenViaRequest(t, ts, req)
-
- // Check to be sure 'test*' without globbing matches 'test*'
- req = logical.TestRequest(t, logical.UpdateOperation, "create/testglobdisabled")
- req.Data["policies"] = []string{"test*"}
- req.ClientToken = parentToken
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || resp != nil && !resp.IsError() {
- t.Fatalf("expected an error response, got %#v", resp)
- }
-
- // Check to be sure 'test*' without globbing doesn't match 'test1' or 'test'
- req = logical.TestRequest(t, logical.UpdateOperation, "create/testglobdisabled")
- req.Data["policies"] = []string{"test1", "test"}
- req.ClientToken = parentToken
- testMakeTokenViaRequest(t, ts, req)
-
- // Create a role to have 'default' policy disallowed
- req = logical.TestRequest(t, logical.UpdateOperation, "roles/default")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "disallowed_policies": "default",
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "create/default")
- req.ClientToken = parentToken
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || resp != nil && !resp.IsError() {
- t.Fatal("expected an error response")
- }
-}
-
-func TestTokenStore_RoleAllowedPolicies(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "roles/test")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "allowed_policies": "test1,test2",
- }
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- req.Data = map[string]interface{}{}
-
- req.Path = "create/test"
- req.Data["policies"] = []string{"foo"}
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatalf("expected error")
- }
-
- req.Data["policies"] = []string{"test2"}
- resp = testMakeTokenViaRequest(t, ts, req)
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // test not glob matching when using allowed_policies instead of allowed_policies_glob
- req = logical.TestRequest(t, logical.UpdateOperation, "roles/testnoglob")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "allowed_policies": "test*",
- }
-
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- req.Path = "create/testnoglob"
- req.Data["policies"] = []string{"test"}
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatalf("expected error")
- }
-
- req.Data["policies"] = []string{"testfoo"}
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatalf("expected error")
- }
-
- req.Data["policies"] = []string{"test*"}
- resp = testMakeTokenViaRequest(t, ts, req)
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // When allowed_policies is blank, should fall back to a subset of the parent policies
- req = logical.TestRequest(t, logical.UpdateOperation, "roles/test")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "allowed_policies": "",
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data["policies"] = []string{"test1", "test2", "test3"}
- resp = testMakeTokenViaRequest(t, ts, req)
- if resp == nil || resp.Auth == nil {
- t.Fatal("got nil response")
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: ClientToken; resp:%#v", resp)
- }
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "test1", "test2", "test3"}) {
- t.Fatalf("bad: %#v", resp.Auth.Policies)
- }
- parentToken := resp.Auth.ClientToken
-
- req.Data = map[string]interface{}{}
- req.ClientToken = parentToken
-
- req.Path = "create/test"
- req.Data["policies"] = []string{"foo"}
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatalf("expected error")
- }
-
- req.Data["policies"] = []string{"test2"}
- resp = testMakeTokenViaRequest(t, ts, req)
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- delete(req.Data, "policies")
- resp = testMakeTokenViaRequest(t, ts, req)
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "test1", "test2", "test3"}) {
- t.Fatalf("bad: %#v", resp.Auth.Policies)
- }
-}
-
-func TestTokenStore_RoleDisallowedPoliciesGlob(t *testing.T) {
- var req *logical.Request
- var resp *logical.Response
- var err error
-
- core, _, root := TestCoreUnsealed(t)
- ts := core.tokenStore
- ps := core.policyStore
-
- // Create 4 different policies
- policy, _ := ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
- policy.Name = "test1"
- if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
- t.Fatal(err)
- }
-
- policy, _ = ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
- policy.Name = "test2"
- if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
- t.Fatal(err)
- }
-
- policy, _ = ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
- policy.Name = "test3"
- if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
- t.Fatal(err)
- }
-
- policy, _ = ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
- policy.Name = "test3b"
- if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
- t.Fatal(err)
- }
-
- // Create roles with different disallowed_policies configuration
- req = logical.TestRequest(t, logical.UpdateOperation, "roles/test1")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "disallowed_policies_glob": "test1",
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "roles/testnot23")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "disallowed_policies_glob": "test2,test3*",
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- // Create a token that has all the policies defined above
- req = logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root
- req.Data["policies"] = []string{"test1", "test2", "test3", "test3b"}
- resp = testMakeTokenViaRequest(t, ts, req)
- if resp == nil || resp.Auth == nil {
- t.Fatal("got nil response")
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: ClientToken; resp:%#v", resp)
- }
- parentToken := resp.Auth.ClientToken
-
- // Test that the parent token's policies are rejected by disallowed_policies
- req = logical.TestRequest(t, logical.UpdateOperation, "create/test1")
- req.ClientToken = parentToken
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || resp != nil && !resp.IsError() {
- t.Fatalf("expected an error response, got %#v", resp)
- }
- req = logical.TestRequest(t, logical.UpdateOperation, "create/testnot23")
- req.ClientToken = parentToken
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || resp != nil && !resp.IsError() {
- t.Fatalf("expected an error response, got %#v", resp)
- }
-
- // Disallowed should act as a blacklist so make sure we can still make
- // something with other policies in the request
- req = logical.TestRequest(t, logical.UpdateOperation, "create/test1")
- req.Data["policies"] = []string{"foo", "bar"}
- req.ClientToken = parentToken
- testMakeTokenViaRequest(t, ts, req)
-
- // Check to be sure 'test3*' matches 'test3'
- req = logical.TestRequest(t, logical.UpdateOperation, "create/testnot23")
- req.Data["policies"] = []string{"test3"}
- req.ClientToken = parentToken
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || resp != nil && !resp.IsError() {
- t.Fatalf("expected an error response, got %#v", resp)
- }
-
- // Check to be sure 'test3*' matches 'test3b'
- req = logical.TestRequest(t, logical.UpdateOperation, "create/testnot23")
- req.Data["policies"] = []string{"test3b"}
- req.ClientToken = parentToken
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || resp != nil && !resp.IsError() {
- t.Fatalf("expected an error response, got %#v", resp)
- }
-
- // Check that non-blacklisted policies still work
- req = logical.TestRequest(t, logical.UpdateOperation, "create/testnot23")
- req.Data["policies"] = []string{"test1"}
- req.ClientToken = parentToken
- testMakeTokenViaRequest(t, ts, req)
-
- // Create a role to have 'default' policy disallowed
- req = logical.TestRequest(t, logical.UpdateOperation, "roles/default")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "disallowed_policies_glob": "default",
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- req = logical.TestRequest(t, logical.UpdateOperation, "create/default")
- req.ClientToken = parentToken
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil || resp != nil && !resp.IsError() {
- t.Fatal("expected an error response")
- }
-}
-
-func TestTokenStore_RoleAllowedPoliciesGlob(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- // test literal matching works in allowed_policies_glob
- req := logical.TestRequest(t, logical.UpdateOperation, "roles/test")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "allowed_policies_glob": "test1,test2",
- }
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- req.Data = map[string]interface{}{}
-
- req.Path = "create/test"
- req.Data["policies"] = []string{"foo"}
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatalf("expected error")
- }
-
- req.Data["policies"] = []string{"test2"}
- resp = testMakeTokenViaRequest(t, ts, req)
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- // test glob matching in allowed_policies_glob
- req = logical.TestRequest(t, logical.UpdateOperation, "roles/test")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "allowed_policies_glob": "test*",
- }
-
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- req.Path = "create/test"
- req.Data["policies"] = []string{"footest"}
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatalf("expected error")
- }
-
- req.Data["policies"] = []string{"testfoo", "test2", "test"}
- resp = testMakeTokenViaRequest(t, ts, req)
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func TestTokenStore_RoleOrphan(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.UpdateOperation, "roles/test")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "orphan": true,
- }
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- req.Path = "create/test"
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- out, err := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if out.Parent != "" {
- t.Fatalf("expected orphan token, but found a parent")
- }
-
- if !strings.HasPrefix(out.Path, "auth/token/create/test") {
- t.Fatalf("expected role in path but did not find it")
- }
-}
-
-func TestTokenStore_RolePathSuffix(t *testing.T) {
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- req := logical.TestRequest(t, logical.CreateOperation, "roles/test")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "path_suffix": "happenin",
- }
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- req.Path = "create/test"
- req.Operation = logical.UpdateOperation
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- out, err := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- if out.Path != "auth/token/create/test/happenin" {
- t.Fatalf("expected role in path but did not find it")
- }
-}
-
-func TestTokenStore_RolePeriod(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
-
- core.defaultLeaseTTL = 10 * time.Second
- core.maxLeaseTTL = 10 * time.Second
-
- // Note: these requests are sent to Core since Core handles registration
- // with the expiration manager and we need the storage to be consistent
-
- req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/roles/test")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "period": 5,
- }
-
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- // This first set of logic is to verify that a normal non-root token will
- // be given a TTL of 10 seconds, and that renewing will not cause the TTL to
- // increase since that's the configured backend max. Then we verify that
- // increment works.
- {
- req.Path = "auth/token/create"
- req.Data = map[string]interface{}{
- "policies": []string{"default"},
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- req.ClientToken = resp.Auth.ClientToken
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl := resp.Data["ttl"].(int64)
- if ttl > 10 {
- t.Fatalf("TTL too large")
- }
-
- // Let the TTL go down a bit to 8 seconds
- time.Sleep(2 * time.Second)
-
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/renew-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl = resp.Data["ttl"].(int64)
- if ttl > 8 {
- t.Fatalf("TTL too large: %d", ttl)
- }
-
- // Renewing should not have the increment increase since we've hit the
- // max
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/renew-self"
- req.Data = map[string]interface{}{
- "increment": 2,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl = resp.Data["ttl"].(int64)
- if ttl > 8 {
- t.Fatalf("TTL too large")
- }
- }
-
- // Now we create a token against the role. We should be able to renew;
- // increment should be ignored as well.
- {
- req.ClientToken = root
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/create/test"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp == nil {
- t.Fatal("response was nil")
- }
- if resp.Auth == nil {
- t.Fatalf(fmt.Sprintf("response auth was nil, resp is %#v", *resp))
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- req.ClientToken = resp.Auth.ClientToken
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl := resp.Data["ttl"].(int64)
- if ttl > 5 {
- t.Fatalf("TTL too large (expected %d, got %d", 5, ttl)
- }
-
- // Let the TTL go down a bit to 3 seconds
- time.Sleep(3 * time.Second)
-
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/renew-self"
- req.Data = map[string]interface{}{
- "increment": 2,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl = resp.Data["ttl"].(int64)
- if ttl > 5 {
- t.Fatalf("TTL too large (expected %d, got %d", 5, ttl)
- }
- }
-}
-
-func TestTokenStore_RoleExplicitMaxTTL(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
-
- core.defaultLeaseTTL = 5 * time.Second
- core.maxLeaseTTL = 5 * time.Hour
-
- // Note: these requests are sent to Core since Core handles registration
- // with the expiration manager and we need the storage to be consistent
-
- // Make sure we can't make it larger than the system/mount max; we should get a warning on role write and an error on token creation
- req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/roles/test")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "explicit_max_ttl": "100h",
- }
-
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp == nil {
- t.Fatalf("expected a warning")
- }
-
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/create/test"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("expected an error")
- }
- if len(resp.Warnings) == 0 {
- t.Fatalf("expected a warning")
- }
-
- // Reset to a good explicit max
- req = logical.TestRequest(t, logical.UpdateOperation, "auth/token/roles/test")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "explicit_max_ttl": "10s",
- }
-
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- // This first set of logic is to verify that a normal non-root token will
- // be given a TTL of 5 seconds, and that renewing will cause the TTL to
- // increase
- {
- req.Path = "auth/token/create"
- req.Data = map[string]interface{}{
- "policies": []string{"default"},
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- req.ClientToken = resp.Auth.ClientToken
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl := resp.Data["ttl"].(int64)
- if ttl > 5 {
- t.Fatalf("TTL too large")
- }
-
- // Let the TTL go down a bit to 3 seconds
- time.Sleep(2 * time.Second)
-
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/renew-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl = resp.Data["ttl"].(int64)
- if ttl < 4 {
- t.Fatalf("TTL too small after renewal")
- }
- }
-
- // Now we create a token against the role. After renew our max should still
- // be the same.
- {
- req.ClientToken = root
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/create/test"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp == nil {
- t.Fatal("response was nil")
- }
- if resp.Auth == nil {
- t.Fatalf(fmt.Sprintf("response auth was nil, resp is %#v", *resp))
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- req.ClientToken = resp.Auth.ClientToken
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl := resp.Data["ttl"].(int64)
- if ttl > 10 {
- t.Fatalf("TTL too big")
- }
- // explicit max ttl is stored in the role so not returned here
- maxTTL := resp.Data["explicit_max_ttl"].(int64)
- if maxTTL != 0 {
- t.Fatalf("expected 0 for explicit max TTL, got %d", maxTTL)
- }
-
- // Let the TTL go down a bit to ~7 seconds (8 against explicit max)
- time.Sleep(2 * time.Second)
-
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/renew-self"
- req.Data = map[string]interface{}{
- "increment": 300,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl = resp.Data["ttl"].(int64)
- if ttl > 8 {
- t.Fatalf("TTL too big: %d", ttl)
- }
-
- // Let the TTL go down a bit more to ~5 seconds (6 against explicit max)
- time.Sleep(2 * time.Second)
-
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/renew-self"
- req.Data = map[string]interface{}{
- "increment": 300,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl = resp.Data["ttl"].(int64)
- if ttl > 6 {
- t.Fatalf("TTL too big")
- }
-
- // It should expire
- time.Sleep(8 * time.Second)
-
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/renew-self"
- req.Data = map[string]interface{}{
- "increment": 300,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err == nil {
- t.Fatalf("expected error")
- }
-
- time.Sleep(2 * time.Second)
-
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if resp != nil && err == nil {
- t.Fatalf("expected error, response is %#v", *resp)
- }
- if err == nil {
- t.Fatalf("expected error")
- }
- }
-}
-
-func TestTokenStore_RoleTokenFields(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- // c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
- rootContext := namespace.RootContext(context.Background())
-
- boundCIDRs, err := parseutil.ParseAddrs([]string{"127.0.0.1/32"})
- if err != nil {
- t.Fatal(err)
- }
-
- // First test the upgrade case. Create a role with values and ensure they
- // are reflected properly on read.
- {
- roleEntry := &tsRoleEntry{
- Name: "test",
- TokenParams: tokenutil.TokenParams{
- TokenType: logical.TokenTypeBatch,
- },
- Period: time.Second,
- ExplicitMaxTTL: time.Hour,
- }
- roleEntry.BoundCIDRs = boundCIDRs
- ns := namespace.RootNamespace
- jsonEntry, err := logical.StorageEntryJSON("test", roleEntry)
- if err != nil {
- t.Fatal(err)
- }
- if err := ts.rolesView(ns).Put(rootContext, jsonEntry); err != nil {
- t.Fatal(err)
- }
- // Read it back
- roleEntry, err = ts.tokenStoreRole(rootContext, "test")
- if err != nil {
- t.Fatal(err)
- }
- expRoleEntry := &tsRoleEntry{
- Name: "test",
- TokenParams: tokenutil.TokenParams{
- TokenPeriod: time.Second,
- TokenExplicitMaxTTL: time.Hour,
- TokenBoundCIDRs: boundCIDRs,
- TokenType: logical.TokenTypeBatch,
- },
- Period: time.Second,
- ExplicitMaxTTL: time.Hour,
- BoundCIDRs: boundCIDRs,
- }
- if diff := deep.Equal(expRoleEntry, roleEntry); diff != nil {
- t.Fatal(diff)
- }
- }
-
- // Now, read that back through the API and verify we see what we expect
- {
- req := logical.TestRequest(t, logical.ReadOperation, "roles/test")
- resp, err := ts.HandleRequest(rootContext, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- expected := map[string]interface{}{
- "name": "test",
- "orphan": false,
- "period": int64(1),
- "token_period": int64(1),
- "allowed_policies": []string(nil),
- "disallowed_policies": []string(nil),
- "allowed_policies_glob": []string(nil),
- "disallowed_policies_glob": []string(nil),
- "path_suffix": "",
- "token_explicit_max_ttl": int64(3600),
- "explicit_max_ttl": int64(3600),
- "renewable": false,
- "token_type": "batch",
- "allowed_entity_aliases": []string(nil),
- "token_no_default_policy": false,
- }
-
- if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {
- t.Fatalf("unexpected bound cidrs: %s", resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String())
- }
- delete(resp.Data, "bound_cidrs")
- if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {
- t.Fatalf("unexpected token bound cidrs: %s", resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String())
- }
- delete(resp.Data, "token_bound_cidrs")
-
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
- }
-
- // Put values in just the old locations, but through the API
- {
- req := logical.TestRequest(t, logical.UpdateOperation, "roles/test")
- req.Data = map[string]interface{}{
- "explicit_max_ttl": 7200,
- "token_type": "default-service",
- "period": 5,
- "bound_cidrs": boundCIDRs[0].String(),
- }
-
- resp, err := ts.HandleRequest(rootContext, req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "roles/test")
- resp, err = ts.HandleRequest(rootContext, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- expected := map[string]interface{}{
- "name": "test",
- "orphan": false,
- "period": int64(5),
- "token_period": int64(5),
- "allowed_policies": []string(nil),
- "disallowed_policies": []string(nil),
- "allowed_policies_glob": []string(nil),
- "disallowed_policies_glob": []string(nil),
- "path_suffix": "",
- "token_explicit_max_ttl": int64(7200),
- "explicit_max_ttl": int64(7200),
- "renewable": false,
- "token_type": "default-service",
- "allowed_entity_aliases": []string(nil),
- "token_no_default_policy": false,
- }
-
- if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {
- t.Fatalf("unexpected bound cidrs: %s", resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String())
- }
- delete(resp.Data, "bound_cidrs")
- if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {
- t.Fatalf("unexpected token bound cidrs: %s", resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String())
- }
- delete(resp.Data, "token_bound_cidrs")
-
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
- }
- // Same thing for just the new locations
- {
- req := logical.TestRequest(t, logical.UpdateOperation, "roles/test")
- req.Data = map[string]interface{}{
- "token_explicit_max_ttl": 5200,
- "token_type": "default-service",
- "token_period": 7,
- "token_bound_cidrs": boundCIDRs[0].String(),
- }
-
- resp, err := ts.HandleRequest(rootContext, req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "roles/test")
- resp, err = ts.HandleRequest(rootContext, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- expected := map[string]interface{}{
- "name": "test",
- "orphan": false,
- "period": int64(0),
- "token_period": int64(7),
- "allowed_policies": []string(nil),
- "disallowed_policies": []string(nil),
- "allowed_policies_glob": []string(nil),
- "disallowed_policies_glob": []string(nil),
- "path_suffix": "",
- "token_explicit_max_ttl": int64(5200),
- "explicit_max_ttl": int64(0),
- "renewable": false,
- "token_type": "default-service",
- "allowed_entity_aliases": []string(nil),
- "token_no_default_policy": false,
- }
-
- if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {
- t.Fatalf("unexpected token bound cidrs: %s", resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String())
- }
- delete(resp.Data, "token_bound_cidrs")
-
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
- }
- // Put values in both locations
- {
- req := logical.TestRequest(t, logical.UpdateOperation, "roles/test")
- req.Data = map[string]interface{}{
- "token_explicit_max_ttl": 7200,
- "explicit_max_ttl": 5200,
- "token_type": "service",
- "token_period": 5,
- "period": 1,
- "token_bound_cidrs": boundCIDRs[0].String(),
- "bound_cidrs": boundCIDRs[0].String(),
- }
-
- resp, err := ts.HandleRequest(rootContext, req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp == nil {
- t.Fatalf("expected a non-nil response")
- }
- if len(resp.Warnings) != 3 {
- t.Fatalf("expected 3 warnings, got %#v", resp.Warnings)
- }
-
- req = logical.TestRequest(t, logical.ReadOperation, "roles/test")
- resp, err = ts.HandleRequest(rootContext, req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- expected := map[string]interface{}{
- "name": "test",
- "orphan": false,
- "period": int64(0),
- "token_period": int64(5),
- "allowed_policies": []string(nil),
- "disallowed_policies": []string(nil),
- "allowed_policies_glob": []string(nil),
- "disallowed_policies_glob": []string(nil),
- "path_suffix": "",
- "token_explicit_max_ttl": int64(7200),
- "explicit_max_ttl": int64(0),
- "renewable": false,
- "token_type": "service",
- "allowed_entity_aliases": []string(nil),
- "token_no_default_policy": false,
- }
-
- if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {
- t.Fatalf("unexpected token bound cidrs: %s", resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String())
- }
- delete(resp.Data, "token_bound_cidrs")
-
- if diff := deep.Equal(expected, resp.Data); diff != nil {
- t.Fatal(diff)
- }
- }
-}
-
-func TestTokenStore_Periodic(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
-
- core.defaultLeaseTTL = 10 * time.Second
- core.maxLeaseTTL = 10 * time.Second
-
- // Note: these requests are sent to Core since Core handles registration
- // with the expiration manager and we need the storage to be consistent
-
- req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/roles/test")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "period": 5,
- }
-
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- // First make one directly and verify on renew it uses the period.
- {
- req.ClientToken = root
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/create"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("response was nil")
- }
- if resp.Auth == nil {
- t.Fatalf(fmt.Sprintf("response auth was nil, resp is %#v", *resp))
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- req.ClientToken = resp.Auth.ClientToken
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl := resp.Data["ttl"].(int64)
- if ttl > 5 {
- t.Fatalf("TTL too large (expected %d, got %d)", 5, ttl)
- }
-
- // Let the TTL go down a bit
- time.Sleep(2 * time.Second)
-
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/renew-self"
- req.Data = map[string]interface{}{
- "increment": 1,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl = resp.Data["ttl"].(int64)
- if ttl > 5 {
- t.Fatalf("TTL too large (expected %d, got %d)", 5, ttl)
- }
- }
-
- // Now we create a token against the role and also set the te value
- // directly. We should use the smaller of the two and be able to renew;
- // increment should be ignored as well.
- {
- req.ClientToken = root
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/create/test"
- req.Data = map[string]interface{}{
- "period": 5,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp == nil {
- t.Fatal("response was nil")
- }
- if resp.Auth == nil {
- t.Fatalf(fmt.Sprintf("response auth was nil, resp is %#v", *resp))
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- req.ClientToken = resp.Auth.ClientToken
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl := resp.Data["ttl"].(int64)
- if ttl < 4 || ttl > 5 {
- t.Fatalf("TTL bad (expected %d, got %d)", 4, ttl)
- }
-
- // Let the TTL go down a bit
- time.Sleep(2 * time.Second)
-
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/renew-self"
- req.Data = map[string]interface{}{
- "increment": 1,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl = resp.Data["ttl"].(int64)
- if ttl > 5 {
- t.Fatalf("TTL bad (expected less than %d, got %d)", 5, ttl)
- }
- }
-}
-
-func testTokenStore_NumUses_ErrorCheckHelper(t *testing.T, resp *logical.Response, err error) {
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("response was nil")
- }
- if resp.Auth == nil {
- t.Fatalf(fmt.Sprintf("response auth was nil, resp is %#v", *resp))
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-}
-
-func testTokenStore_NumUses_SelfLookupHelper(t *testing.T, core *Core, clientToken string, expectedNumUses int) {
- req := logical.TestRequest(t, logical.ReadOperation, "auth/token/lookup-self")
- req.ClientToken = clientToken
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- // Just used the token, this should decrement the num_uses counter
- expectedNumUses = expectedNumUses - 1
- actualNumUses := resp.Data["num_uses"].(int)
-
- if actualNumUses != expectedNumUses {
- t.Fatalf("num_uses mismatch (expected %d, got %d)", expectedNumUses, actualNumUses)
- }
-}
-
-func TestTokenStore_NumUses(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
- roleNumUses := 10
- lesserNumUses := 5
- greaterNumUses := 15
-
- // Create a test role with limited token_num_uses
- req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/roles/test-limited-uses")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "token_num_uses": roleNumUses,
- }
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- // Create a test role with unlimited token_num_uses
- req.Path = "auth/token/roles/test-unlimited-uses"
- req.Data = map[string]interface{}{}
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- // Generate some tokens from the test roles
- req.Path = "auth/token/create/test-limited-uses"
-
- // first token, num_uses is expected to come from the limited uses role
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- testTokenStore_NumUses_ErrorCheckHelper(t, resp, err)
- noOverrideToken := resp.Auth.ClientToken
-
- // second token, override num_uses with a lesser value, this should become the value
- // applied to the token
- req.Data = map[string]interface{}{
- "num_uses": lesserNumUses,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- testTokenStore_NumUses_ErrorCheckHelper(t, resp, err)
- lesserOverrideToken := resp.Auth.ClientToken
-
- // third token, override num_uses with a greater value, the value
- // applied to the token should come from the limited uses role
- req.Data = map[string]interface{}{
- "num_uses": greaterNumUses,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- testTokenStore_NumUses_ErrorCheckHelper(t, resp, err)
- greaterOverrideToken := resp.Auth.ClientToken
-
- // fourth token, override num_uses with a zero value, a num_uses value of zero
- // has an internal meaning of unlimited so num_uses == 1 is actually less than
- // num_uses == 0. In this case, the lesser value of the limited-uses role should be applied.
- req.Data = map[string]interface{}{
- "num_uses": 0,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- testTokenStore_NumUses_ErrorCheckHelper(t, resp, err)
- zeroOverrideToken := resp.Auth.ClientToken
-
- // fifth token, override num_uses with a value from a role that has unlimited num_uses. num_uses
- // should be the specified num_uses parameter at the create endpoint
- req.Path = "auth/token/create/test-unlimited-uses"
- req.Data = map[string]interface{}{
- "num_uses": lesserNumUses,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- testTokenStore_NumUses_ErrorCheckHelper(t, resp, err)
- unlimitedRoleOverrideToken := resp.Auth.ClientToken
-
- testTokenStore_NumUses_SelfLookupHelper(t, core, noOverrideToken, roleNumUses)
- testTokenStore_NumUses_SelfLookupHelper(t, core, lesserOverrideToken, lesserNumUses)
- testTokenStore_NumUses_SelfLookupHelper(t, core, greaterOverrideToken, roleNumUses)
- testTokenStore_NumUses_SelfLookupHelper(t, core, zeroOverrideToken, roleNumUses)
- testTokenStore_NumUses_SelfLookupHelper(t, core, unlimitedRoleOverrideToken, lesserNumUses)
-}
-
-func TestTokenStore_Periodic_ExplicitMax(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
-
- core.defaultLeaseTTL = 10 * time.Second
- core.maxLeaseTTL = 10 * time.Second
-
- // Note: these requests are sent to Core since Core handles registration
- // with the expiration manager and we need the storage to be consistent
-
- req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/roles/test")
- req.ClientToken = root
- req.Data = map[string]interface{}{
- "period": 5,
- }
-
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- // First make one directly and verify on renew it uses the period.
- {
- req.ClientToken = root
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/create"
- req.Data = map[string]interface{}{
- "period": 5,
- "explicit_max_ttl": 4,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil {
- t.Fatal("response was nil")
- }
- if resp.Auth == nil {
- t.Fatalf(fmt.Sprintf("response auth was nil, resp is %#v", *resp))
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- req.ClientToken = resp.Auth.ClientToken
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl := resp.Data["ttl"].(int64)
- if ttl < 3 || ttl > 4 {
- t.Fatalf("TTL bad (expected %d, got %d)", 3, ttl)
- }
-
- // Let the TTL go down a bit
- time.Sleep(2 * time.Second)
-
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/renew-self"
- req.Data = map[string]interface{}{
- "increment": 76,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl = resp.Data["ttl"].(int64)
- if ttl > 2 {
- t.Fatalf("TTL bad (expected less than %d, got %d)", 2, ttl)
- }
- }
-
- // Now we create a token against the role and also set the te value
- // directly. We should use the smaller of the two and be able to renew;
- // increment should be ignored as well.
- {
- req.Path = "auth/token/roles/test"
- req.ClientToken = root
- req.Operation = logical.UpdateOperation
- req.Data = map[string]interface{}{
- "period": 5,
- "explicit_max_ttl": 4,
- }
-
- resp, err := core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp != nil {
- t.Fatalf("expected a nil response")
- }
-
- req.ClientToken = root
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/create/test"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
- if resp == nil {
- t.Fatal("response was nil")
- }
- if resp.Auth == nil {
- t.Fatalf(fmt.Sprintf("response auth was nil, resp is %#v", *resp))
- }
- if resp.Auth.ClientToken == "" {
- t.Fatalf("bad: %#v", resp)
- }
-
- req.ClientToken = resp.Auth.ClientToken
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl := resp.Data["ttl"].(int64)
- if ttl < 3 || ttl > 4 {
- t.Fatalf("TTL bad (expected %d, got %d)", 3, ttl)
- }
-
- // Let the TTL go down a bit
- time.Sleep(2 * time.Second)
-
- req.Operation = logical.UpdateOperation
- req.Path = "auth/token/renew-self"
- req.Data = map[string]interface{}{
- "increment": 1,
- }
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- req.Operation = logical.ReadOperation
- req.Path = "auth/token/lookup-self"
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- ttl = resp.Data["ttl"].(int64)
- if ttl > 2 {
- t.Fatalf("TTL bad (expected less than %d, got %d)", 2, ttl)
- }
- }
-}
-
-func TestTokenStore_NoDefaultPolicy(t *testing.T) {
- var resp *logical.Response
- var err error
-
- core, _, root := TestCoreUnsealed(t)
- ts := core.tokenStore
- ps := core.policyStore
- policy, _ := ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
- policy.Name = "policy1"
- if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
- t.Fatal(err)
- }
-
- // Root token creates a token with desired policy. The created token
- // should also have 'default' attached to it.
- tokenData := map[string]interface{}{
- "policies": []string{"policy1"},
- }
- tokenReq := &logical.Request{
- Path: "create",
- ClientToken: root,
- Operation: logical.UpdateOperation,
- Data: tokenData,
- }
- resp = testMakeTokenViaRequest(t, ts, tokenReq)
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) {
- t.Fatalf("bad: policies: expected: [policy, default]; actual: %s", resp.Auth.Policies)
- }
-
- newToken := resp.Auth.ClientToken
-
- // Root token creates a token with desired policy, but also requests
- // that the token to not have 'default' policy. The resulting token
- // should not have 'default' policy on it.
- tokenData["no_default_policy"] = true
- resp = testMakeTokenViaRequest(t, ts, tokenReq)
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"policy1"}) {
- t.Fatalf("bad: policies: expected: [policy1]; actual: %s", resp.Auth.Policies)
- }
-
- // A non-root token which has 'default' policy attached requests for a
- // child token. Child token should also have 'default' policy attached.
- tokenReq.ClientToken = newToken
- tokenReq.Data = nil
- resp = testMakeTokenViaRequest(t, ts, tokenReq)
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) {
- t.Fatalf("bad: policies: expected: [default policy1]; actual: %s", resp.Auth.Policies)
- }
-
- // A non-root token which has 'default' policy attached and period explicitly
- // set to its zero value requests for a child token. Child token should be
- // successfully created and have 'default' policy attached.
- tokenReq.Data = map[string]interface{}{
- "period": "0s",
- }
- resp = testMakeTokenViaRequest(t, ts, tokenReq)
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) {
- t.Fatalf("bad: policies: expected: [default policy1]; actual: %s", resp.Auth.Policies)
- }
-
- // A non-root token which has 'default' policy attached, request for a
- // child token to not have 'default' policy while not sending a list
- tokenReq.Data = map[string]interface{}{
- "no_default_policy": true,
- }
- resp = testMakeTokenViaRequest(t, ts, tokenReq)
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"policy1"}) {
- t.Fatalf("bad: policies: expected: [policy1]; actual: %s", resp.Auth.Policies)
- }
-
- // In this case "default" shouldn't exist because we are not inheriting
- // parent policies
- tokenReq.Data = map[string]interface{}{
- "policies": []string{"policy1"},
- "no_default_policy": true,
- }
- resp = testMakeTokenViaRequest(t, ts, tokenReq)
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"policy1"}) {
- t.Fatalf("bad: policies: expected: [policy1]; actual: %s", resp.Auth.Policies)
- }
-
- // This is a non-root token which does not have 'default' policy
- // attached
- newToken = resp.Auth.ClientToken
- tokenReq.Data = nil
- tokenReq.ClientToken = newToken
- resp = testMakeTokenViaRequest(t, ts, tokenReq)
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"policy1"}) {
- t.Fatalf("bad: policies: expected: [policy1]; actual: %s", resp.Auth.Policies)
- }
-
- roleReq := &logical.Request{
- ClientToken: root,
- Path: "roles/role1",
- Operation: logical.CreateOperation,
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), roleReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v, resp: %v", err, resp)
- }
- tokenReq.Path = "create/role1"
- tokenReq.Data = map[string]interface{}{
- "policies": []string{"policy1"},
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), tokenReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v, resp: %v", err, resp)
- }
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"policy1"}) {
- t.Fatalf("bad: policies: expected: [policy1]; actual: %s", resp.Auth.Policies)
- }
-
- // If 'allowed_policies' in role does not have 'default' in it, the
- // tokens generated using that role should still have the 'default' policy
- // attached to them.
- roleReq.Operation = logical.UpdateOperation
- roleReq.Data = map[string]interface{}{
- "allowed_policies": "policy1",
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), roleReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v, resp: %v", err, resp)
- }
-
- resp = testMakeTokenViaRequest(t, ts, tokenReq)
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) {
- t.Fatalf("bad: policies: expected: [default policy1]; actual: %s", resp.Auth.Policies)
- }
-
- // If 'allowed_policies' in role does not have 'default' in it, the
- // tokens generated using that role should not have 'default' policy
- // attached to them if disallowed_policies contains "default"
- roleReq.Operation = logical.UpdateOperation
- roleReq.Data = map[string]interface{}{
- "allowed_policies": "policy1",
- "disallowed_policies": "default",
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), roleReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v, resp: %v", err, resp)
- }
-
- resp = testMakeTokenViaRequest(t, ts, tokenReq)
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"policy1"}) {
- t.Fatalf("bad: policies: expected: [policy1]; actual: %s", resp.Auth.Policies)
- }
-
- roleReq.Data = map[string]interface{}{
- "allowed_policies": "",
- "disallowed_policies": "default",
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), roleReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v, resp: %v", err, resp)
- }
-
- resp = testMakeTokenViaRequest(t, ts, tokenReq)
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"policy1"}) {
- t.Fatalf("bad: policies: expected: [policy1]; actual: %s", resp.Auth.Policies)
- }
-
- // Ensure that if default is in both allowed and disallowed, disallowed wins
- roleReq.Data = map[string]interface{}{
- "allowed_policies": "default",
- "disallowed_policies": "default",
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), roleReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v, resp: %v", err, resp)
- }
-
- delete(tokenReq.Data, "policies")
- resp, err = ts.HandleRequest(namespace.RootContext(nil), tokenReq)
- if err == nil || (resp != nil && !resp.IsError()) {
- t.Fatalf("err: %v, resp: %v", err, resp)
- }
-}
-
-func TestTokenStore_AllowedDisallowedPolicies(t *testing.T) {
- var resp *logical.Response
- var err error
-
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- roleReq := &logical.Request{
- ClientToken: root,
- Path: "roles/role1",
- Operation: logical.CreateOperation,
- Data: map[string]interface{}{
- "allowed_policies": "allowed1,allowed2",
- "disallowed_policies": "disallowed1,disallowed2",
- },
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), roleReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v, resp: %v", err, resp)
- }
-
- tokenReq := &logical.Request{
- Path: "create/role1",
- ClientToken: root,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "policies": []string{"allowed1"},
- },
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), tokenReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- expected := []string{"allowed1", "default"}
- if !reflect.DeepEqual(resp.Auth.Policies, expected) {
- t.Fatalf("bad: expected:%#v actual:%#v", expected, resp.Auth.Policies)
- }
-
- // Try again with automatic default adding turned off
- tokenReq = &logical.Request{
- Path: "create/role1",
- ClientToken: root,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "policies": []string{"allowed1"},
- "no_default_policy": true,
- },
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), tokenReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- expected = []string{"allowed1"}
- if !reflect.DeepEqual(resp.Auth.Policies, expected) {
- t.Fatalf("bad: expected:%#v actual:%#v", expected, resp.Auth.Policies)
- }
-
- tokenReq.Data = map[string]interface{}{
- "policies": []string{"disallowed1"},
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), tokenReq)
- if err == nil {
- t.Fatalf("expected an error")
- }
-
- roleReq.Operation = logical.UpdateOperation
- roleReq.Data = map[string]interface{}{
- "allowed_policies": "allowed1,common",
- "disallowed_policies": "disallowed1,common",
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), roleReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v, resp: %v", err, resp)
- }
-
- tokenReq.Data = map[string]interface{}{
- "policies": []string{"allowed1", "common"},
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), tokenReq)
- if err == nil {
- t.Fatalf("expected an error")
- }
-}
-
-// Issue 2189
-func TestTokenStore_RevokeUseCountToken(t *testing.T) {
- var resp *logical.Response
- var err error
- cubbyFuncLock := &sync.RWMutex{}
- cubbyFuncLock.Lock()
-
- exp := mockExpiration(t)
- ts := exp.tokenStore
- root, _ := exp.tokenStore.rootToken(namespace.RootContext(nil))
-
- tokenReq := &logical.Request{
- Path: "create",
- ClientToken: root.ID,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "num_uses": 1,
- },
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), tokenReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- tut := resp.Auth.ClientToken
- saltTut, err := ts.SaltID(namespace.RootContext(nil), tut)
- if err != nil {
- t.Fatal(err)
- }
- te, err := ts.lookupInternal(namespace.RootContext(nil), saltTut, true, false)
- if err != nil {
- t.Fatal(err)
- }
- if te == nil {
- t.Fatal("nil entry")
- }
- if te.NumUses != 1 {
- t.Fatalf("bad: %d", te.NumUses)
- }
-
- te, err = ts.UseToken(namespace.RootContext(nil), te)
- if err != nil {
- t.Fatal(err)
- }
- if te == nil {
- t.Fatal("nil entry")
- }
- if te.NumUses != tokenRevocationPending {
- t.Fatalf("bad: %d", te.NumUses)
- }
-
- // Should return no entry because it's tainted
- te, err = ts.lookupInternal(namespace.RootContext(nil), saltTut, true, false)
- if err != nil {
- t.Fatal(err)
- }
- if te != nil {
- t.Fatalf("%#v", te)
- }
-
- // But it should show up in an API lookup call
- req := &logical.Request{
- Path: "lookup-self",
- ClientToken: tut,
- Operation: logical.UpdateOperation,
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if resp == nil || resp.Data == nil || resp.Data["num_uses"] == nil {
- t.Fatal("nil resp or data")
- }
- if resp.Data["num_uses"].(int) != -1 {
- t.Fatalf("bad: %v", resp.Data["num_uses"])
- }
-
- // Should return tainted entries
- te, err = ts.lookupInternal(namespace.RootContext(nil), saltTut, true, true)
- if err != nil {
- t.Fatal(err)
- }
- if te == nil {
- t.Fatal("nil entry")
- }
- if te.NumUses != tokenRevocationPending {
- t.Fatalf("bad: %d", te.NumUses)
- }
-
- origDestroyCubbyhole := ts.cubbyholeDestroyer
-
- ts.cubbyholeDestroyer = func(context.Context, *TokenStore, *logical.TokenEntry) error {
- return fmt.Errorf("keep it frosty")
- }
-
- err = ts.revokeInternal(namespace.RootContext(nil), saltTut, false)
- if err == nil {
- t.Fatalf("expected err")
- }
-
- // Since revocation failed we should still be able to get a token
- te, err = ts.lookupInternal(namespace.RootContext(nil), saltTut, true, true)
- if err != nil {
- t.Fatal(err)
- }
- if te == nil {
- t.Fatal("nil token entry")
- }
-
- // Check the race condition situation by making the process sleep
- ts.cubbyholeDestroyer = func(context.Context, *TokenStore, *logical.TokenEntry) error {
- time.Sleep(1 * time.Second)
- return fmt.Errorf("keep it frosty")
- }
- cubbyFuncLock.Unlock()
-
- errCh := make(chan error)
- defer close(errCh)
- go func() {
- cubbyFuncLock.RLock()
- err := ts.revokeInternal(namespace.RootContext(nil), saltTut, false)
- cubbyFuncLock.RUnlock()
- errCh <- err
- }()
-
- // Give time for the function to start and grab locks
- time.Sleep(200 * time.Millisecond)
-
- te, err = ts.lookupInternal(namespace.RootContext(nil), saltTut, true, true)
- if err != nil {
- t.Fatal(err)
- }
- if te == nil {
- t.Fatal("nil token entry")
- }
-
- err = <-errCh
- if err == nil {
- t.Fatal("expected error on ts.revokeInternal() in anonymous goroutine")
- }
-
- // Put back to normal
- cubbyFuncLock.Lock()
- defer cubbyFuncLock.Unlock()
- ts.cubbyholeDestroyer = origDestroyCubbyhole
-
- err = ts.revokeInternal(namespace.RootContext(nil), saltTut, false)
- if err != nil {
- t.Fatal(err)
- }
-
- te, err = ts.lookupInternal(namespace.RootContext(nil), saltTut, true, true)
- if err != nil {
- t.Fatal(err)
- }
- if te != nil {
- t.Fatal("found entry")
- }
-}
-
-// Create a token, delete the token entry while leaking accessors, invoke tidy
-// and check if the dangling accessor entry is getting removed
-func TestTokenStore_HandleTidyCase1(t *testing.T) {
- var resp *logical.Response
- var err error
-
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- // List the number of accessors. Since there is only root token
- // present, the list operation should return only one key.
- accessorListReq := &logical.Request{
- Operation: logical.ListOperation,
- Path: "accessors/",
- ClientToken: root,
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), accessorListReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- numberOfAccessors := len(resp.Data["keys"].([]string))
- if numberOfAccessors != 1 {
- t.Fatalf("bad: number of accessors. Expected: 1, Actual: %d", numberOfAccessors)
- }
-
- for i := 1; i <= 100; i++ {
- // Create a regular token
- tokenReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "create",
- ClientToken: root,
- Data: map[string]interface{}{
- "policies": []string{"policy1"},
- },
- }
- resp := testMakeTokenViaRequest(t, ts, tokenReq)
- tut := resp.Auth.ClientToken
-
- // Creation of another token should end up with incrementing
- // the number of accessors
- // the storage
- resp, err = ts.HandleRequest(namespace.RootContext(nil), accessorListReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- numberOfAccessors = len(resp.Data["keys"].([]string))
- if numberOfAccessors != i+1 {
- t.Fatalf("bad: number of accessors. Expected: %d, Actual: %d", i+1, numberOfAccessors)
- }
-
- // Revoke the token while leaking other items associated with the
- // token. Do this by doing what revokeSalted used to do before it was
- // fixed, i.e., by deleting the storage entry for token and its
- // cubbyhole and by not deleting its secondary index, its accessor and
- // associated leases.
-
- saltedTut, err := ts.SaltID(namespace.RootContext(nil), tut)
- if err != nil {
- t.Fatal(err)
- }
- te, err := ts.lookupInternal(namespace.RootContext(nil), saltedTut, true, true)
- if err != nil {
- t.Fatalf("failed to lookup token: %v", err)
- }
-
- // Destroy the token index
- if ts.idView(namespace.RootNamespace).Delete(namespace.RootContext(nil), saltedTut); err != nil {
- t.Fatalf("failed to delete token entry: %v", err)
- }
-
- // Destroy the cubby space
- err = ts.cubbyholeDestroyer(namespace.RootContext(nil), ts, te)
- if err != nil {
- t.Fatalf("failed to destroyCubbyhole: %v", err)
- }
-
- // Leaking of accessor should have resulted in no change to the number
- // of accessors
- resp, err = ts.HandleRequest(namespace.RootContext(nil), accessorListReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- numberOfAccessors = len(resp.Data["keys"].([]string))
- if numberOfAccessors != i+1 {
- t.Fatalf("bad: number of accessors. Expected: %d, Actual: %d", i+1, numberOfAccessors)
- }
- }
-
- tidyReq := &logical.Request{
- Path: "tidy",
- Operation: logical.UpdateOperation,
- ClientToken: root,
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), tidyReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp != nil && resp.IsError() {
- t.Fatalf("resp: %#v", resp)
- }
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- // Tidy runs async so give it time
- time.Sleep(10 * time.Second)
-
- // Tidy should have removed all the dangling accessor entries
- resp, err = ts.HandleRequest(namespace.RootContext(nil), accessorListReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- numberOfAccessors = len(resp.Data["keys"].([]string))
- if numberOfAccessors != 1 {
- t.Fatalf("bad: number of accessors. Expected: 1, Actual: %d", numberOfAccessors)
- }
-}
-
-// Create a set of tokens along with a child token for each of them, delete the
-// token entry while leaking accessors, invoke tidy and check if the dangling
-// accessor entry is getting removed and check if child tokens are still present
-// and turned into orphan tokens.
-func TestTokenStore_HandleTidy_parentCleanup(t *testing.T) {
- var resp *logical.Response
- var err error
-
- c, _, root := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- // List the number of accessors. Since there is only root token
- // present, the list operation should return only one key.
- accessorListReq := &logical.Request{
- Operation: logical.ListOperation,
- Path: "accessors/",
- ClientToken: root,
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), accessorListReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- numberOfAccessors := len(resp.Data["keys"].([]string))
- if numberOfAccessors != 1 {
- t.Fatalf("bad: number of accessors. Expected: 1, Actual: %d", numberOfAccessors)
- }
-
- for i := 1; i <= 100; i++ {
- // Create a token
- tokenReq := &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "create",
- ClientToken: root,
- Data: map[string]interface{}{
- "policies": []string{"policy1"},
- },
- }
- resp := testMakeTokenViaRequest(t, ts, tokenReq)
- tut := resp.Auth.ClientToken
-
- // Create a child token
- tokenReq = &logical.Request{
- Operation: logical.UpdateOperation,
- Path: "create",
- ClientToken: tut,
- Data: map[string]interface{}{
- "policies": []string{"policy1"},
- },
- }
- testMakeTokenViaRequest(t, ts, tokenReq)
-
- // Creation of another token should end up with incrementing the number of
- // accessors the storage
- resp, err = ts.HandleRequest(namespace.RootContext(nil), accessorListReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- numberOfAccessors = len(resp.Data["keys"].([]string))
- if numberOfAccessors != (i*2)+1 {
- t.Fatalf("bad: number of accessors. Expected: %d, Actual: %d", i+1, numberOfAccessors)
- }
-
- // Revoke the token while leaking other items associated with the
- // token. Do this by doing what revokeSalted used to do before it was
- // fixed, i.e., by deleting the storage entry for token and its
- // cubbyhole and by not deleting its secondary index, its accessor and
- // associated leases.
-
- saltedTut, err := ts.SaltID(namespace.RootContext(nil), tut)
- if err != nil {
- t.Fatal(err)
- }
- te, err := ts.lookupInternal(namespace.RootContext(nil), saltedTut, true, true)
- if err != nil {
- t.Fatalf("failed to lookup token: %v", err)
- }
-
- // Destroy the token index
- if ts.idView(namespace.RootNamespace).Delete(namespace.RootContext(nil), saltedTut); err != nil {
- t.Fatalf("failed to delete token entry: %v", err)
- }
-
- // Destroy the cubby space
- err = ts.cubbyholeDestroyer(namespace.RootContext(nil), ts, te)
- if err != nil {
- t.Fatalf("failed to destroyCubbyhole: %v", err)
- }
-
- // Leaking of accessor should have resulted in no change to the number
- // of accessors
- resp, err = ts.HandleRequest(namespace.RootContext(nil), accessorListReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- numberOfAccessors = len(resp.Data["keys"].([]string))
- if numberOfAccessors != (i*2)+1 {
- t.Fatalf("bad: number of accessors. Expected: %d, Actual: %d", (i*2)+1, numberOfAccessors)
- }
- }
-
- tidyReq := &logical.Request{
- Path: "tidy",
- Operation: logical.UpdateOperation,
- ClientToken: root,
- }
- resp, err = ts.HandleRequest(namespace.RootContext(nil), tidyReq)
- if err != nil {
- t.Fatal(err)
- }
- if resp != nil && resp.IsError() {
- t.Fatalf("resp: %#v", resp)
- }
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- // Tidy runs async so give it time
- time.Sleep(10 * time.Second)
-
- // Tidy should have removed all the dangling accessor entries
- resp, err = ts.HandleRequest(namespace.RootContext(nil), accessorListReq)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err:%v resp:%v", err, resp)
- }
-
- // The number of accessors should be equal to number of valid child tokens
- // (100) + the root token (1)
- keys := resp.Data["keys"].([]string)
- numberOfAccessors = len(keys)
- if numberOfAccessors != 101 {
- t.Fatalf("bad: number of accessors. Expected: 1, Actual: %d", numberOfAccessors)
- }
-
- req := logical.TestRequest(t, logical.UpdateOperation, "lookup-accessor")
-
- for _, accessor := range keys {
- req.Data = map[string]interface{}{
- "accessor": accessor,
- }
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- if resp.Data == nil {
- t.Fatalf("response should contain data")
- }
- // These tokens should now be orphaned
- if resp.Data["orphan"] != true {
- t.Fatalf("token should be orphan")
- }
- }
-}
-
-func TestTokenStore_TidyLeaseRevocation(t *testing.T) {
- exp := mockExpiration(t)
- ts := exp.tokenStore
-
- noop := &NoopBackend{}
- _, barrier, _ := mockBarrier(t)
- view := NewBarrierView(barrier, "logical/")
- meUUID, err := uuid.GenerateUUID()
- if err != nil {
- t.Fatal(err)
- }
- err = exp.router.Mount(noop, "prod/aws/", &MountEntry{UUID: meUUID, Accessor: "awsaccessor", namespace: namespace.RootNamespace}, view)
- if err != nil {
- t.Fatal(err)
- }
-
- // Create new token
- root, err := ts.rootToken(namespace.RootContext(nil))
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Create a new token
- req := logical.TestRequest(t, logical.UpdateOperation, "create")
- req.ClientToken = root.ID
- req.Data["policies"] = []string{"default"}
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("err: %v\nresp: %#v", err, resp)
- }
-
- auth := &logical.Auth{
- ClientToken: resp.Auth.ClientToken,
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- Renewable: true,
- },
- }
-
- te := &logical.TokenEntry{
- Path: resp.Auth.CreationPath,
- NamespaceID: namespace.RootNamespaceID,
- }
-
- err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- // Verify token entry through lookup
- testTokenEntry, err := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
- if err != nil {
- t.Fatal(err)
- }
- if testTokenEntry == nil {
- t.Fatal("token entry was nil")
- }
-
- tut := resp.Auth.ClientToken
-
- req = &logical.Request{
- Path: "prod/aws/foo",
- ClientToken: tut,
- }
- req.SetTokenEntry(testTokenEntry)
-
- resp = &logical.Response{
- Secret: &logical.Secret{
- LeaseOptions: logical.LeaseOptions{
- TTL: time.Hour,
- },
- },
- }
-
- leases := []string{}
-
- for i := 0; i < 10; i++ {
- leaseID, err := exp.Register(namespace.RootContext(nil), req, resp, "")
- if err != nil {
- t.Fatal(err)
- }
- leases = append(leases, leaseID)
- }
-
- sort.Strings(leases)
-
- te, err = ts.lookupInternal(namespace.RootContext(nil), tut, false, true)
- if err != nil {
- t.Fatalf("failed to lookup token: %v", err)
- }
- if te == nil {
- t.Fatal("got nil token entry")
- }
-
- storedLeases, err := exp.lookupLeasesByToken(namespace.RootContext(nil), te)
- if err != nil {
- t.Fatal(err)
- }
- sort.Strings(storedLeases)
- if !reflect.DeepEqual(leases, storedLeases) {
- t.Fatalf("bad: %#v vs %#v", leases, storedLeases)
- }
-
- // Now, delete the token entry. The leases should still exist.
- saltedTut, err := ts.SaltID(namespace.RootContext(nil), tut)
- if err != nil {
- t.Fatal(err)
- }
- te, err = ts.lookupInternal(namespace.RootContext(nil), saltedTut, true, true)
- if err != nil {
- t.Fatalf("failed to lookup token: %v", err)
- }
- if te == nil {
- t.Fatal("got nil token entry")
- }
-
- // Destroy the token index
- if ts.idView(namespace.RootNamespace).Delete(namespace.RootContext(nil), saltedTut); err != nil {
- t.Fatalf("failed to delete token entry: %v", err)
- }
- te, err = ts.lookupInternal(namespace.RootContext(nil), saltedTut, true, true)
- if err != nil {
- t.Fatalf("failed to lookup token: %v", err)
- }
- if te != nil {
- t.Fatal("got token entry")
- }
-
- // Verify leases still exist
- storedLeases, err = exp.lookupLeasesByToken(namespace.RootContext(nil), testTokenEntry)
- if err != nil {
- t.Fatal(err)
- }
- sort.Strings(storedLeases)
- if !reflect.DeepEqual(leases, storedLeases) {
- t.Fatalf("bad: %#v vs %#v", leases, storedLeases)
- }
-
- // Call tidy
- ts.handleTidy(namespace.RootContext(nil), &logical.Request{}, nil)
-
- time.Sleep(200 * time.Millisecond)
-
- // Verify leases are gone
- storedLeases, err = exp.lookupLeasesByToken(namespace.RootContext(nil), testTokenEntry)
- if err != nil {
- t.Fatal(err)
- }
- if len(storedLeases) > 0 {
- t.Fatal("found leases")
- }
-}
-
-func TestTokenStore_Batch_CannotCreateChildren(t *testing.T) {
- var resp *logical.Response
-
- core, _, root := TestCoreUnsealed(t)
- ts := core.tokenStore
-
- req := &logical.Request{
- Path: "create",
- ClientToken: root,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "policies": []string{"policy1"},
- "type": "batch",
- },
- }
- resp = testMakeTokenViaRequest(t, ts, req)
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) {
- t.Fatalf("bad: policies: expected: [policy, default]; actual: %s", resp.Auth.Policies)
- }
-
- req.ClientToken = resp.Auth.ClientToken
- resp = testMakeTokenViaRequest(t, ts, req)
- if !resp.IsError() {
- t.Fatalf("bad: expected error, got %#v", *resp)
- }
-}
-
-func TestTokenStore_Batch_CannotRevoke(t *testing.T) {
- var resp *logical.Response
- var err error
-
- core, _, root := TestCoreUnsealed(t)
- ts := core.tokenStore
-
- req := &logical.Request{
- Path: "create",
- ClientToken: root,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "policies": []string{"policy1"},
- "type": "batch",
- },
- }
- resp = testMakeTokenViaRequest(t, ts, req)
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) {
- t.Fatalf("bad: policies: expected: [policy, default]; actual: %s", resp.Auth.Policies)
- }
-
- req.Path = "revoke"
- req.Data["token"] = resp.Auth.ClientToken
- resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
- if err != nil {
- t.Fatal(err)
- }
- if !resp.IsError() {
- t.Fatalf("bad: expected error, got %#v", *resp)
- }
-}
-
-func TestTokenStore_Batch_NoCubbyhole(t *testing.T) {
- var resp *logical.Response
- var err error
-
- core, _, root := TestCoreUnsealed(t)
- ts := core.tokenStore
-
- req := &logical.Request{
- Path: "create",
- ClientToken: root,
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "policies": []string{"policy1"},
- "type": "batch",
- },
- }
- resp = testMakeTokenViaRequest(t, ts, req)
- if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) {
- t.Fatalf("bad: policies: expected: [policy, default]; actual: %s", resp.Auth.Policies)
- }
-
- te, err := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
- if err != nil {
- t.Fatal(err)
- }
-
- req.Path = "cubbyhole/foo"
- req.Operation = logical.CreateOperation
- req.ClientToken = te.ID
- req.SetTokenEntry(te)
- resp, err = core.HandleRequest(namespace.RootContext(nil), req)
- if err != nil && !errwrap.Contains(err, logical.ErrInvalidRequest.Error()) {
- t.Fatal(err)
- }
- if !resp.IsError() {
- t.Fatalf("bad: expected error, got %#v", *resp)
- }
-}
-
-func TestTokenStore_TokenID(t *testing.T) {
- t.Run("no custom ID provided", func(t *testing.T) {
- c, _, initToken := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- // Ensure that a regular service token has a consts.ServiceTokenPrefix prefix
- resp, err := ts.HandleRequest(namespace.RootContext(nil), &logical.Request{
- ClientToken: initToken,
- Path: "create",
- Operation: logical.UpdateOperation,
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
- if !strings.HasPrefix(resp.Auth.ClientToken, consts.ServiceTokenPrefix) {
- t.Fatalf("token %q does not have a 'hvs.' prefix", resp.Auth.ClientToken)
- }
- })
-
- t.Run("plain custom ID", func(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
- ts := core.tokenStore
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), &logical.Request{
- ClientToken: root,
- Path: "create",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "id": "foobar",
- },
- })
- if err != nil || (resp != nil && resp.IsError()) {
- t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
- }
-
- // Ensure that using a custom token ID results in a warning
- expectedWarning := "Supplying a custom ID for the token uses the weaker SHA1 hashing instead of the more secure SHA2-256 HMAC for token obfuscation. SHA1 hashed tokens on the wire leads to less secure lookups."
- if resp.Warnings[0] != expectedWarning {
- t.Fatalf("expected warning not present")
- }
- })
-
- t.Run("service token prefix in custom ID", func(t *testing.T) {
- c, _, initToken := TestCoreUnsealed(t)
- ts := c.tokenStore
-
- // Ensure that custom token ID having a consts.ServiceTokenPrefix prefix fails
- resp, err := ts.HandleRequest(namespace.RootContext(nil), &logical.Request{
- ClientToken: initToken,
- Path: "create",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "id": "hvs.foobar",
- },
- })
- if err == nil {
- t.Fatalf("expected an error")
- }
- if resp.Error().Error() != "custom token ID cannot have the 'hvs.' prefix" {
- t.Fatalf("expected input error not present in error response")
- }
- })
-
- t.Run("period in custom ID", func(t *testing.T) {
- core, _, root := TestCoreUnsealed(t)
- ts := core.tokenStore
-
- resp, err := ts.HandleRequest(namespace.RootContext(nil), &logical.Request{
- ClientToken: root,
- Path: "create",
- Operation: logical.UpdateOperation,
- Data: map[string]interface{}{
- "id": "foobar.baz",
- },
- })
- if err == nil {
- t.Fatalf("expected an error")
- }
- if resp.Error().Error() != "custom token ID cannot have a '.' in the value" {
- t.Fatalf("expected input error not present in error response")
- }
- })
-}
-
-func expectInGaugeCollection(t *testing.T, expectedLabels map[string]string, expectedValue float32, actual []metricsutil.GaugeLabelValues) {
- t.Helper()
-
- for _, glv := range actual {
- actualLabels := make(map[string]string)
- for _, l := range glv.Labels {
- actualLabels[l.Name] = l.Value
- }
- if labelsMatch(actualLabels, expectedLabels) {
- if expectedValue != glv.Value {
- t.Errorf("expeced %v for %v, got %v", expectedValue, expectedLabels, glv.Value)
- }
- return
- }
- }
- t.Errorf("didn't find labels %v", expectedLabels)
-}
-
-func TestTokenStore_Collectors(t *testing.T) {
- ctx := namespace.RootContext(nil)
- exp := mockExpiration(t)
- ts := exp.tokenStore
-
- // This helper is defined in expiration.go
- sampleToken(t, exp, "auth/userpass/login", true, "default")
- sampleToken(t, exp, "auth/userpass/login", true, "policy23457")
- sampleToken(t, exp, "auth/token/create", false, "root")
- sampleToken(t, exp, "auth/github/login", true, "root")
- sampleToken(t, exp, "auth/github/login", false, "root")
-
- waitForRestore(t, exp)
-
- // By namespace:
- values, err := ts.gaugeCollector(ctx)
- if err != nil {
- t.Fatalf("bad collector run: %v", err)
- }
- if len(values) != 1 {
- t.Errorf("got %v values, expected 1", len(values))
- }
- expectInGaugeCollection(t,
- map[string]string{"namespace": "root"},
- 5.0,
- values)
-
- values, err = ts.gaugeCollectorByPolicy(ctx)
- if err != nil {
- t.Fatalf("bad collector run: %v", err)
- }
- if len(values) != 3 {
- t.Errorf("got %v values, expected 3", len(values))
- }
- expectInGaugeCollection(t,
- map[string]string{"namespace": "root", "policy": "root"},
- 3.0,
- values)
- expectInGaugeCollection(t,
- map[string]string{"namespace": "root", "policy": "default"},
- 1.0,
- values)
- expectInGaugeCollection(t,
- map[string]string{"namespace": "root", "policy": "policy23457"},
- 1.0,
- values)
-
- values, err = ts.gaugeCollectorByTtl(ctx)
- if err != nil {
- t.Fatalf("bad collector run: %v", err)
- }
- if len(values) != 2 {
- t.Errorf("got %v values, expected 2", len(values))
- }
- expectInGaugeCollection(t,
- map[string]string{"namespace": "root", "creation_ttl": "1h"},
- 3.0,
- values)
- expectInGaugeCollection(t,
- map[string]string{"namespace": "root", "creation_ttl": "+Inf"},
- 2.0,
- values)
-
- // Need to set up router for this to work, TODO
- // ts.gaugeCollectorByMethod( ctx )
-}
diff --git a/vault/ui_test.go b/vault/ui_test.go
deleted file mode 100644
index 3bad3f8f1..000000000
--- a/vault/ui_test.go
+++ /dev/null
@@ -1,191 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "testing"
-
- "github.com/hashicorp/vault/sdk/logical"
-
- log "github.com/hashicorp/go-hclog"
- "github.com/hashicorp/vault/sdk/helper/logging"
- "github.com/hashicorp/vault/sdk/physical/inmem"
-)
-
-func TestConfig_Enabled(t *testing.T) {
- logger := logging.NewVaultLogger(log.Trace)
- phys, err := inmem.NewTransactionalInmem(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- logl := &logical.InmemStorage{}
-
- config := NewUIConfig(true, phys, logl)
- if !config.Enabled() {
- t.Fatal("ui should be enabled")
- }
-
- config = NewUIConfig(false, phys, logl)
- if config.Enabled() {
- t.Fatal("ui should not be enabled")
- }
-}
-
-func TestConfig_Headers(t *testing.T) {
- logger := logging.NewVaultLogger(log.Trace)
- phys, err := inmem.NewTransactionalInmem(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- logl := &logical.InmemStorage{}
-
- config := NewUIConfig(true, phys, logl)
- headers, err := config.Headers(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(headers) != len(config.defaultHeaders) {
- t.Fatalf("expected %d headers, got %d", len(config.defaultHeaders), len(headers))
- }
-
- head, err := config.GetHeader(context.Background(), "Test-Header")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(head) != 0 {
- t.Fatal("header returned found, should not be found")
- }
- err = config.SetHeader(context.Background(), "Test-Header", []string{"123", "456"})
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- head, err = config.GetHeader(context.Background(), "Test-Header")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(head) != 2 {
- t.Fatalf("header not found or incorrect number of values: %#v", head)
- }
- if head[0] != "123" {
- t.Fatalf("expected: %s, got: %s", "123", head[0])
- }
- if head[1] != "456" {
- t.Fatalf("expected: %s, got: %s", "456", head[1])
- }
-
- head, err = config.GetHeader(context.Background(), "tEST-hEADER")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(head) != 2 {
- t.Fatalf("header not found or incorrect number of values: %#v", head)
- }
- if head[0] != "123" {
- t.Fatalf("expected: %s, got: %s", "123", head[0])
- }
- if head[1] != "456" {
- t.Fatalf("expected: %s, got: %s", "456", head[1])
- }
-
- keys, err := config.HeaderKeys(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(keys) != 1 {
- t.Fatalf("expected 1 key, got %d", len(keys))
- }
-
- err = config.SetHeader(context.Background(), "Test-Header-2", []string{"321"})
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- keys, err = config.HeaderKeys(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(keys) != 2 {
- t.Fatalf("expected 1 key, got %d", len(keys))
- }
- err = config.DeleteHeader(context.Background(), "Test-Header-2")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
-
- err = config.DeleteHeader(context.Background(), "Test-Header")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- head, err = config.GetHeader(context.Background(), "Test-Header")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(head) != 0 {
- t.Fatal("header returned found, should not be found")
- }
- keys, err = config.HeaderKeys(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(keys) != 0 {
- t.Fatalf("expected 0 key, got %d", len(keys))
- }
-}
-
-func TestConfig_DefaultHeaders(t *testing.T) {
- logger := logging.NewVaultLogger(log.Trace)
- phys, err := inmem.NewTransactionalInmem(nil, logger)
- if err != nil {
- t.Fatal(err)
- }
- logl := &logical.InmemStorage{}
-
- config := NewUIConfig(true, phys, logl)
- headers, err := config.Headers(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if len(headers) != len(config.defaultHeaders) {
- t.Fatalf("expected %d headers, got %d", len(config.defaultHeaders), len(headers))
- }
-
- headers, err = config.Headers(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- defaultCSP := config.defaultHeaders.Get("Content-security-Policy")
- head := headers.Get("Content-Security-Policy")
- if head != defaultCSP {
- t.Fatalf("header does not match: expected %s, got %s", defaultCSP, head)
- }
-
- err = config.SetHeader(context.Background(), "Content-security-Policy", []string{"test"})
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- headers, err = config.Headers(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- head = headers.Get("Content-Security-Policy")
- if head != "test" {
- t.Fatalf("header does not match: expected %s, got %s", "test", head)
- }
-
- err = config.DeleteHeader(context.Background(), "Content-Security-Policy")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- headers, err = config.Headers(context.Background())
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- head = headers.Get("Content-Security-Policy")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if head != defaultCSP {
- t.Fatalf("header does not match: expected %s, got %s", defaultCSP, head)
- }
-}
diff --git a/vault/util_test.go b/vault/util_test.go
deleted file mode 100644
index 04d345f0f..000000000
--- a/vault/util_test.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import "testing"
-
-func TestMemZero(t *testing.T) {
- b := []byte{1, 2, 3, 4}
- memzero(b)
- if b[0] != 0 || b[1] != 0 || b[2] != 0 || b[3] != 0 {
- t.Fatalf("bad: %v", b)
- }
-}
-
-func TestRandBytes(t *testing.T) {
- b := randbytes(12)
- if len(b) != 12 {
- t.Fatalf("bad: %v", b)
- }
-}
diff --git a/vault/version_store_test.go b/vault/version_store_test.go
deleted file mode 100644
index 05975cdc5..000000000
--- a/vault/version_store_test.go
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (c) HashiCorp, Inc.
-// SPDX-License-Identifier: BUSL-1.1
-
-package vault
-
-import (
- "context"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/version"
-)
-
-// TestVersionStore_StoreMultipleVaultVersions writes multiple versions of 1.9.0 and verifies that only
-// the original timestamp is stored.
-func TestVersionStore_StoreMultipleVaultVersions(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- upgradeTimePlusEpsilon := time.Now().UTC()
- vaultVersion := &VaultVersion{
- Version: version.Version,
- TimestampInstalled: upgradeTimePlusEpsilon.Add(30 * time.Hour),
- }
- wasStored, err := c.storeVersionEntry(context.Background(), vaultVersion, false)
- if err != nil || wasStored {
- t.Fatalf("vault version was re-stored: %v, err is: %s", wasStored, err.Error())
- }
- versionEntry, ok := c.versionHistory[version.Version]
- if !ok {
- t.Fatalf("no %s version timestamp found", version.Version)
- }
- if versionEntry.TimestampInstalled.After(upgradeTimePlusEpsilon) {
- t.Fatalf("upgrade time for %s is incorrect: got %+v, expected less than %+v", version.Version, versionEntry.TimestampInstalled, upgradeTimePlusEpsilon)
- }
-}
-
-// TestVersionStore_GetOldestVersion verifies that FindOldestVersionTimestamp finds the oldest
-// (in time) vault version stored.
-func TestVersionStore_GetOldestVersion(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- upgradeTimePlusEpsilon := time.Now().UTC()
-
- // 1.6.2 is stored before 1.6.1, so even though it is a higher number, it should be returned.
- versionEntries := []VaultVersion{
- {Version: "1.6.2", TimestampInstalled: upgradeTimePlusEpsilon.Add(-4 * time.Hour)},
- {Version: "1.6.1", TimestampInstalled: upgradeTimePlusEpsilon.Add(2 * time.Hour)},
- }
-
- for _, entry := range versionEntries {
- _, err := c.storeVersionEntry(context.Background(), &entry, false)
- if err != nil {
- t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error())
- }
- }
-
- err := c.loadVersionHistory(c.activeContext)
- if err != nil {
- t.Fatalf("failed to populate version history cache, err: %s", err.Error())
- }
-
- if len(c.versionHistory) != 3 {
- t.Fatalf("expected 3 entries in timestamps map after refresh, found: %d", len(c.versionHistory))
- }
- v, tm, err := c.FindOldestVersionTimestamp()
- if err != nil {
- t.Fatal(err)
- }
- if v != "1.6.2" {
- t.Fatalf("expected 1.6.2, found: %s", v)
- }
- if tm.Before(upgradeTimePlusEpsilon.Add(-6*time.Hour)) || tm.After(upgradeTimePlusEpsilon.Add(-2*time.Hour)) {
- t.Fatalf("incorrect upgrade time logged: %v", tm)
- }
-}
-
-// TestVersionStore_GetNewestVersion verifies that FindNewestVersionTimestamp finds the newest
-// (in time) vault version stored.
-func TestVersionStore_GetNewestVersion(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- upgradeTimePlusEpsilon := time.Now().UTC()
-
- // 1.6.1 is stored after 1.6.2, so even though it is a lower number, it should be returned.
- versionEntries := []VaultVersion{
- {Version: "1.6.2", TimestampInstalled: upgradeTimePlusEpsilon.Add(-4 * time.Hour)},
- {Version: "1.6.1", TimestampInstalled: upgradeTimePlusEpsilon.Add(2 * time.Hour)},
- }
-
- for _, entry := range versionEntries {
- _, err := c.storeVersionEntry(context.Background(), &entry, false)
- if err != nil {
- t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error())
- }
- }
-
- err := c.loadVersionHistory(c.activeContext)
- if err != nil {
- t.Fatalf("failed to populate version history cache, err: %s", err.Error())
- }
-
- if len(c.versionHistory) != 3 {
- t.Fatalf("expected 3 entries in timestamps map after refresh, found: %d", len(c.versionHistory))
- }
- v, tm, err := c.FindNewestVersionTimestamp()
- if err != nil {
- t.Fatal(err)
- }
- if v != "1.6.1" {
- t.Fatalf("expected 1.6.1, found: %s", v)
- }
- if tm.Before(upgradeTimePlusEpsilon.Add(1*time.Hour)) || tm.After(upgradeTimePlusEpsilon.Add(3*time.Hour)) {
- t.Fatalf("incorrect upgrade time logged: %v", tm)
- }
-}
-
-func TestVersionStore_SelfHealUTC(t *testing.T) {
- c, _, _ := TestCoreUnsealed(t)
- estLoc, err := time.LoadLocation("EST")
- if err != nil {
- t.Fatalf("failed to load location, err: %s", err.Error())
- }
-
- nowEST := time.Now().In(estLoc)
-
- versionEntries := []VaultVersion{
- {Version: "1.9.0", TimestampInstalled: nowEST.Add(24 * time.Hour)},
- {Version: "1.9.1", TimestampInstalled: nowEST.Add(48 * time.Hour)},
- }
-
- for _, entry := range versionEntries {
- _, err := c.storeVersionEntry(context.Background(), &entry, false)
- if err != nil {
- t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error())
- }
- }
-
- err = c.loadVersionHistory(c.activeContext)
- if err != nil {
- t.Fatalf("failed to load version timestamps, err: %s", err.Error())
- }
-
- for _, entry := range c.versionHistory {
- if entry.TimestampInstalled.Location() != time.UTC {
- t.Fatalf("failed to convert %s timestamp %s to UTC", entry.Version, entry.TimestampInstalled)
- }
- }
-}