2023-03-28 18:39:22 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
2023-08-11 13:12:13 +00:00
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
2023-03-28 18:39:22 +00:00
|
|
|
|
2023-03-03 20:27:53 +00:00
|
|
|
package agent
|
|
|
|
|
|
|
|
import (
|
2024-09-05 14:50:46 +00:00
|
|
|
"context"
|
2023-03-03 20:27:53 +00:00
|
|
|
"encoding/json"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
2023-08-29 21:39:29 +00:00
|
|
|
"github.com/hashicorp/consul/agent/grpc-external/limiter"
|
2024-09-05 14:50:46 +00:00
|
|
|
"github.com/hashicorp/consul/agent/proxycfg"
|
2023-03-03 20:27:53 +00:00
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
|
|
"github.com/hashicorp/consul/api"
|
|
|
|
"github.com/hashicorp/consul/testrpc"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestAgent_local_proxycfg(t *testing.T) {
|
|
|
|
a := NewTestAgent(t, TestACLConfig())
|
|
|
|
defer a.Shutdown()
|
|
|
|
|
|
|
|
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
|
|
|
|
|
|
|
token := generateUUID()
|
|
|
|
|
|
|
|
svc := &structs.NodeService{
|
|
|
|
ID: "db",
|
|
|
|
Service: "db",
|
|
|
|
Port: 5000,
|
|
|
|
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
|
|
|
|
}
|
|
|
|
require.NoError(t, a.State.AddServiceWithChecks(svc, nil, token, true))
|
|
|
|
|
|
|
|
proxy := &structs.NodeService{
|
|
|
|
Kind: structs.ServiceKindConnectProxy,
|
|
|
|
ID: "db-sidecar-proxy",
|
|
|
|
Service: "db-sidecar-proxy",
|
|
|
|
Port: 5000,
|
|
|
|
// Set this internal state that we expect sidecar registrations to have.
|
|
|
|
LocallyRegisteredAsSidecar: true,
|
|
|
|
Proxy: structs.ConnectProxyConfig{
|
|
|
|
DestinationServiceName: "db",
|
2023-03-22 18:56:18 +00:00
|
|
|
Upstreams: structs.TestUpstreams(t, false),
|
2023-03-03 20:27:53 +00:00
|
|
|
},
|
|
|
|
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
|
|
|
|
}
|
|
|
|
require.NoError(t, a.State.AddServiceWithChecks(proxy, nil, token, true))
|
|
|
|
|
|
|
|
// This is a little gross, but this gives us the layered pair of
|
|
|
|
// local/catalog sources for now.
|
2023-08-29 15:15:34 +00:00
|
|
|
cfg := a.xdsServer.ProxyWatcher
|
2023-03-03 20:27:53 +00:00
|
|
|
|
|
|
|
var (
|
|
|
|
timer = time.After(100 * time.Millisecond)
|
|
|
|
timerFired = false
|
|
|
|
finalTimer <-chan time.Time
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
firstTime = true
|
2024-09-05 14:50:46 +00:00
|
|
|
ch <-chan *proxycfg.ConfigSnapshot
|
2023-03-03 20:27:53 +00:00
|
|
|
stc limiter.SessionTerminatedChan
|
2024-09-05 14:50:46 +00:00
|
|
|
cancel context.CancelFunc
|
2023-03-03 20:27:53 +00:00
|
|
|
)
|
|
|
|
defer func() {
|
|
|
|
if cancel != nil {
|
|
|
|
cancel()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
for {
|
|
|
|
if ch == nil {
|
|
|
|
// Sign up for a stream of config snapshots, in the same manner as the xds server.
|
|
|
|
sid := proxy.CompoundServiceID()
|
|
|
|
|
|
|
|
if firstTime {
|
|
|
|
firstTime = false
|
|
|
|
} else {
|
|
|
|
t.Logf("re-creating watch")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prior to fixes in https://github.com/hashicorp/consul/pull/16497
|
|
|
|
// this call to Watch() would deadlock.
|
|
|
|
var err error
|
2024-09-05 14:50:46 +00:00
|
|
|
ch, stc, _, cancel, err = cfg.Watch(sid, a.config.NodeName, token)
|
2023-03-03 20:27:53 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
select {
|
|
|
|
case <-stc:
|
|
|
|
t.Fatal("session unexpectedly terminated")
|
|
|
|
case snap, ok := <-ch:
|
|
|
|
if !ok {
|
|
|
|
t.Logf("channel is closed")
|
|
|
|
cancel()
|
|
|
|
ch, stc, cancel = nil, nil, nil
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
require.NotNil(t, snap)
|
|
|
|
if !timerFired {
|
|
|
|
t.Fatal("should not have gotten snapshot until after we manifested the token")
|
|
|
|
}
|
|
|
|
return
|
|
|
|
case <-timer:
|
|
|
|
timerFired = true
|
|
|
|
finalTimer = time.After(1 * time.Second)
|
|
|
|
|
|
|
|
// This simulates the eventual consistency of a token
|
|
|
|
// showing up on a server after it's creation by
|
|
|
|
// pre-creating the UUID and later using that as the
|
|
|
|
// initial SecretID for a real token.
|
|
|
|
gotToken := testWriteToken(t, a, &api.ACLToken{
|
|
|
|
AccessorID: generateUUID(),
|
|
|
|
SecretID: token,
|
|
|
|
Description: "my token",
|
|
|
|
ServiceIdentities: []*api.ACLServiceIdentity{{
|
|
|
|
ServiceName: "db",
|
|
|
|
}},
|
|
|
|
})
|
|
|
|
require.Equal(t, token, gotToken)
|
|
|
|
case <-finalTimer:
|
|
|
|
t.Fatal("did not receive a snapshot after the token manifested")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func testWriteToken(t *testing.T, a *TestAgent, tok *api.ACLToken) string {
|
|
|
|
req, _ := http.NewRequest("PUT", "/v1/acl/token", jsonReader(tok))
|
|
|
|
req.Header.Add("X-Consul-Token", "root")
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
a.srv.h.ServeHTTP(resp, req)
|
|
|
|
require.Equal(t, http.StatusOK, resp.Code)
|
|
|
|
|
|
|
|
dec := json.NewDecoder(resp.Body)
|
|
|
|
aclResp := &structs.ACLToken{}
|
|
|
|
require.NoError(t, dec.Decode(aclResp))
|
|
|
|
return aclResp.SecretID
|
|
|
|
}
|