diff --git a/.changeset/moody-geese-serve.md b/.changeset/moody-geese-serve.md new file mode 100644 index 0000000000..4b3b96d255 --- /dev/null +++ b/.changeset/moody-geese-serve.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': patch +--- + +Add TLS support to ingest GSuite LDAP data diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md index 9bc2667aa7..49a2776fb1 100644 --- a/docs/integrations/ldap/org.md +++ b/docs/integrations/ldap/org.md @@ -110,6 +110,18 @@ This is the URL of the targeted server, typically on the form `ldaps://ds.example.net` for SSL enabled servers or `ldap://ds.example.net` without SSL. +#### target.tls.keys + +`keys` in TLS options specifies location of a file, that contains private keys +to establish connection with your LDAP server, in PEM format. See an example +for Google Secure LDAP Service below. + +#### target.tls.certs + +`certs` in TLS options specifies location of a file, that contains certificate +chains to establish connection with your LDAP server, in PEM format. See an +example for Google Secure LDAP Service below. + ### bind The bind block specifies how the plugin should bind (essentially, to @@ -374,3 +386,28 @@ catalog: rules: - allow: [User, Group] ``` + +### Example configurations + +#### Google Secure LDAP Service + +To sync Google Workspace/Cloud Identity organization data to users and groups in backstage, +you must [configure Secure LDAP Service](https://support.google.com/a/answer/9048516) first. + +Once Secure LDAP Service is configured, you can enable TLS options in LDAP configuration, +as mentioned below. `keys` and `certs` specify the location of files that are generated +while configuring Secure LDAP Service above. + +```yaml +ldap: + providers: + - target: ldaps://ldap.google.com:636 + tls: + rejectUnauthorized: false + keys: '/var/secrets/tls/gldap.key' + certs: '/var/secrets/tls/gldap.crt' + users: + # users configuration comes here + groups: + # groups configuration comes here +``` diff --git a/plugins/catalog-backend-module-ldap/api-report.md b/plugins/catalog-backend-module-ldap/api-report.md index 997ad55560..277a48af8d 100644 --- a/plugins/catalog-backend-module-ldap/api-report.md +++ b/plugins/catalog-backend-module-ldap/api-report.md @@ -197,6 +197,8 @@ export function readLdapOrg( // @public export type TLSConfig = { rejectUnauthorized?: boolean; + keys?: string; + certs?: string; }; // @public diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 58afe8b4b8..abd20fa1ce 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -39,8 +39,8 @@ "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", - "@types/ldapjs": "^2.2.0", - "ldapjs": "^2.2.0", + "@types/ldapjs": "^2.2.5", + "ldapjs": "^2.3.3", "lodash": "^4.17.21", "uuid": "^9.0.0", "winston": "^3.2.1" diff --git a/plugins/catalog-backend-module-ldap/src/ldap/client.ts b/plugins/catalog-backend-module-ldap/src/ldap/client.ts index 410318197e..75e54013b3 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/client.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/client.ts @@ -15,16 +15,18 @@ */ import { ForwardedError, stringifyError } from '@backstage/errors'; +import { readFile } from 'fs/promises'; import ldap, { Client, SearchEntry, SearchOptions } from 'ldapjs'; import { cloneDeep } from 'lodash'; +import tlsLib from 'tls'; import { Logger } from 'winston'; import { BindConfig, TLSConfig } from './config'; import { createOptions, errorString } from './util'; import { + AEDirVendor, ActiveDirectoryVendor, DefaultLdapVendor, FreeIpaVendor, - AEDirVendor, LdapVendor, } from './vendors'; @@ -44,9 +46,22 @@ export class LdapClient { bind?: BindConfig, tls?: TLSConfig, ): Promise { + let secureContext; + if (tls && tls.certs && tls.keys) { + const cert = await readFile(tls.certs, 'utf-8'); + const key = await readFile(tls.keys, 'utf-8'); + secureContext = tlsLib.createSecureContext({ + cert: cert, + key: key, + }); + } + const client = ldap.createClient({ url: target, - tlsOptions: tls, + tlsOptions: { + secureContext, + rejectUnauthorized: tls?.rejectUnauthorized, + }, }); // We want to have a catch-all error handler at the top, since the default diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts index 28c842294f..722625f3ed 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts @@ -80,7 +80,11 @@ describe('readLdapConfig', () => { { target: 'target', bind: { dn: 'bdn', secret: 's' }, - tls: { rejectUnauthorized: false }, + tls: { + rejectUnauthorized: false, + keys: '/tmp/keys.pem', + certs: '/tmp/certs.pem', + }, users: { dn: 'udn', options: { @@ -140,7 +144,11 @@ describe('readLdapConfig', () => { { target: 'target', bind: { dn: 'bdn', secret: 's' }, - tls: { rejectUnauthorized: false }, + tls: { + rejectUnauthorized: false, + keys: '/tmp/keys.pem', + certs: '/tmp/certs.pem', + }, users: { dn: 'udn', options: { diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.ts index 9ae4b9ed8d..08aee5feee 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.ts @@ -49,6 +49,10 @@ export type LdapProviderConfig = { export type TLSConfig = { // Node TLS rejectUnauthorized rejectUnauthorized?: boolean; + // A file containing private keys in PEM format + keys?: string; + // A file containing cert chains in PEM format + certs?: string; }; /** @@ -205,6 +209,8 @@ export function readLdapConfig(config: Config): LdapProviderConfig[] { } return { rejectUnauthorized: c.getOptionalBoolean('rejectUnauthorized'), + keys: c.getOptionalString('keys'), + certs: c.getOptionalString('certs'), }; } diff --git a/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts b/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts index 02329b90b3..9f4e365c38 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts @@ -105,7 +105,7 @@ function decode( function formatGUID(objectGUID: string | Buffer): string { let data: Buffer; if (typeof objectGUID === 'string') { - data = new Buffer(objectGUID, 'binary'); + data = Buffer.from(objectGUID, 'binary'); } else { data = objectGUID; } diff --git a/yarn.lock b/yarn.lock index 56f321fd5d..c168fb4dbf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5691,9 +5691,9 @@ __metadata: "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" - "@types/ldapjs": ^2.2.0 + "@types/ldapjs": ^2.2.5 "@types/lodash": ^4.14.151 - ldapjs: ^2.2.0 + ldapjs: ^2.3.3 lodash: ^4.17.21 uuid: ^9.0.0 winston: ^3.2.1 @@ -18988,7 +18988,7 @@ __metadata: languageName: node linkType: hard -"@types/ldapjs@npm:^2.2.0": +"@types/ldapjs@npm:^2.2.5": version: 2.2.5 resolution: "@types/ldapjs@npm:2.2.5" dependencies: @@ -33246,7 +33246,7 @@ __metadata: languageName: node linkType: hard -"ldapjs@npm:^2.2.0": +"ldapjs@npm:^2.3.3": version: 2.3.3 resolution: "ldapjs@npm:2.3.3" dependencies: