9p: numeric uname principal, atomic UID counter

Session uname is now a numeric UID (>=10000) separate from the session
name. Rename no longer affects identity — the mount keeps working with
the same principal.

- SessionStore uses atomic.Uint32 for UID generation
- Session.Uname() returns the immutable principal
- SessionByUname() lookup for attach-time group assignment
- fileOwnerGroup uses principal, not session name
- WithMount() passed from createSession
This commit is contained in:
Levi Neely 2026-05-19 15:06:26 +02:00
parent a25dab6ee5
commit 4cf8a666e3
2 changed files with 36 additions and 9 deletions

View File

@ -279,6 +279,8 @@ func New(sink *olog.Sink, opts ...ServerOption) *Server {
return storeWrite(s.transcriptStore, name, data) return storeWrite(s.transcriptStore, name, data)
}, },
OnRename: func(oldID, newID string) { OnRename: func(oldID, newID string) {
s.RemoveGroup("agent", oldID)
s.AddGroup("agent", newID)
oldPrefix := "/s/" + oldID oldPrefix := "/s/" + oldID
newPrefix := "/s/" + newID newPrefix := "/s/" + newID
for _, c := range s.conns { for _, c := range s.conns {
@ -326,15 +328,14 @@ func (s *Server) InGroup(group, user string) bool {
} }
// fileOwnerGroup returns the uid and gid for a given path. // fileOwnerGroup returns the uid and gid for a given path.
// Session files are owned by the session ID with group "agent". // Session files are owned by the session's principal with group "agent".
// Everything else is owned by "ollie" with group "ollie". // Everything else is owned by "ollie" with group "ollie".
func (s *Server) fileOwnerGroup(path string) (uid, gid string) { func (s *Server) fileOwnerGroup(path string) (uid, gid string) {
if strings.HasPrefix(path, "/s/") { if strings.HasPrefix(path, "/s/") {
parts := strings.SplitN(strings.TrimPrefix(path, "/s/"), "/", 2) parts := strings.SplitN(strings.TrimPrefix(path, "/s/"), "/", 2)
if len(parts) >= 1 && parts[0] != "new" { if len(parts) >= 1 && parts[0] != "new" {
// Check if parts[0] is a session (not a script like sh, bfg, etc.)
if sess := s.sessionStore.Session(parts[0]); sess != nil { if sess := s.sessionStore.Session(parts[0]); sess != nil {
return parts[0], "agent" return sess.Uname(), "agent"
} }
} }
} }
@ -771,9 +772,9 @@ func (s *Server) attach(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
qid := plan9.Qid{Type: QTDir, Path: 0} qid := plan9.Qid{Type: QTDir, Path: 0}
cs.fids[fc.Fid] = &fid{path: "/", qid: qid} cs.fids[fc.Fid] = &fid{path: "/", qid: qid}
s.log.Debug("Tattach uname=%q", fc.Uname) s.log.Debug("Tattach uname=%q", fc.Uname)
// Assign group membership based on whether uname is a session ID. // Assign group membership based on whether uname is a session principal.
if fc.Uname != "" { if fc.Uname != "" {
if sess := s.sessionStore.Session(fc.Uname); sess != nil { if s.sessionStore.SessionByUname(fc.Uname) != nil {
s.AddGroup("agent", fc.Uname) s.AddGroup("agent", fc.Uname)
} else { } else {
s.AddGroup("user", fc.Uname) s.AddGroup("user", fc.Uname)

View File

@ -7,6 +7,7 @@ import (
"sort" "sort"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"ollie/pkg/agent" "ollie/pkg/agent"
"ollie/pkg/backend" "ollie/pkg/backend"
@ -21,6 +22,7 @@ import (
type Session struct { type Session struct {
mu sync.RWMutex mu sync.RWMutex
id string id string
uname string // immutable user principal (numeric UID), set at creation
Core agent.Core Core agent.Core
Ctx context.Context Ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
@ -37,6 +39,7 @@ func NewSession(id string, core agent.Core, ctx context.Context, cancel context.
} }
func (sess *Session) RunnableID() string { return sess.id } func (sess *Session) RunnableID() string { return sess.id }
func (sess *Session) Uname() string { return sess.uname }
func (sess *Session) Cancel() { func (sess *Session) Cancel() {
sess.cancel() sess.cancel()
@ -120,9 +123,15 @@ type SessionStoreConfig struct {
// SessionStore implements Store for session management. // SessionStore implements Store for session management.
type SessionStore struct { type SessionStore struct {
*storeConfig *storeConfig
cfg SessionStoreConfig cfg SessionStoreConfig
mu sync.RWMutex mu sync.RWMutex
sessions map[string]*Session sessions map[string]*Session
nextUID atomic.Uint32 // incrementing principal counter
}
// nextUname generates the next uname atomically.
func (s *SessionStore) nextUname() string {
return fmt.Sprintf("%d", s.nextUID.Add(1))
} }
func NewSessionStore(cfg SessionStoreConfig) *SessionStore { func NewSessionStore(cfg SessionStoreConfig) *SessionStore {
@ -136,6 +145,7 @@ func NewSessionStore(cfg SessionStoreConfig) *SessionStore {
cfg: cfg, cfg: cfg,
sessions: make(map[string]*Session), sessions: make(map[string]*Session),
} }
ss.nextUID.Store(9999)
ss.storeConfig = &storeConfig{ ss.storeConfig = &storeConfig{
StatFn: ss.stat, StatFn: ss.stat,
ListFn: ss.list, ListFn: ss.list,
@ -236,6 +246,18 @@ func (s *SessionStore) Session(id string) *Session {
return s.sessions[id] return s.sessions[id]
} }
// SessionByUname returns the session with the given uname (principal), or nil.
func (s *SessionStore) SessionByUname(uname string) *Session {
s.mu.RLock()
defer s.mu.RUnlock()
for _, sess := range s.sessions {
if sess.uname == uname {
return sess
}
}
return nil
}
// OpenStore returns a RunnableStore for the given session ID. // OpenStore returns a RunnableStore for the given session ID.
func (s *SessionStore) OpenStore(id string) (RunnableStore, error) { func (s *SessionStore) OpenStore(id string) (RunnableStore, error) {
if sess := s.Session(id); sess != nil { if sess := s.Session(id); sess != nil {
@ -358,6 +380,7 @@ func (s *SessionStore) createSession(args []string) error {
var core agent.Core var core agent.Core
var allowTools []string var allowTools []string
uname := s.nextUname()
if s.cfg.NewCore != nil { if s.cfg.NewCore != nil {
var err error var err error
core, err = s.cfg.NewCore(sessID, agentName, cwd) core, err = s.cfg.NewCore(sessID, agentName, cwd)
@ -393,6 +416,7 @@ func (s *SessionStore) createSession(args []string) error {
} }
var execOpts []execute.Option var execOpts []execute.Option
execOpts = append(execOpts, execute.WithMount())
if s.cfg.Strict { if s.cfg.Strict {
execOpts = append(execOpts, execute.WithStrict()) execOpts = append(execOpts, execute.WithStrict())
} }
@ -409,7 +433,7 @@ func (s *SessionStore) createSession(args []string) error {
"execute": execute.Decl(cwd, execOpts...), "execute": execute.Decl(cwd, execOpts...),
}) })
env := agent.BuildAgentEnv(cfg, newDisp(), cwd, []string{"OLLIE_SESSION_ID=" + sessID}) env := agent.BuildAgentEnv(cfg, newDisp(), cwd, []string{"OLLIE_SESSION_ID=" + sessID, "OLLIE_UNAME=" + uname})
core = agent.NewAgentCore(agent.AgentCoreConfig{ core = agent.NewAgentCore(agent.AgentCoreConfig{
Backend: be, Backend: be,
@ -417,6 +441,7 @@ func (s *SessionStore) createSession(args []string) error {
AgentsDir: s.cfg.AgentsDir, AgentsDir: s.cfg.AgentsDir,
SessionsDir: s.cfg.SessionsDir, SessionsDir: s.cfg.SessionsDir,
SessionID: sessID, SessionID: sessID,
Uname: uname,
CWD: cwd, CWD: cwd,
Env: env, Env: env,
NewDispatcher: newDisp, NewDispatcher: newDisp,
@ -429,6 +454,7 @@ func (s *SessionStore) createSession(args []string) error {
sess.AllowTools = allowTools sess.AllowTools = allowTools
s.mu.Lock() s.mu.Lock()
sess.uname = uname
s.sessions[sessID] = sess s.sessions[sessID] = sess
s.mu.Unlock() s.mu.Unlock()