Skip to content

Commit

Permalink
A much more lax approach to privelege separation (#816)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
edigaryev authored Nov 28, 2024
1 parent 3ed56c5 commit 51d0322
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 1 deletion.
27 changes: 27 additions & 0 deletions internal/commands/worker/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,27 @@ package worker
import (
"errors"
"fmt"
"github.com/cirruslabs/cirrus-cli/pkg/privdrop"
"github.com/spf13/cobra"
"runtime"
)

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

var username string

func NewRunCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "run",
Short: "Run persistent worker",
RunE: func(cmd *cobra.Command, args []string) error {
// Initialize privilege dropping on macOS, if requested
if username != "" {
if err := privdrop.Initialize(username); err != nil {
return err
}
}

worker, err := buildWorker(cmd.ErrOrStderr())
if err != nil {
return err
Expand All @@ -24,6 +35,22 @@ func NewRunCmd() *cobra.Command {
},
}

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

attachFlags(cmd)

return cmd
Expand Down
11 changes: 10 additions & 1 deletion internal/executor/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/cirruslabs/cirrus-cli/pkg/privdrop"
"io"
"net/http"
"os"
Expand Down Expand Up @@ -77,10 +78,18 @@ func RetrieveBinary(
}

// Make the agent binary executable
if err := os.Chmod(tmpAgentFile.Name(), 0500); err != nil {
if err := tmpAgentFile.Chmod(0500); err != nil {
return "", err
}

// Make sure that the agent binary belongs to the privilege-dropped
// user and group, in case privilege dropping was requested
if chownTo := privdrop.ChownTo; chownTo != nil {
if err := tmpAgentFile.Chown(chownTo.UID, chownTo.GID); err != nil {
return "", err
}
}

if err := tmpAgentFile.Close(); err != nil {
return "", err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/cirruslabs/cirrus-cli/internal/executor/instance/persistentworker/pwdir"
"github.com/cirruslabs/cirrus-cli/internal/executor/instance/runconfig"
"github.com/cirruslabs/cirrus-cli/internal/logger"
"github.com/cirruslabs/cirrus-cli/pkg/privdrop"
"github.com/otiai10/copy"
"go.opentelemetry.io/otel/attribute"
"os"
Expand Down Expand Up @@ -80,6 +81,11 @@ func (pwi *PersistentWorkerInstance) Run(ctx context.Context, config *runconfig.
pwi.tempDir,
)

// Drop privileges for the spawned process, if requested
if sysProcAttr := privdrop.SysProcAttr; sysProcAttr != nil {
cmd.SysProcAttr = sysProcAttr
}

// Determine the working directory for the agent
if config.DirtyMode {
cmd.Dir = config.ProjectDir
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"errors"
"fmt"
"github.com/cirruslabs/cirrus-cli/pkg/privdrop"
"os/exec"
"strings"
)
Expand All @@ -17,6 +18,11 @@ var (
func runParallelsCommand(ctx context.Context, commandName string, args ...string) (string, string, error) {
cmd := exec.CommandContext(ctx, commandName, args...)

// Drop privileges for the spawned process, if requested
if sysProcAttr := privdrop.SysProcAttr; sysProcAttr != nil {
cmd.SysProcAttr = sysProcAttr
}

var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"errors"
"fmt"
"github.com/cirruslabs/cirrus-cli/pkg/privdrop"
"github.com/cirruslabs/echelon"
"io"
"os/exec"
Expand Down Expand Up @@ -60,6 +61,11 @@ func CmdWithLogger(

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

// Drop privileges for the spawned process, if requested
if sysProcAttr := privdrop.SysProcAttr; sysProcAttr != nil {
cmd.SysProcAttr = sysProcAttr
}

// Work around https://github.com/golang/go/issues/23019,
// most likely happens when running with --net-softnet
cmd.WaitDelay = time.Second
Expand Down
6 changes: 6 additions & 0 deletions pkg/privdrop/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package privdrop

type Chown struct {
UID int
GID int
}
51 changes: 51 additions & 0 deletions pkg/privdrop/privdrop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//go:build !windows

package privdrop

import (
"errors"
"fmt"
userpkg "os/user"
"strconv"
"syscall"
)

var ErrFailed = errors.New("failed to initialize privilege dropping")

var (
SysProcAttr *syscall.SysProcAttr
ChownTo *Chown
)

func Initialize(username string) error {
user, err := userpkg.Lookup(username)
if err != nil {
return fmt.Errorf("%w: failed to lookup username %q: %v",
ErrFailed, username, err)
}

gid, err := strconv.Atoi(user.Gid)
if err != nil {
return fmt.Errorf("%w: failed to parse %s's group ID %q: %v",
ErrFailed, user.Name, user.Gid, err)
}

uid, err := strconv.Atoi(user.Uid)
if err != nil {
return fmt.Errorf("%w: failed to parse %s's user ID %q: %v",
ErrFailed, user.Name, user.Uid, err)
}

SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{
Uid: uint32(uid),
Gid: uint32(gid),
},
}
ChownTo = &Chown{
UID: uid,
GID: gid,
}

return nil
}
17 changes: 17 additions & 0 deletions pkg/privdrop/privdrop_unsupported.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//go:build windows

package privdrop

import (
"fmt"
"syscall"
)

var (
SysProcAttr *syscall.SysProcAttr
ChownTo *Chown
)

func Initialize(username string) error {
return fmt.Errorf("privilege dropping is not supported on this platform")
}

0 comments on commit 51d0322

Please sign in to comment.