class EphemeralTokenCache extends Object
A flexible token cache with pluggable backend storage which encrypts the cache before storing it in the backend. By default, this uses a Linux file-based backend storage; refer to cacheFile. This is intentionally designed for shared use by multiple unrelated token issuers. This provides centralized generic storage for all ephemeral tokens. This class, when instantiated, represents a single token which has an expiration and automated rotation. The backend cache is a shared resource amongst many unique tokens with expirations. The purpose is to reduce API calls to 3rd party services to request new tokens be issued. Reuse already issued tokens if they're still valid or request a rotation for tokens that have expired.
To run this example, clone Jervis and execute ./gradlew console to bring up a Groovy Console with the classpath set up.
Basic sample usage using keys on disk for storage.
import net.gleske.jervis.remotes.creds.EphemeralTokenCache
EphemeralTokenCache tokenCred = new EphemeralTokenCache('src/test/resources/rsa_keys/good_id_rsa_4096')
However, ideally you would load the private key from a more secure credential backend such as HashiCorp Vault or Jenkins. The following example is for reading the credential from Jenkins.
import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey
import com.cloudbees.plugins.credentials.CredentialsProvider
import jenkins.model.Jenkins
import net.gleske.jervis.exceptions.KeyPairDecodeException
import net.gleske.jervis.remotes.creds.EphemeralTokenCache
// Dynamically load an RSA private key from Jenkins. Key must be 2048-bit or
// greater; 4096-bit recommended.
String credentialId = 'some-credential-id'
EphemeralTokenCache tokenCred = new EphemeralTokenCache({->
CredentialsProvider.lookupCredentials(BasicSSHUserPrivateKey, Jenkins.instance, Jenkins.instance.ACL.SYSTEM).find {
it.id == credentialId
}.with { jenkinsCred ->
if(!jenkinsCred?.getPrivateKey()) {
// Throw an exception because this class will disable encryption if
// the credential does not exist i.e. returns null
throw new KeyPairDecodeException("Private key ${credentialId} must not be null.")
}
// return private
jenkinsCred.getPrivateKey()
}
})
Type | Name and description |
---|---|
String |
cacheFile The path to the persistent cache file if this class is initialized with encryption at rest. |
String |
cacheLockFile The path to a lock file which serializes read and write access to persistent cache where issued tokens are stored. |
Closure |
getPrivateKey A closure that returns a PKCS1 or PKCS8 PEM formatted RSA private key as a String used to encipher the cache for encryption at rest. |
Integer |
hash_iterations Customize the number of SHA-256 hash iterations performed during AES encryption operations. |
Closure |
loadCache A closure which should return a String from loading the cache. |
Closure |
obtainLock A centralized lock obtained in order to serialize loading and persisting backend cache. |
Long |
renew_buffer The time buffer before a renewal is forced. |
Closure |
saveCache A closure which which should support a String parameter used to optionally persist a cache. |
Constructor and description |
---|
EphemeralTokenCache
(Closure resolvePrivateKeyString) An ephemeral token cache which provides encryption at rest. |
EphemeralTokenCache
(Boolean allowEmptyPrivateKey) An ephemeral token cache which provides no encryption at rest. |
EphemeralTokenCache
(String privateKeyPath) Creates an encrypted ephemeral token cache from a private key on disk. |
Type Params | Return Type | Name and description |
---|---|---|
|
String |
getExpiration() Gets expiration of the token. |
|
Long |
getRenew_buffer() Returns renew buffer for the current token. |
|
String |
getToken() Gets the access token used for API authentication retrieved from the cache. |
|
Boolean |
isExpired(String hash) Checks if a token is expired. |
|
Boolean |
isExpired() Checks if a token is expired. |
|
Boolean |
isExpired(Instant expires) Checks if the provided time has passed. |
|
void |
setCacheFile(String cacheFile) Set cache file location when the cache is saved to local disk. |
|
void |
setCacheLockFile(String cacheLockFile) Set cache lock file location when the cache is saved to local disk. |
|
void |
setExpiration(String expiration) Sets the expiration for a given token. |
|
void |
setRenew_buffer(Long renew_buffer) Sets a renew buffer. |
|
void |
updateTokenWith(String token, String expiration, String hash) A new token has been issued so this method will update the backend cache to store the new token. |
The path to the persistent cache file if this class is initialized with encryption at rest.
The path to a lock file which serializes read and write access to persistent cache where issued tokens are stored.
A closure that returns a PKCS1 or PKCS8 PEM formatted RSA private key as a String used to encipher the cache for encryption at rest.
This assumes a private key is insecurely stored as a file. Ideally, the private key is stored in a 3rd party credentials backend.
import net.gleske.jervis.remotes.creds.EphemeralTokenCache
// instantiates getPrivateKey for you
EphemeralTokenCache cred = new EphemeralTokenCache({-> new File('path/to/private_key').text })
// alternately you can manually set it
cred.getPrivateKey = {-> new File('path/to/private_key').text }
Customize the number of SHA-256 hash iterations performed during AES encryption operations. Due to encryption strength, CipherMap automatically rotating keys, and the short-lived nature of the ephemeral tokens this value is lowered from the DEFAULT_AES_ITERATIONS.
A closure which should return a String from loading the cache. Persistent caching of tokens is optional. Before this is called a file lock is obtained on a lock file.
{->
File f = new File(this.cacheFile)
if(!f.exists()) {
return ''
}
f.text
}
A centralized lock obtained in order to serialize loading and persisting backend cache. Since the backend cache is file-based, this lock is also file-based by default. You can replace the default with a distributed lock.
{ Closure body ->
new LockableFile(this.cacheLockFile).withLock {
body()
}
}
The time buffer before a renewal is forced. This is to account for clock drift and is customizable by the client. The value is number of seconds.
A closure which which should support a String parameter used to optionally persist a cache. Before this is called a file lock is obtained on a lock file.
{ String cache ->
File f = new File(this.cacheFile)
// initialize file with private permissions
if(!f.exists()) {
['/bin/sh', '-ec', "touch '${this.cacheFile}'; chmod 600 '${this.cacheFile}'"].execute()
}
// write out cache
f.withWriter('UTF-8') { Writer w ->
w << cache
}
}
An ephemeral token cache which provides encryption at rest. A dynamic resolution of the private key from a 3rd party credential backend is the intention of this constructor.
resolvePrivateKeyString
- An executable Closure that takes
no parameters and returns a PKCS1 or PKCS8
PEM formatted RSA private key. If this
ever returns null or empty string, then a
TokenException will be thrown.An ephemeral token cache which provides no encryption at rest. If using this, then you should update the loadCache and saveCache closures to use something other than a file-based backend.
allowEmptyPrivateKey
- Uses a plain text cache if true. It
will throw a TokenException if
false.Creates an encrypted ephemeral token cache from a private key on disk. This assumes a private key is insecurely stored on disk if no 3rd party credential backend is used.
This constructor is provided for convenience and is equavalent to the following closure constructor.
new EphemeralTokenCache({-> new File(privateKeyPath).text })
resolvePrivateKeyString
- Path to a file which contains a PKCS1 or
PKCS8 PEM formatted RSA private key.Gets expiration of the token.
Returns renew buffer for the current token.
Gets the access token used for API authentication retrieved from the cache. Before calling this, you must call isExpired or updateTokenWith method first otherwise null will be returned.
Checks if a token is expired.
hash
- A hash used for performing a lookup on an internal token
cache.Checks if a token is expired.
Checks if the provided time has passed.
expires
- Check if this instant is expired based on
getRenew_buffer() and Instant.now.Set cache file location when the cache is saved to local disk.
cacheFile
- A local filesystem path to a YAML file. The contents
will be YAML.Set cache lock file location when the cache is saved to local disk.
cacheLockFile
- A local filesystem path to a file. This will be used
for file locking to serialize reading and updating
the local cacheFile.Sets the expiration for a given token.
expiration
- An ISO instant formatted string like
Instant.toString.Sets a renew buffer. Does not allow renew buffer to be undefined or go below zero.
Jervis API documentation.