feat: upload cosign public key and notation certificates to cloud (#1744)

- using secrets manager for storing public keys and certificates
- adding a default truststore for notation verification and upload all certificates to this default truststore
- removig `truststoreName` query param from notation api for uploading certificates


(cherry picked from commit eafcc1a213f9d46fcbb3c5347e49e33af360e2cf)

Signed-off-by: Andreea-Lupu <andreealupu1470@yahoo.com>
This commit is contained in:
Andreea Lupu 2023-09-08 10:03:58 +03:00 committed by GitHub
parent 6115eed4ec
commit 5a3fac40db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1661 additions and 563 deletions

2
go.mod
View File

@ -48,6 +48,8 @@ require (
require ( require (
github.com/aquasecurity/trivy v0.44.1 github.com/aquasecurity/trivy v0.44.1
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.21.5 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.21.5
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.27.0 github.com/containers/image/v5 v5.27.0
github.com/google/go-github/v52 v52.0.0 github.com/google/go-github/v52 v52.0.0
github.com/gorilla/handlers v1.5.1 github.com/gorilla/handlers v1.5.1

10
go.sum
View File

@ -406,6 +406,7 @@ 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/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.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.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.287/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.45.2 h1:hTong9YUklQKqzrGk3WnKABReb5R8GjbG4Y6dEQfjnk= github.com/aws/aws-sdk-go v1.45.2 h1:hTong9YUklQKqzrGk3WnKABReb5R8GjbG4Y6dEQfjnk=
github.com/aws/aws-sdk-go v1.45.2/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go v1.45.2/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.7.1/go.mod h1:L5LuPC1ZgDr2xQS7AmIec/Jlc7O/Y1u2KxJyNVab250=
@ -464,6 +465,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.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o=
github.com/aws/aws-sdk-go-v2/service/kms v1.23.0 h1:NXYeZBNg35rDBhcus60DFkIP7q6RNSkarLx+37ERX1g= 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.3.1/go.mod h1:J3A3RGUvuCZjvSuZEcOpHDnzZP/sKbhDWV2T1EOzFIM=
github.com/aws/aws-sdk-go-v2/service/sso v1.13.5 h1:oCvTFSDi67AX0pOX3PuPdGFewvLRU2zzFSrTsgURNo0= github.com/aws/aws-sdk-go-v2/service/sso v1.13.5 h1:oCvTFSDi67AX0pOX3PuPdGFewvLRU2zzFSrTsgURNo0=
github.com/aws/aws-sdk-go-v2/service/sso v1.13.5/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4= github.com/aws/aws-sdk-go-v2/service/sso v1.13.5/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4=
@ -472,6 +475,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5/go.mod h1:yygr8ACQRY2PrEcy3
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.6.0/go.mod h1:q7o0j7d7HrJk/vr9uUt3BVRASvcU7gYZB9PUgPiByXg=
github.com/aws/aws-sdk-go-v2/service/sts v1.21.5 h1:CQBFElb0LS8RojMJlxRSo/HXipvTZW2S44Lt9Mk2aYQ= github.com/aws/aws-sdk-go-v2/service/sts v1.21.5 h1:CQBFElb0LS8RojMJlxRSo/HXipvTZW2S44Lt9Mk2aYQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.21.5/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU= github.com/aws/aws-sdk-go-v2/service/sts v1.21.5/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU=
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.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.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.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
@ -1794,6 +1799,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.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.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= 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.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -1903,6 +1909,7 @@ golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -2061,6 +2068,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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -2071,6 +2079,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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -2088,6 +2097,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.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.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.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.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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 return err
} }
err = ext.SetupExtensions(c.Config, driver, c.Log) //nolint:contextcheck
if err != nil {
return err
}
err = driver.PatchDB() err = driver.PatchDB()
if err != nil { if err != nil {
return err return err

View File

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

View File

@ -38,24 +38,23 @@ curl --data-binary @file.pub -X POST "http://localhost:8080/v2/_zot/ext/cosign
``` ```
As a result of this request, the uploaded file will be stored in `_cosign` directory As a result of this request, the uploaded file will be stored in `_cosign` directory
under the rootDir specified in the zot config. under the rootDir specified in the zot config or in Secrets Manager.
### Uploading a Notation certificate ### Uploading a Notation certificate
Notation certificates are used to sign images with the `notation` tool. Notation certificates are used to sign images with the `notation` tool.
The user needs to specify the type of the truststore through the `truststoreType` The user needs to specify the type of the truststore through the `truststoreType`
query parameter and its name through the `truststoreName` parameter. query parameter.
`truststoreType` defaults to `ca`, while `truststoreName` is a mandatory parameter. `truststoreType` defaults to `ca`.
***Example of request*** ***Example of request***
```bash ```bash
curl --data-binary @certificate.crt -X POST "http://localhost:8080/v2/_zot/ext/notation?truststoreType=ca&truststoreName=upload-cert" curl --data-binary @certificate.crt -X POST "http://localhost:8080/v2/_zot/ext/notation?truststoreType=ca"
``` ```
As a result of this request, the uploaded file will be stored in `_notation/truststore/x509/{truststoreType}/{truststoreName}` As a result of this request, the uploaded file will be stored in `_notation/truststore/x509/{truststoreType}/default`
directory under the rootDir specified in the zot config. directory under the rootDir specified in the zot config or in Secrets Manager.
The `truststores` field found in `_notation/trustpolicy.json` file will be updated automatically as well.
## Verification and results ## Verification and results
@ -118,7 +117,7 @@ The information above will be included in the ManifestSummary objects returned b
## Notes ## Notes
- The files (public keys and certificates) uploaded using the exposed routes will be stored in some specific directories called `_cosign` and `_notation` under `$rootDir`. - The files (public keys and certificates) uploaded using the exposed routes will be stored in some specific directories called `_cosign` and `_notation` under `$rootDir` in case of local filesystem or in Secrets Manager in case of cloud.
- `_cosign` directory will contain the uploaded public keys - `_cosign` directory will contain the uploaded public keys
@ -136,11 +135,11 @@ The information above will be included in the ManifestSummary objects returned b
└── truststore └── truststore
└── x509 └── x509
└── $truststoreType └── $truststoreType
└── $truststoreName └── default
└── $certificate └── $certificate
``` ```
where `trustpolicy.json` file has this default content which can not be modified by the user and which is updated each time a new certificate is added to a new truststore: where `trustpolicy.json` file has this default content which can not be modified by the user:
```json ```json
{ {
@ -152,7 +151,7 @@ The information above will be included in the ManifestSummary objects returned b
"signatureVerification": { "signatureVerification": {
"level" : "strict" "level" : "strict"
}, },
"trustStores": [], "trustStores": ["ca:default","signingAuthority:default"],
"trustedIdentities": [ "trustedIdentities": [
"*" "*"
] ]

View File

@ -21,16 +21,11 @@ import (
"zotregistry.io/zot/pkg/scheduler" "zotregistry.io/zot/pkg/scheduler"
) )
const (
ConfigResource = "config"
SignaturesResource = "signatures"
)
func IsBuiltWithImageTrustExtension() bool { func IsBuiltWithImageTrustExtension() bool {
return true 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()) { if !conf.IsImageTrustEnabled() || (!conf.IsCosignEnabled() && !conf.IsNotationEnabled()) {
log.Info().Msg("skip enabling the image trust routes as the config prerequisites are not met") 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") 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) allowedMethods := zcommon.AllowedMethods(http.MethodPost)
if conf.IsNotationEnabled() { if conf.IsNotationEnabled() {
@ -70,8 +66,9 @@ func SetupImageTrustRoutes(conf *config.Config, router *mux.Router, log log.Logg
} }
type ImageTrust struct { type ImageTrust struct {
Conf *config.Config Conf *config.Config
Log log.Logger ImageTrustStore *imagetrust.ImageTrustStore
Log log.Logger
} }
// Cosign handler godoc // Cosign handler godoc
@ -93,7 +90,7 @@ func (trust *ImageTrust) HandleCosignPublicKeyUpload(response http.ResponseWrite
return return
} }
err = imagetrust.UploadPublicKey(body) err = imagetrust.UploadPublicKey(trust.ImageTrustStore.CosignStorage, body)
if err != nil { if err != nil {
if errors.Is(err, zerr.ErrInvalidPublicKeyContent) { if errors.Is(err, zerr.ErrInvalidPublicKeyContent) {
response.WriteHeader(http.StatusBadRequest) response.WriteHeader(http.StatusBadRequest)
@ -115,7 +112,6 @@ func (trust *ImageTrust) HandleCosignPublicKeyUpload(response http.ResponseWrite
// @Accept octet-stream // @Accept octet-stream
// @Produce json // @Produce json
// @Param truststoreType query string false "truststore type" // @Param truststoreType query string false "truststore type"
// @Param truststoreName query string false "truststore name"
// @Param requestBody body string true "Certificate content" // @Param requestBody body string true "Certificate content"
// @Success 200 {string} string "ok" // @Success 200 {string} string "ok"
// @Failure 400 {string} string "bad request". // @Failure 400 {string} string "bad request".
@ -123,26 +119,12 @@ func (trust *ImageTrust) HandleCosignPublicKeyUpload(response http.ResponseWrite
func (trust *ImageTrust) HandleNotationCertificateUpload(response http.ResponseWriter, request *http.Request) { func (trust *ImageTrust) HandleNotationCertificateUpload(response http.ResponseWriter, request *http.Request) {
var truststoreType string var truststoreType string
if !zcommon.QueryHasParams(request.URL.Query(), []string{"truststoreName"}) {
response.WriteHeader(http.StatusBadRequest)
return
}
if zcommon.QueryHasParams(request.URL.Query(), []string{"truststoreType"}) { if zcommon.QueryHasParams(request.URL.Query(), []string{"truststoreType"}) {
truststoreType = request.URL.Query().Get("truststoreType") truststoreType = request.URL.Query().Get("truststoreType")
} else { } else {
truststoreType = "ca" // default value of "truststoreType" query param truststoreType = "ca" // default value of "truststoreType" query param
} }
truststoreName := request.URL.Query().Get("truststoreName")
if truststoreType == "" || truststoreName == "" {
response.WriteHeader(http.StatusBadRequest)
return
}
body, err := io.ReadAll(request.Body) body, err := io.ReadAll(request.Body)
if err != nil { if err != nil {
trust.Log.Error().Err(err).Msg("image trust: couldn't read notation certificate body") trust.Log.Error().Err(err).Msg("image trust: couldn't read notation certificate body")
@ -151,10 +133,9 @@ func (trust *ImageTrust) HandleNotationCertificateUpload(response http.ResponseW
return return
} }
err = imagetrust.UploadCertificate(body, truststoreType, truststoreName) err = imagetrust.UploadCertificate(trust.ImageTrustStore.NotationStorage, body, truststoreType)
if err != nil { if err != nil {
if errors.Is(err, zerr.ErrInvalidTruststoreType) || if errors.Is(err, zerr.ErrInvalidTruststoreType) ||
errors.Is(err, zerr.ErrInvalidTruststoreName) ||
errors.Is(err, zerr.ErrInvalidCertificateContent) { errors.Is(err, zerr.ErrInvalidCertificateContent) {
response.WriteHeader(http.StatusBadRequest) response.WriteHeader(http.StatusBadRequest)
} else { } else {
@ -178,6 +159,35 @@ func EnableImageTrustVerification(conf *config.Config, taskScheduler *scheduler.
generator := imagetrust.NewTaskGenerator(metaDB, log) generator := imagetrust.NewTaskGenerator(metaDB, log)
numberOfHours := 2 numberOfHours := 2
interval := time.Duration(numberOfHours) * time.Minute interval := time.Duration(numberOfHours) * time.Hour
taskScheduler.SubmitGenerator(generator, interval, scheduler.MediumPriority) 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.NewAWSImageTrustStore(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 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," + 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") "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 " + 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") "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" "testing"
"time" "time"
guuid "github.com/gofrs/uuid"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" "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/options"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
@ -64,7 +65,6 @@ func TestSignatureHandlers(t *testing.T) {
Convey("Test error handling when Notation handler reads the request body", t, func() { Convey("Test error handling when Notation handler reads the request body", t, func() {
request, _ := http.NewRequestWithContext(context.TODO(), http.MethodPost, "baseURL", errReader(0)) request, _ := http.NewRequestWithContext(context.TODO(), http.MethodPost, "baseURL", errReader(0))
query := request.URL.Query() query := request.URL.Query()
query.Add("truststoreName", "someName")
request.URL.RawQuery = query.Encode() request.URL.RawQuery = query.Encode()
response := httptest.NewRecorder() response := httptest.NewRecorder()
@ -111,7 +111,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" repo := "repo"
tag := "0.0.1" tag := "0.0.1"
certName := "test" certName := "test"
@ -128,12 +170,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() globalDir := t.TempDir()
port := test.GetFreePort() port := test.GetFreePort()
conf := config.New() conf := config.New()
conf.HTTP.Port = port conf.HTTP.Port = port
if cacheDriverParams != nil {
conf.Storage.CacheDriver = cacheDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{} conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Trust = &extconf.ImageTrustConfig{} conf.Extensions.Trust = &extconf.ImageTrustConfig{}
conf.Extensions.Trust.Enable = &defaultValue conf.Extensions.Trust.Enable = &defaultValue
@ -246,12 +291,15 @@ func TestSignatureUploadAndVerification(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound) 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() globalDir := t.TempDir()
port := test.GetFreePort() port := test.GetFreePort()
conf := config.New() conf := config.New()
conf.HTTP.Port = port conf.HTTP.Port = port
if cacheDriverParams != nil {
conf.Storage.CacheDriver = cacheDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{} conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Trust = &extconf.ImageTrustConfig{} conf.Extensions.Trust = &extconf.ImageTrustConfig{}
conf.Extensions.Trust.Enable = &defaultValue conf.Extensions.Trust.Enable = &defaultValue
@ -309,7 +357,6 @@ func TestSignatureUploadAndVerification(t *testing.T) {
client := resty.New() client := resty.New()
resp, err := client.R().SetHeader("Content-type", "application/octet-stream"). resp, err := client.R().SetHeader("Content-type", "application/octet-stream").
SetQueryParam("truststoreName", certName).
SetBody(certificateContent).Post(baseURL + constants.FullNotation) SetBody(certificateContent).Post(baseURL + constants.FullNotation)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK) So(resp.StatusCode(), ShouldEqual, http.StatusOK)
@ -330,18 +377,6 @@ func TestSignatureUploadAndVerification(t *testing.T) {
So(found, ShouldBeTrue) So(found, ShouldBeTrue)
resp, err = client.R().SetHeader("Content-type", "application/octet-stream"). resp, err = client.R().SetHeader("Content-type", "application/octet-stream").
SetBody(certificateContent).Post(baseURL + constants.FullNotation)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
resp, err = client.R().SetHeader("Content-type", "application/octet-stream").
SetQueryParam("truststoreName", "").
SetBody(certificateContent).Post(baseURL + constants.FullNotation)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
resp, err = client.R().SetHeader("Content-type", "application/octet-stream").
SetQueryParam("truststoreName", "test").
SetQueryParam("truststoreType", "signatureAuthority"). SetQueryParam("truststoreType", "signatureAuthority").
SetBody([]byte("wrong content")).Post(baseURL + constants.FullNotation) SetBody([]byte("wrong content")).Post(baseURL + constants.FullNotation)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -360,12 +395,15 @@ func TestSignatureUploadAndVerification(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound) So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
}) })
Convey("Verify uploading notation certificates", t, func() { Convey("Verify uploading notation certificates", func() {
globalDir := t.TempDir() globalDir := t.TempDir()
port := test.GetFreePort() port := test.GetFreePort()
conf := config.New() conf := config.New()
conf.HTTP.Port = port conf.HTTP.Port = port
if cacheDriverParams != nil {
conf.Storage.CacheDriver = cacheDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{} conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Search = &extconf.SearchConfig{} conf.Extensions.Search = &extconf.SearchConfig{}
conf.Extensions.Search.Enable = &defaultValue conf.Extensions.Search.Enable = &defaultValue
@ -453,7 +491,6 @@ func TestSignatureUploadAndVerification(t *testing.T) {
client := resty.New() client := resty.New()
resp, err = client.R().SetHeader("Content-type", "application/octet-stream"). resp, err = client.R().SetHeader("Content-type", "application/octet-stream").
SetQueryParam("truststoreName", certName).
SetBody(certificateContent).Post(baseURL + constants.FullNotation) SetBody(certificateContent).Post(baseURL + constants.FullNotation)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK) So(resp.StatusCode(), ShouldEqual, http.StatusOK)
@ -507,18 +544,6 @@ func TestSignatureUploadAndVerification(t *testing.T) {
ShouldEqual, "CN=cert,O=Notary,L=Seattle,ST=WA,C=US") ShouldEqual, "CN=cert,O=Notary,L=Seattle,ST=WA,C=US")
resp, err = client.R().SetHeader("Content-type", "application/octet-stream"). resp, err = client.R().SetHeader("Content-type", "application/octet-stream").
SetBody(certificateContent).Post(baseURL + constants.FullNotation)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
resp, err = client.R().SetHeader("Content-type", "application/octet-stream").
SetQueryParam("truststoreName", "").
SetBody(certificateContent).Post(baseURL + constants.FullNotation)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
resp, err = client.R().SetHeader("Content-type", "application/octet-stream").
SetQueryParam("truststoreName", "test").
SetQueryParam("truststoreType", "signatureAuthority"). SetQueryParam("truststoreType", "signatureAuthority").
SetBody([]byte("wrong content")).Post(baseURL + constants.FullNotation) SetBody([]byte("wrong content")).Post(baseURL + constants.FullNotation)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -533,12 +558,15 @@ func TestSignatureUploadAndVerification(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest) So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
}) })
Convey("Verify uploading cosign public keys", t, func() { Convey("Verify uploading cosign public keys", func() {
globalDir := t.TempDir() globalDir := t.TempDir()
port := test.GetFreePort() port := test.GetFreePort()
conf := config.New() conf := config.New()
conf.HTTP.Port = port conf.HTTP.Port = port
if cacheDriverParams != nil {
conf.Storage.CacheDriver = cacheDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{} conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Search = &extconf.SearchConfig{} conf.Extensions.Search = &extconf.SearchConfig{}
conf.Extensions.Search.Enable = &defaultValue conf.Extensions.Search.Enable = &defaultValue
@ -698,7 +726,7 @@ func TestSignatureUploadAndVerification(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest) 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() globalDir := t.TempDir()
port := test.GetFreePort() port := test.GetFreePort()
testCreds := test.GetCredString("admin", "admin") + "\n" + test.GetCredString("test", "test") testCreds := test.GetCredString("admin", "admin") + "\n" + test.GetCredString("test", "test")
@ -713,6 +741,9 @@ func TestSignatureUploadAndVerification(t *testing.T) {
Actions: []string{}, Actions: []string{},
}, },
} }
if cacheDriverParams != nil {
conf.Storage.CacheDriver = cacheDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{} conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Search = &extconf.SearchConfig{} conf.Extensions.Search = &extconf.SearchConfig{}
conf.Extensions.Search.Enable = &defaultValue conf.Extensions.Search.Enable = &defaultValue
@ -788,12 +819,15 @@ func TestSignatureUploadAndVerification(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusOK) 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() globalDir := t.TempDir()
port := test.GetFreePort() port := test.GetFreePort()
conf := config.New() conf := config.New()
conf.HTTP.Port = port conf.HTTP.Port = port
if cacheDriverParams != nil {
conf.Storage.CacheDriver = cacheDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{} conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Search = &extconf.SearchConfig{} conf.Extensions.Search = &extconf.SearchConfig{}
conf.Extensions.Search.Enable = &defaultValue conf.Extensions.Search.Enable = &defaultValue
@ -890,12 +924,15 @@ func TestSignatureUploadAndVerification(t *testing.T) {
So(imgSummary.Manifests[0].SignatureInfo[0].Author, ShouldEqual, "") 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() globalDir := t.TempDir()
port := test.GetFreePort() port := test.GetFreePort()
conf := config.New() conf := config.New()
conf.HTTP.Port = port conf.HTTP.Port = port
if cacheDriverParams != nil {
conf.Storage.CacheDriver = cacheDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{} conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Search = &extconf.SearchConfig{} conf.Extensions.Search = &extconf.SearchConfig{}
conf.Extensions.Search.Enable = &defaultValue conf.Extensions.Search.Enable = &defaultValue
@ -955,7 +992,6 @@ func TestSignatureUploadAndVerification(t *testing.T) {
client := resty.New() client := resty.New()
resp, err := client.R().SetHeader("Content-type", "application/octet-stream"). resp, err := client.R().SetHeader("Content-type", "application/octet-stream").
SetQueryParam("truststoreName", "test").
SetBody(certificateContent).Post(baseURL + constants.FullNotation) SetBody(certificateContent).Post(baseURL + constants.FullNotation)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError) So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
@ -966,3 +1002,11 @@ func TestSignatureUploadAndVerification(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError) So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
}) })
} }
func skipIt(t *testing.T) {
t.Helper()
if os.Getenv("DYNAMODBMOCK_ENDPOINT") == "" {
t.Skip("Skipping testing without AWS mock server")
}
}

View File

@ -55,3 +55,7 @@ func EnableScheduledTasks(conf *config.Config, taskScheduler *scheduler.Schedule
) { ) {
EnableImageTrustVerification(conf, taskScheduler, metaDB, log) 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" "os"
"path" "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" godigest "github.com/opencontainers/go-digest"
"github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key" "github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key"
sigs "github.com/sigstore/cosign/v2/pkg/signature" sigs "github.com/sigstore/cosign/v2/pkg/signature"
"github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/cryptoutils"
sigstoreSigs "github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/options" "github.com/sigstore/sigstore/pkg/signature/options"
zerr "zotregistry.io/zot/errors" zerr "zotregistry.io/zot/errors"
@ -24,116 +28,242 @@ import (
const cosignDirRelativePath = "_cosign" const cosignDirRelativePath = "_cosign"
var cosignDir = "" //nolint:gochecknoglobals type PublicKeyLocalStorage struct {
cosignDir string
}
func InitCosignDir(rootDir string) error { type PublicKeyAWSStorage 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) dir := path.Join(rootDir, cosignDirRelativePath)
_, err := os.Stat(dir) _, err := os.Stat(dir)
if os.IsNotExist(err) { if os.IsNotExist(err) {
err = os.MkdirAll(dir, defaultDirPerms) err = os.MkdirAll(dir, defaultDirPerms)
if err != nil { if err != nil {
return err return nil, err
} }
} }
if err == nil { if err != nil {
cosignDir = dir return nil, err
} }
return err return &PublicKeyLocalStorage{
cosignDir: dir,
}, nil
} }
func GetCosignDirPath() (string, error) { func NewPublicKeyAWSStorage(
if cosignDir != "" { secretsManagerClient *secretsmanager.Client, secretsManagerCache *secretcache.Cache,
return cosignDir, nil ) *PublicKeyAWSStorage {
return &PublicKeyAWSStorage{
secretsManagerClient: secretsManagerClient,
secretsManagerCache: secretsManagerCache,
}
}
func (local *PublicKeyLocalStorage) GetCosignDirPath() (string, error) {
if local.cosignDir != "" {
return local.cosignDir, nil
} }
return "", zerr.ErrSignConfigDirNotSet return "", zerr.ErrSignConfigDirNotSet
} }
func VerifyCosignSignature( 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) { ) (string, bool, error) {
cosignDir, err := GetCosignDirPath() publicKeys, err := cosignStorage.GetPublicKeys()
if err != nil { if err != nil {
return "", false, err return "", false, err
} }
files, err := os.ReadDir(cosignDir) for _, publicKey := range publicKeys {
if err != nil { // cosign verify the image
return "", false, err pubKeyVerifier, pubKeyContent, err := cosignStorage.GetPublicKeyVerifier(publicKey)
} if err != nil {
continue
}
for _, file := range files { pkcs11Key, ok := pubKeyVerifier.(*pkcs11key.Key)
if !file.IsDir() { if ok {
// cosign verify the image defer pkcs11Key.Close()
ctx := context.Background() }
keyRef := path.Join(cosignDir, file.Name())
hashAlgorithm := crypto.SHA256
pubKey, err := sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, hashAlgorithm) verifier := pubKeyVerifier
if err != nil {
continue
}
pkcs11Key, ok := pubKey.(*pkcs11key.Key) b64sig := signatureKey
if ok {
defer pkcs11Key.Close()
}
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) payload, err := io.ReadAll(compressed)
if err != nil { if err != nil {
continue 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 {
if err != nil { return string(pubKeyContent), true, 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
}
} }
} }
return "", false, 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 *PublicKeyAWSStorage) 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 *PublicKeyAWSStorage) 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 // validate public key
if ok, err := validatePublicKey(publicKeyContent); !ok { if ok, err := validatePublicKey(publicKeyContent); !ok {
return err 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}" // add public key to "{rootDir}/_cosign/{name.pub}"
configDir, err := GetCosignDirPath() cosignDir, err := local.GetCosignDirPath()
if err != nil { if err != nil {
return err return err
} }
name := godigest.FromBytes(publicKeyContent)
// store public key // store public key
publicKeyPath := path.Join(configDir, name.String()) publicKeyPath := path.Join(cosignDir, name.String())
return os.WriteFile(publicKeyPath, publicKeyContent, defaultFilePerms) return os.WriteFile(publicKeyPath, publicKeyContent, defaultFilePerms)
} }
func (cloud *PublicKeyAWSStorage) 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) { func validatePublicKey(publicKeyContent []byte) (bool, error) {
_, err := cryptoutils.UnmarshalPEMToPublicKey(publicKeyContent) _, err := cryptoutils.UnmarshalPEMToPublicKey(publicKeyContent)
if err != nil { if err != nil {

View File

@ -8,6 +8,14 @@ import (
"encoding/json" "encoding/json"
"time" "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" godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1" ispec "github.com/opencontainers/image-spec/specs-go/v1"
@ -23,18 +31,103 @@ const (
defaultFilePerms = 0o644 defaultFilePerms = 0o644
) )
func InitCosignAndNotationDirs(rootDir string) error { type ImageTrustStore struct {
err := InitCosignDir(rootDir) CosignStorage publicKeyStorage
if err != nil { NotationStorage certificateStorage
return err
}
err = InitNotationDir(rootDir)
return err
} }
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 NewAWSImageTrustStore(region, endpoint string) (*ImageTrustStore, error) {
secretsManagerClient, err := GetSecretsManagerClient(region, endpoint)
if err != nil {
return nil, err
}
secretsManagerCache := GetSecretsManagerRetrieval(region, endpoint)
publicKeyStorage := NewPublicKeyAWSStorage(secretsManagerClient, secretsManagerCache)
certStorage, err := NewCertificateAWSStorage(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 {
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
}
func (imgTrustStore *ImageTrustStore) VerifySignature(
signatureType string, rawSignature []byte, sigKey string, manifestDigest godigest.Digest, manifestContent []byte, signatureType string, rawSignature []byte, sigKey string, manifestDigest godigest.Digest, manifestContent []byte,
repo string, repo string,
) (string, time.Time, bool, error) { ) (string, time.Time, bool, error) {
@ -55,11 +148,11 @@ func VerifySignature(
switch signatureType { switch signatureType {
case zcommon.CosignSignature: 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 return author, time.Time{}, isValid, err
case zcommon.NotationSignature: case zcommon.NotationSignature:
return VerifyNotationSignature(desc, manifestDigest.String(), rawSignature, sigKey) return VerifyNotationSignature(imgTrustStore.NotationStorage, desc, manifestDigest.String(), rawSignature, sigKey)
default: default:
return "", time.Time{}, false, zerr.ErrInvalidSignatureType return "", time.Time{}, false, zerr.ErrInvalidSignatureType
} }

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,10 @@
package imagetrust package imagetrust
import ( import (
"bytes"
"context" "context"
"crypto/x509" "crypto/x509"
"encoding/base64"
"encoding/json" "encoding/json"
"encoding/pem" "encoding/pem"
"errors" "errors"
@ -14,9 +16,12 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"sync" "strings"
"time" "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-core-go/signature/jws"
"github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go"
"github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/dir"
@ -30,36 +35,94 @@ import (
zerr "zotregistry.io/zot/errors" zerr "zotregistry.io/zot/errors"
) )
const notationDirRelativePath = "_notation" const (
notationDirRelativePath = "_notation"
var ( truststoreName = "default"
notationDir = "" //nolint:gochecknoglobals
TrustpolicyLock = new(sync.Mutex) //nolint: gochecknoglobals
) )
func InitNotationDir(rootDir string) error { type CertificateLocalStorage struct {
notationDir string
}
type CertificateAWSStorage struct {
secretsManagerClient *secretsmanager.Client
secretsManagerCache *secretcache.Cache
}
type certificateStorage interface {
LoadTrustPolicyDocument() (*trustpolicy.Document, error)
StoreCertificate(certificateContent []byte, truststoreType string) error
GetVerifier(policyDoc *trustpolicy.Document) (notation.Verifier, error)
InitTrustpolicy(trustpolicy []byte) error
}
func NewCertificateLocalStorage(rootDir string) (*CertificateLocalStorage, error) {
dir := path.Join(rootDir, notationDirRelativePath) dir := path.Join(rootDir, notationDirRelativePath)
_, err := os.Stat(dir) _, err := os.Stat(dir)
if os.IsNotExist(err) { if os.IsNotExist(err) {
err = os.MkdirAll(dir, defaultDirPerms) err = os.MkdirAll(dir, defaultDirPerms)
if err != nil { if err != nil {
return err return nil, err
} }
} }
if err == nil { if err != nil {
notationDir = dir return nil, err
}
if _, err := LoadTrustPolicyDocument(notationDir); os.IsNotExist(err) { certStorage := &CertificateLocalStorage{
return InitTrustpolicyFile(notationDir) notationDir: dir,
}
if err := InitTrustpolicyFile(certStorage); err != nil {
return nil, err
}
for _, truststoreType := range truststore.Types {
defaultTruststore := path.Join(dir, "truststore", "x509", string(truststoreType), truststoreName)
_, err = os.Stat(defaultTruststore)
if os.IsNotExist(err) {
err = os.MkdirAll(defaultTruststore, defaultDirPerms)
if err != nil {
return nil, err
}
}
if err != nil {
return nil, err
} }
} }
return err return certStorage, nil
} }
func InitTrustpolicyFile(configDir string) error { func NewCertificateAWSStorage(
secretsManagerClient *secretsmanager.Client, secretsManagerCache *secretcache.Cache,
) (*CertificateAWSStorage, error) {
certStorage := &CertificateAWSStorage{
secretsManagerClient: secretsManagerClient,
secretsManagerCache: secretsManagerCache,
}
err := InitTrustpolicyFile(certStorage)
if err != nil {
return nil, err
}
return certStorage, nil
}
func InitTrustpolicyFile(notationStorage certificateStorage) error {
truststores := []string{}
for _, truststoreType := range truststore.Types {
truststores = append(truststores, fmt.Sprintf("\"%s:%s\"", string(truststoreType), truststoreName))
}
defaultTruststores := strings.Join(truststores, ",")
// according to https://github.com/notaryproject/notation/blob/main/specs/commandline/verify.md // according to https://github.com/notaryproject/notation/blob/main/specs/commandline/verify.md
// the value of signatureVerification.level field from trustpolicy.json file // the value of signatureVerification.level field from trustpolicy.json file
// could be one of these values: `strict`, `permissive`, `audit` or `skip` // could be one of these values: `strict`, `permissive`, `audit` or `skip`
@ -68,40 +131,138 @@ func InitTrustpolicyFile(configDir string) error {
// a certificate that verifies a signature, but that certificate has expired, then the // 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 is not trusted; if this field were set to `permissive` then the
// signature would be trusted) // signature would be trusted)
trustPolicy := ` trustPolicy := `{
{ "version": "1.0",
"version": "1.0", "trustPolicies": [
"trustPolicies": [ {
{ "name": "default-config",
"name": "default-config", "registryScopes": [ "*" ],
"registryScopes": [ "*" ], "signatureVerification": {
"signatureVerification": { "level" : "strict"
"level" : "strict" },
}, "trustStores": [` + defaultTruststores + `],
"trustStores": [], "trustedIdentities": [
"trustedIdentities": [ "*"
"*" ]
] }
} ]
] }`
}`
TrustpolicyLock.Lock() return notationStorage.InitTrustpolicy([]byte(trustPolicy))
defer TrustpolicyLock.Unlock()
return os.WriteFile(path.Join(configDir, dir.PathTrustPolicy), []byte(trustPolicy), defaultDirPerms)
} }
func GetNotationDirPath() (string, error) { func (local *CertificateLocalStorage) InitTrustpolicy(trustpolicy []byte) error {
if notationDir != "" { notationDir, err := local.GetNotationDirPath()
return notationDir, nil if err != nil {
return err
}
return os.WriteFile(path.Join(notationDir, dir.PathTrustPolicy), trustpolicy, defaultDirPerms)
}
func (cloud *CertificateAWSStorage) 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.") {
force := true
deleteSecretParam := &secretsmanager.DeleteSecretInput{
SecretId: &name,
ForceDeleteWithoutRecovery: &force,
}
_, err = cloud.secretsManagerClient.DeleteSecret(context.Background(), deleteSecretParam)
if err != nil {
return err
}
_, err = cloud.secretsManagerClient.CreateSecret(context.Background(), secretInputParam)
return err
}
return err
}
func (local *CertificateLocalStorage) GetNotationDirPath() (string, error) {
if local.notationDir != "" {
return local.notationDir, nil
} }
return "", zerr.ErrSignConfigDirNotSet return "", zerr.ErrSignConfigDirNotSet
} }
func (cloud *CertificateAWSStorage) 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(). // 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) jsonFile, err := dir.NewSysFS(notationDir).Open(dir.PathTrustPolicy)
if err != nil { if err != nil {
return nil, err return nil, err
@ -119,33 +280,66 @@ func LoadTrustPolicyDocument(notationDir string) (*trustpolicy.Document, error)
return policyDocument, nil return policyDocument, nil
} }
func (cloud *CertificateAWSStorage) 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. // NewFromConfig returns a verifier based on local file system.
// Equivalent function for verifier.NewFromConfig() // Equivalent function for verifier.NewFromConfig()
// but using LoadTrustPolicyDocumnt() function instead of trustpolicy.LoadDocument() function. // but using LoadTrustPolicyDocumnt() function instead of trustpolicy.LoadDocument() function.
func NewFromConfig() (notation.Verifier, error) { func NewFromConfig(notationStorage certificateStorage) (notation.Verifier, error) {
notationDir, err := GetNotationDirPath()
if err != nil {
return nil, err
}
// Load trust policy. // Load trust policy.
TrustpolicyLock.Lock() policyDocument, err := notationStorage.LoadTrustPolicyDocument()
defer TrustpolicyLock.Unlock()
policyDocument, err := LoadTrustPolicyDocument(notationDir)
if err != nil { if err != nil {
return nil, err 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)) 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)))) plugin.NewCLIManager(dir.NewSysFS(path.Join(notationDir, dir.PathPlugins))))
} }
func (cloud *CertificateAWSStorage) GetVerifier(policyDoc *trustpolicy.Document) (notation.Verifier, error) {
return verifier.New(policyDoc, cloud,
plugin.NewCLIManager(dir.NewSysFS(path.Join(dir.PathPlugins))))
}
func VerifyNotationSignature( 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) { ) (string, time.Time, bool, error) {
var ( var (
date time.Time date time.Time
@ -161,7 +355,7 @@ func VerifyNotationSignature(
} }
// Initialize verifier. // Initialize verifier.
verifier, err := NewFromConfig() verifier, err := NewFromConfig(notationStorage)
if err != nil { if err != nil {
return author, date, false, err return author, date, false, err
} }
@ -219,24 +413,30 @@ func CheckExpiryErr(verificationResults []*notation.ValidationResult, notAfter t
return false return false
} }
func UploadCertificate(certificateContent []byte, truststoreType, truststoreName string) error { func UploadCertificate(
notationStorage certificateStorage, certificateContent []byte, truststoreType string,
) error {
// validate truststore type // validate truststore type
if !validateTruststoreType(truststoreType) { if !validateTruststoreType(truststoreType) {
return zerr.ErrInvalidTruststoreType return zerr.ErrInvalidTruststoreType
} }
// validate truststore name
if !validateTruststoreName(truststoreName) {
return zerr.ErrInvalidTruststoreName
}
// validate certificate // validate certificate
if ok, err := validateCertificate(certificateContent); !ok { if _, ok, err := parseAndValidateCertificateContent(certificateContent); !ok {
return err return err
} }
// add certificate to "{rootDir}/_notation/truststore/x509/{type}/{name}/{name.crt}" // store certificate
configDir, err := GetNotationDirPath() err := notationStorage.StoreCertificate(certificateContent, truststoreType)
return err
}
func (local *CertificateLocalStorage) StoreCertificate(
certificateContent []byte, truststoreType string,
) error {
// add certificate to "{rootDir}/_notation/truststore/x509/{type}/default/{name.crt}"
configDir, err := local.GetNotationDirPath()
if err != nil { if err != nil {
return err return err
} }
@ -250,36 +450,24 @@ func UploadCertificate(certificateContent []byte, truststoreType, truststoreName
return err return err
} }
err = os.WriteFile(truststorePath, certificateContent, defaultFilePerms) return os.WriteFile(truststorePath, certificateContent, defaultFilePerms)
if err != nil { }
return err
func (cloud *CertificateAWSStorage) StoreCertificate(
certificateContent []byte, truststoreType 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,
} }
// add certificate to "trustpolicy.json" _, err := cloud.secretsManagerClient.CreateSecret(context.Background(), secretInputParam)
TrustpolicyLock.Lock()
defer TrustpolicyLock.Unlock()
trustpolicyDoc, err := LoadTrustPolicyDocument(configDir) return err
if err != nil {
return err
}
truststoreToAppend := fmt.Sprintf("%s:%s", truststoreType, truststoreName)
for _, t := range trustpolicyDoc.TrustPolicies[0].TrustStores {
if t == truststoreToAppend {
return nil
}
}
trustpolicyDoc.TrustPolicies[0].TrustStores = append(trustpolicyDoc.TrustPolicies[0].TrustStores, truststoreToAppend)
trustpolicyDocContent, err := json.Marshal(trustpolicyDoc)
if err != nil {
return err
}
return os.WriteFile(path.Join(configDir, dir.PathTrustPolicy), trustpolicyDocContent, defaultFilePerms)
} }
func validateTruststoreType(truststoreType string) bool { func validateTruststoreType(truststoreType string) bool {
@ -293,11 +481,15 @@ func validateTruststoreType(truststoreType string) bool {
} }
func validateTruststoreName(truststoreName string) bool { func validateTruststoreName(truststoreName string) bool {
if strings.Contains(truststoreName, "..") {
return false
}
return regexp.MustCompile(`^[a-zA-Z0-9_.-]+$`).MatchString(truststoreName) return regexp.MustCompile(`^[a-zA-Z0-9_.-]+$`).MatchString(truststoreName)
} }
// implementation from https://github.com/notaryproject/notation-core-go/blob/main/x509/cert.go#L20 // 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 var certs []*x509.Certificate
block, rest := pem.Decode(certificateContent) block, rest := pem.Decode(certificateContent)
@ -305,7 +497,7 @@ func validateCertificate(certificateContent []byte) (bool, error) {
// data may be in DER format // data may be in DER format
derCerts, err := x509.ParseCertificates(certificateContent) derCerts, err := x509.ParseCertificates(certificateContent)
if err != nil { 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...) certs = append(certs, derCerts...)
@ -314,7 +506,7 @@ func validateCertificate(certificateContent []byte) (bool, error) {
for block != nil { for block != nil {
cert, err := x509.ParseCertificate(block.Bytes) cert, err := x509.ParseCertificate(block.Bytes)
if err != nil { 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) certs = append(certs, cert)
block, rest = pem.Decode(rest) block, rest = pem.Decode(rest)
@ -322,9 +514,9 @@ func validateCertificate(certificateContent []byte) (bool, error) {
} }
if len(certs) == 0 { 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) zerr.ErrInvalidCertificateContent)
} }
return true, nil return certs, true, nil
} }

View File

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

View File

@ -7,6 +7,7 @@ import (
"encoding/json" "encoding/json"
"math" "math"
"testing" "testing"
"time"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1" ispec "github.com/opencontainers/image-spec/specs-go/v1"
@ -22,6 +23,15 @@ import (
"zotregistry.io/zot/pkg/test" "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) { func TestWrapperErrors(t *testing.T) {
Convey("Errors", t, func() { Convey("Errors", t, func() {
tmpDir := t.TempDir() tmpDir := t.TempDir()
@ -35,6 +45,8 @@ func TestWrapperErrors(t *testing.T) {
So(boltdbWrapper, ShouldNotBeNil) So(boltdbWrapper, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
boltdbWrapper.SetImageTrustStore(imgTrustStore{})
repoMeta := mTypes.RepoMetadata{ repoMeta := mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{}, Tags: map[string]mTypes.Descriptor{},
Signatures: map[string]mTypes.ManifestSignatures{}, Signatures: map[string]mTypes.ManifestSignatures{},

View File

@ -18,7 +18,6 @@ import (
zerr "zotregistry.io/zot/errors" zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/api/constants" "zotregistry.io/zot/pkg/api/constants"
zcommon "zotregistry.io/zot/pkg/common" zcommon "zotregistry.io/zot/pkg/common"
"zotregistry.io/zot/pkg/extensions/imagetrust"
"zotregistry.io/zot/pkg/log" "zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/meta/common" "zotregistry.io/zot/pkg/meta/common"
mTypes "zotregistry.io/zot/pkg/meta/types" mTypes "zotregistry.io/zot/pkg/meta/types"
@ -37,10 +36,13 @@ type DynamoDB struct {
UserDataTablename string UserDataTablename string
VersionTablename string VersionTablename string
Patches []func(client *dynamodb.Client, tableNames map[string]string) error Patches []func(client *dynamodb.Client, tableNames map[string]string) error
imgTrustStore mTypes.ImageTrustStore
Log log.Logger 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{ dynamoWrapper := DynamoDB{
Client: client, Client: client,
RepoMetaTablename: params.RepoMetaTablename, RepoMetaTablename: params.RepoMetaTablename,
@ -50,6 +52,7 @@ func New(client *dynamodb.Client, params DBDriverParameters, log log.Logger) (*D
UserDataTablename: params.UserDataTablename, UserDataTablename: params.UserDataTablename,
APIKeyTablename: params.APIKeyTablename, APIKeyTablename: params.APIKeyTablename,
Patches: version.GetDynamoDBPatches(), Patches: version.GetDynamoDBPatches(),
imgTrustStore: nil,
Log: log, Log: log,
} }
@ -87,6 +90,14 @@ func New(client *dynamodb.Client, params DBDriverParameters, log log.Logger) (*D
return &dynamoWrapper, nil 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 { func (dwr *DynamoDB) SetManifestData(manifestDigest godigest.Digest, manifestData mTypes.ManifestData) error {
mdAttributeValue, err := attributevalue.Marshal(manifestData) mdAttributeValue, err := attributevalue.Marshal(manifestData)
if err != nil { if err != nil {
@ -625,6 +636,12 @@ func (dwr *DynamoDB) IncrementImageDownloads(repo string, reference string) erro
} }
func (dwr *DynamoDB) UpdateSignaturesValidity(repo string, manifestDigest godigest.Digest) error { func (dwr *DynamoDB) UpdateSignaturesValidity(repo string, manifestDigest godigest.Digest) error {
imgTrustStore := dwr.ImageTrustStore()
if imgTrustStore == nil {
return nil
}
// get ManifestData of signed manifest // get ManifestData of signed manifest
var blob []byte var blob []byte
@ -659,7 +676,7 @@ func (dwr *DynamoDB) UpdateSignaturesValidity(repo string, manifestDigest godige
layersInfo := []mTypes.LayerInfo{} layersInfo := []mTypes.LayerInfo{}
for _, layerInfo := range sigInfo.LayersInfo { 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) manifestDigest, blob, repo)
if isTrusted { if isTrusted {

View File

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

View File

@ -6,7 +6,6 @@ import (
"zotregistry.io/zot/errors" "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/api/config" "zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/extensions/imagetrust"
"zotregistry.io/zot/pkg/log" "zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/meta/boltdb" "zotregistry.io/zot/pkg/meta/boltdb"
mdynamodb "zotregistry.io/zot/pkg/meta/dynamodb" 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 return nil, err
} }
err = imagetrust.InitCosignAndNotationDirs(params.RootDir)
if err != nil {
return nil, err
}
return Create("boltdb", driver, params, log) //nolint:contextcheck return Create("boltdb", driver, params, log) //nolint:contextcheck
} }

View File

@ -6,6 +6,7 @@ package meta_test
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"math/rand" "math/rand"
"os" "os"
"path" "path"
@ -22,7 +23,6 @@ import (
ispec "github.com/opencontainers/image-spec/specs-go/v1" ispec "github.com/opencontainers/image-spec/specs-go/v1"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/extensions/imagetrust" "zotregistry.io/zot/pkg/extensions/imagetrust"
"zotregistry.io/zot/pkg/log" "zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/meta" "zotregistry.io/zot/pkg/meta"
@ -43,9 +43,12 @@ const (
func TestBoltDB(t *testing.T) { func TestBoltDB(t *testing.T) {
Convey("BoltDB creation", t, func() { 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) boltDriver, err := boltdb.GetBoltDriver(boltDBParams)
So(err, ShouldBeNil) So(err, ShouldBeNil)
defer os.Remove(repoDBPath)
log := log.NewLogger("debug", "") log := log.NewLogger("debug", "")
@ -53,27 +56,36 @@ func TestBoltDB(t *testing.T) {
So(metaDB, ShouldNotBeNil) So(metaDB, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = os.Chmod("repo.db", 0o200) err = os.Chmod(repoDBPath, 0o200)
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, err = boltdb.GetBoltDriver(boltDBParams) _, err = boltdb.GetBoltDriver(boltDBParams)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
err = os.Chmod("repo.db", 0o600) err = os.Chmod(repoDBPath, 0o600)
So(err, ShouldBeNil) So(err, ShouldBeNil)
defer os.Remove("repo.db")
}) })
Convey("BoltDB Wrapper", t, func() { Convey("BoltDB Wrapper", t, func() {
boltDBParams := boltdb.DBParameters{} boltDBParams := boltdb.DBParameters{RootDir: t.TempDir()}
boltDriver, err := boltdb.GetBoltDriver(boltDBParams) boltDriver, err := boltdb.GetBoltDriver(boltDBParams)
So(err, ShouldBeNil) So(err, ShouldBeNil)
log := log.NewLogger("debug", "") log := log.NewLogger("debug", "")
imgTrustStore, err := imagetrust.NewLocalImageTrustStore(boltDBParams.RootDir)
So(err, ShouldBeNil)
boltdbWrapper, err := boltdb.New(boltDriver, log) 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(boltdbWrapper, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -108,6 +120,8 @@ func TestDynamoDBWrapper(t *testing.T) {
Region: "us-east-2", Region: "us-east-2",
} }
t.Logf("using dynamo driver options: %v", dynamoDBDriverParams)
dynamoClient, err := mdynamodb.GetDynamoClient(dynamoDBDriverParams) dynamoClient, err := mdynamodb.GetDynamoClient(dynamoDBDriverParams)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -117,6 +131,11 @@ func TestDynamoDBWrapper(t *testing.T) {
So(dynamoDriver, ShouldNotBeNil) So(dynamoDriver, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
imgTrustStore, err := imagetrust.NewAWSImageTrustStore(dynamoDBDriverParams.Region, dynamoDBDriverParams.Endpoint)
So(err, ShouldBeNil)
dynamoDriver.SetImageTrustStore(imgTrustStore)
resetDynamoDBTables := func() error { resetDynamoDBTables := func() error {
err := dynamoDriver.ResetRepoMetaTable() err := dynamoDriver.ResetRepoMetaTable()
if err != nil { if err != nil {
@ -1300,7 +1319,6 @@ func RunMetaDBTests(t *testing.T, metaDB mTypes.MetaDB, preparationFuncs ...func
So(repoData.Signatures[string(manifestDigest1)]["cosign"][0].LayersInfo[0].Date, So(repoData.Signatures[string(manifestDigest1)]["cosign"][0].LayersInfo[0].Date,
ShouldBeZeroValue) ShouldBeZeroValue)
}) })
Convey("trusted signature", func() { Convey("trusted signature", func() {
_, _, manifest, _ := test.GetRandomImageComponents(10) //nolint:staticcheck _, _, manifest, _ := test.GetRandomImageComponents(10) //nolint:staticcheck
manifestContent, _ := json.Marshal(manifest) manifestContent, _ := json.Marshal(manifest)
@ -1326,7 +1344,10 @@ func RunMetaDBTests(t *testing.T, metaDB mTypes.MetaDB, preparationFuncs ...func
} }
tdir := t.TempDir() 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() test.NotationPathLock.Lock()
defer test.NotationPathLock.Unlock() defer test.NotationPathLock.Unlock()
@ -1376,44 +1397,18 @@ func RunMetaDBTests(t *testing.T, metaDB mTypes.MetaDB, preparationFuncs ...func
}) })
So(err, ShouldBeNil) 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(err, ShouldBeNil)
So(certificateContent, ShouldNotBeNil)
trustpolicyPath := path.Join(tdir, "_notation/trustpolicy.json") imgTrustStore, ok := metaDB.ImageTrustStore().(*imagetrust.ImageTrustStore)
So(ok, ShouldBeTrue)
trustPolicy := ` err = imagetrust.UploadCertificate(imgTrustStore.NotationStorage, certificateContent, "ca")
{
"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"))
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = metaDB.UpdateSignaturesValidity(repo, manifestDigest) //nolint:contextcheck err = metaDB.UpdateSignaturesValidity(repo, manifestDigest) //nolint:contextcheck
@ -1421,6 +1416,7 @@ func RunMetaDBTests(t *testing.T, metaDB mTypes.MetaDB, preparationFuncs ...func
repoData, err := metaDB.GetRepoMeta(repo) repoData, err := metaDB.GetRepoMeta(repo)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(repoData.Signatures[string(manifestDigest)]["notation"][0].LayersInfo[0].Signer, So(repoData.Signatures[string(manifestDigest)]["notation"][0].LayersInfo[0].Signer,
ShouldNotBeEmpty) ShouldNotBeEmpty)
So(repoData.Signatures[string(manifestDigest)]["notation"][0].LayersInfo[0].Date, So(repoData.Signatures[string(manifestDigest)]["notation"][0].LayersInfo[0].Date,
@ -2693,31 +2689,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) { func skipDynamo(t *testing.T) {
t.Helper() t.Helper()

View File

@ -121,6 +121,10 @@ type MetaDB interface { //nolint:interfacebloat
[]RepoMetadata, map[string]ManifestMetadata, map[string]IndexData, error) []RepoMetadata, map[string]ManifestMetadata, map[string]IndexData, error)
PatchDB() error PatchDB() error
ImageTrustStore() ImageTrustStore
SetImageTrustStore(imgTrustStore ImageTrustStore)
} }
type UserDB interface { //nolint:interfacebloat type UserDB interface { //nolint:interfacebloat
@ -160,6 +164,13 @@ type UserDB interface { //nolint:interfacebloat
DeleteUserAPIKey(ctx context.Context, id string) error 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 { type ManifestMetadata struct {
ManifestBlob []byte ManifestBlob []byte
ConfigBlob []byte ConfigBlob []byte

View File

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

View File

@ -106,6 +106,24 @@ type MetaDBMock struct {
DeleteUserAPIKeyFn func(ctx context.Context, id string) error DeleteUserAPIKeyFn func(ctx context.Context, id string) error
PatchDBFn func() 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 { func (sdm MetaDBMock) SetRepoDescription(repo, description string) error {

View File

@ -388,12 +388,6 @@ const docTemplate = `{
"name": "truststoreType", "name": "truststoreType",
"in": "query" "in": "query"
}, },
{
"type": "string",
"description": "truststore name",
"name": "truststoreName",
"in": "query"
},
{ {
"description": "Certificate content", "description": "Certificate content",
"name": "requestBody", "name": "requestBody",

View File

@ -379,12 +379,6 @@
"name": "truststoreType", "name": "truststoreType",
"in": "query" "in": "query"
}, },
{
"type": "string",
"description": "truststore name",
"name": "truststoreName",
"in": "query"
},
{ {
"description": "Certificate content", "description": "Certificate content",
"name": "requestBody", "name": "requestBody",

View File

@ -483,10 +483,6 @@ paths:
in: query in: query
name: truststoreType name: truststoreType
type: string type: string
- description: truststore name
in: query
name: truststoreName
type: string
- description: Certificate content - description: Certificate content
in: body in: body
name: requestBody name: requestBody