Merge pull request #5592 from inspec/vasundhara/add_OPA_support

Add support for OPA: add resource opa_cli and opa_api
This commit is contained in:
Clinton Wolfe 2021-07-27 22:47:23 -04:00 committed by GitHub
commit e1f63cc7a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 319 additions and 0 deletions

View file

@ -0,0 +1,69 @@
+++
title = "opa_api resource"
draft = false
gh_repo = "inspec"
platform = "os"
[menu]
[menu.inspec]
title = "opa_api"
identifier = "inspec/resources/os/opa_api.md mongodb_conf resource"
parent = "inspec/resources/os"
+++
Use the `opa_api` Chef InSpec audit resource to query Open Policy Agent (OPA) using the OPA URL and data.
## Availability
### Installation
This resource is distributed along with Chef InSpec itself. You can use it automatically.
## Syntax
An `opa_api` resource block declares OPA policy configurations that can be tested.
describe opa_api(url: "localhost:8181/v1/data/example/violation", data: "input.json") do
its(["result"]) { should eq 'value' }
end
where
- `'url'` specifies the url of the OPA server on which OPA is running.
- `'data'` specifies the json formatted data or json file.
- `its(["returned_result"]) { should eq 'expected_result' }` compares the results of the query against the expected result in the test.
## parameters
The `opa_api` resource InSpec resource requires a `url` and `data` as a JSON file or a string in JSON format.
### `url` _(required)_
The URL of the OPA API server.
### `data` _(required)_
An OPA query as a JSON data file or a string in JSON format.
## Examples
The following examples show how to use this Chef InSpec audit resource.
describe opa_api(url: "localhost:8181/v1/data/example/allow", data: "input.json") do
its(["result"]) { should eq true }
its("allow") { should eq "true" }
end
The above example shows how the `allow` value can be fetched in two ways.
## Matchers
For a full list of available matchers, please visit our [matchers page](/inspec/matchers/).
## Properties
### allow
The `allow` property checks if specific input is as per the policy defined in OPA. If `allow` is not defined in the policy file then this matcher will not work.
its('allow') { should eq 'value' }

View file

@ -0,0 +1,78 @@
+++
title = "opa_cli resource"
draft = false
gh_repo = "inspec"
platform = "os"
[menu]
[menu.inspec]
title = "opa_cli"
identifier = "inspec/resources/os/opa_cli.md opa_cli resource"
parent = "inspec/resources/os"
+++
Use the `opa_cli` Chef InSpec audit resource to query Open Policy Agent (OPA) using an OPA policy file, a data file, and a query.
## Availability
### Installation
This resource is distributed along with Chef InSpec itself. You can use it automatically.
## Syntax
An `opa_cli` resource block declares OPA policy configurations that can be tested.
describe opa_cli(policy: "example.rego", data: "input.json", query: "data.example.allow") do
its(["result"]) { should eq "value" }
end
where
- `data` specifies the json formatted input data or file path.
- `policy` the path to policy file.
- `query` specifies the query to be run.
- `its(["result"]) { should eq "value" }` compares the results of the query against the expected result in the test
## parameters
The `opa_cli` resource InSpec resource accepts `policy`, `data`, `query`, and `opa_executable_path` as parameters.
### `policy` _(required)_
The path to the OPA policy file.
### `data` _(required)_
An OPA query as a JSON data file or a string in JSON format.
### `query` _(required)_
The query to be evaluated against policy and input data.
### `opa_executable_path`
This is the full path to the OPA binary or EXE file used for running the OPA CLI or OPA commands. By default it will consider that the path is added in PATH variable.
## Examples
The following examples show how to use this Chef InSpec audit resource:
describe opa_cli(query: "data.example.allow", policy: "example.rego", data: "input.json", opa_executable_path: "./opa") do
its(["result", 0, "expressions", 0, "value"]) { should eq true }
its("allow") { should eq "true" }
end
The above example shows how the `allow` value can be fetched in two ways.
## Matchers
For a full list of available matchers, please visit our [matchers page](/inspec/matchers/).
## Properties
### allow
The `allow` property checks if specific input is as per the policy defined in OPA. If `allow` is not defined in the policy file then this matcher will not work.
its('allow') { should eq 'value' }

View file

@ -83,6 +83,8 @@ require "inspec/resources/nginx_conf"
require "inspec/resources/npm"
require "inspec/resources/ntp_conf"
require "inspec/resources/oneget"
require "inspec/resources/opa_cli"
require "inspec/resources/opa_api"
require "inspec/resources/oracledb_session"
require "inspec/resources/os"
require "inspec/resources/os_env"

View file

@ -0,0 +1,23 @@
require "inspec/resources/json"
module Inspec::Resources
class Opa < JsonConfig
name "opa"
supports platform: "unix"
supports platform: "windows"
attr_reader :result
def initialize(content)
@content = content
super({ content: @content })
end
private
def parse(content)
@content = YAML.load(content)
rescue => e
raise Inspec::Exceptions::ResourceFailed, "Unable to parse OPA query output: #{e.message}"
end
end
end

View file

@ -0,0 +1,39 @@
require "inspec/resources/opa"
module Inspec::Resources
class OpaApi < Opa
name "opa_api"
supports platform: "unix"
supports platform: "windows"
def initialize(opts = {})
@url = opts[:url] || nil
@data = opts[:data] || nil
fail_resource "OPA url and data are mandatory." if @url.nil? || @url.empty? || @data.nil? || @data.empty?
@content = load_result
super(@content)
end
def allow
@content["result"]
end
def to_s
"OPA api"
end
private
def load_result
raise Inspec::Exceptions::ResourceFailed, "#{resource_exception_message}" if resource_failed?
result = inspec.command("curl -X POST #{@url} -d @#{@data} -H 'Content-Type: application/json'")
if result.exit_status == 0
result.stdout.gsub("\n", "")
else
error = result.stdout + "\n" + result.stderr
raise Inspec::Exceptions::ResourceFailed, "Error while executing OPA query: #{error}"
end
end
end
end

View file

@ -0,0 +1,43 @@
require "inspec/resources/opa"
module Inspec::Resources
class OpaCli < Opa
name "opa_cli"
supports platform: "unix"
supports platform: "windows"
def initialize(opts = {})
@opa_executable_path = opts[:opa_executable_path] || "opa" # if this path is not provided then we will assume that it's been set in the ENV PATH
@policy = opts[:policy] || nil
@data = opts[:data] || nil
@query = opts[:query] || nil
if (@policy.nil? || @policy.empty?) || (@data.nil? || @data.empty?) || (@query.nil? || @query.empty?)
fail_resource "OPA policy, data and query are mandatory."
end
@content = load_result
super(@content)
end
def allow
@content["result"][0]["expressions"][0]["value"] if @content["result"][0]["expressions"][0]["text"].include?("allow")
end
def to_s
"OPA cli"
end
private
def load_result
raise Inspec::Exceptions::ResourceFailed, "#{resource_exception_message}" if resource_failed?
result = inspec.command("#{@opa_executable_path} eval -i '#{@data}' -d '#{@policy}' '#{@query}'")
if result.exit_status == 0
result.stdout.gsub("\n", "")
else
error = result.stdout + "\n" + result.stderr
raise Inspec::Exceptions::ResourceFailed, "Error while executing OPA query: #{error}"
end
end
end
end

1
test/fixtures/cmd/opa-api-result vendored Normal file
View file

@ -0,0 +1 @@
{"result":["ci","busybox"]}

16
test/fixtures/cmd/opa-result vendored Normal file
View file

@ -0,0 +1,16 @@
{
"result": [
{
"expressions": [
{
"value": false,
"text": "data.example.allow",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}

View file

@ -566,6 +566,8 @@ class MockLoader
"semodule -lfull" => cmd.call("semodule-lfull"),
"semanage boolean -l -n" => cmd.call("semanage-boolean"),
"Get-ChildItem -Path \"C:\\Program Files\\MongoDB\\Server\" -Name" => cmd.call("mongodb-version"),
"opa eval -i 'input.json' -d 'example.rego' 'data.example.allow'" => cmd.call("opa-result"),
"curl -X POST localhost:8181/v1/data/example/violation -d @v1-data-input.json -H 'Content-Type: application/json'" => cmd.call("opa-api-result"),
}
if @platform && (@platform[:name] == "windows" || @platform[:name] == "freebsd")

View file

@ -0,0 +1,23 @@
require "helper"
require "inspec/resource"
require "inspec/resources/opa_api"
describe "Inspec::Resources::OpaApi" do
it "verify opa api query result parsing" do
resource = load_resource("opa_api", url: "localhost:8181/v1/data/example/violation", data: "v1-data-input.json")
_(resource.params["result"]).must_equal %w{ ci busybox }
_(resource.params["result"]).must_include "ci"
end
it "fails when url or data is nil." do
resource = load_resource("opa_api")
_(resource.resource_failed?).must_equal true
_(resource.resource_exception_message).must_equal "OPA url and data are mandatory."
end
it "fails when url or data is empty." do
resource = load_resource("opa_api", url: "", data: "")
_(resource.resource_failed?).must_equal true
_(resource.resource_exception_message).must_equal "OPA url and data are mandatory."
end
end

View file

@ -0,0 +1,23 @@
require "helper"
require "inspec/resource"
require "inspec/resources/opa_cli"
describe "Inspec::Resources::OpaCli" do
it "verify opa eval query result parsing" do
resource = load_resource("opa_cli", policy: "example.rego", data: "input.json", query: "data.example.allow")
_(resource.params["result"][0]["expressions"][0]["value"]).must_equal false
_(resource.allow).must_equal false
end
it "fails when policy, data or query is nil." do
resource = load_resource("opa_cli")
_(resource.resource_failed?).must_equal true
_(resource.resource_exception_message).must_equal "OPA policy, data and query are mandatory."
end
it "fails when empty string passed for options policy, data or query." do
resource = load_resource("opa_cli", policy: "", data: "", query: "")
_(resource.resource_failed?).must_equal true
_(resource.resource_exception_message).must_equal "OPA policy, data and query are mandatory."
end
end