Skip to content

Commit 7575551

Browse files
Add controller for specializing k8se pods (#7083)
1 parent c73bc15 commit 7575551

File tree

4 files changed

+208
-0
lines changed

4 files changed

+208
-0
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Authorization;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.AspNetCore.Mvc;
8+
using Microsoft.Azure.WebJobs.Script.WebHost.Management;
9+
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
10+
using Microsoft.Azure.WebJobs.Script.WebHost.Security.Authorization.Policies;
11+
using Microsoft.Extensions.Logging;
12+
13+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Controllers
14+
{
15+
public class KubernetesPodController : Controller
16+
{
17+
private readonly IEnvironment _environment;
18+
private readonly IInstanceManager _instanceManager;
19+
private readonly ILogger _logger;
20+
private readonly StartupContextProvider _startupContextProvider;
21+
22+
public KubernetesPodController(IEnvironment environment, IInstanceManager instanceManager, ILoggerFactory loggerFactory, StartupContextProvider startupContextProvider)
23+
{
24+
_environment = environment;
25+
_instanceManager = instanceManager;
26+
_logger = loggerFactory.CreateLogger<KubernetesPodController>();
27+
_startupContextProvider = startupContextProvider;
28+
}
29+
30+
[HttpPost]
31+
[Route("admin/pod/assign")]
32+
public async Task<IActionResult> Assign([FromBody] EncryptedHostAssignmentContext encryptedAssignmentContext)
33+
{
34+
_logger.LogDebug($"Starting container assignment for host : {Request?.Host}");
35+
var assignmentContext = _startupContextProvider.SetContext(encryptedAssignmentContext);
36+
37+
string error = await _instanceManager.ValidateContext(assignmentContext);
38+
if (error != null)
39+
{
40+
return StatusCode(StatusCodes.Status400BadRequest, error);
41+
}
42+
43+
var succeeded = _instanceManager.StartAssignment(assignmentContext);
44+
45+
return succeeded
46+
? Accepted()
47+
: StatusCode(StatusCodes.Status409Conflict, "Instance already assigned");
48+
}
49+
}
50+
}

src/WebJobs.Script.WebHost/Security/SimpleWebTokenHelper.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ public static string Decrypt(byte[] encryptionKey, string value)
8686

8787
public static string Decrypt(string value, IEnvironment environment = null)
8888
{
89+
if ((environment != null) && environment.IsKubernetesManagedHosting())
90+
{
91+
var encryptionKey = GetPodEncryptionKey(environment);
92+
return Decrypt(encryptionKey, value);
93+
}
8994
// Use WebSiteAuthEncryptionKey if available else fallback to ContainerEncryptionKey.
9095
// Until the container is specialized to a specific site WebSiteAuthEncryptionKey will not be available.
9196
byte[] key;
@@ -153,6 +158,16 @@ private static bool TryGetEncryptionKey(IEnvironment environment, string keyName
153158
return true;
154159
}
155160

161+
private static byte[] GetPodEncryptionKey(IEnvironment environment)
162+
{
163+
var podEncryptionKey = environment.GetEnvironmentVariable(EnvironmentSettingNames.PodEncryptionKey);
164+
if (string.IsNullOrEmpty(podEncryptionKey))
165+
{
166+
throw new Exception("Pod encryption key is empty.");
167+
}
168+
return Convert.FromBase64String(podEncryptionKey);
169+
}
170+
156171
public static byte[] ToKeyBytes(this string hexOrBase64)
157172
{
158173
// only support 32 bytes (256 bits) key length

src/WebJobs.Script/Environment/EnvironmentSettingNames.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public static class EnvironmentSettingNames
6262
//Function in Kubernetes
6363
public const string PodNamespace = "POD_NAMESPACE";
6464
public const string PodName = "POD_NAME";
65+
public const string PodEncryptionKey = "POD_ENCRYPTION_KEY";
6566

6667
/// <summary>
6768
/// Environment variable dynamically set by the platform when it is safe to
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.Net;
6+
using System.Net.Http;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Mvc;
10+
using Microsoft.Azure.WebJobs.Script.WebHost;
11+
using Microsoft.Azure.WebJobs.Script.WebHost.Controllers;
12+
using Microsoft.Azure.WebJobs.Script.WebHost.Management;
13+
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
14+
using Microsoft.Azure.WebJobs.Script.WebHost.Security;
15+
using Microsoft.Extensions.Logging;
16+
using Microsoft.WebJobs.Script.Tests;
17+
using Moq;
18+
using Moq.Protected;
19+
using Newtonsoft.Json;
20+
using Xunit;
21+
22+
namespace Microsoft.Azure.WebJobs.Script.Tests.Managment
23+
{
24+
[Trait(TestTraits.Category, TestTraits.EndToEnd)]
25+
[Trait(TestTraits.Group, TestTraits.ContainerInstanceTests)]
26+
public class KubernetesPodControllerTests
27+
{
28+
private readonly TestOptionsFactory<ScriptApplicationHostOptions> _optionsFactory = new TestOptionsFactory<ScriptApplicationHostOptions>(new ScriptApplicationHostOptions());
29+
30+
[Fact]
31+
public async Task Assignment_Succeeds_With_Encryption_Key()
32+
{
33+
var environment = new TestEnvironment();
34+
environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsitePlaceholderMode, "1");
35+
36+
var scriptWebEnvironment = new ScriptWebHostEnvironment(environment);
37+
38+
var loggerFactory = new LoggerFactory();
39+
var loggerProvider = new TestLoggerProvider();
40+
loggerFactory.AddProvider(loggerProvider);
41+
42+
var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
43+
44+
handlerMock.Protected().Setup<Task<HttpResponseMessage>>("SendAsync",
45+
ItExpr.IsAny<HttpRequestMessage>(),
46+
ItExpr.IsAny<CancellationToken>()).ReturnsAsync(new HttpResponseMessage
47+
{
48+
StatusCode = HttpStatusCode.OK
49+
});
50+
51+
var instanceManager = new InstanceManager(_optionsFactory, new HttpClient(handlerMock.Object), scriptWebEnvironment, environment, loggerFactory.CreateLogger<InstanceManager>(), new TestMetricsLogger(), null);
52+
var startupContextProvider = new StartupContextProvider(environment, loggerFactory.CreateLogger<StartupContextProvider>());
53+
54+
InstanceManager.Reset();
55+
56+
var podController = new KubernetesPodController(environment, instanceManager, loggerFactory, startupContextProvider);
57+
58+
const string podEncryptionKey = "/a/vXvWJ3Hzgx4PFxlDUJJhQm5QVyGiu0NNLFm/ZMMg=";
59+
var hostAssignmentContext = new HostAssignmentContext
60+
{
61+
Environment = new Dictionary<string, string>()
62+
{
63+
[EnvironmentSettingNames.AzureWebsiteRunFromPackage] = "http://localhost:1234"
64+
}
65+
};
66+
hostAssignmentContext.Secrets = new FunctionAppSecrets();
67+
hostAssignmentContext.IsWarmupRequest = false;
68+
69+
var encryptedHostAssignmentValue = SimpleWebTokenHelper.Encrypt(JsonConvert.SerializeObject(hostAssignmentContext), podEncryptionKey.ToKeyBytes());
70+
71+
var encryptedHostAssignmentContext = new EncryptedHostAssignmentContext()
72+
{
73+
EncryptedContext = encryptedHostAssignmentValue
74+
};
75+
76+
environment.SetEnvironmentVariable(EnvironmentSettingNames.PodEncryptionKey, podEncryptionKey);
77+
environment.SetEnvironmentVariable(EnvironmentSettingNames.KubernetesServiceHost, "http://localhost:80");
78+
environment.SetEnvironmentVariable(EnvironmentSettingNames.PodNamespace, "k8se-apps");
79+
80+
var result = await podController.Assign(encryptedHostAssignmentContext);
81+
Assert.NotNull(startupContextProvider.Context);
82+
Assert.IsType<AcceptedResult>(result);
83+
}
84+
85+
[Fact]
86+
public async Task Assignment_Fails_Without_Encryption_Key()
87+
{
88+
var environment = new TestEnvironment();
89+
environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsitePlaceholderMode, "1");
90+
91+
var scriptWebEnvironment = new ScriptWebHostEnvironment(environment);
92+
93+
var loggerFactory = new LoggerFactory();
94+
var loggerProvider = new TestLoggerProvider();
95+
loggerFactory.AddProvider(loggerProvider);
96+
97+
var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
98+
99+
handlerMock.Protected().Setup<Task<HttpResponseMessage>>("SendAsync",
100+
ItExpr.IsAny<HttpRequestMessage>(),
101+
ItExpr.IsAny<CancellationToken>()).ReturnsAsync(new HttpResponseMessage
102+
{
103+
StatusCode = HttpStatusCode.OK
104+
});
105+
106+
var instanceManager = new InstanceManager(_optionsFactory, new HttpClient(handlerMock.Object), scriptWebEnvironment, environment, loggerFactory.CreateLogger<InstanceManager>(), new TestMetricsLogger(), null);
107+
var startupContextProvider = new StartupContextProvider(environment, loggerFactory.CreateLogger<StartupContextProvider>());
108+
109+
InstanceManager.Reset();
110+
111+
const string podEncryptionKey = "/a/vXvWJ3Hzgx4PFxlDUJJhQm5QVyGiu0NNLFm/ZMMg=";
112+
var podController = new KubernetesPodController(environment, instanceManager, loggerFactory, startupContextProvider);
113+
114+
var hostAssignmentContext = new HostAssignmentContext
115+
{
116+
Environment = new Dictionary<string, string>()
117+
{
118+
[EnvironmentSettingNames.AzureWebsiteRunFromPackage] = "http://localhost:1234"
119+
}
120+
};
121+
hostAssignmentContext.Secrets = new FunctionAppSecrets();
122+
hostAssignmentContext.IsWarmupRequest = false;
123+
124+
var encryptedHostAssignmentValue = SimpleWebTokenHelper.Encrypt(JsonConvert.SerializeObject(hostAssignmentContext), podEncryptionKey.ToKeyBytes());
125+
126+
var encryptedHostAssignmentContext = new EncryptedHostAssignmentContext()
127+
{
128+
EncryptedContext = encryptedHostAssignmentValue
129+
};
130+
131+
environment.SetEnvironmentVariable(EnvironmentSettingNames.KubernetesServiceHost, "http://localhost:80");
132+
environment.SetEnvironmentVariable(EnvironmentSettingNames.PodNamespace, "k8se-apps");
133+
134+
var ex = await Assert.ThrowsAsync<System.Exception>(async () =>
135+
{
136+
await (podController.Assign(encryptedHostAssignmentContext));
137+
});
138+
Assert.Null(startupContextProvider.Context);
139+
}
140+
141+
}
142+
}

0 commit comments

Comments
 (0)