Name | places-env JSON |
Version |
1.0.4.post2
JSON |
| download |
home_page | None |
Summary | securely version control environment files |
upload_time | 2024-12-11 18:51:19 |
maintainer | Marc Krenn |
docs_url | None |
author | Marc Krenn |
requires_python | >=3.11 |
license | MIT License Copyright (c) 2024 Marc Krenn Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
keywords |
environments
environment variables
secrets
env
tool
infisical
dotenv
hashicorp
vault
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
![Main test status](https://img.shields.io/github/actions/workflow/status/marckrenn/places-env/test.yaml?branch=main&label=Test%20(main))
![Develop test status](https://img.shields.io/github/actions/workflow/status/marckrenn/places-env/test.yaml?branch=develop&label=Test%20(develop))
[![PyPI - Version](https://img.shields.io/pypi/v/places-env)](https://pypi.org/project/places-env/)
![GitHub License](https://img.shields.io/github/license/marckrenn/places-env)
# places-env: securely version control environment files
> **Note:**
> _places-env_ is currently a proof of concept (PoC) and is **not ready for use in public projects or production environments**. Use it cautiously and only with private repositories.
> If you appreciate the ideas behind _places-env_, consider contributing by submitting pull requests!
## Motivation / The heck is _places-env_?
![Schematic overview of places](https://raw.githubusercontent.com/marckrenn/places-env/5a99cc9245ca6c8ea9d3cb4adb67d2f2cee56c09/images/places-dark.svg?sanitize=true#gh-dark-mode-only)
![Schematic overview of places](https://raw.githubusercontent.com/marckrenn/places-env/5a99cc9245ca6c8ea9d3cb4adb67d2f2cee56c09/images/places-light.svg?sanitize=true#gh-light-mode-only)
- _places-env_ is a self-contained, completely free open-source (FOSS) alternative to [HashiCorp Vault](https://www.hashicorp.com/products/vault), [Infisical](https://infisical.com/), [dotenv-vault](https://github.com/dotenv-org/dotenv-vault) and [sops](https://github.com/getsops/sops).
- Leverages a single source of truth (SSOT) [`places.yaml`](#placesyaml) for deriving multiple environment files.
- Similar to [sops](https://github.com/getsops/sops), _places-env_ encrypts only the values in [`places.yaml`](#placesyaml), resulting in [`places.enc.yaml`](#placesencyaml), which can be securely checked into git:
- Congrats, your SSOT is now version-controlled 🎉
- Always synchronized with collaborators
- Fully in-sync with branches and tags (try doing that with [Infisical](https://infisical.com/) & co. 😉)
- Changes remain 'human-trackable' — even when values are encrypted
- Contrary to [sops](https://github.com/getsops/sops), encryption keys can be assigned either per environment or on a per-value basis
- Provides a [straightforward setup](#getting-started) with no dependency on external services or libraries.
- [`places watch start`](#watch-start) (persistently) tracks changes in [`places.yaml`](#placesyaml)/[`places.enc.yaml`](#placesencyaml) and automatically handles [encryption](#encrypt), [decryption](#decrypt), [keeps `.gitignore` up-to-date](#sync-gitignore), and [auto-updates](#generate-environment) environment files. So it's essnetially _set and forget_.
<details>
<summary>Fallback Image (for Github Mobile users)</summary>
![Schematic overview of places](https://github.com/marckrenn/places-env/blob/develop/images/places-dark.png?raw=True)
</details>
## Getting started
1. **Install _places-env_:**
- via [pypi](https://pypi.org/project/places-env/):
`pip install places-env`
2. **Init project:** In terminal
- `cd` into your project
- Run one of the following commands:
- [`places init`](#init): Creates an empty [`places.yaml`](#placesyaml), generates a default crypto key at `.places/keys/default`
- [`places init --template min`](#init): Initializes with a minimal template ([view content](src/places/templates/min.yaml)).
- [`places init --tutorial`](#init): Initializes with a tutorial template ([view content](src/places/templates/tutorial.yaml)).
3. **Modify [`places.yaml`](#placesyaml)**:
- Use your preferred text editor
- Or modify it using the [_places-env_ CLI](#places-cli-documentation)
4. **Track changes:**
- Use [`places watch start (optionally: --daemon, --service)`](#watch-start) (recommended)
- Alternatively, use [`places encrypt`](#encrypt) and [`places sync gitignore`](#sync-gitignore). This will automatically add all necessary entries to `.gitignore`.
5. **Generate environment files:**
- If [`places watch start`](#watch-start) is already running, environments with property `watch: true` will be (re)generated whenever [`places.yaml`](#placesyaml) is updated.
- Or use [`places generate environment --all`](#generate-environment) to manually regenerate all environment files.
6. **Commit [`places.enc.yaml`](#placesencyaml)**
7. **Decrypt after switching to another branch**:
- If [`places watch start`](#watch-start) is already running, [`places.enc.yaml`](#placesencyaml) will automatically be decrypted into [`places.yaml`](#placesyaml) after switching branches.
- Otherwise, run [`places decrypt`](#decrypt) to manually derive [`places.yaml`](#placesyaml) from [`places.enc.yaml`](#placesencyaml).
8. **Key exchange:**
- If you're working with collaborators, **securely** share your crypto keys located in `.places/keys` with them.
- Recommended methods include shared password managers like [Bitwarden](https://bitwarden.com/), secure one-time sharing services, or dedicated tools such as [Amazon KMS](https://aws.amazon.com/kms/).
- Collaborators without the necessary decryption keys can still add and edit new secrets but are restricted from reading existing ones.
## Documentation
### `places.yaml`
#### Examples
1. [Minimal example](src/places/templates/min.yaml):
```yaml
key: .places/keys/default
environments:
local:
filepath: .env
watch: true
variables:
PROJECT_NAME: your-project-name
```
[`places generate environment local`](#generate-environment) or [`places watch start`](#watch-start) will generate this `.env` for environment `local`:
```
PROJECT_NAME=your-project-name
```
2. Closer-to-live example based on the [tutorial template](src/places/templates/tutorial.yaml):
```yaml
keys:
default: .places/keys/default
prod: .places/keys/prod
dev: .places/keys/dev
test: .places/keys/test
environments:
local:
filepath: .env
watch: true
key: default
development:
filepath: .env.dev
alias: [dev]
key: dev
production:
filepath: .env.prod
alias: [prod]
key: prod
variables:
PROJECT_NAME: your-project-name
HOST: localhost
PORT:
local: 8000
dev: 8001
prod:
value: 8002
unencrypted: true
ADDRESS: ${HOST}:${PORT}
DOMAIN:
dev: ${PROJECT_NAME}.foo.dev
prod: ${PROJECT_NAME}.foo.com
JSON_MULTILINE: |
{
"key1": "value1",
"key2": "value2"
}
```
[`places generate environment --all`](#generate-environment) or [`places watch start`](#watch-start) will generate
* this `.env` for environment `local`:
```
PROJECT_NAME=your-project-name
HOST=localhost
PORT=8000
ADDRESS=localhost:8000
JSON_MULTILINE='{
"key1": "value1",
"key2": "value2"
}'
```
* this `.env.dev` for environment `development`:
```
PROJECT_NAME=your-project-name
HOST=localhost
PORT=8001
ADDRESS=localhost:8001
DOMAIN=your-project-name.foo.dev
JSON_MULTILINE='{
"key1": "value1",
"key2": "value2"
}'
```
* and this `.env.prod` for environment `production`:
```
PROJECT_NAME=your-project-name
HOST=localhost
PORT=8002
ADDRESS=localhost:8002
DOMAIN=your-project-name.foo.com
JSON_MULTILINE='{
"key1": "value1",
"key2": "value2"
}'
```
<details>
<summary>CLI commands:</summary>
- Encrypt the values in [`places.yaml`](#placesyaml) and saves the encrypted data to [`.places/places.enc.yaml`](#placesencyaml):
[`places encrypt`](#encrypt)
</details>
#### Sections
All sections are case-sensitive!
**Required sections:**
- [`key` / `keys`](#key--keys)
- [`environments`](#environments)
- [`variables`](#variables)
**Optional section:**
- [`settings`](#settings)
##### `key` / `keys`
Encryption/decryption key or keys that can be referenced in [`environments`](#environments).
The `default` key is required as it serves as a fallback when no other key is specified.
**Examples:**
```yaml
key: .places/keys/default # shorthand for keys: default: .places/keys/default
```
```yaml
keys:
default: .places/keys/default
dev: .places/keys/dev
prod: .places/keys/prod
topsecret: .places/keys/topsecret
```
<details>
<summary>CLI commands:</summary>
- Generate key, add it to `.places/keys/` and optionally add key to [`places.yaml`](#placesyaml):
[`places generate key`](#generate-key)
- Add a key from string to `.places/keys/` and optionally add the key to [`places.yaml`](#placesyaml):
[`places add key_from_string`](#add-key_from_string)
- Add existing key to [`places.yaml`](#placesyaml):
[`places add key`](#add-key)
</details>
##### `environments`
`environments` define what environment file(s) should be generated.
**Example:**
```yaml
environments:
local:
filepath: .env
watch: true
development:
filepath: .env.dev
watch: true
alias: [dev, stage]
key: dev
production:
filepath: .env.prod
watch: true
alias: [prod]
key: prod
```
**Options**:
| Option | Type | Default | Required | Description |
|--------|------|---------|:--------:|-------------|
| `filepath` | `String` | `None` | ✅ | filepath of environment file to generate relative to root |
| `key` | `Bool` | `default` | ❌ | Key to encrypt / decrypt variables of this environment. Refers to keys defined in [keys](#key--keys) |
| `alias` | `[String]` | `None` | ❌ |Alias(es) that can be used for this environment|
| `watch` | `Bool` | `false` | ❌ | If `true` and [`places watch start`](#watch-start) is running, this environment will be auto-(re)generated on filechange of [`places.yaml`](#placesyaml) |
<details>
<summary>CLI commands:</summary>
- Add or modify environment in [`places.yaml`](#placesyaml):
[`places add environment`](#add-environment)
</details>
##### `variables`
Key-value pairs to save to environment file(s). Keys should contain only uppercase alphanumerics and underscores; otherwise, a warning is printed.
**Example:**
```yaml
variables:
PROJECT_NAME: your-project-name
HOST: localhost
PORT:
local: 8000
dev: 8001
prod:
value: 8002
unencrypted: true
ADDRESS: ${HOST}:${PORT}
DOMAIN:
dev: ${PROJECT_NAME}.foo.dev
prod: ${PROJECT_NAME}.foo.com
JSON: |
{
'key1': 'value1',
'key2': 'value2'
}
```
**Syntax**:
- Shorthand: Set a key-value for all [environments](#environments). **Note: This will encrypt the value separately with the keys of all environments. Any of these keys will be able to decrypt it!**
```yaml
VARIABLE_NAME: value
```
- Set specific value per [environment](#environments)
```yaml
PORT:
local: 8000
dev: 8001
prod: 8002
```
- Set specific encryption key per value [environment](#environments)
```yaml
SECRET:
local:
value: This won't be encrypted # in places.enc.yaml
unencrypted: true
prod:
value: Dirty secret # will be encrypted with 'topsecret' key
key: topsecret # must be defined in keys section
```
- Multiline strings (must start with `|`):
```yaml
JSON: |
{
'key1': 'value1',
'key2': 'value2'
}
```
- Single-line dicts must be explicitly wrapped into quotes:
```yaml
JSON: "{'key1': 'value1', 'key2': 'value2'}"
```
- Value interpolation:
```yaml
HOST: localhost
PORT:
local: 8000
dev: 8001
prod: 8002
ADDRESS: ${HOST}:${PORT} # .env = localhost:8000, .env.dev = localhost:8001, etc.
```
- Lists/arrays with square brackets (**Note:** yaml-multiline arrays are currently NOT supported, see [Known Issues](#known-issues--limitations)!)
```yaml
ARRAY: [1,2,3,4]
```
- Combination of all syntaxes above.
**Options**:
| Option | Type | Default | Required | Description |
|--------|------|---------|:--------:|-------------|
| `value` | `Any` | `None` | ✅ | value of Key |
| `key` | `String` | `key set in` [environments](#environments) `> default key` | ❌ | encryption / decryption key used for this particular value |
| `unencrypted` | `Bool` | `False` | ❌ | If `true` explicitly not encrypt value |
<details>
<summary>CLI commands:</summary>
- Add variable to [`places.yaml`](#placesyaml):
[`places add variable`](#add-variable)
</details>
##### `settings`
Allows for configuration of project parameters, primarily related to cryptography.
**Examples:**
```yaml
settings:
sync-gitingore: false
cryptography:
hash-function: sha265
iterations: 120000
dklen: 32
salt:
mode: from-file
filepath: version.txt
```
**Options:**
| Option | Type | Default | Required | Description |
|--------|------|---------|:--------:|-------------|
| `sync-gitignore` | `Bool` | `True` | ❌ | If `true` makes sure that all `.envs`, [`places.yaml`](#placesyaml) and `.places` are in `.gitignore` |
| `cryptography`:`hash-function` | `String` | `sha512` | ❌ | Hash function to encrypt / decrypt (`sha256` or `sha512`) |
| `cryptography`:`iterations` | `Int` | `600000` (`sha265`), `210000` (`sha512`) | ❌ | Hash function to encrypt / decrypt (`sha256` or `sha512`) |
| `cryptography`:`dklen` | `Int` | `32` | ❌ | Derived key length |
| `cryptography`:`salt`:`mode` | `String` | `deterministic` | ❌ | Available modes: `deterministic`[^1], `custom`[^2], `from-file`[^3], `git-project`[^4], `git-branch`[^5], `git-project-branch`[^6] |
[^1]: By default, _places-env_ intentionally uses a deterministic salt. While this allows for some statistical attacks, it enables tracking of value changes.
[^2]: Set a custom salt using `cryptography`:`salt`:`value`.
[^3]: Use the content of `cryptography`:`salt`:`filepath` as the salt (e.g., salting with `version.txt`).
[^4]: Use the Git project name as the salt.
[^5]: Use the Git branch as the salt (encrypted values will differ for each branch).
[^6]: Combine the Git project name and branch as the salt.
<details>
<summary>CLI commands:</summary>
- Add settings to [`places.yaml`](#placesyaml):
[`places add setting`](#add-setting)
</details>
### `places.enc.yaml`
The encrypted version of [`places.yaml`](#placesyaml), which is safe to check into Git.
**Example:**
```yaml
keys:
default: .places/keys/default
prod: .places/keys/prod
dev: .places/keys/dev
test: .places/keys/test
environments:
local:
filepath: .env
watch: true
key: default
development:
filepath: .env.dev
alias: [dev]
key: dev
production:
filepath: .env.prod
alias: [prod]
key: prod
variables:
PROJECT_NAME: encrypted(default|dev|prod):kvvmBtvz6I8QadAG5hoDyEZ8kzbfJ2IrGwpNlqD70CWIpWfSlzR6TA==|ddts1k4JhTNmP9f9zrfCyfM6dcth5eP86y9UoCQwGvqmrCW02Y4jwg==|1037LUJgxus4CsF35VtwZ/FjFuioG/PGwzaMuJwGI4GRdKA+eiH0gQ==
HOST: encrypted(default|dev|prod):levmXeHNoZcRN6dHdvE5GZTG8TpBCqD8IxpjtA==|cstsjXQ3zCtnYaC8IPmbMqGVIeONE5EA4QIVyw==|0F37dnhej/M5VLY2xqHJWGrwGUBGg9KWVYPSXA==
PORT:
local: encrypted(default):uOieQPXb5MVQjSDnUF7EXkVfEKHRC2aJ
dev: encrypted(dev):X8gUkGAxiXkySxxyJeDZiABVBFr7JbGD
prod:
value: 8002
unencrypted: true
ADDRESS: encrypted(default|dev|prod):kp+sUOvf4KwlR6tO2hk9z29S5A/pQX1DgBN1LLeFNKwB2DNSnVulEsGPSuE=|db8mgH4ljRBTEay18rT8ztoUAvJXg/yU2hEhXMxD1DlIKFauN2tO6uCKsNU=|1ymxe3JMzsgNJLo/2VhOYNhNYdGefeyuzEl4GkNBfe4rss/5PfZpdaUCf9Y=
DOMAIN:
dev: encrypted(dev):db8mgHgm/hRWObjIwqa1tu44ceVK+of43zRKE0pthsnU3U7da7gqjvX5ZbqKjOdHZHPAfA==
prod: encrypted(prod):1ymxe3RPvcwIDK5C6UoHGOxhEsaDBJfCyWwTVUA1GneBv+DzLbWmIphZPaAPZOd8xM6yYg==
JSON_MULTILINE: encrypted(default|dev|prod):ktuwUPHZk4opXIIP9Scin0NF/DbfOGF6hAgNZjOVzfH5hckrOvVBaL80vB6mdBXPrfFFDYAbk7NXLdeQzHBuv9+lqoi4qetM|dfs6gGQj/jZfCoX03YrjnvYDGsth+uCt3gpZFmt98sXH6GOMmolif4Wj2Zz3KyUGhEiioMYmbHKq2o77duYEKxY+woyWEKFA|122te2hKve4BP5N+9mZRMPRaeeioBPCXyFIAUGElbnqq4KSiQIxsoqc6ZQpj1FexDm9Ya7iPKKkjOcl8JqtuUEtYmQWfu9uX
```
<details>
<summary>CLI commands:</summary>
- Decrypts and derives [`places.yaml`](#placesyaml) from [`places.enc.yaml`](#placesencyaml):
[`places decrypt`](#decrypt)
</details>
## CI/CD
Coming soon!™️
For now, you can integrate _places-env_ into your Python project or include it during CI build time:
* Make sure to copy the required [keys](#key--keys) into `./places/keys` and
* Run [`places generate environment`](#generate-environment) command for the required environment.
## FAQ
- **The hell is this? Do you have _any_ idea what you're doing?**
> No. Consider this a toy, a conversation starter. If this gains traction, those who truly know how things should be done will need to take over.
> This is my first public Python project/package, and it's full of firsts for me, so please keep that in mind. Also, I don't consider myself a professional programmer and have no formal education in this domain.
- **Why?**
> This started as a Hackathon project, and I felt the urge to complete and release something for once. Additionally, I'm preparing a tech stack I’d like to work with, and I wasn’t satisfied with the existing workflows for managing and syncing secrets (see below).
- **Is this for me/my project?**
> Again, consider this a toy. For now, use it only for private repositories and only with people you trust.
- **What happens if a collaborator doesn't have all the crypto keys defined in [`places.yaml`](#placesyaml)?**
- **For per-environment values (e.g., `PORT: local: 8000`)**:
If a collaborator lacks the required keys, [`places decrypt`](#decrypt) will fail to decrypt the encrypted value. In this case, the unencrypted value will remain in [`places.yaml`](#placesyaml) as-is. When re-encrypting with [`places encrypt`](#encrypt), the existing encrypted value will be written to [`places.enc.yaml`](#placesencyaml) unchanged.
- **For shorthand/compound values (e.g., `PROJECT_NAME: your-project-name`) that use multi/compound keys**:
If the user possesses any of the required keys (e.g. `default` and `dev` out of `encrypted(default|dev|prod):kvvmBt…`), [`places decrypt`](#decrypt) will successfully decrypt the value. When encrypting with [`places encrypt`](#encrypt), all keys (e.g. `default` and `dev`) available to the user will be used to encrypt the value.
- **Important Consideration**:
Compound values should only be used for non-sensitive information. For sensitive values, define them explicitly per environment.
- **Is _places-env_ secure?**
> Debateable, but broadly speaking it should be, yes – especially when used in private repositories and with people you trust. In general, _places-env_ exposes encrypted data to others (collaborators or the public), meaning that with enough time, effort and ressources, encrypted values could be cracked. However, _places-env_ was designed to make this unlikely within reasonable boundaries. For instance:
> - [`places sync gitignore`](#sync-gitignore) is executed automatically by default, which should help prevent unencrypted data from being committed.
> - [`places generate key`](#generate-key) generates cryptographic keys with appropriate length and entropy.
> - `AES-512-GCM` with 210,000 iterations is used as per [OWASP recommendations](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) (see [settings options](#settings) for more details).
>
> That said, some design decisions has been made that may weaken security:
> - By default, a deterministic salt is used to allow for deterministic tracking of changes, which introduces some attack vectors. If security is critical, you can choose alternative salting strategies in [settings options](#settings).
> - The cryptographic key exchange between collaborators is manual, so it’s your responsibility to ensure it happens securely.
> - When using the shorthand to define a variable for multiple environment files, any encryption key can decrypt the encrypted value.
> - **If you identify any inherent security flaws in _places-env_, please let me know ASAP. Thank you!**
- **Instead of _places-env_ why not just use …**
- … [sops](https://github.com/getsops/sops)?
> To be honest, I was overwhelmed at first glance and didn’t even try it. It’s almost certainly better and more secure in every regard than _places-env_, but at the same time, it looks cumbersome to set up.
> Additionally, I didn’t like how it seems to require (or strongly encourage) the use of another (potentially overkill) service for key management. Also, it appears to focus on file-based encryption rather than allowing for easy value-based encryption.
- … [dotenv-vault](https://github.com/dotenv-org/dotenv-vault)?
> Similar to [sops](https://github.com/getsops/sops), it looks great and might be a better solution for your use case. It’s also the closest alternative to _places-env_, so you may want to check it out.
> What I prefer about _places-env_ is that it doesn't lock you into the [dotenv.org](https://www.dotenv.org/)-ecosystem and that multiple environment files are derived from a single source of truth ([`places.yaml`](#placesyaml)). Additionally, [`places watch start`](#watch-start) persistently tracks changes in [`places.yaml`](#placesyaml) and automatically manages [encryption](#encrypt), [decryption](#decrypt), and [`auto-updates`](#generate-environment) for your environment files.
- … [Infisical](https://infisical.com/)?
> I genuinely wanted to like it, but their documentation is currently a mess. It took me over half an hour to locate their current Python library, which wasn’t even referenced in the documentation. I ultimately gave up, frustrated, when attempting to align secrets with my version tags.
- … [HashiCorp Vault](https://www.hashicorp.com/products/vault)?
> Yeah, [no](https://www.hashicorp.com/products/vault/pricing).
- … git hooks?
> Glad you asked! This project actually started as Git hooks, and you can find a very basic MVP in [places-mini](places-mini). It uses a single key to encrypt local environment files but lacks many of the convenient features of _places-env_. For example, you’ll need to manually ensure that all the appropriate entries are added to `.gitignore`, among other things. Also, it uses a naughty hack to track changes and force encryption. Don't use it.
- **Why is the code so bad?**
> As I mentioned above, I’m neither a professional coder nor experienced with the Python ecosystem. Additionally, I’ve made some questionable decisions along the way.
- **Why can’t the generated environment files be styled, structured, or annotated?**
> It's on the [roadmap](#roadmap) below.
## Roadmap (unordered)
* **DEV/CI/CD:** Add infos regarding development and add appropriate CI/CD
* **Hombrew:** Distribute _places-env_ also via [Homebrew](https://brew.sh/)
* **CI/CD:** Provide a convient Github Action and Gitlab [CI/CD](#cicd) integration for _places-env_.
* **Comments in environment files**: Add `comment`property to variables
* **Layouting in environment files**: Add "meta-variables" (eg. `places.section`) that add sections and linebreaks at gen-time.
## Known issues / Limitations
* _places-env_ does not adhere to the [YAML specifications](https://yaml.org/).
* Only array/lists in square brackets are supported, block style arrays aren't supported (yet).
* Single-line KV/JSON needs to be wrapped in quotes.
***
# places CLI Documentation
## add environment
Add a new environment configuration.
```shell
places add environment NAME [OPTIONS]
```
<details>
***
<summary>Options & Arguments</summary>
**Options**
| Short | Long Option | Description |
|-------|-------------|-------------|
| `-f` | `--filepath <String>` | Path to environment file. |
| `-w` | `--watch <Bool>` | Enable file watching. |
| `-a` | `--alias <String>` | Environment aliases. |
| `-k` | `--key <String>` | Key to use for encryption. |
**Arguments**
| Argument | Required |
|----------|----------|
| `NAME` | ❌ |
***
</details>
## add key
Add an existing key file reference to places.yaml
```shell
places add key NAME [OPTIONS]
```
<details>
***
<summary>Options & Arguments</summary>
**Options**
| Short | Long Option | Description |
|-------|-------------|-------------|
| `-a` | `--add` | Add key reference to places.yaml |
**Arguments**
| Argument | Required |
|----------|----------|
| `NAME` | ❌ |
***
</details>
## add key_from_string
Add a key from a provided string with the specified name.
```shell
places add key_from_string NAME KEY_STRING [OPTIONS]
```
<details>
***
<summary>Options & Arguments</summary>
**Options**
| Short | Long Option | Description |
|-------|-------------|-------------|
| `-a` | `--add` | Add key to places.yaml |
| `-f` | `--force-overwrite` | Force overwrite without safety checks. |
**Arguments**
| Argument | Required |
|----------|----------|
| `NAME` | ❌ |
| `KEY_STRING` | ❌ |
***
</details>
## add setting
Add or update settings configuration.
```shell
places add setting [OPTIONS]
```
<details>
***
<summary>Options</summary>
**Options**
| Short | Long Option | Description |
|-------|-------------|-------------|
| `-sg` | `--sync-gitignore <Bool>` | Enable/disable .gitignore sync. |
| `-i` | `--iterations <Int>` | Number of iterations for cryptography. |
| `-hf` | `--hash-function <String>` | Hash function for cryptography. |
| `-sm` | `--salt-mode <String>` | Salt mode for cryptography. |
| `-sf` | `--salt-filepath <String>` | Salt filepath for cryptography. |
| `-sv` | `--salt-value <String>` | Salt value for cryptography. |
***
</details>
## add variable
Add a new variable configuration.
```shell
places add variable NAME [OPTIONS]
```
<details>
***
<summary>Options & Arguments</summary>
**Options**
| Short | Long Option | Description |
|-------|-------------|-------------|
| `-v` | `--value <Any>` | Value of variable / secret. |
| `-k` | `--key <String>` | Key to use for encryption. |
| `-u` | `--unencrypt <Bool>` | Mark value as unencrypted. |
| `-e` | `--environment <String>` | Target environment(s). |
**Arguments**
| Argument | Required |
|----------|----------|
| `NAME` | ❌ |
***
</details>
## decrypt
Decrypts `.places/places.enc.yaml` into `places.yaml` file.
```shell
places decrypt [OPTIONS]
```
## encrypt
Encrypts `places.yaml` into `.places/places.enc.yaml` file.
```shell
places encrypt [OPTIONS]
```
## generate environment
Generate .env files for specified environments or all environments defined in `places.yaml`
This generally follows [https://dotenv-linter.github.io/](https://dotenv-linter.github.io/) rules, with the exception of alphabetical ordering.
```shell
places generate environment [ENVIRONMENT]... [OPTIONS]
```
<details>
***
<summary>Options & Arguments</summary>
**Options**
| Short | Long Option | Description |
|-------|-------------|-------------|
| `-a` | `--all` | Generate .env files for all environments. |
**Arguments**
| Argument | Required |
|----------|----------|
| `ENVIRONMENT` | ❌ |
***
</details>
## generate key
Generate a new encryption key with the specified name.
```shell
places generate key [NAME] [OPTIONS]
```
<details>
***
<summary>Options & Arguments</summary>
**Options**
| Short | Long Option | Description |
|-------|-------------|-------------|
| `-l` | `--length <Int>` | Custom length for generated key in bytes. |
| `-a` | `--add` | Add key to places.yaml |
**Arguments**
| Argument | Required |
|----------|----------|
| `NAME` | ❌ |
***
</details>
## init
Initialize a new places project.
Also generates a new default encryption key and adds it to `.places/keys/`.
```shell
places init [OPTIONS]
```
<details>
***
<summary>Options</summary>
**Options**
| Short | Long Option | Description |
|-------|-------------|-------------|
| `-t` | `--template <String>` | Template to use for initialization |
| `--list-templates` | `--list-templates` | List available templates |
***
</details>
## run test
Run tests.
Currently supported tests: e2e, cli.
Specify test names or use –all flag.
```shell
places run test [TESTS]... [OPTIONS]
```
<details>
***
<summary>Options & Arguments</summary>
**Options**
| Short | Long Option | Description |
|-------|-------------|-------------|
| `-a` | `--all` | Run all tests. |
**Arguments**
| Argument | Required |
|----------|----------|
| `TESTS` | ❌ |
***
</details>
## sync gitignore
Sync .gitignore with Places entries.
```shell
places sync gitignore [OPTIONS]
```
## watch start
Start watching for changes.
```shell
places watch start [OPTIONS]
```
<details>
***
<summary>Options</summary>
**Options**
| Short | Long Option | Description |
|-------|-------------|-------------|
| `-s` | `--service` | Run watcher as a persistent system service. |
| `-d` | `--daemon` | Run watcher as a background daemon. |
***
</details>
## watch stop
Stop watching for changes.
```shell
places watch stop [OPTIONS]
```
<details>
***
<summary>Options</summary>
**Options**
| Short | Long Option | Description |
|-------|-------------|-------------|
| `-s` | `--service` | Stop and remove persistent system service. |
| `-d` | `--daemon` | Stop daemon process. |
***
</details>
Raw data
{
"_id": null,
"home_page": null,
"name": "places-env",
"maintainer": "Marc Krenn",
"docs_url": null,
"requires_python": ">=3.11",
"maintainer_email": null,
"keywords": "environments, environment variables, secrets, env, tool, infisical, dotenv, hashicorp, vault",
"author": "Marc Krenn",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/dd/00/3661962a472d78dc8685d2ce31e0bd54c14f5b9a3f7e9fbb979a5d5bab39/places_env-1.0.4.post2.tar.gz",
"platform": null,
"description": "![Main test status](https://img.shields.io/github/actions/workflow/status/marckrenn/places-env/test.yaml?branch=main&label=Test%20(main))\n![Develop test status](https://img.shields.io/github/actions/workflow/status/marckrenn/places-env/test.yaml?branch=develop&label=Test%20(develop))\n[![PyPI - Version](https://img.shields.io/pypi/v/places-env)](https://pypi.org/project/places-env/)\n![GitHub License](https://img.shields.io/github/license/marckrenn/places-env)\n# places-env: securely version control environment files\n> **Note:** \n> _places-env_ is currently a proof of concept (PoC) and is **not ready for use in public projects or production environments**. Use it cautiously and only with private repositories. \n> If you appreciate the ideas behind _places-env_, consider contributing by submitting pull requests!\n\n## Motivation / The heck is _places-env_?\n\n![Schematic overview of places](https://raw.githubusercontent.com/marckrenn/places-env/5a99cc9245ca6c8ea9d3cb4adb67d2f2cee56c09/images/places-dark.svg?sanitize=true#gh-dark-mode-only)\n![Schematic overview of places](https://raw.githubusercontent.com/marckrenn/places-env/5a99cc9245ca6c8ea9d3cb4adb67d2f2cee56c09/images/places-light.svg?sanitize=true#gh-light-mode-only)\n\n- _places-env_ is a self-contained, completely free open-source (FOSS) alternative to [HashiCorp Vault](https://www.hashicorp.com/products/vault), [Infisical](https://infisical.com/), [dotenv-vault](https://github.com/dotenv-org/dotenv-vault) and [sops](https://github.com/getsops/sops). \n- Leverages a single source of truth (SSOT) [`places.yaml`](#placesyaml) for deriving multiple environment files.\n- Similar to [sops](https://github.com/getsops/sops), _places-env_ encrypts only the values in [`places.yaml`](#placesyaml), resulting in [`places.enc.yaml`](#placesencyaml), which can be securely checked into git: \n - Congrats, your SSOT is now version-controlled \ud83c\udf89\n - Always synchronized with collaborators\n - Fully in-sync with branches and tags (try doing that with [Infisical](https://infisical.com/) & co. \ud83d\ude09) \n - Changes remain 'human-trackable' \u2014 even when values are encrypted\n - Contrary to [sops](https://github.com/getsops/sops), encryption keys can be assigned either per environment or on a per-value basis\n- Provides a [straightforward setup](#getting-started) with no dependency on external services or libraries. \n- [`places watch start`](#watch-start) (persistently) tracks changes in [`places.yaml`](#placesyaml)/[`places.enc.yaml`](#placesencyaml) and automatically handles [encryption](#encrypt), [decryption](#decrypt), [keeps `.gitignore` up-to-date](#sync-gitignore), and [auto-updates](#generate-environment) environment files. So it's essnetially _set and forget_.\n\n<details>\n\n<summary>Fallback Image (for Github Mobile users)</summary>\n\n![Schematic overview of places](https://github.com/marckrenn/places-env/blob/develop/images/places-dark.png?raw=True)\n\n</details>\n\n## Getting started\n\n1. **Install _places-env_:**\n\n- via [pypi](https://pypi.org/project/places-env/):\n\n `pip install places-env`\n\n2. **Init project:** In terminal\n - `cd` into your project \n - Run one of the following commands: \n - [`places init`](#init): Creates an empty [`places.yaml`](#placesyaml), generates a default crypto key at `.places/keys/default`\n - [`places init --template min`](#init): Initializes with a minimal template ([view content](src/places/templates/min.yaml)). \n - [`places init --tutorial`](#init): Initializes with a tutorial template ([view content](src/places/templates/tutorial.yaml)).\n\n3. **Modify [`places.yaml`](#placesyaml)**:\n - Use your preferred text editor \n - Or modify it using the [_places-env_ CLI](#places-cli-documentation)\n\n4. **Track changes:**\n - Use [`places watch start (optionally: --daemon, --service)`](#watch-start) (recommended) \n - Alternatively, use [`places encrypt`](#encrypt) and [`places sync gitignore`](#sync-gitignore). This will automatically add all necessary entries to `.gitignore`.\n\n5. **Generate environment files:**\n - If [`places watch start`](#watch-start) is already running, environments with property `watch: true` will be (re)generated whenever [`places.yaml`](#placesyaml) is updated. \n - Or use [`places generate environment --all`](#generate-environment) to manually regenerate all environment files.\n\n6. **Commit [`places.enc.yaml`](#placesencyaml)**\n\n7. **Decrypt after switching to another branch**:\n - If [`places watch start`](#watch-start) is already running, [`places.enc.yaml`](#placesencyaml) will automatically be decrypted into [`places.yaml`](#placesyaml) after switching branches. \n - Otherwise, run [`places decrypt`](#decrypt) to manually derive [`places.yaml`](#placesyaml) from [`places.enc.yaml`](#placesencyaml).\n\n8. **Key exchange:**\n - If you're working with collaborators, **securely** share your crypto keys located in `.places/keys` with them.\n - Recommended methods include shared password managers like [Bitwarden](https://bitwarden.com/), secure one-time sharing services, or dedicated tools such as [Amazon KMS](https://aws.amazon.com/kms/).\n - Collaborators without the necessary decryption keys can still add and edit new secrets but are restricted from reading existing ones.\n\n## Documentation\n\n### `places.yaml`\n\n#### Examples\n\n1. [Minimal example](src/places/templates/min.yaml):\n```yaml\nkey: .places/keys/default\n\nenvironments:\n local:\n filepath: .env\n watch: true\n\nvariables:\n PROJECT_NAME: your-project-name\n```\n\n[`places generate environment local`](#generate-environment) or [`places watch start`](#watch-start) will generate this `.env` for environment `local`:\n\n```\nPROJECT_NAME=your-project-name\n```\n\n\n2. Closer-to-live example based on the [tutorial template](src/places/templates/tutorial.yaml):\n```yaml\n\nkeys:\n default: .places/keys/default\n prod: .places/keys/prod\n dev: .places/keys/dev\n test: .places/keys/test\n\nenvironments:\n\n local:\n filepath: .env\n watch: true\n key: default\n\n development:\n filepath: .env.dev\n alias: [dev]\n key: dev\n\n production:\n filepath: .env.prod\n alias: [prod]\n key: prod\n\nvariables:\n\n PROJECT_NAME: your-project-name\n\n HOST: localhost\n\n PORT:\n local: 8000\n dev: 8001\n prod:\n value: 8002\n unencrypted: true\n \n ADDRESS: ${HOST}:${PORT}\n\n DOMAIN:\n dev: ${PROJECT_NAME}.foo.dev\n prod: ${PROJECT_NAME}.foo.com\n \n JSON_MULTILINE: |\n {\n \"key1\": \"value1\",\n \"key2\": \"value2\"\n }\n\n```\n\n[`places generate environment --all`](#generate-environment) or [`places watch start`](#watch-start) will generate\n\n* this `.env` for environment `local`:\n```\nPROJECT_NAME=your-project-name\nHOST=localhost\nPORT=8000\nADDRESS=localhost:8000\nJSON_MULTILINE='{\n \"key1\": \"value1\",\n \"key2\": \"value2\"\n}'\n```\n\n* this `.env.dev` for environment `development`:\n```\nPROJECT_NAME=your-project-name\nHOST=localhost\nPORT=8001\nADDRESS=localhost:8001\nDOMAIN=your-project-name.foo.dev\nJSON_MULTILINE='{\n \"key1\": \"value1\",\n \"key2\": \"value2\"\n}'\n```\n\n* and this `.env.prod` for environment `production`:\n```\nPROJECT_NAME=your-project-name\nHOST=localhost\nPORT=8002\nADDRESS=localhost:8002\nDOMAIN=your-project-name.foo.com\nJSON_MULTILINE='{\n \"key1\": \"value1\",\n \"key2\": \"value2\"\n}'\n```\n<details>\n<summary>CLI commands:</summary>\n\n- Encrypt the values in [`places.yaml`](#placesyaml) and saves the encrypted data to [`.places/places.enc.yaml`](#placesencyaml):\n \n [`places encrypt`](#encrypt)\n\n</details>\n\n#### Sections\n\nAll sections are case-sensitive!\n\n**Required sections:**\n- [`key` / `keys`](#key--keys)\n- [`environments`](#environments)\n- [`variables`](#variables)\n\n**Optional section:**\n- [`settings`](#settings)\n\n##### `key` / `keys`\n\nEncryption/decryption key or keys that can be referenced in [`environments`](#environments). \n\nThe `default` key is required as it serves as a fallback when no other key is specified.\n\n**Examples:**\n\n```yaml\nkey: .places/keys/default # shorthand for keys: default: .places/keys/default\n```\n\n```yaml\nkeys:\n default: .places/keys/default\n dev: .places/keys/dev\n prod: .places/keys/prod\n topsecret: .places/keys/topsecret\n```\n\n<details>\n<summary>CLI commands:</summary>\n\n- Generate key, add it to `.places/keys/` and optionally add key to [`places.yaml`](#placesyaml):\n \n [`places generate key`](#generate-key)\n\n- Add a key from string to `.places/keys/` and optionally add the key to [`places.yaml`](#placesyaml):\n\n [`places add key_from_string`](#add-key_from_string)\n\n- Add existing key to [`places.yaml`](#placesyaml):\n\n [`places add key`](#add-key)\n\n</details>\n\n##### `environments`\n\n`environments` define what environment file(s) should be generated.\n\n**Example:**\n```yaml\nenvironments:\n local:\n filepath: .env\n watch: true\n development:\n filepath: .env.dev\n watch: true\n alias: [dev, stage]\n key: dev\n production:\n filepath: .env.prod\n watch: true\n alias: [prod]\n key: prod\n```\n\n**Options**:\n\n| Option | Type | Default | Required | Description |\n|--------|------|---------|:--------:|-------------|\n| `filepath` | `String` | `None` | \u2705 | filepath of environment file to generate relative to root |\n| `key` | `Bool` | `default` | \u274c | Key to encrypt / decrypt variables of this environment. Refers to keys defined in [keys](#key--keys) |\n| `alias` | `[String]` | `None` | \u274c |Alias(es) that can be used for this environment|\n| `watch` | `Bool` | `false` | \u274c | If `true` and [`places watch start`](#watch-start) is running, this environment will be auto-(re)generated on filechange of [`places.yaml`](#placesyaml) |\n\n<details>\n<summary>CLI commands:</summary>\n- Add or modify environment in [`places.yaml`](#placesyaml):\n\n [`places add environment`](#add-environment)\n</details>\n\n##### `variables`\n\nKey-value pairs to save to environment file(s). Keys should contain only uppercase alphanumerics and underscores; otherwise, a warning is printed.\n\n**Example:**\n\n```yaml\nvariables:\n\n PROJECT_NAME: your-project-name\n\n HOST: localhost\n\n PORT:\n local: 8000\n dev: 8001\n prod:\n value: 8002\n unencrypted: true\n \n ADDRESS: ${HOST}:${PORT}\n\n DOMAIN:\n dev: ${PROJECT_NAME}.foo.dev\n prod: ${PROJECT_NAME}.foo.com\n \n JSON: |\n {\n 'key1': 'value1',\n 'key2': 'value2'\n }\n```\n\n**Syntax**:\n\n- Shorthand: Set a key-value for all [environments](#environments). **Note: This will encrypt the value separately with the keys of all environments. Any of these keys will be able to decrypt it!**\n\n ```yaml\n VARIABLE_NAME: value\n ```\n\n- Set specific value per [environment](#environments)\n\n ```yaml\n PORT:\n local: 8000\n dev: 8001\n prod: 8002\n ```\n\n- Set specific encryption key per value [environment](#environments)\n\n ```yaml\n SECRET:\n local:\n value: This won't be encrypted # in places.enc.yaml\n unencrypted: true\n prod:\n value: Dirty secret # will be encrypted with 'topsecret' key\n key: topsecret # must be defined in keys section\n ```\n\n- Multiline strings (must start with `|`):\n ```yaml\n JSON: |\n {\n 'key1': 'value1',\n 'key2': 'value2'\n }\n ```\n- Single-line dicts must be explicitly wrapped into quotes:\n ```yaml\n JSON: \"{'key1': 'value1', 'key2': 'value2'}\"\n ```\n\n- Value interpolation:\n\n ```yaml\n HOST: localhost\n\n PORT:\n local: 8000\n dev: 8001\n prod: 8002\n \n ADDRESS: ${HOST}:${PORT} # .env = localhost:8000, .env.dev = localhost:8001, etc.\n ```\n\n- Lists/arrays with square brackets (**Note:** yaml-multiline arrays are currently NOT supported, see [Known Issues](#known-issues--limitations)!)\n\n ```yaml\n ARRAY: [1,2,3,4]\n ```\n\n- Combination of all syntaxes above.\n\n**Options**:\n\n| Option | Type | Default | Required | Description |\n|--------|------|---------|:--------:|-------------|\n| `value` | `Any` | `None` | \u2705 | value of Key |\n| `key` | `String` | `key set in` [environments](#environments) `> default key` | \u274c | encryption / decryption key used for this particular value |\n| `unencrypted` | `Bool` | `False` | \u274c | If `true` explicitly not encrypt value |\n\n<details>\n<summary>CLI commands:</summary>\n\n- Add variable to [`places.yaml`](#placesyaml):\n \n [`places add variable`](#add-variable)\n\n</details>\n\n##### `settings`\n\nAllows for configuration of project parameters, primarily related to cryptography.\n\n**Examples:**\n```yaml\nsettings:\n sync-gitingore: false\n cryptography:\n hash-function: sha265\n iterations: 120000\n dklen: 32\n salt:\n mode: from-file\n filepath: version.txt\n```\n\n**Options:**\n\n| Option | Type | Default | Required | Description |\n|--------|------|---------|:--------:|-------------|\n| `sync-gitignore` | `Bool` | `True` | \u274c | If `true` makes sure that all `.envs`, [`places.yaml`](#placesyaml) and `.places` are in `.gitignore` |\n| `cryptography`:`hash-function` | `String` | `sha512` | \u274c | Hash function to encrypt / decrypt (`sha256` or `sha512`) |\n| `cryptography`:`iterations` | `Int` | `600000` (`sha265`), `210000` (`sha512`) | \u274c | Hash function to encrypt / decrypt (`sha256` or `sha512`) |\n| `cryptography`:`dklen` | `Int` | `32` | \u274c | Derived key length |\n| `cryptography`:`salt`:`mode` | `String` | `deterministic` | \u274c | Available modes: `deterministic`[^1], `custom`[^2], `from-file`[^3], `git-project`[^4], `git-branch`[^5], `git-project-branch`[^6] |\n\n[^1]: By default, _places-env_ intentionally uses a deterministic salt. While this allows for some statistical attacks, it enables tracking of value changes.\n[^2]: Set a custom salt using `cryptography`:`salt`:`value`. \n[^3]: Use the content of `cryptography`:`salt`:`filepath` as the salt (e.g., salting with `version.txt`).\n[^4]: Use the Git project name as the salt. \n[^5]: Use the Git branch as the salt (encrypted values will differ for each branch). \n[^6]: Combine the Git project name and branch as the salt.\n\n\n<details>\n<summary>CLI commands:</summary>\n\n- Add settings to [`places.yaml`](#placesyaml):\n \n [`places add setting`](#add-setting)\n\n</details>\n\n### `places.enc.yaml`\n\nThe encrypted version of [`places.yaml`](#placesyaml), which is safe to check into Git.\n\n**Example:**\n```yaml\nkeys:\n default: .places/keys/default\n prod: .places/keys/prod\n dev: .places/keys/dev\n test: .places/keys/test\n\nenvironments:\n\n local:\n filepath: .env\n watch: true\n key: default\n\n development:\n filepath: .env.dev\n alias: [dev]\n key: dev\n\n production:\n filepath: .env.prod\n alias: [prod]\n key: prod\n\nvariables:\n\n PROJECT_NAME: encrypted(default|dev|prod):kvvmBtvz6I8QadAG5hoDyEZ8kzbfJ2IrGwpNlqD70CWIpWfSlzR6TA==|ddts1k4JhTNmP9f9zrfCyfM6dcth5eP86y9UoCQwGvqmrCW02Y4jwg==|1037LUJgxus4CsF35VtwZ/FjFuioG/PGwzaMuJwGI4GRdKA+eiH0gQ==\n\n HOST: encrypted(default|dev|prod):levmXeHNoZcRN6dHdvE5GZTG8TpBCqD8IxpjtA==|cstsjXQ3zCtnYaC8IPmbMqGVIeONE5EA4QIVyw==|0F37dnhej/M5VLY2xqHJWGrwGUBGg9KWVYPSXA==\n\n PORT:\n local: encrypted(default):uOieQPXb5MVQjSDnUF7EXkVfEKHRC2aJ\n dev: encrypted(dev):X8gUkGAxiXkySxxyJeDZiABVBFr7JbGD\n prod:\n value: 8002\n unencrypted: true\n \n ADDRESS: encrypted(default|dev|prod):kp+sUOvf4KwlR6tO2hk9z29S5A/pQX1DgBN1LLeFNKwB2DNSnVulEsGPSuE=|db8mgH4ljRBTEay18rT8ztoUAvJXg/yU2hEhXMxD1DlIKFauN2tO6uCKsNU=|1ymxe3JMzsgNJLo/2VhOYNhNYdGefeyuzEl4GkNBfe4rss/5PfZpdaUCf9Y=\n\n DOMAIN:\n dev: encrypted(dev):db8mgHgm/hRWObjIwqa1tu44ceVK+of43zRKE0pthsnU3U7da7gqjvX5ZbqKjOdHZHPAfA==\n prod: encrypted(prod):1ymxe3RPvcwIDK5C6UoHGOxhEsaDBJfCyWwTVUA1GneBv+DzLbWmIphZPaAPZOd8xM6yYg==\n \n JSON_MULTILINE: encrypted(default|dev|prod):ktuwUPHZk4opXIIP9Scin0NF/DbfOGF6hAgNZjOVzfH5hckrOvVBaL80vB6mdBXPrfFFDYAbk7NXLdeQzHBuv9+lqoi4qetM|dfs6gGQj/jZfCoX03YrjnvYDGsth+uCt3gpZFmt98sXH6GOMmolif4Wj2Zz3KyUGhEiioMYmbHKq2o77duYEKxY+woyWEKFA|122te2hKve4BP5N+9mZRMPRaeeioBPCXyFIAUGElbnqq4KSiQIxsoqc6ZQpj1FexDm9Ya7iPKKkjOcl8JqtuUEtYmQWfu9uX\n\n```\n<details>\n<summary>CLI commands:</summary>\n\n- Decrypts and derives [`places.yaml`](#placesyaml) from [`places.enc.yaml`](#placesencyaml):\n \n [`places decrypt`](#decrypt)\n\n</details>\n\n## CI/CD\n\nComing soon!\u2122\ufe0f\nFor now, you can integrate _places-env_ into your Python project or include it during CI build time:\n* Make sure to copy the required [keys](#key--keys) into `./places/keys` and\n* Run [`places generate environment`](#generate-environment) command for the required environment.\n\n## FAQ\n\n- **The hell is this? Do you have _any_ idea what you're doing?** \n > No. Consider this a toy, a conversation starter. If this gains traction, those who truly know how things should be done will need to take over. \n > This is my first public Python project/package, and it's full of firsts for me, so please keep that in mind. Also, I don't consider myself a professional programmer and have no formal education in this domain.\n\n- **Why?** \n > This started as a Hackathon project, and I felt the urge to complete and release something for once. Additionally, I'm preparing a tech stack I\u2019d like to work with, and I wasn\u2019t satisfied with the existing workflows for managing and syncing secrets (see below).\n\n- **Is this for me/my project?** \n > Again, consider this a toy. For now, use it only for private repositories and only with people you trust.\n\n- **What happens if a collaborator doesn't have all the crypto keys defined in [`places.yaml`](#placesyaml)?**\n\n - **For per-environment values (e.g., `PORT: local: 8000`)**: \n If a collaborator lacks the required keys, [`places decrypt`](#decrypt) will fail to decrypt the encrypted value. In this case, the unencrypted value will remain in [`places.yaml`](#placesyaml) as-is. When re-encrypting with [`places encrypt`](#encrypt), the existing encrypted value will be written to [`places.enc.yaml`](#placesencyaml) unchanged.\n\n - **For shorthand/compound values (e.g., `PROJECT_NAME: your-project-name`) that use multi/compound keys**: \n If the user possesses any of the required keys (e.g. `default` and `dev` out of `encrypted(default|dev|prod):kvvmBt\u2026`), [`places decrypt`](#decrypt) will successfully decrypt the value. When encrypting with [`places encrypt`](#encrypt), all keys (e.g. `default` and `dev`) available to the user will be used to encrypt the value.\n\n - **Important Consideration**: \n Compound values should only be used for non-sensitive information. For sensitive values, define them explicitly per environment.\n\n- **Is _places-env_ secure?** \n > Debateable, but broadly speaking it should be, yes \u2013 especially when used in private repositories and with people you trust. In general, _places-env_ exposes encrypted data to others (collaborators or the public), meaning that with enough time, effort and ressources, encrypted values could be cracked. However, _places-env_ was designed to make this unlikely within reasonable boundaries. For instance: \n > - [`places sync gitignore`](#sync-gitignore) is executed automatically by default, which should help prevent unencrypted data from being committed. \n > - [`places generate key`](#generate-key) generates cryptographic keys with appropriate length and entropy. \n > - `AES-512-GCM` with 210,000 iterations is used as per [OWASP recommendations](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) (see [settings options](#settings) for more details). \n > \n > That said, some design decisions has been made that may weaken security: \n > - By default, a deterministic salt is used to allow for deterministic tracking of changes, which introduces some attack vectors. If security is critical, you can choose alternative salting strategies in [settings options](#settings). \n > - The cryptographic key exchange between collaborators is manual, so it\u2019s your responsibility to ensure it happens securely. \n > - When using the shorthand to define a variable for multiple environment files, any encryption key can decrypt the encrypted value. \n > - **If you identify any inherent security flaws in _places-env_, please let me know ASAP. Thank you!**\n\n- **Instead of _places-env_ why not just use \u2026**\n - \u2026 [sops](https://github.com/getsops/sops)?\n > To be honest, I was overwhelmed at first glance and didn\u2019t even try it. It\u2019s almost certainly better and more secure in every regard than _places-env_, but at the same time, it looks cumbersome to set up. \n > Additionally, I didn\u2019t like how it seems to require (or strongly encourage) the use of another (potentially overkill) service for key management. Also, it appears to focus on file-based encryption rather than allowing for easy value-based encryption.\n - \u2026 [dotenv-vault](https://github.com/dotenv-org/dotenv-vault)?\n > Similar to [sops](https://github.com/getsops/sops), it looks great and might be a better solution for your use case. It\u2019s also the closest alternative to _places-env_, so you may want to check it out.\n > What I prefer about _places-env_ is that it doesn't lock you into the [dotenv.org](https://www.dotenv.org/)-ecosystem and that multiple environment files are derived from a single source of truth ([`places.yaml`](#placesyaml)). Additionally, [`places watch start`](#watch-start) persistently tracks changes in [`places.yaml`](#placesyaml) and automatically manages [encryption](#encrypt), [decryption](#decrypt), and [`auto-updates`](#generate-environment) for your environment files.\n - \u2026 [Infisical](https://infisical.com/)?\n > I genuinely wanted to like it, but their documentation is currently a mess. It took me over half an hour to locate their current Python library, which wasn\u2019t even referenced in the documentation. I ultimately gave up, frustrated, when attempting to align secrets with my version tags.\n - \u2026 [HashiCorp Vault](https://www.hashicorp.com/products/vault)?\n > Yeah, [no](https://www.hashicorp.com/products/vault/pricing).\n - \u2026 git hooks?\n > Glad you asked! This project actually started as Git hooks, and you can find a very basic MVP in [places-mini](places-mini). It uses a single key to encrypt local environment files but lacks many of the convenient features of _places-env_. For example, you\u2019ll need to manually ensure that all the appropriate entries are added to `.gitignore`, among other things. Also, it uses a naughty hack to track changes and force encryption. Don't use it.\n\n- **Why is the code so bad?**\n > As I mentioned above, I\u2019m neither a professional coder nor experienced with the Python ecosystem. Additionally, I\u2019ve made some questionable decisions along the way.\n\n- **Why can\u2019t the generated environment files be styled, structured, or annotated?**\n > It's on the [roadmap](#roadmap) below.\n\n## Roadmap (unordered)\n\n* **DEV/CI/CD:** Add infos regarding development and add appropriate CI/CD\n* **Hombrew:** Distribute _places-env_ also via [Homebrew](https://brew.sh/)\n* **CI/CD:** Provide a convient Github Action and Gitlab [CI/CD](#cicd) integration for _places-env_.\n* **Comments in environment files**: Add `comment`property to variables\n* **Layouting in environment files**: Add \"meta-variables\" (eg. `places.section`) that add sections and linebreaks at gen-time.\n\n## Known issues / Limitations\n* _places-env_ does not adhere to the [YAML specifications](https://yaml.org/).\n* Only array/lists in square brackets are supported, block style arrays aren't supported (yet).\n* Single-line KV/JSON needs to be wrapped in quotes.\n\n***\n\n# places CLI Documentation\n\n## add environment\n\nAdd a new environment configuration.\n\n```shell\nplaces add environment NAME [OPTIONS]\n```\n\n<details>\n\n***\n\n<summary>Options & Arguments</summary>\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-f` | `--filepath <String>` | Path to environment file. |\n| `-w` | `--watch <Bool>` | Enable file watching. |\n| `-a` | `--alias <String>` | Environment aliases. |\n| `-k` | `--key <String>` | Key to use for encryption. |\n\n\n**Arguments**\n\n| Argument | Required |\n|----------|----------|\n| `NAME` | \u274c |\n\n***\n\n</details>\n\n## add key\n\nAdd an existing key file reference to places.yaml\n\n```shell\nplaces add key NAME [OPTIONS]\n```\n\n<details>\n\n***\n\n<summary>Options & Arguments</summary>\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-a` | `--add` | Add key reference to places.yaml |\n\n\n**Arguments**\n\n| Argument | Required |\n|----------|----------|\n| `NAME` | \u274c |\n\n***\n\n</details>\n\n## add key_from_string\n\nAdd a key from a provided string with the specified name.\n\n```shell\nplaces add key_from_string NAME KEY_STRING [OPTIONS]\n```\n\n<details>\n\n***\n\n<summary>Options & Arguments</summary>\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-a` | `--add` | Add key to places.yaml |\n| `-f` | `--force-overwrite` | Force overwrite without safety checks. |\n\n\n**Arguments**\n\n| Argument | Required |\n|----------|----------|\n| `NAME` | \u274c |\n| `KEY_STRING` | \u274c |\n\n***\n\n</details>\n\n## add setting\n\nAdd or update settings configuration.\n\n```shell\nplaces add setting [OPTIONS]\n```\n\n<details>\n\n***\n\n<summary>Options</summary>\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-sg` | `--sync-gitignore <Bool>` | Enable/disable .gitignore sync. |\n| `-i` | `--iterations <Int>` | Number of iterations for cryptography. |\n| `-hf` | `--hash-function <String>` | Hash function for cryptography. |\n| `-sm` | `--salt-mode <String>` | Salt mode for cryptography. |\n| `-sf` | `--salt-filepath <String>` | Salt filepath for cryptography. |\n| `-sv` | `--salt-value <String>` | Salt value for cryptography. |\n\n***\n\n</details>\n\n## add variable\n\nAdd a new variable configuration.\n\n```shell\nplaces add variable NAME [OPTIONS]\n```\n\n<details>\n\n***\n\n<summary>Options & Arguments</summary>\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-v` | `--value <Any>` | Value of variable / secret. |\n| `-k` | `--key <String>` | Key to use for encryption. |\n| `-u` | `--unencrypt <Bool>` | Mark value as unencrypted. |\n| `-e` | `--environment <String>` | Target environment(s). |\n\n\n**Arguments**\n\n| Argument | Required |\n|----------|----------|\n| `NAME` | \u274c |\n\n***\n\n</details>\n\n## decrypt\n\nDecrypts `.places/places.enc.yaml` into `places.yaml` file.\n\n```shell\nplaces decrypt [OPTIONS]\n```\n\n## encrypt\n\nEncrypts `places.yaml` into `.places/places.enc.yaml` file.\n\n```shell\nplaces encrypt [OPTIONS]\n```\n\n## generate environment\n\nGenerate .env files for specified environments or all environments defined in `places.yaml`\n\nThis generally follows [https://dotenv-linter.github.io/](https://dotenv-linter.github.io/) rules, with the exception of alphabetical ordering.\n\n```shell\nplaces generate environment [ENVIRONMENT]... [OPTIONS]\n```\n\n<details>\n\n***\n\n<summary>Options & Arguments</summary>\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-a` | `--all` | Generate .env files for all environments. |\n\n\n**Arguments**\n\n| Argument | Required |\n|----------|----------|\n| `ENVIRONMENT` | \u274c |\n\n***\n\n</details>\n\n## generate key\n\nGenerate a new encryption key with the specified name.\n\n```shell\nplaces generate key [NAME] [OPTIONS]\n```\n\n<details>\n\n***\n\n<summary>Options & Arguments</summary>\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-l` | `--length <Int>` | Custom length for generated key in bytes. |\n| `-a` | `--add` | Add key to places.yaml |\n\n\n**Arguments**\n\n| Argument | Required |\n|----------|----------|\n| `NAME` | \u274c |\n\n***\n\n</details>\n\n## init\n\nInitialize a new places project.\n\nAlso generates a new default encryption key and adds it to `.places/keys/`.\n\n```shell\nplaces init [OPTIONS]\n```\n\n<details>\n\n***\n\n<summary>Options</summary>\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-t` | `--template <String>` | Template to use for initialization |\n| `--list-templates` | `--list-templates` | List available templates |\n\n***\n\n</details>\n\n## run test\n\nRun tests.\n\nCurrently supported tests: e2e, cli.\n\nSpecify test names or use \u2013all flag.\n\n```shell\nplaces run test [TESTS]... [OPTIONS]\n```\n\n<details>\n\n***\n\n<summary>Options & Arguments</summary>\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-a` | `--all` | Run all tests. |\n\n\n**Arguments**\n\n| Argument | Required |\n|----------|----------|\n| `TESTS` | \u274c |\n\n***\n\n</details>\n\n## sync gitignore\n\nSync .gitignore with Places entries.\n\n```shell\nplaces sync gitignore [OPTIONS]\n```\n\n## watch start\n\nStart watching for changes.\n\n```shell\nplaces watch start [OPTIONS]\n```\n\n<details>\n\n***\n\n<summary>Options</summary>\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-s` | `--service` | Run watcher as a persistent system service. |\n| `-d` | `--daemon` | Run watcher as a background daemon. |\n\n***\n\n</details>\n\n## watch stop\n\nStop watching for changes.\n\n```shell\nplaces watch stop [OPTIONS]\n```\n\n<details>\n\n***\n\n<summary>Options</summary>\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-s` | `--service` | Stop and remove persistent system service. |\n| `-d` | `--daemon` | Stop daemon process. |\n\n***\n\n</details>\n",
"bugtrack_url": null,
"license": "MIT License Copyright (c) 2024 Marc Krenn Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ",
"summary": "securely version control environment files",
"version": "1.0.4.post2",
"project_urls": {
"homepage": "https://github.com/marckrenn/places",
"issues": "https://github.com/marckrenn/places/issues",
"repository": "https://github.com/marckrenn/places"
},
"split_keywords": [
"environments",
" environment variables",
" secrets",
" env",
" tool",
" infisical",
" dotenv",
" hashicorp",
" vault"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "6133a6f034d1844e604be8193643a444d146f3869bbba268af2549a567e83b63",
"md5": "5a89754bce6595f906bc2851c66e9606",
"sha256": "3145aff4cfeed2415484801dfcc29b0063728bae0aebd0ecc1112bbc9a60a925"
},
"downloads": -1,
"filename": "places_env-1.0.4.post2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "5a89754bce6595f906bc2851c66e9606",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.11",
"size": 56391,
"upload_time": "2024-12-11T18:51:17",
"upload_time_iso_8601": "2024-12-11T18:51:17.963103Z",
"url": "https://files.pythonhosted.org/packages/61/33/a6f034d1844e604be8193643a444d146f3869bbba268af2549a567e83b63/places_env-1.0.4.post2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "dd003661962a472d78dc8685d2ce31e0bd54c14f5b9a3f7e9fbb979a5d5bab39",
"md5": "56af0261216689e17067c28e5dc2e776",
"sha256": "33fbd9470ef3008dd0485e595d2bb4adad626c2fc5c24634e084b29d6568bc48"
},
"downloads": -1,
"filename": "places_env-1.0.4.post2.tar.gz",
"has_sig": false,
"md5_digest": "56af0261216689e17067c28e5dc2e776",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11",
"size": 9096931,
"upload_time": "2024-12-11T18:51:19",
"upload_time_iso_8601": "2024-12-11T18:51:19.785715Z",
"url": "https://files.pythonhosted.org/packages/dd/00/3661962a472d78dc8685d2ce31e0bd54c14f5b9a3f7e9fbb979a5d5bab39/places_env-1.0.4.post2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-11 18:51:19",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "marckrenn",
"github_project": "places",
"github_not_found": true,
"lcname": "places-env"
}