diff --git a/Dockerfile b/Dockerfile index e55373e..b51f0cc 100755 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,7 @@ COPY core /app/core COPY static /app/static COPY templates /app/templates COPY temp /app/temp +COPY config.py /app COPY alfred.py /app COPY version.py /app diff --git a/README.md b/README.md index fa9a9c9..b11ffb8 100755 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Ever wanted to have your own version of [OPA's Playground](https://play.openpoli - Syntax Highlighting - Coverage Highlighting - Data / Input / Policy Editor +- Restrict Execution of Builtins, such as: `http.send` or `opa.runtime` - Download Policy as File / Copy to Clipboard # Screenshot @@ -30,6 +31,18 @@ Ever wanted to have your own version of [OPA's Playground](https://play.openpoli Alfred

+# Configuration +There is not a whole lot of configurations required for Alfred. If you want to restrict certain builtins from running in policies, you can do so in `config.py`: + +``` +RESTRICTED_BUILTINS = [ + 'http.send', + 'opa.runtime' +] +``` + +By default, all builtins are allowed. + # How to Install and Use ## Clone repository `git clone https://github.com/dolevf/Open-Policy-Agent-Alfred` diff --git a/alfred.py b/alfred.py index f1a1881..3608ee8 100755 --- a/alfred.py +++ b/alfred.py @@ -22,28 +22,34 @@ def evaluate(): coverage = request.json.get('coverage', False) result = [] - output = utils.opa_evaluate(policy, inputs, data, coverage) covered_lines = [] non_covered_lines = [] - - if 'result' in output: - package_name = utils.get_package_name(policy) - value = output['result'][0]['expressions'][0]['value'] - covered_lines= utils.get_coverage(output, covered=True) - non_covered_lines = utils.get_coverage(output, covered=False) - try: - result = utils.get_recursively(value, package_name)[0] - except IndexError: - print('Error obtaining result.') - - elif 'errors' in output: - for err in output['errors']: - message = err['message'] - row = err['location']['row'] - code = err['code'] - result.append("{0}: {1}: {2} ".format(row, code, message)) - - return jsonify({"result":result, "coverage":covered_lines, "no_coverage":non_covered_lines}) + query_eval_ns = None + + policy_violation_name = utils.check_security_policy(policy) + if policy_violation_name: + result = ['{} is not allowed to be executed!'.format(policy_violation_name)] + else: + output = utils.opa_evaluate(policy, inputs, data, coverage) + if 'result' in output: + package_name = utils.get_package_name(policy) + value = output['result'][0]['expressions'][0]['value'] + query_eval_ns = utils.get_query_eval_ns(output) + covered_lines= utils.get_coverage(output, covered=True) + non_covered_lines = utils.get_coverage(output, covered=False) + try: + result = utils.get_recursively(value, package_name)[0] + except IndexError: + print('Error obtaining result.') + + elif 'errors' in output: + for err in output['errors']: + message = err['message'] + row = err['location']['row'] + code = err['code'] + result.append("{0}: {1}: {2} ".format(row, code, message)) + + return jsonify({"result":result, "coverage":covered_lines, "no_coverage":non_covered_lines, "query_eval_ns":query_eval_ns}) @app.context_processor def versions(): diff --git a/config.py b/config.py new file mode 100644 index 0000000..246c830 --- /dev/null +++ b/config.py @@ -0,0 +1,8 @@ +# Buitins that should never be executed (Security) +# Add any potentially dangerous functions here. +# The full list of builtins can be found at: https://www.openpolicyagent.org/docs/latest/policy-reference/#built-in-functions + +RESTRICTED_BUILTINS = [ + # 'http.send', + # 'opa.runtime' +] \ No newline at end of file diff --git a/core/utils.py b/core/utils.py index 0b1663c..8ca582b 100755 --- a/core/utils.py +++ b/core/utils.py @@ -1,12 +1,22 @@ import os import json import tempfile +import config tempfile.tempdir = "./temp" def run_cmd(cmd): return os.popen(cmd).read() +def check_security_policy(policy): + allowed = False + for line in policy.splitlines(): + if not line.startswith('#'): + for key in config.RESTRICTED_BUILTINS: + if key in line.strip(): + allowed = key + return allowed + def get_recursively(search_dict, field): fields_found = [] @@ -31,10 +41,10 @@ def get_recursively(search_dict, field): def get_coverage(result, covered=True): coverage = [] key = 'covered' - + if not covered: key = 'not_covered' - + files = result.get('coverage', {}).get('files', []) for f in files: if key in files[f]: @@ -45,6 +55,9 @@ def get_coverage(result, covered=True): return coverage +def get_query_eval_ns(result): + return result.get('metrics', {}).get('timer_rego_query_eval_ns', None) + def get_package_name(text): key_name = None for line in text.splitlines(): @@ -85,9 +98,9 @@ def opa_evaluate(policy, inputs, data=' ', coverage=False): res = None if data: - res = run_cmd(f"./bin/opa eval -d {data_file} -i {input_file} -d {policy_file} data {args}") + res = run_cmd(f"./bin/opa eval -d {data_file} -i {input_file} -d {policy_file} --profile data {args}") else: - res = run_cmd(f"./bin/opa eval -i {input_file} -d {policy_file} data {args}") + res = run_cmd(f"./bin/opa eval -i {input_file} -d {policy_file} --profile data {args}") try: res = json.loads(res) diff --git a/templates/index.html b/templates/index.html index 3e10ecf..e406eff 100755 --- a/templates/index.html +++ b/templates/index.html @@ -6,7 +6,7 @@ - Open Policy Agent Sandbox + Open Policy Agent Alfred @@ -85,7 +85,7 @@
- +
@@ -93,7 +93,7 @@
-
+
@@ -101,10 +101,8 @@
- + - -