Files
satellite-provider/SatelliteProvider.IntegrationTests/PerfBootstrap.cs
T
Oleksandr Bezdieniezhnykh f979e18811 [AZ-494] Enable JWT iss/aud validation with fail-fast startup
Option B per user decision: production ships with empty Jwt.Issuer /
Jwt.Audience in appsettings.json so the API process refuses to start
unless JWT_ISSUER + JWT_AUDIENCE env vars are supplied. Development
ships with grep-friendly DEV-ONLY- placeholders so local + docker
flows keep working unchanged.

AuthenticationServiceCollectionExtensions flips ValidateIssuer +
ValidateAudience to true and wires ValidIssuer / ValidAudience via a
new ResolveRequiredOrThrow helper that all three required values
(secret, iss, aud) now share. JwtTokenFactory.Create + CreateExpired
gain optional iss / aud parameters (default null) so existing call
sites compile unchanged. JwtTestHelpers adds MintAuthenticated /
MintExpired wrappers that resolve iss + aud from env, plus
ResolveIssuerOrThrow / ResolveAudienceOrThrow. PerfBootstrap.MintToken
+ Program.cs JWT bootstrap migrated to the new surface so the perf
harness and the integration runner both validate against the same
contract.

Adds 4 fail-fast unit tests (missing/empty issuer + audience), 2
negative integration scenarios (WrongIssuer_Returns401,
WrongAudience_Returns401), and re-tags every existing integration
mint site via MintAuthenticated.

Compose, .env.example, run-tests.sh, run-performance-tests.sh all
load + export JWT_ISSUER + JWT_AUDIENCE alongside JWT_SECRET.

Resolves F-AUTH-2 (security_report.md + owasp_review.md). AC-7
(cross-repo suite/_docs/10_auth.md write) deferred — outside this
workspace; tracked in deploy_cycle2.md R3 follow-up.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 02:28:48 +03:00

98 lines
3.3 KiB
C#

using System.Security.Claims;
using SatelliteProvider.TestSupport;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
namespace SatelliteProvider.IntegrationTests;
// AZ-492: bootstrap helpers invoked by scripts/run-performance-tests.sh.
// Each helper is a short-circuit subcommand that prints/writes its output
// and exits before the integration-test runner does any HTTP or DB work.
// All token-minting goes through the canonical JwtTokenFactory (AZ-491)
// so the shell script does NOT inline a third copy of the JWT logic.
internal static class PerfBootstrap
{
public const string PerfSubject = "perf-tests";
public const string GpsPermission = "GPS";
public const string PermissionsClaimType = "permissions";
public static readonly TimeSpan PerfTokenLifetime = TimeSpan.FromHours(4);
public static int MintToken()
{
string secret;
string issuer;
string audience;
try
{
secret = JwtTestHelpers.ResolveSecretOrThrow();
issuer = JwtTestHelpers.ResolveIssuerOrThrow();
audience = JwtTestHelpers.ResolveAudienceOrThrow();
}
catch (InvalidOperationException ex)
{
Console.Error.WriteLine($"--mint-only: {ex.Message}");
return 1;
}
var token = JwtTokenFactory.Create(
secret,
PerfSubject,
PerfTokenLifetime,
new[] { new Claim(PermissionsClaimType, GpsPermission) },
issuer: issuer,
audience: audience);
Console.Out.Write(token);
return 0;
}
public static int GenerateUavFixture(string[] args)
{
if (args.Length < 2 || string.IsNullOrWhiteSpace(args[1]))
{
Console.Error.WriteLine("--gen-uav-fixture: missing output path. Usage: --gen-uav-fixture <path>");
return 2;
}
var path = args[1];
var directory = Path.GetDirectoryName(Path.GetFullPath(path));
if (!string.IsNullOrEmpty(directory))
{
Directory.CreateDirectory(directory);
}
var bytes = CreateValidJpeg();
File.WriteAllBytes(path, bytes);
Console.Out.WriteLine(path);
return 0;
}
// Mirrors the random-noise JPEG produced by UavUploadTests.CreateValidJpeg so
// that the perf harness exercises the same quality-gate path as the integration
// tests. Pixel pattern is high-variance enough to pass the UAV quality gate.
private static byte[] CreateValidJpeg(int width = 256, int height = 256, int seed = 42)
{
using var image = new Image<Rgba32>(width, height);
var random = new Random(seed);
image.ProcessPixelRows(accessor =>
{
for (var y = 0; y < accessor.Height; y++)
{
var row = accessor.GetRowSpan(y);
for (var x = 0; x < row.Length; x++)
{
row[x] = new Rgba32(
(byte)random.Next(256),
(byte)random.Next(256),
(byte)random.Next(256));
}
}
});
using var stream = new MemoryStream();
image.Save(stream, new JpegEncoder { Quality = 95 });
return stream.ToArray();
}
}