From 48cf2b73ede8a403166e8ac65ebceef8cb1578fa Mon Sep 17 00:00:00 2001 From: Simon Baird Date: Thu, 26 Feb 2026 09:09:20 -0500 Subject: [PATCH 1/7] Go mod tidy cleanup I guess our Renovate PRs don't do this. Unrelated cleanup while working on the PR for.... Ref: https://issues.redhat.com/browse/EC-1681 --- acceptance/go.sum | 34 ++++++++++------------------------ go.mod | 1 - go.sum | 46 ++++++---------------------------------------- 3 files changed, 16 insertions(+), 65 deletions(-) diff --git a/acceptance/go.sum b/acceptance/go.sum index 60630c305..b6a9b8cef 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -17,8 +17,8 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= -cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= -cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= +cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -36,8 +36,8 @@ cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= cloud.google.com/go/kms v1.23.2 h1:4IYDQL5hG4L+HzJBhzejUySoUOheh3Lk5YT4PCyyW6k= cloud.google.com/go/kms v1.23.2/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g= -cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= -cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= +cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= +cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -276,8 +276,6 @@ github.com/gkampitakis/go-snaps v0.5.7 h1:uVGjHR4t4pPHU944udMx7VKHpwepZXmvDMF+yD github.com/gkampitakis/go-snaps v0.5.7/go.mod h1:ZABkO14uCuVxBHAXAfKG+bqNz+aa1bGPAg8jkI0Nk8Y= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4= github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= @@ -317,8 +315,6 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-openapi/analysis v0.24.1 h1:Xp+7Yn/KOnVWYG8d+hPksOYnCYImE3TieBa7rBOesYM= github.com/go-openapi/analysis v0.24.1/go.mod h1:dU+qxX7QGU1rl7IYhBC8bIfmWQdX4Buoea4TGtxXY84= -github.com/go-openapi/errors v0.22.5 h1:Yfv4O/PRYpNF3BNmVkEizcHb3uLVVsrDt3LNdgAKRY4= -github.com/go-openapi/errors v0.22.5/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= github.com/go-openapi/errors v0.22.6 h1:eDxcf89O8odEnohIXwEjY1IB4ph5vmbUsBMsFNwXWPo= github.com/go-openapi/errors v0.22.6/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= @@ -329,8 +325,6 @@ github.com/go-openapi/loads v0.23.2 h1:rJXAcP7g1+lWyBHC7iTY+WAF0rprtM+pm8Jxv1uQJ github.com/go-openapi/loads v0.23.2/go.mod h1:IEVw1GfRt/P2Pplkelxzj9BYFajiWOtY2nHZNj4UnWY= github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0= github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0= -github.com/go-openapi/spec v0.22.2 h1:KEU4Fb+Lp1qg0V4MxrSCPv403ZjBl8Lx1a83gIPU8Qc= -github.com/go-openapi/spec v0.22.2/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs= github.com/go-openapi/spec v0.22.3 h1:qRSmj6Smz2rEBxMnLRBMeBWxbbOvuOoElvSvObIgwQc= github.com/go-openapi/spec v0.22.3/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs= github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ= @@ -475,12 +469,12 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/enterprise-certificate-proxy v0.3.9 h1:TOpi/QG8iDcZlkQlGlFUti/ZtyLkliXvHDcyUIMuFrU= +github.com/googleapis/enterprise-certificate-proxy v0.3.9/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= -github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= +github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -773,8 +767,6 @@ github.com/sigstore/cosign/v3 v3.0.4 h1:SuEn9z8V0eyjF0PWxuGgQ7QSPWReNexLJovkZ3wL github.com/sigstore/cosign/v3 v3.0.4/go.mod h1:DJY5LPzHiI6bWpG/Q/NQUTfeASjkN8TDAUx1Nnt3I0I= github.com/sigstore/protobuf-specs v0.5.0 h1:F8YTI65xOHw70NrvPwJ5PhAzsvTnuJMGLkA4FIkofAY= github.com/sigstore/protobuf-specs v0.5.0/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= -github.com/sigstore/rekor v1.4.3 h1:2+aw4Gbgumv8vYM/QVg6b+hvr4x4Cukur8stJrVPKU0= -github.com/sigstore/rekor v1.4.3/go.mod h1:o0zgY087Q21YwohVvGwV9vK1/tliat5mfnPiVI3i75o= github.com/sigstore/rekor v1.5.0 h1:rL7SghHd5HLCtsCrxw0yQg+NczGvM75EjSPPWuGjaiQ= github.com/sigstore/rekor v1.5.0/go.mod h1:D7JoVCUkxwQOpPDNYeu+CE8zeBC18Y5uDo6tF8s2rcQ= github.com/sigstore/rekor-tiles/v2 v2.0.1 h1:1Wfz15oSRNGF5Dzb0lWn5W8+lfO50ork4PGIfEKjZeo= @@ -1212,8 +1204,6 @@ google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.258.0 h1:IKo1j5FBlN74fe5isA2PVozN3Y5pwNKriEgAXPOkDAc= -google.golang.org/api v0.258.0/go.mod h1:qhOMTQEZ6lUps63ZNq9jhODswwjkjYYguA7fA3TBFww= google.golang.org/api v0.260.0 h1:XbNi5E6bOVEj/uLXQRlt6TKuEzMD7zvW/6tNwltE4P4= google.golang.org/api v0.260.0/go.mod h1:Shj1j0Phr/9sloYrKomICzdYgsSDImpTxME8rGLaZ/o= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -1253,12 +1243,10 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 h1:LvZVVaPE0JSqL+ZWb6ErZfnEOKIqqFWUJE2D0fObSmc= -google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9/go.mod h1:QFOrLhdAe2PsTp3vQY4quuLKTi9j3XG3r6JPPaw7MSc= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1274,8 +1262,6 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/go.mod b/go.mod index 712543213..8d9104a28 100644 --- a/go.mod +++ b/go.mod @@ -327,7 +327,6 @@ require ( github.com/prometheus/statsd_exporter v0.27.1 // indirect github.com/protocolbuffers/txtpbfmt v0.0.0-20251016062345-16587c79cd91 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect - github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect diff --git a/go.sum b/go.sum index 87da268dc..eb1fdc0c2 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,6 @@ cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= -cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= -cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= @@ -45,12 +43,10 @@ cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= cloud.google.com/go/kms v1.23.2 h1:4IYDQL5hG4L+HzJBhzejUySoUOheh3Lk5YT4PCyyW6k= cloud.google.com/go/kms v1.23.2/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g= -cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= -cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= -cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= -cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= -cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= -cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= +cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY= +cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw= +cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= +cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -64,8 +60,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.57.1 h1:gzao6odNJ7dR3XXYvAgPK+Iw4fVPPznEPPyNjbaVkq8= cloud.google.com/go/storage v1.57.1/go.mod h1:329cwlpzALLgJuu8beyJ/uvQznDHpa2U5lGjWednkzg= -cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= -cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= +cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= +cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d h1:LblfooH1lKOpp1hIhukktmSAxFkqMPFk9KR6iZ0MJNI= contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY= contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= @@ -492,8 +488,6 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-akka/configuration v0.0.0-20200606091224-a002c0330665 h1:Iz3aEheYgn+//VX7VisgCmF/wW3BMtXCLbvHV4jMQJA= github.com/go-akka/configuration v0.0.0-20200606091224-a002c0330665/go.mod h1:19bUnum2ZAeftfwwLZ/wRe7idyfoW2MfmXO464Hrfbw= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4= github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -533,8 +527,6 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/analysis v0.24.1 h1:Xp+7Yn/KOnVWYG8d+hPksOYnCYImE3TieBa7rBOesYM= github.com/go-openapi/analysis v0.24.1/go.mod h1:dU+qxX7QGU1rl7IYhBC8bIfmWQdX4Buoea4TGtxXY84= -github.com/go-openapi/errors v0.22.5 h1:Yfv4O/PRYpNF3BNmVkEizcHb3uLVVsrDt3LNdgAKRY4= -github.com/go-openapi/errors v0.22.5/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= github.com/go-openapi/errors v0.22.6 h1:eDxcf89O8odEnohIXwEjY1IB4ph5vmbUsBMsFNwXWPo= github.com/go-openapi/errors v0.22.6/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= @@ -545,8 +537,6 @@ github.com/go-openapi/loads v0.23.2 h1:rJXAcP7g1+lWyBHC7iTY+WAF0rprtM+pm8Jxv1uQJ github.com/go-openapi/loads v0.23.2/go.mod h1:IEVw1GfRt/P2Pplkelxzj9BYFajiWOtY2nHZNj4UnWY= github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0= github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0= -github.com/go-openapi/spec v0.22.2 h1:KEU4Fb+Lp1qg0V4MxrSCPv403ZjBl8Lx1a83gIPU8Qc= -github.com/go-openapi/spec v0.22.2/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs= github.com/go-openapi/spec v0.22.3 h1:qRSmj6Smz2rEBxMnLRBMeBWxbbOvuOoElvSvObIgwQc= github.com/go-openapi/spec v0.22.3/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs= github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ= @@ -720,14 +710,10 @@ github.com/google/trillian v1.7.2/go.mod h1:mfQJW4qRH6/ilABtPYNBerVJAJ/upxHLX81z github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/enterprise-certificate-proxy v0.3.9 h1:TOpi/QG8iDcZlkQlGlFUti/ZtyLkliXvHDcyUIMuFrU= github.com/googleapis/enterprise-certificate-proxy v0.3.9/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= -github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -916,8 +902,6 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -996,12 +980,8 @@ github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= -github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI= -github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= github.com/olekukonko/ll v0.1.3 h1:sV2jrhQGq5B3W0nENUISCR6azIPf7UBUpVq0x/y70Fg= github.com/olekukonko/ll v0.1.3/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew= -github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY= -github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= github.com/olekukonko/tablewriter v1.1.2 h1:L2kI1Y5tZBct/O/TyZK1zIE9GlBj/TVs+AY5tZDCDSc= github.com/olekukonko/tablewriter v1.1.2/go.mod h1:z7SYPugVqGVavWoA2sGsFIoOVNmEHxUAAMrhXONtfkg= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -1100,9 +1080,6 @@ github.com/qri-io/jsonpointer v0.1.1 h1:prVZBZLL6TW5vsSB9fFHFAMBLI4b0ri5vribQlTJ github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= @@ -1151,8 +1128,6 @@ github.com/sigstore/fulcio v1.8.4 h1:awmmItiPwteo8t8sVOoIAPnmbDfLb1JGW0LPY8SNCdY github.com/sigstore/fulcio v1.8.4/go.mod h1:2jh+uWOfWroKHlhUzr81AFqnAYeZiIi3NC/vegCbiYw= github.com/sigstore/protobuf-specs v0.5.0 h1:F8YTI65xOHw70NrvPwJ5PhAzsvTnuJMGLkA4FIkofAY= github.com/sigstore/protobuf-specs v0.5.0/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= -github.com/sigstore/rekor v1.4.3 h1:2+aw4Gbgumv8vYM/QVg6b+hvr4x4Cukur8stJrVPKU0= -github.com/sigstore/rekor v1.4.3/go.mod h1:o0zgY087Q21YwohVvGwV9vK1/tliat5mfnPiVI3i75o= github.com/sigstore/rekor v1.5.0 h1:rL7SghHd5HLCtsCrxw0yQg+NczGvM75EjSPPWuGjaiQ= github.com/sigstore/rekor v1.5.0/go.mod h1:D7JoVCUkxwQOpPDNYeu+CE8zeBC18Y5uDo6tF8s2rcQ= github.com/sigstore/rekor-tiles/v2 v2.0.1 h1:1Wfz15oSRNGF5Dzb0lWn5W8+lfO50ork4PGIfEKjZeo= @@ -1176,8 +1151,6 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0= -github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= @@ -1784,8 +1757,6 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.258.0 h1:IKo1j5FBlN74fe5isA2PVozN3Y5pwNKriEgAXPOkDAc= -google.golang.org/api v0.258.0/go.mod h1:qhOMTQEZ6lUps63ZNq9jhODswwjkjYYguA7fA3TBFww= google.golang.org/api v0.260.0 h1:XbNi5E6bOVEj/uLXQRlt6TKuEzMD7zvW/6tNwltE4P4= google.golang.org/api v0.260.0/go.mod h1:Shj1j0Phr/9sloYrKomICzdYgsSDImpTxME8rGLaZ/o= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -1837,8 +1808,6 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 h1:LvZVVaPE0JSqL+ZWb6ErZfnEOKIqqFWUJE2D0fObSmc= -google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9/go.mod h1:QFOrLhdAe2PsTp3vQY4quuLKTi9j3XG3r6JPPaw7MSc= google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= @@ -1898,7 +1867,6 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k= gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= @@ -1964,8 +1932,6 @@ sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5E sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -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= sigs.k8s.io/release-utils v0.12.3 h1:iNVJY81QfmMCmXxMg8IvvkkeQNk6ZWlLj+iPKSlKyVQ= sigs.k8s.io/release-utils v0.12.3/go.mod h1:BvbNmm1BmM3cnEpBmNHWL3wOSziOdGlsYR8vCFq/Q0o= sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= From faa456e9f55e11b118e57b944dc6aa5b6eec4f11 Mon Sep 17 00:00:00 2001 From: Simon Baird Date: Tue, 24 Feb 2026 18:15:18 -0500 Subject: [PATCH 2/7] Add new rego function for blob files The goal is to be able to access the task definitions inside a Tekton task bundle, so we can then apply task definition related policy checks to them, but I think this is a generally useful addition to our custom oci functions. Ref: https://issues.redhat.com/browse/EC-1681 Co-authored-by: Claude Code --- .../modules/ROOT/pages/ec_oci_blob_files.adoc | 21 ++ docs/modules/ROOT/pages/rego_builtins.adoc | 2 + docs/modules/ROOT/partials/rego_nav.adoc | 1 + internal/rego/oci/oci.go | 256 +++++++++++++++++- internal/rego/oci/oci_test.go | 233 ++++++++++++++++ 5 files changed, 502 insertions(+), 11 deletions(-) create mode 100644 docs/modules/ROOT/pages/ec_oci_blob_files.adoc diff --git a/docs/modules/ROOT/pages/ec_oci_blob_files.adoc b/docs/modules/ROOT/pages/ec_oci_blob_files.adoc new file mode 100644 index 000000000..366a46491 --- /dev/null +++ b/docs/modules/ROOT/pages/ec_oci_blob_files.adoc @@ -0,0 +1,21 @@ += ec.oci.blob_files + +Fetch structured files (YAML or JSON) from within a blob tar archive. + +== Usage + + files = ec.oci.blob_files(ref: string, paths: array) + +== Parameters + +* `ref` (`string`): OCI blob reference +* `paths` (`array`): the list of paths + +== Return + +`files` (`object`): object representing the extracted files + +The object contains dynamic attributes. +The attributes are of `string` type and represent the full path of the file within the blob. +The values are of `any` type and hold the file contents. + diff --git a/docs/modules/ROOT/pages/rego_builtins.adoc b/docs/modules/ROOT/pages/rego_builtins.adoc index cbbfaacb4..0a95c0642 100644 --- a/docs/modules/ROOT/pages/rego_builtins.adoc +++ b/docs/modules/ROOT/pages/rego_builtins.adoc @@ -10,6 +10,8 @@ information. |=== |xref:ec_oci_blob.adoc[ec.oci.blob] |Fetch a blob from an OCI registry. +|xref:ec_oci_blob_files.adoc[ec.oci.blob_files] +|Fetch structured files (YAML or JSON) from within a blob tar archive. |xref:ec_oci_descriptor.adoc[ec.oci.descriptor] |Fetch a raw Image from an OCI registry. |xref:ec_oci_image_files.adoc[ec.oci.image_files] diff --git a/docs/modules/ROOT/partials/rego_nav.adoc b/docs/modules/ROOT/partials/rego_nav.adoc index 1335fbeab..984603ff4 100644 --- a/docs/modules/ROOT/partials/rego_nav.adoc +++ b/docs/modules/ROOT/partials/rego_nav.adoc @@ -1,5 +1,6 @@ * xref:rego_builtins.adoc[Rego Reference] ** xref:ec_oci_blob.adoc[ec.oci.blob] +** xref:ec_oci_blob_files.adoc[ec.oci.blob_files] ** xref:ec_oci_descriptor.adoc[ec.oci.descriptor] ** xref:ec_oci_image_files.adoc[ec.oci.image_files] ** xref:ec_oci_image_index.adoc[ec.oci.image_index] diff --git a/internal/rego/oci/oci.go b/internal/rego/oci/oci.go index f21cfff2f..44e2d2dab 100644 --- a/internal/rego/oci/oci.go +++ b/internal/rego/oci/oci.go @@ -21,6 +21,7 @@ package oci import ( + "archive/tar" "bytes" "context" "crypto/sha256" @@ -28,7 +29,9 @@ import ( "errors" "fmt" "io" + "path" "runtime" + "strings" "sync" "sync/atomic" @@ -42,6 +45,7 @@ import ( "golang.org/x/sync/errgroup" "golang.org/x/sync/singleflight" "k8s.io/client-go/util/retry" + "sigs.k8s.io/yaml" "github.com/conforma/cli/internal/fetchers/oci/files" "github.com/conforma/cli/internal/utils/oci" @@ -49,6 +53,7 @@ import ( const ( ociBlobName = "ec.oci.blob" + ociBlobFilesName = "ec.oci.blob_files" ociDescriptorName = "ec.oci.descriptor" ociImageManifestName = "ec.oci.image_manifest" ociImageManifestsBatchName = "ec.oci.image_manifests" @@ -320,6 +325,35 @@ func registerOCIImageFiles() { rego.RegisterBuiltin2(&decl, ociImageFiles) } +func registerOCIBlobFiles() { + filesObject := types.NewObject( + nil, + types.NewDynamicProperty( + types.Named("path", types.S).Description("the full path of the file within the blob"), + types.Named("content", types.A).Description("the file contents"), + ), + ) + + decl := rego.Function{ + Name: ociBlobFilesName, + Description: "Fetch structured files (YAML or JSON) from within a blob tar archive.", + Decl: types.NewFunction( + types.Args( + types.Named("ref", types.S).Description("OCI blob reference"), + types.Named("paths", types.NewArray([]types.Type{types.S}, nil)).Description("the list of paths"), + ), + types.Named("files", filesObject).Description("object representing the extracted files"), + ), + // As per the documentation, enable memoization to ensure function evaluation is + // deterministic. But also mark it as non-deterministic because it does rely on external + // entities, i.e. OCI registry. https://www.openpolicyagent.org/docs/latest/extensions/ + Memoize: true, + Nondeterministic: true, + } + + rego.RegisterBuiltin2(&decl, ociBlobFiles) +} + func registerOCIImageIndex() { platform := types.NewObject( []*types.StaticProperty{ @@ -381,6 +415,10 @@ func registerOCIImageIndex() { } func ociBlob(bctx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { + return ociBlobInternal(bctx, a, true) +} + +func ociBlobInternal(bctx rego.BuiltinContext, a *ast.Term, verifyDigest bool) (*ast.Term, error) { logger := log.WithField("function", ociBlobName) uri, ok := a.Value.(ast.String) @@ -454,21 +492,36 @@ func ociBlob(bctx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { return nil, nil //nolint:nilerr } - sum := fmt.Sprintf("sha256:%x", hasher.Sum(nil)) - // io.LimitReader truncates the layer if it exceeds its limit. The condition below catches this - // scenario in order to avoid unexpected behavior caused by partial data being returned. - if sum != ref.DigestStr() { - logger.WithFields(log.Fields{ - "action": "verify digest", - "computed_digest": sum, - "expected_digest": ref.DigestStr(), - }).Error("computed digest does not match expected digest") - return nil, nil + // In the past we used io.LimitReader which might truncate the layer if it + // exceeds its limit. The condition below catches this scenario in order + // to avoid unexpected behavior caused by partial data being returned. We + // don't actually use io.LimitReader here any more, but it seems like a + // reasonable idea to keep this digest check anyhow. Todo: Consider if we + // could/should remove the digest check entirely now. + // + // For ociBlobFiles, we skip the digest verification because there's a + // good chance we'd be calculating the digest of the uncompressed layer + // data which would not match. It might be possible to calculate the + // checksum on the layer data before it is uncompressed, but I think + // that's not as easy as it sounds, since it may require another + // io.Copy which could be inefficient. For now let's just skip it. + // + expectedDigest := ref.DigestStr() + if verifyDigest { + computedDigest := fmt.Sprintf("sha256:%x", hasher.Sum(nil)) + if computedDigest != expectedDigest { + logger.WithFields(log.Fields{ + "action": "verify digest", + "computed_digest": computedDigest, + "expected_digest": expectedDigest, + }).Error("computed digest does not match expected digest") + return nil, nil + } } logger.WithFields(log.Fields{ "action": "complete", - "digest": sum, + "digest": expectedDigest, }).Debug("Successfully retrieved blob") term := ast.StringTerm(blob.String()) @@ -950,6 +1003,186 @@ func ociImageFiles(bctx rego.BuiltinContext, refTerm *ast.Term, pathsTerm *ast.T return result.(*ast.Term), nil } +func ociBlobFiles(bctx rego.BuiltinContext, refTerm *ast.Term, pathsTerm *ast.Term) (*ast.Term, error) { + logger := log.WithField("function", ociBlobFilesName) + + uri, ok := refTerm.Value.(ast.String) + if !ok { + logger.Error("input ref is not a string") + return nil, nil + } + refStr := string(uri) + logger = logger.WithField("ref", refStr) + + if pathsTerm == nil { + logger.Error("paths term is nil") + return nil, nil + } + + // Build cache key from ref + paths (hash the paths for a stable key) + pathsHash := fmt.Sprintf("%x", sha256.Sum256([]byte(pathsTerm.String())))[:12] + cacheKey := refStr + ":" + pathsHash + + // Use component-scoped cache if available, otherwise fall back to global. + // Blob files data can be substantial and is unique per component. + cc := componentCacheFromContext(bctx.Context) + + // Check cache first (fast path) + if cached, found := cc.imageFilesCache.Load(cacheKey); found { + logger.Debug("Blob files served from cache") + return cached.(*ast.Term), nil + } + + // Use singleflight to prevent thundering herd + result, err, _ := cc.imageFilesFlight.Do(cacheKey, func() (any, error) { + // Double-check cache inside singleflight + if cached, found := cc.imageFilesCache.Load(cacheKey); found { + logger.Debug("Blob files served from cache (after singleflight)") + return cached, nil + } + logger.Debug("Starting blob files extraction") + + // Get the blob content first (skip digest verification due to compressed/uncompressed mismatch) + blobTerm, err := ociBlobInternal(bctx, refTerm, false) + if err != nil || blobTerm == nil { + logger.WithFields(log.Fields{ + "action": "fetch blob", + "error": err, + }).Error("failed to fetch blob content") + return nil, nil //nolint:nilerr + } + + blobContent, ok := blobTerm.Value.(ast.String) + if !ok { + logger.Error("blob content is not a string") + return nil, nil + } + + pathsArray, err := builtins.ArrayOperand(pathsTerm.Value, 1) + if err != nil { + logger.WithFields(log.Fields{ + "action": "convert paths", + "error": err, + }).Error("failed to convert paths to array operand") + return nil, nil //nolint:nilerr + } + + // Collect target paths for exact file matching + var targetPaths []string + err = pathsArray.Iter(func(pathTerm *ast.Term) error { + pathString, ok := pathTerm.Value.(ast.String) + if !ok { + return fmt.Errorf("path is not a string: %#v", pathTerm) + } + targetPaths = append(targetPaths, string(pathString)) + return nil + }) + if err != nil { + logger.WithFields(log.Fields{ + "action": "iterate paths", + "error": err, + }).Error("failed iterating paths") + return nil, nil //nolint:nilerr + } + + if len(targetPaths) == 0 { + logger.Debug("No paths specified, returning empty result") + term := ast.NewTerm(ast.NewObject()) + cc.imageFilesCache.Store(cacheKey, term) + return term, nil + } + + // Create a tar reader from the blob content + blobReader := strings.NewReader(string(blobContent)) + archive := tar.NewReader(blobReader) + + // Create a set for fast lookup of target paths + targetPathSet := make(map[string]bool) + for _, path := range targetPaths { + targetPathSet[path] = true + } + + extractedFiles := map[string]json.RawMessage{} + for { + header, err := archive.Next() + if err != nil { + if err == io.EOF { + break + } + logger.WithFields(log.Fields{ + "action": "read tar header", + "error": err, + }).Error("failed to read tar archive") + return nil, nil //nolint:nilerr + } + + // Check if this file matches any of our target paths + if !targetPathSet[header.Name] { + continue + } + + // Check if the file has a supported extension or is explicitly requested + ext := path.Ext(header.Name) + supportedExt := false + for _, e := range []string{".yaml", ".yml", ".json"} { + if strings.EqualFold(ext, e) { + supportedExt = true + break + } + } + + // If no supported extension, only process if file is explicitly in target paths + // This allows processing files without extensions that contain structured data + if !supportedExt { + logger.WithField("file", header.Name).Debug("file has no supported extension, attempting to parse anyway since it was explicitly requested") + } + + // Read the file content + data, err := io.ReadAll(archive) + if err != nil { + logger.WithFields(log.Fields{ + "action": "read file content", + "file": header.Name, + "error": err, + }).Error("failed to read file content") + return nil, nil //nolint:nilerr + } + + // Convert YAML to JSON if needed + data, err = yaml.YAMLToJSON(data) + if err != nil { + logger.WithFields(log.Fields{ + "action": "convert to json", + "file": header.Name, + "error": err, + }).Debug("unable to read file as JSON or YAML, ignoring") + continue + } + + extractedFiles[header.Name] = data + } + + filesValue, err := ast.InterfaceToValue(extractedFiles) + if err != nil { + logger.WithFields(log.Fields{ + "action": "convert files", + "error": err, + }).Error("failed to convert files object to value") + return nil, nil //nolint:nilerr + } + + logger.WithField("file_count", len(extractedFiles)).Debug("Successfully extracted blob files") + term := ast.NewTerm(filesValue) + cc.imageFilesCache.Store(cacheKey, term) + return term, nil + }) + + if err != nil || result == nil { + return nil, nil + } + return result.(*ast.Term), nil +} + func ociImageIndex(bctx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { logger := log.WithField("function", ociImageIndexName) @@ -1129,6 +1362,7 @@ func parseReference(uri string) (name.Reference, error) { func init() { registerOCIBlob() + registerOCIBlobFiles() registerOCIDescriptor() registerOCIImageFiles() registerOCIImageManifest() diff --git a/internal/rego/oci/oci_test.go b/internal/rego/oci/oci_test.go index 2cd93ccd3..49e41d85b 100644 --- a/internal/rego/oci/oci_test.go +++ b/internal/rego/oci/oci_test.go @@ -19,9 +19,13 @@ package oci import ( + "archive/tar" + "bytes" "context" + "crypto/sha256" "errors" "fmt" + "strings" "testing" "github.com/gkampitakis/go-snaps/snaps" @@ -116,6 +120,234 @@ func TestOCIBlob(t *testing.T) { } } +func TestOCIBlobFiles(t *testing.T) { + t.Cleanup(ClearCaches) + ClearCaches() // Clear before test to avoid interference from previous tests + + // Helper function to create a tar archive with test files and compute its digest + createTarArchiveWithDigest := func(files map[string]string) ([]byte, string) { + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + + for path, content := range files { + header := &tar.Header{ + Name: path, + Size: int64(len(content)), + } + require.NoError(t, tw.WriteHeader(header)) + _, err := tw.Write([]byte(content)) + require.NoError(t, err) + } + require.NoError(t, tw.Close()) + + data := buf.Bytes() + digest := fmt.Sprintf("sha256:%x", sha256.Sum256(data)) + return data, digest + } + + cases := []struct { + name string + tarFiles map[string]string + paths []string + expectedLen int + err bool + remoteErr error + invalidRef bool + nilPaths bool + invalidPath bool + }{ + { + name: "success with yaml file", + tarFiles: map[string]string{ + "task.yaml": `apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: example-task`, + "other.txt": "not a yaml file", + }, + paths: []string{"task.yaml"}, + expectedLen: 1, + }, + { + name: "success with multiple files", + tarFiles: map[string]string{ + "task.yaml": `{"apiVersion": "tekton.dev/v1beta1", "kind": "Task"}`, + "pipeline.json": `{"apiVersion": "tekton.dev/v1beta1", "kind": "Pipeline"}`, + "other.txt": "not structured", + }, + paths: []string{"task.yaml", "pipeline.json"}, + expectedLen: 2, + }, + { + name: "no matching files", + tarFiles: map[string]string{ + "other.txt": "not a yaml file", + }, + paths: []string{"task.yaml"}, + expectedLen: 0, + }, + { + name: "invalid ref type", + tarFiles: map[string]string{}, + paths: []string{"task.yaml"}, + invalidRef: true, + err: true, + }, + { + name: "nil paths term", + tarFiles: map[string]string{}, + nilPaths: true, + err: true, + }, + { + name: "remote error", + tarFiles: map[string]string{}, + paths: []string{"task.yaml"}, + remoteErr: errors.New("blob fetch failed"), + err: true, + }, + { + name: "invalid path type in array", + tarFiles: map[string]string{ + "task.yaml": `{"apiVersion": "tekton.dev/v1beta1"}`, + }, + invalidPath: true, + err: true, + }, + { + name: "invalid json file ignored", + tarFiles: map[string]string{ + "task.yaml": `{"apiVersion": "tekton.dev/v1beta1", "kind": "Task"}`, + "invalid.json": `{{{malformed content that can't be parsed as JSON or YAML`, + "valid.json": `{"apiVersion": "tekton.dev/v1beta1", "kind": "Pipeline"}`, + }, + paths: []string{"task.yaml", "invalid.json", "valid.json"}, + expectedLen: 2, // Only task.yaml and valid.json should be extracted, invalid.json is ignored + }, + { + name: "empty paths array", + tarFiles: map[string]string{ + "task.yaml": `{"apiVersion": "tekton.dev/v1beta1", "kind": "Task"}`, + }, + paths: []string{}, // Empty paths array + expectedLen: 0, // Should return empty object + }, + { + name: "file without supported extension", + tarFiles: map[string]string{ + "config": `{"apiVersion": "v1", "kind": "ConfigMap"}`, // No file extension but valid JSON + "task.yaml": `{"apiVersion": "tekton.dev/v1beta1", "kind": "Task"}`, + "readme.txt": `This is just text, not structured data`, + }, + paths: []string{"config", "task.yaml"}, // Explicitly request the extensionless file + expectedLen: 2, // Both config and task.yaml should be extracted + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + ClearCaches() // Clear cache before each subtest + + // Create test data and compute digest dynamically + var refTerm *ast.Term + var pathsTerm *ast.Term + + if c.invalidRef { + refTerm = ast.IntNumberTerm(42) + } else if c.nilPaths { + refTerm = ast.StringTerm("registry.local/bundle@sha256:dummy") + pathsTerm = nil + } else if c.invalidPath { + tarData, digest := createTarArchiveWithDigest(c.tarFiles) + refTerm = ast.StringTerm(fmt.Sprintf("registry.local/bundle@%s", digest)) + pathsTerm = ast.ArrayTerm(ast.IntNumberTerm(123)) // invalid path type + + client := fake.FakeClient{} + layer := static.NewLayer(tarData, types.OCIUncompressedLayer) + client.On("Layer", mock.Anything, mock.Anything).Return(layer, nil) + ctx := oci.WithClient(context.Background(), &client) + bctx := rego.BuiltinContext{Context: ctx} + + result, err := ociBlobFiles(bctx, refTerm, pathsTerm) + require.NoError(t, err) + require.Nil(t, result) + return + } else { + // Normal test cases + tarData, digest := createTarArchiveWithDigest(c.tarFiles) + refTerm = ast.StringTerm(fmt.Sprintf("registry.local/bundle@%s", digest)) + + pathTerms := make([]*ast.Term, len(c.paths)) + for i, path := range c.paths { + pathTerms[i] = ast.StringTerm(path) + } + pathsTerm = ast.ArrayTerm(pathTerms...) + + client := fake.FakeClient{} + if c.remoteErr != nil { + client.On("Layer", mock.Anything, mock.Anything).Return(nil, c.remoteErr) + } else { + layer := static.NewLayer(tarData, types.OCIUncompressedLayer) + client.On("Layer", mock.Anything, mock.Anything).Return(layer, nil) + } + ctx := oci.WithClient(context.Background(), &client) + bctx := rego.BuiltinContext{Context: ctx} + + result, err := ociBlobFiles(bctx, refTerm, pathsTerm) + require.NoError(t, err) + + if c.err { + require.Nil(t, result) + } else { + require.NotNil(t, result) + + // Verify the result is an object + obj, ok := result.Value.(ast.Object) + require.True(t, ok, "result should be an object") + + // Check the number of extracted files + require.Equal(t, c.expectedLen, obj.Len(), "unexpected number of extracted files") + + // For success cases with files, verify basic structure + if c.expectedLen > 0 { + // Iterate through the object using Keys() and Get() + keys := obj.Keys() + require.Len(t, keys, c.expectedLen, "unexpected number of keys") + + for _, key := range keys { + keyStr, ok := key.Value.(ast.String) + require.True(t, ok, "file path should be a string") + + // Verify it's one of the expected files + _, exists := c.tarFiles[string(keyStr)] + require.True(t, exists, "unexpected file: %s", string(keyStr)) + + // Get the value and verify it was parsed + value := obj.Get(key) + require.NotNil(t, value, "file content should not be nil") + + // For structured files, just verify the content exists and was processed + if strings.HasSuffix(string(keyStr), ".yaml") || strings.HasSuffix(string(keyStr), ".yml") || strings.HasSuffix(string(keyStr), ".json") { + // The content should be present (exact type checking is complex with AST) + require.NotNil(t, value.Value, "structured file should have parsed content") + } + } + } + } + return + } + + // Handle special cases (invalid ref, nil paths) + ctx := oci.WithClient(context.Background(), &fake.FakeClient{}) + bctx := rego.BuiltinContext{Context: ctx} + + result, err := ociBlobFiles(bctx, refTerm, pathsTerm) + require.NoError(t, err) + require.Nil(t, result) + }) + } +} + func TestOCIDescriptorManifest(t *testing.T) { t.Cleanup(ClearCaches) ClearCaches() @@ -963,6 +1195,7 @@ func TestOCIImageIndex(t *testing.T) { func TestFunctionsRegistered(t *testing.T) { names := []string{ ociBlobName, + ociBlobFilesName, ociDescriptorName, ociImageFilesName, ociImageManifestName, From 6338c88b7b05a97e75b1581638d2cdff9eb814a0 Mon Sep 17 00:00:00 2001 From: Simon Baird Date: Thu, 26 Feb 2026 12:35:15 -0500 Subject: [PATCH 3/7] Be defensive against oversized blob layers Introduce a limit to avoid potential memory exhaustion attacks. Honestly I'm not sure how important this is, but I guess it's fine. It was suggested by in code review by Qodo. We actually did have a limit in the past, but it was too small, and caused problems with large SBOMs. I hestiated to bring back a limit, however, the context is a little different, ociBlob vs ociBlobFile, and I'm making the limit 500MB instead of 10MB. Ref: https://issues.redhat.com/browse/EC-1681 Co-authored-by: Claude Code --- internal/rego/oci/oci.go | 28 +++++++++++++++++++-- internal/rego/oci/oci_test.go | 46 +++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/internal/rego/oci/oci.go b/internal/rego/oci/oci.go index 44e2d2dab..aa9e89bd3 100644 --- a/internal/rego/oci/oci.go +++ b/internal/rego/oci/oci.go @@ -59,8 +59,11 @@ const ( ociImageManifestsBatchName = "ec.oci.image_manifests" ociImageFilesName = "ec.oci.image_files" ociImageIndexName = "ec.oci.image_index" + maxTarEntrySizeConst = 500 * 1024 * 1024 // 500MB ) +var maxTarEntrySize int64 = maxTarEntrySizeConst // Use var to allow override in tests + func registerOCIBlob() { decl := rego.Function{ Name: ociBlobName, @@ -1137,8 +1140,19 @@ func ociBlobFiles(bctx rego.BuiltinContext, refTerm *ast.Term, pathsTerm *ast.Te logger.WithField("file", header.Name).Debug("file has no supported extension, attempting to parse anyway since it was explicitly requested") } - // Read the file content - data, err := io.ReadAll(archive) + // Check file size to prevent memory exhaustion attacks + if header.Size > maxTarEntrySize { + logger.WithFields(log.Fields{ + "file": header.Name, + "size": header.Size, + "maxSize": maxTarEntrySize, + }).Error("tar entry too large, skipping to prevent memory exhaustion") + continue + } + + // Read the file content with size limit protection + limitedReader := io.LimitReader(archive, maxTarEntrySize) + data, err := io.ReadAll(limitedReader) if err != nil { logger.WithFields(log.Fields{ "action": "read file content", @@ -1148,6 +1162,16 @@ func ociBlobFiles(bctx rego.BuiltinContext, refTerm *ast.Term, pathsTerm *ast.Te return nil, nil //nolint:nilerr } + // Verify we didn't hit the size limit (which would indicate truncation) + if int64(len(data)) == maxTarEntrySize && header.Size > maxTarEntrySize { + logger.WithFields(log.Fields{ + "file": header.Name, + "size": header.Size, + "maxSize": maxTarEntrySize, + }).Error("tar entry was truncated due to size limit") + continue + } + // Convert YAML to JSON if needed data, err = yaml.YAMLToJSON(data) if err != nil { diff --git a/internal/rego/oci/oci_test.go b/internal/rego/oci/oci_test.go index 49e41d85b..7b56714bd 100644 --- a/internal/rego/oci/oci_test.go +++ b/internal/rego/oci/oci_test.go @@ -155,6 +155,7 @@ func TestOCIBlobFiles(t *testing.T) { invalidRef bool nilPaths bool invalidPath bool + oversized bool }{ { name: "success with yaml file", @@ -242,6 +243,16 @@ metadata: paths: []string{"config", "task.yaml"}, // Explicitly request the extensionless file expectedLen: 2, // Both config and task.yaml should be extracted }, + { + name: "oversized tar entry skipped", + tarFiles: map[string]string{ + "small.yaml": `{"ok": true}`, // 12 bytes + "large.json": `{"data": "this will be considered oversized"}`, // 43 bytes, will be "too large" with limit set to 20 bytes + }, + paths: []string{"small.yaml", "large.json"}, + expectedLen: 1, // Only small.yaml should be extracted, large.json should be skipped due to temp limit + oversized: true, + }, } for _, c := range cases { @@ -272,6 +283,41 @@ metadata: require.NoError(t, err) require.Nil(t, result) return + } else if c.oversized { + // Temporarily override the size limit to make the test feasible with small files + originalLimit := maxTarEntrySize + maxTarEntrySize = 20 // Set a very small limit of 20 bytes + defer func() { + maxTarEntrySize = originalLimit // Restore original limit + }() + + // Create normal tar archive + tarData, digest := createTarArchiveWithDigest(c.tarFiles) + refTerm = ast.StringTerm(fmt.Sprintf("registry.local/bundle@%s", digest)) + + pathTerms := make([]*ast.Term, len(c.paths)) + for i, path := range c.paths { + pathTerms[i] = ast.StringTerm(path) + } + pathsTerm = ast.ArrayTerm(pathTerms...) + + client := fake.FakeClient{} + layer := static.NewLayer(tarData, types.OCIUncompressedLayer) + client.On("Layer", mock.Anything, mock.Anything).Return(layer, nil) + ctx := oci.WithClient(context.Background(), &client) + bctx := rego.BuiltinContext{Context: ctx} + + result, err := ociBlobFiles(bctx, refTerm, pathsTerm) + require.NoError(t, err) + require.NotNil(t, result) + + // Verify the result is an object + obj, ok := result.Value.(ast.Object) + require.True(t, ok, "result should be an object") + + // Check that only the small file was extracted (large file should be skipped) + require.Equal(t, c.expectedLen, obj.Len(), "unexpected number of extracted files") + return } else { // Normal test cases tarData, digest := createTarArchiveWithDigest(c.tarFiles) From ef1af599a3b480a3ae67170c582e9b23c11977a2 Mon Sep 17 00:00:00 2001 From: Simon Baird Date: Thu, 26 Feb 2026 13:03:35 -0500 Subject: [PATCH 4/7] Avoid theoretical cache key clash for blobs/files Multiple LLMs are worried about using the same cache key for two potentially different things. Let's make it clear that it's not a problem. In reality I don't think it could be a problem, since the cache key includes the image ref, which in the case of a blob will always have a digest. But, I figure there's no harm in making the code intent clear, and avoid having to explain that subtle detail to the bots. Ref: https://issues.redhat.com/browse/EC-1681 Co-authored-by: Claude Code --- internal/rego/oci/oci.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/rego/oci/oci.go b/internal/rego/oci/oci.go index aa9e89bd3..456e44da5 100644 --- a/internal/rego/oci/oci.go +++ b/internal/rego/oci/oci.go @@ -920,7 +920,7 @@ func ociImageFiles(bctx rego.BuiltinContext, refTerm *ast.Term, pathsTerm *ast.T // Build cache key from ref + paths (hash the paths for a stable key) pathsHash := fmt.Sprintf("%x", sha256.Sum256([]byte(pathsTerm.String())))[:12] - cacheKey := refStr + ":" + pathsHash + cacheKey := "image:" + refStr + ":" + pathsHash // Use component-scoped cache if available, otherwise fall back to global. // Image files data can be substantial and is unique per component. @@ -1024,7 +1024,7 @@ func ociBlobFiles(bctx rego.BuiltinContext, refTerm *ast.Term, pathsTerm *ast.Te // Build cache key from ref + paths (hash the paths for a stable key) pathsHash := fmt.Sprintf("%x", sha256.Sum256([]byte(pathsTerm.String())))[:12] - cacheKey := refStr + ":" + pathsHash + cacheKey := "blob:" + refStr + ":" + pathsHash // Use component-scoped cache if available, otherwise fall back to global. // Blob files data can be substantial and is unique per component. From 9502ae81f27c7c71d399f792f64141d6976ea180 Mon Sep 17 00:00:00 2001 From: Simon Baird Date: Thu, 26 Feb 2026 16:18:33 -0500 Subject: [PATCH 5/7] Add make target to run gofmt -w Sometimes Claude doesn't format everything so I want an easy way to fix the formatting. There might be a better way to do it (and maybe make lintfix will do it?), but this works for me. Unrelated tweak while working on... Ref: https://issues.redhat.com/browse/EC-1681 --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 9ddb61d94..4f8ddd580 100644 --- a/Makefile +++ b/Makefile @@ -359,6 +359,9 @@ conftest-test-cmd-diff: <(curl -s https://raw.githubusercontent.com/open-policy-agent/conftest/$${CONFTEST_VER}/internal/commands/test.go) \ cmd/test/test.go +fmt-all: + @git ls-files '*.go' | xargs gofmt -w + # Useful while hacking on build numbers and versions debug-version: @echo $(VERSION) From f54b1df965e2af873273fd1658ecdc18e3e94e01 Mon Sep 17 00:00:00 2001 From: Simon Baird Date: Fri, 27 Feb 2026 09:56:58 -0500 Subject: [PATCH 6/7] Comment about tar file size limit effectiveness Well the bot wanted the limit checks, then the bot pointed out that they aren't very effective. I don't want to add limit checks on all the oci fetches, but I also don't want to re-remove what we have here, so let's at least point out that its value is questionable. Ref: https://issues.redhat.com/browse/EC-1681 --- internal/rego/oci/oci.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/rego/oci/oci.go b/internal/rego/oci/oci.go index 456e44da5..9dd05d033 100644 --- a/internal/rego/oci/oci.go +++ b/internal/rego/oci/oci.go @@ -1151,6 +1151,12 @@ func ociBlobFiles(bctx rego.BuiltinContext, refTerm *ast.Term, pathsTerm *ast.Te } // Read the file content with size limit protection + // Note: This limit protection can't protect against all kinds of memory + // exhaustion attacks since we already loaded the full blobContent prior + // to this. I'm thinking let's keep it here anyhow since it maybe (?) + // can protect against certain kinds of attacks, and it's probably not + // doing any harm. That said, its value is questionable and we may want + // to revisit this later. limitedReader := io.LimitReader(archive, maxTarEntrySize) data, err := io.ReadAll(limitedReader) if err != nil { From 46155ec6cc2c939f459d11b9f36a667e5b35463e Mon Sep 17 00:00:00 2001 From: Simon Baird Date: Fri, 27 Feb 2026 12:02:57 -0500 Subject: [PATCH 7/7] Adjust cache var names for clarity Since we're using the cache for both image files and blob files, let's stop calling it imageFilesCache. Same thing for single flight. Suggested by Qodo in code review. Co-authored-by: Claude Code Ref: https://issues.redhat.com/browse/EC-1681 --- internal/rego/oci/oci.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/internal/rego/oci/oci.go b/internal/rego/oci/oci.go index 9dd05d033..aa0cc87ed 100644 --- a/internal/rego/oci/oci.go +++ b/internal/rego/oci/oci.go @@ -742,10 +742,10 @@ func ClearCaches() { // Lighter caches (manifests, descriptors, image indexes) remain global because they // are small and benefit from cross-component sharing (e.g., shared task bundle manifests). type ComponentCache struct { - blobCache sync.Map - blobFlight singleflight.Group - imageFilesCache sync.Map - imageFilesFlight singleflight.Group + blobCache sync.Map + blobFlight singleflight.Group + filesCache sync.Map + filesFlight singleflight.Group } type componentCacheKey struct{} @@ -927,15 +927,15 @@ func ociImageFiles(bctx rego.BuiltinContext, refTerm *ast.Term, pathsTerm *ast.T cc := componentCacheFromContext(bctx.Context) // Check cache first (fast path) - if cached, found := cc.imageFilesCache.Load(cacheKey); found { + if cached, found := cc.filesCache.Load(cacheKey); found { logger.Debug("Image files served from cache") return cached.(*ast.Term), nil } // Use singleflight to prevent thundering herd - result, err, _ := cc.imageFilesFlight.Do(cacheKey, func() (any, error) { + result, err, _ := cc.filesFlight.Do(cacheKey, func() (any, error) { // Double-check cache inside singleflight - if cached, found := cc.imageFilesCache.Load(cacheKey); found { + if cached, found := cc.filesCache.Load(cacheKey); found { logger.Debug("Image files served from cache (after singleflight)") return cached, nil } @@ -996,7 +996,7 @@ func ociImageFiles(bctx rego.BuiltinContext, refTerm *ast.Term, pathsTerm *ast.T logger.Debug("Successfully extracted image files") term := ast.NewTerm(filesValue) - cc.imageFilesCache.Store(cacheKey, term) + cc.filesCache.Store(cacheKey, term) return term, nil }) @@ -1031,15 +1031,15 @@ func ociBlobFiles(bctx rego.BuiltinContext, refTerm *ast.Term, pathsTerm *ast.Te cc := componentCacheFromContext(bctx.Context) // Check cache first (fast path) - if cached, found := cc.imageFilesCache.Load(cacheKey); found { + if cached, found := cc.filesCache.Load(cacheKey); found { logger.Debug("Blob files served from cache") return cached.(*ast.Term), nil } // Use singleflight to prevent thundering herd - result, err, _ := cc.imageFilesFlight.Do(cacheKey, func() (any, error) { + result, err, _ := cc.filesFlight.Do(cacheKey, func() (any, error) { // Double-check cache inside singleflight - if cached, found := cc.imageFilesCache.Load(cacheKey); found { + if cached, found := cc.filesCache.Load(cacheKey); found { logger.Debug("Blob files served from cache (after singleflight)") return cached, nil } @@ -1091,7 +1091,7 @@ func ociBlobFiles(bctx rego.BuiltinContext, refTerm *ast.Term, pathsTerm *ast.Te if len(targetPaths) == 0 { logger.Debug("No paths specified, returning empty result") term := ast.NewTerm(ast.NewObject()) - cc.imageFilesCache.Store(cacheKey, term) + cc.filesCache.Store(cacheKey, term) return term, nil } @@ -1203,7 +1203,7 @@ func ociBlobFiles(bctx rego.BuiltinContext, refTerm *ast.Term, pathsTerm *ast.Te logger.WithField("file_count", len(extractedFiles)).Debug("Successfully extracted blob files") term := ast.NewTerm(filesValue) - cc.imageFilesCache.Store(cacheKey, term) + cc.filesCache.Store(cacheKey, term) return term, nil })