Skip to content

Commit

Permalink
feat: Update (#13)
Browse files Browse the repository at this point in the history
* feat: add robots.txt

* feat: userinfo endpoint

* feat: signout endpoint

* feat: unauthorized returns failed claim name

* feat: configurable cookie auth scopes

* feat: map all cookie claims

* feat: configurable valid audiences for JWT validation
  • Loading branch information
IvanJosipovic authored Jul 1, 2023
1 parent 036de72 commit 19bb6fd
Show file tree
Hide file tree
Showing 18 changed files with 349 additions and 216 deletions.
21 changes: 9 additions & 12 deletions .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"matchCurrentVersion": "!/^0/",
"automerge": true,
"extends": [":semanticCommitTypeAll(fix)"],
"excludePackageNames": ["Microsoft.VisualStudio.Azure.Containers.Tools.Targets"],
"matchPaths": [
"src/**",
"global.json"
Expand All @@ -29,22 +30,18 @@
"matchUpdateTypes": ["minor", "patch", "digest"],
"matchCurrentVersion": "!/^0/",
"automerge": true,
"matchPackageNames": ["Microsoft.VisualStudio.Azure.Containers.Tools.Targets"],
"matchPaths": [
"tests/**",
"benchmarks/**"
"src/**"
]
},
{
"groupName": "IdentityModel",
"separateMajorMinor": true,
"groupSlug": "identitymodel-libs",
"packageRules": [
{
"matchPackagePatterns": [
"System.IdentityModel.*",
"Microsoft.IdentityModel.*"
]
}
"matchUpdateTypes": ["minor", "patch", "digest"],
"matchCurrentVersion": "!/^0/",
"automerge": true,
"matchPaths": [
"tests/**",
"benchmarks/**"
]
}
]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![GitHub](https://img.shields.io/github/stars/ivanjosipovic/oidc-guard?style=social)](https://github.com/IvanJosipovic/oidc-guard)
[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/oidc-guard)](https://artifacthub.io/packages/helm/oidc-guard/oidc-guard)

OpenID Connect (OIDC) Proxy Server for securing Kubernetes Ingress
OpenID Connect (OIDC) & OAuth 2 Proxy Server for securing Kubernetes Ingress

## What is this?

Expand Down
2 changes: 1 addition & 1 deletion charts/oidc-guard/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
apiVersion: v2
name: oidc-guard
description: OpenID Connect (OIDC) Proxy Server for securing Kubernetes Ingress
description: OpenID Connect (OIDC) & OAuth 2 Proxy Server for securing Kubernetes Ingress

# A chart can be either an 'application' or a 'library' chart.
#
Expand Down
13 changes: 11 additions & 2 deletions charts/oidc-guard/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,26 @@ settings:
# Client Secret
clientSecret: ""

# Scopes to request
scopes:
- openid
- profile

# Control if the access and refresh tokens should be stored in the cookie,
# disable to reduce the size of the authentication cookie.
# You may have to set 'large-client-header-buffers: 4 16k' in ingress-nginx
saveTokensInCookie: false

# Control if the audience will be validated during token validation.
# Control if the audience will be validated during JWT token validation.
# Validation of the audience, mitigates forwarding attacks. For example, a site that receives a token, could not replay it to another site.
# This value can be validated at the Ingress level using /auth?aud=00000000-0000-0000-0000-000000000000
validateAudience: false

# Control if the issuer will be validated during token validation.
# Set valid audiences for JWT validation
validAudiences: []
# - 11111111-1111-1111-1111-111111111111

# Control if the issuer will be validated during JWT token validation.
# Validation of the issuer mitigates forwarding attacks that can occur when an
# Identity Provider represents multiple tenants and signs tokens with the same keys.
# It is possible that a token issued for the same audience could be from a different tenant.
Expand Down
118 changes: 69 additions & 49 deletions src/oidc-guard/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace oidc_guard.Controllers;

[ApiController]
[Route("")]
[Authorize]
public class AuthController : ControllerBase
{
private readonly ILogger<AuthController> _logger;
Expand All @@ -27,7 +28,7 @@ public AuthController(ILogger<AuthController> logger, Settings settings)

[HttpGet("auth")]
[AllowAnonymous]
public ActionResult Auth()
public IActionResult Auth()
{
if (settings.SkipAuthPreflight &&
HttpContext.Request.Headers[CustomHeaderNames.OriginalMethod].FirstOrDefault() == "OPTIONS" &&
Expand All @@ -46,53 +47,55 @@ public ActionResult Auth()
}

// Validate based on rules

foreach (var item in Request.Query)
if (Request.QueryString.HasValue)
{
if (item.Key.Equals("inject-claim", StringComparison.InvariantCultureIgnoreCase))
foreach (var item in Request.Query)
{
foreach (var value in item.Value)
if (item.Key.Equals("inject-claim", StringComparison.InvariantCultureIgnoreCase))
{
if (string.IsNullOrEmpty(value))
{
continue;
}

string claimName;
string headerName;

if (value.Contains(','))
foreach (var value in item.Value)
{
claimName = value.Split(',')[0];
headerName = value.Split(',')[1];
}
else
{
claimName = value;
headerName = value;
}

var claims = HttpContext.User.Claims.Where(x => x.Type == claimName || x.Properties.Any(y => y.Value == claimName)).ToArray();

if (claims == null || claims.Length == 0)
{
continue;
}

if (claims.Length == 1)
{
Response.Headers.Add(headerName, claims[0].Value);
}
else
{
Response.Headers.Add(headerName, new StringValues(claims.Select(x => x.Value).ToArray()));
if (string.IsNullOrEmpty(value))
{
continue;
}

string claimName;
string headerName;

if (value.Contains(','))
{
claimName = value.Split(',')[0];
headerName = value.Split(',')[1];
}
else
{
claimName = value;
headerName = value;
}

var claims = HttpContext.User.Claims.Where(x => x.Type == claimName).ToArray();

if (claims == null || claims.Length == 0)
{
continue;
}

if (claims.Length == 1)
{
Response.Headers.Add(headerName, claims[0].Value);
}
else
{
Response.Headers.Add(headerName, new StringValues(claims.Select(x => x.Value).ToArray()));
}
}
}
}
else if (!HttpContext.User.Claims.Any(x => (x.Type == item.Key || x.Properties.Any(y => y.Value == item.Key)) && item.Value.Any(y => y?.Equals(x.Value) == true)))
{
UnauthorizedGauge.Inc();
return Unauthorized();
else if (!HttpContext.User.Claims.Any(x => x.Type == item.Key && item.Value.Contains(x.Value)))
{
UnauthorizedGauge.Inc();
return Unauthorized($"Claim {item.Key} does not match!");
}
}
}

Expand All @@ -102,19 +105,15 @@ public ActionResult Auth()

[HttpGet("signin")]
[AllowAnonymous]
public ActionResult Signin([FromQuery] Uri rd)
public IActionResult SignIn([FromQuery] Uri rd)
{
if (settings.AllowedRedirectDomains?.Length > 0 && rd.IsAbsoluteUri)
{
var found = false;
foreach (var allowedDomain in settings.AllowedRedirectDomains)
{
if (allowedDomain[0] == '.' && rd.DnsSafeHost.EndsWith(allowedDomain, StringComparison.InvariantCultureIgnoreCase))
{
found = true;
break;
}
else if (rd.DnsSafeHost.Equals(allowedDomain, StringComparison.InvariantCultureIgnoreCase))
if ((allowedDomain[0] == '.' && rd.DnsSafeHost.EndsWith(allowedDomain, StringComparison.InvariantCultureIgnoreCase)) ||
rd.DnsSafeHost.Equals(allowedDomain, StringComparison.InvariantCultureIgnoreCase))
{
found = true;
break;
Expand All @@ -131,4 +130,25 @@ public ActionResult Signin([FromQuery] Uri rd)

return Challenge(new AuthenticationProperties { RedirectUri = rd.ToString() });
}

[HttpGet("signout")]
[Authorize]
public IActionResult SignOut([FromQuery] string rd)
{
return SignOut(new AuthenticationProperties { RedirectUri = rd });
}

[HttpGet("userinfo")]
[Authorize]
public IActionResult UserInfo()
{
return Ok(HttpContext.User.Claims.Select(x => new { Name = x.Type, x.Value }));
}

[HttpGet("robots.txt")]
[AllowAnonymous]
public IActionResult Robots()
{
return Ok("User-agent: *\r\nDisallow: /");
}
}
15 changes: 14 additions & 1 deletion src/oidc-guard/Program.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
Expand All @@ -8,6 +9,7 @@
using Microsoft.Net.Http.Headers;
using oidc_guard.Services;
using Prometheus;
using System.IdentityModel.Tokens.Jwt;

namespace oidc_guard;

Expand All @@ -19,7 +21,7 @@ public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);

Settings settings = builder.Configuration.GetSection("Settings").Get<Settings>()!;
var settings = builder.Configuration.GetSection("Settings").Get<Settings>()!;
builder.Services.AddSingleton(settings);

if (builder.Environment.IsProduction())
Expand All @@ -39,6 +41,8 @@ public static void Main(string[] args)
o.OnDeleteCookie = cookieContext => cookieContext.CookieOptions.SameSite = settings.CookieSameSiteMode;
});

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

builder.Services.AddAuthentication(o =>
{
o.DefaultScheme = AuthenticationScheme;
Expand All @@ -53,17 +57,26 @@ public static void Main(string[] args)
{
o.ClientId = settings.ClientId;
o.ClientSecret = settings.ClientSecret;
o.CorrelationCookie.Name = settings.CookieName;
o.MetadataAddress = settings.OpenIdProviderConfigurationUrl;
o.NonceCookie.Name = settings.CookieName;
o.ResponseType = OpenIdConnectResponseType.Code;
o.SaveTokens = settings.SaveTokensInCookie;
o.TokenValidationParameters.ClockSkew = TimeSpan.FromSeconds(30);
o.Scope.Clear();
foreach (var scope in settings.Scopes)
{
o.Scope.Add(scope);
}
o.ClaimActions.Clear();
o.ClaimActions.MapAllExcept("nonce", /*"aud",*/ "azp", "acr", "iss", "iat", "nbf", "exp", "at_hash", "c_hash", "ipaddr", "platf", "ver");
})
.AddJwtBearer(o =>
{
o.MetadataAddress = settings.OpenIdProviderConfigurationUrl;
o.TokenValidationParameters.ClockSkew = TimeSpan.FromSeconds(30);
o.TokenValidationParameters.ValidateAudience = settings.ValidateAudience;
o.TokenValidationParameters.ValidAudiences = settings.ValidAudiences;
o.TokenValidationParameters.ValidateIssuer = settings.ValidateIssuer;
o.TokenValidationParameters.ValidIssuers = settings.ValidIssuers;
})
Expand Down
10 changes: 6 additions & 4 deletions src/oidc-guard/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

public class Settings
{
public bool EnableAccessTokenInQueryParameter { get; set; }
public bool SaveTokensInCookie { get; set; }
public bool SkipAuthPreflight { get; set; }
public bool ValidateAudience { get; set; }
Expand All @@ -10,12 +11,13 @@ public class Settings
public SameSiteMode CookieSameSiteMode { get; set; }
public string ClientId { get; set; } = null!;
public string ClientSecret { get; set; } = null!;
public string? CookieDomain { get; set; }
public string CookieName { get; set; } = "oidc-guard";
public string OpenIdProviderConfigurationUrl { get; set; } = null!;
public string[]? AllowedRedirectDomains { get; set; }
public string[]? ValidIssuers { get; set; }
public string? CookieDomain { get; set; }
public string? Host { get; set; }
public string? Scheme { get; set; }
public bool EnableAccessTokenInQueryParameter { get; set; }
public string[] Scopes { get; set; }
public string[]? AllowedRedirectDomains { get; set; }
public string[]? ValidAudiences { get; set; }
public string[]? ValidIssuers { get; set; }
}
6 changes: 4 additions & 2 deletions tests/oidc-guard-tests/AllowedRedirectDomainTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using oidc_guard;
using FluentAssertions;
using oidc_guard_tests.Infra;
using System.Net;
using System.Web;
using Xunit;

namespace oidc_guard_tests;

Expand Down Expand Up @@ -31,7 +33,7 @@ public class AllowedRedirectDomainTests

public async Task Signin(string query, string[]? allowedRedirectDomains, HttpStatusCode status)
{
var client = AuthTests.GetClient(x => x.AllowedRedirectDomains = allowedRedirectDomains);
var client = AuthTestsHelpers.GetClient(x => x.AllowedRedirectDomains = allowedRedirectDomains);

var response = await client.GetAsync($"/signin?rd={HttpUtility.UrlEncode(query)}");

Expand Down
Loading

0 comments on commit 19bb6fd

Please sign in to comment.