Skip to content

cotitech-io/coti-secret-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Coti Secret Manager: lightweight encrypted secrets for Docker services

Coti Secret Manager is a tiny Python utility for handling a single encrypted secret (e.g., a private key) stored as a JSON file, with the password supplied at runtime.

It’s designed for Docker environments where you want:

  • the encrypted secret JSON stored on a shared/bind volume (e.g. ./secrets/my-secret.json)
  • the password injected only at service startup
  • the password written to tmpfs inside the container (e.g. /run/secrets/coti_sk.pw)
  • the service to wait for the password, decrypt in memory, then delete the password file

Install

pip

Install from GitHub (example):

pip install "git+https://github.com/<you>/coti-secret.git@main"

Import name is coti_secret (underscores), even if the distribution name is coti-secret.


On the production service machine using docker-compose

Recommended production approach:

  • install nothing with pip on the host
  • use a tool container (part of your docker-compose.yml) to run encryption and password injection commands

You’ll typically have:

  • your main service container (e.g. my-docker-service)
  • a helper tool container (e.g. coti-secret-tool) with Python + coti_secret installed

Example compose snippet:

services:
  coti-secret-tool:
    image: ghcr.io/<you>/coti-secret-tool:latest
    volumes:
      - ./secrets:/keystore
    entrypoint: ["python", "-m", "coti_secret.cli"]

  my-docker-service:
    image: your-service-image:latest
    volumes:
      - ./secrets:/keystore:ro
    tmpfs:
      - /run/secrets:rw,noexec,nosuid,nodev
    environment:
      ENCRYPTED_FILE: /keystore/my-secret.json
      PW_FILE: /run/secrets/coti_sk.pw

CLI

local

Encrypt secret

Option A: run the CLI locally (if you installed with pip):

python -m coti_secret.cli encrypt --out ./secrets/my-secret.json
# paste secret via stdin, then enter password

Option B: run the CLI via the Docker tool container:

docker compose run --rm coti-secret-tool encrypt --out /keystore/my-secret.json
# paste secret via stdin, then enter password

The output file will be written to ./secrets/my-secret.json.


On production using docker-compose

Encrypt secret

Run encryption inside the tool container so the production host doesn’t need Python tooling installed:

docker compose run --rm coti-secret-tool encrypt --out /keystore/my-secret.json

You will:

  • paste the secret
  • enter the password (hidden)
  • the tool writes ./secrets/my-secret.json

Save the pw on tmpfs (startup)

Start the service:

docker compose up -d

Then inject the password into the running container’s tmpfs (stdin → file). Example:

docker compose exec -T my-docker-service sh -lc "umask 077; cat > /run/secrets/coti_sk.pw; chmod 600 /run/secrets/coti_sk.pw"
# then type/paste the password + Enter, Ctrl-D

Why tmpfs?
Deleting a file on tmpfs is meaningfully safer than attempting to “shred” a disk-backed file (journaling/COW/SSD wear-leveling make disk shredding unreliable).


Reading secrets

Python code

import os
from coti_secret import SecretManager, SecretType

encrypted_file = os.environ["ENCRYPTED_FILE"]
pw_file = os.environ.get("PW_FILE", "/run/secrets/coti_sk.pw")

# choose behavior by environment
st = SecretType.for_environment(os.getenv("ENVIRONMENT", "prod"), pw_file=pw_file)

secret_bytes = SecretManager.get(encrypted_file, secret_type=st)

# secret_bytes are now decrypted in memory
# pw_file is shredded+deleted (best-effort) after attempt

SecretManager.get():

  • waits until the password source is available (pw-file strategy)
  • decrypts into memory
  • deletes the pw file (best-effort shred + unlink)

Dev vs prod usage

prod (recommended)

  • Encrypted secret JSON lives on a volume: ./secrets/my-secret.json
  • Password is injected at startup into container tmpfs: /run/secrets/coti_sk.pw
  • SecretType.for_environment("prod") expects the password file (waits until it appears)

Pros: password is never stored on disk or in shared volumes.

dev (convenience)

In dev you may prefer a password in an env var:

export COTI_SK_PW="dev-password"
export ENVIRONMENT="dev"

In code:

from coti_secret import SecretManager, SecretType

st = SecretType.for_environment("dev", pw_env_var="COTI_SK_PW", delete_env_var_after_read=True)
secret = SecretManager.get("./secrets/my-secret.json", secret_type=st)

Warning: env vars are easier to leak into logs/process inspection and are generally weaker than tmpfs password injection.


Future: Using enclave

A strong next step for high-security production is to avoid password-based decryption entirely and move to an enclave-backed approach such as:

  • decrypting inside a hardware-backed environment (enclave/TEE)
  • using a cloud KMS with envelope encryption
  • using HSMs / hardware tokens so raw private keys never exist in service memory

This changes the model from “decrypt secret at startup” to “perform crypto operations without exporting the key.”

About

Python secret manager to be used for Coti services

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published