From 3b2db60eea412812727b81ca67b030ac097d1c87 Mon Sep 17 00:00:00 2001 From: Deepak Bhatt Date: Wed, 27 Nov 2024 10:24:33 +0000 Subject: [PATCH] feat: [CDE-508]: add image auth (#3053) * add comment * split pull image func into smaller parts * split pull image func into smaller parts * use MaskSecret type * fix lint * add image auth * Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/gitness into add-image-auth * add image auth --- .../container/devcontainer_container_utils.go | 64 ++++++++++++++++--- .../embedded_docker_container_orchestrator.go | 11 +++- app/gitspace/types/types.go | 9 +++ 3 files changed, 74 insertions(+), 10 deletions(-) diff --git a/app/gitspace/orchestrator/container/devcontainer_container_utils.go b/app/gitspace/orchestrator/container/devcontainer_container_utils.go index 01dfe8d42..ea3c49899 100644 --- a/app/gitspace/orchestrator/container/devcontainer_container_utils.go +++ b/app/gitspace/orchestrator/container/devcontainer_container_utils.go @@ -16,6 +16,7 @@ package container import ( "context" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -32,6 +33,7 @@ import ( "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/strslice" "github.com/docker/docker/client" "github.com/docker/go-connections/nat" @@ -304,6 +306,7 @@ func PullImage( dockerClient *client.Client, runArgsMap map[types.RunArg]*types.RunArgValue, gitspaceLogger gitspaceTypes.GitspaceLogger, + dockerRegistryAuth gitspaceTypes.DockerRegistryAuth, ) error { imagePullRunArg := getImagePullPolicy(runArgsMap) gitspaceLogger.Info("Image pull policy is: " + imagePullRunArg) @@ -312,26 +315,28 @@ func PullImage( } if imagePullRunArg == "missing" { gitspaceLogger.Info("Checking if image " + imageName + " is present locally") - - filterArgs := filters.NewArgs() - filterArgs.Add("reference", imageName) - - images, err := dockerClient.ImageList(ctx, image.ListOptions{Filters: filterArgs}) + ok, err := isImagePresentLocally(ctx, imageName, dockerClient) if err != nil { gitspaceLogger.Error("Error listing images locally", err) return err } - if len(images) > 0 { + if ok { gitspaceLogger.Info("Image " + imageName + " is present locally") return nil } + gitspaceLogger.Info("Image " + imageName + " is not present locally") } gitspaceLogger.Info("Pulling image: " + imageName) - pullResponse, err := dockerClient.ImagePull(ctx, imageName, image.PullOptions{Platform: getPlatform(runArgsMap)}) + pullOpts, err := buildImagePullOptions(getPlatform(runArgsMap), dockerRegistryAuth) + if err != nil { + return logStreamWrapError(gitspaceLogger, "Error building image pull options", err) + } + + pullResponse, err := dockerClient.ImagePull(ctx, imageName, pullOpts) defer func() { if pullResponse == nil { return @@ -382,6 +387,40 @@ func PullImage( return nil } +func isImagePresentLocally(ctx context.Context, imageName string, dockerClient *client.Client) (bool, error) { + filterArgs := filters.NewArgs() + filterArgs.Add("reference", imageName) + + images, err := dockerClient.ImageList(ctx, image.ListOptions{Filters: filterArgs}) + if err != nil { + return false, err + } + + return len(images) > 0, nil +} + +func buildImagePullOptions( + platform string, + dockerRegistryAuth gitspaceTypes.DockerRegistryAuth, +) (image.PullOptions, error) { + pullOpts := image.PullOptions{Platform: platform} + if dockerRegistryAuth.RegistryURL != "" { + authConfig := registry.AuthConfig{ + Username: dockerRegistryAuth.Username.Value(), + Password: dockerRegistryAuth.Password.Value(), + ServerAddress: dockerRegistryAuth.RegistryURL, + } + auth, err := encodeAuthToBase64(authConfig) + if err != nil { + return image.PullOptions{}, fmt.Errorf("encoding auth for docker registry: %w", err) + } + + pullOpts.RegistryAuth = auth + } + + return pullOpts, nil +} + func ExtractRunArgsWithLogging( ctx context.Context, spaceID int64, @@ -404,7 +443,7 @@ func ExtractRunArgsWithLogging( return runArgsMap, nil } -// getContainerResponse retrieves container information and prepares the start response. +// GetContainerResponse retrieves container information and prepares the start response. func GetContainerResponse( ctx context.Context, dockerClient *client.Client, @@ -423,3 +462,12 @@ func GetContainerResponse( AbsoluteRepoPath: codeRepoDir, }, nil } + +// Helper function to encode the AuthConfig into a Base64 string. +func encodeAuthToBase64(authConfig registry.AuthConfig) (string, error) { + authJSON, err := json.Marshal(authConfig) + if err != nil { + return "", fmt.Errorf("encoding auth config: %w", err) + } + return base64.URLEncoding.EncodeToString(authJSON), nil +} diff --git a/app/gitspace/orchestrator/container/embedded_docker_container_orchestrator.go b/app/gitspace/orchestrator/container/embedded_docker_container_orchestrator.go index 58a742237..04f86a08b 100644 --- a/app/gitspace/orchestrator/container/embedded_docker_container_orchestrator.go +++ b/app/gitspace/orchestrator/container/embedded_docker_container_orchestrator.go @@ -114,6 +114,9 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace( } defer e.closeDockerClient(dockerClient) + // todo : update the code when private repository integration is supported in gitness + dockerRegistryAuth := gitspaceTypes.DockerRegistryAuth{} + // Step 3: Check the current state of the container state, err := e.checkContainerState(ctx, dockerClient, containerName) if err != nil { @@ -144,7 +147,8 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace( resolvedRepoDetails, infra, defaultBaseImage, - ideService); err != nil { + ideService, + dockerRegistryAuth); err != nil { return nil, err } case ContainerStatePaused, ContainerStateCreated, ContainerStateUnknown, ContainerStateDead: @@ -369,6 +373,7 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps( resolvedRepoDetails scm.ResolvedDetails, defaultBaseImage string, gitspaceLogger gitspaceTypes.GitspaceLogger, + dockerRegistryAuth gitspaceTypes.DockerRegistryAuth, ) error { homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier) containerName := GetGitspaceContainerName(gitspaceConfig) @@ -386,7 +391,7 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps( } // Pull the required image - if err := PullImage(ctx, imageName, dockerClient, runArgsMap, gitspaceLogger); err != nil { + if err := PullImage(ctx, imageName, dockerClient, runArgsMap, gitspaceLogger, dockerRegistryAuth); err != nil { return err } portMappings := infrastructure.GitspacePortMappings @@ -665,6 +670,7 @@ func (e *EmbeddedDockerOrchestrator) createAndStartNewGitspace( infrastructure types.Infrastructure, defaultBaseImage string, ideService ide.IDE, + dockerRegistryAuth gitspaceTypes.DockerRegistryAuth, ) error { logStreamInstance, err := e.statefulLogger.CreateLogStream(ctx, gitspaceConfig.ID) if err != nil { @@ -681,6 +687,7 @@ func (e *EmbeddedDockerOrchestrator) createAndStartNewGitspace( resolvedRepoDetails, defaultBaseImage, logStreamInstance, + dockerRegistryAuth, ) if startErr != nil { return fmt.Errorf("failed to start gitspace %s: %w", gitspaceConfig.Identifier, startErr) diff --git a/app/gitspace/types/types.go b/app/gitspace/types/types.go index 1ee536c77..0a24472dd 100644 --- a/app/gitspace/types/types.go +++ b/app/gitspace/types/types.go @@ -18,6 +18,7 @@ import ( "context" "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" + "github.com/harness/gitness/types" "github.com/rs/zerolog" ) @@ -38,3 +39,11 @@ type Step struct { type ZerologAdapter struct { logger *zerolog.Logger } + +type DockerRegistryAuth struct { + // only host name is required + // eg: docker.io instead of https://docker.io + RegistryURL string + Username *types.MaskSecret + Password *types.MaskSecret +}