Mozilla SOPS: kontejner na tajemství do cloudu i na doma

4. 1. 2023
Doba čtení: 8 minut

Sdílet

 Autor: Depositphotos
Pokud potřebujeme uložit do repozitáře nějaká hesla, API klíče, certifikáty či jiné citlivé informace, je dobrý nápad je zašifrovat. SOPS je nástroj, který to pohodlně umožňuje.

Mozilla sops (Secrets OPerationS) je kontejner a editor šifrovaných souborů, který podporuje formáty YAML, JSON, ENV, INI a BINARY a šifruje pomocí AWS KMS, GCP KMS, Azure Key Vault, PGP a nedávno popisovaného age.

Co se dozvíte v článku
  1. Vytvoření šifrovaného kontejneru
  2. Editace souboru
  3. Další formáty
  4. Výchozí nastavení klíčů
  5. Použití mimo CLI
  6. Použití v Kubernetes
  7. Další možnosti
  8. Využití

Podobných nástrojů existuje více, sops se inspiroval nástrojem credstash a passwordstore. Samotný SOPS je napsaný v Go a je distribuován jako statická binárka, takže běží skoro všude. Zároveň umí pracovat se různými nástroji pro šifrování, takže je velmi vhodný pro slepování různých nástrojů, které se spoluprací původně nepočítaly.

Instalace probíhá pomocí stažení binárky a přidání do PATH, případně pomocí binenv.

$ binenv install sops

Vytvoření šifrovaného kontejneru

Pro účely tohoto příkladu si vytvoříme soubor secrets.yaml a do něho si vložime nějaké citlivé informace:

routerAccess:
  username: admin
  password: nbusr124

Před zašifrováním souboru je dobrý nápad ujasnit si, kdo ho bude moci rozšifrovat. V našem případě to budou dva vývojáři s GPG klíči a interní nástroj pro deployment, který používá nástroj age.

Použijeme parametr --encrypt ( -e), a zadáme identity, které mohou zašifrovaný soubor rozšifrovat. Pro různé druhy identit (GPG, age, …) nabízí sops různé přepínače.

$ sops --encrypt \
    --age age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw \
    --pgp 4DEDE685FFA0FC4976B3EF37F7E33058D6AB767D,CE8981E4BBCBB6EC0
    --output secrets.enc.yaml \
    secrets.yaml

Pak sops uloží soubor s šifrovaným kontejnerem secrets.enc.yaml.

Kromě GPG a age je možné použít i další nástroje pro správu klíčů, nejčastěji používáme třeba AWS KMS, které umožňuje poměrně pěkně řídit přístup ke klíči pomocí rolí IAM. Stačí zadat --kms a klíče ARN KMS.

Naopak, pokud nechceme zadávat parametry na příkazové řádce, můžeme použít proměnné prostředí – laskavého čtenáře zde odkážu na README. Proměnné prostředí s oblibou používáme například v CI/CD pipelinách.

$ export SOPS_AGE_RECIPIENTS=age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw
$ export SOPS_PGP_FP=4DEDE685FFA0FC4976B3EF37F7E33058D6AB767D,CE8981E4BBCBB6EC08A2246BCBFB9F30F3F82399
$ sops --encrypt secrets.yaml > secrets.enc.yaml

Pokud rozebereme zašifrovaný soubor (kontejner) secrets.enc.yaml, zjistíme, že obsahuje otevřené původní klíče YAML, a zašifrované jsou pouze jejich hodnoty.

Kromě toho si sops do souboru YAML přidá nový klíč sops:, který obsahuje nový klíč AES (v dokumentaci nazývaný jako data encryption key), který je zašifrovaný pomocí veřejných klíčů, které jsme zadali při šifrování.

Výhodou je, že zašifrovaný kontejner dokážeme vložit do gitovského repozitáře. Pokud ho případný útočník získá bez přístupu ke klíčům, neměl by být schopen kontejner rozšifrovat.

routerAccess:
    username: ENC[AES256_GCM,data:VvjNI8A=,iv:PxSN8MDBPY ...
    password: ENC[AES256_GCM,data:cuGAxjkXGRY=,iv:VK+ZYre ...

sops:
    age:
        - recipient: age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfa ...
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaMXUrd0RCVU9sZDg0SXhH
            bUdoZUwzYkU3NUpDc28xQklFMHhNeUtCZzFZClEwaUxZYWJkRkcxZHd1aDU4UTdB
            ...
    pgp:
        - created_at: "2022-12-20T21:05:50Z"
          enc: |
            -----BEGIN PGP MESSAGE-----
            hQIMA/fjMFjWq3Z9AQ/+Nsz9DnIXnrFOF4yywthtd+OWPVmRa1NxR1B7+0MsO6TA
            xMF0U0/Kc9iNbwvNSh4zNpJ/XDrwzr1BVR+eKhIkJRdH3AZ8y3sjagYeB/Z8YNG9
            ...

Je zřejmé, že předchozí příklad očekával, že v lokálním úložišti GPG máme dostupné veřejné klíče adresátů.

Editace souboru

Pokud budeme chtít obsah šifrovaného kontejneru zobrazit, můžeme použít:

$ sops secrets.enc.yaml

sops zjistí, které soukromé klíče má k dispozici, vhodný klíč použije pro rozšifrování, otevře editor a v něm otevře dočasnou rozšifrovanou verzi souboru. Po zavření editoru se soubor zase zašifruje, uloží a dočasná kopie se zahodí.

Pro rozšifrování nám stačí použít sops s přepínačem --decrypt ( -d), při dešifrování se zahodí metadata uložená pod klíčem  sops:.

$ sops -d secrets.enc.yaml
routerAccess:
    username: admin
    password: nbusr124

Další formáty

Kromě YAML umí sops pracovat i se soubory ve formátu JSON, INI a dotenv, typ souboru se snaží rozpoznat podle přípony. Pokud neuhodne, můžeme mu napovědět přepínačem --input-type ( -i) a --output-type ( -o). Do obou typů souborů si sops vkládá metadata potřebná pro dešifrování pod klíč  sops.

Elegantní použití sops u a souborů ve formátu dotenv je pro správu proměnných prostředí. Pokud mám k dispozici například soubor s údaji pro AWS (config.ini ), mohu si ho zašifrovat.

# config.ini
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

API klíče zašifrujeme a dotenv si necháme uložit ve formátu JSON.

$ sops --encrypt \
    --input-type dotenv --output-type json \
    --output config.enc.json \
    config.ini

Výsledkem bude opět validní JSON, do kterého si sops přidal vlastní klíče s metadaty potřebnými pro dešifrování.

sops potom umí rozšifrovat kontejner, uložit klíče ho do proměnných prostředí a spustit program, který proměnné prostředí použije.

$ sops exec-env config.enc.json 'aws sts get-caller-identity'

Pokud potřebujeme poslat obsah původního souboru do nějakého programu, můžeme použít volání sops exec-file. sops potom vytvoří pojmenovanou rouru (FIFO), do které na požádání pošle obsah souboru.

$ sops exec-file --no-fifo config.enc.json 'cat {}'

Výchozí nastavení klíčů

Pokud nechceme při vytváření šifrovaného kontejneru pokaždé zadávat klíče, můžeme si pravidla pro používání klíčů uložit do souboru  ~/.sops.yaml.

# use one age key and these PGP ones
creation_rules:
    - path_regex: '(\.yml|\.yaml)$'
      age: age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw
      pgp: >-
        4DEDE685FFA0FC4976B3EF37F7E33058D6AB767D,
        CE8981E4BBCBB6EC08A2246BCBFB9F30F3F82399

Při vytváření nového šifrovaného kontejneru zkusí sops najít soubor .sops.yaml v aktuálním adresáři a v případě úspěchu použije pravidla z něj. Pokud ho nenajde, zkusí se podívat do nadřízeného adresáře.

Doporučená praktika je tedy uložit .sops.yaml s popisem pravidel do kořene gitovského adresáře s projektem.

Pokud potřebujeme upravit šifrovací klíče podle aktuálních pravidel, můžeme použít příkaz  updatekeys.

$ sops updatekeys secrets.enc.yaml

Nástroj sops porovná původní a požadované klíče použité pro šifrování kontejneru, porovná je, zobrazí změny a zeptá se, jestli to myslíme upřímně. Po potvrzení se změny v klíčích aplikují do kontejneru.

Použití mimo CLI

Existuje Terraform provider pro SOPS, který umožňuje šifrovat a dešifrovat soubory přímo z Terraformu. Stačí mít k dispozici ty správné poloviny klíčů, které byly použity při šifrování.

Terraform provider sops přidává Terraform Data Source sops_file a sops-external, které umí otevřít kontejner SOPS.

data "sops_file" "production" {
  source_file = "secrets/production.yml"
}

Výsledek Data Source pak můžeme použít ve zbytku modulu.

output "release_password" {
  value = data.sops_file.production.data["release_password"]
}

Použití v Kubernetes

Pro Kubernetes existuje několik wrapperů okolo sops, které umožňují používat sops podle zvyklostí v Kubernetes.

Jeden z nich je sops operator – očekává v API serveru uložené objekty CRD typu SopsSecret, které obsahují právě šifrovaný kontejner. Po zapnutí pak operátor na pozadí šifrované kontejnery otevře a uloží je do Kubernetes Secrets. Při změně SopsSecret se automaticky aktualizují i relevantní Secrets.

Mějme CRD otevřený objekt v secrets-template.yml, který necheme uložit do gitu.

apiVersion: isindir.github.com/v1alpha2
kind: SopsSecret
metadata:
    name: wrapped-secrets
spec:
    secretTemplates:
        - name: pull-secret
          type: kubernetes.io/dockerconfigjson
          data:
            .dockerconfigjson: |
                IF9fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fXwo8IE5hIHRvbXRvIG3DrXN0
                xJsgbcWvxb5lIGLDvXQgVmHFoWUgcmVrbGFtYSEgPgogLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t
                LS0tLS0tLS0tLS0tLS0tCiAgICAgICAgXCAgIF5fX14KICAgICAgICAgXCAgKG9vKVxfX19fX19f
                CiAgICAgICAgICAgIChfXylcICAgICAgIClcL1wKICAgICAgICAgICAgICAgIHx8LS0tLXcgfAog
                ICAgICAgICAgICAgICAgfHwgICAgIHx8Cg==
        - name: aws-access-tokens
          type: Opaque
          stringData:
            AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE
            AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

YAML soubor s CRD objektem pak můžeme zašifrovat:

$ sops \
    --encrypt \
    --encrypted-regex '^(stringData|data)$' \
    --output encrypted-secrets.yml \
    secret-template.yml

Dostaneme opět zašifrovaný kontejner. Protože jsme použili při spuštění sops  parametr --encrypted-regex, zašifroval se pouze obsah pod klíči stringDatadata.

apiVersion: isindir.github.com/v1alpha2
kind: SopsSecret
metadata:
    name: wrapped-secrets
spec:
    secretTemplates:
        - name: pull-secret
          type: kubernetes.io/dockerconfigjson
          data:
            .dockerconfigjson: ENC[AES256_GCM,data:Ofp6i/8N0...
        - name: aws-access-tokens
          type: Opaque
          stringData:
            AWS_ACCESS_KEY_ID: ENC[AES256_GCM,data:4U7jdT0G8M...
            AWS_SECRET_ACCESS_KEY: ENC[AES256_GCM,data:5Ugz4H...
sops:
  ...

Protože do Kubernetes vkládáme validní YAML manifest, API server ho akceptuje a zbytek souboru stále vypadá jako validní manifest Kubernetes v YAML a můžeme ho vložit do API serveru.

Je potom úkolem sops-operatoru, aby vytvořil Kubernetes secrets pull-secret a aws-access-token. Pokud se změní Kubernetes objekt SopsSecret se zašifrovaným kontejnerem, operátor se probudí a upraví závislé secrets.

Podobnou funkcionality v Kubernetes (bez sops) samozřejmě nabízí mnoho  dalších vlastností, např sealed-secrets nebo external-secrets. Výhodou použití SOPS je možnost implementace šifrování s použitím různých druhů klíčů.

Další možnosti

Zajímavou možností je i použití příkazu sops publish, kdy sops umí po změně šifrovaný kontejner odložit do nějakého externího úložiště, například S3, GCP bucketu či Hashicorp Vaultu. Z externího úložiště si pak kontejner může stáhnout nějaká další část CI pipeline.

Kromě použití jednoho klíče pro šifrování je možné v rámci jednoho kontejneru použít více klíčů. Používá se pro to nastavení key_groups a v  .sops.yaml.

Vlastností, které sops umí je mnohem více, laskavého čtenáře odkážu do dokumentace – sops umí využít Shamir thresholds, více klíčů v jednom souboru a mnoho dalších vlastností.

Využití

sops používáme u zákazníků nejčastěji pro šifrování tajných informací v terraformových modulech v prostředí AWS. Při zašifrování hesel s využitím klíče z KMS dokážeme poměrně pěkně řídit přístup včetně základního auditování.

bitcoin_skoleni

Zdá se, že SOPS se stává oblíbeným nástrojem pro ukládání zašifrovaných tajemství. Kromě zmiňovaného použití v Terraformu a Kubernetes jsem narazil na podporu v Terragruntu, Ansible, Fluxu a ve spoustě dalších nástrojů.

Velikou výhodou je, že se dá použít jak při nasazení v cloudech, tak i na vlastním hardware.

Autor článku

Věroš Kaplan pracuje jako nájemný správce serverů na volné noze. Před několika lety objevil kouzlo automatizace a už ho to nepustilo. Zastává názor, že nudné úkoly mají dělat počítače – a měly by se tedy používat i pro správu dalších počítačů.