{ "cells": [ { "cell_type": "markdown", "id": "3d9865d1", "metadata": {}, "source": [ "# Module 2 — Lab 6: Capstone — Investigating Suspicious Logins\n", "\n", " \n", "**Topics:** Pandas, NumPy, Matplotlib, export, analyst summary\n", "\n", "\n", "> **Instructions:** Run the cells in order. Complete every `TODO`, then run the **Self-check** cell. Do not open the Solution notebook before attempting the tasks yourself.\n" ] }, { "cell_type": "markdown", "id": "dda323ff", "metadata": {}, "source": [ "## Mission\n", "\n", "As a junior security analyst, you must build a complete workflow:\n", "\n", "1. load and clean authentication logs;\n", "2. identify IP addresses with repeated failed logins;\n", "3. identify off-hours activity;\n", "4. visualize the results;\n", "5. export the data and write a short conclusion.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "7b91b90f", "metadata": {}, "outputs": [], "source": [ "from pathlib import Path\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "DATA_PATH = Path(\"data/auth_log.csv\")\n", "OUTPUT_DIR = Path(\"output\")\n", "OUTPUT_DIR.mkdir(exist_ok=True)\n" ] }, { "cell_type": "markdown", "id": "2e73090b", "metadata": { "tags": [ "exercise" ] }, "source": [ "## 1. Write a reusable cleaning function\n" ] }, { "cell_type": "code", "execution_count": null, "id": "21e69c93", "metadata": { "tags": [ "exercise" ] }, "outputs": [], "source": [ "def clean_auth_log(df):\n", " result = df.copy()\n", " # TODO 1: drop duplicate rows\n", " # TODO 2: parse timestamp using errors=\"coerce\" and utc=True\n", " # TODO 3: drop rows missing timestamp, source_ip, or status\n", " # TODO 4: normalize source_ip, username, and status\n", " # TODO 5: retain SUCCESS and FAILED records only\n", " # TODO 6: create hour and is_off_hours (< 6 or >= 22)\n", " # TODO 7: sort by timestamp and reset the index\n", " return result\n", "\n", "\n", "raw_df = pd.read_csv(DATA_PATH)\n", "clean_df = clean_auth_log(raw_df)\n", "print(f\"Raw rows: {len(raw_df)} | Clean rows: {len(clean_df)}\")\n", "clean_df.head()\n" ] }, { "cell_type": "markdown", "id": "57d60f86", "metadata": { "tags": [ "exercise" ] }, "source": [ "## 2. Analyze failures and rank IP addresses\n" ] }, { "cell_type": "code", "execution_count": null, "id": "1ea702f5", "metadata": { "tags": [ "exercise" ] }, "outputs": [], "source": [ "# TODO 8: filter FAILED records\n", "failed_df = None\n", "\n", "# TODO 9: group by source_ip, count, sort descending, and name the Series failed_attempts\n", "failed_by_ip = None\n", "\n", "THRESHOLD = 3\n", "# TODO 10: retain IP addresses with count >= THRESHOLD\n", "suspicious_ips = None\n", "\n", "print(failed_by_ip)\n", "print(suspicious_ips)\n" ] }, { "cell_type": "markdown", "id": "eba0fd3d", "metadata": { "tags": [ "exercise" ] }, "source": [ "## 3. Calculate additional investigation metrics\n" ] }, { "cell_type": "code", "execution_count": null, "id": "fea68f16", "metadata": { "tags": [ "exercise" ] }, "outputs": [], "source": [ "# TODO 11: create status_counts in SUCCESS, FAILED order\n", "status_counts = None\n", "\n", "# TODO 12: filter off-hours failed events\n", "off_hours_failed = None\n", "\n", "# TODO 13: determine top_ip, top_ip_count, and failure_rate (%)\n", "top_ip = None\n", "top_ip_count = None\n", "failure_rate = None\n", "\n", "print(f\"Top suspicious IP: {top_ip} ({top_ip_count} failures)\")\n", "print(f\"Failure rate: {failure_rate}%\")\n", "print(\"Off-hours failed attempts:\", None if off_hours_failed is None else len(off_hours_failed))\n" ] }, { "cell_type": "markdown", "id": "eb9420ec", "metadata": { "tags": [ "exercise" ] }, "source": [ "## 4. Visualize the findings\n" ] }, { "cell_type": "code", "execution_count": null, "id": "3f438336", "metadata": { "tags": [ "exercise" ] }, "outputs": [], "source": [ "# TODO 14: create a figure with two charts:\n", "# - a SUCCESS/FAILED bar chart\n", "# - a horizontal bar chart of the top five IP addresses by failed attempts\n", "# TODO 15: add titles and labels, save to output/auth_log_analysis.png, and call plt.show()\n" ] }, { "cell_type": "markdown", "id": "fe92f571", "metadata": { "tags": [ "exercise" ] }, "source": [ "## 5. Export results and write a short report\n" ] }, { "cell_type": "code", "execution_count": null, "id": "608ca490", "metadata": { "tags": [ "exercise" ] }, "outputs": [], "source": [ "# TODO 16: export clean_df and suspicious_ips to CSV\n", "# TODO 17: create a summary string containing clean event count, FAILED count,\n", "# failure rate, top IP, off-hours failures, and recommendations.\n", "# TODO 18: save summary to output/capstone_summary.txt\n" ] }, { "cell_type": "markdown", "id": "a6f05e23", "metadata": {}, "source": [ "## Self-check\n" ] }, { "cell_type": "code", "execution_count": null, "id": "10601dd1", "metadata": {}, "outputs": [], "source": [ "assert len(raw_df) > len(clean_df)\n", "assert clean_df.duplicated().sum() == 0\n", "assert failed_by_ip.index[0] == \"198.51.100.14\"\n", "assert top_ip_count >= THRESHOLD\n", "assert len(off_hours_failed) >= 1\n", "assert (OUTPUT_DIR / \"auth_log_analysis.png\").exists()\n", "assert (OUTPUT_DIR / \"capstone_summary.txt\").exists()\n", "print(\"✓ Capstone lab completed\")\n" ] }, { "cell_type": "markdown", "id": "b56e4f9a", "metadata": {}, "source": [ "## Report questions\n", "\n", "1. Which IP address should be investigated first, and what evidence supports that decision?\n", "2. In what situations could a threshold of three failed attempts produce a false positive?\n", "3. Which additional log sources should be reviewed before deciding to block an IP address?\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.11.6" } }, "nbformat": 4, "nbformat_minor": 5 }