diff --git a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/user_services_functions/start_user_services.go b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/user_services_functions/start_user_services.go index d04897e594..1bde0965b0 100644 --- a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/user_services_functions/start_user_services.go +++ b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/user_services_functions/start_user_services.go @@ -538,6 +538,7 @@ func createStartServiceOperation( cpuAllocationMillicpus := serviceConfig.GetCPUAllocationMillicpus() memoryAllocationMegabytes := serviceConfig.GetMemoryAllocationMegabytes() privateIPAddrPlaceholder := serviceConfig.GetPrivateIPAddrPlaceholder() + user := serviceConfig.GetUser() // We replace the placeholder value with the actual private IP address privateIPAddrStr := privateIpAddr.String() @@ -691,7 +692,7 @@ func createStartServiceOperation( fluentdLoggingDriverCnfg, ).WithRestartPolicy( restartPolicy, - ) + ).WithUser(user) if entrypointArgs != nil { createAndStartArgsBuilder.WithEntrypointArgs(entrypointArgs) diff --git a/container-engine-lib/lib/backend_impls/docker/docker_manager/create_and_start_container_args.go b/container-engine-lib/lib/backend_impls/docker/docker_manager/create_and_start_container_args.go index dd2eed632b..9a1f40c96b 100644 --- a/container-engine-lib/lib/backend_impls/docker/docker_manager/create_and_start_container_args.go +++ b/container-engine-lib/lib/backend_impls/docker/docker_manager/create_and_start_container_args.go @@ -1,6 +1,7 @@ package docker_manager import ( + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_user" "net" "github.com/docker/go-connections/nat" @@ -32,6 +33,7 @@ type CreateAndStartContainerArgs struct { containerInitEnabled bool restartPolicy RestartPolicy imageDownloadMode image_download_mode.ImageDownloadMode + user *service_user.ServiceUser } // Builder for creating CreateAndStartContainerArgs object @@ -59,6 +61,7 @@ type CreateAndStartContainerArgsBuilder struct { containerInitEnabled bool restartPolicy RestartPolicy imageDownloadMode image_download_mode.ImageDownloadMode + user *service_user.ServiceUser } /* @@ -93,6 +96,7 @@ func NewCreateAndStartContainerArgsBuilder(dockerImage string, name string, netw containerInitEnabled: false, restartPolicy: NoRestart, imageDownloadMode: image_download_mode.ImageDownloadMode_Missing, + user: nil, } } @@ -121,6 +125,7 @@ func (builder *CreateAndStartContainerArgsBuilder) Build() *CreateAndStartContai containerInitEnabled: builder.containerInitEnabled, restartPolicy: builder.restartPolicy, imageDownloadMode: builder.imageDownloadMode, + user: builder.user, } } @@ -265,3 +270,8 @@ func (builder *CreateAndStartContainerArgsBuilder) WithFetchingLatestImageIfMiss builder.imageDownloadMode = image_download_mode.ImageDownloadMode_Missing return builder } + +func (builder *CreateAndStartContainerArgsBuilder) WithUser(user *service_user.ServiceUser) *CreateAndStartContainerArgsBuilder { + builder.user = user + return builder +} diff --git a/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go b/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go index 4a0d99f6c0..6bcdcdcdba 100644 --- a/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go +++ b/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go @@ -557,6 +557,11 @@ func (manager *DockerManager) CreateAndStartContainer( usedPortsSet[port] = struct{}{} } + var userStr string + if args.user != nil { + userStr = args.user.GetUIDGIDPairAsStr() + } + containerConfigPtr, err := manager.getContainerCfg( dockerImage, isInteractiveMode, @@ -565,6 +570,7 @@ func (manager *DockerManager) CreateAndStartContainer( args.cmdArgs, args.envVariables, args.labels, + userStr, ) if err != nil { return "", nil, stacktrace.Propagate(err, "Failed to configure container from service.") @@ -1798,7 +1804,8 @@ func (manager *DockerManager) getContainerCfg( entrypointArgs []string, cmdArgs []string, envVariables map[string]string, - labels map[string]string) (config *container.Config, err error) { + labels map[string]string, + user string) (config *container.Config, err error) { envVariablesSlice := make([]string, 0, len(envVariables)) for key, val := range envVariables { @@ -1808,7 +1815,7 @@ func (manager *DockerManager) getContainerCfg( nodeConfigPtr := &container.Config{ Hostname: "", Domainname: "", - User: "", + User: user, AttachStdin: isInteractiveMode, // Analogous to `-a STDIN` option to `docker run` AttachStdout: isInteractiveMode, // Analogous to `-a STDOUT` option to `docker run` AttachStderr: isInteractiveMode, // Analogous to `-a STDERR` option to `docker run` diff --git a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/user_services_functions/start_user_services.go b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/user_services_functions/start_user_services.go index a9027be499..8d85648b92 100644 --- a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/user_services_functions/start_user_services.go +++ b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/user_services_functions/start_user_services.go @@ -3,6 +3,7 @@ package user_services_functions import ( "context" "fmt" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_user" "strings" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/consts" @@ -306,6 +307,7 @@ func createStartServiceOperation( privateIPAddrPlaceholder := serviceConfig.GetPrivateIPAddrPlaceholder() minCpuAllocationMilliCpus := serviceConfig.GetMinCPUAllocationMillicpus() minMemoryAllocationMegabytes := serviceConfig.GetMinMemoryAllocationMegabytes() + user := serviceConfig.GetUser() matchingObjectAndResources, found := servicesObjectsAndResources[serviceUuid] if !found { @@ -396,6 +398,7 @@ func createStartServiceOperation( memoryAllocationMegabytes, minCpuAllocationMilliCpus, minMemoryAllocationMegabytes, + user, ) if err != nil { return nil, stacktrace.Propagate(err, "An error occurred creating the container specs for the user service pod with image '%v'", containerImageName) @@ -632,6 +635,7 @@ func getUserServicePodContainerSpecs( memoryAllocationMegabytes uint64, minCpuAllocationMilliCpus uint64, minMemoryAllocationMegabytes uint64, + user *service_user.ServiceUser, ) ([]apiv1.Container, error) { var containerEnvVars []apiv1.EnvVar @@ -692,6 +696,22 @@ func getUserServicePodContainerSpecs( }, } + if user != nil { + uid := int64(user.GetUID()) + // nolint: exhaustruct + securityContext := &apiv1.SecurityContext{ + RunAsUser: &uid, + } + + gid, gidIsSet := user.GetGID() + if gidIsSet { + gidAsInt64 := int64(gid) + securityContext.RunAsGroup = &gidAsInt64 + } + + containers[0].SecurityContext = securityContext + } + return containers, nil } diff --git a/container-engine-lib/lib/backend_interface/objects/service/service_config.go b/container-engine-lib/lib/backend_interface/objects/service/service_config.go index 94a684aa86..7952b76fcc 100644 --- a/container-engine-lib/lib/backend_interface/objects/service/service_config.go +++ b/container-engine-lib/lib/backend_interface/objects/service/service_config.go @@ -5,6 +5,7 @@ import ( "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_directory" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_user" "github.com/kurtosis-tech/stacktrace" ) @@ -48,6 +49,8 @@ type privateServiceConfig struct { MinMemoryAllocationMegabytes uint64 Labels map[string]string + + User *service_user.ServiceUser } func CreateServiceConfig( @@ -66,6 +69,7 @@ func CreateServiceConfig( minCpuMilliCores uint64, minMemoryMegaBytes uint64, labels map[string]string, + user *service_user.ServiceUser, ) (*ServiceConfig, error) { if err := ValidateServiceConfigLabels(labels); err != nil { @@ -89,6 +93,7 @@ func CreateServiceConfig( MinCpuAllocationMilliCpus: minCpuMilliCores, MinMemoryAllocationMegabytes: minMemoryMegaBytes, Labels: labels, + User: user, } return &ServiceConfig{internalServiceConfig}, nil } @@ -151,6 +156,10 @@ func (serviceConfig *ServiceConfig) GetMinMemoryAllocationMegabytes() uint64 { return serviceConfig.privateServiceConfig.MinMemoryAllocationMegabytes } +func (serviceConfig *ServiceConfig) GetUser() *service_user.ServiceUser { + return serviceConfig.privateServiceConfig.User +} + func (serviceConfig *ServiceConfig) GetLabels() map[string]string { return serviceConfig.privateServiceConfig.Labels } diff --git a/container-engine-lib/lib/backend_interface/objects/service/service_config_test.go b/container-engine-lib/lib/backend_interface/objects/service/service_config_test.go index d5a6ad0fb5..92fecd44c2 100644 --- a/container-engine-lib/lib/backend_interface/objects/service/service_config_test.go +++ b/container-engine-lib/lib/backend_interface/objects/service/service_config_test.go @@ -76,6 +76,7 @@ func getServiceConfigForTest(t *testing.T, imageName string) *ServiceConfig { "test-label-key": "test-label-value", "test-second-label-key": "test-second-label-value", }, + nil, ) require.NoError(t, err) return serviceConfig diff --git a/container-engine-lib/lib/backend_interface/objects/service_user/service_user.go b/container-engine-lib/lib/backend_interface/objects/service_user/service_user.go new file mode 100644 index 0000000000..521903046f --- /dev/null +++ b/container-engine-lib/lib/backend_interface/objects/service_user/service_user.go @@ -0,0 +1,46 @@ +package service_user + +import ( + "fmt" +) + +type UID int64 +type GID int64 + +const ( + defaultGidValue = -1 + gidIsNotSet = false +) + +type ServiceUser struct { + uid UID + gid GID + isGIDSet bool +} + +func NewServiceUser(uid UID) *ServiceUser { + return &ServiceUser{uid: uid, gid: defaultGidValue, isGIDSet: gidIsNotSet} +} + +func (su *ServiceUser) GetUID() UID { + return su.uid +} + +func (su *ServiceUser) GetGID() (GID, bool) { + if !su.isGIDSet { + return 0, false + } + return su.gid, true +} + +func (su *ServiceUser) SetGID(gid GID) { + su.gid = gid + su.isGIDSet = true +} + +func (su *ServiceUser) GetUIDGIDPairAsStr() string { + if su.isGIDSet { + return fmt.Sprintf("%v:%v", su.uid, su.gid) + } + return fmt.Sprintf("%v", su.uid) +} diff --git a/container-engine-lib/lib/database_accessors/enclave_db/service_registration/repository_test.go b/container-engine-lib/lib/database_accessors/enclave_db/service_registration/repository_test.go index b7201e3637..880425a762 100644 --- a/container-engine-lib/lib/database_accessors/enclave_db/service_registration/repository_test.go +++ b/container-engine-lib/lib/database_accessors/enclave_db/service_registration/repository_test.go @@ -321,6 +321,7 @@ func getServiceConfigForTest(t *testing.T, imageName string) *service.ServiceCon "test-label-key": "test-label-value", "test-second-label-key": "test-second-label-value", }, + nil, ) require.NoError(t, err) return serviceConfig diff --git a/core/server/api_container/server/docker_compose_transpiler/docker_compose_transpiler.go b/core/server/api_container/server/docker_compose_transpiler/docker_compose_transpiler.go index 04e46a6cac..1ff690e476 100644 --- a/core/server/api_container/server/docker_compose_transpiler/docker_compose_transpiler.go +++ b/core/server/api_container/server/docker_compose_transpiler/docker_compose_transpiler.go @@ -178,6 +178,7 @@ func createStarlarkScript( return script, nil } +// TODO add support for User here // Turns DockerCompose Service into Kurtosis ServiceConfigs and returns info needed for creating a valid starlark script func convertComposeServicesToStarlarkInfo(composeServices types.Services) ( map[string]StarlarkServiceConfig, // Map of service names to Kurtosis ServiceConfig's diff --git a/core/server/api_container/server/service_network/default_service_network_test.go b/core/server/api_container/server/service_network/default_service_network_test.go index 7199520e88..4cf738dee5 100644 --- a/core/server/api_container/server/service_network/default_service_network_test.go +++ b/core/server/api_container/server/service_network/default_service_network_test.go @@ -1220,6 +1220,7 @@ func testServiceConfig(t *testing.T, imageName string) *service.ServiceConfig { 0, 0, map[string]string{}, + nil, ) require.NoError(t, err) return serviceConfig diff --git a/core/server/api_container/server/startosis_engine/kurtosis_builtins.go b/core/server/api_container/server/startosis_engine/kurtosis_builtins.go index 807e0bc14e..9c8e728675 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_builtins.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_builtins.go @@ -112,5 +112,6 @@ func KurtosisTypeConstructors() []*starlark.Builtin { starlark.NewBuiltin(service_config.ServiceConfigTypeName, service_config.NewServiceConfigType().CreateBuiltin()), starlark.NewBuiltin(service_config.ReadyConditionTypeName, service_config.NewReadyConditionType().CreateBuiltin()), starlark.NewBuiltin(service_config.ImageBuildSpecTypeName, service_config.NewImageBuildSpecType().CreateBuiltin()), + starlark.NewBuiltin(service_config.UserTypeName, service_config.NewUserType().CreateBuiltin()), } } diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go index 5b35126d8d..1505bbce3b 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go @@ -212,6 +212,7 @@ func replaceMagicStrings( serviceConfig.GetMinCPUAllocationMillicpus(), serviceConfig.GetMinMemoryAllocationMegabytes(), serviceConfig.GetLabels(), + serviceConfig.GetUser(), ) if err != nil { return "", nil, stacktrace.Propagate(err, "An error occurred creating a service config") diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared_test.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared_test.go index caa4f96d33..b5c39f0b11 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared_test.go @@ -52,6 +52,7 @@ func TestAddServiceShared_EntryPointArgsRuntimeValueAreReplaced(t *testing.T) { 0, 0, map[string]string{}, + nil, ) require.NoError(t, err) @@ -94,6 +95,7 @@ func TestAddServiceShared_CmdArgsRuntimeValueAreReplaced(t *testing.T) { 0, 0, map[string]string{}, + nil, ) require.NoError(t, err) @@ -138,6 +140,7 @@ func TestAddServiceShared_EnvVarsWithRuntimeValueAreReplaced(t *testing.T) { 0, 0, map[string]string{}, + nil, ) require.NoError(t, err) @@ -183,6 +186,7 @@ func TestAddServiceShared_ServiceNameWithRuntimeValuesAreReplaced(t *testing.T) 0, 0, map[string]string{}, + nil, ) require.NoError(t, err) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/tasks_shared.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/tasks_shared.go index f10d1c7cda..85dc263b06 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/tasks_shared.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/tasks_shared.go @@ -270,6 +270,7 @@ func getServiceConfig(image string, filesArtifactExpansion *service_directory.Fi 0, 0, map[string]string{}, + nil, ) if err != nil { return nil, stacktrace.Propagate(err, "An error occurred creating service config") diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_service_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_service_framework_test.go index 8684f78f36..ffa912ae43 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_service_framework_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_service_framework_test.go @@ -48,6 +48,7 @@ func (suite *KurtosisPlanInstructionTestSuite) TestAddService() { 0, 0, map[string]string{}, + nil, ) require.NoError(suite.T(), err) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_services_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_services_framework_test.go index 2462fb3634..2f76d1eb83 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_services_framework_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_services_framework_test.go @@ -64,6 +64,7 @@ func (suite *KurtosisPlanInstructionTestSuite) TestAddServices() { 0, 0, map[string]string{}, + nil, ) require.NoError(suite.T(), err) @@ -86,6 +87,7 @@ func (suite *KurtosisPlanInstructionTestSuite) TestAddServices() { 0, 0, map[string]string{}, + nil, ) require.NoError(suite.T(), err) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_image_build_spec_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_image_build_spec_test.go index fc124e5199..3a79323692 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_image_build_spec_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_image_build_spec_test.go @@ -83,6 +83,7 @@ func (t *serviceConfigImageBuildSpecTestCase) Assert(typeValue builtin_argument. 0, 0, map[string]string{}, + nil, ) require.NoError(t, err) require.Equal(t, expectedServiceConfig, serviceConfig) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_minimal_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_minimal_framework_test.go index 6972a07ef9..e48ea20984 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_minimal_framework_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_minimal_framework_test.go @@ -60,6 +60,7 @@ func (t *serviceConfigMinimalTestCase) Assert(typeValue builtin_argument.Kurtosi 0, 0, map[string]string{}, + nil, ) require.NoError(t, err) require.Equal(t, expectedServiceConfig, serviceConfig) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/service_config.go b/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/service_config.go index 298c12f65e..df8511012e 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/service_config.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/service_config.go @@ -6,6 +6,7 @@ import ( "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_directory" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_user" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/uuid_generator" "github.com/kurtosis-tech/kurtosis/core/files_artifacts_expander/args" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" @@ -42,6 +43,7 @@ const ( MaxCpuMilliCoresAttr = "max_cpu" MaxMemoryMegaBytesAttr = "max_memory" LabelsAttr = "labels" + UserAttr = "user" DefaultPrivateIPAddrPlaceholder = "KURTOSIS_IP_ADDR_PLACEHOLDER" @@ -180,6 +182,12 @@ func NewServiceConfigType() *kurtosis_type_constructor.KurtosisTypeConstructor { return builtin_argument.ServiceConfigLabels(value, LabelsAttr) }, }, + { + Name: UserAttr, + IsOptional: true, + ZeroValueProvider: builtin_argument.ZeroValueProvider[*User], + Validator: nil, + }, }, }, @@ -424,6 +432,26 @@ func (config *ServiceConfig) ToKurtosisType( } } + var serviceUser *service_user.ServiceUser + user, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[*User](config.KurtosisValueTypeDefault, UserAttr) + if interpretationErr != nil { + return nil, interpretationErr + } + if found { + uid, interpretationErr := user.GetUID() + if interpretationErr != nil { + return nil, interpretationErr + } + serviceUser = service_user.NewServiceUser(service_user.UID(uid)) + gid, gidFound, interpretationErr := user.GetGIDIfSet() + if interpretationErr != nil { + return nil, interpretationErr + } + if gidFound { + serviceUser.SetGID(service_user.GID(gid)) + } + } + serviceConfig, err := service.CreateServiceConfig( imageName, maybeImageBuildSpec, @@ -440,6 +468,7 @@ func (config *ServiceConfig) ToKurtosisType( minCpu, minMemory, labels, + serviceUser, ) if err != nil { return nil, startosis_errors.WrapWithInterpretationError(err, "An error occurred creating a service config") diff --git a/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/service_user.go b/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/service_user.go new file mode 100644 index 0000000000..caac650586 --- /dev/null +++ b/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/service_user.go @@ -0,0 +1,103 @@ +package service_config + +import ( + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_type_constructor" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_errors" + "go.starlark.net/starlark" + "math" +) + +const ( + UserTypeName = "User" + + UIDAttr = "uid" + GIDAttr = "gid" + + idIsAtLeast0 = 0 +) + +func NewUserType() *kurtosis_type_constructor.KurtosisTypeConstructor { + return &kurtosis_type_constructor.KurtosisTypeConstructor{ + KurtosisBaseBuiltin: &kurtosis_starlark_framework.KurtosisBaseBuiltin{ + Name: UserTypeName, + Arguments: []*builtin_argument.BuiltinArgument{ + { + Name: UIDAttr, + IsOptional: false, + ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.Int], + Validator: func(value starlark.Value) *startosis_errors.InterpretationError { + return builtin_argument.Int64InRange(value, UIDAttr, idIsAtLeast0, math.MaxInt64) + }, + }, + { + Name: GIDAttr, + IsOptional: true, + ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.Int], + Validator: func(value starlark.Value) *startosis_errors.InterpretationError { + return builtin_argument.Int64InRange(value, GIDAttr, idIsAtLeast0, math.MaxInt64) + }, + }, + }, + Deprecation: nil, + }, + Instantiate: instantiate, + } +} + +func instantiate(arguments *builtin_argument.ArgumentValuesSet) (builtin_argument.KurtosisValueType, *startosis_errors.InterpretationError) { + kurtosisValueType, interpretationErr := kurtosis_type_constructor.CreateKurtosisStarlarkTypeDefault(UserTypeName, arguments) + if interpretationErr != nil { + return nil, interpretationErr + } + return &User{ + kurtosisValueType, + }, nil +} + +type User struct { + *kurtosis_type_constructor.KurtosisValueTypeDefault +} + +func (user *User) Copy() (builtin_argument.KurtosisValueType, error) { + copiedValueType, err := user.KurtosisValueTypeDefault.Copy() + if err != nil { + return nil, err + } + return &User{ + KurtosisValueTypeDefault: copiedValueType, + }, nil +} + +func (user *User) GetUID() (int64, *startosis_errors.InterpretationError) { + uidValue, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.Int]( + user.KurtosisValueTypeDefault, UIDAttr) + if interpretationErr != nil { + return 0, interpretationErr + } + if !found { + return 0, startosis_errors.NewInterpretationError("Required attribute '%v' couldn't be found on '%v' type", UIDAttr, UserTypeName) + } + uid, ok := uidValue.Int64() + if !ok { + return 0, startosis_errors.NewInterpretationError("Couldn't convert uid '%v' to int64", uidValue) + } + return uid, nil +} + +func (user *User) GetGIDIfSet() (int64, bool, *startosis_errors.InterpretationError) { + gidValue, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.Int]( + user.KurtosisValueTypeDefault, GIDAttr) + if interpretationErr != nil { + return 0, false, interpretationErr + } + if !found { + return 0, false, nil + } + gid, ok := gidValue.Int64() + if !ok { + return 0, false, startosis_errors.NewInterpretationError("Couldn't convert gid '%v' to int64", gidValue) + } + return gid, true, nil +} diff --git a/docs/docs/api-reference/starlark-reference/service-config.md b/docs/docs/api-reference/starlark-reference/service-config.md index 0805b1451f..7f8158165f 100644 --- a/docs/docs/api-reference/starlark-reference/service-config.md +++ b/docs/docs/api-reference/starlark-reference/service-config.md @@ -131,6 +131,13 @@ config = ServiceConfig( # Examples "name": "alice", } + + # The user id and group id to start the container with + # Some containers are configured to start with users other than root by default; this allows you to override to root or other + # users if you choose to. + # Note that the `gid` field is optional + # OPTIONAL + user = User(uid=0, gid=0), ) ``` Note that `ImageBuildSpec` can only be used in packages and not standalone scripts as it relies on build context in package. @@ -179,10 +186,13 @@ labels: ``` ::: +The `user` field expects a `User`[user] object being passed. + [add-service-reference]: ./plan.md#add_service [directory]: ./directory.md [port-spec]: ./port-spec.md [ready-condition]: ./ready-condition.md [locators]: ../../advanced-concepts/locators.md -[package]: ../../advanced-concepts/packages.md \ No newline at end of file +[package]: ../../advanced-concepts/packages.md +[user]: ./user.md \ No newline at end of file diff --git a/docs/docs/api-reference/starlark-reference/user.md b/docs/docs/api-reference/starlark-reference/user.md new file mode 100644 index 0000000000..15391fbb58 --- /dev/null +++ b/docs/docs/api-reference/starlark-reference/user.md @@ -0,0 +1,19 @@ +--- +title: User +sidebar_label: User +--- + +The `User` constructor creates a `User` object that represents a `uid` and `gid` that the service starts with(see `ServiceConfig`[service-config] object) + +```python +user = User(uid=0) + +# or + +user = User(uid=0, gid=0) +``` + +Note that the `gid` is completely optional. + + +[service-config]: ./service-config.md \ No newline at end of file diff --git a/internal_testsuites/golang/testsuite/startlark_user_passing_test/starlark_user_passing_test.go b/internal_testsuites/golang/testsuite/startlark_user_passing_test/starlark_user_passing_test.go new file mode 100644 index 0000000000..b41104f7e7 --- /dev/null +++ b/internal_testsuites/golang/testsuite/startlark_user_passing_test/starlark_user_passing_test.go @@ -0,0 +1,75 @@ +package startlark_user_passing_test + +import ( + "context" + "github.com/kurtosis-tech/kurtosis-cli/golang_internal_testsuite/test_helpers" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" + "testing" +) + +const ( + testName = "starlark-user-passing-test" + noOverrideServiceName = "no-override" + userOverrideServiceName = "user-override" + + starlarkScriptWithUserIdPassed = ` +IMAGE = "hyperledger/besu:latest" +def run(plan, args): + no_override = plan.add_service( + name = "` + noOverrideServiceName + `", + config = ServiceConfig( + image = IMAGE, + cmd = ["tail", "-f", "/dev/null"], + ) + ) + + plan.exec(service_name = no_override.name, recipe = ExecRecipe(command = ["whoami"])) + + root_override = plan.add_service( + name = "` + userOverrideServiceName + `", + config = ServiceConfig( + image = IMAGE, + cmd = ["tail", "-f", "/dev/null"], + user = User(uid=0, gid=0), + ) + ) + + plan.exec(service_name = root_override.name, recipe = ExecRecipe(command = ["whoami"])) +` +) + +func TestUserIDOverridesWork(t *testing.T) { + ctx := context.Background() + // ------------------------------------- ENGINE SETUP ---------------------------------------------- + enclaveCtx, _, destroyEnclaveFunc, err := test_helpers.CreateEnclave(t, ctx, testName) + require.NoError(t, err, "An error occurred creating an enclave") + defer func() { + err = destroyEnclaveFunc() + require.NoError(t, err, "An error occurred destroying the enclave after the test finished") + }() + + // ------------------------------------- TEST RUN ---------------------------------------------- + scriptRunResult, err := test_helpers.RunScriptWithDefaultConfig(ctx, enclaveCtx, starlarkScriptWithUserIdPassed) + logrus.Infof("Test Output: %v", scriptRunResult) + require.NoError(t, err, "Unexpected error executing starlark script") + require.Nil(t, scriptRunResult.InterpretationError, "Unexpected interpretation error") + require.Empty(t, scriptRunResult.ValidationErrors, "Unexpected validation error") + require.Nil(t, scriptRunResult.ExecutionError, "Unexpected execution error") + expectedOutput := `Service '` + noOverrideServiceName + `' added with service UUID '[a-z0-9]{32}' +Command returned with exit code '0' and the following output: +-------------------- +besu + +-------------------- +Service '` + userOverrideServiceName + `' added with service UUID '[a-z0-9]{32}' +Command returned with exit code '0' and the following output: +-------------------- +root + +-------------------- +` + + require.Regexp(t, expectedOutput, scriptRunResult.RunOutput) + +}