diff options
author | Martin Polden <mpolden@mpolden.no> | 2023-02-03 15:20:23 +0100 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2023-02-03 15:35:25 +0100 |
commit | e1e94812425a487069bf33f781bec987e9e49874 (patch) | |
tree | 4a892c3b5c0a7dee2cb76f9971e538cb4aba8a16 /client/go/internal/cli/cmd/cert.go | |
parent | a08ae588d6035b69f0961dff596fc871fd1c4e58 (diff) |
Re-organize Go code
Diffstat (limited to 'client/go/internal/cli/cmd/cert.go')
-rw-r--r-- | client/go/internal/cli/cmd/cert.go | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/client/go/internal/cli/cmd/cert.go b/client/go/internal/cli/cmd/cert.go new file mode 100644 index 00000000000..7f79a9db358 --- /dev/null +++ b/client/go/internal/cli/cmd/cert.go @@ -0,0 +1,219 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// vespa cert command +// Author: mpolden +package cmd + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/vespa-engine/vespa/client/go/internal/util" + "github.com/vespa-engine/vespa/client/go/internal/vespa" +) + +func newCertCmd(cli *CLI) *cobra.Command { + var ( + noApplicationPackage bool + overwriteCertificate bool + ) + cmd := &cobra.Command{ + Use: "cert", + Short: "Create a new private key and self-signed certificate for data-plane access with Vespa Cloud", + Long: `Create a new private key and self-signed certificate for data-plane access with Vespa Cloud. + +The private key and certificate will be stored in the Vespa CLI home directory +(see 'vespa help config'). Other commands will then automatically load the +certificate as necessary. The certificate will be added to your application +package specified as an argument to this command (default '.'). + +It's possible to override the private key and certificate used through +environment variables. This can be useful in continuous integration systems. + +Example of setting the certificate and key in-line: + + export VESPA_CLI_DATA_PLANE_CERT="my cert" + export VESPA_CLI_DATA_PLANE_KEY="my private key" + +Example of loading certificate and key from custom paths: + + export VESPA_CLI_DATA_PLANE_CERT_FILE=/path/to/cert + export VESPA_CLI_DATA_PLANE_KEY_FILE=/path/to/key + +Note that when overriding key pair through environment variables, that key pair +will always be used for all applications. It's not possible to specify an +application-specific key. + +Read more in https://cloud.vespa.ai/en/security/guide`, + Example: `$ vespa auth cert -a my-tenant.my-app.my-instance +$ vespa auth cert -a my-tenant.my-app.my-instance path/to/application/package`, + DisableAutoGenTag: true, + SilenceUsage: true, + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return doCert(cli, overwriteCertificate, noApplicationPackage, args) + }, + } + cmd.Flags().BoolVarP(&overwriteCertificate, "force", "f", false, "Force overwrite of existing certificate and private key") + cmd.Flags().BoolVarP(&noApplicationPackage, "no-add", "N", false, "Do not add certificate to the application package") + cmd.MarkPersistentFlagRequired(applicationFlag) + return cmd +} + +func newCertAddCmd(cli *CLI) *cobra.Command { + var overwriteCertificate bool + cmd := &cobra.Command{ + Use: "add", + Short: "Add certificate to application package", + Long: `Add an existing self-signed certificate for Vespa Cloud deployment to your application package. + +The certificate will be loaded from the Vespa CLI home directory (see 'vespa +help config') by default. + +The location of the application package can be specified as an argument to this +command (default '.').`, + Example: `$ vespa auth cert add -a my-tenant.my-app.my-instance +$ vespa auth cert add -a my-tenant.my-app.my-instance path/to/application/package`, + DisableAutoGenTag: true, + SilenceUsage: true, + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return doCertAdd(cli, overwriteCertificate, args) + }, + } + cmd.Flags().BoolVarP(&overwriteCertificate, "force", "f", false, "Force overwrite of existing certificate") + cmd.MarkPersistentFlagRequired(applicationFlag) + return cmd +} + +func doCert(cli *CLI, overwriteCertificate, noApplicationPackage bool, args []string) error { + app, err := cli.config.application() + if err != nil { + return err + } + var pkg vespa.ApplicationPackage + if !noApplicationPackage { + pkg, err = cli.applicationPackageFrom(args, false) + if err != nil { + return err + } + } + targetType, err := cli.config.targetType() + if err != nil { + return err + } + privateKeyFile, err := cli.config.privateKeyPath(app, targetType) + if err != nil { + return err + } + certificateFile, err := cli.config.certificatePath(app, targetType) + if err != nil { + return err + } + + if !overwriteCertificate { + hint := "Use -f flag to force overwriting" + if !noApplicationPackage { + if pkg.HasCertificate() { + return errHint(fmt.Errorf("application package %s already contains a certificate", pkg.Path), hint) + } + } + if util.PathExists(privateKeyFile) { + return errHint(fmt.Errorf("private key %s already exists", color.CyanString(privateKeyFile)), hint) + } + if util.PathExists(certificateFile) { + return errHint(fmt.Errorf("certificate %s already exists", color.CyanString(certificateFile)), hint) + } + } + if !noApplicationPackage { + if pkg.IsZip() { + hint := "Try running 'mvn clean' before 'vespa auth cert', and then 'mvn package'" + return errHint(fmt.Errorf("cannot add certificate to compressed application package %s", pkg.Path), hint) + } + } + + keyPair, err := vespa.CreateKeyPair() + if err != nil { + return err + } + var pkgCertificateFile string + if !noApplicationPackage { + pkgCertificateFile = filepath.Join(pkg.Path, "security", "clients.pem") + if err := os.MkdirAll(filepath.Dir(pkgCertificateFile), 0755); err != nil { + return fmt.Errorf("could not create security directory: %w", err) + } + if err := keyPair.WriteCertificateFile(pkgCertificateFile, overwriteCertificate); err != nil { + return fmt.Errorf("could not write certificate to application package: %w", err) + } + } + if err := keyPair.WriteCertificateFile(certificateFile, overwriteCertificate); err != nil { + return fmt.Errorf("could not write certificate: %w", err) + } + if err := keyPair.WritePrivateKeyFile(privateKeyFile, overwriteCertificate); err != nil { + return fmt.Errorf("could not write private key: %w", err) + } + if !noApplicationPackage { + cli.printSuccess("Certificate written to ", color.CyanString(pkgCertificateFile)) + } + cli.printSuccess("Certificate written to ", color.CyanString(certificateFile)) + cli.printSuccess("Private key written to ", color.CyanString(privateKeyFile)) + return nil +} + +func doCertAdd(cli *CLI, overwriteCertificate bool, args []string) error { + app, err := cli.config.application() + if err != nil { + return err + } + pkg, err := cli.applicationPackageFrom(args, false) + if err != nil { + return err + } + targetType, err := cli.config.targetType() + if err != nil { + return err + } + certificateFile, err := cli.config.certificatePath(app, targetType) + if err != nil { + return err + } + + if pkg.IsZip() { + hint := "Try running 'mvn clean' before 'vespa auth cert add', and then 'mvn package'" + return errHint(fmt.Errorf("unable to add certificate to compressed application package: %s", pkg.Path), hint) + } + + pkgCertificateFile := filepath.Join(pkg.Path, "security", "clients.pem") + if err := os.MkdirAll(filepath.Dir(pkgCertificateFile), 0755); err != nil { + return fmt.Errorf("could not create security directory: %w", err) + } + src, err := os.Open(certificateFile) + if errors.Is(err, os.ErrNotExist) { + return errHint(fmt.Errorf("there is not key pair generated for application '%s'", app), "Try running 'vespa auth cert' to generate it") + } else if err != nil { + return fmt.Errorf("could not open certificate file: %w", err) + } + defer src.Close() + flags := os.O_CREATE | os.O_RDWR + if overwriteCertificate { + flags |= os.O_TRUNC + } else { + flags |= os.O_EXCL + } + dst, err := os.OpenFile(pkgCertificateFile, flags, 0755) + if errors.Is(err, os.ErrExist) { + return errHint(fmt.Errorf("application package %s already contains a certificate", pkg.Path), "Use -f flag to force overwriting") + } else if err != nil { + return fmt.Errorf("could not open application certificate file for writing: %w", err) + } + if _, err := io.Copy(dst, src); err != nil { + return fmt.Errorf("could not copy certificate file to application: %w", err) + } + + cli.printSuccess("Certificate written to ", color.CyanString(pkgCertificateFile)) + return nil +} |