/*
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
namespace Amazon.SecretsManager.Extensions.Caching
{
using System;
using System.Threading;
using System.Threading.Tasks;
using Amazon.Runtime;
using Amazon.SecretsManager.Model;
using Microsoft.Extensions.Caching.Memory;
///
/// A class used for clide-side caching of secrets stored in AWS Secrets Manager
///
public class SecretsManagerCache : ISecretsManagerCache
{
private readonly IAmazonSecretsManager secretsManager;
private readonly SecretCacheConfiguration config;
private readonly MemoryCacheEntryOptions cacheItemPolicy;
private readonly MemoryCache cache = new MemoryCache(new MemoryCacheOptions{ CompactionPercentage = 0 });
///
/// Initializes a new instance of the class.
///
public SecretsManagerCache()
: this(new AmazonSecretsManagerClient(), new SecretCacheConfiguration())
{
}
///
/// Initializes a new instance of the class.
///
public SecretsManagerCache(IAmazonSecretsManager secretsManager)
: this(secretsManager, new SecretCacheConfiguration())
{
}
///
/// Initializes a new instance of the class.
///
public SecretsManagerCache(SecretCacheConfiguration config)
: this(new AmazonSecretsManagerClient(), config)
{
}
///
/// Initializes a new instance of the class.
///
public SecretsManagerCache(IAmazonSecretsManager secretsManager, SecretCacheConfiguration config)
{
this.config = config;
this.secretsManager = secretsManager;
cacheItemPolicy = new MemoryCacheEntryOptions()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMilliseconds(this.config.CacheItemTTL)
};
if (this.secretsManager is AmazonSecretsManagerClient sm)
{
sm.BeforeRequestEvent += this.ServiceClientBeforeRequestEvent;
}
}
private void ServiceClientBeforeRequestEvent(object sender, RequestEventArgs e)
{
if (e is WebServiceRequestEventArgs args && args.Headers.ContainsKey(VersionInfo.USER_AGENT_HEADER) && !args.Headers[VersionInfo.USER_AGENT_HEADER].Contains(VersionInfo.USER_AGENT_STRING))
args.Headers[VersionInfo.USER_AGENT_HEADER] = String.Format("{0}/{1}", args.Headers[VersionInfo.USER_AGENT_HEADER], VersionInfo.USER_AGENT_STRING);
}
///
/// Disposes all resources currently being used by the SecretManagerCache's underlying MemoryCache.
///
public void Dispose()
{
cache.Dispose();
}
///
/// Asynchronously retrieves the specified SecretString after calling .
///
public async Task GetSecretString(String secretId, CancellationToken cancellationToken = default)
{
SecretCacheItem secret = GetCachedSecret(secretId);
GetSecretValueResponse response = null;
response = await secret.GetSecretValue(cancellationToken);
return response?.SecretString;
}
///
/// Asynchronously retrieves the specified SecretBinary after calling .
///
public async Task GetSecretBinary(String secretId, CancellationToken cancellationToken = default)
{
SecretCacheItem secret = GetCachedSecret(secretId);
GetSecretValueResponse response = null;
response = await secret.GetSecretValue(cancellationToken);
return response?.SecretBinary?.ToArray();
}
///
/// Requests the secret value from SecretsManager asynchronously and updates the cache entry with any changes.
/// If there is no existing cache entry, a new one is created.
/// Returns true or false depending on if the refresh is successful.
///
public async Task RefreshNowAsync(String secretId, CancellationToken cancellationToken = default)
{
return await GetCachedSecret(secretId).RefreshNowAsync(cancellationToken);
}
///
/// Returns the cache entry corresponding to the specified secret if it exists in the cache.
/// Otherwise, the secret value is fetched from Secrets Manager and a new cache entry is created.
///
public SecretCacheItem GetCachedSecret(string secretId)
{
SecretCacheItem secret = cache.Get(secretId);
if (secret == null)
{
secret = cache.Set(secretId, new SecretCacheItem(secretId, secretsManager, config), cacheItemPolicy);
if (cache.Count > config.MaxCacheSize)
{
// Trim cache size to MaxCacheSize, evicting entries using LRU.
cache.Compact((double)(cache.Count - config.MaxCacheSize) / cache.Count);
}
}
return secret;
}
}
}