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
8 changes: 7 additions & 1 deletion .github/workflows/uptime.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
name: Uptime Monitor

on:
push:
branches:
- master
workflow_dispatch:

permissions:
Expand All @@ -18,9 +21,12 @@ jobs:

env:
SITE_URL: https://petabit-production.up.railway.app
MAX_ATTEMPTS: 3
MAX_ATTEMPTS: ${{ github.event_name == 'push' && '8' || '3' }}

steps:
- name: Allow Railway deployment to start
if: github.event_name == 'push'
run: sleep 90

- name: Wait for production liveness
run: |
Expand Down
20 changes: 20 additions & 0 deletions Petabit.Tests/ApplicationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,26 @@ public async Task IssRateLimitRejectsEleventhRequestFromSameClient()
Assert.Equal(HttpStatusCode.TooManyRequests, rejectedResponse.StatusCode);
}

[Fact]
public async Task GlobalRateLimitProtectsDynamicPagesButNotLiveness()
{
using var client = CreateClientWithIssResponse(
HttpStatusCode.OK,
"""{"latitude":45.81,"longitude":15.98,"velocity":27600}""");

for (var requestNumber = 1; requestNumber <= 120; requestNumber++)
{
using var response = await client.GetAsync("/Home/Privacy");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

using var rejectedResponse = await client.GetAsync("/Home/Privacy");
Assert.Equal(HttpStatusCode.TooManyRequests, rejectedResponse.StatusCode);

using var healthResponse = await client.GetAsync("/health/live");
Assert.Equal(HttpStatusCode.OK, healthResponse.StatusCode);
}

private HttpClient CreateClientWithIssResponse(HttpStatusCode statusCode, string content)
=> CreateClientWithHandler(new StubHttpMessageHandler(statusCode, content));

Expand Down
14 changes: 12 additions & 2 deletions Petabit/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ public static void Main(string[] args)
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
RateLimitPartition.GetFixedWindowLimiter(
context.Connection.RemoteIpAddress?.MapToIPv6().ToString() ?? "unknown-client",
_ => new FixedWindowRateLimiterOptions
{
PermitLimit = 120,
Window = TimeSpan.FromMinutes(1),
QueueLimit = 0,
AutoReplenishment = true
}));
options.AddPolicy("iss", context =>
RateLimitPartition.GetFixedWindowLimiter(
context.Connection.RemoteIpAddress?.MapToIPv6().ToString() ?? "unknown-client",
Expand Down Expand Up @@ -212,11 +222,11 @@ public static void Main(string[] args)
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false
});
}).DisableRateLimiting();
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = registration => registration.Tags.Contains("ready")
});
}).DisableRateLimiting();

app.MapControllerRoute(
name: "default",
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

[![CI](https://github.com/ChevCellios/Petabit/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/ChevCellios/Petabit/actions/workflows/ci.yml)
[![Security](https://github.com/ChevCellios/Petabit/actions/workflows/security.yml/badge.svg?branch=master)](https://github.com/ChevCellios/Petabit/actions/workflows/security.yml)
[![Production smoke test](https://github.com/ChevCellios/Petabit/actions/workflows/uptime.yml/badge.svg?branch=master)](https://github.com/ChevCellios/Petabit/actions/workflows/uptime.yml)
[![Station status freshness](https://github.com/ChevCellios/Petabit/actions/workflows/station-status-freshness.yml/badge.svg?branch=master)](https://github.com/ChevCellios/Petabit/actions/workflows/station-status-freshness.yml)
[![Monitored by Better Stack](https://img.shields.io/badge/uptime-Better%20Stack-5B45FF)](https://betterstack.com/uptime)
![.NET](https://img.shields.io/badge/.NET-10.0-blueviolet)
Expand Down Expand Up @@ -52,7 +53,8 @@ Izvori podataka:
- Content Security Policy s jednokratnim nonceom za inline skripte i stilove
- sigurnosna zaglavlja `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy` i `Permissions-Policy`
- antiforgery validacija za POST zahtjeve
- rate limiting ISS endpointa na 10 zahtjeva u minuti po IP adresi klijenta
- globalni rate limiting dinamičkih ruta na 120 zahtjeva u minuti po IP adresi klijenta
- stroži rate limiting ISS endpointa na 10 zahtjeva u minuti po IP adresi klijenta
- sigurno DOM renderiranje API podataka bez umetanja preko `innerHTML`
- culture cookie s atributima `HttpOnly`, `Secure` i `SameSite=Lax`
- analitika tek nakon izričite korisničke privole
Expand All @@ -63,11 +65,11 @@ Izvori podataka:
## 🧯 Pouzdanost i nadzor

- timeout, retry i circuit breaker za vanjski ISS servis
- `/health/live` provjera procesa i `/health/ready` provjera dostupnosti ISS servisa
- ručno pokretljiv GitHub produkcijski smoke test za ciljanu dijagnostiku
- `/health/live` provjera procesa i `/health/ready` provjera dostupnosti ISS servisa, izuzete iz rate limitinga
- automatski GitHub produkcijski smoke test nakon svakog pusha u `master`, uz ručno pokretanje za ciljanu dijagnostiku
- Better Stack provjera produkcijskog liveness endpointa svakih 10 minuta
- tjedna provjera starosti kuriranih podataka o ISS posadi i letjelicama
- ručni smoke test početne stranice, readiness endpointa i ISS trackera
- smoke test početne stranice, readiness endpointa i ISS trackera
- strukturirani JSON logovi u produkciji
- validirani `X-Correlation-ID` za povezivanje korisničkog zahtjeva s Railway logovima
- sigurna produkcijska error stranica bez izlaganja detalja iznimke
Expand All @@ -90,7 +92,7 @@ Grana `master` zaštićena je pull request pravilima i zahtijeva prolazak sljede
- `Audit NuGet dependencies`
- `Analyze C# with CodeQL`

Nakon mergea Railway automatski deploya novu verziju. Better Stack nadzire liveness svakih 10 minuta, a puni produkcijski smoke test pokreće se ručno kada je potrebna ciljana provjera.
Nakon mergea Railway automatski deploya novu verziju, a produkcijski smoke test čeka pokretanje deploya i potvrđuje dostupnost aplikacije. Better Stack dodatno nadzire liveness svakih 10 minuta.

## ✅ Testiranje

Expand Down
Loading