diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..1921a67 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,55 @@ +name: Checks + +on: + push: + branches: + - main + pull_request: + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: ^1.23 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Run unit tests and generate the coverage report + run: make test-race + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: ^1.23 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Install gofumpt + run: go install mvdan.cc/gofumpt@v0.4.0 + + - name: Install staticcheck + run: go install honnef.co/go/tools/cmd/staticcheck@2024.1.1 + + - name: Install golangci-lint + run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.61.0 + + - name: Lint + run: make lint + + - name: Ensure go mod tidy runs without changes + run: | + go mod tidy + git update-index -q --really-refresh + git diff-index HEAD diff --git a/.gitignore b/.gitignore index 0037b51..f051ada 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /constellation/ /cvm-reverse-proxy -/measurements.json \ No newline at end of file +/measurements.json +/build/ \ No newline at end of file diff --git a/Makefile b/Makefile index 14137fe..a0e6f82 100644 --- a/Makefile +++ b/Makefile @@ -1,65 +1,87 @@ +# Heavily inspired by Lighthouse: https://github.com/sigp/lighthouse/blob/stable/Makefile +# and Reth: https://github.com/paradigmxyz/reth/blob/main/Makefile +.DEFAULT_GOAL := help + VERSION := $(shell git describe --tags --always --dirty="-dev") -.PHONY: all -all: clean build-proxy-client build-proxy-server +##@ Help + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) .PHONY: v -v: +v: ## Show the version @echo "Version: ${VERSION}" +##@ Build + .PHONY: clean -clean: +clean: ## Clean the build directory rm -rf build/ +.PHONY: build +build: clean build-proxy-client build-proxy-server ## Build the proxy client and server + .PHONY: build-proxy-client -build-proxy-client: +build-proxy-client: ## Build the proxy client @mkdir -p ./build go build -trimpath -ldflags "-X cvm-reverse-proxy/common.Version=${VERSION}" -v -o ./build/proxy-client cmd/proxy-client/main.go .PHONY: build-proxy-server -build-proxy-server: +build-proxy-server: ## Build the proxy server @mkdir -p ./build go build -trimpath -ldflags "-X cvm-reverse-proxy/common.Version=${VERSION}" -v -o ./build/proxy-server cmd/proxy-server/main.go +##@ Test & Development + .PHONY: test -test: - go test ./... +test: ## Run tests + go test ./cmd/... ./common/... ./proxy/... .PHONY: test-race -test-race: - go test -race ./... +test-race: ## Run tests with race detector + go test -race ./cmd/... ./common/... ./proxy/... .PHONY: lint -lint: +lint: ## Run linters gofmt -d -s cmd common proxy gofumpt -d -extra cmd common proxy go vet ./cmd/... ./common/... ./proxy/... - # staticcheck ./... // complains about 1.22.4 - golangci-lint run --exclude-dirs internal - # nilaway ./cmd/... ./common/... ./proxy/... // incorrect findings + staticcheck ./cmd/... ./common/... ./proxy/... + # golangci-lint run --exclude-dirs internal --exclude-dirs-use-default=false .PHONY: fmt -fmt: +fmt: ## Format the code gofmt -s -w cmd common proxy gci write cmd common proxy gofumpt -w -extra cmd common proxy go mod tidy .PHONY: gofumpt -gofumpt: +gofumpt: ## Run gofumpt gofumpt -l -w -extra cmd common proxy -.PHONY: lt +.PHONY: lt ## Alias for lint and test lt: lint test .PHONY: cover -cover: +cover: ## Run tests with coverage go test -coverprofile=/tmp/go-sim-lb.cover.tmp ./... go tool cover -func /tmp/go-sim-lb.cover.tmp unlink /tmp/go-sim-lb.cover.tmp .PHONY: cover-html -cover-html: +cover-html: ## Run tests with coverage and open the HTML report go test -coverprofile=/tmp/go-sim-lb.cover.tmp ./... go tool cover -html=/tmp/go-sim-lb.cover.tmp unlink /tmp/go-sim-lb.cover.tmp + +.PHONY: docker-images +docker-images: ## Build the Docker images + DOCKER_BUILDKIT=1 docker build \ + --platform linux/amd64 \ + --build-arg VERSION=${VERSION} \ + --file proxy-server.dockerfile \ + --tag cvm-proxy-server \ + . diff --git a/cmd/proxy-client/main.go b/cmd/proxy-client/main.go index 148f223..b01eb4b 100644 --- a/cmd/proxy-client/main.go +++ b/cmd/proxy-client/main.go @@ -8,6 +8,7 @@ import ( "cvm-reverse-proxy/common" "cvm-reverse-proxy/internal/atls" "cvm-reverse-proxy/proxy" + "github.com/urfave/cli/v2" // imports as package "cli" ) @@ -53,7 +54,7 @@ func main() { Name: "proxy-client", Usage: "Serve API, and metrics", Flags: flags, - Action: run_client, + Action: runClient, } if err := app.Run(os.Args); err != nil { @@ -61,7 +62,7 @@ func main() { } } -func run_client(cCtx *cli.Context) error { +func runClient(cCtx *cli.Context) error { listenAddr := cCtx.String("listen-addr") targetAddr := cCtx.String("target-addr") serverMeasurements := cCtx.String("server-measurements") diff --git a/cmd/proxy-server/main.go b/cmd/proxy-server/main.go index 52fa4dd..380bec1 100644 --- a/cmd/proxy-server/main.go +++ b/cmd/proxy-server/main.go @@ -13,6 +13,7 @@ import ( "cvm-reverse-proxy/common" "cvm-reverse-proxy/internal/atls" "cvm-reverse-proxy/proxy" + "github.com/urfave/cli/v2" // imports as package "cli" ) @@ -58,7 +59,7 @@ func main() { Name: "proxy-server", Usage: "Serve API, and metrics", Flags: flags, - Action: run_server, + Action: runServer, } if err := app.Run(os.Args); err != nil { @@ -66,7 +67,7 @@ func main() { } } -func run_server(cCtx *cli.Context) error { +func runServer(cCtx *cli.Context) error { listenAddr := cCtx.String("listen-addr") targetAddr := cCtx.String("target-addr") clientMeasurements := cCtx.String("client-measurements") diff --git a/common/logging.go b/common/logging.go index 9f55b55..8dd423d 100644 --- a/common/logging.go +++ b/common/logging.go @@ -1,3 +1,4 @@ +// Package common contains shared utilities package common import ( diff --git a/proxy-server.dockerfile b/proxy-server.dockerfile new file mode 100644 index 0000000..2f367b9 --- /dev/null +++ b/proxy-server.dockerfile @@ -0,0 +1,23 @@ +# syntax=docker/dockerfile:1 +FROM golang:1.23 AS builder +ARG VERSION +WORKDIR /build +ADD go.mod /build/ +RUN --mount=type=cache,target=/root/.cache/go-build CGO_ENABLED=0 GOOS=linux \ + go mod download +ADD . /build/ +RUN --mount=type=cache,target=/root/.cache/go-build CGO_ENABLED=0 GOOS=linux \ + go build \ + -trimpath \ + -ldflags "-s -X main.version=${VERSION}" \ + -v \ + -o proxy-server \ + cmd/proxy-server/main.go + +FROM alpine:latest +WORKDIR /app +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=builder /build/proxy-server /app/proxy-server +ENV LISTEN_ADDR=":8080" +EXPOSE 8080 +CMD ["/app/proxy-server"] diff --git a/proxy/atls_config.go b/proxy/atls_config.go index 646420e..c1948f6 100644 --- a/proxy/atls_config.go +++ b/proxy/atls_config.go @@ -1,3 +1,4 @@ +// Package proxy contains the core proxy functionality and aTLS configuration package proxy import ( @@ -86,7 +87,7 @@ func ExtractMeasurementsFromExtension(ext *pkix.Extension, v variant.Variant) (m } return measurements, nil default: - return nil, errors.New("unsupported ATLS variant!") + return nil, errors.New("unsupported ATLS variant") } } diff --git a/proxy/mutli_validator.go b/proxy/mutli_validator.go index 12f7ad1..42716ee 100644 --- a/proxy/mutli_validator.go +++ b/proxy/mutli_validator.go @@ -7,7 +7,7 @@ import ( "cvm-reverse-proxy/internal/atls" ) -// Validator for Azure confidential VM attestation using TDX which accepts multiple measurements +// MultiValidator is a validator for Azure confidential VM attestation using TDX which accepts multiple measurements type MultiValidator struct { oid asn1.ObjectIdentifier validators []atls.Validator diff --git a/proxy/proxy.go b/proxy/proxy.go index b94bacb..256b7ad 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -22,8 +22,8 @@ type Proxy struct { validatorOIDs []asn1.ObjectIdentifier } -func NewProxy(targetUrl string, validators []atls.Validator) *Proxy { - target, err := url.Parse(targetUrl) +func NewProxy(targetURL string, validators []atls.Validator) *Proxy { + target, err := url.Parse(targetURL) if err != nil { panic(err) } @@ -46,7 +46,7 @@ func NewProxy(targetUrl string, validators []atls.Validator) *Proxy { } if res.TLS != nil { - err, _ := proxy.copyMeasurementsToHeader(res.TLS, &res.Header) + _, err := proxy.copyMeasurementsToHeader(res.TLS, &res.Header) return err } @@ -75,7 +75,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.TLS != nil { // Forwards validated measurement to the *proxied-to service* - err, errStatus := p.copyMeasurementsToHeader(r.TLS, &r.Header) + errStatus, err := p.copyMeasurementsToHeader(r.TLS, &r.Header) if err != nil { http.Error(w, err.Error(), errStatus) return @@ -85,7 +85,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { p.proxy.ServeHTTP(w, r) } -func (p *Proxy) copyMeasurementsToHeader(conn *tls.ConnectionState, header *http.Header) (error, int) { +func (p *Proxy) copyMeasurementsToHeader(conn *tls.ConnectionState, header *http.Header) (int, error) { // In verifyEmbeddedReport which is used to validate the extensions, only the first matching extension is validated! Refuse to accept multiple var ATLSExtension *pkix.Extension = nil for _, cert := range conn.PeerCertificates { @@ -93,7 +93,7 @@ func (p *Proxy) copyMeasurementsToHeader(conn *tls.ConnectionState, header *http for _, validatorOID := range p.validatorOIDs { if ext.Id.Equal(validatorOID) { if ATLSExtension != nil { - return errors.New("more than one ATLS extension provided, refusing to continue"), http.StatusBadRequest + return http.StatusBadRequest, errors.New("more than one ATLS extension provided, refusing to continue") } ATLSExtension = &ext } @@ -102,24 +102,24 @@ func (p *Proxy) copyMeasurementsToHeader(conn *tls.ConnectionState, header *http } if ATLSExtension == nil { - return nil, 0 + return 0, nil } atlsVariant, err := variant.FromOID(ATLSExtension.Id) if err != nil { - return errors.New("could not get ATLS variant back from a matched extension"), http.StatusTeapot + return http.StatusTeapot, errors.New("could not get ATLS variant back from a matched extension") } measurements, err := ExtractMeasurementsFromExtension(ATLSExtension, atlsVariant) if err != nil { - return errors.New("could not extract measurement from tls extension"), http.StatusTeapot + return http.StatusTeapot, errors.New("could not extract measurement from tls extension") } marshaledPcrs, err := json.Marshal(measurements) if err != nil { - return errors.New("could not marshal measurement extracted from tls extension"), http.StatusInternalServerError + return http.StatusInternalServerError, errors.New("could not marshal measurement extracted from tls extension") } header.Set("X-Flashbots-Cert-Extensions-"+ATLSExtension.Id.String(), string(marshaledPcrs)) - return nil, 0 + return 0, nil }