enforce blocking of registries

Vendor in the latest c/image to enforce blocking of registries when
creating a c/image/docker.dockerClient.  Add integration tests to
avoid regressions.

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg 2019-07-18 18:45:07 +02:00
parent 5f45112678
commit 87c256aebf
19 changed files with 486 additions and 9 deletions

2
go.mod
View File

@ -9,7 +9,7 @@ require (
github.com/VividCortex/ewma v1.1.1 // indirect
github.com/containerd/continuity v0.0.0-20180216233310-d8fb8589b0e8 // indirect
github.com/containers/buildah v1.8.4
github.com/containers/image v1.5.2-0.20190717062552-2178abd5f9b1
github.com/containers/image v1.5.2-0.20190725091050-48acc3dcbb76
github.com/containers/storage v1.12.10
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v0.0.0-20170817175659-5f6282db7d65 // indirect

4
go.sum
View File

@ -14,6 +14,8 @@ github.com/containers/image v1.5.2-0.20190620105408-93b1deece293 h1:EalCgZ875kDC
github.com/containers/image v1.5.2-0.20190620105408-93b1deece293/go.mod h1:8Vtij257IWSanUQKe1tAeNOm2sRVkSqQTVQ1IlwI3+M=
github.com/containers/image v1.5.2-0.20190717062552-2178abd5f9b1 h1:RGlzwWSoGBbc5fgGysRrGAPLn8xQwihzRVPVDW5yQlo=
github.com/containers/image v1.5.2-0.20190717062552-2178abd5f9b1/go.mod h1:8Vtij257IWSanUQKe1tAeNOm2sRVkSqQTVQ1IlwI3+M=
github.com/containers/image v1.5.2-0.20190725091050-48acc3dcbb76 h1:+9unAKrV92Jvifb06UK8H4xTKf7h7XQDOsn4EC9eqH4=
github.com/containers/image v1.5.2-0.20190725091050-48acc3dcbb76/go.mod h1:8Vtij257IWSanUQKe1tAeNOm2sRVkSqQTVQ1IlwI3+M=
github.com/containers/image v2.0.0+incompatible h1:FTr6Br7jlIKNCKMjSOMbAxKp2keQ0//jzJaYNTVhauk=
github.com/containers/image v2.0.0+incompatible/go.mod h1:8Vtij257IWSanUQKe1tAeNOm2sRVkSqQTVQ1IlwI3+M=
github.com/containers/storage v1.12.10 h1:vw1aiLsZ1LvO09ELMxVBTe35tThRiMftI2cPeH+G5ow=
@ -107,6 +109,8 @@ github.com/vbauerster/mpb v3.4.0+incompatible h1:mfiiYw87ARaeRW6x5gWwYRUawxaW1tL
github.com/vbauerster/mpb v3.4.0+incompatible/go.mod h1:zAHG26FUhVKETRu+MWqYXcI70POlC6N8up9p1dID7SU=
github.com/vrothberg/image v0.0.0-20190717060034-cd5ce8239f51 h1:u4Hw4D3PLODtsZJ1FKi7j8bkd+zyJOc28dRSiVTOgyE=
github.com/vrothberg/image v0.0.0-20190717060034-cd5ce8239f51/go.mod h1:/hIyjuUvIY6X2wGj/fbsA9zwlfAize8B2DLPishEHHg=
github.com/vrothberg/image v0.0.0-20190718162835-cdafe647d2d8 h1:LpqO8V+oaT3eXrvKSminmqKWo2vhOdLgu1kp2/+fHAI=
github.com/vrothberg/image v0.0.0-20190718162835-cdafe647d2d8/go.mod h1:/hIyjuUvIY6X2wGj/fbsA9zwlfAize8B2DLPishEHHg=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=

View File

@ -0,0 +1,34 @@
package main
import (
"github.com/go-check/check"
)
const blockedRegistriesConf = "./fixtures/blocked-registries.conf"
const blockedErrorRegex = `.*registry registry-blocked.com is blocked in .*`
func (s *SkopeoSuite) TestCopyBlockedSource(c *check.C) {
assertSkopeoFails(c, blockedErrorRegex,
"--registries-conf", blockedRegistriesConf, "copy",
"docker://registry-blocked.com/image:test",
"docker://registry-unblocked.com/image:test")
}
func (s *SkopeoSuite) TestCopyBlockedDestination(c *check.C) {
assertSkopeoFails(c, blockedErrorRegex,
"--registries-conf", blockedRegistriesConf, "copy",
"docker://registry-unblocked.com/image:test",
"docker://registry-blocked.com/image:test")
}
func (s *SkopeoSuite) TestInspectBlocked(c *check.C) {
assertSkopeoFails(c, blockedErrorRegex,
"--registries-conf", blockedRegistriesConf, "inspect",
"docker://registry-blocked.com/image:test")
}
func (s *SkopeoSuite) TestDeleteBlocked(c *check.C) {
assertSkopeoFails(c, blockedErrorRegex,
"--registries-conf", blockedRegistriesConf, "delete",
"docker://registry-blocked.com/image:test")
}

View File

@ -0,0 +1,6 @@
[[registry]]
location = "registry-unblocked.com"
[[registry]]
location = "registry-blocked.com"
blocked = true

View File

@ -254,6 +254,9 @@ func newDockerClient(sys *types.SystemContext, registry, reference string) (*doc
return nil, errors.Wrapf(err, "error loading registries")
}
if reg != nil {
if reg.Blocked {
return nil, fmt.Errorf("registry %s is blocked in %s", reg.Prefix, sysregistriesv2.ConfigPath(sys))
}
skipVerify = reg.Insecure
}
tlsClientConfig.InsecureSkipVerify = skipVerify

View File

@ -35,6 +35,8 @@ var (
// ErrNotLoggedIn is returned for users not logged into a registry
// that they are trying to logout of
ErrNotLoggedIn = errors.New("not logged in")
// ErrNotSupported is returned for unsupported methods
ErrNotSupported = errors.New("not supported")
)
// SetAuthentication stores the username and password in the auth.json file
@ -44,6 +46,13 @@ func SetAuthentication(sys *types.SystemContext, registry, username, password st
return false, setAuthToCredHelper(ch, registry, username, password)
}
err := setAuthToKernelKeyring(registry, username, password)
if err == nil {
logrus.Debugf("credentials for (%s, %s) were stored in the kernel keyring\n", registry, username)
return false, nil
}
logrus.Debugf("failed to authenticate with the kernel keyring, falling back to authfiles.")
creds := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
newCreds := dockerAuthConfig{Auth: creds}
auths.AuthConfigs[registry] = newCreds
@ -60,6 +69,12 @@ func GetAuthentication(sys *types.SystemContext, registry string) (string, strin
return sys.DockerAuthConfig.Username, sys.DockerAuthConfig.Password, nil
}
username, password, err := getAuthFromKernelKeyring(registry)
if err == nil {
logrus.Debug("returning credentials from kernel keyring")
return username, password, nil
}
dockerLegacyPath := filepath.Join(homedir.Get(), dockerLegacyHomePath)
var paths []string
pathToAuth, err := getPathToAuth(sys)
@ -97,6 +112,14 @@ func RemoveAuthentication(sys *types.SystemContext, registry string) error {
return false, deleteAuthFromCredHelper(ch, registry)
}
// Next try kernel keyring
err := deleteAuthFromKernelKeyring(registry)
if err == nil {
logrus.Debugf("credentials for %s were deleted from the kernel keyring", registry)
return false, nil
}
logrus.Debugf("failed to delete credentials from the kernel keyring, falling back to authfiles")
if _, ok := auths.AuthConfigs[registry]; ok {
delete(auths.AuthConfigs, registry)
} else if _, ok := auths.AuthConfigs[normalizeRegistry(registry)]; ok {

View File

@ -0,0 +1,79 @@
package config
import (
"fmt"
"strings"
"github.com/containers/image/pkg/keyctl"
"github.com/pkg/errors"
)
func getAuthFromKernelKeyring(registry string) (string, string, error) {
userkeyring, err := keyctl.UserKeyring()
if err != nil {
return "", "", err
}
key, err := userkeyring.Search(genDescription(registry))
if err != nil {
return "", "", err
}
authData, err := key.Get()
if err != nil {
return "", "", err
}
parts := strings.SplitN(string(authData), "\x00", 2)
if len(parts) != 2 {
return "", "", nil
}
return parts[0], parts[1], nil
}
func deleteAuthFromKernelKeyring(registry string) error {
userkeyring, err := keyctl.UserKeyring()
if err != nil {
return err
}
key, err := userkeyring.Search(genDescription(registry))
if err != nil {
return err
}
return key.Unlink()
}
func setAuthToKernelKeyring(registry, username, password string) error {
keyring, err := keyctl.SessionKeyring()
if err != nil {
return err
}
id, err := keyring.Add(genDescription(registry), []byte(fmt.Sprintf("%s\x00%s", username, password)))
if err != nil {
return err
}
// sets all permission(view,read,write,search,link,set attribute) for current user
// it enables the user to search the key after it linked to user keyring and unlinked from session keyring
err = keyctl.SetPerm(id, keyctl.PermUserAll)
if err != nil {
return err
}
// link the key to userKeyring
userKeyring, err := keyctl.UserKeyring()
if err != nil {
return errors.Wrapf(err, "error getting user keyring")
}
err = keyctl.Link(userKeyring, id)
if err != nil {
return errors.Wrapf(err, "error linking the key to user keyring")
}
// unlink the key from session keyring
err = keyctl.Unlink(keyring, id)
if err != nil {
return errors.Wrapf(err, "error unlinking the key from session keyring")
}
return nil
}
func genDescription(registry string) string {
return fmt.Sprintf("container-registry-login:%s", registry)
}

View File

@ -0,0 +1,16 @@
// +build !linux
// +build !386 !amd64
package config
func getAuthFromKernelKeyring(registry string) (string, string, error) {
return "", "", ErrNotSupported
}
func deleteAuthFromKernelKeyring(registry string) error {
return ErrNotSupported
}
func setAuthToKernelKeyring(registry, username, password string) error {
return ErrNotSupported
}

65
vendor/github.com/containers/image/pkg/keyctl/key.go generated vendored Normal file
View File

@ -0,0 +1,65 @@
// Copyright 2015 Jesse Sipprell. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
// +build 386 amd64
package keyctl
import (
"unsafe"
)
// Key represents a single key linked to one or more kernel keyrings.
type Key struct {
Name string
id, ring keyID
size int
}
// ID returns the 32-bit kernel identifier for a specific key
func (k *Key) ID() int32 {
return int32(k.id)
}
// Get the key's value as a byte slice
func (k *Key) Get() ([]byte, error) {
var (
b []byte
err error
sizeRead int
)
if k.size == 0 {
k.size = 512
}
size := k.size
b = make([]byte, int(size))
sizeRead = size + 1
for sizeRead > size {
r1, _, err := keyctl(keyctlRead, uintptr(k.id), uintptr(unsafe.Pointer(&b[0])), uintptr(size))
if err != nil {
return nil, err
}
if sizeRead = int(r1); sizeRead > size {
b = make([]byte, sizeRead)
size = sizeRead
sizeRead = size + 1
} else {
k.size = sizeRead
}
}
return b[:k.size], err
}
// Unlink a key from the keyring it was loaded from (or added to). If the key
// is not linked to any other keyrings, it is destroyed.
func (k *Key) Unlink() error {
_, _, err := keyctl(keyctlUnlink, uintptr(k.id), uintptr(k.ring))
return err
}

View File

@ -0,0 +1,73 @@
// Copyright 2015 Jesse Sipprell. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
// +build 386 amd64
// Package keyctl is a Go interface to linux kernel keyrings (keyctl interface)
package keyctl
// Keyring is the basic interface to a linux keyctl keyring.
type Keyring interface {
ID
Add(string, []byte) (*Key, error)
Search(string) (*Key, error)
}
type keyring struct {
id keyID
}
// ID is unique 32-bit serial number identifiers for all Keys and Keyrings have.
type ID interface {
ID() int32
}
// Add a new key to a keyring. The key can be searched for later by name.
func (kr *keyring) Add(name string, key []byte) (*Key, error) {
r, err := addkey("user", name, key, int32(kr.id))
if err == nil {
key := &Key{Name: name, id: keyID(r), ring: kr.id}
return key, nil
}
return nil, err
}
// Search for a key by name, this also searches child keyrings linked to this
// one. The key, if found, is linked to the top keyring that Search() was called
// from.
func (kr *keyring) Search(name string) (*Key, error) {
id, err := searchKeyring(kr.id, name, "user")
if err == nil {
return &Key{Name: name, id: id, ring: kr.id}, nil
}
return nil, err
}
// ID returns the 32-bit kernel identifier of a keyring
func (kr *keyring) ID() int32 {
return int32(kr.id)
}
// SessionKeyring returns the current login session keyring
func SessionKeyring() (Keyring, error) {
return newKeyring(keySpecSessionKeyring)
}
// UserKeyring returns the keyring specific to the current user.
func UserKeyring() (Keyring, error) {
return newKeyring(keySpecUserKeyring)
}
// Unlink an object from a keyring
func Unlink(parent Keyring, child ID) error {
_, _, err := keyctl(keyctlUnlink, uintptr(child.ID()), uintptr(parent.ID()))
return err
}
// Link a key into a keyring
func Link(parent Keyring, child ID) error {
_, _, err := keyctl(keyctlLink, uintptr(child.ID()), uintptr(parent.ID()))
return err
}

29
vendor/github.com/containers/image/pkg/keyctl/perm.go generated vendored Normal file
View File

@ -0,0 +1,29 @@
// Copyright 2015 Jesse Sipprell. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package keyctl
// KeyPerm represents in-kernel access control permission to keys and keyrings
// as a 32-bit integer broken up into four permission sets, one per byte.
// In MSB order, the perms are: Processor, User, Group, Other.
type KeyPerm uint32
const (
// PermOtherAll sets all permission for Other
PermOtherAll KeyPerm = 0x3f << (8 * iota)
// PermGroupAll sets all permission for Group
PermGroupAll
// PermUserAll sets all permission for User
PermUserAll
// PermProcessAll sets all permission for Processor
PermProcessAll
)
// SetPerm sets the permissions on a key or keyring.
func SetPerm(k ID, p KeyPerm) error {
_, _, err := keyctl(keyctlSetPerm, uintptr(k.ID()), uintptr(p))
return err
}

View File

@ -0,0 +1,121 @@
// Copyright 2015 Jesse Sipprell. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
// +build 386 amd64
package keyctl
import (
"syscall"
"unsafe"
)
type keyctlCommand int
type keyID int32
const (
keySpecSessionKeyring keyID = -3
keySpecUserKeyring keyID = -4
)
const (
keyctlGetKeyringID keyctlCommand = 0
keyctlSetPerm keyctlCommand = 5
keyctlLink keyctlCommand = 8
keyctlUnlink keyctlCommand = 9
keyctlSearch keyctlCommand = 10
keyctlRead keyctlCommand = 11
)
func (id keyID) ID() int32 {
return int32(id)
}
func keyctl(cmd keyctlCommand, args ...uintptr) (r1 int32, r2 int32, err error) {
a := make([]uintptr, 6)
l := len(args)
if l > 5 {
l = 5
}
a[0] = uintptr(cmd)
for idx, v := range args[:l] {
a[idx+1] = v
}
v1, v2, errno := syscall.Syscall6(syscallKeyctl, a[0], a[1], a[2], a[3], a[4], a[5])
if errno != 0 {
err = errno
return
}
r1 = int32(v1)
r2 = int32(v2)
return
}
func addkey(keyType, keyDesc string, payload []byte, id int32) (int32, error) {
var (
err error
errno syscall.Errno
b1, b2 *byte
r1 uintptr
pptr unsafe.Pointer
)
if b1, err = syscall.BytePtrFromString(keyType); err != nil {
return 0, err
}
if b2, err = syscall.BytePtrFromString(keyDesc); err != nil {
return 0, err
}
if len(payload) > 0 {
pptr = unsafe.Pointer(&payload[0])
}
r1, _, errno = syscall.Syscall6(syscallAddKey,
uintptr(unsafe.Pointer(b1)),
uintptr(unsafe.Pointer(b2)),
uintptr(pptr),
uintptr(len(payload)),
uintptr(id),
0)
if errno != 0 {
err = errno
return 0, err
}
return int32(r1), nil
}
func newKeyring(id keyID) (*keyring, error) {
r1, _, err := keyctl(keyctlGetKeyringID, uintptr(id), uintptr(1))
if err != nil {
return nil, err
}
if id < 0 {
r1 = int32(id)
}
return &keyring{id: keyID(r1)}, nil
}
func searchKeyring(id keyID, name, keyType string) (keyID, error) {
var (
r1 int32
b1, b2 *byte
err error
)
if b1, err = syscall.BytePtrFromString(keyType); err != nil {
return 0, err
}
if b2, err = syscall.BytePtrFromString(name); err != nil {
return 0, err
}
r1, _, err = keyctl(keyctlSearch, uintptr(id), uintptr(unsafe.Pointer(b1)), uintptr(unsafe.Pointer(b2)))
return keyID(r1), err
}

View File

@ -0,0 +1,12 @@
// Copyright 2015 Jesse Sipprell. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package keyctl
const (
syscallKeyctl uintptr = 288
syscallAddKey uintptr = 286
)

View File

@ -0,0 +1,12 @@
// Copyright 2015 Jesse Sipprell. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package keyctl
const (
syscallKeyctl uintptr = 250
syscallAddKey uintptr = 248
)

View File

@ -303,9 +303,8 @@ func (config *V2RegistriesConf) postProcess() error {
return nil
}
// getConfigPath returns the system-registries config path if specified.
// Otherwise, systemRegistriesConfPath is returned.
func getConfigPath(ctx *types.SystemContext) string {
// ConfigPath returns the path to the system-wide registry configuration file.
func ConfigPath(ctx *types.SystemContext) string {
confPath := systemRegistriesConfPath
if ctx != nil {
if ctx.SystemRegistriesConfPath != "" {
@ -336,7 +335,7 @@ func InvalidateCache() {
// getConfig returns the config object corresponding to ctx, loading it if it is not yet cached.
func getConfig(ctx *types.SystemContext) (*V2RegistriesConf, error) {
configPath := getConfigPath(ctx)
configPath := ConfigPath(ctx)
configMutex.Lock()
defer configMutex.Unlock()

View File

@ -6,7 +6,7 @@
package signature
// NOTE: Keep this in sync with docs/policy.json.md!
// NOTE: Keep this in sync with docs/containers-policy.json.5.md!
// Policy defines requirements for considering a signature, or an image, valid.
type Policy struct {

View File

@ -4,7 +4,7 @@ import (
"strings"
// register all known transports
// NOTE: Make sure docs/policy.json.md is updated when adding or updating
// NOTE: Make sure docs/containers-policy.json.5.md is updated when adding or updating
// a transport.
_ "github.com/containers/image/directory"
_ "github.com/containers/image/docker"

View File

@ -8,7 +8,7 @@ const (
// VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 0
// VersionPatch is for backwards-compatible bug fixes
VersionPatch = 1
VersionPatch = 2
// VersionDev indicates development branch. Releases will be empty string.
VersionDev = "-dev"

3
vendor/modules.txt vendored
View File

@ -26,7 +26,7 @@ github.com/VividCortex/ewma
github.com/containerd/continuity/pathdriver
# github.com/containers/buildah v1.8.4
github.com/containers/buildah/pkg/unshare
# github.com/containers/image v1.5.2-0.20190717062552-2178abd5f9b1
# github.com/containers/image v1.5.2-0.20190725091050-48acc3dcbb76
github.com/containers/image/copy
github.com/containers/image/directory
github.com/containers/image/docker
@ -58,6 +58,7 @@ github.com/containers/image/oci/layout
github.com/containers/image/openshift
github.com/containers/image/ostree
github.com/containers/image/tarball
github.com/containers/image/pkg/keyctl
github.com/containers/image/pkg/blobinfocache/internal/prioritize
github.com/containers/image/docker/tarfile
github.com/containers/image/oci/internal