forked from pool/docker
Add CVE references.
OBS-URL: https://build.opensuse.org/package/show/Virtualization:containers/docker?expand=0&rev=407
This commit is contained in:
commit
bab2de8fff
23
.gitattributes
vendored
Normal file
23
.gitattributes
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
## Default LFS
|
||||||
|
*.7z filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.bsp filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.gem filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.gz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.jar filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.lz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.lzma filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.obscpio filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.oxt filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pdf filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.rpm filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tbz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tbz2 filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tgz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.txz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.whl filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.xz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.zst filter=lfs diff=lfs merge=lfs -text
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.osc
|
@ -0,0 +1,73 @@
|
|||||||
|
From ec53ee338835c4c1dc583695ac166f36bf3bac5c Mon Sep 17 00:00:00 2001
|
||||||
|
From: Aleksa Sarai <asarai@suse.de>
|
||||||
|
Date: Wed, 8 Mar 2017 12:41:54 +1100
|
||||||
|
Subject: [PATCH 1/7] SECRETS: daemon: allow directory creation in /run/secrets
|
||||||
|
|
||||||
|
Since FileMode can have the directory bit set, allow a SecretStore
|
||||||
|
implementation to return secrets that are actually directories. This is
|
||||||
|
useful for creating directories and subdirectories of secrets.
|
||||||
|
|
||||||
|
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
|
||||||
|
Signed-off-by: Aleksa Sarai <asarai@suse.de>
|
||||||
|
---
|
||||||
|
daemon/container_operations_unix.go | 23 ++++++++++++++++++++---
|
||||||
|
1 file changed, 20 insertions(+), 3 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/daemon/container_operations_unix.go b/daemon/container_operations_unix.go
|
||||||
|
index 4dedc1b21c87..b7c310493e79 100644
|
||||||
|
--- a/daemon/container_operations_unix.go
|
||||||
|
+++ b/daemon/container_operations_unix.go
|
||||||
|
@@ -3,6 +3,7 @@
|
||||||
|
package daemon // import "github.com/docker/docker/daemon"
|
||||||
|
|
||||||
|
import (
|
||||||
|
+ "bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
@@ -16,6 +17,7 @@ import (
|
||||||
|
"github.com/docker/docker/daemon/links"
|
||||||
|
"github.com/docker/docker/errdefs"
|
||||||
|
"github.com/docker/docker/libnetwork"
|
||||||
|
+ "github.com/docker/docker/pkg/archive"
|
||||||
|
"github.com/docker/docker/pkg/idtools"
|
||||||
|
"github.com/docker/docker/pkg/process"
|
||||||
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
@@ -240,9 +242,6 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "unable to get secret from secret store")
|
||||||
|
}
|
||||||
|
- if err := os.WriteFile(fPath, secret.Spec.Data, s.File.Mode); err != nil {
|
||||||
|
- return errors.Wrap(err, "error injecting secret")
|
||||||
|
- }
|
||||||
|
|
||||||
|
uid, err := strconv.Atoi(s.File.UID)
|
||||||
|
if err != nil {
|
||||||
|
@@ -253,6 +252,24 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
+ if s.File.Mode.IsDir() {
|
||||||
|
+ if err := os.Mkdir(fPath, s.File.Mode); err != nil {
|
||||||
|
+ return errors.Wrap(err, "error creating secretdir")
|
||||||
|
+ }
|
||||||
|
+ if secret.Spec.Data != nil {
|
||||||
|
+ // If the "file" is a directory, then s.File.Data is actually a tar
|
||||||
|
+ // archive of the directory. So we just do a tar extraction here.
|
||||||
|
+ if err := archive.UntarUncompressed(bytes.NewBuffer(secret.Spec.Data), fPath, &archive.TarOptions{
|
||||||
|
+ IDMap: daemon.idMapping,
|
||||||
|
+ }); err != nil {
|
||||||
|
+ return errors.Wrap(err, "error injecting secretdir")
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ } else {
|
||||||
|
+ if err := os.WriteFile(fPath, secret.Spec.Data, s.File.Mode); err != nil {
|
||||||
|
+ return errors.Wrap(err, "error injecting secret")
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
if err := os.Chown(fPath, rootIDs.UID+uid, rootIDs.GID+gid); err != nil {
|
||||||
|
return errors.Wrap(err, "error setting ownership for secret")
|
||||||
|
}
|
||||||
|
--
|
||||||
|
2.45.2
|
||||||
|
|
460
0002-SECRETS-SUSE-implement-SUSE-container-secrets.patch
Normal file
460
0002-SECRETS-SUSE-implement-SUSE-container-secrets.patch
Normal file
@ -0,0 +1,460 @@
|
|||||||
|
From 759482e941bde2b67d39b52c803e3390555ff9e9 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Aleksa Sarai <asarai@suse.de>
|
||||||
|
Date: Wed, 8 Mar 2017 11:43:29 +1100
|
||||||
|
Subject: [PATCH 2/7] SECRETS: SUSE: implement SUSE container secrets
|
||||||
|
|
||||||
|
This allows for us to pass in host credentials to a container, allowing
|
||||||
|
for SUSEConnect to work with containers.
|
||||||
|
|
||||||
|
THIS PATCH IS NOT TO BE UPSTREAMED, DUE TO THE FACT THAT IT IS
|
||||||
|
SUSE-SPECIFIC, AND UPSTREAM DOES NOT APPROVE OF THIS CONCEPT BECAUSE IT
|
||||||
|
MAKES BUILDS NOT ENTIRELY REPRODUCIBLE.
|
||||||
|
|
||||||
|
SUSE-Bugs: bsc#1065609 bsc#1057743 bsc#1055676 bsc#1030702
|
||||||
|
Signed-off-by: Aleksa Sarai <asarai@suse.de>
|
||||||
|
---
|
||||||
|
daemon/start.go | 5 +
|
||||||
|
daemon/suse_secrets.go | 415 +++++++++++++++++++++++++++++++++++++++++
|
||||||
|
2 files changed, 420 insertions(+)
|
||||||
|
create mode 100644 daemon/suse_secrets.go
|
||||||
|
|
||||||
|
diff --git a/daemon/start.go b/daemon/start.go
|
||||||
|
index b967947af2ce..09e79e410310 100644
|
||||||
|
--- a/daemon/start.go
|
||||||
|
+++ b/daemon/start.go
|
||||||
|
@@ -123,6 +123,11 @@ func (daemon *Daemon) containerStart(ctx context.Context, daemonCfg *configStore
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // SUSE:secrets -- inject the SUSE secret store
|
||||||
|
+ if err := daemon.injectSuseSecretStore(container); err != nil {
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
m, cleanup, err := daemon.setupMounts(ctx, container)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
diff --git a/daemon/suse_secrets.go b/daemon/suse_secrets.go
|
||||||
|
new file mode 100644
|
||||||
|
index 000000000000..32b0ece91b59
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/daemon/suse_secrets.go
|
||||||
|
@@ -0,0 +1,415 @@
|
||||||
|
+/*
|
||||||
|
+ * suse-secrets: patch for Docker to implement SUSE secrets
|
||||||
|
+ * Copyright (C) 2017-2021 SUSE LLC.
|
||||||
|
+ *
|
||||||
|
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
+ * you may not use this file except in compliance with the License.
|
||||||
|
+ * You may obtain a copy of the License at
|
||||||
|
+ *
|
||||||
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
+ *
|
||||||
|
+ * Unless required by applicable law or agreed to in writing, software
|
||||||
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
+ * See the License for the specific language governing permissions and
|
||||||
|
+ * limitations under the License.
|
||||||
|
+ */
|
||||||
|
+
|
||||||
|
+package daemon
|
||||||
|
+
|
||||||
|
+import (
|
||||||
|
+ "archive/tar"
|
||||||
|
+ "bytes"
|
||||||
|
+ "fmt"
|
||||||
|
+ "io"
|
||||||
|
+ "io/ioutil"
|
||||||
|
+ "os"
|
||||||
|
+ "path/filepath"
|
||||||
|
+ "strings"
|
||||||
|
+ "syscall"
|
||||||
|
+
|
||||||
|
+ "github.com/docker/docker/container"
|
||||||
|
+ "github.com/docker/docker/pkg/archive"
|
||||||
|
+ "github.com/docker/docker/pkg/idtools"
|
||||||
|
+
|
||||||
|
+ swarmtypes "github.com/docker/docker/api/types/swarm"
|
||||||
|
+ swarmexec "github.com/moby/swarmkit/v2/agent/exec"
|
||||||
|
+ swarmapi "github.com/moby/swarmkit/v2/api"
|
||||||
|
+
|
||||||
|
+ "github.com/opencontainers/go-digest"
|
||||||
|
+ "github.com/sirupsen/logrus"
|
||||||
|
+)
|
||||||
|
+
|
||||||
|
+func init() {
|
||||||
|
+ // Output to tell us in logs that SUSE:secrets is enabled.
|
||||||
|
+ logrus.Infof("SUSE:secrets :: enabled")
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+// Creating a fake file.
|
||||||
|
+type SuseFakeFile struct {
|
||||||
|
+ Path string
|
||||||
|
+ Uid int
|
||||||
|
+ Gid int
|
||||||
|
+ Mode os.FileMode
|
||||||
|
+ Data []byte
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func (s SuseFakeFile) id() string {
|
||||||
|
+ // NOTE: It is _very_ important that this string always has a prefix of
|
||||||
|
+ // "suse". This is how we can ensure that we can operate on
|
||||||
|
+ // SecretReferences with a confidence that it was made by us.
|
||||||
|
+ return fmt.Sprintf("suse_%s_%s", digest.FromBytes(s.Data).Hex(), s.Path)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func (s SuseFakeFile) toSecret() *swarmapi.Secret {
|
||||||
|
+ return &swarmapi.Secret{
|
||||||
|
+ ID: s.id(),
|
||||||
|
+ Internal: true,
|
||||||
|
+ Spec: swarmapi.SecretSpec{
|
||||||
|
+ Data: s.Data,
|
||||||
|
+ },
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func (s SuseFakeFile) toSecretReference(idMaps idtools.IdentityMapping) *swarmtypes.SecretReference {
|
||||||
|
+ // Figure out the host-facing {uid,gid} based on the provided maps. Fall
|
||||||
|
+ // back to root if the UID/GID don't match (we are guaranteed that root is
|
||||||
|
+ // mapped).
|
||||||
|
+ ctrUser := idtools.Identity{UID: s.Uid, GID: s.Gid}
|
||||||
|
+ hostUser := idMaps.RootPair()
|
||||||
|
+ if user, err := idMaps.ToHost(ctrUser); err == nil {
|
||||||
|
+ hostUser = user
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Return the secret reference as a file target.
|
||||||
|
+ return &swarmtypes.SecretReference{
|
||||||
|
+ SecretID: s.id(),
|
||||||
|
+ SecretName: s.id(),
|
||||||
|
+ File: &swarmtypes.SecretReferenceFileTarget{
|
||||||
|
+ Name: s.Path,
|
||||||
|
+ UID: fmt.Sprintf("%d", hostUser.UID),
|
||||||
|
+ GID: fmt.Sprintf("%d", hostUser.GID),
|
||||||
|
+ Mode: s.Mode,
|
||||||
|
+ },
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+// readDir will recurse into a directory prefix/dir, and return the set of
|
||||||
|
+// secrets in that directory (as a tar archive that is packed inside the "data"
|
||||||
|
+// field). The Path attribute of each has the prefix stripped. Symlinks are
|
||||||
|
+// dereferenced.
|
||||||
|
+func readDir(prefix, dir string) ([]*SuseFakeFile, error) {
|
||||||
|
+ var suseFiles []*SuseFakeFile
|
||||||
|
+
|
||||||
|
+ path := filepath.Join(prefix, dir)
|
||||||
|
+ fi, err := os.Stat(path)
|
||||||
|
+ if err != nil {
|
||||||
|
+ // Ignore missing files.
|
||||||
|
+ if os.IsNotExist(err) {
|
||||||
|
+ // If the path itself exists it was a dangling symlink so give a
|
||||||
|
+ // warning about the symlink dangling.
|
||||||
|
+ _, err2 := os.Lstat(path)
|
||||||
|
+ if !os.IsNotExist(err2) {
|
||||||
|
+ logrus.Warnf("SUSE:secrets :: ignoring dangling symlink: %s", path)
|
||||||
|
+ }
|
||||||
|
+ return nil, nil
|
||||||
|
+ }
|
||||||
|
+ return nil, err
|
||||||
|
+ } else if !fi.IsDir() {
|
||||||
|
+ // Just to be safe.
|
||||||
|
+ logrus.Infof("SUSE:secrets :: expected %q to be a directory, but was a file", path)
|
||||||
|
+ return readFile(prefix, dir)
|
||||||
|
+ }
|
||||||
|
+ path, err = filepath.EvalSymlinks(path)
|
||||||
|
+ if err != nil {
|
||||||
|
+ return nil, err
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Construct a tar archive of the source directory. We tar up the prefix
|
||||||
|
+ // directory and add dir as an IncludeFiles specifically so that we
|
||||||
|
+ // preserve the name of the directory itself.
|
||||||
|
+ tarStream, err := archive.TarWithOptions(path, &archive.TarOptions{
|
||||||
|
+ Compression: archive.Uncompressed,
|
||||||
|
+ IncludeSourceDir: true,
|
||||||
|
+ })
|
||||||
|
+ if err != nil {
|
||||||
|
+ return nil, fmt.Errorf("SUSE:secrets :: failed to tar source directory %q: %v", path, err)
|
||||||
|
+ }
|
||||||
|
+ tarStreamBytes, err := ioutil.ReadAll(tarStream)
|
||||||
|
+ if err != nil {
|
||||||
|
+ return nil, fmt.Errorf("SUSE:secrets :: failed to read full tar archive: %v", err)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Get a list of the symlinks in the tar archive.
|
||||||
|
+ var symlinks []string
|
||||||
|
+ tmpTr := tar.NewReader(bytes.NewBuffer(tarStreamBytes))
|
||||||
|
+ for {
|
||||||
|
+ hdr, err := tmpTr.Next()
|
||||||
|
+ if err == io.EOF {
|
||||||
|
+ break
|
||||||
|
+ }
|
||||||
|
+ if err != nil {
|
||||||
|
+ return nil, fmt.Errorf("SUSE:secrets :: failed to read through tar reader: %v", err)
|
||||||
|
+ }
|
||||||
|
+ if hdr.Typeflag == tar.TypeSymlink {
|
||||||
|
+ symlinks = append(symlinks, hdr.Name)
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Symlinks aren't dereferenced in the above archive, so we explicitly do a
|
||||||
|
+ // rewrite of the tar archive to include all symlinks to files. We cannot
|
||||||
|
+ // do directories here, but lower-level directory symlinks aren't supported
|
||||||
|
+ // by zypper so this isn't an issue.
|
||||||
|
+ symlinkModifyMap := map[string]archive.TarModifierFunc{}
|
||||||
|
+ for _, sym := range symlinks {
|
||||||
|
+ logrus.Debugf("SUSE:secrets: archive(%q) %q is a need-to-rewrite symlink", path, sym)
|
||||||
|
+ symlinkModifyMap[sym] = func(tarPath string, hdr *tar.Header, r io.Reader) (*tar.Header, []byte, error) {
|
||||||
|
+ logrus.Debugf("SUSE:secrets: archive(%q) mapping for symlink %q", path, tarPath)
|
||||||
|
+ tarFullPath := filepath.Join(path, tarPath)
|
||||||
|
+
|
||||||
|
+ // Get a copy of the original byte stream.
|
||||||
|
+ oldContent, err := ioutil.ReadAll(r)
|
||||||
|
+ if err != nil {
|
||||||
|
+ return nil, nil, fmt.Errorf("suse_rewrite: failed to read archive entry %q: %v", tarPath, err)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Check that the file actually exists.
|
||||||
|
+ fi, err := os.Stat(tarFullPath)
|
||||||
|
+ if err != nil {
|
||||||
|
+ logrus.Warnf("suse_rewrite: failed to stat archive entry %q: %v", tarFullPath, err)
|
||||||
|
+ return hdr, oldContent, nil
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Read the actual contents.
|
||||||
|
+ content, err := ioutil.ReadFile(tarFullPath)
|
||||||
|
+ if err != nil {
|
||||||
|
+ logrus.Warnf("suse_rewrite: failed to read %q: %v", tarFullPath, err)
|
||||||
|
+ return hdr, oldContent, nil
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ newHdr, err := tar.FileInfoHeader(fi, "")
|
||||||
|
+ if err != nil {
|
||||||
|
+ // Fake the header.
|
||||||
|
+ newHdr = &tar.Header{
|
||||||
|
+ Typeflag: tar.TypeReg,
|
||||||
|
+ Mode: 0644,
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Update the key fields.
|
||||||
|
+ hdr.Typeflag = newHdr.Typeflag
|
||||||
|
+ hdr.Mode = newHdr.Mode
|
||||||
|
+ hdr.Linkname = ""
|
||||||
|
+ return hdr, content, nil
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Create the rewritten tar stream.
|
||||||
|
+ tarStream = archive.ReplaceFileTarWrapper(ioutil.NopCloser(bytes.NewBuffer(tarStreamBytes)), symlinkModifyMap)
|
||||||
|
+ tarStreamBytes, err = ioutil.ReadAll(tarStream)
|
||||||
|
+ if err != nil {
|
||||||
|
+ return nil, fmt.Errorf("SUSE:secrets :: failed to read rewritten archive: %v", err)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Add the tar stream as a "file".
|
||||||
|
+ suseFiles = append(suseFiles, &SuseFakeFile{
|
||||||
|
+ Path: dir,
|
||||||
|
+ Mode: fi.Mode(),
|
||||||
|
+ Data: tarStreamBytes,
|
||||||
|
+ })
|
||||||
|
+ return suseFiles, nil
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+// readFile returns a secret given a file under a given prefix.
|
||||||
|
+func readFile(prefix, file string) ([]*SuseFakeFile, error) {
|
||||||
|
+ path := filepath.Join(prefix, file)
|
||||||
|
+ fi, err := os.Stat(path)
|
||||||
|
+ if err != nil {
|
||||||
|
+ // Ignore missing files.
|
||||||
|
+ if os.IsNotExist(err) {
|
||||||
|
+ // If the path itself exists it was a dangling symlink so give a
|
||||||
|
+ // warning about the symlink dangling.
|
||||||
|
+ _, err2 := os.Lstat(path)
|
||||||
|
+ if !os.IsNotExist(err2) {
|
||||||
|
+ logrus.Warnf("SUSE:secrets :: ignoring dangling symlink: %s", path)
|
||||||
|
+ }
|
||||||
|
+ return nil, nil
|
||||||
|
+ }
|
||||||
|
+ return nil, err
|
||||||
|
+ } else if fi.IsDir() {
|
||||||
|
+ // Just to be safe.
|
||||||
|
+ logrus.Infof("SUSE:secrets :: expected %q to be a file, but was a directory", path)
|
||||||
|
+ return readDir(prefix, file)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ var uid, gid int
|
||||||
|
+ if stat, ok := fi.Sys().(*syscall.Stat_t); ok {
|
||||||
|
+ uid, gid = int(stat.Uid), int(stat.Gid)
|
||||||
|
+ } else {
|
||||||
|
+ logrus.Warnf("SUSE:secrets :: failed to cast file stat_t: defaulting to owned by root:root: %s", path)
|
||||||
|
+ uid, gid = 0, 0
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ bytes, err := ioutil.ReadFile(path)
|
||||||
|
+ if err != nil {
|
||||||
|
+ return nil, err
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ var suseFiles []*SuseFakeFile
|
||||||
|
+ suseFiles = append(suseFiles, &SuseFakeFile{
|
||||||
|
+ Path: file,
|
||||||
|
+ Uid: uid,
|
||||||
|
+ Gid: gid,
|
||||||
|
+ Mode: fi.Mode(),
|
||||||
|
+ Data: bytes,
|
||||||
|
+ })
|
||||||
|
+ return suseFiles, nil
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+// getHostSuseSecretData returns the list of SuseFakeFiles the need to be added
|
||||||
|
+// as SUSE secrets.
|
||||||
|
+func getHostSuseSecretData() ([]*SuseFakeFile, error) {
|
||||||
|
+ secrets := []*SuseFakeFile{}
|
||||||
|
+
|
||||||
|
+ credentials, err := readDir("/etc/zypp", "credentials.d")
|
||||||
|
+ if err != nil {
|
||||||
|
+ if os.IsNotExist(err) {
|
||||||
|
+ credentials = []*SuseFakeFile{}
|
||||||
|
+ } else {
|
||||||
|
+ logrus.Errorf("SUSE:secrets :: error while reading zypp credentials: %s", err)
|
||||||
|
+ return nil, err
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ secrets = append(secrets, credentials...)
|
||||||
|
+
|
||||||
|
+ suseConnect, err := readFile("/etc", "SUSEConnect")
|
||||||
|
+ if err != nil {
|
||||||
|
+ if os.IsNotExist(err) {
|
||||||
|
+ suseConnect = []*SuseFakeFile{}
|
||||||
|
+ } else {
|
||||||
|
+ logrus.Errorf("SUSE:secrets :: error while reading /etc/SUSEConnect: %s", err)
|
||||||
|
+ return nil, err
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ secrets = append(secrets, suseConnect...)
|
||||||
|
+
|
||||||
|
+ return secrets, nil
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+// To fake an empty store, in the case where we are operating on a container
|
||||||
|
+// that was created pre-swarmkit. Otherwise segfaults and other fun things
|
||||||
|
+// happen. See bsc#1057743.
|
||||||
|
+type (
|
||||||
|
+ suseEmptyStore struct{}
|
||||||
|
+ suseEmptySecret struct{}
|
||||||
|
+ suseEmptyConfig struct{}
|
||||||
|
+ suseEmptyVolume struct{}
|
||||||
|
+)
|
||||||
|
+
|
||||||
|
+// In order to reduce the amount of code touched outside of this file, we
|
||||||
|
+// implement the swarm API for DependencyGetter. This asserts that this
|
||||||
|
+// requirement will always be matched. In addition, for the case of the *empty*
|
||||||
|
+// getters this reduces memory usage by having a global instance.
|
||||||
|
+var (
|
||||||
|
+ _ swarmexec.DependencyGetter = &suseDependencyStore{}
|
||||||
|
+ emptyStore swarmexec.DependencyGetter = suseEmptyStore{}
|
||||||
|
+ emptySecret swarmexec.SecretGetter = suseEmptySecret{}
|
||||||
|
+ emptyConfig swarmexec.ConfigGetter = suseEmptyConfig{}
|
||||||
|
+ emptyVolume swarmexec.VolumeGetter = suseEmptyVolume{}
|
||||||
|
+)
|
||||||
|
+
|
||||||
|
+var errSuseEmptyStore = fmt.Errorf("SUSE:secrets :: tried to get a resource from empty store [this is a bug]")
|
||||||
|
+
|
||||||
|
+func (_ suseEmptyConfig) Get(_ string) (*swarmapi.Config, error) { return nil, errSuseEmptyStore }
|
||||||
|
+func (_ suseEmptySecret) Get(_ string) (*swarmapi.Secret, error) { return nil, errSuseEmptyStore }
|
||||||
|
+func (_ suseEmptyVolume) Get(_ string) (string, error) { return "", errSuseEmptyStore }
|
||||||
|
+func (_ suseEmptyStore) Secrets() swarmexec.SecretGetter { return emptySecret }
|
||||||
|
+func (_ suseEmptyStore) Configs() swarmexec.ConfigGetter { return emptyConfig }
|
||||||
|
+func (_ suseEmptyStore) Volumes() swarmexec.VolumeGetter { return emptyVolume }
|
||||||
|
+
|
||||||
|
+type suseDependencyStore struct {
|
||||||
|
+ dfl swarmexec.DependencyGetter
|
||||||
|
+ secrets map[string]*swarmapi.Secret
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+// The following are effectively dumb wrappers that return ourselves, or the
|
||||||
|
+// default.
|
||||||
|
+func (s *suseDependencyStore) Secrets() swarmexec.SecretGetter { return s }
|
||||||
|
+func (s *suseDependencyStore) Volumes() swarmexec.VolumeGetter { return emptyVolume }
|
||||||
|
+func (s *suseDependencyStore) Configs() swarmexec.ConfigGetter { return s.dfl.Configs() }
|
||||||
|
+
|
||||||
|
+// Get overrides the underlying DependencyGetter with our own secrets (falling
|
||||||
|
+// through to the underlying DependencyGetter if the secret isn't present).
|
||||||
|
+func (s *suseDependencyStore) Get(id string) (*swarmapi.Secret, error) {
|
||||||
|
+ logrus.Debugf("SUSE:secrets :: id=%s requested from suseDependencyGetter", id)
|
||||||
|
+
|
||||||
|
+ secret, ok := s.secrets[id]
|
||||||
|
+ if !ok {
|
||||||
|
+ // fallthrough
|
||||||
|
+ return s.dfl.Secrets().Get(id)
|
||||||
|
+ }
|
||||||
|
+ return secret, nil
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+// removeSuseSecrets removes any SecretReferences which were added by us
|
||||||
|
+// explicitly (this is detected by checking that the prefix has a 'suse'
|
||||||
|
+// prefix). See bsc#1057743.
|
||||||
|
+func removeSuseSecrets(c *container.Container) {
|
||||||
|
+ var without []*swarmtypes.SecretReference
|
||||||
|
+ for _, secret := range c.SecretReferences {
|
||||||
|
+ if strings.HasPrefix(secret.SecretID, "suse") {
|
||||||
|
+ logrus.Warnf("SUSE:secrets :: removing 'old' suse secret %q from container %q", secret.SecretID, c.ID)
|
||||||
|
+ continue
|
||||||
|
+ }
|
||||||
|
+ without = append(without, secret)
|
||||||
|
+ }
|
||||||
|
+ c.SecretReferences = without
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func (daemon *Daemon) injectSuseSecretStore(c *container.Container) error {
|
||||||
|
+ newDependencyStore := &suseDependencyStore{
|
||||||
|
+ dfl: c.DependencyStore,
|
||||||
|
+ secrets: make(map[string]*swarmapi.Secret),
|
||||||
|
+ }
|
||||||
|
+ // Handle old containers. See bsc#1057743.
|
||||||
|
+ if newDependencyStore.dfl == nil {
|
||||||
|
+ newDependencyStore.dfl = emptyStore
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // We drop any "old" SUSE secrets, as it appears that old containers (when
|
||||||
|
+ // restarted) could still have references to old secrets. The .id() of all
|
||||||
|
+ // secrets have a prefix of "suse" so this is much easier. See bsc#1057743
|
||||||
|
+ // for details on why this could cause issues.
|
||||||
|
+ removeSuseSecrets(c)
|
||||||
|
+
|
||||||
|
+ secrets, err := getHostSuseSecretData()
|
||||||
|
+ if err != nil {
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ idMaps := daemon.idMapping
|
||||||
|
+ for _, secret := range secrets {
|
||||||
|
+ newDependencyStore.secrets[secret.id()] = secret.toSecret()
|
||||||
|
+ c.SecretReferences = append(c.SecretReferences, secret.toSecretReference(idMaps))
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ c.DependencyStore = newDependencyStore
|
||||||
|
+
|
||||||
|
+ // bsc#1057743 -- In older versions of Docker we added volumes explicitly
|
||||||
|
+ // to the mount list. This causes clashes because of duplicate namespaces.
|
||||||
|
+ // If we see an existing mount that will clash with the in-built secrets
|
||||||
|
+ // mount we assume it's our fault.
|
||||||
|
+ intendedMounts, err := c.SecretMounts()
|
||||||
|
+ if err != nil {
|
||||||
|
+ logrus.Warnf("SUSE:secrets :: fetching old secret mounts: %v", err)
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
+ for _, intendedMount := range intendedMounts {
|
||||||
|
+ mountPath := intendedMount.Destination
|
||||||
|
+ if volume, ok := c.MountPoints[mountPath]; ok {
|
||||||
|
+ logrus.Debugf("SUSE:secrets :: removing pre-existing %q mount: %#v", mountPath, volume)
|
||||||
|
+ delete(c.MountPoints, mountPath)
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ return nil
|
||||||
|
+}
|
||||||
|
--
|
||||||
|
2.45.2
|
||||||
|
|
@ -0,0 +1,46 @@
|
|||||||
|
From 983a57fd37dc8e42e9c4e4dfc72eb346a4385948 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Aleksa Sarai <asarai@suse.de>
|
||||||
|
Date: Mon, 22 May 2023 15:44:54 +1000
|
||||||
|
Subject: [PATCH 3/7] BUILD: SLE12: revert "graphdriver/btrfs: use kernel UAPI
|
||||||
|
headers"
|
||||||
|
|
||||||
|
This reverts commit 3208dcabdc8997340b255f5b880fef4e3f54580d.
|
||||||
|
|
||||||
|
On SLE 12, our UAPI headers are too old, resulting in us being unable to
|
||||||
|
build the btrfs driver with the new headers. This patch is only needed
|
||||||
|
for SLE-12.
|
||||||
|
|
||||||
|
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
|
||||||
|
---
|
||||||
|
daemon/graphdriver/btrfs/btrfs.go | 13 ++++---------
|
||||||
|
1 file changed, 4 insertions(+), 9 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/daemon/graphdriver/btrfs/btrfs.go b/daemon/graphdriver/btrfs/btrfs.go
|
||||||
|
index 6aaa33cf7622..7264d4036427 100644
|
||||||
|
--- a/daemon/graphdriver/btrfs/btrfs.go
|
||||||
|
+++ b/daemon/graphdriver/btrfs/btrfs.go
|
||||||
|
@@ -4,17 +4,12 @@ package btrfs // import "github.com/docker/docker/daemon/graphdriver/btrfs"
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <stdlib.h>
|
||||||
|
-#include <stdio.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
-#include <linux/version.h>
|
||||||
|
-#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0)
|
||||||
|
- #error "Headers from kernel >= 4.12 are required to build with Btrfs support."
|
||||||
|
- #error "HINT: Set 'DOCKER_BUILDTAGS=exclude_graphdriver_btrfs' to build without Btrfs."
|
||||||
|
-#endif
|
||||||
|
-
|
||||||
|
-#include <linux/btrfs.h>
|
||||||
|
-#include <linux/btrfs_tree.h>
|
||||||
|
+// keep struct field name compatible with btrfs-progs < 6.1.
|
||||||
|
+#define max_referenced max_rfer
|
||||||
|
+#include <btrfs/ioctl.h>
|
||||||
|
+#include <btrfs/ctree.h>
|
||||||
|
|
||||||
|
static void set_name_btrfs_ioctl_vol_args_v2(struct btrfs_ioctl_vol_args_v2* btrfs_struct, const char* value) {
|
||||||
|
snprintf(btrfs_struct->name, BTRFS_SUBVOL_NAME_MAX, "%s", value);
|
||||||
|
--
|
||||||
|
2.45.2
|
||||||
|
|
@ -0,0 +1,89 @@
|
|||||||
|
From 8829bb8ec53399fd41dd6f46e2bad64e773e8eaa Mon Sep 17 00:00:00 2001
|
||||||
|
From: Aleksa Sarai <asarai@suse.de>
|
||||||
|
Date: Fri, 29 Jun 2018 17:59:30 +1000
|
||||||
|
Subject: [PATCH 4/7] bsc1073877: apparmor: clobber docker-default profile on
|
||||||
|
start
|
||||||
|
|
||||||
|
In the process of making docker-default reloading far less expensive,
|
||||||
|
567ef8e7858c ("daemon: switch to 'ensure' workflow for AppArmor
|
||||||
|
profiles") mistakenly made the initial profile load at dockerd start-up
|
||||||
|
lazy. As a result, if you have a running Docker daemon and upgrade it to
|
||||||
|
a new one with an updated AppArmor profile the new profile will not take
|
||||||
|
effect (because the old one is still loaded). The fix for this is quite
|
||||||
|
trivial, and just requires us to clobber the profile on start-up.
|
||||||
|
|
||||||
|
Fixes: 567ef8e7858c ("daemon: switch to 'ensure' workflow for AppArmor profiles")
|
||||||
|
SUSE-Bugs: bsc#1099277
|
||||||
|
Signed-off-by: Aleksa Sarai <asarai@suse.de>
|
||||||
|
---
|
||||||
|
daemon/apparmor_default.go | 14 ++++++++++----
|
||||||
|
daemon/apparmor_default_unsupported.go | 4 ++++
|
||||||
|
daemon/daemon.go | 5 +++--
|
||||||
|
3 files changed, 17 insertions(+), 6 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/daemon/apparmor_default.go b/daemon/apparmor_default.go
|
||||||
|
index 81e10b6cbec0..e695667a190f 100644
|
||||||
|
--- a/daemon/apparmor_default.go
|
||||||
|
+++ b/daemon/apparmor_default.go
|
||||||
|
@@ -23,6 +23,15 @@ func DefaultApparmorProfile() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
+func clobberDefaultAppArmorProfile() error {
|
||||||
|
+ if apparmor.HostSupports() {
|
||||||
|
+ if err := aaprofile.InstallDefault(defaultAppArmorProfile); err != nil {
|
||||||
|
+ return fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded: %s", defaultAppArmorProfile, err)
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ return nil
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
func ensureDefaultAppArmorProfile() error {
|
||||||
|
if apparmor.HostSupports() {
|
||||||
|
loaded, err := aaprofile.IsLoaded(defaultAppArmorProfile)
|
||||||
|
@@ -36,10 +45,7 @@ func ensureDefaultAppArmorProfile() error {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the profile.
|
||||||
|
- if err := aaprofile.InstallDefault(defaultAppArmorProfile); err != nil {
|
||||||
|
- return fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded: %s", defaultAppArmorProfile, err)
|
||||||
|
- }
|
||||||
|
+ return clobberDefaultAppArmorProfile()
|
||||||
|
}
|
||||||
|
-
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
diff --git a/daemon/apparmor_default_unsupported.go b/daemon/apparmor_default_unsupported.go
|
||||||
|
index be4938f5b61a..2b326fea5829 100644
|
||||||
|
--- a/daemon/apparmor_default_unsupported.go
|
||||||
|
+++ b/daemon/apparmor_default_unsupported.go
|
||||||
|
@@ -2,6 +2,10 @@
|
||||||
|
|
||||||
|
package daemon // import "github.com/docker/docker/daemon"
|
||||||
|
|
||||||
|
+func clobberDefaultAppArmorProfile() error {
|
||||||
|
+ return nil
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
func ensureDefaultAppArmorProfile() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
diff --git a/daemon/daemon.go b/daemon/daemon.go
|
||||||
|
index e7ca77d8cbfc..13b39538fb00 100644
|
||||||
|
--- a/daemon/daemon.go
|
||||||
|
+++ b/daemon/daemon.go
|
||||||
|
@@ -916,8 +916,9 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
|
||||||
|
log.G(ctx).Warnf("Failed to configure golang's threads limit: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
- // ensureDefaultAppArmorProfile does nothing if apparmor is disabled
|
||||||
|
- if err := ensureDefaultAppArmorProfile(); err != nil {
|
||||||
|
+ // Make sure we clobber any pre-existing docker-default profile to ensure
|
||||||
|
+ // that upgrades to the profile actually work smoothly.
|
||||||
|
+ if err := clobberDefaultAppArmorProfile(); err != nil {
|
||||||
|
log.G(ctx).Errorf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
--
|
||||||
|
2.45.2
|
||||||
|
|
326
0005-SLE12-revert-apparmor-remove-version-conditionals-fr.patch
Normal file
326
0005-SLE12-revert-apparmor-remove-version-conditionals-fr.patch
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
From 24173cd6a2643e5e680e84920864f42ed43b6f28 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Aleksa Sarai <asarai@suse.de>
|
||||||
|
Date: Wed, 11 Oct 2023 21:19:12 +1100
|
||||||
|
Subject: [PATCH 5/7] SLE12: revert "apparmor: remove version-conditionals from
|
||||||
|
template"
|
||||||
|
|
||||||
|
This reverts the following commits:
|
||||||
|
|
||||||
|
* 7008a514493a ("profiles/apparmor: remove version-conditional constraints (< 2.8.96)")
|
||||||
|
* 2e19a4d56bf2 ("contrib/apparmor: remove version-conditionals (< 2.9) from template")
|
||||||
|
* d169a5730649 ("contrib/apparmor: remove remaining version-conditionals (< 2.9) from template")
|
||||||
|
* ecaab085db4b ("profiles/apparmor: remove use of aaparser.GetVersion()")
|
||||||
|
* e3e715666f95 ("pkg/aaparser: deprecate GetVersion, as it's no longer used")
|
||||||
|
|
||||||
|
These version conditionals are still required on SLE 12, where our
|
||||||
|
apparmor_parser version is quite old.
|
||||||
|
|
||||||
|
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
|
||||||
|
---
|
||||||
|
contrib/apparmor/main.go | 16 ++++++-
|
||||||
|
contrib/apparmor/template.go | 16 +++++++
|
||||||
|
pkg/aaparser/aaparser.go | 86 +++++++++++++++++++++++++++++++++++
|
||||||
|
profiles/apparmor/apparmor.go | 16 ++++++-
|
||||||
|
profiles/apparmor/template.go | 4 ++
|
||||||
|
5 files changed, 134 insertions(+), 4 deletions(-)
|
||||||
|
create mode 100644 pkg/aaparser/aaparser.go
|
||||||
|
|
||||||
|
diff --git a/contrib/apparmor/main.go b/contrib/apparmor/main.go
|
||||||
|
index 899d8378edae..93f98cbd20e5 100644
|
||||||
|
--- a/contrib/apparmor/main.go
|
||||||
|
+++ b/contrib/apparmor/main.go
|
||||||
|
@@ -6,9 +6,13 @@ import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"text/template"
|
||||||
|
+
|
||||||
|
+ "github.com/docker/docker/pkg/aaparser"
|
||||||
|
)
|
||||||
|
|
||||||
|
-type profileData struct{}
|
||||||
|
+type profileData struct {
|
||||||
|
+ Version int
|
||||||
|
+}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
@@ -18,6 +22,15 @@ func main() {
|
||||||
|
// parse the arg
|
||||||
|
apparmorProfilePath := os.Args[1]
|
||||||
|
|
||||||
|
+ version, err := aaparser.GetVersion()
|
||||||
|
+ if err != nil {
|
||||||
|
+ log.Fatal(err)
|
||||||
|
+ }
|
||||||
|
+ data := profileData{
|
||||||
|
+ Version: version,
|
||||||
|
+ }
|
||||||
|
+ fmt.Printf("apparmor_parser is of version %+v\n", data)
|
||||||
|
+
|
||||||
|
// parse the template
|
||||||
|
compiled, err := template.New("apparmor_profile").Parse(dockerProfileTemplate)
|
||||||
|
if err != nil {
|
||||||
|
@@ -35,7 +48,6 @@ func main() {
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
- data := profileData{}
|
||||||
|
if err := compiled.Execute(f, data); err != nil {
|
||||||
|
log.Fatalf("executing template failed: %v", err)
|
||||||
|
}
|
||||||
|
diff --git a/contrib/apparmor/template.go b/contrib/apparmor/template.go
|
||||||
|
index 58afcbe845ee..e6d0b6d37c58 100644
|
||||||
|
--- a/contrib/apparmor/template.go
|
||||||
|
+++ b/contrib/apparmor/template.go
|
||||||
|
@@ -20,9 +20,11 @@ profile /usr/bin/docker (attach_disconnected, complain) {
|
||||||
|
|
||||||
|
umount,
|
||||||
|
pivot_root,
|
||||||
|
+{{if ge .Version 209000}}
|
||||||
|
signal (receive) peer=@{profile_name},
|
||||||
|
signal (receive) peer=unconfined,
|
||||||
|
signal (send),
|
||||||
|
+{{end}}
|
||||||
|
network,
|
||||||
|
capability,
|
||||||
|
owner /** rw,
|
||||||
|
@@ -45,10 +47,12 @@ profile /usr/bin/docker (attach_disconnected, complain) {
|
||||||
|
/etc/ld.so.cache r,
|
||||||
|
/etc/passwd r,
|
||||||
|
|
||||||
|
+{{if ge .Version 209000}}
|
||||||
|
ptrace peer=@{profile_name},
|
||||||
|
ptrace (read) peer=docker-default,
|
||||||
|
deny ptrace (trace) peer=docker-default,
|
||||||
|
deny ptrace peer=/usr/bin/docker///bin/ps,
|
||||||
|
+{{end}}
|
||||||
|
|
||||||
|
/usr/lib/** rm,
|
||||||
|
/lib/** rm,
|
||||||
|
@@ -69,9 +73,11 @@ profile /usr/bin/docker (attach_disconnected, complain) {
|
||||||
|
/sbin/zfs rCx,
|
||||||
|
/sbin/apparmor_parser rCx,
|
||||||
|
|
||||||
|
+{{if ge .Version 209000}}
|
||||||
|
# Transitions
|
||||||
|
change_profile -> docker-*,
|
||||||
|
change_profile -> unconfined,
|
||||||
|
+{{end}}
|
||||||
|
|
||||||
|
profile /bin/cat (complain) {
|
||||||
|
/etc/ld.so.cache r,
|
||||||
|
@@ -93,8 +99,10 @@ profile /usr/bin/docker (attach_disconnected, complain) {
|
||||||
|
/dev/null rw,
|
||||||
|
/bin/ps mr,
|
||||||
|
|
||||||
|
+{{if ge .Version 209000}}
|
||||||
|
# We don't need ptrace so we'll deny and ignore the error.
|
||||||
|
deny ptrace (read, trace),
|
||||||
|
+{{end}}
|
||||||
|
|
||||||
|
# Quiet dac_override denials
|
||||||
|
deny capability dac_override,
|
||||||
|
@@ -112,11 +120,15 @@ profile /usr/bin/docker (attach_disconnected, complain) {
|
||||||
|
/proc/tty/drivers r,
|
||||||
|
}
|
||||||
|
profile /sbin/iptables (complain) {
|
||||||
|
+{{if ge .Version 209000}}
|
||||||
|
signal (receive) peer=/usr/bin/docker,
|
||||||
|
+{{end}}
|
||||||
|
capability net_admin,
|
||||||
|
}
|
||||||
|
profile /sbin/auplink flags=(attach_disconnected, complain) {
|
||||||
|
+{{if ge .Version 209000}}
|
||||||
|
signal (receive) peer=/usr/bin/docker,
|
||||||
|
+{{end}}
|
||||||
|
capability sys_admin,
|
||||||
|
capability dac_override,
|
||||||
|
|
||||||
|
@@ -135,7 +147,9 @@ profile /usr/bin/docker (attach_disconnected, complain) {
|
||||||
|
/proc/[0-9]*/mounts rw,
|
||||||
|
}
|
||||||
|
profile /sbin/modprobe /bin/kmod (complain) {
|
||||||
|
+{{if ge .Version 209000}}
|
||||||
|
signal (receive) peer=/usr/bin/docker,
|
||||||
|
+{{end}}
|
||||||
|
capability sys_module,
|
||||||
|
/etc/ld.so.cache r,
|
||||||
|
/lib/** rm,
|
||||||
|
@@ -149,7 +163,9 @@ profile /usr/bin/docker (attach_disconnected, complain) {
|
||||||
|
}
|
||||||
|
# xz works via pipes, so we do not need access to the filesystem.
|
||||||
|
profile /usr/bin/xz (complain) {
|
||||||
|
+{{if ge .Version 209000}}
|
||||||
|
signal (receive) peer=/usr/bin/docker,
|
||||||
|
+{{end}}
|
||||||
|
/etc/ld.so.cache r,
|
||||||
|
/lib/** rm,
|
||||||
|
/usr/bin/xz rm,
|
||||||
|
diff --git a/pkg/aaparser/aaparser.go b/pkg/aaparser/aaparser.go
|
||||||
|
new file mode 100644
|
||||||
|
index 000000000000..89b48b2dba58
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/pkg/aaparser/aaparser.go
|
||||||
|
@@ -0,0 +1,86 @@
|
||||||
|
+// Package aaparser is a convenience package interacting with `apparmor_parser`.
|
||||||
|
+package aaparser // import "github.com/docker/docker/pkg/aaparser"
|
||||||
|
+
|
||||||
|
+import (
|
||||||
|
+ "fmt"
|
||||||
|
+ "os/exec"
|
||||||
|
+ "strconv"
|
||||||
|
+ "strings"
|
||||||
|
+)
|
||||||
|
+
|
||||||
|
+const (
|
||||||
|
+ binary = "apparmor_parser"
|
||||||
|
+)
|
||||||
|
+
|
||||||
|
+// GetVersion returns the major and minor version of apparmor_parser.
|
||||||
|
+func GetVersion() (int, error) {
|
||||||
|
+ output, err := cmd("", "--version")
|
||||||
|
+ if err != nil {
|
||||||
|
+ return -1, err
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return parseVersion(output)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+// cmd runs `apparmor_parser` with the passed arguments.
|
||||||
|
+func cmd(dir string, arg ...string) (string, error) {
|
||||||
|
+ c := exec.Command(binary, arg...)
|
||||||
|
+ c.Dir = dir
|
||||||
|
+
|
||||||
|
+ output, err := c.CombinedOutput()
|
||||||
|
+ if err != nil {
|
||||||
|
+ return "", fmt.Errorf("running `%s %s` failed with output: %s\nerror: %v", c.Path, strings.Join(c.Args, " "), output, err)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return string(output), nil
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+// parseVersion takes the output from `apparmor_parser --version` and returns
|
||||||
|
+// a representation of the {major, minor, patch} version as a single number of
|
||||||
|
+// the form MMmmPPP {major, minor, patch}.
|
||||||
|
+func parseVersion(output string) (int, error) {
|
||||||
|
+ // output is in the form of the following:
|
||||||
|
+ // AppArmor parser version 2.9.1
|
||||||
|
+ // Copyright (C) 1999-2008 Novell Inc.
|
||||||
|
+ // Copyright 2009-2012 Canonical Ltd.
|
||||||
|
+
|
||||||
|
+ lines := strings.SplitN(output, "\n", 2)
|
||||||
|
+ words := strings.Split(lines[0], " ")
|
||||||
|
+ version := words[len(words)-1]
|
||||||
|
+
|
||||||
|
+ // trim "-beta1" suffix from version="3.0.0-beta1" if exists
|
||||||
|
+ version = strings.SplitN(version, "-", 2)[0]
|
||||||
|
+ // also trim "~..." suffix used historically (https://gitlab.com/apparmor/apparmor/-/commit/bca67d3d27d219d11ce8c9cc70612bd637f88c10)
|
||||||
|
+ version = strings.SplitN(version, "~", 2)[0]
|
||||||
|
+
|
||||||
|
+ // split by major minor version
|
||||||
|
+ v := strings.Split(version, ".")
|
||||||
|
+ if len(v) == 0 || len(v) > 3 {
|
||||||
|
+ return -1, fmt.Errorf("parsing version failed for output: `%s`", output)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Default the versions to 0.
|
||||||
|
+ var majorVersion, minorVersion, patchLevel int
|
||||||
|
+
|
||||||
|
+ majorVersion, err := strconv.Atoi(v[0])
|
||||||
|
+ if err != nil {
|
||||||
|
+ return -1, err
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if len(v) > 1 {
|
||||||
|
+ minorVersion, err = strconv.Atoi(v[1])
|
||||||
|
+ if err != nil {
|
||||||
|
+ return -1, err
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if len(v) > 2 {
|
||||||
|
+ patchLevel, err = strconv.Atoi(v[2])
|
||||||
|
+ if err != nil {
|
||||||
|
+ return -1, err
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // major*10^5 + minor*10^3 + patch*10^0
|
||||||
|
+ numericVersion := majorVersion*1e5 + minorVersion*1e3 + patchLevel
|
||||||
|
+ return numericVersion, nil
|
||||||
|
+}
|
||||||
|
diff --git a/profiles/apparmor/apparmor.go b/profiles/apparmor/apparmor.go
|
||||||
|
index 277c853ebe1f..d1aad80cbfd2 100644
|
||||||
|
--- a/profiles/apparmor/apparmor.go
|
||||||
|
+++ b/profiles/apparmor/apparmor.go
|
||||||
|
@@ -11,10 +11,14 @@ import (
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
+
|
||||||
|
+ "github.com/docker/docker/pkg/aaparser"
|
||||||
|
)
|
||||||
|
|
||||||
|
-// profileDirectory is the file store for apparmor profiles and macros.
|
||||||
|
-const profileDirectory = "/etc/apparmor.d"
|
||||||
|
+var (
|
||||||
|
+ // profileDirectory is the file store for apparmor profiles and macros.
|
||||||
|
+ profileDirectory = "/etc/apparmor.d"
|
||||||
|
+)
|
||||||
|
|
||||||
|
// profileData holds information about the given profile for generation.
|
||||||
|
type profileData struct {
|
||||||
|
@@ -26,6 +30,8 @@ type profileData struct {
|
||||||
|
Imports []string
|
||||||
|
// InnerImports defines the apparmor functions to import in the profile.
|
||||||
|
InnerImports []string
|
||||||
|
+ // Version is the {major, minor, patch} version of apparmor_parser as a single number.
|
||||||
|
+ Version int
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateDefault creates an apparmor profile from ProfileData.
|
||||||
|
@@ -45,6 +51,12 @@ func (p *profileData) generateDefault(out io.Writer) error {
|
||||||
|
p.InnerImports = append(p.InnerImports, "#include <abstractions/base>")
|
||||||
|
}
|
||||||
|
|
||||||
|
+ ver, err := aaparser.GetVersion()
|
||||||
|
+ if err != nil {
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
+ p.Version = ver
|
||||||
|
+
|
||||||
|
return compiled.Execute(out, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/profiles/apparmor/template.go b/profiles/apparmor/template.go
|
||||||
|
index 8dbc1b610288..2062aab1ac99 100644
|
||||||
|
--- a/profiles/apparmor/template.go
|
||||||
|
+++ b/profiles/apparmor/template.go
|
||||||
|
@@ -23,6 +23,7 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
|
||||||
|
capability,
|
||||||
|
file,
|
||||||
|
umount,
|
||||||
|
+{{if ge .Version 208096}}
|
||||||
|
# Host (privileged) processes may send signals to container processes.
|
||||||
|
signal (receive) peer=unconfined,
|
||||||
|
# runc may send signals to container processes (for "docker stop").
|
||||||
|
@@ -33,6 +34,7 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
|
||||||
|
signal (receive) peer={{.DaemonProfile}},
|
||||||
|
# Container processes may send signals amongst themselves.
|
||||||
|
signal (send,receive) peer={{.Name}},
|
||||||
|
+{{end}}
|
||||||
|
|
||||||
|
deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir)
|
||||||
|
# deny write to files not in /proc/<number>/** or /proc/sys/**
|
||||||
|
@@ -53,7 +55,9 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
|
||||||
|
deny /sys/devices/virtual/powercap/** rwklx,
|
||||||
|
deny /sys/kernel/security/** rwklx,
|
||||||
|
|
||||||
|
+{{if ge .Version 208095}}
|
||||||
|
# suppress ptrace denials when using 'docker ps' or using 'ps' inside a container
|
||||||
|
ptrace (trace,read,tracedby,readby) peer={{.Name}},
|
||||||
|
+{{end}}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
--
|
||||||
|
2.45.2
|
||||||
|
|
890
0006-bsc1221916-update-to-patched-buildkit-version-to-fix.patch
Normal file
890
0006-bsc1221916-update-to-patched-buildkit-version-to-fix.patch
Normal file
@ -0,0 +1,890 @@
|
|||||||
|
From dd16d113b9215bf5b0b56c409e7272ce07525836 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Aleksa Sarai <cyphar@cyphar.com>
|
||||||
|
Date: Tue, 7 May 2024 01:51:25 +1000
|
||||||
|
Subject: [PATCH 6/7] bsc1221916: update to patched buildkit version to fix
|
||||||
|
symlink resolution
|
||||||
|
|
||||||
|
SUSE-Bugs: https://bugzilla.suse.com/show_bug.cgi?id=1221916
|
||||||
|
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
|
||||||
|
---
|
||||||
|
vendor.mod | 2 +
|
||||||
|
vendor.sum | 4 +-
|
||||||
|
.../buildkit/cache/contenthash/checksum.go | 393 ++++++++++--------
|
||||||
|
.../moby/buildkit/cache/contenthash/path.go | 161 +++----
|
||||||
|
vendor/modules.txt | 3 +-
|
||||||
|
5 files changed, 314 insertions(+), 249 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/vendor.mod b/vendor.mod
|
||||||
|
index d69d2aa9f87f..5c42a653b91b 100644
|
||||||
|
--- a/vendor.mod
|
||||||
|
+++ b/vendor.mod
|
||||||
|
@@ -114,6 +114,8 @@ require (
|
||||||
|
tags.cncf.io/container-device-interface v0.7.2
|
||||||
|
)
|
||||||
|
|
||||||
|
+replace github.com/moby/buildkit => github.com/cyphar/buildkit v0.0.0-20240624075140-0db2d2345b94
|
||||||
|
+
|
||||||
|
require (
|
||||||
|
cloud.google.com/go v0.110.8 // indirect
|
||||||
|
cloud.google.com/go/compute v1.23.1 // indirect
|
||||||
|
diff --git a/vendor.sum b/vendor.sum
|
||||||
|
index 7a5bd6b4077b..f2aba7f8d3eb 100644
|
||||||
|
--- a/vendor.sum
|
||||||
|
+++ b/vendor.sum
|
||||||
|
@@ -199,6 +199,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||||
|
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||||
|
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
|
+github.com/cyphar/buildkit v0.0.0-20240624075140-0db2d2345b94 h1:xBwPT+ap0LDYsQJh1VKm9NNEKF5A7e/P3TRjnbTqZUE=
|
||||||
|
+github.com/cyphar/buildkit v0.0.0-20240624075140-0db2d2345b94/go.mod h1:2cyVOv9NoHM7arphK9ZfHIWKn9YVZRFd1wXB8kKmEzY=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@@ -480,8 +482,6 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
|
github.com/mndrix/tap-go v0.0.0-20171203230836-629fa407e90b/go.mod h1:pzzDgJWZ34fGzaAZGFW22KVZDfyrYW+QABMrWnJBnSs=
|
||||||
|
-github.com/moby/buildkit v0.13.2 h1:nXNszM4qD9E7QtG7bFWPnDI1teUQFQglBzon/IU3SzI=
|
||||||
|
-github.com/moby/buildkit v0.13.2/go.mod h1:2cyVOv9NoHM7arphK9ZfHIWKn9YVZRFd1wXB8kKmEzY=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/ipvs v1.1.0 h1:ONN4pGaZQgAx+1Scz5RvWV4Q7Gb+mvfRh3NsPS+1XQQ=
|
||||||
|
diff --git a/vendor/github.com/moby/buildkit/cache/contenthash/checksum.go b/vendor/github.com/moby/buildkit/cache/contenthash/checksum.go
|
||||||
|
index e0f58d57b3db..ec649f69b5e0 100644
|
||||||
|
--- a/vendor/github.com/moby/buildkit/cache/contenthash/checksum.go
|
||||||
|
+++ b/vendor/github.com/moby/buildkit/cache/contenthash/checksum.go
|
||||||
|
@@ -10,6 +10,7 @@ import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
+ "sync/atomic"
|
||||||
|
|
||||||
|
iradix "github.com/hashicorp/go-immutable-radix"
|
||||||
|
"github.com/hashicorp/golang-lru/simplelru"
|
||||||
|
@@ -290,7 +291,7 @@ func keyPath(p string) string {
|
||||||
|
// HandleChange notifies the source about a modification operation
|
||||||
|
func (cc *cacheContext) HandleChange(kind fsutil.ChangeKind, p string, fi os.FileInfo, err error) (retErr error) {
|
||||||
|
p = keyPath(p)
|
||||||
|
- k := convertPathToKey([]byte(p))
|
||||||
|
+ k := convertPathToKey(p)
|
||||||
|
|
||||||
|
deleteDir := func(cr *CacheRecord) {
|
||||||
|
if cr.Type == CacheRecordTypeDir {
|
||||||
|
@@ -369,7 +370,7 @@ func (cc *cacheContext) HandleChange(kind fsutil.ChangeKind, p string, fi os.Fil
|
||||||
|
// note that the source may be called later because data writing is async
|
||||||
|
if fi.Mode()&os.ModeSymlink == 0 && stat.Linkname != "" {
|
||||||
|
ln := path.Join("/", filepath.ToSlash(stat.Linkname))
|
||||||
|
- v, ok := cc.txn.Get(convertPathToKey([]byte(ln)))
|
||||||
|
+ v, ok := cc.txn.Get(convertPathToKey(ln))
|
||||||
|
if ok {
|
||||||
|
cp := *v.(*CacheRecord)
|
||||||
|
cr = &cp
|
||||||
|
@@ -407,7 +408,7 @@ func (cc *cacheContext) Checksum(ctx context.Context, mountable cache.Mountable,
|
||||||
|
defer m.clean()
|
||||||
|
|
||||||
|
if !opts.Wildcard && len(opts.IncludePatterns) == 0 && len(opts.ExcludePatterns) == 0 {
|
||||||
|
- return cc.checksumFollow(ctx, m, p, opts.FollowLinks)
|
||||||
|
+ return cc.lazyChecksum(ctx, m, p, opts.FollowLinks)
|
||||||
|
}
|
||||||
|
|
||||||
|
includedPaths, err := cc.includedPaths(ctx, m, p, opts)
|
||||||
|
@@ -418,7 +419,7 @@ func (cc *cacheContext) Checksum(ctx context.Context, mountable cache.Mountable,
|
||||||
|
if opts.FollowLinks {
|
||||||
|
for i, w := range includedPaths {
|
||||||
|
if w.record.Type == CacheRecordTypeSymlink {
|
||||||
|
- dgst, err := cc.checksumFollow(ctx, m, w.path, opts.FollowLinks)
|
||||||
|
+ dgst, err := cc.lazyChecksum(ctx, m, w.path, opts.FollowLinks)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
@@ -445,30 +446,6 @@ func (cc *cacheContext) Checksum(ctx context.Context, mountable cache.Mountable,
|
||||||
|
return digester.Digest(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
-func (cc *cacheContext) checksumFollow(ctx context.Context, m *mount, p string, follow bool) (digest.Digest, error) {
|
||||||
|
- const maxSymlinkLimit = 255
|
||||||
|
- i := 0
|
||||||
|
- for {
|
||||||
|
- if i > maxSymlinkLimit {
|
||||||
|
- return "", errors.Errorf("too many symlinks: %s", p)
|
||||||
|
- }
|
||||||
|
- cr, err := cc.checksumNoFollow(ctx, m, p)
|
||||||
|
- if err != nil {
|
||||||
|
- return "", err
|
||||||
|
- }
|
||||||
|
- if cr.Type == CacheRecordTypeSymlink && follow {
|
||||||
|
- link := cr.Linkname
|
||||||
|
- if !path.IsAbs(cr.Linkname) {
|
||||||
|
- link = path.Join(path.Dir(p), link)
|
||||||
|
- }
|
||||||
|
- i++
|
||||||
|
- p = link
|
||||||
|
- } else {
|
||||||
|
- return cr.Digest, nil
|
||||||
|
- }
|
||||||
|
- }
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
func (cc *cacheContext) includedPaths(ctx context.Context, m *mount, p string, opts ChecksumOpts) ([]*includedPath, error) {
|
||||||
|
cc.mu.Lock()
|
||||||
|
defer cc.mu.Unlock()
|
||||||
|
@@ -478,12 +455,12 @@ func (cc *cacheContext) includedPaths(ctx context.Context, m *mount, p string, o
|
||||||
|
}
|
||||||
|
|
||||||
|
root := cc.tree.Root()
|
||||||
|
- scan, err := cc.needsScan(root, "")
|
||||||
|
+ scan, err := cc.needsScan(root, "", false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if scan {
|
||||||
|
- if err := cc.scanPath(ctx, m, ""); err != nil {
|
||||||
|
+ if err := cc.scanPath(ctx, m, "", false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -536,13 +513,13 @@ func (cc *cacheContext) includedPaths(ctx context.Context, m *mount, p string, o
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
origPrefix = p
|
||||||
|
- k = convertPathToKey([]byte(origPrefix))
|
||||||
|
+ k = convertPathToKey(origPrefix)
|
||||||
|
|
||||||
|
// We need to resolve symlinks here, in case the base path
|
||||||
|
// involves a symlink. That will match fsutil behavior of
|
||||||
|
// calling functions such as stat and walk.
|
||||||
|
var cr *CacheRecord
|
||||||
|
- k, cr, err = getFollowLinks(root, k, true)
|
||||||
|
+ k, cr, err = getFollowLinks(root, k, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
@@ -554,7 +531,7 @@ func (cc *cacheContext) includedPaths(ctx context.Context, m *mount, p string, o
|
||||||
|
iter.SeekLowerBound(append(append([]byte{}, k...), 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
- resolvedPrefix = string(convertKeyToPath(k))
|
||||||
|
+ resolvedPrefix = convertKeyToPath(k)
|
||||||
|
} else {
|
||||||
|
k, _, keyOk = iter.Next()
|
||||||
|
}
|
||||||
|
@@ -565,7 +542,7 @@ func (cc *cacheContext) includedPaths(ctx context.Context, m *mount, p string, o
|
||||||
|
)
|
||||||
|
|
||||||
|
for keyOk {
|
||||||
|
- fn := string(convertKeyToPath(k))
|
||||||
|
+ fn := convertKeyToPath(k)
|
||||||
|
|
||||||
|
// Convert the path prefix from what we found in the prefix
|
||||||
|
// tree to what the argument specified.
|
||||||
|
@@ -751,36 +728,12 @@ func wildcardPrefix(root *iradix.Node, p string) (string, []byte, bool, error) {
|
||||||
|
return "", nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
- linksWalked := 0
|
||||||
|
- k, cr, err := getFollowLinksWalk(root, convertPathToKey([]byte(d1)), true, &linksWalked)
|
||||||
|
+ // Only resolve the final symlink component if there are components in the
|
||||||
|
+ // wildcard segment.
|
||||||
|
+ k, cr, err := getFollowLinks(root, convertPathToKey(d1), d2 != "")
|
||||||
|
if err != nil {
|
||||||
|
return "", k, false, err
|
||||||
|
}
|
||||||
|
-
|
||||||
|
- if d2 != "" && cr != nil && cr.Type == CacheRecordTypeSymlink {
|
||||||
|
- // getFollowLinks only handles symlinks in path
|
||||||
|
- // components before the last component, so
|
||||||
|
- // handle last component in d1 specially.
|
||||||
|
- resolved := string(convertKeyToPath(k))
|
||||||
|
- for {
|
||||||
|
- v, ok := root.Get(k)
|
||||||
|
-
|
||||||
|
- if !ok {
|
||||||
|
- return d1, k, false, nil
|
||||||
|
- }
|
||||||
|
- if v.(*CacheRecord).Type != CacheRecordTypeSymlink {
|
||||||
|
- break
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- linksWalked++
|
||||||
|
- if linksWalked > 255 {
|
||||||
|
- return "", k, false, errors.Errorf("too many links")
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- resolved := cleanLink(resolved, v.(*CacheRecord).Linkname)
|
||||||
|
- k = convertPathToKey([]byte(resolved))
|
||||||
|
- }
|
||||||
|
- }
|
||||||
|
return d1, k, cr != nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -816,19 +769,22 @@ func containsWildcards(name string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
-func (cc *cacheContext) checksumNoFollow(ctx context.Context, m *mount, p string) (*CacheRecord, error) {
|
||||||
|
+func (cc *cacheContext) lazyChecksum(ctx context.Context, m *mount, p string, followTrailing bool) (digest.Digest, error) {
|
||||||
|
p = keyPath(p)
|
||||||
|
+ k := convertPathToKey(p)
|
||||||
|
|
||||||
|
+ // Try to look up the path directly without doing a scan.
|
||||||
|
cc.mu.RLock()
|
||||||
|
if cc.txn == nil {
|
||||||
|
root := cc.tree.Root()
|
||||||
|
cc.mu.RUnlock()
|
||||||
|
- v, ok := root.Get(convertPathToKey([]byte(p)))
|
||||||
|
- if ok {
|
||||||
|
- cr := v.(*CacheRecord)
|
||||||
|
- if cr.Digest != "" {
|
||||||
|
- return cr, nil
|
||||||
|
- }
|
||||||
|
+
|
||||||
|
+ _, cr, err := getFollowLinks(root, k, followTrailing)
|
||||||
|
+ if err != nil {
|
||||||
|
+ return "", err
|
||||||
|
+ }
|
||||||
|
+ if cr != nil && cr.Digest != "" {
|
||||||
|
+ return cr.Digest, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cc.mu.RUnlock()
|
||||||
|
@@ -848,7 +804,11 @@ func (cc *cacheContext) checksumNoFollow(ctx context.Context, m *mount, p string
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
- return cc.lazyChecksum(ctx, m, p)
|
||||||
|
+ cr, err := cc.scanChecksum(ctx, m, p, followTrailing)
|
||||||
|
+ if err != nil {
|
||||||
|
+ return "", err
|
||||||
|
+ }
|
||||||
|
+ return cr.Digest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *cacheContext) commitActiveTransaction() {
|
||||||
|
@@ -856,7 +816,7 @@ func (cc *cacheContext) commitActiveTransaction() {
|
||||||
|
addParentToMap(d, cc.dirtyMap)
|
||||||
|
}
|
||||||
|
for d := range cc.dirtyMap {
|
||||||
|
- k := convertPathToKey([]byte(d))
|
||||||
|
+ k := convertPathToKey(d)
|
||||||
|
if _, ok := cc.txn.Get(k); ok {
|
||||||
|
cc.txn.Insert(k, &CacheRecord{Type: CacheRecordTypeDir})
|
||||||
|
}
|
||||||
|
@@ -867,21 +827,21 @@ func (cc *cacheContext) commitActiveTransaction() {
|
||||||
|
cc.txn = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
-func (cc *cacheContext) lazyChecksum(ctx context.Context, m *mount, p string) (*CacheRecord, error) {
|
||||||
|
+func (cc *cacheContext) scanChecksum(ctx context.Context, m *mount, p string, followTrailing bool) (*CacheRecord, error) {
|
||||||
|
root := cc.tree.Root()
|
||||||
|
- scan, err := cc.needsScan(root, p)
|
||||||
|
+ scan, err := cc.needsScan(root, p, followTrailing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if scan {
|
||||||
|
- if err := cc.scanPath(ctx, m, p); err != nil {
|
||||||
|
+ if err := cc.scanPath(ctx, m, p, followTrailing); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- k := convertPathToKey([]byte(p))
|
||||||
|
+ k := convertPathToKey(p)
|
||||||
|
txn := cc.tree.Txn()
|
||||||
|
root = txn.Root()
|
||||||
|
- cr, updated, err := cc.checksum(ctx, root, txn, m, k, true)
|
||||||
|
+ cr, updated, err := cc.checksum(ctx, root, txn, m, k, followTrailing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
@@ -890,9 +850,9 @@ func (cc *cacheContext) lazyChecksum(ctx context.Context, m *mount, p string) (*
|
||||||
|
return cr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
-func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *iradix.Txn, m *mount, k []byte, follow bool) (*CacheRecord, bool, error) {
|
||||||
|
+func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *iradix.Txn, m *mount, k []byte, followTrailing bool) (*CacheRecord, bool, error) {
|
||||||
|
origk := k
|
||||||
|
- k, cr, err := getFollowLinks(root, k, follow)
|
||||||
|
+ k, cr, err := getFollowLinks(root, k, followTrailing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
@@ -918,7 +878,9 @@ func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *ir
|
||||||
|
}
|
||||||
|
h.Write(bytes.TrimPrefix(subk, k))
|
||||||
|
|
||||||
|
- subcr, _, err := cc.checksum(ctx, root, txn, m, subk, true)
|
||||||
|
+ // We do not follow trailing links when checksumming a directory's
|
||||||
|
+ // contents.
|
||||||
|
+ subcr, _, err := cc.checksum(ctx, root, txn, m, subk, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
@@ -935,7 +897,7 @@ func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *ir
|
||||||
|
dgst = digest.NewDigest(digest.SHA256, h)
|
||||||
|
|
||||||
|
default:
|
||||||
|
- p := string(convertKeyToPath(bytes.TrimSuffix(k, []byte{0})))
|
||||||
|
+ p := convertKeyToPath(bytes.TrimSuffix(k, []byte{0}))
|
||||||
|
|
||||||
|
target, err := m.mount(ctx)
|
||||||
|
if err != nil {
|
||||||
|
@@ -967,42 +929,82 @@ func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *ir
|
||||||
|
return cr2, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
-// needsScan returns false if path is in the tree or a parent path is in tree
|
||||||
|
-// and subpath is missing
|
||||||
|
-func (cc *cacheContext) needsScan(root *iradix.Node, p string) (bool, error) {
|
||||||
|
- var linksWalked int
|
||||||
|
- return cc.needsScanFollow(root, p, &linksWalked)
|
||||||
|
+// pathSet is a set of path prefixes that can be used to see if a given path is
|
||||||
|
+// lexically a child of any path in the set. All paths provided to this set
|
||||||
|
+// MUST be absolute and use / as the separator.
|
||||||
|
+type pathSet struct {
|
||||||
|
+ // prefixes contains paths of the form "/a/b/", so that we correctly detect
|
||||||
|
+ // /a/b as being a parent of /a/b/c but not /a/bc.
|
||||||
|
+ prefixes []string
|
||||||
|
}
|
||||||
|
|
||||||
|
-func (cc *cacheContext) needsScanFollow(root *iradix.Node, p string, linksWalked *int) (bool, error) {
|
||||||
|
- if p == "/" {
|
||||||
|
- p = ""
|
||||||
|
- }
|
||||||
|
- v, ok := root.Get(convertPathToKey([]byte(p)))
|
||||||
|
- if !ok {
|
||||||
|
- if p == "" {
|
||||||
|
- return true, nil
|
||||||
|
+// add a path to the set. This is a no-op if includes(path) == true.
|
||||||
|
+func (s *pathSet) add(p string) {
|
||||||
|
+ // Ensure the path is absolute and clean.
|
||||||
|
+ p = path.Join("/", p)
|
||||||
|
+ if !s.includes(p) {
|
||||||
|
+ if p != "/" {
|
||||||
|
+ p += "/"
|
||||||
|
}
|
||||||
|
- return cc.needsScanFollow(root, path.Clean(path.Dir(p)), linksWalked)
|
||||||
|
+ s.prefixes = append(s.prefixes, p)
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+// includes returns true iff there is a path in the pathSet which is a lexical
|
||||||
|
+// parent of the given path. The provided path MUST be an absolute path and
|
||||||
|
+// MUST NOT contain any ".." components, as they will be path.Clean'd.
|
||||||
|
+func (s pathSet) includes(p string) bool {
|
||||||
|
+ // Ensure the path is absolute and clean.
|
||||||
|
+ p = path.Join("/", p)
|
||||||
|
+ if p != "/" {
|
||||||
|
+ p += "/"
|
||||||
|
}
|
||||||
|
- cr := v.(*CacheRecord)
|
||||||
|
- if cr.Type == CacheRecordTypeSymlink {
|
||||||
|
- if *linksWalked > 255 {
|
||||||
|
- return false, errTooManyLinks
|
||||||
|
+ for _, prefix := range s.prefixes {
|
||||||
|
+ if strings.HasPrefix(p, prefix) {
|
||||||
|
+ return true
|
||||||
|
}
|
||||||
|
- *linksWalked++
|
||||||
|
- link := path.Clean(cr.Linkname)
|
||||||
|
- if !path.IsAbs(cr.Linkname) {
|
||||||
|
- link = path.Join("/", path.Dir(p), link)
|
||||||
|
+ }
|
||||||
|
+ return false
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+// needsScan returns false if path is in the tree or a parent path is in tree
|
||||||
|
+// and subpath is missing.
|
||||||
|
+func (cc *cacheContext) needsScan(root *iradix.Node, path string, followTrailing bool) (bool, error) {
|
||||||
|
+ var (
|
||||||
|
+ goodPaths pathSet
|
||||||
|
+ hasParentInTree bool
|
||||||
|
+ )
|
||||||
|
+ k := convertPathToKey(path)
|
||||||
|
+ _, cr, err := getFollowLinksCallback(root, k, followTrailing, func(subpath string, cr *CacheRecord) error {
|
||||||
|
+ // If we found a path that exists in the cache, add it to the set of
|
||||||
|
+ // known-scanned paths. Otherwise, verify whether the not-found subpath
|
||||||
|
+ // is inside a known-scanned path (we might have hit a "..", taking us
|
||||||
|
+ // out of the scanned paths, or we might hit a non-existent path inside
|
||||||
|
+ // a scanned path). getFollowLinksCallback iterates left-to-right, so
|
||||||
|
+ // we will always hit ancestors first.
|
||||||
|
+ if cr != nil {
|
||||||
|
+ hasParentInTree = cr.Type != CacheRecordTypeSymlink
|
||||||
|
+ goodPaths.add(subpath)
|
||||||
|
+ } else {
|
||||||
|
+ hasParentInTree = goodPaths.includes(subpath)
|
||||||
|
}
|
||||||
|
- return cc.needsScanFollow(root, link, linksWalked)
|
||||||
|
+ return nil
|
||||||
|
+ })
|
||||||
|
+ if err != nil {
|
||||||
|
+ return false, err
|
||||||
|
}
|
||||||
|
- return false, nil
|
||||||
|
+ return cr == nil && !hasParentInTree, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
-func (cc *cacheContext) scanPath(ctx context.Context, m *mount, p string) (retErr error) {
|
||||||
|
+// Only used by TestNeedScanChecksumRegression to make sure scanPath is not
|
||||||
|
+// called for paths we have already scanned.
|
||||||
|
+var (
|
||||||
|
+ scanCounterEnable bool
|
||||||
|
+ scanCounter atomic.Uint64
|
||||||
|
+)
|
||||||
|
+
|
||||||
|
+func (cc *cacheContext) scanPath(ctx context.Context, m *mount, p string, followTrailing bool) (retErr error) {
|
||||||
|
p = path.Join("/", p)
|
||||||
|
- d, _ := path.Split(p)
|
||||||
|
|
||||||
|
mp, err := m.mount(ctx)
|
||||||
|
if err != nil {
|
||||||
|
@@ -1012,33 +1014,42 @@ func (cc *cacheContext) scanPath(ctx context.Context, m *mount, p string) (retEr
|
||||||
|
n := cc.tree.Root()
|
||||||
|
txn := cc.tree.Txn()
|
||||||
|
|
||||||
|
- parentPath, err := rootPath(mp, filepath.FromSlash(d), func(p, link string) error {
|
||||||
|
+ resolvedPath, err := rootPath(mp, filepath.FromSlash(p), followTrailing, func(p, link string) error {
|
||||||
|
cr := &CacheRecord{
|
||||||
|
Type: CacheRecordTypeSymlink,
|
||||||
|
Linkname: filepath.ToSlash(link),
|
||||||
|
}
|
||||||
|
- k := []byte(path.Join("/", filepath.ToSlash(p)))
|
||||||
|
- k = convertPathToKey(k)
|
||||||
|
- txn.Insert(k, cr)
|
||||||
|
+ p = path.Join("/", filepath.ToSlash(p))
|
||||||
|
+ txn.Insert(convertPathToKey(p), cr)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
- err = filepath.Walk(parentPath, func(itemPath string, fi os.FileInfo, err error) error {
|
||||||
|
+ // Scan the parent directory of the path we resolved, unless we're at the
|
||||||
|
+ // root (in which case we scan the root).
|
||||||
|
+ scanPath := filepath.Dir(resolvedPath)
|
||||||
|
+ if !strings.HasPrefix(filepath.ToSlash(scanPath)+"/", filepath.ToSlash(mp)+"/") {
|
||||||
|
+ scanPath = resolvedPath
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ err = filepath.Walk(scanPath, func(itemPath string, fi os.FileInfo, err error) error {
|
||||||
|
+ if scanCounterEnable {
|
||||||
|
+ scanCounter.Add(1)
|
||||||
|
+ }
|
||||||
|
if err != nil {
|
||||||
|
+ // If the root doesn't exist, ignore the error.
|
||||||
|
+ if itemPath == scanPath && errors.Is(err, os.ErrNotExist) {
|
||||||
|
+ return nil
|
||||||
|
+ }
|
||||||
|
return errors.Wrapf(err, "failed to walk %s", itemPath)
|
||||||
|
}
|
||||||
|
rel, err := filepath.Rel(mp, itemPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
- k := []byte(path.Join("/", filepath.ToSlash(rel)))
|
||||||
|
- if string(k) == "/" {
|
||||||
|
- k = []byte{}
|
||||||
|
- }
|
||||||
|
- k = convertPathToKey(k)
|
||||||
|
+ k := convertPathToKey(keyPath(rel))
|
||||||
|
if _, ok := n.Get(k); !ok {
|
||||||
|
cr := &CacheRecord{
|
||||||
|
Type: CacheRecordTypeFile,
|
||||||
|
@@ -1071,55 +1082,118 @@ func (cc *cacheContext) scanPath(ctx context.Context, m *mount, p string) (retEr
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
-func getFollowLinks(root *iradix.Node, k []byte, follow bool) ([]byte, *CacheRecord, error) {
|
||||||
|
- var linksWalked int
|
||||||
|
- return getFollowLinksWalk(root, k, follow, &linksWalked)
|
||||||
|
+// followLinksCallback is called after we try to resolve each element. If the
|
||||||
|
+// path was not found, cr is nil.
|
||||||
|
+type followLinksCallback func(path string, cr *CacheRecord) error
|
||||||
|
+
|
||||||
|
+// getFollowLinks is shorthand for getFollowLinksCallback(..., nil).
|
||||||
|
+func getFollowLinks(root *iradix.Node, k []byte, followTrailing bool) ([]byte, *CacheRecord, error) {
|
||||||
|
+ return getFollowLinksCallback(root, k, followTrailing, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
-func getFollowLinksWalk(root *iradix.Node, k []byte, follow bool, linksWalked *int) ([]byte, *CacheRecord, error) {
|
||||||
|
+// getFollowLinksCallback looks up the requested key, fully resolving any
|
||||||
|
+// symlink components encountered. The implementation is heavily based on
|
||||||
|
+// <https://github.com/cyphar/filepath-securejoin>.
|
||||||
|
+//
|
||||||
|
+// followTrailing indicates whether the *final component* of the path should be
|
||||||
|
+// resolved (effectively O_PATH|O_NOFOLLOW). Note that (in contrast to some
|
||||||
|
+// Linux APIs), followTrailing is obeyed even if the key has a trailing slash
|
||||||
|
+// (though paths like "foo/link/." will cause the link to be resolved).
|
||||||
|
+//
|
||||||
|
+// cb is a callback that is called for each path component encountered during
|
||||||
|
+// path resolution (after the path component is looked up in the cache). This
|
||||||
|
+// means for a path like /a/b/c, the callback will be called for at least
|
||||||
|
+//
|
||||||
|
+// {/, /a, /a/b, /a/b/c}
|
||||||
|
+//
|
||||||
|
+// Note that if any of the components are symlinks, the paths will depend on
|
||||||
|
+// the symlink contents and there will be more callbacks. If the requested key
|
||||||
|
+// has a trailing slash, the callback will also be called for the final
|
||||||
|
+// trailing-slash lookup (/a/b/c/ in the above example). Note that
|
||||||
|
+// getFollowLinksCallback will try to look up the original key directly first
|
||||||
|
+// and the callback is not called for this first lookup.
|
||||||
|
+func getFollowLinksCallback(root *iradix.Node, k []byte, followTrailing bool, cb followLinksCallback) ([]byte, *CacheRecord, error) {
|
||||||
|
v, ok := root.Get(k)
|
||||||
|
- if ok {
|
||||||
|
+ if ok && (!followTrailing || v.(*CacheRecord).Type != CacheRecordTypeSymlink) {
|
||||||
|
return k, v.(*CacheRecord), nil
|
||||||
|
}
|
||||||
|
- if !follow || len(k) == 0 {
|
||||||
|
+ if len(k) == 0 {
|
||||||
|
return k, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
- dir, file := splitKey(k)
|
||||||
|
+ var (
|
||||||
|
+ currentPath = "/"
|
||||||
|
+ remainingPath = convertKeyToPath(k)
|
||||||
|
+ linksWalked int
|
||||||
|
+ cr *CacheRecord
|
||||||
|
+ )
|
||||||
|
+ // Trailing slashes are significant for the cache, but path.Clean strips
|
||||||
|
+ // them. We only care about the slash for the final lookup.
|
||||||
|
+ remainingPath, hadTrailingSlash := strings.CutSuffix(remainingPath, "/")
|
||||||
|
+ for remainingPath != "" {
|
||||||
|
+ // Get next component.
|
||||||
|
+ var part string
|
||||||
|
+ if i := strings.IndexRune(remainingPath, '/'); i == -1 {
|
||||||
|
+ part, remainingPath = remainingPath, ""
|
||||||
|
+ } else {
|
||||||
|
+ part, remainingPath = remainingPath[:i], remainingPath[i+1:]
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- k, parent, err := getFollowLinksWalk(root, dir, follow, linksWalked)
|
||||||
|
- if err != nil {
|
||||||
|
- return nil, nil, err
|
||||||
|
- }
|
||||||
|
- if parent != nil {
|
||||||
|
- if parent.Type == CacheRecordTypeSymlink {
|
||||||
|
- *linksWalked++
|
||||||
|
- if *linksWalked > 255 {
|
||||||
|
- return nil, nil, errors.Errorf("too many links")
|
||||||
|
+ // Apply the component to the path. Since it is a single component, and
|
||||||
|
+ // our current path contains no symlinks, we can just apply it
|
||||||
|
+ // leixically.
|
||||||
|
+ nextPath := keyPath(path.Join("/", currentPath, part))
|
||||||
|
+ // In contrast to rootPath, we don't skip lookups for no-op components
|
||||||
|
+ // or / because we need to call the callback for every path component
|
||||||
|
+ // we hit (including /) and we need to make sure that the CacheRecord
|
||||||
|
+ // we return is correct after every iteration.
|
||||||
|
+
|
||||||
|
+ cr = nil
|
||||||
|
+ v, ok := root.Get(convertPathToKey(nextPath))
|
||||||
|
+ if ok {
|
||||||
|
+ cr = v.(*CacheRecord)
|
||||||
|
+ }
|
||||||
|
+ if cb != nil {
|
||||||
|
+ if err := cb(nextPath, cr); err != nil {
|
||||||
|
+ return nil, nil, err
|
||||||
|
}
|
||||||
|
+ }
|
||||||
|
+ if !ok || cr.Type != CacheRecordTypeSymlink {
|
||||||
|
+ currentPath = nextPath
|
||||||
|
+ continue
|
||||||
|
+ }
|
||||||
|
+ if !followTrailing && remainingPath == "" {
|
||||||
|
+ currentPath = nextPath
|
||||||
|
+ break
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- link := cleanLink(string(convertKeyToPath(dir)), parent.Linkname)
|
||||||
|
- return getFollowLinksWalk(root, append(convertPathToKey([]byte(link)), file...), follow, linksWalked)
|
||||||
|
+ linksWalked++
|
||||||
|
+ if linksWalked > maxSymlinkLimit {
|
||||||
|
+ return nil, nil, errTooManyLinks
|
||||||
|
}
|
||||||
|
- }
|
||||||
|
- k = append(k, file...)
|
||||||
|
- v, ok = root.Get(k)
|
||||||
|
- if ok {
|
||||||
|
- return k, v.(*CacheRecord), nil
|
||||||
|
- }
|
||||||
|
- return k, nil, nil
|
||||||
|
-}
|
||||||
|
|
||||||
|
-func cleanLink(dir, linkname string) string {
|
||||||
|
- dirPath := path.Clean(dir)
|
||||||
|
- if dirPath == "." || dirPath == "/" {
|
||||||
|
- dirPath = ""
|
||||||
|
+ remainingPath = cr.Linkname + "/" + remainingPath
|
||||||
|
+ if path.IsAbs(cr.Linkname) {
|
||||||
|
+ currentPath = "/"
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
- link := path.Clean(linkname)
|
||||||
|
- if !path.IsAbs(link) {
|
||||||
|
- return path.Join("/", path.Join(path.Dir(dirPath), link))
|
||||||
|
+ // We've already looked up the final component. However, if there was a
|
||||||
|
+ // trailing slash in the original path, we need to do the lookup again with
|
||||||
|
+ // the slash applied.
|
||||||
|
+ if hadTrailingSlash {
|
||||||
|
+ cr = nil
|
||||||
|
+ currentPath += "/"
|
||||||
|
+ v, ok := root.Get(convertPathToKey(currentPath))
|
||||||
|
+ if ok {
|
||||||
|
+ cr = v.(*CacheRecord)
|
||||||
|
+ }
|
||||||
|
+ if cb != nil {
|
||||||
|
+ if err := cb(currentPath, cr); err != nil {
|
||||||
|
+ return nil, nil, err
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
- return link
|
||||||
|
+ return convertPathToKey(currentPath), cr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareDigest(fp, p string, fi os.FileInfo) (digest.Digest, error) {
|
||||||
|
@@ -1176,25 +1250,10 @@ func poolsCopy(dst io.Writer, src io.Reader) (written int64, err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
-func convertPathToKey(p []byte) []byte {
|
||||||
|
+func convertPathToKey(p string) []byte {
|
||||||
|
return bytes.Replace([]byte(p), []byte("/"), []byte{0}, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
-func convertKeyToPath(p []byte) []byte {
|
||||||
|
- return bytes.Replace([]byte(p), []byte{0}, []byte("/"), -1)
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-func splitKey(k []byte) ([]byte, []byte) {
|
||||||
|
- foundBytes := false
|
||||||
|
- i := len(k) - 1
|
||||||
|
- for {
|
||||||
|
- if i <= 0 || foundBytes && k[i] == 0 {
|
||||||
|
- break
|
||||||
|
- }
|
||||||
|
- if k[i] != 0 {
|
||||||
|
- foundBytes = true
|
||||||
|
- }
|
||||||
|
- i--
|
||||||
|
- }
|
||||||
|
- return append([]byte{}, k[:i]...), k[i:]
|
||||||
|
+func convertKeyToPath(p []byte) string {
|
||||||
|
+ return string(bytes.Replace(p, []byte{0}, []byte("/"), -1))
|
||||||
|
}
|
||||||
|
diff --git a/vendor/github.com/moby/buildkit/cache/contenthash/path.go b/vendor/github.com/moby/buildkit/cache/contenthash/path.go
|
||||||
|
index 42b7fd8349c7..ae950f713241 100644
|
||||||
|
--- a/vendor/github.com/moby/buildkit/cache/contenthash/path.go
|
||||||
|
+++ b/vendor/github.com/moby/buildkit/cache/contenthash/path.go
|
||||||
|
@@ -1,108 +1,111 @@
|
||||||
|
+// This code mostly comes from <https://github.com/cyphar/filepath-securejoin>.
|
||||||
|
+
|
||||||
|
+// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
|
||||||
|
+// Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
|
||||||
|
+// Use of this source code is governed by a BSD-style
|
||||||
|
+// license that can be found in the LICENSE file.
|
||||||
|
+
|
||||||
|
package contenthash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
+ "strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
-var (
|
||||||
|
- errTooManyLinks = errors.New("too many links")
|
||||||
|
-)
|
||||||
|
+var errTooManyLinks = errors.New("too many links")
|
||||||
|
+
|
||||||
|
+const maxSymlinkLimit = 255
|
||||||
|
|
||||||
|
type onSymlinkFunc func(string, string) error
|
||||||
|
|
||||||
|
-// rootPath joins a path with a root, evaluating and bounding any
|
||||||
|
-// symlink to the root directory.
|
||||||
|
-// This is containerd/continuity/fs RootPath implementation with a callback on
|
||||||
|
-// resolving the symlink.
|
||||||
|
-func rootPath(root, path string, cb onSymlinkFunc) (string, error) {
|
||||||
|
- if path == "" {
|
||||||
|
+// rootPath joins a path with a root, evaluating and bounding any symlink to
|
||||||
|
+// the root directory. This is a slightly modified version of SecureJoin from
|
||||||
|
+// github.com/cyphar/filepath-securejoin, with a callback which we call after
|
||||||
|
+// each symlink resolution.
|
||||||
|
+func rootPath(root, unsafePath string, followTrailing bool, cb onSymlinkFunc) (string, error) {
|
||||||
|
+ if unsafePath == "" {
|
||||||
|
return root, nil
|
||||||
|
}
|
||||||
|
- var linksWalked int // to protect against cycles
|
||||||
|
- for {
|
||||||
|
- i := linksWalked
|
||||||
|
- newpath, err := walkLinks(root, path, &linksWalked, cb)
|
||||||
|
- if err != nil {
|
||||||
|
- return "", err
|
||||||
|
- }
|
||||||
|
- path = newpath
|
||||||
|
- if i == linksWalked {
|
||||||
|
- newpath = filepath.Join("/", newpath)
|
||||||
|
- if path == newpath {
|
||||||
|
- return filepath.Join(root, newpath), nil
|
||||||
|
- }
|
||||||
|
- path = newpath
|
||||||
|
- }
|
||||||
|
- }
|
||||||
|
-}
|
||||||
|
|
||||||
|
-func walkLink(root, path string, linksWalked *int, cb onSymlinkFunc) (newpath string, islink bool, err error) {
|
||||||
|
- if *linksWalked > 255 {
|
||||||
|
- return "", false, errTooManyLinks
|
||||||
|
- }
|
||||||
|
+ unsafePath = filepath.FromSlash(unsafePath)
|
||||||
|
+ var (
|
||||||
|
+ currentPath string
|
||||||
|
+ linksWalked int
|
||||||
|
+ )
|
||||||
|
+ for unsafePath != "" {
|
||||||
|
+ // Windows-specific: remove any drive letters from the path.
|
||||||
|
+ if v := filepath.VolumeName(unsafePath); v != "" {
|
||||||
|
+ unsafePath = unsafePath[len(v):]
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- path = filepath.Join("/", path)
|
||||||
|
- if path == "/" {
|
||||||
|
- return path, false, nil
|
||||||
|
- }
|
||||||
|
- realPath := filepath.Join(root, path)
|
||||||
|
+ // Remove any unnecessary trailing slashes.
|
||||||
|
+ unsafePath = strings.TrimSuffix(unsafePath, string(filepath.Separator))
|
||||||
|
|
||||||
|
- fi, err := os.Lstat(realPath)
|
||||||
|
- if err != nil {
|
||||||
|
- // If path does not yet exist, treat as non-symlink
|
||||||
|
- if errors.Is(err, os.ErrNotExist) {
|
||||||
|
- return path, false, nil
|
||||||
|
+ // Get the next path component.
|
||||||
|
+ var part string
|
||||||
|
+ if i := strings.IndexRune(unsafePath, filepath.Separator); i == -1 {
|
||||||
|
+ part, unsafePath = unsafePath, ""
|
||||||
|
+ } else {
|
||||||
|
+ part, unsafePath = unsafePath[:i], unsafePath[i+1:]
|
||||||
|
}
|
||||||
|
- return "", false, err
|
||||||
|
- }
|
||||||
|
- if fi.Mode()&os.ModeSymlink == 0 {
|
||||||
|
- return path, false, nil
|
||||||
|
- }
|
||||||
|
- newpath, err = os.Readlink(realPath)
|
||||||
|
- if err != nil {
|
||||||
|
- return "", false, err
|
||||||
|
- }
|
||||||
|
- if cb != nil {
|
||||||
|
- if err := cb(path, newpath); err != nil {
|
||||||
|
- return "", false, err
|
||||||
|
- }
|
||||||
|
- }
|
||||||
|
- *linksWalked++
|
||||||
|
- return newpath, true, nil
|
||||||
|
-}
|
||||||
|
|
||||||
|
-func walkLinks(root, path string, linksWalked *int, cb onSymlinkFunc) (string, error) {
|
||||||
|
- switch dir, file := filepath.Split(path); {
|
||||||
|
- case dir == "":
|
||||||
|
- newpath, _, err := walkLink(root, file, linksWalked, cb)
|
||||||
|
- return newpath, err
|
||||||
|
- case file == "":
|
||||||
|
- if os.IsPathSeparator(dir[len(dir)-1]) {
|
||||||
|
- if dir == "/" {
|
||||||
|
- return dir, nil
|
||||||
|
- }
|
||||||
|
- return walkLinks(root, dir[:len(dir)-1], linksWalked, cb)
|
||||||
|
+ // Apply the component lexically to the path we are building. path does
|
||||||
|
+ // not contain any symlinks, and we are lexically dealing with a single
|
||||||
|
+ // component, so it's okay to do filepath.Clean here.
|
||||||
|
+ nextPath := filepath.Join(string(filepath.Separator), currentPath, part)
|
||||||
|
+ if nextPath == string(filepath.Separator) {
|
||||||
|
+ // If we end up back at the root, we don't need to re-evaluate /.
|
||||||
|
+ currentPath = ""
|
||||||
|
+ continue
|
||||||
|
}
|
||||||
|
- newpath, _, err := walkLink(root, dir, linksWalked, cb)
|
||||||
|
- return newpath, err
|
||||||
|
- default:
|
||||||
|
- newdir, err := walkLinks(root, dir, linksWalked, cb)
|
||||||
|
- if err != nil {
|
||||||
|
+ fullPath := root + string(filepath.Separator) + nextPath
|
||||||
|
+
|
||||||
|
+ // Figure out whether the path is a symlink.
|
||||||
|
+ fi, err := os.Lstat(fullPath)
|
||||||
|
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
- newpath, islink, err := walkLink(root, filepath.Join(newdir, file), linksWalked, cb)
|
||||||
|
+ // Treat non-existent path components the same as non-symlinks (we
|
||||||
|
+ // can't do any better here).
|
||||||
|
+ if errors.Is(err, os.ErrNotExist) || fi.Mode()&os.ModeSymlink == 0 {
|
||||||
|
+ currentPath = nextPath
|
||||||
|
+ continue
|
||||||
|
+ }
|
||||||
|
+ // Don't resolve the final component with !followTrailing.
|
||||||
|
+ if !followTrailing && unsafePath == "" {
|
||||||
|
+ currentPath = nextPath
|
||||||
|
+ break
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // It's a symlink, so get its contents and expand it by prepending it
|
||||||
|
+ // to the yet-unparsed path.
|
||||||
|
+ linksWalked++
|
||||||
|
+ if linksWalked > maxSymlinkLimit {
|
||||||
|
+ return "", errTooManyLinks
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ dest, err := os.Readlink(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
- if !islink {
|
||||||
|
- return newpath, nil
|
||||||
|
+ if cb != nil {
|
||||||
|
+ if err := cb(nextPath, dest); err != nil {
|
||||||
|
+ return "", err
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
- if filepath.IsAbs(newpath) {
|
||||||
|
- return newpath, nil
|
||||||
|
+
|
||||||
|
+ unsafePath = dest + string(filepath.Separator) + unsafePath
|
||||||
|
+ // Absolute symlinks reset any work we've already done.
|
||||||
|
+ if filepath.IsAbs(dest) {
|
||||||
|
+ currentPath = ""
|
||||||
|
}
|
||||||
|
- return filepath.Join(newdir, newpath), nil
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ // There should be no lexical components left in path here, but just for
|
||||||
|
+ // safety do a filepath.Clean before the join.
|
||||||
|
+ finalPath := filepath.Join(string(filepath.Separator), currentPath)
|
||||||
|
+ return filepath.Join(root, finalPath), nil
|
||||||
|
}
|
||||||
|
diff --git a/vendor/modules.txt b/vendor/modules.txt
|
||||||
|
index 7f3e6497785d..247f49f3518e 100644
|
||||||
|
--- a/vendor/modules.txt
|
||||||
|
+++ b/vendor/modules.txt
|
||||||
|
@@ -711,7 +711,7 @@ github.com/mitchellh/hashstructure/v2
|
||||||
|
# github.com/mitchellh/reflectwalk v1.0.2
|
||||||
|
## explicit
|
||||||
|
github.com/mitchellh/reflectwalk
|
||||||
|
-# github.com/moby/buildkit v0.13.2
|
||||||
|
+# github.com/moby/buildkit v0.13.2 => github.com/cyphar/buildkit v0.0.0-20240624075140-0db2d2345b94
|
||||||
|
## explicit; go 1.21
|
||||||
|
github.com/moby/buildkit/api/services/control
|
||||||
|
github.com/moby/buildkit/api/types
|
||||||
|
@@ -1610,3 +1610,4 @@ tags.cncf.io/container-device-interface/pkg/parser
|
||||||
|
# tags.cncf.io/container-device-interface/specs-go v0.7.0
|
||||||
|
## explicit; go 1.19
|
||||||
|
tags.cncf.io/container-device-interface/specs-go
|
||||||
|
+# github.com/moby/buildkit => github.com/cyphar/buildkit v0.0.0-20240624075140-0db2d2345b94
|
||||||
|
--
|
||||||
|
2.45.2
|
||||||
|
|
@ -0,0 +1,53 @@
|
|||||||
|
From 62035ba22a45bde6bed2da321e7ad954f5b461b4 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Aleksa Sarai <cyphar@cyphar.com>
|
||||||
|
Date: Wed, 19 Jun 2024 16:30:49 +1000
|
||||||
|
Subject: [PATCH 7/7] bsc1214855: volume: use AtomicWriteFile to save volume
|
||||||
|
options
|
||||||
|
|
||||||
|
If the system (or Docker) crashes while saivng the volume options, on
|
||||||
|
restart the daemon will error out when trying to read the options file
|
||||||
|
because it doesn't contain valid JSON.
|
||||||
|
|
||||||
|
In such a crash scenario, the new volume will be treated as though it
|
||||||
|
has the default options configuration. This is not ideal, but volumes
|
||||||
|
created on very old Docker versions (pre-1.11[1], circa 2016) do not
|
||||||
|
have opts.json and so doing some kind of cleanup when loading the volume
|
||||||
|
store (even if we take care to only delete empty volumes) could delete
|
||||||
|
existing volumes carried over from very old Docker versions that users
|
||||||
|
would not expect to disappear.
|
||||||
|
|
||||||
|
Ultimately, if a user creates a volume and the system crashes, a volume
|
||||||
|
that has the wrong config is better than Docker not being able to start.
|
||||||
|
|
||||||
|
[1]: commit b05b2370757d ("Support mount opts for `local` volume driver")
|
||||||
|
|
||||||
|
SUSE-Bugs: https://bugzilla.suse.com/show_bug.cgi?id=1214855
|
||||||
|
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
|
||||||
|
---
|
||||||
|
volume/local/local.go | 3 ++-
|
||||||
|
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/volume/local/local.go b/volume/local/local.go
|
||||||
|
index 6e96aeea4189..4412f34a3da9 100644
|
||||||
|
--- a/volume/local/local.go
|
||||||
|
+++ b/volume/local/local.go
|
||||||
|
@@ -17,6 +17,7 @@ import (
|
||||||
|
"github.com/docker/docker/daemon/names"
|
||||||
|
"github.com/docker/docker/errdefs"
|
||||||
|
"github.com/docker/docker/pkg/idtools"
|
||||||
|
+ "github.com/docker/docker/pkg/ioutils"
|
||||||
|
"github.com/docker/docker/quota"
|
||||||
|
"github.com/docker/docker/volume"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
@@ -388,7 +389,7 @@ func (v *localVolume) saveOpts() error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
- err = os.WriteFile(filepath.Join(v.rootPath, "opts.json"), b, 0o600)
|
||||||
|
+ err = ioutils.AtomicWriteFile(filepath.Join(v.rootPath, "opts.json"), b, 0o600)
|
||||||
|
if err != nil {
|
||||||
|
return errdefs.System(errors.Wrap(err, "error while persisting volume options"))
|
||||||
|
}
|
||||||
|
--
|
||||||
|
2.45.2
|
||||||
|
|
5
80-docker.rules
Normal file
5
80-docker.rules
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# hide docker's loopback devices from udisks, and thus from user desktops
|
||||||
|
SUBSYSTEM=="block", ENV{DM_NAME}=="docker-*", ENV{UDISKS_PRESENTATION_HIDE}="1", ENV{UDISKS_IGNORE}="1"
|
||||||
|
SUBSYSTEM=="block", DEVPATH=="/devices/virtual/block/loop*", ATTR{loop/backing_file}=="/var/lib/docker/*", ENV{UDISKS_PRESENTATION_HIDE}="1", ENV{UDISKS_IGNORE}="1"
|
||||||
|
|
||||||
|
|
230
README_SUSE.md
Normal file
230
README_SUSE.md
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
# Abstract
|
||||||
|
|
||||||
|
Docker is a lightweight "virtualization" method to run multiple virtual units
|
||||||
|
(containers, akin to “chroot”) simultaneously on a single control host.
|
||||||
|
Containers are isolated with Kernel Control Groups (cgroups) and Kernel Namespaces.
|
||||||
|
|
||||||
|
Docker provides an operating system-level virtualization where the Kernel
|
||||||
|
controls the isolated containers. With other full virtualization solutions
|
||||||
|
like Xen, KVM, or libvirt the processor simulates a complete hardware
|
||||||
|
environment and controls its virtual machines.
|
||||||
|
|
||||||
|
# Terminology
|
||||||
|
|
||||||
|
## chroot
|
||||||
|
|
||||||
|
A change root (chroot, or change root jail) is a section in the file system
|
||||||
|
which is isolated from the rest of the file system. For this purpose, the chroot
|
||||||
|
command is used to change the root of the file system. A program which is
|
||||||
|
executed in such a “chroot jail” cannot access files outside the designated
|
||||||
|
directory tree.
|
||||||
|
|
||||||
|
## cgroups
|
||||||
|
|
||||||
|
Kernel Control Groups (commonly referred to as just “cgroups”) are a Kernel
|
||||||
|
feature that allows aggregating or partitioning tasks (processes) and all their
|
||||||
|
children into hierarchical organized groups to isolate resources.
|
||||||
|
|
||||||
|
## Image
|
||||||
|
|
||||||
|
A "virtual machine" on the host server that can run any Linux system, for
|
||||||
|
example openSUSE, SUSE Linux Enterprise Desktop, or SUSE Linux Enterprise Server.
|
||||||
|
|
||||||
|
A Docker image is made by a series of layers built one over the other. Each layer
|
||||||
|
corresponds to a permanent change committed from a container to the image.
|
||||||
|
|
||||||
|
For more details checkout [Docker's official documentation](http://docs.docker.com/terms/image/).
|
||||||
|
|
||||||
|
## Image Name
|
||||||
|
|
||||||
|
A name that refers to an image. The name is used by the docker commands.
|
||||||
|
|
||||||
|
## Container
|
||||||
|
|
||||||
|
A running Docker Image.
|
||||||
|
|
||||||
|
## Container ID
|
||||||
|
|
||||||
|
A ID that refers to a container. The ID is used by the docker commands.
|
||||||
|
|
||||||
|
## TAG
|
||||||
|
|
||||||
|
A string associated to a Image. It commonly used to identify a specific version
|
||||||
|
of a Image (like tags in version control systems). It is also possible to refer
|
||||||
|
the same Image with different TAGs.
|
||||||
|
|
||||||
|
## Kernel Namespaces
|
||||||
|
|
||||||
|
A Kernel feature to isolate some resources like network, users, and others for
|
||||||
|
a group of processes.
|
||||||
|
|
||||||
|
## Docker Host Server
|
||||||
|
|
||||||
|
The system that runs the Docker daemon, provides the images, and the management
|
||||||
|
control capabilities through cgroups.
|
||||||
|
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
Docker is a platform that allows developers and sysadmins to manage the complete
|
||||||
|
lifecycle of images.
|
||||||
|
|
||||||
|
Docker makes incredibly easy to build, ship and run images containing
|
||||||
|
applications.
|
||||||
|
|
||||||
|
Benefits of Docker:
|
||||||
|
|
||||||
|
* Isolating applications and operating systems through containers.
|
||||||
|
* Providing nearly native performance as Docker manages allocation of resources
|
||||||
|
in real-time.
|
||||||
|
* Controlling network interfaces and applying resources inside containers through cgroups.
|
||||||
|
* Versioning of images.
|
||||||
|
* Building images based on existing ones.
|
||||||
|
* Sharining/storing on [public](http://docs.docker.com/docker-hub/) or
|
||||||
|
[private](http://docs.docker.com/userguide/dockerrepos/#private-repositories)
|
||||||
|
repositories.
|
||||||
|
|
||||||
|
Limitations of Docker:
|
||||||
|
|
||||||
|
* All Docker containers are running inside the host system's Kernel and not with
|
||||||
|
a different Kernel.
|
||||||
|
* Only allows Linux "guest" operating systems.
|
||||||
|
* Docker is not a full virtualization stack like Xen, KVM, or libvirt.
|
||||||
|
* Security depends on the host system. Refer to the [official documentation](http://docs.docker.com/articles/security/)
|
||||||
|
for more details.
|
||||||
|
|
||||||
|
## Container drivers
|
||||||
|
|
||||||
|
Docker has different backend drivers to handle the containers. The recommended
|
||||||
|
on is [libcontainer](https://github.com/docker/libcontainer), which is also the
|
||||||
|
default choice. This driver provides direct access with cgroups.
|
||||||
|
|
||||||
|
The Docker packages ships also a LXC driver which handles containers using the
|
||||||
|
LXC tools.
|
||||||
|
|
||||||
|
At the time of writing, upstream is working on a `libvirt-lxc` driver.
|
||||||
|
|
||||||
|
## Storage drivers
|
||||||
|
|
||||||
|
Docker supports different storage drivers:
|
||||||
|
|
||||||
|
* `vfs`: this driver is automatically used when the Docker host filesystem
|
||||||
|
does not support copy-on-write. This is a simple driver which does not offer
|
||||||
|
some of the advantages of Docker (like sharing layers, more on that in the
|
||||||
|
next sections). It is highly reliable but also slow.
|
||||||
|
* `devicemapper`: this driver relies on the device-mapper thin provisioning
|
||||||
|
module. It supports copy-on-write, hence it offers all the advantages of
|
||||||
|
Docker.
|
||||||
|
* `btrfs`: this driver relies on Btrfs to provide all the features required
|
||||||
|
by Docker. To use this driver the `/var/lib/docker` directory must be on a
|
||||||
|
btrfs filesystem.
|
||||||
|
* `AUFS`: this driver relies on AUFS union filesystem. Neither the upstream
|
||||||
|
kernel nor the SUSE one supports this filesystem. Hence the AUFS driver is
|
||||||
|
not built into the SUSE Docker package.
|
||||||
|
|
||||||
|
It is possible to specify which driver to use by changing the value of the
|
||||||
|
`DOCKER_OPTS` variable defined inside of the `/etc/sysconfig/docker` file.
|
||||||
|
This can be done either manually or using &yast; by browsing to:
|
||||||
|
* System
|
||||||
|
* /etc/sysconfig Editor
|
||||||
|
* System
|
||||||
|
* Management
|
||||||
|
* DOCKER_OPTS
|
||||||
|
menu and entering the `-s storage_driver` string.
|
||||||
|
|
||||||
|
For example, to force the usage of the `devicemapper` driver
|
||||||
|
enter the following text:
|
||||||
|
```
|
||||||
|
DOCKER_OPTS="-s devicemapper
|
||||||
|
```
|
||||||
|
|
||||||
|
It is recommended to have `/var/lib/docker` mounted on a different filesystem
|
||||||
|
to not affect the Docker host OS in case of a filesystem corruption.
|
||||||
|
|
||||||
|
# Setting up a Docker host
|
||||||
|
|
||||||
|
Prepare the host:
|
||||||
|
|
||||||
|
1. Install the `docker` package.
|
||||||
|
2. Automatically start the Docker daemon at boot:
|
||||||
|
`sudo systemctl enable docker`
|
||||||
|
3. Start the Docker daemon:
|
||||||
|
`sudo systemctl start docker`
|
||||||
|
|
||||||
|
The Docker daemon listens on a local socket which is accessible only by the `root`
|
||||||
|
user and by the members of the `docker` group.
|
||||||
|
|
||||||
|
The `docker` group is automatically created at package installation time. To
|
||||||
|
allow a certain user to connect to the local Docker daemon use the following
|
||||||
|
command:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo /usr/sbin/usermod -aG docker <username>
|
||||||
|
```
|
||||||
|
|
||||||
|
The user will be able to communicate with the local Docker daemon upon his next
|
||||||
|
login.
|
||||||
|
|
||||||
|
## Networking
|
||||||
|
|
||||||
|
If you want your containers to be able to access the external network you must
|
||||||
|
enable the `net.ipv4.ip_forward` rule.
|
||||||
|
This can be done using YaST by browsing to the
|
||||||
|
`Network Devices -> Network Settings -> Routing` menu and ensuring that the
|
||||||
|
`Enable IPv4 Forwarding` box is checked.
|
||||||
|
|
||||||
|
This option cannot be changed when networking is handled by the Network Manager.
|
||||||
|
In such cases the `/etc/sysconfig/SuSEfirewall2` file needs to be edited by
|
||||||
|
hand to ensure the `FW_ROUTE` flag is set to `yes` like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
FW_ROUTE="yes"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Basic Docker operations
|
||||||
|
|
||||||
|
Images can be pulled from [Docker's central index](http://index.docker.io) using
|
||||||
|
the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker pull <image name>
|
||||||
|
```
|
||||||
|
|
||||||
|
Containers can be started using the `docker run` command.
|
||||||
|
|
||||||
|
Please refer to the [official documentation](http://docs.docker.com/)
|
||||||
|
for more details.
|
||||||
|
|
||||||
|
|
||||||
|
# Building Docker containers using KIWI
|
||||||
|
|
||||||
|
Starting from version 5.06.8 KIWI can be used to build Docker images.
|
||||||
|
Please refer to KIWI's [official documentation](https://doc.opensuse.org/projects/kiwi/doc/#chap.lxc).
|
||||||
|
The official `kiwi-doc` package contains examples of Docker images.
|
||||||
|
|
||||||
|
## Docker build system versus KIWI
|
||||||
|
|
||||||
|
Docker has an [internal build system](http://docs.docker.com/reference/builder/)
|
||||||
|
which makes incredibly easy to create new images based on existing ones.
|
||||||
|
|
||||||
|
Some users might be confused about what to use. The right approach is to build
|
||||||
|
the [base images](http://docs.docker.com/terms/image/#base-image-def) using KIWI
|
||||||
|
and then use them as foundation blocks inside of your Docker's build system.
|
||||||
|
|
||||||
|
That two advantages:
|
||||||
|
|
||||||
|
1. Be able to use docker specific directives (like `ENTRYPOINT`, `EXPOSE`, ...).
|
||||||
|
2. Be able to reuse already existing layers.
|
||||||
|
|
||||||
|
Sharing the common layers between different images makes possible to:
|
||||||
|
|
||||||
|
* Use less disk space on the Docker hosts.
|
||||||
|
* Make the deployments faster: only the requested layers are sent over the
|
||||||
|
network (it is like upgrading installed packages using delta rpms).
|
||||||
|
* Take full advantage of caching while building Docker images: this will result
|
||||||
|
in faster executions of `docker build` command.
|
||||||
|
|
||||||
|
To recap: KIWI is not to be intended as a replacement for Docker's build system.
|
||||||
|
It rather complements with it.
|
||||||
|
|
22
_service
Normal file
22
_service
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<services>
|
||||||
|
<service name="tar_scm" mode="manual">
|
||||||
|
<param name="url">https://github.com/moby/moby.git</param>
|
||||||
|
<param name="scm">git</param>
|
||||||
|
<param name="exclude">.git</param>
|
||||||
|
<param name="versionformat">26.1.5_ce_%h</param>
|
||||||
|
<param name="revision">v26.1.5</param>
|
||||||
|
<param name="filename">docker</param>
|
||||||
|
</service>
|
||||||
|
<service name="tar_scm" mode="manual">
|
||||||
|
<param name="url">https://github.com/docker/cli.git</param>
|
||||||
|
<param name="scm">git</param>
|
||||||
|
<param name="exclude">.git</param>
|
||||||
|
<param name="versionformat">26.1.5_ce</param>
|
||||||
|
<param name="revision">v26.1.5</param>
|
||||||
|
<param name="filename">docker-cli</param>
|
||||||
|
</service>
|
||||||
|
<service name="recompress" mode="manual">
|
||||||
|
<param name="file">docker-*.tar</param>
|
||||||
|
<param name="compression">xz</param>
|
||||||
|
</service>
|
||||||
|
</services>
|
23382
cli-0001-docs-include-required-tools-in-source-tree.patch
Normal file
23382
cli-0001-docs-include-required-tools-in-source-tree.patch
Normal file
File diff suppressed because it is too large
Load Diff
3
docker-26.1.4_ce_de5c9cf0b96e.tar.xz
Normal file
3
docker-26.1.4_ce_de5c9cf0b96e.tar.xz
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:3a866c020abe705657cb373e692db7f1ad4ad547b9e25c7a557a06f4549a63c9
|
||||||
|
size 9909596
|
3
docker-26.1.5_ce_411e817ddf71.tar.xz
Normal file
3
docker-26.1.5_ce_411e817ddf71.tar.xz
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:e2d3862e95b45b04830bbce47827e07eceaa41761fa5182bd9b492621cbe8469
|
||||||
|
size 9910176
|
27
docker-audit.rules
Normal file
27
docker-audit.rules
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
##
|
||||||
|
# Audit rules based on CIS Docker 1.6 Benchmark v1.0.0
|
||||||
|
# https://benchmarks.cisecurity.org/tools2/docker/CIS_Docker_1.6_Benchmark_v1.0.0.pdf
|
||||||
|
# Not all of these apply to SUSE.
|
||||||
|
# 1.8 Audit docker daemon
|
||||||
|
-w /usr/bin/docker -k docker
|
||||||
|
# 1.9 Audit Docker files and directories
|
||||||
|
-w /var/lib/docker -k docker
|
||||||
|
# 1.10 Audit /etc/docker
|
||||||
|
-w /etc/docker -k docker
|
||||||
|
# 1.11 Audit Docker files and directories - docker-registry.service
|
||||||
|
-w /usr/lib/systemd/system/docker-registry.service -k docker
|
||||||
|
# 1.12 Audit Docker files and directories - docker.service
|
||||||
|
-w /usr/lib/systemd/system/docker.service -k docker
|
||||||
|
# 1.13 Audit Docker files and directories - /var/run/docker.sock
|
||||||
|
-w /var/run/docker.sock -k docker
|
||||||
|
# 1.14 Audit Docker files and directories - /etc/sysconfig/docker
|
||||||
|
-w /etc/sysconfig/docker -k docker
|
||||||
|
# 1.15 Audit Docker files and directories - /etc/sysconfig/docker-network
|
||||||
|
-w /etc/sysconfig/docker-network -k docker
|
||||||
|
# 1.16 Audit Docker files and directories - /etc/sysconfig/docker-registry
|
||||||
|
-w /etc/sysconfig/docker-registry -k docker
|
||||||
|
# 1.17 Audit Docker files and directories - /etc/sysconfig/docker-storage
|
||||||
|
-w /etc/sysconfig/docker-storage -k docker
|
||||||
|
# 1.18 Audit Docker files and directories - /etc/default/docker
|
||||||
|
-w /etc/default/docker -k docker
|
||||||
|
## end docker audit rules
|
3
docker-cli-26.1.4_ce.tar.xz
Normal file
3
docker-cli-26.1.4_ce.tar.xz
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:9a2b7ab7e665e9469fdd71bca1dd28ead5dc58dc9886f285f1fa75978ef5c078
|
||||||
|
size 3971272
|
3
docker-cli-26.1.5_ce.tar.xz
Normal file
3
docker-cli-26.1.5_ce.tar.xz
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:3723a617ed00e85117c3de2691e1aec6267020ee49fdb0db72c12981d0d26f9e
|
||||||
|
size 3972080
|
8
docker-daemon.json
Normal file
8
docker-daemon.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"log-level": "warn",
|
||||||
|
"log-driver": "json-file",
|
||||||
|
"log-opts": {
|
||||||
|
"max-size": "10m",
|
||||||
|
"max-file": "5"
|
||||||
|
}
|
||||||
|
}
|
2
docker-rpmlintrc
Normal file
2
docker-rpmlintrc
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
addFilter("^docker-bash-completion.noarch: (E|W): non-executable-script /usr/share/bash-completion/completions/docker")
|
||||||
|
addFilter("^docker-zsh-completion.noarch: W: non-conffile-in-etc /etc/zsh_completion.d/_docker")
|
4138
docker.changes
Normal file
4138
docker.changes
Normal file
File diff suppressed because it is too large
Load Diff
45
docker.service
Normal file
45
docker.service
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Docker Application Container Engine
|
||||||
|
Documentation=http://docs.docker.com
|
||||||
|
After=network.target lvm2-monitor.service firewalld.service
|
||||||
|
# We don't use the docker socket activation, but doing this ensures that the
|
||||||
|
# docker.socket unit is alive while Docker is (docker.socket has BindsTo, so we
|
||||||
|
# only need a weak requirement to make sure starting docker.service also
|
||||||
|
# "starts" the socket service). Forcefully stopping docker.socket will not
|
||||||
|
# cause docker to die, but there's no nice workaround for that.
|
||||||
|
Wants=docker.socket
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
EnvironmentFile=/etc/sysconfig/docker
|
||||||
|
|
||||||
|
# While Docker has support for socket activation (-H fd://), this is not
|
||||||
|
# enabled by default because enabling socket activation means that on boot your
|
||||||
|
# containers won't start until someone tries to administer the Docker daemon.
|
||||||
|
Type=notify
|
||||||
|
ExecStart=/usr/bin/dockerd --add-runtime oci=/usr/sbin/runc $DOCKER_NETWORK_OPTIONS $DOCKER_OPTS
|
||||||
|
ExecReload=/bin/kill -s HUP $MAINPID
|
||||||
|
|
||||||
|
# Having non-zero Limit*s causes performance problems due to accounting overhead
|
||||||
|
# in the kernel. We recommend using cgroups to do container-local accounting.
|
||||||
|
LimitNOFILE=1048576
|
||||||
|
LimitNPROC=infinity
|
||||||
|
LimitCORE=infinity
|
||||||
|
|
||||||
|
# Uncomment TasksMax if your systemd version supports it.
|
||||||
|
# Only systemd 226 and above support this property.
|
||||||
|
TasksMax=infinity
|
||||||
|
|
||||||
|
# Set delegate yes so that systemd does not reset the cgroups of docker containers
|
||||||
|
# Only systemd 218 and above support this property.
|
||||||
|
Delegate=yes
|
||||||
|
|
||||||
|
# Kill only the docker process, not all processes in the cgroup.
|
||||||
|
KillMode=process
|
||||||
|
|
||||||
|
# Restart the docker process if it exits prematurely.
|
||||||
|
Restart=on-failure
|
||||||
|
StartLimitBurst=3
|
||||||
|
StartLimitInterval=60s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
18
docker.socket
Normal file
18
docker.socket
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Docker Socket for the API
|
||||||
|
# We use BindsTo in order to make sure that you cannot use socket-activation
|
||||||
|
# with Docker (Docker must always start at boot if enabled, otherwise
|
||||||
|
# containers will not run until some administrator interacts with Docker).
|
||||||
|
BindsTo=docker.service
|
||||||
|
|
||||||
|
[Socket]
|
||||||
|
# If /var/run is not implemented as a symlink to /run, you may need to
|
||||||
|
# specify ListenStream=/var/run/docker.sock instead.
|
||||||
|
ListenStream=/run/docker.sock
|
||||||
|
SocketMode=0660
|
||||||
|
SocketUser=root
|
||||||
|
SocketGroup=docker
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sockets.target
|
||||||
|
|
417
docker.spec
Normal file
417
docker.spec
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
#
|
||||||
|
# spec file for package docker
|
||||||
|
#
|
||||||
|
# Copyright (c) 2023 SUSE LLC
|
||||||
|
#
|
||||||
|
# All modifications and additions to the file contributed by third parties
|
||||||
|
# remain the property of their copyright owners, unless otherwise agreed
|
||||||
|
# upon. The license for this file, and modifications and additions to the
|
||||||
|
# file, is the same license as for the pristine package itself (unless the
|
||||||
|
# license for the pristine package is not an Open Source License, in which
|
||||||
|
# case the license is the MIT License). An "Open Source License" is a
|
||||||
|
# license that conforms to the Open Source Definition (Version 1.9)
|
||||||
|
# published by the Open Source Initiative.
|
||||||
|
|
||||||
|
# Please submit bugfixes or comments via https://bugs.opensuse.org/
|
||||||
|
#
|
||||||
|
# nodebuginfo
|
||||||
|
|
||||||
|
%bcond_without apparmor
|
||||||
|
|
||||||
|
# Where important update information will be stored, such that an administrator
|
||||||
|
# is guaranteed to see the relevant warning.
|
||||||
|
%define update_messages %{_localstatedir}/adm/update-messages/%{name}-%{version}-%{release}
|
||||||
|
|
||||||
|
#Compat macro for new _fillupdir macro introduced in Nov 2017
|
||||||
|
%if ! %{defined _fillupdir}
|
||||||
|
%define _fillupdir /var/adm/fillup-templates
|
||||||
|
%endif
|
||||||
|
|
||||||
|
# Used when generating the "build" information for Docker version. The value of
|
||||||
|
# git_commit_epoch is unused here (we use SOURCE_DATE_EPOCH, which rpm
|
||||||
|
# helpfully injects into our build environment from the changelog). If you want
|
||||||
|
# to generate a new git_commit_epoch, use this:
|
||||||
|
# $ date --date="$(git show --format=fuller --date=iso $COMMIT_ID | grep -oP '(?<=^CommitDate: ).*')" '+%s'
|
||||||
|
%define real_version 26.1.5
|
||||||
|
%define git_version 411e817ddf71
|
||||||
|
%define git_commit_epoch 1721763388
|
||||||
|
|
||||||
|
Name: docker
|
||||||
|
Version: %{real_version}_ce
|
||||||
|
# This "nice version" is so that docker --version gives a result that can be
|
||||||
|
# parsed by other people. boo#1182476
|
||||||
|
%define nice_version %{real_version}-ce
|
||||||
|
Release: 0
|
||||||
|
Summary: The Moby-project Linux container runtime
|
||||||
|
License: Apache-2.0
|
||||||
|
Group: System/Management
|
||||||
|
URL: http://www.docker.io
|
||||||
|
Source: %{name}-%{version}_%{git_version}.tar.xz
|
||||||
|
Source1: %{name}-cli-%{version}.tar.xz
|
||||||
|
Source3: docker-rpmlintrc
|
||||||
|
# TODO: Move these source files to somewhere nicer.
|
||||||
|
Source100: docker.service
|
||||||
|
Source101: docker.socket
|
||||||
|
Source110: 80-docker.rules
|
||||||
|
Source120: sysconfig.docker
|
||||||
|
Source130: README_SUSE.md
|
||||||
|
Source140: docker-audit.rules
|
||||||
|
Source150: docker-daemon.json
|
||||||
|
Source160: docker.sysusers
|
||||||
|
# NOTE: All of these patches are maintained in <https://github.com/suse/docker>
|
||||||
|
# in the suse-v<version> branch. Make sure you update the patches in that
|
||||||
|
# branch and then git-format-patch the patch here.
|
||||||
|
# SUSE-FEATURE: Adds the /run/secrets mountpoint inside all Docker containers
|
||||||
|
# which is not snapshotted when images are committed.
|
||||||
|
Patch100: 0001-SECRETS-daemon-allow-directory-creation-in-run-secre.patch
|
||||||
|
Patch101: 0002-SECRETS-SUSE-implement-SUSE-container-secrets.patch
|
||||||
|
# UPSTREAM: Revert of upstream patch to keep SLE-12 build working.
|
||||||
|
Patch200: 0003-BUILD-SLE12-revert-graphdriver-btrfs-use-kernel-UAPI.patch
|
||||||
|
# UPSTREAM: Backport of <https://github.com/moby/moby/pull/41954>.
|
||||||
|
Patch201: 0004-bsc1073877-apparmor-clobber-docker-default-profile-o.patch
|
||||||
|
# UPSTREAM: Revert of upstream patches to make apparmor work on SLE 12.
|
||||||
|
Patch202: 0005-SLE12-revert-apparmor-remove-version-conditionals-fr.patch
|
||||||
|
# UPSTREAM: Backport of <https://github.com/moby/buildkit/pull/4896> and
|
||||||
|
# <https://github.com/moby/buildkit/pull/5060>.
|
||||||
|
Patch203: 0006-bsc1221916-update-to-patched-buildkit-version-to-fix.patch
|
||||||
|
# UPSTREAM: Backport of <https://github.com/moby/moby/pull/48034>.
|
||||||
|
Patch204: 0007-bsc1214855-volume-use-AtomicWriteFile-to-save-volume.patch
|
||||||
|
# UPSTREAM: Backport of <https://github.com/docker/cli/pull/4228>.
|
||||||
|
Patch900: cli-0001-docs-include-required-tools-in-source-tree.patch
|
||||||
|
BuildRequires: audit
|
||||||
|
BuildRequires: bash-completion
|
||||||
|
BuildRequires: ca-certificates
|
||||||
|
BuildRequires: device-mapper-devel >= 1.2.68
|
||||||
|
BuildRequires: fdupes
|
||||||
|
%if %{with apparmor}
|
||||||
|
BuildRequires: libapparmor-devel
|
||||||
|
%endif
|
||||||
|
BuildRequires: libbtrfs-devel >= 3.8
|
||||||
|
BuildRequires: libseccomp-devel >= 2.2
|
||||||
|
BuildRequires: libtool
|
||||||
|
BuildRequires: linux-glibc-devel
|
||||||
|
BuildRequires: procps
|
||||||
|
BuildRequires: sqlite3-devel
|
||||||
|
BuildRequires: zsh
|
||||||
|
BuildRequires: fish
|
||||||
|
BuildRequires: go-go-md2man
|
||||||
|
BuildRequires: pkgconfig(libsystemd)
|
||||||
|
BuildRequires: sysuser-tools
|
||||||
|
BuildRequires: golang(API) = 1.21
|
||||||
|
%if %{with apparmor}
|
||||||
|
%if 0%{?sle_version} >= 150000
|
||||||
|
# This conditional only works on rpm>=4.13, which SLE 12 doesn't have. But we
|
||||||
|
# don't need to support Docker+selinux for SLE 12 anyway.
|
||||||
|
Requires: (apparmor-parser or container-selinux)
|
||||||
|
# This recommends is added to make sure that even if you have container-selinux
|
||||||
|
# installed you will still be prompted to install apparmor-parser which Docker
|
||||||
|
# requires to apply AppArmor profiles (for SELinux systems this doesn't matter
|
||||||
|
# but if you switch back to AppArmor on reboot this would result in insecure
|
||||||
|
# containers).
|
||||||
|
Recommends: apparmor-parser
|
||||||
|
%else
|
||||||
|
Requires: apparmor-parser
|
||||||
|
%endif
|
||||||
|
%else
|
||||||
|
Requires: container-selinux
|
||||||
|
%endif
|
||||||
|
Requires: ca-certificates-mozilla
|
||||||
|
# The docker-proxy binary used to be in a separate package. We obsolete it,
|
||||||
|
# since now docker-proxy is maintained as part of this package.
|
||||||
|
Obsoletes: docker-libnetwork < 0.7.0.2
|
||||||
|
Provides: docker-libnetwork = 0.7.0.2.%{version}
|
||||||
|
# Required to actually run containers. We require the minimum version that is
|
||||||
|
# pinned by Docker, but in order to avoid headaches we allow for updates.
|
||||||
|
Requires: runc >= 1.1.9
|
||||||
|
Requires: containerd >= 1.7.3
|
||||||
|
# Needed for --init support. We don't use "tini", we use our own implementation
|
||||||
|
# which handles edge-cases better.
|
||||||
|
Requires: catatonit
|
||||||
|
# Provides mkfs.ext4 - used by Docker when devicemapper storage driver is used
|
||||||
|
Requires: e2fsprogs
|
||||||
|
Requires: iproute2 >= 3.5
|
||||||
|
Requires: iptables >= 1.4
|
||||||
|
Requires: procps
|
||||||
|
Requires: tar >= 1.26
|
||||||
|
Requires: xz >= 4.9
|
||||||
|
%?sysusers_requires
|
||||||
|
Requires(post): %fillup_prereq
|
||||||
|
Requires(post): udev
|
||||||
|
Requires(post): shadow
|
||||||
|
# Not necessary, but must be installed when the underlying system is
|
||||||
|
# configured to use lvm and the user doesn't explicitly provide a
|
||||||
|
# different storage-driver than devicemapper
|
||||||
|
Recommends: lvm2 >= 2.2.89
|
||||||
|
Recommends: git-core >= 1.7
|
||||||
|
# Required for "docker buildx" support.
|
||||||
|
Recommends: %{name}-buildx
|
||||||
|
Recommends: %{name}-rootless-extras
|
||||||
|
ExcludeArch: s390 ppc
|
||||||
|
|
||||||
|
%description
|
||||||
|
Docker complements LXC with a high-level API which operates at the process
|
||||||
|
level. It runs unix processes with strong guarantees of isolation and
|
||||||
|
repeatability across servers.
|
||||||
|
|
||||||
|
Docker is a great building block for automating distributed systems: large-scale
|
||||||
|
web deployments, database clusters, continuous deployment systems, private PaaS,
|
||||||
|
service-oriented architectures, etc.
|
||||||
|
|
||||||
|
%package rootless-extras
|
||||||
|
Summary: Rootless support for Docker
|
||||||
|
Group: System/Management
|
||||||
|
Requires: %{name} = %{version}
|
||||||
|
Requires: slirp4netns >= 0.4
|
||||||
|
Requires: fuse-overlayfs >= 0.7
|
||||||
|
Requires: rootlesskit
|
||||||
|
BuildArch: noarch
|
||||||
|
|
||||||
|
%description rootless-extras
|
||||||
|
Rootless support for Docker.
|
||||||
|
Use dockerd-rootless.sh to run the daemon.
|
||||||
|
Use dockerd-rootless-setuptool.sh to setup systemd for dockerd-rootless.sh.
|
||||||
|
|
||||||
|
%package bash-completion
|
||||||
|
Summary: Bash Completion for %{name}
|
||||||
|
Group: System/Shells
|
||||||
|
Requires: %{name} = %{version}
|
||||||
|
Requires: bash-completion
|
||||||
|
Supplements: packageand(%{name}:bash-completion)
|
||||||
|
BuildArch: noarch
|
||||||
|
|
||||||
|
%description bash-completion
|
||||||
|
Bash command line completion support for %{name}.
|
||||||
|
|
||||||
|
%package zsh-completion
|
||||||
|
Summary: Zsh Completion for %{name}
|
||||||
|
Group: System/Shells
|
||||||
|
Requires: %{name} = %{version}
|
||||||
|
Requires: zsh
|
||||||
|
Supplements: packageand(%{name}:zsh)
|
||||||
|
BuildArch: noarch
|
||||||
|
|
||||||
|
%description zsh-completion
|
||||||
|
Zsh command line completion support for %{name}.
|
||||||
|
|
||||||
|
%package fish-completion
|
||||||
|
Summary: Fish completion for %{name}
|
||||||
|
Group: System/Shells
|
||||||
|
Requires: %{name} = %{version}
|
||||||
|
Requires: fish
|
||||||
|
Supplements: packageand(%{name}:fish)
|
||||||
|
BuildArch: noarch
|
||||||
|
|
||||||
|
%description fish-completion
|
||||||
|
Fish command line completion support for %{name}.
|
||||||
|
|
||||||
|
%prep
|
||||||
|
# docker-cli
|
||||||
|
%define cli_builddir %{_builddir}/%{name}-cli-%{version}
|
||||||
|
%setup -q -T -b 1 -n %{name}-cli-%{version}
|
||||||
|
[ "%{cli_builddir}" = "$PWD" ]
|
||||||
|
# offline manpages
|
||||||
|
%patch -P900 -p1
|
||||||
|
|
||||||
|
# docker
|
||||||
|
%define docker_builddir %{_builddir}/%{name}-%{version}_%{git_version}
|
||||||
|
%setup -q -n %{name}-%{version}_%{git_version}
|
||||||
|
[ "%{docker_builddir}" = "$PWD" ]
|
||||||
|
# README_SUSE.md for documentation.
|
||||||
|
cp %{SOURCE130} .
|
||||||
|
|
||||||
|
%if 0%{?is_opensuse} == 0
|
||||||
|
# PATCH-SUSE: Secrets patches.
|
||||||
|
%patch -P100 -p1
|
||||||
|
%patch -P101 -p1
|
||||||
|
%endif
|
||||||
|
%if 0%{?sle_version} == 120000
|
||||||
|
# Patches to build on SLE-12.
|
||||||
|
%patch -P200 -p1
|
||||||
|
%endif
|
||||||
|
# bsc#1099277
|
||||||
|
%patch -P201 -p1
|
||||||
|
# Solves apparmor issues on SLE-12, but okay for newer SLE versions too.
|
||||||
|
%patch -P202 -p1
|
||||||
|
# bsc#1221916
|
||||||
|
%patch -P203 -p1
|
||||||
|
# bsc#1214855
|
||||||
|
%patch -P204 -p1
|
||||||
|
|
||||||
|
%build
|
||||||
|
%sysusers_generate_pre %{SOURCE160} %{name} %{name}.conf
|
||||||
|
|
||||||
|
BUILDTAGS="exclude_graphdriver_aufs apparmor selinux seccomp pkcs11"
|
||||||
|
%if 0%{?sle_version} == 120000
|
||||||
|
# Allow us to build with older distros but still have deferred removal
|
||||||
|
# support at runtime. We only use this when building on SLE12, because
|
||||||
|
# later openSUSE/SLE versions have a new enough libdevicemapper to not
|
||||||
|
# require the runtime checking.
|
||||||
|
BUILDTAGS="libdm_dlsym_deferred_remove $BUILDTAGS"
|
||||||
|
%endif
|
||||||
|
|
||||||
|
export AUTO_GOPATH=1
|
||||||
|
# Make sure we always build PIC code. bsc#1048046
|
||||||
|
export BUILDFLAGS="-buildmode=pie"
|
||||||
|
# Specify all of the versioning information. We use SOURCE_DATE_EPOCH if it's
|
||||||
|
# been injected by rpmbuild, otherwise we use the hardcoded git_commit_epoch
|
||||||
|
# generated above. boo#1064781
|
||||||
|
export VERSION="%{nice_version}"
|
||||||
|
export DOCKER_GITCOMMIT="%{git_version}"
|
||||||
|
export GITCOMMIT="%{git_version}"
|
||||||
|
export SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-%{git_commit_epoch}}"
|
||||||
|
export BUILDTIME="$(date -u -d "@$SOURCE_DATE_EPOCH" --rfc-3339 ns 2>/dev/null | sed -e 's/ /T/')"
|
||||||
|
|
||||||
|
###################
|
||||||
|
## DOCKER ENGINE ##
|
||||||
|
###################
|
||||||
|
|
||||||
|
pushd "%{docker_builddir}"
|
||||||
|
# use go module for build
|
||||||
|
ln -s {vendor,go}.mod
|
||||||
|
ln -s {vendor,go}.sum
|
||||||
|
./hack/make.sh dynbinary
|
||||||
|
popd
|
||||||
|
|
||||||
|
###################
|
||||||
|
## DOCKER CLIENT ##
|
||||||
|
###################
|
||||||
|
|
||||||
|
pushd "%{cli_builddir}"
|
||||||
|
# use go module for build
|
||||||
|
ln -s {vendor,go}.mod
|
||||||
|
ln -s {vendor,go}.sum
|
||||||
|
make DISABLE_WARN_OUTSIDE_CONTAINER=1 dynbinary manpages
|
||||||
|
popd
|
||||||
|
|
||||||
|
%install
|
||||||
|
install -Dd -m0755 \
|
||||||
|
%{buildroot}%{_sysconfdir}/init.d \
|
||||||
|
%{buildroot}%{_bindir} \
|
||||||
|
%{buildroot}%{_sbindir}
|
||||||
|
|
||||||
|
# docker daemon
|
||||||
|
install -D -m0755 %{docker_builddir}/bundles/dynbinary-daemon/dockerd %{buildroot}/%{_bindir}/dockerd
|
||||||
|
# docker proxy
|
||||||
|
install -D -m0755 %{docker_builddir}/bundles/dynbinary-daemon/docker-proxy %{buildroot}/%{_bindir}/docker-proxy
|
||||||
|
|
||||||
|
# cli-plugins/
|
||||||
|
install -d %{buildroot}/usr/lib/docker/cli-plugins
|
||||||
|
|
||||||
|
# /var/lib/docker
|
||||||
|
install -d %{buildroot}/%{_localstatedir}/lib/docker
|
||||||
|
# daemon.json config file
|
||||||
|
install -D -m0644 %{SOURCE150} %{buildroot}%{_sysconfdir}/docker/daemon.json
|
||||||
|
|
||||||
|
# docker cli
|
||||||
|
install -D -m0755 %{cli_builddir}/build/docker %{buildroot}/%{_bindir}/docker
|
||||||
|
install -D -m0644 %{cli_builddir}/contrib/completion/bash/docker "%{buildroot}%{_datarootdir}/bash-completion/completions/%{name}"
|
||||||
|
install -D -m0644 %{cli_builddir}/contrib/completion/zsh/_docker "%{buildroot}%{_sysconfdir}/zsh_completion.d/_%{name}"
|
||||||
|
install -D -m0644 %{cli_builddir}/contrib/completion/fish/docker.fish "%{buildroot}/%{_datadir}/fish/vendor_completions.d/%{name}.fish"
|
||||||
|
|
||||||
|
# systemd service
|
||||||
|
install -D -m0644 %{SOURCE100} %{buildroot}%{_unitdir}/%{name}.service
|
||||||
|
install -D -m0644 %{SOURCE101} %{buildroot}%{_unitdir}/%{name}.socket
|
||||||
|
ln -sf service %{buildroot}%{_sbindir}/rcdocker
|
||||||
|
|
||||||
|
# udev rules that prevents dolphin to show all docker devices and slows down
|
||||||
|
# upstream report https://bugs.kde.org/show_bug.cgi?id=329930
|
||||||
|
install -D -m0644 %{SOURCE110} %{buildroot}%{_udevrulesdir}/80-%{name}.rules
|
||||||
|
|
||||||
|
# audit rules
|
||||||
|
install -D -m0640 %{SOURCE140} %{buildroot}%{_sysconfdir}/audit/rules.d/%{name}.rules
|
||||||
|
|
||||||
|
# sysconfig file
|
||||||
|
install -D -m0644 %{SOURCE120} %{buildroot}%{_fillupdir}/sysconfig.docker
|
||||||
|
|
||||||
|
# install manpages (using the ones from the engine)
|
||||||
|
install -d %{buildroot}%{_mandir}/man1
|
||||||
|
install -p -m0644 %{cli_builddir}/man/man1/*.1 %{buildroot}%{_mandir}/man1
|
||||||
|
install -d %{buildroot}%{_mandir}/man5
|
||||||
|
install -p -m0644 %{cli_builddir}/man/man5/Dockerfile.5 %{buildroot}%{_mandir}/man5
|
||||||
|
install -d %{buildroot}%{_mandir}/man8
|
||||||
|
install -p -m0644 %{cli_builddir}/man/man8/*.8 %{buildroot}%{_mandir}/man8
|
||||||
|
|
||||||
|
# sysusers.d
|
||||||
|
install -D -m0644 %{SOURCE160} %{buildroot}%{_sysusersdir}/%{name}.conf
|
||||||
|
|
||||||
|
# rootless extras
|
||||||
|
install -D -p -m 0755 contrib/dockerd-rootless.sh %{buildroot}/%{_bindir}/dockerd-rootless.sh
|
||||||
|
install -D -p -m 0755 contrib/dockerd-rootless-setuptool.sh %{buildroot}/%{_bindir}/dockerd-rootless-setuptool.sh
|
||||||
|
|
||||||
|
%fdupes %{buildroot}
|
||||||
|
|
||||||
|
%pre -f %{name}.pre
|
||||||
|
# /etc/sub[ug]id should exist already (it's part of shadow-utils), but older
|
||||||
|
# distros don't have it. Docker just parses it and doesn't need any special
|
||||||
|
# shadow-utils helpers.
|
||||||
|
touch /etc/subuid /etc/subgid ||:
|
||||||
|
|
||||||
|
# "useradd -r" doesn't add sub[ug]ids so we manually add some. Hopefully there
|
||||||
|
# aren't any conflicts here, because usermod doesn't provide the same "get
|
||||||
|
# unusued range" feature that dockremap does.
|
||||||
|
grep -q '^dockremap:' /etc/subuid || \
|
||||||
|
usermod -v 100000000-200000000 dockremap &>/dev/null || \
|
||||||
|
echo "dockremap:100000000:100000001" >>/etc/subuid ||:
|
||||||
|
grep -q '^dockremap:' /etc/subgid || \
|
||||||
|
usermod -w 100000000-200000000 dockremap &>/dev/null || \
|
||||||
|
echo "dockremap:100000000:100000001" >>/etc/subgid ||:
|
||||||
|
|
||||||
|
%service_add_pre %{name}.service %{name}.socket
|
||||||
|
|
||||||
|
%post
|
||||||
|
%service_add_post %{name}.service %{name}.socket
|
||||||
|
%{fillup_only -n docker}
|
||||||
|
|
||||||
|
%preun
|
||||||
|
%service_del_preun %{name}.service %{name}.socket
|
||||||
|
|
||||||
|
%postun
|
||||||
|
%service_del_postun %{name}.service %{name}.socket
|
||||||
|
|
||||||
|
%files
|
||||||
|
%defattr(-,root,root)
|
||||||
|
%doc README.md README_SUSE.md
|
||||||
|
%license LICENSE
|
||||||
|
%{_bindir}/docker
|
||||||
|
%{_bindir}/dockerd
|
||||||
|
%{_bindir}/docker-proxy
|
||||||
|
%{_sbindir}/rcdocker
|
||||||
|
%dir %{_localstatedir}/lib/docker/
|
||||||
|
|
||||||
|
%dir /usr/lib/docker
|
||||||
|
%dir /usr/lib/docker/cli-plugins
|
||||||
|
|
||||||
|
%{_unitdir}/%{name}.service
|
||||||
|
%{_unitdir}/%{name}.socket
|
||||||
|
%{_sysusersdir}/%{name}.conf
|
||||||
|
|
||||||
|
%dir %{_sysconfdir}/docker
|
||||||
|
%config(noreplace) %{_sysconfdir}/docker/daemon.json
|
||||||
|
%{_fillupdir}/sysconfig.docker
|
||||||
|
|
||||||
|
%config %{_sysconfdir}/audit/rules.d/%{name}.rules
|
||||||
|
%{_udevrulesdir}/80-%{name}.rules
|
||||||
|
|
||||||
|
%{_mandir}/man1/docker-*.1%{ext_man}
|
||||||
|
%{_mandir}/man1/docker.1%{ext_man}
|
||||||
|
%{_mandir}/man5/Dockerfile.5%{ext_man}
|
||||||
|
%{_mandir}/man8/dockerd.8%{ext_man}
|
||||||
|
|
||||||
|
%files rootless-extras
|
||||||
|
%defattr(-,root,root)
|
||||||
|
%{_bindir}/dockerd-rootless.sh
|
||||||
|
%{_bindir}/dockerd-rootless-setuptool.sh
|
||||||
|
|
||||||
|
%files bash-completion
|
||||||
|
%defattr(-,root,root)
|
||||||
|
%{_datarootdir}/bash-completion/completions/%{name}
|
||||||
|
|
||||||
|
%files zsh-completion
|
||||||
|
%defattr(-,root,root)
|
||||||
|
%{_sysconfdir}/zsh_completion.d/_%{name}
|
||||||
|
|
||||||
|
%files fish-completion
|
||||||
|
%defattr(-,root,root)
|
||||||
|
%{_datadir}/fish/vendor_completions.d/%{name}.fish
|
||||||
|
|
||||||
|
%changelog
|
3
docker.sysusers
Normal file
3
docker.sysusers
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#Type Name ID GECOS Home directory Shell
|
||||||
|
g docker - - - -
|
||||||
|
u dockremap - 'docker --userns-remap=default' - -
|
8
sysconfig.docker
Normal file
8
sysconfig.docker
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
## Path : System/Management
|
||||||
|
## Description : Extra cli switches for docker daemon
|
||||||
|
## Type : string
|
||||||
|
## Default : ""
|
||||||
|
## ServiceRestart : docker
|
||||||
|
#
|
||||||
|
DOCKER_OPTS=""
|
Loading…
Reference in New Issue
Block a user