198 lines
5.6 KiB
Go
198 lines
5.6 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"ollie/elevate"
|
|
fs "olliesrv/fs"
|
|
)
|
|
|
|
// elevateTree implements fs.FileTree for the /elevate/ namespace.
|
|
type elevateTree struct {
|
|
broker *elevate.Broker
|
|
}
|
|
|
|
func newElevateTree(broker *elevate.Broker) *elevateTree {
|
|
return &elevateTree{broker: broker}
|
|
}
|
|
|
|
func (t *elevateTree) Stat(name string) (os.FileInfo, error) {
|
|
switch {
|
|
case name == "policy":
|
|
return &fs.SyntheticFileInfo{Name_: "policy", Mode_: 0666}, nil
|
|
case name == "pending":
|
|
return &fs.SyntheticFileInfo{Name_: "pending", Mode_: 0755, IsDir_: true}, nil
|
|
case strings.HasPrefix(name, "pending/"):
|
|
id := strings.TrimPrefix(name, "pending/")
|
|
if t.broker.PendingByID(id) != nil {
|
|
return &fs.SyntheticFileInfo{Name_: id, Mode_: 0666}, nil
|
|
}
|
|
return nil, fmt.Errorf("%s: not found", name)
|
|
}
|
|
return nil, fmt.Errorf("%s: not found", name)
|
|
}
|
|
|
|
func (t *elevateTree) List() ([]os.DirEntry, error) {
|
|
return []os.DirEntry{
|
|
fs.FileEntry("policy", 0666),
|
|
fs.DirEntry("pending", 0755),
|
|
}, nil
|
|
}
|
|
|
|
func (t *elevateTree) Open(name string) (fs.File, error) {
|
|
switch {
|
|
case name == "policy":
|
|
return &elevatePolicyFile{broker: t.broker}, nil
|
|
case name == "pending":
|
|
return &elevatePendingDir{broker: t.broker}, nil
|
|
case strings.HasPrefix(name, "pending/"):
|
|
id := strings.TrimPrefix(name, "pending/")
|
|
req := t.broker.PendingByID(id)
|
|
if req == nil {
|
|
return nil, fmt.Errorf("%s: not found", name)
|
|
}
|
|
return &elevatePendingFile{broker: t.broker, req: req}, nil
|
|
}
|
|
return nil, fmt.Errorf("%s: not found", name)
|
|
}
|
|
|
|
func (t *elevateTree) Create(name string) error { return fmt.Errorf("cannot create in elevate") }
|
|
func (t *elevateTree) Delete(name string) error { return fmt.Errorf("cannot delete in elevate") }
|
|
func (t *elevateTree) Rename(_, _ string) error { return fmt.Errorf("cannot rename in elevate") }
|
|
|
|
// --- policy file ---
|
|
|
|
type elevatePolicyFile struct {
|
|
broker *elevate.Broker
|
|
}
|
|
|
|
func (f *elevatePolicyFile) Stat() (os.FileInfo, error) {
|
|
return &fs.SyntheticFileInfo{Name_: "policy", Mode_: 0666}, nil
|
|
}
|
|
|
|
func (f *elevatePolicyFile) Read() ([]byte, error) {
|
|
p := f.broker.GlobalPolicy().Global()
|
|
return p.Marshal()
|
|
}
|
|
|
|
func (f *elevatePolicyFile) Write(data []byte) error {
|
|
var p elevate.Policy
|
|
if err := elevate.ParsePolicy(data, &p); err != nil {
|
|
return err
|
|
}
|
|
return f.broker.GlobalPolicy().SetGlobal(p)
|
|
}
|
|
|
|
func (f *elevatePolicyFile) BlockingRead(_ context.Context, _ string) ([]byte, string, error) {
|
|
return nil, "", fmt.Errorf("not blocking")
|
|
}
|
|
func (f *elevatePolicyFile) OneShot() bool { return false }
|
|
func (f *elevatePolicyFile) IsBlocking() bool { return false }
|
|
func (f *elevatePolicyFile) Async() bool { return false }
|
|
|
|
// --- pending dir ---
|
|
|
|
type elevatePendingDir struct {
|
|
broker *elevate.Broker
|
|
}
|
|
|
|
func (f *elevatePendingDir) Stat() (os.FileInfo, error) {
|
|
return &fs.SyntheticFileInfo{Name_: "pending", Mode_: 0755, IsDir_: true}, nil
|
|
}
|
|
|
|
func (f *elevatePendingDir) Read() ([]byte, error) {
|
|
// List pending request IDs
|
|
var sb strings.Builder
|
|
for _, r := range f.broker.Pending() {
|
|
sb.WriteString(r.ID)
|
|
sb.WriteByte('\t')
|
|
sb.WriteString(r.Cmd)
|
|
sb.WriteByte('\n')
|
|
}
|
|
return []byte(sb.String()), nil
|
|
}
|
|
|
|
func (f *elevatePendingDir) Write([]byte) error { return fmt.Errorf("read-only") }
|
|
func (f *elevatePendingDir) BlockingRead(_ context.Context, _ string) ([]byte, string, error) {
|
|
return nil, "", fmt.Errorf("not blocking")
|
|
}
|
|
func (f *elevatePendingDir) OneShot() bool { return false }
|
|
func (f *elevatePendingDir) IsBlocking() bool { return false }
|
|
func (f *elevatePendingDir) Async() bool { return false }
|
|
|
|
// --- pending/<id> file ---
|
|
|
|
type elevatePendingFile struct {
|
|
broker *elevate.Broker
|
|
req *elevate.Request
|
|
}
|
|
|
|
func (f *elevatePendingFile) Stat() (os.FileInfo, error) {
|
|
return &fs.SyntheticFileInfo{Name_: f.req.ID, Mode_: 0666}, nil
|
|
}
|
|
|
|
func (f *elevatePendingFile) Read() ([]byte, error) {
|
|
return []byte(f.req.Summary() + "\n"), nil
|
|
}
|
|
|
|
func (f *elevatePendingFile) Write(data []byte) error {
|
|
cmd := strings.TrimSpace(string(data))
|
|
var res elevate.Resolution
|
|
switch cmd {
|
|
case "approve":
|
|
res = elevate.ResolveApprove
|
|
case "deny":
|
|
res = elevate.ResolveDeny
|
|
case "persist":
|
|
res = elevate.ResolvePersist
|
|
default:
|
|
return fmt.Errorf("unknown resolution: %s (use approve/deny/persist)", cmd)
|
|
}
|
|
if !f.broker.Resolve(f.req.ID, res) {
|
|
return fmt.Errorf("request already resolved or timed out")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *elevatePendingFile) BlockingRead(_ context.Context, _ string) ([]byte, string, error) {
|
|
return nil, "", fmt.Errorf("not blocking")
|
|
}
|
|
func (f *elevatePendingFile) OneShot() bool { return false }
|
|
func (f *elevatePendingFile) IsBlocking() bool { return false }
|
|
func (f *elevatePendingFile) Async() bool { return false }
|
|
|
|
// --- s/{id}/elevate file (session policy) ---
|
|
|
|
type sessionElevateFile struct {
|
|
broker *elevate.Broker
|
|
sessionID string
|
|
}
|
|
|
|
func (f *sessionElevateFile) Stat() (os.FileInfo, error) {
|
|
return &fs.SyntheticFileInfo{Name_: "elevate", Mode_: 0666}, nil
|
|
}
|
|
|
|
func (f *sessionElevateFile) Read() ([]byte, error) {
|
|
p := f.broker.SessionPolicy(f.sessionID)
|
|
return p.Marshal()
|
|
}
|
|
|
|
func (f *sessionElevateFile) Write(data []byte) error {
|
|
var p elevate.Policy
|
|
if err := elevate.ParsePolicy(data, &p); err != nil {
|
|
return err
|
|
}
|
|
f.broker.SetSessionPolicy(f.sessionID, p)
|
|
return nil
|
|
}
|
|
|
|
func (f *sessionElevateFile) BlockingRead(_ context.Context, _ string) ([]byte, string, error) {
|
|
return nil, "", fmt.Errorf("not blocking")
|
|
}
|
|
func (f *sessionElevateFile) OneShot() bool { return false }
|
|
func (f *sessionElevateFile) IsBlocking() bool { return false }
|
|
func (f *sessionElevateFile) Async() bool { return false }
|