//go:build linux package host import ( "errors" "fmt" "io" "log" "os" "os/user" "strconv" "strings" "github.com/carlosframework/platform/internal/rootaws" "github.com/carlosframework/platform/internal/safefile" "golang.org/x/sys/unix" ) // openOffcloudCredentials pins the parent and refuses links and special files // before either ownership or mode is changed. O_PATH inspects special files // without opening a device or blocking on a planted FIFO. func openOffcloudCredentials(path string) (_ *os.File, err error) { defer func() { if err != nil && !errors.Is(err, os.ErrNotExist) { log.Printf("ALERT: refusing unsafe offcloud credentials %s: %v", path, err) } }() parent, name, err := openRegistryParent(path) if err != nil { return nil, err } defer parent.Close() fd, err := unix.Openat2(int(parent.Fd()), name, &unix.OpenHow{ Flags: unix.O_PATH | unix.O_NOFOLLOW | unix.O_CLOEXEC, Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV, }) if err != nil { return nil, err } f := os.NewFile(uintptr(fd), path) defer f.Close() var st unix.Stat_t if err := unix.Fstat(fd, &st); err != nil { return nil, err } if st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1 { return nil, fmt.Errorf("not a plain single-link regular file") } // Reopen the pinned regular inode, never the caller's mutable pathname. opened, err := unix.Open(fmt.Sprintf("/proc/self/fd/%d", fd), unix.O_RDONLY|unix.O_CLOEXEC, 0) if err != nil { return nil, err } return os.NewFile(uintptr(opened), path), nil } func checkOffcloudCredentials(path string) (os.FileInfo, error) { f, err := openOffcloudCredentials(path) if err != nil { return nil, err } defer f.Close() return f.Stat() } // Missing means a cloud box. Repair replaces the inode so a service process // holding a writable descriptor cannot change the credentials root will read. func repairOffcloudCredentials(path string) (err error) { defer func() { if err != nil { log.Printf("ALERT: refusing unsafe offcloud credentials %s: %v", path, err) } }() f, err := openOffcloudCredentials(path) if errors.Is(err, os.ErrNotExist) { return nil } if err != nil { return err } defer f.Close() g, err := user.LookupGroup(serviceUser) if err != nil { return err } gid, err := strconv.Atoi(g.Gid) if err != nil { return err } return secureOffcloudCredentials(f, gid) } func secureOffcloudCredentials(f *os.File, gid int) (err error) { defer func() { if err != nil { log.Printf("ALERT: refusing unsafe offcloud credentials %s: %v", f.Name(), err) } }() var st unix.Stat_t if err := unix.Fstat(int(f.Fd()), &st); err != nil { return err } body, err := io.ReadAll(io.LimitReader(f, rootaws.MaxCredentialsSize+1)) if err != nil { return err } if len(body) > rootaws.MaxCredentialsSize { return io.ErrShortBuffer } credentials, err := rootaws.ParseStaticCredentials(body) if err != nil { return err } if st.Uid == 0 && st.Gid == uint32(gid) && st.Mode&0o7777 == 0o640 { return nil } var validated strings.Builder fmt.Fprintf(&validated, "[default]\naws_access_key_id=%s\naws_secret_access_key=%s\n", credentials.AccessKeyID, credentials.SecretAccessKey) if credentials.SessionToken != "" { fmt.Fprintf(&validated, "aws_session_token=%s\n", credentials.SessionToken) } return safefile.Write(f.Name(), 0o640, func(w io.Writer) error { fresh := w.(*os.File) if err := fresh.Chown(0, gid); err != nil { return err } _, err := io.WriteString(w, validated.String()) return err }) }