Skip to content
This repository was archived by the owner on Nov 1, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
959458c
rename client_id in pool to object_id
chkeita Dec 6, 2022
361ebd1
fix tests
chkeita Dec 6, 2022
37de8d0
print out the content body when receiving an error response in the agent
chkeita Dec 6, 2022
8ee7e6c
Merge branch 'main' into rewrite/bug_fix
chkeita Dec 6, 2022
99df6ac
fix test
chkeita Dec 8, 2022
808a845
Merge branch 'main' into rewrite/bug_fix
chkeita Dec 8, 2022
35a54f5
Apply suggestions from code review
chkeita Dec 9, 2022
8b79755
Update src/ApiService/ApiService/Functions/AgentRegistration.cs
chkeita Dec 9, 2022
407ad5e
format
chkeita Dec 9, 2022
937f887
cleanup
chkeita Dec 12, 2022
07f0d20
Merge branch 'main' into rewrite/bug_fix
chkeita Dec 12, 2022
4e2531b
format
chkeita Dec 12, 2022
8d8b7d5
Merge branch 'main' into rewrite/bug_fix
chkeita Dec 12, 2022
f6d694e
Bug fixes and documentation
chkeita Dec 13, 2022
db091da
Merge branch 'main' into unmanaged_nodes/registration_script
chkeita Dec 13, 2022
5f06cb3
update doc
chkeita Dec 13, 2022
ff1ad07
format
chkeita Dec 13, 2022
5d967f1
format
chkeita Dec 13, 2022
ef21440
build fix
chkeita Dec 13, 2022
f024164
format
chkeita Dec 13, 2022
2bd90c3
Merge branch 'main' into unmanaged_nodes/registration_script
chkeita Dec 13, 2022
e0b2aa4
Merge branch 'main' into unmanaged_nodes/registration_script
chkeita Dec 14, 2022
fad65cc
Apply suggestions from code review
chkeita Dec 14, 2022
bc6d667
skip reimage of unmanaged nodes
chkeita Dec 14, 2022
3cfc6e2
update set-env
chkeita Dec 14, 2022
5a9a504
update doc
chkeita Dec 14, 2022
60a35b8
Merge branch 'main' into unmanaged_nodes/registration_script
chkeita Dec 15, 2022
e03abfd
address comment
chkeita Dec 16, 2022
e31a405
Merge branch 'main' into unmanaged_nodes/registration_script
chkeita Dec 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"