Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions infra/caddy/Caddyfile.prod
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,50 @@ oullin.io {
respond 403
}

# --- Browser-facing signature relay
@relay_signature_cors path /relay/generate-signature*
header @relay_signature_cors Access-Control-Allow-Origin "https://oullin.io"
header @relay_signature_cors Access-Control-Allow-Methods "POST, OPTIONS"
header @relay_signature_cors Access-Control-Allow-Headers "X-API-Key, X-API-Username, X-API-Signature, X-API-Timestamp, X-API-Nonce, X-Request-ID, Content-Type, User-Agent, If-None-Match, X-API-Intended-Origin"
header @relay_signature_cors Vary "Origin"

@relay_signature_preflight {
path /relay/generate-signature*
method OPTIONS
}
handle @relay_signature_preflight {
header Access-Control-Max-Age "86400"
respond 204
}

@relay_signature_post {
path /relay/generate-signature*
method POST
}
handle @relay_signature_post {
uri strip_prefix /relay
reverse_proxy api:8080 {
header_up Host {host}

header_up X-API-Username {http.request.header.X-API-Username}
header_up X-API-Key {http.request.header.X-API-Key}
header_up X-API-Timestamp {http.request.header.X-API-Timestamp}
header_up X-Request-ID {http.request.header.X-Request-ID}
header_up Content-Type {http.request.header.Content-Type}
header_up User-Agent {http.request.header.User-Agent}
header_up X-API-Intended-Origin {http.request.header.X-API-Intended-Origin}

transport http {
dial_timeout 10s
response_header_timeout 30s
}
}
}

handle /relay/generate-signature* {
respond 405
}

@preflight {
method OPTIONS
header Origin *
Expand Down
37 changes: 35 additions & 2 deletions infra/caddy/caddyfile_prod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ func protectedPublicPaths(caddyfile string) map[string]bool {
for _, path := range fields[2:] {
paths[path] = true
}

return paths
}

return paths
Expand Down Expand Up @@ -93,6 +91,41 @@ func TestProdCaddyfileBlocksPublicSignatureEndpoint(t *testing.T) {
}
}

func TestProdCaddyfileHandlesBrowserSignatureRelayAtEdge(t *testing.T) {
caddyfile := stripCaddyComments(readProdCaddyfile(t))

relayContract := regexp.MustCompile(`(?s)` +
`@relay_signature_cors\s+path\s+/relay/generate-signature\*.*?` +
`header\s+@relay_signature_cors\s+Access-Control-Allow-Origin\s+"https://oullin\.io".*?` +
`header\s+@relay_signature_cors\s+Access-Control-Allow-Methods\s+"POST, OPTIONS".*?` +
`header\s+@relay_signature_cors\s+Access-Control-Allow-Headers\s+"X-API-Key, X-API-Username, X-API-Signature, X-API-Timestamp, X-API-Nonce, X-Request-ID, Content-Type, User-Agent, If-None-Match, X-API-Intended-Origin".*?` +
`@relay_signature_preflight\s*{\s*` +
`path\s+/relay/generate-signature\*\s+` +
`method\s+OPTIONS\s*` +
`}.*?` +
`handle\s+@relay_signature_preflight\s*{\s*` +
`header\s+Access-Control-Max-Age\s+"86400"\s+` +
`respond\s+204\s*` +
`}.*?` +
`@relay_signature_post\s*{\s*` +
`path\s+/relay/generate-signature\*\s+` +
`method\s+POST\s*` +
`}.*?` +
`handle\s+@relay_signature_post\s*{\s*` +
`uri\s+strip_prefix\s+/relay\s+` +
`reverse_proxy\s+api:8080\s*{.*?` +
`header_up\s+X-API-Intended-Origin\s+\{http\.request\.header\.X-API-Intended-Origin\}.*?` +
`}`)

if !relayContract.MatchString(caddyfile) {
t.Fatal("expected /relay/generate-signature* to be handled by the public edge and proxied to api:8080")
}

if !regexp.MustCompile(`handle\s+/relay/generate-signature\*\s*{\s*respond\s+405\s*}`).MatchString(caddyfile) {
t.Fatal("expected unsupported relay signature methods to respond with 405")
}
}

func TestProdCaddyfileKeepsSignatureEndpointBehindMTLS(t *testing.T) {
caddyfile := readProdCaddyfile(t)
mtlsBlock, ok := caddyBlock(stripCaddyComments(caddyfile), ":8443")
Expand Down
Loading