Skip to content

Commit

Permalink
feat(#461): Add credential helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
harrhp authored and HofmeisterAn committed Jun 16, 2022
1 parent fff8c08 commit 9731c89
Show file tree
Hide file tree
Showing 22 changed files with 186 additions and 74 deletions.
8 changes: 4 additions & 4 deletions src/Testcontainers/Builders/Base64Provider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;

/// <inheritdoc cref="IAuthenticationProvider{TAuthenticationConfiguration}" />
internal sealed class Base64Provider : IAuthenticationProvider<IDockerRegistryAuthenticationConfiguration>
/// <inheritdoc />
internal sealed class Base64Provider : IDockerRegistryAuthenticationProvider
{
private readonly JsonElement rootElement;

Expand Down Expand Up @@ -39,7 +39,7 @@ public Base64Provider(JsonElement jsonElement, ILogger logger)
}

/// <inheritdoc />
public bool IsApplicable()
public bool IsApplicable(string hostname)
{
return !default(JsonElement).Equals(this.rootElement) && this.rootElement.EnumerateObject().Any();
}
Expand All @@ -49,7 +49,7 @@ public IDockerRegistryAuthenticationConfiguration GetAuthConfig(string hostname)
{
this.logger.SearchingDockerRegistryCredential("Auths");

if (!this.IsApplicable())
if (!this.IsApplicable(hostname))
{
return null;
}
Expand Down
56 changes: 51 additions & 5 deletions src/Testcontainers/Builders/CredsHelperProvider.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,66 @@
namespace DotNet.Testcontainers.Builders
{
using System.Text.Json;
using DotNet.Testcontainers.Configurations;
using Microsoft.Extensions.Logging;

/// <inheritdoc cref="IAuthenticationProvider{TAuthenticationConfiguration}" />
internal sealed class CredsHelperProvider : IAuthenticationProvider<IDockerRegistryAuthenticationConfiguration>
/// <inheritdoc />
internal sealed class CredsHelperProvider : IDockerRegistryAuthenticationProvider
{
private const string TokenUsername = "<token>";
private readonly JsonElement dockerConfig;
private readonly ILogger logger;

public CredsHelperProvider(JsonElement dockerConfig, ILogger logger)
{
this.dockerConfig = dockerConfig;
this.logger = logger;
}

/// <inheritdoc />
public bool IsApplicable()
public bool IsApplicable(string hostname)
{
return false;
return this.FindCredHelperName(hostname) != null;
}

/// <inheritdoc />
public IDockerRegistryAuthenticationConfiguration GetAuthConfig(string hostname)
{
return null;
this.logger.SearchingDockerRegistryCredential("CredsHelper");
var credHelperName = this.FindCredHelperName(hostname);
if (credHelperName == null)
{
return null;
}

var credentialProviderOutput = ExternalProcessCredentialProvider.GetCredentialProviderOutput(credHelperName, hostname);
if (credentialProviderOutput == null)
{
return null;
}

DockerRegistryAuthenticationConfiguration dockerRegistryAuthenticationConfiguration;
try
{
var credentialProviderOutputJson = JsonSerializer.Deserialize<JsonElement>(credentialProviderOutput);
var username = credentialProviderOutputJson.GetProperty("Username").GetString();
var secret = credentialProviderOutputJson.GetProperty("Secret").GetString();
dockerRegistryAuthenticationConfiguration = username == TokenUsername ? new DockerRegistryAuthenticationConfiguration(hostname, identityToken: secret) : new DockerRegistryAuthenticationConfiguration(hostname, username, secret);
}
catch (JsonException)
{
return null;
}

this.logger.DockerRegistryCredentialFound(hostname);
return dockerRegistryAuthenticationConfiguration;
}

private string FindCredHelperName(string hostname)
{
return this.dockerConfig.TryGetProperty("credHelpers", out var credHelpersProperty) && credHelpersProperty.ValueKind != JsonValueKind.Null && credHelpersProperty.TryGetProperty(hostname, out var credHelperProperty)
? credHelperProperty.GetString()
: null;
}
}
}
52 changes: 19 additions & 33 deletions src/Testcontainers/Builders/CredsStoreProvider.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
namespace DotNet.Testcontainers.Builders
{
using System.Diagnostics;
using System.Text.Json;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Configurations;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;

/// <inheritdoc cref="IAuthenticationProvider{TAuthenticationConfiguration}" />
internal sealed class CredsStoreProvider : IAuthenticationProvider<IDockerRegistryAuthenticationConfiguration>
/// <inheritdoc />
internal sealed class CredsStoreProvider : IDockerRegistryAuthenticationProvider
{
private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions();

Expand Down Expand Up @@ -45,7 +44,7 @@ public CredsStoreProvider(JsonElement jsonElement, ILogger logger)
}

/// <inheritdoc />
public bool IsApplicable()
public bool IsApplicable(string hostname)
{
return !default(JsonElement).Equals(this.rootElement) && !string.IsNullOrEmpty(this.rootElement.GetString());
}
Expand All @@ -55,43 +54,30 @@ public IDockerRegistryAuthenticationConfiguration GetAuthConfig(string hostname)
{
this.logger.SearchingDockerRegistryCredential("CredsStore");

if (!this.IsApplicable())
if (!this.IsApplicable(hostname))
{
return null;
}

var dockerCredentialStartInfo = new ProcessStartInfo();
dockerCredentialStartInfo.FileName = "docker-credential-" + this.rootElement.GetString();
dockerCredentialStartInfo.Arguments = "get";
dockerCredentialStartInfo.RedirectStandardInput = true;
dockerCredentialStartInfo.RedirectStandardOutput = true;
dockerCredentialStartInfo.UseShellExecute = false;

using (var dockerCredentialProcess = Process.Start(dockerCredentialStartInfo))
var credentialProviderOutput = ExternalProcessCredentialProvider.GetCredentialProviderOutput(this.rootElement.GetString(), hostname);
if (credentialProviderOutput == null)
{
if (dockerCredentialProcess == null)
{
return null;
}

dockerCredentialProcess.StandardInput.WriteLine(hostname);
dockerCredentialProcess.StandardInput.Close();

var stdOut = dockerCredentialProcess.StandardOutput.ReadToEnd();
AuthConfig authConfig;
return null;
}

try
{
authConfig = JsonSerializer.Deserialize<AuthConfig>(JsonDocument.Parse(stdOut).RootElement.GetRawText(), JsonSerializerOptions);
}
catch (JsonException)
{
return null;
}
AuthConfig authConfig;

this.logger.DockerRegistryCredentialFound(hostname);
return new DockerRegistryAuthenticationConfiguration(authConfig.ServerAddress, authConfig.Username, authConfig.Password);
try
{
authConfig = JsonSerializer.Deserialize<AuthConfig>(JsonDocument.Parse(credentialProviderOutput).RootElement.GetRawText(), JsonSerializerOptions);
}
catch (JsonException)
{
return null;
}

this.logger.DockerRegistryCredentialFound(hostname);
return new DockerRegistryAuthenticationConfiguration(authConfig.ServerAddress, authConfig.Username, authConfig.Password);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
using System.Linq;
using DotNet.Testcontainers.Configurations;

/// <inheritdoc cref="IAuthenticationProvider{TAuthenticationConfiguration}" />
internal sealed class DockerEndpointAuthenticationProvider : IAuthenticationProvider<IDockerEndpointAuthenticationConfiguration>
/// <inheritdoc />
internal sealed class DockerEndpointAuthenticationProvider : IDockerEndpointAuthenticationProvider
{
/// <inheritdoc />
public bool IsApplicable()
Expand All @@ -13,11 +13,11 @@ public bool IsApplicable()
}

/// <inheritdoc />
public IDockerEndpointAuthenticationConfiguration GetAuthConfig(string hostname)
public IDockerEndpointAuthenticationConfiguration GetAuthConfig()
{
return new IAuthenticationProvider<IDockerEndpointAuthenticationConfiguration>[] { new EnvironmentEndpointAuthenticationProvider(), new NpipeEndpointAuthenticationProvider(), new UnixEndpointAuthenticationProvider() }
return new IDockerEndpointAuthenticationProvider[] { new EnvironmentEndpointAuthenticationProvider(), new NpipeEndpointAuthenticationProvider(), new UnixEndpointAuthenticationProvider() }
.First(authenticationProvider => authenticationProvider.IsApplicable())
.GetAuthConfig(hostname);
.GetAuthConfig();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;

/// <inheritdoc cref="IAuthenticationProvider{TAuthenticationConfiguration}" />
internal sealed class DockerRegistryAuthenticationProvider : IAuthenticationProvider<IDockerRegistryAuthenticationConfiguration>
/// <inheritdoc />
internal sealed class DockerRegistryAuthenticationProvider : IDockerRegistryAuthenticationProvider
{
private const string DockerHub = "index.docker.io";

Expand Down Expand Up @@ -54,7 +54,7 @@ public DockerRegistryAuthenticationProvider(FileInfo dockerConfigFile, ILogger l
}

/// <inheritdoc />
public bool IsApplicable()
public bool IsApplicable(string hostname)
{
return true;
}
Expand All @@ -81,8 +81,12 @@ private IDockerRegistryAuthenticationConfiguration GetUncachedAuthConfig(string
{
using (var dockerConfigDocument = JsonDocument.Parse(dockerConfigFileStream))
{
authConfig = new IAuthenticationProvider<IDockerRegistryAuthenticationConfiguration>[] { new CredsHelperProvider(), new CredsStoreProvider(dockerConfigDocument, this.logger), new Base64Provider(dockerConfigDocument, this.logger) }
.AsParallel()
authConfig = new IDockerRegistryAuthenticationProvider[]
{
new CredsHelperProvider(dockerConfigDocument.RootElement, this.logger),
new CredsStoreProvider(dockerConfigDocument, this.logger),
new Base64Provider(dockerConfigDocument, this.logger),
}.AsParallel()
.Select(authenticationProvider => authenticationProvider.GetAuthConfig(hostname))
.FirstOrDefault(authenticationProvider => authenticationProvider != null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
using System;
using DotNet.Testcontainers.Configurations;

/// <inheritdoc cref="IAuthenticationProvider{TAuthenticationConfiguration}" />
internal sealed class EnvironmentEndpointAuthenticationProvider : IAuthenticationProvider<IDockerEndpointAuthenticationConfiguration>
/// <inheritdoc />
internal sealed class EnvironmentEndpointAuthenticationProvider : IDockerEndpointAuthenticationProvider
{
private readonly Uri dockerEngine;

Expand All @@ -20,7 +20,7 @@ public bool IsApplicable()
}

/// <inheritdoc />
public IDockerEndpointAuthenticationConfiguration GetAuthConfig(string hostname)
public IDockerEndpointAuthenticationConfiguration GetAuthConfig()
{
return new DockerEndpointAuthenticationConfiguration(this.dockerEngine);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
using System.Runtime.InteropServices;
using DotNet.Testcontainers.Configurations;

/// <inheritdoc cref="IAuthenticationProvider{TAuthenticationConfiguration}" />
internal sealed class NpipeEndpointAuthenticationProvider : IAuthenticationProvider<IDockerEndpointAuthenticationConfiguration>
/// <inheritdoc />
internal sealed class NpipeEndpointAuthenticationProvider : IDockerEndpointAuthenticationProvider
{
#pragma warning disable S1075

Expand All @@ -20,7 +20,7 @@ public bool IsApplicable()
}

/// <inheritdoc />
public IDockerEndpointAuthenticationConfiguration GetAuthConfig(string hostname)
public IDockerEndpointAuthenticationConfiguration GetAuthConfig()
{
return new DockerEndpointAuthenticationConfiguration(DockerEngine);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
using System.Runtime.InteropServices;
using DotNet.Testcontainers.Configurations;

/// <inheritdoc cref="IAuthenticationProvider{TAuthenticationConfiguration}" />
internal sealed class UnixEndpointAuthenticationProvider : IAuthenticationProvider<IDockerEndpointAuthenticationConfiguration>
/// <inheritdoc />
internal sealed class UnixEndpointAuthenticationProvider : IDockerEndpointAuthenticationProvider
{
private static readonly Uri DockerEngine = new Uri("unix:/var/run/docker.sock");

Expand All @@ -16,7 +16,7 @@ public bool IsApplicable()
}

/// <inheritdoc />
public IDockerEndpointAuthenticationConfiguration GetAuthConfig(string hostname)
public IDockerEndpointAuthenticationConfiguration GetAuthConfig()
{
return new DockerEndpointAuthenticationConfiguration(DockerEngine);
}
Expand Down
1 change: 1 addition & 0 deletions src/Testcontainers/Clients/DockerImageOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public async Task CreateAsync(IDockerImage image, IDockerRegistryAuthenticationC
ServerAddress = dockerRegistryAuthConfig.RegistryEndpoint,
Username = dockerRegistryAuthConfig.Username,
Password = dockerRegistryAuthConfig.Password,
IdentityToken = dockerRegistryAuthConfig.IdentityToken,
};

await this.Docker.Images.CreateImageAsync(createParameters, authConfig, this.traceProgress, ct)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ namespace DotNet.Testcontainers.Configurations
public DockerRegistryAuthenticationConfiguration(
string registryEndpoint = null,
string username = null,
string password = null)
string password = null,
string identityToken = null)
{
this.RegistryEndpoint = registryEndpoint;
this.Username = username;
this.Password = password;
this.IdentityToken = identityToken;
}

/// <inheritdoc />
Expand All @@ -30,5 +32,8 @@ public DockerRegistryAuthenticationConfiguration(

/// <inheritdoc />
public string Password { get; }

/// <inheritdoc />
public string IdentityToken { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,11 @@ public interface IDockerRegistryAuthenticationConfiguration
/// </summary>
[CanBeNull]
string Password { get; }

/// <summary>
/// Gets the identity token.
/// </summary>
[CanBeNull]
string IdentityToken { get; }
}
}
2 changes: 1 addition & 1 deletion src/Testcontainers/Configurations/Unix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public sealed class Unix : IOperatingSystem
[PublicAPI]
public Unix()
: this(new DockerEndpointAuthenticationProvider()
.GetAuthConfig(null))
.GetAuthConfig())
{
}

Expand Down
2 changes: 1 addition & 1 deletion src/Testcontainers/Configurations/Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public sealed class Windows : IOperatingSystem
[PublicAPI]
public Windows()
: this(new DockerEndpointAuthenticationProvider()
.GetAuthConfig(null))
.GetAuthConfig())
{
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@echo off
echo {"Username":"username","Secret":"password"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

echo '{"Username":"username","Secret":"password"}'
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@echo off
echo {"Username":"<token>","Secret":"identitytoken"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

echo '{"Username":"<token>","Secret":"identitytoken"}'
1 change: 1 addition & 0 deletions tests/Testcontainers.Tests/Assets/.dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
**/*.md
credHelpers
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public EnvironmentEndpointAuthenticationProviderTest()
[Fact]
public void GetAuthConfig()
{
using (var clientConfiguration = new DockerEndpointAuthenticationProvider().GetAuthConfig(null)!.GetDockerClientConfiguration())
using (var clientConfiguration = new DockerEndpointAuthenticationProvider().GetAuthConfig()!.GetDockerClientConfiguration())
{
Assert.Equal(DockerHost, clientConfiguration.EndpointBaseUri.ToString());
}
Expand Down
Loading

0 comments on commit 9731c89

Please sign in to comment.