Skip to content

Commit 7f93f8b

Browse files
authored
Syncing recent changes. (#1027)
* Making monitoring server IPv4-compatible. * Fixing issues with MySQL and 0 seconsd from epoch timestamps (MySQL expects timestamps starting from 1 second from epoch). * MySQL optimisations to prevent locking issues in client_paths table. * UIv2 work.
1 parent 3928b69 commit 7f93f8b

File tree

26 files changed

+323
-121
lines changed

26 files changed

+323
-121
lines changed

grr/server/grr_response_server/bin/worker_test.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ def handle(l):
4545

4646
results = data_store.REL_DB.ReadClientStats(
4747
client_id=client_id,
48-
min_timestamp=rdfvalue.RDFDatetime.FromSecondsSinceEpoch(0),
49-
max_timestamp=rdfvalue.RDFDatetime.Now())
48+
min_timestamp=data_store.REL_DB.MinTimestamp(),
49+
max_timestamp=rdfvalue.RDFDatetime.Now(),
50+
)
5051
self.assertLen(results, 1)
5152
stats = results[0]
5253

grr/server/grr_response_server/client_index.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ def _NormalizeKeyword(self, keyword):
5252

5353
def _AnalyzeKeywords(self, keywords):
5454
"""Extracts a start time from a list of keywords if present."""
55-
start_time = rdfvalue.RDFDatetime.Now() - rdfvalue.Duration.From(
56-
180, rdfvalue.DAYS)
55+
start_time = max(
56+
rdfvalue.RDFDatetime.Now() - rdfvalue.Duration.From(180, rdfvalue.DAYS),
57+
data_store.REL_DB.MinTimestamp(),
58+
)
5759
filtered_keywords = []
5860

5961
for k in keywords:

grr/server/grr_response_server/databases/db.py

Lines changed: 51 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,16 @@ class Database(metaclass=abc.ABCMeta):
578578
def Now(self) -> rdfvalue.RDFDatetime:
579579
"""Retrieves current time as reported by the database."""
580580

581+
# Different DB engines might make different assumptions about what a valid
582+
# minimal timestamp is.
583+
# For example, MySQL doesn't handle sub second fractional timestamps well:
584+
# Per https://dev.mysql.com/doc/refman/8.0/en/datetime.html:
585+
# "the range for TIMESTAMP values is '1970-01-01 00:00:01.000000' to
586+
# '2038-01-19 03:14:07.999999'".
587+
@abc.abstractmethod
588+
def MinTimestamp(self) -> rdfvalue.RDFDatetime:
589+
"""Returns minimal timestamp allowed by the DB."""
590+
581591
@abc.abstractmethod
582592
def WriteArtifact(self, artifact):
583593
"""Writes new artifact to the database.
@@ -3062,6 +3072,9 @@ def __init__(self, delegate: Database):
30623072
def Now(self) -> rdfvalue.RDFDatetime:
30633073
return self.delegate.Now()
30643074

3075+
def MinTimestamp(self) -> rdfvalue.RDFDatetime:
3076+
return self.delegate.MinTimestamp()
3077+
30653078
def WriteArtifact(self, artifact):
30663079
precondition.AssertType(artifact, rdf_artifacts.Artifact)
30673080
if not artifact.name:
@@ -3185,7 +3198,7 @@ def WriteClientSnapshotHistory(self, clients):
31853198
def ReadClientSnapshotHistory(self, client_id, timerange=None):
31863199
precondition.ValidateClientId(client_id)
31873200
if timerange is not None:
3188-
_ValidateTimeRange(timerange)
3201+
self._ValidateTimeRange(timerange)
31893202

31903203
return self.delegate.ReadClientSnapshotHistory(
31913204
client_id, timerange=timerange)
@@ -3205,7 +3218,7 @@ def ReadClientStartupInfo(self,
32053218
def ReadClientStartupInfoHistory(self, client_id, timerange=None):
32063219
precondition.ValidateClientId(client_id)
32073220
if timerange is not None:
3208-
_ValidateTimeRange(timerange)
3221+
self._ValidateTimeRange(timerange)
32093222

32103223
return self.delegate.ReadClientStartupInfoHistory(
32113224
client_id, timerange=timerange)
@@ -3250,7 +3263,7 @@ def ListClientsForKeywords(
32503263
keywords = set(keywords)
32513264

32523265
if start_time:
3253-
_ValidateTimestamp(start_time)
3266+
self._ValidateTimestamp(start_time)
32543267

32553268
result = self.delegate.ListClientsForKeywords(
32563269
keywords, start_time=start_time)
@@ -3324,12 +3337,12 @@ def ReadClientStats(
33243337
if min_timestamp is None:
33253338
min_timestamp = rdfvalue.RDFDatetime.Now() - CLIENT_STATS_RETENTION
33263339
else:
3327-
_ValidateTimestamp(min_timestamp)
3340+
self._ValidateTimestamp(min_timestamp)
33283341

33293342
if max_timestamp is None:
33303343
max_timestamp = rdfvalue.RDFDatetime.Now()
33313344
else:
3332-
_ValidateTimestamp(max_timestamp)
3345+
self._ValidateTimestamp(max_timestamp)
33333346

33343347
return self.delegate.ReadClientStats(client_id, min_timestamp,
33353348
max_timestamp)
@@ -3475,7 +3488,7 @@ def ReadPathInfo(self, client_id, path_type, components, timestamp=None):
34753488
_ValidatePathComponents(components)
34763489

34773490
if timestamp is not None:
3478-
_ValidateTimestamp(timestamp)
3491+
self._ValidateTimestamp(timestamp)
34793492

34803493
return self.delegate.ReadPathInfo(
34813494
client_id, path_type, components, timestamp=timestamp)
@@ -3525,7 +3538,7 @@ def FindPathInfoByPathID(self, client_id, path_type, path_id, timestamp=None):
35253538
precondition.ValidateClientId(client_id)
35263539

35273540
if timestamp is not None:
3528-
_ValidateTimestamp(timestamp)
3541+
self._ValidateTimestamp(timestamp)
35293542

35303543
return self.delegate.FindPathInfoByPathID( # pytype: disable=attribute-error
35313544
client_id, path_type, path_id, timestamp=timestamp)
@@ -3596,7 +3609,7 @@ def WriteUserNotification(self, notification):
35963609
def ReadUserNotifications(self, username, state=None, timerange=None):
35973610
_ValidateUsername(username)
35983611
if timerange is not None:
3599-
_ValidateTimeRange(timerange)
3612+
self._ValidateTimeRange(timerange)
36003613
if state is not None:
36013614
_ValidateNotificationState(state)
36023615

@@ -3759,7 +3772,7 @@ def ReadCronJobRuns(self, job_id):
37593772
return self.delegate.ReadCronJobRuns(job_id)
37603773

37613774
def DeleteOldCronJobRuns(self, cutoff_timestamp):
3762-
_ValidateTimestamp(cutoff_timestamp)
3775+
self._ValidateTimestamp(cutoff_timestamp)
37633776
return self.delegate.DeleteOldCronJobRuns(cutoff_timestamp)
37643777

37653778
def WriteHashBlobReferences(self, references_by_hash):
@@ -3880,10 +3893,10 @@ def UpdateFlow(self,
38803893
precondition.AssertType(client_crash_info, rdf_client.ClientCrash)
38813894
if processing_since != Database.unchanged:
38823895
if processing_since is not None:
3883-
_ValidateTimestamp(processing_since)
3896+
self._ValidateTimestamp(processing_since)
38843897
if processing_deadline != Database.unchanged:
38853898
if processing_deadline is not None:
3886-
_ValidateTimestamp(processing_deadline)
3899+
self._ValidateTimestamp(processing_deadline)
38873900
return self.delegate.UpdateFlow(
38883901
client_id,
38893902
flow_id,
@@ -4487,6 +4500,33 @@ def ReadBlobEncryptionKeys(
44874500

44884501
return self.delegate.ReadBlobEncryptionKeys(blob_ids)
44894502

4503+
# Minimal allowed timestamp is DB-specific. Thus the validation code for
4504+
# timestamps is DB-specific as well.
4505+
def _ValidateTimeRange(
4506+
self, timerange: Tuple[rdfvalue.RDFDatetime, rdfvalue.RDFDatetime]
4507+
):
4508+
"""Parses a timerange argument and always returns non-None timerange."""
4509+
if len(timerange) != 2:
4510+
raise ValueError("Timerange should be a sequence with 2 items.")
4511+
4512+
(start, end) = timerange
4513+
precondition.AssertOptionalType(start, rdfvalue.RDFDatetime)
4514+
precondition.AssertOptionalType(end, rdfvalue.RDFDatetime)
4515+
if start is not None:
4516+
self._ValidateTimestamp(start)
4517+
if end is not None:
4518+
self._ValidateTimestamp(end)
4519+
4520+
# Minimal allowed timestamp is DB-specific. Thus the validation code for
4521+
# timestamps is DB-specific as well.
4522+
def _ValidateTimestamp(self, timestamp: rdfvalue.RDFDatetime):
4523+
precondition.AssertType(timestamp, rdfvalue.RDFDatetime)
4524+
if timestamp < self.delegate.MinTimestamp():
4525+
raise ValueError(
4526+
"Timestamp is less than the minimal timestamp allowed by the DB: "
4527+
f"{timestamp} < {self.delegate.MinTimestamp()}."
4528+
)
4529+
44904530

44914531
def _ValidateEnumType(value, expected_enum_type):
44924532
if value not in expected_enum_type.reverse_enum:
@@ -4606,35 +4646,10 @@ def _ValidateNotificationState(notification_state):
46064646
raise ValueError("notification_state can't be STATE_UNSET")
46074647

46084648

4609-
def _ValidateTimeRange(timerange):
4610-
"""Parses a timerange argument and always returns non-None timerange."""
4611-
if len(timerange) != 2:
4612-
raise ValueError("Timerange should be a sequence with 2 items.")
4613-
4614-
(start, end) = timerange
4615-
precondition.AssertOptionalType(start, rdfvalue.RDFDatetime)
4616-
precondition.AssertOptionalType(end, rdfvalue.RDFDatetime)
4617-
4618-
4619-
def _ValidateClosedTimeRange(time_range):
4620-
"""Checks that a time-range has both start and end timestamps set."""
4621-
time_range_start, time_range_end = time_range
4622-
_ValidateTimestamp(time_range_start)
4623-
_ValidateTimestamp(time_range_end)
4624-
if time_range_start > time_range_end:
4625-
raise ValueError("Invalid time-range: %d > %d." %
4626-
(time_range_start.AsMicrosecondsSinceEpoch(),
4627-
time_range_end.AsMicrosecondsSinceEpoch()))
4628-
4629-
46304649
def _ValidateDuration(duration):
46314650
precondition.AssertType(duration, rdfvalue.Duration)
46324651

46334652

4634-
def _ValidateTimestamp(timestamp):
4635-
precondition.AssertType(timestamp, rdfvalue.RDFDatetime)
4636-
4637-
46384653
def _ValidateClientPathID(client_path_id):
46394654
precondition.AssertType(client_path_id, rdf_objects.ClientPathID)
46404655

grr/server/grr_response_server/databases/db_flows_test.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -302,12 +302,12 @@ def testFlowTimestamp(self):
302302

303303
self.db.WriteClientMetadata(client_id, fleetspeak_enabled=False)
304304

305-
before_timestamp = rdfvalue.RDFDatetime.Now()
305+
before_timestamp = self.db.Now()
306306

307307
flow_obj = rdf_flow_objects.Flow(client_id=client_id, flow_id=flow_id)
308308
self.db.WriteFlowObject(flow_obj)
309309

310-
after_timestamp = rdfvalue.RDFDatetime.Now()
310+
after_timestamp = self.db.Now()
311311

312312
flow_obj = self.db.ReadFlowObject(client_id=client_id, flow_id=flow_id)
313313
self.assertBetween(flow_obj.create_time, before_timestamp, after_timestamp)
@@ -318,13 +318,13 @@ def testFlowTimestampWithMissingCreationTime(self):
318318

319319
self.db.WriteClientMetadata(client_id, fleetspeak_enabled=False)
320320

321-
before_timestamp = rdfvalue.RDFDatetime.Now()
321+
before_timestamp = self.db.Now()
322322

323323
flow_obj = rdf_flow_objects.Flow(client_id=client_id, flow_id=flow_id)
324324
flow_obj.create_time = None
325325
self.db.WriteFlowObject(flow_obj)
326326

327-
after_timestamp = rdfvalue.RDFDatetime.Now()
327+
after_timestamp = self.db.Now()
328328

329329
flow_obj = self.db.ReadFlowObject(client_id=client_id, flow_id=flow_id)
330330
self.assertBetween(flow_obj.create_time, before_timestamp, after_timestamp)
@@ -698,7 +698,7 @@ def testProcessingInformationUpdate(self):
698698
client_id = db_test_utils.InitializeClient(self.db)
699699
flow_id = db_test_utils.InitializeFlow(self.db, client_id)
700700

701-
now = rdfvalue.RDFDatetime.Now()
701+
now = self.db.Now()
702702
deadline = now + rdfvalue.Duration.From(6, rdfvalue.HOURS)
703703
self.db.UpdateFlow(
704704
client_id,
@@ -1530,10 +1530,10 @@ def testLeaseFlowForProcessingUpdatesFlowObjects(self):
15301530
def testFlowLastUpdateTime(self):
15311531
processing_time = rdfvalue.Duration.From(60, rdfvalue.SECONDS)
15321532

1533-
t0 = rdfvalue.RDFDatetime.Now()
1533+
t0 = self.db.Now()
15341534
client_id = db_test_utils.InitializeClient(self.db)
15351535
flow_id = db_test_utils.InitializeFlow(self.db, client_id)
1536-
t1 = rdfvalue.RDFDatetime.Now()
1536+
t1 = self.db.Now()
15371537

15381538
read_flow = self.db.ReadFlowObject(client_id, flow_id)
15391539

@@ -1543,9 +1543,9 @@ def testFlowLastUpdateTime(self):
15431543
client_id, flow_id, processing_time)
15441544
self.assertBetween(flow_for_processing.last_update_time, t0, t1)
15451545

1546-
t2 = rdfvalue.RDFDatetime.Now()
1546+
t2 = self.db.Now()
15471547
self.db.ReleaseProcessedFlow(flow_for_processing)
1548-
t3 = rdfvalue.RDFDatetime.Now()
1548+
t3 = self.db.Now()
15491549

15501550
read_flow = self.db.ReadFlowObject(client_id, flow_id)
15511551
self.assertBetween(read_flow.last_update_time, t2, t3)

grr/server/grr_response_server/databases/mem.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,6 @@ def _DeepCopy(self, obj):
160160
def Now(self) -> rdfvalue.RDFDatetime:
161161
del self # Unused.
162162
return rdfvalue.RDFDatetime.Now()
163+
164+
def MinTimestamp(self) -> rdfvalue.RDFDatetime:
165+
return rdfvalue.RDFDatetime.FromSecondsSinceEpoch(0)

grr/server/grr_response_server/databases/mysql.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,3 +594,9 @@ def Now(self, cursor: MySQLdb.cursors.Cursor) -> rdfvalue.RDFDatetime:
594594
[(timestamp,)] = cursor.fetchall()
595595

596596
return mysql_utils.TimestampToRDFDatetime(timestamp)
597+
598+
def MinTimestamp(self) -> rdfvalue.RDFDatetime:
599+
# Per https://dev.mysql.com/doc/refman/8.0/en/datetime.html:
600+
# "the range for TIMESTAMP values is '1970-01-01 00:00:01.000000' to
601+
# '2038-01-19 03:14:07.999999'".
602+
return rdfvalue.RDFDatetime.FromSecondsSinceEpoch(1)

grr/server/grr_response_server/databases/mysql_migration.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import contextlib
55
import logging
66
import os
7+
import re
78
import time
89
from typing import Callable, Optional, Sequence, Text
910

@@ -107,4 +108,23 @@ def DumpCurrentSchema(cursor: Cursor) -> Text:
107108
rows = cursor.fetchall()
108109
defs.append(rows[0][1])
109110

111+
cursor.execute("""
112+
SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS
113+
WHERE trigger_schema = (SELECT DATABASE())
114+
""")
115+
for (trigger,) in sorted(cursor.fetchall()):
116+
cursor.execute(f"SHOW CREATE TRIGGER `{trigger}`")
117+
rows = cursor.fetchall()
118+
119+
# `SHOW CREATE TRIGGER` will return the concrete definer of the trigger,
120+
# so we need to patch its output here to show the default `CURRENT_USER`.
121+
trigger_def = re.sub(
122+
r"^CREATE\s+DEFINER\s*=\s*`[^`]+`(@`[^`]+`)?\s*TRIGGER",
123+
"CREATE DEFINER = CURRENT_USER TRIGGER",
124+
rows[0][2],
125+
count=1,
126+
flags=re.DOTALL | re.MULTILINE,
127+
)
128+
defs.append(trigger_def)
129+
110130
return "\n\n".join(defs)

0 commit comments

Comments
 (0)