Skip to content

Commit

Permalink
Use useradd on Alpine when user ID is large (#4851)
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
DenisRumyantsev authored Jun 24, 2024
1 parent 559a6d2 commit 9c3692c
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 5 deletions.
46 changes: 41 additions & 5 deletions src/Agent.Worker/ContainerOperationProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ private async Task<string> 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}");
}
Expand Down Expand Up @@ -708,7 +708,43 @@ private async Task StartContainerAsync(IExecutionContext executionContext, Conta
Func<string, string, string, string> addUserWithIdAndGroup;
Func<string, string, string> addUserToGroup;

bool useShadowIfAlpine = false;

if (isAlpineBasedImage)
{
List<string> 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}";
Expand Down Expand Up @@ -1009,7 +1045,7 @@ private async Task ContainerHealthcheck(IExecutionContext executionContext, Cont
}
}

private async Task<List<string>> DockerExec(IExecutionContext context, string containerId, string command, bool noExceptionOnError=false)
private async Task<List<string>> DockerExec(IExecutionContext context, string containerId, string command, bool noExceptionOnError = false)
{
Trace.Info($"Docker-exec is going to execute: `{command}`; container id: `{containerId}`");
List<string> output = new List<string>();
Expand All @@ -1027,7 +1063,7 @@ private async Task<List<string>> DockerExec(IExecutionContext context, string co
if (exitCode != 0)
{
Trace.Error(message);
if(!noExceptionOnError)
if (!noExceptionOnError)
{
throw new InvalidOperationException(message);
}
Expand All @@ -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
{
Expand Down
1 change: 1 addition & 0 deletions src/Misc/layoutbin/en-US/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}'",
Expand Down

0 comments on commit 9c3692c

Please sign in to comment.