-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds validation to imagestreams and imagestreamtags that forbids new image pull specs not present on the whitelist. Resolves bug 1505315 Signed-off-by: Michal Minář <[email protected]>
- Loading branch information
Michal Minář
committed
Dec 14, 2017
1 parent
d3cdfec
commit 842eba4
Showing
16 changed files
with
957 additions
and
128 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package fake | ||
|
||
import ( | ||
imgadmission "github.com/openshift/origin/pkg/image/admission" | ||
imageapi "github.com/openshift/origin/pkg/image/apis/image" | ||
) | ||
|
||
type FakeImageStreamLimitVerifier struct { | ||
ImageStreamEvaluator func(ns string, is *imageapi.ImageStream) error | ||
Err error | ||
} | ||
|
||
func (f *FakeImageStreamLimitVerifier) VerifyLimits(ns string, is *imageapi.ImageStream) error { | ||
if f.ImageStreamEvaluator != nil { | ||
return f.ImageStreamEvaluator(ns, is) | ||
} | ||
return f.Err | ||
} | ||
|
||
type FakeRegistryWhitelister struct{} | ||
|
||
func (rw *FakeRegistryWhitelister) AdmitHostname(host string, insecure bool) error { return nil } | ||
func (rw *FakeRegistryWhitelister) AdmitDockerImageReference(ref *imageapi.DockerImageReference, insecure bool) error { | ||
return nil | ||
} | ||
func (rw *FakeRegistryWhitelister) WhitelistRegistry(hostPortGlob string, insecure bool) error { | ||
return nil | ||
} | ||
func (rw *FakeRegistryWhitelister) Copy() imgadmission.RegistryWhitelister { | ||
return &FakeRegistryWhitelister{} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
package admission | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
"reflect" | ||
"strings" | ||
|
||
"github.com/golang/glog" | ||
|
||
kerrutil "k8s.io/apimachinery/pkg/util/errors" | ||
"k8s.io/apimachinery/pkg/util/sets" | ||
|
||
serverapi "github.com/openshift/origin/pkg/cmd/server/api" | ||
imageapi "github.com/openshift/origin/pkg/image/apis/image" | ||
stringsutil "github.com/openshift/origin/pkg/util/strings" | ||
) | ||
|
||
// RegistryWhitelister decides whether given image pull specs are allowed by system's image policy. | ||
type RegistryWhitelister interface { | ||
// AdmitHostname returns error if the given host is allowed by the whitelist. | ||
AdmitHostname(host string, insecure bool) error | ||
// AdmitDockerImageReference returns error if the given reference is allowed neither by the whitelist nor | ||
// by collected whitelisted pull specs. | ||
AdmitDockerImageReference(ref *imageapi.DockerImageReference, insecure bool) error | ||
// WhitelistRegistry extends internal whitelist for additional registry domain name. Accepted values are: | ||
// <host>, <host>:<port> | ||
// where each component can contain wildcards like '*' or '??' to mach wide range of registries. If the | ||
// port is omitted, the default will be appended based on the given insecure flag. | ||
WhitelistRegistry(hostPortGlob string, insecure bool) error | ||
// WhitelistPullSpecs allows to whitelist particular pull specs. References must match exactly one of the | ||
// given pull specs for it to be whitelisted. | ||
// TODO: accept insecure flag | ||
WhitelistPullSpecs(pullSpecs ...string) | ||
// Copy returns a deep copy of the whitelister. This is useful for temporarily whitelisting additional | ||
// registires/pullSpecs before a specific validation. | ||
Copy() RegistryWhitelister | ||
} | ||
|
||
type allowedHostPortGlobs struct { | ||
host string | ||
port string | ||
} | ||
|
||
type registryWhitelister struct { | ||
whitelist []allowedHostPortGlobs | ||
pullSpecs sets.String | ||
registryHostRetriever imageapi.RegistryHostnameRetriever | ||
} | ||
|
||
var _ RegistryWhitelister = ®istryWhitelister{} | ||
|
||
func NewRegistryWhitelister( | ||
whitelist serverapi.AllowedRegistries, | ||
registryHostRetriever imageapi.RegistryHostnameRetriever, | ||
) (RegistryWhitelister, error) { | ||
errs := []error{} | ||
rw := registryWhitelister{ | ||
whitelist: make([]allowedHostPortGlobs, 0, len(whitelist)), | ||
pullSpecs: sets.NewString(), | ||
registryHostRetriever: registryHostRetriever, | ||
} | ||
for _, registry := range whitelist { | ||
err := rw.WhitelistRegistry(registry.DomainName, registry.Insecure) | ||
if err != nil { | ||
errs = append(errs, err) | ||
} | ||
} | ||
|
||
if len(errs) > 0 { | ||
return nil, kerrutil.NewAggregate(errs) | ||
} | ||
return &rw, nil | ||
} | ||
|
||
// WhitelistAllRegistries returns a whitelister that will allow any given registry host name. | ||
// TODO: make a new implementation of RegistryWhitelister instead that will not bother with pull specs | ||
func WhitelistAllRegistries() RegistryWhitelister { | ||
return ®istryWhitelister{ | ||
whitelist: []allowedHostPortGlobs{{host: "*", port: "*"}}, | ||
pullSpecs: sets.NewString(), | ||
} | ||
} | ||
|
||
func (rw *registryWhitelister) AdmitHostname(hostname string, insecure bool) error { | ||
return rw.AdmitDockerImageReference(&imageapi.DockerImageReference{Registry: hostname}, insecure) | ||
} | ||
|
||
func (rw *registryWhitelister) AdmitDockerImageReference(ref *imageapi.DockerImageReference, insecure bool) error { | ||
const showMax = 4 | ||
const fullWhitelistLogLevel = 5 | ||
|
||
if rw.pullSpecs.Len() > 0 { | ||
if rw.pullSpecs.Has(ref.Exact()) || rw.pullSpecs.Has(ref.DockerClientDefaults().Exact()) || rw.pullSpecs.Has(ref.DaemonMinimal().Exact()) { | ||
return nil | ||
} | ||
} | ||
|
||
if rw.registryHostRetriever != nil { | ||
if localRegistry, ok := rw.registryHostRetriever.InternalRegistryHostname(); ok { | ||
rw.WhitelistRegistry(localRegistry, false) | ||
} | ||
} | ||
|
||
host, port := ref.RegistryHostPort(insecure) | ||
|
||
matchHost := func(h string) bool { | ||
for _, hp := range rw.whitelist { | ||
if stringsutil.IsWildcardMatch(h, hp.host) && stringsutil.IsWildcardMatch(port, hp.port) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
switch host { | ||
case imageapi.DockerDefaultV1Registry, imageapi.DockerDefaultV2Registry: | ||
// try to match plain docker.io first to satisfy `docker.io:*` wildcard | ||
if matchHost(imageapi.DockerDefaultRegistry) { | ||
return nil | ||
} | ||
fallthrough | ||
default: | ||
if matchHost(host) { | ||
return nil | ||
} | ||
} | ||
|
||
hostname := ref.Registry | ||
if len(ref.Registry) == 0 { | ||
hostname = net.JoinHostPort(host, port) | ||
} | ||
whitelist := []string{} | ||
full := []string{} | ||
for i := 0; i < len(rw.whitelist); i++ { | ||
if i < showMax-1 || len(rw.whitelist)-showMax == 0 { | ||
whitelist = append(whitelist, fmt.Sprintf("%q", net.JoinHostPort(rw.whitelist[i].host, rw.whitelist[i].port))) | ||
} else if i < showMax { | ||
whitelist = append(whitelist, fmt.Sprintf("and %d more ...", len(rw.whitelist)-showMax+1)) | ||
} else if !glog.V(fullWhitelistLogLevel) { | ||
break | ||
} | ||
full = append(full, fmt.Sprintf("%q", net.JoinHostPort(rw.whitelist[i].host, rw.whitelist[i].port))) | ||
} | ||
glog.V(fullWhitelistLogLevel).Infof("registry %q not allowed by whitelist { %s }", hostname, strings.Join(full, ", ")) | ||
return fmt.Errorf("registry %q not allowed by whitelist { %s }", hostname, strings.Join(whitelist, ", ")) | ||
} | ||
|
||
func (rw *registryWhitelister) WhitelistRegistry(hostPortGlob string, insecure bool) error { | ||
hp := allowedHostPortGlobs{} | ||
|
||
parts := strings.SplitN(hostPortGlob, ":", 3) | ||
switch len(parts) { | ||
case 1: | ||
hp.host = parts[0] | ||
if insecure { | ||
hp.port = "80" | ||
} else { | ||
hp.port = "443" | ||
} | ||
case 2: | ||
hp.host, hp.port = parts[0], parts[1] | ||
default: | ||
return fmt.Errorf("failed to parse allowed registry %q: too many colons", hostPortGlob) | ||
} | ||
|
||
for _, item := range rw.whitelist { | ||
if reflect.DeepEqual(item, hp) { | ||
return nil | ||
} | ||
} | ||
rw.whitelist = append(rw.whitelist, hp) | ||
|
||
return nil | ||
} | ||
|
||
func (rw *registryWhitelister) WhitelistPullSpecs(pullSpecs ...string) { | ||
rw.pullSpecs.Insert(pullSpecs...) | ||
} | ||
|
||
func (rw *registryWhitelister) Copy() RegistryWhitelister { | ||
newRW := registryWhitelister{ | ||
whitelist: make([]allowedHostPortGlobs, len(rw.whitelist)), | ||
pullSpecs: sets.NewString(rw.pullSpecs.List()...), | ||
registryHostRetriever: rw.registryHostRetriever, | ||
} | ||
|
||
for i, item := range rw.whitelist { | ||
newRW.whitelist[i] = item | ||
} | ||
return &newRW | ||
} |
Oops, something went wrong.