Skip to content

Commit f9b2e41

Browse files
add auth debug
1 parent cfdb050 commit f9b2e41

1 file changed

Lines changed: 78 additions & 0 deletions

File tree

ApiController.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Text;
2+
using System.Text.Json;
13
using GithubActionsOrchestrator.Auth;
24
using GithubActionsOrchestrator.Database;
35
using GithubActionsOrchestrator.Models;
@@ -678,6 +680,82 @@ public IResult Health()
678680
return Results.Ok(new { status = "healthy" });
679681
}
680682

683+
// Debug helper for tuning Teleport auth. Decodes the incoming JWT WITHOUT verifying
684+
// its signature so you can read the real iss/aud/roles/sub claim names/values even
685+
// when validation is currently failing, and compares them to what's configured.
686+
// Only ever exposes the caller's OWN token. API-key gated like the rest of /api.
687+
[Route("auth-debug")]
688+
[HttpGet]
689+
public IResult AuthDebug()
690+
{
691+
var cfg = Program.Config.TeleportAuth;
692+
string raw = Request.Headers[TeleportAuth.JwtHeader].FirstOrDefault();
693+
694+
object tokenHeader = null;
695+
object tokenClaims = null;
696+
string decodeError = null;
697+
if (!string.IsNullOrEmpty(raw))
698+
{
699+
try
700+
{
701+
var parts = raw.Split('.');
702+
if (parts.Length >= 2)
703+
{
704+
tokenHeader = JsonSerializer.Deserialize<JsonElement>(DecodeJwtSegment(parts[0]));
705+
tokenClaims = JsonSerializer.Deserialize<JsonElement>(DecodeJwtSegment(parts[1]));
706+
}
707+
else
708+
{
709+
decodeError = "Header present but not a well-formed JWT (expected 3 dot-separated parts).";
710+
}
711+
}
712+
catch (Exception ex)
713+
{
714+
decodeError = ex.Message;
715+
}
716+
}
717+
718+
return Results.Json(new
719+
{
720+
// What the orchestrator is currently configured to expect.
721+
expected = new
722+
{
723+
enabled = cfg.Enabled,
724+
jwksUrl = cfg.JwksUrl,
725+
issuer = cfg.Issuer,
726+
audience = cfg.Audience,
727+
authorizedRoles = cfg.AuthorizedRoles,
728+
authorizedUsers = cfg.AuthorizedUsers,
729+
},
730+
// Whether Teleport actually forwarded a token to us.
731+
headerName = TeleportAuth.JwtHeader,
732+
headerPresent = !string.IsNullOrEmpty(raw),
733+
// Decoded but UNVERIFIED — read iss/aud/roles/sub/kid here to fill in config.
734+
tokenHeader,
735+
tokenClaims,
736+
decodeError,
737+
// Result of real verification (empty/false when validation fails).
738+
validated = new
739+
{
740+
authenticated = User?.Identity?.IsAuthenticated == true,
741+
user = TeleportAuth.GetUsername(User),
742+
roles = TeleportAuth.GetRoles(User).Distinct().ToArray(),
743+
canMutate = TeleportAuth.IsAuthorizedToMutate(User, cfg),
744+
},
745+
});
746+
}
747+
748+
private static string DecodeJwtSegment(string segment)
749+
{
750+
string s = segment.Replace('-', '+').Replace('_', '/');
751+
switch (s.Length % 4)
752+
{
753+
case 2: s += "=="; break;
754+
case 3: s += "="; break;
755+
}
756+
return Encoding.UTF8.GetString(Convert.FromBase64String(s));
757+
}
758+
681759
// Identity of the caller (from the verified Teleport JWT, if present) plus whether
682760
// they may run mutating actions. The frontend uses this to gate its action buttons.
683761
[Route("me")]

0 commit comments

Comments
 (0)