mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 17:52:32 +00:00
Clean up on Azure configuration (#29482)
* put in more options and make generic root password ttl * small changes
This commit is contained in:
@@ -236,7 +236,7 @@ export default class ConfigureWif extends Component<Args> {
|
||||
// reset all "account" attributes that are mutually exclusive with "wif" attributes
|
||||
// these attributes are different for each engine
|
||||
type === 'azure'
|
||||
? (mountConfigModel.clientSecret = mountConfigModel.rootPasswordTtl = undefined)
|
||||
? (mountConfigModel.clientSecret = undefined)
|
||||
: type === 'aws'
|
||||
? (mountConfigModel.accessKey = undefined)
|
||||
: null;
|
||||
|
||||
@@ -14,7 +14,12 @@ export default class AzureConfig extends Model {
|
||||
@attr('string', { label: 'Tenant ID' }) tenantId;
|
||||
@attr('string', { label: 'Client ID' }) clientId;
|
||||
@attr('string', { sensitive: true }) clientSecret; // obfuscated, never returned by API
|
||||
@attr('string') environment;
|
||||
|
||||
@attr('string', {
|
||||
subText:
|
||||
'This value can also be provided with the AZURE_ENVIRONMENT environment variable. If not specified, Vault will use Azure Public Cloud.',
|
||||
})
|
||||
environment;
|
||||
|
||||
@attr('string', {
|
||||
subText:
|
||||
@@ -34,8 +39,10 @@ export default class AzureConfig extends Model {
|
||||
@attr({
|
||||
label: 'Root password TTL',
|
||||
editType: 'ttl',
|
||||
helperTextDisabled:
|
||||
'Specifies how long the root password is valid for in Azure when rotate-root generates a new client secret. Defaults to 182 days or 6 months, 1 day and 13 hours.',
|
||||
// default is 15768000 sec. The api docs say 182 days, but this should be updated to 182.5 days.
|
||||
helperTextDisabled: 'Vault will use the default of 182 days.',
|
||||
helperTextEnabled:
|
||||
'Specifies how long the root password is valid for in Azure when rotate-root generates a new client secret.',
|
||||
})
|
||||
rootPasswordTtl;
|
||||
|
||||
@@ -64,11 +71,9 @@ export default class AzureConfig extends Model {
|
||||
return !!this.identityTokenAudience || !!this.identityTokenTtl;
|
||||
}
|
||||
|
||||
get isAccountPluginConfigured() {
|
||||
// clientSecret is not checked here because it's never return by the API
|
||||
// however it is an Azure account field
|
||||
return !!this.rootPasswordTtl;
|
||||
}
|
||||
// the "clientSecret" param is not checked because it's never return by the API.
|
||||
// thus we can never say for sure if the account accessType has been configured so we always return false
|
||||
isAccountPluginConfigured = false;
|
||||
|
||||
/* GETTERS used to generate array of fields to be displayed in:
|
||||
1. details view
|
||||
@@ -91,18 +96,22 @@ export default class AzureConfig extends Model {
|
||||
formFieldGroups(accessType = 'account') {
|
||||
const formFieldGroups = [];
|
||||
formFieldGroups.push({
|
||||
default: ['subscriptionId', 'tenantId', 'clientId', 'environment'],
|
||||
default: ['subscriptionId', 'tenantId', 'clientId'],
|
||||
});
|
||||
if (accessType === 'account') {
|
||||
formFieldGroups.push({
|
||||
default: ['clientSecret'],
|
||||
});
|
||||
}
|
||||
if (accessType === 'wif') {
|
||||
formFieldGroups.push({
|
||||
default: ['identityTokenAudience', 'identityTokenTtl'],
|
||||
});
|
||||
}
|
||||
if (accessType === 'account') {
|
||||
formFieldGroups.push({
|
||||
default: ['clientSecret', 'rootPasswordTtl'],
|
||||
});
|
||||
}
|
||||
formFieldGroups.push({
|
||||
'More options': ['environment', 'rootPasswordTtl'],
|
||||
});
|
||||
|
||||
return formFieldGroups;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,10 +73,10 @@ export default class GcpConfig extends Model {
|
||||
return !!this.identityTokenAudience || !!this.identityTokenTtl || !!this.serviceAccountEmail;
|
||||
}
|
||||
|
||||
isAccountPluginConfigured = false;
|
||||
// the "credentials" param is not checked for "isAccountPluginConfigured" because it's never return by the API
|
||||
// additionally credentials can be set via GOOGLE_APPLICATION_CREDENTIALS env var so we cannot call it a required field in the ui.
|
||||
// thus we can never say for sure if the account accessType has been configured so we always return false
|
||||
isAccountPluginConfigured = false;
|
||||
|
||||
get displayAttrs() {
|
||||
const formFields = expandAttributeMeta(this, this.configurableParams);
|
||||
|
||||
@@ -85,8 +85,8 @@ module('Acceptance | Azure | configuration', function (hooks) {
|
||||
subscription_id: 'subscription-id',
|
||||
tenant_id: 'tenant-id',
|
||||
client_id: 'client-id',
|
||||
root_password_ttl: '20 days 20 hours',
|
||||
environment: 'AZUREPUBLICCLOUD',
|
||||
root_password_ttl: '1800000s',
|
||||
};
|
||||
this.server.get(`${path}/config`, () => {
|
||||
assert.true(true, 'request made to config when navigating to the configuration page.');
|
||||
@@ -112,7 +112,8 @@ module('Acceptance | Azure | configuration', function (hooks) {
|
||||
});
|
||||
|
||||
module('create', function () {
|
||||
test('it should save azure account accessType options', async function (assert) {
|
||||
test('it should save azure account options', async function (assert) {
|
||||
// there are no azure specific options that can be returned from the API so confirm the generic options are saved.
|
||||
assert.expect(3);
|
||||
const path = `azure-${this.uid}`;
|
||||
await enablePage.enable(this.type, path);
|
||||
@@ -125,7 +126,7 @@ module('Acceptance | Azure | configuration', function (hooks) {
|
||||
|
||||
await click(SES.configTab);
|
||||
await click(SES.configure);
|
||||
await fillInAzureConfig(this.type);
|
||||
await fillInAzureConfig();
|
||||
await click(GENERAL.saveButton);
|
||||
assert.true(
|
||||
this.flashSuccessSpy.calledWith(`Successfully saved ${path}'s configuration.`),
|
||||
@@ -133,10 +134,7 @@ module('Acceptance | Azure | configuration', function (hooks) {
|
||||
);
|
||||
assert
|
||||
.dom(GENERAL.infoRowValue('Root password TTL'))
|
||||
.hasText(
|
||||
'1 hour 26 minutes 40 seconds',
|
||||
'Root password TTL, an azure account specific field, has been set.'
|
||||
);
|
||||
.hasText('3 minutes 20 seconds', 'Root password TTL, a generic field, has been set.');
|
||||
assert
|
||||
.dom(GENERAL.infoRowValue('Subscription ID'))
|
||||
.hasText('subscription-id', 'Subscription ID, a generic field, has been set.');
|
||||
@@ -226,7 +224,7 @@ module('Acceptance | Azure | configuration', function (hooks) {
|
||||
|
||||
await click(SES.configTab);
|
||||
await click(SES.configure);
|
||||
await fillInAzureConfig('azure');
|
||||
await fillInAzureConfig();
|
||||
await click(GENERAL.saveButton);
|
||||
|
||||
assert.dom(GENERAL.messageError).hasText('Error welp, that did not work!', 'API error shows on form');
|
||||
@@ -267,12 +265,15 @@ module('Acceptance | Azure | configuration', function (hooks) {
|
||||
identity_token_audience: 'audience',
|
||||
identity_token_ttl: 720000,
|
||||
environment: 'AZUREPUBLICCLOUD',
|
||||
root_password_ttl: '1800000s',
|
||||
};
|
||||
this.server.get(`${path}/config`, () => {
|
||||
assert.true(true, 'request made to config when navigating to the configuration page.');
|
||||
return { data: { id: path, type: this.type, ...wifAttrs } };
|
||||
});
|
||||
await enablePage.enable(this.type, path);
|
||||
GENERAL.toggleGroup('More options');
|
||||
|
||||
for (const key of expectedConfigKeys('azure-wif')) {
|
||||
const responseKeyAndValue = expectedValueOfConfigKeys(this.type, key);
|
||||
assert
|
||||
@@ -362,7 +363,7 @@ module('Acceptance | Azure | configuration', function (hooks) {
|
||||
|
||||
await click(SES.configTab);
|
||||
await click(SES.configure);
|
||||
await fillInAzureConfig('withWif');
|
||||
await fillInAzureConfig(true);
|
||||
await click(GENERAL.saveButton);
|
||||
assert.dom(SES.wif.issuerWarningModal).doesNotExist('issuer warning modal does not show');
|
||||
assert.true(
|
||||
@@ -392,7 +393,7 @@ module('Acceptance | Azure | configuration', function (hooks) {
|
||||
|
||||
await click(SES.configTab);
|
||||
await click(SES.configure);
|
||||
await fillInAzureConfig('withWif');
|
||||
await fillInAzureConfig(true);
|
||||
assert
|
||||
.dom(GENERAL.inputByAttr('issuer'))
|
||||
.hasValue(oldIssuer, 'issuer defaults to previously saved value');
|
||||
@@ -434,7 +435,7 @@ module('Acceptance | Azure | configuration', function (hooks) {
|
||||
|
||||
await click(SES.configTab);
|
||||
await click(SES.configure);
|
||||
await fillInAzureConfig('withWif');
|
||||
await fillInAzureConfig(true);
|
||||
assert
|
||||
.dom(GENERAL.inputByAttr('issuer'))
|
||||
.hasValue(oldIssuer, 'issuer defaults to previously saved value');
|
||||
@@ -465,7 +466,7 @@ module('Acceptance | Azure | configuration', function (hooks) {
|
||||
await enablePage.enable(this.type, path);
|
||||
await click(SES.configTab);
|
||||
await click(SES.configure);
|
||||
await fillInAzureConfig('withWif');
|
||||
await fillInAzureConfig(true);
|
||||
await click(GENERAL.saveButton); // finished creating attributes, go back and edit them.
|
||||
assert
|
||||
.dom(GENERAL.infoRowValue('Identity token audience'))
|
||||
|
||||
@@ -129,7 +129,7 @@ const createAzureConfig = (store, backend, accessType = 'generic') => {
|
||||
subscription_id: 'subscription-id',
|
||||
tenant_id: 'tenant-id',
|
||||
client_id: 'client-id',
|
||||
root_password_ttl: '20 days 20 hours',
|
||||
root_password_ttl: '1800000s',
|
||||
environment: 'AZUREPUBLICCLOUD',
|
||||
},
|
||||
});
|
||||
@@ -144,7 +144,7 @@ const createAzureConfig = (store, backend, accessType = 'generic') => {
|
||||
client_id: 'client-id',
|
||||
identity_token_audience: 'audience',
|
||||
identity_token_ttl: 7200,
|
||||
root_password_ttl: '20 days 20 hours',
|
||||
root_password_ttl: '1800000s',
|
||||
environment: 'AZUREPUBLICCLOUD',
|
||||
},
|
||||
});
|
||||
@@ -158,6 +158,7 @@ const createAzureConfig = (store, backend, accessType = 'generic') => {
|
||||
tenant_id: 'tenant-id-2',
|
||||
client_id: 'client-id-2',
|
||||
environment: 'AZUREPUBLICCLOUD',
|
||||
root_password_ttl: '1800000s',
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -250,22 +251,21 @@ export const fillInAwsConfig = async (situation = 'withAccess') => {
|
||||
}
|
||||
};
|
||||
|
||||
export const fillInAzureConfig = async (situation = 'azure') => {
|
||||
await fillIn(GENERAL.inputByAttr('subscriptionId'), 'subscription-id');
|
||||
await fillIn(GENERAL.inputByAttr('tenantId'), 'tenant-id');
|
||||
await fillIn(GENERAL.inputByAttr('clientId'), 'client-id');
|
||||
await fillIn(GENERAL.inputByAttr('environment'), 'AZUREPUBLICCLOUD');
|
||||
|
||||
if (situation === 'azure') {
|
||||
await fillIn(GENERAL.inputByAttr('clientSecret'), 'client-secret');
|
||||
await click(GENERAL.ttl.toggle('Root password TTL'));
|
||||
await fillIn(GENERAL.ttl.input('Root password TTL'), '5200');
|
||||
}
|
||||
if (situation === 'withWif') {
|
||||
export const fillInAzureConfig = async (withWif = false) => {
|
||||
if (withWif) {
|
||||
await click(SES.wif.accessType('wif')); // toggle to wif
|
||||
await fillIn(GENERAL.inputByAttr('identityTokenAudience'), 'azure-audience');
|
||||
await click(GENERAL.ttl.toggle('Identity token TTL'));
|
||||
await fillIn(GENERAL.ttl.input('Identity token TTL'), '7200');
|
||||
} else {
|
||||
await fillIn(GENERAL.inputByAttr('subscriptionId'), 'subscription-id');
|
||||
await fillIn(GENERAL.inputByAttr('tenantId'), 'tenant-id');
|
||||
await fillIn(GENERAL.inputByAttr('clientId'), 'client-id');
|
||||
await click(GENERAL.toggleGroup('More options'));
|
||||
await fillIn(GENERAL.inputByAttr('environment'), 'AZUREPUBLICCLOUD');
|
||||
await click(GENERAL.ttl.toggle('Root password TTL'));
|
||||
await fillIn(GENERAL.ttl.input('Root password TTL'), '200');
|
||||
await fillIn(GENERAL.inputByAttr('clientSecret'), 'client-secret');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -298,8 +298,8 @@ const awsLeaseKeys = ['Default Lease TTL', 'Max Lease TTL'];
|
||||
const awsKeys = ['Access key', 'Secret key', 'Region', 'IAM endpoint', 'STS endpoint', 'Max retries'];
|
||||
const awsWifKeys = ['Issuer', 'Role ARN', ...genericWifKeys];
|
||||
// Azure specific keys
|
||||
const genericAzureKeys = ['Subscription ID', 'Tenant ID', 'Client ID', 'Environment'];
|
||||
const azureKeys = [...genericAzureKeys, 'Client secret', 'Root password TTL'];
|
||||
const genericAzureKeys = ['Subscription ID', 'Tenant ID', 'Client ID', 'Environment', 'Root password TTL'];
|
||||
const azureKeys = [...genericAzureKeys, 'Client secret'];
|
||||
const azureWifKeys = [...genericAzureKeys, ...genericWifKeys];
|
||||
// GCP specific keys
|
||||
const genericGcpKeys = ['Config TTL', 'Max TTL'];
|
||||
|
||||
@@ -113,6 +113,9 @@ module('Integration | Component | SecretEngine::ConfigureWif', function (hooks)
|
||||
<SecretEngine::ConfigureWif @backendPath={{this.id}} @displayName={{this.displayName}} @type={{this.type}} @mountConfigModel={{this.mountConfigModel}} @issuerConfig={{this.issuerConfig}} @additionalConfigModel={{this.additionalConfigModel}}/>
|
||||
`);
|
||||
await click(SES.wif.accessType('wif'));
|
||||
// toggle grouped fields if it exists
|
||||
const toggleGroup = document.querySelector('[data-test-toggle-group]');
|
||||
toggleGroup ? await click(toggleGroup) : null;
|
||||
// check for the wif fields only
|
||||
for (const key of expectedConfigKeys(`${type}-wif`, true)) {
|
||||
if (key === 'Identity token TTL') {
|
||||
@@ -147,7 +150,7 @@ module('Integration | Component | SecretEngine::ConfigureWif', function (hooks)
|
||||
`Request was made to save the issuer when it should not have been because the user canceled out of the flow.`
|
||||
);
|
||||
});
|
||||
await fillInAzureConfig('withWif');
|
||||
await fillInAzureConfig(true);
|
||||
await click(GENERAL.cancelButton);
|
||||
|
||||
assert.true(this.flashDangerSpy.notCalled, 'No danger flash messages called.');
|
||||
@@ -253,14 +256,15 @@ module('Integration | Component | SecretEngine::ConfigureWif', function (hooks)
|
||||
await render(hbs`
|
||||
<SecretEngine::ConfigureWif @backendPath={{this.id}} @displayName={{this.displayName}} @type='azure' @mountConfigModel={{this.mountConfigModel}} @issuerConfig={{this.issuerConfig}}/>
|
||||
`);
|
||||
await fillInAzureConfig('azure');
|
||||
await fillInAzureConfig();
|
||||
await click(SES.wif.accessType('wif'));
|
||||
await fillInAzureConfig('withWif');
|
||||
await fillInAzureConfig(true);
|
||||
await click(SES.wif.accessType('azure'));
|
||||
await click(GENERAL.toggleGroup('More options'));
|
||||
|
||||
assert
|
||||
.dom(GENERAL.toggleInput('Root password TTL'))
|
||||
.isNotChecked('rootPasswordTtl is cleared after toggling accessType');
|
||||
.isChecked('rootPasswordTtl is not cleared after toggling accessType');
|
||||
assert
|
||||
.dom(GENERAL.inputByAttr('clientSecret'))
|
||||
.hasValue('', 'clientSecret is cleared after toggling accessType');
|
||||
@@ -712,35 +716,49 @@ module('Integration | Component | SecretEngine::ConfigureWif', function (hooks)
|
||||
}
|
||||
|
||||
module('Azure specific', function (hooks) {
|
||||
// "clientSecret" is the only mutually exclusive Azure account attr and it's never returned from the API. Thus, we can only check for the presence of configured wif fields to determine if the accessType should be preselected to wif and disabled.
|
||||
hooks.beforeEach(function () {
|
||||
this.id = `azure-${this.uid}`;
|
||||
this.type = 'azure';
|
||||
this.mountConfigModel = createConfig(this.store, this.id, 'azure');
|
||||
});
|
||||
|
||||
test('it defaults to Azure accessType if Azure account fields are already set', async function (assert) {
|
||||
test('it allows you to change access type if no wif fields are set', async function (assert) {
|
||||
await render(hbs`
|
||||
<SecretEngine::ConfigureWif @backendPath={{this.id}} @displayName='Azure' @type='azure' @mountConfigModel={{this.mountConfigModel}} @issuerConfig={{this.issuerConfig}}/>
|
||||
<SecretEngine::ConfigureWif @backendPath={{this.id}} @displayName='Azure' @type={{this.type}} @mountConfigModel={{this.mountConfigModel}} @issuerConfig={{this.issuerConfig}}/>
|
||||
`);
|
||||
|
||||
assert.dom(SES.wif.accessType('azure')).isChecked('Azure accessType is checked');
|
||||
assert.dom(SES.wif.accessType('azure')).isDisabled('Azure accessType is disabled');
|
||||
assert
|
||||
.dom(SES.wif.accessType('azure'))
|
||||
.isNotDisabled(
|
||||
'Azure accessType is not disabled because we cannot determine if client secret was set as it is not returned by the api.'
|
||||
);
|
||||
assert.dom(SES.wif.accessType('wif')).isNotChecked('WIF accessType is not checked');
|
||||
assert.dom(SES.wif.accessType('wif')).isDisabled('WIF accessType is disabled');
|
||||
assert.dom(SES.wif.accessType('wif')).isNotDisabled('WIF accessType is disabled');
|
||||
assert
|
||||
.dom(SES.wif.accessTypeSubtext)
|
||||
.hasText(
|
||||
'Choose the way to configure access to Azure. Access can be configured either using Azure account credentials or with the Plugin Workload Identity Federation (WIF).'
|
||||
);
|
||||
});
|
||||
|
||||
test('it sets access type to wif if wif fields are set', async function (assert) {
|
||||
this.mountConfigModel = createConfig(this.store, this.id, 'azure-wif');
|
||||
await render(hbs`
|
||||
<SecretEngine::ConfigureWif @backendPath={{this.id}} @displayName={{this.displayName}} @type={{this.type}} @mountConfigModel={{this.mountConfigModel}} @issuerConfig={{this.issuerConfig}}/>
|
||||
`);
|
||||
|
||||
assert.dom(SES.wif.accessType('wif')).isChecked('WIF accessType is checked');
|
||||
assert
|
||||
.dom(SES.wif.accessType('azure'))
|
||||
.isDisabled('Azure accessType IS disabled because wif attributes are set.');
|
||||
|
||||
assert
|
||||
.dom(SES.wif.accessTypeSubtext)
|
||||
.hasText('You cannot edit Access Type if you have already saved access credentials.');
|
||||
});
|
||||
|
||||
test('it allows you to change accessType if record does not have wif or azure values already set', async function (assert) {
|
||||
this.mountConfigModel = createConfig(this.store, this.id, 'azure-generic');
|
||||
await render(hbs`
|
||||
<SecretEngine::ConfigureWif @backendPath={{this.id}} @displayName='Azure' @type='azure' @mountConfigModel={{this.mountConfigModel}} @issuerConfig={{this.issuerConfig}}/>
|
||||
`);
|
||||
|
||||
assert.dom(SES.wif.accessType('wif')).isNotDisabled('WIF accessType is NOT disabled');
|
||||
assert.dom(SES.wif.accessType('azure')).isNotDisabled('Azure accessType is NOT disabled');
|
||||
});
|
||||
|
||||
test('it shows previously saved config information', async function (assert) {
|
||||
this.id = `azure-${this.uid}`;
|
||||
this.mountConfigModel = createConfig(this.store, this.id, 'azure-generic');
|
||||
@@ -847,7 +865,7 @@ module('Integration | Component | SecretEngine::ConfigureWif', function (hooks)
|
||||
});
|
||||
|
||||
module('GCP specific', function (hooks) {
|
||||
// GCP is unique in that "credentials" is the only mutually exclusive GCP account attr and it's never returned from the API. Thus, we can only check for the presence of configured wif fields to determine if the accessType should be preselected to wif and disabled.
|
||||
// "credentials" is the only mutually exclusive GCP account attr and it's never returned from the API. Thus, we can only check for the presence of configured wif fields to determine if the accessType should be preselected to wif and disabled.
|
||||
// If the user has configured the credentials field, the ui will not know until the user tries to save WIF fields. This is a limitation of the API and surfaced to the user in a descriptive API error.
|
||||
// We cover some of this workflow here and error testing in the gcp-configuration acceptance test.
|
||||
hooks.beforeEach(function () {
|
||||
@@ -921,21 +939,21 @@ module('Integration | Component | SecretEngine::ConfigureWif', function (hooks)
|
||||
// toggle grouped fields if it exists
|
||||
const toggleGroup = document.querySelector('[data-test-toggle-group]');
|
||||
toggleGroup ? await click(toggleGroup) : null;
|
||||
|
||||
for (const key of expectedConfigKeys(type, true)) {
|
||||
if (key === 'secretKey' || key === 'clientSecret' || key === 'credentials') return; // these keys are not returned by the API
|
||||
if (type === 'gcp') {
|
||||
// same issues noted in wif enterprise tests with how toggle.hbs passes in name vs how formField input passes in attr to data test selector
|
||||
if (key === 'configTtl') {
|
||||
assert
|
||||
.dom(GENERAL.ttl.input('Config TTL'))
|
||||
.hasValue('100', `${key} for ${type}: has the expected value set on the config`);
|
||||
}
|
||||
if (key === 'maxTtl') {
|
||||
assert
|
||||
.dom(GENERAL.ttl.input('Max TTL'))
|
||||
.hasValue('101', `${key} for ${type}: has the expected value set on the config`);
|
||||
}
|
||||
// same issues noted in wif enterprise tests with how toggle.hbs passes in name vs how formField input passes in attr to data test selector
|
||||
if (key === 'configTtl') {
|
||||
assert
|
||||
.dom(GENERAL.ttl.input('Config TTL'))
|
||||
.hasValue('100', `${key} for ${type}: has the expected value set on the config`);
|
||||
} else if (key === 'maxTtl') {
|
||||
assert
|
||||
.dom(GENERAL.ttl.input('Max TTL'))
|
||||
.hasValue('101', `${key} for ${type}: has the expected value set on the config`);
|
||||
} else if (key === 'rootPasswordTtl') {
|
||||
assert
|
||||
.dom(GENERAL.ttl.input('Root password TTL'))
|
||||
.hasValue('500', `${key} for ${type}: has the expected value set on the config`);
|
||||
} else {
|
||||
assert
|
||||
.dom(GENERAL.inputByAttr(key))
|
||||
|
||||
Reference in New Issue
Block a user