Secrets Management und Konfiguration bei AWS

Unsere Services werden üblicherweise durch Umgebungsvariablen konfiguriert. Diese sind abhängig von der Stage (dev, prod), in der ein Service deployed wird und werden im Vault verwaltet.

Vault dient als sicherer Speicher für Secrets aller Art. Zugangsdaten zu Test- und Produktivsystemen der Produktanbieter, Access Tokens usw. werden im Vault abgelegt und über die Vault UI bearbeitet.

Beim Deployment eines Services kommt dann Ansible ins Spiel. In dessen Konfiguration sind alle Umgebungsvariablen definiert.

service_overrides:
  name: "{{ service_name }}-prod"
  env:
    STAGE: "PROD"
    DB_PASSWORD: "{{ lookup('hashi_vault', 'secret=secret/db/prod-stage token=' + vault_token)['db.password'] }}"
    SERVICE_URL_BASE: "{{ service_base_urls.prod }}"

In dem obigen Ausschnitt ist dies exemplarisch dargestellt. Als Werte für die Umgebungsvariablen werden zum Beispiel Secrets aus Vault referenziert werden. Diese ruft Ansible beim Erzeugen eines Docker Containers ab und setzt sie entsprechend. Der Service startet und kann sie nutzen.

Dieses Vorgehen funktioniert super in unserer aktuellen Infrastruktur. Mit dem Umzug zu AWS möchten wir aber verstärkt auf AWS managed Services wie ECS setzen und mit denen integriert sich Vault nicht gut. Also, gibt’s da etwas bei AWS?

Secrets Manager und Parameter Store

Es gibt zwei AWS Services, die für die sichere Verwaltung von Secrets und Parametern verwendet werden können. Der Parameter Store eignet sich gut zum Speichern von einfachen Key-Value Paaren, wie zum Beispiel die URL eines anderen Services.

Ansicht eines Parameter in AWS Admin Console

Die Values können beliebige Zeichenketten sein. Auf dem Bild ist eine URL zu sehen, man könnte auch JSON Dokumente ablegen. Wahlweise werden Values verschlüsselt oder unverschlüsselt gespeichert. Dies geschieht transparent für den Nutzer und erfordert auf AWS Seite lediglich Rechte zur Nutzung eines KMS Schlüssels. Wird ein Value geändert, wird eine neue Revision angelegt und die Historie der Werte bleibt erhalten.

Der Secrets Manager dient primär zum sicheren Verwalten von Secrets. Im Unterschied zum Parameter Store erfolgt das Speichern hier immer verschlüsselt und dazu sind die nötigen Berechtigungen für die Nutzung eines KMS Keys erforderlich. Der Secrets Manager bietet wesentlich mehr als das Speichern von Key-Value Paaren. Beim Anlegen eines Secrets wird ein Name wie z.B. my-service-secrest und die Art des Secrets (dazu später mehr) festgelegt. Hinter diesem Namen können nun mehrere Key-Value Paare abgelegt werden.

Key-Values Ansicht in AWS Admin Console

Die Anzeige als JSON Dokument ist alternativ auch möglich.

JSON Ansicht in der AWS Admin Console

Man kann in beiden Ansichten bearbeiten, je nachdem was gerade passend erscheint.

Neben dem Speichern und Bearbeiten von Secrets bietet der Secrets Manager noch weitere Funktionen, wie z.B. das automatische Rotieren von Passwörtern. Für AWS Services wie z.B. RDS Datenbanken ist dies per Konfiguration leicht zu aktivieren und verläuft dann automatisch. Was nicht von Haus aus unterstützt wird, kann mit einer Lambda nachgerüstet werden, die z.B. Passwörter einer CouchDB rotieren kann.

Secrets Services zur Verfügung stellen

Der große Vorteil der beiden genannten AWS Services ist die Integration in das AWS Ökosystem. So können wir Secrets aus dem Secrets Manager leicht im CDK Code referenzieren und nutzen.

case Stage.DEV: {
  const secretArn = "arn:aws:secretsmanager:eu-central-1:12345678:secret:/my-service-secrest-3tlkMS"
  
  return {
    "API_USER": ecs.Secret.fromSecretsManager(
      secretsmanager.Secret.fromSecretArn(this, "s1", secretArn),
      "userservice.api.user"),
    "PASSWORD":  ecs.Secret.fromSecretsManager(
      secretsmanager.Secret.fromSecretArn(this, "s1", secretArn),
      "userservice.api.password")
  }
}

In der ECS Task Definition, die beschreibt wie ein dockerisierter Service gestartet wird, wird ebenfalls diese Referenz hinterlegt. Das ist ein wichtiges Detail. CDK bzw. Cloudformation rufen nicht den Inhalt und damit das konkrete Passwort ab. Sie ergänzen lediglich die Task Definition um eine Information, welches Secret aus dem Secrets Manager Fargate beim Starten des Container abgerufen und welcher Umgebungsvariable zugewiesen werden soll. Damit ist sichergestellt, dass das eigentliche Secret nicht unverschlüsselt zwischengespeichert wird.

In der ECS Task Definition werden die Secrets referenziert und Fargate ruft diese zur Startzeit ab.

In dem Code-Ausschnitt oben ist noch ein weiteres Details enthalten. Hier werden zwei Key-Value Paare aus einem Secret verwendet. userservice.api.user und userservice.api.password sind beide im selben Secret gespeichert. Damit haben wir nun auch gesehen, dass einzelne Values in einem Secret anhand des Keys referenziert werden können und der Service nicht ein JSON bekommst, das noch verarbeitet werden muss.

Das ist ein großer Unterschied zum Parameter Store, der dieses Feature nicht beherrscht, bzw. für den Cloudformation es nicht unterstützt. Ansonsten kann man den Parameter Store analog zum Secrets Manager nutzen.

Wie gehts weiter

Wir haben nun gesehen, dass Secrets Manager und Parameter Store sich gut mit ECS Fargate integrieren und wie wir CDK nutzen können, um unsere Services mit Secrets über Umgebungsvariablen zu versorgen.

Um in der Zukunft die automatische Rotation von Passwörtern nutzen zu können, müssen Services darüber hinaus in der Lage sein diese auch zur Laufzeit zu übernehmen. Eine ganz ähnliche Situation besteht bei Lambdas, die nicht so gut integriert sind wie ECS Fargate und man entweder die unverschlüsselten Secrets in den Umgebungsvariablen ablegt, oder im Code selbst Hand anlegen muss. Dazu mehr beim nächsten Mal.