Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Added

- Add support for OpenLDAP backend to user-info-fetcher ([#779]).

[#779]: https://github.com/stackabletech/opa-operator/pull/779

## [25.11.0] - 2025-11-07

## [25.11.0-rc1] - 2025-11-06
Expand Down
147 changes: 147 additions & 0 deletions deploy/helm/opa-operator/crds/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ spec:
- experimentalActiveDirectory
- required:
- experimentalEntra
- required:
- experimentalOpenLdap
properties:
experimentalActiveDirectory:
description: Backend that fetches user information from Active Directory
Expand Down Expand Up @@ -245,6 +247,151 @@ spec:
- clientCredentialsSecret
- tenantId
type: object
experimentalOpenLdap:
description: Backend that fetches user information from OpenLDAP
properties:
bindCredentials:
description: |-
Credentials for binding to the LDAP server.

The bind account is used to search for users and groups in the LDAP directory.
properties:
scope:
description: |-
[Scope](https://docs.stackable.tech/home/nightly/secret-operator/scope) of the
[SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass).
nullable: true
properties:
listenerVolumes:
default: []
description: |-
The listener volume scope allows Node and Service scopes to be inferred from the applicable listeners.
This must correspond to Volume names in the Pod that mount Listeners.
items:
type: string
type: array
node:
default: false
description: |-
The node scope is resolved to the name of the Kubernetes Node object that the Pod is running on.
This will typically be the DNS name of the node.
type: boolean
pod:
default: false
description: |-
The pod scope is resolved to the name of the Kubernetes Pod.
This allows the secret to differentiate between StatefulSet replicas.
type: boolean
services:
default: []
description: |-
The service scope allows Pod objects to specify custom scopes.
This should typically correspond to Service objects that the Pod participates in.
items:
type: string
type: array
type: object
secretClass:
description: '[SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) containing the LDAP bind credentials.'
type: string
required:
- secretClass
type: object
customAttributeMappings:
additionalProperties:
type: string
default: {}
description: Custom attributes, and their LDAP attribute names.
type: object
groupMemberAttribute:
default: member
description: |-
LDAP attribute on group objects that contains member references.

Common values:
- `member`: For `groupOfNames` objects (uses full DN)
- `memberUid`: For `posixGroup` objects (uses username)

Defaults to `member`.
type: string
groupsSearchBase:
description: |-
LDAP search base for groups, e.g. `ou=groups,dc=example,dc=org`.

If not specified, uses the main `searchBase`.
nullable: true
type: string
hostname:
description: Hostname of the LDAP server, e.g. `my.ldap.server`.
type: string
port:
description: Port of the LDAP server. If TLS is used defaults to `636`, otherwise to `389`.
format: uint16
maximum: 65535.0
minimum: 0.0
nullable: true
type: integer
searchBase:
default: ''
description: LDAP search base, e.g. `ou=users,dc=example,dc=org`.
type: string
tls:
description: Use a TLS connection. If not specified no TLS will be used.
nullable: true
properties:
verification:
description: The verification method used to verify the certificates of the server and/or the client.
oneOf:
- required:
- none
- required:
- server
properties:
none:
description: Use TLS but don't verify certificates.
type: object
server:
description: Use TLS and a CA certificate to verify the server.
properties:
caCert:
description: CA cert to verify the server.
oneOf:
- required:
- webPki
- required:
- secretClass
properties:
secretClass:
description: |-
Name of the [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) which will provide the CA certificate.
Note that a SecretClass does not need to have a key but can also work with just a CA certificate,
so if you got provided with a CA cert but don't have access to the key you can still use this method.
type: string
webPki:
description: |-
Use TLS and the CA certificates trusted by the common web browsers to verify the server.
This can be useful when you e.g. use public AWS S3 or other public available services.
type: object
type: object
required:
- caCert
type: object
type: object
required:
- verification
type: object
userIdAttribute:
default: entryUUID
description: LDAP attribute used for the user's unique identifier. Defaults to `entryUUID`.
type: string
userNameAttribute:
default: uid
description: LDAP attribute used for the username. Defaults to `uid`.
type: string
required:
- bindCredentials
- hostname
type: object
experimentalXfscAas:
description: |-
Backend that fetches user information from the Gaia-X
Expand Down
63 changes: 62 additions & 1 deletion docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Currently the following backends are supported:
* xref:#backend-keycloak[]
* xref:#backend-activedirectory[]
* xref:#backend-entra[]
* xref:#backend-openldap[]

[#backends]
== Backends
Expand Down Expand Up @@ -206,6 +207,66 @@ spec:
<2> The Entra tenant ID
<3> A secret containing the `clientId` and `clientSecret` keys

[#backend-openldap]
=== OpenLDAP

WARNING: The OpenLDAP backend is experimental, and subject to change.

Fetch user attributes and groups over LDAP from OpenLDAP servers using simple bind authentication.

OpenLDAP supports different group schemas:

* *groupOfNames* (default): Groups contain a `member` attribute with full user DNs
* *posixGroup*: Groups contain a `memberUid` attribute with usernames

[source,yaml]
----
spec:
clusterConfig:
userInfo:
backend:
experimentalOpenLdap: # <1>
hostname: openldap.default.svc.cluster.local # <2>
port: 1636 # <3>
searchBase: dc=example,dc=org # <4>
bindCredentials: # <5>
secretClass: openldap-bind-credentials # <6>
userIdAttribute: entryUUID # <7>
userNameAttribute: uid # <8>
groupsSearchBase: ou=groups,dc=example,dc=org # <9>
groupMemberAttribute: member # <10>
customAttributeMappings: # <11>
email: mail
displayName: cn
givenName: givenName
surname: sn
tls: # <12>
verification:
server:
caCert:
secretClass: openldap-tls # <13>
cache: # optional, enabled by default
entryTimeToLive: 60s # optional, defaults to 60s
----
<1> Enables the OpenLDAP backend
<2> The hostname of the LDAP server
<3> The port of the LDAP server. Defaults to `636` for LDAPS, or `389` for plain LDAP
<4> The base distinguished name to search. Users outside of this will not be seen
<5> Configuration for LDAP bind credentials
<6> The name of the SecretClass that provides the bind credentials. The secret must contain `user` and `password` keys
<7> LDAP attribute used for the user's unique identifier. Defaults to `entryUUID`
<8> LDAP attribute used for the username. Defaults to `uid`
<9> LDAP search base for groups. If not specified, uses the main `searchBase`
<10> LDAP attribute on group objects that contains member references. Use `member` for `groupOfNames` (default) or `memberUid` for `posixGroup`
<11> Arbitrary LDAP attributes can be requested to be fetched and returned in the user info response. Use this to map custom LDAP attributes to custom attribute names in the response
<12> Optional TLS configuration for secure LDAP connections
<13> The name of the SecretClass that contains the LDAP server's root CA certificate(s)

When retrieving user information from OpenLDAP, the user info fetcher first searches for the user by the `userNameAttribute` (defaults to `uid`) or `userIdAttribute` (defaults to `entryUUID`) depending on the request type. When a user is found, it searches for groups containing the user:

* If `groupMemberAttribute` is `memberUid`: Searches for groups where `memberUid` equals the username (for `posixGroup`)
* Otherwise (e.g. if `groupMemberAttribute` is `member`, which is the default): Searches for groups where `member` equals the user's full DN (for `groupOfNames`)

== User info fetcher API

User information can be retrieved from regorules using the functions `userInfoByUsername(username)` and `userInfoById(id)` in `data.stackable.opa.userinfo.v1`.
Expand All @@ -229,7 +290,7 @@ NOTE: The exact formats of `id` and `groups` will vary depending on the xref:#ba
=== Debug request

To debug the user-info-fetcher you can `curl` it's API for a given user.
To achieve this shell into the `user-info-fetcher` container and execute
To achieve this shell into the `opa` container and execute

[source,bash]
----
Expand Down
14 changes: 14 additions & 0 deletions rust/operator-binary/src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use stackable_operator::{
secret_class::{SecretClassVolume, SecretClassVolumeScope},
tls_verification::{TlsClientDetails, TlsClientDetailsError},
},
crd::authentication::ldap,
k8s_openapi::{
DeepMerge,
api::{
Expand Down Expand Up @@ -313,6 +314,11 @@ pub enum Error {
))]
UserInfoFetcherTlsVolumeAndMounts { source: TlsClientDetailsError },

#[snafu(display(
"failed to build volume or volume mount spec for the User Info Fetcher LDAP config"
))]
UserInfoFetcherLdapVolumeAndMounts { source: ldap::v1alpha1::Error },

#[snafu(display("failed to configure logging"))]
ConfigureLogging { source: LoggingError },

Expand Down Expand Up @@ -1072,6 +1078,14 @@ fn build_server_rolegroup_daemonset(
.add_volumes_and_mounts(&mut pb, vec![&mut cb_user_info_fetcher])
.context(UserInfoFetcherTlsVolumeAndMountsSnafu)?;
}
user_info_fetcher::v1alpha1::Backend::OpenLdap(openldap) => {
// Reuse the logic from the LDAP `AuthenticationProvider` which handles
// volume mounting of TLS secrets and LDAP bind credentials
openldap
.to_ldap_provider()
.add_volumes_and_mounts(&mut pb, vec![&mut cb_user_info_fetcher])
.context(UserInfoFetcherLdapVolumeAndMountsSnafu)?;
}
}

pb.add_container(cb_user_info_fetcher.build());
Expand Down
Loading
Loading