Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LDAP/AD Secrets Engine #20790

Merged
merged 37 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
47e9f90
adds ldap ember engine (#20786)
zofskeez May 25, 2023
44a7e82
adds ldap as mountable and supported secrets engine (#20793)
zofskeez May 25, 2023
e05a9fc
removes active directory as mountable secrets engine (#20798)
zofskeez May 25, 2023
e6a10a3
LDAP Config Ember Data Setup (#20863)
zofskeez Jun 1, 2023
08242db
adds tab-page-header component for ldap secrets engine (#20941)
zofskeez Jun 1, 2023
7599383
LDAP Config Route (#21059)
zofskeez Jun 14, 2023
3fbb217
Merge branch 'main' into ui/VAULT-12945/ldap-secrets-engine
zofskeez Jun 15, 2023
ab828bc
LDAP Configure Page Component (#21384)
zofskeez Jun 21, 2023
ae84792
LDAP Configuration (#21430)
zofskeez Jun 22, 2023
d1caba3
LDAP Overview Route and Page Component (#21579)
zofskeez Jul 5, 2023
902f3b2
LDAP Role Model, Adapter and Serializer (#21655)
zofskeez Jul 7, 2023
d534a34
LDAP Library Model, Adapter and Serializer (#21728)
zofskeez Jul 10, 2023
6bea9cc
LDAP Roles Create and Edit (#21818)
zofskeez Jul 20, 2023
76a779a
LDAP Role Details (#22036)
zofskeez Jul 24, 2023
4fbecad
LDAP Roles (#22070)
zofskeez Jul 31, 2023
608b909
LDAP Role Credentials (#22142)
zofskeez Aug 1, 2023
f496bc1
LDAP Library Create and Edit (#22171)
zofskeez Aug 2, 2023
7ede920
Merge branch 'main' into ui/VAULT-12945/ldap-secrets-engine
zofskeez Aug 2, 2023
a68e399
LDAP Libraries (#22184)
zofskeez Aug 3, 2023
0f9eedb
LDAP Library Details (#22200)
zofskeez Aug 3, 2023
8d33989
LDAP Library Details Configuration (#22201)
zofskeez Aug 3, 2023
75f6170
LDAP Library Account Details (#22287)
zofskeez Aug 10, 2023
9559667
LDAP Library Check-out (#22289)
zofskeez Aug 11, 2023
4012282
Merge branch 'main' into ui/VAULT-12945/ldap-secrets-engine
zofskeez Aug 11, 2023
0b3fc8e
LDAP Overview Cards (#22325)
zofskeez Aug 15, 2023
df32bc8
Merge branch 'main' into ui/VAULT-12945/ldap-secrets-engine
zofskeez Aug 15, 2023
8929d42
adds acceptance tests for ldap workflows (#22375)
zofskeez Aug 17, 2023
23425bc
Fetch Secrets Engine Config Decorator Docs (#22416)
zofskeez Aug 17, 2023
cd7bef8
Merge branch 'main' into ui/VAULT-12945/ldap-secrets-engine
Monkeychip Aug 24, 2023
4fcd852
add changelog
Monkeychip Aug 24, 2023
4f8f9dc
adding back external links, missed due to merge.
Monkeychip Aug 24, 2023
6cddd8b
Merge branch 'main' into ui/VAULT-12945/ldap-secrets-engine
Monkeychip Aug 25, 2023
4b62a60
changelog
Monkeychip Aug 25, 2023
01c05f1
fix test after merging in dashboard work
Monkeychip Aug 25, 2023
4704a64
Merge branch 'main' into ui/VAULT-12945/ldap-secrets-engine
Monkeychip Aug 25, 2023
1260bb2
Merge branch 'main' into ui/VAULT-12945/ldap-secrets-engine
Monkeychip Aug 25, 2023
c3af989
Update 20790.txt
Monkeychip Aug 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
},
},
},
pki: {
dependencies: {
services: [
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