Skip to content

Commit

Permalink
LDAP/AD Secrets Engine (#20790)
Browse files Browse the repository at this point in the history
* adds ldap ember engine (#20786)

* adds ldap as mountable and supported secrets engine (#20793)

* removes active directory as mountable secrets engine (#20798)

* LDAP Config Ember Data Setup (#20863)

* adds secret-engine-path adapter

* adds model, adapater and serializer for ldap config

* adds test for ldap config adapter

* addresses PR feedback

* updates remaining instances of getURL in secrets-engine-path adapter

* adds underscore to getURL method in kubernetes/config adapter

* adds check config vars test for kubernetes/config adapter

* adds comment regarding primaryKey in secrets-engine-path adapter

* adds tab-page-header component for ldap secrets engine (#20941)

* LDAP Config Route (#21059)

* converts secret-mount-path service to ts and moves kubernetes fetch-config decorator to core addon and converts to ts

* adds ldap config route

* fixes withConfig import path in kubernetes roles route

* updates types in ldap config route

* adds unit tests for fetch-secret-config decorator

* updates comments in fetch-secret-config decorator

* renames fetch-secret-config decorator

* LDAP Configure Page Component (#21384)

* adds ldap page configure component

* removes pauseTest and updates radio card selector in ldap config test

* LDAP Configuration (#21430)

* adds ldap configuration route

* adds secrets-engine-mount-config component to core addon

* adds ldap config-cta component

* adds display fields to ldap configuration page and test

* fixes ldap config-cta test

* adds yield to secrets-engine-mount-config component

* fixes tests

* LDAP Overview Route and Page Component (#21579)

* adds ldap overview route and page component

* changes toolbar link action type for create role on overview page

* LDAP Role Model, Adapter and Serializer (#21655)

* adds model, adapter and serializer for ldap roles

* addresses review feedback

* changes ldap role type from tracked prop to attr and sets in adapter for query methods

* adds assertions to verify that frontend only props are returned from query methods in ldap role adapter

* LDAP Library Model, Adapter and Serializer (#21728)

* adds model, adapter and serializer for ldap library

* updates capitalization and punction for ldap role and library form fields

* LDAP Roles Create and Edit (#21818)

* moves stringify and jsonify helpers to core addon

* adds validation error for ttl picker in form field component

* adds ldap roles create and edit routes and page component

* adds ldap mirage handler and factory for roles

* adds example workflow to json editor component

* adds tests for ldap page create and edit component

* addresses feedback

* LDAP Role Details (#22036)

* adds ldap role route to pass down model to child routes

* adds ldap role details route and page component

* updates ldap role model capabilities checks

* adds periods to error messages

* removes modelFor from ldap roles edit and details routes

* adds flash message on ldap role delete success

* LDAP Roles (#22070)

* adds ldap roles route and page component

* update ldap role adapter tests and adds adapter options to query for partialErrorInfo

* updates ldap role adapter based on PR feedback

* adds filter-input component to core addon

* updates ldap roles page to use filter-input component

* updates ldap role adapter tests

* LDAP Role Credentials (#22142)

* adds ldap roles route and page component

* update ldap role adapter tests and adds adapter options to query for partialErrorInfo

* adds credentials actions to ldap roles list menu and fixes rotate action in details view

* adds ldap role credentials route and page component

* adds tests for ldap role credentials

* LDAP Library Create and Edit (#22171)

* adds ldap library create/edit routes and page component

* adds ldap library create-and-edit tests and library mirage factory

* updates form-field component to display validation errors and warnings for all fields

* updates ldap library edit route class name

* updates ldap library model interface name

* adds missing period in flash message

* LDAP Libraries (#22184)

* updates interface and class names in ldap roles route

* adds ldap libraries route and page component

* fixes lint error

* LDAP Library Details (#22200)

* updates interface and class names in ldap roles route

* adds ldap libraries route and page component

* fixes lint error

* adds ldap library details route and page component

* LDAP Library Details Configuration (#22201)

* updates interface and class names in ldap roles route

* adds ldap libraries route and page component

* fixes lint error

* adds ldap library details route and page component

* adds ldap library details configuration route and page component

* updates ldap library check-in enforcement value mapping

* fixes issue in code mirror modifier after merging upgrade

* fixes failing database secrets test

* LDAP Library Account Details (#22287)

* adds route and page component for ldap library accounts

* adds ldap component for checked out accounts

* updates ldap library adapter tests

* LDAP Library Check-out (#22289)

* adds route and page component for ldap library accounts

* adds ldap component for checked out accounts

* adds route and page component for ldap library checkout

* addresses PR feedback

* LDAP Overview Cards (#22325)

* adds overview cards to ldap overview route

* adds create library toolbar action to ldap overview route

* adds acceptance tests for ldap workflows (#22375)

* Fetch Secrets Engine Config Decorator Docs (#22416)

* removes uneccesary asyncs from ldap route model hooks

* updates ldap overview route class name

* adds documentation for fetch-secrets-engine-config decorator

* add changelog

* adding back external links, missed due to merge.

* changelog

* fix test after merging in dashboard work

* Update 20790.txt

---------

Co-authored-by: Angel Garbarino <[email protected]>
Co-authored-by: Angel Garbarino <[email protected]>
  • Loading branch information
3 people authored Aug 25, 2023
1 parent 2d0d5c7 commit a8b5936
Show file tree
Hide file tree
Showing 178 changed files with 7,112 additions and 219 deletions.
3 changes: 3 additions & 0 deletions changelog/20790.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
**UI LDAP secrets engine**: Add LDAP secrets engine to the UI.
```
37 changes: 4 additions & 33 deletions ui/app/adapters/kubernetes/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,12 @@
* SPDX-License-Identifier: BUSL-1.1
*/

import ApplicationAdapter from 'vault/adapters/application';
import { encodePath } from 'vault/utils/path-encoding-helpers';
import SecretsEnginePathAdapter from 'vault/adapters/secrets-engine-path';

export default class KubernetesConfigAdapter extends ApplicationAdapter {
namespace = 'v1';
export default class KubernetesConfigAdapter extends SecretsEnginePathAdapter {
path = 'config';

getURL(backend, path = 'config') {
return `${this.buildURL()}/${encodePath(backend)}/${path}`;
}
urlForUpdateRecord(name, modelName, snapshot) {
return this.getURL(snapshot.attr('backend'));
}
urlForDeleteRecord(backend) {
return this.getURL(backend);
}

queryRecord(store, type, query) {
const { backend } = query;
return this.ajax(this.getURL(backend), 'GET').then((resp) => {
resp.backend = backend;
return resp;
});
}
createRecord() {
return this._saveRecord(...arguments);
}
updateRecord() {
return this._saveRecord(...arguments);
}
_saveRecord(store, { modelName }, snapshot) {
const data = store.serializerFor(modelName).serialize(snapshot);
const url = this.getURL(snapshot.attr('backend'));
return this.ajax(url, 'POST', { data }).then(() => data);
}
checkConfigVars(backend) {
return this.ajax(`${this.getURL(backend, 'check')}`, 'GET');
return this.ajax(`${this._getURL(backend, 'check')}`, 'GET');
}
}
14 changes: 14 additions & 0 deletions ui/app/adapters/ldap/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import SecretsEnginePathAdapter from 'vault/adapters/secrets-engine-path';

export default class LdapConfigAdapter extends SecretsEnginePathAdapter {
path = 'config';

async rotateRoot(backend) {
return this.ajax(this._getURL(backend, 'rotate-root'), 'POST');
}
}
67 changes: 67 additions & 0 deletions ui/app/adapters/ldap/library.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import NamedPathAdapter from 'vault/adapters/named-path';
import { encodePath } from 'vault/utils/path-encoding-helpers';

export default class LdapLibraryAdapter extends NamedPathAdapter {
getURL(backend, name) {
const base = `${this.buildURL()}/${encodePath(backend)}/library`;
return name ? `${base}/${name}` : base;
}

urlForUpdateRecord(name, modelName, snapshot) {
return this.getURL(snapshot.attr('backend'), name);
}
urlForDeleteRecord(name, modelName, snapshot) {
return this.getURL(snapshot.attr('backend'), name);
}

query(store, type, query) {
const { backend } = query;
return this.ajax(this.getURL(backend), 'GET', { data: { list: true } })
.then((resp) => {
return resp.data.keys.map((name) => ({ name, backend }));
})
.catch((error) => {
if (error.httpStatus === 404) {
return [];
}
throw error;
});
}
queryRecord(store, type, query) {
const { backend, name } = query;
return this.ajax(this.getURL(backend, name), 'GET').then((resp) => ({ ...resp.data, backend, name }));
}

fetchStatus(backend, name) {
const url = `${this.getURL(backend, name)}/status`;
return this.ajax(url, 'GET').then((resp) => {
const statuses = [];
for (const key in resp.data) {
const status = {
...resp.data[key],
account: key,
library: name,
};
statuses.push(status);
}
return statuses;
});
}
checkOutAccount(backend, name, ttl) {
const url = `${this.getURL(backend, name)}/check-out`;
return this.ajax(url, 'POST', { data: { ttl } }).then((resp) => {
const { lease_id, lease_duration, renewable } = resp;
const { service_account_name: account, password } = resp.data;
return { account, password, lease_id, lease_duration, renewable };
});
}
checkInAccount(backend, name, service_account_names) {
const url = `${this.getURL(backend, name)}/check-in`;
return this.ajax(url, 'POST', { data: { service_account_names } }).then((resp) => resp.data);
}
}
92 changes: 92 additions & 0 deletions ui/app/adapters/ldap/role.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import NamedPathAdapter from 'vault/adapters/named-path';
import { encodePath } from 'vault/utils/path-encoding-helpers';
import { inject as service } from '@ember/service';

export default class LdapRoleAdapter extends NamedPathAdapter {
@service flashMessages;

getURL(backend, path, name) {
const base = `${this.buildURL()}/${encodePath(backend)}/${path}`;
return name ? `${base}/${name}` : base;
}
pathForRoleType(type, isCred) {
const staticPath = isCred ? 'static-cred' : 'static-role';
const dynamicPath = isCred ? 'creds' : 'role';
return type === 'static' ? staticPath : dynamicPath;
}

urlForUpdateRecord(name, modelName, snapshot) {
const { backend, type } = snapshot.record;
return this.getURL(backend, this.pathForRoleType(type), name);
}
urlForDeleteRecord(name, modelName, snapshot) {
const { backend, type } = snapshot.record;
return this.getURL(backend, this.pathForRoleType(type), name);
}

async query(store, type, query, recordArray, options) {
const { showPartialError } = options.adapterOptions || {};
const { backend } = query;
const roles = [];
const errors = [];

for (const roleType of ['static', 'dynamic']) {
const url = this.getURL(backend, this.pathForRoleType(roleType));
try {
const models = await this.ajax(url, 'GET', { data: { list: true } }).then((resp) => {
return resp.data.keys.map((name) => ({ name, backend, type: roleType }));
});
roles.addObjects(models);
} catch (error) {
if (error.httpStatus !== 404) {
errors.push(error);
}
}
}

if (errors.length) {
const errorMessages = errors.reduce((errors, e) => {
e.errors.forEach((error) => {
errors.push(`${e.path}: ${error}`);
});
return errors;
}, []);
if (errors.length === 2) {
// throw error as normal if both requests fail
// ignore status code and concat errors to be displayed in Page::Error component with generic message
throw { message: 'Error fetching roles:', errors: errorMessages };
} else if (showPartialError) {
// if only one request fails, surface the error to the user an info level flash message
// this may help for permissions errors where a users policy may be incorrect
this.flashMessages.info(`Error fetching roles from ${errorMessages.join(', ')}`);
}
}

return roles.sortBy('name');
}
queryRecord(store, type, query) {
const { backend, name, type: roleType } = query;
const url = this.getURL(backend, this.pathForRoleType(roleType), name);
return this.ajax(url, 'GET').then((resp) => ({ ...resp.data, backend, name, type: roleType }));
}

fetchCredentials(backend, type, name) {
const url = this.getURL(backend, this.pathForRoleType(type, true), name);
return this.ajax(url, 'GET').then((resp) => {
if (type === 'dynamic') {
const { lease_id, lease_duration, renewable } = resp;
return { ...resp.data, lease_id, lease_duration, renewable, type };
}
return { ...resp.data, type };
});
}
rotateStaticPassword(backend, name) {
const url = this.getURL(backend, 'rotate-role', name);
return this.ajax(url, 'POST');
}
}
48 changes: 48 additions & 0 deletions ui/app/adapters/secrets-engine-path.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

/**
* General use adapter to access specified paths on secrets engines
* For example /:backend/config is a typical use case for this adapter
* These types of records do not have an id and use the backend value of the secrets engine as the primaryKey in the serializer
*/

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

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

// define path value in extending class or pass into method directly
_getURL(backend, path) {
return `${this.buildURL()}/${encodePath(backend)}/${path || this.path}`;
}
urlForUpdateRecord(name, modelName, snapshot) {
return this._getURL(snapshot.attr('backend'));
}
// primaryKey must be set to backend in serializer
urlForDeleteRecord(backend) {
return this._getURL(backend);
}

queryRecord(store, type, query) {
const { backend } = query;
return this.ajax(this._getURL(backend), 'GET').then((resp) => {
resp.backend = backend;
return resp;
});
}
createRecord() {
return this._saveRecord(...arguments);
}
updateRecord() {
return this._saveRecord(...arguments);
}
_saveRecord(store, { modelName }, snapshot) {
const data = store.serializerFor(modelName).serialize(snapshot);
const url = this._getURL(snapshot.attr('backend'));
return this.ajax(url, 'POST', { data }).then(() => data);
}
}
8 changes: 8 additions & 0 deletions ui/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ export default class App extends Application {
},
},
},
ldap: {
dependencies: {
services: ['router', 'store', 'secret-mount-path', 'flash-messages', 'auth'],
externalRoutes: {
secrets: 'vault.cluster.secrets.backends',
},
},
},
kv: {
dependencies: {
services: ['download', 'namespace', 'router', 'store', 'secret-mount-path', 'flash-messages'],
Expand Down
13 changes: 7 additions & 6 deletions ui/app/helpers/mountable-secret-engines.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ const ENTERPRISE_SECRET_ENGINES = [
];

const MOUNTABLE_SECRET_ENGINES = [
{
displayName: 'Active Directory',
type: 'ad',
category: 'cloud',
},
{
displayName: 'AliCloud',
type: 'alicloud',
Expand Down Expand Up @@ -110,9 +105,15 @@ const MOUNTABLE_SECRET_ENGINES = [
type: 'totp',
category: 'generic',
},
{
displayName: 'LDAP',
type: 'ldap',
engineRoute: 'ldap.overview',
category: 'generic',
glyph: 'folder-users',
},
{
displayName: 'Kubernetes',
value: 'kubernetes',
type: 'kubernetes',
engineRoute: 'kubernetes.overview',
category: 'generic',
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 @@ -18,6 +18,7 @@ const SUPPORTED_SECRET_BACKENDS = [
'transform',
'keymgmt',
'kubernetes',
'ldap',
];

export function supportedSecretBackends() {
Expand Down
Loading

0 comments on commit a8b5936

Please sign in to comment.