add config option to set default quota per role

This commit is contained in:
David Christofas
2023-02-21 15:26:21 +01:00
parent 30faae2ae4
commit 67549b4ebd
8 changed files with 99 additions and 18 deletions

View File

@@ -0,0 +1,12 @@
Enhancement: Added option to configure default quota per role
Admins can assign default quotas to users with certain roles by adding the following config to the `proxy.yaml`.
E.g.:
```
role_quotas:
d7beeea8-8ff4-406b-8fb6-ab2dd81e6b11: 2300000
```
It maps a role ID to the quota in bytes.
https://github.com/owncloud/ocis/pull/5616

3
go.mod
View File

@@ -11,7 +11,7 @@ require (
github.com/blevesearch/bleve/v2 v2.3.5
github.com/coreos/go-oidc/v3 v3.4.0
github.com/cs3org/go-cs3apis v0.0.0-20221012090518-ef2996678965
github.com/cs3org/reva/v2 v2.12.1-0.20230214085134-ec27f5f8feb3
github.com/cs3org/reva/v2 v2.12.1-0.20230221142843-06fcf4e500bf
github.com/disintegration/imaging v1.6.2
github.com/ggwhite/go-masker v1.0.9
github.com/go-chi/chi/v5 v5.0.7
@@ -251,6 +251,7 @@ require (
github.com/prometheus/procfs v0.8.0 // indirect
github.com/prometheus/statsd_exporter v0.22.8 // indirect
github.com/rivo/uniseg v0.4.2 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/rs/cors v1.8.2 // indirect
github.com/rs/xid v1.4.0 // indirect
github.com/russellhaering/goxmldsig v1.1.1 // indirect

4
go.sum
View File

@@ -343,8 +343,8 @@ github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3p
github.com/crewjam/saml v0.4.6/go.mod h1:ZBOXnNPFzB3CgOkRm7Nd6IVdkG+l/wF+0ZXLqD96t1A=
github.com/crewjam/saml v0.4.9 h1:X2jDv4dv3IvfT9t+RhADavzNFAcq3fVxzTCIH3G605U=
github.com/crewjam/saml v0.4.9/go.mod h1:9Zh6dWPtB3MSzTRt8fIFH60Z351QQ+s7hCU3J/tTlA4=
github.com/cs3org/reva/v2 v2.12.1-0.20230214085134-ec27f5f8feb3 h1:KaFl1ZfjjKSlDsq/zvhBV9f+mXYqnLdK5IhAaZBoXDo=
github.com/cs3org/reva/v2 v2.12.1-0.20230214085134-ec27f5f8feb3/go.mod h1:u73Df9JAZsDj43GIjQIb3DO1PLJuPutZXkRqQH0oGXA=
github.com/cs3org/reva/v2 v2.12.1-0.20230221142843-06fcf4e500bf h1:OZwvSm5O4t/jxoVVgJcwbM2BHYv10rGq0KqqVAwTERY=
github.com/cs3org/reva/v2 v2.12.1-0.20230221142843-06fcf4e500bf/go.mod h1:dbaNP2U3nGQA5BHLc5w/hqviq7b0F4eygNwC38jeaiU=
github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI=
github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=

View File

@@ -13,6 +13,19 @@ The following request authentication schemes are implemented:
- Signed URL
- Public Share Token
## Automatic quota assignments
It is possible to automatically assign a specific quota amount to new users depending on their role.
To do this you need to add the following config snippet to the proxy.yaml config file.
```yaml
role_quotas:
<role ID1>: <quota1>
<role ID2>: <quota2>
```
There you need to configure the mapping between the roles by their ID and the quota in Bytes.
## Recommendations for Production Deployments
In a production deployment, you want to have basic authentication (`PROXY_ENABLE_BASIC_AUTH`) disabled which is the default state. You also want to setup a firewall to only allow requests to the proxy service or the reverse proxy if you have one. Requests to the other services should be blocked by the firewall.

View File

@@ -249,6 +249,7 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config)
middleware.Logger(logger),
middleware.TokenManagerConfig(*cfg.TokenManager),
middleware.RevaGatewayClient(revaClient),
middleware.RoleQuotas(cfg.RoleQuotas),
),
)
}

View File

@@ -21,20 +21,21 @@ type Config struct {
Reva *shared.Reva `yaml:"reva"`
GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
Policies []Policy `yaml:"policies"`
OIDC OIDC `yaml:"oidc"`
TokenManager *TokenManager `mask:"struct" yaml:"token_manager"`
PolicySelector *PolicySelector `yaml:"policy_selector"`
PreSignedURL PreSignedURL `yaml:"pre_signed_url"`
AccountBackend string `yaml:"account_backend" env:"PROXY_ACCOUNT_BACKEND_TYPE" desc:"Account backend the PROXY service should use. Currently only 'cs3' is possible here."`
UserOIDCClaim string `yaml:"user_oidc_claim" env:"PROXY_USER_OIDC_CLAIM" desc:"The name of an OpenID Connect claim that is used for resolving users with the account backend. The value of the claim must hold a per user unique, stable and non re-assignable identifier. The availability of claims depends on your Identity Provider. There are common claims available for most Identity providers like 'email' or 'preferred_user' but you can also add your own claim."`
UserCS3Claim string `yaml:"user_cs3_claim" env:"PROXY_USER_CS3_CLAIM" desc:"The name of a CS3 user attribute (claim) that should be mapped to the 'user_oidc_claim'. Supported values are 'username', 'mail' and 'userid'."`
MachineAuthAPIKey string `mask:"password" yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;PROXY_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary to access resources from other services."`
AutoprovisionAccounts bool `yaml:"auto_provision_accounts" env:"PROXY_AUTOPROVISION_ACCOUNTS" desc:"Set this to 'true' to automatically provision users that do not yet exist in the users service on-demand upon first sign-in. To use this a write-enabled libregraph user backend needs to be setup an running."`
EnableBasicAuth bool `yaml:"enable_basic_auth" env:"PROXY_ENABLE_BASIC_AUTH" desc:"Set this to true to enable 'basic authentication' (username/password)."`
InsecureBackends bool `yaml:"insecure_backends" env:"PROXY_INSECURE_BACKENDS" desc:"Disable TLS certificate validation for all HTTP backend connections."`
BackendHTTPSCACert string `yaml:"backend_https_cacert" env:"PROXY_HTTPS_CACERT" desc:"Path/File for the root CA certificate used to validate the servers TLS certificate for https enabled backend services."`
AuthMiddleware AuthMiddleware `yaml:"auth_middleware"`
RoleQuotas map[string]uint64 `yaml:"role_quotas"`
Policies []Policy `yaml:"policies"`
OIDC OIDC `yaml:"oidc"`
TokenManager *TokenManager `mask:"struct" yaml:"token_manager"`
PolicySelector *PolicySelector `yaml:"policy_selector"`
PreSignedURL PreSignedURL `yaml:"pre_signed_url"`
AccountBackend string `yaml:"account_backend" env:"PROXY_ACCOUNT_BACKEND_TYPE" desc:"Account backend the PROXY service should use. Currently only 'cs3' is possible here."`
UserOIDCClaim string `yaml:"user_oidc_claim" env:"PROXY_USER_OIDC_CLAIM" desc:"The name of an OpenID Connect claim that is used for resolving users with the account backend. The value of the claim must hold a per user unique, stable and non re-assignable identifier. The availability of claims depends on your Identity Provider. There are common claims available for most Identity providers like 'email' or 'preferred_user' but you can also add your own claim."`
UserCS3Claim string `yaml:"user_cs3_claim" env:"PROXY_USER_CS3_CLAIM" desc:"The name of a CS3 user attribute (claim) that should be mapped to the 'user_oidc_claim'. Supported values are 'username', 'mail' and 'userid'."`
MachineAuthAPIKey string `mask:"password" yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;PROXY_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary to access resources from other services."`
AutoprovisionAccounts bool `yaml:"auto_provision_accounts" env:"PROXY_AUTOPROVISION_ACCOUNTS" desc:"Set this to 'true' to automatically provision users that do not yet exist in the users service on-demand upon first sign-in. To use this a write-enabled libregraph user backend needs to be setup an running."`
EnableBasicAuth bool `yaml:"enable_basic_auth" env:"PROXY_ENABLE_BASIC_AUTH" desc:"Set this to true to enable 'basic authentication' (username/password)."`
InsecureBackends bool `yaml:"insecure_backends" env:"PROXY_INSECURE_BACKENDS" desc:"Disable TLS certificate validation for all HTTP backend connections."`
BackendHTTPSCACert string `yaml:"backend_https_cacert" env:"PROXY_HTTPS_CACERT" desc:"Path/File for the root CA certificate used to validate the servers TLS certificate for https enabled backend services."`
AuthMiddleware AuthMiddleware `yaml:"auth_middleware"`
Context context.Context `yaml:"-" json:"-"`
}

View File

@@ -2,13 +2,17 @@ package middleware
import (
"net/http"
"strconv"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
revactx "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode"
"google.golang.org/grpc/metadata"
)
@@ -22,6 +26,7 @@ func CreateHome(optionSetters ...Option) func(next http.Handler) http.Handler {
next: next,
logger: logger,
revaGatewayClient: options.RevaGatewayClient,
roleQuotas: options.RoleQuotas,
}
}
}
@@ -30,6 +35,7 @@ type createHome struct {
next http.Handler
logger log.Logger
revaGatewayClient gateway.GatewayAPIClient
roleQuotas map[string]uint64
}
func (m createHome) ServeHTTP(w http.ResponseWriter, req *http.Request) {
@@ -45,6 +51,19 @@ func (m createHome) ServeHTTP(w http.ResponseWriter, req *http.Request) {
ctx := metadata.AppendToOutgoingContext(req.Context(), revactx.TokenHeader, token)
createHomeReq := &provider.CreateHomeRequest{}
u, ok := revactx.ContextGetUser(ctx)
if ok {
roleIDs, err := m.getUserRoles(u)
if err != nil {
m.logger.Error().Err(err).Str("userid", u.Id.OpaqueId).Msg("failed to get roles for user")
errorcode.GeneralException.Render(w, req, http.StatusInternalServerError, "Unauthorized")
return
}
if limit, hasLimit := m.checkRoleQuotaLimit(roleIDs); hasLimit {
createHomeReq.Opaque = utils.AppendPlainToOpaque(nil, "quota", strconv.FormatUint(limit, 10))
}
}
createHomeRes, err := m.revaGatewayClient.CreateHome(ctx, createHomeReq)
if err != nil {
@@ -62,3 +81,27 @@ func (m createHome) ServeHTTP(w http.ResponseWriter, req *http.Request) {
func (m createHome) shouldServe(req *http.Request) bool {
return req.Header.Get("x-access-token") != ""
}
func (m createHome) getUserRoles(user *userv1beta1.User) ([]string, error) {
var roleIDs []string
if err := utils.ReadJSONFromOpaque(user.Opaque, "roles", &roleIDs); err != nil {
return nil, err
}
tmp := make(map[string]struct{})
for _, id := range roleIDs {
tmp[id] = struct{}{}
}
dedup := make([]string, 0, len(tmp))
for k := range tmp {
dedup = append(dedup, k)
}
return dedup, nil
}
func (m createHome) checkRoleQuotaLimit(roleIDs []string) (uint64, bool) {
id := roleIDs[0] // At the moment a user can only have one role.
quota, ok := m.roleQuotas[id]
return quota, ok
}

View File

@@ -60,6 +60,9 @@ type Options struct {
AccessTokenVerifyMethod string
// JWKS sets the options for fetching the JWKS from the IDP
JWKS config.JWKS
// RoleQuotas hold userid:quota mappings. These will be used when provisioning new users.
// The users will get as much quota as is set for their role.
RoleQuotas map[string]uint64
}
// newOptions initializes the available default options.
@@ -206,9 +209,16 @@ func AccessTokenVerifyMethod(method string) Option {
}
}
// JWKS sets the options for fetching the JWKS from the IDP
// JWKSOptions sets the options for fetching the JWKS from the IDP
func JWKSOptions(jo config.JWKS) Option {
return func(o *Options) {
o.JWKS = jo
}
}
// RoleQuotas sets the role quota mapping setting
func RoleQuotas(roleQuotas map[string]uint64) Option {
return func(o *Options) {
o.RoleQuotas = roleQuotas
}
}