From ecbd8641c2feb9729532867ded4ac7b47f123fdc Mon Sep 17 00:00:00 2001 From: Prasad Tengse <10941447+tprasadtp@users.noreply.github.com> Date: Fri, 19 Apr 2024 00:18:36 +0200 Subject: [PATCH] feat(ci): provide SLSA L3 builds (#307) --- .dockerignore | 9 + .editorconfig | 4 + .github/ISSUE_TEMPLATE/bugreport.yml | 2 +- .github/dependabot.yml | 24 +- .github/workflows/build.yml | 50 +- .github/workflows/docs.yml | 18 - .github/workflows/metadata-gh-pages.yml | 63 --- .github/workflows/metadata.yml | 33 +- .github/workflows/release.yml | 193 ++++++- .golangci.yml | 530 +++++++++++++++++++ .goreleaser.yml | 164 +----- Dockerfile | 2 +- Makefile | 19 +- README.md | 10 +- Taskfile.yml | 309 +++++++++++ docs/cosign.md | 17 + docs/examples/podman/protonwire.container | 2 +- docs/faq.md | 36 +- docs/images/slsa-level3-logo.svg | 47 ++ docs/slsa.md | 41 ++ go.mod | 14 +- go.sum | 15 + internal/tasks/main.go | 131 +++++ protonwire | 2 +- scripts/{goreleaser-wrapper => build-script} | 8 + scripts/bump-patch-version | 52 -- scripts/generate-server-metadata | 37 +- 27 files changed, 1462 insertions(+), 370 deletions(-) create mode 100644 .dockerignore delete mode 100644 .github/workflows/docs.yml delete mode 100644 .github/workflows/metadata-gh-pages.yml create mode 100644 .golangci.yml create mode 100644 Taskfile.yml create mode 100644 docs/cosign.md create mode 100644 docs/images/slsa-level3-logo.svg create mode 100644 docs/slsa.md create mode 100644 internal/tasks/main.go rename scripts/{goreleaser-wrapper => build-script} (90%) delete mode 100755 scripts/bump-patch-version diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2bd7525 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +build/ +dist/ +.git/ +metadata/ +private.key +private-key.pem +wireguard.key +protonwire.key +protonvpn.key diff --git a/.editorconfig b/.editorconfig index fa5b63f..ceb22b0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -34,3 +34,7 @@ insert_final_newline = false [Vagrantfile] indent_size = 2 + +[metadata/**/*] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/.github/ISSUE_TEMPLATE/bugreport.yml b/.github/ISSUE_TEMPLATE/bugreport.yml index d4fee01..f32de04 100644 --- a/.github/ISSUE_TEMPLATE/bugreport.yml +++ b/.github/ISSUE_TEMPLATE/bugreport.yml @@ -27,7 +27,7 @@ body: required: true - label: I have verified that my generated Wireguard private keys are valid and have required features (Netshield Ad-blocker, VPN accelerator etc) are enabled. required: true - - label: I am using a valid server name (either fully qualified DNS name like `nl-free-127.protonvpn.net` or server name like `NL#1`) as mentioned in the docs. + - label: I am using a valid server name (either fully qualified DNS name like `nl-free-127.protonvpn.net` or server IP) as mentioned in the docs. required: true - type: input diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 08891d8..d09263a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -14,14 +14,13 @@ updates: pull-request-branch-name: separator: "-" - - package-ecosystem: docker + - package-ecosystem: github-actions labels: - "dependabot" - - "dep/docker" + - "dep/github-actions" - "luna/autoupdate" - target-branch: release/7.2 commit-message: - prefix: "chore(deps):" + prefix: "ci(deps):" directory: "/" schedule: interval: "weekly" @@ -29,16 +28,19 @@ updates: pull-request-branch-name: separator: "-" - - package-ecosystem: github-actions + # For go, Only builders use the deps it is note a runtime + # dependency, so only apply security updates. + - package-ecosystem: gomod + directory: / labels: - - "dependabot" - - "dep/github-actions" - - "luna/autoupdate" - commit-message: - prefix: "ci(deps):" - directory: "/" + - "bot/dependabot" + - "deps/go" schedule: interval: "weekly" day: "saturday" + commit-message: + prefix: "deps(go):" pull-request-branch-name: separator: "-" + # Disable version updates and only apply security updates. + open-pull-requests-limit: 0 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3798f28..b536482 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,36 +1,66 @@ name: build on: push: + branches: + - "**" + tags-ignore: + - "**" pull_request: workflow_dispatch: + +permissions: {} + jobs: - shellcheck: + lint: runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: stable + + - name: Install Task + run: go install github.com/go-task/task/v3/cmd/task@latest + - name: Shellcheck - run: make shellcheck + run: task --verbose shellcheck + + - name: Update README + run: task --verbose update-readme + + - name: Check if README is up-to date + run: git diff --exit-code README.md + build: runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false + - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Install GoReleaser - uses: goreleaser/goreleaser-action@v5 + - name: Setup Go + uses: actions/setup-go@v5 with: - version: latest - install-only: true + go-version: stable + + - name: Install Task + run: go install github.com/go-task/task/v3/cmd/task@latest - - name: Install help2man - run: sudo apt-get install -y help2man + - name: Install crane + run: go install github.com/google/go-containerregistry/cmd/crane@latest - - name: Run GoReleaser - run: make snapshot + - name: Build Images + run: task --verbose build-images diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index a2458b5..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: docs -on: - push: - pull_request: - workflow_dispatch: -jobs: - update-readme: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Update README - run: make update-readme - - - name: Check if README is up-to date - run: git diff --exit-code README.md diff --git a/.github/workflows/metadata-gh-pages.yml b/.github/workflows/metadata-gh-pages.yml deleted file mode 100644 index 9e6ce2b..0000000 --- a/.github/workflows/metadata-gh-pages.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: metadata-gh-pages -on: - workflow_dispatch: - inputs: - DEBUG: - description: Enable debug logs for metadata - required: false - default: "no" - type: choice - options: - - "yes" - - "no" - push: - branches: - - "**" - tags-ignore: - - "**" - schedule: - - cron: "30 * * * *" -jobs: - deploy: - # exclude dependabot from running this workflow. - if: ${{ github.actor != 'dependabot[bot]' }} - runs-on: ubuntu-latest - permissions: - contents: write - pages: write - id-token: write - - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Install python dependencies - run: | - sudo apt-get install -y \ - python3-gnupg \ - python3-requests \ - python3-bcrypt \ - python3-coloredlogs - - - name: Generate metadata - run: ./scripts/generate-server-metadata --output ./metadata - env: - PROTON_USERNAME: ${{ secrets.PROTON_USERNAME }} - PROTON_PASSWORD: ${{ secrets.PROTON_PASSWORD }} - DEBUG: ${{ inputs.DEBUG }} - - - name: Upload GitHub Pages artifact - uses: actions/upload-pages-artifact@v3 - with: - path: metadata - - - name: Deploy to GitHub Pages (if on master) - if: github.ref == 'refs/heads/master' - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/metadata.yml b/.github/workflows/metadata.yml index dcfdc2d..1009a48 100644 --- a/.github/workflows/metadata.yml +++ b/.github/workflows/metadata.yml @@ -11,17 +11,26 @@ on: - "yes" - "no" push: - branches: - - "**" + branches-ignore: + - "feature/**" + - "feature*" + - "dev/**" + - "dev*" + - "dependabot*" + - "dependabot/**" tags-ignore: - "**" schedule: - cron: "30 * * * *" + +permissions: + contents: read + jobs: metadata: runs-on: ubuntu-latest - # exclude dependabot from running this workflow. - if: ${{ github.actor != 'dependabot[bot]' }} + permissions: + contents: read steps: - name: Checkout project repo uses: actions/checkout@v4 @@ -47,21 +56,21 @@ jobs: uses: actions/create-github-app-token@v1 id: bot-token with: - app-id: ${{ vars.METADATA_BOT_APP_ID }} - repositories: ${{ vars.METADATA_REPO_NAME }} owner: ${{ vars.METADATA_REPO_OWNER }} + app-id: ${{ vars.METADATA_BOT_APP_ID }} private-key: ${{ secrets.METADATA_BOT_APP_PRIVATE_KEY }} + repositories: ${{ vars.METADATA_REPO_NAME }} - - name: Checkout metadata repo to protonwire-api-deploy + - name: Checkout metadata repo to protonwire-api uses: actions/checkout@v4 with: - repository: ${{ vars.METADATA_REPO }} - path: protonwire-api-deploy + path: protonwire-api token: ${{ steps.bot-token.outputs.token }} + repository: ${{ vars.METADATA_REPO }} persist-credentials: true - name: Configure git and remove stale data - working-directory: protonwire-api-deploy + working-directory: protonwire-api run: | git config user.name ${{ vars.METADATA_BOT_GIT_USERNAME }} git config user.email ${{ vars.METADATA_BOT_GIT_EMAIL }} @@ -73,10 +82,10 @@ jobs: --archive \ --human-readable \ ./metadata/ \ - ${GITHUB_WORKSPACE}/protonwire-api-deploy/ + ${GITHUB_WORKSPACE}/protonwire-api/ - name: Push Metadata (if on master) - working-directory: protonwire-api-deploy + working-directory: protonwire-api if: github.ref == 'refs/heads/master' run: | git add --all diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7e22d08..1bb1399 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,22 +1,44 @@ name: release on: - workflow_dispatch: push: tags: - "**" + workflow_dispatch: + +permissions: {} + jobs: - shellcheck: + lint: runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: stable + + - name: Install Task + run: go install github.com/go-task/task/v3/cmd/task@latest + - name: Shellcheck - run: make shellcheck + run: task --verbose shellcheck + + - name: Update README + run: task --verbose update-readme + + - name: Check if README is up-to date + run: git diff --exit-code README.md + build: runs-on: ubuntu-latest permissions: - contents: write + contents: read packages: write - id-token: write + outputs: + manifest-digest: ${{ steps.get-manifest-digest.outputs.digest }} steps: - uses: actions/checkout@v4 with: @@ -29,17 +51,123 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Install GoReleaser - uses: goreleaser/goreleaser-action@v5 + - name: Setup Go + uses: actions/setup-go@v5 with: - version: latest - install-only: true + go-version: stable + + - name: Install Task + run: go install github.com/go-task/task/v3/cmd/task@latest + + - name: Install crane + run: go install github.com/google/go-containerregistry/cmd/crane@latest + + - name: Build Images + run: task --verbose build-images + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push Images + run: task --verbose push-images + + - name: Push Manifests + run: task --verbose push-manifests + + - name: Get Manifest Digest + id: get-manifest-digest + run: | + set -euo pipefail + digest=$(crane digest ghcr.io/tprasadtp/protonwire:${{github.sha}}) + if [[ -z $digest ]]; then + echo "failed to get digest" + exit 1 + fi + echo "digest=${digest}" >> "$GITHUB_OUTPUT" + + sign: + runs-on: ubuntu-latest + needs: + - lint + - build + permissions: + contents: read + actions: read + packages: write + id-token: write + strategy: + fail-fast: false + matrix: + image: + - ghcr.io/tprasadtp/protonwire + - ghcr.io/tprasadtp/protonvpn + steps: + - name: Install Cosign + uses: sigstore/cosign-installer@v3 + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Sign Images + run: >- + cosign sign + --yes + --recursive + --oidc-provider=github-actions + ${{ matrix.image }}@${{ needs.build.outputs.manifest-digest }} + + provenance: + needs: + - lint + - build + permissions: + contents: read + actions: read + id-token: write + packages: write + strategy: + fail-fast: false + matrix: + image: + - ghcr.io/tprasadtp/protonwire + - ghcr.io/tprasadtp/protonvpn + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.0 + with: + registry-username: ${{ github.actor }} + digest: ${{ needs.build.outputs.manifest-digest }} + image: ${{ matrix.image }} + secrets: + registry-password: ${{ secrets.GITHUB_TOKEN }} + + verification: + needs: + - build + - sign + - provenance + runs-on: ubuntu-latest + permissions: + contents: read + packages: read + strategy: + fail-fast: false + matrix: + image: + - ghcr.io/tprasadtp/protonwire + - ghcr.io/tprasadtp/protonvpn + steps: - name: Install Cosign - uses: sigstore/cosign-installer@v3.4.0 + uses: sigstore/cosign-installer@v3 - - name: Install help2man - run: sudo apt-get install -y help2man + - name: Install SLSA verifier + uses: slsa-framework/slsa-verifier/actions/installer@v2.5.1 - name: Login to GitHub Container Registry uses: docker/login-action@v3 @@ -48,7 +176,44 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Run GoReleaser - run: make release-prod + - name: Verify SLSA Provenance + run: >- + slsa-verifier + --source-uri github.com/${{ github.repository }} + verify-image ${{ matrix.image }}@${{ needs.build.outputs.manifest-digest }} + + - name: Verify Cosign Signature + run: >- + cosign verify ${{ matrix.image }}@${{ needs.build.outputs.manifest-digest }} + --certificate-identity-regexp "^https://github.com/${{ github.repository }}" + --certificate-oidc-issuer "https://token.actions.githubusercontent.com" + --certificate-github-workflow-repository "${{ github.repository }}" + --certificate-github-workflow-sha ${{ github.sha }} + create-release: + runs-on: ubuntu-latest + needs: + - build + - sign + - provenance + - verification + if: ${{ github.ref_type == 'tag' }} + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: stable + + - name: Create Github Release + uses: goreleaser/goreleaser-action@v5 + with: + version: latest + args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..c162e54 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,530 @@ +# SPDX-FileCopyrightText: Copyright 2023 Prasad Tengse. +# SPDX-License-Identifier: MIT +# +# yaml-language-server: $schema=https://json.schemastore.org/golangci-lint.json +run: + # Timeout for analysis, e.g. 30s, 5m. + # Default: 1m + timeout: 5m + +# This file contains only configs which differ from defaults. +# All possible options can be found here +# https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml +linters-settings: + cyclop: + # The maximal code complexity to report. + # Default: 10 + max-complexity: 50 + # The maximal average package complexity. + # If it's higher than 0.0 (float) the check is enabled + # Default: 0.0 + package-average: 0 + + # Check declaration order and count of types, constants, variables and functions. + decorder: + # If true, `init` func can be anywhere in file + # (does not have to be declared before all other functions). + # Default: true (disabled) + disable-init-func-first-check: false + + gocyclo: + min-complexity: 50 + + dupl: + threshold: 200 + + errcheck: + # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. + # Such cases aren't reported by default. + # Default: false + check-type-assertions: true + # Lint about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`. + # Such cases aren't reported by default. + # Default: false + check-blank: false + exclude-functions: + - bytes/Buffer.Write + - bytes/Buffer.WriteByte + - bytes/Buffer.WriteString + - strings/Builder.WriteString + - strings/Builder.Write + - strings/Builder.WriteRune + - crypto/Hash.Write + - crypto/rand/Read # https://github.com/golang/go/issues/66821 + - io/Discard.Write + - os/Stderr.Write + - os/Stdout.Write + + exhaustive: + # Program elements to check for exhaustiveness. + # Default: [ switch ] + check: + - switch + - map + # Presence of "default" case in switch statements satisfies exhaustiveness, + # even if all enum members are not listed. + default-signifies-exhaustive: true + + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 + lines: 150 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 + statements: 100 + + gocognit: + # Minimal code complexity to report + min-complexity: 50 + + goconst: + match-constant: true + min-len: 3 + min-occurrences: 9 + ignore-tests: true + ignore-calls: true + numbers: false + + gocritic: + # Settings passed to gocritic. + # The settings key is the name of a supported gocritic checker. + # The list of supported checkers can be find in https://go-critic.github.io/overview. + settings: + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + + goheader: + # Supports two types 'const` and `regexp`. + # Values can be used recursively. + # Default: {} + values: + regexp: + # Define here regexp type values. + # for example: + license: "(BSD\\-3\\-Clause|GPL\\-3\\.0\\-only|GPL\\-2.0\\-only|AGPL\\-3\\.0\\-only|Apache\\-2\\.0|MIT|MPL\\-2.0)" + copyright: "([Cc]opyright\\s+((\\(c\\)|©)\\s+)?[0-9]{4}(.*))" + template: |- + SPDX-FileCopyrightText: {{ copyright }} + SPDX-License-Identifier: {{ license }} + + gomoddirectives: + # Allow local `replace` directives. + # Default: false + replace-local: false + # List of allowed `replace` directives. + # Default: [] + replace-allow-list: [] + # Allow to not explain why the version has been retracted in the `retract` directives. + # Default: false + retract-allow-no-explanation: false + # Forbid the use of the `exclude` directives. + # Default: false + exclude-forbidden: false + + gomodguard: + blocked: + # List of blocked modules. + # Default: [] + modules: + # Use google.golang.org/protobuf. + - github.com/golang/protobuf: + reason: "See https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules" + recommendations: + - google.golang.org/protobuf + + # Use stdlib or custom replacements. + - github.com/mitchellh/go-homedir: + reason: "Use os.UserHomeDir() from stdlib." + + # Go 1.20 introduced errors.Join use it. + - github.com/hashicorp/go-multierror: + reason: "Use errors.Join() from from stdlib." + - go.uber.org/multierr: + reason: "Use errors.Join() from from stdlib." + - github.com/pkg/errors: + reason: "Use error wrapping directive(%w) in stdlib" + + # Go 1.19 introduced New atomic types. + - go.uber.org/atomic: + reason: "Use sync/atomic from stdlib." + + # Go 1.20 introduced fallback roots use it. + - github.com/certifi/gocertifi: + recommendations: + - golang.org/x/crypto/x509roots/fallback + - github.com/breml/rootcerts: + recommendations: + - golang.org/x/crypto/x509roots/fallback + - github.com/gwatts/rootcerts: + recommendations: + - golang.org/x/crypto/x509roots/fallback + - github.com/alexflint/stdroots: + recommendations: + - golang.org/x/crypto/x509roots/fallback + + # Go 1.20 introduced log/slog use it or github.com/tprasadtp/log wrapper + - github.com/hashicorp/go-hclog: + recommendations: + - log/slog + - github.com/tprasadtp/log + - github.com/rs/zerolog: + recommendations: + - log/slog + - github.com/tprasadtp/log + - github.com/sirupsen/logrus: + recommendations: + - log/slog + - github.com/tprasadtp/log + - go.uber.org/zap: + recommendations: + - log/slog + - github.com/tprasadtp/log + + # Use github.com/tprasadtp/go-autotune + - go.uber.org/automaxprocs: + reason: >- + Does not handle fractional CPUs well and does not support Windows. + recommendations: + - github.com/tprasadtp/go-autotune + - github.com/KimMachineGun/automemlimit: + reason: >- + Does not support cgroups mounted at non standard location. + Also does not support memory.high and does not support Windows. + recommendations: + - github.com/tprasadtp/go-autotune + + # Use github.com/tprasadtp/go-githubapp + - github.com/bradleyfalzon/ghinstallation: + reason: Does not support KMS backed keys or keys implementing crypto.Signer interface. + recommendations: + - github.com/tprasadtp/go-githubapp + + - github.com/bradleyfalzon/ghinstallation/v2: + reason: "Does not support KMS backed keys or keys implementing crypto.Signer interface." + recommendations: + - github.com/tprasadtp/go-githubapp + + forbidigo: + # Forbid the following identifiers (list of regexp). + # Default: ["^(fmt\\.Print(|f|ln)|print|println)$"] + forbid: + # Builtin function: + - p: ^print.*$ + msg: "Do not commit print builtin statements." + + # fmt.Prinf|Println + - p: "fmt.Print(f|ln)?(.*)" + msg: "Do not commit print statements." + + # Too many false positives for tests. + - p: time.Sleep + msg: "Avoid using time.Sleep in your code." + + # Exclude godoc examples from forbidigo checks. + # Default: true + exclude-godoc-examples: true + # Instead of matching the literal source code, + # use type information to replace expressions with strings that contain the package name + # and (for methods and fields) the type name. + # This makes it possible to handle import renaming and forbid struct fields and methods. + # Default: false + analyze-types: true + + govet: + # Enable all analyzers. + # Default: false + enable-all: true + # Disable analyzers by name. + # Run `go tool vet help` to see all analyzers. + # Default: [] + disable: + - fieldalignment # too strict + # Settings per analyzer. + settings: + shadow: + # Whether to be strict about shadowing; can be noisy. + # Default: false + strict: true + + ireturn: + # Only allow error, stdlib, empty and generic interfaces. + # Default: [errors, empty, stdlib, anon] + allow: + - error # error interface + - empty # interface{} or any + - stdlib # interfaces in stdlib + - generic # generic interfaces added in go 1.18 (type constraints) + + nakedret: + # Make an issue if func has more lines of code than this setting, and it has naked returns. + # Default: 30 + max-func-lines: 0 + + nestif: + # [Default: 5] + min-complexity: 8 + + nolintlint: + # Exclude following linters from requiring an explanation. + # Default: [] + allow-no-explanation: + - cyclop + - errcheck + - funlen + - gochecknoglobals + - gocognit + - gocyclo + - lll + - nestif + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + # Enable to require nolint directives to mention the specific linter being suppressed. + # Default: false + require-specific: true + + sloglint: + # Enforce using key-value pairs only (incompatible with attr-only). + # Default: false + kv-only: false + # Enforce using attributes only (incompatible with kv-only). + # Default: false + attr-only: true + # Enforce using methods that accept a context. + # Default: false + context-only: false + # Enforce using static values for log messages. + # Default: false + static-msg: true + # Enforce using constants instead of raw keys. + # Default: false + no-raw-keys: false + # Enforce a single key naming convention. + # Values: snake, kebab, camel, pascal + # Default: "" + key-naming-case: snake + # Enforce putting arguments on separate lines. + # Default: false + args-on-sep-lines: false + + tenv: + # The option `all` will run against whole test files + # (`_test.go`) regardless of method/function signatures. + # Otherwise, only methods that take `*testing.T`, `*testing.B`, + # and `testing.TB` as arguments are checked. + # Default: false + all: true + + thelper: + test: + # Check t.Helper() begins helper function. + # Default: true + begin: false + benchmark: + # Check b.Helper() begins helper function. + # Default: true + begin: false + tb: + # Check tb.Helper() begins helper function. + # Default: true + begin: false + fuzz: + # Check f.Helper() begins helper function. + # Default: true + begin: false + +linters: + disable-all: true + enable: + ## Enabled by default + - errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases + - gosimple # specializes in simplifying a code + - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # detects when assignments to existing variables are not used + - staticcheck # is a go vet on steroids, applying a ton of static analysis checks + - typecheck # like the front-end of a Go compiler, parses and type-checks Go code + - unused # checks for unused constants, variables, functions and types + + ## Disabled by default + - asasalint # checks for pass []any as any in variadic func(...any) + - asciicheck # checks that your code does not contain non-ASCII identifiers + - bidichk # checks for dangerous unicode character sequences + - bodyclose # checks whether HTTP response body is closed successfully + - cyclop # checks function and package cyclomatic complexity + - dupl # tool for code clone detection + - decorder # check declaration order and count of types, constants, variables and functions. + - durationcheck # checks for two durations multiplied together + - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error + - errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13 + - execinquery # checks query string in Query function which reads your Go src files and warning it finds + - exhaustive # checks exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - forbidigo # forbids identifiers + - funlen # tool for detection of long functions + - gochecknoinits # checks that no init functions are present in Go code + - gocognit # computes and checks the cognitive complexity of functions + - goconst # finds repeated strings that could be replaced by a constant + - gocritic # provides diagnostics that check for bugs, performance and style issues + - gocyclo # computes and checks the cyclomatic complexity of functions + - godot # checks if comments end in a period + - goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt + - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod + - gomodguard # allow and block lists linter for direct Go module dependencies. + - goprintffuncname # checks that printf-like functions are named with f at the end + - gosec # inspects source code for security problems + - inamedparam # linter that reports interfaces with unnamed method parameters. + - makezero # finds slice declarations with non-zero initial length + - nakedret # finds naked returns in functions greater than a specified function length + - nestif # reports deeply nested if statements + - nilerr # finds the code that returns nil even if it checks that the error is not nil + - nilnil # checks that there is no simultaneous return of nil error and an invalid value + - noctx # finds sending http request without context.Context + - nolintlint # reports ill-formed or insufficient nolint directives + - nonamedreturns # reports all named returns + - nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL + - predeclared # finds code that shadows one of Go's predeclared identifiers + - promlinter # checks Prometheus metrics naming via promlint + - reassign # checks that package variables are not reassigned + - revive # fast, configurable, extensible, flexible, and beautiful linter + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed + - stylecheck # is a replacement for golint + - sloglint # lint log/slog usage + - tenv # detects using os.Setenv instead of t.Setenv since Go1.17 + - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes + - unconvert # removes unnecessary type conversions + - unparam # reports unused function parameters + - usestdlibvars # detects the possibility to use variables/constants from the Go standard library + - wastedassign # finds wasted assignment statements + - whitespace # detects leading and trailing whitespace + - prealloc # finds slice declarations that could potentially be preallocated + - interfacebloat # checks the number of methods inside an interface + - wrapcheck # checks that errors returned from external packages are wrapped + - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) + - importas # enforces consistent import aliases + - goheader # checks for headers + - containedctx # detects struct contained context.Context field + - thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers + + ## you may want to enable + #- godox # detects FIXME, TODO and other comment keywords + + ## disabled + #- gci # [conflicts with vscode gofumpt] controls golang package import order and makes it always deterministic + #- gomnd # [too noisy] detects magic numbers + #- lll # [mostly useless] reports long lines + #- exhaustruct # [mostly useless] checks if all structure fields are initialized + #- gochecknoglobals # [useless, unless you are a beginner] checks that no global variables exist + #- contextcheck # [too many false positives] checks the function whether use a non-inherited context + #- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages + #- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + #- dupword # [useless without config] checks for duplicate words in the source code + #- forcetypeassert # [replaced by errcheck] finds forced type assertions + #- goerr113 # [too strict] checks the errors handling expressions + #- gofmt # [replaced by goimports] checks whether code was gofmt-ed + #- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed + #- grouper # analyzes expression groups + #- maintidx # measures the maintainability index of each function + #- misspell # [useless] finds commonly misspelled English words in comments + #- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity + #- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test + #- tagliatelle # checks the struct tags + #- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines + #- varnamelen [too noisy] # checks that the length of a variable's name matches its scope + #- ireturn # [useless as we use functional options with interfaces a lot] accept interfaces, return concrete types (with stdlib interfaces ignored) + #- testableexamples # checks if examples are testable (have an expected output) + + ## deprecated + #- deadcode # [deprecated, replaced by unused] finds unused code + #- exhaustivestruct # [deprecated, replaced by exhaustruct] checks if all struct's fields are initialized + #- golint # [deprecated, replaced by revive] golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + #- ifshort # [deprecated] checks that your code uses short syntax for if-statements whenever possible + #- interfacer # [deprecated] suggests narrower interface types + #- maligned # [deprecated, replaced by govet fieldalignment] detects Go structs that would take less memory if their fields were sorted + #- nosnakecase # [deprecated, replaced by revive var-naming] detects snake case of variable naming and function name + #- scopelint # [deprecated, replaced by exportloopref] checks for unpinned variables in go programs + #- structcheck # [deprecated, replaced by unused] finds unused struct fields + #- varcheck # [deprecated, replaced by unused] finds unused global variables and constants + +issues: + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 50 + exclude-rules: + # It is mostly okay to shadow err. ineffassign will warn, + # if error checking is not done immediately. However, + # same MUST NOT be done for ctx as context is a "container". + - linters: + - govet + text: 'shadow: declaration of "err" shadows declaration at' + + # In many cases multiple conditions are expected, keep format consistant. + # for both single condition and multi-condition selects. + - linters: + - gosimple + text: "S1000: should use for range instead of for { select {} }" + + # Ignore long lines for go generate. + - source: "^//\\s*go:generate\\s" + linters: + - lll + + # Ignore TODO. + - source: "(noinspection|TODO)" + linters: + - godot + + # Test might add more checks to the else branch and keep them uniform + # across all test cases. This also improves test code readability. + - path: '(.+)_test\.go' + text: "elseif: can replace 'else {if cond {}}' with 'else if cond {}'" + + # Disable some linters for testing code. + - path: '(.+)_test\.go' + linters: + - dupl + - nestif + - funlen + - cyclop + - goconst + - gocyclo + - gocognit + - wrapcheck + - containedctx + - sloglint + + # Disable some linters for generated code. + - path: 'testdata\.go' + linters: + - funlen + - gocyclo + - goconst + - gocyclo + - gocognit + - wrapcheck + - sloglint + + # Disable some linters for ad-hoc scripts and example scripts. + - path: '(example|generate)\.go' + linters: + - wrapcheck + - forbidigo + - sloglint + - gocyclo + - gocognit + + # Disable go-header checks for example files + - path: 'example_(.*)\.go' + linters: + - dupl + - goheader + - sloglint diff --git a/.goreleaser.yml b/.goreleaser.yml index 9ffe015..0d3406d 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -3,23 +3,10 @@ project_name: protonwire snapshot: # This should be semver otherwise triggers https://github.com/goreleaser/goreleaser/issues/2081 - name_template: "{{ .Version }}-SNAPSHOT-{{.ShortCommit}}" + name_template: "{{ .Version }}-{{.ShortCommit}}-dev" builds: - - id: build-binary - goos: - - linux - goarch: - - amd64 # ignored for builds - binary: protonwire - gobinary: ./scripts/goreleaser-wrapper - mod_timestamp: '{{ .CommitTimestamp }}' - skip: false - no_main_check: true - no_unique_dist_dir: true - -archives: - - format: binary + - skip: true changelog: sort: asc @@ -36,150 +23,12 @@ changelog: - title: "🖥️ Metadata API" regexp: "^.*metadata[(\\w)]*:+.*$" order: 4 - filters: exclude: - '^.*doc(s)?[(\\w)]*:+.*$' - '^.*release(s)?[(\\w)]*:+.*$' - '^.*bot(s)?[(\\w)]*:+.*$' -# Build docker images -dockers: - - image_templates: - - 'ghcr.io/tprasadtp/{{ .ProjectName }}:{{ .Version }}-amd64' - - 'ghcr.io/tprasadtp/{{ .ProjectName }}:{{ .FullCommit }}-amd64' - - 'ghcr.io/tprasadtp/protonvpn:{{ .Version }}-amd64' - - 'ghcr.io/tprasadtp/protonvpn:{{ .FullCommit }}-amd64' - - build_flag_templates: - - --label=org.opencontainers.image.created={{.Date}} - - --label=org.opencontainers.image.revision={{.FullCommit}} - - --label=org.opencontainers.image.version={{.Version}} - - --label=org.opencontainers.image.vendor=Prasad Tengse - - --label=org.opencontainers.image.source=https://github.com/tprasadtp/protonwire - - --label=org.opencontainers.image.title=protonwire - - --label=org.opencontainers.image.description=ProtonVPN Wireguard Client for Linux - - --label=org.opencontainers.image.documentation=https://github.com/tprasadtp/protonvpn-docker - - --label=org.opencontainers.image.licenses=GPLv3 - - --label=org.opencontainers.image.url=https://ghcr.io/tprasadtp/protonwire - # Git Info - - --label=io.github.tprasadtp.metadata.git.commit={{.FullCommit}} - - --label=io.github.tprasadtp.metadata.git.shortCommit={{.ShortCommit}} - - --label=io.github.tprasadtp.metadata.git.branch={{.Branch}} - - --label=io.github.tprasadtp.metadata.git.tag={{.Tag}} - - --label=io.github.tprasadtp.metadata.version.major={{.Major}} - - --label=io.github.tprasadtp.metadata.version.minor={{.Minor}} - - --label=io.github.tprasadtp.metadata.version.patch={{.Patch}} - - --label=io.github.tprasadtp.metadata.version.prerelease={{.Prerelease}} - - --label=io.github.tprasadtp.metadata.version.snapshot={{.IsSnapshot}} - # Platform - - --platform=linux/amd64 - - <<: &docker_defaults - dockerfile: Dockerfile - use: buildx - extra_files: - - protonwire - - # ARM64 image - - image_templates: - - 'ghcr.io/tprasadtp/{{ .ProjectName }}:{{ .Version }}-arm64' - - 'ghcr.io/tprasadtp/{{ .ProjectName }}:{{ .FullCommit }}-arm64' - - 'ghcr.io/tprasadtp/protonvpn:{{ .Version }}-arm64' - - 'ghcr.io/tprasadtp/protonvpn:{{ .FullCommit }}-arm64' - - build_flag_templates: - - --label=org.opencontainers.image.created={{.Date}} - - --label=org.opencontainers.image.revision={{.FullCommit}} - - --label=org.opencontainers.image.version={{.Version}} - - --label=org.opencontainers.image.vendor=Prasad Tengse - - --label=org.opencontainers.image.source=https://github.com/tprasadtp/protonwire - - --label=org.opencontainers.image.title=protonwire - - --label=org.opencontainers.image.description=ProtonVPN Wireguard Client for Linux - - --label=org.opencontainers.image.documentation=https://github.com/tprasadtp/protonvpn-docker - - --label=org.opencontainers.image.licenses=GPLv3 - - --label=org.opencontainers.image.url=https://ghcr.io/tprasadtp/protonwire - # Git Info - - --label=io.github.tprasadtp.metadata.git.commit={{.FullCommit}} - - --label=io.github.tprasadtp.metadata.git.shortCommit={{.ShortCommit}} - - --label=io.github.tprasadtp.metadata.git.branch={{.Branch}} - - --label=io.github.tprasadtp.metadata.git.tag={{.Tag}} - - --label=io.github.tprasadtp.metadata.version.major={{.Major}} - - --label=io.github.tprasadtp.metadata.version.minor={{.Minor}} - - --label=io.github.tprasadtp.metadata.version.patch={{.Patch}} - - --label=io.github.tprasadtp.metadata.version.prerelease={{.Prerelease}} - - --label=io.github.tprasadtp.metadata.version.snapshot={{.IsSnapshot}} - # Platform - - --platform=linux/arm64 - - # Import defaults - <<: *docker_defaults - -# # Build manifests -docker_manifests: - # Full Version - - name_template: 'ghcr.io/tprasadtp/{{.ProjectName}}:{{ .Version }}' - image_templates: - - 'ghcr.io/tprasadtp/{{.ProjectName}}:{{ .Version }}-amd64' - - 'ghcr.io/tprasadtp/{{.ProjectName}}:{{ .Version }}-arm64' - - # MAJOR.MINOR - - name_template: 'ghcr.io/tprasadtp/{{.ProjectName}}:{{ .Major }}.{{ .Minor }}{{ if .Prerelease }}-unstable{{ end }}' - image_templates: - - 'ghcr.io/tprasadtp/{{.ProjectName}}:{{ .Version }}-amd64' - - 'ghcr.io/tprasadtp/{{.ProjectName}}:{{ .Version }}-arm64' - - # MAJOR - - name_template: 'ghcr.io/tprasadtp/{{.ProjectName}}:{{ .Major }}{{ if .Prerelease }}-unstable{{ end }}' - image_templates: - - 'ghcr.io/tprasadtp/{{.ProjectName}}:{{ .Version }}-amd64' - - 'ghcr.io/tprasadtp/{{.ProjectName}}:{{ .Version }}-arm64' - - # Commit SHA - - name_template: 'ghcr.io/tprasadtp/{{.ProjectName}}:{{ .FullCommit }}' - image_templates: - - 'ghcr.io/tprasadtp/{{ .ProjectName }}:{{ .FullCommit }}-amd64' - - 'ghcr.io/tprasadtp/{{ .ProjectName }}:{{ .FullCommit }}-arm64' - - # Latest - - name_template: 'ghcr.io/tprasadtp/{{.ProjectName}}:{{ if .Prerelease }}unstable{{else}}latest{{ end }}' - image_templates: - - 'ghcr.io/tprasadtp/{{.ProjectName}}:{{ .Version }}-amd64' - - 'ghcr.io/tprasadtp/{{.ProjectName}}:{{ .Version }}-arm64' - - # Full Version - - name_template: 'ghcr.io/tprasadtp/protonvpn:{{ .Version }}' - image_templates: - - 'ghcr.io/tprasadtp/protonvpn:{{ .Version }}-amd64' - - 'ghcr.io/tprasadtp/protonvpn:{{ .Version }}-arm64' - - # MAJOR.MINOR - - name_template: 'ghcr.io/tprasadtp/protonvpn:{{ .Major }}.{{ .Minor }}{{ if .Prerelease }}-unstable{{ end }}' - image_templates: - - 'ghcr.io/tprasadtp/protonvpn:{{ .Version }}-amd64' - - 'ghcr.io/tprasadtp/protonvpn:{{ .Version }}-arm64' - - # MAJOR - - name_template: 'ghcr.io/tprasadtp/protonvpn:{{ .Major }}{{ if .Prerelease }}-unstable{{ end }}' - image_templates: - - 'ghcr.io/tprasadtp/protonvpn:{{ .Version }}-amd64' - - 'ghcr.io/tprasadtp/protonvpn:{{ .Version }}-arm64' - - # Commit SHA - - name_template: 'ghcr.io/tprasadtp/protonvpn:{{ .FullCommit }}' - image_templates: - - 'ghcr.io/tprasadtp/{{ .ProjectName }}:{{ .FullCommit }}-amd64' - - 'ghcr.io/tprasadtp/{{ .ProjectName }}:{{ .FullCommit }}-arm64' - - # Latest - - name_template: 'ghcr.io/tprasadtp/protonvpn:{{ if .Prerelease }}unstable{{else}}latest{{ end }}' - image_templates: - - 'ghcr.io/tprasadtp/protonvpn:{{ .Version }}-amd64' - - 'ghcr.io/tprasadtp/protonvpn:{{ .Version }}-arm64' - -checksum: - disable: true - release: mode: replace prerelease: auto @@ -191,12 +40,3 @@ release: # Since: v1.11 # Templates: allowed (since v1.15) skip_upload: true - -docker_signs: - - cmd: cosign - artifacts: manifests - output: true - args: - - "sign" - - "${artifact}@${digest}" - - --yes diff --git a/Dockerfile b/Dockerfile index 35d5567..9a4c327 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ #syntax=docker/dockerfile:1.2 -FROM debian:bookworm-20240311-slim as base +FROM debian@sha256:3d5df92588469a4c503adbead0e4129ef3f88e223954011c2169073897547cac as base FROM base diff --git a/Makefile b/Makefile index 962b0f5..fec3d56 100644 --- a/Makefile +++ b/Makefile @@ -37,28 +37,11 @@ docker: ## Build docker image --tag ghcr.io/tprasadtp/protonwire:dev \ $(REPO_ROOT) -.PHONY: snapshot -snapshot: ## Build snapshot - goreleaser release \ - --snapshot \ - --clean - -.PHONY: release -release: ## Build release - goreleaser release \ - --clean \ - --skip-publish \ - --skip-validate \ - --skip-announce \ - --skip-sign - -.PHONY: release-prod -release-prod: ## Build release and publish - goreleaser release --clean .PHONY: clean clean: ## clean rm -rf $(REPO_ROOT)/dist/ + rm -rf $(REPO_ROOT)/build/ rm -rf $(REPO_ROOT)/metadata/ .PHONY: update-readme diff --git a/README.md b/README.md index 272af5f..0ac06d1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@
-# Protonwire - ProtonVPN Wireguard Client +# Protonwire - Docker Wireguard Client for ProtonVPN [![actions-build](https://github.com/tprasadtp/protonvpn-docker/actions/workflows/build.yml/badge.svg)](https://github.com/tprasadtp/protonvpn-docker/actions/workflows/build.yml) [![actions-docs](https://github.com/tprasadtp/protonvpn-docker/actions/workflows/docs.yml/badge.svg)](https://github.com/tprasadtp/protonvpn-docker/actions/workflows/docs.yml) @@ -12,6 +12,9 @@ [![actions-metadata](https://github.com/tprasadtp/protonvpn-docker/actions/workflows/metadata.yml/badge.svg)](https://github.com/tprasadtp/protonvpn-docker/actions/workflows/metadata.yml) [![metadata-refresh](https://img.shields.io/badge/dynamic/json?label=metadata&query=timestamp&url=https%3A%2F%2Fprotonwire-api.vercel.app&logo=protonvpn&labelColor=3a3a3a&logoColor=white&color=7f50a6)](https://protonwire-api.vercel.app/) [![metadata-servers](https://img.shields.io/badge/dynamic/json?label=servers&query=server_count&url=https%3A%2F%2Fprotonwire-api.vercel.app&logo=protonvpn&labelColor=3a3a3a&logoColor=white&color=7f50a6)](https://protonwire-api.vercel.app/) +[![slsa-badge][slsa-badge]][slsa-verify-docs] + +
@@ -376,7 +379,8 @@ See [Troubleshooting][] and [FAQ][] ## Building -Building requires `goreleaser`, and `docker` with `buildx` plugin. +Building requires [`task`](https://taskfile.dev/installation/), [`go`](https://go.dev/dl/) +and `docker` with `buildx` plugin. [drop-in]: https://wiki.archlinux.org/title/systemd#Drop-in_files [nss-resolve]: https://www.freedesktop.org/software/systemd/man/nss-resolve.html @@ -398,3 +402,5 @@ Building requires `goreleaser`, and `docker` with `buildx` plugin. [releases]: https://github.com/tprasadtp/protonwire/releases/latest [Troubleshooting]: ./docs/help.md [FAQ]: ./docs/faq.md +[slsa-verify-docs]: ./docs/slsa.md +[slsa-badge]: https://img.shields.io/badge/SLSA-level%203-39AC60?labelColor=3a3a3a&logoColor=959da5&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABMlBMVEXvMQDvMADwMQDwMADwMADvMADvMADwMADwMQDvMQDvMQDwMADwMADvMADwMADwMADwMQDvMQDvMQDwMQDvMQDwMQDwMADwMADwMQDwMADwMADvMADvMQDvMQDwMADwMQDwMADvMQDwMADwMQDwMADwMADwMADwMADwMADwMADvMQDvMQDwMADwMQDwMADvMQDvMQDwMADvMQDvMQDwMADwMQDwMQDwMQDvMQDwMADvMADwMADwMQDvMQDwMADwMQDwMQDwMQDwMQDvMQDvMQDvMADwMADvMADvMADvMADwMQDwMQDvMADvMQDvMQDvMADvMADvMQDwMQDvMQDvMADvMADvMADvMQDwMQDvMQDvMQDvMADvMADwMADvMQDvMQDvMQDvMADwMADwMQDwMAAAAAA/HoSwAAAAY3RSTlMpsvneQlQrU/LQSWzvM5DzmzeF9Pi+N6vvrk9HuP3asTaPgkVFmO3rUrMjqvL6d0LLTVjI/PuMQNSGOWa/6YU8zNuDLihJ0e6aMGzl8s2IT7b6lIFkRj1mtvQ0eJW95rG0+Sid59x/AAAAAWJLR0Rltd2InwAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAAd0SU1FB+YHGg0tGLrTaD4AAACqSURBVAjXY2BgZEqGAGYWVjYGdg4oj5OLm4eRgZcvBcThFxAUEk4WYRAVE09OlpCUkpaRTU6WY0iWV1BUUlZRVQMqUddgSE7W1NLS1gFp0NXTB3KTDQyNjE2Sk03NzC1A3GR1SytrG1s7e4dkBogtjk7OLq5uyTCuu4enl3cyhOvj66fvHxAIEmYICg4JDQuPiAQrEmGIio6JjZOFOjSegSHBBMpOToxPAgCJfDZC/m2KHgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMi0wNy0yNlQxMzo0NToyNCswMDowMC8AywoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjItMDctMjZUMTM6NDU6MjQrMDA6MDBeXXO2AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAABJRU5ErkJggg== diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..e94b3f3 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,309 @@ +# SPDX-FileCopyrightText: Copyright 2024 Prasad Tengse +# SPDX-License-Identifier: MIT + +# yaml-language-server: $schema=https://taskfile.dev/schema.json +version: "3" + +vars: + PROTONWIRE_IMAGE: "ghcr.io/tprasadtp/protonwire" + PROTONVPN_IMAGE: "ghcr.io/tprasadtp/protonvpn" + SHELLCHECK_VERSION: '{{ default .SHELLCHECK_VERSION "v0.10.0"}}' + +tasks: + # ----------------------------------------------------------------- + # Default Task. Shows List of available tasks. + # + # This intentionally lacks a desc field to hide it from help output. + # ----------------------------------------------------------------- + default: + cmds: + - cmd: task --list + silent: true + # ----------------------------------------------------------------- + # Creates a directory if not present. + # ----------------------------------------------------------------- + internal:mkdir: + internal: true + label: mkdir + requires: + vars: + - DIRECTORY + status: + - >- + {{- if .DIRECTORY }} + test -d {{ .DIRECTORY|quote }} + {{- else }} + exit 0 + {{- end }} + cmds: + # Do not use long form flag --parents as it is not supported on macOS. + - cmd: mkdir -p {{.DIRECTORY|quote}} + platforms: + - linux + - darwin + - freebsd + - netbsd + - dragonfly + - openbsd + - cmd: powershell.exe -NonInteractive -NoProfile -NoLogo -Command 'New-Item -ItemType Directory -Force -Path "{{.DIRECTORY}}"' + platforms: + - windows + # ----------------------------------------------------------------- + # Removes files with PATTERN in the given DIRECTORY. + # ----------------------------------------------------------------- + internal:rm-file-glob: + internal: true + label: "rm" + requires: + vars: + - DIRECTORY + - PATTERN + status: + - >- + {{- if .DIRECTORY }} + ! test -d {{ .DIRECTORY|quote }} + {{- else }} + exit 0 + {{- end }} + cmds: + # Do not use long form flag --parents as it is not supported on macOS. + - cmd: rm -f {{ joinPath (.DIRECTORY | quote) .PATTERN }} + platforms: + - linux + - darwin + - freebsd + - netbsd + - dragonfly + - openbsd + - cmd: powershell.exe -NonInteractive -NoProfile -NoLogo -Command 'Remove-Item -Force -Path "{{ joinPath .DIRECTORY .PATTERN }}"' + platforms: + - windows + # ----------------------------------------------------------------- + # Removes an empty DIRECTORY. + # ----------------------------------------------------------------- + internal:rmdir: + internal: true + label: "rmdir" + requires: + vars: + - DIRECTORY + status: + - >- + {{- if .DIRECTORY }} + ! test -d {{ .DIRECTORY|quote }} + {{- else }} + exit 0 + {{- end }} + cmds: + - cmd: rmdir {{ .DIRECTORY | quote }} + platforms: + - linux + - darwin + - freebsd + - netbsd + - dragonfly + - openbsd + - cmd: powershell.exe -NonInteractive -NoProfile -NoLogo -Command 'Remove-Item -Force -Path "{{ .DIRECTORY }}"' + platforms: + - windows + # ----------------------------------------------------------------- + # Run shellcheck + # ----------------------------------------------------------------- + shellcheck: + desc: "Run shellcheck on protonwire script" + cmds: + - cmd: >- + docker run + --rm + --userns=host + --workdir=/app/ + --network=none + --mount type=bind,src={{.ROOT_DIR}}/protonwire,dst=/protonwire,readonly + koalaman/shellcheck:{{.SHELLCHECK_VERSION}} + --color=always + --extended-analysis=true + /protonwire + # ----------------------------------------------------------------- + # Update README with help from script and docker compose file. + # ----------------------------------------------------------------- + update-readme: + desc: "Update README.md" + cmds: + - cmd: sed -i '//,//!b;//!d;//e echo "
" && ./protonwire --help && echo "
"' README.md + - cmd: sed -i '//,//!b;//!d;//e echo "\\\`\\\`\\\`yaml" && cat docs/examples/docker/docker-compose.yml && echo "\\\`\\\`\\\`"' README.md + # ----------------------------------------------------------------- + # Build Docker images + # ----------------------------------------------------------------- + internal:build-script: + internal: true + requires: + vars: + - "GIT_COMMIT" + - "VERSION" + cmds: + - task: internal:mkdir + vars: + DIRECTORY: "dist" + - cmd: ./scripts/build-script build -o=dist/protonwire main.commit={{.GIT_COMMIT}} main.version={{.VERSION}} + internal:build-image: + internal: true + requires: + vars: + - "PLATFORM" + - "GIT_COMMIT" + - "GIT_COMMIT_TIMESTAMP" + - "GIT_STATUS_PORCELAIN" + vars: + IMAGE_LABEL_FLAGS: >- + {{- printf `--label "%s=%s"` "org.opencontainers.image.revision" .GIT_COMMIT }} + {{- printf ` --label "%s=%s"` "org.opencontainers.image.created" .GIT_COMMIT_TIMESTAMP }} + {{- printf ` --label "%s=%s"` "org.opencontainers.image.vendor" "Prasad Tengse " }} + {{- printf ` --label "%s=%s"` "org.opencontainers.image.source" "https://github.com/tprasadtp/protonvpn-docker" }} + {{- printf ` --label "%s=%s"` "org.opencontainers.image.title" "protonwire" }} + {{- printf ` --label "%s=%s"` "org.opencontainers.image.description" "ProtonVPN Wireguard Client" }} + {{- printf ` --label "%s=%s"` "org.opencontainers.image.licenses" "GPLv3" }} + {{- printf ` --label "%s=%s"` "org.opencontainers.image.documentation" "https://github.com/tprasadtp/protonvpn-docker" }} + {{- printf ` --label "%s=%s"` "io.artifacthub.package.readme-url" "https://raw.githubusercontent.com/tprasadtp/go-autotune/master/README.md" }} + IMAGE_TAG: >- + {{- if .GIT_STATUS_PORCELAIN }} + {{- printf "%s-%s-dirty" .GIT_COMMIT (.PLATFORM | replace "/" "-") }} + {{- else }} + {{- printf "%s-%s" .GIT_COMMIT (.PLATFORM | replace "/" "-") }} + {{- end }} + IMAGE_TARBALL: '{{ printf "%s.tar" (joinPath "build" (.PLATFORM | replace "/" "-")) }}' + env: + DOCKER_BUILDKIT: "1" + cmds: + - cmd: cp Dockerfile dist/Dockerfile + - docker build {{.IMAGE_LABEL_FLAGS}} --tag={{.PROTONWIRE_IMAGE}}:{{.IMAGE_TAG}} --tag={{.PROTONVPN_IMAGE}}:{{.IMAGE_TAG}} --platform={{.PLATFORM}} dist/ + build-images: + desc: "Build Docker Images" + aliases: + - "images-build" + vars: + GIT_COMMIT: + sh: git -c log.showSignature=false show --format=%H --quiet HEAD + GIT_TREE_STATE: + sh: git -c log.showSignature=false status --porcelain + GIT_COMMIT_TIMESTAMP: + sh: git -c log.showSignature=false show --format=%cI --quiet HEAD + GIT_STATUS_PORCELAIN: + sh: git -c log.showSignature=false status --porcelain + GIT_DESCRIBE: + sh: git -c log.showSignature=false describe HEAD + cmds: + - task: internal:build-script + vars: + DIST: "{{.DIST_DIR}}" + GIT_COMMIT: "{{.GIT_COMMIT}}" + VERSION: >- + {{- if .GIT_STATUS_PORCELAIN }} + {{- printf "%s-dirty" .GIT_DESCRIBE }} + {{- else }} + {{- printf "%s" .GIT_DESCRIBE }} + {{- end }} + - for: ["linux/amd64", "linux/arm64"] + task: internal:build-image + vars: + PLATFORM: "{{.ITEM}}" + DIST_DIR: "dist" + GIT_COMMIT: "{{.GIT_COMMIT}}" + GIT_COMMIT_TIMESTAMP: "{{.GIT_COMMIT_TIMESTAMP}}" + GIT_STATUS_PORCELAIN: "{{.GIT_STATUS_PORCELAIN}}" + push-images: + desc: "Push docker images" + vars: + IMAGE_REPOS: "{{.PROTONWIRE_IMAGE}},{{.PROTONVPN_IMAGE}}" + GIT_COMMIT: + sh: git -c log.showSignature=false show --format=%H --quiet HEAD + GIT_STATUS_PORCELAIN: + sh: git -c log.showSignature=false status --porcelain + GIT_DIRTY_SUFFIX: >- + {{- if .GIT_STATUS_PORCELAIN }} + {{- printf "-dirty" }} + {{- end }} + cmds: + - for: ["linux/amd64", "linux/arm64"] + cmd: docker push {{.PROTONWIRE_IMAGE}}:{{.GIT_COMMIT}}-{{ .ITEM | replace "/" "-" }}{{.GIT_DIRTY_SUFFIX}} + - for: ["linux/amd64", "linux/arm64"] + cmd: docker push {{.PROTONVPN_IMAGE}}:{{.GIT_COMMIT}}-{{ .ITEM | replace "/" "-" }}{{.GIT_DIRTY_SUFFIX}} + push-manifests: + # We cannot push images and manifests in single step as digest + # cannot be computed locally by docker. + # - https://github.com/distribution/distribution/issues/1662 + desc: "Push docker image manifests/index" + vars: + IMAGE_REPOS: "{{.PROTONWIRE_IMAGE}},{{.PROTONVPN_IMAGE}}" + GIT_COMMIT: + sh: git -c log.showSignature=false show --format=%H --quiet HEAD + GIT_COMMIT_SHORT: + sh: git -c log.showSignature=false show --format=%h --quiet HEAD + GIT_TAG_POINTS_AT_HEAD: + sh: git -c log.showSignature=false tag --points-at HEAD --sort=-version:refname + GIT_DESCRIBE: + sh: git -c log.showSignature=false describe HEAD + GIT_STATUS_PORCELAIN: + sh: git -c log.showSignature=false status --porcelain + GIT_TAG: >- + {{- if .GIT_TAG }} + {{- printf "%s" .GIT_TAG }} + {{- else if .GIT_TAG_POINTS_AT_HEAD }} + {{- printf "%s" (index (splitLines .GIT_TAG_POINTS_AT_HEAD) 0) | trim }} + {{- else }} + {{- printf "%s" .GIT_DESCRIBE | trim }} + {{- end }} + VERSION: + sh: go run internal/tasks/main.go semver version {{.GIT_TAG}} + V_MAJOR: + sh: go run internal/tasks/main.go semver major {{.GIT_TAG}} + V_MINOR: + sh: go run internal/tasks/main.go semver minor {{.GIT_TAG}} + V_IS_PRE_RELEASE: + sh: go run internal/tasks/main.go semver is-pre-release {{.GIT_TAG}} + V_UNSTABLE_SUFFIX: >- + {{- if eq (.V_IS_PRE_RELEASE|trim) "true" }} + {{- printf "%s" "-unstable" }} + {{- end }} + V_DIRTY_SUFFIX: >- + {{- if .GIT_STATUS_PORCELAIN }} + {{- printf "-dirty" }} + {{- end }} + V_UNSTABLE_OR_LATEST: >- + {{- if eq .V_IS_PRE_RELEASE "true" }} + {{- printf "%s" "unstable" }} + {{- else if eq .GIT_TREE_STATE }} + {{- printf "%s" "unstable" }} + {{- else }} + {{- printf "%s" "latest-debug" }} + {{- end }} + IMAGE_AMD64_DIGEST: + sh: crane digest {{.PROTONWIRE_IMAGE}}:{{.GIT_COMMIT}}-linux-amd64{{.V_DIRTY_SUFFIX}} + IMAGE_ARM64_DIGEST: + sh: crane digest {{.PROTONWIRE_IMAGE}}:{{.GIT_COMMIT}}-linux-arm64{{.V_DIRTY_SUFFIX}} + cmds: + # :commit + - for: { var: IMAGE_REPOS, split: ',', as: IMAGE_REPO } + cmd: crane index append --tag {{.IMAGE_REPO}}:{{.GIT_COMMIT}}{{.V_DIRTY_SUFFIX}} -m {{.IMAGE_REPO}}@{{.IMAGE_AMD64_DIGEST}} -m {{.IMAGE_REPO}}@{{.IMAGE_ARM64_DIGEST}} + # :commit-short + - for: { var: IMAGE_REPOS, split: ',', as: IMAGE_REPO } + cmd: crane index append --tag {{.IMAGE_REPO}}:{{.GIT_COMMIT_SHORT}}{{.V_DIRTY_SUFFIX}} -m {{.IMAGE_REPO}}@{{.IMAGE_AMD64_DIGEST}} -m {{.IMAGE_REPO}}@{{.IMAGE_ARM64_DIGEST}} + # :git-tag + - for: { var: IMAGE_REPOS, split: ',', as: IMAGE_REPO } + cmd: crane index append --tag {{.IMAGE_REPO}}:{{.GIT_TAG}}{{.V_DIRTY_SUFFIX}} -m {{.IMAGE_REPO}}@{{.IMAGE_AMD64_DIGEST}} -m {{.IMAGE_REPO}}@{{.IMAGE_ARM64_DIGEST}} + # :version, skipped if same as git tag i,e without prefix v. + - for: { var: IMAGE_REPOS, split: ',', as: IMAGE_REPO } + cmd: >- + {{- if eq .VERSION .GIT_TAG }} + echo "version and git tag are same ({{.VERSION }}) for image {{.IMAGE_REPO}}" + {{- else }} + crane index append --tag {{.IMAGE_REPO}}:{{.VERSION}}{{.V_DIRTY_SUFFIX}} -m {{.IMAGE_REPO}}@{{.IMAGE_AMD64_DIGEST}} -m {{.IMAGE_REPO}}@{{.IMAGE_ARM64_DIGEST}} + {{- end }} + # :major.minor + - for: { var: IMAGE_REPOS, split: ',', as: IMAGE_REPO } + cmd: crane index append --tag {{.IMAGE_REPO}}:{{.V_MAJOR}}.{{.V_MINOR}}{{.V_UNSTABLE_SUFFIX}}{{.V_DIRTY_SUFFIX}} -m {{.IMAGE_REPO}}@{{.IMAGE_AMD64_DIGEST}} -m {{.IMAGE_REPO}}@{{.IMAGE_ARM64_DIGEST}} + # :major + - for: { var: IMAGE_REPOS, split: ',', as: IMAGE_REPO } + cmd: crane index append --tag {{.IMAGE_REPO}}:{{.V_MAJOR}}{{.V_UNSTABLE_SUFFIX}}{{.V_DIRTY_SUFFIX}} -m {{.IMAGE_REPO}}@{{.IMAGE_AMD64_DIGEST}} -m {{.IMAGE_REPO}}@{{.IMAGE_ARM64_DIGEST}} + # :latest or unstable + - for: { var: IMAGE_REPOS, split: ',', as: IMAGE_REPO } + cmd: crane index append --tag {{.IMAGE_REPO}}:{{.V_UNSTABLE_OR_LATEST}}{{.V_DIRTY_SUFFIX}} -m {{.IMAGE_REPO}}@{{.IMAGE_AMD64_DIGEST}} -m {{.IMAGE_REPO}}@{{.IMAGE_ARM64_DIGEST}} diff --git a/docs/cosign.md b/docs/cosign.md new file mode 100644 index 0000000..9fc9944 --- /dev/null +++ b/docs/cosign.md @@ -0,0 +1,17 @@ +# Cosign + +All artifacts provided by this repository are signed using [cosign]. + +## Verify Cosign signature + +- Install [`cosign`][cosign]. +- Verify cosign signature + ```bash + cosign verify \ + --certificate-identity-regexp "^https://github.com/tprasadtp/protonvpn-docker" \ + --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ + --certificate-github-workflow-repository "tprasadtp/protonvpn-docker" \ + ghcr.io/tprasadtp/protonwire: + ``` + +[cosign]: https://docs.sigstore.dev/system_config/installation/ diff --git a/docs/examples/podman/protonwire.container b/docs/examples/podman/protonwire.container index 0396013..7954ef5 100644 --- a/docs/examples/podman/protonwire.container +++ b/docs/examples/podman/protonwire.container @@ -17,7 +17,7 @@ Notify=true ContainerName=protonwire # Runtime configuration -Image=ghcr.io/tprasadtp/protonwire:7 +Image=ghcr.io/tprasadtp/protonwire:latest Timezone=local RunInit=true diff --git a/docs/faq.md b/docs/faq.md index 386cc9a..501abb3 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -160,7 +160,6 @@ server is the assigned public IP. Bulk of the work is done via `scripts/generate-server-metadata` - `https://protonwire-api.vercel.app/v1/server` (default) -- `https://tprasadtp.github.io/protonvpn-docker/v1/server` (beta) ## LAN/Local DNS Server and API endpoints @@ -191,3 +190,38 @@ Best solution is to build your own pod definitions with __all__ the apps running in a __single pod__ and use protonwire container with command `protonwire connect --kill-switch` as init container. This ensures all the containers in your pod are using the VPN. Do note that `.cluster` domains like `..svc.cluster` are **NOT** resolved (unless your use `SKIP_CONFIG_DNS=1`) as ProtonVPN DNS server is used. Do remember to apply required sysctls to the pod created. + +## Port Forwarding + +Port forwarding is not supported directly, but the image includes tools required to setup via custom +script(`socat` and `natpmpc` etc). It is being tracked via [#125](https://github.com/tprasadtp/protonvpn-docker/issues/125). It might be necessary to write your `service` loop which keeps port forwarding updated. Following commands can be used to setup VPN connection and check it regularly. +Do note that script will still take into consideration `IPCHECK_INTERVAL` for healthchecks, so keep +your custom script compatible with it. + +- Connect to VPN server with kill-switch. + + ```bash + protonwire connect --ks + ``` + +- Verify that connection is active. **DO NOT** use `--container` flag, as it +depends on protonwire running in the background. + + ```bash + protonwire verify + ``` + +- Setup your port forwarding using `natpmpc` and write mapped port to a shared volume. + +- In a loop verify the connection and keep refreshing port forwarding. + + ```bash + protonwire verify || exit 1 + natpmpc || exit 1 + ``` + +- To disconnect, run + + ```bash + protonwire disconnect + ``` diff --git a/docs/images/slsa-level3-logo.svg b/docs/images/slsa-level3-logo.svg new file mode 100644 index 0000000..7154d4a --- /dev/null +++ b/docs/images/slsa-level3-logo.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/slsa.md b/docs/slsa.md new file mode 100644 index 0000000..4c944af --- /dev/null +++ b/docs/slsa.md @@ -0,0 +1,41 @@ +# SLSA + +
+ +[![slsa-badge-level3][slsa-badge-level3]][slsa-level3] + +
+ +All _artifacts_ provided by this repository meet [SLSA L3][slsa-level3]. + +## Verify SLSA provenance + +- Install `slsa-verifier` from [slsa-verifier] project. +- Get digest of image index/manifest. GHCR UI provides the digest in the UI. + alternatively, `docker`, `crane` or `cosign triangulate --type=digest` command + can be used. + + ```bash + docker images \ + --digests \ + --format "Image={{.Repository}}:{{.Tag}} Digest={{.Digest}}" \ + ghcr.io/tprasadtp/protonwire + ``` + +- Verify Image + + ```bash + slsa-verifier verify-image \ + --source-uri=github.com/tprasadtp/protonvpn-docker \ + ghcr.io/tprasadtp/protonwire@ + ``` + +## SLSA provenance for metadata + +Generating slsa provenance for metadata is tricky without leaking all the server names. +As slsa L3 workflows need to save intermediate artifacts which contain server names. + +[cosign]: https://docs.sigstore.dev/system_config/installation/ +[slsa-verifier]: https://github.com/slsa-framework/slsa-verifier +[slsa-badge-level3]: ./images/slsa-level3-logo.svg +[slsa-level3]: https://slsa.dev/spec/v1.0/levels#build-l3 diff --git a/go.mod b/go.mod index 7c09830..67aac05 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,13 @@ -module github.com/tprasadtp/protonwire +module github.com/tprasadtp/protonvpn-docker -go 1.20 +go 1.22 + +require ( + github.com/Masterminds/semver/v3 v3.2.1 + github.com/spf13/cobra v1.8.0 +) + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect +) diff --git a/go.sum b/go.sum index e69de29..5cc8ba3 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,15 @@ +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/tasks/main.go b/internal/tasks/main.go new file mode 100644 index 0000000..a6ca84c --- /dev/null +++ b/internal/tasks/main.go @@ -0,0 +1,131 @@ +package main + +import ( + "context" + "fmt" + "os" + "os/signal" + "strings" + + "github.com/Masterminds/semver/v3" + "github.com/spf13/cobra" +) + +func main() { + ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt) + root := &cobra.Command{ + Use: "builder", + Short: "Builder for protonwire docker images", + DisableAutoGenTag: true, + SilenceUsage: true, + CompletionOptions: cobra.CompletionOptions{ + DisableDefaultCmd: true, + }, + } + root.AddCommand( + newSemverCmd(), + ) + + if err := root.ExecuteContext(ctx); err != nil { + os.Exit(1) + } +} + +func newSemverCmd() *cobra.Command { + semverCmd := &cobra.Command{ + Use: "semver", + Short: "Semantic version parser", + } + versionCmd := &cobra.Command{ + Use: "version VERSION", + Args: cobra.ExactArgs(1), + Short: "Prints semver if VERSION is valid", + RunE: func(cmd *cobra.Command, args []string) error { + return runE(cmd, args, "version") + }, + } + isPreRelCmd := &cobra.Command{ + Use: "is-pre-release VERSION", + Args: cobra.ExactArgs(1), + Short: "Prints true if VERSION is a pre-release otherwise print false", + RunE: func(cmd *cobra.Command, args []string) error { + return runE(cmd, args, "is-pre-release") + }, + } + + semverCmd.AddCommand( + versionCmd, + cmdExtract("major"), + cmdExtract("minor"), + cmdExtract("patch"), + cmdExtract("pre-release"), + cmdExtract("build-version"), + cmdBump("major"), + cmdBump("minor"), + cmdBump("patch"), + isPreRelCmd, + ) + + return semverCmd +} + +func cmdExtract(name string) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("%s VERSION", name), + Short: fmt.Sprintf("Extract %s version from VERSION", name), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return runE(cmd, args, name) + }, + } + return cmd +} + +func cmdBump(name string) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("%s VERSION", name), + Short: fmt.Sprintf("Bump %s version from VERSION", name), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return runE(cmd, args, fmt.Sprintf("bump-%s", name)) + }, + } + return cmd +} + +func runE(cmd *cobra.Command, args []string, typ string) error { + version, err := semver.NewVersion(strings.TrimPrefix(args[0], "v")) + if err != nil { + return fmt.Errorf("Version(%s) is invalid: %w", args[0], err) + } + + switch typ { + case "version": + fmt.Fprintln(cmd.OutOrStdout(), version.String()) + case "major": + fmt.Fprintln(cmd.OutOrStdout(), version.Major()) + case "minor": + fmt.Fprintln(cmd.OutOrStdout(), version.Minor()) + case "patch": + fmt.Fprintln(cmd.OutOrStdout(), version.Patch()) + case "pre-release": + fmt.Fprintln(cmd.OutOrStdout(), version.Prerelease()) + case "build": + fmt.Fprintln(cmd.OutOrStdout(), version.Metadata()) + case "bump-major": + fmt.Fprintln(cmd.OutOrStdout(), version.IncMajor().String()) + case "bump-minor": + fmt.Fprintln(cmd.OutOrStdout(), version.IncMinor().String()) + case "bump-patch": + fmt.Fprintln(cmd.OutOrStdout(), version.IncPatch().String()) + case "is-pre-release": + if version.Prerelease() != "" { + fmt.Fprintln(cmd.OutOrStdout(), "true") + } else { + fmt.Fprintln(cmd.OutOrStdout(), "false") + } + default: + return fmt.Errorf("unknown function: %s", typ) + } + return nil +} diff --git a/protonwire b/protonwire index 22b4a33..7f65c9b 100755 --- a/protonwire +++ b/protonwire @@ -2463,7 +2463,7 @@ function main() { -h | --help | help) cmd_mode="HELP" ;; - --version) + --version|version) cmd_mode="VERSION" ;; --verbose | --debug | -v) diff --git a/scripts/goreleaser-wrapper b/scripts/build-script similarity index 90% rename from scripts/goreleaser-wrapper rename to scripts/build-script index e8a0c42..0768b7b 100755 --- a/scripts/goreleaser-wrapper +++ b/scripts/build-script @@ -34,9 +34,17 @@ elif [[ $1 == "build" ]]; then main.version=*) PROTONWIRE_VERSION="${1##*=}" ;; + -main.version|main.version) + shift + PROTONWIRE_VERSION="${1}" + ;; main.commit=*) PROTONWIRE_COMMIT="${1##*=}" ;; + -main.commit|main.commit) + shift + PROTONWIRE_COMMIT="${1}" + ;; -o=*) output_file="${1##*=}" ;; diff --git a/scripts/bump-patch-version b/scripts/bump-patch-version deleted file mode 100755 index 5f27152..0000000 --- a/scripts/bump-patch-version +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python3 - -""" -Script to bump patch version -""" - - -import argparse -import logging -import sys - -try: - import coloredlogs - coloredlogs.install( - level=logging.INFO, fmt="%(asctime)s [%(levelname)8s] %(message)s" - ) -except ImportError: - logging.basicConfig(level=logging.INFO) - -try: - import semantic_version -except ImportError: - logging.critical( - "Failed to import semantic_version package, install via pip semantic-version or " - "apt-get install python3-semantic-version package" - ) - sys.exit(1) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description=__doc__) - - parser.add_argument( - "version", - type=str, - metavar="VERSION", - default=None, - help="Current latest version", - ) - - args = parser.parse_args() - - try: - v = semantic_version.Version(args.version) - if v.prerelease is None or len(v.prerelease) == 0: - print(v.next_patch()) - else: - logging.error("Pre release versions are not supported %s", args.version) - sys.exit(1) - - except ValueError: - logging.error("Invalid semantic version %s", args.version) diff --git a/scripts/generate-server-metadata b/scripts/generate-server-metadata index 7ce8ec4..8b12ee8 100755 --- a/scripts/generate-server-metadata +++ b/scripts/generate-server-metadata @@ -586,7 +586,6 @@ class EnvDefault(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, values) - def write_metadata( api_url: str, username: str, @@ -622,6 +621,16 @@ def write_metadata( else: metadata_dir_json_v1_srv.mkdir(parents=True) + metadata_dir_json_v1_slsa_srv = metadata_dir_json_v1 / Path("slsa") / Path("server") + if metadata_dir_json_v1_slsa_srv.exists(): + if not metadata_dir_json_v1_slsa_srv.is_dir(): + logging.error( + "{metadata_dir_json_v1_slsa_srv} exits, but is not a directory" + ) + sys.exit(1) + else: + metadata_dir_json_v1_slsa_srv.mkdir(parents=True) + logging.info("Creating a API Session") try: s = Session(api_url=api_url) @@ -922,6 +931,32 @@ def write_metadata( json.dump(metadata_ts, f, indent=2) logging.info("Sever files - %d", stat_file_count) + # Generate hashes for all the server metadata files. + # There may be duplicates, so merge them into a list. + # Because leaking server names is not desired, each configuration + # file is hashed and the hash is added to the list and written to disk. + # slsa generators then hash the list of hashes and generate slsa provenance for it. + # Other builder stages will pick up the provenance and hash list and merge it into + # a single json file which may be uploaded to api endpoint. This is required + # to avoid race conditions where fetched hash list may not correspond to the provenance. + hash_list_sha256: List[str] = [] + logging.info("Generating metadata hashes") + with os.scandir(metadata_dir_json_v1_srv) as items: + for item in items: + if item.is_file(): + logging.debug("hashing file - %s", item.path) + with open(item.path, "rb") as f: + sha256_hex_digest = hashlib.sha256(f.read()).hexdigest() + if sha256_hex_digest not in hash_list_sha256: + logging.debug("adding hash(%s) to list", sha256_hex_digest) + hash_list_sha256.append(sha256_hex_digest) + else: + logging.debug("hash(%s) is already in the list", sha256_hex_digest) + hash_list_file=metadata_dir_json_v1_slsa_srv/Path("hash-list") + logging.info("Generating hash list: %s", hash_list_file) + with open(hash_list_file, "w", encoding="utf-8") as f: + f.write('\n'.join(hash_list_sha256)) + if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__)