[CI] Add PathPrefix integration test suite (#1052)

Add a suite for testing the PathPrefix feature implemented earlier to serve authelia under a multi-purpose domain.

Co-authored-by: Clément Michaud <clement.michaud34@gmail.com>
pull/1058/head
Amir Zarrinkafsh 2020-05-27 21:55:44 +10:00 committed by GitHub
parent 54fe2a9abd
commit b27c1fbae9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 254 additions and 33 deletions

View File

@ -1,9 +1,11 @@
package main
import (
"bufio"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/otiai10/copy"
log "github.com/sirupsen/logrus"
@ -87,6 +89,27 @@ func setupSuite(cmd *cobra.Command, args []string) {
log.Fatal(err)
}
suiteEnv := suiteResourcePath + "/.env"
_, err = os.Stat(suiteEnv)
if err == nil {
file, err := os.Open(suiteEnv)
if err != nil {
log.Fatal(err)
}
env := bufio.NewScanner(file)
for env.Scan() {
v := strings.Split(env.Text(), "=")
err := os.Setenv(v[0], v[1])
if err != nil {
log.Fatal(err)
}
}
}
suiteTmpDirectory := tmpDirectory + suiteName
if exist {

View File

@ -131,10 +131,10 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi
addrPattern := fmt.Sprintf("%s:%d", configuration.Host, configuration.Port)
if configuration.TLSCert != "" && configuration.TLSKey != "" {
logging.Logger().Infof("Authelia is listening for TLS connections on %s", addrPattern)
logging.Logger().Infof("Authelia is listening for TLS connections on %s%s", addrPattern, configuration.Server.Path)
logging.Logger().Fatal(server.ListenAndServeTLS(addrPattern, configuration.TLSCert, configuration.TLSKey))
} else {
logging.Logger().Infof("Authelia is listening for non-TLS connections on %s", addrPattern)
logging.Logger().Infof("Authelia is listening for non-TLS connections on %s%s", addrPattern, configuration.Server.Path)
logging.Logger().Fatal(server.ListenAndServe(addrPattern))
}
}

View File

@ -0,0 +1 @@
PathPrefix=/auth

View File

@ -0,0 +1,48 @@
###############################################################
# Authelia minimal configuration #
###############################################################
port: 9091
tls_cert: /var/lib/authelia/ssl/cert.pem
tls_key: /var/lib/authelia/ssl/key.pem
server:
path: auth
log_level: debug
jwt_secret: unsecure_secret
authentication_backend:
file:
path: /var/lib/authelia/users.yml
session:
secret: unsecure_session_secret
domain: example.com
expiration: 3600 # 1 hour
inactivity: 300 # 5 minutes
remember_me_duration: 1y
storage:
local:
path: /var/lib/authelia/db.sqlite
access_control:
default_policy: bypass
rules:
- domain: "public.example.com"
policy: bypass
- domain: "admin.example.com"
policy: two_factor
- domain: "secure.example.com"
policy: two_factor
- domain: "singlefactor.example.com"
policy: one_factor
notifier:
smtp:
host: smtp
port: 1025
sender: admin@example.com
disable_require_tls: true

View File

@ -0,0 +1,7 @@
version: '3'
services:
authelia-backend:
volumes:
- './PathPrefix/configuration.yml:/etc/authelia/configuration.yml:ro'
- './PathPrefix/users.yml:/var/lib/authelia/users.yml'
- './common/ssl:/var/lib/authelia/ssl:ro'

View File

@ -0,0 +1,29 @@
###############################################################
# Users Database #
###############################################################
# This file can be used if you do not have an LDAP set up.
# List of users
users:
john:
password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email: john.doe@authelia.com
groups:
- admins
- dev
harry:
password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email: harry.potter@authelia.com
groups: []
bob:
password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email: bob.dylan@authelia.com
groups:
- dev
james:
password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email: james.dean@authelia.com

View File

@ -49,7 +49,7 @@ func (wds *WebDriverSession) doLoginTwoFactor(ctx context.Context, t *testing.T,
func (wds *WebDriverSession) doLoginAndRegisterTOTP(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool) string {
wds.doLoginOneFactor(ctx, t, username, password, keepMeLoggedIn, "")
secret := wds.doRegisterTOTP(ctx, t)
wds.doVisit(t, LoginBaseURL)
wds.doVisit(t, GetLoginBaseURL())
wds.verifyIsSecondFactorPage(ctx, t)
return secret

View File

@ -7,6 +7,6 @@ import (
)
func (wds *WebDriverSession) doLogout(ctx context.Context, t *testing.T) {
wds.doVisit(t, fmt.Sprintf("%s%s", LoginBaseURL, "/logout"))
wds.doVisit(t, fmt.Sprintf("%s%s", GetLoginBaseURL(), "/logout"))
wds.verifyIsFirstFactorPage(ctx, t)
}

View File

@ -24,5 +24,5 @@ func (wds *WebDriverSession) doVisitLoginPage(ctx context.Context, t *testing.T,
suffix = fmt.Sprintf("?rd=%s", targetURL)
}
wds.doVisitAndVerifyOneFactorStep(ctx, t, fmt.Sprintf("%s/%s", LoginBaseURL, suffix))
wds.doVisitAndVerifyOneFactorStep(ctx, t, fmt.Sprintf("%s/%s", GetLoginBaseURL(), suffix))
}

View File

@ -1,10 +1,16 @@
package suites
import "fmt"
import (
"fmt"
"os"
)
// BaseDomain the base domain.
var BaseDomain = "example.com:8080"
// PathPrefix the prefix/url_base of the login portal.
var PathPrefix = os.Getenv("PathPrefix")
// LoginBaseURL the base URL of the login portal.
var LoginBaseURL = fmt.Sprintf("https://login.%s", BaseDomain)

View File

@ -18,7 +18,7 @@ services:
- 'traefik.frontend.rule=Host:login.example.com;PathPrefix:/api'
- 'traefik.protocol=https'
# Traefik 2.x
- 'traefik.http.routers.authelia_backend.rule=Host(`login.example.com`) && PathPrefix(`/api`)'
- 'traefik.http.routers.authelia_backend.rule=Host(`login.example.com`) && PathPrefix(`/api`) || Host(`login.example.com`) && PathPrefix(`${PathPrefix}/api`)'
- 'traefik.http.routers.authelia_backend.entrypoints=https'
- 'traefik.http.routers.authelia_backend.tls=true'
- 'traefik.http.services.authelia_backend.loadbalancer.server.scheme=https'

View File

@ -7,7 +7,7 @@ services:
- 'traefik.frontend.rule=Host:login.example.com;PathPrefix:/api'
- 'traefik.protocol=https'
# Traefik 2.x
- 'traefik.http.routers.authelia_backend.rule=Host(`login.example.com`) && PathPrefix(`/api`)'
- 'traefik.http.routers.authelia_backend.rule=Host(`login.example.com`) && PathPrefix(`/api`) || Host(`login.example.com`) && PathPrefix(`${PathPrefix}/api`)'
- 'traefik.http.routers.authelia_backend.entrypoints=https'
- 'traefik.http.routers.authelia_backend.tls=true'
- 'traefik.http.services.authelia_backend.loadbalancer.server.scheme=https'

View File

@ -17,8 +17,10 @@ services:
# Traefik 1.x
- 'traefik.frontend.rule=Host:login.example.com'
# Traefik 2.x
- 'traefik.http.routers.authelia_frontend.rule=Host(`login.example.com`)'
- 'traefik.http.routers.authelia_frontend.rule=Host(`login.example.com`) || Host(`login.example.com`) && PathPrefix(`${PathPrefix}`)'
- 'traefik.http.routers.authelia_frontend.entrypoints=https'
- 'traefik.http.routers.authelia_frontend.tls=true'
environment:
- PUBLIC_URL=${PathPrefix}
networks:
- authelianet

View File

@ -8,7 +8,7 @@ services:
# Traefik 1.x
- 'traefik.frontend.rule=Host:login.example.com'
# Traefik 2.x
- 'traefik.http.routers.authelia_frontend.rule=Host(`login.example.com`)'
- 'traefik.http.routers.authelia_frontend.rule=Host(`login.example.com`) || Host(`login.example.com`) && PathPrefix(`${PathPrefix}`)'
- 'traefik.http.routers.authelia_frontend.entrypoints=https'
- 'traefik.http.routers.authelia_frontend.tls=true'
- 'traefik.http.services.authelia_frontend.loadbalancer.server.port=3000'

View File

@ -16,8 +16,4 @@ services:
- 'traefik.http.routers.httpbin.rule=Host(`public.example.com`) && Path(`/headers`)'
- 'traefik.http.routers.httpbin.priority=150'
- 'traefik.http.routers.httpbin.tls=true'
- 'traefik.http.routers.httpbin.middlewares=authelia'
- 'traefik.http.middlewares.authelia.forwardauth.address=https://authelia-backend:9091/api/verify?rd=https://login.example.com:8080/'
- 'traefik.http.middlewares.authelia.forwardauth.tls.insecureSkipVerify=true'
- 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true'
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups'
- 'traefik.http.routers.httpbin.middlewares=authelia@docker'

View File

@ -4,18 +4,14 @@ services:
image: nginx:alpine
labels:
- 'traefik.frontend.rule=Host:home.example.com,public.example.com,secure.example.com,admin.example.com,singlefactor.example.com' # Traefik 1.x
- 'traefik.frontend.auth.forward.address=https://authelia-backend:9091/api/verify?rd=https://login.example.com:8080/' # Traefik 1.x
- 'traefik.frontend.auth.forward.address=https://authelia-backend:9091/api/verify?rd=https://login.example.com:8080' # Traefik 1.x
- 'traefik.frontend.auth.forward.tls.insecureSkipVerify=true' # Traefik 1.x
- 'traefik.frontend.auth.forward.trustForwardHeader=true' # Traefik 1.x
- 'traefik.frontend.auth.forward.authResponseHeaders=Remote-User,Remote-Groups' # Traefik 1.x
- 'traefik.http.routers.protectedapps.rule=Host(`home.example.com`, `public.example.com`, `secure.example.com`, `admin.example.com`, `singlefactor.example.com`)' # Traefik 2.x
- 'traefik.http.routers.protectedapps.entrypoints=https' # Traefik 2.x
- 'traefik.http.routers.protectedapps.tls=true' # Traefik 2.x
- 'traefik.http.routers.protectedapps.middlewares=authelia' # Traefik 2.x
- 'traefik.http.middlewares.authelia.forwardauth.address=https://authelia-backend:9091/api/verify?rd=https://login.example.com:8080/' # Traefik 2.x
- 'traefik.http.middlewares.authelia.forwardauth.tls.insecureSkipVerify=true' # Traefik 2.x
- 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true' # Traefik 2.x
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups' # Traefik 2.x
- 'traefik.http.routers.protectedapps.middlewares=authelia@docker' # Traefik 2.x
volumes:
- ./example/compose/nginx/backend/html:/usr/share/nginx/html
- ./example/compose/nginx/backend/nginx.conf:/etc/nginx/nginx.conf

View File

@ -1,7 +1,7 @@
version: '3'
services:
traefik:
image: traefik:v2.1.2
image: traefik:v2.2.1
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
labels:
@ -9,6 +9,10 @@ services:
- 'traefik.http.routers.api.entrypoints=https'
- 'traefik.http.routers.api.service=api@internal'
- 'traefik.http.routers.api.tls=true'
- 'traefik.http.middlewares.authelia.forwardauth.address=https://authelia-backend:9091${PathPrefix}/api/verify?rd=https://login.example.com:8080${PathPrefix}' # Traefik 2.x
- 'traefik.http.middlewares.authelia.forwardauth.tls.insecureSkipVerify=true' # Traefik 2.x
- 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true' # Traefik 2.x
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups' # Traefik 2.x
command:
- '--api'
- '--providers.docker=true'

View File

@ -48,7 +48,7 @@ func (s *ResetPasswordScenario) TestShouldResetPassword() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
s.doVisit(s.T(), LoginBaseURL)
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsFirstFactorPage(ctx, s.T())
// Reset the password to abc
@ -72,7 +72,7 @@ func (s *ResetPasswordScenario) TestShouldMakeAttackerThinkPasswordResetIsInitia
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
s.doVisit(s.T(), LoginBaseURL)
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsFirstFactorPage(ctx, s.T())
// Try to initiate a password reset of an nonexistent user.
@ -86,7 +86,7 @@ func (s *ResetPasswordScenario) TestShouldLetUserNoticeThereIsAPasswordMismatch(
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
s.doVisit(s.T(), LoginBaseURL)
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsFirstFactorPage(ctx, s.T())
s.doInitiatePasswordReset(ctx, s.T(), "john")

View File

@ -63,7 +63,7 @@ func (s *UserPreferencesScenario) TestShouldRememberLastUsed2FAMethod() {
s.verifyIsHome(ctx, s.T())
// Then go back to portal.
s.doVisit(s.T(), LoginBaseURL)
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsSecondFactorPage(ctx, s.T())
// And check the latest method is still used.
s.WaitElementLocatedByID(ctx, s.T(), "push-notification-method")

View File

@ -79,7 +79,7 @@ func (s *HighAvailabilityWebDriverSuite) TestShouldKeepSessionAfterAutheliaResta
s.verifyIsHome(ctx, s.T())
// Verify the user is still authenticated
s.doVisit(s.T(), LoginBaseURL)
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsSecondFactorPage(ctx, s.T())
// Then logout and login again to check the secret is still there

View File

@ -71,7 +71,7 @@ func (s *OneFactorOnlyWebSuite) TestShouldDisplayAuthenticatedView() {
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "http://unsafe.local")
s.verifyURLIs(ctx, s.T(), HomeBaseURL+"/")
s.doVisit(s.T(), LoginBaseURL)
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsAuthenticatedPage(ctx, s.T())
}

View File

@ -0,0 +1,64 @@
package suites
import (
"fmt"
"time"
)
var pathPrefixSuiteName = "PathPrefix"
func init() {
dockerEnvironment := NewDockerEnvironment([]string{
"internal/suites/docker-compose.yml",
"internal/suites/PathPrefix/docker-compose.yml",
"internal/suites/example/compose/authelia/docker-compose.backend.{}.yml",
"internal/suites/example/compose/authelia/docker-compose.frontend.{}.yml",
"internal/suites/example/compose/nginx/backend/docker-compose.yml",
"internal/suites/example/compose/traefik2/docker-compose.yml",
"internal/suites/example/compose/smtp/docker-compose.yml",
"internal/suites/example/compose/httpbin/docker-compose.yml",
})
setup := func(suitePath string) error {
err := dockerEnvironment.Up()
if err != nil {
return err
}
return waitUntilAutheliaIsReady(dockerEnvironment)
}
displayAutheliaLogs := func() error {
backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil)
if err != nil {
return err
}
fmt.Println(backendLogs)
frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil)
if err != nil {
return err
}
fmt.Println(frontendLogs)
return nil
}
teardown := func(suitePath string) error {
err := dockerEnvironment.Down()
return err
}
GlobalRegistry.Register(pathPrefixSuiteName, Suite{
SetUp: setup,
SetUpTimeout: 5 * time.Minute,
OnSetupTimeout: displayAutheliaLogs,
OnError: displayAutheliaLogs,
TestTimeout: 2 * time.Minute,
TearDown: teardown,
TearDownTimeout: 2 * time.Minute,
})
}

View File

@ -0,0 +1,35 @@
package suites
import (
"testing"
"github.com/stretchr/testify/suite"
)
type PathPrefixSuite struct {
*SeleniumSuite
}
func NewPathPrefixSuite() *PathPrefixSuite {
return &PathPrefixSuite{SeleniumSuite: new(SeleniumSuite)}
}
func (s *PathPrefixSuite) TestOneFactorScenario() {
suite.Run(s.T(), NewOneFactorScenario())
}
func (s *PathPrefixSuite) TestTwoFactorScenario() {
suite.Run(s.T(), NewTwoFactorScenario())
}
func (s *PathPrefixSuite) TestCustomHeaders() {
suite.Run(s.T(), NewCustomHeadersScenario())
}
func (s *PathPrefixSuite) TestResetPasswordScenario() {
suite.Run(s.T(), NewResetPasswordScenario())
}
func TestPathPrefixSuite(t *testing.T) {
suite.Run(t, NewPathPrefixSuite())
}

View File

@ -62,7 +62,7 @@ func (s *StandaloneWebDriverSuite) TestShouldLetUserKnowHeIsAlreadyAuthenticated
s.verifyIsHome(ctx, s.T())
// Visit the login page and wait for redirection to 2FA page with success icon displayed.
s.doVisit(s.T(), LoginBaseURL)
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsAuthenticatedPage(ctx, s.T())
}
@ -121,7 +121,7 @@ func (s *StandaloneSuite) TestShouldVerifyAPIVerifyUnauthorize() {
// Standard case using Kubernetes.
func (s *StandaloneSuite) TestShouldVerifyAPIVerifyRedirectFromXOriginalURL() {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/verify?rd=%s", AutheliaBaseURL, LoginBaseURL), nil)
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/verify?rd=%s", AutheliaBaseURL, GetLoginBaseURL()), nil)
s.Assert().NoError(err)
req.Header.Set("X-Forwarded-Proto", "https")
req.Header.Set("X-Original-URL", AdminBaseURL)
@ -134,11 +134,11 @@ func (s *StandaloneSuite) TestShouldVerifyAPIVerifyRedirectFromXOriginalURL() {
s.Assert().NoError(err)
urlEncodedAdminURL := url.QueryEscape(AdminBaseURL)
s.Assert().Equal(fmt.Sprintf("Found. Redirecting to %s?rd=%s", LoginBaseURL, urlEncodedAdminURL), string(body))
s.Assert().Equal(fmt.Sprintf("Found. Redirecting to %s?rd=%s", GetLoginBaseURL(), urlEncodedAdminURL), string(body))
}
func (s *StandaloneSuite) TestShouldVerifyAPIVerifyRedirectFromXOriginalHostURI() {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/verify?rd=%s", AutheliaBaseURL, LoginBaseURL), nil)
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/verify?rd=%s", AutheliaBaseURL, GetLoginBaseURL()), nil)
s.Assert().NoError(err)
req.Header.Set("X-Forwarded-Proto", "https")
req.Header.Set("X-Forwarded-Host", "secure.example.com:8080")
@ -152,7 +152,7 @@ func (s *StandaloneSuite) TestShouldVerifyAPIVerifyRedirectFromXOriginalHostURI(
s.Assert().NoError(err)
urlEncodedAdminURL := url.QueryEscape(SecureBaseURL + "/")
s.Assert().Equal(fmt.Sprintf("Found. Redirecting to %s?rd=%s", LoginBaseURL, urlEncodedAdminURL), string(body))
s.Assert().Equal(fmt.Sprintf("Found. Redirecting to %s?rd=%s", GetLoginBaseURL(), urlEncodedAdminURL), string(body))
}
func (s *StandaloneSuite) TestStandaloneWebDriverScenario() {

View File

@ -0,0 +1,10 @@
package suites
// GetLoginBaseURL returns the URL of the login portal and the path prefix if specified.
func GetLoginBaseURL() string {
if PathPrefix != "" {
return LoginBaseURL + PathPrefix
}
return LoginBaseURL
}