Managing Secrets in Node.js with HashiCorp Vault
Tags: security, javascript
Published: 2021-07-04 13:53:07

As the number of services grows in an organization, the problem of secret management only gets worse. Between Zero Trust and the emergence of microservices, handling secrets such as tokens, credentials, and keys has become an increasingly challenging task. That’s where a solution like HashiCorp’s Vault can help organizations solve their secret management woes.

Although there are secret management tools native to each cloud provider, using these solutions lock you in with a specific cloud provider. Vault, on the other hand, is open source and portable.

In this article we’ll look at how [HashiCorp’s Vault](https://www.vaultproject.io/) can help organizations manage their secrets and in turn enhance their cybersecurity posture. We’ll then set up Vault in dev mode on our machines and interact with it via its web UI and CLI. Finally, we’ll programmatically interact with Vault using Node.js.

## Vault Top Features

Vault is HashiCorp’s open-source product for managing secrets and sensitive data. Here’s a list of Vault’s top features that make it a popular choice for secret management:

1. Built-in concept of low trust and enforcement of security by identity  
2. Encryption at rest 
3. Several ways to [authenticate](https://www.vaultproject.io/docs/auth) against Vault, e.g., tokens, LDAP, AppRole, etc.    
4. [Policies](https://www.vaultproject.io/docs/concepts/policies)  to govern the level of access of each identity 
5. Lots of secret backends, each catering to specific needs, including key-value store, [Active Directory](https://www.vaultproject.io/docs/secrets/ad), etc. 
6. Support for multiple storage backends for high availability, e.g., databases (MySQL, Postgres), object stores (GCS, S3), HashiCorp’s Consul, etc. 
7. Ability to generate dynamic secrets, such as [database credentials](https://www.vaultproject.io/docs/secrets/databases), cloud service account keys ([Google](https://www.vaultproject.io/docs/secrets/gcp), [AWS](https://www.vaultproject.io/docs/secrets/aws), [Azure](https://www.vaultproject.io/docs/secrets/azure)), [PKI certificates](https://www.vaultproject.io/docs/secrets/pki), etc.    
8. Built-in [TTL and lease](https://www.vaultproject.io/docs/concepts/lease) for provided credentials 
9. Built-in [audit](https://www.vaultproject.io/docs/audit) trail which logs every interaction with Vault 
10. Several ways to interact with the Vault service, including Web UI, CLI, Rest API, and programmatic access via language libraries

These features make Vault a compelling choice for cloud-based microservices architecture, where each microservice will authenticate with Vault in a distributed manner and access the secrets. The access to secrets can be managed for each individual microservice using policies following the principle of least privilege.

In the next section, we’ll set up Vault in dev mode and discuss ways to set it up in production. We’ll then configure the dev Vault instance for our hands-on demo, learning different configuration options along the way.

## Setup for Hands-On Demo

We’ll use Docker to set up Vault on our local machine. Note that this setup is not production ready. We’ll start Vault in dev mode, which uses all the insecure default configurations.

Running Vault in production isn’t easy. To do so, you can either choose [HashiCorp Cloud Platform](https://www.hashicorp.com/cloud-platform), the fully managed Vault in the cloud, or leave it to your organization’s infrastructure team to set up a secure and highly available Vault cluster. 

Let's get started. 

### Start Vault in Dev Mode

We’ll start the Vault service by using the official Docker image vault:1.7.3.

If you run the container without any argument, it will start the Vault server in Dev mode by default.

```bash
docker run --name vault -p 8200:8200 vault:1.7.3
```

As Vault is starting, you’ll see a stream of logs. The most prominent log is a warning telling you that Vault is running in development mode:
  
```bash
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.
```

If you read the message closely, you’ll notice a few things. First, it says the Vault is unsealed with a single unseal key, and second, it mentions a root token. What does this mean?

By default, when you start Vault in production mode it’s [sealed](https://www.vaultproject.io/docs/concepts/seal), meaning you can’t interact with it yet. To get started, you’ll need to unseal it and get the unseal keys and root token to authenticate against Vault.

In case a breach is detected, the Vault server can be sealed again to protect against malicious access.

The other information that gets printed in logs is a root token, which can be used to authenticate against Vault. The option of authentication by tokens is enabled by default and the root token can be used to initiate the first interaction with Vault.

Note that if your organization’s infrastructure team has set up the Vault, they might have enabled some other auth backends as discussed in the previous section.

Copy the root token, as we’ll use it to login to Vault UI.

Head over to [http://localhost:8200](http://localhost:8200) and you’ll see the login screen below on the Vault web UI.  

![](https://cdn.codersociety.com/uploads/vault-login.png)  

### Enable KV Secret Backend

Enter your root token (copied from the previous step) and hit “Sign In.” You’ll be greeted with the following screen.

![](https://cdn.codersociety.com/uploads/vault-secret-backend.png)

You can see that there is already a `KV backend` enabled at path `secret`. This comes enabled in dev mode by default.

If it is not enabled in your Vault installation, you can do so by clicking on `Enable New Engine` and then selecting `KV backend` and follow through the setup.

We’ll use this backend to store our secrets and then later retrieve them in the Node.js demo.

### Configure AppRole Auth Method

We’ll now configure the AppRole auth method, which our Node.js application will use to retrieve the secrets from our key value backend.

Select `Access` from the top menu. You’ll see only the `token` method enabled. 

Click `Enable New Method` and select `AppRole`. Leave the settings to default and click `Enable Method`.

### Create Policy for Secret Access

We’ll create a policy that allows read-only access to the KV secret backend.

Select `Policies` from the top menu and click `Create ACL Policy`.

Enter name as `readonly-kv-backend`, and enter following content for `Policy`.

```
path "secret/data/mysql/webapp" {
  capabilities = [ "read" ]
}
```

Following the principle of least privilege, this policy will only give read access to secrets at the specific path.

Hit `Create Policy` to save it.

### Create AppRole for Node.js Application

We’re going to switch gears and use Vault CLI to finish setting up our demo. There are two ways to access Vault CLI; you can download the Vault binary, or you can exec into Vault container and access the CLI. For this demo we’ll use the latter.

```bash
docker exec -it vault /bin/sh
```

We’ll then set up the `VAULT_ADDR` and `VAULT_TOKEN` environment variables.

```bash
export VAULT_ADDR=http://localhost:8200
export VAULT_TOKEN=<ROOT TOKEN>
```

Now let’s create an AppRole and attach our policy to this role. 

```bash
vault write auth/approle/role/node-app-role \
    token_ttl=1h \
    token_max_ttl=4h \
    token_policies=readonly-kv-backend
```

You should be able to see it being created successfully.

```
Success! Data written to: auth/approle/role/node-app-role
```

Each AppRole has a `RoleID` and `SecretID`, much like a username and password. The application can exchange this `RoleID` and `SecretID` for a token, which can then be used in subsequent requests.

### Get RoleID and SecretID

Now we’ll fetch the `RoleID` pertaining to the node-app-role via the following command:
  
```bash
vault read auth/approle/role/node-app-role/role-id
```

Next we’ll fetch the `SecretID`:

```bash
vault write -f auth/approle/role/node-app-role/secret-id
```

Make sure you store these values somewhere safe, as we’ll use them in our Node.js application.

Please note that it's not safe to deliver `SecretID` to our applications like this. You should use [response wrapping](https://learn.hashicorp.com/tutorials/vault/approle#response-wrap-the-secretid) to securely deliver `SecretID` to your application. For the purpose of this demo, we’ll pass `SecretID` as an environment variable to our application.

### Create a Secret

As the last step of our setup process, we’ll create a secret key-value pair that we will access via our Node.js application.

```bash
vault kv put secret/mysql/webapp db_name="users" username="admin" password="passw0rd"
```

Now that we have our setup ready, we can proceed to our Node.js application.

## Manage Secrets via Node.js

In this section we’ll see how to interact with Vault via Node.js and use the [node-vault](https://www.npmjs.com/package/node-vault) package to interact with our Vault server.

Install the `node-vault` package first, if not already installed.

```bash
npm install node-vault
```

Before we begin, set the `ROLE_ID` and `SECRET_ID` environment variables to pass these values to the application.

```bash
export ROLE_ID=<role id fetched in previous section>
export SECRET_ID=<secret id fetched in previous section>
```

Now let’s write the sample Node application.

```javascript
const vault = require("node-vault")({
  apiVersion: "v1",
  endpoint: "http://127.0.0.1:8200",
});

const roleId = process.env.ROLE_ID;
const secretId = process.env.SECRET_ID;

const run = async () => {
  const result = await vault.approleLogin({
    role_id: roleId,
    secret_id: secretId,
  });

  vault.token = result.auth.client_token; // Add token to vault object for subsequent requests.

  const { data } = await vault.read("secret/data/mysql/webapp"); // Retrieve the secret stored in previous steps.

  const databaseName = data.data.db_name;
  const username = data.data.username;
  const password = data.data.password;

  console.log({
    databaseName,
    username,
    password,
  });

  console.log("Attempt to delete the secret");

  await vault.delete("secret/data/mysql/webapp"); // This attempt will fail as the AppRole node-app-role doesn't have delete permissions.
};

run();
```

Store this script as `index.js` and run it via the `node index.js` command.

If everything is set up correctly, your secrets should be printed on your screen. Apart from your secrets, you’ll also see the error in deleting the secret. This confirms that our AppRole only has access to read the secret and not delete it.

## Conclusion

In this article we saw the importance of having a secret manager in a distributed systems architecture. We also learned to access Vault via Node.js applications, retrieve secrets, and interface with Vault via Web UI and CLI to configure it for our sample application.

From storage backends to auth backends, Vault comes with a lot of options so you can tune it perfectly to your organization’s needs. If you’re looking for a secret management solution to your microservices architecture challenges, HashiCorp’s Vault should be at the top of your list.

For our latest insights and updates, follow us on [LinkedIn](https://www.linkedin.com/company/codersociety/)

---

*Originally published at [https://codersociety.com](https://codersociety.com)*