Skip to content

Commit

Permalink
Istioctl: generate and use bearer token but don't persist it (istio#2…
Browse files Browse the repository at this point in the history
…6726)

* Istioctl: create and cache tokens on file system

* Make --plaintext a config file option

* Use gRPC credentials.PerRPCCredentials instead of recreating my own replacement

* Release notes

* Don't persist token on file system

* Remove home dir processing and token-file cli option

* Revised examples for 'experimental version' and 'proxy-status'

* Rebase against master
  • Loading branch information
esnible authored Aug 31, 2020
1 parent 362e3d8 commit 09ed125
Show file tree
Hide file tree
Showing 13 changed files with 247 additions and 28 deletions.
1 change: 1 addition & 0 deletions istioctl/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ var (
"cert-dir": env.RegisterStringVar("ISTIOCTL_CERT_DIR", "", "The istioctl --cert-dir override"),
"insecure": env.RegisterBoolVar("ISTIOCTL_INSECURE", false, "The istioctl --insecure override"),
"prefer-experimental": env.RegisterBoolVar("ISTIOCTL_PREFER_EXPERIMENTAL", false, "The istioctl should use experimental subcommand variants"),
"plaintext": env.RegisterBoolVar("ISTIOCTL_PLAINTEXT", false, "The istioctl --plaintext override"),
}
)

Expand Down
1 change: 1 addition & 0 deletions istioctl/cmd/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ authority default
cert-dir default
insecure default
istioNamespace istio-system default
plaintext default
prefer-experimental default
xds-address default
xds-port 15012 default
Expand Down
22 changes: 20 additions & 2 deletions istioctl/cmd/proxystatus.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,28 @@ Retrieves last sent and last acknowledged xDS sync from Istiod to each Envoy in
`,
Example: `# Retrieve sync status for all Envoys in a mesh
istioctl proxy-status
istioctl x proxy-status
# Retrieve sync diff for a single Envoy and Istiod
istioctl proxy-status istio-egressgateway-59585c5b9c-ndc59.istio-system
istioctl x proxy-status istio-egressgateway-59585c5b9c-ndc59.istio-system
# SECURITY OPTIONS
# Retrieve proxy status information directly from the control plane, using token security
# (This is the usual way to get the proxy-status with an out-of-cluster control plane.)
istioctl x ps --xds-address istio.cloudprovider.example.com:15012
# Retrieve proxy status information via Kubernetes config, using token security
# (This is the usual way to get the proxy-status with an in-cluster control plane.)
istioctl x proxy-status
# Retrieve proxy status information directly from the control plane, using RSA certificate security
# (Certificates must be obtained before this step. The --cert-dir flag lets istioctl bypass the Kubernetes API server.)
istioctl x ps --xds-address istio.example.com:15012 --cert-dir ~/.istio-certs
# Retrieve proxy status information via XDS from specific control plane in multi-control plane in-cluster configuration
# (Select a specific control plane in an in-cluster canary Istio configuration.)
istioctl x ps --xds-label istio.io/rev=default
`,
Aliases: []string{"ps"},
RunE: func(c *cobra.Command, args []string) error {
Expand Down
27 changes: 12 additions & 15 deletions istioctl/cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,24 +112,21 @@ func xdsVersionCommand() *cobra.Command {
}
return nil
}
versionCmd.Example = `# Retrieve version information directly from XDS, without security
istioctl x version --xds-address localhost:15012
versionCmd.Example = `# Retrieve version information directly from the control plane, using token security
# (This is the usual way to get the control plane version with an out-of-cluster control plane.)
istioctl x version --xds-address istio.cloudprovider.example.com:15012
# Retrieve version information directly from XDS, with security
# (the certificates must be retrieved before this step)
istioctl x version --xds-address localhost:15010 --cert-dir ~/.istio-certs
# Retrieve version information via Kubernetes config, using token security
# (This is the usual way to get the control plane version with an in-cluster control plane.)
istioctl x version
# Retrieve version information via XDS from all Istio pods in a Kubernetes cluster
# (without security)
istioctl x version --xds-port 15010
# Retrieve version information directly from the control plane, using RSA certificate security
# (Certificates must be obtained before this step. The --cert-dir flag lets istioctl bypass the Kubernetes API server.)
istioctl x version --xds-address istio.example.com:15012 --cert-dir ~/.istio-certs
# Retrieve version information via XDS from all Istio pods in a Kubernetes cluster
# (the certificates must be retrieved before this step)
istioctl x version --cert-dir ~/.istio-certs
# Retrieve version information via XDS from default control plane Istio pods
# in a Kubernetes cluster, without security
istioctl x version --xds-label istio.io/rev=default --xds-port 15010
# Retrieve version information via XDS from specific control plane in multi-control plane in-cluster configuration
# (Select a specific control plane in an in-cluster canary Istio configuration.)
istioctl x version --xds-label istio.io/rev=default
`

versionCmd.Flags().VisitAll(func(flag *pflag.Flag) {
Expand Down
8 changes: 8 additions & 0 deletions istioctl/pkg/clioptions/central.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ type CentralControlPlaneOptions struct {

// XDSSAN is the expected Subject Alternative Name of the XDS server
XDSSAN string

// Plaintext forces plain text communication (for talking to port 15010)
Plaintext bool
}

// AttachControlPlaneFlags attaches control-plane flags to a Cobra command.
Expand All @@ -65,12 +68,17 @@ func (o *CentralControlPlaneOptions) AttachControlPlaneFlags(cmd *cobra.Command)
"XDS Subject Alternative Name (for example istiod.istio-system.svc)")
cmd.PersistentFlags().BoolVar(&o.InsecureSkipVerify, "insecure", viper.GetBool("INSECURE"),
"Skip server certificate and domain verification. (NOT SECURE!)")
cmd.PersistentFlags().BoolVar(&o.Plaintext, "plaintext", viper.GetBool("PLAINTEXT"),
"Use plain-text HTTP/2 when connecting to server (no TLS).")
}

// ValidateControlPlaneFlags checks arguments for valid values and combinations
func (o *CentralControlPlaneOptions) ValidateControlPlaneFlags() error {
if o.Xds != "" && o.XdsPodLabel != "" {
return fmt.Errorf("either --xds-address or --xds-label, not both")
}
if o.Plaintext && o.CertDir != "" {
return fmt.Errorf("either --plaintext or --cert-dir, not both")
}
return nil
}
29 changes: 21 additions & 8 deletions istioctl/pkg/multixds/gather.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ func RequestAndProcessXds(dr *xdsapi.DiscoveryRequest, centralOpts *clioptions.C

// If Central Istiod case, just call it
if centralOpts.Xds != "" {
return xds.GetXdsResponse(dr, centralOpts)
dialOpts, err := xds.DialOptions(centralOpts, kubeClient)
if err != nil {
return nil, err
}
return xds.GetXdsResponse(dr, centralOpts, dialOpts)
}

// Self-administered case. Find all Istiods in revision using K8s, port-forward and call each in turn
Expand Down Expand Up @@ -67,6 +71,15 @@ func queryEachShard(all bool, dr *xdsapi.DiscoveryRequest, istioNamespace string
}

responses := []*xdsapi.DiscoveryResponse{}
xdsOpts := clioptions.CentralControlPlaneOptions{
XDSSAN: makeSan(istioNamespace, kubeClient.Revision()),
CertDir: centralOpts.CertDir,
Timeout: centralOpts.Timeout,
}
dialOpts, err := xds.DialOptions(&xdsOpts, kubeClient)
if err != nil {
return nil, err
}
for _, pod := range pods {
fw, err := kubeClient.NewPortForwarder(pod.Name, pod.Namespace, "localhost", 0, centralOpts.XdsPodPort)
if err != nil {
Expand All @@ -77,12 +90,8 @@ func queryEachShard(all bool, dr *xdsapi.DiscoveryRequest, istioNamespace string
return nil, err
}
defer fw.Close()
response, err := xds.GetXdsResponse(dr, &clioptions.CentralControlPlaneOptions{
Xds: fw.Address(),
XDSSAN: makeSan(istioNamespace, kubeClient.Revision()),
CertDir: centralOpts.CertDir,
Timeout: centralOpts.Timeout,
})
xdsOpts.Xds = fw.Address()
response, err := xds.GetXdsResponse(dr, &xdsOpts, dialOpts)
if err != nil {
return nil, fmt.Errorf("could not get XDS from discovery pod %q: %v", pod.Name, err)
}
Expand Down Expand Up @@ -136,7 +145,11 @@ func multiRequestAndProcessXds(all bool, dr *xdsapi.DiscoveryRequest, centralOpt

// If Central Istiod case, just call it
if centralOpts.Xds != "" {
response, err := xds.GetXdsResponse(dr, centralOpts)
dialOpts, err := xds.DialOptions(centralOpts, kubeClient)
if err != nil {
return nil, err
}
response, err := xds.GetXdsResponse(dr, centralOpts, dialOpts)
if err != nil {
return nil, err
}
Expand Down
45 changes: 44 additions & 1 deletion istioctl/pkg/xds/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,43 @@ package xds
// xds uses ADSC to call XDS

import (
"context"
"crypto/tls"
"fmt"

xdsapi "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"

"istio.io/istio/istioctl/pkg/clioptions"
"istio.io/istio/pilot/pkg/model"
"istio.io/istio/pkg/adsc"
"istio.io/istio/pkg/kube"
)

const (
// defaultExpirationSeconds is how long-lived a token to request (an hour)
defaultExpirationSeconds = 60 * 60

// Service account to create tokens in
tokenServiceAccount = "default"
tokenNamespace = "default"
)

var (
// Audience to create tokens for
tokenAudiences = []string{"istio-ca"}
)

// GetXdsResponse opens a gRPC connection to opts.xds and waits for a single response
func GetXdsResponse(dr *xdsapi.DiscoveryRequest, opts *clioptions.CentralControlPlaneOptions) (*xdsapi.DiscoveryResponse, error) {
func GetXdsResponse(dr *xdsapi.DiscoveryRequest, opts *clioptions.CentralControlPlaneOptions, grpcOpts []grpc.DialOption) (*xdsapi.DiscoveryResponse, error) {
adscConn, err := adsc.Dial(opts.Xds, opts.CertDir, &adsc.Config{
Meta: model.NodeMetadata{
Generator: "event",
}.ToStruct(),
InsecureSkipVerify: opts.InsecureSkipVerify,
XDSSAN: opts.XDSSAN,
GrpcOpts: grpcOpts,
})
if err != nil {
return nil, fmt.Errorf("could not dial: %w", err)
Expand All @@ -47,3 +67,26 @@ func GetXdsResponse(dr *xdsapi.DiscoveryRequest, opts *clioptions.CentralControl
response, err := adscConn.WaitVersion(opts.Timeout, dr.TypeUrl, "")
return response, err
}

// DialOptions constructs gRPC dial options from command line configuration
func DialOptions(opts *clioptions.CentralControlPlaneOptions, kubeClient kube.ExtendedClient) ([]grpc.DialOption, error) {
// If we are using the insecure 15010 don't bother getting a token
if opts.Plaintext || opts.CertDir != "" {
return make([]grpc.DialOption, 0), nil
}

// Use bearer token
supplier, err := kubeClient.CreatePerRPCCredentials(context.TODO(), tokenNamespace, tokenServiceAccount, tokenAudiences, defaultExpirationSeconds)
if err != nil {
return nil, err
}
return []grpc.DialOption{
grpc.WithTransportCredentials(credentials.NewTLS(
&tls.Config{
// Always skip verifying, because without it we always get "certificate signed by unknown authority".
// We don't se the XDSSAN for the same reason.
InsecureSkipVerify: true,
})),
grpc.WithPerRPCCredentials(supplier),
}, err
}
3 changes: 2 additions & 1 deletion pilot/pkg/xds/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ func (f *FakeDiscoveryServer) Connect(p *model.Proxy, watch []string, wait []str
Watch: watch,
GrpcOpts: []grpc.DialOption{grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return f.listener.Dial()
})},
}),
grpc.WithInsecure()},
})
if err != nil {
f.t.Fatalf("Error connecting: %v", err)
Expand Down
3 changes: 2 additions & 1 deletion pkg/adsc/adsc.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,8 @@ func (a *ADSC) Run() error {
}
creds := credentials.NewTLS(tlsCfg)
opts = append(opts, grpc.WithTransportCredentials(creds))
} else {
} else if len(opts) == 0 {
// Only disable transport security if the user didn't supply custom dial options
opts = append(opts, grpc.WithInsecure())
}
a.conn, err = grpc.Dial(a.url, opts...)
Expand Down
10 changes: 10 additions & 0 deletions pkg/kube/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"strings"

"github.com/hashicorp/go-multierror"
"google.golang.org/grpc/credentials"
v1 "k8s.io/api/core/v1"
kubeExtClient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
extfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
Expand Down Expand Up @@ -166,6 +167,10 @@ type ExtendedClient interface {

// DeleteYAMLFilesDryRun performs a dry run for deleting the resources in the given YAML files.
DeleteYAMLFilesDryRun(namespace string, yamlFiles ...string) error

// CreatePerRPCCredentials creates a gRPC bearer token provider that can create (and renew!) Istio tokens
CreatePerRPCCredentials(ctx context.Context, tokenNamespace, tokenServiceAccount string, audiences []string,
expirationSeconds int64) (credentials.PerRPCCredentials, error)
}

var _ Client = &client{}
Expand Down Expand Up @@ -629,6 +634,11 @@ func (c *client) ApplyYAMLFilesDryRun(namespace string, yamlFiles ...string) err
return nil
}

func (c *client) CreatePerRPCCredentials(ctx context.Context, tokenNamespace, tokenServiceAccount string, audiences []string,
expirationSeconds int64) (credentials.PerRPCCredentials, error) {
return NewRPCCredentials(c, tokenNamespace, tokenServiceAccount, audiences, expirationSeconds)
}

func (c *client) applyYAMLFile(namespace string, dryRun bool, file string) error {
dynamicClient, err := c.clientFactory.DynamicClient()
if err != nil {
Expand Down
Loading

0 comments on commit 09ed125

Please sign in to comment.