Secrets Management mit Teller

29.06.2022 Stefan Welsch
Tech cloud-native handson

Heute möchte ich euch ein kleines, aber sehr nützliches Tool an die Hand geben. Teller ist ein "Secrets Manager" für Entwickler, wenn es darum geht, Cloud-Native Applikationen zu entwickeln. Das Projekt wurde im März 2021 ins Leben gerufen und ist komplett in Go geschrieben.

Das Prinzip dahinter ist simpel. Nutze niemals das Terminal, um Passwörter im Klartext ein- oder auszugeben. Dazu wollen wir uns kurz ein kleines Beispiel anschauen und herausfinden, warum das keine gute Praxis ist.

Angenommen ihr nutzt im Unternehmen einen Secrets Manager wie beispielsweise AWS Secrets Manager um eure Secrets zu schützen. Passwörter oder auch Tokens sind damit "sicher" verwahrt und man kann sicher sein, dass nur derjenige das Secret sieht, der auch Zugriff auf die entsprechende Entity im Secrets Manager hat. So weit so gut.

Nun gibt es einen Bug auf einem System bei der Abfrage eines gesicherten Rest-Endpunkts und ihr wollt euch gerne lokal die Daten anschauen, die von diesem Endpunkt zurückkommen. Der Endpunkt ist in unserem Fall mit einer simplen "Basic Auth" abgesichert (natürlich sollte man hier eine sicherere Methode wählen, aber uns reicht das für ein anschauliches Beispiel). Wir öffnen also unsere Konsole und setzen einen curl gegen den Endpunkt ab. Vorher kopieren wir uns noch das entsprechende Passwort aus unserem Secrets Manager in die Zwischenablage.

$ curl -H "Accept: application/json" -u "admin:12345" https://my-secure-rest-endpoint.com/api/data/get

Als Antwort erhalten wir das gewünschte Ergebnis im JSON Format.

{
    "success": "true",
  	"data": {
      "name": "Test",
      "title": "This is a title",
      "text": "This is a text"
    }
}

Wir erkennen, dass es einen Typo bei "title" gibt und können diesen auch sehr schnell beheben. Nun können wir uns dann erstmal einen Kaffee gönnen. Leider hast du vergessen den PC zu sperren, aber zum Glück wurde das Konsolenfenster geschlossen, damit niemand das Passwort sehen kann. Nun kommt jedoch ein Linux Experte vorbei und kennt den magischen Befehl, mit dem er das Passwort dennoch auslesen kann. Er setzt sich also an deinen PC, öffnet das Terminal und tippt den Befehl history. Damit kann er sich alle Befehle anzeigen lassen, welche in deiner Konsole ausgeführt wurden. Er sieht also als letzten Eintrag den Curl Befehl und damit auch den Benutzer und das Passwort im Klartext.

Lange Rede kurzer Sinn. Passwörter im Klartext auf dem Terminal einzutippen, oder diese in ENV Variablen zu speichern ist keine wirklich gute Idee. Genau hier hilft uns Teller. Ausserdem bietet uns Teller auch jede Menge Möglichkeiten, Secrets in den verschiedenen Secret Manager zu verwalten oder auszutauschen.

Schauen wir uns das an einem Beispiel an. Als erstes müssen wir uns Teller lokal installieren. Es gibt jeweils ein Binary für macOS, Linux und auch Windows. Ich nutze macOS und gebe deshalb den folgenden Befehl ein.

$ brew tap spectralops/tap && brew install teller

Anschliessend erstellen wir ein neues Projekt. Dazu tippen wir teller new und müssen anschliessend noch den Namen und die Provider konfigurieren. Der Name ist hier my-project und als Provider nehmen wir erstmal nur AWS Secrets Manager.

$ teller new
? Project name? my-project
? Select your secret providers AWS Secrets Manager
? Would you like extra confirmation before accessing secrets? No
Created file: .teller.yml

Wie wir sehen können wurde nun eine Datei namens .teller.yml in unserem Benutzerordner erstellt. Schauen wir uns diese Datei doch mal an.

$ bat .teller.yml
   1   │
   2   │ project: my-project
   3   │
   4   │ # Set this if you want to carry over parent process' environment variables
   5   │ # carry_env: true
   6   │
   7   │
   8   │ #
   9   │ # Variables
  10   │ #
  11   │ # Feel free to add options here to be used as a variable throughout
  12   │ # paths.
  13   │ #
  14   │ opts:
  15   │   region: env:AWS_REGION    # you can get env variables with the 'env:' prefix
  16   │   stage: development
  17   │
  18   │
  19   │ #
  20   │ # Providers
  21   │ #
  22   │ providers:
  23   │   # configure only from environment
  24   │   aws_secretsmanager:
  25   │     env_sync:
  26   │       path: prod/foo/bar
  27   │     env:
  28   │       FOO_BAR:
  29   │         path: prod/foo/bar
  30   │         field: SOME_KEY

Wie wir sehen können erhalten wir eine Konfigurationsdatei, welche wir nun im Detail besprechen wollen.

Linie 14 - 16: Hier können wir Options definieren, welche später mit opt ausgelesen werden können. (z.B. region)

Linie 22 - 30: Hier erfolgt die Konfiguration des Providers.

Damit wir dies nun testen können, müssen drei Dinge bereits gegeben sein: Ihr müsst im AWS Secrets Manger ein Secret haben, die AWS CLI muss installiert und konfiguriert sein und ihr müsst Zugriff auf das entsprechende Secret haben, welches ihr lesen wollt.

Ich habe mir für mein Beispiel einen Secret mit der ID dev/test/admin und zwei Schlüsseln (user/pass) angelegt. Entsprechend kann ich diesen mit der AWS CLI folgendermassen auslesen:

$ aws secretsmanager get-secret-value --secret-id dev/test/admin

ARN: arn:aws:secretsmanager:eu-central-1:XXXXXXXXXXX:secret:dev/test/admin-3mhwYt
CreatedDate: '2022-05-13T08:26:58.096000+02:00'
Name: dev/test/admin
SecretString: '{"user":"admin","pass":"12345"}'
VersionId: 31dd694d-8c3c-4405-95a5-a25813028d48
VersionStages:
- AWSCURRENT

Wer die AWS-CLI noch nicht installiert hat, sollte hier unterbrechen und dies tun. Nach der Installation muss man noch den Befehl aws configure ausführen, um Zugriff per Token zu erhalten.

Nun können wir uns ans Eigentliche wagen. In der Datei .teller.yaml ändern wir nun im aws_secretsmanager Provider bei env_sync: den Wert von path: prod/foo/bar zu path: dev/test/admin und löschen die Sektion env einfach raus. Die Datei sieht nun also folgendermassen aus:

  ...
  22   │ providers:
  23   │   # configure only from environment
  24   │   aws_secretsmanager:
  25   │     env_sync:
  26   │       path: dev/test/admin

Wollen wir unsere Änderungen also testen. Wir geben im Terminal den folgenden Befehl ein: show

$ teller show
-*- teller: loaded variables for my-project using .teller.yml -*-

[aws_secretsmanager dev/test/admin] user = ad*****
[aws_secretsmanager dev/test/admin] pass = 12*****

Wie wir sehen können, werden die Secrets korrekt ausgelesen und wir können diese nun mit Teller nutzen. Die Secrets werden bei der Ausgabe maskiert, sodass man diese nicht in Klartext lesen kann.


ℹ️ Du kannst die Maskierung der Ausgabe in der .teller.yml Konfigurationsdatei mit der Option redact_with steuern.


Natürlich können wir auch nur einen Schlüssel aus dem Secret auslesen. Dazu ändern wir in der .teller.yml folgendes und lesen den Secret anschliessend wieder mit teller show aus.

  22   │ providers:
  23   │   # configure only from environment
  24   │   aws_secretsmanager:
  25   │     env:
  26   │       ADMIN_USER:
  27   │         path: dev/test/admin
  28   │         field: user
$ teller show
-*- teller: loaded variables for my-project using .teller.yml -*-

[aws_secretsmanager dev/test/admin] ADMIN_USER = ad*****

Nun können wir sehen, dass nur noch der entsprechende Secret ausgegeben wird.

Schauen wir uns als nächstes an, wie wir diese Secrets nun in einer Applikation nutzen können. Dafür schreiben wir uns eine einfache Hello World Go App in der wir uns den User ausgeben lassen wollen. Ich erstelle mir dafür ein neues Verzeichnis teller-golang und lege hier eine Datei main.go an.

package main

import (
	"fmt"
	"os"
)

func main() {
	fmt.Printf("Hello, %s", os.Getenv("ADMIN_USER"))
}

Wenn ich das Programm jetzt ausführe erhalte ich folgende Ausgabe:

~/teller-golang ❯ go run main.go
Hello, %

Wie wir sehen können ist die Variable ADMIN_USER nicht gesetzt. Führen wir das Programm erneut mit Teller aus:

~/teller-golang ❯ teller run go run main.go
FATA[0000] could not read file                           error="open .teller.yml: no such file or directory" file=.teller.yml

Wir erhalten einen Fehler, weil in unserem Projektverzeichnis keine Teller Konfigurationsdatei enthalten ist. Durch den Fehler sehen wir nun, wie praktisch Teller an diese Stelle arbeitet. Für jedes Projektverzeichnis kann eine eigene Konfigurationsdatei angelegt werden. So wird sichergestellt, dass auch wirklich nur die Secrets genutzt werden, die es wirklich braucht. Diese Datei kann natürlich ins VCS (Version Control System) eingecheckt werden, damit alle Entwickler Zugriff auf die gleichen Secrets erhalten.

Kopieren wir uns also die Konfigurationsdatei in unser Projektverzeichnis und versuchen das ganze erneut.

~/teller-golang ❯ cp ~/.teller.yml ~/teller-golang/
~/teller-golang ❯ teller run go run main.go
-*- teller: loaded variables for my-project using .teller.yml -*-
Hello, admin%

Wie wir nun sehen können werden die Teller Variablen geladen und der Go Applikation in Form von Environment Variablen zur Verfügung gestellt. :rocket:

Secrets Management mit Teller

Teller bietet uns noch weitere nützliche Tools, welche uns nicht nur die Entwicklung, sondern generell den Umgang mit Secrets sehr stark vereinfachen. Wir können beispielsweise neue Secrets direkt im Provider anlegen. Dazu erweitern wir die .teller.yml um folgenden Eintrag

aws_secretsmanager:
	env:
  	ADMIN_USER:
  		path: dev/test/admin
  		field: user
  + SECRET:
  +	  path: dev/test/admin
  +	  field: secret

Nun führen wir den folgenden Befehl im Terminal aus.

$ teller put SECRET=my-new-secret --providers aws_secretsmanager -c .teller.yml

Durch diesen Befehl, wird direkt im AWS Secrets Manager ein neuer Schlüssel im angegebenen Pfad angelegt.

image-20220513100834209

Weiterhin bietet uns Teller auch die Möglichkeit Secrets zwischen zwei oder mehreren Providern zu kopieren und diese sogar zu synchronisieren.

Dazu müssen wir natürlich erstmal einen weiteren Provider hinzufügen. Wir öffnen also die Teller Konfigurationsdatei und fügen Google Secrets Manager als Provider hinzu. Sollten Sie noch kein Google Konto haben, oder die CLI noch nicht installiert haben, können Sie dies hier tun. Ausserdem wird ein Service Account benötigt. Die Anleitung dazu finden Sie hier.

Ich erstelle mir in der Google Cloud Console ein neues Projekt teller-golang und erstelle innerhalb von diesem Projekt ein Secret MY_USER=bnova-stefan.

google_secretmanager:
  env:
    MY_USER:
      path: projects/teller-golang/secrets/MY_USER/versions/1

Nachdem wir den Provider eingefügt haben, können wir schauen, ob das Secret gelesen werden kann. Wenn alle Konfigurationen korrekt sind, sollten wir die folgende Ausgabe sehen.

$ teller show
-*- teller: loaded variables for my-project using .teller.yml -*-

[aws_secretsmanager dev/test/admin] SECRET = my*****
[google_secretmanager projects/tell...SER/versions/1] MY_USER = bn*****
[aws_secretsmanager dev/test/admin] ADMIN_USER = ad*****

Nun wollen wir den Secret vom Google Secrets Manager zum AWS Secret Manager kopieren. Synchronisierung ist leider im Google Secret Manager nicht möglich.

Dazu geben wir den folgenden Befehl im Terminal an.

$ teller copy --from google_secretmanager --to aws_secretsmanager
Put MY_USER in aws_secretsmanager: no such key 'MY_USER' in mapping

Wir sehen eine Fehlermeldung, dass es den Key MY_USER im AWS Secret Manager noch nicht gibt. Bevor wir also einen Secret Value kopieren können, muss es im Ziel erst das entsprechende Feld geben. Wir erweitern unsere .teller.yaml also um den folgenden Eintrag und führen anschliessend den Befehl erneut aus.

providers:
  aws_secretsmanager:
    env:
      ADMIN_USER:
        path: dev/test/admin
        field: user
      SECRET:
        path: dev/test/admin
        field: secret
    + MY_USER:
    +   path: dev/test/admin
    +   field: my_user
$ teller copy --from google_secretmanager --to aws_secretsmanager
Put MY_USER (dev/test/admin) in aws_secretsmanager: OK.

Wie wir sehen können konnte der Befehl erfolgreich ausgeführt werden. Schauen wir uns das Ergebnis direkt im AWS Secret Manager an.

image-20220516130615590

Et voilà! Der neue Secret wurde erfolgreich kopiert. Auch teller show zeigt uns den neuen Secret im AWS natürlich an.

$ teller show
-*- teller: loaded variables for my-project using .teller.yml -*-

[aws_secretsmanager dev/test/admin] SECRET = my*****
[aws_secretsmanager dev/test/admin] MY_USER = bn*****
[google_secretmanager projects/tell...SER/versions/1] MY_USER = bn*****
[aws_secretsmanager dev/test/admin] ADMIN_USER = ad*****

Nun haben wir gesehen, wie wir einen Secret anlegen und kopieren können. Wollen wir uns noch anschauen, wie man einen Secret wieder löschen kann. Hierzu bietet uns Teller den teller delete Befehl. Wir wollen jetzt nicht mehr den Admin User in unserer Applikation nutzen, sondern neu MY_USER. Wir können ADMIN_USER also löschen. Dazu führen wir den folgenden Befehl aus.

$ teller delete ADMIN_USER --providers aws_secretsmanager -c .teller.yml
Delete ADMIN_USER (dev/test/admin) in aws_secretsmanager: OK.

Gelöscht! Wir schauen zur Sicherheit noch einmal direkt im AWS Secret Manager.

image-20220516131434753

Und auch hier wurde das Secret gelöscht. Schauen wir uns doch noch an, was teller show uns sagt.

teller show
-*- teller: loaded variables for my-project using .teller.yml -*-

[aws_secretsmanager dev/test/admin] SECRET = my*****
[aws_secretsmanager dev/test/admin] MY_USER = bn*****
[google_secretmanager projects/tell...SER/versions/1] MY_USER = bn*****
[aws_secretsmanager dev/test/admin missing] ADMIN_USER

Wir sehen in der letzten Zeile, dass der Status für das Secret ADMIN_USER im AWS Secret Manager "missing" ist. Das ist so richtig und wir können den Eintrag aus der teller.yaml entfernen.

providers:
  aws_secretsmanager:
    env:
      ADMIN_USER: <-- löschen
        path: dev/test/admin <-- löschen
        field: user <-- löschen
      SECRET:
        path: dev/test/admin
        field: secret
      MY_USER:
        path: dev/test/admin
        field: my_user

Wir haben das Secret so wieder vollständig entfernt.


ℹ️ Nicht alle Provider unterstützen auch alle Befehle. Der Google Secretmanager unterstützt beispielsweise teller delete nicht.


Zusammenfassung

Teller ist ein sehr praktisches Tool, wenn man im Developer Team Secrets sharen will, ohne diese im Klartext anzeigen zu müssen. Durch die praktische Konfigurationsdatei teller.yaml ist es so möglich, für alle Entwickler direkt die gleichen Secrets im Projekt zu hinterlegen. Die Entwickler müssen lediglich die Zugriffsberechtigungen auf die Secrets haben.

Das Secret Management mit Teller ist sehr effektiv, allerdings ist der grosse Nachteil, dass nicht alle Provider die gleichen Funktionalitäten unterstützen. So bietet beispielsweise AWS Secret Manager eine Synchronisation der Secrets an, während Google dies nicht unterstützt. Den Use-Case sehe ich allerdings im Daily Business nur bedingt (z.B Migrationsprojekt von AWS zu Google).

Teller bietet uns mit teller redact ein weiteres Tool an, mit welchem wir Passwörter aus beispielsweise einer Logdatei rausfiltern können. Das ist ebenfalls sehr praktisch, um sicherzustellen, dass man keine Passwörter in der Vergangenheit geloggt hat, ohne dass man die Logdatei löschen muss.

Wir als b-nova werden Teller für zukünftige interne Entwicklungsprojekte auf jeden Fall in Betracht ziehen. Stay tuned! 😄

Stefan Welsch – Pionier, Stuntman, Mentor. Als Gründer von b-nova ist Stefan immer auf der Suche nach neuen und vielversprechenden Entwicklungsfeldern. Er ist durch und durch Pragmatiker und schreibt daher auch am liebsten Beiträge die sich möglichst nahe an 'real-world' Szenarien anlehnen.