Skip to content

Commit 64fe2bb

Browse files
committed
Fixing secrets retrieval bug (#2861)
1 parent a6e0946 commit 64fe2bb

File tree

3 files changed

+40
-45
lines changed

3 files changed

+40
-45
lines changed

src/WebJobs.Script.WebHost/Controllers/KeysController.cs

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,12 @@ public class KeysController : ApiController
2323
private const string MasterKeyName = "_master";
2424

2525
private static readonly Lazy<Dictionary<string, string>> EmptyKeys = new Lazy<Dictionary<string, string>>(() => new Dictionary<string, string>());
26-
private readonly WebScriptHostManager _scriptHostManager;
2726
private readonly ISecretManager _secretManager;
2827
private readonly TraceWriter _traceWriter;
2928
private readonly ILogger _logger;
3029

31-
public KeysController(WebScriptHostManager scriptHostManager, ISecretManager secretManager, TraceWriter traceWriter, ILoggerFactory loggerFactory)
30+
public KeysController(ISecretManager secretManager, TraceWriter traceWriter, ILoggerFactory loggerFactory)
3231
{
33-
_scriptHostManager = scriptHostManager;
3432
_secretManager = secretManager;
3533
_traceWriter = traceWriter.WithDefaults($"{ScriptConstants.TraceSourceSecretManagement}.Api");
3634
_logger = loggerFactory?.CreateLogger(ScriptConstants.LogCategoryKeysController);
@@ -40,8 +38,8 @@ public KeysController(WebScriptHostManager scriptHostManager, ISecretManager sec
4038
[Route("admin/functions/{name}/keys")]
4139
public async Task<IHttpActionResult> Get(string name)
4240
{
43-
IDictionary<string, string> functionKeys = await GetFunctionKeys(name);
44-
return GetKeysResult(functionKeys);
41+
var keys = await GetFunctionKeys(name);
42+
return GetKeysResult(keys);
4543
}
4644

4745
[HttpGet]
@@ -50,7 +48,7 @@ public async Task<IHttpActionResult> Get()
5048
{
5149
string hostKeyScope = GetHostKeyScopeForRequest();
5250

53-
Dictionary<string, string> keys = await GetHostSecretsByScope(hostKeyScope);
51+
var keys = await GetHostSecretsByScope(hostKeyScope);
5452
return GetKeysResult(keys);
5553
}
5654

@@ -59,7 +57,7 @@ public async Task<IHttpActionResult> Get()
5957
public async Task<IHttpActionResult> Get(string functionName, string name)
6058
{
6159
IDictionary<string, string> functionKeys = await GetFunctionKeys(functionName);
62-
if (functionKeys.TryGetValue(name, out string keyValue))
60+
if (functionKeys != null && functionKeys.TryGetValue(name, out string keyValue))
6361
{
6462
var keyResponse = ApiModelUtility.CreateApiModel(new { name = name, value = keyValue }, Request);
6563
return Ok(keyResponse);
@@ -88,11 +86,6 @@ public async Task<IHttpActionResult> GetHostKey(string name)
8886

8987
private async Task<IDictionary<string, string>> GetFunctionKeys(string functionName)
9088
{
91-
if (!_scriptHostManager.Instance.IsFunction(functionName))
92-
{
93-
throw new HttpResponseException(HttpStatusCode.NotFound);
94-
}
95-
9689
return await _secretManager.GetFunctionSecretsAsync(functionName);
9790
}
9891

@@ -134,6 +127,11 @@ private string GetHostKeyScopeForRequest()
134127

135128
private IHttpActionResult GetKeysResult(IDictionary<string, string> keys)
136129
{
130+
if (keys == null)
131+
{
132+
return NotFound();
133+
}
134+
137135
keys = keys ?? EmptyKeys.Value;
138136
var keysContent = new
139137
{
@@ -157,11 +155,6 @@ private async Task<IHttpActionResult> PutKeyAsync(string keyName, Key key, strin
157155

158156
private async Task<IHttpActionResult> AddOrUpdateSecretAsync(string keyName, string value, string keyScope, ScriptSecretsType secretsType)
159157
{
160-
if (secretsType == ScriptSecretsType.Function && keyScope != null && !_scriptHostManager.Instance.IsFunction(keyScope))
161-
{
162-
return NotFound();
163-
}
164-
165158
KeyOperationResult operationResult;
166159
if (secretsType == ScriptSecretsType.Host && string.Equals(keyName, MasterKeyName, StringComparison.OrdinalIgnoreCase))
167160
{
@@ -231,10 +224,9 @@ private async Task<IHttpActionResult> DeleteFunctionSecretAsync(string keyName,
231224
return BadRequest("Invalid key name.");
232225
}
233226

234-
if ((secretsType == ScriptSecretsType.Function && !_scriptHostManager.Instance.IsFunction(keyScope)) ||
235-
!await _secretManager.DeleteSecretAsync(keyName, keyScope, secretsType))
227+
if (!await _secretManager.DeleteSecretAsync(keyName, keyScope, secretsType))
236228
{
237-
// Either the function or the key were not found
229+
// the key was not found
238230
return NotFound();
239231
}
240232

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public async virtual Task<HostSecretsInfo> GetHostSecretsAsync()
7373

7474
if (hostSecrets == null)
7575
{
76+
// host secrets do not yet exist so generate them
7677
_traceWriter.Verbose(Resources.TraceHostSecretGeneration);
7778
_logger?.LogDebug(Resources.TraceHostSecretGeneration);
7879
hostSecrets = GenerateHostSecrets();
@@ -132,6 +133,7 @@ public async virtual Task<IDictionary<string, string>> GetFunctionSecretsAsync(s
132133
FunctionSecrets secrets = await LoadFunctionSecretsAsync(functionName);
133134
if (secrets == null)
134135
{
136+
// no secrets exist for this function so generate them
135137
string message = string.Format(Resources.TraceFunctionSecretGeneration, functionName);
136138
_traceWriter.Verbose(message, traceProperties);
137139

test/WebJobs.Script.Tests/Controllers/Admin/KeysControllerTests.cs

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,55 +26,48 @@ public class KeysControllerTests : IDisposable
2626
{
2727
private readonly ScriptSettingsManager _settingsManager;
2828
private readonly TempDirectory _secretsDirectory = new TempDirectory();
29-
private Mock<ScriptHost> _hostMock;
30-
private Mock<WebScriptHostManager> _managerMock;
3129
private Collection<FunctionDescriptor> _testFunctions;
32-
private Dictionary<string, Collection<string>> _testFunctionErrors;
3330
private KeysController _testController;
3431
private Mock<ISecretManager> _secretsManagerMock;
3532

3633
public KeysControllerTests()
3734
{
3835
_settingsManager = ScriptSettingsManager.Instance;
3936
_testFunctions = new Collection<FunctionDescriptor>();
40-
_testFunctionErrors = new Dictionary<string, Collection<string>>();
4137

4238
var config = new ScriptHostConfiguration();
4339
config.TraceWriter = new TestTraceWriter(TraceLevel.Info);
4440
var environment = new NullScriptHostEnvironment();
4541
var eventManager = new Mock<IScriptEventManager>();
46-
_hostMock = new Mock<ScriptHost>(MockBehavior.Strict, new object[] { environment, eventManager.Object, config, null, null });
47-
_hostMock.Setup(p => p.Functions).Returns(_testFunctions);
48-
_hostMock.Setup(p => p.FunctionErrors).Returns(_testFunctionErrors);
4942

5043
WebHostSettings settings = new WebHostSettings();
5144
settings.SecretsPath = _secretsDirectory.Path;
5245
_secretsManagerMock = new Mock<ISecretManager>(MockBehavior.Strict);
53-
_managerMock = new Mock<WebScriptHostManager>(MockBehavior.Strict, new object[] { config, new TestSecretManagerFactory(_secretsManagerMock.Object), eventManager.Object, _settingsManager, settings });
54-
_managerMock.SetupGet(p => p.Instance).Returns(_hostMock.Object);
5546

5647
var traceWriter = new TestTraceWriter(TraceLevel.Verbose);
5748

58-
_testController = new KeysController(_managerMock.Object, _secretsManagerMock.Object, traceWriter, null);
59-
60-
// setup some test functions
61-
string errorFunction = "ErrorFunction";
62-
var errors = new Collection<string>();
63-
errors.Add("A really really bad error!");
64-
_testFunctionErrors.Add(errorFunction, errors);
49+
_testController = new KeysController(_secretsManagerMock.Object, traceWriter, null);
6550

6651
var keys = new Dictionary<string, string>
6752
{
6853
{ "key1", "secret1" }
6954
};
70-
_secretsManagerMock.Setup(p => p.GetFunctionSecretsAsync(errorFunction, false)).ReturnsAsync(keys);
55+
_secretsManagerMock.Setup(p => p.GetFunctionSecretsAsync("TestFunction1", false)).ReturnsAsync(keys);
56+
57+
keys = new Dictionary<string, string>
58+
{
59+
{ "key1", "secret1" }
60+
};
61+
_secretsManagerMock.Setup(p => p.GetFunctionSecretsAsync("TestFunction2", false)).ReturnsAsync(keys);
62+
63+
_secretsManagerMock.Setup(p => p.GetFunctionSecretsAsync("DNE", false)).ReturnsAsync((IDictionary<string, string>)null);
7164
}
7265

7366
[Fact]
74-
public async Task GetKeys_FunctionInError_ReturnsKeys()
67+
public async Task GetKeys_ReturnsKeys()
7568
{
7669
_testController.Request = new HttpRequestMessage(HttpMethod.Get, "https://local/admin/functions/keys");
77-
var result = (OkNegotiatedContentResult<ApiModel>)(await _testController.Get("ErrorFunction"));
70+
var result = (OkNegotiatedContentResult<ApiModel>)(await _testController.Get("TestFunction1"));
7871

7972
var content = (JObject)result.Content;
8073
var keys = content["keys"];
@@ -83,28 +76,36 @@ public async Task GetKeys_FunctionInError_ReturnsKeys()
8376
}
8477

8578
[Fact]
86-
public async Task PutKey_FunctionInError_Succeeds()
79+
public async Task GetKeys_NoSecrets_ReturnsNotFound()
80+
{
81+
var result = await _testController.Get("DNE");
82+
83+
Assert.True(result is NotFoundResult);
84+
}
85+
86+
[Fact]
87+
public async Task PutKey_Succeeds()
8788
{
8889
_testController.Request = new HttpRequestMessage(HttpMethod.Get, "https://local/admin/functions/keys/key2");
8990

9091
var key = new Key("key2", "secret2");
9192
var keyOperationResult = new KeyOperationResult(key.Value, OperationResult.Updated);
92-
_secretsManagerMock.Setup(p => p.AddOrUpdateFunctionSecretAsync(key.Name, key.Value, "ErrorFunction", ScriptSecretsType.Function)).ReturnsAsync(keyOperationResult);
93+
_secretsManagerMock.Setup(p => p.AddOrUpdateFunctionSecretAsync(key.Name, key.Value, "TestFunction1", ScriptSecretsType.Function)).ReturnsAsync(keyOperationResult);
9394

94-
var result = (OkNegotiatedContentResult<ApiModel>)(await _testController.Put("ErrorFunction", key.Name, key));
95+
var result = (OkNegotiatedContentResult<ApiModel>)(await _testController.Put("TestFunction1", key.Name, key));
9596
var content = (JObject)result.Content;
9697
Assert.Equal("key2", content["name"]);
9798
Assert.Equal("secret2", content["value"]);
9899
}
99100

100101
[Fact]
101-
public async Task DeleteKey_FunctionInError_Succeeds()
102+
public async Task DeleteKey_Succeeds()
102103
{
103104
_testController.Request = new HttpRequestMessage(HttpMethod.Get, "https://local/admin/functions/keys/key2");
104105

105-
_secretsManagerMock.Setup(p => p.DeleteSecretAsync("key2", "ErrorFunction", ScriptSecretsType.Function)).ReturnsAsync(true);
106+
_secretsManagerMock.Setup(p => p.DeleteSecretAsync("key2", "TestFunction1", ScriptSecretsType.Function)).ReturnsAsync(true);
106107

107-
var result = (StatusCodeResult)(await _testController.Delete("ErrorFunction", "key2"));
108+
var result = (StatusCodeResult)(await _testController.Delete("TestFunction1", "key2"));
108109
Assert.Equal(HttpStatusCode.NoContent, result.StatusCode);
109110
}
110111

0 commit comments

Comments
 (0)