From 0fd9a6444bf067e6ce262a7822ca3044bdb272d8 Mon Sep 17 00:00:00 2001 From: Rohith Jayawardene Date: Mon, 19 Dec 2016 13:59:02 +0000 Subject: [PATCH] HTTP Service (#162) - adding a http endpoint to permit redirects - changing the security middleware options to configurable options --- doc.go | 2 +- middleware.go | 14 +++-- middleware_test.go | 15 +++-- server.go | 154 ++++++++++++++++++++++++++------------------- server_test.go | 2 + 5 files changed, 108 insertions(+), 79 deletions(-) diff --git a/doc.go b/doc.go index 53b997c1..78302621 100644 --- a/doc.go +++ b/doc.go @@ -106,7 +106,7 @@ type Config struct { // Listen is the binding interface Listen string `json:"listen" yaml:"listen"` // ListenHTTP is the interface to bind the http only service on - ListenHTTP string `json:"listen-http" yaml:"listen-http" usage:""` + ListenHTTP string `json:"listen-http" yaml:"listen-http"` // DiscoveryURL is the url for the keycloak server DiscoveryURL string `json:"discovery-url" yaml:"discovery-url"` // ClientID is the client id diff --git a/middleware.go b/middleware.go index 8a91d5f6..361bdfb9 100644 --- a/middleware.go +++ b/middleware.go @@ -434,19 +434,21 @@ func (r *oauthProxy) headersMiddleware(custom []string) gin.HandlerFunc { cx.Request.Header.Add("X-Forwarded-For", cx.Request.RemoteAddr) cx.Request.Header.Set("X-Forwarded-Host", cx.Request.Host) + cx.Request.Header.Set("X-Forwarded-Proto", cx.Request.Header.Get("X-Forwarded-Proto")) } } -// // securityMiddleware performs numerous security checks on the request -// func (r *oauthProxy) securityMiddleware() gin.HandlerFunc { + log.Info("enabling the security filter middleware") // step: create the security options secure := secure.New(secure.Options{ - AllowedHosts: r.config.Hostnames, - BrowserXssFilter: true, - ContentTypeNosniff: true, - FrameDeny: true, + AllowedHosts: r.config.Hostnames, + BrowserXssFilter: r.config.EnableBrowserXSSFilter, + ContentSecurityPolicy: r.config.ContentSecurityPolicy, + ContentTypeNosniff: r.config.EnableContentNoSniff, + FrameDeny: r.config.EnableFrameDeny, + SSLRedirect: r.config.EnableHTTPSRedirect, }) return func(cx *gin.Context) { diff --git a/middleware_test.go b/middleware_test.go index 4caef58d..5dff8b40 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -159,20 +159,19 @@ func TestEntrypointWhiteListing(t *testing.T) { func TestEntrypointHandler(t *testing.T) { proxy, _, _ := newTestProxyService(nil) - handler := proxy.entrypointMiddleware() tests := []struct { Context *gin.Context Secure bool }{ - {Context: newFakeGinContext("GET", fakeAdminRoleURL), Secure: true}, - {Context: newFakeGinContext("GET", fakeAdminRoleURL+"/sso"), Secure: true}, - {Context: newFakeGinContext("GET", fakeAdminRoleURL+"/../sso"), Secure: true}, - {Context: newFakeGinContext("GET", "/not_secure")}, - {Context: newFakeGinContext("GET", fakeTestWhitelistedURL)}, - {Context: newFakeGinContext("GET", oauthURL)}, - {Context: newFakeGinContext("GET", fakeTestListenOrdered), Secure: true}, + {Context: newFakeGinContext(http.MethodGet, fakeAdminRoleURL), Secure: true}, + {Context: newFakeGinContext(http.MethodGet, fakeAdminRoleURL+"/sso"), Secure: true}, + {Context: newFakeGinContext(http.MethodGet, fakeAdminRoleURL+"/../sso"), Secure: true}, + {Context: newFakeGinContext(http.MethodGet, "/not_secure")}, + {Context: newFakeGinContext(http.MethodGet, fakeTestWhitelistedURL)}, + {Context: newFakeGinContext(http.MethodGet, oauthURL)}, + {Context: newFakeGinContext(http.MethodGet, fakeTestListenOrdered), Secure: true}, } for i, c := range tests { diff --git a/server.go b/server.go index a181a709..24275125 100644 --- a/server.go +++ b/server.go @@ -64,9 +64,7 @@ func init() { runtime.GOMAXPROCS(runtime.NumCPU()) } -// // newProxy create's a new proxy from configuration -// func newProxy(config *Config) (*oauthProxy, error) { var err error // step: set the logger @@ -131,9 +129,7 @@ func newProxy(config *Config) (*oauthProxy, error) { return service, nil } -// // createReverseProxy creates a reverse proxy -// func (r *oauthProxy) createReverseProxy() error { log.Infof("enabled reverse proxy mode, upstream url: %s", r.config.Upstream) @@ -162,12 +158,10 @@ func (r *oauthProxy) createReverseProxy() error { if r.config.LogRequests { engine.Use(r.loggingMiddleware()) } - // step: enabling the metrics? if r.config.EnableMetrics { engine.Use(r.metricsMiddleware()) } - // step: enabling the security filter? if r.config.EnableSecurityFilter { engine.Use(r.securityMiddleware()) @@ -212,9 +206,7 @@ func (r *oauthProxy) createReverseProxy() error { return nil } -// // createForwardingProxy creates a forwarding proxy -// func (r *oauthProxy) createForwardingProxy() error { log.Infof("enabling forward signing mode, listening on %s", r.config.Listen) @@ -284,94 +276,128 @@ func (r *oauthProxy) createForwardingProxy() error { return nil } -// // Run starts the proxy service -// func (r *oauthProxy) Run() error { - tlsConfig := &tls.Config{} + // step: create the service listener + listener, err := createHTTPListener(listenerConfig{ + listen: r.config.Listen, + certificate: r.config.TLSCertificate, + privateKey: r.config.TLSPrivateKey, + ca: r.config.TLSCaCertificate, + clientCert: r.config.TLSClientCertificate, + proxyProtocol: r.config.EnableProxyProtocol, + }) + if err != nil { + return err + } + // step: create the http server + server := &http.Server{ + Addr: r.config.Listen, + Handler: r.router, + } + + go func() { + log.Infof("keycloak proxy service starting on %s", r.config.Listen) + if err = server.Serve(listener); err != nil { + log.WithFields(log.Fields{ + "error": err.Error(), + }).Fatalf("failed to start the http service") + } + }() - // step: are we doing mutual tls? - if r.config.TLSCaCertificate != "" { - log.Infof("enabling mutual tls, reading in the signing ca: %s", r.config.TLSCaCertificate) - caCert, err := ioutil.ReadFile(r.config.TLSCaCertificate) + // step: are we running http service as well? + if r.config.ListenHTTP != "" { + log.Infof("keycloak proxy service starting on %s", r.config.ListenHTTP) + httpListener, err := createHTTPListener(listenerConfig{ + listen: r.config.ListenHTTP, + proxyProtocol: r.config.EnableProxyProtocol, + }) if err != nil { return err } - - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(caCert) - tlsConfig.ClientCAs = caCertPool - tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + httpsvc := &http.Server{ + Addr: r.config.ListenHTTP, + Handler: r.router, + } + go func() { + if err := httpsvc.Serve(httpListener); err != nil { + log.WithFields(log.Fields{ + "error": err.Error(), + }).Fatalf("failed to start the http redirect service") + } + }() } - server := &http.Server{ - Addr: r.config.Listen, - Handler: r.router, - } + return nil +} + +// listenerConfig encapsulate listener options +type listenerConfig struct { + listen string // the interface to bind the listener to + certificate string // the path to the certificate if any + privateKey string // the path to the private key if any + ca string // the path to a certificate authority + clientCert string // the path to a client certificate to use for mutual tls + proxyProtocol bool // whether to enable proxy protocol on the listen +} - // step: create the listener +// createHTTPListener is responsible for creating a listening socket +func createHTTPListener(config listenerConfig) (net.Listener, error) { var listener net.Listener var err error - switch strings.HasPrefix(r.config.Listen, "unix://") { - case true: - socket := strings.Trim(r.config.Listen, "unix://") + + // step: are we create a unix socket or tcp listener? + if strings.HasPrefix(config.listen, "unix://") { + socket := strings.Trim(config.listen, "unix://") // step: delete the socket if it exists if exists := fileExists(socket); exists { if err = os.Remove(socket); err != nil { - return err + return nil, err } } - - log.Infof("listening on unix socket: %s", r.config.Listen) + log.Infof("listening on unix socket: %s", config.listen) if listener, err = net.Listen("unix", socket); err != nil { - return err + return nil, err } - - default: - listener, err = net.Listen("tcp", r.config.Listen) - if err != nil { - return err + } else { + if listener, err = net.Listen("tcp", config.listen); err != nil { + return nil, err } } - // step: configure tls - if r.config.TLSCertificate != "" && r.config.TLSPrivateKey != "" { - server.TLSConfig = tlsConfig - if tlsConfig.NextProtos == nil { - tlsConfig.NextProtos = []string{"http/1.1"} + // step: does the socket require TLS? + if config.certificate != "" && config.privateKey != "" { + log.Infof("tls enabled, certificate: %s, key: %s", config.certificate, config.privateKey) + tlsConfig := &tls.Config{} + tlsConfig.Certificates = make([]tls.Certificate, 1) + if tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(config.certificate, config.privateKey); err != nil { + return nil, err } - if len(tlsConfig.Certificates) == 0 || r.config.TLSCertificate != "" || r.config.TLSPrivateKey != "" { - tlsConfig.Certificates = make([]tls.Certificate, 1) - if tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(r.config.TLSCertificate, r.config.TLSPrivateKey); err != nil { - return err + listener = tls.NewListener(listener, tlsConfig) + + // step: are we doing mutual tls? + if config.clientCert != "" { + caCert, err := ioutil.ReadFile(config.clientCert) + if err != nil { + return nil, err } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + tlsConfig.ClientCAs = caCertPool + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert } - log.Infof("tls enabled, certificate: %s, key: %s", r.config.TLSCertificate, r.config.TLSPrivateKey) - - listener = tls.NewListener(listener, tlsConfig) } - // step: wrap the listen in a proxy protocol - if r.config.EnableProxyProtocol { - log.Infof("enabling the proxy protocol on listener: %s", r.config.Listen) + // step: does it require proxy protocol? + if config.proxyProtocol { + log.Infof("enabling the proxy protocol on listener: %s", config.listen) listener = &proxyproto.Listener{Listener: listener} } - go func() { - log.Infof("keycloak proxy service starting on %s", r.config.Listen) - if err = server.Serve(listener); err != nil { - log.WithFields(log.Fields{ - "error": err.Error(), - }).Fatalf("failed to start the service") - } - }() - - return nil + return listener, nil } -// // createUpstreamProxy create a reverse http proxy from the upstream -// func (r *oauthProxy) createUpstreamProxy(upstream *url.URL) error { // step: create the default dialer dialer := (&net.Dialer{ diff --git a/server_test.go b/server_test.go index c4d41cba..37475b7f 100644 --- a/server_test.go +++ b/server_test.go @@ -133,6 +133,8 @@ func newFakeKeycloakConfig() *Config { CookieAccessName: "kc-access", CookieRefreshName: "kc-state", DiscoveryURL: "127.0.0.1:8080", + Listen: "127.0.0.1:443", + ListenHTTP: "127.0.0.1:80", EnableAuthorizationHeader: true, EnableRefreshTokens: false, EnableLoginHandler: true,