diff --git a/README.md b/README.md index 304691c..ef010d7 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,104 @@ + # AWS Sentinel -AWS Sentinel is a command-line security scanner for AWS resources. It helps identify common security issues in your AWS account, such as: +AWS Sentinel is a powerful command-line security scanner for AWS resources. It helps identify common security issues and misconfigurations in your AWS environment. -- Public S3 buckets -- Security groups with port 22 (SSH) open to the public -- Unencrypted EBS volumes -- IAM users without Multi-Factor Authentication (MFA) +## Features -## Usage +AWS Sentinel currently checks for the following security issues: + +- **S3 Buckets**: Identifies publicly accessible buckets +- **EC2 Security Groups**: Finds security groups with port 22 (SSH) open to the public +- **EBS Volumes**: Detects unencrypted volumes +- **IAM Users**: Identifies users without Multi-Factor Authentication (MFA) + +## Installation + +You can install AWS Sentinel using pip: -You can clone this repo: +```bash +pip install aws-sentinel +``` -``` bash -git clone https://github.com/rishabkumar7/aws-sentinel +Or using uv +```bash +uv pip install aws-sentinel ``` -Once clone, you can run AWS Sentinel from the command line: +## Usage + +### Basic Usage -``` bash -python main.py --profile your-aws-profile --region your-aws-region +Run a full security scan using your default AWS profile: + +```bash +aws-sentinel scan ``` If you don't specify a profile or region, it will use the default profile and `us-east-1` region. -### Options +### Command Options + +``` +Usage: aws-sentinel scan [OPTIONS] + +Options: + --profile TEXT AWS profile to use for authentication (from + ~/.aws/credentials) + --region TEXT AWS region to scan for security issues + --checks TEXT Comma-separated list of checks to run + (s3,ec2,ebs,iam) or "all" + --output [table|json|csv] Output format for scan results + --severity [low|medium|high|all] + Filter results by minimum severity level + -v, --verbose Enable verbose output + -h, --help Show this message and exit. + +``` + +### Examples + +Run a scan with a specific AWS profile and region: + +```bash +aws-sentinel scan --profile production --region us-west-2 +``` + +Run only specific security checks: + +```bash +aws-sentinel scan --checks s3,iam +``` -- `--profile`: AWS profile to use (default: "default") -- `--region`: AWS region to check (default: "us-east-1") +Export results in JSON format: + +```bash +aws-sentinel scan --output json > security_report.json +``` + +Export results in CSV format: + +```bash +aws-sentinel scan --output csv > security_report.csv +``` + +Show only high severity issues: + +```bash +aws-sentinel scan --severity high +``` + +Get detailed documentation: + +```bash +aws-sentinel docs +``` ## Example Output -``` bash +### Table Format (Default) + +```bash █████╗ ██╗ ██╗███████╗ ███████╗███████╗███╗ ██╗████████╗██╗███╗ ██╗███████╗██╗ ██╔══██╗██║ ██║██╔════╝ ██╔════╝██╔════╝████╗ ██║╚══██╔══╝██║████╗ ██║██╔════╝██║ ███████║██║ █╗ ██║███████╗ ███████╗█████╗ ██╔██╗ ██║ ██║ ██║██╔██╗ ██║█████╗ ██║ @@ -54,39 +122,76 @@ Initializing security checks... +--------+---------------+------------------------------------------+ ``` +### JSON Format + +```json +{ + "scan_results": { + "profile": "default", + "region": "us-east-1", + "scan_time": "2025-04-15T14:32:17.654321", + "issues_count": 3, + "issues": [ + { + "service": "S3", + "resource": "public-bucket", + "issue": "Public bucket", + "severity": "HIGH" + }, + { + "service": "EC2", + "resource": "sg-12345abcde", + "issue": "Security group with port 22 open to public", + "severity": "HIGH" + }, + { + "service": "IAM", + "resource": "admin-user", + "issue": "User without MFA", + "severity": "HIGH" + } + ] + } +} + +``` + ## Requirements - Python 3.9+ - AWS credentials configured (via AWS CLI or environment variables) +- Required permissions to access AWS resources ## Development -To set up AWS Sentinel for development: +To set up the project for development: 1. Clone the repository: -``` bash -git clone https://github.com/yourusername/aws-sentinel.git cd aws-sentinel -``` + ```bash + git clone https://github.com/rishabkumar7/aws-sentinel.git + cd aws-sentinel + + ``` 2. Create a virtual environment: -``` bash -python -m venv venv -source venv/bin/activate # On Windows: venv\Scripts\activate` -``` + ```bash + python -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + ``` 3. Install development dependencies: -``` bash -pip install -r requirements.txt -``` + ```bash + pip install -e '.[dev]' + ``` -4. Run tests: +4. Run the tests: -``` bash -python unittest test_aws_sentinel.py -``` + ```bash + python -m unittest discover tests + ``` ## License @@ -94,4 +199,4 @@ MIT License ## Contributing -Contributions are welcome! Please feel free to submit a Pull Request. +Contributions are welcome! Please feel free to submit an Issue and a Pull Request. diff --git a/aws_sentinel/__init__.py b/aws_sentinel/__init__.py new file mode 100644 index 0000000..caee698 --- /dev/null +++ b/aws_sentinel/__init__.py @@ -0,0 +1,20 @@ +""" +AWS Sentinel - Security Scanner for AWS Resources + +A command-line tool to identify security vulnerabilities and +misconfigurations in your AWS account. +""" + +__version__ = '0.1.0' +__author__ = 'Rishab Kumar' +__email__ = 'rishabkumar7@gmail.com' +__license__ = 'MIT' +__description__ = 'Security scanner for AWS resources' +__url__ = 'https://github.com/rishabkumar7/aws-sentinel' + +from .core import ( + check_public_buckets, + check_public_security_groups, + check_unencrypted_ebs_volumes, + check_iam_users_without_mfa +) \ No newline at end of file diff --git a/aws_sentinel/ascii_art.py b/aws_sentinel/ascii_art.py new file mode 100644 index 0000000..e30b63a --- /dev/null +++ b/aws_sentinel/ascii_art.py @@ -0,0 +1,14 @@ +""" +ASCII art for AWS Sentinel CLI +""" + +BANNER = """ + █████╗ ██╗ ██╗███████╗ ███████╗███████╗███╗ ██╗████████╗██╗███╗ ██╗███████╗██╗ +██╔══██╗██║ ██║██╔════╝ ██╔════╝██╔════╝████╗ ██║╚══██╔══╝██║████╗ ██║██╔════╝██║ +███████║██║ █╗ ██║███████╗ ███████╗█████╗ ██╔██╗ ██║ ██║ ██║██╔██╗ ██║█████╗ ██║ +██╔══██║██║███╗██║╚════██║ ╚════██║██╔══╝ ██║╚██╗██║ ██║ ██║██║╚██╗██║██╔══╝ ██║ +██║ ██║╚███╔███╔╝███████║ ███████║███████╗██║ ╚████║ ██║ ██║██║ ╚████║███████╗███████╗ +╚═╝ ╚═╝ ╚══╝╚══╝ ╚══════╝ ╚══════╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ + + AWS Security Sentinel +""" \ No newline at end of file diff --git a/aws_sentinel/cli.py b/aws_sentinel/cli.py new file mode 100644 index 0000000..8a9f081 --- /dev/null +++ b/aws_sentinel/cli.py @@ -0,0 +1,246 @@ +""" +CLI interface for AWS Sentinel +""" +import boto3 +import click +import sys +import json +from datetime import datetime +from .utils import import_datetime_for_json +from .core import ( + check_public_buckets, + check_public_security_groups, + check_unencrypted_ebs_volumes, + check_iam_users_without_mfa +) +from .utils import create_pretty_table +from .ascii_art import BANNER + +CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) + +@click.group(context_settings=CONTEXT_SETTINGS) +@click.version_option( + version='0.1.0', + prog_name='AWS Sentinel', + message='%(prog)s v%(version)s - A security scanner for AWS resources' +) +def main(): + """ + \b + ╔═══════════════════════════════════════════════╗ + ║ AWS SENTINEL ║ + ║ Security Scanner for AWS Resources ║ + ╚═══════════════════════════════════════════════╝ + + AWS Sentinel scans your AWS account for security vulnerabilities + and misconfigurations, helping you maintain a secure cloud environment. + + Commands: + scan Run a security scan on your AWS resources + version Show the version and exit + + Examples: + aws-sentinel scan --profile production --region us-west-2 + aws-sentinel scan --checks s3,ec2 --output json + """ + pass + +@main.command('scan') +@click.option('--profile', default='default', + help='AWS profile to use for authentication (from ~/.aws/credentials)') +@click.option('--region', default='us-east-1', + help='AWS region to scan for security issues') +@click.option('--checks', default='all', + help='Comma-separated list of checks to run (s3,ec2,ebs,iam) or "all"') +@click.option('--output', type=click.Choice(['table', 'json', 'csv']), default='table', + help='Output format for scan results') +@click.option('--severity', type=click.Choice(['low', 'medium', 'high', 'all']), default='all', + help='Filter results by minimum severity level') +@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output') +def scan(profile, region, checks, output, severity, verbose): + """ + Run a comprehensive security scan on your AWS resources. + + This command analyzes your AWS account for various security issues, + including public S3 buckets, exposed security groups, unencrypted + volumes, and IAM users without MFA. + """ + if output == 'table': + print(BANNER) + click.echo(f"Scanning AWS account using profile: {profile} in region: {region}") + if verbose: + click.echo(f"Checks: {checks}") + click.echo(f"Output format: {output}") + click.echo(f"Severity filter: {severity}") + + if output == 'table': + click.echo("Initializing security checks...\n") + + try: + session = boto3.Session(profile_name=profile, region_name=region) + s3_client = session.client('s3') + ec2_client = session.client('ec2') + iam_client = session.client('iam') + except Exception as e: + click.echo(f"Error connecting to AWS: {str(e)}", err=True) + sys.exit(1) + + results = [] + checks_to_run = checks.lower().split(',') if checks.lower() != 'all' else ['s3', 'ec2', 'ebs', 'iam'] + + if verbose: + click.echo(f"Starting scan with the following checks: {', '.join(checks_to_run)}") + + # S3 Buckets Check + if 's3' in checks_to_run: + if verbose: + click.echo("Checking for public S3 buckets...") + public_buckets = check_public_buckets(s3_client) + for bucket in public_buckets: + results.append(["S3", bucket, "Public bucket", "HIGH"]) + + # Security Groups Check + if 'ec2' in checks_to_run: + if verbose: + click.echo("Checking for security groups with public access...") + public_sgs = check_public_security_groups(ec2_client) + for sg in public_sgs: + results.append(["EC2", sg, "Security group with port 22 open to public", "HIGH"]) + + # EBS Volumes Check + if 'ebs' in checks_to_run: + if verbose: + click.echo("Checking for unencrypted EBS volumes...") + unencrypted_volumes = check_unencrypted_ebs_volumes(ec2_client) + for volume in unencrypted_volumes: + results.append(["EBS", volume, "Unencrypted volume", "MEDIUM"]) + + # IAM Users Check + if 'iam' in checks_to_run: + if verbose: + click.echo("Checking for IAM users without MFA...") + users_without_mfa = check_iam_users_without_mfa(iam_client) + for user in users_without_mfa: + results.append(["IAM", user, "User without MFA", "HIGH"]) + + # Filter by severity if needed + if severity != 'all': + severity_levels = { + 'low': ['LOW', 'MEDIUM', 'HIGH'], + 'medium': ['MEDIUM', 'HIGH'], + 'high': ['HIGH'] + } + results = [r for r in results if r[3] in severity_levels[severity]] + + # Output results + if results: + if output == 'table': + table = create_pretty_table( + "AWS Security Issues Detected", + ["Service", "Resource", "Issue", "Severity"], + results + ) + print(table) + click.echo(f"\nScan complete. Found {len(results)} security issues.") + elif output == 'json': + import json + json_results = { + 'scan_results': { + 'profile': profile, + 'region': region, + 'scan_time': import_datetime_for_json(), + 'issues_count': len(results), + 'issues': [] + } + } + + for r in results: + json_results['scan_results']['issues'].append({ + 'service': r[0], + 'resource': r[1], + 'issue': r[2], + 'severity': r[3] + }) + + # Only output the JSON with no additional text + print(json.dumps(json_results, indent=2, sort_keys=False, ensure_ascii=False)) + elif output == 'csv': + import csv + from io import StringIO + output_buffer = StringIO() + writer = csv.writer(output_buffer) + writer.writerow(["Service", "Resource", "Issue", "Severity"]) + writer.writerows(results) + # Only output the CSV with no additional text + print(output_buffer.getvalue().strip()) + else: + if output == 'table': + click.echo("No security issues found. Your AWS environment looks secure!") + elif output == 'json': + empty_result = { + 'scan_results': { + 'profile': profile, + 'region': region, + 'scan_time': import_datetime_for_json(), + 'issues_count': 0, + 'issues': [] + } + } + print(json.dumps(empty_result, indent=2, sort_keys=False, ensure_ascii=False)) + elif output == 'csv': + print("Service,Resource,Issue,Severity") + +@main.command('version') +def version(): + """Display the version of AWS Sentinel.""" + click.echo("AWS Sentinel v0.1.0") + +# Add a docs command to show more detailed usage instructions +@main.command('docs') +def docs(): + """Display detailed documentation about AWS Sentinel.""" + doc_text = """ + AWS Sentinel Documentation + ========================= + + DESCRIPTION + ----------- + AWS Sentinel is a security scanner for AWS resources that helps identify common + security issues and misconfigurations in your AWS account. + + SUPPORTED CHECKS + --------------- + * S3: Public bucket access + * EC2: Security groups with SSH (port 22) open to the world + * EBS: Unencrypted volumes + * IAM: Users without Multi-Factor Authentication (MFA) + + PREREQUISITES + ------------ + 1. AWS CLI configured with appropriate credentials + 2. Required permissions to access resources in your AWS account + + EXAMPLES + -------- + # Run a full scan + aws-sentinel scan + + # Scan a specific profile and region + aws-sentinel scan --profile production --region us-west-2 + + # Run only S3 and IAM checks + aws-sentinel scan --checks s3,iam + + # Export results as JSON + aws-sentinel scan --output json > security_report.json + + # Show only high severity issues + aws-sentinel scan --severity high + + # Verbose output for debugging + aws-sentinel scan -v + """ + click.echo(doc_text) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/aws_sentinel/core.py b/aws_sentinel/core.py new file mode 100644 index 0000000..36bf8df --- /dev/null +++ b/aws_sentinel/core.py @@ -0,0 +1,83 @@ +""" +Core functionality for AWS Sentinel security checks +""" +import boto3 + +def check_public_buckets(s3_client): + """ + Check for S3 buckets with public access. + + Args: + s3_client: Boto3 S3 client + + Returns: + list: List of public bucket names + """ + public_buckets = [] + buckets = s3_client.list_buckets()['Buckets'] + for bucket in buckets: + try: + acl = s3_client.get_bucket_acl(Bucket=bucket['Name']) + for grant in acl['Grants']: + if grant['Grantee'].get('URI') == 'http://acs.amazonaws.com/groups/global/AllUsers': + public_buckets.append(bucket['Name']) + break + except Exception as e: + print(f"Error checking bucket {bucket['Name']}: {str(e)}") + return public_buckets + +def check_public_security_groups(ec2_client): + """ + Check for security groups with port 22 open to the world. + + Args: + ec2_client: Boto3 EC2 client + + Returns: + list: List of security group IDs with port 22 open + """ + public_sgs = [] + sgs = ec2_client.describe_security_groups()['SecurityGroups'] + for sg in sgs: + for rule in sg['IpPermissions']: + for ip_range in rule.get('IpRanges', []): + if ip_range.get('CidrIp') == '0.0.0.0/0': + if rule.get('FromPort') == 22 or rule.get('ToPort') == 22: + public_sgs.append(sg['GroupId']) + break + return public_sgs + +def check_unencrypted_ebs_volumes(ec2_client): + """ + Check for unencrypted EBS volumes. + + Args: + ec2_client: Boto3 EC2 client + + Returns: + list: List of unencrypted volume IDs + """ + unencrypted_volumes = [] + volumes = ec2_client.describe_volumes()['Volumes'] + for volume in volumes: + if not volume['Encrypted']: + unencrypted_volumes.append(volume['VolumeId']) + return unencrypted_volumes + +def check_iam_users_without_mfa(iam_client): + """ + Check for IAM users without MFA enabled. + + Args: + iam_client: Boto3 IAM client + + Returns: + list: List of IAM usernames without MFA + """ + users_without_mfa = [] + users = iam_client.list_users()['Users'] + for user in users: + mfa_devices = iam_client.list_mfa_devices(UserName=user['UserName'])['MFADevices'] + if not mfa_devices: + users_without_mfa.append(user['UserName']) + return users_without_mfa \ No newline at end of file diff --git a/aws_sentinel/utils.py b/aws_sentinel/utils.py new file mode 100644 index 0000000..9970e13 --- /dev/null +++ b/aws_sentinel/utils.py @@ -0,0 +1,35 @@ +""" +Utility functions for AWS Sentinel +""" +from prettytable import PrettyTable +from datetime import datetime +import json + +def create_pretty_table(title, headers, rows): + """ + Create a prettytable for displaying results. + + Args: + title: Table title + headers: Column headers + rows: Row data + + Returns: + PrettyTable: Formatted table + """ + table = PrettyTable() + table.title = title + table.field_names = headers + for row in rows: + table.add_row(row) + table.align = 'l' # Left-align text + return table + +def import_datetime_for_json(): + """ + Get current datetime in ISO format for JSON output. + + Returns: + str: Current datetime in ISO format + """ + return datetime.now().isoformat() \ No newline at end of file diff --git a/main.py b/main.py deleted file mode 100644 index a4bbcfb..0000000 --- a/main.py +++ /dev/null @@ -1,110 +0,0 @@ -import boto3 -import click -from prettytable import PrettyTable - -ASCII_ART = """ - █████╗ ██╗ ██╗███████╗ ███████╗███████╗███╗ ██╗████████╗██╗███╗ ██╗███████╗██╗ -██╔══██╗██║ ██║██╔════╝ ██╔════╝██╔════╝████╗ ██║╚══██╔══╝██║████╗ ██║██╔════╝██║ -███████║██║ █╗ ██║███████╗ ███████╗█████╗ ██╔██╗ ██║ ██║ ██║██╔██╗ ██║█████╗ ██║ -██╔══██║██║███╗██║╚════██║ ╚════██║██╔══╝ ██║╚██╗██║ ██║ ██║██║╚██╗██║██╔══╝ ██║ -██║ ██║╚███╔███╔╝███████║ ███████║███████╗██║ ╚████║ ██║ ██║██║ ╚████║███████╗███████╗ -╚═╝ ╚═╝ ╚══╝╚══╝ ╚══════╝ ╚══════╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ - - AWS Security Sentinel -""" - -def check_public_buckets(s3_client): - public_buckets = [] - buckets = s3_client.list_buckets()['Buckets'] - for bucket in buckets: - try: - acl = s3_client.get_bucket_acl(Bucket=bucket['Name']) - for grant in acl['Grants']: - if grant['Grantee'].get('URI') == 'http://acs.amazonaws.com/groups/global/AllUsers': - public_buckets.append(bucket['Name']) - break - except Exception as e: - print(f"Error checking bucket {bucket['Name']}: {str(e)}") - return public_buckets - -def check_public_security_groups(ec2_client): - public_sgs = [] - sgs = ec2_client.describe_security_groups()['SecurityGroups'] - for sg in sgs: - for rule in sg['IpPermissions']: - for ip_range in rule.get('IpRanges', []): - if ip_range.get('CidrIp') == '0.0.0.0/0': - if rule.get('FromPort') == 22 or rule.get('ToPort') == 22: - public_sgs.append(sg['GroupId']) - break - return public_sgs - -def check_unencrypted_ebs_volumes(ec2_client): - unencrypted_volumes = [] - volumes = ec2_client.describe_volumes()['Volumes'] - for volume in volumes: - if not volume['Encrypted']: - unencrypted_volumes.append(volume['VolumeId']) - return unencrypted_volumes - -def check_iam_users_without_mfa(iam_client): - users_without_mfa = [] - users = iam_client.list_users()['Users'] - for user in users: - mfa_devices = iam_client.list_mfa_devices(UserName=user['UserName'])['MFADevices'] - if not mfa_devices: - users_without_mfa.append(user['UserName']) - return users_without_mfa - -def create_pretty_table(title, headers, rows): - table = PrettyTable() - table.title = title - table.field_names = headers - for row in rows: - table.add_row(row) - table.align = 'l' # Left-align text - return table - -@click.command() -@click.option('--profile', default='default', help='AWS profile to use') -@click.option('--region', default='us-east-1', help='AWS region to check') -def main(profile, region): - print(ASCII_ART) - click.echo(f"Scanning AWS account using profile: {profile} in region: {region}") - click.echo("Initializing security checks...\n") - - session = boto3.Session(profile_name=profile, region_name=region) - s3_client = session.client('s3') - ec2_client = session.client('ec2') - iam_client = session.client('iam') - - results = [] - - public_buckets = check_public_buckets(s3_client) - for bucket in public_buckets: - results.append(["S3", bucket, "Public bucket"]) - - public_sgs = check_public_security_groups(ec2_client) - for sg in public_sgs: - results.append(["EC2", sg, "Security group with port 22 open to public"]) - - unencrypted_volumes = check_unencrypted_ebs_volumes(ec2_client) - for volume in unencrypted_volumes: - results.append(["EBS", volume, "Unencrypted volume"]) - - users_without_mfa = check_iam_users_without_mfa(iam_client) - for user in users_without_mfa: - results.append(["IAM", user, "User without MFA"]) - - if results: - table = create_pretty_table( - "AWS Security Issues Detected", - ["Service", "Resource", "Issue"], - results - ) - print(table) - else: - click.echo("No security issues found. Your AWS environment looks secure!") - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..99ded87 --- /dev/null +++ b/setup.py @@ -0,0 +1,36 @@ +""" +Setup script for AWS Sentinel +""" +from setuptools import setup, find_packages + +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +setup( + name="aws-sentinel", + version="0.1.0", + author="Rishab Kumar", + author_email="rishabkumar7@gmail.com", + description="A security scanner for AWS resources", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/rishabkumar7/aws-sentinel", + packages=find_packages(), + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires=">=3.9", + install_requires=[ + "boto3>=1.20.0", + "click>=8.0.0", + "prettytable>=2.0.0", + "colorama>=0.4.4", + ], + entry_points={ + "console_scripts": [ + "aws-sentinel=aws_sentinel.cli:main", + ], + }, +) \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..f2bf9bb --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +""" +Test suite for AWS Sentinel +""" \ No newline at end of file diff --git a/test_aws_sentinel.py b/tests/test_aws_sentinel.py similarity index 98% rename from test_aws_sentinel.py rename to tests/test_aws_sentinel.py index 4f86478..1ea17bb 100644 --- a/test_aws_sentinel.py +++ b/tests/test_aws_sentinel.py @@ -1,3 +1,6 @@ +""" +Tests for AWS Sentinel core functionality +""" import unittest from unittest.mock import patch from moto import mock_aws @@ -6,7 +9,8 @@ import sys import colorama from colorama import Fore, Style -from main import ( + +from aws_sentinel.core import ( check_public_buckets, check_public_security_groups, check_unencrypted_ebs_volumes,