Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions csds-client/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# CSDS Client
[Client status discovery service (CSDS)](https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/status/v3/csds.proto) is a generic xDS API that can be used to get information about data plane clients from the control plane’s point of view. It is useful to enhance debuggability of the service mesh, where lots of xDS clients are connected to the control plane.<br/>
The CSDS client is developed as a generic tool that can be used/extended to work with different xDS control planes.<br/>
For now, this initial version of this CSDS client only supports GCP's [Traffic Director](https://cloud.google.com/traffic-director).
For now, this initial version of this CSDS client only supports:
* GCP's [Traffic Director](https://cloud.google.com/traffic-director).
* Envoy's [go-control-plane](https://github.com/envoyproxy/go-control-plane) based implementations.

<br/>Before you start, you'll need [Go](https://golang.org/) installed.

# Building
Expand Down Expand Up @@ -79,4 +82,4 @@ Client ID xDS stream type Config Status
<detailed config>)
OR
(Config has been saved to <output_file>)
```
```
33 changes: 22 additions & 11 deletions csds-client/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,32 @@ import (
"time"
)

// Platforms supported by client implementations
// where `go` is an xDS implementation based on
// https://github.com/envoyproxy/go-control-plane
var (
SupportedPlatforms []string = []string{"gcp", "go"}
)

// ClientOptions are options that are common to use in all the version implementations of client
// TODO: If ClientOptions will no longer be common to use in all the version, it will need to be
// implemented in version packages
type ClientOptions struct {
Uri string
Platform string
AuthnMode string
RequestFile string
RequestYaml string
Jwt string
ConfigFile string
MonitorInterval time.Duration
Visualization bool
FilterMode string
FilterPattern string
Uri string
Authority string
Platform string
AuthnMode string
RequestFile string
RequestYaml string
Jwt string
ConfigFile string
MonitorInterval time.Duration
Visualization bool
FilterMode string
FilterPattern string
TLSCertFilepath string
TLSPrivateKeyFilepath string
TLSCACertsFilepath string
}

// Client implements CSDS Client of a particular version. Upon creation of the new client it is
Expand Down
58 changes: 58 additions & 0 deletions csds-client/client/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package util
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"envoy-tools/csds-client/client"
Expand Down Expand Up @@ -33,6 +34,7 @@ import (
envoy_extensions_accesslog_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/file/v3"
envoy_extensions_filters_http_cors_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/cors/v3"
envoy_extensions_filters_http_fault_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/fault/v3"
envoy_extensions_filters_http_jwt_authn_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/jwt_authn/v3"
envoy_extensions_filters_http_router_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
envoy_extensions_filters_network_http_connection_manager_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
"github.com/ghodss/yaml"
Expand Down Expand Up @@ -125,6 +127,10 @@ func (r *TypeResolver) FindMessageByURL(url string) (protoreflect.MessageType, e
case "type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog":
fileAccessLog := envoy_extensions_accesslog_v3.FileAccessLog{}
return fileAccessLog.ProtoReflect().Type(), nil
case "type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication":
jwtAuthn := envoy_extensions_filters_http_jwt_authn_v3.JwtAuthentication{}
return jwtAuthn.ProtoReflect().Type(), nil

default:
dummy := anypb.Any{}
return dummy.ProtoReflect().Type(), nil
Expand Down Expand Up @@ -423,6 +429,58 @@ func ConnToGCPWithAuto(uri string) (*grpc.ClientConn, error) {
return clientConn, nil
}

// ConnToXDS connects to the the given gRPC target with the provided
// DialOptions.
func ConnToXDS(uri string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
return grpc.Dial(uri, opts...)
}

// DialOptions parses client.ClientOptions and sets up grpc DialOption
// to use when dialing a gRPC service.
//
// If no tls configuration was provided, it defaults to disabling gRPC transport
// security.
//
// Otherwise, the tls configuration is used to build a TLS config to use on
// the client connection.
//
// The optional HTTP/2 :authority header is set if `opts.Authority` is
// provided.
// The same value is also used in the ClientHello message for SNI, and to
// verify the server name on the returned server certificate.
func DialOptions(opts *client.ClientOptions) ([]grpc.DialOption, error) {
var dialOpts []grpc.DialOption
if opts.TLSCertFilepath != "" && opts.TLSPrivateKeyFilepath != "" {
clientCert, err := tls.LoadX509KeyPair(opts.TLSCertFilepath, opts.TLSPrivateKeyFilepath)
if err != nil {
return nil, fmt.Errorf("could not load client cert/key pair: %v", err)
}
tlsCfg := &tls.Config{
Certificates: []tls.Certificate{clientCert},
}
// Load root CAs to use when verifying server certificate.
if opts.TLSCACertsFilepath != "" {
caCertsData, err := ioutil.ReadFile(opts.TLSCACertsFilepath)
if err != nil {
return nil, fmt.Errorf("could not read CA certs file: %v", err)
}
caCertsPool := x509.NewCertPool()
caCertsPool.AppendCertsFromPEM(caCertsData)
tlsCfg.RootCAs = caCertsPool
if opts.Authority != "" {
tlsCfg.ServerName = opts.Authority
}
}
dialOpts = append(dialOpts, grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)))
} else {
dialOpts = append(dialOpts, grpc.WithInsecure())
if opts.Authority != "" {
dialOpts = append(dialOpts, grpc.WithAuthority(opts.Authority))
}
}
return dialOpts, nil
}

// ParseYamlFileToMap parses yaml file to map
func ParseYamlFileToMap(path string) (map[string]interface{}, error) {
// parse yaml to json
Expand Down
10 changes: 9 additions & 1 deletion csds-client/client/v2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ func (c *ClientV2) parseNodeMatcher() error {
return fmt.Errorf("missing field %v in NodeMatcher", key)
}
}
case "go":
default:
return fmt.Errorf("%s platform is not supported, list of supported platforms: gcp", c.opts.Platform)
return fmt.Errorf("%s platform is not supported. supported platforms: %v", c.opts.Platform, client.SupportedPlatforms)
}

if c.opts.FilterMode != "" && c.opts.FilterMode != "prefix" && c.opts.FilterMode != "suffix" && c.opts.FilterMode != "regex" {
Expand Down Expand Up @@ -99,6 +100,13 @@ func (c *ClientV2) connWithAuth() error {
return err
}
return nil
case "go":
dialOpts, err := clientutil.DialOptions(&c.opts)
if err != nil {
return fmt.Errorf("could not build gRPC client dial options: %v", err)
}
c.clientConn, err = grpc.Dial(c.opts.Uri, dialOpts...)
return err
default:
return errors.New("auto authentication mode for this platform is not supported. Please use jwt_file instead")
}
Expand Down
19 changes: 13 additions & 6 deletions csds-client/client/v3/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ func (c *ClientV3) parseNodeMatcher() error {
return fmt.Errorf("missing field %v in NodeMatcher", key)
}
}
case "go":
default:
return fmt.Errorf("%s platform is not supported, list of supported platforms: gcp", c.opts.Platform)
return fmt.Errorf("%s platform is not supported. supported platforms: %v", c.opts.Platform, client.SupportedPlatforms)
}

if c.opts.FilterMode != "" && c.opts.FilterMode != "prefix" && c.opts.FilterMode != "suffix" && c.opts.FilterMode != "regex" {
Expand All @@ -84,7 +85,7 @@ func (c *ClientV3) connWithAuth() error {
}
return nil
default:
return fmt.Errorf("%s platform is not supported, list of supported platforms: gcp", c.opts.Platform)
return fmt.Errorf("%s platform is not supported for jwt authentication, list of supported platforms: gcp", c.opts.Platform)
}

case "auto":
Expand All @@ -99,6 +100,13 @@ func (c *ClientV3) connWithAuth() error {
return err
}
return nil
case "go":
dialOpts, err := clientutil.DialOptions(&c.opts)
if err != nil {
return fmt.Errorf("could not build gRPC client dial options: %v", err)
}
c.clientConn, err = grpc.Dial(c.opts.Uri, dialOpts...)
return err
default:
return errors.New("auto authentication mode for this platform is not supported. Please use jwt_file instead")
}
Expand All @@ -112,8 +120,8 @@ func New(option client.ClientOptions) (*ClientV3, error) {
c := &ClientV3{
opts: option,
}
if c.opts.Platform != "gcp" {
return nil, fmt.Errorf("%s platform is not supported, list of supported platforms: gcp", c.opts.Platform)
if c.opts.Platform != "gcp" && c.opts.Platform != "go" {
return nil, fmt.Errorf("%s platform is not supported, list of supported platforms: [gcp, go]", c.opts.Platform)
}

if err := c.parseNodeMatcher(); err != nil {
Expand Down Expand Up @@ -218,9 +226,8 @@ func printOutResponse(response *csdspb_v3.ClientStatusResponse, opts client.Clie
if response.GetConfig() == nil || len(response.GetConfig()) == 0 {
fmt.Printf("No xDS clients connected.\n")
return nil
} else {
fmt.Printf("%-50s %-30s %-30s \n", "Client ID", "xDS stream type", "Config Status")
}
fmt.Printf("%-50s %-30s %-30s \n", "Client ID", "xDS stream type", "Config Status")

var hasXdsConfig bool

Expand Down
41 changes: 29 additions & 12 deletions csds-client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import (
client_v2 "envoy-tools/csds-client/client/v2"
client_v3 "envoy-tools/csds-client/client/v3"
"flag"
"fmt"
"log"
"time"
)

// flag vars
var uri string
var authority string
var platform string
var authnMode string
var apiVersion string
Expand All @@ -22,10 +24,14 @@ var monitorInterval time.Duration
var visualization bool
var filterMode string
var filterPattern string
var tlsCertFilepath string
var tlsCACertsFilepath string
var tlsPrivateKeyFilepath string

// const default values for flag vars
const (
uriDefault string = "trafficdirector.googleapis.com:443"
authorityDefault string = ""
platformDefault string = "gcp"
authnModeDefault string = "auto"
apiVersionDefault string = "v2"
Expand All @@ -37,12 +43,16 @@ const (
visualizationDefault bool = false
filterModeDefault string = ""
filterPatternDefault string = ""
TLSCertFilepath string = ""
TLSPrivateKeyFilepath string = ""
TLSCACertsFilepath string = ""
)

// init binds flags with variables
func init() {
flag.StringVar(&uri, "service_uri", uriDefault, "the uri of the service to connect to")
flag.StringVar(&platform, "platform", platformDefault, "the platform (e.g. gcp, aws, ...)")
flag.StringVar(&authority, "authority", authorityDefault, "the :authority header to use when connecting to uri")
flag.StringVar(&platform, "platform", platformDefault, fmt.Sprintf("the target platform, one of %v", client.SupportedPlatforms))
flag.StringVar(&authnMode, "authn_mode", authnModeDefault, "the method to use for authentication (e.g. auto, jwt, ...)")
flag.StringVar(&apiVersion, "api_version", apiVersionDefault, "which xds api major version to use (e.g. v2, v3, ...)")
flag.StringVar(&requestFile, "request_file", requestFileDefault, "yaml file that defines the csds request")
Expand All @@ -53,23 +63,30 @@ func init() {
flag.BoolVar(&visualization, "visualization", visualizationDefault, "option to visualize the relationship between xDS")
flag.StringVar(&filterMode, "filter_mode", filterModeDefault, "the filter mode for the filter on xDS nodes to be returned (e.g. prefix, suffix, regex, ...)")
flag.StringVar(&filterPattern, "filter_pattern", filterPatternDefault, "the filter pattern for the filter on xDS nodes to be returned")
flag.StringVar(&tlsCertFilepath, "cert", tlsCertFilepath, "filepath to the client TLS certificate")
flag.StringVar(&tlsPrivateKeyFilepath, "key", tlsPrivateKeyFilepath, "filepath to the client TLS private key")
flag.StringVar(&tlsCACertsFilepath, "cacert", tlsCACertsFilepath, "filepath to the CAs certs used to verify the server's certificate")
}

func main() {
flag.Parse()

clientOpts := client.ClientOptions{
Uri: uri,
Platform: platform,
AuthnMode: authnMode,
RequestFile: requestFile,
RequestYaml: requestYaml,
Jwt: jwt,
ConfigFile: configFile,
MonitorInterval: monitorInterval,
Visualization: visualization,
FilterMode: filterMode,
FilterPattern: filterPattern,
Uri: uri,
Authority: authority,
Platform: platform,
AuthnMode: authnMode,
RequestFile: requestFile,
RequestYaml: requestYaml,
Jwt: jwt,
ConfigFile: configFile,
MonitorInterval: monitorInterval,
Visualization: visualization,
FilterMode: filterMode,
FilterPattern: filterPattern,
TLSCertFilepath: tlsCertFilepath,
TLSCACertsFilepath: tlsCACertsFilepath,
TLSPrivateKeyFilepath: tlsPrivateKeyFilepath,
}

var c client.Client
Expand Down