Part 1: Docker & Vault Agent

Part 3: Docker & Vault Agent with Docker Compose

Part 4: Docker, Vault Agent with Terraform

U prethodnom dijelu smo pisali o povezivanju Vault, Vault Agenta i Docker, ali sa statičnim, KV2 podacima. Logičan nastavak je uvrstiti i primjer dinamički kreiranih podataka poput korisničkog računa za bazu podataka.

Provjerimo da su Vault i Nginx pokrenuti, zaustavimo Vault Agent kontejner (CTRL-C) te pokrenimo Postgres kontejner.

$ docker run -it --net=vault --name=postgres -e POSTGRES_DB=products -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=pass -p 5432:5432 hashicorpdemoapp/product-api-db:v0.0.22

Vault: Database Secret Engine (PostgreSQL)

$ vault secrets enable -path=postgres database

Success! Enabled the database secrets engine at: postgres/

$ vault write postgres/config/products \
    plugin_name=postgresql-database-plugin \
    allowed_roles="*" \
    connection_url="postgresql://{{username}}:{{password}}@postgres:5432/products?sslmode=disable" \
    username="postgres" \
    password="pass"

Success! Data written to: postgres/config/products

Nakon što je konekcija ostvarena, možemo kreirati role koje smiju koristiti ovu konekciju. Potrebna nam je samo jedna, koju ćemo nazvati nginx.

$ vault write postgres/roles/nginx \
  db_name=products \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
  GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\"" \
  default_ttl="30m" \
  max_ttl="24h"

Pokušajmo generirati korisničke podatke:

$ vault read postgres/creds/nginx

Key                Value
---                -----
lease_id           postgres/creds/nginx/eT9YrTgR8F9mKybwiWskWrlL
lease_duration     30m
lease_renewable    true
password           P7KUBJ-1WueV8m9Zb9O8
username           v-token-nginx-AM2KIHBDw7XSQchZBE8i-1681239818

Prijavimo se u psql pomoću tih podataka

$ export PGPASSWORD=P7KUBJ-1WueV8m9Zb9O8
$ psql -h localhost -U v-token-nginx-AM2KIHBDw7XSQchZBE8i-1681239818 -d products -c "select id, name from coffees"

 id |        name         
----+---------------------
  1 | HCP Aeropress
  2 | Packer Spiced Latte
  3 | Vaulatte
  4 | Nomadicano
  5 | Terraspresso
  6 | Vagrante espresso
  7 | Connectaccino
  8 | Boundary Red Eye
  9 | Waypointiato
(9 rows)

Ovo je divno, dinamički kreiran korisnik sa pripadajućom šifrom, koji ima pristup samo određenoj bazi, divota. Znači, Vault je kreirao korisnika u bazi, dodijelio mu šifru i obrisati će ga nakon što mu istekne vrijeme trajanja (TTL). Pomoću creation_statement možemo ograničiti njegov pristup određenim bazama ili čak samo nekim tablicama upotrebljavajući SQL.

Vault: Database Secret Engine (MySQL/MariaDB)

$ docker run --net=vault --name mysql -e MYSQL_ROOT_PASSWORD=pass mysql

$ vault secrets enable -path=mysql database

Success! Enabled the database secrets engine at: mysql/

$ vault write mysql/config/items \
    plugin_name=mysql-database-plugin \
    connection_url="{{username}}:{{password}}@tcp(mysql:3306)/" \
    allowed_roles="my-role" \
    username="root" \
    password="pass"

Success! Data written to: mysql/config/nginx

$ vault write mysql/roles/nginx \
    db_name=itemcollection \
    creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';GRANT SELECT ON *.* TO '{{name}}'@'%';" \
    default_ttl="1h" \
    max_ttl="24h"

$ vault read mysql/creds/nginx

Key                Value
---                -----
lease_id           mysql/creds/nginx/4p9RdInQU6yQJrewzcIiCYwD
lease_duration     1h
lease_renewable    true
password           F5xPhvB9CNWymaa7DoL-
username           v-token-nginx-o1V7sOhWKMl3WKAbnl
$ mysql -h 127.0.0.1 -u v-token-nginx-o1V7sOhWKMl3WKAbnl -pF5xPhvB9CNWymaa7DoL- itemcollection -e "select * from items"
mysql: [Warning] Using a password on the command line interface can be insecure.
+----+----------+
| id | name     |
+----+----------+
|  1 | bike     |
|  2 | baseball |
|  3 | chair    |
+----+----------+

Vault Agent & Dynamic Secrets

Pripremimo dvije nove datoteke (./agent/psql.tmpl i ./agent/mysql.tmpl) i ubacimo slijedeće za PostgreSQL

{{ with secret "postgres/creds/nginx" -}}
<html>
  <head>
    <link rel="stylesheet" href="https://unpkg.com/mvp.css@1.12/mvp.css"> 
</head>
<body>
  <main>
    <h4>Secret path: postgres/creds/nginx, Policy: nginx-agent-policy</h4>
    <ul>
      <ul><li><strong>Connection String</strong>: postgresql://{{ .Data.username }}:{{ .Data.password }}@postgres:5432/products</li>
      <li><strong>username:</strong> {{ .Data.username }}</li>
      <li><strong>password:</strong> {{ .Data.password }}</li>
    </ul>
    <h4>Vault commands</h4>
    <pre><code>
      vault read postgres/creds/nginx
      vault lease renew postgres/creds/nginx/lease_id
      vault lease revoke postgres/creds/nginx/lease_id
      vault policy read nginx
    </code></pre></div>
  </main>
</body></html>
{{- end }}

i MySql:

{{ with secret "mysql/creds/nginx" -}}
<html>
  <head>
    <link rel="stylesheet" href="https://unpkg.com/mvp.css@1.12/mvp.css"> 
</head>
<body>
  <main>
    <h4>Secret path: mysql/creds/nginx, Policy: nginx-agent-policy</h4>
    <ul>
      <ul><li><strong>Connection String</strong>: {{.Data.username}}:{{.Data.password}}@tcp(mysql:3306)</li>
      <li><strong>username:</strong> {{ .Data.username }}</li>
      <li><strong>password:</strong> {{ .Data.password }}</li>
    </ul>
    <h4>Vault commands</h4>
    <pre><code>
      vault read mysql/creds/nginx
      vault lease renew mysql/creds/nginx/lease_id
      vault lease revoke mysql/creds/nginx/lease_id
      vault policy read nginx-agent-policy
    </code></pre></div>
  </main>
</body></html>
{{- end }}

Nakon toga, moramo proširiti konfiguraciju Vault Agenta da bude informiran i o novim datotekama koje treba generirati ./agent/config.hcl

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

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

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

Pokrenimo Vault Agent

$ docker run --cap-add IPC_LOCK -v $PWD/agent:/agent -v $PWD/nginx:/usr/share/nginx/html --net 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-11T20:06:52.704Z [INFO]  agent.sink.file: creating file sink
2023-04-11T20:06:52.704Z [INFO]  agent.sink.file: file sink configured: path=/agent/.token mode=-rw-r--r--
2023-04-11T20:06:52.704Z [INFO]  agent.template.server: starting template server
2023-04-11T20:06:52.704Z [INFO]  agent.sink.server: starting sink server
2023-04-11T20:06:52.704Z [INFO] (runner) creating new runner (dry: false, once: false)
2023-04-11T20:06:52.704Z [INFO]  agent.auth.handler: starting auth handler
2023-04-11T20:06:52.704Z [INFO]  agent.auth.handler: authenticating
2023-04-11T20:06:52.705Z [INFO] (runner) creating watcher
2023-04-11T20:06:52.706Z [INFO]  agent.auth.handler: authentication successful, sending token to sinks
2023-04-11T20:06:52.706Z [INFO]  agent.auth.handler: starting renewal process
2023-04-11T20:06:52.706Z [INFO]  agent.template.server: template server received new token
2023-04-11T20:06:52.706Z [INFO] (runner) stopping
2023-04-11T20:06:52.706Z [INFO] (runner) creating new runner (dry: false, once: false)
2023-04-11T20:06:52.706Z [INFO]  agent.sink.file: token written: path=/agent/.token
2023-04-11T20:06:52.706Z [INFO] (runner) creating watcher
2023-04-11T20:06:52.706Z [INFO] (runner) starting
2023-04-11T20:06:52.707Z [INFO]  agent.auth.handler: renewed auth token
2023-04-11T20:06:52.710Z [WARN] vault.read(mysql/creds/nginx): failed to check if mysql/creds/nginx is KVv2, assume not: Error making API request.

URL: GET http://vault:8200/v1/sys/internal/ui/mounts/mysql/creds/nginx
Code: 403. Errors:

* preflight capability check returned 403, please ensure client's policies grant access to path "mysql/creds/nginx/"
2023-04-11T20:06:52.711Z [WARN] (view) vault.read(mysql/creds/nginx): vault.read(mysql/creds/nginx): Error making API request.

URL: GET http://vault:8200/v1/mysql/creds/nginx
Code: 403. Errors:

* 1 error occurred:
        * permission denied

Odmah na početku, pojavljuje se greška u Vault Agentu. Ako se sjećate, u prvom dijelu smo kreirali policu koja nam dozvoljava samo 2 stvari: čitanje KV2 nginx i postgres credentials.

path "secret/data/nginx/*"
{
  capabilities = ["create", "read", "update", "delete", "list"]
}

path "postgres/creds/nginx"
{
  capabilities = ["read"]
}

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

Nakon toga izvršimo

$ vault policy write nginx-agent-policy ./policy.hcl
Success! Uploaded policy: nginx-agent-policy

i pokrenemo Vault Agent ponovo:

docker run --cap-add IPC_LOCK -v $PWD/agent:/agent -v $PWD/nginx:/usr/share/nginx/html --net 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-11T20:10:50.411Z [INFO]  agent.sink.file: creating file sink
2023-04-11T20:10:50.411Z [INFO]  agent.sink.file: file sink configured: path=/agent/.token mode=-rw-r--r--
2023-04-11T20:10:50.411Z [INFO]  agent.template.server: starting template server
2023-04-11T20:10:50.411Z [INFO]  agent.sink.server: starting sink server
2023-04-11T20:10:50.411Z [INFO]  agent.auth.handler: starting auth handler
2023-04-11T20:10:50.411Z [INFO] (runner) creating new runner (dry: false, once: false)
2023-04-11T20:10:50.411Z [INFO]  agent.auth.handler: authenticating
2023-04-11T20:10:50.412Z [INFO] (runner) creating watcher
2023-04-11T20:10:50.412Z [INFO]  agent.auth.handler: authentication successful, sending token to sinks
2023-04-11T20:10:50.412Z [INFO]  agent.auth.handler: starting renewal process
2023-04-11T20:10:50.412Z [INFO]  agent.template.server: template server received new token
2023-04-11T20:10:50.412Z [INFO] (runner) stopping
2023-04-11T20:10:50.413Z [INFO] (runner) creating new runner (dry: false, once: false)
2023-04-11T20:10:50.413Z [INFO] (runner) creating watcher
2023-04-11T20:10:50.413Z [INFO]  agent.sink.file: token written: path=/agent/.token
2023-04-11T20:10:50.413Z [INFO] (runner) starting
2023-04-11T20:10:50.413Z [INFO]  agent.auth.handler: renewed auth token
2023-04-11T20:10:50.426Z [INFO] (runner) rendered "/agent/psql.tmpl" => "/usr/share/nginx/html/psql.html"
2023-04-11T20:10:50.443Z [INFO] (runner) rendered "/agent/mysql.tmpl" => "/usr/share/nginx/html/mysql.html"

Primjećujete da je agent kreirao 2 nove datoteke: psql.html i mysql.html. Ovorimo ih u pregledniku:

Pogledajmo mysql.html stranicu

Docker, Vault Agent and Dynamic Secrets from Hashicorp Vault

Dodatak: Rails database.yml

Ako kreiramo novi Vault Agent predložak (./agent/rails.tmpl) slijedećeg sadržaja:

{{ with secret "postgres/creds/nginx" -}}
development:
  adapter: postgresql
  database: products
  host: localhost
  port: 5432
  username: {{ .Data.username }}
  password: {{ .Data.password }}
{{- end }}

I u ./agent/config.hcl dodamo još jedan template blok:

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

Ponovo pokrenimo Vault Agent i primjetit ćete da se kreirala nova datoteka rails.yaml koja je popunjena korisničkim podacima i kao takvu je Rails aplikacija normalno može koristiti.

development:
  adapter: postgresql
  database: products
  host: localhost
  port: 5432
  username: v-approle-nginx-EOCjZfcNqVJ33aiDBMxp-1681247094
  password: YhQ9vFzJG1H-omSmxNcf

Dodatak II:

Vault Agent Templates: https://developer.hashicorp.com/vault/docs/agent/template