Skip to content

Commit

Permalink
Add WindowsBaseLayer
Browse files Browse the repository at this point in the history
Required for storing images without Microsoft baselayers on WCOW daemons (i.e. buildpackages)

Adds the bcdhive_gen generator for the creation of BCD file, which uses that hivex library which has tricky run-time dependencies on darwin

- Replaces testhelper with implementation

Signed-off-by: Micah Young <[email protected]>
  • Loading branch information
Micah Young committed Oct 12, 2020
1 parent cea9fc5 commit 4a5f630
Show file tree
Hide file tree
Showing 14 changed files with 633 additions and 107 deletions.
17 changes: 16 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,20 @@ lint: install-golangci-lint
@echo "> Linting code..."
@golangci-lint run -c golangci.yaml

test: format lint
generate: build-bcdhive-generator
ifneq ($(OS),Windows_NT)
$(GOCMD) generate ./...
else
@echo "> Cannot generate on Windows"
endif

build-bcdhive-generator:
ifneq ($(OS),Windows_NT)
@echo "> Building bcdhive-generator in Docker"
docker build tools/bcdhive_generator --tag bcdhive-generator
else
@echo "> Cannot generate on Windows"
endif

test: generate format lint
$(GOCMD) test -parallel=1 -count=1 -v ./...
8 changes: 0 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,6 @@ github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-containerregistry v0.0.0-20200311163244-4b1985e5ea21 h1:rz5VzU1xKHR8HDtifeAJ+SPwE0v3YW0AEien/Lobgww=
Expand Down
31 changes: 31 additions & 0 deletions layer/bcdhive_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 85 additions & 0 deletions layer/windows_baselayer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package layer

import (
"archive/tar"
"bytes"
"io"
)

// Generate using `make generate`
//go:generate docker run --rm -v $PWD:/out/ bcdhive-generator -file=/out/layer/bcdhive_generated.go -package=layer -func=BaseLayerBCD

// Windows base layers must follow this pattern:
// \-> UtilityVM/Files/EFI/Microsoft/Boot/BCD (file must exist and a valid BCD format - from bcdhive_gen)
// \-> Files/Windows/System32/config/DEFAULT (file and must exist but can be empty)
// \-> Files/Windows/System32/config/SAM (file must exist but can be empty)
// \-> Files/Windows/System32/config/SECURITY (file must exist but can be empty)
// \-> Files/Windows/System32/config/SOFTWARE (file must exist but can be empty)
// \-> Files/Windows/System32/config/SYSTEM (file must exist but can be empty)
// Refs:
// https://github.com/microsoft/hcsshim/blob/master/internal/wclayer/legacy.go
// https://github.com/moby/moby/blob/master/daemon/graphdriver/windows/windows.go#L48
func WindowsBaseLayer() (io.Reader, error) {
bcdBytes, err := BaseLayerBCD()
if err != nil {
return nil, err
}

layerBuffer := &bytes.Buffer{}
tw := tar.NewWriter(layerBuffer)

_ = bcdBytes
if err := tw.WriteHeader(&tar.Header{Name: "UtilityVM", Typeflag: tar.TypeDir}); err != nil {
return nil, err
}
if err := tw.WriteHeader(&tar.Header{Name: "UtilityVM/Files", Typeflag: tar.TypeDir}); err != nil {
return nil, err
}
if err := tw.WriteHeader(&tar.Header{Name: "UtilityVM/Files/EFI", Typeflag: tar.TypeDir}); err != nil {
return nil, err
}
if err := tw.WriteHeader(&tar.Header{Name: "UtilityVM/Files/EFI/Microsoft", Typeflag: tar.TypeDir}); err != nil {
return nil, err
}
if err := tw.WriteHeader(&tar.Header{Name: "UtilityVM/Files/EFI/Microsoft/Boot", Typeflag: tar.TypeDir}); err != nil {
return nil, err
}

if err := tw.WriteHeader(&tar.Header{Name: "UtilityVM/Files/EFI/Microsoft/Boot/BCD", Size: int64(len(bcdBytes)), Mode: 0644}); err != nil {
return nil, err
}
if _, err := tw.Write(bcdBytes); err != nil {
return nil, err
}

if err := tw.WriteHeader(&tar.Header{Name: "Files", Typeflag: tar.TypeDir}); err != nil {
return nil, err
}
if err := tw.WriteHeader(&tar.Header{Name: "Files/Windows", Typeflag: tar.TypeDir}); err != nil {
return nil, err
}
if err := tw.WriteHeader(&tar.Header{Name: "Files/Windows/System32", Typeflag: tar.TypeDir}); err != nil {
return nil, err
}
if err := tw.WriteHeader(&tar.Header{Name: "Files/Windows/System32/config", Typeflag: tar.TypeDir}); err != nil {
return nil, err
}

if err := tw.WriteHeader(&tar.Header{Name: "Files/Windows/System32/config/DEFAULT", Size: 0, Mode: 0644}); err != nil {
return nil, err
}
if err := tw.WriteHeader(&tar.Header{Name: "Files/Windows/System32/config/SAM", Size: 0, Mode: 0644}); err != nil {
return nil, err
}
if err := tw.WriteHeader(&tar.Header{Name: "Files/Windows/System32/config/SECURITY", Size: 0, Mode: 0644}); err != nil {
return nil, err
}
if err := tw.WriteHeader(&tar.Header{Name: "Files/Windows/System32/config/SOFTWARE", Size: 0, Mode: 0644}); err != nil {
return nil, err
}
if err := tw.WriteHeader(&tar.Header{Name: "Files/Windows/System32/config/SYSTEM", Size: 0, Mode: 0644}); err != nil {
return nil, err
}

return layerBuffer, nil
}
1 change: 0 additions & 1 deletion local/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1027,7 +1027,6 @@ func testImage(t *testing.T, when spec.G, it spec.S) {
repoName,
dockerClient,
local.FromBaseImage(repoName),
local.WithPreviousImage(repoName),
)
h.AssertNil(t, err)

Expand Down
130 changes: 33 additions & 97 deletions testhelpers/testhelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,23 @@ package testhelpers

import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"math/rand"
"net/http"
"os"
"path"
"regexp"
"strings"
"sync"
"testing"
"time"

"github.com/buildpacks/imgutil/layer"

dockertypes "github.com/docker/docker/api/types"
dockercli "github.com/docker/docker/client"
"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -227,132 +225,70 @@ func CreateSingleFileTarReader(path, txt string) io.ReadCloser {
pw.CloseWithError(err)
}()

// Use the Linux writer, as this isn't a layer tar.
tw := tar.NewWriter(pw)
defer tw.Close()

err = writeTarSingleFileLinux(tw, path, txt) // Use the Linux writer, as this isn't a layer tar.
if err := tw.WriteHeader(&tar.Header{Name: path, Size: int64(len(txt)), Mode: 0644}); err != nil {
pw.CloseWithError(err)
}

if _, err := tw.Write([]byte(txt)); err != nil {
pw.CloseWithError(err)
}
}()

return pr
}

func CreateSingleFileLayerTar(layerPath, txt, osType string) (string, error) {
tarFile, err := ioutil.TempFile("", "create-single-file-layer-tar-path")
if err != nil {
return "", err
}
defer tarFile.Close()

tw := tar.NewWriter(tarFile)
defer tw.Close()
type layerWriter interface {
WriteHeader(*tar.Header) error
Write([]byte) (int, error)
Close() error
}

// regular Linux layer
writeFunc := writeTarSingleFileLinux
func getLayerWriter(osType string, file *os.File) layerWriter {
if osType == "windows" {
// regular Windows layer
writeFunc = writeTarSingleFileWindows
return layer.NewWindowsWriter(file)
}
return tar.NewWriter(file)
}

err = writeFunc(tw, layerPath, txt)
func CreateSingleFileLayerTar(layerPath, txt, osType string) (string, error) {
tarFile, err := ioutil.TempFile("", "create-single-file-layer-tar-path")
if err != nil {
return "", err
}
defer tarFile.Close()

return tarFile.Name(), nil
}
tw := getLayerWriter(osType, tarFile)
defer tw.Close()

func writeTarSingleFileLinux(tw *tar.Writer, layerPath, txt string) error {
if err := tw.WriteHeader(&tar.Header{Name: layerPath, Size: int64(len(txt)), Mode: 0644}); err != nil {
return err
return "", err
}

if _, err := tw.Write([]byte(txt)); err != nil {
return err
return "", err
}

return nil
return tarFile.Name(), nil
}

// WindowsBaseLayer returns a minimal windows base layer.
// This base layer cannot use for running but can be used for saving to a Windows daemon and container creation.
// Windows image layers must follow this pattern¹:
// - base layer² (always required; tar file with relative paths without "/" prefix; all parent directories require own tar entries)
// \-> Files/Windows/System32/config/DEFAULT (file must exist but can be empty)
// \-> Files/Windows/System32/config/SAM (file must exist but can be empty)
// \-> Files/Windows/System32/config/SECURITY (file must exist but can be empty)
// \-> Files/Windows/System32/config/SOFTWARE (file must exist but can be empty)
// \-> Files/Windows/System32/config/SYSTEM (file must exist but can be empty)
// \-> UtilityVM/Files/EFI/Microsoft/Boot/BCD (file must exist and a valid BCD format - via `bcdedit` tool as below)
// - normal or top layer (optional; tar file with relative paths without "/" prefix; all parent directories require own tar entries)
// \-> Files/ (required directory entry)
// \-> Files/mystuff.exe (optional container filesystem files - C:\mystuff.exe)
// \-> Hives/ (required directory entry)
// \-> Hives/DefaultUser_Delta (optional Windows reg hive delta; BCD format - HKEY_USERS\.DEFAULT additional content)
// \-> Hives/Sam_Delta (optional Windows reg hive delta; BCD format - HKEY_LOCAL_MACHINE\SAM additional content)
// \-> Hives/Security_Delta (optional Windows reg hive delta; BCD format - HKEY_LOCAL_MACHINE\SECURITY additional content)
// \-> Hives/Software_Delta (optional Windows reg hive delta; BCD format - HKEY_LOCAL_MACHINE\SOFTWARE additional content)
// \-> Hives/System_Delta (optional Windows reg hive delta; BCD format - HKEY_LOCAL_MACHINE\SYSTEM additional content)
// 1. This was all discovered experimentally and should be considered an undocumented API, subject to change when the Windows Daemon internals change
// 2. There are many other files in an "real" base layer but this is the minimum set which a Daemon can store and use to create an container
func WindowsBaseLayer(t *testing.T) string {
tarFile, err := ioutil.TempFile("", "windows-base-layer.tar")
AssertNil(t, err)
AssertNil(t, tarFile.Close())

tw := tar.NewWriter(tarFile)

//Valid BCD file required, containing Windows Boot Manager and Windows Boot Loader sections
//Note: Gzip/Base64 encoded only to inline the binary BCD file here
//CMD: `bcdedit /createstore c:\output-bcd & bcdedit /create {6a6c1f1b-59d4-11ea-9438-9402e6abd998} /d buildpacks.io /application osloader /store c:\output-bcd & bcdedit /create {bootmgr} /store c:\output-bcd & bcdedit /set {bootmgr} default {6a6c1f1b-59d4-11ea-9438-9402e6abd998} /store c:\output-bcd & bcdedit /enum all /store c:\output-bcd`
//BASH: `gzip --stdout --best output-bcd | base64`
bcdGzipBase64 := "H4sIABeDWF4CA+1YTWgTQRR+m2zSFItGkaJQcKXgyZX8bNKklxatUvyJoh4qKjS7O7GxzQ9Jai210JtFQXrUW4/e2ostCF4EoSCCF6HHQi9FWsxJepH43s5us02XYlFBcL7l7U7evHnzzZtvoJ0Ke5A7BABkoU/f3qxvfZEkbPuBg9oKNcK8fQ/68LkHBvTiuwTjUIOy9VZBR68J++NZ/sXU/J2vR99d/thxdzOz0PqbYp63+CqFWuH7wg+LGwj8MfRuvnovqiAgICAgICAgICAgIPB/YETPF8H+/96Bcw9A7flGo1EcPQvrRVginw99Q6cAfHbsEDYwpEFt+s7Y306PuTrQMmziVq1UYTdLpRr5HmNsdRSg38+N8o5Z9w7yzCDlt8ceB+rT7HlPQB8cAYn/CND9hOLjUZaf3xIEjlGelhiJX2wE7gOfy7lQeG2tU4Gt7vZlZ50KNNd5I7B7ncSVvlc91tmGdl1/yIxaFbYxph4EmLXzq/2nd/KHpGZ+RfbO71XHM2hTyWzSiOaiuppIm5oajbKsmtbiKXxFYiyZ1c10OjUNUMccZZxkGDkMsKpBfBS/s68KPHlbX3Kv10HDBvnXkKezr06/Yu0CB90dUe5KvlzLl7icNjB2LOeDbYn30VqpJmvofzTaZo2d8/H6k11hk5lsgQH1n4cLMACRlofDi/eItJc3ueoS15SbdwhN3od33eItQQqDorFIhPOVacyMH5SwbPO9PVlmgy79Un3I2n9Rvych+Ff0+6Grc9ldF6c/7PfWV9hDX1Sji2OswIq1qrOPiz5eqxU/7/Oab8XvvQ+f5b37cBityzUf1RqhOfqgvkW5qQ+bj6UPHcYhj1U2oQxZMGAUqnAOPSWMI33Pyc3z5j7P7vM2GDzgeUubLJtKxgw1YZimqrGeiJo1jKiai8f0uKaZWk86Md3UPdWezuiqzMd66XZV9q7XRuDgunXr1AfhXToFuy4rgaZOup82dvFwdLIW/D2djAQ4t3qA961avMIWL8leA30v5SuFiWyFXSuZ+VyemV68KIdXfYYlbz1lXLxicUtPcec8zwa5z9EXxYbb9uqLeExBEnWVRGVFIYemgwoJSKPeNGxF8WHYr6JHgzik7FYEYuinkTpGpvFJwfQO/5ch8beGgICAgICAwL+BnwAgqcMAIAAA"
bcdGzip, _ := base64.StdEncoding.DecodeString(bcdGzipBase64)
bcdReader, _ := gzip.NewReader(bytes.NewBuffer(bcdGzip))
bcdBytes, _ := ioutil.ReadAll(bcdReader)

AssertNil(t, tw.WriteHeader(&tar.Header{Name: "Files", Typeflag: tar.TypeDir}))
AssertNil(t, tw.WriteHeader(&tar.Header{Name: "Files/Windows", Typeflag: tar.TypeDir}))
AssertNil(t, tw.WriteHeader(&tar.Header{Name: "Files/Windows/System32", Typeflag: tar.TypeDir}))
AssertNil(t, tw.WriteHeader(&tar.Header{Name: "Files/Windows/System32/config", Typeflag: tar.TypeDir}))

AssertNil(t, tw.WriteHeader(&tar.Header{Name: "UtilityVM", Typeflag: tar.TypeDir}))
AssertNil(t, tw.WriteHeader(&tar.Header{Name: "UtilityVM/Files", Typeflag: tar.TypeDir}))
AssertNil(t, tw.WriteHeader(&tar.Header{Name: "UtilityVM/Files/EFI", Typeflag: tar.TypeDir}))
AssertNil(t, tw.WriteHeader(&tar.Header{Name: "UtilityVM/Files/EFI/Microsoft", Typeflag: tar.TypeDir}))
AssertNil(t, tw.WriteHeader(&tar.Header{Name: "UtilityVM/Files/EFI/Microsoft/Boot", Typeflag: tar.TypeDir}))

AssertNil(t, tw.WriteHeader(&tar.Header{Name: "Files/Windows/System32/config/DEFAULT", Size: 0, Mode: 0644}))
AssertNil(t, tw.WriteHeader(&tar.Header{Name: "Files/Windows/System32/config/SAM", Size: 0, Mode: 0644}))
AssertNil(t, tw.WriteHeader(&tar.Header{Name: "Files/Windows/System32/config/SECURITY", Size: 0, Mode: 0644}))
AssertNil(t, tw.WriteHeader(&tar.Header{Name: "Files/Windows/System32/config/SOFTWARE", Size: 0, Mode: 0644}))
AssertNil(t, tw.WriteHeader(&tar.Header{Name: "Files/Windows/System32/config/SYSTEM", Size: 0, Mode: 0644}))

AssertNil(t, tw.WriteHeader(&tar.Header{Name: "UtilityVM/Files/EFI/Microsoft/Boot/BCD", Size: int64(len(bcdBytes)), Mode: 0644}))
_, err = tw.Write(bcdBytes)
baseLayer, err := layer.WindowsBaseLayer()
AssertNil(t, err)

return tarFile.Name()
}

func writeTarSingleFileWindows(tw *tar.Writer, containerPath, txt string) error {
// root Windows layer directories
if err := tw.WriteHeader(&tar.Header{Name: "Files", Typeflag: tar.TypeDir}); err != nil {
return err
}
if err := tw.WriteHeader(&tar.Header{Name: "Hives", Typeflag: tar.TypeDir}); err != nil {
return err
}

// prepend file entries with "Files"
layerPath := path.Join("Files", containerPath)
if err := tw.WriteHeader(&tar.Header{Name: layerPath, Size: int64(len(txt)), Mode: 0644}); err != nil {
return err
}
baseLayerBytes, err := ioutil.ReadAll(baseLayer)
AssertNil(t, err)

if _, err := tw.Write([]byte(txt)); err != nil {
return err
}
AssertNil(t, ioutil.WriteFile(tarFile.Name(), baseLayerBytes, 0666))

return nil
return tarFile.Name()
}

func FetchManifestLayers(t *testing.T, repoName string) []string {
Expand Down
14 changes: 14 additions & 0 deletions tools/bcdhive_generator/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM golang:1.15-buster

RUN apt update \
&& apt install -y libhivex-dev libhivex-bin libwin-hivex-perl

COPY . /src

WORKDIR /src

RUN go generate ./ \
&& go test -parallel=1 -count=1 -v . \
&& go install .

ENTRYPOINT ["/go/bin/bcdhive_gen"]
Loading

0 comments on commit 4a5f630

Please sign in to comment.