Skip to content

Commit a51c3da

Browse files
committed
feat(container): support multiple registry credentials
1 parent 392aaa2 commit a51c3da

File tree

9 files changed

+76
-73
lines changed

9 files changed

+76
-73
lines changed

src/GZCTF/Extensions/Startup/ServicesExtension.cs

+12-2
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,23 @@ static void AddConfig<TConfig>(this WebApplicationBuilder builder)
2323
internal static void AddServiceConfigurations(this WebApplicationBuilder builder)
2424
{
2525
builder.AddConfig<EmailConfig>();
26-
builder.AddConfig<RegistryConfig>();
2726
builder.AddConfig<AccountPolicy>();
2827
builder.AddConfig<GlobalConfig>();
2928
builder.AddConfig<ContainerPolicy>();
3029
builder.AddConfig<ContainerProvider>();
3130

32-
var forwardedOptions =
31+
builder.Services.Configure<RegistrySet<RegistryConfig>>(builder.Configuration.GetSection("Registries"));
32+
33+
RegistryConfig? oldConfig = builder.Configuration.GetSection(nameof(RegistryConfig)).Get<RegistryConfig>();
34+
if (!string.IsNullOrWhiteSpace(oldConfig?.ServerAddress))
35+
// Add old config to new config set
36+
builder.Services.Configure<RegistrySet<RegistryConfig>>(set =>
37+
{
38+
if (!set.TryAdd(oldConfig.ServerAddress, oldConfig))
39+
set[oldConfig.ServerAddress] = oldConfig;
40+
});
41+
42+
ForwardedOptions? forwardedOptions =
3343
builder.Configuration.GetSection(nameof(ForwardedOptions)).Get<ForwardedOptions>();
3444
if (forwardedOptions is null)
3545
builder.Services.Configure<ForwardedHeadersOptions>(options =>

src/GZCTF/Models/Internal/Configs.cs

+19
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,30 @@ public class KubernetesConfig
293293
public string[]? Dns { get; set; }
294294
}
295295

296+
public class RegistrySet<T> : Dictionary<string, T>
297+
where T : class
298+
{
299+
public T? GetForImage(string image)
300+
{
301+
if (!Uri.TryCreate(image, UriKind.Absolute, out var uri))
302+
return null;
303+
304+
var host = uri.Host;
305+
if (!uri.IsDefaultPort)
306+
host = $"{host}:{uri.Port}";
307+
308+
return TryGetValue(host, out var config) ? config : null;
309+
}
310+
}
311+
296312
public class RegistryConfig
297313
{
298314
public string? ServerAddress { get; set; }
299315
public string? UserName { get; set; }
300316
public string? Password { get; set; }
317+
318+
public bool Valid => !string.IsNullOrEmpty(UserName) &&
319+
!string.IsNullOrEmpty(Password);
301320
}
302321

303322
#endregion

src/GZCTF/Models/Internal/ContainerInfo.cs

-24
This file was deleted.

src/GZCTF/Repositories/ParticipationRepository.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public async Task UpdateParticipation(Participation part, ParticipationEditModel
101101
}
102102

103103
await trans.CommitAsync(token);
104-
104+
105105
if (needFlush)
106106
await cacheHelper.FlushScoreboardCache(part.GameId, token);
107107
}

src/GZCTF/Services/Container/Manager/DockerManager.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,10 @@ await _client.Containers.RemoveContainerAsync(container.ContainerId,
121121
StaticLocalizer[nameof(Resources.Program.ContainerManager_PullContainerImage), config.Image],
122122
TaskStatus.Pending, LogLevel.Information);
123123

124+
var auth = _meta.AuthConfigs.GetForImage(config.Image);
125+
124126
// pull the image and retry
125-
await _client.Images.CreateImageAsync(new() { FromImage = config.Image }, _meta.Auth,
127+
await _client.Images.CreateImageAsync(new() { FromImage = config.Image }, auth,
126128
new Progress<JSONMessage>(msg =>
127129
{
128130
Console.WriteLine($@"{msg.Status}|{msg.ProgressMessage}|{msg.ErrorMessage}");

src/GZCTF/Services/Container/Manager/KubernetesManager.cs

+6-7
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ public KubernetesManager(IContainerProvider<Kubernetes, KubernetesMetadata> prov
4343
return null;
4444
}
4545

46-
var authSecretName = _meta.AuthSecretName;
47-
var options = _meta.Config;
46+
var authSecretName = _meta.AuthSecretNames.GetForImage(config.Image);
47+
KubernetesConfig options = _meta.Config;
4848

4949
var chalImage = imageName.ToValidRFC1123String("chal");
5050

5151
var name = $"{chalImage}-{Guid.NewGuid().ToString("N")[..16]}";
5252

53-
var pod = new V1Pod("v1", "Pod")
53+
var pod = new V1Pod
5454
{
5555
Metadata = new V1ObjectMeta
5656
{
@@ -67,10 +67,6 @@ public KubernetesManager(IContainerProvider<Kubernetes, KubernetesMetadata> prov
6767
},
6868
Spec = new V1PodSpec
6969
{
70-
ImagePullSecrets =
71-
authSecretName is null
72-
? Array.Empty<V1LocalObjectReference>()
73-
: new List<V1LocalObjectReference> { new() { Name = authSecretName } },
7470
DnsPolicy = "None",
7571
DnsConfig = new() { Nameservers = options.Dns ?? ["223.5.5.5", "114.114.114.114"] },
7672
EnableServiceLinks = false,
@@ -113,6 +109,9 @@ config.Flag is null
113109
}
114110
};
115111

112+
if (authSecretName is not null)
113+
pod.Spec.ImagePullSecrets.Add(new() { Name = authSecretName });
114+
116115
try
117116
{
118117
pod = await _client.CreateNamespacedPodAsync(pod, options.Namespace, cancellationToken: token);

src/GZCTF/Services/Container/Manager/SwarmManager.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public async Task DestroyContainerAsync(Models.Data.Container container, Cancell
8181
return null;
8282
}
8383

84-
var parameters = GetServiceCreateParameters(config);
84+
ServiceCreateParameters parameters = GetServiceCreateParameters(config);
8585
ServiceCreateResponse? serviceRes;
8686
var retry = 0;
8787

@@ -171,7 +171,7 @@ public async Task DestroyContainerAsync(Models.Data.Container container, Cancell
171171
ServiceCreateParameters GetServiceCreateParameters(ContainerConfig config) =>
172172
new()
173173
{
174-
RegistryAuth = _meta.Auth,
174+
RegistryAuth = _meta.AuthConfigs.GetForImage(config.Image),
175175
Service = new()
176176
{
177177
Name = DockerMetadata.GetName(config),

src/GZCTF/Services/Container/Provider/DockerProvider.cs

+12-6
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class DockerMetadata : ContainerProviderMetadata
1616
/// <summary>
1717
/// Docker 鉴权用配置
1818
/// </summary>
19-
public AuthConfig? Auth { get; set; }
19+
public RegistrySet<AuthConfig> AuthConfigs { get; set; } = new();
2020

2121
/// <summary>
2222
/// 根据配置获取容器名称
@@ -33,7 +33,7 @@ public class DockerProvider : IContainerProvider<DockerClient, DockerMetadata>
3333
readonly DockerClient _dockerClient;
3434
readonly DockerMetadata _dockerMeta;
3535

36-
public DockerProvider(IOptions<ContainerProvider> options, IOptions<RegistryConfig> registry,
36+
public DockerProvider(IOptions<ContainerProvider> options, IOptions<RegistrySet<RegistryConfig>> registriesOptions,
3737
ILogger<DockerProvider> logger)
3838
{
3939
_dockerMeta = new()
@@ -54,10 +54,16 @@ public DockerProvider(IOptions<ContainerProvider> options, IOptions<RegistryConf
5454

5555
_dockerClient = cfg.CreateClient();
5656

57-
// Auth for registry
58-
if (!string.IsNullOrWhiteSpace(registry.Value.UserName) && !string.IsNullOrWhiteSpace(registry.Value.Password))
59-
_dockerMeta.Auth =
60-
new AuthConfig { Username = registry.Value.UserName, Password = registry.Value.Password };
57+
var registries = registriesOptions.Value;
58+
59+
foreach (var registry in registries.Where(registry =>
60+
registry.Value.Valid))
61+
{
62+
var authConfig = new AuthConfig { Username = registry.Value.UserName, Password = registry.Value.Password };
63+
64+
if (!_dockerMeta.AuthConfigs.TryAdd(registry.Key, authConfig))
65+
_dockerMeta.AuthConfigs[registry.Key] = authConfig;
66+
}
6167

6268
logger.SystemLog(
6369
StaticLocalizer[nameof(Resources.Program.ContainerProvider_DockerInited),

src/GZCTF/Services/Container/Provider/KubernetesProvider.cs

+21-30
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class KubernetesMetadata : ContainerProviderMetadata
1111
/// <summary>
1212
/// 容器注册表鉴权 Secret 名称
1313
/// </summary>
14-
public string? AuthSecretName { get; set; }
14+
public RegistrySet<string> AuthSecretNames { get; set; } = new();
1515

1616
/// <summary>
1717
/// K8s 集群 Host IP
@@ -31,7 +31,7 @@ public class KubernetesProvider : IContainerProvider<Kubernetes, KubernetesMetad
3131
readonly Kubernetes _kubernetesClient;
3232
readonly KubernetesMetadata _kubernetesMetadata;
3333

34-
public KubernetesProvider(IOptions<RegistryConfig> registry, IOptions<ContainerProvider> options,
34+
public KubernetesProvider(IOptions<RegistrySet<RegistryConfig>> registries, IOptions<ContainerProvider> options,
3535
ILogger<KubernetesProvider> logger)
3636
{
3737
_kubernetesMetadata = new()
@@ -63,21 +63,9 @@ public KubernetesProvider(IOptions<RegistryConfig> registry, IOptions<ContainerP
6363

6464
_kubernetesClient = new Kubernetes(config);
6565

66-
var registryValue = registry.Value;
67-
var withAuth = !string.IsNullOrWhiteSpace(registryValue.ServerAddress)
68-
&& !string.IsNullOrWhiteSpace(registryValue.UserName)
69-
&& !string.IsNullOrWhiteSpace(registryValue.Password);
70-
71-
if (withAuth)
72-
{
73-
var padding =
74-
$"{registryValue.UserName}@{registryValue.Password}@{registryValue.ServerAddress}".ToMD5String();
75-
_kubernetesMetadata.AuthSecretName = $"{registryValue.UserName}-{padding}".ToValidRFC1123String("secret");
76-
}
77-
7866
try
7967
{
80-
InitKubernetes(withAuth, registryValue);
68+
InitKubernetes(registries.Value);
8169
}
8270
catch (Exception e)
8371
{
@@ -96,7 +84,7 @@ public KubernetesProvider(IOptions<RegistryConfig> registry, IOptions<ContainerP
9684

9785
public KubernetesMetadata GetMetadata() => _kubernetesMetadata;
9886

99-
void InitKubernetes(bool withAuth, RegistryConfig? registry)
87+
void InitKubernetes(RegistrySet<RegistryConfig> registries)
10088
{
10189
if (_kubernetesClient.CoreV1.ListNamespace().Items
10290
.All(ns => ns.Metadata.Name != _kubernetesMetadata.Config.Namespace))
@@ -133,43 +121,46 @@ void InitKubernetes(bool withAuth, RegistryConfig? registry)
133121
}
134122
}, _kubernetesMetadata.Config.Namespace);
135123

136-
if (!withAuth || registry?.ServerAddress is null)
137-
return;
124+
// create auth secrets for registries
125+
foreach (KeyValuePair<string, RegistryConfig> registry in registries.Where(registry => registry.Value.Valid))
126+
InsertRegistrySecret(registry.Key, registry.Value);
127+
}
128+
129+
void InsertRegistrySecret(string address, RegistryConfig registry)
130+
{
131+
var padding = $"GZCTF@{registry.UserName}@{address}".ToMD5String();
132+
var secretName = $"{registry.UserName}-{padding}".ToValidRFC1123String("secret");
138133

139134
var auth = Codec.Base64.Encode($"{registry.UserName}:{registry.Password}");
140135
var dockerJsonObj = new DockerRegistryOptions(
141-
new Dictionary<string, DockerRegistryEntry>
142-
{
143-
[registry.ServerAddress] = new(auth, registry.UserName, registry.Password)
144-
}
136+
new Dictionary<string, DockerRegistryEntry> { [address] = new(auth, registry.UserName, registry.Password) }
145137
);
146138

147139
var dockerJsonBytes =
148140
JsonSerializer.SerializeToUtf8Bytes(dockerJsonObj, AppJsonSerializerContext.Default.DockerRegistryOptions);
149141
var secret = new V1Secret
150142
{
151143
Metadata =
152-
new V1ObjectMeta
153-
{
154-
Name = _kubernetesMetadata.AuthSecretName,
155-
NamespaceProperty = _kubernetesMetadata.Config.Namespace
156-
},
144+
new V1ObjectMeta { Name = secretName, NamespaceProperty = _kubernetesMetadata.Config.Namespace },
157145
Data = new Dictionary<string, byte[]> { [".dockerconfigjson"] = dockerJsonBytes },
158146
Type = "kubernetes.io/dockerconfigjson"
159147
};
160148

161149
try
162150
{
163-
_kubernetesClient.CoreV1.ReplaceNamespacedSecret(secret, _kubernetesMetadata.AuthSecretName,
151+
_kubernetesClient.CoreV1.ReplaceNamespacedSecret(secret, secretName,
164152
_kubernetesMetadata.Config.Namespace);
165153
}
166154
catch
167155
{
168156
_kubernetesClient.CoreV1.CreateNamespacedSecret(secret, _kubernetesMetadata.Config.Namespace);
169157
}
158+
159+
if (!_kubernetesMetadata.AuthSecretNames.TryAdd(address, secretName))
160+
_kubernetesMetadata.AuthSecretNames[address] = secretName;
170161
}
171162
}
172163

173-
internal record DockerRegistryOptions(Dictionary<string, DockerRegistryEntry> auths);
164+
internal record DockerRegistryOptions(Dictionary<string, DockerRegistryEntry> Auths);
174165

175-
internal record DockerRegistryEntry(string auth, string? username, string? password);
166+
internal record DockerRegistryEntry(string Auth, string? Username, string? Password);

0 commit comments

Comments
 (0)