Skip to content

Commit 4b16943

Browse files
author
OpenShift Bot
authored
Merge pull request #13313 from mfojtik/registry-whitelist
Merged by openshift-bot
2 parents 63ab899 + c058b4d commit 4b16943

File tree

19 files changed

+390
-91
lines changed

19 files changed

+390
-91
lines changed

pkg/api/install/tags_test.go

+7
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ func checkInternalJsonTags(objType reflect.Type, seen *map[reflect.Type]bool, t
9898
if internalTypesWithAllowedJsonTags.Has(objType.Name()) {
9999
return
100100
}
101+
if objType.Kind() != reflect.Struct {
102+
return
103+
}
101104

102105
for i := 0; i < objType.NumField(); i++ {
103106
structField := objType.FieldByIndex([]int{i})
@@ -143,6 +146,10 @@ func checkExternalJsonTags(objType reflect.Type, seen *map[reflect.Type]bool, t
143146
return
144147
}
145148

149+
if objType.Kind() != reflect.Struct {
150+
return
151+
}
152+
146153
for i := 0; i < objType.NumField(); i++ {
147154
structField := objType.FieldByIndex([]int{i})
148155

pkg/cmd/server/api/serialization_test.go

+7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ func fuzzInternalObject(t *testing.T, forVersion unversioned.GroupVersion, item
3636
if len(obj.APILevels) == 0 {
3737
obj.APILevels = configapi.DefaultOpenShiftAPILevels
3838
}
39+
if obj.ImagePolicyConfig.AllowedRegistriesForImport == nil {
40+
obj.ImagePolicyConfig.AllowedRegistriesForImport = &configapi.AllowedRegistries{}
41+
}
3942
if len(obj.Controllers) == 0 {
4043
obj.Controllers = configapi.ControllersAll
4144
}
@@ -259,6 +262,10 @@ func fuzzInternalObject(t *testing.T, forVersion unversioned.GroupVersion, item
259262
if obj.ScheduledImageImportMinimumIntervalSeconds == 0 {
260263
obj.ScheduledImageImportMinimumIntervalSeconds = 15 * 60
261264
}
265+
obj.AllowedRegistriesForImport = &configapi.AllowedRegistries{
266+
{DomainName: "docker.io"},
267+
{DomainName: "gcr.io"},
268+
}
262269
},
263270
func(obj *configapi.DNSConfig, c fuzz.Continue) {
264271
c.FuzzNoCustom(obj)

pkg/cmd/server/api/types.go

+39
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,23 @@ var (
124124
}
125125
KnownOpenShiftFeatures = []string{FeatureBuilder, FeatureS2I, FeatureWebConsole}
126126
AtomicDisabledFeatures = []string{FeatureBuilder, FeatureS2I, FeatureWebConsole}
127+
128+
// List public registries that we are allowing to import images from by default.
129+
// By default all registries have set to be "secure", iow. the port for them is
130+
// defaulted to "443".
131+
// If the registry you are adding here is insecure, you can add 'Insecure: true' to
132+
// make it default to port '80'.
133+
// If the registry you are adding use custom port, you have to specify the port as
134+
// part of the domain name.
135+
DefaultAllowedRegistriesForImport = &AllowedRegistries{
136+
{DomainName: "docker.io"},
137+
{DomainName: "*.docker.io"}, // registry-1.docker.io
138+
{DomainName: "registry.access.redhat.com"},
139+
{DomainName: "gcr.io"},
140+
{DomainName: "quay.io"},
141+
// FIXME: Probably need to have more fine-tuned pattern defined
142+
{DomainName: "*.amazonaws.com"},
143+
}
127144
)
128145

129146
type ExtendedArguments map[string][]string
@@ -455,6 +472,28 @@ type ImagePolicyConfig struct {
455472
// MaxScheduledImageImportsPerMinute is the maximum number of image streams that will be imported in the background per minute.
456473
// The default value is 60. Set to -1 for unlimited.
457474
MaxScheduledImageImportsPerMinute int
475+
// AllowedRegistriesForImport limits the docker registries that normal users may import
476+
// images from. Set this list to the registries that you trust to contain valid Docker
477+
// images and that you want applications to be able to import from. Users with
478+
// permission to create Images or ImageStreamMappings via the API are not affected by
479+
// this policy - typically only administrators or system integrations will have those
480+
// permissions.
481+
AllowedRegistriesForImport *AllowedRegistries
482+
}
483+
484+
// AllowedRegistries represents a list of registries allowed for the image import.
485+
type AllowedRegistries []RegistryLocation
486+
487+
// RegistryLocation contains a location of the registry specified by the registry domain
488+
// name. The domain name might include wildcards, like '*' or '??'.
489+
type RegistryLocation struct {
490+
// DomainName specifies a domain name for the registry
491+
// In case the registry use non-standard (80 or 443) port, the port should be included
492+
// in the domain name as well.
493+
DomainName string
494+
// Insecure indicates whether the registry is secure (https) or insecure (http)
495+
// By default (if not specified) the registry is assumed as secure.
496+
Insecure bool
458497
}
459498

460499
type ProjectConfig struct {

pkg/cmd/server/api/v1/swagger_doc.go

+11
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ var map_ImagePolicyConfig = map[string]string{
305305
"disableScheduledImport": "DisableScheduledImport allows scheduled background import of images to be disabled.",
306306
"scheduledImageImportMinimumIntervalSeconds": "ScheduledImageImportMinimumIntervalSeconds is the minimum number of seconds that can elapse between when image streams scheduled for background import are checked against the upstream repository. The default value is 15 minutes.",
307307
"maxScheduledImageImportsPerMinute": "MaxScheduledImageImportsPerMinute is the maximum number of scheduled image streams that will be imported in the background per minute. The default value is 60. Set to -1 for unlimited.",
308+
"allowedRegistriesForImport": "AllowedRegistriesForImport limits the docker registries that normal users may import images from. Set this list to the registries that you trust to contain valid Docker images and that you want applications to be able to import from. Users with permission to create Images or ImageStreamMappings via the API are not affected by this policy - typically only administrators or system integrations will have those permissions.",
308309
}
309310

310311
func (ImagePolicyConfig) SwaggerDoc() map[string]string {
@@ -702,6 +703,16 @@ func (RFC2307Config) SwaggerDoc() map[string]string {
702703
return map_RFC2307Config
703704
}
704705

706+
var map_RegistryLocation = map[string]string{
707+
"": "RegistryLocation contains a location of the registry specified by the registry domain name. The domain name might include wildcards, like '*' or '??'.",
708+
"domainName": "DomainName specifies a domain name for the registry In case the registry use non-standard (80 or 443) port, the port should be included in the domain name as well.",
709+
"insecure": "Insecure indicates whether the registry is secure (https) or insecure (http) By default (if not specified) the registry is assumed as secure.",
710+
}
711+
712+
func (RegistryLocation) SwaggerDoc() map[string]string {
713+
return map_RegistryLocation
714+
}
715+
705716
var map_RemoteConnectionInfo = map[string]string{
706717
"": "RemoteConnectionInfo holds information necessary for establishing a remote connection",
707718
"url": "URL is the remote URL to connect to",

pkg/cmd/server/api/v1/types.go

+22
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,28 @@ type ImagePolicyConfig struct {
341341
// MaxScheduledImageImportsPerMinute is the maximum number of scheduled image streams that will be imported in the
342342
// background per minute. The default value is 60. Set to -1 for unlimited.
343343
MaxScheduledImageImportsPerMinute int `json:"maxScheduledImageImportsPerMinute"`
344+
// AllowedRegistriesForImport limits the docker registries that normal users may import
345+
// images from. Set this list to the registries that you trust to contain valid Docker
346+
// images and that you want applications to be able to import from. Users with
347+
// permission to create Images or ImageStreamMappings via the API are not affected by
348+
// this policy - typically only administrators or system integrations will have those
349+
// permissions.
350+
AllowedRegistriesForImport *AllowedRegistries `json:"allowedRegistriesForImport,omitempty"`
351+
}
352+
353+
// AllowedRegistries represents a list of registries allowed for the image import.
354+
type AllowedRegistries []RegistryLocation
355+
356+
// RegistryLocation contains a location of the registry specified by the registry domain
357+
// name. The domain name might include wildcards, like '*' or '??'.
358+
type RegistryLocation struct {
359+
// DomainName specifies a domain name for the registry
360+
// In case the registry use non-standard (80 or 443) port, the port should be included
361+
// in the domain name as well.
362+
DomainName string `json:"domainName"`
363+
// Insecure indicates whether the registry is secure (https) or insecure (http)
364+
// By default (if not specified) the registry is assumed as secure.
365+
Insecure bool `json:"insecure,omitempty"`
344366
}
345367

346368
// holds the necessary configuration options for

pkg/cmd/server/api/validation/master.go

+23
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/url"
77
"reflect"
88
"regexp"
9+
"strconv"
910
"strings"
1011
"time"
1112

@@ -473,6 +474,28 @@ func ValidateImagePolicyConfig(config api.ImagePolicyConfig, fldPath *field.Path
473474
if config.MaxScheduledImageImportsPerMinute == 0 || config.MaxScheduledImageImportsPerMinute < -1 {
474475
errs = append(errs, field.Invalid(fldPath.Child("maxScheduledImageImportsPerMinute"), config.MaxScheduledImageImportsPerMinute, "must be a positive integer or -1"))
475476
}
477+
if config.AllowedRegistriesForImport != nil {
478+
for i, registry := range *config.AllowedRegistriesForImport {
479+
if len(registry.DomainName) == 0 {
480+
errs = append(errs, field.Invalid(fldPath.Index(i).Child("allowedRegistriesForImport", "domainName"), registry.DomainName, "cannot be an empty string"))
481+
}
482+
parts := strings.Split(registry.DomainName, ":")
483+
// Check for ':8080'
484+
if len(parts) == 0 || len(parts[0]) == 0 {
485+
errs = append(errs, field.Invalid(fldPath.Index(i).Child("allowedRegistriesForImport", "domainName"), registry.DomainName, "invalid domain specified, must be registry.url.local[:port]"))
486+
}
487+
// Check for 'foo:bar:1234'
488+
if len(parts) > 2 {
489+
errs = append(errs, field.Invalid(fldPath.Index(i).Child("allowedRegistriesForImport", "domainName"), registry.DomainName, "invalid format, must be registry.url.local[:port]"))
490+
}
491+
// Check for 'foo:bar'
492+
if len(parts) == 2 {
493+
if _, err := strconv.Atoi(parts[1]); err != nil {
494+
errs = append(errs, field.Invalid(fldPath.Index(i).Child("allowedRegistriesForImport", "domainName"), registry.DomainName, "invalid port format, must be a number"))
495+
}
496+
}
497+
}
498+
}
476499
return errs
477500
}
478501

pkg/cmd/server/origin/master.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -739,7 +739,7 @@ func (c *MasterConfig) GetRestStorage() map[unversioned.GroupVersion]map[string]
739739
importerDockerClientFn := func() dockerregistry.Client {
740740
return dockerregistry.NewClient(20*time.Second, false)
741741
}
742-
imageStreamImportStorage := imagestreamimport.NewREST(importerFn, imageStreamRegistry, internalImageStreamStorage, imageStorage, c.ImageStreamImportSecretClient(), importTransport, insecureImportTransport, importerDockerClientFn)
742+
imageStreamImportStorage := imagestreamimport.NewREST(importerFn, imageStreamRegistry, internalImageStreamStorage, imageStorage, c.ImageStreamImportSecretClient(), importTransport, insecureImportTransport, importerDockerClientFn, c.Options.ImagePolicyConfig.AllowedRegistriesForImport, c.RegistryNameFn, c.ImageStreamImportSARClient().SubjectAccessReviews())
743743
imageStreamImageStorage := imagestreamimage.NewREST(imageRegistry, imageStreamRegistry)
744744
imageStreamImageRegistry := imagestreamimage.NewRegistry(imageStreamImageStorage)
745745

pkg/cmd/server/origin/master_config.go

+5
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,11 @@ func (c *MasterConfig) ImageStreamImportSecretClient() *osclient.Client {
996996
return c.PrivilegedLoopbackOpenShiftClient
997997
}
998998

999+
// ImageStreamImportSARClient returns the client capable of performing self-SAR requests
1000+
func (c *MasterConfig) ImageStreamImportSARClient() *osclient.Client {
1001+
return c.PrivilegedLoopbackOpenShiftClient
1002+
}
1003+
9991004
// ResourceQuotaManagerClients returns the client capable of retrieving resources needed for resource quota
10001005
// evaluation
10011006
func (c *MasterConfig) ResourceQuotaManagerClients() (*osclient.Client, *kclientset.Clientset) {

pkg/cmd/server/start/master_args.go

+11
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,17 @@ func (args MasterArgs) BuildSerializeableMasterConfig() (*configapi.MasterConfig
242242
Latest: args.ImageFormatArgs.ImageTemplate.Latest,
243243
},
244244

245+
// List public registries that we are allowing to import images from by default.
246+
// By default all registries have set to be "secure", iow. the port for them is
247+
// defaulted to "443".
248+
// If the registry you are adding here is insecure, you can add 'Insecure: true' which
249+
// in that case it will default to port '80'.
250+
// If the registry you are adding use custom port, you have to specify the port as
251+
// part of the domain name.
252+
ImagePolicyConfig: configapi.ImagePolicyConfig{
253+
AllowedRegistriesForImport: configapi.DefaultAllowedRegistriesForImport,
254+
},
255+
245256
ProjectConfig: configapi.ProjectConfig{
246257
DefaultNodeSelector: "",
247258
ProjectRequestMessage: "",

pkg/image/api/helper.go

+20
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"encoding/json"
66
"fmt"
7+
"net"
78
"net/url"
89
"regexp"
910
"sort"
@@ -34,6 +35,10 @@ const (
3435

3536
// TagReferenceAnnotationTagHidden indicates that a given TagReference is hidden from search results
3637
TagReferenceAnnotationTagHidden = "hidden"
38+
39+
// ImportRegistryNotAllowed indicates that the image tag was not imported due to
40+
// untrusted registry.
41+
ImportRegistryNotAllowed = "registry is not allowed for import"
3742
)
3843

3944
// DefaultRegistry returns the default Docker registry (host or host:port), or false if it is not available.
@@ -168,6 +173,21 @@ func (r DockerImageReference) RepositoryName() string {
168173
return r.Exact()
169174
}
170175

176+
// RegistryHostPort returns the registry hostname and the port.
177+
// If the port is not specified in the registry hostname we default to 443.
178+
// This will also default to Docker client defaults if the registry hostname is empty.
179+
func (r DockerImageReference) RegistryHostPort(insecure bool) (string, string) {
180+
registryHost := r.AsV2().DockerClientDefaults().Registry
181+
if strings.Contains(registryHost, ":") {
182+
hostname, port, _ := net.SplitHostPort(registryHost)
183+
return hostname, port
184+
}
185+
if insecure {
186+
return registryHost, "80"
187+
}
188+
return registryHost, "443"
189+
}
190+
171191
// RepositoryName returns the registry relative name
172192
func (r DockerImageReference) RegistryURL() *url.URL {
173193
return &url.URL{

pkg/image/api/validation/validation.go

+33
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import (
1515
"k8s.io/kubernetes/pkg/util/diff"
1616
"k8s.io/kubernetes/pkg/util/validation/field"
1717

18+
serverapi "github.com/openshift/origin/pkg/cmd/server/api"
1819
"github.com/openshift/origin/pkg/image/api"
20+
stringsutil "github.com/openshift/origin/pkg/util/strings"
1921
)
2022

2123
// RepositoryNameComponentRegexp restricts registry path component names to
@@ -297,6 +299,37 @@ func ValidateImageStreamTagUpdate(newIST, oldIST *api.ImageStreamTag) field.Erro
297299
return result
298300
}
299301

302+
func ValidateRegistryAllowedForImport(path *field.Path, name, registryHost, registryPort string, allowedRegistries *serverapi.AllowedRegistries) field.ErrorList {
303+
errs := field.ErrorList{}
304+
if allowedRegistries == nil {
305+
return errs
306+
}
307+
allowedRegistriesForHumans := []string{}
308+
for _, registry := range *allowedRegistries {
309+
allowedRegistryHost, allowedRegistryPort := "", ""
310+
parts := strings.Split(registry.DomainName, ":")
311+
switch len(parts) {
312+
case 1:
313+
allowedRegistryHost = parts[0]
314+
if registry.Insecure {
315+
allowedRegistryPort = "80"
316+
} else {
317+
allowedRegistryPort = "443"
318+
}
319+
case 2:
320+
allowedRegistryHost, allowedRegistryPort = parts[0], parts[1]
321+
default:
322+
continue
323+
}
324+
if stringsutil.IsWildcardMatch(registryHost, allowedRegistryHost) && stringsutil.IsWildcardMatch(registryPort, allowedRegistryPort) {
325+
return errs
326+
}
327+
allowedRegistriesForHumans = append(allowedRegistriesForHumans, registry.DomainName)
328+
}
329+
return append(errs, field.Invalid(path, name,
330+
fmt.Sprintf("importing images from registry %q is forbidden, only images from %q are allowed", registryHost+":"+registryPort, strings.Join(allowedRegistriesForHumans, ","))))
331+
}
332+
300333
func ValidateImageStreamImport(isi *api.ImageStreamImport) field.ErrorList {
301334
specPath := field.NewPath("spec")
302335
imagesPath := specPath.Child("images")

0 commit comments

Comments
 (0)