Skip to content

Commit 51d0322

Browse files
authored
A much more lax approach to privelege separation (#816)
* A much more lax approach to privelege separation Compared to #814. * Correct ErrFailed for privdrop package * privdrop stubs for Windows * Assign *syscall.SysProcAttr instead of *syscall.Credential * chown(2) agent's binary when privelege dropping was requested * privdrop: expose Chown for both Windows and non-Windows * Clarify to which isolations does --user apply
1 parent 3ed56c5 commit 51d0322

File tree

8 files changed

+129
-1
lines changed

8 files changed

+129
-1
lines changed

internal/commands/worker/run.go

+27
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,27 @@ package worker
33
import (
44
"errors"
55
"fmt"
6+
"github.com/cirruslabs/cirrus-cli/pkg/privdrop"
67
"github.com/spf13/cobra"
8+
"runtime"
79
)
810

911
var ErrRun = errors.New("run failed")
1012

13+
var username string
14+
1115
func NewRunCmd() *cobra.Command {
1216
cmd := &cobra.Command{
1317
Use: "run",
1418
Short: "Run persistent worker",
1519
RunE: func(cmd *cobra.Command, args []string) error {
20+
// Initialize privilege dropping on macOS, if requested
21+
if username != "" {
22+
if err := privdrop.Initialize(username); err != nil {
23+
return err
24+
}
25+
}
26+
1627
worker, err := buildWorker(cmd.ErrOrStderr())
1728
if err != nil {
1829
return err
@@ -24,6 +35,22 @@ func NewRunCmd() *cobra.Command {
2435
},
2536
}
2637

38+
// We only need privilege dropping on macOS due to newly introduced
39+
// "Local Network" permission, which cannot be disabled automatically,
40+
// and according to the Apple's documentation[1], running Persistent
41+
// Worker as a superuser is the only choice.
42+
//
43+
// Note that the documentation says that "macOS automatically allows
44+
// local network access by:" and "Any daemon started by launchd". However,
45+
// this is not true for daemons that have <key>UserName</key> set to non-root.
46+
//
47+
//nolint:lll // can't make the link shorter
48+
// [1]: https://developer.apple.com/documentation/technotes/tn3179-understanding-local-network-privacy#macOS-considerations
49+
if runtime.GOOS == "darwin" {
50+
cmd.Flags().StringVar(&username, "user", "", "user name to drop privileges to"+
51+
" when running external programs (only Tart, Parallels and unset isolation are currently supported)")
52+
}
53+
2754
attachFlags(cmd)
2855

2956
return cmd

internal/executor/agent/agent.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"github.com/cirruslabs/cirrus-cli/pkg/privdrop"
78
"io"
89
"net/http"
910
"os"
@@ -77,10 +78,18 @@ func RetrieveBinary(
7778
}
7879

7980
// Make the agent binary executable
80-
if err := os.Chmod(tmpAgentFile.Name(), 0500); err != nil {
81+
if err := tmpAgentFile.Chmod(0500); err != nil {
8182
return "", err
8283
}
8384

85+
// Make sure that the agent binary belongs to the privilege-dropped
86+
// user and group, in case privilege dropping was requested
87+
if chownTo := privdrop.ChownTo; chownTo != nil {
88+
if err := tmpAgentFile.Chown(chownTo.UID, chownTo.GID); err != nil {
89+
return "", err
90+
}
91+
}
92+
8493
if err := tmpAgentFile.Close(); err != nil {
8594
return "", err
8695
}

internal/executor/instance/persistentworker/isolation/none/none.go

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/cirruslabs/cirrus-cli/internal/executor/instance/persistentworker/pwdir"
99
"github.com/cirruslabs/cirrus-cli/internal/executor/instance/runconfig"
1010
"github.com/cirruslabs/cirrus-cli/internal/logger"
11+
"github.com/cirruslabs/cirrus-cli/pkg/privdrop"
1112
"github.com/otiai10/copy"
1213
"go.opentelemetry.io/otel/attribute"
1314
"os"
@@ -80,6 +81,11 @@ func (pwi *PersistentWorkerInstance) Run(ctx context.Context, config *runconfig.
8081
pwi.tempDir,
8182
)
8283

84+
// Drop privileges for the spawned process, if requested
85+
if sysProcAttr := privdrop.SysProcAttr; sysProcAttr != nil {
86+
cmd.SysProcAttr = sysProcAttr
87+
}
88+
8389
// Determine the working directory for the agent
8490
if config.DirtyMode {
8591
cmd.Dir = config.ProjectDir

internal/executor/instance/persistentworker/isolation/parallels/cli.go

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"errors"
77
"fmt"
8+
"github.com/cirruslabs/cirrus-cli/pkg/privdrop"
89
"os/exec"
910
"strings"
1011
)
@@ -17,6 +18,11 @@ var (
1718
func runParallelsCommand(ctx context.Context, commandName string, args ...string) (string, string, error) {
1819
cmd := exec.CommandContext(ctx, commandName, args...)
1920

21+
// Drop privileges for the spawned process, if requested
22+
if sysProcAttr := privdrop.SysProcAttr; sysProcAttr != nil {
23+
cmd.SysProcAttr = sysProcAttr
24+
}
25+
2026
var stdout, stderr bytes.Buffer
2127
cmd.Stdout = &stdout
2228
cmd.Stderr = &stderr

internal/executor/instance/persistentworker/isolation/tart/cmd.go

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"errors"
77
"fmt"
8+
"github.com/cirruslabs/cirrus-cli/pkg/privdrop"
89
"github.com/cirruslabs/echelon"
910
"io"
1011
"os/exec"
@@ -60,6 +61,11 @@ func CmdWithLogger(
6061

6162
cmd := exec.CommandContext(ctx, tartCommandName, args...)
6263

64+
// Drop privileges for the spawned process, if requested
65+
if sysProcAttr := privdrop.SysProcAttr; sysProcAttr != nil {
66+
cmd.SysProcAttr = sysProcAttr
67+
}
68+
6369
// Work around https://github.com/golang/go/issues/23019,
6470
// most likely happens when running with --net-softnet
6571
cmd.WaitDelay = time.Second

pkg/privdrop/common.go

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package privdrop
2+
3+
type Chown struct {
4+
UID int
5+
GID int
6+
}

pkg/privdrop/privdrop.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//go:build !windows
2+
3+
package privdrop
4+
5+
import (
6+
"errors"
7+
"fmt"
8+
userpkg "os/user"
9+
"strconv"
10+
"syscall"
11+
)
12+
13+
var ErrFailed = errors.New("failed to initialize privilege dropping")
14+
15+
var (
16+
SysProcAttr *syscall.SysProcAttr
17+
ChownTo *Chown
18+
)
19+
20+
func Initialize(username string) error {
21+
user, err := userpkg.Lookup(username)
22+
if err != nil {
23+
return fmt.Errorf("%w: failed to lookup username %q: %v",
24+
ErrFailed, username, err)
25+
}
26+
27+
gid, err := strconv.Atoi(user.Gid)
28+
if err != nil {
29+
return fmt.Errorf("%w: failed to parse %s's group ID %q: %v",
30+
ErrFailed, user.Name, user.Gid, err)
31+
}
32+
33+
uid, err := strconv.Atoi(user.Uid)
34+
if err != nil {
35+
return fmt.Errorf("%w: failed to parse %s's user ID %q: %v",
36+
ErrFailed, user.Name, user.Uid, err)
37+
}
38+
39+
SysProcAttr = &syscall.SysProcAttr{
40+
Credential: &syscall.Credential{
41+
Uid: uint32(uid),
42+
Gid: uint32(gid),
43+
},
44+
}
45+
ChownTo = &Chown{
46+
UID: uid,
47+
GID: gid,
48+
}
49+
50+
return nil
51+
}

pkg/privdrop/privdrop_unsupported.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//go:build windows
2+
3+
package privdrop
4+
5+
import (
6+
"fmt"
7+
"syscall"
8+
)
9+
10+
var (
11+
SysProcAttr *syscall.SysProcAttr
12+
ChownTo *Chown
13+
)
14+
15+
func Initialize(username string) error {
16+
return fmt.Errorf("privilege dropping is not supported on this platform")
17+
}

0 commit comments

Comments
 (0)