< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.WebApi.DatabaseConnectionGuard
Assembly: ArturRios.Heimdall.WebApi
File(s): /home/ci/agent/workspace/heimdall-api-docs_main/app/src/Presentation/ArturRios.Heimdall.WebApi/Registration/DatabaseConnectionGuard.cs
Line coverage
80%
Covered lines: 17
Uncovered lines: 4
Coverable lines: 21
Total lines: 86
Line coverage: 80.9%
Branch coverage
25%
Covered branches: 3
Total branches: 12
Branch coverage: 25%
Method coverage
100%
Covered methods: 1
Fully covered methods: 0
Total methods: 1
Method coverage: 100%
Full method coverage: 0%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
WarnIfDatabaseConnectionIsNotEncrypted()25%131280.95%

File(s)

/home/ci/agent/workspace/heimdall-api-docs_main/app/src/Presentation/ArturRios.Heimdall.WebApi/Registration/DatabaseConnectionGuard.cs

#LineLine coverage
 1using ArturRios.Data.Relational.Core.DependencyInjection;
 2using ArturRios.Util.WebApi.Configuration;
 3using ArturRios.Util.WebApi.Security.Extensions;
 4using FluentValidation;
 5using Serilog;
 6
 7namespace ArturRios.Heimdall.WebApi;
 8
 9internal static class DatabaseConnectionGuard
 10{
 11    /// <summary>
 12    ///     Set to <c>true</c> to refuse start-up when the database connection does not require TLS,
 13    ///     rather than warning. Opt-in: see <see cref="WarnIfDatabaseConnectionIsNotEncrypted" />.
 14    /// </summary>
 15    private const string RequireDatabaseTlsVariable = "HEIMDALL_DATA_REQUIRE_TLS";
 16
 17    /// <summary>
 18    ///     Warns when the database connection string does not require TLS (NFR-25, GDPR Art. 32,
 19    ///     LGPD Art. 46).
 20    /// </summary>
 21    /// <remarks>
 22    ///     <para>
 23    ///         Everything this database holds is personal data, and Npgsql's default
 24    ///         <c>SSL Mode</c> is <c>Prefer</c> — which silently falls back to an unencrypted
 25    ///         connection when the server does not offer TLS. A deployment can therefore be sending
 26    ///         credentials and addresses in clear text across the network while looking correctly
 27    ///         configured, which is exactly the failure worth a start-up warning.
 28    ///     </para>
 29    ///     <para>
 30    ///         It warns by default rather than refusing to start, which is a deliberate departure
 31    ///         from how this codebase treats other security configuration — TH-23's email control
 32    ///         fails start-up in Production. The difference is that failing by default would take
 33    ///         down a working deployment on upgrade over a setting the operator may not control
 34    ///         directly.
 35    ///     </para>
 36    ///     <para>
 37    ///         Setting <c>HEIMDALL_DATA_REQUIRE_TLS=true</c> turns the warning into a refusal. It is
 38    ///         opt-in rather than the default for that reason, and it is the right setting for any
 39    ///         deployment that has confirmed TLS is in place: from that point, a connection string
 40    ///         that does not ask for encryption is a misconfiguration rather than a known state, and
 41    ///         starting anyway would be starting in the one condition the check exists to prevent.
 42    ///     </para>
 43    /// </remarks>
 44    internal static void WarnIfDatabaseConnectionIsNotEncrypted()
 45    {
 97346        var connectionString = Environment.GetEnvironmentVariable("HEIMDALL_DATA_CONNECTIONSTRING");
 47
 97348        if (string.IsNullOrWhiteSpace(connectionString))
 49        {
 050            return;
 51        }
 52
 97353        var required = string.Equals(
 97354            Environment.GetEnvironmentVariable(RequireDatabaseTlsVariable),
 97355            "true",
 97356            StringComparison.OrdinalIgnoreCase);
 57
 97358        var requiresTls = connectionString
 97359            .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
 486560            .Where(part => part.Contains('='))
 486561            .Select(part => part.Split('=', 2))
 486562            .Where(pair => pair[0].Replace(" ", string.Empty)
 486563                .Equals("SSLMode", StringComparison.OrdinalIgnoreCase))
 97364            .Any(pair => pair[1].Trim() is "Require" or "VerifyCA" or "VerifyFull");
 65
 97366        if (requiresTls)
 67        {
 068            return;
 69        }
 70
 71        const string explanation =
 72            "The database connection does not require TLS. Npgsql defaults to SSL Mode=Prefer, " +
 73            "which falls back to an unencrypted connection — every credential and address this " +
 74            "API stores would cross the network in clear text. Set SSL Mode=Require or stronger";
 75
 97376        if (required)
 77        {
 78            // Opted in, so the operator has said TLS is in place: a connection string that does not
 79            // ask for it is then a misconfiguration to fail on rather than warn about.
 080            throw new InvalidOperationException(
 081                $"{explanation}. {RequireDatabaseTlsVariable} is set, so this is refused rather than warned about.");
 82        }
 83
 97384        Log.Warning("{Explanation}", explanation);
 97385    }
 86}