diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ff5661df..8f831f7a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -68,7 +68,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 + uses: github/codeql-action/init@8214744c546c1e5c8f03dde8fab3a7353211988d # v3.26.7 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -78,7 +78,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 + uses: github/codeql-action/autobuild@8214744c546c1e5c8f03dde8fab3a7353211988d # v3.26.7 # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -91,6 +91,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 + uses: github/codeql-action/analyze@8214744c546c1e5c8f03dde8fab3a7353211988d # v3.26.7 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 7a170036..a81e00aa 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -85,6 +85,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 + uses: github/codeql-action/upload-sarif@8214744c546c1e5c8f03dde8fab3a7353211988d # v3.26.7 with: sarif_file: results.sarif diff --git a/Dockerfile b/Dockerfile index b5ffbbd6..8e7fcc52 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM golang:1.23.1-alpine@sha256:436e2d978524b15498b98faa367553ba6c3655671226f500c72ceb7afb2ef0b1 AS build +FROM golang:1.23.1-alpine@sha256:ac67716dd016429be8d4c2c53a248d7bcdf06d34127d3dc451bda6aa5a87bc06 AS build WORKDIR /src RUN apk update && apk add --no-cache file git curl RUN curl -sSf https://atlasgo.sh | sh diff --git a/Dockerfile-dev b/Dockerfile-dev index e684a0a4..77041d04 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM golang:1.23.1-alpine@sha256:436e2d978524b15498b98faa367553ba6c3655671226f500c72ceb7afb2ef0b1 AS build +FROM golang:1.23.1-alpine@sha256:ac67716dd016429be8d4c2c53a248d7bcdf06d34127d3dc451bda6aa5a87bc06 AS build WORKDIR /src RUN apk update && apk add --no-cache file git curl RUN curl -sSf https://atlasgo.sh | sh diff --git a/README.md b/README.md index 74896e84..97259b9c 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,14 @@ Archivista is configured through environment variables currently. | ARCHIVISTA_GRAPHQL_WEB_CLIENT_ENABLE | TRUE | Enable GraphiQL, the GraphQL web client | | ARCHIVISTA_ENABLE_ARTIFACT_STORE | FALSE | Enable Artifact Store Endpoints | | ARCHIVISTA_ARTIFACT_STORE_CONFIG | /tmp/artifacts/config.yaml | Location of the config describing available artifacts | +| ARCHIVISTA_PUBLISHER | "" | Publisher to use. Options are DAPR, RSTUF. Supports multiple, Comma-separated list of String | +| ARCHIVISTA_PUBLISHER_DAPR_HOST | localhost | Dapr host | +| ARCHIVISTA_PUBLISHER_DAPR_PORT | 3500 | Dapr port | +| ARCHIVISTA_PUBLISHER_DAPR_COMPONENT_NAME | "archivista" | Dapr pubsub component name | +| ARCHIVISTA_PUBLISHER_DAPR_TOPIC | "attestations" | Dapr pubsub topic | +| ARCHIVISTA_PUBLISHER_DAPR_URL | | Dapr full URL | +| ARCHIVISTA_PUBLISHER_RSTUF_HOST | | RSTUF URL | + ## Using Archivista diff --git a/go.mod b/go.mod index 2c4ffdf0..716d4b01 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( ariga.io/sqlcomment v0.1.0 entgo.io/contrib v0.6.0 entgo.io/ent v0.14.0 - github.com/99designs/gqlgen v0.17.49 + github.com/99designs/gqlgen v0.17.51 github.com/antonfisher/nested-logrus-formatter v1.3.1 github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 github.com/edwarnicke/gitoid v0.0.0-20220710194850-1be5bfda1f9d @@ -104,14 +104,14 @@ require ( go.opentelemetry.io/otel/metric v1.26.0 // indirect go.opentelemetry.io/otel/sdk v1.26.0 // indirect go.opentelemetry.io/otel/trace v1.26.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.27.0 // indirect golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect - golang.org/x/mod v0.18.0 // indirect - golang.org/x/net v0.28.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect - golang.org/x/tools v0.22.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + golang.org/x/tools v0.24.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 32a1537b..aee6f623 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ entgo.io/ent v0.14.0 h1:EO3Z9aZ5bXJatJeGqu/EVdnNr6K4mRq3rWe5owt0MC4= entgo.io/ent v0.14.0/go.mod h1:qCEmo+biw3ccBn9OyL4ZK5dfpwg++l1Gxwac5B1206A= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/99designs/gqlgen v0.17.49 h1:b3hNGexHd33fBSAd4NDT/c3NCcQzcAVkknhN9ym36YQ= -github.com/99designs/gqlgen v0.17.49/go.mod h1:tC8YFVZMed81x7UJ7ORUwXF4Kn6SXuucFqQBhN8+BU0= +github.com/99designs/gqlgen v0.17.51 h1:KHLvUckplsZi14Zv1JdHkemXSkulksN/Dwe7VflePSQ= +github.com/99designs/gqlgen v0.17.51/go.mod h1:77/+pVe6zlTsz++oUg2m8VLgzdUPHxjoAG3BxI5y8Rc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= @@ -24,8 +24,8 @@ github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8 github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= -github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= +github.com/PuerkitoBio/goquery v1.9.3 h1:mpJr/ikUA9/GNJB/DBZcGeFDXUtosHRyRrwh7KGdTG0= +github.com/PuerkitoBio/goquery v1.9.3/go.mod h1:1ndLHPdTz+DyQPICCWYlYQMPl0oXZj0G6D4LCYA6u4U= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= @@ -364,8 +364,8 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= @@ -374,8 +374,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -385,8 +385,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -401,12 +401,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -415,8 +415,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/config/config.go b/pkg/config/config.go index 69d581eb..a2b91a74 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -56,6 +56,14 @@ type Config struct { EnableArtifactStore bool `default:"FALSE" desc:"*** Enable Artifact Store Endpoints" split_words:"true"` ArtifactStoreConfig string `default:"/tmp/artifacts/config.yaml" desc:"Location of the config describing available artifacts" split_words:"true"` + + Publisher []string `default:"" desc:"Publisher to use. Options are DAPR, RSTUF or empty string for disabled." split_words:"true"` + PublisherDaprHost string `default:"http://127.0.0.1" desc:"Host for Dapr" split_words:"true"` + PublisherDaprPort string `default:"3500" desc:"Port for Dapr" split_words:"true"` + PublisherDaprURL string `default:"" desc:"URL for Dapr" split_words:"true"` + PublisherDaprComponentName string `default:"archivista" desc:"Dapr pubsub component name" split_words:"true"` + PublisherDaprTopic string `default:"attestations" desc:"Dapr pubsub topic" split_words:"true"` + PublisherRstufHost string `default:"http://127.0.0.1" desc:"Host for RSTUF" split_words:"true"` } // Process reads config from env diff --git a/pkg/publisherstore/dapr/http.go b/pkg/publisherstore/dapr/http.go new file mode 100644 index 00000000..342dc520 --- /dev/null +++ b/pkg/publisherstore/dapr/http.go @@ -0,0 +1,92 @@ +// Copyright 2024 The Archivista Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package dapr + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/in-toto/archivista/pkg/config" + "github.com/sirupsen/logrus" +) + +type DaprHttp struct { + Client *http.Client + Host string + HttpPort string + PubsubComponentName string + PubsubTopic string + Url string +} + +type daprPayload struct { + Gitoid string + Payload []byte +} + +type Publisher interface { + Publish(ctx context.Context, gitoid string, payload []byte) error +} + +func (d *DaprHttp) Publish(ctx context.Context, gitoid string, payload []byte) error { + if d.Client == nil { + d.Client = &http.Client{ + Timeout: 15 * time.Second, + } + } + + if d.Url == "" { + d.Url = d.Host + ":" + d.HttpPort + + "/v1.0/publish/" + d.PubsubComponentName + "/" + d.PubsubTopic + } + + dp := daprPayload{ + Gitoid: gitoid, + Payload: payload, + } + // Marshal the message to JSON + msgBytes, err := json.Marshal(dp) + if err != nil { + logrus.Error(err.Error()) + return err + } + + res, err := d.Client.Post(d.Url, "application/json", bytes.NewReader(msgBytes)) + if err != nil { + logrus.Error(err.Error()) + return err + } + if res.StatusCode != http.StatusNoContent { + logrus.Printf("failed to publish message: %s", res.Body) + return fmt.Errorf("failed to publish message: %s", res.Body) + } + defer res.Body.Close() + + return nil +} + +func NewPublisher(config *config.Config) Publisher { + daprPublisher := &DaprHttp{ + Host: config.PublisherDaprHost, + HttpPort: config.PublisherDaprPort, + PubsubComponentName: config.PublisherDaprComponentName, + PubsubTopic: config.PublisherDaprTopic, + Url: config.PublisherDaprURL, + } + return daprPublisher +} diff --git a/pkg/publisherstore/publisherstore.go b/pkg/publisherstore/publisherstore.go new file mode 100644 index 00000000..77ace7b4 --- /dev/null +++ b/pkg/publisherstore/publisherstore.go @@ -0,0 +1,47 @@ +// Copyright 2024 The Archivista Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package publisherstore + +import ( + "context" + "strings" + + "github.com/in-toto/archivista/pkg/config" + "github.com/in-toto/archivista/pkg/publisherstore/dapr" + "github.com/in-toto/archivista/pkg/publisherstore/rstuf" + "github.com/sirupsen/logrus" +) + +type Publisher interface { + Publish(ctx context.Context, gitoid string, payload []byte) error +} + +func New(config *config.Config) []Publisher { + var publisherStore []Publisher + for _, pubType := range config.Publisher { + pubType = strings.ToUpper(pubType) // Normalize the input + switch pubType { + case "DAPR": + publisherStore = append(publisherStore, dapr.NewPublisher(config)) + logrus.Info("Using publisher: DAPR") + + case "RSTUF": + publisherStore = append(publisherStore, rstuf.NewPublisher(config)) + logrus.Info("Using publisher: RSTUF") + default: + logrus.Errorf("unsupported publisher type: %s", pubType) + } + } + return publisherStore +} diff --git a/pkg/publisherstore/rstuf/rstuf.go b/pkg/publisherstore/rstuf/rstuf.go new file mode 100644 index 00000000..b0b9abf3 --- /dev/null +++ b/pkg/publisherstore/rstuf/rstuf.go @@ -0,0 +1,124 @@ +// Copyright 2024 The Archivista Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rstuf + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httputil" + + "github.com/in-toto/archivista/pkg/config" + "github.com/sirupsen/logrus" +) + +type RSTUF struct { + Host string +} + +type Publisher interface { + Publish(ctx context.Context, gitoid string, payload []byte) error +} + +func (r *RSTUF) parseRSTUFPayload(gitoid string, payload []byte) ([]byte, error) { + objHash := sha256.Sum256(payload) + // custom := make(map[string]any) + // custom["gitoid"] = gitoid + artifacts := []Artifact{ + { + Path: gitoid, + Info: ArtifactInfo{ + Length: len(payload), + Hashes: Hashes{ + Sha256: hex.EncodeToString(objHash[:]), + }, + // Custom: custom, + }, + }, + } + + artifactPayload := ArtifactPayload{ + Artifacts: artifacts, + AddTaskIDToCustom: false, + PublishTargets: true, + } + + payloadBytes, err := json.Marshal(artifactPayload) + if err != nil { + return nil, fmt.Errorf("error marshaling payload: %v", err) + } + return payloadBytes, nil +} + +func (r *RSTUF) Publish(ctx context.Context, gitoid string, payload []byte) error { + // this publisher allows integration with the RSTUF project to store + // the attestation and policy in the TUF metadata. + // this TUF metadata can be used to build truste when distributing the + // attestations and policies. + // Convert payload to JSON + url := r.Host + "/api/v1/artifacts" + + payloadBytes, err := r.parseRSTUFPayload(gitoid, payload) + if err != nil { + return fmt.Errorf("error parsing payload: %v", err) + } + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(payloadBytes)) + if err != nil { + return fmt.Errorf("error creating request: %v", err) + } + + req.Header.Set("Content-Type", "application/json") + // Add any additional headers or authentication if needed + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + logrus.Errorf("error making request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusAccepted { + logb, _ := httputil.DumpResponse(resp, true) + logrus.Errorf("error body from RSTUF: %v", string(logb)) + return fmt.Errorf("error response from RSTUF: %v", err) + } + + // Handle the response as needed + body, err := io.ReadAll(resp.Body) + if err != nil { + logrus.Errorf("error reading response body: %v", err) + } + + response := Response{} + err = json.Unmarshal(body, &response) + if err != nil { + logrus.Errorf("error unmarshaling response: %v", err) + } + logrus.Debugf("RSTUF task id: %v", response.Data.TaskId) + // TODO: monitor RSTUF task id for completion + return nil +} + +func NewPublisher(config *config.Config) Publisher { + return &RSTUF{ + Host: config.PublisherRstufHost, + } +} diff --git a/pkg/publisherstore/rstuf/structs.go b/pkg/publisherstore/rstuf/structs.go new file mode 100644 index 00000000..7bdb953d --- /dev/null +++ b/pkg/publisherstore/rstuf/structs.go @@ -0,0 +1,51 @@ +// Copyright 2024 The Archivista Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rstuf + +// Hashes represents the Hashes structure +type Hashes struct { + Sha256 string `json:"sha256"` +} + +// ArtifactInfo represents the ArtifactInfo structure +type ArtifactInfo struct { + Length int `json:"length"` + Hashes Hashes `json:"hashes"` + Custom map[string]any `json:"custom,omitempty"` +} + +// Artifact represents the Artifact structure +type Artifact struct { + Path string `json:"path"` + Info ArtifactInfo `json:"info"` +} + +// ArtifactPayload represents the payload structure +type ArtifactPayload struct { + Artifacts []Artifact `json:"artifacts"` + AddTaskIDToCustom bool `json:"add_task_id_to_custom"` + PublishTargets bool `json:"publish_targets"` +} + +type ArtifactsResponse struct { + Artifacts []string `json:"artifacts"` + TaskId string `json:"task_id"` + LastUpdate string `json:"last_update"` + Message string `json:"message"` +} + +type Response struct { + Data ArtifactsResponse `json:"data"` +} diff --git a/pkg/server/server.go b/pkg/server/server.go index ee117856..f992e3df 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -38,16 +38,18 @@ import ( "github.com/in-toto/archivista/pkg/api" "github.com/in-toto/archivista/pkg/artifactstore" "github.com/in-toto/archivista/pkg/config" + "github.com/in-toto/archivista/pkg/publisherstore" "github.com/sirupsen/logrus" httpSwagger "github.com/swaggo/http-swagger/v2" ) type Server struct { - metadataStore Storer - objectStore StorerGetter - artifactStore artifactstore.Store - router *mux.Router - sqlClient *ent.Client + metadataStore Storer + objectStore StorerGetter + artifactStore artifactstore.Store + router *mux.Router + sqlClient *ent.Client + publisherStore []publisherstore.Publisher } type Storer interface { @@ -89,6 +91,12 @@ func WithArtifactStore(wds artifactstore.Store) Option { } } +func WithPublishers(pub []publisherstore.Publisher) Option { + return func(s *Server) { + s.publisherStore = pub + } +} + func New(cfg *config.Config, opts ...Option) (Server, error) { r := mux.NewRouter() s := Server{ @@ -168,6 +176,15 @@ func (s *Server) Upload(ctx context.Context, r io.Reader) (api.UploadResponse, e return api.UploadResponse{}, err } + if s.publisherStore != nil { + for _, publisher := range s.publisherStore { + // TODO: Make publish asynchrouns and use goroutine + if err := publisher.Publish(ctx, gid.String(), payload); err != nil { + logrus.Errorf("received error from publisher: %+v", err) + } + } + } + return api.UploadResponse{Gitoid: gid.String()}, nil } diff --git a/pkg/server/services.go b/pkg/server/services.go index 5255864f..9587d4ee 100644 --- a/pkg/server/services.go +++ b/pkg/server/services.go @@ -30,6 +30,7 @@ import ( "github.com/in-toto/archivista/pkg/metadatastorage/sqlstore" "github.com/in-toto/archivista/pkg/objectstorage/blobstore" "github.com/in-toto/archivista/pkg/objectstorage/filestore" + "github.com/in-toto/archivista/pkg/publisherstore" "github.com/minio/minio-go/v7/pkg/credentials" "github.com/sirupsen/logrus" ) @@ -53,10 +54,11 @@ type ArchivistaService struct { // Setup Archivista Service func (a *ArchivistaService) Setup() (*Server, error) { var ( - level logrus.Level - err error - sqlStore *sqlstore.Store - fileStore StorerGetter + level logrus.Level + err error + sqlStore *sqlstore.Store + fileStore StorerGetter + publisherStore []publisherstore.Publisher ) serverOpts := make([]Option, 0) @@ -128,6 +130,10 @@ func (a *ArchivistaService) Setup() (*Server, error) { serverOpts = append(serverOpts, WithArtifactStore(wds)) } + if a.Cfg.Publisher != nil { + publisherStore = publisherstore.New(a.Cfg) + serverOpts = append(serverOpts, WithPublishers(publisherStore)) + } // Create the Archivista server with all options server, err := New(a.Cfg, serverOpts...) if err != nil {