Docker Swarm Secrets is a Node library for managing Docker secrets. It aims to be a more robust and modern option than previous secret managers and includes:
- Async/Await Support
- Non-blocking File I/O
- Extensible Automatic Secret Parsing
- Typescript Compatability
For more information about creating and using secrets, please refer to the Docker documentation.
Docker-Swarm-Secrets has been rolled into Technician, a brand new library that lets you manage all your application's config (secrets included!) using the familiar syntax of docker-swarm-secrets
. This package will continue receiving security updates, but will no longer receive feature updates.
Migrating from docker-swarm-secrets
to technician
is easy. The biggest difference is that technician
returns only the secret value from read()
. The object containing the raw data Buffer is still available via describe()
.
Technician also caches results forever by default, instead of not caching by default (a decision that was made for legacy compatability in dss).
import { DSSReader } from 'docker-swarm-secrets';
// ...
const secretReader = new DSSReader();
const mySecret = await secretReader.readSecret('mySecret');
// mySecret === { name: 'mySecret', data: Buffer, secret: Buffer | ParsedData }
import { Technician } from 'technician';
import { FSConfigSource } from '@technician/fs-config-source';
// ...
const technicianReader = new Technician();
// Alternatively, your own secret mount point.
// Technician is built to work with any files, not just secrets!
technicianReader.addSource(new FSConfigSource('/run/secrets'));
const mySecret = await technicianReader.read('mySecret');
// mySecret === Buffer<your file contents here>
Custom interpreters are now even more flexible in technician
! Interpreters are set at the reader level in Technician, instead of being passed in with each read. This allows for less complex typing when accessing the cache. To return multiple data types, you can still create a complex branching interpreter -- or just use multiple Technician instances.
Consult the Technician docs for more information on all the new features added to interpreters.
The same DefaultInterpreters
package is still available from Technician as well.
// ...
const mySecret = await secretReader.readSecret('mySecret',
secret => secret?.data.toString('utf8')
);
// mySecret === { name: 'mySecret', data: Buffer, secret: string }
// ...
const technicianReader = new Technician(
secret => value: secret?.data.toString('utf8')
);
const mySecret = await technicianReader.readSecret('mySecret');
// typeof mySecret === 'string'
npm i docker-swarm-secrets
Version 2.x.x
is compatible with Node 10 LTS and up. For compatability with prior Node releases, use the latest 1.x.x
release.
import { DSSReader } from 'docker-swarm-secrets';
// ...
const secretReader = new DSSReader();
const mySecret = await secretReader.readSecret('mySecret');
/**
* { name: 'mySecret', data: Buffer, secret: Buffer | ParsedData }
*/
// or ...
const allMySecrets = await secretReader.readSecrets();
db.connect(allMySecrets.dbUsername.secret, allMySecrets.dbPassword.secret);
By default, all valid secrets are returned as Buffers. Secrets that do not exist are returned as undefined.
If you wish to provide parsing logic for secrets, you may provide an interpreter
function. The secret
value of the returned object will be set to the value returned by your interpreter
function. Typescript should infer your typing automatically.
const myStringSecret = await secretReader.readSecret('myStringSecret',
secret => secret.?data.toString('utf8')
);
myStringSecret.substring(0, 1); // Typescript is okay with this!
// or, you can manually specify the typing ...
const myBoolSecret = await secretReader.readSecret<boolean>('myBoolSecret',
secret => secret.name === 'myBoolSecret'
);
Interpreters can handle any logic necessary to get a secret into a usable state (deserialization, decryption, etc.) whenever it needs to be accessed to reduce duplication of complex parsing code.
const mySuperComplicatedInterpreter = (secret) => {
// ... 10,000 lines later
};
// Reuse it to parse all your secrets
const mySuperSecretStuff = await secretReader.readSecrets(mySuperComplicatedInterpreter);
If you need to handle a variety of secret data types, you may specify multiple interpreters with readSecrets
.
These interpreters may have an optional predicate
, which determines which secrets are passed to them. The first matching interpreter wins. If no interpreters match a secret, it will not be returned. A catch-all interpreter with no predicate may also be specified.
const allMyParsedSecrets = secretReader.readSecrets([
{ // Will only run on secrets ending in .json
interpreter: s => JSON.parse(s?.data.toString('utf8')),
predicate: s => /\.json$/.test(s.name)
},
{ // Will match any secret
interpreter: s => s.?data.toString('utf8')
},
]);
DSS provides a package of ready-to-use interpeters as DefaultInterpreters
.
import { DSSReader, DefaultInterpreters } from 'docker-swarm-secrets';
// ...
// (Default behavior if no interpreter is specified).
const myBufferSecret = secretReader.readSecret('secret', DefaultInterpreters.asBuffer());
// Default encoding is UTF8.
const myStringSecret = secretReader.readSecret('secret', DefaultInterpreters.asText('utf8'));
// Default encoding is UTF8. Invalid JSON will not be returned.
const myJSONSecret = secretReader.readSecret('secret', DefaultInterpreters.asJSON('utf8'));
// Returns JSON if the text is valid JSON or plaintext if it is not.
// This emulates the behavior of dss 1.x.x and is provided for easy migration.
// It is substantially less efficient than using specific interpreters with predicates.
const legacyStyleSecret = secretReader.readSecret('secret', DefaultInterpreters.asTextOrJSON('utf8'));
readSecretSync
and readSecretsSync
are also available for synchronous code.
They are otherwise identical to the async versions.
If you mount your Docker secrets at a non-default path, you may specify it as an argument to the DSSReader
constructor.
Each reader instance may only read from one secret filesystem.
const secretReader = new DSSReader('/some/place/full/of/secrets');
Docker Swarm Secrets 2.x.x
provides much more flexibility with secret reading and parsing than prior versions.
If you wish to emulate the behavior of version 1.x.x
, the following code approximates it.
// 1.x.x - Automatic JSON parsing
const mySecret = dss.readSecret('secret', { encoding: 'utf8', ignoreJSON: false }, (err, secret) => {
// ...
});
// 2.x.x
const mySecret = await secretReader.readSecret('secret', DefaultInterpreters.asTextOrJSON('utf8'));
// 1.x.x - Plaintext only
const mySecret = dss.readSecret('secret', { encoding: 'utf8', ignoreJSON: true }, (err, secret) => {
// ...
});
// 2.x.x
const mySecret = await secretReader.readSecret('secret', DefaultInterpreters.asText('utf8'));
Contributions and pull requests are always welcome. Please be sure your code passes all existing tests and linting.
Pull requests with full code coverage are encouraged.