The fastify-secrets-* plugins let web apps access secrets from different storage mechanisms inside Fastify applications.

Web applications often need access to secret information like database credentials, API keys for external services or signing keys for cryptographic operations in order to function properly. This need poses the question of where to store such secrets so that they’re accessible only to the people and software authorised to access them.

It’s very common to use environment variables for local development because they are easy to write and read via popular libraries like dotenv, which allow storing them on the file system. Using .env files to store secrets is not recommended in production scenarios because exposing them in unencrypted plain text files poses an obvious security risk.

All popular cloud providers offer secret storage services, which allow administrators to configure secrets and applications to retrieve them in a secure way. Google offers GCP Secret Manager, Amazon offers AWS Secrets Manager, and Microsoft offers Azure Key Vault.

When using a cloud infrastructure, it is usually recommended to rely on the provider’s own secret management solution. This solution allows your application to easily interact with the secrets store across any of the services offered by the cloud provider.

Assuming that you’re using a secret management solution, a couple of different approaches provide the secrets your application needs to function:

  1. Load the secrets at runtime from the secrets management service when the application starts.
  2. Load the secrets into the environment directly from the secrets store, thereby decoupling the app from the platform.

In this article, we discuss the first approach in the context of a Fastify application.

Introducing fastify-secrets-* plugins

As is common in the rich Fastify ecosystem, the core framework doesn’t contain any specific features related to secrets management. Instead, several plugins provide such features.

The family of fastify-secrets.* plugins provides a common interface to interact with different secret storage mechanisms.

It does so by introducing an abstraction layer between the application and the underlying storage service. The application only needs to know the name of the secret, and the plugin takes care of looking up the value and providing it to the application.

fastify secrets plugins

The current family of plugins consists of:

The great thing about these plugins is that they expose a common interface and abstract away the implementation details of the secrets storage. Therefore, they can be used interchangeably and they can, for instance, be dynamically chosen based on the environment where the application is running.

fastify-secrets-env

fastify-secrets-env uses environment variables as a secrets storage and is most useful for local development.

npm install --save fastify-secrets-env

If your application stores a PostgreSQL database password in an environment variable called PG_PASS, you can configure the plugin as follows:

const FastifySecrets = require('fastify-secrets-env')

fastify.register(FastifySecrets, {
  secrets: {
    dbPassword: 'PG_PASS'
  }
})

Then, inside your application’s code, you can retrieve the password by accessing the secrets decorator that the plugin registers on the fastify instance.

// content of process.env.PG_PASS
console.log(fastify.secrets.dbPassword)

As the code snippets show, the application’s code is no longer tied to the environment variable where the secret is stored and can use the abstraction layer provided by the plugin to access the secret’s value using a conventional name.

fastify-secrets-gcp

This plugin uses GCP Secret Manager as a secrets storage. It’s perfect for production deployment scenarios.

npm install --save fastify-secrets-gcp

To use the plugin, the application must be configured to access the Secret Manager service. A detailed explanation can be found in the plugin’s documentation and in the official GCP documentation.

The fastify-secrets-gcp plugin is similar to the env variant shown earlier. The difference between them is that, in this case, the secret name is represented by the ID of the secret as configured in GCP Secret Manager.

const FastifySecrets = require('fastify-secrets-gcp')

fastify.register(FastifySecrets, {
  secrets: {
    dbPassword: 'projects/PROJECT-ID/secrets/SECRET-ID/versions/latest'
  }
})

Thanks to the common interface across all the plugins family, the secret is accessed in exactly the same way as before:

// content of projects/PROJECT-ID/secrets/SECRET-ID/versions/latest
console.log(fastify.secrets.dbPassword)

fastify-secrets-aws

The third member of the plugin family is fastify-secrets-aws. This plugin allows accessing secrets using AWS Secret Manager service and is also suitable for production deployments.

As in the GCP case, the application must be configured to be able to communicate with AWS services. The configuration is described at a high level in the plugin’s docs and more in detail in the official AWS documentation.

npm install --save fastify-secrets-aws

Once installed, the plugin can be configured and used in a way similar to the earlier examples:

const FastifySecrets = require('fastify-secrets-aws')

fastify.register(FastifySecrets, {
  secrets: {
    dbPassword: 'database-password'
  }
})

In the example above, database-password is the name of the AWS Secret Manager secret where the database password is stored.
Once again, it can used within the application in the now-familiar way:

// content of 'database-password'
console.log(fastify.secrets.dbPassword)

fastify-secrets-azure

The last member of the plugin family is fastify-secrets-azure, which allows secrets to be accessed using Azure Key Vault and is also suitable for production deployments.

As in the GCP and AWS cases, the application must be configured to be able to communicate with Azure. The configuration process is described at a high level in the plugin’s documentation and more in detail in the official Azure documentation.

npm install --save fastify-secrets-azure

Once installed, the plugin can be configured and used in a similar way to the earlier examples, except that Azure requires the name of a Vault provided via the clientOptions configuration setting:

const FastifySecrets = require('fastify-secrets-azure')

fastify.register(FastifySecrets, {
  secrets: {
    dbPassword: 'database-password'
  },
  clientOptions: {
    vaultName: 'vault-name'
  }
})

In the example above, database-password is the name of the Azure Key Vault secret where the database password is stored and vault-name is the name of the vault.

Once again, it can used within the application in the now-familiar way:

// content of 'database-password'
console.log(fastify.secrets.dbPassword)

Choice depends on the environment

A Fastify application needs to run both in a developer’s local environment and a production deployment. Because environment variables are more suitable for the first case, and cloud services are more suitable for the second, the plugins can be used conditionally, depending on the environment, at runtime.

The NODE_ENV environment variable is a common way to tell Node.js whether it’s running in production or development mode, so we can use that one to decide which plugin to use.

const environment = process.env.NODE_ENV

if (environment === 'development') {
  fastify.register(require('fastify-secrets-env'), {
    secrets: {
      dbPassword: 'PG_PASS'
    }
  })
} else {
  fastify.register(require('fastify-secrets-aws'), {
    secrets: {
      dbPassword: 'database-password'
    }
  })
}

This means the application’s database connection logic doesn’t need to be aware of which plugin is used; it can simply access the database password using the uniform interface, which returns the value based on which plugin is configured:

// content of environment variable "PG_PASS" in development
// content of AWS secret "database-password" otherwise
console.log(fastify.secrets.dbPassword) 

The future of fastify-secrets-* plugins

NearForm created and open sourced a family of plugins to access secrets from different storage mechanisms inside Fastify applications and allow users to secure applications. For examples of how we use them, check out our Titus accelerator.

The family of plugins is still small, but the sheer number of cloud providers offering a secrets management service means there are more to come. Implementing them is very easy, so if you’re interested in contributing to the Fastify ecosystem we encourage you to check out the source code of any of the plugins and implement your own.

Don’t miss a beat

Get all the latest NearForm news,
from technology to design.
View all posts  |  Technology  |  Business  |  Culture  |  Opinion  |  Design
Follow us for more information on this and other topics.