User Authentication migration – LDAP
Kubernetes does not have users; it’s up to the Kubernetes distribution to provide an authentication endpoint that performs user authentication and identity mapping to either a User or a Group. Kubernetes does have roles and cluster roles, which are used to group permissions, and rolebindings and clusterrolebindings, which are used to assign permissions to particular users or groups.
For more information about how authentication is implemented in Kubernetes, see:
In ICP, the internal auth-idp component is used as an OIDC provider that
authenticates users. This component can be configured from the UI and will
connect to LDAP on behalf of the cluster to authenticate users. The Teams
concept is used to group together users or groups from LDAP into logical groups
and is managed by the auth-idp component and persisted in mongodb.
Openshift 3.11 has a similar component embedded in the API server that performs authentication on behalf of the cluster. However, it does not have a UI to configure LDAP, so it takes some work during installation or after installation to configure LDAP.
Openshift 4.x uses a separate operator to perform authentication, and another operator manages that operator and its configuration using a CustomResourceDefinition to configure LDAP and other identity providers.
In our migration scenario, we specifically we looked at migrating an existing ICP LDAP connection to Openshift 3.11.
Our LDAP server had the following contents:
dn: dc=internal-network,dc=local dc: internal-network objectClass: top objectClass: domain dn: cn=ldapadm,dc=internal-network,dc=local objectClass: organizationalRole cn: ldapadm dn: ou=People,dc=internal-network,dc=local objectClass: organizationalUnit ou: People dn: ou=Group,dc=internal-network,dc=local objectClass: organizationalUnit ou: Group dn: cn=binduser,dc=internal-network,dc=local cn: binduser objectClass: organizationalRole objectClass: top objectClass: simpleSecurityObject dn: cn=user1,ou=People,dc=internal-network,dc=local cn: user1 objectClass: person objectClass: simpleSecurityObject objectClass: uidObject objectClass: top sn: one uid: user1 dn: cn=dev1,ou=Group,dc=internal-network,dc=local cn: dev1 objectClass: groupOfUniqueNames objectClass: top uniqueMember: cn=user1,ou=People,dc=internal-network,dc=local uniqueMember: cn=user2,ou=People,dc=internal-network,dc=local dn: cn=user2,ou=People,dc=internal-network,dc=local cn: user2 objectClass: person objectClass: simpleSecurityObject objectClass: uidObject objectClass: top sn: user two uid: user2 dn: cn=clusteradmin,ou=People,dc=internal-network,dc=local cn: clusteradmin objectClass: person objectClass: simpleSecurityObject objectClass: uidObject objectClass: top sn: cluster admin uid: clusteradmin dn: cn=admin,ou=Group,dc=internal-network,dc=local cn: admin objectClass: groupOfUniqueNames objectClass: top uniqueMember: cn=clusteradmin,ou=People,dc=internal-network,dc=local dn: cn=dev2,ou=Group,dc=internal-network,dc=local cn: dev2 objectClass: groupOfUniqueNames objectClass: top uniqueMember: cn=user3,ou=People,dc=internal-network,dc=local uniqueMember: cn=user4,ou=People,dc=internal-network,dc=local dn: cn=user3,ou=People,dc=internal-network,dc=local cn: user3 objectClass: person objectClass: simpleSecurityObject objectClass: uidObject objectClass: top sn: three uid: user3 dn: cn=user4,ou=People,dc=internal-network,dc=local cn: user4 objectClass: person objectClass: simpleSecurityObject objectClass: uidObject objectClass: top sn: four uid: user4
The ICP configuration appeared as follows:
The matching Openshift configuration was configured under identityProviders on each master host in /etc/origin/master/master-config.yaml. Once this was configured we restarted docker on each master host to restart the API server. The configuration is as follows:
identityProviders:
- name: "rhos-ldap"
challenge: true
login: true
mappingMethod: claim
provider:
apiVersion: v1
kind: LDAPPasswordIdentityProvider
attributes:
id:
- dn
email:
- mail
name:
- cn
preferredUsername:
- uid
bindDN: "cn=binduser,dc=internal-network,dc=local"
bindPassword: "xxx"
insecure: true
url: "ldap://192.168.100.4:389/dc=internal-network,dc=local?uid?sub?(objectclass=person)"
Pay particular interest to the url. The format of the URL is
ldap://host:port/basedn?attribute?scope?filter
We have translated this from the ICP configuration, where attribute and
filter are built from the User filter in the ICP configuration. The
query it uses is:
(&(attribute=%v)(filter))
Openshift has explicit user and group resources which the API server
manages. You can list them using the familiar oc get users and oc get
groups commands as well as create additional ones.
As Openshift is a developer platform, the default mappingMethod claim allows
anybody that successfully authenticates access to the platform to login and
create projects. When authentication is successful, the platform will create a
user resource automatically. The ICP model denies access to any users in
LDAP that are not part of a team. To match the ICP model and deny access to
anybody not explicitly added to Openshift there are two options:
-
use the mappingMethod
lookup. However this requires additional overhead as the administrator must individually create users in the Openshift platform before they are given access to log in to Openshift.In our case, we created a user for user1, created an identity for it in ldap, and then mapped them together:
$ oc create user user1 user.user.openshift.io/user1 created $ oc create identity rhos-ldap:cn=user1,ou=People,dc=internal-network,dc=local identity.user.openshift.io/rhos-ldap:cn=user1,ou=People,dc=internal-network,dc=local created $ oc create useridentitymapping rhos-ldap:cn=user1,ou=People,dc=internal-network,dc=local user1 useridentitymapping.user.openshift.io/rhos-ldap:cn=user1,ou=People,dc=internal-network,dc=local created
-
Leave the default mappingMethod
claimbut deny access to create new projects in Openshift. By default thesystem:authenticatedgroup (i.e. anybody in LDAP) is given theself-provisionercluster-role, which allows project creation. Removing the role removes the overhead of having to create new users as they log in, but also prevents authenticated users from consuming resources in the platform without cluster administrator action. See: https://docs.openshift.com/container-platform/3.11/admin_guide/managing_projects.html#disabling-self-provisioning$ oc patch clusterrolebinding.rbac self-provisioners -p '{ "metadata": { "annotations": { "rbac.authorization.kubernetes.io/autoupdate": "false" } } }' $ oc patch clusterrolebinding.rbac self-provisioners -p '{"subjects": null}' -
We think this matches ICP the closest, but allowing users to create projects on their own has some advantages in developer scenarios. Using the above policy makes sense in production clusters but can be relaxed in development/test clusters.
Group migration – LDAP
Note that the Openshift api server does not query groups from LDAP; group definitions must be synced manually. The documentation around this is here: https://docs.openshift.com/container-platform/3.11/install_config/syncing_groups_with_ldap.html
In our scenario we had users in the tree under ou=People, and groups under ou=Group. Three groups were created (dev1, dev2, and admins). We used the following rfc2307 LDAP sync config:
kind: LDAPSyncConfig
apiVersion: v1
url: ldap://192.168.100.4:389/dc=internal-network,dc=local
bindDN: "cn=binduser,dc=internal-network,dc=local"
bindPassword: "xxxx"
insecure: true
rfc2307:
groupsQuery:
baseDN: "ou=Group,dc=internal-network,dc=local"
scope: sub
derefAliases: never
pageSize: 0
filter: "(objectclass=groupOfUniqueNames)"
groupUIDAttribute: dn
groupNameAttributes: [ cn ]
groupMembershipAttributes: [ uniqueMember ]
usersQuery:
baseDN: "ou=People,dc=internal-network,dc=local"
scope: sub
derefAliases: never
pageSize: 0
userUIDAttribute: dn
userNameAttributes: [ uid ]
tolerateMemberNotFoundErrors: false
tolerateMemberOutOfScopeErrors: false
Observe how this maps to the configuration in ICP; the groups are of object
class groupOfUniqueNames and the uniqueMember attribute contains the
members of the group which will be in turn queried.
Running this command will add some Openshift Group resources that can be
assigned roles.
$ oc adm groups sync --sync-config=rfc2307_config.yaml --confirm
group/dev1
group/admin
group/dev2
The result is three groups, with the user mappings as shown.
$ oc get groups NAME USERS admin clusteradmin dev1 user1, user2 dev2 user3, user4
As this is a manual process that produces static user/group mappings, it may be required to run this on a schedule that updates and prunes groups in an ongoing basis.
One additional implementation note is that Openshift issues Opaque tokens; since the authentication module is embedded in the API server it is able to validate the tokens internally. In ICP, the authentication token issued by the auth service is a signed JWT that contains an embedded list of groups that Kubernetes uses to validate permissions. In the next session when we discuss RBAC, we can see how rolebindings and clusterrolebindings are bound to these groups.