Swift client for interacting with Hashicorp Vault and OpenBao.
Hashicorp Vault and OpenBao are both tools for securely storing, auditing and managing access to secrets, such as API tokens, database credentials, and certificates. VaultCourier is a Swift package that can interact with Hashicorp Vault and OpenBao to retrieve and provision secrets. It is built with swift-openapi and pkl.
- Arbitrary storage of Key/Value secrets (KV-v2)
- Manage third-party secrets: generate and revoke on-demand credentials for database systems, like PostgreSQL and Valkey
- Cryptography as a Service (CaaS) via Transit secret engine
- AppRole Authentication
- Token Authentication
- Namespaces: manage isolated secrets from different deployment environments, applications, or teams within a single vault instance.
- Tracing support
- Pkl Resource Reader (Enabled with PackageTrait
PklSupport)
Here is a simple example of reading and writing your first secret! First run a Vault in dev mode with
container_id=$(docker run --rm --detach -p 8200:8200 -e 'VAULT_DEV_ROOT_TOKEN_ID=learn-vault' hashicorp/vault:latest)or with OpenBao
container_id=$(docker run --rm --detach -p 8200:8200 -e 'BAO_DEV_ROOT_TOKEN_ID=learn-vault' openbao/openbao:latest)then authenticate, write and read the secret.
import VaultCourier
import OpenAPIAsyncHTTPClient
import Foundation
let client = VaultClient(configuration: .defaultHttp(),
clientTransport: AsyncHTTPClientTransport())
// Authenticate
try await client.login(method: .token("learn-vault"))
// Write a secret
let keyToSecret = "my-secret-password"
struct Secret: Codable {
let apiKey: String
}
try await client.writeKeyValue(mountPath: "secret",
secret: Secret(apiKey: "secret_api_key"),
key: keyToSecret)
print("Secret written successfully")
// Read a Secret
let secret: Secret = try await client.readKeyValueSecret(mountPath: "secret", key: keyToSecret)
print("Access Granted! API Key: \(secret.apiKey)")To stop the Vault server run:
docker stop "${container_id}" > /dev/nullFor a more realistic example that illustrates many important Vault concepts, see the tutorials and examples in the documentation.
Vault has many secret engines and authentication methods that need to be enabled before they can be used. Similarly, in VaultCourier's functionality can be extended by enabling the respective Package traits. Currently we support the following:
To enable an additional trait on the package, update the package dependency:
.package(
url: "https://github.com/vault-courier/vault-courier",
.upToNextMinor(from: "0.3.0"),
+ traits: [.defaults, "OtherFeatureSupport"]
)Available Package traits:
PostgresPluginSupport(default): Enable support for Vault-PostgreSQL database plugin HTTP API. Plugin available in Vault and OpenBao. This trait enablesDatabaseEngineSupport.ValkeyPluginSupport(default): Enable support for OpenBao-Valkey database plugin HTTP API. This plugin is only available in OpenBao. This trait enablesDatabaseEngineSupport.DatabaseEngineSupport(default): Enable basic support for database engine clients.AppRoleSupport(default): Enable AppRole authentication.MockSupport(default): Provides a mock client transport for unit testing and development, and adds Encodable conformance to certain Vault response types.TransitEngineSupport: Provides encryption/decryption as a service. Manage cryptographic keys from a centralized Vault. Sign CSRs and manage certificate chains.PklSupport(experimental): Enable Pkl Resource reader implementations that can read Vault secrets directly from pkl files.ConfigProviderSupport(experimental): Enable a Vault configuration provider. This trait provides aswift-configurationConfigProvider implementation that can fetch Vault secrets.
The Vault API is covered by sub-clients that specialize in a particular secret engine, authentication method or internal system features. These sub-clients are accessible via handler-methods. For example:
let configuration = PostgresConnectionConfig(connection: "pg-vault")
try await withDatabaseClient(mountPath: "database_eu_central") { client in
try await client.createPostgresConnection(configuration: configuration)
try await client.rotateRoot(connection: configuration.connection)
}These types of handler methods are useful for Vault operators when multiple calls are going to be made to the same group of endpoints, or when functions need to be scoped to a particular mount or child namespace. Application owners will rarely use these handlers, as they simply consume the secrets and the default functionality can be accessed directly from the VaultClient.
The opt-in Vault API is only accessible via an API handler method.
You can find reference documentation and user guides for VaultCourier here. The vault-courier-examples repository has a number of examples of different uses of the library.
This library is currently under-development, so we recommend using .upToNextMinor in your Package.swift when depending on vault-courier. It's likely we'll have breaking changes between minors pre-1.0. Please try it out and give us feedback! Please do not use it in production.
If you believe you have identified a vulnerability in VaultCourier or any of its related repositories please do not post this in a public forum, do not create a GitHub Issue. Instead please responsibly disclose by contacting us at contact@beamsplitter.co.
VaultCourier is available under the Apache 2.0 license.
We'd like to give a special thanks to Anastasiya Mudrak for helping us digitize our first logo.
