From 019e13c2b0e9f89cd4c5b6aa1101a8a63920fd47 Mon Sep 17 00:00:00 2001 From: Praphull Purohit Date: Fri, 1 Mar 2024 12:34:48 +0100 Subject: [PATCH 1/9] Add TLS support to ingest GSuite LDAP data Signed-off-by: Praphull Purohit --- .changeset/clean-sloths-grin.md | 5 ++++ .../catalog-backend-module-ldap/package.json | 4 ++-- .../src/ldap/client.ts | 23 ++++++++++++++++++- .../src/ldap/config.test.ts | 12 ++++++++-- .../src/ldap/config.ts | 6 +++++ .../src/ldap/vendors.ts | 2 +- yarn.lock | 8 +++---- 7 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 .changeset/clean-sloths-grin.md diff --git a/.changeset/clean-sloths-grin.md b/.changeset/clean-sloths-grin.md new file mode 100644 index 0000000000..4b3b96d255 --- /dev/null +++ b/.changeset/clean-sloths-grin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': patch +--- + +Add TLS support to ingest GSuite LDAP data diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 54ecd7b737..9ef953d625 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..dcd5c1e170 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/client.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/client.ts @@ -15,8 +15,10 @@ */ import { ForwardedError, stringifyError } from '@backstage/errors'; +import * as fs from 'fs'; 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'; @@ -44,9 +46,28 @@ export class LdapClient { bind?: BindConfig, tls?: TLSConfig, ): Promise { + const readTLSOptionFile = (file?: string) => + file !== undefined ? fs.readFileSync(file).toString() : undefined; + + const getTlsOptions = () => { + const certs = readTLSOptionFile(tls?.certs); + const keys = readTLSOptionFile(tls?.keys); + + if (certs !== undefined || keys !== undefined) { + return { + secureContext: tlsLib.createSecureContext({ + cert: certs, + key: keys, + }), + rejectUnauthorized: tls?.rejectUnauthorized, + }; + } + return tls; + }; + const client = ldap.createClient({ url: target, - tlsOptions: tls, + tlsOptions: getTlsOptions(), }); // 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 fafc485f01..28e237a7f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5672,9 +5672,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 @@ -19193,7 +19193,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: @@ -33551,7 +33551,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: From 979f62ec477258060e75f7eb0e184613f1934bfe Mon Sep 17 00:00:00 2001 From: Praphull Purohit Date: Fri, 1 Mar 2024 13:18:41 +0100 Subject: [PATCH 2/9] Update documentation about LDAP TLS support Signed-off-by: Praphull Purohit --- docs/integrations/ldap/org.md | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) 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 +``` From 227059170db2f3d43f3491f77c6ec196eee62606 Mon Sep 17 00:00:00 2001 From: Praphull Purohit Date: Fri, 1 Mar 2024 15:05:31 +0100 Subject: [PATCH 3/9] Add API Report Signed-off-by: Praphull Purohit --- plugins/catalog-backend-module-ldap/api-report.md | 2 ++ 1 file changed, 2 insertions(+) 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 From 0a5f938c5d37cba16121cdad289376c0a2de88bc Mon Sep 17 00:00:00 2001 From: Praphull Purohit Date: Mon, 4 Mar 2024 14:09:42 +0100 Subject: [PATCH 4/9] Improve logic to read TLS options Signed-off-by: Praphull Purohit --- .../src/ldap/client.ts | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/plugins/catalog-backend-module-ldap/src/ldap/client.ts b/plugins/catalog-backend-module-ldap/src/ldap/client.ts index dcd5c1e170..5d6303a943 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/client.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/client.ts @@ -15,7 +15,7 @@ */ import { ForwardedError, stringifyError } from '@backstage/errors'; -import * as fs from 'fs'; +import { readFileSync } from 'fs'; import ldap, { Client, SearchEntry, SearchOptions } from 'ldapjs'; import { cloneDeep } from 'lodash'; import tlsLib from 'tls'; @@ -47,27 +47,26 @@ export class LdapClient { tls?: TLSConfig, ): Promise { const readTLSOptionFile = (file?: string) => - file !== undefined ? fs.readFileSync(file).toString() : undefined; + file !== undefined ? readFileSync(file).toString() : undefined; - const getTlsOptions = () => { - const certs = readTLSOptionFile(tls?.certs); - const keys = readTLSOptionFile(tls?.keys); - - if (certs !== undefined || keys !== undefined) { - return { - secureContext: tlsLib.createSecureContext({ + const certs = readTLSOptionFile(tls?.certs); + const keys = readTLSOptionFile(tls?.keys); + const secureContext = + certs !== undefined || keys !== undefined + ? tlsLib.createSecureContext({ cert: certs, key: keys, - }), - rejectUnauthorized: tls?.rejectUnauthorized, - }; - } - return tls; + }) + : undefined; + + const tlsOptions = { + secureContext, + rejectUnauthorized: tls?.rejectUnauthorized, }; const client = ldap.createClient({ url: target, - tlsOptions: getTlsOptions(), + tlsOptions: tlsOptions, }); // We want to have a catch-all error handler at the top, since the default From 2d91b4c383f50f50d68f6a65bf35d2c87bd6c4d9 Mon Sep 17 00:00:00 2001 From: Praphull Purohit Date: Tue, 5 Mar 2024 13:52:07 +0100 Subject: [PATCH 5/9] Create secureContext when both keys & certs are defined Signed-off-by: Praphull Purohit --- .../src/ldap/client.ts | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/plugins/catalog-backend-module-ldap/src/ldap/client.ts b/plugins/catalog-backend-module-ldap/src/ldap/client.ts index 5d6303a943..47e8b1f397 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/client.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/client.ts @@ -23,10 +23,10 @@ import { Logger } from 'winston'; import { BindConfig, TLSConfig } from './config'; import { createOptions, errorString } from './util'; import { + AEDirVendor, ActiveDirectoryVendor, DefaultLdapVendor, FreeIpaVendor, - AEDirVendor, LdapVendor, } from './vendors'; @@ -46,27 +46,20 @@ export class LdapClient { bind?: BindConfig, tls?: TLSConfig, ): Promise { - const readTLSOptionFile = (file?: string) => - file !== undefined ? readFileSync(file).toString() : undefined; - - const certs = readTLSOptionFile(tls?.certs); - const keys = readTLSOptionFile(tls?.keys); const secureContext = - certs !== undefined || keys !== undefined + tls && tls.certs && tls.keys ? tlsLib.createSecureContext({ - cert: certs, - key: keys, + cert: readFileSync(tls.certs).toString(), + key: readFileSync(tls.keys).toString(), }) : undefined; - const tlsOptions = { - secureContext, - rejectUnauthorized: tls?.rejectUnauthorized, - }; - const client = ldap.createClient({ url: target, - tlsOptions: tlsOptions, + tlsOptions: { + secureContext, + rejectUnauthorized: tls?.rejectUnauthorized, + }, }); // We want to have a catch-all error handler at the top, since the default From 4b4bbf759576a5606408dbd23c8b3218e5c45200 Mon Sep 17 00:00:00 2001 From: Praphull Purohit Date: Tue, 5 Mar 2024 13:56:03 +0100 Subject: [PATCH 6/9] Change from patch to minor version bump Signed-off-by: Praphull Purohit --- .changeset/clean-sloths-grin.md | 5 ----- .changeset/moody-geese-serve.md | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 .changeset/clean-sloths-grin.md create mode 100644 .changeset/moody-geese-serve.md diff --git a/.changeset/clean-sloths-grin.md b/.changeset/clean-sloths-grin.md deleted file mode 100644 index 4b3b96d255..0000000000 --- a/.changeset/clean-sloths-grin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-ldap': patch ---- - -Add TLS support to ingest GSuite LDAP data diff --git a/.changeset/moody-geese-serve.md b/.changeset/moody-geese-serve.md new file mode 100644 index 0000000000..29c5722e37 --- /dev/null +++ b/.changeset/moody-geese-serve.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': minor +--- + +Add TLS support to ingest GSuite LDAP data From 86431d1eda6a03b0635ed4cdd19027246a16c5a6 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Tue, 19 Mar 2024 10:20:37 +0100 Subject: [PATCH 7/9] Update moody-geese-serve.md Signed-off-by: Ben Lambert --- .changeset/moody-geese-serve.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/moody-geese-serve.md b/.changeset/moody-geese-serve.md index 29c5722e37..4b3b96d255 100644 --- a/.changeset/moody-geese-serve.md +++ b/.changeset/moody-geese-serve.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog-backend-module-ldap': minor +'@backstage/plugin-catalog-backend-module-ldap': patch --- Add TLS support to ingest GSuite LDAP data From dcfa173fb0b59713b575364044631b3e653abdd5 Mon Sep 17 00:00:00 2001 From: Praphull Purohit Date: Tue, 19 Mar 2024 10:46:20 +0100 Subject: [PATCH 8/9] Use readFile from fs/promises Signed-off-by: Praphull Purohit --- .../src/ldap/client.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/plugins/catalog-backend-module-ldap/src/ldap/client.ts b/plugins/catalog-backend-module-ldap/src/ldap/client.ts index 47e8b1f397..56cc18e2de 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/client.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/client.ts @@ -15,7 +15,7 @@ */ import { ForwardedError, stringifyError } from '@backstage/errors'; -import { readFileSync } from 'fs'; +import { readFile } from 'fs/promises'; import ldap, { Client, SearchEntry, SearchOptions } from 'ldapjs'; import { cloneDeep } from 'lodash'; import tlsLib from 'tls'; @@ -46,13 +46,15 @@ export class LdapClient { bind?: BindConfig, tls?: TLSConfig, ): Promise { - const secureContext = - tls && tls.certs && tls.keys - ? tlsLib.createSecureContext({ - cert: readFileSync(tls.certs).toString(), - key: readFileSync(tls.keys).toString(), - }) - : undefined; + let secureContext; + if (tls && tls.certs && tls.keys) { + const cert = await readFile(tls.certs).then(f => f.toString()); + const key = await readFile(tls.keys).then(f => f.toString()); + secureContext = tlsLib.createSecureContext({ + cert: cert, + key: key, + }); + } const client = ldap.createClient({ url: target, From c1a587a93986a87903e4c7040e3e65abdf59c60c Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 19 Mar 2024 13:14:56 +0100 Subject: [PATCH 9/9] chore: set encoding to utf-8 Signed-off-by: blam --- plugins/catalog-backend-module-ldap/src/ldap/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend-module-ldap/src/ldap/client.ts b/plugins/catalog-backend-module-ldap/src/ldap/client.ts index 56cc18e2de..75e54013b3 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/client.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/client.ts @@ -48,8 +48,8 @@ export class LdapClient { ): Promise { let secureContext; if (tls && tls.certs && tls.keys) { - const cert = await readFile(tls.certs).then(f => f.toString()); - const key = await readFile(tls.keys).then(f => f.toString()); + const cert = await readFile(tls.certs, 'utf-8'); + const key = await readFile(tls.keys, 'utf-8'); secureContext = tlsLib.createSecureContext({ cert: cert, key: key,