Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanJosipovic committed Nov 25, 2023
1 parent b6863e9 commit 18069b8
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 27 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ OpenID Connect (OIDC) & OAuth 2 API Server for securing Kubernetes Ingress

## What is this?

This project is an API server which is used along with the [nginx.ingress.kubernetes.io/auth-url](https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/nginx-configuration/annotations.md#external-authentication) annotation for ingress-nginx and enables per Ingress customizable JWT validation with Cookie support for Web Applications.
This project is an API server which is used along with Ingress Controllers that support External Authentication and enables per Ingress customizable JWT validation with Cookie support for Web Applications.

| Ingress Controller | JWT | Cookie|
|---|---|---|
| Nginx Ingress | X | X |
| Traefik | X | |

## Features

Expand Down
35 changes: 35 additions & 0 deletions docs/Nginx.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Nginx notes

Example Payload from Nginx

```text
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
Request:
Protocol: HTTP/1.1
Method: GET
Scheme: https
PathBase:
Path: /auth
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Connection: close
Host: oidc-guard.oidc-guard.svc.cluster.local
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cache-Control: max-age=0
Upgrade-Insecure-Requests: [Redacted]
X-Request-ID: [Redacted]
X-Original-URL: https://demo-app.test.loc:32443/test123123123?id=2
X-Original-Method: GET
X-Sent-From: [Redacted]
X-Real-IP: [Redacted]
X-Forwarded-For: [Redacted]
X-Auth-Request-Redirect: [Redacted]
sec-ch-ua: [Redacted]
sec-ch-ua-mobile: [Redacted]
sec-ch-ua-platform: [Redacted]
sec-fetch-site: [Redacted]
sec-fetch-mode: [Redacted]
sec-fetch-user: [Redacted]
sec-fetch-dest: [Redacted]
```
35 changes: 35 additions & 0 deletions docs/Traefik.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Traefik notes

Example Payload from Traefik

```text
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
Request:
Protocol: HTTP/1.1
Method: GET
Scheme: https
PathBase:
Path: /auth
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Host: oidc-guard.oidc-guard.svc.cluster.local:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cache-Control: max-age=0
Upgrade-Insecure-Requests: [Redacted]
Sec-Ch-Ua: [Redacted]
Sec-Ch-Ua-Mobile: [Redacted]
Sec-Ch-Ua-Platform: [Redacted]
Sec-Fetch-Dest: [Redacted]
Sec-Fetch-Mode: [Redacted]
Sec-Fetch-Site: [Redacted]
Sec-Fetch-User: [Redacted]
X-Forwarded-For: [Redacted]
X-Forwarded-Host: demo-app.test.loc:32443
X-Forwarded-Method: GET
X-Forwarded-Port: [Redacted]
X-Forwarded-Proto: https
X-Forwarded-Server: [Redacted]
X-Forwarded-Uri: /test123123123?id=2
X-Real-Ip: [Redacted]
```
12 changes: 10 additions & 2 deletions src/oidc-guard/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@

public static class CustomHeaderNames
{
public static readonly string OriginalMethod = "X-Original-Method";
public static readonly string XOriginalMethod = "X-Original-Method";

public static readonly string OriginalUrl = "X-Original-Url";
public static readonly string XOriginalUrl = "X-Original-Url";

public static readonly string XForwardedProto = "X-Forwarded-Proto";

public static readonly string XForwardedHost = "X-Forwarded-Host";

public static readonly string XForwardedUri = "X-Forwarded-Uri";

public static readonly string XForwardedMethod = "X-Forwarded-Method";
}

public static class QueryParameters
Expand Down
29 changes: 17 additions & 12 deletions src/oidc-guard/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,14 @@ public static void Main(string[] args)

builder.Services.AddHttpLogging(logging =>
{
logging.RequestHeaders.Add("Access-Control-Request-Headers");
logging.RequestHeaders.Add("Access-Control-Request-Method");
logging.RequestHeaders.Add("X-Forwarded-Host");
logging.RequestHeaders.Add("X-Forwarded-Proto");
logging.RequestHeaders.Add("X-Forwarded-Scheme");
logging.RequestHeaders.Add("X-Original-Method");
logging.RequestHeaders.Add("X-Original-Url");
logging.RequestHeaders.Add("X-Scheme");
logging.RequestHeaders.Add(HeaderNames.AccessControlRequestHeaders);
logging.RequestHeaders.Add(HeaderNames.AccessControlRequestMethod);
logging.RequestHeaders.Add(CustomHeaderNames.XForwardedHost);
logging.RequestHeaders.Add(CustomHeaderNames.XForwardedMethod);
logging.RequestHeaders.Add(CustomHeaderNames.XForwardedProto);
logging.RequestHeaders.Add(CustomHeaderNames.XForwardedUri);
logging.RequestHeaders.Add(CustomHeaderNames.XOriginalMethod);
logging.RequestHeaders.Add(CustomHeaderNames.XOriginalUrl);
});

builder.Services.AddAuthorization();
Expand Down Expand Up @@ -214,7 +214,7 @@ public static void Main(string[] args)

if (settings.JWT.EnableAccessTokenInQueryParameter &&
context.Request.Path.StartsWithSegments("/auth") &&
context.Request.Headers.TryGetValue(CustomHeaderNames.OriginalUrl, out var originalUrlHeader) &&
context.Request.Headers.TryGetValue(CustomHeaderNames.XOriginalUrl, out var originalUrlHeader) &&
Uri.TryCreate(originalUrlHeader, UriKind.RelativeOrAbsolute, out var uri))
{
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue(QueryParameters.AccessToken, out var token) &&
Expand Down Expand Up @@ -247,7 +247,7 @@ public static void Main(string[] args)
meters.SignInCounter.Add(1);

if (settings.SkipAuthPreflight &&
httpContext.Request.Headers[CustomHeaderNames.OriginalMethod][0] == HttpMethod.Options.Method &&
httpContext.Request.Headers[CustomHeaderNames.XOriginalMethod][0] == HttpMethod.Options.Method &&
!StringValues.IsNullOrEmpty(httpContext.Request.Headers.AccessControlRequestHeaders) &&
!StringValues.IsNullOrEmpty(httpContext.Request.Headers.AccessControlRequestMethod) &&
!StringValues.IsNullOrEmpty(httpContext.Request.Headers.Origin))
Expand All @@ -260,8 +260,8 @@ public static void Main(string[] args)
(httpContext.Request.Query.TryGetValue(QueryParameters.SkipAuth, out var skipEquals) |
httpContext.Request.Query.TryGetValue(QueryParameters.SkipAuthNe, out var skipNotEquals)))
{
var originalUrl = httpContext.Request.Headers[CustomHeaderNames.OriginalUrl][0]!;
var originalMethod = httpContext.Request.Headers[CustomHeaderNames.OriginalMethod][0];
var originalUrl = httpContext.Request.Headers[CustomHeaderNames.XOriginalUrl][0]!;
var originalMethod = httpContext.Request.Headers[CustomHeaderNames.XOriginalMethod][0];

if (skipEquals.Count > 0)
{
Expand Down Expand Up @@ -457,6 +457,11 @@ public static void Main(string[] args)
app.Run();
}

private static string GenerateRedirectUrl(IHeaderDictionary headers)
{
return $"{headers[CustomHeaderNames.XForwardedProto]}://{headers[CustomHeaderNames.XForwardedHost]}{headers[CustomHeaderNames.XForwardedUri]}";
}

private static bool ValidateRedirect(Uri rd, Settings settings)
{
if (settings.Cookie.AllowedRedirectDomains?.Length > 0 && rd.IsAbsoluteUri)
Expand Down
16 changes: 8 additions & 8 deletions tests/oidc-guard-tests/AuthTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ public static IEnumerable<object[]> GetTokenAsQueryParameterTests()
HttpStatusCode.OK,
new Dictionary<string, string>()
{
{CustomHeaderNames.OriginalUrl, $"https://www.example.com?{QueryParameters.AccessToken}={FakeJwtIssuer.GenerateJwtToken(Enumerable.Empty<Claim>())}" }
{CustomHeaderNames.XOriginalUrl, $"https://www.example.com?{QueryParameters.AccessToken}={FakeJwtIssuer.GenerateJwtToken(Enumerable.Empty<Claim>())}" }
}
},
new object[] // Bad Token Only in Query String
Expand All @@ -413,7 +413,7 @@ public static IEnumerable<object[]> GetTokenAsQueryParameterTests()
HttpStatusCode.Unauthorized,
new Dictionary<string, string>()
{
{CustomHeaderNames.OriginalUrl, $"https://www.example.com?{QueryParameters.AccessToken}=BAD" }
{CustomHeaderNames.XOriginalUrl, $"https://www.example.com?{QueryParameters.AccessToken}=BAD" }
}
},
new object[] // Bad Token in Query String and Header, Header is used
Expand All @@ -423,7 +423,7 @@ public static IEnumerable<object[]> GetTokenAsQueryParameterTests()
HttpStatusCode.OK,
new Dictionary<string, string>()
{
{CustomHeaderNames.OriginalUrl, $"https://www.example.com?{QueryParameters.AccessToken}=BAD" }
{CustomHeaderNames.XOriginalUrl, $"https://www.example.com?{QueryParameters.AccessToken}=BAD" }
},
true
},
Expand All @@ -434,7 +434,7 @@ public static IEnumerable<object[]> GetTokenAsQueryParameterTests()
HttpStatusCode.OK,
new Dictionary<string, string>()
{
{CustomHeaderNames.OriginalUrl, "https://www.example.com" }
{CustomHeaderNames.XOriginalUrl, "https://www.example.com" }
},
true
},
Expand All @@ -445,7 +445,7 @@ public static IEnumerable<object[]> GetTokenAsQueryParameterTests()
HttpStatusCode.OK,
new Dictionary<string, string>()
{
{CustomHeaderNames.OriginalUrl, $"https://www.example.com?{QueryParameters.AccessToken}={FakeJwtIssuer.GenerateJwtToken(new List<Claim>{new Claim("tid", "11111111-1111-1111-1111-111111111111")})}" }
{CustomHeaderNames.XOriginalUrl, $"https://www.example.com?{QueryParameters.AccessToken}={FakeJwtIssuer.GenerateJwtToken(new List<Claim>{new Claim("tid", "11111111-1111-1111-1111-111111111111")})}" }
},
},
new object[] // Token in Query String with Bad Claim
Expand All @@ -455,7 +455,7 @@ public static IEnumerable<object[]> GetTokenAsQueryParameterTests()
HttpStatusCode.Unauthorized,
new Dictionary<string, string>()
{
{CustomHeaderNames.OriginalUrl, $"https://www.example.com?{QueryParameters.AccessToken}={FakeJwtIssuer.GenerateJwtToken(new List<Claim>{new Claim("tid", "22222222-2222-2222-2222-222222222222")})}" }
{CustomHeaderNames.XOriginalUrl, $"https://www.example.com?{QueryParameters.AccessToken}={FakeJwtIssuer.GenerateJwtToken(new List<Claim>{new Claim("tid", "22222222-2222-2222-2222-222222222222")})}" }
},
},
};
Expand Down Expand Up @@ -550,8 +550,8 @@ public async Task SkipAuth(string query, string Url, string httpMethod, HttpStat
{
var _client = AuthTestsHelpers.GetClient();

_client.DefaultRequestHeaders.TryAddWithoutValidation(CustomHeaderNames.OriginalUrl, Url);
_client.DefaultRequestHeaders.TryAddWithoutValidation(CustomHeaderNames.OriginalMethod, httpMethod);
_client.DefaultRequestHeaders.TryAddWithoutValidation(CustomHeaderNames.XOriginalUrl, Url);
_client.DefaultRequestHeaders.TryAddWithoutValidation(CustomHeaderNames.XOriginalMethod, httpMethod);

var response = await _client.GetAsync($"/auth{query}");
response.StatusCode.Should().Be(status);
Expand Down
8 changes: 4 additions & 4 deletions tests/oidc-guard-tests/SkipAuthPreflight.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public async Task SkipAuthPreflight()
var _client = AuthTestsHelpers.GetClient(x => { x.SkipAuthPreflight = true; });

_client.DefaultRequestHeaders.TryAddWithoutValidation(HeaderNames.Origin, "localhost");
_client.DefaultRequestHeaders.TryAddWithoutValidation(CustomHeaderNames.OriginalMethod, "OPTIONS");
_client.DefaultRequestHeaders.TryAddWithoutValidation(CustomHeaderNames.XOriginalMethod, "OPTIONS");
_client.DefaultRequestHeaders.TryAddWithoutValidation(HeaderNames.AccessControlRequestHeaders, "origin, x-requested-with");
_client.DefaultRequestHeaders.TryAddWithoutValidation(HeaderNames.AccessControlRequestMethod, "DELETE");

Expand All @@ -28,7 +28,7 @@ public async Task SkipAuthPreflightDisabled()
{
var _client = AuthTestsHelpers.GetClient(x => { x.SkipAuthPreflight = false; });

_client.DefaultRequestHeaders.TryAddWithoutValidation(CustomHeaderNames.OriginalMethod, "OPTIONS");
_client.DefaultRequestHeaders.TryAddWithoutValidation(CustomHeaderNames.XOriginalMethod, "OPTIONS");
_client.DefaultRequestHeaders.TryAddWithoutValidation(HeaderNames.AccessControlRequestHeaders, "origin, x-requested-with");
_client.DefaultRequestHeaders.TryAddWithoutValidation(HeaderNames.AccessControlRequestMethod, "DELETE");

Expand All @@ -42,7 +42,7 @@ public async Task SkipAuthPreflightMissingMethod()
var _client = AuthTestsHelpers.GetClient(x => { x.SkipAuthPreflight = true; });

_client.DefaultRequestHeaders.TryAddWithoutValidation(HeaderNames.Origin, "localhost");
_client.DefaultRequestHeaders.TryAddWithoutValidation(CustomHeaderNames.OriginalMethod, "OPTIONS");
_client.DefaultRequestHeaders.TryAddWithoutValidation(CustomHeaderNames.XOriginalMethod, "OPTIONS");
_client.DefaultRequestHeaders.TryAddWithoutValidation(HeaderNames.AccessControlRequestHeaders, "origin, x-requested-with");

var response = await _client.GetAsync("/auth");
Expand All @@ -55,7 +55,7 @@ public async Task SkipAuthPreflightMissingRequestHeaders()
var _client = AuthTestsHelpers.GetClient(x => { x.SkipAuthPreflight = true; });

_client.DefaultRequestHeaders.TryAddWithoutValidation(HeaderNames.Origin, "localhost");
_client.DefaultRequestHeaders.TryAddWithoutValidation(CustomHeaderNames.OriginalMethod, "OPTIONS");
_client.DefaultRequestHeaders.TryAddWithoutValidation(CustomHeaderNames.XOriginalMethod, "OPTIONS");
_client.DefaultRequestHeaders.TryAddWithoutValidation(HeaderNames.AccessControlRequestMethod, "DELETE");

var response = await _client.GetAsync("/auth");
Expand Down

0 comments on commit 18069b8

Please sign in to comment.