{
"cells": [
{
"cell_type": "markdown",
"id": "4c3a8609",
"metadata": {},
"source": [
"\n",
"# 🛡️ RACF Access Report Analysis\n",
"\n",
"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",
"\n",
"We'll be working with the file `sample_racf_data.txt`.\n",
" "
]
},
{
"cell_type": "markdown",
"id": "a0b3e2b2",
"metadata": {},
"source": [
"\n",
"## 📦 Install Dependencies\n",
"\n",
"If you haven't already installed `pandas`, run:\n",
"\n",
"```bash\n",
"pip install pandas\n",
"```\n",
" "
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "ca527b49",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import re"
]
},
{
"cell_type": "markdown",
"id": "d1694149",
"metadata": {},
"source": [
"## 📂 Load RACF Report"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "4891d98b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"LISTGRP *\n",
"INFORMATION FOR GROUP PAYROLLB\n",
"SUPERIOR GROUP=RESEARCH OWNER=IBMUSER CREATED=06.123\n",
"NO INSTALLATION DATA\n",
"NO MODEL DATA SET\n",
"TERMUACC\n",
"NO SUBGROUPS\n",
"USER(S)= ACCESS= ACCESS COUNT= UNIVERSAL ACCESS=\n",
"IBMUSER JOIN 000000 ALTER\n",
"CONNECT ATTRIBUTES=NONE\n",
"REVOKE DATE=NONE RESUME DATE=NONE\n",
"DAF0 CREATE 000000 READ\n",
"CONNECT ATTRIBUTES=NONE\n",
"REVOKE DATE=NONE RESUME DATE=NONE\n",
"IA0 CREATE 000000 READ\n",
"CONNECT ATTRIBUTES=ADSP SPECIAL OPERATIONS\n",
"REVOKE DATE=NONE RESUME DATE=NONE\n",
"AEH0 CREATE 000000 READ\n",
"CONNECT ATTRIBUTES=NONE\n",
"REVOKE DATE=NONE RESUME DATE=NONE\n"
]
}
],
"source": [
"with open(\"sample_racf_data.txt\", \"r\") as file:\n",
" lines = file.readlines()\n",
"\n",
"# Preview first 20 lines\n",
"for line in lines[:20]:\n",
" print(line.strip())"
]
},
{
"cell_type": "markdown",
"id": "9d5ee64a",
"metadata": {},
"source": [
"## 📝 Parse User Access Records"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "18f06bc9",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" Group | \n",
" User | \n",
" Access | \n",
" Access Count | \n",
" Universal Access | \n",
" Attributes | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" PAYROLLB | \n",
" IBMUSER | \n",
" JOIN | \n",
" 0 | \n",
" ALTER | \n",
" NONE | \n",
"
\n",
" \n",
" 1 | \n",
" PAYROLLB | \n",
" DAF0 | \n",
" CREATE | \n",
" 0 | \n",
" READ | \n",
" NONE | \n",
"
\n",
" \n",
" 2 | \n",
" PAYROLLB | \n",
" IA0 | \n",
" CREATE | \n",
" 0 | \n",
" READ | \n",
" ADSP SPECIAL OPERATIONS | \n",
"
\n",
" \n",
" 3 | \n",
" PAYROLLB | \n",
" AEH0 | \n",
" CREATE | \n",
" 0 | \n",
" READ | \n",
" NONE | \n",
"
\n",
" \n",
" 4 | \n",
" RESEARCH | \n",
" IBMUSER | \n",
" JOIN | \n",
" 0 | \n",
" ALTER | \n",
" NONE | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" Group User Access Access Count Universal Access \\\n",
"0 PAYROLLB IBMUSER JOIN 0 ALTER \n",
"1 PAYROLLB DAF0 CREATE 0 READ \n",
"2 PAYROLLB IA0 CREATE 0 READ \n",
"3 PAYROLLB AEH0 CREATE 0 READ \n",
"4 RESEARCH IBMUSER JOIN 0 ALTER \n",
"\n",
" Attributes \n",
"0 NONE \n",
"1 NONE \n",
"2 ADSP SPECIAL OPERATIONS \n",
"3 NONE \n",
"4 NONE "
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Initialize lists to hold parsed records\n",
"records = []\n",
"current_group = \"\"\n",
"\n",
"for i, line in enumerate(lines):\n",
" if \"INFORMATION FOR GROUP\" in line:\n",
" current_group = line.strip().split()[-1]\n",
"\n",
" # Identify user lines: starts with a non-empty, non-space string followed by access keywords\n",
" match = re.match(r\"^\\s*(\\S+)\\s+(JOIN|CREATE|CONNECT|USE)\\s+(\\d{6})\\s+(\\S+)\", line)\n",
" if match:\n",
" user, access, access_count, universal_access = match.groups()\n",
"\n",
" # Look ahead for CONNECT ATTRIBUTES line\n",
" attr_line = lines[i + 1].strip() if (i + 1) < len(lines) else \"\"\n",
" attr_match = re.search(r\"CONNECT ATTRIBUTES=(.*)\", attr_line)\n",
" attributes = attr_match.group(1) if attr_match else \"NONE\"\n",
"\n",
" records.append(\n",
" {\n",
" \"Group\": current_group,\n",
" \"User\": user,\n",
" \"Access\": access,\n",
" \"Access Count\": int(access_count),\n",
" \"Universal Access\": universal_access,\n",
" \"Attributes\": attributes,\n",
" }\n",
" )\n",
"\n",
"# Convert to DataFrame\n",
"df = pd.DataFrame(records)\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"id": "ab5546a6",
"metadata": {},
"source": [
"## 📊 Analyze Access Data"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "d1b8269a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Access\n",
"CREATE 5\n",
"JOIN 4\n",
"USE 3\n",
"CONNECT 1\n",
"Name: count, dtype: int64\n"
]
},
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" Group | \n",
" User | \n",
" Access | \n",
" Access Count | \n",
" Universal Access | \n",
" Attributes | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" PAYROLLB | \n",
" IBMUSER | \n",
" JOIN | \n",
" 0 | \n",
" ALTER | \n",
" NONE | \n",
"
\n",
" \n",
" 2 | \n",
" PAYROLLB | \n",
" IA0 | \n",
" CREATE | \n",
" 0 | \n",
" READ | \n",
" ADSP SPECIAL OPERATIONS | \n",
"
\n",
" \n",
" 4 | \n",
" RESEARCH | \n",
" IBMUSER | \n",
" JOIN | \n",
" 0 | \n",
" ALTER | \n",
" NONE | \n",
"
\n",
" \n",
" 6 | \n",
" RESEARCH | \n",
" IA0 | \n",
" CONNECT | \n",
" 4 | \n",
" READ | \n",
" ADSP SPECIAL OPERATIONS | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" Group User Access Access Count Universal Access \\\n",
"0 PAYROLLB IBMUSER JOIN 0 ALTER \n",
"2 PAYROLLB IA0 CREATE 0 READ \n",
"4 RESEARCH IBMUSER JOIN 0 ALTER \n",
"6 RESEARCH IA0 CONNECT 4 READ \n",
"\n",
" Attributes \n",
"0 NONE \n",
"2 ADSP SPECIAL OPERATIONS \n",
"4 NONE \n",
"6 ADSP SPECIAL OPERATIONS "
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Count users by Access type\n",
"access_summary = df[\"Access\"].value_counts()\n",
"print(access_summary)\n",
"\n",
"# Identify users with ALTER access or SPECIAL OPERATIONS attribute\n",
"anomalies = df[\n",
" (df[\"Universal Access\"] == \"ALTER\")\n",
" | (df[\"Attributes\"].str.contains(\"SPECIAL OPERATIONS\"))\n",
"]\n",
"\n",
"anomalies"
]
},
{
"cell_type": "markdown",
"id": "38201382",
"metadata": {},
"source": [
"## 📑 Prepare Follow-Up Report"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "a885710c",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" Group | \n",
" User | \n",
" Access | \n",
" Universal Access | \n",
" Attributes | \n",
" Notes | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" PAYROLLB | \n",
" IBMUSER | \n",
" JOIN | \n",
" ALTER | \n",
" NONE | \n",
" Review access appropriateness with system owner | \n",
"
\n",
" \n",
" 1 | \n",
" PAYROLLB | \n",
" IA0 | \n",
" CREATE | \n",
" READ | \n",
" ADSP SPECIAL OPERATIONS | \n",
" Review access appropriateness with system owner | \n",
"
\n",
" \n",
" 2 | \n",
" RESEARCH | \n",
" IBMUSER | \n",
" JOIN | \n",
" ALTER | \n",
" NONE | \n",
" Review access appropriateness with system owner | \n",
"
\n",
" \n",
" 3 | \n",
" RESEARCH | \n",
" IA0 | \n",
" CONNECT | \n",
" READ | \n",
" ADSP SPECIAL OPERATIONS | \n",
" Review access appropriateness with system owner | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" Group User Access Universal Access Attributes \\\n",
"0 PAYROLLB IBMUSER JOIN ALTER NONE \n",
"1 PAYROLLB IA0 CREATE READ ADSP SPECIAL OPERATIONS \n",
"2 RESEARCH IBMUSER JOIN ALTER NONE \n",
"3 RESEARCH IA0 CONNECT READ ADSP SPECIAL OPERATIONS \n",
"\n",
" Notes \n",
"0 Review access appropriateness with system owner \n",
"1 Review access appropriateness with system owner \n",
"2 Review access appropriateness with system owner \n",
"3 Review access appropriateness with system owner "
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Create a concise follow-up report\n",
"follow_up = anomalies[\n",
" [\"Group\", \"User\", \"Access\", \"Universal Access\", \"Attributes\"]\n",
"].copy()\n",
"follow_up[\"Notes\"] = \"Review access appropriateness with system owner\"\n",
"\n",
"follow_up.reset_index(drop=True, inplace=True)\n",
"follow_up"
]
},
{
"cell_type": "markdown",
"id": "0158f787",
"metadata": {},
"source": [
"\n",
"## 🎉 Summary\n",
"\n",
"In this notebook, we:\n",
"- Parsed a RACF-like access report from a fixed-width text file\n",
"- Extracted key fields into a structured DataFrame\n",
"- Analyzed access configurations for high-risk permissions\n",
"- Summarized anomalies requiring follow-up with system owners\n",
"\n",
"🖥️ Use this as a starting point for mainframe audit automation projects!\n",
" "
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}