aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2022-02-28 13:06:21 +0100
committerGitHub <noreply@github.com>2022-02-28 13:06:21 +0100
commit5a51cc51bf80f4d7c1afa1993e7df6c50a6e3d71 (patch)
tree8f6a1a7bb976bf3d76c366b243069dfff7f77404
parent4e5a58e1ff5d3caaedd1e9b969fd0297517cab15 (diff)
parentc25a9cc88372d5e4bd7fe48c64de11e487a71491 (diff)
Merge pull request #21388 from vespa-engine/ean/vespa-cert-without-app-directory
Allow `vespa auth cert` to work without application package
-rw-r--r--client/go/cmd/cert.go195
-rw-r--r--client/go/cmd/cert_test.go42
-rw-r--r--client/go/cmd/command_tester.go2
3 files changed, 200 insertions, 39 deletions
diff --git a/client/go/cmd/cert.go b/client/go/cmd/cert.go
index 97147d1eec0..2a28589aabb 100644
--- a/client/go/cmd/cert.go
+++ b/client/go/cmd/cert.go
@@ -4,7 +4,9 @@
package cmd
import (
+ "errors"
"fmt"
+ "io"
"os"
"path/filepath"
@@ -13,14 +15,36 @@ import (
"github.com/vespa-engine/vespa/client/go/vespa"
)
-var overwriteCertificate bool
+var (
+ noApplicationPackage bool
+ overwriteCertificate bool
+)
-const longDoc = `Create a new private key and self-signed certificate for Vespa Cloud deployment.
+func init() {
+ certCmd.Flags().BoolVarP(&overwriteCertificate, "force", "f", false, "Force overwrite of existing certificate and private key")
+ certCmd.Flags().BoolVarP(&noApplicationPackage, "no-add", "N", false, "Do not add certificate to an application package")
+ certCmd.MarkPersistentFlagRequired(applicationFlag)
+
+ deprecatedCertCmd.Flags().BoolVarP(&overwriteCertificate, "force", "f", false, "Force overwrite of existing certificate and private key")
+ deprecatedCertCmd.MarkPersistentFlagRequired(applicationFlag)
+
+ certCmd.AddCommand(certAddCmd)
+ certAddCmd.Flags().BoolVarP(&overwriteCertificate, "force", "f", false, "Force overwrite of existing certificate")
+ certAddCmd.MarkPersistentFlagRequired(applicationFlag)
+}
+
+var certCmd = &cobra.Command{
+ Use: "cert",
+ Short: "Create a new private key and self-signed certificate for Vespa Cloud deployment",
+ Long: `Create a new private key and self-signed certificate for Vespa Cloud deployment.
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 '.'), unless the '--no-add' is set.
+
It's possible to override the private key and certificate used through
environment variables. This can be useful in continuous integration systems.
@@ -38,24 +62,9 @@ environment variables. This can be useful in continuous integration systems.
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.`
-
-func init() {
- certCmd.Flags().BoolVarP(&overwriteCertificate, "force", "f", false, "Force overwrite of existing certificate and private key")
- certCmd.MarkPersistentFlagRequired(applicationFlag)
- deprecatedCertCmd.Flags().BoolVarP(&overwriteCertificate, "force", "f", false, "Force overwrite of existing certificate and private key")
- deprecatedCertCmd.MarkPersistentFlagRequired(applicationFlag)
-}
-
-func certExample() string {
- return "$ vespa auth cert -a my-tenant.my-app.my-instance"
-}
-
-var certCmd = &cobra.Command{
- Use: "cert",
- Short: "Create a new private key and self-signed certificate for Vespa Cloud deployment",
- Long: longDoc,
- Example: certExample(),
+application-specific key.`,
+ 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),
@@ -63,9 +72,35 @@ var certCmd = &cobra.Command{
}
var deprecatedCertCmd = &cobra.Command{
- Use: "cert",
- Short: "Create a new private key and self-signed certificate for Vespa Cloud deployment",
- Long: longDoc,
+ Use: "cert",
+ Short: "Create a new private key and self-signed certificate for Vespa Cloud deployment",
+ Long: `Create a new private key and self-signed certificate for Vespa Cloud deployment.
+
+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.
+
+* VESPA_CLI_DATA_PLANE_CERT and VESPA_CLI_DATA_PLANE_KEY containing the
+ certificate and private key directly:
+
+ export VESPA_CLI_DATA_PLANE_CERT="my cert"
+ export VESPA_CLI_DATA_PLANE_KEY="my private key"
+
+* VESPA_CLI_DATA_PLANE_CERT_FILE and VESPA_CLI_DATA_PLANE_KEY_FILE containing
+ paths to the certificate and private key:
+
+ 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.`,
Example: "$ vespa cert -a my-tenant.my-app.my-instance",
DisableAutoGenTag: true,
SilenceUsage: true,
@@ -75,14 +110,35 @@ var deprecatedCertCmd = &cobra.Command{
RunE: doCert,
}
+var certAddCmd = &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 looked for in 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.MinimumNArgs(1),
+ RunE: doCertAdd,
+}
+
func doCert(_ *cobra.Command, args []string) error {
app, err := getApplication()
if err != nil {
return err
}
- pkg, err := vespa.FindApplicationPackage(applicationSource(args), false)
- if err != nil {
- return err
+ var pkg vespa.ApplicationPackage
+ if !noApplicationPackage {
+ pkg, err = vespa.FindApplicationPackage(applicationSource(args), false)
+ if err != nil {
+ return err
+ }
}
cfg, err := LoadConfig()
if err != nil {
@@ -99,8 +155,10 @@ func doCert(_ *cobra.Command, args []string) error {
if !overwriteCertificate {
hint := "Use -f flag to force overwriting"
- if pkg.HasCertificate() {
- return errHint(fmt.Errorf("application package %s already contains a certificate", pkg.Path), hint)
+ 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.Cyan(privateKeyFile)), hint)
@@ -109,21 +167,26 @@ func doCert(_ *cobra.Command, args []string) error {
return errHint(fmt.Errorf("certificate %s already exists", color.Cyan(certificateFile)), hint)
}
}
- 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)
+ 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
}
- 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)
+ 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)
@@ -131,8 +194,64 @@ func doCert(_ *cobra.Command, args []string) error {
if err := keyPair.WritePrivateKeyFile(privateKeyFile, overwriteCertificate); err != nil {
return fmt.Errorf("could not write private key: %w", err)
}
- printSuccess("Certificate written to ", color.Cyan(pkgCertificateFile))
+ if !noApplicationPackage {
+ printSuccess("Certificate written to ", color.Cyan(pkgCertificateFile))
+ }
printSuccess("Certificate written to ", color.Cyan(certificateFile))
printSuccess("Private key written to ", color.Cyan(privateKeyFile))
return nil
}
+
+func doCertAdd(_ *cobra.Command, args []string) error {
+ app, err := getApplication()
+ if err != nil {
+ return err
+ }
+ pkg, err := vespa.FindApplicationPackage(applicationSource(args), false)
+ if err != nil {
+ return err
+ }
+ cfg, err := LoadConfig()
+ if err != nil {
+ return err
+ }
+ certificateFile, err := cfg.CertificatePath(app)
+ 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)
+ }
+
+ printSuccess("Certificate written to ", color.Cyan(pkgCertificateFile))
+ return nil
+}
diff --git a/client/go/cmd/cert_test.go b/client/go/cmd/cert_test.go
index 4a115f54c65..51e99938d2b 100644
--- a/client/go/cmd/cert_test.go
+++ b/client/go/cmd/cert_test.go
@@ -9,6 +9,8 @@ import (
"path/filepath"
"testing"
+ "github.com/stretchr/testify/require"
+
"github.com/stretchr/testify/assert"
"github.com/vespa-engine/vespa/client/go/vespa"
)
@@ -39,7 +41,7 @@ func testCert(t *testing.T, subcommand []string) {
assert.Equal(t, fmt.Sprintf("Success: Certificate written to %s\nSuccess: Certificate written to %s\nSuccess: Private key written to %s\n", pkgCertificate, certificate, privateKey), out)
args = append(subcommand, "-a", "t1.a1.i1", pkgDir)
- _, outErr := execute(command{args: []string{"cert", "-a", "t1.a1.i1", pkgDir}, homeDir: homeDir}, t, nil)
+ _, outErr := execute(command{args: args, homeDir: homeDir}, t, nil)
assert.Contains(t, outErr, fmt.Sprintf("Error: application package %s already contains a certificate", appDir))
}
@@ -74,6 +76,44 @@ func testCertCompressedPackage(t *testing.T, subcommand []string) {
assert.Contains(t, out, "Success: Private key written to")
}
+func TestCertAdd(t *testing.T) {
+ homeDir := filepath.Join(t.TempDir(), ".vespa")
+ execute(command{args: []string{"auth", "cert", "-N", "-a", "t1.a1.i1"}, homeDir: homeDir}, t, nil)
+
+ pkgDir := mockApplicationPackage(t, false)
+ out, _ := execute(command{args: []string{"auth", "cert", "add", "-a", "t1.a1.i1", pkgDir}, homeDir: homeDir}, t, nil)
+ appDir := filepath.Join(pkgDir, "src", "main", "application")
+ pkgCertificate := filepath.Join(appDir, "security", "clients.pem")
+ assert.Equal(t, fmt.Sprintf("Success: Certificate written to %s\n", pkgCertificate), out)
+
+ out, outErr := execute(command{args: []string{"auth", "cert", "add", "-a", "t1.a1.i1", pkgDir}, homeDir: homeDir}, t, nil)
+ assert.Equal(t, "", out)
+ assert.Contains(t, outErr, fmt.Sprintf("Error: application package %s already contains a certificate", appDir))
+ out, _ = execute(command{args: []string{"auth", "cert", "add", "-f", "-a", "t1.a1.i1", pkgDir}, homeDir: homeDir}, t, nil)
+ assert.Equal(t, fmt.Sprintf("Success: Certificate written to %s\n", pkgCertificate), out)
+}
+
+func TestCertNoAdd(t *testing.T) {
+ homeDir := filepath.Join(t.TempDir(), ".vespa")
+ out, _ := execute(command{args: []string{"auth", "cert", "-N", "-a", "t1.a1.i1"}, homeDir: homeDir}, t, nil)
+
+ app, err := vespa.ApplicationFromString("t1.a1.i1")
+ assert.Nil(t, err)
+
+ certificate := filepath.Join(homeDir, app.String(), "data-plane-public-cert.pem")
+ privateKey := filepath.Join(homeDir, app.String(), "data-plane-private-key.pem")
+ assert.Equal(t, fmt.Sprintf("Success: Certificate written to %s\nSuccess: Private key written to %s\n", certificate, privateKey), out)
+
+ _, outErr := execute(command{args: []string{"auth", "cert", "-N", "-a", "t1.a1.i1"}, homeDir: homeDir}, t, nil)
+ assert.Contains(t, outErr, fmt.Sprintf("Error: private key %s already exists", privateKey))
+ require.Nil(t, os.Remove(privateKey))
+ _, outErr = execute(command{args: []string{"auth", "cert", "-N", "-a", "t1.a1.i1"}, homeDir: homeDir}, t, nil)
+ assert.Contains(t, outErr, fmt.Sprintf("Error: certificate %s already exists", certificate))
+
+ out, _ = execute(command{args: []string{"auth", "cert", "-N", "-f", "-a", "t1.a1.i1"}, homeDir: homeDir}, t, nil)
+ assert.Equal(t, fmt.Sprintf("Success: Certificate written to %s\nSuccess: Private key written to %s\n", certificate, privateKey), out)
+}
+
func mockApplicationPackage(t *testing.T, java bool) string {
dir := t.TempDir()
appDir := filepath.Join(dir, "src", "main", "application")
diff --git a/client/go/cmd/command_tester.go b/client/go/cmd/command_tester.go
index a12b6a2ff69..2391cb82d15 100644
--- a/client/go/cmd/command_tester.go
+++ b/client/go/cmd/command_tester.go
@@ -99,6 +99,8 @@ func execute(cmd command, t *testing.T, client *mockHttpClient) (string, string)
queryCmd.Flags().VisitAll(resetFlag)
documentCmd.Flags().VisitAll(resetFlag)
logCmd.Flags().VisitAll(resetFlag)
+ certCmd.Flags().VisitAll(resetFlag)
+ certAddCmd.Flags().VisitAll(resetFlag)
// Capture stdout and execute command
var capturedOut bytes.Buffer