Skip to content

Commit

Permalink
fix: work in progress for issue #13 implement distribution spec auth …
Browse files Browse the repository at this point in the history
…interface

- Added auth/Accesscontroller necessary for enabling differnt auth methods like oauth
- Work in progress, not to be merged

Signed-off-by: guacamole <[email protected]>
  • Loading branch information
guacamole committed Sep 30, 2021
1 parent 18438b7 commit 3cfa2a9
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 2 deletions.
200 changes: 200 additions & 0 deletions auth/accesscontroller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package auth

import (
"context"
"crypto"
"crypto/x509"
"encoding/pem"
"fmt"
registry_auth "github.com/distribution/distribution/registry/auth"
"github.com/labstack/echo/v4"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/docker/libtrust"
)

//accessSet maps the named resource to a set of actions authorised
type accessSet map[registry_auth.Resource]actionSet

// newAccessSet constructs an accessSet from
// a variable number of auth.Access items.
func newAccessSet(accessItems ...registry_auth.Access) accessSet {
accessSet := make(accessSet, len(accessItems))

for _, access := range accessItems {
resource := registry_auth.Resource{
Type: access.Type,
Name: access.Name,
}
set,exists := accessSet[resource]
if !exists {
set = newActionSet()
accessSet[resource] = set
}

set.add(access.Action)
}

return accessSet
}

func (s accessSet) scopeParam() string {
scopes := make([]string, 0, len(s))

for resource, actionSet := range s {
actions := strings.Join(actionSet.keys(), ",")
scopes = append(scopes, fmt.Sprintf("%s:%s:%s", resource.Type, resource.Name, actions))
}

return strings.Join(scopes, " ")
}


type authChallenge struct {
err error
realm string
autoRedirect bool
service string
accessSet accessSet
}

func (ac authChallenge) challengeParams(r *http.Request) string {
var realm string
if ac.autoRedirect{
realm = fmt.Sprintf("https://%s/auth/token", r.Host)
}else{
realm = ac.realm
}
str := fmt.Sprintf("Bearer realm=%q,service=%q", realm, ac.service)
if scope := ac.accessSet.scopeParam(); scope != "" {
str = fmt.Sprintf("%s,scope=%q", str, scope)
}

if ac.err.Error()== "ErrInvalidToken" || ac.err.Error() == "ErrMalformedToken" {
str = fmt.Sprintf("%s,error=%q", str, "invalid token")
} else if ac.err.Error() == "ErrInsufficientScope" {
str = fmt.Sprintf("%s,error=%q", str, "insufficient_scope")
}

return str
}

// SetHeaders sets the WWW-Authenticate value for the response.
func (ac authChallenge) SetHeaders(r *http.Request, w http.ResponseWriter) {
w.Header().Add("WWW-Authenticate", ac.challengeParams(r))
}


//tokenAccessOptions is a convenience type for handling
//options to constructor of an accessController
type tokenAccessOptions struct {
realm string
autoRedirect bool
issuer string
service string
rootCertBundle string
}

func checkOptions(options map[string]interface{}) (tokenAccessOptions, error) {
var opts tokenAccessOptions

keys := []string{"realm", "issuer", "service", "rootcertbundle"}
vals := make([]string, 0, len(keys))

for _,key := range keys {
val, ok := options[key].(string)
if !ok {
return opts, fmt.Errorf("token auth requires a valid option string: %q", key)
}
vals = append(vals, val)
}

opts.realm, opts.issuer, opts.service, opts.rootCertBundle = vals[0], vals[1], vals[2], vals[3]
autoRedirectVal, ok := options["autoredirect"]
if ok {
autoRedirect, ok := autoRedirectVal.(bool)
if !ok {
return opts, fmt.Errorf("token auth requires a valid option bool: autoredirect")
}
opts.autoRedirect = autoRedirect
}
return opts, nil
}

func newAccessController(options map[string]interface{}) (registry_auth.AccessController, error) {
config, err := checkOptions(options)
if err != nil {
return nil, err
}

fp, err := os.Open(config.rootCertBundle)
if err != nil {
return nil, fmt.Errorf("unable to open token auth root certificate bundle file %q: %s", config.rootCertBundle, err)
}
defer fp.Close()

rawCertBundle, err := ioutil.ReadAll(fp)
if err != nil {
return nil, fmt.Errorf("unable to read token auth root certificate bundle file %q: %s", config.rootCertBundle, err)
}

var rootCerts []*x509.Certificate
pemBlock, rawCertBundle := pem.Decode(rawCertBundle)
for pemBlock != nil {
if pemBlock.Type == "CERTIFICATE" {
cert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("unable to parse token auth root certificate: %s", err)
}

rootCerts = append(rootCerts, cert)
}
pemBlock, rawCertBundle = pem.Decode(rawCertBundle)
}
if len(rootCerts) == 0 {
return nil, fmt.Errorf("token auth requires atleast one token signing root certificate")
}

rootPool := x509.NewCertPool()
trustedKeys := make(map[string]libtrust.PublicKey, len(rootCerts))
for _, rootCert := range rootCerts {
rootPool.AddCert(rootCert)
pubKey, err := libtrust.FromCryptoPublicKey(crypto.PublicKey(rootCert.PublicKey))
if err != nil {
return nil, fmt.Errorf("unable to get public key from token auth root certificate: %s", err)
}
trustedKeys[pubKey.KeyID()] = pubKey
}

return &auth{
store: nil,
c: nil,
}, nil
}



// Authorized handles checking whether the given request is authorized
// for actions on resources described by the given access items.
func (a *auth) Authorized(ctx context.Context, access ...registry_auth.Access) (context.Context, error) {
if a.realm == "local" {
return nil, nil
}

if a.realm == "akash" {

}

return nil,nil
}

func (a *auth) AuthorizedEcho() echo.MiddlewareFunc {
return func(handlerFunc echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
a.Authorized(c.Request().Context(), )
}
}
}

31 changes: 29 additions & 2 deletions auth/auth.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,50 @@
package auth

import (
"crypto/x509"
"github.com/containerish/OpenRegistry/cache"
"github.com/containerish/OpenRegistry/config"
registry_auth "github.com/distribution/distribution/registry/auth"
"github.com/docker/libtrust"
"github.com/labstack/echo/v4"
"net/http"
)

type Authentication interface {
SignUp(ctx echo.Context) error
SignIn(ctx echo.Context) error
BasicAuth(username, password string) (map[string]interface{}, error)
registry_auth.AccessController
registry_auth.CredentialAuthenticator
registry_auth.Challenge
}

//auth implements the auth.AccessController interface.
type auth struct {
store cache.Store
c *config.RegistryConfig
store cache.Store
c *config.RegistryConfig
realm string
autoRedirect bool
issuer string
service string
rootCerts *x509.CertPool
trustedKeys map[string]libtrust.PublicKey
}

func (a *auth) AuthenticateUser(username, password string) error {
panic("implement me")
}

func (a *auth) Error() string {
return a.Error()
}

func (a *auth) SetHeaders(r *http.Request, w http.ResponseWriter) {
panic("implement me")
}

func New(s cache.Store, c *config.RegistryConfig) Authentication {
accessCtrl := newAccessController()
a := &auth{store: s, c: c}
return a
}
61 changes: 61 additions & 0 deletions auth/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package auth

// StringSet is a useful type for looking up strings.
type stringSet map[string]struct{}

// NewStringSet creates a new StringSet with the given strings.
func newStringSet(keys ...string) stringSet {
ss := make(stringSet, len(keys))
ss.add(keys...)
return ss
}

// Add inserts the given keys into this StringSet.
func (ss stringSet) add(keys ...string) {
for _, key := range keys {
ss[key] = struct{}{}
}
}

// Contains returns whether the given key is in this StringSet.
func (ss stringSet) contains(key string) bool {
_, ok := ss[key]
return ok
}

// Keys returns a slice of all keys in this StringSet.
func (ss stringSet) keys() []string {
keys := make([]string, 0, len(ss))

for key := range ss {
keys = append(keys, key)
}

return keys
}

// actionSet is a special type of stringSet.
type actionSet struct {
stringSet
}

func newActionSet(actions ...string) actionSet {
return actionSet{newStringSet(actions...)}
}

// Contains calls StringSet.Contains() for
// either "*" or the given action string.
func (s actionSet) contains(action string) bool {
return s.stringSet.contains("*") || s.stringSet.contains(action)
}

// contains returns true if q is found in ss.
func contains(ss []string, q string) bool {
for _, s := range ss {
if s == q {
return true
}
}

return false
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ require (
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/distribution/distribution v2.7.1+incompatible // indirect
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,12 @@ github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/distribution/distribution v2.7.1+incompatible h1:aGFx4EvJWKEh//lHPLwFhFgwFHKH06TzNVPamrMn04M=
github.com/distribution/distribution v2.7.1+incompatible/go.mod h1:EgLm2NgWtdKgzF9NpMzUKgzmR7AMmb0VQi2B+ZzDRjc=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
Expand Down

0 comments on commit 3cfa2a9

Please sign in to comment.