Skip to content

Commit 3a6148b

Browse files
authored
MEIER-320: Filter health check logs/traces and add Cloudflare WAF scanner block (#81)
* MEIER-320: Filter health check logs/traces and add Cloudflare WAF scanner block * MEIER-320: Extract shared zone lookups into zone.ts * MEIER-320: Expand WAF rule to cover all observed scanner paths
1 parent 3366de5 commit 3a6148b

File tree

6 files changed

+90
-47
lines changed

6 files changed

+90
-47
lines changed

app/src/App/src/Program.fs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ let configureTracerProvider (config: Config) =
2020
.AddSource(config.appName)
2121
.ConfigureResource(fun resourceBuilder ->
2222
resourceBuilder.AddService(serviceName = config.appName) |> ignore)
23-
.AddAspNetCoreInstrumentation()
23+
.AddAspNetCoreInstrumentation(fun opts ->
24+
opts.Filter <- fun ctx -> ctx.Request.Path.Value <> "/health")
2425
.AddHttpClientInstrumentation()
2526
.AddOtlpExporter(fun opts ->
2627
opts.Endpoint <- Uri(config.seq.endpoint + "/ingest/otlp/v1/traces")
@@ -54,6 +55,10 @@ let configureServices (serviceCollection: IServiceCollection) (tracerProvider: T
5455

5556
let configureApp (services: Services) (app: WebApplication) =
5657
app
58+
.UseSerilogRequestLogging(fun opts ->
59+
opts.GetLevel <- fun ctx _ _ ->
60+
if ctx.Request.Path.Value = "/health" then LogEventLevel.Verbose
61+
else LogEventLevel.Information)
5762
.UseStaticFiles()
5863
.UseGiraffe(Index.Handler.handler services)
5964

pulumi/src/cloudflare/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import './zone'
12
import './record'
23
import './redirect'
34
import './tunnel'
5+
import './waf'

pulumi/src/cloudflare/record.ts

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
11
import * as cloudflare from '@pulumi/cloudflare'
22
import { tunnelHostname } from './tunnel'
33
import { provider } from './provider'
4+
import { andymeierZone, andrewmeierZone, meiermadeZone } from './zone'
45
import * as config from '../config'
56

6-
const zone = cloudflare.getZoneOutput({
7-
filter: {
8-
account: {
9-
id: config.cloudflareConfig.accountId
10-
},
11-
name: 'andymeier.dev'
12-
}
13-
}, { provider })
14-
157
export const andymeier = new cloudflare.DnsRecord(config.identifier, {
168
name: '@',
17-
zoneId: zone.id,
9+
zoneId: andymeierZone.id,
1810
type: 'CNAME',
1911
content: tunnelHostname,
2012
proxied: true,
@@ -24,15 +16,6 @@ export const andymeier = new cloudflare.DnsRecord(config.identifier, {
2416
// DNS records for redirect domains — proxied A records pointing to a dummy IP
2517
// so Cloudflare can intercept requests and apply redirect rulesets.
2618

27-
const andrewmeierZone = cloudflare.getZoneOutput({
28-
filter: {
29-
account: {
30-
id: config.cloudflareConfig.accountId
31-
},
32-
name: 'andrewmeier.dev'
33-
}
34-
}, { provider })
35-
3619
new cloudflare.DnsRecord(`${config.identifier}-andrewmeier-root`, {
3720
name: '@',
3821
zoneId: andrewmeierZone.id,
@@ -51,15 +34,6 @@ new cloudflare.DnsRecord(`${config.identifier}-andrewmeier-www`, {
5134
ttl: 1
5235
}, { provider })
5336

54-
const meiermadeZone = cloudflare.getZoneOutput({
55-
filter: {
56-
account: {
57-
id: config.cloudflareConfig.accountId
58-
},
59-
name: 'meiermade.com'
60-
}
61-
}, { provider })
62-
6337
new cloudflare.DnsRecord(`${config.identifier}-meiermade-root`, {
6438
name: '@',
6539
zoneId: meiermadeZone.id,

pulumi/src/cloudflare/redirect.ts

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
11
import * as cloudflare from '@pulumi/cloudflare'
22
import { provider } from './provider'
3+
import { andrewmeierZone, meiermadeZone } from './zone'
34
import * as config from '../config'
45

56
// andrewmeier.dev -> andymeier.dev
6-
const andrewmeierZone = cloudflare.getZoneOutput({
7-
filter: {
8-
account: {
9-
id: config.cloudflareConfig.accountId
10-
},
11-
name: 'andrewmeier.dev'
12-
}
13-
}, { provider })
14-
157
new cloudflare.Ruleset(`${config.identifier}-andrewmeier-redirect`, {
168
zoneId: andrewmeierZone.id,
179
name: 'Redirect andrewmeier.dev to andymeier.dev',
@@ -36,15 +28,6 @@ new cloudflare.Ruleset(`${config.identifier}-andrewmeier-redirect`, {
3628
}, { provider })
3729

3830
// meiermade.com -> andymeier.dev/services
39-
const meiermadeZone = cloudflare.getZoneOutput({
40-
filter: {
41-
account: {
42-
id: config.cloudflareConfig.accountId
43-
},
44-
name: 'meiermade.com'
45-
}
46-
}, { provider })
47-
4831
new cloudflare.Ruleset(`${config.identifier}-meiermade-redirect`, {
4932
zoneId: meiermadeZone.id,
5033
name: 'Redirect meiermade.com to andymeier.dev/services',

pulumi/src/cloudflare/waf.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import * as cloudflare from '@pulumi/cloudflare'
2+
import { provider } from './provider'
3+
import { andymeierZone } from './zone'
4+
import * as config from '../config'
5+
6+
const expression = [
7+
// Sensitive dotfiles and directories
8+
'(http.request.uri.path contains "/.env")',
9+
'(http.request.uri.path contains "/.git")',
10+
'(http.request.uri.path contains "/.aws")',
11+
'(http.request.uri.path contains "/.ssh")',
12+
'(http.request.uri.path contains "/.terraform")',
13+
14+
// CMS and framework probes
15+
'(http.request.uri.path contains "/wp-")',
16+
'(http.request.uri.path contains "/wordpress")',
17+
'(http.request.uri.path contains "/xmlrpc")',
18+
'(http.request.uri.path contains "/phpMyAdmin")',
19+
'(http.request.uri.path contains "/phpmyadmin")',
20+
'(http.request.uri.path contains "/pma")',
21+
22+
// Admin and server management
23+
'(http.request.uri.path contains "/admin")',
24+
'(http.request.uri.path contains "/cgi-bin")',
25+
'(http.request.uri.path contains "/actuator")',
26+
'(http.request.uri.path contains "/solr")',
27+
'(http.request.uri.path contains "/telescope")',
28+
'(http.request.uri.path contains "/vendor")',
29+
'(http.request.uri.path contains "/invoker")',
30+
'(http.request.uri.path contains "/balancer-manager")',
31+
32+
// Credential and config probes
33+
'(http.request.uri.path contains "/credentials")',
34+
'(http.request.uri.path contains "/known_hosts")',
35+
'(http.request.uri.path contains "sendgrid")',
36+
'(http.request.uri.path contains "codecommit")',
37+
'(http.request.uri.path contains "/env.cfg")',
38+
39+
// Dangerous file extensions
40+
'(http.request.uri.path contains ".php")',
41+
'(http.request.uri.path contains ".asp")',
42+
'(http.request.uri.path contains ".jsp")',
43+
'(http.request.uri.path contains ".cgi")',
44+
'(http.request.uri.path contains ".yml")',
45+
'(http.request.uri.path contains ".xml")',
46+
'(http.request.uri.path contains ".bak")',
47+
'(http.request.uri.path contains ".rb")',
48+
].join(' or ')
49+
50+
new cloudflare.Ruleset(`${config.identifier}-waf`, {
51+
zoneId: andymeierZone.id,
52+
name: 'Block vulnerability scanners',
53+
kind: 'zone',
54+
phase: 'http_request_firewall_custom',
55+
rules: [{
56+
ref: 'block_scan_probes',
57+
description: 'Block common vulnerability scanner paths and file extensions',
58+
enabled: true,
59+
expression,
60+
action: 'block',
61+
}]
62+
}, { provider })

pulumi/src/cloudflare/zone.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as cloudflare from '@pulumi/cloudflare'
2+
import { provider } from './provider'
3+
import * as config from '../config'
4+
5+
const getZone = (name: string) =>
6+
cloudflare.getZoneOutput({
7+
filter: {
8+
account: {
9+
id: config.cloudflareConfig.accountId
10+
},
11+
name
12+
}
13+
}, { provider })
14+
15+
export const andymeierZone = getZone('andymeier.dev')
16+
export const andrewmeierZone = getZone('andrewmeier.dev')
17+
export const meiermadeZone = getZone('meiermade.com')

0 commit comments

Comments
 (0)