gea-program 0.54.0__tar.gz → 0.55.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gea_program-0.54.0 → gea_program-0.55.0}/PKG-INFO +29 -5
- {gea_program-0.54.0 → gea_program-0.55.0}/README.md +28 -4
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/__init__.py +1 -1
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/__main__.py +62 -3
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/acceptance_tests.py +223 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/alerts.py +15 -2
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/doctor.py +12 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/alerts.md +1 -1
- gea_program-0.55.0/gea/help/https.md +19 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/upkeep.md +2 -2
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/helplib.py +1 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/notify.py +3 -1
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/service.py +149 -6
- gea_program-0.55.0/gea/tls.py +530 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/web/app.html +24 -8
- {gea_program-0.54.0 → gea_program-0.55.0}/gea_program.egg-info/PKG-INFO +29 -5
- {gea_program-0.54.0 → gea_program-0.55.0}/gea_program.egg-info/SOURCES.txt +2 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/pyproject.toml +1 -1
- {gea_program-0.54.0 → gea_program-0.55.0}/LICENSE +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/accuracy_statement.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/alarm_engine.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/backup.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/bench.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/blind_harness.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/med_160_sapropels.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog_map.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/catalog_register.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/certificates.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/cli.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/client_reports.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/collision.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/config_versioning.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/conformance.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/control_room.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/correlation.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/dashboard.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/declarations.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/decline.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/deviation.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/dlis.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/dnp3.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/dossier.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/dossier_verify.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/downhole_engine.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/drift_monitor.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/drill_string.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/earth_model.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/economics.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/ed25519.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/edr.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/elevation.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/etp.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/etp12_schemas.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/example_register_map.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/fat_sat.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/files.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/fixtures/README.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/fixtures/comcat_mahoning_1990-2026.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/fixtures/ne_110m_coastline.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/follower.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/forward_model.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/frac_hits.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/friction_priors.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/gamma.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/gauge_aging.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/gauge_specs.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/geodesy.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/geomag.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/gravity_reference.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/hartip.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/TESTER_GUIDE.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/alarms.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/audit-update.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/backup.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/catalog.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/charts.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/collision.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/conformance.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/control-room.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/data-in.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/declare.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/decline.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/dlis.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/doctor.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/dossier.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/drift.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/drillstring.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/economics.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/edr.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/etp.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/field-ports.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/files.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/frac-hits.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/historian.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/instruments.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/interval.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/invoice-check.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/map.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/month-end.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/notifications.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/opseis.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/osdu.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/pad-summary.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/patches.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/penetrator.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/plan.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/pore-pressure.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/ppdm.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/quakeml.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/quality.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/report-history.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/rig-time.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/seedlink.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/seismic.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/setup.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/simulation.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/site.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/sites.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/slip.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/soak.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/sra.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/start.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/structure.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/tools.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/torque-drag.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/transients.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/vibration.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/well-report.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/well-tests.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/help/well3d.md +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/housekeeping.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/interval.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/inverse_engine.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/invoice_check.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/jobs.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/live_ports.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/loadtest.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/matplotlib_demo.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/modbus.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/modbus_rtu.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/model_card.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/month_end.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/mqtt_port.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/op_seismicity.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/opcua_port.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/operator_app.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/osdu.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/pad_summary.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/patches.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/pdf_report.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/penetrator.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/permits.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/piwebapi.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/pore_pressure.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/ports.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/ppdm.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/profile_catalog.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/project.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/qt6_downhole_app.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/quakeml.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reconciler.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/WMM2025.COF +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/eia_henry_hub_daily.csv.gz +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/eia_prices.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/eia_wti_daily.csv.gz +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/iscwsa_rev4_example.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/iscwsa_rev5_example.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/l0701_sonic_gr.las.gz +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/l0701_sonic_gr.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/libmseed_steim.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/mahoning_example.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/mud_motor_specs.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/owsg_tool_models.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/penetrator_ratings.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/seismicity_protocols.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/tool_sources.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/us_outline.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/us_outline.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/volve_3d.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/volve_f4_interpretation.las.gz +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/volve_f4_logs.provenance.json +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/volve_f4_lwd.las.gz +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/volve_surfaces.json.gz +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/volve_traj_F-10.xml.gz +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/volve_traj_F-4.xml.gz +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/volve_traj_F-5_12.25in.xml.gz +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/volve_traj_F-5_17.5in.xml.gz +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/volve_traj_F-5_36in.xml.gz +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/volve_traj_F-5_8.5in.xml.gz +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/reference/volve_well_picks.csv.gz +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/report_history.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/rig_state.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/rock_inventory.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/sample_record.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/sample_well_profile.csv +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/sbom.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/seedlink.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/segy.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/seismic.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/seismic_array.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/seismic_assoc.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/seismic_detect.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/seismic_film.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/seismic_harmonic.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/seismic_qc.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/seismic_response.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/seismic_signature.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/seismic_track.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/seismic_uncertainty.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/seismic_unlisted.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/seismic_watch.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/sensor_swap.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/series_cache.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/service_life.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/setup_checklist.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/shapefile.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/shell.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/shut_in.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/simulation.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/site_map.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/sla_report.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/slip.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/soak.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/sra.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/store_forward.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/strata_join.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/structure.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/supervisor.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/survey_cmd.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/survey_view.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/telemetry.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/tool_catalog.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/tool_library.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/tool_taxonomy.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/torque_drag.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/transient.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/vibration.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/well3d.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/well_assembler.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/well_report.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/well_test_validation.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/wellplan.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/wellpos.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/wits0.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/witsml.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/workspace.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea/xlsx_read.py +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea_program.egg-info/dependency_links.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea_program.egg-info/entry_points.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea_program.egg-info/requires.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/gea_program.egg-info/top_level.txt +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/setup.cfg +0 -0
- {gea_program-0.54.0 → gea_program-0.55.0}/tests/test_acceptance.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gea-program
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.55.0
|
|
4
4
|
Summary: GEA-Program: downhole gauge monitoring - canonical measurement records, quality rules, drift evaluation with a re-fit change log, well-test validation, alarm management, model cards, store-and-forward, monthly SLA measurement, FAT/SAT protocols and a dashboard, generated from live objects in the client's reporting outline.
|
|
5
5
|
Author: Daniel T. Murphy
|
|
6
6
|
License: MPL-2.0
|
|
@@ -243,7 +243,7 @@ disappearing. Every action is written to `records/audit.jsonl` with the
|
|
|
243
243
|
actor's name and the SHA-256 of the inputs it used. The service is written on
|
|
244
244
|
the standard library only (no new dependency, the standalone check still
|
|
245
245
|
passes), listens on the loopback interface by default (`--host 0.0.0.0` for a
|
|
246
|
-
site network,
|
|
246
|
+
site network, over HTTPS - its own, below, or the site's TLS proxy), keeps sessions in HttpOnly
|
|
247
247
|
SameSite cookies, and requires the page's own header on every action so a
|
|
248
248
|
foreign form cannot act. Accounts live in `users.json` as salted PBKDF2-SHA256
|
|
249
249
|
hashes; `gea users --workspace C:\site --action add --name ... --role admin`
|
|
@@ -1249,11 +1249,35 @@ is recomputed in the index. On the Reports page: the packets on record,
|
|
|
1249
1249
|
Assemble as a job, and Schedule monthly (day and hour, the previous month).
|
|
1250
1250
|
`gea help month-end`.
|
|
1251
1251
|
|
|
1252
|
+
### HTTPS
|
|
1253
|
+
|
|
1254
|
+
The service speaks TLS itself when the workspace names a certificate: the
|
|
1255
|
+
site's own (from the company's certificate authority, which managed
|
|
1256
|
+
computers already trust; read where the site keeps it, at every start) or a
|
|
1257
|
+
self-signed one it makes with the standard library alone (an RSA key and an
|
|
1258
|
+
X.509 v3 server certificate for every name and address it is reached by).
|
|
1259
|
+
TLS 1.2 is the floor; the cookie is marked Secure; every answer carries
|
|
1260
|
+
Strict-Transport-Security; a plain `http://` request on the same port is
|
|
1261
|
+
sent to HTTPS; a certificate in its last 30 days raises the `tls.expiring`
|
|
1262
|
+
alert; a workspace whose certificate files are broken does not start rather
|
|
1263
|
+
than fall back to plain HTTP.
|
|
1264
|
+
|
|
1265
|
+
```
|
|
1266
|
+
gea tls --workspace C:\site --site-cert C:\certs\gea-pad3.crt --site-key C:\certs\gea-pad3.key
|
|
1267
|
+
gea tls --workspace C:\site --self-signed --name gea-pad3 --name 10.20.0.15 --export-cert C:\certs\gea-site.crt
|
|
1268
|
+
gea serve --workspace C:\site --host 0.0.0.0 # https://gea-pad3:8765/
|
|
1269
|
+
```
|
|
1270
|
+
|
|
1271
|
+
Or Administration -> **HTTPS** in the page (download the certificate, not
|
|
1272
|
+
the key, for the site's computers to trust; compare the SHA-256 shown).
|
|
1273
|
+
`gea help https`.
|
|
1274
|
+
|
|
1252
1275
|
### Before a site goes live
|
|
1253
1276
|
|
|
1254
|
-
`deploy/README.md` is the checklist:
|
|
1255
|
-
address and put
|
|
1256
|
-
`deploy/nginx-gea.conf`), start with
|
|
1277
|
+
`deploy/README.md` is the checklist: HTTPS from the service itself (above),
|
|
1278
|
+
or keep the service on the loopback address and put the site's TLS proxy in
|
|
1279
|
+
front (`deploy/Caddyfile`, `deploy/nginx-gea.conf`), start with
|
|
1280
|
+
`--behind-proxy`. The service itself
|
|
1257
1281
|
rate-limits sign-ins (five failures lock the name and the address for 15
|
|
1258
1282
|
minutes, audited), lists and revokes live sessions, sends the security
|
|
1259
1283
|
headers on every response, and `gea housekeeping --workspace C:\site --apply`
|
|
@@ -204,7 +204,7 @@ disappearing. Every action is written to `records/audit.jsonl` with the
|
|
|
204
204
|
actor's name and the SHA-256 of the inputs it used. The service is written on
|
|
205
205
|
the standard library only (no new dependency, the standalone check still
|
|
206
206
|
passes), listens on the loopback interface by default (`--host 0.0.0.0` for a
|
|
207
|
-
site network,
|
|
207
|
+
site network, over HTTPS - its own, below, or the site's TLS proxy), keeps sessions in HttpOnly
|
|
208
208
|
SameSite cookies, and requires the page's own header on every action so a
|
|
209
209
|
foreign form cannot act. Accounts live in `users.json` as salted PBKDF2-SHA256
|
|
210
210
|
hashes; `gea users --workspace C:\site --action add --name ... --role admin`
|
|
@@ -1210,11 +1210,35 @@ is recomputed in the index. On the Reports page: the packets on record,
|
|
|
1210
1210
|
Assemble as a job, and Schedule monthly (day and hour, the previous month).
|
|
1211
1211
|
`gea help month-end`.
|
|
1212
1212
|
|
|
1213
|
+
### HTTPS
|
|
1214
|
+
|
|
1215
|
+
The service speaks TLS itself when the workspace names a certificate: the
|
|
1216
|
+
site's own (from the company's certificate authority, which managed
|
|
1217
|
+
computers already trust; read where the site keeps it, at every start) or a
|
|
1218
|
+
self-signed one it makes with the standard library alone (an RSA key and an
|
|
1219
|
+
X.509 v3 server certificate for every name and address it is reached by).
|
|
1220
|
+
TLS 1.2 is the floor; the cookie is marked Secure; every answer carries
|
|
1221
|
+
Strict-Transport-Security; a plain `http://` request on the same port is
|
|
1222
|
+
sent to HTTPS; a certificate in its last 30 days raises the `tls.expiring`
|
|
1223
|
+
alert; a workspace whose certificate files are broken does not start rather
|
|
1224
|
+
than fall back to plain HTTP.
|
|
1225
|
+
|
|
1226
|
+
```
|
|
1227
|
+
gea tls --workspace C:\site --site-cert C:\certs\gea-pad3.crt --site-key C:\certs\gea-pad3.key
|
|
1228
|
+
gea tls --workspace C:\site --self-signed --name gea-pad3 --name 10.20.0.15 --export-cert C:\certs\gea-site.crt
|
|
1229
|
+
gea serve --workspace C:\site --host 0.0.0.0 # https://gea-pad3:8765/
|
|
1230
|
+
```
|
|
1231
|
+
|
|
1232
|
+
Or Administration -> **HTTPS** in the page (download the certificate, not
|
|
1233
|
+
the key, for the site's computers to trust; compare the SHA-256 shown).
|
|
1234
|
+
`gea help https`.
|
|
1235
|
+
|
|
1213
1236
|
### Before a site goes live
|
|
1214
1237
|
|
|
1215
|
-
`deploy/README.md` is the checklist:
|
|
1216
|
-
address and put
|
|
1217
|
-
`deploy/nginx-gea.conf`), start with
|
|
1238
|
+
`deploy/README.md` is the checklist: HTTPS from the service itself (above),
|
|
1239
|
+
or keep the service on the loopback address and put the site's TLS proxy in
|
|
1240
|
+
front (`deploy/Caddyfile`, `deploy/nginx-gea.conf`), start with
|
|
1241
|
+
`--behind-proxy`. The service itself
|
|
1218
1242
|
rate-limits sign-ins (five failures lock the name and the address for 15
|
|
1219
1243
|
minutes, audited), lists and revokes live sessions, sends the security
|
|
1220
1244
|
headers on every response, and `gea housekeeping --workspace C:\site --apply`
|
|
@@ -756,13 +756,26 @@ def main(argv=None) -> int:
|
|
|
756
756
|
|
|
757
757
|
p_sv = sub.add_parser("serve", help="serve the dashboard over a workspace: the page, the reports and the JSON API (standard library HTTP)")
|
|
758
758
|
p_sv.add_argument("--workspace", type=str, required=True, help="the workspace folder (gea workspace --action init)")
|
|
759
|
-
p_sv.add_argument("--host", type=str, default="127.0.0.1", help="bind address; 0.0.0.0 exposes it on the site network (
|
|
759
|
+
p_sv.add_argument("--host", type=str, default="127.0.0.1", help="bind address; 0.0.0.0 exposes it on the site network (turn HTTPS on first: gea tls)")
|
|
760
760
|
p_sv.add_argument("--port", type=int, default=8765)
|
|
761
761
|
p_sv.add_argument("--workers", type=int, default=1, help="jobs run at once")
|
|
762
762
|
p_sv.add_argument("--no-scheduler", action="store_true", help="do not run scheduled jobs from this process")
|
|
763
763
|
p_sv.add_argument("--behind-proxy", action="store_true", help="a reverse proxy terminates TLS in front: trust X-Forwarded-For/-Proto, mark the cookie Secure (deploy/ has nginx and Caddy examples)")
|
|
764
764
|
p_sv.add_argument("--no-browser", action="store_true", help="do not open the control panel in a browser once the service is listening (a scheduled start, a restart, a server with no desktop)")
|
|
765
765
|
|
|
766
|
+
p_tl = sub.add_parser("tls", help="HTTPS for the dashboard: the site's own certificate, a self-signed one made here, or off; with no option, how it stands")
|
|
767
|
+
p_tl.add_argument("--workspace", type=str, required=True)
|
|
768
|
+
p_tl.add_argument("--site-cert", type=str, default=None, help="the site's certificate (PEM, the server's first, then any chain) - read from where it is at every start")
|
|
769
|
+
p_tl.add_argument("--site-key", type=str, default=None, help="its private key (PEM, without a passphrase)")
|
|
770
|
+
p_tl.add_argument("--self-signed", action="store_true", help="make a self-signed certificate and key in the workspace's keys\\tls folder")
|
|
771
|
+
p_tl.add_argument("--name", action="append", default=[], help="--self-signed: a host name or address the dashboard is reached by (repeat; default: this machine's names, localhost, 127.0.0.1)")
|
|
772
|
+
p_tl.add_argument("--days", type=int, default=825, help="--self-signed: days valid (at most 825)")
|
|
773
|
+
p_tl.add_argument("--bits", type=int, default=2048, choices=[2048, 3072])
|
|
774
|
+
p_tl.add_argument("--off", action="store_true", help="plain HTTP from the next start (the files are left where they are)")
|
|
775
|
+
p_tl.add_argument("--export-cert", type=str, default=None, help="write the certificate (public, never the key) to this file, for the site's computers to trust")
|
|
776
|
+
p_tl.add_argument("--actor", type=str, default="cli")
|
|
777
|
+
p_tl.add_argument("--json", action="store_true")
|
|
778
|
+
|
|
766
779
|
p_hk = sub.add_parser("housekeeping", help="rotate the append-only logs, prune finished job folders and old live recordings (dry run unless --apply)")
|
|
767
780
|
p_hk.add_argument("--workspace", type=str, required=True)
|
|
768
781
|
p_hk.add_argument("--apply", action="store_true")
|
|
@@ -2603,6 +2616,41 @@ def main(argv=None) -> int:
|
|
|
2603
2616
|
raise SystemExit(f"pore-pressure: {e.args[0] if e.args else e}")
|
|
2604
2617
|
print(_json.dumps(r, default=str) if a.json else _PPF.text(r))
|
|
2605
2618
|
return 0
|
|
2619
|
+
elif a.cmd == "tls":
|
|
2620
|
+
import json as _json
|
|
2621
|
+
from . import tls as _TLS
|
|
2622
|
+
from .workspace import Workspace, WorkspaceError
|
|
2623
|
+
try:
|
|
2624
|
+
ws = Workspace(a.workspace)
|
|
2625
|
+
if sum(bool(x) for x in (a.self_signed, a.site_cert or a.site_key, a.off)) > 1:
|
|
2626
|
+
raise ValueError("one of --self-signed, --site-cert/--site-key or --off")
|
|
2627
|
+
if a.site_cert or a.site_key:
|
|
2628
|
+
if not (a.site_cert and a.site_key):
|
|
2629
|
+
raise ValueError("--site-cert and --site-key go together")
|
|
2630
|
+
r = _TLS.use_site_certificate(ws, a.actor, a.site_cert, a.site_key)
|
|
2631
|
+
elif a.self_signed:
|
|
2632
|
+
r = _TLS.make_self_signed(ws, a.actor, a.name or None, a.days, a.bits)
|
|
2633
|
+
elif a.off:
|
|
2634
|
+
r = _TLS.turn_off(ws, a.actor)
|
|
2635
|
+
else:
|
|
2636
|
+
r = _TLS.status(ws)
|
|
2637
|
+
if a.export_cert:
|
|
2638
|
+
pem = _TLS.certificate_pem(ws)
|
|
2639
|
+
if not pem:
|
|
2640
|
+
raise ValueError("HTTPS is off: there is no certificate to export")
|
|
2641
|
+
with open(a.export_cert, "wb") as f_:
|
|
2642
|
+
f_.write(pem)
|
|
2643
|
+
except (KeyError, ValueError, OSError, WorkspaceError) as e:
|
|
2644
|
+
raise SystemExit(f"tls: {e.args[0] if e.args else e}")
|
|
2645
|
+
if a.json:
|
|
2646
|
+
print(_json.dumps(r, default=str))
|
|
2647
|
+
else:
|
|
2648
|
+
print(_TLS.text(r))
|
|
2649
|
+
if a.self_signed or a.site_cert or a.off:
|
|
2650
|
+
print(" takes effect at the next start of the control panel (gea serve, or Restart on the Administration page)")
|
|
2651
|
+
if a.export_cert:
|
|
2652
|
+
print(f" certificate written to {a.export_cert} - hand this out, never the key")
|
|
2653
|
+
return 0
|
|
2606
2654
|
elif a.cmd == "frac-hits":
|
|
2607
2655
|
import json as _json
|
|
2608
2656
|
from . import frac_hits as _FH
|
|
@@ -2946,18 +2994,29 @@ def main(argv=None) -> int:
|
|
|
2946
2994
|
print(f"serve: BLOCK {mism}")
|
|
2947
2995
|
if pre or mism:
|
|
2948
2996
|
raise SystemExit("serve: not starting - fix the above (gea doctor --workspace ... shows the full picture)")
|
|
2997
|
+
from .tls import TlsError
|
|
2949
2998
|
try:
|
|
2950
2999
|
svc = Service(a.workspace, host=a.host, port=a.port, workers=a.workers, scheduler=not a.no_scheduler, behind_proxy=a.behind_proxy)
|
|
2951
3000
|
except WorkspaceError as e:
|
|
2952
3001
|
raise SystemExit(f"serve: {e}")
|
|
3002
|
+
except TlsError as e:
|
|
3003
|
+
raise SystemExit(f"serve: HTTPS is on for this workspace but {e}. Put the files right, or: gea tls --workspace \"{a.workspace}\" --off")
|
|
2953
3004
|
n_users = svc.app.users.count()
|
|
2954
3005
|
print(f"gea {__version__}: serving workspace '{svc.ws.manifest['name']}' at {svc.url}")
|
|
2955
3006
|
print(f" code: {os.path.dirname(os.path.abspath(__file__))} (an installed copy serves the files it was installed from; pip install -e <clone> follows the clone)")
|
|
2956
3007
|
print(f" patches: {len(svc.app.patches.store.list())} defined, {len([s for s in svc.app.patches.states() if s['status'] != 'STOPPED'])} running")
|
|
2957
3008
|
if n_users == 0:
|
|
2958
3009
|
print(" no accounts yet: the page will ask for the first administrator's name and password (one time), or run: gea users --workspace ... --action add --role admin --name ...")
|
|
2959
|
-
if
|
|
2960
|
-
|
|
3010
|
+
if svc.tls_status:
|
|
3011
|
+
ts_ = svc.tls_status
|
|
3012
|
+
f_ = ts_.get("facts") or {}
|
|
3013
|
+
print(f" HTTPS ({ts_['state']}): {ts_['detail']}")
|
|
3014
|
+
print(f" names {', '.join(f_.get('names') or [])}; SHA-256 {f_.get('sha256_fingerprint')}")
|
|
3015
|
+
if f_.get("self_signed"):
|
|
3016
|
+
print(f" the certificate to hand out (never the key): {ts_['cert']} - or download it from the Administration page")
|
|
3017
|
+
elif a.host not in ("127.0.0.1", "localhost", "::1") and not a.behind_proxy:
|
|
3018
|
+
print(" NOTE: bound to a network address in plain HTTP. Turn HTTPS on (gea tls --workspace ... --site-cert/--site-key, or --self-signed),")
|
|
3019
|
+
print(" or put the site's TLS proxy in front (--behind-proxy; deploy/README.md), for anything beyond a trusted LAN.")
|
|
2961
3020
|
print(" Ctrl+C stops it; every action is in records/audit.jsonl, every start and stop in records/runlog.jsonl")
|
|
2962
3021
|
print(" the control panel's Stop and Restart end this run with a stated code: 0 stop, 86 start me again -")
|
|
2963
3022
|
print(" the launcher reads it, and on anything but 86 it hands the window to a PowerShell prompt, never a Python one")
|
|
@@ -12239,6 +12239,228 @@ def section_cw_decline(tmp: str) -> None:
|
|
|
12239
12239
|
+ (f" [failed: {', '.join(failed4)}]" if failed4 else ""))
|
|
12240
12240
|
|
|
12241
12241
|
|
|
12242
|
+
def section_cx_https(tmp: str) -> None:
|
|
12243
|
+
"""Section CX - HTTPS: the self-signed certificate made by the standard library and read back; the service speaking
|
|
12244
|
+
TLS 1.2 or later with it - the Secure cookie, HSTS, a plain request sent to HTTPS, an untrusting client refused while
|
|
12245
|
+
the service keeps answering; the site's own certificate read where it lives; the expiry alert, the doctor, a broken
|
|
12246
|
+
pair that does not start, the CLI; the key in no dossier and from no route; the page and the help."""
|
|
12247
|
+
import http.client
|
|
12248
|
+
import socket as _so
|
|
12249
|
+
import ssl as _ssl
|
|
12250
|
+
import subprocess as _sp
|
|
12251
|
+
import urllib.error
|
|
12252
|
+
import zipfile as _zf
|
|
12253
|
+
from datetime import datetime as _dt, timedelta as _td, timezone as _tz
|
|
12254
|
+
from . import __version__ as _ver
|
|
12255
|
+
from . import alerts as AL
|
|
12256
|
+
from . import doctor as DR
|
|
12257
|
+
from . import dossier as DZ
|
|
12258
|
+
from . import helplib as H
|
|
12259
|
+
from . import tls as TLS
|
|
12260
|
+
from .service import Service, Users
|
|
12261
|
+
from .workspace import Workspace
|
|
12262
|
+
pkg = Path(__file__).parent
|
|
12263
|
+
d = Path(tmp, "https"); d.mkdir()
|
|
12264
|
+
|
|
12265
|
+
# -- CX1 the certificate by hand ----------------------------------------------------------------------------------
|
|
12266
|
+
key = TLS.rsa_key(2048)
|
|
12267
|
+
sig = TLS.sign_pkcs1_sha256(key, b"pad 3")
|
|
12268
|
+
cert, kpem, facts = TLS.self_signed(["gea-pad3", "gea-pad3.example.local", "10.20.0.15", "localhost", "127.0.0.1"], days=400)
|
|
12269
|
+
(d / "a.crt").write_bytes(cert); (d / "a.key").write_bytes(kpem)
|
|
12270
|
+
der = _ssl.PEM_cert_to_DER_cert(cert.decode("ascii"))
|
|
12271
|
+
nb = _dt.strptime(facts["not_before"], "%Y-%m-%dT%H:%M:%SZ"); na = _dt.strptime(facts["not_after"], "%Y-%m-%dT%H:%M:%SZ")
|
|
12272
|
+
cert_b, key_b, _ = TLS.self_signed(["other"], days=5)
|
|
12273
|
+
(d / "b.crt").write_bytes(cert_b); (d / "b.key").write_bytes(key_b)
|
|
12274
|
+
(d / "enc.key").write_bytes(b"-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIB\n-----END ENCRYPTED PRIVATE KEY-----\n")
|
|
12275
|
+
|
|
12276
|
+
def refused(fn, *a, **k):
|
|
12277
|
+
try:
|
|
12278
|
+
fn(*a, **k)
|
|
12279
|
+
except TLS.TlsError as e:
|
|
12280
|
+
return str(e)
|
|
12281
|
+
return ""
|
|
12282
|
+
c1 = {
|
|
12283
|
+
"rsa": key["n"].bit_length() == 2048 and key["e"] == 65537 and pow(pow(12345, key["e"], key["n"]), key["d"], key["n"]) == 12345
|
|
12284
|
+
and TLS.verify_pkcs1_sha256(key["n"], key["e"], b"pad 3", sig) and not TLS.verify_pkcs1_sha256(key["n"], key["e"], b"pad 4", sig),
|
|
12285
|
+
"facts": facts["names"] == ["gea-pad3", "gea-pad3.example.local", "10.20.0.15", "localhost", "127.0.0.1"] and facts["subject"] == "gea-pad3"
|
|
12286
|
+
and facts["self_signed"] and facts["self_signature_ok"] and facts["ca"] is False and facts["bits"] == 2048 and facts["key"] == "RSA"
|
|
12287
|
+
and abs((na - nb).total_seconds() - (400 * 86400 + 300)) < 2
|
|
12288
|
+
and facts["sha256_fingerprint"].replace(":", "").lower() == __import__("hashlib").sha256(der).hexdigest(),
|
|
12289
|
+
"loads": isinstance(TLS.server_context(str(d / "a.crt"), str(d / "a.key")), _ssl.SSLContext)
|
|
12290
|
+
and TLS.server_context(str(d / "a.crt"), str(d / "a.key")).minimum_version == _ssl.TLSVersion.TLSv1_2,
|
|
12291
|
+
"refused": all(refused(*x) for x in ((TLS.self_signed, []), (TLS.self_signed, ["x"], 826), (TLS.self_signed, ["x"], 30, 1024),
|
|
12292
|
+
(TLS.self_signed, ["bad name!"])))
|
|
12293
|
+
and "does not belong" in refused(TLS.server_context, str(d / "a.crt"), str(d / "b.key"))
|
|
12294
|
+
and "passphrase" in refused(TLS.server_context, str(d / "a.crt"), str(d / "enc.key"))
|
|
12295
|
+
and "not there" in refused(TLS.server_context, str(d / "nope.crt"), str(d / "a.key"))
|
|
12296
|
+
and "no certificate" in refused(TLS.read_certificate, b"not a certificate"),
|
|
12297
|
+
}
|
|
12298
|
+
failed1 = [k for k, v in c1.items() if not v]
|
|
12299
|
+
ok(not failed1,
|
|
12300
|
+
"CX1 the self-signed certificate is made by the standard library: an RSA key (e 65537; a PKCS #1 v1.5 SHA-256 signature that verifies, and not for "
|
|
12301
|
+
"other bytes); an X.509 v3 server certificate with every name and address as a subject alternative name, its validity, not an authority, its own "
|
|
12302
|
+
"signature good, its SHA-256 fingerprint; loaded by Python's TLS with 1.2 as the floor; no names, more than 825 days, a 1024-bit key, a bad name, a key "
|
|
12303
|
+
"that is not the certificate's, a key with a passphrase and a missing file refused, each saying why" + (f" - failed: {failed1}" if failed1 else ""))
|
|
12304
|
+
|
|
12305
|
+
# -- CX2 the service over HTTPS ------------------------------------------------------------------------------------
|
|
12306
|
+
wsp = str(Path(tmp, "ws_cx")); ws = Workspace.create(wsp, "HTTPS Site", actor="tester")
|
|
12307
|
+
users = Users(str(Path(wsp, "users.json")))
|
|
12308
|
+
users.add("adm", "admin-pass-123", "admin"); users.add("v", "viewer-pass-1", "viewer")
|
|
12309
|
+
st0 = TLS.status(ws)
|
|
12310
|
+
made = TLS.make_self_signed(ws, "tester", ["localhost", "127.0.0.1"], days=400)
|
|
12311
|
+
svc = Service(wsp, host="127.0.0.1", port=0, scheduler=False).start()
|
|
12312
|
+
port = svc.port
|
|
12313
|
+
trust = _ssl.create_default_context(cafile=str(Path(wsp, TLS.SELF_CERT)))
|
|
12314
|
+
|
|
12315
|
+
def req(method, path, body=None, cookie=None, host="127.0.0.1", ctx=trust):
|
|
12316
|
+
c = http.client.HTTPSConnection(host, port, context=ctx, timeout=30)
|
|
12317
|
+
h = {"Content-Type": "application/json", "X-GEA-Action": "1"} if method == "POST" else {}
|
|
12318
|
+
if cookie:
|
|
12319
|
+
h["Cookie"] = cookie
|
|
12320
|
+
c.request(method, path, body=(json.dumps(body) if body is not None else None), headers=h)
|
|
12321
|
+
r = c.getresponse()
|
|
12322
|
+
data = r.read()
|
|
12323
|
+
ver = c.sock.version() if c.sock else None
|
|
12324
|
+
c.close()
|
|
12325
|
+
return r.status, dict(r.getheaders()), data, ver
|
|
12326
|
+
|
|
12327
|
+
def plain(path, host_header):
|
|
12328
|
+
c = http.client.HTTPConnection("127.0.0.1", port, timeout=30)
|
|
12329
|
+
c.request("GET", path, headers={"Host": host_header})
|
|
12330
|
+
r = c.getresponse()
|
|
12331
|
+
out = (r.status, r.getheader("Location"), r.read())
|
|
12332
|
+
c.close()
|
|
12333
|
+
return out
|
|
12334
|
+
try:
|
|
12335
|
+
idle = _so.create_connection(("127.0.0.1", port), timeout=30) # connects and says nothing
|
|
12336
|
+
s_s, h_s, _, ver = req("GET", "/api/session")
|
|
12337
|
+
s_l, h_l, b_l, _ = req("POST", "/api/login", {"name": "adm", "password": "admin-pass-123"})
|
|
12338
|
+
ck = h_l.get("Set-Cookie", "").split(";")[0]
|
|
12339
|
+
s_v, h_v, _, _ = req("POST", "/api/login", {"name": "v", "password": "viewer-pass-1"})
|
|
12340
|
+
vk = h_v.get("Set-Cookie", "").split(";")[0]
|
|
12341
|
+
p301 = plain("/api/overview?x=1", f"127.0.0.1:{port}")
|
|
12342
|
+
p400 = plain("/", "elsewhere.example")
|
|
12343
|
+
try:
|
|
12344
|
+
urllib.request.urlopen(svc.url + "api/session", context=_ssl.create_default_context(), timeout=30)
|
|
12345
|
+
untrusted = "accepted"
|
|
12346
|
+
except urllib.error.URLError as e:
|
|
12347
|
+
untrusted = str(e.reason)
|
|
12348
|
+
wrong_name = _ssl.create_default_context(cafile=str(Path(wsp, TLS.SELF_CERT)))
|
|
12349
|
+
try:
|
|
12350
|
+
with _so.create_connection(("127.0.0.1", port), timeout=30) as raw:
|
|
12351
|
+
with wrong_name.wrap_socket(raw, server_hostname="gea-other.example"):
|
|
12352
|
+
pass
|
|
12353
|
+
name_check = "accepted"
|
|
12354
|
+
except _ssl.SSLError as e:
|
|
12355
|
+
name_check = str(e)
|
|
12356
|
+
s_after, _, _, _ = req("GET", "/api/session")
|
|
12357
|
+
idle.close()
|
|
12358
|
+
# -- CX3 the API ------------------------------------------------------------------------------------------------
|
|
12359
|
+
s_ta, _h, ta_b, _ = req("GET", "/api/tls", cookie=ck)
|
|
12360
|
+
ta = json.loads(ta_b)
|
|
12361
|
+
s_tv, _, _, _ = req("GET", "/api/tls", cookie=vk)
|
|
12362
|
+
s_cv, h_cv, pem_v, _ = req("GET", "/api/tls/certificate", cookie=vk)
|
|
12363
|
+
s_pv, _, _, _ = req("POST", "/api/tls", {"action": "off"}, cookie=vk)
|
|
12364
|
+
s_mm, _, b_mm, _ = req("POST", "/api/tls", {"action": "site", "cert": str(d / "a.crt"), "key": str(d / "b.key")}, cookie=ck)
|
|
12365
|
+
s_bad, _, _, _ = req("POST", "/api/tls", {"action": "nonsense"}, cookie=ck)
|
|
12366
|
+
s_ss, _, b_ss, _ = req("POST", "/api/tls", {"action": "self-signed", "names": "localhost 127.0.0.1", "days": 300}, cookie=ck)
|
|
12367
|
+
s_t2, _, t2, _ = req("GET", "/api/tls", cookie=ck)
|
|
12368
|
+
t2 = json.loads(t2)
|
|
12369
|
+
finally:
|
|
12370
|
+
svc.stop()
|
|
12371
|
+
c2 = {
|
|
12372
|
+
"off_first": st0["state"] == "OFF" and made["state"] == "OK" and made["mode"] == "self-signed",
|
|
12373
|
+
"url": svc.url == f"https://127.0.0.1:{port}/",
|
|
12374
|
+
"tls": s_s == 200 and ver in ("TLSv1.2", "TLSv1.3") and "max-age=" in h_s.get("Strict-Transport-Security", ""),
|
|
12375
|
+
"cookie": s_l == 200 and s_v == 200 and "Secure" in h_l.get("Set-Cookie", "") and "HttpOnly" in h_l.get("Set-Cookie", ""),
|
|
12376
|
+
"plain": p301[0] == 301 and p301[1] == f"https://127.0.0.1:{port}/api/overview?x=1" and p400[0] == 400 and b"speaks HTTPS" in p400[2],
|
|
12377
|
+
"refused": "CERTIFICATE_VERIFY_FAILED" in untrusted and ("mismatch" in name_check.lower() or "doesn't match" in name_check or "not valid" in name_check),
|
|
12378
|
+
"still": s_after == 200 and svc.httpd.tls_refused >= 2,
|
|
12379
|
+
}
|
|
12380
|
+
failed2 = [k for k, v in c2.items() if not v]
|
|
12381
|
+
ok(not failed2,
|
|
12382
|
+
"CX2 with HTTPS on, the service speaks TLS 1.2 or later at an https:// address: a client that trusts the certificate signs in and the cookie is Secure "
|
|
12383
|
+
"and HttpOnly, with Strict-Transport-Security on every answer; plain HTTP on the same port is sent to the same address over HTTPS when it names the "
|
|
12384
|
+
"machine by a name the certificate carries, and told to use HTTPS otherwise; a client that does not trust the certificate, or reaches it by another "
|
|
12385
|
+
"name, is refused - and a silent connection and the refusals do not stop the service answering" + (f" - failed: {failed2}" if failed2 else ""))
|
|
12386
|
+
page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
|
|
12387
|
+
adm_view = page_src[page_src.index("VIEWS.admin = "):page_src.index("VIEWS.help = ")]
|
|
12388
|
+
audit = Path(ws.audit_path).read_text(encoding="utf-8")
|
|
12389
|
+
c3 = {
|
|
12390
|
+
"status": s_ta == 200 and ta["state"] == "OK" and ta["serving"]["https"] and ta["restart_needed"] is False
|
|
12391
|
+
and ta["facts"]["sha256_fingerprint"] == made["facts"]["sha256_fingerprint"],
|
|
12392
|
+
"roles": s_tv == 403 and s_pv == 403 and s_bad == 400,
|
|
12393
|
+
"certificate": s_cv == 200 and pem_v.startswith(b"-----BEGIN CERTIFICATE-----") and b"PRIVATE KEY" not in pem_v
|
|
12394
|
+
and TLS.read_certificate(pem_v)["sha256_fingerprint"] == made["facts"]["sha256_fingerprint"] and "attachment" in h_cv.get("Content-Disposition", ""),
|
|
12395
|
+
"change": s_mm == 400 and b"does not belong" in b_mm and s_ss == 200 and json.loads(b_ss)["restart_needed"] is True
|
|
12396
|
+
and t2["restart_needed"] is True and t2["serving"]["https"] and t2["facts"]["sha256_fingerprint"] != made["facts"]["sha256_fingerprint"],
|
|
12397
|
+
"audit": audit.count('"tls.self_signed"') == 2,
|
|
12398
|
+
"page": all(x in adm_view for x in ("/api/tls", "tlsSite", "tlsSelf", "/api/tls/certificate", "const cStop = $('#ctlStop')", "cAuto.onchange"))
|
|
12399
|
+
and "const cStop" not in page_src[page_src.index("VIEWS.prefs = "):page_src.index("VIEWS.search = ")],
|
|
12400
|
+
}
|
|
12401
|
+
failed3 = [k for k, v in c3.items() if not v]
|
|
12402
|
+
ok(not failed3,
|
|
12403
|
+
"CX3 the HTTPS card (the administrator role): how it stands and what this run serves; the certificate - public - downloads for any signed-in user "
|
|
12404
|
+
"and carries no key; a viewer may not change it; a key that is not the certificate's is refused; a new certificate is kept and says it needs a "
|
|
12405
|
+
"restart; each change is audited; the operations-control buttons on the Administration page are wired there"
|
|
12406
|
+
+ (f" - failed: {failed3}" if failed3 else ""))
|
|
12407
|
+
|
|
12408
|
+
# -- CX4 the site's certificate, the expiry alert, a broken pair, the CLI, the key kept in ----------------------------
|
|
12409
|
+
site_dir = Path(tmp, "site_certs"); site_dir.mkdir()
|
|
12410
|
+
sc, sk, sf = TLS.self_signed(["gea-pad3.example.local", "127.0.0.1"], days=60, org="Example Operator IT")
|
|
12411
|
+
(site_dir / "gea.crt").write_bytes(sc); (site_dir / "gea.key").write_bytes(sk)
|
|
12412
|
+
st_site = TLS.use_site_certificate(ws, "tester", str(site_dir / "gea.crt"), str(site_dir / "gea.key"))
|
|
12413
|
+
copied = sorted(p.name for p in Path(wsp, TLS.TLS_DIR).iterdir())
|
|
12414
|
+
exp_at = _dt.strptime(sf["not_after"], "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=_tz.utc)
|
|
12415
|
+
st_exp = TLS.status(ws, exp_at - _td(days=10))
|
|
12416
|
+
r_exp = AL.check_conditions(ws, None, now=exp_at - _td(days=10), force=True)
|
|
12417
|
+
TLS.make_self_signed(ws, "tester", ["localhost", "127.0.0.1"], days=400)
|
|
12418
|
+
r_clr = AL.check_conditions(ws, None, now=_dt.now(_tz.utc), force=True)
|
|
12419
|
+
os.remove(Path(wsp, TLS.SELF_KEY))
|
|
12420
|
+
st_broken = TLS.status(ws)
|
|
12421
|
+
doc = DR.check_workspace(wsp, "127.0.0.1", 0)
|
|
12422
|
+
try:
|
|
12423
|
+
Service(wsp, host="127.0.0.1", port=0, scheduler=False)
|
|
12424
|
+
start_broken = "started"
|
|
12425
|
+
except TLS.TlsError as e:
|
|
12426
|
+
start_broken = str(e)
|
|
12427
|
+
cli_off = _sp.run([sys.executable, "-m", "gea", "tls", "--workspace", wsp, "--off"], capture_output=True, text=True, cwd=str(pkg.parent))
|
|
12428
|
+
svc_plain = Service(wsp, host="127.0.0.1", port=0, scheduler=False)
|
|
12429
|
+
url_plain = svc_plain.url
|
|
12430
|
+
svc_plain.httpd.server_close()
|
|
12431
|
+
out_crt = str(d / "handed_out.crt")
|
|
12432
|
+
cli_ss = _sp.run([sys.executable, "-m", "gea", "tls", "--workspace", wsp, "--self-signed", "--name", "gea-pad3", "--name", "10.20.0.15", "--days", "90",
|
|
12433
|
+
"--export-cert", out_crt], capture_output=True, text=True, cwd=str(pkg.parent))
|
|
12434
|
+
cli_st = _sp.run([sys.executable, "-m", "gea", "tls", "--workspace", wsp, "--json"], capture_output=True, text=True, cwd=str(pkg.parent))
|
|
12435
|
+
cli_bad = _sp.run([sys.executable, "-m", "gea", "tls", "--workspace", wsp, "--site-cert", str(site_dir / "gea.crt")], capture_output=True, text=True, cwd=str(pkg.parent))
|
|
12436
|
+
__import__("gea.frac_hits", fromlist=["write_report"]).write_report(ws, _ver, "tester")
|
|
12437
|
+
dz = DZ.build(ws, "tester", _ver, None, include_sources=True)
|
|
12438
|
+
zp = Path(ws.reports_dir, dz["path"])
|
|
12439
|
+
with _zf.ZipFile(zp) as z:
|
|
12440
|
+
names = z.namelist()
|
|
12441
|
+
leaked = [n for n in names if b"PRIVATE KEY" in z.read(n)]
|
|
12442
|
+
key_text = Path(wsp, TLS.SELF_KEY).read_bytes()
|
|
12443
|
+
c4 = {
|
|
12444
|
+
"site": st_site["state"] == "OK" and st_site["mode"] == "site" and st_site["cert"] == str(site_dir / "gea.crt")
|
|
12445
|
+
and st_site["facts"]["issuer"] == "gea-pad3.example.local" and "self_signed_key.pem" in copied and "gea.key" not in copied,
|
|
12446
|
+
"expiring": st_exp["state"] == "EXPIRING" and [e["event"] for e in r_exp if e["event"] == "tls.expiring"] == ["tls.expiring"]
|
|
12447
|
+
and any(e["event"] == "condition.cleared" and e.get("cleared") == "tls.expiring" for e in r_clr) and "tls.expiring" in AL.EVENTS,
|
|
12448
|
+
"broken": st_broken["state"] == "BROKEN" and any(f["level"] == "block" and "HTTPS BROKEN" in f["what"] for f in doc) and "not there" in start_broken,
|
|
12449
|
+
"cli": cli_off.returncode == 0 and "HTTPS: OFF" in cli_off.stdout and url_plain.startswith("http://127.0.0.1:")
|
|
12450
|
+
and cli_ss.returncode == 0 and "gea-pad3, 10.20.0.15" in cli_ss.stdout and "next start" in cli_ss.stdout
|
|
12451
|
+
and Path(out_crt).read_bytes().startswith(b"-----BEGIN CERTIFICATE-----") and b"PRIVATE" not in Path(out_crt).read_bytes()
|
|
12452
|
+
and cli_st.returncode == 0 and json.loads(cli_st.stdout)["state"] == "OK" and cli_bad.returncode != 0 and "go together" in (cli_bad.stderr + cli_bad.stdout),
|
|
12453
|
+
"kept_in": not leaked and not any(n.startswith("keys/tls") or n.endswith(".pem") for n in names) and b"PRIVATE KEY" in key_text,
|
|
12454
|
+
"help": not H.check("https") and any(t["topic"] == "https" for t in H.topics()) and "gea help https" in (pkg / "help" / "upkeep.md").read_text(encoding="utf-8"),
|
|
12455
|
+
}
|
|
12456
|
+
failed4 = [k for k, v in c4.items() if not v]
|
|
12457
|
+
ok(not failed4,
|
|
12458
|
+
"CX4 the site's own certificate is used where it lives (not copied in); in its last 30 days it reads EXPIRING and raises tls.expiring once, cleared "
|
|
12459
|
+
"when a new one is in place; with its key gone the doctor says BLOCK and the service does not start (never plain HTTP by surprise) until "
|
|
12460
|
+
"`gea tls --off`; `gea tls --self-signed --name ... --export-cert` makes one and writes the certificate alone; --site-cert without its key is refused; "
|
|
12461
|
+
"no dossier carries the key; the help page" + (f" - failed: {failed4}" if failed4 else ""))
|
|
12462
|
+
|
|
12463
|
+
|
|
12242
12464
|
def PR_read_ok(path) -> bool:
|
|
12243
12465
|
from . import pdf_report as PR
|
|
12244
12466
|
try:
|
|
@@ -12351,6 +12573,7 @@ def main() -> int:
|
|
|
12351
12573
|
section_cu_pore_pressure(tmp)
|
|
12352
12574
|
section_cv_torque_drag(tmp)
|
|
12353
12575
|
section_cw_decline(tmp)
|
|
12576
|
+
section_cx_https(tmp)
|
|
12354
12577
|
if _FAILS:
|
|
12355
12578
|
print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
|
|
12356
12579
|
for f in _FAILS:
|
|
@@ -17,7 +17,10 @@ worth an event, for the conditions the operator does not watch by hand:
|
|
|
17
17
|
attached);
|
|
18
18
|
- `frac.hit` - a fracturing stage reads as a FRAC HIT on a monitored well's pressure (the Frac hits tab; checked every
|
|
19
19
|
15 minutes, once per stage and well);
|
|
20
|
-
- `
|
|
20
|
+
- `tls.expiring` - the dashboard's HTTPS certificate is in its last 30 days, or has expired (checked every 15
|
|
21
|
+
minutes while HTTPS is on);
|
|
22
|
+
- `condition.cleared` - one of motor.due, penetrator.outside, dossier.changed, invoice.differs or tls.expiring is no
|
|
23
|
+
longer so.
|
|
21
24
|
|
|
22
25
|
Every alert is written to `records/alerts.jsonl` whether or not any notification channel is set up - the Alarms
|
|
23
26
|
tab lists them - and is handed to the notification rules, which send it where the site said (a rule per event
|
|
@@ -32,7 +35,7 @@ from datetime import datetime, timezone
|
|
|
32
35
|
from typing import Dict, List, Optional
|
|
33
36
|
|
|
34
37
|
EVENTS = ('seismic.protocol', 'seismic.event', 'seismic.watch_down', 'seismic.watch_up', 'motor.due', 'penetrator.outside',
|
|
35
|
-
'dossier.changed', 'invoice.differs', 'frac.hit', 'condition.cleared')
|
|
38
|
+
'dossier.changed', 'invoice.differs', 'frac.hit', 'tls.expiring', 'condition.cleared')
|
|
36
39
|
CONDITIONS_EVERY_S = 900 # motors and penetrators: every 15 minutes
|
|
37
40
|
DOSSIERS_EVERY_S = 86400 # dossiers: once a day
|
|
38
41
|
_LOCK = threading.Lock()
|
|
@@ -172,6 +175,16 @@ def check_conditions(ws, notifier, now: Optional[datetime] = None, force: bool =
|
|
|
172
175
|
st['frac_hits_seen'] = sorted(done)
|
|
173
176
|
except Exception as ex:
|
|
174
177
|
last['frac_error'] = f'{type(ex).__name__}: {ex}'
|
|
178
|
+
try:
|
|
179
|
+
from . import tls as TLS
|
|
180
|
+
ts = TLS.status(ws, now)
|
|
181
|
+
if ts.get('configured') or 'tls.expiring' in st['conditions']:
|
|
182
|
+
f_ = ts.get('facts') or {}
|
|
183
|
+
_transition(ws, notifier, st, 'tls.expiring', ts['state'] in ('EXPIRING', 'EXPIRED'),
|
|
184
|
+
{'event': 'tls.expiring', 'what': f"the dashboard's HTTPS certificate {f_.get('subject', '')}".strip(),
|
|
185
|
+
'not_after': f_.get('not_after'), 'days_left': ts.get('days_left'), 'detail': ts['detail']}, now, raised)
|
|
186
|
+
except Exception as ex:
|
|
187
|
+
last['tls_error'] = f'{type(ex).__name__}: {ex}'
|
|
175
188
|
for key in [k for k in st['conditions'] if k.split(':')[0] in ('motor.due', 'penetrator.outside') and k not in seen]:
|
|
176
189
|
st['conditions'].pop(key, None) # the run or the record was removed: nothing left to clear
|
|
177
190
|
if due('dossiers', DOSSIERS_EVERY_S):
|
|
@@ -253,6 +253,18 @@ def check_workspace(path: str, host: str = '127.0.0.1', port: int = 8765) -> Lis
|
|
|
253
253
|
else:
|
|
254
254
|
f.append(_finding('block', f'port {port} on {host} is already in use (another gea serve, or another program)',
|
|
255
255
|
f'stop the other service, or start with --port {port + 1}'))
|
|
256
|
+
try:
|
|
257
|
+
from . import tls as TLS
|
|
258
|
+
ts = TLS.status(ws)
|
|
259
|
+
lvl = {'OFF': 'info', 'OK': 'ok', 'EXPIRING': 'warn', 'EXPIRED': 'warn', 'NOT_YET_VALID': 'warn', 'BROKEN': 'block'}[ts['state']]
|
|
260
|
+
fix = {'OFF': f'for the site network: gea tls --workspace "{path}" --site-cert <the .crt or .pem> --site-key <its key>, or --self-signed',
|
|
261
|
+
'EXPIRING': f'gea tls --workspace "{path}" shows the certificate; renew it (or --self-signed for a new one) and restart',
|
|
262
|
+
'EXPIRED': f'gea tls --workspace "{path}" --self-signed makes a new one; a site certificate is renewed by the site',
|
|
263
|
+
'NOT_YET_VALID': "set this machine's clock",
|
|
264
|
+
'BROKEN': f'put the certificate and key files right, or turn HTTPS off: gea tls --workspace "{path}" --off'}.get(ts['state'], '')
|
|
265
|
+
f.append(_finding(lvl, f"HTTPS {ts['state']}: {ts['detail']}", fix))
|
|
266
|
+
except Exception as e:
|
|
267
|
+
f.append(_finding('warn', f'the HTTPS setting could not be read: {e}'))
|
|
256
268
|
prev = _SV.previous_run(ws.path)
|
|
257
269
|
if prev['status'] == 'UNCLEAN':
|
|
258
270
|
f.append(_finding('warn', 'the last run of the control panel was killed rather than stopped: ' + prev['detail'],
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
**How the watch reads the catalogue**: it runs in the service's background poller, so only while `gea serve` (the control panel) is running. An operation is under way when a declared operation (Operations & Seismicity, or the declarations sheet's `[operations]`) has started and has not ended, at a well with a surface position. A poll reads the USGS real-time summary feed of the past day, all magnitudes, updated every minute - the feed USGS asks automated programs to use - and keeps the earthquakes inside the circle around the operating wells (out to the screening distance, 20 km by default, beyond the farthest). On the first poll, or after a gap of a day or more, it asks the FDSN event service for the whole gap instead (from the operation's start, at most 30 days back). USGS revises magnitudes and locations; the newest version wins, and a revision that reaches a higher protocol level is a new alert.
|
|
8
8
|
|
|
9
|
-
**The alerts**: `seismic.protocol` (a protocol level reached, once per level), `seismic.event` (any earthquake within the association radius during or after an operation, once), `seismic.watch_down` (no answer from the catalogue for three intervals while an operation runs) and `seismic.watch_up`; `motor.due` (checked every 15 minutes), `penetrator.outside` (every 15 minutes), `dossier.changed` (the newest dossier of each well and of the pad, re-checked daily), `invoice.differs` (a well's invoice cross-check finds a day that differs; checked daily and when a file is attached), `frac.hit` (a fracturing stage reads as a frac hit on a monitored well's pressure; checked every 15 minutes, once per stage and well), and `condition.cleared` when one of motor.due, penetrator.outside, dossier.changed
|
|
9
|
+
**The alerts**: `seismic.protocol` (a protocol level reached, once per level), `seismic.event` (any earthquake within the association radius during or after an operation, once), `seismic.watch_down` (no answer from the catalogue for three intervals while an operation runs) and `seismic.watch_up`; `motor.due` (checked every 15 minutes), `penetrator.outside` (every 15 minutes), `dossier.changed` (the newest dossier of each well and of the pad, re-checked daily), `invoice.differs` (a well's invoice cross-check finds a day that differs; checked daily and when a file is attached), `frac.hit` (a fracturing stage reads as a frac hit on a monitored well's pressure; checked every 15 minutes, once per stage and well), `tls.expiring` (the control panel's HTTPS certificate in its last 30 days or expired; every 15 minutes while HTTPS is on - `gea help https`), and `condition.cleared` when one of motor.due, penetrator.outside, dossier.changed, invoice.differs or tls.expiring ends.
|
|
10
10
|
|
|
11
11
|
**Getting them to a person**: every alert is written to `records/alerts.jsonl` and shown on the Alarms tab. To have it sent, set up notifications (Administration, Notifications: load the example, edit, validate and commit) with an email (SMTP) or webhook channel and a rule naming the alert, for example `{"event": "seismic.protocol", "channels": ["mail"]}`; the password stays in an environment variable named by `password_env`. A text message: most carriers deliver email sent to the phone's email-to-text address, or a webhook to a messaging service. The `quiet_s` window keeps one alert from repeating.
|
|
12
12
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# HTTPS
|
|
2
|
+
|
|
3
|
+
> How browsers on the site network reach the control panel without anything in plain text: the site's own certificate (best: the company's certificate authority, which managed computers already trust) or a self-signed one made here; the names it must carry; handing it out; renewal and the expiry alert.
|
|
4
|
+
|
|
5
|
+
**The command**: Administration -> **HTTPS** (the administrator role): how it stands, the certificate's names, validity, issuer and SHA-256 fingerprint, **Download the certificate**, and under **Change it**: **Use the site's certificate** (the certificate file and its private key, as paths on the machine running the service), **Make a self-signed certificate** (the names it is reached by, the days), **Turn HTTPS off**. A change takes effect at the next start: **Restart** under Operations control. From a terminal: `gea tls --workspace C:\site` prints how it stands; `--site-cert C:\certs\gea-pad3.crt --site-key C:\certs\gea-pad3.key` uses the site's; `--self-signed --name gea-pad3 --name gea-pad3.example.local --name 10.20.0.15` makes one (with no `--name`: this machine's names, localhost and 127.0.0.1); `--off`; `--export-cert C:\certs\gea-site.crt` writes the certificate (never the key) for the site's computers. Then `gea serve --workspace C:\site --host 0.0.0.0` (or the kit's start-dashboard) and open `https://gea-pad3:8765/`.
|
|
6
|
+
|
|
7
|
+
**What it needs**: for the site's certificate, PEM files: the server certificate first (then any chain), and its private key without a passphrase (the service starts unattended); a `.pfx` is exported to PEM first (`openssl pkcs12 -in gea.pfx -out gea.crt -nokeys` and `-nocerts -nodes -out gea.key`), and the key file is readable by the service's account only. The certificate must carry every name the browsers use - the machine's short name, its full name, its address - as a subject alternative name. The site's files are read where they are, at every start: a renewal put in their place needs only a restart.
|
|
8
|
+
|
|
9
|
+
**How it is read**: TLS 1.2 is the floor (TLS 1.3 when both ends have it). With HTTPS on, the session cookie is marked Secure, every response tells the browser to use only HTTPS from then on (Strict-Transport-Security, 180 days), and a plain `http://` request on the same port is sent to the same address over HTTPS when it named the machine by a name the certificate carries (otherwise it is told to use HTTPS). Each connection's handshake runs in its own thread, so a scanner or an old client cannot hold up the others. The self-signed certificate is made by this program alone: an RSA key of 2048 bits (3072 on request), an X.509 v3 certificate for a TLS server (not a certificate authority), signed with SHA-256, valid for at most 825 days (Apple's systems refuse a longer one even when trusted by hand). A browser warns about a self-signed certificate until the computer trusts it: by group policy across the site, or by hand (open the downloaded `.crt`, Install Certificate, Local Machine, Trusted Root Certification Authorities) - and the fingerprint shown there is compared with the one on the HTTPS card before it is trusted. A workspace whose certificate files are missing, unreadable or not a pair does not start (`gea doctor` says BLOCK): a site that turned HTTPS on is never served in plain text by surprise. A site already running a reverse proxy can keep TLS there instead (`--behind-proxy`, `gea help upkeep`).
|
|
10
|
+
|
|
11
|
+
**What it writes**: the setting in `workspace.json` (`control.tls`: the mode, the two files, when and by whom) with the audit lines `tls.site_certificate`, `tls.self_signed` and `tls.off`; for a self-signed certificate, `keys\tls\self_signed_certificate.pem` and `keys\tls\self_signed_key.pem`. The key is never in a report, an export, an evidence pack or a dossier, and the service never sends it; the backup copies it with the rest of the workspace, like the dossier signing key. The alert `tls.expiring` when the certificate is in its last 30 days or has expired, checked every 15 minutes, and cleared when a new one is in place.
|
|
12
|
+
|
|
13
|
+
**The number to check**: the SHA-256 fingerprint on the HTTPS card against the one the browser shows for the page (the padlock -> the certificate) - the same, or it is not this machine answering; the days left.
|
|
14
|
+
|
|
15
|
+
**What this page will not call a measurement**: a padlock is not proof of who is answering unless the computer trusts the certificate for a reason - the site's authority, or a fingerprint someone compared. A self-signed certificate encrypts; it does not identify until it is trusted that way.
|
|
16
|
+
|
|
17
|
+
**Sources**: RFC 8446 (TLS 1.3) and RFC 5246 (TLS 1.2); RFC 5280 (the X.509 certificate and its extensions); RFC 8017 (RSA, PKCS #1 v1.5 signatures); RFC 6797 (Strict-Transport-Security); Apple, "Requirements for trusted certificates in iOS 13 and macOS 10.15" (HT210176: a server certificate valid for at most 825 days, with its names as subject alternative names and the server-authentication key usage).
|
|
18
|
+
|
|
19
|
+
Related: `gea help upkeep`, `gea help alerts`, `gea help doctor`.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Keeping a site running for years: logs, jobs, sessions, the proxy, the load it can carry.
|
|
4
4
|
|
|
5
|
-
**The command**: `gea housekeeping --workspace C:\site` (a dry run that lists what it would do) and `--apply`; schedule it daily from Administration (preset "housekeeping"). `gea loadtest --patches 16 --seconds 60 --with-service` before a site goes live. Administration -> Live sessions lists who is signed in from where and revokes one or all of a user's sessions; "Your preferences" has "Sign out everywhere else". `deploy/README.md`, `Caddyfile` and `nginx-gea.conf` put TLS in front; start the service with `--behind-proxy`.
|
|
5
|
+
**The command**: `gea housekeeping --workspace C:\site` (a dry run that lists what it would do) and `--apply`; schedule it daily from Administration (preset "housekeeping"). `gea loadtest --patches 16 --seconds 60 --with-service` before a site goes live. Administration -> Live sessions lists who is signed in from where and revokes one or all of a user's sessions; "Your preferences" has "Sign out everywhere else". HTTPS without a proxy: `gea help https` (the service speaks TLS itself with the site's certificate or a self-signed one). Or, where the site already runs one, `deploy/README.md`, `Caddyfile` and `nginx-gea.conf` put TLS in a reverse proxy in front; start the service with `--behind-proxy`.
|
|
6
6
|
|
|
7
7
|
**What it writes**: housekeeping segments the append-only logs (`audit.jsonl.<stamp>`, `alarm_events.jsonl.<stamp>`; the fresh alarm log starts at the carried PROCESSED watermark so nothing is re-processed), removes finished job folders older than 30 days beyond the newest 500, removes live recordings older than 90 days except the newest stream file per well, and audits the run. The load test leaves nothing behind.
|
|
8
8
|
|
|
@@ -10,4 +10,4 @@
|
|
|
10
10
|
|
|
11
11
|
**What this page will not call a measurement**: a segmented log is not a deleted one - every segment is the record. Deletion is only ever of finished job folders and old recordings whose samples are already folded into the stream.
|
|
12
12
|
|
|
13
|
-
Related: `gea help site`, `gea help patches`.
|
|
13
|
+
Related: `gea help site`, `gea help patches`, `gea help https`.
|
|
@@ -84,6 +84,7 @@ INDEX: List[tuple] = [
|
|
|
84
84
|
('doctor', 'Which code is running', 'gea doctor and the serve start-up gate'),
|
|
85
85
|
('audit-update', 'Audit / Update', 'the whole audit log with filters, the program against PyPI, every report against its source'),
|
|
86
86
|
('soak', 'The soak', 'the patches through link outages for hours: reconnection, resume, loss by protocol, the page answering - measured, not modelled'),
|
|
87
|
+
('https', 'HTTPS', "the control panel over HTTPS on the site network: the site's own certificate or a self-signed one made here, the names it carries, handing it out, renewal and the expiry alert"),
|
|
87
88
|
('upkeep', 'Upkeep', 'housekeeping, sessions, the TLS proxy, the load test'),
|
|
88
89
|
]
|
|
89
90
|
# the four lines every page must carry, in this order
|
|
@@ -68,7 +68,7 @@ EXAMPLE = {
|
|
|
68
68
|
}
|
|
69
69
|
EVENTS = ('alarm.activated', 'alarm.shelved', 'job.failed', 'patch.down', 'patch.up', 'approval.pending', 'file.imported', 'test',
|
|
70
70
|
'seismic.protocol', 'seismic.event', 'seismic.watch_down', 'seismic.watch_up', 'motor.due', 'penetrator.outside', 'dossier.changed',
|
|
71
|
-
'condition.cleared', 'pad.summary', 'invoice.differs', 'frac.hit')
|
|
71
|
+
'condition.cleared', 'pad.summary', 'invoice.differs', 'frac.hit', 'tls.expiring')
|
|
72
72
|
_LOCK = threading.Lock()
|
|
73
73
|
|
|
74
74
|
|
|
@@ -185,6 +185,8 @@ def _subject(ev: dict) -> str:
|
|
|
185
185
|
return f"[GEA] the workspace no longer matches dossier {ev.get('dossier_id', '')}"
|
|
186
186
|
if k == 'invoice.differs':
|
|
187
187
|
return f"[GEA] the invoice for {w} differs from the reports and the record: {ev.get('detail', '')}"
|
|
188
|
+
if k == 'tls.expiring':
|
|
189
|
+
return f"[GEA] HTTPS certificate: {ev.get('detail', '')}"
|
|
188
190
|
if k == 'frac.hit':
|
|
189
191
|
return f"[GEA] frac hit: stage {ev.get('stage', '')} of {w} on {ev.get('monitor', '')} - {ev.get('detail', '')}"
|
|
190
192
|
if k == 'pad.summary':
|