AWS Berechtigungen für Kubernetes Pods

In diesem Post zeigen wir euch, wie ihr AWS Berechtigungen für Kubernetes Pods richtig vergebt. So gibt es keine bösen Überraschungen, weil die Web-Applikation auf den S3 Bucket mit den Finanzberichten zugreifen darf 😱.

Warum brauche ich überhaupt AWS-Berechtigungen für meine Pods?

Will man aus einem Pod heraus die AWS API benutzen, braucht man dafür die entsprechenden Berechtigungen. Häufig benutzte Aktionen sind:

  • Dateien zu S3 hochladen
  • Daten aus DynamoDB abrufen
  • Nachrichten in eine SQS Queue ablegen

Üblicherweise implementiert man die Kommunikation mit der AWS Rest API nicht selbst, sondern nutzt die vorhandenen AWS Software Development Kits (kurz SDK).

Damit das SDK die gewünschte Aktion ausführen darf, müssen wir Anmeldeinformationen zur Verfügung stellen, die über die nötigen Berechtigungen verfügen. Dies kann auf zwei unterschiedlichen Wegen passieren:

In der Default Credential Provider Chain geht das SDK eine Liste von Möglichkeiten durch, um die Anmeldeinformationen zu bekommen. Für das Java SDK sind dies die folgenden:

Anmeldeinformationen direkt angeben

In allen AWS SDKs ist möglich, die Anmeldeinformationen direkt anzugeben. In Go geht das zum Beispiel, indem wir die Informationen direkt per Config an die neue Session übergeben:

sess := session.Must(
  session.NewSession(
    aws.NewConfig().
      WithCredentials(credentials.NewStaticCredentials(
        "MY_AWS_ACCESS_KEY_ID",
        "MY_AWS_SECRET_ACCESS_KEY",
        "",
))))
s3Client := s3.New(sess)

Dieser Ansatz hat zwei Probleme:

  • Wir müssen die geheimen Informationen direkt in den Code schreiben und im schlimmsten Fall mit in unser Versionsmanagement einchecken.
  • Wenn sich die Anmeldeinformation ändern, müssen wir unseren Code anpassen. Das schafft unnötige Komplexität und ist eine Fehlerquellen.

Besser ist es, das Auffinden der Anmeldeinformation den Mechanismen des AWS SDK zu überlassen.

Berechtigungen per Instance Profile

Eine Applikation, die auf einer EC2 Instanz ausgeführt wird, hat die Möglichkeit, sich über die EC2 Instanz-Metadaten mit Berechtigungen zu versorgen. Es ist der Applikation somit möglich, die Rolle anzunehmen, die der EC2 Instanz per Instance Profile zugewiesen wurde.

Wenn auf einer EC2-Instanz nur genau eine Applikation läuft, ist das ein guter Ansatz. Da wir bei Kubernetes aber nie genau wissen, auf welcher Instanz ein bestimmter Pod ausgeführt wird, müssen wir bei diesem Ansatz der Instanz -und damit jedem Pod- sehr viele Rechte einräumen.

Nehmen wir folgendes an: Wir haben einen Pod, der die Finanzberichte aus einem S3 Bucket liest und diese per Mail an den CFO schick. Wir müssen nun allen Kubernetes Worker Instanzen die Rechte geben auf den S3 Bucket mit den Finanzberichten zuzugreifen. Ab diesem Moment hat jeder Pod den wir in unserem Cluster starten die Berechtigung auf diesen S3 Bucket zuzugreifen. Das ist eine erhebliche Sicherheitslücke und auf jeden Fall zu vermeiden.

Berechtigungen pro Pod

Das Problem der geteilten Rechte aller Pods auf einer Node lösen Tools wie kube2iam und kiam. Sie fangen die Aufrufe zur Metadaten-API ab und stellen auf den jeweiligen Pod zugeschnittene Rechte bereit. Einfach gesprochen ist es somit möglich, einem Pod per Annotation eine IAM Rolle zuzuweisen.

Das funktioniert sehr gut und wird erfolgreich in vielen produktiven Umgebungen eingesetzt. Allerdings brauchen diese Tools weitreichende Berechtigungen und sind somit eine Gefahr, sollte ein Angreifer die Kontrolle über sie erlangen.

IAM-Rolen für Service Accounts

Betreibt man einen Kubernetes Cluster auf AWS Infrastruktur (durch EKS oder Kops erstellt), gibt es zwei relevante Sicherheitsmechanismen:

IAM benutzt Rollen und RBAC Service Accounts um Identität für Workloads festzustellen. Diesen Identitäten werden dann über Policies (IAM) oder Cluster Role Bindings (RBAC) Rechte in den jeweiligen Systemen gewährt.

Bisher waren diese beiden System unabhängig voneinander und es brauchte oben beschriebene Umwege wie z.B. kube2iam um Kubernetes Workloads (Pods) Rechte in AWS zu gewähren.

Seit September 2019 ist es aber möglich, IAM Rollen direkt mit Kubernetes Service Accounts zu verbinden. Dies geschieht über den Einsatz von OpenID Connect Providern und den AWS API Aktion sts:AssumeRoleWithWebIdentity. Mehr Details zu technischen Implementierung findet ihr in der AWS Dokumentation.

Um einen Service Account mit einer IAM Rolle zu verknüpfen und die entsprechenden Rechte zu gewähren, müssen die folgenden Schritte ausgeführt werden:

  • Erstelle eine IAM Rolle mit einer passenden Policy.
  • Erlaube dem Principal OIDC Provider per Trust Relationship die Rolle anzunehmen.
  • Schränke die Nutzung der Rolle auf den Service Account via Condition ein.
  • Notiere die Rolle per Annotation an dem Service Account.
  • Weise den Service Account dem jeweiligen Pod zu.

Die Trust Relationship für die Rolle sieht dann so aus:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "${OIDC_PROVIDER}:sub": "system:serviceaccount:${NAMESPACE}:${SERVICEACCOUNT}"
        }
      }
    }
  ]
}

Der Service Account wird erstellt und dem Pod zugewiesen:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fin-reporter
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/FinReporter
---
apiVersion: v1
kind: Pod
metadata:
  name: fin-reporter-tq48h
spec:
  serviceAccount: fin-reporter

Zusammenfassung

Gibt man den AWS Worker Instanzen die Menge der Berechtigungen, die von allen Pods benötigt wird, hat man ein unnötiges Sicherheitsrisiko geschaffen. Tools wie kube2iam und kiam haben bisher sehr erfolgreich dabei geholfen, dieses Sicherheitsrisiko zu minimieren.

Seit der Einführung von IAM Rollen für Kubernetes Service Accounts, kann auf den Einsatz von Hilfsmitteln verzichtet werden. Es ist jetzt mit AWS Bordmitteln möglich, IAM Rollen an Kubernetes Pods zu vergeben. Dies reduziert Komplexität und ermöglich es, sich an das Least Privilege Principle zu halten.