bastion 0.62.0
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
// The ask pipe: ask.sock (agentask.go) for a bastion that runs as the Windows
|
||||
// service.
|
||||
//
|
||||
// Installed for the whole machine, the bastion is a LocalSystem service and
|
||||
// its state sits in LocalSystem's profile, where the person's own account can
|
||||
// neither read nor write it. Before this pipe, that account had no way to the
|
||||
// machine's bastion at all: `privatae-bastion ui` signed in with a key of its
|
||||
// own and the console answered 401, the reads and owner acts were refused
|
||||
// beside the service, and an agent could not ask for a stop. The pipe answers
|
||||
// exactly what ask.sock answers, through the same code (askAnswer): a stop
|
||||
// asked by any account, and the console, the reads and the owner's acts for
|
||||
// the account recorded as the machine's owner (approvals-owner --sid), with
|
||||
// the caller taken from the pipe client's token, never from the request.
|
||||
//
|
||||
// The pipe is the approvals pipe's machinery (approvalpipe_windows.go):
|
||||
// overlapped I/O with a timeout on every call, a new instance created before
|
||||
// the one it replaces is handed out, the first instance created with
|
||||
// FILE_FLAG_FIRST_PIPE_INSTANCE, and the asking side checking the pipe's
|
||||
// server is the service's own process before it sends anything, since an act
|
||||
// carries the owner's code.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// startAskPipe serves the ask pipe until done is closed, retrying with backoff
|
||||
// when it cannot (pipeServeRetrying), as the approvals pipe does.
|
||||
func startAskPipe(done <-chan struct{}) {
|
||||
wait := func(d time.Duration) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return false
|
||||
case <-time.After(d):
|
||||
return true
|
||||
}
|
||||
}
|
||||
go pipeServeRetrying(func(ready func()) error {
|
||||
return servePipe(done, ready, askPipeName, askPipeSDDL, serveAskPipeConn)
|
||||
}, wait, func(f string, a ...any) { fmt.Printf(f, a...) })
|
||||
}
|
||||
|
||||
// serveAskPipeConn answers one ask, then waits briefly for the client to
|
||||
// close: closing first would discard an answer it has not read yet.
|
||||
func serveAskPipeConn(c *pipeConn) {
|
||||
client, cerr := pipeClientOf(c.h)
|
||||
raw, err := c.readLine(askMaxBytes, pipeIOTimeout)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var reply any
|
||||
switch {
|
||||
case cerr != nil:
|
||||
reply = askUnattributable(cerr)
|
||||
default:
|
||||
who := pipeWho(client)
|
||||
var req agentAskRequest
|
||||
if err := json.Unmarshal(raw, &req); err != nil {
|
||||
reply = agentAskReply{User: who, Why: "the request could not be read"}
|
||||
break
|
||||
}
|
||||
peer := askPeer{SID: client.UserSID, Name: who, PID: int(client.PID)}
|
||||
reply = askAnswer(peer, req, func(time.Duration) {}, func() int { return int(client.PID) })
|
||||
}
|
||||
out, _ := json.Marshal(reply)
|
||||
if err := c.write(append(out, '\n'), pipeIOTimeout); err != nil {
|
||||
return
|
||||
}
|
||||
_, _ = c.read(pipeIOTimeout)
|
||||
}
|
||||
|
||||
// dialAskLane opens the service's ask pipe; p, ask.sock's path, does not
|
||||
// apply here.
|
||||
func dialAskLane(string) (askConn, error) {
|
||||
c, err := dialServicePipe(askPipeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := pipeServerIsService(c.h); err != nil {
|
||||
c.close()
|
||||
return nil, err
|
||||
}
|
||||
return &askPipeClient{c: c}, nil
|
||||
}
|
||||
|
||||
// askLaneAddr is where the lane is, as a message names it.
|
||||
func askLaneAddr(string) string { return askPipeName }
|
||||
|
||||
// askPipeClient is the asking side's end of the pipe, read and written the
|
||||
// way the lane's code reads and writes a socket: a deadline for the whole
|
||||
// exchange, bytes in, bytes out.
|
||||
type askPipeClient struct {
|
||||
c *pipeConn
|
||||
deadline time.Time
|
||||
pending []byte
|
||||
}
|
||||
|
||||
func (a *askPipeClient) SetDeadline(t time.Time) error {
|
||||
a.deadline = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// left is what remains of the deadline for one call; 0 waits as long as it
|
||||
// takes, which only a caller that set no deadline gets.
|
||||
func (a *askPipeClient) left() time.Duration {
|
||||
if a.deadline.IsZero() {
|
||||
return 0
|
||||
}
|
||||
if d := time.Until(a.deadline); d > 0 {
|
||||
return d
|
||||
}
|
||||
return time.Millisecond
|
||||
}
|
||||
|
||||
func (a *askPipeClient) Read(b []byte) (int, error) {
|
||||
for len(a.pending) == 0 {
|
||||
n, err := a.c.read(a.left())
|
||||
a.pending = append(a.pending, a.c.buf[:n]...)
|
||||
if err != nil && !errors.Is(err, windows.ERROR_MORE_DATA) {
|
||||
if len(a.pending) > 0 {
|
||||
break
|
||||
}
|
||||
if errors.Is(err, windows.ERROR_BROKEN_PIPE) || errors.Is(err, windows.ERROR_PIPE_NOT_CONNECTED) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
n := copy(b, a.pending)
|
||||
a.pending = a.pending[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (a *askPipeClient) Write(b []byte) (int, error) {
|
||||
if err := a.c.write(b, a.left()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (a *askPipeClient) Close() error {
|
||||
a.c.close()
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user