-
Notifications
You must be signed in to change notification settings - Fork 2
/
secrets.go
129 lines (103 loc) · 3.09 KB
/
secrets.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package secrets
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
)
const (
GovUkNotify = "gov-uk-notify-api-key"
GovUkPay = "gov-uk-pay-api-key"
GovUkOneLoginPrivateKey = "private-jwt-key-base64"
GovUkOneLoginIdentityPublicKey = "gov-uk-onelogin-identity-public-key"
OrdnanceSurvey = "os-postcode-lookup-api-key"
LpaStoreJwtSecretKey = "lpa-store-jwt-secret-key"
cookieSessionKeys = "cookie-session-keys"
delay = time.Second
)
type secretsManager interface {
GetSecretValue(ctx context.Context, params *secretsmanager.GetSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.GetSecretValueOutput, error)
}
type cacheItem struct {
untilNano int64
errorCount int64
value string
}
func (i *cacheItem) isFresh() bool {
return time.Now().UnixNano() < i.untilNano
}
type Client struct {
svc secretsManager
ttl int64
mu sync.Mutex
cache map[string]*cacheItem
}
func NewClient(cfg aws.Config, ttl time.Duration) (*Client, error) {
svc := secretsmanager.NewFromConfig(cfg)
return &Client{svc: svc, ttl: ttl.Nanoseconds(), cache: map[string]*cacheItem{}}, nil
}
// Secret retrieves the named secret from the cache. If not in the cache or the
// item is stale then the secret is retrieved from Secrets Manager. On failure
// the stale secret will be returned, and if that isn't possible an error.
func (c *Client) Secret(ctx context.Context, name string) (string, error) {
c.mu.Lock()
defer c.mu.Unlock()
item, found := c.cache[name]
if found && item.isFresh() {
item.errorCount = 0
return item.value, nil
}
result, err := c.svc.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{
SecretId: aws.String(name),
})
if err != nil {
if found {
item.errorCount++
item.untilNano += item.errorCount * delay.Nanoseconds()
return item.value, nil
}
return "", fmt.Errorf("error retrieving secret '%s': %w", name, err)
}
if found {
item.errorCount = 0
item.untilNano = time.Now().UnixNano() + c.ttl
item.value = *result.SecretString
} else {
c.cache[name] = &cacheItem{untilNano: time.Now().UnixNano() + c.ttl, value: *result.SecretString}
}
return *result.SecretString, nil
}
func (c *Client) SecretBytes(ctx context.Context, name string) ([]byte, error) {
secret, err := c.Secret(ctx, name)
if err != nil {
return nil, err
}
keyBytes, err := base64.StdEncoding.DecodeString(secret)
if err != nil {
return nil, fmt.Errorf("error decoding base64 secret '%s': %w", name, err)
}
return keyBytes, nil
}
func (c *Client) CookieSessionKeys(ctx context.Context) ([][]byte, error) {
secret, err := c.Secret(ctx, cookieSessionKeys)
if err != nil {
return nil, err
}
var v []string
if err := json.Unmarshal([]byte(secret), &v); err != nil {
return nil, err
}
keys := make([][]byte, len(v))
for i, data := range v {
enc, err := base64.StdEncoding.DecodeString(data)
if err != nil {
return nil, err
}
keys[i] = []byte(enc)
}
return keys, nil
}