Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Windows] Implement executor on Windows #3517

Merged
merged 2 commits into from
Aug 30, 2023

Conversation

gabriel-samfira
Copy link
Collaborator

@gabriel-samfira gabriel-samfira commented Jan 18, 2023

(I apologize for the large wall of text bellow)

This change enables the containerd executor to run on Windows. It builds on the great work started by @TBBle .

Most of the changes are just a reshuffle of existing code in smaller platform specific functions, with Windows specific changes made.

Some things happening in this PR

  • resolvconf and hosts file are not applicable to Windows
  • Ensuring the CWD exists, requires info about the user that is supposed to own it, which means we need to fetch the SID of the user residing inside the container for Windows as well. More details on that endeavor and approach bellow.
    • The CWD is normally set by the WORKDIR directive, which also creates the location before the executor gets a chance to run. We still need this if we want to call the executor explicitly in a particular CWD.
  • On Windows, the localmounter is only needed when ensuring the CWD. In all other cases, we need to pass the layers in as they are and containerd/hcsshim will handle the proper mounting. We cannot pass in a bind mount as a root filesystem.
  • I am unsure if we can have a readonly root FS on Windows. Will need to check, but I doubt it.

Fetching the SID of the user

Linux uniquely identifies users on a system, using their UID. This information is stored in /etc/passwd and can easily be parsed by simply mounting the root filesystem and reading that file.

On Windows, there is an equivalent to both /etc/passwd and /etc/group in the form of a registry hive called SAM. This file can be found on all Windows systems in C:\Windows\System32\config\SAM.

This hive holds all user information on a particular system, including the SID of the user we care about. The bad news is that the data structures in this hive are completely undocumented and there is no API we can call to load the security info inside an offline SAM hive. We can load it as a registry hive, but parsing the data structures it holds is not documented. It's not impossible to do, but in the absence of an officially supported API to do this for us, we risk that sometime in the future our parser will break.

An attempt to parse the hive was made here: #3248

The alternative is to execute a utility in the context of a container to fetch the SID. This means that we need to run a binary that needs to exist in the container, which will use Windows APIs to fetch the SID of the user. To do this, we defined a small utility as a reexec function in buildkitd, which allows us to mount os.Executable() in C:\Windows\System32\get-user-info.exe and get the SID.

There are shortcuts to get the SID of well known accounts/groups, including the default ContainerAdministrator and ContainerUser. As long as there is no explicit USER stanza that defines a manually created user (via RUN net user testuser /ADD /ACTIVE:yes), this code path will be bypassed.

At this stage, this is just a draft, as it needs a number of other changes to land in various projects. A (hopefully) complete list bellow. Will amend the list if more are needed:

Partially addresses: microsoft/Windows-Containers#34

Signed-off-by: Gabriel Adrian Samfira [email protected]

procInfo := executor.ProcessInfo{
Meta: executor.Meta{
Args: []string{"get-user-info", userName},
User: "ContainerAdministrator",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this run as ContainerUser?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returns Access Denied if I remember correctly. Will double check.

@tonistiigi
Copy link
Member

At this stage, this is just a draft, as it needs a number of other changes to land in various projects.

Don't grow this into a very large single commit. We'll be happy to take windows-modified updates so single packages or groups of functions without the full thing being ready. Eg. #3544 is a nice example.

@gabriel-samfira
Copy link
Collaborator Author

At this stage, this is just a draft, as it needs a number of other changes to land in various projects.

Don't grow this into a very large single commit. We'll be happy to take windows-modified updates so single packages or groups of functions without the full thing being ready. Eg. #3544 is a nice example.

Indeed!

I wanted to first figure out all the needed bits to get buildkitd functioning on Windows. I spoke with @TBBle , and I'll rebase his work and propose the PRs on containerd and hcsshim (hopefully this week).

Once that is done I plan to circle back and split things up, respond to reviews and get everything in a state that is easy to review and merge.

This change splits the containerdexecutor.Run() function into smaller
pieces and enables it to run on Windows.

Signed-off-by: Gabriel Adrian Samfira <[email protected]>
@gabriel-samfira gabriel-samfira marked this pull request as ready for review August 15, 2023 10:12
@gabriel-samfira
Copy link
Collaborator Author

@tonistiigi @AkihiroSuda

I rebased this PR and it's ready for review. Most of the changes are code shuffle in smaller functions and the addition of the Windows specific bits.

PTAL if you have the time.

@gabriel-samfira
Copy link
Collaborator Author

CC @crazy-max @jedevc if you have some time to review, I would appreciate it.

Copy link
Member

@tonistiigi tonistiigi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will wcow be containerd-worker only?

executor/containerdexecutor/executor.go Outdated Show resolved Hide resolved
executor/containerdexecutor/executor.go Outdated Show resolved Hide resolved
executor/containerdexecutor/executor.go Outdated Show resolved Hide resolved
executor/containerdexecutor/executor.go Outdated Show resolved Hide resolved
executor/containerdexecutor/executor.go Outdated Show resolved Hide resolved
executor/containerdexecutor/executor.go Outdated Show resolved Hide resolved
return releaseAll, resolvConf, hostsFile, nil
}

func (w *containerdExecutor) getTaskOpts(ctx context.Context, details *jobDetails) (containerd.NewTaskOpts, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just be a method on containerState (jobDetails), same for cwd. Not sure if makes sense for others.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added getTaskOpts to containerState.

}, nil
}

func (w *containerdExecutor) prepareExecutionEnv(ctx context.Context, rootMount executor.Mount, mounts []executor.Mount, meta executor.Meta, details *jobDetails) (func(), string, string, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't ideal that this is a mix of 2 things: preparing /etc files and setting up properties of details (otherwise I would call this prepareEtcPaths. If it is already a mix, then why is CWD different? If you don't have better ideas then rename the cwd to prepareCWD so it is consitent.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't ideal that this is a mix of 2 things: preparing /etc files and setting up properties of details

I can add the /etc files as fields in details and keep things consistent. This way each platform can pick and choose what they need. Would that be better?

(otherwise I would call this prepareEtcPaths.

It also records the rootfsPath on Linux and rootMounts []mount.Mount on Windows. These are used later to ensure the CWD exists and so they can also be used later in Exec(), fixing the exiting TODO.

. If it is already a mix, then why is CWD different?

The CWD is a bit different because on Windows we're pretty much forced to boot up a container to get the SID (Security Identifier) of the owner of the CWD.

There is that whole song and dance in the windows version that calls into windows.ResolveUsernameToSID(), which passes in the executor so it can resolve the user SID without reimplementing the executor elsewhere. That's why it's attached to the executor. I can attach it to containerState{} and pass in the executor (and any other parameters it needs) as an arg.

How would you prefer?

executor/containerdexecutor/executor_unix.go Outdated Show resolved Hide resolved
executor/oci/spec.go Outdated Show resolved Hide resolved
Copy link
Collaborator Author

@gabriel-samfira gabriel-samfira left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will wcow be containerd-worker only?

At this point, yes.

}, nil
}

func (w *containerdExecutor) prepareExecutionEnv(ctx context.Context, rootMount executor.Mount, mounts []executor.Mount, meta executor.Meta, details *jobDetails) (func(), string, string, error) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't ideal that this is a mix of 2 things: preparing /etc files and setting up properties of details

I can add the /etc files as fields in details and keep things consistent. This way each platform can pick and choose what they need. Would that be better?

(otherwise I would call this prepareEtcPaths.

It also records the rootfsPath on Linux and rootMounts []mount.Mount on Windows. These are used later to ensure the CWD exists and so they can also be used later in Exec(), fixing the exiting TODO.

. If it is already a mix, then why is CWD different?

The CWD is a bit different because on Windows we're pretty much forced to boot up a container to get the SID (Security Identifier) of the owner of the CWD.

There is that whole song and dance in the windows version that calls into windows.ResolveUsernameToSID(), which passes in the executor so it can resolve the user SID without reimplementing the executor elsewhere. That's why it's attached to the executor. I can attach it to containerState{} and pass in the executor (and any other parameters it needs) as an arg.

How would you prefer?

executor/oci/spec.go Outdated Show resolved Hide resolved
return releaseAll, resolvConf, hostsFile, nil
}

func (w *containerdExecutor) getTaskOpts(ctx context.Context, details *jobDetails) (containerd.NewTaskOpts, error) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added getTaskOpts to containerState.

Signed-off-by: Gabriel Adrian Samfira <[email protected]>
@gabriel-samfira
Copy link
Collaborator Author

Failure seems to be unrelated to the changes. May be worth triggering that job again.

@AkihiroSuda AkihiroSuda merged commit 05eb728 into moby:master Aug 30, 2023
56 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants