Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
7d41e66
Add local registry, auth, and push image
kvega005 Apr 6, 2026
638ebfa
Fix test
kvega005 Apr 6, 2026
7fd4026
delete cleanup registry storage
kvega005 Apr 6, 2026
1fdb1b2
Do not use volume
kvega005 Apr 6, 2026
15d30b6
Merge remote-tracking branch 'origin/feature/wsl-for-apps' into user/…
kvega005 Apr 6, 2026
35e6322
Undo entry point fix (getting checked in seperately)
kvega005 Apr 6, 2026
b278c0a
undo entry point fix
kvega005 Apr 6, 2026
b988783
Fix formatting
kvega005 Apr 6, 2026
783ae7b
Address copilot comment
kvega005 Apr 6, 2026
ead90fc
Use packaged wslc-registry
kvega005 Apr 6, 2026
ce78d51
Merge branch 'feature/wsl-for-apps' into user/kevinve/registry
kvega005 Apr 7, 2026
5156db5
Address copilot comments
kvega005 Apr 7, 2026
31b5ad5
Address more copilot comments
kvega005 Apr 7, 2026
4cfd98c
Merge branch 'user/kevinve/registry' of https://github.com/kvega005/W…
kvega005 Apr 7, 2026
6305c58
Fix push and Pulll tests
kvega005 Apr 7, 2026
22cb648
Fix formatting
kvega005 Apr 7, 2026
3a0ba44
Merge branch 'feature/wsl-for-apps' into user/kevinve/registry
kvega005 Apr 7, 2026
83b39f2
Address suggestion
kvega005 Apr 7, 2026
a17819e
Address suggestions
kvega005 Apr 7, 2026
ba6c3de
Merge branch 'user/kevinve/registry' of https://github.com/kvega005/W…
kvega005 Apr 7, 2026
e329ac7
address suggestions
kvega005 Apr 7, 2026
868f69e
Merge branch 'feature/wsl-for-apps' into user/kevinve/registry
kvega005 Apr 7, 2026
3256f5d
Address copilot comments
kvega005 Apr 7, 2026
2d4dce9
Merge branch 'user/kevinve/registry' of https://github.com/kvega005/W…
kvega005 Apr 7, 2026
9177765
Remove wslc local registry
kvega005 Apr 7, 2026
0fa5505
formatting fix
kvega005 Apr 7, 2026
7b41634
Add script and docker file to generate test images.
kvega005 Apr 7, 2026
aaed20e
Address base64 encode feedback
kvega005 Apr 7, 2026
65aed10
Add Auth to SDK
kvega005 Apr 7, 2026
adab0ce
remove unneeded code
kvega005 Apr 7, 2026
3d0bad5
Address custom headers suggestion
kvega005 Apr 7, 2026
10491ee
Make regitsry auth required
kvega005 Apr 7, 2026
404a05e
Nit update script
kvega005 Apr 7, 2026
965f832
Merge branch 'feature/wsl-for-apps' into user/kevinve/registry
kvega005 Apr 7, 2026
2f35e77
Fix formatting
kvega005 Apr 8, 2026
71081bb
Update comment
kvega005 Apr 8, 2026
1734d51
Merge branch 'feature/wsl-for-apps' into user/kevinve/registry
kvega005 Apr 8, 2026
a40b27d
Fix tests
kvega005 Apr 8, 2026
bc989e5
Merge remote-tracking branch 'origin/feature/wsl-for-apps' into user/…
kvega005 Apr 8, 2026
7f59f8f
Merge branch 'feature/wsl-for-apps' into user/kevinve/registry
kvega005 Apr 8, 2026
d1455e0
Merge branch 'user/kevinve/registry' of https://github.com/kvega005/W…
kvega005 Apr 8, 2026
ef6f192
Fix tests
kvega005 Apr 8, 2026
d14cfec
Added back removed test + cleanup
kvega005 Apr 9, 2026
f4c1f39
Merge remote-tracking branch 'origin/feature/wsl-for-apps' into user/…
kvega005 Apr 9, 2026
c945517
Fix empty auth
kvega005 Apr 9, 2026
498588b
Merge branch 'feature/wsl-for-apps' into user/kevinve/registry
kvega005 Apr 9, 2026
baabcd9
Fix clang-format violations in test files
Apr 9, 2026
f6ea418
Merge remote-tracking branch 'origin/feature/wsl-for-apps' into user/…
kvega005 Apr 10, 2026
61dd8b4
Merge branch 'feature/wsl-for-apps' into user/kevinve/registry
kvega005 Apr 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages.config
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<package id="Microsoft.WSL.DeviceHost" version="1.2.10-0" />
<package id="Microsoft.WSL.Kernel" version="6.6.114.1-1" targetFramework="native" />
<package id="Microsoft.WSL.LinuxSdk" version="1.20.0" targetFramework="native" />
<package id="Microsoft.WSL.TestData" version="0.3.0" />
<package id="Microsoft.WSL.TestData" version="0.4.0" />
<package id="Microsoft.WSL.TestDistro" version="2.7.1-1" />
<package id="Microsoft.WSLg" version="1.0.76" />
<package id="Microsoft.Xaml.Behaviors.WinUI.Managed" version="3.0.0" />
Expand Down
4 changes: 4 additions & 0 deletions src/windows/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ set(SOURCES
SubProcess.cpp
svccomm.cpp
WSLCContainerLauncher.cpp
WSLCLocalRegistry.cpp
WslcCredentialStore.cpp
VirtioNetworking.cpp
WSLCProcessLauncher.cpp
WslClient.cpp
Expand Down Expand Up @@ -116,6 +118,8 @@ set(HEADERS
SubProcess.h
svccomm.hpp
WSLCContainerLauncher.h
WSLCLocalRegistry.h
WslcCredentialStore.h
VirtioNetworking.h
WSLCProcessLauncher.h
WslClient.h
Expand Down
1 change: 1 addition & 0 deletions src/windows/common/WSLCContainerLauncher.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class WSLCContainerLauncher : private WSLCProcessLauncher
void SetDnsSearchDomains(std::vector<std::string>&& DnsSearchDomains);
void SetDnsOptions(std::vector<std::string>&& DnsOptions);

using WSLCProcessLauncher::FormatResult;
using WSLCProcessLauncher::SetUser;
using WSLCProcessLauncher::SetWorkingDirectory;

Expand Down
67 changes: 67 additions & 0 deletions src/windows/common/WSLCLocalRegistry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*++

Comment thread
kvega005 marked this conversation as resolved.
Outdated
Copyright (c) Microsoft. All rights reserved.

Module Name:

WSLCLocalRegistry.cpp

Abstract:

Implementation of WSLCLocalRegistry.

--*/
Comment thread
kvega005 marked this conversation as resolved.
Outdated
#include "WSLCLocalRegistry.h"

using wsl::windows::common::RunningWSLCContainer;
using wsl::windows::common::WSLCContainerLauncher;
using wsl::windows::common::WSLCLocalRegistry;

namespace {

constexpr auto c_registryImage = "wslc-registry:latest";

std::vector<std::string> BuildRegistryEnv(const std::string& username, const std::string& password, USHORT port)
{
std::vector<std::string> env = {
std::format("REGISTRY_HTTP_ADDR=0.0.0.0:{}", port),
};

if (!username.empty())
{
env.push_back(std::format("USERNAME={}", username));
env.push_back(std::format("PASSWORD={}", password));
}

return env;
}

} // namespace

WSLCLocalRegistry::WSLCLocalRegistry(
IWSLCSession& session, RunningWSLCContainer&& container, std::string&& username, std::string&& password, std::string&& serverAddress) :
m_session(wil::com_ptr<IWSLCSession>(&session)),
m_username(std::move(username)),
m_password(std::move(password)),
m_serverAddress(std::move(serverAddress)),
m_container(std::move(container))
{
}

WSLCLocalRegistry::~WSLCLocalRegistry()
{
// Delete the container first while the session is still active.
m_container.Reset();
Comment thread
kvega005 marked this conversation as resolved.
Outdated
}

WSLCLocalRegistry WSLCLocalRegistry::Start(IWSLCSession& session, const std::string& username, const std::string& password, USHORT port)
{
auto env = BuildRegistryEnv(username, password, port);

WSLCContainerLauncher launcher(c_registryImage, {}, {}, env);
launcher.SetEntrypoint({"/entrypoint.sh"});
launcher.AddPort(port, port, AF_INET);

auto container = launcher.Launch(session, WSLCContainerStartFlagsNone);
return WSLCLocalRegistry(session, std::move(container), std::string(username), std::string(password), std::format("127.0.0.1:{}", port));
}
53 changes: 53 additions & 0 deletions src/windows/common/WSLCLocalRegistry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

WSLCLocalRegistry.h

Abstract:

Helper class that starts a local Docker registry:3 container inside a WSLC
session, optionally configured with htpasswd basic authentication. Intended
for use in both unit tests and E2E tests that need a private registry without
an external dependency.

--*/

#pragma once
#include "WSLCContainerLauncher.h"
#include "WslcCredentialStore.h"

namespace wsl::windows::common {

class WSLCLocalRegistry
{
public:
NON_COPYABLE(WSLCLocalRegistry);
DEFAULT_MOVABLE(WSLCLocalRegistry);
~WSLCLocalRegistry();

static WSLCLocalRegistry Start(IWSLCSession& Session, const std::string& Username = {}, const std::string& Password = {}, USHORT Port = 5000);
Comment thread
kvega005 marked this conversation as resolved.
Outdated

std::string GetServerAddress()
{
return m_serverAddress;
}

std::string GetAuthHeader()
{
return BuildRegistryAuthHeader(m_username, m_password, m_serverAddress);
}

private:
WSLCLocalRegistry(IWSLCSession& session, RunningWSLCContainer&& container, std::string&& username, std::string&& password, std::string&& serverAddress);

wil::com_ptr<IWSLCSession> m_session;
std::string m_serverAddress;
std::string m_username;
std::string m_password;
RunningWSLCContainer m_container;
};

} // namespace wsl::windows::common
52 changes: 52 additions & 0 deletions src/windows/common/WslcCredentialStore.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

WslcCredentialStore.cpp

Abstract:

Implementation of credential store helpers.

--*/

#include "WslcCredentialStore.h"
#include <nlohmann/json.hpp>
#include <wincrypt.h>

Comment thread
kvega005 marked this conversation as resolved.
Outdated
namespace {

std::string Base64Encode(const std::string& input)
Comment thread
kvega005 marked this conversation as resolved.
Outdated
{
DWORD base64Size = 0;
THROW_IF_WIN32_BOOL_FALSE(CryptBinaryToStringA(
reinterpret_cast<const BYTE*>(input.c_str()), static_cast<DWORD>(input.size()), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr, &base64Size));

auto buffer = std::make_unique<char[]>(base64Size);
THROW_IF_WIN32_BOOL_FALSE(CryptBinaryToStringA(
reinterpret_cast<const BYTE*>(input.c_str()),
static_cast<DWORD>(input.size()),
CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF,
buffer.get(),
&base64Size));

return std::string(buffer.get());
}

} // namespace

std::string wsl::windows::common::BuildRegistryAuthHeader(const std::string& username, const std::string& password, const std::string& serverAddress)
{
nlohmann::json authJson = {{"username", username}, {"password", password}, {"serveraddress", serverAddress}};

return Base64Encode(authJson.dump());
}

std::string wsl::windows::common::BuildRegistryAuthHeader(const std::string& identityToken, const std::string& serverAddress)
{
nlohmann::json authJson = {{"identitytoken", identityToken}, {"serveraddress", serverAddress}};

return Base64Encode(authJson.dump());
}
30 changes: 30 additions & 0 deletions src/windows/common/WslcCredentialStore.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

WslcCredentialStore.h

Abstract:

Helpers for building Docker/OCI registry credential payloads.

--*/

#pragma once
#include <string>

namespace wsl::windows::common {

// Builds the base64-encoded X-Registry-Auth header value used by Docker APIs
// (PullImage, PushImage, etc.) from the given credentials.
std::string BuildRegistryAuthHeader(const std::string& username, const std::string& password, const std::string& serverAddress);

// Builds the base64-encoded X-Registry-Auth header value from an identity token
// returned by Authenticate().
std::string BuildRegistryAuthHeader(const std::string& identityToken, const std::string& serverAddress);

// TODO: Implement credential storage using WinCred

} // namespace wsl::windows::common
19 changes: 19 additions & 0 deletions src/windows/inc/docker_schema.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,25 @@ struct EmptyRequest
using TResponse = void;
};

struct AuthRequest
{
using TResponse = struct AuthResponse;

std::string username;
std::string password;
std::string serveraddress;

NLOHMANN_DEFINE_TYPE_INTRUSIVE(AuthRequest, username, password, serveraddress);
};

struct AuthResponse
{
std::string Status;
std::optional<std::string> IdentityToken;

NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(AuthResponse, Status, IdentityToken);
};

struct CreateVolume
{
using TResponse = void;
Expand Down
2 changes: 2 additions & 0 deletions src/windows/service/inc/wslc.idl
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,9 @@ interface IWSLCSession : IUnknown
HRESULT ListImages([in, unique] const WSLCListImageOptions* Options, [out, size_is(, *Count)] WSLCImageInformation** Images, [out] ULONG* Count);
HRESULT DeleteImage([in] const WSLCDeleteImageOptions* Options, [out, size_is(, *Count)] WSLCDeletedImageInformation** DeletedImages, [out] ULONG* Count);
HRESULT TagImage([in] const WSLCTagImageOptions* Options);
HRESULT PushImage([in] LPCSTR Image, [in] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback);
Comment thread
kvega005 marked this conversation as resolved.
Outdated
HRESULT InspectImage([in] LPCSTR ImageNameOrId, [out] LPSTR* Output);
Comment thread
kvega005 marked this conversation as resolved.
HRESULT Authenticate([in] LPCSTR ServerAddress, [in] LPCSTR Username, [in] LPCSTR Password, [out] LPSTR* IdentityToken);

Comment thread
kvega005 marked this conversation as resolved.
// Container management.
HRESULT CreateContainer([in] const WSLCContainerOptions* Options, [out] IWSLCContainer** Container);
Expand Down
47 changes: 43 additions & 4 deletions src/windows/wslcsession/DockerHTTPClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ DockerHTTPClient::DockerHTTPClient(wsl::shared::SocketChannel&& Channel, HANDLE
{
}

std::unique_ptr<DockerHTTPClient::HTTPRequestContext> DockerHTTPClient::PullImage(const std::string& Repo, const std::optional<std::string>& tagOrDigest)
std::unique_ptr<DockerHTTPClient::HTTPRequestContext> DockerHTTPClient::PullImage(
const std::string& Repo, const std::optional<std::string>& tagOrDigest, const std::optional<std::string>& registryAuth)
{
auto url = URL::Create("/images/create");

Expand All @@ -131,7 +132,14 @@ std::unique_ptr<DockerHTTPClient::HTTPRequestContext> DockerHTTPClient::PullImag
url.SetParameter("tag", tagOrDigest.value());
}

return SendRequestImpl(verb::post, url, {}, {});
std::map<std::string, std::string> customHeaders;

if (registryAuth.has_value())
{
customHeaders["X-Registry-Auth"] = registryAuth.value();
}

return SendRequestImpl(verb::post, url, {}, {}, customHeaders);
}

std::unique_ptr<DockerHTTPClient::HTTPRequestContext> DockerHTTPClient::LoadImage(uint64_t ContentLength)
Expand Down Expand Up @@ -163,6 +171,28 @@ void DockerHTTPClient::TagImage(const std::string& Id, const std::string& Repo,
Transaction<docker_schema::EmptyRequest>(verb::post, url);
}

std::unique_ptr<DockerHTTPClient::HTTPRequestContext> DockerHTTPClient::PushImage(
const std::string& ImageName, const std::optional<std::string>& tag, const std::string& registryAuth)
{
auto url = URL::Create("/images/{}/push", ImageName);

if (tag.has_value())
{
url.SetParameter("tag", tag.value());
}

std::map<std::string, std::string> customHeaders = {{"X-Registry-Auth", registryAuth}};
Comment thread
kvega005 marked this conversation as resolved.
return SendRequestImpl(verb::post, url, {}, {}, customHeaders);
}

std::string DockerHTTPClient::Authenticate(const std::string& serverAddress, const std::string& username, const std::string& password)
{
auto response = Transaction<docker_schema::AuthRequest>(
verb::post, URL::Create("/auth"), {.username = username, .password = password, .serveraddress = serverAddress});

return response.IdentityToken.value_or("");
}

std::vector<docker_schema::Image> DockerHTTPClient::ListImages(bool all, bool digests, const ListImagesFilters& filters)
{
auto url = URL::Create("/images/json");
Expand Down Expand Up @@ -632,7 +662,11 @@ void DockerHTTPClient::DockerHttpResponseHandle::OnResponseBytes(const gsl::span
}

std::unique_ptr<DockerHTTPClient::HTTPRequestContext> DockerHTTPClient::SendRequestImpl(
verb Method, const URL& Url, const std::string& Body, const std::map<boost::beast::http::field, std::string>& Headers)
verb Method,
const URL& Url,
const std::string& Body,
const std::map<boost::beast::http::field, std::string>& Headers,
const std::map<std::string, std::string>& CustomHeaders)
Comment thread
kvega005 marked this conversation as resolved.
Outdated
{
auto context = std::make_unique<DockerHTTPClient::HTTPRequestContext>(ConnectSocket());

Expand All @@ -650,11 +684,16 @@ std::unique_ptr<DockerHTTPClient::HTTPRequestContext> DockerHTTPClient::SendRequ
req.set(http::field::connection, "close");
req.set(http::field::accept, "application/json");

for (const auto [field, value] : Headers)
for (const auto& [field, value] : Headers)
{
req.set(field, value);
}

for (const auto& [name, value] : CustomHeaders)
{
req.set(name, value);
}

Comment thread
kvega005 marked this conversation as resolved.
Outdated
http::write(context->stream, req);

#ifdef WSLC_HTTP_DEBUG
Expand Down
11 changes: 9 additions & 2 deletions src/windows/wslcsession/DockerHTTPClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,13 @@ class DockerHTTPClient
std::vector<std::string> labels;
};

std::unique_ptr<HTTPRequestContext> PullImage(const std::string& Repo, const std::optional<std::string>& tagOrDigest);
std::unique_ptr<HTTPRequestContext> PullImage(
const std::string& Repo, const std::optional<std::string>& tagOrDigest, const std::optional<std::string>& registryAuth = std::nullopt);
std::unique_ptr<HTTPRequestContext> ImportImage(const std::string& Repo, const std::string& Tag, uint64_t ContentLength);
std::unique_ptr<HTTPRequestContext> LoadImage(uint64_t ContentLength);
void TagImage(const std::string& Id, const std::string& Repo, const std::string& Tag);
std::unique_ptr<HTTPRequestContext> PushImage(const std::string& ImageName, const std::optional<std::string>& tag, const std::string& registryAuth);
std::string Authenticate(const std::string& serverAddress, const std::string& username, const std::string& password);
std::vector<common::docker_schema::Image> ListImages(bool all = false, bool digests = false, const ListImagesFilters& filters = {});
common::docker_schema::InspectImage InspectImage(const std::string& NameOrId);
std::vector<common::docker_schema::DeletedImage> DeleteImage(const char* Image, bool Force, bool NoPrune); // Image can be ID or Repo:Tag.
Expand Down Expand Up @@ -224,7 +227,11 @@ class DockerHTTPClient
wil::unique_socket ConnectSocket();

std::unique_ptr<HTTPRequestContext> SendRequestImpl(
boost::beast::http::verb Method, const URL& Url, const std::string& Body, const std::map<boost::beast::http::field, std::string>& Headers);
boost::beast::http::verb Method,
const URL& Url,
const std::string& Body,
const std::map<boost::beast::http::field, std::string>& Headers = {},
const std::map<std::string, std::string>& CustomHeaders = {});

std::pair<HTTPResponse, std::string> SendRequestAndReadResponse(
boost::beast::http::verb Method, const URL& Url, const std::string& Body = "");
Expand Down
Loading