Merge websocket proxy feature from openshift/oauth-proxy. Original author: Hiram Chirino <hiram@hiramchirino.com>
This commit is contained in:
parent
21c9d38ada
commit
c7193b4085
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
## Changes since v3.1.0
|
## Changes since v3.1.0
|
||||||
|
|
||||||
|
- [#92](https://github.com/pusher/oauth2_proxy/pull/92) Merge websocket proxy feature from openshift/oauth-proxy (@butzist)
|
||||||
- [#57](https://github.com/pusher/oauth2_proxy/pull/57) Fall back to using OIDC Subject instead of Email (@aigarius)
|
- [#57](https://github.com/pusher/oauth2_proxy/pull/57) Fall back to using OIDC Subject instead of Email (@aigarius)
|
||||||
- [#85](https://github.com/pusher/oauth2_proxy/pull/85) Use non-root user in docker images (@kskewes)
|
- [#85](https://github.com/pusher/oauth2_proxy/pull/85) Use non-root user in docker images (@kskewes)
|
||||||
- [#68](https://github.com/pusher/oauth2_proxy/pull/68) forward X-Auth-Access-Token header (@davidholsgrove)
|
- [#68](https://github.com/pusher/oauth2_proxy/pull/68) forward X-Auth-Access-Token header (@davidholsgrove)
|
||||||
|
21
Gopkg.lock
generated
21
Gopkg.lock
generated
@ -59,11 +59,11 @@
|
|||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:9408fb9c637c103010e5147469c232ce6b68edc840879cc730a2a15918e6cae8"
|
digest = "1:15c0562bca5d78ac087fb39c211071dc124e79fb18f8b7c3f8a0bc7ffcb2a38e"
|
||||||
name = "github.com/mreiferson/go-options"
|
name = "github.com/mreiferson/go-options"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
pruneopts = ""
|
pruneopts = ""
|
||||||
revision = "77551d20752b54535462404ad9d877ebdb26e53d"
|
revision = "20ba7d382d05facb01e02eb777af0c5f229c5c95"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411"
|
digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411"
|
||||||
@ -87,11 +87,22 @@
|
|||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:3926a4ec9a4ff1a072458451aa2d9b98acd059a45b38f7335d31e06c3d6a0159"
|
digest = "1:3926a4ec9a4ff1a072458451aa2d9b98acd059a45b38f7335d31e06c3d6a0159"
|
||||||
name = "github.com/stretchr/testify"
|
name = "github.com/stretchr/testify"
|
||||||
packages = ["assert"]
|
packages = [
|
||||||
|
"assert",
|
||||||
|
"require",
|
||||||
|
]
|
||||||
pruneopts = ""
|
pruneopts = ""
|
||||||
revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0"
|
revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0"
|
||||||
version = "v1.1.4"
|
version = "v1.1.4"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:39630a0e2844fc4297c27caacb394a9fd342f869292284a62f856877adab65bc"
|
||||||
|
name = "github.com/yhat/wsutil"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "1d66fa95c997864ba4d8479f56609620fe542928"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:f6a006d27619a4d93bf9b66fe1999b8c8d1fa62bdc63af14f10fbe6fcaa2aa1a"
|
digest = "1:f6a006d27619a4d93bf9b66fe1999b8c8d1fa62bdc63af14f10fbe6fcaa2aa1a"
|
||||||
@ -112,6 +123,7 @@
|
|||||||
packages = [
|
packages = [
|
||||||
"context",
|
"context",
|
||||||
"context/ctxhttp",
|
"context/ctxhttp",
|
||||||
|
"websocket",
|
||||||
]
|
]
|
||||||
pruneopts = ""
|
pruneopts = ""
|
||||||
revision = "9dfe39835686865bff950a07b394c12a98ddc811"
|
revision = "9dfe39835686865bff950a07b394c12a98ddc811"
|
||||||
@ -192,7 +204,10 @@
|
|||||||
"github.com/mbland/hmacauth",
|
"github.com/mbland/hmacauth",
|
||||||
"github.com/mreiferson/go-options",
|
"github.com/mreiferson/go-options",
|
||||||
"github.com/stretchr/testify/assert",
|
"github.com/stretchr/testify/assert",
|
||||||
|
"github.com/stretchr/testify/require",
|
||||||
|
"github.com/yhat/wsutil",
|
||||||
"golang.org/x/crypto/bcrypt",
|
"golang.org/x/crypto/bcrypt",
|
||||||
|
"golang.org/x/net/websocket",
|
||||||
"golang.org/x/oauth2",
|
"golang.org/x/oauth2",
|
||||||
"golang.org/x/oauth2/google",
|
"golang.org/x/oauth2/google",
|
||||||
"google.golang.org/api/admin/directory/v1",
|
"google.golang.org/api/admin/directory/v1",
|
||||||
|
@ -242,6 +242,7 @@ Usage of oauth2_proxy:
|
|||||||
-profile-url string: Profile access endpoint
|
-profile-url string: Profile access endpoint
|
||||||
-provider string: OAuth provider (default "google")
|
-provider string: OAuth provider (default "google")
|
||||||
-proxy-prefix string: the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in) (default "/oauth2")
|
-proxy-prefix string: the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in) (default "/oauth2")
|
||||||
|
-proxy-websockets: enables WebSocket proxying (default true)
|
||||||
-redeem-url string: Token redemption endpoint
|
-redeem-url string: Token redemption endpoint
|
||||||
-redirect-url string: the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
|
-redirect-url string: the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
|
||||||
-request-logging: Log requests to stdout (default true)
|
-request-logging: Log requests to stdout (default true)
|
||||||
|
3
main.go
3
main.go
@ -10,7 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/mreiferson/go-options"
|
options "github.com/mreiferson/go-options"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -62,6 +62,7 @@ func main() {
|
|||||||
flagSet.String("custom-templates-dir", "", "path to custom html templates")
|
flagSet.String("custom-templates-dir", "", "path to custom html templates")
|
||||||
flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")
|
flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")
|
||||||
flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)")
|
flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)")
|
||||||
|
flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying")
|
||||||
|
|
||||||
flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates")
|
flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates")
|
||||||
flagSet.String("cookie-secret", "", "the seed string for secure cookies (optionally base64 encoded)")
|
flagSet.String("cookie-secret", "", "the seed string for secure cookies (optionally base64 encoded)")
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/mbland/hmacauth"
|
"github.com/mbland/hmacauth"
|
||||||
"github.com/pusher/oauth2_proxy/cookie"
|
"github.com/pusher/oauth2_proxy/cookie"
|
||||||
"github.com/pusher/oauth2_proxy/providers"
|
"github.com/pusher/oauth2_proxy/providers"
|
||||||
|
"github.com/yhat/wsutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -95,9 +96,10 @@ type OAuthProxy struct {
|
|||||||
|
|
||||||
// UpstreamProxy represents an upstream server to proxy to
|
// UpstreamProxy represents an upstream server to proxy to
|
||||||
type UpstreamProxy struct {
|
type UpstreamProxy struct {
|
||||||
upstream string
|
upstream string
|
||||||
handler http.Handler
|
handler http.Handler
|
||||||
auth hmacauth.HmacAuth
|
wsHandler http.Handler
|
||||||
|
auth hmacauth.HmacAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP proxies requests to the upstream provider while signing the
|
// ServeHTTP proxies requests to the upstream provider while signing the
|
||||||
@ -108,7 +110,12 @@ func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
r.Header.Set("GAP-Auth", w.Header().Get("GAP-Auth"))
|
r.Header.Set("GAP-Auth", w.Header().Get("GAP-Auth"))
|
||||||
u.auth.SignRequest(r)
|
u.auth.SignRequest(r)
|
||||||
}
|
}
|
||||||
u.handler.ServeHTTP(w, r)
|
if u.wsHandler != nil && r.Header.Get("Connection") == "Upgrade" && r.Header.Get("Upgrade") == "websocket" {
|
||||||
|
u.wsHandler.ServeHTTP(w, r)
|
||||||
|
} else {
|
||||||
|
u.handler.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReverseProxy creates a new reverse proxy for proxying requests to upstream
|
// NewReverseProxy creates a new reverse proxy for proxying requests to upstream
|
||||||
@ -145,6 +152,26 @@ func NewFileServer(path string, filesystemPath string) (proxy http.Handler) {
|
|||||||
return http.StripPrefix(path, http.FileServer(http.Dir(filesystemPath)))
|
return http.StripPrefix(path, http.FileServer(http.Dir(filesystemPath)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewWebSocketOrRestReverseProxy creates a reverse proxy for REST or websocket based on url
|
||||||
|
func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.HmacAuth) (restProxy http.Handler) {
|
||||||
|
u.Path = ""
|
||||||
|
proxy := NewReverseProxy(u, opts.FlushInterval)
|
||||||
|
if !opts.PassHostHeader {
|
||||||
|
setProxyUpstreamHostHeader(proxy, u)
|
||||||
|
} else {
|
||||||
|
setProxyDirector(proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
// this should give us a wss:// scheme if the url is https:// based.
|
||||||
|
var wsProxy *wsutil.ReverseProxy
|
||||||
|
if opts.ProxyWebSockets {
|
||||||
|
wsScheme := "ws" + strings.TrimPrefix(u.Scheme, "http")
|
||||||
|
wsURL := &url.URL{Scheme: wsScheme, Host: u.Host}
|
||||||
|
wsProxy = wsutil.NewSingleHostReverseProxy(wsURL)
|
||||||
|
}
|
||||||
|
return &UpstreamProxy{u.Host, proxy, wsProxy, auth}
|
||||||
|
}
|
||||||
|
|
||||||
// NewOAuthProxy creates a new instance of OOuthProxy from the options provided
|
// NewOAuthProxy creates a new instance of OOuthProxy from the options provided
|
||||||
func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
||||||
serveMux := http.NewServeMux()
|
serveMux := http.NewServeMux()
|
||||||
@ -157,23 +184,17 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
|||||||
path := u.Path
|
path := u.Path
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
case httpScheme, httpsScheme:
|
case httpScheme, httpsScheme:
|
||||||
u.Path = ""
|
|
||||||
log.Printf("mapping path %q => upstream %q", path, u)
|
log.Printf("mapping path %q => upstream %q", path, u)
|
||||||
proxy := NewReverseProxy(u, opts.FlushInterval)
|
proxy := NewWebSocketOrRestReverseProxy(u, opts, auth)
|
||||||
if !opts.PassHostHeader {
|
serveMux.Handle(path, proxy)
|
||||||
setProxyUpstreamHostHeader(proxy, u)
|
|
||||||
} else {
|
|
||||||
setProxyDirector(proxy)
|
|
||||||
}
|
|
||||||
serveMux.Handle(path,
|
|
||||||
&UpstreamProxy{u.Host, proxy, auth})
|
|
||||||
case "file":
|
case "file":
|
||||||
if u.Fragment != "" {
|
if u.Fragment != "" {
|
||||||
path = u.Fragment
|
path = u.Fragment
|
||||||
}
|
}
|
||||||
log.Printf("mapping path %q => file system %q", path, u.Path)
|
log.Printf("mapping path %q => file system %q", path, u.Path)
|
||||||
proxy := NewFileServer(path, u.Path)
|
proxy := NewFileServer(path, u.Path)
|
||||||
serveMux.Handle(path, &UpstreamProxy{path, proxy, nil})
|
serveMux.Handle(path, &UpstreamProxy{path, proxy, nil, nil})
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme))
|
panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme))
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/pusher/oauth2_proxy/providers"
|
"github.com/pusher/oauth2_proxy/providers"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -26,6 +27,83 @@ func init() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebSocketOrRestHandler struct {
|
||||||
|
restHandler http.Handler
|
||||||
|
wsHandler http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *WebSocketOrRestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Header.Get("Upgrade") == "websocket" {
|
||||||
|
h.wsHandler.ServeHTTP(w, r)
|
||||||
|
} else {
|
||||||
|
h.restHandler.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebSocketProxy(t *testing.T) {
|
||||||
|
handler := WebSocketOrRestHandler{
|
||||||
|
restHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
hostname, _, _ := net.SplitHostPort(r.Host)
|
||||||
|
w.Write([]byte(hostname))
|
||||||
|
}),
|
||||||
|
wsHandler: websocket.Handler(func(ws *websocket.Conn) {
|
||||||
|
defer ws.Close()
|
||||||
|
var data []byte
|
||||||
|
err := websocket.Message.Receive(ws, &data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = websocket.Message.Send(ws, data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err %s", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
backend := httptest.NewServer(&handler)
|
||||||
|
defer backend.Close()
|
||||||
|
|
||||||
|
backendURL, _ := url.Parse(backend.URL)
|
||||||
|
|
||||||
|
options := NewOptions()
|
||||||
|
var auth hmacauth.HmacAuth
|
||||||
|
options.PassHostHeader = true
|
||||||
|
proxyHandler := NewWebSocketOrRestReverseProxy(backendURL, options, auth)
|
||||||
|
frontend := httptest.NewServer(proxyHandler)
|
||||||
|
defer frontend.Close()
|
||||||
|
|
||||||
|
frontendURL, _ := url.Parse(frontend.URL)
|
||||||
|
frontendWSURL := "ws://" + frontendURL.Host + "/"
|
||||||
|
|
||||||
|
ws, err := websocket.Dial(frontendWSURL, "", "http://localhost/")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err %s", err)
|
||||||
|
}
|
||||||
|
request := []byte("hello, world!")
|
||||||
|
err = websocket.Message.Send(ws, request)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err %s", err)
|
||||||
|
}
|
||||||
|
var response = make([]byte, 1024)
|
||||||
|
websocket.Message.Receive(ws, &response)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err %s", err)
|
||||||
|
}
|
||||||
|
if g, e := string(request), string(response); g != e {
|
||||||
|
t.Errorf("got body %q; expected %q", g, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
getReq, _ := http.NewRequest("GET", frontend.URL, nil)
|
||||||
|
res, _ := http.DefaultClient.Do(getReq)
|
||||||
|
bodyBytes, _ := ioutil.ReadAll(res.Body)
|
||||||
|
backendHostname, _, _ := net.SplitHostPort(backendURL.Host)
|
||||||
|
if g, e := string(bodyBytes), backendHostname; g != e {
|
||||||
|
t.Errorf("got body %q; expected %q", g, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewReverseProxy(t *testing.T) {
|
func TestNewReverseProxy(t *testing.T) {
|
||||||
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
|
18
options.go
18
options.go
@ -21,14 +21,15 @@ import (
|
|||||||
// Options holds Configuration Options that can be set by Command Line Flag,
|
// Options holds Configuration Options that can be set by Command Line Flag,
|
||||||
// or Config File
|
// or Config File
|
||||||
type Options struct {
|
type Options struct {
|
||||||
ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy-prefix"`
|
ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy-prefix"`
|
||||||
HTTPAddress string `flag:"http-address" cfg:"http_address"`
|
ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets"`
|
||||||
HTTPSAddress string `flag:"https-address" cfg:"https_address"`
|
HTTPAddress string `flag:"http-address" cfg:"http_address"`
|
||||||
RedirectURL string `flag:"redirect-url" cfg:"redirect_url"`
|
HTTPSAddress string `flag:"https-address" cfg:"https_address"`
|
||||||
ClientID string `flag:"client-id" cfg:"client_id" env:"OAUTH2_PROXY_CLIENT_ID"`
|
RedirectURL string `flag:"redirect-url" cfg:"redirect_url"`
|
||||||
ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"OAUTH2_PROXY_CLIENT_SECRET"`
|
ClientID string `flag:"client-id" cfg:"client_id" env:"OAUTH2_PROXY_CLIENT_ID"`
|
||||||
TLSCertFile string `flag:"tls-cert" cfg:"tls_cert_file"`
|
ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"OAUTH2_PROXY_CLIENT_SECRET"`
|
||||||
TLSKeyFile string `flag:"tls-key" cfg:"tls_key_file"`
|
TLSCertFile string `flag:"tls-cert" cfg:"tls_cert_file"`
|
||||||
|
TLSKeyFile string `flag:"tls-key" cfg:"tls_key_file"`
|
||||||
|
|
||||||
AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"`
|
AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"`
|
||||||
AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant"`
|
AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant"`
|
||||||
@ -105,6 +106,7 @@ type SignatureData struct {
|
|||||||
func NewOptions() *Options {
|
func NewOptions() *Options {
|
||||||
return &Options{
|
return &Options{
|
||||||
ProxyPrefix: "/oauth2",
|
ProxyPrefix: "/oauth2",
|
||||||
|
ProxyWebSockets: true,
|
||||||
HTTPAddress: "127.0.0.1:4180",
|
HTTPAddress: "127.0.0.1:4180",
|
||||||
HTTPSAddress: ":443",
|
HTTPSAddress: ":443",
|
||||||
DisplayHtpasswdForm: true,
|
DisplayHtpasswdForm: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user