Diff
1diff --git a/.gitignore b/.gitignore
2index ae70eac83c310247658c36ab11f4e764026d29b1..041a83d7fdefbb79b4786c8b5d52cde3f56ad771 100644
3--- a/.gitignore
4+++ b/.gitignore
5@@ -3,7 +3,6 @@ cmd/soft/soft
6 .ssh
7 .repos
8 dist
9-testdata
10 data/
11 completions/
12 manpages/
13diff --git a/server/config/config.go b/server/config/config.go
14index 36a5bddcd8449369c9f13bf33c55e67cd47ccf09..13ecd1268a79c32ebf67cf7551e922c090ba29cf 100644
15--- a/server/config/config.go
16+++ b/server/config/config.go
17@@ -143,6 +143,9 @@ func parseConfig(path string) (*Config, error) {
18 }
19 }
20
21+ // Merge initial admin keys from both config file and environment variables.
22+ initialAdminKeys := append([]string{}, cfg.InitialAdminKeys...)
23+
24 // Override with environment variables
25 if err := env.Parse(cfg, env.Options{
26 Prefix: "SOFT_SERVE_",
27@@ -150,13 +153,31 @@ func parseConfig(path string) (*Config, error) {
28 return cfg, fmt.Errorf("parse environment variables: %w", err)
29 }
30
31+ // Merge initial admin keys from environment variables.
32+ if initialAdminKeysEnv := os.Getenv("SOFT_SERVE_INITIAL_ADMIN_KEYS"); initialAdminKeysEnv != "" {
33+ cfg.InitialAdminKeys = append(cfg.InitialAdminKeys, initialAdminKeys...)
34+ }
35+
36+ // Validate keys
37+ pks := make([]string, 0)
38 for _, key := range cfg.InitialAdminKeys {
39- if _, _, err := backend.ParseAuthorizedKey(key); err != nil {
40- log.Error("invalid initial admin key", "err", err)
41+ var pk string
42+ if bts, err := os.ReadFile(key); err == nil {
43+ // key is a file
44+ pk = string(bts)
45+ }
46+ if _, _, err := backend.ParseAuthorizedKey(key); err == nil {
47+ pk = key
48+ }
49+ pk = strings.TrimSpace(pk)
50+ if pk != "" {
51+ log.Debugf("found initial admin key: %q", key)
52+ pks = append(pks, pk)
53 }
54- log.Debugf("found initial admin key: %q", key)
55 }
56
57+ cfg.InitialAdminKeys = pks
58+
59 // Reset datapath to config dir.
60 // This is necessary because the environment variable may be set to
61 // a different directory.
62diff --git a/server/config/config_test.go b/server/config/config_test.go
63index b3685209546af8797855750b6c23a415bd1a46d2..cd4a90198cc1f14ca1bdfaf88f242a5d26749226 100644
64--- a/server/config/config_test.go
65+++ b/server/config/config_test.go
66@@ -2,18 +2,64 @@ package config
67
68 import (
69 "os"
70+ "path/filepath"
71 "testing"
72
73 "github.com/matryer/is"
74+ "gopkg.in/yaml.v3"
75 )
76
77 func TestParseMultipleKeys(t *testing.T) {
78 is := is.New(t)
79- is.NoErr(os.Setenv("SOFT_SERVE_INITIAL_ADMIN_KEYS", "testdata/k1.pub\ntestdata/k2.pub"))
80- t.Cleanup(func() { is.NoErr(os.Unsetenv("SOFT_SERVE_INITIAL_ADMIN_KEYS")) })
81+ td := t.TempDir()
82+ is.NoErr(os.Setenv("SOFT_SERVE_INITIAL_ADMIN_KEYS", "testdata/k1.pub\nssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxIobhwtfdwN7m1TFt9wx3PsfvcAkISGPxmbmbauST8 a@b"))
83+ is.NoErr(os.Setenv("SOFT_SERVE_DATA_PATH", td))
84+ t.Cleanup(func() {
85+ is.NoErr(os.Unsetenv("SOFT_SERVE_INITIAL_ADMIN_KEYS"))
86+ is.NoErr(os.Unsetenv("SOFT_SERVE_DATA_PATH"))
87+ })
88 cfg := DefaultConfig()
89 is.Equal(cfg.InitialAdminKeys, []string{
90- "testdata/k1.pub",
91- "testdata/k2.pub",
92+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMwLvyV3ouVrTysUYGoJdl5Vgn5BACKov+n9PlzfPwH a@b",
93+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxIobhwtfdwN7m1TFt9wx3PsfvcAkISGPxmbmbauST8 a@b",
94+ })
95+}
96+
97+func TestMergeInitAdminKeys(t *testing.T) {
98+ is := is.New(t)
99+ is.NoErr(os.Setenv("SOFT_SERVE_INITIAL_ADMIN_KEYS", "testdata/k1.pub"))
100+ t.Cleanup(func() { is.NoErr(os.Unsetenv("SOFT_SERVE_INITIAL_ADMIN_KEYS")) })
101+ bts, err := yaml.Marshal(&Config{
102+ InitialAdminKeys: []string{"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxIobhwtfdwN7m1TFt9wx3PsfvcAkISGPxmbmbauST8 a@b"},
103+ })
104+ is.NoErr(err)
105+ fp := filepath.Join(t.TempDir(), "config.yaml")
106+ err = os.WriteFile(fp, bts, 0644)
107+ is.NoErr(err)
108+ cfg, err := ParseConfig(fp)
109+ is.NoErr(err)
110+ is.Equal(cfg.InitialAdminKeys, []string{
111+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMwLvyV3ouVrTysUYGoJdl5Vgn5BACKov+n9PlzfPwH a@b",
112+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxIobhwtfdwN7m1TFt9wx3PsfvcAkISGPxmbmbauST8 a@b",
113+ })
114+}
115+
116+func TestValidateInitAdminKeys(t *testing.T) {
117+ is := is.New(t)
118+ bts, err := yaml.Marshal(&Config{
119+ InitialAdminKeys: []string{
120+ "testdata/k1.pub",
121+ "abc",
122+ "",
123+ },
124+ })
125+ is.NoErr(err)
126+ fp := filepath.Join(t.TempDir(), "config.yaml")
127+ err = os.WriteFile(fp, bts, 0644)
128+ is.NoErr(err)
129+ cfg, err := ParseConfig(fp)
130+ is.NoErr(err)
131+ is.Equal(cfg.InitialAdminKeys, []string{
132+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMwLvyV3ouVrTysUYGoJdl5Vgn5BACKov+n9PlzfPwH a@b",
133 })
134 }
135diff --git a/server/config/testdata/k1.pub b/server/config/testdata/k1.pub
136new file mode 100644
137index 0000000000000000000000000000000000000000..d82e29394d343e6e36bc1759b06689a399ea80a4
138--- /dev/null
139+++ b/server/config/testdata/k1.pub
140@@ -0,0 +1 @@
141+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMwLvyV3ouVrTysUYGoJdl5Vgn5BACKov+n9PlzfPwH a@b