mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-06-21 06:19:09 -04:00
Integrate oid-middleware
This feature is required for user-based routing.
This commit is contained in:
committed by
Ilja Neumann
parent
baa03421e1
commit
b20b05806e
8
changelog/unreleased/add-oidc.md
Normal file
8
changelog/unreleased/add-oidc.md
Normal 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
|
||||
|
||||
107
config/proxy-example-oidc.json
Normal file
107
config/proxy-example-oidc.json
Normal 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
3
go.mod
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
114
pkg/middleware/openidconnect.go
Normal file
114
pkg/middleware/openidconnect.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user