Initial server source import
This commit is contained in:
@@ -0,0 +1,508 @@
|
||||
"""Explicit offline regressions for the Docker test target.
|
||||
|
||||
Run as UID 10001 with read-only /opt/truf/app, writable /tmp tmpfs, and
|
||||
Docker --network none: python -I -S -B /opt/truf/tests/container_unit.py.
|
||||
No /data mount, runtime configuration, credentials, PostgreSQL server, external
|
||||
Git source, or provider entrypoint is needed. SQLite fixtures stay in /tmp;
|
||||
native OwnedProcess children and loopback control sockets are intentional.
|
||||
|
||||
--list and --check-selection inspect source with the stdlib only, including
|
||||
on the host. -k only narrows the reviewed selection; pytest arguments and
|
||||
additional paths/plugins are not accepted. New test files are never discovered.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
from contextlib import ExitStack
|
||||
import os
|
||||
from pathlib import Path
|
||||
import runpy
|
||||
import stat
|
||||
import tempfile
|
||||
from unittest import mock
|
||||
|
||||
|
||||
TESTS = Path(__file__).resolve().parent
|
||||
ROOT = TESTS.parent
|
||||
APP = ROOT / 'app'
|
||||
|
||||
SELECTION = {
|
||||
'test_db_backend_safety.py': (
|
||||
'PostgresTimeoutTests',
|
||||
),
|
||||
'test_docker_foundation.py': (
|
||||
'PortablePathTests',
|
||||
'DockerStagingTests::test_native_checkout_default_is_derived_from_this_copy',
|
||||
'DockerStagingTests::test_python_entrypoints_refuse_before_application_imports',
|
||||
'DockerStagingTests::test_all_copied_powershell_tools_have_static_refusals',
|
||||
'DockerStagingTests::test_docker_context_exceptions_are_explicit_source_files_only',
|
||||
),
|
||||
# Linux metadata and runtime I/O are mocked; filesystem fixtures stay in tmp_path.
|
||||
'test_container_runtime.py': (
|
||||
'test_private_path_accepts_exact_owner_only_mode',
|
||||
'test_private_path_rejects_nonabsolute_or_parent_traversal_before_stat',
|
||||
'test_private_path_rejects_symlink_at_every_component',
|
||||
'test_private_path_fails_closed_on_missing_or_uninspectable_components',
|
||||
'test_private_path_rejects_wrong_owner_group_type_or_exact_mode',
|
||||
'test_container_requires_private_image_native_data_and_dedicated_tmpfs',
|
||||
'test_container_refuses_before_mount_inventory_when_identity_gate_fails',
|
||||
'test_root_is_permitted_only_for_explicit_provisioning',
|
||||
'test_container_rejects_nonprivate_required_paths',
|
||||
'test_container_rejects_missing_shared_or_host_style_storage',
|
||||
'test_new_private_file_is_exclusive_nofollow_and_durable',
|
||||
'test_new_private_file_never_overwrites_existing_file',
|
||||
'test_fresh_provision_generates_private_password_once_without_initializing_pg',
|
||||
'test_provision_refuses_nonempty_or_partial_volume_without_repair',
|
||||
'test_provision_lock_conflict_never_creates_layout_or_credentials',
|
||||
'test_provision_does_not_regenerate_incomplete_marked_volume',
|
||||
'test_provision_adds_only_managed_files_to_legacy_marked_volume',
|
||||
'test_provision_refuses_wrong_existing_managed_files_path',
|
||||
'test_bad_provision_marker_prevents_environment_or_application_imports',
|
||||
'test_prepare_environment_scrubs_dsn_and_runtime_overrides_before_imports',
|
||||
'test_prepare_rejects_invalid_generated_password_before_imports',
|
||||
'test_prepare_refuses_config_escaping_fixed_storage_contract',
|
||||
'test_only_default_image_config_or_private_data_config_is_accepted',
|
||||
'test_bootstrap_target_is_the_real_runtime_safety_migration_module',
|
||||
'test_initialize_publishes_marker_only_after_migration_and_confirmed_stop',
|
||||
'test_failed_initialization_always_stops_maintenance_and_never_marks_success',
|
||||
'test_shutdown_during_initialization_never_publishes_early_marker',
|
||||
'test_partial_initialization_is_not_adopted_or_repaired',
|
||||
'test_initialized_marker_must_match_bound_cluster_identity',
|
||||
'test_health_does_not_import_database_or_control_before_valid_marker',
|
||||
'test_health_requires_active_supervisor_and_explicit_postgres_ready',
|
||||
'test_health_rejects_missing_or_unready_pipeline_processes_before_db',
|
||||
'test_health_requires_each_enabled_pipeline_worker',
|
||||
'test_health_allows_explicitly_disabled_janitor',
|
||||
'test_health_requires_worker_api_only_when_explicitly_enabled',
|
||||
'test_strict_health_requires_enabled_worker_api_before_database',
|
||||
'test_strict_health_probes_exact_unauthorized_worker_endpoint',
|
||||
'test_strict_worker_probe_fails_closed_before_database',
|
||||
'test_health_rejects_failed_or_uncertain_managed_source',
|
||||
'test_health_checks_readonly_schema_cutover_leases_identity_then_storage',
|
||||
'test_health_fails_closed_and_closes_database_on_each_database_gate',
|
||||
'test_health_requires_writable_nonfull_persistent_storage',
|
||||
'test_secret_import_refuses_active_runtime_before_reading_stdin',
|
||||
'test_secret_import_cluster_lock_conflict_precedes_stopped_check_and_stdin',
|
||||
'test_secret_import_is_locked_atomic_private_and_never_prints_credentials',
|
||||
'test_invalid_secret_input_is_bounded_and_does_not_leak_or_replace',
|
||||
'test_secret_import_validation_error_clears_candidate_traceback_locals',
|
||||
'test_secret_import_rejects_config_hash_drift_before_temporary_write',
|
||||
'test_secret_import_rechecks_config_hash_before_replacement',
|
||||
'test_write_new_clears_secret_payload_from_traceback_locals',
|
||||
'test_initialize_rejects_config_hash_drift_before_mutation',
|
||||
'test_secret_import_error_removes_temporary_file_and_preserves_old_credentials',
|
||||
'test_main_container_gate_precedes_every_action',
|
||||
'test_main_provision_does_not_enter_nonroot_environment_or_database',
|
||||
'test_main_import_secrets_uses_preliminary_environment_validation',
|
||||
'test_main_initialize_binds_validated_config_hash',
|
||||
'test_main_run_rechecks_config_hash_before_exec',
|
||||
'test_main_snapshot_requires_approved_digest_and_default_config',
|
||||
'test_main_snapshot_dispatch_preserves_module_signal_state_without_initialize',
|
||||
'test_manifest_digest_is_rejected_for_other_actions',
|
||||
'test_worker_api_requirement_flag_is_restricted_to_health',
|
||||
'test_strict_status_dispatches_worker_api_requirement',
|
||||
'test_status_stdout_contains_readiness_but_no_credentials',
|
||||
),
|
||||
'test_runtime_document.py': (
|
||||
'RuntimeDocumentTests',
|
||||
),
|
||||
'test_managed_files.py': (
|
||||
'ManagedFileConfigurationTests',
|
||||
'ManagedFileTraversalTests',
|
||||
'ManagedFileTraversalLinuxTests',
|
||||
),
|
||||
'test_operations_schema.py': ('OperationsSchemaTests',),
|
||||
'test_operations_control.py': ('OperationsControlTests',),
|
||||
'test_host_agent_protocol.py': (
|
||||
'HostAgentProtocolTests',
|
||||
'HostAgentClientTests',
|
||||
'HostAgentServerTests',
|
||||
),
|
||||
'test_host_agent_linux.py': (
|
||||
'HostAgentLinuxTests',
|
||||
),
|
||||
'test_host_agent_apply.py': (
|
||||
'HostAgentApplyTests',
|
||||
),
|
||||
'test_host_agent_deploy.py': (
|
||||
'HostAgentDeployTests',
|
||||
'HostAgentInstallerTests',
|
||||
),
|
||||
'test_host_agent_lifecycle.py': (
|
||||
'HostAgentLifecycleTests',
|
||||
),
|
||||
'test_host_agent_reconcile.py': (
|
||||
'HostAgentReconcileTests',
|
||||
),
|
||||
'test_host_agent_runtime.py': (
|
||||
'HostAgentRuntimeTests',
|
||||
),
|
||||
'test_host_agent_state.py': (
|
||||
'HostAgentStateTests',
|
||||
),
|
||||
'test_runtime_document_io.py': (
|
||||
'RuntimeDocumentIOTests',
|
||||
),
|
||||
'test_operations_service.py': (
|
||||
'OperationsServiceTests',
|
||||
),
|
||||
'test_owned_process.py': (
|
||||
'OwnedProcessTests',
|
||||
'WindowsOwnedProcessTests',
|
||||
'StaticProcessSafetyTests',
|
||||
),
|
||||
'test_owned_process_linux.py': (
|
||||
'LinuxIdentityTests',
|
||||
'StartupHandshakeTests',
|
||||
'LinuxContainmentTests',
|
||||
'LinuxOwnedProcessIntegrationTests',
|
||||
),
|
||||
'test_owned_process_boundary.py': (
|
||||
'OwnedProcessHostBoundaryTests',
|
||||
'CredentialBoundaryTests',
|
||||
),
|
||||
'test_runtime_bootstrap_authority.py': (
|
||||
'RuntimeBootstrapAuthorityTests::test_authenticated_docker_shadow_dispatches_exact_entrypoint',
|
||||
'RuntimeBootstrapAuthorityTests::test_provider_bootstrap_consumes_one_required_separator_before_provider_parse',
|
||||
'RuntimeBootstrapAuthorityTests::test_provider_bootstrap_rejects_missing_and_duplicate_separators',
|
||||
'RuntimeBootstrapAuthorityTests::test_authenticated_entrypoint_exception_is_classified_as_runtime_failure',
|
||||
'RuntimeBootstrapAuthorityTests::test_command_builders_use_exact_isolation_flag_order',
|
||||
'RuntimeBootstrapAuthorityTests::test_direct_mutating_supervisor_fails_before_config_env_locks_or_children',
|
||||
),
|
||||
'test_supervisor_foreground_shutdown.py': (
|
||||
'test_signal_callback_is_only_an_idempotent_flag_assignment',
|
||||
'test_receipt_parity_and_finalization_order',
|
||||
'test_windows_does_not_register_signals',
|
||||
'test_mutating_launch_gates_precede_config_locks_children_and_signals',
|
||||
'test_non_owning_dispatch_does_not_register_signals',
|
||||
'test_pending_term_cannot_admit_startup',
|
||||
'test_background_activation_wait_consumes_term',
|
||||
'test_admission_rejects_pending_term_before_checkpoint',
|
||||
'test_control_activation_cannot_reopen_pending_shutdown',
|
||||
'test_background_activation_callback_failure_is_sticky',
|
||||
'test_term_between_pipeline_starts_admits_no_more_children',
|
||||
'test_checkpoints_exit_without_polling_or_starting',
|
||||
'test_term_between_source_polls_rejects_next_poll',
|
||||
'test_foreground_waits_consume_term_without_input_or_next_refresh',
|
||||
'test_metadata_publication_failure_leaves_closed_memory_gates',
|
||||
'test_repeated_term_checkpoint_does_not_republish_or_rearm_retry',
|
||||
'test_repeated_shutdown_does_not_discard_confirmed_stop_proof',
|
||||
'test_initial_cleanup_interruption_retains_authority_and_failure',
|
||||
'test_interrupted_cleanup_lock_acquisition_retains_authority',
|
||||
'test_partially_published_activation_uses_full_cleanup',
|
||||
'test_coordinated_shutdown_interruption_closes_gate_before_propagating',
|
||||
'test_failed_hold_survives_interruption_and_logging_failure',
|
||||
'test_failed_hold_does_not_release_with_unconfirmed_children',
|
||||
'test_receipt_write_failure_is_nonzero',
|
||||
'test_late_cleanup_failures_are_in_receipt',
|
||||
'test_failure_latched_by_draining_control_worker_is_in_receipt',
|
||||
'test_terminal_foreground_failure_is_sticky_without_changing_restart_policy',
|
||||
'test_foreground_source_failure_survives_cleanup_status_reset',
|
||||
'test_foreground_missing_autostart_is_nonzero',
|
||||
'test_system_exit_status_is_preserved',
|
||||
'test_interrupt_message_failure_cannot_publish_success',
|
||||
'test_authority_drift_failure_remains_sticky',
|
||||
'test_preactivation_close_failure_is_sticky',
|
||||
'test_noninteractive_keeps_existing_completion_policy',
|
||||
'test_stopper_requires_posix_receipt_and_preserves_windows_crosscheck',
|
||||
'test_unconfirmed_child_defers_all_postgres_actions',
|
||||
'test_children_precede_postgres_and_stop_close_share_budget',
|
||||
'test_postgres_wait_consumes_close_budget',
|
||||
),
|
||||
'test_supervisor_startup_rollback.py': (
|
||||
'test_unconfirmed_launch_rollback_retains_owner_until_stop_retry',
|
||||
'test_retained_rollback_refuses_start_schedule_and_poll',
|
||||
'test_confirmed_launch_rollback_clears_owner_and_allows_explicit_retry',
|
||||
'test_constructor_failure_without_returned_owner_still_closes_log',
|
||||
'test_rollback_interruption_propagates_without_losing_owner',
|
||||
'test_log_cleanup_error_does_not_undo_confirmed_exit',
|
||||
'test_log_pump_with_failed_thread_start_can_close_on_rollback_retry',
|
||||
'test_rollback_pending_closes_admission_before_failed_hold_publication',
|
||||
'test_owned_child_uncertain_defers_postgres_until_retained_retry_confirms_exit',
|
||||
'test_main_retains_locks_and_control_until_startup_rollback_is_confirmed',
|
||||
'test_locked_stale_metadata_recovery_uses_exact_identity_not_pid_or_timestamp',
|
||||
'test_foreground_reconciliation_uses_existing_exact_identity_api',
|
||||
'test_live_or_uncertain_owner_metadata_is_never_replaced',
|
||||
'test_metadata_bound_to_another_instance_path_is_not_removed',
|
||||
'test_reconciliation_error_or_interruption_preserves_metadata_for_retry',
|
||||
'test_changed_instance_during_matching_removal_fails_closed',
|
||||
),
|
||||
'test_supervisor_managed_postgres_gate.py': ('SupervisorManagedPostgresGateTests',),
|
||||
'test_observer_only_coordinated_shutdown.py': ('ObserverOnlyCoordinatedShutdownTests',),
|
||||
'test_discovery_producer_supervisor.py': ('DiscoveryProducerSupervisorTests',),
|
||||
'test_distributed_core_profile.py': (
|
||||
'test_default_distributed_core_profile_is_exact',
|
||||
'test_keychecks_are_independent_from_discovery_profile',
|
||||
'test_core_wrapper_selects_only_discovery_producers',
|
||||
),
|
||||
'test_discovery_only_cycle.py': ('DiscoveryOnlyCycleTests',),
|
||||
'test_discovery_request_budgets.py': (
|
||||
'test_each_discovery_page_has_one_shared_attempt_deadline',
|
||||
'test_discovery_proxy_uses_source_read_timeout_and_at_most_three_attempts',
|
||||
'test_late_discovery_success_fails_closed_without_advancing_page',
|
||||
),
|
||||
'test_supervisor_safety.py': (
|
||||
'AuthenticatedControlTests', 'DependencyFailureTests', 'DashboardManagerTests',
|
||||
),
|
||||
'test_postgres_runtime.py': (
|
||||
'PostgresRuntimePathTests',
|
||||
'PostgresControllerTests',
|
||||
'PostgresBackendTests',
|
||||
'PostgresEntrypointTests',
|
||||
),
|
||||
'test_container_security.py': (
|
||||
'NativeExecutablePolicyTests',
|
||||
'WindowsNativePolicyTests',
|
||||
'ContainerLifecyclePolicyTests',
|
||||
'LinuxFilesystemSecurityTests',
|
||||
),
|
||||
'test_runtime_security.py': ('RuntimeSecurityTests',),
|
||||
'test_postgres_empty_initialization.py': (
|
||||
'NativePostgresPathTests',
|
||||
'InitializeEmptyTests',
|
||||
'InitializationEntrypointTests::test_cli_dispatches_to_lock_owning_initializer_after_preflight',
|
||||
'InitializationEntrypointTests::test_container_refusal_and_full_layout_gate_precede_application_imports',
|
||||
'InitializationEntrypointTests::test_bounded_maintenance_stop_requires_proof_and_never_closes_a_supplied_backend',
|
||||
'InitializationEntrypointTests::test_bootstrap_cannot_replace_an_existing_identity',
|
||||
'MaintenanceEntrypointTests',
|
||||
),
|
||||
'test_process_identity_linux.py': ('LinuxProcessIdentityTests',),
|
||||
'test_container_migration_paths.py': (
|
||||
'LegacySpoolPathTests',
|
||||
'ImmutableNativeHardeningTests',
|
||||
'LegacyCutoverChecksTests',
|
||||
'NormalMigrationContractTests',
|
||||
),
|
||||
'test_container_provider_portability.py': ('AskpassTests', 'ProviderDeadlineTests'),
|
||||
'test_container_e2e_helpers.py': ('ContainerE2EHelperTests', 'ContainerE2EFreshVolumeTests'),
|
||||
'test_container_import.py': ('ContainerImportTests',),
|
||||
'test_container_import_config.py': ('ContainerImportConfigTests',),
|
||||
'test_container_projection_recovery.py': ('ProjectionRecoveryTests',),
|
||||
'test_result_bundle_v2.py': ('ResultBundleV2Tests',),
|
||||
'test_pipeline_cutover_invariants.py': ('PipelineCutoverInvariantTests',),
|
||||
'test_custom_provider_detector_compatibility.py': (
|
||||
'CustomProviderDetectorPolicyTests', 'CustomProviderDetectorCLITests',
|
||||
),
|
||||
'test_scan_execution.py': ('ScanExecutionTests',),
|
||||
'test_synthetic_llm_pipeline.py': ('SyntheticLLMPipelineTests',),
|
||||
'test_worker_api.py': (
|
||||
'WorkerServiceTests', 'WorkerAPIRouteTests', 'WorkerSlotRecoveryTests',
|
||||
),
|
||||
'test_worker_api_runtime.py': (
|
||||
'ConfiguredWorkerServiceTests', 'WorkerSupervisorWiringTests',
|
||||
),
|
||||
'test_worker_assignment.py': ('RemoteGitAssignmentBuilderTests',),
|
||||
'test_worker_assignment_runner.py': (
|
||||
'WorkerAssignmentRunnerProtocolTests', 'WorkerSlotRunnerTests',
|
||||
),
|
||||
'test_worker_runner_handoff_linux.py': ('LinuxWorkerRunnerHandoffTests',),
|
||||
'test_worker_contracts.py': ('WorkerContractTests',),
|
||||
'test_worker_cli.py': ('WorkerCLITests',),
|
||||
'test_worker_local_state.py': ('WorkerLocalStateTests',),
|
||||
'test_worker_supervisor.py': ('WorkerSupervisorTests',),
|
||||
'test_worker_package.py': ('WorkerPackageTests',),
|
||||
'test_multisource_execution_snapshot.py': ('MultisourceExecutionSnapshotTests',),
|
||||
'test_remote_direct_credentials.py': (
|
||||
'test_direct_child_environment_preserves_operator_provider_settings',
|
||||
'test_direct_scanner_entries_reject_credentials_and_skip_docker_pool',
|
||||
'test_anonymous_docker_bearer_never_reads_account_pool',
|
||||
'test_direct_anonymous_docker_auth_failure_is_permanent',
|
||||
'test_huggingface_discovery_errors_never_include_response_body',
|
||||
'test_huggingface_missing_repository_is_permanent_and_not_retryable',
|
||||
'test_direct_provider_access_failures_are_permanent',
|
||||
),
|
||||
'test_remote_worker_db.py': ('RemoteWorkerDBTests',),
|
||||
'test_admin_api.py': ('AdminAPITests',),
|
||||
'test_edge_deployment.py': (
|
||||
'test_updater_persists_canonical_state_and_expires_automatically',
|
||||
'test_updater_reuses_persisted_applied_digest_without_reloading',
|
||||
'test_updater_rejects_corrupt_state_before_commands_or_snippet_changes',
|
||||
'test_updater_rejects_symlinked_snippet_when_supported',
|
||||
'test_updater_rejects_non_ip_or_unsafe_addresses_without_commands',
|
||||
'test_updater_renders_deterministically_and_bounds_matcher_lines',
|
||||
'test_updater_rolls_back_state_and_snippet_when_reload_fails',
|
||||
'test_updater_rolls_back_without_reload_when_validation_fails',
|
||||
'test_fail2ban_filter_counts_only_redacted_supplied_bad_credentials',
|
||||
'test_fail2ban_jail_is_persistent_bounded_and_admin_only',
|
||||
'test_caddy_routes_and_failure_log_are_closed_and_redacted',
|
||||
'test_edge_compose_only_opts_runtime_namespace_into_https_publication',
|
||||
'test_edge_image_and_startup_require_pinned_caddy_hash_and_random_prefix',
|
||||
'test_edge_e2e_image_inputs_are_exact_and_host_orchestrator_stays_host_only',
|
||||
'test_production_server_omits_trufflehog_but_worker_and_test_retain_it',
|
||||
'test_production_runbook_requires_fixed_host_agent_and_runtime_paths',
|
||||
'test_real_caddy_e2e_crawls_all_admin_routes_and_detail',
|
||||
),
|
||||
}
|
||||
|
||||
HELPER_SOURCES = ('owned_process_helper.py', 'container_e2e.py', 'container_import_stop_e2e.py',
|
||||
'container_projection_recovery_e2e.py', 'parity_helpers.py')
|
||||
|
||||
|
||||
def selected_nodes():
|
||||
"""Resolve only the named declarations, without importing any test or app."""
|
||||
result = []
|
||||
for filename, selectors in SELECTION.items():
|
||||
path = TESTS / filename
|
||||
if path.resolve().parent != TESTS or not path.is_file():
|
||||
raise SystemExit('container-unit: missing or nonlocal selected source: ' + filename)
|
||||
tree = ast.parse(path.read_text(encoding='utf-8'), filename=str(path))
|
||||
compile(tree, str(path), 'exec')
|
||||
for selector in selectors:
|
||||
body = tree.body
|
||||
declaration = None
|
||||
for name in selector.split('::'):
|
||||
matches = [node for node in body
|
||||
if isinstance(node, (ast.ClassDef, ast.FunctionDef)) and node.name == name]
|
||||
if len(matches) != 1:
|
||||
raise SystemExit('container-unit: missing or ambiguous selection: ' + filename + '::' + selector)
|
||||
declaration = matches[0]
|
||||
body = declaration.body
|
||||
if isinstance(declaration, ast.ClassDef):
|
||||
members = [node.name for node in body
|
||||
if isinstance(node, ast.FunctionDef) and node.name.startswith('test_')]
|
||||
nodes = [filename + '::' + selector + '::' + member for member in members]
|
||||
elif isinstance(declaration, ast.FunctionDef) and declaration.name.startswith('test_'):
|
||||
nodes = [filename + '::' + selector]
|
||||
else:
|
||||
nodes = []
|
||||
if not nodes:
|
||||
raise SystemExit('container-unit: empty test selection: ' + filename + '::' + selector)
|
||||
result.extend(nodes)
|
||||
if not result or len(result) != len(set(result)):
|
||||
raise SystemExit('container-unit: empty or duplicate selection')
|
||||
for filename in HELPER_SOURCES:
|
||||
path = TESTS / filename
|
||||
if path.resolve().parent != TESTS or not path.is_file():
|
||||
raise SystemExit('container-unit: missing or nonlocal helper source: ' + filename)
|
||||
compile(ast.parse(path.read_text(encoding='utf-8'), filename=str(path)), str(path), 'exec')
|
||||
return result
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
if not (sys.flags.isolated and sys.flags.no_site and sys.flags.dont_write_bytecode) or sys.flags.optimize:
|
||||
raise SystemExit('container-unit requires python -I -S -B without optimization')
|
||||
parser = argparse.ArgumentParser(description=__doc__, allow_abbrev=False, exit_on_error=False)
|
||||
modes = parser.add_mutually_exclusive_group()
|
||||
modes.add_argument('--list', action='store_true', help='print exact leaf selectors without importing tests')
|
||||
modes.add_argument('--check-selection', action='store_true', help='validate selected source AST and node IDs only')
|
||||
parser.add_argument('-k', dest='keyword', metavar='EXPRESSION', help='narrow the allowlist using pytest -k')
|
||||
try:
|
||||
args, extra = parser.parse_known_args(argv)
|
||||
except argparse.ArgumentError:
|
||||
parser.error('invalid runner arguments')
|
||||
if extra or (args.keyword is not None and (args.list or args.check_selection)):
|
||||
parser.error('only --list, --check-selection, or -k EXPRESSION is supported')
|
||||
if args.keyword is not None and (not args.keyword.strip() or len(args.keyword) > 512
|
||||
or any(char in args.keyword for char in '\x00\r\n')):
|
||||
parser.error('-k requires a nonempty single-line expression of at most 512 characters')
|
||||
|
||||
nodes = selected_nodes()
|
||||
if args.list:
|
||||
for node in nodes:
|
||||
print('tests/' + node)
|
||||
return 0
|
||||
if args.check_selection:
|
||||
print(f'container-unit: AST OK; {len(SELECTION)} modules, {len(nodes)} test definitions, '
|
||||
'no runner skips (parametrizations expand in pytest)')
|
||||
return 0
|
||||
|
||||
if sys.platform != 'linux' or os.geteuid() != 10001 or TESTS != Path('/opt/truf/tests'):
|
||||
raise SystemExit('container-unit execution requires Linux UID 10001 at /opt/truf/tests')
|
||||
if not APP.is_dir() or os.access(APP, os.W_OK):
|
||||
raise SystemExit('container-unit requires readable, read-only /opt/truf/app')
|
||||
|
||||
# A fresh allowlist also removes mixed-case/future credentials, not just known
|
||||
# TRUF_/SCANNER_/SCAN_/TRUFFLEHOG_/KEYCHECK_/PG*/DATABASE_URL/proxy names.
|
||||
os.environ.clear()
|
||||
os.environ.update({
|
||||
'PATH': '/usr/local/bin:/usr/bin:/bin',
|
||||
'LANG': 'C.UTF-8', 'LC_ALL': 'C.UTF-8',
|
||||
'PYTHONDONTWRITEBYTECODE': '1', 'PYTHONNOUSERSITE': '1', 'PYTHONPATH': '',
|
||||
'PYTEST_DISABLE_PLUGIN_AUTOLOAD': '1', 'PYTEST_ADDOPTS': '', 'PYTEST_PLUGINS': '',
|
||||
})
|
||||
os.umask(0o077)
|
||||
with tempfile.TemporaryDirectory(prefix='container-unit-', dir='/tmp') as temporary:
|
||||
private = Path(temporary).resolve()
|
||||
details = private.stat()
|
||||
if private.parent != Path('/tmp') or details.st_uid != 10001 or stat.S_IMODE(details.st_mode) != 0o700:
|
||||
raise SystemExit('container-unit temporary directory is not private beneath /tmp')
|
||||
for name in ('ALLTEMP', 'TEMP', 'TMP', 'TMPDIR', 'HOME'):
|
||||
os.environ[name] = str(private)
|
||||
for name, child in (('XDG_CONFIG_HOME', 'config'), ('XDG_CACHE_HOME', 'cache'), ('XDG_DATA_HOME', 'data')):
|
||||
os.environ[name] = str(private / child)
|
||||
tempfile.tempdir = None
|
||||
with tempfile.NamedTemporaryFile(prefix='probe-') as probe:
|
||||
actual = Path(probe.name).resolve()
|
||||
if (Path(tempfile.gettempdir()).resolve() != private or actual.parent != private
|
||||
or stat.S_IMODE(actual.stat().st_mode) != 0o600):
|
||||
raise SystemExit('container-unit tempfile escaped its private directory')
|
||||
|
||||
bootstrap = runpy.run_path(str(APP / 'child_bootstrap.py'))
|
||||
bootstrap['_enable_dependency_paths']('supervisor')
|
||||
sys.path.insert(0, str(APP))
|
||||
import pytest
|
||||
import psycopg
|
||||
import requests
|
||||
import paths
|
||||
|
||||
# Only scanner's import-time defaults are relocated. Restore paths before
|
||||
# collection so portable-path and security checks exercise real policy.
|
||||
blocked_import = AssertionError('container-unit forbids import-time runtime I/O')
|
||||
with ExitStack() as imports:
|
||||
imports.enter_context(mock.patch.multiple(paths, APP_DIR=str(private / 'app'), CANONICAL_ROOT=str(private)))
|
||||
for target in ('builtins.open', 'os.open', 'os.mkdir', 'os.makedirs', 'sqlite3.connect',
|
||||
'subprocess.Popen.__init__', 'threading.Thread.start', 'socket.socket.__init__',
|
||||
'requests.Session.request', 'psycopg.connect'):
|
||||
imports.enter_context(mock.patch(target, side_effect=blocked_import))
|
||||
import scanner
|
||||
|
||||
# Filesystem/process mocks have ended: tests exercise real ACLs and locks.
|
||||
os.chdir(ROOT)
|
||||
sys.path.insert(1, str(TESTS))
|
||||
|
||||
def loopback_only(event, arguments):
|
||||
if event in ('socket.getaddrinfo', 'socket.gethostbyname', 'socket.gethostbyaddr'):
|
||||
host = arguments[0]
|
||||
elif event in ('socket.connect', 'socket.bind', 'socket.sendto', 'socket.getnameinfo'):
|
||||
address = arguments[0] if event == 'socket.getnameinfo' else arguments[-1]
|
||||
host = address[0] if isinstance(address, tuple) and address else None
|
||||
else:
|
||||
return
|
||||
if host not in ('127.0.0.1', '::1', 'localhost'):
|
||||
raise AssertionError('container-unit permits loopback sockets only')
|
||||
|
||||
sys.addaudithook(loopback_only)
|
||||
|
||||
class Scope:
|
||||
def pytest_collection_modifyitems(self, items):
|
||||
for item in items:
|
||||
node = item.nodeid.removeprefix('tests/').split('[', 1)[0]
|
||||
if node not in nodes:
|
||||
raise pytest.UsageError('container-unit collected a test outside its allowlist')
|
||||
|
||||
options = [
|
||||
'-c', '/dev/null', '--rootdir', str(ROOT), '--noconftest',
|
||||
'-p', 'no:cacheprovider', '-o', 'addopts=', '--basetemp', str(private / 'pytest'),
|
||||
'--tb=short', '--show-capture=no', '-ra',
|
||||
]
|
||||
if args.keyword is not None:
|
||||
options.append('-k=' + args.keyword)
|
||||
options.extend(str(TESTS / node) for node in nodes)
|
||||
# Tests may replace these fences with their own scoped mocks, never real
|
||||
# PostgreSQL or provider transports. Native local processes stay real.
|
||||
with mock.patch.object(psycopg, 'connect', side_effect=AssertionError('external PostgreSQL is forbidden')), \
|
||||
mock.patch.object(requests.Session, 'request', side_effect=AssertionError('provider HTTP is forbidden')):
|
||||
return int(pytest.main(options, plugins=[Scope()]))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
code = main()
|
||||
except Exception as error:
|
||||
print('container-unit bootstrap failed (' + type(error).__name__ + '); details withheld', file=sys.stderr)
|
||||
code = 1
|
||||
raise SystemExit(code)
|
||||
Reference in New Issue
Block a user