Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package apko for openSUSE:Factory checked in at 2025-12-12 21:42:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/apko (Old) and /work/SRC/openSUSE:Factory/.apko.new.1939 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "apko" Fri Dec 12 21:42:40 2025 rev:80 rq:1322609 version:0.30.30 Changes: -------- --- /work/SRC/openSUSE:Factory/apko/apko.changes 2025-12-09 12:53:13.603190875 +0100 +++ /work/SRC/openSUSE:Factory/.apko.new.1939/apko.changes 2025-12-12 21:44:01.396035169 +0100 @@ -1,0 +2,25 @@ +Fri Dec 12 12:16:14 UTC 2025 - Johannes Kastl <[email protected]> + +- Update to version 0.30.30: + * build(deps): bump github.com/chainguard-dev/clog from 1.7.0 to + 1.8.0 (#1981) + * cache keys by name (#1968) + * build(deps): bump k8s.io/apimachinery from 0.34.2 to 0.34.3 + (#1979) + * build(deps): bump go.opentelemetry.io/otel/trace from 1.38.0 to + 1.39.0 (#1974) + * build(deps): bump golang.org/x/oauth2 from 0.33.0 to 0.34.0 + (#1973) + * build(deps): bump golang.org/x/sys from 0.38.0 to 0.39.0 + (#1972) + * build(deps): bump golang.org/x/sync from 0.18.0 to 0.19.0 + (#1971) + * build(deps): bump github/codeql-action from 4.31.6 to 4.31.7 + (#1970) + * Add the ability to specify additional certs to be added to the + image (#1977) + * build(deps): bump step-security/harden-runner from 2.13.3 to + 2.14.0 (#1978) + * cli/dot: Ignore Duplicate Node errors (#1976) + +------------------------------------------------------------------- Old: ---- apko-0.30.29.obscpio New: ---- apko-0.30.30.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ apko.spec ++++++ --- /var/tmp/diff_new_pack.OjmvDH/_old 2025-12-12 21:44:02.128066054 +0100 +++ /var/tmp/diff_new_pack.OjmvDH/_new 2025-12-12 21:44:02.132066223 +0100 @@ -17,7 +17,7 @@ Name: apko -Version: 0.30.29 +Version: 0.30.30 Release: 0 Summary: Build OCI images from APK packages directly without Dockerfile License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.OjmvDH/_old 2025-12-12 21:44:02.184068417 +0100 +++ /var/tmp/diff_new_pack.OjmvDH/_new 2025-12-12 21:44:02.192068754 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/chainguard-dev/apko</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v0.30.29</param> + <param name="revision">v0.30.30</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.OjmvDH/_old 2025-12-12 21:44:02.228070273 +0100 +++ /var/tmp/diff_new_pack.OjmvDH/_new 2025-12-12 21:44:02.228070273 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/chainguard-dev/apko</param> - <param name="changesrevision">0b3827aa83414219539e3cfb82cd07f7972a1950</param></service></servicedata> + <param name="changesrevision">d2cb85c1f37e24120b415d4e4372a1b8f811f8e4</param></service></servicedata> (No newline at EOF) ++++++ apko-0.30.29.obscpio -> apko-0.30.30.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-0.30.29/examples/alpine-base.yaml new/apko-0.30.30/examples/alpine-base.yaml --- old/apko-0.30.29/examples/alpine-base.yaml 2025-12-04 15:43:00.000000000 +0100 +++ new/apko-0.30.30/examples/alpine-base.yaml 2025-12-12 12:19:29.000000000 +0100 @@ -12,3 +12,4 @@ archs: - amd64 + - arm64 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-0.30.29/examples/certificates.yaml new/apko-0.30.30/examples/certificates.yaml --- old/apko-0.30.29/examples/certificates.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/apko-0.30.30/examples/certificates.yaml 2025-12-12 12:19:29.000000000 +0100 @@ -0,0 +1,72 @@ +contents: + keyring: + - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub + repositories: + - https://packages.wolfi.dev/os + packages: + - ca-certificates-bundle + - busybox + - apk-tools + +entrypoint: + command: /bin/sh -c + +archs: + - amd64 + +certificates: + additional: + # From https://letsencrypt.org/certs/staging/letsencrypt-stg-root-x1.pem + - name: pretend-pear-x1 + content: | + -----BEGIN CERTIFICATE----- + MIIFmDCCA4CgAwIBAgIQU9C87nMpOIFKYpfvOHFHFDANBgkqhkiG9w0BAQsFADBm + MQswCQYDVQQGEwJVUzEzMDEGA1UEChMqKFNUQUdJTkcpIEludGVybmV0IFNlY3Vy + aXR5IFJlc2VhcmNoIEdyb3VwMSIwIAYDVQQDExkoU1RBR0lORykgUHJldGVuZCBQ + ZWFyIFgxMB4XDTE1MDYwNDExMDQzOFoXDTM1MDYwNDExMDQzOFowZjELMAkGA1UE + BhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1cml0eSBSZXNl + YXJjaCBHcm91cDEiMCAGA1UEAxMZKFNUQUdJTkcpIFByZXRlbmQgUGVhciBYMTCC + AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALbagEdDTa1QgGBWSYkyMhsc + ZXENOBaVRTMX1hceJENgsL0Ma49D3MilI4KS38mtkmdF6cPWnL++fgehT0FbRHZg + jOEr8UAN4jH6omjrbTD++VZneTsMVaGamQmDdFl5g1gYaigkkmx8OiCO68a4QXg4 + wSyn6iDipKP8utsE+x1E28SA75HOYqpdrk4HGxuULvlr03wZGTIf/oRt2/c+dYmD + oaJhge+GOrLAEQByO7+8+vzOwpNAPEx6LW+crEEZ7eBXih6VP19sTGy3yfqK5tPt + TdXXCOQMKAp+gCj/VByhmIr+0iNDC540gtvV303WpcbwnkkLYC0Ft2cYUyHtkstO + fRcRO+K2cZozoSwVPyB8/J9RpcRK3jgnX9lujfwA/pAbP0J2UPQFxmWFRQnFjaq6 + rkqbNEBgLy+kFL1NEsRbvFbKrRi5bYy2lNms2NJPZvdNQbT/2dBZKmJqxHkxCuOQ + FjhJQNeO+Njm1Z1iATS/3rts2yZlqXKsxQUzN6vNbD8KnXRMEeOXUYvbV4lqfCf8 + mS14WEbSiMy87GB5S9ucSV1XUrlTG5UGcMSZOBcEUpisRPEmQWUOTWIoDQ5FOia/ + GI+Ki523r2ruEmbmG37EBSBXdxIdndqrjy+QVAmCebyDx9eVEGOIpn26bW5LKeru + mJxa/CFBaKi4bRvmdJRLAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB + Af8EBTADAQH/MB0GA1UdDgQWBBS182Xy/rAKkh/7PH3zRKCsYyXDFDANBgkqhkiG + 9w0BAQsFAAOCAgEAncDZNytDbrrVe68UT6py1lfF2h6Tm2p8ro42i87WWyP2LK8Y + nLHC0hvNfWeWmjZQYBQfGC5c7aQRezak+tHLdmrNKHkn5kn+9E9LCjCaEsyIIn2j + qdHlAkepu/C3KnNtVx5tW07e5bvIjJScwkCDbP3akWQixPpRFAsnP+ULx7k0aO1x + qAeaAhQ2rgo1F58hcflgqKTXnpPM02intVfiVVkX5GXpJjK5EoQtLceyGOrkxlM/ + sTPq4UrnypmsqSagWV3HcUlYtDinc+nukFk6eR4XkzXBbwKajl0YjztfrCIHOn5Q + CJL6TERVDbM/aAPly8kJ1sWGLuvvWYzMYgLzDul//rUF10gEMWaXVZV51KpS9DY/ + 5CunuvCXmEQJHo7kGcViT7sETn6Jz9KOhvYcXkJ7po6d93A/jy4GKPIPnsKKNEmR + xUuXY4xRdh45tMJnLTUDdC9FIU0flTeO9/vNpVA8OPU1i14vCz+MU8KX1bV3GXm/ + fxlB7VBBjX9v5oUep0o/j68R/iDlCOM4VVfRa8gX6T2FU7fNdatvGro7uQzIvWof + gN9WUwCbEMBy/YhBSrXycKA8crgGg3x1mIsopn88JKwmMBa68oS7EHM9w7C4y71M + 7DiA+/9Qdp9RBWJpTS9i/mDnJg1xvo8Xz49mrrgfmcAXTCJqXi24NatI3Oc= + -----END CERTIFICATE----- + # From https://letsencrypt.org/certs/staging/letsencrypt-stg-root-x2.pem + - name: bogus-broccoli-x2 + content: | + -----BEGIN CERTIFICATE----- + MIICTjCCAdSgAwIBAgIRAIPgc3k5LlLVLtUUvs4K/QcwCgYIKoZIzj0EAwMwaDEL + MAkGA1UEBhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1cml0 + eSBSZXNlYXJjaCBHcm91cDEkMCIGA1UEAxMbKFNUQUdJTkcpIEJvZ3VzIEJyb2Nj + b2xpIFgyMB4XDTIwMDkwNDAwMDAwMFoXDTQwMDkxNzE2MDAwMFowaDELMAkGA1UE + BhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1cml0eSBSZXNl + YXJjaCBHcm91cDEkMCIGA1UEAxMbKFNUQUdJTkcpIEJvZ3VzIEJyb2Njb2xpIFgy + MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOvS+w1kCzAxYOJbA06Aw0HFP2tLBLKPo + FQqR9AMskl1nC2975eQqycR+ACvYelA8rfwFXObMHYXJ23XLB+dAjPJVOJ2OcsjT + VqO4dcDWu+rQ2VILdnJRYypnV1MMThVxo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD + VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU3tGjWWQOwZo2o0busBB2766XlWYwCgYI + KoZIzj0EAwMDaAAwZQIwRcp4ZKBsq9XkUuN8wfX+GEbY1N5nmCRc8e80kUkuAefo + uc2j3cICeXo1cOybQ1iWAjEA3Ooawl8eQyR4wrjCofUE8h44p0j7Yl/kBlJZT8+9 + vbtH7QiVzeKCOTQPINyRql6P + -----END CERTIFICATE----- + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-0.30.29/go.mod new/apko-0.30.30/go.mod --- old/apko-0.30.29/go.mod 2025-12-04 15:43:00.000000000 +0100 +++ new/apko-0.30.30/go.mod 2025-12-12 12:19:29.000000000 +0100 @@ -4,7 +4,7 @@ require ( chainguard.dev/sdk v0.1.44 - github.com/chainguard-dev/clog v1.7.0 + github.com/chainguard-dev/clog v1.8.0 github.com/charmbracelet/log v0.4.2 github.com/go-git/go-git/v5 v5.16.4 github.com/google/go-cmp v0.7.0 @@ -22,17 +22,17 @@ github.com/tmc/dot v0.2.0 github.com/u-root/u-root v0.15.0 go.lsp.dev/uri v0.3.0 - go.opentelemetry.io/otel v1.38.0 - go.opentelemetry.io/otel/trace v1.38.0 + go.opentelemetry.io/otel v1.39.0 + go.opentelemetry.io/otel/trace v1.39.0 go.step.sm/crypto v0.75.0 - golang.org/x/oauth2 v0.33.0 - golang.org/x/sync v0.18.0 - golang.org/x/sys v0.38.0 + golang.org/x/oauth2 v0.34.0 + golang.org/x/sync v0.19.0 + golang.org/x/sys v0.39.0 golang.org/x/time v0.14.0 google.golang.org/api v0.257.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/apimachinery v0.34.2 + k8s.io/apimachinery v0.34.3 sigs.k8s.io/release-utils v0.12.2 ) @@ -122,7 +122,7 @@ go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-0.30.29/go.sum new/apko-0.30.30/go.sum --- old/apko-0.30.29/go.sum 2025-12-04 15:43:00.000000000 +0100 +++ new/apko-0.30.30/go.sum 2025-12-12 12:19:29.000000000 +0100 @@ -35,8 +35,8 @@ github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chainguard-dev/clog v1.7.0 h1:guPznsK8vLHvzz1QJe2yU6MFeYaiSOFOQBYw4OXu+g8= -github.com/chainguard-dev/clog v1.7.0/go.mod h1:4+WFhRMsGH79etYXY3plYdp+tCz/KCkU8fAr0HoaPvs= +github.com/chainguard-dev/clog v1.8.0 h1:frlTMEdg3XQR+ioQ6O9i92uigY8GTUcWKpuCFkhcCHA= +github.com/chainguard-dev/clog v1.8.0/go.mod h1:5MQOZi+Iu7fV7GcJG8ag8rCB5elEOpqRMKEASgnGVdo= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= @@ -267,20 +267,20 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.step.sm/crypto v0.75.0 h1:UAHYD6q6ggYyzLlIKHv1MCUVjZIesXRZpGTlRC/HSHw= @@ -310,13 +310,13 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -331,8 +331,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -386,7 +386,7 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= sigs.k8s.io/release-utils v0.12.2 h1:H06v3FuLElAkf7Ikkd9ll8hnhdtQ+OgktJAni3iIAl8= sigs.k8s.io/release-utils v0.12.2/go.mod h1:Ab9Lb/FpGUw4lUXj1QYbUcF2TRzll+GS7Md54W1G7sA= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-0.30.29/internal/cli/dot.go new/apko-0.30.30/internal/cli/dot.go --- old/apko-0.30.29/internal/cli/dot.go 2025-12-04 15:43:00.000000000 +0100 +++ new/apko-0.30.30/internal/cli/dot.go 2025-12-12 12:19:29.000000000 +0100 @@ -213,7 +213,7 @@ panic(err) } } - if _, err := out.AddNode(n); err != nil { + if _, err := out.AddNode(n); err != nil && !errors.Is(err, dot.ErrDuplicateNode) { panic(err) } @@ -231,7 +231,7 @@ } } } - if _, err := out.AddNode(d); err != nil { + if _, err := out.AddNode(d); err != nil && !errors.Is(err, dot.ErrDuplicateNode) { panic(err) } if _, ok := edges[dep]; !ok || !span { @@ -269,7 +269,7 @@ if err := n.Set("label", pkgver(pkg)); err != nil { panic(err) } - if _, err := out.AddNode(n); err != nil { + if _, err := out.AddNode(n); err != nil && !errors.Is(err, dot.ErrDuplicateNode) { panic(err) } @@ -281,7 +281,7 @@ if err := p.Set("shape", "rect"); err != nil { panic(err) } - if _, err := out.AddNode(p); err != nil { + if _, err := out.AddNode(p); err != nil && !errors.Is(err, dot.ErrDuplicateNode) { panic(err) } @@ -296,7 +296,7 @@ if err := p.Set("shape", "rect"); err != nil { panic(err) } - if _, err := out.AddNode(p); err != nil { + if _, err := out.AddNode(p); err != nil && !errors.Is(err, dot.ErrDuplicateNode) { panic(err) } @@ -310,7 +310,7 @@ } } p := dot.NewNode(prov) - if _, err := out.AddNode(p); err != nil { + if _, err := out.AddNode(p); err != nil && !errors.Is(err, dot.ErrDuplicateNode) { panic(err) } if _, ok := edges[pkg.Name]; !ok || !span { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-0.30.29/pkg/apk/apk/cache.go new/apko-0.30.30/pkg/apk/apk/cache.go --- old/apko-0.30.29/pkg/apk/apk/cache.go 2025-12-04 15:43:00.000000000 +0100 +++ new/apko-0.30.30/pkg/apk/apk/cache.go 2025-12-12 12:19:29.000000000 +0100 @@ -368,6 +368,10 @@ return filepath.Join(filepath.Dir(cacheFile), "APKINDEX") } + if strings.HasSuffix(cacheFile, ".rsa.pub") { + return filepath.Join(filepath.Dir(cacheFile), filepath.Base(cacheFile)) + } + return filepath.Dir(cacheFile) } @@ -381,6 +385,11 @@ ext = ".tar.gz" } + // Keep all the rsa.pub files under subdirectory named by full filename. + if strings.HasSuffix(cacheFile, ".rsa.pub") { + cacheDir = filepath.Join(cacheDir, filepath.Base(cacheFile)) + } + absPath, err := filepath.Abs(filepath.Join(cacheDir, etag+ext)) if err != nil { return "", err diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-0.30.29/pkg/build/build_implementation.go new/apko-0.30.30/pkg/build/build_implementation.go --- old/apko-0.30.29/pkg/build/build_implementation.go 2025-12-04 15:43:00.000000000 +0100 +++ new/apko-0.30.30/pkg/build/build_implementation.go 2025-12-12 12:19:29.000000000 +0100 @@ -194,6 +194,10 @@ return nil, fmt.Errorf("failed to mutate paths: %w", err) } + if err := bc.installCertificates(ctx); err != nil { + return nil, fmt.Errorf("failed to install certificates: %w", err) + } + if err := bc.s6.WriteSupervisionTree(ctx, bc.ic.Entrypoint.Services); err != nil { return nil, fmt.Errorf("failed to write supervision tree: %w", err) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-0.30.29/pkg/build/certificates.go new/apko-0.30.30/pkg/build/certificates.go --- old/apko-0.30.29/pkg/build/certificates.go 1970-01-01 01:00:00.000000000 +0100 +++ new/apko-0.30.30/pkg/build/certificates.go 2025-12-12 12:19:29.000000000 +0100 @@ -0,0 +1,176 @@ +// Copyright 2025 Chainguard, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package build + +import ( + "bytes" + "context" + "crypto/sha256" + "crypto/x509" + "encoding/hex" + "encoding/pem" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + + "go.opentelemetry.io/otel" +) + +const ( + // Directory for individual certificate files (used by update-ca-certificates). + caCertsDir = "usr/local/share/ca-certificates" +) + +var ( + // Common paths for CA bundles. + caBundlePaths = []string{ + "etc/ssl/certs/ca-certificates.crt", // Alpine default. + "var/lib/ecs/deps/execute-command/certs/tls-ca-bundle.pem", // AWS ECS-specific. + } +) + +// parsedCertificate represents a parsed certificate with its metadata. +type parsedCertificate struct { + pem []byte + fingerprint string +} + +// installCertificates installs inline certificates into the build context. +func (bc *Context) installCertificates(ctx context.Context) error { + _, span := otel.Tracer("apko").Start(ctx, "installCertificates") + defer span.End() + + if bc.ic.Certificates == nil || len(bc.ic.Certificates.Additional) == 0 { + // No configuration, nothing to do. + return nil + } + + builtTime, err := bc.GetBuildDateEpoch() + if err != nil { + return fmt.Errorf("failed to get build date epoch: %w", err) + } + + // Create the ca-certificates directory if it doesn't exist + if err := bc.fs.MkdirAll(caCertsDir, 0o755); err != nil { + return fmt.Errorf("failed to create ca-certificates directory: %w", err) + } + + // Open handles for all existing CA bundles to append to. + existingBundles := make([]io.WriteSeeker, 0, len(caBundlePaths)) + for _, caBundlePath := range caBundlePaths { + file, err := bc.fs.OpenFile(caBundlePath, os.O_WRONLY|os.O_APPEND, 0o644) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + // If the bundle doesn't exist, nothing to do, we just ignore that. + continue + } + return fmt.Errorf("failed to open CA bundle for appending: %w", err) + } + defer file.Close() + + existingBundles = append(existingBundles, file) + } + + for _, additional := range bc.ic.Certificates.Additional { + cert, err := parseCertificates(additional.Content) + if err != nil { + return fmt.Errorf("failed to parse certificate %s: %w", additional.Name, err) + } + + // Write individual certificate file for update-ca-certificates to pick up. + // Name is validated not to do any path shenanigans on configuration validation. + // The fingerprint is controlled to be a hash and so also doesn't allow shenanigans. + certPath := filepath.Join(caCertsDir, fmt.Sprintf("%s-%s.crt", additional.Name, cert.fingerprint)) + if err := bc.fs.WriteFile(certPath, cert.pem, 0o644); err != nil { + return fmt.Errorf("failed to write certificate file %s: %w", certPath, err) + } + if err := bc.fs.Chtimes(certPath, builtTime, builtTime); err != nil { + return fmt.Errorf("failed to change times on certificate file %s: %w", certPath, err) + } + + // Append to all existing bundles. + for _, bundle := range existingBundles { + if _, err := bundle.Write(cert.pem); err != nil { + return fmt.Errorf("failed to append certificate to bundle: %w", err) + } + // Put newlines in-between certificates to mimic update-ca-certificates behavior. + if _, err := bundle.Write([]byte("\n")); err != nil { + return fmt.Errorf("failed to append newline to bundle: %w", err) + } + } + } + + for _, caBundlePath := range caBundlePaths { + if err := bc.fs.Chtimes(caBundlePath, builtTime, builtTime); err != nil && !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("failed to change times on CA bundle %s: %w", caBundlePath, err) + } + } + + return nil +} + +// parseCertificates parses a string as a PEM-encoded certificate and returns +// a parsedCertificate struct. +func parseCertificates(pemData string) (*parsedCertificate, error) { + if pemData == "" { + return nil, fmt.Errorf("no certificate data provided") + } + + var cert *parsedCertificate + rest := []byte(pemData) + for { + var block *pem.Block + block, rest = pem.Decode(rest) + if block == nil { + break + } else if cert != nil { + // More than one certificate found. + return nil, fmt.Errorf("multiple certificates found; only one is allowed") + } + + if block.Type != "CERTIFICATE" { + return nil, fmt.Errorf("expected CERTIFICATE block, got %s", block.Type) + } + + // Parse the certificate to validate it + _, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse certificate: %w", err) + } + + // Generate fingerprint (SHA256 hash of DER-encoded certificate). + hash := sha256.Sum256(block.Bytes) + fingerprint := hex.EncodeToString(hash[:]) + + // Re-encode to PEM. This drops any additional text from the original block. + var pemBuf bytes.Buffer + if err := pem.Encode(&pemBuf, block); err != nil { + return nil, fmt.Errorf("failed to re-encode certificate to PEM: %w", err) + } + + cert = &parsedCertificate{ + pem: pemBuf.Bytes(), + fingerprint: fingerprint, + } + } + + if cert == nil { + return nil, fmt.Errorf("no certificates found in PEM data") + } + return cert, nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-0.30.29/pkg/build/certificates_test.go new/apko-0.30.30/pkg/build/certificates_test.go --- old/apko-0.30.29/pkg/build/certificates_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/apko-0.30.30/pkg/build/certificates_test.go 2025-12-12 12:19:29.000000000 +0100 @@ -0,0 +1,304 @@ +// Copyright 2025 Chainguard, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package build + +import ( + "context" + "fmt" + "io/fs" + "path/filepath" + "testing" + "time" + + apkfs "chainguard.dev/apko/pkg/apk/fs" + "chainguard.dev/apko/pkg/build/types" + "chainguard.dev/apko/pkg/options" + + "github.com/google/go-cmp/cmp" +) + +const ( + // From https://letsencrypt.org/certs/staging/letsencrypt-stg-root-x1.pem + testCertPEM = `-----BEGIN CERTIFICATE----- +MIIFmDCCA4CgAwIBAgIQU9C87nMpOIFKYpfvOHFHFDANBgkqhkiG9w0BAQsFADBm +MQswCQYDVQQGEwJVUzEzMDEGA1UEChMqKFNUQUdJTkcpIEludGVybmV0IFNlY3Vy +aXR5IFJlc2VhcmNoIEdyb3VwMSIwIAYDVQQDExkoU1RBR0lORykgUHJldGVuZCBQ +ZWFyIFgxMB4XDTE1MDYwNDExMDQzOFoXDTM1MDYwNDExMDQzOFowZjELMAkGA1UE +BhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1cml0eSBSZXNl +YXJjaCBHcm91cDEiMCAGA1UEAxMZKFNUQUdJTkcpIFByZXRlbmQgUGVhciBYMTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALbagEdDTa1QgGBWSYkyMhsc +ZXENOBaVRTMX1hceJENgsL0Ma49D3MilI4KS38mtkmdF6cPWnL++fgehT0FbRHZg +jOEr8UAN4jH6omjrbTD++VZneTsMVaGamQmDdFl5g1gYaigkkmx8OiCO68a4QXg4 +wSyn6iDipKP8utsE+x1E28SA75HOYqpdrk4HGxuULvlr03wZGTIf/oRt2/c+dYmD +oaJhge+GOrLAEQByO7+8+vzOwpNAPEx6LW+crEEZ7eBXih6VP19sTGy3yfqK5tPt +TdXXCOQMKAp+gCj/VByhmIr+0iNDC540gtvV303WpcbwnkkLYC0Ft2cYUyHtkstO +fRcRO+K2cZozoSwVPyB8/J9RpcRK3jgnX9lujfwA/pAbP0J2UPQFxmWFRQnFjaq6 +rkqbNEBgLy+kFL1NEsRbvFbKrRi5bYy2lNms2NJPZvdNQbT/2dBZKmJqxHkxCuOQ +FjhJQNeO+Njm1Z1iATS/3rts2yZlqXKsxQUzN6vNbD8KnXRMEeOXUYvbV4lqfCf8 +mS14WEbSiMy87GB5S9ucSV1XUrlTG5UGcMSZOBcEUpisRPEmQWUOTWIoDQ5FOia/ +GI+Ki523r2ruEmbmG37EBSBXdxIdndqrjy+QVAmCebyDx9eVEGOIpn26bW5LKeru +mJxa/CFBaKi4bRvmdJRLAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBS182Xy/rAKkh/7PH3zRKCsYyXDFDANBgkqhkiG +9w0BAQsFAAOCAgEAncDZNytDbrrVe68UT6py1lfF2h6Tm2p8ro42i87WWyP2LK8Y +nLHC0hvNfWeWmjZQYBQfGC5c7aQRezak+tHLdmrNKHkn5kn+9E9LCjCaEsyIIn2j +qdHlAkepu/C3KnNtVx5tW07e5bvIjJScwkCDbP3akWQixPpRFAsnP+ULx7k0aO1x +qAeaAhQ2rgo1F58hcflgqKTXnpPM02intVfiVVkX5GXpJjK5EoQtLceyGOrkxlM/ +sTPq4UrnypmsqSagWV3HcUlYtDinc+nukFk6eR4XkzXBbwKajl0YjztfrCIHOn5Q +CJL6TERVDbM/aAPly8kJ1sWGLuvvWYzMYgLzDul//rUF10gEMWaXVZV51KpS9DY/ +5CunuvCXmEQJHo7kGcViT7sETn6Jz9KOhvYcXkJ7po6d93A/jy4GKPIPnsKKNEmR +xUuXY4xRdh45tMJnLTUDdC9FIU0flTeO9/vNpVA8OPU1i14vCz+MU8KX1bV3GXm/ +fxlB7VBBjX9v5oUep0o/j68R/iDlCOM4VVfRa8gX6T2FU7fNdatvGro7uQzIvWof +gN9WUwCbEMBy/YhBSrXycKA8crgGg3x1mIsopn88JKwmMBa68oS7EHM9w7C4y71M +7DiA+/9Qdp9RBWJpTS9i/mDnJg1xvo8Xz49mrrgfmcAXTCJqXi24NatI3Oc= +-----END CERTIFICATE----- +` + testCertPEMFingerprint = "e70570a989f8565aabdf7cae27abd1621872d6a3f811e3fef27e3dba02912198" + + // From https://letsencrypt.org/certs/staging/letsencrypt-stg-root-x2.pem + testCertPEM2 = `-----BEGIN CERTIFICATE----- +MIICTjCCAdSgAwIBAgIRAIPgc3k5LlLVLtUUvs4K/QcwCgYIKoZIzj0EAwMwaDEL +MAkGA1UEBhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1cml0 +eSBSZXNlYXJjaCBHcm91cDEkMCIGA1UEAxMbKFNUQUdJTkcpIEJvZ3VzIEJyb2Nj +b2xpIFgyMB4XDTIwMDkwNDAwMDAwMFoXDTQwMDkxNzE2MDAwMFowaDELMAkGA1UE +BhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1cml0eSBSZXNl +YXJjaCBHcm91cDEkMCIGA1UEAxMbKFNUQUdJTkcpIEJvZ3VzIEJyb2Njb2xpIFgy +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOvS+w1kCzAxYOJbA06Aw0HFP2tLBLKPo +FQqR9AMskl1nC2975eQqycR+ACvYelA8rfwFXObMHYXJ23XLB+dAjPJVOJ2OcsjT +VqO4dcDWu+rQ2VILdnJRYypnV1MMThVxo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU3tGjWWQOwZo2o0busBB2766XlWYwCgYI +KoZIzj0EAwMDaAAwZQIwRcp4ZKBsq9XkUuN8wfX+GEbY1N5nmCRc8e80kUkuAefo +uc2j3cICeXo1cOybQ1iWAjEA3Ooawl8eQyR4wrjCofUE8h44p0j7Yl/kBlJZT8+9 +vbtH7QiVzeKCOTQPINyRql6P +-----END CERTIFICATE----- +` + testCertPEM2Fingerprint = "9b2a339fe6a3e85585c4cd75536cb8c1cf7cd603b9a64bec2521858ae48da85d" +) + +func TestParseCertificates(t *testing.T) { + tests := []struct { + name string + pemData string + wantFingerprint string + wantPEM []byte + wantErr bool + }{{ + name: "single valid certificate", + pemData: testCertPEM, + wantFingerprint: testCertPEMFingerprint, + wantPEM: []byte(testCertPEM), + wantErr: false, + }, { + name: "single valid certificate with extra text", + pemData: "extra text\n" + testCertPEM, + wantFingerprint: testCertPEMFingerprint, + wantPEM: []byte(testCertPEM), // The extra text is stripped. + wantErr: false, + }, { + name: "multiple certificates", + pemData: testCertPEM + "\n" + testCertPEM2, + wantErr: true, + }, { + name: "empty string", + pemData: "", + wantErr: true, + }, { + name: "invalid PEM", + pemData: "not a certificate", + wantErr: true, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cert, err := parseCertificates(tt.pemData) + if tt.wantErr { + if err == nil { + t.Fatalf("expected error but got none") + } + return + } + + if diff := cmp.Diff(tt.wantFingerprint, cert.fingerprint); diff != "" { + t.Errorf("fingerprint mismatch (-want +got):\n%s", diff) + } + if diff := cmp.Diff(tt.wantPEM, cert.pem); diff != "" { + t.Errorf("PEM data mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestInstallCertificates(t *testing.T) { + epoch := time.Unix(1337, 0) + t.Setenv("SOURCE_DATE_EPOCH", fmt.Sprintf("%d", epoch.Unix())) + + tests := []struct { + name string + cfg *types.ImageCertificates + existingFiles map[string]string + wantFiles map[string]string + wantErr bool + }{{ + name: "nil certificates config", + cfg: nil, + }, { + name: "valid single certificate without existing bundle", + cfg: &types.ImageCertificates{ + Additional: []types.AdditionalCertificateEntry{ + {Name: "test-cert", Content: testCertPEM}, + }, + }, + existingFiles: map[string]string{}, + wantFiles: map[string]string{ + filepath.Join(caCertsDir, fmt.Sprintf("test-cert-%s.crt", testCertPEMFingerprint)): testCertPEM, + }, + }, { + name: "multiple certificate entries only one existing bundle", + cfg: &types.ImageCertificates{ + Additional: []types.AdditionalCertificateEntry{ + {Name: "test-cert-1", Content: testCertPEM}, + {Name: "test-cert-2", Content: testCertPEM2}, + }, + }, + existingFiles: map[string]string{ + caBundlePaths[0]: "# Existing CA Bundle\n", + }, + wantFiles: map[string]string{ + caBundlePaths[0]: "# Existing CA Bundle\n" + testCertPEM + "\n" + testCertPEM2 + "\n", + filepath.Join(caCertsDir, fmt.Sprintf("test-cert-1-%s.crt", testCertPEMFingerprint)): testCertPEM, + filepath.Join(caCertsDir, fmt.Sprintf("test-cert-2-%s.crt", testCertPEM2Fingerprint)): testCertPEM2, + }, + }, { + name: "multiple certificate entries with multiple existing bundles", + cfg: &types.ImageCertificates{ + Additional: []types.AdditionalCertificateEntry{ + {Name: "test-cert-1", Content: testCertPEM}, + {Name: "test-cert-2", Content: testCertPEM2}, + }, + }, + existingFiles: map[string]string{ + caBundlePaths[0]: "# Existing CA Bundle\n", + caBundlePaths[1]: "# Another CA Bundle\n", + }, + wantFiles: map[string]string{ + caBundlePaths[0]: "# Existing CA Bundle\n" + testCertPEM + "\n" + testCertPEM2 + "\n", + caBundlePaths[1]: "# Another CA Bundle\n" + testCertPEM + "\n" + testCertPEM2 + "\n", + filepath.Join(caCertsDir, fmt.Sprintf("test-cert-1-%s.crt", testCertPEMFingerprint)): testCertPEM, + filepath.Join(caCertsDir, fmt.Sprintf("test-cert-2-%s.crt", testCertPEM2Fingerprint)): testCertPEM2, + }, + }, { + name: "multiple certificate entries with identical names", + cfg: &types.ImageCertificates{ + Additional: []types.AdditionalCertificateEntry{ + {Name: "test-cert", Content: testCertPEM}, + {Name: "test-cert", Content: testCertPEM2}, + }, + }, + existingFiles: map[string]string{ + caBundlePaths[0]: "# Existing CA Bundle\n", + }, + wantFiles: map[string]string{ + caBundlePaths[0]: "# Existing CA Bundle\n" + testCertPEM + "\n" + testCertPEM2 + "\n", + filepath.Join(caCertsDir, fmt.Sprintf("test-cert-%s.crt", testCertPEMFingerprint)): testCertPEM, + filepath.Join(caCertsDir, fmt.Sprintf("test-cert-%s.crt", testCertPEM2Fingerprint)): testCertPEM2, + }, + }, { + name: "certificate with additional metadata", + cfg: &types.ImageCertificates{ + Additional: []types.AdditionalCertificateEntry{ + {Name: "test-cert", Content: "additional text\n" + testCertPEM}, + }, + }, + existingFiles: map[string]string{ + caBundlePaths[0]: "# Existing CA Bundle\n", + }, + wantFiles: map[string]string{ + caBundlePaths[0]: "# Existing CA Bundle\n" + testCertPEM + "\n", + filepath.Join(caCertsDir, fmt.Sprintf("test-cert-%s.crt", testCertPEMFingerprint)): testCertPEM, + }, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fsys := apkfs.NewMemFS() + bc := &Context{ + o: options.Options{ + SourceDateEpoch: epoch, + }, + ic: types.ImageConfiguration{ + Certificates: tt.cfg, + }, + fs: fsys, + } + + for path, content := range tt.existingFiles { + if err := fsys.MkdirAll(filepath.Dir(path), 0o755); err != nil { + t.Fatalf("failed to create directory for existing file %s: %v", path, err) + } + if err := fsys.WriteFile(path, []byte(content), 0o644); err != nil { + t.Fatalf("failed to write existing file %s: %v", path, err) + } + } + + err := bc.installCertificates(context.Background()) + if (err != nil) != tt.wantErr { + t.Fatalf("installCertificates() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + // Expected error, nothing further to check + return + } + if tt.cfg == nil || len(tt.cfg.Additional) == 0 { + // Nothing further to check + return + } + + // Walk the entire filesystem to ensure we're checking contents for all + // expected files. + fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + t.Fatalf("error walking to %s: %v", path, err) + } + if d.IsDir() { + return nil + } + + wantContent, ok := tt.wantFiles[path] + if !ok { + t.Errorf("unexpected file created: %s", path) + return nil + } + + data, err := fsys.ReadFile(path) + if err != nil { + t.Fatalf("failed to read expected file %s: %v", path, err) + } + gotContent := string(data) + if diff := cmp.Diff(wantContent, gotContent); diff != "" { + t.Errorf("file content mismatch for %s (-want +got):\n%s", path, diff) + } + + stat, err := fsys.Stat(path) + if err != nil { + t.Fatalf("failed to stat file %s: %v", path, err) + } + modTime := stat.ModTime() + if !modTime.Equal(epoch) { + t.Errorf("file %s has mod time %v, want %v", path, modTime, epoch) + } + return nil + }) + }) + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-0.30.29/pkg/build/types/image_configuration.go new/apko-0.30.30/pkg/build/types/image_configuration.go --- old/apko-0.30.29/pkg/build/types/image_configuration.go 2025-12-04 15:43:00.000000000 +0100 +++ new/apko-0.30.30/pkg/build/types/image_configuration.go 2025-12-12 12:19:29.000000000 +0100 @@ -21,6 +21,7 @@ "maps" "os" "reflect" + "regexp" "slices" "strings" @@ -33,6 +34,10 @@ "chainguard.dev/apko/pkg/vcs" ) +// Regex for valid certificate names. Since the name of the certificate is used +// as a filename, we restrict it to a safe subset of characters. +var certNameRegex = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`) + // Attempt to probe an upstream VCS URL if known. func (ic *ImageConfiguration) ProbeVCSUrl(ctx context.Context, imageConfigPath string) { log := clog.FromContext(ctx) @@ -121,6 +126,9 @@ if target.Layering == nil { target.Layering = ic.Layering } + if target.Certificates == nil { + target.Certificates = ic.Certificates + } if len(target.Archs) == 0 { target.Archs = ic.Archs } @@ -223,6 +231,17 @@ return fmt.Errorf("configured group %v has no configured group name", g) } } + + if ic.Certificates != nil { + for _, additional := range ic.Certificates.Additional { + if additional.Name == "" { + return fmt.Errorf("configured additional certificate has no name") + } + if !certNameRegex.MatchString(additional.Name) { + return fmt.Errorf("configured additional certificate %q has an invalid name, it must match %s", additional.Name, certNameRegex.String()) + } + } + } return nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-0.30.29/pkg/build/types/image_configuration_test.go new/apko-0.30.30/pkg/build/types/image_configuration_test.go --- old/apko-0.30.29/pkg/build/types/image_configuration_test.go 2025-12-04 15:43:00.000000000 +0100 +++ new/apko-0.30.30/pkg/build/types/image_configuration_test.go 2025-12-12 12:19:29.000000000 +0100 @@ -93,6 +93,12 @@ Volumes: []string{ "volume1", }, + Certificates: &types.ImageCertificates{ + Additional: []types.AdditionalCertificateEntry{{ + Name: "foo", + Content: "bar", + }}, + }, }, target: types.ImageConfiguration{ Contents: types.ImageContents{ @@ -120,6 +126,12 @@ Volumes: []string{ "volume1", }, + Certificates: &types.ImageCertificates{ + Additional: []types.AdditionalCertificateEntry{{ + Name: "foo", + Content: "bar", + }}, + }, }, }, { name: "simple blend of contents", @@ -240,3 +252,51 @@ }) } } + +func TestValidate(t *testing.T) { + tests := []struct { + name string + configuration types.ImageConfiguration + expectError string + }{{ + name: "no cert name", + configuration: types.ImageConfiguration{ + Certificates: &types.ImageCertificates{ + Additional: []types.AdditionalCertificateEntry{{ + Name: "", + Content: "test", + }}, + }, + }, + expectError: "configured additional certificate has no name", + }, { + name: "path walking cert name", + configuration: types.ImageConfiguration{ + Certificates: &types.ImageCertificates{ + Additional: []types.AdditionalCertificateEntry{{ + Name: "trying/../../../to/break", + Content: "test", + }}, + }, + }, + expectError: `configured additional certificate "trying/../../../to/break" has an invalid name, it must match ^[a-zA-Z0-9_-]+$`, + }, { + name: "weird characters cert name", + configuration: types.ImageConfiguration{ + Certificates: &types.ImageCertificates{ + Additional: []types.AdditionalCertificateEntry{{ + Name: "my-cert@123!", + Content: "test", + }}, + }, + }, + expectError: `configured additional certificate "my-cert@123!" has an invalid name, it must match ^[a-zA-Z0-9_-]+$`, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.configuration.Validate() + require.EqualError(t, err, tt.expectError) + }) + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-0.30.29/pkg/build/types/schema.json new/apko-0.30.30/pkg/build/types/schema.json --- old/apko-0.30.29/pkg/build/types/schema.json 2025-12-04 15:43:00.000000000 +0100 +++ new/apko-0.30.30/pkg/build/types/schema.json 2025-12-12 12:19:29.000000000 +0100 @@ -3,6 +3,20 @@ "$id": "https://chainguard.dev/apko/pkg/build/types/image-configuration", "$ref": "#/$defs/ImageConfiguration", "$defs": { + "AdditionalCertificateEntry": { + "properties": { + "name": { + "type": "string", + "description": "Required: Name of the certificate entry" + }, + "content": { + "type": "string", + "description": "Required: PEM-encoded certificate content to install in the image.\nMust contain exactly one certificate.\nThe certificate will be:\n1. Appended to the default certificate bundles (e.g., /etc/ssl/certs/ca-certificates.crt)\n2. Installed as an individual file in the ca-certificates." + } + }, + "additionalProperties": false, + "type": "object" + }, "BaseImageDescriptor": { "properties": { "image": { @@ -62,6 +76,19 @@ "additionalProperties": false, "type": "object" }, + "ImageCertificates": { + "properties": { + "additional": { + "items": { + "$ref": "#/$defs/AdditionalCertificateEntry" + }, + "type": "array", + "description": "Additional certificates to install in the image" + } + }, + "additionalProperties": false, + "type": "object" + }, "ImageConfiguration": { "properties": { "contents": { @@ -134,6 +161,10 @@ "layering": { "$ref": "#/$defs/Layering", "description": "Optional: Configuration to control layering of the OCI image." + }, + "certificates": { + "$ref": "#/$defs/ImageCertificates", + "description": "Optional: Certificates to install in the container image" } }, "additionalProperties": false, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-0.30.29/pkg/build/types/types.go new/apko-0.30.30/pkg/build/types/types.go --- old/apko-0.30.29/pkg/build/types/types.go 2025-12-04 15:43:00.000000000 +0100 +++ new/apko-0.30.30/pkg/build/types/types.go 2025-12-12 12:19:29.000000000 +0100 @@ -219,6 +219,9 @@ // Optional: Configuration to control layering of the OCI image. Layering *Layering `json:"layering,omitempty" yaml:"layering,omitempty"` + + // Optional: Certificates to install in the container image + Certificates *ImageCertificates `json:"certificates,omitempty" yaml:"certificates,omitempty"` } // Architecture represents a CPU architecture for the container image. @@ -436,3 +439,19 @@ Strategy string `json:"strategy,omitempty" yaml:"strategy,omitempty"` Budget int `json:"budget,omitempty" yaml:"budget,omitempty"` } + +type AdditionalCertificateEntry struct { + // Required: Name of the certificate entry + Name string `json:"name,omitempty" yaml:"name,omitempty"` + // Required: PEM-encoded certificate content to install in the image. + // Must contain exactly one certificate. + // The certificate will be: + // 1. Appended to the default certificate bundles (e.g., /etc/ssl/certs/ca-certificates.crt) + // 2. Installed as an individual file in the ca-certificates. + Content string `json:"content,omitempty" yaml:"content,omitempty"` +} + +type ImageCertificates struct { + // Additional certificates to install in the image + Additional []AdditionalCertificateEntry `json:"additional,omitempty" yaml:"additional,omitempty"` +} ++++++ apko.obsinfo ++++++ --- /var/tmp/diff_new_pack.OjmvDH/_old 2025-12-12 21:44:03.164109765 +0100 +++ /var/tmp/diff_new_pack.OjmvDH/_new 2025-12-12 21:44:03.172110103 +0100 @@ -1,5 +1,5 @@ name: apko -version: 0.30.29 -mtime: 1764859380 -commit: 0b3827aa83414219539e3cfb82cd07f7972a1950 +version: 0.30.30 +mtime: 1765538369 +commit: d2cb85c1f37e24120b415d4e4372a1b8f811f8e4 ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/apko/vendor.tar.gz /work/SRC/openSUSE:Factory/.apko.new.1939/vendor.tar.gz differ: char 93, line 2
