0

Hello-Vault

 1 year ago
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.
neoserver,ios ssh client

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
  1. 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
  2. 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:

  1. On macOS, install the Apple XCode Command Line utilities, if needed.

  2. 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)"
    
  3. 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.

  4. On newer macOS laptops with the M1 ARM chip:

    sudo softwareupdate --install-rosetta
    
  5. 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#

    VIDEO:

    1. 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)

    2. Expand the zip. Click Continue, Install. Password. Close.
    3. Verify SDK installed:
    dotnet --list-sdks
    

    Sample response:

    6.0.401 [/usr/local/share/dotnet/sdk]
    6.0.402 [/usr/local/share/dotnet/sdk]
    
    1. Install Visual Studo Code Unversal Stable version from https://code.visualstudio.com by clicking this link for file “VSCode-darwin-universal.zip”.
    2. Unzip.
    3. In /Applications, open “Visual Studio Code.app”.
    4. Install the C# extension.
    5. 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

  6. 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
  7. 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.

  8. Verify Docker version:

    docker compose version
    

    The response, at time of writing, was:

    Docker Compose version 2.12.2
    
  9. Invoke the Docker desktop.

  10. 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

  1. Navigate into the quick-start folder:

    cd quick-start
  2. 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.

  3. 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.

  4. 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."
    
  5. Make sure that Docker Desktop is runnning.
  6. Restart Docker to clear it from a previous run.

  7. 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

  1. Navigate out of quick-start and into:

    cd sample-app

    run.sh

  2. 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
    
  3. 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.

  4. 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

  5. Edit the run-tests.sh file (within folder sample-app) by using code to use VSCode) or other utility:

    code run-tests.sh
     
  6. 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.

  7. Restart Docker.

  8. 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

  9. 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.

    hello-vault-images-1920x1080.jpg
    • 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…"
    
  10. 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:

  1. 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.).

  2. The app calls Vault (at APP_ADDRESS) to get static secret.

  3. 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).
  4. The app adds the static API key in the HTTP header before calling the secure-service.

  5. The response from the app to run-tests.sh is “hello world”.

  6. run-tests.sh calls GET /api/products to access the products database based on dynamic credentials obtained by Vault.

  7. The app calls Vault to request dynamic DB credentials (instead of using long-lived static passwords).

  8. 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

  9. 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.

  10. 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).

  11. The app uses the Vault-provided credentials to access the database.
  12. 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

  1. 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

  2. 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???

  3. This invokes Maven to compile programs:

    RUN mvn clean package -DskipTests

    Although unspecified in code, Maven always open file pom.xml

  4. 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
  5. 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

  6. Copy the app.jar file created to the root folder:

    COPY –from=build /build-project/target/hello-vault-spring.jar /app.jar

  7. Invoke the app.jar program from above:

    ENTRYPOINT [“java”,”-jar”, “/app.jar”]

    Invoking the app HEALTHCHECK

  8. The HEALTHCHECK in the Dockerfile makes a call to the healthcheck API to the server.

  9. The “trap” line is executed after the service exits:

    /# bring down the services on exit
    trap 'docker compose down --volumes' EXIT
    
  10. 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?

  11. 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

  12. 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

  13. Open another Terminal to define the APP_ADDRESS defined earlier:

    APP_ADDRESS="http://localhost:8080"
    
  14. Issue an ad hoc call:

    echo "$APP_ADDRESS"
    curl --silent --request GET "${APP_ADDRESS}/products"
    

    Inside the app

  15. 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.

vault-renewal-flow-1496 x1054.jpg

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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK