Integrate auth.AccessController into registry app
This changeset integrates the AccessController into the main registry app. This includes support for configuration and a test implementation, called "silly" auth. Auth is only enabled if the configuration is present but takes measure to ensure that configuration errors don't allow the appserver to start with open access.
This commit is contained in:
parent
3b8847f489
commit
d0a9e9b475
9 changed files with 413 additions and 17 deletions
89
auth/silly/access.go
Normal file
89
auth/silly/access.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Package silly provides a simple authentication scheme that checks for the
|
||||
// existence of an Authorization header and issues access if is present and
|
||||
// non-empty.
|
||||
//
|
||||
// This package is present as an example implementation of a minimal
|
||||
// auth.AccessController and for testing. This is not suitable for any kind of
|
||||
// production security.
|
||||
package silly
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker-registry/auth"
|
||||
)
|
||||
|
||||
// accessController provides a simple implementation of auth.AccessController
|
||||
// that simply checks for a non-empty Authorization header. It is useful for
|
||||
// demonstration and testing.
|
||||
type accessController struct {
|
||||
realm string
|
||||
service string
|
||||
}
|
||||
|
||||
var _ auth.AccessController = &accessController{}
|
||||
|
||||
func newAccessController(options map[string]interface{}) (auth.AccessController, error) {
|
||||
realm, present := options["realm"]
|
||||
if _, ok := realm.(string); !present || !ok {
|
||||
return nil, fmt.Errorf(`"realm" must be set for silly access controller`)
|
||||
}
|
||||
|
||||
service, present := options["service"]
|
||||
if _, ok := service.(string); !present || !ok {
|
||||
return nil, fmt.Errorf(`"service" must be set for silly access controller`)
|
||||
}
|
||||
|
||||
return &accessController{realm: realm.(string), service: service.(string)}, nil
|
||||
}
|
||||
|
||||
// Authorized simply checks for the existence of the authorization header,
|
||||
// responding with a bearer challenge if it doesn't exist.
|
||||
func (ac *accessController) Authorized(req *http.Request, accessRecords ...auth.Access) error {
|
||||
if req.Header.Get("Authorization") == "" {
|
||||
challenge := challenge{
|
||||
realm: ac.realm,
|
||||
service: ac.service,
|
||||
}
|
||||
|
||||
if len(accessRecords) > 0 {
|
||||
var scopes []string
|
||||
for _, access := range accessRecords {
|
||||
scopes = append(scopes, fmt.Sprintf("%s:%s:%s", access.Type, access.Resource, access.Action))
|
||||
}
|
||||
challenge.scope = strings.Join(scopes, " ")
|
||||
}
|
||||
|
||||
return &challenge
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type challenge struct {
|
||||
realm string
|
||||
service string
|
||||
scope string
|
||||
}
|
||||
|
||||
func (ch *challenge) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
header := fmt.Sprintf("Bearer realm=%q,service=%q", ch.realm, ch.service)
|
||||
|
||||
if ch.scope != "" {
|
||||
header = fmt.Sprintf("%s,scope=%q", header, ch.scope)
|
||||
}
|
||||
|
||||
w.Header().Set("Authorization", header)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
func (ch *challenge) Error() string {
|
||||
return fmt.Sprintf("silly authentication challenge: %#v", ch)
|
||||
}
|
||||
|
||||
// init registers the silly auth backend.
|
||||
func init() {
|
||||
auth.Register("silly", auth.InitFunc(newAccessController))
|
||||
}
|
58
auth/silly/access_test.go
Normal file
58
auth/silly/access_test.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package silly
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker-registry/auth"
|
||||
)
|
||||
|
||||
func TestSillyAccessController(t *testing.T) {
|
||||
ac := &accessController{
|
||||
realm: "test-realm",
|
||||
service: "test-service",
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := ac.Authorized(r); err != nil {
|
||||
switch err := err.(type) {
|
||||
case auth.Challenge:
|
||||
err.ServeHTTP(w, r)
|
||||
return
|
||||
default:
|
||||
t.Fatalf("unexpected error authorizing request: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
|
||||
resp, err := http.Get(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error during GET: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Request should not be authorized
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
t.Fatalf("unexpected response status: %v != %v", resp.StatusCode, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", server.URL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating new request: %v", err)
|
||||
}
|
||||
req.Header.Set("Authorization", "seriously, anything")
|
||||
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error during GET: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Request should not be authorized
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
t.Fatalf("unexpected response status: %v != %v", resp.StatusCode, http.StatusNoContent)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue