2017-09-18 18:48:07 +03:00
package auth
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
2018-01-10 19:48:04 +03:00
"github.com/containous/traefik/middlewares/tracing"
2017-09-18 18:48:07 +03:00
"github.com/containous/traefik/testhelpers"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
2018-04-23 16:28:04 +03:00
"github.com/stretchr/testify/require"
2017-09-18 18:48:07 +03:00
"github.com/urfave/negroni"
2018-09-18 15:22:03 +03:00
"github.com/vulcand/oxy/forward"
2017-09-18 18:48:07 +03:00
)
func TestForwardAuthFail ( t * testing . T ) {
server := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
http . Error ( w , "Forbidden" , http . StatusForbidden )
} ) )
defer server . Close ( )
middleware , err := NewAuthenticator ( & types . Auth {
Forward : & types . Forward {
Address : server . URL ,
} ,
2018-01-10 19:48:04 +03:00
} , & tracing . Tracing { } )
2017-09-18 18:48:07 +03:00
assert . NoError ( t , err , "there should be no error" )
handler := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
fmt . Fprintln ( w , "traefik" )
} )
n := negroni . New ( middleware )
n . UseHandler ( handler )
ts := httptest . NewServer ( n )
defer ts . Close ( )
req := testhelpers . MustNewRequest ( http . MethodGet , ts . URL , nil )
2018-06-30 08:54:03 +03:00
res , err := http . DefaultClient . Do ( req )
2017-09-18 18:48:07 +03:00
assert . NoError ( t , err , "there should be no error" )
assert . Equal ( t , http . StatusForbidden , res . StatusCode , "they should be equal" )
body , err := ioutil . ReadAll ( res . Body )
assert . NoError ( t , err , "there should be no error" )
assert . Equal ( t , "Forbidden\n" , string ( body ) , "they should be equal" )
}
func TestForwardAuthSuccess ( t * testing . T ) {
server := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2018-06-30 08:54:03 +03:00
w . Header ( ) . Set ( "X-Auth-User" , "user@example.com" )
w . Header ( ) . Set ( "X-Auth-Secret" , "secret" )
2017-09-18 18:48:07 +03:00
fmt . Fprintln ( w , "Success" )
} ) )
defer server . Close ( )
middleware , err := NewAuthenticator ( & types . Auth {
Forward : & types . Forward {
2018-06-30 08:54:03 +03:00
Address : server . URL ,
AuthResponseHeaders : [ ] string { "X-Auth-User" } ,
2017-09-18 18:48:07 +03:00
} ,
2018-01-10 19:48:04 +03:00
} , & tracing . Tracing { } )
2017-09-18 18:48:07 +03:00
assert . NoError ( t , err , "there should be no error" )
handler := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2018-06-30 08:54:03 +03:00
assert . Equal ( t , "user@example.com" , r . Header . Get ( "X-Auth-User" ) )
assert . Empty ( t , r . Header . Get ( "X-Auth-Secret" ) )
2017-09-18 18:48:07 +03:00
fmt . Fprintln ( w , "traefik" )
} )
n := negroni . New ( middleware )
n . UseHandler ( handler )
ts := httptest . NewServer ( n )
defer ts . Close ( )
req := testhelpers . MustNewRequest ( http . MethodGet , ts . URL , nil )
2018-06-30 08:54:03 +03:00
res , err := http . DefaultClient . Do ( req )
2017-09-18 18:48:07 +03:00
assert . NoError ( t , err , "there should be no error" )
assert . Equal ( t , http . StatusOK , res . StatusCode , "they should be equal" )
body , err := ioutil . ReadAll ( res . Body )
assert . NoError ( t , err , "there should be no error" )
assert . Equal ( t , "traefik\n" , string ( body ) , "they should be equal" )
}
2017-11-02 13:06:03 +03:00
func TestForwardAuthRedirect ( t * testing . T ) {
authTs := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
http . Redirect ( w , r , "http://example.com/redirect-test" , http . StatusFound )
} ) )
defer authTs . Close ( )
authMiddleware , err := NewAuthenticator ( & types . Auth {
Forward : & types . Forward {
Address : authTs . URL ,
} ,
2018-01-10 19:48:04 +03:00
} , & tracing . Tracing { } )
2017-11-02 13:06:03 +03:00
assert . NoError ( t , err , "there should be no error" )
handler := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
fmt . Fprintln ( w , "traefik" )
} )
n := negroni . New ( authMiddleware )
n . UseHandler ( handler )
ts := httptest . NewServer ( n )
defer ts . Close ( )
client := & http . Client {
CheckRedirect : func ( r * http . Request , via [ ] * http . Request ) error {
return http . ErrUseLastResponse
} ,
}
req := testhelpers . MustNewRequest ( http . MethodGet , ts . URL , nil )
res , err := client . Do ( req )
assert . NoError ( t , err , "there should be no error" )
assert . Equal ( t , http . StatusFound , res . StatusCode , "they should be equal" )
location , err := res . Location ( )
assert . NoError ( t , err , "there should be no error" )
assert . Equal ( t , "http://example.com/redirect-test" , location . String ( ) , "they should be equal" )
body , err := ioutil . ReadAll ( res . Body )
assert . NoError ( t , err , "there should be no error" )
assert . NotEmpty ( t , string ( body ) , "there should be something in the body" )
}
2018-09-18 15:22:03 +03:00
func TestForwardAuthRemoveHopByHopHeaders ( t * testing . T ) {
authTs := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
headers := w . Header ( )
for _ , header := range forward . HopHeaders {
if header == forward . TransferEncoding {
headers . Add ( header , "identity" )
} else {
headers . Add ( header , "test" )
}
}
http . Redirect ( w , r , "http://example.com/redirect-test" , http . StatusFound )
} ) )
defer authTs . Close ( )
authMiddleware , err := NewAuthenticator ( & types . Auth {
Forward : & types . Forward {
Address : authTs . URL ,
} ,
} , & tracing . Tracing { } )
assert . NoError ( t , err , "there should be no error" )
handler := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
fmt . Fprintln ( w , "traefik" )
} )
n := negroni . New ( authMiddleware )
n . UseHandler ( handler )
ts := httptest . NewServer ( n )
defer ts . Close ( )
client := & http . Client {
CheckRedirect : func ( r * http . Request , via [ ] * http . Request ) error {
return http . ErrUseLastResponse
} ,
}
req := testhelpers . MustNewRequest ( http . MethodGet , ts . URL , nil )
res , err := client . Do ( req )
assert . NoError ( t , err , "there should be no error" )
assert . Equal ( t , http . StatusFound , res . StatusCode , "they should be equal" )
for _ , header := range forward . HopHeaders {
assert . Equal ( t , "" , res . Header . Get ( header ) , "hop-by-hop header '%s' mustn't be set" , header )
}
location , err := res . Location ( )
assert . NoError ( t , err , "there should be no error" )
assert . Equal ( t , "http://example.com/redirect-test" , location . String ( ) , "they should be equal" )
body , err := ioutil . ReadAll ( res . Body )
assert . NoError ( t , err , "there should be no error" )
assert . NotEmpty ( t , string ( body ) , "there should be something in the body" )
}
2018-04-23 16:28:04 +03:00
func TestForwardAuthFailResponseHeaders ( t * testing . T ) {
2017-11-02 13:06:03 +03:00
authTs := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
cookie := & http . Cookie { Name : "example" , Value : "testing" , Path : "/" }
http . SetCookie ( w , cookie )
2018-04-23 16:28:04 +03:00
w . Header ( ) . Add ( "X-Foo" , "bar" )
2017-11-02 13:06:03 +03:00
http . Error ( w , "Forbidden" , http . StatusForbidden )
} ) )
defer authTs . Close ( )
authMiddleware , err := NewAuthenticator ( & types . Auth {
Forward : & types . Forward {
Address : authTs . URL ,
} ,
2018-01-10 19:48:04 +03:00
} , & tracing . Tracing { } )
2017-11-02 13:06:03 +03:00
assert . NoError ( t , err , "there should be no error" )
handler := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
fmt . Fprintln ( w , "traefik" )
} )
n := negroni . New ( authMiddleware )
n . UseHandler ( handler )
ts := httptest . NewServer ( n )
defer ts . Close ( )
req := testhelpers . MustNewRequest ( http . MethodGet , ts . URL , nil )
2018-04-23 16:28:04 +03:00
client := & http . Client { }
2017-11-02 13:06:03 +03:00
res , err := client . Do ( req )
assert . NoError ( t , err , "there should be no error" )
assert . Equal ( t , http . StatusForbidden , res . StatusCode , "they should be equal" )
2018-04-23 16:28:04 +03:00
require . Len ( t , res . Cookies ( ) , 1 )
2017-11-02 13:06:03 +03:00
for _ , cookie := range res . Cookies ( ) {
assert . Equal ( t , "testing" , cookie . Value , "they should be equal" )
}
2018-04-23 16:28:04 +03:00
expectedHeaders := http . Header {
"Content-Length" : [ ] string { "10" } ,
"Content-Type" : [ ] string { "text/plain; charset=utf-8" } ,
"X-Foo" : [ ] string { "bar" } ,
"Set-Cookie" : [ ] string { "example=testing; Path=/" } ,
"X-Content-Type-Options" : [ ] string { "nosniff" } ,
}
assert . Len ( t , res . Header , 6 )
for key , value := range expectedHeaders {
assert . Equal ( t , value , res . Header [ key ] )
}
2017-11-02 13:06:03 +03:00
body , err := ioutil . ReadAll ( res . Body )
assert . NoError ( t , err , "there should be no error" )
assert . Equal ( t , "Forbidden\n" , string ( body ) , "they should be equal" )
}
2017-09-18 18:48:07 +03:00
func Test_writeHeader ( t * testing . T ) {
testCases := [ ] struct {
2018-09-25 16:06:03 +03:00
name string
headers map [ string ] string
trustForwardHeader bool
emptyHost bool
expectedHeaders map [ string ] string
checkForUnexpectedHeaders bool
2017-09-18 18:48:07 +03:00
} {
{
name : "trust Forward Header" ,
headers : map [ string ] string {
"Accept" : "application/json" ,
"X-Forwarded-Host" : "fii.bir" ,
} ,
trustForwardHeader : true ,
expectedHeaders : map [ string ] string {
"Accept" : "application/json" ,
"X-Forwarded-Host" : "fii.bir" ,
} ,
} ,
{
name : "not trust Forward Header" ,
headers : map [ string ] string {
"Accept" : "application/json" ,
"X-Forwarded-Host" : "fii.bir" ,
} ,
trustForwardHeader : false ,
expectedHeaders : map [ string ] string {
"Accept" : "application/json" ,
"X-Forwarded-Host" : "foo.bar" ,
} ,
} ,
{
name : "trust Forward Header with empty Host" ,
headers : map [ string ] string {
"Accept" : "application/json" ,
"X-Forwarded-Host" : "fii.bir" ,
} ,
trustForwardHeader : true ,
emptyHost : true ,
expectedHeaders : map [ string ] string {
"Accept" : "application/json" ,
"X-Forwarded-Host" : "fii.bir" ,
} ,
} ,
{
name : "not trust Forward Header with empty Host" ,
headers : map [ string ] string {
"Accept" : "application/json" ,
"X-Forwarded-Host" : "fii.bir" ,
} ,
trustForwardHeader : false ,
emptyHost : true ,
expectedHeaders : map [ string ] string {
"Accept" : "application/json" ,
"X-Forwarded-Host" : "" ,
} ,
} ,
2017-12-10 02:58:21 +03:00
{
name : "trust Forward Header with forwarded URI" ,
headers : map [ string ] string {
"Accept" : "application/json" ,
"X-Forwarded-Host" : "fii.bir" ,
"X-Forwarded-Uri" : "/forward?q=1" ,
} ,
trustForwardHeader : true ,
expectedHeaders : map [ string ] string {
"Accept" : "application/json" ,
"X-Forwarded-Host" : "fii.bir" ,
"X-Forwarded-Uri" : "/forward?q=1" ,
} ,
} ,
{
name : "not trust Forward Header with forward requested URI" ,
headers : map [ string ] string {
"Accept" : "application/json" ,
"X-Forwarded-Host" : "fii.bir" ,
"X-Forwarded-Uri" : "/forward?q=1" ,
} ,
trustForwardHeader : false ,
expectedHeaders : map [ string ] string {
"Accept" : "application/json" ,
"X-Forwarded-Host" : "foo.bar" ,
"X-Forwarded-Uri" : "/path?q=1" ,
} ,
2018-06-13 16:14:03 +03:00
} , {
name : "trust Forward Header with forwarded request Method" ,
headers : map [ string ] string {
"X-Forwarded-Method" : "OPTIONS" ,
} ,
trustForwardHeader : true ,
expectedHeaders : map [ string ] string {
"X-Forwarded-Method" : "OPTIONS" ,
} ,
} ,
{
name : "not trust Forward Header with forward request Method" ,
headers : map [ string ] string {
"X-Forwarded-Method" : "OPTIONS" ,
} ,
trustForwardHeader : false ,
expectedHeaders : map [ string ] string {
"X-Forwarded-Method" : "GET" ,
} ,
2017-12-10 02:58:21 +03:00
} ,
2018-09-25 16:06:03 +03:00
{
name : "remove hop-by-hop headers" ,
headers : map [ string ] string {
forward . Connection : "Connection" ,
forward . KeepAlive : "KeepAlive" ,
forward . ProxyAuthenticate : "ProxyAuthenticate" ,
forward . ProxyAuthorization : "ProxyAuthorization" ,
forward . Te : "Te" ,
forward . Trailers : "Trailers" ,
forward . TransferEncoding : "TransferEncoding" ,
forward . Upgrade : "Upgrade" ,
"X-CustomHeader" : "CustomHeader" ,
} ,
trustForwardHeader : false ,
expectedHeaders : map [ string ] string {
"X-CustomHeader" : "CustomHeader" ,
"X-Forwarded-Proto" : "http" ,
"X-Forwarded-Host" : "foo.bar" ,
"X-Forwarded-Uri" : "/path?q=1" ,
"X-Forwarded-Method" : "GET" ,
} ,
checkForUnexpectedHeaders : true ,
} ,
2017-09-18 18:48:07 +03:00
}
for _ , test := range testCases {
t . Run ( test . name , func ( t * testing . T ) {
2017-12-10 02:58:21 +03:00
req := testhelpers . MustNewRequest ( http . MethodGet , "http://foo.bar/path?q=1" , nil )
2017-09-18 18:48:07 +03:00
for key , value := range test . headers {
req . Header . Set ( key , value )
}
if test . emptyHost {
req . Host = ""
}
2017-12-10 02:58:21 +03:00
forwardReq := testhelpers . MustNewRequest ( http . MethodGet , "http://foo.bar/path?q=1" , nil )
2017-09-18 18:48:07 +03:00
writeHeader ( req , forwardReq , test . trustForwardHeader )
2018-09-25 16:06:03 +03:00
actualHeaders := forwardReq . Header
expectedHeaders := test . expectedHeaders
for key , value := range expectedHeaders {
assert . Equal ( t , value , actualHeaders . Get ( key ) )
actualHeaders . Del ( key )
}
if test . checkForUnexpectedHeaders {
for key := range actualHeaders {
assert . Fail ( t , "Unexpected header found" , key )
}
2017-09-18 18:48:07 +03:00
}
} )
}
}