Migration from LDAP to OIDC: Forgejo

en
This article is part of a series:
  1. Forgejo
  2. Nextcloud
  3. Matrix

In my hackerspace we operate various services for our members. Up until this month, most of these services used to do user authentication against a LDAP server. For a multitude of reasons, we replaced the LDAP server with an OpenID Connect SSO using a Keycloak server as the OIDC Identity Provider.

In this series of articles I summarize the efforts required to migrate each of the services from LDAP to OIDC authentication. This article covers the setup and migration in Forgejo.

OIDC Setup in Forgejo

Forgejo is a fork of the Gitea Git hosting platform that has emerged recently as a result of the takeover of the Gitea project by for-profit Gitea Ltd. Since both projects are still mostly identical at the time of writing, this section should apply 1:1 to Gitea as well.

To get started, sign into Forgejo as an admin user and create a new authentication source in the settings:

  • Choose OAuth2 as the authentication type and OpenID Connect as OAuth2 provider.
  • Fill in the Client ID and Client Secret as well as the auto-discovery URL.
  • Use at least openid profile email as the scope, and expand as necessary.

Once the authentication source is created and enabled, users can start signing in via OIDC. If a user has already signed in before using their LDAP password, they will be prompted for said password in order to "link" their OIDC user to the existing LDAP user. Whether users actually do this or not does not affect the migration.

Migration of Users From LDAP to OIDC

To understand the next migration steps, we first need to get an overview of the different types of names used to identify users in Forgejo:

  • The username is the name users are known as within Forgejo. You can consider this name to be a primary key (though not exactly true at the database schema level).
  • The display name is the name shown in the web UI.
  • The authentication sign-in name is the name provided by the authentication source.

This decoupling of the username and authentication name simplifies the migration a lot. With an LDAP authentication source, the authentication name is configurable - usually you'd choose an LDAP attribute such as uid or sAMAccoutName or (if you are really concerned about all-time uniqueness) objectGUID.

For the OIDC authentication source, the authentication name is not configurable - Forgejo insists on using the OIDC Subject Identifier (the sub key in the id_token). Keycloak generates an UUID for the subject identifier. Fortunately, Forgejo is smart enough to also consider the preferred_username token claim, and maps it to the Forgejo username - and prompts an already existing user for linking the account, as mentioned above.

However, since we wanted to disable LDAP authentication, this account linking would not have worked, so we needed to change the mapping for all users. For this, you need to obtain the subject ids and usernames of all users in Keycloak - this is most easily done by asking Keycloak's database (using PostgreSQL in this case):

$ sudo -u postgres psql keycloak
keycloak=# SELECT id, username FROM user_entity;
                  id                  | username
--------------------------------------+----------
 7ac0e93b-8725-4efa-95dc-0e20c6dbfa0a | s3lph
 8933480e-146f-4e7c-ac9c-9c8adfd69069 | s4lph
 d398d274-7d9a-45b3-96e7-96b8811afa72 | s5lph

If there's only a small number of users in your Forgejo instance, you could now edit every single one in the admin settings, and change its authentication source to the OIDC provider and the login name to this subject identifier. The more scalable approach, however, would be to transform this list into a series of UPDATE statements for Forgejo's database (in our case, MariaDB is used here):

BEGIN;
UPDATE user SET login_name = '7ac0e93b-8725-4efa-95dc-0e20c6dbfa0a', login_type = 6, login_source = 4 where lower_name = 's3lph';
UPDATE user SET login_name = '8933480e-146f-4e7c-ac9c-9c8adfd69069', login_type = 6, login_source = 4 where lower_name = 's4lph';
UPDATE user SET login_name = 'd398d274-7d9a-45b3-96e7-96b8811afa72', login_type = 6, login_source = 4 where lower_name = 's5lph';
COMMIT;

Note the additional columns login_type and login_source we need to modify:

  • login_type is the type of login source being used. This is 6 for all OAuth sources. The list of type identifiers can be found in the Forgejo source code.
  • login_source is the numeric id of the OIDC login source you created in Forgejo. You can see this ID in the list of authentication sources in the Forgejo admin settings.

Finally, if you configured Forgejo to import SSH public keys from LDAP, you need to remove the mapping from SSH keys to the login source they came from. Otherwise, users with such keys won't be able to open their SSH key settings anymore:

UPDATE public_key SET login_source_id = 0 WHERE login_source_id = 1;

Replace the login source ID in the WHERE clause with the ID of your LDAP authentiation source (again, to be found in the Forgejo admin settings).

This concludes the OIDC migration of Forgejo, and the LDAP authentication source can now be safely disabled or removed.

Tips and Tricks

If user self-registration is disabled on your Forgejo instance, you should add the following to your app.ini so that new OIDC users can sign into Forgejo without admin intervention:

[oauth2_client]
ENABLE_AUTO_REGISTRATION=true

Forgejo does not automatically redirect to the IdP for login - users always have to click the "Login with OpenID" button. If you want your users to be redirected to the SSO automatically when they try to log in, you have to configure this in your reverse proxy. For example, in Apache 2:

RewriteEngine on
RewriteCond %{QUERY_STRING} !local=1
RewriteCond %{REQUEST_METHOD} GET
RewriteRule ^/user/login$ /user/oauth2/oidc [L,R,QSD]

Replace the last URL component (/oidc) with the name you gave to your OIDC authentication source in Forgejo. The RewriteConds in this config allow you to append ?local=1 to disable the redirect, e.g. if you need to sign in as a local admin user.