899 lines
40 KiB
Python
899 lines
40 KiB
Python
import hashlib
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import sqlite3
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
APP_DIR = ROOT / 'app'
|
|
sys.path.insert(0, str(APP_DIR))
|
|
|
|
import db_backend
|
|
import scanner_db
|
|
from scanner_db import RuntimeSafetySchemaError, ScannerDB, migrate_runtime_safety_schema
|
|
|
|
|
|
class DockerDepthExperimentSchemaTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.environment = mock.patch.dict(
|
|
os.environ,
|
|
{'SCANNER_DB_URL': '', 'DATABASE_URL': ''},
|
|
)
|
|
self.environment.start()
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.db = ScannerDB(db_path=os.path.join(self.temp.name, 'scanner.db'))
|
|
|
|
def tearDown(self):
|
|
self.db.close()
|
|
self.temp.cleanup()
|
|
self.environment.stop()
|
|
|
|
def _insert_repository(self, query, repository, resolver_state='pending'):
|
|
now = scanner_db.utc_now_iso()
|
|
cursor = self.db.conn.execute(
|
|
'''INSERT INTO target_queue(
|
|
source, platform, query, target, normalized_target, status,
|
|
resolver_state, created_at, updated_at
|
|
) VALUES ('dockerhub', 'docker', ?, ?, ?, 'deferred', ?, ?, ?)''',
|
|
(query, repository, repository, resolver_state, now, now),
|
|
)
|
|
self.db.conn.commit()
|
|
return cursor.lastrowid
|
|
|
|
def _insert_experiment(self, state='held', fenced=False):
|
|
now = scanner_db.utc_now_iso()
|
|
digest = 'a' * 64
|
|
cursor = self.db.conn.execute(
|
|
'''INSERT INTO docker_depth_experiments(
|
|
experiment_key, source, state, config_sha256,
|
|
ordered_queries_sha256, selector_version, selector_sha256,
|
|
provenance_policy_sha256, query_count, repositories_per_query,
|
|
images_per_repository, target_limit, fence_owner, fence_token,
|
|
fence_expires_at, created_at, updated_at
|
|
) VALUES (?, 'dockerhub', ?, ?, ?, 'selector-v1', ?, ?, 1, 1, 1, 1,
|
|
?, ?, ?, ?, ?)''',
|
|
(
|
|
'fixture-experiment', state, digest, digest, digest, digest,
|
|
'owner' if fenced else None, 'token' if fenced else None,
|
|
now if fenced else None, now, now,
|
|
),
|
|
)
|
|
self.db.conn.commit()
|
|
return cursor.lastrowid
|
|
|
|
def _remove_latest_marker(self):
|
|
self.db.conn.execute(
|
|
'DELETE FROM runtime_schema_migrations WHERE version = ?',
|
|
(scanner_db.PIPELINE_MIGRATION_VERSIONS[-1],),
|
|
)
|
|
self.db.conn.commit()
|
|
|
|
def test_fresh_sqlite_schema_has_every_registered_model_and_marker(self):
|
|
self.assertEqual(
|
|
scanner_db.PIPELINE_MIGRATION_VERSIONS[-1],
|
|
scanner_db.REMOTE_ASSIGNMENT_CAPACITY_MIGRATION,
|
|
)
|
|
marker = self.db.conn.execute(
|
|
'''SELECT version, code_sha256 FROM runtime_schema_migrations
|
|
WHERE version = ?''',
|
|
(scanner_db.PIPELINE_MIGRATION_VERSIONS[-1],),
|
|
).fetchone()
|
|
self.assertIsNotNone(marker)
|
|
self.assertEqual(
|
|
marker['code_sha256'],
|
|
hashlib.sha256(scanner_db.PIPELINE_SCHEMA_SQL.encode('utf-8')).hexdigest(),
|
|
)
|
|
migration_rows = self.db.conn.execute(
|
|
'SELECT version FROM runtime_schema_migrations'
|
|
).fetchall()
|
|
self.assertEqual(
|
|
{row['version'] for row in migration_rows},
|
|
set(scanner_db.PIPELINE_MIGRATION_VERSIONS),
|
|
)
|
|
|
|
for table in scanner_db.DOCKER_DEPTH_EXPERIMENT_TABLES:
|
|
with self.subTest(table=table):
|
|
specs = scanner_db.RUNTIME_TABLE_SPECS[table]
|
|
details = self.db.conn.table_column_details(table)
|
|
self.assertEqual(set(details), set(specs))
|
|
for column, (expected_type, expected_not_null) in specs.items():
|
|
self.assertTrue(
|
|
scanner_db._schema_type_matches(
|
|
details[column]['type'], expected_type, False,
|
|
),
|
|
f'{table}.{column} type',
|
|
)
|
|
self.assertEqual(
|
|
bool(details[column]['not_null']), bool(expected_not_null),
|
|
f'{table}.{column} nullability',
|
|
)
|
|
primary_key = [
|
|
column for column in specs if details[column]['primary_key']
|
|
]
|
|
self.assertEqual(primary_key, scanner_db.RUNTIME_PRIMARY_KEYS[table])
|
|
generated = scanner_db.GENERATED_ID_COLUMNS.get(table)
|
|
if generated:
|
|
self.assertTrue(scanner_db._column_generates_id(details[generated], False))
|
|
|
|
skip_columns = set(self.db.conn.table_columns(
|
|
'docker_depth_experiment_candidate_skips'
|
|
))
|
|
self.assertTrue(
|
|
{'target', 'normalized_target', 'repository'}.isdisjoint(skip_columns)
|
|
)
|
|
refund_columns = set(self.db.conn.table_columns(
|
|
'docker_depth_resolver_attempt_refunds'
|
|
))
|
|
self.assertTrue(
|
|
{'target', 'normalized_target', 'repository', 'last_error_code'}.isdisjoint(
|
|
refund_columns
|
|
)
|
|
)
|
|
disposition_columns = set(self.db.conn.table_columns(
|
|
'docker_depth_resolver_dispositions'
|
|
))
|
|
self.assertTrue(
|
|
{'target', 'normalized_target', 'repository', 'last_error_code'}.isdisjoint(
|
|
disposition_columns
|
|
)
|
|
)
|
|
|
|
policy = self.db.conn.table_column_details('target_queue_policy_events')
|
|
self.assertIn('experiment_id', policy)
|
|
self.assertFalse(policy['experiment_id']['not_null'])
|
|
self.assertTrue({
|
|
'discovery_retry_queue',
|
|
'target_queue_policy_events',
|
|
'docker_depth_experiment_candidate_skips',
|
|
}.issubset(scanner_db.DOCKER_DEPTH_EXPERIMENT_CHECK_SPECS))
|
|
for table, expected_constraints in scanner_db.DOCKER_DEPTH_EXPERIMENT_CHECK_SPECS.items():
|
|
constraints = self.db.conn.table_check_constraints(table)
|
|
for name, expression in expected_constraints.items():
|
|
with self.subTest(table=table, constraint=name):
|
|
self.assertIn(name, constraints)
|
|
self.assertTrue(constraints[name]['valid'])
|
|
self.assertEqual(
|
|
scanner_db._normalized_check_expression(
|
|
constraints[name]['expression'],
|
|
),
|
|
scanner_db._normalized_check_expression(expression),
|
|
)
|
|
hash_tables = set(scanner_db.DOCKER_DEPTH_EXPERIMENT_TABLES) | {
|
|
'discovery_retry_queue', 'target_queue_policy_events',
|
|
}
|
|
for table in hash_tables:
|
|
expressions = ' '.join(
|
|
scanner_db.DOCKER_DEPTH_EXPERIMENT_CHECK_SPECS.get(table, {}).values()
|
|
)
|
|
for column in scanner_db.RUNTIME_TABLE_SPECS[table]:
|
|
if not column.endswith('_sha256') and not (
|
|
table == 'discovery_retry_queue' and column == 'work_key'
|
|
):
|
|
continue
|
|
with self.subTest(table=table, sha256_column=column):
|
|
self.assertIn(scanner_db._sql_sha256_check(column), expressions)
|
|
self.assertTrue(self.db.runtime_safety_schema_available(), self.db.last_error)
|
|
self.assertTrue(self.db.pipeline_schema_available(), self.db.last_error)
|
|
retry_columns = self.db.conn.table_column_details('discovery_retry_queue')
|
|
self.assertIn('source_cycle_id', retry_columns)
|
|
self.assertFalse(retry_columns['source_cycle_id']['not_null'])
|
|
|
|
def test_sqlite_scarcity_cohort_migration_backfills_existing_queries(self):
|
|
table_sql = re.search(
|
|
r'CREATE TABLE IF NOT EXISTS docker_depth_experiment_queries '
|
|
r'\(.*?\n\);',
|
|
scanner_db.DOCKER_DEPTH_EXPERIMENT_SCHEMA_SQL,
|
|
re.DOTALL,
|
|
).group(0)
|
|
legacy_table_sql = table_sql.replace(
|
|
' selected_repository_count INTEGER NOT NULL DEFAULT 0,\n',
|
|
'',
|
|
)
|
|
legacy_table_sql = re.sub(
|
|
r' CONSTRAINT docker_depth_experiment_queries_selection_check '
|
|
r'CHECK \([^\n]+\),\n',
|
|
'',
|
|
legacy_table_sql,
|
|
)
|
|
self.assertNotIn('selected_repository_count', legacy_table_sql)
|
|
legacy_path = os.path.join(self.temp.name, 'legacy-scarcity.db')
|
|
legacy_db = ScannerDB(db_path=legacy_path, initialize=False)
|
|
try:
|
|
legacy_db.conn.executescript(
|
|
scanner_db.SCHEMA_SQL.replace(table_sql, legacy_table_sql)
|
|
)
|
|
now = scanner_db.utc_now_iso()
|
|
digest = '7' * 64
|
|
experiment_id = legacy_db.conn.execute(
|
|
'''INSERT INTO docker_depth_experiments(
|
|
experiment_key, source, state, config_sha256,
|
|
ordered_queries_sha256, selector_version, selector_sha256,
|
|
provenance_policy_sha256, query_count,
|
|
repositories_per_query, images_per_repository, target_limit,
|
|
created_at, updated_at
|
|
) VALUES ('sqlite-scarcity', 'dockerhub', 'held', ?, ?,
|
|
'selector-v1', ?, ?, 1, 10, 10, 1200, ?, ?)''',
|
|
(digest, digest, digest, digest, now, now),
|
|
).lastrowid
|
|
legacy_db.conn.execute(
|
|
'''INSERT INTO docker_depth_experiment_queries(
|
|
experiment_id, source, query_ordinal, query, query_sha256,
|
|
required_repository_count, created_at
|
|
) VALUES (?, 'dockerhub', 0, 'scarcity-query', ?, 7, ?)''',
|
|
(experiment_id, digest, now),
|
|
)
|
|
legacy_db.conn.commit()
|
|
|
|
self.assertTrue(migrate_runtime_safety_schema(
|
|
legacy_db, initialize_base=True,
|
|
))
|
|
self.assertTrue(migrate_runtime_safety_schema(legacy_db))
|
|
|
|
row = legacy_db.conn.execute(
|
|
'''SELECT required_repository_count, selected_repository_count
|
|
FROM docker_depth_experiment_queries
|
|
WHERE experiment_id = ? AND query_ordinal = 0''',
|
|
(experiment_id,),
|
|
).fetchone()
|
|
self.assertEqual(
|
|
(row['required_repository_count'], row['selected_repository_count']),
|
|
(7, 7),
|
|
)
|
|
self.assertIn(
|
|
'docker_depth_experiment_queries_selection_check',
|
|
legacy_db.conn.table_check_constraints(
|
|
'docker_depth_experiment_queries'
|
|
),
|
|
)
|
|
self.assertTrue(
|
|
legacy_db.runtime_safety_schema_available(), legacy_db.last_error,
|
|
)
|
|
finally:
|
|
legacy_db.close()
|
|
|
|
def test_pre_experiment_pipeline_recovery_skips_absent_binding_schema(self):
|
|
self.db.conn.execute(
|
|
'DELETE FROM runtime_schema_migrations WHERE version = ?',
|
|
(scanner_db.DOCKER_DEPTH_EXPERIMENT_MIGRATION,),
|
|
)
|
|
self.db.conn.execute('DROP TABLE docker_depth_experiment_scan_bindings')
|
|
self.db.conn.commit()
|
|
if hasattr(self.db, '_docker_depth_experiment_schema_installed_cache'):
|
|
del self.db._docker_depth_experiment_schema_installed_cache
|
|
|
|
self.assertIsNone(
|
|
self.db._lock_docker_depth_experiment_for_reservation(1)
|
|
)
|
|
self.assertIsNone(
|
|
self.db._docker_depth_binding_for_reservation_locked({'id': 1})
|
|
)
|
|
|
|
def test_migration_adds_nullable_retry_origin_cycle_and_foreign_key(self):
|
|
table_sql = re.search(
|
|
r'CREATE TABLE IF NOT EXISTS discovery_retry_queue \(.*?\n\);',
|
|
scanner_db.PIPELINE_SCHEMA_SQL,
|
|
re.DOTALL,
|
|
).group(0)
|
|
legacy_sql = table_sql.replace(' source_cycle_id INTEGER,\n', '')
|
|
legacy_sql = re.sub(
|
|
r',\n FOREIGN KEY\(source_cycle_id\) REFERENCES source_cycles\(id\)\n\);$',
|
|
'\n);',
|
|
legacy_sql,
|
|
)
|
|
self.assertNotIn('source_cycle_id', legacy_sql)
|
|
legacy_path = os.path.join(self.temp.name, 'legacy-retry.db')
|
|
connection = sqlite3.connect(legacy_path)
|
|
try:
|
|
connection.executescript(legacy_sql)
|
|
finally:
|
|
connection.close()
|
|
|
|
legacy_db = ScannerDB(db_path=legacy_path, initialize=False)
|
|
try:
|
|
self.assertTrue(migrate_runtime_safety_schema(
|
|
legacy_db, initialize_base=True,
|
|
))
|
|
details = legacy_db.conn.table_column_details('discovery_retry_queue')
|
|
self.assertIn('source_cycle_id', details)
|
|
self.assertFalse(details['source_cycle_id']['not_null'])
|
|
foreign_keys = legacy_db.conn.table_foreign_keys('discovery_retry_queue')
|
|
self.assertTrue(any(
|
|
foreign_key['columns'] == ['source_cycle_id']
|
|
and foreign_key['referenced_table'] == 'source_cycles'
|
|
and foreign_key['referenced_columns'] == ['id']
|
|
for foreign_key in foreign_keys.values()
|
|
))
|
|
marker = legacy_db.conn.execute(
|
|
'SELECT version FROM runtime_schema_migrations WHERE version = ?',
|
|
('20260910_19_docker_retry_provenance',),
|
|
).fetchone()
|
|
self.assertIsNotNone(marker)
|
|
finally:
|
|
legacy_db.close()
|
|
|
|
def test_migration_adds_reason_enum_to_empty_legacy_attribution_table(self):
|
|
table_sql = re.search(
|
|
r'CREATE TABLE IF NOT EXISTS docker_finding_layer_attributions \(.*?\n\);',
|
|
scanner_db.DOCKER_DEPTH_EXPERIMENT_SCHEMA_SQL,
|
|
re.DOTALL,
|
|
).group(0)
|
|
legacy_sql = table_sql.replace(' unattributed_reason TEXT,\n', '')
|
|
legacy_sql = re.sub(
|
|
r' CONSTRAINT docker_finding_layer_attributions_reason_check '
|
|
r'CHECK \([^\n]+\),\n',
|
|
'',
|
|
legacy_sql,
|
|
)
|
|
self.assertNotIn('unattributed_reason', legacy_sql)
|
|
self.db.conn.execute('DROP TABLE docker_finding_layer_attributions')
|
|
self.db.conn.executescript(legacy_sql)
|
|
self._remove_latest_marker()
|
|
|
|
self.assertTrue(migrate_runtime_safety_schema(self.db))
|
|
|
|
details = self.db.conn.table_column_details(
|
|
'docker_finding_layer_attributions'
|
|
)
|
|
constraints = self.db.conn.table_check_constraints(
|
|
'docker_finding_layer_attributions'
|
|
)
|
|
self.assertIn('unattributed_reason', details)
|
|
self.assertIn(
|
|
'docker_finding_layer_attributions_reason_check', constraints,
|
|
)
|
|
self.assertTrue(self.db.runtime_safety_schema_available(), self.db.last_error)
|
|
|
|
def test_registered_foreign_keys_types_and_indexes_are_strictly_validated(self):
|
|
tables = set(scanner_db.DOCKER_DEPTH_EXPERIMENT_TABLES) | {
|
|
'discovery_retry_queue', 'target_queue_policy_events',
|
|
}
|
|
for table in tables:
|
|
foreign_keys = self.db.conn.table_foreign_keys(table)
|
|
for columns, referenced_table, referenced_columns in scanner_db.REQUIRED_FOREIGN_KEYS.get(
|
|
table, ()
|
|
):
|
|
matches = [
|
|
foreign_key for foreign_key in foreign_keys.values()
|
|
if tuple(foreign_key['columns']) == columns
|
|
and foreign_key['referenced_table'] == referenced_table
|
|
and tuple(foreign_key['referenced_columns']) == referenced_columns
|
|
]
|
|
self.assertEqual(
|
|
len(matches), 1,
|
|
f'{table}{columns} -> {referenced_table}{referenced_columns}',
|
|
)
|
|
expected_actions = scanner_db._required_foreign_key_actions(table, columns)
|
|
self.assertEqual(
|
|
(matches[0]['update_action'], matches[0]['delete_action']),
|
|
expected_actions,
|
|
)
|
|
|
|
for table, name, columns, unique, predicate in scanner_db.DOCKER_DEPTH_EXPERIMENT_INDEX_SPECS:
|
|
index = self.db.conn.table_indexes(table).get(name)
|
|
self.assertIsNotNone(index, name)
|
|
self.assertEqual(index['columns'], list(columns), name)
|
|
self.assertEqual(bool(index['unique']), bool(unique), name)
|
|
self.assertEqual(
|
|
scanner_db._normalized_predicate(index['predicate']),
|
|
scanner_db._normalized_predicate(predicate),
|
|
name,
|
|
)
|
|
|
|
converted = db_backend._postgres_schema_sql(scanner_db.SCHEMA_SQL)
|
|
for table in scanner_db.DOCKER_DEPTH_EXPERIMENT_TABLES:
|
|
block = re.search(
|
|
rf'CREATE TABLE IF NOT EXISTS {table} \((.*?)\n\);',
|
|
converted,
|
|
re.DOTALL,
|
|
).group(1)
|
|
for column, (expected_type, _) in scanner_db.RUNTIME_TABLE_SPECS[table].items():
|
|
if expected_type in ('id', 'id_ref'):
|
|
self.assertRegex(block, rf'(?m)^ {column} BIGINT\b', table + '.' + column)
|
|
for table, expected_constraints in scanner_db.DOCKER_DEPTH_EXPERIMENT_CHECK_SPECS.items():
|
|
for name, expression in expected_constraints.items():
|
|
self.assertIn(
|
|
f'CONSTRAINT {name} CHECK ({expression})',
|
|
converted,
|
|
)
|
|
|
|
original = self.db.conn.table_column_details
|
|
|
|
def malformed(table):
|
|
details = original(table)
|
|
if table == 'docker_image_manifests':
|
|
details['target_queue_id'] = dict(details['target_queue_id'], type='text')
|
|
return details
|
|
|
|
with mock.patch.object(self.db.conn, 'table_column_details', side_effect=malformed):
|
|
self.assertFalse(self.db.runtime_safety_schema_available())
|
|
self.assertIn('docker_image_manifests.target_queue_id type', self.db.last_error)
|
|
|
|
original_foreign_keys = self.db.conn.table_foreign_keys
|
|
|
|
def missing_foreign_key(table):
|
|
foreign_keys = original_foreign_keys(table)
|
|
if table == 'docker_depth_experiment_scan_bindings':
|
|
return {
|
|
name: value for name, value in foreign_keys.items()
|
|
if value['columns'] != ['reservation_id']
|
|
}
|
|
return foreign_keys
|
|
|
|
with mock.patch.object(
|
|
self.db.conn, 'table_foreign_keys', side_effect=missing_foreign_key,
|
|
):
|
|
self.assertFalse(self.db.runtime_safety_schema_available())
|
|
self.assertIn(
|
|
'foreign key docker_depth_experiment_scan_bindings(reservation_id)',
|
|
self.db.last_error,
|
|
)
|
|
|
|
def wrong_foreign_key_action(table):
|
|
foreign_keys = original_foreign_keys(table)
|
|
if table == 'docker_depth_experiment_scan_bindings':
|
|
foreign_keys = {
|
|
name: dict(value)
|
|
for name, value in foreign_keys.items()
|
|
}
|
|
for foreign_key in foreign_keys.values():
|
|
if foreign_key['columns'] == ['experiment_target_id']:
|
|
foreign_key['delete_action'] = 'CASCADE'
|
|
return foreign_keys
|
|
|
|
with mock.patch.object(
|
|
self.db.conn,
|
|
'table_foreign_keys',
|
|
side_effect=wrong_foreign_key_action,
|
|
):
|
|
self.assertFalse(self.db.runtime_safety_schema_available())
|
|
self.assertIn(
|
|
'foreign key docker_depth_experiment_scan_bindings(experiment_target_id) '
|
|
'-> docker_depth_experiment_targets(id) actions ON UPDATE NO ACTION '
|
|
'ON DELETE NO ACTION',
|
|
self.db.last_error,
|
|
)
|
|
|
|
def test_every_canonical_check_is_fail_closed_when_malformed(self):
|
|
self.assertNotEqual(
|
|
scanner_db._normalized_check_expression('(a = 1 AND b = 2) OR c = 3'),
|
|
scanner_db._normalized_check_expression('a = 1 AND (b = 2 OR c = 3)'),
|
|
)
|
|
original = self.db.conn.table_check_constraints
|
|
for malformed_table, expected_constraints in (
|
|
scanner_db.DOCKER_DEPTH_EXPERIMENT_CHECK_SPECS.items()
|
|
):
|
|
for malformed_name in expected_constraints:
|
|
with self.subTest(table=malformed_table, constraint=malformed_name):
|
|
def malformed(table):
|
|
constraints = {
|
|
name: dict(value)
|
|
for name, value in original(table).items()
|
|
}
|
|
if table == malformed_table:
|
|
constraints[malformed_name]['expression'] = '1 = 1'
|
|
return constraints
|
|
|
|
with mock.patch.object(
|
|
self.db.conn,
|
|
'table_check_constraints',
|
|
side_effect=malformed,
|
|
):
|
|
self.assertFalse(self.db.runtime_safety_schema_available())
|
|
self.assertIn(malformed_name, self.db.last_error)
|
|
|
|
def test_sqlite_constraints_enforce_hash_state_cap_and_fence_shape(self):
|
|
experiment_id = self._insert_experiment()
|
|
malformed_updates = (
|
|
('config_sha256 = ?', ('g' * 64,)),
|
|
('state = ?', ('unknown',)),
|
|
('query_count = 2', ()),
|
|
('fence_owner = ?', ('owner-only',)),
|
|
)
|
|
for assignment, params in malformed_updates:
|
|
with self.subTest(assignment=assignment):
|
|
with self.assertRaises(sqlite3.IntegrityError):
|
|
self.db.conn.execute(
|
|
f'UPDATE docker_depth_experiments SET {assignment} WHERE id = ?',
|
|
params + (experiment_id,),
|
|
)
|
|
self.db.conn.rollback()
|
|
|
|
with self.assertRaisesRegex(
|
|
sqlite3.IntegrityError,
|
|
'docker_finding_layer_attributions_evidence_check',
|
|
):
|
|
self.db.conn.execute(
|
|
'''INSERT INTO docker_finding_layer_attributions(
|
|
scan_binding_id, finding_id, manifest_layer_id,
|
|
attribution_state, reported_layer_digest,
|
|
position_from_base, position_from_top, created_at
|
|
) VALUES (1, 1, 1, 'exact', 'sha256:fixture', NULL, 1, ?)''',
|
|
(scanner_db.utc_now_iso(),),
|
|
)
|
|
self.db.conn.rollback()
|
|
|
|
retry = self.db.enqueue_discovery_retry(
|
|
'dockerhub', 'constraint-query', 'b' * 64, 'ordinary', 'page', 1, 1,
|
|
error_category='network',
|
|
)
|
|
for column, malformed_hash in (
|
|
('work_key', 'A' * 64),
|
|
('policy_sha256', 'g' * 64),
|
|
):
|
|
with self.subTest(retry_hash=column), self.assertRaises(
|
|
sqlite3.IntegrityError
|
|
):
|
|
self.db.conn.execute(
|
|
f'UPDATE discovery_retry_queue SET {column} = ? WHERE id = ?',
|
|
(malformed_hash, retry['id']),
|
|
)
|
|
self.db.conn.rollback()
|
|
with self.assertRaisesRegex(
|
|
sqlite3.IntegrityError, 'discovery_retry_queue_lifecycle_check',
|
|
):
|
|
self.db.conn.execute(
|
|
"UPDATE discovery_retry_queue SET status = 'leased' WHERE id = ?",
|
|
(retry['id'],),
|
|
)
|
|
self.db.conn.rollback()
|
|
|
|
queue_id = self._insert_repository('Policy Query', 'fixture/policy')
|
|
now = scanner_db.utc_now_iso()
|
|
event = self.db.conn.execute(
|
|
'''INSERT INTO target_queue_policy_events(
|
|
queue_id, action, prior_status, next_status, source, platform,
|
|
query, reason_code, config_sha256, policy_sha256,
|
|
manifest_sha256, review_audit_sha256, experiment_id,
|
|
prior_updated_at, created_at
|
|
) VALUES (?, 'cold', 'deferred', 'cold', 'dockerhub', 'docker',
|
|
'Policy Query', 'docker_depth_experiment_hold',
|
|
?, ?, ?, ?, ?, ?, ?) RETURNING id''',
|
|
(
|
|
queue_id, 'c' * 64, 'd' * 64, 'e' * 64, 'f' * 64,
|
|
experiment_id, now, now,
|
|
),
|
|
).fetchone()
|
|
self.db.conn.commit()
|
|
for assignment, params, constraint in (
|
|
('config_sha256 = ?', ('A' * 64,), 'target_queue_policy_events_hash_check'),
|
|
(
|
|
"reason_code = 'query_not_in_canonical_policy'", (),
|
|
'target_queue_policy_events_experiment_ownership_check',
|
|
),
|
|
(
|
|
"next_status = 'pending'", (),
|
|
'target_queue_policy_events_transition_check',
|
|
),
|
|
):
|
|
with self.subTest(policy_assignment=assignment), self.assertRaisesRegex(
|
|
sqlite3.IntegrityError, constraint,
|
|
):
|
|
self.db.conn.execute(
|
|
f'UPDATE target_queue_policy_events SET {assignment} WHERE id = ?',
|
|
params + (event['id'],),
|
|
)
|
|
self.db.conn.rollback()
|
|
|
|
generic_queue_id = self._insert_repository(
|
|
'Generic Policy Query', 'fixture/generic-policy',
|
|
)
|
|
self.db.conn.execute(
|
|
'''INSERT INTO target_queue_policy_events(
|
|
queue_id, action, prior_status, next_status, source, platform,
|
|
query, reason_code, config_sha256, policy_sha256,
|
|
manifest_sha256, review_audit_sha256, prior_updated_at, created_at
|
|
) VALUES (?, 'cold', 'deferred', 'cold', 'dockerhub', 'docker',
|
|
'Generic Policy Query', 'query_not_in_canonical_policy',
|
|
?, ?, ?, ?, ?, ?)''',
|
|
(
|
|
generic_queue_id, '1' * 64, '2' * 64, '3' * 64, '4' * 64,
|
|
now, now,
|
|
),
|
|
)
|
|
self.db.conn.commit()
|
|
|
|
with self.assertRaisesRegex(
|
|
sqlite3.IntegrityError,
|
|
'docker_finding_layer_attributions_reason_check',
|
|
):
|
|
self.db.conn.execute(
|
|
'''INSERT INTO docker_finding_layer_attributions(
|
|
scan_binding_id, finding_id, attribution_state, created_at
|
|
) VALUES (1, 1, 'unattributed', ?)''',
|
|
(scanner_db.utc_now_iso(),),
|
|
)
|
|
self.db.conn.rollback()
|
|
|
|
def test_migration_rejects_malformed_sqlite_check(self):
|
|
cases = (
|
|
('docker_depth_experiments', 'docker_depth_experiments_identity_check'),
|
|
('discovery_retry_queue', 'discovery_retry_queue_lifecycle_check'),
|
|
('target_queue_policy_events', 'target_queue_policy_events_transition_check'),
|
|
)
|
|
for table, name in cases:
|
|
with self.subTest(table=table, constraint=name):
|
|
expression = scanner_db.DOCKER_DEPTH_EXPERIMENT_CHECK_SPECS[table][name]
|
|
row = self.db.conn.execute(
|
|
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
(table,),
|
|
).fetchone()
|
|
original_sql = row['sql']
|
|
expected = f'CONSTRAINT {name} CHECK ({expression})'
|
|
self.assertIn(expected, original_sql)
|
|
malformed = original_sql.replace(
|
|
expected, f'CONSTRAINT {name} CHECK (1 = 1)',
|
|
)
|
|
schema_version = self.db.conn.execute(
|
|
'PRAGMA schema_version'
|
|
).fetchone()[0]
|
|
self.db.conn.execute('PRAGMA writable_schema = ON')
|
|
self.db.conn.execute(
|
|
"UPDATE sqlite_master SET sql = ? WHERE type = 'table' AND name = ?",
|
|
(malformed, table),
|
|
)
|
|
self.db.conn.execute('PRAGMA writable_schema = OFF')
|
|
self.db.conn.execute(f'PRAGMA schema_version = {schema_version + 1}')
|
|
self.db.conn.commit()
|
|
|
|
self.assertFalse(self.db.runtime_safety_schema_available())
|
|
self.assertIn(name, self.db.last_error)
|
|
with self.assertRaisesRegex(
|
|
RuntimeSafetySchemaError, 'noncanonical check constraint',
|
|
):
|
|
migrate_runtime_safety_schema(self.db)
|
|
|
|
schema_version = self.db.conn.execute(
|
|
'PRAGMA schema_version'
|
|
).fetchone()[0]
|
|
self.db.conn.execute('PRAGMA writable_schema = ON')
|
|
self.db.conn.execute(
|
|
"UPDATE sqlite_master SET sql = ? WHERE type = 'table' AND name = ?",
|
|
(original_sql, table),
|
|
)
|
|
self.db.conn.execute('PRAGMA writable_schema = OFF')
|
|
self.db.conn.execute(f'PRAGMA schema_version = {schema_version + 1}')
|
|
self.db.conn.commit()
|
|
self.assertTrue(
|
|
self.db.runtime_safety_schema_available(), self.db.last_error,
|
|
)
|
|
|
|
def test_migration_rejects_wrong_sqlite_fk_actions(self):
|
|
original_foreign_keys = self.db.conn.table_foreign_keys
|
|
|
|
def wrong_action(foreign_table):
|
|
foreign_keys = {
|
|
key: dict(value)
|
|
for key, value in original_foreign_keys(foreign_table).items()
|
|
}
|
|
if foreign_table == 'docker_depth_experiment_scan_bindings':
|
|
for foreign_key in foreign_keys.values():
|
|
if foreign_key['columns'] == ['experiment_target_id']:
|
|
foreign_key['update_action'] = 'CASCADE'
|
|
return foreign_keys
|
|
|
|
with mock.patch.object(
|
|
self.db.conn, 'table_foreign_keys', side_effect=wrong_action,
|
|
):
|
|
with self.assertRaisesRegex(RuntimeSafetySchemaError, 'foreign key actions'):
|
|
migrate_runtime_safety_schema(self.db)
|
|
|
|
def test_migration_is_idempotent_and_repairs_registered_index_shape(self):
|
|
self.db.conn.execute('DROP TABLE docker_finding_layer_attributions')
|
|
self.db.conn.execute('DROP INDEX idx_docker_repository_provenance_eligible')
|
|
self.db.conn.execute(
|
|
'''CREATE INDEX idx_docker_repository_provenance_eligible
|
|
ON docker_repository_query_provenance(source)'''
|
|
)
|
|
self.db.conn.commit()
|
|
self.assertFalse(self.db.runtime_safety_schema_available())
|
|
self.assertIn('index idx_docker_repository_provenance_eligible', self.db.last_error)
|
|
self._remove_latest_marker()
|
|
|
|
self.assertTrue(migrate_runtime_safety_schema(self.db))
|
|
self.assertTrue(migrate_runtime_safety_schema(self.db))
|
|
self.assertTrue(self.db.runtime_safety_schema_available(), self.db.last_error)
|
|
marker_count = self.db.conn.execute(
|
|
'SELECT COUNT(*) AS count FROM runtime_schema_migrations WHERE version = ?',
|
|
(scanner_db.PIPELINE_MIGRATION_VERSIONS[-1],),
|
|
).fetchone()['count']
|
|
self.assertEqual(marker_count, 1)
|
|
index = self.db.conn.table_indexes('docker_repository_query_provenance')[
|
|
'idx_docker_repository_provenance_eligible'
|
|
]
|
|
self.assertEqual(
|
|
index['columns'],
|
|
['source', 'query', 'fresh_coverage_eligible', 'best_search_rank', 'repository_queue_id'],
|
|
)
|
|
|
|
def test_hold_snapshot_queue_scan_index_is_additive_and_repaired(self):
|
|
self.db.conn.execute('DROP INDEX idx_target_scans_queue_id')
|
|
self._remove_latest_marker()
|
|
self.db.conn.commit()
|
|
|
|
self.assertFalse(self.db.runtime_safety_schema_available())
|
|
self.assertIn('idx_target_scans_queue_id', self.db.last_error)
|
|
self.assertTrue(migrate_runtime_safety_schema(self.db))
|
|
self.assertTrue(migrate_runtime_safety_schema(self.db))
|
|
self.assertTrue(self.db.runtime_safety_schema_available(), self.db.last_error)
|
|
index = self.db.conn.table_indexes('target_scans')[
|
|
'idx_target_scans_queue_id'
|
|
]
|
|
self.assertEqual(index['columns'], ['queue_id'])
|
|
|
|
def test_legacy_first_inserter_seed_is_bounded_idempotent_and_ineligible(self):
|
|
legacy_id = self._insert_repository(
|
|
'Exact Legacy Query', 'fixture/legacy', resolver_state=None,
|
|
)
|
|
fresh_id = self._insert_repository('Exact Fresh Query', 'fixture/fresh')
|
|
immutable_id = self._insert_repository(
|
|
'Image Query', 'fixture/image@sha256:' + ('b' * 64), resolver_state=None,
|
|
)
|
|
now = scanner_db.utc_now_iso()
|
|
discovery_pass = self.db.conn.execute(
|
|
'''INSERT INTO docker_discovery_passes(
|
|
pass_token, source, pass_kind, policy_sha256,
|
|
ordered_queries_sha256, expected_query_count,
|
|
completed_query_count, state, started_at, completed_at,
|
|
created_at, updated_at
|
|
) VALUES ('fixture-pass-token', 'dockerhub', 'deep', ?, ?, 1, 1,
|
|
'complete', ?, ?, ?, ?) RETURNING id''',
|
|
('c' * 64, 'd' * 64, now, now, now, now),
|
|
).fetchone()
|
|
discovery_page = self.db.conn.execute(
|
|
'''INSERT INTO docker_discovery_pages(
|
|
pass_id, query, query_ordinal, page_number, result_count,
|
|
admitted_count, query_complete, admission_kind, page_sha256,
|
|
observed_at, created_at
|
|
) VALUES (?, 'Exact Fresh Query', 0, 1, 1, 1, 1, 'main', ?, ?, ?)
|
|
RETURNING id''',
|
|
(discovery_pass['id'], 'e' * 64, now, now),
|
|
).fetchone()
|
|
self.db.conn.execute(
|
|
'''INSERT INTO docker_repository_query_provenance(
|
|
source, query, repository_queue_id, provenance_kind,
|
|
first_observed_at, last_observed_at, first_search_rank,
|
|
best_search_rank, last_search_rank, first_page_id, last_page_id,
|
|
first_policy_sha256, last_policy_sha256,
|
|
observation_count, fresh_observation_count,
|
|
fresh_complete_observation_count, fresh_coverage_eligible,
|
|
created_at, updated_at
|
|
) VALUES ('dockerhub', 'Exact Fresh Query', ?, 'fresh_page', ?, ?,
|
|
1, 1, 1, ?, ?, ?, ?, 1, 1, 1, 1, ?, ?)''',
|
|
(
|
|
fresh_id, now, now, discovery_page['id'], discovery_page['id'],
|
|
'c' * 64, 'c' * 64, now, now,
|
|
),
|
|
)
|
|
self.db.conn.execute(
|
|
'''INSERT INTO docker_repository_query_observations(
|
|
page_id, repository_queue_id, source, query, search_rank,
|
|
observed_at
|
|
) VALUES (?, ?, 'dockerhub', 'Exact Fresh Query', 1, ?)''',
|
|
(discovery_page['id'], fresh_id, now),
|
|
)
|
|
self.db.conn.commit()
|
|
|
|
self.db.conn.execute('SAVEPOINT bounded_legacy_seed')
|
|
with self.assertRaisesRegex(RuntimeSafetySchemaError, 'reviewed 1-row bound'):
|
|
scanner_db._migration_seed_legacy_docker_provenance(
|
|
self.db.conn, max_rows=1, page_size=1,
|
|
)
|
|
self.db.conn.execute('ROLLBACK TO SAVEPOINT bounded_legacy_seed')
|
|
self.db.conn.execute('RELEASE SAVEPOINT bounded_legacy_seed')
|
|
|
|
self._remove_latest_marker()
|
|
migrate_runtime_safety_schema(self.db)
|
|
migrate_runtime_safety_schema(self.db)
|
|
legacy = self.db.conn.execute(
|
|
'''SELECT provenance_kind, observation_count, fresh_observation_count,
|
|
fresh_complete_observation_count, fresh_coverage_eligible,
|
|
first_page_id, last_page_id
|
|
FROM docker_repository_query_provenance
|
|
WHERE repository_queue_id = ?''',
|
|
(legacy_id,),
|
|
).fetchone()
|
|
self.assertEqual(
|
|
tuple(legacy),
|
|
('legacy_queue', 1, 0, 0, 0, None, None),
|
|
)
|
|
fresh = self.db.conn.execute(
|
|
'''SELECT provenance_kind, fresh_complete_observation_count,
|
|
fresh_coverage_eligible
|
|
FROM docker_repository_query_provenance
|
|
WHERE repository_queue_id = ?''',
|
|
(fresh_id,),
|
|
).fetchone()
|
|
self.assertEqual(tuple(fresh), ('fresh_page', 1, 1))
|
|
self.assertIsNone(self.db.conn.execute(
|
|
'''SELECT 1 FROM docker_repository_query_provenance
|
|
WHERE repository_queue_id = ?''',
|
|
(immutable_id,),
|
|
).fetchone())
|
|
self.assertEqual(
|
|
self.db.conn.execute(
|
|
'''SELECT COUNT(*) AS count FROM docker_repository_query_provenance
|
|
WHERE repository_queue_id = ?''',
|
|
(legacy_id,),
|
|
).fetchone()['count'],
|
|
1,
|
|
)
|
|
with self.assertRaises(sqlite3.IntegrityError):
|
|
self.db.conn.execute(
|
|
'''UPDATE docker_repository_query_provenance
|
|
SET fresh_coverage_eligible = 1 WHERE repository_queue_id = ?''',
|
|
(legacy_id,),
|
|
)
|
|
self.db.conn.rollback()
|
|
|
|
def test_migration_quiescence_blocks_resolvers_authority_and_hold_fences(self):
|
|
queue_id = self._insert_repository('Fence Query', 'fixture/fenced')
|
|
self._remove_latest_marker()
|
|
self.db.conn.execute(
|
|
'''UPDATE target_queue SET resolver_state = 'resolving', resolver_token = 'token'
|
|
WHERE id = ?''',
|
|
(queue_id,),
|
|
)
|
|
self.db.conn.commit()
|
|
with self.assertRaisesRegex(RuntimeSafetySchemaError, 'docker_resolvers=1'):
|
|
migrate_runtime_safety_schema(self.db)
|
|
|
|
self.db.conn.execute(
|
|
'''UPDATE target_queue SET resolver_state = 'pending', resolver_token = NULL
|
|
WHERE id = ?''',
|
|
(queue_id,),
|
|
)
|
|
experiment_id = self._insert_experiment(state='held', fenced=True)
|
|
with self.assertRaisesRegex(RuntimeSafetySchemaError, 'experiment_authority_fences=1'):
|
|
migrate_runtime_safety_schema(self.db)
|
|
|
|
self.db.conn.execute(
|
|
'''UPDATE docker_depth_experiments
|
|
SET fence_owner = NULL, fence_token = NULL, fence_expires_at = NULL
|
|
WHERE id = ?''',
|
|
(experiment_id,),
|
|
)
|
|
now = scanner_db.utc_now_iso()
|
|
self.db.conn.execute(
|
|
'''INSERT INTO target_queue_policy_events(
|
|
queue_id, action, prior_status, next_status, source, platform,
|
|
query, reason_code, config_sha256, policy_sha256,
|
|
manifest_sha256, review_audit_sha256, experiment_id,
|
|
prior_updated_at, created_at
|
|
) VALUES (?, 'cold', 'pending', 'cold', 'dockerhub', 'docker',
|
|
'Fence Query', 'docker_depth_experiment_hold',
|
|
?, ?, ?, ?, ?, ?, ?)''',
|
|
(
|
|
queue_id, 'd' * 64, 'e' * 64, 'f' * 64, '1' * 64,
|
|
experiment_id, now, now,
|
|
),
|
|
)
|
|
self.db.conn.commit()
|
|
self.db.conn.execute(
|
|
"UPDATE docker_depth_experiments SET state = 'planned' WHERE id = ?",
|
|
(experiment_id,),
|
|
)
|
|
self.db.conn.commit()
|
|
with self.assertRaisesRegex(RuntimeSafetySchemaError, 'experiment_holds=1'):
|
|
migrate_runtime_safety_schema(self.db)
|
|
|
|
self.db.conn.execute(
|
|
"UPDATE docker_depth_experiments SET state = 'held' WHERE id = ?",
|
|
(experiment_id,),
|
|
)
|
|
self.db.conn.commit()
|
|
migrate_runtime_safety_schema(self.db)
|
|
marker = self.db.conn.execute(
|
|
'SELECT 1 FROM runtime_schema_migrations WHERE version = ?',
|
|
(scanner_db.PIPELINE_MIGRATION_VERSIONS[-1],),
|
|
).fetchone()
|
|
self.assertIsNotNone(marker)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|