Skip to content

Commit 1211b75

Browse files
hc-github-team-secure-vault-corezofskeezlane-wetmore
authored
Backport [UI] Ember Data Migration: KMIP Config into ce/main (hashicorp#11676)
* [UI] Ember Data Migration: KMIP Config (hashicorp#11637) * converts kmip configuration route to ts * adds ts and template lint deps to kmip engine * adds api service to kmip engine * updates kmip configuration route to use api service * converts kmip configuration template to page component * converts kmip configure route to ts * updates kmip configure workflow to use api service and form class * enables ts transform in kmip engine * fixes a11y violations on kmip configuration page * Update ui/app/forms/secrets/kmip/config.ts Co-authored-by: lane-wetmore <lane.wetmore@hashicorp.com> --------- Co-authored-by: lane-wetmore <lane.wetmore@hashicorp.com> * fixes lint error --------- Co-authored-by: Jordan Reimer <zofskeez@gmail.com> Co-authored-by: lane-wetmore <lane.wetmore@hashicorp.com> Co-authored-by: Jordan Reimer <jordan.reimer@hashicorp.com>
1 parent 75d8785 commit 1211b75

File tree

20 files changed

+493
-115
lines changed

20 files changed

+493
-115
lines changed

ui/app/app.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export default class App extends Application {
5454
kmip: {
5555
dependencies: {
5656
services: [
57+
'api',
5758
'auth',
5859
'download',
5960
'flash-messages',
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Copyright IBM Corp. 2016, 2025
3+
* SPDX-License-Identifier: BUSL-1.1
4+
*/
5+
6+
import OpenApiForm from 'vault/forms/open-api';
7+
8+
import type { KmipConfigureRequest } from '@hashicorp/vault-client-typescript';
9+
import type Form from 'vault/forms/form';
10+
import type FormField from 'vault/utils/forms/field';
11+
12+
export default class KmipConfigForm extends OpenApiForm<KmipConfigureRequest> {
13+
constructor(...args: ConstructorParameters<typeof Form>) {
14+
super('KmipConfigureRequest', ...args);
15+
16+
const orderedKeys = [
17+
'listen_addrs',
18+
'default_tls_client_key_bits',
19+
'default_tls_client_key_type',
20+
'default_tls_client_ttl',
21+
'server_hostnames',
22+
'server_ips',
23+
'tls_ca_key_bits',
24+
'tls_ca_key_type',
25+
'tls_min_version',
26+
];
27+
28+
this.formFields = orderedKeys.map((key) => this.formFields.find((f) => f.name === key) as FormField);
29+
}
30+
}

ui/lib/kmip/addon/components/header-scope.hbs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@
1212
<div class="tabs-container box is-sideless is-fullwidth is-paddingless is-marginless">
1313
<nav class="tabs">
1414
<ul>
15-
<LinkTo @route="scopes.index" data-test-kmip-link-scopes="true">
16-
Scopes
17-
</LinkTo>
18-
<LinkTo @route="configuration" data-test-kmip-link-config="true">
19-
Configuration
20-
</LinkTo>
15+
<li>
16+
<LinkTo @route="scopes.index" data-test-kmip-tab="scopes">
17+
Scopes
18+
</LinkTo>
19+
</li>
20+
<li>
21+
<LinkTo @route="configuration" data-test-kmip-tab="config">
22+
Configuration
23+
</LinkTo>
24+
</li>
2125
</ul>
2226
</nav>
2327
</div>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{{!
2+
Copyright IBM Corp. 2016, 2025
3+
SPDX-License-Identifier: BUSL-1.1
4+
}}
5+
6+
<HeaderScope />
7+
8+
<Toolbar>
9+
<ToolbarActions>
10+
{{#if @config.ca_pem}}
11+
<DownloadButton
12+
class="toolbar-button"
13+
@color="secondary"
14+
@filename={{concat this.secretMountPath.currentPath "-ca"}}
15+
@data={{@config.ca_pem}}
16+
@extension="pem"
17+
@text="Download CA cert"
18+
data-test-kmip-toolbar-action="download"
19+
/>
20+
{{/if}}
21+
<ToolbarLink @route="configure" data-test-kmip-toolbar-action="config">
22+
Configure
23+
</ToolbarLink>
24+
</ToolbarActions>
25+
</Toolbar>
26+
27+
{{#if @config}}
28+
{{#each this.displayFields as |field|}}
29+
<InfoTableRow
30+
@label={{field.label}}
31+
@value={{get @config field.key}}
32+
@formatTtl={{eq field.key "default_tls_client_ttl"}}
33+
/>
34+
{{/each}}
35+
<InfoTableRow @label="CA PEM">
36+
<MaskedInput @value={{@config.ca_pem}} @displayOnly={{true}} @allowCopy={{true}} />
37+
</InfoTableRow>
38+
{{else}}
39+
<EmptyState
40+
@title="No configuration for this secrets engine"
41+
@message="We'll need to configure a few things before getting started."
42+
/>
43+
{{/if}}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Copyright IBM Corp. 2016, 2025
3+
* SPDX-License-Identifier: BUSL-1.1
4+
*/
5+
6+
import Component from '@glimmer/component';
7+
import { service } from '@ember/service';
8+
9+
import type SecretMountPath from 'vault/services/secret-mount-path';
10+
11+
export default class KmipConfigurationPageComponent extends Component {
12+
@service declare readonly secretMountPath: SecretMountPath;
13+
14+
displayFields = [
15+
{ key: 'listen_addrs', label: 'Listen addresses' },
16+
{ key: 'default_tls_client_key_bits', label: 'Default TLS client key bits' },
17+
{ key: 'default_tls_client_key_type', label: 'Default TLS client key type' },
18+
{ key: 'default_tls_client_ttl', label: 'Default TLS client TTL' },
19+
{ key: 'server_hostnames', label: 'Server hostnames' },
20+
{ key: 'server_ips', label: 'Server IPs' },
21+
{ key: 'tls_ca_key_bits', label: 'TLS CA key bits' },
22+
{ key: 'tls_ca_key_type', label: 'TLS CA key type' },
23+
{ key: 'tls_min_version', label: 'Minimum TLS version' },
24+
];
25+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{{!
2+
Copyright IBM Corp. 2016, 2025
3+
SPDX-License-Identifier: BUSL-1.1
4+
}}
5+
6+
<Page::Header @title="Configure KMIP Secrets Engine" @icon="lock">
7+
<:breadcrumbs>
8+
<KmipBreadcrumb @showPath={{true}} @currentRoute="Configure" />
9+
</:breadcrumbs>
10+
</Page::Header>
11+
12+
<form {{on "submit" (perform this.save)}}>
13+
<div class="box is-sideless is-fullwidth is-marginless">
14+
<MessageError @errorMessage={{this.errorMessage}} />
15+
<NamespaceReminder @mode="save" />
16+
17+
{{#each @form.formFields as |field|}}
18+
<FormField @attr={{field}} @model={{@form}} @modelValidations={{this.modelValidations}} />
19+
{{/each}}
20+
</div>
21+
22+
<FormSaveButtons @isSaving={{this.save.isRunning}} @cancelLinkParams={{array "configuration"}} @onCancel={{@onCancel}}>
23+
<:error>
24+
{{#if this.invalidFormAlert}}
25+
<AlertInline @type="danger" class="has-top-padding-s" @message={{this.invalidFormAlert}} />
26+
{{/if}}
27+
</:error>
28+
</FormSaveButtons>
29+
</form>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Copyright IBM Corp. 2016, 2025
3+
* SPDX-License-Identifier: BUSL-1.1
4+
*/
5+
import Component from '@glimmer/component';
6+
import { service } from '@ember/service';
7+
import { tracked } from '@glimmer/tracking';
8+
import { task } from 'ember-concurrency';
9+
import { waitFor } from '@ember/test-waiters';
10+
11+
import type SecretMountPath from 'vault/services/secret-mount-path';
12+
import type ApiService from 'vault/services/api';
13+
import type RouterService from '@ember/routing/router-service';
14+
import type FlashMessageService from 'vault/services/flash-messages';
15+
import type { ValidationMap } from 'vault/app-types';
16+
import type { HTMLElementEvent } from 'vault/forms';
17+
import type KmipConfigForm from 'vault/forms/secrets/kmip/config';
18+
19+
interface Args {
20+
form: KmipConfigForm;
21+
}
22+
23+
export default class KmipConfigurePageComponent extends Component<Args> {
24+
@service declare readonly secretMountPath: SecretMountPath;
25+
@service declare readonly api: ApiService;
26+
@service('app-router') declare readonly router: RouterService;
27+
@service declare readonly flashMessages: FlashMessageService;
28+
29+
@tracked modelValidations: ValidationMap | null = null;
30+
@tracked invalidFormAlert: string | null = null;
31+
@tracked errorMessage: string | null = null;
32+
33+
save = task(
34+
waitFor(async (event: HTMLElementEvent<HTMLFormElement>) => {
35+
event.preventDefault();
36+
this.errorMessage = null;
37+
38+
try {
39+
const { isValid, state, invalidFormMessage, data } = this.args.form.toJSON();
40+
this.modelValidations = isValid ? null : state;
41+
this.invalidFormAlert = isValid ? '' : invalidFormMessage;
42+
43+
if (isValid) {
44+
await this.api.secrets.kmipConfigure(this.secretMountPath.currentPath, data);
45+
this.flashMessages.success('Successfully configured KMIP engine');
46+
this.router.transitionTo('vault.cluster.secrets.backend.kmip.configuration');
47+
}
48+
} catch (error) {
49+
const { message } = await this.api.parseError(error);
50+
this.errorMessage = message;
51+
this.invalidFormAlert = 'There was an error submitting this form.';
52+
}
53+
})
54+
);
55+
}

ui/lib/kmip/addon/engine.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default class KmipEngine extends Engine {
1414
Resolver = Resolver;
1515
dependencies = {
1616
services: [
17+
'api',
1718
'auth',
1819
'download',
1920
'flash-messages',

ui/lib/kmip/addon/routes/configuration.js

Lines changed: 0 additions & 35 deletions
This file was deleted.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Copyright IBM Corp. 2016, 2025
3+
* SPDX-License-Identifier: BUSL-1.1
4+
*/
5+
6+
import Route from '@ember/routing/route';
7+
import { service } from '@ember/service';
8+
9+
import type ApiService from 'vault/services/api';
10+
import type SecretMountPath from 'vault/services/secret-mount-path';
11+
12+
export default class KmipConfigurationRoute extends Route {
13+
@service declare readonly api: ApiService;
14+
@service declare readonly secretMountPath: SecretMountPath;
15+
16+
async model() {
17+
try {
18+
const { currentPath } = this.secretMountPath;
19+
const { data } = await this.api.secrets.kmipReadConfiguration(currentPath);
20+
const config = data as Record<string, unknown>;
21+
22+
try {
23+
const { data } = await this.api.secrets.kmipReadCaPem(currentPath);
24+
const ca = data as Record<string, unknown>;
25+
return { ...config, ...ca };
26+
} catch (error) {
27+
// ignore error if CA PEM is not found
28+
// component will conditionally render the field if present
29+
}
30+
return config;
31+
} catch (error) {
32+
const { status } = await this.api.parseError(error);
33+
if (status !== 404) {
34+
throw error;
35+
}
36+
return undefined;
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)