carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package hostenv
2
3import (
4 "context"
5 "errors"
6 "strings"
7
8 "github.com/aws/aws-sdk-go-v2/aws"
9 "github.com/aws/aws-sdk-go-v2/config"
10 "github.com/aws/aws-sdk-go-v2/service/ssm"
11 ssmtypes "github.com/aws/aws-sdk-go-v2/service/ssm/types"
12)
13
14// The AWS adapter for Params (region HA spec 2 §25): SSM Parameter Store
15// SecureStrings, encrypted with the deployment's configuration key (§4).
16// A box reads its two paths with the role's ssm:GetParametersByPath and
17// kms:Decrypt; an operator seeding them needs ssm:PutParameter and
18// kms:Encrypt on that key.
19
20// ssmParams holds method values bound to the two SSM calls it makes, not
21// the *ssm.Client. Handing the client to the paginator (whose argument is
22// an interface) would make the linker keep every exported SSM method once
23// html/template is linked in: about 5 MB of linux/arm64 binary (discussion
24// #275, .amadan/ci.d/20-cross@5). A method value keeps the one call alone.
25type ssmParams struct {
26 getByPath getParametersByPathFunc
27 put func(context.Context, *ssm.PutParameterInput, ...func(*ssm.Options)) (*ssm.PutParameterOutput, error)
28 kmsKey string
29}
30
31// getParametersByPathFunc is ssm.GetParametersByPathAPIClient over one
32// bound method value, for the paginator.
33type getParametersByPathFunc func(context.Context, *ssm.GetParametersByPathInput, ...func(*ssm.Options)) (*ssm.GetParametersByPathOutput, error)
34
35func (f getParametersByPathFunc) GetParametersByPath(ctx context.Context, in *ssm.GetParametersByPathInput, opts ...func(*ssm.Options)) (*ssm.GetParametersByPathOutput, error) {
36 return f(ctx, in, opts...)
37}
38
39// openSSMParams opens SSM in awsRegion ("" is the SDK's default region).
40// kmsKey is the key Set encrypts with.
41func openSSMParams(ctx context.Context, awsRegion, kmsKey string) (Params, error) {
42 var opts []func(*config.LoadOptions) error
43 if awsRegion != "" {
44 opts = append(opts, config.WithRegion(awsRegion))
45 }
46 cfg, err := config.LoadDefaultConfig(ctx, opts...)
47 if err != nil {
48 return nil, err
49 }
50 client := ssm.NewFromConfig(cfg)
51 return &ssmParams{
52 getByPath: getParametersByPathFunc(client.GetParametersByPath),
53 put: client.PutParameter,
54 kmsKey: kmsKey,
55 }, nil
56}
57
58// ByPath reads every parameter one level under prefix, decrypted, page by
59// page. Any failed page fails the whole read.
60func (p *ssmParams) ByPath(ctx context.Context, prefix string) (map[string]string, error) {
61 out := map[string]string{}
62 pages := ssm.NewGetParametersByPathPaginator(p.getByPath, &ssm.GetParametersByPathInput{
63 Path: aws.String(strings.TrimSuffix(prefix, "/")),
64 Recursive: aws.Bool(false),
65 WithDecryption: aws.Bool(true),
66 })
67 for pages.HasMorePages() {
68 page, err := pages.NextPage(ctx)
69 if err != nil {
70 return nil, err
71 }
72 for _, param := range page.Parameters {
73 name, ok := strings.CutPrefix(aws.ToString(param.Name), prefix)
74 if !ok || name == "" || strings.Contains(name, "/") {
75 continue
76 }
77 out[name] = aws.ToString(param.Value)
78 }
79 }
80 return out, nil
81}
82
83// Set writes one SecureString with the deployment's key, replacing it:
84// a region's own values.
85func (p *ssmParams) Set(ctx context.Context, name, value string) error {
86 in := &ssm.PutParameterInput{
87 Name: aws.String(name),
88 Value: aws.String(value),
89 Type: ssmtypes.ParameterTypeSecureString,
90 Overwrite: aws.Bool(true),
91 }
92 if p.kmsKey != "" {
93 in.KeyId = aws.String(p.kmsKey)
94 }
95 _, err := p.put(ctx, in)
96 return err
97}
98
99// SetNew writes one SecureString with the deployment's key only if it does
100// not exist (Overwrite false). SSM's ParameterAlreadyExists is ErrExists.
101func (p *ssmParams) SetNew(ctx context.Context, name, value string) error {
102 in := &ssm.PutParameterInput{
103 Name: aws.String(name),
104 Value: aws.String(value),
105 Type: ssmtypes.ParameterTypeSecureString,
106 Overwrite: aws.Bool(false),
107 }
108 if p.kmsKey != "" {
109 in.KeyId = aws.String(p.kmsKey)
110 }
111 _, err := p.put(ctx, in)
112 var exists *ssmtypes.ParameterAlreadyExists
113 if errors.As(err, &exists) {
114 return ErrExists
115 }
116 return err
117}
118