digitalundso

Markdown Syntax Guide

Table of contents

Overview

Wer sich dem Paradigma der Infrastructure as Code hergegeben hat wird früher oder später mit der Situation konfrontiert sein, Passwörter und Secrets irgendwo ablegen zu müssen. Und wer dann noch als passionierter Homelaber seinen Code per git in einem Repo versioniert und dieses dann z.B. bei codeberg.org pusht, möchte seine Passwörter, Secrets oder andere Dinge, die nicht von allen gesehen werden sollen, dort nicht in Klartext sehen.

Mittlerweile gibt es dafür glücklicherweise diverse Lösungen und eine, die ich sehr gerne in verschiedenen Kontexten benutze, ist SOPS.

Im folgenden möchte ich meine Erfahrung mit SOPS im Zusammenspiel mit Ansible und OpenTofu beschreiben.

SOPS

Für mich stellt SOPS eine großartige Lösung dar, da es mir ermöglicht, Secrets kontextabhängig verwalten und einsetzen zu können sowie die Verwaltung der Secrets in einem Team effizient zu steuern. SOPS ist dabei kein eigener Verschlüsselungsalgorithmus sondern vielmehr ein Verwaltungstool, um eben verschiedene Verschlüsselungsalgorithmen eben zur Verschlüsselung zu nutzen. Aktuell sind das PGP/GPG, age, OpenBao/Hashicorp Vault, Azure Key Vault, Google Cloud Platform KMS und AWS KMS.

age

As mentioned, SOPS can use different encryption algorithms. One of those is age, "[..] a simple, modern and secure file encryption tool, format, and Go library. It features small explicit keys, no config options, and UNIX-style composability."

$ age-keygen -o age-key.txt

This will create a file called age-key.txt that contains the public and private part of your new age encryption key. You can share the public part of your key much like a public SSH key but more on that later.

OpenBao

Ich werde an dieser Stelle nicht auf die Installation und grundlegende Konfiguration von OpenBao eingehen. Details sind hier zu finden.

Im Grunde funktioniert die Integration von SOPS und OpenBao genau so, wie bei SOPS und age nur mit dem Unterschied, dass mein Key zum ver- und entschlüsseln nicht lokal auf meinem PC liegt, sondern mir durch OpenBao bereitgestellt wird. Es wird nämlich die Funktionalität der sogenannten Transit-Keys genutzt, auf die SOPS mittels API zugreifen kann.

Die rudimentäre Einrichtung eines Secret Stores für Transit Keys in OpenBao geht wie folgt:

$ bao secrets enable -path=sops transit

Im Anschluss können unterschiedliche Transit-Keys eingerichtet werden:

$ bao write sops/keys/development type=rsa-4096
$ bao write sops/keys/production type=rsa-4096

Damit ist die grundlegende Einrichtung auf dem OpenBao-Server fertiggestellt. Weiter geht es auf dem Client. Da SOPS an dieser Stelle eigentlich einen Hashicorp Vault erwartet, müssen die Umgebungsvariablen entsprechend geändert werden. Damit SOPS die Verbindung aufbauen kann benötigen wir mindestens diese Umgebungsvariablen:

$ export VAULT_ADDR='s.oMeRaNdOMTokEn'

Was ist der Vorteil bei der Nutzung von OpenBao und SOPS? Für mich sticht insbesondere das Rollen- und Rechtekonzept von OpenBao hier besonders hervor. Dadurch das ich mittels Policies unterschiedlichen Usern (die z.B. aus einem LDAP kommen) Zugriff auf unterschiedliche Transit-Keys gegeben kann und diese Transit-Keys wiederum mittels der .sops.yaml auf unterschiedliche Dateien oder Ordnern (mehr dazu später) geben kann, habe ich eine gute granulare Kontrolle darüber, wer Zugriff auf meine Secrets haben darf.

Der Nachteil ist natürlich, dass wir darauf angewiesen sind, bei der Ver- und Entschlüsselung immer eine Verbindung zum OpenBao-Server zu haben. Fällt der Dienst aus, kann auch SOPS nicht genutzt werden. Wie bei allen wichtigen infrastrukturellen Diensten muss auch hier dafür Sorge getragen werden, dass die Verfügbarkeit gewährleistet ist.

Es spricht aber nichts dagegen, mehrere Verschlüsselungsbackends miteinander zu kombinieren. Im folgenden werde ich auf genau diese Option im Detail eingehen.

Konfiguration

Möchte ich SOPS in Rahmen eines Projektes einsetzen, wird eine .sops.yaml Datei benötigt, die in der einfachsten Art und Weise wie folgt aussehen kann:

creation_rules:
    - age: age1npayaryxhujm49j9mhgrl0a76x97pl5g504eskzpsd3qjhsv74hq28py8t
creation_rules:
    - age: age1tewuayxvre4n35kqe6espx4fyyjk0u2c7n7lpautum2egd7uvedqw2k4en

Hiermit wird definiert, dass alle Dateien mit einem age-Key von SOPS ver- bzw. entschlüsselt werden sollen. Der Wert hinter - age: ist der öffentliche Teil des age-Keys.

Damit aber noch nicht genug. Mithilfe der Datei .sops.yaml kann ich auch komplexere Regeln aufbauen, die auf Datei- oder Ordnernamen basieren:

1creation_rules:
2 - path_regex: \.dev\.yaml$
3 age: age12dff5uxteqzf530y9nhjeqkzylxtjr2rnx76mq5re77cwkpupchq42adgz
4 - path_regex: \.prod\.yaml$
5 age: age1tewuayxvre4n35kqe6espx4fyyjk0u2c7n7lpautum2egd7uvedqw2k4en
6 - path_regex: .*/development/.*
7 hc_vault_transit_uri: "http://localhost:8200/v1/sops/keys/secondkey"
8 - age: age1npayaryxhujm49j9mhgrl0a76x97pl5g504eskzpsd3qjhsv74hq28py8t

Dieses Beispiel zeigt, dass Dateien, die den Suffix .dev.yaml (also z.B. secrets.dev.yaml) enthalten, mithilfe des age-Keys mit der Endung adgz ver- bzw. entschlüsselt werden und Dateien, die den Suffix .prod.yaml enthalten, entsprechend mit dem age-Key mit der Endung k4en. Mithilfe der Zeile path_regex: .*/development/.* geben wir an, dass alle Dateien im Ordner development - unabhängig vom Namen und der Dateiendung mit einen transit-Key von OpenBao ver- und entschlüsselt werden sollen. Über den Eintrag in der letzten Zeile weisen wir SOPS an, für alle anderen Dateien den Key der Endung py8t zu nutzen.

Kann jetzt aber jeder, der Zugriff auf das git-Repo hat einfach seinen eigenen Public Key bzw. eine neue Transit URI hinzufügen und dann die bereits verschlüsselten Dateien einsehen? Natürlich nicht! Das reine Hinzufügen eines neues Keys oder einer neuen URI führt nicht automatisch dazu,

SOPS in action

Ansible

Für das Management von Secrets in Ansible gibt es eine Vielzahl unterschiedlicher Optionen. Die naheliegenste ist natürlich die Nutzung von Ansible Vault. Der große Vorteil von Ansible Vault ist, dass es direkt bei jeder Installation von Ansible direkt mit dabei ist und daher sofort einsatzbereit ist. Grundsätzlich spricht nichts gegen den Einsatz von Ansible Vault, da es bestens in das Ökosystem von Ansible eingebunden ist. Aber sobald man anfängt, Secrets in größeren Teams gemeinsam zu verwalten, merkt man schnell, das Ansible Vault eher für einen überschaubaren Einsatz gedacht ist (so zumindest meine Einschätzung).

Using SOPS without a Ansible module

[defaults]
vars_plugins_enabled = host_group_vars,community.sops.sops

What will happen if you want to interact with the variables but are not allowed (or forgot to set up you age keys properly)?

$ ansible-inventory --list
ERROR! error with file /home/seike/Repos/sops_demo/host_vars/localhost.sops.yml: CouldNotRetrieveKey exited with code 128: Failed to get the data key required to decrypt the SOPS file.

Group 0: FAILED
  age1npayaryxhujm49j9mhgrl0a76x97pl5g504eskzpsd3qjhsv74hq28py8t: FAILED
    - | failed to create reader for decrypting sops data key with
      | age: no identity matched any of the recipients

Recovery failed because no master key was able to decrypt the file. In
order for SOPS to recover the file, at least one key has to be successful,
but none were.

In case of a dynamic Ansible inventory, the SOPS module is not of much help here. Due to the fact, that modules are only used at the role or play level, anything before can't benefit from features of the module.

In the case of the Hetzner inventory plugin, the easiest way to provide the API key is to use it as an environment variable HCLOUD_TOKEN.

$ sops exec-env secrets.sops.yaml 'ansible-inventory --list'
$ sops exec-env secrets.sops.yaml 'export HCLOUD_TOKEN=$hetzner_token; ansible-inventory --list'

OpenTofu

Blockquote

And bold, italics, and even italics and later bold. Even strikethrough. A link to somewhere.

And code highlighting:

var foo = 'bar';

function baz(s) {
   return foo + ':' + s;
}

Or inline code like var foo = 'bar';.

Or an image of bears

bears

The end ...

Tags: #markdown #syntax