Skip to content
Merged
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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ Stable options are:
(e.g., Chrome, macOS). Therefore, we strongly recommend using certificates that comply with
these standards.

- **CredSspCertificateFile** (_FilePath_): Path to the certificate to use for CredSSP credential injection.
When set, this certificate is presented to the client during proxy-based credential injection instead
of the main TLS certificate. If unset, the TLS certificate is used.

- **CredSspPrivateKeyFile** (_FilePath_): Path to the private key to use for CredSSP credential injection.
Required when **CredSspCertificateFile** is set (unless using a PFX/PKCS12 file which bundles the private key).

- **CredSspPrivateKeyPassword** (_String_): Password to use for decrypting the CredSSP private key or PFX/PKCS12 file.

- **Listeners** (_Array_): Array of listener URLs.

Each element has the following schema:
Expand Down
12 changes: 12 additions & 0 deletions config_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@
"$ref": "#/definitions/CertStoreLocation",
"description": "Location of the System Certificate Store to use for TLS."
},
"CredSspCertificateFile": {
"type": "string",
"description": "Path to the certificate to use for CredSSP credential injection (overrides TLS certificate)."
},
"CredSspPrivateKeyFile": {
"type": "string",
"description": "Path to the private key to use for CredSSP credential injection (overrides TLS private key)."
},
"CredSspPrivateKeyPassword": {
"type": "string",
"description": "Password to use for decrypting the CredSSP private key or PFX/PKCS12 file."
},
"Listeners": {
"type": "array",
"items": {
Expand Down
52 changes: 52 additions & 0 deletions devolutions-gateway/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ pub struct Conf {
pub job_queue_database: Utf8PathBuf,
pub traffic_audit_database: Utf8PathBuf,
pub tls: Option<Tls>,
pub credssp_tls: Option<Tls>,
pub provisioner_public_key: PublicKey,
pub provisioner_private_key: Option<PrivateKey>,
pub sub_provisioner_public_key: Option<Subkey>,
Expand Down Expand Up @@ -701,6 +702,41 @@ impl Conf {
anyhow::bail!("TLS usage implied but TLS configuration is missing (certificate or/and private key)");
}

let credssp_tls = match conf_file.credssp_certificate_file.as_ref() {
None => None,
Some(certificate_path) => {
let (certificates, private_key) = match certificate_path.extension() {
Some("pfx" | "p12") => {
read_pfx_file(certificate_path, conf_file.credssp_private_key_password.as_ref())
.context("read CredSSP PFX/PKCS12 file")?
}
None | Some(_) => {
let certificates =
read_rustls_certificate_file(certificate_path).context("read CredSSP certificate")?;

let private_key = conf_file
.credssp_private_key_file
.as_ref()
.context("CredSSP private key file is missing")?
.pipe_deref(read_rustls_priv_key_file)
.context("read CredSSP private key")?;

(certificates, private_key)
}
};

let cert_source = crate::tls::CertificateSource::External {
certificates,
private_key,
};

let credssp_tls =
Tls::init(cert_source, strict_checks).context("failed to initialize CredSSP TLS configuration")?;

Some(credssp_tls)
}
};

let data_dir = get_data_dir();

let log_file = conf_file
Expand Down Expand Up @@ -780,6 +816,7 @@ impl Conf {
job_queue_database,
traffic_audit_database,
tls,
credssp_tls,
provisioner_public_key,
provisioner_private_key,
sub_provisioner_public_key,
Expand Down Expand Up @@ -1480,6 +1517,18 @@ pub mod dto {
#[serde(skip_serializing_if = "Option::is_none")]
pub tls_verify_strict: Option<bool>,

/// Certificate to use for CredSSP credential injection (overrides TLS certificate)
#[serde(skip_serializing_if = "Option::is_none")]
pub credssp_certificate_file: Option<Utf8PathBuf>,

/// Private key to use for CredSSP credential injection (overrides TLS private key)
#[serde(skip_serializing_if = "Option::is_none")]
pub credssp_private_key_file: Option<Utf8PathBuf>,

/// Password to use for decrypting the CredSSP private key
#[serde(skip_serializing_if = "Option::is_none")]
pub credssp_private_key_password: Option<Password>,

/// Listeners to launch at startup
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub listeners: Vec<ListenerConf>,
Expand Down Expand Up @@ -1563,6 +1612,9 @@ pub mod dto {
tls_certificate_store_name: None,
tls_certificate_store_location: None,
tls_verify_strict: Some(true),
credssp_certificate_file: None,
credssp_private_key_file: None,
credssp_private_key_password: None,
listeners: vec![
ListenerConf {
internal_url: "tcp://*:8181".to_owned(),
Expand Down
3 changes: 2 additions & 1 deletion devolutions-gateway/src/rd_clean_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,9 @@ async fn handle_with_credential_injection(
credential_entry: Arc<CredentialEntry>,
) -> anyhow::Result<()> {
let tls_conf = conf
.tls
.credssp_tls
.as_ref()
.or(conf.tls.as_ref())
.context("TLS configuration required for credential injection feature")?;

let gateway_hostname = conf.hostname.clone();
Expand Down
3 changes: 2 additions & 1 deletion devolutions-gateway/src/rdp_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ where
} = proxy;

let tls_conf = conf
.tls
.credssp_tls
.as_ref()
.or(conf.tls.as_ref())
.context("TLS configuration required for credential injection feature")?;
let gateway_hostname = conf.hostname.clone();

Expand Down
18 changes: 18 additions & 0 deletions devolutions-gateway/tests/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ fn hub_sample() -> Sample {
tls_certificate_store_location: None,
tls_certificate_store_name: None,
tls_verify_strict: Some(true),
credssp_certificate_file: None,
credssp_private_key_file: None,
credssp_private_key_password: None,
listeners: vec![
ListenerConf {
internal_url: "tcp://*:8080".to_owned(),
Expand Down Expand Up @@ -128,6 +131,9 @@ fn legacy_sample() -> Sample {
tls_certificate_store_location: None,
tls_certificate_store_name: None,
tls_verify_strict: None,
credssp_certificate_file: None,
credssp_private_key_file: None,
credssp_private_key_password: None,
listeners: vec![],
subscriber: None,
log_file: Some("/path/to/log/file.log".into()),
Expand Down Expand Up @@ -173,6 +179,9 @@ fn system_store_sample() -> Sample {
tls_certificate_store_location: Some(CertStoreLocation::LocalMachine),
tls_certificate_store_name: Some("My".to_owned()),
tls_verify_strict: None,
credssp_certificate_file: None,
credssp_private_key_file: None,
credssp_private_key_password: None,
listeners: vec![],
subscriber: None,
log_file: None,
Expand Down Expand Up @@ -234,6 +243,9 @@ fn standalone_custom_auth_sample() -> Sample {
tls_certificate_store_location: None,
tls_certificate_store_name: None,
tls_verify_strict: None,
credssp_certificate_file: None,
credssp_private_key_file: None,
credssp_private_key_password: None,
listeners: vec![
ListenerConf {
internal_url: "tcp://*:8080".to_owned(),
Expand Down Expand Up @@ -311,6 +323,9 @@ fn standalone_no_auth_sample() -> Sample {
tls_certificate_store_location: None,
tls_certificate_store_name: None,
tls_verify_strict: None,
credssp_certificate_file: None,
credssp_private_key_file: None,
credssp_private_key_password: None,
listeners: vec![
ListenerConf {
internal_url: "tcp://*:8080".to_owned(),
Expand Down Expand Up @@ -388,6 +403,9 @@ fn proxy_sample() -> Sample {
tls_certificate_store_location: None,
tls_certificate_store_name: None,
tls_verify_strict: None,
credssp_certificate_file: None,
credssp_private_key_file: None,
credssp_private_key_password: None,
listeners: vec![
ListenerConf {
internal_url: "tcp://*:8080".to_owned(),
Expand Down
1 change: 1 addition & 0 deletions docs/COOKBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ tail -f /var/log/devolutions-agent/agent.log | grep -i "download\|proxy"
- Generate a session token for the RDP session.
- Generate a scope token for the preflight API.
- Configure the TLS certificate and private key.
Optionally, configure `CredSspCertificateFile` and `CredSspPrivateKeyFile` to use a different certificate for CredSSP.
- Run the Devolutions Gateway.
- We’ll assume it runs on localhost, and it listens for HTTP on 7171 and TCP on 8181.
- Adjust to your needs.
Expand Down