Skip to content

Commit 294ac3b

Browse files
committed
feat(pedm): account tracking
This commit adds tracking of user accounts and domain accounts. Changes to accounts on the system such as name change, creation, removal, and even SID change are captured. Accounts now use an internally generated stable ID. This stable ID can be used to build policies in a robust manner. The functionality has been tested with real account creation, deletion, and removal on Windows for both DB backends. This commit also includes query helpers for parametrization and bulk insertion. A basic endpoint, `/accounts`, is added for displaying info about existing accounts in a fashion similar to `Get-LocalUser`.
1 parent b7b5c63 commit 294ac3b

File tree

16 files changed

+1942
-91
lines changed

16 files changed

+1942
-91
lines changed

Cargo.lock

Lines changed: 126 additions & 71 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/devolutions-pedm/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ tokio-postgres = { version = "0.7", optional = true, features = ["with-chrono-0_
4545
bb8 = { version = "0.9", optional = true }
4646
bb8-postgres = { version = "0.9", optional = true }
4747

48+
[target.'cfg(windows)'.dependencies]
49+
windows = { version = "0.61", features = [
50+
"Win32_NetworkManagement_NetManagement",
51+
"Win32_Security_Authorization",
52+
] }
53+
windows-result = "0.3"
54+
4855
[features]
4956
default = ["libsql"]
5057
libsql = ["dep:libsql"]

crates/devolutions-pedm/build.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use std::path::Path;
2+
use std::{env, fs};
3+
4+
fn main() -> Result<(), Box<dyn std::error::Error>> {
5+
let manifest_dir = env::var("CARGO_MANIFEST_DIR")?;
6+
// This expects the PEDM crate to be in repo_root/crates/devolutions-pedm.
7+
let repo_root = Path::new(&manifest_dir)
8+
.parent()
9+
.expect("crates dir missing")
10+
.parent()
11+
.expect("repo root missing");
12+
13+
let version = fs::read_to_string(repo_root.join("VERSION"))
14+
.expect("failed to read VERSION file")
15+
.trim_end()
16+
.to_string();
17+
println!("cargo:rustc-env=VERSION={}", version);
18+
Ok(())
19+
}

crates/devolutions-pedm/schema/libsql.sql

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
/* In SQLite, we store time as an 8-byte integer (i64) with microsecond precision. This matches TIMESTAMPTZ in Postgres.
22
Use `chrono::DateTime::timestamp_micros` when inserting or fetching timestamps in Rust.
3+
4+
`valid_from` and `valid_to` are used in place of a temporal interval type.
5+
Since the special infinity value does not exist in SQLite, we use NULL. This allows for easy checking of a row validity. A row is presently valid if `valid_to` is NULL.
36
*/
47

58
CREATE TABLE IF NOT EXISTS version
69
(
710
version integer PRIMARY KEY,
811
updated_at integer NOT NULL DEFAULT (
9-
CAST(strftime('%s', 'now') AS integer) * 1000000 + CAST(strftime('%f', 'now') * 1000000 AS integer) % 1000000
12+
cast(strftime('%s', 'now') AS integer) * 1000000 + cast(strftime('%f', 'now') * 1000000 AS integer) % 1000000
1013
)
1114
);
1215

1316
CREATE TABLE IF NOT EXISTS run
1417
(
1518
id integer PRIMARY KEY,
1619
start_time integer NOT NULL DEFAULT (
17-
CAST(strftime('%s', 'now') AS integer) * 1000000 + CAST(strftime('%f', 'now') * 1000000 AS integer) % 1000000
20+
cast(strftime('%s', 'now') AS integer) * 1000000 + cast(strftime('%f', 'now') * 1000000 AS integer) % 1000000
1821
),
1922
pipe_name text NOT NULL
2023
);
@@ -23,7 +26,7 @@ CREATE TABLE IF NOT EXISTS http_request
2326
(
2427
id integer PRIMARY KEY,
2528
at integer NOT NULL DEFAULT (
26-
CAST(strftime('%s', 'now') AS integer) * 1000000 + CAST(strftime('%f', 'now') * 1000000 AS integer) % 1000000
29+
cast(strftime('%s', 'now') AS integer) * 1000000 + cast(strftime('%f', 'now') * 1000000 AS integer) % 1000000
2730
),
2831
method text NOT NULL,
2932
path text NOT NULL,
@@ -69,4 +72,71 @@ CREATE TABLE IF NOT EXISTS jit_elevation_result
6972
FOREIGN KEY (target_user_id) REFERENCES user(id)
7073
);
7174

75+
CREATE TABLE IF NOT EXISTS account_diff_request
76+
(
77+
id integer PRIMARY KEY,
78+
at integer NOT NULL DEFAULT (
79+
cast(strftime('%s', 'now') AS integer) * 1000000 +
80+
cast(strftime('%f', 'now') * 1000000 AS integer) % 1000000
81+
)
82+
);
83+
84+
CREATE TABLE IF NOT EXISTS domain
85+
(
86+
id integer PRIMARY KEY AUTOINCREMENT,
87+
subauth1 integer NOT NULL,
88+
subauth2 integer NOT NULL,
89+
subauth3 integer NOT NULL,
90+
subauth4 integer NOT NULL,
91+
CONSTRAINT unique_domain UNIQUE (subauth1, subauth2, subauth3, subauth4)
92+
);
93+
94+
CREATE TABLE IF NOT EXISTS sid
95+
(
96+
id integer PRIMARY KEY AUTOINCREMENT,
97+
domain_id integer NOT NULL REFERENCES domain (id),
98+
relative_id integer NOT NULL,
99+
CONSTRAINT unique_sid UNIQUE (domain_id, relative_id)
100+
);
101+
102+
CREATE TABLE IF NOT EXISTS account
103+
(
104+
id integer PRIMARY KEY
105+
);
106+
107+
CREATE TABLE IF NOT EXISTS account_name
108+
(
109+
id integer NOT NULL REFERENCES account (id),
110+
name text NOT NULL,
111+
valid_from integer NOT NULL DEFAULT (
112+
cast(strftime('%s', 'now') AS integer) * 1000000 +
113+
cast(strftime('%f', 'now') * 1000000 AS integer) % 1000000
114+
),
115+
valid_to integer DEFAULT NULL,
116+
PRIMARY KEY (id, valid_from)
117+
);
118+
119+
CREATE TABLE IF NOT EXISTS account_removed
120+
(
121+
id integer NOT NULL REFERENCES account (id),
122+
valid_from integer NOT NULL DEFAULT (
123+
cast(strftime('%s', 'now') AS integer) * 1000000 +
124+
cast(strftime('%f', 'now') * 1000000 AS integer) % 1000000
125+
),
126+
valid_to integer DEFAULT NULL,
127+
PRIMARY KEY (id, valid_from)
128+
);
129+
130+
CREATE TABLE IF NOT EXISTS account_sid
131+
(
132+
account_id integer NOT NULL REFERENCES account (id),
133+
sid_id integer NOT NULL REFERENCES sid (id),
134+
valid_from integer NOT NULL DEFAULT (
135+
cast(strftime('%s', 'now') AS integer) * 1000000 +
136+
cast(strftime('%f', 'now') * 1000000 AS integer) % 1000000
137+
),
138+
valid_to integer DEFAULT NULL,
139+
PRIMARY KEY (account_id, sid_id, valid_from)
140+
);
141+
72142
INSERT INTO version (version) VALUES (0) ON CONFLICT DO NOTHING;

crates/devolutions-pedm/schema/pg.sql

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
CREATE EXTENSION IF NOT EXISTS btree_gist;
2+
13
CREATE TABLE IF NOT EXISTS version
24
(
35
version smallint PRIMARY KEY,
@@ -28,4 +30,59 @@ CREATE TABLE IF NOT EXISTS elevate_tmp_request
2830
seconds int NOT NULL
2931
);
3032

33+
CREATE TABLE IF NOT EXISTS account_diff_request
34+
(
35+
id int PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
36+
at timestamptz NOT NULL DEFAULT now()
37+
);
38+
39+
CREATE TABLE IF NOT EXISTS domain
40+
(
41+
id smallint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
42+
subauth1 smallint NOT NULL,
43+
subauth2 bigint NOT NULL,
44+
subauth3 bigint NOT NULL,
45+
subauth4 bigint NOT NULL,
46+
CONSTRAINT unique_domain UNIQUE (subauth1, subauth2, subauth3, subauth4)
47+
);
48+
49+
CREATE TABLE IF NOT EXISTS sid
50+
(
51+
id smallint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
52+
domain_id smallint NOT NULL REFERENCES domain (id),
53+
relative_id smallint NOT NULL,
54+
CONSTRAINT unique_sid UNIQUE (domain_id, relative_id)
55+
);
56+
57+
CREATE TABLE IF NOT EXISTS account
58+
(
59+
id smallint PRIMARY KEY GENERATED ALWAYS AS IDENTITY
60+
);
61+
62+
CREATE TABLE IF NOT EXISTS account_name
63+
(
64+
id smallint NOT NULL REFERENCES account (id),
65+
name text NOT NULL,
66+
during tstzrange NOT NULL DEFAULT tstzrange(now(), 'infinity'),
67+
PRIMARY KEY (id, during),
68+
EXCLUDE USING gist (id WITH =, during WITH &&)
69+
);
70+
71+
CREATE TABLE IF NOT EXISTS account_removed
72+
(
73+
id smallint NOT NULL REFERENCES account (id),
74+
during tstzrange NOT NULL DEFAULT tstzrange(now(), 'infinity'),
75+
PRIMARY KEY (id, during),
76+
EXCLUDE USING gist (id WITH =, during WITH &&)
77+
);
78+
79+
CREATE TABLE IF NOT EXISTS account_sid
80+
(
81+
account_id smallint NOT NULL REFERENCES account (id),
82+
sid_id smallint NOT NULL REFERENCES sid (id),
83+
during tstzrange NOT NULL DEFAULT tstzrange(now(), 'infinity'),
84+
PRIMARY KEY (account_id, sid_id, during),
85+
EXCLUDE USING gist (account_id WITH =, sid_id WITH =, during WITH &&)
86+
);
87+
3188
INSERT INTO version (version) VALUES (0) ON CONFLICT DO NOTHING;

0 commit comments

Comments
 (0)