Bug 49006 - OpenID-Connect Login at UMC
OpenID-Connect Login at UMC
Status: VERIFIED FIXED
Product: UCS
Classification: Unclassified
Component: UMC (Generic)
UCS 4.4
Other Linux
: P5 normal (vote)
: UCS 5.0-6-errata
Assigned To: Florian Best
Arvid Requate
https://git.knut.univention.de/univen...
:
Depends on:
Blocks: 57016
  Show dependency treegraph
 
Reported: 2019-03-15 11:53 CET by Florian Best
Modified: 2024-02-15 11:40 CET (History)
3 users (show)

See Also:
What kind of report is it?: Feature Request
What type of bug is this?: ---
Who will be affected by this bug?: ---
How will those affected feel about the bug?: ---
User Pain:
Enterprise Customer affected?:
School Customer affected?:
ISV affected?:
Waiting Support:
Flags outvoted (downgraded) after PO Review:
Ticket number:
Bug group (optional):
Max CVSS v3 score:
best: Patch_Available+


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Florian Best univentionstaff 2019-03-15 11:53:24 CET
The UMC-Server doesn't provide a login via OpenID connect.
Would be nice to implement this.
Comment 1 Florian Best univentionstaff 2023-06-09 16:41:53 CEST
A implementation is available at: https://github.com/univention/univention-corporate-server/tree/fbest/43633-unify-umc-server-oidc
Comment 2 Florian Best univentionstaff 2023-06-19 15:16:39 CEST
(In reply to Florian Best from comment #1)
> A implementation is available at:
> https://github.com/univention/univention-corporate-server/tree/fbest/43633-
> unify-umc-server-oidc

This has been moved to MR https://git.knut.univention.de/univention/ucs/-/merge_requests/802.
Comment 4 Florian Best univentionstaff 2024-01-24 17:17:26 CET
> commit 531ca18e924e8b425513f8b62ab9d9ba3c7cd0a8

feat(crudeoauth): Implement crudeoauth

Introduce a new project `crudeoauth`, which is a homage to `crudesaml`
but instead validating OAuth 2.0 Access Tokens in form of a JWT.
It is following RFC 7628 (A Set of Simple Authentication and Security Layer (SASL) Mechanisms for OAuth)
to provide a SASL mechanism `OAUTHBEARER` in the package `libsasl2-modules-oauthbearer`.
This is suitable to be used the openldap server via the
`/etc/ldap/sasl2/slapd.conf` configuration file.
It provides configuration options like:
```
mech_list: … OAUTHBEARER
oauthbearer_grace: 3
oauthbearer_userid: preferred_username
oauthbearer_trusted_jwks0: /usr/share/oidc/file_containing_the_authorization_server_certificates_as.jwks
oauthbearer_trusted_iss0: https://sso.example.org/realms/master
oauthbearer_trusted_aud0: ldaps://example.org/
oauthbearer_trusted_azp0: https:/client.example.org/oidc/
```

The username is read from the access token and used as `authcid`.
A optional `authzid` might be provided, and is used if the LDAP server allows it.
Example configuration:
```
authz-regexp
    uid=([^,]*),cn=oauthbearer,cn=auth
    ldap:///dc=example,dc=org??sub?uid=$1
```

Additionally a PAM module `pam_oauthbearer.so` is provided in the package `libpam-oauthbearer`, doing the same validations and having equivalent configuration options via the pam stack definition.
```
auth sufficient pam_oauthbearer.so grace=3 userid=preferred_username iss=https://sso.example.org/realms/master jwks=/usr/share/oidc/file_containing_the_authorization_server_certificates_as.jwks trusted_aud=ldaps://example.org/ trusted_azp=https:/client.example.org/oidc/
```
univention/customers/dataport/upx/iam-team#18

> commit ae19809ce37cbcb93b005c989610a99128822b78

feat(uldap): add bind_oauthbearer(authzid, access_token)

provide `bind_oauthbearer(authzid, access_token)` method for
`univention.uldap.access` and `univention.admin.uldap.access`
which does a SASL LDAP bind using the `OAUTHBEARER` scheme.
The `authzid` is optional and should be given as None. Maybe in the
future this is helpful to impersonate other users for certain service
accounts (e.g. the Portal).

> commit 4a1941af242bfdc65f174430a69f42cd90664ed8

feat(ldap): integrate libsasl2-modules-oauthbearer into LDAP server

Configure `slapd` to provide the `OAUTHBEARER` SASL mechanism.
By configuring `/etc/ldap/sasl2/slapd.conf` via the UCR variables
`ldap/server/sasl/oauthbearer/.*` and map
`uid=.*,cn=oauthbearer,cn=auth` to users in the `authz-regexp`.

TODO: we should move
`management/univention-management-console/conffiles/etc/ldap/sasl2/slapd.conf` to the `univention-ldap` package.

> commit d6203df6682d34355c7c15358608db1963463529

feat(umc): Implement OpenID Connect authentication for UMC

Implement UMC as OpenID Connect relying party.
Authentication is done via the Authorization Code Flow, which is extended with PKCE (Proof Code for Code Exchange).
For private clients PKCE is necessary to prevent "authorization code injection" in certain scenarios.

It provides user initiated logout, which will make a global logout at
the OpenID-Provider. Both frontchannel and backchannel is implemented,
while UMC only uses frontchannel.

It provides a Single Logout endpoint for logouts initiated by the OpenID Provider,
e.g. when another App logs out or manually via the Keycloak Admin Console.
Both frontchannel and backchannel is implemented, while the Relying
Party is configured to do the backchannel logout.

The ID token is verified using the keys from the OpenID provider, which
are downloaded when configuring the issuer via UCR. This happens during
the executing the joinscript, but only if keycloak is installed.
The upgrade doesn't increase the joinscript version. To make it work
manually after the latest version of Keycloak is installed,
`univention-run-join-scripts --force --run-scripts 92univention-management-console-web-server.inst` can be executed.

This also sets up the OAUTHBEARER SASL module configuration for the LDAP server and the PAM configuration for the UMC PAM stack.
And a pseudo-audience `ldap://$domainname` is added to the UMC Relying Party client.
The SASL and PAM module will require one of the audience `ldap://$domainname` or `ldap://$hostname.$domainname`.

UMC additionally verifies the authorized party (azp) of the ID token,
which must match the Client ID of UMC.
At the authorization server, the user must consent once to give access to UDM
REST API and LDAP server while logging in.

For easier testing purposes and probably future service-to-service
communication UMC also acceept HTTP Bearer Authentication with a Access
Token as JWT, which must contain the `uid` claim. This will probably be
useful for the portal.

Currently we cannot access the userinfo endpoint because our Keycloak
doesn't provide the `openid` scope.

The access tokens are by default valid for 5 minutes, the refresh token
for 30 minutes. If a request comes in after the access token is expired
it is tried to get a new one using the refresh token - via backchannel
operation which doesn't involve the browser.

Via `/univention/oidc/.well-known/outh-client` the client metadata,
suitable for Dynamic Client Registration can be obtained.
The URL is officially incorrect, as it must be `/.well-known/outh-client/univention/oidc` instead.

Testing can be done via:

1. Login via Browser Authorization Code Flow with PKC
`firefox http://FQDN/univention/oidc/`

2. Login via HTTP Bearer authentication, using "Resource Owner Password Credentials" grant.
```bash
client_id=$(ucr get umc/oidc/$(hostname -f)/client-id)
token=$(curl -s "https://$(ucr get keycloak/server/sso/fqdn)/realms/ucs/protocol/openid-connect/token" -d "grant_type=password&username=Administrator&password=univention&scope=openid&client_id=$client_id&client_secret=$(cat /etc/umc-oidc.secret)" | jq -r '.access_token')
echo "$token" | python3 -m univention.management.console.oauth
curl http://localhost/univention/command/udm/query -H "Authorization: Bearer $token" -H 'Content-Type: application/json' -d '{"options": {"objectProperty": "username", "objectPropertyValue":"Administrator"},"flavor": "users/user"}'
```

> commit 5d3f345c81d7a3e369b03c3ed8fa15d9c3903906

feat(udm-rest): Add OAuth 2.0 authorization for UDM REST API

Implement the UDM REST API as OAuth 2.0 Resource Server by
accepting Access Tokens using the HTTP Bearer authentication schema.

The UDM REST API just forwards the access token to the LDAP server by
using the OAUTHBEARER SASL mechanism for the LDAP bind.
Therefor UDM REST API is something like a proxy/gateway and not a OAuth
2.0 client or OIDC Relying Party.

The access tokens created by the UMC Relying Party are suitable to use
this, as they contain the required `ldap://$domainname/` audience.
The access token is accessible by all UMC modules.

Example usage for test cases:
```bash
client_id=$(ucr get umc/oidc/$(hostname -f)/client-id)
secret=$(cat /etc/umc-oidc.secret)"
token=$(curl -s "https://$(ucr get keycloak/server/sso/fqdn)/realms/ucs/protocol/openid-connect/token" -d "grant_type=password&username=Administrator&password=univention&scope=openid&client_id=$client_id&client_secret=$secret" | jq -r '.access_token')
echo "$token" | python3 -m univention.management.console.oidc
curl -H 'Accept: application/json' http://localhost/univention/udm/ldap/base/ -H "Authorization: Bearer $token" -i
```

> commit 7fcefb436a922fdc74e6b8a41274a4cb78110a45

refactor(keycloak): unify JSON printing

> commit 7aa7fe69345046db892f1834ea36f222ee554de5

feat(keycloak): Add more required oidc/rp options

* add various `oidc/rp` creation parameters
* add `oidc/rp get --client-id …`
* add `oidc/rp remove`
* add `secrets --as-json`



yder.yaml
531ca18e924e | feat(crudeoauth): Implement crudeoauth

univention-python.yaml
ae19809ce37c | feat(uldap): add bind_oauthbearer(authzid, access_token)

univention-python (13.0.5-1)
ae19809ce37c | feat(uldap): add bind_oauthbearer(authzid, access_token)

univention-management-console.yaml
d6203df6682d | feat(umc): Implement OpenID Connect authentication for UMC

univention-management-console-module-diagnostic.yaml
d6203df6682d | feat(umc): Implement OpenID Connect authentication for UMC

univention-management-console-module-diagnostic (6.0.7-2)
d6203df6682d | feat(umc): Implement OpenID Connect authentication for UMC

univention-management-console (12.0.32-9)
f1f8472333f3 | Bug #49006: make versioned dependencies
d6203df6682d | feat(umc): Implement OpenID Connect authentication for UMC

univention-management-console (12.0.32-8)
4a1941af242b | feat(ldap): integrate libsasl2-modules-oauthbearer into LDAP server

univention-ldap.yaml
4a1941af242b | feat(ldap): integrate libsasl2-modules-oauthbearer into LDAP server

univention-ldap (16.0.14-3)
4a1941af242b | feat(ldap): integrate libsasl2-modules-oauthbearer into LDAP server

univention-keycloak.yaml
7aa7fe693450 | feat(keycloak): Add more required oidc/rp options

univention-keycloak (1.0.10-4)
7fcefb436a92 | refactor(keycloak): unify JSON printing
7aa7fe693450 | feat(keycloak): Add more required oidc/rp options

univention-directory-manager-rest.yaml
5d3f345c81d7 | feat(udm-rest): Add OAuth 2.0 authorization for UDM REST API

univention-directory-manager-rest (10.0.7-5)
f1f8472333f3 | Bug #49006: make versioned dependencies
5d3f345c81d7 | feat(udm-rest): Add OAuth 2.0 authorization for UDM REST API

univention-directory-manager-modules.yaml
ae19809ce37c | feat(uldap): add bind_oauthbearer(authzid, access_token)

univention-directory-manager-modules (15.0.25-3)
f1f8472333f3 | Bug #49006: make versioned dependencies
ae19809ce37c | feat(uldap): add bind_oauthbearer(authzid, access_token)

ulfius.yaml
531ca18e924e | feat(crudeoauth): Implement crudeoauth

ucs-test (4.0.164-21)
r49006 | 10_ldap/49replication_modrdn_with_previous_modification: modify and

ucs-test (10.0.20-16)
046f9ed5dbae | test(keycloak): refactor conftest

orcania.yaml
531ca18e924e | feat(crudeoauth): Implement crudeoauth

crudeoauth.yaml
531ca18e924e | feat(crudeoauth): Implement crudeoauth

crudeoauth (1.0.0-1)
531ca18e924e | feat(crudeoauth): Implement crudeoauth
Comment 5 Florian Best univentionstaff 2024-01-24 19:08:50 CET
How to enable it?
univention-app install keycloak
univention-run-join-scripts --force --run-scripts 92univention-management-console-web-server.inst
ucr set 'umc/web/oidc/enabled'=true
Comment 6 Florian Best univentionstaff 2024-01-24 20:29:58 CET
for crudeoauth we took the rhonabwy version from Debian bookwork, because it fixed some security issue regarding null-signature.
We also had to apply the patch https://github.com/babelouest/rhonabwy/commit/84cebbd6b049a6e861810128a132efab1de4d3df (fix r_jwt_validate_claims for aud claim (Closes: #34)).

The package and it's build dependencies have been added to:
https://hutten.knut.univention.de/mediawiki/index.php/Security_Updates#Spezielle_Pakete

Everything has been build for UCS 5.1 and UCS 5.2 as well.
Comment 7 Julia Bremer univentionstaff 2024-01-28 19:28:40 CET
REOPEN: slapd won't start anymore if 92univention-management-console-web-server.inst is updated and run if keycloak is installed in domain

slapd only says
backup2 slapd[29032]: 65b69a02 slap_sasl_init: server init failed slapschema: slap_init failed!.

and won't get up anymore until I unset these ucr variables, the joinscript sets if keycloak is installed in the domain.

"ldap/server/sasl/oauthbearer/trusted-audience/$domainname?ldaps://$domainname/"
"ldap/server/sasl/oauthbearer/trusted-audience/$hostname.$domainname?ldaps://$hostname.$domainname/"
"ldap/server/sasl/oauthbearer/trusted-issuer/$hostname.$domainname?$(ucr get umc/oidc/issuer)"



The sasl2/slapd.conf looks like this in my domain:
mech_list: EXTERNAL gssapi DIGEST-MD5 CRAM-MD5 LOGIN SAML OAUTHBEARER PLAIN
saml_grace: 600
saml_userid: urn:oid:0.9.2342.19200300.100.1.1
saml_idp0: /usr/share/univention-management-console/saml/idp/ucs-sso-ng.ucs.test.xml
saml_trusted_sp0: https://master.ucs.test/univention/saml/metadata
saml_trusted_sp1: https://backup.ucs.test/univention/saml/metadata
saml_trusted_sp2: https://backup2.ucs.test/univention/saml/metadata
saml_trusted_sp3: https://member.ucs.test/univention/saml/metadata
saml_trusted_sp4: https://slave.ucs.test/univention/saml/metadata
oauthbearer_grace: 3
oauthbearer_userid: uid
oauthbearer_trusted_jwks0: /usr/share/univention-management-console/oidc/*.jwks
oauthbearer_trusted_iss0:
oauthbearer_trusted_aud0: ldaps://master.ucs.test/
oauthbearer_trusted_aud1: ldaps://ucs.test/
oauthbearer_trusted_azp0: https://backup.ucs.test/univention/oidc/
oauthbearer_trusted_azp1: https://backup2.ucs.test/univention/oidc/
oauthbearer_trusted_azp2: https://master.ucs.test/univention/oidc/


oauthbearer_trusted_jwks0 looks wrong but removing that line or
setting umc/oidc/issuer and setting ldap/server/sasl/oauthbearer/trusted-jwks/hostname.domainname manually results in this (better looking?) line 
oauthbearer_trusted_jwks0: https%3A%2F%2Fucs-sso-ng.ucs.test%2Frealms%2Fucs.jwks

but still slapd won't start.
Comment 8 Julia Bremer univentionstaff 2024-01-28 20:28:21 CET
Ah correcting oauthbearer_trusted_jwks0 to reference the specific file 
and setting ldap/server/sasl/oauthbearer/trusted-issuer/hostname.domainname again to $(ucr get umc/oidc/issuer) works.


umc/oidc/issuer is not set yet, when that UCR is created in the joinscript. And it is only set if umc/web/oidc/enabled is set.
So slapd is broken in the default case (if keycloak is installed).


These two issues need to be fixed.
Also, the sasl module logs should be a little more helpful in these cases. It is tedious to guess what went wrong.
Comment 9 Florian Best univentionstaff 2024-01-28 23:46:21 CET
Oh yes, that was some broken by some latest refactoring of the joinscript, which broke the order in which things happened.

Fixed in:
univention-management-console (12.0.32-15)
95967f4a7cd8 | fix(umc): fix order of OIDC setup in joinscript

TBD: slapd should start if the SASL module configuration conaints invalid data  / files.
Comment 10 Florian Best univentionstaff 2024-01-29 15:13:47 CET
(In reply to Julia Bremer from comment #8)
> Also, the sasl module logs should be a little more helpful in these cases.
> It is tedious to guess what went wrong.

This was not an issue of the SASL module but the parsing of the SASL configuration file itself.
The problem was, that it contained `oauthbearer_trusted_iss0: `.
Therefore we cannot enhance the log of this situation.

Instead the UCR template has been adjusted so that it's impossible to reach this situation.

univention-management-console (12.0.32-16)
73fbce8c7a02 | fix(umc): hardening the UCR template for sasl configuration
Comment 11 Arvid Requate univentionstaff 2024-01-30 23:34:13 CET
Verified:
* Not activated/loaded by default
* OAUTHBEARER in UCR default for mech_list but filtered out in UCR template if essential ldap/server/sasl/oauthbearer/* UCR variables are not set
* Code review (mostly the C part)
* Functional test (mostly the C part)
* Package update
* Developer docs under management/univention-management-console/README.md
* No product documentation/support yet

New external dependencies taken from Debian bookworm and added to https://hutten.knut.univention.de/mediawiki/index.php/Security_Updates#Spezielle_Pakete
* rhonabwy (JWK, JWKS, JWS, JWE and JWT library)
  https://babelouest.github.io/rhonabwy/
* orcania (general purpose C library)
  https://babelouest.github.io/orcania/
* yder (logging library written in C)
  https://babelouest.github.io/yder/
* ulfius (Web Framework for REST Applications in C)
  https://babelouest.github.io/ulfius/

Advisories:
* crudeoauth.yaml
  Dependencies:
  * orcania.yaml
  * ulfius.yaml
  * yder.yaml

* univention-management-console.yaml
* univention-ldap.yaml
* univention-directory-manager-rest.yaml
* univention-directory-manager-modules.yaml
* univention-keycloak.yaml
* univention-management-console-module-diagnostic.yaml
* univention-python.yaml

New UCR variables:
* ldap/server/sasl/mech_list
* ldap/server/sasl/oauthbearer/trusted-audience/*
* ldap/server/sasl/oauthbearer/trusted-issuer/*
* ldap/server/sasl/oauthbearer/required-scopes/*
* ldap/server/sasl/oauthbearer/trusted-jwks/*
* ldap/server/sasl/oauthbearer/trusted-authorized-party/*
* ldap/server/sasl/oauthbearer/grace-time
* umc/web/oidc/enabled
* umc/oidc/default-op
* umc/oidc/.*/issuer
* umc/oidc/.*/client-id
* umc/oidc/.*/client-secret-file
* umc/oidc/.*/openid-configuration
* umc/oidc/.*/openid-certs

New UCR category:
* oidc

New UCR module
* setup_oidc_rp.py (triggered by umc/oidc/issuer)
Comment 13 Florian Best univentionstaff 2024-01-31 10:27:48 CET
(In reply to Julia Bremer from comment #12)
> Hi, since yesterday the python2/3 compatibility test for the 
> sasl2/slapd.conf  started failing, could you have a look?

Hm, don't know how this can happen. I added the default value as current workaround:

univention-management-console (12.0.32-18)
c691de1fa19c | fix(umc): workaround for not detected default value of UCR value

→ no diff for py2/py3:
diff <(ucr filter < /etc/univention/templates/files/etc/ldap/sasl2/slapd.conf) <(python $(which ucr) filter < /etc/univention/templates/files/etc/ldap/sasl2/slapd.conf)
Comment 14 Arvid Requate univentionstaff 2024-01-31 13:38:45 CET
Ok that works.