diff --git a/src/ApiService/ApiService/ApiService.csproj b/src/ApiService/ApiService/ApiService.csproj
index c1186f3b6c..f56fac7967 100644
--- a/src/ApiService/ApiService/ApiService.csproj
+++ b/src/ApiService/ApiService/ApiService.csproj
@@ -8,6 +8,7 @@
5
+
diff --git a/src/ApiService/ApiService/OneFuzzTypes/Events.cs b/src/ApiService/ApiService/OneFuzzTypes/Events.cs
index 7f0365044f..205b875826 100644
--- a/src/ApiService/ApiService/OneFuzzTypes/Events.cs
+++ b/src/ApiService/ApiService/OneFuzzTypes/Events.cs
@@ -1,4 +1,4 @@
-using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
+using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
using System.Text.Json;
using System.Text.Json.Serialization;
using PoolName = System.String;
diff --git a/src/ApiService/ApiService/OneFuzzTypes/Model.cs b/src/ApiService/ApiService/OneFuzzTypes/Model.cs
index 1c54b0dd75..acbf6b3797 100644
--- a/src/ApiService/ApiService/OneFuzzTypes/Model.cs
+++ b/src/ApiService/ApiService/OneFuzzTypes/Model.cs
@@ -95,7 +95,7 @@ public record ProxyHeartbeat
DateTimeOffset TimeStamp
);
-public partial record Node
+public record Node
(
DateTimeOffset? InitializedAt,
[PartitionKey] PoolName PoolName,
@@ -111,27 +111,40 @@ bool DebugKeepNode
) : StatefulEntityBase(State);
-public partial record ProxyForward
+public record Forward
(
- [PartitionKey] Region Region,
- [RowKey] int DstPort,
int SrcPort,
+ int DstPort,
string DstIp
+);
+
+
+public record ProxyForward
+(
+ [PartitionKey] Region Region,
+ int Port,
+ Guid ScalesetId,
+ Guid MachineId,
+ Guid? ProxyId,
+ [RowKey] int DstPort,
+ string DstIp,
+ DateTimeOffset EndTime
) : EntityBase();
-public partial record ProxyConfig
+public record ProxyConfig
(
Uri Url,
- string Notification,
+ Uri Notification,
Region Region,
Guid? ProxyId,
- List Forwards,
+ List Forwards,
string InstanceTelemetryKey,
- string MicrosoftTelemetryKey
+ string MicrosoftTelemetryKey,
+ Guid InstanceId
);
-public partial record Proxy
+public record Proxy
(
[PartitionKey] Region Region,
[RowKey] Guid ProxyId,
diff --git a/src/ApiService/ApiService/Program.cs b/src/ApiService/ApiService/Program.cs
index f326659bd3..50a5315274 100644
--- a/src/ApiService/ApiService/Program.cs
+++ b/src/ApiService/ApiService/Program.cs
@@ -1,4 +1,4 @@
-// to avoid collision with Task in model.cs
+// to avoid collision with Task in model.cs
global using Async = System.Threading.Tasks;
global using System;
diff --git a/src/ApiService/ApiService/ServiceConfiguration.cs b/src/ApiService/ApiService/ServiceConfiguration.cs
index 7177a73edd..f82a8e7606 100644
--- a/src/ApiService/ApiService/ServiceConfiguration.cs
+++ b/src/ApiService/ApiService/ServiceConfiguration.cs
@@ -36,6 +36,8 @@ public interface IServiceConfig
public string? OneFuzzResourceGroup { get; }
public string? OneFuzzTelemetry { get; }
+
+ public string OnefuzzVersion { get; }
}
public class ServiceConfiguration : IServiceConfig
@@ -79,4 +81,5 @@ public ServiceConfiguration()
public string? OneFuzzOwner { get => Environment.GetEnvironmentVariable("ONEFUZZ_OWNER"); }
public string? OneFuzzResourceGroup { get => Environment.GetEnvironmentVariable("ONEFUZZ_RESOURCE_GROUP"); }
public string? OneFuzzTelemetry { get => Environment.GetEnvironmentVariable("ONEFUZZ_TELEMETRY"); }
+ public string OnefuzzVersion { get => Environment.GetEnvironmentVariable("ONEFUZZ_VERSION") ?? "0.0.0"; }
}
\ No newline at end of file
diff --git a/src/ApiService/ApiService/onefuzzlib/Auth.cs b/src/ApiService/ApiService/onefuzzlib/Auth.cs
new file mode 100644
index 0000000000..67b8473c14
--- /dev/null
+++ b/src/ApiService/ApiService/onefuzzlib/Auth.cs
@@ -0,0 +1,16 @@
+namespace Microsoft.OneFuzz.Service;
+
+using System.Security.Cryptography;
+
+public class Auth
+{
+ public static Authentication BuildAuth()
+ {
+ var rsa = RSA.Create(2048);
+ string header = "-----BEGIN RSA PRIVATE KEY-----";
+ string footer = "-----END RSA PRIVATE KEY-----";
+ var privateKey = $"{header}\n{Convert.ToBase64String(rsa.ExportRSAPrivateKey())}\n{footer}";
+ var publiceKey = $"{header}\n{Convert.ToBase64String(rsa.ExportRSAPublicKey())}\n{footer}";
+ return new Authentication(Guid.NewGuid().ToString(), publiceKey, privateKey);
+ }
+}
diff --git a/src/ApiService/ApiService/onefuzzlib/Containers.cs b/src/ApiService/ApiService/onefuzzlib/Containers.cs
index 184c63e26a..e71e1dda83 100644
--- a/src/ApiService/ApiService/onefuzzlib/Containers.cs
+++ b/src/ApiService/ApiService/onefuzzlib/Containers.cs
@@ -3,17 +3,20 @@
using Azure.Storage.Blobs;
using Azure.Storage;
using Azure;
+using Azure.Storage.Sas;
namespace Microsoft.OneFuzz.Service;
+
public interface IContainers
{
- public Task?> GetBlob(Container container, string name, StorageType storageType);
+ public Task GetBlob(Container container, string name, StorageType storageType);
public Async.Task FindContainer(Container container, StorageType storageType);
- public Uri GetFileSasUrl(Container container, string name, StorageType storageType, bool read = false, bool add = false, bool create = false, bool write = false, bool delete = false, bool delete_previous_version = false, bool tag = false, int days = 30, int hours = 0, int minutes = 0);
-
+ public Async.Task GetFileSasUrl(Container container, string name, StorageType storageType, BlobSasPermissions permissions, TimeSpan? duration = null);
+ Async.Task saveBlob(Container container, string v1, string v2, StorageType config);
+ Task GetInstanceId();
}
public class Containers : IContainers
@@ -29,7 +32,7 @@ public Containers(ILogTracer log, IStorage storage, ICreds creds)
_creds = creds;
_armClient = creds.ArmClient;
}
- public async Task?> GetBlob(Container container, string name, StorageType storageType)
+ public async Task GetBlob(Container container, string name, StorageType storageType)
{
var client = await FindContainer(container, storageType);
@@ -41,7 +44,7 @@ public Containers(ILogTracer log, IStorage storage, ICreds creds)
try
{
return (await client.GetBlobClient(name).DownloadContentAsync())
- .Value.Content.ToArray();
+ .Value.Content;
}
catch (RequestFailedException)
{
@@ -85,9 +88,59 @@ private static Uri GetUrl(string accountName)
return new Uri($"https://{accountName}.blob.core.windows.net/");
}
- public Uri GetFileSasUrl(Container container, string name, StorageType storageType, bool read = false, bool add = false, bool create = false, bool write = false, bool delete = false, bool delete_previous_version = false, bool tag = false, int days = 30, int hours = 0, int minutes = 0)
+ public async Async.Task GetFileSasUrl(Container container, string name, StorageType storageType, BlobSasPermissions permissions, TimeSpan? duration = null)
{
- throw new NotImplementedException();
+ var client = await FindContainer(container, storageType) ?? throw new Exception($"unable to find container: {container.ContainerName} - {storageType}");
+ var (accountName, accountKey) = _storage.GetStorageAccountNameAndKey(client.AccountName);
+
+ var (startTime, endTime) = SasTimeWindow(duration ?? TimeSpan.FromDays(30));
+
+ var sasBuilder = new BlobSasBuilder(permissions, endTime)
+ {
+ StartsOn = startTime,
+ BlobContainerName = container.ContainerName,
+ BlobName = name
+ };
+
+ var sasUrl = client.GetBlobClient(name).GenerateSasUri(sasBuilder);
+ return sasUrl;
+ }
+
+ public (DateTimeOffset, DateTimeOffset) SasTimeWindow(TimeSpan timeSpan)
+ {
+ // SAS URLs are valid 6 hours earlier, primarily to work around dev
+ // workstations having out-of-sync time. Additionally, SAS URLs are stopped
+ // 15 minutes later than requested based on "Be careful with SAS start time"
+ // guidance.
+ // Ref: https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview
+
+ var SAS_START_TIME_DELTA = TimeSpan.FromHours(6);
+ var SAS_END_TIME_DELTA = TimeSpan.FromMinutes(6);
+
+ // SAS_START_TIME_DELTA = datetime.timedelta(hours = 6)
+ //SAS_END_TIME_DELTA = datetime.timedelta(minutes = 15)
+
+ var now = DateTimeOffset.UtcNow;
+ var start = now - SAS_START_TIME_DELTA;
+ var expiry = now + timeSpan + SAS_END_TIME_DELTA;
+ return (start, expiry);
+ }
+
+ public async System.Threading.Tasks.Task saveBlob(Container container, string name, string data, StorageType storageType)
+ {
+ var client = await FindContainer(container, storageType) ?? throw new Exception($"unable to find container: {container.ContainerName} - {storageType}");
+
+ await client.UploadBlobAsync(name, new BinaryData(data));
+ }
+
+ public async Async.Task GetInstanceId()
+ {
+ var blob = await GetBlob(new Container("base-config"), "instance_id", StorageType.Config);
+ if (blob == null)
+ {
+ throw new System.Exception("Blob Not Found");
+ }
+ return System.Guid.Parse(blob.ToString());
}
}
diff --git a/src/ApiService/ApiService/onefuzzlib/InstanceConfig.cs b/src/ApiService/ApiService/onefuzzlib/InstanceConfig.cs
index d462f5f95e..cabdf93447 100644
--- a/src/ApiService/ApiService/onefuzzlib/InstanceConfig.cs
+++ b/src/ApiService/ApiService/onefuzzlib/InstanceConfig.cs
@@ -15,12 +15,11 @@ public class ConfigOperations : Orm, IConfigOperations
{
private readonly IEvents _events;
private readonly ILogTracer _log;
- private readonly IServiceConfig _config;
+
public ConfigOperations(IStorage storage, IEvents events, ILogTracer log, IServiceConfig config) : base(storage, log, config)
{
_events = events;
_log = log;
- _config = config;
}
public async Task Fetch()
diff --git a/src/ApiService/ApiService/onefuzzlib/Network.cs b/src/ApiService/ApiService/onefuzzlib/Network.cs
index e69df06d7d..77e9a21e80 100644
--- a/src/ApiService/ApiService/onefuzzlib/Network.cs
+++ b/src/ApiService/ApiService/onefuzzlib/Network.cs
@@ -25,12 +25,6 @@ public Network(string region, string group, string name, NetworkConfig networkCo
_subnet = subnet;
}
-
- private static Guid GenerateGuidv5(Guid nameSpace, string name)
- {
- throw new NotImplementedException();
- }
-
public static async Async.Task Create(string region, ICreds creds, IConfigOperations configOperations, ISubnet subnet)
{
var group = creds.GetBaseResourceGroup();
@@ -50,7 +44,7 @@ public static async Async.Task Create(string region, ICreds creds, ICon
}
else
{
- var networkId = GenerateGuidv5(NETWORK_GUID_NAMESPACE, string.Join("|", networkConfig.AddressSpace, networkConfig.Subnet));
+ var networkId = Faithlife.Utility.GuidUtility.Create(NETWORK_GUID_NAMESPACE, string.Join("|", networkConfig.AddressSpace, networkConfig.Subnet), 5);
name = $"{region}-{networkId}";
}
diff --git a/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs b/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs
index 36d1291973..1f4cdf6882 100644
--- a/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs
+++ b/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs
@@ -1,5 +1,6 @@
using System.Text.Json;
using ApiService.OneFuzzLib.Orm;
+using Azure.Storage.Sas;
namespace Microsoft.OneFuzz.Service;
@@ -10,7 +11,6 @@ public interface INotificationOperations
public class NotificationOperations : Orm, INotificationOperations
{
- private ILogTracer _log;
private IReports _reports;
private ITaskOperations _taskOperations;
@@ -23,7 +23,7 @@ public class NotificationOperations : Orm, INotificationOperations
public NotificationOperations(ILogTracer log, IStorage storage, IReports reports, ITaskOperations taskOperations, IContainers containers, IQueue queue, IEvents events, IServiceConfig config)
: base(storage, log, config)
{
- _log = log;
+
_reports = reports;
_taskOperations = taskOperations;
_containers = containers;
@@ -76,9 +76,9 @@ public async Async.Task NewFiles(Container container, string filename, bool fail
{
if (containers.Contains(container.ContainerName))
{
- _log.Info($"queuing input {container.ContainerName} {filename} {task.TaskId}");
- var url = _containers.GetFileSasUrl(container, filename, StorageType.Corpus, read: true, delete: true);
- await _queue.SendMessage(task.TaskId.ToString(), System.Text.Encoding.UTF8.GetBytes(url.ToString()), StorageType.Corpus);
+ _logTracer.Info($"queuing input {container.ContainerName} {filename} {task.TaskId}");
+ var url = _containers.GetFileSasUrl(container, filename, StorageType.Corpus, BlobSasPermissions.Read | BlobSasPermissions.Delete);
+ await _queue.SendMessage(task.TaskId.ToString(), System.Text.Encoding.UTF8.GetBytes(url?.ToString() ?? ""), StorageType.Corpus);
}
}
@@ -125,7 +125,7 @@ public IAsyncEnumerable GetNotifications(Container container)
return await _taskOperations.GetByJobIdAndTaskId(report.CrashTestResult.NoReproReport.JobId, report.CrashTestResult.NoReproReport.TaskId);
}
- _log.Error($"unable to find crash_report or no repro entry for report: {JsonSerializer.Serialize(report)}");
+ _logTracer.Error($"unable to find crash_report or no repro entry for report: {JsonSerializer.Serialize(report)}");
return null;
}
diff --git a/src/ApiService/ApiService/onefuzzlib/Nsg.cs b/src/ApiService/ApiService/onefuzzlib/Nsg.cs
index d87b48bf89..1b96710389 100644
--- a/src/ApiService/ApiService/onefuzzlib/Nsg.cs
+++ b/src/ApiService/ApiService/onefuzzlib/Nsg.cs
@@ -72,8 +72,10 @@ public bool OkToDelete(HashSet active_regions, string nsg_region, string
return !active_regions.Contains(nsg_region) && nsg_region == nsg_name;
}
- // Returns True if deletion completed (thus resource not found) or successfully started.
- // Returns False if failed to start deletion.
+ ///
+ /// Returns True if deletion completed (thus resource not found) or successfully started.
+ /// Returns False if failed to start deletion.
+ ///
public async Async.Task StartDeleteNsg(string name)
{
_logTracer.Info($"deleting nsg: {name}");
diff --git a/src/ApiService/ApiService/onefuzzlib/ProxyForwardOperations.cs b/src/ApiService/ApiService/onefuzzlib/ProxyForwardOperations.cs
new file mode 100644
index 0000000000..f5ee767975
--- /dev/null
+++ b/src/ApiService/ApiService/onefuzzlib/ProxyForwardOperations.cs
@@ -0,0 +1,34 @@
+using ApiService.OneFuzzLib.Orm;
+
+namespace Microsoft.OneFuzz.Service;
+
+
+public interface IProxyForwardOperations : IOrm
+{
+ IAsyncEnumerable SearchForward(Guid? scalesetId = null, string? region = null, Guid? machineId = null, Guid? proxyId = null, int? dstPort = null);
+}
+
+
+public class ProxyForwardOperations : Orm, IProxyForwardOperations
+{
+ public ProxyForwardOperations(IStorage storage, ILogTracer logTracer, IServiceConfig config) : base(storage, logTracer, config)
+ {
+ }
+
+ public IAsyncEnumerable SearchForward(Guid? scalesetId = null, string? region = null, Guid? machineId = null, Guid? proxyId = null, int? dstPort = null)
+ {
+
+ var conditions =
+ new[] {
+ scalesetId != null ? $"scaleset_id eq '{scalesetId}'" : null,
+ region != null ? $"region eq '{region}'" : null ,
+ machineId != null ? $"machine_id eq '{machineId}'" : null ,
+ proxyId != null ? $"proxy_id eq '{proxyId}'" : null ,
+ dstPort != null ? $"dsp_port eq {dstPort }" : null ,
+ }.Where(x => x != null);
+
+ var filter = string.Join(" and ", conditions);
+
+ return QueryAsync(filter);
+ }
+}
diff --git a/src/ApiService/ApiService/onefuzzlib/ProxyOperations.cs b/src/ApiService/ApiService/onefuzzlib/ProxyOperations.cs
index af1db72589..4f36e7f24e 100644
--- a/src/ApiService/ApiService/onefuzzlib/ProxyOperations.cs
+++ b/src/ApiService/ApiService/onefuzzlib/ProxyOperations.cs
@@ -1,4 +1,5 @@
using ApiService.OneFuzzLib.Orm;
+using Azure.Storage.Sas;
using System.Threading.Tasks;
namespace Microsoft.OneFuzz.Service;
@@ -11,19 +12,27 @@ public interface IProxyOperations : IStatefulOrm
bool IsAlive(Proxy proxy);
System.Threading.Tasks.Task SaveProxyConfig(Proxy proxy);
bool IsOutdated(Proxy proxy);
- System.Threading.Tasks.Task GetOrCreate(string region);
+ System.Threading.Tasks.Task GetOrCreate(string region);
}
public class ProxyOperations : StatefulOrm, IProxyOperations
{
- private readonly ILogTracer _log;
private readonly IEvents _events;
+ private readonly IProxyForwardOperations _proxyForwardOperations;
+ private readonly IContainers _containers;
+ private readonly IQueue _queue;
+ private readonly ICreds _creds;
- public ProxyOperations(ILogTracer log, IStorage storage, IEvents events, IServiceConfig config)
- : base(storage, log, config)
+ static TimeSpan PROXY_LIFESPAN = TimeSpan.FromDays(7);
+
+ public ProxyOperations(ILogTracer log, IStorage storage, IEvents events, IProxyForwardOperations proxyForwardOperations, IContainers containers, IQueue queue, ICreds creds, IServiceConfig config)
+ : base(storage, log.WithTag("Component", "scaleset-proxy"), config)
{
- _log = log;
_events = events;
+ _proxyForwardOperations = proxyForwardOperations;
+ _containers = containers;
+ _queue = queue;
+ _creds = creds;
}
public async Task GetByProxyId(Guid proxyId)
@@ -34,26 +43,97 @@ public ProxyOperations(ILogTracer log, IStorage storage, IEvents events, IServic
return await data.FirstOrDefaultAsync();
}
- public System.Threading.Tasks.Task GetOrCreate(string region)
+ public async System.Threading.Tasks.Task GetOrCreate(string region)
{
- throw new NotImplementedException();
+ var proxyList = QueryAsync(filter: $"region eq '{region}' and outdated eq false");
+
+ await foreach (var proxy in proxyList)
+ {
+ if (IsOutdated(proxy))
+ {
+ await Replace(proxy with { Outdated = true });
+ continue;
+ }
+
+ if (!VmStateHelper.Available().Contains(proxy.State))
+ {
+ continue;
+ }
+ return proxy;
+ }
+
+ _logTracer.Info($"creating proxy: region:{region}");
+ var newProxy = new Proxy(region, Guid.NewGuid(), DateTimeOffset.UtcNow, VmState.Init, Auth.BuildAuth(), null, null, _config.OnefuzzVersion, null, false);
+
+ await Replace(newProxy);
+ await _events.SendEvent(new EventProxyCreated(region, newProxy.ProxyId));
+ return newProxy;
}
public bool IsAlive(Proxy proxy)
{
- throw new NotImplementedException();
+ var tenMinutesAgo = DateTimeOffset.UtcNow - TimeSpan.FromMinutes(10);
+
+ if (proxy.Heartbeat != null && proxy.Heartbeat.TimeStamp < tenMinutesAgo)
+ {
+ _logTracer.Info($"last heartbeat is more than an 10 minutes old: {proxy.Region} - last heartbeat:{proxy.Heartbeat} compared_to:{tenMinutesAgo}");
+ return false;
+ }
+
+ if (proxy.Heartbeat != null && proxy.TimeStamp != null && proxy.TimeStamp < tenMinutesAgo)
+ {
+ _logTracer.Error($"no heartbeat in the last 10 minutes: {proxy.Region} timestamp: {proxy.TimeStamp} compared_to:{tenMinutesAgo}");
+ return false;
+ }
+
+ return true;
}
public bool IsOutdated(Proxy proxy)
{
- throw new NotImplementedException();
+ if (!VmStateHelper.Available().Contains(proxy.State))
+ {
+ return false;
+ }
+
+ if (proxy.Version != _config.OnefuzzVersion)
+ {
+ _logTracer.Info($"mismatch version: proxy:{proxy.Version} service:{_config.OnefuzzVersion} state:{proxy.State}");
+ return true;
+ }
+
+ if (proxy.CreatedTimestamp != null)
+ {
+ if (proxy.CreatedTimestamp < (DateTimeOffset.UtcNow - PROXY_LIFESPAN))
+ {
+ _logTracer.Info($"proxy older than 7 days:proxy-created:{proxy.CreatedTimestamp} state:{proxy.State}");
+ return true;
+ }
+ }
+ return false;
}
- public System.Threading.Tasks.Task SaveProxyConfig(Proxy proxy)
+ public async System.Threading.Tasks.Task SaveProxyConfig(Proxy proxy)
{
- throw new NotImplementedException();
+ var forwards = await GetForwards(proxy);
+ var url = (await _containers.GetFileSasUrl(new Container("proxy-configs"), $"{proxy.Region}/{proxy.ProxyId}/config.json", StorageType.Config, BlobSasPermissions.Read)).EnsureNotNull("Can't generate file sas");
+
+ var proxyConfig = new ProxyConfig(
+ Url: url,
+ Notification: _queue.GetQueueSas("proxy", StorageType.Config, QueueSasPermissions.Add).EnsureNotNull("can't generate queue sas"),
+ Region: proxy.Region,
+ ProxyId: proxy.ProxyId,
+ Forwards: forwards,
+ InstanceTelemetryKey: _config.ApplicationInsightsInstrumentationKey.EnsureNotNull("missing InstrumentationKey"),
+ MicrosoftTelemetryKey: _config.OneFuzzTelemetry.EnsureNotNull("missing Telemetry"),
+ InstanceId: await _containers.GetInstanceId());
+
+
+ await _containers.saveBlob(new Container("proxy-configs"), $"{proxy.Region}/{proxy.ProxyId}/config.json", _entityConverter.ToJsonString(proxyConfig), StorageType.Config);
}
+
+
public async Async.Task SetState(Proxy proxy, VmState state)
{
if (proxy.State == state)
@@ -65,4 +145,23 @@ public async Async.Task SetState(Proxy proxy, VmState state)
await _events.SendEvent(new EventProxyStateUpdated(proxy.Region, proxy.ProxyId, proxy.State));
}
+
+
+ public async Async.Task> GetForwards(Proxy proxy)
+ {
+ var forwards = new List();
+
+ await foreach (var entry in _proxyForwardOperations.SearchForward(region: proxy.Region, proxyId: proxy.ProxyId))
+ {
+ if (entry.EndTime < DateTimeOffset.UtcNow)
+ {
+ await _proxyForwardOperations.Delete(entry);
+ }
+ else
+ {
+ forwards.Add(new Forward(entry.Port, entry.DstPort, entry.DstIp));
+ }
+ }
+ return forwards;
+ }
}
diff --git a/src/ApiService/ApiService/onefuzzlib/Queue.cs b/src/ApiService/ApiService/onefuzzlib/Queue.cs
index bce5fb786d..648825528a 100644
--- a/src/ApiService/ApiService/onefuzzlib/Queue.cs
+++ b/src/ApiService/ApiService/onefuzzlib/Queue.cs
@@ -1,5 +1,6 @@
using Azure.Storage;
using Azure.Storage.Queues;
+using Azure.Storage.Sas;
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
using System.Text.Json;
using System.Threading.Tasks;
@@ -9,6 +10,7 @@ public interface IQueue
{
Async.Task SendMessage(string name, byte[] message, StorageType storageType, TimeSpan? visibilityTimeout = null, TimeSpan? timeToLive = null);
Async.Task QueueObject(string name, T obj, StorageType storageType, TimeSpan? visibilityTimeout);
+ Uri? GetQueueSas(string name, StorageType storageType, QueueSasPermissions permissions, TimeSpan? duration = null);
}
@@ -17,6 +19,8 @@ public class Queue : IQueue
IStorage _storage;
ILogTracer _log;
+ static TimeSpan DEFAULT_DURATION = TimeSpan.FromDays(30);
+
public Queue(IStorage storage, ILogTracer log)
{
_storage = storage;
@@ -80,4 +84,12 @@ public async Task QueueObject(string name, T obj, StorageType storageTy
return false;
}
}
+
+ public Uri? GetQueueSas(string name, StorageType storageType, QueueSasPermissions permissions, TimeSpan? duration)
+ {
+ var queue = GetQueue(name, storageType) ?? throw new Exception($"unable to queue object, no such queue: {name}");
+ var sasaBuilder = new QueueSasBuilder(permissions, DateTimeOffset.UtcNow + (duration ?? DEFAULT_DURATION));
+ var url = queue.GenerateSasUri(sasaBuilder);
+ return url;
+ }
}
diff --git a/src/ApiService/ApiService/onefuzzlib/Reports.cs b/src/ApiService/ApiService/onefuzzlib/Reports.cs
index a60ad3d16b..19622abba9 100644
--- a/src/ApiService/ApiService/onefuzzlib/Reports.cs
+++ b/src/ApiService/ApiService/onefuzzlib/Reports.cs
@@ -41,7 +41,7 @@ public Reports(ILogTracer log, IContainers containers)
return null;
}
- return ParseReportOrRegression(blob, filePath, expectReports);
+ return ParseReportOrRegression(blob.ToString(), filePath, expectReports);
}
private RegressionReportOrReport? ParseReportOrRegression(string content, string? filePath, bool expectReports = false)
diff --git a/src/ApiService/ApiService/onefuzzlib/Subnet.cs b/src/ApiService/ApiService/onefuzzlib/Subnet.cs
index ef06a3bf6d..6796b5a769 100644
--- a/src/ApiService/ApiService/onefuzzlib/Subnet.cs
+++ b/src/ApiService/ApiService/onefuzzlib/Subnet.cs
@@ -1,6 +1,8 @@
using Azure.ResourceManager.Network;
+
namespace Microsoft.OneFuzz.Service;
+
public interface ISubnet
{
System.Threading.Tasks.Task GetVnet(string vnetName);
diff --git a/src/ApiService/ApiService/onefuzzlib/orm/EntityConverter.cs b/src/ApiService/ApiService/onefuzzlib/orm/EntityConverter.cs
index f8fbe12980..63391aa6d6 100644
--- a/src/ApiService/ApiService/onefuzzlib/orm/EntityConverter.cs
+++ b/src/ApiService/ApiService/onefuzzlib/orm/EntityConverter.cs
@@ -1,4 +1,4 @@
-using Azure.Data.Tables;
+using Azure.Data.Tables;
using System.Reflection;
using System.Linq.Expressions;
using System.Text.Json;
@@ -14,7 +14,7 @@ public abstract record EntityBase
public DateTimeOffset? TimeStamp { get; set; }
}
-public abstract record StatefulEntityBase([property: JsonIgnore] T state) : EntityBase() where T : Enum;
+public abstract record StatefulEntityBase([property: JsonIgnore] T State) : EntityBase() where T : Enum;
/// Indicates that the enum cases should no be renamed
[AttributeUsage(AttributeTargets.Enum)]
@@ -147,7 +147,7 @@ private EntityInfo GetEntityInfo()
});
}
- public string ToJsonString(T typedEntity) where T : EntityBase
+ public string ToJsonString(T typedEntity)
{
var serialized = JsonSerializer.Serialize(typedEntity, _options);
return serialized;
diff --git a/src/ApiService/ApiService/onefuzzlib/orm/Orm.cs b/src/ApiService/ApiService/onefuzzlib/orm/Orm.cs
index e8c42e5107..51b0f4f7ff 100644
--- a/src/ApiService/ApiService/onefuzzlib/orm/Orm.cs
+++ b/src/ApiService/ApiService/onefuzzlib/orm/Orm.cs
@@ -20,14 +20,13 @@ public interface IOrm where T : EntityBase
}
-
-
public class Orm : IOrm where T : EntityBase
{
- IStorage _storage;
- EntityConverter _entityConverter;
- IServiceConfig _config;
- protected ILogTracer _logTracer;
+ protected readonly IStorage _storage;
+ protected readonly EntityConverter _entityConverter;
+ protected readonly ILogTracer _logTracer;
+
+ protected readonly IServiceConfig _config;
public Orm(IStorage storage, ILogTracer logTracer, IServiceConfig config)
@@ -179,7 +178,7 @@ public StatefulOrm(IStorage storage, ILogTracer logTracer, IServiceConfig config
///
public async System.Threading.Tasks.Task ProcessStateUpdate(T entity)
{
- TState state = entity.state;
+ TState state = entity.State;
var func = _stateFuncs.GetOrAdd(state.ToString(), (string k) =>
typeof(T).GetMethod(k) switch
{
@@ -206,13 +205,13 @@ public StatefulOrm(IStorage storage, ILogTracer logTracer, IServiceConfig config
{
for (int i = 0; i < MaxUpdates; i++)
{
- var state = entity.state;
+ var state = entity.State;
var newEntity = await ProcessStateUpdate(entity);
if (newEntity == null)
return null;
- if (newEntity.state.Equals(state))
+ if (newEntity.State.Equals(state))
{
return newEntity;
}
diff --git a/src/ApiService/ApiService/packages.lock.json b/src/ApiService/ApiService/packages.lock.json
index 80de00828c..8544dc1224 100644
--- a/src/ApiService/ApiService/packages.lock.json
+++ b/src/ApiService/ApiService/packages.lock.json
@@ -128,6 +128,12 @@
"System.Text.Json": "4.7.2"
}
},
+ "Faithlife.Utility": {
+ "type": "Direct",
+ "requested": "[0.12.2, )",
+ "resolved": "0.12.2",
+ "contentHash": "JgMAGj8ekeAzKkagubXqf1UqgfHq89GyA1UQYWbkAe441uRr2Rh2rktkx5Z0LPwmD/aOqu9cxjekD2GZjP8rbw=="
+ },
"Microsoft.Azure.Functions.Worker": {
"type": "Direct",
"requested": "[1.6.0, )",
diff --git a/src/ApiService/Tests/OrmModelsTest.cs b/src/ApiService/Tests/OrmModelsTest.cs
index 0445a7c3af..c240c9fe8d 100644
--- a/src/ApiService/Tests/OrmModelsTest.cs
+++ b/src/ApiService/Tests/OrmModelsTest.cs
@@ -71,13 +71,17 @@ public static Gen Node()
public static Gen ProxyForward()
{
- return Arb.Generate>().Select(
+ return Arb.Generate, Tuple>>().Select(
arg =>
new ProxyForward(
- Region: arg.Item1,
- DstPort: arg.Item2,
- SrcPort: arg.Item3,
- DstIp: arg.Item4.Item.ToString()
+ Region: arg.Item1.Item1,
+ Port: arg.Item1.Item2,
+ ScalesetId: arg.Item1.Item3,
+ MachineId: arg.Item1.Item4,
+ ProxyId: arg.Item1.Item5,
+ DstPort: arg.Item1.Item6,
+ DstIp: arg.Item2.Item1.ToString(),
+ EndTime: arg.Item2.Item2
)
);
}
@@ -594,9 +598,11 @@ public bool Notification(Notification n)
}
+
+
/*
//Sample function on how repro a failing test run, using Replay
- //functionality of FsCheck. Feel free to
+ //functionality of FsCheck. Feel free to
[Property]
void Replay()
{
@@ -845,7 +851,7 @@ public bool RegressionReportOrReport(RegressionReportOrReport e)
/*
//Sample function on how repro a failing test run, using Replay
- //functionality of FsCheck. Feel free to
+ //functionality of FsCheck. Feel free to
[Property]
void Replay()
{