oci_windows.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. package daemon
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "path/filepath"
  6. "runtime"
  7. "strings"
  8. containertypes "github.com/docker/docker/api/types/container"
  9. "github.com/docker/docker/container"
  10. "github.com/docker/docker/layer"
  11. "github.com/docker/docker/oci"
  12. "github.com/docker/docker/pkg/sysinfo"
  13. "github.com/docker/docker/pkg/system"
  14. "github.com/opencontainers/runtime-spec/specs-go"
  15. "golang.org/x/sys/windows"
  16. "golang.org/x/sys/windows/registry"
  17. )
  18. const (
  19. credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs`
  20. credentialSpecFileLocation = "CredentialSpecs"
  21. )
  22. func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
  23. img, err := daemon.GetImage(string(c.ImageID))
  24. if err != nil {
  25. return nil, err
  26. }
  27. s := oci.DefaultOSSpec(img.OS)
  28. linkedEnv, err := daemon.setupLinkedContainers(c)
  29. if err != nil {
  30. return nil, err
  31. }
  32. // Note, unlike Unix, we do NOT call into SetupWorkingDirectory as
  33. // this is done in VMCompute. Further, we couldn't do it for Hyper-V
  34. // containers anyway.
  35. // In base spec
  36. s.Hostname = c.FullHostname()
  37. if err := daemon.setupSecretDir(c); err != nil {
  38. return nil, err
  39. }
  40. if err := daemon.setupConfigDir(c); err != nil {
  41. return nil, err
  42. }
  43. // In s.Mounts
  44. mounts, err := daemon.setupMounts(c)
  45. if err != nil {
  46. return nil, err
  47. }
  48. var isHyperV bool
  49. if c.HostConfig.Isolation.IsDefault() {
  50. // Container using default isolation, so take the default from the daemon configuration
  51. isHyperV = daemon.defaultIsolation.IsHyperV()
  52. } else {
  53. // Container may be requesting an explicit isolation mode.
  54. isHyperV = c.HostConfig.Isolation.IsHyperV()
  55. }
  56. if isHyperV {
  57. s.Windows.HyperV = &specs.WindowsHyperV{}
  58. }
  59. // If the container has not been started, and has configs or secrets
  60. // secrets, create symlinks to each config and secret. If it has been
  61. // started before, the symlinks should have already been created. Also, it
  62. // is important to not mount a Hyper-V container that has been started
  63. // before, to protect the host from the container; for example, from
  64. // malicious mutation of NTFS data structures.
  65. if !c.HasBeenStartedBefore && (len(c.SecretReferences) > 0 || len(c.ConfigReferences) > 0) {
  66. // The container file system is mounted before this function is called,
  67. // except for Hyper-V containers, so mount it here in that case.
  68. if isHyperV {
  69. if err := daemon.Mount(c); err != nil {
  70. return nil, err
  71. }
  72. defer daemon.Unmount(c)
  73. }
  74. if err := c.CreateSecretSymlinks(); err != nil {
  75. return nil, err
  76. }
  77. if err := c.CreateConfigSymlinks(); err != nil {
  78. return nil, err
  79. }
  80. }
  81. if m := c.SecretMounts(); m != nil {
  82. mounts = append(mounts, m...)
  83. }
  84. if m := c.ConfigMounts(); m != nil {
  85. mounts = append(mounts, m...)
  86. }
  87. for _, mount := range mounts {
  88. m := specs.Mount{
  89. Source: mount.Source,
  90. Destination: mount.Destination,
  91. }
  92. if !mount.Writable {
  93. m.Options = append(m.Options, "ro")
  94. }
  95. if img.OS != runtime.GOOS {
  96. m.Type = "bind"
  97. m.Options = append(m.Options, "rbind")
  98. m.Options = append(m.Options, fmt.Sprintf("uvmpath=/tmp/gcs/%s/binds", c.ID))
  99. }
  100. s.Mounts = append(s.Mounts, m)
  101. }
  102. // In s.Process
  103. s.Process.Args = append([]string{c.Path}, c.Args...)
  104. if !c.Config.ArgsEscaped && img.OS == "windows" {
  105. s.Process.Args = escapeArgs(s.Process.Args)
  106. }
  107. s.Process.Cwd = c.Config.WorkingDir
  108. s.Process.Env = c.CreateDaemonEnvironment(c.Config.Tty, linkedEnv)
  109. if c.Config.Tty {
  110. s.Process.Terminal = c.Config.Tty
  111. s.Process.ConsoleSize = &specs.Box{
  112. Height: c.HostConfig.ConsoleSize[0],
  113. Width: c.HostConfig.ConsoleSize[1],
  114. }
  115. }
  116. s.Process.User.Username = c.Config.User
  117. // Get the layer path for each layer.
  118. max := len(img.RootFS.DiffIDs)
  119. for i := 1; i <= max; i++ {
  120. img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
  121. layerPath, err := layer.GetLayerPath(daemon.stores[c.OS].layerStore, img.RootFS.ChainID())
  122. if err != nil {
  123. return nil, fmt.Errorf("failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.stores[c.OS].layerStore, img.RootFS.ChainID(), err)
  124. }
  125. // Reverse order, expecting parent most first
  126. s.Windows.LayerFolders = append([]string{layerPath}, s.Windows.LayerFolders...)
  127. }
  128. m, err := c.RWLayer.Metadata()
  129. if err != nil {
  130. return nil, fmt.Errorf("failed to get layer metadata - %s", err)
  131. }
  132. s.Windows.LayerFolders = append(s.Windows.LayerFolders, m["dir"])
  133. dnsSearch := daemon.getDNSSearchSettings(c)
  134. // Get endpoints for the libnetwork allocated networks to the container
  135. var epList []string
  136. AllowUnqualifiedDNSQuery := false
  137. gwHNSID := ""
  138. if c.NetworkSettings != nil {
  139. for n := range c.NetworkSettings.Networks {
  140. sn, err := daemon.FindNetwork(n)
  141. if err != nil {
  142. continue
  143. }
  144. ep, err := c.GetEndpointInNetwork(sn)
  145. if err != nil {
  146. continue
  147. }
  148. data, err := ep.DriverInfo()
  149. if err != nil {
  150. continue
  151. }
  152. if data["GW_INFO"] != nil {
  153. gwInfo := data["GW_INFO"].(map[string]interface{})
  154. if gwInfo["hnsid"] != nil {
  155. gwHNSID = gwInfo["hnsid"].(string)
  156. }
  157. }
  158. if data["hnsid"] != nil {
  159. epList = append(epList, data["hnsid"].(string))
  160. }
  161. if data["AllowUnqualifiedDNSQuery"] != nil {
  162. AllowUnqualifiedDNSQuery = true
  163. }
  164. }
  165. }
  166. var networkSharedContainerID string
  167. if c.HostConfig.NetworkMode.IsContainer() {
  168. networkSharedContainerID = c.NetworkSharedContainerID
  169. for _, ep := range c.SharedEndpointList {
  170. epList = append(epList, ep)
  171. }
  172. }
  173. if gwHNSID != "" {
  174. epList = append(epList, gwHNSID)
  175. }
  176. s.Windows.Network = &specs.WindowsNetwork{
  177. AllowUnqualifiedDNSQuery: AllowUnqualifiedDNSQuery,
  178. DNSSearchList: dnsSearch,
  179. EndpointList: epList,
  180. NetworkSharedContainerName: networkSharedContainerID,
  181. }
  182. if img.OS == "windows" {
  183. if err := daemon.createSpecWindowsFields(c, &s, isHyperV); err != nil {
  184. return nil, err
  185. }
  186. } else {
  187. // TODO @jhowardmsft LCOW Support. Modify this check when running in dual-mode
  188. if system.LCOWSupported() && img.OS == "linux" {
  189. daemon.createSpecLinuxFields(c, &s)
  190. }
  191. }
  192. return (*specs.Spec)(&s), nil
  193. }
  194. // Sets the Windows-specific fields of the OCI spec
  195. func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) error {
  196. if len(s.Process.Cwd) == 0 {
  197. // We default to C:\ to workaround the oddity of the case that the
  198. // default directory for cmd running as LocalSystem (or
  199. // ContainerAdministrator) is c:\windows\system32. Hence docker run
  200. // <image> cmd will by default end in c:\windows\system32, rather
  201. // than 'root' (/) on Linux. The oddity is that if you have a dockerfile
  202. // which has no WORKDIR and has a COPY file ., . will be interpreted
  203. // as c:\. Hence, setting it to default of c:\ makes for consistency.
  204. s.Process.Cwd = `C:\`
  205. }
  206. s.Root.Readonly = false // Windows does not support a read-only root filesystem
  207. if !isHyperV {
  208. s.Root.Path = c.BaseFS.Path() // This is not set for Hyper-V containers
  209. if !strings.HasSuffix(s.Root.Path, `\`) {
  210. s.Root.Path = s.Root.Path + `\` // Ensure a correctly formatted volume GUID path \\?\Volume{GUID}\
  211. }
  212. }
  213. // First boot optimization
  214. s.Windows.IgnoreFlushesDuringBoot = !c.HasBeenStartedBefore
  215. // In s.Windows.Resources
  216. cpuShares := uint16(c.HostConfig.CPUShares)
  217. cpuMaximum := uint16(c.HostConfig.CPUPercent) * 100
  218. cpuCount := uint64(c.HostConfig.CPUCount)
  219. if c.HostConfig.NanoCPUs > 0 {
  220. if isHyperV {
  221. cpuCount = uint64(c.HostConfig.NanoCPUs / 1e9)
  222. leftoverNanoCPUs := c.HostConfig.NanoCPUs % 1e9
  223. if leftoverNanoCPUs != 0 {
  224. cpuCount++
  225. cpuMaximum = uint16(c.HostConfig.NanoCPUs / int64(cpuCount) / (1e9 / 10000))
  226. if cpuMaximum < 1 {
  227. // The requested NanoCPUs is so small that we rounded to 0, use 1 instead
  228. cpuMaximum = 1
  229. }
  230. }
  231. } else {
  232. cpuMaximum = uint16(c.HostConfig.NanoCPUs / int64(sysinfo.NumCPU()) / (1e9 / 10000))
  233. if cpuMaximum < 1 {
  234. // The requested NanoCPUs is so small that we rounded to 0, use 1 instead
  235. cpuMaximum = 1
  236. }
  237. }
  238. }
  239. memoryLimit := uint64(c.HostConfig.Memory)
  240. s.Windows.Resources = &specs.WindowsResources{
  241. CPU: &specs.WindowsCPUResources{
  242. Maximum: &cpuMaximum,
  243. Shares: &cpuShares,
  244. Count: &cpuCount,
  245. },
  246. Memory: &specs.WindowsMemoryResources{
  247. Limit: &memoryLimit,
  248. },
  249. Storage: &specs.WindowsStorageResources{
  250. Bps: &c.HostConfig.IOMaximumBandwidth,
  251. Iops: &c.HostConfig.IOMaximumIOps,
  252. },
  253. }
  254. // Read and add credentials from the security options if a credential spec has been provided.
  255. if c.HostConfig.SecurityOpt != nil {
  256. cs := ""
  257. for _, sOpt := range c.HostConfig.SecurityOpt {
  258. sOpt = strings.ToLower(sOpt)
  259. if !strings.Contains(sOpt, "=") {
  260. return fmt.Errorf("invalid security option: no equals sign in supplied value %s", sOpt)
  261. }
  262. var splitsOpt []string
  263. splitsOpt = strings.SplitN(sOpt, "=", 2)
  264. if len(splitsOpt) != 2 {
  265. return fmt.Errorf("invalid security option: %s", sOpt)
  266. }
  267. if splitsOpt[0] != "credentialspec" {
  268. return fmt.Errorf("security option not supported: %s", splitsOpt[0])
  269. }
  270. var (
  271. match bool
  272. csValue string
  273. err error
  274. )
  275. if match, csValue = getCredentialSpec("file://", splitsOpt[1]); match {
  276. if csValue == "" {
  277. return fmt.Errorf("no value supplied for file:// credential spec security option")
  278. }
  279. if cs, err = readCredentialSpecFile(c.ID, daemon.root, filepath.Clean(csValue)); err != nil {
  280. return err
  281. }
  282. } else if match, csValue = getCredentialSpec("registry://", splitsOpt[1]); match {
  283. if csValue == "" {
  284. return fmt.Errorf("no value supplied for registry:// credential spec security option")
  285. }
  286. if cs, err = readCredentialSpecRegistry(c.ID, csValue); err != nil {
  287. return err
  288. }
  289. } else {
  290. return fmt.Errorf("invalid credential spec security option - value must be prefixed file:// or registry:// followed by a value")
  291. }
  292. }
  293. s.Windows.CredentialSpec = cs
  294. }
  295. // Assume we are not starting a container for a servicing operation
  296. s.Windows.Servicing = false
  297. return nil
  298. }
  299. // Sets the Linux-specific fields of the OCI spec
  300. // TODO: @jhowardmsft LCOW Support. We need to do a lot more pulling in what can
  301. // be pulled in from oci_linux.go.
  302. func (daemon *Daemon) createSpecLinuxFields(c *container.Container, s *specs.Spec) {
  303. if len(s.Process.Cwd) == 0 {
  304. s.Process.Cwd = `/`
  305. }
  306. s.Root.Path = "rootfs"
  307. s.Root.Readonly = c.HostConfig.ReadonlyRootfs
  308. }
  309. func escapeArgs(args []string) []string {
  310. escapedArgs := make([]string, len(args))
  311. for i, a := range args {
  312. escapedArgs[i] = windows.EscapeArg(a)
  313. }
  314. return escapedArgs
  315. }
  316. // mergeUlimits merge the Ulimits from HostConfig with daemon defaults, and update HostConfig
  317. // It will do nothing on non-Linux platform
  318. func (daemon *Daemon) mergeUlimits(c *containertypes.HostConfig) {
  319. return
  320. }
  321. // getCredentialSpec is a helper function to get the value of a credential spec supplied
  322. // on the CLI, stripping the prefix
  323. func getCredentialSpec(prefix, value string) (bool, string) {
  324. if strings.HasPrefix(value, prefix) {
  325. return true, strings.TrimPrefix(value, prefix)
  326. }
  327. return false, ""
  328. }
  329. // readCredentialSpecRegistry is a helper function to read a credential spec from
  330. // the registry. If not found, we return an empty string and warn in the log.
  331. // This allows for staging on machines which do not have the necessary components.
  332. func readCredentialSpecRegistry(id, name string) (string, error) {
  333. var (
  334. k registry.Key
  335. err error
  336. val string
  337. )
  338. if k, err = registry.OpenKey(registry.LOCAL_MACHINE, credentialSpecRegistryLocation, registry.QUERY_VALUE); err != nil {
  339. return "", fmt.Errorf("failed handling spec %q for container %s - %s could not be opened", name, id, credentialSpecRegistryLocation)
  340. }
  341. if val, _, err = k.GetStringValue(name); err != nil {
  342. if err == registry.ErrNotExist {
  343. return "", fmt.Errorf("credential spec %q for container %s as it was not found", name, id)
  344. }
  345. return "", fmt.Errorf("error %v reading credential spec %q from registry for container %s", err, name, id)
  346. }
  347. return val, nil
  348. }
  349. // readCredentialSpecFile is a helper function to read a credential spec from
  350. // a file. If not found, we return an empty string and warn in the log.
  351. // This allows for staging on machines which do not have the necessary components.
  352. func readCredentialSpecFile(id, root, location string) (string, error) {
  353. if filepath.IsAbs(location) {
  354. return "", fmt.Errorf("invalid credential spec - file:// path cannot be absolute")
  355. }
  356. base := filepath.Join(root, credentialSpecFileLocation)
  357. full := filepath.Join(base, location)
  358. if !strings.HasPrefix(full, base) {
  359. return "", fmt.Errorf("invalid credential spec - file:// path must be under %s", base)
  360. }
  361. bcontents, err := ioutil.ReadFile(full)
  362. if err != nil {
  363. return "", fmt.Errorf("credential spec '%s' for container %s as the file could not be read: %q", full, id, err)
  364. }
  365. return string(bcontents[:]), nil
  366. }