forked from keylime/keylime
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_tpm_engine.py
More file actions
335 lines (269 loc) · 15.2 KB
/
Copy pathtest_tpm_engine.py
File metadata and controls
335 lines (269 loc) · 15.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
"""
Unit tests for keylime.verification.tpm_engine module
"""
# pylint: disable=protected-access,attribute-defined-outside-init
# Testing requires access to protected methods and dynamic test attributes
import unittest
from unittest.mock import MagicMock, PropertyMock, patch
from keylime.failure import Component, Failure
from keylime.verification.tpm_engine import TPMEngine
class TestTPMEngineProcessResults(unittest.TestCase):
"""Tests for TPMEngine._process_results method behavior"""
def setUp(self):
"""Set up test fixtures"""
# Create mock attestation with required attributes
self.mock_attestation = MagicMock()
self.mock_attestation.agent_id = "test-agent-123"
self.mock_attestation.evaluation = None
self.mock_attestation.system_info = None
self.mock_attestation.evidence = MagicMock()
# Mock evidence view to return empty results
mock_view = MagicMock()
mock_view.filter.return_value = mock_view
mock_view.result.return_value = []
self.mock_attestation.evidence.view.return_value = mock_view
# Create mock agent with required attributes
self.mock_agent = MagicMock()
self.mock_agent.agent_id = "test-agent-123"
self.mock_agent.attestation_count = 0
self.mock_agent.accept_attestations = True
self.mock_agent.ima_policy = None
self.mock_agent.mb_policy = None
self.mock_agent.tpm_policy = {}
self.mock_agent.ak_tpm = "mock-ak"
self.mock_agent.accept_tpm_signing_algs = []
self.mock_agent.accept_tpm_hash_algs = []
self.mock_attestation.agent = self.mock_agent
# Create engine instance
self.engine = TPMEngine(self.mock_attestation)
@patch("keylime.verification.tpm_engine.config.get")
@patch("keylime.verification.tpm_engine.config.getboolean")
@patch("keylime.verification.tpm_engine.AuthSession.delete_active_session_for_agent")
def test_process_results_push_mode_failure_preserves_session(
self, mock_delete_session, mock_getboolean, mock_config_get
):
"""In push mode, failed attestation should preserve auth session"""
# Configure mocks
mock_config_get.return_value = "push" # Push mode
mock_getboolean.return_value = True # extend_token_on_attestation
# Create a failure
failure = Failure(Component.QUOTE_VALIDATION)
failure.add_event("test_failure", "Test failure", True)
# Mock all required methods and properties
with patch.object(self.engine, "_select_ima_log_item", return_value=None):
with patch.object(self.engine, "_determine_failure_reason"):
with patch.object(type(self.engine), "attest_state", new_callable=PropertyMock, return_value=None):
with patch.object(
type(self.engine), "failure_reason", new_callable=PropertyMock, return_value=None
):
# Process results with failure
self.engine._process_results(failure)
# In push mode, session should NOT be deleted
mock_delete_session.assert_not_called()
# accept_attestations should remain True in push mode
self.assertTrue(self.mock_agent.accept_attestations)
# Evaluation should be set to fail
self.assertEqual(self.mock_attestation.evaluation, "fail")
@patch("keylime.verification.tpm_engine.config.get")
@patch("keylime.verification.tpm_engine.config.getboolean")
@patch("keylime.verification.tpm_engine.AuthSession.delete_active_session_for_agent")
def test_process_results_pull_mode_failure_deletes_session(
self, mock_delete_session, mock_getboolean, mock_config_get
):
"""In pull mode, failed attestation should delete auth session"""
# Configure mocks
mock_config_get.return_value = "pull" # Pull mode
mock_getboolean.return_value = True # extend_token_on_attestation
# Create a failure
failure = Failure(Component.QUOTE_VALIDATION)
failure.add_event("test_failure", "Test failure", True)
# Mock all required methods and properties
with patch.object(self.engine, "_select_ima_log_item", return_value=None):
with patch.object(self.engine, "_determine_failure_reason"):
with patch.object(type(self.engine), "attest_state", new_callable=PropertyMock, return_value=None):
with patch.object(
type(self.engine), "failure_reason", new_callable=PropertyMock, return_value=None
):
# Process results with failure
self.engine._process_results(failure)
# In pull mode, session SHOULD be deleted
mock_delete_session.assert_called_once_with("test-agent-123")
# accept_attestations should be set to False in pull mode
self.assertFalse(self.mock_agent.accept_attestations)
# Evaluation should be set to fail
self.assertEqual(self.mock_attestation.evaluation, "fail")
@patch("keylime.verification.tpm_engine.config.getboolean")
def test_process_results_success_extends_token(self, mock_getboolean):
"""Successful attestation should extend auth token"""
# Configure mocks
mock_getboolean.return_value = True # extend_token_on_attestation
# Mock _extend_auth_token
with patch.object(self.engine, "_extend_auth_token") as mock_extend:
with patch.object(self.engine, "_select_ima_log_item", return_value=None):
with patch.object(self.engine, "_determine_failure_reason"):
with patch.object(type(self.engine), "attest_state", new_callable=PropertyMock, return_value=None):
with patch.object(
type(self.engine), "failure_reason", new_callable=PropertyMock, return_value=None
):
# Process results with no failure (success)
self.engine._process_results(None)
# Token should be extended on success
mock_extend.assert_called_once()
# Attestation count should increment
self.assertEqual(self.mock_agent.attestation_count, 1)
# Evaluation should be set to pass
self.assertEqual(self.mock_attestation.evaluation, "pass")
@patch("keylime.verification.tpm_engine.config.getboolean")
def test_process_results_success_no_extend_when_disabled(self, mock_getboolean):
"""Successful attestation should not extend token when disabled"""
# Configure mocks
mock_getboolean.return_value = False # extend_token_on_attestation disabled
# Mock _extend_auth_token
with patch.object(self.engine, "_extend_auth_token") as mock_extend:
with patch.object(self.engine, "_select_ima_log_item", return_value=None):
with patch.object(self.engine, "_determine_failure_reason"):
with patch.object(type(self.engine), "attest_state", new_callable=PropertyMock, return_value=None):
with patch.object(
type(self.engine), "failure_reason", new_callable=PropertyMock, return_value=None
):
# Process results with no failure (success)
self.engine._process_results(None)
# Token should NOT be extended when disabled
mock_extend.assert_not_called()
# Attestation count should still increment
self.assertEqual(self.mock_agent.attestation_count, 1)
# Evaluation should be set to pass
self.assertEqual(self.mock_attestation.evaluation, "pass")
@patch("keylime.verification.tpm_engine.config.get")
@patch("keylime.verification.tpm_engine.config.getboolean")
@patch("keylime.verification.tpm_engine.AuthSession.delete_active_session_for_agent")
def test_process_results_failure_does_not_extend_token(
self, _mock_delete_session, mock_getboolean, mock_config_get
):
"""Failed attestation should never extend auth token"""
# Configure mocks for push mode
mock_config_get.return_value = "push"
mock_getboolean.return_value = True # extend_token_on_attestation enabled
# Create a failure
failure = Failure(Component.QUOTE_VALIDATION)
failure.add_event("test_failure", "Test failure", True)
# Mock _extend_auth_token
with patch.object(self.engine, "_extend_auth_token") as mock_extend:
with patch.object(self.engine, "_select_ima_log_item", return_value=None):
with patch.object(self.engine, "_determine_failure_reason"):
with patch.object(type(self.engine), "attest_state", new_callable=PropertyMock, return_value=None):
with patch.object(
type(self.engine), "failure_reason", new_callable=PropertyMock, return_value=None
):
# Process results with failure
self.engine._process_results(failure)
# Token should NOT be extended on failure, even if enabled
mock_extend.assert_not_called()
# Evaluation should be set to fail
self.assertEqual(self.mock_attestation.evaluation, "fail")
@patch("keylime.verification.tpm_engine.config.getboolean")
@patch("keylime.verification.tpm_engine.agent_util.is_push_mode_agent")
@patch("keylime.verification.tpm_engine.push_agent_monitor.schedule_agent_timeout")
def test_process_results_success_recovers_from_timeout(
self, mock_schedule_timeout, mock_is_push_mode, mock_getboolean
):
"""Successful attestation should re-enable accept_attestations for PUSH mode recovery"""
# Configure mocks
mock_getboolean.return_value = True # extend_token_on_attestation
mock_is_push_mode.return_value = True # PUSH mode agent
# Simulate agent that has timed out (accept_attestations=False)
self.mock_agent.accept_attestations = False
# Mock _extend_auth_token
with patch.object(self.engine, "_extend_auth_token"):
with patch.object(self.engine, "_select_ima_log_item", return_value=None):
with patch.object(self.engine, "_determine_failure_reason"):
with patch.object(type(self.engine), "attest_state", new_callable=PropertyMock, return_value=None):
with patch.object(
type(self.engine), "failure_reason", new_callable=PropertyMock, return_value=None
):
# Process results with no failure (successful attestation)
self.engine._process_results(None)
# accept_attestations should be re-enabled for recovery
self.assertTrue(
self.mock_agent.accept_attestations,
"Successful attestation should re-enable accept_attestations to allow PUSH mode agents to recover from timeout",
)
# Consecutive failures should be reset
self.assertEqual(self.mock_agent.consecutive_attestation_failures, 0)
# Attestation count should increment
self.assertEqual(self.mock_agent.attestation_count, 1)
# Timeout should be scheduled for PUSH mode
mock_schedule_timeout.assert_called_once_with("test-agent-123")
# Evaluation should be set to pass
self.assertEqual(self.mock_attestation.evaluation, "pass")
class TestTPMEngineFreshPolicy(unittest.TestCase):
"""Tests for TPMEngine fresh policy loading"""
def setUp(self):
"""Set up test fixtures"""
# Create mock attestation with required attributes
self.mock_attestation = MagicMock()
self.mock_attestation.agent_id = "test-agent-123"
self.mock_attestation.system_info = None
self.mock_attestation.evidence = MagicMock()
# Mock evidence view to return empty results
mock_view = MagicMock()
mock_view.filter.return_value = mock_view
mock_view.result.return_value = []
self.mock_attestation.evidence.view.return_value = mock_view
# Create mock agent with IMA policy
self.mock_agent = MagicMock()
self.mock_agent.agent_id = "test-agent-123"
self.mock_agent.ima_policy = MagicMock()
self.mock_agent.ima_policy.ima_policy = {"verification-keys": "old-keys", "version": 1}
self.mock_agent.ima_policy_id = 42
self.mock_agent.mb_policy = None
self.mock_agent.tpm_policy = {}
self.mock_agent.ak_tpm = "mock-ak"
self.mock_agent.accept_tpm_signing_algs = []
self.mock_agent.accept_tpm_hash_algs = []
self.mock_attestation.agent = self.mock_agent
# Create engine instance
self.engine = TPMEngine(self.mock_attestation)
@patch("keylime.models.base.db_manager.session_context")
def test_ima_policy_reload_bypasses_cache(self, mock_session_context):
"""Test that ima_policy property reloads fresh policy from database"""
# Create a fresh agent with updated policy ID
fresh_agent = MagicMock()
fresh_agent.ima_policy_id = 43 # Different ID indicates policy was updated
# Mock database query result with new policy
mock_result = MagicMock()
mock_result.fetchone.return_value = ['{"verification-keys": "new-keys", "version": 2}']
mock_session = MagicMock()
mock_session.execute.return_value = mock_result
mock_session_context.return_value.__enter__.return_value = mock_session
# Simulate verify_evidence() reloading the agent
self.engine._fresh_agent = fresh_agent # pylint: disable=attribute-defined-outside-init
# Access the ima_policy property
policy = self.engine.ima_policy
# Verify the fresh policy from database was used, not the cached one
self.assertEqual(policy["verification-keys"], "new-keys")
self.assertEqual(policy["version"], 2)
# Verify raw SQL query was executed to bypass ORM cache
mock_session.execute.assert_called_once()
def test_ima_policy_uses_cached_when_no_fresh_agent(self):
"""Test that ima_policy falls back to cached policy when no fresh agent"""
# Access the ima_policy property without setting _fresh_agent
policy = self.engine.ima_policy
# Verify the cached policy from the original agent was used
self.assertEqual(policy["verification-keys"], "old-keys")
self.assertEqual(policy["version"], 1)
def test_agent_property_uses_fresh_agent_when_available(self):
"""Test that agent property returns fresh agent when available"""
# Create a fresh agent
fresh_agent = MagicMock()
fresh_agent.agent_id = "test-agent-123"
fresh_agent.attestation_count = 5 # Different from original
# Simulate verify_evidence() reloading the agent
self.engine._fresh_agent = fresh_agent # pylint: disable=attribute-defined-outside-init
# Access the agent property
agent = self.engine.agent
# Verify the fresh agent was returned
self.assertEqual(agent.attestation_count, 5)
self.assertIs(agent, fresh_agent)
if __name__ == "__main__":
unittest.main()