# IsOPS: **Is** **OP**erations **S**ecure
![release](https://img.shields.io/github/v/release/lorenzophys/isops)
[![codecov](https://codecov.io/gh/lorenzophys/isops/branch/main/graph/badge.svg?token=7RQ5P3X22D)](https://codecov.io/gh/lorenzophys/isops)
![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/lorenzophys/isops/test-workflow.yml?branch=main&label=tests)
![pver](https://img.shields.io/pypi/pyversions/isops)
![MIT](https://img.shields.io/github/license/lorenzophys/isops)
```ascii
__/\\\\\\\\\\\____________________/\\\\\_______/\\\\\\\\\\\\\_______/\\\\\\\\\\\___
_\/////\\\///___________________/\\\///\\\____\/\\\/////////\\\___/\\\/////////\\\_
_____\/\\\____________________/\\\/__\///\\\__\/\\\_______\/\\\__\//\\\______\///__
_____\/\\\______/\\\\\\\\\\__/\\\______\//\\\_\/\\\\\\\\\\\\\/____\////\\\_________
_____\/\\\_____\/\\\//////__\/\\\_______\/\\\_\/\\\/////////_________\////\\\______
_____\/\\\_____\/\\\\\\\\\\_\//\\\______/\\\__\/\\\_____________________\////\\\___
_____\/\\\_____\////////\\\__\///\\\__/\\\____\/\\\______________/\\\______\//\\\__
__/\\\\\\\\\\\__/\\\\\\\\\\____\///\\\\\/_____\/\\\_____________\///\\\\\\\\\\\/___
_\///////////__\//////////_______\/////_______\///________________\///////////_____
```
IsOPS (**Is** **OP**erations **S**ecure) is a minimal command line utility that helps you ensure that your secrets are encrypted correctly with [sops](https://github.com/mozilla/sops) before committing them. `isops` will read your configuration files, will scan all your secrets and alerts you if it finds any key that should be encrypted but it's not.
## Installation
You can install `isops` via `pip`:
```console
user@laptop:~$ pip install isops
```
The CLI is minimal:
```console
user@laptop:~$ isops
Usage: isops [OPTIONS] PATH
Utility to ensure all SOPS secrets are encrypterd.
Options:
-s, --summary Print a summary at the end of the checks.
-h, --help Show this message and exit.
-v, --version Show the version and exit.
-r, --config-regex TEXT The regex that matches all the config files to use.
[required]
```
You must provide a directory to scan and a regex that matches all the sops configuration files.
## How it works?
`isops` is called with a directory and a regex. Then:
1. It finds the config files using the provided regex.
2. For each rule in `creation_rules` it finds the files according to the `path_regex`.
3. For each file found, it scans all the keys, no matter how nested the yaml is, in search for those keys that match the `encrypted_regex`.
4. For each matched key, it checks if the associated value matches the sops regex `"^ENC\[AES256_GCM,data:(.+),iv:(.+),tag:(.+),type:(.+)\]"`.
If the config file doesn't provide a `path_regex` or a `encrypted_regex`, the default values are, respectively, `".ya?ml$"` and `""`.
## Usage example
Suppose you have this situation:
```text
example
├── .sops.yaml
└── secret.yaml
```
A `.sops.yaml`:
```yaml
creation_rules:
- path_regex: (.*)?secret.yaml$
encrypted_regex: "^(data|stringData)$"
pgp: "FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4"
```
and a `secret.yaml`:
```yaml
apiVersion: v1
data:
key: aGhkZDg4OGRoODRmaDQ4ZmJlbnNta21rbHdtc2k4
kind: Secret
metadata:
name: api-key
type: Opaque
```
If you run `isops` you get a warning because your secret is not encrypted:
```console
user@laptop:~$ isops ./example --config-regex .sops.yaml
Found config file: example/.sops.yaml
example/secret.yaml::key [UNSAFE]
user@laptop:~$ echo $?
1
```
If the same secret is encrypted with sops:
```yaml
apiVersion: v1
data:
key: ENC[AES256_GCM,data:iCBh27Ort/dNVhP9D4y/AqI5d78U+2EHtHPX9u0/s9ANhA2VeqKSOQ==,iv:HkQVUgB6nvN3TU355K/PTU2NroahHAdoJhzJdgZFMwo=,tag:ayNppVmYJ/MLGrW9RtjV1A==,type:str]
kind: Secret
metadata:
name: api-key
type: Opaque
sops:
etc...
```
then `isops` will give you the green light:
```console
user@laptop:~$ isops ./example --config-regex .sops.yaml
Found config file: example/.sops.yaml
example/secret.yaml::key [SAFE]
user@laptop:~$ echo $?
0
```
## Another example
You can have a more complicated scenario where there are multiple sops configuration files, multiple environments and lots of secrets.
Suppose you have this situation:
```text
example
├── .sops
│ ├── sops-dev.yaml
│ └── sops-prod.yaml
├── dev
│ ├── api-key-secret.yaml <- Encrypted
│ ├── db-password-secret.yaml <- Encrypted
│ ├── deployment.yaml
│ └── service.yaml
└── prod
├── api-key-secret.yaml <- Not encrypted!
├── db-password-secret.yaml <- Encrypted
├── deployment.yaml
└── service.yaml
```
Then if you run `isops` you get:
```console
user@laptop:~$ isops example --config-regex "example/.sops/(.*).yaml$"
Found config file: example/.sops/sops-dev.yaml
Found config file: example/.sops/sops-prod.yaml
example/dev/db-password-secret.yaml::password [SAFE]
example/dev/api-key-secret.yaml::key [SAFE]
example/prod/db-password-secret.yaml::password [SAFE]
example/prod/api-key-secret.yaml::key [UNSAFE]
```
Sometimes the list of secret is very long: you can enable a small summary at the end with the `--summary` option:
```console
user@laptop:~$ isops example --config-regex "example/.sops/(.*).yaml$" --summary
Found config file: example/.sops/sops-dev.yaml
Found config file: example/.sops/sops-prod.yaml
example/dev/db-password-secret.yaml::password [SAFE]
example/dev/api-key-secret.yaml::key [SAFE]
example/prod/db-password-secret.yaml::password [SAFE]
example/prod/api-key-secret.yaml::key [UNSAFE]
---
Summary:
UNSAFE secret 'key' in 'example/prod/api-key-secret.yaml'
3 safe 1 unsafe
```
The previous example can be found in the `example` directory. The sample application was generated by [ChatGPT](https://chat.openai.com/chat) with the prompt: "Please, generate an example Kubernetes application with two secrets".
## Pre-commit hook
`isops` can be also used as a [pre-commit](https://pre-commit.com) hook. For example:
```yaml
repos:
- repo: https://github.com/lorenzophys/isops
rev: v0.2.0
hooks:
- id: isops
args:
- --config-regex=.sops/(.*).ya?ml$
- --summary
```
## License
This project is licensed under the **MIT License** - see the *LICENSE* file for details.
Raw data
{
"_id": null,
"home_page": "https://github.com/lorenzophys/isops",
"name": "isops",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.7,<4.0",
"maintainer_email": "",
"keywords": "isops,sops,secrets",
"author": "Lorenzo Maffioli",
"author_email": "lorenzo.maffioli@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/d0/7a/4db8adaa3a42cc3fadf663846a67d44c315fec87ed0c113102c770cca5d2/isops-0.2.0.tar.gz",
"platform": null,
"description": "# IsOPS: **Is** **OP**erations **S**ecure\n\n![release](https://img.shields.io/github/v/release/lorenzophys/isops)\n[![codecov](https://codecov.io/gh/lorenzophys/isops/branch/main/graph/badge.svg?token=7RQ5P3X22D)](https://codecov.io/gh/lorenzophys/isops)\n![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/lorenzophys/isops/test-workflow.yml?branch=main&label=tests)\n![pver](https://img.shields.io/pypi/pyversions/isops)\n![MIT](https://img.shields.io/github/license/lorenzophys/isops)\n\n```ascii\n__/\\\\\\\\\\\\\\\\\\\\\\____________________/\\\\\\\\\\_______/\\\\\\\\\\\\\\\\\\\\\\\\\\_______/\\\\\\\\\\\\\\\\\\\\\\___ \n _\\/////\\\\\\///___________________/\\\\\\///\\\\\\____\\/\\\\\\/////////\\\\\\___/\\\\\\/////////\\\\\\_ \n _____\\/\\\\\\____________________/\\\\\\/__\\///\\\\\\__\\/\\\\\\_______\\/\\\\\\__\\//\\\\\\______\\///__ \n _____\\/\\\\\\______/\\\\\\\\\\\\\\\\\\\\__/\\\\\\______\\//\\\\\\_\\/\\\\\\\\\\\\\\\\\\\\\\\\\\/____\\////\\\\\\_________ \n _____\\/\\\\\\_____\\/\\\\\\//////__\\/\\\\\\_______\\/\\\\\\_\\/\\\\\\/////////_________\\////\\\\\\______ \n _____\\/\\\\\\_____\\/\\\\\\\\\\\\\\\\\\\\_\\//\\\\\\______/\\\\\\__\\/\\\\\\_____________________\\////\\\\\\___ \n _____\\/\\\\\\_____\\////////\\\\\\__\\///\\\\\\__/\\\\\\____\\/\\\\\\______________/\\\\\\______\\//\\\\\\__ \n __/\\\\\\\\\\\\\\\\\\\\\\__/\\\\\\\\\\\\\\\\\\\\____\\///\\\\\\\\\\/_____\\/\\\\\\_____________\\///\\\\\\\\\\\\\\\\\\\\\\/___ \n _\\///////////__\\//////////_______\\/////_______\\///________________\\///////////_____\n\n```\n\nIsOPS (**Is** **OP**erations **S**ecure) is a minimal command line utility that helps you ensure that your secrets are encrypted correctly with [sops](https://github.com/mozilla/sops) before committing them. `isops` will read your configuration files, will scan all your secrets and alerts you if it finds any key that should be encrypted but it's not.\n\n## Installation\n\nYou can install `isops` via `pip`:\n\n```console\nuser@laptop:~$ pip install isops\n```\n\nThe CLI is minimal:\n\n```console\nuser@laptop:~$ isops\nUsage: isops [OPTIONS] PATH\n\n Utility to ensure all SOPS secrets are encrypterd.\n\nOptions:\n -s, --summary Print a summary at the end of the checks.\n -h, --help Show this message and exit.\n -v, --version Show the version and exit.\n -r, --config-regex TEXT The regex that matches all the config files to use.\n [required]\n```\n\nYou must provide a directory to scan and a regex that matches all the sops configuration files.\n\n## How it works?\n\n`isops` is called with a directory and a regex. Then:\n\n1. It finds the config files using the provided regex.\n2. For each rule in `creation_rules` it finds the files according to the `path_regex`.\n3. For each file found, it scans all the keys, no matter how nested the yaml is, in search for those keys that match the `encrypted_regex`.\n4. For each matched key, it checks if the associated value matches the sops regex `\"^ENC\\[AES256_GCM,data:(.+),iv:(.+),tag:(.+),type:(.+)\\]\"`.\n\nIf the config file doesn't provide a `path_regex` or a `encrypted_regex`, the default values are, respectively, `\".ya?ml$\"` and `\"\"`.\n\n## Usage example\n\nSuppose you have this situation:\n\n```text\nexample\n\u251c\u2500\u2500 .sops.yaml\n\u2514\u2500\u2500 secret.yaml\n```\n\nA `.sops.yaml`:\n\n```yaml\ncreation_rules:\n - path_regex: (.*)?secret.yaml$\n encrypted_regex: \"^(data|stringData)$\"\n pgp: \"FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4\"\n```\n\nand a `secret.yaml`:\n\n```yaml\napiVersion: v1\ndata:\n key: aGhkZDg4OGRoODRmaDQ4ZmJlbnNta21rbHdtc2k4\nkind: Secret\nmetadata:\n name: api-key\ntype: Opaque\n```\n\nIf you run `isops` you get a warning because your secret is not encrypted:\n\n```console\nuser@laptop:~$ isops ./example --config-regex .sops.yaml\nFound config file: example/.sops.yaml\nexample/secret.yaml::key [UNSAFE]\nuser@laptop:~$ echo $?\n1\n```\n\nIf the same secret is encrypted with sops:\n\n```yaml\napiVersion: v1\ndata:\n key: ENC[AES256_GCM,data:iCBh27Ort/dNVhP9D4y/AqI5d78U+2EHtHPX9u0/s9ANhA2VeqKSOQ==,iv:HkQVUgB6nvN3TU355K/PTU2NroahHAdoJhzJdgZFMwo=,tag:ayNppVmYJ/MLGrW9RtjV1A==,type:str]\nkind: Secret\nmetadata:\n name: api-key\ntype: Opaque\nsops:\n etc...\n\n```\n\nthen `isops` will give you the green light:\n\n```console\nuser@laptop:~$ isops ./example --config-regex .sops.yaml\nFound config file: example/.sops.yaml\nexample/secret.yaml::key [SAFE]\nuser@laptop:~$ echo $?\n0\n```\n\n## Another example\n\nYou can have a more complicated scenario where there are multiple sops configuration files, multiple environments and lots of secrets.\n\nSuppose you have this situation:\n\n```text\nexample\n\u251c\u2500\u2500 .sops\n\u2502 \u251c\u2500\u2500 sops-dev.yaml\n\u2502 \u2514\u2500\u2500 sops-prod.yaml\n\u251c\u2500\u2500 dev\n\u2502 \u251c\u2500\u2500 api-key-secret.yaml <- Encrypted\n\u2502 \u251c\u2500\u2500 db-password-secret.yaml <- Encrypted\n\u2502 \u251c\u2500\u2500 deployment.yaml\n\u2502 \u2514\u2500\u2500 service.yaml\n\u2514\u2500\u2500 prod\n \u251c\u2500\u2500 api-key-secret.yaml <- Not encrypted!\n \u251c\u2500\u2500 db-password-secret.yaml <- Encrypted\n \u251c\u2500\u2500 deployment.yaml\n \u2514\u2500\u2500 service.yaml\n```\n\nThen if you run `isops` you get:\n\n```console\nuser@laptop:~$ isops example --config-regex \"example/.sops/(.*).yaml$\"\nFound config file: example/.sops/sops-dev.yaml\nFound config file: example/.sops/sops-prod.yaml\nexample/dev/db-password-secret.yaml::password [SAFE]\nexample/dev/api-key-secret.yaml::key [SAFE]\nexample/prod/db-password-secret.yaml::password [SAFE]\nexample/prod/api-key-secret.yaml::key [UNSAFE]\n```\n\nSometimes the list of secret is very long: you can enable a small summary at the end with the `--summary` option:\n\n```console\nuser@laptop:~$ isops example --config-regex \"example/.sops/(.*).yaml$\" --summary\nFound config file: example/.sops/sops-dev.yaml\nFound config file: example/.sops/sops-prod.yaml\nexample/dev/db-password-secret.yaml::password [SAFE]\nexample/dev/api-key-secret.yaml::key [SAFE]\nexample/prod/db-password-secret.yaml::password [SAFE]\nexample/prod/api-key-secret.yaml::key [UNSAFE]\n---\nSummary:\nUNSAFE secret 'key' in 'example/prod/api-key-secret.yaml'\n3 safe 1 unsafe\n```\n\nThe previous example can be found in the `example` directory. The sample application was generated by [ChatGPT](https://chat.openai.com/chat) with the prompt: \"Please, generate an example Kubernetes application with two secrets\".\n\n## Pre-commit hook\n\n`isops` can be also used as a [pre-commit](https://pre-commit.com) hook. For example:\n\n```yaml\nrepos:\n - repo: https://github.com/lorenzophys/isops\n rev: v0.2.0\n hooks:\n - id: isops\n args:\n - --config-regex=.sops/(.*).ya?ml$\n - --summary\n```\n\n## License\n\nThis project is licensed under the **MIT License** - see the *LICENSE* file for details.\n",
"bugtrack_url": null,
"license": "",
"summary": "Utility to ensure SOPS secrets are encrypterd.",
"version": "0.2.0",
"split_keywords": [
"isops",
"sops",
"secrets"
],
"urls": [
{
"comment_text": "",
"digests": {
"md5": "2b284e57a0eb8f0c3a3c00bb9ba87890",
"sha256": "1b7caa14732887f065d67ef971de21f2f130501b402c2b30a1b7cc231cf04d98"
},
"downloads": -1,
"filename": "isops-0.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "2b284e57a0eb8f0c3a3c00bb9ba87890",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7,<4.0",
"size": 8153,
"upload_time": "2023-01-01T20:24:20",
"upload_time_iso_8601": "2023-01-01T20:24:20.873033Z",
"url": "https://files.pythonhosted.org/packages/6a/22/3a6a5c1651bff6b074796fe949903f1ad865b3ce1dd2e03b0256b12987f2/isops-0.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "f13c875688f88bf34f6ad24e82eaa57a",
"sha256": "51b70087f41b6aefa72f3dfb593fd08c4ecaa4dacd2af0b28fa861ce70d15f1f"
},
"downloads": -1,
"filename": "isops-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "f13c875688f88bf34f6ad24e82eaa57a",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7,<4.0",
"size": 8411,
"upload_time": "2023-01-01T20:24:22",
"upload_time_iso_8601": "2023-01-01T20:24:22.079744Z",
"url": "https://files.pythonhosted.org/packages/d0/7a/4db8adaa3a42cc3fadf663846a67d44c315fec87ed0c113102c770cca5d2/isops-0.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-01-01 20:24:22",
"github": true,
"gitlab": false,
"bitbucket": false,
"github_user": "lorenzophys",
"github_project": "isops",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"tox": true,
"lcname": "isops"
}