Skip to content

Commit 9c3692c

Browse files
authored
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)
1 parent 559a6d2 commit 9c3692c

File tree

2 files changed

+42
-5
lines changed

2 files changed

+42
-5
lines changed

src/Agent.Worker/ContainerOperationProvider.cs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ private async Task<string> GetAccessTokenUsingWorkloadIdentityFederation(IExecut
194194
Trace.Entering();
195195

196196
var tenantId = string.Empty;
197-
if(!registryEndpoint.Authorization?.Parameters?.TryGetValue(c_tenantId, out tenantId) ?? false)
197+
if (!registryEndpoint.Authorization?.Parameters?.TryGetValue(c_tenantId, out tenantId) ?? false)
198198
{
199199
throw new InvalidOperationException($"Could not read {c_tenantId}");
200200
}
@@ -708,7 +708,43 @@ private async Task StartContainerAsync(IExecutionContext executionContext, Conta
708708
Func<string, string, string, string> addUserWithIdAndGroup;
709709
Func<string, string, string> addUserToGroup;
710710

711+
bool useShadowIfAlpine = false;
712+
711713
if (isAlpineBasedImage)
714+
{
715+
List<string> shadowInfoOutput = await DockerExec(executionContext, container.ContainerId, "apk list --installed | grep shadow");
716+
bool shadowPreinstalled = false;
717+
718+
foreach (string shadowInfoLine in shadowInfoOutput)
719+
{
720+
if (shadowInfoLine.Contains("{shadow}", StringComparison.Ordinal))
721+
{
722+
Trace.Info("The 'shadow' package is preinstalled and therefore will be used.");
723+
shadowPreinstalled = true;
724+
break;
725+
}
726+
}
727+
728+
bool userIdIsOutsideAdduserCommandRange = Int64.Parse(container.CurrentUserId) > 256000;
729+
730+
if (userIdIsOutsideAdduserCommandRange && !shadowPreinstalled)
731+
{
732+
Trace.Info("User ID is outside the range of the 'adduser' command, therefore the 'shadow' package will be installed and used.");
733+
734+
try
735+
{
736+
await DockerExec(executionContext, container.ContainerId, "apk add shadow");
737+
}
738+
catch (InvalidOperationException)
739+
{
740+
throw new InvalidOperationException(StringUtil.Loc("ApkAddShadowFailed"));
741+
}
742+
}
743+
744+
useShadowIfAlpine = shadowPreinstalled || userIdIsOutsideAdduserCommandRange;
745+
}
746+
747+
if (isAlpineBasedImage && !useShadowIfAlpine)
712748
{
713749
addGroup = (groupName) => $"addgroup {groupName}";
714750
addGroupWithId = (groupName, groupId) => $"addgroup -g {groupId} {groupName}";
@@ -1009,7 +1045,7 @@ private async Task ContainerHealthcheck(IExecutionContext executionContext, Cont
10091045
}
10101046
}
10111047

1012-
private async Task<List<string>> DockerExec(IExecutionContext context, string containerId, string command, bool noExceptionOnError=false)
1048+
private async Task<List<string>> DockerExec(IExecutionContext context, string containerId, string command, bool noExceptionOnError = false)
10131049
{
10141050
Trace.Info($"Docker-exec is going to execute: `{command}`; container id: `{containerId}`");
10151051
List<string> output = new List<string>();
@@ -1027,7 +1063,7 @@ private async Task<List<string>> DockerExec(IExecutionContext context, string co
10271063
if (exitCode != 0)
10281064
{
10291065
Trace.Error(message);
1030-
if(!noExceptionOnError)
1066+
if (!noExceptionOnError)
10311067
{
10321068
throw new InvalidOperationException(message);
10331069
}
@@ -1046,14 +1082,14 @@ private static void ThrowIfAlreadyInContainer()
10461082
{
10471083
if (PlatformUtil.RunningOnWindows)
10481084
{
1049-
#pragma warning disable CA1416 // SupportedOSPlatform checks not respected in lambda usage
1085+
#pragma warning disable CA1416 // SupportedOSPlatform checks not respected in lambda usage
10501086
// service CExecSvc is Container Execution Agent.
10511087
ServiceController[] scServices = ServiceController.GetServices();
10521088
if (scServices.Any(x => String.Equals(x.ServiceName, "cexecsvc", StringComparison.OrdinalIgnoreCase) && x.Status == ServiceControllerStatus.Running))
10531089
{
10541090
throw new NotSupportedException(StringUtil.Loc("AgentAlreadyInsideContainer"));
10551091
}
1056-
#pragma warning restore CA1416
1092+
#pragma warning restore CA1416
10571093
}
10581094
else
10591095
{

src/Misc/layoutbin/en-US/strings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"AgentWithSameNameAlreadyExistInPool": "Pool {0} already contains an agent with name {1}.",
2828
"AllowContainerUserRunDocker": "Allow user '{0}' run any docker command without SUDO.",
2929
"AlreadyConfiguredError": "Cannot configure the agent because it is already configured. To reconfigure the agent, run 'config.cmd remove' or './config.sh remove' first.",
30+
"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.",
3031
"ArgumentNeeded": "'{0}' has to be specified.",
3132
"ArtifactCustomPropertiesNotJson": "Artifact custom properties is not valid JSON: '{0}'",
3233
"ArtifactCustomPropertyInvalid": "Artifact custom properties must be prefixed with 'user-'. Invalid property: '{0}'",

0 commit comments

Comments
 (0)