diff options
Diffstat (limited to 'plugins/SessionProvider/src')
4 files changed, 145 insertions, 11 deletions
diff --git a/plugins/SessionProvider/src/Security/WebSessionSecMiddleware.cs b/plugins/SessionProvider/src/Security/WebSessionSecMiddleware.cs new file mode 100644 index 0000000..6a21ded --- /dev/null +++ b/plugins/SessionProvider/src/Security/WebSessionSecMiddleware.cs @@ -0,0 +1,118 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: SessionProvider +* File: WebSessionSecMiddleware.cs +* +* WebSessionSecMiddleware.cs is part of SessionProvider which is part of the larger +* VNLib collection of libraries and utilities. +* +* SessionProvider is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* SessionProvider is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using System; +using System.Threading.Tasks; +using System.Security.Authentication; +using System.Text.Json.Serialization; + +using VNLib.Utils.Logging; +using VNLib.Plugins.Essentials.Extensions; +using VNLib.Plugins.Extensions.Loading; +using VNLib.Plugins.Essentials.Middleware; + +namespace VNLib.Plugins.Essentials.Sessions +{ + [ConfigurationName("web")] + [MiddlewareImpl(MiddlewareImplOptions.SecurityCritical)] + internal sealed class WebSessionSecMiddleware(PluginBase plugin, IConfigScope config) : IHttpMiddleware + { + private readonly ILogProvider _log = plugin.Log.CreateScope("Session-Sec"); + private readonly SecConfig _secConfig = config.Deserialze<SecConfig>(); + + ///<inheritdoc/> + public ValueTask<FileProcessArgs> ProcessAsync(HttpEntity entity) + { + ref readonly SessionInfo session = ref entity.Session; + + if (session.IsSet) + { + + /* + * Check if the session was established over a secure connection, + * and if the current connection is insecure, redirect them to a + * secure connection. + */ + if (session.SecurityProcol > SslProtocols.None && !entity.IsSecure) + { + //Redirect the client to https + UriBuilder ub = new(entity.Server.RequestUri) + { + Scheme = Uri.UriSchemeHttps + }; + + _log.Debug("Possbile session TLS downgrade detected, redirecting {con} to secure endpoint", entity.TrustedRemoteIp); + + //Redirect + entity.Redirect(RedirectType.Moved, ub.Uri); + return ValueTask.FromResult(FileProcessArgs.VirtualSkip); + } + + //If session is not new, then verify it matches stored credentials + if (!session.IsNew && session.SessionType == SessionType.Web) + { + /* + * When sessions are created for connections that come from a different + * origin, their origin is stored for later. + * + * If the session was created from a different origin or the current connection + * is cross origin, then the origin must match the stored origin. + */ + + if (_secConfig.EnforceStrictCors) + { + if ((entity.Server.CrossOrigin || session.CrossOrigin) + && !session.CrossOriginMatch + && entity.Server.Origin != null) + { + _log.Debug("Denied connection from {0} due to cross-origin session mismatch.", entity.TrustedRemoteIp); + return ValueTask.FromResult(FileProcessArgs.Deny); + } + } + + if (_secConfig.EnfoceStrictTlsProtocol) + { + //Try to prevent security downgrade attacks + if (!(session.IPMatch && session.SecurityProcol <= entity.Server.GetSslProtocol())) + { + _log.Debug("Possible TLS downgrade attack stopeed from connection {con}", entity.TrustedRemoteIp); + return ValueTask.FromResult(FileProcessArgs.Deny); + } + } + } + } + + return ValueTask.FromResult(FileProcessArgs.Continue); + } + + sealed class SecConfig + { + [JsonPropertyName("strict_cors")] + public bool EnforceStrictCors { get; set; } = true; + + [JsonPropertyName("strict_tls_protocol")] + public bool EnfoceStrictTlsProtocol { get; set; } = true; + } + } +} diff --git a/plugins/SessionProvider/src/SessionProvider.csproj b/plugins/SessionProvider/src/SessionProvider.csproj index 805ff7d..b9f2a04 100644 --- a/plugins/SessionProvider/src/SessionProvider.csproj +++ b/plugins/SessionProvider/src/SessionProvider.csproj @@ -2,19 +2,20 @@ <PropertyGroup> <TargetFramework>net8.0</TargetFramework> + <Nullable>enable</Nullable> <RootNamespace>VNLib.Plugins.Sessions</RootNamespace> <AssemblyName>SessionProvider</AssemblyName> - <NeutralLanguage>en-US</NeutralLanguage> - <Nullable>enable</Nullable> <GenerateDocumentationFile>True</GenerateDocumentationFile> - <AnalysisLevel>latest-all</AnalysisLevel> - <RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild> - + <RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild> <!-- Resolve nuget dll files and store them in the output dir --> <EnableDynamicLoading>true</EnableDynamicLoading> </PropertyGroup> <PropertyGroup> + <AnalysisLevel Condition="'$(BuildingInsideVisualStudio)' == true">latest-all</AnalysisLevel> + </PropertyGroup> + + <PropertyGroup> <Authors>Vaughn Nugent</Authors> <Company>Vaughn Nugent</Company> <Product>SessionProvider</Product> @@ -23,12 +24,11 @@ <Copyright>Copyright © 2024 Vaughn Nugent</Copyright> <PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/VNLib.Plugins.Sessions</PackageProjectUrl> <RepositoryUrl>https://github.com/VnUgE/VNLib.Plugins.Sessions/tree/master/plugins/SessionProvider</RepositoryUrl> - </PropertyGroup> - - <PropertyGroup> <PackageReadmeFile>README.md</PackageReadmeFile> <PackageLicenseFile>LICENSE</PackageLicenseFile> + <RequireLicenseAcceptance>True</RequireLicenseAcceptance> </PropertyGroup> + <ItemGroup> <None Include="..\..\..\LICENSE"> <Pack>True</Pack> diff --git a/plugins/SessionProvider/src/SessionProvider.sample.json b/plugins/SessionProvider/src/SessionProvider.sample.json index 0675fa1..0a3083a 100644 --- a/plugins/SessionProvider/src/SessionProvider.sample.json +++ b/plugins/SessionProvider/src/SessionProvider.sample.json @@ -19,7 +19,11 @@ //time (in seconds) a session is valid for "valid_for_sec": 3600, //The maxium number of connections waiting for the cache server responses - "max_waiting_connections": 100 + "max_waiting_connections": 100, + //Enforce strict cross-origin session checks + "strict_cors": true, + ///Enforces strict TLS to help prevent tls downgrades based on stored session variables (privacy note: this can be leaked through brute-forced if session id is stolen) + "strict_tls_protocol": true }, //If the OAuth provider is enabled, you may enable the optional revocation endpoint diff --git a/plugins/SessionProvider/src/SessionProviderEntry.cs b/plugins/SessionProvider/src/SessionProviderEntry.cs index fa2bd27..2c8a757 100644 --- a/plugins/SessionProvider/src/SessionProviderEntry.cs +++ b/plugins/SessionProvider/src/SessionProviderEntry.cs @@ -32,6 +32,7 @@ using VNLib.Net.Http; using VNLib.Utils; using VNLib.Utils.Logging; using VNLib.Plugins.Extensions.Loading; +using VNLib.Plugins.Extensions.Loading.Routing; namespace VNLib.Plugins.Essentials.Sessions { @@ -46,7 +47,7 @@ namespace VNLib.Plugins.Essentials.Sessions ///<inheritdoc/> protected override void OnLoad() { - List<RuntimeSessionProvider> providers = new(); + List<RuntimeSessionProvider> providers = []; Log.Verbose("Loading all specified session providers"); @@ -89,6 +90,17 @@ namespace VNLib.Plugins.Essentials.Sessions Log.Information("No session providers loaded"); } + //See if web sessions are loaded + if (this.HasConfigForType<WebSessionSecMiddleware>()) + { + this.ExportMiddleware( + //Init web session sec middlware + this.GetOrCreateSingleton<WebSessionSecMiddleware>() + ); + + Log.Debug("Web session security middleware initialized"); + } + Log.Information("Plugin loaded"); } @@ -129,7 +141,7 @@ namespace VNLib.Plugins.Essentials.Sessions } //Return empty session - return new (SessionHandle.Empty); + return ValueTask.FromResult(SessionHandle.Empty); } protected override void Free() |