OpenShift and IPA Series
This post is part of a collection of blog posts related to OpenShift and FreeIPA (aka idM).
- OpenShift Certificate from IPA on RHEL 8
- OpenShift authentication with IPA
- OpenShift Group Syncing with IPA (Not yet published)
- Automated Certificate Management with IPA and cert-manager (Not yet published)
In the previous post, OpenShift Certificate from IPA on RHEL 8, I explained how to create certificates for an OpenShift cluster using FreeIPA/RHEL idM.
Now, I want to be able to log into my OpenShift cluster using my FreeIPA credentials.
OpenShift has the concept of an
IdentityProvider which connects an
source of identity verification, like FreeIPA’s LDAP server, to
OpenShift 4.x uses an object called an
OAuth to configure identity
providers like LDAP. For OpenShift 3.x, this configuration is
similar, but locaed in
Let’s look at an example OAuth manifest:
apiVersion: config.openshift.io/v1 kind: OAuth metadata: name: cluster spec: identityProviders: - ldap: attributes: email: - mail id: - dn name: - cn preferredUsername: - uid bindDN: uid=openshift,cn=sysaccounts,cn=etc,dc=private,dc=opequon,dc=net bindPassword: name: ldap-secret ca: name: opequon-custom-ca-oauth insecure: false url: ldaps://ipa.private.opequon.net/cn=users,cn=accounts,dc=private,dc=opequon,dc=net?uid mappingMethod: claim name: ldapidp type: LDAP
This is an example of a LDAP Identity provider, there are other
flavors if desired. Notice
OAuth.spec.identityProviders looks for
an array so it is possible to specify multiple providers.
We have a few things that need filled out here:
This is the display name for this
IdentityProvider. When logging
on, if multiple
IdentityProviders are configured, the user will see
this name to choose from. In the case of only having a single
provider, this is almost never seen.
Whatever value for
name is, it should be something meaningful to the
humans that interact with this OpenShift cluster.
bindDN and bindPassword
The username and password for accessing the cluster.
is normally sourced from a Kubernetes
The certificate chain for the LDAP server. Almost every LDAP server uses a certificate signed by a non-public certificate authority (i.e. not included in the default RHEL certificate authority bundle), therefore this is almost always required.
Since FreeIPA acts as a certificate authority, this will need to be specified in the final configuration.
This is normally sourced from a
This is the URL of the LDAP server with a query string, as defined by
The query string is, along with the
attributes, the most important
part fo the configuration. If it is wrong, then no one can log in,
and it is significantly easier to get wrong than the other fields.
The RFCs are rather dense reading, so it’s worthwhile to break down the parts of a query string:
schema://hostname:port/base_dn?attributes?scope?filter ^ ^ +-----------------------------+ Query String
After the schema, hostname, and port, there are four fields:
LDAP organizes it’s data into a tree-like structure.
specifies where in the tree to start searching. This can be visually
seen by tools like Apache Directory
attributes specify what attributes from the found entities to return
from the search. An entity is a node on the tree that represents
something, like a user account or a group. These, of course, can have
many attributes. Some common examples are
sn for surname,
For OpenShift, this should be a attribute that is going to be unique
to each entity in the entities returned by the query, like
represents a Linux username.
Again, a tool like Apache Directory Studio can be very helpful exploring the LDAP structure.
scope determines if or how the search recurses through the tree. The options are:
The default is
base restricts itself to ONLY the
base_dn and is not really useful in this situation.
one searches only the first level below the
sub fully recurses the tree below the
filter is an LDAP Filter that allows refinement of the
resultset even further. This is a really deep topic, but a simple
example of this is
(objectclass=person). This filter will only
return entities that are of
filter is specified, then
(objectclass=*) is the default.
This filter is true for all entities.
With this information, we can create a URL for IPA. Fortunately, for the default install of FreeIPA, we can have a fairly simple query string.
From the example:
This example omits some parameters. If we were to fill in the default explicitly, it would look like this:
In FreeIPA, all users are always listed under
cn=users,cn=accounts,dc=mydomain. This differs some other LDAP
systems (specifically Active Directory). This makes our search really
simple, as we can just pull every entity out of this
base_dn and be
assured that all users will be able to log into OpenShift.
An Aside: Restricting Access
In this example, the LDAP URL is constructed so that all users can log in to OpenShift.
In many situations, there is a desire to lock this down to allow, for example, only users from a certain group to log in or other similar restriction.
While this is certainly possible, with the correct LDAP filter. I would posit that this is an anti-pattern, and that access control is much better co-ordinated via Groups and RBAC policies.
I have several reasons for this:
It’s imperative that the LDAP query returns quickly. This query is run every time a user logs in. If the query takes a long time to run, then the user experience will suffer.
OAuthobject can potentially cause loss of service, especially if updated with incorrect values.
If you configure a very complicated query (especially if using nested group search on a complicated LDAP tree), then #1 is very possible.
And, secondly, if you specify a complicated query it increases the
chances of having to change the query in the future, which increases
the changes for mistakes when updating the
Therefore, my opinion is to let everyone log in. It’s a simple query and is unlikely to every change, unless there are massive change to the LDAP structure.
In that situation, controlling access is done via
Role-Based Access Control. This is easier to implement, less risky to
change, and much more flexible. A future blog post will cover this
topic in detaill.
When a user logs into OpenShift using an
IdentityProvider, a set of
proxy Kubernetes object is created to represent that user. These
objects are used predominately in role based access control and group
membership, as well as in creation metadata for certain other objects.
attributes field configures the
IdentityProvider to map
attributes in the LDAP entity to fields in these proxy objects.
There are four attributes, let’s go over them quickly:
name is a human readable name of the user. In Free IPA, this is in
cn attribute of the LDAP entity.
id is the most important of these attribute, as it is meant to map
to a LDAP field that uniquely identifies the user. As a consequence,
this attribute should never change during the life of the LDAP entity.
If it does change then the link between the user entity in LDAP and
User object in OpenShift will be broken, and the user will no
longer be able to log in.
In Free IPA, the
id attribute should be mapped to the
of the LDAP entity.
preferredUsername is optional, but useful. By default, the
IdentityProvider will use
id as the username for that user in
dn is a big long, ugly LDAP Distinguished
Name, like this:
preferredUsername, if specified, causes a different attribute to be
used for username. Generally, I want to use the same username as I
use for logging into Linux systems. In Free IPA, this is in the
attribute of the LDAP entity.
IdentityProviders are configured in a single cluster,
mappingMethod is set to determine how username conflicts
In the case where you only have one
claim is the right value.
Putting it all together
I need a read-only service account in my FreeIPA LDAP. This is not a regular user. I don’t want it to be able to log into any hosts or even the IPA console. I only want it to be able to do queries against FreeIPA’s LDAP.
To do this, create a LDAP System Account.
All these examples use the base domain of my system
dc=private,dc=opequon,dc=net). You’ll obviously need to swap this
out with the specifics of your FreeIPA installation.
On the FreeIPA server:
[root@ipa ~]# ldapmodify -x -D 'cn=Directory Manager' -W Enter LDAP Password: # Paste in the below dn: uid=openshift,cn=sysaccounts,cn=etc,dc=private,dc=opequon,dc=net <- CHANGE OPENSHIFT changetype: add TO DESIRED NAME OF objectclass: account SERVICE ACCOUNT objectclass: simplesecurityobject uid: openshift <- THIS SHOULD MATCH THE DN ABOVE userPassword: changeMe <- THIS IS YOUR SERVICE ACCOUNT PASSWORD passwordExpirationTime: 20380119031407Z <- 2038 EFFECTIVELY THE END OF TIME nsIdleTimeout: 0 ^D <- ACTUALLY TYPE Control-D
If successful, you should see the following output:
adding new entry "uid=openshift,cn=sysaccounts,cn=etc,dc=private,dc=opequon,dc=net"
You can then test this service account using a command similar to the following:
ldapsearch -x -D 'uid=openshift,cn=sysaccounts,cn=etc,dc=private,dc=opequon,dc=net' -W
This will spit out every object in the LDAP database. Of course you
can filter, see
For both the
ldapsearch command, the arguments mean the following:
-x- use simple authentication (instead of a Kerberos ticket)
-D- the distinguished name of the user
-W- prompt for password
This file is normally found at
/etc/ipa/ca.crt on any machine
connected to FreeIPA.
Grab this file, so it can put it into a config map later, or
oc commands from a Linux machine that is connected
Creating Secrets and ConfigMaps
OAuth object we are creating requires a
Secret for the LDAP
service account password.
This secret must be in the
Project, but can be
named anything so long as that name is used in the later
To do so via the command line:
# oc project openshift-config # oc create secret generic ldap-secret --from-literal=bindPassword=changeMe
This will create a
Secret like the below. Alternatively, this
manifest can be applied directly using
oc create, just be sure to
substitute the value of
data.bindPassword with the Base64 encoded
string of your actual password.
apiVersion: v1 kind: Secret metadata: name: ldap-secret namespace: openshift-config type: Opaque data: bindPassword: Y2hhbmdlTWU=
In addition to the
Secret, the FreeIPA certificate authority chain
must be in a
ConfigMap object in the
This file is normally found at
/etc/ipa/ca.crt on any machine
connected to FreeIPA.
To create this
ConfigMap via the command line:
oc project oc create cm custom-ca-oauth --from-file=ca.crt=/etc/ipa/ca.crt -n openshift-config
Alternatively, this manifest can be applied directly using
apiVersion: v1 kind: ConfigMap metadata: name: custom-ca-oauth namespace: openshift-config data: ca.crt: | -----BEGIN CERTIFICATE----- REPLACE ME -----END CERTIFICATE-----
Creating OAuth Object
With the LDAP service account created and the
ConfigMaps in place, it’s time to create to update the OAuth object.
In OpenShift 4, an OAuth object will exist by default regardless of if
there are any
IdentityProviders configured. We could use
replace to overwrite this object or just edit the object using either
oc edit or the web console.
Regardless of method chosen, the resulting object definition should look like this:
apiVersion: config.openshift.io/v1 kind: OAuth metadata: name: cluster spec: identityProviders: - ldap: attributes: email: - mail id: - dn name: - cn preferredUsername: - uid bindDN: uid=openshift,cn=sysaccounts,cn=etc,dc=private,dc=opequon,dc=net bindPassword: name: ldap-secret # MUST MATCH OUR SECRET FOR LDAP ca: name: custom-ca-oauth # MUST MATCH OUR CA CONFIGMAP insecure: false url: ldaps://ipa.private.opequon.net/cn=users,cn=accounts,dc=private,dc=opequon,dc=net?uid mappingMethod: claim name: ldapidp type: LDAP
url must, of course, be updated to match your
environment. The names of the
Secret and Certificate Authority
ConfigMap must match the names of those created in the previous
Once this is applied, the
apply the configuration you can check the status by looking at the
ClusterOperator objects (short name for these is
# oc get co authentication NAME VERSION AVAILABLE PROGRESSING DEGRADED SINCE MESSAGE authentication 4.10.12 True False False 24h
While being configured, the
PROGRESSING column will flip to false. This configuration should only take a few minutes to apply.
If it the
DEGRADED column flips to true for a long period (or if
AVAILABLE becomes false), then use
oc describe co authentication
to see events related to the problem.
Testing Logging in
ClusterOperator applies the
configuration, on next log in attempt, the user will be presented with
an option to log in using FreeIPA as the provider. The name of this
provider is specified in the
spec.identityprovider.ldap.name field. In our example, this is
Once logged in, we should be able to see our
# oc get users ## Output slightly truncated NAME UID FULL NAME IDENTITIES cfh 826d995a-2045-42dd-a4fa-81d338ebbefe Clark Hale ldapidp:dWlkPWNmaCxjbj11c2Vycyxjbj1hY2NvdW50cyxkYz1wcml2YXRlLGRjPW
Additionally, we can see the
# oc get identity NAME IDP NAME IDP USER NAME USER NAME USER UID ldapidp:dWlkPWNmaCxjbj11c2Vycyxjbj1hY2NvdW50cyxkYz1wcml2YXRlLGRjPW9wZXF1b24sZGM9bmV0 ldapidp dWlkPWNmaCxjbj11c2Vycyxjbj1hY2NvdW50cyxkYz1wcml2YXRlLGRjPW9wZXF1b24sZGM9bmV0 cfh 826d995a-2045-42dd-a4fa-81d338ebbefe
We can see that these long strings are actually the value of the
attribute, which we set to
# echo "dWlkPWNmaCxjbj11c2Vycyxjbj1hY2NvdW50cyxkYz1wcml2YXRlLGRjPW9wZXF1b24sZGM9bmV0" | base64 -d uid=cfh,cn=users,cn=accounts,dc=private,dc=opequon,dc=net
Now that users can log in, the next step is to synchronize groups from FreeIPA and then assigning Roles to those users.