Forseti Security: custom rules

Rafael Alvarez
Google Cloud - Community
4 min readMar 11, 2020

There is no denying that Forseti has a great architecture to back it up. Code is well written, following most industry standards and leaving enough clues for everyone to extend it.

Having said that, when tasked with the chore of “build your own policy” the documentation fails short.

In order to write your own custom policies, Forseti provides us with two alternatives:

  1. Use a traditional “structured” language (namely Python): For this to work, you need to extend Forseti’s source code and write scanners and enforcers that build up the logic required.
  2. Write policies in a logical programming language (ex. prolog): OPA’s rego: A great starting point to use rego policies is the forseti-security/policy-library project.
Forseti custom rules deployment architecture using Python and rego

In short, the decision will fall down to your experience with prolog and the semantic complexity that the policy will add to the project.

In either way, to build the policy you need to start with the inventory provided by the Cloud Asset Inventory (CAI). In case that a specific resource is not exposed by the CAI you need to get in touch with GCP via github or email in order to request for it to be included in future releases.

Extending Forseti to include custom policies written in Python:

In order to start writing a custom scanner, the code should be distributed according to the following structure:

forseti-security/
├── configs
│ └── server
│ └── forseti_conf_server.yaml
├── google
│ └── cloud
│ └── forseti
│ ├── ...
│ └── scanner
│ ├── audit
│ │ ├── ...
│ │ └── foobar_rules_engine.py
│ ├── ...
│ ├── scanner_requirements_map.py
│ └── scanners
│ ├── ...
│ └── foobar_scanner.py
├── ...
└── rules
├── ...
└── foobar_rules.yaml

It takes five steps to have your custom policy within Forseti. In order to better illustrate, let’s create an example Scanner called foobar_scanner.

  1. Write a custom class containing a wrapper for an audit engine (see step 3) and add it to the scanners folder within Forseti Source Code: foobar_scanner.py
  2. Add a custom rule mapping under the rules folder: foobar_rules.py
  3. Write a security audit engine under the audit folder: foobar_rules_engine.py
  4. Add custom map under scanner_requirements_map.py
  5. Register the previously created custom map under forseti_conf_server.yaml.

foobar_scanner.py:

"""Scanner for the Foo Bar rules engine."""
import collections
...
from google.cloud.forseti.scanner.audit import foobar_rules_engine
...
class FoobarScanner(base_scanner.BaseScanner):
"""Scanner for FooBar acls."""
def __init__(self, global_configs, scanner_configs, service_config,
...
self.rules_engine = foobar_rules_engine.FoobarRulesEngine(rules_file_path=self.rules, snapshot_timestamp=self.snapshot_timestamp)
...
def _find_violations(self, bigquery_acl_data):
...
for data in foobar_acl_data:
violations = self.rules_engine.find_violations(
data.parent_project, data.foobar_acl)
LOGGER.debug(violations)
all_violations.extend(violations)
return all_violations
...
def run(self):
"""Runs the data collection."""
foobar_acl_data = self._retrieve()
all_violations = self._find_violations(foobar_acl_data)
self._output_results(all_violations)

foobar_rules.yaml:

rules:
# Note: please use the id of the resource (such as organization id,
# folder id, project id, etc.) when specifying the resource ids.
- name: FooBar rule to search for public datasets
mode: blacklist
resource:
- type: organization
resource_ids:
- {ORGANIZATION_ID}
dataset_ids: ['*']
...

foobar_rules_engine.py:

"""Rules engine for Foo Bar data sets."""
...
class FoobarRulesEngine(bre.BaseRulesEngine):
"""Rules engine for Foo Bar data sets"""
...
def find_violations(self, parent_project, fb_acl, force_rebuild=False):
...
violations = self.rule_book.find_violations(parent_project, fb_acl)
...
class FoobarRuleBook(bre.BaseRuleBook):
"""The RuleBook for Foo Bar dataset resources."""
...

scanner_requirements_map.py:

...
REQUIREMENTS_MAP = {
...
'foobar':
{'module_name': 'foobar_scanner',
'class_name': 'FoobarScanner',
'rules_filename': 'foobar_rules.yaml'},
...
}

forseti_conf_server.yaml:

...
scanners:
- name: foobar
enabled: true
...
...

Using rego to build custom policies:

If functional programming is your thing then there are a couple of steps that are needed in order to have your custom policies up and running within Forseti.

The starting point is to have a tree that mimics the following structure:

policy-library/
├── lib
│ ├── constraints.rego
│ ├── util.rego
│ └── util_test.rego
└── policies
├── constraints
│ └── constraint_to_validate.yaml
└── templates
├── ...
└── gcp-{resource}-{feature}-{version}.yaml

Now, once you have the structure in place (and added to the root level bucket where the Forseti server configuration is stored), there are three simple steps required to enable rego custom rules:

  1. Write the constraint_to_validate.yaml which includes whitelist and blacklist information on how to compute the policy.
  2. Write the template gcp-{resource}-{feature}-{version}.yaml where the rego policy is stored.
  3. Enable the config_validator scanner at the forseti_conf_server.yaml.

constraint_to_validate.yaml:

...
apiVersion: constraints.gatekeeper.sh/v1alpha1
kind: GCPResourceFeatureConstraintV1
metadata:
name: constraint_to_validate
spec:
severity: high
parameters:
mode: "whitelist"
instances:
- //compute.googleapis.com/projects/project-name/zones/zone-name/instances/instance-name

gcp-resource-feature-v1.yaml:

...
apiVersion: templates.gatekeeper.sh/v1alpha1
kind: ConstraintTemplate
metadata:
name: gcp-resource-feature-v1
...
spec:
crd:
spec:
names:
kind: GCPResourceFeatureConstraintV1
plural: gcpresourcefeatureconstraintsv1
validation:
openAPIV3Schema:
properties:
mode:
type: string
enum: [blacklist, whitelist]
instances:
type: array
items: string
targets:
validation.gcp.forsetisecurity.org:
rego: |
package templates.gcp.GCPResourceFeatureConstraintV1
import data.validator.gcp.lib as lib
...

deny[{
"msg": message,
"details": metadata,
}] {
constraint := input.constraint
lib.get_constraint_params(constraint, params)
asset := input.asset
asset.asset_type == "compute.googleapis.com/Instance"
...
}

forseti_conf_server.yaml:

...
scanners:
...
- name: config_validator
enabled: true
...
...

Conclusion:

There are many aspects to consider when choosing a methodology to write custom rules for Forseti (or any other policy enforcer for that matter).

Programming language is just one of them, the footprint that you leave behind is another.

Finally you also have to account for support and maintainability of such rules over the long run.

OPA’s rego is a well known language across security industry and as such it should be the preferable method. On the other hand, OPA is a third party component injected into Forseti and as such there’s no much room on how and when to execute each of the custom policies (basically it’s either run them all or not run them at all).

Python is a well structured language for rapid prototyping, meaning it should be easier to run custom rules on. On the other hand, maintainability and portability, given that you will be extending foresti core components, might be an issue.

In other words, if you have the knowledge on logic programming paradigm and are eager to learn rego (or already know it) then by all means that is the way to go. If you need a POC for rapid prototyping and validation then Python might be better suited (again, remember you have to extend Forseti’s repository with your own custom code).

Either way, happy coding.

--

--

Rafael Alvarez
Google Cloud - Community

Proactive and highly responsible DevOps SME with vast experience in Information Technology and Computer Security along with background in Operational Research.