Hello-Vault
source link: https://wilsonmar.github.io/hello-vault/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
How to code your app to use HashiCorp Vault to write and read static secrets and eliminate database password theft with cubbyholes and wrapped secrets
Why this?
Keeping secrets secret is a fundamental skill for all developers, especially in today’s hostile internet full of scammers, ransomware gangs, and state-sponsored terrorism using “killware”.
This is like working on your TV the different remotes and logins to setup different streaming services. Once you go through the motions, you can get through quickly (for awhile until you change TV).
VIDEO: Meet the team which created this talk about their sample code (in Go).
What does this contribute?
NOTE: Content here are my personal opinions, and not intended to represent any employer (past or present). “PROTIP:” here highlight information I haven’t seen elsewhere on the internet because it is hard-won, little-know but significant facts based on my personal research and experience.
This article provides a step-by-step deep-dive tour, with commentary, contrasting code across several repositories created by developers inside and outside HashiCorp.
This article takes a deep dive into sample (template) code within a GitHub repo. This has features other sample code lack:
- A run.sh shell file that creates servics needed within Docker Compose containers
- A run-tests.sh that calls the program’s APIs
-
The scripts work on new Apple M1 ARM64 chips as well as older Intel x86 macOS machines
- Containers created include a local instance of HashiCorp Vault for the app to store and retrieve secrets
- A database able to communicate with Vault to dynamically create credentials
-
Example of how to generate secrets dynamically instead of using long-running secrets for others to steal
- The same app in other languages: Go, dotnet C#, Java, Ruby to compare and learn
-
Obtain the repo for your language of choice. For example:
git clone https://github.com/hashicorp/hello-vault-spring cd hello-vault-spring
- https://github.com/hashicorp/hello-vault-bash (under construction)
- https://github.com/hashicorp/hello-vault-dotnet (C# with MS-SQL)
- https://github.com/hashicorp/hello-vault-go
- https://github.com/hashicorp/hello-vault-python (under construction)
- https://github.com/hashicorp/hello-vault-spring (Maven, Java) using https://spring.io/projects/spring-vault
- https://github.com/hashicorp/hello-vault-ruby
- https://github.com/hashicorp/hello-vault-rust (under construction)
Each repo uses Docker Compose to create a dev environment containing a dev Vault instance and an app database. All the example repos make calls to a PostgreSQL database, except for the C# (dotnet) example, which calls a MS-SQL database.
These repos provides single shell scripts to make it easier than following guides such as:
- https://spring.io/guides/gs/accessing-vault/
- https://github.com/mp911de/spring-cloud-vault-config-samples
Alternately, to install Vault using Terraform, etc. for production use, see:
- https://github.com/averche/vault-guides
-
Each repo has two folders with two example apps:
-
quick-start to write a secret, then read that secret back
-
sample-app to make API calls (using curl CLI commands) to an app server which interacts with a database
BTW Vault has other Secret Engines to handle other types of secrets not demonstrated by this sample program, such as generation of SSH certificates, X.509 certificates for SSL/TLS, etc.
-
Install on macOS:
Let’s dive in by installing pre-requities. Each technology has a different set of technologies:
-
On macOS, install the Apple XCode Command Line utilities, if needed.
-
Install OS package manager so that you can upgrade to the latest version with a single command.
On macOS: Homebrew.
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
-
Install utilities using package manager.
On macOS:
brew install curl jq tree
NOTE: Brew now knows to not re-install the latest version if it’s already installed.
-
On newer macOS laptops with the M1 ARM chip:
sudo softwareupdate --install-rosetta
-
Install the compiler for your language of choice:
Install Java
For Java, Zulu is my favorite open-source compiler:
brew install zulu
Alternately, to install the latest version of OpenJDK:
brew install --cask temurin
Alternately, to install a specific version of OpenJDK:
brew tap homebrew/cask-versions brew install --cask temurin8 brew install --cask temurin11
Install Go
brew install golang go version
Sample response:
go version go1.19.2 darwin/arm64
See https://formulae.brew.sh/formula/go https://jimkang.medium.com/install-go-on-mac-with-homebrew-5fa421fc55f5
mkdir -p $HOME/go/{bin,src,pkg}
In ~/.bashrc or .zshrc
export GOPATH=$HOME/go export GOROOT="$(brew --prefix golang)/libexec" export PATH="$PATH:${GOPATH}/bin:${GOROOT}/bin"
Install C#
-
Click this link to download dotnet-sdk-6.0.402-osx-arm64.pkg at https://dotnet.microsoft.com (Ignore https://learn.microsoft.com/en-us/dotnet/core/install/macos)
- Expand the zip. Click Continue, Install. Password. Close.
- Verify SDK installed:
dotnet --list-sdks
Sample response:
6.0.401 [/usr/local/share/dotnet/sdk] 6.0.402 [/usr/local/share/dotnet/sdk]
- Install Visual Studo Code Unversal Stable version from https://code.visualstudio.com by clicking this link for file “VSCode-darwin-universal.zip”.
- Unzip.
- In /Applications, open “Visual Studio Code.app”.
- Install the C# extension.
- Verify .NET runtimes (.NET Core) installed:
dotnet --list-runtimes
Microsoft.AspNetCore.App 6.0.9 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.NETCore.App 6.0.9 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
C# versions:
C# 11 is supported only on .NET 7 and newer versions.
C# 10 is supported only on .NET 6 and newer versions.
C# 9 is supported only on .NET 5 and newer versions.Install Ruby
Ruby:
Install Rust
Rust:
Install Python
Python:
brew install python python --version pip install virtualenv # used to: python -m venv venv # create venv enviornment to activate by: source venv/bin/activate # for (venv) prompt to: pip install hvac # needed for Python to work with HashiCorp Vault
virtualvenv is used to ensure that Python packages play nice with each other - so that other Python projects with competing or incompatible versions of the same add-ons (dependencies) don’t collide with this package.
Verify Java
-
-
Verify version to see if install took:
For Java/Spring:
java --version
You should see something like this:
openjdk 19.0.1 2022-10-18 OpenJDK Runtime Environment Zulu19.30+11-CA (build 19.0.1+10) OpenJDK 64-Bit Server VM Zulu19.30+11-CA (build 19.0.1+10, mixed mode, sharing)
- Java 17 the latest (3rd) LTS was released on September 14, 2021.
- Java 19 General Availability began on September 20, 2022.
See https://www.wikiwand.com/en/Java_version_history
-
Install the Docker Desktop. On macOS, see https://docs.docker.com/desktop/mac/apple-silicon/
brew install docker brew install docker-desktop
Alternately, instead of docker-compose, consider Kublet static pod.
PROTIP: Get the Docker Desktop logo on your Mac Taskbar: pinch four fingers on your trackpad to drag and drop the logo onto your Taskbar. You should see the Docker logo when you point the mouse at the top of your screen.
-
Verify Docker version:
docker compose version
The response, at time of writing, was:
Docker Compose version 2.12.2
-
Invoke the Docker desktop.
-
View file .gitignore using cat or a text editor such as code (for VSCode).
cat .gitignore
In the file, the contents of folder target/ generated during each run is not uploaded to GitHub. Several target folders are generated.
Several files generated by STS, IDEA IDE, NetBeans, and VSCode are also not uploaded.
quick-start
-
Navigate into the quick-start folder:
cd quick-start
-
View the application program file using cat or a text editor such as code (for VSCode):
For Spring (Java):
cat src/main/java/com/hashicorp/quickstart/App.java
Alternately, it’s Program.cs for dotnet (C#).
Static dev APP_ADDRESS
In dev programs, the Host, Port, and Scheme are hard-coded for combination into “http://127.0.0.1:8200”.
But in production code, those are obtained from an environment variable APP_ADDRESS with a value such as vault.mycorp.com.
Dev Authentication
A message is not issued for authentication unless it’s unsuccessful: “unable to initialize Vault client:”
dev-only-token is hard-coded. But in production code, the APP_TOKEN environment variable is read by the app to retrieve a secure Token that is unique to each user for access to Vault. And the Token’s value should never be revealed in messages to ensure confidentiality.
Write/Read Static secrets
The program writes a static secret named “password” with a value of “Hashi123”, then reads it back. But normal apps would usually just read secrets written by another program.
Vault client library functions
Each programming language uses a different library to perform low-level functionality.
In the dotnet (C#) repo:
- file quickstart.csproj defines the library used.
- “secret” is defined as the mountPoint
- “my-secret-password” is defined as the path
In Java/Spring:
- function opsForVersionedKeyValue()
- put function to “data”.
In Python:
- hvac is the library
Expected output
When we run the program, we expect that:
- After writing a secret, the program outputs “Secret written successfully.”.
- After reading the secrets successfully, the program outputs “Access granted!”.
Of course, in your own program, you can output whatever text you want.
-
View file run.sh file using cat or a text editor such as code (for VSCode):
cat run.sh
Notice it uses docker compose commands to bring processes down then up again:
docker compose down --volumes docker compose up -d --build
The –build parameter invokes a build referencing the Dockerfile.
Notice the dev Vault server is started with a parameter:
-e ‘VAULT_DEV_ROOT_TOKEN_ID=dev-only-token’
In production, several mechanisms can be used to start the Vault server securely, including access to cloud provider secret managers.
Source compilation
In the Spring (Java) repo, command mvn clean package references the pom.xml file to compile the source code file App.java into file quickstart.jar in the target folder.
java -jar target/quickstart.jar runs the result of App.java source file compilation.
In the dotnet (C#) repo, command dotnet run Program.cs compiles the Program.cs source file.
In the go repo, command go run main.go compiles the Go source and invokes the result.
-
If you want to keep the app running so you can make additional commands, insert a # character at the left edge of these commands to comment them out of executing:
# docker stop "${container_id}" > /dev/null \# echo "Vault server has stopped."
- Make sure that Docker Desktop is runnning.
-
Restart Docker to clear it from a previous run.
-
Invoke the shell script:
./run.sh
Wait for various lines to appear until this appears:
Secret written successfully. Access granted! Stopping Vault dev server.. Vault server has stopped.
sample-app
-
Navigate out of quick-start and into:
cd sample-app
run.sh
-
Let’s run it, then analyze the output:
./run.sh
If you get this response, it means you need to get Docker Desktop running:
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
Otherwise, you should see a bunch of lines scroll by until ending with this list and statuses:
Container services
[+] Running 8/8 ⠿ Network sample-app_default Created 0.1s ⠿ Volume "sample-app_trusted-orchestrator-volume" Created 0.0s ⠿ Container sample-app-secure-service-1 Healthy 11.1s ⠿ Container sample-app-database-1 Healthy 11.1s ⠿ Container sample-app-vault-server-1 Healthy 11.0s ⠿ Container sample-app-trusted-orchestrator-1 Healthy 11.9s ⠿ Container sample-app-app-1 Healthy 22.7s ⠿ Container sample-app-healthy-1 Started 22.9s
-
View file run.sh using cat or a text editor such as code (for VSCode).
cat run.sh
The two commands invokes docker compose first down, then up again.
docker compose down --volumes docker compose up -d --build
docker-compose.xml
docker compose commands always invoke the docker-compose.yml in the same folder. It contains declarations to setup containers.
NOTE: hello-vault-dotnet, as separate docker-compose.arm64.yaml is, at time of writing, needed to work around mssql/server’s incompatibility with arm64 architecture.
-
Let’s use a text editor code (VSCode) to look at the docker-compose.yml file within the sample-app folder:
cat docker-compose.yml
The file begins with …
version: "3.9" services: app: build: WebService/ environment: VAULT_ADDRESS: http://vault-server:8200 VAULT_APPROLE_ROLE_ID: demo-web-app VAULT_APPROLE_SECRET_ID_FILE: /tmp/secret VAULT_API_KEY_PATH: api-key VAULT_API_KEY_FIELD: api-key-descriptor VAULT_DATABASE_CREDENTIALS_ROLE: dev-readonly DATABASE_DATA_SOURCE: tcp:database,1433 DATABASE_INITIAL_CATALOG: example DATABASE_TIMEOUT: 30 SECURE_SERVICE_ENDPOINT: http://secure-service/api ...
The heading for each group in the file correspond to the Container name:
Processes in Docker
To summarize the name of each group in docker-compose.yml:
docker-compose.yaml Container/Volume in Docker app: sample-app-app-1 vault-server: sample-app-vault-server-1 trusted-orchestrator: sample-app-trusted-orchestrator-1 database: sample-app-database-1 secure-service: sample-app-secure-service-1 healthy: sample-app-healthy-1 volumes: sample-app_trusted-orchestrator-volume Edit run-tests.sh
-
Edit the run-tests.sh file (within folder sample-app) by using code to use VSCode) or other utility:
code run-tests.sh
-
If you don’t want processes to stop after the script ends (so you can issue more commands), type a “#” comment character in front of the docker compose down command line, like this:
# bring down the services on exit # trap 'docker compose down --volumes' EXIT
If you comment out the compose down and save the file, processes will continue to run unless you break out by pressing command+C.
-
Restart Docker.
-
Let’s run it, then analyze the output:
./run-tests.sh
Wait for a bunch of lines to scroll by until ending with this list and statuses:
[+] Running 6/6 ⠿ Container sample-app-database-1 Healthy 1.7s ⠿ Container sample-app-secure-service-1 Healthy 1.7s ⠿ Container sample-app-vault-server-1 Healthy 1.7s ⠿ Container sample-app-trusted-orchestrator-1 Healthy 1.7s ⠿ Container sample-app-app-1 Healthy 2.3s ⠿ Container sample-app-healthy-1 Started 2.6s
App output
These lines are output from the app (which we’ll examine next):
[TEST 1]: output: {"message":"hello world!"} [TEST 1]: OK [TEST 2]: output: [{"id":1,"name":"Rustic Webcam"},{"id":2,"name":"Haunted Coloring Book"}] [TEST 2]: OK
Docker removal output
These lines are output from Docker won’t appear if you edited out the removal commands:
[+] Running 8/8 ⠿ Container sample-app-healthy-1 Removed 0.0s ⠿ Container sample-app-app-1 Removed 4.4s ⠿ Container sample-app-trusted-orchestrator-1 Removed 0.2s ⠿ Container sample-app-secure-service-1 Removed 0.2s ⠿ Container sample-app-vault-server-1 Removed 0.2s ⠿ Container sample-app-database-1 Removed 0.3s ⠿ Volume sample-app_trusted-orchestrator-volume Removed 0.0s ⠿ Network sample-app_default Removed 0.0s
Additional command to show ports
-
If you commented out, you can obtain the commands used to create each Docker process along with each of their ports. To avoid widening the width of the Terminal, specify columns using this command:
Ports used by each container
NAMES PORTS sample-app-app-1 0.0.0.0:8080->8080/tcp sample-app-trusted-orchestrator-1 sample-app-vault-server-1 0.0.0.0:8200->8200/tcp sample-app-secure-service-1 0.0.0.0:1717->80/tcp sample-app-database-1 0.0.0.0:5432->5432/tcp
Containers diagram with Ports
Each component illustrated in this diagram is a container running within Docker.
-
app - “Web App” running app.jar compiled from App.java.
-
secure-service - a simulated 3rd party (mock) service docker-entrypoint that responds to calls authenticated by a static API key sent as the value to the X-Vault-Token HTTP header of the call. The response is 200 from GET & LIST and 204 from POST, PUT, DELETE.
-
database, from docker-entrypoint.s, contains SQL to 1- create the database, 2- populate with data, 3- define roles
-
trusted-orchestrator is created from a Dockerfile used to build its container image and an entrypoint.sh at the root. It is invoked when the service becomes active. It is the mechanism that launches applications and injects them with a Secret ID at runtime; typically something like Terraform, K8s, or Chef. ??? See https://learn.hashicorp.com/tutorials/vault/secure-introduction#trusted-orchestrator
-
vault-server, initiated by /vault/entrypoint.sh, contains a default.conf.template file which issues the “hello world!” response if API calls succeed.
Additionally, two services appears in the list of containers:
- app-healthy - a dummy service to block “docker compose up -d” from returning until all services are up & healthy
Container invocations
NAMES COMMAND sample-app-app-1 "java -jar /app.jar" sample-app-trusted-orchestrator-1 "./entrypoint.sh" sample-app-vault-server-1 "/vault/entrypoint.sh" sample-app-secure-service-1 "/docker-entrypoint.…" sample-app-database-1 "docker-entrypoint.s…"
-
-
View the run-tests.sh file (within sample-app) using the built-in cat command or use a text editor code (VSCode):
cat run-tests.sh
The shell script run-tests.sh invokes two calls to the Web App:
“[TEST 1]” = POST /api/payments obtains static API keys to call the payments API
“[TEST 2]” = GET /api/products obtains dynamic credentials to call the products database
Flowchart sequence
This seems so complex (clever) that I am making a video to gradually (logically) reveal each component in this flow:
-
run-tests.sh calls POST /api/payments to write the static API keys to be used to call the payments API. The call can also be to a 3rd-party service (such as Twilio for mail, SMS, PayPal, etc.).
-
The app calls Vault (at APP_ADDRESS) to get static secret.
-
The static API key and value is added into Vault. For our mock example, at the right side of the diagram, we manually store the API key to our Secure Server using this Vault CLI command:
vault kv put kv-v2/api-key apikey=my-secret-key
Output logs
Print logs that were output from the app process:
docker logs sample-app-app-1
... 2022/01/11 20:29:01 getting secret api key from vault 2022/01/11 20:29:01 getting secret api key from vault: success! [GIN] 2022/01/11 - 20:29:01 | 200 | 7.366042ms | 192.168.192.1 | POST "/payments"
BTW, in production, there would be a background process that forwards logs to a central collection SIEM (Security Information and Event Management) system such as Splunk. This log centralization provides a detailed enterprise-wide history of operations that makes security forensics possible by the corporate SOC (Security Operations Center). -
The app adds the static API key in the HTTP header before calling the secure-service.
-
The response from the app to run-tests.sh is “hello world”.
-
run-tests.sh calls GET /api/products to access the products database based on dynamic credentials obtained by Vault.
-
The app calls Vault to request dynamic DB credentials (instead of using long-lived static passwords).
-
Vault uses its pre-defined partnership with PostgreSQL to request that temporary (short-lived) credentials be created dynamically. The equivalent CLI command is:
kv put secret/mysql/webapp db-name-"users" \ username="admin" password="12345"
Remember that the 3-define file contains:
CREATE ROLE vault_db_user LOGIN SUPERUSER PASSWORD 'vault_db_password'; CREATE ROLE readonly NOINHERIT; GRANT SELECT ON ALL TABLES IN SCHEMA public TO "readonly";
NOTE: Although PostgreSQL is used in this sample, Vault also works with MySQL, Microsoft SQL Server, and other database vendors.
To transmit created credentials securely to the Web App, Vault puts the secret in a cubbyhole for each user.
“Cubbyhole” is an American phrase for a small safe place allocated to each individual.
Even the root account cannot read the contents of an individual cubbyhole. – see COURSE at CloudAcademy.com
-
Rather than exposing the client token during transmission, for safe delivery to the Web App, Vault has a Trusted Orchestrator figuratively “wrap” that secret within a short-lived single-use token.
The token sent to the Web App acts as a pointer to the user’s Cubbyhole.
-
The Web App receives the wrapping token for “unwrap” by retrieving the secret from its cubbyhole ???
Note that retrieval can only occur once. An error is logged (and sent to the SOC) if additional retrievals are attempted. Thus, the library can detect malfeasance with the response-wrapping token.
Even the system who created the initial token won’t see the original value. See https://learn.hashicorp.com/tutorials/vault/cubbyhole-response-wrapping
Functionally speaking, the token provides authorization to use an encryption key from Vault’s keyring to decrypt the data:
- https://learn.hashicorp.com/tutorials/vault/cubbyhole-response-wrapping
- https://www.vaultproject.io/docs/concepts/response-wrapping
- VIDEO: Using the Cubbyhole Secret’s Engine in HashiCorp Vault to Securely Share Secrets
BTW, the wrapping token can be revoked (just like any other token) to minimize risk of unauthorized access (especially in a “Break Glass” stop-loss action after a breach).
- The app uses the Vault-provided credentials to access the database.
-
The data returned from the database is output by run-tests.sh
[TEST 2]: output: [{“id”:1,”name”:”Rustic Webcam”},{“id”:2,”name”:”Haunted Coloring Book”}]
OK is output after the response is validated.
Diving into run-tests.sh
APP_ADDRESS
-
Notice APP_ADDRESS is hard-coded:
APP_ADDRESS=”http://localhost:8080”
But in production, the program would instead retrieve APP_ADDRESS from a system variable. Also, production APP_ADDRESS would, instead of “http”, specify use of secure “https” protocol (on default port 443).
Also, Production code would retrieve the APP_TOKEN to ensure valid identity for using Vault.
Dockerfile
-
docker compose up -d –build –quiet-pull builds based on the Dockerfile
FROM maven:3.8.4-openjdk-17 as build COPY . /build-project WORKDIR /build-project RUN mvn clean package -DskipTests FROM openjdk:17 EXPOSE 8080 COPY --from=build /build-project/target/hello-vault-spring.jar /app.jar ENTRYPOINT ["java","-jar", "/app.jar"] HEALTHCHECK \ --start-period=1s \ --interval=10s \ --timeout=1s \ --retries=30 \ CMD curl --fail -s http://localhost:8080/healthcheck || exit 1
Occassional version update
The top line FROM clause retrieves from maven hub openjdk version 17. This would needs to be updated occassionally.
build-project folder???
-
This invokes Maven to compile programs:
RUN mvn clean package -DskipTests
Although unspecified in code, Maven always open file pom.xml
-
View file pom.xml using cat or a text editor such as code (for VSCode).
cat pom.xml
In the file, note that versions need to be updated over time. See
- https://github.com/spring-projects/spring-boot/releases is v2.7.5 as of October, 2022
- https://github.com/spring-projects/spring-framework/releases
-
File HelloVaultSpringApplicationTests.java within folder path /test/java/com/hashicorp/hellovaultsprint is compiled:
package com.hashicorp.hellovaultspring; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class HelloVaultSpringApplicationTests { @Test void contextLoads() { } }
which specifies java to compile using
-
Copy the app.jar file created to the root folder:
COPY –from=build /build-project/target/hello-vault-spring.jar /app.jar
-
Invoke the app.jar program from above:
ENTRYPOINT [“java”,”-jar”, “/app.jar”]
Invoking the app HEALTHCHECK
-
The HEALTHCHECK in the Dockerfile makes a call to the healthcheck API to the server.
-
The “trap” line is executed after the service exits:
/# bring down the services on exit trap 'docker compose down --volumes' EXIT
-
This retrieves from Vault’s payments secret:
# TEST 1: POST /payments (static secrets) output1=$(curl --silent --request POST "${APP_ADDRESS}/payments")
That is what causes the response:
[TEST 1]: output: {"message":"hello world!"}
“hello world” was issued from file default.conf.template within folder /sample-app/setup/secure-service/default.conf.template which defines server responses:
server { listen 80; server_name localhost secure-service; default_type application/json; location /healthcheck { return 200 "{\"message\":\"ok\"}"; } location /api { if ($http_x_api_key != "${EXPECTED_API_KEY}") { return 401 "{\"error\":\"unauthorized\"}"; } return 200 "{\"message\":\"hello world!\"}"; } location / { return 404 "{\"error\":\"resource not found\"}"; } }
QUESTION: Can you think of a better response than “hello world”. How about “posted”?
QUESTION: In Production, “localhost” would not be there. What replaces it?
-
This obtains the products secret:
# TEST 2: GET /products (dynamic secrets) output2=$(curl --silent --request GET "${APP_ADDRESS}/products")
That curl CLI command is what causes response:
[TEST 2]: output: [{"id":1,"name":"Rustic Webcam"},{"id":2,"name":"Haunted Coloring Book"}] [TEST 2]: OK
PROTIP: “Rustic Webcam” and “Haunted Coloring Book” are returned because the database was loaded from the 2-data.sql file within folder /setup/database:
INSERT INTO products (name) VALUES ('Rustic Webcam'), ('Haunted Coloring Book'); INSERT INTO customers (first_name, last_name, email, phone) VALUES ('Winston', 'Higginsbury', '[email protected]', '555-555-5555'), ('Vivian', 'Vavilov', '[email protected]', '555-555-5556');
Create username and password in Database
-
This 2-data.sql was invoked to define a role used to create a user within the database:
CREATE ROLE vault_db_user LOGIN SUPERUSER PASSWORD 'vault_db_password'; CREATE ROLE readonly NOINHERIT; GRANT SELECT ON ALL TABLES IN SCHEMA public TO "readonly";
Ad hoc request
-
Open another Terminal to define the APP_ADDRESS defined earlier:
APP_ADDRESS="http://localhost:8080"
-
Issue an ad hoc call:
echo "$APP_ADDRESS" curl --silent --request GET "${APP_ADDRESS}/products"
Inside the app
-
Set breakpoint in the Java program: ???
Renewal of wrapping tokens
We know that static passwords with unlimited validity are bad because that provides time when secrets can be stolen.
So we improve security by limiting the duration when each secret is valid by giving each secret a limited Time To Live (TTL) before expiration. We do this by creating a token that grants access with a specific TTL.
Monitoring is necessary to achieve a balance between two extremes:
-
A token which provides longer time than needeed exposes the asset to risk of compromise.
-
A token which provides not enough time would cause delay and errors in processing, which we want to avoid.
We track how often either condition occurs. And we track the distribution of how long leases are actually needed in order to set the TTL a bit longer after the average time needed.
PROTIP: Track the maximum time a lease is actually needed.
To reduce the disruption of apps experiencing expired tokens, we provide a way to renew tokens during a “grace period”. Renewals are done instead or re-issuing tokens because the cryptographic processing to create tokens require some effort. Renewals take less computing effort.
Thus, we have two TTLs for each component:
-
A default TTL for each individual token.
-
A maximum TTL when no more renewals are allowed, and authentication or reconnection is required again.
Additionally, there can be limits on the number of times a lease/token can be renewed.
This diagram illustrates the relationship of renewals among components.
Notice that the TTL is longest at the top component and gets shorter as we go down the stack toward the asset:
- Wrapping tokens managed by the Trusted Orchestrator have a TTL that is the token_max_ttl
- Account authorization tokens managed by Vault
- Each lease to access the database (the asset) has the shortest TTL
Each component has a different name for each TTL:
-
Each token for authorization into the system must be renewed before the token_ttl. When the maximum number of token renewals or token_max_ttl is reached, another login is necessary again.
-
The wrapping-token lifetime is limited by the token_max_ttl.
-
If an account needs to login again, that account must also getcreds and reconnect to the database.
-
Each lease to access the database must be renewed before the default_ttl. When the maximum number of lease renewals or max_ttl is reached, reconnection is necessary again.
Now let’s analyze the coding to achieve the above.
Coding for renewal is performed by vault_renewal.go. Currently, only hello-vault-go has renewal logic.
VIDEO: this sample code uses an extraordinaryly short TTL (Time To Live) in order to trigger renewals to show how it works. In production, timeouts are generally 30-60 minutes.
See VIDEO: Vault 1.2: Database Credential Rotation and Identity Tokens
Legacy services that can’t handle token regeneration would use “periodic” tokens with no max_ttl.
The equivalent CLI command to specify daily renewal period (repeatable indefinitely):
vault write auth/token/create policies="example" period="24h"
Limiting the number of times that a token can be renewed to 2 is set by -use-limit=2
Database within Kubernetes
References
- https://medium.com/hashicorp-engineering/essential-elements-of-vault-part-1-5a64d3de3be8
- https://medium.com/hashicorp-engineering/essential-patterns-of-vault-part-2-b4d34976f1dc
Others must know: please click to share:
Hello-Vault was published on November 03, 2022.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK