Nomad
Create a secret provider plugin
This page describes the plugin specification for custom secret providers, with examples, so you can write your own plugin to utilize secrets from any secrets store.
The full specification follows the examples, and is followed by a list of general considerations.
Follow this workflow to create and use a custom secret provider:
- Develop the plugin code based on the specification.
- Compile your plugin into an executable binary.
- Place the binary in the
client.common_plugin_dir/secrets/directory on each Nomad client node. - Configure the
secretblock in your job specification.
Example
We wrote the example in Go, but the specification is lean enough to be readily fulfilled in any language. The example code is not meant to be inclusive of all error handling.
In this example code, the aws secret provider plugin fetches a secret from AWS
Secret Manager. Refer to the external AWS Secrets Manager User
Guide
for details on AWS Secrets Manager and how to fetch secrets in various languages.
Plugin code
aws.go
package main
import (
"context"
"fmt"
"os"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
"github.com/aws/aws-sdk-go/aws"
)
const generalUsage = "You must run with either 'fingerprint' or 'fetch'. \nYou must pass 'fetch' to the path of the secret."
func returnErr(err string) {
fmt.Printf(`{"error": "%s"}`, err)
os.Exit(1)
}
func main() {
if len(os.Args) < 2 {
returnErr("internal error not enough args")
}
switch os.Args[1] {
case "fingerprint":
fingerprint()
case "fetch":
// fetch should be called with the correct secret path
if len(os.Args) < 3 {
returnErr("internal error no path specified")
}
fetch(os.Args[2])
default:
returnErr("internal error incorrect function")
}
}
func fingerprint() {
fmt.Println(`{"type": "secrets", "version": "0.0.1"}`)
}
// resource: https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets-go-sdk.html
func fetch(secretName string) {
ctx := context.Background()
config, err := config.LoadDefaultConfig(ctx, config.WithRegion(os.Getenv("AWS_REGION")))
if err != nil {
returnErr("error loading default aws config")
}
// Create Secrets Manager client
svc := secretsmanager.NewFromConfig(config)
input := &secretsmanager.GetSecretValueInput{
SecretId: aws.String(secretName),
VersionStage: aws.String("AWSCURRENT"),
}
result, err := svc.GetSecretValue(ctx, input)
if err != nil {
returnErr(err.Error())
}
// Decrypts secret using the associated KMS key.
var secretString string = *result.SecretString
fmt.Printf(`{"result": "%s"}`, secretString)
}
Task configuration
Configure your task to fetch the secret with your customer aws secret
provider. Make sure the provider value matches the name of your plugin
executable.
In this example job specification, the task uses your custom aws
secret provider to fetch an AWS secret named my-aws-secret from the AWS
Secrets Manager in region us-east-2.
job "docs" {
group "example" {
task "server" {
secret "my_secret" {
provider = "aws"
path = "my-aws-secret"
env {
AWS_REGION = "us-east-2"
}
}
}
}
}
Specification
Nomad registers a secret provider plugin if the plugin meets all of the following criteria:
- Is an executable file, such as a script or binary
- Is located in the
client.common_plugin_dir/secrets/directory on Nomad client nodes - Responds appropriately to a
fingerprintcall
Operations
A secret provider plugin must fulfill all of the following operations:
Nomad passes the operation as the first positional argument to the plugin and as
an environment variable prefixed with CPI_.
fingerprint
Nomad calls
fingerprintto discover valid plugins when the client agent starts or is reloaded with a SIGHUP. The version it returns is used to register the plugin on the Nomad node.CLI arguments:
$1=fingerprintEnvironment variables:
CPI_OPERATION=fingerprintExpected stdout:
{"type": "secrets", "version": "0.0.1"}Requirements:
- Must complete within 10 seconds, or Nomad kills it. It should be much faster, as no actual work should be done.
- "type" registers this as a secret provider plugin.
- "version" value must be valid per the hashicorp/go-version golang package.
fetch
Nomad calls
fetchwhen a Nomad job includes a secret block with a provider registered as a plugin. When calling fetch, Nomad includes the secret path as the second CLI argument. If theenv{}block was specified, any key/values are supplied as environment variables to the plugin process.CLI Arguments:
$1=fetch $2=pathEnvironment variables:
CPI_OPERATION=fetchExpected stdout:
{"result": {"key1": "value1", "key2": "value2"}}Expected stdout on error:
{"result": {}, "error": "error message"}Returning an error message is optional. Nomad returns the error message in any error returned to the user.
Requirements:
- Must complete within 10 seconds, or Nomad kills it.
- Must return secret contents as a key/value object, even if the secret was not originally in key/value format.
Considerations
Execution
- The plugin is executed as the same user as the Nomad agent (likely root).