mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-20 22:21:09 +02:00
* manual cherry pick to deal with all the merge things * changelog * test fixes * Update 28148.txt * fix tests failures after main merge * fix test failures after main merge * Add Access Type and conditionally render WIF fields (#28149) * initial work. * remove access_type * better no model logic well kind of * rollback attrs * remove defaults * stopping point * wip changing back to sidebranch * hustling shuffling and serializing * some of the component test coverage * disable acces type if editing * test coverage * hide max retries that sneaky bugger * cleanup * cleanup * Update root-config.js * remove flash message check, locally passes great but on ci flaky * clean up * thank you chelsea * test clean up per enterprise vs community * address pr comments * welp a miss add * UI (sidebranch) WIF Issuer field (#28187) * Add type declaration files for aws config models * use updated task syntax for save method on configure-aws * fix types on edit route * fetch issuer on configure edit page if aws + enterprise * track issuer within configure-aws component * add placeholder support on form-field * Add warning if issuer changed from previous value or could not be read * cleanup * preliminary tests * dont use while loop so we can test the modal * tests * cleanup * fix tests * remove extra tracked value and duplicate changed attrs check * modal footer --------- Co-authored-by: Angel Garbarino <argarbarino@gmail.com> * Display issuer on Configuration details (#28209) * display issuer on configuration details * workflow complete, now on to testing * handle issuer things * fix all the broken tests things * add test coveragE: * cleanup * rename model/adapter * Update configure-aws.ts * Update aws-configuration-test.js * 90 percent there for pr comments * last one for tonight * a few more because why not * hasDirtyAttributes fixes * revert back to previous noRead->queryIssuerError --------- Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>
233 lines
8.7 KiB
TypeScript
233 lines
8.7 KiB
TypeScript
/**
|
|
* Copyright (c) HashiCorp, Inc.
|
|
* SPDX-License-Identifier: BUSL-1.1
|
|
*/
|
|
|
|
import Component from '@glimmer/component';
|
|
import { action } from '@ember/object';
|
|
import { task } from 'ember-concurrency';
|
|
import { waitFor } from '@ember/test-waiters';
|
|
import { service } from '@ember/service';
|
|
import { tracked } from '@glimmer/tracking';
|
|
import { ValidationMap } from 'vault/vault/app-types';
|
|
import errorMessage from 'vault/utils/error-message';
|
|
|
|
import type LeaseConfigModel from 'vault/models/aws/lease-config';
|
|
import type RootConfigModel from 'vault/models/aws/root-config';
|
|
import type IdentityOidcConfigModel from 'vault/models/identity/oidc/config';
|
|
import type Router from '@ember/routing/router';
|
|
import type StoreService from 'vault/services/store';
|
|
import type VersionService from 'vault/services/version';
|
|
import type FlashMessageService from 'vault/services/flash-messages';
|
|
|
|
/**
|
|
* @module ConfigureAwsComponent is used to configure the AWS secret engine
|
|
* A user can configure the endpoint root/config and/or lease/config.
|
|
* For enterprise users, they will see an additional option to config WIF attributes in place of IAM attributes.
|
|
* The fields for these endpoints are on one form.
|
|
*
|
|
* @example
|
|
* ```js
|
|
* <SecretEngine::ConfigureAws
|
|
@rootConfig={{this.model.aws-root-config}}
|
|
@leaseConfig={{this.model.aws-lease-config}}
|
|
@backendPath={{this.model.id}}
|
|
/>
|
|
* ```
|
|
*
|
|
* @param {object} rootConfig - AWS config/root model
|
|
* @param {object} leaseConfig - AWS config/lease model
|
|
* @param {string} backendPath - name of the AWS secret engine, ex: 'aws-123'
|
|
*/
|
|
|
|
interface Args {
|
|
leaseConfig: LeaseConfigModel;
|
|
rootConfig: RootConfigModel;
|
|
issuerConfig: IdentityOidcConfigModel;
|
|
backendPath: string;
|
|
issuer?: string;
|
|
}
|
|
|
|
export default class ConfigureAwsComponent extends Component<Args> {
|
|
@service declare readonly router: Router;
|
|
@service declare readonly store: StoreService;
|
|
@service declare readonly version: VersionService;
|
|
@service declare readonly flashMessages: FlashMessageService;
|
|
|
|
@tracked errorMessageRoot: string | null = null;
|
|
@tracked errorMessageLease: string | null = null;
|
|
@tracked invalidFormAlert: string | null = null;
|
|
@tracked modelValidationsLease: ValidationMap | null = null;
|
|
@tracked accessType = 'iam';
|
|
@tracked saveIssuerWarning = '';
|
|
|
|
disableAccessType = false;
|
|
|
|
constructor(owner: unknown, args: Args) {
|
|
super(owner, args);
|
|
// the following checks are only relevant to enterprise users and those editing an existing root configuration.
|
|
if (this.version.isCommunity || this.args.rootConfig.isNew) return;
|
|
const { roleArn, identityTokenAudience, identityTokenTtl, accessKey } = this.args.rootConfig;
|
|
// do not include issuer in this check. Issuer is a global endpoint and can bet set even if we're not editing wif attributes
|
|
const wifAttributesSet = !!roleArn || !!identityTokenAudience || !!identityTokenTtl;
|
|
const iamAttributesSet = !!accessKey;
|
|
// If any WIF attributes have been set in the rootConfig model, set accessType to 'wif'.
|
|
this.accessType = wifAttributesSet ? 'wif' : 'iam';
|
|
// If there are either WIF or IAM attributes set then disable user's ability to change accessType.
|
|
this.disableAccessType = wifAttributesSet || iamAttributesSet;
|
|
}
|
|
|
|
@action continueSubmitForm() {
|
|
// called when the user confirms they are okay with the issuer change
|
|
this.saveIssuerWarning = '';
|
|
this.save.perform();
|
|
}
|
|
|
|
// on form submit - validate inputs and check for issuer changes
|
|
submitForm = task(
|
|
waitFor(async (event: Event) => {
|
|
event?.preventDefault();
|
|
this.resetErrors();
|
|
const { leaseConfig, issuerConfig } = this.args;
|
|
// Note: only aws/lease-config model has validations
|
|
const isValid = this.validate(leaseConfig);
|
|
if (!isValid) return;
|
|
if (issuerConfig?.hasDirtyAttributes) {
|
|
// if the issuer has changed show modal with warning that the config will change
|
|
// if the modal is shown, the user has to click confirm to continue save
|
|
this.saveIssuerWarning = `You are updating the global issuer config. This will overwrite Vault's current issuer ${
|
|
issuerConfig.queryIssuerError ? 'if it exists ' : ''
|
|
}and may affect other configurations using this value. Continue?`;
|
|
// exit task until user confirms
|
|
return;
|
|
}
|
|
await this.save.perform();
|
|
})
|
|
);
|
|
|
|
save = task(
|
|
waitFor(async () => {
|
|
// when we get here, the models have already been validated so just continue with save
|
|
const { leaseConfig, rootConfig, issuerConfig } = this.args;
|
|
// Check if any of the models' attributes have changed.
|
|
// If no changes to either model, transition and notify user.
|
|
// If changes to either model, save the model(s) that changed and notify user.
|
|
// Note: "backend" dirties model state so explicity ignore it here.
|
|
const leaseAttrChanged = Object.keys(leaseConfig?.changedAttributes()).some(
|
|
(item) => item !== 'backend'
|
|
);
|
|
const rootAttrChanged = Object.keys(rootConfig?.changedAttributes()).some((item) => item !== 'backend');
|
|
const issuerAttrChanged = issuerConfig?.hasDirtyAttributes;
|
|
if (!leaseAttrChanged && !rootAttrChanged && !issuerAttrChanged) {
|
|
this.flashMessages.info('No changes detected.');
|
|
this.transition();
|
|
return;
|
|
}
|
|
// Attempt saves of changed models. If at least one of them succeed, transition
|
|
const rootSaved = rootAttrChanged ? await this.saveRoot() : false;
|
|
const leaseSaved = leaseAttrChanged ? await this.saveLease() : false;
|
|
const issuerSaved = issuerAttrChanged ? await this.updateIssuer() : false;
|
|
|
|
if (rootSaved || leaseSaved || issuerSaved) {
|
|
this.transition();
|
|
} else {
|
|
// otherwise there was a failure and we should not transition and exit the function.
|
|
return;
|
|
}
|
|
})
|
|
);
|
|
|
|
async updateIssuer(): Promise<boolean> {
|
|
try {
|
|
await this.args.issuerConfig.save();
|
|
this.flashMessages.success('Issuer saved successfully');
|
|
return true;
|
|
} catch (e) {
|
|
this.flashMessages.danger(`Issuer was not saved: ${errorMessage(e, 'Check Vault logs for details.')}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async saveRoot(): Promise<boolean> {
|
|
const { backendPath, rootConfig } = this.args;
|
|
try {
|
|
await rootConfig.save();
|
|
this.flashMessages.success(`Successfully saved ${backendPath}'s root configuration.`);
|
|
return true;
|
|
} catch (error) {
|
|
this.errorMessageRoot = errorMessage(error);
|
|
this.invalidFormAlert = 'There was an error submitting this form.';
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async saveLease(): Promise<boolean> {
|
|
const { backendPath, leaseConfig } = this.args;
|
|
try {
|
|
await leaseConfig.save();
|
|
this.flashMessages.success(`Successfully saved ${backendPath}'s lease configuration.`);
|
|
return true;
|
|
} catch (error) {
|
|
// if lease config fails, but there was no error saving rootConfig: notify user of the lease failure with a flash message, save the root config, and transition.
|
|
if (!this.errorMessageRoot) {
|
|
this.flashMessages.danger(`Lease configuration was not saved: ${errorMessage(error)}`, {
|
|
sticky: true,
|
|
});
|
|
return true;
|
|
} else {
|
|
this.errorMessageLease = errorMessage(error);
|
|
this.flashMessages.danger(
|
|
`Configuration not saved: ${errorMessage(error)}. ${this.errorMessageRoot}`
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
resetErrors() {
|
|
this.flashMessages.clearMessages();
|
|
this.errorMessageRoot = null;
|
|
this.invalidFormAlert = null;
|
|
}
|
|
|
|
transition() {
|
|
this.router.transitionTo('vault.cluster.secrets.backend.configuration', this.args.backendPath);
|
|
}
|
|
|
|
validate(model: LeaseConfigModel) {
|
|
const { isValid, state, invalidFormMessage } = model.validate();
|
|
this.modelValidationsLease = isValid ? null : state;
|
|
this.invalidFormAlert = isValid ? '' : invalidFormMessage;
|
|
return isValid;
|
|
}
|
|
|
|
unloadModels() {
|
|
this.args.rootConfig.unloadRecord();
|
|
this.args.leaseConfig.unloadRecord();
|
|
}
|
|
|
|
@action
|
|
onChangeAccessType(accessType: string) {
|
|
this.accessType = accessType;
|
|
const { rootConfig } = this.args;
|
|
if (accessType === 'iam') {
|
|
// reset all WIF attributes
|
|
rootConfig.roleArn = rootConfig.identityTokenAudience = rootConfig.identityTokenTtl = undefined;
|
|
// for the issuer return to the globally set value (if there is one) on toggle
|
|
this.args.issuerConfig.rollbackAttributes();
|
|
}
|
|
if (accessType === 'wif') {
|
|
// reset all IAM attributes
|
|
rootConfig.accessKey = rootConfig.secretKey = undefined;
|
|
}
|
|
}
|
|
|
|
@action
|
|
onCancel() {
|
|
// clear errors because they're canceling out of the workflow.
|
|
this.resetErrors();
|
|
this.unloadModels();
|
|
this.transition();
|
|
}
|
|
}
|