├── README.md ├── ssm-cache-impl.go └── ssm-cache.go /README.md: -------------------------------------------------------------------------------- 1 | # ssm-cache 2 | 3 | An in memory cache that provides typed accessors to 4 | the [SSM Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-paramstore.html), inspired by [ssm-cache-python](https://github.com/alexcasalboni/ssm-cache-python). 5 | 6 | # Usage 7 | 8 | ## Create a Client 9 | 10 | Create a single ssmcache.Client instance for your 11 | lambda application. This client should only 12 | be initialized once as part of an `init()` function. 13 | 14 | The single argument is the default expiry for cached 15 | values. To create a cache whose elements do not expire by 16 | default, provide the [NoExpiration](https://godoc.org/github.com/patrickmn/go-cache#pkg-constants) value. 17 | 18 | ```go 19 | 20 | var cacheClient ssmcache.Client 21 | func init() { 22 | cacheClient = ssmcache.NewClient(5 * time.Minute) 23 | } 24 | ``` 25 | 26 | ## Fetch Value 27 | 28 | ### Default Expiry 29 | 30 | ```go 31 | stringVal, stringValErr := cacheClient.GetString("MyParam") 32 | stringSliceVal, stringSliceValErr := cacheClient.GetStringList("MyParam") 33 | decryptedStringVal, decryptedStringValErr := cacheClient.GetSecureString("MyParam") 34 | ``` 35 | 36 | ### Custom Expiry 37 | 38 | ```go 39 | stringVal, stringValErr := cacheClient.GetExpiringString("MyParam", 30*time.Second) 40 | stringSliceVal, stringSliceValErr := cacheClient.GetExpiringStringList("MyParam", 30*time.Second) 41 | decryptedStringVal, decryptedStringValErr := cacheClient.GetExpiringSecureString("MyParam", 30*time.Second) 42 | ``` 43 | 44 | ## Force Refresh 45 | 46 | Use `Purge(keyname)` to force delete a cached entry and reload the value from SSM. 47 | 48 | ```go 49 | stringVal, stringValErr := cacheClient.Purge("MyParam").GetString("MyParam") 50 | ``` 51 | 52 | ## Fetch Group 53 | 54 | ### Default Expiry 55 | 56 | ```go 57 | paramMap, paramMapErr := cacheClient.GetParameterGroup("StorageKey", "/my/custom/ssm-path") 58 | ``` 59 | 60 | ### Custom Expiry 61 | 62 | ```go 63 | paramMap, paramMapErr := cacheClient.GetExpiringParameterGroup("StorageKey", "/my/custom/ssm-path") 64 | ``` 65 | 66 | ## References 67 | 68 | * [Sharing Secrets with AWS Lambda Using AWS Systems Manager Parameter Store](https://aws.amazon.com/blogs/compute/sharing-secrets-with-aws-lambda-using-aws-systems-manager-parameter-store/) -------------------------------------------------------------------------------- /ssm-cache-impl.go: -------------------------------------------------------------------------------- 1 | package ssmcache 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/service/ssm" 9 | gocache "github.com/patrickmn/go-cache" 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | type ssmCacheImpl struct { 14 | ssmSvc *ssm.SSM 15 | cache *gocache.Cache 16 | } 17 | 18 | var _ Client = &ssmCacheImpl{} 19 | 20 | func (ssmCache *ssmCacheImpl) getParameter(paramName string, 21 | encrypted bool, 22 | paramType string, 23 | expiry time.Duration) (string, error) { 24 | 25 | paramInput := &ssm.GetParameterInput{ 26 | Name: aws.String(paramName), 27 | WithDecryption: aws.Bool(encrypted), 28 | } 29 | 30 | paramValueOutput, paramValueOutputErr := ssmCache.ssmSvc.GetParameter(paramInput) 31 | if paramValueOutputErr != nil { 32 | return "", paramValueOutputErr 33 | } 34 | if *paramValueOutput.Parameter.Type != paramType { 35 | return "", errors.Errorf("Parameter %s is type %s, not type %s", 36 | paramName, 37 | *paramValueOutput.Parameter.Type, 38 | paramType) 39 | } 40 | paramValue := "" 41 | if paramValueOutput.Parameter.Value != nil { 42 | paramValue = *paramValueOutput.Parameter.Value 43 | } 44 | ssmCache.cache.Set(paramName, paramValue, expiry) 45 | return paramValue, nil 46 | } 47 | 48 | func (ssmCache *ssmCacheImpl) Purge(paramName string) Client { 49 | ssmCache.cache.Delete(paramName) 50 | return ssmCache 51 | } 52 | 53 | func (ssmCache *ssmCacheImpl) GetString(paramName string) (string, error) { 54 | return ssmCache.GetExpiringString(paramName, gocache.DefaultExpiration) 55 | } 56 | 57 | func (ssmCache *ssmCacheImpl) GetExpiringString(paramName string, expiry time.Duration) (string, error) { 58 | value, valueExists := ssmCache.cache.Get(paramName) 59 | if valueExists { 60 | stringValue, stringValueOk := value.(string) 61 | if !stringValueOk { 62 | return "", errors.Errorf("Failed to type assert cached param: %s", paramName) 63 | } 64 | return stringValue, nil 65 | } 66 | newValue, newValueErr := ssmCache.getParameter(paramName, 67 | false, 68 | ssm.ParameterTypeString, 69 | expiry) 70 | if newValueErr != nil { 71 | return "", errors.Wrapf(newValueErr, "Attempting to get parameter: %s", paramName) 72 | } 73 | return newValue, nil 74 | } 75 | func (ssmCache *ssmCacheImpl) GetStringList(paramName string) ([]string, error) { 76 | return nil, nil 77 | } 78 | 79 | func (ssmCache *ssmCacheImpl) GetExpiringStringList(paramName string, expiry time.Duration) ([]string, error) { 80 | value, valueExists := ssmCache.cache.Get(paramName) 81 | if valueExists { 82 | stringValue, stringValueOk := value.(string) 83 | if !stringValueOk { 84 | return nil, errors.Errorf("Failed to type assert cached param: %s", paramName) 85 | } 86 | return strings.Split(stringValue, ","), nil 87 | } 88 | newValue, newValueErr := ssmCache.getParameter(paramName, 89 | false, 90 | ssm.ParameterTypeStringList, 91 | expiry) 92 | if newValueErr != nil { 93 | return nil, errors.Wrapf(newValueErr, "Attempting to get parameter: %s", paramName) 94 | } 95 | return strings.Split(newValue, ","), nil 96 | } 97 | 98 | func (ssmCache *ssmCacheImpl) GetSecureString(paramName string) (string, error) { 99 | return ssmCache.GetExpiringSecureString(paramName, gocache.DefaultExpiration) 100 | } 101 | 102 | func (ssmCache *ssmCacheImpl) GetExpiringSecureString(paramName string, expiry time.Duration) (string, error) { 103 | value, valueExists := ssmCache.cache.Get(paramName) 104 | if valueExists { 105 | stringValue, stringValueOk := value.(string) 106 | if !stringValueOk { 107 | return "", errors.Errorf("Failed to type assert cached param: %s", paramName) 108 | } 109 | return stringValue, nil 110 | } 111 | newValue, newValueErr := ssmCache.getParameter(paramName, 112 | true, 113 | ssm.ParameterTypeSecureString, 114 | expiry) 115 | if newValueErr != nil { 116 | return "", errors.Wrapf(newValueErr, "Attempting to get parameter: %s", paramName) 117 | } 118 | return newValue, nil 119 | } 120 | 121 | func (ssmCache *ssmCacheImpl) GetParameterGroup(groupKey string, ssmKeyPath string) (ParameterGroup, error) { 122 | return ssmCache.GetExpiringParameterGroup(groupKey, ssmKeyPath, gocache.DefaultExpiration) 123 | } 124 | 125 | func (ssmCache *ssmCacheImpl) GetExpiringParameterGroup(groupKey string, 126 | ssmKeyPath string, 127 | expiry time.Duration) (ParameterGroup, error) { 128 | 129 | value, valueExists := ssmCache.cache.Get(groupKey) 130 | if valueExists { 131 | paramGroup, paramGroupOK := value.(ParameterGroup) 132 | if !paramGroupOK { 133 | return nil, errors.Errorf("Failed to type assert cached ParameterGroup type: %s", groupKey) 134 | } 135 | return paramGroup, nil 136 | } 137 | 138 | // Get all the parameters, stuff them into a map 139 | paramByPathInput := &ssm.GetParametersByPathInput{ 140 | Path: aws.String(ssmKeyPath), 141 | Recursive: aws.Bool(true), 142 | } 143 | // Walk the pages... 144 | paramMap := make(map[string]interface{}, 0) 145 | pagingErr := ssmCache.ssmSvc.GetParametersByPathPages(paramByPathInput, 146 | func(page *ssm.GetParametersByPathOutput, lastPage bool) bool { 147 | for _, eachParam := range page.Parameters { 148 | value := "" 149 | if eachParam.Value != nil { 150 | value = *eachParam.Value 151 | } 152 | paramMap[*eachParam.Name] = value 153 | } 154 | return true 155 | }) 156 | 157 | if pagingErr != nil { 158 | return nil, errors.Wrapf(pagingErr, "Failed to page through all parameters in SSM tree: %s", ssmKeyPath) 159 | } 160 | // Cache it... 161 | ssmCache.cache.Set(groupKey, paramMap, expiry) 162 | return paramMap, nil 163 | } 164 | -------------------------------------------------------------------------------- /ssm-cache.go: -------------------------------------------------------------------------------- 1 | package ssmcache 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aws/aws-sdk-go/aws/session" 7 | "github.com/aws/aws-sdk-go/service/ssm" 8 | gocache "github.com/patrickmn/go-cache" 9 | ) 10 | 11 | // ParameterGroup represents a group of parameters that share a common 12 | // root in SSM and a common expiry in the client cache 13 | type ParameterGroup map[string]interface{} 14 | 15 | // Client represents an auto-expiring cache that's populated by 16 | // SSM data 17 | type Client interface { 18 | // GetString returns an SSM param value, caching it with the 19 | // default expiration time. 20 | GetString(paramName string) (string, error) 21 | // GetExpiringString returns an SSM param value, caching it using 22 | // a custom expiration time. 23 | GetExpiringString(paramName string, expiry time.Duration) (string, error) 24 | // GetStringList returns an SSM param value, caching it with the 25 | // default expiration time. 26 | GetStringList(paramName string) ([]string, error) 27 | // GetExpiringStringList returns an SSM param value, caching it using 28 | // a custom expiration time. 29 | GetExpiringStringList(paramName string, expiry time.Duration) ([]string, error) 30 | // GetSecureString returns an encrypted SSM param value, caching it with the 31 | // default expiration time. 32 | GetSecureString(paramName string) (string, error) 33 | // GetExpiringSecureString returns an encrypted SSM param value, caching it with 34 | // a custom expiration time. 35 | GetExpiringSecureString(paramName string, expiry time.Duration) (string, error) 36 | 37 | // Purge deletes entries from the cache 38 | Purge(paramName string) Client 39 | 40 | // GetParameterGroup returns a map of SSM parameter values that are keyed 41 | // by their SSM Key name. The fetch is recursive and the map is cached 42 | // using the groupKey and the default expiration time. 43 | GetParameterGroup(groupKey string, ssmKeyPath string) (ParameterGroup, error) 44 | 45 | // GetParameterGroup returns a map of SSM parameter values that are keyed 46 | // by their SSM Key name. The fetch is recursive and the map is cached 47 | // using the groupKey and a custom expiration time. 48 | GetExpiringParameterGroup(groupKey string, ssmKeyPath string, expiry time.Duration) (ParameterGroup, error) 49 | } 50 | 51 | // NewClient returns an SSM Cache that wraps up accessing parameters in SSM 52 | func NewClient(defaultExpiry time.Duration) Client { 53 | return NewClientWithSession(session.New(), defaultExpiry) 54 | } 55 | 56 | // NewClientWithSession creates a new cache with the given AWS session 57 | func NewClientWithSession(session *session.Session, defaultExpiry time.Duration) Client { 58 | return &ssmCacheImpl{ 59 | ssmSvc: ssm.New(session), 60 | cache: gocache.New(defaultExpiry, gocache.DefaultExpiration), 61 | } 62 | } 63 | --------------------------------------------------------------------------------