source: advance the checked projection
Source-Sha: 194a59adfd6c901635da7a9c18253bb2210e2426 Policy-Sha: b9b0be0ee74e55f561a803b8aef6be3c2134f5a83b46816a069a372f2eb04f4d Tree-Digest: 4697f2e281c1c6ecafff17cf6eeb391a0a1ac0fa6e5fc429f5b473f4579c252c
This commit is contained in:
parent
0401c76c79
commit
ffdccba10f
42 changed files with 2516 additions and 554 deletions
|
|
@ -49,6 +49,8 @@ ApplicationWindow {
|
|||
property string pendingProcessQuery: ""
|
||||
property var approval: parse(machine.approvalJson, {
|
||||
})
|
||||
property var fleetJoin: parse(machine.joinJson, {
|
||||
})
|
||||
// The capabilities that turn "a process" into "a process that can rewrite the
|
||||
// machine". Tinted so a wall of chips still reads at a glance.
|
||||
readonly property var sharpCapabilities: ["CAP_SYS_ADMIN", "CAP_SYS_MODULE", "CAP_SYS_RAWIO", "CAP_SYS_PTRACE", "CAP_SYS_BOOT", "CAP_BPF", "CAP_NET_ADMIN", "CAP_NET_RAW", "CAP_DAC_READ_SEARCH", "CAP_SETUID", "CAP_SETGID"]
|
||||
|
|
@ -1781,7 +1783,6 @@ ApplicationWindow {
|
|||
font.pixelSize: Theme.fontMeta
|
||||
Layout.preferredWidth: 100
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3004,6 +3005,243 @@ ApplicationWindow {
|
|||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: fleetJoinCard
|
||||
|
||||
visible: machine.enrollment === "standalone" || machine.enrollment === "joining fleet"
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: visible ? fleetJoinColumn.implicitHeight + 30 : 0
|
||||
color: Theme.surface
|
||||
radius: Theme.radius
|
||||
border.width: 1
|
||||
border.color: machine.enrollment === "joining fleet" ? Theme.warn : Theme.divider
|
||||
|
||||
ColumnLayout {
|
||||
id: fleetJoinColumn
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 15
|
||||
spacing: 10
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
text: "JOIN THIS MACHINE TO A FLEET"
|
||||
color: Theme.text
|
||||
font.pixelSize: 11
|
||||
font.weight: 750
|
||||
font.letterSpacing: 1
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Keep this machine's identity. Replace its local signing authority with the fleet Server key."
|
||||
color: Theme.dim
|
||||
font.pixelSize: 10
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: machine.enrollment === "joining fleet" ? "CONVERGING" : "ONE WAY"
|
||||
color: machine.enrollment === "joining fleet" ? Theme.warn : Theme.red
|
||||
font.pixelSize: 9
|
||||
font.weight: 750
|
||||
font.letterSpacing: 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Button {
|
||||
text: machine.joinSeed.length ? "New host seed" : "Create host seed"
|
||||
enabled: !machine.joinRunning && machine.enrollment === "standalone"
|
||||
onClicked: machine.prepareFleetJoin()
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.pressed ? Theme.hover : Theme.raised
|
||||
border.width: 1
|
||||
border.color: Theme.divider
|
||||
radius: Theme.radiusSm
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: parent.text
|
||||
color: parent.enabled ? Theme.text : Theme.faint
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: 11
|
||||
font.weight: 650
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: fleetJoinSeed
|
||||
|
||||
Layout.fillWidth: true
|
||||
text: machine.joinSeed
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
placeholderText: "Create the host proof seed first"
|
||||
color: Theme.cyan
|
||||
placeholderTextColor: Theme.faint
|
||||
font.family: Theme.mono
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.raised
|
||||
border.width: 1
|
||||
border.color: fleetJoinSeed.activeFocus ? Theme.cyan : Theme.divider
|
||||
radius: Theme.radiusSm
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: "In RedFlag Web, create a one-seat fleet join key using this host seed. Then return here with the key."
|
||||
color: Theme.faint
|
||||
font.pixelSize: 10
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.fillWidth: true
|
||||
columns: 2
|
||||
columnSpacing: 8
|
||||
rowSpacing: 4
|
||||
|
||||
Text {
|
||||
text: "SERVER URL"
|
||||
color: Theme.faint
|
||||
font.pixelSize: 9
|
||||
font.weight: 700
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "ONE-SEAT JOIN KEY"
|
||||
color: Theme.faint
|
||||
font.pixelSize: 9
|
||||
font.weight: 700
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: fleetJoinServer
|
||||
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "https://redflag.example"
|
||||
color: Theme.text
|
||||
placeholderTextColor: Theme.faint
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.raised
|
||||
border.width: 1
|
||||
border.color: fleetJoinServer.activeFocus ? Theme.red : Theme.divider
|
||||
radius: Theme.radiusSm
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: fleetJoinToken
|
||||
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "Paste the join key"
|
||||
echoMode: TextInput.Password
|
||||
color: Theme.text
|
||||
placeholderTextColor: Theme.faint
|
||||
font.family: Theme.mono
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.raised
|
||||
border.width: 1
|
||||
border.color: fleetJoinToken.activeFocus ? Theme.red : Theme.divider
|
||||
radius: Theme.radiusSm
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: fleetJoinConfirm
|
||||
|
||||
enabled: machine.enrollment === "standalone" && !machine.joinRunning
|
||||
text: "Retire this machine's standalone signing key and local mint permission."
|
||||
|
||||
contentItem: Text {
|
||||
text: parent.text
|
||||
color: parent.checked ? Theme.warn : Theme.dim
|
||||
font.pixelSize: 11
|
||||
leftPadding: parent.indicator.width + parent.spacing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: app.fleetJoin.error || (app.fleetJoin.restart_required ? "Fleet authority installed. The Agent is restarting into fleet mode." : "")
|
||||
color: app.fleetJoin.error ? Theme.bad : Theme.good
|
||||
font.pixelSize: 10
|
||||
font.family: Theme.mono
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Button {
|
||||
text: machine.joinRunning ? "Joining…" : machine.enrollment === "joining fleet" ? "Restarting Agent…" : "Join fleet"
|
||||
enabled: machine.enrollment === "standalone" && !machine.joinRunning && fleetJoinConfirm.checked && fleetJoinSeed.text.length > 0 && fleetJoinServer.text.trim().length > 0 && fleetJoinToken.text.trim().length > 0
|
||||
onClicked: machine.joinFleet(fleetJoinServer.text.trim(), fleetJoinToken.text.trim(), fleetJoinSeed.text.trim())
|
||||
|
||||
background: Rectangle {
|
||||
color: !parent.enabled ? Theme.raised : parent.pressed ? Qt.darker(Theme.red, 1.15) : Theme.red
|
||||
radius: Theme.radiusSm
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: parent.text
|
||||
color: parent.enabled ? "white" : Theme.faint
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: 11
|
||||
font.weight: 650
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onJoinFinished(success) {
|
||||
if (success) {
|
||||
fleetJoinToken.text = "";
|
||||
fleetJoinConfirm.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
target: machine
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 100
|
||||
|
|
|
|||
|
|
@ -69,11 +69,14 @@ pub mod ffi {
|
|||
#[qproperty(bool, scan_status_loading)]
|
||||
#[qproperty(QString, operation_message)]
|
||||
#[qproperty(QString, approval_json)]
|
||||
#[qproperty(QString, join_seed)]
|
||||
#[qproperty(QString, join_json)]
|
||||
#[qproperty(i32, software_count)]
|
||||
#[qproperty(i32, software_explicit_count)]
|
||||
#[qproperty(i32, software_dependency_count)]
|
||||
#[qproperty(i32, software_foreign_count)]
|
||||
#[qproperty(bool, approval_running)]
|
||||
#[qproperty(bool, join_running)]
|
||||
#[qproperty(bool, telemetry_loading)]
|
||||
#[qproperty(bool, overview_loading)]
|
||||
#[qproperty(bool, processes_loading)]
|
||||
|
|
@ -106,6 +109,15 @@ pub mod ffi {
|
|||
available_version: &QString,
|
||||
override_reason: &QString,
|
||||
);
|
||||
#[qinvokable]
|
||||
fn prepare_fleet_join(self: Pin<&mut Machine>);
|
||||
#[qinvokable]
|
||||
fn join_fleet(
|
||||
self: Pin<&mut Machine>,
|
||||
server_url: &QString,
|
||||
registration_token: &QString,
|
||||
seed: &QString,
|
||||
);
|
||||
|
||||
#[qsignal]
|
||||
fn telemetry_updated(self: Pin<&mut Machine>);
|
||||
|
|
@ -125,6 +137,10 @@ pub mod ffi {
|
|||
fn operation_finished(self: Pin<&mut Machine>, success: bool);
|
||||
#[qsignal]
|
||||
fn approval_finished(self: Pin<&mut Machine>, success: bool);
|
||||
#[qsignal]
|
||||
fn join_prepared(self: Pin<&mut Machine>, success: bool);
|
||||
#[qsignal]
|
||||
fn join_finished(self: Pin<&mut Machine>, success: bool);
|
||||
}
|
||||
|
||||
impl cxx_qt::Threading for Machine {}
|
||||
|
|
@ -181,11 +197,14 @@ pub struct MachineRust {
|
|||
scan_status_loading: bool,
|
||||
operation_message: QString,
|
||||
approval_json: QString,
|
||||
join_seed: QString,
|
||||
join_json: QString,
|
||||
software_count: i32,
|
||||
software_explicit_count: i32,
|
||||
software_dependency_count: i32,
|
||||
software_foreign_count: i32,
|
||||
approval_running: bool,
|
||||
join_running: bool,
|
||||
telemetry_loading: bool,
|
||||
overview_loading: bool,
|
||||
processes_loading: bool,
|
||||
|
|
@ -245,11 +264,14 @@ impl Default for MachineRust {
|
|||
scan_status_loading: false,
|
||||
operation_message: QString::default(),
|
||||
approval_json: QString::from("{}"),
|
||||
join_seed: QString::default(),
|
||||
join_json: QString::from("{}"),
|
||||
software_count: 0,
|
||||
software_explicit_count: 0,
|
||||
software_dependency_count: 0,
|
||||
software_foreign_count: 0,
|
||||
approval_running: false,
|
||||
join_running: false,
|
||||
telemetry_loading: false,
|
||||
overview_loading: false,
|
||||
processes_loading: false,
|
||||
|
|
@ -427,6 +449,74 @@ impl ffi::Machine {
|
|||
self.approval_finished(success);
|
||||
}
|
||||
|
||||
pub fn prepare_fleet_join(mut self: Pin<&mut Self>) {
|
||||
if self.join_running {
|
||||
return;
|
||||
}
|
||||
self.as_mut().set_join_running(true);
|
||||
self.as_mut().set_join_json(QString::from("{}"));
|
||||
let thread = self.qt_thread();
|
||||
std::thread::spawn(move || {
|
||||
let result = local_api::post(
|
||||
"/v1/actions/prepare-fleet-join",
|
||||
&Value::Object(Default::default()),
|
||||
);
|
||||
let _ = thread.queue(move |machine| machine.apply_fleet_join_preparation(result));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn join_fleet(
|
||||
mut self: Pin<&mut Self>,
|
||||
server_url: &QString,
|
||||
registration_token: &QString,
|
||||
seed: &QString,
|
||||
) {
|
||||
if self.join_running {
|
||||
return;
|
||||
}
|
||||
let request = serde_json::json!({
|
||||
"server_url": server_url.to_string(),
|
||||
"registration_token": registration_token.to_string(),
|
||||
"totp_seed": seed.to_string(),
|
||||
});
|
||||
self.as_mut().set_join_running(true);
|
||||
self.as_mut().set_join_json(QString::from("{}"));
|
||||
let thread = self.qt_thread();
|
||||
std::thread::spawn(move || {
|
||||
let result = local_api::post("/v1/actions/join-fleet", &request);
|
||||
let _ = thread.queue(move |machine| machine.apply_fleet_join(result));
|
||||
});
|
||||
}
|
||||
|
||||
fn apply_fleet_join_preparation(mut self: Pin<&mut Self>, result: Result<Value, String>) {
|
||||
self.as_mut().set_join_running(false);
|
||||
let success = result.is_ok();
|
||||
let payload = match result {
|
||||
Ok(value) => {
|
||||
self.as_mut().set_join_seed(qtext(&value, "totp_seed"));
|
||||
value
|
||||
}
|
||||
Err(error) => serde_json::json!({ "error": error }),
|
||||
};
|
||||
self.as_mut().set_join_json(json_string(&payload));
|
||||
self.join_prepared(success);
|
||||
}
|
||||
|
||||
fn apply_fleet_join(mut self: Pin<&mut Self>, result: Result<Value, String>) {
|
||||
self.as_mut().set_join_running(false);
|
||||
let success = result.is_ok();
|
||||
let payload = match result {
|
||||
Ok(value) => {
|
||||
self.as_mut().set_enrollment(QString::from("joining fleet"));
|
||||
self.as_mut().set_join_seed(QString::default());
|
||||
value
|
||||
}
|
||||
Err(error) => serde_json::json!({ "error": error }),
|
||||
};
|
||||
self.as_mut().set_join_json(json_string(&payload));
|
||||
self.join_finished(success);
|
||||
}
|
||||
|
||||
fn apply_telemetry(mut self: Pin<&mut Self>, result: Result<Value, String>) {
|
||||
self.as_mut().set_telemetry_loading(false);
|
||||
match result {
|
||||
|
|
@ -501,6 +591,14 @@ impl ffi::Machine {
|
|||
.or_else(|| text(info, "device_type"))
|
||||
.unwrap_or_else(|| "Computer".into());
|
||||
let registered = data.identity["registered"].as_bool().unwrap_or(false);
|
||||
let enrollment = match data.identity["mode"].as_str() {
|
||||
Some("fleet") => "fleet enrolled",
|
||||
Some("standalone") => "standalone",
|
||||
Some("joining") => "joining fleet",
|
||||
Some("incomplete") => "incomplete",
|
||||
_ if registered => "fleet enrolled",
|
||||
_ => "standalone",
|
||||
};
|
||||
let updates = data.updates["updates"]
|
||||
.as_array()
|
||||
.cloned()
|
||||
|
|
@ -536,11 +634,7 @@ impl ffi::Machine {
|
|||
.set_agent_version(qtext(&data.identity, "agent_version"));
|
||||
self.as_mut()
|
||||
.set_agent_status(qtext(&data.status, "agent_status"));
|
||||
self.as_mut().set_enrollment(QString::from(if registered {
|
||||
"fleet enrolled"
|
||||
} else {
|
||||
"standalone"
|
||||
}));
|
||||
self.as_mut().set_enrollment(QString::from(enrollment));
|
||||
self.as_mut().set_uptime(qtext(info, "uptime"));
|
||||
self.as_mut()
|
||||
.set_process_count(info["running_processes"].as_i64().unwrap_or(0) as i32);
|
||||
|
|
|
|||
Loading…
Reference in a new issue