mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-15 03:18:07 +00:00
migrate timer_proxy part 2 (#1836)
This commit is contained in:
@ -8,6 +8,7 @@
|
|||||||
<WarningLevel>5</WarningLevel>
|
<WarningLevel>5</WarningLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Faithlife.Utility" Version="0.12.2" />
|
||||||
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.EventGrid" Version="2.1.0" />
|
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.EventGrid" Version="2.1.0" />
|
||||||
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
|
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using PoolName = System.String;
|
using PoolName = System.String;
|
||||||
|
@ -95,7 +95,7 @@ public record ProxyHeartbeat
|
|||||||
DateTimeOffset TimeStamp
|
DateTimeOffset TimeStamp
|
||||||
);
|
);
|
||||||
|
|
||||||
public partial record Node
|
public record Node
|
||||||
(
|
(
|
||||||
DateTimeOffset? InitializedAt,
|
DateTimeOffset? InitializedAt,
|
||||||
[PartitionKey] PoolName PoolName,
|
[PartitionKey] PoolName PoolName,
|
||||||
@ -111,27 +111,40 @@ public partial record Node
|
|||||||
) : StatefulEntityBase<NodeState>(State);
|
) : StatefulEntityBase<NodeState>(State);
|
||||||
|
|
||||||
|
|
||||||
public partial record ProxyForward
|
public record Forward
|
||||||
|
(
|
||||||
|
int SrcPort,
|
||||||
|
int DstPort,
|
||||||
|
string DstIp
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
public record ProxyForward
|
||||||
(
|
(
|
||||||
[PartitionKey] Region Region,
|
[PartitionKey] Region Region,
|
||||||
|
int Port,
|
||||||
|
Guid ScalesetId,
|
||||||
|
Guid MachineId,
|
||||||
|
Guid? ProxyId,
|
||||||
[RowKey] int DstPort,
|
[RowKey] int DstPort,
|
||||||
int SrcPort,
|
string DstIp,
|
||||||
string DstIp
|
DateTimeOffset EndTime
|
||||||
) : EntityBase();
|
) : EntityBase();
|
||||||
|
|
||||||
public partial record ProxyConfig
|
public record ProxyConfig
|
||||||
(
|
(
|
||||||
Uri Url,
|
Uri Url,
|
||||||
string Notification,
|
Uri Notification,
|
||||||
Region Region,
|
Region Region,
|
||||||
Guid? ProxyId,
|
Guid? ProxyId,
|
||||||
List<ProxyForward> Forwards,
|
List<Forward> Forwards,
|
||||||
string InstanceTelemetryKey,
|
string InstanceTelemetryKey,
|
||||||
string MicrosoftTelemetryKey
|
string MicrosoftTelemetryKey,
|
||||||
|
Guid InstanceId
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
public partial record Proxy
|
public record Proxy
|
||||||
(
|
(
|
||||||
[PartitionKey] Region Region,
|
[PartitionKey] Region Region,
|
||||||
[RowKey] Guid ProxyId,
|
[RowKey] Guid ProxyId,
|
||||||
|
@ -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 Async = System.Threading.Tasks;
|
||||||
|
|
||||||
global using System;
|
global using System;
|
||||||
|
@ -36,6 +36,8 @@ public interface IServiceConfig
|
|||||||
|
|
||||||
public string? OneFuzzResourceGroup { get; }
|
public string? OneFuzzResourceGroup { get; }
|
||||||
public string? OneFuzzTelemetry { get; }
|
public string? OneFuzzTelemetry { get; }
|
||||||
|
|
||||||
|
public string OnefuzzVersion { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ServiceConfiguration : IServiceConfig
|
public class ServiceConfiguration : IServiceConfig
|
||||||
@ -79,4 +81,5 @@ public class ServiceConfiguration : IServiceConfig
|
|||||||
public string? OneFuzzOwner { get => Environment.GetEnvironmentVariable("ONEFUZZ_OWNER"); }
|
public string? OneFuzzOwner { get => Environment.GetEnvironmentVariable("ONEFUZZ_OWNER"); }
|
||||||
public string? OneFuzzResourceGroup { get => Environment.GetEnvironmentVariable("ONEFUZZ_RESOURCE_GROUP"); }
|
public string? OneFuzzResourceGroup { get => Environment.GetEnvironmentVariable("ONEFUZZ_RESOURCE_GROUP"); }
|
||||||
public string? OneFuzzTelemetry { get => Environment.GetEnvironmentVariable("ONEFUZZ_TELEMETRY"); }
|
public string? OneFuzzTelemetry { get => Environment.GetEnvironmentVariable("ONEFUZZ_TELEMETRY"); }
|
||||||
|
public string OnefuzzVersion { get => Environment.GetEnvironmentVariable("ONEFUZZ_VERSION") ?? "0.0.0"; }
|
||||||
}
|
}
|
16
src/ApiService/ApiService/onefuzzlib/Auth.cs
Normal file
16
src/ApiService/ApiService/onefuzzlib/Auth.cs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -3,17 +3,20 @@ using Azure.ResourceManager;
|
|||||||
using Azure.Storage.Blobs;
|
using Azure.Storage.Blobs;
|
||||||
using Azure.Storage;
|
using Azure.Storage;
|
||||||
using Azure;
|
using Azure;
|
||||||
|
using Azure.Storage.Sas;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service;
|
namespace Microsoft.OneFuzz.Service;
|
||||||
|
|
||||||
|
|
||||||
public interface IContainers
|
public interface IContainers
|
||||||
{
|
{
|
||||||
public Task<IEnumerable<byte>?> GetBlob(Container container, string name, StorageType storageType);
|
public Task<BinaryData?> GetBlob(Container container, string name, StorageType storageType);
|
||||||
|
|
||||||
public Async.Task<BlobContainerClient?> FindContainer(Container container, StorageType storageType);
|
public Async.Task<BlobContainerClient?> 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<Uri?> GetFileSasUrl(Container container, string name, StorageType storageType, BlobSasPermissions permissions, TimeSpan? duration = null);
|
||||||
|
Async.Task saveBlob(Container container, string v1, string v2, StorageType config);
|
||||||
|
Task<Guid> GetInstanceId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Containers : IContainers
|
public class Containers : IContainers
|
||||||
@ -29,7 +32,7 @@ public class Containers : IContainers
|
|||||||
_creds = creds;
|
_creds = creds;
|
||||||
_armClient = creds.ArmClient;
|
_armClient = creds.ArmClient;
|
||||||
}
|
}
|
||||||
public async Task<IEnumerable<byte>?> GetBlob(Container container, string name, StorageType storageType)
|
public async Task<BinaryData?> GetBlob(Container container, string name, StorageType storageType)
|
||||||
{
|
{
|
||||||
var client = await FindContainer(container, storageType);
|
var client = await FindContainer(container, storageType);
|
||||||
|
|
||||||
@ -41,7 +44,7 @@ public class Containers : IContainers
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
return (await client.GetBlobClient(name).DownloadContentAsync())
|
return (await client.GetBlobClient(name).DownloadContentAsync())
|
||||||
.Value.Content.ToArray();
|
.Value.Content;
|
||||||
}
|
}
|
||||||
catch (RequestFailedException)
|
catch (RequestFailedException)
|
||||||
{
|
{
|
||||||
@ -85,9 +88,59 @@ public class Containers : IContainers
|
|||||||
return new Uri($"https://{accountName}.blob.core.windows.net/");
|
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<Uri?> 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<Guid> 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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,12 +15,11 @@ public class ConfigOperations : Orm<InstanceConfig>, IConfigOperations
|
|||||||
{
|
{
|
||||||
private readonly IEvents _events;
|
private readonly IEvents _events;
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IServiceConfig _config;
|
|
||||||
public ConfigOperations(IStorage storage, IEvents events, ILogTracer log, IServiceConfig config) : base(storage, log, config)
|
public ConfigOperations(IStorage storage, IEvents events, ILogTracer log, IServiceConfig config) : base(storage, log, config)
|
||||||
{
|
{
|
||||||
_events = events;
|
_events = events;
|
||||||
_log = log;
|
_log = log;
|
||||||
_config = config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<InstanceConfig> Fetch()
|
public async Task<InstanceConfig> Fetch()
|
||||||
|
@ -25,12 +25,6 @@ public partial class TimerProxy
|
|||||||
_subnet = subnet;
|
_subnet = subnet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Guid GenerateGuidv5(Guid nameSpace, string name)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Async.Task<Network> Create(string region, ICreds creds, IConfigOperations configOperations, ISubnet subnet)
|
public static async Async.Task<Network> Create(string region, ICreds creds, IConfigOperations configOperations, ISubnet subnet)
|
||||||
{
|
{
|
||||||
var group = creds.GetBaseResourceGroup();
|
var group = creds.GetBaseResourceGroup();
|
||||||
@ -50,7 +44,7 @@ public partial class TimerProxy
|
|||||||
}
|
}
|
||||||
else
|
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}";
|
name = $"{region}-{networkId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using ApiService.OneFuzzLib.Orm;
|
using ApiService.OneFuzzLib.Orm;
|
||||||
|
using Azure.Storage.Sas;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service;
|
namespace Microsoft.OneFuzz.Service;
|
||||||
|
|
||||||
@ -10,7 +11,6 @@ public interface INotificationOperations
|
|||||||
|
|
||||||
public class NotificationOperations : Orm<Notification>, INotificationOperations
|
public class NotificationOperations : Orm<Notification>, INotificationOperations
|
||||||
{
|
{
|
||||||
private ILogTracer _log;
|
|
||||||
private IReports _reports;
|
private IReports _reports;
|
||||||
private ITaskOperations _taskOperations;
|
private ITaskOperations _taskOperations;
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
|
|||||||
public NotificationOperations(ILogTracer log, IStorage storage, IReports reports, ITaskOperations taskOperations, IContainers containers, IQueue queue, IEvents events, IServiceConfig config)
|
public NotificationOperations(ILogTracer log, IStorage storage, IReports reports, ITaskOperations taskOperations, IContainers containers, IQueue queue, IEvents events, IServiceConfig config)
|
||||||
: base(storage, log, config)
|
: base(storage, log, config)
|
||||||
{
|
{
|
||||||
_log = log;
|
|
||||||
_reports = reports;
|
_reports = reports;
|
||||||
_taskOperations = taskOperations;
|
_taskOperations = taskOperations;
|
||||||
_containers = containers;
|
_containers = containers;
|
||||||
@ -76,9 +76,9 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
|
|||||||
{
|
{
|
||||||
if (containers.Contains(container.ContainerName))
|
if (containers.Contains(container.ContainerName))
|
||||||
{
|
{
|
||||||
_log.Info($"queuing input {container.ContainerName} {filename} {task.TaskId}");
|
_logTracer.Info($"queuing input {container.ContainerName} {filename} {task.TaskId}");
|
||||||
var url = _containers.GetFileSasUrl(container, filename, StorageType.Corpus, read: true, delete: true);
|
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);
|
await _queue.SendMessage(task.TaskId.ToString(), System.Text.Encoding.UTF8.GetBytes(url?.ToString() ?? ""), StorageType.Corpus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
|
|||||||
return await _taskOperations.GetByJobIdAndTaskId(report.CrashTestResult.NoReproReport.JobId, report.CrashTestResult.NoReproReport.TaskId);
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,8 +72,10 @@ namespace Microsoft.OneFuzz.Service
|
|||||||
return !active_regions.Contains(nsg_region) && nsg_region == nsg_name;
|
return !active_regions.Contains(nsg_region) && nsg_region == nsg_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns True if deletion completed (thus resource not found) or successfully started.
|
/// <summary>
|
||||||
// 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.
|
||||||
|
/// </summary>
|
||||||
public async Async.Task<bool> StartDeleteNsg(string name)
|
public async Async.Task<bool> StartDeleteNsg(string name)
|
||||||
{
|
{
|
||||||
_logTracer.Info($"deleting nsg: {name}");
|
_logTracer.Info($"deleting nsg: {name}");
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
using ApiService.OneFuzzLib.Orm;
|
||||||
|
|
||||||
|
namespace Microsoft.OneFuzz.Service;
|
||||||
|
|
||||||
|
|
||||||
|
public interface IProxyForwardOperations : IOrm<ProxyForward>
|
||||||
|
{
|
||||||
|
IAsyncEnumerable<ProxyForward> SearchForward(Guid? scalesetId = null, string? region = null, Guid? machineId = null, Guid? proxyId = null, int? dstPort = null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class ProxyForwardOperations : Orm<ProxyForward>, IProxyForwardOperations
|
||||||
|
{
|
||||||
|
public ProxyForwardOperations(IStorage storage, ILogTracer logTracer, IServiceConfig config) : base(storage, logTracer, config)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAsyncEnumerable<ProxyForward> 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);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using ApiService.OneFuzzLib.Orm;
|
using ApiService.OneFuzzLib.Orm;
|
||||||
|
using Azure.Storage.Sas;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service;
|
namespace Microsoft.OneFuzz.Service;
|
||||||
@ -11,19 +12,27 @@ public interface IProxyOperations : IStatefulOrm<Proxy, VmState>
|
|||||||
bool IsAlive(Proxy proxy);
|
bool IsAlive(Proxy proxy);
|
||||||
System.Threading.Tasks.Task SaveProxyConfig(Proxy proxy);
|
System.Threading.Tasks.Task SaveProxyConfig(Proxy proxy);
|
||||||
bool IsOutdated(Proxy proxy);
|
bool IsOutdated(Proxy proxy);
|
||||||
System.Threading.Tasks.Task GetOrCreate(string region);
|
System.Threading.Tasks.Task<Proxy?> GetOrCreate(string region);
|
||||||
}
|
}
|
||||||
public class ProxyOperations : StatefulOrm<Proxy, VmState>, IProxyOperations
|
public class ProxyOperations : StatefulOrm<Proxy, VmState>, IProxyOperations
|
||||||
{
|
{
|
||||||
private readonly ILogTracer _log;
|
|
||||||
|
|
||||||
private readonly IEvents _events;
|
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)
|
static TimeSpan PROXY_LIFESPAN = TimeSpan.FromDays(7);
|
||||||
: base(storage, log, config)
|
|
||||||
|
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;
|
_events = events;
|
||||||
|
_proxyForwardOperations = proxyForwardOperations;
|
||||||
|
_containers = containers;
|
||||||
|
_queue = queue;
|
||||||
|
_creds = creds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Proxy?> GetByProxyId(Guid proxyId)
|
public async Task<Proxy?> GetByProxyId(Guid proxyId)
|
||||||
@ -34,26 +43,97 @@ public class ProxyOperations : StatefulOrm<Proxy, VmState>, IProxyOperations
|
|||||||
return await data.FirstOrDefaultAsync();
|
return await data.FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public System.Threading.Tasks.Task GetOrCreate(string region)
|
public async System.Threading.Tasks.Task<Proxy?> 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)
|
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)
|
public bool IsOutdated(Proxy proxy)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (!VmStateHelper.Available().Contains(proxy.State))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public System.Threading.Tasks.Task SaveProxyConfig(Proxy proxy)
|
if (proxy.Version != _config.OnefuzzVersion)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
_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 async System.Threading.Tasks.Task SaveProxyConfig(Proxy proxy)
|
||||||
|
{
|
||||||
|
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)
|
public async Async.Task SetState(Proxy proxy, VmState state)
|
||||||
{
|
{
|
||||||
if (proxy.State == state)
|
if (proxy.State == state)
|
||||||
@ -65,4 +145,23 @@ public class ProxyOperations : StatefulOrm<Proxy, VmState>, IProxyOperations
|
|||||||
|
|
||||||
await _events.SendEvent(new EventProxyStateUpdated(proxy.Region, proxy.ProxyId, proxy.State));
|
await _events.SendEvent(new EventProxyStateUpdated(proxy.Region, proxy.ProxyId, proxy.State));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Async.Task<List<Forward>> GetForwards(Proxy proxy)
|
||||||
|
{
|
||||||
|
var forwards = new List<Forward>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using Azure.Storage;
|
using Azure.Storage;
|
||||||
using Azure.Storage.Queues;
|
using Azure.Storage.Queues;
|
||||||
|
using Azure.Storage.Sas;
|
||||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
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 SendMessage(string name, byte[] message, StorageType storageType, TimeSpan? visibilityTimeout = null, TimeSpan? timeToLive = null);
|
||||||
Async.Task<bool> QueueObject<T>(string name, T obj, StorageType storageType, TimeSpan? visibilityTimeout);
|
Async.Task<bool> QueueObject<T>(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;
|
IStorage _storage;
|
||||||
ILogTracer _log;
|
ILogTracer _log;
|
||||||
|
|
||||||
|
static TimeSpan DEFAULT_DURATION = TimeSpan.FromDays(30);
|
||||||
|
|
||||||
public Queue(IStorage storage, ILogTracer log)
|
public Queue(IStorage storage, ILogTracer log)
|
||||||
{
|
{
|
||||||
_storage = storage;
|
_storage = storage;
|
||||||
@ -80,4 +84,12 @@ public class Queue : IQueue
|
|||||||
return false;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ public class Reports : IReports
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseReportOrRegression(blob, filePath, expectReports);
|
return ParseReportOrRegression(blob.ToString(), filePath, expectReports);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RegressionReportOrReport? ParseReportOrRegression(string content, string? filePath, bool expectReports = false)
|
private RegressionReportOrReport? ParseReportOrRegression(string content, string? filePath, bool expectReports = false)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
using Azure.ResourceManager.Network;
|
using Azure.ResourceManager.Network;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service;
|
namespace Microsoft.OneFuzz.Service;
|
||||||
|
|
||||||
|
|
||||||
public interface ISubnet
|
public interface ISubnet
|
||||||
{
|
{
|
||||||
System.Threading.Tasks.Task<VirtualNetworkResource?> GetVnet(string vnetName);
|
System.Threading.Tasks.Task<VirtualNetworkResource?> GetVnet(string vnetName);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using Azure.Data.Tables;
|
using Azure.Data.Tables;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@ -14,7 +14,7 @@ public abstract record EntityBase
|
|||||||
public DateTimeOffset? TimeStamp { get; set; }
|
public DateTimeOffset? TimeStamp { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract record StatefulEntityBase<T>([property: JsonIgnore] T state) : EntityBase() where T : Enum;
|
public abstract record StatefulEntityBase<T>([property: JsonIgnore] T State) : EntityBase() where T : Enum;
|
||||||
|
|
||||||
/// Indicates that the enum cases should no be renamed
|
/// Indicates that the enum cases should no be renamed
|
||||||
[AttributeUsage(AttributeTargets.Enum)]
|
[AttributeUsage(AttributeTargets.Enum)]
|
||||||
@ -147,7 +147,7 @@ public class EntityConverter
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ToJsonString<T>(T typedEntity) where T : EntityBase
|
public string ToJsonString<T>(T typedEntity)
|
||||||
{
|
{
|
||||||
var serialized = JsonSerializer.Serialize(typedEntity, _options);
|
var serialized = JsonSerializer.Serialize(typedEntity, _options);
|
||||||
return serialized;
|
return serialized;
|
||||||
|
@ -20,14 +20,13 @@ namespace ApiService.OneFuzzLib.Orm
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class Orm<T> : IOrm<T> where T : EntityBase
|
public class Orm<T> : IOrm<T> where T : EntityBase
|
||||||
{
|
{
|
||||||
IStorage _storage;
|
protected readonly IStorage _storage;
|
||||||
EntityConverter _entityConverter;
|
protected readonly EntityConverter _entityConverter;
|
||||||
IServiceConfig _config;
|
protected readonly ILogTracer _logTracer;
|
||||||
protected ILogTracer _logTracer;
|
|
||||||
|
protected readonly IServiceConfig _config;
|
||||||
|
|
||||||
|
|
||||||
public Orm(IStorage storage, ILogTracer logTracer, IServiceConfig config)
|
public Orm(IStorage storage, ILogTracer logTracer, IServiceConfig config)
|
||||||
@ -179,7 +178,7 @@ namespace ApiService.OneFuzzLib.Orm
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async System.Threading.Tasks.Task<T?> ProcessStateUpdate(T entity)
|
public async System.Threading.Tasks.Task<T?> ProcessStateUpdate(T entity)
|
||||||
{
|
{
|
||||||
TState state = entity.state;
|
TState state = entity.State;
|
||||||
var func = _stateFuncs.GetOrAdd(state.ToString(), (string k) =>
|
var func = _stateFuncs.GetOrAdd(state.ToString(), (string k) =>
|
||||||
typeof(T).GetMethod(k) switch
|
typeof(T).GetMethod(k) switch
|
||||||
{
|
{
|
||||||
@ -206,13 +205,13 @@ namespace ApiService.OneFuzzLib.Orm
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < MaxUpdates; i++)
|
for (int i = 0; i < MaxUpdates; i++)
|
||||||
{
|
{
|
||||||
var state = entity.state;
|
var state = entity.State;
|
||||||
var newEntity = await ProcessStateUpdate(entity);
|
var newEntity = await ProcessStateUpdate(entity);
|
||||||
|
|
||||||
if (newEntity == null)
|
if (newEntity == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (newEntity.state.Equals(state))
|
if (newEntity.State.Equals(state))
|
||||||
{
|
{
|
||||||
return newEntity;
|
return newEntity;
|
||||||
}
|
}
|
||||||
|
@ -128,6 +128,12 @@
|
|||||||
"System.Text.Json": "4.7.2"
|
"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": {
|
"Microsoft.Azure.Functions.Worker": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[1.6.0, )",
|
"requested": "[1.6.0, )",
|
||||||
|
@ -71,13 +71,17 @@ namespace Tests
|
|||||||
|
|
||||||
public static Gen<ProxyForward> ProxyForward()
|
public static Gen<ProxyForward> ProxyForward()
|
||||||
{
|
{
|
||||||
return Arb.Generate<Tuple<string, int, int, IPv4Address>>().Select(
|
return Arb.Generate<Tuple<Tuple<string, int, Guid, Guid, Guid?, int>, Tuple<IPv4Address, DateTimeOffset>>>().Select(
|
||||||
arg =>
|
arg =>
|
||||||
new ProxyForward(
|
new ProxyForward(
|
||||||
Region: arg.Item1,
|
Region: arg.Item1.Item1,
|
||||||
DstPort: arg.Item2,
|
Port: arg.Item1.Item2,
|
||||||
SrcPort: arg.Item3,
|
ScalesetId: arg.Item1.Item3,
|
||||||
DstIp: arg.Item4.Item.ToString()
|
MachineId: arg.Item1.Item4,
|
||||||
|
ProxyId: arg.Item1.Item5,
|
||||||
|
DstPort: arg.Item1.Item6,
|
||||||
|
DstIp: arg.Item2.Item1.ToString(),
|
||||||
|
EndTime: arg.Item2.Item2
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -594,6 +598,8 @@ namespace Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
//Sample function on how repro a failing test run, using Replay
|
//Sample function on how repro a failing test run, using Replay
|
||||||
//functionality of FsCheck. Feel free to
|
//functionality of FsCheck. Feel free to
|
||||||
|
Reference in New Issue
Block a user