Skip to main content

Basics

note

This guide assumes familiarity with the Spring Framework.
If you are new to Spring, we recommend starting with their official guides to get up to speed.

Managing secrets like API keys, database credentials, and encryption keys securely is a critical security practice. Secrets should never be hardcoded in your application's source code.

Singularity provides a robust, pluggable secret store that handles the management, rotation, and retrieval of secrets.

Core Concepts

Secret

This is the fundamental data model for a secret. It stores a unique identifier (id), a developer-friendly key (key), the secret value itself (value), and a timestamp for when it was created (createdAt).

SecretStore

This interface abstracts the underlying storage mechanism for secrets, allowing for different implementations like HashiCorp Vault or a local file system. The SecretStore also includes built-in caching to improve performance by reducing the number of calls to the external secret manager.
Secrets loaded from the store are automatically cached with an expiration time.

SecretService

This is the core class for managing a single, specific secret. It handles the logic for retrieving the current secret and for key rotation. It relies on a SecretStore to persist the secrets.

Usage

To use the secret store, you must define a class that extends SecretService. This class will be responsible for managing a single type of secret.

Step 1: Define a SecretService

By extending the abstract SecretService, you define a new type of secret that the framework's key rotation service will recognize and manage automatically. You must provide the SecretStore and AppProperties dependencies, along with a unique key for your secret.

@Service
class MySecretService(
secretStore: SecretStore,
appProperties: AppProperties
) : SecretService(
secretStore = secretStore,
key = "my-secret-key",
algorithm = "AES",
appProperties = appProperties
) {
override val logger = KotlinLogging.logger {}
}

Step 2: Use the Secret

Once defined, you can inject your custom SecretService into any other service or component to retrieve the current secret's value.

@Service
class MyOtherService(
private val mySecretService: MySecretService
) {
suspend fun doSomethingSecure(): Result<SomethingSecret, SecretStoreException> = coroutineBinding {
val secret = mySecretService.getCurrentSecret().bind().value

// Use the secret value for your secure operation
SomethingSecret.fromSecret(secret)
}
}

Secret Key Rotation

To mitigate the risk of a compromised key, the secret keys will be rotated periodically. Key rotation is automatically scheduled to run via a cron job. You can learn more about the configuration in the next section.

You can also manually trigger key rotation through the following endpoints:

Configuration

PropertyTypeDescriptionDefault value
singularity.secrets.storeLOCAL or VAULTSingularity currently provides two different implementations for secret stores: LOCAL and VAULT. The local secret store is preconfigured. For large-scale application it is recommended to use VAULT.LOCAL
singularity.secrets.key-rotation-cronString of a cron jobThe scheduled rate when key rotation should be performed. Default is every three months on the first day of the month at 04:00:00 am.0 0 4 1 */3 *
singularity.secrets.cache-expirationLongSecrets are cached for a specific amount of time. This value configures after how many seconds they will expire.900000