Skip to content
Closed
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
32 changes: 32 additions & 0 deletions dag/extdeps/tailscale/acl.dag
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ data tag_dashboard: NonEmptyStr = "tag:dashboard"
data dashboard_tcp_port: NonEmptyStr = "tcp:3737"
data ssh_tcp_port: NonEmptyStr = "tcp:22"

// dusk-1 is the operator's desktop (WSL) serving an OpenAI-compatible inference API while the
// Sparks are sold. It is an untagged member device, so no tag grant reaches it; this admits only
// the dashboard plane to its API port. tag:ci stays quarantined to the dashboard port.
data dusk_serving_ip: NonEmptyStr = "100.127.39.77"
data dusk_serving_api_tcp_port: NonEmptyStr = "tcp:8080"

type TailscaleAclSelector
= AclTag { label: NonEmptyStr }
| AclAutogroup { group: NonEmptyStr }
Expand Down Expand Up @@ -158,12 +164,21 @@ fn tag_dashboard_to_ci_ssh_grant() -> TailscaleGrant {
}
}

fn tag_dashboard_to_dusk_serving_api_grant() -> TailscaleGrant {
TailscaleGrant {
src: [sel_tag_dashboard()],
dst: [sel_ipv4(address: dusk_serving_ip)],
ip: [sel_proto_port(spec: dusk_serving_api_tcp_port)],
}
}

fn operator_tailscale_acl_grants() -> List<TailscaleGrant> {
[
member_baseline_grant(),
tag_ci_quarantine_grant(),
tag_dashboard_self_grant(),
tag_dashboard_to_ci_ssh_grant(),
tag_dashboard_to_dusk_serving_api_grant(),
]
}

Expand Down Expand Up @@ -304,10 +319,27 @@ fn ssh_dashboard_reaches_ci_root_and_nonroot(policy: TailscaleAclPolicy) -> Bool
)
}

fn grant_reaches_dusk_serving(g: TailscaleGrant) -> Bool {
selector_list_contains(items: g.dst, needle: sel_ipv4(address: dusk_serving_ip))
}
Comment on lines +322 to +324

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include wildcard destinations in the dusk reachability check

grant_reaches_dusk_serving recognizes only a literal 100.127.39.77 destination, but the existing member_baseline_grant uses dst: [sel_wildcard()] and ip: [sel_wildcard()]. Tailscale defines a destination wildcard as matching all tailnet IP addresses and an ip wildcard as matching every protocol and port (Tailscale selector reference), so the emitted policy lets every member reach dusk-1 on all ports while this new security-spine predicate reports that only tag:dashboard can reach TCP 8080. Account for wildcard destinations (and any modeled ranges containing this address) in this reachability check and its negative witness.

Useful? React with 👍 / 👎.


fn dusk_serving_reached_by_dashboard_api_only(policy: TailscaleAclPolicy) -> Bool {
let tagged = policy.grants |> filter(g => grant_reaches_dusk_serving(g: g))
match first(tagged) {
Present { value: g } =>
(length(tagged) == 1)
&& (g.src == [sel_tag_dashboard()])
&& (length(g.dst) == 1)
&& (g.ip == [sel_proto_port(spec: dusk_serving_api_tcp_port)])
Absent => false
}
}

fn operator_acl_security_spine_holds(policy: TailscaleAclPolicy) -> Bool {
tag_ci_quarantine_holds(policy: policy)
&& tag_dashboard_admin_plane_holds(policy: policy)
&& dashboard_cutover_ip_is_transient_in_tag_ci_grant(policy: policy)
&& ssh_members_reach_ci_nonroot_only(policy: policy)
&& ssh_dashboard_reaches_ci_root_and_nonroot(policy: policy)
&& dusk_serving_reached_by_dashboard_api_only(policy: policy)
}
18 changes: 17 additions & 1 deletion dag/test/claim/tailscale_acl_witness_test.dag
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ data tailscale_acl_reference_fixture_path: String = "dag/test/fixture/tailscale_

fn witness_operator_policy_row_counts() -> Bool {
let policy = operator_tailscale_acl_policy
(length(policy.grants) == 4)
(length(policy.grants) == 5)
&& (length(policy.ssh) == 3)
&& (length(policy.node_attrs) == 1)
&& (length(policy.tag_owners) == 2)
Expand Down Expand Up @@ -43,6 +43,21 @@ fn witness_emit_matches_reference_fixture() -> Bool {
read.success && (expected_tailscale_acl_json() == read.content)
}

fn witness_dusk_serving_api_only() -> Bool {
let wider = TailscaleAclPolicy {
grants: operator_tailscale_acl_policy.grants.append(TailscaleGrant {
src: [sel_tag_ci()],
dst: [sel_ipv4(address: dusk_serving_ip)],
ip: [sel_wildcard()],
}),
ssh: operator_tailscale_acl_policy.ssh,
node_attrs: operator_tailscale_acl_policy.node_attrs,
tag_owners: operator_tailscale_acl_policy.tag_owners,
}
dusk_serving_reached_by_dashboard_api_only(policy: operator_tailscale_acl_policy)
&& !dusk_serving_reached_by_dashboard_api_only(policy: wider)
}

fn witness_dashboard_tag_not_ci_in_acl_model() -> Bool {
tag_dashboard != tag_ci
&& !string_contains(s: tag_dashboard, pattern: "tag:ci")
Expand All @@ -58,4 +73,5 @@ test fn tailscale_acl_witnesses() -> Bool {
&& witness_security_spine()
&& witness_emit_matches_reference_fixture()
&& witness_dashboard_tag_not_ci_in_acl_model()
&& witness_dusk_serving_api_only()
}
2 changes: 1 addition & 1 deletion dag/test/fixture/tailscale_acl_reference.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"grants": [{"src": ["autogroup:member"], "dst": ["*"], "ip": ["*"]}, {"src": ["tag:ci"], "dst": ["100.65.94.55", "tag:dashboard"], "ip": ["tcp:3737"]}, {"src": ["tag:dashboard"], "dst": ["tag:dashboard"], "ip": ["*"]}, {"src": ["tag:dashboard"], "dst": ["tag:ci"], "ip": ["tcp:22"]}], "ssh": [{"action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:self"], "users": ["autogroup:nonroot", "root"]}, {"action": "accept", "src": ["autogroup:member"], "dst": ["tag:ci"], "users": ["autogroup:nonroot"]}, {"action": "accept", "src": ["tag:dashboard"], "dst": ["tag:ci"], "users": ["autogroup:nonroot", "root"]}], "nodeAttrs": [{"target": ["autogroup:member"], "attr": ["funnel"]}], "tagOwners": {"tag:ci": ["autogroup:admin"], "tag:dashboard": ["autogroup:admin"]}}
{"grants": [{"src": ["autogroup:member"], "dst": ["*"], "ip": ["*"]}, {"src": ["tag:ci"], "dst": ["100.65.94.55", "tag:dashboard"], "ip": ["tcp:3737"]}, {"src": ["tag:dashboard"], "dst": ["tag:dashboard"], "ip": ["*"]}, {"src": ["tag:dashboard"], "dst": ["tag:ci"], "ip": ["tcp:22"]}, {"src": ["tag:dashboard"], "dst": ["100.127.39.77"], "ip": ["tcp:8080"]}], "ssh": [{"action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:self"], "users": ["autogroup:nonroot", "root"]}, {"action": "accept", "src": ["autogroup:member"], "dst": ["tag:ci"], "users": ["autogroup:nonroot"]}, {"action": "accept", "src": ["tag:dashboard"], "dst": ["tag:ci"], "users": ["autogroup:nonroot", "root"]}], "nodeAttrs": [{"target": ["autogroup:member"], "attr": ["funnel"]}], "tagOwners": {"tag:ci": ["autogroup:admin"], "tag:dashboard": ["autogroup:admin"]}}
Loading