0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00

feat: upload cosign public key and notation certificates to cloud

Signed-off-by: Andreea-Lupu <andreealupu1470@yahoo.com>
(cherry picked from commit 5f2f8adbc3)

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
This commit is contained in:
Andreea-Lupu 2023-07-04 15:15:54 +03:00 committed by Andrei Aaron
parent 9bccd784a9
commit eafcc1a213
No known key found for this signature in database
GPG key ID: B6D92FCF54530D2C
23 changed files with 1385 additions and 413 deletions

12
go.mod
View file

@ -50,6 +50,8 @@ require (
require (
github.com/aquasecurity/trivy v0.43.1
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.21.2
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.21.3
github.com/aws/aws-secretsmanager-caching-go v1.1.2
github.com/containers/image/v5 v5.25.0
github.com/gobwas/glob v0.2.3
github.com/google/go-github/v52 v52.0.0
@ -256,14 +258,14 @@ require (
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go v1.44.333
github.com/aws/aws-sdk-go-v2 v1.20.1
github.com/aws/aws-sdk-go v1.44.334
github.com/aws/aws-sdk-go-v2 v1.21.0
github.com/aws/aws-sdk-go-v2/config v1.18.27
github.com/aws/aws-sdk-go-v2/credentials v1.13.26 // indirect
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.10.36
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.38 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.32 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.17.18 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.12.0 // indirect
@ -271,7 +273,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.19.2 // indirect
github.com/aws/smithy-go v1.14.1 // indirect
github.com/aws/smithy-go v1.14.2 // indirect
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220228164355-396b2034c795 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect

25
go.sum
View file

@ -405,16 +405,18 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.333 h1:X0j5TGXtHLZzDB/uRcGKLG77ERFtxYQtXefs+Apf2PU=
github.com/aws/aws-sdk-go v1.44.333/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.287/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.334 h1:h2bdbGb//fez6Sv6PaYv868s9liDeoYM6hYsAqTB4MU=
github.com/aws/aws-sdk-go v1.44.334/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v1.7.1/go.mod h1:L5LuPC1ZgDr2xQS7AmIec/Jlc7O/Y1u2KxJyNVab250=
github.com/aws/aws-sdk-go-v2 v1.14.0/go.mod h1:ZA3Y8V0LrlWj63MQAnRHgKf/5QB//LSZCPNWlWrNGLU=
github.com/aws/aws-sdk-go-v2 v1.16.16/go.mod h1:SwiyXi/1zTUZ6KIAmLK5V5ll8SiURNUYOqTerZPaF9k=
github.com/aws/aws-sdk-go-v2 v1.17.1/go.mod h1:JLnGeGONAyi2lWXI1p0PCIOIy333JMVK1U7Hf0aRFLw=
github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2 v1.18.1/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2 v1.20.1 h1:rZBf5DWr7YGrnlTK4kgDQGn1ltqOg5orCYb/UhOFZkg=
github.com/aws/aws-sdk-go-v2 v1.20.1/go.mod h1:NU06lETsFm8fUC6ZjhgDpVBcGZTFQ6XM+LZWZxMI4ac=
github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc=
github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M=
github.com/aws/aws-sdk-go-v2/config v1.5.0/go.mod h1:RWlPOAW3E3tbtNAqTwvSW54Of/yP3oiZXMI0xfUdjyA=
github.com/aws/aws-sdk-go-v2/config v1.18.27 h1:Az9uLwmssTE6OGTpsFqOnaGpLnKDqNYOJzWuC6UAYzA=
github.com/aws/aws-sdk-go-v2/config v1.18.27/go.mod h1:0My+YgmkGxeqjXZb5BYme5pc4drjTnM+x1GJ3zv42Nw=
@ -431,15 +433,17 @@ github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23/go.mod h1:2DFxAQ9pfI
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.25/go.mod h1:Zb29PYkf42vVYQY6pvSyJCJcFHlPIiY+YKdPtwnvMkY=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34/go.mod h1:wZpTEecJe0Btj3IYnDx/VlUzor9wm3fJHyvLpQF0VwY=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.38 h1:c8ed/T9T2K5I+h/JzmF5tpI46+OODQ74dzmdo+QnaMg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.38/go.mod h1:qggunOChCMu9ZF/UkAfhTz25+U2rLVb3ya0Ua6TTfCA=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.3.0/go.mod h1:miRSv9l093jX/t/j+mBCaLqFHo9xKYzJ7DGm1BsGoJM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17/go.mod h1:pRwaTYCJemADaqCbUAxltMoHKata7hmB5PjEXeu0kfg=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19/go.mod h1:6Q0546uHDp421okhmmGfbxzq2hBqbXFNpi4k+Q1JnQA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28/go.mod h1:7VRpKQQedkfIEXb4k52I7swUnZP0wohVajJMRn3vsUw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.32 h1:hNeAAymUY5gu11WrrmFb3CVIp9Dar9hbo44yzzcQpzA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.32/go.mod h1:0ZXSqrty4FtQ7p8TEuRde/SZm9X05KT18LAUlR40Ln0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.1.1/go.mod h1:Zy8smImhTdOETZqfyn01iNOe0CNggVbPjCajyaz6Gvg=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 h1:LWA+3kDM8ly001vJ1X1waCuLJdtTl48gwkPKWy9sosI=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35/go.mod h1:0Eg1YjxE0Bhn56lx+SHJwCzhW+2JGtizsrx+lCqrfm0=
@ -466,6 +470,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EO
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 h1:bkRyG4a929RCnpVSTvLM2j/T4ls015ZhhYApbmYs15s=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28/go.mod h1:jj7znCIg05jXlaGBlFMGP8+7UN3VtCkRBG2spnmRQkU=
github.com/aws/aws-sdk-go-v2/service/kms v1.23.0 h1:NXYeZBNg35rDBhcus60DFkIP7q6RNSkarLx+37ERX1g=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.21.3 h1:H6ZipEknzu7RkJW3w2PP75zd8XOdR35AEY5D57YrJtA=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.21.3/go.mod h1:5W2cYXDPabUmwULErlC92ffLhtTuyv4ai+5HhdbhfNo=
github.com/aws/aws-sdk-go-v2/service/sso v1.3.1/go.mod h1:J3A3RGUvuCZjvSuZEcOpHDnzZP/sKbhDWV2T1EOzFIM=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 h1:nneMBM2p79PGWBQovYO/6Xnc2ryRMw3InnDJq1FHkSY=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.12/go.mod h1:HuCOxYsF21eKrerARYO6HapNeh9GBNq7fius2AcwodY=
@ -474,13 +480,16 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12/go.mod h1:E4VrHCPzmVB/KFXt
github.com/aws/aws-sdk-go-v2/service/sts v1.6.0/go.mod h1:q7o0j7d7HrJk/vr9uUt3BVRASvcU7gYZB9PUgPiByXg=
github.com/aws/aws-sdk-go-v2/service/sts v1.19.2 h1:XFJ2Z6sNUUcAz9poj+245DMkrHE4h2j5I9/xD50RHfE=
github.com/aws/aws-sdk-go-v2/service/sts v1.19.2/go.mod h1:dp0yLPsLBOi++WTxzCjA/oZqi6NPIhoR+uF7GeMU9eg=
github.com/aws/aws-secretsmanager-caching-go v1.1.2 h1:tY3pRhAkaohm75KFpGHoqjWrnRpznqrc8iX/wTLVpH0=
github.com/aws/aws-secretsmanager-caching-go v1.1.2/go.mod h1:s3Or+O0O8obPyDJz6875Rg1WApAbQ64L0WTBwYNnKLo=
github.com/aws/smithy-go v1.6.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/aws/smithy-go v1.11.0/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
github.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/aws/smithy-go v1.13.4/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/aws/smithy-go v1.14.1 h1:EFKMUmH/iHMqLiwoEDx2rRjRQpI1YCn5jTysoaDujFs=
github.com/aws/smithy-go v1.14.1/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ=
github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220228164355-396b2034c795 h1:IWeCJzU+IYaO2rVEBlGPTBfe90cmGXFTLdhUFlzDGsY=
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220228164355-396b2034c795/go.mod h1:8vJsEZ4iRqG+Vx6pKhWK6U00qcj0KC37IsfszMkY6UE=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
@ -1808,6 +1817,7 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -2078,6 +2088,7 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -2088,6 +2099,7 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -2105,6 +2117,7 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View file

@ -265,6 +265,11 @@ func (c *Controller) InitMetaDB(reloadCtx context.Context) error {
return err
}
err = ext.SetupExtensions(c.Config, driver, c.Log) //nolint:contextcheck
if err != nil {
return err
}
err = driver.PatchDB()
if err != nil {
return err

View file

@ -194,7 +194,7 @@ func (rh *RouteHandler) SetupRoutes() {
// Preconditions for enabling the actual extension routes are part of extensions themselves
ext.SetupSearchRoutes(rh.c.Config, prefixedRouter, rh.c.StoreController, rh.c.MetaDB, rh.c.CveInfo,
rh.c.Log)
ext.SetupImageTrustRoutes(rh.c.Config, prefixedRouter, rh.c.Log)
ext.SetupImageTrustRoutes(rh.c.Config, prefixedRouter, rh.c.MetaDB, rh.c.Log)
ext.SetupMgmtRoutes(rh.c.Config, prefixedRouter, rh.c.Log)
ext.SetupUserPreferencesRoutes(rh.c.Config, prefixedRouter, rh.c.MetaDB, rh.c.Log)
// last should always be UI because it will setup a http.FileServer and paths will be resolved by this FileServer.

View file

@ -21,16 +21,11 @@ import (
"zotregistry.io/zot/pkg/scheduler"
)
const (
ConfigResource = "config"
SignaturesResource = "signatures"
)
func IsBuiltWithImageTrustExtension() bool {
return true
}
func SetupImageTrustRoutes(conf *config.Config, router *mux.Router, log log.Logger) {
func SetupImageTrustRoutes(conf *config.Config, router *mux.Router, metaDB mTypes.MetaDB, log log.Logger) {
if !conf.IsImageTrustEnabled() || (!conf.IsCosignEnabled() && !conf.IsNotationEnabled()) {
log.Info().Msg("skip enabling the image trust routes as the config prerequisites are not met")
@ -39,7 +34,8 @@ func SetupImageTrustRoutes(conf *config.Config, router *mux.Router, log log.Logg
log.Info().Msg("setting up image trust routes")
trust := ImageTrust{Conf: conf, Log: log}
imgTrustStore, _ := metaDB.ImageTrustStore().(*imagetrust.ImageTrustStore)
trust := ImageTrust{Conf: conf, ImageTrustStore: imgTrustStore, Log: log}
allowedMethods := zcommon.AllowedMethods(http.MethodPost)
if conf.IsNotationEnabled() {
@ -70,8 +66,9 @@ func SetupImageTrustRoutes(conf *config.Config, router *mux.Router, log log.Logg
}
type ImageTrust struct {
Conf *config.Config
Log log.Logger
Conf *config.Config
ImageTrustStore *imagetrust.ImageTrustStore
Log log.Logger
}
// Cosign handler godoc
@ -93,7 +90,7 @@ func (trust *ImageTrust) HandleCosignPublicKeyUpload(response http.ResponseWrite
return
}
err = imagetrust.UploadPublicKey(body)
err = imagetrust.UploadPublicKey(trust.ImageTrustStore.CosignStorage, body)
if err != nil {
if errors.Is(err, zerr.ErrInvalidPublicKeyContent) {
response.WriteHeader(http.StatusBadRequest)
@ -151,7 +148,7 @@ func (trust *ImageTrust) HandleNotationCertificateUpload(response http.ResponseW
return
}
err = imagetrust.UploadCertificate(body, truststoreType, truststoreName)
err = imagetrust.UploadCertificate(trust.ImageTrustStore.NotationStorage, body, truststoreType, truststoreName)
if err != nil {
if errors.Is(err, zerr.ErrInvalidTruststoreType) ||
errors.Is(err, zerr.ErrInvalidTruststoreName) ||
@ -178,6 +175,35 @@ func EnableImageTrustVerification(conf *config.Config, taskScheduler *scheduler.
generator := imagetrust.NewTaskGenerator(metaDB, log)
numberOfHours := 2
interval := time.Duration(numberOfHours) * time.Minute
interval := time.Duration(numberOfHours) * time.Hour
taskScheduler.SubmitGenerator(generator, interval, scheduler.MediumPriority)
}
func SetupImageTrustExtension(conf *config.Config, metaDB mTypes.MetaDB, log log.Logger) error {
if !conf.IsImageTrustEnabled() {
return nil
}
var imgTrustStore mTypes.ImageTrustStore
var err error
if conf.Storage.RemoteCache {
endpoint, _ := conf.Storage.CacheDriver["endpoint"].(string)
region, _ := conf.Storage.CacheDriver["region"].(string)
imgTrustStore, err = imagetrust.NewCloudImageTrustStore(region, endpoint)
if err != nil {
return err
}
} else {
imgTrustStore, err = imagetrust.NewLocalImageTrustStore(conf.Storage.RootDirectory)
if err != nil {
return err
}
}
metaDB.SetImageTrustStore(imgTrustStore)
return nil
}

View file

@ -16,7 +16,7 @@ func IsBuiltWithImageTrustExtension() bool {
return false
}
func SetupImageTrustRoutes(config *config.Config, router *mux.Router, log log.Logger) {
func SetupImageTrustRoutes(config *config.Config, router *mux.Router, metaDB mTypes.MetaDB, log log.Logger) {
log.Warn().Msg("skipping setting up image trust routes because given zot binary doesn't include this feature," +
"please build a binary that does so")
}
@ -27,3 +27,10 @@ func EnableImageTrustVerification(config *config.Config, taskScheduler *schedule
log.Warn().Msg("skipping adding to the scheduler a generator for updating signatures validity because " +
"given binary doesn't include this feature, please build a binary that does so")
}
func SetupImageTrustExtension(conf *config.Config, metaDB mTypes.MetaDB, log log.Logger) error {
log.Warn().Msg("skipping setting up image trust because given zot binary doesn't include this feature," +
"please build a binary that does so")
return nil
}

View file

@ -16,6 +16,7 @@ import (
"testing"
"time"
guuid "github.com/gofrs/uuid"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/generate"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
@ -111,7 +112,49 @@ func TestSignaturesAllowedMethodsHeader(t *testing.T) {
})
}
func TestSignatureUploadAndVerification(t *testing.T) {
func TestSignatureUploadAndVerificationLocal(t *testing.T) {
Convey("test with local storage", t, func() {
var cacheDriverParams map[string]interface{}
RunSignatureUploadAndVerificationTests(t, cacheDriverParams)
})
}
func TestSignatureUploadAndVerificationAWS(t *testing.T) {
skipIt(t)
Convey("test with AWS", t, func() {
uuid, err := guuid.NewV4()
So(err, ShouldBeNil)
cacheTablename := "BlobTable" + uuid.String()
repoMetaTablename := "RepoMetadataTable" + uuid.String()
manifestDataTablename := "ManifestDataTable" + uuid.String()
versionTablename := "Version" + uuid.String()
indexDataTablename := "IndexDataTable" + uuid.String()
userDataTablename := "UserDataTable" + uuid.String()
apiKeyTablename := "ApiKeyTable" + uuid.String()
cacheDriverParams := map[string]interface{}{
"name": "dynamoDB",
"endpoint": os.Getenv("DYNAMODBMOCK_ENDPOINT"),
"region": "us-east-2",
"cacheTablename": cacheTablename,
"repoMetaTablename": repoMetaTablename,
"manifestDataTablename": manifestDataTablename,
"indexDataTablename": indexDataTablename,
"userDataTablename": userDataTablename,
"apiKeyTablename": apiKeyTablename,
"versionTablename": versionTablename,
}
t.Logf("using dynamo driver options: %v", cacheDriverParams)
RunSignatureUploadAndVerificationTests(t, cacheDriverParams)
})
}
func RunSignatureUploadAndVerificationTests(t *testing.T, cacheDriverParams map[string]interface{}) { //nolint: thelper
repo := "repo"
tag := "0.0.1"
certName := "test"
@ -128,12 +171,15 @@ func TestSignatureUploadAndVerification(t *testing.T) {
}
}`
Convey("Verify cosign public key upload without search or notation being enabled", t, func() {
Convey("Verify cosign public key upload without search or notation being enabled", func() {
globalDir := t.TempDir()
port := test.GetFreePort()
conf := config.New()
conf.HTTP.Port = port
if cacheDriverParams != nil {
conf.Storage.CacheDriver = cacheDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Trust = &extconf.ImageTrustConfig{}
conf.Extensions.Trust.Enable = &defaultValue
@ -246,12 +292,15 @@ func TestSignatureUploadAndVerification(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
})
Convey("Verify notation certificate upload without search or cosign being enabled", t, func() {
Convey("Verify notation certificate upload without search or cosign being enabled", func() {
globalDir := t.TempDir()
port := test.GetFreePort()
conf := config.New()
conf.HTTP.Port = port
if cacheDriverParams != nil {
conf.Storage.CacheDriver = cacheDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Trust = &extconf.ImageTrustConfig{}
conf.Extensions.Trust.Enable = &defaultValue
@ -360,12 +409,15 @@ func TestSignatureUploadAndVerification(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
})
Convey("Verify uploading notation certificates", t, func() {
Convey("Verify uploading notation certificates", func() {
globalDir := t.TempDir()
port := test.GetFreePort()
conf := config.New()
conf.HTTP.Port = port
if cacheDriverParams != nil {
conf.Storage.CacheDriver = cacheDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Search = &extconf.SearchConfig{}
conf.Extensions.Search.Enable = &defaultValue
@ -533,12 +585,15 @@ func TestSignatureUploadAndVerification(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
})
Convey("Verify uploading cosign public keys", t, func() {
Convey("Verify uploading cosign public keys", func() {
globalDir := t.TempDir()
port := test.GetFreePort()
conf := config.New()
conf.HTTP.Port = port
if cacheDriverParams != nil {
conf.Storage.CacheDriver = cacheDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Search = &extconf.SearchConfig{}
conf.Extensions.Search.Enable = &defaultValue
@ -698,7 +753,7 @@ func TestSignatureUploadAndVerification(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
})
Convey("Verify uploading cosign public keys with auth configured", t, func() {
Convey("Verify uploading cosign public keys with auth configured", func() {
globalDir := t.TempDir()
port := test.GetFreePort()
testCreds := test.GetCredString("admin", "admin") + "\n" + test.GetCredString("test", "test")
@ -713,6 +768,9 @@ func TestSignatureUploadAndVerification(t *testing.T) {
Actions: []string{},
},
}
if cacheDriverParams != nil {
conf.Storage.CacheDriver = cacheDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Search = &extconf.SearchConfig{}
conf.Extensions.Search.Enable = &defaultValue
@ -788,12 +846,15 @@ func TestSignatureUploadAndVerification(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
})
Convey("Verify signatures are read from the disk and updated in the DB when zot starts", t, func() {
Convey("Verify signatures are read from the disk and updated in the DB when zot starts", func() {
globalDir := t.TempDir()
port := test.GetFreePort()
conf := config.New()
conf.HTTP.Port = port
if cacheDriverParams != nil {
conf.Storage.CacheDriver = cacheDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Search = &extconf.SearchConfig{}
conf.Extensions.Search.Enable = &defaultValue
@ -890,12 +951,15 @@ func TestSignatureUploadAndVerification(t *testing.T) {
So(imgSummary.Manifests[0].SignatureInfo[0].Author, ShouldEqual, "")
})
Convey("Verify failures when saving uploaded certificates and public keys", t, func() {
Convey("Verify failures when saving uploaded certificates and public keys", func() {
globalDir := t.TempDir()
port := test.GetFreePort()
conf := config.New()
conf.HTTP.Port = port
if cacheDriverParams != nil {
conf.Storage.CacheDriver = cacheDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Search = &extconf.SearchConfig{}
conf.Extensions.Search.Enable = &defaultValue
@ -966,3 +1030,11 @@ func TestSignatureUploadAndVerification(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
})
}
func skipIt(t *testing.T) {
t.Helper()
if os.Getenv("DYNAMODBMOCK_ENDPOINT") == "" {
t.Skip("Skipping testing without AWS S3 mock server")
}
}

View file

@ -55,3 +55,7 @@ func EnableScheduledTasks(conf *config.Config, taskScheduler *scheduler.Schedule
) {
EnableImageTrustVerification(conf, taskScheduler, metaDB, log)
}
func SetupExtensions(conf *config.Config, metaDB mTypes.MetaDB, log log.Logger) error {
return SetupImageTrustExtension(conf, metaDB, log)
}

View file

@ -13,10 +13,14 @@ import (
"os"
"path"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager/types"
"github.com/aws/aws-secretsmanager-caching-go/secretcache"
godigest "github.com/opencontainers/go-digest"
"github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key"
sigs "github.com/sigstore/cosign/v2/pkg/signature"
"github.com/sigstore/sigstore/pkg/cryptoutils"
sigstoreSigs "github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/options"
zerr "zotregistry.io/zot/errors"
@ -24,116 +28,242 @@ import (
const cosignDirRelativePath = "_cosign"
var cosignDir = "" //nolint:gochecknoglobals
type PublicKeyLocalStorage struct {
cosignDir string
}
func InitCosignDir(rootDir string) error {
type PublicKeyCloudStorage struct {
secretsManagerClient *secretsmanager.Client
secretsManagerCache *secretcache.Cache
}
type publicKeyStorage interface {
StorePublicKey(name godigest.Digest, publicKeyContent []byte) error
GetPublicKeyVerifier(name string) (sigstoreSigs.Verifier, []byte, error)
GetPublicKeys() ([]string, error)
}
func NewPublicKeyLocalStorage(rootDir string) (*PublicKeyLocalStorage, error) {
dir := path.Join(rootDir, cosignDirRelativePath)
_, err := os.Stat(dir)
if os.IsNotExist(err) {
err = os.MkdirAll(dir, defaultDirPerms)
if err != nil {
return err
return nil, err
}
}
if err == nil {
cosignDir = dir
if err != nil {
return nil, err
}
return err
return &PublicKeyLocalStorage{
cosignDir: dir,
}, nil
}
func GetCosignDirPath() (string, error) {
if cosignDir != "" {
return cosignDir, nil
func NewPublicKeyCloudStorage(
secretsManagerClient *secretsmanager.Client, secretsManagerCache *secretcache.Cache,
) *PublicKeyCloudStorage {
return &PublicKeyCloudStorage{
secretsManagerClient: secretsManagerClient,
secretsManagerCache: secretsManagerCache,
}
}
func (local *PublicKeyLocalStorage) GetCosignDirPath() (string, error) {
if local.cosignDir != "" {
return local.cosignDir, nil
}
return "", zerr.ErrSignConfigDirNotSet
}
func VerifyCosignSignature(
repo string, digest godigest.Digest, signatureKey string, layerContent []byte,
cosignStorage publicKeyStorage, repo string, digest godigest.Digest, signatureKey string, layerContent []byte,
) (string, bool, error) {
cosignDir, err := GetCosignDirPath()
publicKeys, err := cosignStorage.GetPublicKeys()
if err != nil {
return "", false, err
}
files, err := os.ReadDir(cosignDir)
if err != nil {
return "", false, err
}
for _, publicKey := range publicKeys {
// cosign verify the image
pubKeyVerifier, pubKeyContent, err := cosignStorage.GetPublicKeyVerifier(publicKey)
if err != nil {
continue
}
for _, file := range files {
if !file.IsDir() {
// cosign verify the image
ctx := context.Background()
keyRef := path.Join(cosignDir, file.Name())
hashAlgorithm := crypto.SHA256
pkcs11Key, ok := pubKeyVerifier.(*pkcs11key.Key)
if ok {
defer pkcs11Key.Close()
}
pubKey, err := sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, hashAlgorithm)
if err != nil {
continue
}
verifier := pubKeyVerifier
pkcs11Key, ok := pubKey.(*pkcs11key.Key)
if ok {
defer pkcs11Key.Close()
}
b64sig := signatureKey
verifier := pubKey
signature, err := base64.StdEncoding.DecodeString(b64sig)
if err != nil {
continue
}
b64sig := signatureKey
compressed := io.NopCloser(bytes.NewReader(layerContent))
signature, err := base64.StdEncoding.DecodeString(b64sig)
if err != nil {
continue
}
payload, err := io.ReadAll(compressed)
if err != nil {
continue
}
compressed := io.NopCloser(bytes.NewReader(layerContent))
err = verifier.VerifySignature(bytes.NewReader(signature), bytes.NewReader(payload),
options.WithContext(context.Background()))
payload, err := io.ReadAll(compressed)
if err != nil {
continue
}
err = verifier.VerifySignature(bytes.NewReader(signature), bytes.NewReader(payload), options.WithContext(ctx))
if err == nil {
publicKey, err := os.ReadFile(keyRef)
if err != nil {
continue
}
return string(publicKey), true, nil
}
if err == nil {
return string(pubKeyContent), true, nil
}
}
return "", false, nil
}
func UploadPublicKey(publicKeyContent []byte) error {
func (local *PublicKeyLocalStorage) GetPublicKeyVerifier(fileName string) (sigstoreSigs.Verifier, []byte, error) {
cosignDir, err := local.GetCosignDirPath()
if err != nil {
return nil, []byte{}, err
}
ctx := context.Background()
keyRef := path.Join(cosignDir, fileName)
hashAlgorithm := crypto.SHA256
pubKeyContent, err := os.ReadFile(keyRef)
if err != nil {
return nil, nil, err
}
pubKey, err := sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, hashAlgorithm)
if err != nil {
return nil, nil, err
}
return pubKey, pubKeyContent, nil
}
func (cloud *PublicKeyCloudStorage) GetPublicKeyVerifier(secretName string) (sigstoreSigs.Verifier, []byte, error) {
hashAlgorithm := crypto.SHA256
// get key
raw, err := cloud.secretsManagerCache.GetSecretString(secretName)
if err != nil {
return nil, nil, err
}
rawDecoded, err := base64.StdEncoding.DecodeString(raw)
if err != nil {
return nil, nil, err
}
// PEM encoded file.
key, err := cryptoutils.UnmarshalPEMToPublicKey(rawDecoded)
if err != nil {
return nil, nil, err
}
pubKey, err := sigstoreSigs.LoadVerifier(key, hashAlgorithm)
if err != nil {
return nil, nil, err
}
return pubKey, rawDecoded, nil
}
func (local *PublicKeyLocalStorage) GetPublicKeys() ([]string, error) {
cosignDir, err := local.GetCosignDirPath()
if err != nil {
return []string{}, err
}
files, err := os.ReadDir(cosignDir)
if err != nil {
return []string{}, err
}
publicKeys := []string{}
for _, file := range files {
publicKeys = append(publicKeys, file.Name())
}
return publicKeys, nil
}
func (cloud *PublicKeyCloudStorage) GetPublicKeys() ([]string, error) {
ctx := context.Background()
listSecretsInput := secretsmanager.ListSecretsInput{
Filters: []types.Filter{
{
Key: types.FilterNameStringTypeDescription,
Values: []string{"cosign public key"},
},
},
}
secrets, err := cloud.secretsManagerClient.ListSecrets(ctx, &listSecretsInput)
if err != nil {
return []string{}, err
}
publicKeys := []string{}
for _, secret := range secrets.SecretList {
publicKeys = append(publicKeys, *(secret.Name))
}
return publicKeys, nil
}
func UploadPublicKey(cosignStorage publicKeyStorage, publicKeyContent []byte) error {
// validate public key
if ok, err := validatePublicKey(publicKeyContent); !ok {
return err
}
name := godigest.FromBytes(publicKeyContent)
return cosignStorage.StorePublicKey(name, publicKeyContent)
}
func (local *PublicKeyLocalStorage) StorePublicKey(name godigest.Digest, publicKeyContent []byte) error {
// add public key to "{rootDir}/_cosign/{name.pub}"
configDir, err := GetCosignDirPath()
cosignDir, err := local.GetCosignDirPath()
if err != nil {
return err
}
name := godigest.FromBytes(publicKeyContent)
// store public key
publicKeyPath := path.Join(configDir, name.String())
publicKeyPath := path.Join(cosignDir, name.String())
return os.WriteFile(publicKeyPath, publicKeyContent, defaultFilePerms)
}
func (cloud *PublicKeyCloudStorage) StorePublicKey(name godigest.Digest, publicKeyContent []byte) error {
n := name.Encoded()
description := "cosign public key"
secret := base64.StdEncoding.EncodeToString(publicKeyContent)
secretInputParam := &secretsmanager.CreateSecretInput{
Name: &n,
Description: &description,
SecretString: &secret,
}
_, err := cloud.secretsManagerClient.CreateSecret(context.Background(), secretInputParam)
if err != nil {
return err
}
return nil
}
func validatePublicKey(publicKeyContent []byte) (bool, error) {
_, err := cryptoutils.UnmarshalPEMToPublicKey(publicKeyContent)
if err != nil {

View file

@ -8,6 +8,14 @@ import (
"encoding/json"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
aws1 "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/session"
smanager "github.com/aws/aws-sdk-go/service/secretsmanager"
"github.com/aws/aws-secretsmanager-caching-go/secretcache"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
@ -23,18 +31,106 @@ const (
defaultFilePerms = 0o644
)
func InitCosignAndNotationDirs(rootDir string) error {
err := InitCosignDir(rootDir)
if err != nil {
return err
}
err = InitNotationDir(rootDir)
return err
type ImageTrustStore struct {
CosignStorage publicKeyStorage
NotationStorage certificateStorage
}
func VerifySignature(
func NewLocalImageTrustStore(rootDir string) (*ImageTrustStore, error) {
publicKeyStorage, err := NewPublicKeyLocalStorage(rootDir)
if err != nil {
return nil, err
}
certStorage, err := NewCertificateLocalStorage(rootDir)
if err != nil {
return nil, err
}
return &ImageTrustStore{
CosignStorage: publicKeyStorage,
NotationStorage: certStorage,
}, nil
}
func NewCloudImageTrustStore(region, endpoint string) (*ImageTrustStore, error) {
secretsManagerClient, err := GetSecretsManagerClient(region, endpoint)
if err != nil {
return nil, err
}
secretsManagerCache, err := GetSecretsManagerRetrieval(region, endpoint)
if err != nil {
return nil, err
}
publicKeyStorage := NewPublicKeyCloudStorage(secretsManagerClient, secretsManagerCache)
certStorage, err := NewCertificateCloudStorage(secretsManagerClient, secretsManagerCache)
if err != nil {
return nil, err
}
return &ImageTrustStore{
CosignStorage: publicKeyStorage,
NotationStorage: certStorage,
}, nil
}
func GetSecretsManagerClient(region, endpoint string) (*secretsmanager.Client, error) {
customResolver := aws.EndpointResolverWithOptionsFunc(
func(service, region string, options ...interface{}) (aws.Endpoint, error) {
return aws.Endpoint{
PartitionID: "aws",
URL: endpoint,
SigningRegion: region,
}, nil
})
// Using the SDK's default configuration, loading additional config
// and credentials values from the environment variables, shared
// credentials, and shared configuration files
cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(region),
config.WithEndpointResolverWithOptions(customResolver))
if err != nil {
return nil, err
}
return secretsmanager.NewFromConfig(cfg), nil
}
func GetSecretsManagerRetrieval(region, endpoint string) (*secretcache.Cache, error) {
endpointFunc := func(service, region string, optFns ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) {
return endpoints.ResolvedEndpoint{
PartitionID: "aws",
URL: endpoint,
SigningRegion: region,
}, nil
}
customResolver := endpoints.ResolverFunc(endpointFunc)
cfg := aws1.NewConfig().WithRegion(region).WithEndpointResolver(customResolver)
newSession := session.Must(session.NewSession())
client := smanager.New(newSession, cfg)
// Create a custom CacheConfig struct
config := secretcache.CacheConfig{
MaxCacheSize: secretcache.DefaultMaxCacheSize,
VersionStage: secretcache.DefaultVersionStage,
CacheItemTTL: secretcache.DefaultCacheItemTTL,
}
// Instantiate the cache
cache, _ := secretcache.New(
func(c *secretcache.Cache) { c.CacheConfig = config },
func(c *secretcache.Cache) { c.Client = client },
)
return cache, nil
}
func (imgTrustStore *ImageTrustStore) VerifySignature(
signatureType string, rawSignature []byte, sigKey string, manifestDigest godigest.Digest, manifestContent []byte,
repo string,
) (string, time.Time, bool, error) {
@ -55,11 +151,11 @@ func VerifySignature(
switch signatureType {
case zcommon.CosignSignature:
author, isValid, err := VerifyCosignSignature(repo, manifestDigest, sigKey, rawSignature)
author, isValid, err := VerifyCosignSignature(imgTrustStore.CosignStorage, repo, manifestDigest, sigKey, rawSignature)
return author, time.Time{}, isValid, err
case zcommon.NotationSignature:
return VerifyNotationSignature(desc, manifestDigest.String(), rawSignature, sigKey)
return VerifyNotationSignature(imgTrustStore.NotationStorage, desc, manifestDigest.String(), rawSignature, sigKey)
default:
return "", time.Time{}, false, zerr.ErrInvalidSignatureType
}

View file

@ -9,19 +9,17 @@ import (
godigest "github.com/opencontainers/go-digest"
)
func InitCosignAndNotationDirs(rootDir string) error {
return nil
func NewLocalImageTrustStore(dir string) (*imageTrustDisabled, error) {
return &imageTrustDisabled{}, nil
}
func InitCosignDir(rootDir string) error {
return nil
func NewCloudImageTrustStore(region, endpoint string) (*imageTrustDisabled, error) {
return &imageTrustDisabled{}, nil
}
func InitNotationDir(rootDir string) error {
return nil
}
type imageTrustDisabled struct{}
func VerifySignature(
func (imgTrustStore *imageTrustDisabled) VerifySignature(
signatureType string, rawSignature []byte, sigKey string, manifestDigest godigest.Digest, manifestContent []byte,
repo string,
) (string, time.Time, bool, error) {

View file

@ -3,6 +3,7 @@
package imagetrust_test
import (
"encoding/json"
"os"
"path"
"testing"
@ -10,35 +11,56 @@ import (
. "github.com/smartystreets/goconvey/convey"
"zotregistry.io/zot/pkg/extensions/imagetrust"
"zotregistry.io/zot/pkg/test"
)
func TestImageTrust(t *testing.T) {
Convey("binary doesn't include imagetrust", t, func() {
rootDir := t.TempDir()
err := imagetrust.InitCosignDir(rootDir)
So(err, ShouldBeNil)
cosignDir := path.Join(rootDir, "_cosign")
_, err = os.Stat(cosignDir)
_, err := os.Stat(cosignDir)
So(os.IsNotExist(err), ShouldBeTrue)
err = imagetrust.InitNotationDir(rootDir)
So(err, ShouldBeNil)
notationDir := path.Join(rootDir, "_notation")
_, err = os.Stat(notationDir)
So(os.IsNotExist(err), ShouldBeTrue)
err = imagetrust.InitCosignAndNotationDirs(rootDir)
repo := "repo"
image, err := test.GetRandomImage() //nolint:staticcheck
So(err, ShouldBeNil)
manifestContent, err := json.Marshal(image.Manifest)
So(err, ShouldBeNil)
manifestDigest := image.Digest()
localImgTrustStore, err := imagetrust.NewLocalImageTrustStore(rootDir)
So(err, ShouldBeNil)
author, expTime, ok, err := localImgTrustStore.VerifySignature("cosign",
[]byte(""), "", manifestDigest, manifestContent, repo,
)
So(author, ShouldBeEmpty)
So(expTime, ShouldBeZeroValue)
So(ok, ShouldBeFalse)
So(err, ShouldBeNil)
_, err = os.Stat(cosignDir)
So(os.IsNotExist(err), ShouldBeTrue)
_, err = os.Stat(notationDir)
So(os.IsNotExist(err), ShouldBeTrue)
author, expTime, ok, err := imagetrust.VerifySignature("", []byte{}, "", "", []byte{}, "")
cloudImgTrustStore, err := imagetrust.NewCloudImageTrustStore("region",
"endpoint",
)
So(err, ShouldBeNil)
author, expTime, ok, err = cloudImgTrustStore.VerifySignature("cosign",
[]byte(""), "", manifestDigest, manifestContent, repo,
)
So(author, ShouldBeEmpty)
So(expTime, ShouldBeZeroValue)
So(ok, ShouldBeFalse)

View file

@ -8,23 +8,30 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"path"
"testing"
"time"
guuid "github.com/gofrs/uuid"
"github.com/notaryproject/notation-go"
notreg "github.com/notaryproject/notation-go/registry"
"github.com/notaryproject/notation-go/verifier/trustpolicy"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/generate"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/resty.v1"
zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/api"
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/api/constants"
zcommon "zotregistry.io/zot/pkg/common"
extconf "zotregistry.io/zot/pkg/extensions/config"
"zotregistry.io/zot/pkg/extensions/imagetrust"
"zotregistry.io/zot/pkg/test"
)
@ -37,16 +44,17 @@ func TestInitCosignAndNotationDirs(t *testing.T) {
err := os.Chmod(dir, 0o000)
So(err, ShouldBeNil)
err = imagetrust.InitCosignAndNotationDirs(dir)
_, err = imagetrust.NewPublicKeyLocalStorage(dir)
So(err, ShouldNotBeNil)
err = os.Chmod(dir, 0o500)
So(err, ShouldBeNil)
err = imagetrust.InitCosignAndNotationDirs(dir)
_, err = imagetrust.NewPublicKeyLocalStorage(dir)
So(err, ShouldNotBeNil)
cosignDir, err := imagetrust.GetCosignDirPath()
pubKeyStorage := &imagetrust.PublicKeyLocalStorage{}
cosignDir, err := pubKeyStorage.GetCosignDirPath()
So(cosignDir, ShouldBeEmpty)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
@ -57,22 +65,23 @@ func TestInitCosignAndNotationDirs(t *testing.T) {
err := os.Chmod(dir, 0o000)
So(err, ShouldBeNil)
err = imagetrust.InitCosignAndNotationDirs(dir)
_, err = imagetrust.NewPublicKeyLocalStorage(dir)
So(err, ShouldNotBeNil)
err = imagetrust.InitNotationDir(dir)
_, err = imagetrust.NewCertificateLocalStorage(dir)
So(err, ShouldNotBeNil)
err = os.Chmod(dir, 0o500)
So(err, ShouldBeNil)
err = imagetrust.InitCosignAndNotationDirs(dir)
_, err = imagetrust.NewPublicKeyLocalStorage(dir)
So(err, ShouldNotBeNil)
err = imagetrust.InitNotationDir(dir)
_, err = imagetrust.NewCertificateLocalStorage(dir)
So(err, ShouldNotBeNil)
notationDir, err := imagetrust.GetNotationDirPath()
certStorage := &imagetrust.CertificateLocalStorage{}
notationDir, err := certStorage.GetNotationDirPath()
So(notationDir, ShouldBeEmpty)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
@ -94,7 +103,8 @@ func TestInitCosignAndNotationDirs(t *testing.T) {
So(err, ShouldBeNil)
So(certificateContent, ShouldNotBeNil)
err = imagetrust.UploadCertificate(certificateContent, "ca", "notation-upload-test")
certStorgae := &imagetrust.CertificateLocalStorage{}
err = imagetrust.UploadCertificate(certStorgae, certificateContent, "ca", "notation-upload-test")
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
})
@ -118,7 +128,8 @@ func TestInitCosignAndNotationDirs(t *testing.T) {
So(err, ShouldBeNil)
So(publicKeyContent, ShouldNotBeNil)
err = imagetrust.UploadPublicKey(publicKeyContent)
pubKeyStorage := &imagetrust.PublicKeyLocalStorage{}
err = imagetrust.UploadPublicKey(pubKeyStorage, publicKeyContent)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
})
@ -128,7 +139,8 @@ func TestVerifySignatures(t *testing.T) {
Convey("wrong manifest content", t, func() {
manifestContent := []byte("wrong json")
_, _, _, err := imagetrust.VerifySignature("", []byte(""), "", "", manifestContent, "repo")
imgTrustStore := &imagetrust.ImageTrustStore{}
_, _, _, err := imgTrustStore.VerifySignature("", []byte(""), "", "", manifestContent, "repo")
So(err, ShouldNotBeNil)
})
@ -139,7 +151,8 @@ func TestVerifySignatures(t *testing.T) {
manifestContent, err := json.Marshal(image.Manifest)
So(err, ShouldBeNil)
_, _, _, err = imagetrust.VerifySignature("", []byte(""), "", "", manifestContent, "repo")
imgTrustStore := &imagetrust.ImageTrustStore{}
_, _, _, err = imgTrustStore.VerifySignature("", []byte(""), "", "", manifestContent, "repo")
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrBadManifestDigest)
})
@ -153,14 +166,15 @@ func TestVerifySignatures(t *testing.T) {
manifestDigest := image.Digest()
_, _, _, err = imagetrust.VerifySignature("wrongType", []byte(""), "", manifestDigest, manifestContent, "repo")
imgTrustStore := &imagetrust.ImageTrustStore{}
_, _, _, err = imgTrustStore.VerifySignature("wrongType", []byte(""), "", manifestDigest, manifestContent, "repo")
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrInvalidSignatureType)
})
Convey("verify cosign signature", t, func() {
repo := "repo"
tag := "test"
repo := "repo" //nolint:goconst
tag := "test" //nolint:goconst
image, err := test.GetRandomImage() //nolint:staticcheck
So(err, ShouldBeNil)
@ -170,7 +184,11 @@ func TestVerifySignatures(t *testing.T) {
manifestDigest := image.Digest()
Convey("cosignDir is not set", func() {
_, _, _, err = imagetrust.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo)
imgTrustStore := &imagetrust.ImageTrustStore{
CosignStorage: &imagetrust.PublicKeyLocalStorage{},
}
_, _, _, err = imgTrustStore.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
})
@ -178,31 +196,40 @@ func TestVerifySignatures(t *testing.T) {
Convey("cosignDir does not have read permissions", func() {
dir := t.TempDir()
err := imagetrust.InitCosignDir(dir)
pubKeyStorage, err := imagetrust.NewPublicKeyLocalStorage(dir)
So(err, ShouldBeNil)
cosignDir, err := imagetrust.GetCosignDirPath()
cosignDir, err := pubKeyStorage.GetCosignDirPath()
So(err, ShouldBeNil)
err = os.Chmod(cosignDir, 0o300)
So(err, ShouldBeNil)
_, _, _, err = imagetrust.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo)
imgTrustStore := &imagetrust.ImageTrustStore{
CosignStorage: pubKeyStorage,
}
_, _, _, err = imgTrustStore.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo)
So(err, ShouldNotBeNil)
})
Convey("no valid public key", func() {
dir := t.TempDir()
err := imagetrust.InitCosignDir(dir)
pubKeyStorage, err := imagetrust.NewPublicKeyLocalStorage(dir)
So(err, ShouldBeNil)
cosignDir, err := imagetrust.GetCosignDirPath()
cosignDir, err := pubKeyStorage.GetCosignDirPath()
So(err, ShouldBeNil)
err = test.WriteFileWithPermission(path.Join(cosignDir, "file"), []byte("not a public key"), 0o600, false)
So(err, ShouldBeNil)
_, _, isTrusted, err := imagetrust.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo)
imgTrustStore := &imagetrust.ImageTrustStore{
CosignStorage: pubKeyStorage,
}
_, _, isTrusted, err := imgTrustStore.VerifySignature("cosign", []byte(""), "", manifestDigest,
manifestContent, repo)
So(err, ShouldBeNil)
So(isTrusted, ShouldBeFalse)
})
@ -225,10 +252,10 @@ func TestVerifySignatures(t *testing.T) {
err := test.UploadImage(image, baseURL, repo, tag)
So(err, ShouldBeNil)
err = imagetrust.InitCosignDir(rootDir)
pubKeyStorage, err := imagetrust.NewPublicKeyLocalStorage(rootDir)
So(err, ShouldBeNil)
cosignDir, err := imagetrust.GetCosignDirPath()
cosignDir, err := pubKeyStorage.GetCosignDirPath()
So(err, ShouldBeNil)
cwd, err := os.Getwd()
@ -284,8 +311,12 @@ func TestVerifySignatures(t *testing.T) {
}
}
imgTrustStore := &imagetrust.ImageTrustStore{
CosignStorage: pubKeyStorage,
}
// signature is trusted
author, _, isTrusted, err := imagetrust.VerifySignature("cosign", rawSignature, sigKey, manifestDigest,
author, _, isTrusted, err := imgTrustStore.VerifySignature("cosign", rawSignature, sigKey, manifestDigest,
manifestContent, repo)
So(err, ShouldBeNil)
So(isTrusted, ShouldBeTrue)
@ -294,8 +325,8 @@ func TestVerifySignatures(t *testing.T) {
})
Convey("verify notation signature", t, func() {
repo := "repo"
tag := "test"
repo := "repo" //nolint:goconst
tag := "test" //nolint:goconst
image, err := test.GetRandomImage() //nolint:staticcheck
So(err, ShouldBeNil)
@ -305,7 +336,12 @@ func TestVerifySignatures(t *testing.T) {
manifestDigest := image.Digest()
Convey("notationDir is not set", func() {
_, _, _, err = imagetrust.VerifySignature("notation", []byte("signature"), "", manifestDigest, manifestContent, repo)
imgTrustStore := &imagetrust.ImageTrustStore{
NotationStorage: &imagetrust.CertificateLocalStorage{},
}
_, _, _, err = imgTrustStore.VerifySignature("notation", []byte("signature"), "", manifestDigest,
manifestContent, repo)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
})
@ -313,10 +349,15 @@ func TestVerifySignatures(t *testing.T) {
Convey("no signature provided", func() {
dir := t.TempDir()
err := imagetrust.InitNotationDir(dir)
certStorage, err := imagetrust.NewCertificateLocalStorage(dir)
So(err, ShouldBeNil)
_, _, isTrusted, err := imagetrust.VerifySignature("notation", []byte(""), "", manifestDigest, manifestContent, repo)
imgTrustStore := &imagetrust.ImageTrustStore{
NotationStorage: certStorage,
}
_, _, isTrusted, err := imgTrustStore.VerifySignature("notation", []byte(""), "", manifestDigest,
manifestContent, repo)
So(err, ShouldNotBeNil)
So(isTrusted, ShouldBeFalse)
})
@ -324,32 +365,41 @@ func TestVerifySignatures(t *testing.T) {
Convey("trustpolicy.json does not exist", func() {
dir := t.TempDir()
err := imagetrust.InitNotationDir(dir)
certStorage, err := imagetrust.NewCertificateLocalStorage(dir)
So(err, ShouldBeNil)
notationDir, _ := imagetrust.GetNotationDirPath()
notationDir, _ := certStorage.GetNotationDirPath()
err = os.Remove(path.Join(notationDir, "trustpolicy.json"))
So(err, ShouldBeNil)
_, _, _, err = imagetrust.VerifySignature("notation", []byte("signature"), "", manifestDigest, manifestContent, repo)
imgTrustStore := &imagetrust.ImageTrustStore{
NotationStorage: certStorage,
}
_, _, _, err = imgTrustStore.VerifySignature("notation", []byte("signature"), "", manifestDigest,
manifestContent, repo)
So(err, ShouldNotBeNil)
})
Convey("trustpolicy.json has invalid content", func() {
dir := t.TempDir()
err := imagetrust.InitNotationDir(dir)
certStorage, err := imagetrust.NewCertificateLocalStorage(dir)
So(err, ShouldBeNil)
notationDir, err := imagetrust.GetNotationDirPath()
notationDir, err := certStorage.GetNotationDirPath()
So(err, ShouldBeNil)
err = test.WriteFileWithPermission(path.Join(notationDir, "trustpolicy.json"), []byte("invalid content"),
0o600, true)
So(err, ShouldBeNil)
_, _, _, err = imagetrust.VerifySignature("notation", []byte("signature"), "", manifestDigest, manifestContent,
imgTrustStore := &imagetrust.ImageTrustStore{
NotationStorage: certStorage,
}
_, _, _, err = imgTrustStore.VerifySignature("notation", []byte("signature"), "", manifestDigest, manifestContent,
repo)
So(err, ShouldNotBeNil)
})
@ -372,10 +422,10 @@ func TestVerifySignatures(t *testing.T) {
err := test.UploadImage(image, baseURL, repo, tag)
So(err, ShouldBeNil)
err = imagetrust.InitNotationDir(rootDir)
certStorage, err := imagetrust.NewCertificateLocalStorage(rootDir)
So(err, ShouldBeNil)
notationDir, err := imagetrust.GetNotationDirPath()
notationDir, err := certStorage.GetNotationDirPath()
So(err, ShouldBeNil)
test.NotationPathLock.Lock()
@ -447,8 +497,12 @@ func TestVerifySignatures(t *testing.T) {
}
}
imgTrustStore := &imagetrust.ImageTrustStore{
NotationStorage: certStorage,
}
// signature is trusted
author, _, isTrusted, err := imagetrust.VerifySignature("notation", rawSignature, sigKey, manifestDigest,
author, _, isTrusted, err := imgTrustStore.VerifySignature("notation", rawSignature, sigKey, manifestDigest,
manifestContent, repo)
So(err, ShouldBeNil)
So(isTrusted, ShouldBeTrue)
@ -458,7 +512,7 @@ func TestVerifySignatures(t *testing.T) {
So(err, ShouldBeNil)
// signature is not trusted
author, _, isTrusted, err = imagetrust.VerifySignature("notation", rawSignature, sigKey, manifestDigest,
author, _, isTrusted, err = imgTrustStore.VerifySignature("notation", rawSignature, sigKey, manifestDigest,
manifestContent, repo)
So(err, ShouldNotBeNil)
So(isTrusted, ShouldBeFalse)
@ -492,69 +546,7 @@ func TestCheckExpiryErr(t *testing.T) {
})
}
func TestUploadPublicKey(t *testing.T) {
Convey("public key - invalid content", t, func() {
err := imagetrust.UploadPublicKey([]byte("wrong content"))
So(err, ShouldNotBeNil)
})
Convey("upload public key successfully", t, func() {
rootDir := t.TempDir()
cwd, err := os.Getwd()
So(err, ShouldBeNil)
_ = os.Chdir(rootDir)
// generate a keypair
os.Setenv("COSIGN_PASSWORD", "")
err = generate.GenerateKeyPairCmd(context.TODO(), "", "cosign", nil)
So(err, ShouldBeNil)
_ = os.Chdir(cwd)
publicKeyContent, err := os.ReadFile(path.Join(rootDir, "cosign.pub"))
So(err, ShouldBeNil)
So(publicKeyContent, ShouldNotBeNil)
err = imagetrust.InitCosignDir(rootDir)
So(err, ShouldBeNil)
err = imagetrust.UploadPublicKey(publicKeyContent)
So(err, ShouldBeNil)
})
}
func TestUploadCertificate(t *testing.T) {
Convey("invalid truststore type", t, func() {
err := imagetrust.UploadCertificate([]byte("certificate content"), "wrongType", "store")
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrInvalidTruststoreType)
})
Convey("invalid truststore name", t, func() {
err := imagetrust.UploadCertificate([]byte("certificate content"), "ca", "*store?")
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrInvalidTruststoreName)
})
Convey("invalid certificate content", t, func() {
err := imagetrust.UploadCertificate([]byte("invalid content"), "ca", "store")
So(err, ShouldNotBeNil)
content := `-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
`
err = imagetrust.UploadCertificate([]byte(content), "ca", "store")
So(err, ShouldNotBeNil)
content = ``
err = imagetrust.UploadCertificate([]byte(content), "ca", "store")
So(err, ShouldNotBeNil)
})
func TestLocalTrustStoreUploadErr(t *testing.T) {
Convey("truststore dir can not be created", t, func() {
rootDir := t.TempDir()
@ -571,16 +563,16 @@ func TestUploadCertificate(t *testing.T) {
So(err, ShouldBeNil)
So(certificateContent, ShouldNotBeNil)
err = imagetrust.InitNotationDir(rootDir)
certStorage, err := imagetrust.NewCertificateLocalStorage(rootDir)
So(err, ShouldBeNil)
notationDir, err := imagetrust.GetNotationDirPath()
notationDir, err := certStorage.GetNotationDirPath()
So(err, ShouldBeNil)
err = os.Chmod(notationDir, 0o100)
So(err, ShouldBeNil)
err = imagetrust.UploadCertificate(certificateContent, "ca", "notation-upload-test")
err = imagetrust.UploadCertificate(certStorage, certificateContent, "ca", "notation-upload-test")
So(err, ShouldNotBeNil)
err = os.Chmod(notationDir, 0o777)
@ -603,10 +595,10 @@ func TestUploadCertificate(t *testing.T) {
So(err, ShouldBeNil)
So(certificateContent, ShouldNotBeNil)
err = imagetrust.InitNotationDir(rootDir)
certStorage, err := imagetrust.NewCertificateLocalStorage(rootDir)
So(err, ShouldBeNil)
notationDir, err := imagetrust.GetNotationDirPath()
notationDir, err := certStorage.GetNotationDirPath()
So(err, ShouldBeNil)
err = os.MkdirAll(path.Join(notationDir, "truststore/x509/ca/notation-upload-test"), 0o777)
@ -615,7 +607,7 @@ func TestUploadCertificate(t *testing.T) {
err = os.Chmod(path.Join(notationDir, "truststore/x509/ca/notation-upload-test"), 0o100)
So(err, ShouldBeNil)
err = imagetrust.UploadCertificate(certificateContent, "ca", "notation-upload-test")
err = imagetrust.UploadCertificate(certStorage, certificateContent, "ca", "notation-upload-test")
So(err, ShouldNotBeNil)
})
@ -635,17 +627,17 @@ func TestUploadCertificate(t *testing.T) {
So(err, ShouldBeNil)
So(certificateContent, ShouldNotBeNil)
err = imagetrust.InitNotationDir(rootDir)
certStorage, err := imagetrust.NewCertificateLocalStorage(rootDir)
So(err, ShouldBeNil)
notationDir, err := imagetrust.GetNotationDirPath()
notationDir, err := certStorage.GetNotationDirPath()
So(err, ShouldBeNil)
err = test.WriteFileWithPermission(path.Join(notationDir, "trustpolicy.json"), []byte("invalid content"),
0o600, true)
So(err, ShouldBeNil)
err = imagetrust.UploadCertificate(certificateContent, "ca", "notation-upload-test")
err = imagetrust.UploadCertificate(certStorage, certificateContent, "ca", "notation-upload-test")
So(err, ShouldNotBeNil)
})
@ -665,13 +657,13 @@ func TestUploadCertificate(t *testing.T) {
So(err, ShouldBeNil)
So(certificateContent, ShouldNotBeNil)
err = imagetrust.InitNotationDir(rootDir)
certStorage, err := imagetrust.NewCertificateLocalStorage(rootDir)
So(err, ShouldBeNil)
notationDir, err := imagetrust.GetNotationDirPath()
notationDir, err := certStorage.GetNotationDirPath()
So(err, ShouldBeNil)
trustpolicyDoc, err := imagetrust.LoadTrustPolicyDocument(notationDir)
trustpolicyDoc, err := certStorage.LoadTrustPolicyDocument()
So(err, ShouldBeNil)
trustpolicyDoc.TrustPolicies[0].TrustStores = append(trustpolicyDoc.TrustPolicies[0].TrustStores,
@ -683,30 +675,382 @@ func TestUploadCertificate(t *testing.T) {
err = os.WriteFile(path.Join(notationDir, "trustpolicy.json"), trustpolicyDocContent, 0o400)
So(err, ShouldBeNil)
err = imagetrust.UploadCertificate(certificateContent, "ca", "notation-upload-test")
err = imagetrust.UploadCertificate(certStorage, certificateContent, "ca", "notation-upload-test")
So(err, ShouldBeNil)
})
}
func TestLocalTrustStore(t *testing.T) {
Convey("test with local storage", t, func() {
rootDir := t.TempDir()
imageTrustStore, err := imagetrust.NewLocalImageTrustStore(rootDir)
So(err, ShouldBeNil)
var dbDriverParams map[string]interface{}
RunUploadTests(t, *imageTrustStore)
RunVerificationTests(t, dbDriverParams)
})
}
func TestAWSTrustStore(t *testing.T) {
skipIt(t)
Convey("test with AWS storage", t, func() {
uuid, err := guuid.NewV4()
if err != nil {
panic(err)
}
repoMetaTablename := "RepoMetadataTable" + uuid.String()
manifestDataTablename := "ManifestDataTable" + uuid.String()
versionTablename := "Version" + uuid.String()
indexDataTablename := "IndexDataTable" + uuid.String()
userDataTablename := "UserDataTable" + uuid.String()
apiKeyTablename := "ApiKeyTable" + uuid.String()
dynamoDBDriverParams := map[string]interface{}{
"name": "dynamoDB",
"endpoint": os.Getenv("DYNAMODBMOCK_ENDPOINT"),
"region": "us-east-2",
"repoMetaTablename": repoMetaTablename,
"manifestDataTablename": manifestDataTablename,
"indexDataTablename": indexDataTablename,
"userDataTablename": userDataTablename,
"apiKeyTablename": apiKeyTablename,
"versionTablename": versionTablename,
}
t.Logf("using dynamo driver options: %v", dynamoDBDriverParams)
imageTrustStore, err := imagetrust.NewCloudImageTrustStore(
"us-east-2",
os.Getenv("DYNAMODBMOCK_ENDPOINT"),
)
So(err, ShouldBeNil)
RunUploadTests(t, *imageTrustStore)
RunVerificationTests(t, dynamoDBDriverParams)
})
}
func RunUploadTests(t *testing.T, imageTrustStore imagetrust.ImageTrustStore) { //nolint: thelper
cosignStorage := imageTrustStore.CosignStorage
notationStorage := imageTrustStore.NotationStorage
Convey("public key - invalid content", func() {
err := imagetrust.UploadPublicKey(cosignStorage, []byte("wrong content"))
So(err, ShouldNotBeNil)
})
Convey("upload public key successfully", func() {
cwd, err := os.Getwd()
So(err, ShouldBeNil)
keyDir := t.TempDir()
_ = os.Chdir(keyDir)
// generate a keypair
os.Setenv("COSIGN_PASSWORD", "")
err = generate.GenerateKeyPairCmd(context.TODO(), "", "cosign", nil)
So(err, ShouldBeNil)
_ = os.Chdir(cwd)
publicKeyContent, err := os.ReadFile(path.Join(keyDir, "cosign.pub"))
So(err, ShouldBeNil)
So(publicKeyContent, ShouldNotBeNil)
err = imagetrust.UploadPublicKey(cosignStorage, publicKeyContent)
So(err, ShouldBeNil)
})
Convey("upload certificate successfully", t, func() {
rootDir := t.TempDir()
Convey("invalid truststore type", func() {
err := imagetrust.UploadCertificate(notationStorage,
[]byte("certificate content"), "wrongType", "store",
)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrInvalidTruststoreType)
})
Convey("invalid truststore name", func() {
err := imagetrust.UploadCertificate(notationStorage,
[]byte("certificate content"), "ca", "*store?",
)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrInvalidTruststoreName)
})
Convey("invalid certificate content", func() {
content := "invalid content"
err := imagetrust.UploadCertificate(notationStorage,
[]byte(content), "ca", "store",
)
So(err, ShouldNotBeNil)
content = `-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
`
err = imagetrust.UploadCertificate(notationStorage,
[]byte(content), "ca", "store",
)
So(err, ShouldNotBeNil)
content = ``
err = imagetrust.UploadCertificate(notationStorage,
[]byte(content), "ca", "store",
)
So(err, ShouldNotBeNil)
})
Convey("upload certificate successfully", func() {
certDir := t.TempDir()
test.NotationPathLock.Lock()
defer test.NotationPathLock.Unlock()
test.LoadNotationPath(rootDir)
test.LoadNotationPath(certDir)
// generate a keypair
err := test.GenerateNotationCerts(rootDir, "notation-upload-test")
err := test.GenerateNotationCerts(certDir, "notation-upload-test")
So(err, ShouldBeNil)
certificateContent, err := os.ReadFile(path.Join(rootDir, "notation/localkeys", "notation-upload-test.crt"))
certificateContent, err := os.ReadFile(path.Join(certDir, "notation/localkeys", "notation-upload-test.crt"))
So(err, ShouldBeNil)
So(certificateContent, ShouldNotBeNil)
err = imagetrust.InitNotationDir(rootDir)
So(err, ShouldBeNil)
err = imagetrust.UploadCertificate(certificateContent, "ca", "notation-upload-test")
err = imagetrust.UploadCertificate(notationStorage, certificateContent, "ca", "notation-upload-test")
So(err, ShouldBeNil)
})
}
func RunVerificationTests(t *testing.T, dbDriverParams map[string]interface{}) { //nolint: thelper
Convey("verify signatures are trusted", func() {
defaultValue := true
rootDir := t.TempDir()
logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
So(err, ShouldBeNil)
logPath := logFile.Name()
defer os.Remove(logPath)
writers := io.MultiWriter(os.Stdout, logFile)
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
conf.Storage.GC = false
ctlr := api.NewController(conf)
ctlr.Log.Logger = ctlr.Log.Output(writers)
ctlr.Config.Storage.RootDirectory = rootDir
if dbDriverParams != nil {
conf.Storage.CacheDriver = dbDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Trust = &extconf.ImageTrustConfig{}
conf.Extensions.Trust.Enable = &defaultValue
conf.Extensions.Trust.Cosign = defaultValue
conf.Extensions.Trust.Notation = defaultValue
cm := test.NewControllerManager(ctlr)
cm.StartAndWait(conf.HTTP.Port)
defer cm.StopServer()
repo := "repo" //nolint:goconst
tag := "test" //nolint:goconst
Convey("verify cosign signature is trusted", func() {
image, err := test.GetRandomImage() //nolint:staticcheck
So(err, ShouldBeNil)
manifestContent, err := json.Marshal(image.Manifest)
So(err, ShouldBeNil)
manifestDigest := image.Digest()
err = test.UploadImage(image, baseURL, repo, tag)
So(err, ShouldBeNil)
cwd, err := os.Getwd()
So(err, ShouldBeNil)
keyDir := t.TempDir()
_ = os.Chdir(keyDir)
// generate a keypair
os.Setenv("COSIGN_PASSWORD", "")
err = generate.GenerateKeyPairCmd(context.TODO(), "", "cosign", nil)
So(err, ShouldBeNil)
_ = os.Chdir(cwd)
// sign the image
err = sign.SignCmd(&options.RootOptions{Verbose: true, Timeout: 1 * time.Minute},
options.KeyOpts{KeyRef: path.Join(keyDir, "cosign.key"), PassFunc: generate.GetPass},
options.SignOptions{
Registry: options.RegistryOptions{AllowInsecure: true},
AnnotationOptions: options.AnnotationOptions{Annotations: []string{fmt.Sprintf("tag=%s", tag)}},
Upload: true,
},
[]string{fmt.Sprintf("localhost:%s/%s@%s", port, repo, manifestDigest.String())})
So(err, ShouldBeNil)
indexContent, err := ctlr.StoreController.DefaultStore.GetIndexContent(repo)
So(err, ShouldBeNil)
var index ispec.Index
err = json.Unmarshal(indexContent, &index)
So(err, ShouldBeNil)
var rawSignature []byte
var sigKey string
for _, manifest := range index.Manifests {
if manifest.Digest != manifestDigest {
blobContent, err := ctlr.StoreController.DefaultStore.GetBlobContent(repo, manifest.Digest)
So(err, ShouldBeNil)
var cosignSig ispec.Manifest
err = json.Unmarshal(blobContent, &cosignSig)
So(err, ShouldBeNil)
sigKey = cosignSig.Layers[0].Annotations[zcommon.CosignSigKey]
rawSignature, err = ctlr.StoreController.DefaultStore.GetBlobContent(repo, cosignSig.Layers[0].Digest)
So(err, ShouldBeNil)
}
}
publicKeyContent, err := os.ReadFile(path.Join(keyDir, "cosign.pub"))
So(err, ShouldBeNil)
So(publicKeyContent, ShouldNotBeNil)
// upload the public key
client := resty.New()
resp, err := client.R().SetHeader("Content-type", "application/octet-stream").
SetBody(publicKeyContent).Post(baseURL + constants.FullCosign)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
imageTrustStore := ctlr.MetaDB.ImageTrustStore()
// signature is trusted
author, _, isTrusted, err := imageTrustStore.VerifySignature("cosign", rawSignature, sigKey, manifestDigest,
manifestContent, repo)
So(err, ShouldBeNil)
So(isTrusted, ShouldBeTrue)
So(author, ShouldNotBeEmpty)
})
Convey("verify notation signature is trusted", func() {
image, err := test.GetRandomImage() //nolint:staticcheck
So(err, ShouldBeNil)
manifestContent, err := json.Marshal(image.Manifest)
So(err, ShouldBeNil)
manifestDigest := image.Digest()
err = test.UploadImage(image, baseURL, repo, tag)
So(err, ShouldBeNil)
notationDir := t.TempDir()
test.NotationPathLock.Lock()
defer test.NotationPathLock.Unlock()
test.LoadNotationPath(notationDir)
uuid, err := guuid.NewV4()
So(err, ShouldBeNil)
certName := fmt.Sprintf("notation-sign-test-%s", uuid)
// generate a keypair
err = test.GenerateNotationCerts(notationDir, certName)
So(err, ShouldBeNil)
// sign the image
imageURL := fmt.Sprintf("localhost:%s/%s", port, fmt.Sprintf("%s:%s", repo, tag))
err = test.SignWithNotation(certName, imageURL, notationDir)
So(err, ShouldBeNil)
indexContent, err := ctlr.StoreController.DefaultStore.GetIndexContent(repo)
So(err, ShouldBeNil)
var index ispec.Index
err = json.Unmarshal(indexContent, &index)
So(err, ShouldBeNil)
var rawSignature []byte
var sigKey string
for _, manifest := range index.Manifests {
blobContent, err := ctlr.StoreController.DefaultStore.GetBlobContent(repo, manifest.Digest)
So(err, ShouldBeNil)
var notationSig ispec.Manifest
err = json.Unmarshal(blobContent, &notationSig)
So(err, ShouldBeNil)
t.Logf("Processing manifest %v", notationSig)
if notationSig.Config.MediaType != notreg.ArtifactTypeNotation ||
notationSig.Subject.Digest != manifestDigest {
continue
}
sigKey = notationSig.Layers[0].MediaType
rawSignature, err = ctlr.StoreController.DefaultStore.GetBlobContent(repo, notationSig.Layers[0].Digest)
So(err, ShouldBeNil)
t.Logf("Identified notation signature manifest %v", notationSig)
break
}
So(sigKey, ShouldNotBeEmpty)
certificateContent, err := os.ReadFile(
path.Join(notationDir,
fmt.Sprintf("notation/truststore/x509/ca/%s", certName),
fmt.Sprintf("%s.crt", certName),
),
)
So(err, ShouldBeNil)
So(certificateContent, ShouldNotBeNil)
client := resty.New()
resp, err := client.R().SetHeader("Content-type", "application/octet-stream").
SetQueryParam("truststoreName", certName).
SetBody(certificateContent).Post(baseURL + constants.FullNotation)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
imageTrustStore := ctlr.MetaDB.ImageTrustStore()
// signature is trusted
author, _, isTrusted, err := imageTrustStore.VerifySignature("notation", rawSignature, sigKey, manifestDigest,
manifestContent, repo)
So(err, ShouldBeNil)
So(isTrusted, ShouldBeTrue)
So(author, ShouldEqual, "CN=cert,O=Notary,L=Seattle,ST=WA,C=US")
})
})
}
func skipIt(t *testing.T) {
t.Helper()
if os.Getenv("DYNAMODBMOCK_ENDPOINT") == "" {
t.Skip("Skipping testing without AWS S3 mock server")
}
}

View file

@ -4,8 +4,10 @@
package imagetrust
import (
"bytes"
"context"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
@ -14,9 +16,12 @@ import (
"path"
"path/filepath"
"regexp"
"sync"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager/types"
"github.com/aws/aws-secretsmanager-caching-go/secretcache"
_ "github.com/notaryproject/notation-core-go/signature/jws"
"github.com/notaryproject/notation-go"
"github.com/notaryproject/notation-go/dir"
@ -32,34 +37,71 @@ import (
const notationDirRelativePath = "_notation"
var (
notationDir = "" //nolint:gochecknoglobals
TrustpolicyLock = new(sync.Mutex) //nolint: gochecknoglobals
)
type CertificateLocalStorage struct {
notationDir string
}
func InitNotationDir(rootDir string) error {
type CertificateCloudStorage struct {
secretsManagerClient *secretsmanager.Client
secretsManagerCache *secretcache.Cache
}
type certificateStorage interface {
LoadTrustPolicyDocument() (*trustpolicy.Document, error)
StoreCertificate(certificateContent []byte, truststoreType, truststoreName string) error
UpdateTrustPolicyDocument(trustpolicyDocContent []byte) error
GetVerifier(policyDoc *trustpolicy.Document) (notation.Verifier, error)
InitTrustpolicy(trustpolicy []byte) error
}
func NewCertificateLocalStorage(rootDir string) (*CertificateLocalStorage, error) {
dir := path.Join(rootDir, notationDirRelativePath)
_, err := os.Stat(dir)
if os.IsNotExist(err) {
err = os.MkdirAll(dir, defaultDirPerms)
if err != nil {
return err
return nil, err
}
}
if err == nil {
notationDir = dir
if _, err := LoadTrustPolicyDocument(notationDir); os.IsNotExist(err) {
return InitTrustpolicyFile(notationDir)
}
if err != nil {
return nil, err
}
return err
certStorage := &CertificateLocalStorage{
notationDir: dir,
}
_, err = certStorage.LoadTrustPolicyDocument()
if os.IsNotExist(err) {
if err := InitTrustpolicyFile(certStorage); err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
return certStorage, nil
}
func InitTrustpolicyFile(configDir string) error {
func NewCertificateCloudStorage(
secretsManagerClient *secretsmanager.Client, secretsManagerCache *secretcache.Cache,
) (*CertificateCloudStorage, error) {
certStorage := &CertificateCloudStorage{
secretsManagerClient: secretsManagerClient,
secretsManagerCache: secretsManagerCache,
}
err := InitTrustpolicyFile(certStorage)
if err != nil {
return nil, err
}
return certStorage, nil
}
func InitTrustpolicyFile(notationStorage certificateStorage) error {
// according to https://github.com/notaryproject/notation/blob/main/specs/commandline/verify.md
// the value of signatureVerification.level field from trustpolicy.json file
// could be one of these values: `strict`, `permissive`, `audit` or `skip`
@ -68,40 +110,123 @@ func InitTrustpolicyFile(configDir string) error {
// a certificate that verifies a signature, but that certificate has expired, then the
// signature is not trusted; if this field were set to `permissive` then the
// signature would be trusted)
trustPolicy := `
{
"version": "1.0",
"trustPolicies": [
{
"name": "default-config",
"registryScopes": [ "*" ],
"signatureVerification": {
"level" : "strict"
},
"trustStores": [],
"trustedIdentities": [
"*"
]
}
]
}`
trustPolicy := `{
"version": "1.0",
"trustPolicies": [
{
"name": "default-config",
"registryScopes": [ "*" ],
"signatureVerification": {
"level" : "strict"
},
"trustStores": [],
"trustedIdentities": [
"*"
]
}
]
}`
TrustpolicyLock.Lock()
defer TrustpolicyLock.Unlock()
return os.WriteFile(path.Join(configDir, dir.PathTrustPolicy), []byte(trustPolicy), defaultDirPerms)
return notationStorage.InitTrustpolicy([]byte(trustPolicy))
}
func GetNotationDirPath() (string, error) {
if notationDir != "" {
return notationDir, nil
func (local *CertificateLocalStorage) InitTrustpolicy(trustpolicy []byte) error {
notationDir, err := local.GetNotationDirPath()
if err != nil {
return err
}
return os.WriteFile(path.Join(notationDir, dir.PathTrustPolicy), trustpolicy, defaultDirPerms)
}
func (cloud *CertificateCloudStorage) InitTrustpolicy(trustpolicy []byte) error {
name := "trustpolicy"
description := "notation trustpolicy file"
secret := base64.StdEncoding.EncodeToString(trustpolicy)
secretInputParam := &secretsmanager.CreateSecretInput{
Name: &name,
Description: &description,
SecretString: &secret,
}
_, err := cloud.secretsManagerClient.CreateSecret(context.Background(), secretInputParam)
if err != nil && !strings.Contains(err.Error(), "the secret trustpolicy already exists.") {
return err
}
return nil
}
func (local *CertificateLocalStorage) GetNotationDirPath() (string, error) {
if local.notationDir != "" {
return local.notationDir, nil
}
return "", zerr.ErrSignConfigDirNotSet
}
func (cloud *CertificateCloudStorage) GetCertificates(
ctx context.Context, storeType truststore.Type, namedStore string,
) ([]*x509.Certificate, error) {
certificates := []*x509.Certificate{}
if !validateTruststoreType(string(storeType)) {
return []*x509.Certificate{}, zerr.ErrInvalidTruststoreType
}
if !validateTruststoreName(namedStore) {
return []*x509.Certificate{}, zerr.ErrInvalidTruststoreName
}
listSecretsInput := secretsmanager.ListSecretsInput{
Filters: []types.Filter{
{
Key: types.FilterNameStringTypeName,
Values: []string{path.Join(string(storeType), namedStore)},
},
},
}
secrets, err := cloud.secretsManagerClient.ListSecrets(ctx, &listSecretsInput)
if err != nil {
return []*x509.Certificate{}, err
}
for _, secret := range secrets.SecretList {
// get key
raw, err := cloud.secretsManagerCache.GetSecretString(*(secret.Name))
if err != nil {
return []*x509.Certificate{}, err
}
rawDecoded, err := base64.StdEncoding.DecodeString(raw)
if err != nil {
return []*x509.Certificate{}, err
}
certs, _, err := parseAndValidateCertificateContent(rawDecoded)
if err != nil {
return []*x509.Certificate{}, err
}
err = truststore.ValidateCertificates(certs)
if err != nil {
return []*x509.Certificate{}, err
}
certificates = append(certificates, certs...)
}
return certificates, nil
}
// Equivalent function for trustpolicy.LoadDocument() but using a specific SysFS not the one returned by ConfigFS().
func LoadTrustPolicyDocument(notationDir string) (*trustpolicy.Document, error) {
func (local *CertificateLocalStorage) LoadTrustPolicyDocument() (*trustpolicy.Document, error) {
notationDir, err := local.GetNotationDirPath()
if err != nil {
return nil, err
}
jsonFile, err := dir.NewSysFS(notationDir).Open(dir.PathTrustPolicy)
if err != nil {
return nil, err
@ -119,33 +244,66 @@ func LoadTrustPolicyDocument(notationDir string) (*trustpolicy.Document, error)
return policyDocument, nil
}
func (cloud *CertificateCloudStorage) LoadTrustPolicyDocument() (*trustpolicy.Document, error) {
policyDocument := &trustpolicy.Document{}
raw, err := cloud.secretsManagerCache.GetSecretString("trustpolicy")
if err != nil {
return nil, err
}
rawDecoded, err := base64.StdEncoding.DecodeString(raw)
if err != nil {
return nil, err
}
var buf bytes.Buffer
err = json.Compact(&buf, rawDecoded)
if err != nil {
return nil, err
}
err = json.Unmarshal(buf.Bytes(), policyDocument)
if err != nil {
return nil, err
}
return policyDocument, nil
}
// NewFromConfig returns a verifier based on local file system.
// Equivalent function for verifier.NewFromConfig()
// but using LoadTrustPolicyDocumnt() function instead of trustpolicy.LoadDocument() function.
func NewFromConfig() (notation.Verifier, error) {
notationDir, err := GetNotationDirPath()
if err != nil {
return nil, err
}
func NewFromConfig(notationStorage certificateStorage) (notation.Verifier, error) {
// Load trust policy.
TrustpolicyLock.Lock()
defer TrustpolicyLock.Unlock()
policyDocument, err := LoadTrustPolicyDocument(notationDir)
policyDocument, err := notationStorage.LoadTrustPolicyDocument()
if err != nil {
return nil, err
}
// Load trust store.
return notationStorage.GetVerifier(policyDocument)
}
func (local *CertificateLocalStorage) GetVerifier(policyDoc *trustpolicy.Document) (notation.Verifier, error) {
notationDir, err := local.GetNotationDirPath()
if err != nil {
return nil, err
}
x509TrustStore := truststore.NewX509TrustStore(dir.NewSysFS(notationDir))
return verifier.New(policyDocument, x509TrustStore,
return verifier.New(policyDoc, x509TrustStore,
plugin.NewCLIManager(dir.NewSysFS(path.Join(notationDir, dir.PathPlugins))))
}
func (cloud *CertificateCloudStorage) GetVerifier(policyDoc *trustpolicy.Document) (notation.Verifier, error) {
return verifier.New(policyDoc, cloud,
plugin.NewCLIManager(dir.NewSysFS(path.Join(dir.PathPlugins))))
}
func VerifyNotationSignature(
artifactDescriptor ispec.Descriptor, artifactReference string, rawSignature []byte, signatureMediaType string,
notationStorage certificateStorage, artifactDescriptor ispec.Descriptor, artifactReference string,
rawSignature []byte, signatureMediaType string,
) (string, time.Time, bool, error) {
var (
date time.Time
@ -161,7 +319,7 @@ func VerifyNotationSignature(
}
// Initialize verifier.
verifier, err := NewFromConfig()
verifier, err := NewFromConfig(notationStorage)
if err != nil {
return author, date, false, err
}
@ -219,7 +377,9 @@ func CheckExpiryErr(verificationResults []*notation.ValidationResult, notAfter t
return false
}
func UploadCertificate(certificateContent []byte, truststoreType, truststoreName string) error {
func UploadCertificate(
notationStorage certificateStorage, certificateContent []byte, truststoreType, truststoreName string,
) error {
// validate truststore type
if !validateTruststoreType(truststoreType) {
return zerr.ErrInvalidTruststoreType
@ -231,35 +391,19 @@ func UploadCertificate(certificateContent []byte, truststoreType, truststoreName
}
// validate certificate
if ok, err := validateCertificate(certificateContent); !ok {
if _, ok, err := parseAndValidateCertificateContent(certificateContent); !ok {
return err
}
// add certificate to "{rootDir}/_notation/truststore/x509/{type}/{name}/{name.crt}"
configDir, err := GetNotationDirPath()
if err != nil {
return err
}
name := godigest.FromBytes(certificateContent)
// store certificate
truststorePath := path.Join(configDir, dir.TrustStoreDir, "x509", truststoreType, truststoreName, name.String())
if err := os.MkdirAll(filepath.Dir(truststorePath), defaultDirPerms); err != nil {
return err
}
err = os.WriteFile(truststorePath, certificateContent, defaultFilePerms)
err := notationStorage.StoreCertificate(certificateContent, truststoreType, truststoreName)
if err != nil {
return err
}
// update "trustpolicy.json" file
// add certificate to "trustpolicy.json"
TrustpolicyLock.Lock()
defer TrustpolicyLock.Unlock()
trustpolicyDoc, err := LoadTrustPolicyDocument(configDir)
trustpolicyDoc, err := notationStorage.LoadTrustPolicyDocument()
if err != nil {
return err
}
@ -279,7 +423,67 @@ func UploadCertificate(certificateContent []byte, truststoreType, truststoreName
return err
}
return os.WriteFile(path.Join(configDir, dir.PathTrustPolicy), trustpolicyDocContent, defaultFilePerms)
return notationStorage.UpdateTrustPolicyDocument(trustpolicyDocContent)
}
func (local *CertificateLocalStorage) StoreCertificate(certificateContent []byte,
truststoreType, truststoreName string,
) error {
// add certificate to "{rootDir}/_notation/truststore/x509/{type}/{name}/{name.crt}"
configDir, err := local.GetNotationDirPath()
if err != nil {
return err
}
name := godigest.FromBytes(certificateContent)
// store certificate
truststorePath := path.Join(configDir, dir.TrustStoreDir, "x509", truststoreType, truststoreName, name.String())
if err := os.MkdirAll(filepath.Dir(truststorePath), defaultDirPerms); err != nil {
return err
}
return os.WriteFile(truststorePath, certificateContent, defaultFilePerms)
}
func (local *CertificateLocalStorage) UpdateTrustPolicyDocument(content []byte) error {
configDir, err := local.GetNotationDirPath()
if err != nil {
return err
}
return os.WriteFile(path.Join(configDir, dir.PathTrustPolicy), content, defaultFilePerms)
}
func (cloud *CertificateCloudStorage) StoreCertificate(certificateContent []byte,
truststoreType, truststoreName string,
) error {
name := path.Join(truststoreType, truststoreName, godigest.FromBytes(certificateContent).Encoded())
description := "notation certificate"
secret := base64.StdEncoding.EncodeToString(certificateContent)
secretInputParam := &secretsmanager.CreateSecretInput{
Name: &name,
Description: &description,
SecretString: &secret,
}
_, err := cloud.secretsManagerClient.CreateSecret(context.Background(), secretInputParam)
return err
}
func (cloud *CertificateCloudStorage) UpdateTrustPolicyDocument(content []byte) error {
trustpolicyName := "trustpolicy"
trustpolicySecret := base64.StdEncoding.EncodeToString(content)
trustpolicySecretInputParam := &secretsmanager.UpdateSecretInput{
SecretId: &trustpolicyName,
SecretString: &trustpolicySecret,
}
_, err := cloud.secretsManagerClient.UpdateSecret(context.Background(), trustpolicySecretInputParam)
return err
}
func validateTruststoreType(truststoreType string) bool {
@ -293,11 +497,15 @@ func validateTruststoreType(truststoreType string) bool {
}
func validateTruststoreName(truststoreName string) bool {
if strings.Contains(truststoreName, "..") {
return false
}
return regexp.MustCompile(`^[a-zA-Z0-9_.-]+$`).MatchString(truststoreName)
}
// implementation from https://github.com/notaryproject/notation-core-go/blob/main/x509/cert.go#L20
func validateCertificate(certificateContent []byte) (bool, error) {
func parseAndValidateCertificateContent(certificateContent []byte) ([]*x509.Certificate, bool, error) {
var certs []*x509.Certificate
block, rest := pem.Decode(certificateContent)
@ -305,7 +513,7 @@ func validateCertificate(certificateContent []byte) (bool, error) {
// data may be in DER format
derCerts, err := x509.ParseCertificates(certificateContent)
if err != nil {
return false, fmt.Errorf("%w: %w", zerr.ErrInvalidCertificateContent, err)
return []*x509.Certificate{}, false, fmt.Errorf("%w: %w", zerr.ErrInvalidCertificateContent, err)
}
certs = append(certs, derCerts...)
@ -314,7 +522,7 @@ func validateCertificate(certificateContent []byte) (bool, error) {
for block != nil {
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return false, fmt.Errorf("%w: %w", zerr.ErrInvalidCertificateContent, err)
return []*x509.Certificate{}, false, fmt.Errorf("%w: %w", zerr.ErrInvalidCertificateContent, err)
}
certs = append(certs, cert)
block, rest = pem.Decode(rest)
@ -322,9 +530,9 @@ func validateCertificate(certificateContent []byte) (bool, error) {
}
if len(certs) == 0 {
return false, fmt.Errorf("%w: no valid certificates found in payload",
return []*x509.Certificate{}, false, fmt.Errorf("%w: no valid certificates found in payload",
zerr.ErrInvalidCertificateContent)
}
return true, nil
return certs, true, nil
}

View file

@ -14,7 +14,6 @@ import (
zerr "zotregistry.io/zot/errors"
zcommon "zotregistry.io/zot/pkg/common"
"zotregistry.io/zot/pkg/extensions/imagetrust"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/meta/common"
mTypes "zotregistry.io/zot/pkg/meta/types"
@ -23,9 +22,10 @@ import (
)
type BoltDB struct {
DB *bbolt.DB
Patches []func(DB *bbolt.DB) error
Log log.Logger
DB *bbolt.DB
Patches []func(DB *bbolt.DB) error
imgTrustStore mTypes.ImageTrustStore
Log log.Logger
}
func New(boltDB *bbolt.DB, log log.Logger) (*BoltDB, error) {
@ -72,12 +72,21 @@ func New(boltDB *bbolt.DB, log log.Logger) (*BoltDB, error) {
}
return &BoltDB{
DB: boltDB,
Patches: version.GetBoltDBPatches(),
Log: log,
DB: boltDB,
Patches: version.GetBoltDBPatches(),
imgTrustStore: nil,
Log: log,
}, nil
}
func (bdw *BoltDB) ImageTrustStore() mTypes.ImageTrustStore {
return bdw.imgTrustStore
}
func (bdw *BoltDB) SetImageTrustStore(imgTrustStore mTypes.ImageTrustStore) {
bdw.imgTrustStore = imgTrustStore
}
func (bdw *BoltDB) SetManifestData(manifestDigest godigest.Digest, manifestData mTypes.ManifestData) error {
err := bdw.DB.Update(func(tx *bbolt.Tx) error {
buck := tx.Bucket([]byte(ManifestDataBucket))
@ -721,6 +730,12 @@ func (bdw *BoltDB) IncrementImageDownloads(repo string, reference string) error
func (bdw *BoltDB) UpdateSignaturesValidity(repo string, manifestDigest godigest.Digest) error {
err := bdw.DB.Update(func(transaction *bbolt.Tx) error {
imgTrustStore := bdw.ImageTrustStore()
if imgTrustStore == nil {
return nil
}
// get ManifestData of signed manifest
manifestBuck := transaction.Bucket([]byte(ManifestDataBucket))
mdBlob := manifestBuck.Get([]byte(manifestDigest))
@ -778,8 +793,8 @@ func (bdw *BoltDB) UpdateSignaturesValidity(repo string, manifestDigest godigest
layersInfo := []mTypes.LayerInfo{}
for _, layerInfo := range sigInfo.LayersInfo {
author, date, isTrusted, _ := imagetrust.VerifySignature(sigType, layerInfo.LayerContent, layerInfo.SignatureKey,
manifestDigest, blob, repo)
author, date, isTrusted, _ := imgTrustStore.VerifySignature(sigType, layerInfo.LayerContent,
layerInfo.SignatureKey, manifestDigest, blob, repo)
if isTrusted {
layerInfo.Signer = author

View file

@ -7,6 +7,7 @@ import (
"encoding/json"
"math"
"testing"
"time"
"github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
@ -22,6 +23,15 @@ import (
"zotregistry.io/zot/pkg/test"
)
type imgTrustStore struct{}
func (its imgTrustStore) VerifySignature(
signatureType string, rawSignature []byte, sigKey string, manifestDigest digest.Digest, manifestContent []byte,
repo string,
) (string, time.Time, bool, error) {
return "", time.Time{}, false, nil
}
func TestWrapperErrors(t *testing.T) {
Convey("Errors", t, func() {
tmpDir := t.TempDir()
@ -35,6 +45,8 @@ func TestWrapperErrors(t *testing.T) {
So(boltdbWrapper, ShouldNotBeNil)
So(err, ShouldBeNil)
boltdbWrapper.SetImageTrustStore(imgTrustStore{})
repoMeta := mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{},
Signatures: map[string]mTypes.ManifestSignatures{},

View file

@ -17,7 +17,6 @@ import (
zerr "zotregistry.io/zot/errors"
zcommon "zotregistry.io/zot/pkg/common"
"zotregistry.io/zot/pkg/extensions/imagetrust"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/meta/common"
mTypes "zotregistry.io/zot/pkg/meta/types"
@ -36,10 +35,13 @@ type DynamoDB struct {
UserDataTablename string
VersionTablename string
Patches []func(client *dynamodb.Client, tableNames map[string]string) error
imgTrustStore mTypes.ImageTrustStore
Log log.Logger
}
func New(client *dynamodb.Client, params DBDriverParameters, log log.Logger) (*DynamoDB, error) {
func New(
client *dynamodb.Client, params DBDriverParameters, log log.Logger,
) (*DynamoDB, error) {
dynamoWrapper := DynamoDB{
Client: client,
RepoMetaTablename: params.RepoMetaTablename,
@ -49,6 +51,7 @@ func New(client *dynamodb.Client, params DBDriverParameters, log log.Logger) (*D
UserDataTablename: params.UserDataTablename,
APIKeyTablename: params.APIKeyTablename,
Patches: version.GetDynamoDBPatches(),
imgTrustStore: nil,
Log: log,
}
@ -86,6 +89,14 @@ func New(client *dynamodb.Client, params DBDriverParameters, log log.Logger) (*D
return &dynamoWrapper, nil
}
func (dwr *DynamoDB) ImageTrustStore() mTypes.ImageTrustStore {
return dwr.imgTrustStore
}
func (dwr *DynamoDB) SetImageTrustStore(imgTrustStore mTypes.ImageTrustStore) {
dwr.imgTrustStore = imgTrustStore
}
func (dwr *DynamoDB) SetManifestData(manifestDigest godigest.Digest, manifestData mTypes.ManifestData) error {
mdAttributeValue, err := attributevalue.Marshal(manifestData)
if err != nil {
@ -624,6 +635,12 @@ func (dwr *DynamoDB) IncrementImageDownloads(repo string, reference string) erro
}
func (dwr *DynamoDB) UpdateSignaturesValidity(repo string, manifestDigest godigest.Digest) error {
imgTrustStore := dwr.ImageTrustStore()
if imgTrustStore == nil {
return nil
}
// get ManifestData of signed manifest
var blob []byte
@ -658,7 +675,7 @@ func (dwr *DynamoDB) UpdateSignaturesValidity(repo string, manifestDigest godige
layersInfo := []mTypes.LayerInfo{}
for _, layerInfo := range sigInfo.LayersInfo {
author, date, isTrusted, _ := imagetrust.VerifySignature(sigType, layerInfo.LayerContent, layerInfo.SignatureKey,
author, date, isTrusted, _ := imgTrustStore.VerifySignature(sigType, layerInfo.LayerContent, layerInfo.SignatureKey,
manifestDigest, blob, repo)
if isTrusted {

View file

@ -17,6 +17,7 @@ import (
"github.com/rs/zerolog"
. "github.com/smartystreets/goconvey/convey"
"zotregistry.io/zot/pkg/extensions/imagetrust"
"zotregistry.io/zot/pkg/log"
mdynamodb "zotregistry.io/zot/pkg/meta/dynamodb"
mTypes "zotregistry.io/zot/pkg/meta/types"
@ -164,9 +165,14 @@ func TestWrapperErrors(t *testing.T) {
client, err := mdynamodb.GetDynamoClient(params) //nolint:contextcheck
So(err, ShouldBeNil)
imgTrustStore, err := imagetrust.NewCloudImageTrustStore(params.Region, params.Endpoint)
So(err, ShouldBeNil)
dynamoWrapper, err := mdynamodb.New(client, params, log) //nolint:contextcheck
So(err, ShouldBeNil)
dynamoWrapper.SetImageTrustStore(imgTrustStore)
So(dynamoWrapper.ResetManifestDataTable(), ShouldBeNil) //nolint:contextcheck
So(dynamoWrapper.ResetRepoMetaTable(), ShouldBeNil) //nolint:contextcheck

View file

@ -6,7 +6,6 @@ import (
"zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/extensions/imagetrust"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/meta/boltdb"
mdynamodb "zotregistry.io/zot/pkg/meta/dynamodb"
@ -33,11 +32,6 @@ func New(storageConfig config.StorageConfig, log log.Logger) (mTypes.MetaDB, err
return nil, err
}
err = imagetrust.InitCosignAndNotationDirs(params.RootDir)
if err != nil {
return nil, err
}
return Create("boltdb", driver, params, log) //nolint:contextcheck
}

View file

@ -6,6 +6,7 @@ package meta_test
import (
"context"
"encoding/json"
"fmt"
"math/rand"
"os"
"path"
@ -22,7 +23,6 @@ import (
ispec "github.com/opencontainers/image-spec/specs-go/v1"
. "github.com/smartystreets/goconvey/convey"
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/extensions/imagetrust"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/meta"
@ -43,9 +43,12 @@ const (
func TestBoltDB(t *testing.T) {
Convey("BoltDB creation", t, func() {
boltDBParams := boltdb.DBParameters{}
boltDBParams := boltdb.DBParameters{RootDir: t.TempDir()}
repoDBPath := path.Join(boltDBParams.RootDir, "repo.db")
boltDriver, err := boltdb.GetBoltDriver(boltDBParams)
So(err, ShouldBeNil)
defer os.Remove(repoDBPath)
log := log.NewLogger("debug", "")
@ -53,27 +56,36 @@ func TestBoltDB(t *testing.T) {
So(metaDB, ShouldNotBeNil)
So(err, ShouldBeNil)
err = os.Chmod("repo.db", 0o200)
err = os.Chmod(repoDBPath, 0o200)
So(err, ShouldBeNil)
_, err = boltdb.GetBoltDriver(boltDBParams)
So(err, ShouldNotBeNil)
err = os.Chmod("repo.db", 0o600)
err = os.Chmod(repoDBPath, 0o600)
So(err, ShouldBeNil)
defer os.Remove("repo.db")
})
Convey("BoltDB Wrapper", t, func() {
boltDBParams := boltdb.DBParameters{}
boltDBParams := boltdb.DBParameters{RootDir: t.TempDir()}
boltDriver, err := boltdb.GetBoltDriver(boltDBParams)
So(err, ShouldBeNil)
log := log.NewLogger("debug", "")
imgTrustStore, err := imagetrust.NewLocalImageTrustStore(boltDBParams.RootDir)
So(err, ShouldBeNil)
boltdbWrapper, err := boltdb.New(boltDriver, log)
defer os.Remove("repo.db")
boltdbWrapper.SetImageTrustStore(imgTrustStore)
defer func() {
os.Remove(path.Join(boltDBParams.RootDir, "repo.db"))
os.RemoveAll(path.Join(boltDBParams.RootDir, "_cosign"))
os.RemoveAll(path.Join(boltDBParams.RootDir, "_notation"))
}()
So(boltdbWrapper, ShouldNotBeNil)
So(err, ShouldBeNil)
@ -108,6 +120,8 @@ func TestDynamoDBWrapper(t *testing.T) {
Region: "us-east-2",
}
t.Logf("using dynamo driver options: %v", dynamoDBDriverParams)
dynamoClient, err := mdynamodb.GetDynamoClient(dynamoDBDriverParams)
So(err, ShouldBeNil)
@ -117,6 +131,11 @@ func TestDynamoDBWrapper(t *testing.T) {
So(dynamoDriver, ShouldNotBeNil)
So(err, ShouldBeNil)
imgTrustStore, err := imagetrust.NewCloudImageTrustStore(dynamoDBDriverParams.Region, dynamoDBDriverParams.Endpoint)
So(err, ShouldBeNil)
dynamoDriver.SetImageTrustStore(imgTrustStore)
resetDynamoDBTables := func() error {
err := dynamoDriver.ResetRepoMetaTable()
if err != nil {
@ -1318,7 +1337,6 @@ func RunMetaDBTests(t *testing.T, metaDB mTypes.MetaDB, preparationFuncs ...func
So(repoData.Signatures[string(manifestDigest1)]["cosign"][0].LayersInfo[0].Date,
ShouldBeZeroValue)
})
Convey("trusted signature", func() {
_, _, manifest, _ := test.GetRandomImageComponents(10) //nolint:staticcheck
manifestContent, _ := json.Marshal(manifest)
@ -1344,7 +1362,10 @@ func RunMetaDBTests(t *testing.T, metaDB mTypes.MetaDB, preparationFuncs ...func
}
tdir := t.TempDir()
keyName := "notation-sign-test"
uuid, err := guuid.NewV4()
So(err, ShouldBeNil)
keyName := fmt.Sprintf("notation-sign-test-%s", uuid)
test.NotationPathLock.Lock()
defer test.NotationPathLock.Unlock()
@ -1394,44 +1415,18 @@ func RunMetaDBTests(t *testing.T, metaDB mTypes.MetaDB, preparationFuncs ...func
})
So(err, ShouldBeNil)
err = imagetrust.InitNotationDir(tdir)
certificateContent, err := os.ReadFile(path.Join(
tdir,
"notation/localkeys",
fmt.Sprintf("%s.crt", keyName),
))
So(err, ShouldBeNil)
So(certificateContent, ShouldNotBeNil)
trustpolicyPath := path.Join(tdir, "_notation/trustpolicy.json")
imgTrustStore, ok := metaDB.ImageTrustStore().(*imagetrust.ImageTrustStore)
So(ok, ShouldBeTrue)
trustPolicy := `
{
"version": "1.0",
"trustPolicies": [
{
"name": "notation-sign-test",
"registryScopes": [ "*" ],
"signatureVerification": {
"level" : "strict"
},
"trustStores": ["ca:notation-sign-test"],
"trustedIdentities": [
"*"
]
}
]
}`
file, err := os.Create(trustpolicyPath)
So(err, ShouldBeNil)
defer file.Close()
_, err = file.WriteString(trustPolicy)
So(err, ShouldBeNil)
truststore := "_notation/truststore/x509/ca/notation-sign-test"
truststoreSrc := "notation/truststore/x509/ca/notation-sign-test"
err = os.MkdirAll(path.Join(tdir, truststore), 0o755)
So(err, ShouldBeNil)
err = test.CopyFile(path.Join(tdir, truststoreSrc, "notation-sign-test.crt"),
path.Join(tdir, truststore, "notation-sign-test.crt"))
err = imagetrust.UploadCertificate(imgTrustStore.NotationStorage, certificateContent, "ca", keyName)
So(err, ShouldBeNil)
err = metaDB.UpdateSignaturesValidity(repo, manifestDigest) //nolint:contextcheck
@ -1439,6 +1434,7 @@ func RunMetaDBTests(t *testing.T, metaDB mTypes.MetaDB, preparationFuncs ...func
repoData, err := metaDB.GetRepoMeta(repo)
So(err, ShouldBeNil)
So(repoData.Signatures[string(manifestDigest)]["notation"][0].LayersInfo[0].Signer,
ShouldNotBeEmpty)
So(repoData.Signatures[string(manifestDigest)]["notation"][0].LayersInfo[0].Date,
@ -2721,31 +2717,6 @@ func TestCreateBoltDB(t *testing.T) {
})
}
func TestNew(t *testing.T) {
Convey("InitCosignAndNotationDirs fails", t, func() {
rootDir := t.TempDir()
var storageConfig config.StorageConfig
storageConfig.RootDirectory = rootDir
storageConfig.RemoteCache = false
log := log.NewLogger("debug", "")
_, err := os.Create(path.Join(rootDir, "repo.db"))
So(err, ShouldBeNil)
err = os.Chmod(rootDir, 0o555)
So(err, ShouldBeNil)
newMetaDB, err := meta.New(storageConfig, log)
So(newMetaDB, ShouldBeNil)
So(err, ShouldNotBeNil)
err = os.Chmod(rootDir, 0o777)
So(err, ShouldBeNil)
})
}
func skipDynamo(t *testing.T) {
t.Helper()

View file

@ -121,6 +121,10 @@ type MetaDB interface { //nolint:interfacebloat
[]RepoMetadata, map[string]ManifestMetadata, map[string]IndexData, error)
PatchDB() error
ImageTrustStore() ImageTrustStore
SetImageTrustStore(imgTrustStore ImageTrustStore)
}
type UserDB interface { //nolint:interfacebloat
@ -160,6 +164,13 @@ type UserDB interface { //nolint:interfacebloat
DeleteUserAPIKey(ctx context.Context, id string) error
}
type ImageTrustStore interface {
VerifySignature(
signatureType string, rawSignature []byte, sigKey string, manifestDigest godigest.Digest, manifestContent []byte,
repo string,
) (string, time.Time, bool, error)
}
type ManifestMetadata struct {
ManifestBlob []byte
ConfigBlob []byte

View file

@ -4,6 +4,7 @@ import (
"context"
"errors"
"os"
"path"
"testing"
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
@ -31,7 +32,7 @@ func TestVersioningBoltDB(t *testing.T) {
log := log.NewLogger("debug", "")
boltdbWrapper, err := boltdb.New(boltDriver, log)
defer os.Remove("repo.db")
defer os.Remove(path.Join(boltDBParams.RootDir, "repo.db"))
So(boltdbWrapper, ShouldNotBeNil)
So(err, ShouldBeNil)

View file

@ -106,6 +106,24 @@ type MetaDBMock struct {
DeleteUserAPIKeyFn func(ctx context.Context, id string) error
PatchDBFn func() error
ImageTrustStoreFn func() mTypes.ImageTrustStore
SetImageTrustStoreFn func(mTypes.ImageTrustStore)
}
func (sdm MetaDBMock) ImageTrustStore() mTypes.ImageTrustStore {
if sdm.ImageTrustStoreFn != nil {
return sdm.ImageTrustStoreFn()
}
return nil
}
func (sdm MetaDBMock) SetImageTrustStore(imgTrustStore mTypes.ImageTrustStore) {
if sdm.SetImageTrustStoreFn != nil {
sdm.SetImageTrustStoreFn(imgTrustStore)
}
}
func (sdm MetaDBMock) SetRepoDescription(repo, description string) error {