From 9c3692c01e1003ba402fc280485d502cde06a87e Mon Sep 17 00:00:00 2001 From: Denis Rumyantsev Date: Mon, 24 Jun 2024 07:36:08 +0200 Subject: [PATCH] Use useradd on Alpine when user ID is large (#4851) Use the `useradd` command from the `shadow` package on Alpine when user ID is outside the range of the `adduser` command (default command for Alpine) --- .../ContainerOperationProvider.cs | 46 +++++++++++++++++-- src/Misc/layoutbin/en-US/strings.json | 1 + 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/Agent.Worker/ContainerOperationProvider.cs b/src/Agent.Worker/ContainerOperationProvider.cs index 2362725334..07a13ff41f 100644 --- a/src/Agent.Worker/ContainerOperationProvider.cs +++ b/src/Agent.Worker/ContainerOperationProvider.cs @@ -194,7 +194,7 @@ private async Task GetAccessTokenUsingWorkloadIdentityFederation(IExecut Trace.Entering(); var tenantId = string.Empty; - if(!registryEndpoint.Authorization?.Parameters?.TryGetValue(c_tenantId, out tenantId) ?? false) + if (!registryEndpoint.Authorization?.Parameters?.TryGetValue(c_tenantId, out tenantId) ?? false) { throw new InvalidOperationException($"Could not read {c_tenantId}"); } @@ -708,7 +708,43 @@ private async Task StartContainerAsync(IExecutionContext executionContext, Conta Func addUserWithIdAndGroup; Func addUserToGroup; + bool useShadowIfAlpine = false; + if (isAlpineBasedImage) + { + List shadowInfoOutput = await DockerExec(executionContext, container.ContainerId, "apk list --installed | grep shadow"); + bool shadowPreinstalled = false; + + foreach (string shadowInfoLine in shadowInfoOutput) + { + if (shadowInfoLine.Contains("{shadow}", StringComparison.Ordinal)) + { + Trace.Info("The 'shadow' package is preinstalled and therefore will be used."); + shadowPreinstalled = true; + break; + } + } + + bool userIdIsOutsideAdduserCommandRange = Int64.Parse(container.CurrentUserId) > 256000; + + if (userIdIsOutsideAdduserCommandRange && !shadowPreinstalled) + { + Trace.Info("User ID is outside the range of the 'adduser' command, therefore the 'shadow' package will be installed and used."); + + try + { + await DockerExec(executionContext, container.ContainerId, "apk add shadow"); + } + catch (InvalidOperationException) + { + throw new InvalidOperationException(StringUtil.Loc("ApkAddShadowFailed")); + } + } + + useShadowIfAlpine = shadowPreinstalled || userIdIsOutsideAdduserCommandRange; + } + + if (isAlpineBasedImage && !useShadowIfAlpine) { addGroup = (groupName) => $"addgroup {groupName}"; addGroupWithId = (groupName, groupId) => $"addgroup -g {groupId} {groupName}"; @@ -1009,7 +1045,7 @@ private async Task ContainerHealthcheck(IExecutionContext executionContext, Cont } } - private async Task> DockerExec(IExecutionContext context, string containerId, string command, bool noExceptionOnError=false) + private async Task> DockerExec(IExecutionContext context, string containerId, string command, bool noExceptionOnError = false) { Trace.Info($"Docker-exec is going to execute: `{command}`; container id: `{containerId}`"); List output = new List(); @@ -1027,7 +1063,7 @@ private async Task> DockerExec(IExecutionContext context, string co if (exitCode != 0) { Trace.Error(message); - if(!noExceptionOnError) + if (!noExceptionOnError) { throw new InvalidOperationException(message); } @@ -1046,14 +1082,14 @@ private static void ThrowIfAlreadyInContainer() { if (PlatformUtil.RunningOnWindows) { - #pragma warning disable CA1416 // SupportedOSPlatform checks not respected in lambda usage +#pragma warning disable CA1416 // SupportedOSPlatform checks not respected in lambda usage // service CExecSvc is Container Execution Agent. ServiceController[] scServices = ServiceController.GetServices(); if (scServices.Any(x => String.Equals(x.ServiceName, "cexecsvc", StringComparison.OrdinalIgnoreCase) && x.Status == ServiceControllerStatus.Running)) { throw new NotSupportedException(StringUtil.Loc("AgentAlreadyInsideContainer")); } - #pragma warning restore CA1416 +#pragma warning restore CA1416 } else { diff --git a/src/Misc/layoutbin/en-US/strings.json b/src/Misc/layoutbin/en-US/strings.json index ef6d182260..64ece52636 100644 --- a/src/Misc/layoutbin/en-US/strings.json +++ b/src/Misc/layoutbin/en-US/strings.json @@ -27,6 +27,7 @@ "AgentWithSameNameAlreadyExistInPool": "Pool {0} already contains an agent with name {1}.", "AllowContainerUserRunDocker": "Allow user '{0}' run any docker command without SUDO.", "AlreadyConfiguredError": "Cannot configure the agent because it is already configured. To reconfigure the agent, run 'config.cmd remove' or './config.sh remove' first.", + "ApkAddShadowFailed": "The user ID is outside the range of the 'adduser' command. The alternative command 'useradd' cannot be used because the 'shadow' package is not preinstalled and the attempt to install this package failed. Check network availability or use a docker image with the 'shadow' package preinstalled.", "ArgumentNeeded": "'{0}' has to be specified.", "ArtifactCustomPropertiesNotJson": "Artifact custom properties is not valid JSON: '{0}'", "ArtifactCustomPropertyInvalid": "Artifact custom properties must be prefixed with 'user-'. Invalid property: '{0}'",