Skip to content

Commit

Permalink
fix: google distroless patching for libssl1.1 (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
CodeMonkeyLeet committed Feb 21, 2023
1 parent e28c02d commit 4c5b92c
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 13 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/test-images.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@
"image": "mcr.microsoft.com/oss/fluent/fluent-bit",
"tag": "v1.8.4",
"distro": "Google Distroless",
"description": "Custom dpkg/status.d, no apt"
"description": "Custom dpkg/status.d with base64 names, no apt"
},
{
"image": "mcr.microsoft.com/oss/open-policy-agent/opa",
"tag": "0.46.0",
"distro": "Google Distroless",
"description": "Custom dpkg/status.d with text names, no apt"
},
{
"image": "mcr.microsoft.com/oss/calico/cni",
Expand Down
27 changes: 19 additions & 8 deletions pkg/pkgmgr/dpkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func (dm *dpkgManager) probeDPKGStatus(ctx context.Context, toolImage string) er
// - sh and apt installed on the image
// - valid dpkg status on the image
//
// Images Images with neither (i.e. Google Debian Distroless) should be patched with dpkgUnpackPackages
// Images Images with neither (i.e. Google Debian Distroless) should be patched with unpackAndMergeUpdates
//
// TODO: Support Debian images with valid dpkg status but missing tools. No current examples exist in test set
// i.e. extra RunOption to mount a copy of busybox-static or full apt install into the image and invoking that.
Expand Down Expand Up @@ -250,16 +250,20 @@ func (dm *dpkgManager) unpackAndMergeUpdates(ctx context.Context, updates types.
downloaded := updated.Dir(downloadPath).Run(llb.Shlex(downloadCmd)).Root()

// Scripted enumeration and dpkg unpack of all downloaded packages [layer to merge with target]
const extractTemplate = `find %s -name '*.deb' -exec dpkg-deb -x '{}' / \;`
extractCmd := fmt.Sprintf(extractTemplate, downloadPath)
const extractTemplate = `find %s -name '*.deb' -exec dpkg-deb -x '{}' %s \;`
extractCmd := fmt.Sprintf(extractTemplate, downloadPath, unpackPath)
unpacked := downloaded.Run(llb.Shlex(extractCmd)).Root()
unpackedToRoot := llb.Scratch().File(llb.Copy(unpacked, unpackPath, "/", &llb.CopyInfo{CopyDirContentsOnly: true}))

// Scripted extraction of all debinfo for version checking to separate layer into local mount
// Note that target dirs of shell commands need to be created before use
mkFolders := downloaded.File(llb.Mkdir(resultsPath, 0o744, llb.WithParents(true))).File(llb.Mkdir(dpkgStatusFolder, 0o744, llb.WithParents(true)))
const writeFieldsTemplate = `find . -name '*.deb' -exec sh -c "dpkg-deb -f {} > %s" \;`
writeFieldsCmd := fmt.Sprintf(writeFieldsTemplate, filepath.Join(resultsPath, "{}.fields"))
fieldsWritten := mkFolders.Dir(downloadPath).Run(llb.Shlex(writeFieldsCmd)).Root()
if err := buildkit.SolveToDocker(ctx, dm.config.Client, &fieldsWritten, dm.config.ConfigData, dm.config.ImageName+"-fields"); err != nil {
return nil, err
}

// Write the name and version of the packages applied to the results.manifest file for the host
const outputResultsTemplate = `find . -name '*.fields' -exec sh -c 'grep "^Package:\|^Version:" {} >> %s' \;`
Expand All @@ -271,23 +275,30 @@ func (dm *dpkgManager) unpackAndMergeUpdates(ctx context.Context, updates types.
}

// Update the status.d folder with the package info from the applied update packages
// Each .fields file contains the control information for the package updated in the status.d folder.
// The package name is used as the file name but has to deal with two possible idiosyncrasies:
// - Older distroless images had a bug where the file names were base64 encoded. If the base64 versions
// of the names were found in the folder previously, then we use those names.
// - Non-base64 encoded names don't use the full package name, but instead appear to be truncated to
// use the name up to the first period (e.g. 'libssl1' instead of 'libssl1.1')
const copyStatusTemplate = `find . -name '*.fields' -exec sh -c
"awk -v statusDir=%s -v statusdNames=\"(%s)\"
'BEGIN{split(statusdNames,names); for (n in names) b64names[names[n]]=\"\"} {a[\$1]=\$2} END
{cmd = \"printf \" a[\"Package:\"] \" | base64\" ;
'BEGIN{split(statusdNames,names); for (n in names) b64names[names[n]]=\"\"} {a[\$1]=\$2}
END{cmd = \"printf \" a[\"Package:\"] \" | base64\" ;
cmd | getline b64name ;
close(cmd) ;
outname = b64name in b64names ? b64name : a[\"Package:\"] ;
textname = a[\"Package:\"] ;
gsub(\"\\\\.[^.]*$\", \"\", textname);
outname = b64name in b64names ? b64name : textname;
outpath = statusDir \"/\" outname ;
printf \"cp \\\"%%s\\\" \\\"%%s\\\"\\\n\",FILENAME,outpath }'
{} | sh" \;`
copyStatusCmd := fmt.Sprintf(strings.ReplaceAll(copyStatusTemplate, "\n", ""), dpkgStatusFolder, dm.statusdNames)
statusUpdated := fieldsWritten.Dir(resultsPath).Run(llb.Shlex(copyStatusCmd)).Root()

// Diff unpacked packages layers from previous and merge with target
patchDiff := llb.Diff(downloaded, unpacked)
statusDiff := llb.Diff(fieldsWritten, statusUpdated)
merged := llb.Merge([]llb.State{dm.config.ImageState, patchDiff, statusDiff})
merged := llb.Merge([]llb.State{dm.config.ImageState, unpackedToRoot, statusDiff})
return &merged, nil
}

Expand Down
1 change: 1 addition & 0 deletions pkg/pkgmgr/pkgmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const (
copaPrefix = "copa-"
resultsPath = "/" + copaPrefix + "out"
downloadPath = "/" + copaPrefix + "downloads"
unpackPath = "/" + copaPrefix + "unpacked"
resultManifest = "results.manifest"
)

Expand Down
10 changes: 6 additions & 4 deletions pkg/pkgmgr/rpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,14 +366,16 @@ func (rm *rpmManager) unpackAndMergeUpdates(ctx context.Context, updates types.U
downloadCmd := fmt.Sprintf(aptDownloadTemplate, strings.Join(pkgStrings, " "))
downloaded := busyboxCopied.Run(llb.Shlex(downloadCmd)).Root()

// Scripted enumeration and rpm install of all downloaded packages [layer to merge with target]
const extractTemplate = `chroot %s ./busybox find . -name '*.rpm' -exec ./busybox rpm -i '{}' / \;`
// Scripted enumeration and rpm install of all downloaded packages under the download folder as root
// `rpm -i` doesn't support installing to a target directory, so chroot into the download folder to install the packages.
const extractTemplate = `chroot %s ./busybox find . -name '*.rpm' -exec ./busybox rpm -i '{}' \;`
extractCmd := fmt.Sprintf(extractTemplate, downloadPath)
unpacked := downloaded.Run(llb.Shlex(extractCmd)).Root()

// Diff out the layer containing the installed rpm changes under chroot with downloadPath as root
// Diff out busybox and downloaded rpm packages from the installed files under the download folder as root
// then move the results to normal root for the layer to merge with target image.
patchDiff := llb.Diff(downloaded, unpacked)
patchedRoot := llb.Scratch().File(llb.Copy(patchDiff, filepath.Join(downloadPath, "*"), "/", &llb.CopyInfo{AllowWildcard: true}))
patchedRoot := llb.Scratch().File(llb.Copy(patchDiff, downloadPath, "/", &llb.CopyInfo{CopyDirContentsOnly: true}))

// Scripted extraction of all rpm manifest fields for version checking to separate layer into local mount
// Note that target dirs of shell commands need to be created before use
Expand Down

0 comments on commit 4c5b92c

Please sign in to comment.