File: util_windows.go

package info (click to toggle)
singularity-container 4.1.5%2Bds4-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 43,876 kB
  • sloc: asm: 14,840; sh: 3,190; ansic: 1,751; awk: 414; makefile: 413; python: 99
file content (155 lines) | stat: -rw-r--r-- 5,385 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package windows

import (
	"bytes"
	"context"
	"encoding/json"
	"strings"
	"syscall"

	"github.com/containerd/containerd/mount"
	"github.com/docker/docker/pkg/idtools"
	"github.com/moby/buildkit/executor"
	"github.com/moby/buildkit/snapshot"
	"github.com/pkg/errors"
)

func ResolveUsernameToSID(ctx context.Context, exec executor.Executor, rootMount []mount.Mount, userName string) (idtools.Identity, error) {
	// This is a shortcut in case the user is one of the builtin users that should exist
	// in any WCOW container. While these users do exist in containers, they don't exist on the
	// host. We check them before trying to look them up using LookupSID().
	if strings.EqualFold(userName, "ContainerAdministrator") || userName == "" {
		return idtools.Identity{SID: idtools.ContainerAdministratorSidString}, nil
	} else if strings.EqualFold(userName, "ContainerUser") {
		return idtools.Identity{SID: idtools.ContainerUserSidString}, nil
	}

	// We might have a SID set as username. There is no guarantee that this SID will exist
	// inside the container, even if we can successfully parse it. If a SID was used, we trust
	// that the user has made sure it does map to an identity inside the container.
	if strings.HasPrefix(strings.ToLower(userName), "s-") {
		if _, err := syscall.StringToSid(userName); err == nil {
			return idtools.Identity{SID: userName}, nil
		}
	}

	// We test for well known accounts that should exist inside any system. This has the potential
	// to fail if the usernames/group names differ in the container, as is the case on internationalized
	// versions where the builtin account and group names may have names in the local language.
	// If the user specified an internationalized version of the account name, but the host is in English,
	// this lookup will most likely fail and we will fall back to running get-account-info inside the container.
	// This should however catch most of the cases when well known accounts/groups are used.
	sid, _, accountType, err := syscall.LookupSID("", userName)
	if err == nil {
		if accountType == syscall.SidTypeAlias || accountType == syscall.SidTypeWellKnownGroup {
			sidAsString, err := sid.String()
			if err == nil {
				return idtools.Identity{SID: sidAsString}, nil
			}
		}
	}

	// Last resort.
	// The equivalent in Windows of /etc/passwd and /etc/group is a registry hive called SAM which can be found
	// on any windows system 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 a supported API to do this for us, we risk that sometime in the future our parser
	// will break.
	//
	// That being said, we have no choice but to execute a command inside the rootMount and attempt to get the
	// SID of the user we care about using officially supported Windows APIs. This obviously adds some overhead.
	//
	// TODO(gsamfira): Should we use a snapshot of the rootMount?
	ident, err := GetUserIdentFromContainer(ctx, exec, rootMount, userName)
	if err != nil {
		return idtools.Identity{}, errors.Wrap(err, "getting account SID from container")
	}
	return ident, nil
}

func GetUserIdentFromContainer(ctx context.Context, exec executor.Executor, rootMounts []mount.Mount, userName string) (idtools.Identity, error) {
	var ident idtools.Identity

	if len(rootMounts) > 1 {
		return ident, errors.Errorf("unexpected number of root mounts: %d", len(rootMounts))
	}

	stdout := &bytesReadWriteCloser{
		bw: &bytes.Buffer{},
	}
	stderr := &bytesReadWriteCloser{
		bw: &bytes.Buffer{},
	}

	defer stdout.Close()
	defer stderr.Close()

	procInfo := executor.ProcessInfo{
		Meta: executor.Meta{
			Args: []string{"get-user-info", userName},
			User: "ContainerAdministrator",
			Cwd:  "/",
		},
		Stdin:  nil,
		Stdout: stdout,
		Stderr: stderr,
	}

	if _, err := exec.Run(ctx, "", newStubMountable(rootMounts), nil, procInfo, nil); err != nil {
		return ident, errors.Wrap(err, "executing command")
	}

	data := stdout.bw.Bytes()
	if err := json.Unmarshal(data, &ident); err != nil {
		return ident, errors.Wrap(err, "reading user info")
	}

	return ident, nil
}

type bytesReadWriteCloser struct {
	bw *bytes.Buffer
}

func (b *bytesReadWriteCloser) Write(p []byte) (int, error) {
	if b.bw == nil {
		return 0, errors.Errorf("invalid bytes buffer")
	}
	return b.bw.Write(p)
}

func (b *bytesReadWriteCloser) Close() error {
	if b.bw == nil {
		return nil
	}
	b.bw.Reset()
	return nil
}

type snapshotMountable struct {
	m []mount.Mount
}

func (m *snapshotMountable) Mount() ([]mount.Mount, func() error, error) {
	cleanup := func() error { return nil }
	return m.m, cleanup, nil
}
func (m *snapshotMountable) IdentityMapping() *idtools.IdentityMapping {
	return nil
}

type executorMountable struct {
	m snapshot.Mountable
}

func (m *executorMountable) Mount(ctx context.Context, readonly bool) (snapshot.Mountable, error) {
	return m.m, nil
}

func newStubMountable(m []mount.Mount) executor.Mount {
	return executor.Mount{Src: &executorMountable{m: &snapshotMountable{m: m}}}
}