Using LDAP with MQ multiplatform and nested groups.

This blog post follows on from Using LDAP with multi platform.

Nested groups can be used to simplify administration

It is good practice to grant authority to access resources using groups, rather than giving access to individual userids. For example if an application uses 10 queues, and a new person joins the team you can either connect the new id to one group, or give the id access to the 10 queues.

Using nested groups takes this further. Imagine the payroll queues are managed by the GRPAYROLL access group, the HR queues managed by the GRHR access group, and the Finance queue managed by the GRFINANCE access group. The MQ system programmers can manage any queue. You could give GRMQADMIN access to the Payroll queues, HR queues, and Finance queues. Or you say the GRMQADMIN is the super set and includes a reference to GRPAYROLL, GRHR, and GRFINANCE. If you define a HR queue, you just give GRHR access to it, and the MQ Administration team get access to it “for free”.

You can take this further, and have a group GRHR_GR1, and GRHR_GR2 which give access to a subset of HR queues. The GRHR group could include both of these.

The model of this is a tree where a group high up in the tree incorporates the groups lower down the tree.

There are two ways of defining groups in LDAP.

1. Define a group record, and list the members of the group. This is called a static group

dn:cn=GR1,OU=groups,o=myorg
member:cn=user1,ou=users,o=myorg
member:cn=user2,ou=users,o=myorg

To retrieve the groups for a user, you issue a query for all groups which have the given member data.

2. Specify the group name as part of the user’s record, this is known as a dynamic group.

dn=cn=user1,ou=users,o=myorg
group=cn=group1,ou=groups,o=myorg
group=cn=group2,ou=groups,o=myorg

For this you retrieve the “group” attributes from the record. Note: The group attribute does not exist in LDAP, you have to use a different one ( I used the st attribute).

Nested group support.

There is an MQ AUTHINFO parameter NESTGRP. The documentation says

  • NESTGRP Group nesting.
    • YES The group list is searched recursively to enumerate all the groups to which a user belongs.

Static groups and nested group support.

My MQ ADUTHINFO had


AUTHORMD(SEARCHGRP) +
BASEDNG(‘ou=groups,o=your Company’) +
CLASSGRP(‘groupOfNames’) +
GRPFIELD(sn) +
FINDGRP(‘member’) +
NESTGRP(yes) +

On my system the userid ibmuser is a “member” of two groups mqstatic and mqstatic2

dn: cn=mqstatic,ou=groups,o=your Company
objectclass: groupOfNames
cn: mqstatic
ou:groups.
member: cn=ibmuser,o=your Company
member: cn=adcdb,o=your Company

and

dn: cn=mqstatic2,ou=groups,o=your Company
objectclass: groupOfNames
cn: mqstatic2
ou:groups.
member: cn=ibmuser,o=your Company

There is another group, which refers to the mqstatic group.

dn: cn=mega,ou=groups,o=your Company
objectclass: groupOfNames
cn: mega
ou:groups.
member: cn=mqstatic,ou=groups,o=your Company
member: cn=gg,ou=groups,o=your Company

As part of the userid to group mapping, MQ issues the query What groups have member: cn=ibmuser,o=your Company. The response is

  • dn: cn=mqstatic2,ou=groups,o=your Company
  • dn: cn=mqstatic,ou=groups,o=your Company

When NESTGRP(YES) is specified MQ then queries all groups that have cn=mqstatic2,ou=groups,o=your Company, and does another query for dn: cn=mqstatic,ou=groups,o=your Company

and gets the response dn: cn=mega,ou=groups,o=your Company.

The cn=ibmuser is associated with the three groups, and those three groups are used for access checking.

For a data model I see it as

dn=cn=GRHR_GR1
isusedby: CN=GRHR

and

dn:cn=GRHR
isusedby: CN=MQADMIN

and

dn: cn=mqadmin
member:cn=user1

The “isusedby” attribute name, is in reality “member”, which I feel is very confusing, as member implies membership, and there is no membership involved. This is an LDAP naming problem – not MQ’s.

This model feels upside down (or inside out) (or both). It takes several cups of tea with biscuits to draw up the groups and the connections definitions you need. Put the kettle on, and try defining the groups for the MQADMIN, GRFINANCE, and GRFINANCEGR1, GRFINANCEGR2 groups; three people in MQADMIN, and two people in finance; then add a definition for GRFINANCEGRN.

Using dynamic groups and nested group support

This feels more the right way up – but it is upside down compared to static groups.

My authinfo had

AUTHORMD(SEARCHUSR) +
FINDGRP(‘st’) +
BASEDNU(‘o=Your Company’) +
CLASSGRP(‘person’) +

With my userid I had

dn: cn=user1, o=Your Company
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: ibm-nativeAuthentication
objectclass: inetOrgPerson
departmentNumber: mqadmin
cn: ibmuser
sn: ibmuser
ou: test
st: cn=group,ou=groups,o=your Company
st: cn=mqadmin,ou=groups,o=your Company
ibm-nativeId: ibmuser

Where st: defined the groups.

For the entry for cn=group, I defined a nested group called cn=deepergroup.

dn: cn=group,ou=groups,o=your Company
objectclass: top
objectclass: person
objectclass: organizationalPerson
cn: cpgroup
ou: groups
st : cn=deepergroup,o=your Company
sn : groupgroup

You need an entry for cn=deepergroup… in LDAP

dn: cn=deepergroupou=groups,o=your Company
objectclass: person
cn: mega
sn:mega

# no st attribute, so there is no more nesting

I define the cn=mqadmin group to LDAP with no “sn” entry, because it has no nesting.

MQ issues a query to LDAP asking for the group definitions ( st in my case) for the given user. For each group that gets returned, query that group for any more group (st in my case) definitions, etc.

Together these give three groups for the cn=ibmuser:

  • cn=mqadmin,ou=groups,o=your Company
  • cn=deepergroup,ou=groups,o=your Company
  • cn=group,ou=groups,o=your Company

Dynamic group data model

The data model for dynamic group feels more natural, where the “is in” and “includes” are the attribute you chose, “st” in my case.

  • dn=cn=user1
    • is in: cn=group1…
  • dn=cn=group1…
    • includes: cn=groupdeep

Enabling nestgrp(yes) with dynamic groups may throw errors

If you start with

dn=cn=user1,…
group=cn=group1,..
group=cn=group2,…

With nestgrp(no) this defines the userid is in two groups.

If you enable nestgrp(yes) then when the user id is used, MQ checks there are entries in LDAP for group=cn=group1,.. and group=cn=group2,… If the definitions do not exist, you get the MQ message:

AMQ5532E: Error authorizing entity in LDAP

EXPLANATION:
The LDAP authorization service has failed in the ldap_first_entry call while trying to find user or group ‘NULL’. Returned count is 0. Additional context is ‘cn=group1,… ‘.

To solve the problem I defined the two groups to LDAP.

For group1 I defined

dn:cn=group1,…
group:cn=deepergroup…

and I defined the deepergroup group (to keep mq happy) with no group entries (to indicate end of the nesting).

dn:cn=deepergroup,…

Using LDAP with MQ multi platform.

MQ multiplatform can use LDAP as a userid and group repository, so you can logon to any machine where MQ is running, and use your corporate userid and password.

I’ve logged on to MQ on my Linux machine, and used my z/OS userid and password. It was pretty easy to set up (I had prior experience of using LDAP) but although it didn’t quite behave as I thought it would – I thought this was pretty clever.

Once you have installed LDAP, started it, and created your directory structure, (user, groups) and access permissions, you can start to use it. I’ve documented some of the initial settup here. It covers some of the concepts referred to below.

I used LDAP (Tivoli Directory Server) on z/OS as my LDAP server.

Contents

I’ve also written using LDAP with MQ and nested groups (MQ NESTGRP).

Using LDAP from MQ Multiplatform

The IBM documentation for this is so-so. It gives examples, but the examples didn’t work for me, but they were enough go point me in the right direction.

Start here.

I created a LDAP.MQSC file with

DEFINE AUTHINFO(MYLDAP) +
AUTHTYPE(IDPWLDAP) +
CONNAME(‘10.1.1.2(389)’) +
AUTHORMD(SEARCHGRP) +
BASEDNG(‘o=Your Company’) +
BASEDNU(‘o=Your Company’) +
LDAPUSER(‘cn=adcda, o=Your Company’) +
LDAPPWD(‘adcdapw1’) +
SECCOMM(NO) +
CLASSUSR(‘ibm-nativeAuthentication’) +
CLASSGRP(‘groupOfNames’) +
GRPFIELD(sn) +
SHORTUSR(sn) +
REPLACE

ALTER QMGR CONNAUTH(MYLDAP)

REFRESH SECURITY TYPE (CONNAUTH)
* ALTER QMGR CONNAUTH(SYSTEM.DEFAULT.AUTHINFO.IDPWOS)

Where the key fields for connecting to LDAP are

  • conname – the IP address of the LDAP server.
  • ldapuser and ldappwd – userid and password to access LDAP.
  • seccomm – use TLS/SSL to contact the LDAP server. I used “no” while setting this up.

the key fields for identifying users are

  • basednu – the subtree to be used for userids, for example all users are one level under ou=user,o=myorg.
  • classusr – is the objectclass attribute to identify the userid. The default is inetOrgPerson.
  • shortusr – the dn identifiers are too long. MQ needs IDs with 12 characters or less. This attribute says which attribute to use.

the key fields for identifying which groups an id belongs to

  • authormd – how to search for the authorisation.
  • basedng – the subtree to be used for groups, for example ou=group,o=myorg.
  • classgrp – the objectType which objects must have to be recognised as a group
  • grpfield – the simple name of the group
  • findgrp – what to filter for. For example ‘member’

Being a careful person, I started an interactive runmqsc session in one terminal, and used runmqsc … < LDAP.MQSC in another window. This way if there were problem I could use the interactive session to reset the QMGR CONNAUTH (as in the comment above). I know that the userid that started the queue manager, does not need a password; so if you issued strmqm qma you can use runmqsc qma without a userid and password. It gets harder if your id is not the id the queue manager is running under.

LDAP definition of my logon userid

The userid I wanted to use with MQ was defined

dn: cn=ibmuser, o=Your Company
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: ibm-nativeAuthentication
objectclass: inetOrgPerson
cn: ibmuser
sn: ibmuser
ou: test
st: cn=group,ou=groups,o=your Company
st: cn=mqadmin,ou=groups,o=your Company
ibm-nativeId: ibmuser

The authinfo data has

BASEDNU(‘o=Your Company‘) +
CLASSUSR(‘ibm-nativeAuthentication‘) +
SHORTUSR(sn) +

When I try to logon with userid ibmuser, MQ issues an LDAP query for the record with

  • sn=ibmuser
  • with an object class = ‘ibm-nativeAuthentication (which provides the RACF support for userid and password)
  • in the subtree o=Your Company

Check the LDAP configuration when things go wrong

It took me a few hours to determine why I could logon with one id, but not another id. Some LDAP entries worked, and some did not. It turned out to be an Access Control List (ACL) set up problem, where the LDAPUSER userid was not authorised to see some of the records. With the above AUTHINFO object, the query that MQ uses to check authorisation is like

ldapsearch -h 127.0.0.1 -D “cn=adcda, o=Your Company” -w ? -b “o=Your Company” “&(objectClass=ibm-nativeAuthentication)(sn=zadcdc)”

Where the parameters match up with the authinfo object above, and zadcdc is the userid trying to logon.

If you get no data back, get an authorised person (cn=ibmuser…) to issue the command for the user problem userid zadcdc:

ldapsearch -h 127.0.0.1 -D “cn=ibmuser, o=Your Company” -w ? -b “o=Your Company” “&(objectClass=*)(sn=zadcdc)”aclentry aclsource

The aclentry will give you the userids or groups who are authorised to use the entry, and the access they have.

The aclsource tells you which node in the tree the ACL was inherited from (for example aclsource=o=Your Company says it came from the root node). I had set up an ACL for my zadcdc which did not include my LDAPUSER.

Setting up MQ connect authorities

You can issue the command

setmqaut -m qml -t qmgr -p ibmuser +connect

to give ibmuser connect authority.

You can use LDAP groups for example

setmqaut -m qml -t qmgr -g “cn=mqadmin,ou=groups,o=your Company” +connect.

How do you set up a group in LDAP?

This is where it gets interesting. You can define a static group with its list of members, or create a dynamic group which is more flexible and “modern” (where modern is within the last 30 years).

Using a static group (with a list of members defined in it)

You can define a static group in LDAP using

dn: cn=mqstatic,ou=groups,o=your Company
objectclass: groupOfNames
ou:groups.
member: cn=ibmuser,o=your Company
member: cn=adcdb,o=your Company

It has two members.

When the userid authenticates, the queue manager asks LDAP for the groups that the userid is in; using the AUTHINFO definitions CLASSGRP(‘groupOfNames’) + GRPFIELD(…) FINDGRP(‘…’) a query is done for the groups which have the userid id. For example with

BASEDNG(‘ou=groups,o=your Company’) +
CLASSGRP(‘groupOfNames’) +
FINDGRP(‘member’) +

and cn=ibmuser o=your Company. The query is

(&(objectClass=groupOfNames)(member=cn=ibmuser, o=Your Company) in subtree (ou=groups,o=your Company)

The ldap search asking to return the member attribute

ldapsearch … -b “ou=groups,o=your Company” “(&(objectClass=groupOfNames) (member=cn=ibmuser, o=Your Company)) ” cn

gave two group names –

cn=mqstatic2,ou=groups,o=your Company
cn=mqstatic,ou=groups,o=your Company

This gives the information as to which groups a user is in. MQ then saves the userid and group information, and does not need to go to LDAP the next time the userid needs access checking.

Knowing which groups a userid is in, MQ can then decide on the access by comparing with the setmqaut definitions.

Using a dynamic group – or using a group attributer in the user record.

Instead of a group having a list of members, you can add information to the user’s record.

For example

dn: cn=adcda, o=Your Company
changetype: add
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: ibm-nativeAuthentication
sn: adcda
group: cn=group,o=your Company
cn: mq
ibm-nativeId: adcda

Unfortunately there is no “group” attribute defined in the LDAP schema, so I had to find another attribute to use. I used st for state. I used st: cn=group,o=your Company instead of group: cn=group,o=your company.

In my MQ AUTHINFO definition I had

BASEDNU(‘o=Your Company’) +
CLASSUSR(‘ibm-nativeAuthentication’) +
SHORTUSR(sn) +
AUTHORMD(SEARCHUSR) +
FINDGRP(st,‘) +
BASEDNG(‘ou=zzzzz,o=your Company’) +

MQ did an LDAP search using this information

ldapsearch… -b “o=Your Company” “&(objectClass=(ibm-nativeAuthentication)
(sn=ibmuser))” st

Which is just a display of the userid information – and return the fields with the attribute you specified(st). It returned

cn=ibmuser, o=Your Company
st=cn=group,ou=groups,o=your Company
st=cn=mqadmin,ou=groups,o=your Company

This tells MQ to use the groups

cn=group,ou=groups,o=your Company and cn=mqadmin,ou=groups,o=your Company.

You can use the setmqaut command to give the group access

setmqaut -m qml -t qmgr -g “cn=mqadmin,ou=groups,o=your Company” +connect

Once this was done, the cn=ibmuser could connect to MQ using groups.

Can I use LDAP to hold my setauth information?

Only the group and userid information are held in LDAP, all the other information is held in the queue manager. You cannot use LDAP to have your setmqaut configuration in LDAP, and shared by multiple queue managers. You still have to use setmqaut to set up each queue manager access.

Giving userids access to MQ objects

You can use the setmqaut command or the set authrec runmqsc command to give principals or groups access to resources.

For example

setmqaut -m qml -n CP0000 -t queue -g “cn=mqstatic,ou=groups,o=your Company” +inq

I’ve changed the definitions in LDAP – when will they get picked up?

Changes get picked up when

  • the queue manager is restarted
  • when the resfresh security, refresh security type(authserv) or the refresh security type(connauth) command is issued.

I’ve started LDAP now what do I do?

Contents

What is LDAP?

I found this a good introduction on LDAP; the structure of the data, searching and filters.

I’ve written up

Setting up LDAP on z/OS

I created a new LDAP instance on z/OS see getting started with LDAP on z/OS, and the definitions and JCL I used to create LDAP. I used the standard schema /usr/lpp/ldap/etc/schema.user.ldif and the IBM extensions /usr/lpp/ldap/etc/schema.IBM.ldif which give you the attributes for working with RACF etc.

Now what do I do?

It is much easier to set up your LDAP structure properly, before you start adding in lots of records, rather than try to change the structure it once you have populated it with all your data. You could be agile, develop your LDAP data, back up the data, and “just” recreate the LDAP repository once you know what you want. Where “just” means write Python or Rexx scripts to take the LDAP data and convert to the new format, for example adding additional information to every definition before adding it to the dictionary.

Because I did not fully understand how Access Control Lists work, I managed to make some of my data invisible to the end user requests, so it is easy to make mistakes when you do not know what you are doing. This blog should give you some hints about setting up your LDAP environment, and avoid some of the rework.

Background to LDAP

LDAP is a generalised directory with an application interface over IP.

The data is held in a hierarchical(upside down tree) form, for example the top of the tree may be called o=myorg. Where o stands for Organisation.

You configure this top of the tree in the LDAP config file for example

# this defines a file based database
database LDBM GLDBLD31/GLDBLD64
# this says it can use RACF for password checking
useNativeAuth all
#this is the top of the tree
suffix “o=myorg
# this is the location on disk of the database
databaseDirectory /var/ldap/ldbm

The next levels down might be

  1. ou=users, o=myorg
  2. ou=groups,o=myorg
  3. ou=corporate data,o=myorg

The data for the first subtree could be stored in DB2, the data for the second subtree could be in files, and the data for the third subtree could be in another LDAP, somewhere else.

A “record” or leaf of the tree could be identified by

dn:cn=colin paice,c=GB,ou=users,o=myorg

Where

  • cn= is the common name
  • c= is the country name
  • ou= is the organisational unit
  • o= your organisation.

With each record you need one or more objectTypes. Object types have attributes. For example an objectType of person can have an attribute telephoneNumber. If you want to use telephoneNumber you need an objectType that supports it.

A typical entry might be

dn: cn=mq, o=Your Company
objectclass: top
objectclass: organizationalPerson
cn: mqadmin
telephoneNumber: 1234567
telephoneNumber: 987654321
sn: mqadmin

I set up LDAP so I could logon to the Linux queue manager, and use my z/OS userid and password. For this I had

dn: cn=ibmuser, o=Your Company
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: ibm-nativeAuthentication
cn: ibmuser
sn: snibmuser
ibm-nativeId: IBMUSER

Where

  • objectclass: ibm-nativeAuthentication is for the RACF authorisation
  • ibm-nativeId: IBMUSER says use the RACF userid IBMUSER when cn=ibmuser, o=Your Company is used.

I also set up an Access Control List entry for this userid, so it can search, and read entries

dn: o=Your Company
changetype: modify
aclEntry : access-id:cn=ibmuser, o=Your Company:normal:grant:rscw

This says

  • for the subtree under the distinguished name of o=Your Company. DNs of ou=groups,o=myorg, and ou=users,o=myorg would be more typical subtree names.
  • the dn cn=ibmuser, o=Your Company, the dn of the user for this ACL. This would normally be a group rather than a userid. You can have multiple entries for each dn.
  • has read, search and compare and write on normal fields. A social security number is “sensitive” field, and a password is a “critical” field. This ACL only gives access to normal fields.

What do you want in a year’s time?

It is much easier to set up your LDAP structure properly before you start, rather than try to change the structure it once you are using it.

For example you could have a flat tree with entries like

  • dn:cn=colin paice,o=myorg for users
  • dn:cn=mqadmin,o=myorg for groups

This will quickly become hard to manage. You may find the following are better.

  • dn:cn=colin paice,ou=users,o=myorg for users
  • dn:cn=mqadmin,ou=groups,o=myorg for groups
  • dn:cn=testmqadmin,ou=groups,o=myorg for a different group with the department name in it.

or

  • dn:cn=colin paice,c=gb,ou=users,o=myorg for users by country
  • dn:cn=mqadmin,ou=test,ou=groups,o=myorg for test groups

Administration

You can give access to administer nodes in the tree, at the subtree level. For example for the sub node in the tree ou=test,ou=groups,o=myorg might have administrators.

  • cn=hqadmin,ou=groups,o=myorg
  • cn=colin paice,c=gb,ou=users,o=myorg

Having a structure like dn:cn=mqadmin,ou=test,ou=groups,o=myorg means you can give the test manager admin control to this test groups, but the test manager has no authority over the production groups. If you had a structure like dn:cn=mqadmin,ou=groups,o=myorg. It makes it much harder to separate the responsibilities.

For the top of the tree o=myorg, you could set up only the following group has administration.

  • cn=hqadmin,ou=groups,o=myorg

From a performance perspective it will be cheaper and faster to access data in a subtree, rather than search the whole tree – bearing in mind you could have millions of entries in the tree.

Note: The adminDN userid in the LDAP config file has authority over every thing. The ACLs on the tree, or subtree, or record define who has administration authority.

Controlling access

You control access using Access Control Lists. An ACL looks like

dn: ou=users,o=myorg
changetype: modify
replace: aclEntry
aclEntry : access-id:cn=ibmuser, o=Your Company:
  object:ad:normal:grant:rscw:sensitive:rscw:
  critical:rscw:restricted:rscw
aclEntry: group:cn=authenticated:normal:rsc

This defines the access for the subtree ou=users,o=myorg

  • cn=ibmuser, o=Your Company can add delete entries under the subtree; and use any of the fields including sensitive ( eg social security number) and critical (eg password).
  • group: cn=authenticated any user who has authenticated can read, search and compare on normal fields. They cannot see or select on sensitive or critical fields.
  • object:a|d is to add or delete objects in the subtree.
  • normal:… sensitive… critical… these give access to the fields in the data ( so you can issue an ldap_search for example). You can specify <grant:|deny:> attributes, and so give access or remove access
  • restricted: To update ACLs you must have read and write access.
  • You can specify what access people have to individual fields, so you can give them access to most fields, but deny access to specific fields.

The userid defined in the LDAP configuration file under adminDN can change anything. When I messed up my data, I changed the config file to use adminDN cn=admin and password secret1, stopped and restarted the LDAP server, and fixed my problem. I undid the changes to the config file, stopped and restarted the server, to get back to normal operation.

You need to plan on what access you want, at which levels of the tree, and who has what access to which fields.

If is better to give access to groups, and add users to groups, than to give access to userids. For example, if you have many ACLs, if someone joins the team, changing the group to add the id is one change. If you have to change each ACL, and add the id, you may have many changes.

What is going to use it?

Programs using LDAP may have requirements on the data structure. For example MQ can use userid and group information in LDAP. It expects the data to be under a single subtree. You could specify groups are to be found under, ou=test,ou=groups,o=myorg. You cannot say under ou=test,ou=groups,o=myorg and ou=production,ou=groups,o=myorg. Similary, users would be under ou=users,o=myorg. If you specified the subtree c=gb,ou=users,o=myorg, then this limits users to having a GB userid, which may not be what you want.

Using groups

Although LDAP on z/OS can support static groups (with a list of members), nested groups containing groups, and dynamic groups ( where you can say those users with ou=production), the group of groups and dynamic groups cannot be used to check group membership.

You can query and display all users in all group types.

You can say give me the only the group names, where colinPaice is a member; when the group is a static group, not groups of groups, nor dynamic groups. This makes managing groups much harder, and you may need to have bigger groups than specific smaller groups.

You cannot exploit the flexibility of the nested and dynamics groups.

What fields do you want in the records.

If you have a definition like

dn: cn=ibmuser, o=Your Company
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: ibm-nativeAuthentication
cn: ibmuser
sn: snibmuser
ibm-nativeId: IBMUSER

If you want to use the ibm-nativeId field to give the RACF userid to use, then you need objectclass: ibm-nativeAuthentication.

If you have ibm-nativeAuthentication you must have ibm-nativeId, and may have other fields.

The “must” and “may” fields are defined in a schema.

The schema /usr/lpp/ldap/etc/schema.IBM.ldif has

objectclasses: (
  NAME 'ibm-nativeAuthentication'
  DESC 'Indicates native security manager should be used during authentication.'
  SUP top
  AUXILIARY
  MUST ( ibm-nativeId )
)

The objectClass: person has

objectclasses: (
  NAME 'person'
  DESC 'Defines entries that generically represent people.'
  MUST ( cn $ sn )
  MAY ( userPassword $ telephoneNumber $ seeAlso $ description )
)

This means you must provide a cn and an sn entry, and you can provide other entries

The schema can give information about an attribute

attributetypes: (
  NAME ( 'sn' 'surName' )
  DESC 'This is the X.500 surname attribute, which contains the family name of a person.'
  EQUALITY caseIgnoreMatch
  ORDERING caseIgnoreOrderingMatch
  SUBSTR caseIgnoreSubstringsMatch
)

This shows that for ordering or comparing, it ignores the case of the data, so “colin paice” is the same as “COLIN PAICE”.

You need to decides what fields you want, and how you want the fields to be processed, for example case, and comparison.

You can write your own schema for fields that are unique to your organisation.

What you use the field for is up to you. For example “sn” could be surname, or”short name”. You just have to be consistent and document it.

What tools are there to help me?

You can use the tools provided with LDAP to administer LDAP. For example the ldap_modify command can be used to process a batch of definitions; whole records, or attributes within records.

Eclipse has a plugin Apache Directory Studio. It works well, and looks like it is highly recommended. This plugin allows you to browse and manage entries. I could not get it to display the schema.

How do I backup/export the data

You can use the ds2ldif command. It creates a file in ldif format which can be used to add back all the records (using the ldap_add or ldap_modify commands).

Colin’s list of MQ messages

This blog post is my annotations to MQ messages, containing descriptions of what I did to get an MQ message, and what I did to fix the problem. It is meant for web search programs, rather than humans.

I will extend it as I experience problems.

AMQ7026E: A principal or group name was invalid.

I could display an auth entry

dspmqaut -m qml -t qmgr -g “cn=dynamic, o=Your Company”


Entity cn=dynamic, o=Your Company has the following authorizations for object qml:
connect

but not delete it

setmqaut -m qml -t qmgr -g “cn=dynamic,o=Your Company” -connect

AMQ7026E: A principal or group name was invalid.

I had set this up using LDAP, but MQ was not able to find the record in LDAP. This is because I had changed the configuration. There was an LDAP search

(&(objectClass=groupOfNames)(OU=cn=dynamic, o=Your Company))

It could not be found because there was no LDAP entry with objectClass=groupOfNames with an entry (OU=cn=dynamic, o=Your Company)

I had changed

DEFINE AUTHINFO(MYLDAP) +
AUTHTYPE(IDPWLDAP) +
GRPFIELD(sn)

to

GRPFIELD(ou)

So when MQ came to delete it, it looked for

(&(objectClass=groupOfNames)(OU=cn=dynamic, o=Your Company))

instead of

(&(objectClass=groupOfNames)(SN=cn=dynamic, o=Your Company))

and could not delete it.

I changed the LDAP group to add the OU, and the SETMQAUT command worked.

AMQ5532E: Error authorizing entity in LDAP

EXPLANATION:
The LDAP authorization service has failed in the ldap_first_entry call while
trying to find user or group ‘NULL’. Returned count is 0. Additional context is
‘cn=mqadmin,ou=groups,o=your Company’.

Colin’s comments

I had defined a dynamic group, by specifying the group name as part of the LDAP user entry.

When I changed the authinfo object yo have nestgrp(yes), I got the above message because there was no record for cn=mqadmin,ou=groups,o=your Company’.

Define the record with the appropriate object class as defined by the MQ AUTHINFO attribute CLASSGRP. (CLASSGRP(‘groupOfNames’) in my case).

AMQ5530E: Error from LDAP authentication and authorization service

EXPLANATION:
The LDAP authentication and authorization service has failed. The
‘ldap_ssl_environment_init’ call returned error 113 : ‘SSL initialization call
failed’. The context string is ‘keyfile=”/var/mqm/qmgrs/qml/ssl/key.kdb”
SSL/TLS rc=408 (ERROR BAD KEYFILE PASSWORD)’. Additional code is 0.

Colin’s comments.

I had changed the keyring using alter qmgr CERTLABL(ECRSA1024) SSLKEYR(‘/home/colinpaice/mq/zzserver’)

AMQ5530E: Error from LDAP authentication and authorization service

EXPLANATION:
The LDAP authentication and authorization service has failed. The
‘ldap_simple_bind’ call returned error 49 : ‘Invalid credentials’. The context
string is ‘10.1.1.2:389 ‘. Additional code is 0.

Colin’s comments

An anonymous logon to LDAP was attempted (LDAPUSER and LDAPPWD omitted) and the LDAP server had allowAnonymousBinds off.

Specify userid and password.


LDAP error messages and codes

This blog post is a repository for the LDAP error codes I experienced, and the actions I took to resolve the problems.

LDAP return codes

Messages include return codes like “3”, but the LDAP programming book has terms like “LDAP_PARAM_ERROR”.

These are defined in

/usr/include/ldap*.h,

SSL initialization failures reason codes.

https://www.ibm.com/docs/en/zos/2.5.0?topic=utilities-ssltls-information-ldap-client#idg18488__failrc

GLD1342E Unwilling to open file or directory ‘/var/ldap/schema’:

File or directory UID 0, UID of program 990023, GID of file or directory 1, GIDs of program (990018).

Colin’s comments

The LDAP started task expects to be the file owner of the /var/ldap/* files. On ADCD they were OMVSKERN:OMVSGRP. I used

chown -R gldsrv:gldgrp /var/ldap/*

to change the file owner.

Object class violation: additional info: R001026 No structural object class specified for ‘cn=ibmuser, o=Your Company’.

Colin’s comments

I had an ldif file with

dn: cn=mq, o=Your Company
changetype: add
objectclass: top
#objectclass: person
#objectclass: organizationalPerson
objectclass: ibm-nativeAuthentication
cn: mq
telephoneNumber: 1234567
telephoneNumber: 12345672
sn: Administrator

And no proper object class. When I uncommented person or organizationalPerson it worked.

R001030 Entry contains attribute ‘ibm-nativeid’ which is not allowed for object class

I was trying to add ‘ibm-nativeid’ to an entry. This attributed belongs to object class ibm-nativeAuthentication. The object has to have this object class, for example, add the lines in the bold font.

dn: cn=colin, o=Your Company
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: ibm-nativeAuthentication
cn: LDAP Administrator
sn: Administrator
ibm-nativeId: COLIN

Credentials are not valid: R004062 Credentials are not valid

By accident I overwrote my administration userid definition.

Colin’s comments

Edit GLD.CNFOUT(DSCONFIG) ( or what every config file you are using)

Comment out the adminDN, and add in the cn=admin and its password.

From

adminDN “cn=ibmuser, o=Your Company”
# adminDN “cn=Admin”
# adminPW secret

to

# adminDN “cn=ibmuser, o=Your Company”
adminDN “cn=Admin”
adminPW secret

  • Stop and restart LDAP.
  • Fix the userid
  • Change the admin definitions back
  • restart LDAP.

R003070 Access denied because user does not have ‘write’ permission for all modified attributes

I used a command like ldapmodify -a -h 127.0.0.1 -p 389 -D “cn=adcda, o=Your Company” -w adcdapw1 -f mqacl.* to change some definitions, but the userid cn=adcda,o=Your Company did not have the correct permissions.

You can enable the LDAP acl trace using f gldsrv,debug 128, and reset it using f gldsrv,debug 0

To change/add/delete an ACL the id needs restricted:rscw

For example

dn: o=Your Company
changetype: modify
replace: aclEntry
aclEntry : access-id:cn=ibmuser, o=Your Company:
object:ad:normal:grant:rscw:sensitive:rscw:critical:rscw
aclEntry : access-id:cn=adcda, o=Your Company:
object:ad:normal:rscw:sensitive:rscw:critical:rscw:restricted:rscw

Insufficient access: R003057 Access denied because user does not have ‘add’ permission for the parent entry

Trying to add an entry.

The userid is not authorised to add an entry. It needs an acl with object:ad ( a for add, d for delete)

dn: o=Your Company
changetype: modify
replace: aclEntry
aclEntry : access-id:cn=ibmuser, o=Your Company:
object:ad:normal:rscw:sensitive:rscw:critical:rscw
aclEntry : access-id:cn=adcda, o=Your Company:
object:ad:normal:rscw

GLD1063E Unable to initialize the SSL environment: 416 – Permission denied.

BPXM023I (GLDSRV) GLD1160E Unable to initialize the LDAP client SSL support: Error 113, Reason -17.

ICH408I USER(GLDSRV ) GROUP(GLDGRP ) IRR.DIGTCERT.LISTRING CL(FACILITY) INSUFFICIENT ACCESS AUTHORITY ACCESS INTENT(READ ) ACCESS ALLOWED(NONE )

In the LDAP config file I had sslKeyRingFile START1.MQRING. The userid GLDSRV did not have read access to the list ring facitity IRR.DIGTCERT.LISTRING CL(FACILITY)

permit IRR.DIGTCERT.LISTRING CL(FACILITY) id(GLDSRV)
SETROPTS RACLIST(FACILITY) REFRESH

GLD1160E Unable to initialize the LDAP client SSL support: Error 113, Reason 2.
GLD1063E Unable to initialize the SSL environment: 202 – Error detected while opening the certificate database.

  • reason code 2: Keyring open error
  • SSL return code 202: Keyring open error

Actions

  • Check value specified
  • Check access
    • rdefine rdatalib START1.MQRING.LST UACC(NONE)
    • SETROPTS RACLIST(RDATALIB) REFRESH
    • permit START1.MQRING.LST class(RDATALIB) ACCESS(READ) id(GLDSRV)

Check the keyring exists ( list the contents of it)

RACDCERT LISTRING(name) ID(COLIN)

Get out a gsk trace .

  • Add GSK_TRACE=0xff to the env file.
  • By default the output goes to gskssl.*.trc
  • Format it using gsktrace gskssl.*.trc gsktrace.out
  • oedit gsktrace.out search for ERROR. I had ERROR gsk_open_keyring(): IRRSDL00 GetData failed: SAF 8, RC 8, Reason 84.
  • These are documented here. 8 8 84 means keyring not found.

GLD1116E Unable to initialize an SSL connection …

533 – Remote partner indicates unsupported certificate.

LDAPSEARCH client on Linubbx

ldap_search_ext: Bad search filter (-7)

with -b “o=Your Company” “&(objectClass=*)”

remove the &()s

-b “o=Your Company” “objectClass=*”

worked

Using groups for authority and access checking is so last century.

I’ve been exploring LDAP as a userid repository (as can be used by MQ multi platform). This got me into an interesting rabbit warren of Role Based Access Control (RBAC) and Attribute Based Access Control(ABAC), and how you set up your repository to hold userid and access information.

In LDAP on z/OS I can set up a user

dn: cn=adcda, o=Your Company
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: ibm-nativeAuthentication
sn:adcda
cn: mqadmin
ibm-nativeId: adcda

With this, if I try to logon with cn=adcda, o=Your Company . It will try to use RACF to check the password I specified and the userid in ibm-nativeId (adcda) is valid. I’ve logged on to MQ on Linux using this definition, and had RACF on z/OS check my password. (I thought this was pretty neat).

This definition has an attribute sn ( surName) of adcda and a cn (commonName) of mqadmin.

LDAP groups.

You can set up statics LDAP groups

These have a list of members

dn: cn=ldap_team_static,o=myorg
objectclass: groupOfNames
cn: ldap_team_static
member: cn=colin,o=myorg
member: cn=colin2,o=myorg

Groups within groups

A group can have a group name to be included

dn: cn=ldap_team_nested,o=myorg
objectclass: container
objectclass: ibm-nestedGroup
cn: ldap_team_nested
ibm-memberGroup: cn=ldap_team_static,o=myorg
ibm-memberGroup: cn=mq_team,o=myorg

You can display group members

ldapsearch …–b “ldap_team_nested,o=myorg” “objectclass=*” ibmallMembers

You can also have smart, dynamic groups

This is the “new” way of doing it – which has been available for about 30 years.

dn: cn=dynamic_team,o=Your Company
objectclass: groupOfUrls
cn: dynamic_team
memberurl: ldap:///o=Your Company?sub?(cn=mqadmin)

This says

  • query the tree under o=Your Company ( a more realistic subtree would be ou=users,o=Your Company)
  • sub, says all levels in the tree (base is search just the specified URL, one is search just one level below the specified URL)
  • list all those with the specified attribute cn = mqadmin.

Instead of updating a group – you add information into the user’s entry, and it would get picked up automatically.

Ideally there would be an LDAP attribute “role” which you could use for this. The default schemas do not have this.

RACF

If you are using RACF you can set up a userid, and connect it to a group. RACF does not support nested groups for authority and access checking.

Access to resources

Using group or Access Control List

Many systems provides group or Access Control List (ACL) to control access to a resource.

For example you might say users in group MQADMIN can update dataset MQ.JCL, and userids in group MQOTHER can read the MQ.JCL dataset.

This has limitations in that the resources are treated individually, so if you have 10 files, you have to grant a group access to 10 profiles.

Role Based Access Control(RBAC)

I struggled initially to see the difference between RBAC and group or ACLs.

With RBAC you do not give update or read access to a resource, you give access to a “task” or “role” like “Maintain records”, “client admin”, or “clerk”. You then give the tasks the appropriate access. You could implement this at a basic level using groups called MAINTREC, and CLIENTADM to give it update access to the resource, and group CLERK with a read access.

Attribute Based Access Control(ABAC)

ABAC seems to take this further. There are products you buy which can do this for you, but I could not see how it was configured or how it worked. Below is my interpretation of how I might configure it using LDAP.

You could have a user defined like

dn: cn=colin paice, o=Your Company
role: doctor
sn:adcda
cn: mqadmin
ibm-nativeId: adcda

A set of resources like

dn: cn=doctor update,o=Your Company
table: HOSPITAL.PATIENT
table:HOSPITAL.XRAYS.DATA
MQQueue:Surgery.queue

This is a list of resources a doctor needs to do their job.

And a set of rules

dn:cn=doctorsRules,o=Your Company
role: doctor
resource: cn=doctor update,o=Your Company
site:London
site:Glasgow
reason:Patient Update

If someone (Doctor Colin Paice) wants up update a patients record, you can do a query using dynamic groups

  1. What roles does Colin Paice have?
  2. What is the resource group for the DB2 table HOSPITAL
  3. Is there a valid rule for the list of roles for Colin Paice, with the resource group for the table HOSPITAL.PATIENTS, where access is from LONDON, and reason is Patient Update.

Is it that simple?

You have to be able to handle the case when a doctor may only look at the patients notes if the doctor is the “attending physician” – so the person is a patient of the doctor. This might mean a “patientOf: Doctors_name” field in the user’s record.

It looks like you have to be very careful in setting this environment up, as you could have many thousands of rules, and it could be very hard to manage.

Even if it is hard, I think the idea of virtual groups, were you select records based on a criteria is a good idea. It may be faster than using groups because it can exploit the index capability of the underlying database, rather than build lists of group membership.