Skip to content
This repository has been archived by the owner on Dec 7, 2020. It is now read-only.

Commit

Permalink
HTTP Service (#162)
Browse files Browse the repository at this point in the history
- adding a http endpoint to permit redirects
- changing the security middleware options to configurable options
  • Loading branch information
gambol99 authored Dec 19, 2016
1 parent 4b83ccd commit 0fd9a64
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 79 deletions.
2 changes: 1 addition & 1 deletion doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 8 additions & 6 deletions middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
15 changes: 7 additions & 8 deletions middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
154 changes: 90 additions & 64 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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{
Expand Down
2 changes: 2 additions & 0 deletions server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 0fd9a64

Please sign in to comment.