using System.Globalization; using System.Net; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using OpenTelemetry.Instrumentation.AspNetCore; using OpenTelemetry.Instrumentation.Runtime; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; using Serilog; using Serilog.Events; using StellaOps.AirGap.Policy; using StellaOps.Auth.Abstractions; using StellaOps.Auth.ServerIntegration; using StellaOps.Configuration; using StellaOps.Telemetry.Core; using StellaOps.Registry.TokenService; using StellaOps.Registry.TokenService.Admin; using StellaOps.Registry.TokenService.Observability; var builder = WebApplication.CreateBuilder(args); builder.Configuration.AddStellaOpsDefaults(options => { options.BasePath = builder.Environment.ContentRootPath; options.EnvironmentPrefix = "REGISTRY_TOKEN_"; options.ConfigureBuilder = configurationBuilder => { configurationBuilder.AddYamlFile("../etc/registry-token.yaml", optional: true, reloadOnChange: true); }; }); var bootstrapOptions = builder.Configuration.BindOptions( RegistryTokenServiceOptions.SectionName, (opts, _) => opts.Validate()); builder.Host.UseSerilog((context, services, loggerConfiguration) => { loggerConfiguration .MinimumLevel.Information() .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Console(); }); builder.Services.AddOptions() .Bind(builder.Configuration.GetSection(RegistryTokenServiceOptions.SectionName)) .PostConfigure(options => options.Validate()) .ValidateOnStart(); builder.Services.AddSingleton(TimeProvider.System); builder.Services.AddSingleton(); builder.Services.AddSingleton(sp => { var options = sp.GetRequiredService>().Value; return new PlanRegistry(options); }); builder.Services.AddSingleton(); // Plan Admin API dependencies builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHealthChecks().AddCheck("self", () => Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Healthy()); builder.Services.AddAirGapEgressPolicy(builder.Configuration); builder.Services.AddStellaOpsTelemetry( builder.Configuration, serviceName: "StellaOps.Registry.TokenService", configureMetrics: metricsBuilder => { metricsBuilder.AddRuntimeInstrumentation(); metricsBuilder.AddMeter(RegistryTokenMetrics.MeterName); }, configureTracing: tracerBuilder => { tracerBuilder.AddAspNetCoreInstrumentation(); tracerBuilder.AddHttpClientInstrumentation(); }); builder.Services.AddStellaOpsResourceServerAuthentication( builder.Configuration, configurationSection: null, configure: resourceOptions => { resourceOptions.Authority = bootstrapOptions.Authority.Issuer; resourceOptions.RequireHttpsMetadata = bootstrapOptions.Authority.RequireHttpsMetadata; resourceOptions.MetadataAddress = bootstrapOptions.Authority.MetadataAddress; resourceOptions.Audiences.Clear(); foreach (var audience in bootstrapOptions.Authority.Audiences) { resourceOptions.Audiences.Add(audience); } }); builder.Services.AddAuthorization(options => { var scopes = bootstrapOptions.Authority.RequiredScopes.Count == 0 ? new[] { "registry.token.issue" } : bootstrapOptions.Authority.RequiredScopes.ToArray(); options.AddPolicy("registry.token.issue", policy => { policy.RequireAuthenticatedUser(); policy.Requirements.Add(new StellaOpsScopeRequirement(scopes)); policy.AddAuthenticationSchemes(StellaOpsAuthenticationDefaults.AuthenticationScheme); }); // Admin policy for plan management options.AddPolicy("registry.admin", policy => { policy.RequireAuthenticatedUser(); policy.Requirements.Add(new StellaOpsScopeRequirement(["registry.admin"])); policy.AddAuthenticationSchemes(StellaOpsAuthenticationDefaults.AuthenticationScheme); }); }); var app = builder.Build(); app.UseSerilogRequestLogging(); app.UseAuthentication(); app.UseAuthorization(); app.MapHealthChecks("/healthz"); // Plan Admin API endpoints app.MapPlanAdminEndpoints(); app.MapGet("/token", ( HttpContext context, [FromServices] IOptions options, [FromServices] RegistryTokenIssuer issuer) => { var serviceOptions = options.Value; var service = context.Request.Query["service"].FirstOrDefault()?.Trim(); if (string.IsNullOrWhiteSpace(service)) { return Results.Problem( detail: "The 'service' query parameter is required.", statusCode: StatusCodes.Status400BadRequest); } if (serviceOptions.Registry.AllowedServices.Count > 0 && !serviceOptions.Registry.AllowedServices.Contains(service, StringComparer.OrdinalIgnoreCase)) { return Results.Problem( detail: "The requested registry service is not permitted for this installation.", statusCode: StatusCodes.Status403Forbidden); } IReadOnlyList accessRequests; try { accessRequests = RegistryScopeParser.Parse(context.Request.Query); } catch (InvalidScopeException ex) { return Results.Problem( detail: ex.Message, statusCode: StatusCodes.Status400BadRequest); } if (accessRequests.Count == 0) { return Results.Problem( detail: "At least one scope must be requested.", statusCode: StatusCodes.Status400BadRequest); } try { var response = issuer.IssueToken(context.User, service, accessRequests); return Results.Json(new { token = response.Token, expires_in = response.ExpiresIn, issued_at = response.IssuedAt.UtcDateTime.ToString("O", CultureInfo.InvariantCulture), issued_token_type = "urn:ietf:params:oauth:token-type:access_token" }); } catch (RegistryTokenException ex) { return Results.Problem( detail: ex.Message, statusCode: StatusCodes.Status403Forbidden); } }) .WithName("GetRegistryToken") .RequireAuthorization("registry.token.issue") .Produces(StatusCodes.Status200OK) .ProducesProblem(StatusCodes.Status400BadRequest) .ProducesProblem(StatusCodes.Status403Forbidden); app.Run();