2019-03-14 11:30:04 +03:00
package integration
import (
"crypto/tls"
2021-11-25 13:10:06 +03:00
"crypto/x509"
"errors"
"fmt"
2019-03-14 11:30:04 +03:00
"net"
"net/http"
2019-06-07 20:30:07 +03:00
"net/http/httptest"
2019-03-14 11:30:04 +03:00
"os"
2019-09-13 21:00:06 +03:00
"strings"
2019-03-14 11:30:04 +03:00
"time"
"github.com/go-check/check"
2020-09-16 16:46:04 +03:00
"github.com/traefik/traefik/v2/integration/try"
2019-03-14 11:30:04 +03:00
checker "github.com/vdemeester/shakers"
)
type TCPSuite struct { BaseSuite }
func ( s * TCPSuite ) SetUpSuite ( c * check . C ) {
s . createComposeProject ( c , "tcp" )
2021-11-25 13:10:06 +03:00
s . composeUp ( c )
2019-03-14 11:30:04 +03:00
}
func ( s * TCPSuite ) TestMixed ( c * check . C ) {
2022-07-13 19:32:08 +03:00
file := s . adaptFile ( c , "fixtures/tcp/mixed.toml" , struct {
Whoami string
WhoamiA string
WhoamiB string
WhoamiNoCert string
} {
Whoami : "http://" + s . getComposeServiceIP ( c , "whoami" ) + ":80" ,
WhoamiA : s . getComposeServiceIP ( c , "whoami-a" ) + ":8080" ,
WhoamiB : s . getComposeServiceIP ( c , "whoami-b" ) + ":8080" ,
WhoamiNoCert : s . getComposeServiceIP ( c , "whoami-no-cert" ) + ":8080" ,
} )
2019-03-14 11:30:04 +03:00
defer os . Remove ( file )
cmd , display := s . traefikCmd ( withConfigFile ( file ) )
defer display ( c )
err := cmd . Start ( )
c . Assert ( err , checker . IsNil )
2020-10-09 10:32:03 +03:00
defer s . killCmd ( cmd )
2019-03-14 11:30:04 +03:00
2019-08-11 13:22:14 +03:00
err = try . GetRequest ( "http://127.0.0.1:8080/api/rawdata" , 5 * time . Second , try . StatusCodeIs ( http . StatusOK ) , try . BodyContains ( "Path(`/test`)" ) )
2019-03-14 11:30:04 +03:00
c . Assert ( err , checker . IsNil )
2019-06-07 20:30:07 +03:00
// Traefik passes through, termination handled by whoami-a
2021-11-25 13:10:06 +03:00
out , err := guessWhoTLSPassthrough ( "127.0.0.1:8093" , "whoami-a.test" )
2019-03-14 11:30:04 +03:00
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Contains , "whoami-a" )
2019-06-07 20:30:07 +03:00
// Traefik passes through, termination handled by whoami-b
2021-11-25 13:10:06 +03:00
out , err = guessWhoTLSPassthrough ( "127.0.0.1:8093" , "whoami-b.test" )
2019-03-14 11:30:04 +03:00
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Contains , "whoami-b" )
2019-06-07 20:30:07 +03:00
// Termination handled by traefik
2019-03-14 11:30:04 +03:00
out , err = guessWho ( "127.0.0.1:8093" , "whoami-c.test" , true )
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Contains , "whoami-no-cert" )
tr1 := & http . Transport {
TLSClientConfig : & tls . Config {
InsecureSkipVerify : true ,
} ,
}
req , err := http . NewRequest ( http . MethodGet , "https://127.0.0.1:8093/whoami/" , nil )
c . Assert ( err , checker . IsNil )
err = try . RequestWithTransport ( req , 10 * time . Second , tr1 , try . StatusCodeIs ( http . StatusOK ) )
c . Assert ( err , checker . IsNil )
req , err = http . NewRequest ( http . MethodGet , "https://127.0.0.1:8093/not-found/" , nil )
c . Assert ( err , checker . IsNil )
err = try . RequestWithTransport ( req , 10 * time . Second , tr1 , try . StatusCodeIs ( http . StatusNotFound ) )
c . Assert ( err , checker . IsNil )
err = try . GetRequest ( "http://127.0.0.1:8093/test" , 500 * time . Millisecond , try . StatusCodeIs ( http . StatusOK ) )
c . Assert ( err , checker . IsNil )
err = try . GetRequest ( "http://127.0.0.1:8093/not-found" , 500 * time . Millisecond , try . StatusCodeIs ( http . StatusNotFound ) )
c . Assert ( err , checker . IsNil )
}
2019-06-17 19:14:08 +03:00
func ( s * TCPSuite ) TestTLSOptions ( c * check . C ) {
2022-07-13 19:32:08 +03:00
file := s . adaptFile ( c , "fixtures/tcp/multi-tls-options.toml" , struct {
WhoamiNoCert string
} {
WhoamiNoCert : s . getComposeServiceIP ( c , "whoami-no-cert" ) + ":8080" ,
} )
2019-06-17 19:14:08 +03:00
defer os . Remove ( file )
cmd , display := s . traefikCmd ( withConfigFile ( file ) )
defer display ( c )
err := cmd . Start ( )
c . Assert ( err , checker . IsNil )
2020-10-09 10:32:03 +03:00
defer s . killCmd ( cmd )
2019-06-17 19:14:08 +03:00
2019-08-11 13:22:14 +03:00
err = try . GetRequest ( "http://127.0.0.1:8080/api/rawdata" , 5 * time . Second , try . StatusCodeIs ( http . StatusOK ) , try . BodyContains ( "HostSNI(`whoami-c.test`)" ) )
2019-06-17 19:14:08 +03:00
c . Assert ( err , checker . IsNil )
2022-08-09 18:36:08 +03:00
// Check that we can use a client tls version <= 1.2 with hostSNI 'whoami-c.test'
out , err := guessWhoTLSMaxVersion ( "127.0.0.1:8093" , "whoami-c.test" , true , tls . VersionTLS12 )
2019-06-17 19:14:08 +03:00
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Contains , "whoami-no-cert" )
2022-08-09 18:36:08 +03:00
// Check that we can use a client tls version <= 1.3 with hostSNI 'whoami-d.test'
out , err = guessWhoTLSMaxVersion ( "127.0.0.1:8093" , "whoami-d.test" , true , tls . VersionTLS13 )
2019-06-17 19:14:08 +03:00
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Contains , "whoami-no-cert" )
2022-08-09 18:36:08 +03:00
// Check that we cannot use a client tls version <= 1.2 with hostSNI 'whoami-d.test'
_ , err = guessWhoTLSMaxVersion ( "127.0.0.1:8093" , "whoami-d.test" , true , tls . VersionTLS12 )
2019-06-17 19:14:08 +03:00
c . Assert ( err , checker . NotNil )
c . Assert ( err . Error ( ) , checker . Contains , "protocol version not supported" )
2022-12-06 20:28:05 +03:00
// Check that we can't reach a route with an invalid mTLS configuration.
conn , err := tls . Dial ( "tcp" , "127.0.0.1:8093" , & tls . Config {
ServerName : "whoami-i.test" ,
InsecureSkipVerify : true ,
} )
c . Assert ( conn , checker . IsNil )
c . Assert ( err , checker . NotNil )
2019-06-17 19:14:08 +03:00
}
2019-03-14 11:30:04 +03:00
func ( s * TCPSuite ) TestNonTLSFallback ( c * check . C ) {
2022-07-13 19:32:08 +03:00
file := s . adaptFile ( c , "fixtures/tcp/non-tls-fallback.toml" , struct {
WhoamiA string
WhoamiB string
WhoamiNoCert string
WhoamiNoTLS string
} {
WhoamiA : s . getComposeServiceIP ( c , "whoami-a" ) + ":8080" ,
WhoamiB : s . getComposeServiceIP ( c , "whoami-b" ) + ":8080" ,
WhoamiNoCert : s . getComposeServiceIP ( c , "whoami-no-cert" ) + ":8080" ,
WhoamiNoTLS : s . getComposeServiceIP ( c , "whoami-no-tls" ) + ":8080" ,
} )
2019-03-14 11:30:04 +03:00
defer os . Remove ( file )
cmd , display := s . traefikCmd ( withConfigFile ( file ) )
defer display ( c )
err := cmd . Start ( )
c . Assert ( err , checker . IsNil )
2020-10-09 10:32:03 +03:00
defer s . killCmd ( cmd )
2019-03-14 11:30:04 +03:00
2019-08-11 13:22:14 +03:00
err = try . GetRequest ( "http://127.0.0.1:8080/api/rawdata" , 5 * time . Second , try . StatusCodeIs ( http . StatusOK ) , try . BodyContains ( "HostSNI(`*`)" ) )
2019-03-14 11:30:04 +03:00
c . Assert ( err , checker . IsNil )
2019-06-07 20:30:07 +03:00
// Traefik passes through, termination handled by whoami-a
2021-11-25 13:10:06 +03:00
out , err := guessWhoTLSPassthrough ( "127.0.0.1:8093" , "whoami-a.test" )
2019-03-14 11:30:04 +03:00
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Contains , "whoami-a" )
2019-06-07 20:30:07 +03:00
// Traefik passes through, termination handled by whoami-b
2021-11-25 13:10:06 +03:00
out , err = guessWhoTLSPassthrough ( "127.0.0.1:8093" , "whoami-b.test" )
2019-03-14 11:30:04 +03:00
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Contains , "whoami-b" )
2019-06-07 20:30:07 +03:00
// Termination handled by traefik
2019-03-14 11:30:04 +03:00
out , err = guessWho ( "127.0.0.1:8093" , "whoami-c.test" , true )
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Contains , "whoami-no-cert" )
out , err = guessWho ( "127.0.0.1:8093" , "" , false )
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Contains , "whoami-no-tls" )
}
func ( s * TCPSuite ) TestNonTlsTcp ( c * check . C ) {
2022-07-13 19:32:08 +03:00
file := s . adaptFile ( c , "fixtures/tcp/non-tls.toml" , struct {
WhoamiNoTLS string
} {
WhoamiNoTLS : s . getComposeServiceIP ( c , "whoami-no-tls" ) + ":8080" ,
} )
2019-03-14 11:30:04 +03:00
defer os . Remove ( file )
cmd , display := s . traefikCmd ( withConfigFile ( file ) )
defer display ( c )
err := cmd . Start ( )
c . Assert ( err , checker . IsNil )
2020-10-09 10:32:03 +03:00
defer s . killCmd ( cmd )
2019-03-14 11:30:04 +03:00
2019-08-11 13:22:14 +03:00
err = try . GetRequest ( "http://127.0.0.1:8080/api/rawdata" , 5 * time . Second , try . StatusCodeIs ( http . StatusOK ) , try . BodyContains ( "HostSNI(`*`)" ) )
2019-03-14 11:30:04 +03:00
c . Assert ( err , checker . IsNil )
2019-06-07 20:30:07 +03:00
// Traefik will forward every requests on the given port to whoami-no-tls
2019-03-14 11:30:04 +03:00
out , err := guessWho ( "127.0.0.1:8093" , "" , false )
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Contains , "whoami-no-tls" )
}
2019-06-07 20:30:07 +03:00
func ( s * TCPSuite ) TestCatchAllNoTLS ( c * check . C ) {
2022-07-13 19:32:08 +03:00
file := s . adaptFile ( c , "fixtures/tcp/catch-all-no-tls.toml" , struct {
WhoamiBannerAddress string
} {
WhoamiBannerAddress : s . getComposeServiceIP ( c , "whoami-banner" ) + ":8080" ,
} )
2019-06-07 20:30:07 +03:00
defer os . Remove ( file )
cmd , display := s . traefikCmd ( withConfigFile ( file ) )
defer display ( c )
err := cmd . Start ( )
c . Assert ( err , checker . IsNil )
2020-10-09 10:32:03 +03:00
defer s . killCmd ( cmd )
2019-06-07 20:30:07 +03:00
2019-08-11 13:22:14 +03:00
err = try . GetRequest ( "http://127.0.0.1:8080/api/rawdata" , 5 * time . Second , try . StatusCodeIs ( http . StatusOK ) , try . BodyContains ( "HostSNI(`*`)" ) )
2019-06-07 20:30:07 +03:00
c . Assert ( err , checker . IsNil )
// Traefik will forward every requests on the given port to whoami-no-tls
out , err := welcome ( "127.0.0.1:8093" )
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Contains , "Welcome" )
}
func ( s * TCPSuite ) TestCatchAllNoTLSWithHTTPS ( c * check . C ) {
2022-07-13 19:32:08 +03:00
file := s . adaptFile ( c , "fixtures/tcp/catch-all-no-tls-with-https.toml" , struct {
WhoamiNoTLSAddress string
WhoamiURL string
} {
WhoamiNoTLSAddress : s . getComposeServiceIP ( c , "whoami-no-tls" ) + ":8080" ,
WhoamiURL : "http://" + s . getComposeServiceIP ( c , "whoami" ) + ":80" ,
} )
2019-06-07 20:30:07 +03:00
defer os . Remove ( file )
cmd , display := s . traefikCmd ( withConfigFile ( file ) )
defer display ( c )
err := cmd . Start ( )
c . Assert ( err , checker . IsNil )
2020-10-09 10:32:03 +03:00
defer s . killCmd ( cmd )
2019-06-07 20:30:07 +03:00
2019-08-11 13:22:14 +03:00
err = try . GetRequest ( "http://127.0.0.1:8080/api/rawdata" , 5 * time . Second , try . StatusCodeIs ( http . StatusOK ) , try . BodyContains ( "HostSNI(`*`)" ) )
2019-06-07 20:30:07 +03:00
c . Assert ( err , checker . IsNil )
req := httptest . NewRequest ( http . MethodGet , "https://127.0.0.1:8093/test" , nil )
req . RequestURI = ""
err = try . RequestWithTransport ( req , 500 * time . Millisecond , & http . Transport {
TLSClientConfig : & tls . Config {
InsecureSkipVerify : true ,
} ,
} , try . StatusCodeIs ( http . StatusOK ) )
c . Assert ( err , checker . IsNil )
}
2021-06-11 16:30:05 +03:00
func ( s * TCPSuite ) TestMiddlewareWhiteList ( c * check . C ) {
2022-07-13 19:32:08 +03:00
file := s . adaptFile ( c , "fixtures/tcp/ip-whitelist.toml" , struct {
WhoamiA string
WhoamiB string
} {
WhoamiA : s . getComposeServiceIP ( c , "whoami-a" ) + ":8080" ,
WhoamiB : s . getComposeServiceIP ( c , "whoami-b" ) + ":8080" ,
} )
2021-06-11 16:30:05 +03:00
defer os . Remove ( file )
cmd , display := s . traefikCmd ( withConfigFile ( file ) )
defer display ( c )
err := cmd . Start ( )
c . Assert ( err , checker . IsNil )
defer s . killCmd ( cmd )
2021-11-25 13:10:06 +03:00
err = try . GetRequest ( "http://127.0.0.1:8080/api/rawdata" , 5 * time . Second , try . StatusCodeIs ( http . StatusOK ) , try . BodyContains ( "HostSNI(`whoami-a.test`)" ) )
2021-06-11 16:30:05 +03:00
c . Assert ( err , checker . IsNil )
// Traefik not passes through, ipWhitelist closes connection
2021-11-25 13:10:06 +03:00
_ , err = guessWhoTLSPassthrough ( "127.0.0.1:8093" , "whoami-a.test" )
c . Assert ( err , checker . ErrorMatches , "EOF" )
2021-06-11 16:30:05 +03:00
// Traefik passes through, termination handled by whoami-b
2021-11-25 13:10:06 +03:00
out , err := guessWhoTLSPassthrough ( "127.0.0.1:8093" , "whoami-b.test" )
2021-06-11 16:30:05 +03:00
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Contains , "whoami-b" )
}
2021-11-25 13:10:06 +03:00
func ( s * TCPSuite ) TestWRR ( c * check . C ) {
2022-07-13 19:32:08 +03:00
file := s . adaptFile ( c , "fixtures/tcp/wrr.toml" , struct {
WhoamiB string
WhoamiAB string
} {
WhoamiB : s . getComposeServiceIP ( c , "whoami-b" ) + ":8080" ,
WhoamiAB : s . getComposeServiceIP ( c , "whoami-ab" ) + ":8080" ,
} )
2021-11-25 13:10:06 +03:00
defer os . Remove ( file )
cmd , display := s . traefikCmd ( withConfigFile ( file ) )
defer display ( c )
err := cmd . Start ( )
c . Assert ( err , checker . IsNil )
defer s . killCmd ( cmd )
err = try . GetRequest ( "http://127.0.0.1:8080/api/rawdata" , 5 * time . Second , try . StatusCodeIs ( http . StatusOK ) , try . BodyContains ( "HostSNI(`whoami-b.test`)" ) )
c . Assert ( err , checker . IsNil )
call := map [ string ] int { }
for i := 0 ; i < 4 ; i ++ {
// Traefik passes through, termination handled by whoami-b or whoami-bb
out , err := guessWhoTLSPassthrough ( "127.0.0.1:8093" , "whoami-b.test" )
c . Assert ( err , checker . IsNil )
switch {
case strings . Contains ( out , "whoami-b" ) :
call [ "whoami-b" ] ++
case strings . Contains ( out , "whoami-ab" ) :
call [ "whoami-ab" ] ++
default :
call [ "unknown" ] ++
}
time . Sleep ( time . Second )
}
c . Assert ( call , checker . DeepEquals , map [ string ] int { "whoami-b" : 3 , "whoami-ab" : 1 } )
}
2019-06-07 20:30:07 +03:00
func welcome ( addr string ) ( string , error ) {
tcpAddr , err := net . ResolveTCPAddr ( "tcp" , addr )
if err != nil {
return "" , err
}
conn , err := net . DialTCP ( "tcp" , nil , tcpAddr )
if err != nil {
return "" , err
}
defer conn . Close ( )
out := make ( [ ] byte , 2048 )
n , err := conn . Read ( out )
if err != nil {
return "" , err
}
return string ( out [ : n ] ) , nil
}
2019-03-14 11:30:04 +03:00
func guessWho ( addr , serverName string , tlsCall bool ) ( string , error ) {
2019-06-17 19:14:08 +03:00
return guessWhoTLSMaxVersion ( addr , serverName , tlsCall , 0 )
}
func guessWhoTLSMaxVersion ( addr , serverName string , tlsCall bool , tlsMaxVersion uint16 ) ( string , error ) {
2019-03-14 11:30:04 +03:00
var conn net . Conn
var err error
if tlsCall {
2019-06-17 19:14:08 +03:00
conn , err = tls . Dial ( "tcp" , addr , & tls . Config {
ServerName : serverName ,
InsecureSkipVerify : true ,
MinVersion : 0 ,
MaxVersion : tlsMaxVersion ,
} )
2019-03-14 11:30:04 +03:00
} else {
tcpAddr , err2 := net . ResolveTCPAddr ( "tcp" , addr )
if err2 != nil {
return "" , err2
}
conn , err = net . DialTCP ( "tcp" , nil , tcpAddr )
if err != nil {
return "" , err
}
}
if err != nil {
return "" , err
}
defer conn . Close ( )
_ , err = conn . Write ( [ ] byte ( "WHO" ) )
if err != nil {
return "" , err
}
out := make ( [ ] byte , 2048 )
n , err := conn . Read ( out )
if err != nil {
return "" , err
}
return string ( out [ : n ] ) , nil
}
2019-09-13 21:00:06 +03:00
2021-11-25 13:10:06 +03:00
// guessWhoTLSPassthrough guesses service identity and ensures that the
// certificate is valid for the given server name.
func guessWhoTLSPassthrough ( addr , serverName string ) ( string , error ) {
var conn net . Conn
var err error
2019-09-13 21:00:06 +03:00
2021-11-25 13:10:06 +03:00
conn , err = tls . Dial ( "tcp" , addr , & tls . Config {
ServerName : serverName ,
InsecureSkipVerify : true ,
MinVersion : 0 ,
MaxVersion : 0 ,
VerifyPeerCertificate : func ( rawCerts [ ] [ ] byte , verifiedChains [ ] [ ] * x509 . Certificate ) error {
if len ( rawCerts ) > 1 {
return errors . New ( "tls: more than one certificates from peer" )
}
cert , err := x509 . ParseCertificate ( rawCerts [ 0 ] )
if err != nil {
return fmt . Errorf ( "tls: failed to parse certificate from peer: %w" , err )
}
if cert . Subject . CommonName == serverName {
return nil
}
if err = cert . VerifyHostname ( serverName ) ; err == nil {
return nil
}
return fmt . Errorf ( "tls: no valid certificate for serverName %s" , serverName )
} ,
} )
2019-09-13 21:00:06 +03:00
2021-11-25 13:10:06 +03:00
if err != nil {
return "" , err
}
defer conn . Close ( )
2019-09-13 21:00:06 +03:00
2021-11-25 13:10:06 +03:00
_ , err = conn . Write ( [ ] byte ( "WHO" ) )
if err != nil {
return "" , err
}
2019-09-13 21:00:06 +03:00
2021-11-25 13:10:06 +03:00
out := make ( [ ] byte , 2048 )
n , err := conn . Read ( out )
if err != nil {
return "" , err
2019-09-13 21:00:06 +03:00
}
2021-11-25 13:10:06 +03:00
return string ( out [ : n ] ) , nil
2019-09-13 21:00:06 +03:00
}