Skip to content

Commit

Permalink
Mint scope-based access tokens for RBAC
Browse files Browse the repository at this point in the history
  • Loading branch information
ishank011 committed Apr 29, 2021
1 parent 561dc73 commit 65e451a
Show file tree
Hide file tree
Showing 27 changed files with 440 additions and 87 deletions.
3 changes: 3 additions & 0 deletions changelog/unreleased/scope-based-tokens.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Enhancement: Mint scope-based access tokens for RBAC

https://github.com/cs3org/reva/pull/1669
6 changes: 6 additions & 0 deletions examples/storage-references/gateway.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ home_provider = "/home"
[grpc.services.storageregistry.drivers.static.rules]
"/home" = {"address" = "localhost:17000"}
"/reva" = {"address" = "localhost:18000"}
"/public" = {"address" = "localhost:16000"}
"123e4567-e89b-12d3-a456-426655440000" = {"address" = "localhost:18000"}

[grpc.services.authprovider]
[grpc.services.authregistry]

[grpc.services.authregistry.drivers.static.rules]
basic = "localhost:19000"
publicshares = "localhost:16000"

[grpc.services.userprovider]
[grpc.services.usershareprovider]
[grpc.services.groupprovider]
Expand Down
15 changes: 15 additions & 0 deletions examples/storage-references/storage-public.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[grpc]
address = "0.0.0.0:16000"

[grpc.services.publicstorageprovider]
driver = "localhome"
mount_path = "/public"
mount_id = "123e4567-e89b-12d3-a456-426655440000"
data_server_url = "http://localhost:16001/data"
gateway_addr = "localhost:19000"

[grpc.services.authprovider]
auth_manager = "publicshares"

[grpc.services.authprovider.auth_managers.publicshares]
gateway_addr = "localhost:19000"
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ require (
go 1.16

replace (
github.com/cs3org/go-cs3apis => ../cs3apis/build/go-cs3apis
github.com/eventials/go-tus => github.com/andrewmostello/go-tus v0.0.0-20200314041820-904a9904af9a
github.com/oleiade/reflections => github.com/oleiade/reflections v1.0.1
google.golang.org/grpc => google.golang.org/grpc v1.26.0 // temporary downgrade
Expand Down
11 changes: 7 additions & 4 deletions internal/grpc/interceptors/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,16 @@ func NewUnary(m map[string]interface{}, unprotected []string) (grpc.UnaryServerI
// to decide the storage provider.
tkn, ok := token.ContextGetToken(ctx)
if ok {
u, err := tokenManager.DismantleToken(ctx, tkn)
u, err := tokenManager.DismantleToken(ctx, tkn, req)
if err == nil {
ctx = user.ContextSetUser(ctx, u)
}
}
return handler(ctx, req)
}

log.Info().Msgf("GRPC unary interceptor %s, %+v", info.FullMethod, req)

span.AddAttributes(trace.BoolAttribute("auth_enabled", true))

tkn, ok := token.ContextGetToken(ctx)
Expand All @@ -106,7 +108,7 @@ func NewUnary(m map[string]interface{}, unprotected []string) (grpc.UnaryServerI
}

// validate the token
u, err := tokenManager.DismantleToken(ctx, tkn)
u, err := tokenManager.DismantleToken(ctx, tkn, req)
if err != nil {
log.Warn().Msg("access token is invalid")
return nil, status.Errorf(codes.Unauthenticated, "auth: core access token is invalid")
Expand Down Expand Up @@ -151,6 +153,7 @@ func NewStream(m map[string]interface{}, unprotected []string) (grpc.StreamServe
interceptor := func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
ctx := ss.Context()
log := appctx.GetLogger(ctx)
log.Info().Msgf("GRPC stream interceptor %s, %+v", info.FullMethod, unprotected)

if utils.Skip(info.FullMethod, unprotected) {
log.Debug().Str("method", info.FullMethod).Msg("skipping auth")
Expand All @@ -159,7 +162,7 @@ func NewStream(m map[string]interface{}, unprotected []string) (grpc.StreamServe
// to decide the storage provider.
tkn, ok := token.ContextGetToken(ctx)
if ok {
u, err := tokenManager.DismantleToken(ctx, tkn)
u, err := tokenManager.DismantleToken(ctx, tkn, ss)
if err == nil {
ctx = user.ContextSetUser(ctx, u)
ss = newWrappedServerStream(ctx, ss)
Expand All @@ -177,7 +180,7 @@ func NewStream(m map[string]interface{}, unprotected []string) (grpc.StreamServe
}

// validate the token
claims, err := tokenManager.DismantleToken(ctx, tkn)
claims, err := tokenManager.DismantleToken(ctx, tkn, ss)
if err != nil {
log.Warn().Msg("access token invalid")
return status.Errorf(codes.Unauthenticated, "auth: core access token is invalid")
Expand Down
5 changes: 5 additions & 0 deletions internal/grpc/services/appprovider/appprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/cs3org/reva/pkg/app"
"github.com/cs3org/reva/pkg/app/provider/demo"
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/rgrpc"
"github.com/cs3org/reva/pkg/rgrpc/status"
"github.com/cs3org/reva/pkg/rhttp"
Expand Down Expand Up @@ -274,3 +275,7 @@ func (s *service) OpenFileInAppProvider(ctx context.Context, req *providerpb.Ope
AppProviderUrl: appProviderURL,
}, nil
}

func (s *service) OpenInApp(ctx context.Context, req *providerpb.OpenInAppRequest) (*providerpb.OpenInAppResponse, error) {
return nil, errtypes.NotSupported("Unimplemented")
}
7 changes: 4 additions & 3 deletions internal/grpc/services/authprovider/authprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,14 @@ func (s *service) Authenticate(ctx context.Context, req *provider.AuthenticateRe
username := req.ClientId
password := req.ClientSecret

u, err := s.authmgr.Authenticate(ctx, username, password)
u, scope, err := s.authmgr.Authenticate(ctx, username, password)
switch v := err.(type) {
case nil:
log.Info().Msgf("user %s authenticated", u.String())
return &provider.AuthenticateResponse{
Status: status.NewOK(ctx),
User: u,
Status: status.NewOK(ctx),
User: u,
TokenScope: scope,
}, nil
case errtypes.InvalidCredentials:
return &provider.AuthenticateResponse{
Expand Down
8 changes: 3 additions & 5 deletions internal/grpc/services/gateway/authprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,7 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest
}, nil
}

user := res.User

token, err := s.tokenmgr.MintToken(ctx, user)
token, err := s.tokenmgr.MintToken(ctx, res.User, res.TokenScope)
if err != nil {
err = errors.Wrap(err, "authsvc: error in MintToken")
res := &gateway.AuthenticateResponse{
Expand All @@ -116,7 +114,7 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest
// we need to pass the token to authenticate the CreateHome request.
// TODO(labkode): appending to existing context will not pass the token.
ctx = tokenpkg.ContextSetToken(ctx, token)
ctx = userpkg.ContextSetUser(ctx, user)
ctx = userpkg.ContextSetUser(ctx, res.User)
ctx = metadata.AppendToOutgoingContext(ctx, tokenpkg.TokenHeader, token) // TODO(jfd): hardcoded metadata key. use PerRPCCredentials?

// create home directory
Expand Down Expand Up @@ -145,7 +143,7 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest
}

func (s *svc) WhoAmI(ctx context.Context, req *gateway.WhoAmIRequest) (*gateway.WhoAmIResponse, error) {
u, err := s.tokenmgr.DismantleToken(ctx, req.Token)
u, err := s.tokenmgr.DismantleToken(ctx, req.Token, nil)
if err != nil {
err = errors.Wrap(err, "gateway: error getting user from token")
return &gateway.WhoAmIResponse{
Expand Down
3 changes: 2 additions & 1 deletion internal/http/interceptors/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ func New(m map[string]interface{}, unprotected []string) (global.Middleware, err
}

log := appctx.GetLogger(ctx)
log.Info().Msgf("HTTP interceptor %s, %+v", r.URL.Path, unprotected)

// skip auth for urls set in the config.
// TODO(labkode): maybe use method:url to bypass auth.
Expand Down Expand Up @@ -234,7 +235,7 @@ func New(m map[string]interface{}, unprotected []string) (global.Middleware, err
}

// validate token
claims, err := tokenManager.DismantleToken(r.Context(), tkn)
claims, err := tokenManager.DismantleToken(r.Context(), tkn, r.URL.Path)
if err != nil {
log.Error().Err(err).Msg("error dismantling token")
w.WriteHeader(http.StatusUnauthorized)
Expand Down
3 changes: 2 additions & 1 deletion pkg/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ import (
"context"
"net/http"

authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
registry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
)

// Manager is the interface to implement to authenticate users
type Manager interface {
Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, error)
Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error)
}

// Credentials contains the auth type, client id and secret.
Expand Down
29 changes: 26 additions & 3 deletions pkg/auth/manager/demo/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ package demo

import (
"context"
"encoding/json"

authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/pkg/auth"
"github.com/cs3org/reva/pkg/auth/manager/registry"
"github.com/cs3org/reva/pkg/errtypes"
Expand All @@ -48,13 +52,32 @@ func New(m map[string]interface{}) (auth.Manager, error) {
return &manager{credentials: creds}, nil
}

func (m *manager) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, error) {
func (m *manager) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) {
ref := &provider.Reference{
Spec: &provider.Reference_Path{
Path: "/",
},
}
val, err := json.Marshal(ref)
if err != nil {
return nil, nil, err
}
scope := map[string]*authpb.Scope{
"user": &authpb.Scope{
Resource: &types.OpaqueEntry{
Decoder: "json",
Value: val,
},
Role: authpb.Role_ROLE_OWNER,
},
}

if c, ok := m.credentials[clientID]; ok {
if c.Secret == clientSecret {
return c.User, nil
return c.User, scope, nil
}
}
return nil, errtypes.InvalidCredentials(clientID)
return nil, nil, errtypes.InvalidCredentials(clientID)
}

func getCredentials() map[string]Credentials {
Expand Down
4 changes: 2 additions & 2 deletions pkg/auth/manager/demo/demo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ func TestUserManager(t *testing.T) {
manager, _ := New(nil)

// Authenticate - positive test
_, err := manager.Authenticate(ctx, "einstein", "relativity")
_, _, err := manager.Authenticate(ctx, "einstein", "relativity")
if err != nil {
t.Fatalf("error while authenticate with correct credentials")
}

// Authenticate - negative test
_, err = manager.Authenticate(ctx, "einstein", "NotARealPassword")
_, _, err = manager.Authenticate(ctx, "einstein", "NotARealPassword")
if err == nil {
t.Fatalf("no error (but we expected one) while authenticate with bad credentials")
}
Expand Down
28 changes: 26 additions & 2 deletions pkg/auth/manager/impersonator/impersonator.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ package impersonator

import (
"context"
"encoding/json"
"strings"

authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/pkg/auth"
"github.com/cs3org/reva/pkg/auth/manager/registry"
)
Expand All @@ -38,7 +42,7 @@ func New(c map[string]interface{}) (auth.Manager, error) {
return &mgr{}, nil
}

func (m *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, error) {
func (m *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) {
// allow passing in uid as <opaqueid>@<idp>
at := strings.LastIndex(clientID, "@")
uid := &user.UserId{}
Expand All @@ -48,8 +52,28 @@ func (m *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (
uid.OpaqueId = clientID[:at]
uid.Idp = clientID[at+1:]
}

ref := &provider.Reference{
Spec: &provider.Reference_Path{
Path: "/",
},
}
val, err := json.Marshal(ref)
if err != nil {
return nil, nil, err
}
scope := map[string]*authpb.Scope{
"user": &authpb.Scope{
Resource: &types.OpaqueEntry{
Decoder: "json",
Value: val,
},
Role: authpb.Role_ROLE_OWNER,
},
}

return &user.User{
Id: uid,
// not much else to provide
}, nil
}, scope, nil
}
4 changes: 2 additions & 2 deletions pkg/auth/manager/impersonator/impersonator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
func TestImpersonator(t *testing.T) {
ctx := context.Background()
i, _ := New(nil)
u, err := i.Authenticate(ctx, "admin", "pwd")
u, _, err := i.Authenticate(ctx, "admin", "pwd")
if err != nil {
t.Fatal(err)
}
Expand All @@ -39,7 +39,7 @@ func TestImpersonator(t *testing.T) {
}

ctx = context.Background()
u, err = i.Authenticate(ctx, "opaqueid@idp", "pwd")
u, _, err = i.Authenticate(ctx, "opaqueid@idp", "pwd")
if err != nil {
t.Fatal(err)
}
Expand Down
27 changes: 24 additions & 3 deletions pkg/auth/manager/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import (
"encoding/json"
"io/ioutil"

authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/pkg/auth"
"github.com/cs3org/reva/pkg/auth/manager/registry"
Expand Down Expand Up @@ -101,7 +103,26 @@ func New(m map[string]interface{}) (auth.Manager, error) {
return manager, nil
}

func (m *manager) Authenticate(ctx context.Context, username string, secret string) (*user.User, error) {
func (m *manager) Authenticate(ctx context.Context, username string, secret string) (*user.User, map[string]*authpb.Scope, error) {
ref := &provider.Reference{
Spec: &provider.Reference_Path{
Path: "/",
},
}
val, err := json.Marshal(ref)
if err != nil {
return nil, nil, err
}
scope := map[string]*authpb.Scope{
"user": &authpb.Scope{
Resource: &typespb.OpaqueEntry{
Decoder: "json",
Value: val,
},
Role: authpb.Role_ROLE_OWNER,
},
}

if c, ok := m.credentials[username]; ok {
if c.Secret == secret {
return &user.User{
Expand All @@ -113,8 +134,8 @@ func (m *manager) Authenticate(ctx context.Context, username string, secret stri
Groups: c.Groups,
Opaque: c.Opaque,
// TODO add arbitrary keys as opaque data
}, nil
}, scope, nil
}
}
return nil, errtypes.InvalidCredentials(username)
return nil, nil, errtypes.InvalidCredentials(username)
}
2 changes: 1 addition & 1 deletion pkg/auth/manager/json/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ func TestGetAuthenticatedManager(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
authenticated, err := manager.Authenticate(ctx, tt.username, tt.secret)
authenticated, _, err := manager.Authenticate(ctx, tt.username, tt.secret)
if !tt.expectAuthenticated {
assert.Empty(t, authenticated)
assert.EqualError(t, err, tt.expectedError.message)
Expand Down
Loading

0 comments on commit 65e451a

Please sign in to comment.