Skip to content

Commit ab8e8db

Browse files
authored
Add support for environment variable PGSSLNEGOTIATION (#5882)
Related to #5677 Followup to #5881
1 parent 588b018 commit ab8e8db

4 files changed

Lines changed: 49 additions & 7 deletions

File tree

src/Npgsql/Internal/NpgsqlConnector.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -783,9 +783,9 @@ async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, Cancellat
783783

784784
IsSecure = false;
785785

786-
if (Settings.SslNegotiation == SslNegotiation.Direct)
786+
if (GetSslNegotiation(Settings) == SslNegotiation.Direct)
787787
{
788-
// We already check that in NpgsqlConnectionStringBuilder.PostProcessAndValidate, but just on the off case
788+
// We already check that in NpgsqlConnectionStringBuilder.PostProcessAndValidate, but since we also allow environment variables...
789789
if (Settings.SslMode is not SslMode.Require and not SslMode.VerifyCA and not SslMode.VerifyFull)
790790
throw new ArgumentException("SSL Mode has to be Require or higher to be used with direct SSL Negotiation");
791791
await DataSource.TransportSecurityHandler.NegotiateEncryption(async, this, sslMode, timeout, cancellationToken).ConfigureAwait(false);
@@ -836,6 +836,20 @@ async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, Cancellat
836836
}
837837
}
838838

839+
static SslNegotiation GetSslNegotiation(NpgsqlConnectionStringBuilder settings)
840+
{
841+
if (settings.UserProvidedSslNegotiation is { } userProvidedSslNegotiation)
842+
return userProvidedSslNegotiation;
843+
844+
if (PostgresEnvironment.SslNegotiation is { } sslNegotiationEnv)
845+
{
846+
if (Enum.TryParse<SslNegotiation>(sslNegotiationEnv, ignoreCase: true, out var sslNegotiation))
847+
return sslNegotiation;
848+
}
849+
850+
return SslNegotiation.Postgres;
851+
}
852+
839853
internal async Task NegotiateEncryption(SslMode sslMode, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken)
840854
{
841855
var clientCertificates = new X509Certificate2Collection();

src/Npgsql/NpgsqlConnectionStringBuilder.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -467,19 +467,18 @@ public SslMode SslMode
467467
[Category("Security")]
468468
[Description("Controls how SSL encryption is negotiated with the server, if SSL is used.")]
469469
[DisplayName("SSL Negotiation")]
470-
[DefaultValue(SslNegotiation.Postgres)]
471470
[NpgsqlConnectionStringProperty]
472471
public SslNegotiation SslNegotiation
473472
{
474-
get => _sslNegotiation;
473+
get => UserProvidedSslNegotiation ?? SslNegotiation.Postgres;
475474
set
476475
{
477-
_sslNegotiation = value;
476+
UserProvidedSslNegotiation = value;
478477
SetValue(nameof(SslNegotiation), value);
479478
}
480479
}
481480

482-
SslNegotiation _sslNegotiation;
481+
internal SslNegotiation? UserProvidedSslNegotiation { get; private set; }
483482

484483
/// <summary>
485484
/// Location of a client certificate to be sent to the server.

src/Npgsql/PostgresEnvironment.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,13 @@ internal static string? SslCertRootDefault
4848

4949
internal static string? TargetSessionAttributes => Environment.GetEnvironmentVariable("PGTARGETSESSIONATTRS");
5050

51+
internal static string? SslNegotiation => Environment.GetEnvironmentVariable("PGSSLNEGOTIATION");
52+
5153
static string? GetHomeDir()
5254
=> Environment.GetEnvironmentVariable(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "APPDATA" : "HOME");
5355

5456
static string? GetHomePostgresDir()
5557
=> GetHomeDir() is string homedir
5658
? Path.Combine(homedir, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "postgresql" : ".postgresql")
5759
: null;
58-
}
60+
}

test/Npgsql.Tests/SecurityTests.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,33 @@ public void Direct_ssl_requires_correct_sslmode([Values] SslMode sslMode)
522522
}
523523
}
524524

525+
[Test]
526+
[NonParallelizable] // Sets environment variable
527+
public async Task Direct_ssl_via_env_requires_correct_sslmode()
528+
{
529+
await using var adminConn = await OpenConnectionAsync();
530+
MinimumPgVersion(adminConn, "17.0");
531+
532+
// NonParallelizable attribute doesn't work with parameters that well
533+
foreach (var sslMode in new[] { SslMode.Disable, SslMode.Allow, SslMode.Prefer, SslMode.Require })
534+
{
535+
using var _ = SetEnvironmentVariable("PGSSLNEGOTIATION", nameof(SslNegotiation.Direct));
536+
await using var dataSource = CreateDataSource(csb =>
537+
{
538+
csb.SslMode = sslMode;
539+
});
540+
if (sslMode is SslMode.Disable or SslMode.Allow or SslMode.Prefer)
541+
{
542+
var ex = Assert.ThrowsAsync<ArgumentException>(async () => await dataSource.OpenConnectionAsync())!;
543+
Assert.That(ex.Message, Is.EqualTo("SSL Mode has to be Require or higher to be used with direct SSL Negotiation"));
544+
}
545+
else
546+
{
547+
await using var conn = await dataSource.OpenConnectionAsync();
548+
}
549+
}
550+
}
551+
525552
#region Setup / Teardown / Utils
526553

527554
[OneTimeSetUp]

0 commit comments

Comments
 (0)