From ae0b864a92cefc33593f5817fe4a6afe75e395d1 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Wed, 28 May 2025 13:02:35 -0500 Subject: feat: add jupyter notebooks folder (#11) * feat: add jupyter notebooks folder * Commit from GitHub Actions (Ruff) --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- notebooks/racf_access_analysis.ipynb | 526 +++++++++++++++++++++++++++++++++++ 1 file changed, 526 insertions(+) create mode 100644 notebooks/racf_access_analysis.ipynb (limited to 'notebooks/racf_access_analysis.ipynb') 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": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
GroupUserAccessAccess CountUniversal AccessAttributes
0PAYROLLBIBMUSERJOIN0ALTERNONE
1PAYROLLBDAF0CREATE0READNONE
2PAYROLLBIA0CREATE0READADSP SPECIAL OPERATIONS
3PAYROLLBAEH0CREATE0READNONE
4RESEARCHIBMUSERJOIN0ALTERNONE
\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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
GroupUserAccessAccess CountUniversal AccessAttributes
0PAYROLLBIBMUSERJOIN0ALTERNONE
2PAYROLLBIA0CREATE0READADSP SPECIAL OPERATIONS
4RESEARCHIBMUSERJOIN0ALTERNONE
6RESEARCHIA0CONNECT4READADSP SPECIAL OPERATIONS
\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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
GroupUserAccessUniversal AccessAttributesNotes
0PAYROLLBIBMUSERJOINALTERNONEReview access appropriateness with system owner
1PAYROLLBIA0CREATEREADADSP SPECIAL OPERATIONSReview access appropriateness with system owner
2RESEARCHIBMUSERJOINALTERNONEReview access appropriateness with system owner
3RESEARCHIA0CONNECTREADADSP SPECIAL OPERATIONSReview access appropriateness with system owner
\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 +} -- cgit v1.2.3-70-g09d2