Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A much more lax approach to privelege separation #816

Merged
merged 7 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
}