Add --cacert flag to velero cli commands (#2364)

* Add --cacert flag to velero cli commands

Adds a --cacert flag to the log and describe commands
that takes a path to a PEM-encoded certificate bundle
as an alternative to --insecure-skip-tls-verify for
dealing with self-signed certificates.

Signed-off-by: Sam Lucidi <slucidi@redhat.com>
pull/2398/head
Samuel Lucidi 2020-04-03 11:02:41 -04:00 committed by GitHub
parent 016868ecd3
commit c8223608ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 103 additions and 23 deletions

View File

@ -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.

View File

@ -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.
@ -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")
}

View File

@ -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",
@ -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)
@ -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
}

View File

@ -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",
@ -58,6 +64,7 @@ type DownloadOptions struct {
Timeout time.Duration
InsecureSkipTLSVerify bool
writeOptions int
caCertFile string
}
func NewDownloadOptions() *DownloadOptions {
@ -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 {
@ -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)

View File

@ -17,6 +17,7 @@ limitations under the License.
package backup
import (
"fmt"
"os"
"time"
@ -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",
@ -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
}

View File

@ -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",
@ -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)
@ -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
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package restore
import (
"fmt"
"os"
"time"
@ -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",
@ -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
}

View File

@ -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,
@ -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{
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)

View File

@ -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
}()

View File

@ -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)
@ -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()
@ -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)
@ -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()
}
@ -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
}
@ -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

View File

@ -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)
@ -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)
@ -114,7 +114,7 @@ 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
}
@ -122,7 +122,7 @@ func describeRestoreResults(d *Describer, restore *v1.Restore, veleroClient clie
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
}