Skip to content
This repository has been archived by the owner on Nov 1, 2023. It is now read-only.

Commit

Permalink
Merge branch 'main' into user/noharper/remove-net-func
Browse files Browse the repository at this point in the history
  • Loading branch information
nharper285 authored Dec 16, 2022
2 parents aef96fd + 0fb8bc4 commit 21e848b
Show file tree
Hide file tree
Showing 15 changed files with 147 additions and 23 deletions.
80 changes: 80 additions & 0 deletions docs/unmnaged-nodes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Unmanaged Nodes
The default mode of OneFuzz is to run the agents inside scalesets managed by the the Onefuzz instance. But it is possible to run outside of the Instance infrastructure.
This is the unmanaged scenario. In this mode, the user can use their own resource to participate in the fuzzing.

## Set-up
These are the steps to run an unmanaged node


### Create an Application Registration in Azure Active Directory
We will create the authentication method for the unmanaged node.
From the [azure cli](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) create a new **application registration**:
```cmd
az ad app create --display-name <registration_name>
```
Then use the application `app_id` in the result to create the associated **service principal**:

```cmd
az ad sp create --id <app_id>
```
Take note of the `id` returned by this request. We will call it the `principal_id`.

Next, create a `client_secret`:

```
az ad app credential reset --id <pp_id> --append
```
Take note of the `password` returned.

### Authorize the application in OneFuzz
From the OneFuzz `deployment` folder run the following script using the `app_id` from above:
``` cmd
python .\deploylib\registration.py register_app <onefuzz_instance_id> <subscription_id> --app_id <app_id> --role UnmanagedNode
```

### Create an unmanaged pool
Using the OneFuzz CLI:
``` cmd
onefuzz pools create <pool_name> <os> --unmanaged --object_id <principal_id>
```

### Download the agent binaries and the agent configuration
Download a zip file containing the agent binaries:
```
onefuzz tools get <destination_folder>
```
Extract the zip file in a folder of your choice.

Download the configuration file for the agent:

```
onefuzz pools get_config <pool_name>
```

Under the `client_credential` section of the agent config file, update `client_id` and `client_secret`:
```json
{
"client_id": "<app_id>",
"client_secret": "<password>",
}
```
Save the config to the file.

### Start the agent.
Navigate to the folder corresponding to your OS.
Set the necessary environment variable by running the script `set-env.ps1` (for Windows) or `set-env.sh` (for Linux).
Run the agent with the following command. If you need more nodes use a different `machine_guid` for each one:
```cmd
onefuzz-agent run --machine_id <machine_guid> -c <path_to_config_file> --reset_lock
```

### Verify that the agent is registered to OneFuzz

Using the OneFuzz CLI run the following command:

```
onefuzz nodes get <machine_guid>
```

This should return one entry. Verify that the `pool_name` matched the pool name created earlier.
From here you will be able to schedule jobs on that pool and they will be running.
4 changes: 3 additions & 1 deletion src/ApiService/ApiService/Functions/AgentRegistration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
MachineId: machineId,
ScalesetId: scalesetId,
InstanceId: instanceId,
Version: version
Version: version,
Os: os ?? pool.Os,
Managed: pool.Managed
);

var r = await _context.NodeOperations.Replace(node);
Expand Down
3 changes: 2 additions & 1 deletion src/ApiService/ApiService/OneFuzzTypes/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ public record Node

bool ReimageRequested = false,
bool DeleteRequested = false,
bool DebugKeepNode = false
bool DebugKeepNode = false,
bool Managed = true
) : StatefulEntityBase<NodeState>(State) {

public List<NodeTasks>? Tasks { get; set; }
Expand Down
2 changes: 2 additions & 0 deletions src/ApiService/ApiService/ServiceConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public interface IServiceConfig {
public ResourceIdentifier? OneFuzzFuncStorage { get; }
public string? OneFuzzInstance { get; }
public string? OneFuzzInstanceName { get; }
public string? OneFuzzEndpoint { get; }
public string? OneFuzzKeyvault { get; }

public string? OneFuzzMonitor { get; }
Expand Down Expand Up @@ -117,6 +118,7 @@ public ResourceIdentifier? OneFuzzFuncStorage {

public string? OneFuzzInstance { get => GetEnv("ONEFUZZ_INSTANCE"); }
public string? OneFuzzInstanceName { get => GetEnv("ONEFUZZ_INSTANCE_NAME"); }
public string? OneFuzzEndpoint { get => GetEnv("ONEFUZZ_ENDPOINT"); }
public string? OneFuzzKeyvault { get => GetEnv("ONEFUZZ_KEYVAULT"); }
public string? OneFuzzMonitor { get => GetEnv("ONEFUZZ_MONITOR"); }
public string? OneFuzzOwner { get => GetEnv("ONEFUZZ_OWNER"); }
Expand Down
6 changes: 4 additions & 2 deletions src/ApiService/ApiService/onefuzzlib/Creds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,10 @@ public Async.Task<Region> GetBaseRegion() {
});
}

public Uri GetInstanceUrl()
=> new($"https://{GetInstanceName()}.azurewebsites.net");
public Uri GetInstanceUrl() {
var onefuzzEndpoint = _config.OneFuzzEndpoint;
return onefuzzEndpoint != null ? new Uri(onefuzzEndpoint) : new($"https://{GetInstanceName()}.azurewebsites.net");
}

public record ScaleSetIdentity(string principalId);

Expand Down
4 changes: 4 additions & 0 deletions src/ApiService/ApiService/onefuzzlib/NodeOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,10 @@ public async Async.Task CleanupBusyNodesWithoutWork() {
}

public async Async.Task<Node> ToReimage(Node node, bool done = false) {
if (!node.Managed) {
_logTracer.Info($"skip reimage for unmanaged node: {node.MachineId:Tag:MachineId}");
return node;
}

var nodeState = node.State;
if (done) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public TestServiceConfiguration(string tablePrefix) {

// -- Remainder not implemented --

public string? OneFuzzEndpoint => throw new System.NotImplementedException();

public LogDestination[] LogDestinations { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }

public SeverityLevel LogSeverityLevel => throw new System.NotImplementedException();
Expand Down
11 changes: 7 additions & 4 deletions src/agent/onefuzz-agent/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ struct RawStaticConfig {
}

impl StaticConfig {
pub async fn new(data: &[u8]) -> Result<Self> {
pub async fn new(data: &[u8], machine_identity: Option<MachineIdentity>) -> Result<Self> {
let config: RawStaticConfig = serde_json::from_slice(data)?;

let credentials = match config.client_credentials {
Expand All @@ -104,7 +104,7 @@ impl StaticConfig {
managed.into()
}
};
let machine_identity = match config.machine_identity {
let machine_identity = match machine_identity.or(config.machine_identity) {
Some(machine_identity) => machine_identity,
None => MachineIdentity::from_metadata().await?,
};
Expand All @@ -125,11 +125,14 @@ impl StaticConfig {
Ok(config)
}

pub async fn from_file(config_path: impl AsRef<Path>) -> Result<Self> {
pub async fn from_file(
config_path: impl AsRef<Path>,
machine_identity: Option<MachineIdentity>,
) -> Result<Self> {
let config_path = config_path.as_ref();
let data = std::fs::read(config_path)
.with_context(|| format!("unable to read config file: {}", config_path.display()))?;
Self::new(&data).await
Self::new(&data, machine_identity).await
}

pub fn from_env() -> Result<Self> {
Expand Down
23 changes: 10 additions & 13 deletions src/agent/onefuzz-agent/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,6 @@ fn run(opt: RunOpt) -> Result<()> {
if opt.redirect_output.is_some() {
return redirect(opt);
}
let opt_machine_id = opt.machine_id;
let opt_machine_name = opt.machine_name.clone();
let rt = tokio::runtime::Runtime::new()?;
let reset_lock = opt.reset_node_lock;
let config = rt.block_on(load_config(opt));
Expand All @@ -184,15 +182,6 @@ fn run(opt: RunOpt) -> Result<()> {

let config = config?;

let config = StaticConfig {
machine_identity: MachineIdentity {
machine_id: opt_machine_id.unwrap_or(config.machine_identity.machine_id),
machine_name: opt_machine_name.unwrap_or(config.machine_identity.machine_name),
..config.machine_identity
},
..config
};

if reset_lock {
done::remove_done_lock(config.machine_identity.machine_id)?;
} else if done::is_agent_done(config.machine_identity.machine_id)? {
Expand All @@ -218,10 +207,18 @@ fn run(opt: RunOpt) -> Result<()> {
}

async fn load_config(opt: RunOpt) -> Result<StaticConfig> {
info!("loading supervisor agent config");
info!("loading supervisor agent config: {:?}", opt);
let opt_machine_id = opt.machine_id;
let opt_machine_name = opt.machine_name.clone();

let machine_identity = opt_machine_id.map(|machine_id| MachineIdentity {
machine_id,
machine_name: opt_machine_name.unwrap_or(format!("{}", machine_id)),
scaleset_name: None,
});

let config = match &opt.config_path {
Some(config_path) => StaticConfig::from_file(config_path).await?,
Some(config_path) => StaticConfig::from_file(config_path, machine_identity).await?,
None => StaticConfig::from_env()?,
};

Expand Down
4 changes: 3 additions & 1 deletion src/agent/onefuzz/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,11 @@ impl ClientCredentials {
pub async fn access_token(&self) -> Result<AccessToken> {
let (authority, scope) = {
let url = Url::parse(&self.resource.clone())?;
let host = url.host_str().ok_or_else(|| {
let port = url.port().map(|p| format!(":{}", p)).unwrap_or_default();
let host_name = url.host_str().ok_or_else(|| {
anyhow::format_err!("resource URL does not have a host string: {}", url)
})?;
let host = format!("{}{}", host_name, port);
if let Some(domain) = &self.multi_tenant_domain {
let instance: Vec<&str> = host.split('.').collect();
(
Expand Down
11 changes: 11 additions & 0 deletions src/cli/onefuzz/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from enum import Enum
from shutil import which
from typing import Callable, Dict, List, Optional, Tuple, Type, TypeVar
from urllib.parse import urlparse
from uuid import UUID

import semver
Expand Down Expand Up @@ -1268,6 +1269,16 @@ def get_config(self, pool_name: primitives.PoolName) -> models.AgentConfig:
if pool.config is None:
raise Exception("Missing AgentConfig in response")

config = pool.config
if not pool.managed:
config.client_credentials = models.ClientCredentials( # nosec
client_id=uuid.UUID(int=0),
client_secret="<client_secret>",
resource=self.onefuzz._backend.config.endpoint,
tenant=urlparse(self.onefuzz._backend.config.authority).path.strip("/"),
multi_tenant_domain=self.onefuzz._backend.config.tenant_domain,
)

return pool.config

def shutdown(self, name: str, *, now: bool = False) -> responses.BoolResult:
Expand Down
5 changes: 4 additions & 1 deletion src/deployment/deploylib/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -861,10 +861,13 @@ def main() -> None:
"--registration_name", help="the name of the cli registration"
)
register_app_parser = subparsers.add_parser("register_app", parents=[parent_parser])
register_app_parser.add_argument("--app_id", help="the application id to register")
register_app_parser.add_argument(
"--app_id", help="the application id to register", required=True
)
register_app_parser.add_argument(
"--role",
help=f"the role of the application to register. Valid values: {', '.join([member.value for member in OnefuzzAppRole])}",
required=True,
)

args = parser.parse_args()
Expand Down
3 changes: 3 additions & 0 deletions src/pytypes/onefuzztypes/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,9 @@ class SyncedDir(BaseModel):
class ClientCredentials(BaseModel):
client_id: UUID
client_secret: str
resource: str
tenant: str
multi_tenant_domain: Optional[str]


class AgentConfig(BaseModel):
Expand Down
6 changes: 6 additions & 0 deletions src/runtime-tools/linux/set-env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
export DOTNET_ROOT=/onefuzz/tools/dotnet
export DOTNET_CLI_HOME="$DOTNET_ROOT"
export LLVM_SYMBOLIZER_PATH=/onefuzz/bin/llvm-symbolizer
export RUST_LOG = "info"
6 changes: 6 additions & 0 deletions src/runtime-tools/win64/set-env.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

$env:Path += ";C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\;C:\onefuzz\win64;C:\onefuzz\tools\win64;C:\onefuzz\tools\win64\radamsa;$env:ProgramFiles\LLVM\bin"
$env:LLVM_SYMBOLIZER_PATH = "C:\Program Files\LLVM\bin\llvm-symbolizer.exe"
$env:RUST_LOG = "info"

0 comments on commit 21e848b

Please sign in to comment.