Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:

env:
# Lowest API version that GitHub runners support.
DOCKER_API_VERSION: 1.52
DOCKER_API_VERSION: 1.44

steps:
- name: Checkout Repository
Expand Down
23 changes: 23 additions & 0 deletions docs/modules/postgres.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,29 @@ The following example utilizes the [xUnit.net](/test_frameworks/xunit_net/) modu
--8<-- "tests/Testcontainers.PostgreSql.Tests/PostgreSqlContainerTest.cs:UsePostgreSqlContainer"
```

## SSL

Use `WithSsl` to enable TLS and map the server certificates. Configure the client connection string with `SslMode` and (for validation) the CA certificate.

!!! note
When SSL is enabled, Testcontainers doesn't set the SSL mode for the connection string. You'll need to choose the `SslMode` and configure it yourself.

```csharp
--8<-- "tests/Testcontainers.PostgreSql.Tests/PostgreSqlContainerTest.cs:PostgreSqlSslBuilder"
```

```csharp
--8<-- "tests/Testcontainers.PostgreSql.Tests/PostgreSqlContainerTest.cs:PostgreSqlSslConnectionString"
```

### VerifyFull and DNS SANs

`SslMode=VerifyFull` validates DNS SANs. Use a DNS host like `localhost` if you need full verification.

```csharp
--8<-- "tests/Testcontainers.PostgreSql.Tests/PostgreSqlContainerTest.cs:PostgreSqlSslVerifyFull"
```

The test example uses the following NuGet dependencies:

=== "Package References"
Expand Down
39 changes: 39 additions & 0 deletions src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ public sealed class PostgreSqlBuilder : ContainerBuilder<PostgreSqlBuilder, Post

public const string DefaultPassword = "postgres";

private const ushort PostgresUid = 999;

private const ushort PostgresGid = 999;

private const string CertificateFilePath = "/etc/ssl/postgresql/server.crt";

private const string CertificateKeyFilePath = "/etc/ssl/postgresql/server.key";

private const string CaCertificateFilePath = "/etc/ssl/postgresql/root.crt";

/// <summary>
/// Initializes a new instance of the <see cref="PostgreSqlBuilder" /> class.
/// </summary>
Expand Down Expand Up @@ -102,6 +112,35 @@ public PostgreSqlBuilder WithPassword(string password)
.WithEnvironment("POSTGRES_PASSWORD", password);
}

/// <summary>
/// Enables SSL for PostgreSql.
/// </summary>
/// <param name="certificateFilePath">The SSL certificate file.</param>
/// <param name="certificateKeyFilePath">The SSL certificate private key file.</param>
/// <returns>A configured instance of <see cref="PostgreSqlBuilder" />.</returns>
public PostgreSqlBuilder WithSsl(string certificateFilePath, string certificateKeyFilePath)
{
return WithResourceMapping(new FileInfo(certificateFilePath), new FileInfo(CertificateFilePath), PostgresUid, PostgresGid, Unix.FileMode600)
.WithResourceMapping(new FileInfo(certificateKeyFilePath), new FileInfo(CertificateKeyFilePath), PostgresUid, PostgresGid, Unix.FileMode600)
.WithCommand("-c", "ssl=on")
.WithCommand("-c", "ssl_cert_file=" + CertificateFilePath)
.WithCommand("-c", "ssl_key_file=" + CertificateKeyFilePath);
}

/// <summary>
/// Enables SSL for PostgreSql.
/// </summary>
/// <param name="certificateFilePath">The SSL certificate file.</param>
/// <param name="certificateKeyFilePath">The SSL certificate private key file.</param>
/// <param name="caCertificateFilePath">The CA certificate file.</param>
/// <returns>A configured instance of <see cref="PostgreSqlBuilder" />.</returns>
public PostgreSqlBuilder WithSsl(string certificateFilePath, string certificateKeyFilePath, string caCertificateFilePath)
{
return WithSsl(certificateFilePath, certificateKeyFilePath)
.WithResourceMapping(new FileInfo(caCertificateFilePath), new FileInfo(CaCertificateFilePath), PostgresUid, PostgresGid, Unix.FileMode600)
.WithCommand("-c", "ssl_ca_file=" + CaCertificateFilePath);
}

/// <inheritdoc />
public override PostgreSqlContainer Build()
{
Expand Down
7 changes: 7 additions & 0 deletions src/Testcontainers/Configurations/Unix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ namespace DotNet.Testcontainers.Configurations
[PublicAPI]
public sealed class Unix : IOperatingSystem
{
/// <summary>
/// Represents the Unix file mode 600, which grants read and write permissions to the user and no permissions to the group and others.
/// </summary>
public const UnixFileModes FileMode600 =
UnixFileModes.UserRead |
UnixFileModes.UserWrite;

/// <summary>
/// Represents the Unix file mode 644, which grants read and write permissions to the user and read permissions to the group and others.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ namespace Testcontainers.PostgreSql;

public abstract class PostgreSqlContainerTest(PostgreSqlContainerTest.PostgreSqlDefaultFixture fixture)
{
private static readonly string ServerCertificateFilePath = Certificates.Instance.GetFilePath("server", "server.crt");

private static readonly string ServerCertificateKeyFilePath = Certificates.Instance.GetFilePath("server", "server.key");

private static readonly string CaCertificateFilePath = Certificates.Instance.GetFilePath("ca", "ca.crt");

Comment thread
HofmeisterAn marked this conversation as resolved.
// # --8<-- [start:UsePostgreSqlContainer]
[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
Expand Down Expand Up @@ -85,11 +91,90 @@ protected override PostgreSqlBuilder Configure()
=> base.Configure().WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory));
}

[UsedImplicitly]
public class PostgreSqlSslRequireFixture(IMessageSink messageSink)
: PostgreSqlDefaultFixture(messageSink)
{
protected override PostgreSqlBuilder Configure()
=> base.Configure().WithSsl(ServerCertificateFilePath, ServerCertificateKeyFilePath);

public override string ConnectionString
{
get
{
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(base.ConnectionString);
connectionStringBuilder.TrustServerCertificate = true;
connectionStringBuilder.SslMode = SslMode.Require;
return connectionStringBuilder.ConnectionString;
}
}
}

[UsedImplicitly]
public class PostgreSqlSslVerifyCaFixture(IMessageSink messageSink)
: PostgreSqlDefaultFixture(messageSink)
{
// # --8<-- [start:PostgreSqlSslBuilder]
protected override PostgreSqlBuilder Configure()
=> base.Configure().WithSsl(ServerCertificateFilePath, ServerCertificateKeyFilePath, CaCertificateFilePath);
// # --8<-- [end:PostgreSqlSslBuilder]

public override string ConnectionString
{
get
{
// # --8<-- [start:PostgreSqlSslConnectionString]
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(base.ConnectionString);
connectionStringBuilder.SslMode = SslMode.VerifyCA;
connectionStringBuilder.RootCertificate = CaCertificateFilePath;
return connectionStringBuilder.ConnectionString;
// # --8<-- [end:PostgreSqlSslConnectionString]
}
}
}

[UsedImplicitly]
public class PostgreSqlSslVerifyFullFixture(IMessageSink messageSink)
: PostgreSqlDefaultFixture(messageSink)
{
protected override PostgreSqlBuilder Configure()
=> base.Configure().WithSsl(ServerCertificateFilePath, ServerCertificateKeyFilePath, CaCertificateFilePath);

public override string ConnectionString
{
get
{
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(base.ConnectionString);
// # --8<-- [start:PostgreSqlSslVerifyFull]
// Npgsql checks VerifyFull against DNS SANs, it's necessary to use "localhost" instead of
// the IP address. Testcontainers defaults to using the IP because of an old Docker bug
// with IPv4/IPv6 port mapping, where "localhost" might resolve to a different public port.
connectionStringBuilder.Host = "localhost";
connectionStringBuilder.SslMode = SslMode.VerifyFull;
// # --8<-- [end:PostgreSqlSslVerifyFull]
connectionStringBuilder.RootCertificate = CaCertificateFilePath;
return connectionStringBuilder.ConnectionString;
}
}
}

[UsedImplicitly]
public sealed class PostgreSqlDefaultConfiguration(PostgreSqlDefaultFixture fixture)
: PostgreSqlContainerTest(fixture), IClassFixture<PostgreSqlDefaultFixture>;

[UsedImplicitly]
public sealed class PostgreSqlWaitForDatabaseConfiguration(PostgreSqlWaitForDatabaseFixture fixture)
: PostgreSqlContainerTest(fixture), IClassFixture<PostgreSqlWaitForDatabaseFixture>;

[UsedImplicitly]
public sealed class PostgreSqlSslRequireConfiguration(PostgreSqlSslRequireFixture fixture)
: PostgreSqlContainerTest(fixture), IClassFixture<PostgreSqlSslRequireFixture>;

[UsedImplicitly]
public sealed class PostgreSqlSslVerifyCaConfiguration(PostgreSqlSslVerifyCaFixture fixture)
: PostgreSqlContainerTest(fixture), IClassFixture<PostgreSqlSslVerifyCaFixture>;

[UsedImplicitly]
public sealed class PostgreSqlSslVerifyFullConfiguration(PostgreSqlSslVerifyFullFixture fixture)
: PostgreSqlContainerTest(fixture), IClassFixture<PostgreSqlSslVerifyFullFixture>;
}
Loading