Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set default rootless sigstore #1035

Merged
merged 1 commit into from
Sep 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docker/docker_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,15 @@ func dockerCertDir(sys *types.SystemContext, hostPort string) (string, error) {

// newDockerClientFromRef returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry)
// “write” specifies whether the client will be used for "write" access (in particular passed to lookaside.go:toplevelFromSection)
// signatureBase is always set in the return value
func newDockerClientFromRef(sys *types.SystemContext, ref dockerReference, write bool, actions string) (*dockerClient, error) {
registry := reference.Domain(ref.ref)
auth, err := config.GetCredentials(sys, registry)
if err != nil {
return nil, errors.Wrapf(err, "error getting username and password")
}

sigBase, err := configuredSignatureStorageBase(sys, ref, write)
sigBase, err := SignatureStorageBaseURL(sys, ref, write)
if err != nil {
return nil, err
}
Expand Down
18 changes: 6 additions & 12 deletions docker/docker_image_dest.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ func (d *dockerImageDestination) SupportsSignatures(ctx context.Context) error {
return err
}
switch {
case d.c.signatureBase != nil:
return nil
case d.c.supportsSignatures:
return nil
case d.c.signatureBase != nil:
return nil
default:
mtrmac marked this conversation as resolved.
Show resolved Hide resolved
return errors.Errorf("X-Registry-Supports-Signatures extension not supported, and lookaside is not configured")
return errors.Errorf("Internal error: X-Registry-Supports-Signatures extension not supported, and lookaside should not be empty configuration")
}
}

Expand Down Expand Up @@ -479,12 +479,12 @@ func (d *dockerImageDestination) PutSignatures(ctx context.Context, signatures [
return err
}
switch {
case d.c.signatureBase != nil:
return d.putSignaturesToLookaside(signatures, *instanceDigest)
case d.c.supportsSignatures:
return d.putSignaturesToAPIExtension(ctx, signatures, *instanceDigest)
case d.c.signatureBase != nil:
return d.putSignaturesToLookaside(signatures, *instanceDigest)
default:
mtrmac marked this conversation as resolved.
Show resolved Hide resolved
return errors.Errorf("X-Registry-Supports-Signatures extension not supported, and lookaside is not configured")
return errors.Errorf("Internal error: X-Registry-Supports-Signatures extension not supported, and lookaside should not be empty configuration")
}
}

Expand All @@ -502,9 +502,6 @@ func (d *dockerImageDestination) putSignaturesToLookaside(signatures [][]byte, m
// NOTE: Keep this in sync with docs/signature-protocols.md!
for i, signature := range signatures {
url := signatureStorageURL(d.c.signatureBase, manifestDigest, i)
if url == nil {
return errors.Errorf("Internal error: signatureStorageURL with non-nil base returned nil")
}
err := d.putOneSignature(url, signature)
if err != nil {
return err
Expand All @@ -517,9 +514,6 @@ func (d *dockerImageDestination) putSignaturesToLookaside(signatures [][]byte, m
// is sufficient.
for i := len(signatures); ; i++ {
url := signatureStorageURL(d.c.signatureBase, manifestDigest, i)
if url == nil {
return errors.Errorf("Internal error: signatureStorageURL with non-nil base returned nil")
}
missing, err := d.c.deleteOneSignature(url)
if err != nil {
return err
Expand Down
34 changes: 13 additions & 21 deletions docker/docker_image_src.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,12 +297,12 @@ func (s *dockerImageSource) GetSignatures(ctx context.Context, instanceDigest *d
return nil, err
}
switch {
case s.c.signatureBase != nil:
return s.getSignaturesFromLookaside(ctx, instanceDigest)
case s.c.supportsSignatures:
return s.getSignaturesFromAPIExtension(ctx, instanceDigest)
case s.c.signatureBase != nil:
return s.getSignaturesFromLookaside(ctx, instanceDigest)
default:
mtrmac marked this conversation as resolved.
Show resolved Hide resolved
return [][]byte{}, nil
return nil, errors.Errorf("Internal error: X-Registry-Supports-Signatures extension not supported, and lookaside should not be empty configuration")
}
}

Expand Down Expand Up @@ -336,9 +336,6 @@ func (s *dockerImageSource) getSignaturesFromLookaside(ctx context.Context, inst
signatures := [][]byte{}
for i := 0; ; i++ {
url := signatureStorageURL(s.c.signatureBase, manifestDigest, i)
if url == nil {
return nil, errors.Errorf("Internal error: signatureStorageURL with non-nil base returned nil")
}
signature, missing, err := s.getOneSignature(ctx, url)
if err != nil {
return nil, err
Expand Down Expand Up @@ -474,24 +471,19 @@ func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerRefere
return errors.Errorf("Failed to delete %v: %s (%v)", deletePath, string(body), delete.Status)
}

if c.signatureBase != nil {
manifestDigest, err := manifest.Digest(manifestBody)
manifestDigest, err := manifest.Digest(manifestBody)
mtrmac marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}

for i := 0; ; i++ {
url := signatureStorageURL(c.signatureBase, manifestDigest, i)
missing, err := c.deleteOneSignature(url)
if err != nil {
return err
}

for i := 0; ; i++ {
url := signatureStorageURL(c.signatureBase, manifestDigest, i)
if url == nil {
return errors.Errorf("Internal error: signatureStorageURL with non-nil base returned nil")
}
missing, err := c.deleteOneSignature(url)
if err != nil {
return err
}
if missing {
break
}
if missing {
break
}
}

Expand Down
67 changes: 44 additions & 23 deletions docker/lookaside.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/internal/rootless"
"github.com/containers/image/v5/types"
"github.com/containers/storage/pkg/homedir"
"github.com/ghodss/yaml"
Expand All @@ -30,6 +31,12 @@ const builtinRegistriesDirPath = "/etc/containers/registries.d"
// userRegistriesDirPath is the path to the per user registries.d.
var userRegistriesDir = filepath.FromSlash(".config/containers/registries.d")

// defaultUserDockerDir is the default sigstore directory for unprivileged user
var defaultUserDockerDir = filepath.FromSlash(".local/share/containers/sigstore")

// defaultDockerDir is the default sigstore directory for root
var defaultDockerDir = "/var/lib/containers/sigstore"

// registryConfiguration is one of the files in registriesDirPath configuring lookaside locations, or the result of merging them all.
// NOTE: Keep this in sync with docs/registries.d.md!
type registryConfiguration struct {
Expand All @@ -45,11 +52,18 @@ type registryNamespace struct {
}

// signatureStorageBase is an "opaque" type representing a lookaside Docker signature storage.
// Users outside of this file should use configuredSignatureStorageBase and signatureStorageURL below.
type signatureStorageBase *url.URL // The only documented value is nil, meaning storage is not supported.

// configuredSignatureStorageBase reads configuration to find an appropriate signature storage URL for ref, for write access if “write”.
func configuredSignatureStorageBase(sys *types.SystemContext, ref dockerReference, write bool) (signatureStorageBase, error) {
// Users outside of this file should use SignatureStorageBaseURL and signatureStorageURL below.
type signatureStorageBase *url.URL

// SignatureStorageBaseURL reads configuration to find an appropriate signature storage URL for ref, for write access if “write”.
// the usage of the BaseURL is defined under docker/distribution registries—separate storage of docs/signature-protocols.md
// Warning: This function only exposes configuration in registries.d;
// just because this function returns an URL does not mean that the URL will be used by c/image/docker (e.g. if the registry natively supports X-R-S-S).
func SignatureStorageBaseURL(sys *types.SystemContext, ref types.ImageReference, write bool) (*url.URL, error) {
dr, ok := ref.(dockerReference)
if !ok {
return nil, errors.Errorf("ref must be a dockerReference")
}
// FIXME? Loading and parsing the config could be cached across calls.
dirPath := registriesDirPath(sys)
logrus.Debugf(`Using registries.d directory %s for sigstore configuration`, dirPath)
Expand All @@ -58,20 +72,23 @@ func configuredSignatureStorageBase(sys *types.SystemContext, ref dockerReferenc
return nil, err
}

topLevel := config.signatureTopLevel(ref, write)
if topLevel == "" {
return nil, nil
}

url, err := url.Parse(topLevel)
if err != nil {
return nil, errors.Wrapf(err, "Invalid signature storage URL %s", topLevel)
topLevel := config.signatureTopLevel(dr, write)
var url *url.URL
if topLevel != "" {
url, err = url.Parse(topLevel)
if err != nil {
return nil, errors.Wrapf(err, "Invalid signature storage URL %s", topLevel)
}
} else {
// returns default directory if no sigstore specified in configuration file
url = builtinDefaultSignatureStorageDir(rootless.GetRootlessEUID())
logrus.Debugf(" No signature storage configuration found for %s, using built-in default %s", dr.PolicyConfigurationIdentity(), url.String())
}
// NOTE: Keep this in sync with docs/signature-protocols.md!
// FIXME? Restrict to explicitly supported schemes?
repo := reference.Path(ref.ref) // Note that this is without a tag or digest.
if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references
return nil, errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", ref.ref.String())
repo := reference.Path(dr.ref) // Note that this is without a tag or digest.
if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references
return nil, errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", dr.ref.String())
}
mtrmac marked this conversation as resolved.
Show resolved Hide resolved
url.Path = url.Path + "/" + repo
return url, nil
Expand All @@ -93,6 +110,14 @@ func registriesDirPath(sys *types.SystemContext) string {
return systemRegistriesDirPath
}

// builtinDefaultSignatureStorageDir returns default signature storage URL as per euid
func builtinDefaultSignatureStorageDir(euid int) *url.URL {
if euid != 0 {
return &url.URL{Scheme: "file", Path: filepath.Join(homedir.Get(), defaultUserDockerDir)}
}
return &url.URL{Scheme: "file", Path: defaultDockerDir}
}

// loadAndMergeConfig loads configuration files in dirPath
func loadAndMergeConfig(dirPath string) (*registryConfiguration, error) {
mergedConfig := registryConfiguration{Docker: map[string]registryNamespace{}}
Expand Down Expand Up @@ -149,7 +174,7 @@ func loadAndMergeConfig(dirPath string) (*registryConfiguration, error) {
}

// config.signatureTopLevel returns an URL string configured in config for ref, for write access if “write”.
// (the top level of the storage, namespaced by repo.FullName etc.), or "" if no signature storage should be used.
// (the top level of the storage, namespaced by repo.FullName etc.), or "" if nothing has been configured.
func (config *registryConfiguration) signatureTopLevel(ref dockerReference, write bool) string {
if config.Docker != nil {
// Look for a full match.
Expand Down Expand Up @@ -178,7 +203,6 @@ func (config *registryConfiguration) signatureTopLevel(ref dockerReference, writ
return url
}
}
logrus.Debugf(" No signature storage configuration found for %s", ref.PolicyConfigurationIdentity())
return ""
}

Expand All @@ -196,13 +220,10 @@ func (ns registryNamespace) signatureTopLevel(write bool) string {
return ""
}

// signatureStorageURL returns an URL usable for accessing signature index in base with known manifestDigest, or nil if not applicable.
// Returns nil iff base == nil.
// signatureStorageURL returns an URL usable for accessing signature index in base with known manifestDigest.
// base is not nil from the caller
// NOTE: Keep this in sync with docs/signature-protocols.md!
func signatureStorageURL(base signatureStorageBase, manifestDigest digest.Digest, index int) *url.URL {
mtrmac marked this conversation as resolved.
Show resolved Hide resolved
if base == nil {
return nil
}
url := *base
url.Path = fmt.Sprintf("%s@%s=%s/signature-%d", url.Path, manifestDigest.Algorithm(), manifestDigest.Hex(), index+1)
return &url
Expand Down
27 changes: 19 additions & 8 deletions docker/lookaside_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,34 @@ func dockerRefFromString(t *testing.T, s string) dockerReference {
return dockerRef
}

func TestConfiguredSignatureStorageBase(t *testing.T) {
func TestSignatureStorageBaseURL(t *testing.T) {
// Error reading configuration directory (/dev/null is not a directory)
_, err := configuredSignatureStorageBase(&types.SystemContext{RegistriesDirPath: "/dev/null"},
_, err := SignatureStorageBaseURL(&types.SystemContext{RegistriesDirPath: "/dev/null"},
dockerRefFromString(t, "//busybox"), false)
assert.Error(t, err)

// No match found
// expect default user storage base
emptyDir, err := ioutil.TempDir("", "empty-dir")
require.NoError(t, err)
defer os.RemoveAll(emptyDir)
base, err := configuredSignatureStorageBase(&types.SystemContext{RegistriesDirPath: emptyDir},
base, err := SignatureStorageBaseURL(&types.SystemContext{RegistriesDirPath: emptyDir},
dockerRefFromString(t, "//this/is/not/in/the:configuration"), false)
assert.NoError(t, err)
assert.Nil(t, base)
assert.NotNil(t, base)
assert.Equal(t, "file://"+filepath.Join(os.Getenv("HOME"), defaultUserDockerDir, "//this/is/not/in/the"), base.String())

// Invalid URL
_, err = configuredSignatureStorageBase(&types.SystemContext{RegistriesDirPath: "fixtures/registries.d"},
_, err = SignatureStorageBaseURL(&types.SystemContext{RegistriesDirPath: "fixtures/registries.d"},
dockerRefFromString(t, "//localhost/invalid/url/test"), false)
assert.Error(t, err)

// Success
base, err = configuredSignatureStorageBase(&types.SystemContext{RegistriesDirPath: "fixtures/registries.d"},
base, err = SignatureStorageBaseURL(&types.SystemContext{RegistriesDirPath: "fixtures/registries.d"},
dockerRefFromString(t, "//example.com/my/project"), false)
assert.NoError(t, err)
require.NotNil(t, base)
assert.Equal(t, "https://sigstore.example.com/my/project", (*url.URL)(base).String())
assert.Equal(t, "https://sigstore.example.com/my/project", base.String())
}

func TestRegistriesDirPath(t *testing.T) {
Expand Down Expand Up @@ -292,7 +294,6 @@ func TestSignatureStorageBaseSignatureStorageURL(t *testing.T) {
const mdInput = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
const mdMapped = "sha256=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"

assert.True(t, signatureStorageURL(nil, mdInput, 0) == nil)
for _, c := range []struct {
base string
index int
Expand All @@ -313,3 +314,13 @@ func TestSignatureStorageBaseSignatureStorageURL(t *testing.T) {
assert.Equal(t, expectedURL, res, c.expected)
}
}

func TestBuiltinDefaultSignatureStorageDir(t *testing.T) {
base := builtinDefaultSignatureStorageDir(0)
assert.NotNil(t, base)
assert.Equal(t, "file://"+defaultDockerDir, base.String())

base = builtinDefaultSignatureStorageDir(1000)
assert.NotNil(t, base)
assert.Equal(t, "file://"+filepath.Join(os.Getenv("HOME"), defaultUserDockerDir), base.String())
}
6 changes: 6 additions & 0 deletions docs/containers-registries.d.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ more general scopes is ignored. For example, if _any_ configuration exists for
`docker.io/library/busybox`, the configuration for `docker.io` is ignored
(even if some element of the configuration is defined for `docker.io` and not for `docker.io/library/busybox`).

### Built-in Defaults

If no `docker` section can be found for the container image, and no `default-docker` section is configured,
the default directory, `/var/lib/containers/sigstore` for root and `$HOME/.local/share/containers/sigstore` for unprivileged user, will be used for reading and writing signatures.

## Individual Configuration Sections

A single configuration section is selected for a container image using the process
Expand All @@ -77,6 +82,7 @@ described above. The configuration section is a YAML mapping, with the followin
This key is optional; if it is missing, no signature storage is defined (no signatures
are download along with images, adding new signatures is possible only if `sigstore-staging` is defined).


## Examples
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding an extra “example” for no configuration.


### Using Containers from Various Origins
Expand Down
25 changes: 25 additions & 0 deletions internal/rootless/rootless.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package rootless

import (
"os"
"strconv"
)

// GetRootlessEUID returns the UID of the current user (in the parent userNS, if any)
//
// Podman and similar software, in “rootless” configuration, when run as a non-root
// user, very early switches to a user namespace, where Geteuid() == 0 (but does not
// switch to a limited mount namespace); so, code relying on Geteuid() would use
// system-wide paths in e.g. /var, when the user is actually not privileged to write to
// them, and expects state to be stored in the home directory.
//
// If Podman is setting up such a user namespace, it records the original UID in an
// environment variable, allowing us to make choices based on the actual user’s identity.
func GetRootlessEUID() int {
euidEnv := os.Getenv("_CONTAINERS_ROOTLESS_UID")
if euidEnv != "" {
euid, _ := strconv.Atoi(euidEnv)
return euid
}
return os.Geteuid()
}
13 changes: 2 additions & 11 deletions pkg/blobinfocache/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"

"github.com/containers/image/v5/internal/rootless"
"github.com/containers/image/v5/pkg/blobinfocache/boltdb"
"github.com/containers/image/v5/pkg/blobinfocache/memory"
"github.com/containers/image/v5/types"
Expand Down Expand Up @@ -48,18 +48,9 @@ func blobInfoCacheDir(sys *types.SystemContext, euid int) (string, error) {
return filepath.Join(dataDir, "containers", "cache"), nil
}

func getRootlessUID() int {
uidEnv := os.Getenv("_CONTAINERS_ROOTLESS_UID")
if uidEnv != "" {
u, _ := strconv.Atoi(uidEnv)
return u
}
return os.Geteuid()
}

// DefaultCache returns the default BlobInfoCache implementation appropriate for sys.
func DefaultCache(sys *types.SystemContext) types.BlobInfoCache {
dir, err := blobInfoCacheDir(sys, getRootlessUID())
dir, err := blobInfoCacheDir(sys, rootless.GetRootlessEUID())
if err != nil {
logrus.Debugf("Error determining a location for %s, using a memory-only cache", blobInfoCacheFilename)
return memory.New()
Expand Down