skip to content

RACF — z/OS Security Administration

TSO commands for RACF user, group, dataset, and general-resource profile administration on z/OS.

19 min read 40 snippets deep dive

RACF — z/OS Security Administration#

What it is#

RACF (Resource Access Control Facility) is IBM’s security manager for z/OS — the System Authorization Facility (SAF) implementation that authenticates users at logon, authorises every dataset open and program call, and writes an SMF audit trail of access decisions. Profiles live in the RACF database and are administered through a small family of TSO commands: ADDUSER/ALTUSER for accounts, PERMIT for the access lists, RDEFINE for general-resource profiles, and SETROPTS for system-wide options. Reach for RACF on z/OS systems; the equivalent stacks ACF2 and Top Secret use different command syntax but cover the same SAF surface.

Install#

RACF is shipped with z/OS as a base element; there is nothing to install. The TSO commands are available on every system where RACF is the active SAF backend. Use TSO option 6 (or =6 from anywhere in ISPF) to drop into a command line, or paste them into an ISPF Edit session and prefix with TSO to execute one ad-hoc.

TSO LU ALICE

Output: (none — exits 0 on success)

Syntax#

Every RACF command is a TSO command word followed by positional and keyword operands; class and profile names are quoted with single quotes when they contain mixed case or special characters. Continuation across lines uses a trailing + or -; + strips leading blanks on the next line, - preserves them verbatim.

COMMAND  profile-name  KEYWORD(value)  KEYWORD(value)
COMMAND  profile-name  KEYWORD(value1 value2)         (* list values *)
COMMAND  profile-name  KEYWORD(value) -               (* continued line *)
                       KEYWORD(value)

Output: (none — exits 0 on success)

Essential commands#

CommandMeaning
LISTUSER / LUShow user profile (segments: BASE, TSO, OMVS, …)
LISTGRP / LGShow group profile and members
LISTDSD / LDShow dataset profile and access list
RLISTShow general-resource profile
ADDUSER / AUCreate a new userid
ALTUSER / AU (alias)Modify a userid
DELUSER / DUDelete a userid
ADDGROUP / AGCreate a new group
CONNECT / COAdd a user to a group
REMOVE / RERemove a user from a group
ADDSD / ADAdd a dataset profile
ALTDSD / AD (alias)Alter a dataset profile
DELDSD / DDDelete a dataset profile
PERMIT / PEAdd or remove an access-list entry
RDEFINE / RDEFDefine a general-resource profile
RALTER / RALTAlter a general-resource profile
RDELETE / RDELDelete a general-resource profile
SETROPTS / SETRSystem-wide RACF options
SEARCH / SRFind profiles matching a mask or filter

Users and groups#

User and group profiles are the foundation of RACF — every authenticated session runs under one user, and every user is connected to one default group plus optional additional groups. Each profile carries a stack of segments (BASE for the common attributes, TSO for the logon parameters, OMVS for the USS uid/gid/home, plus DFP, WORKATTR, NETVIEW, and others), and segments are added or queried independently with the BASE/TSO/OMVS keywords.

(* List a user with all segments *)
LISTUSER ALICE TSO OMVS WORKATTR

(* Just the BASE segment *)
LU ALICE

(* List everything in shorthand *)
LU ALICE ALL

Output:

USER=ALICE  NAME=ALICE DEV  OWNER=SECADM   CREATED=24.145
 DEFAULT-GROUP=USERS  PASSDATE=26.145  PASS-INTERVAL= 60 PHRASEDATE=N/A
 ATTRIBUTES=NONE
 REVOKE DATE=NONE   RESUME DATE=NONE
 LAST-ACCESS=26.144/10:32:11
 CLASS AUTHORIZATIONS=NONE
 NO-INSTALLATION-DATA
 NO-MODEL-NAME
 LOGON ALLOWED   (DAYS)          (TIME)
 ---------------------------------------------
 ANYDAY                          ANYTIME
  GROUP=USERS      AUTH=USE      CONNECT-OWNER=SECADM   CONNECT-DATE=24.145
    CONNECTS=  142  UACC=NONE     LAST-CONNECT=26.144/10:32:11
    CONNECT ATTRIBUTES=NONE
    REVOKE DATE=NONE   RESUME DATE=NONE

TSO INFORMATION
---------------
 ACCTNUM= ACCT#
 PROC= IKJACCNT
 SIZE= 00065536
 MAXSIZE= 00000000
 USERDATA= 0000

OMVS INFORMATION
----------------
 UID= 0000000501
 HOME= /home/alice
 PROGRAM= /bin/sh

Creating and modifying users#

ADDUSER creates the userid and seeds whichever segments are listed; ALTUSER changes an existing user. The BASE attributes (SPECIAL, OPERATIONS, AUDITOR) grant system-wide privilege and should be limited to the security team. Default group, owner, and password are required for a usable account — the password is set to a temporary value here and forced to expire so the user must change it at first logon.

(* New user with TSO and OMVS *)
ADDUSER ALICE -
   NAME('Alice Dev')                    -
   OWNER(SECADM)                        -
   DFLTGRP(USERS)                       -
   PASSWORD(WELC0M3)                    -
   TSO(ACCTNUM(ACCT#) PROC(IKJACCNT)    -
       SIZE(65536))                     -
   OMVS(UID(501) HOME('/home/alice')   -
        PROGRAM('/bin/sh'))

(* Force password change on next logon *)
ALTUSER ALICE PASSWORD(WELC0M3) EXPIRED

(* Grant SPECIAL — system-wide RACF authority *)
ALTUSER ALICE SPECIAL

(* Revoke and resume *)
ALTUSER ALICE REVOKE
ALTUSER ALICE RESUME

(* Set a password phrase *)
ALTUSER ALICE PHRASE('correct horse battery staple') EXPIRED

Output: (none — exits 0 on success)

Groups and connects#

Groups own dataset profiles, scope administrative authority, and gather users for PERMIT ID(group) access. A user inherits the access of every group they are CONNECTed to; the default group is the one assumed when RACF=NONE is omitted from the JOB card.

(* New group *)
ADDGROUP DEVTEAM OWNER(SECADM) SUPGROUP(USERS)

(* Connect a user with USE authority *)
CONNECT ALICE GROUP(DEVTEAM) AUTHORITY(USE)

(* Higher authorities: CREATE, CONNECT, JOIN *)
CONNECT ALICE GROUP(DEVTEAM) AUTHORITY(CONNECT)

(* Remove from a group *)
REMOVE ALICE GROUP(DEVTEAM)

(* List a group and its connected users *)
LISTGRP DEVTEAM

Output:

INFORMATION FOR GROUP DEVTEAM
   SUPERIOR GROUP=USERS  OWNER=SECADM    CREATED=26.040
   NO INSTALLATION DATA
   NO MODEL DATA SET
   TERMUACC
   NO SUBGROUPS
   USER(S)=      ACCESS=   ACCESS COUNT=   UNIVERSAL ACCESS=
     ALICE        USE          14              NONE
     BOB          CREATE        2              NONE

Dataset profiles#

Dataset profiles control who can read, write, or delete MVS datasets and are matched against the dataset name at OPEN time. The first qualifier of the dataset name (the HLQ) must match either a userid or a group; access is granted by the most specific profile that covers the dataset. Profiles are either discrete (cover one exact dataset) or generic (cover a pattern with */%/**) — generics are the workhorse and are the only kind you should be writing for a real environment.

(* Discrete profile — exact match only *)
ADDSD 'ALICE.PAYROLL.MASTER' UACC(NONE) OWNER(ALICE)

(* Generic — covers ALICE.PAYROLL.anything (single qualifier) *)
ADDSD 'ALICE.PAYROLL.*' UACC(NONE) OWNER(ALICE) GENERIC

(* Multi-qualifier generic *)
ADDSD 'ALICE.PAYROLL.**' UACC(NONE) OWNER(ALICE) GENERIC

(* List the profile and its access list *)
LISTDSD DATASET('ALICE.PAYROLL.**') ALL GENERIC

Output:

INFORMATION FOR DATASET ALICE.PAYROLL.** (G)

LEVEL  OWNER      UNIVERSAL ACCESS   WARNING   ERASE
 00    ALICE          NONE             NO       NO

AUDITING
--------
FAILURES(READ)

NOTIFY
------
NO USER TO BE NOTIFIED

YOUR ACCESS  CREATION GROUP   DATASET TYPE
-----------  --------------   ------------
   ALTER         DEVTEAM         NON-VSAM

NO INSTALLATION DATA

ID            ACCESS   ACCESS COUNT
--------      ------   ------------
DEVTEAM         UPDATE      42
ALICE           ALTER       91

Profile rules and wildcards#

Generic profiles use a small pattern language; understanding it is the difference between locking down the right datasets and locking the wrong ones. The pattern is matched against the fully qualified dataset name at access time, never against another profile.

PatternMatches
%Exactly one character within a single qualifier
*Zero or more characters within a single qualifier (does not cross a .)
**Zero or more whole qualifiers (only at the start, end, or between two qualifiers)
&RACUIDReplaced with the requesting userid at access time
(* All datasets owned by ALICE *)
ADDSD 'ALICE.**' UACC(NONE) GENERIC

(* Any 8-char third qualifier *)
ADDSD 'ALICE.PROJ.%%%%%%%%' UACC(NONE) GENERIC

(* Any qualifier ending in .LOAD anywhere in the name *)
ADDSD 'ALICE.**.LOAD' UACC(NONE) GENERIC

(* Substring within one qualifier *)
ADDSD 'ALICE.JCL*.PROD' UACC(NONE) GENERIC

Output: (none — exits 0 on success)

PERMIT and access levels#

PERMIT adds (or with DELETE removes) an entry on a profile’s access list. The level is one of NONE, EXECUTE (load-library only), READ, UPDATE, CONTROL (VSAM only), ALTER. The most permissive matching entry wins; a user matching multiple groups gets the union of all their accesses.

(* Grant a user READ *)
PERMIT 'ALICE.PAYROLL.**' ID(BOB) ACCESS(READ) GENERIC

(* Grant a group UPDATE *)
PERMIT 'ALICE.PAYROLL.**' ID(DEVTEAM) ACCESS(UPDATE) GENERIC

(* Revoke *)
PERMIT 'ALICE.PAYROLL.**' ID(BOB) DELETE GENERIC

(* Reset (delete) the entire access list *)
PERMIT 'ALICE.PAYROLL.**' RESET GENERIC

(* List the result *)
LD DA('ALICE.PAYROLL.**') AUTHUSER GENERIC

Output: (none — exits 0 on success)

REFRESH after generic changes#

Generic profile lookups are cached in storage. After ADDSD, ALTDSD, DELDSD, or PERMIT against a generic profile, a SETROPTS GENERIC(DATASET) REFRESH is required for the change to take effect across the system.

SETROPTS GENERIC(DATASET) REFRESH

Output:

ICH14063I SETROPTS COMMAND COMPLETE.

General-resource profiles#

Anything that is not a dataset (TSO commands, JES output classes, DB2 plans, USS facilities, CICS transactions, FTP commands, RRSF nodes, …) is protected by a general resource in one of dozens of classes such as FACILITY, OPERCMDS, TSOAUTH, PROGRAM, JESJOBS, SURROGAT, UNIXPRIV. The profile lives in the class, the protected name lives in the profile, and access is granted exactly the same way: PERMIT name CLASS(class) ID(...) ACCESS(...).

(* Create a FACILITY profile guarding a USS function *)
RDEFINE FACILITY BPX.SUPERUSER UACC(NONE) OWNER(SECADM)

(* Grant a user *)
PERMIT BPX.SUPERUSER CLASS(FACILITY) ID(ALICE) ACCESS(READ)

(* Show the profile *)
RLIST FACILITY BPX.SUPERUSER AUTHUSER

Output:

CLASS      NAME
-----      ----
FACILITY   BPX.SUPERUSER

LEVEL  OWNER     UNIVERSAL ACCESS  YOUR ACCESS  WARNING
 00    SECADM         NONE            READ        NO

USER      ACCESS   ACCESS COUNT
----      ------   ------------
ALICE     READ          17

Common general-resource classes#

ClassWhat it protects
FACILITYBPX.* USS hooks, IRR.* RACROUTE, STGADMIN.* storage admin
OPERCMDSMVS operator commands (MVS.START.*, JES2.MODIFY.*)
TSOAUTHTSO authorisations (OPER, MOUNT, ACCT)
PROGRAMLoad modules — guards PADS and program-controlled environments
JESJOBSSubmit, cancel, modify by job name
JESSPOOLSpool dataset access
SURROGATSubmit jobs under another userid (alice.SUBMIT)
UNIXPRIVGranular USS privileges (SUPERUSER.FILESYS.*)
DSNRDB2 subsystem access
TCICSTRN / GCICSTRNCICS transactions and groups
XFACILITLong-name FACILITY (over 39 chars)
STARTEDStarted-task associations

RACLISTed classes#

Classes that are RACLISTed are kept in storage for fast lookup; changes to them need a class-specific refresh instead of the DATASET refresh.

(* List which classes are RACLISTed *)
SETROPTS LIST | FIND 'RACLIST'

(* Refresh a single class *)
SETROPTS RACLIST(FACILITY) REFRESH

(* Refresh several at once *)
SETROPTS RACLIST(FACILITY OPERCMDS UNIXPRIV) REFRESH

Output:

ICH14063I SETROPTS COMMAND COMPLETE.

SETROPTS — system-wide options#

SETROPTS is the one command that configures RACF as a whole — which classes are active, which are generic-enabled, which are RACLISTed, what the password rules are, and which kinds of events generate SMF records. Only users with the SPECIAL attribute can change these; AUDITOR can read them and change audit-related ones.

(* Display every system option *)
SETROPTS LIST

Output:

ATTRIBUTES = INITSTATS WHEN(PROGRAM) NOEGN
STATISTICS = NONE
ACTIVE CLASSES = DATASET USER GROUP DASDVOL TAPEVOL TERMINAL APPL JESJOBS ...
GENERIC PROFILE CLASSES = DATASET FACILITY OPERCMDS ...
GENLIST CLASSES = NONE
GLOBAL CHECKING CLASSES = NONE
SETROPTS AUDIT CLASSES = USER GROUP DATASET
LOGOPTIONS:
  ALWAYS  = NONE
  NEVER   = NONE
  SUCCESSES = NONE
  FAILURES = DATASET FACILITY OPERCMDS
DEFAULT - DEFAULT - DEFAULT
PASSWORD PROCESSING OPTIONS:
  PASSWORD CHANGE INTERVAL IS 60 DAYS.
  PASSWORD MINIMUM CHANGE INTERVAL IS 1 DAYS.
  MIXED CASE PASSWORD SUPPORT IS IN EFFECT
  6 REVISIONS TO BE REMEMBERED
  ...

Common SETROPTS commands#

(* Activate a class and turn on generic profiles for it *)
SETROPTS CLASSACT(FACILITY) GENERIC(FACILITY)

(* RACLIST a class for in-storage lookup *)
SETROPTS RACLIST(FACILITY)

(* Refresh after profile changes *)
SETROPTS GENERIC(DATASET) REFRESH
SETROPTS RACLIST(FACILITY) REFRESH
SETROPTS WHEN(PROGRAM) REFRESH

(* Password rules — note: MIXEDALL requires content-keyword setup *)
SETROPTS PASSWORD(INTERVAL(60) HISTORY(6) REVOKE(5) MINCHANGE(1) +
                  RULE1(LENGTH(8:12) ALPHA(1,8) NUMERIC(2,7) +
                        MIXEDCONSONANT(1) NOVOWEL))

(* MIXEDALL — modern rule, any-position alphanumeric + special chars *)
SETROPTS PASSWORD(MIXEDCASE) PASSWORD(SPECIALCHARS)
SETROPTS PASSWORD(RULE2(LENGTH(8:12) MIXEDALL(1:12)))

(* KDFAES — stronger password hashing (also disables PWXPWHST field) *)
SETROPTS PASSWORD(ALGORITHM(KDFAES))

(* Audit *)
SETROPTS AUDIT(DATASET USER GROUP)
SETROPTS LOGOPTIONS(FAILURES(DATASET FACILITY))
SETROPTS LOGOPTIONS(SUCCESSES(OPERCMDS))
SETROPTS OPERAUDIT

(* Erase-on-scratch (DoD requirement) *)
SETROPTS ERASE(ALL)

(* Display only the password section *)
SETROPTS LIST PASSWORD

Output: (none — exits 0 on success)

What’s new — z/OS 3.2 and recent APARs#

z/OS 3.2 (GA 30 September 2025) and recent APAR rollups deliver several RACF capabilities worth knowing about. None of them change the day-to-day TSO syntax, but each unlocks new policy options.

ALTUSER ... CONTAIN — quarantine a userid (z/OS 3.2)#

Until 3.2 the strongest immediate action against a compromised account was ALTUSER ... REVOKE, which prevents new logons but lets currently-active sessions continue accessing RACF-protected resources. z/OS 3.2 adds CONTAIN (and NOCONTAIN) — when applied, the userid is revoked and in-flight access decisions for that userid start denying immediately, so an attacker who is already authenticated cannot continue to open new datasets. Pair with UAUDIT for a complete forensic trail. The companion product IBM Threat Detection for z/OS (TDz) can trigger CONTAIN automatically when an anomaly score crosses a threshold.

ALTUSER EMRG01 CONTAIN UAUDIT          (* quarantine — denies even active sessions *)
ALTUSER EMRG01 NOCONTAIN               (* release the quarantine *)
LU EMRG01                              (* CONTAIN status shows in BASE segment *)

Output: (LU excerpt)

USER=EMRG01  NAME=EMERGENCY FIX  OWNER=SECADM
 ATTRIBUTES=SPECIAL CONTAIN
 REVOKE DATE=NONE   RESUME DATE=NONE
 UAUDIT=YES

Digital certificate enhancements (z/OS 3.2)#

z/OS 3.2 RACF adds multi-alternate-name support to RACDCERT and certificate validation — a single certificate can carry multiple Subject Alternative Names (SANs) for different hostnames or service accounts. SHA-384 and SHA-512 signing algorithms are accepted on certificate generation, replacing the SHA-256 ceiling of earlier releases. Use these when partner systems require certificates that comply with current CA/Browser Forum baselines or with FIPS 140-3 cipher suites.

RACDCERT GENCERT(myhost.example.com) -
   ID(WEBSRV1) -
   SUBJECTSDN(CN('myhost.example.com')) -
   ALTNAME(IP('10.20.30.5')                  -
           DOMAIN('myhost.example.com')      -
           DOMAIN('api.example.com')         -
           DOMAIN('admin.example.com')) -
   SIZE(4096) SIGNWITH(CERTAUTH LABEL('SiteCA')) -
   WITHLABEL('webcert') -
   HASH(SHA512)

Output: (none — exits 0 on success; verify with RACDCERT LIST(LABEL('webcert')) ID(WEBSRV1))

Multi-Factor Authentication and RACF Identity Tokens#

IBM Z Multi-Factor Authentication (IBM Z MFA, product 5655-MA1, current release 2.2) integrates with RACF for layered authentication. Modern shops typically run compound in-band: the user types their RACF password or passphrase followed by an OTP from IBM Cloud Identity Verify (CIV), Yubikey, RSA SecurID, or TOTP. Configure factors with MFADEF general-resource profiles and bind them to a userid with ALTUSER ... MFA(...). APAR OA55926 introduced RACF Identity Tokens — short-lived JWTs returned by RACROUTE REQUEST=VERIFY that callers can replay across subsequent verify calls so the entire authentication sequence is recognised as one transaction.

(* Activate the MFADEF class and bind a TOTP factor to a user *)
SETROPTS CLASSACT(MFADEF) GENERIC(MFADEF) RACLIST(MFADEF)
RDEFINE MFADEF FACTOR.AZFTOTP1 UACC(NONE)
ALTUSER ALICE MFA(FACTOR(AZFTOTP1) ACTIVE TAGS(TIMEOUT:60))

(* Tag the userid as "MFA required" — userid cannot log on with password alone *)
ALTUSER ALICE MFA(PWFALLBACK(NO))

(* Verify *)
LU ALICE MFA

Output:

MFA INFORMATION
---------------
PWFALLBACK = NO
FACTOR = AZFTOTP1  ACTIVE = YES  TAGS = TIMEOUT:60
LAST FACTOR USED = AZFTOTP1

Cloud Identity Verify and SAML/JWT bridge#

IBM Cloud Identity Verify (CIV) can issue OTPs in-band, and recent APARs extend the bridge so an external SAML/JWT identity provider can be the primary authenticator with RACF acting as the resource manager. The mapping is held in the IDIDMAP class — a SAML name-ID or JWT subject is mapped to a RACF userid:

RDEFINE IDIDMAP SAML.alice@example.com APPLDATA('ALICE') UACC(NONE)
SETROPTS RACLIST(IDIDMAP) REFRESH

Output: (none — exits 0 on success)

SEARCH — find profiles#

SEARCH queries the RACF database for profiles matching a name mask or by attribute. It is the right tool for inventorying everything a user can touch, finding profiles with broad UACC, or pulling a working list before a migration.

(* All dataset profiles starting with ALICE *)
SEARCH CLASS(DATASET) MASK(ALICE)

(* Same with a filter character (% any char in qualifier, * any qualifier) *)
SEARCH CLASS(DATASET) FILTER(ALICE.*.PROD.**)

(* Profiles owned by ALICE *)
SEARCH USER(ALICE) CLASS(DATASET)

(* Profiles with UACC ≥ READ — the audit favourite *)
SEARCH CLASS(DATASET) UACC(READ)

(* Profiles a user has explicit access to *)
SEARCH USER(BOB) CLASS(DATASET) ACCESS(UPDATE)

(* Profiles in a general-resource class *)
SEARCH CLASS(FACILITY) MASK(BPX)

(* Pipe output as CLIST input — generate commands *)
SEARCH CLASS(DATASET) MASK(ALICE) -
       CLIST('PERMIT ' ' ID(BOB) ACCESS(READ) GENERIC')
EX 'ALICE.EXEC.RACF.CLIST'

Output:

ALICE
ALICE.JCL.**             (G)
ALICE.PAYROLL.**         (G)
ALICE.SOURCE.**          (G)
ALICE.LOAD.**            (G)

Auditing and SMF records#

RACF writes audit events to SMF — type 80 records cover RACF command activity and access decisions, type 81 records cover the RACF initialisation at IPL, and type 83 records cover R_auditx events from Db2, RRSF, and other subsystems. The combination of SETROPTS AUDIT, SETROPTS LOGOPTIONS, and per-profile AUDIT(...) keywords decides which events get cut.

(* Audit a single dataset profile aggressively *)
ALTDSD 'ALICE.PAYROLL.**' AUDIT(SUCCESS(UPDATE) FAILURES(READ)) -
       GENERIC NOTIFY(ALICE)

(* Audit a FACILITY profile *)
RALTER FACILITY BPX.SUPERUSER AUDIT(ALL(READ))

(* System-wide: audit all failures in DATASET and FACILITY *)
SETROPTS LOGOPTIONS(FAILURES(DATASET FACILITY))

(* System-wide: audit successful TSO logons *)
SETROPTS LOGOPTIONS(SUCCESSES(TSOAUTH))

(* Generate a quick report from SMF using IRRADU00 *)
//RACFRPT  EXEC PGM=IFASMFDP
//SYSPRINT DD SYSOUT=*
//ADUPRINT DD SYSOUT=*
//OUTDD    DD DSN=ALICE.IRRADU00.OUT,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(5,5)),DCB=(RECFM=VB,LRECL=8192,BLKSIZE=0)
//SMFIN    DD DSN=SYS1.MAN1,DISP=SHR
//SMFOUT   DD DUMMY
//SYSIN    DD *
   INDD(SMFIN,OPTIONS(DUMP))
   OUTDD(SMFOUT,TYPE(000:255))
   USER2(IRRADU00)
   USER3(IRRADU86)
/*

Output:

IFA010I  SMF DUMP PARAMETERS
         INDD(SMFIN,OPTIONS(DUMP))
         OUTDD(SMFOUT,TYPE(000:255))
         USER2(IRRADU00) -- RECORDS EXTRACTED
         USER3(IRRADU86)
IFA020I  RECORDS READ FROM SMFIN              123456
IFA020I  RECORDS WRITTEN TO ADUPRINT           4218
IRR67100I  IRRADU00 PROCESSING COMPLETE.  RC=0

Useful audit fields#

Field in IRRADU00 outputMeaning
EVENTThe RACF event code (ACCESS, ADDSD, PERMIT, LOGON, …)
EVENT QUALSUCCESS, INSAUTH, VIOL, WARNED, FAILED
USERUserid that did the action
JOBIDJES job ID (batch) or TSU* (TSO)
RESOURCEDataset or general-resource name
INTENT / ALLOWEDRequested vs granted access level
PROFILEProfile that matched (or **NOPROF** if none)

Common pitfalls#

  1. Forgetting SETROPTS … REFRESH after a generic changePERMIT runs cleanly, the user still gets denied. After any change to a generic DATASET profile run SETROPTS GENERIC(DATASET) REFRESH; for a RACLISTed class run SETROPTS RACLIST(class) REFRESH.
  2. Discrete vs genericLISTDSD DATASET('ALICE.PROD') matches a discrete profile first; the matching generic is shown with (G). Add GENERIC to force a generic-only view, otherwise you may be staring at a stale discrete profile no one cleaned up.
  3. UACC(READ) on a dataset profile is world-readable — universal access applies to every authenticated user, not just the access list. Use UACC(NONE) and grant explicitly via PERMIT. The SEARCH CLASS(DATASET) UACC(READ) query above is the audit-friendly way to find profiles that violate this.
  4. PADS and the PROGRAM class — once WHEN(PROGRAM) is active, datasets with WHEN(PROGRAM(name)) permits are only accessible while running under that program. A user with no other access will get ICH408I violations until they go through the controlled load module.
  5. * vs ** in generic profilesALICE.* matches ALICE.X but not ALICE.X.Y; you need ALICE.** for that. The most common mistake when migrating from one system to another.
  6. Password phrases require ICHPWX11 — the PHRASE(...) keyword is rejected unless the password-phrase exit is installed and SETROPTS PASSWORD(RULE…) permits it. Check SETROPTS LIST for PHRASE PROCESSING.
  7. SPECIAL is not the same as OPERATIONSSPECIAL lets you administer RACF; OPERATIONS bypasses RACF dataset checks (for storage management). Granting OPERATIONS by mistake gives a user implicit ALTER on every dataset profile.
  8. Group-OWNER vs profile-OWNER — the OWNER of a profile can administer it; the OWNER of a group can administer the group’s members. Don’t conflate them or you’ll grant the wrong scope.
  9. UID(0) in OMVS — any USS user with UID 0 is root and bypasses POSIX permissions. Use UNIXPRIV profiles (SUPERUSER.FILESYS.*, SUPERUSER.PROCESS.*) for granular privilege instead.
  10. SEARCH with no CLASS() defaults to DATASET — a SEARCH MASK(SYS1) against a non-DATASET class quietly returns nothing matching; always include CLASS(...).

Real-world recipes#

Grant a TSO user UPDATE access to a new dataset prefix#

A developer needs to write to ALICE.PROD.PAYROLL.**. The profile may not exist yet; you also want failure logging and a notification on access violations.

(* 1. Create the generic profile if it doesn't exist *)
ADDSD 'ALICE.PROD.PAYROLL.**' UACC(NONE) OWNER(SECADM) -
   NOTIFY(SECADM) GENERIC AUDIT(FAILURES(READ))

(* 2. PERMIT *)
PERMIT 'ALICE.PROD.PAYROLL.**' ID(BOB) ACCESS(UPDATE) GENERIC

(* 3. Refresh *)
SETROPTS GENERIC(DATASET) REFRESH

(* 4. Verify *)
LISTDSD DATASET('ALICE.PROD.PAYROLL.**') ALL GENERIC

Output:

ICH06011I RACF DATABASE BACKUP DATA SET IS NOT ACTIVE.
ICH14063I SETROPTS COMMAND COMPLETE.
INFORMATION FOR DATASET ALICE.PROD.PAYROLL.** (G)
...
   ID       ACCESS   ACCESS COUNT
   BOB      UPDATE      0

Onboard a new developer#

Create the userid, attach the TSO and OMVS segments, connect to the development group, and confirm the result.

ADDUSER CAROL -
   NAME('Carol Dev')                 -
   OWNER(SECADM)                    -
   DFLTGRP(USERS)                   -
   PASSWORD(C4r0lDev)               -
   TSO(ACCTNUM(ACCT#) PROC(IKJACCNT) SIZE(65536)) -
   OMVS(UID(503) HOME('/home/carol') PROGRAM('/bin/sh'))

ALTUSER CAROL PASSWORD(C4r0lDev) EXPIRED

CONNECT CAROL GROUP(DEVTEAM) AUTHORITY(USE)

LISTUSER CAROL TSO OMVS

Output:

USER=CAROL  NAME=CAROL DEV  OWNER=SECADM   CREATED=26.145
 DEFAULT-GROUP=USERS
 ATTRIBUTES=NONE
 ...
 GROUP=DEVTEAM    AUTH=USE
 ...
TSO INFORMATION  ACCTNUM=ACCT#  PROC=IKJACCNT  SIZE=65536
OMVS INFORMATION  UID=503  HOME=/home/carol  PROGRAM=/bin/sh

Lock down an emergency-fix userid#

A break-glass account should be REVOKEd by default, RESUMEd on demand, force password-change every login, and be audited heavily.

ADDUSER EMRG01 NAME('Emergency Fix') OWNER(SECADM) -
   DFLTGRP(SECGRP) PASSWORD(Br3@kGl@s) -
   TSO(ACCTNUM(SEC#) PROC(IKJACCNT))

ALTUSER EMRG01 SPECIAL                 (* tightly held privilege *)
ALTUSER EMRG01 REVOKE                  (* default locked *)

(* Audit every action *)
SETROPTS LOGOPTIONS(SUCCESSES(USER))

(* Audit any time the userid does anything *)
ALTUSER EMRG01 UAUDIT

Output: (none — exits 0 on success)

Convert from UACC(READ) to explicit READ#

You discovered (via the audit SEARCH above) that a dataset has world-read enabled and need to lock it down without breaking current consumers.

(* 1. Find who has been using it (last 30 days, SMF reduction is offline) *)
SEARCH CLASS(DATASET) MASK(SHARED.REPORTS) ACCESS(UPDATE)

(* 2. Add an explicit READ for the group that legitimately needs it *)
PERMIT 'SHARED.REPORTS.**' ID(REPORTRS) ACCESS(READ) GENERIC

(* 3. Lower UACC to NONE *)
ALTDSD 'SHARED.REPORTS.**' UACC(NONE) GENERIC

(* 4. Refresh *)
SETROPTS GENERIC(DATASET) REFRESH

(* 5. Watch for legitimate breakage for a week *)
ALTDSD 'SHARED.REPORTS.**' AUDIT(FAILURES(READ)) GENERIC NOTIFY(SECADM)

Output: (none — exits 0 on success)

Allow a user to submit jobs as another userid#

The SURROGAT class controls whether one user (the surrogate) can submit a job that runs under another userid (the execution user).

(* Define the profile guarding job submission under userid PRODSVC *)
RDEFINE SURROGAT PRODSVC.SUBMIT UACC(NONE) OWNER(SECADM)

(* PERMIT the user *)
PERMIT PRODSVC.SUBMIT CLASS(SURROGAT) ID(ALICE) ACCESS(READ)

(* SURROGAT is RACLISTed in most shops *)
SETROPTS RACLIST(SURROGAT) REFRESH

(* ALICE can now code USER=PRODSVC on a JOB card *)
LD DA('ALICE.JCL.LIB(SURJOB)')

Output: (none — exits 0 on success)

Find every profile a leaving employee owns or can use#

Before deleting the user account, transfer ownership and revoke explicit access.

(* Profiles they own *)
SEARCH USER(DAVE) CLASS(DATASET)

(* Profiles they appear on the access list of *)
SEARCH CLASS(DATASET) FILTER(**) USER(DAVE) ACCESS(READ:ALTER)

(* Generate transfer commands *)
SEARCH USER(DAVE) CLASS(DATASET) -
   CLIST('ALTDSD ' ' OWNER(SECADM) GENERIC')
EX 'DAVE.EXEC.RACF.CLIST'

(* Generate removal commands *)
SEARCH CLASS(DATASET) FILTER(**) USER(DAVE) -
   CLIST('PERMIT ' ' ID(DAVE) DELETE GENERIC')
EX 'DAVE.EXEC.RACF.CLIST'

(* Finally, delete the userid *)
DELUSER DAVE

Output: (none — exits 0 on success)

Build a one-shot SMF audit report#

Pull yesterday’s RACF events, format with IRRADU00, and dump to a sequential dataset for spreadsheet ingestion. Pair this with a Zowe files download to pull it to a laptop.

//ALICEJ01 JOB (ACCT),'AUDIT REPORT',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//RACFRPT  EXEC PGM=IFASMFDP
//SYSPRINT DD SYSOUT=*
//ADUPRINT DD SYSOUT=*
//OUTDD    DD DSN=ALICE.IRRADU00.YDAY,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(5,5)),DCB=(RECFM=VB,LRECL=8192)
//SMFIN    DD DSN=SYS1.MAN1,DISP=SHR
//SMFOUT   DD DUMMY
//SYSIN    DD *
   INDD(SMFIN,OPTIONS(DUMP))
   OUTDD(SMFOUT,TYPE(080))
   USER2(IRRADU00)
   DATE(2026146,2026146)
/*

Output:

IFA020I  RECORDS READ FROM SMFIN              28714
IFA020I  RECORDS WRITTEN TO ADUPRINT           1184
IRR67100I  IRRADU00 PROCESSING COMPLETE.  RC=0

Sources#