Plug-and-play executable to pass secrets as environment variables to a process.
Murmur is a small binary that reads its environment variables, replaces references to secrets with the secrets' values, and passes the resulting variables to your application. Variables that do not reference secrets are passed as-is.
Several tools like Murmur exist, each supporting a different secret provider. Murmur aims to support as many providers as possible, so you can use Murmur no matter which provider you use.
Scaleway | AWS | Azure | GCP | Vault | 1Password | Doppler | |
---|---|---|---|---|---|---|---|
🤫 Murmur | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
Berglas | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
Bank Vaults | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
1Password CLI | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
Doppler CLI | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
If you know of a similar tool that is not listed here, please open an issue so that we can add it to the list.
If you use a secret provider that is not supported by Murmur, please open an issue so that we can track demand for it.
- Fetching a database password
- Adding Murmur to a container image
- Adding Murmur to a Kubernetes pod
- Parsing JSON secrets
- Go library usage
- Providers and filters
- Error handling and troubleshooting
- Changes from v0.4 to v0.5
Murmur runs as a wrapper around any command. For example, if you want to connect to a PostgreSQL database, instead of running this command:
export PGPASSWORD="Q-gVzyDPmvsX6rRAPVjVjvfvR@KGzPJzCEg2"
psql -h 10.1.12.34 -U my-user -d my-database
You run this instead:
export PGPASSWORD="scwsm:database-password"
murmur run -- psql -h 10.1.12.34 -U my-user -d my-database
Murmur will fetch the value of the database-password
secret from Scaleway
Secret Manager, set the PGPASSWORD
environment variable to that value, and
then run psql
.
Murmur is a static binary, so you can simply copy it into your container image and use it as your entrypoint. For convenience, the murmur binary is released as a container image you can copy from in your Dockerfile:
COPY --from=ghcr.io/busser/murmur:latest /murmur /bin/murmur
Then you can change your image's entrypoint:
# from this:
ENTRYPOINT ["/bin/run-my-app"]
# to this:
ENTRYPOINT ["/bin/murmur", "run", "--", "/bin/run-my-app"]
You can use Murmur in a Kubernetes pod even if your application's container image does not include Murmur. To do so, you can use an init container that copies Murmur into an emptyDir volume, and then use that volume in your application's container.
Here is an example:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
initContainers:
- name: copy-murmur
image: ghcr.io/busser/murmur:latest
command: ["cp", "/murmur", "/shared/murmur"]
volumeMounts:
- name: shared
mountPath: /shared
containers:
- name: my-app
image: my-app:latest
command: ["/shared/murmur", "run", "--", "/bin/run-my-app"]
volumeMounts:
- name: shared
mountPath: /shared
volumes:
- name: shared
emptyDir: {}
Storing secrets as JSON is a common pattern. For example, a secret might contain a JSON object with multiple fields:
{
"host": "10.1.12.34",
"port": 5432,
"database": "my-database",
"username": "my-user",
"password": "Q-gVzyDPmvsX6rRAPVjVjvfvR@KGzPJzCEg2"
}
Murmur can parse that JSON and set environment variables for each field by using
the jsonpath
filter:
export PGHOST="scwsm:database-credentials|jsonpath:{.host}"
export PGPORT="scwsm:database-credentials|jsonpath:{.port}"
export PGDATABASE="scwsm:database-credentials|jsonpath:{.database}"
export PGUSER="scwsm:database-credentials|jsonpath:{.username}"
export PGPASSWORD="scwsm:database-credentials|jsonpath:{.password}"
murmur run -- psql
If you have multiple references to the same secret, Murmur will fetch the secret only once to avoid unnecessary API calls.
Alternatively, you can use the jsonpath
filter to set a single environment
variable with the entire JSON object:
# psql supports connection strings, so we can use a single variable
export PGDATABASE="scwsm:database-credentials|jsonpath:postgres://{.username}:{password}@{.host}:{.port}/{.database}"
murmur run -- psql
Murmur uses the Kubernetes JSONPath syntax for the jsonpath
filter. See the
Kubernetes documentation
for a full list of capabilities.
As of v0.7.0, Murmur's internal components are available as a public Go library. You can import and use Murmur's provider system and secret resolution pipeline directly in your Go applications.
package main
import (
"fmt"
"log"
"github.com/busser/murmur/pkg/murmur"
)
func main() {
// Define secrets using the same syntax as environment variables
secrets := map[string]string{
"DB_PASSWORD": "awssm:my-database-secret",
"API_KEY": "gcpsm:my-project/api-credentials|jsonpath:{.key}",
"DEBUG_MODE": "passthrough:true", // Non-secret values pass through
}
// Resolve all secrets
resolved, err := murmur.ResolveAll(secrets)
if err != nil {
log.Fatal(err)
}
fmt.Printf("DB Password: %s\n", resolved["DB_PASSWORD"])
fmt.Printf("API Key: %s\n", resolved["API_KEY"])
fmt.Printf("Debug Mode: %s\n", resolved["DEBUG_MODE"])
}
package main
import (
"context"
"fmt"
"log"
"github.com/busser/murmur/pkg/murmur"
)
func main() {
// Get a specific provider
providerFactory, exists := murmur.ProviderFactories["awssm"]
if !exists {
log.Fatal("AWS Secrets Manager provider not available")
}
provider, err := providerFactory()
if err != nil {
log.Fatal(err)
}
defer provider.Close()
// Fetch a secret directly
secret, err := provider.Resolve(context.Background(), "my-secret")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Secret value: %s\n", secret)
}
All built-in providers are available through murmur.ProviderFactories
:
"awssm"
- AWS Secrets Manager"azkv"
- Azure Key Vault"gcpsm"
- GCP Secret Manager"scwsm"
- Scaleway Secret Manager"passthrough"
- Testing/no-op provider
The Go library enables several powerful patterns:
- Configuration-based secret resolution instead of environment variables
- Custom secret injection workflows in Go applications
- Testing with mock providers for unit tests
- Building custom secret management tools on top of Murmur's provider system
Murmur's architecture is built around providers and filters. Providers fetch secrets from a secret manager, and filters parse and transform the secrets.
Murmur only edits environment variables which contain valid queries. A valid query is structured as follows:
provider_id:secret_ref|filter_id:filter_rule
Using a filter is optional, so this is also a valid query:
provider_id:secret_ref
Murmur does not support chaining multiple filters yet.
To fetch a secret from Scaleway Secret Manager, the query must be structured as follows:
scwsm:[region/]{name|id}[#version]
If region
is not specified, Murmur will delegate region selection to the
Scaleway SDK. The SDK determines the region based on the environment, by looking
at environment variables and configuration files.
One of name
or id
must be specified. Murmur guesses whether the string is a
name or an ID depending on whether it is a valid UUID. UUIDs are treated as IDs,
and other strings are treated as names.
The version
must either be a positive integer or the "latest" string. If
version
is not specified, Murmur defaults to "latest".
Examples:
scwsm:my-secret
scwsm:my-secret#123
scwsm:my-secret#latest
scwsm:fr-par/my-secret
scwsm:fr-par/my-secret#123
scwsm:fr-par/my-secret#latest
scwsm:3f34b83f-47a6-4344-bcd4-b63721481cd3
scwsm:3f34b83f-47a6-4344-bcd4-b63721481cd3#123
scwsm:3f34b83f-47a6-4344-bcd4-b63721481cd3#latest
scwsm:fr-par/3f34b83f-47a6-4344-bcd4-b63721481cd3
scwsm:fr-par/3f34b83f-47a6-4344-bcd4-b63721481cd3#123
scwsm:fr-par/3f34b83f-47a6-4344-bcd4-b63721481cd3#latest
Murmur uses the environment's default credentials to authenticate to Scaleway.
You can configure Murmur the same way you can configure the scw
CLI.
To fetch a secret from AWS Secrets Manager, the query must be structured as follows:
awssm:{name|arn}[#{version_id|version_stage}]
One of name
or arn
must be specified. You can use a full or partial ARN.
However, if your secret's name ends with a hyphen followed by six characters,
you should not use a partial ARN. See these AWS docs
for more information.
You can optionally specify one of version_id
or version_stage
. Murmur
guesses whether the string is an ID or a stage depending on whether it is a
valid UUID. UUIDs are treated as version IDs, and other strings are treated as
version stages. If neither version_id
or version_stage
are specified, Murmur
defaults to "AWSCURRENT".
Examples:
awssm:my-secret
awssm:my-secret#MY_VERSION_STAGE
awssm:my-secret#9517cc59-646a-4393-81d7-5e6f2d43cbe7
awssm:arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret
awssm:arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret#MY_VERSION_STAGE
awssm:arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret#9517cc59-646a-4393-81d7-5e6f2d43cbe7
Murmur uses the environment's default credentials to authenticate to AWS.
You can configure Murmur the same way you can configure the aws
CLI.
To fetch a secret from Azure Key Vault, the query must be structured as follows:
azkv:keyvault_hostname/name[#version]
The keyvault_hostname
must be the fully qualified domain name of the Key
Vault. For example, if your Key Vault's URL is https://example.vault.azure.net/
,
then the keyvault_hostname
is example.vault.azure.net
.
The name
is the name of the secret.
The version
must be a valid version ID. If version
is not specified, Murmur
defaults to the latest version of the secret.
Examples:
azkv:example.vault.azure.net/my-secret
azkv:example.vault.azure.net/my-secret#5ddc29704c1c4429a4c53605b7949100
Murmur uses the environment's default credentials to authenticate to Azure. You
can set these credentials with the environment variables listed here
(such as AZURE_CLIENT_ID
, AZURE_CLIENT_SECRET
, AZURE_TENANT_ID
), or with workload identity.
To fetch a secret from GCP Secret Manager, the query must be structured as follows:
gcpsm:project/name[#version]
The project
must be either a project ID or a project number.
The name
is the name of the secret.
The version
must be a valid version number. If version
is not specified,
Murmur defaults to the latest version of the secret.
Examples:
gcpsm:my-project-123/my-secret
gcpsm:my-project-123/my-secret#1
gcpsm:my-project-123/my-secret#latest
gcpsm:123456789012/database-credentials
gcpsm:123456789012/database-credentials#5
Murmur uses the environment's default credentials to authenticate to GCP.
You can configure Murmur the same way you can configure the gcloud
CLI.
This provider is meant for demo and testing purposes. It does not fetch any secrets and simply returns the secret reference as the secret's value.
This provider, like all other providers, is fully tested. It is safe to use in production, although why would you?
Examples:
passthrough:my-not-so-secret-value
To parse a JSON secret and extract a value from it, or to use a secret value in a template, the query must be stuctured as follows:
provider_id:secret_ref|jsonpath:template
The provider_id
and secret_ref
can be any valid secret reference.
The template
is a JSONPath template.
Murmur uses the Kubernetes JSONPath implementation, so you can use any feature
described in the Kubernetes docs.
If the secret's value is not valid JSON, Murmur will treat it as a string and execute the template anyway. This means that you can use JSONPath templates with non-JSON secrets.
Examples:
scwsm:my-secret|jsonpath:{.password}
scwsm:my-secret|jsonpath:postgres://{.username}:{.password}@{.hostname}:{.port}/{.database}
scwsm:my-secret|jsonpath:the secret is {@}
Invalid reference format
Error: invalid reference: missing colon
This means your secret reference doesn't follow the correct format. Ensure you use:
provider_id:secret_ref[|filter_id:filter_rule]
Authentication failures
Error: failed to load AWS config: NoCredentialProviders
This indicates missing or invalid cloud provider credentials. Check:
- AWS:
AWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
, or AWS profile configuration - Azure:
AZURE_CLIENT_ID
,AZURE_CLIENT_SECRET
,AZURE_TENANT_ID
- GCP:
GOOGLE_APPLICATION_CREDENTIALS
orgcloud auth application-default login
- Scaleway:
SCW_ACCESS_KEY
,SCW_SECRET_KEY
Secret not found
Error: failed to access secret "my-secret": secret not found
Verify:
- Secret name/ID is correct
- Secret exists in the specified region/project
- You have the necessary permissions to read the secret
- For versioned secrets, the version exists
JSONPath filter errors
Error: failed to apply filter: invalid JSONPath expression
Check your JSONPath syntax against the Kubernetes JSONPath documentation.
Enable verbose logging (when using as CLI):
export MURMUR_LOG_LEVEL=debug
murmur run -- your-command
Test secret resolution (when using as library):
resolved, err := murmur.ResolveAll(map[string]string{
"TEST": "passthrough:hello-world",
})
if err != nil {
log.Printf("Resolution failed: %v", err)
}
Validate individual providers:
provider, err := murmur.ProviderFactories["awssm"]()
if err != nil {
log.Printf("Provider initialization failed: %v", err)
}
defer provider.Close()
secret, err := provider.Resolve(context.Background(), "test-secret")
if err != nil {
log.Printf("Secret resolution failed: %v", err)
}
- Concurrent resolution: Murmur fetches secrets concurrently per provider, but sequentially within each provider
- Caching: Duplicate secret references are cached within a single resolution call
- Resource cleanup: Always call
Close()
on providers when using the library directly
Following community feedback, we have made two significant changes in v0.5:
- We have renamed the project from "Whisper" to "Murmur", to make the project documentation easier to find on search engines.
- We have renamed the
exec
command torun
, to make it clear that we are not executing the command directly, but rather running it as a subprocess.
We have made it so that none of these changes are breaking. You can upgrade to v0.5 without changing anything in how you use Whisper/Murmur.
We now publish binaries and container images with both names. The exec
command
is still available, but it will log a warning message telling you to use the new
run
command instead.
We recommend that you update your scripts to use the new name and command, but you have all the time you need to do so.