Skip to content

Commit dbd8a05

Browse files
Merge remote-tracking branch 'remotes/from/ce/main'
2 parents e79d414 + 31fb778 commit dbd8a05

26 files changed

Lines changed: 1635 additions & 318 deletions

File tree

changelog/_14001.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:feature
2+
**Secrets Sync UI**: Added Workload Identity Federation (WIF) support in the UI for AWS, Azure, and GCP sync destinations
3+
```

ui/app/forms/sync/aws-sm.ts

Lines changed: 68 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,63 +3,86 @@
33
* SPDX-License-Identifier: BUSL-1.1
44
*/
55

6-
import Form from 'vault/forms/form';
76
import FormField from 'vault/utils/forms/field';
87
import FormFieldGroup from 'vault/utils/forms/field-group';
9-
import { commonFields, getPayload } from './shared';
8+
import { regions } from 'vault/helpers/aws-regions';
9+
import { CredentialType, DestinationType } from 'sync/utils/constants';
10+
import CreateDestinationForm from './create-destination';
1011

1112
import type { SystemWriteSyncDestinationsAwsSmNameRequest } from '@hashicorp/vault-client-typescript';
1213

1314
type AwsSmFormData = SystemWriteSyncDestinationsAwsSmNameRequest & {
1415
name: string;
16+
credential_type: CredentialType;
1517
};
1618

17-
export default class AwsSmForm extends Form<AwsSmFormData> {
18-
formFieldGroups = [
19-
new FormFieldGroup('default', [
20-
commonFields.name,
21-
new FormField('region', 'string', {
22-
subText:
23-
'For AWS secrets manager, the name of the region must be supplied, something like “us-west-1.” If empty, Vault will use the AWS_REGION environment variable if configured.',
24-
editDisabled: true,
25-
}),
26-
new FormField('role_arn', 'string', {
27-
label: 'Role ARN',
28-
subText:
29-
'Specifies a role to assume when connecting to AWS. When assuming a role, Vault uses temporary STS credentials to authenticate.',
30-
}),
31-
new FormField('external_id', 'string', {
32-
label: 'External ID',
33-
subText:
34-
'Optional extra protection that must match the trust policy granting access to the AWS IAM role ARN. We recommend using a different random UUID per destination.',
35-
}),
36-
]),
37-
new FormFieldGroup('Credentials', [
38-
new FormField('access_key_id', 'string', {
39-
label: 'Access key ID',
40-
subText:
41-
'Access key ID to authenticate against the secrets manager. If empty, Vault will use the AWS_ACCESS_KEY_ID environment variable if configured.',
42-
sensitive: true,
43-
noCopy: true,
44-
}),
45-
new FormField('secret_access_key', 'string', {
46-
label: 'Secret access key',
47-
subText:
48-
'Secret access key to authenticate against the secrets manager. If empty, Vault will use the AWS_SECRET_ACCESS_KEY environment variable if configured.',
49-
sensitive: true,
50-
noCopy: true,
51-
}),
52-
]),
53-
new FormFieldGroup('Advanced configuration', [
54-
commonFields.granularity,
55-
commonFields.secretNameTemplate,
56-
commonFields.customTags,
57-
]),
58-
];
19+
export default class AwsSmForm extends CreateDestinationForm<AwsSmFormData> {
20+
get isAccountPluginConfigured() {
21+
return !!this.data.access_key_id;
22+
}
23+
24+
get isWifPluginConfigured() {
25+
const { identity_token_audience, identity_token_ttl, role_arn } = this.data;
26+
return !!identity_token_audience || !!identity_token_ttl || !!role_arn;
27+
}
28+
29+
accountCredentialGroup = new FormFieldGroup('IAM credentials', [
30+
new FormField('access_key_id', 'string', {
31+
label: 'Access key ID',
32+
subText:
33+
'Access key ID to authenticate against the secrets manager. If empty, Vault will use the AWS_ACCESS_KEY_ID environment variable if configured.',
34+
sensitive: true,
35+
noCopy: true,
36+
}),
37+
new FormField('secret_access_key', 'string', {
38+
label: 'Secret access key',
39+
subText:
40+
'Secret access key to authenticate against the secrets manager. If empty, Vault will use the AWS_SECRET_ACCESS_KEY environment variable if configured.',
41+
sensitive: true,
42+
noCopy: true,
43+
}),
44+
]);
45+
46+
get wifCredentialGroup() {
47+
return this.createWifCredentialGroup();
48+
}
49+
50+
get formFieldGroups() {
51+
const credentialGroup =
52+
this.credentialType === CredentialType.ACCOUNT ? this.accountCredentialGroup : this.wifCredentialGroup;
53+
return [
54+
new FormFieldGroup('Destination details', [
55+
this.commonFields.name,
56+
new FormField('region', 'string', {
57+
possibleValues: regions(),
58+
noDefault: true,
59+
subText:
60+
'For AWS secrets manager, the name of the region must be supplied, something like “us-west-1.” If empty, Vault will use the AWS_REGION environment variable if configured.',
61+
editDisabled: true,
62+
}),
63+
new FormField('role_arn', 'string', {
64+
label: 'Role ARN',
65+
subText:
66+
'Specifies a role to assume when connecting to AWS. When assuming a role, Vault uses temporary STS credentials to authenticate.',
67+
}),
68+
new FormField('external_id', 'string', {
69+
label: 'External ID',
70+
subText:
71+
'Optional extra protection that must match the trust policy granting access to the AWS IAM role ARN. We recommend using a different random UUID per destination.',
72+
}),
73+
]),
74+
credentialGroup,
75+
new FormFieldGroup('Advanced configuration', [
76+
this.commonFields.granularity,
77+
this.commonFields.secretNameTemplate,
78+
this.commonFields.customTags,
79+
]),
80+
];
81+
}
5982

6083
toJSON() {
6184
const formState = super.toJSON();
62-
const data = getPayload<AwsSmFormData>('aws-sm', this.data, this.isNew);
85+
const data = this.getPayload<AwsSmFormData>(DestinationType.AwsSm, this.data, this.isNew);
6386
return { ...formState, data };
6487
}
6588
}

ui/app/forms/sync/azure-kv.ts

Lines changed: 63 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,61 +3,81 @@
33
* SPDX-License-Identifier: BUSL-1.1
44
*/
55

6-
import Form from 'vault/forms/form';
76
import FormField from 'vault/utils/forms/field';
87
import FormFieldGroup from 'vault/utils/forms/field-group';
9-
import { commonFields, getPayload } from './shared';
8+
import { CredentialType, DestinationType } from 'sync/utils/constants';
109

1110
import type { SystemWriteSyncDestinationsAzureKvNameRequest } from '@hashicorp/vault-client-typescript';
11+
import CreateDestinationForm from './create-destination';
1212

1313
type AzureKvFormData = SystemWriteSyncDestinationsAzureKvNameRequest & {
1414
name: string;
15+
credential_type: CredentialType;
1516
};
1617

17-
export default class AzureKvForm extends Form<AzureKvFormData> {
18-
formFieldGroups = [
19-
new FormFieldGroup('default', [
20-
commonFields.name,
21-
new FormField('key_vault_uri', 'string', {
22-
label: 'Key Vault URI',
23-
subText:
24-
'URI of an existing Azure Key Vault instance. If empty, Vault will use the KEY_VAULT_URI environment variable if configured.',
25-
editDisabled: true,
26-
}),
27-
new FormField('tenant_id', 'string', {
28-
label: 'Tenant ID',
29-
subText:
30-
'ID of the target Azure tenant. If empty, Vault will use the AZURE_TENANT_ID environment variable if configured.',
31-
editDisabled: true,
32-
}),
33-
new FormField('cloud', 'string', {
34-
subText: 'Specifies a cloud for the client. The default is Azure Public Cloud.',
35-
editDisabled: true,
36-
}),
37-
new FormField('client_id', 'string', {
38-
label: 'Client ID',
39-
subText:
40-
'Client ID of an Azure app registration. If empty, Vault will use the AZURE_CLIENT_ID environment variable if configured.',
41-
}),
42-
]),
43-
new FormFieldGroup('Credentials', [
44-
new FormField('client_secret', 'string', {
45-
subText:
46-
'Client secret of an Azure app registration. If empty, Vault will use the AZURE_CLIENT_SECRET environment variable if configured.',
47-
sensitive: true,
48-
noCopy: true,
49-
}),
50-
]),
51-
new FormFieldGroup('Advanced configuration', [
52-
commonFields.granularity,
53-
commonFields.secretNameTemplate,
54-
commonFields.customTags,
55-
]),
56-
];
18+
export default class AzureKvForm extends CreateDestinationForm<AzureKvFormData> {
19+
// the "clientSecret" param is not checked because it's never returned by the API.
20+
// thus we can never say for sure if the account accessType has been configured so we always return false
21+
isAccountPluginConfigured = false;
22+
23+
get isWifPluginConfigured() {
24+
const { identity_token_audience, identity_token_ttl } = this.data;
25+
return !!identity_token_audience || !!identity_token_ttl;
26+
}
27+
28+
accountCredentialGroup = new FormFieldGroup('Client secret', [
29+
new FormField('client_secret', 'string', {
30+
subText:
31+
'Client secret of an Azure app registration. If empty, Vault will use the AZURE_CLIENT_SECRET environment variable if configured.',
32+
sensitive: true,
33+
noCopy: true,
34+
}),
35+
]);
36+
37+
get wifCredentialGroup() {
38+
return this.createWifCredentialGroup();
39+
}
40+
41+
get formFieldGroups() {
42+
const credentialGroup =
43+
this.credentialType === CredentialType.ACCOUNT ? this.accountCredentialGroup : this.wifCredentialGroup;
44+
return [
45+
new FormFieldGroup('Destination details', [
46+
this.commonFields.name,
47+
new FormField('key_vault_uri', 'string', {
48+
label: 'Key Vault URI',
49+
subText:
50+
'URI of an existing Azure Key Vault instance. If empty, Vault will use the KEY_VAULT_URI environment variable if configured.',
51+
editDisabled: true,
52+
}),
53+
new FormField('tenant_id', 'string', {
54+
label: 'Tenant ID',
55+
subText:
56+
'ID of the target Azure tenant. If empty, Vault will use the AZURE_TENANT_ID environment variable if configured.',
57+
editDisabled: true,
58+
}),
59+
new FormField('cloud', 'string', {
60+
subText: 'Specifies a cloud for the client. The default is Azure Public Cloud.',
61+
editDisabled: true,
62+
}),
63+
new FormField('client_id', 'string', {
64+
label: 'Client ID',
65+
subText:
66+
'Client ID of an Azure app registration. If empty, Vault will use the AZURE_CLIENT_ID environment variable if configured.',
67+
}),
68+
]),
69+
credentialGroup,
70+
new FormFieldGroup('Advanced configuration', [
71+
this.commonFields.granularity,
72+
this.commonFields.secretNameTemplate,
73+
this.commonFields.customTags,
74+
]),
75+
];
76+
}
5777

5878
toJSON() {
5979
const formState = super.toJSON();
60-
const data = getPayload<AzureKvFormData>('azure-kv', this.data, this.isNew);
80+
const data = this.getPayload<AzureKvFormData>(DestinationType.AzureKv, this.data, this.isNew);
6181
return { ...formState, data };
6282
}
6383
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* Copyright IBM Corp. 2016, 2025
3+
* SPDX-License-Identifier: BUSL-1.1
4+
*/
5+
6+
import Form from 'vault/forms/form';
7+
import FormField from 'vault/utils/forms/field';
8+
import FormFieldGroup from 'vault/utils/forms/field-group';
9+
import { findDestination } from 'core/helpers/sync-destinations';
10+
import { CredentialType, DestinationType } from 'sync/utils/constants';
11+
12+
import { tracked } from '@glimmer/tracking';
13+
14+
export const DEFAULT_IDENTITY_TOKEN_TTL = '3600s';
15+
16+
export default class CreateDestinationForm<T extends object> extends Form<T> {
17+
@tracked credentialType: CredentialType = CredentialType.ACCOUNT;
18+
19+
commonFields = {
20+
name: new FormField('name', 'string', {
21+
subText: 'Specifies the name for this destination.',
22+
editDisabled: true,
23+
}),
24+
25+
secretNameTemplate: new FormField('secret_name_template', 'string', {
26+
subText:
27+
'Go-template string that indicates how to format the secret name at the destination. The default template varies by destination type but is generally in the form of "vault-{{ .MountAccessor }}-{{ .SecretPath }}" e.g. "vault-kv_9a8f68ad-my-secret-1". Optional.',
28+
}),
29+
30+
granularity: new FormField('granularity', 'string', {
31+
editType: 'radio',
32+
label: 'Secret sync granularity',
33+
possibleValues: [
34+
{
35+
label: 'Secret path',
36+
subText: 'Sync entire secret contents as a single entry at the destination.',
37+
value: 'secret-path',
38+
},
39+
{
40+
label: 'Secret key',
41+
subText: 'Sync each key-value pair of secret data as a distinct entry at the destination.',
42+
helpText:
43+
'Only top-level keys will be synced and any nested or complex values will be encoded as a JSON string.',
44+
value: 'secret-key',
45+
},
46+
],
47+
}),
48+
49+
customTags: new FormField('custom_tags', 'object', {
50+
subText:
51+
'An optional set of informational key-value pairs added as additional metadata on secrets synced to this destination. Custom tags are merged with built-in tags.',
52+
editType: 'kv',
53+
}),
54+
};
55+
56+
getPayload<T>(type: DestinationType, data: T, isNew: boolean) {
57+
const { maskedParams, readonlyParams } = findDestination(type);
58+
const payload: T = { ...data };
59+
60+
// the server returns ****** for sensitive fields
61+
// these are represented as maskedParams in the sync-destinations helper
62+
// when editing, remove these fields from the payload if they haven't been changed
63+
if (!isNew) {
64+
maskedParams.forEach((maskedParam) => {
65+
const key = maskedParam as keyof T;
66+
const value = (payload[key] as string) || '';
67+
// if the value is asterisks, remove it from the payload
68+
if (value.match(/^\*+$/)) {
69+
delete payload[key];
70+
}
71+
});
72+
73+
// to preserve the original Ember Data payload structure, remove fields that are not editable
74+
// since editing is disabled in the form the value will not change so this is mostly to satisfy existing test conditions
75+
readonlyParams.forEach((readonlyParam) => {
76+
delete payload[readonlyParam as keyof T];
77+
});
78+
}
79+
80+
return payload;
81+
}
82+
83+
protected createWifCredentialGroup(additionalFields: FormField[] = []): FormFieldGroup {
84+
const commonFields = [
85+
new FormField('identity_token_audience', 'string', {
86+
label: 'Identity token audience',
87+
sensitive: true,
88+
noCopy: true,
89+
}),
90+
new FormField('identity_token_key', 'string', {
91+
label: 'Identity token key',
92+
sensitive: true,
93+
noCopy: true,
94+
}),
95+
new FormField('identity_token_ttl', 'string', {
96+
label: 'Identity token time to live (TTL)',
97+
editType: 'ttl',
98+
helperTextEnabled: 'The TTL of generated tokens.',
99+
hideToggle: true,
100+
}),
101+
];
102+
return new FormFieldGroup('WIF credentials', [...additionalFields, ...commonFields]);
103+
}
104+
}

0 commit comments

Comments
 (0)