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: 1 addition & 1 deletion devolutions-agent/src/psu_event_hub/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub(crate) mod compat;
mod executor;
mod models;
mod powershell_worker;
pub(crate) mod powershell_worker;
mod result_store;
mod signalr;

Expand Down
16 changes: 8 additions & 8 deletions devolutions-agent/src/psu_event_hub/powershell_worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,15 +169,15 @@ $response | ConvertTo-Json -Compress -Depth 16
const POWERSHELL_EXECUTION_TIMEOUT: Duration = Duration::from_secs(30 * 60);

#[derive(Debug, Clone)]
pub(super) struct PowerShellWorker {
pub(crate) struct PowerShellWorker {
conf: PsuPowerShellConf,
permits: Arc<Semaphore>,
worker_script: Arc<WorkerScriptFile>,
execution_timeout: Duration,
}

impl PowerShellWorker {
pub(super) fn new(conf: PsuPowerShellConf) -> anyhow::Result<Self> {
pub(crate) fn new(conf: PsuPowerShellConf) -> anyhow::Result<Self> {
Self::with_execution_timeout(conf, POWERSHELL_EXECUTION_TIMEOUT)
}

Expand All @@ -191,8 +191,8 @@ impl PowerShellWorker {
})
}

pub(super) async fn resolve_app_token(&self, app_token: &str) -> anyhow::Result<String> {
let Some(secret_name) = secret_reference_name(app_token) else {
pub(crate) async fn resolve_app_token(&self, app_token: &str) -> anyhow::Result<String> {
let Some(secret_name) = app_token_secret_reference_name(app_token) else {
return Ok(app_token.to_owned());
};

Expand Down Expand Up @@ -399,7 +399,7 @@ impl WorkerRequest {
}
}

fn secret_reference_name(app_token: &str) -> Option<&str> {
pub(crate) fn app_token_secret_reference_name(app_token: &str) -> Option<&str> {
let prefix = "$secret:";
app_token
.get(..prefix.len())
Expand Down Expand Up @@ -563,9 +563,9 @@ mod tests {

#[test]
fn secret_reference_name_is_case_insensitive() {
assert_eq!(secret_reference_name("$secret:AppToken"), Some("AppToken"));
assert_eq!(secret_reference_name("$SECRET:AppToken"), Some("AppToken"));
assert_eq!(secret_reference_name("literal-token"), None);
assert_eq!(app_token_secret_reference_name("$secret:AppToken"), Some("AppToken"));
assert_eq!(app_token_secret_reference_name("$SECRET:AppToken"), Some("AppToken"));
assert_eq!(app_token_secret_reference_name("literal-token"), None);
}

#[test]
Expand Down
63 changes: 59 additions & 4 deletions devolutions-agent/src/psu_grpc_agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use tonic::transport::Endpoint;
use uuid::Uuid;

use crate::config::{ConfHandle, dto};
use crate::psu_event_hub::powershell_worker::{PowerShellWorker, app_token_secret_reference_name};
use crate::psu_grpc_agent::process::{ProcessControl, ProcessRegistry};

#[allow(unused_qualifications, clippy::clone_on_ref_ptr, clippy::similar_names)]
Expand Down Expand Up @@ -140,6 +141,7 @@ impl PsuGrpcAgent {
}

async fn run_single_connection(&self, shutdown_signal: &mut ShutdownSignal) -> anyhow::Result<()> {
let app_token = self.resolve_app_token().await?;
let endpoint = Endpoint::from_shared(self.server_url.clone())?;
let channel = endpoint
.connect()
Expand All @@ -155,10 +157,7 @@ impl PsuGrpcAgent {
.context("failed to queue PSU gRPC agent registration")?;

let mut response_stream = client
.connect(connect_request(
ReceiverStream::new(outgoing_rx),
self.app_token.as_deref(),
)?)
.connect(connect_request(ReceiverStream::new(outgoing_rx), app_token.as_deref())?)
.await
.context("failed to start PSU gRPC agent stream")?
.into_inner();
Expand Down Expand Up @@ -259,6 +258,25 @@ impl PsuGrpcAgent {
Ok(())
}

async fn resolve_app_token(&self) -> anyhow::Result<Option<String>> {
let Some(app_token) = self.app_token.as_deref() else {
return Ok(None);
};

if app_token_secret_reference_name(app_token).is_none() {
return Ok(Some(app_token.to_owned()));
}

let worker = PowerShellWorker::new(self.conf.powershell.clone())
.context("failed to initialize PSU PowerShell worker for gRPC AppToken secret resolution")?;

worker
.resolve_app_token(app_token)
.await
.map(Some)
.context("failed to resolve PSU gRPC AppToken secret")
}

fn create_registration_message(&self, powershell_version: String) -> AgentMessage {
AgentMessage {
request_id: Uuid::new_v4().simple().to_string(),
Expand Down Expand Up @@ -437,4 +455,41 @@ mod tests {
"Bearer token"
);
}

#[tokio::test]
async fn literal_app_token_does_not_require_secret_resolution() {
let agent = PsuGrpcAgent::new(dto::PsuGrpcAgentConf {
enabled: true,
server_url: Some("http://localhost:5000".parse().expect("server URL")),
agent_id: Some("agent-01".to_owned()),
display_name: None,
app_token: Some("literal-token".to_owned()),
hubs: Vec::new(),
powershell: dto::PsuPowerShellConf {
executable_path: Some("missing-pwsh".into()),
..dto::PsuPowerShellConf::default()
},
})
.expect("create agent");

let app_token = agent.resolve_app_token().await.expect("resolve AppToken");

assert_eq!(app_token.as_deref(), Some("literal-token"));
}

#[test]
fn empty_app_token_is_ignored() {
let agent = PsuGrpcAgent::new(dto::PsuGrpcAgentConf {
enabled: true,
server_url: Some("http://localhost:5000".parse().expect("server URL")),
agent_id: Some("agent-01".to_owned()),
display_name: None,
app_token: Some(" ".to_owned()),
hubs: Vec::new(),
powershell: dto::PsuPowerShellConf::default(),
})
.expect("create agent");

assert!(agent.app_token.is_none());
}
}
Loading