Bug fixes and documentation (#2694)

This commit is contained in:
Cheick Keita 2022-12-15 19:39:17 -08:00 committed by GitHub
parent ff923d28e7
commit 0fb8bc4a86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 147 additions and 23 deletions

80
docs/unmnaged-nodes.md Normal file
View File

@ -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.

View File

@ -152,7 +152,9 @@ public class AgentRegistration {
MachineId: machineId,
ScalesetId: scalesetId,
InstanceId: instanceId,
Version: version
Version: version,
Os: os ?? pool.Os,
Managed: pool.Managed
);
var r = await _context.NodeOperations.Replace(node);

View File

@ -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; }

View File

@ -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; }
@ -117,6 +118,7 @@ public class ServiceConfiguration : IServiceConfig {
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"); }

View File

@ -105,8 +105,10 @@ public sealed class Creds : ICreds {
});
}
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);

View File

@ -337,6 +337,10 @@ public class NodeOperations : StatefulOrm<Node, NodeState, NodeOperations>, INod
}
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) {

View File

@ -31,6 +31,8 @@ public sealed class TestServiceConfiguration : IServiceConfig {
// -- 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();

View File

@ -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 {
@ -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?,
};
@ -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> {

View File

@ -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));
@ -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)? {
@ -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()?,
};

View File

@ -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();
(

View File

@ -14,6 +14,7 @@ import uuid
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
@ -1268,6 +1269,16 @@ class Pool(Endpoint):
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:

View File

@ -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()

View File

@ -328,6 +328,9 @@ CONTAINER_DEF = Optional[Union[SyncedDir, List[SyncedDir]]]
class ClientCredentials(BaseModel):
client_id: UUID
client_secret: str
resource: str
tenant: str
multi_tenant_domain: Optional[str]
class AgentConfig(BaseModel):

View File

@ -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"

View File

@ -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"