Skip to content

Commit

Permalink
Merge pull request #1035 from QiWang19/sigstore
Browse files Browse the repository at this point in the history
Set default rootless sigstore
  • Loading branch information
mtrmac authored Sep 18, 2020
2 parents a1e2b35 + 701023d commit 1a0dda7
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 76 deletions.
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:
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:
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:
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)
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())
}
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 {
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

### 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

0 comments on commit 1a0dda7

Please sign in to comment.