More flexibility for user management in MQ

A new option in MQ 9.2.1 on Unix/Linux platforms gives more flexibility in user management, with no need to make operating system definitions for application users. This post describes why you might want this, how you can use the option and gives a simple example to demonstrate it. Note that this does not apply to Windows or the MQ Appliance.

MQ has never tried to be a repository of user definitions. Before MQ 8, it always used the operating system as the source of user and group information. An alternative mechanism was then added to allow MQ to go directly to an LDAP directory. One article I wrote about configuring LDAP is here. While using LDAP can be very effective if you already have a central repository to hold your identities, there are times when it may be overkill. Especially if you do not already have a managed directory. So for the rest of this article I’ll ignore the LDAP approach. Instead I’ll talk about changes to the “operating system” approach.

When you are an MQ administrator, you may not have the ability to define new users on your platform. And when you are running MQ inside a container, it may actually be impossible to define new users because of how security is managed in that environment. We wanted to enable use of a non-operating system repository which gives the MQ administrators the ability to more directly control who can access the queue manager, without requiring broader privileges.

This clearly can be useful for client-connected applications where the username comes in some way from the application connection.

But it also can help with scenarios where the username comes from an inbound message such as from a RCVR channel. That username may need to be authorised to access the target queue or (in some circumstances) for report messages to be sent back to the originator via a ReplyQueue. If your queue manager receives messages from another organisation where there is no common shared cross-system userid management, then you may have to map inbound usernames to a known local username. That may perhaps use a message exit or a forwarding application on a gateway queue manager that accepts any userid and sends the message onwards with your known id.

Now there is an option for queue manager configuration that says that users can be defined via some external mechanism. Which might then be manageable by an MQ administrator.

The UserExternal policy

Traditionally, MQ’s security approach on Unix platforms has been group-based. If you do a setmqaut -p user, the default behaviour is to change permissions on the user’s primary group. MQ 8 added a new SecurityPolicy=User option that can be set in the qm.ini file, or be part of the crtmqm command using the -oa option. Permissions can be set for users explicitly, ignoring their groups (although permissions that have been assigned to groups to which that user belongs are still applied when we need to check authorities).

MQ 9.2.1 extends this with a new SecurityPolicy=UserExternal option.

Service:
   Name=AuthorizationService
   EntryPoints=14
   SecurityPolicy=UserExternal

This behaves exactly the same as the User option except that the username need not be known to the operating system. So you can define authorisations for users that are defined in some arbitrary external repository (or even not defined anywhere at all).

You do not need to manage all of your users outside of the OS. If a username is known to the OS, then it can be used in the normal way. And any OS groups to which that user belongs are also respected.

Essentially the return code from the “does this user exist in the OS” check made by the queue manager is being ignored so that the rest of the authorisation checks can be made.

12 character limit remains

The username must still fit inside the 12 character limit allowed in places such as the MQMD.UserIdentifer field.

External users are not in any OS groups

These users are not considered to be in any operating system groups. MQ on Unix systems does have a pseudo-group, “nobody“, to which everybody belongs, including the non-OS users. That makes it possible to do things like setmqaut -t qmgr -m QM1 -g nobody +connect to allow everyone to connect regardless of who they are and what other groups they belong to. But for obvious reasons that is rarely used.

If you want to add a group concept for these non-OS users, then it ought to be possible to write and add an NSS module that acts as an alternative source of group information. As an NSS configuration affects every program on the system, it might be fine for a container environment. But you may want to be more selective when running in a full OS installation. I’ve not actually written a functional or usable NSS module (just an exploratory proof-of-concept which also proved how poor some of the OS docs are in this area but at least the invoking glibc source code is available for study); perhaps something better could be a later exercise.

Setting Authorisations

There is no real change to managing authority settings. The user must still be given authorisation to connect to the queue manager and access any other objects. All the commands such as dmpmqaut or SET AUTHREC work exactly as before – subject to you remembering that the permissions only apply to the username, who is of course not a member of any normal group.

On a queue manager without the UserExternal setting, trying to set permissions for an unknown user will fail:

$ setmqaut -t qmgr -m QM1 -p user1 +connect
AMQ7026E: A principal or group name was invalid.

Modifying the qm.ini file to set the SecurityPolicy and restarting the queue manager, we now have:

$ setmqaut -t qmgr -m QM1 -p user1 +connect
The setmqaut command completed successfully.

Controlling the username

There are many ways in which the username associated with a connection can be controlled.

The simplest way is probably to set an MCAUSER value on a SVRCONN channel. Anyone using that channel will be treated as if that is their username. But of course that is not likely to be a secure system without additional controls.

Various other ways of validating and assigning identities can be used with MQ. None of these are new, but they probably become more critical now that there may be no local definition of that user to act as a back-stop check.

  • You might use CHLAUTH rules to assign an MCAUSER.
  • A channel security exit might set the MCAUSER based on TLS certificate names.
  • I have seen an extension to MQ’s Object Authority Manager that overrides the Authentication point, allowing the user (and a password) to be verified through some custom code.
  • I’ve also seen a JAAS module to control users coming in through AMQP channels.

But in this article I’ll give a simple example of how you might exploit the PAM authentication process in a regular (non-containerised) MQ environment.

Receiving-type channels

There is no authentication applied to messages arriving across a channel. So a PAM setup is not relevant for these. But there may still be authorisation requirements. Setting PUTAUT(CTX) on the channel means that the MQMD.UserIdentifier applies when determining if the message can be put to the target queue.

And now you can set the authorities for that user even if it is not known locally.

Authentication Example with PAM

The PAM authentication process allows a variety of user validation modules to be executed, possibly in a chain to permit multiple checks. For MQ, the modules to be invoked are defined in the /etc/pam.d/ibmmq file. Its default setting uses a system-level checking of a user’s password and that the account does exist in the OS. The CONNAUTH attribute instructs the queue manager to use an AUTHINFO object that in turns refers to PAM for the authentication steps:

Starting MQSC for queue manager QM1.
display qmgr connauth
     1 : display qmgr connauth
AMQ8408I: Display Queue Manager details.
   QMNAME(QM1)                             CONNAUTH(PAM)
display authinfo(PAM) all
     2 : display authinfo(PAM) all
AMQ8566I: Display authentication information details.
   AUTHINFO(PAM)                           AUTHTYPE(IDPWOS)
   ADOPTCTX(YES)                           DESCR( )
   CHCKCLNT(OPTIONAL)                      CHCKLOCL(OPTIONAL)
   FAILDLAY(1)                             AUTHENMD(PAM)
   ALTDATE(2020-03-19)                     ALTTIME(12.30.48) 

So we need to modify how PAM makes its checks. One of the common PAM modules is pam_exec which allows an arbitrary external command to be used within the chain. Although we want to validate non-OS users through this, we also want to still work with OS users when they are defined. First I modify the /etc/pam.d/ibmmq file:

auth sufficient pam_exec.so expose_authtok /var/mqm/exits/p_exec.sh 
auth include system-auth 

account sufficient  pam_exec.so  /var/mqm/exits/p_exec.sh 
account include system-auth 

For both authentication and account existence, this first tries the pam_exec module. If the program it calls succeeds (returns 0) then no further checks are needed. If it fails, PAM goes on to try the standard OS-based checks. The expose_authtok directive in this file tells PAM to push the user-supplied password to the processing module where it can be read via stdin. The invoked program accesses other important values through environment variables. In order for PAM to permit access, both the authentication and account tests must be passed.

Next, we need a callable program to do the verification. I wrote a very simple script, which in turn checks the password in an external file. As you can see, the passwords are in plaintext here. Not very secure, but it’s good enough for a demonstration.

Configuration file

The configuration file has lines with the users and their passwords:

# The format consists of username:password lines. Password is
# plaintext! No spaces before or between tokens
user1:passw0rd1
user2:passw0rd2

The script assumes this file is in /var/mqm/exits/p_exec.cfg.

The invoked script

And the script itself. Store it in /var/mqm/exits/p_exec.sh and make sure it is executable.

#!/bin/bash
function debug {
   if $debug
   then
     echo $* >> $log
   fi
}
rc=1 # assume error
cfgFile=/var/mqm/exits/p_exec.cfg
log=/tmp/p_exec.log
debug=false
debug "-------------------"
debug `date`
if [ ! -r $cfgFile ]
then
  debug "Can't read $cfgFile"
  exit 1
fi

# Each line looks like "user:password"
l=`grep "^$PAM_USER:" $cfgFile 2>/dev/null`
if [ -z "$l" ]
then
  debug "Can't find user $PAM_USER in $cfgFile"
  rc=1
else
  if [ $PAM_TYPE = "auth" ]
  then
    # It's looking for authentication. Does password match?
    fpw=`echo $l | cut -d: -f2`
    read upw # Password supplied on stdin
    if [ "$fpw" != "$upw" ]
    then
      debug "Password mismatch"
      rc=1
    else
      debug "Approving auth check for $PAM_USER"
      rc=0
    fi
  else
    # Can approve it now we know it's in the file
    debug "Approving acct check for $PAM_USER"
    rc=0
  fi
fi
exit $rc

I can now use one of the sample programs to adopt an identity:

$ echo passw0rd1 | MQSAMP_USER_ID=user1 amqsget Q QM1
Sample AMQSGET0 start
Enter password: 
MQOPEN ended with reason code 2035
unable to open queue for input
Sample AMQSGET0 end

You can see that the authentication must have succeeded and the rest of the connection process has also succeeded (as we did the setmqaut for the user earlier) as we have not got an MQCONN failure. But the MQOPEN has failed as we’ve not authorised that.

In a real system, the script would likely be replaced by a more complex program, and the passwords would be stored more securely, but this should show the core concepts.

And to prove that OS usernames continue to work as before, I can run a program as myself:

$ echo "<redacted>" | MQSAMP_USER_ID=`whoami` amqsget Q QM1
Sample AMQSGET0 start
Enter password: 
no more messages
Sample AMQSGET0 end

Errors

Looking in the queue manager error log after that test, we see what we would hope to see:

AMQ8077W: Entity 'user1' has insufficient authority to access object Q [queue].
EXPLANATION:
The specified entity is not authorized to access the required object.
The following requested permissions are unauthorized: get
ACTION:
Ensure that the correct level of authority has been set for this entity
against the required object, or ensure that the entity is a member of a
privileged group. 

Resolving authentication and authorisation errors is identical to how it has been in the past. Just one extra warning to be aware of: because there is no longer a check on whether a user exists, any typos when you set authorities (including using the wrong case for a username) will be preserved in the authority records. So you may want to more frequently do a dmpmqaut to cleanup any erroneous authorisations.

Conclusion

This new option is intended to give more flexibility for MQ administrators, particularly but not exclusively to those running queue managers inside containers. I hope it proves useful.

This post was last updated on December 3rd, 2020 at 02:08 pm

Leave a Reply

Your email address will not be published. Required fields are marked *