Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions cmd/zb/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"net/url"
"os"
"path/filepath"
"slices"
"strings"
"time"

Expand Down Expand Up @@ -73,12 +74,14 @@ func (g *globalConfig) clone() *globalConfig {
return nil
}
g = new(*g)
g.TrustedPublicKeys = slices.Clone(g.TrustedPublicKeys)
if g.Server.Download != nil {
g.Server.Download = new(*g.Server.Download)
}
if g.Server.Upload != nil {
g.Server.Upload = new(*g.Server.Upload)
}
g.Server.KeyFiles = slices.Clone(g.Server.KeyFiles)
return g
}

Expand Down Expand Up @@ -170,6 +173,19 @@ func (g *globalConfig) resolveRelativePaths(dir string, prev *globalConfig) {
g.Server.Upload = g.Server.Upload.resolve(baseURL)
}
}
if prev == nil || !slices.Equal(g.Server.KeyFiles, prev.Server.KeyFiles) {
// If the previous slice is a prefix of the current slice,
// then only resolve the newly added paths.
toResolve := g.Server.KeyFiles
if len(g.Server.KeyFiles) > len(prev.Server.KeyFiles) &&
slices.Equal(g.Server.KeyFiles[:len(prev.Server.KeyFiles)], prev.Server.KeyFiles) {
toResolve = g.Server.KeyFiles[len(prev.Server.KeyFiles):]
}

for i, path := range toResolve {
toResolve[i] = resolve(path)
}
}
}

// UnmarshalJSONFrom unmarshals the configuration object from the JSON decoder,
Expand Down Expand Up @@ -242,6 +258,9 @@ func (g *globalConfig) UnmarshalJSONFrom(in *jsontext.Decoder) error {
if reject, _ := jsonv2.GetOption(in.Options(), jsonv2.RejectUnknownMembers); reject {
return fmt.Errorf("unmarshal config: unknown field %q", k)
}
if err := in.SkipValue(); err != nil {
return fmt.Errorf("unmarshal config.server: %w", err)
}
}
}
}
Expand Down
83 changes: 55 additions & 28 deletions cmd/zb/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,19 @@ func TestGlobalConfigMergeFiles(t *testing.T) {
tests := []struct {
name string
files []string
want globalConfig
want func(dir string) *globalConfig
}{
{
name: "MergeScalar",
files: []string{
`{"debug": true, "storeDirectory": "/foo"}` + "\n",
`{"storeDirectory": "/bar"}` + "\n",
},
want: globalConfig{
Debug: true,
Directory: "/bar",
want: func(dir string) *globalConfig {
return &globalConfig{
Debug: true,
Directory: "/bar",
}
},
},
{
Expand All @@ -50,10 +52,12 @@ func TestGlobalConfigMergeFiles(t *testing.T) {
`{"allowEnvironment": ["FOO"]}` + "\n",
`{"allowEnvironment": ["BAR"]}` + "\n",
},
want: globalConfig{
AllowEnv: stringAllowList{
set: sets.New("BAR"),
},
want: func(dir string) *globalConfig {
return &globalConfig{
AllowEnv: stringAllowList{
set: sets.New("BAR"),
},
}
},
},
{
Expand All @@ -62,8 +66,10 @@ func TestGlobalConfigMergeFiles(t *testing.T) {
`{"allowEnvironment": ["FOO"]}` + "\n",
`{"allowEnvironment": true}` + "\n",
},
want: globalConfig{
AllowEnv: stringAllowList{all: true},
want: func(dir string) *globalConfig {
return &globalConfig{
AllowEnv: stringAllowList{all: true},
}
},
},
{
Expand All @@ -72,26 +78,46 @@ func TestGlobalConfigMergeFiles(t *testing.T) {
`{"trustedPublicKeys": [{"format": "ed25519", "publicKey": "+NMDNfvjCmdT9mLr9zadYQXwF/mPLsToMw36yX7w6HCVCSK9J2WsMGPCAT9U2Y959NFgAfdiSWGRvWbXYlGUcA=="}]}` + "\n",
`{"trustedPublicKeys": [{"format": "foo", "publicKey": "YmFy"}]}` + "\n",
},
want: globalConfig{
TrustedPublicKeys: []*zbstore.RealizationPublicKey{
{
Format: "ed25519",
Data: []byte{
0xf8, 0xd3, 0x03, 0x35, 0xfb, 0xe3, 0x0a, 0x67,
0x53, 0xf6, 0x62, 0xeb, 0xf7, 0x36, 0x9d, 0x61,
0x05, 0xf0, 0x17, 0xf9, 0x8f, 0x2e, 0xc4, 0xe8,
0x33, 0x0d, 0xfa, 0xc9, 0x7e, 0xf0, 0xe8, 0x70,
0x95, 0x09, 0x22, 0xbd, 0x27, 0x65, 0xac, 0x30,
0x63, 0xc2, 0x01, 0x3f, 0x54, 0xd9, 0x8f, 0x79,
0xf4, 0xd1, 0x60, 0x01, 0xf7, 0x62, 0x49, 0x61,
0x91, 0xbd, 0x66, 0xd7, 0x62, 0x51, 0x94, 0x70,
want: func(dir string) *globalConfig {
return &globalConfig{
TrustedPublicKeys: []*zbstore.RealizationPublicKey{
{
Format: "ed25519",
Data: []byte{
0xf8, 0xd3, 0x03, 0x35, 0xfb, 0xe3, 0x0a, 0x67,
0x53, 0xf6, 0x62, 0xeb, 0xf7, 0x36, 0x9d, 0x61,
0x05, 0xf0, 0x17, 0xf9, 0x8f, 0x2e, 0xc4, 0xe8,
0x33, 0x0d, 0xfa, 0xc9, 0x7e, 0xf0, 0xe8, 0x70,
0x95, 0x09, 0x22, 0xbd, 0x27, 0x65, 0xac, 0x30,
0x63, 0xc2, 0x01, 0x3f, 0x54, 0xd9, 0x8f, 0x79,
0xf4, 0xd1, 0x60, 0x01, 0xf7, 0x62, 0x49, 0x61,
0x91, 0xbd, 0x66, 0xd7, 0x62, 0x51, 0x94, 0x70,
},
},
{
Format: "foo",
Data: []byte{0x62, 0x61, 0x72},
},
},
{
Format: "foo",
Data: []byte{0x62, 0x61, 0x72},
}
},
},
{
name: "MergeServerSigningKeyFiles",
files: []string{
`{"server": {"signingKeyFiles": ["foo.json", "bar.json"]}}` + "\n",
`{"server": {"signingKeyFiles": ["baz.json"]}}` + "\n",
},
want: func(dir string) *globalConfig {
return &globalConfig{
Server: serverConfig{
KeyFiles: []string{
filepath.Join(dir, "foo.json"),
filepath.Join(dir, "bar.json"),
filepath.Join(dir, "baz.json"),
},
},
},
}
},
},
}
Expand All @@ -113,7 +139,7 @@ func TestGlobalConfigMergeFiles(t *testing.T) {
if err != nil {
t.Error("mergeFiles:", err)
}
if diff := cmp.Diff(&test.want, got, globalConfigCompareOptions); diff != "" {
if diff := cmp.Diff(test.want(dir), got, globalConfigCompareOptions); diff != "" {
t.Errorf("-want +got:\n%s", diff)
}
})
Expand All @@ -129,6 +155,7 @@ func FuzzConfigMarshal(f *testing.F) {
f.Add([]byte(`{"trustedPublicKeys": [{"format": "ed25519", "publicKey": "+NMDNfvjCmdT9mLr9zadYQXwF/mPLsToMw36yX7w6HCVCSK9J2WsMGPCAT9U2Y959NFgAfdiSWGRvWbXYlGUcA=="}]}` + "\n"))
f.Add([]byte(`{"trustedPublicKeys": [{"format": "foo", "publicKey": "YmFy"}]}`))
f.Add([]byte(`{"netrcFile": "/etc/netrc"}` + "\n"))
f.Add([]byte(`{"server": {"signingKeyFiles": ["secret-key.json"]}}` + "\n"))

f.Fuzz(func(t *testing.T, in []byte) {
init := defaultGlobalConfig()
Expand Down
70 changes: 64 additions & 6 deletions cmd/zb/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"time"

"github.com/coreos/go-systemd/v22/activation"
jsonv2 "github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
"golang.org/x/sync/errgroup"
"zb.256lights.llc/pkg/bytebuffer"
"zb.256lights.llc/pkg/internal/backend"
Expand All @@ -40,11 +42,6 @@ import (

const contentAddressTempFilePattern = "zb-ca-*"

type serverConfig struct {
Download *storeConfig `json:"download"`
Upload *storeConfig `json:"upload"`
}

type serveCommand struct {
storeDatabaseFlags `kong:"embed"`

Expand Down Expand Up @@ -79,7 +76,10 @@ func (c *serveCommand) Run(ctx context.Context, g *globalConfig, drain drainSign
}
return fmt.Errorf("sandboxing requested but unable to use (are you running with admin privileges?)")
}
keyring, err := readKeyringFromFiles(c.KeyFiles)
allKeyFiles := make([]string, 0, len(g.Server.KeyFiles)+len(c.KeyFiles))
allKeyFiles = append(allKeyFiles, g.Server.KeyFiles...)
allKeyFiles = append(allKeyFiles, c.KeyFiles...)
keyring, err := readKeyringFromFiles(allKeyFiles)
if err != nil {
return err
}
Expand Down Expand Up @@ -386,6 +386,64 @@ func buildUsersForGroup(ctx context.Context, name string) (gid int, buildUsers [
return gid, buildUsers, nil
}

type serverConfig struct {
Download *storeConfig `json:"download"`
Upload *storeConfig `json:"upload"`
KeyFiles []string `json:"signingKeyFiles"`
}

// UnmarshalJSONFrom unmarshals the server configuration object from the JSON decoder,
// merging any fields in the JSON object with existing values.
func (sc *serverConfig) UnmarshalJSONFrom(in *jsontext.Decoder) error {
tok, err := in.ReadToken()
if err != nil {
return err
}
if got := tok.Kind(); got != '{' {
return fmt.Errorf("config must be an object not a %v", got)
}

for {
keyToken, err := in.ReadToken()
if err != nil {
return err
}
switch kind := keyToken.Kind(); kind {
case '}':
return nil
case '"':
// Keep going.
default:
return fmt.Errorf("unexpected non-string key (%v) in object", kind)
}

switch k := keyToken.String(); k {
case "download":
if err := jsonv2.UnmarshalDecode(in, &sc.Download); err != nil {
return fmt.Errorf("unmarshal config.server.download: %w", err)
}
case "upload":
if err := jsonv2.UnmarshalDecode(in, &sc.Upload); err != nil {
return fmt.Errorf("unmarshal config.server.upload: %w", err)
}
case "signingKeyFiles":
newKeyFiles := sc.KeyFiles[len(sc.KeyFiles):]

if err := jsonv2.UnmarshalDecode(in, &newKeyFiles); err != nil {
return fmt.Errorf("unmarshal config.server.signingKeyFiles: %w", err)
}
sc.KeyFiles = append(sc.KeyFiles, newKeyFiles...)
default:
if reject, _ := jsonv2.GetOption(in.Options(), jsonv2.RejectUnknownMembers); reject {
return fmt.Errorf("unmarshal config.server: unknown field %q", k)
}
if err := in.SkipValue(); err != nil {
return fmt.Errorf("unmarshal config.server: %w", err)
}
}
}
}

type sandboxPathsFlags struct {
SandboxPaths map[string]string `kong:"name=sandbox-path,type=pathmap,placeholder=path,help=Paths to allow in sandbox (can be passed multiple times)"`
ImplicitSystemDeps sets.Set[string] `kong:"name=implicit-system-dep,placeholder=path,help=Paths to always mount in sandbox (can be passed multiple times)"`
Expand Down
Loading