rootfs: Add support for rootfs-overlay and bump to buildah v1.22.1-0.202108

Allows users to specify a readonly rootfs with :O, in exchange podman will create a writable overlay.

bump builah to v1.22.1-0.20210823173221-da2b428c56ce

[NO TESTS NEEDED]

Signed-off-by: flouthoc <flouthoc.git@gmail.com>
This commit is contained in:
flouthoc
2021-08-25 16:13:17 +05:30
committed by Aditya Rajan
parent b603c7a4b9
commit a55e2a00fc
54 changed files with 1868 additions and 412 deletions

View File

@@ -26,6 +26,7 @@ import (
"github.com/containers/buildah/copier"
"github.com/containers/buildah/define"
"github.com/containers/buildah/pkg/overlay"
"github.com/containers/buildah/pkg/sshagent"
"github.com/containers/buildah/util"
"github.com/containers/common/pkg/capabilities"
"github.com/containers/common/pkg/chown"
@@ -207,7 +208,7 @@ func (b *Builder) Run(command []string, options RunOptions) error {
}
if !(contains(volumes, "/etc/resolv.conf") || (len(b.CommonBuildOpts.DNSServers) == 1 && strings.ToLower(b.CommonBuildOpts.DNSServers[0]) == "none")) {
resolvFile, err := b.addNetworkConfig(path, "/etc/resolv.conf", rootIDPair, b.CommonBuildOpts.DNSServers, b.CommonBuildOpts.DNSSearch, b.CommonBuildOpts.DNSOptions, namespaceOptions)
resolvFile, err := b.addResolvConf(path, rootIDPair, b.CommonBuildOpts.DNSServers, b.CommonBuildOpts.DNSSearch, b.CommonBuildOpts.DNSOptions, namespaceOptions)
if err != nil {
return err
}
@@ -246,14 +247,17 @@ rootless=%d
bindFiles["/run/.containerenv"] = containerenvPath
}
runMountTargets, err := b.setupMounts(mountPoint, spec, path, options.Mounts, bindFiles, volumes, b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize, namespaceOptions, options.Secrets, options.RunMounts)
runArtifacts, err := b.setupMounts(mountPoint, spec, path, options.Mounts, bindFiles, volumes, b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize, namespaceOptions, options.Secrets, options.SSHSources, options.RunMounts)
if err != nil {
return errors.Wrapf(err, "error resolving mountpoints for container %q", b.ContainerID)
}
if runArtifacts.SSHAuthSock != "" {
sshenv := "SSH_AUTH_SOCK=" + runArtifacts.SSHAuthSock
spec.Process.Env = append(spec.Process.Env, sshenv)
}
defer func() {
if err := cleanupRunMounts(runMountTargets, mountPoint); err != nil {
if err := cleanupRunMounts(mountPoint, runArtifacts); err != nil {
options.Logger.Errorf("unabe to cleanup run mounts %v", err)
}
}()
@@ -409,7 +413,7 @@ func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, builtin
return mounts, nil
}
func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath string, optionMounts []specs.Mount, bindFiles map[string]string, builtinVolumes, volumeMounts []string, shmSize string, namespaceOptions define.NamespaceOptions, secrets map[string]string, runFileMounts []string) (runMountTargets []string, err error) {
func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath string, optionMounts []specs.Mount, bindFiles map[string]string, builtinVolumes, volumeMounts []string, shmSize string, namespaceOptions define.NamespaceOptions, secrets map[string]string, sshSources map[string]*sshagent.Source, runFileMounts []string) (*runMountArtifacts, error) {
// Start building a new list of mounts.
var mounts []specs.Mount
haveMount := func(destination string) bool {
@@ -517,18 +521,17 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath st
subscriptionMounts := subscriptions.MountsWithUIDGID(b.MountLabel, cdir, b.DefaultMountsFilePath, mountPoint, int(rootUID), int(rootGID), unshare.IsRootless(), false)
// Get the list of mounts that are just for this Run() call.
runMounts, runTargets, err := runSetupRunMounts(runFileMounts, secrets, b.MountLabel, cdir, spec.Linux.UIDMappings, spec.Linux.GIDMappings)
// TODO: acui: de-spaghettify run mounts
runMounts, mountArtifacts, err := runSetupRunMounts(runFileMounts, secrets, sshSources, b.MountLabel, cdir, spec.Linux.UIDMappings, spec.Linux.GIDMappings, b.ProcessLabel)
if err != nil {
return nil, err
}
// Add temporary copies of the contents of volume locations at the
// volume locations, unless we already have something there.
builtins, err := runSetupBuiltinVolumes(b.MountLabel, mountPoint, cdir, builtinVolumes, int(rootUID), int(rootGID))
if err != nil {
return nil, err
}
// Get host UID and GID of the container process.
processUID, processGID, err := util.GetHostIDs(spec.Linux.UIDMappings, spec.Linux.GIDMappings, spec.Process.User.UID, spec.Process.User.GID)
if err != nil {
@@ -554,22 +557,55 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath st
// Set the list in the spec.
spec.Mounts = mounts
return runTargets, nil
return mountArtifacts, nil
}
// addNetworkConfig copies files from host and sets them up to bind mount into container
func (b *Builder) addNetworkConfig(rdir, hostPath string, chownOpts *idtools.IDPair, dnsServers, dnsSearch, dnsOptions []string, namespaceOptions define.NamespaceOptions) (string, error) {
stat, err := os.Stat(hostPath)
// addResolvConf copies files from host and sets them up to bind mount into container
func (b *Builder) addResolvConf(rdir string, chownOpts *idtools.IDPair, dnsServers, dnsSearch, dnsOptions []string, namespaceOptions define.NamespaceOptions) (string, error) {
resolvConf := "/etc/resolv.conf"
stat, err := os.Stat(resolvConf)
if err != nil {
return "", err
}
contents, err := ioutil.ReadFile(hostPath)
if err != nil {
contents, err := ioutil.ReadFile(resolvConf)
// resolv.conf doesn't have to exists
if err != nil && !os.IsNotExist(err) {
return "", err
}
netns := false
ns := namespaceOptions.Find(string(spec.NetworkNamespace))
if ns != nil && !ns.Host {
netns = true
}
nameservers := resolvconf.GetNameservers(contents, types.IPv4)
// check if systemd-resolved is used, assume it is used when 127.0.0.53 is the only nameserver
if len(nameservers) == 1 && nameservers[0] == "127.0.0.53" && netns {
// read the actual resolv.conf file for systemd-resolved
resolvedContents, err := ioutil.ReadFile("/run/systemd/resolve/resolv.conf")
if err != nil {
if !os.IsNotExist(err) {
return "", errors.Wrapf(err, "detected that systemd-resolved is in use, but could not locate real resolv.conf")
}
} else {
contents = resolvedContents
}
}
// Ensure that the container's /etc/resolv.conf is compatible with its
// network configuration.
if netns {
// FIXME handle IPv6
resolve, err := resolvconf.FilterResolvDNS(contents, true)
if err != nil {
return "", errors.Wrapf(err, "error parsing host resolv.conf")
}
contents = resolve.Content
}
search := resolvconf.GetSearchDomains(contents)
nameservers := resolvconf.GetNameservers(contents, types.IP)
nameservers = resolvconf.GetNameservers(contents, types.IP)
options := resolvconf.GetOptions(contents)
defaultContainerConfig, err := config.Default()
@@ -582,7 +618,6 @@ func (b *Builder) addNetworkConfig(rdir, hostPath string, chownOpts *idtools.IDP
}
if b.Isolation == IsolationOCIRootless {
ns := namespaceOptions.Find(string(specs.NetworkNamespace))
if ns != nil && !ns.Host && ns.Path == "" {
// if we are using slirp4netns, also add the built-in DNS server.
logrus.Debugf("adding slirp4netns 10.0.2.3 built-in DNS server")
@@ -607,7 +642,7 @@ func (b *Builder) addNetworkConfig(rdir, hostPath string, chownOpts *idtools.IDP
options = dnsOptions
}
cfile := filepath.Join(rdir, filepath.Base(hostPath))
cfile := filepath.Join(rdir, filepath.Base(resolvConf))
if _, err = resolvconf.Build(cfile, nameservers, search, options); err != nil {
return "", errors.Wrapf(err, "error building resolv.conf for container %s", b.ContainerID)
}
@@ -702,6 +737,10 @@ func setupTerminal(g *generate.Generator, terminalPolicy TerminalPolicy, termina
}
func runUsingRuntime(isolation define.Isolation, options RunOptions, configureNetwork bool, configureNetworks, moreCreateArgs []string, spec *specs.Spec, bundlePath, containerName string) (wstatus unix.WaitStatus, err error) {
if options.Logger == nil {
options.Logger = logrus.StandardLogger()
}
// Lock the caller to a single OS-level thread.
runtime.LockOSThread()
@@ -1743,7 +1782,6 @@ func (b *Builder) cleanupTempVolumes() {
}
func (b *Builder) runSetupVolumeMounts(mountLabel string, volumeMounts []string, optionMounts []specs.Mount, rootUID, rootGID, processUID, processGID int) (mounts []specs.Mount, Err error) {
// Make sure the overlay directory is clean before running
containerDir, err := b.store.ContainerDirectory(b.ContainerID)
if err != nil {
@@ -1805,7 +1843,6 @@ func (b *Builder) runSetupVolumeMounts(mountLabel string, volumeMounts []string,
overlayMount, err := overlay.Mount(contentDir, host, container, rootUID, rootGID, b.store.GraphOptions())
if err == nil {
b.TempVolumes[contentDir] = true
}
@@ -2287,21 +2324,23 @@ func init() {
}
// runSetupRunMounts sets up mounts that exist only in this RUN, not in subsequent runs
func runSetupRunMounts(mounts []string, secrets map[string]string, mountlabel string, containerWorkingDir string, uidmap []spec.LinuxIDMapping, gidmap []spec.LinuxIDMapping) ([]spec.Mount, []string, error) {
func runSetupRunMounts(mounts []string, secrets map[string]string, sshSources map[string]*sshagent.Source, mountlabel string, containerWorkingDir string, uidmap []spec.LinuxIDMapping, gidmap []spec.LinuxIDMapping, processLabel string) ([]spec.Mount, *runMountArtifacts, error) {
mountTargets := make([]string, 0, 10)
finalMounts := make([]specs.Mount, 0, len(mounts))
agents := make([]*sshagent.AgentServer, 0, len(mounts))
sshCount := 0
defaultSSHSock := ""
tokens := []string{}
for _, mount := range mounts {
arr := strings.SplitN(mount, ",", 2)
if len(arr) < 2 {
return nil, nil, errors.New("invalid mount syntax")
}
kv := strings.Split(arr[0], "=")
if len(kv) != 2 || kv[0] != "type" {
return nil, nil, errors.New("invalid mount type")
}
tokens := strings.Split(arr[1], ",")
if len(arr) == 2 {
tokens = strings.Split(arr[1], ",")
}
// For now, we only support type secret.
switch kv[1] {
case "secret":
@@ -2314,16 +2353,38 @@ func runSetupRunMounts(mounts []string, secrets map[string]string, mountlabel st
mountTargets = append(mountTargets, mount.Destination)
}
case "ssh":
mount, agent, err := getSSHMount(tokens, sshCount, sshSources, mountlabel, uidmap, gidmap, processLabel)
if err != nil {
return nil, nil, err
}
if mount != nil {
finalMounts = append(finalMounts, *mount)
mountTargets = append(mountTargets, mount.Destination)
agents = append(agents, agent)
if sshCount == 0 {
defaultSSHSock = mount.Destination
}
// Count is needed as the default destination of the ssh sock inside the container is /run/buildkit/ssh_agent.{i}
sshCount++
}
default:
return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1])
return nil, nil, errors.Errorf("invalid mount type %q", kv[1])
}
}
return finalMounts, mountTargets, nil
artifacts := &runMountArtifacts{
RunMountTargets: mountTargets,
Agents: agents,
SSHAuthSock: defaultSSHSock,
}
return finalMounts, artifacts, nil
}
func getSecretMount(tokens []string, secrets map[string]string, mountlabel string, containerWorkingDir string, uidmap []spec.LinuxIDMapping, gidmap []spec.LinuxIDMapping) (*spec.Mount, error) {
errInvalidSyntax := errors.New("secret should have syntax id=id[,target=path,required=bool,mode=uint,uid=uint,gid=uint")
if len(tokens) == 0 {
return nil, errInvalidSyntax
}
var err error
var id, target string
var required bool
@@ -2419,11 +2480,134 @@ func getSecretMount(tokens []string, secrets map[string]string, mountlabel strin
return &newMount, nil
}
func cleanupRunMounts(paths []string, mountpoint string) error {
// getSSHMount parses the --mount type=ssh flag in the Containerfile, checks if there's an ssh source provided, and creates and starts an ssh-agent to be forwarded into the container
func getSSHMount(tokens []string, count int, sshsources map[string]*sshagent.Source, mountlabel string, uidmap []spec.LinuxIDMapping, gidmap []spec.LinuxIDMapping, processLabel string) (*spec.Mount, *sshagent.AgentServer, error) {
errInvalidSyntax := errors.New("ssh should have syntax id=id[,target=path,required=bool,mode=uint,uid=uint,gid=uint")
var err error
var id, target string
var required bool
var uid, gid uint32
var mode uint32 = 400
for _, val := range tokens {
kv := strings.SplitN(val, "=", 2)
if len(kv) < 2 {
return nil, nil, errInvalidSyntax
}
switch kv[0] {
case "id":
id = kv[1]
case "target", "dst", "destination":
target = kv[1]
case "required":
required, err = strconv.ParseBool(kv[1])
if err != nil {
return nil, nil, errInvalidSyntax
}
case "mode":
mode64, err := strconv.ParseUint(kv[1], 8, 32)
if err != nil {
return nil, nil, errInvalidSyntax
}
mode = uint32(mode64)
case "uid":
uid64, err := strconv.ParseUint(kv[1], 10, 32)
if err != nil {
return nil, nil, errInvalidSyntax
}
uid = uint32(uid64)
case "gid":
gid64, err := strconv.ParseUint(kv[1], 10, 32)
if err != nil {
return nil, nil, errInvalidSyntax
}
gid = uint32(gid64)
default:
return nil, nil, errInvalidSyntax
}
}
if id == "" {
id = "default"
}
// Default location for secretis is /run/buildkit/ssh_agent.{i}
if target == "" {
target = fmt.Sprintf("/run/buildkit/ssh_agent.%d", count)
}
sshsource, ok := sshsources[id]
if !ok {
if required {
return nil, nil, errors.Errorf("ssh required but no ssh with id %s found", id)
}
return nil, nil, nil
}
// Create new agent from keys or socket
fwdAgent, err := sshagent.NewAgentServer(sshsource)
if err != nil {
return nil, nil, err
}
// Start ssh server, and get the host sock we're mounting in the container
hostSock, err := fwdAgent.Serve(processLabel)
if err != nil {
return nil, nil, err
}
if err := label.Relabel(filepath.Dir(hostSock), mountlabel, false); err != nil {
if shutdownErr := fwdAgent.Shutdown(); shutdownErr != nil {
logrus.Errorf("error shutting down agent: %v", shutdownErr)
}
return nil, nil, err
}
if err := label.Relabel(hostSock, mountlabel, false); err != nil {
if shutdownErr := fwdAgent.Shutdown(); shutdownErr != nil {
logrus.Errorf("error shutting down agent: %v", shutdownErr)
}
return nil, nil, err
}
hostUID, hostGID, err := util.GetHostIDs(uidmap, gidmap, uid, gid)
if err != nil {
if shutdownErr := fwdAgent.Shutdown(); shutdownErr != nil {
logrus.Errorf("error shutting down agent: %v", shutdownErr)
}
return nil, nil, err
}
if err := os.Lchown(hostSock, int(hostUID), int(hostGID)); err != nil {
if shutdownErr := fwdAgent.Shutdown(); shutdownErr != nil {
logrus.Errorf("error shutting down agent: %v", shutdownErr)
}
return nil, nil, err
}
if err := os.Chmod(hostSock, os.FileMode(mode)); err != nil {
if shutdownErr := fwdAgent.Shutdown(); shutdownErr != nil {
logrus.Errorf("error shutting down agent: %v", shutdownErr)
}
return nil, nil, err
}
newMount := specs.Mount{
Destination: target,
Type: "bind",
Source: hostSock,
Options: []string{"bind", "rprivate", "ro"},
}
return &newMount, fwdAgent, nil
}
// cleanupRunMounts cleans up run mounts so they only appear in this run.
func cleanupRunMounts(mountpoint string, artifacts *runMountArtifacts) error {
for _, agent := range artifacts.Agents {
err := agent.Shutdown()
if err != nil {
return err
}
}
opts := copier.RemoveOptions{
All: true,
}
for _, path := range paths {
for _, path := range artifacts.RunMountTargets {
err := copier.Remove(mountpoint, path, opts)
if err != nil {
return err