Skip to content

Commit

Permalink
Extends APIC plugins with prod Azure credentials (#661)
Browse files Browse the repository at this point in the history
  • Loading branch information
waldekmastykarz authored Apr 19, 2024
1 parent 5145fd1 commit 61e04ca
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 21 deletions.
57 changes: 45 additions & 12 deletions dev-proxy-plugins/RequestLogs/ApiCenterOnboardingPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
// Licensed under the MIT License.

using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using Azure.Core;
using Azure.Core.Diagnostics;
using Azure.Identity;
using Microsoft.DevProxy.Abstractions;
using Microsoft.DevProxy.Plugins.RequestLogs.ApiCenter;
Expand All @@ -24,19 +26,15 @@ internal class ApiCenterOnboardingPluginConfiguration
public string ServiceName { get; set; } = "";
public string WorkspaceName { get; set; } = "default";
public bool CreateApicEntryForNewApis { get; set; } = true;
public bool ExcludeDevCredentials { get; set; } = false;
public bool ExcludeProdCredentials { get; set; } = true;
}

public class ApiCenterOnboardingPlugin : BaseProxyPlugin
{
private ApiCenterOnboardingPluginConfiguration _configuration = new();
private readonly string[] _scopes = ["https://management.azure.com/.default"];
private readonly TokenCredential _credential = new ChainedTokenCredential(
new VisualStudioCredential(),
new VisualStudioCodeCredential(),
new AzureCliCredential(),
new AzurePowerShellCredential(),
new AzureDeveloperCliCredential()
);
private TokenCredential _credential = new DefaultAzureCredential();
private HttpClient? _httpClient;
private JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions
{
Expand All @@ -57,29 +55,63 @@ public override void Register(IPluginEvents pluginEvents,

if (string.IsNullOrEmpty(_configuration.SubscriptionId))
{
_logger?.LogError("Specify SubscriptionId in the ApiCenterOnboardingPlugin configuration. The ApiCenterOnboardingPlugin will not be used.");
_logger?.LogError("Specify SubscriptionId in the {plugin} configuration. The {plugin} will not be used.", Name, Name);
return;
}
if (string.IsNullOrEmpty(_configuration.ResourceGroupName))
{
_logger?.LogError("Specify ResourceGroupName in the ApiCenterOnboardingPlugin configuration. The ApiCenterOnboardingPlugin will not be used.");
_logger?.LogError("Specify ResourceGroupName in the {plugin} configuration. The {plugin} will not be used.", Name, Name);
return;
}
if (string.IsNullOrEmpty(_configuration.ServiceName))
{
_logger?.LogError("Specify ServiceName in the ApiCenterOnboardingPlugin configuration. The ApiCenterOnboardingPlugin will not be used.");
_logger?.LogError("Specify ServiceName in the {plugin} configuration. The {plugin} will not be used.", Name, Name);
return;
}
if (_configuration.ExcludeDevCredentials && _configuration.ExcludeProdCredentials)
{
_logger?.LogError("Both ExcludeDevCredentials and ExcludeProdCredentials are set to true. You need to use at least one set of credentials The {plugin} will not be used.", Name);
return;
}

var credentials = new List<TokenCredential>();
if (!_configuration.ExcludeDevCredentials)
{
credentials.AddRange([
new SharedTokenCacheCredential(),
new VisualStudioCredential(),
new VisualStudioCodeCredential(),
new AzureCliCredential(),
new AzurePowerShellCredential(),
new AzureDeveloperCliCredential(),
]);
}
if (!_configuration.ExcludeProdCredentials)
{
credentials.AddRange([
new EnvironmentCredential(),
new WorkloadIdentityCredential(),
new ManagedIdentityCredential()
]);
}
_credential = new ChainedTokenCredential(credentials.ToArray());

if (_logger?.LogLevel == LogLevel.Debug)
{
var consoleListener = AzureEventSourceListener.CreateConsoleLogger(EventLevel.Verbose);
}

_logger?.LogDebug("[{now}] Plugin {plugin} checking Azure auth...", DateTime.Now, Name);
try
{
_ = _credential.GetTokenAsync(new TokenRequestContext(_scopes), CancellationToken.None).Result;
}
catch (AuthenticationFailedException ex)
{
_logger?.LogError(ex, "Failed to authenticate with Azure. The ApiCenterOnboardingPlugin will not be used.");
_logger?.LogError(ex, "Failed to authenticate with Azure. The {plugin} will not be used.", Name);
return;
}
_logger?.LogDebug("[{now}] Plugin {plugin} auth confirmed...", DateTime.Now, Name);

var authenticationHandler = new AuthenticationDelegatingHandler(_credential, _scopes)
{
Expand Down Expand Up @@ -114,7 +146,8 @@ private async Task AfterRecordingStop(object sender, RecordingArgs e)
var newApis = new List<Tuple<string, string>>();
var interceptedRequests = e.RequestLogs
.Where(l => l.MessageType == MessageType.InterceptedRequest)
.Select(request => {
.Select(request =>
{
var methodAndUrl = request.MessageLines.First().Split(' ');
return new Tuple<string, string>(methodAndUrl[0], methodAndUrl[1]);
})
Expand Down
48 changes: 40 additions & 8 deletions dev-proxy-plugins/RequestLogs/ApiCenterProductionVersionPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// Licensed under the MIT License.

using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Text.Json;
using Azure.Core;
using Azure.Core.Diagnostics;
using Azure.Identity;
using Microsoft.DevProxy.Abstractions;
using Microsoft.DevProxy.Plugins;
Expand Down Expand Up @@ -38,19 +40,15 @@ internal class ApiCenterProductionVersionPluginConfiguration
public string ResourceGroupName { get; set; } = "";
public string ServiceName { get; set; } = "";
public string WorkspaceName { get; set; } = "default";
public bool ExcludeDevCredentials { get; set; } = false;
public bool ExcludeProdCredentials { get; set; } = true;
}

public class ApiCenterProductionVersionPlugin : BaseProxyPlugin
{
private ApiCenterProductionVersionPluginConfiguration _configuration = new();
private readonly string[] _scopes = ["https://management.azure.com/.default"];
private readonly TokenCredential _credential = new ChainedTokenCredential(
new VisualStudioCredential(),
new VisualStudioCodeCredential(),
new AzureCliCredential(),
new AzurePowerShellCredential(),
new AzureDeveloperCliCredential()
);
private TokenCredential _credential = new DefaultAzureCredential();
private HttpClient? _httpClient;
private JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions
{
Expand Down Expand Up @@ -84,16 +82,50 @@ public override void Register(IPluginEvents pluginEvents,
_logger?.LogError("Specify ServiceName in the ApiCenterProductionVersionPlugin configuration. The ApiCenterProductionVersionPlugin will not be used.");
return;
}
if (_configuration.ExcludeDevCredentials && _configuration.ExcludeProdCredentials)
{
_logger?.LogError("Both ExcludeDevCredentials and ExcludeProdCredentials are set to true. You need to use at least one set of credentials The {plugin} will not be used.", Name);
return;
}

var credentials = new List<TokenCredential>();
if (!_configuration.ExcludeDevCredentials)
{
credentials.AddRange([
new SharedTokenCacheCredential(),
new VisualStudioCredential(),
new VisualStudioCodeCredential(),
new AzureCliCredential(),
new AzurePowerShellCredential(),
new AzureDeveloperCliCredential(),
]);
}
if (!_configuration.ExcludeProdCredentials)
{
credentials.AddRange([
new EnvironmentCredential(),
new WorkloadIdentityCredential(),
new ManagedIdentityCredential()
]);
}
_credential = new ChainedTokenCredential(credentials.ToArray());

if (_logger?.LogLevel == LogLevel.Debug)
{
var consoleListener = AzureEventSourceListener.CreateConsoleLogger(EventLevel.Verbose);
}

_logger?.LogDebug("[{now}] Plugin {plugin} checking Azure auth...", DateTime.Now, Name);
try
{
_ = _credential.GetTokenAsync(new TokenRequestContext(_scopes), CancellationToken.None).Result;
}
catch (AuthenticationFailedException ex)
{
_logger?.LogError(ex, "Failed to authenticate with Azure. The ApiCenterProductionVersionPlugin will not be used.");
_logger?.LogError(ex, "Failed to authenticate with Azure. The {plugin} will not be used.", Name);
return;
}
_logger?.LogDebug("[{now}] Plugin {plugin} auth confirmed...", DateTime.Now, Name);

var authenticationHandler = new AuthenticationDelegatingHandler(_credential, _scopes)
{
Expand Down
4 changes: 3 additions & 1 deletion dev-proxy/PluginLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public PluginLoaderResult LoadPlugins(IPluginEvents pluginEvents, IProxyContext
// Load Handler Assembly if enabled
string pluginLocation = Path.GetFullPath(Path.Combine(configFileDirectory, ProxyUtils.ReplacePathTokens(h.PluginPath.Replace('\\', Path.DirectorySeparatorChar))));
PluginLoadContext pluginLoadContext = new PluginLoadContext(pluginLocation);
_logger.LogDebug("Loading plugin {pluginName} from: {pluginLocation}", h.Name, pluginLocation);
_logger?.LogDebug("Loading plugin {pluginName} from: {pluginLocation}", h.Name, pluginLocation);
Assembly assembly = pluginLoadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(pluginLocation)));
IEnumerable<UrlToWatch>? pluginUrlsList = h.UrlsToWatch?.Select(ConvertToRegex);
ISet<UrlToWatch>? pluginUrls = null;
Expand All @@ -53,12 +53,14 @@ public PluginLoaderResult LoadPlugins(IPluginEvents pluginEvents, IProxyContext
}
// Load Plugins from assembly
IProxyPlugin plugin = CreatePlugin(assembly, h);
_logger?.LogDebug("Registering plugin {pluginName}...", plugin.Name);
plugin.Register(
pluginEvents,
proxyContext,
(pluginUrls != null && pluginUrls.Any()) ? pluginUrls : defaultUrlsToWatch,
h.ConfigSection is null ? null : Configuration.GetSection(h.ConfigSection)
);
_logger?.LogDebug("Plugin {pluginName} registered.", plugin.Name);
plugins.Add(plugin);
}
}
Expand Down

0 comments on commit 61e04ca

Please sign in to comment.