Skip to content

Archivista storage #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions config/100-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,13 @@ metadata:
app.kubernetes.io/part-of: tekton-chains
# The data can be tweaked at install time, it is commented out
# because these are the default settings.
# data:
# artifacts.taskrun.format: tekton
# artifacts.taskrun.storage: tekton
data:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this change before upstream PR

artifacts.taskrun.format: in-toto
artifacts.taskrun.storage: archivista
artifacts.pipelinerun.format: in-toto
artifacts.pipelinerun.storage: archivista
storage.archivista.url: https://archivista.testifysec.io

# artifacts.taskrun.signer: x509
# artifacts.oci.storage: oci
# artifacts.oci.format: simplesigning
Expand Down
186 changes: 91 additions & 95 deletions docs/config.md

Large diffs are not rendered by default.

23 changes: 13 additions & 10 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ require (
github.com/google/go-licenses v1.6.0
github.com/grafeas/grafeas v0.2.3
github.com/hashicorp/go-multierror v1.1.1
github.com/in-toto/attestation v1.1.1
github.com/in-toto/archivista v0.9.0
github.com/in-toto/attestation v1.1.0
github.com/in-toto/go-witness v0.7.0
github.com/in-toto/in-toto-golang v0.9.1-0.20240317085821-8e2966059a09
github.com/opencontainers/go-digest v1.0.0
github.com/pkg/errors v0.9.1
Expand All @@ -37,14 +39,14 @@ require (
gocloud.dev v0.40.0
gocloud.dev/docstore/mongodocstore v0.40.0
gocloud.dev/pubsub/kafkapubsub v0.40.0
golang.org/x/crypto v0.33.0
golang.org/x/crypto v0.32.0
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
google.golang.org/grpc v1.70.0
google.golang.org/protobuf v1.36.4
k8s.io/api v0.32.1
k8s.io/apimachinery v0.32.1
k8s.io/client-go v0.32.1
k8s.io/code-generator v0.32.1
k8s.io/api v0.32.0
k8s.io/apimachinery v0.32.0
k8s.io/client-go v0.32.0
k8s.io/code-generator v0.32.0
knative.dev/pkg v0.0.0-20240416145024-0f34a8815650
sigs.k8s.io/yaml v1.4.0
)
Expand Down Expand Up @@ -183,6 +185,7 @@ require (
github.com/eapache/go-resiliency v1.7.0 // indirect
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
github.com/eapache/queue v1.1.0 // indirect
github.com/edwarnicke/gitoid v0.0.0-20220710194850-1be5bfda1f9d // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/envoyproxy/go-control-plane v0.13.1 // indirect
Expand Down Expand Up @@ -441,10 +444,10 @@ require (
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/oauth2 v0.26.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.29.0 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
Expand Down
46 changes: 26 additions & 20 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4A
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edwarnicke/gitoid v0.0.0-20220710194850-1be5bfda1f9d h1:4l+Uq5zFWSagXgGFaKRRVWJrnlzeathyagWgYUltCgY=
github.com/edwarnicke/gitoid v0.0.0-20220710194850-1be5bfda1f9d/go.mod h1:WxWwA3EYuCQjlR5EBUX3uaTS8bh9BOa7BcqVREHQ0uQ=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/proto v1.13.4 h1:myn1fyf8t7tAqIzV91Tj9qXpvyXXGXk8OS2H6IBSc9g=
Expand Down Expand Up @@ -863,8 +865,12 @@ github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJ
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/in-toto/attestation v1.1.1 h1:QD3d+oATQ0dFsWoNh5oT0udQ3tUrOsZZ0Fc3tSgWbzI=
github.com/in-toto/attestation v1.1.1/go.mod h1:Dcq1zVwA2V7Qin8I7rgOi+i837wEf/mOZwRm047Sjys=
github.com/in-toto/archivista v0.9.0 h1:XlS+jkrcFjmwSMhp6BZbP5y8FOvFPXM1h23WvCDT8bQ=
github.com/in-toto/archivista v0.9.0/go.mod h1:cLhrICj86j+8wJZmrUzDbNQdcwdc2lqX+v1SKV4tXpE=
github.com/in-toto/attestation v1.1.0 h1:oRWzfmZPDSctChD0VaQV7MJrywKOzyNrtpENQFq//2Q=
github.com/in-toto/attestation v1.1.0/go.mod h1:DB59ytd3z7cIHgXxwpSX2SABrU6WJUKg/grpdgHVgVs=
github.com/in-toto/go-witness v0.7.0 h1:I48FUCLfyos0uCSlHJoqCJO6HjtxF2f/y65TQVpxd8k=
github.com/in-toto/go-witness v0.7.0/go.mod h1:WZQY96yHqPPYkRcQU7dXl0d3saMKAg9DepWbUVL586E=
github.com/in-toto/in-toto-golang v0.9.1-0.20240317085821-8e2966059a09 h1:cwCITdi9pF50CF8uh40qDbkJ/VrEVzx5AoaHP7OPdEo=
github.com/in-toto/in-toto-golang v0.9.1-0.20240317085821-8e2966059a09/go.mod h1:yGCBn2JKF1m26FX8GmkcLSOFVjB6khWRxFsHwWIg7hw=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
Expand Down Expand Up @@ -1506,8 +1512,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
Expand Down Expand Up @@ -1668,8 +1674,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down Expand Up @@ -1765,8 +1771,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
Expand All @@ -1780,8 +1786,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand All @@ -1800,8 +1806,8 @@ 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.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down Expand Up @@ -2137,16 +2143,16 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I=
honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs=
k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc=
k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k=
k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE=
k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0=
k8s.io/apiextensions-apiserver v0.29.2 h1:UK3xB5lOWSnhaCk0RFZ0LUacPZz9RY4wi/yt2Iu+btg=
k8s.io/apiextensions-apiserver v0.29.2/go.mod h1:aLfYjpA5p3OwtqNXQFkhJ56TB+spV8Gc4wfMhUA3/b8=
k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs=
k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU=
k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg=
k8s.io/code-generator v0.32.1 h1:4lw1kFNDuFYXquTkB7Sl5EwPMUP2yyW9hh6BnFfRZFY=
k8s.io/code-generator v0.32.1/go.mod h1:zaILfm00CVyP/6/pJMJ3zxRepXkxyDfUV5SNG4CjZI4=
k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg=
k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8=
k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8=
k8s.io/code-generator v0.32.0 h1:s0lNN8VSWny8LBz5t5iy7MCdgwdOhdg7vAGVxvS+VWU=
k8s.io/code-generator v0.32.0/go.mod h1:b7Q7KMZkvsYFy72A79QYjiv4aTz3GvW0f1T3UfhFq4s=
k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 h1:si3PfKm8dDYxgfbeA6orqrtLkvvIeH8UqffFJDl0bz4=
k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
Expand Down
217 changes: 217 additions & 0 deletions pkg/chains/storage/archivista/archivista.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package archivista

import (
"context"
"crypto"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"net/http"
"strings"

archivistaClient "github.com/in-toto/archivista/pkg/http-client"
"github.com/in-toto/go-witness/cryptoutil"
"github.com/in-toto/go-witness/dsse"
"github.com/tektoncd/chains/pkg/chains/objects"
"github.com/tektoncd/chains/pkg/config"
tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" // if needed
tektonclient "github.com/tektoncd/pipeline/pkg/client/clientset/versioned"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"knative.dev/pkg/logging"
)

const (
StorageBackendArchivista = "archivista"
)

// generatePublicKeyIDFunc is a package-level variable wrapping the public key ID generation.
// It allows tests to simulate errors.
var generatePublicKeyIDFunc = cryptoutil.GeneratePublicKeyID

// buildEnvelope constructs a DSSE envelope from the raw payload, signature, keyID, and certificate chain.
// If a valid chain is provided, it parses it into a leaf and intermediates; otherwise, certificate data is omitted.
func buildEnvelope(rawPayload []byte, signature, keyID string, chain string) dsse.Envelope {
var leaf []byte
var inters [][]byte

chain = strings.TrimSpace(chain)
if chain != "" {
var err error
leaf, inters, err = parseAndOrderCertificateChain(chain)
if err != nil {
// Log error if needed and fall back to no certificate data.
leaf = nil
inters = [][]byte{}
}
}
return dsse.Envelope{
Payload: rawPayload,
PayloadType: "application/vnd.in-toto+json",
Signatures: []dsse.Signature{
{
KeyID: keyID,
Signature: []byte(signature),
Certificate: leaf,
Intermediates: inters,
},
},
}
}

// Backend is the interface that all storage backends must implement.
type Backend interface {
StorePayload(ctx context.Context, obj objects.TektonObject, rawPayload []byte, signature string, opts config.StorageOpts) error
RetrievePayloads(ctx context.Context, obj objects.TektonObject, opts config.StorageOpts) (map[string]string, error)
RetrieveSignatures(ctx context.Context, obj objects.TektonObject, opts config.StorageOpts) (map[string][]string, error)
Type() string
}

// ArchivistaStorage implements the Backend interface for Archivista.
type ArchivistaStorage struct {
client *archivistaClient.ArchivistaClient
url string
cfg config.ArchivistaStorageConfig
tektonClient tektonclient.Interface // Injected Tekton client for patching objects
}

// NewArchivistaStorage initializes a new ArchivistaStorage backend.
func NewArchivistaStorage(cfg config.Config, tektonClient tektonclient.Interface) (*ArchivistaStorage, error) {
archCfg := cfg.Storage.Archivista
if strings.TrimSpace(archCfg.URL) == "" {
return nil, fmt.Errorf("missing archivista URL in storage configuration")
}

client, err := archivistaClient.CreateArchivistaClient(&http.Client{}, archCfg.URL)
if err != nil {
return nil, fmt.Errorf("failed to create Archivista client: %w", err)
}

return &ArchivistaStorage{
client: client,
url: archCfg.URL,
cfg: archCfg,
tektonClient: tektonClient,
}, nil
}

// patchTektonObjectAnnotations patches the Tekton object's annotations with the given key/value pairs
// in one single patch call.
func PatchTektonObjectAnnotations(ctx context.Context, obj objects.TektonObject, annotations map[string]string, tektonClient tektonclient.Interface) error {
patchData := map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": annotations,
},
}
patchBytes, err := json.Marshal(patchData)
if err != nil {
return fmt.Errorf("failed to marshal patch data: %w", err)
}

switch o := obj.GetObject().(type) {
case *tektonv1.TaskRun:
_, err = tektonClient.TektonV1().TaskRuns(o.Namespace).Patch(ctx, o.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{})
return err
case *tektonv1.PipelineRun:
_, err = tektonClient.TektonV1().PipelineRuns(o.Namespace).Patch(ctx, o.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{})
return err
case *v1beta1.TaskRun:
_, err = tektonClient.TektonV1beta1().TaskRuns(o.Namespace).Patch(ctx, o.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{})
return err
case *v1beta1.PipelineRun:
_, err = tektonClient.TektonV1beta1().PipelineRuns(o.Namespace).Patch(ctx, o.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{})
return err
default:
return fmt.Errorf("unsupported Tekton object type for patching")
}
}

// StorePayload builds a DSSE envelope from the raw payload and signature,
// logs the envelope, uploads it via the Archivista client API, and patches the
// Tekton object with the returned gitoid and Archivista URL.
func (a *ArchivistaStorage) StorePayload(ctx context.Context, obj objects.TektonObject, rawPayload []byte, signature string, opts config.StorageOpts) error {
logger := logging.FromContext(ctx)

// Validate signature.
if strings.TrimSpace(signature) == "" {
return fmt.Errorf("missing signature")
}

var keyID string
certPEM := strings.TrimSpace(opts.Cert)
if certPEM != "" {
block, _ := pem.Decode([]byte(certPEM))
if block != nil {
cert, err := x509.ParseCertificate(block.Bytes)
if err == nil {
// Generate keyID from the public key.
keyID, err = generatePublicKeyIDFunc(cert.PublicKey, crypto.SHA256)
if err != nil {
logger.Errorw("Failed to generate KeyID", "error", err)
keyID = ""
}
} else {
logger.Errorw("Failed to parse certificate", "error", err)
}
} else {
logger.Error("Failed to decode certificate PEM")
}
} // if no certificate provided, keyID remains blank

// Optionally decode the payload for logging.
decodedPayload, err := base64.StdEncoding.DecodeString(string(rawPayload))
if err != nil {
logger.Errorw("Failed to base64 decode payload", "keyID", keyID, "error", err)
logger.Infof("Raw payload (not base64 decoded): %s", string(rawPayload))
} else {
logger.Infof("Decoded payload: %s", string(decodedPayload))
}

env := buildEnvelope(rawPayload, signature, keyID, opts.Chain)

// Upload the envelope using the Archivista client's Store method.
uploadResp, err := a.client.Store(ctx, env)
if err != nil {
logger.Errorw("Failed to upload DSSE envelope to Archivista", "error", err)
return fmt.Errorf("failed to upload envelope to Archivista: %w", err)
}
logger.Infof("Successfully uploaded DSSE envelope to Archivista, response: %+v", uploadResp)

// Update the in-memory Tekton object with Archivista annotations.
annotations := map[string]string{
"chains.tekton.dev/archivista-gitoid": uploadResp.Gitoid,
"chains.tekton.dev/archivista-url": a.url,
}
obj.SetAnnotations(annotations)

// Patch the live Tekton object in one call.
if err := PatchTektonObjectAnnotations(ctx, obj, annotations, a.tektonClient); err != nil {
logger.Errorw("Failed to patch Tekton object with Archivista annotations", "error", err)
return fmt.Errorf("failed to patch Tekton object: %w", err)
}

return nil
}

// RetrievePayload is not implemented for Archivista.
func (a *ArchivistaStorage) RetrievePayload(ctx context.Context, key string) ([]byte, []byte, error) {
return nil, nil, fmt.Errorf("RetrievePayload not implemented for Archivista")
}

// RetrievePayloads is not implemented for Archivista.
func (a *ArchivistaStorage) RetrievePayloads(ctx context.Context, obj objects.TektonObject, opts config.StorageOpts) (map[string]string, error) {
return nil, fmt.Errorf("RetrievePayloads not implemented for Archivista")
}

// RetrieveSignatures is not implemented for Archivista.
func (a *ArchivistaStorage) RetrieveSignatures(ctx context.Context, obj objects.TektonObject, opts config.StorageOpts) (map[string][]string, error) {
return nil, fmt.Errorf("RetrieveSignatures not implemented for Archivista")
}

// Type returns the storage backend type.
func (a *ArchivistaStorage) Type() string {
return StorageBackendArchivista
}
Loading