add instance_id generated at install time (#245)

This commit is contained in:
bmc-msft
2020-11-02 14:27:51 -05:00
committed by GitHub
parent 89ebc7be50
commit 6c598773dd
20 changed files with 83 additions and 21 deletions

View File

@ -66,6 +66,7 @@ The following describes the information sent to Microsoft if telemetry is enable
The following are common data types used in multiple locations: The following are common data types used in multiple locations:
* Instance ID - A randomly generated GUID used to uniquely identify an instance of OneFuzz
* Task ID - A randomly generated GUID used to uniquely identify a fuzzing task. * Task ID - A randomly generated GUID used to uniquely identify a fuzzing task.
* Job ID - A randomly generated GUID used to uniquely identify a job. * Job ID - A randomly generated GUID used to uniquely identify a job.
* Machine ID - A GUID used to identify the machine running the task. When run in * Machine ID - A GUID used to identify the machine running the task. When run in

View File

@ -64,6 +64,7 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
telemetry_key: None, telemetry_key: None,
job_id: Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(), job_id: Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(),
task_id: Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap(), task_id: Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap(),
instance_id: Uuid::parse_str("22222222-2222-2222-2222-222222222222").unwrap(),
}, },
}; };

View File

@ -64,6 +64,7 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
telemetry_key: None, telemetry_key: None,
job_id: Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(), job_id: Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(),
task_id: Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap(), task_id: Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap(),
instance_id: Uuid::parse_str("22222222-2222-2222-2222-222222222222").unwrap(),
}, },
}; };

View File

@ -60,6 +60,7 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
telemetry_key: None, telemetry_key: None,
job_id: Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(), job_id: Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(),
task_id: Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap(), task_id: Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap(),
instance_id: Uuid::parse_str("22222222-2222-2222-2222-222222222222").unwrap(),
}, },
}; };

View File

@ -62,6 +62,7 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
telemetry_key: None, telemetry_key: None,
job_id: Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(), job_id: Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(),
task_id: Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap(), task_id: Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap(),
instance_id: Uuid::parse_str("22222222-2222-2222-2222-222222222222").unwrap(),
}, },
}; };

View File

@ -26,6 +26,8 @@ pub struct CommonConfig {
pub task_id: Uuid, pub task_id: Uuid,
pub instance_id: Uuid,
pub instrumentation_key: Option<Uuid>, pub instrumentation_key: Option<Uuid>,
pub heartbeat_queue: Option<Url>, pub heartbeat_queue: Option<Url>,
@ -127,6 +129,7 @@ impl Config {
telemetry::set_property(EventData::TaskId(self.common().task_id)); telemetry::set_property(EventData::TaskId(self.common().task_id));
telemetry::set_property(EventData::MachineId(get_machine_id().await?)); telemetry::set_property(EventData::MachineId(get_machine_id().await?));
telemetry::set_property(EventData::Version(env!("ONEFUZZ_VERSION").to_string())); telemetry::set_property(EventData::Version(env!("ONEFUZZ_VERSION").to_string()));
telemetry::set_property(EventData::InstanceId(self.common().instance_id));
if let Ok(scaleset) = get_scaleset_name().await { if let Ok(scaleset) = get_scaleset_name().await {
telemetry::set_property(EventData::ScalesetId(scaleset)); telemetry::set_property(EventData::ScalesetId(scaleset));
} }

View File

@ -28,6 +28,8 @@ pub struct StaticConfig {
pub telemetry_key: Option<Uuid>, pub telemetry_key: Option<Uuid>,
pub heartbeat_queue: Option<Url>, pub heartbeat_queue: Option<Url>,
pub instance_id: Uuid,
} }
// Temporary shim type to bridge the current service-provided config. // Temporary shim type to bridge the current service-provided config.
@ -44,6 +46,8 @@ struct RawStaticConfig {
pub telemetry_key: Option<Uuid>, pub telemetry_key: Option<Uuid>,
pub heartbeat_queue: Option<Url>, pub heartbeat_queue: Option<Url>,
pub instance_id: Uuid,
} }
impl StaticConfig { impl StaticConfig {
@ -70,6 +74,7 @@ impl StaticConfig {
instrumentation_key: config.instrumentation_key, instrumentation_key: config.instrumentation_key,
telemetry_key: config.telemetry_key, telemetry_key: config.telemetry_key,
heartbeat_queue: config.heartbeat_queue, heartbeat_queue: config.heartbeat_queue,
instance_id: config.instance_id,
}; };
Ok(config) Ok(config)

View File

@ -120,6 +120,7 @@ fn load_config(opt: RunOpt) -> Result<StaticConfig> {
} }
async fn run_agent(config: StaticConfig) -> Result<()> { async fn run_agent(config: StaticConfig) -> Result<()> {
telemetry::set_property(EventData::InstanceId(config.instance_id));
telemetry::set_property(EventData::MachineId(get_machine_id().await?)); telemetry::set_property(EventData::MachineId(get_machine_id().await?));
telemetry::set_property(EventData::Version(env!("ONEFUZZ_VERSION").to_string())); telemetry::set_property(EventData::Version(env!("ONEFUZZ_VERSION").to_string()));
if let Ok(scaleset) = get_scaleset_name().await { if let Ok(scaleset) = get_scaleset_name().await {

View File

@ -43,6 +43,7 @@ impl Event {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum EventData { pub enum EventData {
WorkerId(u64), WorkerId(u64),
InstanceId(Uuid),
JobId(Uuid), JobId(Uuid),
TaskId(Uuid), TaskId(Uuid),
ScalesetId(String), ScalesetId(String),
@ -77,6 +78,7 @@ impl EventData {
pub fn as_values(&self) -> (&str, String) { pub fn as_values(&self) -> (&str, String) {
match self { match self {
Self::Version(x) => ("version", x.to_string()), Self::Version(x) => ("version", x.to_string()),
Self::InstanceId(x) => ("instance_id", x.to_string()),
Self::JobId(x) => ("job_id", x.to_string()), Self::JobId(x) => ("job_id", x.to_string()),
Self::TaskId(x) => ("task_id", x.to_string()), Self::TaskId(x) => ("task_id", x.to_string()),
Self::ScalesetId(x) => ("scaleset_id", x.to_string()), Self::ScalesetId(x) => ("scaleset_id", x.to_string()),
@ -113,6 +115,7 @@ impl EventData {
// TODO: Request CELA review of Version, as having this for central stats // TODO: Request CELA review of Version, as having this for central stats
// would be useful to track uptake of new releases // would be useful to track uptake of new releases
Self::Version(_) => false, Self::Version(_) => false,
Self::InstanceId(_) => true,
Self::TaskId(_) => true, Self::TaskId(_) => true,
Self::JobId(_) => true, Self::JobId(_) => true,
Self::MachineId(_) => true, Self::MachineId(_) => true,

View File

@ -9,6 +9,7 @@ from onefuzztypes.responses import Info
from ..onefuzzlib.azure.creds import ( from ..onefuzzlib.azure.creds import (
get_base_region, get_base_region,
get_base_resource_group, get_base_resource_group,
get_instance_id,
get_subscription, get_subscription,
) )
from ..onefuzzlib.request import ok from ..onefuzzlib.request import ok
@ -22,5 +23,6 @@ def main(req: func.HttpRequest) -> func.HttpResponse:
region=get_base_region(), region=get_base_region(),
subscription=get_subscription(), subscription=get_subscription(),
versions=versions(), versions=versions(),
instance_id=get_instance_id(),
) )
) )

View File

@ -6,7 +6,7 @@
import datetime import datetime
import os import os
import urllib.parse import urllib.parse
from typing import Any, Dict, Optional, Union, cast from typing import Dict, Optional, Union, cast
from azure.common import AzureHttpError, AzureMissingResourceHttpError from azure.common import AzureHttpError, AzureMissingResourceHttpError
from azure.storage.blob import BlobPermissions, ContainerPermissions from azure.storage.blob import BlobPermissions, ContainerPermissions
@ -129,11 +129,11 @@ def save_blob(
def get_blob( def get_blob(
container: str, name: str, account_id: Optional[str] = None container: str, name: str, account_id: Optional[str] = None
) -> Optional[Any]: # should be bytes ) -> Optional[bytes]:
service = get_blob_service(account_id) service = get_blob_service(account_id)
try: try:
blob = service.get_blob_to_bytes(container, name).content blob = service.get_blob_to_bytes(container, name).content
return blob return cast(bytes, blob)
except AzureMissingResourceHttpError: except AzureMissingResourceHttpError:
return None return None

View File

@ -102,6 +102,16 @@ def get_instance_url() -> str:
return "https://%s.azurewebsites.net" % get_instance_name() return "https://%s.azurewebsites.net" % get_instance_name()
@cached
def get_instance_id() -> UUID:
from .containers import get_blob
blob = get_blob("base-config", "instance_id", account_id=get_func_storage())
if blob is None:
raise Exception("missing instance_id")
return UUID(blob.decode())
DAY_IN_SECONDS = 60 * 60 * 24 DAY_IN_SECONDS = 60 * 60 * 24

View File

@ -12,7 +12,7 @@ from onefuzztypes.models import AgentConfig, ReproConfig
from onefuzztypes.primitives import Extension, Region from onefuzztypes.primitives import Extension, Region
from .azure.containers import get_container_sas_url, get_file_sas_url, save_blob from .azure.containers import get_container_sas_url, get_file_sas_url, save_blob
from .azure.creds import get_func_storage, get_instance_url from .azure.creds import get_func_storage, get_instance_id, get_instance_url
from .azure.monitor import get_monitor_settings from .azure.monitor import get_monitor_settings
from .azure.queue import get_queue_sas from .azure.queue import get_queue_sas
from .reports import get_report from .reports import get_report
@ -100,6 +100,7 @@ def build_pool_config(pool_name: str) -> str:
add=True, add=True,
), ),
telemetry_key=os.environ.get("ONEFUZZ_TELEMETRY"), telemetry_key=os.environ.get("ONEFUZZ_TELEMETRY"),
instance_id=get_instance_id(),
) )
save_blob( save_blob(

View File

@ -5,7 +5,7 @@
import json import json
import logging import logging
from typing import Optional from typing import Optional, Union
from onefuzztypes.models import Report from onefuzztypes.models import Report
from pydantic import ValidationError from pydantic import ValidationError
@ -13,7 +13,9 @@ from pydantic import ValidationError
from .azure.containers import get_blob from .azure.containers import get_blob
def parse_report(content: str, metadata: Optional[str] = None) -> Optional[Report]: def parse_report(
content: Union[str, bytes], metadata: Optional[str] = None
) -> Optional[Report]:
if isinstance(content, bytes): if isinstance(content, bytes):
try: try:
content = content.decode() content = content.decode()

View File

@ -12,7 +12,12 @@ from onefuzztypes.enums import Compare, ContainerPermission, ContainerType, Task
from onefuzztypes.models import TaskConfig, TaskDefinition, TaskUnitConfig from onefuzztypes.models import TaskConfig, TaskDefinition, TaskUnitConfig
from ..azure.containers import blob_exists, container_exists, get_container_sas_url from ..azure.containers import blob_exists, container_exists, get_container_sas_url
from ..azure.creds import get_func_storage, get_fuzz_storage, get_instance_url from ..azure.creds import (
get_func_storage,
get_fuzz_storage,
get_instance_id,
get_instance_url,
)
from ..azure.queue import get_queue_sas from ..azure.queue import get_queue_sas
from .defs import TASK_DEFINITIONS from .defs import TASK_DEFINITIONS
@ -187,6 +192,7 @@ def build_task_config(
add=True, add=True,
), ),
back_channel_address="https://%s/api/back_channel" % (get_instance_url()), back_channel_address="https://%s/api/back_channel" % (get_instance_url()),
instance_id=get_instance_id(),
) )
if definition.monitor_queue: if definition.monitor_queue:

View File

@ -15,6 +15,7 @@ from onefuzztypes.responses import BoolResult
from ..onefuzzlib.azure.creds import ( from ..onefuzzlib.azure.creds import (
get_base_region, get_base_region,
get_func_storage, get_func_storage,
get_instance_id,
get_instance_url, get_instance_url,
get_regions, get_regions,
) )
@ -35,6 +36,7 @@ def set_config(pool: Pool) -> Pool:
account_id=get_func_storage(), account_id=get_func_storage(),
add=True, add=True,
), ),
instance_id=get_instance_id(),
) )
return pool return pool

View File

@ -63,8 +63,7 @@
"namespace": "onefuzz", "namespace": "onefuzz",
"members": { "members": {
"severitiesAtMostInfo": { "severitiesAtMostInfo": {
"parameters": [ "parameters": [],
],
"output": { "output": {
"type": "array", "type": "array",
"value": [ "value": [
@ -209,8 +208,7 @@
], ],
"linuxFxVersion": "Python|3.7", "linuxFxVersion": "Python|3.7",
"alwaysOn": true, "alwaysOn": true,
"defaultDocuments": [ "defaultDocuments": [],
],
"httpLoggingEnabled": true, "httpLoggingEnabled": true,
"logsDirectorySizeLimit": 100, "logsDirectorySizeLimit": 100,
"detailedErrorLoggingEnabled": true, "detailedErrorLoggingEnabled": true,
@ -243,8 +241,7 @@
"type": "Microsoft.Web/serverFarms", "type": "Microsoft.Web/serverFarms",
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"kind": "linux", "kind": "linux",
"dependsOn": [ "dependsOn": [],
],
"properties": { "properties": {
"name": "[parameters('name')]", "name": "[parameters('name')]",
"reserved": true "reserved": true
@ -394,7 +391,6 @@
"properties": { "properties": {
"workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', variables('monitorAccountName'))]" "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', variables('monitorAccountName'))]"
}, },
"plan": { "plan": {
"name": "[concat('VMInsights', '(', variables('monitorAccountName'), ')')]", "name": "[concat('VMInsights', '(', variables('monitorAccountName'), ')')]",
"publisher": "Microsoft", "publisher": "Microsoft",
@ -694,20 +690,17 @@
{ {
"flag": "ServiceMode", "flag": "ServiceMode",
"value": "Serverless", "value": "Serverless",
"properties": { "properties": {}
}
}, },
{ {
"flag": "EnableConnectivityLogs", "flag": "EnableConnectivityLogs",
"value": "True", "value": "True",
"properties": { "properties": {}
}
}, },
{ {
"flag": "EnableMessagingLogs", "flag": "EnableMessagingLogs",
"value": "False", "value": "False",
"properties": { "properties": {}
}
} }
] ]
} }
@ -743,4 +736,4 @@
"value": "[variables('scaleset_identity')]" "value": "[variables('scaleset_identity')]"
} }
} }
} }

View File

@ -92,6 +92,7 @@ FUNC_TOOLS_ERROR = (
logger = logging.getLogger("deploy") logger = logging.getLogger("deploy")
def gen_guid(): def gen_guid():
return str(uuid.uuid4()) return str(uuid.uuid4())
@ -464,6 +465,29 @@ class Client:
% json.dumps(result.as_dict(), indent=4, sort_keys=True), % json.dumps(result.as_dict(), indent=4, sort_keys=True),
) )
def add_instance_id(self):
logger.info("setting instance_id log export")
container_name = "base-config"
blob_name = "instance_id"
account_name = self.results["deploy"]["func-name"]["value"]
key = self.results["deploy"]["func-key"]["value"]
account_url = "https://%s.blob.core.windows.net" % account_name
client = BlobServiceClient(account_url, credential=key)
if container_name not in [x["name"] for x in client.list_containers()]:
client.create_container(container_name)
blob_client = client.get_blob_client(container_name, blob_name)
if blob_client.exists():
logger.debug("instance_id already exists")
instance_id = uuid.UUID(blob_client.download_blob().readall())
else:
logger.debug("creating new instance_id")
instance_id = uuid.uuid4()
blob_client.upload_blob(str(instance_id))
logger.info("instance_id: %s", instance_id)
def add_log_export(self): def add_log_export(self):
if not self.export_appinsights: if not self.export_appinsights:
logger.info("not exporting appinsights") logger.info("not exporting appinsights")
@ -683,6 +707,7 @@ def main():
("queues", Client.create_queues), ("queues", Client.create_queues),
("eventgrid", Client.create_eventgrid), ("eventgrid", Client.create_eventgrid),
("tools", Client.upload_tools), ("tools", Client.upload_tools),
("add_instance_id", Client.add_instance_id),
("instance-specific-setup", Client.upload_instance_setup), ("instance-specific-setup", Client.upload_instance_setup),
("third-party", Client.upload_third_party), ("third-party", Client.upload_third_party),
("api", Client.deploy_app), ("api", Client.deploy_app),

View File

@ -270,9 +270,11 @@ class AgentConfig(BaseModel):
heartbeat_queue: Optional[str] heartbeat_queue: Optional[str]
instrumentation_key: Optional[str] instrumentation_key: Optional[str]
telemetry_key: Optional[str] telemetry_key: Optional[str]
instance_id: UUID
class TaskUnitConfig(BaseModel): class TaskUnitConfig(BaseModel):
instance_id: UUID
job_id: UUID job_id: UUID
task_id: UUID task_id: UUID
task_type: TaskType task_type: TaskType

View File

@ -4,6 +4,7 @@
# Licensed under the MIT License. # Licensed under the MIT License.
from typing import Dict, Optional from typing import Dict, Optional
from uuid import UUID
from pydantic import BaseModel from pydantic import BaseModel
@ -35,6 +36,7 @@ class Info(BaseResponse):
region: Region region: Region
subscription: str subscription: str
versions: Dict[str, Version] versions: Dict[str, Version]
instance_id: Optional[UUID]
class ContainerInfoBase(BaseResponse): class ContainerInfoBase(BaseResponse):