Skip to content

Commit

Permalink
Add Santa voting metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
np5 committed Dec 9, 2024
1 parent 8ff10d4 commit 79361f1
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 39 deletions.
96 changes: 61 additions & 35 deletions tests/santa/test_metrics_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from prometheus_client.parser import text_string_to_metric_families
from zentral.contrib.santa.models import Rule, Target
from zentral.conf import settings
from .utils import force_configuration, force_enrolled_machine, force_rule, force_target_counter
from .utils import (force_ballot, force_configuration, force_enrolled_machine, force_realm_user, force_rule,
force_target, force_target_counter)


class SantaMetricsViewsTestCase(TestCase):
Expand Down Expand Up @@ -57,6 +58,7 @@ def test_configurations_info_unknown_mode(self, warning, connection):
[], # 2nd call for the enrolled machines gauge
[], # 3rd call for the rules gauge
[], # 4th call for the targets gauge
[], # 5th call for the votes gauge
]
response = self._make_authenticated_request()
family_count = 0
Expand All @@ -69,7 +71,7 @@ def test_configurations_info_unknown_mode(self, warning, connection):
self.assertEqual(family_count, 1)
self.assertEqual(sample_count, 0)
warning.assert_called_once_with("Unknown santa configuration mode: %s", 42)
self.assertEqual(mocked_fetchall.mock_calls, [call() for _ in range(4)])
self.assertEqual(mocked_fetchall.mock_calls, [call() for _ in range(5)])

def test_enrolled_machines(self):
em_m = force_enrolled_machine(lockdown=False, santa_version="2024.5")
Expand All @@ -78,20 +80,19 @@ def test_enrolled_machines(self):
for family in text_string_to_metric_families(response.content.decode("utf-8")):
if family.name != "zentral_santa_enrolled_machines_total":
continue
else:
self.assertEqual(len(family.samples), 2)
for sample in family.samples:
self.assertEqual(sample.value, 1)
cfg_pk = int(sample.labels["cfg_pk"])
if cfg_pk == em_m.enrollment.configuration.pk:
self.assertEqual(sample.labels["mode"], "MONITOR")
self.assertEqual(sample.labels["santa_version"], "2024.5")
elif cfg_pk == em_l.enrollment.configuration.pk:
self.assertEqual(sample.labels["mode"], "LOCKDOWN")
self.assertEqual(sample.labels["santa_version"], "2024.6")
else:
raise AssertionError("Unknown enrolled machine")
break
self.assertEqual(len(family.samples), 2)
for sample in family.samples:
self.assertEqual(sample.value, 1)
cfg_pk = int(sample.labels["cfg_pk"])
if cfg_pk == em_m.enrollment.configuration.pk:
self.assertEqual(sample.labels["mode"], "MONITOR")
self.assertEqual(sample.labels["santa_version"], "2024.5")
elif cfg_pk == em_l.enrollment.configuration.pk:
self.assertEqual(sample.labels["mode"], "LOCKDOWN")
self.assertEqual(sample.labels["santa_version"], "2024.6")
else:
raise AssertionError("Unknown enrolled machine")
break
else:
raise AssertionError("could not find expected metric family")
self.assertEqual(response.status_code, 200)
Expand All @@ -105,6 +106,7 @@ def test_enrolled_machines_unknown_mode(self, warning, connection):
[(1, 42, "2024.5", 1)], # 2nd call for the enrolled machines gauge
[], # 3rd call for the rules gauge
[], # 4th call for the targets gauge
[], # 5th call for the votes gauge
]
response = self._make_authenticated_request()
family_count = 0
Expand All @@ -117,33 +119,33 @@ def test_enrolled_machines_unknown_mode(self, warning, connection):
self.assertEqual(family_count, 1)
self.assertEqual(sample_count, 0)
warning.assert_called_once_with("Unknown santa configuration mode: %s", 42)
self.assertEqual(mocked_fetchall.mock_calls, [call() for _ in range(4)])
self.assertEqual(mocked_fetchall.mock_calls, [call() for _ in range(5)])

def test_rules(self):
rules = {}
for target_type, policy in (
(Target.Type.BINARY, Rule.Policy.ALLOWLIST),
(Target.Type.BUNDLE, Rule.Policy.BLOCKLIST),
(Target.Type.CDHASH, Rule.Policy.ALLOWLIST_COMPILER),
(Target.Type.CERTIFICATE, Rule.Policy.SILENT_BLOCKLIST),
(Target.Type.TEAM_ID, Rule.Policy.ALLOWLIST),
(Target.Type.SIGNING_ID, Rule.Policy.BLOCKLIST),
for target_type, policy, is_voting_rule in (
(Target.Type.BINARY, Rule.Policy.ALLOWLIST, False),
(Target.Type.BUNDLE, Rule.Policy.BLOCKLIST, False),
(Target.Type.CDHASH, Rule.Policy.ALLOWLIST_COMPILER, False),
(Target.Type.CERTIFICATE, Rule.Policy.SILENT_BLOCKLIST, False),
(Target.Type.TEAM_ID, Rule.Policy.ALLOWLIST, True),
(Target.Type.SIGNING_ID, Rule.Policy.BLOCKLIST, False),
):
rule = force_rule(target_type=target_type, policy=policy)
rules[str(rule.configuration.pk)] = rule
response = self._make_authenticated_request()
for family in text_string_to_metric_families(response.content.decode("utf-8")):
if family.name != "zentral_santa_rules_total":
continue
else:
self.assertEqual(len(family.samples), 6)
for sample in family.samples:
self.assertEqual(sample.value, 1)
self.assertEqual(sample.labels["ruleset"], "_")
rule = rules[sample.labels["cfg_pk"]]
self.assertEqual(sample.labels["policy"], rule.policy.name)
self.assertEqual(sample.labels["target_type"], rule.target.type)
break
self.assertEqual(len(family.samples), 6)
for sample in family.samples:
self.assertEqual(sample.value, 1)
self.assertEqual(sample.labels["ruleset"], "_")
rule = rules[sample.labels["cfg_pk"]]
self.assertEqual(sample.labels["policy"], rule.policy.name)
self.assertEqual(sample.labels["target_type"], rule.target.type)
self.assertEqual(sample.labels["voting"], str(rule.is_voting_rule).lower())
break
else:
raise AssertionError("could not find expected metric family")
self.assertEqual(response.status_code, 200)
Expand All @@ -155,8 +157,9 @@ def test_rules_unknown_policy(self, warning, connection):
mocked_fetchall.side_effect = [
[], # 1st call for the configurations info
[], # 2nd call for the enrolled machines gauge
[(1, None, "BUNDLE", 42, 1)], # 3rd call with unknown policy
[(1, None, "BUNDLE", 42, False, 1)], # 3rd call with unknown policy
[], # 4th call for the targets gauge
[], # 5th call for the votes gauge
]
response = self._make_authenticated_request()
family_count = 0
Expand All @@ -169,7 +172,7 @@ def test_rules_unknown_policy(self, warning, connection):
self.assertEqual(family_count, 1)
self.assertEqual(sample_count, 0)
warning.assert_called_once_with("Unknown rule policy: %s", 42)
self.assertEqual(mocked_fetchall.mock_calls, [call() for _ in range(4)])
self.assertEqual(mocked_fetchall.mock_calls, [call() for _ in range(5)])

def test_targets(self):
target_counters = {}
Expand Down Expand Up @@ -221,3 +224,26 @@ def test_targets(self):
total_keys,
{"total", "blocked_total", "collected_total", "executed_total", "rules_total"}
)

def test_votes(self):
target = force_target()
realm, realm_user = force_realm_user()
configuration = force_configuration(voting_realm=realm)
force_ballot(target, realm_user, [(configuration, True, 1)])
response = self._make_authenticated_request()
self.assertEqual(response.status_code, 200)
for family in text_string_to_metric_families(response.content.decode("utf-8")):
if family.name != "zentral_santa_votes_total":
continue
self.assertEqual(len(family.samples), 1)
for sample in family.samples:
self.assertEqual(sample.value, 1)
self.assertEqual(sample.labels["cfg_pk"], str(configuration.pk))
self.assertEqual(sample.labels["event_target_type"], "_")
self.assertEqual(sample.labels["realm"], realm.name)
self.assertEqual(sample.labels["target_type"], target.type)
self.assertEqual(sample.labels["weight"], "1")
self.assertEqual(sample.labels["yes"], "true")
break
else:
raise AssertionError("could not find expected metric family")
35 changes: 31 additions & 4 deletions zentral/contrib/santa/metrics_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,17 @@ def add_enrolled_machines_gauge(self):

def add_rules_gauge(self):
g = Gauge('zentral_santa_rules_total', 'Zentral Santa Rules',
['cfg_pk', 'ruleset', 'target_type', 'policy'], registry=self.registry)
['cfg_pk', 'ruleset', 'target_type', 'policy', 'voting'], registry=self.registry)
query = (
"select r.configuration_id, s.name, t.type, r.policy, count(*) "
"select r.configuration_id, s.name, t.type, r.policy, r.is_voting_rule, count(*) "
"from santa_rule as r "
"left join santa_ruleset as s on (r.ruleset_id = s.id) "
"join santa_target as t on (r.target_id = t.id) "
"group by r.configuration_id, s.name, t.type, r.policy"
"group by r.configuration_id, s.name, t.type, r.policy, r.is_voting_rule"
)
with connection.cursor() as cursor:
cursor.execute(query)
for cfg_pk, ruleset, target_type, policy, count in cursor.fetchall():
for cfg_pk, ruleset, target_type, policy, is_voting_rule, count in cursor.fetchall():
try:
policy_label = Rule.Policy(policy).name
except ValueError:
Expand All @@ -76,6 +76,7 @@ def add_rules_gauge(self):
ruleset=ruleset if ruleset else "_",
target_type=target_type,
policy=policy_label,
voting=str(is_voting_rule).lower(),
).set(count)

def add_targets_gauges(self):
Expand Down Expand Up @@ -115,8 +116,34 @@ def add_targets_gauges(self):
type=result_d["target_type"]
).set(result_d[total])

def add_votes_gauge(self):
g = Gauge('zentral_santa_votes_total', 'Zentral Santa Votes',
['cfg_pk', 'realm', 'yes', 'weight', 'target_type', 'event_target_type'], registry=self.registry)
query = (
"select v.configuration_id, r.name, v.was_yes_vote, v.weight, t.type, et.type, count(*) "
"from santa_vote v "
"join santa_ballot b on (v.ballot_id = b.id) "
"left join realms_realmuser u on (b.realm_user_id = u.uuid) "
"left join realms_realm r on (u.realm_id = r.uuid) "
"join santa_target t on (b.target_id = t.id) "
"left join santa_target et on (b.event_target_id = et.id) "
"group by v.configuration_id, r.name, v.was_yes_vote, v.weight, t.type, et.type;"
)
with connection.cursor() as cursor:
cursor.execute(query)
for cfg_pk, realm_name, was_yes_vote, weight, target_type, event_target_type, count in cursor.fetchall():
g.labels(
cfg_pk=cfg_pk,
realm=realm_name if realm_name else "_",
yes=str(was_yes_vote).lower(),
weight=weight,
target_type=target_type,
event_target_type=event_target_type if event_target_type else "_",
).set(count)

def populate_registry(self):
self.add_configurations_info()
self.add_enrolled_machines_gauge()
self.add_rules_gauge()
self.add_targets_gauges()
self.add_votes_gauge()

0 comments on commit 79361f1

Please sign in to comment.