mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-16 11:37:04 +02:00
* Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License. Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUS-1.1 * Fix test that expected exact offset on hcl file --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Co-authored-by: Sarah Thompson <sthompson@hashicorp.com> Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
510 lines
14 KiB
JavaScript
510 lines
14 KiB
JavaScript
/**
|
|
* 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=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i \
|
|
region=us-east-1`,
|
|
expected: {
|
|
method: 'write',
|
|
flagArray: [],
|
|
path: 'aws/config/root',
|
|
dataArray: [
|
|
'access_key=AKIAJWVN5Z4FOFT7NLNA',
|
|
'secret_key=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i',
|
|
'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=R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i',
|
|
'region=us-east-1',
|
|
],
|
|
flagInput: [],
|
|
expected: {
|
|
data: {
|
|
access_key: 'AKIAJWVN5Z4FOFT7NLNA',
|
|
secret_key: 'R4nm063hgMVo4BTT5xOs5nHLeLXA6lar7ZJ3Nt0i',
|
|
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'
|
|
);
|
|
});
|
|
});
|
|
});
|