Skip to content

Commit 4bd345d

Browse files
Supporting new platform specialization payload (#9302)
Co-authored-by: anandagopal <[email protected]>
1 parent 2ea7952 commit 4bd345d

File tree

4 files changed

+115
-10
lines changed

4 files changed

+115
-10
lines changed

src/WebJobs.Script.WebHost/Management/AtlasInstanceManager.cs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
1616
using Microsoft.Extensions.Logging;
1717
using Microsoft.Extensions.Options;
18+
using Microsoft.IdentityModel.Tokens;
1819
using Newtonsoft.Json;
1920

2021
namespace Microsoft.Azure.WebJobs.Script.WebHost.Management
@@ -63,25 +64,36 @@ public override async Task<string> SpecializeMSISidecar(HostAssignmentContext co
6364

6465
if (msiEnabled)
6566
{
66-
if (context.MSIContext == null)
67+
if (context.MSIContext == null && context.EncryptedTokenServiceSpecializationPayload == null)
6768
{
68-
_logger.LogWarning("Skipping specialization of MSI sidecar since MSIContext was absent");
69+
_logger.LogWarning("Skipping specialization of MSI sidecar since MSIContext and EncryptedTokenServiceSpecializationPayload were absent");
6970
await _meshServiceClient.NotifyHealthEvent(ContainerHealthEventType.Fatal, this.GetType(),
70-
"Could not specialize MSI sidecar since MSIContext was empty");
71+
"Could not specialize MSI sidecar since MSIContext and EncryptedTokenServiceSpecializationPayload were empty");
7172
}
7273
else
7374
{
7475
using (_metricsLogger.LatencyEvent(MetricEventNames.LinuxContainerSpecializationMSIInit))
7576
{
7677
var uri = new Uri(endpoint);
77-
var address = $"http://{uri.Host}:{uri.Port}{ScriptConstants.LinuxMSISpecializationStem}";
78+
var addressStem = GetMsiSpecializationRequestAddressStem(context);
7879

80+
var address = $"http://{uri.Host}:{uri.Port}{addressStem}";
7981
_logger.LogDebug($"Specializing sidecar at {address}");
8082

83+
StringContent payload;
84+
if (string.IsNullOrEmpty(context.EncryptedTokenServiceSpecializationPayload))
85+
{
86+
payload = new StringContent(JsonConvert.SerializeObject(context.MSIContext),
87+
Encoding.UTF8, "application/json");
88+
}
89+
else
90+
{
91+
payload = new StringContent(context.EncryptedTokenServiceSpecializationPayload, Encoding.UTF8);
92+
}
93+
8194
var requestMessage = new HttpRequestMessage(HttpMethod.Post, address)
8295
{
83-
Content = new StringContent(JsonConvert.SerializeObject(context.MSIContext),
84-
Encoding.UTF8, "application/json")
96+
Content = payload
8597
};
8698

8799
var response = await _client.SendAsync(requestMessage);
@@ -364,5 +376,22 @@ await Utility.InvokeWithRetriesAsync(async () =>
364376
return false;
365377
}
366378
}
379+
380+
private string GetMsiSpecializationRequestAddressStem(HostAssignmentContext context)
381+
{
382+
var stem = ScriptConstants.LinuxMSISpecializationStem;
383+
384+
if (!string.IsNullOrEmpty(context.EncryptedTokenServiceSpecializationPayload))
385+
{
386+
_logger.LogDebug("Using encrypted TokenService payload format");
387+
388+
// use default encrypted API endpoint if endpoint not provided in context
389+
stem = string.IsNullOrEmpty(context.TokenServiceApiEndpoint)
390+
? ScriptConstants.LinuxEncryptedTokenServiceSpecializationStem
391+
: context.TokenServiceApiEndpoint;
392+
}
393+
394+
return stem;
395+
}
367396
}
368397
}

src/WebJobs.Script.WebHost/Models/HostAssignmentContext.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ public class HostAssignmentContext
2727
[JsonProperty("MSISpecializationPayload")]
2828
public MSIContext MSIContext { get; set; }
2929

30+
[JsonProperty("EncryptedTokenServiceSpecializationPayload")]
31+
public string EncryptedTokenServiceSpecializationPayload { get; set; }
32+
33+
[JsonProperty("TokenServiceApiEndpoint")]
34+
public string TokenServiceApiEndpoint { get; set; }
35+
3036
[JsonProperty("CorsSpecializationPayload")]
3137
public CorsSettings CorsSettings { get; set; }
3238

src/WebJobs.Script/ScriptConstants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ public static class ScriptConstants
172172
public const string LinuxFunctionDetailsEventStreamName = "MS_FUNCTION_DETAILS";
173173
public const string LinuxAzureMonitorEventStreamName = "MS_FUNCTION_AZURE_MONITOR_EVENT";
174174
public const string LinuxMSISpecializationStem = "/api/specialize?api-version=2022-01-01";
175+
public const string LinuxEncryptedTokenServiceSpecializationStem = "/api/specialize?api-version=2023-05-01";
175176

176177
public const string DurableTaskPropertyName = "durableTask";
177178
public const string DurableTaskHubName = "HubName";

test/WebJobs.Script.Tests.Integration/Management/InstanceManagerTests.cs

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,69 @@ public async Task SpecializeMSISidecar_Succeeds()
618618
p => Assert.StartsWith("Specialize MSI sidecar returned OK", p));
619619
}
620620

621+
[Fact]
622+
public async Task SpecializeMSISidecar_Succeeds_EncryptedMSIContextWithoutProvidedEndpoint()
623+
{
624+
var environment = new Dictionary<string, string>()
625+
{
626+
{ EnvironmentSettingNames.MsiEndpoint, "http://localhost:8081" },
627+
{ EnvironmentSettingNames.MsiSecret, "secret" }
628+
};
629+
var assignmentContext = new HostAssignmentContext
630+
{
631+
SiteId = 1234,
632+
SiteName = "TestSite",
633+
Environment = environment,
634+
IsWarmupRequest = false,
635+
MSIContext = new MSIContext(),
636+
EncryptedTokenServiceSpecializationPayload = "TestContext"
637+
};
638+
639+
var instanceManager = GetInstanceManagerForMSISpecialization(assignmentContext, HttpStatusCode.OK, null);
640+
641+
string error = await instanceManager.SpecializeMSISidecar(assignmentContext);
642+
Assert.Null(error);
643+
644+
var logs = _loggerProvider.GetAllLogMessages().Select(p => p.FormattedMessage).ToArray();
645+
Assert.Collection(logs,
646+
p => Assert.StartsWith("MSI enabled status: True", p),
647+
p => Assert.StartsWith("Using encrypted TokenService payload format", p),
648+
p => Assert.Equal($"Specializing sidecar at http://localhost:8081{ScriptConstants.LinuxEncryptedTokenServiceSpecializationStem}", p),
649+
p => Assert.StartsWith("Specialize MSI sidecar returned OK", p));
650+
}
651+
652+
[Fact]
653+
public async Task SpecializeMSISidecar_Succeeds_EncryptedMSIContextWithProvidedEndpoint()
654+
{
655+
var environment = new Dictionary<string, string>()
656+
{
657+
{ EnvironmentSettingNames.MsiEndpoint, "http://localhost:8081" },
658+
{ EnvironmentSettingNames.MsiSecret, "secret" }
659+
};
660+
var assignmentContext = new HostAssignmentContext
661+
{
662+
SiteId = 1234,
663+
SiteName = "TestSite",
664+
Environment = environment,
665+
IsWarmupRequest = false,
666+
MSIContext = new MSIContext(),
667+
EncryptedTokenServiceSpecializationPayload = "TestContext",
668+
TokenServiceApiEndpoint = "/api/TestEndpoint"
669+
};
670+
671+
var instanceManager = GetInstanceManagerForMSISpecialization(assignmentContext, HttpStatusCode.OK, null);
672+
673+
string error = await instanceManager.SpecializeMSISidecar(assignmentContext);
674+
Assert.Null(error);
675+
676+
var logs = _loggerProvider.GetAllLogMessages().Select(p => p.FormattedMessage).ToArray();
677+
Assert.Collection(logs,
678+
p => Assert.StartsWith("MSI enabled status: True", p),
679+
p => Assert.StartsWith("Using encrypted TokenService payload format", p),
680+
p => Assert.Equal($"Specializing sidecar at http://localhost:8081{assignmentContext.TokenServiceApiEndpoint}", p),
681+
p => Assert.StartsWith("Specialize MSI sidecar returned OK", p));
682+
}
683+
621684
[Fact]
622685
public async Task SpecializeMsiSidecar_RequiredPropertiesInPayload()
623686
{
@@ -751,7 +814,7 @@ public async Task DoesNotSpecializeMSISidecar_WhenMSIContextNull()
751814

752815
var meshServiceClient = new Mock<IMeshServiceClient>(MockBehavior.Strict);
753816
meshServiceClient.Setup(c => c.NotifyHealthEvent(ContainerHealthEventType.Fatal,
754-
It.Is<Type>(t => t == typeof(AtlasInstanceManager)), "Could not specialize MSI sidecar since MSIContext was empty")).Returns(Task.CompletedTask);
817+
It.Is<Type>(t => t == typeof(AtlasInstanceManager)), "Could not specialize MSI sidecar since MSIContext and EncryptedTokenServiceSpecializationPayload were empty")).Returns(Task.CompletedTask);
755818

756819
var instanceManager = GetInstanceManagerForMSISpecialization(assignmentContext, HttpStatusCode.BadRequest, meshServiceClient.Object);
757820

@@ -761,10 +824,10 @@ public async Task DoesNotSpecializeMSISidecar_WhenMSIContextNull()
761824
var logs = _loggerProvider.GetAllLogMessages().Select(p => p.FormattedMessage).ToArray();
762825
Assert.Collection(logs,
763826
p => Assert.StartsWith("MSI enabled status: True", p),
764-
p => Assert.StartsWith("Skipping specialization of MSI sidecar since MSIContext was absent", p));
827+
p => Assert.StartsWith("Skipping specialization of MSI sidecar since MSIContext and EncryptedTokenServiceSpecializationPayload were absent", p));
765828

766829
meshServiceClient.Verify(c => c.NotifyHealthEvent(ContainerHealthEventType.Fatal,
767-
It.Is<Type>(t => t == typeof(AtlasInstanceManager)), "Could not specialize MSI sidecar since MSIContext was empty"), Times.Once);
830+
It.Is<Type>(t => t == typeof(AtlasInstanceManager)), "Could not specialize MSI sidecar since MSIContext and EncryptedTokenServiceSpecializationPayload were empty"), Times.Once);
768831
}
769832

770833
[Fact]
@@ -1308,9 +1371,15 @@ private AtlasInstanceManager GetInstanceManagerForMSISpecialization(HostAssignme
13081371

13091372
var msiEndpoint = hostAssignmentContext.Environment[EnvironmentSettingNames.MsiEndpoint] + ScriptConstants.LinuxMSISpecializationStem;
13101373

1374+
var defaultEncryptedMsiEndpoint = hostAssignmentContext.Environment[EnvironmentSettingNames.MsiEndpoint] + ScriptConstants.LinuxEncryptedTokenServiceSpecializationStem;
1375+
1376+
var providedEncryptedMsiEndpoint = hostAssignmentContext.Environment[EnvironmentSettingNames.MsiEndpoint] + hostAssignmentContext.TokenServiceApiEndpoint;
1377+
13111378
handlerMock.Protected().Setup<Task<HttpResponseMessage>>("SendAsync",
13121379
ItExpr.Is<HttpRequestMessage>(request => request.Method == HttpMethod.Post
1313-
&& request.RequestUri.AbsoluteUri.Equals(msiEndpoint)
1380+
&& (request.RequestUri.AbsoluteUri.Equals(msiEndpoint)
1381+
|| request.RequestUri.AbsoluteUri.Equals(defaultEncryptedMsiEndpoint)
1382+
|| request.RequestUri.AbsoluteUri.Equals(providedEncryptedMsiEndpoint))
13141383
&& request.Content != null),
13151384
ItExpr.IsAny<CancellationToken>())
13161385
.Callback<HttpRequestMessage, CancellationToken>((request, token) => customAction?.Invoke(request, token))

0 commit comments

Comments
 (0)