validate certs and get stats (#16139)
This commit is contained in:
parent
28a8de3e7e
commit
ffd311c2b7
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue