oci_windows.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. package daemon // import "github.com/docker/docker/daemon"
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "strings"
  8. "github.com/Microsoft/hcsshim/osversion"
  9. containertypes "github.com/docker/docker/api/types/container"
  10. "github.com/docker/docker/container"
  11. "github.com/docker/docker/errdefs"
  12. "github.com/docker/docker/oci"
  13. "github.com/docker/docker/pkg/sysinfo"
  14. "github.com/docker/docker/pkg/system"
  15. specs "github.com/opencontainers/runtime-spec/specs-go"
  16. "github.com/pkg/errors"
  17. "github.com/sirupsen/logrus"
  18. "golang.org/x/sys/windows/registry"
  19. )
  20. const (
  21. credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs`
  22. credentialSpecFileLocation = "CredentialSpecs"
  23. )
  24. func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
  25. img, err := daemon.imageService.GetImage(string(c.ImageID), nil)
  26. if err != nil {
  27. return nil, err
  28. }
  29. if !system.IsOSSupported(img.OperatingSystem()) {
  30. return nil, system.ErrNotSupportedOperatingSystem
  31. }
  32. s := oci.DefaultSpec()
  33. linkedEnv, err := daemon.setupLinkedContainers(c)
  34. if err != nil {
  35. return nil, err
  36. }
  37. // Note, unlike Unix, we do NOT call into SetupWorkingDirectory as
  38. // this is done in VMCompute. Further, we couldn't do it for Hyper-V
  39. // containers anyway.
  40. if err := daemon.setupSecretDir(c); err != nil {
  41. return nil, err
  42. }
  43. if err := daemon.setupConfigDir(c); err != nil {
  44. return nil, err
  45. }
  46. // In s.Mounts
  47. mounts, err := daemon.setupMounts(c)
  48. if err != nil {
  49. return nil, err
  50. }
  51. var isHyperV bool
  52. if c.HostConfig.Isolation.IsDefault() {
  53. // Container using default isolation, so take the default from the daemon configuration
  54. isHyperV = daemon.defaultIsolation.IsHyperV()
  55. } else {
  56. // Container may be requesting an explicit isolation mode.
  57. isHyperV = c.HostConfig.Isolation.IsHyperV()
  58. }
  59. if isHyperV {
  60. s.Windows.HyperV = &specs.WindowsHyperV{}
  61. }
  62. // If the container has not been started, and has configs or secrets
  63. // secrets, create symlinks to each config and secret. If it has been
  64. // started before, the symlinks should have already been created. Also, it
  65. // is important to not mount a Hyper-V container that has been started
  66. // before, to protect the host from the container; for example, from
  67. // malicious mutation of NTFS data structures.
  68. if !c.HasBeenStartedBefore && (len(c.SecretReferences) > 0 || len(c.ConfigReferences) > 0) {
  69. // The container file system is mounted before this function is called,
  70. // except for Hyper-V containers, so mount it here in that case.
  71. if isHyperV {
  72. if err := daemon.Mount(c); err != nil {
  73. return nil, err
  74. }
  75. defer daemon.Unmount(c)
  76. }
  77. if err := c.CreateSecretSymlinks(); err != nil {
  78. return nil, err
  79. }
  80. if err := c.CreateConfigSymlinks(); err != nil {
  81. return nil, err
  82. }
  83. }
  84. secretMounts, err := c.SecretMounts()
  85. if err != nil {
  86. return nil, err
  87. }
  88. if secretMounts != nil {
  89. mounts = append(mounts, secretMounts...)
  90. }
  91. configMounts := c.ConfigMounts()
  92. if configMounts != nil {
  93. mounts = append(mounts, configMounts...)
  94. }
  95. for _, mount := range mounts {
  96. m := specs.Mount{
  97. Source: mount.Source,
  98. Destination: mount.Destination,
  99. }
  100. if !mount.Writable {
  101. m.Options = append(m.Options, "ro")
  102. }
  103. s.Mounts = append(s.Mounts, m)
  104. }
  105. // In s.Process
  106. s.Process.Cwd = c.Config.WorkingDir
  107. s.Process.Env = c.CreateDaemonEnvironment(c.Config.Tty, linkedEnv)
  108. s.Process.Terminal = c.Config.Tty
  109. if c.Config.Tty {
  110. s.Process.ConsoleSize = &specs.Box{
  111. Height: c.HostConfig.ConsoleSize[0],
  112. Width: c.HostConfig.ConsoleSize[1],
  113. }
  114. }
  115. s.Process.User.Username = c.Config.User
  116. s.Windows.LayerFolders, err = daemon.imageService.GetLayerFolders(img, c.RWLayer)
  117. if err != nil {
  118. return nil, errors.Wrapf(err, "container %s", c.ID)
  119. }
  120. dnsSearch := daemon.getDNSSearchSettings(c)
  121. // Get endpoints for the libnetwork allocated networks to the container
  122. var epList []string
  123. AllowUnqualifiedDNSQuery := false
  124. gwHNSID := ""
  125. if c.NetworkSettings != nil {
  126. for n := range c.NetworkSettings.Networks {
  127. sn, err := daemon.FindNetwork(n)
  128. if err != nil {
  129. continue
  130. }
  131. ep, err := getEndpointInNetwork(c.Name, sn)
  132. if err != nil {
  133. continue
  134. }
  135. data, err := ep.DriverInfo()
  136. if err != nil {
  137. continue
  138. }
  139. if data["GW_INFO"] != nil {
  140. gwInfo := data["GW_INFO"].(map[string]interface{})
  141. if gwInfo["hnsid"] != nil {
  142. gwHNSID = gwInfo["hnsid"].(string)
  143. }
  144. }
  145. if data["hnsid"] != nil {
  146. epList = append(epList, data["hnsid"].(string))
  147. }
  148. if data["AllowUnqualifiedDNSQuery"] != nil {
  149. AllowUnqualifiedDNSQuery = true
  150. }
  151. }
  152. }
  153. var networkSharedContainerID string
  154. if c.HostConfig.NetworkMode.IsContainer() {
  155. networkSharedContainerID = c.NetworkSharedContainerID
  156. for _, ep := range c.SharedEndpointList {
  157. epList = append(epList, ep)
  158. }
  159. }
  160. if gwHNSID != "" {
  161. epList = append(epList, gwHNSID)
  162. }
  163. s.Windows.Network = &specs.WindowsNetwork{
  164. AllowUnqualifiedDNSQuery: AllowUnqualifiedDNSQuery,
  165. DNSSearchList: dnsSearch,
  166. EndpointList: epList,
  167. NetworkSharedContainerName: networkSharedContainerID,
  168. }
  169. if err := daemon.createSpecWindowsFields(c, &s, isHyperV); err != nil {
  170. return nil, err
  171. }
  172. if logrus.IsLevelEnabled(logrus.DebugLevel) {
  173. if b, err := json.Marshal(&s); err == nil {
  174. logrus.Debugf("Generated spec: %s", string(b))
  175. }
  176. }
  177. return &s, nil
  178. }
  179. // Sets the Windows-specific fields of the OCI spec
  180. func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) error {
  181. s.Hostname = c.FullHostname()
  182. if len(s.Process.Cwd) == 0 {
  183. // We default to C:\ to workaround the oddity of the case that the
  184. // default directory for cmd running as LocalSystem (or
  185. // ContainerAdministrator) is c:\windows\system32. Hence docker run
  186. // <image> cmd will by default end in c:\windows\system32, rather
  187. // than 'root' (/) on Linux. The oddity is that if you have a dockerfile
  188. // which has no WORKDIR and has a COPY file ., . will be interpreted
  189. // as c:\. Hence, setting it to default of c:\ makes for consistency.
  190. s.Process.Cwd = `C:\`
  191. }
  192. if c.Config.ArgsEscaped {
  193. s.Process.CommandLine = c.Path
  194. if len(c.Args) > 0 {
  195. s.Process.CommandLine += " " + system.EscapeArgs(c.Args)
  196. }
  197. } else {
  198. s.Process.Args = append([]string{c.Path}, c.Args...)
  199. }
  200. s.Root.Readonly = false // Windows does not support a read-only root filesystem
  201. if !isHyperV {
  202. if c.BaseFS == nil {
  203. return errors.New("createSpecWindowsFields: BaseFS of container " + c.ID + " is unexpectedly nil")
  204. }
  205. s.Root.Path = c.BaseFS.Path() // This is not set for Hyper-V containers
  206. if !strings.HasSuffix(s.Root.Path, `\`) {
  207. s.Root.Path = s.Root.Path + `\` // Ensure a correctly formatted volume GUID path \\?\Volume{GUID}\
  208. }
  209. }
  210. // First boot optimization
  211. s.Windows.IgnoreFlushesDuringBoot = !c.HasBeenStartedBefore
  212. setResourcesInSpec(c, s, isHyperV)
  213. // Read and add credentials from the security options if a credential spec has been provided.
  214. if err := daemon.setWindowsCredentialSpec(c, s); err != nil {
  215. return err
  216. }
  217. // Do we have any assigned devices?
  218. if len(c.HostConfig.Devices) > 0 {
  219. if isHyperV {
  220. return errors.New("device assignment is not supported for HyperV containers")
  221. }
  222. if osversion.Build() < osversion.RS5 {
  223. return errors.New("device assignment requires Windows builds RS5 (17763+) or later")
  224. }
  225. for _, deviceMapping := range c.HostConfig.Devices {
  226. srcParts := strings.SplitN(deviceMapping.PathOnHost, "/", 2)
  227. if len(srcParts) != 2 {
  228. return errors.New("invalid device assignment path")
  229. }
  230. if srcParts[0] != "class" {
  231. return errors.Errorf("invalid device assignment type: '%s' should be 'class'", srcParts[0])
  232. }
  233. wd := specs.WindowsDevice{
  234. ID: srcParts[1],
  235. IDType: srcParts[0],
  236. }
  237. s.Windows.Devices = append(s.Windows.Devices, wd)
  238. }
  239. }
  240. return nil
  241. }
  242. var errInvalidCredentialSpecSecOpt = errdefs.InvalidParameter(fmt.Errorf("invalid credential spec security option - value must be prefixed by 'file://', 'registry://', or 'raw://' followed by a non-empty value"))
  243. // setWindowsCredentialSpec sets the spec's `Windows.CredentialSpec`
  244. // field if relevant
  245. func (daemon *Daemon) setWindowsCredentialSpec(c *container.Container, s *specs.Spec) error {
  246. if c.HostConfig == nil || c.HostConfig.SecurityOpt == nil {
  247. return nil
  248. }
  249. // TODO (jrouge/wk8): if provided with several security options, we silently ignore
  250. // all but the last one (provided they're all valid, otherwise we do return an error);
  251. // this doesn't seem like a great idea?
  252. credentialSpec := ""
  253. for _, secOpt := range c.HostConfig.SecurityOpt {
  254. optSplits := strings.SplitN(secOpt, "=", 2)
  255. if len(optSplits) != 2 {
  256. return errdefs.InvalidParameter(fmt.Errorf("invalid security option: no equals sign in supplied value %s", secOpt))
  257. }
  258. if !strings.EqualFold(optSplits[0], "credentialspec") {
  259. return errdefs.InvalidParameter(fmt.Errorf("security option not supported: %s", optSplits[0]))
  260. }
  261. credSpecSplits := strings.SplitN(optSplits[1], "://", 2)
  262. if len(credSpecSplits) != 2 || credSpecSplits[1] == "" {
  263. return errInvalidCredentialSpecSecOpt
  264. }
  265. value := credSpecSplits[1]
  266. var err error
  267. switch strings.ToLower(credSpecSplits[0]) {
  268. case "file":
  269. if credentialSpec, err = readCredentialSpecFile(c.ID, daemon.root, filepath.Clean(value)); err != nil {
  270. return errdefs.InvalidParameter(err)
  271. }
  272. case "registry":
  273. if credentialSpec, err = readCredentialSpecRegistry(c.ID, value); err != nil {
  274. return errdefs.InvalidParameter(err)
  275. }
  276. case "config":
  277. // if the container does not have a DependencyStore, then it
  278. // isn't swarmkit managed. In order to avoid creating any
  279. // impression that `config://` is a valid API, return the same
  280. // error as if you'd passed any other random word.
  281. if c.DependencyStore == nil {
  282. return errInvalidCredentialSpecSecOpt
  283. }
  284. csConfig, err := c.DependencyStore.Configs().Get(value)
  285. if err != nil {
  286. return errdefs.System(errors.Wrap(err, "error getting value from config store"))
  287. }
  288. // stuff the resulting secret data into a string to use as the
  289. // CredentialSpec
  290. credentialSpec = string(csConfig.Spec.Data)
  291. case "raw":
  292. credentialSpec = value
  293. default:
  294. return errInvalidCredentialSpecSecOpt
  295. }
  296. }
  297. if credentialSpec != "" {
  298. if s.Windows == nil {
  299. s.Windows = &specs.Windows{}
  300. }
  301. s.Windows.CredentialSpec = credentialSpec
  302. }
  303. return nil
  304. }
  305. func setResourcesInSpec(c *container.Container, s *specs.Spec, isHyperV bool) {
  306. // In s.Windows.Resources
  307. cpuShares := uint16(c.HostConfig.CPUShares)
  308. cpuMaximum := uint16(c.HostConfig.CPUPercent) * 100
  309. cpuCount := uint64(c.HostConfig.CPUCount)
  310. if c.HostConfig.NanoCPUs > 0 {
  311. if isHyperV {
  312. cpuCount = uint64(c.HostConfig.NanoCPUs / 1e9)
  313. leftoverNanoCPUs := c.HostConfig.NanoCPUs % 1e9
  314. if leftoverNanoCPUs != 0 {
  315. cpuCount++
  316. cpuMaximum = uint16(c.HostConfig.NanoCPUs / int64(cpuCount) / (1e9 / 10000))
  317. if cpuMaximum < 1 {
  318. // The requested NanoCPUs is so small that we rounded to 0, use 1 instead
  319. cpuMaximum = 1
  320. }
  321. }
  322. } else {
  323. cpuMaximum = uint16(c.HostConfig.NanoCPUs / int64(sysinfo.NumCPU()) / (1e9 / 10000))
  324. if cpuMaximum < 1 {
  325. // The requested NanoCPUs is so small that we rounded to 0, use 1 instead
  326. cpuMaximum = 1
  327. }
  328. }
  329. }
  330. if cpuMaximum != 0 || cpuShares != 0 || cpuCount != 0 {
  331. if s.Windows.Resources == nil {
  332. s.Windows.Resources = &specs.WindowsResources{}
  333. }
  334. s.Windows.Resources.CPU = &specs.WindowsCPUResources{
  335. Maximum: &cpuMaximum,
  336. Shares: &cpuShares,
  337. Count: &cpuCount,
  338. }
  339. }
  340. memoryLimit := uint64(c.HostConfig.Memory)
  341. if memoryLimit != 0 {
  342. if s.Windows.Resources == nil {
  343. s.Windows.Resources = &specs.WindowsResources{}
  344. }
  345. s.Windows.Resources.Memory = &specs.WindowsMemoryResources{
  346. Limit: &memoryLimit,
  347. }
  348. }
  349. if c.HostConfig.IOMaximumBandwidth != 0 || c.HostConfig.IOMaximumIOps != 0 {
  350. if s.Windows.Resources == nil {
  351. s.Windows.Resources = &specs.WindowsResources{}
  352. }
  353. s.Windows.Resources.Storage = &specs.WindowsStorageResources{
  354. Bps: &c.HostConfig.IOMaximumBandwidth,
  355. Iops: &c.HostConfig.IOMaximumIOps,
  356. }
  357. }
  358. }
  359. // mergeUlimits merge the Ulimits from HostConfig with daemon defaults, and update HostConfig
  360. // It will do nothing on non-Linux platform
  361. func (daemon *Daemon) mergeUlimits(c *containertypes.HostConfig) {
  362. return
  363. }
  364. // registryKey is an interface wrapper around `registry.Key`,
  365. // listing only the methods we care about here.
  366. // It's mainly useful to easily allow mocking the registry in tests.
  367. type registryKey interface {
  368. GetStringValue(name string) (val string, valtype uint32, err error)
  369. Close() error
  370. }
  371. var registryOpenKeyFunc = func(baseKey registry.Key, path string, access uint32) (registryKey, error) {
  372. return registry.OpenKey(baseKey, path, access)
  373. }
  374. // readCredentialSpecRegistry is a helper function to read a credential spec from
  375. // the registry. If not found, we return an empty string and warn in the log.
  376. // This allows for staging on machines which do not have the necessary components.
  377. func readCredentialSpecRegistry(id, name string) (string, error) {
  378. key, err := registryOpenKeyFunc(registry.LOCAL_MACHINE, credentialSpecRegistryLocation, registry.QUERY_VALUE)
  379. if err != nil {
  380. return "", errors.Wrapf(err, "failed handling spec %q for container %s - registry key %s could not be opened", name, id, credentialSpecRegistryLocation)
  381. }
  382. defer key.Close()
  383. value, _, err := key.GetStringValue(name)
  384. if err != nil {
  385. if err == registry.ErrNotExist {
  386. return "", fmt.Errorf("registry credential spec %q for container %s was not found", name, id)
  387. }
  388. return "", errors.Wrapf(err, "error reading credential spec %q from registry for container %s", name, id)
  389. }
  390. return value, nil
  391. }
  392. // readCredentialSpecFile is a helper function to read a credential spec from
  393. // a file. If not found, we return an empty string and warn in the log.
  394. // This allows for staging on machines which do not have the necessary components.
  395. func readCredentialSpecFile(id, root, location string) (string, error) {
  396. if filepath.IsAbs(location) {
  397. return "", fmt.Errorf("invalid credential spec - file:// path cannot be absolute")
  398. }
  399. base := filepath.Join(root, credentialSpecFileLocation)
  400. full := filepath.Join(base, location)
  401. if !strings.HasPrefix(full, base) {
  402. return "", fmt.Errorf("invalid credential spec - file:// path must be under %s", base)
  403. }
  404. bcontents, err := os.ReadFile(full)
  405. if err != nil {
  406. return "", errors.Wrapf(err, "credential spec for container %s could not be read from file %q", id, full)
  407. }
  408. return string(bcontents[:]), nil
  409. }