-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from novateams/AWK-NOVA
version: 0.0.2
- Loading branch information
Showing
27 changed files
with
1,285 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Contributing to the Project | ||
|
||
By making contributions to this project, you acknowledge that these contributions are either your original work or have been authorized by your employer. Additionally, you grant an unrestricted, perpetual, and irrevocable copyright license to all present and future users and developers of the project. This license is granted in accordance with the existing license of the project. | ||
|
||
Please note that by submitting your contributions, you affirm that you have the necessary rights to grant this license and that your contributions will be made available under the terms and conditions of the project's existing license. | ||
|
||
Thank you for your contributions to the project! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
name: Updating collection version, git tag and release | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
|
||
jobs: | ||
version_collection_and_tag: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v2 | ||
with: | ||
fetch-depth: 0 | ||
|
||
- name: Configuring collection & tag versions | ||
run: | | ||
target_file="nova/core/galaxy.yml" | ||
# Configuring git | ||
git config --global user.name "Nova CI" | ||
git config --global user.email "[email protected]" | ||
# Updating the version in the galaxy.yml file | ||
version_row_old=$(grep "version: " $target_file) | ||
version=$(echo $version_row_old | cut -d: -f2) | ||
major=$(echo $version | cut -d. -f1) | ||
minor=$(echo $version | cut -d. -f2) | ||
patch=$(echo $version | cut -d. -f3) | ||
patch_new=$(( $patch+1 )) | ||
version_row_new="version: $major.$minor.$patch_new" | ||
sed -i "s/$version_row_old/$version_row_new/" $target_file | ||
TAG_NAME="v$major.$minor.$patch_new" | ||
echo "LATEST_TAG=$TAG_NAME" >> $GITHUB_ENV | ||
# Adding the changed file to git | ||
git add $target_file | ||
# Committing the change | ||
git commit -m "Set nova.core collection version to $major.$minor.$patch_new" | ||
git push | ||
# Tagging and pushing the change | ||
git tag $TAG_NAME | ||
git push origin $TAG_NAME | ||
# Creating temp changelog file | ||
git log --pretty=format:"- %s" $(git describe --tags --abbrev=0 HEAD^^)..HEAD > CHANGELOG.md | ||
- uses: ncipollo/release-action@v1 | ||
with: | ||
tag: ${{ env.LATEST_TAG }} | ||
bodyFile: CHANGELOG.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.githooks | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,6 @@ | ||
# nova.core | ||
Ansible collection for roles and plugins | ||
# Ansible Collection - nova.core | ||
|
||
This is an Ansible collection consisting of some roles and a an inventory plugin for [Providentia](https://github.com/ClarifiedSecurity/Providentia) maintained by the Nova team. This collection is a culmination of years for cyber defense exercises and is maintained by: | ||
|
||
- [Clarified Security](https://www.clarifiedsecurity.com) | ||
- [CCDCOE](https://ccdcoe.org/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
### REQUIRED | ||
# The namespace of the collection. This can be a company/brand/organization or product namespace under which all | ||
# content lives. May only contain alphanumeric lowercase characters and underscores. Namespaces cannot start with | ||
# underscores or numbers and cannot contain consecutive underscores | ||
namespace: nova | ||
|
||
# The name of the collection. Has the same character restrictions as 'namespace' | ||
name: core | ||
|
||
# The version of the collection. Must be compatible with semantic versioning | ||
version: 0.0.1 | ||
|
||
# The path to the Markdown (.md) readme file. This path is relative to the root of the collection | ||
readme: README.md | ||
|
||
# A list of the collection's content authors. Can be just the name or in the format 'Full Name <email> (url) | ||
# @nicks:irc/im.site#channel' | ||
authors: | ||
- https://github.com/novateams | ||
|
||
### OPTIONAL but strongly recommended | ||
# A short summary description of the collection | ||
description: This is a collection of public roles nad plugins that are developed by the Nova team. These roles go very well with Catapult https://github.com/ClarifiedSecurity/catapult but can be used separately. | ||
|
||
# The path to the license file for the collection. This path is relative to the root of the collection. This key is | ||
# mutually exclusive with 'license' | ||
license: | ||
- AGPL-3.0-or-later | ||
|
||
# A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character | ||
# requirements as 'namespace' and 'name' | ||
tags: [] | ||
|
||
# Collections that this collection requires to be installed for it to be usable. The key of the dict is the | ||
# collection label 'namespace.name'. The value is a version range | ||
# L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). Multiple version | ||
# range specifiers can be set and are separated by ',' | ||
dependencies: {} | ||
|
||
# The URL of the originating SCM repository | ||
repository: https://github.com/novateams/nova.core | ||
|
||
# The URL to any online docs | ||
documentation: COMING SOON | ||
|
||
# The URL to the homepage of the collection/project | ||
homepage: https://github.com/novateams/nova.core | ||
|
||
# The URL to the collection issue tracker | ||
issues: https://github.com/novateams/nova.core/issues | ||
|
||
# A list of file glob-like patterns used to filter any files or directories that should not be included in the build | ||
# artifact. A pattern is matched from the relative path of the file or directory of the collection directory. This | ||
# uses 'fnmatch' to match the files or directories. Some directories and files like 'galaxy.yml', '*.pyc', '*.retry', | ||
# and '.git' are always filtered | ||
build_ignore: [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
--- | ||
requires_ansible: ">=2.13.12" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
DOCUMENTATION = """ | ||
name: providentia_v3 | ||
plugin_type: inventory | ||
short_description: Providentia inventory source | ||
requirements: | ||
- requests >= 2.18.4 | ||
- requests_oauthlib | ||
- oauthlib | ||
description: | ||
- Get inventory hosts and groups from Providentia. | ||
- Uses a YAML configuration file that ends with providentia.(yml|yaml). | ||
options: | ||
plugin: | ||
description: token that ensures this is a source file for the 'providentia' plugin. | ||
required: True | ||
providentia_host: | ||
description: Root URL to Providentia. | ||
type: string | ||
required: True | ||
exercise: | ||
description: Exercise abbreviation which defines configuration to populate inventory with. | ||
type: string | ||
required: True | ||
sso_token_url: | ||
description: The endpoint where token may be obtained for Providentia | ||
sso_client_id: | ||
description: SSO client id for Providentia. | ||
type: string | ||
default: "Providentia" | ||
credentials_lookup_env: | ||
description: ENV var used to lookup Providentia credentials KeePass path | ||
type: string | ||
default: KEEPASS_DEPLOYER_CREDENTIALS_PATH | ||
required: False | ||
""" | ||
|
||
from typing import DefaultDict | ||
import requests | ||
import os | ||
import json | ||
import socket | ||
import aiohttp | ||
import asyncio | ||
from oauthlib.oauth2 import LegacyApplicationClient | ||
from pykeepass import PyKeePass | ||
from requests_oauthlib import OAuth2Session | ||
from ansible.plugins.inventory import BaseInventoryPlugin | ||
from ansible.errors import AnsibleError, AnsibleParserError | ||
from ansible.utils.vars import combine_vars, load_extra_vars | ||
from pprint import pprint | ||
|
||
class InventoryModule(BaseInventoryPlugin): | ||
NAME = 'providentia_v3' | ||
|
||
def verify_file(self, path): | ||
if super(InventoryModule, self).verify_file(path): | ||
return True | ||
return False | ||
|
||
def parse(self, inventory, loader, path, cache=True): | ||
super(InventoryModule, self).parse(inventory, loader, path) | ||
self._read_config_data(path) | ||
# merge extra vars | ||
self._options = combine_vars(self._options, load_extra_vars(loader)) | ||
|
||
asyncio.run(self.run()) | ||
|
||
async def run(self): | ||
self.init_inventory() | ||
await self.store_access_token() | ||
|
||
async with aiohttp.ClientSession() as session: | ||
self._session = session | ||
await self.fetch_environment() | ||
await self.fetch_groups() | ||
await self.fetch_hosts() | ||
|
||
def init_inventory(self): | ||
self.inventory.add_group("all") | ||
|
||
self.inventory.set_variable("all", "providentia_api_version", 3) | ||
|
||
async def store_access_token(self): | ||
keepass_creds = os.environ.get(self.get_option('credentials_lookup_env'),"").strip() | ||
sso_creds = self.fetch_keepass_creds(keepass_creds) | ||
|
||
self._access_token = self.fetch_access_token(sso_creds) | ||
|
||
def fetch_keepass_creds(self, creds_path): | ||
kp_soc = "/tmp/ansible-keepass.sock" | ||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) | ||
sock.connect(kp_soc) | ||
|
||
username = {'attr': "username", 'path': creds_path} | ||
sock.send(json.dumps(username).encode()) | ||
username = json.loads(sock.recv(1024).decode()) | ||
|
||
password = {'attr': "password", 'path': creds_path} | ||
sock.send(json.dumps(password).encode()) | ||
password = json.loads(sock.recv(1024).decode()) | ||
|
||
sock.close() | ||
|
||
if(username['status']=='error' or password['status']=='error'): | ||
raise Exception('Error retrieving credentials from Keepass') | ||
|
||
return { | ||
'username': username['text'], | ||
'password': password['text'] | ||
} | ||
|
||
async def fetch_environment(self): | ||
event = await self.fetch_from_providentia('') | ||
for key,value in event['result'].items(): | ||
self.inventory.set_variable("all", key, value) | ||
|
||
async def fetch_groups(self): | ||
groups = await self.fetch_from_providentia('tags') | ||
|
||
# Add groups to inventory | ||
for group_data in groups['result']: | ||
group = group_data['id'] | ||
group_vars = group_data['config_map'] | ||
priority = group_data.get('priority') | ||
|
||
self.inventory.add_group(group) | ||
|
||
# Add group specific variables to group | ||
for key, value in group_vars.items(): | ||
self.inventory.set_variable(group, key, value) | ||
|
||
if priority: | ||
self.inventory.set_variable(group, 'ansible_group_priority', int(priority)) | ||
|
||
# Add groups to inventory | ||
# We do this in separate loop because of groups can reference to | ||
# child groups that may not have been added to inventory already | ||
for group_data in groups['result']: | ||
group = group_data['id'] | ||
group_children = group_data['children'] | ||
|
||
for child_group in group_children: | ||
self.inventory.add_child(group, child_group) | ||
|
||
async def fetch_hosts(self): | ||
hosts = await self.fetch_from_providentia('inventory') | ||
|
||
# List of keys that should be excluded from host variables to avoid endless recursion and overwriting | ||
excluded_keys = ["id", "instances"] | ||
|
||
# Creating a new dictionary with filtered parent vars using first host as a template since all of the host have the same keys | ||
filtered_parent_vars = {key: value for key, value in hosts['result'][0].items() if key not in excluded_keys} | ||
|
||
# Add hosts to inventory | ||
for host in hosts['result']: | ||
for host_instance in host.get('instances', []): | ||
host_instance_id = host_instance['id'] | ||
self.inventory.add_host(host_instance_id) | ||
|
||
self.inventory.set_variable(host_instance_id, "main_id", host['id']) | ||
|
||
for var_name in filtered_parent_vars: | ||
if var_name in host: | ||
self.inventory.set_variable(host_instance_id, var_name, host[var_name]) | ||
|
||
for key, value in host_instance.items(): | ||
self.inventory.set_variable(host_instance_id, key, value) | ||
|
||
for group in host.get('tags', []): | ||
self.inventory.add_child(group, host_instance_id) | ||
|
||
for group in host_instance.get('tags', []): | ||
self.inventory.add_child(group, host_instance_id) | ||
|
||
async def fetch_from_providentia(self, endpoint=""): | ||
providentia_host = self.get_option('providentia_host') | ||
exercise = self.get_option('exercise') | ||
|
||
url = f"{providentia_host}/api/v3/{exercise}/{endpoint}" | ||
|
||
headers = { | ||
'Authorization': f"{self._access_token['token_type']} {self._access_token['access_token']}" | ||
} | ||
async with self._session.get(url, headers=headers) as response: | ||
if response.status == 200: | ||
return await response.json() | ||
|
||
if response.status == 401: | ||
raise Exception('Providentia responded with 401: Unauthenticated') | ||
|
||
if response.status == 403: | ||
raise Exception('Requested token is not authorized to perform this action') | ||
|
||
if response.status == 404: | ||
raise Exception('Providentia responded with 404: not found') | ||
|
||
if response.status == 500: | ||
raise Exception('Providentia responded with 500: server error') | ||
|
||
def fetch_access_token(self, creds): | ||
client_id = self.get_option('sso_client_id') | ||
oauth = OAuth2Session(client=LegacyApplicationClient(client_id=client_id)) | ||
token = oauth.fetch_token( | ||
token_url=self.get_option('sso_token_url'), | ||
username=creds['username'], | ||
password=creds['password'], | ||
client_id=client_id) | ||
|
||
return token |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Role Name | ||
|
||
This role is used to create Virtual Machines in different environments. Currently supported environments are: | ||
|
||
- AWS | ||
- Linode | ||
- VMware vSphere | ||
- VMWare Workstation | ||
|
||
## Requirements | ||
|
||
none | ||
|
||
## Role Variables | ||
|
||
Refer to the [defaults/main.yml](https://github.com/novateams/nova.core/blob/main/nova/core/roles/create/defaults/main.yml) file for a list of variables and their default values. | ||
|
||
## Dependencies | ||
|
||
Depending on the environment you want to create the VM in, you will need to install the following Ansible collections: | ||
|
||
- amazon.aws | ||
- community.aws | ||
- vmware.vmware_rest | ||
- community.vmware | ||
|
||
## Example |
Oops, something went wrong.