Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions crates/now-package-broker/src/command_builder/winget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ pub fn build_winget_command(request: &PackageRequest) -> anyhow::Result<Vec<Stri
// `--no-upgrade` only applies to install (skip if a version is already present).
if matches!(request.operation, Operation::Install) {
set_if_true(&mut command, "--no-upgrade", request.options.no_upgrade);

// Without --force, winget converts an install of an installed package into an upgrade
// and rejects a lower pinned version (0x8A15002B), so downgrades never apply.
set_if_true(&mut command, "--force", request.package.version.is_some());
}

// `--uninstall-previous` applies to upgrade (remove the previous version).
Expand Down
2 changes: 2 additions & 0 deletions devolutions-agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ workspace = true
[dependencies]
agent-channel-client = { path = "../crates/agent-channel-client" }
agent-identity = { path = "../crates/agent-identity" }
agent-identity-httpsig = { path = "../crates/agent-identity-httpsig" }
agent-sysevent-codes = { path = "../crates/agent-sysevent-codes" }
device-key = { path = "../crates/device-key" }
agent-tunnel-proto = { path = "../crates/agent-tunnel-proto" }
Expand Down Expand Up @@ -52,6 +53,7 @@ tempfile = "3"
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12", "ring"] }
tokio-stream = "0.1"
tonic = { version = "0.12", features = ["transport", "tls-native-roots"] }
tower = "0.5"
tracing = "0.1"
url = { version = "2.5", features = ["serde"] }
x509-parser = "0.16"
Expand Down
4 changes: 2 additions & 2 deletions devolutions-agent/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ fn generate_psu_agent_proto() {

tonic_build::configure()
.build_transport(false)
.compile_protos(&["proto/psu_agent.proto"], &["proto"])
.expect("failed to compile PSU agent proto");
.compile_protos(&["proto/psu_agent.proto", "proto/device_management.proto"], &["proto"])
.expect("failed to compile agent protos");
}

#[cfg(target_os = "windows")]
Expand Down
119 changes: 119 additions & 0 deletions devolutions-agent/proto/device_management.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
syntax = "proto3";

// Prototype contract between the Devolutions Agent and DVLS for device management.
// Keep in sync with DVLS/Server/server/Website/Protos/device_management.proto.
// The stream authenticates with Agent Identity: the opening request is signed with the identity key
// (ChannelLayer), DVLS sends a Challenge, and the agent answers with a Hello that proves it holds the key.
package devolutions.device_management.v1;

option csharp_namespace = "Devolutions.Server.DeviceManagement.Grpc";

service DeviceManagement {
// The agent opens one long-lived stream. DVLS pushes requests on it, the agent pushes reports.
rpc Connect(stream AgentMessage) returns (stream ServerMessage);
}

message AgentMessage {
reserved 3;
oneof payload {
Hello hello = 1;
InventoryReport inventory = 2;
PackageOperationResult operation_result = 4;
CatalogSyncResult catalog_sync_result = 5;
}
}

// First agent message on the stream: the answer to the Challenge.
message Hello {
reserved 1;
// The challenge_id of the Challenge.
string correlation_id = 2;
// Agent Identity channel proof over the challenge and the opening nonce.
bytes proof = 3;
// Agent Identity metadata (hostname, os_name, os_version, arch, agent_version, ...).
map<string, string> metadata = 4;
}
message InventoryReport {
repeated InstalledPackage packages = 1;
// Package managers and sources that UniGetUI has on the machine. Each report replaces the previous ones.
repeated PackageManager managers = 2;
repeated PackageSource sources = 3;
}

message InstalledPackage {
string manager = 1;
string source = 2;
string id = 3;
string name = 4;
string version = 5;
// Newer version reported by the package manager. Empty when the package is up to date.
string available_version = 6;
}

message PackageManager {
string name = 1;
bool installed = 2;
bool enabled = 3;
string version = 4;
}

message PackageSource {
string manager = 1;
string name = 2;
string url = 3;
}

message ServerMessage {
reserved 2;
oneof payload {
SyncInventoryRequest sync_inventory = 1;
PackageOperationRequest package_operation = 3;
Challenge challenge = 4;
CatalogSync catalog_sync = 5;
}
}

// First server message on the stream.
message Challenge {
string challenge_id = 1;
bytes challenge = 2;
}

message SyncInventoryRequest {}

enum PackageOperationType {
PACKAGE_OPERATION_TYPE_INSTALL = 0;
PACKAGE_OPERATION_TYPE_UPDATE = 1;
PACKAGE_OPERATION_TYPE_UNINSTALL = 2;
}

// Installs, updates or uninstalls one package through UniGetUI. An empty version means the latest version.
message PackageOperationRequest {
string operation_id = 1;
PackageOperationType type = 2;
string manager = 3;
string source = 4;
string package_id = 5;
string version = 6;
}

// Answer to PackageOperationRequest. Error is set when success is false.
message PackageOperationResult {
string operation_id = 1;
bool success = 2;
string error = 3;
}

message CatalogSync {
int64 revision = 1;
oneof content {
string document = 2;
bool delete = 3;
}
}

message CatalogSyncResult {
int64 revision = 1;
bool ok = 2;
string error = 3;
}
33 changes: 33 additions & 0 deletions devolutions-agent/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub struct Conf {
pub package_broker: dto::PackageBrokerConf,
pub tunnel: TunnelConf,
pub psu_agent: Option<PsuConf>,
pub device_management: Option<DeviceManagementConf>,
pub proxy: dto::ProxyConf,
pub debug: dto::DebugConf,
}
Expand Down Expand Up @@ -287,6 +288,18 @@ impl TryFrom<dto::PsuConf> for Option<PsuConf> {
}
}

/// Validated device management configuration.
///
/// A disabled device management is `None`. The stream goes to the Agent Identity channel URL of the enrolled identity.
#[derive(Debug, Clone)]
pub struct DeviceManagementConf;

impl From<dto::DeviceManagementConf> for Option<DeviceManagementConf> {
fn from(conf: dto::DeviceManagementConf) -> Self {
conf.enabled.then_some(DeviceManagementConf)
}
}

impl Conf {
pub fn from_conf_file(conf_file: &dto::ConfFile) -> anyhow::Result<Self> {
let data_dir = get_data_dir();
Expand Down Expand Up @@ -323,6 +336,12 @@ impl Conf {
.unwrap_or_default()
.pipe(Option::<PsuConf>::try_from)
.context("invalid PSU agent config")?,
device_management: conf_file
.device_management
.clone()
.unwrap_or_default()
.pipe(Option::<DeviceManagementConf>::from),

package_broker: conf_file.package_broker.clone().unwrap_or_default(),
tunnel: conf_file
.tunnel
Expand Down Expand Up @@ -778,6 +797,15 @@ pub mod dto {
}
}

/// Device management configuration (Devolutions Server package inventory).
#[derive(PartialEq, Eq, Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct DeviceManagementConf {
/// Enable device management. It needs an enrolled Agent Identity.
pub enabled: bool,
}


#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct PsuPowerShellConf {
Expand Down Expand Up @@ -895,6 +923,10 @@ pub mod dto {
#[serde(alias = "PsuGrpcAgent", skip_serializing_if = "Option::is_none")]
pub psu_agent: Option<PsuConf>,

/// Device management (Devolutions Server package inventory).
#[serde(skip_serializing_if = "Option::is_none")]
pub device_management: Option<DeviceManagementConf>,

/// HTTP/SOCKS proxy configuration for outbound requests
#[serde(skip_serializing_if = "Option::is_none")]
pub proxy: Option<ProxyConf>,
Expand Down Expand Up @@ -928,6 +960,7 @@ pub mod dto {
package_broker: None,
tunnel: None,
psu_agent: None,
device_management: None,
rest: serde_json::Map::new(),
}
}
Expand Down
Loading
Loading