Skip to content

Commit

Permalink
Merge pull request #3926 from kobergj/ServiceAccounts
Browse files Browse the repository at this point in the history
Service Accounts
  • Loading branch information
kobergj authored Aug 29, 2023
2 parents 594d4e1 + c2f5d68 commit 8ba013d
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 21 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/service-accounts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Service Accounts

Makes reva ready for service accounts by introducing an serviceaccounts auth manager

https://github.com/cs3org/reva/pull/3926
1 change: 1 addition & 0 deletions pkg/auth/manager/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ import (
_ "github.com/cs3org/reva/v2/pkg/auth/manager/oidc"
_ "github.com/cs3org/reva/v2/pkg/auth/manager/owncloudsql"
_ "github.com/cs3org/reva/v2/pkg/auth/manager/publicshares"
_ "github.com/cs3org/reva/v2/pkg/auth/manager/serviceaccounts"
// Add your own here
)
4 changes: 3 additions & 1 deletion pkg/auth/manager/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@

package registry

import "github.com/cs3org/reva/v2/pkg/auth"
import (
"github.com/cs3org/reva/v2/pkg/auth"
)

// NewFunc is the function that auth implementations
// should register to at init time.
Expand Down
90 changes: 90 additions & 0 deletions pkg/auth/manager/serviceaccounts/serviceaccounts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package serviceaccounts

import (
"context"

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

"github.com/cs3org/reva/v2/pkg/auth"
"github.com/cs3org/reva/v2/pkg/auth/manager/registry"
"github.com/cs3org/reva/v2/pkg/auth/scope"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)

type conf struct {
ServiceUsers []serviceuser `mapstructure:"service_accounts"`
}

type serviceuser struct {
ID string `mapstructure:"id"`
Secret string `mapstructure:"secret"`
}

type manager struct {
authenticate func(userID, secret string) error
}

func init() {
registry.Register("serviceaccounts", New)
}

// Configure parses the map conf
func (m *manager) Configure(config map[string]interface{}) error {
c := &conf{}
if err := mapstructure.Decode(config, c); err != nil {
return errors.Wrap(err, "error decoding conf")
}
// only inmem authenticator for now
a := &inmemAuthenticator{make(map[string]string)}
for _, s := range c.ServiceUsers {
a.m[s.ID] = s.Secret
}
m.authenticate = a.Authenticate
return nil
}

// New creates a new manager for the 'service' authentication
func New(conf map[string]interface{}) (auth.Manager, error) {
m := &manager{}
err := m.Configure(conf)
if err != nil {
return nil, err
}

return m, nil
}

// Authenticate authenticates the service account
func (m *manager) Authenticate(ctx context.Context, userID string, secret string) (*userpb.User, map[string]*authpb.Scope, error) {
if err := m.authenticate(userID, secret); err != nil {
return nil, nil, err
}
scope, err := scope.AddOwnerScope(nil)
if err != nil {
return nil, nil, err
}
return &userpb.User{
// TODO: more details for service users?
Id: &userpb.UserId{
OpaqueId: userID,
Type: userpb.UserType_USER_TYPE_SERVICE,
Idp: "none",
},
}, scope, nil
}

type inmemAuthenticator struct {
m map[string]string
}

func (a *inmemAuthenticator) Authenticate(userID string, secret string) error {
if secret == "" || a.m[userID] == "" {
return errors.New("unknown user")
}
if a.m[userID] == secret {
return nil
}
return errors.New("secrets do not match")
}
2 changes: 1 addition & 1 deletion pkg/storage/registry/spaces/spaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ func (r *registry) findProvidersForResource(ctx context.Context, id string, find
},
})
}
spaces, err := r.findStorageSpaceOnProvider(ctx, address, filters, false)
spaces, err := r.findStorageSpaceOnProvider(ctx, address, filters, unrestricted)
if err != nil {
appctx.GetLogger(ctx).Debug().Err(err).Interface("provider", provider).Msg("findStorageSpaceOnProvider by id failed, continuing")
continue
Expand Down
20 changes: 20 additions & 0 deletions pkg/storage/utils/decomposedfs/node/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"context"
"strings"

userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/pkg/appctx"
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
Expand Down Expand Up @@ -84,6 +85,21 @@ func OwnerPermissions() provider.ResourcePermissions {
}
}

// ServiceAccountPermissions defines the permissions for nodes when requested by a service account
func ServiceAccountPermissions() provider.ResourcePermissions {
// TODO: Different permissions for different service accounts
return provider.ResourcePermissions{
Stat: true,
ListContainer: true,
GetPath: true, // for search index
InitiateFileUpload: true, // for personal data export
InitiateFileDownload: true, // for full-text-search
RemoveGrant: true, // for share expiry
ListRecycle: true, // for purge-trash-bin command
PurgeRecycle: true, // for purge-trash-bin command
}
}

// Permissions implements permission checks
type Permissions struct {
lu PathLookup
Expand Down Expand Up @@ -113,6 +129,10 @@ func (p *Permissions) assemblePermissions(ctx context.Context, n *Node, failOnTr
return NoPermissions(), nil
}

if u.GetId().GetType() == userpb.UserType_USER_TYPE_SERVICE {
return ServiceAccountPermissions(), nil
}

// are we reading a revision?
if strings.Contains(n.ID, RevisionIDDelimiter) {
// verify revision key format
Expand Down
27 changes: 8 additions & 19 deletions pkg/utils/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,8 @@ import (
"google.golang.org/grpc/metadata"
)

// Impersonate returns an authenticated reva context and the user it represents
func Impersonate(userID *user.UserId, gwc gateway.GatewayAPIClient, machineAuthAPIKey string) (context.Context, *user.User, error) {
usr, err := GetUser(userID, gwc, machineAuthAPIKey)
if err != nil {
return nil, nil, err
}

ctx, err := ImpersonateUser(usr, gwc, machineAuthAPIKey)
return ctx, usr, err
}

// GetUser gets the specified user
func GetUser(userID *user.UserId, gwc gateway.GatewayAPIClient, machineAuthAPIKey string) (*user.User, error) {
func GetUser(userID *user.UserId, gwc gateway.GatewayAPIClient) (*user.User, error) {
getUserResponse, err := gwc.GetUser(context.Background(), &user.GetUserRequest{UserId: userID})
if err != nil {
return nil, err
Expand All @@ -35,19 +24,19 @@ func GetUser(userID *user.UserId, gwc gateway.GatewayAPIClient, machineAuthAPIKe
return getUserResponse.GetUser(), nil
}

// ImpersonateUser impersonates the given user
func ImpersonateUser(usr *user.User, gwc gateway.GatewayAPIClient, machineAuthAPIKey string) (context.Context, error) {
ctx := revactx.ContextSetUser(context.Background(), usr)
// GetServiceUserContext returns an authenticated context of the given service user
func GetServiceUserContext(serviceUserID string, gwc gateway.GatewayAPIClient, serviceUserSecret string) (context.Context, error) {
ctx := context.Background()
authRes, err := gwc.Authenticate(ctx, &gateway.AuthenticateRequest{
Type: "machine",
ClientId: "userid:" + usr.GetId().GetOpaqueId(),
ClientSecret: machineAuthAPIKey,
Type: "serviceaccounts",
ClientId: serviceUserID,
ClientSecret: serviceUserSecret,
})
if err != nil {
return nil, err
}
if authRes.GetStatus().GetCode() != rpc.Code_CODE_OK {
return nil, fmt.Errorf("error impersonating user: %s", authRes.Status.Message)
return nil, fmt.Errorf("error authenticating service user: %s", authRes.Status.Message)
}

return metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, authRes.Token), nil
Expand Down

0 comments on commit 8ba013d

Please sign in to comment.