summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEirik Nygaard <eirik.nygaard@yahooinc.com>2022-02-24 14:41:05 +0100
committerGitHub <noreply@github.com>2022-02-24 14:41:05 +0100
commitc23bc766b78769f2fbfb09452d3bbdde2b55e4e9 (patch)
tree381ffe59d528cceead34085f6ab30689b4b2d8c4
parent88cfafdd9e56d97f021061ad3f460689fda62567 (diff)
parent831453b8c393bcdf31fb3d72faccc8a3035837b5 (diff)
Merge pull request #21335 from vespa-engine/ean/device-flow-as-default
Ean/device flow as default
-rw-r--r--client/go/auth0/auth0.go11
-rw-r--r--client/go/cmd/api_key.go21
-rw-r--r--client/go/cmd/api_key_test.go14
-rw-r--r--client/go/cmd/auth.go20
-rw-r--r--client/go/cmd/cert.go15
-rw-r--r--client/go/cmd/cert_test.go28
-rw-r--r--client/go/cmd/command_tester.go18
-rw-r--r--client/go/cmd/config.go49
-rw-r--r--client/go/cmd/config_test.go70
-rw-r--r--client/go/cmd/curl.go5
-rw-r--r--client/go/cmd/curl_test.go6
-rw-r--r--client/go/cmd/helpers.go41
-rw-r--r--client/go/cmd/log_test.go9
-rw-r--r--client/go/cmd/login.go11
-rw-r--r--client/go/cmd/prod_test.go4
-rw-r--r--client/go/cmd/root.go11
-rw-r--r--client/go/cmd/test_test.go11
-rw-r--r--client/go/vespa/deploy.go5
-rw-r--r--client/go/vespa/target.go41
-rw-r--r--client/go/vespa/target_test.go7
20 files changed, 242 insertions, 155 deletions
diff --git a/client/go/auth0/auth0.go b/client/go/auth0/auth0.go
index 5acd4354274..bcd70c4f54a 100644
--- a/client/go/auth0/auth0.go
+++ b/client/go/auth0/auth0.go
@@ -129,6 +129,7 @@ func (a *Auth0) IsLoggedIn() bool {
}
// Check if token is valid.
+ // TODO: Choose issuer based on system
if err = jwt.Validate(token, jwt.WithIssuer("https://vespa-cd.auth0.com/")); err != nil {
return false
}
@@ -233,6 +234,16 @@ func (a *Auth0) getSystem() (*System, error) {
return s, nil
}
+// HasSystem checks if the system is configured
+// TODO: Used to print deprecation warning if we fall back to use tenant API key.
+// Remove when this is not longer needed.
+func (a *Auth0) HasSystem() bool {
+ if _, err := a.getSystem(); err != nil {
+ return false
+ }
+ return true
+}
+
// AddSystem assigns an existing, or new System. This is expected to be called
// after a login has completed.
func (a *Auth0) AddSystem(s *System) error {
diff --git a/client/go/cmd/api_key.go b/client/go/cmd/api_key.go
index faab67781d1..5cc1dab8a35 100644
--- a/client/go/cmd/api_key.go
+++ b/client/go/cmd/api_key.go
@@ -38,14 +38,12 @@ always be used. It's not possible to specify a tenant-specific key.`
func init() {
apiKeyCmd.Flags().BoolVarP(&overwriteKey, "force", "f", false, "Force overwrite of existing API key")
apiKeyCmd.MarkPersistentFlagRequired(applicationFlag)
+ deprecatedApiKeyCmd.Flags().BoolVarP(&overwriteKey, "force", "f", false, "Force overwrite of existing API key")
+ deprecatedApiKeyCmd.MarkPersistentFlagRequired(applicationFlag)
}
func apiKeyExample() string {
- if vespa.Auth0AccessTokenEnabled() {
- return "$ vespa auth api-key -a my-tenant.my-app.my-instance"
- } else {
- return "$ vespa api-key -a my-tenant.my-app.my-instance"
- }
+ return "$ vespa auth api-key -a my-tenant.my-app.my-instance"
}
var apiKeyCmd = &cobra.Command{
@@ -94,18 +92,7 @@ func doApiKey(_ *cobra.Command, _ []string) error {
}
if err := ioutil.WriteFile(apiKeyFile, apiKey, 0600); err == nil {
printSuccess("API private key written to ", apiKeyFile)
- printPublicKey(apiKeyFile, app.Tenant)
- if vespa.Auth0AccessTokenEnabled() {
- if err == nil {
- if err := cfg.Set(cloudAuthFlag, "api-key"); err != nil {
- return fmt.Errorf("could not write config: %w", err)
- }
- if err := cfg.Write(); err != nil {
- return err
- }
- }
- }
- return nil
+ return printPublicKey(apiKeyFile, app.Tenant)
} else {
return fmt.Errorf("failed to write: %s: %w", apiKeyFile, err)
}
diff --git a/client/go/cmd/api_key_test.go b/client/go/cmd/api_key_test.go
index df3cf144180..935b8676c09 100644
--- a/client/go/cmd/api_key_test.go
+++ b/client/go/cmd/api_key_test.go
@@ -11,13 +11,23 @@ import (
)
func TestAPIKey(t *testing.T) {
+ t.Run("auth api-key", func(t *testing.T) {
+ testAPIKey(t, []string{"auth", "api-key"})
+ })
+ t.Run("api-key (deprecated)", func(t *testing.T) {
+ testAPIKey(t, []string{"api-key"})
+ })
+}
+
+func testAPIKey(t *testing.T, subcommand []string) {
homeDir := filepath.Join(t.TempDir(), ".vespa")
keyFile := filepath.Join(homeDir, "t1.api-key.pem")
- out, _ := execute(command{args: []string{"api-key", "-a", "t1.a1.i1"}, homeDir: homeDir}, t, nil)
+ args := append(subcommand, "-a", "t1.a1.i1")
+ out, _ := execute(command{args: args, homeDir: homeDir}, t, nil)
assert.Contains(t, out, "Success: API private key written to "+keyFile+"\n")
- out, outErr := execute(command{args: []string{"api-key", "-a", "t1.a1.i1"}, homeDir: homeDir}, t, nil)
+ out, outErr := execute(command{args: args, homeDir: homeDir}, t, nil)
assert.Contains(t, outErr, "Error: refusing to overwrite "+keyFile+"\nHint: Use -f to overwrite it\n")
assert.Contains(t, out, "This is your public key")
}
diff --git a/client/go/cmd/auth.go b/client/go/cmd/auth.go
index 68d7fa00fdf..592c18c8b22 100644
--- a/client/go/cmd/auth.go
+++ b/client/go/cmd/auth.go
@@ -4,22 +4,16 @@ import (
"fmt"
"github.com/spf13/cobra"
- "github.com/vespa-engine/vespa/client/go/vespa"
)
func init() {
- if vespa.Auth0AccessTokenEnabled() {
- rootCmd.AddCommand(authCmd)
- rootCmd.AddCommand(deprecatedCertCmd)
- rootCmd.AddCommand(deprecatedApiKeyCmd)
- authCmd.AddCommand(certCmd)
- authCmd.AddCommand(apiKeyCmd)
- authCmd.AddCommand(loginCmd)
- authCmd.AddCommand(logoutCmd)
- } else {
- rootCmd.AddCommand(certCmd)
- rootCmd.AddCommand(apiKeyCmd)
- }
+ rootCmd.AddCommand(authCmd)
+ rootCmd.AddCommand(deprecatedCertCmd)
+ rootCmd.AddCommand(deprecatedApiKeyCmd)
+ authCmd.AddCommand(certCmd)
+ authCmd.AddCommand(apiKeyCmd)
+ authCmd.AddCommand(loginCmd)
+ authCmd.AddCommand(logoutCmd)
}
var authCmd = &cobra.Command{
diff --git a/client/go/cmd/cert.go b/client/go/cmd/cert.go
index f338a1ba81a..97147d1eec0 100644
--- a/client/go/cmd/cert.go
+++ b/client/go/cmd/cert.go
@@ -43,14 +43,12 @@ 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 {
- if vespa.Auth0AccessTokenEnabled() {
- return "$ vespa auth cert -a my-tenant.my-app.my-instance"
- } else {
- return "$ vespa cert -a my-tenant.my-app.my-instance"
- }
+ return "$ vespa auth cert -a my-tenant.my-app.my-instance"
}
var certCmd = &cobra.Command{
@@ -112,12 +110,7 @@ func doCert(_ *cobra.Command, args []string) error {
}
}
if pkg.IsZip() {
- var hint string
- if vespa.Auth0AccessTokenEnabled() {
- hint = "Try running 'mvn clean' before 'vespa auth cert', and then 'mvn package'"
- } else {
- hint = "Try running 'mvn clean' before 'vespa cert', and then 'mvn package'"
- }
+ 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)
}
diff --git a/client/go/cmd/cert_test.go b/client/go/cmd/cert_test.go
index aae40db43a6..4a115f54c65 100644
--- a/client/go/cmd/cert_test.go
+++ b/client/go/cmd/cert_test.go
@@ -14,9 +14,19 @@ import (
)
func TestCert(t *testing.T) {
+ t.Run("auth cert", func(t *testing.T) {
+ testCert(t, []string{"auth", "cert"})
+ })
+ t.Run("cert (deprecated)", func(t *testing.T) {
+ testCert(t, []string{"cert"})
+ })
+}
+
+func testCert(t *testing.T, subcommand []string) {
homeDir := filepath.Join(t.TempDir(), ".vespa")
pkgDir := mockApplicationPackage(t, false)
- out, _ := execute(command{args: []string{"cert", "-a", "t1.a1.i1", pkgDir}, homeDir: homeDir}, t, nil)
+ args := append(subcommand, "-a", "t1.a1.i1", pkgDir)
+ out, _ := execute(command{args: args, homeDir: homeDir}, t, nil)
app, err := vespa.ApplicationFromString("t1.a1.i1")
assert.Nil(t, err)
@@ -28,11 +38,21 @@ func TestCert(t *testing.T) {
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)
assert.Contains(t, outErr, fmt.Sprintf("Error: application package %s already contains a certificate", appDir))
}
func TestCertCompressedPackage(t *testing.T) {
+ t.Run("auth cert", func(t *testing.T) {
+ testCertCompressedPackage(t, []string{"auth", "cert"})
+ })
+ t.Run("cert (deprecated)", func(t *testing.T) {
+ testCertCompressedPackage(t, []string{"cert"})
+ })
+}
+
+func testCertCompressedPackage(t *testing.T, subcommand []string) {
homeDir := filepath.Join(t.TempDir(), ".vespa")
pkgDir := mockApplicationPackage(t, true)
zipFile := filepath.Join(pkgDir, "target", "application.zip")
@@ -41,13 +61,15 @@ func TestCertCompressedPackage(t *testing.T) {
_, err = os.Create(zipFile)
assert.Nil(t, err)
- _, outErr := execute(command{args: []string{"cert", "-a", "t1.a1.i1", pkgDir}, homeDir: homeDir}, t, nil)
+ args := append(subcommand, "-a", "t1.a1.i1", pkgDir)
+ _, outErr := execute(command{args: args, homeDir: homeDir}, t, nil)
assert.Contains(t, outErr, "Error: cannot add certificate to compressed application package")
err = os.Remove(zipFile)
assert.Nil(t, err)
- out, _ := execute(command{args: []string{"cert", "-f", "-a", "t1.a1.i1", pkgDir}, homeDir: homeDir}, t, nil)
+ args = append(subcommand, "-f", "-a", "t1.a1.i1", pkgDir)
+ out, _ := execute(command{args: args, homeDir: homeDir}, t, nil)
assert.Contains(t, out, "Success: Certificate written to")
assert.Contains(t, out, "Success: Private key written to")
}
diff --git a/client/go/cmd/command_tester.go b/client/go/cmd/command_tester.go
index 82682b3b355..aaa1ba5d4ea 100644
--- a/client/go/cmd/command_tester.go
+++ b/client/go/cmd/command_tester.go
@@ -28,6 +28,7 @@ type command struct {
stdin io.ReadWriter
args []string
moreArgs []string
+ env map[string]string
failTestOnError bool
}
@@ -47,6 +48,23 @@ func execute(cmd command, t *testing.T, client *mockHttpClient) (string, string)
if client != nil {
util.ActiveHttpClient = client
}
+ originalEnv := map[string]string{}
+ for k, v := range cmd.env {
+ value, ok := os.LookupEnv(k)
+ if ok {
+ originalEnv[k] = value
+ }
+ os.Setenv(k, v)
+ }
+ defer func() {
+ for k, _ := range cmd.env {
+ if v, ok := originalEnv[k]; ok {
+ os.Setenv(k, v)
+ } else {
+ os.Unsetenv(k)
+ }
+ }
+ }()
// Set Vespa CLI directories. Use a separate one per test if none is specified
if cmd.homeDir == "" {
diff --git a/client/go/cmd/config.go b/client/go/cmd/config.go
index a195a3e1975..9d42f0683fd 100644
--- a/client/go/cmd/config.go
+++ b/client/go/cmd/config.go
@@ -17,6 +17,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
+ "github.com/vespa-engine/vespa/client/go/auth0"
"github.com/vespa-engine/vespa/client/go/util"
"github.com/vespa-engine/vespa/client/go/vespa"
)
@@ -26,7 +27,10 @@ const (
configType = "yaml"
)
-var flagToConfigBindings map[string]*cobra.Command = make(map[string]*cobra.Command)
+var (
+ flagToConfigBindings map[string]*cobra.Command = make(map[string]*cobra.Command)
+ envToConfigBindings map[string]string = make(map[string]string)
+)
func init() {
rootCmd.AddCommand(configCmd)
@@ -179,19 +183,42 @@ func (c *Config) X509KeyPair(app vespa.ApplicationID) (KeyPair, error) {
}
func (c *Config) APIKeyPath(tenantName string) string {
- if override, ok := os.LookupEnv("VESPA_CLI_API_KEY_FILE"); ok {
+ if override, err := c.Get(apiKeyFileFlag); err == nil && override != "" {
return override
}
return filepath.Join(c.Home, tenantName+".api-key.pem")
}
func (c *Config) ReadAPIKey(tenantName string) ([]byte, error) {
- if override, ok := os.LookupEnv("VESPA_CLI_API_KEY"); ok {
+ if override, err := c.Get(apiKeyFlag); err == nil && override != "" {
return []byte(override), nil
}
return ioutil.ReadFile(c.APIKeyPath(tenantName))
}
+// UseAPIKey checks if api key should be used be checking if api-key or api-key-file has been set.
+func (c *Config) UseAPIKey(tenantName string) bool {
+ if _, err := c.Get(apiKeyFlag); err == nil {
+ return true
+ }
+ if _, err := c.Get(apiKeyFileFlag); err == nil {
+ return true
+ }
+
+ // If no Auth0 token is created, fall back to tenant api key, but warn that this functionality is deprecated
+ // TODO: Remove this when users have had time to migrate over to Auth0 device flow authentication
+ a, err := auth0.GetAuth0(c.AuthConfigPath(), getSystemName(), getApiURL())
+ if err != nil || !a.HasSystem() {
+ fmt.Fprintln(stderr, "Defaulting to tenant API key is deprecated. Use Auth0 device flow: 'vespa auth login' instead")
+ if !util.PathExists(c.APIKeyPath(tenantName)) {
+ return false
+ }
+ return true
+ }
+
+ return false
+}
+
func (c *Config) AuthConfigPath() string {
return filepath.Join(c.Home, "auth.json")
}
@@ -234,6 +261,9 @@ func (c *Config) load() error {
for option, command := range flagToConfigBindings {
viper.BindPFlag(option, command.PersistentFlags().Lookup(option))
}
+ for option, env := range envToConfigBindings {
+ viper.BindEnv(option, env)
+ }
err := viper.ReadInConfig()
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
return nil
@@ -279,12 +309,9 @@ func (c *Config) Set(option, value string) error {
viper.Set(option, value)
return nil
}
- case cloudAuthFlag:
- switch value {
- case "access-token", "api-key":
- viper.Set(option, value)
- return nil
- }
+ case apiKeyFileFlag:
+ viper.Set(option, value)
+ return nil
}
return fmt.Errorf("invalid option or value: %q: %q", option, value)
}
@@ -302,3 +329,7 @@ func printOption(cfg *Config, option string) {
func bindFlagToConfig(option string, command *cobra.Command) {
flagToConfigBindings[option] = command
}
+
+func bindEnvToConfig(option string, env string) {
+ envToConfigBindings[option] = env
+}
diff --git a/client/go/cmd/config_test.go b/client/go/cmd/config_test.go
index ba0df781715..1d8fc95d765 100644
--- a/client/go/cmd/config_test.go
+++ b/client/go/cmd/config_test.go
@@ -2,10 +2,14 @@
package cmd
import (
+ "io/ioutil"
+ "os"
"path/filepath"
"testing"
+ "github.com/spf13/viper"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestConfig(t *testing.T) {
@@ -18,21 +22,31 @@ func TestConfig(t *testing.T) {
assertConfigCommand(t, "", homeDir, "config", "set", "target", "http://127.0.0.1:8080")
assertConfigCommand(t, "", homeDir, "config", "set", "target", "https://127.0.0.1")
assertConfigCommand(t, "target = https://127.0.0.1\n", homeDir, "config", "get", "target")
+ assertEnvConfigCommand(t, "api-key-file = /tmp/private.key\n", homeDir, map[string]string{"VESPA_CLI_API_KEY_FILE": "/tmp/private.key"}, "config", "get", "api-key-file")
+ assertConfigCommand(t, "", homeDir, "config", "set", "api-key-file", "/tmp/private.key")
+ assertConfigCommand(t, "api-key-file = /tmp/private.key\n", homeDir, "config", "get", "api-key-file")
assertConfigCommandErr(t, "Error: invalid application: \"foo\"\n", homeDir, "config", "set", "application", "foo")
assertConfigCommand(t, "application = <unset>\n", homeDir, "config", "get", "application")
assertConfigCommand(t, "", homeDir, "config", "set", "application", "t1.a1.i1")
assertConfigCommand(t, "application = t1.a1.i1\n", homeDir, "config", "get", "application")
- assertConfigCommand(t, "application = t1.a1.i1\ncolor = auto\nquiet = false\ntarget = https://127.0.0.1\nwait = 0\n", homeDir, "config", "get")
+ assertConfigCommand(t, "api-key-file = /tmp/private.key\napplication = t1.a1.i1\ncolor = auto\nquiet = false\ntarget = https://127.0.0.1\nwait = 0\n", homeDir, "config", "get")
assertConfigCommand(t, "", homeDir, "config", "set", "wait", "60")
assertConfigCommandErr(t, "Error: wait option must be an integer >= 0, got \"foo\"\n", homeDir, "config", "set", "wait", "foo")
assertConfigCommand(t, "wait = 60\n", homeDir, "config", "get", "wait")
+
+ // Avoid leaking config settings to other tests
+ viper.Reset()
}
func assertConfigCommand(t *testing.T, expected, homeDir string, args ...string) {
- out, _ := execute(command{homeDir: homeDir, args: args}, t, nil)
+ assertEnvConfigCommand(t, expected, homeDir, nil, args...)
+}
+
+func assertEnvConfigCommand(t *testing.T, expected, homeDir string, env map[string]string, args ...string) {
+ out, _ := execute(command{homeDir: homeDir, env: env, args: args}, t, nil)
assert.Equal(t, expected, out)
}
@@ -40,3 +54,55 @@ func assertConfigCommandErr(t *testing.T, expected, homeDir string, args ...stri
_, outErr := execute(command{homeDir: homeDir, args: args}, t, nil)
assert.Equal(t, expected, outErr)
}
+
+func withEnv(key, value string, fn func()) {
+ orig, ok := os.LookupEnv(key)
+ os.Setenv(key, value)
+ fn()
+ if ok {
+ os.Setenv(key, orig)
+ } else {
+ os.Unsetenv(key)
+ }
+}
+
+func TestUseAPIKey(t *testing.T) {
+ homeDir := t.TempDir()
+ c := Config{Home: homeDir}
+
+ assert.False(t, c.UseAPIKey("t1"))
+
+ c.Set(apiKeyFileFlag, "/tmp/foo")
+ assert.True(t, c.UseAPIKey("t1"))
+ c.Set(apiKeyFileFlag, "")
+
+ withEnv("VESPA_CLI_API_KEY", "...", func() {
+ require.Nil(t, c.load())
+ assert.True(t, c.UseAPIKey("t1"))
+ })
+
+ // Test deprecated functionality
+ authContent := `
+{
+ "version": 1,
+ "providers": {
+ "auth0": {
+ "version": 1,
+ "systems": {
+ "public": {
+ "access_token": "...",
+ "scopes": ["openid", "offline_access"],
+ "expires_at": "2030-01-01T01:01:01.000001+01:00"
+ }
+ }
+ }
+ }
+}`
+ withEnv("VESPA_CLI_CLOUD_SYSTEM", "public", func() {
+ _, err := os.Create(filepath.Join(homeDir, "t2.api-key.pem"))
+ require.Nil(t, err)
+ assert.True(t, c.UseAPIKey("t2"))
+ require.Nil(t, ioutil.WriteFile(filepath.Join(homeDir, "auth.json"), []byte(authContent), 0600))
+ assert.False(t, c.UseAPIKey("t2"))
+ })
+}
diff --git a/client/go/cmd/curl.go b/client/go/cmd/curl.go
index 47d0dcde95b..b66780780ed 100644
--- a/client/go/cmd/curl.go
+++ b/client/go/cmd/curl.go
@@ -2,7 +2,6 @@
package cmd
import (
- "errors"
"fmt"
"log"
"os"
@@ -11,7 +10,6 @@ import (
"github.com/spf13/cobra"
"github.com/vespa-engine/vespa/client/go/auth0"
"github.com/vespa-engine/vespa/client/go/curl"
- "github.com/vespa-engine/vespa/client/go/vespa"
)
var curlDryRun bool
@@ -64,9 +62,6 @@ $ vespa curl -- -v --data-urlencode "yql=select * from music where album contain
return err
}
if t.Type() == "cloud" {
- if !vespa.Auth0AccessTokenEnabled() {
- return errors.New("accessing control plane using curl subcommand is only supported for Auth0 device flow")
- }
if err := addCloudAuth0Authentication(cfg, c); err != nil {
return err
}
diff --git a/client/go/cmd/curl_test.go b/client/go/cmd/curl_test.go
index e593f48a390..5709096fe37 100644
--- a/client/go/cmd/curl_test.go
+++ b/client/go/cmd/curl_test.go
@@ -14,12 +14,12 @@ func TestCurl(t *testing.T) {
httpClient := &mockHttpClient{}
out, _ := execute(command{homeDir: homeDir, args: []string{"curl", "-n", "-a", "t1.a1.i1", "--", "-v", "--data-urlencode", "arg=with space", "/search"}}, t, httpClient)
- expected := fmt.Sprintf("curl --key %s --cert %s -v --data-urlencode 'arg=with space' https://127.0.0.1:8080/search\n",
+ expected := fmt.Sprintf("curl --key %s --cert %s -v --data-urlencode 'arg=with space' http://127.0.0.1:8080/search\n",
filepath.Join(homeDir, "t1.a1.i1", "data-plane-private-key.pem"),
filepath.Join(homeDir, "t1.a1.i1", "data-plane-public-cert.pem"))
assert.Equal(t, expected, out)
- out, _ = execute(command{homeDir: homeDir, args: []string{"curl", "-s", "deploy", "-n", "/application/v4/tenant/foo"}}, t, httpClient)
- expected = "curl https://127.0.0.1:19071/application/v4/tenant/foo\n"
+ out, _ = execute(command{homeDir: homeDir, args: []string{"curl", "-a", "t1.a1.i1", "-s", "deploy", "-n", "/application/v4/tenant/foo"}}, t, httpClient)
+ expected = "curl http://127.0.0.1:19071/application/v4/tenant/foo\n"
assert.Equal(t, expected, out)
}
diff --git a/client/go/cmd/helpers.go b/client/go/cmd/helpers.go
index 905e51dda4f..547126a8156 100644
--- a/client/go/cmd/helpers.go
+++ b/client/go/cmd/helpers.go
@@ -189,34 +189,15 @@ func createTarget() (vespa.Target, error) {
}
var apiKey []byte = nil
- apiKey, err = cfg.ReadAPIKey(deployment.Application.Tenant)
- if !vespa.Auth0AccessTokenEnabled() && endpoints == nil {
+ if cfg.UseAPIKey(deployment.Application.Tenant) {
+ apiKey, err = cfg.ReadAPIKey(deployment.Application.Tenant)
if err != nil {
- return nil, errHint(err, "Deployment to cloud requires an API key. Try 'vespa api-key'")
+ return nil, err
}
}
kp, err := cfg.X509KeyPair(deployment.Application)
if err != nil {
- var hint string
- if vespa.Auth0AccessTokenEnabled() {
- hint = "Deployment to cloud requires a certificate. Try 'vespa auth cert'"
- } else {
- hint = "Deployment to cloud requires a certificate. Try 'vespa cert'"
- }
- return nil, errHint(err, hint)
- }
- var cloudAuth string
- if vespa.Auth0AccessTokenEnabled() {
- cloudAuth, err = cfg.Get(cloudAuthFlag)
- if err != nil {
- if apiKey != nil {
- cloudAuth = "api-key"
- } else {
- cloudAuth = "access-token"
- }
- }
- } else {
- cloudAuth = ""
+ return nil, errHint(err, "Deployment to cloud requires a certificate. Try 'vespa auth cert'")
}
return vespa.CloudTarget(
@@ -234,7 +215,6 @@ func createTarget() (vespa.Target, error) {
},
cfg.AuthConfigPath(),
getSystemName(),
- cloudAuth,
endpoints,
), nil
}
@@ -270,18 +250,13 @@ func getDeploymentOpts(cfg *Config, pkg vespa.ApplicationPackage, target vespa.T
return vespa.DeploymentOpts{}, err
}
if !opts.ApplicationPackage.HasCertificate() {
- var hint string
- if vespa.Auth0AccessTokenEnabled() {
- hint = "Try 'vespa auth cert'"
- } else {
- hint = "Try 'vespa cert'"
- }
+ hint := "Try 'vespa auth cert'"
return vespa.DeploymentOpts{}, errHint(fmt.Errorf("missing certificate in application package"), "Applications in Vespa Cloud require a certificate", hint)
}
- opts.APIKey, err = cfg.ReadAPIKey(deployment.Application.Tenant)
- if !vespa.Auth0AccessTokenEnabled() {
+ if cfg.UseAPIKey(deployment.Application.Tenant) {
+ opts.APIKey, err = cfg.ReadAPIKey(deployment.Application.Tenant)
if err != nil {
- return vespa.DeploymentOpts{}, errHint(err, "Deployment to cloud requires an API key. Try 'vespa api-key'")
+ return vespa.DeploymentOpts{}, err
}
}
opts.Deployment = deployment
diff --git a/client/go/cmd/log_test.go b/client/go/cmd/log_test.go
index 9d895d1f244..24ccd9e266d 100644
--- a/client/go/cmd/log_test.go
+++ b/client/go/cmd/log_test.go
@@ -5,6 +5,7 @@ import (
"path/filepath"
"testing"
+ "github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/vespa-engine/vespa/client/go/build"
)
@@ -16,7 +17,8 @@ func TestLog(t *testing.T) {
httpClient.NextResponse(200, `1632738690.905535 host1a.dev.aws-us-east-1c 806/53 logserver-container Container.com.yahoo.container.jdisc.ConfiguredApplication info Switching to the latest deployed set of configurations and components. Application config generation: 52532`)
execute(command{homeDir: homeDir, args: []string{"config", "set", "application", "t1.a1.i1"}}, t, httpClient)
execute(command{homeDir: homeDir, args: []string{"config", "set", "target", "cloud"}}, t, httpClient)
- execute(command{homeDir: homeDir, args: []string{"api-key"}}, t, httpClient)
+ execute(command{homeDir: homeDir, args: []string{"auth", "api-key"}}, t, httpClient)
+ execute(command{homeDir: homeDir, args: []string{"config", "set", "api-key-file", filepath.Join(homeDir, "t1.api-key.pem")}}, t, httpClient)
execute(command{homeDir: homeDir, args: []string{"cert", pkgDir}}, t, httpClient)
out, _ := execute(command{homeDir: homeDir, args: []string{"log", "--from", "2021-09-27T10:00:00Z", "--to", "2021-09-27T11:00:00Z"}}, t, httpClient)
@@ -26,6 +28,7 @@ func TestLog(t *testing.T) {
_, errOut := execute(command{homeDir: homeDir, args: []string{"log", "--from", "2021-09-27T13:12:49Z", "--to", "2021-09-27T13:15:00", "1h"}}, t, httpClient)
assert.Equal(t, "Error: invalid period: cannot combine --from/--to with relative value: 1h\n", errOut)
+ viper.Reset()
}
func TestLogOldClient(t *testing.T) {
@@ -37,11 +40,13 @@ func TestLogOldClient(t *testing.T) {
httpClient.NextResponse(200, `{"minVersion": "8.0.0"}`)
execute(command{homeDir: homeDir, args: []string{"config", "set", "application", "t1.a1.i1"}}, t, httpClient)
execute(command{homeDir: homeDir, args: []string{"config", "set", "target", "cloud"}}, t, httpClient)
- execute(command{homeDir: homeDir, args: []string{"api-key"}}, t, httpClient)
+ execute(command{homeDir: homeDir, args: []string{"auth", "api-key"}}, t, httpClient)
+ execute(command{homeDir: homeDir, args: []string{"config", "set", "api-key-file", filepath.Join(homeDir, "t1.api-key.pem")}}, t, httpClient)
execute(command{homeDir: homeDir, args: []string{"cert", pkgDir}}, t, httpClient)
out, errOut := execute(command{homeDir: homeDir, args: []string{"log"}}, t, httpClient)
assert.Equal(t, "", out)
expected := "Error: client version 7.0.0 is less than the minimum supported version: 8.0.0\nHint: This is not a fatal error, but this version may not work as expected\nHint: Try 'vespa version' to check for a new version\n"
assert.Equal(t, expected, errOut)
build.Version = buildVersion
+ viper.Reset()
}
diff --git a/client/go/cmd/login.go b/client/go/cmd/login.go
index f4438cdbb24..8787f1f80f5 100644
--- a/client/go/cmd/login.go
+++ b/client/go/cmd/login.go
@@ -3,7 +3,6 @@ package cmd
import (
"github.com/spf13/cobra"
"github.com/vespa-engine/vespa/client/go/auth0"
- "github.com/vespa-engine/vespa/client/go/vespa"
)
var loginCmd = &cobra.Command{
@@ -24,16 +23,6 @@ var loginCmd = &cobra.Command{
return err
}
_, err = auth0.RunLogin(ctx, a, false)
- if vespa.Auth0AccessTokenEnabled() {
- if err == nil {
- if err := cfg.Set(cloudAuthFlag, "access-token"); err != nil {
- return err
- }
- if err := cfg.Write(); err != nil {
- return err
- }
- }
- }
return err
},
}
diff --git a/client/go/cmd/prod_test.go b/client/go/cmd/prod_test.go
index 4e635f87a75..8fa9ef401b5 100644
--- a/client/go/cmd/prod_test.go
+++ b/client/go/cmd/prod_test.go
@@ -166,7 +166,7 @@ func TestProdSubmit(t *testing.T) {
if err := os.Setenv("CI", "true"); err != nil {
t.Fatal(err)
}
- out, err := execute(command{homeDir: homeDir, args: []string{"prod", "submit"}}, t, httpClient)
+ out, err := execute(command{homeDir: homeDir, args: []string{"prod", "submit", "-k", filepath.Join(homeDir, "t1.api-key.pem")}}, t, httpClient)
assert.Equal(t, "", err)
assert.Contains(t, out, "Success: Submitted")
assert.Contains(t, out, "See https://console.vespa.oath.cloud/tenant/t1/application/a1/prod/deployment for deployment progress")
@@ -191,7 +191,7 @@ func TestProdSubmitWithJava(t *testing.T) {
testZipFile := filepath.Join(testAppDir, "application-test.zip")
copyFile(t, filepath.Join(pkgDir, "target", "application-test.zip"), testZipFile)
- out, _ := execute(command{homeDir: homeDir, args: []string{"prod", "submit", pkgDir}}, t, httpClient)
+ out, _ := execute(command{homeDir: homeDir, args: []string{"prod", "submit", "-k", filepath.Join(homeDir, "t1.api-key.pem"), pkgDir}}, t, httpClient)
assert.Contains(t, out, "Success: Submitted")
assert.Contains(t, out, "See https://console.vespa.oath.cloud/tenant/t1/application/a1/prod/deployment for deployment progress")
}
diff --git a/client/go/cmd/root.go b/client/go/cmd/root.go
index 0f4bef5595f..f5a846536c5 100644
--- a/client/go/cmd/root.go
+++ b/client/go/cmd/root.go
@@ -53,6 +53,8 @@ Vespa documentation: https://docs.vespa.ai`,
waitSecsArg int
colorArg string
quietArg bool
+ apiKeyFileArg string
+ apiKeyArg string
stdin io.ReadWriter = os.Stdin
color = aurora.NewAurora(false)
@@ -66,7 +68,8 @@ const (
waitFlag = "wait"
colorFlag = "color"
quietFlag = "quiet"
- cloudAuthFlag = "cloudAuth"
+ apiKeyFileFlag = "api-key-file"
+ apiKeyFlag = "api-key"
)
func isTerminal() bool {
@@ -115,11 +118,17 @@ func init() {
rootCmd.PersistentFlags().IntVarP(&waitSecsArg, waitFlag, "w", 0, "Number of seconds to wait for a service to become ready")
rootCmd.PersistentFlags().StringVarP(&colorArg, colorFlag, "c", "auto", "Whether to use colors in output. Can be \"auto\", \"never\" or \"always\"")
rootCmd.PersistentFlags().BoolVarP(&quietArg, quietFlag, "q", false, "Quiet mode. Only errors are printed.")
+ rootCmd.PersistentFlags().StringVarP(&apiKeyFileArg, apiKeyFileFlag, "k", "", "Path to API key used for deployment authentication")
+
bindFlagToConfig(targetFlag, rootCmd)
bindFlagToConfig(applicationFlag, rootCmd)
bindFlagToConfig(waitFlag, rootCmd)
bindFlagToConfig(colorFlag, rootCmd)
bindFlagToConfig(quietFlag, rootCmd)
+ bindFlagToConfig(apiKeyFileFlag, rootCmd)
+
+ bindEnvToConfig(apiKeyFlag, "VESPA_CLI_API_KEY")
+ bindEnvToConfig(apiKeyFileFlag, "VESPA_CLI_API_KEY_FILE")
}
// errHint creates a new CLI error, with optional hints that will be printed after the error
diff --git a/client/go/cmd/test_test.go b/client/go/cmd/test_test.go
index 2c59bbbc030..9c161c091ec 100644
--- a/client/go/cmd/test_test.go
+++ b/client/go/cmd/test_test.go
@@ -13,10 +13,10 @@ import (
"strings"
"testing"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"github.com/vespa-engine/vespa/client/go/util"
"github.com/vespa-engine/vespa/client/go/vespa"
-
- "github.com/stretchr/testify/assert"
)
func TestSuite(t *testing.T) {
@@ -93,7 +93,12 @@ func TestSingleTest(t *testing.T) {
}
func TestSingleTestWithCloudAndEndpoints(t *testing.T) {
- cmd := command{args: []string{"test", "testdata/tests/system-test/test.json", "-t", "cloud", "-a", "t.a.i"}}
+ apiKey, err := vespa.CreateAPIKey()
+ require.Nil(t, err)
+ cmd := command{
+ args: []string{"test", "testdata/tests/system-test/test.json", "-t", "cloud", "-a", "t.a.i"},
+ env: map[string]string{"VESPA_CLI_API_KEY": string(apiKey)},
+ }
cmd.homeDir = filepath.Join(t.TempDir(), ".vespa")
os.MkdirAll(cmd.homeDir, 0700)
keyFile := filepath.Join(cmd.homeDir, "key")
diff --git a/client/go/vespa/deploy.go b/client/go/vespa/deploy.go
index cac03067a69..aed430399a0 100644
--- a/client/go/vespa/deploy.go
+++ b/client/go/vespa/deploy.go
@@ -348,11 +348,6 @@ func checkDeploymentOpts(opts DeploymentOpts) error {
if !opts.ApplicationPackage.HasCertificate() {
return fmt.Errorf("%s: missing certificate in package", opts)
}
- if !Auth0AccessTokenEnabled() {
- if opts.APIKey == nil {
- return fmt.Errorf("%s: missing api key", opts.String())
- }
- }
return nil
}
diff --git a/client/go/vespa/target.go b/client/go/vespa/target.go
index ae4934d788d..204dbc143c6 100644
--- a/client/go/vespa/target.go
+++ b/client/go/vespa/target.go
@@ -12,10 +12,8 @@ import (
"math"
"net/http"
"net/url"
- "os"
"sort"
"strconv"
- "strings"
"time"
"github.com/vespa-engine/vespa/client/go/auth0"
@@ -35,6 +33,11 @@ const (
retryInterval = 2 * time.Second
)
+const (
+ CloudAuthApiKey = "api-key"
+ CloudAuthAccessToken = "access-token"
+)
+
// Service represents a Vespa service.
type Service struct {
BaseURL string
@@ -77,14 +80,6 @@ type LogOptions struct {
Level int
}
-func Auth0AccessTokenEnabled() bool {
- v, present := os.LookupEnv("VESPA_CLI_OAUTH2_DEVICE_FLOW")
- if !present {
- return false
- }
- return strings.ToLower(v) == "true" || v == "1" || v == ""
-}
-
type customTarget struct {
targetType string
baseURL string
@@ -218,7 +213,6 @@ type cloudTarget struct {
urlsByCluster map[string]string
authConfigPath string
systemName string
- cloudAuth string
}
func (t *cloudTarget) resolveEndpoint(cluster string) (string, error) {
@@ -272,26 +266,18 @@ func (t *cloudTarget) Service(name string, timeout time.Duration, runID int64, c
return nil, fmt.Errorf("unknown service: %s", name)
}
+// SignRequest adds authentication data to a http.Request.
+// The api key is used if set on cloudTarget, if not the Auth0 device flow is used.
func (t *cloudTarget) SignRequest(req *http.Request, sigKeyId string) error {
- if Auth0AccessTokenEnabled() {
- if t.cloudAuth == "access-token" {
- if err := t.addAuth0AccessToken(req); err != nil {
- return err
- }
- } else {
- if t.apiKey == nil {
- return fmt.Errorf("Deployment to cloud requires an API key. Try 'vespa api-key'")
- }
- signer := NewRequestSigner(sigKeyId, t.apiKey)
- if err := signer.SignRequest(req); err != nil {
- return err
- }
- }
- } else {
+ if t.apiKey != nil {
signer := NewRequestSigner(sigKeyId, t.apiKey)
if err := signer.SignRequest(req); err != nil {
return err
}
+ } else {
+ if err := t.addAuth0AccessToken(req); err != nil {
+ return err
+ }
}
return nil
}
@@ -528,7 +514,7 @@ func CustomTarget(baseURL string) Target {
// CloudTarget creates a Target for the Vespa Cloud platform.
func CloudTarget(apiURL string, deployment Deployment, apiKey []byte, tlsOptions TLSOptions, logOptions LogOptions,
- authConfigPath string, systemName string, cloudAuth string, urlsByCluster map[string]string) Target {
+ authConfigPath string, systemName string, urlsByCluster map[string]string) Target {
return &cloudTarget{
apiURL: apiURL,
targetType: cloudTargetType,
@@ -538,7 +524,6 @@ func CloudTarget(apiURL string, deployment Deployment, apiKey []byte, tlsOptions
logOptions: logOptions,
authConfigPath: authConfigPath,
systemName: systemName,
- cloudAuth: cloudAuth,
urlsByCluster: urlsByCluster,
}
}
diff --git a/client/go/vespa/target_test.go b/client/go/vespa/target_test.go
index 5aa20b22465..8391655eaf7 100644
--- a/client/go/vespa/target_test.go
+++ b/client/go/vespa/target_test.go
@@ -166,16 +166,13 @@ func createCloudTarget(t *testing.T, url string, logWriter io.Writer) Target {
x509KeyPair, err := tls.X509KeyPair(kp.Certificate, kp.PrivateKey)
assert.Nil(t, err)
- var apiKey []byte = nil
- if !Auth0AccessTokenEnabled() {
- apiKey, err = CreateAPIKey()
- }
+ apiKey, err := CreateAPIKey()
assert.Nil(t, err)
target := CloudTarget("https://example.com", Deployment{
Application: ApplicationID{Tenant: "t1", Application: "a1", Instance: "i1"},
Zone: ZoneID{Environment: "dev", Region: "us-north-1"},
- }, apiKey, TLSOptions{KeyPair: x509KeyPair}, LogOptions{Writer: logWriter}, "", "", "", nil)
+ }, apiKey, TLSOptions{KeyPair: x509KeyPair}, LogOptions{Writer: logWriter}, "", "", nil)
if ct, ok := target.(*cloudTarget); ok {
ct.apiURL = url
} else {