2017-07-06 14:28:13 +00:00
|
|
|
package integration
|
2016-04-20 18:54:57 +00:00
|
|
|
|
|
|
|
import (
|
2018-01-24 17:18:03 +00:00
|
|
|
"crypto/md5"
|
|
|
|
"crypto/rand"
|
2023-11-17 00:50:06 +00:00
|
|
|
"encoding/hex"
|
2023-09-27 13:22:06 +00:00
|
|
|
"encoding/json"
|
2016-04-20 18:54:57 +00:00
|
|
|
"fmt"
|
2018-01-24 17:18:03 +00:00
|
|
|
"io"
|
2016-04-20 18:54:57 +00:00
|
|
|
"net/http"
|
|
|
|
"os"
|
2019-11-14 15:40:05 +00:00
|
|
|
"strconv"
|
2016-04-20 18:54:57 +00:00
|
|
|
"strings"
|
2024-01-09 16:00:07 +00:00
|
|
|
"testing"
|
2016-04-20 18:54:57 +00:00
|
|
|
"time"
|
|
|
|
|
2022-11-21 17:36:05 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
2024-01-09 16:00:07 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/stretchr/testify/suite"
|
2023-02-03 14:24:05 +00:00
|
|
|
"github.com/traefik/traefik/v3/integration/try"
|
|
|
|
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
2016-04-20 18:54:57 +00:00
|
|
|
)
|
|
|
|
|
2017-07-10 14:58:12 +00:00
|
|
|
const (
|
|
|
|
traefikTestLogFile = "traefik.log"
|
|
|
|
traefikTestAccessLogFile = "access.log"
|
|
|
|
)
|
|
|
|
|
2020-05-11 10:06:07 +00:00
|
|
|
// AccessLogSuite tests suite.
|
2016-04-20 18:54:57 +00:00
|
|
|
type AccessLogSuite struct{ BaseSuite }
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func TestAccessLogSuite(t *testing.T) {
|
|
|
|
suite.Run(t, new(AccessLogSuite))
|
|
|
|
}
|
|
|
|
|
2018-01-11 09:04:03 +00:00
|
|
|
type accessLogValue struct {
|
2019-01-18 14:18:04 +00:00
|
|
|
formatOnly bool
|
|
|
|
code string
|
|
|
|
user string
|
|
|
|
routerName string
|
|
|
|
serviceURL string
|
2018-01-11 09:04:03 +00:00
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) SetupSuite() {
|
|
|
|
s.BaseSuite.SetupSuite()
|
|
|
|
s.createComposeProject("access_log")
|
|
|
|
s.composeUp()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *AccessLogSuite) TearDownSuite() {
|
|
|
|
s.BaseSuite.TearDownSuite()
|
2018-01-11 09:04:03 +00:00
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) TearDownTest() {
|
|
|
|
s.displayTraefikLogFile(traefikTestLogFile)
|
2019-09-13 17:28:04 +00:00
|
|
|
_ = os.Remove(traefikTestAccessLogFile)
|
2018-02-05 08:48:03 +00:00
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) TestAccessLog() {
|
2018-01-11 09:04:03 +00:00
|
|
|
ensureWorkingDirectoryIsClean()
|
2016-04-20 18:54:57 +00:00
|
|
|
|
|
|
|
// Start Traefik
|
2024-01-09 16:00:07 +00:00
|
|
|
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
2018-02-05 08:48:03 +00:00
|
|
|
|
2019-01-18 14:18:04 +00:00
|
|
|
defer func() {
|
2021-03-04 19:08:03 +00:00
|
|
|
traefikLog, err := os.ReadFile(traefikTestLogFile)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2022-11-21 17:36:05 +00:00
|
|
|
log.Info().Msg(string(traefikLog))
|
2019-01-18 14:18:04 +00:00
|
|
|
}()
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.waitForTraefik("server1")
|
2017-05-17 13:22:44 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkStatsForLogFile()
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify Traefik started OK
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkTraefikStarted()
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Make some requests
|
|
|
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
req.Host = "frontend1.docker.local"
|
|
|
|
|
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
req.Host = "frontend2.docker.local"
|
|
|
|
|
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify access.log output as expected
|
2024-01-09 16:00:07 +00:00
|
|
|
count := s.checkAccessLogOutput()
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
assert.Equal(s.T(), 3, count)
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify no other Traefik problems
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkNoOtherTraefikProblems()
|
2018-01-11 09:04:03 +00:00
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) TestAccessLogAuthFrontend() {
|
2018-01-11 09:04:03 +00:00
|
|
|
ensureWorkingDirectoryIsClean()
|
|
|
|
|
|
|
|
expected := []accessLogValue{
|
|
|
|
{
|
2019-01-18 14:18:04 +00:00
|
|
|
formatOnly: false,
|
|
|
|
code: "401",
|
|
|
|
user: "-",
|
|
|
|
routerName: "rt-authFrontend",
|
|
|
|
serviceURL: "-",
|
2018-01-11 09:04:03 +00:00
|
|
|
},
|
2020-06-18 14:02:04 +00:00
|
|
|
{
|
|
|
|
formatOnly: false,
|
|
|
|
code: "401",
|
|
|
|
user: "test",
|
|
|
|
routerName: "rt-authFrontend",
|
|
|
|
serviceURL: "-",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
formatOnly: false,
|
|
|
|
code: "200",
|
|
|
|
user: "test",
|
|
|
|
routerName: "rt-authFrontend",
|
2021-11-25 10:10:06 +00:00
|
|
|
serviceURL: "http://172.31.42",
|
2020-06-18 14:02:04 +00:00
|
|
|
},
|
2018-01-11 09:04:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Start Traefik
|
2024-01-09 16:00:07 +00:00
|
|
|
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkStatsForLogFile()
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.waitForTraefik("authFrontend")
|
2016-04-20 18:54:57 +00:00
|
|
|
|
|
|
|
// Verify Traefik started OK
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkTraefikStarted()
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2020-06-18 14:02:04 +00:00
|
|
|
// Test auth entrypoint
|
2018-01-11 09:04:03 +00:00
|
|
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8006/", nil)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
req.Host = "frontend.auth.docker.local"
|
|
|
|
|
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2020-06-18 14:02:04 +00:00
|
|
|
req.SetBasicAuth("test", "")
|
|
|
|
|
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2020-06-18 14:02:04 +00:00
|
|
|
|
|
|
|
req.SetBasicAuth("test", "test")
|
|
|
|
|
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2020-06-18 14:02:04 +00:00
|
|
|
|
2018-01-11 09:04:03 +00:00
|
|
|
// Verify access.log output as expected
|
2024-01-09 16:00:07 +00:00
|
|
|
count := s.checkAccessLogExactValuesOutput(expected)
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
assert.GreaterOrEqual(s.T(), count, len(expected))
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify no other Traefik problems
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkNoOtherTraefikProblems()
|
2018-01-11 09:04:03 +00:00
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) TestAccessLogDigestAuthMiddleware() {
|
2018-01-11 09:04:03 +00:00
|
|
|
ensureWorkingDirectoryIsClean()
|
|
|
|
|
|
|
|
expected := []accessLogValue{
|
|
|
|
{
|
2019-01-18 14:18:04 +00:00
|
|
|
formatOnly: false,
|
|
|
|
code: "401",
|
|
|
|
user: "-",
|
|
|
|
routerName: "rt-digestAuthMiddleware",
|
|
|
|
serviceURL: "-",
|
2018-01-11 09:04:03 +00:00
|
|
|
},
|
2020-06-18 14:02:04 +00:00
|
|
|
{
|
|
|
|
formatOnly: false,
|
|
|
|
code: "401",
|
|
|
|
user: "test",
|
|
|
|
routerName: "rt-digestAuthMiddleware",
|
|
|
|
serviceURL: "-",
|
|
|
|
},
|
2018-01-24 17:18:03 +00:00
|
|
|
{
|
2019-01-18 14:18:04 +00:00
|
|
|
formatOnly: false,
|
|
|
|
code: "200",
|
|
|
|
user: "test",
|
|
|
|
routerName: "rt-digestAuthMiddleware",
|
2021-11-25 10:10:06 +00:00
|
|
|
serviceURL: "http://172.31.42",
|
2018-01-24 17:18:03 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start Traefik
|
2024-01-09 16:00:07 +00:00
|
|
|
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
2018-01-24 17:18:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkStatsForLogFile()
|
2018-01-24 17:18:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.waitForTraefik("digestAuthMiddleware")
|
2018-01-24 17:18:03 +00:00
|
|
|
|
|
|
|
// Verify Traefik started OK
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkTraefikStarted()
|
2018-01-24 17:18:03 +00:00
|
|
|
|
|
|
|
// Test auth entrypoint
|
|
|
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8008/", nil)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-24 17:18:03 +00:00
|
|
|
req.Host = "entrypoint.digest.auth.docker.local"
|
|
|
|
|
|
|
|
resp, err := try.ResponseUntilStatusCode(req, 500*time.Millisecond, http.StatusUnauthorized)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-24 17:18:03 +00:00
|
|
|
|
2020-06-18 14:02:04 +00:00
|
|
|
digest := digestParts(resp)
|
|
|
|
digest["uri"] = "/"
|
|
|
|
digest["method"] = http.MethodGet
|
|
|
|
digest["username"] = "test"
|
|
|
|
digest["password"] = "wrong"
|
2018-01-24 17:18:03 +00:00
|
|
|
|
2020-06-18 14:02:04 +00:00
|
|
|
req.Header.Set("Authorization", getDigestAuthorization(digest))
|
2018-01-24 17:18:03 +00:00
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
2020-06-18 14:02:04 +00:00
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2020-06-18 14:02:04 +00:00
|
|
|
|
|
|
|
digest["password"] = "test"
|
|
|
|
|
|
|
|
req.Header.Set("Authorization", getDigestAuthorization(digest))
|
|
|
|
|
2018-01-24 17:18:03 +00:00
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-24 17:18:03 +00:00
|
|
|
|
|
|
|
// Verify access.log output as expected
|
2024-01-09 16:00:07 +00:00
|
|
|
count := s.checkAccessLogExactValuesOutput(expected)
|
2018-01-24 17:18:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
assert.GreaterOrEqual(s.T(), count, len(expected))
|
2018-01-24 17:18:03 +00:00
|
|
|
|
|
|
|
// Verify no other Traefik problems
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkNoOtherTraefikProblems()
|
2018-01-24 17:18:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Thanks to mvndaai for digest authentication
|
|
|
|
// https://stackoverflow.com/questions/39474284/how-do-you-do-a-http-post-with-digest-authentication-in-golang/39481441#39481441
|
|
|
|
func digestParts(resp *http.Response) map[string]string {
|
|
|
|
result := map[string]string{}
|
|
|
|
if len(resp.Header["Www-Authenticate"]) > 0 {
|
|
|
|
wantedHeaders := []string{"nonce", "realm", "qop", "opaque"}
|
|
|
|
responseHeaders := strings.Split(resp.Header["Www-Authenticate"][0], ",")
|
|
|
|
for _, r := range responseHeaders {
|
|
|
|
for _, w := range wantedHeaders {
|
|
|
|
if strings.Contains(r, w) {
|
|
|
|
result[w] = strings.Split(r, `"`)[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func getMD5(data string) string {
|
|
|
|
digest := md5.New()
|
2018-08-06 18:00:03 +00:00
|
|
|
if _, err := digest.Write([]byte(data)); err != nil {
|
2022-11-21 17:36:05 +00:00
|
|
|
log.Error().Err(err).Send()
|
2018-08-06 18:00:03 +00:00
|
|
|
}
|
2023-11-17 00:50:06 +00:00
|
|
|
|
|
|
|
return hex.EncodeToString(digest.Sum(nil))
|
2018-01-24 17:18:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func getCnonce() string {
|
|
|
|
b := make([]byte, 8)
|
2018-08-06 18:00:03 +00:00
|
|
|
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
2022-11-21 17:36:05 +00:00
|
|
|
log.Error().Err(err).Send()
|
2018-08-06 18:00:03 +00:00
|
|
|
}
|
2023-11-17 00:50:06 +00:00
|
|
|
|
|
|
|
return hex.EncodeToString(b)[:16]
|
2018-01-24 17:18:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func getDigestAuthorization(digestParts map[string]string) string {
|
|
|
|
d := digestParts
|
|
|
|
ha1 := getMD5(d["username"] + ":" + d["realm"] + ":" + d["password"])
|
|
|
|
ha2 := getMD5(d["method"] + ":" + d["uri"])
|
|
|
|
nonceCount := "00000001"
|
|
|
|
cnonce := getCnonce()
|
|
|
|
response := getMD5(fmt.Sprintf("%s:%s:%s:%s:%s:%s", ha1, d["nonce"], nonceCount, cnonce, d["qop"], ha2))
|
|
|
|
authorization := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc=%s, qop=%s, response="%s", opaque="%s", algorithm="MD5"`,
|
|
|
|
d["username"], d["realm"], d["nonce"], d["uri"], cnonce, nonceCount, d["qop"], response, d["opaque"])
|
|
|
|
return authorization
|
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) TestAccessLogFrontendRedirect() {
|
2018-01-11 09:04:03 +00:00
|
|
|
ensureWorkingDirectoryIsClean()
|
|
|
|
|
|
|
|
expected := []accessLogValue{
|
|
|
|
{
|
2019-01-18 14:18:04 +00:00
|
|
|
formatOnly: false,
|
|
|
|
code: "302",
|
|
|
|
user: "-",
|
|
|
|
routerName: "rt-frontendRedirect",
|
|
|
|
serviceURL: "-",
|
2018-01-11 09:04:03 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
formatOnly: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start Traefik
|
2024-01-09 16:00:07 +00:00
|
|
|
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
2018-02-05 08:48:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkStatsForLogFile()
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.waitForTraefik("frontendRedirect")
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify Traefik started OK
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkTraefikStarted()
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Test frontend redirect
|
|
|
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8005/test", nil)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
req.Host = ""
|
|
|
|
|
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify access.log output as expected
|
2024-01-09 16:00:07 +00:00
|
|
|
count := s.checkAccessLogExactValuesOutput(expected)
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
assert.GreaterOrEqual(s.T(), count, len(expected))
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify no other Traefik problems
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkNoOtherTraefikProblems()
|
2018-01-11 09:04:03 +00:00
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) TestAccessLogJSONFrontendRedirect() {
|
2023-09-27 13:22:06 +00:00
|
|
|
ensureWorkingDirectoryIsClean()
|
|
|
|
|
|
|
|
type logLine struct {
|
|
|
|
DownstreamStatus int `json:"downstreamStatus"`
|
|
|
|
OriginStatus int `json:"originStatus"`
|
|
|
|
RouterName string `json:"routerName"`
|
|
|
|
ServiceName string `json:"serviceName"`
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := []logLine{
|
|
|
|
{
|
|
|
|
DownstreamStatus: 302,
|
|
|
|
OriginStatus: 0,
|
|
|
|
RouterName: "rt-frontendRedirect@docker",
|
|
|
|
ServiceName: "",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
DownstreamStatus: 200,
|
|
|
|
OriginStatus: 200,
|
|
|
|
RouterName: "rt-server0@docker",
|
|
|
|
ServiceName: "service1@docker",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start Traefik
|
2024-01-09 16:00:07 +00:00
|
|
|
s.traefikCmd(withConfigFile("fixtures/access_log_json_config.toml"))
|
2023-09-27 13:22:06 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkStatsForLogFile()
|
2023-09-27 13:22:06 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.waitForTraefik("frontendRedirect")
|
2023-09-27 13:22:06 +00:00
|
|
|
|
|
|
|
// Verify Traefik started OK
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkTraefikStarted()
|
2023-09-27 13:22:06 +00:00
|
|
|
|
|
|
|
// Test frontend redirect
|
|
|
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8005/test", nil)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2023-09-27 13:22:06 +00:00
|
|
|
req.Host = ""
|
|
|
|
|
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2023-09-27 13:22:06 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
lines := s.extractLines()
|
|
|
|
assert.GreaterOrEqual(s.T(), len(lines), len(expected))
|
2023-09-27 13:22:06 +00:00
|
|
|
|
|
|
|
for i, line := range lines {
|
|
|
|
if line == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
var logline logLine
|
|
|
|
err := json.Unmarshal([]byte(line), &logline)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
|
|
|
assert.Equal(s.T(), expected[i].DownstreamStatus, logline.DownstreamStatus)
|
|
|
|
assert.Equal(s.T(), expected[i].OriginStatus, logline.OriginStatus)
|
|
|
|
assert.Equal(s.T(), expected[i].RouterName, logline.RouterName)
|
|
|
|
assert.Equal(s.T(), expected[i].ServiceName, logline.ServiceName)
|
2023-09-27 13:22:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) TestAccessLogRateLimit() {
|
2018-01-11 09:04:03 +00:00
|
|
|
ensureWorkingDirectoryIsClean()
|
|
|
|
|
|
|
|
expected := []accessLogValue{
|
|
|
|
{
|
|
|
|
formatOnly: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
formatOnly: true,
|
|
|
|
},
|
|
|
|
{
|
2019-01-18 14:18:04 +00:00
|
|
|
formatOnly: false,
|
|
|
|
code: "429",
|
|
|
|
user: "-",
|
|
|
|
routerName: "rt-rateLimit",
|
|
|
|
serviceURL: "-",
|
2018-01-11 09:04:03 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start Traefik
|
2024-01-09 16:00:07 +00:00
|
|
|
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkStatsForLogFile()
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.waitForTraefik("rateLimit")
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify Traefik started OK
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkTraefikStarted()
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Test rate limit
|
|
|
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8007/", nil)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
req.Host = "ratelimit.docker.local"
|
|
|
|
|
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify access.log output as expected
|
2024-01-09 16:00:07 +00:00
|
|
|
count := s.checkAccessLogExactValuesOutput(expected)
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
assert.GreaterOrEqual(s.T(), count, len(expected))
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify no other Traefik problems
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkNoOtherTraefikProblems()
|
2018-01-11 09:04:03 +00:00
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) TestAccessLogBackendNotFound() {
|
2018-01-11 09:04:03 +00:00
|
|
|
ensureWorkingDirectoryIsClean()
|
|
|
|
|
|
|
|
expected := []accessLogValue{
|
|
|
|
{
|
2019-01-18 14:18:04 +00:00
|
|
|
formatOnly: false,
|
|
|
|
code: "404",
|
|
|
|
user: "-",
|
|
|
|
routerName: "-",
|
|
|
|
serviceURL: "-",
|
2018-01-11 09:04:03 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start Traefik
|
2024-01-09 16:00:07 +00:00
|
|
|
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
2018-02-05 08:48:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.waitForTraefik("server1")
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkStatsForLogFile()
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify Traefik started OK
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkTraefikStarted()
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Test rate limit
|
|
|
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
req.Host = "backendnotfound.docker.local"
|
|
|
|
|
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify access.log output as expected
|
2024-01-09 16:00:07 +00:00
|
|
|
count := s.checkAccessLogExactValuesOutput(expected)
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
assert.GreaterOrEqual(s.T(), count, len(expected))
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify no other Traefik problems
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkNoOtherTraefikProblems()
|
2018-01-11 09:04:03 +00:00
|
|
|
}
|
|
|
|
|
2024-01-10 09:47:44 +00:00
|
|
|
func (s *AccessLogSuite) TestAccessLogFrontendAllowlist() {
|
2018-01-11 09:04:03 +00:00
|
|
|
ensureWorkingDirectoryIsClean()
|
|
|
|
|
|
|
|
expected := []accessLogValue{
|
|
|
|
{
|
2019-01-18 14:18:04 +00:00
|
|
|
formatOnly: false,
|
|
|
|
code: "403",
|
|
|
|
user: "-",
|
2022-10-26 15:16:05 +00:00
|
|
|
routerName: "rt-frontendAllowlist",
|
2019-01-18 14:18:04 +00:00
|
|
|
serviceURL: "-",
|
2018-01-11 09:04:03 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start Traefik
|
2024-01-09 16:00:07 +00:00
|
|
|
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkStatsForLogFile()
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2024-01-10 09:47:44 +00:00
|
|
|
s.waitForTraefik("frontendAllowlist")
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify Traefik started OK
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkTraefikStarted()
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Test rate limit
|
|
|
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2022-10-26 15:16:05 +00:00
|
|
|
req.Host = "frontend.allowlist.docker.local"
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusForbidden), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify access.log output as expected
|
2024-01-09 16:00:07 +00:00
|
|
|
count := s.checkAccessLogExactValuesOutput(expected)
|
2018-01-11 09:04:03 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
assert.GreaterOrEqual(s.T(), count, len(expected))
|
2018-01-11 09:04:03 +00:00
|
|
|
|
|
|
|
// Verify no other Traefik problems
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkNoOtherTraefikProblems()
|
2018-01-11 09:04:03 +00:00
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) TestAccessLogAuthFrontendSuccess() {
|
2018-10-25 16:00:05 +00:00
|
|
|
ensureWorkingDirectoryIsClean()
|
|
|
|
|
|
|
|
expected := []accessLogValue{
|
|
|
|
{
|
2019-01-18 14:18:04 +00:00
|
|
|
formatOnly: false,
|
|
|
|
code: "200",
|
|
|
|
user: "test",
|
|
|
|
routerName: "rt-authFrontend",
|
2021-11-25 10:10:06 +00:00
|
|
|
serviceURL: "http://172.31.42",
|
2018-10-25 16:00:05 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start Traefik
|
2024-01-09 16:00:07 +00:00
|
|
|
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
2018-10-25 16:00:05 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkStatsForLogFile()
|
2018-10-25 16:00:05 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.waitForTraefik("authFrontend")
|
2018-10-25 16:00:05 +00:00
|
|
|
|
|
|
|
// Verify Traefik started OK
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkTraefikStarted()
|
2018-10-25 16:00:05 +00:00
|
|
|
|
|
|
|
// Test auth entrypoint
|
|
|
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8006/", nil)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-10-25 16:00:05 +00:00
|
|
|
req.Host = "frontend.auth.docker.local"
|
|
|
|
req.SetBasicAuth("test", "test")
|
|
|
|
|
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2023-09-29 10:18:06 +00:00
|
|
|
|
|
|
|
// Verify access.log output as expected
|
2024-01-09 16:00:07 +00:00
|
|
|
count := s.checkAccessLogExactValuesOutput(expected)
|
2023-09-29 10:18:06 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
assert.GreaterOrEqual(s.T(), count, len(expected))
|
2023-09-29 10:18:06 +00:00
|
|
|
|
|
|
|
// Verify no other Traefik problems
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkNoOtherTraefikProblems()
|
2023-09-29 10:18:06 +00:00
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware() {
|
2023-09-29 10:18:06 +00:00
|
|
|
ensureWorkingDirectoryIsClean()
|
|
|
|
|
|
|
|
expected := []accessLogValue{
|
|
|
|
{
|
|
|
|
formatOnly: false,
|
|
|
|
code: "200",
|
|
|
|
user: "-",
|
|
|
|
routerName: "rt-preflightCORS",
|
|
|
|
serviceURL: "-",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start Traefik
|
2024-01-09 16:00:07 +00:00
|
|
|
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
2023-09-29 10:18:06 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkStatsForLogFile()
|
2023-09-29 10:18:06 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
s.waitForTraefik("preflightCORS")
|
2023-09-29 10:18:06 +00:00
|
|
|
|
|
|
|
// Verify Traefik started OK
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkTraefikStarted()
|
2023-09-29 10:18:06 +00:00
|
|
|
|
|
|
|
// Test preflight response
|
|
|
|
req, err := http.NewRequest(http.MethodOptions, "http://127.0.0.1:8009/", nil)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2023-09-29 10:18:06 +00:00
|
|
|
req.Host = "preflight.docker.local"
|
|
|
|
req.Header.Set("Origin", "whatever")
|
|
|
|
req.Header.Set("Access-Control-Request-Method", "GET")
|
|
|
|
|
|
|
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-10-25 16:00:05 +00:00
|
|
|
|
|
|
|
// Verify access.log output as expected
|
2024-01-09 16:00:07 +00:00
|
|
|
count := s.checkAccessLogExactValuesOutput(expected)
|
2018-10-25 16:00:05 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
assert.GreaterOrEqual(s.T(), count, len(expected))
|
2018-10-25 16:00:05 +00:00
|
|
|
|
|
|
|
// Verify no other Traefik problems
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkNoOtherTraefikProblems()
|
2018-10-25 16:00:05 +00:00
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) checkNoOtherTraefikProblems() {
|
2021-03-04 19:08:03 +00:00
|
|
|
traefikLog, err := os.ReadFile(traefikTestLogFile)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
if len(traefikLog) > 0 {
|
|
|
|
fmt.Printf("%s\n", string(traefikLog))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) checkAccessLogOutput() int {
|
|
|
|
lines := s.extractLines()
|
2016-04-28 10:53:02 +00:00
|
|
|
count := 0
|
|
|
|
for i, line := range lines {
|
|
|
|
if len(line) > 0 {
|
|
|
|
count++
|
2024-01-09 16:00:07 +00:00
|
|
|
s.CheckAccessLogFormat(line, i)
|
2016-04-20 18:54:57 +00:00
|
|
|
}
|
|
|
|
}
|
2018-01-11 09:04:03 +00:00
|
|
|
return count
|
|
|
|
}
|
2016-04-20 18:54:57 +00:00
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) checkAccessLogExactValuesOutput(values []accessLogValue) int {
|
|
|
|
lines := s.extractLines()
|
2018-01-11 09:04:03 +00:00
|
|
|
count := 0
|
|
|
|
for i, line := range lines {
|
2019-01-18 14:18:04 +00:00
|
|
|
fmt.Println(line)
|
2018-01-11 09:04:03 +00:00
|
|
|
if len(line) > 0 {
|
|
|
|
count++
|
|
|
|
if values[i].formatOnly {
|
2024-01-09 16:00:07 +00:00
|
|
|
s.CheckAccessLogFormat(line, i)
|
2018-01-11 09:04:03 +00:00
|
|
|
} else {
|
2024-01-09 16:00:07 +00:00
|
|
|
s.checkAccessLogExactValues(line, i, values[i])
|
2018-01-11 09:04:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return count
|
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) extractLines() []string {
|
2021-03-04 19:08:03 +00:00
|
|
|
accessLog, err := os.ReadFile(traefikTestAccessLogFile)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2019-11-14 15:40:05 +00:00
|
|
|
|
2018-01-11 09:04:03 +00:00
|
|
|
lines := strings.Split(string(accessLog), "\n")
|
2019-11-14 15:40:05 +00:00
|
|
|
|
|
|
|
var clean []string
|
|
|
|
for _, line := range lines {
|
|
|
|
if !strings.Contains(line, "/api/rawdata") {
|
|
|
|
clean = append(clean, line)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return clean
|
2018-01-11 09:04:03 +00:00
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) checkStatsForLogFile() {
|
2018-01-11 09:04:03 +00:00
|
|
|
err := try.Do(1*time.Second, func() error {
|
|
|
|
if _, errStat := os.Stat(traefikTestLogFile); errStat != nil {
|
2020-05-11 10:06:07 +00:00
|
|
|
return fmt.Errorf("could not get stats for log file: %w", errStat)
|
2018-01-11 09:04:03 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2018-01-11 09:04:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func ensureWorkingDirectoryIsClean() {
|
|
|
|
os.Remove(traefikTestAccessLogFile)
|
|
|
|
os.Remove(traefikTestLogFile)
|
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) checkTraefikStarted() []byte {
|
2021-03-04 19:08:03 +00:00
|
|
|
traefikLog, err := os.ReadFile(traefikTestLogFile)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
2016-04-28 10:53:02 +00:00
|
|
|
if len(traefikLog) > 0 {
|
2016-04-20 18:54:57 +00:00
|
|
|
fmt.Printf("%s\n", string(traefikLog))
|
|
|
|
}
|
2018-01-11 09:04:03 +00:00
|
|
|
return traefikLog
|
2016-04-20 18:54:57 +00:00
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *BaseSuite) CheckAccessLogFormat(line string, i int) {
|
2018-03-14 13:12:04 +00:00
|
|
|
results, err := accesslog.ParseAccessLog(line)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
|
|
|
assert.Len(s.T(), results, 14)
|
|
|
|
assert.Regexp(s.T(), `^(-|\d{3})$`, results[accesslog.OriginStatus])
|
2019-11-14 15:40:05 +00:00
|
|
|
count, _ := strconv.Atoi(results[accesslog.RequestCount])
|
2024-01-09 16:00:07 +00:00
|
|
|
assert.GreaterOrEqual(s.T(), count, i+1)
|
|
|
|
assert.Regexp(s.T(), `"(rt-.+@docker|api@internal)"`, results[accesslog.RouterName])
|
|
|
|
assert.True(s.T(), strings.HasPrefix(results[accesslog.ServiceURL], `"http://`))
|
|
|
|
assert.Regexp(s.T(), `^\d+ms$`, results[accesslog.Duration])
|
2017-08-11 10:04:58 +00:00
|
|
|
}
|
|
|
|
|
2024-01-09 16:00:07 +00:00
|
|
|
func (s *AccessLogSuite) checkAccessLogExactValues(line string, i int, v accessLogValue) {
|
2018-03-14 13:12:04 +00:00
|
|
|
results, err := accesslog.ParseAccessLog(line)
|
2024-01-09 16:00:07 +00:00
|
|
|
require.NoError(s.T(), err)
|
|
|
|
assert.Len(s.T(), results, 14)
|
2018-01-24 17:18:03 +00:00
|
|
|
if len(v.user) > 0 {
|
2024-01-09 16:00:07 +00:00
|
|
|
assert.Equal(s.T(), v.user, results[accesslog.ClientUsername])
|
2018-01-24 17:18:03 +00:00
|
|
|
}
|
2024-01-09 16:00:07 +00:00
|
|
|
assert.Equal(s.T(), v.code, results[accesslog.OriginStatus])
|
2019-11-14 15:40:05 +00:00
|
|
|
count, _ := strconv.Atoi(results[accesslog.RequestCount])
|
2024-01-09 16:00:07 +00:00
|
|
|
assert.GreaterOrEqual(s.T(), count, i+1)
|
|
|
|
assert.Regexp(s.T(), `^"?`+v.routerName+`.*(@docker)?$`, results[accesslog.RouterName])
|
|
|
|
assert.Regexp(s.T(), `^"?`+v.serviceURL+`.*$`, results[accesslog.ServiceURL])
|
|
|
|
assert.Regexp(s.T(), `^\d+ms$`, results[accesslog.Duration])
|
2018-02-05 08:48:03 +00:00
|
|
|
}
|