fix(config): parse initial admin keys paths

Ayman Bagabas created

Change summary

.gitignore                    |  1 
server/config/config.go       | 27 ++++++++++++++++--
server/config/config_test.go  | 54 ++++++++++++++++++++++++++++++++++--
server/config/testdata/k1.pub |  1 
4 files changed, 75 insertions(+), 8 deletions(-)

Detailed changes

.gitignore 🔗

@@ -3,7 +3,6 @@ cmd/soft/soft
 .ssh
 .repos
 dist
-testdata
 data/
 completions/
 manpages/

server/config/config.go 🔗

@@ -143,6 +143,9 @@ func parseConfig(path string) (*Config, error) {
 		}
 	}
 
+	// Merge initial admin keys from both config file and environment variables.
+	initialAdminKeys := append([]string{}, cfg.InitialAdminKeys...)
+
 	// Override with environment variables
 	if err := env.Parse(cfg, env.Options{
 		Prefix: "SOFT_SERVE_",
@@ -150,13 +153,31 @@ func parseConfig(path string) (*Config, error) {
 		return cfg, fmt.Errorf("parse environment variables: %w", err)
 	}
 
+	// Merge initial admin keys from environment variables.
+	if initialAdminKeysEnv := os.Getenv("SOFT_SERVE_INITIAL_ADMIN_KEYS"); initialAdminKeysEnv != "" {
+		cfg.InitialAdminKeys = append(cfg.InitialAdminKeys, initialAdminKeys...)
+	}
+
+	// Validate keys
+	pks := make([]string, 0)
 	for _, key := range cfg.InitialAdminKeys {
-		if _, _, err := backend.ParseAuthorizedKey(key); err != nil {
-			log.Error("invalid initial admin key", "err", err)
+		var pk string
+		if bts, err := os.ReadFile(key); err == nil {
+			// key is a file
+			pk = string(bts)
+		}
+		if _, _, err := backend.ParseAuthorizedKey(key); err == nil {
+			pk = key
+		}
+		pk = strings.TrimSpace(pk)
+		if pk != "" {
+			log.Debugf("found initial admin key: %q", key)
+			pks = append(pks, pk)
 		}
-		log.Debugf("found initial admin key: %q", key)
 	}
 
+	cfg.InitialAdminKeys = pks
+
 	// Reset datapath to config dir.
 	// This is necessary because the environment variable may be set to
 	// a different directory.

server/config/config_test.go 🔗

@@ -2,18 +2,64 @@ package config
 
 import (
 	"os"
+	"path/filepath"
 	"testing"
 
 	"github.com/matryer/is"
+	"gopkg.in/yaml.v3"
 )
 
 func TestParseMultipleKeys(t *testing.T) {
 	is := is.New(t)
-	is.NoErr(os.Setenv("SOFT_SERVE_INITIAL_ADMIN_KEYS", "testdata/k1.pub\ntestdata/k2.pub"))
-	t.Cleanup(func() { is.NoErr(os.Unsetenv("SOFT_SERVE_INITIAL_ADMIN_KEYS")) })
+	td := t.TempDir()
+	is.NoErr(os.Setenv("SOFT_SERVE_INITIAL_ADMIN_KEYS", "testdata/k1.pub\nssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxIobhwtfdwN7m1TFt9wx3PsfvcAkISGPxmbmbauST8 a@b"))
+	is.NoErr(os.Setenv("SOFT_SERVE_DATA_PATH", td))
+	t.Cleanup(func() {
+		is.NoErr(os.Unsetenv("SOFT_SERVE_INITIAL_ADMIN_KEYS"))
+		is.NoErr(os.Unsetenv("SOFT_SERVE_DATA_PATH"))
+	})
 	cfg := DefaultConfig()
 	is.Equal(cfg.InitialAdminKeys, []string{
-		"testdata/k1.pub",
-		"testdata/k2.pub",
+		"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMwLvyV3ouVrTysUYGoJdl5Vgn5BACKov+n9PlzfPwH a@b",
+		"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxIobhwtfdwN7m1TFt9wx3PsfvcAkISGPxmbmbauST8 a@b",
+	})
+}
+
+func TestMergeInitAdminKeys(t *testing.T) {
+	is := is.New(t)
+	is.NoErr(os.Setenv("SOFT_SERVE_INITIAL_ADMIN_KEYS", "testdata/k1.pub"))
+	t.Cleanup(func() { is.NoErr(os.Unsetenv("SOFT_SERVE_INITIAL_ADMIN_KEYS")) })
+	bts, err := yaml.Marshal(&Config{
+		InitialAdminKeys: []string{"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxIobhwtfdwN7m1TFt9wx3PsfvcAkISGPxmbmbauST8 a@b"},
+	})
+	is.NoErr(err)
+	fp := filepath.Join(t.TempDir(), "config.yaml")
+	err = os.WriteFile(fp, bts, 0644)
+	is.NoErr(err)
+	cfg, err := ParseConfig(fp)
+	is.NoErr(err)
+	is.Equal(cfg.InitialAdminKeys, []string{
+		"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMwLvyV3ouVrTysUYGoJdl5Vgn5BACKov+n9PlzfPwH a@b",
+		"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxIobhwtfdwN7m1TFt9wx3PsfvcAkISGPxmbmbauST8 a@b",
+	})
+}
+
+func TestValidateInitAdminKeys(t *testing.T) {
+	is := is.New(t)
+	bts, err := yaml.Marshal(&Config{
+		InitialAdminKeys: []string{
+			"testdata/k1.pub",
+			"abc",
+			"",
+		},
+	})
+	is.NoErr(err)
+	fp := filepath.Join(t.TempDir(), "config.yaml")
+	err = os.WriteFile(fp, bts, 0644)
+	is.NoErr(err)
+	cfg, err := ParseConfig(fp)
+	is.NoErr(err)
+	is.Equal(cfg.InitialAdminKeys, []string{
+		"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMwLvyV3ouVrTysUYGoJdl5Vgn5BACKov+n9PlzfPwH a@b",
 	})
 }