Integrate oid-middleware

This feature is required for user-based routing.
This commit is contained in:
Ilja Neumann
2020-03-25 21:17:20 +01:00
committed by Ilja Neumann
parent baa03421e1
commit b20b05806e
8 changed files with 305 additions and 19 deletions

View File

@@ -0,0 +1,8 @@
Enhancement: Configurable OpenID Connect client
The proxy will try to authenticate every request with the configured OIDC provider.
See configs/proxy-example.oidc.json for an example-configuration.
https://github.com/owncloud/ocis-proxy/pull/27

View File

@@ -0,0 +1,107 @@
{
"HTTP": {
"Namespace": "com.owncloud"
},
"oidc": {
"endpoint": "https://localhost:9200",
"realm": "",
"signing_algs": ["RS256", "PS256"],
"insecure": true
},
"policies": [
{
"name": "reva",
"routes": [
{
"endpoint": "/",
"backend": "http://localhost:9100"
},
{
"endpoint": "/.well-known/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/konnect/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/signin/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/ocs/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/remote.php/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/dav/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/webdav/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/status.php",
"backend": "http://localhost:9140"
},
{
"endpoint": "/index.php/",
"backend": "http://localhost:9140"
}
]
},
{
"name": "oc10",
"routes": [
{
"endpoint": "/",
"backend": "http://localhost:9100"
},
{
"endpoint": "/.well-known/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/konnect/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/signin/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/ocs/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/remote.php/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/dav/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/webdav/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/status.php",
"backend": "https://demo.owncloud.com"
},
{
"endpoint": "/index.php/",
"backend": "https://demo.owncloud.com"
}
]
}
]
}

3
go.mod
View File

@@ -6,6 +6,7 @@ require (
contrib.go.opencensus.io/exporter/jaeger v0.2.0
contrib.go.opencensus.io/exporter/ocagent v0.6.0
contrib.go.opencensus.io/exporter/zipkin v0.1.1
github.com/coreos/go-oidc v2.1.0+incompatible
github.com/micro/cli/v2 v2.1.2-0.20200203150404-894195727d9c
github.com/micro/go-micro/v2 v2.0.1-0.20200212105717-d76baf59de2e // indirect
github.com/oklog/run v1.1.0
@@ -18,7 +19,7 @@ require (
github.com/spf13/viper v1.6.2
go.opencensus.io v0.22.2
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 // indirect
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
gopkg.in/square/go-jose.v2 v2.4.0 // indirect

View File

@@ -2,6 +2,10 @@ package command
import (
"context"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-pkg/v2/oidc"
"github.com/owncloud/ocis-proxy/pkg/middleware"
"net/http"
"os"
"os/signal"
"strings"
@@ -19,7 +23,7 @@ import (
"github.com/owncloud/ocis-proxy/pkg/metrics"
"github.com/owncloud/ocis-proxy/pkg/proxy"
"github.com/owncloud/ocis-proxy/pkg/server/debug"
"github.com/owncloud/ocis-proxy/pkg/server/http"
proxyHTTP "github.com/owncloud/ocis-proxy/pkg/server/http"
"go.opencensus.io/stats/view"
"go.opencensus.io/trace"
)
@@ -141,15 +145,16 @@ func Server(cfg *config.Config) *cli.Command {
)
{
server, err := http.Server(
http.Handler(rp),
http.Logger(logger),
http.Namespace(httpNamespace),
http.Context(ctx),
http.Config(cfg),
http.Metrics(metrics),
http.Flags(flagset.RootWithConfig(config.New())),
http.Flags(flagset.ServerWithConfig(config.New())),
server, err := proxyHTTP.Server(
proxyHTTP.Handler(rp),
proxyHTTP.Logger(logger),
proxyHTTP.Namespace(httpNamespace),
proxyHTTP.Context(ctx),
proxyHTTP.Config(cfg),
proxyHTTP.Metrics(metrics),
proxyHTTP.Flags(flagset.RootWithConfig(config.New())),
proxyHTTP.Flags(flagset.ServerWithConfig(config.New())),
proxyHTTP.Middlewares(loadMiddlewares(cfg, logger)...),
)
if err != nil {
@@ -228,3 +233,22 @@ func Server(cfg *config.Config) *cli.Command {
},
}
}
func loadMiddlewares(cfg *config.Config, l log.Logger) []func(handler http.Handler) http.Handler {
var configuredMiddlewares = make([]func(handler http.Handler) http.Handler, 0)
if cfg.OIDC != nil {
l.Info().Msg("Loading OIDC-Middleware")
l.Debug().Interface("oidc_config", cfg.OIDC).Msg("OIDC-Config")
oidcMW := middleware.OpenIDConnect(
oidc.Endpoint(cfg.OIDC.Endpoint),
oidc.Insecure(cfg.OIDC.Insecure),
oidc.Realm(cfg.OIDC.Realm),
oidc.SigningAlgs(cfg.OIDC.SigningAlgs),
oidc.Logger(l),
)
configuredMiddlewares = append(configuredMiddlewares, oidcMW)
}
return configuredMiddlewares
}

View File

@@ -80,6 +80,16 @@ type Config struct {
Tracing Tracing
Asset Asset
Policies []Policy
OIDC *OIDC
}
// OIDC is the config for the OpenID-Connect middleware. If set the proxy will try to authenticate every request
// with the configured oidc-provider
type OIDC struct {
Endpoint string
Realm string
SigningAlgs []string
Insecure bool
}
// New initializes a new configuration

View File

@@ -0,0 +1,114 @@
package middleware
import (
"context"
"crypto/tls"
"errors"
"net/http"
"strings"
"time"
oidc "github.com/coreos/go-oidc"
ocisoidc "github.com/owncloud/ocis-pkg/v2/oidc"
"golang.org/x/oauth2"
)
var (
// ErrInvalidToken is returned when the request token is invalid.
ErrInvalidToken = errors.New("invalid or missing token")
)
// newOIDCOptions initializes the available default options.
func newOIDCOptions(opts ...ocisoidc.Option) ocisoidc.Options {
opt := ocisoidc.Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// OpenIDConnect provides a middleware to check access secured by a static token.
func OpenIDConnect(opts ...ocisoidc.Option) func(http.Handler) http.Handler {
opt := newOIDCOptions(opts...)
// set defaults
if opt.Realm == "" {
opt.Realm = opt.Endpoint
}
if len(opt.SigningAlgs) < 1 {
opt.SigningAlgs = []string{"RS256", "PS256"}
}
var oidcProvider *oidc.Provider
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
header := r.Header.Get("Authorization")
path := r.URL.Path
// Ignore request to "/konnect/v1/userinfo" as this will cause endless loop when getting userinfo
// needs a better idea on how to not hardcode this
if header == "" || !strings.HasPrefix(header, "Bearer ") || path == "/konnect/v1/userinfo" {
next.ServeHTTP(w, r)
return
}
token := header[7:]
customHTTPClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: opt.Insecure,
},
},
Timeout: time.Second * 10,
}
customCtx := context.WithValue(r.Context(), oauth2.HTTPClient, customHTTPClient)
// use cached provider
if oidcProvider == nil {
// Initialize a provider by specifying the issuer URL.
// provider needs to be cached as when it is created
// it will fetch the keys from the issuer using the .well-known
// endpoint
provider, err := oidc.NewProvider(customCtx, opt.Endpoint)
if err != nil {
opt.Logger.Error().Err(err).Msg("could not initialize oidc provider")
w.WriteHeader(http.StatusInternalServerError)
return
}
oidcProvider = provider
}
// The claims we want to have
var claims ocisoidc.StandardClaims
// TODO cache userinfo for access token if we can determine the expiry (which works in case it is a jwt based access token)
oauth2Token := &oauth2.Token{
AccessToken: token,
}
userInfo, err := oidcProvider.UserInfo(customCtx, oauth2.StaticTokenSource(oauth2Token))
if err != nil {
opt.Logger.Error().Err(err).Str("token", token).Msg("Failed to get userinfo")
http.Error(w, ErrInvalidToken.Error(), http.StatusUnauthorized)
return
}
// parse claims
if err := userInfo.Claims(&claims); err != nil {
opt.Logger.Error().Err(err).Interface("userinfo", userInfo).Msg("failed to unmarshal userinfo claims")
w.WriteHeader(http.StatusInternalServerError)
return
}
opt.Logger.Debug().Interface("claims", claims).Interface("userInfo", userInfo).Msg("unmarshalled userinfo")
// store claims in context
// uses the original context, not the one with probably reduced security
nr := r.WithContext(ocisoidc.NewContext(r.Context(), &claims))
next.ServeHTTP(w, nr)
})
}
}

View File

@@ -15,13 +15,14 @@ type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Context context.Context
Config *config.Config
Handler http.Handler
Metrics *metrics.Metrics
Flags []cli.Flag
Namespace string
Logger log.Logger
Context context.Context
Config *config.Config
Handler http.Handler
Metrics *metrics.Metrics
Flags []cli.Flag
Namespace string
Middlewares []func(handler http.Handler) http.Handler
}
// newOptions initializes the available default options.
@@ -83,3 +84,10 @@ func Handler(h http.Handler) Option {
o.Handler = h
}
}
// Middlewares provides a function to register middlewares
func Middlewares(val ...func(handler http.Handler) http.Handler) Option {
return func(o *Options) {
o.Middlewares = val
}
}

View File

@@ -5,6 +5,7 @@ import (
svc "github.com/owncloud/ocis-pkg/v2/service/http"
"github.com/owncloud/ocis-proxy/pkg/crypto"
"github.com/owncloud/ocis-proxy/pkg/version"
"net/http"
"os"
)
@@ -40,7 +41,6 @@ func Server(opts ...Option) (svc.Service, error) {
service := svc.NewService(
svc.Name("web.proxy"),
svc.Handler(options.Handler),
svc.TLSConfig(tlsConfig),
svc.Logger(options.Logger),
svc.Namespace(options.Namespace),
@@ -48,6 +48,11 @@ func Server(opts ...Option) (svc.Service, error) {
svc.Address(options.Config.HTTP.Addr),
svc.Context(options.Context),
svc.Flags(options.Flags...),
svc.Handler(applyMiddlewares(
options.Handler,
options.Middlewares...,
),
),
)
if err := service.Init(); err != nil {
@@ -56,3 +61,12 @@ func Server(opts ...Option) (svc.Service, error) {
return service, nil
}
func applyMiddlewares(h http.Handler, mws ...func(handler http.Handler) http.Handler) http.Handler {
var han = h
for _, mw := range mws {
han = mw(han)
}
return han
}