Using Cobol and DB2 in ADCD and ZPDT

There were questions in news group about using COBOL and DB2 under ADCD on ZPDT.

Here is an answer. It provides a proc (which you can put into USER.*.PROCLIB, and JCL to use it.

ADCD provides a proc in ADCD.Z24*.PROCLIB(DSNHICOB)

//
//* *
//* DSNCOB63 - COMPILE AND LINKEDIT A DB2 COBOL PROGRAM USING *
//* COBOL 6.3.0 P200901 *
//* *
//* NOTES: *
//* 1. THE COBOL 'SQL' COMPILER OPTION IS USED IN PLACE OF *
//* PRECOMPILE STEP. *
//* 2. THE DEFAULT DB2 VERSION IS 12. *
//* *
//* *
//
//* *
//* CHANGE LOG *
//* ---------- *
//* *
//
//* *
//* DATE/AUTHOR DESCRIPTION *
//* ----------- --------------------------------------------------- *
//*   -----------  --------------------------------------------------- * 
//*   2021/04/01   ORIGINAL FOR Z/OS 2.4 AND COBOL 6.30.               * 
//*   J. ABELL     THE SQL OPTION AND ITS SUBOPTIONS SUCH              * 
//*                AS ATTACH(CAF) AND ATTACH(RRSAF) NORMALLY USED WITH * 
//*                THE DB2 PRECOMPILER.                                * 
//*                                                                    * 
//*                NOTE: DBRMLIB IS NOW PART OF THE COMPILE STEP.      * 
//*                      OBSOLETE PARMS RENOVED.                       * 
//*                                                                    * 
//********************************************************************** 
//DSNCOB63  PROC COBOPTS=, 
//        DBRMLIB=, 
//        DBRMMOD=, 
//        DSNHLQ=DSNC10,              DEFAULT - DB2 V12 
//        LKPARM=, 
//        LOADLIB=, 
//        LOADMOD=, 
//        SQLOPTS=, 
//        SRCLIB=, 
//        SRCMOD= 
//* 
//*  ****                                                      **** 
//*  ****                                                      ****
//*  ****   DO NOT CHANGE THE DOUBLE QUOTES CODED IN THE PARM  ****
//*  ****                                                      ****
//* 
//COB      EXEC PGM=IGYCRCTL,REGION=0M, 
//      PARM='SQL("&SQLOPTS"),BUFSIZE(16K),&COBOPTS' 
//* 
//STEPLIB  DD DISP=SHR,DSN=&DSNHLQ..SDSNLOAD 
//SYSIN    DD DISP=SHR,DSN=&SRCLIB(&SRCMOD) 
//DBRMLIB  DD DISP=SHR,DSN=&DBRMLIB(&DBRMMOD) 
//SYSPRINT DD SYSOUT=* 
//SYSLIN   DD DSN=&&LOADSET,DISP=(MOD,PASS),UNIT=WRK, 
//            DCB=BLKSIZE=0, 
//            SPACE=(CYL,(10,10)) 
//SYSUT1   DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//SYSUT2   DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//SYSUT3   DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//SYSUT4   DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//SYSUT5   DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//SYSUT6   DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//SYSUT7   DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//SYSUT8   DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//SYSUT9   DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//SYSUT10  DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//SYSUT11  DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//SYSUT12  DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//SYSUT13  DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//SYSUT14  DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//SYSUT15  DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//SYSMDECK DD UNIT=VIO,SPACE=(CYL,(1,1)) 
//* 
//*  LINKEDIT IF THE PRECOMPILE AND COMPILE 
//*      RETURN CODES ARE 4 OR LESS 
//* 
//LKED     EXEC PGM=IEWL,REGION=0M,PARM='XREF,&LKPARM', 
//             COND=(4,LT,COB) 
//* 
//SYSLIB   DD DISP=SHR,DSN=CEE.SCEELKED 
//         DD DISP=SHR,DSN=CEE.SCEELKEX 
//         DD DISP=SHR,DSN=&DSNHLQ..SDSNLOAD 
//SYSLIN   DD DSN=&&LOADSET,DISP=(OLD,DELETE) 
//         DD DDNAME=SYSIN 
//SYSLMOD  DD DSN=&LOADLIB(&LOADMOD), 
//            DISP=SHR 
//SYSPRINT DD SYSOUT=* 
//SYSUDUMP DD SYSOUT=* 
//SYSUT1   DD SPACE=(1024,(50,50)),UNIT=VIO 

Some sample JCL to use it

/*JOBPARM  S=S0W1 
//* 
//CMPTEST EXEC DSNCOB63, 
//        COBOPTS='OPTIMIZE(2),CODEPAGE(500)', 
//        DBRMLIB=TEST.DBCG.DBRM, 
//        DBRMMOD=TESTDB2, 
//        DSNHLQ=DSNC10, 
//        SQLOPTS='ATTACH(CAF)', 
//        SRCLIB=TEST.SOURCE, 
//        SRCMOD=TESTDB2, 
//        LKPARM='EDIT=NO', 
//        LOADLIB=TEST.LIBRARY, 
//        LOADMOD=TESTDB2 
//* 
//LKED.SYSLIB DD 
//            DD DISP=SHR,DSN=TEST.LIBRARY
//            DD DISP=SHR,DSN=DSNC10.SDSNLOAD 
//SYSIN DD * 
program goes here
/* 
//  

Using DB2 and ISPF with ADCD

Someone was having problems accessing the DB2 panels from ISPF with their ZPDT system.

Here are some of their notes

  • Using IBMUSER as my TSO/E login.
  • Use DBSPROCC as my TSO proc.
  • After login, issued D A,L and noticed DB2 tasks are running.
  • Navigate to and select 15 – DB2 V12
  • Select 1 – SPUFI Error message: DSNE110E DSN NOT VALID SUBSYSTEM ID, COMMAND TERMINATED
  • Issue D A,L and noticed the DB tasks have DBCG as a prefix.
  • Specified DBCG as the SSID or DB2 Name in DB2 Default panel.
  • Now able to invoke SPUFI and perform DB2 development tasks.
  • Was also able to invoke option 16 – DB2ADM and perform certain tasks

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

Logging on with z/OS userid and password.

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.

API: 2460 (099C) (RC2460): MQRC_HMSG_ERROR during get

I had this during an MQGET when the GMO was not using Version:4.

This would apply to PMO not using Version:3.

Reason 2464: FAILED: MQRC_IMPO_ERROR

MQIMPO impo = {MQIMPO_DEFAULT};

I got this return code because I had a C program on z/OS and compiled it using the ASCII option. This meant the IMPO eye catcher was ASCII 0x494D504F)  instead of EBCDIC 0xC9D4D7D6.

MQIMPO impo = {MQIMPO_DEFAULT};

// if we are in ascii mode we need to convert eyecatcher from ASCII to
// EBCDIC
__a2e_l(impo.StrucId,sizeof(impo.StrucId));

This also applies to

2482: FAILED: MQRC_PD_ERROR

2440 (0988) (RC2440): MQRC_SUB_NAME_ERROR

I got this because my data was in EBCDIC, but I had specified the code page as 437 ( ASCII).

AMQ9669E The PKCS #11 token could not be found

AMQ9669E The PKCS #11 token could not be found. Severity 30 : Error Explanation The PKCS #11 driver failed to find the token specified to MQ in the PKCS #11 token label field of the GSK_PKCS11 SSL CryptoHardware parameter. The channel is <insert_3>; in some cases its name cannot be determined and so is shown as ‘????’. The channel did not start. Response Ensure that the PKCS #11 token exists with the label specified. Restart the channel.

I got this when I had the following in my mqclient.ini

SSL:

SSLCryptoHardware=GSK_PKCS11=/usr/lib/x86_64-linux-gnu/pkcs11/opensc-pkcs11.so\;UserPIN (mytoken)\;12345678\;SYMMETRIC_CIPHER_ON\;

I commented out the SSLCryptoHardware… (I used #SSLCryptoHardware…) and it worked.

Z/OS messages

CSQY010E %CSQ9 CSQYASCP LOAD MODULE CSQYARIB IS NOT AT THE CORRECT
RELEASE LEVEL

If you get CSQYARIB then this can be caused by running Beta code past its validity date.

CSQX630E %CSQ9 CSQXRESP Channel ???? requires SSL

The chinit needs some SSL tasks and a keyring

%csq9 ALTER QMGR SSLTASKS(5) SSLKEYR(MQRING)

Restart the CHINIT.

Return codes

3015 (0BC7) (RC3015): MQRCCF_CFST_PARM_ID_ERR

IBM Documentation: Explanation. Parameter identifier is not valid. The MQCFST Parameter field value was not valid.

Colin’s comment. I passed a value which was valid, but the context was not valid. For example I issued a PCF command to start SMDSCONN. On the console the command gave

CSQM174E M801 CSQMSSMD ‘SMDSCONN’ KEYWORD IS NOT ALLOWED WITH CFLEVEL(4) – THIS KEYWORD REQUIRES CFLEVEL(5)

3229 (0C9D) (RC3220) MQRCCF_PARM_VALUE_ERROR

I got this trying to use MQCMD_INQUIRE_Q.

I had used qtype=MQOT_LOCAL_Q = 1004 when I should have used MQQT_LOCAL = 1

The PCF return message told me the incorrect parameter and value

2019 (07E3) (RC2019): MQRC_HOBJ_ERROR

I got the MQRC_HOBJ_ERROR when using MQSUB to subscribe to a topic. This is described here.

Colin’s comment

I got this because the queue I was using was not consistent with the subscription definition. For example

  • The subscription was using a managed subscription and I was using a queue
  • The queue name I specified in the queue handle did not match the queue name in the subscription

You can use the DISPLAY SUB(..) DEST DESTCLAS

A managed subscription will have a DEST(SYSTEM.MANAGED.DURABLE…) and DESTCLAS(MANAGED).

When using a queue you will have DEST(COLINSUBQ) DISTYPE(RESOLVED)


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

GLD1116E Unable to initialize an SSL connection with …: 515 – Key share list is not valid.

My GSK_CLIENT_TLS_KEY_SHARES GSK_SERVER_TLS_KEY_SHARES environment variables had an invalid value. They had 0021 which is not supported in TLS 1.3.

Look in the gsktrace

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

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 705

I had GSK_OCSP_CLIENT_CACHE_SIZE=10000, when I set it to 100, it worked.

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.

GLD1160E Unable to initialize the LDAP client SSL support: Error 113, Reason -99.

Reason -99 is GSK_ERROR_UNKNOWN_ERROR!

I got this when trying to use OCSP with LDAP. I had

GSK_OCSP_RESPONSE_SIGALG_PAIRS=0601050305010804

If I remove the 0804 ( or 0806 or 0805) then startup got past this message.

GLD1116E Unable to initialize an SSL connection 8 – Certificate validation error.

Colin’s comments

I got this many times for many different reasons.

I had Certificate Revocation List processing enabled.

In the GSK trace I had

ERROR check_crl_issuer_extensions(): crlSign bit is not set in KeyUsage
ERROR check_revoked(): Unable to verify CRL issuer extensions: Error 0x03353026

03353026 Incorrect key usage.

Explanation: The key usage certificate extension does not permit the requested key operation.

My CA was not defined properly I needed

keyUsage = keyCertSign, digitalSignature,cRLSign

GLD1116E Unable to initialize an SSL connection openssl SSL alert number 42

Colin’s comments

The certificate sent from the client was missing the authorityKeyIdentifier extension, because the CA certificate is missing.

In the -config xxx.cnf and the specified -extensions … or the default extensions

subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always, issuer:always

You need to change the CA, regenerate the end user certificate, and redistribute the CA.

533 – Remote partner indicates unsupported certificate.

GLD1116E Unable to initialize an SSL connection 402 – No SSL cipher

Colin’s comments 1

I could see from the gsktrace on zOS there was a message ERROR read_client_hello_cipher_select(): No intersection with client cipher suites.

This means the list of available cipher specs on the server did not include the one sent from the client.

Colin’s comments 2

The server’s certificate was not compatible with the list in GSK_V3_CIPHER_SPECS_EXPANDED. For example the list had only EC certificates, but the server was RSA.

Colin’s comments 3

I had a server certificate defined as

SIZE(521) NISTECC …

In the trace I had

EXIT gsk_get_ec_parameters_info(): <— Exit status 0x00000000 (0) EC curve type 34, key size 521
ERROR send_v3_alert(): Sent SSL V3 alert 40 to 10.1.0.2[38736]

INFO edit_ciphers(): Server certificate ec curve 0034 not in supported ecurve tls extension. EC cipher suites disabled

When I changed the size to 256 it worked, and used (C02C,C02B,C024,C023)

From here 0034 is TLS_DH_anon_WITH_AES_128_CBC_SHA

GLD1116E Unable to initialize an SSL connection with 10.1.0.2: 412 –
SSL protocol or certificate type is not supported.

Colin’s comments

The server had been configured for only GSK_PROTOCOL_TLSV1_3=on.

The GSKTRACE output has Client does not support TLS V1.3. No protocol version match found.

GLD1116E Unable to initialize an SSL connection with … 434 – Certificate key is not compatible with cipher suite.

Colin’s comments

The server’s certificate is not consistent with the certificate sent from the client.

For example the server is using an RSA certificate, but the client is sending and EC certificate.

For example server is an RSA certificate. From the list GSK_V3_CIPHER_SPECS_EXPANDED = C024C006C007C008c024c023c025130313011302009E

it chooses 009E. 009E is TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 =

128-bit AES in Galois Counter Mode encryption with 128-bit AEAD authentication and ephemeral Diffie-Hellman key exchange signed with an RSA certificate.

When my elliptic certificate from the client comes in,

Signature Algorithm: SHA256withRSA, Key: Sun EC public key, 521 bits, parameters: secp521r1 NIST P-521

This is incompatible.

When the server certificate is an Elliptic certificate, then certificate C024 is used. C024 is

TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 256-bit AES encryption with SHA-384 message authentication and ephemeral ECDH key exchange signed with an ECDSA certificate.

This works for both of them.

GLD1116E Unable to initialize an SSL connection 440 – Incorrect key usage.

Colin’s comments.

The server’s certificate was defined with KEYUSAGE(CERTSIGN,KEYAGREE)

When I added HANDSHAKE, recreated the certificate, and restarted the LDAP server it worked. KEYUSAGE(HANDSHAKE,CERTSIGN,KEYAGREE).

Certsign says this is a used as a CA.

GLD1116E Unable to initialize an SSL connection with 127.0.0.1: 533 – Remote partner indicates unsupported certificate.

Colin’s comments.

I got this when the GSK_TLS_SIG_ALG_PAIRS=”0403″ did match up with the server’s version.

In the gsktrace for the server I got TLS 1.3 alert 43 received from

In the client’s gsktrace it had

Certificate key algorithm 13, Signature algorithm 25
INFO read_tls13_certificate(): Using client’s signature algorithm list to check server certificate chain
ERROR read_tls13_certificate(): Signature algorithm 25 in server certificate not in client signature algorithms list
ERROR send_tls13_alert(): Sent TLS 1.3 alert 43 to …

in gskcms.h

  • x509_alg_ecPublicKey = 13,
  • x509_alg_sha256WithRsaEncryption = 25,

GLD1116E Unable to initialize an SSL connection with 127.0.0.1: 467 – Signature algorithm not in signature algorithm pairs list.

See previous for 553. The GSK_TLS_SIG_ALG_PAIRS from the client does not mach the server certitificate’s signature.

RACDCERT LIST(LABEL(‘SERVEREC’ )) id(start1)

gives

Signing Algorithm: sha256RSA

This table says 0401 SHA-256 with RSA, so this value is needed in the GSK_TLS_SIG_ALG_PAIRS.

GLD1116E Unable to initialize an SSL connection with …: 516 – No key share groups in common with parter

The configuration was

  • server GSK_SERVER_TLS_KEY_SHARES=0030
  • client GSK_CLIENT_TLS_KEY_SHARES=0024

Specify values with a common value.

TLS 1.3 supports 00300029002500240023

TLS 1.2 supports 00250024002300210019

So you could specify =002300240025

LDAPSEARCH client on Linux

ldap_search_ext: Bad search filter (-7)

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

remove the &()s

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

worked

ldap_sasl_interactive_bind_s: Unknown authentication method (-6)
additional info: SASL(-4): no mechanism available:

No certificate was sent from the client to the host.

ldap_sasl_interactive_bind_s: Can’t contact LDAP server (-1)
additional info: A TLS fatal alert has been received.

Colin’s comments 1

The list of certificate types the client sent up ( first part of the handshake) did not match any of the list of supported certificates in GSK_V3_CIPHER_SPECS_EXPANDED=009E002FC027c02dc023c025130313011302

I used Wireshark to display network traffic, and the the list of supported certificate types sent in the client hello.

z/OS gsktrace shows

Initial SSL V3 4-character cipher specs:
009E002FC027C02DC023C025130313011302
SSL V3 cipher C02D skipped due to key algorithm
SSL V3 cipher C023 skipped due to key algorithm
SSL V3 cipher C025 skipped due to key algorithm
SSL V3 cipher 1303 skipped for TLS V1.2 sessions
SSL V3 cipher 1301 skipped for TLS V1.2 sessions
SSL V3 cipher 1302 skipped for TLS V1.2 sessions
SSL V3 cipher specs: 009E002FC027
Using TLSV1.2 protocol
Using V3 cipher specification 009E

  • 009E is TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
  • 002F is TLS_RSA_WITH_AES_128_CBC_SHA
  • C027 is TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256

and the z/OS default key had

Signing Algorithm: sha256RSA
Key Usage: HANDSHAKE, DATAENCRYPT, DOCSIGN
Key Type: RSA
Key Size: 4096

Colins’s comments 2

In the gsktrace I had

ERROR check_ocsp_signer_extensions(): extended keyUsage does not allow OCSP Signing

This is because the certificate used in the ocsp server, did not have

Extended Key Usage: critical, OCSP Signing

Resign the certicate and check the attribute has been set by

openssl x509 -in ocspcert.pem -text -nooutless

ldap_sasl_interactive_bind_s: Can’t contact LDAP server (-1)
additional info: An unknown public key algorithm was encountered.

Colin’s comments

As part of the “certificate verify”, the Signature Algorithm passed to the server, was not in the GSK_TLS_SIG_ALG_PAIRS list in the z/OS LDAP environment file.

Check all relevant are specified

GSK_TLS_SIG_ALG_PAIRS=060105010401030108060805080405030403

ldap_sasl_interactive_bind_s: Can’t contact LDAP server (-1)
additional info: (unknown error code)

I got this when using OCSP for certificate validation. OCSP sent down a flow from the server, and the ldapserver code was not expecting it, so ends.

Action: set

GSK_SERVER_OCSP_STAPLING=OFF

ldap_sasl_interactive_bind_s: Invalid credentials (49)


additional info: R004062 Credentials are not valid (srv_ssl_get_client_info:928)

Colin’s comments 1.

The TLS handshake was accepted, but the mapping of the DN to a userid did not return a userid.

Turn on LDAP trace using f GLDSRV,debug LDAPBE gave

LDAPBE srv_process_bind_request()374: do_bind msgID=1, connID=4, flags=0x22, controls=0x0, DN=”, authType=3, bindType=1, version=3
LDAPBE srv_process_bind_request()939: do_return_bind msgID=1, connID=4, bindDN=”, safUserID=”, dnList=0x0, grpList=0x0, rc=49: R004062 Credentials are not valid (srv_ssl_get_client_info:928)

ERROR srv_process_bind_request()957: Request failed OP code=0 bind=CN=secp521r,O=cpwebuser,C=GB

Map the certificate to a userid – note the ‘.’ in the SDNFILTER name.

//S1 EXEC PGM=IKJEFT01,REGION=0M
//STEPLIB DD DISP=SHR,DSN=SYS1.MIGLIB
//SYSPRINT DD SYSOUT=*
//SYSTSPRT DD SYSOUT=*
//SYSTSIN DD *
*RACDCERT LISTMAP ID(ADCDE)
*RACDCERT DELMAP(LABEL('LINUXECEC' )) ID(ADCDE)
*SETROPTS RACLIST(DIGTNMAP, DIGTCRIT) REFRESH
RACDCERT MAP ID(ADCDE ) -
   SDNFILTER('CN=secp521r.O=cpwebuser.C=GB') -
   WITHLABEL('LINUXECEC)
RACDCERT LISTMAP ID(ADCDE)
SETROPTS RACLIST(DIGTNMAP, DIGTCRIT) REFRESH
/*

Once you have defined the mapping, you do not need to restart LDAP, it is picked up on the next usage.

Colin’s comments 2.

The mapping of certificate to userid exists, but the userid is revoked, or otherwise not available.

ERROR srv_ssl_get_client_info() 902: safRc=8 racfRc=8 racfRsn=40

This is from R_usermap (IRRSIM00): Map application user

  • racfrsn 28 – Certificate is not valid.
  • racfrsn 40 – The Distinguished Name length is not valid, or the Distinguished Name string is all blanks (x’20’), all nulls (x’00’), or a combination of blanks and nulls.
  • racfrsn 48 – There is no distributed identity filter mapping the supplied distributed identity to a RACF user ID, or The IDIDMAP RACF general resource class is not active or not RACLISTed.

ldap_ssl_client_init failed! rc == 113, failureReasonCode == 2

This is not listed in table 7 of the LDAP client programming

I turned on trace using ldapsearch .. -d all …

and got

ERROR ldap_ssl_client_init()710: Unable to initialize SSL environment: Error 202
TRACE ldap_ssl_client_init()744: <= Status 113, Reason 2

Error code 202 is in the table = Keyring open error.

ldap_connect()409: Unable to initialize SSL connection to 127.0.0.1[1389]: Error 116, Reason -99

Colin’s comments

This was due to a mismatch in the GSK_TLS_SIG_ALG_PAIRS statement.

ldap_connect()409: Unable to initialize SSL connection to 127.0.0.1[1389]: Error 116, Reason -13

Colin’s comments

This was due to a mismatch in the supported versions of TLS.

ldap_connect()409: Unable to initialize SSL connection to 127.0.0.1[1389]: Error 116, Reason 438

ldap_ssl_socket_initUnable to initialize SSL connection: Error 456.

Colin’s comments

On the system log I had

ICH408I USER(COLIN ) GROUP(SYS1 ) NAME(COLIN PAICE )
CSFOWH CL(CSFSERV )
INSUFFICIENT ACCESS AUTHORITY
ACCESS INTENT(READ ) ACCESS ALLOWED(NONE )

I used the following to get access, and it worked.

permit CSFOWH class(CSFSERV) ACCESS(read) id(COLIN)
SETROPTS RACLIST(CSFSERV) REFRESH

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.