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
|
|
|
|
2022-05-27 11:38:52 +00:00
|
|
|
package local
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-08-29 15:15:34 +00:00
|
|
|
proxysnapshot "github.com/hashicorp/consul/internal/mesh/proxy-snapshot"
|
2023-03-03 20:27:53 +00:00
|
|
|
"time"
|
2022-05-27 11:38:52 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
|
|
|
|
|
|
"github.com/hashicorp/consul/agent/local"
|
|
|
|
"github.com/hashicorp/consul/agent/proxycfg"
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
|
|
"github.com/hashicorp/consul/agent/token"
|
|
|
|
)
|
|
|
|
|
2023-03-03 20:27:53 +00:00
|
|
|
const resyncFrequency = 30 * time.Second
|
|
|
|
|
2022-05-27 11:38:52 +00:00
|
|
|
const source proxycfg.ProxySource = "local"
|
|
|
|
|
|
|
|
// SyncConfig contains the dependencies required by Sync.
|
|
|
|
type SyncConfig struct {
|
|
|
|
// Manager is the proxycfg Manager with which proxy services will be registered.
|
|
|
|
Manager ConfigManager
|
|
|
|
|
|
|
|
// State is the agent's local state that will be watched for proxy registrations.
|
|
|
|
State *local.State
|
|
|
|
|
|
|
|
// Tokens is used to retrieve a fallback ACL token if a service is registered
|
|
|
|
// without one.
|
|
|
|
Tokens *token.Store
|
|
|
|
|
|
|
|
// NodeName is the name of the local agent node.
|
|
|
|
NodeName string
|
|
|
|
|
|
|
|
// Logger will be used to write log messages.
|
|
|
|
Logger hclog.Logger
|
2023-03-03 20:27:53 +00:00
|
|
|
|
|
|
|
// ResyncFrequency is how often to do a resync and recreate any terminated
|
|
|
|
// watches.
|
|
|
|
ResyncFrequency time.Duration
|
2022-05-27 11:38:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Sync watches the agent's local state and registers/deregisters services with
|
|
|
|
// the proxycfg Manager ahead-of-time so they're ready immediately when a proxy
|
|
|
|
// begins an xDS stream.
|
|
|
|
//
|
|
|
|
// It runs until the given context is canceled, so should be called it its own
|
|
|
|
// goroutine.
|
|
|
|
//
|
|
|
|
// Note: proxy service definitions from the agent's local state will always
|
|
|
|
// overwrite definitions of the same service from other sources (e.g. the
|
|
|
|
// catalog).
|
|
|
|
func Sync(ctx context.Context, cfg SyncConfig) {
|
|
|
|
// Single item buffer is enough since there is no data transferred so this is
|
|
|
|
// "level triggering" and we can't miss actual data.
|
|
|
|
stateCh := make(chan struct{}, 1)
|
|
|
|
|
|
|
|
cfg.State.Notify(stateCh)
|
|
|
|
defer cfg.State.StopNotify(stateCh)
|
|
|
|
|
2023-03-03 20:27:53 +00:00
|
|
|
var resyncCh <-chan time.Time
|
2022-05-27 11:38:52 +00:00
|
|
|
for {
|
|
|
|
sync(cfg)
|
|
|
|
|
2023-03-03 20:27:53 +00:00
|
|
|
if resyncCh == nil && cfg.ResyncFrequency > 0 {
|
|
|
|
resyncCh = time.After(cfg.ResyncFrequency)
|
|
|
|
}
|
|
|
|
|
2022-05-27 11:38:52 +00:00
|
|
|
select {
|
|
|
|
case <-stateCh:
|
|
|
|
// Wait for a state change.
|
2023-03-03 20:27:53 +00:00
|
|
|
case <-resyncCh:
|
|
|
|
resyncCh = nil
|
2022-05-27 11:38:52 +00:00
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func sync(cfg SyncConfig) {
|
|
|
|
cfg.Logger.Trace("syncing proxy services from local state")
|
|
|
|
|
|
|
|
services := cfg.State.AllServices()
|
|
|
|
|
|
|
|
// Traverse the local state and ensure all proxy services are registered
|
|
|
|
for sid, svc := range services {
|
|
|
|
if !svc.Kind.IsProxy() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retrieve the token used to register the service, or fallback to the
|
|
|
|
// default user token. This token is expected to match the token used in
|
|
|
|
// the xDS request for this data.
|
|
|
|
token := cfg.State.ServiceToken(sid)
|
|
|
|
if token == "" {
|
|
|
|
token = cfg.Tokens.UserToken()
|
|
|
|
}
|
|
|
|
|
|
|
|
id := proxycfg.ProxyID{
|
|
|
|
ServiceID: sid,
|
|
|
|
NodeName: cfg.NodeName,
|
|
|
|
|
|
|
|
// Note: we *intentionally* don't set Token here. All watches on local
|
|
|
|
// services use the same ACL token, regardless of whatever token is
|
|
|
|
// presented in the xDS stream (the token presented to the xDS server
|
|
|
|
// is checked before the watch is created).
|
|
|
|
Token: "",
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(banks): need to work out when to default some stuff. For example
|
|
|
|
// Proxy.LocalServicePort is practically necessary for any sidecar and can
|
|
|
|
// default to the port of the sidecar service, but only if it's already
|
|
|
|
// registered and once we get past here, we don't have enough context to
|
|
|
|
// know that so we'd need to set it here if not during registration of the
|
|
|
|
// proxy service. Sidecar Service in the interim can do that, but we should
|
|
|
|
// validate more generally that that is always true.
|
|
|
|
err := cfg.Manager.Register(id, svc, source, token, true)
|
|
|
|
if err != nil {
|
|
|
|
cfg.Logger.Error("failed to watch proxy service",
|
|
|
|
"service", sid.String(),
|
|
|
|
"error", err,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now see if any proxies were removed
|
|
|
|
for _, proxyID := range cfg.Manager.RegisteredProxies(source) {
|
|
|
|
if _, ok := services[proxyID.ServiceID]; !ok {
|
|
|
|
cfg.Manager.Deregister(proxyID, source)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//go:generate mockery --name ConfigManager --inpackage
|
|
|
|
type ConfigManager interface {
|
2023-08-29 15:15:34 +00:00
|
|
|
Watch(id proxycfg.ProxyID) (<-chan proxysnapshot.ProxySnapshot, proxysnapshot.CancelFunc)
|
2022-05-27 11:38:52 +00:00
|
|
|
Register(proxyID proxycfg.ProxyID, service *structs.NodeService, source proxycfg.ProxySource, token string, overwrite bool) error
|
|
|
|
Deregister(proxyID proxycfg.ProxyID, source proxycfg.ProxySource)
|
|
|
|
RegisteredProxies(source proxycfg.ProxySource) []proxycfg.ProxyID
|
|
|
|
}
|