From 60ce5b454f4cb1c08b19c8f7b8a7fc8e23879af3 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Fri, 21 Aug 2026 19:55:18 -0700 Subject: [PATCH] fix(packages): match APK index key name --- internal/adapter/repogen/generator.go | 10 +++--- internal/adapter/repogen/generator_test.go | 37 ++++++++++++++++------ internal/stage/pkgrepo/build.go | 9 +++--- internal/stage/pkgrepo/build_test.go | 1 + internal/stage/pkgrepo/values.go | 2 ++ 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/internal/adapter/repogen/generator.go b/internal/adapter/repogen/generator.go index fcc0e4e..96dbcd1 100644 --- a/internal/adapter/repogen/generator.go +++ b/internal/adapter/repogen/generator.go @@ -183,9 +183,10 @@ func validateGenerateRequest(ctx context.Context, generator *Generator, request if err := validateOwnerOnlyFile(generator.apkSigningKey); err != nil { return err } - keyName := filepath.Base(generator.apkSigningKey) - if keyName == "." || keyName == string(filepath.Separator) || strings.ContainsAny(keyName, ":,\n\r") { - return fmt.Errorf("APK signing key basename %q is unsafe", keyName) + keyName := request.APKSigningKeyName + if keyName == "" || keyName == "." || filepath.Base(keyName) != keyName || + strings.ContainsAny(keyName, "/\\:,\n\r") { + return fmt.Errorf("APK signing key name %q is unsafe", keyName) } return nil @@ -246,8 +247,7 @@ chown -R "$owner" /repo/rpm // generateAPK runs apk index and abuild-sign with a fixed source epoch. func (g *Generator) generateAPK(ctx context.Context, request pkgrepo.GenerateRequest) error { epoch := strconv.FormatInt(request.ReleaseTime.Unix(), 10) - keyName := filepath.Base(g.apkSigningKey) - containerKey := path.Join(containerKeys, keyName) + containerKey := path.Join(containerKeys, request.APKSigningKeyName) script := `set -eu owner="$(stat -c '%u:%g' /repo)" export SOURCE_DATE_EPOCH=` + epoch + ` diff --git a/internal/adapter/repogen/generator_test.go b/internal/adapter/repogen/generator_test.go index 0ba7713..428955d 100644 --- a/internal/adapter/repogen/generator_test.go +++ b/internal/adapter/repogen/generator_test.go @@ -117,6 +117,20 @@ func TestGenerateRejectsExposedAPKPrivateKey(t *testing.T) { assert.Contains(t, err.Error(), "allow group or other access") } +// TestGenerateRejectsUnsafeAPKSigningKeyName keeps the key mount inside its fixed directory. +func TestGenerateRejectsUnsafeAPKSigningKeyName(t *testing.T) { + skipWindows(t) + t.Parallel() + + fixture := newGeneratorFixture(t) + request := fixture.request() + request.APKSigningKeyName = "../outside.rsa" + + err := fixture.generator.Generate(context.Background(), request) + require.Error(t, err) + assert.Contains(t, err.Error(), "APK signing key name") +} + // generatorFixture owns one fake three-format generation run. type generatorFixture struct { // generator is the configured adapter under test. @@ -139,27 +153,32 @@ func newGeneratorFixture(t *testing.T) *generatorFixture { writeAPTIndexes(t, root) writeFile(t, filepath.Join(root, "apt", "dists", "stable", "Release"), []byte(buildRelease(t, root, true))) fake := filepath.Join(directory, "docker") - key := filepath.Join(directory, "apk-index-001.rsa") + key := filepath.Join(directory, "materialized-private-key.pem") record := filepath.Join(directory, "args") writeFile(t, fake, []byte(fakeGeneratorScript)) require.NoError(t, os.Chmod(fake, 0o755)) writeFile(t, key, []byte("private key sentinel")) environ := append(os.Environ(), "REPOGEN_RECORD="+record) return &generatorFixture{ - generator: New(Options{DockerPath: fake, APKSigningKey: key, Environ: environ}), - root: root, - key: key, - record: record, + generator: New(Options{ + DockerPath: fake, + APKSigningKey: key, + Environ: environ, + }), + root: root, + key: key, + record: record, } } // request returns the deterministic generation request used by the accepted spike. func (f *generatorFixture) request() pkgrepo.GenerateRequest { return pkgrepo.GenerateRequest{ - Root: f.root, - Channel: pkgrepo.ChannelStable, - ReleaseTime: time.Date(2026, time.August, 21, 12, 0, 0, 0, time.UTC), - ValidUntil: time.Date(2027, time.August, 21, 12, 0, 0, 0, time.UTC), + Root: f.root, + Channel: pkgrepo.ChannelStable, + APKSigningKeyName: "apk-index-001.rsa", + ReleaseTime: time.Date(2026, time.August, 21, 12, 0, 0, 0, time.UTC), + ValidUntil: time.Date(2027, time.August, 21, 12, 0, 0, 0, time.UTC), } } diff --git a/internal/stage/pkgrepo/build.go b/internal/stage/pkgrepo/build.go index 4dd847f..9f20a3b 100644 --- a/internal/stage/pkgrepo/build.go +++ b/internal/stage/pkgrepo/build.go @@ -118,10 +118,11 @@ func Build( } request := GenerateRequest{ - Root: input.Output.Name(), - Channel: input.Config.Channel, - ReleaseTime: input.ReleaseTime, - ValidUntil: input.ValidUntil, + Root: input.Output.Name(), + Channel: input.Config.Channel, + APKSigningKeyName: strings.TrimSuffix(path.Base(input.Config.APKKey.Published), ".pub"), + ReleaseTime: input.ReleaseTime, + ValidUntil: input.ValidUntil, } if generateErr := generator.Generate(ctx, request); generateErr != nil { return BuildResult{}, fmt.Errorf("generate repository metadata: %w", generateErr) diff --git a/internal/stage/pkgrepo/build_test.go b/internal/stage/pkgrepo/build_test.go index 5e80f77..932c4d9 100644 --- a/internal/stage/pkgrepo/build_test.go +++ b/internal/stage/pkgrepo/build_test.go @@ -52,6 +52,7 @@ func TestBuildProducesOrderedSignedRepository(t *testing.T) { generator.EXPECT().Generate(mock.Anything, mock.MatchedBy(func(request pkgrepo.GenerateRequest) bool { return request.Root == fixture.outputDir && request.Channel == pkgrepo.ChannelStable && + request.APKSigningKeyName == "apk-index-001.rsa" && request.ReleaseTime.Equal(fixture.releaseTime) && request.ValidUntil.Equal(fixture.validUntil) })).Run(func(_ context.Context, request pkgrepo.GenerateRequest) { diff --git a/internal/stage/pkgrepo/values.go b/internal/stage/pkgrepo/values.go index 9e366a0..b2491a0 100644 --- a/internal/stage/pkgrepo/values.go +++ b/internal/stage/pkgrepo/values.go @@ -172,6 +172,8 @@ type GenerateRequest struct { Root string // Channel is the configured repository channel. Channel Channel + // APKSigningKeyName is the private-key basename matching the published public key. + APKSigningKeyName string // ReleaseTime is the deterministic metadata creation time. ReleaseTime time.Time // ValidUntil is the APT metadata expiry time and must follow ReleaseTime.