Repository navigation
Expand file tree
/
Copy pathRegistryProxy.cs
More file actions
210 lines (179 loc) · 8.91 KB
/
Copy pathRegistryProxy.cs
File metadata and controls
210 lines (179 loc) · 8.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
using System;
using System.Collections.Immutable;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using Semmle.Util;
using Semmle.Util.Logging;
using Newtonsoft.Json;
using System.Linq;
namespace Semmle.Extraction.CSharp.DependencyFetching
{
public class RegistryProxy : IRegistryProxy
{
/// <summary>
/// Represents configurations for package registries.
/// </summary>
public class RegistryConfig
{
/// <summary>
/// The type of the package registry.
/// </summary>
public string? Type { get; init; }
/// <summary>
/// The URL of the package registry.
/// </summary>
public string? Url { get; init; }
/// <summary>
/// A boolean indicating whether this registry replaces the base registry.
/// </summary>
[JsonProperty("replaces-base")]
public bool ReplacesBase { get; init; } = false;
};
public string Address { get; }
private readonly ILogger logger;
/// <summary>
/// A dictionary mapping registry URLs to a boolean indicating whether they replace the base registry.
/// </summary>
private readonly Dictionary<string, bool> registryMapping = [];
private ImmutableHashSet<string>? registryURLs;
/// <summary>
/// Gets the set of registry URLs that have been configured as part of the organization-level
/// private registry configuration. This includes all registries, regardless of whether they replace
/// the default feeds.
/// </summary>
public ImmutableHashSet<string> RegistryURLs =>
registryURLs ??= registryMapping.Keys.ToImmutableHashSet();
private ImmutableHashSet<string>? registryBaseURLs;
/// <summary>
/// Gets the set of registry URLs that have been configured as part of the organization-level
/// private registry configuration and that replace the default registry. This is a subset of
/// <see cref="RegistryURLs"/>.
/// If non-empty, the set should be used as a replacement for the default registry during
/// package resolution.
/// </summary>
public ImmutableHashSet<string> RegistryBaseURLs =>
registryBaseURLs ??= registryMapping.Where(kvp => kvp.Value).Select(kvp => kvp.Key).ToImmutableHashSet();
public string? CertificatePath { get; private set; }
public X509Certificate2? Certificate { get; private set; }
private RegistryProxy(IRegistryProxyConfiguration config, ILogger l, TemporaryDirectory tempWorkingDirectory)
{
logger = l;
Address = $"http://{config.Host}:{config.Port}";
if (!string.IsNullOrWhiteSpace(config.Certificate))
{
var certDirPath = new DirectoryInfo(Path.Join(tempWorkingDirectory.DirInfo.FullName, ".registry-proxy"));
Directory.CreateDirectory(certDirPath.FullName);
CertificatePath = Path.Join(certDirPath.FullName, "proxy.crt");
var certFile = new FileInfo(CertificatePath);
using var writer = certFile.CreateText();
writer.Write(config.Certificate);
writer.Close();
logger.LogInfo($"Stored registry proxy certificate at {CertificatePath}");
Certificate = X509Certificate2.CreateFromPem(config.Certificate);
}
if (!string.IsNullOrWhiteSpace(config.RegistryURLs))
{
try
{
var array = JsonConvert.DeserializeObject<List<RegistryConfig>>(config.RegistryURLs);
if (array is not null)
{
foreach (RegistryConfig registry in array)
{
if (string.IsNullOrWhiteSpace(registry.Url))
{
logger.LogError("Ignoring registry with empty URL.");
continue;
}
if (string.IsNullOrWhiteSpace(registry.Type))
{
logger.LogError($"Ignoring registry at '{registry.Url}' since it has no type.");
continue;
}
// The array contains all configured private registries, not just ones for C#.
// We ignore the non-C# ones here.
if (!registry.Type.Equals("nuget_feed"))
{
logger.LogDebug($"Ignoring registry at '{registry.Url}' since it is not of type 'nuget_feed'.");
continue;
}
logger.LogInfo($"Found private registry at '{registry.Url}'");
registryMapping.AddOrUpdateToLatest(registry.Url, registry.ReplacesBase);
}
}
}
catch (JsonException ex)
{
logger.LogError($"Unable to parse '{EnvironmentVariableNames.ProxyURLs}': {ex.Message}");
}
}
}
internal static IRegistryProxy? Make(ILogger logger, IDiagnosticsWriter diagnosticsWriter, TemporaryDirectory tempWorkingDirectory)
{
// Setting HTTP(S)_PROXY and SSL_CERT_FILE have no effect on Windows or macOS,
// but we would still end up using the registry proxy to check for feed reachability.
// This would result in us discovering that the feeds are reachable, but `dotnet` would
// fail to connect to them. To prevent this from happening, we do not initialise an
// instance of `RegistryProxy` on those platforms.
if (SystemBuildActions.Instance.IsWindows() || SystemBuildActions.Instance.IsMacOs())
{
return null;
}
return Make(new RegistryProxyConfiguration(), logger, diagnosticsWriter, tempWorkingDirectory);
}
/// <summary>
/// Creates an instance of the registry proxy using the specified configuration.
/// Returns null if the proxy cannot be created.
/// This overload is exposed primarily to enable platform-independent unit testing.
/// </summary>
internal static IRegistryProxy? Make(
IRegistryProxyConfiguration proxyConfig, ILogger logger, IDiagnosticsWriter diagnosticsWriter, TemporaryDirectory tempWorkingDirectory)
{
if (string.IsNullOrWhiteSpace(proxyConfig.Host) || string.IsNullOrWhiteSpace(proxyConfig.Port))
{
logger.LogDebug("No registry proxy credentials are configured.");
return null;
}
var result = new RegistryProxy(proxyConfig, logger, tempWorkingDirectory);
logger.LogInfo($"Registry proxy configured at {result.Address}");
// Emit a diagnostic for the discovered private registries, so that it is easy
// for users to see that they were picked up.
if (result.RegistryURLs.Count > 0)
{
diagnosticsWriter.AddEntry(new DiagnosticMessage(
Language.CSharp,
"buildless/analysis-using-private-registries",
severity: DiagnosticMessage.TspSeverity.Note,
visibility: new DiagnosticMessage.TspVisibility(true, true, true),
name: "C# extraction used private package registries",
markdownMessage: string.Format(
"C# was extracted using the following private package registries:\n\n{0}\n",
string.Join("\n", result.RegistryURLs.Select(url => string.Format("- `{0}`", url)))
)
));
}
return result;
}
public void SetProcessEnvironment(ProcessStartInfo pi)
{
logger.LogDebug($"Configuring environment variables for the registry proxy at {Address}");
pi.EnvironmentVariables["HTTP_PROXY"] = Address;
pi.EnvironmentVariables["HTTPS_PROXY"] = Address;
// Also set the lower case variants of the environment variables
// This might be needed on Linux systems.
pi.EnvironmentVariables["http_proxy"] = Address;
pi.EnvironmentVariables["https_proxy"] = Address;
if (CertificatePath != null)
{
logger.LogDebug("Setting the SSL certificate path for the registry proxy.");
pi.EnvironmentVariables["SSL_CERT_FILE"] = CertificatePath;
}
}
public void Dispose()
{
Certificate?.Dispose();
}
}
}