Authentifizierung von Applikationen mit HashiCorp Vault AppRole

HashiCorp Vault ist ein Open Source Tool zur Verwaltung von Geheimnissen aller Art. Im Folgenden wird beschrieben wie sich Applikationen an Vault anmelden und auf Geheimnisse zugreifen können.

Warum AppRole?

Authentifizierungsmethoden in Vault sind diejenigen Komponenten, die – wie der Name bereits sagt – Authentifikation ermöglichen und definierte Policies an Benutzer, Systeme oder eben Applikationen zuweisen.

Vault unterstützt dabei mehrere Authentifizierungsmethoden zur Absicherung diverser Use-Cases. Organisationen die Active Directory einsetzen, können zum Beispiel LDAP verwenden damit Benutzer Zugriff auf Geheimnisse in Vault bekommen. Ähnlich verhält es sich beispielsweise mit Github. EC2-Instanzen in AWS dagegen können die Auth-Methode „AWS“ benutzen.

Während Methoden wie LDAP oder Github menschlichen Benutzern erlauben sich an Vault anzumelden, besteht die gleiche Notwendigkeit natürlich auch für Applikationen. Genau da kommt „AppRole“ ins Spiel. AppRole erlaubt es Applikationen sich gegen in Vault definierte Rollen zu authentifizieren. Diese Methode eignet sich besonders gut für die
Automatisierung von speziellen Workflows (Jenkins-Pipelines zum Beispiel).

Eine „AppRole“ stellt dabei ein Set von Vault-Policies und Login-Beschränkungen dar, die alle erfüllt sein müssen um einen gültigen Token mit diesen Policies zu erhalten.

Vorbedingungen

Selbstverständlich wird eine produktive Vault-Instanz bzw. ein Vault-Cluster benötigt. Wie das geht, findet sich zum Beispiel in einem früheren Artikel hier im Blog.

Vault lässt sich jedoch auch sehr einfach in einem Dev-Modus starten.

CLI oder API

Im Grunde spielt es keine Rolle ob per CLI oder API mit Vault kommuniziert und gearbeitet wird. In diesem Artikel wird von beiden Varianten Gebrauch gemacht.

Beispiel-Use-Case: Deployment von SSL-Zertifikaten

Im Folgenden soll eine Jenkins-Pipeline entstehen, durch diese regelmäßig SSL-Zertifikate auf bekannte Nodes deployed werden. Die Pipeline soll also Geheimnisse aus Vault lesen und „etwas“ damit tun.

In einer ersten Ausbaustufe werden einfach nur die Zertifikate in Vault benutzt, die dort bereits liegen. Zum jetzigen Zeitpunkt spielt es keine Rolle, wie sie dort landen. Dieses Beispiel dient vielmehr zur Veranschaulichung des sicheren Zugriffs auf Geheimnisse mit „AppRole“.

3 Rollen

In diesem skizzierten Szenario partizipieren 3 grundlegende Rollen:

  • Administrator
  • Trusted Entity (Jenkins)
  • Application (Pipeline)

Mit dem Konzept der 3 Personen löst sich das Problem, wie die ROLE_ID und SECRET_ID (Erklärung erfolgt im weiteren Verlauf) der Application zugänglich gemacht werden.

1. Vault Login

Rolle: Administrator

docker exec -ti vault sh

export VAULT_ADDR=http://127.0.0.1:8200

vault login

# jetzt erfolgt die Eingabe des Tokens eines Operators (zum Beispiel der Root-Token)

2. AppRole auth method aktivieren

Rolle: Administrator

vault auth enable approle

3. Policy „sslcert“ erstellen

Rolle: Administrator

Folgende Policy soll gesetzt werden:

path "secret/ssl/*" {
  capabilities = [
    "read",
    "list"]
}

Übersetzt bedeutet das, dass die Rolle „sslcert“ alle Geheimnisse aus secret/ssl/* lesen und auflisten können soll.

Mittels

vault policy write sslcert sslcert.hcl

 wird diese Policy gesetzt.

4. approle „sslcert“ erstellen

Rolle: Administrator 

vault write auth/approle/role/sslcert \
        secret_id_num_uses=1 \
        secret_id_ttl=1m \
        token_ttl=10m \
        token_max_tll=10m \
        policies="sslcert"
Parameter bei der Erstellung der „sslcert“ approle

In diesem Beispiel werden folgende Parameter gesetzt und benutzt:

  1. secret_id_num_uses: Number of times any particular SecretID can be used to fetch a token from this AppRole, after which the SecretID will expire.
  2. secret_id_ttl: Duration* after which any SecretID expires.
  3. token_ttl: Duration* to set as the TTL** for issued tokens and at renewal time.
  4. token_max_tll: Duration* after which the issued token can no longer be renewed.

* an integer number of seconds (3600) or an integer time unit (60m)
**TTL bezeichnet die „Time to Live“ (also die Lebenszeit des Tokens)

Unter https://www.vaultproject.io/api/auth/approle/index.html#parameters lässt sich die vollständige Dokumentation der Parameter nachlesen.

5. ROLE_ID auslesen und abspeichern

Rolle: Administrator

Eine Application, in unserem Fall die Jenkins-Pipeline, benötigt einen Benutzernamen sowie ein Passwort um sich an Vault anzumelden und Geheimnisse auszulesen. In Vault-Sprache reden wir dabei von der ROLE_ID sowie der SECRET_ID. Die ROLE_ID ist dabei nicht besonders schützenswert, da sie ohne gültigen Token sowie SECRET_ID keinen Wert besitzt.

Mittels

vault read -field=role_id auth/approle/role/sslcert/role-id

 lesen wir die ROLE_ID aus. 

Um sie später in unserer Applikation – der Pipeline – benutzen zu können, speichern wir diese (trotzdem relativ sicher) im Jenkins Credential Store.

Bei mehreren Projekten (Pipelines) die unterschiedliche ROLE_ID’s benutzen könnte es Sinn ergeben, diese direkt im Jenkinsfile abzuspeichern.

6. Trusted Entity Policy erstellen

Rolle: Administrator

Im weiteren Verlauf wollen wir nicht mit einem administrativen Benutzer weiterarbeiten um die noch notwendige SECRET_ID zu generieren. Diese Aufgabe soll von einer Trusted Entity – nämlich Jenkins – übernommen werden.

Dabei beschränken wir trotzdem die Rechte dieser Instanz und ermöglichen ihr ausschließlich create-, read- und update-Operationen auf der SECRET_ID. Dafür gibt es die Policy

path "auth/approle/role/sslcert/secret-id" {
  capabilities = [
    "create",
    "read",
    "update"]
}

die wir mittels

vault policy write jenkins jenkins.hcl

erstellen.

7. Token für die Trusted Entity erstellen

Rolle: Administrator

An dieser Stelle brechen wir kurz aus Vault aus. Alle Vorbereitungen sind getroffen. Jetzt müssen wir uns darum kümmern, wie Jenkins einen Token zur Verfügung gestellt bekommt mit dem eine SECRET_ID generiert werden kann.

Nehmen wir an, der von uns betrachtete Jenkins wird von einem Automatisierungstool wie etwa Ansible, Puppet, Chef und Co provisioniert. Dann bietet es sich an, dass wir diesem Tool eine Möglichkeit einräumen, einen Token für die Trusted Entitiy „Jenkins“ erstellen und erneuern zu lassen. Diesen Token legen wir im Home Verzeichnis des Jenkins ($JENKINS_HOME) unter dem Namen .vault_jenkins_token ab.

Existiert diese Datei noch nicht, erstellen wir diese mittels:

curl -H "X-Vault-Token: $VAULT_ROOT_TOKEN" -XPOST \
     -d '{"policies":["jenkins"],"ttl":"168h","renewable":true}' \
     http://vault:8200/v1/auth/token/create" \
     | jq '.auth.client_token' \
     | tr -d '"' > $JENKINS_HOME/.vault_jenkins_token

Ist diese Datei und damit der Token bereits vorhanden, erneuern wir den Token mit jedem Provisionierungslauf:

curl -H "X-Vault-Token: $(cat $JENKINS_HOME/.vault_jenkins_token)" -XPOST \
     -d '{"increment":"168h"}' \
     http://vault:8200/v1/auth/token/renew-self

Selbstverständlich sind viele weitere Methoden und Vorgehensweisen denkbar, wie ein Token für eine Trusted Entity erstellt und erneuert werden kann.


(Info) Damit sind nun endgültig alle Aufgaben des Administrators erledigt.


8. SECRET_ID für den Zugriff der Pipeline erstellen

Rolle: Trusted Entity (Jenkins)

Wie bereits etwas weiter oben beschrieben, benötigt eine Applikation (unsere Pipeline) eine ROLE_ID sowie eine SECRET_ID um auf Geheimnisse zugreifen zu können. Erstere ist der Trusted Entity (dem Jenkins) bereits bekannt und im Jenkins Credential Store hinterlegt (s.o.). Letztere wird in folgendem Schritt direkt durch die Trusted Entity (Jenkins) erstellt.

VAULT_TOKEN=$(cat $JENKINS_HOME/.vault_jenkins_token) \
  vault write -field=secret_id -f auth/approle/role/sslcert/secret-id

An diesem Punkt ist es jetzt natürlich Zeit das wir uns von der CLI verabschieden und etwas näher an den echten Use-Case und damit an die Pipeline gehen.

Innerhalb einer Pipeline können wir die SECRET_ID durch die Trusted Entity (Jenkins) wie folgt ermitteln:

#!groovy

node('master') {
    currentBuild.result = "SUCCESS"

    try {
      stage('prepare') {
        deleteDir()
      }

      stage('generate SECRET_ID') {
        sh '''
          set +x

          export SECRET_ID=$(curl -s -H "X-Vault-Token: $(cat ~/.vault_jenkins_token)" -XPOST http://vault:8200/v1/auth/approle/role/sslcert/secret-id | jq '.data.secret_id' | tr -d '"')
        '''
      }
    }

    catch (err) {
      currentBuild.result = "FAILURE"

      throw err
    }
}

9. Lesen der Geheimnisse / Deployment der Zertifikate

Rolle: Application (Pipeline)

An dieser Stelle haben wir alles damit sich unsere Application (die Pipeline) an Vault anmelden und die Zertifikate auslesen kann. Mit Hilfe der ROLE_ID und der SECRET_ID ist unsere Application (die Pipeline) in der Lage sich einen Token zu generieren, der alles im Pfad „secret/ssl/*“ lesen darf. Mit der CLI würde die Erstellung eines Application-Tokens wie folgt aussehen:

vault write -field=token auth/approle/login role_id=${ROLE_ID} secret_id=${SECRET_ID}

Das Auslesen eines echten Zertifikates passiert mittels:

curl -X GET -H "X-Vault-Token: <Ausgabe des vorangegangenen Befehls>" http://vault:8200/v1/secret/ssl/dev/frontend01

In unserer Pipeline sieht ein realer Schritt dabei wie folgt aus (als Ausschnitt aus der obigen Pipeline):

...


stage('read ssl cert') {
  withCredentials([string(credentialsId: 'ROLE_ID', variable: 'ROLE_ID')]) {
    sh '''
      set +x

      export SECRET_ID=$(curl -s -H "X-Vault-Token: $(cat ~/.vault_jenkins_token)" -XPOST http://vault:8200/v1/auth/approle/role/sslcert/secret-id | jq '.data.secret_id' | tr -d '"')

      export APP_TOKEN=$(curl -s -XPOST -d '{"role_id":"'"$ROLE_ID"'","secret_id":"'"$SECRET_ID"'"}' http://vault:8200/v1/auth/approle/login | jq '.auth.client_token' | tr -d '"')

      CERT=$(curl -s -H "X-Vault-Token: $APP_TOKEN" -XGET http://vault:8200/v1/secret/ssl/dev/frontend01)

      # Deployment von $CERT auf das Zielsystem
    '''
  }
}

...

10. Revoke des Application-Token

Ein Application-Token hat eine insgesamte Lebensdauer von 10 Minuten (inkl. eventuellem Renew-Event). Am Ende der Pipeline ist es denkbar und durchaus sinnvoll, den Application-Token zu revoken.

Das geschieht mittels:

curl -s -H "X-Vault-Token: $APP_TOKEN" -XPOST http://vault:8200/v1/auth/token/revoke-self

 Nachbetrachtungen

Aufgrund der Trennung der Rollen erreichen wir ein sicheres Login für unsere Application (die Pipeline).

Jede generierte SECRET_ID darf durch die Trusted Entity (Jenkins) genau einmal benutzt werden um einen Application Token für die Pipeline zu generieren. Dazu kommt eine TTL von 1 Minute.

Jeder Application-Token hat eine insgesamte Lebensdauer von 10 Minuten.

Der Token für die Trusted Entity (Jenkins) hat eine Lebensdauer von 7 Tagen und wird ständig erneuert.

Kommentar schreiben

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.