ugly working draft

This commit is contained in:
A.Unger
2020-12-02 15:31:17 +01:00
parent 752cd4f626
commit 2910e88ba5
9 changed files with 81 additions and 16 deletions
+13 -2
View File
@@ -3,6 +3,7 @@ package command
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"os"
"os/signal"
@@ -48,8 +49,17 @@ func Server(cfg *config.Config) *cli.Command {
}
cfg.PreSignedURL.AllowedHTTPMethods = ctx.StringSlice("presignedurl-allow-method")
// When running on single binary mode the before hook from the root command won't get called. We manually
// call this before hook from ocis command, so the configuration can be loaded.
cfg.Reva.Middleware.Auth.CredentialsByUserAgent = make(map[string]string, 0)
uaw := ctx.StringSlice("proxy-user-agent-whitelist")
for _, v := range uaw {
parts := strings.Split(v, ":")
if len(parts) != 2 {
return fmt.Errorf("unexpected config value for user-agent whitelist: %v, expected format is userAgent:challenge", v)
}
cfg.Reva.Middleware.Auth.CredentialsByUserAgent[parts[0]] = parts[1]
}
return ParseConfig(ctx, cfg)
},
Action: func(c *cli.Context) error {
@@ -288,6 +298,7 @@ func loadMiddlewares(ctx context.Context, l log.Logger, cfg *config.Config) alic
middleware.EnableBasicAuth(cfg.EnableBasicAuth),
middleware.AccountsClient(accountsClient),
middleware.OIDCIss(cfg.OIDC.Issuer),
middleware.CredentialsByUserAgent(cfg.Reva.Middleware.Auth.CredentialsByUserAgent),
),
middleware.SignedURLAuth(
middleware.Logger(l),
+10 -1
View File
@@ -80,7 +80,16 @@ var (
// Reva defines all available REVA configuration.
type Reva struct {
Address string
Address string
Middleware Middleware
}
type Middleware struct {
Auth Auth
}
type Auth struct {
CredentialsByUserAgent map[string]string
}
// Cache is a TTL cache configuration.
+8 -1
View File
@@ -248,8 +248,15 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag {
EnvVars: []string{"PROXY_ENABLE_BASIC_AUTH"},
Destination: &cfg.EnableBasicAuth,
},
}
// Reva Middlewares Config
&cli.StringSliceFlag{
Name: "proxy-user-agent-whitelist", // TODO naming?
Value: cli.NewStringSlice(""),
Usage: "TODO",
EnvVars: []string{"PROXY_MIDDLEWARE_AUTH_CREDENTIALS_BY_USER_AGENT"},
},
}
}
// ListProxyWithConfig applies the config to the list commands flags.
+2
View File
@@ -42,6 +42,7 @@ func Authentication(opts ...Option) func(next http.Handler) http.Handler {
OIDCIss(options.OIDCIss),
TokenCacheSize(options.UserinfoCacheSize),
TokenCacheTTL(time.Second*time.Duration(options.UserinfoCacheTTL)),
CredentialsByUserAgent(options.CredentialsByUserAgent),
)
basic := BasicAuth(
@@ -49,6 +50,7 @@ func Authentication(opts ...Option) func(next http.Handler) http.Handler {
EnableBasicAuth(options.EnableBasicAuth),
AccountsClient(options.AccountsClient),
OIDCIss(options.OIDCIss),
CredentialsByUserAgent(options.CredentialsByUserAgent),
)
return func(next http.Handler) http.Handler {
+29 -3
View File
@@ -37,21 +37,47 @@ func BasicAuth(optionSetters ...Option) func(next http.Handler) http.Handler {
if !h.isPublicLink(req) {
for i := 0; i < len(ProxyWwwAuthenticate); i++ {
if strings.Contains(req.RequestURI, fmt.Sprintf("/%v/", ProxyWwwAuthenticate[i])) {
for k, v := range options.CredentialsByUserAgent {
if strings.Contains(k, req.UserAgent()) {
w.Header().Del("Www-Authenticate")
w.Header().Add("Www-Authenticate", fmt.Sprintf("%v realm=\"%s\", charset=\"UTF-8\"", strings.Title(v), req.Host))
goto OUT
}
}
w.Header().Add("Www-Authenticate", fmt.Sprintf("%v realm=\"%s\", charset=\"UTF-8\"", "Basic", req.Host))
}
}
}
OUT:
next.ServeHTTP(w, req)
return
}
w.Header().Del("Www-Authenticate")
account, ok := h.getAccount(req)
// touch is a user agent locking guard, when touched changes to true it indicates the User-Agent on the
// request is configured to support only one challenge, it it remains untouched, there are no considera-
// tions and we should write all available authentication challenges to the response.
touch := false
if !ok {
for i := 0; i < len(ProxyWwwAuthenticate); i++ {
if strings.Contains(req.RequestURI, fmt.Sprintf("/%v/", ProxyWwwAuthenticate[i])) {
w.Header().Add("Www-Authenticate", fmt.Sprintf("%v realm=\"%s\", charset=\"UTF-8\"", "Basic", req.Host))
// if the request is bound to a user agent the locked write Www-Authenticate for such user
for k, v := range options.CredentialsByUserAgent {
if strings.Contains(k, req.UserAgent()) {
w.Header().Del("Www-Authenticate")
w.Header().Add("Www-Authenticate", fmt.Sprintf("%v realm=\"%s\", charset=\"UTF-8\"", strings.Title(v), req.Host))
touch = true
break
}
}
// if the request is not bound to any user agent, write all available challenges
if !touch {
writeSupportedAuthenticateHeader(w, req)
}
w.WriteHeader(http.StatusUnauthorized)
return
}
+8 -7
View File
@@ -41,18 +41,19 @@ func OIDCAuth(optionSetters ...Option) func(next http.Handler) http.Handler {
// there is no bearer token on the request,
if !h.shouldServe(req) {
// oidc supported but token not present, add header and handover to the next middleware.
// TODO for this logic to work and we don't return superfluous Www-Authenticate headers we would need to
// add Www-Authenticate only on selected endpoints, because Reva won't cleanup already written headers.
// this means that requests such as:
// curl -v -k -u admin:admin -H "depth: 0" -X PROPFIND https://localhost:9200/remote.php/dav/files | xmllint --format -
// even when succeeding, will contain a Www-Authenticate header.
for i := 0; i < len(ProxyWwwAuthenticate); i++ {
if strings.Contains(req.RequestURI, fmt.Sprintf("/%v/", ProxyWwwAuthenticate[i])) {
for k, v := range options.CredentialsByUserAgent {
if strings.Contains(k, req.UserAgent()) {
w.Header().Del("Www-Authenticate")
w.Header().Add("Www-Authenticate", fmt.Sprintf("%v realm=\"%s\", charset=\"UTF-8\"", strings.Title(v), req.Host))
goto OUT
}
}
w.Header().Add("Www-Authenticate", fmt.Sprintf("%v realm=\"%s\", charset=\"UTF-8\"", "Bearer", req.Host))
}
}
OUT:
next.ServeHTTP(w, req)
return
}
+9
View File
@@ -46,6 +46,8 @@ type Options struct {
UserinfoCacheSize int
// UserinfoCacheTTL sets the max cache duration for the userinfo cache, intended for the oidc_auth middleware
UserinfoCacheTTL time.Duration
// CredentialsByUserAgent sets the auth challenges on a per user-agent basis
CredentialsByUserAgent map[string]string
}
// newOptions initializes the available default options.
@@ -108,6 +110,13 @@ func OIDCIss(iss string) Option {
}
}
// CredentialsByUserAgent sets UserAgentChallenges.
func CredentialsByUserAgent(v map[string]string) Option {
return func(o *Options) {
o.CredentialsByUserAgent = v
}
}
// RevaGatewayClient provides a function to set the the reva gateway service client option.
func RevaGatewayClient(gc gateway.GatewayAPIClient) Option {
return func(o *Options) {