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

Add Wazuh collection form to Assessments page #1651

Merged
merged 7 commits into from
Jul 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,5 @@ _notes/

env_var.sh
dev_env/docker/ssh/
environment.json
environment.json
environment.okta.json
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ v0.9.6-dev (June XX, 2021)

* Display legacy control implementation statements within system's statements.
* Added compare components button to compare one component's statements to other selected components.
* Added a Select/Deselect button for component comparison choice
* Added a Select/Deselect button for component comparison choice.
* Add accordion to assessment page to provide information on getting data from Wazuh.
* Add form to Assessments page to collect Wazuh information.

**Bug fixes**

Expand All @@ -16,12 +18,12 @@ v0.9.6-dev (June XX, 2021)
**Developer changes**

* Add custom Django command to batch import legacy control implementation statements from legacy SSPs Excel spreadsheet exports. Currently supports CSAM.
* Added missing unit test for portfolio project endpoint
* Added missing unit test for portfolio project endpoint.
* Add `sec_srvc.SecurityService` class to represent a security service from which data could be collected.

**Data changes**

* Set all `StatementTypeEnum.<LABEL>.value` to `StatementTypeEnum.<LABEL>.name` in order for relevant label/term to show up in Django database admin interface.

* Fisma impact level is now represented as Security Sensitivity level following OSCAL's schema.


Expand Down
1 change: 1 addition & 0 deletions controls/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,4 @@ def clean(self):

assessment_results = forms.CharField(label='System assessment result items (JSON)', required=False, widget=forms.Textarea(),
help_text="Listing of assessment items in JSON")

1 change: 1 addition & 0 deletions controls/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

# Systems Assessment Results
url(r'^(?P<system_id>.*)/assessments$', views.system_assessment_results_list, name="system_assessment_results_list"),
url(r'^(?P<system_id>.*)/assessments/new/wazuh$', views.new_system_assessment_result_wazuh, name="new_system_assessment_result_wazuh"),
url(r'^(?P<system_id>.*)/assessment/new$', views.manage_system_assessment_result, name="new_system_assessment_result"),
url(r'^(?P<system_id>.*)/sar/(?P<sar_id>.*)/view$', views.view_system_assessment_result_summary, name="view_system_assessment_result_summary"),
url(r'^(?P<system_id>.*)/sar/(?P<sar_id>.*)/edit$', views.manage_system_assessment_result, name="manage_system_assessment_result"),
Expand Down
95 changes: 94 additions & 1 deletion controls/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from .utilities import *
from simple_history.utils import update_change_reason
import functools
import subprocess
logging.basicConfig()
import structlog
from structlog import get_logger
Expand Down Expand Up @@ -3172,11 +3173,16 @@ def system_assessment_results_list(request, system_id=None):
project = system.projects.all()[0]
sars = system.system_assessment_result.all().order_by('created').reverse()

# Return the controls
# Retrieve user's API keys
api_keys = request.user.get_api_keys()

context = {
"system": system,
"project": project,
"sars": sars,
"api_key_ro": api_keys['ro'],
"api_key_rw": api_keys['rw'],
"api_key_wo": api_keys['wo']
}
return render(request, "systems/sar_list.html", context)

Expand Down Expand Up @@ -3271,3 +3277,90 @@ def system_assessment_result_history(request, system_id, sar_id=None):
"deployment": full_sar_history,
}
return render(request, "systems/sar_history.html", context)

@login_required
def new_system_assessment_result_wazuh(request, system_id):
"""Returns a SAR info from Wazuh and adds to system"""

if request.method != "POST":
return HttpResponseNotAllowed(["POST"])

else:
# Validate data
valid = True
for param in ["wazuhhost_val", "user_val", "passwd_val", "agents_val"]:
if param not in request.POST or request.POST[param] == "":
valid = False
messages.add_message(request, messages.WARNING, f"Please complete field {param.replace('_val','')}")
if not valid:
return HttpResponseRedirect(f"/systems/{system_id}/assessments")

# Check user permissions
system = System.objects.get(pk=system_id)
if not request.user.has_perm('change_system', system):
# User does not have write permissions
# Log permission to save answer denied
logger.info(
event="delete_smt permission_denied",
object={"object": "statement", "id": statement.id}, # todo - statement not defined anywhere - Greg
user={"id": request.user.id, "username": request.user.username}
)
return HttpResponseForbidden(
"Permission denied. {} does not have change privileges to system and/or project.".format(
request.user.username))

from sec_srvc.wazuh import WazuhSecurityService
wazuh_sec_svc = WazuhSecurityService()
wazuh_sec_svc.setup(base_url=request.POST['wazuhhost_val'])

authentication = wazuh_sec_svc.authenticate(request.POST['user_val'], request.POST['passwd_val'])

if wazuh_sec_svc.is_authenticated:
identifiers = request.POST['agents_val']
extracted_data = wazuh_sec_svc.extract_data(authentication, identifiers)

# TODO: Set deployment id
deployment_uuid = None

transformed_data = wazuh_sec_svc.transform_data(extracted_data, system_id, "Scan Title", "Scan description", deployment_uuid)
loaded_data = wazuh_sec_svc.load_data(transformed_data)

# Determine deployment_id from deployment_uuid
# TODO: Make sure deployment is associated with system
if deployment_uuid is None or deployment_uuid == "None":
# When deployment is not defined, leave blank and attach SAR to system only
deployment = None
deployment_id = None
else:
deployment = Deployment.objects.get(uuid=deployment_uuid)
deployment_id = deployment.id

sar = SystemAssessmentResult(
name=transformed_data["metadata"]["title"],
description=transformed_data["metadata"]["description"],
system_id=transformed_data["metadata"]["system_id"],
deployment_id=deployment_id,
assessment_results=transformed_data
# assessment_results=json.loads(request.FILES.get('data').read().decode("utf8", "replace"))
)
sar.save()
logger.info(
event="create_system_assessment_result",
object={"object": "system_assessment_result", "id": sar.id, "name":sar.name},
user={"id": request.user.id, "username": request.user.username}
)
messages.add_message(request, messages.INFO, "Data from Wazuh retrieved and loaded")

# Redirect
return HttpResponseRedirect(f"/systems/{system_id}/assessments")

else:
# TODO: better handling of response code; 401, 301, etc.
raise Exception(wazuh_sec_svc.error_msg['error'])







Empty file added sec_srvc/__init__.py
Empty file.
39 changes: 39 additions & 0 deletions sec_srvc/security_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

class SecurityService:
DESCRIPTION = {}

def __init__(self, **kwargs):
assert self.DESCRIPTION, "Developer must assign a description dict"
self.__is_authenticated = False
self.error_msg = {}
self.auth_dict = {}
self.data = None
self.base_url = None

def setup(self, **kwargs):
raise NotImplementedError()

def get_response(self, url, headers=None, verify=False):
raise NotImplementedError()

def authenticate(self, user=None, passwd=None):
"""Authenticate with service"""
raise NotImplementedError()

@property
def is_authenticated(self):
return self.__is_authenticated

@is_authenticated.setter
def is_authenticated(self, value):
self.__is_authenticated = value

def extract_data(self, authentication, identifiers):
"""Extract data from Security Service using list of identifiers"""
raise NotImplementedError()

def transform_data(self, data, system_id=None, title=None, description=None, deployment_uuid=None):
raise NotImplementedError()

def load_data(self, data):
raise NotImplementedError()
103 changes: 103 additions & 0 deletions sec_srvc/wazuh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import json

import requests
from sec_srvc.security_service import SecurityService
from base64 import b64encode
from urllib.parse import urlparse


class WazuhSecurityService(SecurityService):
DESCRIPTION = {
"name": "Example Security Service",
"description": "Short description",
"version": "2.1"
}

def setup(self, **kwargs):
self.base_url = kwargs.pop('base_url')
url_parts = urlparse(self.base_url)
if not (url_parts.scheme and url_parts.netloc):
raise Exception("Invalid base url")

def authenticate(self, user=None, passwd=None):
"""Authenticate with service"""
if user is None or passwd is None:
self.error_msg = {"error": "Username and/or password not provided."}
return
login_url = f"{self.base_url}/security/user/authenticate"
basic_auth = f"{user}:{passwd}".encode()
headers = {'Authorization': f'Basic {b64encode(basic_auth).decode()}'}
token = f'Bearer {self.get_response(login_url, headers)["data"]["token"]}'
self.auth_dict = {"token": token}
self.is_authenticated = True

def extract_data(self, authentication, identifiers):
"""Extract data from Security Service using list of identifiers"""
if not self.is_authenticated:
raise Exception("User must be authenticated before making extraction request from Wazuh")

identifier_ids = None
if identifiers is not None:
identifier_ids = identifiers.split(",")
description = self.DESCRIPTION["description"]
title = self.DESCRIPTION["name"]

sar_list = []
headers = dict(Authorization=self.auth_dict['token'])

# Extract data
for identifer_id in identifier_ids:
# Retrieve Wazuh SCA scan for each agent
endpoint = f'/sca/{identifer_id}?pretty=true'
response = self.get_response(self.base_url + endpoint, headers)

# Enhance data
# TODO: Should this be in transform?
if len(response["data"]["affected_items"]) == 1:
endpoint_result = response["data"]["affected_items"][0]
# Enhance agent result
endpoint_result['agent'] = identifer_id
endpoint_result['ip_address'] = None
endpoint_result[
'url_link'] = f"https://{self.base_url}/app/wazuh#/overview/?tab=sca&agentId={identifer_id}"
sar_list.append(endpoint_result)
else:
endpoint_result = None
data = sar_list
return data

def transform_data(self, data, system_id=None, title=None, description=None, deployment_uuid=None):
modified_items = data

if deployment_uuid is not None:
d_uuid_uuid = uuid.UUID(f'urn:uuid:{deployment_uuid}')
else:
d_uuid_uuid = None

# TODO: update data time fields
transformed_data = {'schema': 'GovReadySimpleSAR',
'version': '0.2',
"metadata": {
"deployment_uuid": f"{d_uuid_uuid}",
"description": f"{description}",
"last-modified": "dateTime-with-timezone",
"published": "dateTime-with-timezone",
"schema": "GovReadySimpleSAR",
"system_id": f"{system_id}",
"title": f"{title}",
"version": "0.2"
},
'sar': modified_items
}
return transformed_data

def get_response(self, url, headers=None, verify=False):
request_result = requests.get(url, headers=headers, verify=verify)
if request_result.status_code == 200:
return json.loads(request_result.content.decode())
else:
# TODO: better handling of response code; 401, 301, etc.
raise Exception(f"Error obtaining response: {request_result.json()}")

def load_data(self, data):
return "Load data"
76 changes: 75 additions & 1 deletion templates/systems/sar_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,88 @@

{% block head %}
{{block.super}}

<style>

details > summary::before {
content: '▶ ';
}

details[open] > summary::before {
content: '▼ ';
}

</style>
{% include "controls/_style-controls.html" %}
{% endblock %}

<!-- action buttons included from project-base.html -->
<!-- authoring_tool_enabled included from project-base.html -->

{% block body_content %}
<div class="systems-top">

<div>
<form method="post" action="{% url 'new_system_assessment_result_wazuh' project.system.id %}"
style="border: 1px solid #999; padding:12px 12px 12px 12px; border-radius:12px; margin-bottom: 12px;">
<div style="">Retrieve data from Wazuh</div>
{% csrf_token %}
<div class="form-inline">
<label for="wazuhhost_val">Wazuh host: </label>
<input id="wazuhhost_val" class="form-control" type="text" name="wazuhhost_val" class="form-control" placeholder="https://wazuh.example.com:5500" onchange="update_wazuhhost(this.value); return false;" style="width: 250px;"/>
&nbsp;&nbsp;
<label for="user_val">User: </label>
<input id="user_val" class="form-control" type="text" name="user_val" class="form-control" placeholder="seabiscuit" onchange="update_user(this.value); return false;"/>
&nbsp;&nbsp;
<label for="passwd_val"> Password: </label>
<input id="passwd_val" class="form-control" type="password" name="passwd_val" class="form-control" placeholder="secretword" onchange="update_passwd(this.value)"/>
&nbsp;&nbsp;
<label for="agents_val"> Agents: </label>
<input id="agents_val" class="form-control" type="text" name="agents_val" class="form-control" placeholder="001,002,003,004" onchange="update_agents(this.value)"/>
<button type="submit" id="create-object-button" class="btn btn-success">Execute &raquo;</button>
</div>

<details style="margin-left: 100px; margin-right: 24px; margin-top:8px;">
<summary>View Wazuh Commandline</summary>
<div>
<pre>
# Run wazuht_etl.py
python tools/simple_sar_server/wazuh_etl.py {{api_key_wo}} <span class="wazuhhost_val">https://wazuh.example.com:55000</span> -s {{project.system.id}} --agents <span class="agents_val">001,002,003,004</span>

# Authenticate and get Bearer token
TOKEN=$(curl -u <span class="user_val">seabisbuit</span>:<span class="passwd_val">secret</span> -k -X GET "<span class="wazuhhost_val">https://wazuh.example.com:55000</span>/security/user/authenticate?raw=true")

# Retrieve agent (e.g. monitored host) information
curl -k -X GET "<span class="wazuhhost_val">WAZUH_HOST</span>/agents?pretty=true&sort=-ip,name" -H "Authorization: Bearer $TOKEN"
</pre>

</div>
</details>

</div>

<script type="text/javascript">
// Change target values
function update_wazuhhost(value) {
$(".wazuhhost_val").html(value);
return false;
}
function update_user(value) {
$(".user_val").html(value);
return false;
}
function update_passwd(value) {
var str = new Array(value.length + 1).join( "X" );
$(".passwd_val").html(str);
return false;
}
function update_agents(value) {
$(".agents_val").html(value);
return false;
}
</script>
</form>


<div id="tab-content" class="row rows-header">
<div id="" class="col-xs-4 col-sm-4 col-md-4 col-lg-4 col-xl-4 systems-poam">System Assessment Results</div>
<div id="" class="col-xs-5 col-sm-5 col-md-5 col-lg-5 col-xl-5">&nbsp;</div>
Expand Down