diff options
Diffstat (limited to 'notebooks/racf_access_analysis.ipynb')
-rw-r--r-- | notebooks/racf_access_analysis.ipynb | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/notebooks/racf_access_analysis.ipynb b/notebooks/racf_access_analysis.ipynb new file mode 100644 index 0000000..48bea8d --- /dev/null +++ b/notebooks/racf_access_analysis.ipynb @@ -0,0 +1,526 @@ +{ + "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": [ + "<div>\n", + "<style scoped>\n", + " .dataframe tbody tr th:only-of-type {\n", + " vertical-align: middle;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: right;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>Group</th>\n", + " <th>User</th>\n", + " <th>Access</th>\n", + " <th>Access Count</th>\n", + " <th>Universal Access</th>\n", + " <th>Attributes</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>PAYROLLB</td>\n", + " <td>IBMUSER</td>\n", + " <td>JOIN</td>\n", + " <td>0</td>\n", + " <td>ALTER</td>\n", + " <td>NONE</td>\n", + " </tr>\n", + " <tr>\n", + " <th>1</th>\n", + " <td>PAYROLLB</td>\n", + " <td>DAF0</td>\n", + " <td>CREATE</td>\n", + " <td>0</td>\n", + " <td>READ</td>\n", + " <td>NONE</td>\n", + " </tr>\n", + " <tr>\n", + " <th>2</th>\n", + " <td>PAYROLLB</td>\n", + " <td>IA0</td>\n", + " <td>CREATE</td>\n", + " <td>0</td>\n", + " <td>READ</td>\n", + " <td>ADSP SPECIAL OPERATIONS</td>\n", + " </tr>\n", + " <tr>\n", + " <th>3</th>\n", + " <td>PAYROLLB</td>\n", + " <td>AEH0</td>\n", + " <td>CREATE</td>\n", + " <td>0</td>\n", + " <td>READ</td>\n", + " <td>NONE</td>\n", + " </tr>\n", + " <tr>\n", + " <th>4</th>\n", + " <td>RESEARCH</td>\n", + " <td>IBMUSER</td>\n", + " <td>JOIN</td>\n", + " <td>0</td>\n", + " <td>ALTER</td>\n", + " <td>NONE</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "</div>" + ], + "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": [ + "<div>\n", + "<style scoped>\n", + " .dataframe tbody tr th:only-of-type {\n", + " vertical-align: middle;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: right;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>Group</th>\n", + " <th>User</th>\n", + " <th>Access</th>\n", + " <th>Access Count</th>\n", + " <th>Universal Access</th>\n", + " <th>Attributes</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>PAYROLLB</td>\n", + " <td>IBMUSER</td>\n", + " <td>JOIN</td>\n", + " <td>0</td>\n", + " <td>ALTER</td>\n", + " <td>NONE</td>\n", + " </tr>\n", + " <tr>\n", + " <th>2</th>\n", + " <td>PAYROLLB</td>\n", + " <td>IA0</td>\n", + " <td>CREATE</td>\n", + " <td>0</td>\n", + " <td>READ</td>\n", + " <td>ADSP SPECIAL OPERATIONS</td>\n", + " </tr>\n", + " <tr>\n", + " <th>4</th>\n", + " <td>RESEARCH</td>\n", + " <td>IBMUSER</td>\n", + " <td>JOIN</td>\n", + " <td>0</td>\n", + " <td>ALTER</td>\n", + " <td>NONE</td>\n", + " </tr>\n", + " <tr>\n", + " <th>6</th>\n", + " <td>RESEARCH</td>\n", + " <td>IA0</td>\n", + " <td>CONNECT</td>\n", + " <td>4</td>\n", + " <td>READ</td>\n", + " <td>ADSP SPECIAL OPERATIONS</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "</div>" + ], + "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": [ + "<div>\n", + "<style scoped>\n", + " .dataframe tbody tr th:only-of-type {\n", + " vertical-align: middle;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: right;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>Group</th>\n", + " <th>User</th>\n", + " <th>Access</th>\n", + " <th>Universal Access</th>\n", + " <th>Attributes</th>\n", + " <th>Notes</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>PAYROLLB</td>\n", + " <td>IBMUSER</td>\n", + " <td>JOIN</td>\n", + " <td>ALTER</td>\n", + " <td>NONE</td>\n", + " <td>Review access appropriateness with system owner</td>\n", + " </tr>\n", + " <tr>\n", + " <th>1</th>\n", + " <td>PAYROLLB</td>\n", + " <td>IA0</td>\n", + " <td>CREATE</td>\n", + " <td>READ</td>\n", + " <td>ADSP SPECIAL OPERATIONS</td>\n", + " <td>Review access appropriateness with system owner</td>\n", + " </tr>\n", + " <tr>\n", + " <th>2</th>\n", + " <td>RESEARCH</td>\n", + " <td>IBMUSER</td>\n", + " <td>JOIN</td>\n", + " <td>ALTER</td>\n", + " <td>NONE</td>\n", + " <td>Review access appropriateness with system owner</td>\n", + " </tr>\n", + " <tr>\n", + " <th>3</th>\n", + " <td>RESEARCH</td>\n", + " <td>IA0</td>\n", + " <td>CONNECT</td>\n", + " <td>READ</td>\n", + " <td>ADSP SPECIAL OPERATIONS</td>\n", + " <td>Review access appropriateness with system owner</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "</div>" + ], + "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 +} |