secretcache/cache.go (101 lines of code) (raw):
// Copyright 2019 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.
// Package secretcache provides the Cache struct for in-memory caching of secrets stored in AWS Secrets Manager
// Also exports a CacheHook, for pre-store and post-fetch processing of cached values
package secretcache
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws/middleware"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
smithymiddleware "github.com/aws/smithy-go/middleware"
)
// Cache client for AWS Secrets Manager secrets.
type Cache struct {
lru *lruCache
CacheConfig
Client SecretsManagerAPIClient
}
// New constructs a secret cache using functional options, uses defaults otherwise.
// Initialises a SecretsManager Client from a new config.LoadDefaultConfig.
// Initialises CacheConfig to default values.
// Initialises lru cache with a default max size.
func New(optFns ...func(*Cache)) (*Cache, error) {
cache := &Cache{
//Initialise default configuration
CacheConfig: CacheConfig{
MaxCacheSize: DefaultMaxCacheSize,
VersionStage: DefaultVersionStage,
CacheItemTTL: DefaultCacheItemTTL,
},
}
// Iterate over options allowing user to specify alternate
// configurations.
for _, optFn := range optFns {
optFn(cache)
}
//Initialise lru cache
cache.lru = newLRUCache(cache.MaxCacheSize)
//Initialise the secrets manager client
if cache.Client == nil {
cfg, err := config.LoadDefaultConfig(context.Background(), config.WithAPIOptions([]func(*smithymiddleware.Stack) error{
middleware.AddUserAgentKey(userAgent()),
}))
if err != nil {
return nil, err
}
cache.Client = secretsmanager.NewFromConfig(cfg)
}
return cache, nil
}
// getCachedSecret gets a cached secret for the given secret identifier.
// Returns cached secret item.
func (c *Cache) getCachedSecret(secretId string) *secretCacheItem {
lruValue, found := c.lru.get(secretId)
if !found {
cacheItem := newSecretCacheItem(c.CacheConfig, c.Client, secretId)
c.lru.putIfAbsent(secretId, &cacheItem)
lruValue, _ = c.lru.get(secretId)
}
secretCacheItem, _ := lruValue.(*secretCacheItem)
return secretCacheItem
}
// GetSecretString gets the secret string value from the cache for given secret id and a default version stage.
// Returns the secret string and an error if operation failed.
func (c *Cache) GetSecretString(secretId string) (string, error) {
return c.GetSecretStringWithContext(context.Background(), secretId)
}
func (c *Cache) GetSecretStringWithContext(ctx context.Context, secretId string) (string, error) {
return c.GetSecretStringWithStageWithContext(ctx, secretId, DefaultVersionStage)
}
// GetSecretStringWithStage gets the secret string value from the cache for given secret id and version stage.
// Returns the secret string and an error if operation failed.
func (c *Cache) GetSecretStringWithStage(secretId string, versionStage string) (string, error) {
return c.GetSecretStringWithStageWithContext(context.Background(), secretId, versionStage)
}
func (c *Cache) GetSecretStringWithStageWithContext(ctx context.Context, secretId string, versionStage string) (string, error) {
secretCacheItem := c.getCachedSecret(secretId)
getSecretValueOutput, err := secretCacheItem.getSecretValue(ctx, versionStage)
if err != nil {
return "", err
}
if getSecretValueOutput.SecretString == nil {
return "", &InvalidOperationError{
baseError{
Message: "requested secret version does not contain SecretString",
},
}
}
return *getSecretValueOutput.SecretString, nil
}
// GetSecretBinary gets the secret binary value from the cache for given secret id and a default version stage.
// Returns the secret binary and an error if operation failed.
func (c *Cache) GetSecretBinary(secretId string) ([]byte, error) {
return c.GetSecretBinaryWithContext(context.Background(), secretId)
}
func (c *Cache) GetSecretBinaryWithContext(ctx context.Context, secretId string) ([]byte, error) {
return c.GetSecretBinaryWithStageWithContext(ctx, secretId, DefaultVersionStage)
}
// GetSecretBinaryWithStage gets the secret binary value from the cache for given secret id and version stage.
// Returns the secret binary and an error if operation failed.
func (c *Cache) GetSecretBinaryWithStage(secretId string, versionStage string) ([]byte, error) {
return c.GetSecretBinaryWithStageWithContext(context.Background(), secretId, versionStage)
}
func (c *Cache) GetSecretBinaryWithStageWithContext(ctx context.Context, secretId string, versionStage string) ([]byte, error) {
secretCacheItem := c.getCachedSecret(secretId)
getSecretValueOutput, err := secretCacheItem.getSecretValue(ctx, versionStage)
if err != nil {
return nil, err
}
if getSecretValueOutput.SecretBinary == nil {
return nil, &InvalidOperationError{
baseError{
Message: "requested secret version does not contain SecretBinary",
},
}
}
return getSecretValueOutput.SecretBinary, nil
}
// Method to force the refresh of a secret inside the cache
func (c *Cache) RefreshNow(secretId string) {
c.RefreshNowWithContext(context.Background(), secretId)
}
func (c *Cache) RefreshNowWithContext(ctx context.Context, secretId string) {
secretCacheItem := c.getCachedSecret(secretId)
secretCacheItem.refreshNow(ctx)
}