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
1 change: 1 addition & 0 deletions ares-cli/src/ops/inject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ pub(crate) async fn ops_inject_trust(
direction,
trust_type: trust_type.clone(),
sid_filtering,
security_identifier: None,
};

let added = reader.add_trusted_domain(&mut conn, &trust).await?;
Expand Down
4 changes: 4 additions & 0 deletions ares-cli/src/orchestrator/automation/trust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2642,6 +2642,7 @@ mod tests {
direction: "bidirectional".into(),
trust_type: "forest".into(),
sid_filtering: true,
security_identifier: None,
};
let s = state_with_trust("fabrikam.local", trust);
assert!(is_filtered_inter_forest_trust(
Expand All @@ -2659,6 +2660,7 @@ mod tests {
direction: "bidirectional".into(),
trust_type: "forest".into(),
sid_filtering: false,
security_identifier: None,
};
let s = state_with_trust("fabrikam.local", trust);
assert!(!is_filtered_inter_forest_trust(
Expand Down Expand Up @@ -2696,6 +2698,7 @@ mod tests {
direction: "bidirectional".into(),
trust_type: "parent_child".into(),
sid_filtering: false,
security_identifier: None,
};
let s = state_with_trust("contoso.local", parent_trust);
// Target fabrikam.local has no metadata — try the forge.
Expand All @@ -2716,6 +2719,7 @@ mod tests {
direction: "bidirectional".into(),
trust_type: "forest".into(),
sid_filtering: true,
security_identifier: None,
};
let s = state_with_trust("fabrikam.local", target_trust);
assert!(is_filtered_inter_forest_trust(
Expand Down
1 change: 1 addition & 0 deletions ares-cli/src/orchestrator/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,7 @@ mod tests {
direction: "bidirectional".to_string(),
trust_type: trust_type.to_string(),
sid_filtering: false,
security_identifier: None,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ mod tests {
direction: "bidirectional".to_string(),
trust_type: "forest".to_string(),
sid_filtering: true,
security_identifier: None,
}
}

Expand Down
59 changes: 59 additions & 0 deletions ares-cli/src/orchestrator/state/publishing/entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,25 @@ impl SharedState {
let added = reader.add_trusted_domain(&mut conn, &trust).await?;
if added {
let domain_key = trust.domain.to_lowercase();
// Capture the SID *before* moving `trust` into the map. Upserting
// domain_sids from trust-enum data is the load-bearing step that
// lets `auto_trust_follow` pass its parent-SID gate on hardened
// 2019+ parent DCs where the post-hoc SAMR / null-session lsaquery
// fallbacks (in `golden_ticket::resolve_domain_sid`) are blocked.
let trust_sid = trust.security_identifier.clone();
{
let mut state = self.inner.write().await;
state.trusted_domains.insert(domain_key.clone(), trust);
if let Some(ref sid) = trust_sid {
state.domain_sids.insert(domain_key.clone(), sid.clone());
}
}
if let Some(sid) = trust_sid {
// Persist to redis so a replayed/reloaded operation inherits
// the SID — mirrors the persistence path used after a SAMR
// lookup succeeds in resolve_domain_sid.
let mut conn2 = queue.connection();
let _ = reader.set_domain_sid(&mut conn2, &domain_key, &sid).await;
}
// Also promote the foreign domain into state.domains so the
// per-domain automations pick it up.
Expand Down Expand Up @@ -542,6 +558,7 @@ mod tests {
direction: "bidirectional".to_string(),
trust_type: "forest".to_string(),
sid_filtering: false,
security_identifier: None,
}
}

Expand Down Expand Up @@ -818,6 +835,48 @@ mod tests {
assert_eq!(t.trust_type, "forest");
}

#[tokio::test]
async fn publish_trust_info_upserts_domain_sid_when_carried() {
// When the trust enum captured securityIdentifier, publish_trust_info
// must mirror it into state.domain_sids so `auto_trust_follow` passes
// its parent-SID gate without needing the SAMR/lsaquery fallbacks.
// This is the load-bearing wiring for the child→parent forge path.
let state = SharedState::new("op-sid".to_string());
let q = mock_queue();

let mut trust = make_trust("contoso.local");
trust.security_identifier = Some("S-1-5-21-1111111111-2222222222-3333333333".into());
let added = state.publish_trust_info(&q, trust).await.unwrap();
assert!(added);

let s = state.inner.read().await;
assert_eq!(
s.domain_sids.get("contoso.local").map(String::as_str),
Some("S-1-5-21-1111111111-2222222222-3333333333"),
"domain_sids must be populated from the trust's security_identifier"
);
}

#[tokio::test]
async fn publish_trust_info_no_sid_leaves_domain_sids_empty() {
// Legacy trust enum runs (no securityIdentifier) must not corrupt
// domain_sids — we leave the slot for `golden_ticket::resolve_domain_sid`
// to fill via SAMR/lsaquery.
let state = SharedState::new("op-nosid".to_string());
let q = mock_queue();

let trust = make_trust("fabrikam.local");
assert!(trust.security_identifier.is_none());
let added = state.publish_trust_info(&q, trust).await.unwrap();
assert!(added);

let s = state.inner.read().await;
assert!(
!s.domain_sids.contains_key("fabrikam.local"),
"missing SID must NOT insert a domain_sids entry"
);
}

#[test]
fn same_domain_is_same_forest() {
assert!(are_in_same_forest("contoso.local", "contoso.local"));
Expand Down
15 changes: 15 additions & 0 deletions ares-core/src/models/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ mod tests {
direction: "bidirectional".to_string(),
trust_type: "parent_child".to_string(),
sid_filtering: false,
security_identifier: None,
};
assert!(t.is_parent_child());
assert!(!t.is_cross_forest());
Expand All @@ -268,6 +269,7 @@ mod tests {
direction: "outbound".to_string(),
trust_type: "forest".to_string(),
sid_filtering: true,
security_identifier: None,
};
assert!(t.is_cross_forest());
assert!(!t.is_parent_child());
Expand All @@ -281,6 +283,7 @@ mod tests {
direction: "inbound".to_string(),
trust_type: "external".to_string(),
sid_filtering: false,
security_identifier: None,
};
assert!(t.is_cross_forest());
}
Expand All @@ -293,6 +296,7 @@ mod tests {
direction: String::new(),
trust_type: "unknown".to_string(),
sid_filtering: false,
security_identifier: None,
};
assert!(!t.is_cross_forest());
assert!(!t.is_parent_child());
Expand Down Expand Up @@ -467,6 +471,7 @@ mod tests {
direction: "bidirectional".to_string(),
trust_type: "parent_child".to_string(),
sid_filtering: true,
security_identifier: None,
};
let json = serde_json::to_string(&trust).unwrap();
let deser: TrustInfo = serde_json::from_str(&json).unwrap();
Expand Down Expand Up @@ -532,6 +537,16 @@ pub struct TrustInfo {
/// Whether SID filtering is active (blocks RID < 1000 across forest trusts).
#[serde(default)]
pub sid_filtering: bool,
/// Domain SID of the trusted partner, in canonical S-1-5-21-X-Y-Z form
/// when the LDAP `securityIdentifier` attribute was captured by
/// `enumerate_domain_trusts`. Carrying this on the trust object lets the
/// orchestrator pre-populate `state.domain_sids` for the partner without
/// a separate authenticated SAMR lookup against the foreign DC — that
/// lookup is the gate that previously blocked child→parent forge dispatch
/// on hardened (2019+) parent DCs where cross-realm NTLM is rejected and
/// null-session lsaquery is disabled.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub security_identifier: Option<String>,
}

impl TrustInfo {
Expand Down
1 change: 1 addition & 0 deletions ares-core/src/state/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,7 @@ mod tests {
direction: "bidirectional".to_string(),
trust_type: trust_type.to_string(),
sid_filtering: false,
security_identifier: None,
}
}

Expand Down
1 change: 1 addition & 0 deletions ares-llm/src/routing/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ mod tests {
direction: "bidirectional".to_string(),
trust_type: "forest".to_string(),
sid_filtering: true,
security_identifier: None,
},
);
assert!(is_valid_credential_for_domain(
Expand Down
Loading
Loading