diff --git a/src/ApiService/ApiService/TestHooks.cs b/src/ApiService/ApiService/TestHooks.cs index 620bdbe207..4c8a920256 100644 --- a/src/ApiService/ApiService/TestHooks.cs +++ b/src/ApiService/ApiService/TestHooks.cs @@ -41,27 +41,7 @@ public async Task Info([HttpTrigger(AuthorizationLevel.Anonymo return response; } - [Function("InstanceConfig")] - public async Task InstanceConfig([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/instance-config")] HttpRequestData req) { - _log.Info("Fetching instance config"); - var config = await _configOps.Fetch(); - - if (config is null) { - _log.Error("Instance config is null"); - Error err = new(ErrorCode.INVALID_REQUEST, new[] { "Instance config is null" }); - var resp = req.CreateResponse(HttpStatusCode.InternalServerError); - await resp.WriteAsJsonAsync(err); - return resp; - } else { - await _events.SendEvent(new EventInstanceConfigUpdated(config)); - var str = (new EntityConverter()).ToJsonString(config); - - var resp = req.CreateResponse(HttpStatusCode.OK); - await resp.WriteStringAsync(str); - return resp; - } - } [Function("GetKeyvaultAddress")] public async Task GetKeyVaultAddress([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/secrets/keyvaultaddress")] HttpRequestData req) { diff --git a/src/ApiService/ApiService/TestHooks/ContainerTestHooks.cs b/src/ApiService/ApiService/TestHooks/ContainerTestHooks.cs new file mode 100644 index 0000000000..2be7460753 --- /dev/null +++ b/src/ApiService/ApiService/TestHooks/ContainerTestHooks.cs @@ -0,0 +1,35 @@ +using System.Net; +using System.Threading.Tasks; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; + +using Microsoft.OneFuzz.Service; + + +namespace ApiService.TestHooks { + public class ContainerTestHooks { + + private readonly ILogTracer _log; + private readonly IConfigOperations _configOps; + private readonly IContainers _containers; + + public ContainerTestHooks(ILogTracer log, IConfigOperations configOps, IContainers containers) { + _log = log.WithTag("TestHooks", "ContainerTestHooks"); + _configOps = configOps; + _containers = containers; + } + + [Function("GetInstanceId")] + public async Task GetInstanceId([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/containers/instanceId")] HttpRequestData req) { + _log.Info("Get instance ID"); + var instanceId = await _containers.GetInstanceId(); + + var resp = req.CreateResponse(HttpStatusCode.OK); + await resp.WriteStringAsync(instanceId.ToString()); + return resp; + } + + + + } +} diff --git a/src/ApiService/ApiService/TestHooks/CredsTestHookks.cs b/src/ApiService/ApiService/TestHooks/CredsTestHookks.cs new file mode 100644 index 0000000000..4b9bb26cbc --- /dev/null +++ b/src/ApiService/ApiService/TestHooks/CredsTestHookks.cs @@ -0,0 +1,65 @@ +using System.Net; +using System.Threading.Tasks; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; + +using Microsoft.OneFuzz.Service; + + + +namespace ApiService.TestHooks { + public class CredsTestHookks { + private readonly ILogTracer _log; + private readonly IConfigOperations _configOps; + private readonly ICreds _creds; + + public CredsTestHookks(ILogTracer log, IConfigOperations configOps, ICreds creds) { + _log = log.WithTag("TestHooks", "ContainerTestHooks"); + _configOps = configOps; + _creds = creds; + } + + [Function("GetSubscription")] + public async Task GetSubscription([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/creds/subscription")] HttpRequestData req) { + _log.Info("Get subscription"); + var resp = req.CreateResponse(HttpStatusCode.OK); + await resp.WriteStringAsync(_creds.GetSubscription().ToString()); + return resp; + } + + + [Function("GetBaseResourceGroup")] + public async Task GetBaseResourceGroup([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/creds/baseResourceGroup")] HttpRequestData req) { + _log.Info("Get base resource group"); + var resp = req.CreateResponse(HttpStatusCode.OK); + await resp.WriteStringAsync(_creds.GetBaseResourceGroup().ToString()); + return resp; + } + + [Function("GetInstanceName")] + public async Task GetInstanceName([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/creds/instanceName")] HttpRequestData req) { + _log.Info("Get instance name"); + var resp = req.CreateResponse(HttpStatusCode.OK); + await resp.WriteStringAsync(_creds.GetInstanceName().ToString()); + return resp; + } + + [Function("GetBaseRegion")] + public async Task GetBaseRegion([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/creds/baseRegion")] HttpRequestData req) { + _log.Info("Get base region"); + var resp = req.CreateResponse(HttpStatusCode.OK); + var region = await _creds.GetBaseRegion(); + await resp.WriteStringAsync(region); + return resp; + } + + [Function("GetInstanceUrl")] + public async Task GetInstanceUrl([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/creds/instanceUrl")] HttpRequestData req) { + _log.Info("Get instance url"); + var resp = req.CreateResponse(HttpStatusCode.OK); + await resp.WriteStringAsync(_creds.GetInstanceUrl().ToString()); + return resp; + } + + } +} diff --git a/src/ApiService/ApiService/TestHooks/DiskOperationsTestHooks.cs b/src/ApiService/ApiService/TestHooks/DiskOperationsTestHooks.cs new file mode 100644 index 0000000000..3422b0b79b --- /dev/null +++ b/src/ApiService/ApiService/TestHooks/DiskOperationsTestHooks.cs @@ -0,0 +1,36 @@ +using System.Net; +using System.Threading.Tasks; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; + +using Microsoft.OneFuzz.Service; + + + +namespace ApiService.TestHooks { + public class DiskOperationsTestHooks { + private readonly ILogTracer _log; + private readonly IConfigOperations _configOps; + private readonly IDiskOperations _diskOps; + private readonly ICreds _creds; + + public DiskOperationsTestHooks(ILogTracer log, IConfigOperations configOps, IDiskOperations diskOps, ICreds creds) { + _log = log.WithTag("TestHooks", "ContainerTestHooks"); + _configOps = configOps; + _diskOps = diskOps; + _creds = creds; + } + + [Function("GetDiskNames")] + public async Task GetSubscription([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/disks")] HttpRequestData req) { + _log.Info("Get disk names"); + var resp = req.CreateResponse(HttpStatusCode.OK); + var diskNames = _diskOps.ListDisks(_creds.GetBaseResourceGroup()).ToList().Select(x => x.Data.Name); + await resp.WriteAsJsonAsync(diskNames); + return resp; + } + + + + } +} diff --git a/src/ApiService/ApiService/TestHooks/InstanceConfigTestHooks.cs b/src/ApiService/ApiService/TestHooks/InstanceConfigTestHooks.cs new file mode 100644 index 0000000000..f2fae2f501 --- /dev/null +++ b/src/ApiService/ApiService/TestHooks/InstanceConfigTestHooks.cs @@ -0,0 +1,72 @@ +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.OneFuzz.Service; +using Microsoft.OneFuzz.Service.OneFuzzLib.Orm; + +namespace ApiService.TestHooks { + public class InstanceConfigTestHooks { + + private readonly ILogTracer _log; + private readonly IConfigOperations _configOps; + + public InstanceConfigTestHooks(ILogTracer log, IConfigOperations configOps) { + _log = log.WithTag("TestHooks", "InstanceConfigTestHooks"); + _configOps = configOps; + } + + [Function("GetInstanceConfig")] + public async Task Get([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/instance-config")] HttpRequestData req) { + _log.Info("Fetching instance config"); + var config = await _configOps.Fetch(); + + if (config is null) { + _log.Error("Instance config is null"); + Error err = new(ErrorCode.INVALID_REQUEST, new[] { "Instance config is null" }); + var resp = req.CreateResponse(HttpStatusCode.InternalServerError); + await resp.WriteAsJsonAsync(err); + return resp; + } else { + var str = (new EntityConverter()).ToJsonString(config); + + var resp = req.CreateResponse(HttpStatusCode.OK); + await resp.WriteStringAsync(str); + return resp; + } + } + + [Function("PatchInstanceConfig")] + public async Task Patch([HttpTrigger(AuthorizationLevel.Anonymous, "patch", Route = "testhooks/instance-config")] HttpRequestData req) { + _log.Info("Patch instance config"); + + var s = await req.ReadAsStringAsync(); + var newInstanceConfig = JsonSerializer.Deserialize(s!, EntityConverter.GetJsonSerializerOptions()); + + if (newInstanceConfig is null) { + var resp = req.CreateResponse(); + resp.StatusCode = HttpStatusCode.BadRequest; + await resp.WriteAsJsonAsync(new { Error = "Instance config is not set" }); + return resp; + } else { + + var query = UriExtension.GetQueryComponents(req.Url); + bool isNew = UriExtension.GetBoolValue("isNew", query, false); + //requireEtag wont' work since our current schema does not return etag to the client when getting data form the table, so + // there is no way to know which etag to use + bool requireEtag = UriExtension.GetBoolValue("requireEtag", query, false); + + await _configOps.Save(newInstanceConfig, isNew, requireEtag); + + var resp = req.CreateResponse(); + resp.StatusCode = HttpStatusCode.OK; + return resp; + } + } + + + + + } +} diff --git a/src/ApiService/ApiService/TestHooks/UriExtension.cs b/src/ApiService/ApiService/TestHooks/UriExtension.cs new file mode 100644 index 0000000000..068afc2a19 --- /dev/null +++ b/src/ApiService/ApiService/TestHooks/UriExtension.cs @@ -0,0 +1,30 @@ +namespace ApiService.TestHooks { + public class UriExtension { + + public static IDictionary GetQueryComponents(System.Uri uri) { + var queryComponents = uri.GetComponents(UriComponents.Query, UriFormat.UriEscaped).Split("&"); + + var q = + from cs in queryComponents + where !string.IsNullOrEmpty(cs) + let i = cs.IndexOf('=') + select new KeyValuePair(Uri.UnescapeDataString(cs.Substring(0, i)), Uri.UnescapeDataString(cs.Substring(i + 1))); + + return new Dictionary(q); + } + + public static bool GetBoolValue(string key, IDictionary query, bool defaultValue = false) { + bool v; + if (query.ContainsKey(key)) { + if (!bool.TryParse(query[key], out v)) { + v = defaultValue; + } + } else { + v = defaultValue; + } + return v; + } + + + } +} diff --git a/src/ApiService/ApiService/onefuzzlib/Creds.cs b/src/ApiService/ApiService/onefuzzlib/Creds.cs index 9ae20915cd..3eae735e14 100644 --- a/src/ApiService/ApiService/onefuzzlib/Creds.cs +++ b/src/ApiService/ApiService/onefuzzlib/Creds.cs @@ -20,7 +20,7 @@ public interface ICreds { public ResourceGroupResource GetResourceGroupResource(); - public string GetBaseRegion(); + public Async.Task GetBaseRegion(); public Uri GetInstanceUrl(); } @@ -74,8 +74,12 @@ public ResourceGroupResource GetResourceGroupResource() { return ArmClient.GetResourceGroupResource(resourceId); } - public string GetBaseRegion() { - return ArmClient.GetResourceGroupResource(GetResourceGroupResourceIdentifier()).Data.Location.Name; + public async Async.Task GetBaseRegion() { + var rg = await ArmClient.GetResourceGroupResource(GetResourceGroupResourceIdentifier()).GetAsync(); + if (rg.GetRawResponse().IsError) { + throw new Exception($"Failed to get base region due to [{rg.GetRawResponse().Status}] {rg.GetRawResponse().ReasonPhrase}"); + } + return rg.Value.Data.Location.Name; } public Uri GetInstanceUrl() { diff --git a/src/ApiService/ApiService/onefuzzlib/ReproOperations.cs b/src/ApiService/ApiService/onefuzzlib/ReproOperations.cs index 0152f1da42..f5c28a637a 100644 --- a/src/ApiService/ApiService/onefuzzlib/ReproOperations.cs +++ b/src/ApiService/ApiService/onefuzzlib/ReproOperations.cs @@ -48,7 +48,7 @@ public async Async.Task GetVm(Repro repro, InstanceConfig config) { } vmConfig = new TaskVm( - _creds.GetBaseRegion(), + await _creds.GetBaseRegion(), DEFAULT_SKU, DEFAULT_OS[task.Os], null