Skip to content

Commit c806fe5

Browse files
committed
fix: restore Go CI security checks
1 parent 723c769 commit c806fe5

4 files changed

Lines changed: 102 additions & 4 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ scripts/*
174174
!scripts/.handler-db-access-allowlist
175175
!scripts/openapi-convert/
176176
!scripts/openapi-convert/**
177+
!scripts/openapi-v2-check/
178+
!scripts/openapi-v2-check/**
177179
.golangci-report.json
178180
blog/
179181
.fallow/

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ require (
2727
github.com/go-webauthn/webauthn v0.17.4
2828
github.com/golang-jwt/jwt/v5 v5.3.1
2929
github.com/google/jsonschema-go v0.4.2
30+
github.com/google/uuid v1.6.0
3031
github.com/gorilla/securecookie v1.1.2
3132
github.com/grafana/sobek v0.0.0-20260406180825-6d789dcdd177
3233
github.com/jimlambrt/gldap v0.1.14
@@ -44,7 +45,7 @@ require (
4445
github.com/teambition/rrule-go v1.8.2
4546
github.com/yuin/goldmark v1.7.17
4647
github.com/zitadel/oidc/v3 v3.45.3
47-
golang.org/x/crypto v0.55.0
48+
golang.org/x/crypto v0.56.0
4849
golang.org/x/image v0.45.0
4950
golang.org/x/sync v0.22.0
5051
golang.org/x/text v0.41.0
@@ -133,7 +134,6 @@ require (
133134
github.com/google/go-tpm v0.9.8 // indirect
134135
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
135136
github.com/google/s2a-go v0.1.9 // indirect
136-
github.com/google/uuid v1.6.0 // indirect
137137
github.com/googleapis/enterprise-certificate-proxy v0.3.20 // indirect
138138
github.com/googleapis/gax-go/v2 v2.23.0 // indirect
139139
github.com/gorilla/css v1.0.1 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -485,8 +485,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
485485
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
486486
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
487487
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
488-
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
489-
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
488+
golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
489+
golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
490490
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
491491
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
492492
golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0=

scripts/openapi-v2-check/main.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"flag"
6+
"fmt"
7+
"net/http"
8+
"os"
9+
"strings"
10+
11+
v2 "windshift/internal/restapi/v2"
12+
)
13+
14+
type document struct {
15+
OpenAPI string `json:"openapi"`
16+
Paths map[string]map[string]operation `json:"paths"`
17+
}
18+
19+
type operation struct {
20+
Security []map[string][]string `json:"security"`
21+
}
22+
23+
func main() {
24+
specPath := flag.String("spec", "api/openapi-v2.json", "v2 OpenAPI JSON path")
25+
flag.Parse()
26+
27+
data, err := os.ReadFile(*specPath)
28+
if err != nil {
29+
fail("read spec: %v", err)
30+
}
31+
var spec document
32+
if err := json.Unmarshal(data, &spec); err != nil {
33+
fail("decode spec: %v", err)
34+
}
35+
if !strings.HasPrefix(spec.OpenAPI, "3.") {
36+
fail("openapi version %q is not 3.x", spec.OpenAPI)
37+
}
38+
39+
want := make(map[string]v2.Route)
40+
for _, route := range v2.Inventory() {
41+
key := strings.ToLower(route.Method) + " " + route.Path
42+
want[key] = route
43+
item, ok := spec.Paths[route.Path]
44+
if !ok {
45+
fail("route %s is missing", key)
46+
}
47+
operation, ok := item[strings.ToLower(route.Method)]
48+
if !ok {
49+
fail("route %s is missing", key)
50+
}
51+
validateSecurity(route, operation)
52+
}
53+
54+
for path, item := range spec.Paths {
55+
for method := range item {
56+
if !isHTTPMethod(method) {
57+
continue
58+
}
59+
key := method + " " + path
60+
if _, ok := want[key]; !ok {
61+
fail("OpenAPI operation %s is not in the v2 inventory", key)
62+
}
63+
}
64+
}
65+
fmt.Printf("API v2 OpenAPI parity is valid (%d operations).\n", len(want))
66+
}
67+
68+
func validateSecurity(route v2.Route, operation operation) {
69+
if route.Auth == v2.AuthPublic {
70+
if len(operation.Security) != 0 {
71+
fail("public route %s %s declares security", route.Method, route.Path)
72+
}
73+
return
74+
}
75+
if len(operation.Security) != 1 {
76+
fail("authenticated route %s %s must declare one security requirement", route.Method, route.Path)
77+
}
78+
scopes, ok := operation.Security[0]["BearerAuth"]
79+
if !ok || strings.Join(scopes, "\x00") != strings.Join(route.Scopes, "\x00") {
80+
fail("route %s %s scopes do not match its inventory", route.Method, route.Path)
81+
}
82+
}
83+
84+
func isHTTPMethod(method string) bool {
85+
switch strings.ToUpper(method) {
86+
case http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodHead, http.MethodOptions:
87+
return true
88+
default:
89+
return false
90+
}
91+
}
92+
93+
func fail(format string, args ...any) {
94+
fmt.Fprintf(os.Stderr, "openapi-v2-check: "+format+"\n", args...)
95+
os.Exit(1)
96+
}

0 commit comments

Comments
 (0)