Skip to content

Commit e02278f

Browse files
committed
Update registry server to support repository class
Use whitelist of allowed repository classes to enforce. By default all repository classes are allowed. Add authorized resources to context after authorization. Signed-off-by: Derek McGowan <[email protected]> (github: dmcgowan)
1 parent 61e65ec commit e02278f

5 files changed

Lines changed: 146 additions & 1 deletion

File tree

configuration/configuration.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,19 @@ type Configuration struct {
203203
} `yaml:"urls,omitempty"`
204204
} `yaml:"manifests,omitempty"`
205205
} `yaml:"validation,omitempty"`
206+
207+
// Policy configures registry policy options.
208+
Policy struct {
209+
// Repository configures policies for repositories
210+
Repository struct {
211+
// Classes is a list of repository classes which the
212+
// registry allows content for. This class is matched
213+
// against the configuration media type inside uploaded
214+
// manifests. When non-empty, the registry will enforce
215+
// the class in authorized resources.
216+
Classes []string `yaml:"classes"`
217+
} `yaml:"repository,omitempty"`
218+
} `yaml:"policy,omitempty"`
206219
}
207220

208221
// LogHook is composed of hook Level and Type.

registry/auth/auth.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,39 @@ func (uic userInfoContext) Value(key interface{}) interface{} {
136136
return uic.Context.Value(key)
137137
}
138138

139+
// WithResources returns a context with the authorized resources.
140+
func WithResources(ctx context.Context, resources []Resource) context.Context {
141+
return resourceContext{
142+
Context: ctx,
143+
resources: resources,
144+
}
145+
}
146+
147+
type resourceContext struct {
148+
context.Context
149+
resources []Resource
150+
}
151+
152+
type resourceKey struct{}
153+
154+
func (rc resourceContext) Value(key interface{}) interface{} {
155+
if key == (resourceKey{}) {
156+
return rc.resources
157+
}
158+
159+
return rc.Context.Value(key)
160+
}
161+
162+
// AuthorizedResources returns the list of resources which have
163+
// been authorized for this request.
164+
func AuthorizedResources(ctx context.Context) []Resource {
165+
if resources, ok := ctx.Value(resourceKey{}).([]Resource); ok {
166+
return resources
167+
}
168+
169+
return nil
170+
}
171+
139172
// InitFunc is the type of an AccessController factory function and is used
140173
// to register the constructor for different AccesController backends.
141174
type InitFunc func(options map[string]interface{}) (AccessController, error)

registry/auth/token/accesscontroller.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ func (ac *accessController) Authorized(ctx context.Context, accessItems ...auth.
261261
}
262262
}
263263

264+
ctx = auth.WithResources(ctx, token.resources())
265+
264266
return auth.WithUser(ctx, auth.UserInfo{Name: token.Claims.Subject}), nil
265267
}
266268

registry/auth/token/token.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ var (
3434
// ResourceActions stores allowed actions on a named and typed resource.
3535
type ResourceActions struct {
3636
Type string `json:"type"`
37-
Class string `json:"class"`
37+
Class string `json:"class,omitempty"`
3838
Name string `json:"name"`
3939
Actions []string `json:"actions"`
4040
}
@@ -350,6 +350,29 @@ func (t *Token) accessSet() accessSet {
350350
return accessSet
351351
}
352352

353+
func (t *Token) resources() []auth.Resource {
354+
if t.Claims == nil {
355+
return nil
356+
}
357+
358+
resourceSet := map[auth.Resource]struct{}{}
359+
for _, resourceActions := range t.Claims.Access {
360+
resource := auth.Resource{
361+
Type: resourceActions.Type,
362+
Class: resourceActions.Class,
363+
Name: resourceActions.Name,
364+
}
365+
resourceSet[resource] = struct{}{}
366+
}
367+
368+
resources := make([]auth.Resource, 0, len(resourceSet))
369+
for resource := range resourceSet {
370+
resources = append(resources, resource)
371+
}
372+
373+
return resources
374+
}
375+
353376
func (t *Token) compactRaw() string {
354377
return fmt.Sprintf("%s.%s", t.Raw, joseBase64UrlEncode(t.Signature))
355378
}

registry/handlers/images.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/docker/distribution/reference"
1616
"github.com/docker/distribution/registry/api/errcode"
1717
"github.com/docker/distribution/registry/api/v2"
18+
"github.com/docker/distribution/registry/auth"
1819
"github.com/gorilla/handlers"
1920
)
2021

@@ -269,6 +270,12 @@ func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http
269270
if imh.Tag != "" {
270271
options = append(options, distribution.WithTag(imh.Tag))
271272
}
273+
274+
if err := imh.applyResourcePolicy(manifest); err != nil {
275+
imh.Errors = append(imh.Errors, err)
276+
return
277+
}
278+
272279
_, err = manifests.Put(imh, manifest, options...)
273280
if err != nil {
274281
// TODO(stevvooe): These error handling switches really need to be
@@ -339,6 +346,73 @@ func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http
339346
w.WriteHeader(http.StatusCreated)
340347
}
341348

349+
// applyResourcePolicy checks whether the resource class matches what has
350+
// been authorized and allowed by the policy configuration.
351+
func (imh *imageManifestHandler) applyResourcePolicy(manifest distribution.Manifest) error {
352+
allowedClasses := imh.App.Config.Policy.Repository.Classes
353+
if len(allowedClasses) == 0 {
354+
return nil
355+
}
356+
357+
var class string
358+
switch m := manifest.(type) {
359+
case *schema1.SignedManifest:
360+
class = "image"
361+
case *schema2.DeserializedManifest:
362+
switch m.Config.MediaType {
363+
case schema2.MediaTypeConfig:
364+
class = "image"
365+
case schema2.MediaTypePluginConfig:
366+
class = "plugin"
367+
default:
368+
message := fmt.Sprintf("unknown manifest class for %s", m.Config.MediaType)
369+
return errcode.ErrorCodeDenied.WithMessage(message)
370+
}
371+
}
372+
373+
if class == "" {
374+
return nil
375+
}
376+
377+
// Check to see if class is allowed in registry
378+
var allowedClass bool
379+
for _, c := range allowedClasses {
380+
if class == c {
381+
allowedClass = true
382+
break
383+
}
384+
}
385+
if !allowedClass {
386+
message := fmt.Sprintf("registry does not allow %s manifest", class)
387+
return errcode.ErrorCodeDenied.WithMessage(message)
388+
}
389+
390+
resources := auth.AuthorizedResources(imh)
391+
n := imh.Repository.Named().Name()
392+
393+
var foundResource bool
394+
for _, r := range resources {
395+
if r.Name == n {
396+
if r.Class == "" {
397+
r.Class = "image"
398+
}
399+
if r.Class == class {
400+
return nil
401+
}
402+
foundResource = true
403+
}
404+
}
405+
406+
// resource was found but no matching class was found
407+
if foundResource {
408+
message := fmt.Sprintf("repository not authorized for %s manifest", class)
409+
return errcode.ErrorCodeDenied.WithMessage(message)
410+
}
411+
412+
return nil
413+
414+
}
415+
342416
// DeleteImageManifest removes the manifest with the given digest from the registry.
343417
func (imh *imageManifestHandler) DeleteImageManifest(w http.ResponseWriter, r *http.Request) {
344418
ctxu.GetLogger(imh).Debug("DeleteImageManifest")

0 commit comments

Comments
 (0)