Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable Easy Auth Local Dev #789

Closed
wants to merge 6 commits into from
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Threading.Tasks;
using Azure.Functions.Cli.Helpers;
using Azure.Functions.Cli.Interfaces;
using Fclp;

namespace Azure.Functions.Cli.Actions.AzureActions
glennamanns marked this conversation as resolved.
Show resolved Hide resolved
{
// Invoke via `func azure auth create-aad-app --AADAppRegistrationName {yourAppRegistrationName} --appName {yourAppName}`
[Action(Name = "create-aad-app", Context = Context.Azure, SubContext = Context.Auth, HelpText = "Creates an Azure Active Directory app registration. Can be linked to an Azure App Service or Function app.")]
class CreateAADApplication : BaseAzureAction
glennamanns marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly IAuthManager _authManager;
private readonly ISecretsManager _secretsManager;

public string AADAppRegistrationName { get; set; }

public string AppName { get; set; }

public CreateAADApplication(IAuthManager authManager, ISecretsManager secretsManager)
{
_authManager = authManager;
_secretsManager = secretsManager;
}

public override async Task RunAsync()
{
var workerRuntime = WorkerRuntimeLanguageHelper.GetCurrentWorkerRuntimeLanguage(_secretsManager);

await _authManager.CreateAADApplication(AccessToken, AADAppRegistrationName, workerRuntime, AppName);
}

public override ICommandLineParserResult ParseArgs(string[] args)
{
Parser
.Setup<string>("AADAppRegistrationName")
.WithDescription("Name of the Azure Active Directory app registration to create.")
.Callback(f => AADAppRegistrationName = f);

Parser
.Setup<string>("appName")
.WithDescription("Name of the Azure App Service or Azure Functions app which will be connected to the Azure AD app registration.")
.Callback(f => AppName = f);

return base.ParseArgs(args);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using Azure.Functions.Cli.Actions.LocalActions;
using Azure.Functions.Cli.Arm;
using Azure.Functions.Cli.Arm.Models;
using Azure.Functions.Cli.Common;
using Azure.Functions.Cli.Extensions;
Expand Down Expand Up @@ -430,7 +428,7 @@ private IDictionary<string, string> MergeAppSettings(IDictionary<string, string>
foreach (var pair in local)
{
if (result.ContainsKeyCaseInsensitive(pair.Key) &&
!result.GetValueCaseInsensitive(pair.Key).Equals(pair.Value, StringComparison.OrdinalIgnoreCase))
!string.Equals(result.GetValueCaseInsensitive(pair.Key), pair.Value, StringComparison.OrdinalIgnoreCase))
{
ColoredConsole.WriteLine($"App setting {pair.Key} is different between azure and {SecretsManager.AppSettingsFileName}");
if (OverwriteSettings)
Expand Down
77 changes: 66 additions & 11 deletions src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Azure.Functions.Cli.Actions.HostActions.WebHost.Security;
using Azure.Functions.Cli.Common;
Expand All @@ -17,6 +19,7 @@
using Fclp;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Azure.AppService.Middleware;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Script;
using Microsoft.Azure.WebJobs.Script.WebHost;
Expand All @@ -39,7 +42,7 @@ namespace Azure.Functions.Cli.Actions.HostActions
[Action(Name = "start", HelpText = "Launches the functions runtime host")]
internal class StartHostAction : BaseAction
{
private const int DefaultPort = 7071;
public const int DefaultPort = 7071;
private const int DefaultTimeout = 20;
private readonly ISecretsManager _secretsManager;

Expand Down Expand Up @@ -124,10 +127,19 @@ public override ICommandLineParserResult ParseArgs(string[] args)
return Parser.Parse(args);
}

private async Task<IWebHost> BuildWebHost(ScriptApplicationHostOptions hostOptions, WorkerRuntime workerRuntime, Uri baseAddress, X509Certificate2 certificate)
private async Task<IWebHost> BuildWebHost(ScriptApplicationHostOptions hostOptions, WorkerRuntime workerRuntime, Uri baseAddress, X509Certificate2 certificate, bool authenticationEnabled, Uri baseUri)
{
IDictionary<string, string> settings = await GetConfigurationSettings(hostOptions.ScriptPath, baseAddress);
settings.AddRange(LanguageWorkerHelper.GetWorkerConfiguration(workerRuntime, LanguageWorkerSetting));

if (authenticationEnabled)
{
var middlewareAuthSettings = new AuthSettingsFile(SecretsManager.MiddlewareAuthSettingsFilePath).GetValues();
settings.AddRange((IReadOnlyDictionary<string, string>)middlewareAuthSettings);

SetupMiddlewareConfig(baseUri);
}

UpdateEnvironmentVariables(settings);

var defaultBuilder = Microsoft.AspNetCore.WebHost.CreateDefaultBuilder(Array.Empty<string>());
Expand All @@ -137,9 +149,9 @@ private async Task<IWebHost> BuildWebHost(ScriptApplicationHostOptions hostOptio
defaultBuilder
.UseKestrel(options =>
{
options.Listen(IPAddress.Loopback, baseAddress.Port, listenOptins =>
options.Listen(IPAddress.Loopback, baseAddress.Port, listenOptions =>
{
listenOptins.UseHttps(certificate);
listenOptions.UseHttps(certificate);
});
});
}
Expand All @@ -157,7 +169,7 @@ private async Task<IWebHost> BuildWebHost(ScriptApplicationHostOptions hostOptio
loggingBuilder.AddDefaultWebJobsFilters();
loggingBuilder.AddProvider(new ColoredConsoleLoggerProvider((cat, level) => level >= LogLevel.Information));
})
.ConfigureServices((context, services) => services.AddSingleton<IStartup>(new Startup(context, hostOptions, CorsOrigins)))
.ConfigureServices((context, services) => services.AddSingleton<IStartup>(new Startup(context, hostOptions, CorsOrigins, authenticationEnabled)))
.Build();
}

Expand Down Expand Up @@ -217,9 +229,16 @@ public override async Task RunAsync()

var settings = SelfHostWebHostSettingsFactory.Create(Environment.CurrentDirectory);

// Determine if middleware (Easy Auth) is enabled
var middlewareAuthSettings = _secretsManager.GetMiddlewareAuthSettings();
bool authenticationEnabled =
middlewareAuthSettings.TryGetValue(Constants.MiddlewareAuthEnabledSetting, out string enabledValue) &&
bool.TryParse(enabledValue, out bool isEnabled) &&
isEnabled;

(var listenUri, var baseUri, var certificate) = await Setup();

IWebHost host = await BuildWebHost(settings, workerRuntime, listenUri, certificate);
IWebHost host = await BuildWebHost(settings, workerRuntime, listenUri, certificate, authenticationEnabled, baseUri);
var runTask = host.RunAsync();

var hostService = host.Services.GetRequiredService<WebJobsScriptHostService>();
Expand Down Expand Up @@ -395,25 +414,53 @@ internal static async Task CheckNonOptionalSettings(IEnumerable<KeyValuePair<str
}
}

/// <summary>
/// Add additional settings to be consumed by the EasyAuth Middleware
/// </summary>
/// <param name="baseUri"></param>
private static void SetupMiddlewareConfig(Uri baseUri)
{
// These are not necessary when Easy Auth is used in conjunction with the Functions CLI
// We add them here for compatibility with existing Easy Auth / Middleware codebase
var args = new string[] {
string.Format("{0}={1}", Constants.MiddlewareListenUrlSetting, baseUri.ToString()),
string.Format("{0}={1}", Constants.MiddlewareHostUrlSetting, baseUri.ToString()),
};

// Add above arguments, as well as the settings from local.middleware.json
IConfigurationRoot config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(SecretsManager.MiddlewareAuthSettingsFilePath)
.AddCommandLine(args)
.Build();

ModuleConfig.HostConfig = config;
}

private async Task<(Uri listenUri, Uri baseUri, X509Certificate2 cert)> Setup()
{
var protocol = UseHttps ? "https" : "http";
X509Certificate2 cert = UseHttps
? await SecurityHelpers.GetOrCreateCertificate(CertPath, CertPassword)
: null;
return (new Uri($"{protocol}://0.0.0.0:{Port}"), new Uri($"{protocol}://localhost:{Port}"), cert);
if (UseHttps)
{
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: revert this to the original logic

X509Certificate2 cert = await SecurityHelpers.GetOrCreateCertificate(CertPath, CertPassword);
return (new Uri($"{protocol}://0.0.0.0:{Port}"), new Uri($"{protocol}://localhost:{Port}"), cert);
}

return (new Uri($"{protocol}://0.0.0.0:{Port}"), new Uri($"{protocol}://localhost:{Port}"), null);
}

public class Startup : IStartup
{
private readonly WebHostBuilderContext _builderContext;
private readonly ScriptApplicationHostOptions _hostOptions;
private readonly string[] _corsOrigins;
private readonly bool _authenticationEnabled;

public Startup(WebHostBuilderContext builderContext, ScriptApplicationHostOptions hostOptions, string corsOrigins)
public Startup(WebHostBuilderContext builderContext, ScriptApplicationHostOptions hostOptions, string corsOrigins, bool authEnabled)
{
_builderContext = builderContext;
_hostOptions = hostOptions;
_authenticationEnabled = authEnabled;

if (!string.IsNullOrEmpty(corsOrigins))
{
Expand Down Expand Up @@ -469,6 +516,14 @@ public void Configure(IApplicationBuilder app)
IApplicationLifetime applicationLifetime = app.ApplicationServices
.GetRequiredService<IApplicationLifetime>();

if (_authenticationEnabled)
{
ILoggerProvider loggerProvider = app.ApplicationServices
.GetRequiredService<ILoggerProvider>();

app.UseAppServiceMiddleware(loggerProvider);
}

app.UseWebJobsScriptHost(applicationLifetime);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace Azure.Functions.Cli.Actions.HostActions.WebHost.Security
{
static class AuthMiddlewareExtensions
{
public static IApplicationBuilder UseAppServiceMiddleware(this IApplicationBuilder builder, ILoggerProvider loggerProvider)
{
return builder.UseMiddleware<AuthMiddleware>(builder, loggerProvider);
}
}

class AuthMiddleware : Microsoft.Azure.AppService.MiddlewareShim.Startup
{
// The middleware delegate to call after this one finishes processing
private readonly RequestDelegate _next;

public AuthMiddleware(RequestDelegate next, IApplicationBuilder app, ILoggerProvider loggerProvider)
{
_next = next;
this.Configure(app, loggerProvider);
}

public Task Invoke(HttpContext httpContext)
{
// OnRequest handles calling Invoke on _next and sending the response
return this.OnRequest(httpContext, _next);
}
}
}
2 changes: 2 additions & 0 deletions src/Azure.Functions.Cli/Arm/Models/Site.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public class Site

public IDictionary<string, string> AzureAppSettings { get; set; }

public IDictionary<string, string> AzureAuthSettings { get; set; }

public IDictionary<string, AppServiceConnectionString> ConnectionStrings { get; set; }

public bool IsLinux
Expand Down
10 changes: 10 additions & 0 deletions src/Azure.Functions.Cli/Azure.Functions.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@
<PackageReference Include="Microsoft.Azure.Functions.NodeJsWorker" Version="1.0.0-beta4-10082" />
<PackageReference Include="Microsoft.Azure.Functions.PowerShellWorker" Version="0.1.20-alpha" />
<PackageReference Include="Microsoft.Azure.WebJobs.Script.WebHost" Version="2.0.0-12134" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup>
<ItemGroup>
<!-- ! CANNOT BE MERGED. WILL NOT BE NECESSARY WHEN AVAILABLE VIA NUGET !-->
<Reference Include="Microsoft.Azure.AppService.Middleware">
<HintPath>C:\wagit\AAPT\Antares\EasyAuth\src\EasyAuth\MiddlewareLinux\bin\Debug\netcoreapp2.0\Microsoft.Azure.AppService.Middleware.dll</HintPath>
</Reference>
<Reference Include="MiddlewareLinux">
<HintPath>C:\wagit\AAPT\Antares\EasyAuth\src\EasyAuth\MiddlewareLinux\bin\Debug\netcoreapp2.0\MiddlewareLinux.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
Loading