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 support for the GWS Policy API #481

Closed
wants to merge 6 commits into from
Closed
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
1 change: 1 addition & 0 deletions docs/authentication/OAuth.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Only complete this section if not authenticating via [Service Account](/docs/aut
1. In the center screen click **Enable APIS AND Services**
1. Search for and enable the **Admin SDK API**
1. Search for and enable the **Groups Settings API**
1. Search for and enable the **Cloud Identity** subtext **Google Enterprise API**
1. During the first run of this tool your default web browser will open up a page to consent to the API scopes needed to run this tool. Sign in
with an account with the necessary privileges and click allow.

Expand Down
3 changes: 2 additions & 1 deletion docs/prerequisites/Prerequisites.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ https://www.googleapis.com/auth/admin.directory.domain.readonly,
https://www.googleapis.com/auth/admin.directory.group.readonly,
https://www.googleapis.com/auth/admin.directory.orgunit.readonly,
https://www.googleapis.com/auth/admin.directory.user.readonly,
https://www.googleapis.com/auth/apps.groups.settings
https://www.googleapis.com/auth/apps.groups.settings,
https://www.googleapis.com/auth/cloud-identity.policies.readonly
```

When running ScubaGoggles for the first time you will be prompted to consent to these API scopes. Users with the Super Admin role automatically have the privilege to consent to these scopes. A custom admin role can also be made with the minimum permissions to consent to these scopes. See this [Google Admin SDK Prerequisites guide](https://developers.google.com/admin-sdk/reports/v1/guides/prerequisites) for more information.
Expand Down
9 changes: 6 additions & 3 deletions scubagoggles/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@
from google_auth_oauthlib.flow import InstalledAppFlow

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/admin.reports.audit.readonly',
SCOPES = [
'https://www.googleapis.com/auth/admin.reports.audit.readonly',
"https://www.googleapis.com/auth/admin.directory.domain.readonly",
"https://www.googleapis.com/auth/admin.directory.orgunit.readonly",
"https://www.googleapis.com/auth/admin.directory.user.readonly",
"https://www.googleapis.com/auth/admin.directory.group.readonly",
"https://www.googleapis.com/auth/apps.groups.settings"]
"https://www.googleapis.com/auth/apps.groups.settings",
"https://www.googleapis.com/auth/cloud-identity.policies.readonly"
]


def gws_auth(cred_path:str, subject_email: str = None):
def gws_auth(cred_path: str, subject_email: str = None):
"""
Generates an Oauth token for accessing Google's APIs

Expand Down
90 changes: 55 additions & 35 deletions scubagoggles/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@
from datetime import datetime
from tqdm import tqdm
from googleapiclient.discovery import build
# FUTURE REMOVE
from google.auth.transport.requests import AuthorizedSession

from scubagoggles.auth import gws_auth
from scubagoggles.provider import Provider
from scubagoggles.run_rego import opa_eval
from scubagoggles.reporter import md_parser
from scubagoggles.reporter.reporter import Reporter
from scubagoggles.utils import rel_abs_path
# FUTURE REMOVE
from scubagoggles.policy_api import PolicyAPI


class Orchestrator:
Expand Down Expand Up @@ -58,7 +62,6 @@ class Orchestrator:
}

def __init__(self, args: argparse.Namespace):

"""Orchestrator class initialization

:param args: command arguments parsed by the argparse module. See
Expand Down Expand Up @@ -91,7 +94,7 @@ def _run_gws_providers(self, services: dict):
provider_dict['successful_calls'] = list(provider.successful_calls)
provider_dict['unsuccessful_calls'] = list(provider.unsuccessful_calls)

settings_json = json.dumps(provider_dict, indent = 4)
settings_json = json.dumps(provider_dict, indent=4)
out_path = out_folder + f'/{args.outputproviderfilename}.json'
with open(out_path, mode="w", encoding='UTF-8') as outfile:
outfile.write(settings_json)
Expand All @@ -113,21 +116,22 @@ def _rego_eval(self):
opa_path = args.opapath
rego_path = args.regopath

products_bar.set_description(f"Running Rego verification for {product}...")
products_bar.set_description(
f"Running Rego verification for {product}...")
product_tests = opa_eval(
product_name=product_name,
input_file=input_file,
opa_path=opa_path,
rego_path=rego_path,
omit_sudo=args.omitsudo,
debug=args.debug
product_name=product_name,
input_file=input_file,
opa_path=opa_path,
rego_path=rego_path,
omit_sudo=args.omitsudo,
debug=args.debug
)
try:
results.extend(product_tests[0])
except Exception as exc:
raise Exception("run_rego error") from exc

settings_json = json.dumps(results,sort_keys=True ,indent = 4)
settings_json = json.dumps(results, sort_keys=True, indent=4)
out_path = out_folder + f'/{args.outputregofilename}.json'
with open(out_path, mode="w", encoding='UTF-8') as outfile:
outfile.write(settings_json)
Expand Down Expand Up @@ -158,7 +162,7 @@ def _generate_summary(cls, stats: dict) -> str:
n_omit = stats['Omit']

pass_summary = (f"<div class='summary pass'>{n_success}"
f" {cls._pluralize('pass', 'passes', n_success)}</div>")
f" {cls._pluralize('pass', 'passes', n_success)}</div>")

# The warnings, failures, and manuals are only shown if they are
# greater than zero. Reserve the space for them here. They will
Expand All @@ -171,19 +175,19 @@ def _generate_summary(cls, stats: dict) -> str:

if n_warn > 0:
warning_summary = (f"<div class='summary warning'>{n_warn}"
f" {cls._pluralize('warning', 'warnings', n_warn)}</div>")
f" {cls._pluralize('warning', 'warnings', n_warn)}</div>")
if n_fail > 0:
failure_summary = (f"<div class='summary failure'>{n_fail}"
f" {cls._pluralize('failure', 'failures', n_fail)}</div>")
f" {cls._pluralize('failure', 'failures', n_fail)}</div>")
if n_manual > 0:
manual_summary = (f"<div class='summary manual'>{n_manual} manual"
f" {cls._pluralize('check', 'checks', n_manual)}</div>")
f" {cls._pluralize('check', 'checks', n_manual)}</div>")
if n_error > 0:
error_summary = (f"<div class='summary error'>{n_error}"
f" {cls._pluralize('error', 'errors', n_error)}</div>")
f" {cls._pluralize('error', 'errors', n_error)}</div>")
if n_omit > 0:
omit_summary = (f"<div class='summary manual'>{n_omit}"
" omitted</div>")
" omitted</div>")

return f"{pass_summary}{warning_summary}{failure_summary}" \
f"{manual_summary}{omit_summary}{error_summary}"
Expand Down Expand Up @@ -243,11 +247,11 @@ def _run_reporter(self):
break
else:
raise RuntimeError("Unable to process 'rules' as no policy group named "
"'System-defined Rules' found in the Common Controls baseline.")
"'System-defined Rules' found in the Common Controls baseline.")

# Load Org metadata from provider
with open(f'{out_folder}/{args.outputproviderfilename}.json',
mode='r',encoding='UTF-8') as file:
mode='r', encoding='UTF-8') as file:
tenant_info = json.load(file)['tenant_info']
tenant_domain = tenant_info['domain']

Expand All @@ -269,7 +273,8 @@ def _run_reporter(self):
fullname in prod_to_fullname.items()}

timestamp_utc = datetime.utcnow()
timestamp_zulu = timestamp_utc.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
timestamp_zulu = timestamp_utc.strftime(
'%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'

report_metadata = {
"TenantId": None,
Expand All @@ -288,7 +293,8 @@ def _run_reporter(self):
main_report_name = args.outputreportfilename
products_bar = tqdm(products, leave=False, disable=args.quiet)
for product in products_bar:
products_bar.set_description(f"Creating the HTML and JSON Report for {product}...")
products_bar.set_description(
f"Creating the HTML and JSON Report for {product}...")
reporter = Reporter(product,
tenant_domain,
main_report_name,
Expand All @@ -301,8 +307,9 @@ def _run_reporter(self):
stats_and_data[product] = \
reporter.rego_json_to_ind_reports(test_results_data,
out_folder)
baseline_product_summary = {product:stats_and_data[product][0]}
baseline_product_results_json = {product:stats_and_data[product][1]}
baseline_product_summary = {product: stats_and_data[product][0]}
baseline_product_results_json = {
product: stats_and_data[product][1]}
summary.update(baseline_product_summary)
results.update(baseline_product_results_json)
total_output.update({"Summary": summary})
Expand All @@ -312,7 +319,7 @@ def _run_reporter(self):
with open(f'{out_folder}/{args.outputproviderfilename}.json', encoding='UTF-8') as file:
raw_data = json.load(file)
total_output.update({"Raw": raw_data})
report = json.dumps(total_output, indent = 4)
report = json.dumps(total_output, indent=4)
with open(f"{out_folder}/{out_jsonfile}.json", mode='w', encoding='UTF-8') as results_file:
results_file.write(report)

Expand All @@ -326,10 +333,10 @@ def _run_reporter(self):
fragments = []
table_data = []
for product, stats in stats_and_data.items():
## Build the "Baseline Conformance Reports" column
# Build the "Baseline Conformance Reports" column
product_capitalize = product.capitalize()
full_name = prod_to_fullname[product]
link_path = "./IndividualReports/" f"{product_capitalize}Report.html"
link_path = "./IndividualReports/" f"{product_capitalize}Report.html"
link = f"<a class=\"individual_reports\" href={link_path}>{full_name}</a>"
table_data.append({
"Baseline Conformance Reports": link,
Expand Down Expand Up @@ -357,16 +364,19 @@ def _run_cached(self):
"""

args = self._args
args.outputpath = str(rel_abs_path(__file__,args.outputpath))
args.outputpath = str(rel_abs_path(__file__, args.outputpath))
Path(args.outputpath).mkdir(parents=True, exist_ok=True)
args.outputpath = os.path.abspath(args.outputpath)

if not args.skipexport:
creds = gws_auth(args.credentials)
services = {}
services['reports'] = build('admin', 'reports_v1', credentials=creds)
services['directory'] = build('admin', 'directory_v1', credentials=creds)
services['groups'] = build('groupssettings', 'v1', credentials=creds)
services['reports'] = build(
'admin', 'reports_v1', credentials=creds)
services['directory'] = build(
'admin', 'directory_v1', credentials=creds)
services['groups'] = build(
'groupssettings', 'v1', credentials=creds)
self._run_gws_providers(services)

if not os.path.exists(f'{args.outputpath}/{args.outputproviderfilename}.json'):
Expand All @@ -375,10 +385,10 @@ def _run_cached(self):
# output doesn't exist as a standalone file, create it from the scuba results
# file so the other functions can execute as normal.
with open(f'{args.outputpath}/{args.outjsonfilename}.json', 'r',
encoding='UTF-8') as scuba_results:
encoding='UTF-8') as scuba_results:
provider_output = json.load(scuba_results)['Raw']
with open(f'{args.outputpath}/{args.outputproviderfilename}.json', 'w',
encoding='UTF-8') as provider_file:
encoding='UTF-8') as provider_file:
json.dump(provider_output, provider_file)
self._rego_eval()
self._run_reporter()
Expand Down Expand Up @@ -409,7 +419,7 @@ def start_automation(self):

if args.skipexport and not args.runcached:
exc = 'Used --skipexport without --runcached' \
'please rerun scubagoggles with --runcached as well'
'please rerun scubagoggles with --runcached as well'
raise Exception(exc)

if not args.runcached:
Expand All @@ -423,10 +433,20 @@ def start_automation(self):

# authenticate
creds = gws_auth(args.credentials, args.subjectemail)

# FUTURE REMOVE: Temporarily authenticate to the Policy API using policy.py
session = AuthorizedSession(creds)
policy_api = PolicyAPI(session)

services = {}
services['reports'] = build('admin', 'reports_v1', credentials=creds)
services['directory'] = build('admin', 'directory_v1', credentials=creds)
services['groups'] = build('groupssettings', 'v1', credentials=creds)
services['reports'] = build(
'admin', 'reports_v1', credentials=creds)
services['directory'] = build(
'admin', 'directory_v1', credentials=creds)
services['groups'] = build(
'groupssettings', 'v1', credentials=creds)
# FUTURE REMOVE: Temporarily stick the policy API object in services
services['cloudidentity'] = policy_api

self._run_gws_providers(services)
self._rego_eval()
Expand Down
Loading
Loading