Files
teleport/integration/appaccess/pack.go
T
STeve (Xin) Huang 3a0ab15f4a Support App access behind load balancer (#22565)
* Support App access behind load balancer

* revert unecessary change

* fix lint

* Fix UT

* minor refactor
2023-03-14 13:47:41 +00:00

910 lines
25 KiB
Go

// Copyright 2022 Gravitational, Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package appaccess
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"testing"
"time"
"github.com/google/uuid"
"github.com/gorilla/websocket"
"github.com/gravitational/oxy/forward"
"github.com/gravitational/trace"
"github.com/stretchr/testify/require"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/breaker"
apidefaults "github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/types"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/integration/helpers"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/auth/native"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/httplib/csrf"
"github.com/gravitational/teleport/lib/reversetunnel"
"github.com/gravitational/teleport/lib/service"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/srv/alpnproxy"
alpncommon "github.com/gravitational/teleport/lib/srv/alpnproxy/common"
"github.com/gravitational/teleport/lib/srv/app/common"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/web"
"github.com/gravitational/teleport/lib/web/app"
)
// Pack contains identity as well as initialized Teleport clusters and instances.
type Pack struct {
username string
password string
tc *client.TeleportClient
user types.User
webCookie string
webToken string
rootCluster *helpers.TeleInstance
rootAppServers []*service.TeleportProcess
rootCertPool *x509.CertPool
rootAppName string
rootAppPublicAddr string
rootAppClusterName string
rootMessage string
rootAppURI string
rootWSAppName string
rootWSPublicAddr string
rootWSMessage string
rootWSAppURI string
rootWSSAppName string
rootWSSPublicAddr string
rootWSSMessage string
rootWSSAppURI string
rootTCPAppName string
rootTCPPublicAddr string
rootTCPMessage string
rootTCPAppURI string
rootTCPTwoWayAppName string
rootTCPTwoWayPublicAddr string
rootTCPTwoWayMessage string
rootTCPTwoWayAppURI string
jwtAppName string
jwtAppPublicAddr string
jwtAppClusterName string
jwtAppURI string
dumperAppURI string
leafCluster *helpers.TeleInstance
leafAppServers []*service.TeleportProcess
leafAppName string
leafAppPublicAddr string
leafAppClusterName string
leafMessage string
leafAppURI string
leafWSAppName string
leafWSPublicAddr string
leafWSMessage string
leafWSAppURI string
leafWSSAppName string
leafWSSPublicAddr string
leafWSSMessage string
leafWSSAppURI string
leafTCPAppName string
leafTCPPublicAddr string
leafTCPMessage string
leafTCPAppURI string
headerAppName string
headerAppPublicAddr string
headerAppClusterName string
headerAppURI string
wsHeaderAppName string
wsHeaderAppPublicAddr string
wsHeaderAppClusterName string
wsHeaderAppURI string
flushAppName string
flushAppPublicAddr string
flushAppClusterName string
flushAppURI string
}
func (p *Pack) RootWebAddr() string {
return p.rootCluster.Web
}
func (p *Pack) RootAppClusterName() string {
return p.rootAppClusterName
}
func (p *Pack) RootAppPublicAddr() string {
return p.rootAppPublicAddr
}
func (p *Pack) LeafAppClusterName() string {
return p.leafAppClusterName
}
func (p *Pack) LeafAppPublicAddr() string {
return p.leafAppPublicAddr
}
// initUser will create a user within the root cluster.
func (p *Pack) initUser(t *testing.T) {
p.user, p.password = p.CreateUser(t)
p.username = p.user.GetName()
}
// CreateUser creates and upserts a new user into the root cluster, and returns the new user and password.
func (p *Pack) CreateUser(t *testing.T) (types.User, string) {
username := uuid.New().String()
password := uuid.New().String()
user, err := types.NewUser(username)
require.NoError(t, err)
role := services.RoleForUser(user)
role.SetLogins(types.Allow, []string{username, "root", "ubuntu"})
err = p.rootCluster.Process.GetAuthServer().UpsertRole(context.Background(), role)
require.NoError(t, err)
user.AddRole(role.GetName())
user.SetTraits(map[string][]string{"env": {"production"}, "empty": {}, "nil": nil})
err = p.rootCluster.Process.GetAuthServer().CreateUser(context.Background(), user)
require.NoError(t, err)
err = p.rootCluster.Process.GetAuthServer().UpsertPassword(user.GetName(), []byte(password))
require.NoError(t, err)
return user, password
}
// initWebSession creates a Web UI session within the root cluster.
func (p *Pack) initWebSession(t *testing.T) {
csReq, err := json.Marshal(web.CreateSessionReq{
User: p.username,
Pass: p.password,
})
require.NoError(t, err)
// Create POST request to create session.
u := url.URL{
Scheme: "https",
Host: p.rootCluster.Web,
Path: "/v1/webapi/sessions/web",
}
req, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewBuffer(csReq))
require.NoError(t, err)
// Attach CSRF token in cookie and header.
csrfToken, err := utils.CryptoRandomHex(32)
require.NoError(t, err)
req.AddCookie(&http.Cookie{
Name: csrf.CookieName,
Value: csrfToken,
})
req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.Header.Set(csrf.HeaderName, csrfToken)
// Issue request.
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
// Read in response.
var csResp *web.CreateSessionResponse
err = json.NewDecoder(resp.Body).Decode(&csResp)
require.NoError(t, err)
// Extract session cookie and bearer token.
require.Len(t, resp.Cookies(), 1)
cookie := resp.Cookies()[0]
require.Equal(t, cookie.Name, web.CookieName)
p.webCookie = cookie.Value
p.webToken = csResp.Token
}
// initTeleportClient initializes a Teleport client with this pack's user
// credentials.
func (p *Pack) initTeleportClient(t *testing.T, opts AppTestOptions) {
p.tc = p.MakeTeleportClient(t, p.username)
}
func (p *Pack) MakeTeleportClient(t *testing.T, user string) *client.TeleportClient {
creds, err := helpers.GenerateUserCreds(helpers.UserCredsRequest{
Process: p.rootCluster.Process,
Username: user,
})
require.NoError(t, err)
tc, err := p.rootCluster.NewClientWithCreds(helpers.ClientConfig{
Login: user,
Cluster: p.rootCluster.Secrets.SiteName,
Host: helpers.Loopback,
Port: helpers.Port(t, p.rootCluster.SSH),
}, *creds)
require.NoError(t, err)
return tc
}
// CreateAppSession creates an application session with the root cluster. The
// application that the user connects to may be running in a leaf cluster.
func (p *Pack) CreateAppSession(t *testing.T, publicAddr, clusterName string) []*http.Cookie {
require.NotEmpty(t, p.webCookie)
require.NotEmpty(t, p.webToken)
casReq, err := json.Marshal(web.CreateAppSessionRequest{
FQDNHint: publicAddr,
PublicAddr: publicAddr,
ClusterName: clusterName,
})
require.NoError(t, err)
statusCode, body, err := p.makeWebapiRequest(http.MethodPost, "sessions/app", casReq)
require.NoError(t, err)
require.Equal(t, http.StatusOK, statusCode)
var casResp *web.CreateAppSessionResponse
err = json.Unmarshal(body, &casResp)
require.NoError(t, err)
return []*http.Cookie{
{
Name: app.CookieName,
Value: casResp.CookieValue,
},
{
Name: app.SubjectCookieName,
Value: casResp.SubjectCookieValue,
},
}
}
// CreateAppSessionWithClientCert creates an application session with the root
// cluster and returns the client cert that can be used for an application
// request.
func (p *Pack) CreateAppSessionWithClientCert(t *testing.T) []tls.Certificate {
session, err := p.tc.CreateAppSession(context.Background(), types.CreateAppSessionRequest{
Username: p.username,
PublicAddr: p.rootAppPublicAddr,
ClusterName: p.rootAppClusterName,
})
require.NoError(t, err)
config := p.makeTLSConfig(t, session.GetName(), session.GetUser(), p.rootAppPublicAddr, p.rootAppClusterName)
return config.Certificates
}
// LockUser will lock the configured user for this pack.
func (p *Pack) LockUser(t *testing.T) {
err := p.rootCluster.Process.GetAuthServer().UpsertLock(context.Background(), &types.LockV2{
Spec: types.LockSpecV2{
Target: types.LockTarget{
User: p.username,
},
},
Metadata: types.Metadata{
Name: "test-lock",
},
})
require.NoError(t, err)
}
// makeWebapiRequest makes a request to the root cluster Web API.
func (p *Pack) makeWebapiRequest(method, endpoint string, payload []byte) (int, []byte, error) {
u := url.URL{
Scheme: "https",
Host: p.rootCluster.Web,
Path: fmt.Sprintf("/v1/webapi/%s", endpoint),
}
req, err := http.NewRequest(method, u.String(), bytes.NewBuffer(payload))
if err != nil {
return 0, nil, trace.Wrap(err)
}
req.AddCookie(&http.Cookie{
Name: web.CookieName,
Value: p.webCookie,
})
req.Header.Add("Authorization", fmt.Sprintf("Bearer %v", p.webToken))
req.Header.Add("Content-Type", "application/json")
statusCode, body, err := p.sendRequest(req, nil)
return statusCode, []byte(body), trace.Wrap(err)
}
func (p *Pack) ensureAuditEvent(t *testing.T, eventType string, checkEvent func(event apievents.AuditEvent)) {
require.Eventuallyf(t, func() bool {
events, _, err := p.rootCluster.Process.GetAuthServer().SearchEvents(
time.Now().Add(-time.Hour),
time.Now().Add(time.Hour),
apidefaults.Namespace,
[]string{eventType},
1,
types.EventOrderDescending,
"",
)
require.NoError(t, err)
if len(events) == 0 {
return false
}
checkEvent(events[0])
return true
}, 500*time.Millisecond, 50*time.Millisecond, "failed to fetch audit event \"%s\"", eventType)
}
// initCertPool initializes root cluster CA pool.
func (p *Pack) initCertPool(t *testing.T) {
authClient := p.rootCluster.GetSiteAPI(p.rootCluster.Secrets.SiteName)
ca, err := authClient.GetCertAuthority(context.Background(), types.CertAuthID{
Type: types.HostCA,
DomainName: p.rootCluster.Secrets.SiteName,
}, false)
require.NoError(t, err)
pool, err := services.CertPool(ca)
require.NoError(t, err)
p.rootCertPool = pool
}
// startLocalProxy starts a local ALPN proxy for the specified application.
func (p *Pack) startLocalProxy(t *testing.T, tlsConfig *tls.Config) string {
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
proxy, err := alpnproxy.NewLocalProxy(alpnproxy.LocalProxyConfig{
RemoteProxyAddr: p.rootCluster.Web,
Protocols: []alpncommon.Protocol{alpncommon.ProtocolTCP},
InsecureSkipVerify: true,
Listener: listener,
ParentContext: context.Background(),
Certs: tlsConfig.Certificates,
})
require.NoError(t, err)
t.Cleanup(func() { proxy.Close() })
go proxy.Start(context.Background())
return proxy.GetAddr()
}
// makeTLSConfig returns TLS config suitable for making an app access request.
func (p *Pack) makeTLSConfig(t *testing.T, sessionID, username, publicAddr, clusterName string) *tls.Config {
privateKey, publicKey, err := native.GenerateKeyPair()
require.NoError(t, err)
// Make sure the session ID can be seen in the backend before we continue onward.
require.Eventually(t, func() bool {
_, err := p.rootCluster.Process.GetAuthServer().GetAppSession(context.Background(), types.GetAppSessionRequest{
SessionID: sessionID,
})
return err == nil
}, 5*time.Second, 100*time.Millisecond)
certificate, err := p.rootCluster.Process.GetAuthServer().GenerateUserAppTestCert(
auth.AppTestCertRequest{
PublicKey: publicKey,
Username: username,
TTL: time.Hour,
PublicAddr: publicAddr,
ClusterName: clusterName,
SessionID: sessionID,
})
require.NoError(t, err)
tlsCert, err := tls.X509KeyPair(certificate, privateKey)
require.NoError(t, err)
return &tls.Config{
RootCAs: p.rootCertPool,
Certificates: []tls.Certificate{tlsCert},
InsecureSkipVerify: true,
}
}
// makeTLSConfigNoSession returns TLS config for application access without
// creating session to simulate nonexistent session scenario.
func (p *Pack) makeTLSConfigNoSession(t *testing.T, publicAddr, clusterName string) *tls.Config {
privateKey, publicKey, err := native.GenerateKeyPair()
require.NoError(t, err)
certificate, err := p.rootCluster.Process.GetAuthServer().GenerateUserAppTestCert(
auth.AppTestCertRequest{
PublicKey: publicKey,
Username: p.user.GetName(),
TTL: time.Hour,
PublicAddr: publicAddr,
ClusterName: clusterName,
// Use arbitrary session ID
SessionID: uuid.New().String(),
})
require.NoError(t, err)
tlsCert, err := tls.X509KeyPair(certificate, privateKey)
require.NoError(t, err)
return &tls.Config{
RootCAs: p.rootCertPool,
Certificates: []tls.Certificate{tlsCert},
InsecureSkipVerify: true,
}
}
// MakeRequest makes a request to the root cluster with the given session cookie.
func (p *Pack) MakeRequest(cookies []*http.Cookie, method string, endpoint string, headers ...servicecfg.Header) (int, string, error) {
req, err := http.NewRequest(method, p.assembleRootProxyURL(endpoint), nil)
if err != nil {
return 0, "", trace.Wrap(err)
}
// attach session cookies if passed in.
for _, c := range cookies {
req.AddCookie(c)
}
for _, h := range headers {
req.Header.Add(h.Name, h.Value)
}
return p.sendRequest(req, nil)
}
// makeRequestWithClientCert makes a request to the root cluster using the
// client certificate authentication from the provided tls config.
func (p *Pack) makeRequestWithClientCert(tlsConfig *tls.Config, method, endpoint string) (int, string, error) {
req, err := http.NewRequest(method, p.assembleRootProxyURL(endpoint), nil)
if err != nil {
return 0, "", trace.Wrap(err)
}
return p.sendRequest(req, tlsConfig)
}
// makeWebsocketRequest makes a websocket request with the given session cookie.
func (p *Pack) makeWebsocketRequest(cookies []*http.Cookie, endpoint string) (string, error) {
header := http.Header{}
dialer := websocket.Dialer{}
for _, c := range cookies {
header.Add("Cookie", c.String())
}
dialer.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
conn, resp, err := dialer.Dial(fmt.Sprintf("wss://%s%s", p.rootCluster.Web, endpoint), header)
if err != nil {
return "", err
}
defer conn.Close()
defer resp.Body.Close()
stream := &web.WebsocketIO{Conn: conn}
data, err := io.ReadAll(stream)
if err != nil && websocket.IsUnexpectedCloseError(err, websocket.CloseAbnormalClosure) {
return "", err
}
return string(data), nil
}
// assembleRootProxyURL returns the URL string of an endpoint at the root
// cluster's proxy web.
func (p *Pack) assembleRootProxyURL(endpoint string) string {
u := url.URL{
Scheme: "https",
Host: p.rootCluster.Web,
Path: endpoint,
}
return u.String()
}
// sendReqeust sends the request to the root cluster.
func (p *Pack) sendRequest(req *http.Request, tlsConfig *tls.Config) (int, string, error) {
if tlsConfig == nil {
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
resp, err := client.Do(req)
if err != nil {
return 0, "", trace.Wrap(err)
}
defer resp.Body.Close()
// Read in response body.
body, err := io.ReadAll(resp.Body)
if err != nil {
return 0, "", trace.Wrap(err)
}
return resp.StatusCode, string(body), nil
}
// waitForLogout keeps making request with the passed in session cookie until
// they return a non-200 status.
func (p *Pack) waitForLogout(appCookies []*http.Cookie) (int, error) {
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
timeout := time.NewTimer(5 * time.Second)
defer timeout.Stop()
for {
select {
case <-ticker.C:
status, _, err := p.MakeRequest(appCookies, http.MethodGet, "/")
if err != nil {
return 0, trace.Wrap(err)
}
if status != http.StatusOK {
return status, nil
}
case <-timeout.C:
return 0, trace.BadParameter("timed out waiting for logout")
}
}
}
func (p *Pack) startRootAppServers(t *testing.T, count int, opts AppTestOptions) []*service.TeleportProcess {
log := utils.NewLoggerForTests()
configs := make([]*servicecfg.Config, count)
for i := 0; i < count; i++ {
raConf := servicecfg.MakeDefaultConfig()
raConf.Clock = opts.Clock
raConf.Console = nil
raConf.Log = log
raConf.DataDir = t.TempDir()
raConf.SetToken("static-token-value")
raConf.SetAuthServerAddress(utils.NetAddr{
AddrNetwork: "tcp",
Addr: p.rootCluster.Web,
})
raConf.Auth.Enabled = false
raConf.Proxy.Enabled = false
raConf.SSH.Enabled = false
raConf.Apps.Enabled = true
raConf.CircuitBreakerConfig = breaker.NoopBreakerConfig()
raConf.Apps.MonitorCloseChannel = opts.MonitorCloseChannel
raConf.Apps.Apps = append([]servicecfg.App{
{
Name: p.rootAppName,
URI: p.rootAppURI,
PublicAddr: p.rootAppPublicAddr,
},
{
Name: p.rootWSAppName,
URI: p.rootWSAppURI,
PublicAddr: p.rootWSPublicAddr,
},
{
Name: p.rootWSSAppName,
URI: p.rootWSSAppURI,
PublicAddr: p.rootWSSPublicAddr,
},
{
Name: p.rootTCPAppName,
URI: p.rootTCPAppURI,
PublicAddr: p.rootTCPPublicAddr,
},
{
Name: p.rootTCPTwoWayAppName,
URI: p.rootTCPTwoWayAppURI,
PublicAddr: p.rootTCPTwoWayPublicAddr,
},
{
Name: p.jwtAppName,
URI: p.jwtAppURI,
PublicAddr: p.jwtAppPublicAddr,
},
{
Name: p.headerAppName,
URI: p.headerAppURI,
PublicAddr: p.headerAppPublicAddr,
},
{
Name: p.wsHeaderAppName,
URI: p.wsHeaderAppURI,
PublicAddr: p.wsHeaderAppPublicAddr,
},
{
Name: p.flushAppName,
URI: p.flushAppURI,
PublicAddr: p.flushAppPublicAddr,
},
{
Name: "dumper-root",
URI: p.dumperAppURI,
PublicAddr: "dumper-root.example.com",
Rewrite: &servicecfg.Rewrite{
Headers: []servicecfg.Header{
{
Name: "X-Teleport-Cluster",
Value: "root",
},
{
Name: "X-External-Env",
Value: "{{external.env}}",
},
// Make sure can rewrite Host header.
{
Name: "Host",
Value: "example.com",
},
// Make sure can rewrite existing header.
{
Name: "X-Existing",
Value: "rewritten-existing-header",
},
// Make sure can't rewrite Teleport headers.
{
Name: teleport.AppJWTHeader,
Value: "rewritten-app-jwt-header",
},
{
Name: teleport.AppCFHeader,
Value: "rewritten-app-cf-header",
},
{
Name: common.TeleportAPIErrorHeader,
Value: "rewritten-x-teleport-api-error",
},
{
Name: forward.XForwardedFor,
Value: "rewritten-x-forwarded-for-header",
},
{
Name: forward.XForwardedHost,
Value: "rewritten-x-forwarded-host-header",
},
{
Name: forward.XForwardedProto,
Value: "rewritten-x-forwarded-proto-header",
},
{
Name: forward.XForwardedServer,
Value: "rewritten-x-forwarded-server-header",
},
{
Name: common.XForwardedSSL,
Value: "rewritten-x-forwarded-ssl-header",
},
{
Name: forward.XForwardedPort,
Value: "rewritten-x-forwarded-port-header",
},
// Make sure we can insert JWT token in custom header.
{
Name: "X-JWT",
Value: teleport.TraitInternalJWTVariable,
},
},
},
},
}, opts.ExtraRootApps...)
configs[i] = raConf
}
servers, err := p.rootCluster.StartApps(configs)
require.NoError(t, err)
require.Equal(t, len(configs), len(servers))
for i, appServer := range servers {
srv := appServer
t.Cleanup(func() {
require.NoError(t, srv.Close())
})
waitForAppServer(t, p.rootCluster.Tunnel, p.rootAppClusterName, srv.Config.HostUUID, configs[i].Apps.Apps)
}
return servers
}
func waitForAppServer(t *testing.T, tunnel reversetunnel.Server, name string, hostUUID string, apps []servicecfg.App) {
// Make sure that the app server is ready to accept connections.
// The remote site cache needs to be filled with new registered application services.
waitForAppRegInRemoteSiteCache(t, tunnel, name, apps, hostUUID)
}
func (p *Pack) startLeafAppServers(t *testing.T, count int, opts AppTestOptions) []*service.TeleportProcess {
log := utils.NewLoggerForTests()
configs := make([]*servicecfg.Config, count)
for i := 0; i < count; i++ {
laConf := servicecfg.MakeDefaultConfig()
laConf.Clock = opts.Clock
laConf.Console = nil
laConf.Log = log
laConf.DataDir = t.TempDir()
laConf.SetToken("static-token-value")
laConf.SetAuthServerAddress(utils.NetAddr{
AddrNetwork: "tcp",
Addr: p.leafCluster.Web,
})
laConf.Auth.Enabled = false
laConf.Proxy.Enabled = false
laConf.SSH.Enabled = false
laConf.Apps.Enabled = true
laConf.CircuitBreakerConfig = breaker.NoopBreakerConfig()
laConf.Apps.MonitorCloseChannel = opts.MonitorCloseChannel
laConf.Apps.Apps = append([]servicecfg.App{
{
Name: p.leafAppName,
URI: p.leafAppURI,
PublicAddr: p.leafAppPublicAddr,
},
{
Name: p.leafWSAppName,
URI: p.leafWSAppURI,
PublicAddr: p.leafWSPublicAddr,
},
{
Name: p.leafWSSAppName,
URI: p.leafWSSAppURI,
PublicAddr: p.leafWSSPublicAddr,
},
{
Name: p.leafTCPAppName,
URI: p.leafTCPAppURI,
PublicAddr: p.leafTCPPublicAddr,
},
{
Name: "dumper-leaf",
URI: p.dumperAppURI,
PublicAddr: "dumper-leaf.example.com",
Rewrite: &servicecfg.Rewrite{
Headers: []servicecfg.Header{
{
Name: "X-Teleport-Cluster",
Value: "leaf",
},
// In leaf clusters internal.logins variable is
// populated with the user's root role logins.
{
Name: "X-Teleport-Login",
Value: "{{internal.logins}}",
},
{
Name: "X-External-Env",
Value: "{{external.env}}",
},
// Make sure can rewrite Host header.
{
Name: "Host",
Value: "example.com",
},
// Make sure can rewrite existing header.
{
Name: "X-Existing",
Value: "rewritten-existing-header",
},
// Make sure can't rewrite Teleport headers.
{
Name: teleport.AppJWTHeader,
Value: "rewritten-app-jwt-header",
},
{
Name: teleport.AppCFHeader,
Value: "rewritten-app-cf-header",
},
{
Name: common.TeleportAPIErrorHeader,
Value: "rewritten-x-teleport-api-error",
},
{
Name: forward.XForwardedFor,
Value: "rewritten-x-forwarded-for-header",
},
{
Name: forward.XForwardedHost,
Value: "rewritten-x-forwarded-host-header",
},
{
Name: forward.XForwardedProto,
Value: "rewritten-x-forwarded-proto-header",
},
{
Name: forward.XForwardedServer,
Value: "rewritten-x-forwarded-server-header",
},
{
Name: common.XForwardedSSL,
Value: "rewritten-x-forwarded-ssl-header",
},
{
Name: forward.XForwardedPort,
Value: "rewritten-x-forwarded-port-header",
},
},
},
},
}, opts.ExtraLeafApps...)
configs[i] = laConf
}
servers, err := p.leafCluster.StartApps(configs)
require.NoError(t, err)
require.Equal(t, len(configs), len(servers))
for i, appServer := range servers {
srv := appServer
t.Cleanup(func() {
require.NoError(t, srv.Close())
})
waitForAppServer(t, p.leafCluster.Tunnel, p.leafAppClusterName, srv.Config.HostUUID, configs[i].Apps.Apps)
}
return servers
}
func waitForAppRegInRemoteSiteCache(t *testing.T, tunnel reversetunnel.Server, clusterName string, cfgApps []servicecfg.App, hostUUID string) {
require.Eventually(t, func() bool {
site, err := tunnel.GetSite(clusterName)
require.NoError(t, err)
ap, err := site.CachingAccessPoint()
require.NoError(t, err)
apps, err := ap.GetApplicationServers(context.Background(), apidefaults.Namespace)
require.NoError(t, err)
counter := 0
for _, v := range apps {
if v.GetHostID() == hostUUID {
counter++
}
}
return len(cfgApps) == counter
}, time.Minute*2, time.Millisecond*200)
}