Jerry Sun 6714d6b8d7 Add Docker Registry Token Server
As part of setting up Keystone authentication with Docker registry,
a token server needs to be deployed and managed by SM. This commit
adds the source code and spec files to build the token server used by
Docker registry for authentication with Keystone. The token server is
a modified version of the example token server provided by Docker
Distribution. The changes uses the Gophercloud library to communicate
with Keystone as opposed to checking the user credentials against an
existing HTPASSWD file.

Story: 2002840
Task: 22783
Depends-On: https://review.openstack.org/#/c/625335/

Change-Id: I00f5aa1073d496aa0b08223c6fa1fcbaf9d5b89b
Signed-off-by: Jerry Sun <jerry.sun@windriver.com>
2019-01-08 11:42:04 -05:00

97 lines
2.6 KiB
Go

// Initial file was taken from https://github.com/docker/distribution 2018 Sept
//
// Copyright (c) 2018 Wind River Systems, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// Package keystone provides a simple authentication scheme that checks for the
// user credential against keystone with configuration-determined endpoint
//
// This authentication method MUST be used under TLS, as simple token-replay attack is possible.
package keystone
import (
"fmt"
"net/http"
"github.com/docker/distribution/context"
"github.com/docker/distribution/registry/auth"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
)
type accessController struct {
realm string
endpoint 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 keystone access controller`)
}
endpoint, present := options["endpoint"]
if _, ok := endpoint.(string); !present || !ok {
return nil, fmt.Errorf(`"endpoint" must be set for keystone access controller`)
}
return &accessController{realm: realm.(string), endpoint: endpoint.(string)}, nil
}
func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) {
req, err := context.GetRequest(ctx)
if err != nil {
return nil, err
}
username, password, ok := req.BasicAuth()
if !ok {
return nil, &challenge{
realm: ac.realm,
err: auth.ErrInvalidCredential,
}
}
opts := gophercloud.AuthOptions{
IdentityEndpoint: ac.endpoint,
Username: username,
Password: password,
DomainID: "default",
}
if _, err := openstack.AuthenticatedClient(opts); err != nil {
context.GetLogger(ctx).Errorf("error authenticating user %q: %v", username, err)
return nil, &challenge{
realm: ac.realm,
err: auth.ErrAuthenticationFailure,
}
}
return auth.WithUser(ctx, auth.UserInfo{Name: username}), nil
}
// challenge implements the auth.Challenge interface.
type challenge struct {
realm string
err error
}
var _ auth.Challenge = challenge{}
// SetHeaders sets the basic challenge header on the response.
func (ch challenge) SetHeaders(w http.ResponseWriter) {
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", ch.realm))
}
func (ch challenge) Error() string {
return fmt.Sprintf("basic authentication challenge for realm %q: %s", ch.realm, ch.err)
}
func init() {
auth.Register("keystone", auth.InitFunc(newAccessController))
}