OpenShift authentication with IPA
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)
Introduction
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 Background
OpenShift has the concept of an IdentityProvider
which connects an
source of identity verification, like FreeIPA’s LDAP server, to
OpenShift.
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 /etc/origin/master/master-config.yaml
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:
name
bindDN
bindPassword
ca
url
attributes
mappingMethod
Name
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. bindPassword
is normally sourced from a Kubernetes Secret
.
ca
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 ConfigMap
.
url
This is the URL of the LDAP server with a query string, as defined by
RFC4516 (which
obsoletes, RFC2254).
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:
base_dn
attributes
scope
filter
LDAP organizes it’s data into a tree-like structure. base_dn
specifies where in the tree to start searching. This can be visually
seen by tools like Apache Directory
Studio.
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 mail
for e-mail address,
sn
for surname, givenName
, loginShell
, homeDirectory
.
For OpenShift, this should be a attribute that is going to be unique
to each entity in the entities returned by the query, like uid
which
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:
base
one
sub
The default is sub
base
restricts itself to ONLY the base_dn
and is not really useful in this situation.
one
searches only the first level below the base_dn
.
sub
fully recurses the tree below the base_dn
.
The LDAP Wiki has a good page explaining LDAP Search Scopes.
Finally, 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 objectclass
person
.
If no filter
is specified, then (objectclass=*)
is the default.
This filter is true for all entities.
The LDAP Wiki has a lot of good examples of LDAP Queries.
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:
ldaps://ipa.private.opequon.net/cn=users,cn=accounts,dc=private,dc=opequon,dc=net?uid
This example omits some parameters. If we were to fill in the default explicitly, it would look like this:
ldaps://ipa.private.opequon.net/cn=users,cn=accounts,dc=private,dc=opequon,dc=net?uid?sub?(objectclass=*)
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.
-
Changing the
OAuth
object 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 OAuth
object.
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 Groups
and
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.
attributes
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.
The 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
email
id
preferredUsername
name
is a human readable name of the user. In Free IPA, this is in
the cn
attribute of the LDAP entity.
email
is, obviously, the user’s email address. In Free IPA, this is in
the mail
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
the 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 dn
attribute
of the LDAP entity.
preferredUsername
is optional, but useful. By default, the
IdentityProvider
will use id
as the username for that user in
OpenShift. Unfortunately, dn
is a big long, ugly LDAP Distinguished
Name, like this:
uid=cfh,cn=users,cn=accounts,dc=private,dc=opequon,dc=net
.
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 uid
attribute of the LDAP entity.
mappingMethod
When multiple IdentityProviders
are configured in a single cluster,
then the mappingMethod
is set to determine how username conflicts
are handled.
In the case where you only have one IdentityProvider
configured,
then claim
is the right value.
Putting it all together
Pre-requisites
Service Account
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 ldapsearch(1)
.
For both the ldapmodify
and 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
Certificate Authority
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
alernatively run oc
commands from a Linux machine that is connected
to FreeIPA.
OpenShift Configuration
Creating Secrets and ConfigMaps
The OAuth
object we are creating requires a Secret
for the LDAP
service account password.
This secret must be in the openshift-config
Project
, but can be
named anything so long as that name is used in the later OAuth
object.
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 openshift-config
namespace.
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 oc
create
.
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 Secrets
and
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 oc
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
bindDN
and 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
section.
Once this is applied, the authentication
ClusterOperator
will
apply the configuration you can check the status by looking at the
ClusterOperator
objects (short name for these is co
)
# 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
After the authentication
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 OAuth
object
spec.identityprovider.ldap.name
field. In our example, this is
ldapipa
.
Once logged in, we should be able to see our User
object.
# oc get users
NAME UID FULL NAME IDENTITIES
cfh 826d995a-2045-42dd-a4fa-81d338ebbefe Clark Hale ldapidp:dWlkPWNmaCxjbj11c2Vycyxjbj1hY2NvdW50cyxkYz1wcml2YXRlLGRjPW9wZXF1b24sZGM9bmV0
Additionally, we can see the Identity
object
[root@meriadoc opequon_labs_ocp_playbooks]# 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 id
attribute
, which we set to dn
:
[root@meriadoc opequon_labs_ocp_playbooks]# echo "dWlkPWNmaCxjbj11c2Vycyxjbj1hY2NvdW50cyxkYz1wcml2YXRlLGRjPW9wZXF1b24sZGM9bmV0" | base64 -d
uid=cfh,cn=users,cn=accounts,dc=private,dc=opequon,dc=net
Next Steps
Now that users can log in, the next step is to synchronize groups from FreeIPA and then assigning Roles to those users.
- 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)