1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00
moby--moby/daemon/graphdriver/windows/windows.go
John Starks 6f8878872f Windows: Fix Hyper-V container ACLs for TP5 (#21974)
In TP5, Hyper-V containers need all image files ACLed so that the virtual
machine process can access them. This was fixed post-TP5 in Windows, but
for TP5 we need to explicitly add these ACLs.

Signed-off-by: John Starks <jostarks@microsoft.com>
2016-04-13 10:15:38 -07:00

805 lines
20 KiB
Go

//+build windows
package windows
import (
"bufio"
"crypto/sha512"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"syscall"
"time"
"unsafe"
"github.com/Microsoft/go-winio"
"github.com/Microsoft/go-winio/archive/tar"
"github.com/Microsoft/go-winio/backuptar"
"github.com/Microsoft/hcsshim"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/longpath"
"github.com/docker/docker/pkg/system"
"github.com/vbatts/tar-split/tar/storage"
)
// init registers the windows graph drivers to the register.
func init() {
graphdriver.Register("windowsfilter", InitFilter)
graphdriver.Register("windowsdiff", InitDiff)
}
const (
// diffDriver is an hcsshim driver type
diffDriver = iota
// filterDriver is an hcsshim driver type
filterDriver
)
// Driver represents a windows graph driver.
type Driver struct {
// info stores the shim driver information
info hcsshim.DriverInfo
}
var _ graphdriver.DiffGetterDriver = &Driver{}
func isTP5OrOlder() bool {
return system.GetOSVersion().Build <= 14300
}
// InitFilter returns a new Windows storage filter driver.
func InitFilter(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
logrus.Debugf("WindowsGraphDriver InitFilter at %s", home)
d := &Driver{
info: hcsshim.DriverInfo{
HomeDir: home,
Flavour: filterDriver,
},
}
return d, nil
}
// InitDiff returns a new Windows differencing disk driver.
func InitDiff(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
logrus.Debugf("WindowsGraphDriver InitDiff at %s", home)
d := &Driver{
info: hcsshim.DriverInfo{
HomeDir: home,
Flavour: diffDriver,
},
}
return d, nil
}
// String returns the string representation of a driver.
func (d *Driver) String() string {
switch d.info.Flavour {
case diffDriver:
return "windowsdiff"
case filterDriver:
return "windowsfilter"
default:
return "Unknown driver flavour"
}
}
// Status returns the status of the driver.
func (d *Driver) Status() [][2]string {
return [][2]string{
{"Windows", ""},
}
}
// Exists returns true if the given id is registered with this driver.
func (d *Driver) Exists(id string) bool {
rID, err := d.resolveID(id)
if err != nil {
return false
}
result, err := hcsshim.LayerExists(d.info, rID)
if err != nil {
return false
}
return result
}
// CreateReadWrite creates a layer that is writable for use as a container
// file system.
func (d *Driver) CreateReadWrite(id, parent, mountLabel string, storageOpt map[string]string) error {
return d.create(id, parent, mountLabel, false, storageOpt)
}
// Create creates a new read-only layer with the given id.
func (d *Driver) Create(id, parent, mountLabel string, storageOpt map[string]string) error {
return d.create(id, parent, mountLabel, true, storageOpt)
}
func (d *Driver) create(id, parent, mountLabel string, readOnly bool, storageOpt map[string]string) error {
if len(storageOpt) != 0 {
return fmt.Errorf("--storage-opt is not supported for windows")
}
rPId, err := d.resolveID(parent)
if err != nil {
return err
}
parentChain, err := d.getLayerChain(rPId)
if err != nil {
return err
}
var layerChain []string
if rPId != "" {
parentPath, err := hcsshim.GetLayerMountPath(d.info, rPId)
if err != nil {
return err
}
if _, err := os.Stat(filepath.Join(parentPath, "Files")); err == nil {
// This is a legitimate parent layer (not the empty "-init" layer),
// so include it in the layer chain.
layerChain = []string{parentPath}
}
}
layerChain = append(layerChain, parentChain...)
if readOnly {
if err := hcsshim.CreateLayer(d.info, id, rPId); err != nil {
return err
}
} else {
var parentPath string
if len(layerChain) != 0 {
parentPath = layerChain[0]
}
if isTP5OrOlder() {
// Pre-create the layer directory, providing an ACL to give the Hyper-V Virtual Machines
// group access. This is necessary to ensure that Hyper-V containers can access the
// virtual machine data. This is not necessary post-TP5.
path, err := syscall.UTF16FromString(filepath.Join(d.info.HomeDir, id))
if err != nil {
return err
}
// Give system and administrators full control, and VMs read, write, and execute.
// Mark these ACEs as inherited.
sd, err := winio.SddlToSecurityDescriptor("D:(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FRFWFX;;;S-1-5-83-0)")
if err != nil {
return err
}
err = syscall.CreateDirectory(&path[0], &syscall.SecurityAttributes{
Length: uint32(unsafe.Sizeof(syscall.SecurityAttributes{})),
SecurityDescriptor: uintptr(unsafe.Pointer(&sd[0])),
})
if err != nil {
return err
}
}
if err := hcsshim.CreateSandboxLayer(d.info, id, parentPath, layerChain); err != nil {
return err
}
}
if _, err := os.Lstat(d.dir(parent)); err != nil {
if err2 := hcsshim.DestroyLayer(d.info, id); err2 != nil {
logrus.Warnf("Failed to DestroyLayer %s: %s", id, err2)
}
return fmt.Errorf("Cannot create layer with missing parent %s: %s", parent, err)
}
if err := d.setLayerChain(id, layerChain); err != nil {
if err2 := hcsshim.DestroyLayer(d.info, id); err2 != nil {
logrus.Warnf("Failed to DestroyLayer %s: %s", id, err2)
}
return err
}
return nil
}
// dir returns the absolute path to the layer.
func (d *Driver) dir(id string) string {
return filepath.Join(d.info.HomeDir, filepath.Base(id))
}
// Remove unmounts and removes the dir information.
func (d *Driver) Remove(id string) error {
rID, err := d.resolveID(id)
if err != nil {
return err
}
os.RemoveAll(filepath.Join(d.info.HomeDir, "sysfile-backups", rID)) // ok to fail
return hcsshim.DestroyLayer(d.info, rID)
}
// Get returns the rootfs path for the id. This will mount the dir at it's given path.
func (d *Driver) Get(id, mountLabel string) (string, error) {
logrus.Debugf("WindowsGraphDriver Get() id %s mountLabel %s", id, mountLabel)
var dir string
rID, err := d.resolveID(id)
if err != nil {
return "", err
}
// Getting the layer paths must be done outside of the lock.
layerChain, err := d.getLayerChain(rID)
if err != nil {
return "", err
}
if err := hcsshim.ActivateLayer(d.info, rID); err != nil {
return "", err
}
if err := hcsshim.PrepareLayer(d.info, rID, layerChain); err != nil {
if err2 := hcsshim.DeactivateLayer(d.info, rID); err2 != nil {
logrus.Warnf("Failed to Deactivate %s: %s", id, err)
}
return "", err
}
mountPath, err := hcsshim.GetLayerMountPath(d.info, rID)
if err != nil {
if err2 := hcsshim.DeactivateLayer(d.info, rID); err2 != nil {
logrus.Warnf("Failed to Deactivate %s: %s", id, err)
}
return "", err
}
// If the layer has a mount path, use that. Otherwise, use the
// folder path.
if mountPath != "" {
dir = mountPath
} else {
dir = d.dir(id)
}
return dir, nil
}
// Put adds a new layer to the driver.
func (d *Driver) Put(id string) error {
logrus.Debugf("WindowsGraphDriver Put() id %s", id)
rID, err := d.resolveID(id)
if err != nil {
return err
}
if err := hcsshim.UnprepareLayer(d.info, rID); err != nil {
return err
}
return hcsshim.DeactivateLayer(d.info, rID)
}
// Cleanup ensures the information the driver stores is properly removed.
func (d *Driver) Cleanup() error {
return nil
}
// Diff produces an archive of the changes between the specified
// layer and its parent layer which may be "".
// The layer should be mounted when calling this function
func (d *Driver) Diff(id, parent string) (_ archive.Archive, err error) {
rID, err := d.resolveID(id)
if err != nil {
return
}
layerChain, err := d.getLayerChain(rID)
if err != nil {
return
}
// this is assuming that the layer is unmounted
if err := hcsshim.UnprepareLayer(d.info, rID); err != nil {
return nil, err
}
defer func() {
if err := hcsshim.PrepareLayer(d.info, rID, layerChain); err != nil {
logrus.Warnf("Failed to Deactivate %s: %s", rID, err)
}
}()
arch, err := d.exportLayer(rID, layerChain)
if err != nil {
return
}
return ioutils.NewReadCloserWrapper(arch, func() error {
return arch.Close()
}), nil
}
// Changes produces a list of changes between the specified layer
// and its parent layer. If parent is "", then all changes will be ADD changes.
// The layer should be mounted when calling this function
func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
rID, err := d.resolveID(id)
if err != nil {
return nil, err
}
parentChain, err := d.getLayerChain(rID)
if err != nil {
return nil, err
}
// this is assuming that the layer is unmounted
if err := hcsshim.UnprepareLayer(d.info, rID); err != nil {
return nil, err
}
defer func() {
if err := hcsshim.PrepareLayer(d.info, rID, parentChain); err != nil {
logrus.Warnf("Failed to Deactivate %s: %s", rID, err)
}
}()
r, err := hcsshim.NewLayerReader(d.info, id, parentChain)
if err != nil {
return nil, err
}
defer r.Close()
var changes []archive.Change
for {
name, _, fileInfo, err := r.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
name = filepath.ToSlash(name)
if fileInfo == nil {
changes = append(changes, archive.Change{Path: name, Kind: archive.ChangeDelete})
} else {
// Currently there is no way to tell between an add and a modify.
changes = append(changes, archive.Change{Path: name, Kind: archive.ChangeModify})
}
}
return changes, nil
}
// ApplyDiff extracts the changeset from the given diff into the
// layer with the specified id and parent, returning the size of the
// new layer in bytes.
// The layer should not be mounted when calling this function
func (d *Driver) ApplyDiff(id, parent string, diff archive.Reader) (int64, error) {
if d.info.Flavour == diffDriver {
start := time.Now().UTC()
logrus.Debugf("WindowsGraphDriver ApplyDiff: Start untar layer")
destination := d.dir(id)
destination = filepath.Dir(destination)
size, err := chrootarchive.ApplyUncompressedLayer(destination, diff, nil)
if err != nil {
return 0, err
}
logrus.Debugf("WindowsGraphDriver ApplyDiff: Untar time: %vs", time.Now().UTC().Sub(start).Seconds())
return size, nil
}
var layerChain []string
if parent != "" {
rPId, err := d.resolveID(parent)
if err != nil {
return 0, err
}
parentChain, err := d.getLayerChain(rPId)
if err != nil {
return 0, err
}
parentPath, err := hcsshim.GetLayerMountPath(d.info, rPId)
if err != nil {
return 0, err
}
layerChain = append(layerChain, parentPath)
layerChain = append(layerChain, parentChain...)
}
size, err := d.importLayer(id, diff, layerChain)
if err != nil {
return 0, err
}
if err = d.setLayerChain(id, layerChain); err != nil {
return 0, err
}
return size, nil
}
// DiffSize calculates the changes between the specified layer
// and its parent and returns the size in bytes of the changes
// relative to its base filesystem directory.
func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
rPId, err := d.resolveID(parent)
if err != nil {
return
}
changes, err := d.Changes(id, rPId)
if err != nil {
return
}
layerFs, err := d.Get(id, "")
if err != nil {
return
}
defer d.Put(id)
return archive.ChangesSize(layerFs, changes), nil
}
// CustomImageInfo is the object returned by the driver describing the base
// image.
type CustomImageInfo struct {
ID string
Name string
Version string
Path string
Size int64
CreatedTime time.Time
OSVersion string `json:"-"`
OSFeatures []string `json:"-"`
}
// GetCustomImageInfos returns the image infos for window specific
// base images which should always be present.
func (d *Driver) GetCustomImageInfos() ([]CustomImageInfo, error) {
strData, err := hcsshim.GetSharedBaseImages()
if err != nil {
return nil, fmt.Errorf("Failed to restore base images: %s", err)
}
type customImageInfoList struct {
Images []CustomImageInfo
}
var infoData customImageInfoList
if err = json.Unmarshal([]byte(strData), &infoData); err != nil {
err = fmt.Errorf("JSON unmarshal returned error=%s", err)
logrus.Error(err)
return nil, err
}
var images []CustomImageInfo
for _, imageData := range infoData.Images {
folderName := filepath.Base(imageData.Path)
// Use crypto hash of the foldername to generate a docker style id.
h := sha512.Sum384([]byte(folderName))
id := fmt.Sprintf("%x", h[:32])
if err := d.Create(id, "", "", nil); err != nil {
return nil, err
}
// Create the alternate ID file.
if err := d.setID(id, folderName); err != nil {
return nil, err
}
imageData.ID = id
// For now, hard code that all base images except nanoserver depend on win32k support
if imageData.Name != "NanoServer" {
imageData.OSFeatures = append(imageData.OSFeatures, "win32k")
}
versionData := strings.Split(imageData.Version, ".")
if len(versionData) != 4 {
logrus.Warn("Could not parse Windows version %s", imageData.Version)
} else {
// Include just major.minor.build, skip the fourth version field, which does not influence
// OS compatibility.
imageData.OSVersion = strings.Join(versionData[:3], ".")
}
images = append(images, imageData)
}
return images, nil
}
// GetMetadata returns custom driver information.
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
m := make(map[string]string)
m["dir"] = d.dir(id)
return m, nil
}
func writeTarFromLayer(r hcsshim.LayerReader, w io.Writer) error {
t := tar.NewWriter(w)
for {
name, size, fileInfo, err := r.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
if fileInfo == nil {
// Write a whiteout file.
hdr := &tar.Header{
Name: filepath.ToSlash(filepath.Join(filepath.Dir(name), archive.WhiteoutPrefix+filepath.Base(name))),
}
err := t.WriteHeader(hdr)
if err != nil {
return err
}
} else {
err = backuptar.WriteTarFileFromBackupStream(t, r, name, size, fileInfo)
if err != nil {
return err
}
}
}
return t.Close()
}
// exportLayer generates an archive from a layer based on the given ID.
func (d *Driver) exportLayer(id string, parentLayerPaths []string) (archive.Archive, error) {
var r hcsshim.LayerReader
r, err := hcsshim.NewLayerReader(d.info, id, parentLayerPaths)
if err != nil {
return nil, err
}
archive, w := io.Pipe()
go func() {
err := writeTarFromLayer(r, w)
cerr := r.Close()
if err == nil {
err = cerr
}
w.CloseWithError(err)
}()
return archive, nil
}
func writeLayerFromTar(r archive.Reader, w hcsshim.LayerWriter) (int64, error) {
t := tar.NewReader(r)
hdr, err := t.Next()
totalSize := int64(0)
buf := bufio.NewWriter(nil)
for err == nil {
base := path.Base(hdr.Name)
if strings.HasPrefix(base, archive.WhiteoutPrefix) {
name := path.Join(path.Dir(hdr.Name), base[len(archive.WhiteoutPrefix):])
err = w.Remove(filepath.FromSlash(name))
if err != nil {
return 0, err
}
hdr, err = t.Next()
} else if hdr.Typeflag == tar.TypeLink {
err = w.AddLink(filepath.FromSlash(hdr.Name), filepath.FromSlash(hdr.Linkname))
if err != nil {
return 0, err
}
hdr, err = t.Next()
} else {
var (
name string
size int64
fileInfo *winio.FileBasicInfo
)
name, size, fileInfo, err = backuptar.FileInfoFromHeader(hdr)
if err != nil {
return 0, err
}
err = w.Add(filepath.FromSlash(name), fileInfo)
if err != nil {
return 0, err
}
buf.Reset(w)
// Add the Hyper-V Virutal Machine group ACE to the security descriptor
// for TP5 so that Xenons can access all files. This is not necessary
// for post-TP5 builds.
if isTP5OrOlder() {
if sddl, ok := hdr.Winheaders["sd"]; ok {
var ace string
if hdr.Typeflag == tar.TypeDir {
ace = "(A;OICI;0x1200a9;;;S-1-5-83-0)"
} else {
ace = "(A;;0x1200a9;;;S-1-5-83-0)"
}
if hdr.Winheaders["sd"], ok = addAceToSddlDacl(sddl, ace); !ok {
logrus.Debugf("failed to add VM ACE to %s", sddl)
}
}
}
hdr, err = backuptar.WriteBackupStreamFromTarFile(buf, t, hdr)
ferr := buf.Flush()
if ferr != nil {
err = ferr
}
totalSize += size
}
}
if err != io.EOF {
return 0, err
}
return totalSize, nil
}
func addAceToSddlDacl(sddl, ace string) (string, bool) {
daclStart := strings.Index(sddl, "D:")
if daclStart < 0 {
return sddl, false
}
dacl := sddl[daclStart:]
daclEnd := strings.Index(dacl, "S:")
if daclEnd < 0 {
daclEnd = len(dacl)
}
dacl = dacl[:daclEnd]
if strings.Contains(dacl, ace) {
return sddl, true
}
i := 2
for i+1 < len(dacl) {
if dacl[i] != '(' {
return sddl, false
}
if dacl[i+1] == 'A' {
break
}
i += 2
for p := 1; i < len(dacl) && p > 0; i++ {
if dacl[i] == '(' {
p++
} else if dacl[i] == ')' {
p--
}
}
}
return sddl[:daclStart+i] + ace + sddl[daclStart+i:], true
}
// importLayer adds a new layer to the tag and graph store based on the given data.
func (d *Driver) importLayer(id string, layerData archive.Reader, parentLayerPaths []string) (size int64, err error) {
var w hcsshim.LayerWriter
w, err = hcsshim.NewLayerWriter(d.info, id, parentLayerPaths)
if err != nil {
return
}
size, err = writeLayerFromTar(layerData, w)
if err != nil {
w.Close()
return
}
err = w.Close()
if err != nil {
return
}
return
}
// resolveID computes the layerID information based on the given id.
func (d *Driver) resolveID(id string) (string, error) {
content, err := ioutil.ReadFile(filepath.Join(d.dir(id), "layerID"))
if os.IsNotExist(err) {
return id, nil
} else if err != nil {
return "", err
}
return string(content), nil
}
// setID stores the layerId in disk.
func (d *Driver) setID(id, altID string) error {
err := ioutil.WriteFile(filepath.Join(d.dir(id), "layerId"), []byte(altID), 0600)
if err != nil {
return err
}
return nil
}
// getLayerChain returns the layer chain information.
func (d *Driver) getLayerChain(id string) ([]string, error) {
jPath := filepath.Join(d.dir(id), "layerchain.json")
content, err := ioutil.ReadFile(jPath)
if os.IsNotExist(err) {
return nil, nil
} else if err != nil {
return nil, fmt.Errorf("Unable to read layerchain file - %s", err)
}
var layerChain []string
err = json.Unmarshal(content, &layerChain)
if err != nil {
return nil, fmt.Errorf("Failed to unmarshall layerchain json - %s", err)
}
return layerChain, nil
}
// setLayerChain stores the layer chain information in disk.
func (d *Driver) setLayerChain(id string, chain []string) error {
content, err := json.Marshal(&chain)
if err != nil {
return fmt.Errorf("Failed to marshall layerchain json - %s", err)
}
jPath := filepath.Join(d.dir(id), "layerchain.json")
err = ioutil.WriteFile(jPath, content, 0600)
if err != nil {
return fmt.Errorf("Unable to write layerchain file - %s", err)
}
return nil
}
type fileGetCloserWithBackupPrivileges struct {
path string
}
func (fg *fileGetCloserWithBackupPrivileges) Get(filename string) (io.ReadCloser, error) {
var f *os.File
// Open the file while holding the Windows backup privilege. This ensures that the
// file can be opened even if the caller does not actually have access to it according
// to the security descriptor.
err := winio.RunWithPrivilege(winio.SeBackupPrivilege, func() error {
path := longpath.AddPrefix(filepath.Join(fg.path, filename))
p, err := syscall.UTF16FromString(path)
if err != nil {
return err
}
h, err := syscall.CreateFile(&p[0], syscall.GENERIC_READ, syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
if err != nil {
return &os.PathError{Op: "open", Path: path, Err: err}
}
f = os.NewFile(uintptr(h), path)
return nil
})
return f, err
}
func (fg *fileGetCloserWithBackupPrivileges) Close() error {
return nil
}
type fileGetDestroyCloser struct {
storage.FileGetter
path string
}
func (f *fileGetDestroyCloser) Close() error {
// TODO: activate layers and release here?
return os.RemoveAll(f.path)
}
// DiffGetter returns a FileGetCloser that can read files from the directory that
// contains files for the layer differences. Used for direct access for tar-split.
func (d *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) {
id, err := d.resolveID(id)
if err != nil {
return nil, err
}
return &fileGetCloserWithBackupPrivileges{d.dir(id)}, nil
}