Part 2: Docker & Vault Agent with Dynamic Secrets

Part 3: Docker & Vault Agent with Docker Compose

Part 4: Docker, Vault Agent with Terraform

Kao Hashicorp Vault konzultant, vrlo često nakon razgovora s klijentom, dobijem ideju za opisati neki specifičan problem a koji je iskrsnuo u razgovoru. U ovom slučaju, pitanje je bilo kako integrirati Vault, Vault Agent i Docker, kako postupati u slučaju legacy aplikacija te može li se secrets čitati iz memorije a ne zapisivati u lokalnu datoteku.

Vault Secret možemo koristiti unutar kontejnera na 4 načina:

Environment files

Svi developeri koji su radili sa Ruby on Rails znaju za config.yaml ili application.yaml datoteke. U njima su zapisani osjetljivi podaci kao konfiguracija, što predstavlja ogroman rizik. Nema sumnje da odmah na početku te datoteke dodajemo u .gitignore datoteku.

Hardkodiranje osjetljivih podataka je još gore, a sve više programera slijedi princip “The 12 Factor App”, skup pravila u kojem je bitna rečenica vezana za konfiguracijske datoteke:

A litmus test for whether an app has all config correctly factored out of the code is whether the codebase could be made open source at any moment, without compromising any credentials.

Znači, unutar kontejnera možemo kreirati datoteku (yaml, env, ini, …) sličnu ili identičnu postojećoj pa u tom slučaju nije potrebno mijenjati kod aplikacije.

Environment Variables

Pravila iz prethodne metode su primjenjiva i ako se radi o aplikacijama koje čitaju podatke iz memorije. Svaki Docker container ima mogućnosti kreiranja env varijabli osim što ih možemo kreirati dinamički a ne tijekom kreiranja Docker image.

Mounted Volumes

Po meni, manje sigurna verzija koju možemo koristiti kada je Vault agent instaliran na hostu pa mapiramo datoteke i mape u Docker container.

Fetch secrets from Vault secret store

Iako je pitanje bilo o legacy aplikacijama, ne smijemo zanemariti mogućnost direktnog pozivanja Vault API posebice ako koristimo Vault library za određen programski jezik. Znači, moramo nadograditi aplikaciju da je sama u mogućnosti povuči podatak i pohraniti ga u memoriji. Istina, ako imamo stotine klijenata, kako će se Vault ponašati pod opterećenjem ali to je već tema za drugi članak.

Prvi nedostatak, gdje većina klijenata prestane razmatrati ovaj pristup je potreba za izmjenama aplikacije (ponekad prilično opsežne) a gdje u slučaju legacy aplikacije to može biti i dosta mukotrpno ili čak i neizvedivo.

Vault

Pokrenimo Vault

$ docker run -it --cap-add IPC_LOCK --name=vault -p 8200:8200 -e VAULT_DEV_ROOT_TOKEN_ID=root vault
==> Vault server configuration:
             Api Address: http://0.0.0.0:8200
                     Cgo: disabled
         Cluster Address: https://0.0.0.0:8201
   Environment Variables: GODEBUG, HOME, HOSTNAME, PATH, PWD, SHLVL, VAULT_DEV_ROOT_TOKEN_ID
              Go Version: go1.20.1
              Listener 1: tcp (addr: "0.0.0.0:8200", cluster address: "0.0.0.0:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
               Log Level: 
                   Mlock: supported: true, enabled: false
           Recovery Mode: false
                 Storage: inmem
                 Version: Vault v1.13.1, built 2023-03-23T12:51:35Z
             Version Sha: 4472e4a3fbcc984b7e3dc48f5a8283f3efe6f282
==> Vault server started! Log data will stream in below:
2023-04-11T16:56:29.761Z [INFO]  proxy environment: http_proxy="" https_proxy="" no_proxy=""
...
WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.
You may need to set the following environment variables:
    $ export VAULT_ADDR='http://0.0.0.0:8200'
The unseal key and root token are displayed below in case you want to
seal/unseal the Vault or re-authenticate.
Unseal Key: 6o6/BIQHjJUPMhwjK1rWeIREinqqi3OFW5tiFBoTd3A=
Root Token: root
Development mode should NOT be used in production installations!

Primjećujemo da je Vault već incijaliziran, pokrenut u development načinu, KV engine je omogućen, a možemo mu pristupiti na adresi http://localhost:8200 i prijaviti se koristeći token root.

$ export VAULT_ADDR=http://localhost:8200
$ export VAULT_TOKEN=root
$ vault status

Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 1.12.2
Build Date 2022-11-23T12:53:46Z
Storage Type inmem
Cluster Name vault-cluster-97e2827a
Cluster ID 9b770101-e621-488c-a316-b35a23c060ec
HA Enabled false

Nginx

Otvorimo novi terminal tab i pripremimo Nginx.

mkdir nginx

U nginx mapi, kreirajte datoteku index.html slijedećeg sadržaja

<!DOCTYPE html>
<html>

<head>
  <title>Welcome to nginx!</title>
  <style>
    body {
      width: 35em;
      margin: 0 auto;
      font-family: Tahoma, Verdana, Arial, sans-serif;
    }
  </style>
</head>

<body>
  <h1>Welcome to nginx!</h1>
  <p><em>Thank you for using nginx.</em></p>
</body>

</html>

U novom terminalu pokrenemo nginx.

$ docker run -p 80:80 -v $PWD/nginx:/usr/share/nginx/html nginx

Što je Vault Agent?

Vault Agent je ista Vault binary datoteka ali izvršena u Agent modu, a omogućava nam određene funkcionalnosti i interakciju sa Vault serverom, poput:

  • Auto-Auth: Automatizirana autentikacija na Vault server definirana metodom. Najčešće korištena metoda je AppRole a jednom kada je Vault Agent prijavljen u Vault, sam brine o ponovnoj autentikaciji.
  • Caching: Omogućava keširanje Vault odgovora poput tokena i podataka i uglavnom ga koristimo kod intenzivnije upotrebe Vaulta. Kao i kod svakog keširanja, svrha mu je da se smanje zahtjevi prema Vaultu.
  • Templating: Vault Agent koristi consul-template za kreiranje datoteka prema zadanom predlošku.

Dok su Vault i Nginx pokrenuti, otvorimo novi terminal i prije pokretanja Vault Agenta, pripremimo Vault.

Kreirajmo mapu agent.

mkdir agent

Pripremimo Vault AppRole metodu i kreirajmo KV secrets.

$ export VAULT_ADDR=http://localhost:8200
$ export VAULT_TOKEN=root
$ vault auth enable approle
Success! Enabled approle auth method at: approle/
$ vault write auth/approle/role/agent token_policies="nginx-agent-policy"
Success! Data written to: auth/approle/role/agent
$ vault read --field=role_id auth/approle/role/agent/role-id > ./agent/role-id
$ vault write --field=secret_id -f auth/approle/role/agent/secret-id > ./agent/secret-id
$ vault kv put secret/nginx/front-page foo=bar app=nginx username=user password=pass

Kreirajmo policy datoteku (policy.hcl)

# This policy allows the nginx app to access Key/Value and Database Secrets engine

# List, create, update, and delete key/value secrets
path "secret/data/nginx/*"
{
  capabilities = ["create", "read", "update", "delete", "list"]
}

# Read dynamic database secrets
path "postgres/creds/nginx"
{
  capabilities = ["read"]
}

Kreirajmo policy u Vaultu.

$ vault policy write nginx-agent-policy ./policy.hcl

Success! Uploaded policy: nginx-policy

Uredimo konfiguraciju Vault Agenta (./agent/config.hcl)

pid_file = "/agent/pidfile"

auto_auth {
  method {
    type = "approle"

    config = {
      role_id_file_path = "/agent/role-id"
      secret_id_file_path = "/agent/secret-id"
      remove_secret_id_file_after_reading = false
    }
  }

  sink {
    type = "file"
    config = {
      path = "/agent/.token"
      mode = 0644
    }
  }
}

template {
  source = "/agent/kv.tmpl"
  destination = "/usr/share/nginx/html/kv.html"
}

vault {
  address = "http://vault:8200"
}

Obzirom da Vault Agent koristi consul predloške, moramo kreirati još jednu datoteku (./agent/kv.tmpl) slijedećeg sadržaja:

{{ with secret "secret/data/nginx/front-page" -}}
<html>
  <head>
    <link rel="stylesheet" href="https://unpkg.com/mvp.css@1.12/mvp.css"> 
</head>
<body>
  <main>
    <h4>Secret path: secret/data/nginx/front-page, Policy: nginx-agent-policy</h4>
    <ul>
      <li><strong>app:</strong> {{ .Data.data.app }}</li>
      <li><strong>username:</strong> {{ .Data.data.username }}</li>
      <li><strong>password:</strong> {{ .Data.data.password }}</li>
    </ul>
    <h4>Vault commands</h4>
    <pre><code>
      vault kv get secret/nginx/front-page
      vault kv list secret/nginx
      vault policy read nginx-agent-policy
    </code></pre></div>
  </main>
</body></html>
{{- end }}

Pokrenimo Vault Agent:

$ docker run --cap-add IPC_LOCK -v $PWD/agent:/agent -v $PWD/nginx:/usr/share/nginx/html --link=vault vault vault agent -config=/agent/config.hcl

==> Vault Agent started! Log data will stream in below:

==> Vault Agent configuration:

           Api Address 1: http://bufconn
                     Cgo: disabled
               Log Level: 
                 Version: Vault v1.13.1, built 2023-03-23T12:51:35Z
             Version Sha: 4472e4a3fbcc984b7e3dc48f5a8283f3efe6f282

2023-04-11T17:38:03.937Z [INFO]  agent.sink.file: creating file sink
2023-04-11T17:38:03.937Z [INFO]  agent.sink.file: file sink configured: path=/agent/.token mode=-rw-r--r--
2023-04-11T17:38:03.937Z [INFO]  agent.template.server: starting template server
2023-04-11T17:38:03.937Z [INFO] (runner) creating new runner (dry: false, once: false)
2023-04-11T17:38:03.937Z [INFO]  agent.sink.server: starting sink server
2023-04-11T17:38:03.937Z [INFO]  agent.auth.handler: starting auth handler
2023-04-11T17:38:03.937Z [INFO]  agent.auth.handler: authenticating
2023-04-11T17:38:03.938Z [INFO] (runner) creating watcher
2023-04-11T17:38:03.939Z [INFO]  agent.auth.handler: authentication successful, sending token to sinks
2023-04-11T17:38:03.939Z [INFO]  agent.auth.handler: starting renewal process
2023-04-11T17:38:03.939Z [INFO]  agent.template.server: template server received new token
2023-04-11T17:38:03.939Z [INFO] (runner) stopping
2023-04-11T17:38:03.939Z [INFO] (runner) creating new runner (dry: false, once: false)
2023-04-11T17:38:03.939Z [INFO] (runner) creating watcher
2023-04-11T17:38:03.939Z [INFO] (runner) starting
2023-04-11T17:38:03.939Z [INFO]  agent.sink.file: token written: path=/agent/.token
2023-04-11T17:38:03.940Z [INFO]  agent.auth.handler: renewed auth token

Ako je sve kreirano kako treba, trebali bismo primjetiti nekoliko stvari:

  • imamo novu datoteku (kv.html) u nginx mapi
  • ako otvorimo localhost/kv.html vidjet ćeme naše podatke iz Vaulta koje smo unijeli prije

Nastavak: Docker i Vault Agent – Part 2