audit-labs/tutorials

Learn how to perform data analysis, scripting, automation, and more!

clone: git clone https://gitbay.org/audit-labs/tutorials.git

main: notebooks/racf_access_analysis/racf_access_analysis.ipynb · raw

  1{
  2 "cells": [
  3  {
  4   "cell_type": "markdown",
  5   "id": "4c3a8609",
  6   "metadata": {},
  7   "source": [
  8    "\n",
  9    "# RACF Access Report Analysis\n",
 10    "\n",
 11    "This notebook demonstrates how to parse a RACF-like mainframe access report stored in a fixed-width text format, extract user access details, identify unusual access configurations, and summarize the results for follow-up.\n",
 12    "\n",
 13    "We'll be working with the file `sample_racf_data.txt`.\n",
 14    "    "
 15   ]
 16  },
 17  {
 18   "cell_type": "markdown",
 19   "id": "a0b3e2b2",
 20   "metadata": {},
 21   "source": [
 22    "\n",
 23    "## Install Dependencies\n",
 24    "\n",
 25    "If you haven't already installed `pandas`, run:\n",
 26    "\n",
 27    "```bash\n",
 28    "pip install pandas\n",
 29    "```\n",
 30    "    "
 31   ]
 32  },
 33  {
 34   "cell_type": "code",
 35   "execution_count": 1,
 36   "id": "ca527b49",
 37   "metadata": {},
 38   "outputs": [],
 39   "source": [
 40    "import pandas as pd\n",
 41    "import re"
 42   ]
 43  },
 44  {
 45   "cell_type": "markdown",
 46   "id": "d1694149",
 47   "metadata": {},
 48   "source": [
 49    "## Load RACF Report"
 50   ]
 51  },
 52  {
 53   "cell_type": "code",
 54   "execution_count": 2,
 55   "id": "4891d98b",
 56   "metadata": {},
 57   "outputs": [
 58    {
 59     "name": "stdout",
 60     "output_type": "stream",
 61     "text": [
 62      "LISTGRP *\n",
 63      "INFORMATION FOR GROUP PAYROLLB\n",
 64      "SUPERIOR GROUP=RESEARCH     OWNER=IBMUSER   CREATED=06.123\n",
 65      "NO INSTALLATION DATA\n",
 66      "NO MODEL DATA SET\n",
 67      "TERMUACC\n",
 68      "NO SUBGROUPS\n",
 69      "USER(S)=      ACCESS=      ACCESS COUNT=      UNIVERSAL ACCESS=\n",
 70      "IBMUSER       JOIN          000000               ALTER\n",
 71      "CONNECT ATTRIBUTES=NONE\n",
 72      "REVOKE DATE=NONE                  RESUME DATE=NONE\n",
 73      "DAF0          CREATE        000000               READ\n",
 74      "CONNECT ATTRIBUTES=NONE\n",
 75      "REVOKE DATE=NONE                  RESUME DATE=NONE\n",
 76      "IA0           CREATE        000000               READ\n",
 77      "CONNECT ATTRIBUTES=ADSP SPECIAL OPERATIONS\n",
 78      "REVOKE DATE=NONE                  RESUME DATE=NONE\n",
 79      "AEH0          CREATE        000000               READ\n",
 80      "CONNECT ATTRIBUTES=NONE\n",
 81      "REVOKE DATE=NONE                  RESUME DATE=NONE\n"
 82     ]
 83    }
 84   ],
 85   "source": [
 86    "with open(\"sample_racf_data.txt\", \"r\") as file:\n",
 87    "    lines = file.readlines()\n",
 88    "\n",
 89    "# Preview first 20 lines\n",
 90    "for line in lines[:20]:\n",
 91    "    print(line.strip())"
 92   ]
 93  },
 94  {
 95   "cell_type": "markdown",
 96   "id": "9d5ee64a",
 97   "metadata": {},
 98   "source": [
 99    "## Parse User Access Records"
100   ]
101  },
102  {
103   "cell_type": "code",
104   "execution_count": 3,
105   "id": "18f06bc9",
106   "metadata": {},
107   "outputs": [
108    {
109     "data": {
110      "text/html": [
111       "<div>\n",
112       "<style scoped>\n",
113       "    .dataframe tbody tr th:only-of-type {\n",
114       "        vertical-align: middle;\n",
115       "    }\n",
116       "\n",
117       "    .dataframe tbody tr th {\n",
118       "        vertical-align: top;\n",
119       "    }\n",
120       "\n",
121       "    .dataframe thead th {\n",
122       "        text-align: right;\n",
123       "    }\n",
124       "</style>\n",
125       "<table border=\"1\" class=\"dataframe\">\n",
126       "  <thead>\n",
127       "    <tr style=\"text-align: right;\">\n",
128       "      <th></th>\n",
129       "      <th>Group</th>\n",
130       "      <th>User</th>\n",
131       "      <th>Access</th>\n",
132       "      <th>Access Count</th>\n",
133       "      <th>Universal Access</th>\n",
134       "      <th>Attributes</th>\n",
135       "    </tr>\n",
136       "  </thead>\n",
137       "  <tbody>\n",
138       "    <tr>\n",
139       "      <th>0</th>\n",
140       "      <td>PAYROLLB</td>\n",
141       "      <td>IBMUSER</td>\n",
142       "      <td>JOIN</td>\n",
143       "      <td>0</td>\n",
144       "      <td>ALTER</td>\n",
145       "      <td>NONE</td>\n",
146       "    </tr>\n",
147       "    <tr>\n",
148       "      <th>1</th>\n",
149       "      <td>PAYROLLB</td>\n",
150       "      <td>DAF0</td>\n",
151       "      <td>CREATE</td>\n",
152       "      <td>0</td>\n",
153       "      <td>READ</td>\n",
154       "      <td>NONE</td>\n",
155       "    </tr>\n",
156       "    <tr>\n",
157       "      <th>2</th>\n",
158       "      <td>PAYROLLB</td>\n",
159       "      <td>IA0</td>\n",
160       "      <td>CREATE</td>\n",
161       "      <td>0</td>\n",
162       "      <td>READ</td>\n",
163       "      <td>ADSP SPECIAL OPERATIONS</td>\n",
164       "    </tr>\n",
165       "    <tr>\n",
166       "      <th>3</th>\n",
167       "      <td>PAYROLLB</td>\n",
168       "      <td>AEH0</td>\n",
169       "      <td>CREATE</td>\n",
170       "      <td>0</td>\n",
171       "      <td>READ</td>\n",
172       "      <td>NONE</td>\n",
173       "    </tr>\n",
174       "    <tr>\n",
175       "      <th>4</th>\n",
176       "      <td>RESEARCH</td>\n",
177       "      <td>IBMUSER</td>\n",
178       "      <td>JOIN</td>\n",
179       "      <td>0</td>\n",
180       "      <td>ALTER</td>\n",
181       "      <td>NONE</td>\n",
182       "    </tr>\n",
183       "  </tbody>\n",
184       "</table>\n",
185       "</div>"
186      ],
187      "text/plain": [
188       "      Group     User  Access  Access Count Universal Access  \\\n",
189       "0  PAYROLLB  IBMUSER    JOIN             0            ALTER   \n",
190       "1  PAYROLLB     DAF0  CREATE             0             READ   \n",
191       "2  PAYROLLB      IA0  CREATE             0             READ   \n",
192       "3  PAYROLLB     AEH0  CREATE             0             READ   \n",
193       "4  RESEARCH  IBMUSER    JOIN             0            ALTER   \n",
194       "\n",
195       "                Attributes  \n",
196       "0                     NONE  \n",
197       "1                     NONE  \n",
198       "2  ADSP SPECIAL OPERATIONS  \n",
199       "3                     NONE  \n",
200       "4                     NONE  "
201      ]
202     },
203     "execution_count": 3,
204     "metadata": {},
205     "output_type": "execute_result"
206    }
207   ],
208   "source": [
209    "# Initialize lists to hold parsed records\n",
210    "records = []\n",
211    "current_group = \"\"\n",
212    "\n",
213    "for i, line in enumerate(lines):\n",
214    "    if \"INFORMATION FOR GROUP\" in line:\n",
215    "        current_group = line.strip().split()[-1]\n",
216    "\n",
217    "    # Identify user lines: starts with a non-empty, non-space string followed by access keywords\n",
218    "    match = re.match(r\"^\\s*(\\S+)\\s+(JOIN|CREATE|CONNECT|USE)\\s+(\\d{6})\\s+(\\S+)\", line)\n",
219    "    if match:\n",
220    "        user, access, access_count, universal_access = match.groups()\n",
221    "\n",
222    "        # Look ahead for CONNECT ATTRIBUTES line\n",
223    "        attr_line = lines[i + 1].strip() if (i + 1) < len(lines) else \"\"\n",
224    "        attr_match = re.search(r\"CONNECT ATTRIBUTES=(.*)\", attr_line)\n",
225    "        attributes = attr_match.group(1) if attr_match else \"NONE\"\n",
226    "\n",
227    "        records.append(\n",
228    "            {\n",
229    "                \"Group\": current_group,\n",
230    "                \"User\": user,\n",
231    "                \"Access\": access,\n",
232    "                \"Access Count\": int(access_count),\n",
233    "                \"Universal Access\": universal_access,\n",
234    "                \"Attributes\": attributes,\n",
235    "            }\n",
236    "        )\n",
237    "\n",
238    "# Convert to DataFrame\n",
239    "df = pd.DataFrame(records)\n",
240    "df.head()"
241   ]
242  },
243  {
244   "cell_type": "markdown",
245   "id": "ab5546a6",
246   "metadata": {},
247   "source": [
248    "## Analyze Access Data"
249   ]
250  },
251  {
252   "cell_type": "code",
253   "execution_count": 4,
254   "id": "d1b8269a",
255   "metadata": {},
256   "outputs": [
257    {
258     "name": "stdout",
259     "output_type": "stream",
260     "text": [
261      "Access\n",
262      "CREATE     5\n",
263      "JOIN       4\n",
264      "USE        3\n",
265      "CONNECT    1\n",
266      "Name: count, dtype: int64\n"
267     ]
268    },
269    {
270     "data": {
271      "text/html": [
272       "<div>\n",
273       "<style scoped>\n",
274       "    .dataframe tbody tr th:only-of-type {\n",
275       "        vertical-align: middle;\n",
276       "    }\n",
277       "\n",
278       "    .dataframe tbody tr th {\n",
279       "        vertical-align: top;\n",
280       "    }\n",
281       "\n",
282       "    .dataframe thead th {\n",
283       "        text-align: right;\n",
284       "    }\n",
285       "</style>\n",
286       "<table border=\"1\" class=\"dataframe\">\n",
287       "  <thead>\n",
288       "    <tr style=\"text-align: right;\">\n",
289       "      <th></th>\n",
290       "      <th>Group</th>\n",
291       "      <th>User</th>\n",
292       "      <th>Access</th>\n",
293       "      <th>Access Count</th>\n",
294       "      <th>Universal Access</th>\n",
295       "      <th>Attributes</th>\n",
296       "    </tr>\n",
297       "  </thead>\n",
298       "  <tbody>\n",
299       "    <tr>\n",
300       "      <th>0</th>\n",
301       "      <td>PAYROLLB</td>\n",
302       "      <td>IBMUSER</td>\n",
303       "      <td>JOIN</td>\n",
304       "      <td>0</td>\n",
305       "      <td>ALTER</td>\n",
306       "      <td>NONE</td>\n",
307       "    </tr>\n",
308       "    <tr>\n",
309       "      <th>2</th>\n",
310       "      <td>PAYROLLB</td>\n",
311       "      <td>IA0</td>\n",
312       "      <td>CREATE</td>\n",
313       "      <td>0</td>\n",
314       "      <td>READ</td>\n",
315       "      <td>ADSP SPECIAL OPERATIONS</td>\n",
316       "    </tr>\n",
317       "    <tr>\n",
318       "      <th>4</th>\n",
319       "      <td>RESEARCH</td>\n",
320       "      <td>IBMUSER</td>\n",
321       "      <td>JOIN</td>\n",
322       "      <td>0</td>\n",
323       "      <td>ALTER</td>\n",
324       "      <td>NONE</td>\n",
325       "    </tr>\n",
326       "    <tr>\n",
327       "      <th>6</th>\n",
328       "      <td>RESEARCH</td>\n",
329       "      <td>IA0</td>\n",
330       "      <td>CONNECT</td>\n",
331       "      <td>4</td>\n",
332       "      <td>READ</td>\n",
333       "      <td>ADSP SPECIAL OPERATIONS</td>\n",
334       "    </tr>\n",
335       "  </tbody>\n",
336       "</table>\n",
337       "</div>"
338      ],
339      "text/plain": [
340       "      Group     User   Access  Access Count Universal Access  \\\n",
341       "0  PAYROLLB  IBMUSER     JOIN             0            ALTER   \n",
342       "2  PAYROLLB      IA0   CREATE             0             READ   \n",
343       "4  RESEARCH  IBMUSER     JOIN             0            ALTER   \n",
344       "6  RESEARCH      IA0  CONNECT             4             READ   \n",
345       "\n",
346       "                Attributes  \n",
347       "0                     NONE  \n",
348       "2  ADSP SPECIAL OPERATIONS  \n",
349       "4                     NONE  \n",
350       "6  ADSP SPECIAL OPERATIONS  "
351      ]
352     },
353     "execution_count": 4,
354     "metadata": {},
355     "output_type": "execute_result"
356    }
357   ],
358   "source": [
359    "# Count users by Access type\n",
360    "access_summary = df[\"Access\"].value_counts()\n",
361    "print(access_summary)\n",
362    "\n",
363    "# Identify users with ALTER access or SPECIAL OPERATIONS attribute\n",
364    "anomalies = df[\n",
365    "    (df[\"Universal Access\"] == \"ALTER\")\n",
366    "    | (df[\"Attributes\"].str.contains(\"SPECIAL OPERATIONS\"))\n",
367    "]\n",
368    "\n",
369    "anomalies"
370   ]
371  },
372  {
373   "cell_type": "markdown",
374   "id": "38201382",
375   "metadata": {},
376   "source": [
377    "## Prepare Follow-Up Report"
378   ]
379  },
380  {
381   "cell_type": "code",
382   "execution_count": 5,
383   "id": "a885710c",
384   "metadata": {},
385   "outputs": [
386    {
387     "data": {
388      "text/html": [
389       "<div>\n",
390       "<style scoped>\n",
391       "    .dataframe tbody tr th:only-of-type {\n",
392       "        vertical-align: middle;\n",
393       "    }\n",
394       "\n",
395       "    .dataframe tbody tr th {\n",
396       "        vertical-align: top;\n",
397       "    }\n",
398       "\n",
399       "    .dataframe thead th {\n",
400       "        text-align: right;\n",
401       "    }\n",
402       "</style>\n",
403       "<table border=\"1\" class=\"dataframe\">\n",
404       "  <thead>\n",
405       "    <tr style=\"text-align: right;\">\n",
406       "      <th></th>\n",
407       "      <th>Group</th>\n",
408       "      <th>User</th>\n",
409       "      <th>Access</th>\n",
410       "      <th>Universal Access</th>\n",
411       "      <th>Attributes</th>\n",
412       "      <th>Notes</th>\n",
413       "    </tr>\n",
414       "  </thead>\n",
415       "  <tbody>\n",
416       "    <tr>\n",
417       "      <th>0</th>\n",
418       "      <td>PAYROLLB</td>\n",
419       "      <td>IBMUSER</td>\n",
420       "      <td>JOIN</td>\n",
421       "      <td>ALTER</td>\n",
422       "      <td>NONE</td>\n",
423       "      <td>Review access appropriateness with system owner</td>\n",
424       "    </tr>\n",
425       "    <tr>\n",
426       "      <th>1</th>\n",
427       "      <td>PAYROLLB</td>\n",
428       "      <td>IA0</td>\n",
429       "      <td>CREATE</td>\n",
430       "      <td>READ</td>\n",
431       "      <td>ADSP SPECIAL OPERATIONS</td>\n",
432       "      <td>Review access appropriateness with system owner</td>\n",
433       "    </tr>\n",
434       "    <tr>\n",
435       "      <th>2</th>\n",
436       "      <td>RESEARCH</td>\n",
437       "      <td>IBMUSER</td>\n",
438       "      <td>JOIN</td>\n",
439       "      <td>ALTER</td>\n",
440       "      <td>NONE</td>\n",
441       "      <td>Review access appropriateness with system owner</td>\n",
442       "    </tr>\n",
443       "    <tr>\n",
444       "      <th>3</th>\n",
445       "      <td>RESEARCH</td>\n",
446       "      <td>IA0</td>\n",
447       "      <td>CONNECT</td>\n",
448       "      <td>READ</td>\n",
449       "      <td>ADSP SPECIAL OPERATIONS</td>\n",
450       "      <td>Review access appropriateness with system owner</td>\n",
451       "    </tr>\n",
452       "  </tbody>\n",
453       "</table>\n",
454       "</div>"
455      ],
456      "text/plain": [
457       "      Group     User   Access Universal Access               Attributes  \\\n",
458       "0  PAYROLLB  IBMUSER     JOIN            ALTER                     NONE   \n",
459       "1  PAYROLLB      IA0   CREATE             READ  ADSP SPECIAL OPERATIONS   \n",
460       "2  RESEARCH  IBMUSER     JOIN            ALTER                     NONE   \n",
461       "3  RESEARCH      IA0  CONNECT             READ  ADSP SPECIAL OPERATIONS   \n",
462       "\n",
463       "                                             Notes  \n",
464       "0  Review access appropriateness with system owner  \n",
465       "1  Review access appropriateness with system owner  \n",
466       "2  Review access appropriateness with system owner  \n",
467       "3  Review access appropriateness with system owner  "
468      ]
469     },
470     "execution_count": 5,
471     "metadata": {},
472     "output_type": "execute_result"
473    }
474   ],
475   "source": [
476    "# Create a concise follow-up report\n",
477    "follow_up = anomalies[\n",
478    "    [\"Group\", \"User\", \"Access\", \"Universal Access\", \"Attributes\"]\n",
479    "].copy()\n",
480    "follow_up[\"Notes\"] = \"Review access appropriateness with system owner\"\n",
481    "\n",
482    "follow_up.reset_index(drop=True, inplace=True)\n",
483    "follow_up"
484   ]
485  },
486  {
487   "cell_type": "markdown",
488   "id": "0158f787",
489   "metadata": {},
490   "source": [
491    "\n",
492    "## Summary\n",
493    "\n",
494    "In this notebook, we:\n",
495    "- Parsed a RACF-like access report from a fixed-width text file\n",
496    "- Extracted key fields into a structured DataFrame\n",
497    "- Analyzed access configurations for high-risk permissions\n",
498    "- Summarized anomalies requiring follow-up with system owners\n",
499    "\n",
500    "Use this as a starting point for mainframe audit automation projects!\n",
501    "    "
502   ]
503  }
504 ],
505 "metadata": {
506  "kernelspec": {
507   "display_name": "Python 3 (ipykernel)",
508   "language": "python",
509   "name": "python3"
510  },
511  "language_info": {
512   "codemirror_mode": {
513    "name": "ipython",
514    "version": 3
515   },
516   "file_extension": ".py",
517   "mimetype": "text/x-python",
518   "name": "python",
519   "nbconvert_exporter": "python",
520   "pygments_lexer": "ipython3",
521   "version": "3.14.2"
522  }
523 },
524 "nbformat": 4,
525 "nbformat_minor": 5
526}