Hashicorp Vault

Apr. 19, 2021·
Julio Batista Silva
Julio Batista Silva
· 10 Min Lesezeit
Hinweis
Ziehe Infisical als Alternative zu Hashicorp Vault in Betracht.

HashiCorp Vault ist ein Tool für den sicheren Zugriff auf Secrets (Passwörter, API‑Keys, Zertifikate etc.) über eine einheitliche Schnittstelle – mit detaillierten Audit‑Logs.

Einige Funktionen von Vault:

  • Verschlüsselung
  • Erzeugung dynamischer Secrets mit Ablaufzeit
  • Erneuerung und Widerruf von Secrets

Vieles davon bieten auch Azure Key Vault und AWS Key Management Service. Vault ist jedoch Open Source und nicht an eine bestimmte Cloud gebunden.

Secrets werden in einem Storage‑Backend gespeichert (RAM, Disk, DB, Azure Blob, AWS S3, …). Vault stellt die Schnittstelle bereit.
Im anderen Artikel zeige ich, wie Vault die Key‑Value‑Store des Consul nutzt.

Vault ist in Go geschrieben und kommt als Einzel‑Binary. Man kann per CLI, HTTP‑REST oder Web‑UI arbeiten.

Secrets Engines

Secrets Engines speichern, erzeugen und verschlüsseln Daten.

Manche Engines speichern nur Daten, andere verbinden sich zu Diensten und erzeugen on‑demand Zugangsdaten. Wieder andere bieten „Encryption‑as‑a‑Service“, generieren TOTP und Zertifikate.

Die Vault‑Seite listet knapp 50 Engines (u. a. AWS, Azure, MySQL, PostgreSQL, Active Directory, PKI, SSH).

Engines werden unter einem Path aktiviert. vault path-help zeigt Beschreibung und akzeptierte Paths.

Lebenszyklus einer Secrets Engine:

  • Enable: vault secrets enable
  • Disable: vault secrets disable
  • Move: vault secrets move
  • Tune: vault secrets tune

KV Secrets Engine

Die KV‑Engine speichert Key‑Value‑Secrets.

Es gibt zwei Versionen:

  • KV v1:
    • Kein Versioning
    • Gelöschte Secrets sind nicht wiederherstellbar
    • Bessere Performance
  • KV v2:
    • Versioning (Standard 10 Versionen)
    • Soft Delete
    • Geringere Performance

Lebenszyklus von Secrets

  • Create: kv put
  • Read: kv get
  • Update: kv put
  • Delete/Undelete: kv delete / kv undelete
  • Destroy: kv destroy
  • Metadata Destroy: kv metadata delete

Authentifizierungsmethoden

Vault unterstützt viele Auth‑Methoden, z. B. LDAP, Azure Active Directory und AWS IAM.

  • Aktive Methoden auflisten: vault auth list
  • Methode aktivieren: vault auth enable [methode]
  • Methode konfigurieren: vault write auth/[methode]/config
  • Rolle hinzufügen: vault write auth/[methode]/role/[rollenname]
  • Methode deaktivieren: vault auth disable [methode]

Zugriffsrichtlinien

Eine Vault‑Policy (Vault Policy) ist ein Dokument in HCL oder JSON, das fein granular regelt, wer was wo darf.

Man kann sehr genau steuern, welche Kommandos an welchen Paths erlaubt sind.
Die Doku hat viele Beispiele.

  • Policies auflisten: vault policy list
  • Policy erstellen: vault policy write [policy] [policy_file.hcl]
  • Policy aktualisieren: vault write sys/policy/[policy]/ policy=[policy_file.hcl]
  • Policy löschen: vault delete sys/policy/[policy]

Dynamische Secrets

Dynamic Secrets werden on‑demand mit Lease erzeugt. Ohne Erneuerung werden sie widerrufen.

Cubbyhole Response Wrapping

Statt einen Token direkt zu geben, legen wir ihn in ein Cubbyhole und erzeugen einen kurzlebigen One‑Time‑Token, der Zugriff auf das Cubbyhole hat – Cubbyhole Response Wrapping.

Vorteile:

  • Secret ist nur kurz sichtbar (Lease TTL)
  • Leaked Token ist nutzlos (Einmal‑Token)
  • Nutzer sieht nicht das Secret, sondern eine Referenz

Auditing

Vault loggt alle Requests und Responses (inkl. Fehler) als JSON. Mehrere Audit‑Devices (Datei, Syslog, Socket) sind möglich.

Ohne log_raw=true werden nur Hashes der Secrets geloggt.

Sicherheit: Vault akzeptiert keine Requests, wenn kein Audit‑Device verfügbar ist.

  • Audit‑Device aktivieren: vault audit enable -path=vault_path [type]
  • Audit‑Device deaktivieren: vault audit disable [path]
  • Audit‑Devices listen: vault audit list --detailed

Praxis‑Walkthrough

Beispiele zu den obigen Konzepten.

  • Vault installieren und starten

    Über den Paketmanager deiner Distro oder wie im anderen Artikel beschrieben.

    # pacman -S vault
    
    # cat /etc/vault.hcl
    backend "file" {
        path = "/var/lib/vault"
    }
    
    listener "tcp" {
        tls_disable = 1
    }
    
    # systemctl start vault.service
    
    $ vault operator init
    Unseal Key 1: p/rCDw28akkqMBogtkpuH7OoB0IbwNK6fOPE1/K56mCw
    Unseal Key 2: 4Qy/bymi4BShb0JaoMnQm8Qry/MrrM3pP3CQDeJu93s+
    Unseal Key 3: +Qdh8mBl1yzqi5V2u1tKG1u3pWtRfXEQ1cwMjoYpuSAy
    Unseal Key 4: lG6Vl8iiJhUpvmmWF5S8EgKd89wMUHsL4PNHuT4xGZ82
    Unseal Key 5: rsufBM3HiobRE39El8ErIkzx+qkxz9unzIrMb/w5gZTB
    
    Initial Root Token: s.3iZvNY9lhNdeJVH5fIHU1RDv
    
  • Umgebungsvariablen mit Adresse und Token setzen

    $ export VAULT_ADDR='http://127.0.0.1:8200'
    $ export VAULT_TOKEN='s.3iZvNY9lhNdeJVH5fIHU1RDv'
    

    Wenn Vault in einem Docker‑Container läuft, ist folgender Alias praktisch:

    $ alias vault='docker exec -it -e VAULT_TOKEN=$VAULT_TOKEN vault.server1 vault "$@"'
    
  • Unseal

    $ vault operator unseal p/rCDw28akkqMBogtkpuH7OoB0IbwNK6fOPE1/K56mCw
    Sealed             true
    Unseal Progress    1/3
    
    $ vault operator unseal +Qdh8mBl1yzqi5V2u1tKG1u3pWtRfXEQ1cwMjoYpuSAy
    Sealed             true
    Unseal Progress    2/3
    
    $ vault operator unseal rsufBM3HiobRE39El8ErIkzx+qkxz9unzIrMb/w5gZTB
    Sealed          false
    
  • Login

    $ vault login
    
  • Secrets‑Backend aktivieren

    $ vault secrets enable -path=kv -version=2 kv
    Success! Enabled the kv secrets engine at: kv/
    
  • Maximal 2 Versionen speichern

    $ vault write kv/config max_versions=2
    Success! Data written to: kv/config
    
  • Secret schreiben

    $ vault kv put kv/meu_app senha=123456
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:34.948088026Z
    deletion_time    n/a
    destroyed        false
    version          1
    
  • Zweite Version anlegen

    $ vault kv put kv/meu_app senha=123456 usuario=julio
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:44.839504165Z
    deletion_time    n/a
    destroyed        false
    version          2
    
  • Dritte Version anlegen

    $ vault kv put kv/meu_app senha=654321 usuario=julio
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:53.768980035Z
    deletion_time    n/a
    destroyed        false
    version          3
    
  • Letzte Version lesen

    $ vault kv get kv/meu_app
    ====== Metadata ======
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:53.768980035Z
    deletion_time    n/a
    destroyed        false
    version          3
    
    ===== Data =====
    Key        Value
    ---        -----
    senha      654321
    usuario    julio
    
  • Zweite Version lesen

    $ vault kv get -version=2 kv/meu_app
    ====== Metadata ======
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:44.839504165Z
    deletion_time    n/a
    destroyed        false
    version          2
    
    ===== Data =====
    Key        Value
    ---        -----
    senha      123456
    usuario    julio
    
  • Erste Version lesen

    Diese Version wurde zerstört, da wir nur die letzten zwei Versionen behalten.

    $ vault kv get -version=1 kv/meu_app
    No value found at kv/data/meu_app
    
  • Zweite Version als JSON ausgeben

    $ vault kv get -format=json -version=2 kv/meu_app
    {
      "request_id": "379bbb04-a21b-cebe-9e99-392b85372592",
      "lease_id": "",
      "lease_duration": 0,
      "renewable": false,
      "data": {
        "data": {
          "senha": "123456",
          "usuario": "julio"
        },
        "metadata": {
          "created_time": "2021-04-19T04:11:44.839504165Z",
          "deletion_time": "",
          "destroyed": false,
          "version": 2
        }
      },
      "warnings": null
    }
    
  • Mit jq filtern

    $ vault kv get -format=json -version=2 kv/meu_app | jq -r .data.data.senha
    123456
    
  • Zweite Version über die API lesen

    $ curl -k --header "X-Vault-Token: $VAULT_TOKEN" $VAULT_ADDR/v1/kv/data/meu_app?version=2 | jq .data.data
    {
      "senha": "123456",
      "usuario": "julio"
    }
    
  • Versionen 2 und 3 löschen

    Wird keine Version angegeben, wird die letzte gelöscht.

    $ vault kv delete -versions="2,3" kv/meu_app
    Success! Data deleted (if it existed) at: kv/meu_app
    
    $ vault kv get -version=2 kv/meu_app
    ====== Metadata ======
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:44.839504165Z
    deletion_time    2021-04-19T04:14:27.514456987Z
    destroyed        false
    version          2
    
    $ vault kv get -version=3 kv/meu_app
    ====== Metadata ======
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:53.768980035Z
    deletion_time    2021-04-19T04:14:27.514457229Z
    destroyed        false
    version          3
    
  • Version 2 wiederherstellen

    $ vault kv undelete -versions=2 kv/meu_app
    Success! Data written to: kv/undelete/meu_app
    
  • Version 2 endgültig zerstören

    $ vault kv destroy -versions=2 kv/meu_app
    Success! Data written to: kv/destroy/meu_app
    
    $ vault kv get -version=2 kv/meu_app
    ====== Metadata ======
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:44.839504165Z
    deletion_time    n/a
    destroyed        true
    version          2
    
  • Alle Metadaten löschen

    $ vault kv list kv/
    Keys
    ----
    meu_app
    
    $ vault kv metadata delete kv/meu_app
    Success! Data deleted (if it existed) at: kv/metadata/meu_app
    
    $ vault kv list kv/
    No value found at kv/metadata
    
  • Policy „admin“ erstellen

    $ cat policy_admin.hcl
    # Mount secrets engines
    path "sys/mounts/*" {
      capabilities = [ "create", "read", "update", "delete", "list" ]
    }
    
    # Configure the database secrets engine and create roles
    path "database/*" {
      capabilities = [ "create", "read", "update", "delete", "list" ]
    }
    
    # Manage the leases
    path "sys/leases/+/database/creds/readonly/*" {
      capabilities = [ "create", "read", "update", "delete", "list", "sudo" ]
    }
    
    path "sys/leases/+/database/creds/readonly" {
      capabilities = [ "create", "read", "update", "delete", "list", "sudo" ]
    }
    
    # Write ACL policies
    path "sys/policies/acl/*" {
      capabilities = [ "create", "read", "update", "delete", "list" ]
    }
    
    # Manage tokens for verification
    path "auth/token/create" {
      capabilities = [ "create", "read", "update", "delete", "list", "sudo" ]
    }
    
    $ vault policy write admin policy_admin.hcl
    Success! Uploaded policy: admin
    
  • Policy „apps“ erstellen

    $ cat policy_apps.hcl
    # Get credentials from the database secrets engine 'readonly' role.
    path "database/creds/readonly" {
      capabilities = [ "read" ]
    }
    
    $ vault policy write apps policy_apps.hcl
    Success! Uploaded policy: apps
    
  • Policies listen

    $ vault policy list
    admin
    apps
    default
    root
    
  • Docker starten und PostgreSQL‑Image ziehen

    # systemctl start docker.service
    $ docker pull postgres:13
    
  • Datenbank starten

    $ docker run \
          --name postgres \
          --env POSTGRES_USER=root \
          --env POSTGRES_PASSWORD=rootpassword \
          --detach  \
          --publish 5432:5432 \
          postgres:13
    
  • Mit der DB verbinden und eine Rolle ro mit Lesezugriff erstellen

    $ docker exec -it postgres psql
    root=# CREATE ROLE ro NOINHERIT;
    CREATE ROLE
    root=# GRANT SELECT ON ALL TABLES IN SCHEMA public TO "ro";
    GRANT
    root=# \q
    
  • Database‑Engine aktivieren

    $ vault secrets enable database
    Success! Enabled the database secrets engine at: database/
    
  • Engine für Postgres konfigurieren

    $ vault write database/config/postgresql \
         plugin_name=postgresql-database-plugin \
         connection_url="postgresql://{{username}}:{{password}}@localhost:5432/postgres?sslmode=disable" \
         allowed_roles=readonly \
         username="root" \
         password="rootpassword"
    
  • SQL zum Erzeugen der Credentials

    $ cat readonly.sql
    CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' INHERIT;
    GRANT ro TO "{{name}}";
    
  • Rolle readonly erstellen

    $ vault write database/roles/readonly \
          db_name=postgresql \
          creation_statements=@readonly.sql \
          default_ttl=1h \
          max_ttl=24h
    Success! Data written to: database/roles/readonly
    
  • DB‑Credentials generieren

    $ vault read database/creds/readonly
    Key                Value
    ---                -----
    lease_id           database/creds/readonly/hTFHqyO2AVcCBQRFmRTv8bGV
    lease_duration     1h
    lease_renewable    true
    password           NMVS-H2YO5BNMAkzP6Nl
    username           v-root-readonly-sKjgJBT41RYrrLtB1MtS-1618807755
    
  • Postgres‑User listen

    $ docker exec -it postgres psql
    root=# \du
                                                 List of roles
                Role name       |                   Attributes              | Member of
    ----------------------------+-------------------------------------------+-----------
     ro                         | No inheritance, Cannot login              | {}
     root                       | Superuser, Create…Replication, Bypass RLS | {}
     v-root-readonly-sKj…7755 | Password valid until 2021-04-19 05:49:20+00 | {ro}
    
    root=# \q
    
  • Leases listen

    $ vault list sys/leases/lookup/database/creds/readonly
    Keys
    ----
    hTFHqyO2AVcCBQRFmRTv8bGV
    
  • Lease‑ID in Variable speichern

    $ LEASE_ID=$(vault list -format=json sys/leases/lookup/database/creds/readonly | jq -r ".[0]")
    
    $ echo $LEASE_ID
    hTFHqyO2AVcCBQRFmRTv8bGV
    
  • Lease verlängern

    Wird ein zu hoher Wert angefragt, wird er auf das Maximum gekappt.

    $ vault lease renew -increment=3600 database/creds/readonly/$LEASE_ID
    
    $ vault lease renew -increment=96400 database/creds/readonly/$LEASE_ID
    WARNING! The following warnings were returned from Vault:
    
    * TTL of "26h46m40s" exceeded the effective max_ttl of "23h33m18s"; TTL
    value is capped accordingly
    
    Key                Value
    ---                -----
    lease_id           database/creds/readonly/hTFHqyO2AVcCBQRFmRTv8bGV
    lease_duration     23h33m18s
    lease_renewable    true
    
    root=# \du
                                                 List of roles
                Role name       |                   Attributes              | Member of
    ----------------------------+-------------------------------------------+-----------
     ro                         | No inheritance, Cannot login              | {}
     root                       | Superuser, Create…Replication, Bypass RLS | {}
     v-root-readonly-sKj…7755 | Password valid until 2021-04-20 04:49:20+00 | {ro}
    
    root=# \q
    
  • Lease widerrufen

    $ vault lease revoke database/creds/readonly/$LEASE_ID
    All revocation operations queued successfully!
    
    $ docker exec -it postgres psql
    root=# \du
                                       List of roles
     Role name |                         Attributes                 | Member of
    -----------+----------------------------------------------------+-----------
     ro        | No inheritance, Cannot login                       | {}
     root      | Superuser, Create role, …, Replication, Bypass RLS | {}
    
    root=# \q
    
    $ vault list sys/leases/lookup/database/creds/readonly
    No value found at sys/leases/lookup/database/creds/readonly
    
  • Aktive Auth‑Methoden listen

    $ vault auth list
    Path      Type     Accessor               Description
    ----      ----     --------               -----------
    token/    token    auth_token_5e067aef    token based credentials
    
  • userpass aktivieren

    $ vault auth enable userpass
    Success! Enabled userpass auth method at: userpass/
    
  • Verfügbare Optionen ansehen

    $ vault path-help auth/userpass
    
  • Benutzer hinzufügen

    $ vault write auth/userpass/users/julio \
        password=minha_senha \
        policies=admin
    Success! Data written to: auth/userpass/users/julio
    
  • Benutzer listen

    $ vault list auth/userpass/users
    Keys
    ----
    julio
    
  • In neuem Terminal mit dem Benutzer einloggen

    $ export VAULT_ADDR='http://127.0.0.1:8200'
    $ vault login -method=userpass username=julio
    Success! You are now authenticated. The token information displayed below
    is already stored in the token helper. You do NOT need to run "vault login"
    again. Future Vault requests will automatically use this token.
    
    Key                    Value
    ---                    -----
    token                  s.8vRGmBVEQ1indCPtGL796od3
    token_accessor         CmTWhmRHFq4RMLb6wccFgOFT
    token_duration         768h
    token_renewable        true
    token_policies         ["admin" "default"]
    identity_policies      []
    policies               ["admin" "default"]
    token_meta_username    julio
    
  • Benutzer löschen

    $ vault delete auth/userpass/users/julio
    Success! Data deleted (if it existed) at: auth/userpass/users/julio
    
  • Secret im Vault speichern

    $ vault kv put kv/app-server api-key=123456
    Key              Value
    ---              -----
    created_time     2021-04-19T06:02:13.217806976Z
    deletion_time    n/a
    destroyed        false
    version          1
    
  • Wrapping‑Token (5 Minuten) erzeugen

    $ vault kv get -wrap-ttl=300 kv/app-server
    Key                              Value
    ---                              -----
    wrapping_token:                  s.wqyqcD8lPcaOGznSg4ZNVFPC
    wrapping_accessor:               sy6OAxfoKcmPLbUufzGhxkFY
    wrapping_token_ttl:              5m
    wrapping_token_creation_time:    2021-04-19 13:08:13.210327049 -0300 -03
    wrapping_token_creation_path:    kv/data/app-server
    
  • Secret mit diesem Token abrufen

    $ curl --header "X-Vault-Token: s.wqyqcD8lPcaOGznSg4ZNVFPC" --request POST \
      $VAULT_ADDR/v1/sys/wrapping/unwrap | jq
    {
      "request_id": "69598fb3-0f90-a1c8-22d8-387f5c79564e",
      "lease_id": "",
      "renewable": false,
      "lease_duration": 0,
      "data": {
        "data": {
          "api-key": "123456"
        },
        "metadata": {
          "created_time": "2021-04-19T06:02:13.217806976Z",
          "deletion_time": "",
          "destroyed": false,
          "version": 1
        }
      },
      "wrap_info": null,
      "warnings": null,
      "auth": null
    }
    
  • KV‑Store am Pfad webkv anlegen

    $ vault secrets enable -path=webkv kv
    Success! Enabled the kv secrets engine at: webkv/
    
  • Secret in webkv schreiben

    $ vault kv put webkv/app-server api-key=123456
    Success! Data written to: webkv/app-server
    
  • Web‑Policy anlegen

    $ cat webpol.hcl
    path "webkv/*" {
      capabilities = ["read", "list"]
    }
    
    $ vault policy write web webpol.hcl
    Success! Uploaded policy: web
    
  • Token mit Web‑Policy (5 Minuten) erstellen

    $ vault token create -policy=web -wrap-ttl=300
    Key                              Value
    ---                              -----
    wrapping_token:                  s.FPSHG7GJ3pWWliA0zSDEb77w
    wrapping_accessor:               oLR1tRBpyQACPICrMIWrtfDm
    wrapping_token_ttl:              5m
    wrapping_token_creation_time:    2021-04-19 14:58:31.807307058 -0300 -03
    wrapping_token_creation_path:    auth/token/create
    wrapped_accessor:                eP85Zj7EmwrDgE1GoagsNJ6p
    
  • Token auspacken

    $ curl --header "X-Vault-Token: s.FPSHG7GJ3pWWliA0zSDEb77w" --request POST \
        $VAULT_ADDR/v1/sys/wrapping/unwrap | jq
    {
      "request_id": "4cfe3fd2-942a-d7e3-34e5-2b7d644b3e28",
      "lease_id": "",
      "renewable": false,
      "lease_duration": 0,
      "data": null,
      "wrap_info": null,
      "warnings": null,
      "auth": {
        "client_token": "s.lDqthe94B6bFVyXxNqLMuwYO",
        "accessor": "eP85Zj7EmwrDgE1GoagsNJ6p",
        "policies": [
          "default",
          "web"
        ],
        "token_policies": [
          "default",
          "web"
        ],
        "metadata": null,
        "lease_duration": 2764800,
        "renewable": true,
        "entity_id": "",
        "token_type": "service",
        "orphan": false
      }
    }
    
  • Secret mit dem erhaltenen Token lesen

    $ curl --header "X-Vault-Token: s.lDqthe94B6bFVyXxNqLMuwYO" $VAULT_ADDR/v1/webkv/app-server | jq
    {
      "request_id": "f63dcc67-b9f6-43f6-dbb8-756c709e6a2f",
      "lease_id": "",
      "renewable": false,
      "lease_duration": 2764800,
      "data": {
        "api-key": "123456"
      },
      "wrap_info": null,
      "warnings": null,
      "auth": null
    }
    

Julio Batista Silva
Autoren
Senior Cloud-Entwickler
comments powered by Disqus