mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-18 12:48:07 +00:00
Support for retention policies on containers (#3501)
- [x] ability to specify a retention period on a container, which applies to newly-created blobs - [x] specify default retention periods in templates from CLI side There's a small breaking change to the Python JobHelper class.
This commit is contained in:
committed by
Cheick Keita
parent
80fe109a50
commit
1cee562cf5
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@ -123,7 +123,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.7
|
python-version: "3.10"
|
||||||
- name: lint
|
- name: lint
|
||||||
shell: bash
|
shell: bash
|
||||||
run: src/ci/check-check-pr.sh
|
run: src/ci/check-check-pr.sh
|
||||||
@ -137,7 +137,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.7
|
python-version: "3.10"
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: artifact-onefuzztypes
|
name: artifact-onefuzztypes
|
||||||
@ -190,7 +190,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: "3.10"
|
||||||
- name: lint
|
- name: lint
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@ -208,7 +208,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: "3.10"
|
||||||
- name: lint
|
- name: lint
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@ -224,7 +224,7 @@ jobs:
|
|||||||
- run: src/ci/set-versions.sh
|
- run: src/ci/set-versions.sh
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.7
|
python-version: "3.10"
|
||||||
- run: src/ci/onefuzztypes.sh
|
- run: src/ci/onefuzztypes.sh
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
@ -481,7 +481,7 @@ jobs:
|
|||||||
path: artifacts
|
path: artifacts
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.7
|
python-version: "3.10"
|
||||||
- name: Lint
|
- name: Lint
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
@ -8,4 +8,5 @@ public static class FeatureFlagConstants {
|
|||||||
public const string EnableBlobRetentionPolicy = "EnableBlobRetentionPolicy";
|
public const string EnableBlobRetentionPolicy = "EnableBlobRetentionPolicy";
|
||||||
public const string EnableDryRunBlobRetention = "EnableDryRunBlobRetention";
|
public const string EnableDryRunBlobRetention = "EnableDryRunBlobRetention";
|
||||||
public const string EnableWorkItemCreation = "EnableWorkItemCreation";
|
public const string EnableWorkItemCreation = "EnableWorkItemCreation";
|
||||||
|
public const string EnableContainerRetentionPolicies = "EnableContainerRetentionPolicies";
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Azure.Core;
|
using Azure.Core;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -54,6 +55,8 @@ public class QueueFileChanges {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var storageAccount = new ResourceIdentifier(topicElement.GetString()!);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Setting isLastRetryAttempt to false will rethrow any exceptions
|
// Setting isLastRetryAttempt to false will rethrow any exceptions
|
||||||
// With the intention that the azure functions runtime will handle requeing
|
// With the intention that the azure functions runtime will handle requeing
|
||||||
@ -61,7 +64,7 @@ public class QueueFileChanges {
|
|||||||
// requeuing ourselves because azure functions doesn't support retry policies
|
// requeuing ourselves because azure functions doesn't support retry policies
|
||||||
// for queue based functions.
|
// for queue based functions.
|
||||||
|
|
||||||
var result = await FileAdded(fileChangeEvent, isLastRetryAttempt: false);
|
var result = await FileAdded(storageAccount, fileChangeEvent, isLastRetryAttempt: false);
|
||||||
if (!result.IsOk && result.ErrorV.Code == ErrorCode.ADO_WORKITEM_PROCESSING_DISABLED) {
|
if (!result.IsOk && result.ErrorV.Code == ErrorCode.ADO_WORKITEM_PROCESSING_DISABLED) {
|
||||||
await RequeueMessage(msg, TimeSpan.FromDays(1));
|
await RequeueMessage(msg, TimeSpan.FromDays(1));
|
||||||
}
|
}
|
||||||
@ -71,16 +74,47 @@ public class QueueFileChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Async.Task<OneFuzzResultVoid> FileAdded(JsonDocument fileChangeEvent, bool isLastRetryAttempt) {
|
private async Async.Task<OneFuzzResultVoid> FileAdded(ResourceIdentifier storageAccount, JsonDocument fileChangeEvent, bool isLastRetryAttempt) {
|
||||||
var data = fileChangeEvent.RootElement.GetProperty("data");
|
var data = fileChangeEvent.RootElement.GetProperty("data");
|
||||||
var url = data.GetProperty("url").GetString()!;
|
var url = data.GetProperty("url").GetString()!;
|
||||||
var parts = url.Split("/").Skip(3).ToList();
|
var parts = url.Split("/").Skip(3).ToList();
|
||||||
|
|
||||||
var container = parts[0];
|
var container = Container.Parse(parts[0]);
|
||||||
var path = string.Join('/', parts.Skip(1));
|
var path = string.Join('/', parts.Skip(1));
|
||||||
|
|
||||||
_log.LogInformation("file added : {Container} - {Path}", container, path);
|
_log.LogInformation("file added : {Container} - {Path}", container.String, path);
|
||||||
return await _notificationOperations.NewFiles(Container.Parse(container), path, isLastRetryAttempt);
|
|
||||||
|
var (_, result) = await (
|
||||||
|
ApplyRetentionPolicy(storageAccount, container, path),
|
||||||
|
_notificationOperations.NewFiles(container, path, isLastRetryAttempt));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Async.Task<bool> ApplyRetentionPolicy(ResourceIdentifier storageAccount, Container container, string path) {
|
||||||
|
if (await _context.FeatureManagerSnapshot.IsEnabledAsync(FeatureFlagConstants.EnableContainerRetentionPolicies)) {
|
||||||
|
// default retention period can be applied to the container
|
||||||
|
// if one exists, we will set the expiry date on the newly-created blob, if it doesn't already have one
|
||||||
|
var account = await _storage.GetBlobServiceClientForAccount(storageAccount);
|
||||||
|
var containerClient = account.GetBlobContainerClient(container.String);
|
||||||
|
var containerProps = await containerClient.GetPropertiesAsync();
|
||||||
|
var retentionPeriod = RetentionPolicyUtils.GetContainerRetentionPeriodFromMetadata(containerProps.Value.Metadata);
|
||||||
|
if (!retentionPeriod.IsOk) {
|
||||||
|
_log.LogError("invalid retention period: {Error}", retentionPeriod.ErrorV);
|
||||||
|
} else if (retentionPeriod.OkV is TimeSpan period) {
|
||||||
|
var blobClient = containerClient.GetBlobClient(path);
|
||||||
|
var tags = (await blobClient.GetTagsAsync()).Value.Tags;
|
||||||
|
var expiryDate = DateTime.UtcNow + period;
|
||||||
|
var tag = RetentionPolicyUtils.CreateExpiryDateTag(DateOnly.FromDateTime(expiryDate));
|
||||||
|
if (tags.TryAdd(tag.Key, tag.Value)) {
|
||||||
|
_ = await blobClient.SetTagsAsync(tags);
|
||||||
|
_log.LogInformation("applied container retention policy ({Policy}) to {Path}", period, path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Async.Task RequeueMessage(string msg, TimeSpan? visibilityTimeout = null) {
|
private async Async.Task RequeueMessage(string msg, TimeSpan? visibilityTimeout = null) {
|
||||||
|
@ -50,6 +50,7 @@ public enum ErrorCode {
|
|||||||
ADO_WORKITEM_PROCESSING_DISABLED = 494,
|
ADO_WORKITEM_PROCESSING_DISABLED = 494,
|
||||||
ADO_VALIDATION_INVALID_PATH = 495,
|
ADO_VALIDATION_INVALID_PATH = 495,
|
||||||
ADO_VALIDATION_INVALID_PROJECT = 496,
|
ADO_VALIDATION_INVALID_PROJECT = 496,
|
||||||
|
INVALID_RETENTION_PERIOD = 497,
|
||||||
// NB: if you update this enum, also update enums.py
|
// NB: if you update this enum, also update enums.py
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,13 +22,12 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
|
|||||||
|
|
||||||
}
|
}
|
||||||
public async Async.Task<OneFuzzResultVoid> NewFiles(Container container, string filename, bool isLastRetryAttempt) {
|
public async Async.Task<OneFuzzResultVoid> NewFiles(Container container, string filename, bool isLastRetryAttempt) {
|
||||||
var result = OneFuzzResultVoid.Ok;
|
|
||||||
|
|
||||||
// We don't want to store file added events for the events container because that causes an infinite loop
|
// We don't want to store file added events for the events container because that causes an infinite loop
|
||||||
if (container == WellKnownContainers.Events) {
|
if (container == WellKnownContainers.Events) {
|
||||||
return result;
|
return Result.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var result = OneFuzzResultVoid.Ok;
|
||||||
var notifications = GetNotifications(container);
|
var notifications = GetNotifications(container);
|
||||||
var hasNotifications = await notifications.AnyAsync();
|
var hasNotifications = await notifications.AnyAsync();
|
||||||
var reportOrRegression = await _context.Reports.GetReportOrRegression(container, filename, expectReports: hasNotifications);
|
var reportOrRegression = await _context.Reports.GetReportOrRegression(container, filename, expectReports: hasNotifications);
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
namespace Microsoft.OneFuzz.Service;
|
|
||||||
|
|
||||||
|
|
||||||
public interface IRetentionPolicy {
|
|
||||||
DateOnly GetExpiryDate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RetentionPolicyUtils {
|
|
||||||
public const string EXPIRY_TAG = "Expiry";
|
|
||||||
public static KeyValuePair<string, string> CreateExpiryDateTag(DateOnly expiryDate) =>
|
|
||||||
new(EXPIRY_TAG, expiryDate.ToString());
|
|
||||||
|
|
||||||
public static DateOnly? GetExpiryDateTagFromTags(IDictionary<string, string>? blobTags) {
|
|
||||||
if (blobTags != null &&
|
|
||||||
blobTags.TryGetValue(EXPIRY_TAG, out var expiryTag) &&
|
|
||||||
!string.IsNullOrWhiteSpace(expiryTag) &&
|
|
||||||
DateOnly.TryParse(expiryTag, out var expiryDate)) {
|
|
||||||
return expiryDate;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string CreateExpiredBlobTagFilter() => $@"""{EXPIRY_TAG}"" <= '{DateOnly.FromDateTime(DateTime.UtcNow)}'";
|
|
||||||
}
|
|
43
src/ApiService/ApiService/onefuzzlib/RetentionPolicy.cs
Normal file
43
src/ApiService/ApiService/onefuzzlib/RetentionPolicy.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace Microsoft.OneFuzz.Service;
|
||||||
|
|
||||||
|
|
||||||
|
public interface IRetentionPolicy {
|
||||||
|
DateOnly GetExpiryDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RetentionPolicyUtils {
|
||||||
|
public const string EXPIRY_TAG = "Expiry";
|
||||||
|
public static KeyValuePair<string, string> CreateExpiryDateTag(DateOnly expiryDate) =>
|
||||||
|
new(EXPIRY_TAG, expiryDate.ToString());
|
||||||
|
|
||||||
|
public static DateOnly? GetExpiryDateTagFromTags(IDictionary<string, string>? blobTags) {
|
||||||
|
if (blobTags != null &&
|
||||||
|
blobTags.TryGetValue(EXPIRY_TAG, out var expiryTag) &&
|
||||||
|
!string.IsNullOrWhiteSpace(expiryTag) &&
|
||||||
|
DateOnly.TryParse(expiryTag, out var expiryDate)) {
|
||||||
|
return expiryDate;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CreateExpiredBlobTagFilter() => $@"""{EXPIRY_TAG}"" <= '{DateOnly.FromDateTime(DateTime.UtcNow)}'";
|
||||||
|
|
||||||
|
// NB: this must match the value used on the CLI side
|
||||||
|
public const string CONTAINER_RETENTION_KEY = "onefuzz_retentionperiod";
|
||||||
|
|
||||||
|
public static OneFuzzResult<TimeSpan?> GetContainerRetentionPeriodFromMetadata(IDictionary<string, string>? containerMetadata) {
|
||||||
|
if (containerMetadata is not null &&
|
||||||
|
containerMetadata.TryGetValue(CONTAINER_RETENTION_KEY, out var retentionString) &&
|
||||||
|
!string.IsNullOrWhiteSpace(retentionString)) {
|
||||||
|
try {
|
||||||
|
return Result.Ok<TimeSpan?>(XmlConvert.ToTimeSpan(retentionString));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return Error.Create(ErrorCode.INVALID_RETENTION_PERIOD, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok<TimeSpan?>(null);
|
||||||
|
}
|
||||||
|
}
|
@ -67,7 +67,7 @@ def upload_to_fuzzer_container(of: Onefuzz, fuzzer_name: str, fuzzer_url: str) -
|
|||||||
|
|
||||||
|
|
||||||
def upload_to_setup_container(of: Onefuzz, helper: JobHelper, setup_dir: str) -> None:
|
def upload_to_setup_container(of: Onefuzz, helper: JobHelper, setup_dir: str) -> None:
|
||||||
setup_sas = of.containers.get(helper.containers[ContainerType.setup]).sas_url
|
setup_sas = of.containers.get(helper.container_name(ContainerType.setup)).sas_url
|
||||||
if AZCOPY_PATH is None:
|
if AZCOPY_PATH is None:
|
||||||
raise Exception("missing azcopy")
|
raise Exception("missing azcopy")
|
||||||
command = [AZCOPY_PATH, "sync", setup_dir, setup_sas]
|
command = [AZCOPY_PATH, "sync", setup_dir, setup_sas]
|
||||||
@ -143,13 +143,16 @@ def main() -> None:
|
|||||||
helper.create_containers()
|
helper.create_containers()
|
||||||
helper.setup_notifications(notification_config)
|
helper.setup_notifications(notification_config)
|
||||||
upload_to_setup_container(of, helper, args.setup_dir)
|
upload_to_setup_container(of, helper, args.setup_dir)
|
||||||
add_setup_script(of, helper.containers[ContainerType.setup])
|
add_setup_script(of, helper.container_name(ContainerType.setup))
|
||||||
|
|
||||||
containers = [
|
containers = [
|
||||||
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.crashes, helper.containers[ContainerType.crashes]),
|
(ContainerType.crashes, helper.container_name(ContainerType.crashes)),
|
||||||
(ContainerType.reports, helper.containers[ContainerType.reports]),
|
(ContainerType.reports, helper.container_name(ContainerType.reports)),
|
||||||
(ContainerType.unique_reports, helper.containers[ContainerType.unique_reports]),
|
(
|
||||||
|
ContainerType.unique_reports,
|
||||||
|
helper.container_name(ContainerType.unique_reports),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
of.logger.info("Creating generic_crash_report task")
|
of.logger.info("Creating generic_crash_report task")
|
||||||
@ -164,11 +167,11 @@ def main() -> None:
|
|||||||
|
|
||||||
containers = [
|
containers = [
|
||||||
(ContainerType.tools, Container(FUZZER_NAME)),
|
(ContainerType.tools, Container(FUZZER_NAME)),
|
||||||
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.crashes, helper.containers[ContainerType.crashes]),
|
(ContainerType.crashes, helper.container_name(ContainerType.crashes)),
|
||||||
(
|
(
|
||||||
ContainerType.readonly_inputs,
|
ContainerType.readonly_inputs,
|
||||||
helper.containers[ContainerType.readonly_inputs],
|
helper.container_name(ContainerType.readonly_inputs),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -88,13 +88,16 @@ def main() -> None:
|
|||||||
if args.inputs:
|
if args.inputs:
|
||||||
helper.upload_inputs(args.inputs)
|
helper.upload_inputs(args.inputs)
|
||||||
|
|
||||||
add_setup_script(of, helper.containers[ContainerType.setup])
|
add_setup_script(of, helper.container_name(ContainerType.setup))
|
||||||
|
|
||||||
containers = [
|
containers = [
|
||||||
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.crashes, helper.containers[ContainerType.crashes]),
|
(ContainerType.crashes, helper.container_name(ContainerType.crashes)),
|
||||||
(ContainerType.reports, helper.containers[ContainerType.reports]),
|
(ContainerType.reports, helper.container_name(ContainerType.reports)),
|
||||||
(ContainerType.unique_reports, helper.containers[ContainerType.unique_reports]),
|
(
|
||||||
|
ContainerType.unique_reports,
|
||||||
|
helper.container_name(ContainerType.unique_reports),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
of.logger.info("Creating generic_crash_report task")
|
of.logger.info("Creating generic_crash_report task")
|
||||||
@ -109,11 +112,11 @@ def main() -> None:
|
|||||||
|
|
||||||
containers = [
|
containers = [
|
||||||
(ContainerType.tools, Container("honggfuzz")),
|
(ContainerType.tools, Container("honggfuzz")),
|
||||||
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.crashes, helper.containers[ContainerType.crashes]),
|
(ContainerType.crashes, helper.container_name(ContainerType.crashes)),
|
||||||
(
|
(
|
||||||
ContainerType.inputs,
|
ContainerType.inputs,
|
||||||
helper.containers[ContainerType.inputs],
|
helper.container_name(ContainerType.inputs),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -74,15 +74,15 @@ def main() -> None:
|
|||||||
helper.create_containers()
|
helper.create_containers()
|
||||||
|
|
||||||
of.containers.files.upload_file(
|
of.containers.files.upload_file(
|
||||||
helper.containers[ContainerType.tools], f"{args.tools}/source-coverage.sh"
|
helper.container_name(ContainerType.tools), f"{args.tools}/source-coverage.sh"
|
||||||
)
|
)
|
||||||
|
|
||||||
containers = [
|
containers = [
|
||||||
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.analysis, helper.containers[ContainerType.analysis]),
|
(ContainerType.analysis, helper.container_name(ContainerType.analysis)),
|
||||||
(ContainerType.tools, helper.containers[ContainerType.tools]),
|
(ContainerType.tools, helper.container_name(ContainerType.tools)),
|
||||||
# note, analysis is typically for crashes, but this is analyzing inputs
|
# note, analysis is typically for crashes, but this is analyzing inputs
|
||||||
(ContainerType.crashes, helper.containers[ContainerType.inputs]),
|
(ContainerType.crashes, helper.container_name(ContainerType.inputs)),
|
||||||
]
|
]
|
||||||
|
|
||||||
of.logger.info("Creating generic_analysis task")
|
of.logger.info("Creating generic_analysis task")
|
||||||
|
@ -61,15 +61,15 @@ def main() -> None:
|
|||||||
helper.upload_inputs(args.inputs)
|
helper.upload_inputs(args.inputs)
|
||||||
|
|
||||||
of.containers.files.upload_file(
|
of.containers.files.upload_file(
|
||||||
helper.containers[ContainerType.tools], f"{args.tools}/source-coverage.sh"
|
helper.container_name(ContainerType.tools), f"{args.tools}/source-coverage.sh"
|
||||||
)
|
)
|
||||||
|
|
||||||
containers = [
|
containers = [
|
||||||
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.analysis, helper.containers[ContainerType.analysis]),
|
(ContainerType.analysis, helper.container_name(ContainerType.analysis)),
|
||||||
(ContainerType.tools, helper.containers[ContainerType.tools]),
|
(ContainerType.tools, helper.container_name(ContainerType.tools)),
|
||||||
# note, analysis is typically for crashes, but this is analyzing inputs
|
# note, analysis is typically for crashes, but this is analyzing inputs
|
||||||
(ContainerType.crashes, helper.containers[ContainerType.inputs]),
|
(ContainerType.crashes, helper.container_name(ContainerType.inputs)),
|
||||||
]
|
]
|
||||||
|
|
||||||
of.logger.info("Creating generic_analysis task")
|
of.logger.info("Creating generic_analysis task")
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import zipfile
|
import zipfile
|
||||||
|
from datetime import timedelta
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
@ -22,6 +23,30 @@ class StoppedEarly(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerTemplate:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: Container,
|
||||||
|
exists: bool,
|
||||||
|
*,
|
||||||
|
retention_period: Optional[timedelta] = None,
|
||||||
|
):
|
||||||
|
self.name = name
|
||||||
|
self.retention_period = retention_period
|
||||||
|
# TODO: exists is not yet used/checked
|
||||||
|
self.exists = exists
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def existing(name: Container) -> "ContainerTemplate":
|
||||||
|
return ContainerTemplate(name, True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fresh(
|
||||||
|
name: Container, *, retention_period: Optional[timedelta] = None
|
||||||
|
) -> "ContainerTemplate":
|
||||||
|
return ContainerTemplate(name, False, retention_period=retention_period)
|
||||||
|
|
||||||
|
|
||||||
class JobHelper:
|
class JobHelper:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -59,7 +84,7 @@ class JobHelper:
|
|||||||
|
|
||||||
self.wait_for_running: bool = False
|
self.wait_for_running: bool = False
|
||||||
self.wait_for_stopped: bool = False
|
self.wait_for_stopped: bool = False
|
||||||
self.containers: Dict[ContainerType, Container] = {}
|
self.containers: Dict[ContainerType, ContainerTemplate] = {}
|
||||||
self.tags: Dict[str, str] = {"project": project, "name": name, "build": build}
|
self.tags: Dict[str, str] = {"project": project, "name": name, "build": build}
|
||||||
if job is None:
|
if job is None:
|
||||||
self.onefuzz.versions.check()
|
self.onefuzz.versions.check()
|
||||||
@ -71,6 +96,20 @@ class JobHelper:
|
|||||||
else:
|
else:
|
||||||
self.job = job
|
self.job = job
|
||||||
|
|
||||||
|
def add_existing_container(
|
||||||
|
self, container_type: ContainerType, container: Container
|
||||||
|
) -> None:
|
||||||
|
self.containers[container_type] = ContainerTemplate.existing(container)
|
||||||
|
|
||||||
|
def container_name(self, container_type: ContainerType) -> Container:
|
||||||
|
return self.containers[container_type].name
|
||||||
|
|
||||||
|
def container_names(self) -> Dict[ContainerType, Container]:
|
||||||
|
return {
|
||||||
|
container_type: container.name
|
||||||
|
for (container_type, container) in self.containers.items()
|
||||||
|
}
|
||||||
|
|
||||||
def define_containers(self, *types: ContainerType) -> None:
|
def define_containers(self, *types: ContainerType) -> None:
|
||||||
"""
|
"""
|
||||||
Define default container set based on provided types
|
Define default container set based on provided types
|
||||||
@ -79,13 +118,23 @@ class JobHelper:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
for container_type in types:
|
for container_type in types:
|
||||||
self.containers[container_type] = self.onefuzz.utils.build_container_name(
|
container_name = self.onefuzz.utils.build_container_name(
|
||||||
container_type=container_type,
|
container_type=container_type,
|
||||||
project=self.project,
|
project=self.project,
|
||||||
name=self.name,
|
name=self.name,
|
||||||
build=self.build,
|
build=self.build,
|
||||||
platform=self.platform,
|
platform=self.platform,
|
||||||
)
|
)
|
||||||
|
self.containers[container_type] = ContainerTemplate.fresh(
|
||||||
|
container_name,
|
||||||
|
retention_period=JobHelper._default_retention_period(container_type),
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _default_retention_period(container_type: ContainerType) -> Optional[timedelta]:
|
||||||
|
if container_type == ContainerType.crashdumps:
|
||||||
|
return timedelta(days=90)
|
||||||
|
return None
|
||||||
|
|
||||||
def get_unique_container_name(self, container_type: ContainerType) -> Container:
|
def get_unique_container_name(self, container_type: ContainerType) -> Container:
|
||||||
return Container(
|
return Container(
|
||||||
@ -97,11 +146,17 @@ class JobHelper:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def create_containers(self) -> None:
|
def create_containers(self) -> None:
|
||||||
for container_type, container_name in self.containers.items():
|
for container_type, container in self.containers.items():
|
||||||
self.logger.info("using container: %s", container_name)
|
self.logger.info("using container: %s", container.name)
|
||||||
self.onefuzz.containers.create(
|
metadata = {"container_type": container_type.name}
|
||||||
container_name, metadata={"container_type": container_type.name}
|
if container.retention_period is not None:
|
||||||
)
|
# format as ISO8601 period
|
||||||
|
# NB: this must match the value used on the server side
|
||||||
|
metadata[
|
||||||
|
"onefuzz_retentionperiod"
|
||||||
|
] = f"P{container.retention_period.days}D"
|
||||||
|
|
||||||
|
self.onefuzz.containers.create(container.name, metadata=metadata)
|
||||||
|
|
||||||
def delete_container(self, container_name: Container) -> None:
|
def delete_container(self, container_name: Container) -> None:
|
||||||
self.onefuzz.containers.delete(container_name)
|
self.onefuzz.containers.delete(container_name)
|
||||||
@ -112,12 +167,12 @@ class JobHelper:
|
|||||||
|
|
||||||
containers: List[Container] = []
|
containers: List[Container] = []
|
||||||
if ContainerType.unique_reports in self.containers:
|
if ContainerType.unique_reports in self.containers:
|
||||||
containers.append(self.containers[ContainerType.unique_reports])
|
containers.append(self.container_name(ContainerType.unique_reports))
|
||||||
else:
|
else:
|
||||||
containers.append(self.containers[ContainerType.reports])
|
containers.append(self.container_name(ContainerType.reports))
|
||||||
|
|
||||||
if ContainerType.regression_reports in self.containers:
|
if ContainerType.regression_reports in self.containers:
|
||||||
containers.append(self.containers[ContainerType.regression_reports])
|
containers.append(self.container_name(ContainerType.regression_reports))
|
||||||
|
|
||||||
for container in containers:
|
for container in containers:
|
||||||
self.logger.info("creating notification config for %s", container)
|
self.logger.info("creating notification config for %s", container)
|
||||||
@ -141,25 +196,25 @@ class JobHelper:
|
|||||||
|
|
||||||
self.logger.info("uploading setup dir `%s`" % setup_dir)
|
self.logger.info("uploading setup dir `%s`" % setup_dir)
|
||||||
self.onefuzz.containers.files.upload_dir(
|
self.onefuzz.containers.files.upload_dir(
|
||||||
self.containers[ContainerType.setup], setup_dir
|
self.container_name(ContainerType.setup), setup_dir
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.logger.info("uploading target exe `%s`" % target_exe)
|
self.logger.info("uploading target exe `%s`" % target_exe)
|
||||||
self.onefuzz.containers.files.upload_file(
|
self.onefuzz.containers.files.upload_file(
|
||||||
self.containers[ContainerType.setup], target_exe
|
self.container_name(ContainerType.setup), target_exe
|
||||||
)
|
)
|
||||||
|
|
||||||
pdb_path = os.path.splitext(target_exe)[0] + ".pdb"
|
pdb_path = os.path.splitext(target_exe)[0] + ".pdb"
|
||||||
if os.path.exists(pdb_path):
|
if os.path.exists(pdb_path):
|
||||||
pdb_name = os.path.basename(pdb_path)
|
pdb_name = os.path.basename(pdb_path)
|
||||||
self.onefuzz.containers.files.upload_file(
|
self.onefuzz.containers.files.upload_file(
|
||||||
self.containers[ContainerType.setup], pdb_path, pdb_name
|
self.container_name(ContainerType.setup), pdb_path, pdb_name
|
||||||
)
|
)
|
||||||
if setup_files:
|
if setup_files:
|
||||||
for filename in setup_files:
|
for filename in setup_files:
|
||||||
self.logger.info("uploading %s", filename)
|
self.logger.info("uploading %s", filename)
|
||||||
self.onefuzz.containers.files.upload_file(
|
self.onefuzz.containers.files.upload_file(
|
||||||
self.containers[ContainerType.setup], filename
|
self.container_name(ContainerType.setup), filename
|
||||||
)
|
)
|
||||||
|
|
||||||
def upload_inputs(self, path: Directory, read_only: bool = False) -> None:
|
def upload_inputs(self, path: Directory, read_only: bool = False) -> None:
|
||||||
@ -167,7 +222,9 @@ class JobHelper:
|
|||||||
container_type = ContainerType.inputs
|
container_type = ContainerType.inputs
|
||||||
if read_only:
|
if read_only:
|
||||||
container_type = ContainerType.readonly_inputs
|
container_type = ContainerType.readonly_inputs
|
||||||
self.onefuzz.containers.files.upload_dir(self.containers[container_type], path)
|
self.onefuzz.containers.files.upload_dir(
|
||||||
|
self.container_name(container_type), path
|
||||||
|
)
|
||||||
|
|
||||||
def upload_inputs_zip(self, path: File) -> None:
|
def upload_inputs_zip(self, path: File) -> None:
|
||||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
@ -176,7 +233,7 @@ class JobHelper:
|
|||||||
|
|
||||||
self.logger.info("uploading inputs from zip: `%s`" % path)
|
self.logger.info("uploading inputs from zip: `%s`" % path)
|
||||||
self.onefuzz.containers.files.upload_dir(
|
self.onefuzz.containers.files.upload_dir(
|
||||||
self.containers[ContainerType.inputs], Directory(tmp_dir)
|
self.container_name(ContainerType.inputs), Directory(tmp_dir)
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -195,8 +252,8 @@ class JobHelper:
|
|||||||
wait_for_files = []
|
wait_for_files = []
|
||||||
|
|
||||||
self.to_monitor = {
|
self.to_monitor = {
|
||||||
self.containers[x]: len(
|
self.container_name(x): len(
|
||||||
self.onefuzz.containers.files.list(self.containers[x]).files
|
self.onefuzz.containers.files.list(self.container_name(x)).files
|
||||||
)
|
)
|
||||||
for x in wait_for_files
|
for x in wait_for_files
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ class AFL(Command):
|
|||||||
|
|
||||||
if existing_inputs:
|
if existing_inputs:
|
||||||
self.onefuzz.containers.get(existing_inputs)
|
self.onefuzz.containers.get(existing_inputs)
|
||||||
helper.containers[ContainerType.inputs] = existing_inputs
|
helper.add_existing_container(ContainerType.inputs, existing_inputs)
|
||||||
else:
|
else:
|
||||||
helper.define_containers(ContainerType.inputs)
|
helper.define_containers(ContainerType.inputs)
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ class AFL(Command):
|
|||||||
if (
|
if (
|
||||||
len(
|
len(
|
||||||
self.onefuzz.containers.files.list(
|
self.onefuzz.containers.files.list(
|
||||||
helper.containers[ContainerType.inputs]
|
helper.containers[ContainerType.inputs].name
|
||||||
).files
|
).files
|
||||||
)
|
)
|
||||||
== 0
|
== 0
|
||||||
@ -131,16 +131,16 @@ class AFL(Command):
|
|||||||
|
|
||||||
containers = [
|
containers = [
|
||||||
(ContainerType.tools, afl_container),
|
(ContainerType.tools, afl_container),
|
||||||
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.crashes, helper.containers[ContainerType.crashes]),
|
(ContainerType.crashes, helper.container_name(ContainerType.crashes)),
|
||||||
(ContainerType.inputs, helper.containers[ContainerType.inputs]),
|
(ContainerType.inputs, helper.container_name(ContainerType.inputs)),
|
||||||
]
|
]
|
||||||
|
|
||||||
if extra_setup_container is not None:
|
if extra_setup_container is not None:
|
||||||
containers.append(
|
containers.append(
|
||||||
(
|
(
|
||||||
ContainerType.extra_setup,
|
ContainerType.extra_setup,
|
||||||
helper.containers[ContainerType.extra_setup],
|
extra_setup_container,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -169,12 +169,12 @@ class AFL(Command):
|
|||||||
)
|
)
|
||||||
|
|
||||||
report_containers = [
|
report_containers = [
|
||||||
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.crashes, helper.containers[ContainerType.crashes]),
|
(ContainerType.crashes, helper.container_name(ContainerType.crashes)),
|
||||||
(ContainerType.reports, helper.containers[ContainerType.reports]),
|
(ContainerType.reports, helper.container_name(ContainerType.reports)),
|
||||||
(
|
(
|
||||||
ContainerType.unique_reports,
|
ContainerType.unique_reports,
|
||||||
helper.containers[ContainerType.unique_reports],
|
helper.container_name(ContainerType.unique_reports),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ class AFL(Command):
|
|||||||
report_containers.append(
|
report_containers.append(
|
||||||
(
|
(
|
||||||
ContainerType.extra_setup,
|
ContainerType.extra_setup,
|
||||||
helper.containers[ContainerType.extra_setup],
|
helper.container_name(ContainerType.extra_setup),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ class Libfuzzer(Command):
|
|||||||
task_env: Optional[Dict[str, str]] = None,
|
task_env: Optional[Dict[str, str]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
target_options = target_options or []
|
target_options = target_options or []
|
||||||
regression_containers = [
|
regression_containers: List[Tuple[ContainerType, Container]] = [
|
||||||
(ContainerType.setup, containers[ContainerType.setup]),
|
(ContainerType.setup, containers[ContainerType.setup]),
|
||||||
(ContainerType.crashes, containers[ContainerType.crashes]),
|
(ContainerType.crashes, containers[ContainerType.crashes]),
|
||||||
(ContainerType.unique_reports, containers[ContainerType.unique_reports]),
|
(ContainerType.unique_reports, containers[ContainerType.unique_reports]),
|
||||||
@ -129,7 +129,7 @@ class Libfuzzer(Command):
|
|||||||
task_env=task_env,
|
task_env=task_env,
|
||||||
)
|
)
|
||||||
|
|
||||||
fuzzer_containers = [
|
fuzzer_containers: List[Tuple[ContainerType, Container]] = [
|
||||||
(ContainerType.setup, containers[ContainerType.setup]),
|
(ContainerType.setup, containers[ContainerType.setup]),
|
||||||
(ContainerType.crashes, containers[ContainerType.crashes]),
|
(ContainerType.crashes, containers[ContainerType.crashes]),
|
||||||
(ContainerType.crashdumps, containers[ContainerType.crashdumps]),
|
(ContainerType.crashdumps, containers[ContainerType.crashdumps]),
|
||||||
@ -184,7 +184,7 @@ class Libfuzzer(Command):
|
|||||||
|
|
||||||
prereq_tasks = [fuzzer_task.task_id, regression_task.task_id]
|
prereq_tasks = [fuzzer_task.task_id, regression_task.task_id]
|
||||||
|
|
||||||
coverage_containers = [
|
coverage_containers: List[Tuple[ContainerType, Container]] = [
|
||||||
(ContainerType.setup, containers[ContainerType.setup]),
|
(ContainerType.setup, containers[ContainerType.setup]),
|
||||||
(ContainerType.coverage, containers[ContainerType.coverage]),
|
(ContainerType.coverage, containers[ContainerType.coverage]),
|
||||||
(ContainerType.readonly_inputs, containers[ContainerType.inputs]),
|
(ContainerType.readonly_inputs, containers[ContainerType.inputs]),
|
||||||
@ -245,7 +245,7 @@ class Libfuzzer(Command):
|
|||||||
task_env=task_env,
|
task_env=task_env,
|
||||||
)
|
)
|
||||||
|
|
||||||
report_containers = [
|
report_containers: List[Tuple[ContainerType, Container]] = [
|
||||||
(ContainerType.setup, containers[ContainerType.setup]),
|
(ContainerType.setup, containers[ContainerType.setup]),
|
||||||
(ContainerType.crashes, containers[ContainerType.crashes]),
|
(ContainerType.crashes, containers[ContainerType.crashes]),
|
||||||
(ContainerType.reports, containers[ContainerType.reports]),
|
(ContainerType.reports, containers[ContainerType.reports]),
|
||||||
@ -285,7 +285,7 @@ class Libfuzzer(Command):
|
|||||||
if analyzer_exe is not None:
|
if analyzer_exe is not None:
|
||||||
self.logger.info("creating custom analysis")
|
self.logger.info("creating custom analysis")
|
||||||
|
|
||||||
analysis_containers = [
|
analysis_containers: List[Tuple[ContainerType, Container]] = [
|
||||||
(ContainerType.setup, containers[ContainerType.setup]),
|
(ContainerType.setup, containers[ContainerType.setup]),
|
||||||
(ContainerType.analysis, containers[ContainerType.analysis]),
|
(ContainerType.analysis, containers[ContainerType.analysis]),
|
||||||
(ContainerType.crashes, containers[ContainerType.crashes]),
|
(ContainerType.crashes, containers[ContainerType.crashes]),
|
||||||
@ -428,15 +428,17 @@ class Libfuzzer(Command):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if existing_inputs:
|
if existing_inputs:
|
||||||
helper.containers[ContainerType.inputs] = existing_inputs
|
helper.add_existing_container(ContainerType.inputs, existing_inputs)
|
||||||
else:
|
else:
|
||||||
helper.define_containers(ContainerType.inputs)
|
helper.define_containers(ContainerType.inputs)
|
||||||
|
|
||||||
if readonly_inputs:
|
if readonly_inputs:
|
||||||
helper.containers[ContainerType.readonly_inputs] = readonly_inputs
|
helper.add_existing_container(
|
||||||
|
ContainerType.readonly_inputs, readonly_inputs
|
||||||
|
)
|
||||||
|
|
||||||
if crashes:
|
if crashes:
|
||||||
helper.containers[ContainerType.crashes] = crashes
|
helper.add_existing_container(ContainerType.crashes, crashes)
|
||||||
|
|
||||||
if analyzer_exe is not None:
|
if analyzer_exe is not None:
|
||||||
helper.define_containers(ContainerType.analysis)
|
helper.define_containers(ContainerType.analysis)
|
||||||
@ -465,17 +467,19 @@ class Libfuzzer(Command):
|
|||||||
else:
|
else:
|
||||||
source_allowlist_blob_name = None
|
source_allowlist_blob_name = None
|
||||||
|
|
||||||
containers = helper.containers
|
|
||||||
|
|
||||||
if extra_setup_container is not None:
|
if extra_setup_container is not None:
|
||||||
containers[ContainerType.extra_setup] = extra_setup_container
|
helper.add_existing_container(
|
||||||
|
ContainerType.extra_setup, extra_setup_container
|
||||||
|
)
|
||||||
|
|
||||||
if extra_output_container is not None:
|
if extra_output_container is not None:
|
||||||
containers[ContainerType.extra_output] = extra_output_container
|
helper.add_existing_container(
|
||||||
|
ContainerType.extra_output, extra_output_container
|
||||||
|
)
|
||||||
|
|
||||||
self._create_tasks(
|
self._create_tasks(
|
||||||
job=helper.job,
|
job=helper.job,
|
||||||
containers=containers,
|
containers=helper.container_names(),
|
||||||
pool_name=pool_name,
|
pool_name=pool_name,
|
||||||
target_exe=target_exe_blob_name,
|
target_exe=target_exe_blob_name,
|
||||||
vm_count=vm_count,
|
vm_count=vm_count,
|
||||||
@ -600,19 +604,35 @@ class Libfuzzer(Command):
|
|||||||
target_exe_blob_name = helper.setup_relative_blob_name(target_exe, setup_dir)
|
target_exe_blob_name = helper.setup_relative_blob_name(target_exe, setup_dir)
|
||||||
|
|
||||||
merge_containers = [
|
merge_containers = [
|
||||||
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(
|
|
||||||
ContainerType.unique_inputs,
|
|
||||||
output_container or helper.containers[ContainerType.unique_inputs],
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if output_container:
|
||||||
|
merge_containers.append(
|
||||||
|
(
|
||||||
|
ContainerType.unique_inputs,
|
||||||
|
output_container,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
merge_containers.append(
|
||||||
|
(
|
||||||
|
ContainerType.unique_inputs,
|
||||||
|
helper.container_name(ContainerType.unique_inputs),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if extra_setup_container is not None:
|
if extra_setup_container is not None:
|
||||||
merge_containers.append((ContainerType.extra_setup, extra_setup_container))
|
merge_containers.append(
|
||||||
|
(
|
||||||
|
ContainerType.extra_setup,
|
||||||
|
extra_setup_container,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if inputs:
|
if inputs:
|
||||||
merge_containers.append(
|
merge_containers.append(
|
||||||
(ContainerType.inputs, helper.containers[ContainerType.inputs])
|
(ContainerType.inputs, helper.container_name(ContainerType.inputs))
|
||||||
)
|
)
|
||||||
if existing_inputs:
|
if existing_inputs:
|
||||||
for existing_container in existing_inputs:
|
for existing_container in existing_inputs:
|
||||||
@ -735,18 +755,18 @@ class Libfuzzer(Command):
|
|||||||
ContainerType.no_repro,
|
ContainerType.no_repro,
|
||||||
)
|
)
|
||||||
|
|
||||||
containers = helper.containers
|
|
||||||
|
|
||||||
if existing_inputs:
|
if existing_inputs:
|
||||||
helper.containers[ContainerType.inputs] = existing_inputs
|
helper.add_existing_container(ContainerType.inputs, existing_inputs)
|
||||||
else:
|
else:
|
||||||
helper.define_containers(ContainerType.inputs)
|
helper.define_containers(ContainerType.inputs)
|
||||||
|
|
||||||
if readonly_inputs:
|
if readonly_inputs:
|
||||||
helper.containers[ContainerType.readonly_inputs] = readonly_inputs
|
helper.add_existing_container(
|
||||||
|
ContainerType.readonly_inputs, readonly_inputs
|
||||||
|
)
|
||||||
|
|
||||||
if crashes:
|
if crashes:
|
||||||
helper.containers[ContainerType.crashes] = crashes
|
helper.add_existing_container(ContainerType.crashes, crashes)
|
||||||
|
|
||||||
# Assumes that `libfuzzer-dotnet` and supporting tools were uploaded upon deployment.
|
# Assumes that `libfuzzer-dotnet` and supporting tools were uploaded upon deployment.
|
||||||
fuzzer_tools_container = Container(
|
fuzzer_tools_container = Container(
|
||||||
@ -754,15 +774,20 @@ class Libfuzzer(Command):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fuzzer_containers = [
|
fuzzer_containers = [
|
||||||
(ContainerType.setup, containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.crashes, containers[ContainerType.crashes]),
|
(ContainerType.crashes, helper.container_name(ContainerType.crashes)),
|
||||||
(ContainerType.crashdumps, containers[ContainerType.crashdumps]),
|
(ContainerType.crashdumps, helper.container_name(ContainerType.crashdumps)),
|
||||||
(ContainerType.inputs, containers[ContainerType.inputs]),
|
(ContainerType.inputs, helper.container_name(ContainerType.inputs)),
|
||||||
(ContainerType.tools, fuzzer_tools_container),
|
(ContainerType.tools, fuzzer_tools_container),
|
||||||
]
|
]
|
||||||
|
|
||||||
if extra_setup_container is not None:
|
if extra_setup_container is not None:
|
||||||
fuzzer_containers.append((ContainerType.extra_setup, extra_setup_container))
|
fuzzer_containers.append(
|
||||||
|
(
|
||||||
|
ContainerType.extra_setup,
|
||||||
|
extra_setup_container,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
helper.create_containers()
|
helper.create_containers()
|
||||||
helper.setup_notifications(notification_config)
|
helper.setup_notifications(notification_config)
|
||||||
@ -814,15 +839,21 @@ class Libfuzzer(Command):
|
|||||||
libfuzzer_dotnet_loader_dll = LIBFUZZER_DOTNET_LOADER_PATH
|
libfuzzer_dotnet_loader_dll = LIBFUZZER_DOTNET_LOADER_PATH
|
||||||
|
|
||||||
coverage_containers = [
|
coverage_containers = [
|
||||||
(ContainerType.setup, containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.coverage, containers[ContainerType.coverage]),
|
(ContainerType.coverage, helper.container_name(ContainerType.coverage)),
|
||||||
(ContainerType.readonly_inputs, containers[ContainerType.inputs]),
|
(
|
||||||
|
ContainerType.readonly_inputs,
|
||||||
|
helper.container_name(ContainerType.inputs),
|
||||||
|
),
|
||||||
(ContainerType.tools, fuzzer_tools_container),
|
(ContainerType.tools, fuzzer_tools_container),
|
||||||
]
|
]
|
||||||
|
|
||||||
if extra_setup_container is not None:
|
if extra_setup_container is not None:
|
||||||
coverage_containers.append(
|
coverage_containers.append(
|
||||||
(ContainerType.extra_setup, extra_setup_container)
|
(
|
||||||
|
ContainerType.extra_setup,
|
||||||
|
extra_setup_container,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.logger.info("creating `dotnet_coverage` task")
|
self.logger.info("creating `dotnet_coverage` task")
|
||||||
@ -846,16 +877,24 @@ class Libfuzzer(Command):
|
|||||||
)
|
)
|
||||||
|
|
||||||
report_containers = [
|
report_containers = [
|
||||||
(ContainerType.setup, containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.crashes, containers[ContainerType.crashes]),
|
(ContainerType.crashes, helper.container_name(ContainerType.crashes)),
|
||||||
(ContainerType.reports, containers[ContainerType.reports]),
|
(ContainerType.reports, helper.container_name(ContainerType.reports)),
|
||||||
(ContainerType.unique_reports, containers[ContainerType.unique_reports]),
|
(
|
||||||
(ContainerType.no_repro, containers[ContainerType.no_repro]),
|
ContainerType.unique_reports,
|
||||||
|
helper.container_name(ContainerType.unique_reports),
|
||||||
|
),
|
||||||
|
(ContainerType.no_repro, helper.container_name(ContainerType.no_repro)),
|
||||||
(ContainerType.tools, fuzzer_tools_container),
|
(ContainerType.tools, fuzzer_tools_container),
|
||||||
]
|
]
|
||||||
|
|
||||||
if extra_setup_container is not None:
|
if extra_setup_container is not None:
|
||||||
report_containers.append((ContainerType.extra_setup, extra_setup_container))
|
report_containers.append(
|
||||||
|
(
|
||||||
|
ContainerType.extra_setup,
|
||||||
|
extra_setup_container,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.logger.info("creating `dotnet_crash_report` task")
|
self.logger.info("creating `dotnet_crash_report` task")
|
||||||
self.onefuzz.tasks.create(
|
self.onefuzz.tasks.create(
|
||||||
@ -972,27 +1011,37 @@ class Libfuzzer(Command):
|
|||||||
|
|
||||||
if existing_inputs:
|
if existing_inputs:
|
||||||
self.onefuzz.containers.get(existing_inputs) # ensure it exists
|
self.onefuzz.containers.get(existing_inputs) # ensure it exists
|
||||||
helper.containers[ContainerType.inputs] = existing_inputs
|
helper.add_existing_container(ContainerType.inputs, existing_inputs)
|
||||||
else:
|
else:
|
||||||
helper.define_containers(ContainerType.inputs)
|
helper.define_containers(ContainerType.inputs)
|
||||||
|
|
||||||
if crashes:
|
if crashes:
|
||||||
self.onefuzz.containers.get(crashes)
|
self.onefuzz.containers.get(crashes)
|
||||||
helper.containers[ContainerType.crashes] = crashes
|
helper.add_existing_container(ContainerType.crashes, crashes)
|
||||||
|
|
||||||
fuzzer_containers = [
|
fuzzer_containers = [
|
||||||
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.crashes, helper.containers[ContainerType.crashes]),
|
(ContainerType.crashes, helper.container_name(ContainerType.crashes)),
|
||||||
(ContainerType.crashdumps, helper.containers[ContainerType.crashdumps]),
|
(ContainerType.crashdumps, helper.container_name(ContainerType.crashdumps)),
|
||||||
(ContainerType.inputs, helper.containers[ContainerType.inputs]),
|
(ContainerType.inputs, helper.container_name(ContainerType.inputs)),
|
||||||
]
|
]
|
||||||
|
|
||||||
if extra_setup_container is not None:
|
if extra_setup_container is not None:
|
||||||
fuzzer_containers.append((ContainerType.extra_setup, extra_setup_container))
|
fuzzer_containers.append(
|
||||||
|
(
|
||||||
|
ContainerType.extra_setup,
|
||||||
|
extra_setup_container,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if readonly_inputs is not None:
|
if readonly_inputs is not None:
|
||||||
self.onefuzz.containers.get(readonly_inputs) # ensure it exists
|
self.onefuzz.containers.get(readonly_inputs) # ensure it exists
|
||||||
fuzzer_containers.append((ContainerType.readonly_inputs, readonly_inputs))
|
fuzzer_containers.append(
|
||||||
|
(
|
||||||
|
ContainerType.readonly_inputs,
|
||||||
|
readonly_inputs,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
helper.create_containers()
|
helper.create_containers()
|
||||||
|
|
||||||
@ -1079,18 +1128,23 @@ class Libfuzzer(Command):
|
|||||||
)
|
)
|
||||||
|
|
||||||
report_containers = [
|
report_containers = [
|
||||||
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.crashes, helper.containers[ContainerType.crashes]),
|
(ContainerType.crashes, helper.container_name(ContainerType.crashes)),
|
||||||
(ContainerType.reports, helper.containers[ContainerType.reports]),
|
(ContainerType.reports, helper.container_name(ContainerType.reports)),
|
||||||
(
|
(
|
||||||
ContainerType.unique_reports,
|
ContainerType.unique_reports,
|
||||||
helper.containers[ContainerType.unique_reports],
|
helper.container_name(ContainerType.unique_reports),
|
||||||
),
|
),
|
||||||
(ContainerType.no_repro, helper.containers[ContainerType.no_repro]),
|
(ContainerType.no_repro, helper.container_name(ContainerType.no_repro)),
|
||||||
]
|
]
|
||||||
|
|
||||||
if extra_setup_container is not None:
|
if extra_setup_container is not None:
|
||||||
report_containers.append((ContainerType.extra_setup, extra_setup_container))
|
report_containers.append(
|
||||||
|
(
|
||||||
|
ContainerType.extra_setup,
|
||||||
|
extra_setup_container,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.logger.info("creating libfuzzer_crash_report task")
|
self.logger.info("creating libfuzzer_crash_report task")
|
||||||
self.onefuzz.tasks.create(
|
self.onefuzz.tasks.create(
|
||||||
|
@ -215,13 +215,15 @@ class OssFuzz(Command):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if extra_setup_container is not None:
|
if extra_setup_container is not None:
|
||||||
helper.containers[ContainerType.extra_setup] = extra_setup_container
|
helper.add_existing_container(
|
||||||
|
ContainerType.extra_setup, extra_setup_container
|
||||||
|
)
|
||||||
|
|
||||||
helper.create_containers()
|
helper.create_containers()
|
||||||
helper.setup_notifications(notification_config)
|
helper.setup_notifications(notification_config)
|
||||||
|
|
||||||
dst_sas = self.onefuzz.containers.get(
|
dst_sas = self.onefuzz.containers.get(
|
||||||
helper.containers[ContainerType.setup]
|
helper.containers[ContainerType.setup].name
|
||||||
).sas_url
|
).sas_url
|
||||||
self._copy_exe(container_sas["build"], dst_sas, File(fuzzer))
|
self._copy_exe(container_sas["build"], dst_sas, File(fuzzer))
|
||||||
self._copy_all(container_sas["base"], dst_sas)
|
self._copy_all(container_sas["base"], dst_sas)
|
||||||
@ -245,7 +247,7 @@ class OssFuzz(Command):
|
|||||||
|
|
||||||
self.onefuzz.template.libfuzzer._create_tasks(
|
self.onefuzz.template.libfuzzer._create_tasks(
|
||||||
job=base_helper.job,
|
job=base_helper.job,
|
||||||
containers=helper.containers,
|
containers=helper.container_names(),
|
||||||
pool_name=pool_name,
|
pool_name=pool_name,
|
||||||
target_exe=fuzzer_blob_name,
|
target_exe=fuzzer_blob_name,
|
||||||
vm_count=VM_COUNT,
|
vm_count=VM_COUNT,
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
# Copyright (c) Microsoft Corporation.
|
# Copyright (c) Microsoft Corporation.
|
||||||
# Licensed under the MIT License.
|
# Licensed under the MIT License.
|
||||||
|
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from onefuzztypes.enums import OS, ContainerType, TaskDebugFlag, TaskType
|
from onefuzztypes.enums import OS, ContainerType, TaskDebugFlag, TaskType
|
||||||
from onefuzztypes.models import Job, NotificationConfig
|
from onefuzztypes.models import Job, NotificationConfig
|
||||||
@ -94,7 +94,9 @@ class Radamsa(Command):
|
|||||||
|
|
||||||
if existing_inputs:
|
if existing_inputs:
|
||||||
self.onefuzz.containers.get(existing_inputs)
|
self.onefuzz.containers.get(existing_inputs)
|
||||||
helper.containers[ContainerType.readonly_inputs] = existing_inputs
|
helper.add_existing_container(
|
||||||
|
ContainerType.readonly_inputs, existing_inputs
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
helper.define_containers(ContainerType.readonly_inputs)
|
helper.define_containers(ContainerType.readonly_inputs)
|
||||||
helper.create_containers()
|
helper.create_containers()
|
||||||
@ -108,7 +110,7 @@ class Radamsa(Command):
|
|||||||
if (
|
if (
|
||||||
len(
|
len(
|
||||||
self.onefuzz.containers.files.list(
|
self.onefuzz.containers.files.list(
|
||||||
helper.containers[ContainerType.readonly_inputs]
|
helper.containers[ContainerType.readonly_inputs].name
|
||||||
).files
|
).files
|
||||||
)
|
)
|
||||||
== 0
|
== 0
|
||||||
@ -147,18 +149,23 @@ class Radamsa(Command):
|
|||||||
|
|
||||||
self.logger.info("creating radamsa task")
|
self.logger.info("creating radamsa task")
|
||||||
|
|
||||||
containers = [
|
containers: List[Tuple[ContainerType, Container]] = [
|
||||||
(ContainerType.tools, tools),
|
(ContainerType.tools, tools),
|
||||||
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.crashes, helper.containers[ContainerType.crashes]),
|
(ContainerType.crashes, helper.container_name(ContainerType.crashes)),
|
||||||
(
|
(
|
||||||
ContainerType.readonly_inputs,
|
ContainerType.readonly_inputs,
|
||||||
helper.containers[ContainerType.readonly_inputs],
|
helper.container_name(ContainerType.readonly_inputs),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
if extra_setup_container is not None:
|
if extra_setup_container is not None:
|
||||||
containers.append((ContainerType.extra_setup, extra_setup_container))
|
containers.append(
|
||||||
|
(
|
||||||
|
ContainerType.extra_setup,
|
||||||
|
extra_setup_container,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
fuzzer_task = self.onefuzz.tasks.create(
|
fuzzer_task = self.onefuzz.tasks.create(
|
||||||
helper.job.job_id,
|
helper.job.job_id,
|
||||||
@ -183,18 +190,23 @@ class Radamsa(Command):
|
|||||||
)
|
)
|
||||||
|
|
||||||
report_containers = [
|
report_containers = [
|
||||||
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.crashes, helper.containers[ContainerType.crashes]),
|
(ContainerType.crashes, helper.container_name(ContainerType.crashes)),
|
||||||
(ContainerType.reports, helper.containers[ContainerType.reports]),
|
(ContainerType.reports, helper.container_name(ContainerType.reports)),
|
||||||
(
|
(
|
||||||
ContainerType.unique_reports,
|
ContainerType.unique_reports,
|
||||||
helper.containers[ContainerType.unique_reports],
|
helper.container_name(ContainerType.unique_reports),
|
||||||
),
|
),
|
||||||
(ContainerType.no_repro, helper.containers[ContainerType.no_repro]),
|
(ContainerType.no_repro, helper.container_name(ContainerType.no_repro)),
|
||||||
]
|
]
|
||||||
|
|
||||||
if extra_setup_container is not None:
|
if extra_setup_container is not None:
|
||||||
report_containers.append((ContainerType.extra_setup, extra_setup_container))
|
report_containers.append(
|
||||||
|
(
|
||||||
|
ContainerType.extra_setup,
|
||||||
|
extra_setup_container,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.logger.info("creating generic_crash_report task")
|
self.logger.info("creating generic_crash_report task")
|
||||||
self.onefuzz.tasks.create(
|
self.onefuzz.tasks.create(
|
||||||
@ -233,15 +245,18 @@ class Radamsa(Command):
|
|||||||
self.logger.info("creating custom analysis")
|
self.logger.info("creating custom analysis")
|
||||||
|
|
||||||
analysis_containers = [
|
analysis_containers = [
|
||||||
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.tools, tools),
|
(ContainerType.tools, tools),
|
||||||
(ContainerType.analysis, helper.containers[ContainerType.analysis]),
|
(ContainerType.analysis, helper.container_name(ContainerType.analysis)),
|
||||||
(ContainerType.crashes, helper.containers[ContainerType.crashes]),
|
(ContainerType.crashes, helper.container_name(ContainerType.crashes)),
|
||||||
]
|
]
|
||||||
|
|
||||||
if extra_setup_container is not None:
|
if extra_setup_container is not None:
|
||||||
analysis_containers.append(
|
analysis_containers.append(
|
||||||
(ContainerType.extra_setup, extra_setup_container)
|
(
|
||||||
|
ContainerType.extra_setup,
|
||||||
|
extra_setup_container,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.onefuzz.tasks.create(
|
self.onefuzz.tasks.create(
|
||||||
|
@ -12,7 +12,7 @@ from onefuzztypes.primitives import Container, Directory, File, PoolName
|
|||||||
|
|
||||||
from onefuzz.api import Command
|
from onefuzz.api import Command
|
||||||
|
|
||||||
from . import JobHelper
|
from . import ContainerTemplate, JobHelper
|
||||||
|
|
||||||
|
|
||||||
class Regression(Command):
|
class Regression(Command):
|
||||||
@ -207,31 +207,36 @@ class Regression(Command):
|
|||||||
)
|
)
|
||||||
|
|
||||||
containers = [
|
containers = [
|
||||||
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
(ContainerType.setup, helper.container_name(ContainerType.setup)),
|
||||||
(ContainerType.crashes, helper.containers[ContainerType.crashes]),
|
(ContainerType.crashes, helper.container_name(ContainerType.crashes)),
|
||||||
(ContainerType.reports, helper.containers[ContainerType.reports]),
|
(ContainerType.reports, helper.container_name(ContainerType.reports)),
|
||||||
(ContainerType.no_repro, helper.containers[ContainerType.no_repro]),
|
(ContainerType.no_repro, helper.container_name(ContainerType.no_repro)),
|
||||||
(
|
(
|
||||||
ContainerType.unique_reports,
|
ContainerType.unique_reports,
|
||||||
helper.containers[ContainerType.unique_reports],
|
helper.container_name(ContainerType.unique_reports),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
ContainerType.regression_reports,
|
ContainerType.regression_reports,
|
||||||
helper.containers[ContainerType.regression_reports],
|
helper.container_name(ContainerType.regression_reports),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
if extra_setup_container:
|
if extra_setup_container:
|
||||||
containers.append((ContainerType.extra_setup, extra_setup_container))
|
containers.append(
|
||||||
|
(
|
||||||
|
ContainerType.extra_setup,
|
||||||
|
extra_setup_container,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if crashes:
|
if crashes:
|
||||||
helper.containers[
|
helper.containers[ContainerType.readonly_inputs] = ContainerTemplate.fresh(
|
||||||
ContainerType.readonly_inputs
|
helper.get_unique_container_name(ContainerType.readonly_inputs)
|
||||||
] = helper.get_unique_container_name(ContainerType.readonly_inputs)
|
)
|
||||||
containers.append(
|
containers.append(
|
||||||
(
|
(
|
||||||
ContainerType.readonly_inputs,
|
ContainerType.readonly_inputs,
|
||||||
helper.containers[ContainerType.readonly_inputs],
|
helper.container_name(ContainerType.readonly_inputs),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -239,7 +244,7 @@ class Regression(Command):
|
|||||||
if crashes:
|
if crashes:
|
||||||
for file in crashes:
|
for file in crashes:
|
||||||
self.onefuzz.containers.files.upload_file(
|
self.onefuzz.containers.files.upload_file(
|
||||||
helper.containers[ContainerType.readonly_inputs], file
|
helper.container_name(ContainerType.readonly_inputs), file
|
||||||
)
|
)
|
||||||
|
|
||||||
helper.setup_notifications(notification_config)
|
helper.setup_notifications(notification_config)
|
||||||
@ -276,7 +281,7 @@ class Regression(Command):
|
|||||||
if task.error:
|
if task.error:
|
||||||
raise Exception("task failed: %s", task.error)
|
raise Exception("task failed: %s", task.error)
|
||||||
|
|
||||||
container = helper.containers[ContainerType.regression_reports]
|
container = helper.containers[ContainerType.regression_reports].name
|
||||||
for filename in self.onefuzz.containers.files.list(container).files:
|
for filename in self.onefuzz.containers.files.list(container).files:
|
||||||
self.logger.info("checking file: %s", filename)
|
self.logger.info("checking file: %s", filename)
|
||||||
if self._check_regression(container, File(filename)):
|
if self._check_regression(container, File(filename)):
|
||||||
@ -287,4 +292,6 @@ class Regression(Command):
|
|||||||
delete_input_container
|
delete_input_container
|
||||||
and ContainerType.readonly_inputs in helper.containers
|
and ContainerType.readonly_inputs in helper.containers
|
||||||
):
|
):
|
||||||
helper.delete_container(helper.containers[ContainerType.readonly_inputs])
|
helper.delete_container(
|
||||||
|
helper.containers[ContainerType.readonly_inputs].name
|
||||||
|
)
|
||||||
|
@ -89,4 +89,17 @@ resource enableWorkItemCreation 'Microsoft.AppConfiguration/configurationStores/
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource enableContainerRetentionPolicies 'Microsoft.AppConfiguration/configurationStores/keyValues@2021-10-01-preview' = {
|
||||||
|
parent: featureFlags
|
||||||
|
name: '.appconfig.featureflag~2FEnableContainerRetentionPolicies'
|
||||||
|
properties: {
|
||||||
|
value: string({
|
||||||
|
id: 'EnableContainerRetentionPolicies'
|
||||||
|
description: 'Enable retention policies on containers'
|
||||||
|
enabled: true
|
||||||
|
})
|
||||||
|
contentType: 'application/vnd.microsoft.appconfig.ff+json;charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
output AppConfigEndpoint string = 'https://${appConfigName}.azconfig.io'
|
output AppConfigEndpoint string = 'https://${appConfigName}.azconfig.io'
|
||||||
|
@ -304,6 +304,7 @@ class ErrorCode(Enum):
|
|||||||
ADO_VALIDATION_MISSING_PAT_SCOPES = 492
|
ADO_VALIDATION_MISSING_PAT_SCOPES = 492
|
||||||
ADO_VALIDATION_INVALID_PATH = 495
|
ADO_VALIDATION_INVALID_PATH = 495
|
||||||
ADO_VALIDATION_INVALID_PROJECT = 496
|
ADO_VALIDATION_INVALID_PROJECT = 496
|
||||||
|
INVALID_RETENTION_PERIOD = 497
|
||||||
# NB: if you update this enum, also update Enums.cs
|
# NB: if you update this enum, also update Enums.cs
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user