Skip to content

Commit

Permalink
Basic implementation for a CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
ab-smith committed Sep 23, 2024
1 parent 53d3dc6 commit 71f04f6
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 0 deletions.
118 changes: 118 additions & 0 deletions ca-cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#! python3
from typing import Required
import click
import requests
import yaml
from rich import print
import sys, os
import pandas as pd

cli_cfg = dict()
auth_data = dict()

API_URL = ""
TOKEN = ""
GLOBAL_FOLDER_ID = None

with open("cli_config.yaml", "r") as yfile:
cli_cfg = yaml.safe_load(yfile)

try:
API_URL = cli_cfg["rest"]["url"]
except:
print("Missing API URL. Check the yaml file")
sys.exit(1)

with open(".tmp.yaml", "r") as yfile:
auth_data = yaml.safe_load(yfile)

try:
TOKEN = auth_data["token"]
except:
print("Missing a valid token. Try the auth command.")
sys.exit(1)


@click.group()
def cli():
"""A CLI tool to interact with CISO Assistant REST API."""
pass


@click.command()
@click.option("--user-id", default=1, help="User ID to fetch data for.")
def get_user(user_id):
"""Get user details by user ID."""
response = requests.get(f"{API_URL}/users/{user_id}")

if response.status_code == 200:
user = response.json()
click.echo(f"Name: {user['name']}")
click.echo(f"Username: {user['username']}")
click.echo(f"Email: {user['email']}")
else:
click.echo("Failed to retrieve user.")


@click.command()
@click.option("--email", required=True)
@click.option("--password", required=True)
def auth(email, password):
"""Authenticate to get a temp token"""
url = f"{API_URL}/iam/login/"
data = {"username": email, "password": password}
headers = {"accept": "application/json", "Content-Type": "application/json"}

res = requests.post(url, data, headers)
with open(".tmp.yaml", "w") as yfile:
yaml.safe_dump(res.json(), yfile)


@click.command()
def get_folders():
"""Get folders"""
url = f"{API_URL}/folders/"
headers = {"Authorization": f"Token {TOKEN}"}
res = requests.get(url, headers=headers)
# TODO: should we handle pagination for this one?
if res.status_code == 200:
output = res.json()
for folder in output["results"]:
if folder["content_type"] == "GLOBAL":
GLOBAL_FOLDER_ID = folder["id"]
break
print(res.json())


@click.command()
@click.option("--ifile", required=True, help="Path of the csv file with assets")
def import_assets(ifile):
"""import assets from a csv"""
df = pd.read_csv(ifile)
url = f"{API_URL}/assets/"
headers = {
"Authorization": f"Token {TOKEN}",
}
for id, row in df.iterrows():
asset_type = "SP"
name = row["name"]
if row["type"]:
asset_type = row["type"]

data = {
"name": "titi",
"folder": "2e9fb22c-c521-4f16-839d-c16b6ed0670d",
"type": "SP",
}
res = requests.post(url, data=data, headers=headers)
if res.status_code != 200:
click.echo("something went wrong")


# Add commands to the CLI group
cli.add_command(get_folders)
cli.add_command(auth)
cli.add_command(import_assets)

if __name__ == "__main__":
cli()
1 change: 1 addition & 0 deletions cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.tmp.yaml
168 changes: 168 additions & 0 deletions cli/ca-cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#! python3
import click
import requests
import yaml
from rich import print
import sys, os
import pandas as pd
from pathlib import Path

cli_cfg = dict()
auth_data = dict()

API_URL = ""
GLOBAL_FOLDER_ID = None
TOKEN = ""
USERNAME = ""
PASSWORD = ""

with open("cli_config.yaml", "r") as yfile:
cli_cfg = yaml.safe_load(yfile)

try:
API_URL = cli_cfg["rest"]["url"]
except:
print("Missing API URL. Check the yaml file")
sys.exit(1)

try:
USERNAME = cli_cfg["credentials"]["username"]
PASSWORD = cli_cfg["credentials"]["password"]
except:
print(
"Missing credentials in the config file. You need to pass them to the CLI in this case."
)


def check_auth():
if Path(".tmp.yaml").exists():
click.echo("Found auth data. Trying them")
with open(".tmp.yaml", "r") as yfile:
auth_data = yaml.safe_load(yfile)
return auth_data["token"]
else:
click.echo("Could not find authentication data.")


TOKEN = check_auth()


@click.group()
def cli():
"""A CLI tool to interact with CISO Assistant REST API."""
pass


@click.command()
@click.option("--email", required=False)
@click.option("--password", required=False)
def auth(email, password):
"""Authenticate to get a temp token"""
url = f"{API_URL}/iam/login/"
if email and password:
data = {"username": email, "password": password}
else:
print("trying credentials from the config file")
data = {"username": USERNAME, "password": PASSWORD}
headers = {"accept": "application/json", "Content-Type": "application/json"}

res = requests.post(url, data, headers)
print(res.status_code)
with open(".tmp.yaml", "w") as yfile:
yaml.safe_dump(res.json(), yfile)
print("Looks good, you can move to other commands.")


def _get_folders():
url = f"{API_URL}/folders/"
headers = {"Authorization": f"Token {TOKEN}"}
res = requests.get(url, headers=headers)
# TODO: should we handle pagination for this one?
if res.status_code == 200:
output = res.json()
for folder in output["results"]:
if folder["content_type"] == "GLOBAL":
GLOBAL_FOLDER_ID = folder["id"]
return GLOBAL_FOLDER_ID


@click.command()
def get_folders():
"""Get folders"""
GLOBAL_FOLDER_ID = _get_folders()
print("GLOBAL_FOLDER_ID: ", GLOBAL_FOLDER_ID)


@click.command()
@click.option("--ifile", required=True, help="Path of the csv file with assets")
def import_assets(ifile):
"""import assets from a csv"""
GLOBAL_FOLDER_ID = _get_folders()
df = pd.read_csv(ifile)
url = f"{API_URL}/assets/"
headers = {
"Authorization": f"Token {TOKEN}",
}
for _, row in df.iterrows():
asset_type = "SP"
name = row["name"]
if row["type"].lower() == "primary":
asset_type = "PR"
else:
asset_type = "SP"

data = {
"name": name,
"folder": GLOBAL_FOLDER_ID,
"type": asset_type,
}
res = requests.post(url, json=data, headers=headers)
if res.status_code != 201:
click.echo("❌ something went wrong")
print(res.json())
else:
print(f"✅ {name} created")


@click.command()
@click.option(
"--ifile", required=True, help="Path of the csv file with applied controls"
)
def import_controls(ifile):
"""import applied controls"""
df = pd.read_csv(ifile)
GLOBAL_FOLDER_ID = _get_folders()
url = f"{API_URL}/applied-controls/"
headers = {
"Authorization": f"Token {TOKEN}",
}
print(GLOBAL_FOLDER_ID)
for _, row in df.iterrows():
name = row["name"]
description = row["description"]
csf_function = row["csf_function"]
category = row["category"]

data = {
"name": name,
"folder": GLOBAL_FOLDER_ID,
"description": description,
"csf_function": csf_function.lower(),
"category": category.lower(),
}
res = requests.post(url, json=data, headers=headers)
if res.status_code != 201:
click.echo("❌ something went wrong")
print(res.json())
else:
print(f"✅ {name} created")


# Add commands to the CLI group
cli.add_command(get_folders)
cli.add_command(auth)
cli.add_command(import_assets)
cli.add_command(import_controls)

if __name__ == "__main__":
cli()
5 changes: 5 additions & 0 deletions cli/cli_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
rest:
url: http://localhost:8000/api # don't put a trailing slash
credentials:
username: (some username)
password: (some password)
4 changes: 4 additions & 0 deletions cli/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pandas
rich
requests
click
3 changes: 3 additions & 0 deletions cli/sample_assets.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name,description,domain,type
asset1,relevant information,Global,Primary
asset2,relevant information,Global,Support
21 changes: 21 additions & 0 deletions cli/sample_controls.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name,description,category,csf_function
Firewall,Monitors and controls incoming and outgoing network traffic.,Technical,Protect
Disaster Recovery Plan,Provides steps for recovering from a major disaster.,Policy,Recover
Vulnerability Scanning,Identifies vulnerabilities in systems and applications.,Process,Identify
Security Information and Event Management (SIEM),Aggregates and analyzes security activity.,Technical,Detect
Encryption,Secures data by converting it into unreadable code.,Technical,Protect
Door and Facility Locks,Restricts physical access to sensitive areas.,Physical,Protect
Business Continuity Plan,Ensures that critical business functions continue during a disaster.,Policy,Recover
Multi-Factor Authentication (MFA),Requires two or more verification methods for access.,Process,Protect
Physical Security Controls,Secures physical access to critical systems.,Physical,Protect
Access Control Policy,Defines access privileges for employees.,Policy,Protect
Intrusion Detection System (IDS),Detects suspicious activity and anomalies in the network.,Technical,Detect
Incident Response Plan,Provides guidelines for responding to security incidents.,Policy,Respond
Backup and Restore Procedures,Outlines steps to back up and restore data.,Process,Recover
Patch Management,Regularly updates software to fix security vulnerabilities.,Process,Protect
Network Segmentation,Divides networks into segments to limit access.,Technical,Protect
Data Loss Prevention (DLP),Prevents sensitive data from being accessed or leaked.,Technical,Protect
Intrusion Prevention System (IPS),Automatically blocks suspicious activity in the network.,Technical,Protect
Endpoint Detection and Response (EDR),Provides real-time monitoring and response to endpoint threats.,Technical,Detect
Anti-Virus Software,Detects and removes malicious software.,Technical,Protect
Security Awareness Training,Educates employees on security best practices.,Process,Protect
2 changes: 2 additions & 0 deletions cli_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
rest:
url: http://localhost:8000/api # don't put a trailing slash
2 changes: 2 additions & 0 deletions sample_assets.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name,description,domain,type
asset1,relevant information,Global,Primary

0 comments on commit 71f04f6

Please sign in to comment.