Project: Backstage (App Definition and Development / Application Definition & Image Build)
File: org.md

id: org title: LDAP Organizational Data sidebar_label: Org Data description: Setting up ingestion of organizational data from LDAP The Backstage catalog can be set up to ingest organizational data - users and groups - directly from an LDAP compatible service. The result is a hierarchy of and kind entities that mirror your org setup. Backstage in general supports OpenLDAP compatible vendors, as well as Active Directory and FreeIPA. If you are using a vendor that does not seem to be supported, please . This guide will use the Entity Provider method. If you for some reason prefer the Processor method (not recommended), it is described separately below. The provider is not installed by default, therefore you have to add a dependency to `@backstage/plugin-catalog-backend-module-ldap` to your backend package. ```bash yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-ldap ``` Note: When configuring to use a Provider instead of a Processor you do not need to add a location pointing to your LDAP server Update the catalog plugin initialization in your backend to add the provider and schedule it: ```ts title="packages/backend/src/plugins/catalog.ts" / highlight-add-next-line / import { LdapOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-ldap'; export default async function createPlugin( env: PluginEnvironment, ): Promise<Router> { const builder = await CatalogBuilder.create(env); / highlight-add-start / // The target parameter below needs to match the ldap.providers.target // value specified in your app-config. builder.addEntityProvider( LdapOrgEntityProvider.fromConfig(env.config, { id: 'our-ldap-master', target: 'ldaps://ds.example.net', logger: env.logger, schedule: env.scheduler.createScheduledTaskRunner({ frequency: { minutes: 60 }, timeout: { minutes: 15 }, }), }), ); / highlight-add-end / // .. } ``` After this, you also have to add some configuration in your app-config that describes what you want to import for that target. The following configuration is a small example of how a setup could look for importing groups and users from a corporate LDAP server. ```yaml ldap: providers: target: ldaps://ds.example.net bind: dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net secret: ${LDAP_SECRET} users: dn: ou=people,ou=example,dc=example,dc=net options: filter: (uid=*) map: description: l set: metadata.customField: 'hello' groups: dn: ou=access,ou=groups,ou=example,dc=example,dc=net options: filter: (&(objectClass=some-group-class)(!(groupType=email))) map: description: l set: metadata.customField: 'hello' ``` There may be many providers, each targeting a specific `target` which is supposed to match the `target` of a dedicated provider instance - i.e., you will add one entity provider class instance per target to ingest from. These config blocks have a lot of options in them, so we will describe each "root" key within the block separately. 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. `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. `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

The bind block specifies how the plugin should bind (essentially, to authenticate) towards the server. It has the following fields. ```yaml dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net secret: ${LDAP_SECRET} ``` The `dn` is the full LDAP Distinguished Name for the user that the plugin authenticates itself as. At this point, only regular user based authentication is supported. The `secret` is the password of the same user. In this example, it is given in the form of an environment variable `LDAP_SECRET`, that has to be set when the backend starts. The `users` block defines the settings that govern the reading and interpretation of users. Its fields are explained in separate sections below. The DN under which users are stored, e.g. `ou=people,ou=example,dc=example,dc=net`. The search options to use when sending the query to the server, when reading all users. All the options are shown below, with their default values, but they are all optional. ```yaml options: scope: one filter: (uid=*) attributes: ['*', '+'] paged: false ``` This optional piece lets you specify a number of JSON paths (on a.b.c form) and hard coded values to set on those paths. This can be useful for example if you want to hard code a namespace or similar on the generated entities. ```yaml set: metadata.namespace: 'ldap' ``` Mappings from well known entity fields, to LDAP attribute names. This is where you are able to define how to interpret the attributes of each LDAP result item, and to move them into the corresponding entity fields. All the options are shown below, with their default values, but they are all optional. If you leave out an optional mapping, it will still be copied using that default value. For example, even if you do not put in the field `displayName` in your config, the provider will still copy the attribute `cn` into the entity field `spec.profile.displayName`. ```yaml map: rdn: uid name: uid description: description displayName: cn email: mail picture: <nothing, left out> memberOf: memberOf ``` The `groups` block defines the settings that govern the reading and interpretation of groups. Its fields are explained in separate sections below. The DN under which groups are stored, e.g. `ou=people,ou=example,dc=example,dc=net`. The search options to use when sending the query to the server, when reading all groups. All the options are shown below, with their default values, but they are all optional. ```yaml options: scope: one filter: (&(objectClass=some-group-class)(!(groupType=email))) attributes: ['*', '+'] paged: false ``` This optional piece lets you specify a number of JSON paths (on a.b.c form) and hard coded values to set on those paths. This can be useful for example if you want to hard code a namespace or similar on the generated entities. ```yaml set: metadata.namespace: 'ldap' ``` Mappings from well known entity fields, to LDAP attribute

This is where you are able to define how to interpret the attributes of each LDAP result item, and to move them into the corresponding entity fields. All of the options are shown below, with their default values, but they are all optional. If you leave out an optional mapping, it will still be copied using that default value. For example, even if you do not put in the field `displayName` in your config, the provider will still copy the attribute `cn` into the entity field `spec.profile.displayName`. If the target field is optional, such as the display name, the importer will accept missing attributes and just leave the target field unset. If the target field is mandatory, such as the name of the entity, validation will fail if the source attribute is missing. ```yaml map: rdn: cn name: cn description: description type: groupType displayName: cn email: <nothing, left out> picture: <nothing, left out> memberOf: memberOf members: member ``` In case you want to customize the ingested entities, the provider allows to pass transformers for users and groups. Here we will show an example of overriding the group transformer. Create a transformer: ```ts export async function myGroupTransformer( vendor: LdapVendor, config: GroupConfig, group: SearchEntry, ): Promise<GroupEntity | undefined> { // Transformations may change namespace, change entity naming pattern, fill // profile with more or other details... // Create the group entity on your own, or wrap the default transformer return await defaultGroupTransformer(vendor, config, group); } ``` Configure the provider with the transformer: ```ts const ldapEntityProvider = LdapOrgEntityProvider.fromConfig(env.config, { id: 'our-ldap-master', target: 'ldaps://ds.example.net', logger: env.logger, groupTransformer: myGroupTransformer, }); ``` An alternative to using the Provider for ingesting LDAP entries is to use a Processor. This is the old way that's based on registering locations with the proper type and target, triggering the processor to run. The drawback of this method is that it will leave orphaned Group/User entities whenever they are deleted on your LDAP server, and you cannot control the frequency with which they are refreshed, separately from other processors. The `LdapOrgReaderProcessor` is not registered by default, so you have to register it in the catalog plugin: ```typescript title="packages/backend/src/plugins/catalog.ts" builder.addProcessor( LdapOrgReaderProcessor.fromConfig(env.config, { logger: env.logger, }), ); ``` Locations point out the specific org(s) you want to import. The `type` of these locations must be `ldap-org`, and the `target` must point to the exact URL (starting with `ldap://` or `ldaps://`) of the targeted LDAP server. You can have several such location entries if you want, but typically you will have just one. ```yaml catalog: locations: type: ldap-org target: ldaps://ds.example.net rules: allow: [User, Group] ``` To sync Google Workspace/Cloud Identity organization data to users and groups in backstage, you must 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: groups: ```