Skip to content
Open
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
28 changes: 8 additions & 20 deletions internal/cli/install-keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ package cli

import (
"fmt"
"os"
"path/filepath"

"github.com/chainguard-dev/clog"
"github.com/spf13/cobra"

"chainguard.dev/apko/pkg/apk/apk"
"chainguard.dev/apko/pkg/apk/apk/keyring"
)

func installKeys() *cobra.Command {
Expand All @@ -19,7 +17,6 @@ func installKeys() *cobra.Command {
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
log := clog.FromContext(ctx)

a, err := apk.New(ctx)
if err != nil {
Expand All @@ -29,24 +26,15 @@ func installKeys() *cobra.Command {
if err != nil {
return err
}
for _, repo := range repos {
keys, err := a.DiscoverKeys(ctx, repo)
if err != nil {
return err
}

if err := os.MkdirAll("/etc/apk/keys", 0755); err != nil {
return err
}
for _, key := range keys {
fn := filepath.Join("/etc/apk/keys", key.ID)
if err := os.WriteFile(fn, key.Bytes, 0o644); err != nil { //nolint: gosec
return fmt.Errorf("failed to write key %s: %w", key.ID, err)
}
log.With("repo", repo).Infof("wrote %s", fn)
}
keyRing, err := keyring.NewKeyRing(
keyring.AddRepositories(repos...),
)
if err != nil {
return fmt.Errorf("creating keyring: %w", err)
}
return nil

return a.DownloadAndStoreKeys(ctx, keyRing)
},
}
}
100 changes: 23 additions & 77 deletions internal/cli/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ import (
"slices"
"sort"
"strings"
"time"

"github.com/spf13/cobra"

"github.com/chainguard-dev/clog"

"chainguard.dev/apko/pkg/apk/apk"
"chainguard.dev/apko/pkg/apk/apk/keyring"
"chainguard.dev/apko/pkg/apk/auth"
apkfs "chainguard.dev/apko/pkg/apk/fs"
"chainguard.dev/apko/pkg/build"
Expand Down Expand Up @@ -281,87 +281,33 @@ func stripURLScheme(url string) string {
func discoverKeysForLock(ctx context.Context, ic *types.ImageConfiguration, archs []types.Architecture) []pkglock.LockKeyring {
log := clog.FromContext(ctx)

// Collect all unique repositories
repoSet := make(map[string]struct{})
for _, repo := range ic.Contents.BuildRepositories {
repoSet[repo] = struct{}{}
}
for _, repo := range ic.Contents.RuntimeOnlyRepositories {
repoSet[repo] = struct{}{}
}
for _, repo := range ic.Contents.Repositories {
repoSet[repo] = struct{}{}
keys, err := keyring.NewKeyRing(
keyring.AddRepositories(ic.Contents.BuildRepositories...),
keyring.AddRepositories(ic.Contents.RuntimeOnlyRepositories...),
keyring.AddRepositories(ic.Contents.Repositories...),
)
if err != nil {
log.Errorf("adding repositories for key discovery: %v", err)
return nil
}

// Map to track discovered keys by URL to avoid duplicates
discoveredKeyMap := make(map[string]pkglock.LockKeyring)

// Fetch Alpine releases once (cached by HTTP client)
client := &http.Client{}
var alpineReleases *apk.Releases

// Discover keys for each repository and architecture
for repo := range repoSet {
// Try Alpine-style key discovery
if ver, ok := apk.ParseAlpineVersion(repo); ok {
// Fetch releases.json if not already fetched
if alpineReleases == nil {
releases, err := apk.FetchAlpineReleases(ctx, client)
if err != nil {
log.Warnf("Failed to fetch Alpine releases: %v", err)
continue
}
alpineReleases = releases
}

branch := alpineReleases.GetReleaseBranch(ver)
if branch == nil {
log.Debugf("Alpine version %s not found in releases", ver)
continue
}

// Get keys for each architecture
for _, arch := range archs {
log.Debugf("Discovering Alpine keys for %s (version %s, arch %s)", repo, ver, arch.ToAPK())
urls := branch.KeysFor(arch.ToAPK(), time.Now())
if len(urls) == 0 {
log.Debugf("No keys found for arch %s and version %s", arch.ToAPK(), ver)
continue
}

// Add discovered key URLs to the map
for _, u := range urls {
discoveredKeyMap[u] = pkglock.LockKeyring{
Name: stripURLScheme(u),
URL: u,
}
}
}
}
archStrs := make([]string, 0, len(archs))
for _, arch := range archs {
archStrs = append(archStrs, arch.ToAPK())
}

// Try Chainguard-style key discovery
log.Debugf("Attempting Chainguard-style key discovery for %s", repo)
keys, err := apk.DiscoverKeys(ctx, client, auth.DefaultAuthenticators, repo)
if err != nil {
log.Debugf("Chainguard-style key discovery failed for %s: %v", repo, err)
} else if len(keys) > 0 {
log.Debugf("Discovered %d Chainguard-style keys for %s", len(keys), repo)
// For each JWKS key, emit a URL: repository + "/" + KeyID
repoBase := strings.TrimSuffix(repo, "/")
for _, key := range keys {
keyURL := repoBase + "/" + key.ID
discoveredKeyMap[keyURL] = pkglock.LockKeyring{
Name: stripURLScheme(keyURL),
URL: keyURL,
}
}
}
fetchedKeys, err := keys.FetchKeys(ctx, keyring.NewFetcher(http.DefaultClient, auth.DefaultAuthenticators), archStrs)
if err != nil {
log.Errorf("downloading keys from repositories: %v", err)
return nil
}

// Convert map to slice
discoveredKeys := make([]pkglock.LockKeyring, 0, len(discoveredKeyMap))
for _, key := range discoveredKeyMap {
discoveredKeys = append(discoveredKeys, key)
discoveredKeys := make([]pkglock.LockKeyring, 0, len(fetchedKeys))
for _, key := range fetchedKeys {
discoveredKeys = append(discoveredKeys, pkglock.LockKeyring{
Name: stripURLScheme(key.URL),
URL: key.URL,
})
}

log.Infof("Discovered %d auto-discovered keys", len(discoveredKeys))
Expand Down
7 changes: 2 additions & 5 deletions pkg/apk/apk/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@ type Cache struct {
etagCache *sync.Map
headFlight *singleflight.Group
getFlight *singleflight.Group

discoverKeys *flightCache[string, []Key]
}

// NewCache returns a new Cache, which allows us to persist the results of HEAD requests
Expand All @@ -103,9 +101,8 @@ type Cache struct {
// requests for the same resource when passing etag=false.
func NewCache(etag bool) *Cache {
c := &Cache{
headFlight: &singleflight.Group{},
getFlight: &singleflight.Group{},
discoverKeys: newFlightCache[string, []Key](),
headFlight: &singleflight.Group{},
getFlight: &singleflight.Group{},
}

if etag {
Expand Down
3 changes: 0 additions & 3 deletions pkg/apk/apk/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,5 @@ const (
// which PAX record we use in the tar header
paxRecordsChecksumKey = "APK-TOOLS.checksum.SHA1"

// for fetching the alpine keys
alpineReleasesURL = "https://alpinelinux.org/releases.json"

xattrTarPAXRecordsPrefix = "SCHILY.xattr."
)
Loading