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

docs: add initial design document #1

Merged
merged 11 commits into from
Oct 25, 2024
265 changes: 265 additions & 0 deletions docs/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
# Design
martinohmann marked this conversation as resolved.
Show resolved Hide resolved

This document explains the purpose of the `sops-compliance-checker` and also
documents the functional and non-functional requirements we have, as well as
the details about design decisions we took. It assumes familarity with
[SOPS][sops] and the concepts that it uses.

martinohmann marked this conversation as resolved.
Show resolved Hide resolved
## Purpose

Check SOPS files for correct and compliant usage without decrypting the SOPS
files to ensure that all SOPS files are configured in the desired fashion. The
goal is to provide a security linter that safeguards the security of the data
protected by the SOPS files against common mistakes and against malicious
configurations.

## Background

SOPS supports encrypting files using one or more "trust anchors" (the
encryption keys used to encrypt a SOPS file). The security of the SOPS file is
primarily governed by the security and selection of suitable. Especially in
enterprise contexts it is important to enforce some rules around the trust
anchors that are in use. Some questions that to be answered could be:

- How can we ensure that any given SOPS file is encrypted using a set of
required trust anchors?
- How can we detect if non-approved trust anchors are used?
- How can we prevent the usage of certain trust anchors in the presence of
others (mutual exclusivity)?

To answer these questions for any given SOPS file, the
`sops-compliance-checker` needs to be flexible enough to process a set of
user-defined rules which indicate success or failure.

## Functional Requirements

The high-level functionality of the compliance checker can be summarized like
this:

- Recursively scan a directory tree (such as a Git repository) for SOPS
encrypted files.
- It should support all file formats supported by SOPS itself.
- Extract trust anchors from SOPS encrypted files to match them against a set
of user-defined rules.
- Report check results back to the user.

The rules engine needs to support the following functionality:

- Matching of a trust anchor ("match")
- **Example A**: The trust anchor is an AWS KMS Key with the ARN
`arn:aws:kms:eu-west-1:123456789012:alias/my-team`.
martinohmann marked this conversation as resolved.
Show resolved Hide resolved
- **Example B**: The trust anchor matches the regular expression
`^arn:aws:kms:eu-(central|west)-1:123456789012:.*$`.
martinohmann marked this conversation as resolved.
Show resolved Hide resolved
- Matching of all rules ("and" / "all of")
- **Example**: The SOPS file must be encrypted with the AGE disaster recovery
key as well as the keys of the owner of the file and the deploy key for the
CI/CD to be able to decrypt it.
- Matching of any rule ("or" / "any of")
- **Example**: The SOPS file must be encrypted using the key of one or more
teams responsible for the software component.
- Matching of exactly one rule ("xor" / "one of")
- **Example**: The trust anchors can only contain the deploy key for
production **or** staging, but not both, to prevent secret sharing between
environments.
- Inversion of match behaviour ("not")
- **Example**: There's an explicit key that should not be part of the trust
anchors.
- Reject excess trust anchors (not matched by any rule):
- **Example**: Developers should not be allowed to use additional encryption
keys apart from the keys managed by the company within any given SOPS file.
This protects against employees still retaining access to secrets after
they left the company.

It must be possible to nest these rule arbitrarily deep to allow building
complex match pattern with different dependencies between each other.

## Non-functional Requirements

- The compliance checker should be packaged as a container image for easy
integration into CI/CD pipelines.

## Design Choices

### Data Model

Based on the functional requirements, the [JSON schema][config-schema] for the rule
configuration could look like this (in YAML for better readability):

```yaml
---
$schema: https://json-schema.org/draft-07/schema
additionalProperties: false
definitions:
rule:
additionalProperties: false
description: Defines a single matching rule.
oneOf:
- not:
required: [anyOf, match, not, oneOf]
required: [allOf]
- not:
required: [allOf, match, not, oneOf]
required: [anyOf]
- not:
required: [allOf, anyOf, not, oneOf]
required: [match]
- not:
required: [allOf, anyOf, match, oneOf]
required: [not]
- not:
required: [allOf, anyOf, match, not]
required: [oneOf]
properties:
allOf:
$ref: '#/definitions/rules'
description: Asserts that all of the nested rules match.
description:
description: Rule description displayed as context to the user.
type: string
anyOf:
$ref: '#/definitions/rules'
description: Asserts that at least one of the nested rules matches.
match:
description: >-
Defines the pattern to match trust anchors against. Can be an exact
string or a regular expression.
type: string
not:
$ref: '#/definitions/rule'
description: Inverts the matching behaviour of a rule.
oneOf:
$ref: '#/definitions/rules'
description: Asserts that exactly one of the nested rules matches.
url:
description: URL to documentation of the rule.
type: string
type: object
rules:
items:
$ref: '#/definitions/rule'
type: array
description: Schema of the sops-compliance-checker configuration file
properties:
rules:
$ref: '#/definitions/rules'
description: A list of matching rules.
title: sops-compliance-checker configuration
type: object
```

This is highly influenced by the [JSON Schema Specification][jsonschema-spec]
itself to allow for great flexibility in the rule composition.

### Configuration Example

Given the following requirements:

- The SOPS file must contain all necessary approved trust anchors:
- AGE key `age1u79ltfzz5k79ex4mpl3r76p2532xex4mpl3z7vttctudr6gedn6ex4mpl3`
used for offline disaster recovery.
- AWS KMS keys of **at least one team** owning the component. Additional team
keys are allowed, as long as they are part of the list of approved trust
anchors.
- AWS KMS keys used for deployment via CI/CD for **exactly one** target
environment (development/staging/production).
- AWS KMS keys **must come in pairs** of two different regions to increase availability.
- Any excess trust anchors not matching any of these rules **must be rejected**.

One possible configuration to acheive this could be:

```yaml
---
rules:
# All rules must match. This will automatically reject excess trust anchors
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should have a default (or something) key that specifies what happens if the rules don't match? Not sure though

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default would be to fail the validation if a key is encountered that does not match any of the rules.

Under "Other Considerations" I mentioned a warning mode that could be implemented to control the behaviour. I'm still undecided whether this would be a global configuration or if it should be supported at the rule level as well (e.g. allow certain rules to fail, but emit a warning).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, maybe the mode should be outside of the rules file.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note: the schema is actually describing the configuration file of the checker, not just the rules. "Outside the rule" would rather mean "in a section other than rules" within the same file here.

# not matching any of the nested rules.
- allOf:
- description: Disaster recovery key must be present.
match: age1u79ltfzz5k79ex4mpl3r76p2532xex4mpl3z7vttctudr6gedn6ex4mpl3
- anyOf:
- allOf:
- match: arn:aws:kms:eu-central-1:123456789012:alias/team-foo
- match: arn:aws:kms:eu-west-1:123456789012:alias/team-foo
description: Regional keys of team-foo.
- allOf:
- match: arn:aws:kms:eu-central-1:123456789012:alias/team-bar
- match: arn:aws:kms:eu-west-1:123456789012:alias/team-bar
description: Regional keys of team-bar.
description: The AWS KMS key pair of at least one team must be present.
- oneOf:
- allOf:
- match: arn:aws:kms:eu-central-1:123456789012:alias/production-cicd
- match: arn:aws:kms:eu-west-1:123456789012:alias/production-cicd
description: Regional production keys.
- allOf:
- match: arn:aws:kms:eu-central-1:123456789012:alias/staging-cicd
- match: arn:aws:kms:eu-west-1:123456789012:alias/staging-cicd
description: Regional staging keys.
description: >-
The AWS KMS key pair of exactly one deployment target environment
must be present.
```

### Output

To be as helpful as possible to user, the output should make it clear what the
problem is. It should include information about:

- The SOPS file that failed the compliance check.
- Display information about expected, but missing trust anchors ("any of" / "all of").
- Display information about conflicting trust anchors ("one of").
- Display information about explicitly denied trust anchors ("not").
- Display the trust anchors that did not match any rule.
- Ideally, include context about the line and column in the SOPS file where the
check failed.
- Optionally provide guidance how to make the file compliant with the rules.
martinohmann marked this conversation as resolved.
Show resolved Hide resolved

#### Output formats

The compliance checker should support at least textual output intended for
display to users as well as machine readable output in the [SARIF format][sarif].

### Out of Scope

The compliance checker only looks at the trust anchors found in the SOPS
metadata without actually having access to the encryption keys. Because of
that:

- It does not decrypt the contents of the SOPS files.
- It does not check for data corruption within SOPS files. SOPS already does
this via a MAC signature.
- It does not assess the security of the trust anchors in use. Checking if the
encryption keys of any of the referenced trust anchors were leaked to the
internet is out of scope.
- It does not scan the directory tree for plaintext secrets. There are plenty
of other tools out there that already do this, such as [Gitleaks][gitleaks],
[Secretlint][secretlint] or [TruffleHog][trufflehog].

## Other Considerations

There are some potential optional features that could be supported by the
compliance checker:

- **Warning mode**: An option to mark certain rules as soft-errors, which emit
a warning instead of failing the check.
- **Ignore pattern**: Support honoring ignore files, e.g. do not check files
matching the pattern in `.gitignore`. Another use case may be to explicitly
exclude certain directories, such as those containing test data.
- **Remote rule lookup**: To manage organization-wide rules it might be useful
to have the option to read a rules file from a remote location for central
management.
- **Matching n-of**: Support for matching lists of `m` trust anchors where `n`
must match the rules, with `n <= m`.
- **Configuration extension**: Support for importing rules from existing
configuration files into another one.
- **More rule metadata**: A rule configuration could carry more metadata like a
URL to internal documentation describing the rationale behind it. Other
options could be tags or keywords to enable better grouping of errors in the
output based on context.

[config-schema]: ../schema.json
[gitleaks]: https://github.com/gitleaks/gitleaks
[jsonschema-spec]: https://json-schema.org/draft/2020-12/json-schema-core
[sarif]: https://sarifweb.azurewebsites.net/
[secretlint]: https://github.com/secretlint/secretlint
[sops]: https://github.com/getsops/sops
[trufflehog]: https://github.com/trufflesecurity/trufflehog
63 changes: 63 additions & 0 deletions schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"$schema": "https://json-schema.org/draft-07/schema",
"additionalProperties": false,
"definitions": {
"rule": {
"additionalProperties": false,
"description": "Defines a single matching rule.",
"oneOf": [
{ "not": { "required": ["anyOf", "match", "not", "oneOf"] }, "required": ["allOf"] },
{ "not": { "required": ["allOf", "match", "not", "oneOf"] }, "required": ["anyOf"] },
{ "not": { "required": ["allOf", "anyOf", "not", "oneOf"] }, "required": ["match"] },
{ "not": { "required": ["allOf", "anyOf", "match", "oneOf"] }, "required": ["not"] },
{ "not": { "required": ["allOf", "anyOf", "match", "not"] }, "required": ["oneOf"] }
],
"properties": {
"allOf": {
"$ref": "#/definitions/rules",
"description": "Asserts that all of the nested rules match."
},
"description": {
"description": "Rule description displayed as context to the user.",
"type": "string"
},
"anyOf": {
"$ref": "#/definitions/rules",
"description": "Asserts that at least one of the nested rules matches."
},
"match": {
"description": "Defines the pattern to match trust anchors against. Can be an exact string or a regular expression.",
"type": "string"
},
"not": {
"$ref": "#/definitions/rule",
"description": "Inverts the matching behaviour of a rule."
},
"oneOf": {
"$ref": "#/definitions/rules",
"description": "Asserts that exactly one of the nested rules matches."
},
"url": {
"description": "URL to documentation of the rule.",
"type": "string"
}
},
"type": "object"
},
"rules": {
"items": {
"$ref": "#/definitions/rule"
},
"type": "array"
}
},
"description": "Schema of the sops-compliance-checker configuration file",
"properties": {
"rules": {
"$ref": "#/definitions/rules",
"description": "A list of matching rules."
}
},
"title": "sops-compliance-checker configuration",
"type": "object"
}