carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostfence
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "io"
8 "os"
9 "regexp"
10
11 "github.com/aws/aws-sdk-go-v2/aws"
12 "github.com/aws/aws-sdk-go-v2/credentials"
13 "github.com/aws/aws-sdk-go-v2/credentials/stscreds"
14 "github.com/aws/aws-sdk-go-v2/service/s3"
15 "github.com/aws/aws-sdk-go-v2/service/sts"
16 "github.com/carlosframework/platform/internal/hostfloor"
17 "github.com/carlosframework/platform/internal/release"
18)
19
20type fenceCredentials struct {
21 Role string `json:"role"`
22 Region string `json:"region"`
23 Bucket string `json:"bucket"`
24 AccessKey string `json:"access_key"`
25 SecretKey string `json:"secret_key"`
26}
27
28// OpenRootFenceStore uses only the dedicated root bootstrap identity. AWS
29// environment credentials, IMDS and shared profiles are never consulted.
30func OpenRootFenceStore(ctx context.Context, bucket string) (release.Store, error) {
31 if os.Geteuid() != 0 {
32 return nil, os.ErrPermission
33 }
34 if err := ctx.Err(); err != nil {
35 return nil, err
36 }
37 f, err := hostfloor.OpenProtected("/etc/carlos/fence-writer/credentials.json")
38 if err != nil {
39 return nil, err
40 }
41 defer f.Close()
42 st, err := f.Stat()
43 if err != nil {
44 return nil, err
45 }
46 if st.Mode().Perm() != 0400 && st.Mode().Perm() != 0600 {
47 return nil, os.ErrPermission
48 }
49 var reader io.Reader = f
50 if st.Mode().Perm() == 0600 {
51 reader, err = activationCredentialReader(f)
52 if err != nil {
53 return nil, err
54 }
55 }
56 c, err := decodeFenceCredentials(reader, bucket)
57 if err != nil {
58 return nil, err
59 }
60 cfg := aws.Config{Region: c.Region, Credentials: aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(c.AccessKey, c.SecretKey, ""))}
61 provider := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), c.Role, func(o *stscreds.AssumeRoleOptions) { o.RoleSessionName = "carlos-root-fence" })
62 cfg.Credentials = aws.NewCredentialsCache(provider)
63 return &release.S3Store{Client: s3.NewFromConfig(cfg), Bucket: c.Bucket}, nil
64}
65func decodeFenceCredentials(r io.Reader, bucket string) (fenceCredentials, error) {
66 var c fenceCredentials
67 d := json.NewDecoder(io.LimitReader(r, 16385))
68 d.DisallowUnknownFields()
69 if err := d.Decode(&c); err != nil {
70 return c, err
71 }
72 var extra any
73 if err := d.Decode(&extra); err != io.EOF {
74 return c, errors.New("host fences: invalid credential file")
75 }
76 if c.Bucket != bucket || bucket == "" || c.Region == "" || c.AccessKey == "" || c.SecretKey == "" || !regexp.MustCompile(`^arn:aws:iam::[0-9]{12}:role/[A-Za-z0-9+=,.@_/-]+$`).MatchString(c.Role) {
77 return c, errors.New("host fences: incomplete dedicated credential scope")
78 }
79 return c, nil
80}
81