Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into sgm/mldsa-ed25519-ce
Browse files Browse the repository at this point in the history
  • Loading branch information
sgmiller committed Jan 3, 2025
2 parents 19d9604 + ecf8d0b commit 6dd5471
Show file tree
Hide file tree
Showing 26 changed files with 545 additions and 758 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ for write requests as a GA feature (enabled by default) for Integrated Storage.
* **Audit Entry Exclusion (enterprise)**: Audit devices support excluding fields from entries being written to them, with expression-based rules (powered by go-bexpr) to determine when the specific fields are excluded.
* **Workload Identity Federation UI for AWS (enterprise)**: Add WIF fields to AWS secrets engine. [[GH-28148](https://github.com/hashicorp/vault/pull/28148)]
* **KV v2 Patch/Subkey (enterprise)**: Adds GUI support to read the subkeys of a KV v2 secret and patch (partially update) secret data. [[GH-28212](https://github.com/hashicorp/vault/pull/28212)]
* **Self-Managed Static Roles**: Self-Managed Static Roles are now supported for select SQL database engines (Postgres, Oracle). Requires Vault Enterprise. [[GH-28199](https://github.com/hashicorp/vault/pull/28199)]
* **Self-Managed Static Roles**: Self-Managed Static Roles are now supported for the Postgres SQL database engine. Requires Vault Enterprise. [[GH-28199](https://github.com/hashicorp/vault/pull/28199)]
* **Vault Minimal Version**: Add the ability to build a minimal version of Vault
with only core features using the BUILD_MINIMAL environment variable. [[GH-27394](https://github.com/hashicorp/vault/pull/27394)]
* **Vault PKI 3GPP CMPv2 Server (Enterprise)**: Support for the PKI 3GPP CMPv2 certificate management protocol has been added to the Vault PKI Plugin. This allows standard CMPv2 clients to request certificates from a Vault server with no knowledge of Vault APIs.
Expand Down
2 changes: 1 addition & 1 deletion command/command_testonly/operator_usage_testonly_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestOperatorUsageCommandRun(t *testing.T) {

now := time.Now().UTC()

_, _, err = clientcountutil.NewActivityLogData(client).
_, err = clientcountutil.NewActivityLogData(client).
NewPreviousMonthData(1).
NewClientsSeen(6, clientcountutil.WithClientType("entity")).
NewClientsSeen(4, clientcountutil.WithClientType("non-entity-token")).
Expand Down
23 changes: 7 additions & 16 deletions sdk/helper/clientcountutil/clientcountutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,42 +282,33 @@ func (d *ActivityLogDataGenerator) ToProto() *generation.ActivityLogMockInput {
// Write writes the data to the API with the given write options. The method
// returns the new paths that have been written. Note that the API endpoint will
// only be present when Vault has been compiled with the "testonly" flag.
func (d *ActivityLogDataGenerator) Write(ctx context.Context, writeOptions ...generation.WriteOptions) ([]string, []string, error) {
func (d *ActivityLogDataGenerator) Write(ctx context.Context, writeOptions ...generation.WriteOptions) ([]string, error) {
d.data.Write = writeOptions
err := VerifyInput(d.data)
if err != nil {
return nil, nil, err
return nil, err
}
data, err := d.ToJSON()
if err != nil {
return nil, nil, err
return nil, err
}
resp, err := d.client.Logical().WriteWithContext(ctx, "sys/internal/counters/activity/write", map[string]interface{}{"input": string(data)})
if err != nil {
return nil, nil, err
return nil, err
}
if resp.Data == nil {
return nil, nil, fmt.Errorf("received no data")
return nil, fmt.Errorf("received no data")
}
paths := resp.Data["paths"]
castedPaths, ok := paths.([]interface{})
if !ok {
return nil, nil, fmt.Errorf("invalid paths data: %v", paths)
return nil, fmt.Errorf("invalid paths data: %v", paths)
}
returnPaths := make([]string, 0, len(castedPaths))
for _, path := range castedPaths {
returnPaths = append(returnPaths, path.(string))
}
globalPaths := resp.Data["global_paths"]
globalCastedPaths, ok := globalPaths.([]interface{})
if !ok {
return nil, nil, fmt.Errorf("invalid global paths data: %v", globalPaths)
}
returnGlobalPaths := make([]string, 0, len(globalCastedPaths))
for _, path := range globalCastedPaths {
returnGlobalPaths = append(returnGlobalPaths, path.(string))
}
return returnPaths, returnGlobalPaths, nil
return returnPaths, nil
}

// VerifyInput checks that the input data is valid
Expand Down
5 changes: 2 additions & 3 deletions sdk/helper/clientcountutil/clientcountutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func TestNewCurrentMonthData_AddClients(t *testing.T) {
// sent to the server is correct.
func TestWrite(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := io.WriteString(w, `{"data":{"paths":["path1","path2"],"global_paths":["path2","path3"]}}`)
_, err := io.WriteString(w, `{"data":{"paths":["path1","path2"]}}`)
require.NoError(t, err)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
Expand All @@ -131,7 +131,7 @@ func TestWrite(t *testing.T) {
Address: ts.URL,
})
require.NoError(t, err)
paths, globalPaths, err := NewActivityLogData(client).
paths, err := NewActivityLogData(client).
NewPreviousMonthData(3).
NewClientSeen().
NewPreviousMonthData(2).
Expand All @@ -141,7 +141,6 @@ func TestWrite(t *testing.T) {

require.NoError(t, err)
require.Equal(t, []string{"path1", "path2"}, paths)
require.Equal(t, []string{"path2", "path3"}, globalPaths)
}

func testAddClients(t *testing.T, makeGenerator func() *ActivityLogDataGenerator, getClient func(data *ActivityLogDataGenerator) *generation.Client) {
Expand Down
26 changes: 26 additions & 0 deletions ui/app/adapters/gcp/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/

import ApplicationAdapter from '../application';
import { encodePath } from 'vault/utils/path-encoding-helpers';

export default class GcpConfig extends ApplicationAdapter {
namespace = 'v1';

_url(backend) {
return `${this.buildURL()}/${encodePath(backend)}/config`;
}

queryRecord(store, type, query) {
const { backend } = query;
return this.ajax(this._url(backend), 'GET').then((resp) => {
return {
...resp,
id: backend,
backend,
};
});
}
}
17 changes: 10 additions & 7 deletions ui/app/components/secret-engine/configuration-details.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@
@title="{{@typeDisplay}} not configured"
@message="Get started by configuring your {{@typeDisplay}} secrets engine."
>
<Hds::Link::Standalone
@icon="chevron-right"
@iconPosition="trailing"
@text="Configure {{@typeDisplay}}"
@route="vault.cluster.secrets.backend.configuration.edit"
@model={{@id}}
/>
{{! TODO: short-term conditional to be removed once configuration for gcp is merged. }}
{{#unless (eq @typeDisplay "Google Cloud")}}
<Hds::Link::Standalone
@icon="chevron-right"
@iconPosition="trailing"
@text="Configure {{@typeDisplay}}"
@route="vault.cluster.secrets.backend.configuration.edit"
@model={{@id}}
/>
{{/unless}}
</EmptyState>
{{/each}}
7 changes: 3 additions & 4 deletions ui/app/helpers/mountable-secret-engines.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,15 @@ export function wifEngines() {
}

// The UI only supports configuration views for these secrets engines. The CLI must be used to manage other engine resources (i.e. roles, credentials).
// Will eventually include gcp.
export const CONFIGURATION_ONLY = ['azure'];
export const CONFIGURATION_ONLY = ['azure', 'gcp'];

export function configurationOnly() {
return CONFIGURATION_ONLY.slice();
}

// Secret engines that have their own configuration page and actions
// These engines do not exist in their own Ember engine.
export const CONFIGURABLE_SECRET_ENGINES = ['aws', 'azure', 'ssh'];
export const CONFIGURABLE_SECRET_ENGINES = ['aws', 'azure', 'gcp', 'ssh'];

export function configurableSecretEngines() {
return CONFIGURABLE_SECRET_ENGINES.slice();
Expand All @@ -161,7 +160,7 @@ export function mountableEngines() {
return MOUNTABLE_SECRET_ENGINES.slice();
}
// secret engines that have not other views than the mount view and mount details view
export const UNSUPPORTED_ENGINES = ['alicloud', 'consul', 'gcp', 'gcpkms', 'nomad', 'rabbitmq', 'totp'];
export const UNSUPPORTED_ENGINES = ['alicloud', 'consul', 'gcpkms', 'nomad', 'rabbitmq', 'totp'];

export function unsupportedEngines() {
return UNSUPPORTED_ENGINES.slice();
Expand Down
1 change: 1 addition & 0 deletions ui/app/helpers/supported-secret-backends.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const SUPPORTED_SECRET_BACKENDS = [
'azure',
'cubbyhole',
'database',
'gcp',
'generic',
'keymgmt',
'kmip',
Expand Down
71 changes: 71 additions & 0 deletions ui/app/models/gcp/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/

import Model, { attr } from '@ember-data/model';
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';

export default class GcpConfig extends Model {
@attr('string') backend; // dynamic path of secret -- set on response from value passed to queryRecord

/* GCP config fields */
@attr({
label: 'Config TTL',
editType: 'ttl',
helperTextDisabled: 'The TTL (time-to-live) of generated tokens.',
})
ttl;

@attr({
label: 'Max TTL',
editType: 'ttl',
helperTextDisabled:
'Specifies the maximum config TTL (time-to-live) for long-lived credentials (i.e. service account keys).',
})
maxTtl;

@attr('string', {
label: 'JSON credentials',
subText:
'If empty, Vault will use the GOOGLE_APPLICATION_CREDENTIALS environment variable if configured.',
editType: 'file',
docLink: '/vault/docs/secrets/gcp#authentication',
})
credentials; // obfuscated, never returned by API.

/* WIF config fields */
@attr('string', {
subText:
'The audience claim value for plugin identity tokens. Must match an allowed audience configured for the target IAM OIDC identity provider.',
})
identityTokenAudience;

@attr({
label: 'Identity token TTL',
helperTextDisabled:
'The TTL of generated tokens. Defaults to 1 hour, toggle on to specify a different value.',
helperTextEnabled: 'The TTL of generated tokens.',
editType: 'ttl',
})
identityTokenTtl;

@attr('string', {
subText: 'Email ID for the Service Account to impersonate for Workload Identity Federation.',
})
serviceAccountEmail;

configurableParams = [
'credentials',
'serviceAccountEmail',
'ttl',
'maxTtl',
'identityTokenAudience',
'identityTokenTtl',
];

get displayAttrs() {
const formFields = expandAttributeMeta(this, this.configurableParams);
return formFields.filter((attr) => attr.name !== 'credentials');
}
}
72 changes: 48 additions & 24 deletions ui/app/routes/vault/cluster/secrets/backend/configuration/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,16 @@ export default class SecretsBackendConfigurationRoute extends Route {
}

fetchConfig(type, id) {
// id is the path where the backend is mounted since there's only one config per engine (often this path is referred to just as backend)
switch (type) {
case 'aws':
return this.fetchAwsConfigs(id);
case 'ssh':
return this.fetchSshCaConfig(id);
case 'azure':
return this.fetchAzureConfig(id);
case 'gcp':
return this.fetchGcpConfig(id);
case 'ssh':
return this.fetchSshCaConfig(id);
default:
return reject({ httpStatus: 404, message: 'not found', path: id });
}
Expand Down Expand Up @@ -104,28 +107,6 @@ export default class SecretsBackendConfigurationRoute extends Route {
}
}

async fetchIssuer() {
try {
return await this.store.queryRecord('identity/oidc/config', {});
} catch (e) {
// silently fail if the endpoint is not available or the user doesn't have permission to access it.
return;
}
}

async fetchSshCaConfig(id) {
try {
return await this.store.queryRecord('ssh/ca-config', { backend: id });
} catch (e) {
if (e.httpStatus === 400 && e.errors[0] === `keys haven't been configured yet`) {
// When first mounting a SSH engine it throws a 400 error with this specific message.
// We want to catch this situation and return nothing so that the component can handle it correctly.
return;
}
throw e;
}
}

async fetchAzureConfig(id) {
try {
const azureModel = await this.store.queryRecord('azure/config', { backend: id });
Expand All @@ -149,6 +130,49 @@ export default class SecretsBackendConfigurationRoute extends Route {
}
}

async fetchGcpConfig(id) {
try {
const gcpModel = await this.store.queryRecord('gcp/config', { backend: id });
let issuer = null;
if (this.version.isEnterprise) {
const WIF_FIELDS = ['identityTokenAudience', 'identityTokenTtl', 'serviceAccountEmail'];
WIF_FIELDS.some((field) => gcpModel[field]) ? (issuer = await this.fetchIssuer()) : null;
}
const configArray = [];
if (gcpModel) configArray.push(gcpModel);
if (issuer) configArray.push(issuer);
return configArray;
} catch (e) {
if (e.httpStatus === 404) {
// a 404 error is thrown when GCP's config hasn't been set yet.
return;
}
throw e;
}
}

async fetchIssuer() {
try {
return await this.store.queryRecord('identity/oidc/config', {});
} catch (e) {
// silently fail if the endpoint is not available or the user doesn't have permission to access it.
return;
}
}

async fetchSshCaConfig(id) {
try {
return await this.store.queryRecord('ssh/ca-config', { backend: id });
} catch (e) {
if (e.httpStatus === 400 && e.errors[0] === `keys haven't been configured yet`) {
// When first mounting a SSH engine it throws a 400 error with this specific message.
// We want to catch this situation and return nothing so that the component can handle it correctly.
return;
}
throw e;
}
}

setupController(controller, resolvedModel) {
super.setupController(controller, resolvedModel);
controller.typeDisplay = allEngines().find(
Expand Down
23 changes: 23 additions & 0 deletions ui/app/serializers/gcp/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/

import ApplicationSerializer from '../application';

export default class GcpConfigSerializer extends ApplicationSerializer {
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
if (!payload.data) {
return super.normalizeResponse(...arguments);
}

const normalizedPayload = {
id: payload.id,
backend: payload.backend,
data: {
...payload.data,
},
};
return super.normalizeResponse(store, primaryModelClass, normalizedPayload, id, requestType);
}
}
Loading

0 comments on commit 6dd5471

Please sign in to comment.