Importing Users and Groups in Tanzu Developer Portal (TAP-GUI)

TDP is an amazing solution built on top of backstage, and the declarative nature in which it is configured and the entities are added is a huge benefit in my mind.

With that said, one of the pain points this brings is user management.

Users and Groups are entity types in backstage just log components and systems, and as such the default way of adding them to your system is to create YAML manifests defining users and groups, and then to register them into backstage. While this is possible to do, the challenge in large organizations is that the need to define these users and groups in multiple places is a big burden and always leads to misconfigurations and lack of consistency across different systems.

It is important to clearly define what we are talking about in this post. TDP since its initial release has supported configuration of login providers which can be nearly any OIDC compliant IDP, as well as a few other key IDPs. This allows us to for example add a login mechanism supporting logins from GitHub, Azure AD, Okta, Gitlab etc.

While that part is solved (except LDAP which is not possible OOTB), the syncing of users and groups from an external identity provider into backstage as user and group entities has been a challenge for many customers.

Recently i discovered that while not documented, there actually is a way of ingesting users automatically from any LDAP server and from Azure AD (now Entra ID) in TAP since the very early releases. In this blog post we will see how we can configure this to work.

Before we dive into the configuration, we need to understand a few terms used in backstage that are relevant to this process:

  1. Catalog Processor – The catalog has a concept of processors to perform catalog ingestion tasks, such as reading raw entity data from a remote source, parsing it, transforming it, and validating it. These processors are configured under the catalog.processors configuration key
  2. Catalog Provider – Similar to a processor but is the newer and more recommended approach, entity providers sit at the very edge of the catalog. They are the original sources of entities that form roots of the processing tree. The dynamic location store API, and the static locations you can specify in your app-config, are two examples of builtin providers in the catalog.
  3. Catalog Location – The catalog holds a number of registered locations, that were added either by site admins or by individual Backstage users. Their purpose is to reference some sort of data that the catalog shall keep itself up to date with. Each location has a type, and a target that are both strings. A location is used heavily in processors, as they define the data which is passed to the ingestion process in which the processor is executed.

As of the early releases of TDP, we have an unspoken treasure in the system, which is the Azure AD and LDAP catalog processors. With these options we can have our users and groups synced automatically from our external IDP into backstage constantly.

Lets see how we can configure this for LDAP:

LDAP Integration

To get started with the LDAP configuration we need to get some data about our LDAP server which in my case is an instance of Active Directory. The details we will need are:

  1. what protocol to use (LDAP or LDAPS)
  2. the FQDN of our LDAP server
  3. the Binding users DN and password
  4. the base DN for users and for groups

Once we have that information we can add the needed configuration in our TAP values.

Under the tap_gui.app_config section we most likely already have a catalog key where we have defined some locations. for our use case we will add another key under the catalog key called “processors” under which we will configure our LDAP processor:

catalog:
  processors:
    ldapOrg:
      providers:
      - target: ldap://FQDN_OF_YOUR_LDAP_SERVER
        bind:
          dn: "DN_OF_YOUR_BIND_USER"
          secret: "PASSWORD_OF_YOUR_BIND_USER"
        users:
          dn: "BASE_DN_FOR_USER_SEARCHING"
          options:
            scope: sub
            filter: "(objectClass=person)"
          map:
            description: l
            name: sAMAccountName
            rdn: sAMAccountName
        groups:
          dn: "BASE_DN_FOR_GROUP_SEARCHING"
          options:
            scope: sub
            filter: "(objectClass=group)"
          map:
            rdn: sAMAccountName
            name: sAMAccountName
            description: l

An example configuration could look like:

catalog:
  processors:
    ldapOrg:
      providers:
      - target: ldaps://demo-ad.vrabbi.demo
        bind:
          dn: "CN=Scott Rosenberg,OU=Users,DC=vrabbi,DC=demo"
          secret: "MyS3cR3tP@ssw0rd"
        users:
          dn: "DC=vrabbi,DC=demo"
          options:
            scope: sub
            filter: "(objectClass=person)"
          map:
            description: l
            name: sAMAccountName
            rdn: sAMAccountName
        groups:
          dn: "DC=vrabbi,DC=demo"
          options:
            scope: sub
            filter: "(objectClass=group)"
          map:
            rdn: sAMAccountName
            name: sAMAccountName

With that configured we now have told backstage how to process users and groups from that specific LDAP environment, but we still need to tell backstage to pull in data in the first place which as mentioned above, is done via catalog locations.
Just like with a standard git location for a base catalog, we will configure another entry under tap_gui.app_config.catalog.locations like bellow:

locations:
  - type: ldap-org
    target: ldap://YOUR_LDAP_SERVER_FQDN
    rules:
      - allow: [User, Group]

As we can see we are creating a location of type ldap-org, and then simply providing the target just as it is provided in the processor above.
The rules section is where we can specify that this location is only allowed to register entities of specific types into our environment which in the case of LDAP makes sense to configure as users and groups.

Once we have this configured we can simply update our TAP installation with the new values and within a few minutes, your LDAP users and groups will be available within your TDP instance!

Azure AD Integration

For Azure AD the prinicipals are the same as above, we just need to do a bit of work on the Azure AD side first and then we can configure our instance.

The work we must do in Azure AD is to create a App Registration. This app must have the following permissions (they cant be delegated) for Microsoft Graph:

  • User.Read.All
  • GroupMember.Read.All

In many cases you will need administrative consent to allow these permissions. Once we have that configured we then need to generate a client secret for our app registration, and collect our client ID and tenant ID settings.

With all of that data we can configure our Azure AD processor:

catalog:
  processors:
    microsoftGraphOrg:
      providers:
      - target: "https://graph.microsoft.com/v1.0"
        authority: "https://login.microsoftonline.com"
        tenantId: "YOUR_AZURE_AD_TENANT_ID"
        clientId: "YOUR_AZURE_AD_APP_REGISTRATION_CLIENT_ID"
        clientSecret: "YOUR_AZURE_AD_APP_REGISTRATION_CLIENT_SECRET"
        userFilter: "accountEnabled eq true and userType eq 'member'"
        groupFilter: "mailEnabled eq true"
        userSelect:
        - id
        - displayName
        - description
        groupSelect:
        - id
        - displayName
        - description

And we can then configure our location as well to point at the graph API:

  locations:
  - type: microsoft-graph-org
    target: https://graph.microsoft.com/v1.0
    rules:
      - allow: [Group, User]

With all of this configured we can update our TAP installation with the new values and within a few minutes we should see all of our users and groups synced into our TDP instance!

Summary

This capability is extremely powerful and when using Azure AD can and should be easily integrated alongside Azure AD authentication using the built-in integration, allowing for a seamless end to end user management mechanism.

For LDAP, I would suggest looking into deploying a simple solution like Dex, which can provide an OIDC interface above LDAP and integrate backstage to use Dex as the backend IDP for logins. You can also take a look at the community ldap-auth plugin for backstage which could be wrapped up via a TDP wrapper and integrated into your environment using the TDP configurator, but currently i believe that using Dex will provide a better experience, with less overhead and much easier to configure making it the better solution currently for this use case.

Hopefully this will help you better customize your TDP instance, and make the adoption smoother for you and your users!

Leave a Reply

Discover more from vRabbi's Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading