diff --git a/app/gitspace/logutil/stateful_logger.go b/app/gitspace/logutil/stateful_logger.go index 33479632c..2285274e8 100644 --- a/app/gitspace/logutil/stateful_logger.go +++ b/app/gitspace/logutil/stateful_logger.go @@ -124,6 +124,10 @@ func (l *LogStreamInstance) Debug(msg string) { l.Write("DEBUG: " + msg) //nolint:errcheck } +func (l *LogStreamInstance) Warn(msg string) { + l.Write("WARN: " + msg) //nolint:errcheck +} + func (l *LogStreamInstance) Error(msg string, err error) { l.Write("ERROR: " + msg + ": " + err.Error()) //nolint:errcheck } diff --git a/app/gitspace/orchestrator/container/devcontainer_config_utils.go b/app/gitspace/orchestrator/container/devcontainer_config_utils.go index 07bbe74ea..01b8c7959 100644 --- a/app/gitspace/orchestrator/container/devcontainer_config_utils.go +++ b/app/gitspace/orchestrator/container/devcontainer_config_utils.go @@ -17,11 +17,12 @@ package container import ( "context" "fmt" + "regexp" "strings" "github.com/harness/gitness/app/gitspace/orchestrator/ide" "github.com/harness/gitness/app/gitspace/orchestrator/runarg" - types2 "github.com/harness/gitness/app/gitspace/types" + gitspaceTypes "github.com/harness/gitness/app/gitspace/types" "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" @@ -40,11 +41,11 @@ func ExtractRunArgs( } var runArgsMap = make(map[types.RunArg]*types.RunArgValue) - primaryLoopCounter := 0 - for primaryLoopCounter < len(runArgsRaw) { - currentArg := runArgsRaw[primaryLoopCounter] + argLoopCounter := 0 + for argLoopCounter < len(runArgsRaw) { + currentArg := runArgsRaw[argLoopCounter] if currentArg == "" || !isArg(currentArg) { - primaryLoopCounter++ + argLoopCounter++ continue } @@ -53,13 +54,13 @@ func ExtractRunArgs( currentRunArgDefinition, isSupportedArg := supportedRunArgsMap[types.RunArg(argKey)] if !isSupportedArg { - primaryLoopCounter++ + argLoopCounter++ continue } - updatedPrimaryLoopCounter, allowedValues, isAnyValueBlocked := getValues(runArgsRaw, argParts, - primaryLoopCounter, currentRunArgDefinition) + updatedArgLoopCounter, allowedValues, isAnyValueBlocked := getValues(runArgsRaw, argParts, argLoopCounter, + currentRunArgDefinition) - primaryLoopCounter = updatedPrimaryLoopCounter + argLoopCounter = updatedArgLoopCounter if isAnyValueBlocked && len(allowedValues) == 0 { continue @@ -84,45 +85,69 @@ func ExtractRunArgs( func getValues( runArgs []string, argParts []string, - primaryLoopCounter int, + argLoopCounter int, currentRunArgDefinition types.RunArgDefinition, ) (int, []string, bool) { values := make([]string, 0) if len(argParts) > 1 { - values = append(values, argParts[1]) - primaryLoopCounter++ + values = append(values, strings.TrimSpace(argParts[1])) + argLoopCounter++ } else { - var secondaryLoopCounter = primaryLoopCounter + 1 - for secondaryLoopCounter < len(runArgs) { - currentValue := runArgs[secondaryLoopCounter] + var valueLoopCounter = argLoopCounter + 1 + for valueLoopCounter < len(runArgs) { + currentValue := runArgs[valueLoopCounter] if isArg(currentValue) { break } - values = append(values, currentValue) - secondaryLoopCounter++ + values = append(values, strings.TrimSpace(currentValue)) + valueLoopCounter++ } - primaryLoopCounter = secondaryLoopCounter + argLoopCounter = valueLoopCounter } allowedValues, isAnyValueBlocked := filterAllowedValues(values, currentRunArgDefinition) - return primaryLoopCounter, allowedValues, isAnyValueBlocked + return argLoopCounter, allowedValues, isAnyValueBlocked } -func filterAllowedValues(values []string, currentRunArgDefinition types.RunArgDefinition) ([]string, bool) { +func filterAllowedValues( + values []string, + currentRunArgDefinition types.RunArgDefinition, +) ([]string, bool) { isAnyValueBlocked := false allowedValues := make([]string, 0) for _, v := range values { switch { case len(currentRunArgDefinition.AllowedValues) > 0: - if _, ok := currentRunArgDefinition.AllowedValues[v]; ok { - allowedValues = append(allowedValues, v) - } else { - isAnyValueBlocked = true + for allowedValue := range currentRunArgDefinition.AllowedValues { + matches, err := regexp.MatchString(allowedValue, v) + if err != nil { + log.Warn().Err(err).Msgf("error checking allowed values for RunArg %s value %s", + currentRunArgDefinition.Name, v) + continue + } + if matches { + allowedValues = append(allowedValues, v) + } else { + log.Warn().Msgf("Value %s for runArg %s not allowed", v, currentRunArgDefinition.Name) + isAnyValueBlocked = true + } } case len(currentRunArgDefinition.BlockedValues) > 0: - if _, ok := currentRunArgDefinition.BlockedValues[v]; !ok { + var isValueBlocked = false + for blockedValue := range currentRunArgDefinition.BlockedValues { + matches, err := regexp.MatchString(blockedValue, v) + if err != nil { + log.Warn().Err(err).Msgf("error checking blocked values for RunArg %s value %s", + currentRunArgDefinition.Name, v) + continue + } + if matches { + log.Warn().Msgf("Value %s for runArg %s not allowed", v, currentRunArgDefinition.Name) + isValueBlocked = true + isAnyValueBlocked = true + } + } + if !isValueBlocked { allowedValues = append(allowedValues, v) - } else { - isAnyValueBlocked = true } default: allowedValues = append(allowedValues, v) @@ -176,7 +201,7 @@ func ExtractIDECustomizations( var args = make(map[string]interface{}) if ideService.Type() == enum.IDETypeVSCodeWeb || ideService.Type() == enum.IDETypeVSCode { if devcontainerConfig.Customizations.ExtractVSCodeSpec() != nil { - args[types2.VSCodeCustomization] = *devcontainerConfig.Customizations.ExtractVSCodeSpec() + args[gitspaceTypes.VSCodeCustomization] = *devcontainerConfig.Customizations.ExtractVSCodeSpec() } } return args diff --git a/app/gitspace/orchestrator/container/devcontainer_container_utils.go b/app/gitspace/orchestrator/container/devcontainer_container_utils.go index d6a3cc799..5998138be 100644 --- a/app/gitspace/orchestrator/container/devcontainer_container_utils.go +++ b/app/gitspace/orchestrator/container/devcontainer_container_utils.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "io" + "path/filepath" goruntime "runtime" "strconv" "strings" @@ -136,6 +137,8 @@ func CreateContainer( portMappings map[int]*types.PortMapping, env []string, runArgsMap map[types.RunArg]*types.RunArgValue, + containerUser string, + remoteUser string, ) error { exposedPorts, portBindings := applyPortMappings(portMappings) @@ -161,6 +164,10 @@ func CreateContainer( cmd = []string{"-c", "trap 'exit 0' 15; sleep infinity & wait $!"} } + labels := getLabels(runArgsMap) + // Setting the following so that it can be read later to form gitspace URL. + labels[gitspaceRemoteUserLabel] = remoteUser + // Create the container containerConfig := &container.Config{ Hostname: getHostname(runArgsMap), @@ -170,12 +177,12 @@ func CreateContainer( Entrypoint: entrypoint, Cmd: cmd, ExposedPorts: exposedPorts, - Labels: getLabels(runArgsMap), + Labels: labels, Healthcheck: healthCheckConfig, MacAddress: getMACAddress(runArgsMap), StopSignal: getStopSignal(runArgsMap), StopTimeout: stopTimeout, - User: getUser(runArgsMap), + User: containerUser, } _, err = dockerClient.ContainerCreate(ctx, containerConfig, hostConfig, nil, nil, containerName) @@ -278,10 +285,10 @@ func GetContainerInfo( containerName string, dockerClient *client.Client, portMappings map[int]*types.PortMapping, -) (string, map[int]string, error) { +) (string, map[int]string, string, error) { inspectResp, err := dockerClient.ContainerInspect(ctx, containerName) if err != nil { - return "", nil, fmt.Errorf("could not inspect container %s: %w", containerName, err) + return "", nil, "", fmt.Errorf("could not inspect container %s: %w", containerName, err) } usedPorts := make(map[int]string) @@ -289,7 +296,7 @@ func GetContainerInfo( portRaw := strings.Split(string(portAndProtocol), "/")[0] port, conversionErr := strconv.Atoi(portRaw) if conversionErr != nil { - return "", nil, fmt.Errorf("could not convert port %s to int: %w", portRaw, conversionErr) + return "", nil, "", fmt.Errorf("could not convert port %s to int: %w", portRaw, conversionErr) } if portMappings[port] != nil { @@ -297,7 +304,34 @@ func GetContainerInfo( } } - return inspectResp.ID, usedPorts, nil + remoteUser := ExtractRemoteUserFromLabels(inspectResp) + + return inspectResp.ID, usedPorts, remoteUser, nil +} + +func ExtractMetadataFromImage( + ctx context.Context, + imageName string, + dockerClient *client.Client, +) (map[string]any, error) { + imageInspect, _, err := dockerClient.ImageInspectWithRaw(ctx, imageName) + if err != nil { + return nil, fmt.Errorf("error while inspecting image: %w", err) + } + metadataMap := map[string]any{} + if metadata, ok := imageInspect.Config.Labels["devcontainer.metadata"]; ok { + dst := []map[string]any{} + unmarshalErr := json.Unmarshal([]byte(metadata), &dst) + if unmarshalErr != nil { + return nil, fmt.Errorf("error while unmarshalling metadata: %w", err) + } + for _, values := range dst { + for k, v := range values { + metadataMap[k] = v + } + } + } + return metadataMap, nil } func PullImage( @@ -429,7 +463,6 @@ func ExtractRunArgsWithLogging( runArgsRaw []string, gitspaceLogger gitspaceTypes.GitspaceLogger, ) (map[types.RunArg]*types.RunArgValue, error) { - gitspaceLogger.Info("Extracting runsArgs") runArgsMap, err := ExtractRunArgs(ctx, spaceID, runArgProvider, runArgsRaw) if err != nil { return nil, logStreamWrapError(gitspaceLogger, "Error while extracting runArgs", err) @@ -439,7 +472,9 @@ func ExtractRunArgsWithLogging( for key, value := range runArgsMap { st = fmt.Sprintf("%s%s: %s\n", st, key, value) } - gitspaceLogger.Info(fmt.Sprintf("Extracted following runArgs\n%v", st)) + gitspaceLogger.Info(fmt.Sprintf("Using the following runArgs\n%v", st)) + } else { + gitspaceLogger.Info("No runArgs found") } return runArgsMap, nil } @@ -450,20 +485,38 @@ func GetContainerResponse( dockerClient *client.Client, containerName string, portMappings map[int]*types.PortMapping, - codeRepoDir string, + repoName string, ) (*StartResponse, error) { - id, ports, err := GetContainerInfo(ctx, containerName, dockerClient, portMappings) + id, ports, remoteUser, err := GetContainerInfo(ctx, containerName, dockerClient, portMappings) if err != nil { return nil, err } + + homeDir := GetUserHomeDir(remoteUser) + codeRepoDir := filepath.Join(homeDir, repoName) + return &StartResponse{ ContainerID: id, ContainerName: containerName, PublishedPorts: ports, AbsoluteRepoPath: codeRepoDir, + RemoteUser: remoteUser, }, nil } +func GetRemoteUserFromContainerLabel( + ctx context.Context, + containerName string, + dockerClient *client.Client, +) (string, error) { + inspectResp, err := dockerClient.ContainerInspect(ctx, containerName) + if err != nil { + return "", fmt.Errorf("could not inspect container %s: %w", containerName, err) + } + + return ExtractRemoteUserFromLabels(inspectResp), nil +} + // Helper function to encode the AuthConfig into a Base64 string. func encodeAuthToBase64(authConfig registry.AuthConfig) (string, error) { authJSON, err := json.Marshal(authConfig) diff --git a/app/gitspace/orchestrator/container/embedded_docker_container_orchestrator.go b/app/gitspace/orchestrator/container/embedded_docker_container_orchestrator.go index 9829ff8a8..020059b53 100644 --- a/app/gitspace/orchestrator/container/embedded_docker_container_orchestrator.go +++ b/app/gitspace/orchestrator/container/embedded_docker_container_orchestrator.go @@ -159,11 +159,8 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace( return nil, fmt.Errorf("gitspace %s is in a bad state: %s", containerName, state) } - homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier) - codeRepoDir := filepath.Join(homeDir, resolvedRepoDetails.RepoName) - // Step 5: Retrieve container information and return response - return GetContainerResponse(ctx, dockerClient, containerName, infra.GitspacePortMappings, codeRepoDir) + return GetContainerResponse(ctx, dockerClient, containerName, infra.GitspacePortMappings, resolvedRepoDetails.RepoName) } // startStoppedGitspace starts the Gitspace container if it was stopped. @@ -179,25 +176,33 @@ func (e *EmbeddedDockerOrchestrator) startStoppedGitspace( containerName := GetGitspaceContainerName(gitspaceConfig) if err != nil { - return fmt.Errorf("error getting log stream for gitspace ID %d: %w", gitspaceConfig.ID, err) + return fmt.Errorf("error getting log stream for gitspace instance %s: %w", + gitspaceConfig.GitspaceInstance.Identifier, err) } defer e.flushLogStream(logStreamInstance, gitspaceConfig.ID) + remoteUser, err := GetRemoteUserFromContainerLabel(ctx, containerName, dockerClient) + if err != nil { + return fmt.Errorf("error getting remote user for gitspace instance %s: %w", + gitspaceConfig.GitspaceInstance.Identifier, err) + } + + homeDir := GetUserHomeDir(remoteUser) + startErr := ManageContainer(ctx, ContainerActionStart, containerName, dockerClient, logStreamInstance) if startErr != nil { return startErr } - homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier) codeRepoDir := filepath.Join(homeDir, resolvedRepoDetails.RepoName) exec := &devcontainer.Exec{ - ContainerName: containerName, - DockerClient: dockerClient, - HomeDir: homeDir, - UserIdentifier: gitspaceConfig.GitspaceUser.Identifier, - AccessKey: accessKey, - AccessType: gitspaceConfig.GitspaceInstance.AccessType, + ContainerName: containerName, + DockerClient: dockerClient, + HomeDir: homeDir, + RemoteUser: remoteUser, + AccessKey: accessKey, + AccessType: gitspaceConfig.GitspaceInstance.AccessType, } // Set up git credentials if needed @@ -376,14 +381,10 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps( gitspaceLogger gitspaceTypes.GitspaceLogger, imageAuthMap map[string]gitspaceTypes.DockerRegistryAuth, ) error { - homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier) containerName := GetGitspaceContainerName(gitspaceConfig) devcontainerConfig := resolvedRepoDetails.DevcontainerConfig - imageName := devcontainerConfig.Image - if imageName == "" { - imageName = defaultBaseImage - } + imageName := GetImage(devcontainerConfig, defaultBaseImage) runArgsMap, err := ExtractRunArgsWithLogging(ctx, gitspaceConfig.SpaceID, e.runArgProvider, devcontainerConfig.RunArgs, gitspaceLogger) @@ -395,6 +396,12 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps( if err := PullImage(ctx, imageName, dockerClient, runArgsMap, gitspaceLogger, imageAuthMap); err != nil { return err } + + metadataFromImage, err := ExtractMetadataFromImage(ctx, imageName, dockerClient) + if err != nil { + return err + } + portMappings := infrastructure.GitspacePortMappings forwardPorts := ExtractForwardPorts(devcontainerConfig) if len(forwardPorts) > 0 { @@ -412,6 +419,15 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps( if len(environment) > 0 { gitspaceLogger.Info(fmt.Sprintf("Setting Environment : %v", environment)) } + + containerUser := GetContainerUser(runArgsMap, devcontainerConfig, metadataFromImage) + remoteUser := GetRemoteUser(devcontainerConfig, metadataFromImage, containerUser) + + homeDir := GetUserHomeDir(remoteUser) + + gitspaceLogger.Info(fmt.Sprintf("Container user: %s", containerUser)) + gitspaceLogger.Info(fmt.Sprintf("Remote user: %s", remoteUser)) + // Create the container err = CreateContainer( ctx, @@ -425,6 +441,8 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps( portMappings, environment, runArgsMap, + containerUser, + remoteUser, ) if err != nil { return err @@ -437,12 +455,12 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps( // Setup and run commands exec := &devcontainer.Exec{ - ContainerName: containerName, - DockerClient: dockerClient, - HomeDir: homeDir, - UserIdentifier: gitspaceConfig.GitspaceUser.Identifier, - AccessKey: *gitspaceConfig.GitspaceInstance.AccessKey, - AccessType: gitspaceConfig.GitspaceInstance.AccessType, + ContainerName: containerName, + DockerClient: dockerClient, + HomeDir: homeDir, + RemoteUser: remoteUser, + AccessKey: *gitspaceConfig.GitspaceInstance.AccessKey, + AccessType: gitspaceConfig.GitspaceInstance.AccessType, } if err := e.setupGitspaceAndIDE( diff --git a/app/gitspace/orchestrator/container/runarg_utils.go b/app/gitspace/orchestrator/container/runarg_utils.go index 65e1369ea..900012ce5 100644 --- a/app/gitspace/orchestrator/container/runarg_utils.go +++ b/app/gitspace/orchestrator/container/runarg_utils.go @@ -372,8 +372,8 @@ func getHealthCheckConfig(runArgsMap map[types.RunArg]*types.RunArgValue) (*cont } func getLabels(runArgsMap map[types.RunArg]*types.RunArgValue) map[string]string { - arg, ok := runArgsMap[types.RunArgLabel] labelsMap := make(map[string]string) + arg, ok := runArgsMap[types.RunArgLabel] if ok { labels := arg.Values for _, v := range labels { diff --git a/app/gitspace/orchestrator/container/types.go b/app/gitspace/orchestrator/container/types.go index bfb0e91ac..60ca6a86e 100644 --- a/app/gitspace/orchestrator/container/types.go +++ b/app/gitspace/orchestrator/container/types.go @@ -19,6 +19,7 @@ type StartResponse struct { ContainerName string PublishedPorts map[int]string AbsoluteRepoPath string + RemoteUser string } type PostAction string diff --git a/app/gitspace/orchestrator/container/util.go b/app/gitspace/orchestrator/container/util.go index ce9cb16be..b0ba67466 100644 --- a/app/gitspace/orchestrator/container/util.go +++ b/app/gitspace/orchestrator/container/util.go @@ -18,10 +18,14 @@ import ( "path/filepath" "github.com/harness/gitness/types" + + types2 "github.com/docker/docker/api/types" ) const ( - linuxHome = "/home" + linuxHome = "/home" + deprecatedRemoteUser = "harness" + gitspaceRemoteUserLabel = "gitspace.remote.user" ) func GetGitspaceContainerName(config types.GitspaceConfig) string { @@ -29,5 +33,56 @@ func GetGitspaceContainerName(config types.GitspaceConfig) string { } func GetUserHomeDir(userIdentifier string) string { + if userIdentifier == "root" { + return "/root" + } return filepath.Join(linuxHome, userIdentifier) } + +func GetImage(devcontainerConfig types.DevcontainerConfig, defaultBaseImage string) string { + imageName := devcontainerConfig.Image + if imageName == "" { + imageName = defaultBaseImage + } + return imageName +} + +func GetContainerUser( + runArgsMap map[types.RunArg]*types.RunArgValue, + devcontainerConfig types.DevcontainerConfig, + metadataFromImage map[string]any, +) string { + if containerUser := getUser(runArgsMap); containerUser != "" { + return containerUser + } + if devcontainerConfig.ContainerUser != "" { + return devcontainerConfig.ContainerUser + } + if containerUser, ok := metadataFromImage["containerUser"].(string); ok { + return containerUser + } + return "" +} + +func ExtractRemoteUserFromLabels(inspectResp types2.ContainerJSON) string { + remoteUser := deprecatedRemoteUser + + if remoteUserValue, ok := inspectResp.Config.Labels[gitspaceRemoteUserLabel]; ok { + remoteUser = remoteUserValue + } + return remoteUser +} + +func GetRemoteUser( + devcontainerConfig types.DevcontainerConfig, + metadataFromImage map[string]any, + containerUser string, +) string { + if devcontainerConfig.RemoteUser != "" { + return devcontainerConfig.RemoteUser + } + if remoteUser, ok := metadataFromImage["remoteUser"].(string); ok { + return remoteUser + } + return containerUser +} diff --git a/app/gitspace/orchestrator/devcontainer/exec.go b/app/gitspace/orchestrator/devcontainer/exec.go index 5f98e1048..710dab5f2 100644 --- a/app/gitspace/orchestrator/devcontainer/exec.go +++ b/app/gitspace/orchestrator/devcontainer/exec.go @@ -34,12 +34,12 @@ const RootUser = "root" const ErrMsgTCP = "unable to upgrade to tcp, received 200" type Exec struct { - ContainerName string - DockerClient *client.Client - HomeDir string - UserIdentifier string - AccessKey string - AccessType enum.GitspaceAccessType + ContainerName string + DockerClient *client.Client + HomeDir string + RemoteUser string + AccessKey string + AccessType enum.GitspaceAccessType } type execResult struct { @@ -56,7 +56,7 @@ func (e *Exec) ExecuteCommand( workingDir string, outputCh chan []byte, // channel to stream output as []byte ) error { - user := e.UserIdentifier + user := e.RemoteUser if root { user = RootUser } diff --git a/app/gitspace/orchestrator/ide/vscode.go b/app/gitspace/orchestrator/ide/vscode.go index d25105261..328d9aaf0 100644 --- a/app/gitspace/orchestrator/ide/vscode.go +++ b/app/gitspace/orchestrator/ide/vscode.go @@ -53,7 +53,7 @@ func (v *VSCode) Setup( osInfoScript := common.GetOSInfoScript() sshServerScript, err := template.GenerateScriptFromTemplate( templateSetupSSHServer, &template.SetupSSHServerPayload{ - Username: exec.UserIdentifier, + Username: exec.RemoteUser, AccessType: exec.AccessType, OSInfoScript: osInfoScript, }) diff --git a/app/gitspace/orchestrator/orchestrator_resume.go b/app/gitspace/orchestrator/orchestrator_resume.go index 07bde11c5..69a448851 100644 --- a/app/gitspace/orchestrator/orchestrator_resume.go +++ b/app/gitspace/orchestrator/orchestrator_resume.go @@ -220,7 +220,7 @@ func generateIDEURL( Host: "", // Empty since we include the host and port in the path Path: fmt.Sprintf( "ssh-remote+%s@%s:%s", - gitspaceConfig.GitspaceUser.Identifier, + startResponse.RemoteUser, host, filepath.Join(forwardedPort, relativeRepoPath), ), diff --git a/app/gitspace/orchestrator/runarg/resolver.go b/app/gitspace/orchestrator/runarg/resolver.go new file mode 100644 index 000000000..eac7889ed --- /dev/null +++ b/app/gitspace/orchestrator/runarg/resolver.go @@ -0,0 +1,54 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package runarg + +import ( + "fmt" + + "github.com/harness/gitness/types" + + "gopkg.in/yaml.v3" + + _ "embed" +) + +//go:embed runArgs.yaml +var supportedRunArgsRaw []byte + +type Resolver struct { + supportedRunArgsMap map[types.RunArg]types.RunArgDefinition +} + +func NewResolver() (*Resolver, error) { + allRunArgs := make([]types.RunArgDefinition, 0) + err := yaml.Unmarshal(supportedRunArgsRaw, &allRunArgs) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal runArgs.yaml: %w", err) + } + argsMap := make(map[types.RunArg]types.RunArgDefinition) + for _, arg := range allRunArgs { + if arg.Supported { + argsMap[arg.Name] = arg + if arg.ShortHand != "" { + argsMap[arg.ShortHand] = arg + } + } + } + return &Resolver{supportedRunArgsMap: argsMap}, nil +} + +func (r *Resolver) ResolveSupportedRunArgs() map[types.RunArg]types.RunArgDefinition { + return r.supportedRunArgsMap +} diff --git a/app/gitspace/orchestrator/runarg/runArgs.yaml b/app/gitspace/orchestrator/runarg/runArgs.yaml index 3c7aa0545..f03e35f5c 100644 --- a/app/gitspace/orchestrator/runarg/runArgs.yaml +++ b/app/gitspace/orchestrator/runarg/runArgs.yaml @@ -2,36 +2,36 @@ - name: --add-host short_hand: supported: true - blocked_values: {} - allowed_values: {} + blocked_values: { } + allowed_values: { } allow_multiple_occurrences: true - name: --annotation short_hand: supported: true - blocked_values: {} - allowed_values: {} + blocked_values: { } + allowed_values: { } allow_multiple_occurrences: true - name: --attach short_hand: -a supported: false - blocked_values: {} - allowed_values: {} + blocked_values: { } + allowed_values: { } allow_multiple_occurrences: true - name: --blkio-weight short_hand: supported: true - blocked_values: {} - allowed_values: {} + blocked_values: { } + allowed_values: { } allow_multiple_occurrences: false - name: --blkio-weight-device short_hand: supported: false - blocked_values: {} - allowed_values: {} + blocked_values: { } + allowed_values: { } allow_multiple_occurrences: true - name: --cap-add @@ -394,15 +394,16 @@ - name: --label short_hand: -l supported: true - blocked_values: { } + blocked_values: + ^gitspace\.remote\.user=: true allowed_values: { } allow_multiple_occurrences: true - name: --label-file short_hand: supported: false - blocked_values: {} - allowed_values: {} + blocked_values: { } + allowed_values: { } allow_multiple_occurrences: true - name: --link @@ -486,8 +487,8 @@ short_hand: supported: true blocked_values: - host: true - none: true + ^host$: true + ^none$: true allowed_values: { } allow_multiple_occurrences: false diff --git a/app/gitspace/orchestrator/runarg/static_provider.go b/app/gitspace/orchestrator/runarg/static_provider.go index 26b0b40da..b30826503 100644 --- a/app/gitspace/orchestrator/runarg/static_provider.go +++ b/app/gitspace/orchestrator/runarg/static_provider.go @@ -16,40 +16,18 @@ package runarg import ( "context" - "fmt" "github.com/harness/gitness/types" - - "gopkg.in/yaml.v3" - - _ "embed" ) var _ Provider = (*StaticProvider)(nil) -//go:embed runArgs.yaml -var supportedRunArgsRaw []byte - type StaticProvider struct { supportedRunArgsMap map[types.RunArg]types.RunArgDefinition } -func NewStaticProvider() (Provider, error) { - allRunArgs := make([]types.RunArgDefinition, 0) - err := yaml.Unmarshal(supportedRunArgsRaw, &allRunArgs) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal runArgs.yaml: %w", err) - } - argsMap := make(map[types.RunArg]types.RunArgDefinition) - for _, arg := range allRunArgs { - if arg.Supported { - argsMap[arg.Name] = arg - if arg.ShortHand != "" { - argsMap[arg.ShortHand] = arg - } - } - } - return &StaticProvider{supportedRunArgsMap: argsMap}, nil +func NewStaticProvider(resolver *Resolver) (Provider, error) { + return &StaticProvider{supportedRunArgsMap: resolver.ResolveSupportedRunArgs()}, nil } // ProvideSupportedRunArgs provides a static map of supported run args. diff --git a/app/gitspace/orchestrator/runarg/wire.go b/app/gitspace/orchestrator/runarg/wire.go index ff2360f98..6f01c9b20 100644 --- a/app/gitspace/orchestrator/runarg/wire.go +++ b/app/gitspace/orchestrator/runarg/wire.go @@ -20,8 +20,12 @@ import ( var WireSet = wire.NewSet( ProvideStaticProvider, + ProvideResolver, ) -func ProvideStaticProvider() (Provider, error) { - return NewStaticProvider() +func ProvideStaticProvider(resolver *Resolver) (Provider, error) { + return NewStaticProvider(resolver) +} +func ProvideResolver() (*Resolver, error) { + return NewResolver() } diff --git a/app/gitspace/orchestrator/template/template.go b/app/gitspace/orchestrator/template/template.go index 3ff15e134..4440ca1e1 100644 --- a/app/gitspace/orchestrator/template/template.go +++ b/app/gitspace/orchestrator/template/template.go @@ -63,11 +63,10 @@ type SetupVSCodeWebPayload struct { } type SetupUserPayload struct { - Username string - AccessKey string - AccessType enum.GitspaceAccessType - HomeDir string - OSInfoScript string + Username string + AccessKey string + AccessType enum.GitspaceAccessType + HomeDir string } type SetupSSHServerPayload struct { diff --git a/app/gitspace/orchestrator/template/templates/manage_user.sh b/app/gitspace/orchestrator/template/templates/manage_user.sh index d23375fe3..f5955efbb 100644 --- a/app/gitspace/orchestrator/template/templates/manage_user.sh +++ b/app/gitspace/orchestrator/template/templates/manage_user.sh @@ -1,65 +1,41 @@ #!/bin/sh -username={{ .Username }} +username="{{ .Username }}" accessKey="{{ .AccessKey }}" -homeDir={{ .HomeDir }} +homeDir="{{ .HomeDir }}" accessType={{ .AccessType }} -osInfoScript={{ .OSInfoScript }} -eval "$osInfoScript" - -# Check if the user already exists -if id "$username" >/dev/null 2>&1; then - echo "User $username already exists." +# Check if the user's home directory exists +if [ ! -d "$homeDir" ]; then + echo "Directory $homeDir does not exist. Creating it..." + mkdir -p "$homeDir" + if [ $? -ne 0 ]; then + echo "Failed to create directory $homeDir." + exit 1 + fi else - # Create a new user - case "$(distro)" in - debian) - apt-get update && apt-get install -y adduser && apt-get install -y sudo - adduser --disabled-password --home "$homeDir" --gecos "" "$username" - usermod -aG sudo "$username" - echo "%sudo ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers - ;; - fedora) - useradd -m -d "$homeDir" "$username" - usermod -aG wheel "$username" - dnf install -y sudo - echo "%wheel ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers - ;; - opensuse) - useradd -m -d "$homeDir" "$username" - passwd -l "$username" # Locks the password to prevent login - zypper in -y sudo - groupadd sudo - usermod -aG sudo "$username" - echo "%sudo ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers - chown -R "$username":sudo "$homeDir" - ;; - alpine) - adduser -h "$homeDir" -s /bin/ash -D "$username" # Default shell is ash for Alpine - ;; - arch) - useradd -m -d "$homeDir" -s /bin/bash "$username" - ;; - freebsd) - pw useradd -n "$username" -d "$homeDir" -m - ;; - *) - echo "Unsupported distribution: $distro." - exit 1 - ;; - esac - - if [ $? -ne 0 ]; then - echo "Failed to create user $username." - exit 1 - fi + echo "Directory $homeDir already exists." fi -# Changing ownership of everything inside user home to the newly created user -chown -R $username:$username $homeDir -echo "Changing ownership of dir $homeDir to $username." -chmod 755 $homeDir +# Ensure the user has ownership and permissions to the home directory +currentOwner=$(stat -c '%U' "$homeDir") +if [ "$currentOwner" != "$username" ]; then + echo "Updating ownership of $homeDir to $username..." + chown -R "$username:$username" "$homeDir" + if [ $? -ne 0 ]; then + echo "Failed to update ownership of $homeDir." + exit 1 + fi +fi + +echo "Ensuring proper permissions for $homeDir..." +chmod 755 "$homeDir" +if [ $? -ne 0 ]; then + echo "Failed to set permissions for $homeDir." + exit 1 +fi + +echo "Directory setup for $username is complete." if [ "ssh_key" = "$accessType" ] ; then echo "Add ssh key in $homeDir/.ssh/authorized_keys" @@ -68,7 +44,6 @@ if [ "ssh_key" = "$accessType" ] ; then echo $accessKey > $homeDir/.ssh/authorized_keys chmod 600 $homeDir/.ssh/authorized_keys chown -R $username:$username $homeDir/.ssh - echo "$username:$username" | chpasswd elif [ "user_credentials" = "$accessType" ] ; then echo "$username:$accessKey" | chpasswd else diff --git a/app/gitspace/orchestrator/template/templates/setup_ssh_server.sh b/app/gitspace/orchestrator/template/templates/setup_ssh_server.sh index f57f7f999..3d16dceb3 100644 --- a/app/gitspace/orchestrator/template/templates/setup_ssh_server.sh +++ b/app/gitspace/orchestrator/template/templates/setup_ssh_server.sh @@ -47,8 +47,8 @@ config_file='/etc/ssh/sshd_config' grep -q "^AllowUsers" $config_file if [ $? -eq 0 ]; then - # If AllowUsers exists, add the user to it - sed -i "/^AllowUsers/ s/$/ $username/" $config_file + # If AllowUsers exists, overwrite all existing users with new user + sed -i "s/^AllowUsers.*/AllowUsers $username/" $config_file else # Otherwise, add a new AllowUsers line echo "AllowUsers $username" >> $config_file @@ -67,10 +67,15 @@ echo "AuthorizedKeysFile .ssh/authorized_keys" >> $config_file echo "PubkeyAuthentication yes" >> $config_file else # Ensure password authentication is enabled -sed -i 's/^PasswordAuthentication no/PasswordAuthentication yes/' $config_file if ! grep -q "^PasswordAuthentication yes" $config_file; then echo "PasswordAuthentication yes" >> $config_file fi +if ! grep -q "^PermitEmptyPasswords yes" $config_file; then + echo "PermitEmptyPasswords yes" >> $config_file +fi +if ! grep -q "^PermitRootLogin yes" $config_file; then + echo "PermitRootLogin yes" >> $config_file +fi fi mkdir -p /var/run/sshd \ No newline at end of file diff --git a/app/gitspace/orchestrator/user/user_impl.go b/app/gitspace/orchestrator/user/user_impl.go index 5bf75332b..0cdaa2b3b 100644 --- a/app/gitspace/orchestrator/user/user_impl.go +++ b/app/gitspace/orchestrator/user/user_impl.go @@ -40,28 +40,25 @@ func (u *ServiceImpl) Manage( exec *devcontainer.Exec, gitspaceLogger gitspaceTypes.GitspaceLogger, ) error { - osInfoScript := common.GetOSInfoScript() script, err := template.GenerateScriptFromTemplate( templateManagerUser, &template.SetupUserPayload{ - Username: exec.UserIdentifier, - AccessKey: exec.AccessKey, - AccessType: exec.AccessType, - HomeDir: exec.HomeDir, - OSInfoScript: osInfoScript, + Username: exec.RemoteUser, + AccessKey: exec.AccessKey, + AccessType: exec.AccessType, + HomeDir: exec.HomeDir, }) if err != nil { return fmt.Errorf( "failed to generate scipt to manager user from template %s: %w", templateManagerUser, err) } - gitspaceLogger.Info("Setting up user inside container") - gitspaceLogger.Info("Managing user output...") + gitspaceLogger.Info("Configuring user directory and credentials inside container") err = common.ExecuteCommandInHomeDirAndLog(ctx, exec, script, true, gitspaceLogger) if err != nil { return fmt.Errorf("failed to setup user: %w", err) } - gitspaceLogger.Info("Successfully setup user") + gitspaceLogger.Info("Successfully configured the user directory and credentials.") return nil } diff --git a/app/gitspace/types/types.go b/app/gitspace/types/types.go index 7ddc2ce35..c7c469f17 100644 --- a/app/gitspace/types/types.go +++ b/app/gitspace/types/types.go @@ -29,6 +29,7 @@ const VSCodeProxyURI = "VSCODE_PROXY_URI" type GitspaceLogger interface { Info(msg string) Debug(msg string) + Warn(msg string) Error(msg string, err error) } diff --git a/app/gitspace/types/zerolog_adapter.go b/app/gitspace/types/zerolog_adapter.go index cfc4fbb36..193a1f4ec 100644 --- a/app/gitspace/types/zerolog_adapter.go +++ b/app/gitspace/types/zerolog_adapter.go @@ -32,6 +32,10 @@ func (z *ZerologAdapter) Debug(msg string) { z.logger.Debug().Msg("DEBUG: " + msg) } +func (z *ZerologAdapter) Warn(msg string) { + z.logger.Warn().Msg("WARN: " + msg) +} + func (z *ZerologAdapter) Error(msg string, err error) { z.logger.Err(err).Msg("ERROR: " + msg) } diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index 6ef6c8a00..bbea0f18d 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -319,7 +319,11 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro statefulLogger := logutil.ProvideStatefulLogger(logStream) gitService := git2.ProvideGitServiceImpl() userService := user2.ProvideUserServiceImpl() - runargProvider, err := runarg.ProvideStaticProvider() + runargResolver, err := runarg.ProvideResolver() + if err != nil { + return nil, err + } + runargProvider, err := runarg.ProvideStaticProvider(runargResolver) if err != nil { return nil, err } diff --git a/types/devcontainer_config.go b/types/devcontainer_config.go index b86b395bf..4ddb5efa1 100644 --- a/types/devcontainer_config.go +++ b/types/devcontainer_config.go @@ -27,7 +27,9 @@ type DevcontainerConfig struct { ForwardPorts []json.Number `json:"forwardPorts,omitempty"` //nolint:tagliatelle ContainerEnv map[string]string `json:"containerEnv,omitempty"` //nolint:tagliatelle Customizations DevContainerConfigCustomizations `json:"customizations,omitempty"` - RunArgs []string `json:"runArgs,omitempty"` //nolint:tagliatelle + RunArgs []string `json:"runArgs,omitempty"` //nolint:tagliatelle + ContainerUser string `json:"containerUser,omitempty"` //nolint:tagliatelle + RemoteUser string `json:"remoteUser,omitempty"` //nolint:tagliatelle } // LifecycleCommand supports multiple formats for lifecycle commands.