diff options
Diffstat (limited to 'secrets/client.go')
-rw-r--r-- | secrets/client.go | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/secrets/client.go b/secrets/client.go new file mode 100644 index 0000000..7a1f51b --- /dev/null +++ b/secrets/client.go | |||
@@ -0,0 +1,112 @@ | |||
1 | package secrets | ||
2 | |||
3 | import ( | ||
4 | "context" | ||
5 | "crypto/rsa" | ||
6 | "crypto/x509" | ||
7 | "encoding/base64" | ||
8 | "fmt" | ||
9 | "sync" | ||
10 | "time" | ||
11 | |||
12 | "code.crute.us/mcrute/golib/log" | ||
13 | "code.crute.us/mcrute/golib/service" | ||
14 | ) | ||
15 | |||
16 | type Handle interface { | ||
17 | Reference() string | ||
18 | } | ||
19 | |||
20 | type Renewal struct { | ||
21 | Name string | ||
22 | Critical bool | ||
23 | Time time.Time | ||
24 | Error error | ||
25 | } | ||
26 | |||
27 | type Credential struct { | ||
28 | Username string `json:"username"` | ||
29 | Password string `json:"password"` | ||
30 | } | ||
31 | |||
32 | type ApiKey struct { | ||
33 | Key string `json:"key"` | ||
34 | } | ||
35 | |||
36 | type RSAKey struct { | ||
37 | Key string `json:"key"` | ||
38 | } | ||
39 | |||
40 | func (k *RSAKey) RSAPrivateKey() (*rsa.PrivateKey, error) { | ||
41 | der, err := base64.StdEncoding.DecodeString(k.Key) | ||
42 | if err != nil { | ||
43 | return nil, err | ||
44 | } | ||
45 | |||
46 | pr, err := x509.ParsePKCS8PrivateKey(der) | ||
47 | if err != nil { | ||
48 | return nil, err | ||
49 | } | ||
50 | |||
51 | pk, ok := pr.(*rsa.PrivateKey) | ||
52 | if !ok { | ||
53 | return nil, fmt.Errorf("RSAKey: parsed key is not an rsa.PrivateKey") | ||
54 | } | ||
55 | |||
56 | return pk, nil | ||
57 | } | ||
58 | |||
59 | // Client is the interface that users of secrets returned by a secret | ||
60 | // back-end should expect. This interface contains only secret related | ||
61 | // functionality and none of the functions for running the back-end | ||
62 | // itself. This is separate from the manager functions to make it easier | ||
63 | // to inject stubs to code that doesn't care about the fact that a | ||
64 | // manager may exist. | ||
65 | type Client interface { | ||
66 | DatabaseCredential(context.Context, string) (*Credential, Handle, error) | ||
67 | Secret(context.Context, string, any) (Handle, error) | ||
68 | WriteSecret(context.Context, string, any) error | ||
69 | Destroy(Handle) error | ||
70 | MakeNonCritical(Handle) error | ||
71 | } | ||
72 | |||
73 | // ClientManager is like a Client, and contains a Client, but also | ||
74 | // contains other runtime functionality for running the secret back-end | ||
75 | // infrastructure that most consumers of secretes don't care about but | ||
76 | // the main process runner does. | ||
77 | type ClientManager interface { | ||
78 | Client | ||
79 | Authenticate(context.Context) error | ||
80 | Notifications() <-chan Renewal | ||
81 | Run(context.Context, *sync.WaitGroup) error | ||
82 | } | ||
83 | |||
84 | // MakeRenewalLogger subscribes to a ClientManager notification channel | ||
85 | // and logs those to the logger. If a critical credential fails the | ||
86 | // terminator callback will be called which should shut down the | ||
87 | // application in an orderly fashion. | ||
88 | func MakeRenewalLogger(cm ClientManager, log log.LeveledLogger, terminator func()) service.RunnerFunc { | ||
89 | return func(ctx context.Context, wg *sync.WaitGroup) error { | ||
90 | wg.Add(1) | ||
91 | defer wg.Done() | ||
92 | |||
93 | for { | ||
94 | select { | ||
95 | case r := <-cm.Notifications(): | ||
96 | if r.Error != nil { | ||
97 | if r.Critical { | ||
98 | log.Errorf("Failed to renew critical secret %s due to %s", r.Name, r.Error) | ||
99 | terminator() | ||
100 | } else { | ||
101 | log.Errorf("Failed to renew non-critical secret %s", r.Name) | ||
102 | } | ||
103 | } else { | ||
104 | log.Infof("Renewing credential %s", r.Name) | ||
105 | } | ||
106 | case <-ctx.Done(): | ||
107 | log.Infof("Shutting down secret renewal logger") | ||
108 | return nil | ||
109 | } | ||
110 | } | ||
111 | } | ||
112 | } | ||