Skip to content

Commit

Permalink
Replace site restricted token implementation with SWT
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmelsayed committed Mar 30, 2018
1 parent f2e2586 commit c61dda9
Show file tree
Hide file tree
Showing 18 changed files with 139 additions and 63 deletions.
4 changes: 2 additions & 2 deletions Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ public static TimeSpan MaxAllowedExecutionTime
//Setting for VC++ for node builds
public const string VCVersion = "2015";

// Enviroment variable for site retricted jwt token
public const string SiteRestrictedJWT = "X-MS-SITE-RESTRICTED-JWT";
public const string SiteRestrictedToken = "x-ms-site-restricted-token";
public const string SiteAuthEncryptionKey = "WEBSITE_AUTH_ENCRYPTION_KEY";
public const string HttpHost = "HTTP_HOST";
public const string WebSiteSwapSlotName = "WEBSITE_SWAP_SLOTNAME";

Expand Down
10 changes: 4 additions & 6 deletions Kudu.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ private static int Main(string[] args)
string wapTargets = args[1];
string deployer = args.Length == 2 ? null : args[2];
string requestId = System.Environment.GetEnvironmentVariable(Constants.RequestIdHeader);
string siteRestrictedJwt = System.Environment.GetEnvironmentVariable(Constants.SiteRestrictedJWT);

IEnvironment env = GetEnvironment(appRoot, requestId, siteRestrictedJwt);
IEnvironment env = GetEnvironment(appRoot, requestId);
ISettings settings = new XmlSettings.Settings(GetSettingsPath(env));
IDeploymentSettingsManager settingsManager = new DeploymentSettingsManager(settings);

Expand Down Expand Up @@ -182,7 +181,7 @@ private static int PerformDeploy(
IDeploymentStatusFile statusFile = deploymentStatusManager.Open(changeSet.Id);
if (statusFile != null && statusFile.Status == DeployStatus.Success)
{
PostDeploymentHelper.PerformAutoSwap(env.RequestId, env.SiteRestrictedJwt, new PostDeploymentTraceListener(tracer, deploymentManager.GetLogger(changeSet.Id))).Wait();
PostDeploymentHelper.PerformAutoSwap(env.RequestId, new PostDeploymentTraceListener(tracer, deploymentManager.GetLogger(changeSet.Id))).Wait();
}
}
}
Expand Down Expand Up @@ -245,7 +244,7 @@ private static string GetSettingsPath(IEnvironment environment)
return Path.Combine(environment.DeploymentsPath, Constants.DeploySettingsPath);
}

private static IEnvironment GetEnvironment(string siteRoot, string requestId, string siteRestrictedJwt)
private static IEnvironment GetEnvironment(string siteRoot, string requestId)
{
string root = Path.GetFullPath(Path.Combine(siteRoot, ".."));

Expand All @@ -263,8 +262,7 @@ private static IEnvironment GetEnvironment(string siteRoot, string requestId, st
return new Kudu.Core.Environment(root,
EnvironmentHelper.NormalizeBinPath(binPath),
repositoryPath,
requestId,
siteRestrictedJwt);
requestId);
}
}
}
1 change: 0 additions & 1 deletion Kudu.Contracts/IEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ public interface IEnvironment
string FunctionsPath { get; } // e.g. /site/wwwroot
string AppBaseUrlPrefix { get; } // e.g. siteName.azurewebsites.net
string RequestId { get; } // e.g. x-arr-log-id or x-ms-request-id header value
string SiteRestrictedJwt { get; } // e.g. x-ms-site-restricted-jwt header value
string SitePackagesPath { get; } // e.g. /data/SitePackages
}
}
25 changes: 18 additions & 7 deletions Kudu.Core.Test/AutoSwapHandlerFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Kudu.Contracts.Tracing;
using Kudu.Core.Deployment;
Expand Down Expand Up @@ -75,53 +76,63 @@ public async Task HandleAutoSwapTests()
var traceListener = new PostDeploymentTraceListener(tracerMock.Object, Mock.Of<ILogger>());

TestTracer.Trace("Autoswap will not happen, since it is not enabled.");
await PostDeploymentHelper.PerformAutoSwap(string.Empty, string.Empty, traceListener);
await PostDeploymentHelper.PerformAutoSwap(string.Empty, traceListener);
tracerMock.Verify(l => l.Trace("AutoSwap is not enabled", It.IsAny<IDictionary<string, string>>()), Times.Once);

TestTracer.Trace("Autoswap will not happen, since there is no HTTP_HOST env.");
System.Environment.SetEnvironmentVariable(Constants.WebSiteSwapSlotName, "someslot");
string jwtToken = Guid.NewGuid().ToString();
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => PostDeploymentHelper.PerformAutoSwap(string.Empty, jwtToken, traceListener));
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => PostDeploymentHelper.PerformAutoSwap(string.Empty, traceListener));
Assert.Equal("Missing HTTP_HOST env!", exception.Message);

string hostName = "foo.scm.bar";
System.Environment.SetEnvironmentVariable(Constants.HttpHost, hostName);
System.Environment.SetEnvironmentVariable(Constants.SiteAuthEncryptionKey, GenerateKey());

TestTracer.Trace("Autoswap will be triggered");
string newDeploymentId = Guid.NewGuid().ToString();

string autoSwapRequestUrl = null;
string bearerToken = null;
string swtToken = null;
PostDeploymentHelper.HttpClientFactory = () => new HttpClient(new TestMessageHandler((HttpRequestMessage requestMessage) =>
{
autoSwapRequestUrl = requestMessage.RequestUri.AbsoluteUri;
bearerToken = requestMessage.Headers.GetValues("Authorization").First();
swtToken = requestMessage.Headers.GetValues(Constants.SiteRestrictedToken).First();
return new HttpResponseMessage(HttpStatusCode.OK);
}));

Assert.True(!File.Exists(autoSwapLockFile), string.Format("File {0} should not exist.", autoSwapLockFile));

await PostDeploymentHelper.PerformAutoSwap(string.Empty, jwtToken, traceListener);
await PostDeploymentHelper.PerformAutoSwap(string.Empty, traceListener);

Assert.True(File.Exists(autoSwapLockFile), string.Format("File {0} should exist.", autoSwapLockFile));

Assert.NotNull(autoSwapRequestUrl);
Assert.True(autoSwapRequestUrl.StartsWith("https://foo.scm.bar/operations/autoswap?slot=someslot&operationId=AUTOSWAP"));

Assert.NotNull(bearerToken);
Assert.Equal("Bearer " + jwtToken, bearerToken);
Assert.NotNull(swtToken);
}
finally
{
System.Environment.SetEnvironmentVariable(Constants.HttpHost, null);
System.Environment.SetEnvironmentVariable("HOME", homePath);
System.Environment.SetEnvironmentVariable(Constants.WebSiteSwapSlotName, null);
System.Environment.SetEnvironmentVariable(Constants.SiteAuthEncryptionKey, null);
PostDeploymentHelper.HttpClientFactory = null;
if (Directory.Exists(tempPath))
{
Directory.Delete(tempPath, recursive: true);
}
}
}

private static string GenerateKey()
{
using (var aes = new AesManaged())
{
aes.GenerateKey();
return BitConverter.ToString(aes.Key).Replace("-", string.Empty);
}
}
}
}
3 changes: 1 addition & 2 deletions Kudu.Core.Test/Infrastructure/EnvironmentFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public void ConstructorThrowsIfRepositoryPathResolverIsNull()

// Act and Assert
var ex = Assert.Throws<ArgumentNullException>(() =>
new Environment(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null));
new Environment(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null));

Assert.Equal("repositoryPath", ex.ParamName);
}
Expand Down Expand Up @@ -151,7 +151,6 @@ private static Environment CreateEnvironment(
dataPath,
siteExtensionSettingsPath,
sitePackagesPath,
null,
null);
}
}
Expand Down
16 changes: 8 additions & 8 deletions Kudu.Core/Deployment/DeploymentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -625,11 +625,11 @@ private async Task Build(
NextManifestFilePath = GetDeploymentManifestPath(id),
PreviousManifestFilePath = GetActiveDeploymentManifestPath(),
IgnoreManifest = deploymentInfo != null && deploymentInfo.CleanupTargetDirectory,
// Ignoring the manifest will cause kudusync to delete sub-directories / files
// in the destination directory that are not present in the source directory,
// without checking the manifest to see if the file was copied over to the destination
// during a previous kudusync operation. This effectively performs a clean deployment
// from the source to the destination directory.
// Ignoring the manifest will cause kudusync to delete sub-directories / files
// in the destination directory that are not present in the source directory,
// without checking the manifest to see if the file was copied over to the destination
// during a previous kudusync operation. This effectively performs a clean deployment
// from the source to the destination directory.
Tracer = tracer,
Logger = logger,
GlobalLogger = _globalLogger,
Expand Down Expand Up @@ -661,7 +661,7 @@ private async Task Build(
await builder.Build(context);
builder.PostBuild(context);

await PostDeploymentHelper.SyncFunctionsTriggers(_environment.RequestId, _environment.SiteRestrictedJwt, new PostDeploymentTraceListener(tracer, logger), deploymentInfo?.SyncFunctionsTriggersPath);
await PostDeploymentHelper.SyncFunctionsTriggers(_environment.RequestId, new PostDeploymentTraceListener(tracer, logger), deploymentInfo?.SyncFunctionsTriggersPath);

if (_settings.TouchWatchedFileAfterDeployment())
{
Expand Down Expand Up @@ -701,7 +701,7 @@ private async Task Build(

private void PreDeployment(ITracer tracer)
{
if (Environment.IsAzureEnvironment()
if (Environment.IsAzureEnvironment()
&& FileSystemHelpers.DirectoryExists(_environment.SSHKeyPath)
&& OSDetector.IsOnWindows())
{
Expand All @@ -710,7 +710,7 @@ private void PreDeployment(ITracer tracer)

if (!String.Equals(src, dst, StringComparison.OrdinalIgnoreCase))
{
// copy %HOME%\.ssh to %USERPROFILE%\.ssh key to workaround
// copy %HOME%\.ssh to %USERPROFILE%\.ssh key to workaround
// npm with private ssh git dependency
using (tracer.Step("Copying SSH keys"))
{
Expand Down
2 changes: 1 addition & 1 deletion Kudu.Core/Deployment/FetchDeploymentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ await _deploymentManager.DeployAsync(
{
// if last change is not null and finish successfully, mean there was at least one deployoment happened
// since deployment is now done, trigger swap if enabled
await PostDeploymentHelper.PerformAutoSwap(_environment.RequestId, _environment.SiteRestrictedJwt, new PostDeploymentTraceListener(_tracer, _deploymentManager.GetLogger(lastChange.Id)));
await PostDeploymentHelper.PerformAutoSwap(_environment.RequestId, new PostDeploymentTraceListener(_tracer, _deploymentManager.GetLogger(lastChange.Id)));
}
}
}
Expand Down
14 changes: 2 additions & 12 deletions Kudu.Core/Environment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ public Environment(
string dataPath,
string siteExtensionSettingsPath,
string sitePackagesPath,
string requestId,
string siteRestrictedJwt)
string requestId)
{
if (repositoryPath == null)
{
Expand Down Expand Up @@ -91,15 +90,13 @@ public Environment(
_sitePackagesPath = sitePackagesPath;

RequestId = !string.IsNullOrEmpty(requestId) ? requestId : Guid.Empty.ToString();
SiteRestrictedJwt = siteRestrictedJwt;
}

public Environment(
string rootPath,
string binPath,
string repositoryPath,
string requestId,
string siteRetrictedJwt)
string requestId)
{
RootPath = rootPath;

Expand Down Expand Up @@ -148,7 +145,6 @@ public Environment(
_sitePackagesPath = Path.Combine(_dataPath, Constants.SitePackages);

RequestId = !string.IsNullOrEmpty(requestId) ? requestId : Guid.Empty.ToString();
SiteRestrictedJwt = siteRetrictedJwt;
}

public string RepositoryPath
Expand Down Expand Up @@ -370,12 +366,6 @@ public string RequestId
private set;
}

public string SiteRestrictedJwt
{
get;
private set;
}

public static bool IsAzureEnvironment()
{
return !String.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID"));
Expand Down
2 changes: 1 addition & 1 deletion Kudu.Core/Functions/FunctionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public async Task SyncTriggersAsync(ITracer tracer = null)
tracer = tracer ?? _traceFactory.GetTracer();
using (tracer.Step("FunctionManager.SyncTriggers"))
{
await PostDeploymentHelper.SyncFunctionsTriggers(_environment.RequestId, _environment.SiteRestrictedJwt, new PostDeploymentTraceListener(tracer));
await PostDeploymentHelper.SyncFunctionsTriggers(_environment.RequestId, new PostDeploymentTraceListener(tracer));
}
}

Expand Down
31 changes: 22 additions & 9 deletions Kudu.Core/Helpers/PostDeploymentHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net;
Expand Down Expand Up @@ -91,16 +92,28 @@ public static bool IsAzureEnvironment()
/// It is written to require least dependencies but framework assemblies.
/// Caller is responsible for synchronization.
/// </summary>
[SuppressMessage("Microsoft.Usage", "CA1801:Parameter 'siteRestrictedJwt' is never used",
Justification = "Method signature has to be the same because it's called via reflections from web-deploy")]
public static async Task Run(string requestId, string siteRestrictedJwt, TraceListener tracer)
{
await Invoke(requestId, tracer);
}

/// <summary>
/// This common codes is to invoke post deployment operations.
/// It is written to require least dependencies but framework assemblies.
/// Caller is responsible for synchronization.
/// </summary>
public static async Task Invoke(string requestId, TraceListener tracer)
{
RunPostDeploymentScripts(tracer);

await SyncFunctionsTriggers(requestId, siteRestrictedJwt, tracer);
await SyncFunctionsTriggers(requestId, tracer);

await PerformAutoSwap(requestId, siteRestrictedJwt, tracer);
await PerformAutoSwap(requestId, tracer);
}

public static async Task SyncFunctionsTriggers(string requestId, string siteRestrictedJwt, TraceListener tracer, string functionsPath = null)
public static async Task SyncFunctionsTriggers(string requestId, TraceListener tracer, string functionsPath = null)
{
_tracer = tracer;

Expand All @@ -122,7 +135,7 @@ public static async Task SyncFunctionsTriggers(string requestId, string siteRest
? functionsPath
: System.Environment.ExpandEnvironmentVariables(@"%HOME%\site\wwwroot");

// Read host.json
// Read host.json
// Get HubName property for Durable Functions
string taskHubName = null;
string hostJson = Path.Combine(functionsPath, Constants.FunctionsHostConfigFile);
Expand Down Expand Up @@ -166,7 +179,7 @@ public static async Task SyncFunctionsTriggers(string requestId, string siteRest
Exception exception = null;
try
{
await PostAsync("/operations/settriggers", requestId, siteRestrictedJwt, content);
await PostAsync("/operations/settriggers", requestId, content);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -265,7 +278,7 @@ public static bool IsAutoSwapEnabled()
return !string.IsNullOrEmpty(WebSiteSwapSlotName);
}

public static async Task PerformAutoSwap(string requestId, string siteRestrictedJwt, TraceListener tracer)
public static async Task PerformAutoSwap(string requestId, TraceListener tracer)
{
_tracer = tracer;

Expand All @@ -282,7 +295,7 @@ public static async Task PerformAutoSwap(string requestId, string siteRestricted
Exception exception = null;
try
{
await PostAsync(string.Format("/operations/autoswap?slot={0}&operationId={1}", slotSwapName, operationId), requestId, siteRestrictedJwt);
await PostAsync(string.Format("/operations/autoswap?slot={0}&operationId={1}", slotSwapName, operationId), requestId);

WriteAutoSwapOngoing();
}
Expand Down Expand Up @@ -409,7 +422,7 @@ private static void WriteAutoSwapOngoing()
}
}

private static async Task PostAsync(string path, string requestId, string siteRestrictedJwt, string content = null)
private static async Task PostAsync(string path, string requestId, string content = null)
{
var host = HttpHost;
var statusCode = default(HttpStatusCode);
Expand All @@ -420,7 +433,7 @@ private static async Task PostAsync(string path, string requestId, string siteRe
{
client.BaseAddress = new Uri(string.Format("https://{0}", host));
client.DefaultRequestHeaders.UserAgent.Add(_userAgent.Value);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", siteRestrictedJwt);
client.DefaultRequestHeaders.Add(Constants.SiteRestrictedToken, SimpleWebTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5)));
client.DefaultRequestHeaders.Add(Constants.RequestIdHeader, requestId);

var payload = new StringContent(content ?? string.Empty, Encoding.UTF8, "application/json");
Expand Down
Loading

0 comments on commit c61dda9

Please sign in to comment.