diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 302c629..096ae87 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -46,3 +46,30 @@ jobs: with: name: "bin" path: "bin/" + + - name: Push to builds branch + run: | + # Capture the short SHA hash of the current commit + SHORT_SHA=$(git rev-parse --short HEAD) + + # Set Git user name and email for the commits made by this workflow + git config --global user.name "GitHub Actions" + git config --global user.email "actions@github.com" + + # Create a new orphan branch named 'builds' with no prior history + git checkout --orphan builds + + # Remove all existing files in the working directory + git rm -rf . + + # Reset to a clean state with no files in the working directory + git reset --hard + + # Add the contents of the 'bin' directory to the staging area + git add bin/ + + # Commit the build with a message including the short SHA hash + git commit -m "Build binaries - SHA: $SHORT_SHA" + + # Force push the 'builds' branch to the remote repository + git push origin +builds \ No newline at end of file diff --git a/.gitignore b/.gitignore index 23105b4..4a9772f 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,9 @@ go.work .DS_Store rfc.pdf -/alphalog \ No newline at end of file +/alphalog + +frontend/ +stuff_test.go +devcontainer.json +dependabot.yml \ No newline at end of file diff --git a/bin/main.wasm b/bin/main.wasm new file mode 100755 index 0000000..8f74617 Binary files /dev/null and b/bin/main.wasm differ diff --git a/cmd/fastly-monitor/main.go b/cmd/fastly-monitor/main.go new file mode 100644 index 0000000..1a6a72b --- /dev/null +++ b/cmd/fastly-monitor/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "log" + "os" + + "github.com/fastly/compute-sdk-go/fsthttp" + "itko.dev/internal/ctmonitor" +) + +func main() { + log.Println("FASTLY_SERVICE_VERSION:", os.Getenv("FASTLY_SERVICE_VERSION")) + fsthttp.ServeFunc(ctmonitor.FastlyServe) +} diff --git a/fastly.toml b/fastly.toml new file mode 100644 index 0000000..0fe12c7 --- /dev/null +++ b/fastly.toml @@ -0,0 +1,8 @@ +manifest_version = 3 +name = "ct2025-monitoring" +language = "go" +service_id = "X7QzVa4t0wXcAvoOMoXzA5" + +[scripts] +env_vars = ["GOARCH=wasm", "GOOS=wasip1"] +build = "go build -o bin/main.wasm ./cmd/fastly-monitor" \ No newline at end of file diff --git a/go.mod b/go.mod index 689181f..1d18bf4 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,7 @@ require ( github.com/docker/docker v27.0.3+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/fastly/compute-sdk-go v1.3.2 github.com/fatih/color v1.17.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect diff --git a/go.sum b/go.sum index 039dc38..c2f3ed7 100644 --- a/go.sum +++ b/go.sum @@ -87,6 +87,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fastly/compute-sdk-go v1.3.2 h1:9y49qzks/dH8QIPUuawfW8eKJRAOT1XY5hO9XP8xoCw= +github.com/fastly/compute-sdk-go v1.3.2/go.mod h1:AdOwbVc56tusuDLtufaO57NIzfoox4bgvRc460B/T8o= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= diff --git a/internal/ctmonitor/fastly.go b/internal/ctmonitor/fastly.go new file mode 100644 index 0000000..607131b --- /dev/null +++ b/internal/ctmonitor/fastly.go @@ -0,0 +1,133 @@ +package ctmonitor + +import ( + "context" + "errors" + "fmt" + "io" + "log" + "math/rand" + "net/url" + + "github.com/fastly/compute-sdk-go/configstore" + "github.com/fastly/compute-sdk-go/fsthttp" +) + +// The log needs two configs, the backend service name and the mask size. +// These can be fetched from edge config but for now we will hard code them. +const configStoreName = "hostmap" +const maskSize = 5 + +func FastlyServe(ctx context.Context, w fsthttp.ResponseWriter, r *fsthttp.Request) { + if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" || r.Method == "DELETE" { + w.WriteHeader(fsthttp.StatusMethodNotAllowed) + fmt.Fprintf(w, "This method is not allowed\n") + return + } + + config, err := configstore.Open(configStoreName) + if err != nil { + w.WriteHeader(fsthttp.StatusInternalServerError) + log.Println("Error opening config store: %v\n", err) + return + } + + backend, err := config.Get(r.Host) + if err != nil { + w.WriteHeader(fsthttp.StatusNotFound) + fmt.Fprintln(w, "Not found") + return + } + + s := &FastlyStorage{backend} + f := newFetch(s, maskSize) + + if r.URL.Path == "/ct/v1/get-sth-consistency" { + FastlyWrapper(f.get_sth_consistency)(ctx, w, r) + } else if r.URL.Path == "/ct/v1/get-proof-by-hash" { + FastlyWrapper(f.get_proof_by_hash)(ctx, w, r) + } else if r.URL.Path == "/ct/v1/get-entries" { + FastlyWrapper(f.get_entries)(ctx, w, r) + } else if r.URL.Path == "/ct/v1/get-entry-and-proof" { + FastlyWrapper(f.get_entry_and_proof)(ctx, w, r) + } else if r.URL.Path == "/" { + w.WriteHeader(fsthttp.StatusOK) + fmt.Fprintln(w, "OK\nhttps://github.com/aditsachde/itko?tab=readme-ov-file#public-instance") + } else { + path := r.URL.Path + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + + data, notfound, err := s.Get(ctx, r.URL.Path) + + if notfound { + w.WriteHeader(fsthttp.StatusNotFound) + fmt.Fprintln(w, "Not found") + return + } + if err != nil { + w.WriteHeader(fsthttp.StatusInternalServerError) + fmt.Fprintln(w, "Error fetching data") + return + } + + w.Header().Set("Content-Length", fmt.Sprintf("%d", len(data))) + w.Header().Set("Content-Type", "application/octet-stream") + w.Write(data) + } +} + +func FastlyWrapper(wrapped func(ctx context.Context, reqBody io.ReadCloser, query url.Values) (resp []byte, code int, err error)) func(c context.Context, w fsthttp.ResponseWriter, r *fsthttp.Request) { + return func(c context.Context, w fsthttp.ResponseWriter, r *fsthttp.Request) { + log.Println("Path:", r.URL.Path, "Method:", r.Method, "Query:", r.URL.Query()) + + query := r.URL.Query() + resp, code, err := wrapped(c, r.Body, query) + + if err != nil { + if code == fsthttp.StatusServiceUnavailable { + w.Header().Set("Retry-After", fmt.Sprintf("%d", 30+rand.Intn(60))) + fsthttp.Error(w, "pool full", code) + } else { + fsthttp.Error(w, err.Error(), code) + } + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + if _, err = w.Write(resp); err != nil { + log.Printf("Error writing response: %v", err) + } + } +} + +type FastlyStorage struct { + backend string +} + +func (f *FastlyStorage) Get(ctx context.Context, key string) (data []byte, notfounderr bool, err error) { + url := fmt.Sprintf("https://%s/%s", f.backend, key) + + req, err := fsthttp.NewRequest("GET", url, nil) + if err != nil { + return nil, false, err + } + resp, err := req.Send(ctx, f.backend) + if err != nil { + return nil, false, err + } + if resp.StatusCode != 200 { + if resp.StatusCode == 404 { + return nil, true, errors.New(fsthttp.StatusText(resp.StatusCode)) + } else { + return nil, false, errors.New(fsthttp.StatusText(resp.StatusCode)) + } + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, false, err + } + return body, false, nil +} diff --git a/pkg/ct2025-monitoring.tar.gz b/pkg/ct2025-monitoring.tar.gz new file mode 100644 index 0000000..460c9fd Binary files /dev/null and b/pkg/ct2025-monitoring.tar.gz differ