Skip to content

Commit 00034f2

Browse files
committed
allow reading uid from ContainerUser to set the Tar entry uid
1 parent 5b7b6d8 commit 00034f2

File tree

4 files changed

+68
-19
lines changed

4 files changed

+68
-19
lines changed

src/Containers/Microsoft.NET.Build.Containers/ContainerBuilder.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ internal static async Task<int> ContainerizeAsync(
114114
KnownImageFormats.OCI => SchemaTypes.OciManifestV1,
115115
_ => imageBuilder.ManifestMediaType // should be impossible unless we add to the enum
116116
};
117-
118-
Layer newLayer = Layer.FromDirectory(publishDirectory.FullName, workingDir, imageBuilder.IsWindows, imageBuilder.ManifestMediaType);
117+
var userId = imageBuilder.IsWindows ? null : TryParseUserId(containerUser);
118+
Layer newLayer = Layer.FromDirectory(publishDirectory.FullName, workingDir, imageBuilder.IsWindows, imageBuilder.ManifestMediaType, userId);
119119
imageBuilder.AddLayer(newLayer);
120120
imageBuilder.SetWorkingDirectory(workingDir);
121121

@@ -200,6 +200,24 @@ internal static async Task<int> ContainerizeAsync(
200200
return exitCode;
201201
}
202202

203+
public static int? TryParseUserId(string? containerUser)
204+
{
205+
if (containerUser is null)
206+
{
207+
return null;
208+
}
209+
if (int.TryParse(containerUser, out int userId))
210+
{
211+
return userId;
212+
}
213+
if (containerUser.Equals("root", StringComparison.OrdinalIgnoreCase))
214+
{
215+
return 0; // root user
216+
}
217+
// TODO: on Linux we could _potentially_ try to map the user name to a UID
218+
return null;
219+
}
220+
203221
private static async Task<int> PushToLocalRegistryAsync(ILogger logger, BuiltImage builtImage, SourceImageReference sourceImageReference,
204222
DestinationImageReference destinationImageReference,
205223
CancellationToken cancellationToken)

src/Containers/Microsoft.NET.Build.Containers/Layer.cs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ namespace Microsoft.NET.Build.Containers;
1212

1313
internal class Layer
1414
{
15-
// NOTE: The SID string below was created using the following snippet. As the code is Windows only we keep the constant
15+
// NOTE: The SID string below was created using the following snippet. As the code is Windows only we keep the constant,
16+
// so that we can author Windows layers successfully on non-Windows hosts.
17+
//
1618
// private static string CreateUserOwnerAndGroupSID()
1719
// {
1820
// var descriptor = new RawSecurityDescriptor(
@@ -50,7 +52,7 @@ public static Layer FromDescriptor(Descriptor descriptor)
5052
return new(ContentStore.PathForDescriptor(descriptor), descriptor);
5153
}
5254

53-
public static Layer FromDirectory(string directory, string containerPath, bool isWindowsLayer, string manifestMediaType)
55+
public static Layer FromDirectory(string directory, string containerPath, bool isWindowsLayer, string manifestMediaType, int? userId = null)
5456
{
5557
long fileSize;
5658
Span<byte> hash = stackalloc byte[SHA256.HashSizeInBytes];
@@ -101,7 +103,7 @@ public static Layer FromDirectory(string directory, string containerPath, bool i
101103
}
102104

103105
// Write an entry for the application directory.
104-
WriteTarEntryForFile(writer, new DirectoryInfo(directory), containerPath, entryAttributes);
106+
WriteTarEntryForFile(writer, new DirectoryInfo(directory), containerPath, entryAttributes, isWindowsLayer ? null : userId);
105107

106108
// Write entries for the application directory contents.
107109
var fileList = new FileSystemEnumerable<(FileSystemInfo file, string containerPath)>(
@@ -124,7 +126,7 @@ public static Layer FromDirectory(string directory, string containerPath, bool i
124126
});
125127
foreach (var item in fileList)
126128
{
127-
WriteTarEntryForFile(writer, item.file, item.containerPath, entryAttributes);
129+
WriteTarEntryForFile(writer, item.file, item.containerPath, entryAttributes, isWindowsLayer ? null : userId);
128130
}
129131

130132
// Windows layers need a Hives folder, we do not need to create any Registry Hive deltas inside
@@ -148,27 +150,36 @@ public static Layer FromDirectory(string directory, string containerPath, bool i
148150
Debug.Assert(bW == hash.Length);
149151

150152
// Writes a tar entry corresponding to the file system item.
151-
static void WriteTarEntryForFile(TarWriter writer, FileSystemInfo file, string containerPath, IEnumerable<KeyValuePair<string, string>> entryAttributes)
153+
static void WriteTarEntryForFile(TarWriter writer, FileSystemInfo file, string containerPath, IEnumerable<KeyValuePair<string, string>> entryAttributes, int? userId)
152154
{
153155
UnixFileMode mode = DetermineFileMode(file);
156+
PaxTarEntry entry;
154157

155158
if (file is FileInfo)
156159
{
157-
using var fileStream = File.OpenRead(file.FullName);
158-
PaxTarEntry entry = new(TarEntryType.RegularFile, containerPath, entryAttributes)
160+
var fileStream = File.OpenRead(file.FullName);
161+
entry = new(TarEntryType.RegularFile, containerPath, entryAttributes)
159162
{
160-
Mode = mode,
161-
DataStream = fileStream
163+
DataStream = fileStream,
162164
};
163-
writer.WriteEntry(entry);
164165
}
165166
else
166167
{
167-
PaxTarEntry entry = new(TarEntryType.Directory, containerPath, entryAttributes)
168-
{
169-
Mode = mode
170-
};
171-
writer.WriteEntry(entry);
168+
entry = new(TarEntryType.Directory, containerPath, entryAttributes);
169+
}
170+
171+
entry.Mode = mode;
172+
if (userId is int uid)
173+
{
174+
entry.Uid = uid;
175+
}
176+
177+
writer.WriteEntry(entry);
178+
179+
if (entry.DataStream is not null)
180+
{
181+
// no longer relying on the `using` of the FileStream, so need to do it manually
182+
entry.DataStream.Dispose();
172183
}
173184

174185
static UnixFileMode DetermineFileMode(FileSystemInfo file)

src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@ internal async Task<bool> ExecuteAsync(CancellationToken cancellationToken)
160160
Log.LogErrorWithCodeFromResources(nameof(Strings.InvalidContainerImageFormat), ImageFormat, string.Join(",", Enum.GetValues<KnownImageFormats>()));
161161
}
162162
}
163-
164-
Layer newLayer = Layer.FromDirectory(PublishDirectory, WorkingDirectory, imageBuilder.IsWindows, imageBuilder.ManifestMediaType);
163+
var userId = imageBuilder.IsWindows ? null : ContainerBuilder.TryParseUserId(ContainerUser);
164+
Layer newLayer = Layer.FromDirectory(PublishDirectory, WorkingDirectory, imageBuilder.IsWindows, imageBuilder.ManifestMediaType, userId);
165165
imageBuilder.AddLayer(newLayer);
166166
imageBuilder.SetWorkingDirectory(WorkingDirectory);
167167

test/Microsoft.NET.Build.Containers.IntegrationTests/LayerEndToEndTests.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,26 @@ public void SingleFileInHiddenFolder()
9999
Assert.True(allEntries.TryGetValue("app/wwwroot/.well-known/TestFile.txt", out var fileEntry) && fileEntry.EntryType == TarEntryType.RegularFile, "Missing app/wwwroot/.well-known/TestFile.txt file entry");
100100
}
101101

102+
[Fact]
103+
public void UserIdIsAppliedToFiles()
104+
{
105+
using TransientTestFolder folder = new();
106+
107+
string testFilePath = Path.Join(folder.Path, "TestFile.txt");
108+
string testString = $"Test content for {nameof(SingleFileInFolder)}";
109+
File.WriteAllText(testFilePath, testString);
110+
111+
var userId = 1234;
112+
Layer l = Layer.FromDirectory(directory: folder.Path, containerPath: "/app", false, SchemaTypes.DockerManifestV2, userId: userId);
113+
var allEntries = LoadAllTarEntries(l.BackingFile);
114+
Assert.True(allEntries.TryGetValue("app", out var appEntry) && appEntry.EntryType == TarEntryType.Directory, "Missing app directory entry");
115+
Assert.True(allEntries.TryGetValue("app/TestFile.txt", out var fileEntry) && fileEntry.EntryType == TarEntryType.RegularFile, "Missing TestFile.txt file entry");
116+
Assert.All(allEntries.Values, entry =>
117+
{
118+
Assert.True(entry.Uid == userId, $"Expected UID {userId} for entry {entry.Name}, but got {entry.Uid}");
119+
});
120+
}
121+
102122
private static void VerifyDescriptorInfo(Layer l)
103123
{
104124
Assert.Equal(l.Descriptor.Size, new FileInfo(l.BackingFile).Length);

0 commit comments

Comments
 (0)