carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package host
4
5import (
6 "errors"
7 "fmt"
8 "io"
9 "log"
10 "os"
11 "os/user"
12 "strconv"
13 "strings"
14
15 "github.com/carlosframework/platform/internal/rootaws"
16 "github.com/carlosframework/platform/internal/safefile"
17 "golang.org/x/sys/unix"
18)
19
20// openOffcloudCredentials pins the parent and refuses links and special files
21// before either ownership or mode is changed. O_PATH inspects special files
22// without opening a device or blocking on a planted FIFO.
23func openOffcloudCredentials(path string) (_ *os.File, err error) {
24 defer func() {
25 if err != nil && !errors.Is(err, os.ErrNotExist) {
26 log.Printf("ALERT: refusing unsafe offcloud credentials %s: %v", path, err)
27 }
28 }()
29 parent, name, err := openRegistryParent(path)
30 if err != nil {
31 return nil, err
32 }
33 defer parent.Close()
34 fd, err := unix.Openat2(int(parent.Fd()), name, &unix.OpenHow{
35 Flags: unix.O_PATH | unix.O_NOFOLLOW | unix.O_CLOEXEC,
36 Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV,
37 })
38 if err != nil {
39 return nil, err
40 }
41 f := os.NewFile(uintptr(fd), path)
42 defer f.Close()
43 var st unix.Stat_t
44 if err := unix.Fstat(fd, &st); err != nil {
45 return nil, err
46 }
47 if st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1 {
48 return nil, fmt.Errorf("not a plain single-link regular file")
49 }
50 // Reopen the pinned regular inode, never the caller's mutable pathname.
51 opened, err := unix.Open(fmt.Sprintf("/proc/self/fd/%d", fd), unix.O_RDONLY|unix.O_CLOEXEC, 0)
52 if err != nil {
53 return nil, err
54 }
55 return os.NewFile(uintptr(opened), path), nil
56}
57
58func checkOffcloudCredentials(path string) (os.FileInfo, error) {
59 f, err := openOffcloudCredentials(path)
60 if err != nil {
61 return nil, err
62 }
63 defer f.Close()
64 return f.Stat()
65}
66
67// Missing means a cloud box. Repair replaces the inode so a service process
68// holding a writable descriptor cannot change the credentials root will read.
69func repairOffcloudCredentials(path string) (err error) {
70 defer func() {
71 if err != nil {
72 log.Printf("ALERT: refusing unsafe offcloud credentials %s: %v", path, err)
73 }
74 }()
75 f, err := openOffcloudCredentials(path)
76 if errors.Is(err, os.ErrNotExist) {
77 return nil
78 }
79 if err != nil {
80 return err
81 }
82 defer f.Close()
83 g, err := user.LookupGroup(serviceUser)
84 if err != nil {
85 return err
86 }
87 gid, err := strconv.Atoi(g.Gid)
88 if err != nil {
89 return err
90 }
91 return secureOffcloudCredentials(f, gid)
92}
93
94func secureOffcloudCredentials(f *os.File, gid int) (err error) {
95 defer func() {
96 if err != nil {
97 log.Printf("ALERT: refusing unsafe offcloud credentials %s: %v", f.Name(), err)
98 }
99 }()
100 var st unix.Stat_t
101 if err := unix.Fstat(int(f.Fd()), &st); err != nil {
102 return err
103 }
104 body, err := io.ReadAll(io.LimitReader(f, rootaws.MaxCredentialsSize+1))
105 if err != nil {
106 return err
107 }
108 if len(body) > rootaws.MaxCredentialsSize {
109 return io.ErrShortBuffer
110 }
111 credentials, err := rootaws.ParseStaticCredentials(body)
112 if err != nil {
113 return err
114 }
115 if st.Uid == 0 && st.Gid == uint32(gid) && st.Mode&0o7777 == 0o640 {
116 return nil
117 }
118 var validated strings.Builder
119 fmt.Fprintf(&validated, "[default]\naws_access_key_id=%s\naws_secret_access_key=%s\n", credentials.AccessKeyID, credentials.SecretAccessKey)
120 if credentials.SessionToken != "" {
121 fmt.Fprintf(&validated, "aws_session_token=%s\n", credentials.SessionToken)
122 }
123 return safefile.Write(f.Name(), 0o640, func(w io.Writer) error {
124 fresh := w.(*os.File)
125 if err := fresh.Chown(0, gid); err != nil {
126 return err
127 }
128 _, err := io.WriteString(w, validated.String())
129 return err
130 })
131}
132