2017-09-18 18:48:07 +03:00
package auth
import (
2018-11-14 12:18:03 +03:00
"context"
2017-09-18 18:48:07 +03:00
"fmt"
2020-01-07 17:48:07 +03:00
"io"
2017-09-18 18:48:07 +03:00
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
2019-08-03 04:58:23 +03:00
"github.com/containous/traefik/v2/pkg/config/dynamic"
2020-01-07 17:48:07 +03:00
tracingMiddleware "github.com/containous/traefik/v2/pkg/middlewares/tracing"
2019-08-03 04:58:23 +03:00
"github.com/containous/traefik/v2/pkg/testhelpers"
2020-01-07 17:48:07 +03:00
"github.com/containous/traefik/v2/pkg/tracing"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/mocktracer"
2017-09-18 18:48:07 +03:00
"github.com/stretchr/testify/assert"
2018-04-23 16:28:04 +03:00
"github.com/stretchr/testify/require"
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 ) {
2018-11-14 12:18:03 +03:00
next := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
fmt . Fprintln ( w , "traefik" )
} )
2017-09-18 18:48:07 +03:00
server := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
http . Error ( w , "Forbidden" , http . StatusForbidden )
} ) )
defer server . Close ( )
2019-07-10 10:26:04 +03:00
middleware , err := NewForward ( context . Background ( ) , next , dynamic . ForwardAuth {
2018-11-14 12:18:03 +03:00
Address : server . URL ,
} , "authTest" )
require . NoError ( t , err )
2017-09-18 18:48:07 +03:00
2018-11-14 12:18:03 +03:00
ts := httptest . NewServer ( middleware )
2017-09-18 18:48:07 +03:00
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 )
2018-11-14 12:18:03 +03:00
require . NoError ( t , err )
assert . Equal ( t , http . StatusForbidden , res . StatusCode )
2017-09-18 18:48:07 +03:00
body , err := ioutil . ReadAll ( res . Body )
2018-11-14 12:18:03 +03:00
require . NoError ( t , err )
err = res . Body . Close ( )
require . NoError ( t , err )
assert . Equal ( t , "Forbidden\n" , string ( body ) )
2017-09-18 18:48:07 +03:00
}
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" )
2019-04-10 18:18:06 +03:00
w . Header ( ) . Add ( "X-Auth-Group" , "group1" )
w . Header ( ) . Add ( "X-Auth-Group" , "group2" )
2017-09-18 18:48:07 +03:00
fmt . Fprintln ( w , "Success" )
} ) )
defer server . Close ( )
2018-11-14 12:18:03 +03:00
next := 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" ) )
2019-04-10 18:18:06 +03:00
assert . Equal ( t , [ ] string { "group1" , "group2" } , r . Header [ "X-Auth-Group" ] )
2017-09-18 18:48:07 +03:00
fmt . Fprintln ( w , "traefik" )
} )
2018-11-14 12:18:03 +03:00
2019-07-10 10:26:04 +03:00
auth := dynamic . ForwardAuth {
2018-11-14 12:18:03 +03:00
Address : server . URL ,
2019-04-10 18:18:06 +03:00
AuthResponseHeaders : [ ] string { "X-Auth-User" , "X-Auth-Group" } ,
2018-11-14 12:18:03 +03:00
}
middleware , err := NewForward ( context . Background ( ) , next , auth , "authTest" )
require . NoError ( t , err )
ts := httptest . NewServer ( middleware )
2017-09-18 18:48:07 +03:00
defer ts . Close ( )
req := testhelpers . MustNewRequest ( http . MethodGet , ts . URL , nil )
2019-04-10 18:18:06 +03:00
req . Header . Set ( "X-Auth-Group" , "admin_group" )
2018-06-30 08:54:03 +03:00
res , err := http . DefaultClient . Do ( req )
2018-11-14 12:18:03 +03:00
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , res . StatusCode )
2017-09-18 18:48:07 +03:00
body , err := ioutil . ReadAll ( res . Body )
2018-11-14 12:18:03 +03:00
require . NoError ( t , err )
err = res . Body . Close ( )
require . NoError ( t , err )
assert . Equal ( t , "traefik\n" , string ( body ) )
2017-09-18 18:48:07 +03:00
}
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 ( )
2018-11-14 12:18:03 +03:00
next := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2017-11-02 13:06:03 +03:00
fmt . Fprintln ( w , "traefik" )
} )
2018-11-14 12:18:03 +03:00
2019-07-10 10:26:04 +03:00
auth := dynamic . ForwardAuth {
2018-11-14 12:18:03 +03:00
Address : authTs . URL ,
}
authMiddleware , err := NewForward ( context . Background ( ) , next , auth , "authTest" )
require . NoError ( t , err )
ts := httptest . NewServer ( authMiddleware )
2017-11-02 13:06:03 +03:00
defer ts . Close ( )
client := & http . Client {
CheckRedirect : func ( r * http . Request , via [ ] * http . Request ) error {
return http . ErrUseLastResponse
} ,
}
2018-11-14 12:18:03 +03:00
2017-11-02 13:06:03 +03:00
req := testhelpers . MustNewRequest ( http . MethodGet , ts . URL , nil )
2018-11-14 12:18:03 +03:00
2017-11-02 13:06:03 +03:00
res , err := client . Do ( req )
2018-11-14 12:18:03 +03:00
require . NoError ( t , err )
assert . Equal ( t , http . StatusFound , res . StatusCode )
2017-11-02 13:06:03 +03:00
location , err := res . Location ( )
2018-11-14 12:18:03 +03:00
require . NoError ( t , err )
assert . Equal ( t , "http://example.com/redirect-test" , location . String ( ) )
2017-11-02 13:06:03 +03:00
body , err := ioutil . ReadAll ( res . Body )
2018-11-14 12:18:03 +03:00
require . NoError ( t , err )
err = res . Body . Close ( )
require . NoError ( t , err )
assert . NotEmpty ( t , string ( body ) )
2017-11-02 13:06:03 +03:00
}
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 ( )
2018-11-14 12:18:03 +03:00
next := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2018-09-18 15:22:03 +03:00
fmt . Fprintln ( w , "traefik" )
} )
2019-07-10 10:26:04 +03:00
auth := dynamic . ForwardAuth {
2018-11-14 12:18:03 +03:00
Address : authTs . URL ,
}
authMiddleware , err := NewForward ( context . Background ( ) , next , auth , "authTest" )
assert . NoError ( t , err , "there should be no error" )
ts := httptest . NewServer ( authMiddleware )
2018-09-18 15:22:03 +03:00
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 ( )
2018-11-14 12:18:03 +03:00
next := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2017-11-02 13:06:03 +03:00
fmt . Fprintln ( w , "traefik" )
} )
2018-11-14 12:18:03 +03:00
2019-07-10 10:26:04 +03:00
auth := dynamic . ForwardAuth {
2018-11-14 12:18:03 +03:00
Address : authTs . URL ,
}
authMiddleware , err := NewForward ( context . Background ( ) , next , auth , "authTest" )
require . NoError ( t , err )
ts := httptest . NewServer ( authMiddleware )
2017-11-02 13:06:03 +03:00
defer ts . Close ( )
req := testhelpers . MustNewRequest ( http . MethodGet , ts . URL , nil )
2018-11-14 12:18:03 +03:00
res , err := http . DefaultClient . Do ( req )
require . NoError ( t , err )
assert . Equal ( t , http . StatusForbidden , res . StatusCode )
2017-11-02 13:06:03 +03:00
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 ( ) {
2018-11-14 12:18:03 +03:00
assert . Equal ( t , "testing" , cookie . Value )
2017-11-02 13:06:03 +03:00
}
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 )
2018-11-14 12:18:03 +03:00
require . NoError ( t , err )
err = res . Body . Close ( )
require . NoError ( t , err )
assert . Equal ( t , "Forbidden\n" , string ( body ) )
2017-11-02 13:06:03 +03:00
}
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" ,
} ,
2020-07-07 15:42:03 +03:00
} ,
{
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
}
} )
}
}
2020-01-07 17:48:07 +03:00
func TestForwardAuthUsesTracing ( t * testing . T ) {
server := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if r . Header . Get ( "Mockpfx-Ids-Traceid" ) == "" {
t . Errorf ( "expected Mockpfx-Ids-Traceid header to be present in request" )
}
} ) )
defer server . Close ( )
next := http . Handler ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) { } ) )
auth := dynamic . ForwardAuth {
Address : server . URL ,
}
tracer := mocktracer . New ( )
opentracing . SetGlobalTracer ( tracer )
tr , _ := tracing . NewTracing ( "testApp" , 100 , & mockBackend { tracer } )
next , err := NewForward ( context . Background ( ) , next , auth , "authTest" )
require . NoError ( t , err )
next = tracingMiddleware . NewEntryPoint ( context . Background ( ) , tr , "tracingTest" , next )
ts := httptest . NewServer ( next )
defer ts . Close ( )
req := testhelpers . MustNewRequest ( http . MethodGet , ts . URL , nil )
res , err := http . DefaultClient . Do ( req )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , res . StatusCode )
}
type mockBackend struct {
opentracing . Tracer
}
func ( b * mockBackend ) Setup ( componentName string ) ( opentracing . Tracer , io . Closer , error ) {
return b . Tracer , ioutil . NopCloser ( nil ) , nil
}