Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --cacert flag to velero cli commands #2364

Merged
merged 4 commits into from
Apr 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelogs/unreleased/2364-mansam
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added a `--cacert` flag to the velero client describe, download, and logs commands to allow passing a path to a certificate to use when verifying TLS connections to object storage. Also added a corresponding client config option called `cacert` which takes a path to a certificate bundle to use as a default when `--cacert` is not specified.
14 changes: 14 additions & 0 deletions pkg/client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
const (
ConfigKeyNamespace = "namespace"
ConfigKeyFeatures = "features"
ConfigKeyCACert = "cacert"
)

// VeleroConfig is a map of strings to interface{} for deserializing Velero client config options.
Expand Down Expand Up @@ -110,6 +111,19 @@ func (c VeleroConfig) Features() []string {
return strings.Split(features, ",")
}

func (c VeleroConfig) CACertFile() string {
val, ok := c[ConfigKeyCACert]
if !ok {
return ""
}
caCertFile, ok := val.(string)
if !ok {
return ""
}

return caCertFile
}

func configFileName() string {
return filepath.Join(os.Getenv("HOME"), ".config", "velero", "config.json")
}
10 changes: 8 additions & 2 deletions pkg/cmd/cli/backup/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
insecureSkipTLSVerify bool
)

config, err := client.LoadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "WARNING: Error reading config file: %v\n", err)
}
caCertFile := config.CACertFile()

c := &cobra.Command{
Use: use + " [NAME1] [NAME2] [NAME...]",
Short: "Describe backups",
Expand Down Expand Up @@ -72,7 +78,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
fmt.Fprintf(os.Stderr, "error getting PodVolumeBackups for backup %s: %v\n", backup.Name, err)
}

s := output.DescribeBackup(&backup, deleteRequestList.Items, podVolumeBackupList.Items, details, veleroClient, insecureSkipTLSVerify)
s := output.DescribeBackup(&backup, deleteRequestList.Items, podVolumeBackupList.Items, details, veleroClient, insecureSkipTLSVerify, caCertFile)
if first {
first = false
fmt.Print(s)
Expand All @@ -87,6 +93,6 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "only show items matching this label selector")
c.Flags().BoolVar(&details, "details", details, "display additional detail in the command output")
c.Flags().BoolVar(&insecureSkipTLSVerify, "insecure-skip-tls-verify", insecureSkipTLSVerify, "If true, the object store's TLS certificate will not be checked for validity. This is insecure and susceptible to man-in-the-middle attacks. Not recommended for production.")

c.Flags().StringVar(&caCertFile, "cacert", caCertFile, "path to a certificate bundle to use when verifying TLS connections")
return c
}
11 changes: 10 additions & 1 deletion pkg/cmd/cli/backup/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ import (
)

func NewDownloadCommand(f client.Factory) *cobra.Command {
config, err := client.LoadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "WARNING: Error reading config file: %v\n", err)
}
o := NewDownloadOptions()
o.caCertFile = config.CACertFile()

c := &cobra.Command{
Use: "download NAME",
Short: "Download a backup",
Expand All @@ -58,6 +64,7 @@ type DownloadOptions struct {
Timeout time.Duration
InsecureSkipTLSVerify bool
writeOptions int
caCertFile string
}

func NewDownloadOptions() *DownloadOptions {
Expand All @@ -71,6 +78,8 @@ func (o *DownloadOptions) BindFlags(flags *pflag.FlagSet) {
flags.BoolVar(&o.Force, "force", o.Force, "forces the download and will overwrite file if it exists already")
flags.DurationVar(&o.Timeout, "timeout", o.Timeout, "maximum time to wait to process download request")
flags.BoolVar(&o.InsecureSkipTLSVerify, "insecure-skip-tls-verify", o.InsecureSkipTLSVerify, "If true, the object store's TLS certificate will not be checked for validity. This is insecure and susceptible to man-in-the-middle attacks. Not recommended for production.")
flags.StringVar(&o.caCertFile, "cacert", o.caCertFile, "path to a certificate bundle to use when verifying TLS connections")

}

func (o *DownloadOptions) Validate(c *cobra.Command, args []string, f client.Factory) error {
Expand Down Expand Up @@ -113,7 +122,7 @@ func (o *DownloadOptions) Run(c *cobra.Command, f client.Factory) error {
}
defer backupDest.Close()

err = downloadrequest.Stream(veleroClient.VeleroV1(), f.Namespace(), o.Name, v1.DownloadTargetKindBackupContents, backupDest, o.Timeout, o.InsecureSkipTLSVerify)
err = downloadrequest.Stream(veleroClient.VeleroV1(), f.Namespace(), o.Name, v1.DownloadTargetKindBackupContents, backupDest, o.Timeout, o.InsecureSkipTLSVerify, o.caCertFile)
if err != nil {
os.Remove(o.Output)
cmd.CheckError(err)
Expand Down
11 changes: 9 additions & 2 deletions pkg/cmd/cli/backup/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package backup

import (
"fmt"
"os"
"time"

Expand All @@ -31,8 +32,14 @@ import (
)

func NewLogsCommand(f client.Factory) *cobra.Command {
config, err := client.LoadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "WARNING: Error reading config file: %v\n", err)
}

timeout := time.Minute
insecureSkipTLSVerify := false
caCertFile := config.CACertFile()

c := &cobra.Command{
Use: "logs BACKUP",
Expand All @@ -59,13 +66,13 @@ func NewLogsCommand(f client.Factory) *cobra.Command {
"until the backup has a phase of Completed or Failed and try again.", backupName)
}

err = downloadrequest.Stream(veleroClient.VeleroV1(), f.Namespace(), backupName, v1.DownloadTargetKindBackupLog, os.Stdout, timeout, insecureSkipTLSVerify)
err = downloadrequest.Stream(veleroClient.VeleroV1(), f.Namespace(), backupName, v1.DownloadTargetKindBackupLog, os.Stdout, timeout, insecureSkipTLSVerify, caCertFile)
cmd.CheckError(err)
},
}

c.Flags().DurationVar(&timeout, "timeout", timeout, "how long to wait to receive logs")
c.Flags().BoolVar(&insecureSkipTLSVerify, "insecure-skip-tls-verify", insecureSkipTLSVerify, "If true, the object store's TLS certificate will not be checked for validity. This is insecure and susceptible to man-in-the-middle attacks. Not recommended for production.")

c.Flags().StringVar(&caCertFile, "cacert", caCertFile, "path to a certificate bundle to use when verifying TLS connections")
return c
}
9 changes: 8 additions & 1 deletion pkg/cmd/cli/restore/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
insecureSkipTLSVerify bool
)

config, err := client.LoadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "WARNING: Error reading config file: %v\n", err)
}
caCertFile := config.CACertFile()

c := &cobra.Command{
Use: use + " [NAME1] [NAME2] [NAME...]",
Short: "Describe restores",
Expand Down Expand Up @@ -65,7 +71,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
fmt.Fprintf(os.Stderr, "error getting PodVolumeRestores for restore %s: %v\n", restore.Name, err)
}

s := output.DescribeRestore(&restore, podvolumeRestoreList.Items, details, veleroClient, insecureSkipTLSVerify)
s := output.DescribeRestore(&restore, podvolumeRestoreList.Items, details, veleroClient, insecureSkipTLSVerify, caCertFile)
if first {
first = false
fmt.Print(s)
Expand All @@ -80,6 +86,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "only show items matching this label selector")
c.Flags().BoolVar(&details, "details", details, "display additional detail in the command output")
c.Flags().BoolVar(&insecureSkipTLSVerify, "insecure-skip-tls-verify", insecureSkipTLSVerify, "If true, the object store's TLS certificate will not be checked for validity. This is insecure and susceptible to man-in-the-middle attacks. Not recommended for production.")
c.Flags().StringVar(&caCertFile, "cacert", caCertFile, "path to a certificate bundle to use when verifying TLS connections")

return c
}
10 changes: 9 additions & 1 deletion pkg/cmd/cli/restore/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package restore

import (
"fmt"
"os"
"time"

Expand All @@ -31,8 +32,14 @@ import (
)

func NewLogsCommand(f client.Factory) *cobra.Command {
config, err := client.LoadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "WARNING: Error reading config file: %v\n", err)
}

timeout := time.Minute
insecureSkipTLSVerify := false
caCertFile := config.CACertFile()

c := &cobra.Command{
Use: "logs RESTORE",
Expand All @@ -59,13 +66,14 @@ func NewLogsCommand(f client.Factory) *cobra.Command {
"until the restore has a phase of Completed or Failed and try again.", restoreName)
}

err = downloadrequest.Stream(veleroClient.VeleroV1(), f.Namespace(), restoreName, v1.DownloadTargetKindRestoreLog, os.Stdout, timeout, insecureSkipTLSVerify)
err = downloadrequest.Stream(veleroClient.VeleroV1(), f.Namespace(), restoreName, v1.DownloadTargetKindRestoreLog, os.Stdout, timeout, insecureSkipTLSVerify, caCertFile)
cmd.CheckError(err)
},
}

c.Flags().DurationVar(&timeout, "timeout", timeout, "how long to wait to receive logs")
c.Flags().BoolVar(&insecureSkipTLSVerify, "insecure-skip-tls-verify", insecureSkipTLSVerify, "If true, the object store's TLS certificate will not be checked for validity. This is insecure and susceptible to man-in-the-middle attacks. Not recommended for production.")
c.Flags().StringVar(&caCertFile, "cacert", caCertFile, "path to a certificate bundle to use when verifying TLS connections")

return c
}
37 changes: 32 additions & 5 deletions pkg/cmd/util/downloadrequest/downloadrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import (
// not found
var ErrNotFound = errors.New("file not found")

func Stream(client velerov1client.DownloadRequestsGetter, namespace, name string, kind v1.DownloadTargetKind, w io.Writer, timeout time.Duration, insecureSkipTLSVerify bool) error {
func Stream(client velerov1client.DownloadRequestsGetter, namespace, name string, kind v1.DownloadTargetKind, w io.Writer, timeout time.Duration, insecureSkipTLSVerify bool, caCertFile string) error {
req := &v1.DownloadRequest{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Expand Down Expand Up @@ -99,11 +99,38 @@ Loop:
return ErrNotFound
}

httpClient := new(http.Client)
if insecureSkipTLSVerify {
httpClient.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
var caPool *x509.CertPool
if len(caCertFile) > 0 {
caCert, err := ioutil.ReadFile(caCertFile)
if err != nil {
return errors.Wrapf(err, "couldn't open cacert")
}
// bundle the passed in cert with the system cert pool
// if it's available, otherwise create a new pool just
// for this.
caPool, err = x509.SystemCertPool()
if err != nil {
caPool = x509.NewCertPool()
}
caPool.AppendCertsFromPEM(caCert)
}

defaultTransport := http.DefaultTransport.(*http.Transport)
// same settings as the default transport
// aside from timeout and TLSClientConfig
httpClient := new(http.Client)
httpClient.Transport = &http.Transport{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we probably want to use the timeout passed to Stream on the transport, since the Go HTTP client doesn't have one by default and will just wait forever if it's not specified (https://golang.org/src/net/http/transport.go#L211).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the right call. While fixing that I also set the rest of the values on the transport to match go's DefaultTransport. https://golang.org/src/net/http/transport.go#L42

TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecureSkipTLSVerify,
RootCAs: caPool,
},
IdleConnTimeout: timeout,
DialContext: defaultTransport.DialContext,
ForceAttemptHTTP2: defaultTransport.ForceAttemptHTTP2,
MaxIdleConns: defaultTransport.MaxIdleConns,
Proxy: defaultTransport.Proxy,
TLSHandshakeTimeout: defaultTransport.TLSHandshakeTimeout,
ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
}

httpReq, err := http.NewRequest("GET", req.Status.DownloadURL, nil)
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/util/downloadrequest/downloadrequest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func TestStream(t *testing.T) {
output := new(bytes.Buffer)
errCh := make(chan error)
go func() {
err := Stream(client.VeleroV1(), "namespace", "name", test.kind, output, timeout, false)
err := Stream(client.VeleroV1(), "namespace", "name", test.kind, output, timeout, false, "")
errCh <- err
}()

Expand Down
13 changes: 7 additions & 6 deletions pkg/cmd/util/output/backup_describer.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func DescribeBackup(
details bool,
veleroClient clientset.Interface,
insecureSkipTLSVerify bool,
caCertFile string,
) string {
return Describe(func(d *Describer) {
d.DescribeMetadata(backup.ObjectMeta)
Expand Down Expand Up @@ -75,7 +76,7 @@ func DescribeBackup(
DescribeBackupSpec(d, backup.Spec)

d.Println()
DescribeBackupStatus(d, backup, details, veleroClient, insecureSkipTLSVerify)
DescribeBackupStatus(d, backup, details, veleroClient, insecureSkipTLSVerify, caCertFile)

if len(deleteRequests) > 0 {
d.Println()
Expand Down Expand Up @@ -212,7 +213,7 @@ func DescribeBackupSpec(d *Describer, spec velerov1api.BackupSpec) {
}

// DescribeBackupStatus describes a backup status in human-readable format.
func DescribeBackupStatus(d *Describer, backup *velerov1api.Backup, details bool, veleroClient clientset.Interface, insecureSkipTLSVerify bool) {
func DescribeBackupStatus(d *Describer, backup *velerov1api.Backup, details bool, veleroClient clientset.Interface, insecureSkipTLSVerify bool, caCertPath string) {
status := backup.Status

d.Printf("Backup Format Version:\t%d\n", status.Version)
Expand All @@ -238,7 +239,7 @@ func DescribeBackupStatus(d *Describer, backup *velerov1api.Backup, details bool
d.Println()

if details {
describeBackupResourceList(d, backup, veleroClient, insecureSkipTLSVerify)
describeBackupResourceList(d, backup, veleroClient, insecureSkipTLSVerify, caCertPath)
d.Println()
}

Expand All @@ -249,7 +250,7 @@ func DescribeBackupStatus(d *Describer, backup *velerov1api.Backup, details bool
}

buf := new(bytes.Buffer)
if err := downloadrequest.Stream(veleroClient.VeleroV1(), backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupVolumeSnapshots, buf, downloadRequestTimeout, insecureSkipTLSVerify); err != nil {
if err := downloadrequest.Stream(veleroClient.VeleroV1(), backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupVolumeSnapshots, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
d.Printf("Persistent Volumes:\t<error getting volume snapshot info: %v>\n", err)
return
}
Expand All @@ -270,9 +271,9 @@ func DescribeBackupStatus(d *Describer, backup *velerov1api.Backup, details bool
d.Printf("Persistent Volumes: <none included>\n")
}

func describeBackupResourceList(d *Describer, backup *velerov1api.Backup, veleroClient clientset.Interface, insecureSkipTLSVerify bool) {
func describeBackupResourceList(d *Describer, backup *velerov1api.Backup, veleroClient clientset.Interface, insecureSkipTLSVerify bool, caCertPath string) {
buf := new(bytes.Buffer)
if err := downloadrequest.Stream(veleroClient.VeleroV1(), backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupResourceList, buf, downloadRequestTimeout, insecureSkipTLSVerify); err != nil {
if err := downloadrequest.Stream(veleroClient.VeleroV1(), backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupResourceList, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
if err == downloadrequest.ErrNotFound {
// the backup resource list could be missing if (other reasons may exist as well):
// - the backup was taken prior to v1.1; or
Expand Down
8 changes: 4 additions & 4 deletions pkg/cmd/util/output/restore_describer.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
pkgrestore "github.com/vmware-tanzu/velero/pkg/restore"
)

func DescribeRestore(restore *v1.Restore, podVolumeRestores []v1.PodVolumeRestore, details bool, veleroClient clientset.Interface, insecureSkipTLSVerify bool) string {
func DescribeRestore(restore *v1.Restore, podVolumeRestores []v1.PodVolumeRestore, details bool, veleroClient clientset.Interface, insecureSkipTLSVerify bool, caCertFile string) string {
return Describe(func(d *Describer) {
d.DescribeMetadata(restore.ObjectMeta)

Expand All @@ -56,7 +56,7 @@ func DescribeRestore(restore *v1.Restore, podVolumeRestores []v1.PodVolumeRestor
}
}

describeRestoreResults(d, restore, veleroClient, insecureSkipTLSVerify)
describeRestoreResults(d, restore, veleroClient, insecureSkipTLSVerify, caCertFile)

d.Println()
d.Printf("Backup:\t%s\n", restore.Spec.BackupName)
Expand Down Expand Up @@ -114,15 +114,15 @@ func DescribeRestore(restore *v1.Restore, podVolumeRestores []v1.PodVolumeRestor
})
}

func describeRestoreResults(d *Describer, restore *v1.Restore, veleroClient clientset.Interface, insecureSkipTLSVerify bool) {
func describeRestoreResults(d *Describer, restore *v1.Restore, veleroClient clientset.Interface, insecureSkipTLSVerify bool, caCertPath string) {
if restore.Status.Warnings == 0 && restore.Status.Errors == 0 {
return
}

var buf bytes.Buffer
var resultMap map[string]pkgrestore.Result

if err := downloadrequest.Stream(veleroClient.VeleroV1(), restore.Namespace, restore.Name, v1.DownloadTargetKindRestoreResults, &buf, downloadRequestTimeout, insecureSkipTLSVerify); err != nil {
if err := downloadrequest.Stream(veleroClient.VeleroV1(), restore.Namespace, restore.Name, v1.DownloadTargetKindRestoreResults, &buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
d.Printf("Warnings:\t<error getting warnings: %v>\n\nErrors:\t<error getting errors: %v>\n", err, err)
return
}
Expand Down