diff options
author | Martin Polden <mpolden@mpolden.no> | 2024-06-12 11:55:11 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2024-06-12 12:01:57 +0200 |
commit | 885635cb82a648f04c96aad63e10c86ba3fe67f6 (patch) | |
tree | dc6d83f59da4e12b1d45a00f15c92fc90456000d /client/go | |
parent | 17f9406eb10982c8201ace9de673c0d53af98ed0 (diff) |
Detect missing or mismatching certificate
Diffstat (limited to 'client/go')
-rw-r--r-- | client/go/internal/cli/cmd/cert.go | 36 | ||||
-rw-r--r-- | client/go/internal/cli/cmd/deploy.go | 2 | ||||
-rw-r--r-- | client/go/internal/cli/cmd/deploy_test.go | 15 | ||||
-rw-r--r-- | client/go/internal/cli/cmd/prod.go | 2 | ||||
-rw-r--r-- | client/go/internal/vespa/application.go | 9 |
5 files changed, 49 insertions, 15 deletions
diff --git a/client/go/internal/cli/cmd/cert.go b/client/go/internal/cli/cmd/cert.go index 14e4861cec3..24b414443a0 100644 --- a/client/go/internal/cli/cmd/cert.go +++ b/client/go/internal/cli/cmd/cert.go @@ -160,10 +160,10 @@ func doCertAdd(cli *CLI, overwriteCertificate bool, args []string) error { if pkg.HasCertificate() && !overwriteCertificate { return errHint(fmt.Errorf("application package '%s' already contains a certificate", pkg.Path), "Use -f flag to force overwriting") } - return maybeCopyCertificate(true, false, cli, target, pkg) + return requireCertificate(true, false, cli, target, pkg) } -func maybeCopyCertificate(force, ignoreZip bool, cli *CLI, target vespa.Target, pkg vespa.ApplicationPackage) error { +func requireCertificate(force, ignoreZip bool, cli *CLI, target vespa.Target, pkg vespa.ApplicationPackage) error { if pkg.IsZip() { if ignoreZip { cli.printWarning("Cannot verify existence of "+color.CyanString("security/clients.pem")+" since '"+pkg.Path+"' is compressed", @@ -175,10 +175,28 @@ func maybeCopyCertificate(force, ignoreZip bool, cli *CLI, target vespa.Target, return errHint(fmt.Errorf("cannot add certificate to compressed application package: '%s'", pkg.Path), hint) } } + tlsOptions, err := cli.config.readTLSOptions(target.Deployment().Application, target.Type()) + if err != nil { + return err + } + if len(tlsOptions.CertificatePEM) == 0 { + return errHint(fmt.Errorf("no certificate exists for %s", target.Deployment().Application.String()), "Try (re)creating the certificate with 'vespa auth cert'") + } if force { - return copyCertificate(cli, target, pkg) + return copyCertificate(tlsOptions, cli, pkg) } if pkg.HasCertificate() { + matches, err := pkg.HasMatchingCertificate(tlsOptions.CertificatePEM) + if err != nil { + return err + } + if !matches { + return errHint(fmt.Errorf("certificate in %s does not match the stored key pair for %s", + filepath.Join("security", "clients.pem"), + target.Deployment().Application.String()), + "If this application was deployed using a different application ID in the past, the matching key pair may be stored under a different ID in "+cli.config.homeDir, + "Specify the matching application with --application, or add the current certificate to the package using --add-cert") + } return nil } if cli.isTerminal() { @@ -188,7 +206,7 @@ func maybeCopyCertificate(force, ignoreZip bool, cli *CLI, target vespa.Target, return err } if ok { - return copyCertificate(cli, target, pkg) + return copyCertificate(tlsOptions, cli, pkg) } } return errHint(fmt.Errorf("deployment to Vespa Cloud requires certificate in application package"), @@ -196,15 +214,7 @@ func maybeCopyCertificate(force, ignoreZip bool, cli *CLI, target vespa.Target, "Pass --add-cert to use the certificate of the current application") } -func copyCertificate(cli *CLI, target vespa.Target, pkg vespa.ApplicationPackage) error { - tlsOptions, err := cli.config.readTLSOptions(target.Deployment().Application, target.Type()) - if err != nil { - return err - } - hint := "Try generating the certificate with 'vespa auth cert'" - if tlsOptions.CertificateFile == "" { - return errHint(fmt.Errorf("no certificate exists for "+target.Deployment().Application.String()), hint) - } +func copyCertificate(tlsOptions vespa.TLSOptions, cli *CLI, pkg vespa.ApplicationPackage) error { data, err := os.ReadFile(tlsOptions.CertificateFile) if err != nil { return errHint(fmt.Errorf("could not read certificate file: %w", err)) diff --git a/client/go/internal/cli/cmd/deploy.go b/client/go/internal/cli/cmd/deploy.go index 718e2b27b2b..c9f08655759 100644 --- a/client/go/internal/cli/cmd/deploy.go +++ b/client/go/internal/cli/cmd/deploy.go @@ -68,7 +68,7 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`, opts.Version = version } if target.Type() == vespa.TargetCloud { - if err := maybeCopyCertificate(copyCert, true, cli, target, pkg); err != nil { + if err := requireCertificate(copyCert, true, cli, target, pkg); err != nil { return err } } diff --git a/client/go/internal/cli/cmd/deploy_test.go b/client/go/internal/cli/cmd/deploy_test.go index 8d1c202e554..b8a684cf1cf 100644 --- a/client/go/internal/cli/cmd/deploy_test.go +++ b/client/go/internal/cli/cmd/deploy_test.go @@ -62,6 +62,21 @@ Hint: Pass --add-cert to use the certificate of the current application buf.WriteString("y\n") require.Nil(t, cli.Run("deploy", "--add-cert=false", "--wait=0", pkgDir2)) assert.Contains(t, stdout.String(), "Success: Triggered deployment") + + // Missing application certificate is detected + stderr.Reset() + require.NotNil(t, cli.Run("deploy", "--application=t1.a2.i2", pkgDir2)) + assert.Equal(t, "Error: no certificate exists for t1.a2.i2\nHint: Try (re)creating the certificate with 'vespa auth cert'\n", stderr.String()) + + // Mismatching certificate is detected + stdout.Reset() + stderr.Reset() + assert.Nil(t, cli.Run("auth", "cert", "--application=t1.a1.i1", "-f", "--no-add")) + require.NotNil(t, cli.Run("deploy", "--application=t1.a1.i1", pkgDir2)) + assert.Equal(t, `Error: certificate in security/clients.pem does not match the stored key pair for t1.a1.i1 +Hint: If this application was deployed using a different application ID in the past, the matching key pair may be stored under a different ID in `+ + cli.config.homeDir+"\nHint: Specify the matching application with --application, or add the current certificate to the package using --add-cert\n", + stderr.String()) } func TestDeployCloudFastWait(t *testing.T) { diff --git a/client/go/internal/cli/cmd/prod.go b/client/go/internal/cli/cmd/prod.go index 08d4086719d..139e4690ed2 100644 --- a/client/go/internal/cli/cmd/prod.go +++ b/client/go/internal/cli/cmd/prod.go @@ -154,7 +154,7 @@ $ vespa prod deploy`, if err := verifyTests(cli, pkg); err != nil { return err } - if err := maybeCopyCertificate(options.copyCert, true, cli, target, pkg); err != nil { + if err := requireCertificate(options.copyCert, true, cli, target, pkg); err != nil { return err } deployment := vespa.DeploymentOptions{ApplicationPackage: pkg, Target: target} diff --git a/client/go/internal/vespa/application.go b/client/go/internal/vespa/application.go index 618d23b6bab..d499006c982 100644 --- a/client/go/internal/vespa/application.go +++ b/client/go/internal/vespa/application.go @@ -3,6 +3,7 @@ package vespa import ( "archive/zip" + "bytes" "errors" "fmt" "io" @@ -21,6 +22,14 @@ type ApplicationPackage struct { func (ap *ApplicationPackage) HasCertificate() bool { return ap.hasFile("security", "clients.pem") } +func (ap *ApplicationPackage) HasMatchingCertificate(certificatePEM []byte) (bool, error) { + clientsPEM, err := os.ReadFile(filepath.Join(ap.Path, "security", "clients.pem")) + if err != nil { + return false, err + } + return bytes.Equal(clientsPEM, certificatePEM), nil +} + func (ap *ApplicationPackage) HasDeploymentSpec() bool { return ap.hasFile("deployment.xml", "") } func (ap *ApplicationPackage) hasFile(pathSegment ...string) bool { |