Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docker integration using Github CI Actions #48

Merged
merged 13 commits into from
Apr 16, 2023
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
VT_APIKey
Talos_APIKey
HIBP_APIKey
Config/config.ini
settings.json
Logs/
31 changes: 31 additions & 0 deletions .github/workflows/auto-build-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Build and Test
on:
push:
branches: [ "main" ]
pull_request:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9.13", "3.10"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with black
run: |
black .
- name: Test with pytest
run: |
pytest
37 changes: 37 additions & 0 deletions .github/workflows/docker-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Build and Push Docker Image

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v3

- name: Log in to Docker Hub
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: initd1/hackalert

- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
97 changes: 97 additions & 0 deletions CONTRIBUTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Contributing to HackAlert

Thank you for your interest in contributing to HackAlert! We appreciate your help in making our product better and more secure.
## About HackAlert

HackAlert is a tool that scans the internet for your online persona, including financial, social, and personal accounts, to detect data breaches or hacks. With real-time notifications via text or email, you can take action before it's too late. **Stay safe and secure with HackAlert.**
## Technical Development Guidelines
### Branch Naming Conventions

When creating branches, please follow these naming conventions:

- `feature/<feature-name>` for new features
- `enhancement/<enhancement-name>` for improvements to existing features
- `bug/<bug-name> `for bug fixes

### Git Branch Management

1. Create a new branch for each issue and work on the feature/bug in that branch.
2. Merge your completed feature/bug branch into the main branch via a pull request.
3. The main branch is a protected branch and requires approval of pull requests from feature/bug branches before being merged.
4. When a good number of features are implemented and merged into main, a separate release branch is created from main.
5. The release branch follows the Semantic Versioning naming convention https://semver.org/. A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative integers, and MUST NOT contain leading zeroes. X is the major version, Y is the minor version, and Z is the patch version. Each element MUST increase numerically. For instance: 1.9.0 -> 1.10.0 -> 1.11.0.6.
6. A tag by the same name will be created post-release.
7. The release branch will be locked and serve as the Last Known Good for any future work.

### Environment Set-up

To set up your development environment, please follow these steps:

1. Clone the GitHub repository

```bash

git clone https://github.com/initd1/HackAlert.git
```
2. Install Python and pip in your environment

3. Install **virtualenv**

```bash
pip install virtualenv
```
4. Create a virtual environment

```bash

virtualenv <environment-name>
```
5. Activate the virtual environment

__Mac or Linux__

```bash

source <environment-name>/bin/activate
```
__Windows__

```bash

<environment-name>\Scripts\activate.bat
```
6. Install the required packages in the virtual environment

```python

pip install -r requirements.txt
```
## Testing

It is important that before you do anything, you have the `requirements.txt` file installed as it is required for the dependencies that are used in this project.

To install all dependencies listed in this file, run the following:

```bash

python -m pip install -r requirements.txt
```
To execute tests, please ensure that you have the **pytest** module installed. The **unittest** module is used, however, this is native to Python and does not have to be installed.

To run tests, please ensure that you are in the base directory, where `tests` and `main.py` are visible.

To run tests with print output:

```bash

pytest -s
```
To run tests without print output:

```bash

pytest
```
## Docker Containerization

We suggest packaging the code in a lightweight Docker container like python-slim and running the code from it. That way, the environment is set and can be run from any environment that supports Docker.
6 changes: 4 additions & 2 deletions Common/breach_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
import ipaddress
from Config.config import configure_logging
import logging

configure_logging()

# import argparse
# import asyncio


class BreachChecker:
def is_valid_email(self, email):
# Verify email format
# TODO: Add a list of email domains accepted..may be
# Verify email format
# TODO: Add a list of email domains accepted..may be
if re.match(r"[^@]+@[^@]+\.[^@]+", email):
logging.debug("Input is a valid email address.")
return True
Expand Down
64 changes: 33 additions & 31 deletions Common/utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Utils file to:
# fetch the relevant API keys of different services
# validate keys
# process non critical and fatal errors
# TODO: Logging to file
# fetch the relevant API keys of different services
# validate keys
# process non critical and fatal errors
# TODO: Logging to file
import requests
import json
import re
import ipaddress
from termcolor import colored
from termcolor import colored
import sys
import configparser
from . import utils
Expand All @@ -17,19 +17,22 @@

configure_logging()


def error_message(errormsg):
logging.error(colored("Error: "+errormsg, 'red'))
logging.error(colored("Error: " + errormsg, "red"))
# TODO: Extend error module to log to error log file
# print(errormsg)


def exit_message(exitmsg):
logging.critical("\033[91m{}\033[0m".format("Fatal Error: "+exitmsg))
logging.critical("\033[91m{}\033[0m".format("Fatal Error: " + exitmsg))
exit()


class Validator:
def is_valid_email(self, email):
# Verify email format
# TODO: Add a list of email domains accepted..may be
# Verify email format
# TODO: Add a list of email domains accepted..may be
if re.match(r"[^@]+@[^@]+\.[^@]+", email):
# print("Email address validation: \033[92m{}\033[0m".format("Success"))
return True
Expand All @@ -44,8 +47,8 @@ def is_valid_ip(self, ip):
return False

def is_valid_username(self, username):
# Verify username format
# TODO: Add a list of username domains accepted..may be
# Verify username format
# TODO: Add a list of username domains accepted..may be
if re.match(r"^[a-zA-Z0-9\-\_\!\@\#\$\%\^\&\*\(\)]+", username):
logging.debug("Input is a valid username")
return True
Expand All @@ -57,61 +60,60 @@ def is_valid_username(self, username):
def check_VTAPIkey(self, VT_APIKey):
# Google IP just for validating key
ip = "8.8.8.8"
url = "https://www.virustotal.com/api/v3/ip_addresses/"+ip
payload={}
headers = {
'x-apikey': VT_APIKey
}
url = "https://www.virustotal.com/api/v3/ip_addresses/" + ip
payload = {}
headers = {"x-apikey": VT_APIKey}
response = requests.request("GET", url, headers=headers, data=payload).text
data = json.loads(response)
if 'error' not in data:
if "error" not in data:
# Print pretty json response
# print(json.dumps(data, indent=4, sort_keys=True))
logging.info("Virus Total Key Validation: \033[92m{}\033[0m".format("Success"))
logging.info(
"Virus Total Key Validation: \033[92m{}\033[0m".format("Success")
)
return True
else:
error_message(json.dumps(data['error'], indent=4, sort_keys=True))
error_message(json.dumps(data["error"], indent=4, sort_keys=True))
exit_message("Virus Total Key Validation failed")
return False

# def check_VTAPIkey(self, VT_APIKey):
# instead of wasting a call to HIBP API just to check validity of key,
# execute the call for the actual query and then throw error if key is
# invalid (since likelihood of key being wrong is slim)
# instead of wasting a call to HIBP API just to check validity of key,
# execute the call for the actual query and then throw error if key is
# invalid (since likelihood of key being wrong is slim)


class KeyFetcher:
def getVTAPIKey(self):

config = configparser.ConfigParser()
if config.read('Config/config.ini', encoding='utf-8'):

if config.read("Config/config.ini", encoding="utf-8"):
pass
else:
exit_message("Config file not found")
try:
VT_APIKey = config['APIKeys']['VT_APIKey']
VT_APIKey = config["APIKeys"]["VT_APIKey"]
except Exception as er:
error_message(str(er))
exit_message("VT API Key not found in config file")
if VT_APIKey == '':
if VT_APIKey == "":
exit_message("VT API Key could not be retrieved")
else:
return VT_APIKey

def getHIBPAPIKey(self):

config = configparser.ConfigParser()
if config.read('Config/config.ini', encoding='utf-8'):
if config.read("Config/config.ini", encoding="utf-8"):
# print("Reading config file...")
pass
else:
exit_message("Config file not found")
try:
HIBP_APIKey = config['APIKeys']['HIBP_APIKey']
HIBP_APIKey = config["APIKeys"]["HIBP_APIKey"]
except Exception as er:
error_message(str(er))
exit_message("HIBP API Key not found in config file")
if HIBP_APIKey == '':
if HIBP_APIKey == "":
exit_message("HIBP API Key could not be retrieved")
else:
return HIBP_APIKey
Expand Down
23 changes: 11 additions & 12 deletions Config/check_config_validity.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import configparser

config = configparser.ConfigParser()
config.read('Config/logger.ini')
config.read("Config/logger.ini")

# check if the required sections are present
if 'loggers' not in config.sections() or 'handlers' not in config.sections():
raise ValueError('Missing required section in config.ini')
if "loggers" not in config.sections() or "handlers" not in config.sections():
raise ValueError("Missing required section in config.ini")

if 'keys' not in config['loggers'] or 'suspicious' not in config['loggers']:
raise ValueError('Missing required key in loggers section in config.ini')
if "keys" not in config["loggers"] or "suspicious" not in config["loggers"]:
raise ValueError("Missing required key in loggers section in config.ini")

# check if the required keys are present in the 'handlers' section
if 'keys' not in config['handlers'] or 'console' not in config['handlers']['keys']:
raise ValueError('Missing required key in handlers section in config.ini')
if "keys" not in config["handlers"] or "console" not in config["handlers"]["keys"]:
raise ValueError("Missing required key in handlers section in config.ini")

# check if the values of the keys are in the expected format
if not isinstance(config.getint('handlers', 'console.level'), int):
raise ValueError('Invalid value for console.level in config.ini')

if not isinstance(config.getint('loggers', 'keys.suspicious.level'), int):
raise ValueError('Invalid value for keys.suspicious.level in config.ini')
if not isinstance(config.getint("handlers", "console.level"), int):
raise ValueError("Invalid value for console.level in config.ini")

if not isinstance(config.getint("loggers", "keys.suspicious.level"), int):
raise ValueError("Invalid value for keys.suspicious.level in config.ini")
Loading