LearnAI ToolsCareerPractice BuildsPlayContact
Mainframe SystemsIntermediate~1.5 hours

Plan a RACF Access Model

Define user groups and dataset permissions for a small mainframe team.

RACFSecurityAccess Control

Overview

RACF (Resource Access Control Facility) does not protect individual files the way file-system permissions do on a single machine — it protects *profiles*, which are pattern-matched against dataset names, and it grants access to *groups*, which users are connected to rather than being permissioned one by one. This indirection is deliberate: a new hire who joins the developer team gets every permission that team already has just by being connected to `DEVGRP`, and someone leaving the team loses all of it by being disconnected — no one has to remember to individually revoke a dozen separate dataset permissions.

This project designs a RACF access model for a small mainframe team working on the student-records system from Projects 2 and 3: three groups — operators, developers, and auditors — with different levels of access to the production datasets, the JCL/program libraries, and the master file, expressed as real `ADDGROUP`, `ADDUSER`, `ADDSD`, and `PERMIT` commands. By the end you will understand the READ/UPDATE/ALTER/CONTROL access hierarchy and why "who can run the job" and "who can change the JCL" are deliberately different questions with different answers.

What You'll Build
  • Three RACF groups — OPERGRP, DEVGRP, AUDGRP — each representing a real job function.
  • User-to-group connections via `ADDUSER`/`CONNECT`.
  • Dataset profiles via `ADDSD` covering the production, JCL, and master datasets.
  • Access grants via `PERMIT`, mapping each group to the correct access level per dataset category.
  • A verification pass using `LISTDSD` to confirm the access model matches the plan.

Prerequisites

  • The dataset layout from Project 2 — this project secures STU.DAILY.TRANS, STU.PROD.JCLLIB, STU.PROD.LOADLIB, and STU.MASTER.KSDS directly.
  • The general idea of role-based access control from any prior security background — permissions attached to a role, not to an individual.
  • Dataset name patterns and high-level qualifiers, since RACF dataset profiles are usually defined against a pattern, not one exact name.
  • That READ, UPDATE, ALTER, and CONTROL form an increasing hierarchy of access, not four unrelated flags.

Project Structure

Three commands types build the whole model, applied in a fixed order: groups must exist before users can be connected to them, and dataset profiles must exist before access can be permitted against them. `ADDGROUP`/`CONNECT` define *who*, `ADDSD`/`ALTDSD` define *what* is protected, and `PERMIT` is the piece that actually joins the two — which is also why revoking a group's access is a single `PERMIT ... DELETE` per profile, not a hunt through every user's individual settings.

CommandDefinesDepends On
ADDGROUPA RACF group representing a roleNothing — first step
ADDUSER / CONNECTWhich users belong to which group(s)The group must already exist
ADDSDA dataset profile covering a name patternNothing — can run in parallel with group setup
PERMITA group's access level against a dataset profileBoth the group and the profile must already exist

Step 1: Define RACF Groups

Each group below maps to one real job function on the student-records team, and the access each will get in Step 4 is scoped to what that function actually needs — this is the principle of least privilege applied at the group-design stage, before a single permission is granted. `SUPGROUP` names the superior group each new group is created under, forming a hierarchy RACF uses for administrative delegation; here all three report up to a shared `STUADMIN` group owned by the security administrator.

ADDGROUP OPERGRP SUPGROUP(STUADMIN) OWNER(SECADM) -
DATA('STUDENT-RECORDS BATCH OPERATORS')
ADDGROUP DEVGRP SUPGROUP(STUADMIN) OWNER(SECADM) -
DATA('STUDENT-RECORDS DEVELOPERS')
ADDGROUP AUDGRP SUPGROUP(STUADMIN) OWNER(SECADM) -
DATA('STUDENT-RECORDS AUDITORS')
*
* OPERGRP - runs the nightly batch cycle, needs to READ input/UPDATE
* the master, but should never be able to edit the JCL itself
* DEVGRP - writes and changes JCL/programs, needs ALTER on JCLLIB/
* LOADLIB but only READ on live production data
* AUDGRP - reviews what happened, needs READ everywhere and nothing more
*
* DATA() is a free-text description, purely documentary - it has no
* effect on access, but makes 'LISTGRP OPERGRP' self-explanatory later.

Step 2: Connect Users to Groups

`ADDUSER` creates the user ID and gives it a default group in one step; `CONNECT` adds a *second* group membership without changing the user's default. Both are shown here because a real hire is usually added fresh (`ADDUSER`), while an existing user picking up a second responsibility — like a developer who also needs to review audit output — is connected to an additional group instead. `AUTHORITY(USE)` on the connect is the ordinary member-level authority; it does not grant any RACF administrative power over the group itself.

ADDUSER OPUSER1 DFLTGRP(OPERGRP) OWNER(SECADM) -
NAME('BATCH OPERATIONS TEAM') PASSWORD(TEMP0001) OWNER(SECADM)
ADDUSER DEVUSER1 DFLTGRP(DEVGRP) OWNER(SECADM) -
NAME('J SMITH - DEVELOPER') PASSWORD(TEMP0002) OWNER(SECADM)
ADDUSER AUDUSER1 DFLTGRP(AUDGRP) OWNER(SECADM) -
NAME('M PATEL - AUDITOR') PASSWORD(TEMP0003) OWNER(SECADM)
*
* Each ADDUSER creates the ID with its DFLTGRP as primary group -
* PASSWORD() here is a forced temporary password the user must
* change at first logon; RACF flags it as expired automatically.
*
CONNECT DEVUSER1 GROUP(AUDGRP) AUTHORITY(USE) OWNER(SECADM)
*
* DEVUSER1 now belongs to BOTH DevGrp (default) and AudGrp (connected) -
* useful when one person legitimately needs two roles' worth of access,
* without making either group's access the sole basis for the other.

Step 3: Define Dataset Profiles

A RACF dataset profile is defined against a name pattern, not one physical dataset, which is exactly why the Project 2 naming convention pays off here: `STU.PROD.**` in a generic profile covers every current and future dataset under that qualifier without a new `ADDSD` every time one is added. `UACC(NONE)` sets the *universal* access — what anyone with no explicit permission gets — to nothing, which is the secure default; every group's actual access is then granted explicitly with `PERMIT` in Step 4, rather than relying on a permissive universal default.

ADDSD 'STU.MASTER.KSDS' UACC(NONE) OWNER(SECADM) -
DATA('STUDENT MASTER FILE - VSAM KSDS')
ADDSD 'STU.DAILY.TRANS' UACC(NONE) OWNER(SECADM) -
DATA('DAILY TRANSACTION FILE')
ADDSD 'STU.PROD.JCLLIB' UACC(NONE) OWNER(SECADM) -
DATA('PRODUCTION JCL LIBRARY')
ADDSD 'STU.PROD.LOADLIB' UACC(NONE) OWNER(SECADM) -
DATA('PRODUCTION LOAD MODULE LIBRARY')
*
* UACC(NONE) on every profile: nobody gets access by default. Every
* group's actual access is granted explicitly in Step 4 with PERMIT -
* this is the "deny by default, allow by exception" model RACF is
* built around, and it is what makes an access model auditable: every
* permission that exists has a corresponding PERMIT command that
* granted it, rather than an implicit default someone has to remember.

Step 4: Grant Access with PERMIT

RACF's access levels form a strict hierarchy — READ lets you view/copy, UPDATE additionally lets you write to existing records, ALTER additionally lets you create/delete/rename the dataset itself, and CONTROL is a VSAM-specific level between UPDATE and ALTER that permits certain control-interval-level operations without full ALTER. The pattern below is deliberately asymmetric: operators get UPDATE on the master (they run the job that changes it) but only READ on the JCL that defines the job (they should not be able to silently change what the job does); developers get the opposite.

* --- STU.MASTER.KSDS: operators update it, developers/auditors only read it ---
PERMIT 'STU.MASTER.KSDS' ID(OPERGRP) ACCESS(UPDATE) OWNER(SECADM)
PERMIT 'STU.MASTER.KSDS' ID(DEVGRP) ACCESS(READ) OWNER(SECADM)
PERMIT 'STU.MASTER.KSDS' ID(AUDGRP) ACCESS(READ) OWNER(SECADM)
* --- STU.DAILY.TRANS: operators create/consume it, others just read ---
PERMIT 'STU.DAILY.TRANS' ID(OPERGRP) ACCESS(UPDATE) OWNER(SECADM)
PERMIT 'STU.DAILY.TRANS' ID(DEVGRP) ACCESS(READ) OWNER(SECADM)
PERMIT 'STU.DAILY.TRANS' ID(AUDGRP) ACCESS(READ) OWNER(SECADM)
* --- STU.PROD.JCLLIB: developers alter it (their job), operators only read ---
PERMIT 'STU.PROD.JCLLIB' ID(DEVGRP) ACCESS(ALTER) OWNER(SECADM)
PERMIT 'STU.PROD.JCLLIB' ID(OPERGRP) ACCESS(READ) OWNER(SECADM)
PERMIT 'STU.PROD.JCLLIB' ID(AUDGRP) ACCESS(READ) OWNER(SECADM)
* --- STU.PROD.LOADLIB: same shape as JCLLIB - developers own it ---
PERMIT 'STU.PROD.LOADLIB' ID(DEVGRP) ACCESS(ALTER) OWNER(SECADM)
PERMIT 'STU.PROD.LOADLIB' ID(OPERGRP) ACCESS(READ) OWNER(SECADM)
PERMIT 'STU.PROD.LOADLIB' ID(AUDGRP) ACCESS(READ) OWNER(SECADM)
*
* Notice the deliberate asymmetry: OPERGRP can UPDATE the data the
* batch job touches but can only READ the JCL that defines the job;
* DEVGRP is the reverse. Neither group can silently change what the
* other one is responsible for.
Dataset CategoryOPERGRPDEVGRPAUDGRP
STU.MASTER.KSDSUPDATEREADREAD
STU.DAILY.TRANSUPDATEREADREAD
STU.PROD.JCLLIBREADALTERREAD
STU.PROD.LOADLIBREADALTERREAD

Step 5: Verify the Access Model

`LISTDSD` displays a profile's current access list — the same information `PERMIT` writes, read back to confirm it matches the plan. This step matters in practice for a reason beyond double-checking your own work: a RACF audit (which AUDGRP exists specifically to perform) is largely a repeated exercise of running `LISTDSD` against every sensitive profile and confirming the access list still matches what was actually authorized, catching permissions that were granted temporarily and never removed.

LISTDSD DATASET('STU.MASTER.KSDS') ALL
*
* ALL requests the full profile detail, including the complete
* access list - exactly what an auditor or security admin checks
* to confirm the live access matches the documented access model.
LISTDSD Output

Click Run to see what this code prints.

Complete Commands

The full command sequence, in the order RACF requires: groups first, then users connected to them, then dataset profiles, then the permissions tying groups to profiles.

* 1. Groups
ADDGROUP OPERGRP SUPGROUP(STUADMIN) OWNER(SECADM) DATA('STUDENT-RECORDS BATCH OPERATORS')
ADDGROUP DEVGRP SUPGROUP(STUADMIN) OWNER(SECADM) DATA('STUDENT-RECORDS DEVELOPERS')
ADDGROUP AUDGRP SUPGROUP(STUADMIN) OWNER(SECADM) DATA('STUDENT-RECORDS AUDITORS')
* 2. Users
ADDUSER OPUSER1 DFLTGRP(OPERGRP) OWNER(SECADM) PASSWORD(TEMP0001)
ADDUSER DEVUSER1 DFLTGRP(DEVGRP) OWNER(SECADM) PASSWORD(TEMP0002)
ADDUSER AUDUSER1 DFLTGRP(AUDGRP) OWNER(SECADM) PASSWORD(TEMP0003)
CONNECT DEVUSER1 GROUP(AUDGRP) AUTHORITY(USE) OWNER(SECADM)
* 3. Dataset profiles
ADDSD 'STU.MASTER.KSDS' UACC(NONE) OWNER(SECADM)
ADDSD 'STU.DAILY.TRANS' UACC(NONE) OWNER(SECADM)
ADDSD 'STU.PROD.JCLLIB' UACC(NONE) OWNER(SECADM)
ADDSD 'STU.PROD.LOADLIB' UACC(NONE) OWNER(SECADM)
* 4. Permissions
PERMIT 'STU.MASTER.KSDS' ID(OPERGRP) ACCESS(UPDATE) OWNER(SECADM)
PERMIT 'STU.MASTER.KSDS' ID(DEVGRP) ACCESS(READ) OWNER(SECADM)
PERMIT 'STU.MASTER.KSDS' ID(AUDGRP) ACCESS(READ) OWNER(SECADM)
PERMIT 'STU.DAILY.TRANS' ID(OPERGRP) ACCESS(UPDATE) OWNER(SECADM)
PERMIT 'STU.DAILY.TRANS' ID(DEVGRP) ACCESS(READ) OWNER(SECADM)
PERMIT 'STU.DAILY.TRANS' ID(AUDGRP) ACCESS(READ) OWNER(SECADM)
PERMIT 'STU.PROD.JCLLIB' ID(DEVGRP) ACCESS(ALTER) OWNER(SECADM)
PERMIT 'STU.PROD.JCLLIB' ID(OPERGRP) ACCESS(READ) OWNER(SECADM)
PERMIT 'STU.PROD.JCLLIB' ID(AUDGRP) ACCESS(READ) OWNER(SECADM)
PERMIT 'STU.PROD.LOADLIB' ID(DEVGRP) ACCESS(ALTER) OWNER(SECADM)
PERMIT 'STU.PROD.LOADLIB' ID(OPERGRP) ACCESS(READ) OWNER(SECADM)
PERMIT 'STU.PROD.LOADLIB' ID(AUDGRP) ACCESS(READ) OWNER(SECADM)

Sample Run

With the model applied, the three users now experience three different mainframes, in effect. OPUSER1 (operator) can submit the nightly batch cycle from Project 3 and it succeeds; the same user attempting to edit `STU.PROD.JCLLIB(NIGHTBAT)` in ISPF Edit is refused.

Access Model in Action

Click Run to see what this code prints.

Extend This Project

  • Add a fourth group, `SECADM`-delegated `SUPPORT`, with time-limited emergency ALTER access, and design how it would be granted and automatically revoked.
  • Add `AUDIT(SUCCESS(UPDATE) FAILURES(READ))` via `ALTDSD` on `STU.MASTER.KSDS` and explain what each half of that clause actually logs.
  • Model what changes when a developer moves to the operations team — which `CONNECT`/`REMOVE` commands are needed, and in what order.
  • Add a generic profile `STU.TEST.**` with more permissive access than `STU.PROD.**`, and explain why test and production should never share one profile.
  • Write the `PERMIT ... DELETE` commands that would revoke AUDGRP's access entirely if the audit engagement ended.

Summary

You designed a RACF access model around groups rather than individual users, dataset profiles rather than one-off exceptions, and a deny-by-default `UACC(NONE)` baseline with every real permission granted explicitly and traceably through `PERMIT`. The asymmetric access — operators can update data but not the JCL that processes it, developers can alter the JCL but not touch production data directly — is the same separation-of-duties pattern real mainframe shops build their security around, and the `LISTDSD` verification step is the exact mechanism an auditor uses to confirm a system's actual access still matches what was intended.