From 8d634db3c9c970a617823e3ba14a2153dac480c6 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Thu, 1 Jun 2023 13:53:49 +0200 Subject: [PATCH 1/6] add service-accounts auth manager Signed-off-by: jkoberg --- changelog/unreleased/service-accounts.md | 5 ++ pkg/auth/manager/loader/loader.go | 1 + pkg/auth/manager/registry/registry.go | 4 +- .../serviceaccounts/serviceaccounts.go | 85 +++++++++++++++++++ pkg/utils/grpc.go | 21 +++++ 5 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 changelog/unreleased/service-accounts.md create mode 100644 pkg/auth/manager/serviceaccounts/serviceaccounts.go diff --git a/changelog/unreleased/service-accounts.md b/changelog/unreleased/service-accounts.md new file mode 100644 index 0000000000..084ccf32c7 --- /dev/null +++ b/changelog/unreleased/service-accounts.md @@ -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 diff --git a/pkg/auth/manager/loader/loader.go b/pkg/auth/manager/loader/loader.go index 694cd98c29..9fcba05541 100644 --- a/pkg/auth/manager/loader/loader.go +++ b/pkg/auth/manager/loader/loader.go @@ -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 ) diff --git a/pkg/auth/manager/registry/registry.go b/pkg/auth/manager/registry/registry.go index aea682f79d..8d92a13e19 100644 --- a/pkg/auth/manager/registry/registry.go +++ b/pkg/auth/manager/registry/registry.go @@ -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. diff --git a/pkg/auth/manager/serviceaccounts/serviceaccounts.go b/pkg/auth/manager/serviceaccounts/serviceaccounts.go new file mode 100644 index 0000000000..43b09b7d18 --- /dev/null +++ b/pkg/auth/manager/serviceaccounts/serviceaccounts.go @@ -0,0 +1,85 @@ +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 { + // TODO: hash secrets + 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}, + }, scope, nil +} + +type inmemAuthenticator struct { + m map[string]string +} + +func (a *inmemAuthenticator) Authenticate(userID string, secret string) error { + // TODO: hash secrets + if a.m[userID] == secret { + return nil + } + return errors.New("secrets do not match") +} diff --git a/pkg/utils/grpc.go b/pkg/utils/grpc.go index 3945d32e8c..2088aa9bbb 100644 --- a/pkg/utils/grpc.go +++ b/pkg/utils/grpc.go @@ -37,6 +37,9 @@ func GetUser(userID *user.UserId, gwc gateway.GatewayAPIClient, machineAuthAPIKe // ImpersonateUser impersonates the given user func ImpersonateUser(usr *user.User, gwc gateway.GatewayAPIClient, machineAuthAPIKey string) (context.Context, error) { + if true { + return ImpersonateServiceUser("service-user-id", gwc, "secret-string") + } ctx := revactx.ContextSetUser(context.Background(), usr) authRes, err := gwc.Authenticate(ctx, &gateway.AuthenticateRequest{ Type: "machine", @@ -52,3 +55,21 @@ func ImpersonateUser(usr *user.User, gwc gateway.GatewayAPIClient, machineAuthAP return metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, authRes.Token), nil } + +// ImpersonateServiceUser impersonates the given user +func ImpersonateServiceUser(userID string, gwc gateway.GatewayAPIClient, machineAuthAPIKey string) (context.Context, error) { + ctx := context.Background() + authRes, err := gwc.Authenticate(ctx, &gateway.AuthenticateRequest{ + Type: "serviceaccounts", + ClientId: userID, + ClientSecret: machineAuthAPIKey, + }) + 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 metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, authRes.Token), nil +} From d22567c91250d8abe9fc153a83d295c4f1503c65 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Wed, 9 Aug 2023 14:03:27 +0200 Subject: [PATCH 2/6] fix the heisenbug Signed-off-by: jkoberg --- pkg/storage/registry/spaces/spaces.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/storage/registry/spaces/spaces.go b/pkg/storage/registry/spaces/spaces.go index 17f3cea740..aa8385f662 100644 --- a/pkg/storage/registry/spaces/spaces.go +++ b/pkg/storage/registry/spaces/spaces.go @@ -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 From 3c8254acb921b2a61ca06c3906b17b550e461b89 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Thu, 10 Aug 2023 13:17:08 +0200 Subject: [PATCH 3/6] check service user in decomposedfs Signed-off-by: jkoberg --- pkg/storage/utils/decomposedfs/node/node.go | 4 ++++ pkg/utils/grpc.go | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/storage/utils/decomposedfs/node/node.go b/pkg/storage/utils/decomposedfs/node/node.go index a3e31ee90b..ae73eac27d 100644 --- a/pkg/storage/utils/decomposedfs/node/node.go +++ b/pkg/storage/utils/decomposedfs/node/node.go @@ -974,6 +974,10 @@ func (n *Node) ReadUserPermissions(ctx context.Context, u *userpb.User) (ap prov return OwnerPermissions(), false, nil } + if u.Id.GetOpaqueId() == "service-user-id" { + return OwnerPermissions(), false, nil + } + ap = provider.ResourcePermissions{} // for an efficient group lookup convert the list of groups to a map diff --git a/pkg/utils/grpc.go b/pkg/utils/grpc.go index 2088aa9bbb..40b9320d98 100644 --- a/pkg/utils/grpc.go +++ b/pkg/utils/grpc.go @@ -36,6 +36,7 @@ func GetUser(userID *user.UserId, gwc gateway.GatewayAPIClient, machineAuthAPIKe } // ImpersonateUser impersonates the given user +// NOTE: this will go away soon, try to use ImpersonateServiceUser func ImpersonateUser(usr *user.User, gwc gateway.GatewayAPIClient, machineAuthAPIKey string) (context.Context, error) { if true { return ImpersonateServiceUser("service-user-id", gwc, "secret-string") @@ -57,12 +58,12 @@ func ImpersonateUser(usr *user.User, gwc gateway.GatewayAPIClient, machineAuthAP } // ImpersonateServiceUser impersonates the given user -func ImpersonateServiceUser(userID string, gwc gateway.GatewayAPIClient, machineAuthAPIKey string) (context.Context, error) { +func ImpersonateServiceUser(serviceUserID string, gwc gateway.GatewayAPIClient, serviceUserSecret string) (context.Context, error) { ctx := context.Background() authRes, err := gwc.Authenticate(ctx, &gateway.AuthenticateRequest{ Type: "serviceaccounts", - ClientId: userID, - ClientSecret: machineAuthAPIKey, + ClientId: serviceUserID, + ClientSecret: serviceUserSecret, }) if err != nil { return nil, err From ca1b99440567ae39553b6f28bb367446c4c39d5b Mon Sep 17 00:00:00 2001 From: jkoberg Date: Wed, 16 Aug 2023 11:11:49 +0200 Subject: [PATCH 4/6] clean utils/grpc Signed-off-by: jkoberg --- pkg/utils/grpc.go | 35 +---------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/pkg/utils/grpc.go b/pkg/utils/grpc.go index 40b9320d98..06ce9753f8 100644 --- a/pkg/utils/grpc.go +++ b/pkg/utils/grpc.go @@ -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 @@ -35,28 +24,6 @@ func GetUser(userID *user.UserId, gwc gateway.GatewayAPIClient, machineAuthAPIKe return getUserResponse.GetUser(), nil } -// ImpersonateUser impersonates the given user -// NOTE: this will go away soon, try to use ImpersonateServiceUser -func ImpersonateUser(usr *user.User, gwc gateway.GatewayAPIClient, machineAuthAPIKey string) (context.Context, error) { - if true { - return ImpersonateServiceUser("service-user-id", gwc, "secret-string") - } - ctx := revactx.ContextSetUser(context.Background(), usr) - authRes, err := gwc.Authenticate(ctx, &gateway.AuthenticateRequest{ - Type: "machine", - ClientId: "userid:" + usr.GetId().GetOpaqueId(), - ClientSecret: machineAuthAPIKey, - }) - 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 metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, authRes.Token), nil -} - // ImpersonateServiceUser impersonates the given user func ImpersonateServiceUser(serviceUserID string, gwc gateway.GatewayAPIClient, serviceUserSecret string) (context.Context, error) { ctx := context.Background() From ae3b406540d169f3c7da6e867d67dfdd55b82f53 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Thu, 17 Aug 2023 11:21:53 +0200 Subject: [PATCH 5/6] define permissions for service accounts Signed-off-by: jkoberg --- .../serviceaccounts/serviceaccounts.go | 5 ++++- pkg/storage/utils/decomposedfs/node/node.go | 4 ---- .../utils/decomposedfs/node/permissions.go | 20 +++++++++++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/pkg/auth/manager/serviceaccounts/serviceaccounts.go b/pkg/auth/manager/serviceaccounts/serviceaccounts.go index 43b09b7d18..48e16cf09d 100644 --- a/pkg/auth/manager/serviceaccounts/serviceaccounts.go +++ b/pkg/auth/manager/serviceaccounts/serviceaccounts.go @@ -68,7 +68,10 @@ func (m *manager) Authenticate(ctx context.Context, userID string, secret string } return &userpb.User{ // TODO: more details for service users? - Id: &userpb.UserId{OpaqueId: userID}, + Id: &userpb.UserId{ + OpaqueId: userID, + Type: userpb.UserType_USER_TYPE_SERVICE, + }, }, scope, nil } diff --git a/pkg/storage/utils/decomposedfs/node/node.go b/pkg/storage/utils/decomposedfs/node/node.go index ae73eac27d..a3e31ee90b 100644 --- a/pkg/storage/utils/decomposedfs/node/node.go +++ b/pkg/storage/utils/decomposedfs/node/node.go @@ -974,10 +974,6 @@ func (n *Node) ReadUserPermissions(ctx context.Context, u *userpb.User) (ap prov return OwnerPermissions(), false, nil } - if u.Id.GetOpaqueId() == "service-user-id" { - return OwnerPermissions(), false, nil - } - ap = provider.ResourcePermissions{} // for an efficient group lookup convert the list of groups to a map diff --git a/pkg/storage/utils/decomposedfs/node/permissions.go b/pkg/storage/utils/decomposedfs/node/permissions.go index 1e5017241b..84814a1641 100644 --- a/pkg/storage/utils/decomposedfs/node/permissions.go +++ b/pkg/storage/utils/decomposedfs/node/permissions.go @@ -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" @@ -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 @@ -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 From c2f5d68261cee023c16ffb6e9ed34e907895c762 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Thu, 24 Aug 2023 13:04:52 +0200 Subject: [PATCH 6/6] improve service user authentication Signed-off-by: jkoberg --- pkg/auth/manager/serviceaccounts/serviceaccounts.go | 6 ++++-- pkg/utils/grpc.go | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/auth/manager/serviceaccounts/serviceaccounts.go b/pkg/auth/manager/serviceaccounts/serviceaccounts.go index 48e16cf09d..dad1dcf72a 100644 --- a/pkg/auth/manager/serviceaccounts/serviceaccounts.go +++ b/pkg/auth/manager/serviceaccounts/serviceaccounts.go @@ -39,7 +39,6 @@ func (m *manager) Configure(config map[string]interface{}) error { // only inmem authenticator for now a := &inmemAuthenticator{make(map[string]string)} for _, s := range c.ServiceUsers { - // TODO: hash secrets a.m[s.ID] = s.Secret } m.authenticate = a.Authenticate @@ -71,6 +70,7 @@ func (m *manager) Authenticate(ctx context.Context, userID string, secret string Id: &userpb.UserId{ OpaqueId: userID, Type: userpb.UserType_USER_TYPE_SERVICE, + Idp: "none", }, }, scope, nil } @@ -80,7 +80,9 @@ type inmemAuthenticator struct { } func (a *inmemAuthenticator) Authenticate(userID string, secret string) error { - // TODO: hash secrets + if secret == "" || a.m[userID] == "" { + return errors.New("unknown user") + } if a.m[userID] == secret { return nil } diff --git a/pkg/utils/grpc.go b/pkg/utils/grpc.go index 06ce9753f8..90547443fa 100644 --- a/pkg/utils/grpc.go +++ b/pkg/utils/grpc.go @@ -24,8 +24,8 @@ func GetUser(userID *user.UserId, gwc gateway.GatewayAPIClient) (*user.User, err return getUserResponse.GetUser(), nil } -// ImpersonateServiceUser impersonates the given user -func ImpersonateServiceUser(serviceUserID string, gwc gateway.GatewayAPIClient, serviceUserSecret string) (context.Context, error) { +// 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: "serviceaccounts", @@ -36,7 +36,7 @@ func ImpersonateServiceUser(serviceUserID string, gwc gateway.GatewayAPIClient, 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