validate certs and get stats (#16139)

This commit is contained in:
malizz 2023-02-02 14:24:18 -08:00 committed by GitHub
parent 28a8de3e7e
commit ffd311c2b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 174 additions and 6 deletions

View File

@ -81,7 +81,10 @@ func (c *cmd) Run(args []string) int {
c.UI.Error("error running the tests: " + err.Error())
return 1
}
c.UI.Output(output)
for _, o := range output {
c.UI.Output(o)
}
return 0
}
@ -97,6 +100,7 @@ const (
synopsis = "Troubleshoots service mesh issues from the current envoy instance"
help = `
Usage: consul troubleshoot proxy [options]
Connects to local envoy proxy and troubleshoots service mesh communication issues.
Requires an upstream service envoy identifier.
Examples:

54
troubleshoot/certs.go Normal file
View File

@ -0,0 +1,54 @@
package troubleshoot
import (
"fmt"
"time"
envoy_admin_v3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3"
"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
"google.golang.org/protobuf/encoding/protojson"
)
func (t *Troubleshoot) validateCerts(certs *envoy_admin_v3.Certificates) error {
// TODO: we can probably warn if the expiration date is close
var resultErr error
now := time.Now()
for _, cert := range certs.GetCertificates() {
for _, cacert := range cert.GetCaCert() {
if now.After(cacert.GetExpirationTime().AsTime()) {
resultErr = multierror.Append(resultErr, errors.New("Ca cert is expired"))
}
}
for _, cc := range cert.GetCertChain() {
if now.After(cc.GetExpirationTime().AsTime()) {
resultErr = multierror.Append(resultErr, errors.New("cert chain is expired"))
}
}
}
return resultErr
}
func (t *Troubleshoot) getEnvoyCerts() (*envoy_admin_v3.Certificates, error) {
certsRaw, err := t.request("certs?format=json")
if err != nil {
return nil, fmt.Errorf("error in requesting Envoy Admin API /certs endpoint: %w", err)
}
certs := &envoy_admin_v3.Certificates{}
unmarshal := &protojson.UnmarshalOptions{
DiscardUnknown: true,
}
err = unmarshal.Unmarshal(certsRaw, certs)
if err != nil {
return nil, fmt.Errorf("error in unmarshalling /certs response: %w", err)
}
t.envoyCerts = certs
return certs, nil
}

47
troubleshoot/stats.go Normal file
View File

@ -0,0 +1,47 @@
package troubleshoot
import (
"encoding/json"
"fmt"
envoy_admin_v3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3"
)
type statsJson struct {
Stats []simpleMetric `json:"stats"`
}
type simpleMetric struct {
Value int64 `json:"value,omitempty"`
Name string `json:"name,omitempty"`
}
func (t *Troubleshoot) getEnvoyStats(filter string) ([]*envoy_admin_v3.SimpleMetric, error) {
var resultErr error
jsonRaw, err := t.request(fmt.Sprintf("stats?format=json&filter=%s&type=Counters", filter))
if err != nil {
return nil, fmt.Errorf("error in requesting envoy Admin API /stats endpoint: %w", err)
}
var rawStats statsJson
err = json.Unmarshal(jsonRaw, &rawStats)
if err != nil {
return nil, fmt.Errorf("could not unmarshal /stats response: %w", err)
}
stats := []*envoy_admin_v3.SimpleMetric{}
for _, s := range rawStats.Stats {
stat := &envoy_admin_v3.SimpleMetric{
Value: uint64(s.Value),
Name: s.Name,
}
stats = append(stats, stat)
}
t.envoyStats = stats
return stats, resultErr
}

View File

@ -5,8 +5,10 @@ import (
"net"
envoy_admin_v3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3"
"github.com/pkg/errors"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/go-multierror"
)
type Troubleshoot struct {
@ -21,11 +23,9 @@ type TroubleshootInfo struct {
envoyClusters *envoy_admin_v3.Clusters
envoyConfigDump *envoy_admin_v3.ConfigDump
envoyCerts *envoy_admin_v3.Certificates
envoyStats EnvoyStats
envoyStats []*envoy_admin_v3.SimpleMetric
}
type EnvoyStats []envoy_admin_v3.SimpleMetric
func NewTroubleshoot(envoyIP *net.IPAddr, envoyPort string) (*Troubleshoot, error) {
cfg := api.DefaultConfig()
c, err := api.NewClient(cfg)
@ -39,8 +39,35 @@ func NewTroubleshoot(envoyIP *net.IPAddr, envoyPort string) (*Troubleshoot, erro
}, nil
}
func (t *Troubleshoot) RunAllTests(upstreamSNI string) (string, error) {
return "", fmt.Errorf("not implemented")
func (t *Troubleshoot) RunAllTests(upstreamEnvoyID string) ([]string, error) {
var resultErr error
var output []string
certs, err := t.getEnvoyCerts()
if err != nil {
resultErr = multierror.Append(resultErr, fmt.Errorf("unable to get certs: %w", err))
}
if certs != nil && len(certs.GetCertificates()) != 0 {
err = t.validateCerts(certs)
if err != nil {
resultErr = multierror.Append(resultErr, fmt.Errorf("unable to validate certs: %w", err))
} else {
output = append(output, "certs are valid")
}
} else {
resultErr = multierror.Append(resultErr, errors.New("no certificate found"))
}
// getStats usage example
// rejectionStats, err := t.getEnvoyStats("update_rejected")
// if err != nil {
// resultErr = multierror.Append(resultErr, err)
// }
return output, resultErr
}
func (t *Troubleshoot) GetUpstreams() ([]string, error) {

36
troubleshoot/utils.go Normal file
View File

@ -0,0 +1,36 @@
package troubleshoot
import (
"context"
"fmt"
"io"
"net/http"
)
func (t *Troubleshoot) request(path string) ([]byte, error) {
client := &http.Client{}
url := fmt.Sprintf("http://%v:%s/%s", t.envoyAddr.IP, t.envoyAdminPort, path)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
resp, err := client.Do(req.WithContext(context.Background()))
if err != nil {
return nil, err
}
if resp != nil {
defer resp.Body.Close()
}
if resp != nil && resp.StatusCode == http.StatusNotFound {
return nil, fmt.Errorf("ErrBackendNotMounted")
}
rawConfig, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return rawConfig, nil
}