14
envoy_admin_v3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3"
15
"github.com/pkg/errors"
17
"github.com/kumahq/kuma/pkg/core/ca"
18
core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
19
"github.com/kumahq/kuma/pkg/core/resources/manager"
20
core_model "github.com/kumahq/kuma/pkg/core/resources/model"
21
envoy_admin_tls "github.com/kumahq/kuma/pkg/envoy/admin/tls"
22
util_proto "github.com/kumahq/kuma/pkg/util/proto"
25
type EnvoyAdminClient interface {
26
PostQuit(ctx context.Context, dataplane *core_mesh.DataplaneResource) error
28
Stats(ctx context.Context, proxy core_model.ResourceWithAddress) ([]byte, error)
29
Clusters(ctx context.Context, proxy core_model.ResourceWithAddress) ([]byte, error)
30
ConfigDump(ctx context.Context, proxy core_model.ResourceWithAddress) ([]byte, error)
33
type envoyAdminClient struct {
34
rm manager.ResourceManager
35
caManagers ca.Managers
36
defaultAdminPort uint32
38
caCertPool *x509.CertPool
39
clientCert *tls.Certificate
42
func NewEnvoyAdminClient(rm manager.ResourceManager, caManagers ca.Managers, adminPort uint32) EnvoyAdminClient {
43
client := &envoyAdminClient{
45
caManagers: caManagers,
46
defaultAdminPort: adminPort,
51
func (a *envoyAdminClient) buildHTTPClient(ctx context.Context) (*http.Client, error) {
52
caCertPool, clientCert, err := a.mtlsCerts(ctx)
58
Transport: &http.Transport{
59
DialContext: (&net.Dialer{
60
Timeout: 3 * time.Second,
62
TLSHandshakeTimeout: 3 * time.Second,
63
TLSClientConfig: &tls.Config{
64
MinVersion: tls.VersionTLS12,
66
Certificates: []tls.Certificate{clientCert},
69
Timeout: 5 * time.Second,
74
func (a *envoyAdminClient) mtlsCerts(ctx context.Context) (x509.CertPool, tls.Certificate, error) {
75
if a.caCertPool == nil {
76
ca, err := envoy_admin_tls.LoadCA(ctx, a.rm)
78
return x509.CertPool{}, tls.Certificate{}, errors.Wrap(err, "could not load the CA")
80
caCertPool := x509.NewCertPool()
81
caCert, err := x509.ParseCertificate(ca.Certificate[0])
83
return x509.CertPool{}, tls.Certificate{}, errors.Wrap(err, "could not parse CA")
85
caCertPool.AddCert(caCert)
87
pair, err := envoy_admin_tls.GenerateClientCert(ca)
89
return x509.CertPool{}, tls.Certificate{}, errors.Wrap(err, "could not generate a client certificate")
91
clientCert, err := tls.X509KeyPair(pair.CertPEM, pair.KeyPEM)
93
return x509.CertPool{}, tls.Certificate{}, errors.Wrap(err, "could not parse the client certificate")
98
a.caCertPool = caCertPool
99
a.clientCert = &clientCert
101
return *a.caCertPool, *a.clientCert, nil
105
quitquitquit = "quitquitquit"
108
func (a *envoyAdminClient) PostQuit(ctx context.Context, dataplane *core_mesh.DataplaneResource) error {
109
httpClient, err := a.buildHTTPClient(ctx)
114
url := fmt.Sprintf("https://%s/%s", dataplane.AdminAddress(a.defaultAdminPort), quitquitquit)
115
request, err := http.NewRequestWithContext(ctx, "POST", url, nil)
121
response, err := httpClient.Do(request)
122
if errors.Is(err, io.EOF) {
126
return errors.Wrapf(err, "unable to send POST to %s", quitquitquit)
128
defer response.Body.Close()
130
if response.StatusCode != http.StatusOK {
131
return errors.Errorf("envoy response [%d %s] [%s]", response.StatusCode, response.Status, response.Body)
137
func (a *envoyAdminClient) Stats(ctx context.Context, proxy core_model.ResourceWithAddress) ([]byte, error) {
138
return a.executeRequest(ctx, proxy, "stats")
141
func (a *envoyAdminClient) Clusters(ctx context.Context, proxy core_model.ResourceWithAddress) ([]byte, error) {
142
return a.executeRequest(ctx, proxy, "clusters")
145
func (a *envoyAdminClient) ConfigDump(ctx context.Context, proxy core_model.ResourceWithAddress) ([]byte, error) {
146
configDump, err := a.executeRequest(ctx, proxy, "config_dump")
151
cd := &envoy_admin_v3.ConfigDump{}
152
if err := util_proto.FromJSON(configDump, cd); err != nil {
156
if err := Sanitize(cd); err != nil {
160
return util_proto.ToJSONIndent(cd, " ")
163
func (a *envoyAdminClient) executeRequest(ctx context.Context, proxy core_model.ResourceWithAddress, path string) ([]byte, error) {
164
var httpClient *http.Client
168
switch proxy.(type) {
169
case *core_mesh.DataplaneResource:
170
httpClient, err = a.buildHTTPClient(ctx)
175
case *core_mesh.ZoneIngressResource, *core_mesh.ZoneEgressResource:
176
httpClient, err = a.buildHTTPClient(ctx)
182
return nil, errors.New("unsupported proxy type")
185
if host, _, err := net.SplitHostPort(proxy.AdminAddress(a.defaultAdminPort)); err == nil && host == "127.0.0.1" {
186
httpClient = &http.Client{
187
Timeout: 5 * time.Second,
192
u.Host = proxy.AdminAddress(a.defaultAdminPort)
194
request, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
199
response, err := httpClient.Do(request)
201
return nil, errors.Wrapf(err, "unable to send GET to %s", "config_dump")
203
defer response.Body.Close()
205
if response.StatusCode != http.StatusOK {
206
return nil, errors.Errorf("envoy response [%d %s] [%s]", response.StatusCode, response.Status, response.Body)
209
resp, err := io.ReadAll(response.Body)