├── archivista.graphql ├── docs └── assets │ └── logo.png ├── .gitleaksignore ├── ent ├── migrate │ ├── migrations │ │ ├── mysql │ │ │ ├── 20250808191739_mysql.sql │ │ │ └── atlas.sum │ │ └── pgsql │ │ │ ├── 20250808191741_pgsql.sql │ │ │ └── atlas.sum │ └── migrate.go ├── runtime │ └── runtime.go ├── gql_transaction.go ├── schema │ ├── uuidgql │ │ └── uuidgql.go │ ├── timestamp.go │ ├── attestation.go │ ├── attestationcollection.go │ ├── payloaddigest.go │ ├── subjectdigest.go │ ├── signature.go │ ├── statement.go │ ├── attestationpolicy.go │ ├── dsse.go │ └── subject.go ├── entc.go ├── predicate │ └── predicate.go ├── enttest │ └── enttest.go ├── dsse_delete.go ├── subject_delete.go ├── signature_delete.go ├── statement_delete.go ├── timestamp_delete.go ├── attestation_delete.go ├── payloaddigest_delete.go ├── subjectdigest_delete.go ├── attestationpolicy_delete.go ├── attestationcollection_delete.go ├── attestationpolicy │ └── attestationpolicy.go ├── timestamp │ └── timestamp.go ├── attestation │ └── attestation.go ├── payloaddigest │ └── payloaddigest.go └── subjectdigest │ └── subjectdigest.go ├── test ├── invalid_payload.attestation.json └── deploy-services.sh ├── GOVERNANCE.md ├── MAINTAINERS.md ├── .gitignore ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature-request.md ├── dependabot.yml └── workflows │ ├── label-issues.yml │ ├── fossa.yml │ ├── golangci-lint.yml │ ├── dependency-review.yml │ ├── db-migrations.yml │ ├── verify-licence.yml │ ├── witness.yml │ ├── update-pre-commit-hooks.yml │ ├── codeql.yml │ └── scorecards.yml ├── gen.go ├── cmd ├── archivistactl │ ├── main.go │ └── cmd │ │ ├── root_test.go │ │ ├── store.go │ │ ├── root.go │ │ ├── search_test.go │ │ ├── iam_test.go │ │ ├── iam.go │ │ ├── retrieve_test.go │ │ ├── retrieve.go │ │ └── search.go └── archivista │ └── main.go ├── pkg ├── metadatastorage │ ├── parser.go │ ├── parserregistry │ │ └── registry.go │ ├── attestationcollection │ │ ├── parser_registry.go │ │ ├── parser_registry_test.go │ │ └── parserstorer.go │ └── sqlstore │ │ ├── iam.go │ │ └── utils.go ├── api │ ├── options.go │ ├── structs.go │ ├── upload.go │ ├── download.go │ └── graphql.go ├── publisherstore │ ├── publisherstore.go │ ├── rstuf │ │ ├── structs.go │ │ └── rstuf.go │ └── dapr │ │ └── http.go ├── objectstorage │ ├── filestore │ │ ├── file.go │ │ └── file_test.go │ └── blobstore │ │ └── minio.go └── artifactstore │ └── artifactstore_test.go ├── compose-psql.yml ├── Dockerfile-dev ├── .pre-commit-config.yaml ├── SECURITY.md ├── .golangci.yaml ├── resolver.go ├── Dockerfile ├── .gitlab-ci.yml ├── gqlgen.yml ├── DEPENDENCY.md ├── entrypoint-dev.sh ├── ent.resolvers.go ├── entrypoint.sh ├── Makefile ├── compose.yml ├── compose-dev.yml ├── SECURITY-INSIGHTS.yml └── .goreleaser.yaml /archivista.graphql: -------------------------------------------------------------------------------- 1 | scalar Time 2 | -------------------------------------------------------------------------------- /docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/in-toto/archivista/HEAD/docs/assets/logo.png -------------------------------------------------------------------------------- /.gitleaksignore: -------------------------------------------------------------------------------- 1 | test/package.attestation.json:generic-api-key:1 2 | test/fail.attestation.json:generic-api-key:1 3 | test/build.attestation.json:generic-api-key:1 4 | -------------------------------------------------------------------------------- /ent/migrate/migrations/mysql/20250808191739_mysql.sql: -------------------------------------------------------------------------------- 1 | -- Modify "dsses" table 2 | ALTER TABLE `dsses` ADD COLUMN `created_at` timestamp NULL; 3 | -- Modify "subjects" table 4 | ALTER TABLE `subjects` ADD COLUMN `created_at` timestamp NULL; 5 | -------------------------------------------------------------------------------- /ent/migrate/migrations/mysql/atlas.sum: -------------------------------------------------------------------------------- 1 | h1:O6S3E1WYf4e0DRuiVtMC+rriO4EsQy5UAIO/mRkozCI= 2 | 20240524112613_mysql.sql h1:P16hl/ui8F+xn7opuJT+GCQ8vnJEsQkZp8Q9PMOhrRI= 3 | 20250808191739_mysql.sql h1:AmhCFWr+PxS2lIdA1cc4lgjx4Fspi/sgnPuuItj/o5g= 4 | -------------------------------------------------------------------------------- /ent/migrate/migrations/pgsql/20250808191741_pgsql.sql: -------------------------------------------------------------------------------- 1 | -- Modify "dsses" table 2 | ALTER TABLE "dsses" ADD COLUMN "created_at" timestamptz NULL; 3 | -- Modify "subjects" table 4 | ALTER TABLE "subjects" ADD COLUMN "created_at" timestamptz NULL; 5 | -------------------------------------------------------------------------------- /ent/migrate/migrations/pgsql/atlas.sum: -------------------------------------------------------------------------------- 1 | h1:gbJ0ewlYG8EOT7Qs60syCuqj2RppBTDBCSo47KN5CN8= 2 | 20240524112615_pgsql.sql h1:HMRY5DPVr3SjgjpdkCY3+3Us5y5LvtSzNEBwoIND5sY= 3 | 20250808191741_pgsql.sql h1:g6V+TT8sGHon7iwgJTb5QCjopE3/oKbPA54jMIkRDlk= 4 | -------------------------------------------------------------------------------- /test/invalid_payload.attestation.json: -------------------------------------------------------------------------------- 1 | {"payload": "e30K", 2 | "payloadType": "application/vnd.in-toto+json", 3 | "signatures": [{"keyid": "ae2dcc989ea9c109a36e8eba5c4bc16d8fafcfe8e1a614164670d50aedacd647", 4 | "sig": "ahsjnNBEVNqo5/umfwQzWiVAvLx4yk3z6Xh+fsaWyxhmGD1syhOhMOkXapmVvEuscm6la9a/edQKNXXg02ghCw==" 5 | }]} 6 | -------------------------------------------------------------------------------- /ent/runtime/runtime.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package runtime 4 | 5 | // The schema-stitching logic is generated in github.com/in-toto/archivista/ent/runtime.go 6 | 7 | const ( 8 | Version = "v0.14.5" // Version of ent codegen. 9 | Sum = "h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4=" // Sum of ent codegen. 10 | ) 11 | -------------------------------------------------------------------------------- /GOVERNANCE.md: -------------------------------------------------------------------------------- 1 | As a sub-project of in-toto, this repository is subject to the governance by the in-toto steering committee. 2 | 3 | This repository is also subject to the in-toto and CNCF code of conduct. 4 | 5 | For more details, please reference the in-toto community repository: 6 | 7 | - [GOVERNANCE.md](https://github.com/in-toto/community/blob/main/GOVERNANCE.md) 8 | - [CODE_OF_CONDUCT.md](https://github.com/in-toto/community/blob/main/CODE-OF-CONDUCT.md) 9 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | | Name | GitHub | 4 | |-------------------------------|-----------------| 5 | | Kairo de Araujo (Independent) | [@kairoaraujo](https://github.com/kairoaraujo) | 6 | | Cole Kennedy (TestifySec) | [@colek42](https://github.com/colek42) | 7 | | John Kjell (ControlPlane) | [@jkjell](https://github.com/jkjell) | 8 | | Mikhail Swift (TestifySec) | [@mikhailswift](https://github.com/mikhailswift) | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | ./*.json 18 | .gitignore 19 | .idea/ 20 | 21 | ## protect against development environment being leaked out 22 | *.pem 23 | *.nix 24 | ./archivista 25 | ./archivistctl 26 | .witness.yaml 27 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What this PR does / why we need it 2 | 3 | Description 4 | 5 | ## Which issue(s) this PR fixes (optional) 6 | 7 | (optional, using `fixes #(, fixes #, ...)` format, will close the issue(s) when the PR gets merged)* 8 | 9 | Fixes # 10 | 11 | ## Acceptance Criteria Met 12 | 13 | - [ ] Docs changes if needed 14 | - [ ] Testing changes if needed 15 | - [ ] All workflow checks passing (automatically enforced) 16 | - [ ] All review conversations resolved (automatically enforced) 17 | - [ ] [DCO Sign-off](https://github.com/apps/dco) 18 | 19 | **Special notes for your reviewer**: -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '[Bug]: ' 5 | labels: ['bug', triage'] 6 | assignees: '' 7 | --- 8 | 9 | **What steps did you take and what happened:** 10 | 11 | [A clear and concise description of what the bug is.] 12 | 13 | **What did you expect to happen:** 14 | 15 | [Expected outcome listed here.] 16 | 17 | **Anything else you would like to add:** 18 | 19 | [Miscellaneous information that will assist in solving the issue.] 20 | 21 | **Environment:** 22 | 23 | - Witness version: 24 | - Architecture: 25 | - Attestors used: 26 | - Archivista version: 27 | -------------------------------------------------------------------------------- /gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package archivista 16 | 17 | //go:generate go run -mod=mod ./ent/entc.go 18 | //go:generate go run -mod=mod github.com/99designs/gqlgen 19 | -------------------------------------------------------------------------------- /ent/gql_transaction.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "database/sql/driver" 8 | "errors" 9 | ) 10 | 11 | // OpenTx opens a transaction and returns a transactional 12 | // context along with the created transaction. 13 | func (c *Client) OpenTx(ctx context.Context) (context.Context, driver.Tx, error) { 14 | tx, err := c.Tx(ctx) 15 | if err != nil { 16 | return nil, nil, err 17 | } 18 | ctx = NewTxContext(ctx, tx) 19 | ctx = NewContext(ctx, tx.Client()) 20 | return ctx, tx, nil 21 | } 22 | 23 | // OpenTxFromContext open transactions from client stored in context. 24 | func OpenTxFromContext(ctx context.Context) (context.Context, driver.Tx, error) { 25 | client := FromContext(ctx) 26 | if client == nil { 27 | return nil, nil, errors.New("no client attached to context") 28 | } 29 | return client.OpenTx(ctx) 30 | } 31 | -------------------------------------------------------------------------------- /cmd/archivistactl/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "os" 19 | 20 | "github.com/in-toto/archivista/cmd/archivistactl/cmd" 21 | ) 22 | 23 | func main() { 24 | if err := cmd.Execute(); err != nil { 25 | os.Exit(1) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pkg/metadatastorage/parser.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metadatastorage 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/google/uuid" 21 | "github.com/in-toto/archivista/ent" 22 | ) 23 | 24 | type ParserFunc func([]byte) (Storer, error) 25 | 26 | type Storer interface { 27 | Store(ctx context.Context, tx *ent.Tx, stmtID uuid.UUID) error 28 | } 29 | -------------------------------------------------------------------------------- /compose-psql.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | services: 16 | psql: 17 | image: postgres:16 18 | restart: always 19 | environment: 20 | POSTGRES_USER: testify 21 | POSTGRES_PASSWORD: example 22 | 23 | archivista: 24 | environment: 25 | ARCHIVISTA_SQL_STORE_BACKEND: PSQL 26 | ARCHIVISTA_SQL_STORE_CONNECTION_STRING: postgresql://testify:example@psql?sslmode=disable 27 | -------------------------------------------------------------------------------- /Dockerfile-dev: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM golang:1.25.5-alpine@sha256:26111811bc967321e7b6f852e914d14bede324cd1accb7f81811929a6a57fea9 AS build 16 | WORKDIR /src 17 | RUN apk update && apk add --no-cache file git curl 18 | RUN curl -sSf https://atlasgo.sh | sh 19 | ENV GOMODCACHE /root/.cache/gocache 20 | RUN go install github.com/githubnemo/CompileDaemon@v1.4.0 21 | ENTRYPOINT ["sh", "entrypoint-dev.sh"] 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '[Feat]: ' 5 | labels: ['feature', 'triage'] 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the solution you'd like:** 11 | 12 | [A clear and concise description of what you want to happen.] 13 | 14 | **User value:** 15 | 16 | [Why will this feature be valuable to you? Why will this be valuable to others?] 17 | 18 | **Expected behavior:** 19 | 20 | [What would you like to see happen] 21 | 22 | **Proposed solution:** 23 | 24 | [If you're able, describe possible solution workflow] 25 | 26 | **Anything else you would like to add:** 27 | 28 | [Miscellaneous information that will assist in solving the issue.] 29 | 30 | **Testing changes required:** 31 | 32 | [List possible testing changes required, if none please explain, if unsure assignee will assist] 33 | 34 | **Documentation changes required:** 35 | 36 | [List possible documentation changes required, if none please explain, if unsure assignee will assist] 37 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | repos: 16 | - repo: https://github.com/gitleaks/gitleaks 17 | rev: v8.30.0 18 | hooks: 19 | - id: gitleaks 20 | - repo: https://github.com/golangci/golangci-lint 21 | rev: v2.7.2 22 | hooks: 23 | - id: golangci-lint 24 | - repo: https://github.com/jumanjihouse/pre-commit-hooks 25 | rev: 3.0.0 26 | hooks: 27 | - id: shellcheck 28 | - repo: https://github.com/pre-commit/pre-commit-hooks 29 | rev: v6.0.0 30 | hooks: 31 | - id: end-of-file-fixer 32 | - id: trailing-whitespace 33 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Security Bulletins 4 | 5 | See current security bullentins on GitHub: https://github.com/in-toto/archivista/security/advisories 6 | 7 | For information regarding the security of this project please join: 8 | 9 | * in-toto-archivista on CNCF Slack 10 | 11 | ## Reporting a Vulnerability 12 | 13 | Please use the below process to report a vulnerability to the project: 14 | 15 | Web Form: 16 | 17 | 1. Please visit https://github.com/in-toto/archivista/security/advisories/new 18 | * You will receive a confirmation email upon submission 19 | 1. You may be contacted by a maintainer to further discuss the reported item 20 | within 3 days. Please bear with us as we seek to understand the breadth 21 | and scope of the reported problem, recreate it, and confirm if there is an 22 | vulnerability present. 23 | 24 | This project follows a 30 day disclosure timeline. 25 | 26 | ## Supported Versions 27 | 28 | Information regarding supported versions of this project can be found on 29 | in the below table: 30 | 31 | | Version | Supported | 32 | | --- | --- | 33 | | Latest | :white_check_mark: | 34 | | <= Latest - 2 | :x: | 35 | -------------------------------------------------------------------------------- /pkg/metadatastorage/parserregistry/registry.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package parserregistry 16 | 17 | import ( 18 | "github.com/in-toto/archivista/pkg/metadatastorage" 19 | "github.com/in-toto/archivista/pkg/metadatastorage/attestationcollection" 20 | ) 21 | 22 | var ( 23 | parsersByPredicate = map[string]metadatastorage.ParserFunc{ 24 | attestationcollection.Predicate: attestationcollection.Parse, 25 | } 26 | ) 27 | 28 | func ParserForPredicate(predicate string) (metadatastorage.ParserFunc, bool) { 29 | pf, ok := parsersByPredicate[predicate] 30 | return pf, ok 31 | } 32 | -------------------------------------------------------------------------------- /ent/schema/uuidgql/uuidgql.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-present Facebook 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package uuidgql 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "strconv" 21 | 22 | "github.com/99designs/gqlgen/graphql" 23 | "github.com/google/uuid" 24 | ) 25 | 26 | func MarshalUUID(u uuid.UUID) graphql.Marshaler { 27 | return graphql.WriterFunc(func(w io.Writer) { 28 | _, _ = io.WriteString(w, strconv.Quote(u.String())) 29 | }) 30 | } 31 | 32 | func UnmarshalUUID(v interface{}) (u uuid.UUID, err error) { 33 | s, ok := v.(string) 34 | if !ok { 35 | return u, fmt.Errorf("invalid type %T, expect string", v) 36 | } 37 | return uuid.Parse(s) 38 | } 39 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | version: "2" 16 | linters: 17 | enable: 18 | - gosec 19 | exclusions: 20 | generated: lax 21 | presets: 22 | - comments 23 | - common-false-positives 24 | - legacy 25 | - std-error-handling 26 | rules: 27 | - linters: 28 | - gosec 29 | path: _test.go 30 | paths: 31 | - third_party$ 32 | - builtin$ 33 | - examples$ 34 | issues: 35 | max-same-issues: 50 36 | formatters: 37 | exclusions: 38 | generated: lax 39 | paths: 40 | - third_party$ 41 | - builtin$ 42 | - examples$ 43 | -------------------------------------------------------------------------------- /resolver.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package archivista 16 | 17 | // This file will not be regenerated automatically. 18 | // 19 | // It serves as dependency injection for your app, add any dependencies you require here. 20 | 21 | import ( 22 | "github.com/99designs/gqlgen/graphql" 23 | "github.com/in-toto/archivista/ent" 24 | ) 25 | 26 | // Resolver is the resolver root. 27 | type Resolver struct{ client *ent.Client } 28 | 29 | // NewSchema creates a graphql executable schema. 30 | func NewSchema(client *ent.Client) graphql.ExecutableSchema { 31 | return NewExecutableSchema(Config{ 32 | Resolvers: &Resolver{client}, 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /ent/schema/timestamp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schema 16 | 17 | import ( 18 | "entgo.io/ent" 19 | "entgo.io/ent/schema/edge" 20 | "entgo.io/ent/schema/field" 21 | "github.com/google/uuid" 22 | ) 23 | 24 | type Timestamp struct { 25 | ent.Schema 26 | } 27 | 28 | func (Timestamp) Fields() []ent.Field { 29 | return []ent.Field{ 30 | field.UUID("id", uuid.UUID{}).Default(uuid.New).Immutable().Unique(), 31 | field.String("type"), 32 | field.Time("timestamp"), 33 | } 34 | } 35 | 36 | func (Timestamp) Edges() []ent.Edge { 37 | return []ent.Edge{ 38 | edge.From("signature", Signature.Type).Ref("timestamps").Unique(), 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ent/entc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build ignore 16 | 17 | package main 18 | 19 | import ( 20 | "log" 21 | 22 | "entgo.io/contrib/entgql" 23 | "entgo.io/ent/entc" 24 | "entgo.io/ent/entc/gen" 25 | ) 26 | 27 | func main() { 28 | ex, err := entgql.NewExtension( 29 | entgql.WithWhereInputs(true), 30 | entgql.WithConfigPath("./gqlgen.yml"), 31 | entgql.WithSchemaGenerator(), 32 | entgql.WithSchemaPath("./ent.graphql"), 33 | ) 34 | 35 | if err != nil { 36 | log.Fatalf("creating entgql extension: %v", err) 37 | } 38 | if err := entc.Generate("./ent/schema", &gen.Config{}, entc.Extensions(ex)); err != nil { 39 | log.Fatalf("running ent codegen: %v", err) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | version: 2 16 | updates: 17 | - package-ecosystem: "gomod" # See documentation for possible values 18 | directory: "/" # Location of package manifests 19 | schedule: 20 | interval: "weekly" 21 | commit-message: 22 | prefix: "chore" 23 | - package-ecosystem: "github-actions" 24 | directory: "/" 25 | schedule: 26 | interval: "weekly" 27 | commit-message: 28 | prefix: "chore" 29 | groups: 30 | github-actions: 31 | patterns: 32 | - "*" 33 | - package-ecosystem: docker 34 | directory: / 35 | schedule: 36 | interval: daily 37 | commit-message: 38 | prefix: "chore" 39 | -------------------------------------------------------------------------------- /ent/predicate/predicate.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package predicate 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | ) 8 | 9 | // Attestation is the predicate function for attestation builders. 10 | type Attestation func(*sql.Selector) 11 | 12 | // AttestationCollection is the predicate function for attestationcollection builders. 13 | type AttestationCollection func(*sql.Selector) 14 | 15 | // AttestationPolicy is the predicate function for attestationpolicy builders. 16 | type AttestationPolicy func(*sql.Selector) 17 | 18 | // Dsse is the predicate function for dsse builders. 19 | type Dsse func(*sql.Selector) 20 | 21 | // PayloadDigest is the predicate function for payloaddigest builders. 22 | type PayloadDigest func(*sql.Selector) 23 | 24 | // Signature is the predicate function for signature builders. 25 | type Signature func(*sql.Selector) 26 | 27 | // Statement is the predicate function for statement builders. 28 | type Statement func(*sql.Selector) 29 | 30 | // Subject is the predicate function for subject builders. 31 | type Subject func(*sql.Selector) 32 | 33 | // SubjectDigest is the predicate function for subjectdigest builders. 34 | type SubjectDigest func(*sql.Selector) 35 | 36 | // Timestamp is the predicate function for timestamp builders. 37 | type Timestamp func(*sql.Selector) 38 | -------------------------------------------------------------------------------- /pkg/metadatastorage/attestationcollection/parser_registry.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package attestationcollection 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "github.com/in-toto/archivista/ent" 21 | "log" 22 | ) 23 | 24 | var registeredParsers map[string]AttestationParser 25 | 26 | func init() { 27 | registeredParsers = make(map[string]AttestationParser) 28 | } 29 | 30 | func Register(attestationType string, parser AttestationParser) { 31 | registeredParsers[attestationType] = parser 32 | log.Printf("parser registered: %s", attestationType) 33 | } 34 | 35 | type AttestationParser func(ctx context.Context, tx *ent.Tx, attestation *ent.Attestation, attestationType string, message json.RawMessage) error 36 | -------------------------------------------------------------------------------- /cmd/archivistactl/cmd/root_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "bytes" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/suite" 22 | ) 23 | 24 | // Test Suite: UT Root 25 | type UTRootSuite struct { 26 | suite.Suite 27 | } 28 | 29 | func TestUTRootSuite(t *testing.T) { 30 | suite.Run(t, new(UTRootSuite)) 31 | } 32 | 33 | func (ut *UTRootSuite) Test_Root() { 34 | output := bytes.NewBufferString("") 35 | rootCmd.SetOut(output) 36 | rootCmd.SetErr(output) 37 | rootCmd.SetArgs([]string{"help"}) 38 | err := Execute() 39 | if err != nil { 40 | ut.FailNow(err.Error()) 41 | } 42 | actual := output.String() 43 | ut.Contains(actual, "A utility to interact with an archivista server") 44 | 45 | } 46 | -------------------------------------------------------------------------------- /pkg/api/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package api 15 | 16 | import "net/http" 17 | 18 | type RequestOption func(*requestOptions) 19 | 20 | type requestOptions struct { 21 | additionalHeaders http.Header 22 | } 23 | 24 | func WithHeaders(h http.Header) RequestOption { 25 | return func(ro *requestOptions) { 26 | if h != nil { 27 | ro.additionalHeaders = h.Clone() 28 | } 29 | } 30 | } 31 | 32 | func applyRequestOptions(req *http.Request, requestOpts ...RequestOption) *http.Request { 33 | if req == nil { 34 | return nil 35 | } 36 | 37 | opts := &requestOptions{} 38 | for _, opt := range requestOpts { 39 | if opt == nil { 40 | continue 41 | } 42 | 43 | opt(opts) 44 | } 45 | 46 | if opts.additionalHeaders != nil { 47 | req.Header = opts.additionalHeaders 48 | } 49 | 50 | return req 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/label-issues.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Label issues 16 | on: 17 | issues: 18 | types: 19 | - reopened 20 | - opened 21 | permissions: 22 | contents: read 23 | 24 | jobs: 25 | label_issues: 26 | runs-on: ubuntu-latest 27 | permissions: 28 | issues: write 29 | steps: 30 | - name: Harden Runner 31 | uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 32 | with: 33 | egress-policy: audit 34 | 35 | - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 36 | with: 37 | script: | 38 | github.rest.issues.addLabels({ 39 | issue_number: context.issue.number, 40 | owner: context.repo.owner, 41 | repo: context.repo.repo, 42 | labels: ["needs triage"] 43 | }) 44 | -------------------------------------------------------------------------------- /.github/workflows/fossa.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: "Fossa Scan" 16 | 17 | on: 18 | push: 19 | branches: ["main"] 20 | pull_request: 21 | # The branches below must be a subset of the branches above 22 | branches: ["main"] 23 | schedule: 24 | - cron: "0 0 * * 1" 25 | 26 | permissions: 27 | contents: read 28 | 29 | jobs: 30 | fossa-scan: 31 | env: 32 | FOSSA_API_KEY: ${{ secrets.fossaApiKey }} 33 | runs-on: ubuntu-latest 34 | steps: 35 | - if: ${{ env.FOSSA_API_KEY != '' }} 36 | name: "Checkout Code" 37 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 38 | - if: ${{ env.FOSSA_API_KEY != '' }} 39 | name: "Run FOSSA Scan" 40 | uses: fossas/fossa-action@3ebcea1862c6ffbd5cf1b4d0bd6b3fe7bd6f2cac # v1.7.0 41 | with: 42 | api-key: ${{ env.FOSSA_API_KEY }} 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM golang:1.25.5-alpine@sha256:26111811bc967321e7b6f852e914d14bede324cd1accb7f81811929a6a57fea9 AS build 16 | WORKDIR /src 17 | RUN apk update && apk add --no-cache file git curl 18 | RUN curl -sSf https://atlasgo.sh | sh 19 | ENV GOMODCACHE=/root/.cache/gocache 20 | RUN --mount=target=. --mount=target=/root/.cache,type=cache \ 21 | CGO_ENABLED=0 go build -o /out/ -ldflags '-s -d -w' ./cmd/...; \ 22 | file /out/archivista | grep "statically linked" 23 | 24 | FROM alpine:3.23.0@sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375 25 | COPY --from=build /out/archivista /bin/archivista 26 | COPY --from=build /out/archivistactl /bin/archivistactl 27 | COPY --from=build /usr/local/bin/atlas /bin/atlas 28 | ADD entrypoint.sh /bin/entrypoint.sh 29 | ADD ent/migrate/migrations /archivista/migrations 30 | RUN mkdir /tmp/archivista 31 | ENTRYPOINT ["sh", "/bin/entrypoint.sh"] 32 | -------------------------------------------------------------------------------- /ent/schema/attestation.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schema 16 | 17 | import ( 18 | "entgo.io/ent" 19 | "entgo.io/ent/schema/edge" 20 | "entgo.io/ent/schema/field" 21 | "entgo.io/ent/schema/index" 22 | "github.com/google/uuid" 23 | ) 24 | 25 | // Attestation represents an attestation from a witness attestation collection 26 | type Attestation struct { 27 | ent.Schema 28 | } 29 | 30 | func (Attestation) Fields() []ent.Field { 31 | return []ent.Field{ 32 | field.UUID("id", uuid.UUID{}).Default(uuid.New).Immutable().Unique(), 33 | field.String("type").NotEmpty(), 34 | } 35 | } 36 | 37 | func (Attestation) Edges() []ent.Edge { 38 | return []ent.Edge{ 39 | edge.From("attestation_collection", AttestationCollection.Type).Ref("attestations").Unique().Required(), 40 | } 41 | } 42 | 43 | func (Attestation) Indexes() []ent.Index { 44 | return []ent.Index{ 45 | index.Fields("type"), 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pkg/metadatastorage/attestationcollection/parser_registry_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package attestationcollection 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "testing" 21 | 22 | "github.com/in-toto/archivista/ent" 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func TestRegister(t *testing.T) { 27 | // Define a mock parser function 28 | mockParser := func(ctx context.Context, tx *ent.Tx, attestation *ent.Attestation, attestationType string, message json.RawMessage) error { 29 | return nil 30 | } 31 | 32 | // Register the mock parser 33 | Register("mockType", mockParser) 34 | 35 | // Check if the parser is registered 36 | registeredParser, exists := registeredParsers["mockType"] 37 | var typedParser AttestationParser = mockParser 38 | assert.True(t, exists, "Parser should be registered") 39 | assert.IsType(t, typedParser, registeredParser, "Registered parser should match the mock parser") 40 | } 41 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | stages: 16 | - build 17 | 18 | build-and-push-server: 19 | variables: 20 | KO_DOCKER_REPO: registry.gitlab.com/testifysec/judge-platform/archivista/archivista 21 | stage: build 22 | image: 23 | name: registry.gitlab.com/testifysec/docker-images/ko:0.11.2-go1.19.2 24 | entrypoint: [""] 25 | script: 26 | - ko auth login -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD} ${CI_REGISTRY} 27 | - ko publish --bare --tags=${CI_COMMIT_SHORT_SHA} ./cmd/archivista 28 | 29 | build-and-push-client: 30 | variables: 31 | KO_DOCKER_REPO: registry.gitlab.com/testifysec/judge-platform/archivista/archivistactl 32 | stage: build 33 | image: 34 | name: registry.gitlab.com/testifysec/docker-images/ko:0.11.2-go1.19.2 35 | entrypoint: [""] 36 | script: 37 | - ko auth login -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD} ${CI_REGISTRY} 38 | - ko publish --bare --tags ${CI_COMMIT_SHORT_SHA} ./cmd/archivistactl 39 | -------------------------------------------------------------------------------- /gqlgen.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | schema: 16 | - archivista.graphql 17 | - ent.graphql 18 | 19 | resolver: 20 | # Tell gqlgen to generate resolvers next to the schema file. 21 | layout: follow-schema 22 | dir: . 23 | 24 | # gqlgen will search for any type names in the schema in the generated 25 | # ent package. If they match it will use them, otherwise it will new ones. 26 | autobind: 27 | - github.com/in-toto/archivista/ent 28 | 29 | models: 30 | ID: 31 | model: 32 | - github.com/in-toto/archivista/ent/schema/uuidgql.UUID 33 | Uint64: 34 | model: 35 | - github.com/99designs/gqlgen/graphql.Uint64 36 | UUID: 37 | model: 38 | - github.com/in-toto/archivista/ent/schema/uuidgql.UUID 39 | # Node: 40 | # model: 41 | # # ent.Noder is the new interface generated by the Node template. 42 | # - github.com/in-toto/archivista/ent.Noder 43 | # Subject: 44 | # model: 45 | # - github.com/in-toto/archivista/ent.Subject 46 | -------------------------------------------------------------------------------- /ent/schema/attestationcollection.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schema 16 | 17 | import ( 18 | "entgo.io/ent" 19 | "entgo.io/ent/schema/edge" 20 | "entgo.io/ent/schema/field" 21 | "entgo.io/ent/schema/index" 22 | "github.com/google/uuid" 23 | ) 24 | 25 | // AttestationCollection represents a witness attestation collection 26 | type AttestationCollection struct { 27 | ent.Schema 28 | } 29 | 30 | func (AttestationCollection) Fields() []ent.Field { 31 | return []ent.Field{ 32 | field.UUID("id", uuid.UUID{}).Default(uuid.New).Immutable().Unique(), 33 | field.String("name").NotEmpty(), 34 | } 35 | } 36 | 37 | func (AttestationCollection) Edges() []ent.Edge { 38 | return []ent.Edge{ 39 | edge.To("attestations", Attestation.Type), 40 | edge.From("statement", Statement.Type).Ref("attestation_collections").Unique().Required(), 41 | } 42 | } 43 | 44 | func (AttestationCollection) Indexes() []ent.Index { 45 | return []ent.Index{ 46 | index.Fields("name"), 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: golangci-lint 16 | on: 17 | push: 18 | tags: 19 | - v* 20 | branches: 21 | - master 22 | - main 23 | pull_request: 24 | permissions: 25 | contents: read 26 | pull-requests: read 27 | jobs: 28 | golangci: 29 | name: lint 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Harden Runner 33 | uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 34 | with: 35 | egress-policy: audit 36 | 37 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 38 | 39 | - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 40 | with: 41 | go-version-file: "go.mod" 42 | 43 | - name: golangci-lint 44 | uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 45 | with: 46 | version: latest 47 | args: --timeout=3m 48 | -------------------------------------------------------------------------------- /ent/schema/payloaddigest.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schema 16 | 17 | import ( 18 | "entgo.io/ent" 19 | "entgo.io/ent/schema/edge" 20 | "entgo.io/ent/schema/field" 21 | "entgo.io/ent/schema/index" 22 | "github.com/google/uuid" 23 | ) 24 | 25 | // PayloadDigest represents the digest of the payload of a DSSE envelope 26 | type PayloadDigest struct { 27 | ent.Schema 28 | } 29 | 30 | // Fields of the Digest. 31 | func (PayloadDigest) Fields() []ent.Field { 32 | return []ent.Field{ 33 | field.UUID("id", uuid.UUID{}).Default(uuid.New).Immutable().Unique(), 34 | field.String("algorithm").NotEmpty(), 35 | field.String("value").NotEmpty(), 36 | } 37 | } 38 | 39 | // Edges of the Digest. 40 | func (PayloadDigest) Edges() []ent.Edge { 41 | return []ent.Edge{ 42 | edge.From("dsse", Dsse.Type). 43 | Ref("payload_digests"). 44 | Unique(), 45 | } 46 | } 47 | 48 | func (PayloadDigest) Indexes() []ent.Index { 49 | return []ent.Index{ 50 | index.Fields("value"), 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ent/schema/subjectdigest.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schema 16 | 17 | import ( 18 | "entgo.io/ent" 19 | "entgo.io/ent/schema/edge" 20 | "entgo.io/ent/schema/field" 21 | "entgo.io/ent/schema/index" 22 | "github.com/google/uuid" 23 | ) 24 | 25 | // SubjectDigest represents the digests of a subject from an in-toto statement 26 | type SubjectDigest struct { 27 | ent.Schema 28 | } 29 | 30 | // Fields of the Digest. 31 | func (SubjectDigest) Fields() []ent.Field { 32 | return []ent.Field{ 33 | field.UUID("id", uuid.UUID{}).Default(uuid.New).Immutable().Unique(), 34 | field.String("algorithm").NotEmpty(), 35 | field.String("value").NotEmpty(), 36 | } 37 | } 38 | 39 | // Edges of the Digest. 40 | func (SubjectDigest) Edges() []ent.Edge { 41 | return []ent.Edge{ 42 | edge.From("subject", Subject.Type). 43 | Ref("subject_digests"). 44 | Unique(), 45 | } 46 | } 47 | 48 | func (SubjectDigest) Indexes() []ent.Index { 49 | return []ent.Index{ 50 | // Index on the "value" field. 51 | index.Fields("value"), 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ent/schema/signature.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schema 16 | 17 | import ( 18 | "entgo.io/ent" 19 | "entgo.io/ent/dialect" 20 | "entgo.io/ent/schema/edge" 21 | "entgo.io/ent/schema/field" 22 | "entgo.io/ent/schema/index" 23 | "github.com/google/uuid" 24 | ) 25 | 26 | // Signature represents signatures on a DSSE envelope 27 | type Signature struct { 28 | ent.Schema 29 | } 30 | 31 | // Fields of the Signature. 32 | func (Signature) Fields() []ent.Field { 33 | return []ent.Field{ 34 | field.UUID("id", uuid.UUID{}).Default(uuid.New).Immutable().Unique(), 35 | field.String("key_id"), 36 | field.String("signature").NotEmpty().SchemaType(map[string]string{dialect.MySQL: "text"}), 37 | } 38 | } 39 | 40 | // Edges of the Signature. 41 | func (Signature) Edges() []ent.Edge { 42 | return []ent.Edge{ 43 | edge.From("dsse", Dsse.Type).Ref("signatures").Unique(), 44 | edge.To("timestamps", Timestamp.Type), 45 | } 46 | } 47 | 48 | func (Signature) Indexes() []ent.Index { 49 | return []ent.Index{ 50 | index.Fields("key_id"), 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pkg/publisherstore/publisherstore.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package publisherstore 15 | 16 | import ( 17 | "context" 18 | "strings" 19 | 20 | "github.com/in-toto/archivista/pkg/config" 21 | "github.com/in-toto/archivista/pkg/publisherstore/dapr" 22 | "github.com/in-toto/archivista/pkg/publisherstore/rstuf" 23 | "github.com/sirupsen/logrus" 24 | ) 25 | 26 | type Publisher interface { 27 | Publish(ctx context.Context, gitoid string, payload []byte) error 28 | } 29 | 30 | func New(config *config.Config) []Publisher { 31 | var publisherStore []Publisher 32 | for _, pubType := range config.Publisher { 33 | pubType = strings.ToUpper(pubType) // Normalize the input 34 | switch pubType { 35 | case "DAPR": 36 | publisherStore = append(publisherStore, dapr.NewPublisher(config)) 37 | logrus.Info("Using publisher: DAPR") 38 | 39 | case "RSTUF": 40 | publisherStore = append(publisherStore, rstuf.NewPublisher(config)) 41 | logrus.Info("Using publisher: RSTUF") 42 | default: 43 | logrus.Errorf("unsupported publisher type: %s", pubType) 44 | } 45 | } 46 | return publisherStore 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Dependency Review Action 16 | # 17 | # This Action will scan dependency manifest files that change as part of a Pull Request, 18 | # surfacing known-vulnerable versions of the packages declared or updated in the PR. 19 | # Once installed, if the workflow run is marked as required, 20 | # PRs introducing known-vulnerable packages will be blocked from merging. 21 | # 22 | # Source repository: https://github.com/actions/dependency-review-action 23 | name: 'Dependency Review' 24 | on: [pull_request] 25 | 26 | permissions: 27 | contents: read 28 | 29 | jobs: 30 | dependency-review: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Harden Runner 34 | uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 35 | with: 36 | egress-policy: audit 37 | 38 | - name: 'Checkout Repository' 39 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 40 | - name: 'Dependency Review' 41 | uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 42 | -------------------------------------------------------------------------------- /.github/workflows/db-migrations.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: db-migrations 16 | on: 17 | push: 18 | tags: 19 | - v* 20 | branches: 21 | - master 22 | - main 23 | pull_request: 24 | paths: 25 | - 'ent/**' 26 | - 'ent.*' 27 | - '.github/workflows/db-migrations.yml' 28 | workflow_dispatch: 29 | 30 | permissions: 31 | contents: read 32 | pull-requests: read 33 | 34 | jobs: 35 | db-migrations: 36 | name: db-migrations 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 40 | 41 | - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 42 | with: 43 | go-version-file: "go.mod" 44 | 45 | - name: Check DB Migrations 46 | run: | 47 | curl -sSf https://atlasgo.sh | sh 48 | before=$(find ent/migrate/migrations/ -type f | wc -l | awk '{ print $1 }') 49 | make db-migrations 50 | after=$(find ent/migrate/migrations/ -type f | wc -l | awk '{ print $1 }') 51 | if [[ $before -lt $after ]]; then echo "missing 'make db-migrations'"; exit 1; fi 52 | -------------------------------------------------------------------------------- /ent/schema/statement.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schema 16 | 17 | import ( 18 | "entgo.io/contrib/entgql" 19 | "entgo.io/ent" 20 | "entgo.io/ent/schema/edge" 21 | "entgo.io/ent/schema/field" 22 | "entgo.io/ent/schema/index" 23 | "github.com/google/uuid" 24 | ) 25 | 26 | // Statement represents an in-toto statement from an archived dsse envelope 27 | type Statement struct { 28 | ent.Schema 29 | } 30 | 31 | // Fields of the Statement. 32 | func (Statement) Fields() []ent.Field { 33 | return []ent.Field{ 34 | field.UUID("id", uuid.UUID{}).Default(uuid.New).Immutable().Unique(), 35 | field.String("predicate").NotEmpty(), 36 | } 37 | } 38 | 39 | // Edges of the Statement. 40 | func (Statement) Edges() []ent.Edge { 41 | return []ent.Edge{ 42 | edge.To("subjects", Subject.Type).Annotations(entgql.RelayConnection()), 43 | edge.To("policy", AttestationPolicy.Type).Unique(), 44 | edge.To("attestation_collections", AttestationCollection.Type).Unique(), 45 | 46 | edge.From("dsse", Dsse.Type).Ref("statement"), 47 | } 48 | } 49 | 50 | func (Statement) Indexes() []ent.Index { 51 | return []ent.Index{ 52 | index.Fields("predicate"), 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pkg/publisherstore/rstuf/structs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package rstuf 16 | 17 | // Hashes represents the Hashes structure 18 | type Hashes struct { 19 | Sha256 string `json:"sha256"` 20 | } 21 | 22 | // ArtifactInfo represents the ArtifactInfo structure 23 | type ArtifactInfo struct { 24 | Length int `json:"length"` 25 | Hashes Hashes `json:"hashes"` 26 | Custom map[string]any `json:"custom,omitempty"` 27 | } 28 | 29 | // Artifact represents the Artifact structure 30 | type Artifact struct { 31 | Path string `json:"path"` 32 | Info ArtifactInfo `json:"info"` 33 | } 34 | 35 | // ArtifactPayload represents the payload structure 36 | type ArtifactPayload struct { 37 | Artifacts []Artifact `json:"artifacts"` 38 | AddTaskIDToCustom bool `json:"add_task_id_to_custom"` 39 | PublishTargets bool `json:"publish_targets"` 40 | } 41 | 42 | type ArtifactsResponse struct { 43 | Artifacts []string `json:"artifacts"` 44 | TaskId string `json:"task_id"` 45 | LastUpdate string `json:"last_update"` 46 | Message string `json:"message"` 47 | } 48 | 49 | type Response struct { 50 | Data ArtifactsResponse `json:"data"` 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/verify-licence.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Verify License 16 | on: 17 | workflow_dispatch: 18 | push: 19 | branches: ['main', 'release-*'] 20 | pull_request: 21 | permissions: 22 | contents: read 23 | 24 | jobs: 25 | license-check: 26 | name: license boilerplate check 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Harden Runner 30 | uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 31 | with: 32 | egress-policy: audit 33 | 34 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 35 | - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 36 | with: 37 | go-version-file: "go.mod" 38 | - name: Install addlicense 39 | run: go install github.com/google/addlicense@v1.1.1 40 | - name: Check license headers 41 | run: | 42 | set -e 43 | addlicense --check -l apache -c 'The Archivista Contributors' --ignore "generated.go" --ignore "ent.resolvers.go" --ignore "ent/migrate/migrations/**" --ignore "docs/**" --ignore "chart/**/*.yaml" -v ./ 44 | -------------------------------------------------------------------------------- /ent/schema/attestationpolicy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schema 16 | 17 | import ( 18 | "entgo.io/contrib/entgql" 19 | "entgo.io/ent" 20 | "entgo.io/ent/schema" 21 | "entgo.io/ent/schema/edge" 22 | "entgo.io/ent/schema/field" 23 | "entgo.io/ent/schema/index" 24 | "github.com/google/uuid" 25 | ) 26 | 27 | // Attestation represents an attestation from a witness attestation collection 28 | type AttestationPolicy struct { 29 | ent.Schema 30 | } 31 | 32 | func (AttestationPolicy) Fields() []ent.Field { 33 | return []ent.Field{ 34 | field.UUID("id", uuid.UUID{}).Default(uuid.New).Immutable().Unique(), 35 | field.String("name").NotEmpty(), 36 | } 37 | } 38 | 39 | // Edges of the AttestationPolicy. 40 | func (AttestationPolicy) Edges() []ent.Edge { 41 | return []ent.Edge{ 42 | edge.From("statement", Statement.Type). 43 | Ref("policy").Unique(), 44 | } 45 | } 46 | 47 | func (AttestationPolicy) Indexes() []ent.Index { 48 | return []ent.Index{ 49 | index.Fields("name"), 50 | } 51 | } 52 | 53 | func (AttestationPolicy) Annotations() []schema.Annotation { 54 | return []schema.Annotation{ 55 | entgql.RelayConnection(), 56 | entgql.QueryField(), 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /DEPENDENCY.md: -------------------------------------------------------------------------------- 1 | # Environment Dependencies Policy 2 | 3 | ## Purpose 4 | 5 | This policy describes how Archivista maintainers consume third-party packages. 6 | 7 | ## Scope 8 | 9 | This policy applies to all Archivista maintainers and all third-party packages used in the Archivista project. 10 | 11 | ## Policy 12 | 13 | Archivista maintainers must follow these guidelines when consuming third-party packages: 14 | 15 | - Only use third-party packages that are necessary for the functionality of Archivista. 16 | - Use the latest version of all third-party packages whenever possible. 17 | - Avoid using third-party packages that are known to have security vulnerabilities. 18 | - Pin all third-party packages to specific versions in the Archivista codebase. 19 | - Use a dependency management tool, such as Go modules, to manage third-party dependencies. 20 | 21 | ## Procedure 22 | 23 | When adding a new third-party package to Archivista, maintainers must follow these steps: 24 | 25 | 1. Evaluate the need for the package. Is it necessary for the functionality of Archivista? 26 | 2. Research the package. Is it well-maintained? Does it have a good reputation? 27 | 3. Choose a version of the package. Use the latest version whenever possible. 28 | 4. Pin the package to the specific version in the Archivista codebase. 29 | 5. Update the Archivista documentation to reflect the new dependency. 30 | 31 | ## Enforcement 32 | 33 | This policy is enforced by the Archivista maintainers. 34 | Maintainers are expected to review each other's code changes to ensure that they comply with this policy. 35 | 36 | ## Exceptions 37 | 38 | Exceptions to this policy may be granted by the Archivista project lead on a case-by-case basis. 39 | 40 | ## Credits 41 | 42 | This policy was adapted from the [Kubescape Community](https://github.com/kubescape/kubescape/blob/master/docs/environment-dependencies-policy.md) 43 | -------------------------------------------------------------------------------- /ent/schema/dsse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schema 16 | 17 | import ( 18 | "time" 19 | 20 | "entgo.io/contrib/entgql" 21 | "entgo.io/ent" 22 | "entgo.io/ent/schema" 23 | "entgo.io/ent/schema/edge" 24 | "entgo.io/ent/schema/field" 25 | "github.com/google/uuid" 26 | ) 27 | 28 | // Dsse represents some metadata about an archived DSSE envelope 29 | type Dsse struct { 30 | ent.Schema 31 | } 32 | 33 | // Fields of the Statement. 34 | func (Dsse) Fields() []ent.Field { 35 | return []ent.Field{ 36 | field.UUID("id", uuid.UUID{}).Default(uuid.New).Immutable().Unique(), 37 | field.Time("created_at").Default(time.Now).Immutable().Optional().Nillable().Annotations(entgql.OrderField("CREATED_AT")), 38 | field.String("gitoid_sha256").NotEmpty().Unique(), 39 | field.String("payload_type").NotEmpty(), 40 | } 41 | } 42 | 43 | // Edges of the Statement. 44 | func (Dsse) Edges() []ent.Edge { 45 | return []ent.Edge{ 46 | edge.To("statement", Statement.Type).Unique(), 47 | edge.To("signatures", Signature.Type), 48 | edge.To("payload_digests", PayloadDigest.Type), 49 | } 50 | } 51 | 52 | func (Dsse) Annotations() []schema.Annotation { 53 | return []schema.Annotation{ 54 | entgql.RelayConnection(), 55 | entgql.QueryField(), 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /entrypoint-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2023 The Archivista Contributors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | env 16 | if [[ -z $ARCHIVISTA_SQL_STORE_BACKEND ]]; then 17 | SQL_TYPE="MYSQL" 18 | else 19 | SQL_TYPE=$(echo "$ARCHIVISTA_SQL_STORE_BACKEND" | tr '[:lower:]' '[:upper:]') 20 | fi 21 | 22 | case $SQL_TYPE in 23 | MYSQL) 24 | if [[ -z $ARCHIVISTA_SQL_STORE_CONNECTION_STRING ]]; then 25 | ARCHIVISTA_SQL_STORE_CONNECTION_STRING="root:example@db/testify" 26 | fi 27 | echo "Running migrations for MySQL" 28 | atlas migrate apply --dir "file:///src/ent/migrate/migrations/mysql" --url "mysql://$ARCHIVISTA_SQL_STORE_CONNECTION_STRING" 29 | atlas_rc=$? 30 | ;; 31 | PSQL) 32 | echo "Running migrations for Postgres" 33 | atlas migrate apply --dir "file:///src/ent/migrate/migrations/pgsql" --url "$ARCHIVISTA_SQL_STORE_CONNECTION_STRING" 34 | atlas_rc=$? 35 | ;; 36 | *) 37 | echo "Unknown SQL backend: $ARCHIVISTA_SQL_STORE_BACKEND" 38 | exit 1 39 | ;; 40 | esac 41 | 42 | if [[ $atlas_rc -ne 0 ]]; then 43 | echo "Failed to apply migrations" 44 | exit 1 45 | fi 46 | 47 | CompileDaemon -log-prefix=false -build="go build -buildvcs=false -o /out/archivista ./cmd/archivista" -command="/out/archivista" 48 | -------------------------------------------------------------------------------- /cmd/archivistactl/cmd/store.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2024 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "os" 21 | 22 | "github.com/in-toto/archivista/pkg/api" 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | var storeCmd = &cobra.Command{ 27 | Use: "store", 28 | Short: "stores an attestation on the archivista server", 29 | SilenceUsage: true, 30 | Args: cobra.MinimumNArgs(1), 31 | RunE: func(cmd *cobra.Command, args []string) error { 32 | for _, filePath := range args { 33 | if gitoid, err := storeAttestationByPath(cmd.Context(), archivistaUrl, filePath); err != nil { 34 | return fmt.Errorf("failed to store %s: %w", filePath, err) 35 | } else { 36 | rootCmd.Printf("%s stored with gitoid %s\n", filePath, gitoid) 37 | } 38 | } 39 | 40 | return nil 41 | }, 42 | } 43 | 44 | func init() { 45 | rootCmd.AddCommand(storeCmd) 46 | } 47 | 48 | func storeAttestationByPath(ctx context.Context, baseUrl, path string) (string, error) { 49 | file, err := os.Open(path) 50 | if err != nil { 51 | return "", err 52 | } 53 | 54 | defer file.Close() 55 | resp, err := api.StoreWithReader(ctx, baseUrl, file, requestOptions()...) 56 | if err != nil { 57 | return "", err 58 | } 59 | 60 | return resp.Gitoid, nil 61 | } 62 | -------------------------------------------------------------------------------- /cmd/archivistactl/cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "log" 19 | "net/http" 20 | "strings" 21 | 22 | "github.com/in-toto/archivista/pkg/api" 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | var ( 27 | archivistaUrl string 28 | requestHeaders []string 29 | 30 | rootCmd = &cobra.Command{ 31 | Use: "archivistactl", 32 | Short: "A utility to interact with an archivista server", 33 | } 34 | ) 35 | 36 | func init() { 37 | rootCmd.PersistentFlags().StringVarP(&archivistaUrl, "archivistaurl", "u", "http://localhost:8082", "url of the archivista instance") 38 | rootCmd.PersistentFlags().StringArrayVarP(&requestHeaders, "headers", "H", []string{}, "headers to use when making requests to archivista") 39 | } 40 | 41 | func Execute() error { 42 | return rootCmd.Execute() 43 | } 44 | 45 | func requestOptions() []api.RequestOption { 46 | opts := []api.RequestOption{} 47 | headers := http.Header{} 48 | for _, header := range requestHeaders { 49 | headerParts := strings.SplitN(header, ":", 2) 50 | if len(headerParts) != 2 { 51 | log.Fatalf("invalid header: %v", header) 52 | } 53 | 54 | headers.Set(headerParts[0], headerParts[1]) 55 | } 56 | 57 | if len(headers) > 0 { 58 | opts = append(opts, api.WithHeaders(headers)) 59 | } 60 | 61 | return opts 62 | } 63 | -------------------------------------------------------------------------------- /ent/schema/subject.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schema 16 | 17 | import ( 18 | "time" 19 | 20 | "entgo.io/contrib/entgql" 21 | "entgo.io/ent" 22 | "entgo.io/ent/schema" 23 | "entgo.io/ent/schema/edge" 24 | "entgo.io/ent/schema/field" 25 | "entgo.io/ent/schema/index" 26 | "github.com/google/uuid" 27 | ) 28 | 29 | // Subject represents subjects from an in-toto statement. 30 | type Subject struct { 31 | ent.Schema 32 | } 33 | 34 | // Fields of the Subject. 35 | func (Subject) Fields() []ent.Field { 36 | return []ent.Field{ 37 | field.UUID("id", uuid.UUID{}).Default(uuid.New).Immutable().Unique(), 38 | field.Time("created_at").Default(time.Now).Immutable().Optional().Nillable().Annotations(entgql.OrderField("CREATED_AT")), 39 | field.String("name").NotEmpty(), 40 | } 41 | } 42 | 43 | // Edges of the Subject. 44 | func (Subject) Edges() []ent.Edge { 45 | return []ent.Edge{ 46 | edge.To("subject_digests", SubjectDigest.Type), 47 | 48 | edge.From("statement", Statement.Type). 49 | Ref("subjects").Unique(), 50 | } 51 | } 52 | 53 | func (Subject) Indexes() []ent.Index { 54 | return []ent.Index{ 55 | // Index on the "name" field. 56 | index.Fields("name"), 57 | } 58 | } 59 | 60 | func (Subject) Annotations() []schema.Annotation { 61 | return []schema.Annotation{ 62 | entgql.RelayConnection(), 63 | entgql.QueryField(), 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /cmd/archivistactl/cmd/search_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "bytes" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/suite" 22 | ) 23 | 24 | // Test Suite: UT Search 25 | type UTSearchSuite struct { 26 | suite.Suite 27 | } 28 | 29 | func TestUTSearchSuite(t *testing.T) { 30 | suite.Run(t, new(UTSearchSuite)) 31 | } 32 | 33 | func (ut *UTSearchSuite) Test_SearchMissingArgs() { 34 | output := bytes.NewBufferString("") 35 | rootCmd.SetOut(output) 36 | rootCmd.SetErr(output) 37 | rootCmd.SetArgs([]string{"search"}) 38 | err := rootCmd.Execute() 39 | if err != nil { 40 | ut.ErrorContains(err, "expected exactly 1 argument") 41 | } else { 42 | ut.FailNow("Expected: error") 43 | } 44 | } 45 | 46 | func (ut *UTSearchSuite) Test_SearchInvalidDigestString() { 47 | output := bytes.NewBufferString("") 48 | rootCmd.SetOut(output) 49 | rootCmd.SetErr(output) 50 | rootCmd.SetArgs([]string{"search", "invalidDigest"}) 51 | err := rootCmd.Execute() 52 | if err != nil { 53 | ut.ErrorContains(err, "invalid digest string. expected algorithm:digest") 54 | } else { 55 | ut.FailNow("Expected: error") 56 | } 57 | } 58 | 59 | func (ut *UTSearchSuite) Test_NoDB() { 60 | output := bytes.NewBufferString("") 61 | rootCmd.SetOut(output) 62 | rootCmd.SetErr(output) 63 | rootCmd.SetArgs([]string{"search", "sha256:test"}) 64 | err := rootCmd.Execute() 65 | if err != nil { 66 | ut.ErrorContains(err, "connection re") 67 | } else { 68 | ut.FailNow("Expected: error") 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /ent.resolvers.go: -------------------------------------------------------------------------------- 1 | package archivista 2 | 3 | // This file will be automatically regenerated based on the schema, any resolver implementations 4 | // will be copied through when generating and any unknown code will be moved to the end. 5 | // Code generated by github.com/99designs/gqlgen version v0.17.78 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "entgo.io/contrib/entgql" 12 | "github.com/google/uuid" 13 | "github.com/in-toto/archivista/ent" 14 | ) 15 | 16 | // Node is the resolver for the node field. 17 | func (r *queryResolver) Node(ctx context.Context, id uuid.UUID) (ent.Noder, error) { 18 | return r.client.Noder(ctx, id) 19 | } 20 | 21 | // Nodes is the resolver for the nodes field. 22 | func (r *queryResolver) Nodes(ctx context.Context, ids []uuid.UUID) ([]ent.Noder, error) { 23 | return r.client.Noders(ctx, ids) 24 | } 25 | 26 | // AttestationPolicies is the resolver for the attestationPolicies field. 27 | func (r *queryResolver) AttestationPolicies(ctx context.Context, after *entgql.Cursor[uuid.UUID], first *int, before *entgql.Cursor[uuid.UUID], last *int, where *ent.AttestationPolicyWhereInput) (*ent.AttestationPolicyConnection, error) { 28 | panic(fmt.Errorf("not implemented: AttestationPolicies - attestationPolicies")) 29 | } 30 | 31 | // Dsses is the resolver for the dsses field. 32 | func (r *queryResolver) Dsses(ctx context.Context, after *entgql.Cursor[uuid.UUID], first *int, before *entgql.Cursor[uuid.UUID], last *int, orderBy *ent.DsseOrder, where *ent.DsseWhereInput) (*ent.DsseConnection, error) { 33 | return r.client.Dsse.Query().Paginate(ctx, after, first, before, last, ent.WithDsseFilter(where.Filter)) 34 | } 35 | 36 | // Subjects is the resolver for the subjects field. 37 | func (r *queryResolver) Subjects(ctx context.Context, after *entgql.Cursor[uuid.UUID], first *int, before *entgql.Cursor[uuid.UUID], last *int, orderBy *ent.SubjectOrder, where *ent.SubjectWhereInput) (*ent.SubjectConnection, error) { 38 | return r.client.Subject.Query().Paginate(ctx, after, first, before, last, ent.WithSubjectFilter(where.Filter)) 39 | } 40 | 41 | // Query returns QueryResolver implementation. 42 | func (r *Resolver) Query() QueryResolver { return &queryResolver{r} } 43 | 44 | type queryResolver struct{ *Resolver } 45 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2023 The Archivista Contributors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ARCHIVISTA_ENABLE_SQL_STORE=$(echo ${ARCHIVISTA_ENABLE_SQL_STORE} | tr '[:lower:]' '[:upper:]') 17 | 18 | if [ "${ARCHIVISTA_ENABLE_SQL_STORE}" = "FALSE" ]; then 19 | echo "Skipping migrations" 20 | else 21 | if [[ -z $ARCHIVISTA_SQL_STORE_BACKEND ]]; then 22 | SQL_TYPE="MYSQL" 23 | else 24 | SQL_TYPE=$(echo "$ARCHIVISTA_SQL_STORE_BACKEND" | tr '[:lower:]' '[:upper:]') 25 | fi 26 | case $SQL_TYPE in 27 | MYSQL*) 28 | if [[ -z $ARCHIVISTA_SQL_STORE_CONNECTION_STRING ]]; then 29 | ARCHIVISTA_SQL_STORE_CONNECTION_STRING="root:example@db/testify" 30 | fi 31 | echo "Running migrations for MySQL" 32 | DB_URL="mysql://${ARCHIVISTA_SQL_STORE_CONNECTION_STRING}" 33 | if [[ "$SQL_TYPE" == *_RDS_IAM ]]; then 34 | DB_URL=$(/bin/archivistactl iam "${SQL_TYPE}" "${DB_URL}") 35 | fi 36 | atlas migrate apply --dir "file:///archivista/migrations/mysql" --url "${DB_URL}" 37 | atlas_rc=$? 38 | ;; 39 | PSQL*) 40 | echo "Running migrations for Postgres" 41 | DB_URL="${ARCHIVISTA_SQL_STORE_CONNECTION_STRING}" 42 | if [[ "$SQL_TYPE" == *_RDS_IAM ]]; then 43 | DB_URL=$(/bin/archivistactl iam "${SQL_TYPE}" "${DB_URL}") 44 | fi 45 | atlas migrate apply --dir "file:///archivista/migrations/pgsql" --url "${DB_URL}" 46 | atlas_rc=$? 47 | ;; 48 | *) 49 | echo "Unknown SQL backend: $ARCHIVISTA_SQL_STORE_BACKEND" 50 | exit 1 51 | ;; 52 | esac 53 | 54 | if [[ $atlas_rc -ne 0 ]]; then 55 | echo "Failed to apply migrations" 56 | exit 1 57 | fi 58 | fi 59 | 60 | /bin/archivista 61 | -------------------------------------------------------------------------------- /ent/enttest/enttest.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package enttest 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/in-toto/archivista/ent" 9 | // required by schema hooks. 10 | _ "github.com/in-toto/archivista/ent/runtime" 11 | 12 | "entgo.io/ent/dialect/sql/schema" 13 | "github.com/in-toto/archivista/ent/migrate" 14 | ) 15 | 16 | type ( 17 | // TestingT is the interface that is shared between 18 | // testing.T and testing.B and used by enttest. 19 | TestingT interface { 20 | FailNow() 21 | Error(...any) 22 | } 23 | 24 | // Option configures client creation. 25 | Option func(*options) 26 | 27 | options struct { 28 | opts []ent.Option 29 | migrateOpts []schema.MigrateOption 30 | } 31 | ) 32 | 33 | // WithOptions forwards options to client creation. 34 | func WithOptions(opts ...ent.Option) Option { 35 | return func(o *options) { 36 | o.opts = append(o.opts, opts...) 37 | } 38 | } 39 | 40 | // WithMigrateOptions forwards options to auto migration. 41 | func WithMigrateOptions(opts ...schema.MigrateOption) Option { 42 | return func(o *options) { 43 | o.migrateOpts = append(o.migrateOpts, opts...) 44 | } 45 | } 46 | 47 | func newOptions(opts []Option) *options { 48 | o := &options{} 49 | for _, opt := range opts { 50 | opt(o) 51 | } 52 | return o 53 | } 54 | 55 | // Open calls ent.Open and auto-run migration. 56 | func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *ent.Client { 57 | o := newOptions(opts) 58 | c, err := ent.Open(driverName, dataSourceName, o.opts...) 59 | if err != nil { 60 | t.Error(err) 61 | t.FailNow() 62 | } 63 | migrateSchema(t, c, o) 64 | return c 65 | } 66 | 67 | // NewClient calls ent.NewClient and auto-run migration. 68 | func NewClient(t TestingT, opts ...Option) *ent.Client { 69 | o := newOptions(opts) 70 | c := ent.NewClient(o.opts...) 71 | migrateSchema(t, c, o) 72 | return c 73 | } 74 | func migrateSchema(t TestingT, c *ent.Client, o *options) { 75 | tables, err := schema.CopyTables(migrate.Tables) 76 | if err != nil { 77 | t.Error(err) 78 | t.FailNow() 79 | } 80 | if err := migrate.Create(context.Background(), c.Schema, tables, o.migrateOpts...); err != nil { 81 | t.Error(err) 82 | t.FailNow() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /pkg/objectstorage/filestore/file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package filestore 16 | 17 | import ( 18 | "context" 19 | "io" 20 | "log" 21 | "net/http" 22 | "os" 23 | "path/filepath" 24 | "time" 25 | 26 | "github.com/gorilla/handlers" 27 | ) 28 | 29 | type Store struct { 30 | prefix string 31 | } 32 | 33 | func New(ctx context.Context, directory string, address string) (*Store, <-chan error, error) { 34 | errCh := make(chan error) 35 | go func() { 36 | server := &http.Server{ 37 | Addr: address, 38 | Handler: handlers.CompressHandler(http.FileServer(http.Dir(directory))), 39 | ReadTimeout: 5 * time.Second, 40 | WriteTimeout: 10 * time.Second, 41 | } 42 | 43 | if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { 44 | log.Fatalf("Could not listen on %s: %v\n", address, err) 45 | } 46 | 47 | <-ctx.Done() 48 | 49 | if err := server.Shutdown(context.Background()); err != nil { 50 | log.Fatalf("Server Shutdown Failed:%+v", err) 51 | } 52 | 53 | close(errCh) 54 | }() 55 | 56 | return &Store{ 57 | prefix: directory, 58 | }, errCh, nil 59 | } 60 | 61 | func (s *Store) Get(ctx context.Context, gitoid string) (io.ReadCloser, error) { 62 | if filepath.IsLocal(gitoid) { 63 | return os.Open(filepath.Join(s.prefix, gitoid+".json")) 64 | } else { 65 | return nil, filepath.ErrBadPattern 66 | } 67 | } 68 | 69 | func (s *Store) Store(ctx context.Context, gitoid string, payload []byte) error { 70 | if filepath.IsLocal(gitoid) { 71 | return os.WriteFile(filepath.Join(s.prefix, gitoid+".json"), payload, 0o600) 72 | } else { 73 | return filepath.ErrBadPattern 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /.github/workflows/witness.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | on: 16 | workflow_call: 17 | inputs: 18 | pull_request: 19 | required: true 20 | type: boolean 21 | command: 22 | required: true 23 | type: string 24 | step: 25 | required: true 26 | type: string 27 | attestations: 28 | required: true 29 | type: string 30 | 31 | permissions: 32 | contents: read 33 | 34 | jobs: 35 | witness: 36 | runs-on: ubuntu-22.04 37 | permissions: 38 | contents: read 39 | id-token: write 40 | steps: 41 | - name: Harden Runner 42 | uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 43 | with: 44 | egress-policy: audit 45 | 46 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 47 | - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 48 | with: 49 | go-version-file: "go.mod" 50 | 51 | - if: ${{ inputs.pull_request == false }} 52 | uses: testifysec/witness-run-action@7aa15e327829f1f2a523365c564c948d5dde69dd 53 | with: 54 | witness-install-dir: /opt/witness 55 | version: 0.9.1 56 | step: ${{ inputs.step }} 57 | attestations: ${{ inputs.attestations }} 58 | command: /bin/sh -c "${{ inputs.command }}" 59 | 60 | - if: ${{ inputs.pull_request == true }} 61 | run: ${{ inputs.command }} 62 | 63 | - if: ${{ inputs.step == 'tests' }} 64 | uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de 65 | -------------------------------------------------------------------------------- /test/deploy-services.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2023 The Archivista Contributors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -e 17 | 18 | DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 19 | 20 | checkprograms() { 21 | local result=0 22 | for prog in "$@" 23 | do 24 | if ! command -v "$prog" > /dev/null; then 25 | print "$prog is required to run this script. please ensure it is installed and in your PATH\n" 26 | result=1 27 | fi 28 | done 29 | 30 | return $result 31 | } 32 | 33 | waitForArchivista() { 34 | echo "Waiting for archivista to be ready..." 35 | for attempt in $(seq 1 6); do 36 | sleep 10 37 | local archivistastate 38 | archivistastate=$(docker compose -f "$DIR/../compose.yml" ps archivista --format json | jq -r '.State') 39 | if [ "$archivistastate" == "running" ]; then 40 | break 41 | fi 42 | 43 | if [[ attempt -eq 6 ]]; then 44 | echo "timed out waiting for archivista" 45 | docker compose logs 46 | exit 1 47 | fi 48 | done 49 | } 50 | 51 | if ! checkprograms docker jq ; then 52 | exit 1 53 | fi 54 | 55 | startMySQL() { 56 | echo "Test mysql..." 57 | docker compose -f "$DIR/../compose.yml" up --build -d 58 | waitForArchivista 59 | } 60 | 61 | startPgSQL(){ 62 | echo "Test psql..." 63 | docker compose -f "$DIR/../compose.yml" -f "$DIR/../compose-psql.yml" up --build -d 64 | waitForArchivista 65 | } 66 | 67 | 68 | stopServices() { 69 | docker compose -f "$DIR/../compose.yml" -f "$DIR/../compose-psql.yml" down -v 70 | } 71 | 72 | 73 | case $1 in 74 | start-mysql) 75 | startMySQL 76 | waitForArchivista 77 | ;; 78 | 79 | start-pgsql) 80 | startPgSQL 81 | waitForArchivista 82 | ;; 83 | 84 | stop) 85 | stopServices 86 | ;; 87 | 88 | *) 89 | echo "Wrong option. Use $0 start-mysql|start-pgsql|stop" 90 | ;; 91 | esac 92 | -------------------------------------------------------------------------------- /.github/workflows/update-pre-commit-hooks.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # This workflow uses actions that are not certified by GitHub. They are provided 16 | # by a third-party and are governed by separate terms of service, privacy 17 | # policy, and support documentation. 18 | 19 | name: Update pre-commit hooks 20 | on: 21 | workflow_dispatch: 22 | schedule: 23 | # Run at 8:00 AM every day 24 | - cron: "0 8 * * *" 25 | permissions: 26 | contents: read 27 | jobs: 28 | update-pre-commit-hooks: 29 | runs-on: ubuntu-latest 30 | permissions: 31 | contents: write 32 | pull-requests: write 33 | steps: 34 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 35 | - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 36 | with: 37 | python-version: "3.11" 38 | - name: Install prerequisites 39 | run: | 40 | pip install pre-commit==3.6.0 41 | - name: Update pre-commit hooks 42 | run: | 43 | pre-commit autoupdate 44 | - name: Check for pre-commit config file changes 45 | id: git_diff 46 | run: | 47 | echo "GIT_DIFF=$(git diff --exit-code 1> /dev/null; echo $?)" >> $GITHUB_OUTPUT 48 | - name: Create Pull Request 49 | uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0 50 | with: 51 | token: ${{ secrets.GITHUB_TOKEN }} 52 | commit-message: "build: Update pre-commit hooks" 53 | branch: "archivista-bot/update-pre-commit-hooks" 54 | delete-branch: true 55 | title: "build: Update pre-commit hooks" 56 | body: > 57 | The following PR updates the pre-commit hooks (`.pre-commit-config.yaml` file) using `pre-commit autoupdate`. 58 | labels: report, automated pr, pre-commit 59 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Nothing to be run by default as the CodeQL autobuild tries to run make 16 | # See https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages#autobuild-for-go 17 | all: help 18 | 19 | run-dev: ## Run the dev server 20 | @echo "Running dev server. It will refresh automatically when you change code." 21 | @docker compose -f compose-dev.yml up --remove-orphans 22 | 23 | 24 | .PHONY: stop 25 | stop: ## Stop the dev server 26 | @docker compose -f compose-dev.yml down -v 27 | 28 | 29 | .PHONY: clean 30 | clean: ## Clean up the dev server 31 | $(MAKE) stop 32 | @docker compose -f compose-dev.yml rm --force 33 | @docker rmi archivista-archivista --force 34 | 35 | 36 | .PHONY: test 37 | test: ## Run tests 38 | @go test ./... -covermode atomic -coverprofile=cover.out -v 39 | 40 | .PHONY: coverage 41 | coverage: ## Show html coverage 42 | @go tool cover -html=cover.out 43 | 44 | 45 | .PHONY: lint 46 | lint: ## Run linter 47 | @golangci-lint run 48 | @go fmt ./... 49 | @go vet ./... 50 | 51 | 52 | .PHONY: docs 53 | docs: ## Generate swagger docs 54 | @go install github.com/swaggo/swag/cmd/swag@v1.16.2 55 | @swag init -o docs -d pkg/server -g server.go -pd 56 | 57 | .PHONY: db-migrations 58 | db-migrations: ## Run the migrations for the database 59 | @go generate ./... 60 | @atlas migrate diff mysql --dir "file://ent/migrate/migrations/mysql" --to "ent://ent/schema" --dev-url "docker://mysql/8/dev" 61 | @atlas migrate diff pgsql --dir "file://ent/migrate/migrations/pgsql" --to "ent://ent/schema" --dev-url "docker://postgres/16/dev?search_path=public" 62 | 63 | 64 | help: ## Show this help 65 | @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 66 | -------------------------------------------------------------------------------- /cmd/archivistactl/cmd/iam_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "bytes" 19 | "io" 20 | "os" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func TestIamCmd(t *testing.T) { 27 | tests := []struct { 28 | name string 29 | args []string 30 | expectedOutput string 31 | expectedError bool 32 | }{ 33 | { 34 | name: "PSQL No IAM", 35 | args: []string{"iam", "PSQL", "postgres://user:password@host:5432/dbname"}, 36 | expectedOutput: "postgres://user:password@host:5432/dbname\n", 37 | }, 38 | { 39 | name: "PSQL RDS IAM", 40 | args: []string{"iam", "PSQL_RDS_IAM", "postgres://user@host:5432/dbname", "--dryrun"}, 41 | expectedOutput: "postgres://user:authtoken@host:5432/dbname\n", 42 | }, 43 | { 44 | name: "MYSQL RDS IAM", 45 | args: []string{"iam", "MYSQL_RDS_IAM", "user@tcp(host)/dbname", "--dryrun"}, 46 | expectedOutput: "user:authtoken@tcp(host:3306)/dbname?allowCleartextPasswords=true&parseTime=true&tls=true\n", 47 | }, 48 | { 49 | name: "PSQL RDS IAM Invalid Connection String", 50 | args: []string{"iam", "PSQL_RDS_IAM", "http://invalid", "--dryrun"}, 51 | expectedError: true, 52 | }, 53 | } 54 | 55 | for _, tt := range tests { 56 | t.Run(tt.name, func(t *testing.T) { 57 | old := os.Stdout 58 | r, w, _ := os.Pipe() 59 | os.Stdout = w 60 | 61 | rootCmd.SetArgs(tt.args) 62 | err := rootCmd.Execute() 63 | 64 | w.Close() 65 | os.Stdout = old 66 | 67 | var out bytes.Buffer 68 | _, copyErr := io.Copy(&out, r) 69 | assert.NoError(t, copyErr) 70 | 71 | if tt.expectedError { 72 | assert.Error(t, err) 73 | } else { 74 | assert.NoError(t, err) 75 | assert.Equal(t, tt.expectedOutput, out.String()) 76 | } 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /pkg/api/structs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package api 16 | 17 | type GraphQLError struct { 18 | Message string `json:"message"` 19 | } 20 | 21 | type GraphQLResponseGeneric[T any] struct { 22 | Data T `json:"data,omitempty"` 23 | Errors []GraphQLError `json:"errors,omitempty"` 24 | } 25 | 26 | type GraphQLRequestBodyGeneric[TVars any] struct { 27 | Query string `json:"query"` 28 | Variables TVars `json:"variables,omitempty"` 29 | } 30 | 31 | type RetrieveSubjectVars struct { 32 | Gitoid string `json:"gitoid"` 33 | } 34 | 35 | type SearchVars struct { 36 | Algorithm string `json:"algo"` 37 | Digest string `json:"digest"` 38 | } 39 | 40 | type RetrieveSubjectResults struct { 41 | Subjects Subjects `json:"subjects"` 42 | } 43 | 44 | type Subjects struct { 45 | Edges []SubjectEdge `json:"edges"` 46 | } 47 | 48 | type SubjectEdge struct { 49 | Node SubjectNode `json:"node"` 50 | } 51 | 52 | type SubjectNode struct { 53 | Name string `json:"name"` 54 | SubjectDigests []SubjectDigest `json:"subjectDigests"` 55 | } 56 | 57 | type SubjectDigest struct { 58 | Algorithm string `json:"algorithm"` 59 | Value string `json:"value"` 60 | } 61 | 62 | type SearchResults struct { 63 | Dsses DSSES `json:"dsses"` 64 | } 65 | 66 | type DSSES struct { 67 | Edges []SearchEdge `json:"edges"` 68 | } 69 | 70 | type SearchEdge struct { 71 | Node SearchNode `json:"node"` 72 | } 73 | 74 | type SearchNode struct { 75 | GitoidSha256 string `json:"gitoidSha256"` 76 | Statement Statement `json:"statement"` 77 | } 78 | 79 | type Statement struct { 80 | AttestationCollection AttestationCollection `json:"attestationCollections"` 81 | } 82 | 83 | type AttestationCollection struct { 84 | Name string `json:"name"` 85 | Attestations []Attestation `json:"attestations"` 86 | } 87 | 88 | type Attestation struct { 89 | Type string `json:"type"` 90 | } 91 | -------------------------------------------------------------------------------- /ent/dsse_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/in-toto/archivista/ent/dsse" 12 | "github.com/in-toto/archivista/ent/predicate" 13 | ) 14 | 15 | // DsseDelete is the builder for deleting a Dsse entity. 16 | type DsseDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *DsseMutation 20 | } 21 | 22 | // Where appends a list predicates to the DsseDelete builder. 23 | func (_d *DsseDelete) Where(ps ...predicate.Dsse) *DsseDelete { 24 | _d.mutation.Where(ps...) 25 | return _d 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (_d *DsseDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (_d *DsseDelete) ExecX(ctx context.Context) int { 35 | n, err := _d.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (_d *DsseDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(dsse.Table, sqlgraph.NewFieldSpec(dsse.FieldID, field.TypeUUID)) 44 | if ps := _d.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | _d.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // DsseDeleteOne is the builder for deleting a single Dsse entity. 60 | type DsseDeleteOne struct { 61 | _d *DsseDelete 62 | } 63 | 64 | // Where appends a list predicates to the DsseDelete builder. 65 | func (_d *DsseDeleteOne) Where(ps ...predicate.Dsse) *DsseDeleteOne { 66 | _d._d.mutation.Where(ps...) 67 | return _d 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (_d *DsseDeleteOne) Exec(ctx context.Context) error { 72 | n, err := _d._d.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{dsse.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (_d *DsseDeleteOne) ExecX(ctx context.Context) { 85 | if err := _d.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /compose.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | version: '3.9' 16 | 17 | services: 18 | db: 19 | image: mysql:oracle 20 | restart: always 21 | ports: 22 | - 3306:3306 23 | environment: 24 | MYSQL_DATABASE: testify 25 | MYSQL_ROOT_PASSWORD: example 26 | volumes: 27 | - mysqldata:/var/lib/mysql 28 | 29 | archivista: 30 | build: . 31 | restart: unless-stopped 32 | environment: 33 | ARCHIVISTA_LISTEN_ON: tcp://0.0.0.0:8082 34 | ARCHIVISTA_ENABLE_SPIFFE: "false" 35 | ARCHIVISTA_STORAGE_BACKEND: BLOB 36 | ARCHIVISTA_FILE_DIR: /tmp/archivista/ 37 | ARCHIVISTA_FILE_SERVE_ON: :8081 38 | ARCHIVISTA_BLOB_STORE_USE_TLS: "false" 39 | ARCHIVISTA_BLOB_STORE_CREDENTIAL_TYPE: ACCESS_KEY 40 | ARCHIVISTA_BLOB_STORE_ACCESS_KEY_ID: testifytestifytestify 41 | ARCHIVISTA_BLOB_STORE_SECRET_ACCESS_KEY_ID: exampleexampleexample 42 | ARCHIVISTA_BLOB_STORE_BUCKET_NAME: attestations 43 | ARCHIVISTA_BLOB_STORE_ENDPOINT: minio:9000 44 | ARCHIVISTA_ENABLE_GRAPHQL: "true" 45 | ARCHIVISTA_GRAPHQL_WEB_CLIENT_ENABLE: "true" 46 | ARCHIVISTA_CORS_ALLOW_ORIGINS: "http://localhost:1234" 47 | 48 | ports: 49 | - 8081:8081 50 | - 8082:8082 51 | volumes: 52 | - fileserver:/tmp/archivista 53 | 54 | minio: 55 | image: quay.io/minio/minio 56 | restart: always 57 | command: server /data --console-address ":9001" 58 | ports: 59 | - 9000:9000 60 | - 9001:9001 61 | environment: 62 | MINIO_ROOT_USER: testifytestifytestify 63 | MINIO_ROOT_PASSWORD: exampleexampleexample 64 | volumes: 65 | - miniodata:/data 66 | 67 | minio-init: 68 | image: quay.io/minio/mc 69 | restart: on-failure 70 | command: mb --insecure --ignore-existing testminio/attestations 71 | environment: 72 | MC_HOST_testminio: http://testifytestifytestify:exampleexampleexample@minio:9000 73 | 74 | volumes: 75 | fileserver: 76 | miniodata: 77 | mysqldata: 78 | -------------------------------------------------------------------------------- /ent/migrate/migrate.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package migrate 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "io" 9 | 10 | "entgo.io/ent/dialect" 11 | "entgo.io/ent/dialect/sql/schema" 12 | ) 13 | 14 | var ( 15 | // WithGlobalUniqueID sets the universal ids options to the migration. 16 | // If this option is enabled, ent migration will allocate a 1<<32 range 17 | // for the ids of each entity (table). 18 | // Note that this option cannot be applied on tables that already exist. 19 | WithGlobalUniqueID = schema.WithGlobalUniqueID 20 | // WithDropColumn sets the drop column option to the migration. 21 | // If this option is enabled, ent migration will drop old columns 22 | // that were used for both fields and edges. This defaults to false. 23 | WithDropColumn = schema.WithDropColumn 24 | // WithDropIndex sets the drop index option to the migration. 25 | // If this option is enabled, ent migration will drop old indexes 26 | // that were defined in the schema. This defaults to false. 27 | // Note that unique constraints are defined using `UNIQUE INDEX`, 28 | // and therefore, it's recommended to enable this option to get more 29 | // flexibility in the schema changes. 30 | WithDropIndex = schema.WithDropIndex 31 | // WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true. 32 | WithForeignKeys = schema.WithForeignKeys 33 | ) 34 | 35 | // Schema is the API for creating, migrating and dropping a schema. 36 | type Schema struct { 37 | drv dialect.Driver 38 | } 39 | 40 | // NewSchema creates a new schema client. 41 | func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} } 42 | 43 | // Create creates all schema resources. 44 | func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error { 45 | return Create(ctx, s, Tables, opts...) 46 | } 47 | 48 | // Create creates all table resources using the given schema driver. 49 | func Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...schema.MigrateOption) error { 50 | migrate, err := schema.NewMigrate(s.drv, opts...) 51 | if err != nil { 52 | return fmt.Errorf("ent/migrate: %w", err) 53 | } 54 | return migrate.Create(ctx, tables...) 55 | } 56 | 57 | // WriteTo writes the schema changes to w instead of running them against the database. 58 | // 59 | // if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil { 60 | // log.Fatal(err) 61 | // } 62 | func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error { 63 | return Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv}}, Tables, opts...) 64 | } 65 | -------------------------------------------------------------------------------- /ent/subject_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/in-toto/archivista/ent/predicate" 12 | "github.com/in-toto/archivista/ent/subject" 13 | ) 14 | 15 | // SubjectDelete is the builder for deleting a Subject entity. 16 | type SubjectDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *SubjectMutation 20 | } 21 | 22 | // Where appends a list predicates to the SubjectDelete builder. 23 | func (_d *SubjectDelete) Where(ps ...predicate.Subject) *SubjectDelete { 24 | _d.mutation.Where(ps...) 25 | return _d 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (_d *SubjectDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (_d *SubjectDelete) ExecX(ctx context.Context) int { 35 | n, err := _d.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (_d *SubjectDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(subject.Table, sqlgraph.NewFieldSpec(subject.FieldID, field.TypeUUID)) 44 | if ps := _d.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | _d.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // SubjectDeleteOne is the builder for deleting a single Subject entity. 60 | type SubjectDeleteOne struct { 61 | _d *SubjectDelete 62 | } 63 | 64 | // Where appends a list predicates to the SubjectDelete builder. 65 | func (_d *SubjectDeleteOne) Where(ps ...predicate.Subject) *SubjectDeleteOne { 66 | _d._d.mutation.Where(ps...) 67 | return _d 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (_d *SubjectDeleteOne) Exec(ctx context.Context) error { 72 | n, err := _d._d.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{subject.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (_d *SubjectDeleteOne) ExecX(ctx context.Context) { 85 | if err := _d.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ent/signature_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/in-toto/archivista/ent/predicate" 12 | "github.com/in-toto/archivista/ent/signature" 13 | ) 14 | 15 | // SignatureDelete is the builder for deleting a Signature entity. 16 | type SignatureDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *SignatureMutation 20 | } 21 | 22 | // Where appends a list predicates to the SignatureDelete builder. 23 | func (_d *SignatureDelete) Where(ps ...predicate.Signature) *SignatureDelete { 24 | _d.mutation.Where(ps...) 25 | return _d 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (_d *SignatureDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (_d *SignatureDelete) ExecX(ctx context.Context) int { 35 | n, err := _d.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (_d *SignatureDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(signature.Table, sqlgraph.NewFieldSpec(signature.FieldID, field.TypeUUID)) 44 | if ps := _d.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | _d.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // SignatureDeleteOne is the builder for deleting a single Signature entity. 60 | type SignatureDeleteOne struct { 61 | _d *SignatureDelete 62 | } 63 | 64 | // Where appends a list predicates to the SignatureDelete builder. 65 | func (_d *SignatureDeleteOne) Where(ps ...predicate.Signature) *SignatureDeleteOne { 66 | _d._d.mutation.Where(ps...) 67 | return _d 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (_d *SignatureDeleteOne) Exec(ctx context.Context) error { 72 | n, err := _d._d.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{signature.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (_d *SignatureDeleteOne) ExecX(ctx context.Context) { 85 | if err := _d.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ent/statement_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/in-toto/archivista/ent/predicate" 12 | "github.com/in-toto/archivista/ent/statement" 13 | ) 14 | 15 | // StatementDelete is the builder for deleting a Statement entity. 16 | type StatementDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *StatementMutation 20 | } 21 | 22 | // Where appends a list predicates to the StatementDelete builder. 23 | func (_d *StatementDelete) Where(ps ...predicate.Statement) *StatementDelete { 24 | _d.mutation.Where(ps...) 25 | return _d 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (_d *StatementDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (_d *StatementDelete) ExecX(ctx context.Context) int { 35 | n, err := _d.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (_d *StatementDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(statement.Table, sqlgraph.NewFieldSpec(statement.FieldID, field.TypeUUID)) 44 | if ps := _d.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | _d.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // StatementDeleteOne is the builder for deleting a single Statement entity. 60 | type StatementDeleteOne struct { 61 | _d *StatementDelete 62 | } 63 | 64 | // Where appends a list predicates to the StatementDelete builder. 65 | func (_d *StatementDeleteOne) Where(ps ...predicate.Statement) *StatementDeleteOne { 66 | _d._d.mutation.Where(ps...) 67 | return _d 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (_d *StatementDeleteOne) Exec(ctx context.Context) error { 72 | n, err := _d._d.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{statement.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (_d *StatementDeleteOne) ExecX(ctx context.Context) { 85 | if err := _d.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ent/timestamp_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/in-toto/archivista/ent/predicate" 12 | "github.com/in-toto/archivista/ent/timestamp" 13 | ) 14 | 15 | // TimestampDelete is the builder for deleting a Timestamp entity. 16 | type TimestampDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *TimestampMutation 20 | } 21 | 22 | // Where appends a list predicates to the TimestampDelete builder. 23 | func (_d *TimestampDelete) Where(ps ...predicate.Timestamp) *TimestampDelete { 24 | _d.mutation.Where(ps...) 25 | return _d 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (_d *TimestampDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (_d *TimestampDelete) ExecX(ctx context.Context) int { 35 | n, err := _d.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (_d *TimestampDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(timestamp.Table, sqlgraph.NewFieldSpec(timestamp.FieldID, field.TypeUUID)) 44 | if ps := _d.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | _d.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // TimestampDeleteOne is the builder for deleting a single Timestamp entity. 60 | type TimestampDeleteOne struct { 61 | _d *TimestampDelete 62 | } 63 | 64 | // Where appends a list predicates to the TimestampDelete builder. 65 | func (_d *TimestampDeleteOne) Where(ps ...predicate.Timestamp) *TimestampDeleteOne { 66 | _d._d.mutation.Where(ps...) 67 | return _d 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (_d *TimestampDeleteOne) Exec(ctx context.Context) error { 72 | n, err := _d._d.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{timestamp.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (_d *TimestampDeleteOne) ExecX(ctx context.Context) { 85 | if err := _d.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /compose-dev.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | version: '3.9' 16 | 17 | services: 18 | db: 19 | image: mysql:oracle 20 | restart: always 21 | ports: 22 | - 3306:3306 23 | environment: 24 | MYSQL_DATABASE: testify 25 | MYSQL_ROOT_PASSWORD: example 26 | volumes: 27 | - mysqldata:/var/lib/mysql 28 | 29 | archivista: 30 | build: 31 | context: . 32 | dockerfile: ./Dockerfile-dev 33 | restart: unless-stopped 34 | environment: 35 | ARCHIVISTA_LISTEN_ON: tcp://0.0.0.0:8082 36 | ARCHIVISTA_ENABLE_SPIFFE: "false" 37 | ARCHIVISTA_STORAGE_BACKEND: BLOB 38 | ARCHIVISTA_FILE_DIR: /tmp/archivista/ 39 | ARCHIVISTA_FILE_SERVE_ON: :8081 40 | ARCHIVISTA_BLOB_STORE_USE_TLS: "false" 41 | ARCHIVISTA_BLOB_STORE_CREDENTIAL_TYPE: ACCESS_KEY 42 | ARCHIVISTA_BLOB_STORE_ACCESS_KEY_ID: testifytestifytestify 43 | ARCHIVISTA_BLOB_STORE_SECRET_ACCESS_KEY_ID: exampleexampleexample 44 | ARCHIVISTA_BLOB_STORE_BUCKET_NAME: attestations 45 | ARCHIVISTA_BLOB_STORE_ENDPOINT: minio:9000 46 | ARCHIVISTA_ENABLE_GRAPHQL: "true" 47 | ARCHIVISTA_GRAPHQL_WEB_CLIENT_ENABLE: "true" 48 | ARCHIVISTA_CORS_ALLOW_ORIGINS: "http://localhost:1234" 49 | 50 | ports: 51 | - 8081:8081 52 | - 8082:8082 53 | volumes: 54 | - fileserver:/tmp/archivista 55 | - ./:/src 56 | 57 | minio: 58 | image: quay.io/minio/minio 59 | restart: always 60 | command: server /data --console-address ":9001" 61 | ports: 62 | - 9000:9000 63 | - 9001:9001 64 | environment: 65 | MINIO_ROOT_USER: testifytestifytestify 66 | MINIO_ROOT_PASSWORD: exampleexampleexample 67 | volumes: 68 | - miniodata:/data 69 | 70 | minio-init: 71 | image: quay.io/minio/mc 72 | restart: on-failure 73 | command: mb --insecure --ignore-existing testminio/attestations 74 | environment: 75 | MC_HOST_testminio: http://testifytestifytestify:exampleexampleexample@minio:9000 76 | 77 | volumes: 78 | fileserver: 79 | miniodata: 80 | mysqldata: 81 | -------------------------------------------------------------------------------- /ent/attestation_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/in-toto/archivista/ent/attestation" 12 | "github.com/in-toto/archivista/ent/predicate" 13 | ) 14 | 15 | // AttestationDelete is the builder for deleting a Attestation entity. 16 | type AttestationDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *AttestationMutation 20 | } 21 | 22 | // Where appends a list predicates to the AttestationDelete builder. 23 | func (_d *AttestationDelete) Where(ps ...predicate.Attestation) *AttestationDelete { 24 | _d.mutation.Where(ps...) 25 | return _d 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (_d *AttestationDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (_d *AttestationDelete) ExecX(ctx context.Context) int { 35 | n, err := _d.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (_d *AttestationDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(attestation.Table, sqlgraph.NewFieldSpec(attestation.FieldID, field.TypeUUID)) 44 | if ps := _d.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | _d.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // AttestationDeleteOne is the builder for deleting a single Attestation entity. 60 | type AttestationDeleteOne struct { 61 | _d *AttestationDelete 62 | } 63 | 64 | // Where appends a list predicates to the AttestationDelete builder. 65 | func (_d *AttestationDeleteOne) Where(ps ...predicate.Attestation) *AttestationDeleteOne { 66 | _d._d.mutation.Where(ps...) 67 | return _d 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (_d *AttestationDeleteOne) Exec(ctx context.Context) error { 72 | n, err := _d._d.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{attestation.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (_d *AttestationDeleteOne) ExecX(ctx context.Context) { 85 | if err := _d.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pkg/publisherstore/dapr/http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package dapr 15 | 16 | import ( 17 | "bytes" 18 | "context" 19 | "encoding/json" 20 | "fmt" 21 | "net/http" 22 | "time" 23 | 24 | "github.com/in-toto/archivista/pkg/config" 25 | "github.com/sirupsen/logrus" 26 | ) 27 | 28 | type DaprHttp struct { 29 | Client *http.Client 30 | Host string 31 | HttpPort string 32 | PubsubComponentName string 33 | PubsubTopic string 34 | Url string 35 | } 36 | 37 | type daprPayload struct { 38 | Gitoid string 39 | Payload []byte 40 | } 41 | 42 | type Publisher interface { 43 | Publish(ctx context.Context, gitoid string, payload []byte) error 44 | } 45 | 46 | func (d *DaprHttp) Publish(ctx context.Context, gitoid string, payload []byte) error { 47 | if d.Client == nil { 48 | d.Client = &http.Client{ 49 | Timeout: 15 * time.Second, 50 | } 51 | } 52 | 53 | if d.Url == "" { 54 | d.Url = d.Host + ":" + d.HttpPort + 55 | "/v1.0/publish/" + d.PubsubComponentName + "/" + d.PubsubTopic 56 | } 57 | 58 | dp := daprPayload{ 59 | Gitoid: gitoid, 60 | Payload: payload, 61 | } 62 | // Marshal the message to JSON 63 | msgBytes, err := json.Marshal(dp) 64 | if err != nil { 65 | logrus.Error(err.Error()) 66 | return err 67 | } 68 | 69 | res, err := d.Client.Post(d.Url, "application/json", bytes.NewReader(msgBytes)) 70 | if err != nil { 71 | logrus.Error(err.Error()) 72 | return err 73 | } 74 | if res.StatusCode != http.StatusNoContent { 75 | logrus.Printf("failed to publish message: %s", res.Body) 76 | return fmt.Errorf("failed to publish message: %s", res.Body) 77 | } 78 | defer res.Body.Close() 79 | 80 | return nil 81 | } 82 | 83 | func NewPublisher(config *config.Config) Publisher { 84 | daprPublisher := &DaprHttp{ 85 | Host: config.PublisherDaprHost, 86 | HttpPort: config.PublisherDaprPort, 87 | PubsubComponentName: config.PublisherDaprComponentName, 88 | PubsubTopic: config.PublisherDaprTopic, 89 | Url: config.PublisherDaprURL, 90 | } 91 | return daprPublisher 92 | } 93 | -------------------------------------------------------------------------------- /pkg/objectstorage/blobstore/minio.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package blobstore 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "fmt" 21 | "github.com/minio/minio-go/v7" 22 | "io" 23 | 24 | "github.com/minio/minio-go/v7/pkg/credentials" 25 | ) 26 | 27 | type Store struct { 28 | client *minio.Client 29 | bucket string 30 | location string 31 | } 32 | 33 | // PutBlob stores the attestation blob into the backend store 34 | func (store *Store) PutBlob(ctx context.Context, idx string, obj []byte) error { 35 | opt := minio.PutObjectOptions{} 36 | size := int64(len(obj)) 37 | n, err := store.client.PutObject(ctx, store.bucket, idx, bytes.NewReader(obj), size, opt) 38 | if err != nil { 39 | return fmt.Errorf("failed to put blob: %v", err) 40 | } else if n.Size != size { 41 | return fmt.Errorf("failed to upload full blob: size %d != uploaded size %d", size, n.Size) 42 | } 43 | return nil 44 | } 45 | 46 | // New returns a reader/writer for storing/retrieving attestations 47 | func New(ctx context.Context, endpoint string, creds *credentials.Credentials, bucketName string, useTLS bool) (*Store, <-chan error, error) { 48 | errCh := make(chan error) 49 | go func() { 50 | <-ctx.Done() 51 | close(errCh) 52 | }() 53 | 54 | c, err := minio.New(endpoint, &minio.Options{ 55 | Creds: creds, 56 | Secure: useTLS, 57 | }) 58 | if err != nil { 59 | return nil, errCh, err 60 | } 61 | 62 | exists, err := c.BucketExists(ctx, bucketName) 63 | if !exists || err != nil { 64 | return nil, errCh, fmt.Errorf("failed to find bucket exists: %v", err) 65 | } 66 | 67 | loc, err := c.GetBucketLocation(ctx, bucketName) 68 | if err != nil { 69 | return nil, errCh, err 70 | } 71 | 72 | return &Store{ 73 | client: c, 74 | location: loc, 75 | bucket: bucketName, 76 | }, errCh, nil 77 | } 78 | 79 | func (s *Store) Get(ctx context.Context, gitoid string) (io.ReadCloser, error) { 80 | return s.client.GetObject(ctx, s.bucket, gitoid, minio.GetObjectOptions{}) 81 | } 82 | 83 | func (s *Store) Store(ctx context.Context, gitoid string, payload []byte) error { 84 | return s.PutBlob(ctx, gitoid, payload) 85 | } 86 | -------------------------------------------------------------------------------- /ent/payloaddigest_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/in-toto/archivista/ent/payloaddigest" 12 | "github.com/in-toto/archivista/ent/predicate" 13 | ) 14 | 15 | // PayloadDigestDelete is the builder for deleting a PayloadDigest entity. 16 | type PayloadDigestDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *PayloadDigestMutation 20 | } 21 | 22 | // Where appends a list predicates to the PayloadDigestDelete builder. 23 | func (_d *PayloadDigestDelete) Where(ps ...predicate.PayloadDigest) *PayloadDigestDelete { 24 | _d.mutation.Where(ps...) 25 | return _d 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (_d *PayloadDigestDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (_d *PayloadDigestDelete) ExecX(ctx context.Context) int { 35 | n, err := _d.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (_d *PayloadDigestDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(payloaddigest.Table, sqlgraph.NewFieldSpec(payloaddigest.FieldID, field.TypeUUID)) 44 | if ps := _d.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | _d.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // PayloadDigestDeleteOne is the builder for deleting a single PayloadDigest entity. 60 | type PayloadDigestDeleteOne struct { 61 | _d *PayloadDigestDelete 62 | } 63 | 64 | // Where appends a list predicates to the PayloadDigestDelete builder. 65 | func (_d *PayloadDigestDeleteOne) Where(ps ...predicate.PayloadDigest) *PayloadDigestDeleteOne { 66 | _d._d.mutation.Where(ps...) 67 | return _d 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (_d *PayloadDigestDeleteOne) Exec(ctx context.Context) error { 72 | n, err := _d._d.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{payloaddigest.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (_d *PayloadDigestDeleteOne) ExecX(ctx context.Context) { 85 | if err := _d.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ent/subjectdigest_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/in-toto/archivista/ent/predicate" 12 | "github.com/in-toto/archivista/ent/subjectdigest" 13 | ) 14 | 15 | // SubjectDigestDelete is the builder for deleting a SubjectDigest entity. 16 | type SubjectDigestDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *SubjectDigestMutation 20 | } 21 | 22 | // Where appends a list predicates to the SubjectDigestDelete builder. 23 | func (_d *SubjectDigestDelete) Where(ps ...predicate.SubjectDigest) *SubjectDigestDelete { 24 | _d.mutation.Where(ps...) 25 | return _d 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (_d *SubjectDigestDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (_d *SubjectDigestDelete) ExecX(ctx context.Context) int { 35 | n, err := _d.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (_d *SubjectDigestDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(subjectdigest.Table, sqlgraph.NewFieldSpec(subjectdigest.FieldID, field.TypeUUID)) 44 | if ps := _d.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | _d.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // SubjectDigestDeleteOne is the builder for deleting a single SubjectDigest entity. 60 | type SubjectDigestDeleteOne struct { 61 | _d *SubjectDigestDelete 62 | } 63 | 64 | // Where appends a list predicates to the SubjectDigestDelete builder. 65 | func (_d *SubjectDigestDeleteOne) Where(ps ...predicate.SubjectDigest) *SubjectDigestDeleteOne { 66 | _d._d.mutation.Where(ps...) 67 | return _d 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (_d *SubjectDigestDeleteOne) Exec(ctx context.Context) error { 72 | n, err := _d._d.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{subjectdigest.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (_d *SubjectDigestDeleteOne) ExecX(ctx context.Context) { 85 | if err := _d.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ent/attestationpolicy_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/in-toto/archivista/ent/attestationpolicy" 12 | "github.com/in-toto/archivista/ent/predicate" 13 | ) 14 | 15 | // AttestationPolicyDelete is the builder for deleting a AttestationPolicy entity. 16 | type AttestationPolicyDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *AttestationPolicyMutation 20 | } 21 | 22 | // Where appends a list predicates to the AttestationPolicyDelete builder. 23 | func (_d *AttestationPolicyDelete) Where(ps ...predicate.AttestationPolicy) *AttestationPolicyDelete { 24 | _d.mutation.Where(ps...) 25 | return _d 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (_d *AttestationPolicyDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (_d *AttestationPolicyDelete) ExecX(ctx context.Context) int { 35 | n, err := _d.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (_d *AttestationPolicyDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(attestationpolicy.Table, sqlgraph.NewFieldSpec(attestationpolicy.FieldID, field.TypeUUID)) 44 | if ps := _d.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | _d.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // AttestationPolicyDeleteOne is the builder for deleting a single AttestationPolicy entity. 60 | type AttestationPolicyDeleteOne struct { 61 | _d *AttestationPolicyDelete 62 | } 63 | 64 | // Where appends a list predicates to the AttestationPolicyDelete builder. 65 | func (_d *AttestationPolicyDeleteOne) Where(ps ...predicate.AttestationPolicy) *AttestationPolicyDeleteOne { 66 | _d._d.mutation.Where(ps...) 67 | return _d 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (_d *AttestationPolicyDeleteOne) Exec(ctx context.Context) error { 72 | n, err := _d._d.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{attestationpolicy.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (_d *AttestationPolicyDeleteOne) ExecX(ctx context.Context) { 85 | if err := _d.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pkg/metadatastorage/attestationcollection/parserstorer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package attestationcollection 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "github.com/google/uuid" 21 | "github.com/in-toto/archivista/ent" 22 | "github.com/in-toto/archivista/pkg/metadatastorage" 23 | "github.com/in-toto/go-witness/attestation" 24 | "github.com/in-toto/go-witness/log" 25 | ) 26 | 27 | const ( 28 | Predicate = "https://witness.testifysec.com/attestation-collection/v0.1" 29 | ) 30 | 31 | // attestation.Collection from go-witness will try to parse each of the attestations by calling their factory functions, 32 | // which require the attestations to be registered in the go-witness library. We don't really care about the actual attestation 33 | // data for the purposes here, so just leave it as a raw message. 34 | type ParsedCollection struct { 35 | attestation.Collection 36 | Attestations []struct { 37 | Type string `json:"type"` 38 | Attestation json.RawMessage `json:"attestation"` 39 | } `json:"attestations"` 40 | } 41 | 42 | func Parse(data []byte) (metadatastorage.Storer, error) { 43 | parsedCollection := ParsedCollection{} 44 | if err := json.Unmarshal(data, &parsedCollection); err != nil { 45 | return parsedCollection, err 46 | } 47 | 48 | return parsedCollection, nil 49 | } 50 | 51 | func (parsedCollection ParsedCollection) Store(ctx context.Context, tx *ent.Tx, stmtID uuid.UUID) error { 52 | collection, err := tx.AttestationCollection.Create(). 53 | SetStatementID(stmtID). 54 | SetName(parsedCollection.Name). 55 | Save(ctx) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | for _, a := range parsedCollection.Attestations { 61 | newAttestation, err := tx.Attestation.Create(). 62 | SetAttestationCollectionID(collection.ID). 63 | SetType(a.Type). 64 | Save(ctx) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | // we parse if a parser is available. otherwise, we ignore it if no parser is available. 70 | if parser, exists := registeredParsers[a.Type]; exists { 71 | if err := parser(ctx, tx, newAttestation, a.Type, a.Attestation); err != nil { 72 | log.Errorf("failed to parse attestation of type %s: %w", a.Type, err) 73 | return err 74 | } 75 | } 76 | } 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /cmd/archivistactl/cmd/iam.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "strings" 21 | 22 | "github.com/aws/aws-sdk-go-v2/aws" 23 | "github.com/aws/aws-sdk-go-v2/config" 24 | "github.com/aws/aws-sdk-go-v2/credentials" 25 | "github.com/in-toto/archivista/pkg/metadatastorage/sqlstore" 26 | "github.com/spf13/cobra" 27 | ) 28 | 29 | var iamCmd = &cobra.Command{ 30 | Use: "iam [sql backend] [connection string]", 31 | Short: "Converts a connection string to use AWS RDS IAM authentication", 32 | Long: `If the sql backend ends with _RDS_IAM, an IAM authentication token is added to the connection string.`, 33 | Example: `archivistactl iam PSQL_RDS_IAM "postgres://user@host:3306/dbname"`, 34 | Args: cobra.ExactArgs(2), 35 | ValidArgs: []string{"MYSQL", "MYSQL_RDS_IAM", "PSQL", "PSQL_RDS_IAM"}, 36 | Annotations: map[string]string{"help:args": "sql backend and connection string"}, 37 | SilenceUsage: true, 38 | DisableFlagsInUseLine: true, 39 | RunE: func(cmd *cobra.Command, args []string) error { 40 | sqlBackend := strings.ToUpper(args[0]) 41 | connectionString := args[1] 42 | dryrun, _ := cmd.Flags().GetBool("dryrun") 43 | 44 | if strings.HasSuffix(sqlBackend, "_RDS_IAM") { 45 | if dryrun { 46 | sqlstore.AwsConfigAPI = &dryrunConfig{ 47 | cfg: aws.Config{ 48 | Region: "us-east-1", 49 | Credentials: credentials.NewStaticCredentialsProvider("foo", "bar", "baz"), 50 | }, 51 | } 52 | } 53 | revisedConnectionString, err := sqlstore.RewriteConnectionStringForIAM(sqlBackend, connectionString, dryrun) 54 | if err != nil { 55 | return err 56 | } 57 | fmt.Println(revisedConnectionString) 58 | } else { 59 | fmt.Println(connectionString) 60 | } 61 | 62 | return nil 63 | }, 64 | } 65 | 66 | func init() { 67 | rootCmd.AddCommand(iamCmd) 68 | iamCmd.Flags().Bool("dryrun", false, "Shows the result using a fake authentication token 'authtoken'") 69 | } 70 | 71 | type dryrunConfig struct { 72 | cfg aws.Config 73 | } 74 | 75 | func (m *dryrunConfig) LoadDefaultConfig(ctx context.Context, optFns ...func(*config.LoadOptions) error) (aws.Config, error) { 76 | return m.cfg, nil 77 | } 78 | -------------------------------------------------------------------------------- /cmd/archivistactl/cmd/retrieve_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "bytes" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/suite" 22 | ) 23 | 24 | // Test Suite: UT Retrieve 25 | type UTRetrieveSuite struct { 26 | suite.Suite 27 | } 28 | 29 | func TestUTRetrieveSuite(t *testing.T) { 30 | suite.Run(t, new(UTRetrieveSuite)) 31 | } 32 | 33 | func (ut *UTRetrieveSuite) Test_MissingSubCommand() { 34 | output := bytes.NewBufferString("") 35 | rootCmd.SetOut(output) 36 | rootCmd.SetErr(output) 37 | rootCmd.SetArgs([]string{"retrieve"}) 38 | err := rootCmd.Execute() 39 | if err != nil { 40 | ut.FailNow("Expected: error") 41 | } 42 | ut.Contains(output.String(), "archivistactl retrieve") 43 | } 44 | 45 | func (ut *UTRetrieveSuite) Test_RetrieveEnvelopeMissingArg() { 46 | output := bytes.NewBufferString("") 47 | rootCmd.SetOut(output) 48 | rootCmd.SetErr(output) 49 | rootCmd.SetArgs([]string{"retrieve", "envelope"}) 50 | err := rootCmd.Execute() 51 | if err != nil { 52 | ut.ErrorContains(err, "accepts 1 arg(s), received 0") 53 | } else { 54 | ut.FailNow("Expected: error") 55 | } 56 | } 57 | 58 | func (ut *UTRetrieveSuite) Test_RetrieveEnvelope_NoDB() { 59 | output := bytes.NewBufferString("") 60 | rootCmd.SetOut(output) 61 | rootCmd.SetErr(output) 62 | rootCmd.SetArgs([]string{"retrieve", "envelope", "test"}) 63 | err := rootCmd.Execute() 64 | if err != nil { 65 | ut.ErrorContains(err, "connection re") 66 | } else { 67 | ut.FailNow("Expected: error") 68 | } 69 | } 70 | 71 | func (ut *UTRetrieveSuite) Test_RetrieveSubjectsMissingArg() { 72 | output := bytes.NewBufferString("") 73 | rootCmd.SetOut(output) 74 | rootCmd.SetErr(output) 75 | rootCmd.SetArgs([]string{"retrieve", "subjects"}) 76 | err := rootCmd.Execute() 77 | if err != nil { 78 | ut.ErrorContains(err, "accepts 1 arg(s), received 0") 79 | } else { 80 | ut.FailNow("Expected: error") 81 | } 82 | } 83 | 84 | func (ut *UTRetrieveSuite) Test_RetrieveSubjectsNoDB() { 85 | output := bytes.NewBufferString("") 86 | rootCmd.SetOut(output) 87 | rootCmd.SetErr(output) 88 | rootCmd.SetArgs([]string{"retrieve", "subjects", "test"}) 89 | err := rootCmd.Execute() 90 | if err != nil { 91 | ut.ErrorContains(err, "connection re") 92 | } else { 93 | ut.FailNow("Expected: error") 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /ent/attestationcollection_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/in-toto/archivista/ent/attestationcollection" 12 | "github.com/in-toto/archivista/ent/predicate" 13 | ) 14 | 15 | // AttestationCollectionDelete is the builder for deleting a AttestationCollection entity. 16 | type AttestationCollectionDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *AttestationCollectionMutation 20 | } 21 | 22 | // Where appends a list predicates to the AttestationCollectionDelete builder. 23 | func (_d *AttestationCollectionDelete) Where(ps ...predicate.AttestationCollection) *AttestationCollectionDelete { 24 | _d.mutation.Where(ps...) 25 | return _d 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (_d *AttestationCollectionDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (_d *AttestationCollectionDelete) ExecX(ctx context.Context) int { 35 | n, err := _d.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (_d *AttestationCollectionDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(attestationcollection.Table, sqlgraph.NewFieldSpec(attestationcollection.FieldID, field.TypeUUID)) 44 | if ps := _d.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | _d.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // AttestationCollectionDeleteOne is the builder for deleting a single AttestationCollection entity. 60 | type AttestationCollectionDeleteOne struct { 61 | _d *AttestationCollectionDelete 62 | } 63 | 64 | // Where appends a list predicates to the AttestationCollectionDelete builder. 65 | func (_d *AttestationCollectionDeleteOne) Where(ps ...predicate.AttestationCollection) *AttestationCollectionDeleteOne { 66 | _d._d.mutation.Where(ps...) 67 | return _d 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (_d *AttestationCollectionDeleteOne) Exec(ctx context.Context) error { 72 | n, err := _d._d.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{attestationcollection.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (_d *AttestationCollectionDeleteOne) ExecX(ctx context.Context) { 85 | if err := _d.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /SECURITY-INSIGHTS.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Witness Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | header: 16 | schema-version: 1.0.0 17 | expiration-date: '2024-08-31T10:10:09.000Z' 18 | last-updated: '2023-12-20' 19 | last-reviewed: '2023-12-20' 20 | commit-hash: cd0c222058a8830a8e190b840e466098b25a3c41 21 | project-url: https://github.com/in-toto/archivista 22 | project-release: 'v0.2.0' 23 | changelog: https://github.com/in-toto/archivista/releases/tag/v0.2.0 24 | license: https://github.com/in-toto/archivista/blob/main/LICENSE 25 | 26 | project-lifecycle: 27 | status: active 28 | roadmap: https://github.com/orgs/in-toto/projects/4/views/3 29 | bug-fixes-only: false 30 | core-maintainers: 31 | - https://github.com/in-toto/archivista/MAINTAINERS.md 32 | release-cycle: https://github.com/in-toto/archivista/releases 33 | 34 | contribution-policy: 35 | accepts-pull-requests: true 36 | accepts-automated-pull-requests: true 37 | contributing-policy: https://github.com/in-toto/archivista/blob/main/CONTRIBUTING.md 38 | code-of-conduct: https://github.com/in-toto/archivista/blob/main/CODE_OF_CONDUCT.md 39 | 40 | documentation: 41 | - https://in-toto.io 42 | 43 | distribution-points: 44 | - https://github.com/in-toto/archivista/releases 45 | 46 | security-testing: 47 | - tool-type: sca 48 | tool-name: Dependabot 49 | tool-version: 2 50 | tool-url: https://github.com/dependabot 51 | integration: 52 | ad-hoc: false 53 | ci: true 54 | before-release: false 55 | 56 | security-contacts: 57 | - type: email 58 | value: security@testifysec.com 59 | primary: true 60 | 61 | vulnerability-reporting: 62 | accepts-vulnerability-reports: true 63 | email-contact: security@testifysec.com 64 | security-policy: https://github.com/in-toto/archivista/SECURITY.md 65 | 66 | dependencies: 67 | third-party-packages: true 68 | dependencies-lists: 69 | - https://github.com/in-toto/archivista/go.mod 70 | sbom: 71 | - sbom-file: https://foo.bar/sbom 72 | sbom-format: CycloneDX 73 | sbom-url: https://foo.bar 74 | dependencies-lifecycle: 75 | policy-url: https://github.com/in-toto/archivista/SECURITY.md 76 | comment: | 77 | All dependencies are subject to the Archivista Security Policy. 78 | env-dependencies-policy: 79 | policy-url: https://github.com/in-toto/archivista/DEPENDENCY.md 80 | comment: | 81 | All dependencies are subject to the Archivista Dependency Policy. 82 | -------------------------------------------------------------------------------- /pkg/objectstorage/filestore/file_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package filestore_test 15 | 16 | import ( 17 | "context" 18 | "io" 19 | "os" 20 | "path/filepath" 21 | "testing" 22 | 23 | "github.com/in-toto/archivista/pkg/objectstorage/filestore" 24 | "github.com/stretchr/testify/suite" 25 | ) 26 | 27 | // Test Suite: UT FileStoreSuite 28 | type UTFileStoreSuite struct { 29 | suite.Suite 30 | tempDir string 31 | payload []byte 32 | } 33 | 34 | func TestUTFileStoreSuite(t *testing.T) { 35 | suite.Run(t, new(UTFileStoreSuite)) 36 | } 37 | 38 | func (ut *UTFileStoreSuite) SetupTest() { 39 | // Create a temporary directory for testing 40 | tempDir, err := os.MkdirTemp("", "filestore_test") 41 | if err != nil { 42 | ut.FailNow(err.Error()) 43 | } 44 | ut.tempDir = tempDir 45 | ut.payload = []byte("test payload") 46 | } 47 | 48 | func (ut *UTFileStoreSuite) TearDownTest() { 49 | os.RemoveAll(ut.tempDir) 50 | } 51 | func (ut *UTFileStoreSuite) Test_Get() { 52 | 53 | store, _, err := filestore.New(context.Background(), ut.tempDir, ":50025") 54 | if err != nil { 55 | ut.FailNow(err.Error()) 56 | } 57 | 58 | // Define a test payload 59 | payload := []byte("test payload") 60 | 61 | // Store the payload 62 | err = store.Store(context.Background(), "test_gitoid", payload) 63 | if err != nil { 64 | ut.FailNow(err.Error()) 65 | } 66 | 67 | // Attempt storing at malicious payload location 68 | err = store.Store(context.Background(), "../../test_gitoid", payload) 69 | if err != nil && err != filepath.ErrBadPattern { 70 | ut.FailNowf("Failed to detect bad path: %v", err.Error()) 71 | } 72 | 73 | // Retrieve the payload 74 | reader, err := store.Get(context.Background(), "test_gitoid") 75 | if err != nil { 76 | ut.FailNowf("Failed to retrieve payload: %v", err.Error()) 77 | } 78 | defer reader.Close() 79 | 80 | // Read the payload from the reader 81 | retrievedPayload, err := io.ReadAll(reader) 82 | if err != nil { 83 | ut.FailNowf("Failed to read payload: %v", err.Error()) 84 | } 85 | 86 | // Compare the retrieved payload with the original payload 87 | ut.Equal(string(retrievedPayload), string(payload)) 88 | 89 | // Attempt to retrieve non-local payload 90 | _, err = store.Get(context.Background(), "/etc/passwd") 91 | if err != nil && err != filepath.ErrBadPattern { 92 | ut.FailNowf("Failed to detect bad path: %v", err.Error()) 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /pkg/api/upload.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package api 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "encoding/json" 21 | "errors" 22 | "io" 23 | "net/http" 24 | "net/url" 25 | 26 | "github.com/in-toto/go-witness/dsse" 27 | ) 28 | 29 | type UploadResponse struct { 30 | Gitoid string `json:"gitoid"` 31 | } 32 | 33 | // Deprecated: Use UploadResponse instead. It will be removed in version >= v0.6.0 34 | type StoreResponse = UploadResponse 35 | 36 | // Deprecated: Use Store instead. It will be removed in version >= v0.6.0 37 | func Upload(ctx context.Context, baseURL string, envelope dsse.Envelope, requestOptions ...RequestOption) (UploadResponse, error) { 38 | return Store(ctx, baseURL, envelope, requestOptions...) 39 | } 40 | 41 | func Store(ctx context.Context, baseURL string, envelope dsse.Envelope, requestOptions ...RequestOption) (StoreResponse, error) { 42 | buf := &bytes.Buffer{} 43 | enc := json.NewEncoder(buf) 44 | if err := enc.Encode(envelope); err != nil { 45 | return StoreResponse{}, err 46 | } 47 | 48 | return StoreWithReader(ctx, baseURL, buf, requestOptions...) 49 | } 50 | 51 | func StoreWithReader(ctx context.Context, baseURL string, r io.Reader, requestOptions ...RequestOption) (StoreResponse, error) { 52 | return StoreWithReaderWithHTTPClient(ctx, &http.Client{}, baseURL, r, requestOptions...) 53 | } 54 | 55 | func StoreWithReaderWithHTTPClient(ctx context.Context, client *http.Client, baseURL string, r io.Reader, requestOptions ...RequestOption) (StoreResponse, error) { 56 | uploadPath, err := url.JoinPath(baseURL, "upload") 57 | if err != nil { 58 | return UploadResponse{}, err 59 | } 60 | 61 | req, err := http.NewRequestWithContext(ctx, "POST", uploadPath, r) 62 | if err != nil { 63 | return UploadResponse{}, err 64 | } 65 | 66 | req = applyRequestOptions(req, requestOptions...) 67 | req.Header.Set("Content-Type", "application/json") 68 | hc := &http.Client{} 69 | resp, err := hc.Do(req) 70 | if err != nil { 71 | return UploadResponse{}, err 72 | } 73 | 74 | defer resp.Body.Close() 75 | bodyBytes, err := io.ReadAll(resp.Body) 76 | if err != nil { 77 | return UploadResponse{}, err 78 | } 79 | 80 | if resp.StatusCode != http.StatusOK { 81 | return UploadResponse{}, errors.New(string(bodyBytes)) 82 | } 83 | 84 | uploadResp := UploadResponse{} 85 | if err := json.Unmarshal(bodyBytes, &uploadResp); err != nil { 86 | return UploadResponse{}, err 87 | } 88 | 89 | return uploadResp, nil 90 | } 91 | -------------------------------------------------------------------------------- /pkg/metadatastorage/sqlstore/iam.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sqlstore 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "strings" 21 | 22 | "github.com/aws/aws-sdk-go-v2/aws" 23 | "github.com/aws/aws-sdk-go-v2/config" 24 | "github.com/jkjell/go-db-credential-refresh/driver" 25 | 26 | _ "github.com/lib/pq" 27 | ) 28 | 29 | type AWSConfigAPI interface { 30 | // LoadDefaultConfig loads the default AWS configuration. 31 | LoadDefaultConfig(ctx context.Context, opts ...func(*config.LoadOptions) error) (aws.Config, error) 32 | } 33 | 34 | type AWSConfig struct{} 35 | 36 | func (c *AWSConfig) LoadDefaultConfig(ctx context.Context, opts ...func(*config.LoadOptions) error) (aws.Config, error) { 37 | return config.LoadDefaultConfig(ctx, opts...) 38 | } 39 | 40 | var AwsConfigAPI AWSConfigAPI = &AWSConfig{} 41 | 42 | // RewriteConnectionStringForIAM rewrites the connection string to use AWS RDS IAM authentication 43 | // It supports both MYSQL_RDS_IAM and PSQL_RDS_IAM backends 44 | func RewriteConnectionStringForIAM(sqlBackend string, connectionString string, dryRun bool) (string, error) { 45 | var dc *driver.Config 46 | var user string 47 | upperSqlBackend := strings.ToUpper(sqlBackend) 48 | 49 | if strings.HasPrefix(upperSqlBackend, "MYSQL") { 50 | var err error 51 | dc, user, _, err = ConfigFromMySQL(connectionString) 52 | if err != nil { 53 | return "", fmt.Errorf("could not get driver config from mysql connection string: %w", err) 54 | } 55 | // for mysql, we need to add some query parameters 56 | dc.Opts["tls"] = "true" 57 | dc.Opts["allowCleartextPasswords"] = "true" 58 | } else if strings.HasPrefix(upperSqlBackend, "PSQL") { 59 | var err error 60 | dc, user, _, err = ConfigFromPostgres(connectionString) 61 | if err != nil { 62 | return "", fmt.Errorf("could not get driver config from mysql connection string: %w", err) 63 | } 64 | } else { 65 | return "", fmt.Errorf("unsupported sql backend: %s", sqlBackend) 66 | } 67 | 68 | s, err := AWSRDSStoreFromDriverConfig(dc, user) 69 | if err != nil { 70 | return "", fmt.Errorf("could not create credentials refresh store: %w", err) 71 | } 72 | 73 | creds, err := s.Get(context.Background()) 74 | if err != nil { 75 | return "", fmt.Errorf("could not get refreshed credentials: %w", err) 76 | } 77 | 78 | if creds == nil { 79 | return "", fmt.Errorf("refreshed credentials are nil") 80 | } 81 | 82 | var password string 83 | if dryRun { 84 | password = "authtoken" 85 | } else { 86 | password = creds.GetPassword() 87 | } 88 | return dc.Formatter(creds.GetUsername(), password, dc.Host, dc.Port, dc.DB, dc.Opts), nil 89 | } 90 | -------------------------------------------------------------------------------- /ent/attestationpolicy/attestationpolicy.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package attestationpolicy 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | "entgo.io/ent/dialect/sql/sqlgraph" 8 | "github.com/google/uuid" 9 | ) 10 | 11 | const ( 12 | // Label holds the string label denoting the attestationpolicy type in the database. 13 | Label = "attestation_policy" 14 | // FieldID holds the string denoting the id field in the database. 15 | FieldID = "id" 16 | // FieldName holds the string denoting the name field in the database. 17 | FieldName = "name" 18 | // EdgeStatement holds the string denoting the statement edge name in mutations. 19 | EdgeStatement = "statement" 20 | // Table holds the table name of the attestationpolicy in the database. 21 | Table = "attestation_policies" 22 | // StatementTable is the table that holds the statement relation/edge. 23 | StatementTable = "attestation_policies" 24 | // StatementInverseTable is the table name for the Statement entity. 25 | // It exists in this package in order to avoid circular dependency with the "statement" package. 26 | StatementInverseTable = "statements" 27 | // StatementColumn is the table column denoting the statement relation/edge. 28 | StatementColumn = "statement_policy" 29 | ) 30 | 31 | // Columns holds all SQL columns for attestationpolicy fields. 32 | var Columns = []string{ 33 | FieldID, 34 | FieldName, 35 | } 36 | 37 | // ForeignKeys holds the SQL foreign-keys that are owned by the "attestation_policies" 38 | // table and are not defined as standalone fields in the schema. 39 | var ForeignKeys = []string{ 40 | "statement_policy", 41 | } 42 | 43 | // ValidColumn reports if the column name is valid (part of the table columns). 44 | func ValidColumn(column string) bool { 45 | for i := range Columns { 46 | if column == Columns[i] { 47 | return true 48 | } 49 | } 50 | for i := range ForeignKeys { 51 | if column == ForeignKeys[i] { 52 | return true 53 | } 54 | } 55 | return false 56 | } 57 | 58 | var ( 59 | // NameValidator is a validator for the "name" field. It is called by the builders before save. 60 | NameValidator func(string) error 61 | // DefaultID holds the default value on creation for the "id" field. 62 | DefaultID func() uuid.UUID 63 | ) 64 | 65 | // OrderOption defines the ordering options for the AttestationPolicy queries. 66 | type OrderOption func(*sql.Selector) 67 | 68 | // ByID orders the results by the id field. 69 | func ByID(opts ...sql.OrderTermOption) OrderOption { 70 | return sql.OrderByField(FieldID, opts...).ToFunc() 71 | } 72 | 73 | // ByName orders the results by the name field. 74 | func ByName(opts ...sql.OrderTermOption) OrderOption { 75 | return sql.OrderByField(FieldName, opts...).ToFunc() 76 | } 77 | 78 | // ByStatementField orders the results by statement field. 79 | func ByStatementField(field string, opts ...sql.OrderTermOption) OrderOption { 80 | return func(s *sql.Selector) { 81 | sqlgraph.OrderByNeighborTerms(s, newStatementStep(), sql.OrderByField(field, opts...)) 82 | } 83 | } 84 | func newStatementStep() *sqlgraph.Step { 85 | return sqlgraph.NewStep( 86 | sqlgraph.From(Table, FieldID), 87 | sqlgraph.To(StatementInverseTable, FieldID), 88 | sqlgraph.Edge(sqlgraph.O2O, true, StatementTable, StatementColumn), 89 | ) 90 | } 91 | -------------------------------------------------------------------------------- /ent/timestamp/timestamp.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package timestamp 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | "entgo.io/ent/dialect/sql/sqlgraph" 8 | "github.com/google/uuid" 9 | ) 10 | 11 | const ( 12 | // Label holds the string label denoting the timestamp type in the database. 13 | Label = "timestamp" 14 | // FieldID holds the string denoting the id field in the database. 15 | FieldID = "id" 16 | // FieldType holds the string denoting the type field in the database. 17 | FieldType = "type" 18 | // FieldTimestamp holds the string denoting the timestamp field in the database. 19 | FieldTimestamp = "timestamp" 20 | // EdgeSignature holds the string denoting the signature edge name in mutations. 21 | EdgeSignature = "signature" 22 | // Table holds the table name of the timestamp in the database. 23 | Table = "timestamps" 24 | // SignatureTable is the table that holds the signature relation/edge. 25 | SignatureTable = "timestamps" 26 | // SignatureInverseTable is the table name for the Signature entity. 27 | // It exists in this package in order to avoid circular dependency with the "signature" package. 28 | SignatureInverseTable = "signatures" 29 | // SignatureColumn is the table column denoting the signature relation/edge. 30 | SignatureColumn = "signature_timestamps" 31 | ) 32 | 33 | // Columns holds all SQL columns for timestamp fields. 34 | var Columns = []string{ 35 | FieldID, 36 | FieldType, 37 | FieldTimestamp, 38 | } 39 | 40 | // ForeignKeys holds the SQL foreign-keys that are owned by the "timestamps" 41 | // table and are not defined as standalone fields in the schema. 42 | var ForeignKeys = []string{ 43 | "signature_timestamps", 44 | } 45 | 46 | // ValidColumn reports if the column name is valid (part of the table columns). 47 | func ValidColumn(column string) bool { 48 | for i := range Columns { 49 | if column == Columns[i] { 50 | return true 51 | } 52 | } 53 | for i := range ForeignKeys { 54 | if column == ForeignKeys[i] { 55 | return true 56 | } 57 | } 58 | return false 59 | } 60 | 61 | var ( 62 | // DefaultID holds the default value on creation for the "id" field. 63 | DefaultID func() uuid.UUID 64 | ) 65 | 66 | // OrderOption defines the ordering options for the Timestamp queries. 67 | type OrderOption func(*sql.Selector) 68 | 69 | // ByID orders the results by the id field. 70 | func ByID(opts ...sql.OrderTermOption) OrderOption { 71 | return sql.OrderByField(FieldID, opts...).ToFunc() 72 | } 73 | 74 | // ByType orders the results by the type field. 75 | func ByType(opts ...sql.OrderTermOption) OrderOption { 76 | return sql.OrderByField(FieldType, opts...).ToFunc() 77 | } 78 | 79 | // ByTimestamp orders the results by the timestamp field. 80 | func ByTimestamp(opts ...sql.OrderTermOption) OrderOption { 81 | return sql.OrderByField(FieldTimestamp, opts...).ToFunc() 82 | } 83 | 84 | // BySignatureField orders the results by signature field. 85 | func BySignatureField(field string, opts ...sql.OrderTermOption) OrderOption { 86 | return func(s *sql.Selector) { 87 | sqlgraph.OrderByNeighborTerms(s, newSignatureStep(), sql.OrderByField(field, opts...)) 88 | } 89 | } 90 | func newSignatureStep() *sqlgraph.Step { 91 | return sqlgraph.NewStep( 92 | sqlgraph.From(Table, FieldID), 93 | sqlgraph.To(SignatureInverseTable, FieldID), 94 | sqlgraph.Edge(sqlgraph.M2O, true, SignatureTable, SignatureColumn), 95 | ) 96 | } 97 | -------------------------------------------------------------------------------- /pkg/artifactstore/artifactstore_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package artifactstore 16 | 17 | import ( 18 | "os" 19 | "path/filepath" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | "github.com/stretchr/testify/require" 24 | ) 25 | 26 | func createTestConfigFile(t *testing.T, workingDir, version, distroFilePath, distroDigest string) string { 27 | testConfig := `artifacts: 28 | witness: 29 | versions: 30 | ` + version + `: 31 | description: test 32 | distributions: 33 | linux: 34 | filelocation: ` + distroFilePath + ` 35 | sha256digest: ` + distroDigest 36 | testConfigFilePath := filepath.Join(workingDir, "config.yaml") 37 | testConfigFile, err := os.Create(testConfigFilePath) 38 | require.NoError(t, err) 39 | _, err = testConfigFile.WriteString(testConfig) 40 | require.NoError(t, err) 41 | require.NoError(t, testConfigFile.Close()) 42 | return testConfigFilePath 43 | } 44 | 45 | func TestStore(t *testing.T) { 46 | workingDir := t.TempDir() 47 | testDistroFilePath := filepath.Join(workingDir, "test") 48 | testDistroDigest := "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" 49 | testVersion := "v0.1.0" 50 | testDistroFile, err := os.Create(testDistroFilePath) 51 | require.NoError(t, err) 52 | _, err = testDistroFile.Write([]byte("test")) 53 | require.NoError(t, err) 54 | require.NoError(t, testDistroFile.Close()) 55 | 56 | t.Run("all good", func(t *testing.T) { 57 | testArtifactName := "witness" 58 | testDistroName := "linux" 59 | testConfigFilePath := createTestConfigFile(t, workingDir, testVersion, testDistroFilePath, testDistroDigest) 60 | as, err := New(WithConfigFile(testConfigFilePath)) 61 | require.NoError(t, err) 62 | artifacts := as.Artifacts() 63 | assert.Len(t, artifacts, 1) 64 | versions, ok := as.Versions(testArtifactName) 65 | assert.True(t, ok) 66 | assert.Len(t, versions, 1) 67 | version, ok := as.Version(testArtifactName, testVersion) 68 | assert.True(t, ok) 69 | assert.Len(t, version.Distributions, 1) 70 | testDistro, ok := as.Distribution(testArtifactName, testVersion, testDistroName) 71 | assert.True(t, ok) 72 | assert.Equal(t, testDistro.FileLocation, testDistroFilePath) 73 | assert.Equal(t, testDistro.SHA256Digest, testDistroDigest) 74 | }) 75 | 76 | t.Run("wrong file path", func(t *testing.T) { 77 | testConfigFilePath := createTestConfigFile(t, workingDir, testVersion, "garbage", testDistroDigest) 78 | _, err := New(WithConfigFile(testConfigFilePath)) 79 | assert.Error(t, err) 80 | 81 | }) 82 | 83 | t.Run("wrong file digest", func(t *testing.T) { 84 | testConfigFilePath := createTestConfigFile(t, workingDir, testVersion, testDistroFilePath, "garbage") 85 | _, err := New(WithConfigFile(testConfigFilePath)) 86 | assert.Error(t, err) 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /ent/attestation/attestation.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package attestation 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | "entgo.io/ent/dialect/sql/sqlgraph" 8 | "github.com/google/uuid" 9 | ) 10 | 11 | const ( 12 | // Label holds the string label denoting the attestation type in the database. 13 | Label = "attestation" 14 | // FieldID holds the string denoting the id field in the database. 15 | FieldID = "id" 16 | // FieldType holds the string denoting the type field in the database. 17 | FieldType = "type" 18 | // EdgeAttestationCollection holds the string denoting the attestation_collection edge name in mutations. 19 | EdgeAttestationCollection = "attestation_collection" 20 | // Table holds the table name of the attestation in the database. 21 | Table = "attestations" 22 | // AttestationCollectionTable is the table that holds the attestation_collection relation/edge. 23 | AttestationCollectionTable = "attestations" 24 | // AttestationCollectionInverseTable is the table name for the AttestationCollection entity. 25 | // It exists in this package in order to avoid circular dependency with the "attestationcollection" package. 26 | AttestationCollectionInverseTable = "attestation_collections" 27 | // AttestationCollectionColumn is the table column denoting the attestation_collection relation/edge. 28 | AttestationCollectionColumn = "attestation_collection_attestations" 29 | ) 30 | 31 | // Columns holds all SQL columns for attestation fields. 32 | var Columns = []string{ 33 | FieldID, 34 | FieldType, 35 | } 36 | 37 | // ForeignKeys holds the SQL foreign-keys that are owned by the "attestations" 38 | // table and are not defined as standalone fields in the schema. 39 | var ForeignKeys = []string{ 40 | "attestation_collection_attestations", 41 | } 42 | 43 | // ValidColumn reports if the column name is valid (part of the table columns). 44 | func ValidColumn(column string) bool { 45 | for i := range Columns { 46 | if column == Columns[i] { 47 | return true 48 | } 49 | } 50 | for i := range ForeignKeys { 51 | if column == ForeignKeys[i] { 52 | return true 53 | } 54 | } 55 | return false 56 | } 57 | 58 | var ( 59 | // TypeValidator is a validator for the "type" field. It is called by the builders before save. 60 | TypeValidator func(string) error 61 | // DefaultID holds the default value on creation for the "id" field. 62 | DefaultID func() uuid.UUID 63 | ) 64 | 65 | // OrderOption defines the ordering options for the Attestation queries. 66 | type OrderOption func(*sql.Selector) 67 | 68 | // ByID orders the results by the id field. 69 | func ByID(opts ...sql.OrderTermOption) OrderOption { 70 | return sql.OrderByField(FieldID, opts...).ToFunc() 71 | } 72 | 73 | // ByType orders the results by the type field. 74 | func ByType(opts ...sql.OrderTermOption) OrderOption { 75 | return sql.OrderByField(FieldType, opts...).ToFunc() 76 | } 77 | 78 | // ByAttestationCollectionField orders the results by attestation_collection field. 79 | func ByAttestationCollectionField(field string, opts ...sql.OrderTermOption) OrderOption { 80 | return func(s *sql.Selector) { 81 | sqlgraph.OrderByNeighborTerms(s, newAttestationCollectionStep(), sql.OrderByField(field, opts...)) 82 | } 83 | } 84 | func newAttestationCollectionStep() *sqlgraph.Step { 85 | return sqlgraph.NewStep( 86 | sqlgraph.From(Table, FieldID), 87 | sqlgraph.To(AttestationCollectionInverseTable, FieldID), 88 | sqlgraph.Edge(sqlgraph.M2O, true, AttestationCollectionTable, AttestationCollectionColumn), 89 | ) 90 | } 91 | -------------------------------------------------------------------------------- /pkg/metadatastorage/sqlstore/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sqlstore 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "strconv" 21 | "strings" 22 | 23 | "github.com/go-sql-driver/mysql" 24 | "github.com/jackc/pgx/v5" 25 | "github.com/jkjell/go-db-credential-refresh/driver" 26 | "github.com/jkjell/go-db-credential-refresh/store/awsrds" 27 | ) 28 | 29 | func ConfigFromPostgres(connectionString string) (c *driver.Config, user, password string, err error) { 30 | dc := driver.Config{ 31 | Retries: 3, 32 | } 33 | dc.Formatter = driver.PgFormatter 34 | 35 | dbConfig, err := pgx.ParseConfig(connectionString) 36 | if err != nil { 37 | return nil, "", "", fmt.Errorf("could not parse postgresql connection string: %w", err) 38 | } 39 | 40 | dc.Host = dbConfig.Host 41 | dc.Port = int(dbConfig.Port) 42 | dc.DB = dbConfig.Database 43 | dc.Opts = dbConfig.RuntimeParams 44 | if dc.Opts == nil { 45 | dc.Opts = make(map[string]string) 46 | } 47 | // ParseConfig truncates sslmode param 48 | if dbConfig.TLSConfig == nil { 49 | dc.Opts["sslmode"] = "disable" 50 | } 51 | 52 | if dbConfig.User == "" { 53 | return nil, "", "", fmt.Errorf("connection string is missing a user") 54 | } 55 | 56 | return &dc, dbConfig.User, dbConfig.Password, nil 57 | } 58 | 59 | func ConfigFromMySQL(connectionString string) (c *driver.Config, user, password string, err error) { 60 | dc := driver.Config{ 61 | Retries: 3, 62 | } 63 | dc.Formatter = driver.MysqlFormatter 64 | 65 | dbConfig, err := mysql.ParseDSN(connectionString) 66 | if err != nil { 67 | return nil, "", "", fmt.Errorf("parsing connection string: %w", err) 68 | } 69 | 70 | addr := strings.Split(dbConfig.Addr, ":") 71 | dc.Host = addr[0] 72 | port, err := strconv.Atoi(addr[1]) 73 | if err != nil { 74 | return nil, "", "", fmt.Errorf("could not parse mysql port: %w", err) 75 | } 76 | dc.Port = port 77 | dc.DB = dbConfig.DBName 78 | 79 | dc.Opts = dbConfig.Params 80 | // this tells the go-sql-driver to parse times from mysql to go's time.Time 81 | // see https://github.com/go-sql-driver/mysql#timetime-support for details 82 | if dc.Opts == nil { 83 | dc.Opts = make(map[string]string) 84 | } 85 | dc.Opts["parseTime"] = "true" 86 | 87 | if dbConfig.User == "" { 88 | return nil, "", "", fmt.Errorf("connection string is missing a user") 89 | } 90 | 91 | return &dc, dbConfig.User, dbConfig.Passwd, nil 92 | } 93 | 94 | func AWSRDSStoreFromDriverConfig(dc *driver.Config, user string) (driver.Store, error) { 95 | awsConfig, err := AwsConfigAPI.LoadDefaultConfig(context.Background()) 96 | if err != nil { 97 | return nil, fmt.Errorf("problem loading AWS config: %w", err) 98 | } 99 | 100 | rdsEndpoint := fmt.Sprintf("%s:%d", dc.Host, dc.Port) 101 | config := awsrds.Config{ 102 | Credentials: awsConfig.Credentials, 103 | Endpoint: rdsEndpoint, 104 | User: user, 105 | Region: awsConfig.Region, 106 | } 107 | 108 | return awsrds.NewStore(&config) 109 | } 110 | -------------------------------------------------------------------------------- /ent/payloaddigest/payloaddigest.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package payloaddigest 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | "entgo.io/ent/dialect/sql/sqlgraph" 8 | "github.com/google/uuid" 9 | ) 10 | 11 | const ( 12 | // Label holds the string label denoting the payloaddigest type in the database. 13 | Label = "payload_digest" 14 | // FieldID holds the string denoting the id field in the database. 15 | FieldID = "id" 16 | // FieldAlgorithm holds the string denoting the algorithm field in the database. 17 | FieldAlgorithm = "algorithm" 18 | // FieldValue holds the string denoting the value field in the database. 19 | FieldValue = "value" 20 | // EdgeDsse holds the string denoting the dsse edge name in mutations. 21 | EdgeDsse = "dsse" 22 | // Table holds the table name of the payloaddigest in the database. 23 | Table = "payload_digests" 24 | // DsseTable is the table that holds the dsse relation/edge. 25 | DsseTable = "payload_digests" 26 | // DsseInverseTable is the table name for the Dsse entity. 27 | // It exists in this package in order to avoid circular dependency with the "dsse" package. 28 | DsseInverseTable = "dsses" 29 | // DsseColumn is the table column denoting the dsse relation/edge. 30 | DsseColumn = "dsse_payload_digests" 31 | ) 32 | 33 | // Columns holds all SQL columns for payloaddigest fields. 34 | var Columns = []string{ 35 | FieldID, 36 | FieldAlgorithm, 37 | FieldValue, 38 | } 39 | 40 | // ForeignKeys holds the SQL foreign-keys that are owned by the "payload_digests" 41 | // table and are not defined as standalone fields in the schema. 42 | var ForeignKeys = []string{ 43 | "dsse_payload_digests", 44 | } 45 | 46 | // ValidColumn reports if the column name is valid (part of the table columns). 47 | func ValidColumn(column string) bool { 48 | for i := range Columns { 49 | if column == Columns[i] { 50 | return true 51 | } 52 | } 53 | for i := range ForeignKeys { 54 | if column == ForeignKeys[i] { 55 | return true 56 | } 57 | } 58 | return false 59 | } 60 | 61 | var ( 62 | // AlgorithmValidator is a validator for the "algorithm" field. It is called by the builders before save. 63 | AlgorithmValidator func(string) error 64 | // ValueValidator is a validator for the "value" field. It is called by the builders before save. 65 | ValueValidator func(string) error 66 | // DefaultID holds the default value on creation for the "id" field. 67 | DefaultID func() uuid.UUID 68 | ) 69 | 70 | // OrderOption defines the ordering options for the PayloadDigest queries. 71 | type OrderOption func(*sql.Selector) 72 | 73 | // ByID orders the results by the id field. 74 | func ByID(opts ...sql.OrderTermOption) OrderOption { 75 | return sql.OrderByField(FieldID, opts...).ToFunc() 76 | } 77 | 78 | // ByAlgorithm orders the results by the algorithm field. 79 | func ByAlgorithm(opts ...sql.OrderTermOption) OrderOption { 80 | return sql.OrderByField(FieldAlgorithm, opts...).ToFunc() 81 | } 82 | 83 | // ByValue orders the results by the value field. 84 | func ByValue(opts ...sql.OrderTermOption) OrderOption { 85 | return sql.OrderByField(FieldValue, opts...).ToFunc() 86 | } 87 | 88 | // ByDsseField orders the results by dsse field. 89 | func ByDsseField(field string, opts ...sql.OrderTermOption) OrderOption { 90 | return func(s *sql.Selector) { 91 | sqlgraph.OrderByNeighborTerms(s, newDsseStep(), sql.OrderByField(field, opts...)) 92 | } 93 | } 94 | func newDsseStep() *sqlgraph.Step { 95 | return sqlgraph.NewStep( 96 | sqlgraph.From(Table, FieldID), 97 | sqlgraph.To(DsseInverseTable, FieldID), 98 | sqlgraph.Edge(sqlgraph.M2O, true, DsseTable, DsseColumn), 99 | ) 100 | } 101 | -------------------------------------------------------------------------------- /ent/subjectdigest/subjectdigest.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package subjectdigest 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | "entgo.io/ent/dialect/sql/sqlgraph" 8 | "github.com/google/uuid" 9 | ) 10 | 11 | const ( 12 | // Label holds the string label denoting the subjectdigest type in the database. 13 | Label = "subject_digest" 14 | // FieldID holds the string denoting the id field in the database. 15 | FieldID = "id" 16 | // FieldAlgorithm holds the string denoting the algorithm field in the database. 17 | FieldAlgorithm = "algorithm" 18 | // FieldValue holds the string denoting the value field in the database. 19 | FieldValue = "value" 20 | // EdgeSubject holds the string denoting the subject edge name in mutations. 21 | EdgeSubject = "subject" 22 | // Table holds the table name of the subjectdigest in the database. 23 | Table = "subject_digests" 24 | // SubjectTable is the table that holds the subject relation/edge. 25 | SubjectTable = "subject_digests" 26 | // SubjectInverseTable is the table name for the Subject entity. 27 | // It exists in this package in order to avoid circular dependency with the "subject" package. 28 | SubjectInverseTable = "subjects" 29 | // SubjectColumn is the table column denoting the subject relation/edge. 30 | SubjectColumn = "subject_subject_digests" 31 | ) 32 | 33 | // Columns holds all SQL columns for subjectdigest fields. 34 | var Columns = []string{ 35 | FieldID, 36 | FieldAlgorithm, 37 | FieldValue, 38 | } 39 | 40 | // ForeignKeys holds the SQL foreign-keys that are owned by the "subject_digests" 41 | // table and are not defined as standalone fields in the schema. 42 | var ForeignKeys = []string{ 43 | "subject_subject_digests", 44 | } 45 | 46 | // ValidColumn reports if the column name is valid (part of the table columns). 47 | func ValidColumn(column string) bool { 48 | for i := range Columns { 49 | if column == Columns[i] { 50 | return true 51 | } 52 | } 53 | for i := range ForeignKeys { 54 | if column == ForeignKeys[i] { 55 | return true 56 | } 57 | } 58 | return false 59 | } 60 | 61 | var ( 62 | // AlgorithmValidator is a validator for the "algorithm" field. It is called by the builders before save. 63 | AlgorithmValidator func(string) error 64 | // ValueValidator is a validator for the "value" field. It is called by the builders before save. 65 | ValueValidator func(string) error 66 | // DefaultID holds the default value on creation for the "id" field. 67 | DefaultID func() uuid.UUID 68 | ) 69 | 70 | // OrderOption defines the ordering options for the SubjectDigest queries. 71 | type OrderOption func(*sql.Selector) 72 | 73 | // ByID orders the results by the id field. 74 | func ByID(opts ...sql.OrderTermOption) OrderOption { 75 | return sql.OrderByField(FieldID, opts...).ToFunc() 76 | } 77 | 78 | // ByAlgorithm orders the results by the algorithm field. 79 | func ByAlgorithm(opts ...sql.OrderTermOption) OrderOption { 80 | return sql.OrderByField(FieldAlgorithm, opts...).ToFunc() 81 | } 82 | 83 | // ByValue orders the results by the value field. 84 | func ByValue(opts ...sql.OrderTermOption) OrderOption { 85 | return sql.OrderByField(FieldValue, opts...).ToFunc() 86 | } 87 | 88 | // BySubjectField orders the results by subject field. 89 | func BySubjectField(field string, opts ...sql.OrderTermOption) OrderOption { 90 | return func(s *sql.Selector) { 91 | sqlgraph.OrderByNeighborTerms(s, newSubjectStep(), sql.OrderByField(field, opts...)) 92 | } 93 | } 94 | func newSubjectStep() *sqlgraph.Step { 95 | return sqlgraph.NewStep( 96 | sqlgraph.From(Table, FieldID), 97 | sqlgraph.To(SubjectInverseTable, FieldID), 98 | sqlgraph.Edge(sqlgraph.M2O, true, SubjectTable, SubjectColumn), 99 | ) 100 | } 101 | -------------------------------------------------------------------------------- /pkg/api/download.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package api 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "encoding/json" 21 | "errors" 22 | "io" 23 | "net/http" 24 | "net/url" 25 | 26 | "github.com/in-toto/go-witness/dsse" 27 | ) 28 | 29 | func DownloadReadCloser(ctx context.Context, baseURL string, gitoid string, requestOptions ...RequestOption) (io.ReadCloser, error) { 30 | return DownloadReadCloserWithHTTPClient(ctx, &http.Client{}, baseURL, gitoid, requestOptions...) 31 | } 32 | 33 | func DownloadReadCloserWithHTTPClient(ctx context.Context, client *http.Client, baseURL string, gitoid string, requestOptions ...RequestOption) (io.ReadCloser, error) { 34 | downloadURL, err := url.JoinPath(baseURL, "download", gitoid) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | req = applyRequestOptions(req, requestOptions...) 45 | req.Header.Set("Content-Type", "application/json") 46 | resp, err := client.Do(req) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | if resp.StatusCode != http.StatusOK { 52 | // NOTE: attempt to read body on error and 53 | // only close if an error occurs 54 | defer resp.Body.Close() 55 | errMsg, err := io.ReadAll(resp.Body) 56 | if err != nil { 57 | return nil, err 58 | } 59 | return nil, errors.New(string(errMsg)) 60 | } 61 | return resp.Body, nil 62 | } 63 | 64 | func Download(ctx context.Context, baseURL string, gitoid string, requestOptions ...RequestOption) (dsse.Envelope, error) { 65 | buf := &bytes.Buffer{} 66 | if err := DownloadWithWriter(ctx, baseURL, gitoid, buf, requestOptions...); err != nil { 67 | return dsse.Envelope{}, err 68 | } 69 | 70 | env := dsse.Envelope{} 71 | dec := json.NewDecoder(buf) 72 | if err := dec.Decode(&env); err != nil { 73 | return env, err 74 | } 75 | 76 | return env, nil 77 | } 78 | 79 | func DownloadWithWriter(ctx context.Context, baseURL string, gitoid string, dst io.Writer, requestOptions ...RequestOption) error { 80 | return DownloadWithWriterWithHTTPClient(ctx, &http.Client{}, baseURL, gitoid, dst, requestOptions...) 81 | } 82 | 83 | func DownloadWithWriterWithHTTPClient(ctx context.Context, client *http.Client, baseURL string, gitoid string, dst io.Writer, requestOptions ...RequestOption) error { 84 | downloadUrl, err := url.JoinPath(baseURL, "download", gitoid) 85 | if err != nil { 86 | return err 87 | } 88 | 89 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadUrl, nil) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | req = applyRequestOptions(req, requestOptions...) 95 | req.Header.Set("Content-Type", "application/json") 96 | hc := &http.Client{} 97 | resp, err := hc.Do(req) 98 | if err != nil { 99 | return err 100 | } 101 | 102 | defer resp.Body.Close() 103 | if resp.StatusCode != http.StatusOK { 104 | errMsg, err := io.ReadAll(resp.Body) 105 | if err != nil { 106 | return err 107 | } 108 | 109 | return errors.New(string(errMsg)) 110 | } 111 | 112 | _, err = io.Copy(dst, resp.Body) 113 | return err 114 | } 115 | -------------------------------------------------------------------------------- /pkg/publisherstore/rstuf/rstuf.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package rstuf 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "crypto/sha256" 21 | "encoding/hex" 22 | "encoding/json" 23 | "fmt" 24 | "io" 25 | "net/http" 26 | "net/http/httputil" 27 | 28 | "github.com/in-toto/archivista/pkg/config" 29 | "github.com/sirupsen/logrus" 30 | ) 31 | 32 | type RSTUF struct { 33 | Host string 34 | } 35 | 36 | type Publisher interface { 37 | Publish(ctx context.Context, gitoid string, payload []byte) error 38 | } 39 | 40 | func (r *RSTUF) parseRSTUFPayload(gitoid string, payload []byte) ([]byte, error) { 41 | objHash := sha256.Sum256(payload) 42 | // custom := make(map[string]any) 43 | // custom["gitoid"] = gitoid 44 | artifacts := []Artifact{ 45 | { 46 | Path: gitoid, 47 | Info: ArtifactInfo{ 48 | Length: len(payload), 49 | Hashes: Hashes{ 50 | Sha256: hex.EncodeToString(objHash[:]), 51 | }, 52 | // Custom: custom, 53 | }, 54 | }, 55 | } 56 | 57 | artifactPayload := ArtifactPayload{ 58 | Artifacts: artifacts, 59 | AddTaskIDToCustom: false, 60 | PublishTargets: true, 61 | } 62 | 63 | payloadBytes, err := json.Marshal(artifactPayload) 64 | if err != nil { 65 | return nil, fmt.Errorf("error marshaling payload: %v", err) 66 | } 67 | return payloadBytes, nil 68 | } 69 | 70 | func (r *RSTUF) Publish(ctx context.Context, gitoid string, payload []byte) error { 71 | // this publisher allows integration with the RSTUF project to store 72 | // the attestation and policy in the TUF metadata. 73 | // this TUF metadata can be used to build truste when distributing the 74 | // attestations and policies. 75 | // Convert payload to JSON 76 | url := r.Host + "/api/v1/artifacts" 77 | 78 | payloadBytes, err := r.parseRSTUFPayload(gitoid, payload) 79 | if err != nil { 80 | return fmt.Errorf("error parsing payload: %v", err) 81 | } 82 | 83 | req, err := http.NewRequest("POST", url, bytes.NewBuffer(payloadBytes)) 84 | if err != nil { 85 | return fmt.Errorf("error creating request: %v", err) 86 | } 87 | 88 | req.Header.Set("Content-Type", "application/json") 89 | // Add any additional headers or authentication if needed 90 | 91 | client := &http.Client{} 92 | resp, err := client.Do(req) 93 | if err != nil { 94 | logrus.Errorf("error making request: %v", err) 95 | } 96 | defer resp.Body.Close() 97 | 98 | if resp.StatusCode != http.StatusAccepted { 99 | logb, _ := httputil.DumpResponse(resp, true) 100 | logrus.Errorf("error body from RSTUF: %v", string(logb)) 101 | return fmt.Errorf("error response from RSTUF: %v", err) 102 | } 103 | 104 | // Handle the response as needed 105 | body, err := io.ReadAll(resp.Body) 106 | if err != nil { 107 | logrus.Errorf("error reading response body: %v", err) 108 | } 109 | 110 | response := Response{} 111 | err = json.Unmarshal(body, &response) 112 | if err != nil { 113 | logrus.Errorf("error unmarshaling response: %v", err) 114 | } 115 | logrus.Debugf("RSTUF task id: %v", response.Data.TaskId) 116 | // TODO: monitor RSTUF task id for completion 117 | return nil 118 | } 119 | 120 | func NewPublisher(config *config.Config) Publisher { 121 | return &RSTUF{ 122 | Host: config.PublisherRstufHost, 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /pkg/api/graphql.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package api 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "encoding/json" 21 | "errors" 22 | "fmt" 23 | "io" 24 | "net/http" 25 | "net/url" 26 | ) 27 | 28 | const RetrieveSubjectsQuery = `query($gitoid: String!) { 29 | subjects( 30 | where: { 31 | hasStatementWith:{ 32 | hasDsseWith:{ 33 | gitoidSha256: $gitoid 34 | } 35 | } 36 | } 37 | ) { 38 | edges { 39 | node{ 40 | name 41 | subjectDigests{ 42 | algorithm 43 | value 44 | } 45 | } 46 | } 47 | } 48 | }` 49 | 50 | const SearchQuery = `query($algo: String!, $digest: String!) { 51 | dsses( 52 | where: { 53 | hasStatementWith: { 54 | hasSubjectsWith: { 55 | hasSubjectDigestsWith: { 56 | value: $digest, 57 | algorithm: $algo 58 | } 59 | } 60 | } 61 | } 62 | ) { 63 | edges { 64 | node { 65 | gitoidSha256 66 | statement { 67 | attestationCollections { 68 | name 69 | attestations { 70 | type 71 | } 72 | } 73 | } 74 | } 75 | } 76 | } 77 | }` 78 | 79 | func GraphQlQuery[TRes any, TVars any](ctx context.Context, baseUrl, query string, vars TVars, requestOptions ...RequestOption) (TRes, error) { 80 | var response TRes 81 | queryUrl, err := url.JoinPath(baseUrl, "query") 82 | if err != nil { 83 | return response, err 84 | } 85 | 86 | requestBody := GraphQLRequestBodyGeneric[TVars]{ 87 | Query: query, 88 | Variables: vars, 89 | } 90 | 91 | reqBody, err := json.Marshal(requestBody) 92 | if err != nil { 93 | return response, err 94 | } 95 | 96 | req, err := http.NewRequestWithContext(ctx, http.MethodPost, queryUrl, bytes.NewReader(reqBody)) 97 | if err != nil { 98 | return response, err 99 | } 100 | 101 | req = applyRequestOptions(req, requestOptions...) 102 | req.Header.Set("Content-Type", "application/json") 103 | hc := &http.Client{} 104 | res, err := hc.Do(req) 105 | if err != nil { 106 | return response, err 107 | } 108 | 109 | defer res.Body.Close() 110 | if res.StatusCode != http.StatusOK { 111 | errMsg, err := io.ReadAll(res.Body) 112 | if err != nil { 113 | return response, err 114 | } 115 | 116 | return response, errors.New(string(errMsg)) 117 | } 118 | 119 | dec := json.NewDecoder(res.Body) 120 | gqlRes := GraphQLResponseGeneric[TRes]{} 121 | if err := dec.Decode(&gqlRes); err != nil { 122 | return response, err 123 | } 124 | 125 | if len(gqlRes.Errors) > 0 { 126 | return response, fmt.Errorf("graph ql query failed: %v", gqlRes.Errors) 127 | } 128 | 129 | return gqlRes.Data, nil 130 | } 131 | 132 | // Deprecated: Use GraphQlQuery with the WithHeaders RequestOption 133 | func GraphQlQueryWithHeaders[TRes any, TVars any](ctx context.Context, baseUrl, query string, vars TVars, headers map[string]string, requestOptions ...RequestOption) (TRes, error) { 134 | h := http.Header{} 135 | for k, v := range headers { 136 | h.Set(k, v) 137 | } 138 | 139 | requestOptions = append(requestOptions, WithHeaders(h)) 140 | return GraphQlQuery[TRes](ctx, baseUrl, query, vars, requestOptions...) 141 | } 142 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # For most projects, this workflow file will not need changing; you simply need 16 | # to commit it to your repository. 17 | # 18 | # You may wish to alter this file to override the set of languages analyzed, 19 | # or to provide custom queries or build logic. 20 | # 21 | # ******** NOTE ******** 22 | # We have attempted to detect the languages in your repository. Please check 23 | # the `language` matrix defined below to confirm you have the correct set of 24 | # supported CodeQL languages. 25 | # 26 | name: "CodeQL" 27 | 28 | on: 29 | push: 30 | branches: ["main"] 31 | pull_request: 32 | # The branches below must be a subset of the branches above 33 | branches: ["main"] 34 | schedule: 35 | - cron: "0 0 * * 1" 36 | 37 | permissions: 38 | contents: read 39 | 40 | jobs: 41 | analyze: 42 | name: Analyze 43 | runs-on: ubuntu-latest 44 | permissions: 45 | actions: read 46 | contents: read 47 | security-events: write 48 | 49 | strategy: 50 | fail-fast: false 51 | matrix: 52 | language: ["go"] 53 | # CodeQL supports [ $supported-codeql-languages ] 54 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 55 | 56 | steps: 57 | - name: Harden Runner 58 | uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 59 | with: 60 | egress-policy: audit 61 | 62 | - name: Checkout repository 63 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 64 | 65 | - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 66 | with: 67 | go-version-file: "go.mod" 68 | 69 | # Initializes the CodeQL tools for scanning. 70 | - name: Initialize CodeQL 71 | uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v3.29.5 72 | with: 73 | languages: ${{ matrix.language }} 74 | # If you wish to specify custom queries, you can do so here or in a config file. 75 | # By default, queries listed here will override any specified in a config file. 76 | # Prefix the list here with "+" to use these queries and those in the config file. 77 | 78 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 79 | # If this step fails, then you should remove it and run the build manually (see below) 80 | - name: Autobuild 81 | uses: github/codeql-action/autobuild@1b168cd39490f61582a9beae412bb7057a6b2c4e # v3.29.5 82 | 83 | # ℹ️ Command-line programs to run using the OS shell. 84 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 85 | 86 | # If the Autobuild fails above, remove it and uncomment the following three lines. 87 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 88 | 89 | # - run: | 90 | # echo "Run, Build Application using script" 91 | # ./location_of_script_within_repo/buildscript.sh 92 | 93 | - name: Perform CodeQL Analysis 94 | uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v3.29.5 95 | with: 96 | category: "/language:${{matrix.language}}" 97 | -------------------------------------------------------------------------------- /.github/workflows/scorecards.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # This workflow uses actions that are not certified by GitHub. They are provided 16 | # by a third-party and are governed by separate terms of service, privacy 17 | # policy, and support documentation. 18 | 19 | name: Scorecard supply-chain security 20 | on: 21 | # For Branch-Protection check. Only the default branch is supported. See 22 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 23 | branch_protection_rule: 24 | # To guarantee Maintained check is occasionally updated. See 25 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 26 | schedule: 27 | - cron: '20 7 * * 2' 28 | push: 29 | branches: ["main"] 30 | 31 | # Declare default permissions as read only. 32 | permissions: read-all 33 | 34 | jobs: 35 | analysis: 36 | name: Scorecard analysis 37 | runs-on: ubuntu-latest 38 | permissions: 39 | # Needed to upload the results to code-scanning dashboard. 40 | security-events: write 41 | # Needed to publish results and get a badge (see publish_results below). 42 | id-token: write 43 | contents: read 44 | actions: read 45 | 46 | steps: 47 | - name: Harden Runner 48 | uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 49 | with: 50 | egress-policy: audit 51 | 52 | - name: "Checkout code" 53 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 54 | with: 55 | persist-credentials: false 56 | 57 | - name: "Run analysis" 58 | uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 59 | with: 60 | results_file: results.sarif 61 | results_format: sarif 62 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 63 | # - you want to enable the Branch-Protection check on a *public* repository, or 64 | # - you are installing Scorecards on a *private* repository 65 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 66 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 67 | 68 | # Public repositories: 69 | # - Publish results to OpenSSF REST API for easy access by consumers 70 | # - Allows the repository to include the Scorecard badge. 71 | # - See https://github.com/ossf/scorecard-action#publishing-results. 72 | # For private repositories: 73 | # - `publish_results` will always be set to `false`, regardless 74 | # of the value entered here. 75 | publish_results: true 76 | 77 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 78 | # format to the repository Actions tab. 79 | - name: "Upload artifact" 80 | uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 81 | with: 82 | name: SARIF file 83 | path: results.sarif 84 | retention-days: 5 85 | 86 | # Upload the results to GitHub's code scanning dashboard. 87 | - name: "Upload to code-scanning" 88 | uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v3.29.5 89 | with: 90 | sarif_file: results.sarif 91 | -------------------------------------------------------------------------------- /cmd/archivistactl/cmd/retrieve.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "os" 21 | "strings" 22 | 23 | "github.com/in-toto/archivista/pkg/api" 24 | "github.com/spf13/cobra" 25 | ) 26 | 27 | var ( 28 | outFile string 29 | 30 | retrieveCmd = &cobra.Command{ 31 | Use: "retrieve", 32 | Short: "Retrieve information from an archivista server", 33 | SilenceUsage: true, 34 | } 35 | 36 | envelopeCmd = &cobra.Command{ 37 | Use: "envelope", 38 | Short: "Retrieves a dsse envelope by it's gitoid from archivista", 39 | SilenceUsage: true, 40 | Args: cobra.ExactArgs(1), 41 | RunE: func(cmd *cobra.Command, args []string) error { 42 | var out io.Writer = os.Stdout 43 | if len(outFile) > 0 { 44 | file, err := os.Create(outFile) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | defer file.Close() 50 | out = file 51 | } 52 | 53 | return api.DownloadWithWriter(cmd.Context(), archivistaUrl, args[0], out, requestOptions()...) 54 | }, 55 | } 56 | 57 | subjectCmd = &cobra.Command{ 58 | Use: "subjects", 59 | Short: "Retrieves all subjects on an in-toto statement by the envelope gitoid", 60 | SilenceUsage: true, 61 | Args: cobra.ExactArgs(1), 62 | RunE: func(cmd *cobra.Command, args []string) error { 63 | results, err := api.GraphQlQuery[retrieveSubjectResults](cmd.Context(), archivistaUrl, retrieveSubjectsQuery, retrieveSubjectVars{Gitoid: args[0]}, requestOptions()...) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | printSubjects(results) 69 | return nil 70 | }, 71 | } 72 | ) 73 | 74 | func init() { 75 | rootCmd.AddCommand(retrieveCmd) 76 | retrieveCmd.AddCommand(envelopeCmd) 77 | retrieveCmd.AddCommand(subjectCmd) 78 | envelopeCmd.Flags().StringVarP(&outFile, "out", "o", "", "File to write the envelope out to. Defaults to stdout") 79 | } 80 | 81 | func printSubjects(results retrieveSubjectResults) { 82 | for _, edge := range results.Subjects.Edges { 83 | digestStrings := make([]string, 0, len(edge.Node.SubjectDigests)) 84 | for _, digest := range edge.Node.SubjectDigests { 85 | digestStrings = append(digestStrings, fmt.Sprintf("%s:%s", digest.Algorithm, digest.Value)) 86 | } 87 | 88 | rootCmd.Printf("Name: %s\nDigests: %s\n", edge.Node.Name, strings.Join(digestStrings, ", ")) 89 | } 90 | } 91 | 92 | type retrieveSubjectVars struct { 93 | Gitoid string `json:"gitoid"` 94 | } 95 | 96 | type retrieveSubjectResults struct { 97 | Subjects struct { 98 | Edges []struct { 99 | Node struct { 100 | Name string `json:"name"` 101 | SubjectDigests []struct { 102 | Algorithm string `json:"algorithm"` 103 | Value string `json:"value"` 104 | } `json:"subjectDigests"` 105 | } `json:"node"` 106 | } `json:"edges"` 107 | } `json:"subjects"` 108 | } 109 | 110 | const retrieveSubjectsQuery = `query($gitoid: String!) { 111 | subjects( 112 | where: { 113 | hasStatementWith:{ 114 | hasDsseWith:{ 115 | gitoidSha256: $gitoid 116 | } 117 | } 118 | } 119 | ) { 120 | edges { 121 | node{ 122 | name 123 | subjectDigests{ 124 | algorithm 125 | value 126 | } 127 | } 128 | } 129 | } 130 | }` 131 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Archivista Contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | project_name: archivista 16 | builds: 17 | - ldflags: 18 | - "-s -w" 19 | - "-extldflags=-zrelro" 20 | - "-extldflags=-znow" 21 | - "-extldflags -w -X 'github.com/in-toto/archivista/cmd.Version={{.Tag}}-{{.ShortCommit}}'" 22 | env: 23 | - "CGO_ENABLED=0" 24 | - "GO111MODULE=on" 25 | - "GOFLAGS=-mod=readonly -trimpath" 26 | goos: 27 | - linux 28 | - windows 29 | - darwin 30 | goarch: 31 | - amd64 32 | - arm64 33 | main: ./cmd/archivista 34 | binary: archivista 35 | id: archivista 36 | - ldflags: 37 | - "-s -w" 38 | - "-extldflags=-zrelro" 39 | - "-extldflags=-znow" 40 | - "-extldflags -w -X 'github.com/in-toto/archivistactl/cmd.Version={{.Tag}}-{{.ShortCommit}}'" 41 | env: 42 | - "CGO_ENABLED=0" 43 | - "GO111MODULE=on" 44 | - "GOFLAGS=-mod=readonly -trimpath" 45 | goos: 46 | - linux 47 | - windows 48 | - darwin 49 | goarch: 50 | - amd64 51 | - arm64 52 | main: ./cmd/archivistactl 53 | binary: archivistactl 54 | id: archivistactl 55 | gomod: 56 | proxy: false 57 | source: 58 | enabled: true 59 | changelog: 60 | use: github 61 | groups: 62 | - title: Features 63 | regexp: "^.*feat[(\\w)]*:+.*$" 64 | order: 0 65 | - title: 'Bug fixes' 66 | regexp: "^.*fix[(\\w)]*:+.*$" 67 | order: 1 68 | - title: 'Documentation' 69 | regexp: "^.*docs[(\\w)]*:+.*$" 70 | order: 2 71 | - title: Others 72 | order: 999 73 | release: 74 | prerelease: auto 75 | github: 76 | owner: "{{ .Env.GITHUB_REPOSITORY_OWNER }}" 77 | dockers: 78 | - image_templates: 79 | - "ghcr.io/in-toto/archivista:{{ .Version }}-amd64" 80 | use: buildx 81 | build_flag_templates: 82 | - "--pull" 83 | - "--platform=linux/amd64" 84 | extra_files: 85 | - "archivista.graphql" 86 | - "ent.graphql" 87 | - "ent.resolvers.go" 88 | - "entrypoint.sh" 89 | - "gen.go" 90 | - "generated.go" 91 | - "go.mod" 92 | - "go.sum" 93 | - "resolver.go" 94 | - "docs" 95 | - "ent" 96 | - "cmd" 97 | - "ent" 98 | - "pkg" 99 | - image_templates: 100 | - "ghcr.io/in-toto/archivista:{{ .Version }}-arm64" 101 | use: buildx 102 | build_flag_templates: 103 | - "--pull" 104 | - "--platform=linux/arm64" 105 | extra_files: 106 | - "archivista.graphql" 107 | - "ent.graphql" 108 | - "ent.resolvers.go" 109 | - "entrypoint.sh" 110 | - "gen.go" 111 | - "generated.go" 112 | - "go.mod" 113 | - "go.sum" 114 | - "resolver.go" 115 | - "docs" 116 | - "ent" 117 | - "cmd" 118 | - "ent" 119 | - "pkg" 120 | goarch: arm64 121 | docker_manifests: 122 | - name_template: "ghcr.io/in-toto/archivista:{{ .Version }}" 123 | image_templates: 124 | - "ghcr.io/in-toto/archivista:{{ .Version }}-amd64" 125 | - "ghcr.io/in-toto/archivista:{{ .Version }}-arm64" 126 | kos: 127 | - repository: ghcr.io/in-toto/archivistactl 128 | id: archivistactl 129 | build: archivistactl 130 | tags: 131 | - '{{.Version}}' 132 | bare: true 133 | preserve_import_paths: false 134 | creation_time: '{{.CommitTimestamp}}' 135 | platforms: 136 | - linux/amd64 137 | - linux/arm64 138 | -------------------------------------------------------------------------------- /cmd/archivista/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2024 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // A note: this follows a pattern followed by network service mesh. 16 | // The pattern was copied from the Network Service Mesh Project 17 | // and modified for use here. The original code was published under the 18 | // Apache License V2. 19 | 20 | package main 21 | 22 | import ( 23 | "context" 24 | "net" 25 | "net/http" 26 | "os" 27 | "os/signal" 28 | "strings" 29 | "syscall" 30 | "time" 31 | 32 | nested "github.com/antonfisher/nested-logrus-formatter" 33 | "github.com/gorilla/handlers" 34 | "github.com/in-toto/archivista/pkg/server" 35 | "github.com/sirupsen/logrus" 36 | ) 37 | 38 | func init() { 39 | logrus.SetFormatter(&nested.Formatter{}) 40 | } 41 | 42 | func main() { 43 | ctx, cancel := signal.NotifyContext( 44 | context.Background(), 45 | os.Interrupt, 46 | syscall.SIGHUP, 47 | syscall.SIGQUIT, 48 | syscall.SIGTERM, 49 | ) 50 | defer cancel() 51 | 52 | startTime := time.Now() 53 | 54 | archivistaService := &server.ArchivistaService{Ctx: ctx, Cfg: nil} 55 | 56 | server, err := archivistaService.Setup() 57 | if err != nil { 58 | logrus.Fatalf("unable to setup archivista service: %+v", err) 59 | } 60 | // ******************************************************************************** 61 | logrus.Infof("executing phase: create and register http service (time since start: %s)", time.Since(startTime)) 62 | // ******************************************************************************** 63 | now := time.Now() 64 | 65 | listenAddress := archivistaService.Cfg.ListenOn 66 | listenAddress = strings.ToLower(strings.TrimSpace(listenAddress)) 67 | proto := "" 68 | if strings.HasPrefix(listenAddress, "tcp://") { 69 | proto = "tcp" 70 | listenAddress = listenAddress[len("tcp://"):] 71 | } else if strings.HasPrefix(listenAddress, "unix://") { 72 | proto = "unix" 73 | listenAddress = listenAddress[len("unix://"):] 74 | } 75 | 76 | listener, err := net.Listen(proto, listenAddress) 77 | if err != nil { 78 | logrus.Fatalf("unable to start http listener: %+v", err) 79 | } 80 | srv := &http.Server{ 81 | Handler: handlers.CORS( 82 | handlers.AllowedOrigins(archivistaService.Cfg.CORSAllowOrigins), 83 | handlers.AllowedMethods([]string{"GET", "POST", "OPTIONS"}), 84 | handlers.AllowedHeaders([]string{"Accept", "Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization"}), 85 | )(server.Router()), 86 | ReadTimeout: time.Duration(archivistaService.Cfg.ReadTimeout) * time.Second, 87 | WriteTimeout: time.Duration(archivistaService.Cfg.WriteTimeout) * time.Second, 88 | } 89 | 90 | go func() { 91 | if archivistaService.Cfg.EnableTLS { 92 | if err := srv.ServeTLS(listener, archivistaService.Cfg.TLSCert, archivistaService.Cfg.TLSKey); err != nil { 93 | logrus.Fatalf("unable to start http server: %+v", err) 94 | } 95 | } else { 96 | if err := srv.Serve(listener); err != nil { 97 | logrus.Fatalf("unable to start http server: %+v", err) 98 | } 99 | } 100 | }() 101 | 102 | logrus.WithField("duration", time.Since(now)).Infof("completed phase: create and register http service") 103 | logrus.Infof("startup complete (time since start: %s)", time.Since(startTime)) 104 | 105 | <-ctx.Done() 106 | <-archivistaService.GetFileStoreCh() 107 | <-archivistaService.GetSQLStoreCh() 108 | 109 | logrus.Infof("exiting, uptime: %v", time.Since(startTime)) 110 | } 111 | -------------------------------------------------------------------------------- /cmd/archivistactl/cmd/search.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Archivista Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | "strings" 20 | 21 | "github.com/in-toto/archivista/pkg/api" 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | var searchCmd = &cobra.Command{ 26 | Use: "search", 27 | Short: "Searches the archivista instance for an attestation matching a query", 28 | SilenceUsage: true, 29 | Long: `Searches the archivista instance for an envelope with a specified subject digest. 30 | Optionally a collection name can be provided to further constrain results. 31 | 32 | Digests are expected to be in the form algorithm:digest, for instance: sha256:456c0c9a7c05e2a7f84c139bbacedbe3e8e88f9c`, 33 | Args: func(cmd *cobra.Command, args []string) error { 34 | if len(args) != 1 { 35 | return errors.New("expected exactly 1 argument") 36 | } 37 | 38 | if _, _, err := validateDigestString(args[0]); err != nil { 39 | return err 40 | } 41 | 42 | return nil 43 | }, 44 | RunE: func(cmd *cobra.Command, args []string) error { 45 | algo, digest, err := validateDigestString(args[0]) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | results, err := api.GraphQlQuery[searchResults](cmd.Context(), archivistaUrl, searchQuery, searchVars{Algorithm: algo, Digest: digest}, requestOptions()...) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | printResults(results) 56 | return nil 57 | }, 58 | } 59 | 60 | func init() { 61 | rootCmd.AddCommand(searchCmd) 62 | } 63 | 64 | func validateDigestString(ds string) (algo, digest string, err error) { 65 | algo, digest, found := strings.Cut(ds, ":") 66 | if !found { 67 | return "", "", errors.New("invalid digest string. expected algorithm:digest") 68 | } 69 | 70 | return algo, digest, nil 71 | } 72 | 73 | func printResults(results searchResults) { 74 | for _, edge := range results.Dsses.Edges { 75 | rootCmd.Printf("Gitoid: %s\n", edge.Node.GitoidSha256) 76 | rootCmd.Printf("Collection name: %s\n", edge.Node.Statement.AttestationCollection.Name) 77 | types := make([]string, 0, len(edge.Node.Statement.AttestationCollection.Attestations)) 78 | for _, attestation := range edge.Node.Statement.AttestationCollection.Attestations { 79 | types = append(types, attestation.Type) 80 | } 81 | 82 | rootCmd.Printf("Attestations: %s\n\n", strings.Join(types, ", ")) 83 | } 84 | } 85 | 86 | type searchVars struct { 87 | Algorithm string `json:"algo"` 88 | Digest string `json:"digest"` 89 | } 90 | 91 | type searchResults struct { 92 | Dsses struct { 93 | Edges []struct { 94 | Node struct { 95 | GitoidSha256 string `json:"gitoidSha256"` 96 | Statement struct { 97 | AttestationCollection struct { 98 | Name string `json:"name"` 99 | Attestations []struct { 100 | Type string `json:"type"` 101 | } `json:"attestations"` 102 | } `json:"attestationCollections"` 103 | } `json:"statement"` 104 | } `json:"node"` 105 | } `json:"edges"` 106 | } `json:"dsses"` 107 | } 108 | 109 | const searchQuery = `query($algo: String!, $digest: String!) { 110 | dsses( 111 | where: { 112 | hasStatementWith: { 113 | hasSubjectsWith: { 114 | hasSubjectDigestsWith: { 115 | value: $digest, 116 | algorithm: $algo 117 | } 118 | } 119 | } 120 | } 121 | ) { 122 | edges { 123 | node { 124 | gitoidSha256 125 | statement { 126 | attestationCollections { 127 | name 128 | attestations { 129 | type 130 | } 131 | } 132 | } 133 | } 134 | } 135 | } 136 | }` 137 | --------------------------------------------------------------------------------