Skip to content

Commit

Permalink
feat: add user flag to service exec command (#2626)
Browse files Browse the repository at this point in the history
## Description
We depend on some containers that use non-root users for certain things,
so would be nice to be able to `service exec --user foo` as those users,
similar as the docker CLI interface.

This PR adds --user to `service exec` and passes the user on the
RunExecCommand in the docker and kubernetes managers. I could not find a
good way to do it for kubernetes, but that might be appropriate for more
production like configurations.

## REMINDER: 
@tedim52

## Is this change user facing?
YES
  • Loading branch information
christophercampbell authored Jan 16, 2025
1 parent 593f9c2 commit be3dbe2
Show file tree
Hide file tree
Showing 14 changed files with 258 additions and 50 deletions.
25 changes: 23 additions & 2 deletions cli/cli/commands/service/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package exec
import (
"context"
"fmt"

"github.com/kurtosis-tech/kurtosis/api/golang/engine/kurtosis_engine_rpc_api_bindings"
"github.com/kurtosis-tech/kurtosis/api/golang/engine/lib/kurtosis_context"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/highlevel/enclave_id_arg"
Expand Down Expand Up @@ -32,6 +33,10 @@ const (
isExecCommandArgOptional = false
isExecCommandArgGreedy = false

containerUserKey = "user"
containerUserShortKey = "u"
containerUserDefault = "root"

kurtosisBackendCtxKey = "kurtosis-backend"
engineClientCtxKey = "engine-client"

Expand All @@ -45,7 +50,13 @@ var ServiceShellCmd = &engine_consuming_kurtosis_command.EngineConsumingKurtosis
LongDescription: "Execute a command in a service. Note if the command being run is multiple words you should wrap it in quotes",
KurtosisBackendContextKey: kurtosisBackendCtxKey,
EngineClientContextKey: engineClientCtxKey,
Flags: []*flags.FlagConfig{},
Flags: []*flags.FlagConfig{{
Key: containerUserKey,
Usage: "optional service container user for command",
Shorthand: containerUserShortKey,
Type: flags.FlagType_String,
Default: containerUserDefault,
}},
Args: []*args.ArgConfig{
enclave_id_arg.NewEnclaveIdentifierArg(
enclaveIdentifierArgKey,
Expand Down Expand Up @@ -109,7 +120,17 @@ func run(
}
serviceUuid := service.ServiceUUID(serviceCtx.GetServiceUUID())

results, resultErrors, err := kurtosisBackend.RunUserServiceExecCommands(ctx, enclaveUuid, map[service.ServiceUUID][]string{
containerUser, err := flags.GetString(containerUserKey)
if err != nil {
return stacktrace.Propagate(err, "An error occurred getting the exec container user flag '%v'", containerUserKey)
}
// convert "root" to "" as it is implied if empty. This simplifies the backend
// impl usages that do not support --user
if containerUser == containerUserDefault {
containerUser = ""
}

results, resultErrors, err := kurtosisBackend.RunUserServiceExecCommands(ctx, enclaveUuid, containerUser, map[service.ServiceUUID][]string{
serviceUuid: {
binShCommand,
binShCommandFlag,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,13 +297,14 @@ func (backend *DockerKurtosisBackend) GetUserServiceLogs(
func (backend *DockerKurtosisBackend) RunUserServiceExecCommands(
ctx context.Context,
enclaveUuid enclave.EnclaveUUID,
containerUser string,
userServiceCommands map[service.ServiceUUID][]string,
) (
map[service.ServiceUUID]*exec_result.ExecResult,
map[service.ServiceUUID]error,
error,
) {
return user_service_functions.RunUserServiceExecCommands(ctx, enclaveUuid, userServiceCommands, backend.dockerManager)
return user_service_functions.RunUserServiceExecCommands(ctx, enclaveUuid, containerUser, userServiceCommands, backend.dockerManager)
}

func (backend *DockerKurtosisBackend) RunUserServiceExecCommandWithStreamedOutput(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func storeConfigInVolume(
}
for i := uint(0); i < maxRetries; i++ {
outputBuffer := &bytes.Buffer{}
exitCode, err := dockerManager.RunExecCommand(ctx, containerId, execCmd, outputBuffer)
exitCode, err := dockerManager.RunUserServiceExecCommands(ctx, containerId, "", execCmd, outputBuffer)
if err == nil {
if exitCode == creationSuccessExitCode {
logrus.Debugf("The Docker config file was successfully added into the volume.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import (
"bytes"
"context"
"fmt"
"os"
"path"
"time"

"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/consts"
"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/docker/docker_manager"
"github.com/kurtosis-tech/stacktrace"
"github.com/sirupsen/logrus"
"os"
"path"
"time"
)

const (
Expand Down Expand Up @@ -124,7 +125,7 @@ func storeTokenInVolume(
}
for i := uint(0); i < maxRetries; i++ {
outputBuffer := &bytes.Buffer{}
exitCode, err := dockerManager.RunExecCommand(ctx, containerId, execCmd, outputBuffer)
exitCode, err := dockerManager.RunUserServiceExecCommands(ctx, containerId, "", execCmd, outputBuffer)
if err == nil {
if exitCode == authStorageCreationSuccessExitCode {
logrus.Debugf("The GitHub auth token was successfully added into the volume.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func (fluent *fluentbitConfigurationCreator) createFluentbitConfigFileInVolume(
}
for i := uint(0); i < maxRetries; i++ {
outputBuffer := &bytes.Buffer{}
exitCode, err := dockerManager.RunExecCommand(ctx, containerId, execCmd, outputBuffer)
exitCode, err := dockerManager.RunUserServiceExecCommands(ctx, containerId, "", execCmd, outputBuffer)
if err == nil {
if exitCode == configFileCreationSuccessExitCode {
logrus.Debugf("The Fluentbit config file with content '%v' was successfully added into the volume", configFileContentStr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/docker_label_key"
"io"
"net"
"os"
Expand All @@ -14,6 +13,8 @@ import (
"strings"
"time"

"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/docker_label_key"

"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/go-connections/nat"
"github.com/gammazero/workerpool"
Expand Down Expand Up @@ -379,7 +380,7 @@ func WaitForPortAvailabilityUsingNetstat(
}
for i := uint(0); i < maxRetries; i++ {
outputBuffer := &bytes.Buffer{}
exitCode, err := dockerManager.RunExecCommand(ctx, containerId, execCmd, outputBuffer)
exitCode, err := dockerManager.RunUserServiceExecCommands(ctx, containerId, "", execCmd, outputBuffer)
if err == nil {
if exitCode == netstatSuccessExitCode {
return nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ package user_service_functions
import (
"bytes"
"context"
"reflect"

"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/shared_helpers"
"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/docker/docker_manager"
"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/enclave"
"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/exec_result"
"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service"
"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/operation_parallelizer"
"github.com/kurtosis-tech/stacktrace"
"reflect"
)

// TODO Switch these to streaming so that huge command outputs don't blow up the API container memory
func RunUserServiceExecCommands(
ctx context.Context,
enclaveId enclave.EnclaveUUID,
containerUser string,
userServiceCommands map[service.ServiceUUID][]string,
dockerManager *docker_manager.DockerManager,
) (
Expand Down Expand Up @@ -46,7 +48,7 @@ func RunUserServiceExecCommands(
// the error to the map downstream
}

successfulExecs, failedExecs, err := runExecOperationsInParallel(ctx, userServiceCommands, userServiceDockerResources, dockerManager)
successfulExecs, failedExecs, err := runExecOperationsInParallel(ctx, containerUser, userServiceCommands, userServiceDockerResources, dockerManager)
if err != nil {
return nil, nil, stacktrace.Propagate(err, "An unexpected error occurred running the exec commands in parallel")
}
Expand All @@ -55,6 +57,7 @@ func RunUserServiceExecCommands(

func runExecOperationsInParallel(
ctx context.Context,
containerUser string,
commandArgs map[service.ServiceUUID][]string,
userServiceDockerResources map[service.ServiceUUID]*shared_helpers.UserServiceDockerResources,
dockerManager *docker_manager.DockerManager,
Expand All @@ -79,7 +82,7 @@ func runExecOperationsInParallel(
}

execOperationId := operation_parallelizer.OperationID(serviceUuid)
execOperation := createExecOperation(ctx, serviceUuid, userServiceDockerResource, commandArg, dockerManager)
execOperation := createExecOperation(ctx, serviceUuid, containerUser, userServiceDockerResource, commandArg, dockerManager)
execOperations[execOperationId] = execOperation
}

Expand Down Expand Up @@ -108,6 +111,7 @@ func runExecOperationsInParallel(
func createExecOperation(
ctx context.Context,
serviceUuid service.ServiceUUID,
containerUser string,
userServiceDockerResource *shared_helpers.UserServiceDockerResources,
commandArg []string,
dockerManager *docker_manager.DockerManager,
Expand All @@ -116,9 +120,10 @@ func createExecOperation(
execOutputBuf := &bytes.Buffer{}
userServiceDockerContainer := userServiceDockerResource.ServiceContainer

exitCode, err := dockerManager.RunExecCommand(
exitCode, err := dockerManager.RunUserServiceExecCommands(
ctx,
userServiceDockerContainer.GetId(),
containerUser,
commandArg,
execOutputBuf,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -990,13 +990,13 @@ func (manager *DockerManager) GetContainerLogs(
}

/*
RunExecCommand
RunUserServiceExecCommands
Executes the given command inside the container with the given ID, blocking until the command completes
*/
func (manager *DockerManager) RunExecCommand(context context.Context, containerId string, command []string, logOutput io.Writer) (int32, error) {
func (manager *DockerManager) RunUserServiceExecCommands(context context.Context, containerId, userId string, command []string, logOutput io.Writer) (int32, error) {
dockerClient := manager.dockerClient
execConfig := types.ExecConfig{
User: "",
User: userId,
Privileged: false,
Tty: false,
ConsoleSize: nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,12 +347,17 @@ func (backend *KubernetesKurtosisBackend) GetUserServiceLogs(
func (backend *KubernetesKurtosisBackend) RunUserServiceExecCommands(
ctx context.Context,
enclaveUuid enclave.EnclaveUUID,
containerUser string,
userServiceCommands map[service.ServiceUUID][]string,
) (
succesfulUserServiceExecResults map[service.ServiceUUID]*exec_result.ExecResult,
erroredUserServiceUuids map[service.ServiceUUID]error,
resultErr error,
) {
if containerUser != "" {
resultErr = stacktrace.NewError("--user not implemented for kurtosis backend")
return
}
return user_services_functions.RunUserServiceExecCommands(
ctx,
enclaveUuid,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,13 +322,14 @@ func (backend *MetricsReportingKurtosisBackend) GetUserServiceLogs(
func (backend *MetricsReportingKurtosisBackend) RunUserServiceExecCommands(
ctx context.Context,
enclaveUuid enclave.EnclaveUUID,
containerUser string,
userServiceCommands map[service.ServiceUUID][]string,
) (
succesfulUserServiceExecResults map[service.ServiceUUID]*exec_result.ExecResult,
successfulUserServiceExecResults map[service.ServiceUUID]*exec_result.ExecResult,
erroredUserServiceUuids map[service.ServiceUUID]error,
resultErr error,
) {
succesfulUserServiceExecResults, erroredUserServiceUuids, err := backend.underlying.RunUserServiceExecCommands(ctx, enclaveUuid, userServiceCommands)
successfulUserServiceExecResults, erroredUserServiceUuids, err := backend.underlying.RunUserServiceExecCommands(ctx, enclaveUuid, containerUser, userServiceCommands)
if err != nil {
return nil, nil, stacktrace.Propagate(
err,
Expand All @@ -337,7 +338,7 @@ func (backend *MetricsReportingKurtosisBackend) RunUserServiceExecCommands(
enclaveUuid,
)
}
return succesfulUserServiceExecResults, erroredUserServiceUuids, nil
return successfulUserServiceExecResults, erroredUserServiceUuids, nil
}

func (backend *MetricsReportingKurtosisBackend) RunUserServiceExecCommandWithStreamedOutput(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,10 @@ type KurtosisBackend interface {
RunUserServiceExecCommands(
ctx context.Context,
enclaveUuid enclave.EnclaveUUID,
containerUser string,
userServiceCommands map[service.ServiceUUID][]string,
) (
succesfulUserServiceExecResults map[service.ServiceUUID]*exec_result.ExecResult,
successfulUserServiceExecResults map[service.ServiceUUID]*exec_result.ExecResult,
erroredUserServiceUuids map[service.ServiceUUID]error,
resultErr error,
)
Expand Down
Loading

0 comments on commit be3dbe2

Please sign in to comment.